2024-03-17 00:06:13 +01:00
// ----------------------------------------------------------------------------------------------
2019-02-16 17:34:17 +01:00
// _ _ _ ____ _ _____
2017-11-18 17:27:06 +01:00
// / \ _ __ ___ | |__ (_)/ ___| | |_ ___ __ _ _ __ ___ | ___|__ _ _ __ _ __ ___
// / _ \ | '__|/ __|| '_ \ | |\___ \ | __|/ _ \ / _` || '_ ` _ \ | |_ / _` || '__|| '_ ` _ \
// / ___ \ | | | (__ | | | || | ___) || |_| __/| (_| || | | | | || _|| (_| || | | | | | | |
// /_/ \_\|_| \___||_| |_||_||____/ \__|\___| \__,_||_| |_| |_||_| \__,_||_| |_| |_| |_|
2024-03-17 00:06:13 +01:00
// ----------------------------------------------------------------------------------------------
2024-03-17 02:35:40 +01:00
// |
2024-01-08 11:33:28 +01:00
// Copyright 2015-2024 Łukasz "JustArchi" Domeradzki
2018-07-27 04:52:14 +02:00
// Contact: JustArchi@JustArchi.net
2024-03-17 02:35:40 +01:00
// |
2018-07-27 04:52:14 +02:00
// 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
2024-03-17 02:35:40 +01:00
// |
2018-07-27 04:52:14 +02:00
// http://www.apache.org/licenses/LICENSE-2.0
2024-03-17 02:35:40 +01:00
// |
2018-07-27 04:52:14 +02:00
// 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.
2015-10-28 19:21:27 +01:00
using System ;
2024-01-03 13:46:54 +01:00
using System.Collections.Frozen ;
2016-03-09 03:10:33 +01:00
using System.Collections.Generic ;
using System.Diagnostics ;
2017-01-08 05:32:59 +01:00
using System.Globalization ;
2015-10-25 06:16:50 +01:00
using System.IO ;
2016-03-09 03:52:04 +01:00
using System.Linq ;
2024-10-04 14:27:02 +02:00
using System.Net.Http ;
2023-11-14 20:01:29 +01:00
using System.Net.Quic ;
2021-06-15 13:10:45 +02:00
using System.Reflection ;
2021-11-10 18:40:12 +01:00
using System.Runtime.InteropServices ;
2021-05-11 23:10:16 +02:00
using System.Text ;
2015-10-29 17:36:16 +01:00
using System.Threading.Tasks ;
2021-05-08 01:37:22 +02:00
using ArchiSteamFarm.Core ;
using ArchiSteamFarm.Helpers ;
2024-02-21 03:09:36 +01:00
using ArchiSteamFarm.Helpers.Json ;
2018-09-08 00:05:23 +02:00
using ArchiSteamFarm.IPC ;
2017-01-06 13:20:36 +01:00
using ArchiSteamFarm.Localization ;
2018-09-08 01:03:55 +02:00
using ArchiSteamFarm.NLog ;
2021-05-08 01:37:22 +02:00
using ArchiSteamFarm.NLog.Targets ;
using ArchiSteamFarm.Steam ;
using ArchiSteamFarm.Storage ;
2021-05-06 20:16:06 +02:00
using ArchiSteamFarm.Web ;
2017-06-26 01:37:14 +02:00
using NLog ;
2016-11-24 07:32:16 +01:00
using SteamKit2 ;
2015-10-25 06:16:50 +01:00
2021-11-10 21:23:24 +01:00
namespace ArchiSteamFarm ;
internal static class Program {
Implement basic crash protection
- On start, we create/load crash file, if previous startup was less than 5 minutes ago, we also increase the counter
- If counter reaches 5, we abort the process, that is, freeze the process by not loading anything other than auto-updates
- In order for user to recover from above, he needs to manually remove ASF.crash file
- If process exits normally without reaching counter of 5 yet, we remove crash file (reset the counter), normal exit includes SIGTERM, SIGINT, clicking [X], !exit, !restart, and anything else that allows ASF to gracefully quit
- If process exits abnormally, that includes SIGKILL, unhandled exceptions, as well as power outages and anything that prevents ASF from graceful quit, we keep crash file around
- Update procedure, as an exception, allows crash file removal even with counter of 5. This should allow crash file recovery for people that crashed before, without a need of manual removal.
2024-02-03 21:18:47 +01:00
internal static bool AllowCrashFileRemoval { get ; set ; }
2021-11-10 21:23:24 +01:00
internal static bool ConfigMigrate { get ; private set ; } = true ;
internal static bool ConfigWatch { get ; private set ; } = true ;
2024-01-24 12:43:36 +01:00
internal static bool IgnoreUnsupportedEnvironment { get ; private set ; }
2021-11-10 21:23:24 +01:00
internal static string? NetworkGroup { get ; private set ; }
internal static bool RestartAllowed { get ; private set ; } = true ;
internal static bool Service { get ; private set ; }
internal static bool ShutdownSequenceInitialized { get ; private set ; }
2022-04-06 14:16:26 +02:00
internal static bool SteamParentalGeneration { get ; private set ; } = true ;
2017-08-02 19:36:43 +02:00
2021-11-10 21:23:24 +01:00
private static readonly Dictionary < PosixSignal , PosixSignalRegistration > RegisteredPosixSignals = new ( ) ;
private static readonly TaskCompletionSource < byte > ShutdownResetEvent = new ( ) ;
2024-01-03 13:46:54 +01:00
private static readonly FrozenSet < PosixSignal > SupportedPosixSignals = new HashSet < PosixSignal > ( 2 ) { PosixSignal . SIGINT , PosixSignal . SIGTERM } . ToFrozenSet ( ) ;
2021-11-10 18:40:12 +01:00
2022-10-07 19:32:58 +02:00
private static bool InputCryptkeyManually ;
2023-02-08 16:11:50 +01:00
private static bool Minimized ;
2021-11-10 21:23:24 +01:00
private static bool SystemRequired ;
2016-10-07 00:04:08 +02:00
2021-11-10 21:23:24 +01:00
internal static async Task Exit ( byte exitCode = 0 ) {
if ( exitCode ! = 0 ) {
2024-08-05 02:37:50 +02:00
ASF . ArchiLogger . LogGenericError ( Strings . FormatErrorExitingWithNonZeroErrorCode ( exitCode ) ) ;
2021-11-10 21:23:24 +01:00
}
2017-01-06 13:20:36 +01:00
2021-11-10 21:23:24 +01:00
await Shutdown ( exitCode ) . ConfigureAwait ( false ) ;
Environment . Exit ( exitCode ) ;
}
internal static async Task Restart ( ) {
if ( ! await InitShutdownSequence ( ) . ConfigureAwait ( false ) ) {
return ;
2016-04-02 13:08:43 +02:00
}
2021-11-10 21:23:24 +01:00
string executableName = Path . GetFileNameWithoutExtension ( OS . ProcessFileName ) ;
2016-11-24 07:32:16 +01:00
2023-11-14 19:12:33 +01:00
ArgumentException . ThrowIfNullOrEmpty ( executableName ) ;
2018-12-15 00:27:15 +01:00
2021-11-10 21:23:24 +01:00
IEnumerable < string > arguments = Environment . GetCommandLineArgs ( ) . Skip ( executableName . Equals ( SharedInfo . AssemblyName , StringComparison . Ordinal ) ? 1 : 0 ) ;
2018-06-11 01:24:54 +02:00
2021-11-10 21:23:24 +01:00
try {
2023-11-29 00:08:16 +01:00
Process . Start ( OS . ProcessFileName , string . Join ( ' ' , arguments ) ) ;
2021-11-10 21:23:24 +01:00
} catch ( Exception e ) {
ASF . ArchiLogger . LogGenericException ( e ) ;
}
2017-07-01 06:10:15 +02:00
2021-11-10 21:23:24 +01:00
// Give new process some time to take over the window (if needed)
2022-12-29 23:03:40 +01:00
await Task . Delay ( 5000 ) . ConfigureAwait ( false ) ;
2016-11-24 07:32:16 +01:00
2021-11-10 21:23:24 +01:00
ShutdownResetEvent . TrySetResult ( 0 ) ;
Environment . Exit ( 0 ) ;
}
2017-07-05 07:07:03 +02:00
2021-11-10 21:23:24 +01:00
private static void HandleCryptKeyArgument ( string cryptKey ) {
2023-11-14 19:12:33 +01:00
ArgumentException . ThrowIfNullOrEmpty ( cryptKey ) ;
2016-11-24 07:32:16 +01:00
2021-11-10 21:23:24 +01:00
ArchiCryptoHelper . SetEncryptionKey ( cryptKey ) ;
}
2017-10-21 16:56:48 +02:00
2022-09-08 19:53:36 +02:00
private static async Task < bool > HandleCryptKeyFileArgument ( string cryptKeyFile ) {
2023-11-14 19:12:33 +01:00
ArgumentException . ThrowIfNullOrEmpty ( cryptKeyFile ) ;
2022-09-08 19:53:36 +02:00
if ( ! File . Exists ( cryptKeyFile ) ) {
2024-08-05 02:37:50 +02:00
ASF . ArchiLogger . LogGenericError ( Strings . FormatErrorIsInvalid ( nameof ( cryptKeyFile ) ) ) ;
2022-09-08 20:08:39 +02:00
2022-09-08 19:53:36 +02:00
return false ;
}
string cryptKey ;
try {
cryptKey = await File . ReadAllTextAsync ( cryptKeyFile ) . ConfigureAwait ( false ) ;
} catch ( Exception e ) {
ASF . ArchiLogger . LogGenericException ( e ) ;
return false ;
}
if ( string . IsNullOrEmpty ( cryptKey ) ) {
2024-08-05 02:37:50 +02:00
ASF . ArchiLogger . LogGenericError ( Strings . FormatErrorIsEmpty ( nameof ( cryptKeyFile ) ) ) ;
2022-09-08 20:08:39 +02:00
2022-09-08 19:53:36 +02:00
return false ;
}
HandleCryptKeyArgument ( cryptKey ) ;
return true ;
}
2021-11-10 21:23:24 +01:00
private static void HandleNetworkGroupArgument ( string networkGroup ) {
2023-11-14 19:12:33 +01:00
ArgumentException . ThrowIfNullOrEmpty ( networkGroup ) ;
2017-10-21 16:56:48 +02:00
2021-11-10 21:23:24 +01:00
NetworkGroup = networkGroup ;
}
2020-05-26 23:24:43 +02:00
2021-12-04 02:33:23 +01:00
private static bool HandlePathArgument ( string path ) {
2023-11-14 19:12:33 +01:00
ArgumentException . ThrowIfNullOrEmpty ( path ) ;
2020-05-26 23:24:43 +02:00
2021-11-29 23:32:51 +01:00
// Aid userspace and replace ~ with user's home directory if possible
if ( path . Contains ( '~' , StringComparison . Ordinal ) ) {
try {
2021-11-29 23:43:19 +01:00
string homeDirectory = Environment . GetFolderPath ( Environment . SpecialFolder . UserProfile , Environment . SpecialFolderOption . DoNotVerify ) ;
2021-11-29 23:32:51 +01:00
if ( ! string . IsNullOrEmpty ( homeDirectory ) ) {
path = path . Replace ( "~" , homeDirectory , StringComparison . Ordinal ) ;
}
} catch ( Exception e ) {
ASF . ArchiLogger . LogGenericException ( e ) ;
2021-12-04 02:33:23 +01:00
return false ;
2021-11-29 23:32:51 +01:00
}
}
2021-11-10 21:23:24 +01:00
try {
Directory . SetCurrentDirectory ( path ) ;
} catch ( Exception e ) {
ASF . ArchiLogger . LogGenericException ( e ) ;
2021-12-04 02:33:23 +01:00
return false ;
2017-10-21 16:56:48 +02:00
}
2021-12-04 02:33:23 +01:00
return true ;
2021-11-10 21:23:24 +01:00
}
2017-10-21 16:56:48 +02:00
2021-11-10 21:23:24 +01:00
private static async Task Init ( IReadOnlyCollection < string > ? args ) {
AppDomain . CurrentDomain . ProcessExit + = OnProcessExit ;
AppDomain . CurrentDomain . UnhandledException + = OnUnhandledException ;
TaskScheduler . UnobservedTaskException + = OnUnobservedTaskException ;
2016-11-24 07:32:16 +01:00
2021-11-10 21:23:24 +01:00
if ( OperatingSystem . IsFreeBSD ( ) | | OperatingSystem . IsLinux ( ) | | OperatingSystem . IsMacOS ( ) ) {
foreach ( PosixSignal signal in SupportedPosixSignals ) {
RegisteredPosixSignals [ signal ] = PosixSignalRegistration . Create ( signal , OnPosixSignal ) ;
2021-11-10 18:40:12 +01:00
}
2021-11-10 21:23:24 +01:00
}
2021-11-10 18:40:12 +01:00
2021-11-10 21:23:24 +01:00
Console . CancelKeyPress + = OnCancelKeyPress ;
2021-11-01 18:55:58 +01:00
2021-11-10 21:23:24 +01:00
// Add support for custom encodings
Encoding . RegisterProvider ( CodePagesEncodingProvider . Instance ) ;
2021-05-11 23:10:16 +02:00
2021-11-10 21:23:24 +01:00
// Add support for custom logging targets
2023-06-02 16:13:54 +02:00
LogManager . Setup ( ) . SetupExtensions (
static extensions = > {
extensions . RegisterTarget < HistoryTarget > ( HistoryTarget . TargetName ) ;
extensions . RegisterTarget < SteamTarget > ( SteamTarget . TargetName ) ;
}
) ;
2016-07-19 22:18:00 +02:00
2021-11-10 21:23:24 +01:00
if ( ! await InitCore ( args ) . ConfigureAwait ( false ) | | ! await InitASF ( ) . ConfigureAwait ( false ) ) {
await Exit ( 1 ) . ConfigureAwait ( false ) ;
2017-02-05 14:32:38 +01:00
}
2021-11-10 21:23:24 +01:00
}
2016-06-28 07:14:51 +02:00
2021-11-10 21:23:24 +01:00
private static async Task < bool > InitASF ( ) {
if ( ! await InitGlobalConfigAndLanguage ( ) . ConfigureAwait ( false ) ) {
return false ;
}
2018-04-18 01:11:46 +02:00
2021-12-20 18:27:54 +01:00
OS . Init ( ASF . GlobalConfig ? . OptimizationMode ? ? GlobalConfig . DefaultOptimizationMode ) ;
2020-11-19 21:01:17 +01:00
2024-01-24 12:43:36 +01:00
return await InitGlobalDatabaseAndServices ( ) . ConfigureAwait ( false ) & & await ASF . Init ( ) . ConfigureAwait ( false ) ;
2021-11-10 21:23:24 +01:00
}
2018-12-15 00:27:15 +01:00
2021-11-10 21:23:24 +01:00
private static async Task < bool > InitCore ( IReadOnlyCollection < string > ? args ) {
2021-12-04 02:33:23 +01:00
// Init emergency loggers used for failures before setting up ones according to preference of the user
Logging . InitEmergencyLoggers ( ) ;
2021-11-10 21:23:24 +01:00
Directory . SetCurrentDirectory ( SharedInfo . HomeDirectory ) ;
// Allow loading configs from source tree if it's a debug build
if ( Debugging . IsDebugBuild ) {
// Common structure is bin/(x64/)Debug/ArchiSteamFarm.exe, so we allow up to 4 directories up
for ( byte i = 0 ; i < 4 ; i + + ) {
Directory . SetCurrentDirectory ( ".." ) ;
2017-02-05 14:32:38 +01:00
2021-11-10 21:23:24 +01:00
if ( Directory . Exists ( SharedInfo . ConfigDirectory ) ) {
break ;
2017-02-05 14:32:38 +01:00
}
}
2021-11-10 21:23:24 +01:00
// If config directory doesn't exist after our adjustment, abort all of that
if ( ! Directory . Exists ( SharedInfo . ConfigDirectory ) ) {
Directory . SetCurrentDirectory ( SharedInfo . HomeDirectory ) ;
2017-02-05 14:32:38 +01:00
}
2021-11-10 21:23:24 +01:00
}
2017-02-05 14:32:38 +01:00
2021-11-10 21:23:24 +01:00
// Parse environment variables
2022-09-08 19:53:36 +02:00
if ( ! await ParseEnvironmentVariables ( ) . ConfigureAwait ( false ) ) {
2021-12-04 02:33:23 +01:00
await Task . Delay ( SharedInfo . InformationDelay ) . ConfigureAwait ( false ) ;
return false ;
}
2020-11-26 18:22:55 +01:00
2023-02-26 10:59:48 +01:00
// Parse ASF_ARGS
try {
string [ ] ? asfArgs = Environment . GetEnvironmentVariable ( SharedInfo . EnvironmentVariableArguments ) ? . Split ( Array . Empty < char > ( ) , StringSplitOptions . RemoveEmptyEntries ) ;
if ( asfArgs ? . Length > 0 ) {
if ( ! await ParseArgs ( asfArgs ) . ConfigureAwait ( false ) ) {
await Task . Delay ( SharedInfo . InformationDelay ) . ConfigureAwait ( false ) ;
return false ;
}
}
} catch ( Exception e ) {
ASF . ArchiLogger . LogGenericException ( e ) ;
await Task . Delay ( SharedInfo . InformationDelay ) . ConfigureAwait ( false ) ;
return false ;
}
// Parse cmdline args
2021-11-10 21:23:24 +01:00
if ( args ? . Count > 0 ) {
2022-09-08 19:53:36 +02:00
if ( ! await ParseArgs ( args ) . ConfigureAwait ( false ) ) {
2021-12-04 02:33:23 +01:00
await Task . Delay ( SharedInfo . InformationDelay ) . ConfigureAwait ( false ) ;
return false ;
}
2021-11-10 21:23:24 +01:00
}
2019-03-29 23:07:06 +01:00
2021-11-10 21:23:24 +01:00
bool uniqueInstance = await OS . RegisterProcess ( ) . ConfigureAwait ( false ) ;
2019-03-29 23:07:06 +01:00
2021-12-04 02:33:23 +01:00
// Init core loggers according to user's preferences
2021-11-10 21:23:24 +01:00
Logging . InitCoreLoggers ( uniqueInstance ) ;
2019-03-29 23:07:06 +01:00
2021-11-10 21:23:24 +01:00
if ( ! uniqueInstance ) {
ASF . ArchiLogger . LogGenericError ( Strings . ErrorSingleInstanceRequired ) ;
await Task . Delay ( SharedInfo . InformationDelay ) . ConfigureAwait ( false ) ;
2020-11-19 21:01:17 +01:00
2021-11-10 21:23:24 +01:00
return false ;
}
2020-11-19 21:01:17 +01:00
2023-02-08 16:11:50 +01:00
OS . CoreInit ( Minimized , SystemRequired ) ;
2021-06-15 13:10:45 +02:00
2021-11-10 21:23:24 +01:00
Console . Title = SharedInfo . ProgramIdentifier ;
ASF . ArchiLogger . LogGenericInfo ( SharedInfo . ProgramIdentifier ) ;
2021-06-15 13:10:45 +02:00
2021-11-10 21:23:24 +01:00
string? copyright = Assembly . GetExecutingAssembly ( ) . GetCustomAttribute < AssemblyCopyrightAttribute > ( ) ? . Copyright ;
2021-10-17 00:11:04 +02:00
2021-11-10 21:23:24 +01:00
if ( ! string . IsNullOrEmpty ( copyright ) ) {
2023-11-14 20:01:29 +01:00
ASF . ArchiLogger . LogGenericInfo ( copyright ) ;
2021-11-10 21:23:24 +01:00
}
2021-10-17 00:11:04 +02:00
2021-11-10 21:23:24 +01:00
if ( IgnoreUnsupportedEnvironment ) {
ASF . ArchiLogger . LogGenericWarning ( Strings . WarningRunningInUnsupportedEnvironment ) ;
} else {
if ( ! OS . VerifyEnvironment ( ) ) {
2024-09-16 17:58:35 +02:00
ASF . ArchiLogger . LogGenericError ( Strings . FormatWarningUnsupportedEnvironment ( BuildInfo . Variant , OS . Version ) ) ;
2021-11-10 21:23:24 +01:00
await Task . Delay ( SharedInfo . InformationDelay ) . ConfigureAwait ( false ) ;
2020-11-19 21:01:17 +01:00
2021-11-10 21:23:24 +01:00
return false ;
2020-11-19 21:01:17 +01:00
}
2021-11-10 21:23:24 +01:00
if ( OS . IsRunningAsRoot ( ) ) {
2021-12-04 13:38:00 +01:00
ASF . ArchiLogger . LogGenericWarning ( Strings . WarningRunningAsRoot ) ;
await Task . Delay ( SharedInfo . ShortInformationDelay ) . ConfigureAwait ( false ) ;
2020-11-28 23:55:09 +01:00
}
2017-02-05 14:32:38 +01:00
}
2022-10-07 19:32:58 +02:00
if ( InputCryptkeyManually ) {
string? cryptkey = await Logging . GetUserInput ( ASF . EUserInputType . Cryptkey ) . ConfigureAwait ( false ) ;
if ( string . IsNullOrEmpty ( cryptkey ) ) {
2024-08-05 02:37:50 +02:00
ASF . ArchiLogger . LogGenericError ( Strings . FormatErrorIsInvalid ( nameof ( cryptkey ) ) ) ;
2022-10-07 19:32:58 +02:00
return false ;
}
2023-11-14 20:01:29 +01:00
ArchiCryptoHelper . SetEncryptionKey ( cryptkey ) ;
2022-10-07 19:32:58 +02:00
}
2021-11-10 21:23:24 +01:00
if ( ! Directory . Exists ( SharedInfo . ConfigDirectory ) ) {
ASF . ArchiLogger . LogGenericError ( Strings . ErrorConfigDirectoryNotFound ) ;
await Task . Delay ( SharedInfo . InformationDelay ) . ConfigureAwait ( false ) ;
2019-03-08 16:24:49 +01:00
2021-11-10 21:23:24 +01:00
return false ;
}
2016-07-31 17:38:14 +02:00
2021-11-10 21:23:24 +01:00
return true ;
}
2021-05-08 23:23:34 +02:00
2021-11-10 21:23:24 +01:00
private static async Task < bool > InitGlobalConfigAndLanguage ( ) {
string globalConfigFile = ASF . GetFilePath ( ASF . EFileType . Config ) ;
2019-01-14 21:50:23 +01:00
2021-11-10 21:23:24 +01:00
if ( string . IsNullOrEmpty ( globalConfigFile ) ) {
2023-11-14 19:12:33 +01:00
throw new InvalidOperationException ( nameof ( globalConfigFile ) ) ;
2021-11-10 21:23:24 +01:00
}
2018-12-15 00:27:15 +01:00
2021-11-10 21:23:24 +01:00
string? latestJson = null ;
2018-12-15 00:27:15 +01:00
2021-11-10 21:23:24 +01:00
GlobalConfig ? globalConfig ;
2017-01-08 05:32:59 +01:00
2021-11-10 21:23:24 +01:00
if ( File . Exists ( globalConfigFile ) ) {
( globalConfig , latestJson ) = await GlobalConfig . Load ( globalConfigFile ) . ConfigureAwait ( false ) ;
2018-02-25 18:57:06 +01:00
2021-11-10 21:23:24 +01:00
if ( globalConfig = = null ) {
2024-08-05 02:37:50 +02:00
ASF . ArchiLogger . LogGenericError ( Strings . FormatErrorGlobalConfigNotLoaded ( globalConfigFile ) ) ;
2021-11-10 21:23:24 +01:00
await Task . Delay ( SharedInfo . ShortInformationDelay ) . ConfigureAwait ( false ) ;
2020-05-10 18:46:55 +02:00
2021-11-10 21:23:24 +01:00
return false ;
2016-03-06 23:28:56 +01:00
}
2021-11-10 21:23:24 +01:00
} else {
globalConfig = new GlobalConfig ( ) ;
}
2016-03-06 23:28:56 +01:00
2021-11-10 21:23:24 +01:00
if ( globalConfig . Debug ) {
2024-02-21 03:09:36 +01:00
ASF . ArchiLogger . LogGenericDebug ( $"{globalConfigFile}: {globalConfig.ToJsonText(true)}" ) ;
2021-11-10 21:23:24 +01:00
}
2021-07-12 21:45:17 +02:00
2021-11-10 21:23:24 +01:00
if ( ! string . IsNullOrEmpty ( globalConfig . CurrentCulture ) ) {
try {
// GetCultureInfo() would be better but we can't use it for specifying neutral cultures such as "en"
2023-11-14 20:01:29 +01:00
CultureInfo culture = CultureInfo . CreateSpecificCulture ( globalConfig . CurrentCulture ) ;
2021-11-10 21:23:24 +01:00
CultureInfo . DefaultThreadCurrentCulture = CultureInfo . DefaultThreadCurrentUICulture = culture ;
} catch ( Exception e ) {
ASF . ArchiLogger . LogGenericWarningException ( e ) ;
2021-07-12 21:45:17 +02:00
2021-11-10 21:23:24 +01:00
ASF . ArchiLogger . LogGenericError ( Strings . ErrorInvalidCurrentCulture ) ;
2021-05-08 23:23:34 +02:00
}
2021-11-10 21:23:24 +01:00
} else {
// April Fools easter egg logic
AprilFools . Init ( ) ;
}
2021-05-08 23:23:34 +02:00
2021-11-10 21:23:24 +01:00
if ( ! string . IsNullOrEmpty ( latestJson ) ) {
2024-08-05 02:37:50 +02:00
ASF . ArchiLogger . LogGenericWarning ( Strings . FormatAutomaticFileMigration ( globalConfigFile ) ) ;
2021-05-08 23:23:34 +02:00
2023-11-14 20:01:29 +01:00
await SerializableFile . Write ( globalConfigFile , latestJson ) . ConfigureAwait ( false ) ;
2020-08-22 21:41:01 +02:00
2021-11-10 21:23:24 +01:00
ASF . ArchiLogger . LogGenericInfo ( Strings . Done ) ;
2017-02-05 09:02:27 +01:00
}
2017-01-20 22:48:44 +01:00
2021-11-10 21:23:24 +01:00
ASF . GlobalConfig = globalConfig ;
2019-03-08 16:24:49 +01:00
2021-11-10 21:23:24 +01:00
Utilities . WarnAboutIncompleteTranslation ( Strings . ResourceManager ) ;
2016-07-31 17:38:14 +02:00
2021-11-10 21:23:24 +01:00
return true ;
}
2017-01-06 22:18:41 +01:00
2021-11-10 21:23:24 +01:00
private static async Task < bool > InitGlobalDatabaseAndServices ( ) {
string globalDatabaseFile = ASF . GetFilePath ( ASF . EFileType . Database ) ;
2018-12-15 00:27:15 +01:00
2021-11-10 21:23:24 +01:00
if ( string . IsNullOrEmpty ( globalDatabaseFile ) ) {
2023-11-14 19:12:33 +01:00
throw new InvalidOperationException ( nameof ( globalDatabaseFile ) ) ;
2021-11-10 21:23:24 +01:00
}
2018-12-15 00:27:15 +01:00
2021-11-10 21:23:24 +01:00
if ( ! File . Exists ( globalDatabaseFile ) ) {
ASF . ArchiLogger . LogGenericInfo ( Strings . Welcome ) ;
await Task . Delay ( SharedInfo . InformationDelay ) . ConfigureAwait ( false ) ;
ASF . ArchiLogger . LogGenericWarning ( Strings . WarningPrivacyPolicy ) ;
await Task . Delay ( SharedInfo . ShortInformationDelay ) . ConfigureAwait ( false ) ;
}
2016-03-07 02:39:55 +01:00
2021-11-10 21:23:24 +01:00
GlobalDatabase ? globalDatabase = await GlobalDatabase . CreateOrLoad ( globalDatabaseFile ) . ConfigureAwait ( false ) ;
2019-01-17 20:43:45 +01:00
2021-11-10 21:23:24 +01:00
if ( globalDatabase = = null ) {
2024-08-05 02:37:50 +02:00
ASF . ArchiLogger . LogGenericError ( Strings . FormatErrorDatabaseInvalid ( globalDatabaseFile ) ) ;
2021-11-10 21:23:24 +01:00
await Task . Delay ( SharedInfo . ShortInformationDelay ) . ConfigureAwait ( false ) ;
2019-09-17 19:04:12 +02:00
2021-11-10 21:23:24 +01:00
return false ;
}
2018-04-18 01:04:28 +02:00
2021-11-10 21:23:24 +01:00
ASF . GlobalDatabase = globalDatabase ;
2020-06-06 22:06:32 +02:00
2021-11-10 21:23:24 +01:00
// If debugging is on, we prepare debug directory prior to running
if ( Debugging . IsUserDebugging ) {
Logging . EnableTraceLogging ( ) ;
if ( Debugging . IsDebugConfigured ) {
2024-04-04 21:21:58 +02:00
ASF . ArchiLogger . LogGenericDebug ( $"{globalDatabaseFile}: {globalDatabase.ToJsonText(true)}" ) ;
2021-11-10 21:23:24 +01:00
DebugLog . AddListener ( new Debugging . DebugListener ( ) ) ;
2024-04-04 21:21:58 +02:00
2021-11-10 21:23:24 +01:00
DebugLog . Enabled = true ;
2024-04-04 21:21:58 +02:00
try {
Directory . CreateDirectory ( ASF . DebugDirectory ) ;
} catch ( Exception e ) {
ASF . ArchiLogger . LogGenericException ( e ) ;
2018-04-18 01:04:28 +02:00
}
2021-11-10 21:23:24 +01:00
}
}
2018-04-18 01:04:28 +02:00
2021-11-10 21:23:24 +01:00
WebBrowser . Init ( ) ;
2020-11-19 21:08:50 +01:00
2021-11-10 21:23:24 +01:00
return true ;
}
2015-11-25 16:31:39 +01:00
Implement basic crash protection
- On start, we create/load crash file, if previous startup was less than 5 minutes ago, we also increase the counter
- If counter reaches 5, we abort the process, that is, freeze the process by not loading anything other than auto-updates
- In order for user to recover from above, he needs to manually remove ASF.crash file
- If process exits normally without reaching counter of 5 yet, we remove crash file (reset the counter), normal exit includes SIGTERM, SIGINT, clicking [X], !exit, !restart, and anything else that allows ASF to gracefully quit
- If process exits abnormally, that includes SIGKILL, unhandled exceptions, as well as power outages and anything that prevents ASF from graceful quit, we keep crash file around
- Update procedure, as an exception, allows crash file removal even with counter of 5. This should allow crash file recovery for people that crashed before, without a need of manual removal.
2024-02-03 21:18:47 +01:00
private static async Task < bool > InitShutdownSequence ( byte exitCode = 0 ) {
2021-11-10 21:23:24 +01:00
if ( ShutdownSequenceInitialized ) {
2022-12-29 23:53:35 +01:00
// We've already initialized shutdown sequence before, we won't allow the caller to init shutdown sequence again
// While normally this will be respected, caller might not have any say in this for example because it's the runtime terminating ASF due to fatal exception
// Because of that, the least we can still do, is to ensure that exception is written to any logs on our "best effort" basis
LogManager . Flush ( ) ;
2021-11-10 21:23:24 +01:00
return false ;
}
2016-11-24 07:32:16 +01:00
2021-11-10 21:23:24 +01:00
ShutdownSequenceInitialized = true ;
2016-11-24 07:32:16 +01:00
Implement basic crash protection
- On start, we create/load crash file, if previous startup was less than 5 minutes ago, we also increase the counter
- If counter reaches 5, we abort the process, that is, freeze the process by not loading anything other than auto-updates
- In order for user to recover from above, he needs to manually remove ASF.crash file
- If process exits normally without reaching counter of 5 yet, we remove crash file (reset the counter), normal exit includes SIGTERM, SIGINT, clicking [X], !exit, !restart, and anything else that allows ASF to gracefully quit
- If process exits abnormally, that includes SIGKILL, unhandled exceptions, as well as power outages and anything that prevents ASF from graceful quit, we keep crash file around
- Update procedure, as an exception, allows crash file removal even with counter of 5. This should allow crash file recovery for people that crashed before, without a need of manual removal.
2024-02-03 21:18:47 +01:00
// Unregister from registered signals
2021-11-10 21:23:24 +01:00
if ( OperatingSystem . IsFreeBSD ( ) | | OperatingSystem . IsLinux ( ) | | OperatingSystem . IsMacOS ( ) ) {
foreach ( PosixSignalRegistration registration in RegisteredPosixSignals . Values ) {
registration . Dispose ( ) ;
2021-11-10 18:40:12 +01:00
}
2021-11-10 21:23:24 +01:00
RegisteredPosixSignals . Clear ( ) ;
}
2021-11-10 18:40:12 +01:00
Implement basic crash protection
- On start, we create/load crash file, if previous startup was less than 5 minutes ago, we also increase the counter
- If counter reaches 5, we abort the process, that is, freeze the process by not loading anything other than auto-updates
- In order for user to recover from above, he needs to manually remove ASF.crash file
- If process exits normally without reaching counter of 5 yet, we remove crash file (reset the counter), normal exit includes SIGTERM, SIGINT, clicking [X], !exit, !restart, and anything else that allows ASF to gracefully quit
- If process exits abnormally, that includes SIGKILL, unhandled exceptions, as well as power outages and anything that prevents ASF from graceful quit, we keep crash file around
- Update procedure, as an exception, allows crash file removal even with counter of 5. This should allow crash file recovery for people that crashed before, without a need of manual removal.
2024-02-03 21:18:47 +01:00
// Remove crash file if allowed
if ( ( exitCode = = 0 ) & & AllowCrashFileRemoval ) {
string crashFile = ASF . GetFilePath ( ASF . EFileType . Crash ) ;
if ( File . Exists ( crashFile ) ) {
try {
File . Delete ( crashFile ) ;
} catch ( Exception e ) {
ASF . ArchiLogger . LogGenericException ( e ) ;
}
}
}
2021-11-10 21:23:24 +01:00
// Sockets created by IPC might still be running for a short while after complete app shutdown
// Ensure that IPC is stopped before we finalize shutdown sequence
await ArchiKestrel . Stop ( ) . ConfigureAwait ( false ) ;
2017-11-30 23:39:35 +01:00
2021-11-10 21:23:24 +01:00
// Stop all the active bots so they can disconnect cleanly
if ( Bot . Bots ? . Count > 0 ) {
// Stop() function can block due to SK2 sockets, don't forget a maximum delay
2024-04-04 02:15:16 +02:00
await Task . WhenAny ( Utilities . InParallel ( Bot . Bots . Values . Select ( static bot = > Task . Run ( ( ) = > bot . Stop ( true ) ) ) ) , Task . Delay ( ( Bot . Bots . Count + WebBrowser . MaxTries ) * 1000 ) ) . ConfigureAwait ( false ) ;
2017-04-27 01:57:04 +02:00
2021-11-10 21:23:24 +01:00
// Extra second for Steam requests to go through
await Task . Delay ( 1000 ) . ConfigureAwait ( false ) ;
}
2016-11-24 07:32:16 +01:00
2021-11-10 21:23:24 +01:00
// Flush all the pending writes to log files
LogManager . Flush ( ) ;
2018-12-15 00:27:15 +01:00
2021-11-10 21:23:24 +01:00
// Unregister the process from single instancing
OS . UnregisterProcess ( ) ;
2019-03-29 23:07:06 +01:00
2021-11-10 21:23:24 +01:00
return true ;
}
2016-11-24 07:32:16 +01:00
2021-11-10 21:23:24 +01:00
private static async Task < int > Main ( string [ ] args ) {
2021-12-12 01:12:54 +01:00
ArgumentNullException . ThrowIfNull ( args ) ;
2021-07-26 00:19:09 +02:00
2021-11-10 21:23:24 +01:00
// Initialize
await Init ( args . Length > 0 ? args : null ) . ConfigureAwait ( false ) ;
2017-06-26 03:36:51 +02:00
2021-11-10 21:23:24 +01:00
// Wait for shutdown event
return await ShutdownResetEvent . Task . ConfigureAwait ( false ) ;
}
2016-11-24 07:32:16 +01:00
Implement basic crash protection
- On start, we create/load crash file, if previous startup was less than 5 minutes ago, we also increase the counter
- If counter reaches 5, we abort the process, that is, freeze the process by not loading anything other than auto-updates
- In order for user to recover from above, he needs to manually remove ASF.crash file
- If process exits normally without reaching counter of 5 yet, we remove crash file (reset the counter), normal exit includes SIGTERM, SIGINT, clicking [X], !exit, !restart, and anything else that allows ASF to gracefully quit
- If process exits abnormally, that includes SIGKILL, unhandled exceptions, as well as power outages and anything that prevents ASF from graceful quit, we keep crash file around
- Update procedure, as an exception, allows crash file removal even with counter of 5. This should allow crash file recovery for people that crashed before, without a need of manual removal.
2024-02-03 21:18:47 +01:00
private static async void OnCancelKeyPress ( object? sender , ConsoleCancelEventArgs e ) = > await Exit ( ) . ConfigureAwait ( false ) ;
2021-11-01 18:55:58 +01:00
2021-11-10 21:23:24 +01:00
private static async void OnPosixSignal ( PosixSignalContext signal ) {
2021-12-12 01:12:54 +01:00
ArgumentNullException . ThrowIfNull ( signal ) ;
2021-11-10 18:40:12 +01:00
2021-11-10 21:23:24 +01:00
switch ( signal . Signal ) {
case PosixSignal . SIGINT :
case PosixSignal . SIGTERM :
await Exit ( ) . ConfigureAwait ( false ) ;
2021-11-10 18:40:12 +01:00
2021-11-10 21:23:24 +01:00
break ;
2022-06-04 22:46:37 +02:00
default :
throw new InvalidOperationException ( nameof ( signal . Signal ) ) ;
2021-11-10 18:40:12 +01:00
}
2021-11-10 21:23:24 +01:00
}
2021-11-10 18:40:12 +01:00
2021-11-10 21:23:24 +01:00
private static async void OnProcessExit ( object? sender , EventArgs e ) = > await Shutdown ( ) . ConfigureAwait ( false ) ;
2017-07-10 17:07:48 +02:00
2021-11-10 21:23:24 +01:00
private static async void OnUnhandledException ( object? sender , UnhandledExceptionEventArgs e ) {
2021-12-12 01:12:54 +01:00
ArgumentNullException . ThrowIfNull ( e ) ;
2022-05-25 17:18:06 +02:00
ArgumentNullException . ThrowIfNull ( e . ExceptionObject ) ;
2017-07-10 17:07:48 +02:00
2021-11-10 21:23:24 +01:00
await ASF . ArchiLogger . LogFatalException ( ( Exception ) e . ExceptionObject ) . ConfigureAwait ( false ) ;
2024-10-04 14:32:42 +02:00
if ( e . IsTerminating ) {
await Exit ( 1 ) . ConfigureAwait ( false ) ;
}
2021-11-10 21:23:24 +01:00
}
private static async void OnUnobservedTaskException ( object? sender , UnobservedTaskExceptionEventArgs e ) {
2021-12-12 01:12:54 +01:00
ArgumentNullException . ThrowIfNull ( e ) ;
2022-05-25 17:18:06 +02:00
ArgumentNullException . ThrowIfNull ( e . Exception ) ;
2020-08-23 20:45:24 +02:00
2024-10-04 14:27:02 +02:00
// TODO: Remove conditionally ignoring exceptions once reports are resolved
// https://github.com/dotnet/runtime/issues/80111
// https://github.com/dotnet/runtime/issues/102772
bool ignored = e . Exception . InnerExceptions . Any ( static exception = > exception is HttpIOException or QuicException ) ;
if ( ! ignored ) {
2023-01-13 10:40:37 +01:00
await ASF . ArchiLogger . LogFatalException ( e . Exception ) . ConfigureAwait ( false ) ;
}
2017-07-10 17:07:48 +02:00
2021-11-23 10:47:33 +01:00
// Normally we should abort the application, but due to the fact that unobserved exceptions do not have to do that, it's a better idea to log it and try to continue
2021-11-10 21:23:24 +01:00
e . SetObserved ( ) ;
}
2018-06-24 14:41:19 +02:00
2022-09-08 19:53:36 +02:00
private static async Task < bool > ParseArgs ( IReadOnlyCollection < string > args ) {
2021-11-10 21:23:24 +01:00
if ( ( args = = null ) | | ( args . Count = = 0 ) ) {
throw new ArgumentNullException ( nameof ( args ) ) ;
2017-07-10 17:07:48 +02:00
}
2021-11-10 21:23:24 +01:00
bool cryptKeyNext = false ;
2022-09-08 19:53:36 +02:00
bool cryptKeyFileNext = false ;
2021-11-10 21:23:24 +01:00
bool networkGroupNext = false ;
bool pathNext = false ;
foreach ( string arg in args ) {
switch ( arg . ToUpperInvariant ( ) ) {
2022-10-07 19:32:58 +02:00
case "--CRYPTKEY" when noArgumentValueNext ( ) :
2021-11-10 21:23:24 +01:00
cryptKeyNext = true ;
break ;
2022-10-07 19:32:58 +02:00
case "--CRYPTKEY-FILE" when noArgumentValueNext ( ) :
2022-09-08 19:53:36 +02:00
cryptKeyFileNext = true ;
break ;
2022-10-07 19:32:58 +02:00
case "--IGNORE-UNSUPPORTED-ENVIRONMENT" when noArgumentValueNext ( ) :
2021-11-10 21:23:24 +01:00
IgnoreUnsupportedEnvironment = true ;
break ;
2022-10-07 19:32:58 +02:00
case "--INPUT-CRYPTKEY" when noArgumentValueNext ( ) :
InputCryptkeyManually = true ;
2023-02-08 16:11:50 +01:00
break ;
case "--MINIMIZED" when noArgumentValueNext ( ) :
Minimized = true ;
2022-10-07 19:32:58 +02:00
break ;
case "--NETWORK-GROUP" when noArgumentValueNext ( ) :
2021-11-10 21:23:24 +01:00
networkGroupNext = true ;
break ;
2022-10-07 19:32:58 +02:00
case "--NO-CONFIG-MIGRATE" when noArgumentValueNext ( ) :
2021-11-10 21:23:24 +01:00
ConfigMigrate = false ;
break ;
2022-10-07 19:32:58 +02:00
case "--NO-CONFIG-WATCH" when noArgumentValueNext ( ) :
2021-11-10 21:23:24 +01:00
ConfigWatch = false ;
break ;
2022-10-07 19:32:58 +02:00
case "--NO-RESTART" when noArgumentValueNext ( ) :
2021-11-10 21:23:24 +01:00
RestartAllowed = false ;
2022-04-06 14:16:26 +02:00
break ;
2022-10-07 19:32:58 +02:00
case "--NO-STEAM-PARENTAL-GENERATION" when noArgumentValueNext ( ) :
2022-04-06 14:16:26 +02:00
SteamParentalGeneration = false ;
2021-11-10 21:23:24 +01:00
break ;
2022-10-07 19:32:58 +02:00
case "--PATH" when noArgumentValueNext ( ) :
2021-11-10 21:23:24 +01:00
pathNext = true ;
2016-07-24 00:36:15 +02:00
2021-11-10 21:23:24 +01:00
break ;
2022-10-07 19:32:58 +02:00
case "--SERVICE" when noArgumentValueNext ( ) :
2021-11-10 21:23:24 +01:00
Service = true ;
break ;
2022-10-07 19:32:58 +02:00
case "--SYSTEM-REQUIRED" when noArgumentValueNext ( ) :
2021-11-10 21:23:24 +01:00
SystemRequired = true ;
break ;
default :
if ( cryptKeyNext ) {
cryptKeyNext = false ;
HandleCryptKeyArgument ( arg ) ;
2022-09-08 19:53:36 +02:00
} else if ( cryptKeyFileNext ) {
cryptKeyFileNext = false ;
if ( ! await HandleCryptKeyFileArgument ( arg ) . ConfigureAwait ( false ) ) {
return false ;
}
2021-11-10 21:23:24 +01:00
} else if ( networkGroupNext ) {
networkGroupNext = false ;
HandleNetworkGroupArgument ( arg ) ;
} else if ( pathNext ) {
pathNext = false ;
2021-12-04 02:33:23 +01:00
if ( ! HandlePathArgument ( arg ) ) {
return false ;
}
2021-11-10 21:23:24 +01:00
} else {
switch ( arg . Length ) {
2022-09-08 19:53:36 +02:00
case > 16 when arg . StartsWith ( "--CRYPTKEY-FILE=" , StringComparison . OrdinalIgnoreCase ) :
if ( ! await HandleCryptKeyFileArgument ( arg [ 16. . ] ) . ConfigureAwait ( false ) ) {
return false ;
}
break ;
2021-11-10 21:23:24 +01:00
case > 16 when arg . StartsWith ( "--NETWORK-GROUP=" , StringComparison . OrdinalIgnoreCase ) :
HandleNetworkGroupArgument ( arg [ 16. . ] ) ;
break ;
case > 11 when arg . StartsWith ( "--CRYPTKEY=" , StringComparison . OrdinalIgnoreCase ) :
HandleCryptKeyArgument ( arg [ 11. . ] ) ;
break ;
case > 7 when arg . StartsWith ( "--PATH=" , StringComparison . OrdinalIgnoreCase ) :
2021-12-04 02:33:23 +01:00
if ( ! HandlePathArgument ( arg [ 7. . ] ) ) {
return false ;
}
2021-11-10 21:23:24 +01:00
break ;
default :
2024-08-05 02:37:50 +02:00
ASF . ArchiLogger . LogGenericWarning ( Strings . FormatWarningUnknownCommandLineArgument ( arg ) ) ;
2021-11-10 21:23:24 +01:00
break ;
2016-01-03 20:36:13 +01:00
}
2021-11-10 21:23:24 +01:00
}
2016-01-03 20:36:13 +01:00
2021-11-10 21:23:24 +01:00
break ;
2016-01-03 20:36:13 +01:00
}
}
2021-12-04 02:33:23 +01:00
return true ;
2023-08-10 21:36:17 +02:00
bool noArgumentValueNext ( ) = > ! cryptKeyNext & & ! cryptKeyFileNext & & ! networkGroupNext & & ! pathNext ;
2021-11-10 21:23:24 +01:00
}
2016-01-03 20:36:13 +01:00
2022-09-08 19:53:36 +02:00
private static async Task < bool > ParseEnvironmentVariables ( ) {
2021-11-10 21:23:24 +01:00
// We're using a single try-catch block here, as a failure for getting one variable will result in the same failure for all other ones
try {
2023-11-24 10:34:20 +01:00
string? envPath = Environment . GetEnvironmentVariable ( SharedInfo . EnvironmentVariablePath ) ;
if ( ! string . IsNullOrEmpty ( envPath ) ) {
if ( ! HandlePathArgument ( envPath ) ) {
return false ;
}
}
2021-11-10 21:23:24 +01:00
string? envCryptKey = Environment . GetEnvironmentVariable ( SharedInfo . EnvironmentVariableCryptKey ) ;
2021-07-26 00:19:09 +02:00
2021-11-10 21:23:24 +01:00
if ( ! string . IsNullOrEmpty ( envCryptKey ) ) {
2023-11-14 20:01:29 +01:00
HandleCryptKeyArgument ( envCryptKey ) ;
2021-11-10 21:23:24 +01:00
}
2021-07-26 00:19:09 +02:00
2022-09-08 19:53:36 +02:00
string? envCryptKeyFile = Environment . GetEnvironmentVariable ( SharedInfo . EnvironmentVariableCryptKeyFile ) ;
if ( ! string . IsNullOrEmpty ( envCryptKeyFile ) ) {
2023-11-14 20:01:29 +01:00
if ( ! await HandleCryptKeyFileArgument ( envCryptKeyFile ) . ConfigureAwait ( false ) ) {
2022-09-08 19:53:36 +02:00
return false ;
}
}
2021-11-10 21:23:24 +01:00
string? envNetworkGroup = Environment . GetEnvironmentVariable ( SharedInfo . EnvironmentVariableNetworkGroup ) ;
2021-07-26 00:19:09 +02:00
2021-11-10 21:23:24 +01:00
if ( ! string . IsNullOrEmpty ( envNetworkGroup ) ) {
2023-11-14 20:01:29 +01:00
HandleNetworkGroupArgument ( envNetworkGroup ) ;
2021-11-10 21:23:24 +01:00
}
} catch ( Exception e ) {
ASF . ArchiLogger . LogGenericException ( e ) ;
2021-12-04 02:33:23 +01:00
return false ;
2021-07-26 00:19:09 +02:00
}
2021-12-04 02:33:23 +01:00
return true ;
2021-11-10 21:23:24 +01:00
}
2021-07-26 00:19:09 +02:00
2021-11-10 21:23:24 +01:00
private static async Task Shutdown ( byte exitCode = 0 ) {
Implement basic crash protection
- On start, we create/load crash file, if previous startup was less than 5 minutes ago, we also increase the counter
- If counter reaches 5, we abort the process, that is, freeze the process by not loading anything other than auto-updates
- In order for user to recover from above, he needs to manually remove ASF.crash file
- If process exits normally without reaching counter of 5 yet, we remove crash file (reset the counter), normal exit includes SIGTERM, SIGINT, clicking [X], !exit, !restart, and anything else that allows ASF to gracefully quit
- If process exits abnormally, that includes SIGKILL, unhandled exceptions, as well as power outages and anything that prevents ASF from graceful quit, we keep crash file around
- Update procedure, as an exception, allows crash file removal even with counter of 5. This should allow crash file recovery for people that crashed before, without a need of manual removal.
2024-02-03 21:18:47 +01:00
if ( ! await InitShutdownSequence ( exitCode ) . ConfigureAwait ( false ) ) {
2021-11-10 21:23:24 +01:00
return ;
2017-01-06 16:29:34 +01:00
}
2021-11-10 21:23:24 +01:00
ShutdownResetEvent . TrySetResult ( exitCode ) ;
2016-06-28 06:58:59 +02:00
}
2018-07-31 16:17:24 +02:00
}