Improve runtime compatibility

This commit does 3 things:
- adds safeguard against running generic-netf in unsupported environments
- adds notice about providing ASF with unknown command-line arguments
- refactors old pre+post args parsing into a single window
This commit is contained in:
JustArchi
2020-11-19 21:01:17 +01:00
parent fc36174953
commit 51ad3b0c76
4 changed files with 350 additions and 303 deletions

File diff suppressed because it is too large Load Diff

View File

@@ -1,17 +1,17 @@
<?xml version="1.0" encoding="utf-8"?>
<root>
<!--
Microsoft ResX Schema
<!--
Microsoft ResX Schema
Version 2.0
The primary goals of this format is to allow a simple XML format
that is mostly human readable. The generation and parsing of the
various data types are done through the TypeConverter classes
The primary goals of this format is to allow a simple XML format
that is mostly human readable. The generation and parsing of the
various data types are done through the TypeConverter classes
associated with the data types.
Example:
... ado.net/XML headers & schema ...
<resheader name="resmimetype">text/microsoft-resx</resheader>
<resheader name="version">2.0</resheader>
@@ -26,36 +26,36 @@
<value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
<comment>This is a comment</comment>
</data>
There are any number of "resheader" rows that contain simple
There are any number of "resheader" rows that contain simple
name/value pairs.
Each data row contains a name, and value. The row also contains a
type or mimetype. Type corresponds to a .NET class that support
text/value conversion through the TypeConverter architecture.
Classes that don't support this are serialized and stored with the
Each data row contains a name, and value. The row also contains a
type or mimetype. Type corresponds to a .NET class that support
text/value conversion through the TypeConverter architecture.
Classes that don't support this are serialized and stored with the
mimetype set.
The mimetype is used for serialized objects, and tells the
ResXResourceReader how to depersist the object. This is currently not
The mimetype is used for serialized objects, and tells the
ResXResourceReader how to depersist the object. This is currently not
extensible. For a given mimetype the value must be set accordingly:
Note - application/x-microsoft.net.object.binary.base64 is the format
that the ResXResourceWriter will generate, however the reader can
Note - application/x-microsoft.net.object.binary.base64 is the format
that the ResXResourceWriter will generate, however the reader can
read any of the formats listed below.
mimetype: application/x-microsoft.net.object.binary.base64
value : The object must be serialized with
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.soap.base64
value : The object must be serialized with
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.bytearray.base64
value : The object must be serialized into a byte array
value : The object must be serialized into a byte array
: using a System.ComponentModel.TypeConverter
: and then encoded with base64 encoding.
-->
@@ -736,4 +736,10 @@ Process uptime: {1}</value>
<value>Result: {0}</value>
<comment>{0} will be replaced by generic result of various functions that use this string</comment>
</data>
</root>
<data name="WarningUnsupportedEnvironment" xml:space="preserve">
<value>You're attempting to run {0} variant of ASF in unsupported environment: {1}. Supply --ignore-unsupported-environment argument if you really know what you're doing.</value>
</data>
<data name="WarningUnknownCommandLineArgument" xml:space="preserve">
<value>Unknown command-line argument: {0}</value>
</data>
</root>

View File

@@ -40,20 +40,26 @@ namespace ArchiSteamFarm {
private static Mutex? SingleInstance;
internal static void CoreInit() {
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows) && !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;
internal static void CoreInit(bool systemRequired) {
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) {
if (systemRequired) {
WindowsKeepSystemActive();
}
// 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();
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();
}
}
}
@@ -67,15 +73,39 @@ namespace ArchiSteamFarm {
return new CrossProcessFileBasedSemaphore(resourceName);
}
internal static void Init(bool systemRequired, GlobalConfig.EOptimizationMode optimizationMode) {
if (!Enum.IsDefined(typeof(GlobalConfig.EOptimizationMode), optimizationMode)) {
throw new ArgumentNullException(nameof(optimizationMode));
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;
}
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) {
if (systemRequired) {
WindowsKeepSystemActive();
}
// 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
}
internal static void Init(GlobalConfig.EOptimizationMode optimizationMode) {
if (!Enum.IsDefined(typeof(GlobalConfig.EOptimizationMode), optimizationMode)) {
throw new ArgumentNullException(nameof(optimizationMode));
}
switch (optimizationMode) {

View File

@@ -45,6 +45,7 @@ namespace ArchiSteamFarm {
private static readonly TaskCompletionSource<byte> ShutdownResetEvent = new();
private static bool IgnoreUnsupportedEnvironment;
private static bool SystemRequired;
internal static async Task Exit(byte exitCode = 0) {
@@ -119,38 +120,26 @@ namespace ArchiSteamFarm {
Target.Register<HistoryTarget>(HistoryTarget.TargetName);
Target.Register<SteamTarget>(SteamTarget.TargetName);
if (!await InitCore(args).ConfigureAwait(false)) {
if (!await InitCore(args).ConfigureAwait(false) || !await InitASF().ConfigureAwait(false)) {
await Exit(1).ConfigureAwait(false);
return;
}
await InitASF(args).ConfigureAwait(false);
}
private static async Task InitASF(IReadOnlyCollection<string>? args) {
OS.CoreInit();
Console.Title = SharedInfo.ProgramIdentifier;
ASF.ArchiLogger.LogGenericInfo(SharedInfo.ProgramIdentifier);
private static async Task<bool> InitASF() {
if (!await InitGlobalConfigAndLanguage().ConfigureAwait(false)) {
return;
return false;
}
if (ASF.GlobalConfig == null) {
throw new InvalidOperationException(nameof(ASF.GlobalConfig));
}
// Parse post-init args
if (args != null) {
ParsePostInitArgs(args);
}
OS.Init(SystemRequired, ASF.GlobalConfig.OptimizationMode);
OS.Init(ASF.GlobalConfig.OptimizationMode);
await InitGlobalDatabaseAndServices().ConfigureAwait(false);
await ASF.Init().ConfigureAwait(false);
return true;
}
private static async Task<bool> InitCore(IReadOnlyCollection<string>? args) {
@@ -173,9 +162,9 @@ namespace ArchiSteamFarm {
}
}
// Parse pre-init args
// Parse args
if (args != null) {
ParsePreInitArgs(args);
ParseArgs(args);
}
bool uniqueInstance = OS.RegisterProcess();
@@ -188,6 +177,20 @@ namespace ArchiSteamFarm {
return false;
}
OS.CoreInit(SystemRequired);
Console.Title = SharedInfo.ProgramIdentifier;
ASF.ArchiLogger.LogGenericInfo(SharedInfo.ProgramIdentifier);
if (!IgnoreUnsupportedEnvironment) {
if (!OS.VerifyEnvironment()) {
ASF.ArchiLogger.LogGenericError(string.Format(CultureInfo.CurrentCulture, Strings.WarningUnsupportedEnvironment, SharedInfo.BuildInfo.Variant, OS.Variant));
await Task.Delay(10000).ConfigureAwait(false);
return false;
}
}
return true;
}
@@ -413,7 +416,7 @@ namespace ArchiSteamFarm {
e.SetObserved();
}
private static void ParsePostInitArgs(IReadOnlyCollection<string> args) {
private static void ParseArgs(IReadOnlyCollection<string> args) {
if (args == null) {
throw new ArgumentNullException(nameof(args));
}
@@ -424,49 +427,7 @@ namespace ArchiSteamFarm {
if (!string.IsNullOrEmpty(envCryptKey)) {
HandleCryptKeyArgument(envCryptKey!);
}
} catch (Exception e) {
ASF.ArchiLogger.LogGenericException(e);
}
bool cryptKeyNext = false;
foreach (string arg in args) {
switch (arg) {
case "--cryptkey" when !cryptKeyNext:
cryptKeyNext = true;
break;
case "--no-restart" when !cryptKeyNext:
RestartAllowed = false;
break;
case "--process-required" when !cryptKeyNext:
ProcessRequired = true;
break;
case "--system-required" when !cryptKeyNext:
SystemRequired = true;
break;
default:
if (cryptKeyNext) {
cryptKeyNext = false;
HandleCryptKeyArgument(arg);
} else if ((arg.Length > 11) && arg.StartsWith("--cryptkey=", StringComparison.Ordinal)) {
HandleCryptKeyArgument(arg.Substring(11));
}
break;
}
}
}
private static void ParsePreInitArgs(IReadOnlyCollection<string> args) {
if (args == null) {
throw new ArgumentNullException(nameof(args));
}
try {
string? envNetworkGroup = Environment.GetEnvironmentVariable(SharedInfo.EnvironmentVariableNetworkGroup);
if (!string.IsNullOrEmpty(envNetworkGroup)) {
@@ -482,21 +443,45 @@ namespace ArchiSteamFarm {
ASF.ArchiLogger.LogGenericException(e);
}
bool cryptKeyNext = false;
bool networkGroupNext = false;
bool pathNext = false;
foreach (string arg in args) {
switch (arg) {
case "--network-group" when !networkGroupNext && !pathNext:
case "--cryptkey" when !cryptKeyNext && !networkGroupNext && !pathNext:
cryptKeyNext = true;
break;
case "--ignore-unsupported-environment" when !cryptKeyNext && !networkGroupNext && !pathNext:
IgnoreUnsupportedEnvironment = true;
break;
case "--network-group" when !cryptKeyNext && !networkGroupNext && !pathNext:
networkGroupNext = true;
break;
case "--path" when !networkGroupNext && !pathNext:
case "--no-restart" when !cryptKeyNext && !networkGroupNext && !pathNext:
RestartAllowed = false;
break;
case "--process-required" when !cryptKeyNext && !networkGroupNext && !pathNext:
ProcessRequired = true;
break;
case "--path" when !cryptKeyNext && !networkGroupNext && !pathNext:
pathNext = true;
break;
case "--system-required" when !cryptKeyNext && !networkGroupNext && !pathNext:
SystemRequired = true;
break;
default:
if (networkGroupNext) {
if (cryptKeyNext) {
cryptKeyNext = false;
HandleCryptKeyArgument(arg);
} else if (networkGroupNext) {
networkGroupNext = false;
HandleNetworkGroupArgument(arg);
} else if (pathNext) {
@@ -507,10 +492,18 @@ namespace ArchiSteamFarm {
case > 16 when arg.StartsWith("--network-group=", StringComparison.Ordinal):
HandleNetworkGroupArgument(arg.Substring(16));
break;
case > 11 when arg.StartsWith("--cryptkey=", StringComparison.Ordinal):
HandleCryptKeyArgument(arg.Substring(11));
break;
case > 7 when arg.StartsWith("--path=", StringComparison.Ordinal):
HandlePathArgument(arg.Substring(7));
break;
default:
ASF.ArchiLogger.LogGenericWarning(string.Format(CultureInfo.CurrentCulture, Strings.WarningUnknownCommandLineArgument, arg));
break;
}
}