From bf04824c39663a251dfb1813ccc6302e9110c1f0 Mon Sep 17 00:00:00 2001 From: Arne Kiesewetter Date: Thu, 30 Mar 2023 18:41:07 +0200 Subject: [PATCH 1/5] Add LaunchArgument support class --- NeosModLoader/Utility/LaunchArguments.cs | 234 +++++++++++++++++++++++ 1 file changed, 234 insertions(+) create mode 100644 NeosModLoader/Utility/LaunchArguments.cs diff --git a/NeosModLoader/Utility/LaunchArguments.cs b/NeosModLoader/Utility/LaunchArguments.cs new file mode 100644 index 0000000..94c487a --- /dev/null +++ b/NeosModLoader/Utility/LaunchArguments.cs @@ -0,0 +1,234 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using static FrooxEngine.FinalIK.IKSolverVR; + +namespace NeosModLoader.Utility +{ + /// + /// Contains methods to access the command line arguments Neos was launched with. + /// + /// Refer to FrooxEngine.Engine.Initialize(...) to gather possible Arguments + public static class LaunchArguments + { + /// + /// Prefix symbol that indicates an argument name: - + /// + public const string ArgumentIndicator = "-"; + + /// + /// Prefix string that indicated an argument targeted at NML itself or mods loaded by it: -NML.
+ /// This is removed from argument names to get their proper name. + ///
+ public const string NMLArgumentPrefix = ArgumentIndicator + "NML."; + + private static readonly Dictionary arguments = new(); + private static readonly string[] possibleNeosArguments = { "config", "LoadAssembly", "GeneratePrecache", "Verbose", "pro", "backgroundworkers", "priorityworkers" }; + private static readonly string[] possibleNeosFlagArguments = { "GeneratePrecache", "Verbose", "pro" }; + + /// + /// Gets all arguments Neos was launched with. + /// + public static IEnumerable Arguments + { + get + { + foreach (var argument in arguments.Values) + yield return argument; + } + } + + /// + /// Gets the names of all flag arguments recognized by Neos.
+ /// These are forbidden as suffixes of any other arguments. + ///
+ public static IEnumerable PossibleNeosFlagArguments + { + get + { + foreach (var flagArgument in possibleNeosFlagArguments) + yield return flagArgument; + } + } + + /// + /// Gets the names of all arguments recognized by Neos.
+ /// These are forbidden as suffixes of any other arguments. + ///
+ public static IEnumerable PossibleNeosLaunchArguments + { + get + { + foreach (var argument in possibleNeosArguments) + yield return argument; + } + } + + static LaunchArguments() + { + var args = Environment.GetCommandLineArgs(); + + var i = 0; + while (i < args.Length) + { + var arg = args[i++]; + + var matchedNeosArgument = MatchNeosArgument(arg); + if (matchedNeosArgument != null) + { + if (MatchNeosFlagArgument(matchedNeosArgument) != null) + arguments.Add(matchedNeosArgument, new Argument(Target.Neos, arg, matchedNeosArgument)); + else if (i < args.Length) + arguments.Add(matchedNeosArgument, new Argument(Target.Neos, arg, matchedNeosArgument, args[i++])); + else + Logger.WarnInternal($"Found Neos Launch Argument [{matchedNeosArgument}] without a value"); + + continue; + } + + string? value = null; + // If it's the last argument or followed by another, treat it as a flag + if (i < args.Length && !args[i].StartsWith(ArgumentIndicator) && MatchNeosArgument(args[i]) == null) + value = args[i]; + + if (!arg.StartsWith(NMLArgumentPrefix, StringComparison.InvariantCultureIgnoreCase)) + { + // The value of an unknown argument is not skipped, but added as its own argument in the next iteration as well + arguments.Add(arg, new Argument(Target.Unknown, arg, value)); + continue; + } + + // The value of an NML argument gets skipped + if (value != null) + ++i; + + var name = arg.Substring(NMLArgumentPrefix.Length); + arguments.Add(name, new Argument(Target.NML, arg, name, value)); + } + + foreach (var argument in arguments) + Logger.MsgInternal($"Parsed {argument}"); + } + + /// + /// Gets the with the given proper name. + /// + /// The proper name of the argument. + /// The with the given proper name. + /// + /// + public static Argument GetArgument(string name) + => arguments[name]; + + /// + /// Checks whether an argument with the given proper name is present. + /// + /// The proper name of the argument. + /// true if such an argument exists, otherwise false. + public static bool IsPresent(string name) + => arguments.ContainsKey(name); + + /// + /// Tries to find one of the that is a suffix of the given name. + /// + /// The name to check. + /// The matched Neos launch argument or null if there's no match. + public static string? MatchNeosArgument(string name) + => possibleNeosArguments.FirstOrDefault(neosArg => name.EndsWith(neosArg, StringComparison.InvariantCultureIgnoreCase)); + + /// + /// Tries to find one of the that is a suffix of the given name. + /// + /// The name to check. + /// The matched Neos launch argument flags or null if there's no match. + public static string? MatchNeosFlagArgument(string name) + => possibleNeosFlagArguments.FirstOrDefault(neosArg => name.EndsWith(neosArg, StringComparison.InvariantCultureIgnoreCase)); + + /// + /// Tries to get the with the given proper name. + /// + /// The proper name of the argument. + /// The with the given proper name or default() if it's not present. + /// true if such an argument exists, otherwise false. + /// + public static bool TryGetArgument(string name, out Argument argument) + => arguments.TryGetValue(name, out argument); + + /// + /// Data structure for launch arguments. + /// + public readonly struct Argument + { + /// + /// Gets whether the argument is a flag, i.e. whether it doesn't have a value. + /// + public bool IsFlag => Value == null; + + /// + /// Gets the proper name of the argument.
+ /// For this is the name Neos looks for, + /// while for this is the name without the NML Argument Prefix. + ///
+ public string Name { get; } + + /// + /// Gets the raw name of the argument, as it is on the command line. + /// + public string RawName { get; } + + /// + /// Gets the target that the argument is for. + /// + public Target Target { get; } + + /// + /// Gets the value associated with the argument. Is null for flags. + /// + public string? Value { get; } + + internal Argument(Target target, string rawName, string name, string? value = null) + { + Target = target; + RawName = rawName; + Name = name; + Value = value; + } + + internal Argument(Target target, string name, string? value = null) + : this(target, name, name, value) + { } + + /// + /// Gets a string representation of this parsed argument. + /// + /// A string representing this parsed argument. + public override string ToString() + { + return $"{Target} Argument {RawName} ({Name}): {(IsFlag ? "present" : $"\"{Value}\"")}"; + } + } + + /// + /// Different possible targets for command line arguments. + /// + public enum Target + { + /// + /// Not a known target. + /// + Unknown, + + /// + /// Targeted at Neos itself. + /// + Neos, + + /// + /// Targeted at NML or a mod it loads. + /// + NML + } + } +} From 564339f83b81adfbaee53bbb993c2ce4825dbf2c Mon Sep 17 00:00:00 2001 From: Arne Kiesewetter Date: Thu, 30 Mar 2023 23:26:53 +0200 Subject: [PATCH 2/5] Add _all_ the possible Neos arguments --- NeosModLoader/Utility/LaunchArguments.cs | 190 ++++++++++++++++++----- 1 file changed, 154 insertions(+), 36 deletions(-) diff --git a/NeosModLoader/Utility/LaunchArguments.cs b/NeosModLoader/Utility/LaunchArguments.cs index 94c487a..90a178e 100644 --- a/NeosModLoader/Utility/LaunchArguments.cs +++ b/NeosModLoader/Utility/LaunchArguments.cs @@ -1,3 +1,4 @@ +using FrooxEngine; using System; using System.Collections.Generic; using System.Linq; @@ -10,23 +11,53 @@ namespace NeosModLoader.Utility /// /// Contains methods to access the command line arguments Neos was launched with. /// - /// Refer to FrooxEngine.Engine.Initialize(...) to gather possible Arguments + /// Use analyze on Environment.GetCommandLineArgs() to find all possible arguments. public static class LaunchArguments { /// /// Prefix symbol that indicates an argument name: - /// - public const string ArgumentIndicator = "-"; + public const char ArgumentIndicator = '-'; /// - /// Prefix string that indicated an argument targeted at NML itself or mods loaded by it: -NML.
+ /// Prefix string after the argument indicator, that indicated an argument targeted at NML itself or mods loaded by it: NML.
/// This is removed from argument names to get their proper name. ///
- public const string NMLArgumentPrefix = ArgumentIndicator + "NML."; + public const string NMLArgumentPrefix = "NML."; - private static readonly Dictionary arguments = new(); - private static readonly string[] possibleNeosArguments = { "config", "LoadAssembly", "GeneratePrecache", "Verbose", "pro", "backgroundworkers", "priorityworkers" }; - private static readonly string[] possibleNeosFlagArguments = { "GeneratePrecache", "Verbose", "pro" }; + private static readonly Dictionary arguments = new(StringComparer.InvariantCultureIgnoreCase); + + private static readonly string[] possibleNeosExactFlagArguments = + { + "ctaa", "ctaatemporaledgepower", "ctaasharpnessenabled", "ctaaadaptivesharpness", "ForceSRAnipal", + "StereoDisplay", "MixedReality", "DirectComposition", "ExternalComposition", "create_mrc_config", + "load_mrc_config" + }; + + private static readonly string[] possibleNeosExactParameterArguments = + { + "TextureSizeRatio", "DataPath", "CachePath" + }; + + private static readonly string[] possibleNeosInfixFlagArguments = + { + "CameraBiggestGroup", "CameraTimelapse", "CameraStayBehind", "CameraStayInFront", "AnnounceHomeOnLAN" + }; + + private static readonly string[] possibleNeosSuffixFlagArguments = + { + "GeneratePrecache", "Verbose", "pro", "UseLocalCloud", "UseStagingCloud", "ForceRelay", "Invisible", + "ForceReticleAboveHorizon", "ForceNoVoice", "ResetDash", "Kiosk", "DontAutoOpenCloudHome", "ResetUserspace", + "ForceLANOnly", "DeleteUnsyncedCloudRecords", "ForceSyncConflictingCloudRecords", "ForceIntroTutorial", + "SkipIntroTutorial", "RepairDatabase", "UseNeosCamera", "LegacySteamVRInput", "Etee", "HideScreenReticle", + "Viveport", "DisableNativeTextureUpload" + }; + + private static readonly string[] possibleNeosSuffixParameterArguments = + { + "Config", "LoadAssembly", "BackgroundWorkers", "PriorityWorkers", "Bench", "Watchdog", "Bootstrap", + "OnlyHost", "Join", "Open", "OpenUnsafe", "EnableOWO" + }; /// /// Gets all arguments Neos was launched with. @@ -40,76 +71,151 @@ public static IEnumerable Arguments } } + /// + /// Gets the path to the launched Neos executable. + /// + public static string ExecutablePath { get; } + + /// + /// Gets the names of all arguments recognized by Neos.
+ /// These have different rules relating to the names of other arguments. + ///
+ public static IEnumerable PossibleNeosArguments + => PossibleNeosParameterArguments.Concat(PossibleNeosFlagArguments); + + /// + /// Gets the names of all exact flag arguments recognized by Neos.
+ /// These are forbidden as the exact names of any other arguments. + ///
+ public static IEnumerable PossibleNeosExactFlagArguments + { + get + { + foreach (var argument in possibleNeosExactFlagArguments) + yield return argument; + } + } + + /// + /// Gets the names of all exact parameter arguments recognized by Neos.
+ /// These are forbidden as the exact names of any other arguments. + ///
+ public static IEnumerable PossibleNeosExactParameterArguments + { + get + { + foreach (var argument in possibleNeosExactParameterArguments) + yield return argument; + } + } + /// /// Gets the names of all flag arguments recognized by Neos.
- /// These are forbidden as suffixes of any other arguments. + /// These have different rules relating to the names of other arguments. ///
public static IEnumerable PossibleNeosFlagArguments + => possibleNeosExactFlagArguments.Concat(possibleNeosSuffixFlagArguments).Concat(possibleNeosInfixFlagArguments); + + /// + /// Gets the names of all infix flag arguments recognized by Neos.
+ /// These are forbidden as infixes of any other arguments. + ///
+ public static IEnumerable PossibleNeosInfixFlagArguments + { + get + { + foreach (var argument in possibleNeosInfixFlagArguments) + yield return argument; + } + } + + /// + /// Gets the names of all parameter arguments recognized by Neos.
+ /// These have different rules relating to the names of other arguments. + ///
+ public static IEnumerable PossibleNeosParameterArguments + => possibleNeosExactParameterArguments.Concat(possibleNeosSuffixParameterArguments); + + /// + /// Gets the names of all suffix flag arguments recognized by Neos.
+ /// These are forbidden as suffixes of any other arguments. + ///
+ public static IEnumerable PossibleNeosSuffixFlagArguments { get { - foreach (var flagArgument in possibleNeosFlagArguments) + foreach (var flagArgument in possibleNeosSuffixFlagArguments) yield return flagArgument; } } /// - /// Gets the names of all arguments recognized by Neos.
+ /// Gets the names of all suffix parameter arguments recognized by Neos.
/// These are forbidden as suffixes of any other arguments. ///
- public static IEnumerable PossibleNeosLaunchArguments + public static IEnumerable PossibleNeosSuffixParameterArguments { get { - foreach (var argument in possibleNeosArguments) + foreach (var argument in possibleNeosSuffixParameterArguments) yield return argument; } } static LaunchArguments() { + possibleNeosExactFlagArguments = possibleNeosExactFlagArguments + .Concat( + Enum.GetValues(typeof(HeadOutputDevice)) + .Cast() + .Select(v => v.ToString())) + .ToArray(); + + // First argument is the path of the executable var args = Environment.GetCommandLineArgs(); + ExecutablePath = args[0]; - var i = 0; + var i = 1; while (i < args.Length) { - var arg = args[i++]; + var arg = args[i++].TrimStart(ArgumentIndicator); + var hasParameter = i < args.Length && args[i].FirstOrDefault() != ArgumentIndicator && MatchNeosArgument(args[i]) == null; - var matchedNeosArgument = MatchNeosArgument(arg); + var matchedNeosArgument = MatchNeosFlagArgument(arg); if (matchedNeosArgument != null) { - if (MatchNeosFlagArgument(matchedNeosArgument) != null) - arguments.Add(matchedNeosArgument, new Argument(Target.Neos, arg, matchedNeosArgument)); - else if (i < args.Length) + arguments.Add(matchedNeosArgument, new Argument(Target.Neos, arg, matchedNeosArgument)); + + if (hasParameter) + Logger.WarnInternal($"Possible misplaced parameter value after flag argument: {matchedNeosArgument}"); + + continue; + } + + matchedNeosArgument = MatchNeosParameterArgument(arg); + if (matchedNeosArgument != null) + { + if (hasParameter) arguments.Add(matchedNeosArgument, new Argument(Target.Neos, arg, matchedNeosArgument, args[i++])); else - Logger.WarnInternal($"Found Neos Launch Argument [{matchedNeosArgument}] without a value"); + Logger.WarnInternal($"Expected parameter for argument: {matchedNeosArgument}"); continue; } - string? value = null; - // If it's the last argument or followed by another, treat it as a flag - if (i < args.Length && !args[i].StartsWith(ArgumentIndicator) && MatchNeosArgument(args[i]) == null) - value = args[i]; - if (!arg.StartsWith(NMLArgumentPrefix, StringComparison.InvariantCultureIgnoreCase)) { // The value of an unknown argument is not skipped, but added as its own argument in the next iteration as well - arguments.Add(arg, new Argument(Target.Unknown, arg, value)); + arguments.Add(arg, new Argument(Target.Unknown, arg, hasParameter ? args[i] : null)); continue; } - // The value of an NML argument gets skipped - if (value != null) - ++i; - var name = arg.Substring(NMLArgumentPrefix.Length); - arguments.Add(name, new Argument(Target.NML, arg, name, value)); + arguments.Add(name, new Argument(Target.NML, arg, name, hasParameter ? args[i++] : null)); } foreach (var argument in arguments) - Logger.MsgInternal($"Parsed {argument}"); + Logger.MsgInternal($"Parsed {argument.Value}"); } /// @@ -131,20 +237,32 @@ public static bool IsPresent(string name) => arguments.ContainsKey(name); /// - /// Tries to find one of the that is a suffix of the given name. + /// Tries to find one of the that matches the given name in the right way. /// /// The name to check. /// The matched Neos launch argument or null if there's no match. public static string? MatchNeosArgument(string name) - => possibleNeosArguments.FirstOrDefault(neosArg => name.EndsWith(neosArg, StringComparison.InvariantCultureIgnoreCase)); + => MatchNeosParameterArgument(name) + ?? MatchNeosFlagArgument(name); /// - /// Tries to find one of the that is a suffix of the given name. + /// Tries to find one of the that matches the given name in the right way. /// /// The name to check. /// The matched Neos launch argument flags or null if there's no match. public static string? MatchNeosFlagArgument(string name) - => possibleNeosFlagArguments.FirstOrDefault(neosArg => name.EndsWith(neosArg, StringComparison.InvariantCultureIgnoreCase)); + => possibleNeosExactFlagArguments.FirstOrDefault(neosArg => name.Equals(neosArg, StringComparison.InvariantCultureIgnoreCase)) + ?? possibleNeosSuffixFlagArguments.FirstOrDefault(neosArg => name.EndsWith(neosArg, StringComparison.InvariantCultureIgnoreCase)) + ?? possibleNeosInfixFlagArguments.FirstOrDefault(neosArg => name.IndexOf(neosArg, StringComparison.InvariantCultureIgnoreCase) >= 0); + + /// + /// Tries to find one of the that matches the given name in the right way. + /// + /// The name to check. + /// The matched Neos launch argument or null if there's no match. + public static string? MatchNeosParameterArgument(string name) + => possibleNeosExactParameterArguments.FirstOrDefault(neosArg => name.Equals(neosArg, StringComparison.InvariantCultureIgnoreCase)) + ?? possibleNeosSuffixParameterArguments.FirstOrDefault(neosArg => name.EndsWith(neosArg, StringComparison.InvariantCultureIgnoreCase)); /// /// Tries to get the with the given proper name. @@ -206,7 +324,7 @@ internal Argument(Target target, string name, string? value = null) /// A string representing this parsed argument. public override string ToString() { - return $"{Target} Argument {RawName} ({Name}): {(IsFlag ? "present" : $"\"{Value}\"")}"; + return $"{Target} Argument {(Name.Equals(RawName, StringComparison.InvariantCultureIgnoreCase) ? Name : $"{RawName} ({Name})")}: {(IsFlag ? "present" : $"\"{Value}\"")}"; } } From 3f194223151fc1de6833a7abf84d868381a6a38a Mon Sep 17 00:00:00 2001 From: Arne Kiesewetter Date: Thu, 30 Mar 2023 23:29:47 +0200 Subject: [PATCH 3/5] Overhaul Configuration loading: Assign with reflection, override path and/or values with launch arguments --- NeosModLoader/ModLoaderConfiguration.cs | 182 +++++++++++++++--------- 1 file changed, 118 insertions(+), 64 deletions(-) diff --git a/NeosModLoader/ModLoaderConfiguration.cs b/NeosModLoader/ModLoaderConfiguration.cs index 4c96ca4..4d9dbc5 100644 --- a/NeosModLoader/ModLoaderConfiguration.cs +++ b/NeosModLoader/ModLoaderConfiguration.cs @@ -1,23 +1,87 @@ +using HarmonyLib; +using NeosModLoader.Utility; using System; +using System.ComponentModel; using System.IO; +using System.Linq; using System.Reflection; namespace NeosModLoader { internal class ModLoaderConfiguration { + private static readonly Lazy _configuration = new(LoadConfig); private static readonly string CONFIG_FILENAME = "NeosModLoader.config"; - private static ModLoaderConfiguration? _configuration; + public bool AdvertiseVersion { get; private set; } = false; + + public bool Debug { get; private set; } = false; - internal static ModLoaderConfiguration Get() + public bool ExposeLateTypes { - if (_configuration == null) - { - // the config file can just sit next to the dll. Simple. - string path = Path.Combine(GetAssemblyDirectory(), CONFIG_FILENAME); - _configuration = new ModLoaderConfiguration(); + get => !HideLateTypes; + set => HideLateTypes = !value; + } + + public bool ExposeModTypes + { + get => !HideModTypes; + set => HideModTypes = !value; + } + + public bool HideConflicts + { + get => !LogConflicts; + set => LogConflicts = !value; + } + + public bool HideLateTypes { get; private set; } = true; + + public bool HideModTypes { get; private set; } = true; + + public bool HideVisuals { get; private set; } = false; + + public bool LogConflicts { get; private set; } = true; + + public bool NoLibraries { get; private set; } = false; + + public bool NoMods { get; private set; } = false; + + public bool Unsafe { get; private set; } = false; + + internal static ModLoaderConfiguration Get() => _configuration.Value; + + private static string GetAssemblyDirectory() + { + var codeBase = Assembly.GetExecutingAssembly().CodeBase; + var uri = new UriBuilder(codeBase); + var path = Uri.UnescapeDataString(uri.Path); + + return Path.GetDirectoryName(path); + } + + private static string? GetConfigPath() + { + if (LaunchArguments.TryGetArgument("Config.Path", out var argument)) + return argument.Value; + + // the config file can just sit next to the dll. Simple. + return Path.Combine(GetAssemblyDirectory(), CONFIG_FILENAME); + } + private static ModLoaderConfiguration LoadConfig() + { + var path = GetConfigPath(); + var config = new ModLoaderConfiguration(); + + var configOptions = typeof(ModLoaderConfiguration).GetProperties(AccessTools.all).ToArray(); + + if (!File.Exists(path)) + { + Logger.MsgInternal($"Using default config - file doesn't exist: {path}"); + } + else + { // .NET's ConfigurationManager is some hot trash to the point where I'm just done with it. // Time to reinvent the wheel. This parses simple key=value style properties from a text file. try @@ -25,49 +89,25 @@ internal static ModLoaderConfiguration Get() var lines = File.ReadAllLines(path); foreach (var line in lines) { - int splitIdx = line.IndexOf('='); - if (splitIdx != -1) + var splitIdx = line.IndexOf('='); + if (splitIdx == -1) + continue; + + string key = line.Substring(0, splitIdx).Trim(); + string value = line.Substring(splitIdx + 1).Trim(); + + var possibleProperty = configOptions.FirstOrDefault(property => property.Name.Equals(key, StringComparison.InvariantCultureIgnoreCase)); + if (possibleProperty == null) { - string key = line.Substring(0, splitIdx); - string value = line.Substring(splitIdx + 1); - - if ("unsafe".Equals(key) && "true".Equals(value)) - { - _configuration.Unsafe = true; - } - else if ("debug".Equals(key) && "true".Equals(value)) - { - _configuration.Debug = true; - } - else if ("hidevisuals".Equals(key) && "true".Equals(value)) - { - _configuration.HideVisuals = true; - } - else if ("nomods".Equals(key) && "true".Equals(value)) - { - _configuration.NoMods = true; - } - else if ("nolibraries".Equals(key) && "true".Equals(value)) - { - _configuration.NoLibraries = true; - } - else if ("advertiseversion".Equals(key) && "true".Equals(value)) - { - _configuration.AdvertiseVersion = true; - } - else if ("logconflicts".Equals(key) && "false".Equals(value)) - { - _configuration.LogConflicts = false; - } - else if ("hidemodtypes".Equals(key) && "false".Equals(value)) - { - _configuration.HideModTypes = false; - } - else if ("hidelatetypes".Equals(key) && "false".Equals(value)) - { - _configuration.HideLateTypes = false; - } + Logger.WarnInternal($"Unknown key found in config file: {key}"); + Logger.WarnInternal($"Supported keys: {string.Join(", ", configOptions.Select(property => property.Name))}"); + continue; } + + var parsedValue = TypeDescriptor.GetConverter(possibleProperty.PropertyType).ConvertFromInvariantString(value); + possibleProperty.SetValue(config, parsedValue); + + Logger.MsgInternal($"Loaded value for {possibleProperty.Name} from file: {parsedValue}"); } } catch (Exception e) @@ -86,25 +126,39 @@ internal static ModLoaderConfiguration Get() } } } - return _configuration; + + try + { + var boolType = typeof(bool); + foreach (var option in configOptions) + { + if (LaunchArguments.TryGetArgument($"Config.{option.Name}", out var argument)) + { + if (option.PropertyType == boolType) + { + option.SetValue(config, true); + Logger.MsgInternal($"Enabling [{option.Name}] from launch flag"); + } + else if (!argument.IsFlag) + { + config.SetProperty(option, argument.Value!); + Logger.MsgInternal($"Setting [{option.Name}] from launch flag: {argument.Value}"); + } + } + } + } + catch + { + throw; + } + + return config; } - private static string GetAssemblyDirectory() + private void SetProperty(PropertyInfo property, string value) { - string codeBase = Assembly.GetExecutingAssembly().CodeBase; - UriBuilder uri = new(codeBase); - string path = Uri.UnescapeDataString(uri.Path); - return Path.GetDirectoryName(path); + var parsedValue = TypeDescriptor.GetConverter(property.PropertyType).ConvertFromInvariantString(value); + property.SetValue(this, parsedValue); } - - public bool Unsafe { get; private set; } = false; - public bool Debug { get; private set; } = false; - public bool HideVisuals { get; private set; } = false; - public bool NoMods { get; private set; } = false; - public bool NoLibraries { get; private set; } = false; - public bool AdvertiseVersion { get; private set; } = false; - public bool LogConflicts { get; private set; } = true; - public bool HideModTypes { get; private set; } = true; - public bool HideLateTypes { get; private set; } = true; } } From e640f765753d3b138d94a52239e184221a0e187d Mon Sep 17 00:00:00 2001 From: Arne Kiesewetter Date: Thu, 30 Mar 2023 23:49:52 +0200 Subject: [PATCH 4/5] Add index to arguments and remove conflicting constructor --- NeosModLoader/Utility/LaunchArguments.cs | 20 +++++++++++--------- 1 file changed, 11 insertions(+), 9 deletions(-) diff --git a/NeosModLoader/Utility/LaunchArguments.cs b/NeosModLoader/Utility/LaunchArguments.cs index 90a178e..a6462e9 100644 --- a/NeosModLoader/Utility/LaunchArguments.cs +++ b/NeosModLoader/Utility/LaunchArguments.cs @@ -184,7 +184,7 @@ static LaunchArguments() var matchedNeosArgument = MatchNeosFlagArgument(arg); if (matchedNeosArgument != null) { - arguments.Add(matchedNeosArgument, new Argument(Target.Neos, arg, matchedNeosArgument)); + arguments.Add(matchedNeosArgument, new Argument(Target.Neos, i, arg, matchedNeosArgument)); if (hasParameter) Logger.WarnInternal($"Possible misplaced parameter value after flag argument: {matchedNeosArgument}"); @@ -196,7 +196,7 @@ static LaunchArguments() if (matchedNeosArgument != null) { if (hasParameter) - arguments.Add(matchedNeosArgument, new Argument(Target.Neos, arg, matchedNeosArgument, args[i++])); + arguments.Add(matchedNeosArgument, new Argument(Target.Neos, i, arg, matchedNeosArgument, args[i++])); else Logger.WarnInternal($"Expected parameter for argument: {matchedNeosArgument}"); @@ -206,12 +206,12 @@ static LaunchArguments() if (!arg.StartsWith(NMLArgumentPrefix, StringComparison.InvariantCultureIgnoreCase)) { // The value of an unknown argument is not skipped, but added as its own argument in the next iteration as well - arguments.Add(arg, new Argument(Target.Unknown, arg, hasParameter ? args[i] : null)); + arguments.Add(arg, new Argument(Target.Unknown, i, arg, arg, hasParameter ? args[i] : null)); continue; } var name = arg.Substring(NMLArgumentPrefix.Length); - arguments.Add(name, new Argument(Target.NML, arg, name, hasParameter ? args[i++] : null)); + arguments.Add(name, new Argument(Target.NML, i, arg, name, hasParameter ? args[i++] : null)); } foreach (var argument in arguments) @@ -279,6 +279,11 @@ public static bool TryGetArgument(string name, out Argument argument) /// public readonly struct Argument { + /// + /// Gets the index of this argument in the array returned by . + /// + public int Index { get; } + /// /// Gets whether the argument is a flag, i.e. whether it doesn't have a value. /// @@ -306,18 +311,15 @@ public readonly struct Argument /// public string? Value { get; } - internal Argument(Target target, string rawName, string name, string? value = null) + internal Argument(Target target, int index, string rawName, string name, string? value = null) { Target = target; + Index = index - 1; RawName = rawName; Name = name; Value = value; } - internal Argument(Target target, string name, string? value = null) - : this(target, name, name, value) - { } - /// /// Gets a string representation of this parsed argument. /// From e8f70115a79478f048ed7745e6bdd16ab12aa6cf Mon Sep 17 00:00:00 2001 From: Arne Kiesewetter Date: Fri, 31 Mar 2023 19:14:23 +0200 Subject: [PATCH 5/5] Move unknown config file keys warning --- NeosModLoader/ModLoaderConfiguration.cs | 42 +++++++++++++------------ 1 file changed, 22 insertions(+), 20 deletions(-) diff --git a/NeosModLoader/ModLoaderConfiguration.cs b/NeosModLoader/ModLoaderConfiguration.cs index 4d9dbc5..8dfe17b 100644 --- a/NeosModLoader/ModLoaderConfiguration.cs +++ b/NeosModLoader/ModLoaderConfiguration.cs @@ -1,6 +1,8 @@ +using FrooxEngine; using HarmonyLib; using NeosModLoader.Utility; using System; +using System.Collections.Generic; using System.ComponentModel; using System.IO; using System.Linq; @@ -86,7 +88,9 @@ private static ModLoaderConfiguration LoadConfig() // Time to reinvent the wheel. This parses simple key=value style properties from a text file. try { + var unknownKeys = new List(); var lines = File.ReadAllLines(path); + foreach (var line in lines) { var splitIdx = line.IndexOf('='); @@ -99,8 +103,7 @@ private static ModLoaderConfiguration LoadConfig() var possibleProperty = configOptions.FirstOrDefault(property => property.Name.Equals(key, StringComparison.InvariantCultureIgnoreCase)); if (possibleProperty == null) { - Logger.WarnInternal($"Unknown key found in config file: {key}"); - Logger.WarnInternal($"Supported keys: {string.Join(", ", configOptions.Select(property => property.Name))}"); + unknownKeys.Add(key); continue; } @@ -109,6 +112,9 @@ private static ModLoaderConfiguration LoadConfig() Logger.MsgInternal($"Loaded value for {possibleProperty.Name} from file: {parsedValue}"); } + + Logger.WarnInternal($"Unknown key found in config file: {string.Join(", ", unknownKeys)}"); + Logger.WarnInternal($"Supported keys: {string.Join(", ", configOptions.Select(property => $"{property.PropertyType} {property.Name}"))}"); } catch (Exception e) { @@ -127,30 +133,26 @@ private static ModLoaderConfiguration LoadConfig() } } - try + var boolType = typeof(bool); + foreach (var option in configOptions) { - var boolType = typeof(bool); - foreach (var option in configOptions) + if (LaunchArguments.TryGetArgument($"Config.{option.Name}", out var argument)) { - if (LaunchArguments.TryGetArgument($"Config.{option.Name}", out var argument)) + if (option.PropertyType == boolType) { - if (option.PropertyType == boolType) - { - option.SetValue(config, true); - Logger.MsgInternal($"Enabling [{option.Name}] from launch flag"); - } - else if (!argument.IsFlag) - { - config.SetProperty(option, argument.Value!); - Logger.MsgInternal($"Setting [{option.Name}] from launch flag: {argument.Value}"); - } + option.SetValue(config, true); + Logger.MsgInternal($"Enabling [{option.Name}] from launch flag"); + + if (!argument.IsFlag) + Logger.WarnInternal("Found possible misplaced parameter value after this flag argument"); + } + else if (!argument.IsFlag) + { + config.SetProperty(option, argument.Value!); + Logger.MsgInternal($"Setting [{option.Name}] from launch flag: {argument.Value}"); } } } - catch - { - throw; - } return config; }