diff --git a/README.md b/README.md index 59748cc..00f07ad 100644 --- a/README.md +++ b/README.md @@ -1,9 +1,9 @@ # ViVe ViVe is a C# library you can use to make your own programs that interact with the A/B feature experiment mechanism found in Windows 10 & newer. -In case you'd like to talk to NTDLL exports directly, you can use its *NativeMethods*. +The *FeatureManager* class should cover most feature management needs with the added benefit of some struct heavy lifting being done for you. Boot persistence and LKG management is offered exclusively by this class as it had to be reimplemented. -Otherwise, *RtlFeatureManager* offers the same featureset with the benefit of some struct heavy lifting being done for you. +In case you'd like to talk to NTDLL exports directly, you can use *NativeMethods*. # ViVeTool ViVeTool is both an example of how to use ViVe, as well as a straightforward tool for power users which want to use the new APIs instantly. @@ -13,4 +13,4 @@ ViVeTool is both an example of how to use ViVe, as well as a straightforward too # Compatibility In order to use ViVe, you must be running Windows 10 build 18963 or newer. -![ViVeTool Helpfile](https://i.imgur.com/HrLiSxe.png) +![ViVeTool Helpfile](https://i.imgur.com/PzCHEUQ.png) diff --git a/ViVe/FeatureConfiguration.cs b/ViVe/FeatureConfiguration.cs deleted file mode 100644 index 86fe557..0000000 --- a/ViVe/FeatureConfiguration.cs +++ /dev/null @@ -1,94 +0,0 @@ -/* - ViVe - Vibranium feature configuration library - Copyright (C) 2019 @thebookisclosed - - This program is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program. If not, see . - */ - -using System; - -namespace Albacore.ViVe -{ - public class FeatureConfiguration - { - private int _group; - private FeatureEnabledState _enabledState; - private int _enabledStateOptions; - private int _variant; - private int _variantPayloadKind; - private FeatureConfigurationAction _action; - - public uint FeatureId { get; set; } - public int Group - { - get { return _group; } - set { - if (value > 14) - throw new ArgumentException("Group must not be more than 14"); - _group = value; - } - } - public FeatureEnabledState EnabledState - { - get { return _enabledState; } - set - { - if ((int)value > 2) - throw new ArgumentException("EnabledState must not be more than 2"); - _enabledState = value; - } - } - public int EnabledStateOptions - { - get { return _enabledStateOptions; } - set - { - if (value > 1) - throw new ArgumentException("EnabledStateOptions must not be more than 1"); - _enabledStateOptions = value; - } - } - public int Variant - { - get { return _variant; } - set - { - if (value > 63) - throw new ArgumentException("Variant must not be more than 63"); - _variant = value; - } - } - public int VariantPayloadKind - { - get { return _variantPayloadKind; } - set - { - if (value > 3) - throw new ArgumentException("VariantPayloadKind must not be more than 3"); - _variantPayloadKind = value; - } - } - public int VariantPayload { get; set; } - public FeatureConfigurationAction Action - { - get { return _action; } - set - { - if ((int)value > 4) - throw new ArgumentException("Invalid feature configuration action"); - _action = value; - } - } - } -} diff --git a/ViVe/FeatureConfigurationSection.cs b/ViVe/FeatureConfigurationSection.cs deleted file mode 100644 index 0ca4fc0..0000000 --- a/ViVe/FeatureConfigurationSection.cs +++ /dev/null @@ -1,27 +0,0 @@ -/* - ViVe - Vibranium feature configuration library - Copyright (C) 2019 @thebookisclosed - - This program is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program. If not, see . - */ - -namespace Albacore.ViVe -{ - public enum FeatureConfigurationSection - { - Boot = 0, - Runtime = 1, - UsageTriggers = 2 - } -} diff --git a/ViVe/FeatureEnabledState.cs b/ViVe/FeatureEnabledState.cs deleted file mode 100644 index 0399dd8..0000000 --- a/ViVe/FeatureEnabledState.cs +++ /dev/null @@ -1,27 +0,0 @@ -/* - ViVe - Vibranium feature configuration library - Copyright (C) 2019 @thebookisclosed - - This program is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program. If not, see . - */ - -namespace Albacore.ViVe -{ - public enum FeatureEnabledState - { - Default = 0, - Disabled = 1, - Enabled = 2 - } -} diff --git a/ViVe/FeatureManager.cs b/ViVe/FeatureManager.cs new file mode 100644 index 0000000..43c08fa --- /dev/null +++ b/ViVe/FeatureManager.cs @@ -0,0 +1,309 @@ +/* + ViVe - Windows feature configuration library + Copyright (C) 2019-2022 @thebookisclosed + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + */ + +using Albacore.ViVe.NativeEnums; +using Albacore.ViVe.NativeMethods; +using Albacore.ViVe.NativeStructs; +using Microsoft.Win32; +using System; +using System.Runtime.InteropServices; + +namespace Albacore.ViVe +{ + public static class FeatureManager + { + public unsafe static RTL_FEATURE_CONFIGURATION[] QueryAllFeatureConfigurations(RTL_FEATURE_CONFIGURATION_TYPE configurationType = RTL_FEATURE_CONFIGURATION_TYPE.Runtime) + { + return QueryAllFeatureConfigurations(configurationType, null); + } + + public unsafe static RTL_FEATURE_CONFIGURATION[] QueryAllFeatureConfigurations(RTL_FEATURE_CONFIGURATION_TYPE configurationType, ref ulong changeStamp) + { + fixed (ulong* changeStampPtr = &changeStamp) + return QueryAllFeatureConfigurations(configurationType, changeStampPtr); + } + + public unsafe static RTL_FEATURE_CONFIGURATION[] QueryAllFeatureConfigurations(RTL_FEATURE_CONFIGURATION_TYPE configurationType, ulong* changeStamp) + { + Ntdll.RtlQueryAllFeatureConfigurations(configurationType, changeStamp, null, out int configCount); + if (configCount == 0) + return null; + var allFeatureConfigs = new RTL_FEATURE_CONFIGURATION[configCount]; + int hRes; + fixed (RTL_FEATURE_CONFIGURATION* configsPtr = allFeatureConfigs) + hRes = Ntdll.RtlQueryAllFeatureConfigurations(configurationType, changeStamp, configsPtr, out configCount); + if (hRes == 0) + return allFeatureConfigs; + else + return null; + } + + public static RTL_FEATURE_CONFIGURATION? QueryFeatureConfiguration(uint featureId, RTL_FEATURE_CONFIGURATION_TYPE configurationType = RTL_FEATURE_CONFIGURATION_TYPE.Runtime) + { + ulong dummy = 0; + return QueryFeatureConfiguration(featureId, configurationType, ref dummy); + } + + public static RTL_FEATURE_CONFIGURATION? QueryFeatureConfiguration(uint featureId, RTL_FEATURE_CONFIGURATION_TYPE configurationType, ref ulong changeStamp) + { + int hRes = Ntdll.RtlQueryFeatureConfiguration(featureId, configurationType, ref changeStamp, out RTL_FEATURE_CONFIGURATION config); + if (hRes != 0) + return null; + return config; + } + + public static ulong QueryFeatureConfigurationChangeStamp() + { + return Ntdll.RtlQueryFeatureConfigurationChangeStamp(); + } + + public static int SetFeatureConfigurations(RTL_FEATURE_CONFIGURATION_UPDATE[] updates, RTL_FEATURE_CONFIGURATION_TYPE configurationType = RTL_FEATURE_CONFIGURATION_TYPE.Runtime) + { + ulong dummy = 0; + return SetFeatureConfigurations(updates, configurationType, ref dummy); + } + + public static int SetFeatureConfigurations(RTL_FEATURE_CONFIGURATION_UPDATE[] updates, RTL_FEATURE_CONFIGURATION_TYPE configurationType, ref ulong previousChangeStamp) + { + if (configurationType == RTL_FEATURE_CONFIGURATION_TYPE.Runtime) + { + foreach (var update in updates) + if (update.Priority == RTL_FEATURE_CONFIGURATION_PRIORITY.ImageDefault || update.Priority == RTL_FEATURE_CONFIGURATION_PRIORITY.ImageOverride) + throw new ArgumentException("Windows does not support Runtime configuration of features with ImageDefault or ImageOverride priority."); + else if (update.Priority == RTL_FEATURE_CONFIGURATION_PRIORITY.UserPolicy && !update.UserPolicyPriorityCompatible) + throw new ArgumentException("UserPolicy priority features do not support persisting properties other than EnabledState."); + return Ntdll.RtlSetFeatureConfigurations(ref previousChangeStamp, RTL_FEATURE_CONFIGURATION_TYPE.Runtime, updates, updates.Length); + } + else + { + foreach (var update in updates) + if (update.Priority == RTL_FEATURE_CONFIGURATION_PRIORITY.UserPolicy && !update.UserPolicyPriorityCompatible) + throw new ArgumentException("UserPolicy priority features do not support persisting properties other than EnabledState."); + return SetFeatureConfigurationsInRegistry(updates, previousChangeStamp); + } + } + + public static IntPtr RegisterFeatureConfigurationChangeNotification(FeatureConfigurationChangeCallback callback) + { + return RegisterFeatureConfigurationChangeNotification(callback, IntPtr.Zero); + } + + public static IntPtr RegisterFeatureConfigurationChangeNotification(FeatureConfigurationChangeCallback callback, IntPtr context) + { + Ntdll.RtlRegisterFeatureConfigurationChangeNotification(callback, context, IntPtr.Zero, out IntPtr sub); + return sub; + } + + public static IntPtr RegisterFeatureConfigurationChangeNotification(FeatureConfigurationChangeCallback callback, IntPtr context, ref ulong waitForChangeStamp) + { + Ntdll.RtlRegisterFeatureConfigurationChangeNotification(callback, context, ref waitForChangeStamp, out IntPtr sub); + return sub; + } + + public static int UnregisterFeatureConfigurationChangeNotification(IntPtr notification) + { + return Ntdll.RtlUnregisterFeatureConfigurationChangeNotification(notification); + } + + public unsafe static RTL_FEATURE_USAGE_SUBSCRIPTION_DETAILS[] QueryFeatureUsageSubscriptions() + { + Ntdll.RtlQueryFeatureUsageNotificationSubscriptions(null, out int subCount); + if (subCount == 0) + return null; + var allSubscriptions = new RTL_FEATURE_USAGE_SUBSCRIPTION_DETAILS[subCount]; + int hRes; + fixed (RTL_FEATURE_USAGE_SUBSCRIPTION_DETAILS* subsPtr = allSubscriptions) + hRes = Ntdll.RtlQueryFeatureUsageNotificationSubscriptions(subsPtr, out subCount); + if (hRes == 0) + return allSubscriptions; + else + return null; + } + + public static int AddFeatureUsageSubscriptions(RTL_FEATURE_USAGE_SUBSCRIPTION_DETAILS[] subscriptions) + { + return Ntdll.RtlSubscribeForFeatureUsageNotification(subscriptions, subscriptions.Length); + } + + public static int RemoveFeatureUsageSubscriptions(RTL_FEATURE_USAGE_SUBSCRIPTION_DETAILS[] subscriptions) + { + return Ntdll.RtlUnsubscribeFromFeatureUsageNotifications(subscriptions, subscriptions.Length); + } + + public static int NotifyFeatureUsage(ref RTL_FEATURE_USAGE_REPORT report) + { + return Ntdll.RtlNotifyFeatureUsage(ref report); + } + + private const int RtlBsdItemFeatureConfigurationState = 17; + public static int SetBootFeatureConfigurationState(BSD_FEATURE_CONFIGURATION_STATE state) + { + var newState = (int)state; + return Ntdll.RtlSetSystemBootStatus(RtlBsdItemFeatureConfigurationState, ref newState, sizeof(int), IntPtr.Zero); + } + + public static int GetBootFeatureConfigurationState(out BSD_FEATURE_CONFIGURATION_STATE state) + { + var apiResult = Ntdll.RtlGetSystemBootStatus(RtlBsdItemFeatureConfigurationState, out int intState, sizeof(int), IntPtr.Zero); + state = (BSD_FEATURE_CONFIGURATION_STATE)intState; + return apiResult; + } + + // The Last Known Good feature store sometimes gets corrupted by the OS due to a use-after-free bug in fcon.dll + // We fix the corrupted store header to make sure that Windows won't completely wipe feature configurations in case it shifts into LKG mode + // One feature is unfortunately lost to corruption in case this does happen, but it's still much better to lose one than to lose all + public static bool FixLKGStore() + { + try + { + using (var rKey = Registry.LocalMachine.OpenSubKey(@"CurrentControlSet\Control\FeatureManagement\LastKnownGood")) + { + var lkgBlob = (byte[])rKey.GetValue("LKGConfiguration"); + // Header is a simple 32-bit zero + if (BitConverter.ToInt32(lkgBlob, 0) == 0) + return false; + var headerSize = sizeof(int); + var oneConfigSize = Marshal.SizeOf(typeof(RTL_FEATURE_CONFIGURATION)); + var fixedBlob = new byte[lkgBlob.Length - oneConfigSize]; + Array.Copy(lkgBlob, headerSize + oneConfigSize, fixedBlob, headerSize, fixedBlob.Length - headerSize); + rKey.SetValue("LKGConfiguration", fixedBlob); + return true; + } + } catch { return false; } + } + + private static int SetFeatureConfigurationsInRegistry(RTL_FEATURE_CONFIGURATION_UPDATE[] updates, ulong previousStamp) + { + // Stamp behavior and return values are taken from equivalent kernel operations for runtime feature configuration + if (previousStamp > 0) + { + var currentStamp = QueryFeatureConfigurationChangeStamp(); + if (previousStamp != currentStamp) + return unchecked((int)0xC0000001); + } + + try + { + foreach (var update in updates) + { + var isUserPolicy = update.Priority == RTL_FEATURE_CONFIGURATION_PRIORITY.UserPolicy; + var obfuscatedId = ObfuscationHelpers.ObfuscateFeatureId(update.FeatureId).ToString(); + var overrideKey = isUserPolicy ? + @"SYSTEM\CurrentControlSet\Policies\Microsoft\FeatureManagement\Overrides" : + $@"SYSTEM\CurrentControlSet\Control\FeatureManagement\Overrides\{(int)update.Priority}\{obfuscatedId}"; + if (update.Operation == RTL_FEATURE_CONFIGURATION_OPERATION.ResetState) + { + if (isUserPolicy) + { + var rKey = Registry.LocalMachine.OpenSubKey(overrideKey); + if (rKey != null) + { + using (rKey) + rKey.DeleteValue(obfuscatedId); + } + } + else + Registry.LocalMachine.DeleteSubKeyTree(overrideKey, false); + } + else + { + using (var rKey = Registry.LocalMachine.CreateSubKey(overrideKey)) + { + if (update.Operation.HasFlag(RTL_FEATURE_CONFIGURATION_OPERATION.FeatureState)) + { + if (isUserPolicy) + { + rKey.SetValue(obfuscatedId, (int)update.EnabledState); + } + else + { + rKey.SetValue("EnabledState", (int)update.EnabledState); + rKey.SetValue("EnabledStateOptions", (int)update.EnabledStateOptions); + } + } + if (!isUserPolicy && update.Operation.HasFlag(RTL_FEATURE_CONFIGURATION_OPERATION.VariantState)) + { + // Casting these is needed otherwise they end up getting written as strings + rKey.SetValue("Variant", (int)update.Variant); + rKey.SetValue("VariantPayload", (int)update.VariantPayload); + rKey.SetValue("VariantPayloadKind", (int)update.VariantPayloadKind); + } + } + } + } + return 0; + } catch (Exception ex) { return ex.HResult; } + } + + public static int AddFeatureUsageSubscriptionsToRegistry(RTL_FEATURE_USAGE_SUBSCRIPTION_DETAILS[] subscriptions) + { + try + { + foreach (var sub in subscriptions) + { + uint obfuscatedId = ObfuscationHelpers.ObfuscateFeatureId(sub.FeatureId); + using (var rKey = Registry.LocalMachine.CreateSubKey($@"SYSTEM\CurrentControlSet\Control\FeatureManagement\UsageSubscriptions\{obfuscatedId}\{{{Guid.NewGuid()}}}")) + { + rKey.SetValue("ReportingKind", (int)sub.ReportingKind); + rKey.SetValue("ReportingOptions", (int)sub.ReportingOptions); + rKey.SetValue("ReportingTarget", BitConverter.GetBytes(sub.ReportingTarget)); + } + } + return 0; + } + catch (Exception ex) { return ex.HResult; } + } + + public static int RemoveFeatureUsageSubscriptionsFromRegistry(RTL_FEATURE_USAGE_SUBSCRIPTION_DETAILS[] subscriptions) + { + try + { + foreach (var sub in subscriptions) + { + var obfuscatedKey = @"SYSTEM\CurrentControlSet\Control\FeatureManagement\UsageSubscriptions\" + + ObfuscationHelpers.ObfuscateFeatureId(sub.FeatureId).ToString(); + var sKey = Registry.LocalMachine.OpenSubKey(obfuscatedKey, true); + if (sKey != null) + { + var isEmpty = false; + using (sKey) + { + foreach (var subGuid in sKey.GetSubKeyNames()) + { + var toRemove = false; + using (var gKey = sKey.OpenSubKey(subGuid)) + { + if ((int)gKey.GetValue("ReportingKind") == sub.ReportingKind && + BitConverter.ToUInt64((byte[])gKey.GetValue("ReportingTarget"), 0) == sub.ReportingTarget) + toRemove = true; + } + if (toRemove) + sKey.DeleteSubKeyTree(subGuid, false); + } + isEmpty = sKey.SubKeyCount == 0; + } + if (isEmpty) + Registry.LocalMachine.DeleteSubKeyTree(obfuscatedKey, false); + } + } + return 0; + } + catch (Exception ex) { return ex.HResult; } + } + } +} diff --git a/ViVe/FeatureConfigurationAction.cs b/ViVe/FeaturePropertyOverflowException.cs similarity index 63% rename from ViVe/FeatureConfigurationAction.cs rename to ViVe/FeaturePropertyOverflowException.cs index ea36f79..7ebf629 100644 --- a/ViVe/FeatureConfigurationAction.cs +++ b/ViVe/FeaturePropertyOverflowException.cs @@ -1,6 +1,6 @@ /* - ViVe - Vibranium feature configuration library - Copyright (C) 2019 @thebookisclosed + ViVe - Windows feature configuration library + Copyright (C) 2019-2022 @thebookisclosed This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -16,14 +16,15 @@ along with this program. If not, see . */ -namespace Albacore.ViVe +using System; + +namespace Albacore.ViVe.Exceptions { - public enum FeatureConfigurationAction + public class FeaturePropertyOverflowException : Exception { - None = 0, - UpdateEnabledState = 1, - UpdateVariant = 2, - UpdateAll = 3, - Delete = 4 + public FeaturePropertyOverflowException(string propertyName, int maximumValue) + : base($"{propertyName} must not be higher than {maximumValue}.") + { + } } } diff --git a/ViVe/FeatureUsageSubscription.cs b/ViVe/FeatureUsageSubscription.cs deleted file mode 100644 index f8dc7ed..0000000 --- a/ViVe/FeatureUsageSubscription.cs +++ /dev/null @@ -1,28 +0,0 @@ -/* - ViVe - Vibranium feature configuration library - Copyright (C) 2019 @thebookisclosed - - This program is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program. If not, see . - */ - -namespace Albacore.ViVe -{ - public class FeatureUsageSubscription - { - public uint FeatureId { get; set; } - public ushort ReportingKind { get; set; } - public ushort ReportingOptions { get; set; } - public ulong ReportingTarget { get; set; } - } -} diff --git a/ViVe/NativeEnums.cs b/ViVe/NativeEnums.cs new file mode 100644 index 0000000..a7e65a3 --- /dev/null +++ b/ViVe/NativeEnums.cs @@ -0,0 +1,78 @@ +/* + ViVe - Windows feature configuration library + Copyright (C) 2019-2022 @thebookisclosed + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + */ + +using System; + +namespace Albacore.ViVe.NativeEnums +{ + public enum RTL_FEATURE_CONFIGURATION_TYPE : uint + { + Boot = 0, + Runtime = 1 + } + + public enum RTL_FEATURE_ENABLED_STATE : uint + { + Default = 0, + Disabled = 1, + Enabled = 2 + } + + public enum RTL_FEATURE_CONFIGURATION_PRIORITY : uint + { + ImageDefault = 0, + Enrollment = 2, + Service = 4, + User = 8, + UserPolicy = 10, + Test = 12, + ImageOverride = 15 + } + + [Flags] + public enum RTL_FEATURE_VARIANT_PAYLOAD_KIND : uint + { + None = 0, + Resident = 1, + External = 2 + } + + [Flags] + public enum RTL_FEATURE_CONFIGURATION_OPERATION : uint + { + None = 0, + FeatureState = 1, + VariantState = 2, + ResetState = 4 + } + + public enum RTL_FEATURE_ENABLED_STATE_OPTIONS + { + None = 0, + WexpConfig = 1 + } + + public enum BSD_FEATURE_CONFIGURATION_STATE + { + Uninitialized = 0, + BootPending = 1, + LKGPending = 2, + RollbackPending = 3, + Committed = 4 + } +} diff --git a/ViVe/NativeMethods.cs b/ViVe/NativeMethods.Ntdll.cs similarity index 53% rename from ViVe/NativeMethods.cs rename to ViVe/NativeMethods.Ntdll.cs index 22b729b..03ff0b4 100644 --- a/ViVe/NativeMethods.cs +++ b/ViVe/NativeMethods.Ntdll.cs @@ -1,6 +1,6 @@ /* - ViVe - Vibranium feature configuration library - Copyright (C) 2019 @thebookisclosed + ViVe - Windows feature configuration library + Copyright (C) 2019-2022 @thebookisclosed This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -16,53 +16,65 @@ along with this program. If not, see . */ +using Albacore.ViVe.NativeEnums; +using Albacore.ViVe.NativeStructs; using System; using System.Runtime.InteropServices; -namespace Albacore.ViVe +namespace Albacore.ViVe.NativeMethods { public delegate void FeatureConfigurationChangeCallback(IntPtr Context); - public static class NativeMethods + public static class Ntdll { + // Kernel supports null pointer for change stamp ref, unlike single feature query [DllImport("ntdll.dll")] - public static extern int RtlQueryAllFeatureConfigurations( - FeatureConfigurationSection sectionType, - ref ulong changeStamp, - IntPtr buffer, - ref int featureCount + public unsafe static extern int RtlQueryAllFeatureConfigurations( + RTL_FEATURE_CONFIGURATION_TYPE featureConfigurationType, + ulong* changeStamp, + RTL_FEATURE_CONFIGURATION* featureConfigurations, + out int featureConfigurationCount ); [DllImport("ntdll.dll")] public static extern int RtlQueryFeatureConfiguration( uint featureId, - FeatureConfigurationSection sectionType, + RTL_FEATURE_CONFIGURATION_TYPE featureConfigurationType, ref ulong changeStamp, - IntPtr buffer + out RTL_FEATURE_CONFIGURATION featureConfiguration ); [DllImport("ntdll.dll")] public static extern ulong RtlQueryFeatureConfigurationChangeStamp(); [DllImport("ntdll.dll")] - public static extern int RtlQueryFeatureUsageNotificationSubscriptions( - IntPtr buffer, - ref int subscriptionCount + public unsafe static extern int RtlQueryFeatureUsageNotificationSubscriptions( + RTL_FEATURE_USAGE_SUBSCRIPTION_DETAILS* subscriptions, + out int subscriptionCount ); + // Kernel treats pointer to 0 the same as a null pointer, no alternate signature required [DllImport("ntdll.dll")] public static extern int RtlSetFeatureConfigurations( - ref ulong changeStamp, - FeatureConfigurationSection sectionType, - byte[] buffer, - int featureCount + ref ulong previousChangeStamp, + RTL_FEATURE_CONFIGURATION_TYPE featureConfigurationType, + RTL_FEATURE_CONFIGURATION_UPDATE[] featureConfigurations, + int featureConfigurationCount + ); + + [DllImport("ntdll.dll")] + public static extern int RtlRegisterFeatureConfigurationChangeNotification( + FeatureConfigurationChangeCallback callback, + IntPtr context, + IntPtr waitForChangeStamp, + out IntPtr subscription ); [DllImport("ntdll.dll")] public static extern int RtlRegisterFeatureConfigurationChangeNotification( FeatureConfigurationChangeCallback callback, IntPtr context, - IntPtr unknown, + ref ulong waitForChangeStamp, out IntPtr subscription ); @@ -73,35 +85,36 @@ IntPtr subscription [DllImport("ntdll.dll")] public static extern int RtlSubscribeForFeatureUsageNotification( - byte[] buffer, + RTL_FEATURE_USAGE_SUBSCRIPTION_DETAILS[] subscriptions, int subscriptionCount ); [DllImport("ntdll.dll")] public static extern int RtlUnsubscribeFromFeatureUsageNotifications( - byte[] buffer, + RTL_FEATURE_USAGE_SUBSCRIPTION_DETAILS[] subscriptions, int subscriptionCount ); [DllImport("ntdll.dll")] public static extern int RtlNotifyFeatureUsage( - byte[] buffer + ref RTL_FEATURE_USAGE_REPORT report ); + // BSD Items can have varying sizes, int signature is enough for our needs though [DllImport("ntdll.dll")] public static extern int RtlSetSystemBootStatus( - int infoClass, - ref int state, - int stateSize, - IntPtr output + int bsdItemType, + ref int data, + int dataLength, + IntPtr returnLength ); [DllImport("ntdll.dll")] public static extern int RtlGetSystemBootStatus( - int infoClass, - ref int state, - int stateSize, - IntPtr output + int bsdItemType, + out int data, + int dataLength, + IntPtr returnLength ); } } diff --git a/ViVe/NativeStructs.cs b/ViVe/NativeStructs.cs new file mode 100644 index 0000000..cdd91e1 --- /dev/null +++ b/ViVe/NativeStructs.cs @@ -0,0 +1,234 @@ +/* + ViVe - Windows feature configuration library + Copyright (C) 2019-2022 @thebookisclosed + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + */ + +using Albacore.ViVe.Exceptions; +using Albacore.ViVe.NativeEnums; +using System.Runtime.InteropServices; + +namespace Albacore.ViVe.NativeStructs +{ + [StructLayout(LayoutKind.Sequential)] + public struct RTL_FEATURE_USAGE_REPORT + { + public uint FeatureId; + public ushort ReportingKind; + public ushort ReportingOptions; + } + + [StructLayout(LayoutKind.Sequential)] + public struct RTL_FEATURE_CONFIGURATION + { + public uint FeatureId; + public uint CompactState; + public uint VariantPayload; + + public RTL_FEATURE_CONFIGURATION_PRIORITY Priority + { + get + { + return (RTL_FEATURE_CONFIGURATION_PRIORITY)(CompactState & 0xF); + } + set + { + if ((uint)value > 15) + throw new FeaturePropertyOverflowException("Priority", 15); + CompactState = (CompactState & 0xFFFFFFF0) | (uint)value; + } + } + + public RTL_FEATURE_ENABLED_STATE EnabledState + { + get + { + return (RTL_FEATURE_ENABLED_STATE)((CompactState & 0x30) >> 4); + } + set + { + if ((uint)value > 2) + throw new FeaturePropertyOverflowException("EnabledState", 2); + CompactState = (CompactState & 0xFFFFFFCF) | ((uint)value << 4); + } + } + + public bool IsWexpConfiguration + { + get + { + return ((CompactState & 0x40) >> 6) == 1; + } + set + { + CompactState = (CompactState & 0xFFFFFFBF) | ((value ? (uint)1 : 0) << 6); + } + } + + public bool HasSubscriptions + { + get + { + return ((CompactState & 0x80) >> 7) == 1; + } + set + { + CompactState = (CompactState & 0xFFFFFF7F) | ((value ? (uint)1 : 0) << 7); + } + } + + public uint Variant + { + get + { + return (CompactState & 0x3F00) >> 8; + } + set + { + if (value > 63) + throw new FeaturePropertyOverflowException("Variant", 63); + CompactState = (CompactState & 0xFFFFC0FF) | (value << 8); + } + } + + public RTL_FEATURE_VARIANT_PAYLOAD_KIND VariantPayloadKind + { + get + { + return (RTL_FEATURE_VARIANT_PAYLOAD_KIND)((CompactState & 0xC000) >> 14); + } + set + { + if ((uint)value > 3) + throw new FeaturePropertyOverflowException("VariantPayloadKind", 3); + CompactState = (CompactState & 0xFFFF3FFF) | ((uint)value << 14); + } + } + } + + [StructLayout(LayoutKind.Sequential)] + public struct RTL_FEATURE_USAGE_SUBSCRIPTION_DETAILS + { + public uint FeatureId; + public ushort ReportingKind; + public ushort ReportingOptions; + public ulong ReportingTarget; + } + + [StructLayout(LayoutKind.Sequential)] + public struct RTL_FEATURE_CONFIGURATION_UPDATE + { + public uint FeatureId; + private RTL_FEATURE_CONFIGURATION_PRIORITY _priority; + private RTL_FEATURE_ENABLED_STATE _enabledState; + private RTL_FEATURE_ENABLED_STATE_OPTIONS _enabledStateOptions; + private uint _variant; + private RTL_FEATURE_VARIANT_PAYLOAD_KIND _variantPayloadKind; + public uint VariantPayload; + private RTL_FEATURE_CONFIGURATION_OPERATION _operation; + + public RTL_FEATURE_CONFIGURATION_PRIORITY Priority + { + get + { + return _priority; + } + set + { + if ((uint)value > 15) + throw new FeaturePropertyOverflowException("Priority", 15); + _priority = value; + } + } + + public RTL_FEATURE_ENABLED_STATE EnabledState + { + get + { + return _enabledState; + } + set + { + if ((uint)value > 2) + throw new FeaturePropertyOverflowException("EnabledState", 2); + _enabledState = value; + } + } + + public RTL_FEATURE_ENABLED_STATE_OPTIONS EnabledStateOptions + { + get + { + return _enabledStateOptions; + } + set + { + if ((uint)value > 1) + throw new FeaturePropertyOverflowException("EnabledStateOptions", 1); + _enabledStateOptions = value; + } + } + + public uint Variant + { + get + { + return _variant; + } + set + { + if (value > 63) + throw new FeaturePropertyOverflowException("Variant", 63); + _variant = value; + } + } + + public RTL_FEATURE_VARIANT_PAYLOAD_KIND VariantPayloadKind + { + get + { + return _variantPayloadKind; + } + set + { + if ((uint)value > 3) + throw new FeaturePropertyOverflowException("VariantPayloadKind", 3); + _variantPayloadKind = value; + } + } + + public RTL_FEATURE_CONFIGURATION_OPERATION Operation + { + get + { + return _operation; + } + set + { + if ((uint)value > 4) + throw new FeaturePropertyOverflowException("Operation", 4); + _operation = value; + } + } + + public bool UserPolicyPriorityCompatible + { + get + { + return !((uint)EnabledStateOptions != 0 || _variant != 0 || (uint)_variantPayloadKind != 0 || VariantPayload != 0); + } + } + } +} diff --git a/ViVe/ObfuscationHelpers.cs b/ViVe/ObfuscationHelpers.cs new file mode 100644 index 0000000..25bbe7b --- /dev/null +++ b/ViVe/ObfuscationHelpers.cs @@ -0,0 +1,44 @@ +/* + ViVe - Windows feature configuration library + Copyright (C) 2019-2022 @thebookisclosed + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + */ + +namespace Albacore.ViVe +{ + public static class ObfuscationHelpers + { + private static uint SwapBytes(uint x) + { + x = (x >> 16) | (x << 16); + return ((x & 0xFF00FF00) >> 8) | ((x & 0x00FF00FF) << 8); + } + + private static uint RotateRight32(uint value, int shift) + { + return (value >> shift) | (value << (32 - shift)); + } + + public static uint ObfuscateFeatureId(uint featureId) + { + return RotateRight32(SwapBytes(featureId ^ 0x74161A4E) ^ 0x8FB23D4F, -1) ^ 0x833EA8FF; + } + + public static uint DeobfuscateFeatureId(uint featureId) + { + return SwapBytes(RotateRight32(featureId ^ 0x833EA8FF, 1) ^ 0x8FB23D4F) ^ 0x74161A4E; + } + } +} diff --git a/ViVe/Properties/AssemblyInfo.cs b/ViVe/Properties/AssemblyInfo.cs index 2833174..6cbc780 100644 --- a/ViVe/Properties/AssemblyInfo.cs +++ b/ViVe/Properties/AssemblyInfo.cs @@ -10,7 +10,7 @@ [assembly: AssemblyConfiguration("")] [assembly: AssemblyCompany("")] [assembly: AssemblyProduct("Albacore.ViVe")] -[assembly: AssemblyCopyright("Copyright © @thebookisclosed 2020")] +[assembly: AssemblyCopyright("Copyright © @thebookisclosed 2022")] [assembly: AssemblyTrademark("")] [assembly: AssemblyCulture("")] @@ -32,5 +32,5 @@ // You can specify all the values or you can default the Build and Revision Numbers // by using the '*' as shown below: // [assembly: AssemblyVersion("1.0.*")] -[assembly: AssemblyVersion("2020.8.11.1001")] -[assembly: AssemblyFileVersion("2020.8.11.1001")] +[assembly: AssemblyVersion("2022.6.27.0")] +[assembly: AssemblyFileVersion("2022.6.27.0")] diff --git a/ViVe/RtlDataHelpers.cs b/ViVe/RtlDataHelpers.cs deleted file mode 100644 index 2bb2f88..0000000 --- a/ViVe/RtlDataHelpers.cs +++ /dev/null @@ -1,95 +0,0 @@ -/* - ViVe - Vibranium feature configuration library - Copyright (C) 2019 @thebookisclosed - - This program is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program. If not, see . - */ - -using System.Collections.Generic; -using System.IO; - -namespace Albacore.ViVe -{ - public static class RtlDataHelpers - { - public static byte[] SerializeFeatureConfigurations(List configurations) - { - byte[] retArray = new byte[configurations.Count * 32]; - using (MemoryStream ms = new MemoryStream(retArray, true)) - { - using (BinaryWriter bw = new BinaryWriter(ms)) - foreach (var thing in configurations) - { - bw.Write(thing.FeatureId); - bw.Write(thing.Group); - bw.Write((int)thing.EnabledState); - bw.Write(thing.EnabledStateOptions); - bw.Write(thing.Variant); - bw.Write(thing.VariantPayloadKind); - bw.Write(thing.VariantPayload); - bw.Write((int)thing.Action); - } - } - return retArray; - } - - public static byte[] SerializeFeatureUsageSubscriptions(List subscriptions) - { - byte[] retArray = new byte[subscriptions.Count * 16]; - using (MemoryStream ms = new MemoryStream(retArray, true)) - { - using (BinaryWriter bw = new BinaryWriter(ms)) - foreach (var thing in subscriptions) - { - bw.Write(thing.FeatureId); - bw.Write(thing.ReportingKind); - bw.Write(thing.ReportingOptions); - bw.Write(thing.ReportingTarget); - } - } - return retArray; - } - - public static byte[] SerializeFeatureUsageReport(FeatureUsageReport report) - { - byte[] retArray = new byte[8]; - using (MemoryStream ms = new MemoryStream(retArray, true)) - { - using (BinaryWriter bw = new BinaryWriter(ms)) - { - bw.Write(report.FeatureId); - bw.Write(report.ReportingKind); - bw.Write(report.ReportingOptions); - } - } - return retArray; - } - - private static uint SwapBytes(uint x) - { - x = (x >> 16) | (x << 16); - return ((x & 0xFF00FF00) >> 8) | ((x & 0x00FF00FF) << 8); - } - - private static uint RotateRight32(uint value, int shift) - { - return (value >> shift) | (value << (32 - shift)); - } - - public static uint GetObfuscatedFeatureId(uint featureId) - { - return RotateRight32(SwapBytes(featureId ^ 0x74161A4E) ^ 0x8FB23D4F, -1) ^ 0x833EA8FF; - } - } -} diff --git a/ViVe/RtlFeatureManager.cs b/ViVe/RtlFeatureManager.cs deleted file mode 100644 index f09da93..0000000 --- a/ViVe/RtlFeatureManager.cs +++ /dev/null @@ -1,320 +0,0 @@ -/* - ViVe - Vibranium feature configuration library - Copyright (C) 2019 @thebookisclosed - - This program is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program. If not, see . - */ - -using Microsoft.Win32; -using System; -using System.Collections.Generic; -using System.IO; -using System.Linq; -using System.Runtime.InteropServices; - -namespace Albacore.ViVe -{ - public static class RtlFeatureManager - { - public static List QueryAllFeatureConfigurations() - { - ulong dummy = 0; - return QueryAllFeatureConfigurations(FeatureConfigurationSection.Runtime, ref dummy); - } - - public static List QueryAllFeatureConfigurations(FeatureConfigurationSection section) - { - ulong dummy = 0; - return QueryAllFeatureConfigurations(section, ref dummy); - } - - public static List QueryAllFeatureConfigurations(FeatureConfigurationSection section, ref ulong changeStamp) - { - int featureCount = 0; - NativeMethods.RtlQueryAllFeatureConfigurations(section, ref changeStamp, IntPtr.Zero, ref featureCount); - if (featureCount == 0) - return null; - // One feature config is 12 bytes long - IntPtr rawBuf = Marshal.AllocHGlobal(featureCount * 12); - int hRes = NativeMethods.RtlQueryAllFeatureConfigurations(section, ref changeStamp, rawBuf, ref featureCount); - if (hRes != 0) - { - Marshal.FreeHGlobal(rawBuf); - return null; - } - byte[] buf = new byte[featureCount * 12]; - Marshal.Copy(rawBuf, buf, 0, buf.Length); - Marshal.FreeHGlobal(rawBuf); - var allFeatureConfigs = new List(); - using (MemoryStream ms = new MemoryStream(buf, false)) - { - using (BinaryReader br = new BinaryReader(ms)) - { - for (int i = 0; i < featureCount; i++) - { - uint featureId = br.ReadUInt32(); - int compactState = br.ReadInt32(); - - allFeatureConfigs.Add(new FeatureConfiguration() { - FeatureId = featureId, - Group = compactState & 0xF, - EnabledState = (FeatureEnabledState)((compactState & 0x30) >> 4), - EnabledStateOptions = (compactState & 0x40) >> 6, - Variant = (compactState & 0x3F00) >> 8, - VariantPayloadKind = (compactState & 0xC000) >> 14, - VariantPayload = br.ReadInt32() }); - } - } - } - return allFeatureConfigs; - } - - public static FeatureConfiguration QueryFeatureConfiguration(uint featureId) - { - ulong dummy = 0; - return QueryFeatureConfiguration(featureId, FeatureConfigurationSection.Runtime, ref dummy); - } - - public static FeatureConfiguration QueryFeatureConfiguration(uint featureId, FeatureConfigurationSection section) - { - ulong dummy = 0; - return QueryFeatureConfiguration(featureId, section, ref dummy); - } - - public static FeatureConfiguration QueryFeatureConfiguration(uint featureId, FeatureConfigurationSection section, ref ulong changeStamp) - { - // One feature config is 12 bytes long - IntPtr rawBuf = Marshal.AllocHGlobal(12); - int hRes = NativeMethods.RtlQueryFeatureConfiguration(featureId, section, ref changeStamp, rawBuf); - if (hRes != 0) - { - Marshal.FreeHGlobal(rawBuf); - return null; - } - byte[] buf = new byte[12]; - Marshal.Copy(rawBuf, buf, 0, buf.Length); - Marshal.FreeHGlobal(rawBuf); - int compactState = BitConverter.ToInt32(buf, 4); - return new FeatureConfiguration() { - FeatureId = BitConverter.ToUInt32(buf, 0), - Group = compactState & 0xF, - EnabledState = (FeatureEnabledState)((compactState & 0x30) >> 4), - EnabledStateOptions = (compactState & 0x40) >> 6, - Variant = (compactState & 0x3F00) >> 8, - VariantPayloadKind = (compactState & 0xC000) >> 14, - VariantPayload = BitConverter.ToInt32(buf, 8) }; - } - - public static ulong QueryFeatureConfigurationChangeStamp() - { - return NativeMethods.RtlQueryFeatureConfigurationChangeStamp(); - } - - public static int SetLiveFeatureConfigurations(List configurations) - { - ulong dummy = 0; - return SetLiveFeatureConfigurations(configurations, FeatureConfigurationSection.Runtime, ref dummy); - } - - public static int SetLiveFeatureConfigurations(List configurations, FeatureConfigurationSection section) - { - ulong dummy = 0; - return SetLiveFeatureConfigurations(configurations, section, ref dummy); - } - - public static int SetLiveFeatureConfigurations(List configurations, FeatureConfigurationSection section, ref ulong changeStamp) - { - return NativeMethods.RtlSetFeatureConfigurations(ref changeStamp, section, RtlDataHelpers.SerializeFeatureConfigurations(configurations), configurations.Count); - } - - public static IntPtr RegisterFeatureConfigurationChangeNotification(FeatureConfigurationChangeCallback callback) - { - return RegisterFeatureConfigurationChangeNotification(callback, IntPtr.Zero); - } - - public static IntPtr RegisterFeatureConfigurationChangeNotification(FeatureConfigurationChangeCallback callback, IntPtr context) - { - NativeMethods.RtlRegisterFeatureConfigurationChangeNotification(callback, context, IntPtr.Zero, out IntPtr sub); - return sub; - } - - public static int UnregisterFeatureConfigurationChangeNotification(IntPtr notification) - { - return NativeMethods.RtlUnregisterFeatureConfigurationChangeNotification(notification); - } - - public static List QueryFeatureUsageSubscriptions() - { - int subCount = 0; - NativeMethods.RtlQueryFeatureUsageNotificationSubscriptions(IntPtr.Zero, ref subCount); - if (subCount == 0) - return null; - // One feature subscription is 16 bytes long - IntPtr rawBuf = Marshal.AllocHGlobal(subCount * 16); - int hRes = NativeMethods.RtlQueryFeatureUsageNotificationSubscriptions(rawBuf, ref subCount); - if (hRes != 0) - return null; - byte[] buf = new byte[subCount * 16]; - Marshal.Copy(rawBuf, buf, 0, buf.Length); - Marshal.FreeHGlobal(rawBuf); - var allSubscriptions = new List(); - using (MemoryStream ms = new MemoryStream(buf, false)) - { - using (BinaryReader br = new BinaryReader(ms)) - { - for (int i = 0; i < subCount; i++) - { - allSubscriptions.Add(new FeatureUsageSubscription() { - FeatureId = br.ReadUInt32(), - ReportingKind = br.ReadUInt16(), - ReportingOptions = br.ReadUInt16(), - ReportingTarget = br.ReadUInt64() - }); - } - } - } - return allSubscriptions; - } - - public static int AddLiveFeatureUsageSubscriptions(List subscriptions) - { - return NativeMethods.RtlSubscribeForFeatureUsageNotification(RtlDataHelpers.SerializeFeatureUsageSubscriptions(subscriptions), subscriptions.Count); - } - - public static int RemoveLiveFeatureUsageSubscriptions(List subscriptions) - { - return NativeMethods.RtlUnsubscribeFromFeatureUsageNotifications(RtlDataHelpers.SerializeFeatureUsageSubscriptions(subscriptions), subscriptions.Count); - } - - public static int NotifyFeatureUsage(FeatureUsageReport report) - { - return NativeMethods.RtlNotifyFeatureUsage(RtlDataHelpers.SerializeFeatureUsageReport(report)); - } - - public static int SetBootFeatureConfigurationState(ref int state) - { - return NativeMethods.RtlSetSystemBootStatus(17, ref state, sizeof(int), IntPtr.Zero); - } - - public static int GetBootFeatureConfigurationState(ref int state) - { - return NativeMethods.RtlGetSystemBootStatus(17, ref state, sizeof(int), IntPtr.Zero); - } - - public static bool SetBootFeatureConfigurations(List configurations) - { - try - { - foreach (var config in configurations) - { - uint obfuscatedId = RtlDataHelpers.GetObfuscatedFeatureId(config.FeatureId); - var obfuscatedKey = $@"SYSTEM\CurrentControlSet\Control\FeatureManagement\Overrides\{config.Group}\{obfuscatedId}"; - if (config.Action == FeatureConfigurationAction.Delete) - Registry.LocalMachine.DeleteSubKeyTree(obfuscatedKey, false); - else - { - using (var rKey = Registry.LocalMachine.CreateSubKey(obfuscatedKey)) - { - if ((config.Action & FeatureConfigurationAction.UpdateEnabledState) == FeatureConfigurationAction.UpdateEnabledState) - { - rKey.SetValue("EnabledState", (int)config.EnabledState); - rKey.SetValue("EnabledStateOptions", config.EnabledStateOptions); - } - if ((config.Action & FeatureConfigurationAction.UpdateVariant) == FeatureConfigurationAction.UpdateVariant) - { - rKey.SetValue("Variant", config.Variant); - rKey.SetValue("VariantPayload", config.VariantPayload); - rKey.SetValue("VariantPayloadKind", config.VariantPayloadKind); - } - } - } - } - return true; - } catch { return false; } - } - - public static bool RemoveBootFeatureConfigurations(List configurations) - { - try - { - foreach (var config in configurations) - { - uint obfuscatedId = RtlDataHelpers.GetObfuscatedFeatureId(config.FeatureId); - Registry.LocalMachine.DeleteSubKeyTree($@"SYSTEM\CurrentControlSet\Control\FeatureManagement\Overrides\{config.Group}\{obfuscatedId}", false); - } - return true; - } - catch { return false; } - } - - public static bool AddBootFeatureUsageSubscriptions(List subscriptions) - { - try - { - foreach (var sub in subscriptions) - { - uint obfuscatedId = RtlDataHelpers.GetObfuscatedFeatureId(sub.FeatureId); - using (var rKey = Registry.LocalMachine.CreateSubKey($@"SYSTEM\CurrentControlSet\Control\FeatureManagement\UsageSubscriptions\{obfuscatedId}\{{{Guid.NewGuid()}}}")) - { - rKey.SetValue("ReportingKind", (int)sub.ReportingKind); - rKey.SetValue("ReportingOptions", (int)sub.ReportingOptions); - rKey.SetValue("ReportingTarget", BitConverter.GetBytes(sub.ReportingTarget)); - } - } - return true; - } - catch { return false; } - } - - public static bool RemoveBootFeatureUsageSubscriptions(List subscriptions) - { - try - { - string[] bootSubs; - using (var rKey = Registry.LocalMachine.OpenSubKey(@"SYSTEM\CurrentControlSet\Control\FeatureManagement\UsageSubscriptions")) - bootSubs = rKey.GetSubKeyNames(); - foreach (var sub in subscriptions) - { - var obfuscatedKey = RtlDataHelpers.GetObfuscatedFeatureId(sub.FeatureId).ToString(); - if (bootSubs.Contains(obfuscatedKey)) - { - bool isEmpty = false; - obfuscatedKey = @"SYSTEM\CurrentControlSet\Control\FeatureManagement\UsageSubscriptions\" + obfuscatedKey; - using (var sKey = Registry.LocalMachine.OpenSubKey(obfuscatedKey, true)) - { - foreach (var subGuid in sKey.GetSubKeyNames()) - { - bool toRemove = false; - using (var gKey = sKey.OpenSubKey(subGuid)) - { - if ((int)gKey.GetValue("ReportingKind") == sub.ReportingKind && - (int)gKey.GetValue("ReportingOptions") == sub.ReportingOptions && - BitConverter.ToUInt64((byte[])gKey.GetValue("ReportingTarget"), 0) == sub.ReportingTarget) - toRemove = true; - } - if (toRemove) - sKey.DeleteSubKeyTree(subGuid, false); - } - isEmpty = sKey.SubKeyCount == 0; - } - if (isEmpty) - Registry.LocalMachine.DeleteSubKeyTree(obfuscatedKey, false); - } - } - return true; - } - catch { return false; } - } - } -} diff --git a/ViVe/ViVe.csproj b/ViVe/ViVe.csproj index 1336b3d..50ed583 100644 --- a/ViVe/ViVe.csproj +++ b/ViVe/ViVe.csproj @@ -21,6 +21,7 @@ DEBUG;TRACE prompt 4 + true pdbonly @@ -29,6 +30,7 @@ TRACE prompt 4 + true @@ -41,16 +43,13 @@ - - - - - - - + + + + - - + + \ No newline at end of file diff --git a/ViVeTool/ArgumentBlock.cs b/ViVeTool/ArgumentBlock.cs new file mode 100644 index 0000000..6654963 --- /dev/null +++ b/ViVeTool/ArgumentBlock.cs @@ -0,0 +1,276 @@ +/* + ViVe - Windows feature configuration library + Copyright (C) 2019-2022 @thebookisclosed + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + */ + +using Albacore.ViVe.NativeEnums; +using System; +using System.Collections.Generic; + +namespace Albacore.ViVeTool +{ + static class ArgumentBlock + { + internal static FeatureConfigurationTypeEx? Store; + internal static List IdList; + internal static FeatureConigurationProperties FeatureConigurationProperties; + internal static SubscriptionProperties SubscriptionProperties; +#if SET_LKG_COMMAND + internal static BSD_FEATURE_CONFIGURATION_STATE? LKGStatus; +#endif + internal static string FileName; + internal static bool ImportReplace; + internal static bool HelpMode; + + internal static bool ShouldUseBothStores { get { return !Store.HasValue || Store.Value == FeatureConfigurationTypeEx.Both; } } + + internal static void Initialize(string[] args, ArgumentBlockFlags flags) + { + // Fast help path + if (args.Length == 2 && (args[1] == "/?" || string.Compare(args[1], "/help", true) == 0)) + { + HelpMode = true; + return; + } + + if (flags != 0 && + (flags.HasFlag(ArgumentBlockFlags.EnabledStateOptions) || + flags.HasFlag(ArgumentBlockFlags.Variant) || + flags.HasFlag(ArgumentBlockFlags.VariantPayloadKind) || + flags.HasFlag(ArgumentBlockFlags.VariantPayload) || + flags.HasFlag(ArgumentBlockFlags.Priority))) + FeatureConigurationProperties = new FeatureConigurationProperties(); + + if (flags != 0 && + (flags.HasFlag(ArgumentBlockFlags.ReportingKind) || + flags.HasFlag(ArgumentBlockFlags.ReportingOptions) || + flags.HasFlag(ArgumentBlockFlags.ReportingTarget))) + SubscriptionProperties = new SubscriptionProperties(); + + var bothStoresArgAllowed = flags.HasFlag(ArgumentBlockFlags.AllowBothStoresArgument); + + for (int i = 1; i < args.Length && flags != 0; i++) + { + var firstSc = args[i].IndexOf(':'); + //if (firstSc == -1) continue; + var hasValue = firstSc != -1; + var lower = args[i].ToLowerInvariant(); + var key = hasValue ? lower.Substring(0, firstSc) : lower; + var value = hasValue ? args[i].Substring(firstSc + 1) : null; + if (flags.HasFlag(ArgumentBlockFlags.Store) && key == "/store") + { + if (Enum.TryParse(value, true, out FeatureConfigurationTypeEx parsedStore)) + { + if (!bothStoresArgAllowed && parsedStore == FeatureConfigurationTypeEx.Both) + { + ConsoleEx.WriteErrorLine(Properties.Resources.InvalidEnumSpecInScenario, value, "Store"); + HelpMode = true; + return; + } + Store = parsedStore; + } + else + { + ConsoleEx.WriteErrorLine(Properties.Resources.InvalidEnumSpec, value, "Store"); + HelpMode = true; + return; + } + flags &= ~ArgumentBlockFlags.Store; + } + else if (flags.HasFlag(ArgumentBlockFlags.IdList) && key == "/id") + { + if (IdList == null) + IdList = new List(); + foreach (var strId in value.Split(',')) + { + if (TryParseDecHexUint(strId, out uint parsedId)) + IdList.Add(parsedId); + else + Console.WriteLine("Unable to parse feature ID: {0}", strId); + } + flags &= ~ArgumentBlockFlags.IdList; + } + else if (flags.HasFlag(ArgumentBlockFlags.NameList) && key == "/name") + { + if (IdList == null) + IdList = new List(); + var foundIds = FeatureNaming.FindIdsForNames(value.Split(',')); + if (foundIds != null) + IdList.AddRange(foundIds); + flags &= ~ArgumentBlockFlags.NameList; + } + else if (flags.HasFlag(ArgumentBlockFlags.EnabledStateOptions) && key == "/experiment") + { + FeatureConigurationProperties.EnabledStateOptions = RTL_FEATURE_ENABLED_STATE_OPTIONS.WexpConfig; + flags &= ~ArgumentBlockFlags.EnabledStateOptions; + } + else if (flags.HasFlag(ArgumentBlockFlags.Variant) && key == "/variant") + { + if (TryParseDecHexUint(value, out uint parsedVariant)) + FeatureConigurationProperties.Variant = parsedVariant; + flags &= ~ArgumentBlockFlags.Variant; + } + else if (flags.HasFlag(ArgumentBlockFlags.VariantPayloadKind) && key == "/variantpayloadkind") + { + if (!Enum.TryParse(value, true, out RTL_FEATURE_VARIANT_PAYLOAD_KIND parsedKind)) + { + ConsoleEx.WriteErrorLine(Properties.Resources.InvalidEnumSpec, value, "Variant Payload Kind"); + HelpMode = true; + return; + } + FeatureConigurationProperties.VariantPayloadKind = parsedKind; + flags &= ~ArgumentBlockFlags.VariantPayloadKind; + } + else if (flags.HasFlag(ArgumentBlockFlags.VariantPayload) && key == "/variantpayload") + { + if (TryParseDecHexUint(value, out uint parsedPayload)) + FeatureConigurationProperties.VariantPayload = parsedPayload; + flags &= ~ArgumentBlockFlags.VariantPayload; + } + else if (flags.HasFlag(ArgumentBlockFlags.Priority) && key == "/priority") + { + if (!Enum.TryParse(value, true, out RTL_FEATURE_CONFIGURATION_PRIORITY parsedPriority)) + { + ConsoleEx.WriteErrorLine(Properties.Resources.InvalidEnumSpec, value, "Priority"); + HelpMode = true; + return; + } + FeatureConigurationProperties.Priority = parsedPriority; + flags &= ~ArgumentBlockFlags.Priority; + } + else if (flags.HasFlag(ArgumentBlockFlags.ReportingKind) && key == "/reportingkind") + { + if (TryParseDecHexUshort(value, out ushort parsedKind)) + SubscriptionProperties.ReportingKind = parsedKind; + flags &= ~ArgumentBlockFlags.ReportingKind; + } + else if (flags.HasFlag(ArgumentBlockFlags.ReportingOptions) && key == "/reportingoptions") + { + if (TryParseDecHexUshort(value, out ushort parsedOptions)) + SubscriptionProperties.ReportingOptions = parsedOptions; + flags &= ~ArgumentBlockFlags.ReportingOptions; + } + else if (flags.HasFlag(ArgumentBlockFlags.ReportingTarget) && key == "/reportingtarget") + { + if (ulong.TryParse(value, System.Globalization.NumberStyles.HexNumber, System.Globalization.CultureInfo.InvariantCulture, out ulong parsedTarget)) + SubscriptionProperties.ReportingTarget = parsedTarget; + flags &= ~ArgumentBlockFlags.ReportingTarget; + } +#if SET_LKG_COMMAND + else if (flags.HasFlag(ArgumentBlockFlags.LKGStatus) && key == "/status") + { + if (!Enum.TryParse(value, true, out BSD_FEATURE_CONFIGURATION_STATE parsedStatus)) + { + ConsoleEx.WriteErrorLine(Properties.Resources.InvalidEnumSpec, value, "LKG Status"); + HelpMode = true; + return; + } + LKGStatus = parsedStatus; + flags &= ~ArgumentBlockFlags.LKGStatus; + } +#endif + else if (flags.HasFlag(ArgumentBlockFlags.FileName) && key == "/filename") + { + FileName = value; + flags &= ~ArgumentBlockFlags.FileName; + } + else if (flags.HasFlag(ArgumentBlockFlags.ImportReplace) && key == "/replace") + { + ImportReplace = true; + flags &= ~ArgumentBlockFlags.ImportReplace; + } + else if (key == "/?" || string.Compare(key, "/help", true) == 0) + { + HelpMode = true; + return; + } + else + { + ConsoleEx.WriteWarnLine(Properties.Resources.UnrecognizedParameter, key); + } + } + } + + private static bool TryParseDecHexUint(string input, out uint output) + { + bool success; + if (input.StartsWith("0x")) + success = uint.TryParse(input.Substring(2), System.Globalization.NumberStyles.HexNumber, System.Globalization.CultureInfo.InvariantCulture, out output); + else + success = uint.TryParse(input, out output); + return success; + } + + private static bool TryParseDecHexUshort(string input, out ushort output) + { + bool success; + if (input.StartsWith("0x")) + success = ushort.TryParse(input.Substring(2), System.Globalization.NumberStyles.HexNumber, System.Globalization.CultureInfo.InvariantCulture, out output); + else + success = ushort.TryParse(input, out output); + return success; + } + } + + internal class FeatureConigurationProperties + { + internal RTL_FEATURE_ENABLED_STATE_OPTIONS EnabledStateOptions; + internal uint Variant; + internal RTL_FEATURE_VARIANT_PAYLOAD_KIND VariantPayloadKind; + internal uint VariantPayload; + internal RTL_FEATURE_CONFIGURATION_PRIORITY? Priority; + } + + internal class SubscriptionProperties + { + internal ushort ReportingKind; + internal ushort ReportingOptions; + internal ulong ReportingTarget; + } + + [Flags] + enum ArgumentBlockFlags + { + Store = 1, + IdList = 2, + NameList = 4, + EnabledStateOptions = 8, + Variant = 16, + VariantPayloadKind = 32, + VariantPayload = 64, + Priority = 128, + ReportingKind = 256, + ReportingOptions = 512, + ReportingTarget = 1024, + AllowBothStoresArgument = 2048, +#if SET_LKG_COMMAND + LKGStatus = 4096, +#endif + FileName = 8192, + ImportReplace = 16384, + Identifiers = IdList | NameList, + FeatureConfigurationProperties = EnabledStateOptions | Variant | VariantPayloadKind | VariantPayload | Priority, + SubscriptionProperties = ReportingKind | ReportingOptions | ReportingTarget, + Export = Store | AllowBothStoresArgument | FileName + } + + enum FeatureConfigurationTypeEx : uint + { + Boot = 0, + Runtime = 1, + Both = 2 + } +} diff --git a/ViVeTool/ConsoleEx.cs b/ViVeTool/ConsoleEx.cs new file mode 100644 index 0000000..8d2400c --- /dev/null +++ b/ViVeTool/ConsoleEx.cs @@ -0,0 +1,72 @@ +/* + ViVe - Windows feature configuration library + Copyright (C) 2019-2022 @thebookisclosed + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + */ + +using System; + +namespace Albacore.ViVeTool +{ + public static class ConsoleEx + { + public static void WriteErrorLine(string text, params object[] parameters) + { + var formatted = string.Format(text, parameters); + WriteErrorLine(formatted); + } + + public static void WriteErrorLine(string text) + { + var defaultFg = Console.ForegroundColor; + Console.ForegroundColor = ConsoleColor.Red; + Console.WriteLine(text); + Console.ForegroundColor = defaultFg; + } + + public static void WriteWarnLine(string text, params object[] parameters) + { + var formatted = string.Format(text, parameters); + WriteWarnLine(formatted); + } + + public static void WriteWarnLine(string text) + { + var defaultFg = Console.ForegroundColor; + Console.ForegroundColor = ConsoleColor.Yellow; + Console.WriteLine(text); + Console.ForegroundColor = defaultFg; + } + + public static bool UserQuestion(string question) + { + var defaultFg = Console.ForegroundColor; + bool? returnValue = null; + while (!returnValue.HasValue) + { + Console.ForegroundColor = ConsoleColor.Yellow; + Console.Write(question + " [Y/N] "); + Console.ForegroundColor = defaultFg; + var key = Console.ReadKey(); + if (key.Key == ConsoleKey.Y) + returnValue = true; + else if (key.Key == ConsoleKey.N) + returnValue = false; + Console.WriteLine(); + } + return returnValue.Value; + } + } +} diff --git a/ViVeTool/FeatureNaming.cs b/ViVeTool/FeatureNaming.cs new file mode 100644 index 0000000..7300bcc --- /dev/null +++ b/ViVeTool/FeatureNaming.cs @@ -0,0 +1,82 @@ +/* + ViVe - Windows feature configuration library + Copyright (C) 2019-2022 @thebookisclosed + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + */ + +using System.Collections.Generic; +using System.IO; +using System.Linq; + +namespace Albacore.ViVeTool +{ + internal class FeatureNaming + { + internal const string DictFileName = "FeatureDictionary.pfs"; + internal static List FindIdsForNames(IEnumerable featureNames) + { + if (!File.Exists(DictFileName)) + return null; + var result = new List(); + var namesCommas = featureNames.Select(x => x.ToLowerInvariant() + ",").ToList(); + using (StreamReader reader = new StreamReader(File.OpenRead(DictFileName))) + { + while (!reader.EndOfStream) + { + var currentLine = reader.ReadLine().ToLowerInvariant(); + foreach (var nc in namesCommas) + { + if (currentLine.StartsWith(nc)) + { + result.Add(uint.Parse(currentLine.Substring(nc.Length))); + namesCommas.Remove(nc); + break; + } + } + if (namesCommas.Count == 0) + break; + } + } + return result; + } + + internal static Dictionary FindNamesForFeatures(IEnumerable featureIDs) + { + var result = new Dictionary(); + if (!File.Exists(DictFileName)) + return null; + var idsCommas = featureIDs.Select(x => "," + x.ToString()).ToList(); + using (StreamReader reader = new StreamReader(File.OpenRead(DictFileName))) + { + while (!reader.EndOfStream) + { + var currentLine = reader.ReadLine(); + foreach (var ic in idsCommas) + { + if (currentLine.EndsWith(ic)) + { + result[uint.Parse(ic.Substring(1))] = currentLine.Substring(0, currentLine.IndexOf(',')); + idsCommas.Remove(ic); + break; + } + } + if (idsCommas.Count == 0) + break; + } + } + return result; + } + } +} diff --git a/ViVe/FeatureUsageReport.cs b/ViVeTool/NativeMethods.cs similarity index 69% rename from ViVe/FeatureUsageReport.cs rename to ViVeTool/NativeMethods.cs index 8ea174e..cc149f7 100644 --- a/ViVe/FeatureUsageReport.cs +++ b/ViVeTool/NativeMethods.cs @@ -1,6 +1,6 @@ /* - ViVe - Vibranium feature configuration library - Copyright (C) 2019 @thebookisclosed + ViVe - Windows feature configuration library + Copyright (C) 2019-2022 @thebookisclosed This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -16,12 +16,13 @@ along with this program. If not, see . */ -namespace Albacore.ViVe +using System.Runtime.InteropServices; + +namespace Albacore.ViVeTool { - public class FeatureUsageReport + internal class NativeMethods { - public uint FeatureId { get; set; } - public ushort ReportingKind { get; set; } - public ushort ReportingOptions { get; set; } + [DllImport("ntdll.dll")] + internal static extern int RtlNtStatusToDosError(int ntStatus); } } diff --git a/ViVeTool/Program.cs b/ViVeTool/Program.cs index 700a695..85aea28 100644 --- a/ViVeTool/Program.cs +++ b/ViVeTool/Program.cs @@ -1,6 +1,6 @@ /* - ViVeTool - Vibranium feature configuration tool - Copyright (C) 2019 @thebookisclosed + ViVeTool - Windows feature configuration tool + Copyright (C) 2019-2022 @thebookisclosed This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -16,10 +16,15 @@ along with this program. If not, see . */ - using System; using System.Collections.Generic; +using System.ComponentModel; +using System.Diagnostics; +using System.IO; +using System.Linq; using Albacore.ViVe; +using Albacore.ViVe.NativeEnums; +using Albacore.ViVe.NativeStructs; namespace Albacore.ViVeTool { @@ -27,7 +32,7 @@ class Program { static void Main(string[] args) { - Console.WriteLine("ViVeTool v0.2.1 - Vibranium feature configuration tool\n"); + Console.WriteLine(Properties.Resources.Branding); if (args.Length < 1) { PrintHelp(); @@ -35,359 +40,728 @@ static void Main(string[] args) } if (Environment.OSVersion.Version.Build < 18963) { - Console.WriteLine("Windows 10 build 18963 or newer is required for this program to function"); + // An attempt at stopping people from mistaking 18363 for 18963 by showing them both target & current numbers + Console.WriteLine(Properties.Resources.IncompatibleBuild, Environment.OSVersion.Version.Build); return; } ProcessArgs(args); + if (Debugger.IsAttached) + Console.ReadKey(); + } + + static void PrintHelp() + { + Console.WriteLine(Properties.Resources.Help_Commands); } static void ProcessArgs(string[] args) { - for (int i = 0; i < args.Length; i++) - args[i] = args[i].ToLower(); + var mainCmd = args[0].ToLowerInvariant(); + switch (mainCmd) + { + #region Current commands + case "/query": + ArgumentBlock.Initialize(args, ArgumentBlockFlags.Store | ArgumentBlockFlags.Identifiers); + HandleQuery(); + break; + case "/enable": + ArgumentBlock.Initialize(args, ArgumentBlockFlags.Identifiers | ArgumentBlockFlags.FeatureConfigurationProperties | ArgumentBlockFlags.AllowBothStoresArgument); + HandleSet(RTL_FEATURE_ENABLED_STATE.Enabled); + break; + case "/disable": + ArgumentBlock.Initialize(args, ArgumentBlockFlags.Identifiers | ArgumentBlockFlags.FeatureConfigurationProperties | ArgumentBlockFlags.AllowBothStoresArgument); + HandleSet(RTL_FEATURE_ENABLED_STATE.Disabled); + break; + case "/reset": + ArgumentBlock.Initialize(args, ArgumentBlockFlags.Identifiers | ArgumentBlockFlags.Priority | ArgumentBlockFlags.AllowBothStoresArgument); + HandleReset(); + break; + case "/fullreset": + ArgumentBlock.Initialize(args, ArgumentBlockFlags.Store | ArgumentBlockFlags.AllowBothStoresArgument); + HandleFullReset(); + break; + case "/changestamp": + HandleChangeStamp(); + break; + case "/querysubs": + HandleQuerySubs(); + break; + case "/addsub": + ArgumentBlock.Initialize(args, ArgumentBlockFlags.Identifiers | ArgumentBlockFlags.SubscriptionProperties); + HandleSetSubs(false); + break; + case "/delsub": + ArgumentBlock.Initialize(args, ArgumentBlockFlags.Identifiers | ArgumentBlockFlags.ReportingKind | ArgumentBlockFlags.ReportingTarget); + HandleSetSubs(true); + break; + case "/notifyusage": + ArgumentBlock.Initialize(args, ArgumentBlockFlags.Identifiers | ArgumentBlockFlags.ReportingKind | ArgumentBlockFlags.ReportingOptions); + HandleNotifyUsage(); + break; + case "/lkgstatus": + ArgumentBlock.Initialize(args, 0); + HandleLKGStatus(); + break; +#if SET_LKG_COMMAND + case "/setlkg": + ArgumentBlock.Initialize(args, ArgumentBlockFlags.LKGStatus); + HandleSetLKG(); + break; +#endif + case "/export": + ArgumentBlock.Initialize(args, ArgumentBlockFlags.Export); + HandleExport(); + break; + case "/import": + ArgumentBlock.Initialize(args, ArgumentBlockFlags.Export | ArgumentBlockFlags.ImportReplace); + HandleImport(); + break; + case "/fixlkg": + ArgumentBlock.Initialize(args, 0); + HandleFixLKG(); + break; + case "/appupdate": + HandleAppUpdate(); + break; + case "/dictupdate": + HandleDictUpdate(); + break; + case "/?": + case "/help": + PrintHelp(); + break; + #endregion + #region Migration tips + case "queryconfig": + CommandMigrationInfoTip(mainCmd, "/query"); + break; + case "addconfig": + CommandMigrationInfoTip(mainCmd, "/enable' & '/disable"); + break; + case "delconfig": + CommandMigrationInfoTip(mainCmd, "/reset"); + break; + case "querysubs": + CommandMigrationInfoTip(mainCmd, "/querysubs"); + break; + case "addsub": + CommandMigrationInfoTip(mainCmd, "/addsub"); + break; + case "delsub": + CommandMigrationInfoTip(mainCmd, "/delsub"); + break; + case "notifyusage": + CommandMigrationInfoTip(mainCmd, "/notifyusage"); + break; + case "changestamp": + CommandMigrationInfoTip(mainCmd, "/changestamp"); + break; + #endregion + default: + ConsoleEx.WriteWarnLine(Properties.Resources.UnrecognizedCommand, mainCmd); + PrintHelp(); + break; + } + } - #region QueryConfig - if (args[0] == "queryconfig") + #region Main handlers + static void HandleQuery() + { + if (ArgumentBlock.HelpMode) { - if (args.Length < 2) - { - Console.WriteLine("Syntax: queryconfig [runtime | boot]\n"); - Console.WriteLine("Examples: queryconfig all\n queryconfig 123456 runtime\n"); - Console.WriteLine("If no section or an invalid section is specified, 'runtime' will be used"); - return; - } - FeatureConfigurationSection sectionToUse = FeatureConfigurationSection.Runtime; - if (args.Length > 2 && args[2] == "boot") - sectionToUse = FeatureConfigurationSection.Boot; - if (args[1] == "all") + Console.WriteLine(Properties.Resources.Help_Query); + return; + } + + var storeToUse = ArgumentBlock.Store.HasValue ? (RTL_FEATURE_CONFIGURATION_TYPE)ArgumentBlock.Store.Value : RTL_FEATURE_CONFIGURATION_TYPE.Runtime; + if (ArgumentBlock.IdList == null || ArgumentBlock.IdList.Count == 0) + { + var retrievedConfigs = FeatureManager.QueryAllFeatureConfigurations(storeToUse); + if (retrievedConfigs != null) { - var retrievedConfigs = RtlFeatureManager.QueryAllFeatureConfigurations(sectionToUse); - if (retrievedConfigs != null) + var namesAll = FeatureNaming.FindNamesForFeatures(retrievedConfigs.Select(x => x.FeatureId)); + foreach (var config in retrievedConfigs) { - foreach (var config in retrievedConfigs) - { - PrintFeatureConfig(config); - Console.WriteLine(); - } + string name = null; + try { name = namesAll[config.FeatureId]; } catch { } + PrintFeatureConfig(config, name); } - else - Console.WriteLine("Failed to query feature configurations"); } else - { - if (!TryParseDecHexUint(args[1], out uint featureId)) - { - Console.WriteLine("Couldn't parse feature ID"); - return; - } - var config = RtlFeatureManager.QueryFeatureConfiguration(featureId, sectionToUse); - if (config != null) - PrintFeatureConfig(config); - else - Console.WriteLine("Failed to query feature configuration, the specified ID may not exist in the store"); - } + ConsoleEx.WriteErrorLine(Properties.Resources.QueryFailed); + return; } - #endregion - #region QuerySubs - else if (args[0] == "querysubs") + + var namesSpecific = FeatureNaming.FindNamesForFeatures(ArgumentBlock.IdList); + foreach (var id in ArgumentBlock.IdList) { - var retrievedSubs = RtlFeatureManager.QueryFeatureUsageSubscriptions(); - if (retrievedSubs != null) + var config = FeatureManager.QueryFeatureConfiguration(id, storeToUse); + if (config != null) { - foreach (var sub in retrievedSubs) - { - PrintSubscription(sub); - Console.WriteLine(); - } + string name = null; + try { name = namesSpecific[id]; } catch { } + PrintFeatureConfig(config.Value, name); } + else + ConsoleEx.WriteErrorLine(Properties.Resources.SingleQueryFailed, id, ArgumentBlock.Store); } - #endregion - #region ChangeStamp - else if (args[0] == "changestamp") + } + + static void HandleSet(RTL_FEATURE_ENABLED_STATE state) + { + if (ArgumentBlock.HelpMode) { - Console.WriteLine("Change stamp: {0}", RtlFeatureManager.QueryFeatureConfigurationChangeStamp()); + Console.WriteLine(Properties.Resources.Help_Set, state == RTL_FEATURE_ENABLED_STATE.Enabled ? "/enable" : "/disable"); + return; } - #endregion - #region AddConfig - else if (args[0] == "addconfig") + + if (ArgumentBlock.IdList == null || ArgumentBlock.IdList.Count == 0) { - ProcessSetConfig(args, FeatureConfigurationAction.UpdateAll); + ConsoleEx.WriteErrorLine(Properties.Resources.NoFeaturesSpecified); + return; } - #endregion - #region DelConfig - else if (args[0] == "delconfig") + + var fcp = ArgumentBlock.FeatureConigurationProperties; + var updates = new RTL_FEATURE_CONFIGURATION_UPDATE[ArgumentBlock.IdList.Count]; + for (int i = 0; i < updates.Length; i++) { - ProcessSetConfig(args, FeatureConfigurationAction.Delete); + updates[i] = new RTL_FEATURE_CONFIGURATION_UPDATE() + { + FeatureId = ArgumentBlock.IdList[i], + EnabledState = state, + EnabledStateOptions = fcp.EnabledStateOptions, + Priority = fcp.Priority ?? RTL_FEATURE_CONFIGURATION_PRIORITY.Service, + Variant = fcp.Variant, + VariantPayloadKind = fcp.VariantPayloadKind, + VariantPayload = fcp.VariantPayload, + Operation = RTL_FEATURE_CONFIGURATION_OPERATION.FeatureState | RTL_FEATURE_CONFIGURATION_OPERATION.VariantState + }; } - #endregion - #region AddSub - else if (args[0] == "addsub") + + FinalizeSet(updates, false); + } + + static void HandleReset() + { + if (ArgumentBlock.HelpMode) { - ProcessSetSubs(args, false); + Console.WriteLine(Properties.Resources.Help_Reset); + return; } - #endregion - #region DelSub - else if (args[0] == "delsub") + + if (ArgumentBlock.IdList == null || ArgumentBlock.IdList.Count == 0) { - ProcessSetSubs(args, true); + ConsoleEx.WriteErrorLine(Properties.Resources.NoFeaturesSpecified); + return; } - #endregion - #region NotifyUsage - else if (args[0] == "notifyusage") + + RTL_FEATURE_CONFIGURATION_UPDATE[] updates; + var fcp = ArgumentBlock.FeatureConigurationProperties; + if (fcp.Priority.HasValue) { - if (args.Length < 2) - { - Console.WriteLine("Syntax: notifyusage \n"); - Console.WriteLine("Examples: notifyusage 123465 2 1"); - return; - } - if (!TryParseDecHexUint(args[1], out uint featureId)) - { - Console.WriteLine("Couldn't parse feature ID"); - return; - } - if (!TryParseDecHexUshort(args[2], out ushort reportingKind)) + var priority = ArgumentBlock.FeatureConigurationProperties.Priority.Value; + updates = new RTL_FEATURE_CONFIGURATION_UPDATE[ArgumentBlock.IdList.Count]; + for (int i = 0; i < updates.Length; i++) { - Console.WriteLine("Couldn't parse reporting kind"); - return; - } - if (!TryParseDecHexUshort(args[3], out ushort reportingOptions)) - { - Console.WriteLine("Couldn't parse reporting options"); - return; + updates[i] = new RTL_FEATURE_CONFIGURATION_UPDATE() + { + FeatureId = ArgumentBlock.IdList[i], + Priority = priority, + Operation = RTL_FEATURE_CONFIGURATION_OPERATION.ResetState + }; } - int result = RtlFeatureManager.NotifyFeatureUsage(new FeatureUsageReport() { - FeatureId = featureId, - ReportingKind = reportingKind, - ReportingOptions = reportingOptions - }); - if (result != 0) - Console.WriteLine("An error occurred while firing the notification (hr=0x{0:x8}), no such subscription may exist", result); - else - Console.WriteLine("Successfully fired the feature usage notification"); } - #endregion - #region SetBootState - else if (args[0] == "setbootstate") + else + updates = FindResettables(false); + + FinalizeSet(updates, true); + } + + static void HandleFullReset() + { + if (ArgumentBlock.HelpMode) { - int state = 1; - if (args.Length > 1) - if (!int.TryParse(args[1], out state)) - { - Console.WriteLine("Couldn't parse desired state"); - return; - } - var result = RtlFeatureManager.SetBootFeatureConfigurationState(ref state); - if (result < 0) - Console.WriteLine("Failed to set boot state (hr=0x{0:x8})", result); - else - Console.WriteLine("Successfully set boot state"); + Console.WriteLine(Properties.Resources.Help_FullReset); + return; } - #endregion - #region GetBootState - else if (args[0] == "getbootstate") + + var userConsent = ConsoleEx.UserQuestion(Properties.Resources.FullResetPrompt); + if (!userConsent) { - int state = 0; - var result = RtlFeatureManager.GetBootFeatureConfigurationState(ref state); - if (result < 0) - Console.WriteLine("Failed to query boot state (hr=0x{0:x8})", result); - else - Console.WriteLine("Boot state: {0}", state); + Console.WriteLine(Properties.Resources.FullResetCanceled); + return; } - #endregion + + var updates = FindResettables(true); + + FinalizeSet(updates, true); + } + + static void HandleChangeStamp() + { + Console.WriteLine(Properties.Resources.ChangestampDisplay, FeatureManager.QueryFeatureConfigurationChangeStamp()); } - static void ProcessSetSubs(string[] args, bool del) + static void HandleQuerySubs() { - if (args.Length < 5) + var retrievedSubs = FeatureManager.QueryFeatureUsageSubscriptions(); + if (retrievedSubs != null) { - string helpPrefix = del ? "del" : "add"; - Console.WriteLine("Syntax: {0}sub \n", helpPrefix); - Console.WriteLine("Reporting targets are WNF state names\n"); - Console.WriteLine("Examples: {0}sub 123456 2 1 0d83063ea3bdf875", helpPrefix); - return; + var names = FeatureNaming.FindNamesForFeatures(retrievedSubs.Select(x => x.FeatureId)); + foreach (var sub in retrievedSubs) + { + string name = null; + try { name = names[sub.FeatureId]; } catch { } + PrintSubscription(sub, name); + } } - if (!TryParseDecHexUint(args[1], out uint featureId)) + else + ConsoleEx.WriteErrorLine(Properties.Resources.QuerySubsFailed); + } + + // No individual Store management is supported due to queries effectively being Runtime only + static void HandleSetSubs(bool delete) + { + if (ArgumentBlock.HelpMode) { - Console.WriteLine("Couldn't parse feature ID"); + Console.WriteLine(Properties.Resources.Help_SetSubs, + delete ? "/delsub" : "/addsub", + delete ? "" : " [/reportingoptions:<0-65535>]", + delete ? Properties.Resources.Help_SetSubs_Delete : Properties.Resources.Help_SetSubs_Add); return; } - if (!TryParseDecHexUshort(args[2], out ushort reportingKind)) + + var sp = ArgumentBlock.SubscriptionProperties; + if (ArgumentBlock.IdList == null || ArgumentBlock.IdList.Count == 0) { - Console.WriteLine("Couldn't parse reporting kind"); + ConsoleEx.WriteErrorLine(Properties.Resources.NoFeaturesSpecified); return; } - if (!TryParseDecHexUshort(args[3], out ushort reportingOptions)) + else if (sp.ReportingTarget == 0) { - Console.WriteLine("Couldn't parse reporting options"); + ConsoleEx.WriteErrorLine(Properties.Resources.NoReportingTargetSpecified); return; } - if (!ulong.TryParse(args[4], System.Globalization.NumberStyles.HexNumber, System.Globalization.CultureInfo.InvariantCulture, out ulong reportingTarget)) + + var subs = new RTL_FEATURE_USAGE_SUBSCRIPTION_DETAILS[ArgumentBlock.IdList.Count]; + for (int i = 0; i < subs.Length; i++) { - Console.WriteLine("Couldn't parse reporting target"); - return; + subs[i] = new RTL_FEATURE_USAGE_SUBSCRIPTION_DETAILS() + { + FeatureId = ArgumentBlock.IdList[i], + ReportingKind = sp.ReportingKind, + ReportingOptions = sp.ReportingOptions, + ReportingTarget = sp.ReportingTarget + }; } - var subList = new List() {new FeatureUsageSubscription() - { - FeatureId = featureId, - ReportingKind = reportingKind, - ReportingOptions = reportingOptions, - ReportingTarget = reportingTarget - } }; int result; - if (del) - result = RtlFeatureManager.RemoveLiveFeatureUsageSubscriptions(subList); + if (delete) + result = FeatureManager.RemoveFeatureUsageSubscriptions(subs); else - result = RtlFeatureManager.AddLiveFeatureUsageSubscriptions(subList); + result = FeatureManager.AddFeatureUsageSubscriptions(subs); if (result != 0) { - Console.WriteLine("An error occurred while setting a live feature usage subscription (hr=0x{0:x8})", result); + ConsoleEx.WriteErrorLine(Properties.Resources.SetSubsRuntimeFailed, + GetHumanErrorDescription(result)); return; } - if (del) - result = RtlFeatureManager.RemoveBootFeatureUsageSubscriptions(subList) ? 0 : -1; + if (delete) + result = FeatureManager.RemoveFeatureUsageSubscriptionsFromRegistry(subs); else - result = RtlFeatureManager.AddBootFeatureUsageSubscriptions(subList) ? 0 : -1; + result = FeatureManager.AddFeatureUsageSubscriptionsToRegistry(subs); if (result != 0) - Console.WriteLine("An error occurred while setting a boot feature usage subscription"); + ConsoleEx.WriteErrorLine(Properties.Resources.SetSubsBootFailed, + GetHumanErrorDescription(result, true)); else - Console.WriteLine("Successfully set feature usage subscription"); + Console.WriteLine(Properties.Resources.SetSubsSuccess); } - static void ProcessSetConfig(string[] args, FeatureConfigurationAction actionToUse) + static void HandleNotifyUsage() { - if (args.Length < 3) + if (ArgumentBlock.HelpMode) { - string helpPrefix = actionToUse == FeatureConfigurationAction.Delete ? "del" : "add"; - Console.WriteLine("Syntax: {0}config \n [enabled state options] [variant] [variant payload kind] [variant payload] [group]\n", helpPrefix); - Console.WriteLine("Enabled state types: 0 = Default, 1 = Disabled, 2 = Enabled\n"); - Console.WriteLine("Examples: {0}config 23456789 2\n {0}config 23456789 2 1 1 0 0 4", helpPrefix); + Console.WriteLine(Properties.Resources.Help_NotifyUsage); return; } - if (!TryParseDecHexUint(args[1], out uint featureId)) + if (ArgumentBlock.IdList == null || ArgumentBlock.IdList.Count == 0) { - Console.WriteLine("Couldn't parse feature ID"); + ConsoleEx.WriteErrorLine(Properties.Resources.NoFeaturesSpecified); return; } - if (!TryParseDecHexInt(args[2], out int enabledState)) + + var sp = ArgumentBlock.SubscriptionProperties; + foreach (var id in ArgumentBlock.IdList) { - Console.WriteLine("Couldn't parse enabled state"); - return; + var report = new RTL_FEATURE_USAGE_REPORT() + { + FeatureId = id, + ReportingKind = sp.ReportingKind, + ReportingOptions = sp.ReportingOptions + }; + int result = FeatureManager.NotifyFeatureUsage(ref report); + if (result != 0) + ConsoleEx.WriteErrorLine(Properties.Resources.NotifyUsageFailed, + id, + GetHumanErrorDescription(result)); + else + Console.WriteLine(Properties.Resources.NotifyUsageSuccess, id); } - int enabledStateOptions = 1; - int variant = 0; - int variantPayloadKind = 0; - int variantPayload = 0; - int group = 4; - if (args.Length > 4 && !TryParseDecHexInt(args[3], out enabledStateOptions)) + } + + static void HandleLKGStatus() + { + if (ArgumentBlock.HelpMode) { - Console.WriteLine("Couldn't parse enabled state options"); + Console.WriteLine(Properties.Resources.Help_LKGStatus); return; } - if (args.Length > 5 && !TryParseDecHexInt(args[4], out variant)) + + var result = FeatureManager.GetBootFeatureConfigurationState(out BSD_FEATURE_CONFIGURATION_STATE state); + if (result != 0) + ConsoleEx.WriteErrorLine(Properties.Resources.LKGQueryFailed, + GetHumanErrorDescription(result)); + else + Console.WriteLine(Properties.Resources.LKGStatusDisplay, state); + } + +#if SET_LKG_COMMAND + static void HandleSetLKG() + { + UpdateLKGStatus(ArgumentBlock.LKGStatus.Value); + } +#endif + + static void HandleExport() + { + if (ArgumentBlock.HelpMode) { - Console.WriteLine("Couldn't parse variant"); + Console.WriteLine(Properties.Resources.Help_Export); return; } - if (args.Length > 6 && !TryParseDecHexInt(args[5], out variantPayloadKind)) + + if (string.IsNullOrEmpty(ArgumentBlock.FileName)) { - Console.WriteLine("Couldn't parse variant payload kind"); + ConsoleEx.WriteErrorLine(Properties.Resources.NoFileNameSpecified); return; } - if (args.Length > 7 && !TryParseDecHexInt(args[6], out variantPayload)) + + var useBothStores = ArgumentBlock.ShouldUseBothStores; + RTL_FEATURE_CONFIGURATION[] runtimeFeatures = null; + RTL_FEATURE_CONFIGURATION[] bootFeatures = null; + if (useBothStores || ArgumentBlock.Store.Value == FeatureConfigurationTypeEx.Runtime) + runtimeFeatures = FeatureManager.QueryAllFeatureConfigurations(RTL_FEATURE_CONFIGURATION_TYPE.Runtime); + if (useBothStores || ArgumentBlock.Store.Value == FeatureConfigurationTypeEx.Boot) + bootFeatures = FeatureManager.QueryAllFeatureConfigurations(RTL_FEATURE_CONFIGURATION_TYPE.Boot); + + using (var fs = new FileStream(ArgumentBlock.FileName, FileMode.Create)) + using (var bw = new BinaryWriter(fs)) + { + SerializeConfigsToStream(bw, runtimeFeatures); + SerializeConfigsToStream(bw, bootFeatures); + } + + Console.WriteLine(Properties.Resources.ExportSuccess, runtimeFeatures?.Length ?? 0, bootFeatures?.Length ?? 0, ArgumentBlock.FileName); + } + + static void HandleImport() + { + if (ArgumentBlock.HelpMode) { - Console.WriteLine("Couldn't parse variant payload"); + Console.WriteLine(Properties.Resources.Help_Import); return; } - if (args.Length > 8 && !TryParseDecHexInt(args[7], out group)) + + if (string.IsNullOrEmpty(ArgumentBlock.FileName)) { - Console.WriteLine("Couldn't parse group"); + ConsoleEx.WriteErrorLine(Properties.Resources.NoFileNameSpecified); return; } - var configs = new List() { - new FeatureConfiguration() { - FeatureId = featureId, - EnabledState = (FeatureEnabledState)enabledState, - EnabledStateOptions = enabledStateOptions, - Group = group, - Variant = variant, - VariantPayloadKind = variantPayloadKind, - VariantPayload = variantPayload, - Action = actionToUse - } }; - int result = RtlFeatureManager.SetLiveFeatureConfigurations(configs, FeatureConfigurationSection.Runtime); - if (result != 0) + + List runtimeFeatures, bootFeatures; + using (var fs = new FileStream(ArgumentBlock.FileName, FileMode.Open, FileAccess.Read, FileShare.ReadWrite)) + using (var br = new BinaryReader(fs)) + { + runtimeFeatures = DeserializeConfigsFromStream(br); + bootFeatures = DeserializeConfigsFromStream(br); + } + + Console.WriteLine(Properties.Resources.ImportBreakdown, runtimeFeatures.Count, bootFeatures.Count, ArgumentBlock.FileName); + + if (ArgumentBlock.ImportReplace) + HandleFullReset(); + + var useBothStores = ArgumentBlock.ShouldUseBothStores; + if ((useBothStores || ArgumentBlock.Store.Value == FeatureConfigurationTypeEx.Runtime) && runtimeFeatures.Count > 0) + { + Console.WriteLine(Properties.Resources.ImportProcessing, runtimeFeatures.Count, FeatureConfigurationTypeEx.Runtime); + var updates = ConvertConfigsToUpdates(runtimeFeatures); + var prevValue = ArgumentBlock.Store; + ArgumentBlock.Store = FeatureConfigurationTypeEx.Runtime; + FinalizeSet(updates, false); + ArgumentBlock.Store = prevValue; + } + if ((useBothStores || ArgumentBlock.Store.Value == FeatureConfigurationTypeEx.Boot) && bootFeatures.Count > 0) { - Console.WriteLine("An error occurred while setting a live feature configuration (hr=0x{0:x8})", result); + Console.WriteLine(Properties.Resources.ImportProcessing, runtimeFeatures.Count, FeatureConfigurationTypeEx.Boot); + var updates = ConvertConfigsToUpdates(bootFeatures); + var prevValue = ArgumentBlock.Store; + ArgumentBlock.Store = FeatureConfigurationTypeEx.Boot; + FinalizeSet(updates, false); + ArgumentBlock.Store = prevValue; + + Console.WriteLine(Properties.Resources.RebootRecommended); + } + } + + static void HandleFixLKG() + { + if (ArgumentBlock.HelpMode) + { + Console.WriteLine(Properties.Resources.Help_FixLKG); return; } - if (!RtlFeatureManager.SetBootFeatureConfigurations(configs)) - Console.WriteLine("An error occurred while setting a boot feature configuration"); + + var fixPerformed = FeatureManager.FixLKGStore(); + if (fixPerformed) + Console.WriteLine(Properties.Resources.FixLKGPerformed); else - Console.WriteLine("Successfully set feature configuration"); + Console.WriteLine(Properties.Resources.FixLKGNotNeeded); } - static bool TryParseDecHexUshort(string input, out ushort output) + static void HandleAppUpdate() { - bool success; - if (input.StartsWith("0x")) - success = ushort.TryParse(input.Substring(2), System.Globalization.NumberStyles.HexNumber, System.Globalization.CultureInfo.InvariantCulture, out output); + Console.WriteLine(Properties.Resources.CheckingAppUpdates); + var lri = UpdateCheck.GetLatestReleaseInfo(); + if (lri == null || !UpdateCheck.IsAppOutdated(lri.tag_name)) + Console.WriteLine(Properties.Resources.NoNewerVersionFound); else - success = ushort.TryParse(input, out output); - return success; + Console.WriteLine(Properties.Resources.NewAppUpdateDisplay, lri.name, lri.published_at, lri.body, lri.html_url); } - static bool TryParseDecHexUint(string input, out uint output) + static void HandleDictUpdate() { - bool success; - if (input.StartsWith("0x")) - success = uint.TryParse(input.Substring(2), System.Globalization.NumberStyles.HexNumber, System.Globalization.CultureInfo.InvariantCulture, out output); + Console.WriteLine(Properties.Resources.CheckingDictUpdates); + var ldi = UpdateCheck.GetLatestDictionaryInfo(); + if (ldi == null || !UpdateCheck.IsDictOutdated(ldi.sha)) + Console.WriteLine(Properties.Resources.NoNewerVersionFound); else - success = uint.TryParse(input, out output); - return success; + { + Console.WriteLine(Properties.Resources.NewDictUpdateDisplay, ldi.sha); + var dlNew = ConsoleEx.UserQuestion(Properties.Resources.DictUpdateConsent); + if (dlNew) + { + UpdateCheck.ReplaceDict(ldi.download_url); + Console.WriteLine(Properties.Resources.DictUpdateFinished); + } + else + Console.WriteLine(Properties.Resources.DictUpdateCanceled); + } + } + #endregion + + #region Helpers + static RTL_FEATURE_CONFIGURATION_UPDATE[] FindResettables(bool fullReset) + { + var useBothStores = ArgumentBlock.ShouldUseBothStores; + + var dupeCheckHs = new HashSet(); + var updateList = new List(); + if (useBothStores || ArgumentBlock.Store.Value == FeatureConfigurationTypeEx.Runtime) + FindResettablesInternal(RTL_FEATURE_CONFIGURATION_TYPE.Runtime, fullReset, updateList, dupeCheckHs); + if (useBothStores || ArgumentBlock.Store.Value == FeatureConfigurationTypeEx.Boot) + FindResettablesInternal(RTL_FEATURE_CONFIGURATION_TYPE.Boot, fullReset, updateList, dupeCheckHs); + + return updateList.ToArray(); } - static bool TryParseDecHexInt(string input, out int output) + static void FindResettablesInternal(RTL_FEATURE_CONFIGURATION_TYPE type, bool fullReset, List targetList, HashSet alreadyFoundSet = null) { - bool success; - if (input.StartsWith("0x")) - success = int.TryParse(input, System.Globalization.NumberStyles.HexNumber, System.Globalization.CultureInfo.InvariantCulture, out output); + var configs = FeatureManager.QueryAllFeatureConfigurations(type); + foreach (var cfg in configs) + { + if (cfg.Priority == RTL_FEATURE_CONFIGURATION_PRIORITY.ImageDefault || cfg.Priority == RTL_FEATURE_CONFIGURATION_PRIORITY.ImageOverride) + continue; + if (fullReset || ArgumentBlock.IdList.Contains(cfg.FeatureId)) + { + if (alreadyFoundSet != null) + { + var preCount = alreadyFoundSet.Count; + alreadyFoundSet.Add($"{cfg.FeatureId}-{(uint)cfg.Priority}"); + if (alreadyFoundSet.Count == preCount) + continue; + } + targetList.Add(new RTL_FEATURE_CONFIGURATION_UPDATE() + { + FeatureId = cfg.FeatureId, + Priority = cfg.Priority, + Operation = RTL_FEATURE_CONFIGURATION_OPERATION.ResetState + }); + } + } + } + + static void FinalizeSet(RTL_FEATURE_CONFIGURATION_UPDATE[] updates, bool isReset) + { + var useBothStores = ArgumentBlock.ShouldUseBothStores; + if (useBothStores || ArgumentBlock.Store == FeatureConfigurationTypeEx.Runtime) + { + var result = FeatureManager.SetFeatureConfigurations(updates, RTL_FEATURE_CONFIGURATION_TYPE.Runtime); + if (result != 0) + { + ConsoleEx.WriteErrorLine(isReset ? Properties.Resources.ResetRuntimeFailed : Properties.Resources.SetRuntimeFailed, + GetHumanErrorDescription(result)); + return; + } + } + if (useBothStores || ArgumentBlock.Store == FeatureConfigurationTypeEx.Boot) + { + var result = FeatureManager.SetFeatureConfigurations(updates, RTL_FEATURE_CONFIGURATION_TYPE.Boot); + if (result != 0) + { + ConsoleEx.WriteErrorLine(isReset ? Properties.Resources.ResetBootFailed : Properties.Resources.SetBootFailed, + GetHumanErrorDescription(result)); + return; + } + + UpdateLKGStatus(BSD_FEATURE_CONFIGURATION_STATE.BootPending); + } + + Console.WriteLine(isReset ? Properties.Resources.ResetSuccess : Properties.Resources.SetSuccess); + } + + static void UpdateLKGStatus(BSD_FEATURE_CONFIGURATION_STATE newStatus) + { + var result = FeatureManager.GetBootFeatureConfigurationState(out BSD_FEATURE_CONFIGURATION_STATE state); + if (result != 0) + return; + + if (state != newStatus) + { + result = FeatureManager.SetBootFeatureConfigurationState(newStatus); + if (result != 0) + ConsoleEx.WriteWarnLine(Properties.Resources.LKGUpdateFailed, + GetHumanErrorDescription(result), + state); + } + } + + static void SerializeConfigsToStream(BinaryWriter bw, RTL_FEATURE_CONFIGURATION[] configurations) + { + if (configurations != null) + { + bw.Write(configurations.Length); + foreach (var feature in configurations) + { + bw.Write(feature.FeatureId); + bw.Write(feature.CompactState); + bw.Write(feature.VariantPayload); + } + } else - success = int.TryParse(input, out output); - return success; + bw.Write(0); } - static void PrintHelp() + static List DeserializeConfigsFromStream(BinaryReader br) + { + var count = br.ReadInt32(); + var configurations = new List(); + for (int i = 0; i < count; i++) + { + var config = new RTL_FEATURE_CONFIGURATION + { + FeatureId = br.ReadUInt32(), + CompactState = br.ReadUInt32(), + VariantPayload = br.ReadUInt32() + }; + // Ignore these as they can't be written to the Runtime store and should be purely CBS managed + if (config.Priority == RTL_FEATURE_CONFIGURATION_PRIORITY.ImageDefault || config.Priority == RTL_FEATURE_CONFIGURATION_PRIORITY.ImageOverride) + continue; + configurations.Add(config); + } + return configurations; + } + + static RTL_FEATURE_CONFIGURATION_UPDATE[] ConvertConfigsToUpdates(List configurations) + { + var updates = new RTL_FEATURE_CONFIGURATION_UPDATE[configurations.Count]; + for (int i = 0; i < updates.Length; i++) + { + var config = configurations[i]; + var update = new RTL_FEATURE_CONFIGURATION_UPDATE() + { + FeatureId = config.FeatureId, + Priority = config.Priority, + EnabledState = config.EnabledState, + EnabledStateOptions = config.IsWexpConfiguration ? RTL_FEATURE_ENABLED_STATE_OPTIONS.WexpConfig : RTL_FEATURE_ENABLED_STATE_OPTIONS.None, + Variant = config.Variant, + VariantPayloadKind = config.VariantPayloadKind, + VariantPayload = config.VariantPayload, + Operation = RTL_FEATURE_CONFIGURATION_OPERATION.FeatureState | RTL_FEATURE_CONFIGURATION_OPERATION.VariantState + }; + updates[i] = update; + } + return updates; + } + + static void CommandMigrationInfoTip(string oldCommand, string newCommand) + { + ConsoleEx.WriteWarnLine(Properties.Resources.CommandMigrationNote, oldCommand, newCommand); + } + + static string GetHumanErrorDescription(int ntStatus, bool noTranslate = false) { - Console.WriteLine("Available commands:"); - Console.WriteLine(" queryconfig\tLists existing feature configuration(s)"); - Console.WriteLine(" querysubs\tLists existing feature usage notification subscriptions"); - Console.WriteLine(" changestamp\tPrints the current feature store change stamp"); - Console.WriteLine(" addconfig\tAdds a feature configuration"); - Console.WriteLine(" delconfig\tDeletes a feature configuration"); - Console.WriteLine(" addsub\tAdds a feature usage subscription notification"); - Console.WriteLine(" delsub\tDeletes a feature usage subscription notification"); - Console.WriteLine(" notifyusage\tFires a feature usage notification"); + var hResult = 0; + if (!noTranslate) + hResult = NativeMethods.RtlNtStatusToDosError(ntStatus); + if (noTranslate || hResult == 0x13D) //ERROR_MR_MID_NOT_FOUND + hResult = ntStatus; + var w32ex = new Win32Exception(hResult); + return w32ex.Message; } + #endregion - static void PrintFeatureConfig(FeatureConfiguration config) + #region Console printing + static void PrintFeatureConfig(RTL_FEATURE_CONFIGURATION config, string name = null) { - Console.WriteLine("[0x{0:X8} / {0}]", config.FeatureId); - Console.WriteLine("Group: {0}", config.Group); - Console.WriteLine("EnabledState: {0} ({1})", config.EnabledState, (int)config.EnabledState); - Console.WriteLine("EnabledStateOptions: {0}", config.EnabledStateOptions); - Console.WriteLine("Variant: {0}", config.Variant); - Console.WriteLine("VariantPayloadKind: {0}", config.VariantPayloadKind); - Console.WriteLine("VariantPayload: {0:x}", config.VariantPayload); + var defaultFg = Console.ForegroundColor; + Console.ForegroundColor = ConsoleColor.Yellow; + Console.Write("[{0}]", config.FeatureId); + if (!string.IsNullOrEmpty(name)) + Console.Write(" ({0})", name); + Console.WriteLine(); + Console.ForegroundColor = defaultFg; + Console.WriteLine(Properties.Resources.FeatureDisplay_Priority, config.Priority, (uint)config.Priority); + Console.WriteLine(Properties.Resources.FeatureDisplay_State, config.EnabledState, (uint)config.EnabledState); + Console.WriteLine(Properties.Resources.FeatureDisplay_Type, + config.IsWexpConfiguration ? Properties.Resources.FeatureType_Experiment : Properties.Resources.FeatureType_Override, + config.IsWexpConfiguration ? 1 : 0); + if (config.HasSubscriptions) + Console.WriteLine(Properties.Resources.FeatureDisplay_HasSubscriptions, config.HasSubscriptions); + if (config.Variant != 0) + Console.WriteLine(Properties.Resources.FeatureDisplay_Variant, config.Variant); + var vpkDefined = config.VariantPayloadKind != 0; + if (vpkDefined) + Console.WriteLine(Properties.Resources.FeatureDisplay_PayloadKind, config.VariantPayloadKind, (uint)config.VariantPayloadKind); + if (vpkDefined || config.VariantPayload != 0) + Console.WriteLine(Properties.Resources.FeatureDisplay_Payload, config.VariantPayload); + Console.WriteLine(); } - static void PrintSubscription(FeatureUsageSubscription sub) + static void PrintSubscription(RTL_FEATURE_USAGE_SUBSCRIPTION_DETAILS sub, string name = null) { - Console.WriteLine("[0x{0:X8} / {0}]", sub.FeatureId); - Console.WriteLine("ReportingKind: {0}", sub.ReportingKind); - Console.WriteLine("ReportingOptions: {0}", sub.ReportingOptions); - Console.WriteLine("ReportingTarget: {0:x16}", sub.ReportingTarget); + var defaultFg = Console.ForegroundColor; + Console.ForegroundColor = ConsoleColor.Yellow; + Console.Write("[{0}]", sub.FeatureId); + if (!string.IsNullOrEmpty(name)) + Console.Write(" ({0})", name); + Console.WriteLine(); + Console.ForegroundColor = defaultFg; + Console.WriteLine(Properties.Resources.SubscriptionDisplay_ReportingKind, sub.ReportingKind); + Console.WriteLine(Properties.Resources.SubscriptionDisplay_ReportingOptions, sub.ReportingOptions); + Console.WriteLine(Properties.Resources.SubscriptionDisplay_ReportingTarget, sub.ReportingTarget); + Console.WriteLine(); } + #endregion } } diff --git a/ViVeTool/Properties/AssemblyInfo.cs b/ViVeTool/Properties/AssemblyInfo.cs index 542a461..4500cb2 100644 --- a/ViVeTool/Properties/AssemblyInfo.cs +++ b/ViVeTool/Properties/AssemblyInfo.cs @@ -1,5 +1,4 @@ using System.Reflection; -using System.Runtime.CompilerServices; using System.Runtime.InteropServices; // General Information about an assembly is controlled through the following @@ -10,7 +9,7 @@ [assembly: AssemblyConfiguration("")] [assembly: AssemblyCompany("")] [assembly: AssemblyProduct("ViVeTool")] -[assembly: AssemblyCopyright("Copyright © @thebookisclosed 2020")] +[assembly: AssemblyCopyright("Copyright © @thebookisclosed 2022")] [assembly: AssemblyTrademark("")] [assembly: AssemblyCulture("")] @@ -32,5 +31,5 @@ // You can specify all the values or you can default the Build and Revision Numbers // by using the '*' as shown below: // [assembly: AssemblyVersion("1.0.*")] -[assembly: AssemblyVersion("0.1.2.0")] -[assembly: AssemblyFileVersion("0.1.2.0")] +[assembly: AssemblyVersion("0.3.0.0")] +[assembly: AssemblyFileVersion("0.3.0.0")] diff --git a/ViVeTool/Properties/Resources.Designer.cs b/ViVeTool/Properties/Resources.Designer.cs new file mode 100644 index 0000000..61a80e3 --- /dev/null +++ b/ViVeTool/Properties/Resources.Designer.cs @@ -0,0 +1,794 @@ +//------------------------------------------------------------------------------ +// +// This code was generated by a tool. +// Runtime Version:4.0.30319.42000 +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +//------------------------------------------------------------------------------ + +namespace Albacore.ViVeTool.Properties { + using System; + + + /// + /// A strongly-typed resource class, for looking up localized strings, etc. + /// + // This class was auto-generated by the StronglyTypedResourceBuilder + // class via a tool like ResGen or Visual Studio. + // To add or remove a member, edit your .ResX file then rerun ResGen + // with the /str option, or rebuild your VS project. + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "17.0.0.0")] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] + internal class Resources { + + private static global::System.Resources.ResourceManager resourceMan; + + private static global::System.Globalization.CultureInfo resourceCulture; + + [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] + internal Resources() { + } + + /// + /// Returns the cached ResourceManager instance used by this class. + /// + [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] + internal static global::System.Resources.ResourceManager ResourceManager { + get { + if (object.ReferenceEquals(resourceMan, null)) { + global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("Albacore.ViVeTool.Properties.Resources", typeof(Resources).Assembly); + resourceMan = temp; + } + return resourceMan; + } + } + + /// + /// Overrides the current thread's CurrentUICulture property for all + /// resource lookups using this strongly typed resource class. + /// + [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] + internal static global::System.Globalization.CultureInfo Culture { + get { + return resourceCulture; + } + set { + resourceCulture = value; + } + } + + /// + /// Looks up a localized string similar to ViVeTool v0.3.0 - Windows feature configuration tool + ///. + /// + internal static string Branding { + get { + return ResourceManager.GetString("Branding", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Changestamp: {0}. + /// + internal static string ChangestampDisplay { + get { + return ResourceManager.GetString("ChangestampDisplay", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Checking for app updates.... + /// + internal static string CheckingAppUpdates { + get { + return ResourceManager.GetString("CheckingAppUpdates", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Checking for feature dictionary updates.... + /// + internal static string CheckingDictUpdates { + get { + return ResourceManager.GetString("CheckingDictUpdates", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to The '{0}' command has been moved to '{1}' as part of a syntax improvement effort. + ///Arguments are now position independent and clearly labeled, ambiguous strings of numbers are no longer used. + ////? can be used to view more information about usage.. + /// + internal static string CommandMigrationNote { + get { + return ResourceManager.GetString("CommandMigrationNote", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Dictionary update canceled. + /// + internal static string DictUpdateCanceled { + get { + return ResourceManager.GetString("DictUpdateCanceled", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Replace current version with new version?. + /// + internal static string DictUpdateConsent { + get { + return ResourceManager.GetString("DictUpdateConsent", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Dictionary update finished. + /// + internal static string DictUpdateFinished { + get { + return ResourceManager.GetString("DictUpdateFinished", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Exported {0} Runtime type and {1} Boot type feature configuration(s) to {2}. + /// + internal static string ExportSuccess { + get { + return ResourceManager.GetString("ExportSuccess", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to HasSubscriptions: {0}. + /// + internal static string FeatureDisplay_HasSubscriptions { + get { + return ResourceManager.GetString("FeatureDisplay_HasSubscriptions", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Payload : {0:x}. + /// + internal static string FeatureDisplay_Payload { + get { + return ResourceManager.GetString("FeatureDisplay_Payload", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to PayloadKind : {0} ({1}). + /// + internal static string FeatureDisplay_PayloadKind { + get { + return ResourceManager.GetString("FeatureDisplay_PayloadKind", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Priority : {0} ({1}). + /// + internal static string FeatureDisplay_Priority { + get { + return ResourceManager.GetString("FeatureDisplay_Priority", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to State : {0} ({1}). + /// + internal static string FeatureDisplay_State { + get { + return ResourceManager.GetString("FeatureDisplay_State", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Type : {0} ({1}). + /// + internal static string FeatureDisplay_Type { + get { + return ResourceManager.GetString("FeatureDisplay_Type", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Variant : {0}. + /// + internal static string FeatureDisplay_Variant { + get { + return ResourceManager.GetString("FeatureDisplay_Variant", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Experiment. + /// + internal static string FeatureType_Experiment { + get { + return ResourceManager.GetString("FeatureType_Experiment", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Override. + /// + internal static string FeatureType_Override { + get { + return ResourceManager.GetString("FeatureType_Override", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to 'Last Known Good' rollback system data corruption was not found. + /// + internal static string FixLKGNotNeeded { + get { + return ResourceManager.GetString("FixLKGNotNeeded", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to 'Last Known Good' rollback system data has been fixed successfully. + /// + internal static string FixLKGPerformed { + get { + return ResourceManager.GetString("FixLKGPerformed", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Full reset canceled. + /// + internal static string FullResetCanceled { + get { + return ResourceManager.GetString("FullResetCanceled", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Are you sure you want to perform a full reset?. + /// + internal static string FullResetPrompt { + get { + return ResourceManager.GetString("FullResetPrompt", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Available commands: + /// /query Lists existing feature configuration(s) + /// /enable Enables a feature + /// /disable Disables a feature + /// /reset Removes custom configurations for a specific feature + /// /fullreset Removes all custom feature configurations + /// /changestamp Prints the feature store change counter (changestamp)* + /// /querysubs Lists existing feature usage subscriptions* + /// /addsub Adds a feature usage subscription + /// /delsub Removes a feature usage subscription + /// /notifyusage Fires a feature [rest of string was truncated]";. + /// + internal static string Help_Commands { + get { + return ResourceManager.GetString("Help_Commands", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Syntax: + /// /export /filename:<path to new file> [/store:<both | runtime | boot>] + /// + ///Exports all currently loaded feature configurations to a file. By default both stores are exported. + /// + ///Examples: + /// /export /filename:features.bin. + /// + internal static string Help_Export { + get { + return ResourceManager.GetString("Help_Export", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Syntax: + /// /fixlkg + /// + ///Attempts to fix corrupted 'Last Known Good' rollback system data. Windows system components can occassionally + ///invalidate this data due to a programming oversight.. + /// + internal static string Help_FixLKG { + get { + return ResourceManager.GetString("Help_FixLKG", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Syntax: + /// /fullreset [/store:<both | runtime | boot>] + /// + ///This command removes all custom feature configuration overrides, effectively reverting + ///feature store contents to their clean install state. Both stores are targeted by default. + ///Use with caution. + /// + ///ImageDefault (0) and ImageOverride (15) priority configurations are unaffected by this command.. + /// + internal static string Help_FullReset { + get { + return ResourceManager.GetString("Help_FullReset", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Syntax: + /// /import /filename:<path to file> [/store:<both | runtime | boot>] [/replace] + /// + ///Imports all feature configurations stored in a file into your system. By default both stores will be imported to. + /// + ///Specifying /replace performs a full reset before importing data. + /// + ///ImageDefault (0) and ImageOverride (15) priority configurations are unaffected by this command. + /// + ///Examples: + /// /import /filename:features.bin + /// /import /filename:features.bin /store:boot /replace. + /// + internal static string Help_Import { + get { + return ResourceManager.GetString("Help_Import", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Syntax: + /// /lkgstatus + /// + ///Queries the 'Last Known Good' (LKG) rollback system status. The LKG blob is a snapshot of feature + ///configurations known to cause no critical system failures. + /// + ///Status descriptions: + /// - BootPending Feature configurations have changed since booting up, a reboot is required + /// to confirm that they don't cause boot-time failures + /// + /// - LKGPending The system was able to boot successfully after a feature confguration change, + /// the current state will shortly become the new LKG blob /// [rest of string was truncated]";. + /// + internal static string Help_LKGStatus { + get { + return ResourceManager.GetString("Help_LKGStatus", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Syntax: + /// /notifyusage {{/id:<comma delimited feature IDs> | /name:<comma delimited feature names>}} + /// /reportingkind:<0-65535> /reportingoptions:<0-65535> + /// + ///Fires a feature usage notification. If a subscription with matching Kind & Options conditions + ///is found, the Target WNF state ID associated with it receives usage info. + /// + ///Features can be specified using both their IDs and names, mixing and matching is allowed. + /// + ///Examples: + /// /notifyusage /id:12345678 /reportingkind:4 /reportingoptions:1. + /// + internal static string Help_NotifyUsage { + get { + return ResourceManager.GetString("Help_NotifyUsage", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Syntax: + /// /query [/store:<runtime | boot>] [/id:<comma delimited feature IDs>] + /// [/name:<comma delimited feature names>] + /// + ///If no store is specified, the Runtime store will be queried by default. + ///You can specify feature IDs or names to filter the query results, in this case only + ///the override with the highest priority will be displayed. + /// + ///Examples: + /// /query + /// /query /store:boot /id:12345678 + /// /query /store:runtime /name:TIFE,STTest. + /// + internal static string Help_Query { + get { + return ResourceManager.GetString("Help_Query", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Syntax: + /// /reset {{/id:<comma delimited feature IDs> | /name:<comma delimited feature names>}} + /// [/priority:<enrollment | service | user | userpolicy | test>] [/store:<both | runtime | boot>] + /// + ///Features can be specified using both their IDs and names, mixing and matching is allowed. + /// + ///By default the features you've chosen will have their configuration overrides erased from + ///all priorities and both stores. Specifying a priority will limit the scope of the reset. + /// + ///ImageDefault (0) and ImageOverride (15) [rest of string was truncated]";. + /// + internal static string Help_Reset { + get { + return ResourceManager.GetString("Help_Reset", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Syntax: + /// {0} {{/id:<comma delimited feature IDs> | /name:<comma delimited feature names>}} [/variant:<0-63>] + /// [/variantpayloadkind:<none | resident | external>] [/variantpayload:<0-4294967295>] [/experiment] + /// [/priority:<enrollment | service | user | userpolicy | test>] [/store:<both | runtime | boot>] + /// + ///The parameters in square brackets don't need to be specified and will use these defaults: + /// Variant : 0 + /// VariantPayloadKind: None + /// VariantPayload : 0 + /// Experiment : false + /// [rest of string was truncated]";. + /// + internal static string Help_Set { + get { + return ResourceManager.GetString("Help_Set", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Syntax: + /// {0} {{/id:<comma delimited feature IDs> | /name:<comma delimited feature names>}} + /// /reportingkind:<0-65535> /reportingtarget:<wnf state id>{1} + /// + ///{2} + /// + ///Features can be specified using both their IDs and names, mixing and matching is allowed. + /// + ///Examples: + /// {0} /id:12345678 /reportingkind:4 /reportingtarget:0d83063ea3bdf875. + /// + internal static string Help_SetSubs { + get { + return ResourceManager.GetString("Help_SetSubs", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Creates a feature usage subscription. When the conditions specified by the Kind & Options + ///parameters are met, the Target WNF state ID receives usage info.. + /// + internal static string Help_SetSubs_Add { + get { + return ResourceManager.GetString("Help_SetSubs_Add", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Deletes a feature usage subscription.. + /// + internal static string Help_SetSubs_Delete { + get { + return ResourceManager.GetString("Help_SetSubs_Delete", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to File contains {0} Runtime type and {1} Boot type applicable feature configuration(s). + /// + internal static string ImportBreakdown { + get { + return ResourceManager.GetString("ImportBreakdown", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Importing {0} feature configuration(s) into the {1} store.... + /// + internal static string ImportProcessing { + get { + return ResourceManager.GetString("ImportProcessing", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Windows 10 build 18963 or newer is required for this program to function + ///Your current build: {0}. + /// + internal static string IncompatibleBuild { + get { + return ResourceManager.GetString("IncompatibleBuild", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to '{0}' is not a valid {1} specification. + /// + internal static string InvalidEnumSpec { + get { + return ResourceManager.GetString("InvalidEnumSpec", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to '{0}' is not a valid {1} specification in this scenario. + /// + internal static string InvalidEnumSpecInScenario { + get { + return ResourceManager.GetString("InvalidEnumSpecInScenario", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to An error occurred while querying the 'Last Known Good' rollback system status ({0}). + /// + internal static string LKGQueryFailed { + get { + return ResourceManager.GetString("LKGQueryFailed", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to LKG status: {0}. + /// + internal static string LKGStatusDisplay { + get { + return ResourceManager.GetString("LKGStatusDisplay", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to An error occurred while setting the 'Last Known Good' rollback system status ({0}) + ///Changes were made but their persistence cannot be guaranteed (Current status: {1}). + /// + internal static string LKGUpdateFailed { + get { + return ResourceManager.GetString("LKGUpdateFailed", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Found release: {0} (published on {1:d}) + /// + ///Release notes: + ///{2} + /// + ///Download at: {3}. + /// + internal static string NewAppUpdateDisplay { + get { + return ResourceManager.GetString("NewAppUpdateDisplay", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Newer dictionary found (revision {0}). + /// + internal static string NewDictUpdateDisplay { + get { + return ResourceManager.GetString("NewDictUpdateDisplay", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to No features were specified. + /// + internal static string NoFeaturesSpecified { + get { + return ResourceManager.GetString("NoFeaturesSpecified", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to No file name was specified. + /// + internal static string NoFileNameSpecified { + get { + return ResourceManager.GetString("NoFileNameSpecified", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to You're using the latest version. + /// + internal static string NoNewerVersionFound { + get { + return ResourceManager.GetString("NoNewerVersionFound", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to No Reporting Target was specified. + /// + internal static string NoReportingTargetSpecified { + get { + return ResourceManager.GetString("NoReportingTargetSpecified", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to An error occurred while firing a usage notification for feature ID {0} ({1}). + /// + internal static string NotifyUsageFailed { + get { + return ResourceManager.GetString("NotifyUsageFailed", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Successfully fired usage notification for feature ID {0}. + /// + internal static string NotifyUsageSuccess { + get { + return ResourceManager.GetString("NotifyUsageSuccess", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Failed to query feature configurations. + /// + internal static string QueryFailed { + get { + return ResourceManager.GetString("QueryFailed", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Failed to query feature usage subscriptions. + /// + internal static string QuerySubsFailed { + get { + return ResourceManager.GetString("QuerySubsFailed", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to A reboot is recommended for all changes to take effect. + /// + internal static string RebootRecommended { + get { + return ResourceManager.GetString("RebootRecommended", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to An error occurred while resetting feature configurations in the Boot store ({0}), configurations will return after reboot. + /// + internal static string ResetBootFailed { + get { + return ResourceManager.GetString("ResetBootFailed", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to An error occurred while resetting feature configurations in the Runtime store ({0}). + /// + internal static string ResetRuntimeFailed { + get { + return ResourceManager.GetString("ResetRuntimeFailed", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Successfully reset feature configuration(s). + /// + internal static string ResetSuccess { + get { + return ResourceManager.GetString("ResetSuccess", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to An error occurred while setting feature configurations in the Boot store ({0}), configurations will revert after reboot. + /// + internal static string SetBootFailed { + get { + return ResourceManager.GetString("SetBootFailed", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to An error occurred while setting feature configurations in the Runtime store ({0}). + /// + internal static string SetRuntimeFailed { + get { + return ResourceManager.GetString("SetRuntimeFailed", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to An error occurred while setting feature usage subscriptions in the Boot store ({1}), subscriptions will revert after reboot. + /// + internal static string SetSubsBootFailed { + get { + return ResourceManager.GetString("SetSubsBootFailed", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to An error occurred while setting feature usage subscriptions in the Runtime store ({0}). + /// + internal static string SetSubsRuntimeFailed { + get { + return ResourceManager.GetString("SetSubsRuntimeFailed", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Successfully set feature usage subscription(s). + /// + internal static string SetSubsSuccess { + get { + return ResourceManager.GetString("SetSubsSuccess", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Successfully set feature configuration(s). + /// + internal static string SetSuccess { + get { + return ResourceManager.GetString("SetSuccess", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to No configuration for feature ID {0} was found in the {1} store. + /// + internal static string SingleQueryFailed { + get { + return ResourceManager.GetString("SingleQueryFailed", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to ReportingKind : {0}. + /// + internal static string SubscriptionDisplay_ReportingKind { + get { + return ResourceManager.GetString("SubscriptionDisplay_ReportingKind", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to ReportingOptions: {0}. + /// + internal static string SubscriptionDisplay_ReportingOptions { + get { + return ResourceManager.GetString("SubscriptionDisplay_ReportingOptions", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to ReportingTarget : {0:x16}. + /// + internal static string SubscriptionDisplay_ReportingTarget { + get { + return ResourceManager.GetString("SubscriptionDisplay_ReportingTarget", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Unrecognized command: {0}. + /// + internal static string UnrecognizedCommand { + get { + return ResourceManager.GetString("UnrecognizedCommand", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Unrecognized parameter: {0} + ///. + /// + internal static string UnrecognizedParameter { + get { + return ResourceManager.GetString("UnrecognizedParameter", resourceCulture); + } + } + } +} diff --git a/ViVeTool/Properties/Resources.resx b/ViVeTool/Properties/Resources.resx new file mode 100644 index 0000000..93d1777 --- /dev/null +++ b/ViVeTool/Properties/Resources.resx @@ -0,0 +1,472 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + ViVeTool v0.3.0 - Windows feature configuration tool + + + + Changestamp: {0} + + + Checking for app updates... + + + Checking for feature dictionary updates... + + + The '{0}' command has been moved to '{1}' as part of a syntax improvement effort. +Arguments are now position independent and clearly labeled, ambiguous strings of numbers are no longer used. +/? can be used to view more information about usage. + + + Dictionary update canceled + + + Replace current version with new version? + + + Dictionary update finished + + + Exported {0} Runtime type and {1} Boot type feature configuration(s) to {2} + + + HasSubscriptions: {0} + + + Payload : {0:x} + + + PayloadKind : {0} ({1}) + + + Priority : {0} ({1}) + + + State : {0} ({1}) + + + Type : {0} ({1}) + + + Variant : {0} + + + Experiment + + + Override + + + 'Last Known Good' rollback system data corruption was not found + + + 'Last Known Good' rollback system data has been fixed successfully + + + Full reset canceled + + + Are you sure you want to perform a full reset? + + + Available commands: + /query Lists existing feature configuration(s) + /enable Enables a feature + /disable Disables a feature + /reset Removes custom configurations for a specific feature + /fullreset Removes all custom feature configurations + /changestamp Prints the feature store change counter (changestamp)* + /querysubs Lists existing feature usage subscriptions* + /addsub Adds a feature usage subscription + /delsub Removes a feature usage subscription + /notifyusage Fires a feature usage notification + /export Exports custom feature configurations + /import Imports custom feature configurations + /lkgstatus Prints the current 'Last Known Good' rollback system status + /fixlkg Fixes 'Last Known Good' rollback system corruption + /appupdate Checks for a new version of ViVeTool* + /dictupdate Checks for a new version of the feature name dictionary* + +Commands can be used along with /? to view more information about usage +*Does not apply to commands marked with an asterisk + + + Syntax: + /export /filename:<path to new file> [/store:<both | runtime | boot>] + +Exports all currently loaded feature configurations to a file. By default both stores are exported. + +Examples: + /export /filename:features.bin + + + Syntax: + /fixlkg + +Attempts to fix corrupted 'Last Known Good' rollback system data. Windows system components can occassionally +invalidate this data due to a programming oversight. + + + Syntax: + /fullreset [/store:<both | runtime | boot>] + +This command removes all custom feature configuration overrides, effectively reverting +feature store contents to their clean install state. Both stores are targeted by default. +Use with caution. + +ImageDefault (0) and ImageOverride (15) priority configurations are unaffected by this command. + + + Syntax: + /import /filename:<path to file> [/store:<both | runtime | boot>] [/replace] + +Imports all feature configurations stored in a file into your system. By default both stores will be imported to. + +Specifying /replace performs a full reset before importing data. + +ImageDefault (0) and ImageOverride (15) priority configurations are unaffected by this command. + +Examples: + /import /filename:features.bin + /import /filename:features.bin /store:boot /replace + + + Syntax: + /lkgstatus + +Queries the 'Last Known Good' (LKG) rollback system status. The LKG blob is a snapshot of feature +configurations known to cause no critical system failures. + +Status descriptions: + - BootPending Feature configurations have changed since booting up, a reboot is required + to confirm that they don't cause boot-time failures + + - LKGPending The system was able to boot successfully after a feature confguration change, + the current state will shortly become the new LKG blob + + - RollbackPending Recent feature configuration changes were found to cause issues, the current + state will soon be replaced by the LKG blob + + - Committed No feature configuration changes were made since booting up, the LKG blob + matches the current configuration + + + Syntax: + /notifyusage {{/id:<comma delimited feature IDs> | /name:<comma delimited feature names>}} + /reportingkind:<0-65535> /reportingoptions:<0-65535> + +Fires a feature usage notification. If a subscription with matching Kind & Options conditions +is found, the Target WNF state ID associated with it receives usage info. + +Features can be specified using both their IDs and names, mixing and matching is allowed. + +Examples: + /notifyusage /id:12345678 /reportingkind:4 /reportingoptions:1 + + + Syntax: + /query [/store:<runtime | boot>] [/id:<comma delimited feature IDs>] + [/name:<comma delimited feature names>] + +If no store is specified, the Runtime store will be queried by default. +You can specify feature IDs or names to filter the query results, in this case only +the override with the highest priority will be displayed. + +Examples: + /query + /query /store:boot /id:12345678 + /query /store:runtime /name:TIFE,STTest + + + Syntax: + /reset {{/id:<comma delimited feature IDs> | /name:<comma delimited feature names>}} + [/priority:<enrollment | service | user | userpolicy | test>] [/store:<both | runtime | boot>] + +Features can be specified using both their IDs and names, mixing and matching is allowed. + +By default the features you've chosen will have their configuration overrides erased from +all priorities and both stores. Specifying a priority will limit the scope of the reset. + +ImageDefault (0) and ImageOverride (15) priority configurations are unaffected by this command. + +Examples: + /reset /id:12345678 + /reset /name:TIFE,STTest + /reset /name:SmartClipboardUX /id:33000420 /priority:user + + + Syntax: + {0} {{/id:<comma delimited feature IDs> | /name:<comma delimited feature names>}} [/variant:<0-63>] + [/variantpayloadkind:<none | resident | external>] [/variantpayload:<0-4294967295>] [/experiment] + [/priority:<enrollment | service | user | userpolicy | test>] [/store:<both | runtime | boot>] + +The parameters in square brackets don't need to be specified and will use these defaults: + Variant : 0 + VariantPayloadKind: None + VariantPayload : 0 + Experiment : false + Priority : Service + Store : Both + +Features can be specified using both their IDs and names, mixing and matching is allowed. + +When an override is marked as an Experiment it becomes eligible for deletion by Windows's automatic +A/B feature delivery mechanism. Do NOT use this flag if you want overrides to persist. + +Writing to the Boot store is necessary for features to persist across reboots. Changes to this store become +effective the next time you reboot. The Runtime store can be used to make instantenous changes, however not +all features support this, meaning a reboot may be required to apply changes. Use both for best results. + +Examples: + {0} /id:12345678 + {0} /name:TIFE,STTest /variant:1 + {0} /name:SmartClipboardUX /id:33000420 /experiment /priority:user /store:boot + + + Syntax: + {0} {{/id:<comma delimited feature IDs> | /name:<comma delimited feature names>}} + /reportingkind:<0-65535> /reportingtarget:<wnf state id>{1} + +{2} + +Features can be specified using both their IDs and names, mixing and matching is allowed. + +Examples: + {0} /id:12345678 /reportingkind:4 /reportingtarget:0d83063ea3bdf875 + + + Creates a feature usage subscription. When the conditions specified by the Kind & Options +parameters are met, the Target WNF state ID receives usage info. + + + Deletes a feature usage subscription. + + + File contains {0} Runtime type and {1} Boot type applicable feature configuration(s) + + + Importing {0} feature configuration(s) into the {1} store... + + + Windows 10 build 18963 or newer is required for this program to function +Your current build: {0} + + + '{0}' is not a valid {1} specification + + + '{0}' is not a valid {1} specification in this scenario + + + An error occurred while querying the 'Last Known Good' rollback system status ({0}) + + + LKG status: {0} + + + An error occurred while setting the 'Last Known Good' rollback system status ({0}) +Changes were made but their persistence cannot be guaranteed (Current status: {1}) + + + Found release: {0} (published on {1:d}) + +Release notes: +{2} + +Download at: {3} + + + Newer dictionary found (revision {0}) + + + No features were specified + + + No file name was specified + + + You're using the latest version + + + No Reporting Target was specified + + + An error occurred while firing a usage notification for feature ID {0} ({1}) + + + Successfully fired usage notification for feature ID {0} + + + Failed to query feature configurations + + + Failed to query feature usage subscriptions + + + A reboot is recommended for all changes to take effect + + + An error occurred while resetting feature configurations in the Boot store ({0}), configurations will return after reboot + + + An error occurred while resetting feature configurations in the Runtime store ({0}) + + + Successfully reset feature configuration(s) + + + An error occurred while setting feature configurations in the Boot store ({0}), configurations will revert after reboot + + + An error occurred while setting feature configurations in the Runtime store ({0}) + + + An error occurred while setting feature usage subscriptions in the Boot store ({1}), subscriptions will revert after reboot + + + An error occurred while setting feature usage subscriptions in the Runtime store ({0}) + + + Successfully set feature usage subscription(s) + + + Successfully set feature configuration(s) + + + No configuration for feature ID {0} was found in the {1} store + + + ReportingKind : {0} + + + ReportingOptions: {0} + + + ReportingTarget : {0:x16} + + + Unrecognized command: {0} + + + Unrecognized parameter: {0} + + + \ No newline at end of file diff --git a/ViVeTool/UpdateCheck.cs b/ViVeTool/UpdateCheck.cs new file mode 100644 index 0000000..43a3e81 --- /dev/null +++ b/ViVeTool/UpdateCheck.cs @@ -0,0 +1,129 @@ +/* + ViVe - Windows feature configuration library + Copyright (C) 2019-2022 @thebookisclosed + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + */ + +using Newtonsoft.Json; +using System; +using System.IO; +using System.Linq; +using System.Net; +using System.Security.Cryptography; +using System.Text; + +namespace Albacore.ViVeTool +{ + internal class UpdateCheck + { + private const string GitHubRepoApiRoot = "https://api.github.com/repos/thebookisclosed/ViVe/"; + private static WebClient UcWebClient; + + internal static GitHubReleaseInfo GetLatestReleaseInfo() + { + return GetJsonResponse("releases/latest"); + } + + internal static bool IsAppOutdated(string versionTag) + { + var foundVersion = new Version(versionTag.Substring(1)); + var currentVersion = System.Reflection.Assembly.GetExecutingAssembly().GetName().Version; + return foundVersion > currentVersion; + } + + internal static GitHubRepoContent GetLatestDictionaryInfo() + { + var resp = GetJsonResponse("contents/Extra"); + var dictInfo = resp?.Where(x => x.name == FeatureNaming.DictFileName).FirstOrDefault(); + return dictInfo; + } + + internal static bool IsDictOutdated(string sha) + { + return sha != HashUTF8TextFile(FeatureNaming.DictFileName); + } + + internal static void ReplaceDict(string url) + { + EnsureWebClient(); + UcWebClient.DownloadFile(url, FeatureNaming.DictFileName); + } + + private static void EnsureWebClient() + { + if (UcWebClient == null) + { + UcWebClient = new WebClient(); + UcWebClient.Headers.Add("User-Agent", "ViVeTool"); + } + } + + private static T GetJsonResponse(string apiPath) + { + EnsureWebClient(); + try + { + var stringResponse = UcWebClient.DownloadString(GitHubRepoApiRoot + apiPath); + return JsonConvert.DeserializeObject(stringResponse); + } + catch { return default; } + } + + private static string HashUTF8TextFile(string filePath) + { + if (!File.Exists(filePath)) + return null; + using (var sha1Csp = new SHA1CryptoServiceProvider()) + { + var fileBody = Encoding.UTF8.GetBytes(File.ReadAllText(filePath).Replace("\r\n", "\n")); + var fullLength = fileBody.Length; + + var preamble = Encoding.UTF8.GetPreamble(); + var filePortion = new byte[preamble.Length]; + using (var fs = new FileStream(filePath, FileMode.Open, FileAccess.Read, FileShare.ReadWrite)) + fs.Read(filePortion, 0, filePortion.Length); + + for (int i = 0; i < preamble.Length; i++) + if (preamble[i] == filePortion[i]) + fullLength++; + + var preface = Encoding.UTF8.GetBytes($"blob {fullLength}\0"); + sha1Csp.TransformBlock(preface, 0, preface.Length, null, 0); + if (fullLength > fileBody.Length) + sha1Csp.TransformBlock(preamble, 0, preamble.Length, null, 0); + sha1Csp.TransformFinalBlock(fileBody, 0, fileBody.Length); + + return BitConverter.ToString(sha1Csp.Hash).Replace("-", "").ToLowerInvariant(); + } + } + } + + public class GitHubReleaseInfo + { + public string html_url { get; set; } + public string tag_name { get; set; } + public string name { get; set; } + public DateTime published_at { get; set; } + public string body { get; set; } + } + + + public class GitHubRepoContent + { + public string name { get; set; } + public string sha { get; set; } + public string download_url { get; set; } + } +} diff --git a/ViVeTool/ViVeTool.csproj b/ViVeTool/ViVeTool.csproj index 45fcd73..3ab0265 100644 --- a/ViVeTool/ViVeTool.csproj +++ b/ViVeTool/ViVeTool.csproj @@ -19,7 +19,7 @@ full false bin\Debug\ - DEBUG;TRACE + TRACE;DEBUG;SET_LKG_COMMAND prompt 4 false @@ -38,6 +38,9 @@ app.manifest + + ..\packages\Newtonsoft.Json.13.0.1\lib\net45\Newtonsoft.Json.dll + @@ -48,12 +51,23 @@ + + + + + + + True + True + Resources.resx + + @@ -61,5 +75,11 @@ ViVe + + + ResXFileCodeGenerator + Resources.Designer.cs + + \ No newline at end of file diff --git a/ViVeTool/packages.config b/ViVeTool/packages.config new file mode 100644 index 0000000..5eaa239 --- /dev/null +++ b/ViVeTool/packages.config @@ -0,0 +1,4 @@ + + + + \ No newline at end of file