2019-02-16 17:34:17 +01:00
// _ _ _ ____ _ _____
2017-11-18 17:27:06 +01:00
// / \ _ __ ___ | |__ (_)/ ___| | |_ ___ __ _ _ __ ___ | ___|__ _ _ __ _ __ ___
// / _ \ | '__|/ __|| '_ \ | |\___ \ | __|/ _ \ / _` || '_ ` _ \ | |_ / _` || '__|| '_ ` _ \
// / ___ \ | | | (__ | | | || | ___) || |_| __/| (_| || | | | | || _|| (_| || | | | | | | |
// /_/ \_\|_| \___||_| |_||_||____/ \__|\___| \__,_||_| |_| |_||_| \__,_||_| |_| |_| |_|
2019-01-14 19:11:17 +01:00
// |
2020-02-01 23:33:35 +01:00
// Copyright 2015-2020 Ł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 ;
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-05-20 22:01:29 +02:00
using ArchiSteamFarm.Helpers ;
2017-02-05 13:52:00 +01:00
using ArchiSteamFarm.Localization ;
2019-01-10 23:44:32 +01:00
using JetBrains.Annotations ;
2017-02-05 13:52:00 +01:00
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
internal static readonly string ProcessFileName = Process . GetCurrentProcess ( ) . MainModule ? . FileName ? ? throw new ArgumentNullException ( nameof ( ProcessFileName ) ) ;
2018-05-11 23:20:25 +02:00
internal static bool IsUnix = > RuntimeInformation . IsOSPlatform ( OSPlatform . Linux ) | | RuntimeInformation . IsOSPlatform ( OSPlatform . OSX ) ;
2019-01-10 23:44:32 +01:00
[NotNull]
2018-06-16 08:44:36 +02:00
internal static string Variant = > RuntimeInformation . OSDescription . Trim ( ) ;
2018-05-11 23:20:25 +02:00
2019-12-08 00:26:41 +01:00
private static bool IsWindows = > RuntimeInformation . IsOSPlatform ( OSPlatform . Windows ) ;
2019-03-29 23:07:06 +01:00
private static Mutex SingleInstance ;
2019-06-02 00:05:06 +02:00
internal static void CoreInit ( ) {
2019-12-08 00:26:41 +01:00
if ( IsWindows & & ! Console . IsOutputRedirected ) {
2019-06-23 01:06:11 +02:00
// 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 ;
2019-06-02 14:39:16 +02:00
2019-12-08 00:26:41 +01:00
// 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-05-20 22:01:29 +02:00
internal static ICrossProcessSemaphore CreateCrossProcessSemaphore ( string objectName ) {
if ( string . IsNullOrEmpty ( objectName ) ) {
ASF . ArchiLogger . LogNullError ( nameof ( objectName ) ) ;
return null ;
}
string resourceName = GetOsResourceName ( objectName ) ;
2020-06-15 23:28:52 +02:00
return new CrossProcessFileBasedSemaphore ( resourceName ) ;
2020-05-20 22:01:29 +02:00
}
2019-06-02 00:05:06 +02:00
internal static void Init ( bool systemRequired , GlobalConfig . EOptimizationMode optimizationMode ) {
2019-06-18 14:48:06 +02:00
if ( ! Enum . IsDefined ( typeof ( GlobalConfig . EOptimizationMode ) , optimizationMode ) ) {
ASF . ArchiLogger . LogNullError ( nameof ( optimizationMode ) ) ;
return ;
}
2019-12-08 00:26:41 +01:00
if ( IsWindows ) {
2018-04-18 01:04:28 +02:00
if ( systemRequired ) {
2019-12-08 00:26:41 +01:00
WindowsKeepSystemActive ( ) ;
2018-03-10 14:47:20 +01:00
}
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 :
ASF . ArchiLogger . LogGenericError ( string . Format ( Strings . WarningUnknownValuePleaseReport , nameof ( optimizationMode ) , optimizationMode ) ) ;
2018-12-15 00:27:15 +01:00
2018-08-17 19:20:27 +02:00
return ;
}
2017-02-05 13:52:00 +01:00
}
2019-03-29 23:07:06 +01:00
internal static bool RegisterProcess ( ) {
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
// Because of that, MD5 is sufficient for our case, as it generates alphanumeric characters only, and is barely 128-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 ( MD5 hashingAlgorithm = MD5 . Create ( ) ) {
uniqueName = "Global\\" + GetOsResourceName ( nameof ( SingleInstance ) ) + "-" + BitConverter . ToString ( hashingAlgorithm . ComputeHash ( Encoding . UTF8 . GetBytes ( Directory . GetCurrentDirectory ( ) ) ) ) . Replace ( "-" , "" ) ;
}
2019-03-29 23:07:06 +01:00
Mutex singleInstance = new Mutex ( true , uniqueName , out bool result ) ;
if ( ! result ) {
2019-06-18 14:48:06 +02:00
singleInstance . Dispose ( ) ;
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 ) ) {
2017-09-24 13:05:53 +02:00
ASF . ArchiLogger . LogNullError ( nameof ( path ) ) ;
2018-12-15 00:27:15 +01:00
2017-08-02 20:47:53 +02:00
return ;
}
2019-12-08 00:26:41 +01:00
if ( ! IsUnix ) {
return ;
}
2020-05-24 00:17:36 +02:00
if ( ! File . Exists ( path ) & & ! Directory . Exists ( path ) ) {
ASF . ArchiLogger . LogGenericError ( string . Format ( Strings . WarningFailedWithError , "!" + nameof ( path ) ) ) ;
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 ) {
2017-08-31 07:30:09 +02:00
ASF . ArchiLogger . LogGenericError ( string . Format ( 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-05-20 22:01:29 +02:00
private static string GetOsResourceName ( string objectName ) {
if ( string . IsNullOrEmpty ( objectName ) ) {
ASF . ArchiLogger . LogNullError ( nameof ( objectName ) ) ;
return null ;
}
return SharedInfo . AssemblyName + "-" + objectName ;
}
2019-12-08 00:26:41 +01:00
private static void WindowsDisableQuickEditMode ( ) {
if ( ! IsWindows ) {
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 ( ) {
if ( ! IsWindows ) {
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 ) {
2017-02-05 13:52:00 +01:00
ASF . ArchiLogger . LogGenericError ( string . Format ( Strings . WarningFailedWithError , result ) ) ;
}
}
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
2017-08-31 07:30:09 +02:00
[DllImport("libc", EntryPoint = "chmod", SetLastError = true)]
internal static extern int Chmod ( string path , int mode ) ;
[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
}