mirror of
https://github.com/JustArchiNET/ArchiSteamFarm.git
synced 2025-12-25 18:56:49 +00:00
Compare commits
130 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c6c63a7928 | ||
|
|
8ef60a6316 | ||
|
|
82aebaf457 | ||
|
|
3c9ab82b7d | ||
|
|
5d0a3f5ebe | ||
|
|
9d5ccbca27 | ||
|
|
90af55e6f4 | ||
|
|
a11b2025e9 | ||
|
|
67a3b5a745 | ||
|
|
42079006a0 | ||
|
|
efb4aa70f4 | ||
|
|
d4684d123a | ||
|
|
0d38c2b1f4 | ||
|
|
4938bcf673 | ||
|
|
33eba37ed2 | ||
|
|
602d22fa9c | ||
|
|
795757ab0a | ||
|
|
5d69059e0b | ||
|
|
89ef81c5a8 | ||
|
|
3bf60d2762 | ||
|
|
d9c5f7e044 | ||
|
|
1d8b01e166 | ||
|
|
45cbb59144 | ||
|
|
0c5a05ab1c | ||
|
|
a81d02bd46 | ||
|
|
3191ef4f4d | ||
|
|
5c6bd0eb6f | ||
|
|
6b4634746a | ||
|
|
cdd08ad14b | ||
|
|
b64e630cbe | ||
|
|
197539f353 | ||
|
|
bf89ba7638 | ||
|
|
9f4a6e6e67 | ||
|
|
9718245793 | ||
|
|
9af542597c | ||
|
|
6391a5f79c | ||
|
|
20c0ba28ef | ||
|
|
f01740a3db | ||
|
|
a89c6e630c | ||
|
|
0b37323a5f | ||
|
|
14799c2733 | ||
|
|
031386ffb9 | ||
|
|
b561c3fc17 | ||
|
|
644f95e96c | ||
|
|
864c909760 | ||
|
|
aacf491a0c | ||
|
|
5ce219eb6e | ||
|
|
f1578a5174 | ||
|
|
1d7dbe3791 | ||
|
|
5c121bee75 | ||
|
|
6be0cff505 | ||
|
|
6281e258da | ||
|
|
68110f2038 | ||
|
|
f423f35c62 | ||
|
|
56246d3853 | ||
|
|
f6cbd67206 | ||
|
|
720214016b | ||
|
|
55d7ccd28b | ||
|
|
6d1ea0b20c | ||
|
|
40e8d85359 | ||
|
|
e397b7f7cc | ||
|
|
4090b40ac7 | ||
|
|
05df0ee725 | ||
|
|
eaf56d0221 | ||
|
|
1fc9b96f83 | ||
|
|
462020f842 | ||
|
|
b4e8e24921 | ||
|
|
84129da691 | ||
|
|
141673409f | ||
|
|
2ad10a5946 | ||
|
|
495b7594f9 | ||
|
|
1727492e07 | ||
|
|
82e38d78eb | ||
|
|
55af650da5 | ||
|
|
a062e98f2d | ||
|
|
8b8671c679 | ||
|
|
5e4f6a7926 | ||
|
|
57b2c8f1cf | ||
|
|
02e7f2144f | ||
|
|
c13fd10bd8 | ||
|
|
7fe0b4499b | ||
|
|
94160bdc42 | ||
|
|
c01c0f7cd1 | ||
|
|
65b06ae3b9 | ||
|
|
5d443f3ed2 | ||
|
|
6a6c903d7d | ||
|
|
52f5ef2a39 | ||
|
|
b92a4ea505 | ||
|
|
8ae9db3a34 | ||
|
|
388c72052c | ||
|
|
e965899395 | ||
|
|
5605e04f0a | ||
|
|
953103719c | ||
|
|
ea53fd0a87 | ||
|
|
f6770ea1c9 | ||
|
|
561b2a3566 | ||
|
|
d24e6d0302 | ||
|
|
98491a4562 | ||
|
|
08d7b9deb0 | ||
|
|
b904b1062f | ||
|
|
ad187c0d88 | ||
|
|
6a035f4832 | ||
|
|
021e8d2ad9 | ||
|
|
61609574d0 | ||
|
|
7b7f1518a6 | ||
|
|
6aa5a633e4 | ||
|
|
6f80ee9faa | ||
|
|
99e8df318c | ||
|
|
2ea334c62e | ||
|
|
27d0b7427a | ||
|
|
62c20c331e | ||
|
|
51d59f0f66 | ||
|
|
ab90c9dc68 | ||
|
|
f7c8b871b3 | ||
|
|
678b32f318 | ||
|
|
62221fd6b8 | ||
|
|
f932be1395 | ||
|
|
d8fd1035c3 | ||
|
|
09e8a52811 | ||
|
|
0b1032199b | ||
|
|
3e5dfb3174 | ||
|
|
4ada8595bd | ||
|
|
bc64c43748 | ||
|
|
f4f7935d4c | ||
|
|
aacc0a1720 | ||
|
|
e5ee909b96 | ||
|
|
f3f444d0bd | ||
|
|
43d18b6d49 | ||
|
|
f24be67c8c | ||
|
|
6ce4f2941b |
26
.travis.yml
26
.travis.yml
@@ -16,13 +16,14 @@ branches:
|
||||
mono: none
|
||||
|
||||
# ASF requires .NET Core 2.0+
|
||||
# TODO: We should target stable 2.0.0 once it's released
|
||||
dotnet: 2.0.0-preview2-006497
|
||||
dotnet: 2.0.0
|
||||
|
||||
env:
|
||||
global:
|
||||
- CONFIGURATION: Release
|
||||
- DOTNET_CLI_TELEMETRY_OPTOUT: 1
|
||||
- DOTNET_SKIP_FIRST_TIME_EXPERIENCE: 1
|
||||
- RUNTIMES="generic win-x64 linux-x64 linux-arm osx-x64" # https://github.com/travis-ci/travis-ci/issues/1444
|
||||
|
||||
before_script: dotnet restore
|
||||
|
||||
@@ -30,20 +31,23 @@ script:
|
||||
- |
|
||||
set -e
|
||||
|
||||
RUNTIMES="generic win-x64 linux-x64 linux-arm osx-x64"
|
||||
dotnet build -c "$CONFIGURATION" -o 'out/source' --no-restore /nologo
|
||||
dotnet test ArchiSteamFarm.Tests -c "$CONFIGURATION" -o 'out/source' --no-build --no-restore
|
||||
|
||||
dotnet build -c Release
|
||||
dotnet test -c Release --no-build --no-restore ArchiSteamFarm.Tests
|
||||
|
||||
for RUNTIME in $RUNTIMES; do
|
||||
if [ "$RUNTIME" = "generic" ]; then
|
||||
dotnet publish -c Release -o "out/${RUNTIME}"
|
||||
publish() {
|
||||
if [ "$1" = 'generic' ]; then
|
||||
dotnet publish ArchiSteamFarm -c "$CONFIGURATION" -o "out/${1}" --no-restore /nologo
|
||||
else
|
||||
dotnet publish -c Release -r "$RUNTIME" -o "out/${RUNTIME}"
|
||||
dotnet publish ArchiSteamFarm -c "$CONFIGURATION" -o "out/${1}" -r "$1" --no-restore /nologo
|
||||
fi
|
||||
|
||||
echo "$RUNTIME" > "ArchiSteamFarm/out/${RUNTIME}/ArchiSteamFarm.version"
|
||||
echo "$1" > "ArchiSteamFarm/out/${1}/ArchiSteamFarm.version"
|
||||
}
|
||||
|
||||
for RUNTIME in $RUNTIMES; do
|
||||
publish "$RUNTIME" &
|
||||
done
|
||||
wait
|
||||
|
||||
matrix:
|
||||
# We can use fast finish, as we don't need to wait for allow_failures builds to mark build as success
|
||||
|
||||
@@ -24,9 +24,9 @@
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="15.5.0-preview-20170727-01" />
|
||||
<PackageReference Include="MSTest.TestAdapter" Version="1.2.0-beta" />
|
||||
<PackageReference Include="MSTest.TestFramework" Version="1.2.0-beta" />
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="15.5.0-preview-20170810-02" />
|
||||
<PackageReference Include="MSTest.TestAdapter" Version="1.2.0-beta3" />
|
||||
<PackageReference Include="MSTest.TestFramework" Version="1.2.0-beta3" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
||||
@@ -41,7 +41,7 @@ namespace ArchiSteamFarm {
|
||||
|
||||
internal static readonly ArchiLogger ArchiLogger = new ArchiLogger(SharedInfo.ASF);
|
||||
|
||||
private static readonly ConcurrentDictionary<Bot, DateTime> LastWriteTimes = new ConcurrentDictionary<Bot, DateTime>();
|
||||
private static readonly ConcurrentDictionary<string, DateTime> LastWriteTimes = new ConcurrentDictionary<string, DateTime>();
|
||||
|
||||
private static Timer AutoUpdatesTimer;
|
||||
private static FileSystemWatcher FileSystemWatcher;
|
||||
@@ -210,6 +210,13 @@ namespace ArchiSteamFarm {
|
||||
return;
|
||||
}
|
||||
|
||||
if (IsUnixVersion(version)) {
|
||||
string executable = Path.Combine(targetDirectory, SharedInfo.AssemblyName);
|
||||
if (File.Exists(executable)) {
|
||||
OS.UnixSetFileAccessExecutable(executable);
|
||||
}
|
||||
}
|
||||
|
||||
ArchiLogger.LogGenericInfo(Strings.UpdateFinished);
|
||||
await RestartOrExit().ConfigureAwait(false);
|
||||
}
|
||||
@@ -268,6 +275,22 @@ namespace ArchiSteamFarm {
|
||||
Bot.RegisterBot(botName);
|
||||
}
|
||||
|
||||
private static bool IsUnixVersion(string version) {
|
||||
if (string.IsNullOrEmpty(version)) {
|
||||
ArchiLogger.LogNullError(nameof(version));
|
||||
return false;
|
||||
}
|
||||
|
||||
switch (version) {
|
||||
case "linux-arm":
|
||||
case "linux-x64":
|
||||
case "osx-x64":
|
||||
return true;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private static bool IsValidBotName(string botName) {
|
||||
if (string.IsNullOrEmpty(botName)) {
|
||||
ArchiLogger.LogNullError(nameof(botName));
|
||||
@@ -318,6 +341,32 @@ namespace ArchiSteamFarm {
|
||||
return;
|
||||
}
|
||||
|
||||
DateTime lastWriteTime = File.GetLastWriteTime(e.FullPath);
|
||||
|
||||
if (LastWriteTimes.TryGetValue(botName, out DateTime savedLastWriteTime)) {
|
||||
if (savedLastWriteTime >= lastWriteTime) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
LastWriteTimes[botName] = lastWriteTime;
|
||||
|
||||
// It's entirely possible that some process is still accessing our file, allow at least a second before trying to read it
|
||||
await Task.Delay(1000).ConfigureAwait(false);
|
||||
|
||||
// It's also possible that we got some other event in the meantime
|
||||
if (LastWriteTimes.TryGetValue(botName, out savedLastWriteTime)) {
|
||||
if (lastWriteTime != savedLastWriteTime) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (LastWriteTimes.TryRemove(botName, out savedLastWriteTime)) {
|
||||
if (lastWriteTime != savedLastWriteTime) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (botName.Equals(SharedInfo.ASF)) {
|
||||
ArchiLogger.LogGenericInfo(Strings.GlobalConfigChanged);
|
||||
await RestartOrExit().ConfigureAwait(false);
|
||||
@@ -328,32 +377,6 @@ namespace ArchiSteamFarm {
|
||||
return;
|
||||
}
|
||||
|
||||
DateTime lastWriteTime = File.GetLastWriteTime(e.FullPath);
|
||||
|
||||
if (LastWriteTimes.TryGetValue(bot, out DateTime savedLastWriteTime)) {
|
||||
if (savedLastWriteTime >= lastWriteTime) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
LastWriteTimes[bot] = lastWriteTime;
|
||||
|
||||
// It's entirely possible that some process is still accessing our file, allow at least a second before trying to read it
|
||||
await Task.Delay(1000).ConfigureAwait(false);
|
||||
|
||||
// It's also possible that we got some other event in the meantime
|
||||
if (LastWriteTimes.TryGetValue(bot, out savedLastWriteTime)) {
|
||||
if (lastWriteTime != savedLastWriteTime) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (LastWriteTimes.TryRemove(bot, out savedLastWriteTime)) {
|
||||
if (lastWriteTime != savedLastWriteTime) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
await bot.OnNewConfigLoaded(new BotConfigEventArgs(BotConfig.Load(e.FullPath))).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
@@ -363,18 +386,32 @@ namespace ArchiSteamFarm {
|
||||
return;
|
||||
}
|
||||
|
||||
string botName = Path.GetFileNameWithoutExtension(e.Name);
|
||||
await OnCreatedFile(e.Name, e.FullPath).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
private static async Task<bool> OnCreatedFile(string name, string fullPath) {
|
||||
if (string.IsNullOrEmpty(name) || string.IsNullOrEmpty(fullPath)) {
|
||||
ArchiLogger.LogNullError(nameof(name) + " || " + nameof(fullPath));
|
||||
return false;
|
||||
}
|
||||
|
||||
string botName = Path.GetFileNameWithoutExtension(name);
|
||||
if (string.IsNullOrEmpty(botName) || (botName[0] == '.')) {
|
||||
return;
|
||||
return false;
|
||||
}
|
||||
|
||||
if (botName.Equals(SharedInfo.ASF)) {
|
||||
ArchiLogger.LogGenericInfo(Strings.GlobalConfigChanged);
|
||||
await RestartOrExit().ConfigureAwait(false);
|
||||
return;
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!IsValidBotName(botName)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
await CreateBot(botName).ConfigureAwait(false);
|
||||
return true;
|
||||
}
|
||||
|
||||
private static async void OnDeleted(object sender, FileSystemEventArgs e) {
|
||||
@@ -383,27 +420,42 @@ namespace ArchiSteamFarm {
|
||||
return;
|
||||
}
|
||||
|
||||
string botName = Path.GetFileNameWithoutExtension(e.Name);
|
||||
await OnDeletedFile(e.Name, e.FullPath).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
private static async Task<bool> OnDeletedFile(string name, string fullPath) {
|
||||
if (string.IsNullOrEmpty(name) || string.IsNullOrEmpty(fullPath)) {
|
||||
ArchiLogger.LogNullError(nameof(name) + " || " + nameof(fullPath));
|
||||
return false;
|
||||
}
|
||||
|
||||
string botName = Path.GetFileNameWithoutExtension(name);
|
||||
if (string.IsNullOrEmpty(botName)) {
|
||||
return;
|
||||
return false;
|
||||
}
|
||||
|
||||
if (botName.Equals(SharedInfo.ASF)) {
|
||||
if (File.Exists(fullPath)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Some editors might decide to delete file and re-create it in order to modify it
|
||||
// If that's the case, we wait for maximum of 5 seconds before shutting down
|
||||
await Task.Delay(5000).ConfigureAwait(false);
|
||||
if (File.Exists(e.FullPath)) {
|
||||
return;
|
||||
if (File.Exists(fullPath)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
ArchiLogger.LogGenericError(Strings.ErrorGlobalConfigRemoved);
|
||||
await Program.Exit(1).ConfigureAwait(false);
|
||||
return;
|
||||
return false;
|
||||
}
|
||||
|
||||
if (Bot.Bots.TryGetValue(botName, out Bot bot)) {
|
||||
await bot.OnNewConfigLoaded(new BotConfigEventArgs()).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private static async void OnRenamed(object sender, RenamedEventArgs e) {
|
||||
@@ -412,27 +464,19 @@ namespace ArchiSteamFarm {
|
||||
return;
|
||||
}
|
||||
|
||||
string oldBotName = Path.GetFileNameWithoutExtension(e.OldName);
|
||||
if (string.IsNullOrEmpty(oldBotName)) {
|
||||
return;
|
||||
// We must remember to handle all three cases here - *.any to *.json, *.json to *.any and *.json to *.json
|
||||
|
||||
string oldFileExtension = Path.GetExtension(e.OldName);
|
||||
if (!string.IsNullOrEmpty(oldFileExtension) && oldFileExtension.Equals(".json")) {
|
||||
if (!await OnDeletedFile(e.OldName, e.OldFullPath).ConfigureAwait(false)) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (oldBotName.Equals(SharedInfo.ASF)) {
|
||||
ArchiLogger.LogGenericError(Strings.ErrorGlobalConfigRemoved);
|
||||
await Program.Exit(1).ConfigureAwait(false);
|
||||
return;
|
||||
string newFileExtension = Path.GetExtension(e.Name);
|
||||
if (!string.IsNullOrEmpty(newFileExtension) && newFileExtension.Equals(".json")) {
|
||||
await OnCreatedFile(e.Name, e.FullPath).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
if (Bot.Bots.TryGetValue(oldBotName, out Bot bot)) {
|
||||
await bot.OnNewConfigLoaded(new BotConfigEventArgs()).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
string newBotName = Path.GetFileNameWithoutExtension(e.Name);
|
||||
if (string.IsNullOrEmpty(newBotName) || (newBotName[0] == '.')) {
|
||||
return;
|
||||
}
|
||||
|
||||
await CreateBot(newBotName).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
private static async Task RestartOrExit() {
|
||||
@@ -459,7 +503,14 @@ namespace ArchiSteamFarm {
|
||||
// Move top-level runtime in-use files to other directory
|
||||
// We must do it in order to not crash at later stage - all libraries/executables must keep original names
|
||||
foreach (string file in Directory.EnumerateFiles(targetDirectory)) {
|
||||
string target = Path.Combine(backupDirectory, Path.GetFileName(file));
|
||||
string fileName = Path.GetFileName(file);
|
||||
switch (fileName) {
|
||||
// Files that we want to keep in original directory
|
||||
case "NLog.config":
|
||||
continue;
|
||||
}
|
||||
|
||||
string target = Path.Combine(backupDirectory, fileName);
|
||||
File.Move(file, target);
|
||||
}
|
||||
|
||||
|
||||
@@ -96,7 +96,7 @@ namespace ArchiSteamFarm {
|
||||
Client.Send(request);
|
||||
}
|
||||
|
||||
internal void PlayGames(IEnumerable<uint> gameIDs, string gameName = null) {
|
||||
internal async Task PlayGames(IEnumerable<uint> gameIDs, string gameName = null) {
|
||||
if (gameIDs == null) {
|
||||
ArchiLogger.LogNullError(nameof(gameIDs));
|
||||
return;
|
||||
@@ -109,6 +109,11 @@ namespace ArchiSteamFarm {
|
||||
ClientMsgProtobuf<CMsgClientGamesPlayed> request = new ClientMsgProtobuf<CMsgClientGamesPlayed>(EMsg.ClientGamesPlayed);
|
||||
|
||||
if (!string.IsNullOrEmpty(gameName)) {
|
||||
// If we have custom name to display, we must workaround the Steam network fuckup and send request on clean non-playing session
|
||||
// This ensures that custom name will in fact display properly
|
||||
Client.Send(request);
|
||||
await Task.Delay(Bot.CallbackSleep).ConfigureAwait(false);
|
||||
|
||||
request.Body.games_played.Add(new CMsgClientGamesPlayed.GamePlayed {
|
||||
game_extra_info = gameName,
|
||||
game_id = new GameID {
|
||||
|
||||
@@ -3,8 +3,8 @@
|
||||
<PropertyGroup>
|
||||
<OutputType>Exe</OutputType>
|
||||
<TargetFramework>netcoreapp2.0</TargetFramework>
|
||||
<AssemblyVersion>3.0.0.7</AssemblyVersion>
|
||||
<FileVersion>3.0.0.7</FileVersion>
|
||||
<AssemblyVersion>3.0.2.3</AssemblyVersion>
|
||||
<FileVersion>3.0.2.3</FileVersion>
|
||||
<LangVersion>latest</LangVersion>
|
||||
<ErrorReport>none</ErrorReport>
|
||||
<ApplicationIcon>ASF.ico</ApplicationIcon>
|
||||
@@ -18,20 +18,27 @@
|
||||
<RepositoryUrl>https://github.com/JustArchi/ArchiSteamFarm.git</RepositoryUrl>
|
||||
<PackageIconUrl>https://github.com/JustArchi/ArchiSteamFarm/raw/master/resources/ASF.ico</PackageIconUrl>
|
||||
<RepositoryType>Git</RepositoryType>
|
||||
<NoWarn />
|
||||
<ServerGarbageCollection>false</ServerGarbageCollection>
|
||||
<ConcurrentGarbageCollection>true</ConcurrentGarbageCollection>
|
||||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|AnyCPU'">
|
||||
<DebugType>none</DebugType>
|
||||
<DebugSymbols>false</DebugSymbols>
|
||||
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
|
||||
<WarningsAsErrors />
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="HtmlAgilityPack" Version="1.5.2-beta3" />
|
||||
<PackageReference Include="HtmlAgilityPack" Version="1.5.5" />
|
||||
<PackageReference Include="Humanizer" Version="2.2.0" />
|
||||
<PackageReference Include="Newtonsoft.Json" Version="10.0.3" />
|
||||
<PackageReference Include="NLog" Version="5.0.0-beta09" />
|
||||
<PackageReference Include="SteamKit2" Version="2.0.0-Alpha5" />
|
||||
<PackageReference Include="System.Security.Cryptography.ProtectedData" Version="4.4.0-preview2-25405-01" />
|
||||
<PackageReference Include="NLog" Version="5.0.0-beta10" />
|
||||
<PackageReference Include="SteamKit2" Version="2.0.0-Alpha8" />
|
||||
<PackageReference Include="System.Net.NameResolution" Version="4.3.0" />
|
||||
<PackageReference Include="System.Security.Cryptography.ProtectedData" Version="4.4.0" />
|
||||
<PackageReference Include="System.Threading.ThreadPool" Version="4.3.0" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
||||
@@ -58,15 +58,13 @@ namespace ArchiSteamFarm {
|
||||
|
||||
private const string SteamStoreURL = "http://" + SteamStoreHost;
|
||||
|
||||
private static readonly SemaphoreSlim InventorySemaphore = new SemaphoreSlim(1);
|
||||
private static readonly SemaphoreSlim InventorySemaphore = new SemaphoreSlim(1, 1);
|
||||
|
||||
private static int Timeout = GlobalConfig.DefaultConnectionTimeout * 1000; // This must be int type
|
||||
|
||||
private readonly SemaphoreSlim ApiKeySemaphore = new SemaphoreSlim(1);
|
||||
private readonly SemaphoreSlim ApiKeySemaphore = new SemaphoreSlim(1, 1);
|
||||
private readonly Bot Bot;
|
||||
private readonly SemaphoreSlim PublicInventorySemaphore = new SemaphoreSlim(1);
|
||||
private readonly SemaphoreSlim SessionSemaphore = new SemaphoreSlim(1);
|
||||
private readonly SemaphoreSlim TradeTokenSemaphore = new SemaphoreSlim(1);
|
||||
private readonly SemaphoreSlim PublicInventorySemaphore = new SemaphoreSlim(1, 1);
|
||||
private readonly SemaphoreSlim SessionSemaphore = new SemaphoreSlim(1, 1);
|
||||
private readonly SemaphoreSlim TradeTokenSemaphore = new SemaphoreSlim(1, 1);
|
||||
private readonly WebBrowser WebBrowser;
|
||||
|
||||
private string CachedApiKey;
|
||||
@@ -86,6 +84,7 @@ namespace ArchiSteamFarm {
|
||||
PublicInventorySemaphore.Dispose();
|
||||
SessionSemaphore.Dispose();
|
||||
TradeTokenSemaphore.Dispose();
|
||||
WebBrowser.Dispose();
|
||||
}
|
||||
|
||||
internal async Task<bool> AcceptTradeOffer(ulong tradeID) {
|
||||
@@ -181,21 +180,19 @@ namespace ArchiSteamFarm {
|
||||
|
||||
KeyValue response = null;
|
||||
for (byte i = 0; (i < WebBrowser.MaxTries) && (response == null); i++) {
|
||||
await Task.Run(() => {
|
||||
using (dynamic iEconService = WebAPI.GetInterface(IEconService, steamApiKey)) {
|
||||
iEconService.Timeout = Timeout;
|
||||
using (dynamic iEconService = WebAPI.GetAsyncInterface(IEconService, steamApiKey)) {
|
||||
iEconService.Timeout = WebBrowser.Timeout;
|
||||
|
||||
try {
|
||||
response = iEconService.DeclineTradeOffer(
|
||||
tradeofferid: tradeID.ToString(),
|
||||
method: WebRequestMethods.Http.Post,
|
||||
secure: true
|
||||
);
|
||||
} catch (Exception e) {
|
||||
Bot.ArchiLogger.LogGenericWarningException(e);
|
||||
}
|
||||
try {
|
||||
response = await iEconService.DeclineTradeOffer(
|
||||
tradeofferid: tradeID.ToString(),
|
||||
method: WebRequestMethods.Http.Post,
|
||||
secure: true
|
||||
);
|
||||
} catch (Exception e) {
|
||||
Bot.ArchiLogger.LogGenericWarningException(e);
|
||||
}
|
||||
}).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
|
||||
if (response == null) {
|
||||
@@ -232,23 +229,21 @@ namespace ArchiSteamFarm {
|
||||
|
||||
KeyValue response = null;
|
||||
for (byte i = 0; (i < WebBrowser.MaxTries) && (response == null); i++) {
|
||||
await Task.Run(() => {
|
||||
using (dynamic iEconService = WebAPI.GetInterface(IEconService, steamApiKey)) {
|
||||
iEconService.Timeout = Timeout;
|
||||
using (dynamic iEconService = WebAPI.GetAsyncInterface(IEconService, steamApiKey)) {
|
||||
iEconService.Timeout = WebBrowser.Timeout;
|
||||
|
||||
try {
|
||||
response = iEconService.GetTradeOffers(
|
||||
active_only: 1,
|
||||
get_descriptions: 1,
|
||||
get_received_offers: 1,
|
||||
secure: true,
|
||||
time_historical_cutoff: uint.MaxValue
|
||||
);
|
||||
} catch (Exception e) {
|
||||
Bot.ArchiLogger.LogGenericWarningException(e);
|
||||
}
|
||||
try {
|
||||
response = await iEconService.GetTradeOffers(
|
||||
active_only: 1,
|
||||
get_descriptions: 1,
|
||||
get_received_offers: 1,
|
||||
secure: true,
|
||||
time_historical_cutoff: uint.MaxValue
|
||||
);
|
||||
} catch (Exception e) {
|
||||
Bot.ArchiLogger.LogGenericWarningException(e);
|
||||
}
|
||||
}).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
|
||||
if (response == null) {
|
||||
@@ -482,7 +477,7 @@ namespace ArchiSteamFarm {
|
||||
}
|
||||
|
||||
[SuppressMessage("ReSharper", "FunctionComplexityOverflow")]
|
||||
internal async Task<HashSet<Steam.Item>> GetMySteamInventory(bool tradable, HashSet<Steam.Item.EType> wantedTypes, HashSet<uint> wantedRealAppIDs = null) {
|
||||
internal async Task<HashSet<Steam.Item>> GetMySteamInventory(bool trading, HashSet<Steam.Item.EType> wantedTypes, HashSet<uint> wantedRealAppIDs = null) {
|
||||
if ((wantedTypes == null) || (wantedTypes.Count == 0)) {
|
||||
Bot.ArchiLogger.LogNullError(nameof(wantedTypes));
|
||||
return null;
|
||||
@@ -494,7 +489,7 @@ namespace ArchiSteamFarm {
|
||||
|
||||
HashSet<Steam.Item> result = new HashSet<Steam.Item>();
|
||||
|
||||
string request = SteamCommunityURL + "/my/inventory/json/" + Steam.Item.SteamAppID + "/" + Steam.Item.SteamCommunityContextID + "?l=english&trading=" + (tradable ? "1" : "0") + "&start=";
|
||||
string request = SteamCommunityURL + "/my/inventory/json/" + Steam.Item.SteamAppID + "/" + Steam.Item.SteamCommunityContextID + "?l=english" + (trading ? "&trading=1" : "") + "&start=";
|
||||
uint currentPage = 0;
|
||||
|
||||
await InventorySemaphore.WaitAsync().ConfigureAwait(false);
|
||||
@@ -629,21 +624,19 @@ namespace ArchiSteamFarm {
|
||||
|
||||
KeyValue response = null;
|
||||
for (byte i = 0; (i < WebBrowser.MaxTries) && (response == null); i++) {
|
||||
await Task.Run(() => {
|
||||
using (dynamic iPlayerService = WebAPI.GetInterface(IPlayerService, steamApiKey)) {
|
||||
iPlayerService.Timeout = Timeout;
|
||||
using (dynamic iPlayerService = WebAPI.GetAsyncInterface(IPlayerService, steamApiKey)) {
|
||||
iPlayerService.Timeout = WebBrowser.Timeout;
|
||||
|
||||
try {
|
||||
response = iPlayerService.GetOwnedGames(
|
||||
steamid: steamID,
|
||||
include_appinfo: 1,
|
||||
secure: true
|
||||
);
|
||||
} catch (Exception e) {
|
||||
Bot.ArchiLogger.LogGenericWarningException(e);
|
||||
}
|
||||
try {
|
||||
response = await iPlayerService.GetOwnedGames(
|
||||
steamid: steamID,
|
||||
include_appinfo: 1,
|
||||
secure: true
|
||||
);
|
||||
} catch (Exception e) {
|
||||
Bot.ArchiLogger.LogGenericWarningException(e);
|
||||
}
|
||||
}).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
|
||||
if (response == null) {
|
||||
@@ -668,20 +661,18 @@ namespace ArchiSteamFarm {
|
||||
internal async Task<uint> GetServerTime() {
|
||||
KeyValue response = null;
|
||||
for (byte i = 0; (i < WebBrowser.MaxTries) && (response == null); i++) {
|
||||
await Task.Run(() => {
|
||||
using (dynamic iTwoFactorService = WebAPI.GetInterface(ITwoFactorService)) {
|
||||
iTwoFactorService.Timeout = Timeout;
|
||||
using (dynamic iTwoFactorService = WebAPI.GetAsyncInterface(ITwoFactorService)) {
|
||||
iTwoFactorService.Timeout = WebBrowser.Timeout;
|
||||
|
||||
try {
|
||||
response = iTwoFactorService.QueryTime(
|
||||
method: WebRequestMethods.Http.Post,
|
||||
secure: true
|
||||
);
|
||||
} catch (Exception e) {
|
||||
Bot.ArchiLogger.LogGenericWarningException(e);
|
||||
}
|
||||
try {
|
||||
response = await iTwoFactorService.QueryTime(
|
||||
method: WebRequestMethods.Http.Post,
|
||||
secure: true
|
||||
);
|
||||
} catch (Exception e) {
|
||||
Bot.ArchiLogger.LogGenericWarningException(e);
|
||||
}
|
||||
}).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
|
||||
if (response != null) {
|
||||
@@ -866,8 +857,6 @@ namespace ArchiSteamFarm {
|
||||
|
||||
internal async Task<bool> HasValidApiKey() => !string.IsNullOrEmpty(await GetApiKey().ConfigureAwait(false));
|
||||
|
||||
internal static void Init() => Timeout = Program.GlobalConfig.ConnectionTimeout * 1000;
|
||||
|
||||
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)) {
|
||||
Bot.ArchiLogger.LogNullError(nameof(steamID) + " || " + nameof(universe) + " || " + nameof(webAPIUserNonce) + " || " + nameof(parentalPin));
|
||||
@@ -900,23 +889,21 @@ namespace ArchiSteamFarm {
|
||||
Bot.ArchiLogger.LogGenericInfo(string.Format(Strings.LoggingIn, ISteamUserAuth));
|
||||
|
||||
KeyValue authResult = null;
|
||||
await Task.Run(() => {
|
||||
using (dynamic iSteamUserAuth = WebAPI.GetInterface(ISteamUserAuth)) {
|
||||
iSteamUserAuth.Timeout = Timeout;
|
||||
using (dynamic iSteamUserAuth = WebAPI.GetAsyncInterface(ISteamUserAuth)) {
|
||||
iSteamUserAuth.Timeout = WebBrowser.Timeout;
|
||||
|
||||
try {
|
||||
authResult = iSteamUserAuth.AuthenticateUser(
|
||||
steamid: steamID,
|
||||
sessionkey: Encoding.ASCII.GetString(WebUtility.UrlEncodeToBytes(cryptedSessionKey, 0, cryptedSessionKey.Length)),
|
||||
encrypted_loginkey: Encoding.ASCII.GetString(WebUtility.UrlEncodeToBytes(cryptedLoginKey, 0, cryptedLoginKey.Length)),
|
||||
method: WebRequestMethods.Http.Post,
|
||||
secure: true
|
||||
);
|
||||
} catch (Exception e) {
|
||||
Bot.ArchiLogger.LogGenericWarningException(e);
|
||||
}
|
||||
try {
|
||||
authResult = await iSteamUserAuth.AuthenticateUser(
|
||||
steamid: steamID,
|
||||
sessionkey: Encoding.ASCII.GetString(WebUtility.UrlEncodeToBytes(cryptedSessionKey, 0, cryptedSessionKey.Length)),
|
||||
encrypted_loginkey: Encoding.ASCII.GetString(WebUtility.UrlEncodeToBytes(cryptedLoginKey, 0, cryptedLoginKey.Length)),
|
||||
method: WebRequestMethods.Http.Post,
|
||||
secure: true
|
||||
);
|
||||
} catch (Exception e) {
|
||||
Bot.ArchiLogger.LogGenericWarningException(e);
|
||||
}
|
||||
}).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
if (authResult == null) {
|
||||
return false;
|
||||
@@ -1178,7 +1165,7 @@ namespace ArchiSteamFarm {
|
||||
break;
|
||||
default:
|
||||
// We got an unhandled error, this should never happen
|
||||
Bot.ArchiLogger.LogGenericWarning(string.Format(Strings.WarningUnknownValuePleaseReport, nameof(result.Value.State), result.Value.State));
|
||||
Bot.ArchiLogger.LogGenericError(string.Format(Strings.WarningUnknownValuePleaseReport, nameof(result.Value.State), result.Value.State));
|
||||
break;
|
||||
}
|
||||
|
||||
|
||||
@@ -41,21 +41,20 @@ using SteamKit2.Discovery;
|
||||
|
||||
namespace ArchiSteamFarm {
|
||||
internal sealed class Bot : IDisposable {
|
||||
internal const ushort CallbackSleep = 500; // In miliseconds
|
||||
internal const byte MinPlayingBlockedTTL = 60; // Delay in seconds added when account was occupied during our disconnect, to not disconnect other Steam client session too soon
|
||||
|
||||
private const ushort CallbackSleep = 500; // In miliseconds
|
||||
private const byte FamilySharingInactivityMinutes = 5;
|
||||
private const byte LoginCooldownInMinutes = 25; // Captcha disappears after around 20 minutes, so we make it 25
|
||||
private const uint LoginID = GlobalConfig.DefaultIPCPort; // This must be the same for all ASF bots and all ASF processes
|
||||
private const ushort MaxSteamMessageLength = 2048;
|
||||
private const byte MaxTwoFactorCodeFailures = 3;
|
||||
private const byte MinHeartBeatTTL = GlobalConfig.DefaultConnectionTimeout; // Assume client is responsive for at least that amount of seconds
|
||||
private const byte PICSCooldownInMiliseconds = 200; // We might need to tune this further
|
||||
|
||||
internal static readonly ConcurrentDictionary<string, Bot> Bots = new ConcurrentDictionary<string, Bot>();
|
||||
|
||||
private static readonly SemaphoreSlim GiftsSemaphore = new SemaphoreSlim(1);
|
||||
private static readonly SemaphoreSlim LoginSemaphore = new SemaphoreSlim(1);
|
||||
private static readonly SemaphoreSlim GiftsSemaphore = new SemaphoreSlim(1, 1);
|
||||
private static readonly SemaphoreSlim LoginSemaphore = new SemaphoreSlim(1, 1);
|
||||
private static readonly SteamConfiguration SteamConfiguration = new SteamConfiguration();
|
||||
|
||||
internal readonly ArchiLogger ArchiLogger;
|
||||
@@ -74,16 +73,16 @@ namespace ArchiSteamFarm {
|
||||
private readonly BotDatabase BotDatabase;
|
||||
private readonly string BotName;
|
||||
private readonly CallbackManager CallbackManager;
|
||||
private readonly SemaphoreSlim CallbackSemaphore = new SemaphoreSlim(1);
|
||||
private readonly SemaphoreSlim CallbackSemaphore = new SemaphoreSlim(1, 1);
|
||||
|
||||
[JsonProperty]
|
||||
private readonly CardsFarmer CardsFarmer;
|
||||
|
||||
private readonly ConcurrentHashSet<ulong> HandledGifts = new ConcurrentHashSet<ulong>();
|
||||
private readonly Timer HeartBeatTimer;
|
||||
private readonly SemaphoreSlim InitializationSemaphore = new SemaphoreSlim(1);
|
||||
private readonly SemaphoreSlim LootingSemaphore = new SemaphoreSlim(1);
|
||||
private readonly SemaphoreSlim PICSSemaphore = new SemaphoreSlim(1);
|
||||
private readonly SemaphoreSlim InitializationSemaphore = new SemaphoreSlim(1, 1);
|
||||
private readonly SemaphoreSlim LootingSemaphore = new SemaphoreSlim(1, 1);
|
||||
private readonly SemaphoreSlim PICSSemaphore = new SemaphoreSlim(1, 1);
|
||||
private readonly Statistics Statistics;
|
||||
private readonly SteamApps SteamApps;
|
||||
private readonly SteamClient SteamClient;
|
||||
@@ -232,22 +231,24 @@ namespace ArchiSteamFarm {
|
||||
|
||||
public void Dispose() {
|
||||
// Those are objects that are always being created if constructor doesn't throw exception
|
||||
ArchiWebHandler.Dispose();
|
||||
CallbackSemaphore.Dispose();
|
||||
CardsFarmer.Dispose();
|
||||
HeartBeatTimer.Dispose();
|
||||
InitializationSemaphore.Dispose();
|
||||
LootingSemaphore.Dispose();
|
||||
PICSSemaphore.Dispose();
|
||||
Trading.Dispose();
|
||||
|
||||
// Those are objects that might be null and the check should be in-place
|
||||
ArchiWebHandler?.Dispose();
|
||||
BotDatabase?.Dispose();
|
||||
CardsFarmer?.Dispose();
|
||||
CardsFarmerResumeTimer?.Dispose();
|
||||
ConnectionFailureTimer?.Dispose();
|
||||
FamilySharingInactivityTimer?.Dispose();
|
||||
HeartBeatTimer?.Dispose();
|
||||
PlayingWasBlockedTimer?.Dispose();
|
||||
SendItemsTimer?.Dispose();
|
||||
Statistics?.Dispose();
|
||||
SteamSaleEvent?.Dispose();
|
||||
Trading?.Dispose();
|
||||
}
|
||||
|
||||
internal async Task<bool> AcceptConfirmations(bool accept, Steam.ConfirmationDetails.EType acceptedType = Steam.ConfirmationDetails.EType.Unknown, ulong acceptedSteamID = 0, HashSet<ulong> acceptedTradeIDs = null) {
|
||||
@@ -317,15 +318,15 @@ namespace ArchiSteamFarm {
|
||||
return null;
|
||||
}
|
||||
|
||||
internal async Task<(uint PlayableAppID, DateTime IgnoredUntil)> GetAppDataForIdling(uint appID, bool allowRecursiveDiscovery = true) {
|
||||
if (appID == 0) {
|
||||
ArchiLogger.LogNullError(nameof(appID));
|
||||
return (0, DateTime.MinValue);
|
||||
internal async Task<(uint PlayableAppID, DateTime IgnoredUntil)> GetAppDataForIdling(uint appID, float hoursPlayed, bool allowRecursiveDiscovery = true) {
|
||||
if ((appID == 0) || (hoursPlayed < 0)) {
|
||||
ArchiLogger.LogNullError(nameof(appID) + " || " + nameof(hoursPlayed));
|
||||
return (0, DateTime.MaxValue);
|
||||
}
|
||||
|
||||
if (!BotConfig.IdleRefundableGames) {
|
||||
if ((hoursPlayed < CardsFarmer.HoursToBump) && !BotConfig.IdleRefundableGames) {
|
||||
if (!Program.GlobalDatabase.AppIDsToPackageIDs.TryGetValue(appID, out ConcurrentHashSet<uint> packageIDs)) {
|
||||
return (0, DateTime.MinValue);
|
||||
return (0, DateTime.MaxValue);
|
||||
}
|
||||
|
||||
if (packageIDs.Count > 0) {
|
||||
@@ -343,7 +344,7 @@ namespace ArchiSteamFarm {
|
||||
}
|
||||
|
||||
if (mostRecent != DateTime.MinValue) {
|
||||
DateTime playableIn = mostRecent.AddDays(14);
|
||||
DateTime playableIn = mostRecent.AddDays(CardsFarmer.DaysForRefund);
|
||||
if (playableIn > DateTime.UtcNow) {
|
||||
return (0, playableIn);
|
||||
}
|
||||
@@ -358,13 +359,10 @@ namespace ArchiSteamFarm {
|
||||
try {
|
||||
productInfoResultSet = await SteamApps.PICSGetProductInfo(appID, null, false);
|
||||
} catch (Exception e) {
|
||||
ArchiLogger.LogGenericException(e);
|
||||
return (0, DateTime.MinValue);
|
||||
ArchiLogger.LogGenericWarningException(e);
|
||||
return (0, DateTime.MaxValue);
|
||||
} finally {
|
||||
Task.Run(async () => {
|
||||
await Task.Delay(PICSCooldownInMiliseconds).ConfigureAwait(false);
|
||||
PICSSemaphore.Release();
|
||||
}).Forget();
|
||||
PICSSemaphore.Release();
|
||||
}
|
||||
|
||||
// ReSharper disable once LoopCanBePartlyConvertedToQuery - C# 7.0 out can't be used within LINQ query yet | https://github.com/dotnet/roslyn/issues/15619
|
||||
@@ -444,7 +442,7 @@ namespace ArchiSteamFarm {
|
||||
break;
|
||||
}
|
||||
|
||||
(uint PlayableAppID, DateTime IgnoredUntil) dlcAppData = await GetAppDataForIdling(dlcAppID, false).ConfigureAwait(false);
|
||||
(uint PlayableAppID, DateTime IgnoredUntil) dlcAppData = await GetAppDataForIdling(dlcAppID, hoursPlayed, false).ConfigureAwait(false);
|
||||
if (dlcAppData.PlayableAppID != 0) {
|
||||
return (dlcAppData.PlayableAppID, DateTime.MinValue);
|
||||
}
|
||||
@@ -454,7 +452,7 @@ namespace ArchiSteamFarm {
|
||||
}
|
||||
|
||||
if (!productInfoResultSet.Complete || productInfoResultSet.Failed) {
|
||||
return (0, DateTime.MinValue);
|
||||
return (0, DateTime.MaxValue);
|
||||
}
|
||||
|
||||
return (appID, DateTime.MinValue);
|
||||
@@ -468,13 +466,10 @@ namespace ArchiSteamFarm {
|
||||
try {
|
||||
productInfoResultSet = await SteamApps.PICSGetProductInfo(Enumerable.Empty<uint>(), packageIDs);
|
||||
} catch (Exception e) {
|
||||
ArchiLogger.LogGenericException(e);
|
||||
ArchiLogger.LogGenericWarningException(e);
|
||||
return null;
|
||||
} finally {
|
||||
Task.Run(async () => {
|
||||
await Task.Delay(PICSCooldownInMiliseconds).ConfigureAwait(false);
|
||||
PICSSemaphore.Release();
|
||||
}).Forget();
|
||||
PICSSemaphore.Release();
|
||||
}
|
||||
|
||||
Dictionary<uint, HashSet<uint>> result = new Dictionary<uint, HashSet<uint>>();
|
||||
@@ -528,15 +523,13 @@ namespace ArchiSteamFarm {
|
||||
return result;
|
||||
}
|
||||
|
||||
internal void IdleGame(uint gameID) => IdleGames(gameID.ToEnumerable());
|
||||
|
||||
internal void IdleGames(IEnumerable<uint> gameIDs) {
|
||||
internal async Task IdleGames(IEnumerable<uint> gameIDs) {
|
||||
if (gameIDs == null) {
|
||||
ArchiLogger.LogNullError(nameof(gameIDs));
|
||||
return;
|
||||
}
|
||||
|
||||
ArchiHandler.PlayGames(gameIDs, BotConfig.CustomGamePlayedWhileFarming);
|
||||
await ArchiHandler.PlayGames(gameIDs, BotConfig.CustomGamePlayedWhileFarming).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
internal static async Task InitializeSteamConfiguration(ProtocolTypes protocolTypes, uint cellID, InMemoryServerListProvider serverListProvider) {
|
||||
@@ -609,7 +602,7 @@ namespace ArchiSteamFarm {
|
||||
}
|
||||
|
||||
internal async Task OnFarmingFinished(bool farmedSomething) {
|
||||
OnFarmingStopped();
|
||||
await OnFarmingStopped().ConfigureAwait(false);
|
||||
|
||||
if (farmedSomething || !FirstTradeSent) {
|
||||
FirstTradeSent = true;
|
||||
@@ -630,7 +623,7 @@ namespace ArchiSteamFarm {
|
||||
}
|
||||
}
|
||||
|
||||
internal void OnFarmingStopped() => ResetGamesPlayed();
|
||||
internal async Task OnFarmingStopped() => await ResetGamesPlayed().ConfigureAwait(false);
|
||||
|
||||
internal async Task OnNewConfigLoaded(ASF.BotConfigEventArgs args) {
|
||||
if (args == null) {
|
||||
@@ -674,7 +667,7 @@ namespace ArchiSteamFarm {
|
||||
try {
|
||||
callback = await SteamUser.RequestWebAPIUserNonce();
|
||||
} catch (Exception e) {
|
||||
ArchiLogger.LogGenericException(e);
|
||||
ArchiLogger.LogGenericWarningException(e);
|
||||
await Connect(true).ConfigureAwait(false);
|
||||
return false;
|
||||
}
|
||||
@@ -810,13 +803,13 @@ namespace ArchiSteamFarm {
|
||||
return await ResponseBlacklistAdd(steamID, args[1], args[2]).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
return ResponseBlacklistAdd(steamID, args[1]);
|
||||
return await ResponseBlacklistAdd(steamID, args[1]).ConfigureAwait(false);
|
||||
case "!BLRM":
|
||||
if (args.Length > 2) {
|
||||
return await ResponseBlacklistRemove(steamID, args[1], args[2]).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
return ResponseBlacklistRemove(steamID, args[1]);
|
||||
return await ResponseBlacklistRemove(steamID, args[1]).ConfigureAwait(false);
|
||||
case "!FARM":
|
||||
return await ResponseFarm(steamID, args[1]).ConfigureAwait(false);
|
||||
case "!INPUT":
|
||||
@@ -832,13 +825,13 @@ namespace ArchiSteamFarm {
|
||||
return await ResponseIdleQueueAdd(steamID, args[1], args[2]).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
return ResponseIdleQueueAdd(steamID, args[1]);
|
||||
return await ResponseIdleQueueAdd(steamID, args[1]).ConfigureAwait(false);
|
||||
case "!IQRM":
|
||||
if (args.Length > 2) {
|
||||
return await ResponseIdleQueueRemove(steamID, args[1], args[2]).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
return ResponseIdleQueueRemove(steamID, args[1]);
|
||||
return await ResponseIdleQueueRemove(steamID, args[1]).ConfigureAwait(false);
|
||||
case "!LOOT":
|
||||
return await ResponseLoot(steamID, args[1]).ConfigureAwait(false);
|
||||
case "!LOOT^":
|
||||
@@ -903,6 +896,16 @@ namespace ArchiSteamFarm {
|
||||
return await ResponseStatus(steamID, args[1]).ConfigureAwait(false);
|
||||
case "!STOP":
|
||||
return await ResponseStop(steamID, args[1]).ConfigureAwait(false);
|
||||
case "!TRANSFER":
|
||||
if (args.Length > 3) {
|
||||
return await ResponseTransfer(steamID, args[1], args[2], args[3]).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
if (args.Length > 2) {
|
||||
return await ResponseTransfer(steamID, args[1], args[2]).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
return ResponseUnknown(steamID);
|
||||
case "!UNPACK":
|
||||
return await ResponseUnpackBoosters(steamID, args[1]).ConfigureAwait(false);
|
||||
default:
|
||||
@@ -947,7 +950,10 @@ namespace ArchiSteamFarm {
|
||||
|
||||
ArchiLogger.LogGenericInfo(Strings.BotAutomaticIdlingPauseTimeout);
|
||||
StopFamilySharingInactivityTimer();
|
||||
await CardsFarmer.Resume(false).ConfigureAwait(false);
|
||||
|
||||
if (!await CardsFarmer.Resume(false).ConfigureAwait(false)) {
|
||||
await ResetGamesPlayed().ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
|
||||
private async Task CheckOccupationStatus() {
|
||||
@@ -962,7 +968,10 @@ namespace ArchiSteamFarm {
|
||||
|
||||
ArchiLogger.LogGenericInfo(Strings.BotAccountFree);
|
||||
PlayingWasBlocked = false;
|
||||
await CardsFarmer.Resume(false).ConfigureAwait(false);
|
||||
|
||||
if (!await CardsFarmer.Resume(false).ConfigureAwait(false)) {
|
||||
await ResetGamesPlayed().ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
|
||||
private async Task Connect(bool force = false) {
|
||||
@@ -1141,7 +1150,7 @@ namespace ArchiSteamFarm {
|
||||
|
||||
try {
|
||||
if (DateTime.UtcNow.Subtract(ArchiHandler.LastPacketReceived).TotalSeconds > MinHeartBeatTTL) {
|
||||
await SteamApps.PICSGetProductInfo(0, null);
|
||||
await SteamFriends.RequestProfileInfo(SteamClient.SteamID);
|
||||
}
|
||||
|
||||
HeartBeatFailures = 0;
|
||||
@@ -1163,7 +1172,7 @@ namespace ArchiSteamFarm {
|
||||
}
|
||||
}
|
||||
|
||||
private void ImportAuthenticator(string maFilePath) {
|
||||
private async Task ImportAuthenticator(string maFilePath) {
|
||||
if (HasMobileAuthenticator || !File.Exists(maFilePath)) {
|
||||
return;
|
||||
}
|
||||
@@ -1171,7 +1180,8 @@ namespace ArchiSteamFarm {
|
||||
ArchiLogger.LogGenericInfo(Strings.BotAuthenticatorConverting);
|
||||
|
||||
try {
|
||||
BotDatabase.MobileAuthenticator = JsonConvert.DeserializeObject<MobileAuthenticator>(File.ReadAllText(maFilePath));
|
||||
MobileAuthenticator authenticator = JsonConvert.DeserializeObject<MobileAuthenticator>(File.ReadAllText(maFilePath));
|
||||
await BotDatabase.SetMobileAuthenticator(authenticator).ConfigureAwait(false);
|
||||
File.Delete(maFilePath);
|
||||
} catch (Exception e) {
|
||||
ArchiLogger.LogGenericException(e);
|
||||
@@ -1190,14 +1200,14 @@ namespace ArchiSteamFarm {
|
||||
if (string.IsNullOrEmpty(DeviceID)) {
|
||||
string deviceID = Program.GetUserInput(ASF.EUserInputType.DeviceID, BotName);
|
||||
if (string.IsNullOrEmpty(deviceID)) {
|
||||
BotDatabase.MobileAuthenticator = null;
|
||||
await BotDatabase.SetMobileAuthenticator().ConfigureAwait(false);
|
||||
return;
|
||||
}
|
||||
|
||||
SetUserInput(ASF.EUserInputType.DeviceID, deviceID);
|
||||
}
|
||||
|
||||
BotDatabase.CorrectMobileAuthenticatorDeviceID(DeviceID);
|
||||
await BotDatabase.CorrectMobileAuthenticatorDeviceID(DeviceID).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
ArchiLogger.LogGenericInfo(Strings.BotAuthenticatorImportFinished);
|
||||
@@ -1283,7 +1293,7 @@ namespace ArchiSteamFarm {
|
||||
return;
|
||||
}
|
||||
|
||||
ArchiLogger.LogGenericError(Strings.BotHeartBeatFailed);
|
||||
ArchiLogger.LogGenericWarning(Strings.BotHeartBeatFailed);
|
||||
Destroy(true);
|
||||
RegisterBot(BotName);
|
||||
}
|
||||
@@ -1425,15 +1435,15 @@ namespace ArchiSteamFarm {
|
||||
nickname = SteamFriends.GetPersonaName();
|
||||
}
|
||||
|
||||
if (string.IsNullOrEmpty(nickname) || nickname.Equals("[unassigned]")) {
|
||||
ArchiLogger.LogGenericError(string.Format(Strings.ErrorObjectIsNull, nameof(nickname)));
|
||||
return;
|
||||
}
|
||||
// We should return here if [unassigned] is still our nickname at this point
|
||||
// However, [unassigned] could be real nickname of the user regardless
|
||||
// In this case, we can't tell a difference between real nickname and lack of it
|
||||
// We must blindly assume that SK2 did the right thing and our timeout was enough
|
||||
|
||||
try {
|
||||
await SteamFriends.SetPersonaState(EPersonaState.Online);
|
||||
} catch (Exception e) {
|
||||
ArchiLogger.LogGenericException(e);
|
||||
ArchiLogger.LogGenericWarningException(e);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1456,7 +1466,7 @@ namespace ArchiSteamFarm {
|
||||
return;
|
||||
}
|
||||
|
||||
if ((callback.ChatMsgType != EChatEntryType.ChatMsg) || string.IsNullOrEmpty(callback.Message)) {
|
||||
if ((callback.ChatMsgType != EChatEntryType.ChatMsg) || string.IsNullOrWhiteSpace(callback.Message)) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -1591,7 +1601,7 @@ namespace ArchiSteamFarm {
|
||||
goto case EResult.RateLimitExceeded;
|
||||
}
|
||||
|
||||
BotDatabase.LoginKey = null;
|
||||
await BotDatabase.SetLoginKey().ConfigureAwait(false);
|
||||
ArchiLogger.LogGenericInfo(Strings.BotRemovedExpiredLoginKey);
|
||||
break;
|
||||
case EResult.NoConnection:
|
||||
@@ -1629,7 +1639,7 @@ namespace ArchiSteamFarm {
|
||||
return;
|
||||
}
|
||||
|
||||
if ((callback.EntryType != EChatEntryType.ChatMsg) || string.IsNullOrEmpty(callback.Message)) {
|
||||
if ((callback.EntryType != EChatEntryType.ChatMsg) || string.IsNullOrWhiteSpace(callback.Message)) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -1726,6 +1736,20 @@ namespace ArchiSteamFarm {
|
||||
return;
|
||||
}
|
||||
|
||||
// Return early if this update doesn't bring anything new
|
||||
if (callback.LicenseList.Count == OwnedPackageIDs.Count) {
|
||||
if (callback.LicenseList.All(license => OwnedPackageIDs.ContainsKey(license.PackageID))) {
|
||||
// Wait 2 seconds for eventual PlayingSessionStateCallback or SharedLibraryLockStatusCallback
|
||||
await Task.Delay(2000).ConfigureAwait(false);
|
||||
|
||||
if (!await CardsFarmer.Resume(false).ConfigureAwait(false)) {
|
||||
await ResetGamesPlayed().ConfigureAwait(false);
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
OwnedPackageIDs.Clear();
|
||||
foreach (SteamApps.LicenseListCallback.License license in callback.LicenseList) {
|
||||
OwnedPackageIDs[license.PackageID] = (license.PaymentMethod, license.TimeCreated);
|
||||
@@ -1737,15 +1761,13 @@ namespace ArchiSteamFarm {
|
||||
}
|
||||
}
|
||||
|
||||
await Task.Delay(1000).ConfigureAwait(false); // Wait a second for eventual PlayingSessionStateCallback or SharedLibraryLockStatusCallback
|
||||
// Wait a second for eventual PlayingSessionStateCallback or SharedLibraryLockStatusCallback
|
||||
await Task.Delay(1000).ConfigureAwait(false);
|
||||
|
||||
// Normally we ResetGamesPlayed() in OnFarmingStopped() but there is no farming event if CardsFarmer module is disabled
|
||||
// Therefore, trigger extra ResetGamesPlayed(), but only in this specific case
|
||||
if (CardsFarmer.Paused) {
|
||||
ResetGamesPlayed();
|
||||
await ResetGamesPlayed().ConfigureAwait(false);
|
||||
}
|
||||
|
||||
// We trigger OnNewGameAdded() anyway, as CardsFarmer has other things to handle regardless of being Paused or not
|
||||
await CardsFarmer.OnNewGameAdded().ConfigureAwait(false);
|
||||
}
|
||||
|
||||
@@ -1828,15 +1850,15 @@ namespace ArchiSteamFarm {
|
||||
ArchiLogger.LogGenericWarning(Strings.BotAccountLocked);
|
||||
}
|
||||
|
||||
if (callback.CellID != 0) {
|
||||
Program.GlobalDatabase.CellID = callback.CellID;
|
||||
if ((callback.CellID != 0) && (callback.CellID != Program.GlobalDatabase.CellID)) {
|
||||
await Program.GlobalDatabase.SetCellID(callback.CellID).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
if (!HasMobileAuthenticator) {
|
||||
// Support and convert 2FA files
|
||||
string maFilePath = Path.Combine(SharedInfo.ConfigDirectory, callback.ClientSteamID.ConvertToUInt64() + ".maFile");
|
||||
if (File.Exists(maFilePath)) {
|
||||
ImportAuthenticator(maFilePath);
|
||||
await ImportAuthenticator(maFilePath).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1871,6 +1893,7 @@ namespace ArchiSteamFarm {
|
||||
|
||||
Statistics?.OnLoggedOn().Forget();
|
||||
Trading.OnNewTrade().Forget();
|
||||
|
||||
break;
|
||||
case EResult.InvalidPassword:
|
||||
case EResult.NoConnection:
|
||||
@@ -1903,7 +1926,7 @@ namespace ArchiSteamFarm {
|
||||
}
|
||||
}
|
||||
|
||||
private void OnLoginKey(SteamUser.LoginKeyCallback callback) {
|
||||
private async void OnLoginKey(SteamUser.LoginKeyCallback callback) {
|
||||
if (string.IsNullOrEmpty(callback?.LoginKey)) {
|
||||
ArchiLogger.LogNullError(nameof(callback) + " || " + nameof(callback.LoginKey));
|
||||
return;
|
||||
@@ -1914,7 +1937,7 @@ namespace ArchiSteamFarm {
|
||||
loginKey = CryptoHelper.Encrypt(BotConfig.PasswordFormat, loginKey);
|
||||
}
|
||||
|
||||
BotDatabase.LoginKey = loginKey;
|
||||
await BotDatabase.SetLoginKey(loginKey).ConfigureAwait(false);
|
||||
SteamUser.AcceptNewLoginKey(callback);
|
||||
}
|
||||
|
||||
@@ -2069,12 +2092,12 @@ namespace ArchiSteamFarm {
|
||||
}
|
||||
}
|
||||
|
||||
private void ResetGamesPlayed() {
|
||||
if (!IsPlayingPossible || (FamilySharingInactivityTimer != null)) {
|
||||
private async Task ResetGamesPlayed() {
|
||||
if (!IsPlayingPossible || (FamilySharingInactivityTimer != null) || CardsFarmer.NowFarming) {
|
||||
return;
|
||||
}
|
||||
|
||||
ArchiHandler.PlayGames(BotConfig.GamesPlayedWhileIdle, BotConfig.CustomGamePlayedWhileIdle);
|
||||
await ArchiHandler.PlayGames(BotConfig.GamesPlayedWhileIdle, BotConfig.CustomGamePlayedWhileIdle).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
private void ResetPlayingWasBlockedWithTimer() {
|
||||
@@ -2210,7 +2233,7 @@ namespace ArchiSteamFarm {
|
||||
try {
|
||||
callback = await SteamApps.RequestFreeLicense(gameID);
|
||||
} catch (Exception e) {
|
||||
ArchiLogger.LogGenericException(e);
|
||||
ArchiLogger.LogGenericWarningException(e);
|
||||
response.Append(FormatBotResponse(string.Format(Strings.BotAddLicense, gameID, EResult.Timeout)));
|
||||
break;
|
||||
}
|
||||
@@ -2313,7 +2336,7 @@ namespace ArchiSteamFarm {
|
||||
foreach (string flag in flags) {
|
||||
switch (flag.ToUpperInvariant()) {
|
||||
case "FD":
|
||||
redeemFlags |= ERedeemFlags.ForceDistribution;
|
||||
redeemFlags |= ERedeemFlags.ForceDistributing;
|
||||
break;
|
||||
case "FF":
|
||||
redeemFlags |= ERedeemFlags.ForceForwarding;
|
||||
@@ -2322,7 +2345,7 @@ namespace ArchiSteamFarm {
|
||||
redeemFlags |= ERedeemFlags.ForceKeepMissingGames;
|
||||
break;
|
||||
case "SD":
|
||||
redeemFlags |= ERedeemFlags.SkipDistribution;
|
||||
redeemFlags |= ERedeemFlags.SkipDistributing;
|
||||
break;
|
||||
case "SF":
|
||||
redeemFlags |= ERedeemFlags.SkipForwarding;
|
||||
@@ -2438,7 +2461,7 @@ namespace ArchiSteamFarm {
|
||||
return null;
|
||||
}
|
||||
|
||||
private string ResponseBlacklistAdd(ulong steamID, string targetsText) {
|
||||
private async Task<string> ResponseBlacklistAdd(ulong steamID, string targetsText) {
|
||||
if ((steamID == 0) || string.IsNullOrEmpty(targetsText)) {
|
||||
ArchiLogger.LogNullError(nameof(steamID) + " || " + nameof(targetsText));
|
||||
return null;
|
||||
@@ -2463,7 +2486,7 @@ namespace ArchiSteamFarm {
|
||||
return FormatBotResponse(string.Format(Strings.ErrorIsEmpty, nameof(targetIDs)));
|
||||
}
|
||||
|
||||
BotDatabase.AddBlacklistedFromTradesSteamIDs(targetIDs);
|
||||
await BotDatabase.AddBlacklistedFromTradesSteamIDs(targetIDs).ConfigureAwait(false);
|
||||
return FormatBotResponse(Strings.Done);
|
||||
}
|
||||
|
||||
@@ -2478,7 +2501,7 @@ namespace ArchiSteamFarm {
|
||||
return IsOwner(steamID) ? FormatStaticResponse(string.Format(Strings.BotNotFound, botNames)) : null;
|
||||
}
|
||||
|
||||
IEnumerable<Task<string>> tasks = bots.Select(bot => Task.Run(() => bot.ResponseBlacklistAdd(steamID, targetsText)));
|
||||
IEnumerable<Task<string>> tasks = bots.Select(bot => bot.ResponseBlacklistAdd(steamID, targetsText));
|
||||
ICollection<string> results;
|
||||
|
||||
switch (Program.GlobalConfig.OptimizationMode) {
|
||||
@@ -2509,7 +2532,7 @@ namespace ArchiSteamFarm {
|
||||
return IsOwner(steamID) ? FormatStaticResponse(string.Format(Strings.BotNotFound, botNames)) : null;
|
||||
}
|
||||
|
||||
IEnumerable<Task<string>> tasks = bots.Select(bot => Task.Run(() => bot.ResponseBlacklistRemove(steamID, targetsText)));
|
||||
IEnumerable<Task<string>> tasks = bots.Select(bot => bot.ResponseBlacklistRemove(steamID, targetsText));
|
||||
ICollection<string> results;
|
||||
|
||||
switch (Program.GlobalConfig.OptimizationMode) {
|
||||
@@ -2529,7 +2552,7 @@ namespace ArchiSteamFarm {
|
||||
return responses.Count > 0 ? string.Join("", responses) : null;
|
||||
}
|
||||
|
||||
private string ResponseBlacklistRemove(ulong steamID, string targetsText) {
|
||||
private async Task<string> ResponseBlacklistRemove(ulong steamID, string targetsText) {
|
||||
if ((steamID == 0) || string.IsNullOrEmpty(targetsText)) {
|
||||
ArchiLogger.LogNullError(nameof(steamID) + " || " + nameof(targetsText));
|
||||
return null;
|
||||
@@ -2554,7 +2577,7 @@ namespace ArchiSteamFarm {
|
||||
return FormatBotResponse(string.Format(Strings.ErrorIsEmpty, nameof(targetIDs)));
|
||||
}
|
||||
|
||||
BotDatabase.RemoveBlacklistedFromTradesSteamIDs(targetIDs);
|
||||
await BotDatabase.RemoveBlacklistedFromTradesSteamIDs(targetIDs).ConfigureAwait(false);
|
||||
return FormatBotResponse(Strings.Done);
|
||||
}
|
||||
|
||||
@@ -2591,7 +2614,10 @@ namespace ArchiSteamFarm {
|
||||
return FormatBotResponse(Strings.BotNotConnected);
|
||||
}
|
||||
|
||||
await CardsFarmer.StopFarming().ConfigureAwait(false);
|
||||
if (CardsFarmer.NowFarming) {
|
||||
await CardsFarmer.StopFarming().ConfigureAwait(false);
|
||||
}
|
||||
|
||||
CardsFarmer.StartFarming().Forget();
|
||||
|
||||
return FormatBotResponse(Strings.Done);
|
||||
@@ -2678,7 +2704,7 @@ namespace ArchiSteamFarm {
|
||||
return result;
|
||||
}
|
||||
|
||||
private string ResponseIdleQueueAdd(ulong steamID, string targetsText) {
|
||||
private async Task<string> ResponseIdleQueueAdd(ulong steamID, string targetsText) {
|
||||
if ((steamID == 0) || string.IsNullOrEmpty(targetsText)) {
|
||||
ArchiLogger.LogNullError(nameof(steamID) + " || " + nameof(targetsText));
|
||||
return null;
|
||||
@@ -2703,7 +2729,7 @@ namespace ArchiSteamFarm {
|
||||
return FormatBotResponse(string.Format(Strings.ErrorIsEmpty, nameof(appIDs)));
|
||||
}
|
||||
|
||||
BotDatabase.AddIdlingPriorityAppIDs(appIDs);
|
||||
await BotDatabase.AddIdlingPriorityAppIDs(appIDs).ConfigureAwait(false);
|
||||
return FormatBotResponse(Strings.Done);
|
||||
}
|
||||
|
||||
@@ -2718,7 +2744,7 @@ namespace ArchiSteamFarm {
|
||||
return IsOwner(steamID) ? FormatStaticResponse(string.Format(Strings.BotNotFound, botNames)) : null;
|
||||
}
|
||||
|
||||
IEnumerable<Task<string>> tasks = bots.Select(bot => Task.Run(() => bot.ResponseIdleQueueAdd(steamID, targetsText)));
|
||||
IEnumerable<Task<string>> tasks = bots.Select(bot => bot.ResponseIdleQueueAdd(steamID, targetsText));
|
||||
ICollection<string> results;
|
||||
|
||||
switch (Program.GlobalConfig.OptimizationMode) {
|
||||
@@ -2749,7 +2775,7 @@ namespace ArchiSteamFarm {
|
||||
return IsOwner(steamID) ? FormatStaticResponse(string.Format(Strings.BotNotFound, botNames)) : null;
|
||||
}
|
||||
|
||||
IEnumerable<Task<string>> tasks = bots.Select(bot => Task.Run(() => bot.ResponseIdleQueueRemove(steamID, targetsText)));
|
||||
IEnumerable<Task<string>> tasks = bots.Select(bot => bot.ResponseIdleQueueRemove(steamID, targetsText));
|
||||
ICollection<string> results;
|
||||
|
||||
switch (Program.GlobalConfig.OptimizationMode) {
|
||||
@@ -2769,7 +2795,7 @@ namespace ArchiSteamFarm {
|
||||
return responses.Count > 0 ? string.Join("", responses) : null;
|
||||
}
|
||||
|
||||
private string ResponseIdleQueueRemove(ulong steamID, string targetsText) {
|
||||
private async Task<string> ResponseIdleQueueRemove(ulong steamID, string targetsText) {
|
||||
if ((steamID == 0) || string.IsNullOrEmpty(targetsText)) {
|
||||
ArchiLogger.LogNullError(nameof(steamID) + " || " + nameof(targetsText));
|
||||
return null;
|
||||
@@ -2794,7 +2820,7 @@ namespace ArchiSteamFarm {
|
||||
return FormatBotResponse(string.Format(Strings.ErrorIsEmpty, nameof(appIDs)));
|
||||
}
|
||||
|
||||
BotDatabase.RemoveIdlingPriorityAppIDs(appIDs);
|
||||
await BotDatabase.RemoveIdlingPriorityAppIDs(appIDs).ConfigureAwait(false);
|
||||
return FormatBotResponse(Strings.Done);
|
||||
}
|
||||
|
||||
@@ -3011,7 +3037,7 @@ namespace ArchiSteamFarm {
|
||||
try {
|
||||
result = await SteamFriends.SetPersonaName(nickname);
|
||||
} catch (Exception e) {
|
||||
ArchiLogger.LogGenericException(e);
|
||||
ArchiLogger.LogGenericWarningException(e);
|
||||
return FormatBotResponse(Strings.WarningFailed);
|
||||
}
|
||||
|
||||
@@ -3067,6 +3093,8 @@ namespace ArchiSteamFarm {
|
||||
return (FormatBotResponse(Strings.BotNotConnected), false);
|
||||
}
|
||||
|
||||
await LimitGiftsRequestsAsync().ConfigureAwait(false);
|
||||
|
||||
Dictionary<uint, string> ownedGames;
|
||||
if (await ArchiWebHandler.HasValidApiKey().ConfigureAwait(false)) {
|
||||
ownedGames = await ArchiWebHandler.GetOwnedGames(SteamID).ConfigureAwait(false);
|
||||
@@ -3242,7 +3270,7 @@ namespace ArchiSteamFarm {
|
||||
// We add extra delay because OnFarmingStopped() also executes PlayGames()
|
||||
// Despite of proper order on our end, Steam network might not respect it
|
||||
await Task.Delay(CallbackSleep).ConfigureAwait(false);
|
||||
ArchiHandler.PlayGames(Enumerable.Empty<uint>(), BotConfig.CustomGamePlayedWhileIdle);
|
||||
await ArchiHandler.PlayGames(Enumerable.Empty<uint>(), BotConfig.CustomGamePlayedWhileIdle).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
if (resumeInSeconds > 0) {
|
||||
@@ -3316,7 +3344,7 @@ namespace ArchiSteamFarm {
|
||||
await CardsFarmer.Pause(false).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
ArchiHandler.PlayGames(gameIDs);
|
||||
await ArchiHandler.PlayGames(gameIDs).ConfigureAwait(false);
|
||||
return FormatBotResponse(Strings.Done);
|
||||
}
|
||||
|
||||
@@ -3403,7 +3431,7 @@ namespace ArchiSteamFarm {
|
||||
}
|
||||
|
||||
bool forward = !redeemFlags.HasFlag(ERedeemFlags.SkipForwarding) && (redeemFlags.HasFlag(ERedeemFlags.ForceForwarding) || BotConfig.RedeemingPreferences.HasFlag(BotConfig.ERedeemingPreferences.Forwarding));
|
||||
bool distribute = !redeemFlags.HasFlag(ERedeemFlags.SkipDistribution) && (redeemFlags.HasFlag(ERedeemFlags.ForceDistribution) || BotConfig.RedeemingPreferences.HasFlag(BotConfig.ERedeemingPreferences.Distributing));
|
||||
bool distribute = !redeemFlags.HasFlag(ERedeemFlags.SkipDistributing) && (redeemFlags.HasFlag(ERedeemFlags.ForceDistributing) || BotConfig.RedeemingPreferences.HasFlag(BotConfig.ERedeemingPreferences.Distributing));
|
||||
bool keepMissingGames = !redeemFlags.HasFlag(ERedeemFlags.SkipKeepMissingGames) && (redeemFlags.HasFlag(ERedeemFlags.ForceKeepMissingGames) || BotConfig.RedeemingPreferences.HasFlag(BotConfig.ERedeemingPreferences.KeepMissingGames));
|
||||
|
||||
string[] keysList = keys.Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries);
|
||||
@@ -3817,7 +3845,7 @@ namespace ArchiSteamFarm {
|
||||
return (FormatBotResponse(Strings.BotStatusLocked), this);
|
||||
}
|
||||
|
||||
if (CardsFarmer.CurrentGamesFarming.Count == 0) {
|
||||
if (!CardsFarmer.NowFarming || (CardsFarmer.CurrentGamesFarming.Count == 0)) {
|
||||
return (FormatBotResponse(Strings.BotStatusNotIdling), this);
|
||||
}
|
||||
|
||||
@@ -3916,6 +3944,153 @@ namespace ArchiSteamFarm {
|
||||
return responses.Count > 0 ? string.Join("", responses) : null;
|
||||
}
|
||||
|
||||
private async Task<string> ResponseTransfer(ulong steamID, string mode, string botNameTo) {
|
||||
if ((steamID == 0) || string.IsNullOrEmpty(botNameTo) || string.IsNullOrEmpty(mode)) {
|
||||
ASF.ArchiLogger.LogNullError(nameof(steamID) + " || " + nameof(mode) + " || " + nameof(botNameTo));
|
||||
return null;
|
||||
}
|
||||
|
||||
if (!IsMaster(steamID)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (!IsConnectedAndLoggedOn) {
|
||||
return FormatBotResponse(Strings.BotNotConnected);
|
||||
}
|
||||
|
||||
if (!LootingAllowed) {
|
||||
return FormatBotResponse(Strings.BotLootingTemporarilyDisabled);
|
||||
}
|
||||
|
||||
if (!Bots.TryGetValue(botNameTo, out Bot targetBot)) {
|
||||
return IsOwner(steamID) ? FormatBotResponse(string.Format(Strings.BotNotFound, botNameTo)) : null;
|
||||
}
|
||||
|
||||
if (targetBot.SteamID == 0) {
|
||||
return FormatBotResponse(Strings.BotNotConnected);
|
||||
}
|
||||
|
||||
if (targetBot.SteamID == SteamID) {
|
||||
return FormatBotResponse(Strings.BotLootingYourself);
|
||||
}
|
||||
|
||||
HashSet<Steam.Item.EType> transferTypes = new HashSet<Steam.Item.EType>();
|
||||
|
||||
string[] modes = mode.Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries);
|
||||
foreach (string singleMode in modes) {
|
||||
switch (singleMode.ToUpper()) {
|
||||
case "A":
|
||||
case "ALL":
|
||||
foreach (Steam.Item.EType type in Enum.GetValues(typeof(Steam.Item.EType))) {
|
||||
transferTypes.Add(type);
|
||||
}
|
||||
|
||||
break;
|
||||
case "BG":
|
||||
case "BACKGROUND":
|
||||
transferTypes.Add(Steam.Item.EType.ProfileBackground);
|
||||
break;
|
||||
case "BO":
|
||||
case "BOOSTER":
|
||||
transferTypes.Add(Steam.Item.EType.BoosterPack);
|
||||
break;
|
||||
case "C":
|
||||
case "CARD":
|
||||
transferTypes.Add(Steam.Item.EType.TradingCard);
|
||||
break;
|
||||
case "E":
|
||||
case "EMOTICON":
|
||||
transferTypes.Add(Steam.Item.EType.Emoticon);
|
||||
break;
|
||||
case "F":
|
||||
case "FOIL":
|
||||
transferTypes.Add(Steam.Item.EType.FoilTradingCard);
|
||||
break;
|
||||
case "G":
|
||||
case "GEMS":
|
||||
transferTypes.Add(Steam.Item.EType.SteamGems);
|
||||
break;
|
||||
case "U":
|
||||
case "UNKNOWN":
|
||||
transferTypes.Add(Steam.Item.EType.Unknown);
|
||||
break;
|
||||
default:
|
||||
return FormatBotResponse(string.Format(Strings.ErrorIsInvalid, mode));
|
||||
}
|
||||
}
|
||||
|
||||
if (!LootingSemaphore.Wait(0)) {
|
||||
return FormatBotResponse(Strings.BotLootingFailed);
|
||||
}
|
||||
|
||||
try {
|
||||
HashSet<Steam.Item> inventory = await ArchiWebHandler.GetMySteamInventory(true, transferTypes).ConfigureAwait(false);
|
||||
if ((inventory == null) || (inventory.Count == 0)) {
|
||||
return FormatBotResponse(string.Format(Strings.ErrorIsEmpty, nameof(inventory)));
|
||||
}
|
||||
|
||||
string tradeToken = null;
|
||||
|
||||
if (SteamFriends.GetFriendRelationship(targetBot.SteamID) != EFriendRelationship.Friend) {
|
||||
tradeToken = await targetBot.ArchiWebHandler.GetTradeToken().ConfigureAwait(false);
|
||||
if (string.IsNullOrEmpty(tradeToken)) {
|
||||
return FormatBotResponse(Strings.BotLootingFailed);
|
||||
}
|
||||
}
|
||||
|
||||
if (!await ArchiWebHandler.MarkSentTrades().ConfigureAwait(false)) {
|
||||
return FormatBotResponse(Strings.BotLootingFailed);
|
||||
}
|
||||
|
||||
if (!await ArchiWebHandler.SendTradeOffer(inventory, targetBot.SteamID, tradeToken).ConfigureAwait(false)) {
|
||||
return FormatBotResponse(Strings.BotLootingFailed);
|
||||
}
|
||||
|
||||
if (HasMobileAuthenticator) {
|
||||
// Give Steam network some time to generate confirmations
|
||||
await Task.Delay(3000).ConfigureAwait(false);
|
||||
if (!await AcceptConfirmations(true, Steam.ConfirmationDetails.EType.Trade, targetBot.SteamID).ConfigureAwait(false)) {
|
||||
return FormatBotResponse(Strings.BotLootingFailed);
|
||||
}
|
||||
}
|
||||
} finally {
|
||||
LootingSemaphore.Release();
|
||||
}
|
||||
|
||||
return FormatBotResponse(Strings.BotLootingSuccess);
|
||||
}
|
||||
|
||||
private static async Task<string> ResponseTransfer(ulong steamID, string botNames, string mode, string botNameTo) {
|
||||
if ((steamID == 0) || string.IsNullOrEmpty(botNames) || string.IsNullOrEmpty(mode) || string.IsNullOrEmpty(botNameTo)) {
|
||||
ASF.ArchiLogger.LogNullError(nameof(steamID) + " || " + nameof(botNames) + " || " + nameof(mode) + " || " + nameof(botNameTo));
|
||||
return null;
|
||||
}
|
||||
|
||||
HashSet<Bot> bots = GetBots(botNames);
|
||||
if ((bots == null) || (bots.Count == 0)) {
|
||||
return IsOwner(steamID) ? FormatStaticResponse(string.Format(Strings.BotNotFound, botNames)) : null;
|
||||
}
|
||||
|
||||
IEnumerable<Task<string>> tasks = bots.Select(bot => bot.ResponseTransfer(steamID, mode, botNameTo));
|
||||
ICollection<string> results;
|
||||
|
||||
switch (Program.GlobalConfig.OptimizationMode) {
|
||||
case GlobalConfig.EOptimizationMode.MinMemoryUsage:
|
||||
results = new List<string>(bots.Count);
|
||||
foreach (Task<string> task in tasks) {
|
||||
results.Add(await task.ConfigureAwait(false));
|
||||
}
|
||||
|
||||
break;
|
||||
default:
|
||||
results = await Task.WhenAll(tasks).ConfigureAwait(false);
|
||||
break;
|
||||
}
|
||||
|
||||
List<string> responses = new List<string>(results.Where(result => !string.IsNullOrEmpty(result)));
|
||||
return responses.Count > 0 ? string.Join("", responses) : null;
|
||||
}
|
||||
|
||||
private string ResponseUnknown(ulong steamID) {
|
||||
if (steamID != 0) {
|
||||
return IsOperator(steamID) ? FormatBotResponse(Strings.UnknownCommand) : null;
|
||||
@@ -4027,6 +4202,8 @@ namespace ArchiSteamFarm {
|
||||
return;
|
||||
}
|
||||
|
||||
ArchiLogger.LogGenericTrace(steamID + "/" + SteamID + ": " + message);
|
||||
|
||||
for (int i = 0; i < message.Length; i += MaxSteamMessageLength - 2) {
|
||||
if (i > 0) {
|
||||
await Task.Delay(CallbackSleep).ConfigureAwait(false);
|
||||
@@ -4047,6 +4224,8 @@ namespace ArchiSteamFarm {
|
||||
return;
|
||||
}
|
||||
|
||||
ArchiLogger.LogGenericTrace(steamID + "/" + SteamID + ": " + message);
|
||||
|
||||
for (int i = 0; i < message.Length; i += MaxSteamMessageLength - 2) {
|
||||
if (i > 0) {
|
||||
await Task.Delay(CallbackSleep).ConfigureAwait(false);
|
||||
@@ -4095,7 +4274,7 @@ namespace ArchiSteamFarm {
|
||||
TwoFactorCode = inputValue;
|
||||
break;
|
||||
default:
|
||||
ASF.ArchiLogger.LogGenericWarning(string.Format(Strings.WarningUnknownValuePleaseReport, nameof(inputType), inputType));
|
||||
ASF.ArchiLogger.LogGenericError(string.Format(Strings.WarningUnknownValuePleaseReport, nameof(inputType), inputType));
|
||||
break;
|
||||
}
|
||||
}
|
||||
@@ -4111,7 +4290,7 @@ namespace ArchiSteamFarm {
|
||||
if (!HasMobileAuthenticator) {
|
||||
string maFilePath = BotPath + ".maFile";
|
||||
if (File.Exists(maFilePath)) {
|
||||
ImportAuthenticator(maFilePath);
|
||||
await ImportAuthenticator(maFilePath).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4164,8 +4343,8 @@ namespace ArchiSteamFarm {
|
||||
Validate = 1,
|
||||
ForceForwarding = 2,
|
||||
SkipForwarding = 4,
|
||||
ForceDistribution = 8,
|
||||
SkipDistribution = 16,
|
||||
ForceDistributing = 8,
|
||||
SkipDistributing = 16,
|
||||
SkipInitial = 32,
|
||||
ForceKeepMissingGames = 64,
|
||||
SkipKeepMissingGames = 128
|
||||
|
||||
@@ -172,6 +172,11 @@ namespace ArchiSteamFarm {
|
||||
// This constructor is used only by deserializer
|
||||
private BotConfig() { }
|
||||
|
||||
// Functions below are used for skipping serialization of sensitive fields in API response
|
||||
public bool ShouldSerializeSteamLogin() => false;
|
||||
public bool ShouldSerializeSteamParentalPIN() => false;
|
||||
public bool ShouldSerializeSteamPassword() => false;
|
||||
|
||||
internal static BotConfig Load(string filePath) {
|
||||
if (string.IsNullOrEmpty(filePath)) {
|
||||
ASF.ArchiLogger.LogNullError(nameof(filePath));
|
||||
|
||||
@@ -26,49 +26,25 @@ using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.IO;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace ArchiSteamFarm {
|
||||
internal sealed class BotDatabase {
|
||||
internal sealed class BotDatabase : IDisposable {
|
||||
[JsonProperty(Required = Required.DisallowNull)]
|
||||
private readonly ConcurrentHashSet<ulong> BlacklistedFromTradesSteamIDs = new ConcurrentHashSet<ulong>();
|
||||
|
||||
private readonly object FileLock = new object();
|
||||
private readonly SemaphoreSlim FileSemaphore = new SemaphoreSlim(1, 1);
|
||||
|
||||
[JsonProperty(Required = Required.DisallowNull)]
|
||||
private readonly ConcurrentHashSet<uint> IdlingPriorityAppIDs = new ConcurrentHashSet<uint>();
|
||||
|
||||
internal string LoginKey {
|
||||
get => _LoginKey;
|
||||
[JsonProperty(PropertyName = "_LoginKey")]
|
||||
internal string LoginKey { get; private set; }
|
||||
|
||||
set {
|
||||
if (_LoginKey == value) {
|
||||
return;
|
||||
}
|
||||
|
||||
_LoginKey = value;
|
||||
Save();
|
||||
}
|
||||
}
|
||||
|
||||
internal MobileAuthenticator MobileAuthenticator {
|
||||
get => _MobileAuthenticator;
|
||||
|
||||
set {
|
||||
if (_MobileAuthenticator == value) {
|
||||
return;
|
||||
}
|
||||
|
||||
_MobileAuthenticator = value;
|
||||
Save();
|
||||
}
|
||||
}
|
||||
|
||||
[JsonProperty]
|
||||
private string _LoginKey;
|
||||
|
||||
[JsonProperty]
|
||||
private MobileAuthenticator _MobileAuthenticator;
|
||||
[JsonProperty(PropertyName = "_MobileAuthenticator")]
|
||||
internal MobileAuthenticator MobileAuthenticator { get; private set; }
|
||||
|
||||
private string FilePath;
|
||||
|
||||
@@ -79,43 +55,51 @@ namespace ArchiSteamFarm {
|
||||
}
|
||||
|
||||
FilePath = filePath;
|
||||
Save();
|
||||
Save().Wait();
|
||||
}
|
||||
|
||||
// This constructor is used only by deserializer
|
||||
[SuppressMessage("ReSharper", "UnusedMember.Local")]
|
||||
private BotDatabase() { }
|
||||
|
||||
internal void AddBlacklistedFromTradesSteamIDs(HashSet<ulong> steamIDs) {
|
||||
public void Dispose() {
|
||||
// Those are objects that are always being created if constructor doesn't throw exception
|
||||
FileSemaphore.Dispose();
|
||||
|
||||
// Those are objects that might be null and the check should be in-place
|
||||
MobileAuthenticator?.Dispose();
|
||||
}
|
||||
|
||||
internal async Task AddBlacklistedFromTradesSteamIDs(HashSet<ulong> steamIDs) {
|
||||
if ((steamIDs == null) || (steamIDs.Count == 0)) {
|
||||
ASF.ArchiLogger.LogNullError(nameof(steamIDs));
|
||||
return;
|
||||
}
|
||||
|
||||
if (BlacklistedFromTradesSteamIDs.AddRange(steamIDs)) {
|
||||
Save();
|
||||
await Save().ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
|
||||
internal void AddIdlingPriorityAppIDs(HashSet<uint> appIDs) {
|
||||
internal async Task AddIdlingPriorityAppIDs(HashSet<uint> appIDs) {
|
||||
if ((appIDs == null) || (appIDs.Count == 0)) {
|
||||
ASF.ArchiLogger.LogNullError(nameof(appIDs));
|
||||
return;
|
||||
}
|
||||
|
||||
if (IdlingPriorityAppIDs.AddRange(appIDs)) {
|
||||
Save();
|
||||
await Save().ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
|
||||
internal void CorrectMobileAuthenticatorDeviceID(string deviceID) {
|
||||
internal async Task CorrectMobileAuthenticatorDeviceID(string deviceID) {
|
||||
if (string.IsNullOrEmpty(deviceID) || (MobileAuthenticator == null)) {
|
||||
ASF.ArchiLogger.LogNullError(nameof(deviceID) + " || " + nameof(MobileAuthenticator));
|
||||
return;
|
||||
}
|
||||
|
||||
if (MobileAuthenticator.CorrectDeviceID(deviceID)) {
|
||||
Save();
|
||||
await Save().ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -170,49 +154,69 @@ namespace ArchiSteamFarm {
|
||||
return botDatabase;
|
||||
}
|
||||
|
||||
internal void RemoveBlacklistedFromTradesSteamIDs(HashSet<ulong> steamIDs) {
|
||||
internal async Task RemoveBlacklistedFromTradesSteamIDs(HashSet<ulong> steamIDs) {
|
||||
if ((steamIDs == null) || (steamIDs.Count == 0)) {
|
||||
ASF.ArchiLogger.LogNullError(nameof(steamIDs));
|
||||
return;
|
||||
}
|
||||
|
||||
if (BlacklistedFromTradesSteamIDs.RemoveRange(steamIDs)) {
|
||||
Save();
|
||||
await Save().ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
|
||||
internal void RemoveIdlingPriorityAppIDs(HashSet<uint> appIDs) {
|
||||
internal async Task RemoveIdlingPriorityAppIDs(HashSet<uint> appIDs) {
|
||||
if ((appIDs == null) || (appIDs.Count == 0)) {
|
||||
ASF.ArchiLogger.LogNullError(nameof(appIDs));
|
||||
return;
|
||||
}
|
||||
|
||||
if (IdlingPriorityAppIDs.RemoveRange(appIDs)) {
|
||||
Save();
|
||||
await Save().ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
|
||||
private void Save() {
|
||||
internal async Task SetLoginKey(string value = null) {
|
||||
if (value == LoginKey) {
|
||||
return;
|
||||
}
|
||||
|
||||
LoginKey = value;
|
||||
await Save().ConfigureAwait(false);
|
||||
}
|
||||
|
||||
internal async Task SetMobileAuthenticator(MobileAuthenticator value = null) {
|
||||
if (value == MobileAuthenticator) {
|
||||
return;
|
||||
}
|
||||
|
||||
MobileAuthenticator = value;
|
||||
await Save().ConfigureAwait(false);
|
||||
}
|
||||
|
||||
private async Task Save() {
|
||||
string json = JsonConvert.SerializeObject(this);
|
||||
if (string.IsNullOrEmpty(json)) {
|
||||
ASF.ArchiLogger.LogNullError(nameof(json));
|
||||
return;
|
||||
}
|
||||
|
||||
lock (FileLock) {
|
||||
string newFilePath = FilePath + ".new";
|
||||
string newFilePath = FilePath + ".new";
|
||||
|
||||
try {
|
||||
File.WriteAllText(newFilePath, json);
|
||||
await FileSemaphore.WaitAsync().ConfigureAwait(false);
|
||||
|
||||
if (File.Exists(FilePath)) {
|
||||
File.Replace(newFilePath, FilePath, null);
|
||||
} else {
|
||||
File.Move(newFilePath, FilePath);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
ASF.ArchiLogger.LogGenericException(e);
|
||||
try {
|
||||
await File.WriteAllTextAsync(newFilePath, json).ConfigureAwait(false);
|
||||
|
||||
if (File.Exists(FilePath)) {
|
||||
File.Replace(newFilePath, FilePath, null);
|
||||
} else {
|
||||
File.Move(newFilePath, FilePath);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
ASF.ArchiLogger.LogGenericException(e);
|
||||
} finally {
|
||||
FileSemaphore.Release();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -38,7 +38,9 @@ using SteamKit2;
|
||||
|
||||
namespace ArchiSteamFarm {
|
||||
internal sealed class CardsFarmer : IDisposable {
|
||||
private const byte HoursToBump = 2; // How many hours are required for restricted accounts
|
||||
internal const byte DaysForRefund = 14; // In how many days since payment we're allowed to refund
|
||||
internal const byte HoursToBump = 2; // How many hours are required for restricted accounts
|
||||
|
||||
private const byte HoursToIgnore = 24; // How many hours we ignore unreleased appIDs and don't bother checking them again
|
||||
|
||||
private static readonly ConcurrentDictionary<uint, DateTime> IgnoredAppIDs = new ConcurrentDictionary<uint, DateTime>(); // Reserved for unreleased games
|
||||
@@ -52,23 +54,25 @@ namespace ArchiSteamFarm {
|
||||
|
||||
[JsonProperty]
|
||||
internal TimeSpan TimeRemaining => new TimeSpan(
|
||||
Bot.BotConfig.CardDropsRestricted ? (int) Math.Ceiling(GamesToFarm.Count / (float) ArchiHandler.MaxGamesPlayedConcurrently) * HoursToBump : 0,
|
||||
Bot.BotConfig.CardDropsRestricted ? (ushort) Math.Ceiling(GamesToFarm.Count / (float) ArchiHandler.MaxGamesPlayedConcurrently) * HoursToBump : 0,
|
||||
30 * GamesToFarm.Sum(game => game.CardsRemaining),
|
||||
0
|
||||
);
|
||||
|
||||
private readonly Bot Bot;
|
||||
private readonly SemaphoreSlim EventSemaphore = new SemaphoreSlim(1);
|
||||
private readonly SemaphoreSlim FarmingSemaphore = new SemaphoreSlim(1);
|
||||
private readonly ManualResetEventSlim FarmResetEvent = new ManualResetEventSlim(false);
|
||||
private readonly SemaphoreSlim EventSemaphore = new SemaphoreSlim(1, 1);
|
||||
private readonly SemaphoreSlim FarmingInitializationSemaphore = new SemaphoreSlim(1, 1);
|
||||
private readonly SemaphoreSlim FarmingResetSemaphore = new SemaphoreSlim(0, 1);
|
||||
private readonly Timer IdleFarmingTimer;
|
||||
|
||||
internal bool NowFarming { get; private set; }
|
||||
|
||||
[JsonProperty]
|
||||
internal bool Paused { get; private set; }
|
||||
|
||||
private bool KeepFarming;
|
||||
private bool NowFarming;
|
||||
private bool ParsingScheduled;
|
||||
private bool ShouldResumeFarming = true;
|
||||
private bool StickyPause;
|
||||
|
||||
internal CardsFarmer(Bot bot) {
|
||||
@@ -87,8 +91,9 @@ namespace ArchiSteamFarm {
|
||||
public void Dispose() {
|
||||
// Those are objects that are always being created if constructor doesn't throw exception
|
||||
EventSemaphore.Dispose();
|
||||
FarmingSemaphore.Dispose();
|
||||
FarmResetEvent.Dispose();
|
||||
FarmingInitializationSemaphore.Dispose();
|
||||
FarmingResetSemaphore.Dispose();
|
||||
GamesToFarm.Dispose();
|
||||
|
||||
// Those are objects that might be null and the check should be in-place
|
||||
IdleFarmingTimer?.Dispose();
|
||||
@@ -103,6 +108,8 @@ namespace ArchiSteamFarm {
|
||||
}
|
||||
|
||||
internal async Task OnNewGameAdded() {
|
||||
ShouldResumeFarming = true;
|
||||
|
||||
// We aim to have a maximum of 2 tasks, one already parsing, and one waiting in the queue
|
||||
// This way we can call this function as many times as needed e.g. because of Steam events
|
||||
lock (EventSemaphore) {
|
||||
@@ -140,8 +147,19 @@ namespace ArchiSteamFarm {
|
||||
|
||||
internal async Task OnNewItemsNotification() {
|
||||
if (NowFarming) {
|
||||
FarmResetEvent.Set();
|
||||
return;
|
||||
await FarmingInitializationSemaphore.WaitAsync().ConfigureAwait(false);
|
||||
|
||||
try {
|
||||
if (NowFarming) {
|
||||
if (FarmingResetSemaphore.CurrentCount == 0) {
|
||||
FarmingResetSemaphore.Release();
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
} finally {
|
||||
FarmingInitializationSemaphore.Release();
|
||||
}
|
||||
}
|
||||
|
||||
// If we're not farming, and we got new items, it's likely to be a booster pack or likewise
|
||||
@@ -155,25 +173,36 @@ namespace ArchiSteamFarm {
|
||||
}
|
||||
|
||||
Paused = true;
|
||||
if (NowFarming) {
|
||||
await StopFarming().ConfigureAwait(false);
|
||||
|
||||
if (!NowFarming) {
|
||||
return;
|
||||
}
|
||||
|
||||
await StopFarming().ConfigureAwait(false);
|
||||
}
|
||||
|
||||
internal async Task Resume(bool userAction) {
|
||||
internal async Task<bool> Resume(bool userAction) {
|
||||
if (StickyPause) {
|
||||
if (!userAction) {
|
||||
Bot.ArchiLogger.LogGenericInfo(Strings.IgnoredStickyPauseEnabled);
|
||||
return;
|
||||
return false;
|
||||
}
|
||||
|
||||
StickyPause = false;
|
||||
}
|
||||
|
||||
Paused = false;
|
||||
if (!NowFarming) {
|
||||
await StartFarming().ConfigureAwait(false);
|
||||
|
||||
if (NowFarming) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (!userAction && !ShouldResumeFarming) {
|
||||
return false;
|
||||
}
|
||||
|
||||
await StartFarming().ConfigureAwait(false);
|
||||
return true;
|
||||
}
|
||||
|
||||
internal void SetInitialState(bool paused) => StickyPause = Paused = paused;
|
||||
@@ -188,7 +217,7 @@ namespace ArchiSteamFarm {
|
||||
return;
|
||||
}
|
||||
|
||||
await FarmingSemaphore.WaitAsync().ConfigureAwait(false);
|
||||
await FarmingInitializationSemaphore.WaitAsync().ConfigureAwait(false);
|
||||
|
||||
try {
|
||||
if (NowFarming || Paused || !Bot.IsPlayingPossible) {
|
||||
@@ -231,7 +260,7 @@ namespace ArchiSteamFarm {
|
||||
KeepFarming = NowFarming = true;
|
||||
Utilities.StartBackgroundFunction(Farm);
|
||||
} finally {
|
||||
FarmingSemaphore.Release();
|
||||
FarmingInitializationSemaphore.Release();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -240,7 +269,7 @@ namespace ArchiSteamFarm {
|
||||
return;
|
||||
}
|
||||
|
||||
await FarmingSemaphore.WaitAsync().ConfigureAwait(false);
|
||||
await FarmingInitializationSemaphore.WaitAsync().ConfigureAwait(false);
|
||||
|
||||
try {
|
||||
if (!NowFarming) {
|
||||
@@ -248,9 +277,12 @@ namespace ArchiSteamFarm {
|
||||
}
|
||||
|
||||
KeepFarming = false;
|
||||
FarmResetEvent.Set();
|
||||
|
||||
for (byte i = 0; (i < 5) && NowFarming; i++) {
|
||||
if (FarmingResetSemaphore.CurrentCount == 0) {
|
||||
FarmingResetSemaphore.Release();
|
||||
}
|
||||
|
||||
for (byte i = 0; (i < WebBrowser.MaxTries) && NowFarming; i++) {
|
||||
await Task.Delay(1000).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
@@ -259,9 +291,9 @@ namespace ArchiSteamFarm {
|
||||
}
|
||||
|
||||
Bot.ArchiLogger.LogGenericInfo(Strings.IdlingStopped);
|
||||
Bot.OnFarmingStopped();
|
||||
await Bot.OnFarmingStopped().ConfigureAwait(false);
|
||||
} finally {
|
||||
FarmingSemaphore.Release();
|
||||
FarmingInitializationSemaphore.Release();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -334,7 +366,7 @@ namespace ArchiSteamFarm {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (GlobalConfig.GlobalBlacklist.Contains(appID) || Program.GlobalConfig.Blacklist.Contains(appID)) {
|
||||
if (GlobalConfig.GamesBlacklist.Contains(appID) || GlobalConfig.SalesBlacklist.Contains(appID) || Program.GlobalConfig.Blacklist.Contains(appID)) {
|
||||
// We have this appID blacklisted, so skip it
|
||||
continue;
|
||||
}
|
||||
@@ -556,65 +588,72 @@ namespace ArchiSteamFarm {
|
||||
// If we have restricted card drops, we use complex algorithm
|
||||
Bot.ArchiLogger.LogGenericInfo(string.Format(Strings.ChosenFarmingAlgorithm, "Complex"));
|
||||
while (GamesToFarm.Count > 0) {
|
||||
HashSet<Game> playableGamesToFarmSolo = new HashSet<Game>();
|
||||
foreach (Game game in GamesToFarm.Where(game => game.HoursPlayed >= HoursToBump)) {
|
||||
if (await IsPlayableGame(game).ConfigureAwait(false)) {
|
||||
playableGamesToFarmSolo.Add(game);
|
||||
}
|
||||
}
|
||||
HashSet<Game> gamesToCheck = new HashSet<Game>(GamesToFarm.Count > 1 ? GamesToFarm.Where(game => game.HoursPlayed >= HoursToBump) : GamesToFarm);
|
||||
|
||||
if (playableGamesToFarmSolo.Count > 0) {
|
||||
while (playableGamesToFarmSolo.Count > 0) {
|
||||
Game playableGame = playableGamesToFarmSolo.First();
|
||||
if (await FarmSolo(playableGame).ConfigureAwait(false)) {
|
||||
playableGamesToFarmSolo.Remove(playableGame);
|
||||
} else {
|
||||
NowFarming = false;
|
||||
return;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
HashSet<Game> playableGamesToFarmMultiple = new HashSet<Game>();
|
||||
foreach (Game game in GamesToFarm.Where(game => game.HoursPlayed < HoursToBump).OrderByDescending(game => game.HoursPlayed)) {
|
||||
if (await IsPlayableGame(game).ConfigureAwait(false)) {
|
||||
playableGamesToFarmMultiple.Add(game);
|
||||
if (gamesToCheck.Count > 0) {
|
||||
foreach (Game game in gamesToCheck) {
|
||||
if (!await IsPlayableGame(game).ConfigureAwait(false)) {
|
||||
GamesToFarm.Remove(game);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (playableGamesToFarmMultiple.Count >= ArchiHandler.MaxGamesPlayedConcurrently) {
|
||||
break;
|
||||
if (await FarmSolo(game).ConfigureAwait(false)) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
if (FarmMultiple(playableGamesToFarmMultiple)) {
|
||||
Bot.ArchiLogger.LogGenericInfo(string.Format(Strings.IdlingFinishedForGames, string.Join(", ", playableGamesToFarmMultiple.Select(game => game.AppID))));
|
||||
} else {
|
||||
NowFarming = false;
|
||||
return;
|
||||
}
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
gamesToCheck = new HashSet<Game>(GamesToFarm.OrderByDescending(game => game.HoursPlayed));
|
||||
HashSet<Game> playableGamesToFarmMultiple = new HashSet<Game>();
|
||||
|
||||
foreach (Game game in gamesToCheck) {
|
||||
if (!await IsPlayableGame(game).ConfigureAwait(false)) {
|
||||
GamesToFarm.Remove(game);
|
||||
continue;
|
||||
}
|
||||
|
||||
playableGamesToFarmMultiple.Add(game);
|
||||
if (playableGamesToFarmMultiple.Count >= ArchiHandler.MaxGamesPlayedConcurrently) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (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 {
|
||||
NowFarming = false;
|
||||
return;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// If we have unrestricted card drops, we use simple algorithm
|
||||
Bot.ArchiLogger.LogGenericInfo(string.Format(Strings.ChosenFarmingAlgorithm, "Simple"));
|
||||
|
||||
while (GamesToFarm.Count > 0) {
|
||||
Game playableGame = null;
|
||||
foreach (Game game in GamesToFarm) {
|
||||
HashSet<Game> gamesToCheck = new HashSet<Game>(GamesToFarm);
|
||||
|
||||
foreach (Game game in gamesToCheck) {
|
||||
if (!await IsPlayableGame(game).ConfigureAwait(false)) {
|
||||
GamesToFarm.Remove(game);
|
||||
continue;
|
||||
}
|
||||
|
||||
playableGame = game;
|
||||
break;
|
||||
}
|
||||
|
||||
if (playableGame != null) {
|
||||
if (await FarmSolo(playableGame).ConfigureAwait(false)) {
|
||||
if (await FarmSolo(game).ConfigureAwait(false)) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
NowFarming = false;
|
||||
return;
|
||||
NowFarming = false;
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
} while ((await IsAnythingToFarm().ConfigureAwait(false)).GetValueOrDefault());
|
||||
@@ -638,7 +677,7 @@ namespace ArchiSteamFarm {
|
||||
Bot.ArchiLogger.LogGenericWarning(string.Format(Strings.WarningIdlingGameMismatch, game.AppID, game.GameName, game.PlayableAppID));
|
||||
}
|
||||
|
||||
Bot.IdleGame(game.PlayableAppID);
|
||||
await Bot.IdleGames(game.PlayableAppID.ToEnumerable()).ConfigureAwait(false);
|
||||
DateTime endFarmingDate = DateTime.UtcNow.AddHours(Program.GlobalConfig.MaxFarmingTime);
|
||||
|
||||
bool? keepFarming = await ShouldFarm(game).ConfigureAwait(false);
|
||||
@@ -646,8 +685,7 @@ namespace ArchiSteamFarm {
|
||||
Bot.ArchiLogger.LogGenericInfo(string.Format(Strings.StillIdling, game.AppID, game.GameName));
|
||||
|
||||
DateTime startFarmingPeriod = DateTime.UtcNow;
|
||||
if (FarmResetEvent.Wait(60 * 1000 * Program.GlobalConfig.FarmingDelay)) {
|
||||
FarmResetEvent.Reset();
|
||||
if (await FarmingResetSemaphore.WaitAsync(60 * 1000 * Program.GlobalConfig.FarmingDelay).ConfigureAwait(false)) {
|
||||
success = KeepFarming;
|
||||
}
|
||||
|
||||
@@ -665,7 +703,7 @@ namespace ArchiSteamFarm {
|
||||
return success;
|
||||
}
|
||||
|
||||
private bool FarmHours(ConcurrentHashSet<Game> games) {
|
||||
private async Task<bool> FarmHours(ConcurrentHashSet<Game> games) {
|
||||
if ((games == null) || (games.Count == 0)) {
|
||||
Bot.ArchiLogger.LogNullError(nameof(games));
|
||||
return false;
|
||||
@@ -682,15 +720,14 @@ namespace ArchiSteamFarm {
|
||||
return true;
|
||||
}
|
||||
|
||||
Bot.IdleGames(games.Select(game => game.PlayableAppID));
|
||||
await Bot.IdleGames(games.Select(game => game.PlayableAppID)).ConfigureAwait(false);
|
||||
|
||||
bool success = true;
|
||||
while (maxHour < 2) {
|
||||
while (maxHour < HoursToBump) {
|
||||
Bot.ArchiLogger.LogGenericInfo(string.Format(Strings.StillIdlingList, string.Join(", ", games.Select(game => game.AppID))));
|
||||
|
||||
DateTime startFarmingPeriod = DateTime.UtcNow;
|
||||
if (FarmResetEvent.Wait(60 * 1000 * Program.GlobalConfig.FarmingDelay)) {
|
||||
FarmResetEvent.Reset();
|
||||
if (await FarmingResetSemaphore.WaitAsync(60 * 1000 * Program.GlobalConfig.FarmingDelay).ConfigureAwait(false)) {
|
||||
success = KeepFarming;
|
||||
}
|
||||
|
||||
@@ -711,7 +748,7 @@ namespace ArchiSteamFarm {
|
||||
return success;
|
||||
}
|
||||
|
||||
private bool FarmMultiple(IEnumerable<Game> games) {
|
||||
private async Task<bool> FarmMultiple(IEnumerable<Game> games) {
|
||||
if (games == null) {
|
||||
Bot.ArchiLogger.LogNullError(nameof(games));
|
||||
return false;
|
||||
@@ -721,7 +758,7 @@ namespace ArchiSteamFarm {
|
||||
|
||||
Bot.ArchiLogger.LogGenericInfo(string.Format(Strings.NowIdlingList, string.Join(", ", CurrentGamesFarming.Select(game => game.AppID))));
|
||||
|
||||
bool result = FarmHours(CurrentGamesFarming);
|
||||
bool result = await FarmHours(CurrentGamesFarming).ConfigureAwait(false);
|
||||
CurrentGamesFarming.Clear();
|
||||
return result;
|
||||
}
|
||||
@@ -846,15 +883,17 @@ namespace ArchiSteamFarm {
|
||||
}
|
||||
|
||||
if (GamesToFarm.Count == 0) {
|
||||
ShouldResumeFarming = false;
|
||||
return false;
|
||||
}
|
||||
|
||||
ShouldResumeFarming = true;
|
||||
SortGamesToFarm();
|
||||
return true;
|
||||
}
|
||||
|
||||
private async Task<bool> IsPlayableGame(Game game) {
|
||||
(uint PlayableAppID, DateTime IgnoredUntil) appData = await Bot.GetAppDataForIdling(game.AppID).ConfigureAwait(false);
|
||||
(uint PlayableAppID, DateTime IgnoredUntil) appData = await Bot.GetAppDataForIdling(game.AppID, game.HoursPlayed).ConfigureAwait(false);
|
||||
if (appData.PlayableAppID != 0) {
|
||||
game.PlayableAppID = appData.PlayableAppID;
|
||||
return true;
|
||||
|
||||
@@ -31,12 +31,12 @@ namespace ArchiSteamFarm {
|
||||
internal sealed class ConcurrentSortedHashSet<T> : IDisposable, IReadOnlyCollection<T>, ISet<T> {
|
||||
public int Count {
|
||||
get {
|
||||
SemaphoreSlim.Wait();
|
||||
CollectionSemaphore.Wait();
|
||||
|
||||
try {
|
||||
return BackingCollection.Count;
|
||||
} finally {
|
||||
SemaphoreSlim.Release();
|
||||
CollectionSemaphore.Release();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -44,159 +44,159 @@ namespace ArchiSteamFarm {
|
||||
public bool IsReadOnly => false;
|
||||
|
||||
private readonly HashSet<T> BackingCollection = new HashSet<T>();
|
||||
private readonly SemaphoreSlim SemaphoreSlim = new SemaphoreSlim(1);
|
||||
private readonly SemaphoreSlim CollectionSemaphore = new SemaphoreSlim(1, 1);
|
||||
|
||||
public bool Add(T item) {
|
||||
SemaphoreSlim.Wait();
|
||||
CollectionSemaphore.Wait();
|
||||
|
||||
try {
|
||||
return BackingCollection.Add(item);
|
||||
} finally {
|
||||
SemaphoreSlim.Release();
|
||||
CollectionSemaphore.Release();
|
||||
}
|
||||
}
|
||||
|
||||
public void Clear() {
|
||||
SemaphoreSlim.Wait();
|
||||
CollectionSemaphore.Wait();
|
||||
|
||||
try {
|
||||
BackingCollection.Clear();
|
||||
} finally {
|
||||
SemaphoreSlim.Release();
|
||||
CollectionSemaphore.Release();
|
||||
}
|
||||
}
|
||||
|
||||
public bool Contains(T item) {
|
||||
SemaphoreSlim.Wait();
|
||||
CollectionSemaphore.Wait();
|
||||
|
||||
try {
|
||||
return BackingCollection.Contains(item);
|
||||
} finally {
|
||||
SemaphoreSlim.Release();
|
||||
CollectionSemaphore.Release();
|
||||
}
|
||||
}
|
||||
|
||||
public void CopyTo(T[] array, int arrayIndex) {
|
||||
SemaphoreSlim.Wait();
|
||||
CollectionSemaphore.Wait();
|
||||
|
||||
try {
|
||||
BackingCollection.CopyTo(array, arrayIndex);
|
||||
} finally {
|
||||
SemaphoreSlim.Release();
|
||||
CollectionSemaphore.Release();
|
||||
}
|
||||
}
|
||||
|
||||
public void Dispose() => SemaphoreSlim.Dispose();
|
||||
public void Dispose() => CollectionSemaphore.Dispose();
|
||||
|
||||
public void ExceptWith(IEnumerable<T> other) {
|
||||
SemaphoreSlim.Wait();
|
||||
CollectionSemaphore.Wait();
|
||||
|
||||
try {
|
||||
BackingCollection.ExceptWith(other);
|
||||
} finally {
|
||||
SemaphoreSlim.Release();
|
||||
CollectionSemaphore.Release();
|
||||
}
|
||||
}
|
||||
|
||||
public IEnumerator<T> GetEnumerator() => new ConcurrentEnumerator<T>(BackingCollection, SemaphoreSlim);
|
||||
public IEnumerator<T> GetEnumerator() => new ConcurrentEnumerator<T>(BackingCollection, CollectionSemaphore);
|
||||
|
||||
public void IntersectWith(IEnumerable<T> other) {
|
||||
SemaphoreSlim.Wait();
|
||||
CollectionSemaphore.Wait();
|
||||
|
||||
try {
|
||||
BackingCollection.IntersectWith(other);
|
||||
} finally {
|
||||
SemaphoreSlim.Release();
|
||||
CollectionSemaphore.Release();
|
||||
}
|
||||
}
|
||||
|
||||
public bool IsProperSubsetOf(IEnumerable<T> other) {
|
||||
SemaphoreSlim.Wait();
|
||||
CollectionSemaphore.Wait();
|
||||
|
||||
try {
|
||||
return BackingCollection.IsProperSubsetOf(other);
|
||||
} finally {
|
||||
SemaphoreSlim.Release();
|
||||
CollectionSemaphore.Release();
|
||||
}
|
||||
}
|
||||
|
||||
public bool IsProperSupersetOf(IEnumerable<T> other) {
|
||||
SemaphoreSlim.Wait();
|
||||
CollectionSemaphore.Wait();
|
||||
|
||||
try {
|
||||
return BackingCollection.IsProperSupersetOf(other);
|
||||
} finally {
|
||||
SemaphoreSlim.Release();
|
||||
CollectionSemaphore.Release();
|
||||
}
|
||||
}
|
||||
|
||||
public bool IsSubsetOf(IEnumerable<T> other) {
|
||||
SemaphoreSlim.Wait();
|
||||
CollectionSemaphore.Wait();
|
||||
|
||||
try {
|
||||
return BackingCollection.IsSubsetOf(other);
|
||||
} finally {
|
||||
SemaphoreSlim.Release();
|
||||
CollectionSemaphore.Release();
|
||||
}
|
||||
}
|
||||
|
||||
public bool IsSupersetOf(IEnumerable<T> other) {
|
||||
SemaphoreSlim.Wait();
|
||||
CollectionSemaphore.Wait();
|
||||
|
||||
try {
|
||||
return BackingCollection.IsSupersetOf(other);
|
||||
} finally {
|
||||
SemaphoreSlim.Release();
|
||||
CollectionSemaphore.Release();
|
||||
}
|
||||
}
|
||||
|
||||
public bool Overlaps(IEnumerable<T> other) {
|
||||
SemaphoreSlim.Wait();
|
||||
CollectionSemaphore.Wait();
|
||||
|
||||
try {
|
||||
return BackingCollection.Overlaps(other);
|
||||
} finally {
|
||||
SemaphoreSlim.Release();
|
||||
CollectionSemaphore.Release();
|
||||
}
|
||||
}
|
||||
|
||||
public bool Remove(T item) {
|
||||
SemaphoreSlim.Wait();
|
||||
CollectionSemaphore.Wait();
|
||||
|
||||
try {
|
||||
return BackingCollection.Remove(item);
|
||||
} finally {
|
||||
SemaphoreSlim.Release();
|
||||
CollectionSemaphore.Release();
|
||||
}
|
||||
}
|
||||
|
||||
public bool SetEquals(IEnumerable<T> other) {
|
||||
SemaphoreSlim.Wait();
|
||||
CollectionSemaphore.Wait();
|
||||
|
||||
try {
|
||||
return BackingCollection.SetEquals(other);
|
||||
} finally {
|
||||
SemaphoreSlim.Release();
|
||||
CollectionSemaphore.Release();
|
||||
}
|
||||
}
|
||||
|
||||
public void SymmetricExceptWith(IEnumerable<T> other) {
|
||||
SemaphoreSlim.Wait();
|
||||
CollectionSemaphore.Wait();
|
||||
|
||||
try {
|
||||
BackingCollection.SymmetricExceptWith(other);
|
||||
} finally {
|
||||
SemaphoreSlim.Release();
|
||||
CollectionSemaphore.Release();
|
||||
}
|
||||
}
|
||||
|
||||
public void UnionWith(IEnumerable<T> other) {
|
||||
SemaphoreSlim.Wait();
|
||||
CollectionSemaphore.Wait();
|
||||
|
||||
try {
|
||||
BackingCollection.UnionWith(other);
|
||||
} finally {
|
||||
SemaphoreSlim.Release();
|
||||
CollectionSemaphore.Release();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -204,7 +204,7 @@ namespace ArchiSteamFarm {
|
||||
IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
|
||||
|
||||
internal void ReplaceWith(IEnumerable<T> other) {
|
||||
SemaphoreSlim.Wait();
|
||||
CollectionSemaphore.Wait();
|
||||
|
||||
try {
|
||||
BackingCollection.Clear();
|
||||
@@ -213,7 +213,7 @@ namespace ArchiSteamFarm {
|
||||
BackingCollection.Add(item);
|
||||
}
|
||||
} finally {
|
||||
SemaphoreSlim.Release();
|
||||
CollectionSemaphore.Release();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -40,8 +40,8 @@ namespace ArchiSteamFarm {
|
||||
internal const byte DefaultLoginLimiterDelay = 10;
|
||||
internal const string UlongStringPrefix = "s_";
|
||||
|
||||
// This is hardcoded blacklist which should not be possible to change
|
||||
internal static readonly HashSet<uint> GlobalBlacklist = new HashSet<uint> { 267420, 303700, 335590, 368020, 402590, 425280, 480730, 566020, 639900 };
|
||||
internal static readonly HashSet<uint> GamesBlacklist = new HashSet<uint> { 402590 }; // Games with broken/unobtainable card drops
|
||||
internal static readonly HashSet<uint> SalesBlacklist = new HashSet<uint> { 267420, 303700, 335590, 368020, 425280, 480730, 566020, 639900 }; // Steam Summer/Winter sales
|
||||
|
||||
[JsonProperty(Required = Required.DisallowNull)]
|
||||
internal readonly bool AutoRestart = true;
|
||||
@@ -49,6 +49,9 @@ namespace ArchiSteamFarm {
|
||||
[JsonProperty(Required = Required.DisallowNull)]
|
||||
internal readonly bool AutoUpdates = true;
|
||||
|
||||
[JsonProperty(Required = Required.DisallowNull)]
|
||||
internal readonly byte BackgroundGCPeriod;
|
||||
|
||||
[SuppressMessage("ReSharper", "CollectionNeverUpdated.Global")]
|
||||
[JsonProperty(Required = Required.DisallowNull)]
|
||||
internal readonly HashSet<uint> Blacklist = new HashSet<uint>();
|
||||
@@ -78,7 +81,7 @@ namespace ArchiSteamFarm {
|
||||
#pragma warning restore 649
|
||||
|
||||
[JsonProperty(Required = Required.DisallowNull)]
|
||||
internal readonly byte IdleFarmingPeriod = 3;
|
||||
internal readonly byte IdleFarmingPeriod = 8;
|
||||
|
||||
[JsonProperty(Required = Required.DisallowNull)]
|
||||
internal readonly byte InventoryLimiterDelay = 3;
|
||||
@@ -102,7 +105,7 @@ namespace ArchiSteamFarm {
|
||||
internal readonly bool Statistics = true;
|
||||
|
||||
[JsonProperty(Required = Required.DisallowNull)]
|
||||
internal readonly ProtocolTypes SteamProtocols = ProtocolTypes.All;
|
||||
internal readonly ProtocolTypes SteamProtocols = ProtocolTypes.WebSocket;
|
||||
|
||||
[JsonProperty(Required = Required.DisallowNull)]
|
||||
internal readonly EUpdateChannel UpdateChannel = EUpdateChannel.Stable;
|
||||
|
||||
@@ -42,24 +42,11 @@ namespace ArchiSteamFarm {
|
||||
[JsonProperty(Required = Required.DisallowNull)]
|
||||
internal readonly InMemoryServerListProvider ServerListProvider = new InMemoryServerListProvider();
|
||||
|
||||
private readonly object FileLock = new object();
|
||||
private readonly SemaphoreSlim FileSemaphore = new SemaphoreSlim(1, 1);
|
||||
private readonly SemaphoreSlim PackagesRefreshSemaphore = new SemaphoreSlim(1, 1);
|
||||
|
||||
private readonly SemaphoreSlim PackagesRefreshSemaphore = new SemaphoreSlim(1);
|
||||
|
||||
internal uint CellID {
|
||||
get => _CellID;
|
||||
set {
|
||||
if ((value == 0) || (_CellID == value)) {
|
||||
return;
|
||||
}
|
||||
|
||||
_CellID = value;
|
||||
Save();
|
||||
}
|
||||
}
|
||||
|
||||
[JsonProperty(Required = Required.DisallowNull)]
|
||||
private uint _CellID;
|
||||
[JsonProperty(PropertyName = "_CellID", Required = Required.DisallowNull)]
|
||||
internal uint CellID { get; private set; }
|
||||
|
||||
private string FilePath;
|
||||
|
||||
@@ -70,13 +57,20 @@ namespace ArchiSteamFarm {
|
||||
}
|
||||
|
||||
FilePath = filePath;
|
||||
Save();
|
||||
Save().Wait();
|
||||
}
|
||||
|
||||
// This constructor is used only by deserializer
|
||||
private GlobalDatabase() => ServerListProvider.ServerListUpdated += OnServerListUpdated;
|
||||
|
||||
public void Dispose() => ServerListProvider.ServerListUpdated -= OnServerListUpdated;
|
||||
public void Dispose() {
|
||||
// Events we registered
|
||||
ServerListProvider.ServerListUpdated -= OnServerListUpdated;
|
||||
|
||||
// Those are objects that are always being created if constructor doesn't throw exception
|
||||
FileSemaphore.Dispose();
|
||||
PackagesRefreshSemaphore.Dispose();
|
||||
}
|
||||
|
||||
internal static GlobalDatabase Load(string filePath) {
|
||||
if (string.IsNullOrEmpty(filePath)) {
|
||||
@@ -120,7 +114,7 @@ namespace ArchiSteamFarm {
|
||||
return;
|
||||
}
|
||||
|
||||
Dictionary<uint, HashSet<uint>> appIDsToPackageIDs = await bot.GetAppIDsToPackageIDs(missingPackageIDs);
|
||||
Dictionary<uint, HashSet<uint>> appIDsToPackageIDs = await bot.GetAppIDsToPackageIDs(missingPackageIDs).ConfigureAwait(false);
|
||||
if ((appIDsToPackageIDs == null) || (appIDsToPackageIDs.Count == 0)) {
|
||||
return;
|
||||
}
|
||||
@@ -136,35 +130,46 @@ namespace ArchiSteamFarm {
|
||||
}
|
||||
}
|
||||
|
||||
Save();
|
||||
await Save().ConfigureAwait(false);
|
||||
} finally {
|
||||
PackagesRefreshSemaphore.Release();
|
||||
}
|
||||
}
|
||||
|
||||
private void OnServerListUpdated(object sender, EventArgs e) => Save();
|
||||
internal async Task SetCellID(uint value = 0) {
|
||||
if (value == CellID) {
|
||||
return;
|
||||
}
|
||||
|
||||
private void Save() {
|
||||
CellID = value;
|
||||
await Save().ConfigureAwait(false);
|
||||
}
|
||||
|
||||
private async void OnServerListUpdated(object sender, EventArgs e) => await Save().ConfigureAwait(false);
|
||||
|
||||
private async Task Save() {
|
||||
string json = JsonConvert.SerializeObject(this);
|
||||
if (string.IsNullOrEmpty(json)) {
|
||||
ASF.ArchiLogger.LogNullError(nameof(json));
|
||||
return;
|
||||
}
|
||||
|
||||
lock (FileLock) {
|
||||
string newFilePath = FilePath + ".new";
|
||||
string newFilePath = FilePath + ".new";
|
||||
|
||||
try {
|
||||
File.WriteAllText(newFilePath, json);
|
||||
await FileSemaphore.WaitAsync().ConfigureAwait(false);
|
||||
|
||||
if (File.Exists(FilePath)) {
|
||||
File.Replace(newFilePath, FilePath, null);
|
||||
} else {
|
||||
File.Move(newFilePath, FilePath);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
ASF.ArchiLogger.LogGenericException(e);
|
||||
try {
|
||||
await File.WriteAllTextAsync(newFilePath, json).ConfigureAwait(false);
|
||||
|
||||
if (File.Exists(FilePath)) {
|
||||
File.Replace(newFilePath, FilePath, null);
|
||||
} else {
|
||||
File.Move(newFilePath, FilePath);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
ASF.ArchiLogger.LogGenericException(e);
|
||||
} finally {
|
||||
FileSemaphore.Release();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -28,18 +28,21 @@ using System.Threading;
|
||||
|
||||
namespace ArchiSteamFarm {
|
||||
internal static class Hacks {
|
||||
private const byte GarbageCollectorDelay = 1;
|
||||
|
||||
private static Timer GarbageCollectionTimer;
|
||||
private static Timer GarbageCompactionTimer;
|
||||
|
||||
internal static void Init() {
|
||||
internal static void EnableBackgroundGC(byte period) {
|
||||
if (period == 0) {
|
||||
ASF.ArchiLogger.LogNullError(nameof(period));
|
||||
return;
|
||||
}
|
||||
|
||||
if (GarbageCollectionTimer == null) {
|
||||
GarbageCollectionTimer = new Timer(
|
||||
e => GC.Collect(),
|
||||
null,
|
||||
TimeSpan.FromSeconds(GarbageCollectorDelay), // Delay
|
||||
TimeSpan.FromSeconds(GarbageCollectorDelay) // Period
|
||||
TimeSpan.FromSeconds(period), // Delay
|
||||
TimeSpan.FromSeconds(period) // Period
|
||||
);
|
||||
}
|
||||
|
||||
@@ -47,8 +50,8 @@ namespace ArchiSteamFarm {
|
||||
GarbageCompactionTimer = new Timer(
|
||||
e => GCSettings.LargeObjectHeapCompactionMode = GCLargeObjectHeapCompactionMode.CompactOnce,
|
||||
null,
|
||||
TimeSpan.FromMinutes(GarbageCollectorDelay), // Delay
|
||||
TimeSpan.FromMinutes(GarbageCollectorDelay) // Period
|
||||
TimeSpan.FromMinutes(period), // Delay
|
||||
TimeSpan.FromMinutes(period) // Period
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -45,6 +45,14 @@ namespace ArchiSteamFarm {
|
||||
return;
|
||||
}
|
||||
|
||||
switch (host) {
|
||||
case "0.0.0.0":
|
||||
case "::":
|
||||
// Silently map INADDR_ANY to match HttpListener expectations
|
||||
host = "*";
|
||||
break;
|
||||
}
|
||||
|
||||
string url = "http://" + host + ":" + port + "/" + nameof(IPC) + "/";
|
||||
HttpListener.Prefixes.Add(url);
|
||||
}
|
||||
@@ -104,7 +112,7 @@ namespace ArchiSteamFarm {
|
||||
switch (key) {
|
||||
case "command":
|
||||
string command = context.Request.QueryString.Get(i);
|
||||
if (string.IsNullOrEmpty(command)) {
|
||||
if (string.IsNullOrWhiteSpace(command)) {
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -167,12 +175,14 @@ namespace ArchiSteamFarm {
|
||||
response.StatusCode = (ushort) statusCode;
|
||||
}
|
||||
|
||||
response.AppendHeader("Access-Control-Allow-Origin", "null");
|
||||
|
||||
Encoding encoding = Encoding.UTF8;
|
||||
|
||||
response.ContentEncoding = encoding;
|
||||
response.ContentType = "text/plain; charset=" + encoding.WebName;
|
||||
|
||||
string html = "<html><head><meta http-equiv=\"Content-Type\" content=\"text/html; charset=utf-8\"></head><body><p>" + message + "</p></body></html>";
|
||||
|
||||
byte[] buffer = encoding.GetBytes(html);
|
||||
byte[] buffer = encoding.GetBytes(message + Environment.NewLine);
|
||||
response.ContentLength64 = buffer.Length;
|
||||
|
||||
await response.OutputStream.WriteAsync(buffer, 0, buffer.Length).ConfigureAwait(false);
|
||||
|
||||
18
ArchiSteamFarm/Localization/Strings.Designer.cs
generated
18
ArchiSteamFarm/Localization/Strings.Designer.cs
generated
@@ -864,15 +864,6 @@ namespace ArchiSteamFarm.Localization {
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Wyszukuje zlokalizowany ciąg podobny do ciągu IPC service could not be started because of AddressAccessDeniedException! If you want to use IPC service provided by ASF, consider starting ASF as administrator, or giving proper permissions!.
|
||||
/// </summary>
|
||||
internal static string ErrorIPCAddressAccessDeniedException {
|
||||
get {
|
||||
return ResourceManager.GetString("ErrorIPCAddressAccessDeniedException", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Wyszukuje zlokalizowany ciąg podobny do ciągu {0} is empty!.
|
||||
/// </summary>
|
||||
@@ -927,15 +918,6 @@ namespace ArchiSteamFarm.Localization {
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Wyszukuje zlokalizowany ciąg podobny do ciągu Could not remove old ASF binary. Please remove {0} manually in order for update function to work!.
|
||||
/// </summary>
|
||||
internal static string ErrorRemovingOldBinary {
|
||||
get {
|
||||
return ResourceManager.GetString("ErrorRemovingOldBinary", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Wyszukuje zlokalizowany ciąg podobny do ciągu Request failed after {0} attempts!.
|
||||
/// </summary>
|
||||
|
||||
@@ -157,7 +157,6 @@
|
||||
|
||||
|
||||
|
||||
|
||||
<data name="WarningFailed" xml:space="preserve">
|
||||
<value>فشل!</value>
|
||||
</data>
|
||||
@@ -216,7 +215,6 @@
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
<data name="Done" xml:space="preserve">
|
||||
|
||||
@@ -121,7 +121,10 @@
|
||||
<value>Приемане на замяната: {0}</value>
|
||||
<comment>{0} will be replaced by trade number</comment>
|
||||
</data>
|
||||
|
||||
<data name="AutoUpdateCheckInfo" xml:space="preserve">
|
||||
<value>ASF автоматично ще проверява за нови версии на всеки {0} час/а/.</value>
|
||||
<comment>{0} will be replaced by translated TimeSpan string (such as "24 hours")</comment>
|
||||
</data>
|
||||
<data name="Content" xml:space="preserve">
|
||||
<value>Съдържание: {0}</value>
|
||||
<comment>{0} will be replaced by content string. Please note that this string should include newline for formatting.</comment>
|
||||
@@ -167,10 +170,6 @@
|
||||
<value>Разборът {0} се провали!</value>
|
||||
<comment>{0} will be replaced by object's name</comment>
|
||||
</data>
|
||||
<data name="ErrorRemovingOldBinary" xml:space="preserve">
|
||||
<value>Не може да се премахне старият ASF файл. Моля премахнете {0} ръчно, за може обновлението да сработи!</value>
|
||||
<comment>{0} will be replaced by file's path</comment>
|
||||
</data>
|
||||
<data name="ErrorRequestFailedTooManyTimes" xml:space="preserve">
|
||||
<value>Заявката не е изпълнена след {0} опити!</value>
|
||||
<comment>{0} will be replaced by maximum number of tries</comment>
|
||||
@@ -178,14 +177,19 @@
|
||||
<data name="ErrorUpdateCheckFailed" xml:space="preserve">
|
||||
<value>Не успя да се провери за последната версия!</value>
|
||||
</data>
|
||||
|
||||
<data name="ErrorUpdateNoAssetForThisVersion" xml:space="preserve">
|
||||
<value>Не може да се продължи с обновяването, защото няма такава възможност за сегашната Ви версия! Автоматично обновяване на тази версия не е възможно.</value>
|
||||
</data>
|
||||
<data name="ErrorUpdateNoAssets" xml:space="preserve">
|
||||
<value>Не може да се премине към обновление, защото тази версия не включва никакви файлове!</value>
|
||||
</data>
|
||||
<data name="ErrorUserInputRunningInHeadlessMode" xml:space="preserve">
|
||||
<value>Получено е желание за промяна от потребителя, но процеса продължава в режим без възможност за промяна!</value>
|
||||
</data>
|
||||
|
||||
<data name="ErrorIPCAccessDenied" xml:space="preserve">
|
||||
<value>Отказва да обработи заявката, защото SteamOwnerID не е зададено!</value>
|
||||
<comment>SteamOwnerID is name of bot config property, it should not be translated</comment>
|
||||
</data>
|
||||
<data name="Exiting" xml:space="preserve">
|
||||
<value>Излизане…</value>
|
||||
</data>
|
||||
@@ -235,7 +239,10 @@
|
||||
<data name="UpdateCheckingNewVersion" xml:space="preserve">
|
||||
<value>Проверяване за нова версия...</value>
|
||||
</data>
|
||||
|
||||
<data name="UpdateDownloadingNewVersion" xml:space="preserve">
|
||||
<value>В момента се сваля новата версия: {0} ({1} MB)... Докато чакате, помислете за дарение, ако оценявате свършената от нас работа! :)</value>
|
||||
<comment>{0} will be replaced by version string, {1} will be replaced by update size (in megabytes)</comment>
|
||||
</data>
|
||||
<data name="UpdateFinished" xml:space="preserve">
|
||||
<value>Обновлението приключи!</value>
|
||||
</data>
|
||||
@@ -274,7 +281,10 @@
|
||||
<value>Моля, въведете недокументирана стойност от {0}: </value>
|
||||
<comment>{0} will be replaced by property name. Please note that this translation should end with space</comment>
|
||||
</data>
|
||||
|
||||
<data name="UserInputIPCHost" xml:space="preserve">
|
||||
<value>Моля, въведете Вашият IPC host: </value>
|
||||
<comment>Please note that this translation should end with space</comment>
|
||||
</data>
|
||||
<data name="WarningUnknownValuePleaseReport" xml:space="preserve">
|
||||
<value>Получена е непонятна стойност за {0}, моля съобщете за това: {1}</value>
|
||||
<comment>{0} will be replaced by object's name, {1} will be replaced by value for that object</comment>
|
||||
@@ -283,10 +293,17 @@
|
||||
<value>Играенето на повече от {0} игри едновременно е невъзможно, само първите {0} от {1} ще бъдат ползвани!</value>
|
||||
<comment>{0} will be replaced by max number of games, {1} will be replaced by name of the configuration property</comment>
|
||||
</data>
|
||||
|
||||
|
||||
|
||||
|
||||
<data name="IPCAnswered" xml:space="preserve">
|
||||
<value>Отговоряне на IPC командата: {0} с {1}</value>
|
||||
<comment>{0} will be replaced by IPC command, {1} will be replaced by IPC answer</comment>
|
||||
</data>
|
||||
<data name="IPCReady" xml:space="preserve">
|
||||
<value>IPC сървърът е готов!</value>
|
||||
</data>
|
||||
<data name="IPCStarting" xml:space="preserve">
|
||||
<value>Стартиране IPC сървър на {0}...</value>
|
||||
<comment>{0} will be replaced by IPC hostname</comment>
|
||||
</data>
|
||||
<data name="BotAlreadyStopped" xml:space="preserve">
|
||||
<value>Този бот вече е спрян!</value>
|
||||
</data>
|
||||
@@ -377,13 +394,20 @@
|
||||
<data name="UnknownCommand" xml:space="preserve">
|
||||
<value>Непозната команда!</value>
|
||||
</data>
|
||||
|
||||
|
||||
<data name="WarningCouldNotCheckBadges" xml:space="preserve">
|
||||
<value>Не е възможно да се набави информация за значките, ще опитаме отново по-късно!</value>
|
||||
</data>
|
||||
<data name="WarningCouldNotCheckCardsStatus" xml:space="preserve">
|
||||
<value>Не може да се провери статуса на картите за: {0} ({1}), ще опитаме отново по-късно!</value>
|
||||
<comment>{0} will be replaced by game's ID (number), {1} will be replaced by game's name</comment>
|
||||
</data>
|
||||
<data name="BotAcceptingGift" xml:space="preserve">
|
||||
<value>Приемане на подарък: {0}...</value>
|
||||
<comment>{0} will be replaced by giftID (number)</comment>
|
||||
</data>
|
||||
|
||||
<data name="BotAccountLimited" xml:space="preserve">
|
||||
<value>Този акаунт е ограничен, процесът по вадене на карти е невъзможен докато ограничението не бъде премахнато!</value>
|
||||
</data>
|
||||
<data name="BotAddLicense" xml:space="preserve">
|
||||
<value>ID: {0} | Статус: {1}</value>
|
||||
<comment>{0} will be replaced by game's ID (number), {1} will be replaced by status string</comment>
|
||||
@@ -398,17 +422,32 @@
|
||||
<data name="BotAuthenticatorConverting" xml:space="preserve">
|
||||
<value>Превръщането на .maFile в ASF формат...</value>
|
||||
</data>
|
||||
|
||||
|
||||
<data name="BotAuthenticatorImportFinished" xml:space="preserve">
|
||||
<value>Успешно приключи въвеждането на мобилното потвърждение!</value>
|
||||
</data>
|
||||
<data name="BotAuthenticatorInvalidDeviceID" xml:space="preserve">
|
||||
<value>Вашият DeviceID е неправилен или не съществува!</value>
|
||||
</data>
|
||||
<data name="BotAuthenticatorToken" xml:space="preserve">
|
||||
<value>2FA Token: {0}</value>
|
||||
<comment>{0} will be replaced by generated 2FA token (string)</comment>
|
||||
</data>
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
<data name="BotAutomaticIdlingNowPaused" xml:space="preserve">
|
||||
<value>Автоматичното вадене на карти е временно прекратено!</value>
|
||||
</data>
|
||||
<data name="BotAutomaticIdlingNowResumed" xml:space="preserve">
|
||||
<value>Автоматичното вадене на карти е възобновено!</value>
|
||||
</data>
|
||||
<data name="BotAutomaticIdlingPausedAlready" xml:space="preserve">
|
||||
<value>Автоматичното вадене на карти е вече временно прекратено!</value>
|
||||
</data>
|
||||
<data name="BotAutomaticIdlingPausedWithCountdown" xml:space="preserve">
|
||||
<value>Автоматичното вадене на карти е вече прекратено! Имате {0} за да стартирате игра.</value>
|
||||
<comment>{0} will be replaced by translated TimeSpan string (such as "5 minutes")</comment>
|
||||
</data>
|
||||
<data name="BotAutomaticIdlingResumedAlready" xml:space="preserve">
|
||||
<value>Автоматичното вадене на карти е вече възобновено!</value>
|
||||
</data>
|
||||
<data name="BotConnected" xml:space="preserve">
|
||||
<value>Свързан към Steam!</value>
|
||||
</data>
|
||||
@@ -422,30 +461,56 @@
|
||||
<value>[{0}] парола: {1}</value>
|
||||
<comment>{0} will be replaced by password encryption method (string), {1} will be replaced by encrypted password using that method (string)</comment>
|
||||
</data>
|
||||
|
||||
|
||||
|
||||
<data name="BotInstanceNotStartingBecauseDisabled" xml:space="preserve">
|
||||
<value>Не се стартира този бот, защото е изключен във файлът с настройки!</value>
|
||||
</data>
|
||||
<data name="BotInvalidAuthenticatorDuringLogin" xml:space="preserve">
|
||||
<value>Получен е TwoFactorCodeMismatch код за грешка {0} пъти поред, това почти винаги означава невалидни ASF 2FA стойности, прекратяване!</value>
|
||||
<comment>{0} will be replaced by maximum allowed number of failed 2FA attempts</comment>
|
||||
</data>
|
||||
<data name="BotLoggedOff" xml:space="preserve">
|
||||
<value>Излязъл от Steam: {0}</value>
|
||||
<comment>{0} will be replaced by logging off reason (string)</comment>
|
||||
</data>
|
||||
<data name="BotLoggedOn" xml:space="preserve">
|
||||
<value>Влезли сте успешно!</value>
|
||||
</data>
|
||||
<data name="BotLoggingIn" xml:space="preserve">
|
||||
<value>Влизане…</value>
|
||||
</data>
|
||||
|
||||
<data name="BotLogonSessionReplaced" xml:space="preserve">
|
||||
<value>Този потребител изглежда се ползва в друг ASF работещ в момента, това е неправилно, отказване да продължи работата му!</value>
|
||||
</data>
|
||||
<data name="BotLootingFailed" xml:space="preserve">
|
||||
<value>Предложението за замяна е неуспешно!</value>
|
||||
</data>
|
||||
|
||||
|
||||
|
||||
|
||||
<data name="BotLootingMasterNotDefined" xml:space="preserve">
|
||||
<value>Предложението за размяна не може да бъде избратено, защото няма потребител с master разрешително!</value>
|
||||
</data>
|
||||
<data name="BotLootingNoLootableTypes" xml:space="preserve">
|
||||
<value>Нямата никакви видове грабене зададени!</value>
|
||||
</data>
|
||||
<data name="BotLootingNowDisabled" xml:space="preserve">
|
||||
<value>Грабенето сега е изключено!</value>
|
||||
</data>
|
||||
<data name="BotLootingNowEnabled" xml:space="preserve">
|
||||
<value>Грабенето сега е включено!</value>
|
||||
</data>
|
||||
<data name="BotLootingSuccess" xml:space="preserve">
|
||||
<value>Предложението за замяна е изпратено успешно!</value>
|
||||
</data>
|
||||
|
||||
|
||||
|
||||
|
||||
<data name="BotLootingTemporarilyDisabled" xml:space="preserve">
|
||||
<value>Грабенето е временно изключено!</value>
|
||||
</data>
|
||||
<data name="BotLootingYourself" xml:space="preserve">
|
||||
<value>Не можете да ограбите себе си!</value>
|
||||
</data>
|
||||
<data name="BotNoASFAuthenticator" xml:space="preserve">
|
||||
<value>Този бот няма ASF 2FA активиран! Да не би да сте забравили да въведете своят authenticator като ASF 2FA?</value>
|
||||
</data>
|
||||
<data name="BotNotConnected" xml:space="preserve">
|
||||
<value>Този бот не е свързан!</value>
|
||||
</data>
|
||||
<data name="BotNotOwnedYet" xml:space="preserve">
|
||||
<value>Все още не се притежава: {0}</value>
|
||||
<comment>{0} will be replaced by query (string)</comment>
|
||||
@@ -454,7 +519,10 @@
|
||||
<value>Вече се притежава: {0} | {1}</value>
|
||||
<comment>{0} will be replaced by game's ID (number), {1} will be replaced by game's name</comment>
|
||||
</data>
|
||||
|
||||
<data name="BotRateLimitExceeded" xml:space="preserve">
|
||||
<value>Ограничението надвишено; Ще повторим след {0} на "отброяване"...</value>
|
||||
<comment>{0} will be replaced by translated TimeSpan string (such as "25 minutes")</comment>
|
||||
</data>
|
||||
<data name="BotReconnecting" xml:space="preserve">
|
||||
<value>Повторно свързване…</value>
|
||||
</data>
|
||||
@@ -481,7 +549,9 @@
|
||||
<data name="BotStatusNotRunning" xml:space="preserve">
|
||||
<value>Ботът не работи.</value>
|
||||
</data>
|
||||
|
||||
<data name="BotStatusPaused" xml:space="preserve">
|
||||
<value>Ботът е паузиран или работи в ръчен режим.</value>
|
||||
</data>
|
||||
<data name="BotStatusPlayingNotAvailable" xml:space="preserve">
|
||||
<value>В момента се ползва бот.</value>
|
||||
</data>
|
||||
@@ -501,38 +571,73 @@
|
||||
<value>Неуспешно поради грешка: {0}</value>
|
||||
<comment>{0} will be replaced by failure reason (string)</comment>
|
||||
</data>
|
||||
|
||||
|
||||
|
||||
|
||||
<data name="BotConnectionLost" xml:space="preserve">
|
||||
<value>Връзката с мрежата на Steam е загубена. Повторно свързване...</value>
|
||||
</data>
|
||||
<data name="BotAccountFree" xml:space="preserve">
|
||||
<value>Акаунтът не е вече зает: ваденето на карти е възобновено!</value>
|
||||
</data>
|
||||
<data name="BotAccountOccupied" xml:space="preserve">
|
||||
<value>Акаунтът в момента се ползва: ASF ще възобнови ваденето на карти, когато е свободен...</value>
|
||||
</data>
|
||||
<data name="BotAutomaticIdlingPauseTimeout" xml:space="preserve">
|
||||
<value>Споделената библиотека не е стартирана през даденият период на време. Ваденето на карти е продължено!</value>
|
||||
</data>
|
||||
<data name="BotConnecting" xml:space="preserve">
|
||||
<value>Свързване…</value>
|
||||
</data>
|
||||
|
||||
|
||||
<data name="BotHeartBeatFailed" xml:space="preserve">
|
||||
<value>Неуспешно прекъсване на връзката на клиента. Премахва се този бот!</value>
|
||||
</data>
|
||||
<data name="BotSteamDirectoryInitializationFailed" xml:space="preserve">
|
||||
<value>Неуспешно стартиране на SteamDirectory: свързването с мрежата на Steam може да отнеме много повече време от обичайното!</value>
|
||||
</data>
|
||||
<data name="BotStopping" xml:space="preserve">
|
||||
<value>Спиране…</value>
|
||||
</data>
|
||||
|
||||
|
||||
<data name="ErrorBotConfigInvalid" xml:space="preserve">
|
||||
<value>Настройката на вашият бот е невалидна. Моля проверете съдържанието на {0} и опитайте отново!</value>
|
||||
<comment>{0} will be replaced by file's path</comment>
|
||||
</data>
|
||||
<data name="ErrorDatabaseInvalid" xml:space="preserve">
|
||||
<value>Постоянната база данни не може да бъде заредена, ако проблемът продължава, моля премахнете {0} за да се пресъздаде базата данни!</value>
|
||||
<comment>{0} will be replaced by file's path</comment>
|
||||
</data>
|
||||
<data name="Initializing" xml:space="preserve">
|
||||
<value>Стартиране {0}…</value>
|
||||
<comment>{0} will be replaced by service name that is being initialized</comment>
|
||||
</data>
|
||||
|
||||
|
||||
<data name="WarningPrivacyPolicy" xml:space="preserve">
|
||||
<value>Моля, прегледайте нашата секция в wiki за "лична неприкосновеност", ако сте загрижени за това, което всъщност прави ASF!</value>
|
||||
</data>
|
||||
<data name="Welcome" xml:space="preserve">
|
||||
<value>Това изглежда е първото стартиране на програмата, добре дошли!</value>
|
||||
</data>
|
||||
<data name="ErrorInvalidCurrentCulture" xml:space="preserve">
|
||||
<value>Задали сте невалиден CurrentCulture. ASF ще продължи работа със зададения по подразбиране!</value>
|
||||
</data>
|
||||
|
||||
|
||||
|
||||
<data name="TranslationIncomplete" xml:space="preserve">
|
||||
<value>ASF ще се опита да използва вашият предпочитан {0} език, но преводът на този език е завършен само в {1}. Може би можете да помогнете да подобрим ASF с превод за вашия език?</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>
|
||||
</data>
|
||||
<data name="IdlingGameNotPossible" xml:space="preserve">
|
||||
<value>Вадене на карти {0} ({1}) е временно забранено, тъй като ASG не е в състояние да играе тази игра в момента.</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">
|
||||
<value>ASF откри несъответствие на ID за {0} ({1}) и ще използва ID на {2} вместо това.</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>
|
||||
</data>
|
||||
<data name="BotVersion" xml:space="preserve">
|
||||
<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>
|
||||
</data>
|
||||
|
||||
|
||||
<data name="BotAccountLocked" xml:space="preserve">
|
||||
<value>Този акаунт е заключен, ваденето на карти е недостъпно завинаги!</value>
|
||||
</data>
|
||||
<data name="BotStatusLocked" xml:space="preserve">
|
||||
<value>Бота е заключен и не може да пусне никакви карти чрез "играене".</value>
|
||||
</data>
|
||||
<data name="ErrorFunctionOnlyInHeadlessMode" xml:space="preserve">
|
||||
<value>Тази функция е възможна само в headless режим!</value>
|
||||
</data>
|
||||
@@ -543,9 +648,23 @@
|
||||
<data name="ErrorAccessDenied" xml:space="preserve">
|
||||
<value>Достъпът отказан!</value>
|
||||
</data>
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
<data name="WarningPreReleaseVersion" xml:space="preserve">
|
||||
<value>Вие използвате версия, която е по-нова от последната версия за актуализация. Моля, имайте предвид, че предварителните версии са посветени на потребителите, които знаят как да докладват за бъгове, да се справят с проблеми и да дават обратна връзка - няма да ви бъде осигурена техническа подръжка.</value>
|
||||
</data>
|
||||
<data name="BotStats" xml:space="preserve">
|
||||
<value>В момента се ползва памет: {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>Изчистване на предложенията на Steam #{0}...</value>
|
||||
<comment>{0} will be replaced by queue number</comment>
|
||||
</data>
|
||||
<data name="DoneClearingDiscoveryQueue" xml:space="preserve">
|
||||
<value>Завърши изчистването на предложенията на Steam #{0}.</value>
|
||||
<comment>{0} will be replaced by queue number</comment>
|
||||
</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>
|
||||
|
||||
@@ -173,10 +173,6 @@ StackTrace:
|
||||
<value>Analýza {0} selhala.</value>
|
||||
<comment>{0} will be replaced by object's name</comment>
|
||||
</data>
|
||||
<data name="ErrorRemovingOldBinary" xml:space="preserve">
|
||||
<value>Nepodařilo se odstranit starý binární soubor aplikace ASF. Chcete-li zajistit správné fungování funkce aktualizace, odstraňte soubor {0} ručně.</value>
|
||||
<comment>{0} will be replaced by file's path</comment>
|
||||
</data>
|
||||
<data name="ErrorRequestFailedTooManyTimes" xml:space="preserve">
|
||||
<value>Požadavek selhal po {0} pokusech.</value>
|
||||
<comment>{0} will be replaced by maximum number of tries</comment>
|
||||
@@ -300,9 +296,6 @@ StackTrace:
|
||||
<value>Spušt2ní více než {0} her není aktuálně možné, použito bude pouze prvních {0} položek z {1}.</value>
|
||||
<comment>{0} will be replaced by max number of games, {1} will be replaced by name of the configuration property</comment>
|
||||
</data>
|
||||
<data name="ErrorIPCAddressAccessDeniedException" xml:space="preserve">
|
||||
<value>Služba IPC nemohla být spuštěna kvůli AddressAccessDeniedException. Pokud si přejete použít službu IPC, poskytovanou aplikací ASF, zvažte spuštění aplikace ASF jako správce, nebo aplikaci přidělte dostatečná oprávnění.</value>
|
||||
</data>
|
||||
<data name="IPCAnswered" xml:space="preserve">
|
||||
<value>Odpovězeno na příkaz IPC: {0} odpověď: {1}</value>
|
||||
<comment>{0} will be replaced by IPC command, {1} will be replaced by IPC answer</comment>
|
||||
|
||||
@@ -172,10 +172,6 @@ StackTrace:
|
||||
<value>Parsing {0} fejlet!</value>
|
||||
<comment>{0} will be replaced by object's name</comment>
|
||||
</data>
|
||||
<data name="ErrorRemovingOldBinary" xml:space="preserve">
|
||||
<value>Kunne ikke fjerne gammel ASF binær fil. Vær venlig at fjerne {0} manuelt for at opdaterings funktionen virker!</value>
|
||||
<comment>{0} will be replaced by file's path</comment>
|
||||
</data>
|
||||
<data name="ErrorRequestFailedTooManyTimes" xml:space="preserve">
|
||||
<value>Anmodning fejlede efter {0} forsøg!</value>
|
||||
<comment>{0} will be replaced by maximum number of tries</comment>
|
||||
@@ -291,7 +287,6 @@ StackTrace:
|
||||
|
||||
|
||||
|
||||
|
||||
<data name="BotAlreadyStopped" xml:space="preserve">
|
||||
<value>Denne bot er allerede stoppet!</value>
|
||||
</data>
|
||||
|
||||
@@ -172,10 +172,6 @@ StackTrace:
|
||||
<value>Fehler beim Parsen von {0}!</value>
|
||||
<comment>{0} will be replaced by object's name</comment>
|
||||
</data>
|
||||
<data name="ErrorRemovingOldBinary" xml:space="preserve">
|
||||
<value>Konnte alte ASF Binärdatei nicht löschen. Bitte entferne {0} manuell, damit die Updatefunktion funktionieren kann!</value>
|
||||
<comment>{0} will be replaced by file's path</comment>
|
||||
</data>
|
||||
<data name="ErrorRequestFailedTooManyTimes" xml:space="preserve">
|
||||
<value>Anfrage nach {0} Versuchen fehlgeschlagen!</value>
|
||||
<comment>{0} will be replaced by maximum number of tries</comment>
|
||||
@@ -299,9 +295,6 @@ 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>
|
||||
<comment>{0} will be replaced by max number of games, {1} will be replaced by name of the configuration property</comment>
|
||||
</data>
|
||||
<data name="ErrorIPCAddressAccessDeniedException" xml:space="preserve">
|
||||
<value>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>
|
||||
@@ -672,5 +665,8 @@ StackTrace:
|
||||
<value>Fertig mit Löschung der Steam Entdeckungsliste #{0}.</value>
|
||||
<comment>{0} will be replaced by queue number</comment>
|
||||
</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>
|
||||
|
||||
@@ -173,10 +173,6 @@ StackTrace:
|
||||
<value>Fehler beim Parsen von {0}!</value>
|
||||
<comment>{0} will be replaced by object's name</comment>
|
||||
</data>
|
||||
<data name="ErrorRemovingOldBinary" xml:space="preserve">
|
||||
<value>Konnte alte ASF Binärdatei nicht löschen. Bitte entferne {0} manuell, damit die Updatefunktion funktionieren kann!</value>
|
||||
<comment>{0} will be replaced by file's path</comment>
|
||||
</data>
|
||||
<data name="ErrorRequestFailedTooManyTimes" xml:space="preserve">
|
||||
<value>Anfrage nach {0} Versuchen fehlgeschlagen!</value>
|
||||
<comment>{0} will be replaced by maximum number of tries</comment>
|
||||
@@ -300,9 +296,6 @@ 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>
|
||||
<comment>{0} will be replaced by max number of games, {1} will be replaced by name of the configuration property</comment>
|
||||
</data>
|
||||
<data name="ErrorIPCAddressAccessDeniedException" xml:space="preserve">
|
||||
<value>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>
|
||||
|
||||
@@ -173,10 +173,6 @@ StackTrace:
|
||||
<value>Η ανάλυση του {0} απέτυχε!</value>
|
||||
<comment>{0} will be replaced by object's name</comment>
|
||||
</data>
|
||||
<data name="ErrorRemovingOldBinary" xml:space="preserve">
|
||||
<value>Αδυναμία αφαίρεσης του παλιού ASF binary, αφαιρέστε το {0} χειροκίνητα ώστε να λειτουργήσει η λειτουργία ενημέρωσης!</value>
|
||||
<comment>{0} will be replaced by file's path</comment>
|
||||
</data>
|
||||
<data name="ErrorRequestFailedTooManyTimes" xml:space="preserve">
|
||||
<value>Το αίτημα απέτυχε έπειτα από {0} προσπάθειες!</value>
|
||||
<comment>{0} will be replaced by maximum number of tries</comment>
|
||||
@@ -300,9 +296,6 @@ StackTrace:
|
||||
<value>Η συλλογή περισσότερων από {0} παιχνιδιών ταυτόχρονα δεν είναι δυνατή, μόνο οι πρώτες {0} καταχρήσεις από το {1} θα χρησιμοποιηθούν!</value>
|
||||
<comment>{0} will be replaced by max number of games, {1} will be replaced by name of the configuration property</comment>
|
||||
</data>
|
||||
<data name="ErrorIPCAddressAccessDeniedException" xml:space="preserve">
|
||||
<value>Η υπηρεσία IPC δεν ήταν δυνατό να εκκινηθεί λόγω σφάλματος AddressAccessDeniedException! Εάν θέλετε να χρησιμοποιήσετε την υπηρεσία IPC που παρέχεται από το ASF, δοκιμάστε να εκκινήσετε το ASF ως διαχειριστής ή να παραχωρήσετε τα κατάλληλα δικαιώματα!</value>
|
||||
</data>
|
||||
<data name="IPCAnswered" xml:space="preserve">
|
||||
<value>Έγινε απάντηση στην εντολή IPC: {0} με: {1}</value>
|
||||
<comment>{0} will be replaced by IPC command, {1} will be replaced by IPC answer</comment>
|
||||
|
||||
@@ -130,7 +130,7 @@
|
||||
<comment>{0} will be replaced by content string. Please note that this string should include newline for formatting.</comment>
|
||||
</data>
|
||||
<data name="ErrorConfigPropertyInvalid" xml:space="preserve">
|
||||
<value>La propiedad {0} configurada es inválida: {1}</value>
|
||||
<value>La propiedad {0} configurada no es válida: {1}</value>
|
||||
<comment>{0} will be replaced by name of the configuration property, {1} will be replaced by invalid value</comment>
|
||||
</data>
|
||||
<data name="ErrorEarlyFatalExceptionInfo" xml:space="preserve">
|
||||
@@ -139,7 +139,7 @@
|
||||
</data>
|
||||
<data name="ErrorEarlyFatalExceptionPrint" xml:space="preserve">
|
||||
<value>Excepción: {0}() {1}
|
||||
Trazo de pila:
|
||||
StackTrace:
|
||||
{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>
|
||||
</data>
|
||||
@@ -155,7 +155,7 @@ Trazo de pila:
|
||||
<comment>{0} will be replaced by file's path</comment>
|
||||
</data>
|
||||
<data name="ErrorIsInvalid" xml:space="preserve">
|
||||
<value>{0} es inválido!</value>
|
||||
<value>¡{0} no es válido!</value>
|
||||
<comment>{0} will be replaced by object's name</comment>
|
||||
</data>
|
||||
<data name="ErrorMobileAuthenticatorInvalidDeviceID" xml:space="preserve">
|
||||
@@ -172,10 +172,6 @@ Trazo de pila:
|
||||
<value>Análisis de {0} fallido!</value>
|
||||
<comment>{0} will be replaced by object's name</comment>
|
||||
</data>
|
||||
<data name="ErrorRemovingOldBinary" xml:space="preserve">
|
||||
<value>No se pudo borrar el anterior ejecutable de ASF. Por favor elimina {0} manualmente para que la actualización funcione!</value>
|
||||
<comment>{0} will be replaced by file's path</comment>
|
||||
</data>
|
||||
<data name="ErrorRequestFailedTooManyTimes" xml:space="preserve">
|
||||
<value>¡La solicitud falló después de {0} intentos!</value>
|
||||
<comment>{0} will be replaced by maximum number of tries</comment>
|
||||
@@ -193,11 +189,11 @@ Trazo de pila:
|
||||
<value>Recibida una solicitud de entrada del usuario, ¡pero el proceso se está ejecutando en modo servidor!</value>
|
||||
</data>
|
||||
<data name="ErrorIPCAccessDenied" xml:space="preserve">
|
||||
<value>¡Solicitud denegada porque SteamOwnerID no esta establecido!</value>
|
||||
<value>¡la solicitud no se puede atender 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">
|
||||
<value>Saliendo...</value>
|
||||
<value>Terminando...</value>
|
||||
</data>
|
||||
<data name="WarningFailed" xml:space="preserve">
|
||||
<value>¡Fallido!</value>
|
||||
@@ -217,7 +213,7 @@ Trazo de pila:
|
||||
<comment>{0} will be replaced by service's name</comment>
|
||||
</data>
|
||||
<data name="NoBotsAreRunning" xml:space="preserve">
|
||||
<value>No hay bots activos, saliendo...</value>
|
||||
<value>No hay bots activos, terminando...</value>
|
||||
</data>
|
||||
<data name="RefreshingOurSession" xml:space="preserve">
|
||||
<value>¡Actualizando sesión!</value>
|
||||
@@ -272,7 +268,7 @@ Trazo de pila:
|
||||
<comment>Please note that this translation should end with space</comment>
|
||||
</data>
|
||||
<data name="UserInputSteamLogin" xml:space="preserve">
|
||||
<value>Por favor ingresa tus credenciales de Steam: </value>
|
||||
<value>Introduzca su usuario de Steam: </value>
|
||||
<comment>Please note that this translation should end with space</comment>
|
||||
</data>
|
||||
<data name="UserInputSteamParentalPIN" xml:space="preserve">
|
||||
@@ -299,9 +295,6 @@ 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>
|
||||
<comment>{0} will be replaced by max number of games, {1} will be replaced by name of the configuration property</comment>
|
||||
</data>
|
||||
<data name="ErrorIPCAddressAccessDeniedException" xml:space="preserve">
|
||||
<value>¡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>
|
||||
@@ -432,7 +425,7 @@ Trazo de pila:
|
||||
<value>Convirtiendo .maFile en formato ASF...</value>
|
||||
</data>
|
||||
<data name="BotAuthenticatorImportFinished" xml:space="preserve">
|
||||
<value>Autenticador móvil importado exitosamente.</value>
|
||||
<value>¡Se ha finalizado exitosamente la importación del identificador móvil!</value>
|
||||
</data>
|
||||
<data name="BotAuthenticatorInvalidDeviceID" xml:space="preserve">
|
||||
<value>¡Su DeviceID es incorrecto o no existe!</value>
|
||||
@@ -672,5 +665,8 @@ Trazo de pila:
|
||||
<value>Lista de decubrimientos de Steam #{0} completada.</value>
|
||||
<comment>{0} will be replaced by queue number</comment>
|
||||
</data>
|
||||
|
||||
<data name="BotOwnsOverview" xml:space="preserve">
|
||||
<value>Hay {0}/{1} bots que poseen todos los juegos comprobados.</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>
|
||||
|
||||
@@ -152,10 +152,6 @@
|
||||
<value>{0} jäsentäminen epäonnistui!</value>
|
||||
<comment>{0} will be replaced by object's name</comment>
|
||||
</data>
|
||||
<data name="ErrorRemovingOldBinary" xml:space="preserve">
|
||||
<value>Vanhaa ASF-binaariä ei voitu poistaa. Ole hyvä ja poista {0} manuaalisesti, jotta päivitystoiminto toimisi!</value>
|
||||
<comment>{0} will be replaced by file's path</comment>
|
||||
</data>
|
||||
<data name="ErrorRequestFailedTooManyTimes" xml:space="preserve">
|
||||
<value>Pyyntö epäonnistui {0} yrityksen jälkeen!</value>
|
||||
<comment>{0} will be replaced by maximum number of tries</comment>
|
||||
@@ -226,7 +222,6 @@
|
||||
|
||||
|
||||
|
||||
|
||||
<data name="BotNotFound" xml:space="preserve">
|
||||
<value>Bottia nimeltä {0} ei voitu löytää!</value>
|
||||
<comment>{0} will be replaced by bot's name query (string)</comment>
|
||||
|
||||
@@ -173,10 +173,6 @@ StackTrace :
|
||||
<value>L’analyse de {0} a échoué !</value>
|
||||
<comment>{0} will be replaced by object's name</comment>
|
||||
</data>
|
||||
<data name="ErrorRemovingOldBinary" xml:space="preserve">
|
||||
<value>Impossible de supprimer l'ancien binaire d'ASF, merci de supprimer {0} manuellement afin que la fonction de mise à jour puisse fonctionner !</value>
|
||||
<comment>{0} will be replaced by file's path</comment>
|
||||
</data>
|
||||
<data name="ErrorRequestFailedTooManyTimes" xml:space="preserve">
|
||||
<value>La requête a échoué après {0} tentatives !</value>
|
||||
<comment>{0} will be replaced by maximum number of tries</comment>
|
||||
@@ -184,7 +180,9 @@ StackTrace :
|
||||
<data name="ErrorUpdateCheckFailed" xml:space="preserve">
|
||||
<value>Impossible de vérifier la dernière version !</value>
|
||||
</data>
|
||||
|
||||
<data name="ErrorUpdateNoAssetForThisVersion" xml:space="preserve">
|
||||
<value>Impossible de procéder à la mise à jour car il n'y a aucun fichier correspondant à la version actuelle ! La mise à jour automatique vers cette version n'est pas possible.</value>
|
||||
</data>
|
||||
<data name="ErrorUpdateNoAssets" xml:space="preserve">
|
||||
<value>Impossible de procéder à une mise à jour parce que cette version ne contient aucun fichier !</value>
|
||||
</data>
|
||||
@@ -286,7 +284,10 @@ StackTrace :
|
||||
<value>Veuillez entrer la valeur non documentée de {0} : </value>
|
||||
<comment>{0} will be replaced by property name. Please note that this translation should end with space</comment>
|
||||
</data>
|
||||
|
||||
<data name="UserInputIPCHost" xml:space="preserve">
|
||||
<value>Veuillez saisir votre hôte IPC: </value>
|
||||
<comment>Please note that this translation should end with space</comment>
|
||||
</data>
|
||||
<data name="WarningUnknownValuePleaseReport" xml:space="preserve">
|
||||
<value>{0} a reçu une valeur inconnue, veuillez le signaler : {1}</value>
|
||||
<comment>{0} will be replaced by object's name, {1} will be replaced by value for that object</comment>
|
||||
@@ -295,10 +296,17 @@ StackTrace :
|
||||
<value>Jouer à plus de {0} jeux en même temps n’est pas actuelllement possible, seules les {0} premières entrées de {1} seront utilisées !</value>
|
||||
<comment>{0} will be replaced by max number of games, {1} will be replaced by name of the configuration property</comment>
|
||||
</data>
|
||||
|
||||
|
||||
|
||||
|
||||
<data name="IPCAnswered" xml:space="preserve">
|
||||
<value>Réponse à la commande IPC: {0} avec {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>Serveur IPC prêt !</value>
|
||||
</data>
|
||||
<data name="IPCStarting" xml:space="preserve">
|
||||
<value>Démarrage du serveur IPC sur {0}...</value>
|
||||
<comment>{0} will be replaced by IPC hostname</comment>
|
||||
</data>
|
||||
<data name="BotAlreadyStopped" xml:space="preserve">
|
||||
<value>Ce bot est déjà arrêté !</value>
|
||||
</data>
|
||||
@@ -658,5 +666,8 @@ StackTrace :
|
||||
<value>Fini de consulter la liste de découvertes #{0}.</value>
|
||||
<comment>{0} will be replaced by queue number</comment>
|
||||
</data>
|
||||
|
||||
<data name="BotOwnsOverview" xml:space="preserve">
|
||||
<value>Il y a {0}/{1} bots qui possède déjà tous les jeux en cours de vérification.</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>
|
||||
|
||||
@@ -173,10 +173,6 @@ StackTrace :
|
||||
<value>L’analyse de {0} a échoué !</value>
|
||||
<comment>{0} will be replaced by object's name</comment>
|
||||
</data>
|
||||
<data name="ErrorRemovingOldBinary" xml:space="preserve">
|
||||
<value>Impossible de supprimer l'ancien fichier d'ASF. Merci de supprimer {0} manuellement afin que la fonction de mise à jour puisse fonctionner !</value>
|
||||
<comment>{0} will be replaced by file's path</comment>
|
||||
</data>
|
||||
<data name="ErrorRequestFailedTooManyTimes" xml:space="preserve">
|
||||
<value>La requête a échoué après {0} tentatives !</value>
|
||||
<comment>{0} will be replaced by maximum number of tries</comment>
|
||||
@@ -184,7 +180,9 @@ StackTrace :
|
||||
<data name="ErrorUpdateCheckFailed" xml:space="preserve">
|
||||
<value>Impossible de vérifier la dernière version !</value>
|
||||
</data>
|
||||
|
||||
<data name="ErrorUpdateNoAssetForThisVersion" xml:space="preserve">
|
||||
<value>Impossible de procéder à la mise à jour car il n'y a aucun fichier correspondant à la version actuelle ! La mise à jour automatique vers cette version n'est pas possible.</value>
|
||||
</data>
|
||||
<data name="ErrorUpdateNoAssets" xml:space="preserve">
|
||||
<value>Impossible de procéder à une mise à jour parce que cette version ne contient aucun fichier !</value>
|
||||
</data>
|
||||
@@ -286,7 +284,10 @@ StackTrace :
|
||||
<value>Veuillez entrer la valeur non documentée de {0} : </value>
|
||||
<comment>{0} will be replaced by property name. Please note that this translation should end with space</comment>
|
||||
</data>
|
||||
|
||||
<data name="UserInputIPCHost" xml:space="preserve">
|
||||
<value>Veuillez saisir votre hôte IPC: </value>
|
||||
<comment>Please note that this translation should end with space</comment>
|
||||
</data>
|
||||
<data name="WarningUnknownValuePleaseReport" xml:space="preserve">
|
||||
<value>{0} a reçu une valeur inconnue, veuillez le signaler : {1}</value>
|
||||
<comment>{0} will be replaced by object's name, {1} will be replaced by value for that object</comment>
|
||||
@@ -295,10 +296,17 @@ StackTrace :
|
||||
<value>Jouer à plus de {0} jeux en même temps n’est pas possible, seules les {0} premières entrées de {1} seront utilisées !</value>
|
||||
<comment>{0} will be replaced by max number of games, {1} will be replaced by name of the configuration property</comment>
|
||||
</data>
|
||||
|
||||
|
||||
|
||||
|
||||
<data name="IPCAnswered" xml:space="preserve">
|
||||
<value>Réponse à la commande IPC: {0} avec {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>Serveur IPC prêt !</value>
|
||||
</data>
|
||||
<data name="IPCStarting" xml:space="preserve">
|
||||
<value>Démarrage du serveur IPC sur {0}...</value>
|
||||
<comment>{0} will be replaced by IPC hostname</comment>
|
||||
</data>
|
||||
<data name="BotAlreadyStopped" xml:space="preserve">
|
||||
<value>Ce bot est déjà à l'arrêt !</value>
|
||||
</data>
|
||||
@@ -658,5 +666,8 @@ StackTrace :
|
||||
<value>Fini de consulter la liste de découvertes #{0}.</value>
|
||||
<comment>{0} will be replaced by queue number</comment>
|
||||
</data>
|
||||
|
||||
<data name="BotOwnsOverview" xml:space="preserve">
|
||||
<value>Il y a {0}/{1} bots qui possède déjà tous les jeux en cours de vérification.</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>
|
||||
|
||||
@@ -167,7 +167,6 @@ StackTrace:
|
||||
<comment>{0} will be replaced by object's name</comment>
|
||||
</data>
|
||||
|
||||
|
||||
<data name="ErrorUpdateCheckFailed" xml:space="preserve">
|
||||
<value>לא ניתן לבדוק את הגירסה העדכנית ביותר!</value>
|
||||
</data>
|
||||
@@ -253,7 +252,6 @@ StackTrace:
|
||||
|
||||
|
||||
|
||||
|
||||
<data name="BotNotFound" xml:space="preserve">
|
||||
<value>לא ניתן למצוא אף בוט בשם {0}!</value>
|
||||
<comment>{0} will be replaced by bot's name query (string)</comment>
|
||||
|
||||
@@ -163,7 +163,6 @@
|
||||
<comment>{0} will be replaced by object's name</comment>
|
||||
</data>
|
||||
|
||||
|
||||
<data name="ErrorUpdateCheckFailed" xml:space="preserve">
|
||||
<value>कुड नॉट चैक लैटेस्ट वर्ज़न!</value>
|
||||
</data>
|
||||
@@ -309,7 +308,6 @@
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
</root>
|
||||
|
||||
@@ -171,10 +171,6 @@ StackTrace: {2}</value>
|
||||
<value>{0} feldolgozása sikertelen!</value>
|
||||
<comment>{0} will be replaced by object's name</comment>
|
||||
</data>
|
||||
<data name="ErrorRemovingOldBinary" xml:space="preserve">
|
||||
<value>Nem lehet kitörölni a régi ASF bináris fájlt. Kérlek manuálisan távolítsd el a {0}-t, hogy a frissítés sikeres legyen!</value>
|
||||
<comment>{0} will be replaced by file's path</comment>
|
||||
</data>
|
||||
<data name="ErrorRequestFailedTooManyTimes" xml:space="preserve">
|
||||
<value>A kérés sikertelen volt {0} próbálkozás után!</value>
|
||||
<comment>{0} will be replaced by maximum number of tries</comment>
|
||||
@@ -290,7 +286,6 @@ StackTrace: {2}</value>
|
||||
|
||||
|
||||
|
||||
|
||||
<data name="BotAlreadyStopped" xml:space="preserve">
|
||||
<value>Ez a bor már leállt!</value>
|
||||
</data>
|
||||
|
||||
@@ -130,8 +130,7 @@
|
||||
<comment>{0} will be replaced by content string. Please note that this string should include newline for formatting.</comment>
|
||||
</data>
|
||||
<data name="ErrorConfigPropertyInvalid" xml:space="preserve">
|
||||
<value>ErrorPropertiConfiginvalid
|
||||
{0} akan diubah dengan nama properti konfigurasi, {1} akan diubah dengan nilai invalid</value>
|
||||
<value>Konfigurasi properti {0} tidak valid: {1}</value>
|
||||
<comment>{0} will be replaced by name of the configuration property, {1} will be replaced by invalid value</comment>
|
||||
</data>
|
||||
<data name="ErrorEarlyFatalExceptionInfo" xml:space="preserve">
|
||||
@@ -171,10 +170,6 @@
|
||||
<value>Parsing {0} gagal!</value>
|
||||
<comment>{0} will be replaced by object's name</comment>
|
||||
</data>
|
||||
<data name="ErrorRemovingOldBinary" xml:space="preserve">
|
||||
<value>Tidak dapat menghapus file biner ASF lama, silakan hapus {0} secara manual agar fungsi update bekerja!</value>
|
||||
<comment>{0} will be replaced by file's path</comment>
|
||||
</data>
|
||||
<data name="ErrorRequestFailedTooManyTimes" xml:space="preserve">
|
||||
<value>Permintaan gagal setelah {0} kali upaya!</value>
|
||||
<comment>{0} will be replaced by maximum number of tries</comment>
|
||||
@@ -182,14 +177,19 @@
|
||||
<data name="ErrorUpdateCheckFailed" xml:space="preserve">
|
||||
<value>Tidak dapat memeriksa versi terbaru!</value>
|
||||
</data>
|
||||
|
||||
<data name="ErrorUpdateNoAssetForThisVersion" xml:space="preserve">
|
||||
<value>Tidak bisa melanjutkan dengan pembaruan karena tidak ada asset yang berkaitan dengan versi yang berjalan sekarang! Pembaruan otomatis ke versi tersebut tidak mungkin.</value>
|
||||
</data>
|
||||
<data name="ErrorUpdateNoAssets" xml:space="preserve">
|
||||
<value>Tidak bisa melanjutkan update karena tak ada aset yang termasuk dalam versi tersebut!</value>
|
||||
</data>
|
||||
<data name="ErrorUserInputRunningInHeadlessMode" xml:space="preserve">
|
||||
<value>Menerima permintaan untuk input pengguna, tetapi proses berjalan dalam mode Headless!</value>
|
||||
</data>
|
||||
|
||||
<data name="ErrorIPCAccessDenied" xml:space="preserve">
|
||||
<value>Menolak untuk menangani permintaan karena SteamOwnerID tidak diatur!</value>
|
||||
<comment>SteamOwnerID is name of bot config property, it should not be translated</comment>
|
||||
</data>
|
||||
<data name="Exiting" xml:space="preserve">
|
||||
<value>Menutup...</value>
|
||||
</data>
|
||||
@@ -239,9 +239,12 @@
|
||||
<data name="UpdateCheckingNewVersion" xml:space="preserve">
|
||||
<value>Sedang mengecek versi terbaru...</value>
|
||||
</data>
|
||||
|
||||
<data name="UpdateDownloadingNewVersion" xml:space="preserve">
|
||||
<value>Mengunduh versi baru: {0} ({1} MB)... Sambil menunggu, pertimbangkan untuk mengapresiasi seluruh kerja keras dengan mendonasi! :)</value>
|
||||
<comment>{0} will be replaced by version string, {1} will be replaced by update size (in megabytes)</comment>
|
||||
</data>
|
||||
<data name="UpdateFinished" xml:space="preserve">
|
||||
<value>Proses update selesai!</value>
|
||||
<value>Proses pembaruan selesai!</value>
|
||||
</data>
|
||||
<data name="UpdateNewVersionAvailable" xml:space="preserve">
|
||||
<value>Versi terbaru ASF tersedia! Pertimbangkan untuk di-update!</value>
|
||||
@@ -278,19 +281,29 @@
|
||||
<value>Masukkan suatu nilai yang tidak terdokumentasi {0}: </value>
|
||||
<comment>{0} will be replaced by property name. Please note that this translation should end with space</comment>
|
||||
</data>
|
||||
|
||||
<data name="UserInputIPCHost" xml:space="preserve">
|
||||
<value>Masukkan host WCF Anda: </value>
|
||||
<comment>Please note that this translation should end with space</comment>
|
||||
</data>
|
||||
<data name="WarningUnknownValuePleaseReport" xml:space="preserve">
|
||||
<value>Menerima nilai yang tidak diketahui untuk {0}, laporkan hal ini: {1}</value>
|
||||
<value>Menerima nilai yang tidak diketahui untuk {0}, mohon laporkan ini: {1}</value>
|
||||
<comment>{0} will be replaced by object's name, {1} will be replaced by value for that object</comment>
|
||||
</data>
|
||||
<data name="WarningTooManyGamesToPlay" xml:space="preserve">
|
||||
<value>Tidak dapat bermain lebih dari {0} game secara bersamaan, hanya {0} entri pertama dari {1} game yang akan digunakan</value>
|
||||
<comment>{0} will be replaced by max number of games, {1} will be replaced by name of the configuration property</comment>
|
||||
</data>
|
||||
|
||||
|
||||
|
||||
|
||||
<data name="IPCAnswered" xml:space="preserve">
|
||||
<value>Jawaban untuk perintah IPC: {0} dengan: {1}</value>
|
||||
<comment>{0} will be replaced by IPC command, {1} will be replaced by IPC answer</comment>
|
||||
</data>
|
||||
<data name="IPCReady" xml:space="preserve">
|
||||
<value>Server IPC siap!</value>
|
||||
</data>
|
||||
<data name="IPCStarting" xml:space="preserve">
|
||||
<value>Memulai server IPC di {0}...</value>
|
||||
<comment>{0} will be replaced by IPC hostname</comment>
|
||||
</data>
|
||||
<data name="BotAlreadyStopped" xml:space="preserve">
|
||||
<value>Bot ini sudah berhenti!</value>
|
||||
</data>
|
||||
@@ -317,14 +330,14 @@
|
||||
<value>Mengecek halaman badge lainnya...</value>
|
||||
</data>
|
||||
<data name="ChosenFarmingAlgorithm" xml:space="preserve">
|
||||
<value>Memilih Algoritma Idling: {0}</value>
|
||||
<value>Memilih algoritma idling: {0}</value>
|
||||
<comment>{0} will be replaced by the name of chosen idling algorithm</comment>
|
||||
</data>
|
||||
<data name="Done" xml:space="preserve">
|
||||
<value>Selesai!</value>
|
||||
</data>
|
||||
<data name="GamesToIdle" xml:space="preserve">
|
||||
<value>Kami memiliki total {0} permainan ({1} kartu) meninggalkan ke siaga (~{2} yang tersisa)...</value>
|
||||
<value>Kita memiliki total {0} permainan ({1} kartu) tersisa untuk idle (~{2} tersisa)...</value>
|
||||
<comment>{0} will be replaced by number of games, {1} will be replaced by number of cards, {2} will be replaced by translated TimeSpan string (such as "1 day, 5 hours and 30 minutes")</comment>
|
||||
</data>
|
||||
<data name="IdlingFinished" xml:space="preserve">
|
||||
@@ -360,7 +373,7 @@
|
||||
<comment>{0} will be replaced by list of the games (IDs, numbers), separated by a comma</comment>
|
||||
</data>
|
||||
<data name="PlayingNotAvailable" xml:space="preserve">
|
||||
<value>Bermain sedang tidak tersedia, kami akan mencoba lagi nanti!</value>
|
||||
<value>Bermain sedang tidak tersedia, kita akan coba lagi nanti!</value>
|
||||
</data>
|
||||
<data name="StillIdling" xml:space="preserve">
|
||||
<value>Masih idling: {0} ({1})</value>
|
||||
@@ -460,7 +473,7 @@
|
||||
<comment>{0} will be replaced by logging off reason (string)</comment>
|
||||
</data>
|
||||
<data name="BotLoggedOn" xml:space="preserve">
|
||||
<value>Berhasil Login!</value>
|
||||
<value>Berhasil login!</value>
|
||||
</data>
|
||||
<data name="BotLoggingIn" xml:space="preserve">
|
||||
<value>Sedang masuk...</value>
|
||||
@@ -650,5 +663,8 @@
|
||||
<value>Selesai membersihkan antrian penemuan Steam #{0}.</value>
|
||||
<comment>{0} will be replaced by queue number</comment>
|
||||
</data>
|
||||
|
||||
<data name="BotOwnsOverview" xml:space="preserve">
|
||||
<value>Ada {0}/{1} bot yang sudah memiliki semua permainan yang sedang diperiksa.</value>
|
||||
<comment>{0} will be replaced by number of bots that already own games being checked, {1} will be replaced by total number of bots that were checked during the process</comment>
|
||||
</data>
|
||||
</root>
|
||||
|
||||
@@ -171,10 +171,6 @@
|
||||
<value>Analisi di {0} non riuscita!</value>
|
||||
<comment>{0} will be replaced by object's name</comment>
|
||||
</data>
|
||||
<data name="ErrorRemovingOldBinary" xml:space="preserve">
|
||||
<value>Impossibile rimuovere i vecchi file binari di ASF. Si prega di rimuovere {0} manualmente affinché l'aggiornamento funzioni correttamente!</value>
|
||||
<comment>{0} will be replaced by file's path</comment>
|
||||
</data>
|
||||
<data name="ErrorRequestFailedTooManyTimes" xml:space="preserve">
|
||||
<value>Richiesta non riuscita dopo {0} tentativi!</value>
|
||||
<comment>{0} will be replaced by maximum number of tries</comment>
|
||||
@@ -182,14 +178,19 @@
|
||||
<data name="ErrorUpdateCheckFailed" xml:space="preserve">
|
||||
<value>Non è stato possibile controllare la versione più recente!</value>
|
||||
</data>
|
||||
|
||||
<data name="ErrorUpdateNoAssetForThisVersion" xml:space="preserve">
|
||||
<value>Non posso procedere con l'aggiornamento perché non c'è una risorsa legata alla versione in esecuzione al momento! Un aggiornamento automatico a quella versione non è possibile.</value>
|
||||
</data>
|
||||
<data name="ErrorUpdateNoAssets" xml:space="preserve">
|
||||
<value>Impossibile procedere con un aggiornamento poiché tale versione non include risorse!</value>
|
||||
</data>
|
||||
<data name="ErrorUserInputRunningInHeadlessMode" xml:space="preserve">
|
||||
<value>Ricevuta una richiesta di input da parte dell'utente, ma il processo è in esecuzione in modalità headless!</value>
|
||||
</data>
|
||||
|
||||
<data name="ErrorIPCAccessDenied" xml:space="preserve">
|
||||
<value>Rifiutando di gestire la richiesta poiché SteamOwnerID non è stato impostato correttamente!</value>
|
||||
<comment>SteamOwnerID is name of bot config property, it should not be translated</comment>
|
||||
</data>
|
||||
<data name="Exiting" xml:space="preserve">
|
||||
<value>Uscita in corso...</value>
|
||||
</data>
|
||||
@@ -239,7 +240,10 @@
|
||||
<data name="UpdateCheckingNewVersion" xml:space="preserve">
|
||||
<value>Verifica della nuova versione...</value>
|
||||
</data>
|
||||
|
||||
<data name="UpdateDownloadingNewVersion" xml:space="preserve">
|
||||
<value>Scaricando la nuova versione: {0} ({1} MB)... Durante l'attesa, considera una donazione se apprezzi il lavoro svolto! :)</value>
|
||||
<comment>{0} will be replaced by version string, {1} will be replaced by update size (in megabytes)</comment>
|
||||
</data>
|
||||
<data name="UpdateFinished" xml:space="preserve">
|
||||
<value>Aggiornamento completato!</value>
|
||||
</data>
|
||||
@@ -267,7 +271,7 @@
|
||||
<comment>Please note that this translation should end with space</comment>
|
||||
</data>
|
||||
<data name="UserInputSteamParentalPIN" xml:space="preserve">
|
||||
<value>Si prega di inserire il PIN famigliare Steam: </value>
|
||||
<value>Si prega di inserire il PIN famigliare di Steam: </value>
|
||||
<comment>Please note that this translation should end with space</comment>
|
||||
</data>
|
||||
<data name="UserInputSteamPassword" xml:space="preserve">
|
||||
@@ -278,7 +282,10 @@
|
||||
<value>Inserisci il valore non documentato {0}: </value>
|
||||
<comment>{0} will be replaced by property name. Please note that this translation should end with space</comment>
|
||||
</data>
|
||||
|
||||
<data name="UserInputIPCHost" xml:space="preserve">
|
||||
<value>Inserisci il tuo host IPC: </value>
|
||||
<comment>Please note that this translation should end with space</comment>
|
||||
</data>
|
||||
<data name="WarningUnknownValuePleaseReport" xml:space="preserve">
|
||||
<value>Ricevuto valore sconosciuto per {0}, si prega di segnalare: {1}</value>
|
||||
<comment>{0} will be replaced by object's name, {1} will be replaced by value for that object</comment>
|
||||
@@ -287,10 +294,17 @@
|
||||
<value>Non è possibile giocare a più di {0} giochi contemporaneamente, verranno utilizzate solo le prime {0} voci di {1}!</value>
|
||||
<comment>{0} will be replaced by max number of games, {1} will be replaced by name of the configuration property</comment>
|
||||
</data>
|
||||
|
||||
|
||||
|
||||
|
||||
<data name="IPCAnswered" xml:space="preserve">
|
||||
<value>Risposto al comando IPC: {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>Il server IPC è pronto!</value>
|
||||
</data>
|
||||
<data name="IPCStarting" xml:space="preserve">
|
||||
<value>Avvio del server IPC in {0}...</value>
|
||||
<comment>{0} will be replaced by IPC hostname</comment>
|
||||
</data>
|
||||
<data name="BotAlreadyStopped" xml:space="preserve">
|
||||
<value>Questo bot è già stato arrestato!</value>
|
||||
</data>
|
||||
@@ -433,7 +447,7 @@
|
||||
<comment>{0} will be replaced by translated TimeSpan string (such as "5 minutes")</comment>
|
||||
</data>
|
||||
<data name="BotAutomaticIdlingResumedAlready" xml:space="preserve">
|
||||
<value>L'idling automatico è già attivo!</value>
|
||||
<value>L'idling automatico è già stato ripreso!</value>
|
||||
</data>
|
||||
<data name="BotConnected" xml:space="preserve">
|
||||
<value>Connesso a Steam!</value>
|
||||
@@ -525,7 +539,7 @@
|
||||
<value>Rimossa la chiave di accesso scaduta!</value>
|
||||
</data>
|
||||
<data name="BotStatusNotIdling" xml:space="preserve">
|
||||
<value>Il Bot non sta trovando niente.</value>
|
||||
<value>Il Bot non sta trovando niente su cui fare idling.</value>
|
||||
</data>
|
||||
<data name="BotStatusLimited" xml:space="preserve">
|
||||
<value>Il Bot è un account limitato e non può ottenere carte tramite idling.</value>
|
||||
@@ -534,7 +548,7 @@
|
||||
<value>Il bot si stà connettendo alla rete di Steam.</value>
|
||||
</data>
|
||||
<data name="BotStatusNotRunning" xml:space="preserve">
|
||||
<value>Bot non è in esecuzione.</value>
|
||||
<value>Il bot non è in esecuzione.</value>
|
||||
</data>
|
||||
<data name="BotStatusPaused" xml:space="preserve">
|
||||
<value>Il bot è in pausa o in esecuzione in modalità manuale.</value>
|
||||
@@ -608,7 +622,7 @@
|
||||
<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 name="IdlingGameNotPossible" xml:space="preserve">
|
||||
<value>L'idling di {0} ({1}) è temporaneamente disabilitato, poiché ASF non è in grado di riprodurre questo gioco al momento.</value>
|
||||
<value>L'idling di {0} ({1}) è temporaneamente disabilitato, poiché ASF non è in grado di eseguire questo gioco al momento.</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">
|
||||
@@ -650,5 +664,8 @@
|
||||
<value>Fine coda #{0} dell'elenco scoperte Steam.</value>
|
||||
<comment>{0} will be replaced by queue number</comment>
|
||||
</data>
|
||||
|
||||
<data name="BotOwnsOverview" xml:space="preserve">
|
||||
<value>Ci sono {0}/{1} bots che posseggono già tutti i giochi che sono stati controllati.</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>
|
||||
|
||||
@@ -170,10 +170,6 @@
|
||||
<value>{0} の解析に失敗しました!</value>
|
||||
<comment>{0} will be replaced by object's name</comment>
|
||||
</data>
|
||||
<data name="ErrorRemovingOldBinary" xml:space="preserve">
|
||||
<value>古いASFのバイナリを削除できませんでした。アップデート機能を動作させるには、{0} を手動で削除してください!</value>
|
||||
<comment>{0} will be replaced by file's path</comment>
|
||||
</data>
|
||||
<data name="ErrorRequestFailedTooManyTimes" xml:space="preserve">
|
||||
<value>要求を{0} 回試行し、失敗しました!</value>
|
||||
<comment>{0} will be replaced by maximum number of tries</comment>
|
||||
@@ -188,7 +184,10 @@
|
||||
<data name="ErrorUserInputRunningInHeadlessMode" xml:space="preserve">
|
||||
<value>ユーザー入力のリクエストを受け取りましたが、プロセスはheadlessモードで実行されています!</value>
|
||||
</data>
|
||||
|
||||
<data name="ErrorIPCAccessDenied" xml:space="preserve">
|
||||
<value>SteamOwnerIDが設定されていないため、要求を処理しませんでした!</value>
|
||||
<comment>SteamOwnerID is name of bot config property, it should not be translated</comment>
|
||||
</data>
|
||||
<data name="Exiting" xml:space="preserve">
|
||||
<value>終了中...</value>
|
||||
</data>
|
||||
@@ -238,7 +237,10 @@
|
||||
<data name="UpdateCheckingNewVersion" xml:space="preserve">
|
||||
<value>新しいバージョンをチェックしています...</value>
|
||||
</data>
|
||||
|
||||
<data name="UpdateDownloadingNewVersion" xml:space="preserve">
|
||||
<value>新しいバージョンをダウンロードしています: {0} ({1} MB) ... 待っている間、作者への寄付をご検討ください! :)</value>
|
||||
<comment>{0} will be replaced by version string, {1} will be replaced by update size (in megabytes)</comment>
|
||||
</data>
|
||||
<data name="UpdateFinished" xml:space="preserve">
|
||||
<value>アップデート完了!</value>
|
||||
</data>
|
||||
@@ -277,7 +279,10 @@
|
||||
<value>{0} の文章化されていない値を入力してください: </value>
|
||||
<comment>{0} will be replaced by property name. Please note that this translation should end with space</comment>
|
||||
</data>
|
||||
|
||||
<data name="UserInputIPCHost" xml:space="preserve">
|
||||
<value>あなたのIPCホストを入力してください: </value>
|
||||
<comment>Please note that this translation should end with space</comment>
|
||||
</data>
|
||||
<data name="WarningUnknownValuePleaseReport" xml:space="preserve">
|
||||
<value>{0} に不明な値を受信しました。この問題を報告してください: {1}</value>
|
||||
<comment>{0} will be replaced by object's name, {1} will be replaced by value for that object</comment>
|
||||
@@ -286,10 +291,17 @@
|
||||
<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>
|
||||
</data>
|
||||
|
||||
|
||||
|
||||
|
||||
<data name="IPCAnswered" xml:space="preserve">
|
||||
<value>IPC コマンド: {0} 返答:{1}</value>
|
||||
<comment>{0} will be replaced by IPC command, {1} will be replaced by IPC answer</comment>
|
||||
</data>
|
||||
<data name="IPCReady" xml:space="preserve">
|
||||
<value>IPC サーバーの準備ができました!</value>
|
||||
</data>
|
||||
<data name="IPCStarting" xml:space="preserve">
|
||||
<value>IPC サーバーを {0} に開始中...</value>
|
||||
<comment>{0} will be replaced by IPC hostname</comment>
|
||||
</data>
|
||||
<data name="BotAlreadyStopped" xml:space="preserve">
|
||||
<value>このBotは既に停止しています!</value>
|
||||
</data>
|
||||
|
||||
@@ -173,10 +173,6 @@ StackTrace:
|
||||
<value>{0}의 분석에 실패했습니다!</value>
|
||||
<comment>{0} will be replaced by object's name</comment>
|
||||
</data>
|
||||
<data name="ErrorRemovingOldBinary" xml:space="preserve">
|
||||
<value>이전의 ASF 실행 파일을 제거할 수 없습니다. 업데이트 기능의 작동을 위해, 수동으로 {0}을(를) 제거하시기 바랍니다.</value>
|
||||
<comment>{0} will be replaced by file's path</comment>
|
||||
</data>
|
||||
<data name="ErrorRequestFailedTooManyTimes" xml:space="preserve">
|
||||
<value>{0} 번의 시도 후, 요청이 실패했습니다!</value>
|
||||
<comment>{0} will be replaced by maximum number of tries</comment>
|
||||
@@ -300,9 +296,6 @@ StackTrace:
|
||||
<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>
|
||||
</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>
|
||||
|
||||
@@ -149,7 +149,7 @@
|
||||
<comment>{0} will be replaced by URL of the request</comment>
|
||||
</data>
|
||||
<data name="ErrorGlobalConfigNotLoaded" xml:space="preserve">
|
||||
<value>Pasaulinė konfigūracija negalėjo būti įkelta, įsitikinkite, kad {0} egzistuoja ir yra galiojantis! Sekite steigimo vadovą vikipedijoje jei esate supainioti.</value>
|
||||
<value>Pagrindinė konfigūracija negalėjo būti įkelta, įsitikinkite, kad {0} egzistuoja ir yra galiojantis! Sekite steigimo vadovą vikipedijoje jei esate supainioti.</value>
|
||||
<comment>{0} will be replaced by file's path</comment>
|
||||
</data>
|
||||
<data name="ErrorIsInvalid" xml:space="preserve">
|
||||
@@ -170,10 +170,6 @@
|
||||
<value>Apdorojanti {0} nepavyko!</value>
|
||||
<comment>{0} will be replaced by object's name</comment>
|
||||
</data>
|
||||
<data name="ErrorRemovingOldBinary" xml:space="preserve">
|
||||
<value>Nepavyko pašalinti senos ASF bibliotekos. Prašome pašalinti {0} rankiniu būdu tam, kad atnaujinimo funkcija veiktų!</value>
|
||||
<comment>{0} will be replaced by file's path</comment>
|
||||
</data>
|
||||
<data name="ErrorRequestFailedTooManyTimes" xml:space="preserve">
|
||||
<value>Užklausa nepavyko po {0} bandymų!</value>
|
||||
<comment>{0} will be replaced by maximum number of tries</comment>
|
||||
@@ -201,10 +197,10 @@
|
||||
<value>Nepavyko!</value>
|
||||
</data>
|
||||
<data name="GlobalConfigChanged" xml:space="preserve">
|
||||
<value>Pasaulinis konfiguracijos failas buvo pakeistas!</value>
|
||||
<value>Pagrindinis konfiguracijos failas buvo pakeistas!</value>
|
||||
</data>
|
||||
<data name="ErrorGlobalConfigRemoved" xml:space="preserve">
|
||||
<value>Pasaulinis konfiguracijos failas buvo pašalintas!</value>
|
||||
<value>Pagrindinis konfiguracijos failas buvo pašalintas!</value>
|
||||
</data>
|
||||
<data name="IgnoringTrade" xml:space="preserve">
|
||||
<value>Ignoruojami mainai: {0}</value>
|
||||
@@ -297,9 +293,6 @@
|
||||
<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>
|
||||
</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>
|
||||
|
||||
@@ -172,10 +172,6 @@ StackTrace:
|
||||
<value>Verwerking van {0} mislukt!</value>
|
||||
<comment>{0} will be replaced by object's name</comment>
|
||||
</data>
|
||||
<data name="ErrorRemovingOldBinary" xml:space="preserve">
|
||||
<value>Het oude ASF-bestand kon niet worden verwijderd. Verwijder {0} handmatig, zodat de update uitgevoerd kan worden!</value>
|
||||
<comment>{0} will be replaced by file's path</comment>
|
||||
</data>
|
||||
<data name="ErrorRequestFailedTooManyTimes" xml:space="preserve">
|
||||
<value>Verzoek is mislukt na {0} pogingen!</value>
|
||||
<comment>{0} will be replaced by maximum number of tries</comment>
|
||||
@@ -300,9 +296,6 @@ StackTrace:
|
||||
<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>
|
||||
</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>
|
||||
|
||||
@@ -172,10 +172,6 @@ StackTrace:
|
||||
<value>Verwerking van {0} mislukt!</value>
|
||||
<comment>{0} will be replaced by object's name</comment>
|
||||
</data>
|
||||
<data name="ErrorRemovingOldBinary" xml:space="preserve">
|
||||
<value>Het oude ASF-bestand kon niet worden verwijderd. Verwijder {0} handmatig, zodat de update uitgevoerd kan worden!</value>
|
||||
<comment>{0} will be replaced by file's path</comment>
|
||||
</data>
|
||||
<data name="ErrorRequestFailedTooManyTimes" xml:space="preserve">
|
||||
<value>Verzoek is mislukt na {0} pogingen!</value>
|
||||
<comment>{0} will be replaced by maximum number of tries</comment>
|
||||
@@ -300,9 +296,6 @@ StackTrace:
|
||||
<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>
|
||||
</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>
|
||||
|
||||
@@ -143,7 +143,6 @@
|
||||
|
||||
|
||||
|
||||
|
||||
<data name="Exiting" xml:space="preserve">
|
||||
<value>Avslutter...</value>
|
||||
</data>
|
||||
@@ -196,7 +195,6 @@
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
<data name="Done" xml:space="preserve">
|
||||
|
||||
@@ -173,10 +173,6 @@ StackTrace:
|
||||
<value>Analizowanie {0} nie powiodło się!</value>
|
||||
<comment>{0} will be replaced by object's name</comment>
|
||||
</data>
|
||||
<data name="ErrorRemovingOldBinary" xml:space="preserve">
|
||||
<value>Nie można usunąć starej binarki ASF. Usuń ręcznie {0} aby naprawić funkcję aktualizacji!</value>
|
||||
<comment>{0} will be replaced by file's path</comment>
|
||||
</data>
|
||||
<data name="ErrorRequestFailedTooManyTimes" xml:space="preserve">
|
||||
<value>Żądanie nie powiodło się, po {0} próbach!</value>
|
||||
<comment>{0} will be replaced by maximum number of tries</comment>
|
||||
@@ -300,9 +296,6 @@ StackTrace:
|
||||
<value>Uruchomienie więcej niż {0} gier naraz nie jest możliwe, jedynie pierwsze {0} gier z {1} zostanie użytych!</value>
|
||||
<comment>{0} will be replaced by max number of games, {1} will be replaced by name of the configuration property</comment>
|
||||
</data>
|
||||
<data name="ErrorIPCAddressAccessDeniedException" xml:space="preserve">
|
||||
<value>Usługa IPC nie mogła zostać uruchomiona z powodu błędu AddressAccessDeniedException! Jeżeli chcesz używać usługi IPC dostarczonej przez ASF, przemyśl uruchomienie ASF jako administrator lub przyznanie odpowiednich uprawnień!</value>
|
||||
</data>
|
||||
<data name="IPCAnswered" xml:space="preserve">
|
||||
<value>Odpowiedziano na komendę IPC: {0} wiadomością: {1}</value>
|
||||
<comment>{0} will be replaced by IPC command, {1} will be replaced by IPC answer</comment>
|
||||
|
||||
@@ -173,10 +173,6 @@ StackTrace:
|
||||
<value>Falha ao analisar {0}!</value>
|
||||
<comment>{0} will be replaced by object's name</comment>
|
||||
</data>
|
||||
<data name="ErrorRemovingOldBinary" xml:space="preserve">
|
||||
<value>Não foi possível remover o arquivo {0}. Por favor, remova-o manualmente para que a atualização funcione normalmente!</value>
|
||||
<comment>{0} will be replaced by file's path</comment>
|
||||
</data>
|
||||
<data name="ErrorRequestFailedTooManyTimes" xml:space="preserve">
|
||||
<value>A solicitação falhou após {0} tentativas!</value>
|
||||
<comment>{0} will be replaced by maximum number of tries</comment>
|
||||
@@ -247,7 +243,7 @@ StackTrace:
|
||||
<value>Verificando se há atualizações...</value>
|
||||
</data>
|
||||
<data name="UpdateDownloadingNewVersion" xml:space="preserve">
|
||||
<value>Baixando uma nova versão: {0} ({1} MB)... Enquanto aguarda, considere doar se você aprecia o nosso trabalho! :)</value>
|
||||
<value>Baixando nova versão: {0} ({1} MB)... Enquanto espera, considere doar caso aprecie o nosso trabalho! :)</value>
|
||||
<comment>{0} will be replaced by version string, {1} will be replaced by update size (in megabytes)</comment>
|
||||
</data>
|
||||
<data name="UpdateFinished" xml:space="preserve">
|
||||
@@ -289,7 +285,7 @@ StackTrace:
|
||||
<comment>{0} will be replaced by property name. Please note that this translation should end with space</comment>
|
||||
</data>
|
||||
<data name="UserInputIPCHost" xml:space="preserve">
|
||||
<value>Por favor insira o seu host IPC: </value>
|
||||
<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">
|
||||
@@ -300,9 +296,6 @@ 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>
|
||||
<comment>{0} will be replaced by max number of games, {1} will be replaced by name of the configuration property</comment>
|
||||
</data>
|
||||
<data name="ErrorIPCAddressAccessDeniedException" xml:space="preserve">
|
||||
<value>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>
|
||||
@@ -416,7 +409,7 @@ StackTrace:
|
||||
<comment>{0} will be replaced by giftID (number)</comment>
|
||||
</data>
|
||||
<data name="BotAccountLimited" xml:space="preserve">
|
||||
<value>Esta conta está limitada, processo de farm indisponível até que a restrição seja removida!</value>
|
||||
<value>Esta conta é limitada, processo de coleta indisponível até que a restrição seja removida!</value>
|
||||
</data>
|
||||
<data name="BotAddLicense" xml:space="preserve">
|
||||
<value>ID: {0} | Estado: {1}</value>
|
||||
@@ -446,13 +439,13 @@ StackTrace:
|
||||
<value>O processo de coleta automático foi pausado!</value>
|
||||
</data>
|
||||
<data name="BotAutomaticIdlingNowResumed" xml:space="preserve">
|
||||
<value>O processo de farm automático foi retomado!</value>
|
||||
<value>Coleta automática retomada!</value>
|
||||
</data>
|
||||
<data name="BotAutomaticIdlingPausedAlready" xml:space="preserve">
|
||||
<value>A coleta automática de cartas já está pausada!</value>
|
||||
</data>
|
||||
<data name="BotAutomaticIdlingPausedWithCountdown" xml:space="preserve">
|
||||
<value>Idle automático foi pausado! Você tem {0} para começar um jogo.</value>
|
||||
<value>Coleta automática pausada! Você tem {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">
|
||||
@@ -530,7 +523,7 @@ StackTrace:
|
||||
<comment>{0} will be replaced by game's ID (number), {1} will be replaced by game's name</comment>
|
||||
</data>
|
||||
<data name="BotRateLimitExceeded" xml:space="preserve">
|
||||
<value>Limite de taxas excedido; vamos tentar novamente depois do intervalo de {0}...</value>
|
||||
<value>Limite de tráfego excedido; tentaremos novamente após um intervalo de {0}...</value>
|
||||
<comment>{0} will be replaced by translated TimeSpan string (such as "25 minutes")</comment>
|
||||
</data>
|
||||
<data name="BotReconnecting" xml:space="preserve">
|
||||
@@ -563,7 +556,7 @@ StackTrace:
|
||||
<value>Bot está pausado ou funcionando em modo manual.</value>
|
||||
</data>
|
||||
<data name="BotStatusPlayingNotAvailable" xml:space="preserve">
|
||||
<value>Bot está sendo usado no momento.</value>
|
||||
<value>Bot está sendo usado.</value>
|
||||
</data>
|
||||
<data name="BotUnableToLogin" xml:space="preserve">
|
||||
<value>Não foi possível iniciar a sessão no Steam: {0}/{1}</value>
|
||||
@@ -582,16 +575,16 @@ StackTrace:
|
||||
<comment>{0} will be replaced by failure reason (string)</comment>
|
||||
</data>
|
||||
<data name="BotConnectionLost" xml:space="preserve">
|
||||
<value>Conexão com a rede Steam perdida. Reconectando...</value>
|
||||
<value>A conexão à rede Steam foi perdida. Reconectando...</value>
|
||||
</data>
|
||||
<data name="BotAccountFree" xml:space="preserve">
|
||||
<value>A conta não está mais sendo usada: processo de coleta de cartas retomado!</value>
|
||||
</data>
|
||||
<data name="BotAccountOccupied" xml:space="preserve">
|
||||
<value>A conta está sendo usada no momento, o ASF voltará a farmar quando ela estiver livre...</value>
|
||||
<value>Conta em uso, o ASF retomará a coleta quando ela estiver disponível...</value>
|
||||
</data>
|
||||
<data name="BotAutomaticIdlingPauseTimeout" xml:space="preserve">
|
||||
<value>A biblioteca compartilhada não foi iniciada no período de tempo especificado, o farm foi retomado!</value>
|
||||
<value>A biblioteca compartilhada não foi iniciada no período de tempo especificado. O processo de coleta foi retomado!</value>
|
||||
</data>
|
||||
<data name="BotConnecting" xml:space="preserve">
|
||||
<value>Conectando...</value>
|
||||
@@ -666,15 +659,15 @@ StackTrace:
|
||||
<comment>{0} will be replaced by number (in megabytes) of memory being used</comment>
|
||||
</data>
|
||||
<data name="ClearingDiscoveryQueue" xml:space="preserve">
|
||||
<value>Limpando a fila de descoberta do Steam #{0}...</value>
|
||||
<value>Limpando a lista de descobrimento #{0}...</value>
|
||||
<comment>{0} will be replaced by queue number</comment>
|
||||
</data>
|
||||
<data name="DoneClearingDiscoveryQueue" xml:space="preserve">
|
||||
<value>Limpeza da lista de descoberta da Steam #{0} concluída.</value>
|
||||
<value>Limpeza da lista de descobrimento #{0} concluída.</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ão sendo verificados.</value>
|
||||
<value>Há {0} de {1} bots que já possuem todos os jogos 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>
|
||||
|
||||
@@ -173,10 +173,6 @@ StackTrace:
|
||||
<value>Falha ao analisar {0}!</value>
|
||||
<comment>{0} will be replaced by object's name</comment>
|
||||
</data>
|
||||
<data name="ErrorRemovingOldBinary" xml:space="preserve">
|
||||
<value>Não foi possível remover o velho ASF, por favor remova {0} manualmente para a função de atualização funcionar!</value>
|
||||
<comment>{0} will be replaced by file's path</comment>
|
||||
</data>
|
||||
<data name="ErrorRequestFailedTooManyTimes" xml:space="preserve">
|
||||
<value>O pedido falhou após {0} tentativas!</value>
|
||||
<comment>{0} will be replaced by maximum number of tries</comment>
|
||||
@@ -300,9 +296,6 @@ StackTrace:
|
||||
<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>
|
||||
</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>
|
||||
|
||||
@@ -173,10 +173,6 @@ StackTrace:
|
||||
<value>Parsing {0} failed!</value>
|
||||
<comment>{0} will be replaced by object's name</comment>
|
||||
</data>
|
||||
<data name="ErrorRemovingOldBinary" xml:space="preserve">
|
||||
<value>Could not remove old ASF binary. Please remove {0} manually in order for update function to work!</value>
|
||||
<comment>{0} will be replaced by file's path</comment>
|
||||
</data>
|
||||
<data name="ErrorRequestFailedTooManyTimes" xml:space="preserve">
|
||||
<value>Request failed after {0} attempts!</value>
|
||||
<comment>{0} will be replaced by maximum number of tries</comment>
|
||||
@@ -300,9 +296,6 @@ StackTrace:
|
||||
<value>Playing more than {0} games concurrently is not possible, only first {0} entries from {1} will be used!</value>
|
||||
<comment>{0} will be replaced by max number of games, {1} will be replaced by name of the configuration property</comment>
|
||||
</data>
|
||||
<data name="ErrorIPCAddressAccessDeniedException" xml:space="preserve">
|
||||
<value>IPC service could not be started because of AddressAccessDeniedException! If you want to use IPC service provided by ASF, consider starting ASF as administrator, or giving proper permissions!</value>
|
||||
</data>
|
||||
<data name="IPCAnswered" xml:space="preserve">
|
||||
<value>Answered to IPC command: {0} with: {1}</value>
|
||||
<comment>{0} will be replaced by IPC command, {1} will be replaced by IPC answer</comment>
|
||||
|
||||
@@ -173,10 +173,6 @@ StackTrace:
|
||||
<value>Parsarea {0} a eșuat!</value>
|
||||
<comment>{0} will be replaced by object's name</comment>
|
||||
</data>
|
||||
<data name="ErrorRemovingOldBinary" xml:space="preserve">
|
||||
<value>Nu a fost posibilă eliminarea ultimului executabil ASF. Te rog să elimini manual {0} pentru ca funcția de actualizare să funcționeze!</value>
|
||||
<comment>{0} will be replaced by file's path</comment>
|
||||
</data>
|
||||
<data name="ErrorRequestFailedTooManyTimes" xml:space="preserve">
|
||||
<value>Cererea a eșuat după {0} încercări!</value>
|
||||
<comment>{0} will be replaced by maximum number of tries</comment>
|
||||
@@ -300,9 +296,6 @@ StackTrace:
|
||||
<value>Jucarea a mai mult de {0} jocuri concomitent nu este posibil, numai primele {0} intrări de la {1} vor fi folosite!</value>
|
||||
<comment>{0} will be replaced by max number of games, {1} will be replaced by name of the configuration property</comment>
|
||||
</data>
|
||||
<data name="ErrorIPCAddressAccessDeniedException" xml:space="preserve">
|
||||
<value>Serviciul IPC nu a putut fi pornit din cauza AddressAccessDeniedException! Dacă dorești să utilizezi serviciul IPC oferit de ASF, ia în considerare rularea ASF-ului ca administrator sau acordarea de permisiuni adecvate!</value>
|
||||
</data>
|
||||
<data name="IPCAnswered" xml:space="preserve">
|
||||
<value>Se răspunde la comanda IPC: {0} cu: {1}</value>
|
||||
<comment>{0} will be replaced by IPC command, {1} will be replaced by IPC answer</comment>
|
||||
|
||||
@@ -173,10 +173,6 @@
|
||||
<value>Обработка {0} не удалась!</value>
|
||||
<comment>{0} will be replaced by object's name</comment>
|
||||
</data>
|
||||
<data name="ErrorRemovingOldBinary" xml:space="preserve">
|
||||
<value>Невозможно удалить старый исполняемый файл ASF. Пожалуйста, удалите {0} самостоятельно, для восстановления функции автоматического обновления!</value>
|
||||
<comment>{0} will be replaced by file's path</comment>
|
||||
</data>
|
||||
<data name="ErrorRequestFailedTooManyTimes" xml:space="preserve">
|
||||
<value>Запрос окончился неудачей после {0} попыток!</value>
|
||||
<comment>{0} will be replaced by maximum number of tries</comment>
|
||||
@@ -247,7 +243,7 @@
|
||||
<value>Проверка новой версии...</value>
|
||||
</data>
|
||||
<data name="UpdateDownloadingNewVersion" xml:space="preserve">
|
||||
<value>Скачивание новой версии: {0} ({1} MB)... В ожидании, подумайте о пожертвовании, если вам нравится проделанная работа! :)</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 name="UpdateFinished" xml:space="preserve">
|
||||
@@ -300,11 +296,8 @@
|
||||
<value>Невозможен запуск более {0} игр одновременно, будут использованы только первые {0} записей из параметра {1}!</value>
|
||||
<comment>{0} will be replaced by max number of games, {1} will be replaced by name of the configuration property</comment>
|
||||
</data>
|
||||
<data name="ErrorIPCAddressAccessDeniedException" xml:space="preserve">
|
||||
<value>Сервис IPC не может быть запущен, из-за "AddressAccessDeniedException" (исключение: отказ в доступе к адресу)! Если Вы желаете использовать сервис IPC, предоставляемый ASF, то попробуйте запустить ASF от имени администратора, или выдать необходимые права!</value>
|
||||
</data>
|
||||
<data name="IPCAnswered" xml:space="preserve">
|
||||
<value>На команду IPC: {0} был ответ: {1}</value>
|
||||
<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">
|
||||
@@ -673,5 +666,8 @@
|
||||
<value>Очищен список рекомендаций #{0}.</value>
|
||||
<comment>{0} will be replaced by queue number</comment>
|
||||
</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>
|
||||
|
||||
@@ -173,10 +173,6 @@ StackTrace:
|
||||
<value>Spracovávanie {0} zlyhalo!</value>
|
||||
<comment>{0} will be replaced by object's name</comment>
|
||||
</data>
|
||||
<data name="ErrorRemovingOldBinary" xml:space="preserve">
|
||||
<value>Nepodarilo sa odstrániť starý binárny súbor ASF. Aby aktualizačná funkcia pracovala správne, odstráň prosím súbor {0} ručne!</value>
|
||||
<comment>{0} will be replaced by file's path</comment>
|
||||
</data>
|
||||
<data name="ErrorRequestFailedTooManyTimes" xml:space="preserve">
|
||||
<value>Požiadavka zlyhala po {0} pokusoch!</value>
|
||||
<comment>{0} will be replaced by maximum number of tries</comment>
|
||||
@@ -292,7 +288,6 @@ StackTrace:
|
||||
|
||||
|
||||
|
||||
|
||||
<data name="BotAlreadyStopped" xml:space="preserve">
|
||||
<value>Tento bot už bol zastavený!</value>
|
||||
</data>
|
||||
|
||||
@@ -164,7 +164,6 @@ StackTrace:
|
||||
<comment>{0} will be replaced by object's name</comment>
|
||||
</data>
|
||||
|
||||
|
||||
<data name="ErrorUpdateCheckFailed" xml:space="preserve">
|
||||
<value>Neuspešno traženje nove verzije!</value>
|
||||
</data>
|
||||
@@ -256,7 +255,6 @@ StackTrace:
|
||||
|
||||
|
||||
|
||||
|
||||
<data name="BotNotFound" xml:space="preserve">
|
||||
<value>Nije moguće pronaći bilo kakvog bot-a nazvanog {0}!</value>
|
||||
<comment>{0} will be replaced by bot's name query (string)</comment>
|
||||
|
||||
@@ -152,7 +152,7 @@ StackTrace:
|
||||
<comment>{0} will be replaced by URL of the request</comment>
|
||||
</data>
|
||||
<data name="ErrorGlobalConfigNotLoaded" xml:space="preserve">
|
||||
<value>Den globala konfigurationen kunde inte laddas. Försäkra dig om att {0} existerar och är valid! Följ installations-guiden på wiki-sidan om du är förvirrad.</value>
|
||||
<value>Den globala konfigurationen kunde inte laddas. Försäkra dig om att {0} existerar och är giltlig! Följ installationsguiden på wikisidan om du är förvirrad.</value>
|
||||
<comment>{0} will be replaced by file's path</comment>
|
||||
</data>
|
||||
<data name="ErrorIsInvalid" xml:space="preserve">
|
||||
@@ -173,10 +173,6 @@ StackTrace:
|
||||
<value>Tolkning {0} misslyckades!</value>
|
||||
<comment>{0} will be replaced by object's name</comment>
|
||||
</data>
|
||||
<data name="ErrorRemovingOldBinary" xml:space="preserve">
|
||||
<value>Kunde inte ta bort den gamla ASF binären, vänligen ta bort {0} manuellt för att uppdateringsfunktionen ska fungera!</value>
|
||||
<comment>{0} will be replaced by file's path</comment>
|
||||
</data>
|
||||
<data name="ErrorRequestFailedTooManyTimes" xml:space="preserve">
|
||||
<value>Begäran misslyckades efter {0} försök!</value>
|
||||
<comment>{0} will be replaced by maximum number of tries</comment>
|
||||
@@ -184,14 +180,19 @@ StackTrace:
|
||||
<data name="ErrorUpdateCheckFailed" xml:space="preserve">
|
||||
<value>Kunde inte kontrollera senaste versionen!</value>
|
||||
</data>
|
||||
|
||||
<data name="ErrorUpdateNoAssetForThisVersion" xml:space="preserve">
|
||||
<value>Kunde inte fortsätta med uppdateringen eftersom det inte finns någon tillgång som relaterar till den nuvarande versionen! Automatisk uppdatering till denna version är inte möjlig.</value>
|
||||
</data>
|
||||
<data name="ErrorUpdateNoAssets" xml:space="preserve">
|
||||
<value>Kunde inte fortsätta med en uppdatering för den versionen innehåller inte några filer!</value>
|
||||
</data>
|
||||
<data name="ErrorUserInputRunningInHeadlessMode" xml:space="preserve">
|
||||
<value>Mottagit en begäran om användarens input, men processen körs i huvudlöst läge!</value>
|
||||
</data>
|
||||
|
||||
<data name="ErrorIPCAccessDenied" xml:space="preserve">
|
||||
<value>Vägrar att hantera begäran eftersom SteamOwnerID inte är inställt!</value>
|
||||
<comment>SteamOwnerID is name of bot config property, it should not be translated</comment>
|
||||
</data>
|
||||
<data name="Exiting" xml:space="preserve">
|
||||
<value>Stänger ner...</value>
|
||||
</data>
|
||||
@@ -205,7 +206,7 @@ StackTrace:
|
||||
<value>Globala konfigurationsfilen har tagits bort!</value>
|
||||
</data>
|
||||
<data name="IgnoringTrade" xml:space="preserve">
|
||||
<value>Avböjer byte: {0}</value>
|
||||
<value>Ignorerar bytesförfrågan: {0}</value>
|
||||
<comment>{0} will be replaced by trade number</comment>
|
||||
</data>
|
||||
<data name="LoggingIn" xml:space="preserve">
|
||||
@@ -216,7 +217,7 @@ StackTrace:
|
||||
<value>Inga bottar körs just nu, stänger ner...</value>
|
||||
</data>
|
||||
<data name="RefreshingOurSession" xml:space="preserve">
|
||||
<value>Startar om våran session!</value>
|
||||
<value>Startar om sessionen!</value>
|
||||
</data>
|
||||
<data name="RejectingTrade" xml:space="preserve">
|
||||
<value>Avböjer bytesförfrågan: {0}</value>
|
||||
@@ -236,12 +237,16 @@ StackTrace:
|
||||
<value>Framgång!</value>
|
||||
</data>
|
||||
<data name="UnlockingParentalAccount" xml:space="preserve">
|
||||
<value>Låser upp föräldrarnas konto...</value>
|
||||
<value>Låser upp föräldrarkontot...</value>
|
||||
</data>
|
||||
<data name="UpdateCheckingNewVersion" xml:space="preserve">
|
||||
<value>Söker efter senaste versionen...</value>
|
||||
</data>
|
||||
|
||||
<data name="UpdateDownloadingNewVersion" xml:space="preserve">
|
||||
<value>Laddar ner senaste versionen: {0} ({1} MB)... Medan du väntar, fundera på att donera om du uppskattar arbetet som görs!
|
||||
:)</value>
|
||||
<comment>{0} will be replaced by version string, {1} will be replaced by update size (in megabytes)</comment>
|
||||
</data>
|
||||
<data name="UpdateFinished" xml:space="preserve">
|
||||
<value>Uppdateringsprocessen klar!</value>
|
||||
</data>
|
||||
@@ -253,7 +258,7 @@ StackTrace:
|
||||
<comment>{0} will be replaced by current version, {1} will be replaced by remote version</comment>
|
||||
</data>
|
||||
<data name="UserInputDeviceID" xml:space="preserve">
|
||||
<value>Vänligen ange din mobil-autentiserares enhets-ID (inklusive "android:"): </value>
|
||||
<value>Vänligen ange mobil-autentiserarens enhets-ID (inklusive "android:"): </value>
|
||||
<comment>Please note that this translation should end with space</comment>
|
||||
</data>
|
||||
<data name="UserInputSteam2FA" xml:space="preserve">
|
||||
@@ -280,7 +285,10 @@ StackTrace:
|
||||
<value>Vänligen ange odokumenterade värde av {0}: </value>
|
||||
<comment>{0} will be replaced by property name. Please note that this translation should end with space</comment>
|
||||
</data>
|
||||
|
||||
<data name="UserInputIPCHost" xml:space="preserve">
|
||||
<value>Vänligen ange din IPC Host: </value>
|
||||
<comment>Please note that this translation should end with space</comment>
|
||||
</data>
|
||||
<data name="WarningUnknownValuePleaseReport" xml:space="preserve">
|
||||
<value>Tog emot okänt värde för {0}, vänligen rapportera detta: {1}</value>
|
||||
<comment>{0} will be replaced by object's name, {1} will be replaced by value for that object</comment>
|
||||
@@ -289,10 +297,17 @@ StackTrace:
|
||||
<value>Går inte att spela mer än {0} spel samtidigt, bara första {0} poster från {1} kommer att användas!</value>
|
||||
<comment>{0} will be replaced by max number of games, {1} will be replaced by name of the configuration property</comment>
|
||||
</data>
|
||||
|
||||
|
||||
|
||||
|
||||
<data name="IPCAnswered" xml:space="preserve">
|
||||
<value>Svarade till IPC kommandot {0} med: {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 servern är redo!</value>
|
||||
</data>
|
||||
<data name="IPCStarting" xml:space="preserve">
|
||||
<value>Startar IPC servern på {0}...</value>
|
||||
<comment>{0} will be replaced by IPC hostname</comment>
|
||||
</data>
|
||||
<data name="BotAlreadyStopped" xml:space="preserve">
|
||||
<value>Bot-instansen har redan stoppats!</value>
|
||||
</data>
|
||||
@@ -640,8 +655,20 @@ StackTrace:
|
||||
<data name="WarningPreReleaseVersion" xml:space="preserve">
|
||||
<value>Du använder en version som är nyare än den senast släppta versionen i din uppdateringskanal. Vänligen notera att förhandsversioner är avsedda för användare med förmågan att rapportera buggar, handskas med eventuella problem och viljan att ge feedback - Ingen teknisk support kommer att ges.</value>
|
||||
</data>
|
||||
|
||||
|
||||
|
||||
|
||||
<data name="BotStats" xml:space="preserve">
|
||||
<value>Nuvarande minnesförbrukning: {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>Genomgår Steam discovery-kön #{0}...</value>
|
||||
<comment>{0} will be replaced by queue number</comment>
|
||||
</data>
|
||||
<data name="DoneClearingDiscoveryQueue" xml:space="preserve">
|
||||
<value>Genomgått Steam discovery-kön #{0}.</value>
|
||||
<comment>{0} will be replaced by queue number</comment>
|
||||
</data>
|
||||
<data name="BotOwnsOverview" xml:space="preserve">
|
||||
<value>Det finns {0}/{1} bottar som redan äger samtliga spel som kontrollerats.</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>
|
||||
|
||||
@@ -173,10 +173,6 @@ Yığın izleme:
|
||||
<value>{0} nesnesi işlenirken bir hata oluştu!</value>
|
||||
<comment>{0} will be replaced by object's name</comment>
|
||||
</data>
|
||||
<data name="ErrorRemovingOldBinary" xml:space="preserve">
|
||||
<value>Eski ASF ikili dosyası kaldırılamadı, lütfen güncelleme fonksiyonunun çalışması için {0} dosyasını el ile kaldırın!</value>
|
||||
<comment>{0} will be replaced by file's path</comment>
|
||||
</data>
|
||||
<data name="ErrorRequestFailedTooManyTimes" xml:space="preserve">
|
||||
<value>{0} denemeden sonra istek başarısız oldu!</value>
|
||||
<comment>{0} will be replaced by maximum number of tries</comment>
|
||||
@@ -300,9 +296,6 @@ 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>
|
||||
<comment>{0} will be replaced by max number of games, {1} will be replaced by name of the configuration property</comment>
|
||||
</data>
|
||||
<data name="ErrorIPCAddressAccessDeniedException" xml:space="preserve">
|
||||
<value>IPC 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>
|
||||
|
||||
@@ -173,10 +173,6 @@
|
||||
<value>Не вдалося обробити {0}!</value>
|
||||
<comment>{0} will be replaced by object's name</comment>
|
||||
</data>
|
||||
<data name="ErrorRemovingOldBinary" xml:space="preserve">
|
||||
<value>Неможливо видалити старий виконуваний файл ASF. Будь ласка, видаліть {0} самостійно, для відновлення функції автоматичного оновлення!</value>
|
||||
<comment>{0} will be replaced by file's path</comment>
|
||||
</data>
|
||||
<data name="ErrorRequestFailedTooManyTimes" xml:space="preserve">
|
||||
<value>Не вдалося виконати запит після {0} спроб!</value>
|
||||
<comment>{0} will be replaced by maximum number of tries</comment>
|
||||
@@ -292,7 +288,6 @@
|
||||
|
||||
|
||||
|
||||
|
||||
<data name="BotAlreadyStopped" xml:space="preserve">
|
||||
<value>Цей бот вже зупинений!</value>
|
||||
</data>
|
||||
|
||||
@@ -173,10 +173,6 @@ StackTrace:
|
||||
<value>Phân tách ngữ pháp {0} không thành công!</value>
|
||||
<comment>{0} will be replaced by object's name</comment>
|
||||
</data>
|
||||
<data name="ErrorRemovingOldBinary" xml:space="preserve">
|
||||
<value>Không thể xóa tập tin ASF cũ, vui lòng xóa thủ công {0} để chạy chức năng cập nhật!</value>
|
||||
<comment>{0} will be replaced by file's path</comment>
|
||||
</data>
|
||||
<data name="ErrorRequestFailedTooManyTimes" xml:space="preserve">
|
||||
<value>Yêu cầu thất bại sau {0} nỗ lực!</value>
|
||||
<comment>{0} will be replaced by maximum number of tries</comment>
|
||||
@@ -300,9 +296,6 @@ 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>
|
||||
<comment>{0} will be replaced by max number of games, {1} will be replaced by name of the configuration property</comment>
|
||||
</data>
|
||||
<data name="ErrorIPCAddressAccessDeniedException" xml:space="preserve">
|
||||
<value>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>
|
||||
|
||||
@@ -170,10 +170,6 @@
|
||||
<value>解析 {0} 失败 !</value>
|
||||
<comment>{0} will be replaced by object's name</comment>
|
||||
</data>
|
||||
<data name="ErrorRemovingOldBinary" xml:space="preserve">
|
||||
<value>无法删除旧的ASF文件,请手动移除{0} 以启动更新功能!</value>
|
||||
<comment>{0} will be replaced by file's path</comment>
|
||||
</data>
|
||||
<data name="ErrorRequestFailedTooManyTimes" xml:space="preserve">
|
||||
<value>在 {0} 次尝试后请求失败!</value>
|
||||
<comment>{0} will be replaced by maximum number of tries</comment>
|
||||
@@ -218,7 +214,7 @@
|
||||
<value>无账号正在运行,即将退出...</value>
|
||||
</data>
|
||||
<data name="RefreshingOurSession" xml:space="preserve">
|
||||
<value>刷新会话 !</value>
|
||||
<value>正在刷新会话!</value>
|
||||
</data>
|
||||
<data name="RejectingTrade" xml:space="preserve">
|
||||
<value>拒绝交易︰ {0}</value>
|
||||
@@ -297,9 +293,6 @@
|
||||
<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>
|
||||
</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>
|
||||
@@ -341,10 +334,10 @@
|
||||
<comment>{0} will be replaced by the name of chosen idling algorithm</comment>
|
||||
</data>
|
||||
<data name="Done" xml:space="preserve">
|
||||
<value>完成。</value>
|
||||
<value>完成!</value>
|
||||
</data>
|
||||
<data name="GamesToIdle" xml:space="preserve">
|
||||
<value>共有{0} 个游戏(共计{1} 张卡) 等待挂卡(~还剩{2})...</value>
|
||||
<value>共有 {0} 个游戏(共计 {1} 张卡) 等待挂卡(~还剩{2})...</value>
|
||||
<comment>{0} will be replaced by number of games, {1} will be replaced by number of cards, {2} will be replaced by translated TimeSpan string (such as "1 day, 5 hours and 30 minutes")</comment>
|
||||
</data>
|
||||
<data name="IdlingFinished" xml:space="preserve">
|
||||
@@ -363,13 +356,13 @@
|
||||
<comment>{0} will be replaced by game's ID (number), {1} will be replaced by game's name, {2} will be replaced by number of cards left to idle</comment>
|
||||
</data>
|
||||
<data name="IdlingStopped" xml:space="preserve">
|
||||
<value>挂卡已停止</value>
|
||||
<value>已停止挂卡!</value>
|
||||
</data>
|
||||
<data name="IgnoredStickyPauseEnabled" xml:space="preserve">
|
||||
<value>请求已忽略,因为强制暂停已开启</value>
|
||||
</data>
|
||||
<data name="NothingToIdle" xml:space="preserve">
|
||||
<value>该账户已经无卡可挂</value>
|
||||
<value>该账户已经无卡可挂!</value>
|
||||
</data>
|
||||
<data name="NowIdling" xml:space="preserve">
|
||||
<value>正在挂卡︰{0} ({1})</value>
|
||||
@@ -495,7 +488,7 @@
|
||||
<value>不能发送报价,因为没有定义主权限的用户!</value>
|
||||
</data>
|
||||
<data name="BotLootingNoLootableTypes" xml:space="preserve">
|
||||
<value>你没有设置任何拾取类型!</value>
|
||||
<value>你没有设置任何拾取类型!</value>
|
||||
</data>
|
||||
<data name="BotLootingNowDisabled" xml:space="preserve">
|
||||
<value>拾取现在已禁用!</value>
|
||||
|
||||
@@ -170,10 +170,6 @@
|
||||
<value>分析 {0} 失敗 !</value>
|
||||
<comment>{0} will be replaced by object's name</comment>
|
||||
</data>
|
||||
<data name="ErrorRemovingOldBinary" xml:space="preserve">
|
||||
<value>無法刪除舊的 ASF 檔案,請依序手動刪除 {0} ,使更新功能正常運作!</value>
|
||||
<comment>{0} will be replaced by file's path</comment>
|
||||
</data>
|
||||
<data name="ErrorRequestFailedTooManyTimes" xml:space="preserve">
|
||||
<value>嘗試請求 {0} 次後失敗!</value>
|
||||
<comment>{0} will be replaced by maximum number of tries</comment>
|
||||
@@ -289,7 +285,6 @@
|
||||
|
||||
|
||||
|
||||
|
||||
<data name="BotAlreadyStopped" xml:space="preserve">
|
||||
<value>這個 BOT 已經停止了!</value>
|
||||
</data>
|
||||
@@ -403,7 +398,7 @@
|
||||
<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">
|
||||
<value>BOT已在運行中!</value>
|
||||
<value>BOT 已在運行中!</value>
|
||||
</data>
|
||||
<data name="BotAuthenticatorConverting" xml:space="preserve">
|
||||
<value>正在將 .maFile 轉化成 ASF 的文件格式...</value>
|
||||
@@ -649,5 +644,8 @@
|
||||
<value>已完成 Steam 探索佇列 #{0}。</value>
|
||||
<comment>{0} will be replaced by queue number</comment>
|
||||
</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>
|
||||
|
||||
@@ -42,14 +42,14 @@ namespace ArchiSteamFarm {
|
||||
private const byte CodeInterval = 30;
|
||||
|
||||
private static readonly char[] CodeCharacters = { '2', '3', '4', '5', '6', '7', '8', '9', 'B', 'C', 'D', 'F', 'G', 'H', 'J', 'K', 'M', 'N', 'P', 'Q', 'R', 'T', 'V', 'W', 'X', 'Y' };
|
||||
private static readonly SemaphoreSlim TimeSemaphore = new SemaphoreSlim(1);
|
||||
private static readonly SemaphoreSlim TimeSemaphore = new SemaphoreSlim(1, 1);
|
||||
|
||||
private static int? SteamTimeDifference;
|
||||
|
||||
// "ERROR" is being used by SteamDesktopAuthenticator
|
||||
internal bool HasCorrectDeviceID => !string.IsNullOrEmpty(DeviceID) && !DeviceID.Equals("ERROR");
|
||||
|
||||
private readonly SemaphoreSlim ConfirmationsSemaphore = new SemaphoreSlim(1);
|
||||
private readonly SemaphoreSlim ConfirmationsSemaphore = new SemaphoreSlim(1, 1);
|
||||
|
||||
#pragma warning disable 649
|
||||
[JsonProperty(PropertyName = "identity_secret", Required = Required.Always)]
|
||||
|
||||
@@ -23,6 +23,7 @@
|
||||
*/
|
||||
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Runtime.InteropServices;
|
||||
using ArchiSteamFarm.Localization;
|
||||
|
||||
@@ -44,6 +45,17 @@ namespace ArchiSteamFarm {
|
||||
}
|
||||
}
|
||||
|
||||
internal static void UnixSetFileAccessExecutable(string path) {
|
||||
if (!File.Exists(path)) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Chmod() returns 0 on success, -1 on failure
|
||||
if (NativeMethods.Chmod(path, (int) NativeMethods.UnixExecutePermission) != 0) {
|
||||
ASF.ArchiLogger.LogGenericError(string.Format(Strings.WarningFailedWithError, Marshal.GetLastWin32Error()));
|
||||
}
|
||||
}
|
||||
|
||||
private static void DisableQuickEditMode() {
|
||||
// http://stackoverflow.com/questions/30418886/how-and-why-does-quickedit-mode-in-command-prompt-freeze-applications
|
||||
IntPtr consoleHandle = NativeMethods.GetStdHandle(NativeMethods.StandardInputHandle);
|
||||
@@ -64,7 +76,7 @@ namespace ArchiSteamFarm {
|
||||
// This function calls unmanaged API in order to tell Windows OS that it should not enter sleep state while the program is running
|
||||
// If user wishes to enter sleep mode, then he should use ShutdownOnFarmingFinished or manage ASF process with third-party tool or script
|
||||
// More info: https://msdn.microsoft.com/library/windows/desktop/aa373208(v=vs.85).aspx
|
||||
NativeMethods.EExecutionState result = NativeMethods.SetThreadExecutionState(NativeMethods.EExecutionState.AwayModeRequired | NativeMethods.EExecutionState.Continuous | NativeMethods.EExecutionState.SystemRequired);
|
||||
NativeMethods.EExecutionState result = NativeMethods.SetThreadExecutionState(NativeMethods.AwakeExecutionState);
|
||||
|
||||
// SetThreadExecutionState() returns NULL on failure, which is mapped to 0 (EExecutionState.Error) in our case
|
||||
if (result == NativeMethods.EExecutionState.Error) {
|
||||
@@ -73,19 +85,24 @@ namespace ArchiSteamFarm {
|
||||
}
|
||||
|
||||
private static class NativeMethods {
|
||||
internal const EExecutionState AwakeExecutionState = EExecutionState.SystemRequired | EExecutionState.AwayModeRequired | EExecutionState.Continuous;
|
||||
internal const uint EnableQuickEditMode = 0x0040;
|
||||
internal const sbyte StandardInputHandle = -10;
|
||||
internal const EUnixPermission UnixExecutePermission = EUnixPermission.UserRead | EUnixPermission.UserWrite | EUnixPermission.UserExecute | EUnixPermission.GroupRead | EUnixPermission.GroupExecute | EUnixPermission.OtherRead | EUnixPermission.OtherExecute;
|
||||
|
||||
[DllImport("kernel32.dll", CharSet = CharSet.Auto)]
|
||||
[DllImport("libc", EntryPoint = "chmod", SetLastError = true)]
|
||||
internal static extern int Chmod(string path, int mode);
|
||||
|
||||
[DllImport("kernel32.dll")]
|
||||
internal static extern bool GetConsoleMode(IntPtr hConsoleHandle, out uint lpMode);
|
||||
|
||||
[DllImport("kernel32.dll", CharSet = CharSet.Auto)]
|
||||
[DllImport("kernel32.dll")]
|
||||
internal static extern IntPtr GetStdHandle(int nStdHandle);
|
||||
|
||||
[DllImport("kernel32.dll", CharSet = CharSet.Auto)]
|
||||
[DllImport("kernel32.dll")]
|
||||
internal static extern bool SetConsoleMode(IntPtr hConsoleHandle, uint dwMode);
|
||||
|
||||
[DllImport("kernel32.dll", CharSet = CharSet.Auto)]
|
||||
[DllImport("kernel32.dll")]
|
||||
internal static extern EExecutionState SetThreadExecutionState(EExecutionState executionState);
|
||||
|
||||
[Flags]
|
||||
@@ -95,6 +112,22 @@ namespace ArchiSteamFarm {
|
||||
AwayModeRequired = 0x00000040,
|
||||
Continuous = 0x80000000
|
||||
}
|
||||
|
||||
[Flags]
|
||||
internal enum EUnixPermission {
|
||||
OtherExecute = 0x1,
|
||||
OtherRead = 0x4,
|
||||
GroupExecute = 0x8,
|
||||
GroupRead = 0x20,
|
||||
UserExecute = 0x40,
|
||||
UserWrite = 0x80,
|
||||
UserRead = 0x100
|
||||
|
||||
/*
|
||||
OtherWrite = 0x2
|
||||
GroupWrite = 0x10
|
||||
*/
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -31,8 +31,6 @@ using System.IO;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using System.Resources;
|
||||
using System.Runtime;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using ArchiSteamFarm.Localization;
|
||||
using NLog;
|
||||
@@ -53,7 +51,11 @@ namespace ArchiSteamFarm {
|
||||
internal static WebBrowser WebBrowser { get; private set; }
|
||||
|
||||
private static readonly object ConsoleLock = new object();
|
||||
private static readonly ManualResetEventSlim ShutdownResetEvent = new ManualResetEventSlim(false);
|
||||
|
||||
// We need to keep this one assigned and not calculated on-demand
|
||||
private static readonly string ProcessFileName = Process.GetCurrentProcess().MainModule.FileName;
|
||||
|
||||
private static readonly TaskCompletionSource<bool> ShutdownResetEvent = new TaskCompletionSource<bool>();
|
||||
|
||||
private static bool ShutdownSequenceInitialized;
|
||||
|
||||
@@ -82,33 +84,39 @@ namespace ArchiSteamFarm {
|
||||
switch (userInputType) {
|
||||
case ASF.EUserInputType.DeviceID:
|
||||
Console.Write(Bot.FormatBotResponse(Strings.UserInputDeviceID, botName));
|
||||
result = Console.ReadLine();
|
||||
break;
|
||||
case ASF.EUserInputType.IPCHostname:
|
||||
Console.Write(Bot.FormatBotResponse(Strings.UserInputIPCHost, botName));
|
||||
result = Console.ReadLine();
|
||||
break;
|
||||
case ASF.EUserInputType.Login:
|
||||
Console.Write(Bot.FormatBotResponse(Strings.UserInputSteamLogin, botName));
|
||||
result = Console.ReadLine();
|
||||
break;
|
||||
case ASF.EUserInputType.Password:
|
||||
Console.Write(Bot.FormatBotResponse(Strings.UserInputSteamPassword, botName));
|
||||
result = Utilities.ReadLineMasked();
|
||||
break;
|
||||
case ASF.EUserInputType.SteamGuard:
|
||||
Console.Write(Bot.FormatBotResponse(Strings.UserInputSteamGuard, botName));
|
||||
result = Console.ReadLine();
|
||||
break;
|
||||
case ASF.EUserInputType.SteamParentalPIN:
|
||||
Console.Write(Bot.FormatBotResponse(Strings.UserInputSteamParentalPIN, botName));
|
||||
result = Utilities.ReadLineMasked();
|
||||
break;
|
||||
case ASF.EUserInputType.TwoFactorAuthentication:
|
||||
Console.Write(Bot.FormatBotResponse(Strings.UserInputSteam2FA, botName));
|
||||
result = Console.ReadLine();
|
||||
break;
|
||||
default:
|
||||
ASF.ArchiLogger.LogGenericWarning(string.Format(Strings.WarningUnknownValuePleaseReport, nameof(userInputType), userInputType));
|
||||
ASF.ArchiLogger.LogGenericError(string.Format(Strings.WarningUnknownValuePleaseReport, nameof(userInputType), userInputType));
|
||||
Console.Write(Bot.FormatBotResponse(string.Format(Strings.UserInputUnknown, userInputType), botName));
|
||||
result = Console.ReadLine();
|
||||
break;
|
||||
}
|
||||
|
||||
result = Console.ReadLine();
|
||||
|
||||
if (!Console.IsOutputRedirected) {
|
||||
Console.Clear(); // For security purposes
|
||||
}
|
||||
@@ -124,13 +132,15 @@ namespace ArchiSteamFarm {
|
||||
return;
|
||||
}
|
||||
|
||||
string executable = Process.GetCurrentProcess().MainModule.FileName;
|
||||
string executableName = Path.GetFileNameWithoutExtension(executable);
|
||||
// New process might want to start IPC before we in fact close
|
||||
// Ensure that IPC is stopped before Process.Start()
|
||||
IPC.Stop();
|
||||
|
||||
string executableName = Path.GetFileNameWithoutExtension(ProcessFileName);
|
||||
IEnumerable<string> arguments = Environment.GetCommandLineArgs().Skip(executableName.Equals(SharedInfo.AssemblyName) ? 1 : 0);
|
||||
|
||||
try {
|
||||
Process.Start(executable, string.Join(" ", arguments));
|
||||
Process.Start(ProcessFileName, string.Join(" ", arguments));
|
||||
} catch (Exception e) {
|
||||
ASF.ArchiLogger.LogGenericException(e);
|
||||
}
|
||||
@@ -138,7 +148,7 @@ namespace ArchiSteamFarm {
|
||||
// Give new process some time to take over the window (if needed)
|
||||
await Task.Delay(2000).ConfigureAwait(false);
|
||||
|
||||
ShutdownResetEvent.Set();
|
||||
ShutdownResetEvent.TrySetResult(true);
|
||||
Environment.Exit(0);
|
||||
}
|
||||
|
||||
@@ -155,7 +165,7 @@ namespace ArchiSteamFarm {
|
||||
}
|
||||
|
||||
private static async Task InitASF(string[] args) {
|
||||
ASF.ArchiLogger.LogGenericInfo("ASF V" + SharedInfo.Version);
|
||||
ASF.ArchiLogger.LogGenericInfo("ASF V" + SharedInfo.Version + " (" + SharedInfo.ModuleVersion + ")");
|
||||
|
||||
await InitGlobalConfigAndLanguage().ConfigureAwait(false);
|
||||
await InitGlobalDatabaseAndServices().ConfigureAwait(false);
|
||||
@@ -231,8 +241,8 @@ namespace ArchiSteamFarm {
|
||||
return;
|
||||
}
|
||||
|
||||
if (GCSettings.IsServerGC) {
|
||||
Hacks.Init();
|
||||
if (GlobalConfig.BackgroundGCPeriod > 0) {
|
||||
Hacks.EnableBackgroundGC(GlobalConfig.BackgroundGCPeriod);
|
||||
}
|
||||
|
||||
if (!string.IsNullOrEmpty(GlobalConfig.CurrentCulture)) {
|
||||
@@ -292,7 +302,7 @@ namespace ArchiSteamFarm {
|
||||
if (!File.Exists(globalDatabaseFile)) {
|
||||
ASF.ArchiLogger.LogGenericInfo(Strings.Welcome);
|
||||
ASF.ArchiLogger.LogGenericWarning(Strings.WarningPrivacyPolicy);
|
||||
await Task.Delay(15 * 1000).ConfigureAwait(false);
|
||||
await Task.Delay(10 * 1000).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
GlobalDatabase = GlobalDatabase.Load(globalDatabaseFile);
|
||||
@@ -303,7 +313,6 @@ namespace ArchiSteamFarm {
|
||||
return;
|
||||
}
|
||||
|
||||
ArchiWebHandler.Init();
|
||||
IPC.Initialize(GlobalConfig.IPCHost, GlobalConfig.IPCPort);
|
||||
OS.Init(GlobalConfig.Headless);
|
||||
WebBrowser.Init();
|
||||
@@ -318,36 +327,35 @@ namespace ArchiSteamFarm {
|
||||
|
||||
ShutdownSequenceInitialized = true;
|
||||
|
||||
if (Bot.Bots.Count == 0) {
|
||||
return true;
|
||||
}
|
||||
if (Bot.Bots.Count > 0) {
|
||||
IEnumerable<Task> tasks = Bot.Bots.Values.Select(bot => Task.Run(() => bot.Stop(false)));
|
||||
|
||||
IEnumerable<Task> tasks = Bot.Bots.Values.Select(bot => Task.Run(() => bot.Stop(false)));
|
||||
switch (GlobalConfig.OptimizationMode) {
|
||||
case GlobalConfig.EOptimizationMode.MinMemoryUsage:
|
||||
foreach (Task task in tasks) {
|
||||
await Task.WhenAny(task, Task.Delay(WebBrowser.MaxTries * 1000)).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
switch (GlobalConfig.OptimizationMode) {
|
||||
case GlobalConfig.EOptimizationMode.MinMemoryUsage:
|
||||
foreach (Task task in tasks) {
|
||||
await Task.WhenAny(task, Task.Delay(WebBrowser.MaxTries * 1000)).ConfigureAwait(false);
|
||||
}
|
||||
break;
|
||||
default:
|
||||
await Task.WhenAny(Task.WhenAll(tasks), Task.Delay(Bot.Bots.Count * WebBrowser.MaxTries * 1000)).ConfigureAwait(false);
|
||||
break;
|
||||
}
|
||||
|
||||
break;
|
||||
default:
|
||||
await Task.WhenAny(Task.WhenAll(tasks), Task.Delay(Bot.Bots.Count * WebBrowser.MaxTries * 1000)).ConfigureAwait(false);
|
||||
break;
|
||||
// Extra second for Steam requests to go through
|
||||
await Task.Delay(1000).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
LogManager.Flush();
|
||||
return true;
|
||||
}
|
||||
|
||||
private static void Main(string[] args) {
|
||||
Init(args).Wait();
|
||||
private static async Task Main(string[] args) {
|
||||
// Initialize
|
||||
await Init(args).ConfigureAwait(false);
|
||||
|
||||
// Wait for signal to shutdown
|
||||
ShutdownResetEvent.Wait();
|
||||
|
||||
// We got a signal to shutdown
|
||||
Exit().Wait();
|
||||
// Wait for shutdown event
|
||||
await ShutdownResetEvent.Task.ConfigureAwait(false);
|
||||
}
|
||||
|
||||
private static void OnProcessExit(object sender, EventArgs e) => IPC.Stop();
|
||||
@@ -427,7 +435,7 @@ namespace ArchiSteamFarm {
|
||||
return;
|
||||
}
|
||||
|
||||
ShutdownResetEvent.Set();
|
||||
ShutdownResetEvent.TrySetResult(true);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -38,16 +38,8 @@ namespace ArchiSteamFarm {
|
||||
internal readonly ProtocolTypes ProtocolTypes;
|
||||
|
||||
internal ServerRecordEndPoint(string host, ushort port, ProtocolTypes protocolTypes) {
|
||||
if (string.IsNullOrEmpty(host)) {
|
||||
throw new ArgumentNullException(nameof(host));
|
||||
}
|
||||
|
||||
if (port == 0) {
|
||||
throw new ArgumentNullException(nameof(port));
|
||||
}
|
||||
|
||||
if (protocolTypes == 0) {
|
||||
throw new ArgumentNullException(nameof(protocolTypes));
|
||||
if (string.IsNullOrEmpty(host) || (port == 0) || (protocolTypes == 0)) {
|
||||
throw new ArgumentNullException(nameof(host) + " || " + nameof(port) + " || " + nameof(protocolTypes));
|
||||
}
|
||||
|
||||
Host = host;
|
||||
|
||||
@@ -42,6 +42,7 @@ namespace ArchiSteamFarm {
|
||||
internal const string UpdateDirectory = "_old";
|
||||
internal const string VersionFile = AssemblyName + ".version";
|
||||
|
||||
internal static readonly Version Version = Assembly.GetEntryAssembly().GetName().Version;
|
||||
internal static Guid ModuleVersion => Assembly.GetEntryAssembly().ManifestModule.ModuleVersionId;
|
||||
internal static Version Version => Assembly.GetEntryAssembly().GetName().Version;
|
||||
}
|
||||
}
|
||||
@@ -40,7 +40,7 @@ namespace ArchiSteamFarm {
|
||||
private const string URL = "https://" + SharedInfo.StatisticsServer;
|
||||
|
||||
private readonly Bot Bot;
|
||||
private readonly SemaphoreSlim Semaphore = new SemaphoreSlim(1);
|
||||
private readonly SemaphoreSlim RequestsSemaphore = new SemaphoreSlim(1, 1);
|
||||
|
||||
private DateTime LastAnnouncementCheck = DateTime.MinValue;
|
||||
private DateTime LastHeartBeat = DateTime.MinValue;
|
||||
@@ -49,7 +49,7 @@ namespace ArchiSteamFarm {
|
||||
|
||||
internal Statistics(Bot bot) => Bot = bot ?? throw new ArgumentNullException(nameof(bot));
|
||||
|
||||
public void Dispose() => Semaphore.Dispose();
|
||||
public void Dispose() => RequestsSemaphore.Dispose();
|
||||
|
||||
internal async Task OnHeartBeat() {
|
||||
// Request persona update if needed
|
||||
@@ -62,7 +62,7 @@ namespace ArchiSteamFarm {
|
||||
return;
|
||||
}
|
||||
|
||||
await Semaphore.WaitAsync().ConfigureAwait(false);
|
||||
await RequestsSemaphore.WaitAsync().ConfigureAwait(false);
|
||||
|
||||
try {
|
||||
if (!ShouldSendHeartBeats || (DateTime.UtcNow < LastHeartBeat.AddMinutes(MinHeartBeatTTL))) {
|
||||
@@ -80,7 +80,7 @@ namespace ArchiSteamFarm {
|
||||
LastHeartBeat = DateTime.UtcNow;
|
||||
}
|
||||
} finally {
|
||||
Semaphore.Release();
|
||||
RequestsSemaphore.Release();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -114,7 +114,7 @@ namespace ArchiSteamFarm {
|
||||
}
|
||||
}
|
||||
|
||||
await Semaphore.WaitAsync().ConfigureAwait(false);
|
||||
await RequestsSemaphore.WaitAsync().ConfigureAwait(false);
|
||||
|
||||
try {
|
||||
if (DateTime.UtcNow < LastAnnouncementCheck.AddHours(MinAnnouncementCheckTTL)) {
|
||||
@@ -154,7 +154,7 @@ namespace ArchiSteamFarm {
|
||||
ShouldSendHeartBeats = true;
|
||||
}
|
||||
} finally {
|
||||
Semaphore.Release();
|
||||
RequestsSemaphore.Release();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -56,6 +56,8 @@ namespace ArchiSteamFarm {
|
||||
return;
|
||||
}
|
||||
|
||||
base.Write(logEvent);
|
||||
|
||||
if (SteamID == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -37,7 +37,7 @@ namespace ArchiSteamFarm {
|
||||
|
||||
private readonly Bot Bot;
|
||||
private readonly ConcurrentHashSet<ulong> IgnoredTrades = new ConcurrentHashSet<ulong>();
|
||||
private readonly SemaphoreSlim TradesSemaphore = new SemaphoreSlim(1);
|
||||
private readonly SemaphoreSlim TradesSemaphore = new SemaphoreSlim(1, 1);
|
||||
|
||||
private bool ParsingScheduled;
|
||||
|
||||
@@ -213,9 +213,7 @@ namespace ArchiSteamFarm {
|
||||
case ParseTradeResult.EResult.RejectedTemporarily:
|
||||
if (result.Result == ParseTradeResult.EResult.RejectedPermanently) {
|
||||
if (Bot.BotConfig.IsBotAccount) {
|
||||
Bot.ArchiLogger.LogGenericInfo(string.Format(Strings.RejectingTrade, tradeOffer.TradeOfferID));
|
||||
await Bot.ArchiWebHandler.DeclineTradeOffer(tradeOffer.TradeOfferID).ConfigureAwait(false);
|
||||
break;
|
||||
goto case ParseTradeResult.EResult.RejectedAndBlacklisted;
|
||||
}
|
||||
|
||||
IgnoredTrades.Add(tradeOffer.TradeOfferID);
|
||||
@@ -223,8 +221,12 @@ namespace ArchiSteamFarm {
|
||||
|
||||
Bot.ArchiLogger.LogGenericInfo(string.Format(Strings.IgnoringTrade, tradeOffer.TradeOfferID));
|
||||
break;
|
||||
case ParseTradeResult.EResult.RejectedAndBlacklisted:
|
||||
Bot.ArchiLogger.LogGenericInfo(string.Format(Strings.RejectingTrade, tradeOffer.TradeOfferID));
|
||||
await Bot.ArchiWebHandler.DeclineTradeOffer(tradeOffer.TradeOfferID).ConfigureAwait(false);
|
||||
break;
|
||||
default:
|
||||
Bot.ArchiLogger.LogGenericError(string.Format(Strings.ErrorIsInvalid, result.Result));
|
||||
Bot.ArchiLogger.LogGenericError(string.Format(Strings.WarningUnknownValuePleaseReport, nameof(result.Result), result.Result));
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -245,7 +247,7 @@ namespace ArchiSteamFarm {
|
||||
|
||||
// Always deny trades from blacklisted steamIDs
|
||||
if (Bot.IsBlacklistedFromTrades(tradeOffer.OtherSteamID64)) {
|
||||
return new ParseTradeResult(tradeOffer.TradeOfferID, ParseTradeResult.EResult.RejectedPermanently);
|
||||
return new ParseTradeResult(tradeOffer.TradeOfferID, ParseTradeResult.EResult.RejectedAndBlacklisted);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -301,7 +303,7 @@ namespace ArchiSteamFarm {
|
||||
// If user has a trade hold, we add extra logic
|
||||
if (holdDuration.Value > 0) {
|
||||
// If trade hold duration exceeds our max, or user asks for cards with short lifespan, reject the trade
|
||||
if ((holdDuration.Value > Program.GlobalConfig.MaxTradeHoldDuration) || tradeOffer.ItemsToGive.Any(item => GlobalConfig.GlobalBlacklist.Contains(item.RealAppID))) {
|
||||
if ((holdDuration.Value > Program.GlobalConfig.MaxTradeHoldDuration) || tradeOffer.ItemsToGive.Any(item => GlobalConfig.SalesBlacklist.Contains(item.RealAppID))) {
|
||||
return new ParseTradeResult(tradeOffer.TradeOfferID, ParseTradeResult.EResult.RejectedPermanently);
|
||||
}
|
||||
}
|
||||
@@ -353,7 +355,8 @@ namespace ArchiSteamFarm {
|
||||
AcceptedWithItemLose,
|
||||
AcceptedWithoutItemLose,
|
||||
RejectedTemporarily,
|
||||
RejectedPermanently
|
||||
RejectedPermanently,
|
||||
RejectedAndBlacklisted
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -29,6 +29,7 @@ using System.Globalization;
|
||||
using System.Linq;
|
||||
using System.Net;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using Humanizer;
|
||||
|
||||
@@ -85,6 +86,31 @@ namespace ArchiSteamFarm {
|
||||
}
|
||||
}
|
||||
|
||||
internal static string ReadLineMasked(char mask = '*') {
|
||||
StringBuilder result = new StringBuilder();
|
||||
|
||||
ConsoleKeyInfo keyInfo;
|
||||
while ((keyInfo = Console.ReadKey(true)).Key != ConsoleKey.Enter) {
|
||||
if (!char.IsControl(keyInfo.KeyChar)) {
|
||||
result.Append(keyInfo.KeyChar);
|
||||
Console.Write(mask);
|
||||
} else if ((keyInfo.Key == ConsoleKey.Backspace) && (result.Length > 0)) {
|
||||
result.Remove(result.Length - 1, 1);
|
||||
|
||||
if (Console.CursorLeft == 0) {
|
||||
Console.SetCursorPosition(Console.BufferWidth - 1, Console.CursorTop - 1);
|
||||
Console.Write(' ');
|
||||
Console.SetCursorPosition(Console.BufferWidth - 1, Console.CursorTop - 1);
|
||||
} else {
|
||||
Console.Write("\b \b");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Console.WriteLine();
|
||||
return result.ToString();
|
||||
}
|
||||
|
||||
internal static void StartBackgroundAction(Action action, bool longRunning = true) {
|
||||
if (action == null) {
|
||||
ASF.ArchiLogger.LogNullError(nameof(action));
|
||||
|
||||
@@ -34,7 +34,7 @@ using Newtonsoft.Json;
|
||||
using Newtonsoft.Json.Linq;
|
||||
|
||||
namespace ArchiSteamFarm {
|
||||
internal sealed class WebBrowser {
|
||||
internal sealed class WebBrowser : IDisposable {
|
||||
internal const byte MaxTries = 5; // Defines maximum number of recommended tries for a single request
|
||||
|
||||
private const byte ExtendedTimeoutMultiplier = 10; // Defines multiplier of timeout for WebBrowsers dealing with huge data (ASF update)
|
||||
@@ -43,6 +43,8 @@ namespace ArchiSteamFarm {
|
||||
|
||||
internal readonly CookieContainer CookieContainer = new CookieContainer();
|
||||
|
||||
internal TimeSpan Timeout => HttpClient.Timeout;
|
||||
|
||||
private readonly ArchiLogger ArchiLogger;
|
||||
private readonly HttpClient HttpClient;
|
||||
|
||||
@@ -63,6 +65,8 @@ namespace ArchiSteamFarm {
|
||||
HttpClient.DefaultRequestHeaders.UserAgent.ParseAdd(SharedInfo.AssemblyName + "/" + SharedInfo.Version);
|
||||
}
|
||||
|
||||
public void Dispose() => HttpClient.Dispose();
|
||||
|
||||
internal static void Init() {
|
||||
// Set max connection limit from default of 2 to desired value
|
||||
ServicePointManager.DefaultConnectionLimit = MaxConnections;
|
||||
@@ -535,11 +539,19 @@ namespace ArchiSteamFarm {
|
||||
ushort status = (ushort) responseMessage.StatusCode;
|
||||
if ((status >= 300) && (status <= 399) && (maxRedirections > 0)) {
|
||||
redirectUri = responseMessage.Headers.Location;
|
||||
if (!redirectUri.IsAbsoluteUri) {
|
||||
|
||||
if (redirectUri.IsAbsoluteUri) {
|
||||
switch (redirectUri.Scheme) {
|
||||
case "http":
|
||||
case "https":
|
||||
break;
|
||||
default:
|
||||
// Invalid ones such as "steammobile"
|
||||
return null;
|
||||
}
|
||||
} else {
|
||||
redirectUri = new Uri(requestUri.GetLeftPart(UriPartial.Authority) + redirectUri);
|
||||
}
|
||||
|
||||
ASF.ArchiLogger.LogGenericDebug("Asked for <" + requestUri + ">, got unsafely redirected to <" + responseMessage.Headers.Location + ">, resolved URI to: <" + redirectUri + ">");
|
||||
} else {
|
||||
if (!Debugging.IsDebugBuild) {
|
||||
return null;
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
{
|
||||
"AutoRestart": true,
|
||||
"AutoUpdates": true,
|
||||
"BackgroundGCPeriod": 0,
|
||||
"Blacklist": [],
|
||||
"ConnectionTimeout": 60,
|
||||
"CurrentCulture": null,
|
||||
@@ -8,7 +9,7 @@
|
||||
"FarmingDelay": 15,
|
||||
"GiftsLimiterDelay": 1,
|
||||
"Headless": false,
|
||||
"IdleFarmingPeriod": 3,
|
||||
"IdleFarmingPeriod": 8,
|
||||
"InventoryLimiterDelay": 3,
|
||||
"IPCHost": "127.0.0.1",
|
||||
"IPCPort": 1242,
|
||||
@@ -18,6 +19,6 @@
|
||||
"OptimizationMode": 0,
|
||||
"Statistics": true,
|
||||
"SteamOwnerID": 0,
|
||||
"SteamProtocols": 7,
|
||||
"SteamProtocols": 4,
|
||||
"UpdateChannel": 1
|
||||
}
|
||||
File diff suppressed because one or more lines are too long
69
appveyor.yml
69
appveyor.yml
@@ -5,39 +5,76 @@ branches:
|
||||
only:
|
||||
- master
|
||||
skip_branch_with_pr: true
|
||||
image: Visual Studio 2017 Preview
|
||||
image: Visual Studio 2017
|
||||
configuration: Release
|
||||
platform: Any CPU
|
||||
clone_depth: 10
|
||||
environment:
|
||||
DOTNET_CHANNEL: 2.0
|
||||
DOTNET_CLI_TELEMETRY_OPTOUT: true
|
||||
DOTNET_SKIP_FIRST_TIME_EXPERIENCE: true
|
||||
RUNTIMES: generic win-x64 linux-x64 linux-arm osx-x64
|
||||
matrix:
|
||||
fast_finish: true
|
||||
install:
|
||||
- ps: >-
|
||||
$ErrorActionPreference = 'Stop'
|
||||
|
||||
dotnet --info
|
||||
|
||||
$env:DOTNET_INSTALL_DIR = "$pwd\.dotnetcli"
|
||||
|
||||
|
||||
New-Item '.\scripts\obtain' -ItemType 'directory' -Force
|
||||
|
||||
Invoke-WebRequest -Uri 'https://raw.githubusercontent.com/dotnet/cli/master/scripts/obtain/dotnet-install.ps1' -OutFile '.\scripts\obtain\dotnet-install.ps1'
|
||||
|
||||
.\scripts\obtain\dotnet-install.ps1 -Channel "$env:DOTNET_CHANNEL" -InstallDir "$env:DOTNET_INSTALL_DIR"
|
||||
before_build:
|
||||
- ps: dotnet restore
|
||||
build:
|
||||
project: ArchiSteamFarm.sln
|
||||
parallel: true
|
||||
verbosity: minimal
|
||||
- ps: >-
|
||||
$ErrorActionPreference = 'Stop'
|
||||
|
||||
dotnet --info
|
||||
|
||||
dotnet restore
|
||||
build_script:
|
||||
- ps: >-
|
||||
$ErrorActionPreference = 'Stop'
|
||||
|
||||
dotnet build -c "$env:CONFIGURATION" -o 'out\source' --no-restore /nologo
|
||||
test_script:
|
||||
- ps: >-
|
||||
$ErrorActionPreference = 'Stop'
|
||||
|
||||
dotnet test ArchiSteamFarm.Tests -c "$env:CONFIGURATION" -o 'out\source' --no-build --no-restore
|
||||
after_test:
|
||||
- ps: >-
|
||||
$ErrorActionPreference = 'Stop'
|
||||
|
||||
$PublishBlock = {
|
||||
param($RUNTIME)
|
||||
|
||||
$RUNTIMES = 'generic', 'win-x64', 'linux-x64', 'linux-arm', 'osx-x64'
|
||||
$ErrorActionPreference = 'Stop'
|
||||
|
||||
Set-Location -Path "$env:APPVEYOR_BUILD_FOLDER"
|
||||
|
||||
foreach ($RUNTIME in $RUNTIMES) {
|
||||
if ($RUNTIME -eq 'generic') {
|
||||
dotnet publish -c "$env:CONFIGURATION" -o "out\$RUNTIME"
|
||||
} else {
|
||||
dotnet publish -c "$env:CONFIGURATION" -r "$RUNTIME" -o "out\$RUNTIME"
|
||||
}
|
||||
if ($RUNTIME -eq 'generic') {
|
||||
dotnet publish ArchiSteamFarm -c "$env:CONFIGURATION" -o "out\$RUNTIME" --no-restore /nologo
|
||||
} else {
|
||||
dotnet publish ArchiSteamFarm -c "$env:CONFIGURATION" -o "out\$RUNTIME" -r "$RUNTIME" --no-restore /nologo
|
||||
}
|
||||
|
||||
Set-Content -Path "ArchiSteamFarm\out\$RUNTIME\ArchiSteamFarm.version" -Value "$RUNTIME"
|
||||
Set-Content -Path "ArchiSteamFarm\out\$RUNTIME\ArchiSteamFarm.version" -Value "$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"
|
||||
7z a -bd -tzip -mm=Deflate64 -mx=9 -mfb=257 -mpass=3 "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"
|
||||
}
|
||||
|
||||
foreach ($RUNTIME in $env:RUNTIMES.Split([char[]] $null, [System.StringSplitOptions]::RemoveEmptyEntries)) {
|
||||
Start-Job -Name "$RUNTIME" -ScriptBlock $PublishBlock -ArgumentList "$RUNTIME"
|
||||
}
|
||||
|
||||
Get-Job | Receive-Job -AutoRemoveJob -Wait
|
||||
deploy:
|
||||
- provider: GitHub
|
||||
tag: $(appveyor_repo_tag_name)
|
||||
|
||||
25
cc.sh
25
cc.sh
@@ -2,10 +2,11 @@
|
||||
set -eu
|
||||
|
||||
PROJECT="ArchiSteamFarm"
|
||||
OUT="out"
|
||||
MSBUILD_ARGS=("/nologo")
|
||||
OUT="out/source"
|
||||
|
||||
SOLUTION="${PROJECT}.sln"
|
||||
CONFIGURATION="Release"
|
||||
|
||||
BUILD="Release"
|
||||
CLEAN=0
|
||||
|
||||
PRINT_USAGE() {
|
||||
@@ -15,8 +16,8 @@ PRINT_USAGE() {
|
||||
|
||||
for ARG in "$@"; do
|
||||
case "$ARG" in
|
||||
release|Release) BUILD="Release" ;;
|
||||
debug|Debug) BUILD="Debug" ;;
|
||||
release|Release) CONFIGURATION="Release" ;;
|
||||
debug|Debug) CONFIGURATION="Debug" ;;
|
||||
--clean) CLEAN=1 ;;
|
||||
*) PRINT_USAGE
|
||||
esac
|
||||
@@ -27,24 +28,28 @@ if ! hash dotnet &>/dev/null; then
|
||||
exit 1
|
||||
fi
|
||||
|
||||
dotnet --info
|
||||
|
||||
cd "$(dirname "$(readlink -f "$0")")"
|
||||
|
||||
if [[ -d ".git" ]] && hash git &>/dev/null; then
|
||||
git pull || true
|
||||
fi
|
||||
|
||||
if [[ ! -f "${PROJECT}.sln" ]]; then
|
||||
echo "ERROR: ${PROJECT}.sln could not be found!"
|
||||
if [[ ! -f "$SOLUTION" ]]; then
|
||||
echo "ERROR: $SOLUTION could not be found!"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [[ "$CLEAN" -eq 1 ]]; then
|
||||
dotnet clean -c "$BUILD" -o "$OUT"
|
||||
rm -rf "$OUT"
|
||||
dotnet clean -c "$CONFIGURATION" -o "$OUT"
|
||||
rm -rf "ArchiSteamFarm/${OUT}" "ArchiSteamFarm.Tests/${OUT}"
|
||||
fi
|
||||
|
||||
dotnet restore
|
||||
dotnet build -c "$BUILD" -o "$OUT" "${MSBUILD_ARGS[@]}"
|
||||
|
||||
dotnet build -c "$CONFIGURATION" -o "$OUT" --no-restore /nologo
|
||||
dotnet test ArchiSteamFarm.Tests -c "$CONFIGURATION" -o "$OUT" --no-build --no-restore
|
||||
|
||||
echo
|
||||
echo "Compilation finished successfully! :)"
|
||||
|
||||
7
run.sh
7
run.sh
@@ -2,7 +2,8 @@
|
||||
set -eu
|
||||
|
||||
PROJECT="ArchiSteamFarm"
|
||||
OUT="out"
|
||||
OUT="out/source"
|
||||
|
||||
BINARY="${PROJECT}/${OUT}/${PROJECT}.dll"
|
||||
|
||||
ASF_ARGS=("")
|
||||
@@ -28,6 +29,8 @@ if ! hash dotnet &>/dev/null; then
|
||||
exit 1
|
||||
fi
|
||||
|
||||
dotnet --info
|
||||
|
||||
cd "$(dirname "$(readlink -f "$0")")"
|
||||
|
||||
if [[ ! -f "$BINARY" ]]; then
|
||||
@@ -37,7 +40,7 @@ fi
|
||||
|
||||
if [[ "$UNTIL_CLEAN_EXIT" -eq 0 ]]; then
|
||||
dotnet exec "$BINARY" "${ASF_ARGS[@]}"
|
||||
exit $?
|
||||
exit $? # In this case $? can only be 0 because otherwise set -e terminates the script
|
||||
fi
|
||||
|
||||
while [[ -f "$BINARY" ]]; do
|
||||
|
||||
Binary file not shown.
Binary file not shown.
@@ -4,14 +4,14 @@ NetHook2
|
||||
This tool is used for reverse-engineering of Steam client. It's capable of hooking and recording network traffic sent/received by the client. If you're not trying to implement missing SK2 functionality in ASF, then please do not proceed.
|
||||
|
||||
1. Launch Steam client
|
||||
2. Execute ```hook.bat```
|
||||
2. Execute `hook.cmd`
|
||||
3. Reproduce the functionality you're trying to add
|
||||
4. Execute ```unhook.bat```
|
||||
5. Use ```NetHookAnalyzer2.exe``` for analyzing recorded log (which can be found in your Steam directory)
|
||||
4. Execute `unhook.cmd`
|
||||
5. Use `NetHookAnalyzer2.exe` for analyzing recorded log (which can be found in your Steam directory)
|
||||
|
||||
- Source of the ```NetHook2.dll``` can be found **[here](https://github.com/SteamRE/SteamKit/tree/master/Resources/NetHook2)**
|
||||
- Source of the ```NetHookAnalyzer2.exe``` can be found **[here](https://github.com/SteamRE/SteamKit/tree/master/Resources/NetHookAnalyzer2)**
|
||||
- Source of the `NetHook2.dll` can be found **[here](https://github.com/SteamRE/SteamKit/tree/master/Resources/NetHook2)**
|
||||
- Source of the `NetHookAnalyzer2.exe` can be found **[here](https://github.com/SteamRE/SteamKit/tree/master/Resources/NetHookAnalyzer2)**
|
||||
|
||||
===================
|
||||
|
||||
There is absolutely no guarantee that this will even work for you, not to mention the consequences from hooking the external DLL into steam client. You're on your own.
|
||||
There is absolutely no guarantee that this will even work for you, not to mention the consequences from hooking the external DLL into steam client. You're on your own. This build is for me so I don't need to compile it from scratch every time - I strongly recommend against using it. You have SK2 sources for a reason.
|
||||
|
||||
Binary file not shown.
Binary file not shown.
@@ -36,5 +36,5 @@ This tool is being used by ASF developers for synchronization of strings/transla
|
||||
|
||||
- `archi_download.bat` for downloading translations from Crowdin (typically last commit before release).
|
||||
|
||||
- `archi_sync.bat` for upload + download (tree sync).
|
||||
- `archi_sync.bat` for upload + download (tree sync, e.g. when modifying/removing original strings).
|
||||
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
@echo off
|
||||
pushd %~dp0
|
||||
cd ..\\..
|
||||
call crowdin -b master --identity tools\\crowdin-cli\\crowdin_identity.yaml download
|
||||
pause
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
@echo off
|
||||
pushd %~dp0
|
||||
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
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
@echo off
|
||||
pushd %~dp0
|
||||
cd ..\\..
|
||||
call crowdin -b master --identity tools\\crowdin-cli\\crowdin_identity.yaml upload sources
|
||||
pause
|
||||
|
||||
Binary file not shown.
@@ -2,6 +2,7 @@
|
||||
SETLOCAL
|
||||
SET TEMPFILE=%TEMP%\tmpfile
|
||||
|
||||
pushd %~dp0
|
||||
setx /M CROWDIN_HOME "%cd%"
|
||||
setx /M PATH "%PATH%;%cd%"
|
||||
|
||||
|
||||
Reference in New Issue
Block a user