diff --git a/README.md b/README.md new file mode 100644 index 0000000..77cd1a3 --- /dev/null +++ b/README.md @@ -0,0 +1,14 @@ +# ViVe +ViVe is a C# library you can use to make your own programs that interact with Windows 10's A/B feature mechanism + +In case you'd like to talk to NTDLL exports directly, you can use its *NativeMethods*. + +Otherwise, *RtlFeatureManager* offers the same featureset with the benefit of all unmanaged structures being delivered as standard C# classes instead. + +# 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. + +# Compatibility +In order to use ViVe, you must be running Windows 10 build 18963 or newer. + +![ViVeTool Helpfile](https://i.imgur.com/HrLiSxe.png) \ No newline at end of file diff --git a/ViVe.sln b/ViVe.sln new file mode 100644 index 0000000..8121c64 --- /dev/null +++ b/ViVe.sln @@ -0,0 +1,31 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 16 +VisualStudioVersion = 16.0.29306.81 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ViVe", "ViVe\ViVe.csproj", "{80DCDA4D-8022-4740-8CCF-459DD3FE6F72}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ViVeTool", "ViVeTool\ViVeTool.csproj", "{4DAAB723-3613-4133-AE54-646133538E44}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {80DCDA4D-8022-4740-8CCF-459DD3FE6F72}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {80DCDA4D-8022-4740-8CCF-459DD3FE6F72}.Debug|Any CPU.Build.0 = Debug|Any CPU + {80DCDA4D-8022-4740-8CCF-459DD3FE6F72}.Release|Any CPU.ActiveCfg = Release|Any CPU + {80DCDA4D-8022-4740-8CCF-459DD3FE6F72}.Release|Any CPU.Build.0 = Release|Any CPU + {4DAAB723-3613-4133-AE54-646133538E44}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {4DAAB723-3613-4133-AE54-646133538E44}.Debug|Any CPU.Build.0 = Debug|Any CPU + {4DAAB723-3613-4133-AE54-646133538E44}.Release|Any CPU.ActiveCfg = Release|Any CPU + {4DAAB723-3613-4133-AE54-646133538E44}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {E5173A5A-E3A8-4C36-9798-11628C4E4684} + EndGlobalSection +EndGlobal diff --git a/ViVe/FeatureConfiguration.cs b/ViVe/FeatureConfiguration.cs new file mode 100644 index 0000000..6d10a1c --- /dev/null +++ b/ViVe/FeatureConfiguration.cs @@ -0,0 +1,76 @@ +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 > 15) + throw new ArgumentException("Group must not be more than 15"); + _group = value; + } + } + public FeatureEnabledState EnabledState + { + get { return _enabledState; } + set + { + if ((int)value > 3) + throw new ArgumentException("EnabledState must not be more than 3"); + _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/FeatureConfigurationAction.cs b/ViVe/FeatureConfigurationAction.cs new file mode 100644 index 0000000..88ee1e3 --- /dev/null +++ b/ViVe/FeatureConfigurationAction.cs @@ -0,0 +1,11 @@ +namespace Albacore.ViVe +{ + public enum FeatureConfigurationAction + { + None = 0, + UpdateEnabledState = 1, + UpdateVariant = 2, + UpdateAll = 3, + Delete = 4 + } +} diff --git a/ViVe/FeatureConfigurationSection.cs b/ViVe/FeatureConfigurationSection.cs new file mode 100644 index 0000000..a45d671 --- /dev/null +++ b/ViVe/FeatureConfigurationSection.cs @@ -0,0 +1,9 @@ +namespace Albacore.ViVe +{ + public enum FeatureConfigurationSection + { + Boot = 0, + Runtime = 1, + UsageTriggers = 2 + } +} diff --git a/ViVe/FeatureEnabledState.cs b/ViVe/FeatureEnabledState.cs new file mode 100644 index 0000000..45907d7 --- /dev/null +++ b/ViVe/FeatureEnabledState.cs @@ -0,0 +1,9 @@ +namespace Albacore.ViVe +{ + public enum FeatureEnabledState + { + Default = 0, + Disabled = 1, + Enabled = 2 + } +} diff --git a/ViVe/FeatureUsageReport.cs b/ViVe/FeatureUsageReport.cs new file mode 100644 index 0000000..419db3c --- /dev/null +++ b/ViVe/FeatureUsageReport.cs @@ -0,0 +1,9 @@ +namespace Albacore.ViVe +{ + public class FeatureUsageReport + { + public uint FeatureId { get; set; } + public ushort ReportingKind { get; set; } + public ushort ReportingOptions { get; set; } + } +} diff --git a/ViVe/FeatureUsageSubscription.cs b/ViVe/FeatureUsageSubscription.cs new file mode 100644 index 0000000..e757210 --- /dev/null +++ b/ViVe/FeatureUsageSubscription.cs @@ -0,0 +1,10 @@ +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/NativeMethods.cs b/ViVe/NativeMethods.cs new file mode 100644 index 0000000..fb99dd7 --- /dev/null +++ b/ViVe/NativeMethods.cs @@ -0,0 +1,73 @@ +using System; +using System.Runtime.InteropServices; + +namespace Albacore.ViVe +{ + public delegate void FeatureConfigurationChangeCallback(IntPtr Context); + + public static class NativeMethods + { + [DllImport("ntdll.dll")] + public static extern int RtlQueryAllFeatureConfigurations( + FeatureConfigurationSection sectionType, + ref uint changeStamp, + IntPtr buffer, + ref int featureCount + ); + + [DllImport("ntdll.dll")] + public static extern int RtlQueryFeatureConfiguration( + uint featureId, + FeatureConfigurationSection sectionType, + ref uint changeStamp, + IntPtr buffer + ); + + [DllImport("ntdll.dll")] + public static extern uint RtlQueryFeatureConfigurationChangeStamp(); + + [DllImport("ntdll.dll")] + public static extern int RtlQueryFeatureUsageNotificationSubscriptions( + IntPtr buffer, + ref int subscriptionCount + ); + + [DllImport("ntdll.dll")] + public static extern int RtlSetFeatureConfigurations( + ref uint changeStamp, + FeatureConfigurationSection sectionType, + byte[] buffer, + int featureCount + ); + + [DllImport("ntdll.dll")] + public static extern int RtlRegisterFeatureConfigurationChangeNotification( + FeatureConfigurationChangeCallback callback, + IntPtr context, + IntPtr unknown, + out IntPtr subscription + ); + + [DllImport("ntdll.dll")] + public static extern int RtlUnregisterFeatureConfigurationChangeNotification( + IntPtr subscription + ); + + [DllImport("ntdll.dll")] + public static extern int RtlSubscribeForFeatureUsageNotification( + byte[] buffer, + int subscriptionCount + ); + + [DllImport("ntdll.dll")] + public static extern int RtlUnsubscribeFromFeatureUsageNotifications( + byte[] buffer, + int subscriptionCount + ); + + [DllImport("ntdll.dll")] + public static extern int RtlNotifyFeatureUsage( + byte[] buffer + ); + } +} diff --git a/ViVe/Properties/AssemblyInfo.cs b/ViVe/Properties/AssemblyInfo.cs new file mode 100644 index 0000000..286840e --- /dev/null +++ b/ViVe/Properties/AssemblyInfo.cs @@ -0,0 +1,36 @@ +using System.Reflection; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +// General Information about an assembly is controlled through the following +// set of attributes. Change these attribute values to modify the information +// associated with an assembly. +[assembly: AssemblyTitle("Albacore.ViVe")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("")] +[assembly: AssemblyProduct("Albacore.ViVe")] +[assembly: AssemblyCopyright("Copyright © @thebookisclosed 2019")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] + +// Setting ComVisible to false makes the types in this assembly not visible +// to COM components. If you need to access a type in this assembly from +// COM, set the ComVisible attribute to true on that type. +[assembly: ComVisible(false)] + +// The following GUID is for the ID of the typelib if this project is exposed to COM +[assembly: Guid("80dcda4d-8022-4740-8ccf-459dd3fe6f72")] + +// Version information for an assembly consists of the following four values: +// +// Major Version +// Minor Version +// Build Number +// Revision +// +// 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("2019.9.18.1002")] +[assembly: AssemblyFileVersion("2019.9.18.1002")] diff --git a/ViVe/RtlDataHelpers.cs b/ViVe/RtlDataHelpers.cs new file mode 100644 index 0000000..86b7bbc --- /dev/null +++ b/ViVe/RtlDataHelpers.cs @@ -0,0 +1,61 @@ +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; + } + } +} diff --git a/ViVe/RtlFeatureManager.cs b/ViVe/RtlFeatureManager.cs new file mode 100644 index 0000000..224abd0 --- /dev/null +++ b/ViVe/RtlFeatureManager.cs @@ -0,0 +1,185 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Runtime.InteropServices; + +namespace Albacore.ViVe +{ + public static class RtlFeatureManager + { + public static List QueryAllFeatureConfigurations() + { + uint dummy = 0; + return QueryAllFeatureConfigurations(FeatureConfigurationSection.Runtime, ref dummy); + } + + public static List QueryAllFeatureConfigurations(FeatureConfigurationSection section) + { + uint dummy = 0; + return QueryAllFeatureConfigurations(section, ref dummy); + } + + public static List QueryAllFeatureConfigurations(FeatureConfigurationSection section, ref uint 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) + { + uint dummy = 0; + return QueryFeatureConfiguration(featureId, FeatureConfigurationSection.Runtime, ref dummy); + } + + public static FeatureConfiguration QueryFeatureConfiguration(uint featureId, FeatureConfigurationSection section) + { + uint dummy = 0; + return QueryFeatureConfiguration(featureId, section, ref dummy); + } + + public static FeatureConfiguration QueryFeatureConfiguration(uint featureId, FeatureConfigurationSection section, ref uint 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 uint QueryFeatureConfigurationChangeStamp() + { + return NativeMethods.RtlQueryFeatureConfigurationChangeStamp(); + } + + public static int SetFeatureConfigurations(List configurations) + { + uint dummy = 0; + return SetFeatureConfigurations(configurations, FeatureConfigurationSection.Runtime, ref dummy); + } + + public static int SetFeatureConfigurations(List configurations, FeatureConfigurationSection section) + { + uint dummy = 0; + return SetFeatureConfigurations(configurations, section, ref dummy); + } + + public static int SetFeatureConfigurations(List configurations, FeatureConfigurationSection section, ref uint 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 AddFeatureUsageSubscriptions(List subscriptions) + { + return NativeMethods.RtlSubscribeForFeatureUsageNotification(RtlDataHelpers.SerializeFeatureUsageSubscriptions(subscriptions), subscriptions.Count); + } + + public static int RemoveFeatureUsageSubscriptions(List subscriptions) + { + return NativeMethods.RtlUnsubscribeFromFeatureUsageNotifications(RtlDataHelpers.SerializeFeatureUsageSubscriptions(subscriptions), subscriptions.Count); + } + + public static int NotifyFeatureUsage(FeatureUsageReport report) + { + return NativeMethods.RtlNotifyFeatureUsage(RtlDataHelpers.SerializeFeatureUsageReport(report)); + } + } +} diff --git a/ViVe/ViVe.csproj b/ViVe/ViVe.csproj new file mode 100644 index 0000000..1336b3d --- /dev/null +++ b/ViVe/ViVe.csproj @@ -0,0 +1,56 @@ + + + + + Debug + AnyCPU + {80DCDA4D-8022-4740-8CCF-459DD3FE6F72} + Library + Properties + Albacore.ViVe + Albacore.ViVe + v4.7.2 + 512 + true + + + true + full + false + bin\Debug\ + DEBUG;TRACE + prompt + 4 + + + pdbonly + true + bin\Release\ + TRACE + prompt + 4 + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/ViVeTool/App.config b/ViVeTool/App.config new file mode 100644 index 0000000..56efbc7 --- /dev/null +++ b/ViVeTool/App.config @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/ViVeTool/Program.cs b/ViVeTool/Program.cs new file mode 100644 index 0000000..3895e80 --- /dev/null +++ b/ViVeTool/Program.cs @@ -0,0 +1,346 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using Albacore.ViVe; + +namespace Albacore.ViVeTool +{ + class Program + { + static void Main(string[] args) + { + Console.WriteLine("ViVeTool v0.1 - Vibranium feature configuration tool\n"); + if (args.Length < 1) + { + PrintHelp(); + return; + } + if (Environment.OSVersion.Version.Build < 18963) + { + Console.WriteLine("Windows 10 build 18963 or newer is required for this program to function"); + return; + } + ProcessArgs(args); + } + + static void ProcessArgs(string[] args) + { + for (int i = 0; i < args.Length; i++) + args[i] = args[i].ToLower(); + + #region QueryConfig + if (args[0] == "queryconfig") + { + 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") + { + var retrievedConfigs = RtlFeatureManager.QueryAllFeatureConfigurations(sectionToUse); + if (retrievedConfigs != null) + { + foreach (var config in retrievedConfigs) + { + PrintFeatureConfig(config); + Console.WriteLine(); + } + } + 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"); + } + } + #endregion + #region QuerySubs + else if (args[0] == "querysubs") + { + var retrievedSubs = RtlFeatureManager.QueryFeatureUsageSubscriptions(); + if (retrievedSubs != null) + { + foreach (var sub in retrievedSubs) + { + PrintSubscription(sub); + Console.WriteLine(); + } + } + } + #endregion + #region ChangeStamp + else if (args[0] == "changestamp") + { + Console.WriteLine("Change stamp: {0}", RtlFeatureManager.QueryFeatureConfigurationChangeStamp()); + } + #endregion + #region AddConfig + else if (args[0] == "addconfig") + { + ProcessSetConfig(args, FeatureConfigurationAction.UpdateAll); + } + #endregion + #region DelConfig + else if (args[0] == "delconfig") + { + ProcessSetConfig(args, FeatureConfigurationAction.Delete); + } + #endregion + #region AddConfig + else if (args[0] == "addsub") + { + ProcessSetSubs(args, false); + } + #endregion + #region DelConfig + else if (args[0] == "delsub") + { + ProcessSetSubs(args, true); + } + #endregion + #region NotifyUsage + else if (args[0] == "notifyusage") + { + 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)) + { + Console.WriteLine("Couldn't parse reporting kind"); + return; + } + if (!TryParseDecHexUshort(args[3], out ushort reportingOptions)) + { + Console.WriteLine("Couldn't parse reporting options"); + return; + } + 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 + } + + static void ProcessSetSubs(string[] args, bool del) + { + if (args.Length < 5) + { + 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; + } + if (!TryParseDecHexUint(args[1], out uint featureId)) + { + Console.WriteLine("Couldn't parse feature ID"); + return; + } + if (!TryParseDecHexUshort(args[2], out ushort reportingKind)) + { + Console.WriteLine("Couldn't parse reporting kind"); + return; + } + if (!TryParseDecHexUshort(args[3], out ushort reportingOptions)) + { + Console.WriteLine("Couldn't parse reporting options"); + return; + } + if (!ulong.TryParse(args[4], System.Globalization.NumberStyles.HexNumber, System.Globalization.CultureInfo.InvariantCulture, out ulong reportingTarget)) + { + Console.WriteLine("Couldn't parse reporting target"); + return; + } + int result; + if (del) + result = RtlFeatureManager.RemoveFeatureUsageSubscriptions(new List() { + new FeatureUsageSubscription() { + FeatureId = featureId, + ReportingKind = reportingKind, + ReportingOptions = reportingOptions, + ReportingTarget = reportingTarget + } }); + else + result = RtlFeatureManager.AddFeatureUsageSubscriptions(new List() { + new FeatureUsageSubscription() { + FeatureId = featureId, + ReportingKind = reportingKind, + ReportingOptions = reportingOptions, + ReportingTarget = reportingTarget + } }); + if (result != 0) + Console.WriteLine("An error occurred while setting the feature usage subscription (hr=0x{0:x8})", result); + else + Console.WriteLine("Successfully set feature usage subscription"); + } + + static void ProcessSetConfig(string[] args, FeatureConfigurationAction actionToUse) + { + if (args.Length < 4) + { + 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("If an invalid section is specified, 'runtime' will be used\n\nEnabled state types: 0 = Default, 1 = Disabled, 2 = Enabled\n"); + Console.WriteLine("Examples: {0}config runtime 123456 2\n {0}config runtime 456789 2 1 4 1 0 0", helpPrefix); + return; + } + FeatureConfigurationSection sectionToUse = FeatureConfigurationSection.Runtime; + if (args[1] == "boot") + sectionToUse = FeatureConfigurationSection.Boot; + else + { + Console.WriteLine("Invalid action, only 'add' or 'delete' are supported"); + return; + } + if (!TryParseDecHexUint(args[2], out uint featureId)) + { + Console.WriteLine("Couldn't parse feature ID"); + return; + } + if (!TryParseDecHexInt(args[3], out int enabledState)) + { + Console.WriteLine("Couldn't parse enabled state"); + return; + } + int enabledStateOptions = 1; + int variant = 0; + int variantPayloadKind = 0; + int variantPayload = 0; + int group = 4; + if (args.Length > 4 && !TryParseDecHexInt(args[4], out enabledStateOptions)) + { + Console.WriteLine("Couldn't parse enabled state options"); + return; + } + if (args.Length > 5 && !TryParseDecHexInt(args[5], out variant)) + { + Console.WriteLine("Couldn't parse variant"); + return; + } + if (args.Length > 6 && !TryParseDecHexInt(args[6], out variantPayloadKind)) + { + Console.WriteLine("Couldn't parse variant payload kind"); + return; + } + if (args.Length > 7 && !TryParseDecHexInt(args[7], out variantPayload)) + { + Console.WriteLine("Couldn't parse variant payload"); + return; + } + if (args.Length > 8 && !TryParseDecHexInt(args[8], out group)) + { + Console.WriteLine("Couldn't parse group"); + return; + } + int result = RtlFeatureManager.SetFeatureConfigurations(new List() { + new FeatureConfiguration() { + FeatureId = featureId, + EnabledState = (FeatureEnabledState)enabledState, + EnabledStateOptions = enabledStateOptions, + Group = group, + Variant = variant, + VariantPayloadKind = variantPayloadKind, + VariantPayload = variantPayload, + Action = actionToUse + } }, sectionToUse); + if (result != 0) + Console.WriteLine("An error occurred while setting the feature configuration (hr=0x{0:x8})", result); + else + Console.WriteLine("Successfully set feature configuration"); + } + + 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; + } + + 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; + } + + static bool TryParseDecHexInt(string input, out int output) + { + bool success; + if (input.StartsWith("0x")) + success = int.TryParse(input, System.Globalization.NumberStyles.HexNumber, System.Globalization.CultureInfo.InvariantCulture, out output); + else + success = int.TryParse(input, out output); + return success; + } + + static void PrintHelp() + { + 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"); + } + + static void PrintFeatureConfig(FeatureConfiguration config) + { + 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); + } + + static void PrintSubscription(FeatureUsageSubscription sub) + { + 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); + } + } +} diff --git a/ViVeTool/Properties/AssemblyInfo.cs b/ViVeTool/Properties/AssemblyInfo.cs new file mode 100644 index 0000000..b3c2cc5 --- /dev/null +++ b/ViVeTool/Properties/AssemblyInfo.cs @@ -0,0 +1,36 @@ +using System.Reflection; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +// General Information about an assembly is controlled through the following +// set of attributes. Change these attribute values to modify the information +// associated with an assembly. +[assembly: AssemblyTitle("ViVeTool")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("")] +[assembly: AssemblyProduct("ViVeTool")] +[assembly: AssemblyCopyright("Copyright © @thebookisclosed 2019")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] + +// Setting ComVisible to false makes the types in this assembly not visible +// to COM components. If you need to access a type in this assembly from +// COM, set the ComVisible attribute to true on that type. +[assembly: ComVisible(false)] + +// The following GUID is for the ID of the typelib if this project is exposed to COM +[assembly: Guid("4daab723-3613-4133-ae54-646133538e44")] + +// Version information for an assembly consists of the following four values: +// +// Major Version +// Minor Version +// Build Number +// Revision +// +// 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.0.0")] +[assembly: AssemblyFileVersion("0.1.0.0")] diff --git a/ViVeTool/ViVeTool.csproj b/ViVeTool/ViVeTool.csproj new file mode 100644 index 0000000..45fcd73 --- /dev/null +++ b/ViVeTool/ViVeTool.csproj @@ -0,0 +1,65 @@ + + + + + Debug + AnyCPU + {4DAAB723-3613-4133-AE54-646133538E44} + Exe + Albacore.ViVeTool + ViVeTool + v4.7.2 + 512 + true + true + + + AnyCPU + true + full + false + bin\Debug\ + DEBUG;TRACE + prompt + 4 + false + + + AnyCPU + pdbonly + true + bin\Release\ + TRACE + prompt + 4 + false + + + app.manifest + + + + + + + + + + + + + + + + + + + + + + {80dcda4d-8022-4740-8ccf-459dd3fe6f72} + ViVe + + + + \ No newline at end of file diff --git a/ViVeTool/app.manifest b/ViVeTool/app.manifest new file mode 100644 index 0000000..ea01d77 --- /dev/null +++ b/ViVeTool/app.manifest @@ -0,0 +1,16 @@ + + + + + + + + + + + + + + + +