2019-02-16 17:34:17 +01:00
// _ _ _ ____ _ _____
2017-11-18 17:27:06 +01:00
// / \ _ __ ___ | |__ (_)/ ___| | |_ ___ __ _ _ __ ___ | ___|__ _ _ __ _ __ ___
// / _ \ | '__|/ __|| '_ \ | |\___ \ | __|/ _ \ / _` || '_ ` _ \ | |_ / _` || '__|| '_ ` _ \
// / ___ \ | | | (__ | | | || | ___) || |_| __/| (_| || | | | | || _|| (_| || | | | | | | |
// /_/ \_\|_| \___||_| |_||_||____/ \__|\___| \__,_||_| |_| |_||_| \__,_||_| |_| |_| |_|
2019-01-14 19:11:17 +01:00
// |
2021-01-03 22:24:22 +01:00
// Copyright 2015-2021 Łukasz "JustArchi" Domeradzki
2018-07-27 04:52:14 +02:00
// Contact: JustArchi@JustArchi.net
2019-01-14 19:11:17 +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
2019-01-14 19:11:17 +01:00
// |
2018-07-27 04:52:14 +02:00
// http://www.apache.org/licenses/LICENSE-2.0
2019-01-14 19:11:17 +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.
2017-02-05 13:52:00 +01:00
using System ;
2019-12-08 00:32:59 +01:00
using System.Diagnostics ;
2020-11-14 22:37:00 +01:00
using System.Globalization ;
2017-08-02 20:47:53 +02:00
using System.IO ;
2017-02-05 13:52:00 +01:00
using System.Runtime.InteropServices ;
2020-05-22 00:04:04 +02:00
using System.Security.Cryptography ;
2019-03-29 23:07:06 +01:00
using System.Text ;
2018-08-17 19:20:27 +02:00
using System.Text.RegularExpressions ;
2019-03-29 23:07:06 +01:00
using System.Threading ;
2020-11-26 18:22:55 +01:00
using System.Threading.Tasks ;
2017-02-05 13:52:00 +01:00
using ArchiSteamFarm.Localization ;
namespace ArchiSteamFarm {
internal static class OS {
2019-12-08 00:32:59 +01:00
// We need to keep this one assigned and not calculated on-demand
2020-11-28 23:16:03 +01:00
internal static readonly string ProcessFileName = Process . GetCurrentProcess ( ) . MainModule ? . FileName ? ? throw new InvalidOperationException ( nameof ( ProcessFileName ) ) ;
2019-12-08 00:32:59 +01:00
2018-06-16 08:44:36 +02:00
internal static string Variant = > RuntimeInformation . OSDescription . Trim ( ) ;
2018-05-11 23:20:25 +02:00
2020-08-22 21:41:01 +02:00
private static Mutex ? SingleInstance ;
2019-03-29 23:07:06 +01:00
2020-11-19 21:01:17 +01:00
internal static void CoreInit ( bool systemRequired ) {
if ( RuntimeInformation . IsOSPlatform ( OSPlatform . Windows ) ) {
if ( systemRequired ) {
WindowsKeepSystemActive ( ) ;
}
if ( ! Console . IsOutputRedirected ) {
// Normally we should use UTF-8 encoding as it's the most correct one for our case, and we already use it on other OSes such as Linux
// However, older Windows versions, mainly 7/8.1 can't into UTF-8 without appropriate console font, and expecting from users to change it manually is unwanted
// As irrational as it can sound, those versions actually can work with unicode encoding instead, as they magically map it into proper chars despite of incorrect font
// We could in theory conditionally use UTF-8 for Windows 10+ and unicode otherwise, but Windows version detection is simply not worth the hassle in this case
// Therefore, until we can drop support for Windows < 10, we'll stick with Unicode for all Windows boxes, unless there will be valid reasoning for conditional switch
// See https://github.com/JustArchiNET/ArchiSteamFarm/issues/1289 for more details
Console . OutputEncoding = Encoding . Unicode ;
// Quick edit mode will freeze when user start selecting something on the console until the selection is cancelled
// Users are very often doing it accidentally without any real purpose, and we want to avoid this common issue which causes the whole process to hang
// See http://stackoverflow.com/questions/30418886/how-and-why-does-quickedit-mode-in-command-prompt-freeze-applications for more details
WindowsDisableQuickEditMode ( ) ;
}
2019-06-02 00:05:06 +02:00
}
}
2017-03-07 03:47:56 +01:00
2020-12-18 22:06:15 +01:00
internal static string GetOsResourceName ( string objectName ) {
2020-05-20 22:01:29 +02:00
if ( string . IsNullOrEmpty ( objectName ) ) {
2020-08-22 21:41:01 +02:00
throw new ArgumentNullException ( nameof ( objectName ) ) ;
2020-05-20 22:01:29 +02:00
}
2020-12-18 22:06:15 +01:00
return SharedInfo . AssemblyName + "-" + objectName ;
2020-05-20 22:01:29 +02:00
}
2020-11-19 21:01:17 +01:00
internal static void Init ( GlobalConfig . EOptimizationMode optimizationMode ) {
if ( ! Enum . IsDefined ( typeof ( GlobalConfig . EOptimizationMode ) , optimizationMode ) ) {
throw new ArgumentNullException ( nameof ( optimizationMode ) ) ;
2017-02-05 13:52:00 +01:00
}
2018-08-17 19:20:27 +02:00
switch ( optimizationMode ) {
case GlobalConfig . EOptimizationMode . MaxPerformance :
// No specific tuning required for now, ASF is optimized for max performance by default
break ;
case GlobalConfig . EOptimizationMode . MinMemoryUsage :
// We can disable regex cache which will slightly lower memory usage (for a huge performance hit)
Regex . CacheSize = 0 ;
2018-12-15 00:27:15 +01:00
2018-08-17 19:20:27 +02:00
break ;
default :
2020-08-22 21:41:01 +02:00
throw new ArgumentOutOfRangeException ( nameof ( optimizationMode ) ) ;
2018-08-17 19:20:27 +02:00
}
2017-02-05 13:52:00 +01:00
}
2020-11-26 18:22:55 +01:00
internal static async Task < bool > RegisterProcess ( ) {
2019-03-29 23:07:06 +01:00
if ( SingleInstance ! = null ) {
return false ;
}
2020-05-22 00:04:04 +02:00
string uniqueName ;
// The only purpose of using hashingAlgorithm here is to cut on a potential size of the resource name - paths can be really long, and we almost certainly have some upper limit on the resource name we can allocate
// At the same time it'd be the best if we avoided all special characters, such as '/' found e.g. in base64, as we can't be sure that it's not a prohibited character in regards to native OS implementation
2020-11-14 22:37:00 +01:00
// Because of that, SHA256 is sufficient for our case, as it generates alphanumeric characters only, and is barely 256-bit long. We don't need any kind of complex cryptography or collision detection here, any hashing algorithm will do, and the shorter the better
using ( SHA256CryptoServiceProvider hashingAlgorithm = new ( ) ) {
2020-05-22 00:04:04 +02:00
uniqueName = "Global\\" + GetOsResourceName ( nameof ( SingleInstance ) ) + "-" + BitConverter . ToString ( hashingAlgorithm . ComputeHash ( Encoding . UTF8 . GetBytes ( Directory . GetCurrentDirectory ( ) ) ) ) . Replace ( "-" , "" ) ;
}
2019-03-29 23:07:06 +01:00
2020-11-26 18:22:55 +01:00
Mutex ? singleInstance = null ;
for ( byte i = 0 ; ( i < WebBrowser . MaxTries ) & & ( singleInstance = = null ) ; i + + ) {
2020-11-26 18:24:31 +01:00
if ( i > 0 ) {
await Task . Delay ( 1000 ) . ConfigureAwait ( false ) ;
}
2020-11-26 18:22:55 +01:00
singleInstance = new Mutex ( true , uniqueName , out bool result ) ;
if ( result ) {
break ;
}
2019-03-29 23:07:06 +01:00
2019-06-18 14:48:06 +02:00
singleInstance . Dispose ( ) ;
2020-11-26 18:22:55 +01:00
singleInstance = null ;
}
2019-06-18 14:48:06 +02:00
2020-11-26 18:22:55 +01:00
if ( singleInstance = = null ) {
2019-03-29 23:07:06 +01:00
return false ;
}
SingleInstance = singleInstance ;
return true ;
}
2020-05-21 23:02:04 +02:00
internal static void UnixSetFileAccess ( string path , EUnixPermission permission ) {
2020-05-24 00:17:36 +02:00
if ( string . IsNullOrEmpty ( path ) ) {
2020-08-22 21:41:01 +02:00
throw new ArgumentNullException ( nameof ( path ) ) ;
2017-08-02 20:47:53 +02:00
}
2020-11-10 23:22:57 +01:00
if ( ! RuntimeInformation . IsOSPlatform ( OSPlatform . Linux ) & & ! RuntimeInformation . IsOSPlatform ( OSPlatform . OSX ) ) {
2019-12-08 00:26:41 +01:00
return ;
}
2020-05-24 00:17:36 +02:00
if ( ! File . Exists ( path ) & & ! Directory . Exists ( path ) ) {
2020-11-14 22:37:00 +01:00
ASF . ArchiLogger . LogGenericError ( string . Format ( CultureInfo . CurrentCulture , Strings . WarningFailedWithError , "!" + nameof ( path ) ) ) ;
2020-05-24 00:17:36 +02:00
return ;
}
2017-08-31 07:30:09 +02:00
// Chmod() returns 0 on success, -1 on failure
2020-05-21 23:02:04 +02:00
if ( NativeMethods . Chmod ( path , ( int ) permission ) ! = 0 ) {
2020-11-14 22:37:00 +01:00
ASF . ArchiLogger . LogGenericError ( string . Format ( CultureInfo . CurrentCulture , Strings . WarningFailedWithError , Marshal . GetLastWin32Error ( ) ) ) ;
2017-08-02 20:47:53 +02:00
}
}
2019-03-29 23:07:06 +01:00
internal static void UnregisterProcess ( ) {
if ( SingleInstance = = null ) {
return ;
}
2019-03-30 21:00:41 +01:00
// We should release the mutex here, but that can be done only from the same thread due to thread affinity
// Instead, we'll dispose the mutex which should automatically release it by the CLR
2019-03-29 23:07:06 +01:00
SingleInstance . Dispose ( ) ;
SingleInstance = null ;
}
2020-11-19 21:04:45 +01:00
internal static bool VerifyEnvironment ( ) {
#if NETFRAMEWORK
// This is .NET Framework build, we support that one only on mono for platforms not supported by .NET Core
// We're not going to analyze source builds, as we don't know what changes the author has made, assume they have a point
if ( SharedInfo . BuildInfo . IsCustomBuild ) {
return true ;
}
// All windows variants have valid .NET Core build, and generic-netf is supported only on mono
if ( RuntimeInformation . IsOSPlatform ( OSPlatform . Windows ) | | ! RuntimeCompatibility . IsRunningOnMono ) {
return false ;
}
return RuntimeInformation . OSArchitecture switch {
// Sadly we can't tell a difference between ARMv6 and ARMv7 reliably, we'll believe that this linux-arm user knows what he's doing and he's indeed in need of generic-netf on ARMv6
Architecture . Arm = > true ,
// Apart from real x86, this also covers all unknown architectures, such as sparc, ppc64, and anything else Mono might support, we're fine with that
Architecture . X86 = > true ,
// Everything else is covered by .NET Core
_ = > false
} ;
#else
// This is .NET Core build, we support all scenarios
return true ;
#endif
}
2019-12-08 00:26:41 +01:00
private static void WindowsDisableQuickEditMode ( ) {
2020-11-10 23:22:57 +01:00
if ( ! RuntimeInformation . IsOSPlatform ( OSPlatform . Windows ) ) {
2019-12-08 00:26:41 +01:00
return ;
}
2017-03-05 17:41:57 +01:00
IntPtr consoleHandle = NativeMethods . GetStdHandle ( NativeMethods . StandardInputHandle ) ;
2017-03-14 08:41:33 -03:00
if ( ! NativeMethods . GetConsoleMode ( consoleHandle , out uint consoleMode ) ) {
2017-03-05 17:41:57 +01:00
ASF . ArchiLogger . LogGenericError ( Strings . WarningFailed ) ;
2018-12-15 00:27:15 +01:00
2017-03-05 17:41:57 +01:00
return ;
}
consoleMode & = ~ NativeMethods . EnableQuickEditMode ;
if ( ! NativeMethods . SetConsoleMode ( consoleHandle , consoleMode ) ) {
ASF . ArchiLogger . LogGenericError ( Strings . WarningFailed ) ;
}
}
2019-12-08 00:26:41 +01:00
private static void WindowsKeepSystemActive ( ) {
2020-11-10 23:22:57 +01:00
if ( ! RuntimeInformation . IsOSPlatform ( OSPlatform . Windows ) ) {
2019-12-08 00:26:41 +01:00
return ;
}
2017-02-05 13:52:00 +01:00
// This function calls unmanaged API in order to tell Windows OS that it should not enter sleep state while the program is running
// If user wishes to enter sleep mode, then he should use ShutdownOnFarmingFinished or manage ASF process with third-party tool or script
2019-12-08 00:26:41 +01:00
// See https://docs.microsoft.com/windows/win32/api/winbase/nf-winbase-setthreadexecutionstate for more details
2017-08-31 07:30:09 +02:00
NativeMethods . EExecutionState result = NativeMethods . SetThreadExecutionState ( NativeMethods . AwakeExecutionState ) ;
2017-02-05 13:52:00 +01:00
2019-06-18 14:48:06 +02:00
// SetThreadExecutionState() returns NULL on failure, which is mapped to 0 (EExecutionState.None) in our case
if ( result = = NativeMethods . EExecutionState . None ) {
2020-11-14 22:37:00 +01:00
ASF . ArchiLogger . LogGenericError ( string . Format ( CultureInfo . CurrentCulture , Strings . WarningFailedWithError , result ) ) ;
2017-02-05 13:52:00 +01:00
}
}
2020-05-21 23:02:04 +02:00
[Flags]
internal enum EUnixPermission : ushort {
OtherExecute = 0x1 ,
OtherWrite = 0x2 ,
OtherRead = 0x4 ,
GroupExecute = 0x8 ,
GroupWrite = 0x10 ,
GroupRead = 0x20 ,
UserExecute = 0x40 ,
UserWrite = 0x80 ,
UserRead = 0x100 ,
Combined755 = UserRead | UserWrite | UserExecute | GroupRead | GroupExecute | OtherRead | OtherExecute ,
Combined777 = UserRead | UserWrite | UserExecute | GroupRead | GroupWrite | GroupExecute | OtherRead | OtherWrite | OtherExecute
}
2017-02-27 20:05:02 +01:00
private static class NativeMethods {
2017-08-31 07:30:09 +02:00
internal const EExecutionState AwakeExecutionState = EExecutionState . SystemRequired | EExecutionState . AwayModeRequired | EExecutionState . Continuous ;
2017-03-05 17:41:57 +01:00
internal const uint EnableQuickEditMode = 0x0040 ;
2017-07-13 06:08:52 +02:00
internal const sbyte StandardInputHandle = - 10 ;
2017-03-05 17:41:57 +01:00
2020-11-17 14:13:36 +01:00
#pragma warning disable CA2101
[DllImport("libc", EntryPoint = "chmod", SetLastError = true)]
2017-08-31 07:30:09 +02:00
internal static extern int Chmod ( string path , int mode ) ;
2020-11-17 14:13:36 +01:00
#pragma warning restore CA2101
2017-08-31 07:30:09 +02:00
[DllImport("kernel32.dll")]
2017-03-05 17:41:57 +01:00
internal static extern bool GetConsoleMode ( IntPtr hConsoleHandle , out uint lpMode ) ;
2017-08-31 07:30:09 +02:00
[DllImport("kernel32.dll")]
2017-03-05 17:41:57 +01:00
internal static extern IntPtr GetStdHandle ( int nStdHandle ) ;
2017-08-31 07:30:09 +02:00
[DllImport("kernel32.dll")]
2017-03-05 17:41:57 +01:00
internal static extern bool SetConsoleMode ( IntPtr hConsoleHandle , uint dwMode ) ;
2017-08-31 07:30:09 +02:00
[DllImport("kernel32.dll")]
2017-02-27 20:05:02 +01:00
internal static extern EExecutionState SetThreadExecutionState ( EExecutionState executionState ) ;
2017-02-05 13:52:00 +01:00
2017-02-27 20:05:02 +01:00
[Flags]
internal enum EExecutionState : uint {
2019-06-18 14:48:06 +02:00
None = 0 ,
2017-02-27 20:05:02 +01:00
SystemRequired = 0x00000001 ,
AwayModeRequired = 0x00000040 ,
Continuous = 0x80000000
}
2017-02-05 13:52:00 +01:00
}
}
2018-08-17 19:20:27 +02:00
}