mirror of
https://github.com/JustArchiNET/ArchiSteamFarm.git
synced 2025-12-25 02:36:48 +00:00
Compare commits
42 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
74881094ab | ||
|
|
ee98282475 | ||
|
|
e49ec14c41 | ||
|
|
56f4604819 | ||
|
|
56eb5ba8a8 | ||
|
|
8206dff4c1 | ||
|
|
f7426a4439 | ||
|
|
c478ca691d | ||
|
|
a3ff3cafa4 | ||
|
|
313d353ae5 | ||
|
|
dabf391576 | ||
|
|
8ad552b0da | ||
|
|
c16c25da77 | ||
|
|
aa1fc829b7 | ||
|
|
e5eea110f1 | ||
|
|
2e5828779f | ||
|
|
cc2e73ae5a | ||
|
|
b89840f103 | ||
|
|
12b32472ba | ||
|
|
86ac2ade1e | ||
|
|
a180c46b5d | ||
|
|
34555f8bb5 | ||
|
|
4db8826e4d | ||
|
|
69358541bf | ||
|
|
896c92b678 | ||
|
|
6ef52c5184 | ||
|
|
5b098b79a5 | ||
|
|
2cab6c82e4 | ||
|
|
91d3012581 | ||
|
|
354b60db53 | ||
|
|
fd89c42e48 | ||
|
|
79a862dd0e | ||
|
|
54a20246db | ||
|
|
822bfc239f | ||
|
|
f80bf929f7 | ||
|
|
e201d88e7a | ||
|
|
ce5443ca9a | ||
|
|
4b570f7928 | ||
|
|
b5152eb852 | ||
|
|
c5315be572 | ||
|
|
b4a2c76736 | ||
|
|
53726a6a26 |
2
.github/ISSUE_TEMPLATE/Bug_report.md
vendored
2
.github/ISSUE_TEMPLATE/Bug_report.md
vendored
@@ -40,7 +40,7 @@ Feel free to remove our notice and fill the template below with your details.
|
||||
|
||||
### Full log.txt recorded during reproducing the problem
|
||||
|
||||
```
|
||||
```text
|
||||
Paste here, in-between triple backtick tags
|
||||
|
||||
Ensure that your log is complete and was NOT recorded in Debug mode, as debug log may contain sensitive information that should not be shared publicly, as per our wiki statement. Standard ASF log does not include sensitive information.
|
||||
|
||||
2
.github/ISSUE_TEMPLATE/config.yml
vendored
2
.github/ISSUE_TEMPLATE/config.yml
vendored
@@ -6,7 +6,7 @@ contact_links:
|
||||
- name: Localization improvement
|
||||
url: https://github.com/JustArchiNET/ArchiSteamFarm/wiki/Localization
|
||||
about: Please use our crowdin platform.
|
||||
- name: Questions and technical issues
|
||||
- name: Support question or technical issue
|
||||
url: https://github.com/JustArchiNET/ArchiSteamFarm/blob/master/SUPPORT.md
|
||||
about: Please review our support guidelines.
|
||||
- name: Negative feedback, complaints and demands
|
||||
|
||||
12
.github/workflows/ci.yml
vendored
12
.github/workflows/ci.yml
vendored
@@ -62,7 +62,7 @@ jobs:
|
||||
run: dotnet build ArchiSteamFarm.CustomPlugins.PeriodicGC -c "${{ env.CONFIGURATION }}" -p:UseAppHost=false --nologo
|
||||
|
||||
- name: Build ArchiSteamFarm.OfficialPlugins.SteamTokenDumper
|
||||
run: dotnet build ArchiSteamFarm.OfficialPlugins.SteamTokenDumper -c "${{ env.CONFIGURATION }}" -p:UseAppHost=false --nologo
|
||||
run: dotnet build "${{ env.STEAM_TOKEN_DUMPER_NAME }}" -c "${{ env.CONFIGURATION }}" -p:UseAppHost=false --nologo
|
||||
|
||||
- name: Run ArchiSteamFarm.Tests
|
||||
run: dotnet test ArchiSteamFarm.Tests -c "${{ env.CONFIGURATION }}" -p:UseAppHost=false --nologo
|
||||
@@ -71,9 +71,9 @@ jobs:
|
||||
if: startsWith(matrix.os, 'macos-') || startsWith(matrix.os, 'ubuntu-')
|
||||
shell: sh
|
||||
run: |
|
||||
if [ -n "${STEAM_TOKEN_DUMPER_TOKEN-}" ] && [ -f "ArchiSteamFarm.OfficialPlugins.SteamTokenDumper/SharedInfo.cs" ]; then
|
||||
sed "s/STEAM_TOKEN_DUMPER_TOKEN/${STEAM_TOKEN_DUMPER_TOKEN}/g" "ArchiSteamFarm.OfficialPlugins.SteamTokenDumper/SharedInfo.cs" > "ArchiSteamFarm.OfficialPlugins.SteamTokenDumper/SharedInfo.cs.new";
|
||||
mv "ArchiSteamFarm.OfficialPlugins.SteamTokenDumper/SharedInfo.cs.new" "ArchiSteamFarm.OfficialPlugins.SteamTokenDumper/SharedInfo.cs"
|
||||
if [ -n "${STEAM_TOKEN_DUMPER_TOKEN-}" ] && [ -f "${STEAM_TOKEN_DUMPER_NAME}/SharedInfo.cs" ]; then
|
||||
sed "s/STEAM_TOKEN_DUMPER_TOKEN/${STEAM_TOKEN_DUMPER_TOKEN}/g" "${STEAM_TOKEN_DUMPER_NAME}/SharedInfo.cs" > "${STEAM_TOKEN_DUMPER_NAME}/SharedInfo.cs.new";
|
||||
mv "${STEAM_TOKEN_DUMPER_NAME}/SharedInfo.cs.new" "${STEAM_TOKEN_DUMPER_NAME}/SharedInfo.cs"
|
||||
fi
|
||||
- name: Prepare ArchiSteamFarm.OfficialPlugins.SteamTokenDumper on Windows
|
||||
if: startsWith(matrix.os, 'windows-')
|
||||
@@ -83,8 +83,8 @@ jobs:
|
||||
$ErrorActionPreference = 'Stop'
|
||||
$ProgressPreference = 'SilentlyContinue'
|
||||
|
||||
if ((Test-Path env:STEAM_TOKEN_DUMPER_TOKEN) -and (Test-Path 'ArchiSteamFarm.OfficialPlugins.SteamTokenDumper\SharedInfo.cs' -PathType Leaf)) {
|
||||
(Get-Content 'ArchiSteamFarm.OfficialPlugins.SteamTokenDumper\SharedInfo.cs').Replace('STEAM_TOKEN_DUMPER_TOKEN', "$env:STEAM_TOKEN_DUMPER_TOKEN") | Set-Content 'ArchiSteamFarm.OfficialPlugins.SteamTokenDumper\SharedInfo.cs'
|
||||
if ((Test-Path env:STEAM_TOKEN_DUMPER_TOKEN) -and (Test-Path "$env:STEAM_TOKEN_DUMPER_NAME\SharedInfo.cs" -PathType Leaf)) {
|
||||
(Get-Content "$env:STEAM_TOKEN_DUMPER_NAME\SharedInfo.cs").Replace('STEAM_TOKEN_DUMPER_TOKEN', "$env:STEAM_TOKEN_DUMPER_TOKEN") | Set-Content "$env:STEAM_TOKEN_DUMPER_NAME\SharedInfo.cs"
|
||||
}
|
||||
- name: Publish ArchiSteamFarm.OfficialPlugins.SteamTokenDumper for .NET Core
|
||||
run: dotnet publish "${{ env.STEAM_TOKEN_DUMPER_NAME }}" -c "${{ env.CONFIGURATION }}" -f "${{ env.NET_CORE_VERSION }}" -o "out/${{ env.STEAM_TOKEN_DUMPER_NAME }}/${{ env.NET_CORE_VERSION }}" -p:UseAppHost=false --nologo
|
||||
|
||||
@@ -38,12 +38,12 @@ script:
|
||||
dotnet build ArchiSteamFarm -c "$CONFIGURATION" -p:UseAppHost=false --nologo
|
||||
dotnet build ArchiSteamFarm.CustomPlugins.ExamplePlugin -c "$CONFIGURATION" -p:UseAppHost=false --nologo
|
||||
dotnet build ArchiSteamFarm.CustomPlugins.PeriodicGC -c "$CONFIGURATION" -p:UseAppHost=false --nologo
|
||||
dotnet build ArchiSteamFarm.OfficialPlugins.SteamTokenDumper -c "$CONFIGURATION" -p:UseAppHost=false --nologo
|
||||
dotnet build "$STEAM_TOKEN_DUMPER_NAME" -c "$CONFIGURATION" -p:UseAppHost=false --nologo
|
||||
dotnet test ArchiSteamFarm.Tests -c "$CONFIGURATION" -p:UseAppHost=false --nologo
|
||||
|
||||
if [ -n "${STEAM_TOKEN_DUMPER_TOKEN-}" ] && [ -f "ArchiSteamFarm.OfficialPlugins.SteamTokenDumper/SharedInfo.cs" ]; then
|
||||
sed "s/STEAM_TOKEN_DUMPER_TOKEN/${STEAM_TOKEN_DUMPER_TOKEN}/g" "ArchiSteamFarm.OfficialPlugins.SteamTokenDumper/SharedInfo.cs" > "ArchiSteamFarm.OfficialPlugins.SteamTokenDumper/SharedInfo.cs.new";
|
||||
mv "ArchiSteamFarm.OfficialPlugins.SteamTokenDumper/SharedInfo.cs.new" "ArchiSteamFarm.OfficialPlugins.SteamTokenDumper/SharedInfo.cs"
|
||||
if [ -n "${STEAM_TOKEN_DUMPER_TOKEN-}" ] && [ -f "${STEAM_TOKEN_DUMPER_NAME}/SharedInfo.cs" ]; then
|
||||
sed "s/STEAM_TOKEN_DUMPER_TOKEN/${STEAM_TOKEN_DUMPER_TOKEN}/g" "${STEAM_TOKEN_DUMPER_NAME}/SharedInfo.cs" > "${STEAM_TOKEN_DUMPER_NAME}/SharedInfo.cs.new";
|
||||
mv "${STEAM_TOKEN_DUMPER_NAME}/SharedInfo.cs.new" "${STEAM_TOKEN_DUMPER_NAME}/SharedInfo.cs"
|
||||
fi
|
||||
|
||||
dotnet publish "$STEAM_TOKEN_DUMPER_NAME" -c "$CONFIGURATION" -f "$NET_CORE_VERSION" -o "out/${STEAM_TOKEN_DUMPER_NAME}/${NET_CORE_VERSION}" -p:UseAppHost=false --nologo
|
||||
|
||||
Submodule ASF-WebConfigGenerator updated: 3289a9ab60...5efdaf96ac
2
ASF-ui
2
ASF-ui
Submodule ASF-ui updated: 17c5a777b9...b74e43068e
@@ -22,14 +22,13 @@
|
||||
namespace ArchiSteamFarm.OfficialPlugins.SteamTokenDumper {
|
||||
internal static class SharedInfo {
|
||||
internal const byte ApiVersion = 1;
|
||||
internal const byte AppInfosPerSingleRequest = byte.MaxValue;
|
||||
internal const string ConfigurationPropertyEnabled = nameof(SteamTokenDumperPlugin) + "Enabled";
|
||||
internal const ushort ItemsPerSingleRequest = 2048; // Should be synchronized with TimeoutForLongRunningTasksInSeconds
|
||||
internal const byte MaximumHoursBetweenRefresh = 8; // Per single bot account, makes sense to be 2 or 3 times less than MinimumHoursBetweenUploads
|
||||
internal const byte MaximumMinutesBeforeFirstUpload = 60; // Must be greater or equal to MinimumMinutesBeforeFirstUpload
|
||||
internal const byte MinimumHoursBetweenUploads = 24;
|
||||
internal const byte MinimumMinutesBeforeFirstUpload = 10; // Must be less or equal to MaximumMinutesBeforeFirstUpload
|
||||
internal const string ServerURL = "https://asf-token-dumper.xpaw.me";
|
||||
internal const byte TimeoutForLongRunningTasksInSeconds = 60; // Should be synchronized with ItemsPerSingleRequest
|
||||
internal const string Token = "STEAM_TOKEN_DUMPER_TOKEN";
|
||||
|
||||
internal static bool HasValidToken => Token.Length == 128;
|
||||
|
||||
@@ -1,40 +0,0 @@
|
||||
// _ _ _ ____ _ _____
|
||||
// / \ _ __ ___ | |__ (_)/ ___| | |_ ___ __ _ _ __ ___ | ___|__ _ _ __ _ __ ___
|
||||
// / _ \ | '__|/ __|| '_ \ | |\___ \ | __|/ _ \ / _` || '_ ` _ \ | |_ / _` || '__|| '_ ` _ \
|
||||
// / ___ \ | | | (__ | | | || | ___) || |_| __/| (_| || | | | | || _|| (_| || | | | | | | |
|
||||
// /_/ \_\|_| \___||_| |_||_||____/ \__|\___| \__,_||_| |_| |_||_| \__,_||_| |_| |_| |_|
|
||||
// |
|
||||
// Copyright 2015-2020 Łukasz "JustArchi" Domeradzki
|
||||
// Contact: JustArchi@JustArchi.net
|
||||
// |
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
// |
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
// |
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
using System;
|
||||
using System.Threading.Tasks;
|
||||
using JetBrains.Annotations;
|
||||
using SteamKit2;
|
||||
|
||||
namespace ArchiSteamFarm.OfficialPlugins.SteamTokenDumper {
|
||||
internal static class StaticHelpers {
|
||||
[NotNull]
|
||||
internal static Task<T> ToLongRunningTask<T>([NotNull] this AsyncJob<T> job) where T : CallbackMsg {
|
||||
if (job == null) {
|
||||
throw new ArgumentNullException(nameof(job));
|
||||
}
|
||||
|
||||
job.Timeout = TimeSpan.FromSeconds(SharedInfo.TimeoutForLongRunningTasksInSeconds);
|
||||
|
||||
return job.ToTask();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -250,11 +250,11 @@ namespace ArchiSteamFarm.OfficialPlugins.SteamTokenDumper {
|
||||
|
||||
bot.ArchiLogger.LogGenericInfo($"Retrieving a total of {appIDsToRefresh.Count} app access tokens...");
|
||||
|
||||
HashSet<uint> appIDsThisRound = new HashSet<uint>(Math.Min(appIDsToRefresh.Count, SharedInfo.ItemsPerSingleRequest));
|
||||
HashSet<uint> appIDsThisRound = new HashSet<uint>(Math.Min(appIDsToRefresh.Count, SharedInfo.AppInfosPerSingleRequest));
|
||||
|
||||
using (HashSet<uint>.Enumerator enumerator = appIDsToRefresh.GetEnumerator()) {
|
||||
while (true) {
|
||||
while ((appIDsThisRound.Count < SharedInfo.ItemsPerSingleRequest) && enumerator.MoveNext()) {
|
||||
while ((appIDsThisRound.Count < SharedInfo.AppInfosPerSingleRequest) && enumerator.MoveNext()) {
|
||||
appIDsThisRound.Add(enumerator.Current);
|
||||
}
|
||||
|
||||
@@ -262,18 +262,28 @@ namespace ArchiSteamFarm.OfficialPlugins.SteamTokenDumper {
|
||||
break;
|
||||
}
|
||||
|
||||
if (!bot.IsConnectedAndLoggedOn) {
|
||||
return;
|
||||
}
|
||||
|
||||
bot.ArchiLogger.LogGenericInfo($"Retrieving {appIDsThisRound.Count} app access tokens...");
|
||||
|
||||
SteamApps.PICSTokensCallback response;
|
||||
|
||||
try {
|
||||
response = await bot.SteamApps.PICSGetAccessTokens(appIDsThisRound, Enumerable.Empty<uint>());
|
||||
response = await bot.SteamApps.PICSGetAccessTokens(appIDsThisRound, Enumerable.Empty<uint>()).ToLongRunningTask().ConfigureAwait(false);
|
||||
} catch (Exception e) {
|
||||
bot.ArchiLogger.LogGenericWarningException(e);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (response == null) {
|
||||
bot.ArchiLogger.LogGenericWarning(string.Format(Strings.WarningFailedWithError, nameof(response)));
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
bot.ArchiLogger.LogGenericInfo($"Finished retrieving {appIDsThisRound.Count} app access tokens.");
|
||||
|
||||
appIDsThisRound.Clear();
|
||||
@@ -289,7 +299,7 @@ namespace ArchiSteamFarm.OfficialPlugins.SteamTokenDumper {
|
||||
|
||||
using (HashSet<uint>.Enumerator enumerator = appIDsToRefresh.GetEnumerator()) {
|
||||
while (true) {
|
||||
while ((appIDsThisRound.Count < SharedInfo.ItemsPerSingleRequest) && enumerator.MoveNext()) {
|
||||
while ((appIDsThisRound.Count < SharedInfo.AppInfosPerSingleRequest) && enumerator.MoveNext()) {
|
||||
appIDsThisRound.Add(enumerator.Current);
|
||||
}
|
||||
|
||||
@@ -297,12 +307,16 @@ namespace ArchiSteamFarm.OfficialPlugins.SteamTokenDumper {
|
||||
break;
|
||||
}
|
||||
|
||||
if (!bot.IsConnectedAndLoggedOn) {
|
||||
return;
|
||||
}
|
||||
|
||||
bot.ArchiLogger.LogGenericInfo($"Retrieving {appIDsThisRound.Count} app infos...");
|
||||
|
||||
AsyncJobMultiple<SteamApps.PICSProductInfoCallback>.ResultSet response;
|
||||
|
||||
try {
|
||||
response = await bot.SteamApps.PICSGetProductInfo(appIDsThisRound.Select(appID => new SteamApps.PICSRequest { ID = appID, AccessToken = GlobalCache.GetAppToken(appID), Public = false }), Enumerable.Empty<SteamApps.PICSRequest>());
|
||||
response = await bot.SteamApps.PICSGetProductInfo(appIDsThisRound.Select(appID => new SteamApps.PICSRequest { ID = appID, AccessToken = GlobalCache.GetAppToken(appID), Public = false }), Enumerable.Empty<SteamApps.PICSRequest>()).ToLongRunningTask().ConfigureAwait(false);
|
||||
} catch (Exception e) {
|
||||
bot.ArchiLogger.LogGenericWarningException(e);
|
||||
|
||||
@@ -320,6 +334,7 @@ namespace ArchiSteamFarm.OfficialPlugins.SteamTokenDumper {
|
||||
appIDsThisRound.Clear();
|
||||
|
||||
Dictionary<uint, uint> appChangeNumbers = new Dictionary<uint, uint>();
|
||||
|
||||
HashSet<Task<SteamApps.DepotKeyCallback>> depotTasks = new HashSet<Task<SteamApps.DepotKeyCallback>>();
|
||||
|
||||
foreach (SteamApps.PICSProductInfoCallback.PICSProductInfo app in response.Results.SelectMany(result => result.Apps.Values)) {
|
||||
|
||||
@@ -218,7 +218,7 @@ namespace ArchiSteamFarm {
|
||||
SteamUnifiedMessages.ServiceMethodResponse response;
|
||||
|
||||
try {
|
||||
response = await UnifiedPlayerService.SendMessage(x => x.AddFriend(request));
|
||||
response = await UnifiedPlayerService.SendMessage(x => x.AddFriend(request)).ToLongRunningTask().ConfigureAwait(false);
|
||||
} catch (Exception e) {
|
||||
ArchiLogger.LogGenericWarningException(e);
|
||||
|
||||
@@ -253,7 +253,7 @@ namespace ArchiSteamFarm {
|
||||
SteamUnifiedMessages.ServiceMethodResponse response;
|
||||
|
||||
try {
|
||||
response = await UnifiedClanChatRoomsService.SendMessage(x => x.GetClanChatRoomInfo(request));
|
||||
response = await UnifiedClanChatRoomsService.SendMessage(x => x.GetClanChatRoomInfo(request)).ToLongRunningTask().ConfigureAwait(false);
|
||||
} catch (Exception e) {
|
||||
ArchiLogger.LogGenericWarningException(e);
|
||||
|
||||
@@ -284,7 +284,7 @@ namespace ArchiSteamFarm {
|
||||
SteamUnifiedMessages.ServiceMethodResponse response;
|
||||
|
||||
try {
|
||||
response = await UnifiedPlayerService.SendMessage(x => x.GetGameBadgeLevels(request));
|
||||
response = await UnifiedPlayerService.SendMessage(x => x.GetGameBadgeLevels(request)).ToLongRunningTask().ConfigureAwait(false);
|
||||
} catch (Exception e) {
|
||||
ArchiLogger.LogGenericWarningException(e);
|
||||
|
||||
@@ -316,7 +316,7 @@ namespace ArchiSteamFarm {
|
||||
SteamUnifiedMessages.ServiceMethodResponse response;
|
||||
|
||||
try {
|
||||
response = await UnifiedChatRoomService.SendMessage(x => x.GetMyChatRoomGroups(request));
|
||||
response = await UnifiedChatRoomService.SendMessage(x => x.GetMyChatRoomGroups(request)).ToLongRunningTask().ConfigureAwait(false);
|
||||
} catch (Exception e) {
|
||||
ArchiLogger.LogGenericWarningException(e);
|
||||
|
||||
@@ -348,7 +348,7 @@ namespace ArchiSteamFarm {
|
||||
SteamUnifiedMessages.ServiceMethodResponse response;
|
||||
|
||||
try {
|
||||
response = await UnifiedEconService.SendMessage(x => x.GetTradeOfferAccessToken(request));
|
||||
response = await UnifiedEconService.SendMessage(x => x.GetTradeOfferAccessToken(request)).ToLongRunningTask().ConfigureAwait(false);
|
||||
} catch (Exception e) {
|
||||
ArchiLogger.LogGenericWarningException(e);
|
||||
|
||||
@@ -386,7 +386,7 @@ namespace ArchiSteamFarm {
|
||||
SteamUnifiedMessages.ServiceMethodResponse response;
|
||||
|
||||
try {
|
||||
response = await UnifiedChatRoomService.SendMessage(x => x.JoinChatRoomGroup(request));
|
||||
response = await UnifiedChatRoomService.SendMessage(x => x.JoinChatRoomGroup(request)).ToLongRunningTask().ConfigureAwait(false);
|
||||
} catch (Exception e) {
|
||||
ArchiLogger.LogGenericWarningException(e);
|
||||
|
||||
@@ -477,7 +477,7 @@ namespace ArchiSteamFarm {
|
||||
Client.Send(request);
|
||||
|
||||
try {
|
||||
return await new AsyncJob<RedeemGuestPassResponseCallback>(Client, request.SourceJobID);
|
||||
return await new AsyncJob<RedeemGuestPassResponseCallback>(Client, request.SourceJobID).ToLongRunningTask().ConfigureAwait(false);
|
||||
} catch (Exception e) {
|
||||
ArchiLogger.LogGenericException(e);
|
||||
|
||||
@@ -510,7 +510,7 @@ namespace ArchiSteamFarm {
|
||||
Client.Send(request);
|
||||
|
||||
try {
|
||||
return await new AsyncJob<PurchaseResponseCallback>(Client, request.SourceJobID);
|
||||
return await new AsyncJob<PurchaseResponseCallback>(Client, request.SourceJobID).ToLongRunningTask().ConfigureAwait(false);
|
||||
} catch (Exception e) {
|
||||
ArchiLogger.LogGenericException(e);
|
||||
|
||||
@@ -540,7 +540,7 @@ namespace ArchiSteamFarm {
|
||||
SteamUnifiedMessages.ServiceMethodResponse response;
|
||||
|
||||
try {
|
||||
response = await UnifiedPlayerService.SendMessage(x => x.RemoveFriend(request));
|
||||
response = await UnifiedPlayerService.SendMessage(x => x.RemoveFriend(request)).ToLongRunningTask().ConfigureAwait(false);
|
||||
} catch (Exception e) {
|
||||
ArchiLogger.LogGenericWarningException(e);
|
||||
|
||||
@@ -592,7 +592,7 @@ namespace ArchiSteamFarm {
|
||||
SteamUnifiedMessages.ServiceMethodResponse response;
|
||||
|
||||
try {
|
||||
response = await UnifiedFriendMessagesService.SendMessage(x => x.SendMessage(request));
|
||||
response = await UnifiedFriendMessagesService.SendMessage(x => x.SendMessage(request)).ToLongRunningTask().ConfigureAwait(false);
|
||||
} catch (Exception e) {
|
||||
ArchiLogger.LogGenericWarningException(e);
|
||||
|
||||
@@ -628,7 +628,7 @@ namespace ArchiSteamFarm {
|
||||
SteamUnifiedMessages.ServiceMethodResponse response;
|
||||
|
||||
try {
|
||||
response = await UnifiedChatRoomService.SendMessage(x => x.SendChatMessage(request));
|
||||
response = await UnifiedChatRoomService.SendMessage(x => x.SendChatMessage(request)).ToLongRunningTask().ConfigureAwait(false);
|
||||
} catch (Exception e) {
|
||||
ArchiLogger.LogGenericWarningException(e);
|
||||
|
||||
@@ -663,7 +663,7 @@ namespace ArchiSteamFarm {
|
||||
SteamUnifiedMessages.ServiceMethodResponse response;
|
||||
|
||||
try {
|
||||
response = await UnifiedFriendMessagesService.SendMessage(x => x.SendMessage(request));
|
||||
response = await UnifiedFriendMessagesService.SendMessage(x => x.SendMessage(request)).ToLongRunningTask().ConfigureAwait(false);
|
||||
} catch (Exception e) {
|
||||
ArchiLogger.LogGenericWarningException(e);
|
||||
|
||||
|
||||
@@ -27,9 +27,9 @@
|
||||
<PackageReference Include="NLog" Version="4.7.2" />
|
||||
<PackageReference Include="NLog.Web.AspNetCore" Version="4.9.2" />
|
||||
<PackageReference Include="SteamKit2" Version="2.3.0-beta.2" />
|
||||
<PackageReference Include="Swashbuckle.AspNetCore" Version="5.4.1" />
|
||||
<PackageReference Include="Swashbuckle.AspNetCore.Annotations" Version="5.4.1" />
|
||||
<PackageReference Include="Swashbuckle.AspNetCore.Newtonsoft" Version="5.4.1" />
|
||||
<PackageReference Include="Swashbuckle.AspNetCore" Version="5.5.0" />
|
||||
<PackageReference Include="Swashbuckle.AspNetCore.Annotations" Version="5.5.0" />
|
||||
<PackageReference Include="Swashbuckle.AspNetCore.Newtonsoft" Version="5.5.0" />
|
||||
<PackageReference Include="System.Composition" Version="1.4.1" />
|
||||
<PackageReference Include="System.Linq.Async" Version="4.1.1" />
|
||||
</ItemGroup>
|
||||
|
||||
@@ -50,13 +50,13 @@ namespace ArchiSteamFarm {
|
||||
[PublicAPI]
|
||||
public const string SteamStoreURL = "https://" + SteamStoreHost;
|
||||
|
||||
internal const ushort MaxItemsInSingleInventoryRequest = 5000;
|
||||
|
||||
private const string IEconService = "IEconService";
|
||||
private const string IPlayerService = "IPlayerService";
|
||||
private const string ISteamApps = "ISteamApps";
|
||||
private const string ISteamUserAuth = "ISteamUserAuth";
|
||||
private const string ITwoFactorService = "ITwoFactorService";
|
||||
private const ushort MaxItemsInSingleInventoryRequest = 5000;
|
||||
private const byte MinSessionValidityInSeconds = GlobalConfig.DefaultConnectionTimeout / 6;
|
||||
private const string SteamCommunityHost = "steamcommunity.com";
|
||||
private const string SteamHelpHost = "help.steampowered.com";
|
||||
private const string SteamStoreHost = "store.steampowered.com";
|
||||
@@ -2392,14 +2392,16 @@ namespace ArchiSteamFarm {
|
||||
}
|
||||
|
||||
private async Task<bool?> IsSessionExpired() {
|
||||
if (DateTime.UtcNow < LastSessionCheck.AddSeconds(MinSessionValidityInSeconds)) {
|
||||
DateTime triggeredAt = DateTime.UtcNow;
|
||||
|
||||
if (triggeredAt <= LastSessionCheck) {
|
||||
return LastSessionCheck != LastSessionRefresh;
|
||||
}
|
||||
|
||||
await SessionSemaphore.WaitAsync().ConfigureAwait(false);
|
||||
|
||||
try {
|
||||
if (DateTime.UtcNow < LastSessionCheck.AddSeconds(MinSessionValidityInSeconds)) {
|
||||
if (triggeredAt <= LastSessionCheck) {
|
||||
return LastSessionCheck != LastSessionRefresh;
|
||||
}
|
||||
|
||||
@@ -2521,15 +2523,15 @@ namespace ArchiSteamFarm {
|
||||
|
||||
DateTime triggeredAt = DateTime.UtcNow;
|
||||
|
||||
if (triggeredAt < LastSessionRefresh.AddSeconds(MinSessionValidityInSeconds)) {
|
||||
return true;
|
||||
if (triggeredAt <= LastSessionCheck) {
|
||||
return LastSessionCheck == LastSessionRefresh;
|
||||
}
|
||||
|
||||
await SessionSemaphore.WaitAsync().ConfigureAwait(false);
|
||||
|
||||
try {
|
||||
if (triggeredAt < LastSessionRefresh.AddSeconds(MinSessionValidityInSeconds)) {
|
||||
return true;
|
||||
if (triggeredAt <= LastSessionCheck) {
|
||||
return LastSessionCheck == LastSessionRefresh;
|
||||
}
|
||||
|
||||
if (!Bot.IsConnectedAndLoggedOn) {
|
||||
@@ -2539,10 +2541,14 @@ namespace ArchiSteamFarm {
|
||||
Bot.ArchiLogger.LogGenericInfo(Strings.RefreshingOurSession);
|
||||
bool result = await Bot.RefreshSession().ConfigureAwait(false);
|
||||
|
||||
DateTime now = DateTime.UtcNow;
|
||||
|
||||
if (result) {
|
||||
LastSessionCheck = LastSessionRefresh = DateTime.UtcNow;
|
||||
LastSessionRefresh = now;
|
||||
}
|
||||
|
||||
LastSessionCheck = now;
|
||||
|
||||
return result;
|
||||
} finally {
|
||||
SessionSemaphore.Release();
|
||||
|
||||
@@ -23,6 +23,7 @@ using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.Immutable;
|
||||
using System.Collections.Specialized;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
@@ -122,8 +123,6 @@ namespace ArchiSteamFarm {
|
||||
internal readonly ArchiHandler ArchiHandler;
|
||||
internal readonly BotDatabase BotDatabase;
|
||||
|
||||
internal readonly ConcurrentDictionary<uint, (EPaymentMethod PaymentMethod, DateTime TimeCreated)> OwnedPackageIDs = new ConcurrentDictionary<uint, (EPaymentMethod PaymentMethod, DateTime TimeCreated)>();
|
||||
|
||||
internal bool CanReceiveSteamCards => !IsAccountLimited && !IsAccountLocked;
|
||||
internal bool IsAccountLimited => AccountFlags.HasFlag(EAccountFlags.LimitedUser) || AccountFlags.HasFlag(EAccountFlags.LimitedUserForce);
|
||||
internal bool IsAccountLocked => AccountFlags.HasFlag(EAccountFlags.Lockdown);
|
||||
@@ -195,6 +194,7 @@ namespace ArchiSteamFarm {
|
||||
[PublicAPI]
|
||||
public ECurrencyCode WalletCurrency { get; private set; }
|
||||
|
||||
internal ImmutableDictionary<uint, (EPaymentMethod PaymentMethod, DateTime TimeCreated)> OwnedPackageIDs { get; private set; } = ImmutableDictionary<uint, (EPaymentMethod PaymentMethod, DateTime TimeCreated)>.Empty;
|
||||
internal bool PlayingBlocked { get; private set; }
|
||||
internal bool PlayingWasBlocked { get; private set; }
|
||||
|
||||
@@ -671,11 +671,27 @@ namespace ArchiSteamFarm {
|
||||
}
|
||||
}
|
||||
|
||||
SteamApps.PICSTokensCallback tokenCallback = null;
|
||||
|
||||
for (byte i = 0; (i < WebBrowser.MaxTries) && (tokenCallback == null) && IsConnectedAndLoggedOn; i++) {
|
||||
try {
|
||||
tokenCallback = await SteamApps.PICSGetAccessTokens(appID, null).ToLongRunningTask().ConfigureAwait(false);
|
||||
} catch (Exception e) {
|
||||
ArchiLogger.LogGenericWarningException(e);
|
||||
}
|
||||
}
|
||||
|
||||
if (tokenCallback == null) {
|
||||
return (optimisticDiscovery ? appID : 0, DateTime.MinValue, true);
|
||||
}
|
||||
|
||||
SteamApps.PICSRequest request = new SteamApps.PICSRequest(appID, tokenCallback.AppTokens.TryGetValue(appID, out ulong accessToken) ? accessToken : 0, false);
|
||||
|
||||
AsyncJobMultiple<SteamApps.PICSProductInfoCallback>.ResultSet productInfoResultSet = null;
|
||||
|
||||
for (byte i = 0; (i < WebBrowser.MaxTries) && (productInfoResultSet == null) && IsConnectedAndLoggedOn; i++) {
|
||||
try {
|
||||
productInfoResultSet = await SteamApps.PICSGetProductInfo(appID, null, false);
|
||||
productInfoResultSet = await SteamApps.PICSGetProductInfo(request.ToEnumerable(), Enumerable.Empty<SteamApps.PICSRequest>()).ToLongRunningTask().ConfigureAwait(false);
|
||||
} catch (Exception e) {
|
||||
ArchiLogger.LogGenericWarningException(e);
|
||||
}
|
||||
@@ -845,7 +861,7 @@ namespace ArchiSteamFarm {
|
||||
|
||||
for (byte i = 0; (i < WebBrowser.MaxTries) && (productInfoResultSet == null) && IsConnectedAndLoggedOn; i++) {
|
||||
try {
|
||||
productInfoResultSet = await SteamApps.PICSGetProductInfo(Enumerable.Empty<SteamApps.PICSRequest>(), packageRequests);
|
||||
productInfoResultSet = await SteamApps.PICSGetProductInfo(Enumerable.Empty<SteamApps.PICSRequest>(), packageRequests).ToLongRunningTask().ConfigureAwait(false);
|
||||
} catch (Exception e) {
|
||||
ArchiLogger.LogGenericWarningException(e);
|
||||
}
|
||||
@@ -1119,7 +1135,7 @@ namespace ArchiSteamFarm {
|
||||
SteamUser.WebAPIUserNonceCallback callback;
|
||||
|
||||
try {
|
||||
callback = await SteamUser.RequestWebAPIUserNonce();
|
||||
callback = await SteamUser.RequestWebAPIUserNonce().ToLongRunningTask().ConfigureAwait(false);
|
||||
} catch (Exception e) {
|
||||
ArchiLogger.LogGenericWarningException(e);
|
||||
await Connect(true).ConfigureAwait(false);
|
||||
@@ -1733,7 +1749,7 @@ namespace ArchiSteamFarm {
|
||||
|
||||
try {
|
||||
if (DateTime.UtcNow.Subtract(ArchiHandler.LastPacketReceived).TotalSeconds > ASF.GlobalConfig.ConnectionTimeout) {
|
||||
await SteamFriends.RequestProfileInfo(SteamID);
|
||||
await SteamFriends.RequestProfileInfo(SteamID).ToLongRunningTask().ConfigureAwait(false);
|
||||
}
|
||||
|
||||
HeartBeatFailures = 0;
|
||||
@@ -2144,7 +2160,6 @@ namespace ArchiSteamFarm {
|
||||
|
||||
ArchiLogger.LogGenericInfo(Strings.BotDisconnected);
|
||||
|
||||
OwnedPackageIDs.Clear();
|
||||
PastNotifications.Clear();
|
||||
|
||||
Actions.OnDisconnected();
|
||||
@@ -2368,16 +2383,21 @@ namespace ArchiSteamFarm {
|
||||
return;
|
||||
}
|
||||
|
||||
bool initialLogin = OwnedPackageIDs.Count == 0;
|
||||
if (callback.LicenseList.Count == 0) {
|
||||
ArchiLogger.LogGenericError(string.Format(Strings.ErrorIsEmpty, nameof(callback.LicenseList)));
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
Commands.OnNewLicenseList();
|
||||
OwnedPackageIDs.Clear();
|
||||
|
||||
Dictionary<uint, (EPaymentMethod PaymentMethod, DateTime TimeCreated)> ownedPackageIDs = new Dictionary<uint, (EPaymentMethod PaymentMethod, DateTime TimeCreated)>();
|
||||
|
||||
Dictionary<uint, ulong> packageAccessTokens = new Dictionary<uint, ulong>();
|
||||
Dictionary<uint, uint> packagesToRefresh = new Dictionary<uint, uint>();
|
||||
|
||||
foreach (SteamApps.LicenseListCallback.License license in callback.LicenseList.GroupBy(license => license.PackageID, (packageID, licenses) => licenses.OrderByDescending(license => license.TimeCreated).First())) {
|
||||
OwnedPackageIDs[license.PackageID] = (license.PaymentMethod, license.TimeCreated);
|
||||
ownedPackageIDs[license.PackageID] = (license.PaymentMethod, license.TimeCreated);
|
||||
|
||||
if (!ASF.GlobalDatabase.PackageAccessTokensReadOnly.TryGetValue(license.PackageID, out ulong packageAccessToken) || (packageAccessToken != license.AccessToken)) {
|
||||
packageAccessTokens[license.PackageID] = license.AccessToken;
|
||||
@@ -2389,6 +2409,8 @@ namespace ArchiSteamFarm {
|
||||
}
|
||||
}
|
||||
|
||||
OwnedPackageIDs = ownedPackageIDs.ToImmutableDictionary();
|
||||
|
||||
if (packageAccessTokens.Count > 0) {
|
||||
ASF.GlobalDatabase.RefreshPackageAccessTokens(packageAccessTokens);
|
||||
}
|
||||
@@ -2399,11 +2421,6 @@ namespace ArchiSteamFarm {
|
||||
ArchiLogger.LogGenericTrace(Strings.Done);
|
||||
}
|
||||
|
||||
if (initialLogin && CardsFarmer.Paused) {
|
||||
// Emit initial game playing status in this case
|
||||
await ResetGamesPlayed().ConfigureAwait(false);
|
||||
}
|
||||
|
||||
await CardsFarmer.OnNewGameAdded().ConfigureAwait(false);
|
||||
}
|
||||
|
||||
@@ -2607,6 +2624,11 @@ namespace ArchiSteamFarm {
|
||||
);
|
||||
}
|
||||
|
||||
if (CardsFarmer.Paused) {
|
||||
// Emit initial game playing status in this case
|
||||
Utilities.InBackground(ResetGamesPlayed);
|
||||
}
|
||||
|
||||
SteamPICSChanges.OnBotLoggedOn();
|
||||
|
||||
await PluginsCore.OnBotLoggedOn(this).ConfigureAwait(false);
|
||||
|
||||
@@ -46,7 +46,7 @@ namespace ArchiSteamFarm {
|
||||
private const byte HoursToIgnore = 24; // How many hours we ignore unreleased appIDs and don't bother checking them again
|
||||
|
||||
[PublicAPI]
|
||||
public static readonly ImmutableHashSet<uint> SalesBlacklist = ImmutableHashSet.Create<uint>(267420, 303700, 335590, 368020, 425280, 480730, 566020, 639900, 762800, 876740, 991980, 1195670);
|
||||
public static readonly ImmutableHashSet<uint> SalesBlacklist = ImmutableHashSet.Create<uint>(267420, 303700, 335590, 368020, 425280, 480730, 566020, 639900, 762800, 876740, 991980, 1195670, 1343890);
|
||||
|
||||
private static readonly ConcurrentDictionary<uint, DateTime> GloballyIgnoredAppIDs = new ConcurrentDictionary<uint, DateTime>(); // Reserved for unreleased games
|
||||
|
||||
|
||||
@@ -576,7 +576,7 @@ namespace ArchiSteamFarm {
|
||||
SteamApps.FreeLicenseCallback callback;
|
||||
|
||||
try {
|
||||
callback = await Bot.SteamApps.RequestFreeLicense(gameID);
|
||||
callback = await Bot.SteamApps.RequestFreeLicense(gameID).ToLongRunningTask().ConfigureAwait(false);
|
||||
} catch (Exception e) {
|
||||
Bot.ArchiLogger.LogGenericWarningException(e);
|
||||
response.AppendLine(FormatBotResponse(string.Format(Strings.BotAddLicense, "app/" + gameID, EResult.Timeout)));
|
||||
|
||||
@@ -36,12 +36,12 @@ using SteamKit2;
|
||||
namespace ArchiSteamFarm {
|
||||
[SuppressMessage("ReSharper", "ClassCannotBeInstantiated")]
|
||||
public sealed class GlobalConfig {
|
||||
internal const byte DefaultConnectionTimeout = 90;
|
||||
internal const byte DefaultLoginLimiterDelay = 10;
|
||||
|
||||
private const bool DefaultAutoRestart = true;
|
||||
private const string DefaultCommandPrefix = "!";
|
||||
private const byte DefaultConfirmationsLimiterDelay = 10;
|
||||
private const byte DefaultConnectionTimeout = 90;
|
||||
private const string DefaultCurrentCulture = null;
|
||||
private const bool DefaultDebug = false;
|
||||
private const byte DefaultFarmingDelay = 15;
|
||||
|
||||
@@ -136,8 +136,8 @@ namespace ArchiSteamFarm {
|
||||
return globalDatabase;
|
||||
}
|
||||
|
||||
internal HashSet<uint> GetPackageIDs(uint appID, ICollection<uint> packageIDs) {
|
||||
if ((appID == 0) || (packageIDs == null) || (packageIDs.Count == 0)) {
|
||||
internal HashSet<uint> GetPackageIDs(uint appID, IEnumerable<uint> packageIDs) {
|
||||
if ((appID == 0) || (packageIDs == null)) {
|
||||
ASF.ArchiLogger.LogNullError(nameof(appID) + " || " + nameof(packageIDs));
|
||||
|
||||
return null;
|
||||
|
||||
@@ -166,9 +166,15 @@ namespace ArchiSteamFarm.Helpers {
|
||||
OS.UnixSetFileAccess(directoryPath, OS.EUnixPermission.Combined777);
|
||||
} else {
|
||||
DirectoryInfo directoryInfo = new DirectoryInfo(directoryPath);
|
||||
DirectorySecurity directorySecurity = new DirectorySecurity(FilePath, AccessControlSections.All);
|
||||
|
||||
directoryInfo.SetAccessControl(directorySecurity);
|
||||
try {
|
||||
DirectorySecurity directorySecurity = new DirectorySecurity(directoryPath, AccessControlSections.All);
|
||||
|
||||
directoryInfo.SetAccessControl(directorySecurity);
|
||||
} catch (PrivilegeNotHeldException e) {
|
||||
// Non-critical, user might have no rights to manage the resource
|
||||
ASF.ArchiLogger.LogGenericDebuggingException(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -179,9 +185,15 @@ namespace ArchiSteamFarm.Helpers {
|
||||
OS.UnixSetFileAccess(FilePath, OS.EUnixPermission.Combined777);
|
||||
} else {
|
||||
FileInfo fileInfo = new FileInfo(FilePath);
|
||||
FileSecurity fileSecurity = new FileSecurity(FilePath, AccessControlSections.All);
|
||||
|
||||
fileInfo.SetAccessControl(fileSecurity);
|
||||
try {
|
||||
FileSecurity fileSecurity = new FileSecurity(FilePath, AccessControlSections.All);
|
||||
|
||||
fileInfo.SetAccessControl(fileSecurity);
|
||||
} catch (PrivilegeNotHeldException e) {
|
||||
// Non-critical, user might have no rights to manage the resource
|
||||
ASF.ArchiLogger.LogGenericDebuggingException(e);
|
||||
}
|
||||
}
|
||||
} catch (IOException) {
|
||||
// Ignored, if the file was already created in the meantime by another instance, this is fine
|
||||
|
||||
@@ -1,53 +0,0 @@
|
||||
// _ _ _ ____ _ _____
|
||||
// / \ _ __ ___ | |__ (_)/ ___| | |_ ___ __ _ _ __ ___ | ___|__ _ _ __ _ __ ___
|
||||
// / _ \ | '__|/ __|| '_ \ | |\___ \ | __|/ _ \ / _` || '_ ` _ \ | |_ / _` || '__|| '_ ` _ \
|
||||
// / ___ \ | | | (__ | | | || | ___) || |_| __/| (_| || | | | | || _|| (_| || | | | | | | |
|
||||
// /_/ \_\|_| \___||_| |_||_||____/ \__|\___| \__,_||_| |_| |_||_| \__,_||_| |_| |_| |_|
|
||||
// |
|
||||
// Copyright 2015-2020 Łukasz "JustArchi" Domeradzki
|
||||
// Contact: JustArchi@JustArchi.net
|
||||
// |
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
// |
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
// |
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
using System;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using JetBrains.Annotations;
|
||||
|
||||
namespace ArchiSteamFarm.Helpers {
|
||||
internal sealed class CrossProcessSemaphore : ICrossProcessSemaphore {
|
||||
private readonly Semaphore GlobalSemaphore;
|
||||
|
||||
internal CrossProcessSemaphore([NotNull] string name) {
|
||||
if (string.IsNullOrEmpty(name)) {
|
||||
throw new ArgumentNullException(nameof(name));
|
||||
}
|
||||
|
||||
GlobalSemaphore = new Semaphore(1, 1, name);
|
||||
}
|
||||
|
||||
public void Dispose() => GlobalSemaphore.Dispose();
|
||||
|
||||
void ICrossProcessSemaphore.Release() => GlobalSemaphore.Release();
|
||||
|
||||
[NotNull]
|
||||
Task ICrossProcessSemaphore.WaitAsync() {
|
||||
GlobalSemaphore.WaitOne();
|
||||
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
[NotNull]
|
||||
Task<bool> ICrossProcessSemaphore.WaitAsync(int millisecondsTimeout) => Task.FromResult(GlobalSemaphore.WaitOne(millisecondsTimeout));
|
||||
}
|
||||
}
|
||||
@@ -71,14 +71,7 @@ namespace ArchiSteamFarm {
|
||||
|
||||
string resourceName = GetOsResourceName(objectName);
|
||||
|
||||
try {
|
||||
return new CrossProcessSemaphore(resourceName);
|
||||
} catch (PlatformNotSupportedException e) {
|
||||
// CrossProcessSemaphore is currently available only for Windows platforms, we use alternative synchronization for other OSes
|
||||
ASF.ArchiLogger.LogGenericDebuggingException(e);
|
||||
|
||||
return new CrossProcessFileBasedSemaphore(resourceName);
|
||||
}
|
||||
return new CrossProcessFileBasedSemaphore(resourceName);
|
||||
}
|
||||
|
||||
internal static void Init(bool systemRequired, GlobalConfig.EOptimizationMode optimizationMode) {
|
||||
|
||||
@@ -34,8 +34,8 @@ using Newtonsoft.Json;
|
||||
|
||||
namespace ArchiSteamFarm {
|
||||
internal sealed class Statistics : IAsyncDisposable {
|
||||
private const ushort MaxItemsForFairBots = ArchiWebHandler.MaxItemsInSingleInventoryRequest * WebBrowser.MaxTries; // Determines which fair bots we'll deprioritize when matching due to excessive number of inventory requests they need to make, which are likely to fail in the process or cause excessive delays
|
||||
private const byte MaxMatchedBotsHard = 40; // Determines how many bots we can attempt to match in total, where match attempt is equal to analyzing bot's inventory
|
||||
private const byte MaxMatchedBotsSoft = MaxMatchedBotsHard / 2; // Determines how many consecutive empty matches we need to get before we decide to skip bots from the same category
|
||||
private const byte MaxMatchingRounds = 10; // Determines maximum amount of matching rounds we're going to consider before leaving the rest of work for the next batch
|
||||
private const byte MinAnnouncementCheckTTL = 6; // Minimum amount of hours we must wait before checking eligibility for Announcement, should be lower than MinPersonaStateTTL
|
||||
private const byte MinHeartBeatTTL = 10; // Minimum amount of minutes we must wait before sending next HeartBeat
|
||||
@@ -325,10 +325,11 @@ namespace ArchiSteamFarm {
|
||||
|
||||
Dictionary<ulong, (byte Tries, ISet<ulong> GivenAssetIDs, ISet<ulong> ReceivedAssetIDs)> triedSteamIDs = new Dictionary<ulong, (byte Tries, ISet<ulong> GivenAssetIDs, ISet<ulong> ReceivedAssetIDs)>();
|
||||
|
||||
bool match = true;
|
||||
bool shouldContinueMatching = true;
|
||||
bool tradedSomething = false;
|
||||
|
||||
for (byte i = 0; (i < MaxMatchingRounds) && match; i++) {
|
||||
if (i > 0) {
|
||||
for (byte i = 0; (i < MaxMatchingRounds) && shouldContinueMatching; i++) {
|
||||
if ((i > 0) && tradedSomething) {
|
||||
// After each round we wait at least 5 minutes for all bots to react
|
||||
await Task.Delay(5 * 60 * 1000).ConfigureAwait(false);
|
||||
}
|
||||
@@ -341,7 +342,7 @@ namespace ArchiSteamFarm {
|
||||
|
||||
using (await Bot.Actions.GetTradingLock().ConfigureAwait(false)) {
|
||||
Bot.ArchiLogger.LogGenericInfo(string.Format(Strings.ActivelyMatchingItems, i));
|
||||
match = await MatchActivelyRound(acceptedMatchableTypes, triedSteamIDs).ConfigureAwait(false);
|
||||
(shouldContinueMatching, tradedSomething) = await MatchActivelyRound(acceptedMatchableTypes, triedSteamIDs).ConfigureAwait(false);
|
||||
Bot.ArchiLogger.LogGenericInfo(string.Format(Strings.DoneActivelyMatchingItems, i));
|
||||
}
|
||||
}
|
||||
@@ -352,11 +353,11 @@ namespace ArchiSteamFarm {
|
||||
}
|
||||
}
|
||||
|
||||
private async Task<bool> MatchActivelyRound(IReadOnlyCollection<Steam.Asset.EType> acceptedMatchableTypes, IDictionary<ulong, (byte Tries, ISet<ulong> GivenAssetIDs, ISet<ulong> ReceivedAssetIDs)> triedSteamIDs) {
|
||||
private async Task<(bool ShouldContinueMatching, bool TradedSomething)> MatchActivelyRound(IReadOnlyCollection<Steam.Asset.EType> acceptedMatchableTypes, IDictionary<ulong, (byte Tries, ISet<ulong> GivenAssetIDs, ISet<ulong> ReceivedAssetIDs)> triedSteamIDs) {
|
||||
if ((acceptedMatchableTypes == null) || (acceptedMatchableTypes.Count == 0) || (triedSteamIDs == null)) {
|
||||
Bot.ArchiLogger.LogNullError(nameof(acceptedMatchableTypes) + " || " + nameof(triedSteamIDs));
|
||||
|
||||
return false;
|
||||
return (false, false);
|
||||
}
|
||||
|
||||
HashSet<Steam.Asset> ourInventory;
|
||||
@@ -364,17 +365,17 @@ namespace ArchiSteamFarm {
|
||||
try {
|
||||
ourInventory = await Bot.ArchiWebHandler.GetInventoryAsync().Where(item => acceptedMatchableTypes.Contains(item.Type)).ToHashSetAsync().ConfigureAwait(false);
|
||||
} catch (HttpRequestException) {
|
||||
return false;
|
||||
return (false, false);
|
||||
} catch (Exception e) {
|
||||
Bot.ArchiLogger.LogGenericException(e);
|
||||
|
||||
return false;
|
||||
return (false, false);
|
||||
}
|
||||
|
||||
if (ourInventory.Count == 0) {
|
||||
Bot.ArchiLogger.LogGenericTrace(string.Format(Strings.ErrorIsEmpty, nameof(ourInventory)));
|
||||
|
||||
return false;
|
||||
return (false, false);
|
||||
}
|
||||
|
||||
(Dictionary<(uint RealAppID, Steam.Asset.EType Type, Steam.Asset.ERarity Rarity), Dictionary<ulong, uint>> ourFullState, Dictionary<(uint RealAppID, Steam.Asset.EType Type, Steam.Asset.ERarity Rarity), Dictionary<ulong, uint>> ourTradableState) = Trading.GetDividedInventoryState(ourInventory);
|
||||
@@ -383,7 +384,7 @@ namespace ArchiSteamFarm {
|
||||
// User doesn't have any more dupes in the inventory
|
||||
Bot.ArchiLogger.LogGenericTrace(string.Format(Strings.ErrorIsEmpty, nameof(ourFullState) + " || " + nameof(ourTradableState)));
|
||||
|
||||
return false;
|
||||
return (false, false);
|
||||
}
|
||||
|
||||
ImmutableHashSet<ListedUser> listedUsers = await GetListedUsers().ConfigureAwait(false);
|
||||
@@ -391,20 +392,14 @@ namespace ArchiSteamFarm {
|
||||
if ((listedUsers == null) || (listedUsers.Count == 0)) {
|
||||
Bot.ArchiLogger.LogGenericTrace(string.Format(Strings.ErrorIsEmpty, nameof(listedUsers)));
|
||||
|
||||
return false;
|
||||
return (false, false);
|
||||
}
|
||||
|
||||
bool skipAnyBots = false;
|
||||
byte emptyMatches = 0;
|
||||
byte totalMatches = 0;
|
||||
|
||||
HashSet<(uint RealAppID, Steam.Asset.EType Type, Steam.Asset.ERarity Rarity)> skippedSetsThisRound = new HashSet<(uint RealAppID, Steam.Asset.EType Type, Steam.Asset.ERarity Rarity)>();
|
||||
|
||||
foreach (ListedUser listedUser in listedUsers.Where(listedUser => (listedUser.SteamID != Bot.SteamID) && acceptedMatchableTypes.Any(listedUser.MatchableTypes.Contains) && (!triedSteamIDs.TryGetValue(listedUser.SteamID, out (byte Tries, ISet<ulong> GivenAssetIDs, ISet<ulong> ReceivedAssetIDs) attempt) || (attempt.Tries < byte.MaxValue)) && !Bot.IsBlacklistedFromTrades(listedUser.SteamID)).OrderBy(listedUser => triedSteamIDs.TryGetValue(listedUser.SteamID, out (byte Tries, ISet<ulong> GivenAssetIDs, ISet<ulong> ReceivedAssetIDs) attempt) ? attempt.Tries : 0).ThenByDescending(listedUser => listedUser.MatchEverything).ThenByDescending(listedUser => listedUser.Score)) {
|
||||
if (listedUser.MatchEverything && skipAnyBots) {
|
||||
continue;
|
||||
}
|
||||
|
||||
foreach (ListedUser listedUser in listedUsers.Where(listedUser => (listedUser.SteamID != Bot.SteamID) && acceptedMatchableTypes.Any(listedUser.MatchableTypes.Contains) && (!triedSteamIDs.TryGetValue(listedUser.SteamID, out (byte Tries, ISet<ulong> GivenAssetIDs, ISet<ulong> ReceivedAssetIDs) attempt) || (attempt.Tries < byte.MaxValue)) && !Bot.IsBlacklistedFromTrades(listedUser.SteamID)).OrderBy(listedUser => triedSteamIDs.TryGetValue(listedUser.SteamID, out (byte Tries, ISet<ulong> GivenAssetIDs, ISet<ulong> ReceivedAssetIDs) attempt) ? attempt.Tries : 0).ThenByDescending(listedUser => listedUser.MatchEverything).ThenByDescending(listedUser => listedUser.MatchEverything || (listedUser.ItemsCount < MaxItemsForFairBots)).ThenByDescending(listedUser => listedUser.Score)) {
|
||||
HashSet<(uint RealAppID, Steam.Asset.EType Type, Steam.Asset.ERarity Rarity)> wantedSets = ourTradableState.Keys.Where(set => !skippedSetsThisRound.Contains(set) && listedUser.MatchableTypes.Contains(set.Type)).ToHashSet();
|
||||
|
||||
if (wantedSets.Count == 0) {
|
||||
@@ -468,7 +463,7 @@ namespace ArchiSteamFarm {
|
||||
if (!ourFullSet.TryGetValue(classID, out uint fullAmount) || (fullAmount == 0) || (fullAmount < amount)) {
|
||||
Bot.ArchiLogger.LogNullError(nameof(fullAmount));
|
||||
|
||||
return false;
|
||||
return (false, skippedSetsThisRound.Count > 0);
|
||||
}
|
||||
|
||||
if (fullAmount > amount) {
|
||||
@@ -480,7 +475,7 @@ namespace ArchiSteamFarm {
|
||||
if (!ourTradableSet.TryGetValue(classID, out uint tradableAmount) || (tradableAmount == 0) || (tradableAmount < amount)) {
|
||||
Bot.ArchiLogger.LogNullError(nameof(tradableAmount));
|
||||
|
||||
return false;
|
||||
return (false, skippedSetsThisRound.Count > 0);
|
||||
}
|
||||
|
||||
if (fullAmount > amount) {
|
||||
@@ -606,7 +601,7 @@ namespace ArchiSteamFarm {
|
||||
// Failsafe
|
||||
Bot.ArchiLogger.LogGenericError(string.Format(Strings.WarningFailedWithError, Strings.ErrorAborted));
|
||||
|
||||
return false;
|
||||
return (false, skippedSetsThisRound.Count > 0);
|
||||
}
|
||||
|
||||
if (triedSteamIDs.TryGetValue(listedUser.SteamID, out (byte Tries, ISet<ulong> GivenAssetIDs, ISet<ulong> ReceivedAssetIDs) previousAttempt)) {
|
||||
@@ -626,8 +621,6 @@ namespace ArchiSteamFarm {
|
||||
|
||||
triedSteamIDs[listedUser.SteamID] = (++previousAttempt.Tries, previousAttempt.GivenAssetIDs, previousAttempt.ReceivedAssetIDs);
|
||||
|
||||
emptyMatches = 0;
|
||||
|
||||
Bot.ArchiLogger.LogGenericTrace(Bot.SteamID + " <- " + string.Join(", ", itemsToReceive.Select(item => item.RealAppID + "/" + item.Type + "-" + item.ClassID + " #" + item.Amount)) + " | " + string.Join(", ", itemsToGive.Select(item => item.RealAppID + "/" + item.Type + "-" + item.ClassID + " #" + item.Amount)) + " -> " + listedUser.SteamID);
|
||||
|
||||
(bool success, HashSet<ulong> mobileTradeOfferIDs) = await Bot.ArchiWebHandler.SendTradeOffer(listedUser.SteamID, itemsToGive, itemsToReceive, listedUser.TradeToken, true).ConfigureAwait(false);
|
||||
@@ -638,7 +631,7 @@ namespace ArchiSteamFarm {
|
||||
if (!twoFactorSuccess) {
|
||||
Bot.ArchiLogger.LogGenericTrace(Strings.WarningFailed);
|
||||
|
||||
return false;
|
||||
return (false, skippedSetsThisRound.Count > 0);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -661,15 +654,6 @@ namespace ArchiSteamFarm {
|
||||
triedSteamIDs[listedUser.SteamID] = (byte.MaxValue, null, null);
|
||||
}
|
||||
|
||||
if (++emptyMatches >= MaxMatchedBotsSoft) {
|
||||
if (skipAnyBots) {
|
||||
break;
|
||||
}
|
||||
|
||||
skipAnyBots = true;
|
||||
emptyMatches = 0;
|
||||
}
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -691,11 +675,17 @@ namespace ArchiSteamFarm {
|
||||
|
||||
Bot.ArchiLogger.LogGenericInfo(string.Format(Strings.ActivelyMatchingItemsRound, skippedSetsThisRound.Count));
|
||||
|
||||
return skippedSetsThisRound.Count > 0;
|
||||
// Keep matching when we either traded something this round (so it makes sense for a refresh) or if we didn't try all available bots yet (so it makes sense to keep going)
|
||||
return ((totalMatches > 0) && ((skippedSetsThisRound.Count > 0) || triedSteamIDs.Values.All(data => data.Tries < 2)), skippedSetsThisRound.Count > 0);
|
||||
}
|
||||
|
||||
[SuppressMessage("ReSharper", "ClassCannotBeInstantiated")]
|
||||
private sealed class ListedUser {
|
||||
#pragma warning disable 649
|
||||
[JsonProperty(PropertyName = "items_count", Required = Required.Always)]
|
||||
internal readonly ushort ItemsCount;
|
||||
#pragma warning restore 649
|
||||
|
||||
internal readonly HashSet<Steam.Asset.EType> MatchableTypes = new HashSet<Steam.Asset.EType>();
|
||||
|
||||
#pragma warning disable 649
|
||||
@@ -715,11 +705,6 @@ namespace ArchiSteamFarm {
|
||||
private readonly ushort GamesCount;
|
||||
#pragma warning restore 649
|
||||
|
||||
#pragma warning disable 649
|
||||
[JsonProperty(PropertyName = "items_count", Required = Required.Always)]
|
||||
private readonly ushort ItemsCount;
|
||||
#pragma warning restore 649
|
||||
|
||||
internal bool MatchEverything { get; private set; }
|
||||
|
||||
#pragma warning disable IDE0051
|
||||
|
||||
@@ -71,7 +71,7 @@ namespace ArchiSteamFarm {
|
||||
}
|
||||
|
||||
try {
|
||||
picsChanges = await refreshBot.SteamApps.PICSGetChangesSince(LastChangeNumber, true, true);
|
||||
picsChanges = await refreshBot.SteamApps.PICSGetChangesSince(LastChangeNumber, true, true).ToLongRunningTask().ConfigureAwait(false);
|
||||
} catch (Exception e) {
|
||||
refreshBot.ArchiLogger.LogGenericWarningException(e);
|
||||
}
|
||||
|
||||
@@ -32,9 +32,12 @@ using AngleSharp.XPath;
|
||||
using Humanizer;
|
||||
using Humanizer.Localisation;
|
||||
using JetBrains.Annotations;
|
||||
using SteamKit2;
|
||||
|
||||
namespace ArchiSteamFarm {
|
||||
public static class Utilities {
|
||||
private const byte TimeoutForLongRunningTasksInSeconds = 60;
|
||||
|
||||
// Normally we wouldn't need to use this singleton, but we want to ensure decent randomness across entire program's lifetime
|
||||
private static readonly Random Random = new Random();
|
||||
|
||||
@@ -259,6 +262,30 @@ namespace ArchiSteamFarm {
|
||||
[PublicAPI]
|
||||
public static string ToHumanReadable(this TimeSpan timeSpan) => timeSpan.Humanize(3, maxUnit: TimeUnit.Year, minUnit: TimeUnit.Second);
|
||||
|
||||
[NotNull]
|
||||
[PublicAPI]
|
||||
public static Task<T> ToLongRunningTask<T>([NotNull] this AsyncJob<T> job) where T : CallbackMsg {
|
||||
if (job == null) {
|
||||
throw new ArgumentNullException(nameof(job));
|
||||
}
|
||||
|
||||
job.Timeout = TimeSpan.FromSeconds(TimeoutForLongRunningTasksInSeconds);
|
||||
|
||||
return job.ToTask();
|
||||
}
|
||||
|
||||
[NotNull]
|
||||
[PublicAPI]
|
||||
public static Task<AsyncJobMultiple<T>.ResultSet> ToLongRunningTask<T>([NotNull] this AsyncJobMultiple<T> job) where T : CallbackMsg {
|
||||
if (job == null) {
|
||||
throw new ArgumentNullException(nameof(job));
|
||||
}
|
||||
|
||||
job.Timeout = TimeSpan.FromSeconds(TimeoutForLongRunningTasksInSeconds);
|
||||
|
||||
return job.ToTask();
|
||||
}
|
||||
|
||||
internal static void DeleteEmptyDirectoriesRecursively(string directory) {
|
||||
if (string.IsNullOrEmpty(directory)) {
|
||||
ASF.ArchiLogger.LogNullError(nameof(directory));
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<Project>
|
||||
<PropertyGroup>
|
||||
<Version>4.2.2.5</Version>
|
||||
<Version>4.2.3.2</Version>
|
||||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup>
|
||||
|
||||
@@ -127,7 +127,7 @@ build_script:
|
||||
}
|
||||
|
||||
|
||||
dotnet build ArchiSteamFarm.OfficialPlugins.SteamTokenDumper -c "$env:CONFIGURATION" -p:UseAppHost=false --nologo
|
||||
dotnet build "$env:STEAM_TOKEN_DUMPER_NAME" -c "$env:CONFIGURATION" -p:UseAppHost=false --nologo
|
||||
|
||||
|
||||
if ($LastExitCode -ne 0) {
|
||||
@@ -157,8 +157,8 @@ after_test:
|
||||
$ProgressPreference = 'SilentlyContinue'
|
||||
|
||||
|
||||
if ((Test-Path env:STEAM_TOKEN_DUMPER_TOKEN) -and (Test-Path 'ArchiSteamFarm.OfficialPlugins.SteamTokenDumper\SharedInfo.cs' -PathType Leaf)) {
|
||||
(Get-Content 'ArchiSteamFarm.OfficialPlugins.SteamTokenDumper\SharedInfo.cs').Replace('STEAM_TOKEN_DUMPER_TOKEN', "$env:STEAM_TOKEN_DUMPER_TOKEN") | Set-Content 'ArchiSteamFarm.OfficialPlugins.SteamTokenDumper\SharedInfo.cs'
|
||||
if ((Test-Path env:STEAM_TOKEN_DUMPER_TOKEN) -and (Test-Path "$env:STEAM_TOKEN_DUMPER_NAME\SharedInfo.cs" -PathType Leaf)) {
|
||||
(Get-Content "$env:STEAM_TOKEN_DUMPER_NAME\SharedInfo.cs").Replace('STEAM_TOKEN_DUMPER_TOKEN', "$env:STEAM_TOKEN_DUMPER_TOKEN") | Set-Content "$env:STEAM_TOKEN_DUMPER_NAME\SharedInfo.cs"
|
||||
}
|
||||
|
||||
|
||||
|
||||
21
cc.sh
21
cc.sh
@@ -4,10 +4,13 @@ set -eu
|
||||
TARGET_FRAMEWORK="netcoreapp3.1"
|
||||
|
||||
MAIN_PROJECT="ArchiSteamFarm"
|
||||
STEAM_TOKEN_DUMPER_NAME="${MAIN_PROJECT}.OfficialPlugins.SteamTokenDumper"
|
||||
TESTS_PROJECT="${MAIN_PROJECT}.Tests"
|
||||
SOLUTION="${MAIN_PROJECT}.sln"
|
||||
CONFIGURATION="Release"
|
||||
OUT="out"
|
||||
OUT_ASF="${OUT}/result"
|
||||
OUT_STD="${OUT}/${STEAM_TOKEN_DUMPER_NAME}"
|
||||
|
||||
ASF_UI=1
|
||||
CLEAN=0
|
||||
@@ -88,7 +91,7 @@ if [ "$ASF_UI" -eq 1 ]; then
|
||||
npm run-script deploy --no-progress --prefix ASF-ui
|
||||
|
||||
# ASF's output www folder needs cleaning as well
|
||||
rm -rf "${OUT}/www"
|
||||
rm -rf "${OUT_ASF}/www"
|
||||
else
|
||||
echo "WARNING: ASF-ui dependencies are missing, skipping build of ASF-ui..."
|
||||
fi
|
||||
@@ -109,7 +112,21 @@ if [ "$TEST" -eq 1 ]; then
|
||||
dotnet test "$TESTS_PROJECT" $DOTNET_FLAGS
|
||||
fi
|
||||
|
||||
dotnet publish "$MAIN_PROJECT" -o "$OUT" $DOTNET_FLAGS
|
||||
dotnet publish "$MAIN_PROJECT" -o "$OUT_ASF" $DOTNET_FLAGS
|
||||
|
||||
if [ -n "${STEAM_TOKEN_DUMPER_TOKEN-}" ] && [ -f "${STEAM_TOKEN_DUMPER_NAME}/SharedInfo.cs" ]; then
|
||||
git checkout -- "${STEAM_TOKEN_DUMPER_NAME}/SharedInfo.cs"
|
||||
sed "s/STEAM_TOKEN_DUMPER_TOKEN/${STEAM_TOKEN_DUMPER_TOKEN}/g" "${STEAM_TOKEN_DUMPER_NAME}/SharedInfo.cs" > "${STEAM_TOKEN_DUMPER_NAME}/SharedInfo.cs.new";
|
||||
mv "${STEAM_TOKEN_DUMPER_NAME}/SharedInfo.cs.new" "${STEAM_TOKEN_DUMPER_NAME}/SharedInfo.cs"
|
||||
|
||||
dotnet publish "$STEAM_TOKEN_DUMPER_NAME" -o "$OUT_STD" $DOTNET_FLAGS
|
||||
git checkout -- "${STEAM_TOKEN_DUMPER_NAME}/SharedInfo.cs"
|
||||
|
||||
mkdir -p "${OUT_ASF}/plugins/${STEAM_TOKEN_DUMPER_NAME}"
|
||||
cp "${OUT_STD}/${STEAM_TOKEN_DUMPER_NAME}.dll" "${OUT_ASF}/plugins/${STEAM_TOKEN_DUMPER_NAME}"
|
||||
else
|
||||
echo "WARNING: STEAM_TOKEN_DUMPER_TOKEN is missing, skipping build of ${STEAM_TOKEN_DUMPER_NAME}..."
|
||||
fi
|
||||
|
||||
echo
|
||||
echo "SUCCESS: Compilation finished successfully! :)"
|
||||
|
||||
18
run.sh
18
run.sh
@@ -1,20 +1,20 @@
|
||||
#!/usr/bin/env sh
|
||||
set -eu
|
||||
|
||||
BINARY_PATH="$(dirname "$(readlink -f "$0")")/out"
|
||||
BINARY_DIR="$(dirname "$(readlink -f "$0")")/out/result"
|
||||
CONFIG_PATH="config/ASF.json"
|
||||
|
||||
if [ ! -d "$BINARY_PATH" ]; then
|
||||
echo "ERROR: $BINARY_PATH could not be found!"
|
||||
if [ ! -d "$BINARY_DIR" ]; then
|
||||
echo "ERROR: $BINARY_DIR could not be found!"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
cd "$BINARY_PATH"
|
||||
cd "$BINARY_DIR"
|
||||
|
||||
BINARY="$(pwd)/ArchiSteamFarm.dll"
|
||||
BINARY_PATH="$(pwd)/ArchiSteamFarm.dll"
|
||||
|
||||
if [ ! -f "$BINARY" ]; then
|
||||
echo "ERROR: $BINARY could not be found!"
|
||||
if [ ! -f "$BINARY_PATH" ]; then
|
||||
echo "ERROR: $BINARY_PATH could not be found!"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
@@ -74,9 +74,9 @@ dotnet --info
|
||||
|
||||
if [ -f "$CONFIG_PATH" ] && grep -Eq '"Headless":\s+?true' "$CONFIG_PATH"; then
|
||||
# We're running ASF in headless mode so we don't need STDIN
|
||||
dotnet "$BINARY" $BINARY_ARGS & # Start ASF in the background, trap will work properly due to non-blocking call
|
||||
dotnet "$BINARY_PATH" $BINARY_ARGS & # Start ASF in the background, trap will work properly due to non-blocking call
|
||||
wait $! # This will forward dotnet error code, set -e will abort the script if it's non-zero
|
||||
else
|
||||
# We're running ASF in non-headless mode, so we need STDIN to be operative
|
||||
dotnet "$BINARY" $BINARY_ARGS # Start ASF in the foreground, trap won't work until process exit
|
||||
dotnet "$BINARY_PATH" $BINARY_ARGS # Start ASF in the foreground, trap won't work until process exit
|
||||
fi
|
||||
|
||||
2
wiki
2
wiki
Submodule wiki updated: b825f6ddbb...31e95fe1d8
Reference in New Issue
Block a user