diff --git a/.gitignore b/.gitignore
index cec6728b..db8c84cb 100644
--- a/.gitignore
+++ b/.gitignore
@@ -346,7 +346,6 @@ healthchecksdb
/DSHMC.schema.json
/setup/DsHidMini Driver-SetupFiles
/setup/DsHidMini Driver-cache
-/ControlApp
**/vcpkg_installed
**/*.g.wxs
/XInputBridge/exports
diff --git a/ControlApp/App.xaml b/ControlApp/App.xaml
new file mode 100644
index 00000000..efa55f68
--- /dev/null
+++ b/ControlApp/App.xaml
@@ -0,0 +1,34 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/ControlApp/App.xaml.cs b/ControlApp/App.xaml.cs
new file mode 100644
index 00000000..899cba1f
--- /dev/null
+++ b/ControlApp/App.xaml.cs
@@ -0,0 +1,116 @@
+// This Source Code Form is subject to the terms of the MIT License.
+// If a copy of the MIT was not distributed with this file, You can obtain one at https://opensource.org/licenses/MIT.
+// Copyright (C) Leszek Pomianowski and WPF UI Contributors.
+// All Rights Reserved.
+
+using System.IO;
+using System.Reflection;
+using System.Windows.Threading;
+using Microsoft.Extensions.Configuration;
+using Microsoft.Extensions.DependencyInjection;
+using Microsoft.Extensions.Hosting;
+using Nefarius.DsHidMini.ControlApp.Models;
+using Nefarius.DsHidMini.ControlApp.Models.DshmConfigManager;
+using Nefarius.DsHidMini.ControlApp.Services;
+using Nefarius.DsHidMini.ControlApp.ViewModels.Pages;
+using Nefarius.DsHidMini.ControlApp.ViewModels.Windows;
+using Nefarius.DsHidMini.ControlApp.Views.Pages;
+using Nefarius.DsHidMini.ControlApp.Views.Windows;
+using Nefarius.Utilities.Bluetooth;
+using Nefarius.Utilities.DeviceManagement.PnP;
+
+using Serilog;
+using Serilog.Core;
+using Serilog.Events;
+using Serilog.Sinks.File;
+
+using Wpf.Ui;
+
+namespace Nefarius.DsHidMini.ControlApp
+{
+ ///
+ /// Interaction logic for App.xaml
+ ///
+ public partial class App
+ {
+ // The.NET Generic Host provides dependency injection, configuration, logging, and other services.
+ // https://docs.microsoft.com/dotnet/core/extensions/generic-host
+ // https://docs.microsoft.com/dotnet/core/extensions/dependency-injection
+ // https://docs.microsoft.com/dotnet/core/extensions/configuration
+ // https://docs.microsoft.com/dotnet/core/extensions/logging
+ private static readonly IHost _host = Host
+ .CreateDefaultBuilder()
+ .ConfigureAppConfiguration(c => { c.SetBasePath(Path.GetDirectoryName(Assembly.GetEntryAssembly()!.Location)); })
+ .ConfigureServices((context, services) =>
+ {
+ services.AddHostedService();
+
+ services.AddSingleton();
+ services.AddSingleton();
+ services.AddSingleton();
+ services.AddSingleton();
+ services.AddSingleton();
+
+ services.AddSingleton();
+ services.AddSingleton();
+
+ services.AddSingleton();
+ services.AddSingleton();
+
+ services.AddSingleton();
+ services.AddSingleton();
+ services.AddSingleton();
+ services.AddSingleton();
+ services.AddSingleton();
+ services.AddSingleton();
+ services.AddSingleton();
+
+ Log.Logger = new LoggerConfiguration()
+ //.MinimumLevel.Debug()
+ .WriteTo.File(Path.Combine(
+ Environment.GetFolderPath(Environment.SpecialFolder.CommonApplicationData),
+ "DsHidMini\\Log\\ControlAppLog.txt"))
+ .CreateLogger();
+
+ services.AddSerilog(Log.Logger);
+ }).Build();
+
+ ///
+ /// Gets registered service.
+ ///
+ /// Type of the service to get.
+ /// Instance of the service or .
+ public static T GetService()
+ where T : class
+ {
+ return _host.Services.GetService(typeof(T)) as T;
+ }
+
+ ///
+ /// Occurs when the application is loading.
+ ///
+ private void OnStartup(object sender, StartupEventArgs e)
+ {
+ Log.Logger.Information("App startup");
+ _host.Start();
+ }
+
+ ///
+ /// Occurs when the application is closing.
+ ///
+ private async void OnExit(object sender, ExitEventArgs e)
+ {
+ Log.Logger.Information("App exiting");
+ await _host.StopAsync();
+ _host.Dispose();
+ }
+
+ ///
+ /// Occurs when an exception is thrown by an application but not handled.
+ ///
+ private void OnDispatcherUnhandledException(object sender, DispatcherUnhandledExceptionEventArgs e)
+ {
+ // For more info see https://docs.microsoft.com/en-us/dotnet/api/system.windows.application.dispatcherunhandledexception?view=windowsdesktop-6.0
+ }
+ }
+}
diff --git a/ControlApp/AssemblyInfo.cs b/ControlApp/AssemblyInfo.cs
new file mode 100644
index 00000000..d575b147
--- /dev/null
+++ b/ControlApp/AssemblyInfo.cs
@@ -0,0 +1,15 @@
+// This Source Code Form is subject to the terms of the MIT License.
+// If a copy of the MIT was not distributed with this file, You can obtain one at https://opensource.org/licenses/MIT.
+// Copyright (C) Leszek Pomianowski and WPF UI Contributors.
+// All Rights Reserved.
+
+using System.Windows;
+
+[assembly: ThemeInfo(
+ ResourceDictionaryLocation.None, //where theme specific resource dictionaries are located
+ //(used if a resource is not found in the page,
+ // or application resource dictionaries)
+ ResourceDictionaryLocation.SourceAssembly //where the generic resource dictionary is located
+//(used if a resource is not found in the page,
+// app, or any theme specific resource dictionaries)
+)]
diff --git a/ControlApp/Assets/wpfui-icon-1024.png b/ControlApp/Assets/wpfui-icon-1024.png
new file mode 100644
index 00000000..b70c4ed5
Binary files /dev/null and b/ControlApp/Assets/wpfui-icon-1024.png differ
diff --git a/ControlApp/Assets/wpfui-icon-256.png b/ControlApp/Assets/wpfui-icon-256.png
new file mode 100644
index 00000000..6b5cf5d5
Binary files /dev/null and b/ControlApp/Assets/wpfui-icon-256.png differ
diff --git a/ControlApp/ControlApp.csproj b/ControlApp/ControlApp.csproj
new file mode 100644
index 00000000..ff0fe958
--- /dev/null
+++ b/ControlApp/ControlApp.csproj
@@ -0,0 +1,71 @@
+
+
+
+ WinExe
+ net7.0-windows10.0.17763.0
+ app.manifest
+ wpfui-icon.ico
+ enable
+ enable
+ true
+ AnyCPU;x64
+ 10.0.17763.0
+ Nefarius.DsHidMini.ControlApp
+ true
+ 3.0.0
+
+
+
+
+
+
+
+
+ all
+ runtime; build; native; contentfiles; analyzers; buildtransitive
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ True
+ True
+ test.resx
+
+
+ Code
+
+
+ Code
+
+
+
+
+
+ PublicResXFileCodeGenerator
+ test.Designer.cs
+
+
+
+
diff --git a/ControlApp/FodyWeavers.xml b/ControlApp/FodyWeavers.xml
new file mode 100644
index 00000000..63fc1484
--- /dev/null
+++ b/ControlApp/FodyWeavers.xml
@@ -0,0 +1,3 @@
+
+
+
\ No newline at end of file
diff --git a/ControlApp/FodyWeavers.xsd b/ControlApp/FodyWeavers.xsd
new file mode 100644
index 00000000..f3ac4762
--- /dev/null
+++ b/ControlApp/FodyWeavers.xsd
@@ -0,0 +1,26 @@
+
+
+
+
+
+
+
+
+
+
+ 'true' to run assembly verification (PEVerify) on the target assembly after all weavers have been executed.
+
+
+
+
+ A comma-separated list of error codes that can be safely ignored in assembly verification.
+
+
+
+
+ 'false' to turn off automatic generation of the XML Schema file.
+
+
+
+
+
\ No newline at end of file
diff --git a/ControlApp/Helpers/BooleanConverter.cs b/ControlApp/Helpers/BooleanConverter.cs
new file mode 100644
index 00000000..817aca3c
--- /dev/null
+++ b/ControlApp/Helpers/BooleanConverter.cs
@@ -0,0 +1,36 @@
+using System.Globalization;
+using System.Windows.Data;
+
+namespace Nefarius.DsHidMini.ControlApp.Helpers
+{
+ public class BooleanConverter : IValueConverter
+ {
+ public BooleanConverter(T trueValue, T falseValue)
+ {
+ True = trueValue;
+ False = falseValue;
+ }
+
+ public T True { get; set; }
+ public T False { get; set; }
+
+ public virtual object Convert(object value, Type targetType, object parameter, CultureInfo culture)
+ {
+ return value is bool && ((bool)value) ? True : False;
+ }
+
+ public virtual object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
+ {
+ return value is T && EqualityComparer.Default.Equals((T)value, True);
+ }
+ }
+
+ public sealed class BooleanToVisibilityConverter : BooleanConverter
+ {
+ public BooleanToVisibilityConverter() :
+ base(Visibility.Visible, Visibility.Collapsed)
+ { }
+ }
+
+
+}
diff --git a/ControlApp/Helpers/BooleanToReverseConverter.cs b/ControlApp/Helpers/BooleanToReverseConverter.cs
new file mode 100644
index 00000000..81fdfcec
--- /dev/null
+++ b/ControlApp/Helpers/BooleanToReverseConverter.cs
@@ -0,0 +1,17 @@
+using System.Globalization;
+using System.Windows.Data;
+
+namespace Nefarius.DsHidMini.ControlApp.Helpers
+{
+ public class BooleanToReverseConverter : IValueConverter
+ {
+ public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
+ {
+
+ return !(bool?)value ?? true;
+ }
+
+ public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
+ => !(value as bool?);
+ }
+}
\ No newline at end of file
diff --git a/ControlApp/Helpers/EnumBindingSourceExtension.cs b/ControlApp/Helpers/EnumBindingSourceExtension.cs
new file mode 100644
index 00000000..976238ab
--- /dev/null
+++ b/ControlApp/Helpers/EnumBindingSourceExtension.cs
@@ -0,0 +1,53 @@
+using System.Windows.Markup;
+
+namespace Nefarius.DsHidMini.ControlApp.Helpers
+{
+ public class EnumBindingSourceExtension : MarkupExtension
+ {
+ private Type _enumType;
+
+ public EnumBindingSourceExtension()
+ {
+ }
+
+ public EnumBindingSourceExtension(Type enumType)
+ {
+ EnumType = enumType;
+ }
+
+ public Type EnumType
+ {
+ get => _enumType;
+ set
+ {
+ if (value != _enumType)
+ {
+ if (null != value)
+ {
+ var enumType = Nullable.GetUnderlyingType(value) ?? value;
+ if (!enumType.IsEnum)
+ throw new ArgumentException("Type must be for an Enum.");
+ }
+
+ _enumType = value;
+ }
+ }
+ }
+
+ public override object ProvideValue(IServiceProvider serviceProvider)
+ {
+ if (null == _enumType)
+ throw new InvalidOperationException("The EnumType must be specified.");
+
+ var actualEnumType = Nullable.GetUnderlyingType(_enumType) ?? _enumType;
+ var enumValues = Enum.GetValues(actualEnumType);
+
+ if (actualEnumType == _enumType)
+ return enumValues;
+
+ var tempArray = Array.CreateInstance(actualEnumType, enumValues.Length + 1);
+ enumValues.CopyTo(tempArray, 1);
+ return tempArray;
+ }
+ }
+}
\ No newline at end of file
diff --git a/ControlApp/Helpers/EnumDescriptionTypeConverter.cs b/ControlApp/Helpers/EnumDescriptionTypeConverter.cs
new file mode 100644
index 00000000..91ae4a1d
--- /dev/null
+++ b/ControlApp/Helpers/EnumDescriptionTypeConverter.cs
@@ -0,0 +1,37 @@
+using System.ComponentModel;
+using System.Globalization;
+
+namespace Nefarius.DsHidMini.ControlApp.Helpers
+{
+ public class EnumDescriptionTypeConverter : EnumConverter
+ {
+ public EnumDescriptionTypeConverter(Type type)
+ : base(type)
+ {
+ }
+
+ public override object ConvertTo(ITypeDescriptorContext context, CultureInfo culture, object value,
+ Type destinationType)
+ {
+ if (destinationType == typeof(string))
+ {
+ if (value != null)
+ {
+ var fi = value.GetType().GetField(value.ToString());
+ if (fi != null)
+ {
+ var attributes =
+ (DescriptionAttribute[])fi.GetCustomAttributes(typeof(DescriptionAttribute), false);
+ return attributes.Length > 0 && !string.IsNullOrEmpty(attributes[0].Description)
+ ? attributes[0].Description
+ : value.ToString();
+ }
+ }
+
+ return string.Empty;
+ }
+
+ return base.ConvertTo(context, culture, value, destinationType);
+ }
+ }
+}
\ No newline at end of file
diff --git a/ControlApp/Helpers/EnumToBooleanConverter.cs b/ControlApp/Helpers/EnumToBooleanConverter.cs
new file mode 100644
index 00000000..b2e73150
--- /dev/null
+++ b/ControlApp/Helpers/EnumToBooleanConverter.cs
@@ -0,0 +1,40 @@
+// This Source Code Form is subject to the terms of the MIT License.
+// If a copy of the MIT was not distributed with this file, You can obtain one at https://opensource.org/licenses/MIT.
+// Copyright (C) Leszek Pomianowski and WPF UI Contributors.
+// All Rights Reserved.
+
+using System.Globalization;
+using System.Windows.Data;
+
+namespace Nefarius.DsHidMini.ControlApp.Helpers
+{
+ internal class EnumToBooleanConverter : IValueConverter
+ {
+ public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
+ {
+ if (parameter is not String enumString)
+ {
+ throw new ArgumentException("ExceptionEnumToBooleanConverterParameterMustBeAnEnumName");
+ }
+
+ if (!Enum.IsDefined(typeof(Wpf.Ui.Appearance.ApplicationTheme), value))
+ {
+ throw new ArgumentException("ExceptionEnumToBooleanConverterValueMustBeAnEnum");
+ }
+
+ var enumValue = Enum.Parse(typeof(Wpf.Ui.Appearance.ApplicationTheme), enumString);
+
+ return enumValue.Equals(value);
+ }
+
+ public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
+ {
+ if (parameter is not String enumString)
+ {
+ throw new ArgumentException("ExceptionEnumToBooleanConverterParameterMustBeAnEnumName");
+ }
+
+ return Enum.Parse(typeof(Wpf.Ui.Appearance.ApplicationTheme), enumString);
+ }
+ }
+}
diff --git a/ControlApp/Helpers/LocalizedDescriptionAtribute.cs b/ControlApp/Helpers/LocalizedDescriptionAtribute.cs
new file mode 100644
index 00000000..b7a3ef69
--- /dev/null
+++ b/ControlApp/Helpers/LocalizedDescriptionAtribute.cs
@@ -0,0 +1,30 @@
+using System;
+using System.Collections.Generic;
+using System.ComponentModel;
+using System.Linq;
+using System.Resources;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace Nefarius.DsHidMini.ControlApp.Helpers
+{
+ public class LocalizedDescriptionAttribute : DescriptionAttribute
+ {
+ ResourceManager _resourceManager;
+ string _resourceKey;
+ public LocalizedDescriptionAttribute(string resourceKey, Type resourceType)
+ {
+ _resourceManager = new ResourceManager(resourceType);
+ _resourceKey = resourceKey;
+ }
+
+ public override string Description
+ {
+ get
+ {
+ string description = _resourceManager.GetString(_resourceKey);
+ return string.IsNullOrWhiteSpace(description) ? string.Format("[[{0}]]", _resourceKey) : description;
+ }
+ }
+ }
+}
diff --git a/ControlApp/Helpers/SettingsGroupsDataTemplateSelector.cs b/ControlApp/Helpers/SettingsGroupsDataTemplateSelector.cs
new file mode 100644
index 00000000..3c985ef4
--- /dev/null
+++ b/ControlApp/Helpers/SettingsGroupsDataTemplateSelector.cs
@@ -0,0 +1,34 @@
+using System.Windows.Controls;
+using Nefarius.DsHidMini.ControlApp.ViewModels.UserControls.DeviceSettings;
+
+namespace Nefarius.DsHidMini.ControlApp.Helpers
+{
+ internal class SettingsGroupsDataTemplateSelector : DataTemplateSelector
+ {
+ public override DataTemplate
+ SelectTemplate(object item, DependencyObject container)
+ {
+ if(item != null)
+ {
+ if (GroupSettingsTemplateIndex.TryGetValue(item.GetType(), out string templateKey))
+ {
+ return Application.Current.TryFindResource(templateKey) as DataTemplate;
+ }
+ }
+ return null;
+ }
+
+ public Dictionary GroupSettingsTemplateIndex = new Dictionary()
+ {
+ { typeof(HidModeSettingsViewModel), "Template_Unique_All"},
+ { typeof(LedsSettingsViewModel), "Template_LEDsSettings"},
+ { typeof(WirelessSettingsViewModel), "Template_WirelessSettings"},
+ { typeof(SticksSettingsViewModel), "Template_SticksDeadZone"},
+ { typeof(GeneralRumbleSettingsViewModel), "Template_RumbleBasicFunctions"},
+ { typeof(OutputReportSettingsViewModel), "Template_OutputRateControl"},
+ { typeof(LeftMotorRescalingSettingsViewModel), "Template_RumbleHeavyStrRescale"},
+ { typeof(AltRumbleModeSettingsViewModel), "Template_RumbleVariableLightEmuTuning"},
+
+ };
+ }
+}
diff --git a/ControlApp/Helpers/VisibilityPerHidModeConverter.cs b/ControlApp/Helpers/VisibilityPerHidModeConverter.cs
new file mode 100644
index 00000000..ce3cf3ba
--- /dev/null
+++ b/ControlApp/Helpers/VisibilityPerHidModeConverter.cs
@@ -0,0 +1,52 @@
+using System.Globalization;
+using System.Windows.Data;
+using Nefarius.DsHidMini.ControlApp.Models.DshmConfigManager.Enums;
+
+namespace Nefarius.DsHidMini.ControlApp.Helpers
+{
+ public class VisibilityPerHidModeConverter : IValueConverter
+ {
+
+ public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
+ {
+
+ int VisibilityPerHIDModeFlags = System.Convert.ToInt32((string)parameter, 2);
+ SettingsContext context = (SettingsContext)value;
+ int amountToBitShift = 0;
+
+ switch (context)
+ {
+ case SettingsContext.SDF:
+ amountToBitShift = 0;
+ break;
+ case SettingsContext.GPJ:
+ amountToBitShift = 1;
+ break;
+ case SettingsContext.SXS:
+ amountToBitShift = 2;
+ break;
+ case SettingsContext.DS4W:
+ amountToBitShift = 3;
+ break;
+ case SettingsContext.XInput:
+ amountToBitShift = 4;
+ break;
+ case SettingsContext.General:
+ amountToBitShift = 5;
+ break;
+ case SettingsContext.Global:
+ amountToBitShift = 6;
+ break;
+ default:
+ return false;
+ }
+
+ return (((VisibilityPerHIDModeFlags >> amountToBitShift) & 1U) == 1) ? Visibility.Visible : Visibility.Collapsed;
+ }
+
+ public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
+ {
+ throw new NotImplementedException();
+ }
+ }
+}
\ No newline at end of file
diff --git a/ControlApp/Models/AppConfig.cs b/ControlApp/Models/AppConfig.cs
new file mode 100644
index 00000000..73c91405
--- /dev/null
+++ b/ControlApp/Models/AppConfig.cs
@@ -0,0 +1,14 @@
+// This Source Code Form is subject to the terms of the MIT License.
+// If a copy of the MIT was not distributed with this file, You can obtain one at https://opensource.org/licenses/MIT.
+// Copyright (C) Leszek Pomianowski and WPF UI Contributors.
+// All Rights Reserved.
+
+namespace Nefarius.DsHidMini.ControlApp.Models
+{
+ public class AppConfig
+ {
+ public string ConfigurationsFolder { get; set; }
+
+ public string AppPropertiesFileName { get; set; }
+ }
+}
diff --git a/ControlApp/Models/ApplicationConfiguration.cs b/ControlApp/Models/ApplicationConfiguration.cs
new file mode 100644
index 00000000..802107c2
--- /dev/null
+++ b/ControlApp/Models/ApplicationConfiguration.cs
@@ -0,0 +1,62 @@
+namespace Nefarius.DsHidMini.ControlApp.Models
+{
+ ///
+ /// Global settings of the UI tool (stored in %AppData%).
+ ///
+ public class ApplicationConfiguration
+ {
+ ///
+ /// Implicitly loads configuration from file.
+ ///
+ private static readonly Lazy AppConfigLazy =
+ new Lazy(() => JsonApplicationConfiguration
+ .Load(
+ GlobalConfigFileName,
+ true,
+ false));
+
+ ///
+ /// JSON (and schema) file name holding global configuration values.
+ ///
+ public static string GlobalConfigFileName => "ControlApp";
+
+ ///
+ /// True if a log file should be generated, false otherwise.
+ ///
+ public bool IsLoggingEnabled { get; set; } = false;
+
+ ///
+ /// True if check for new version happens on startup, false otherwise.
+ ///
+ public bool IsUpdateCheckEnabled { get; set; } = true;
+
+ ///
+ /// If true, downloads genuine OUI list and compares controller MAC against.
+ ///
+ public bool IsGenuineCheckEnabled { get; set; } = true;
+
+ ///
+ /// Whether user has acknowledged the donation dialog.
+ ///
+ public bool HasAcknowledgedDonationDialog { get; set; } = false;
+
+ ///
+ /// Singleton instance of app configuration.
+ ///
+ public static ApplicationConfiguration Instance => AppConfigLazy.Value;
+
+ ///
+ /// Write changes to file.
+ ///
+ public void Save()
+ {
+ //
+ // Store (modified) configuration to disk
+ //
+ JsonApplicationConfiguration.Save(
+ GlobalConfigFileName,
+ this,
+ false);
+ }
+ }
+}
\ No newline at end of file
diff --git a/ControlApp/Models/Drivers/BthPS3FilterDriver.cs b/ControlApp/Models/Drivers/BthPS3FilterDriver.cs
new file mode 100644
index 00000000..9f757ebf
--- /dev/null
+++ b/ControlApp/Models/Drivers/BthPS3FilterDriver.cs
@@ -0,0 +1,172 @@
+using System.Runtime.InteropServices;
+using Windows.Win32;
+using Windows.Win32.Foundation;
+using Windows.Win32.Storage.FileSystem;
+using Nefarius.DsHidMini.ControlApp.Models.Util;
+
+namespace Nefarius.DsHidMini.ControlApp.Models.Drivers
+{
+ public static class BthPS3FilterDriver
+ {
+ private const uint IOCTL_BTHPS3PSM_ENABLE_PSM_PATCHING = 0x002AAC04;
+ private const uint IOCTL_BTHPS3PSM_DISABLE_PSM_PATCHING = 0x002AAC08;
+ private const uint IOCTL_BTHPS3PSM_GET_PSM_PATCHING = 0x002A6C0C;
+
+ private static readonly string BTHPS3PSM_CONTROL_DEVICE_PATH = "\\\\.\\BthPS3PSMControl";
+
+ private static string ErrorMessage =>
+ "BthPS3 filter driver access failed. Is Bluetooth turned on? Are the drivers installed?";
+
+ ///
+ /// True if filter driver is currently loaded and operational, false otherwise.
+ ///
+ public static bool IsFilterAvailable
+ {
+ get
+ {
+ if (!BluetoothHelper.IsBluetoothRadioAvailable)
+ return false;
+
+ using var handle = PInvoke.CreateFile(
+ BTHPS3PSM_CONTROL_DEVICE_PATH,
+ FILE_ACCESS_FLAGS.FILE_GENERIC_READ | FILE_ACCESS_FLAGS.FILE_GENERIC_WRITE,
+ FILE_SHARE_MODE.FILE_SHARE_READ | FILE_SHARE_MODE.FILE_SHARE_WRITE,
+ null,
+ FILE_CREATION_DISPOSITION.OPEN_EXISTING,
+ FILE_FLAGS_AND_ATTRIBUTES.FILE_ATTRIBUTE_NORMAL, null
+ );
+
+ var error = (WIN32_ERROR)Marshal.GetLastWin32Error();
+
+ return error is WIN32_ERROR.ERROR_SUCCESS or WIN32_ERROR.ERROR_ACCESS_DENIED;
+ }
+ }
+
+ ///
+ /// Gets or sets current filter patching state.
+ ///
+ public static unsafe bool IsFilterEnabled
+ {
+ get
+ {
+ if (!BluetoothHelper.IsBluetoothRadioAvailable)
+ return false;
+
+ using var handle = PInvoke.CreateFile(
+ BTHPS3PSM_CONTROL_DEVICE_PATH,
+ FILE_ACCESS_FLAGS.FILE_GENERIC_READ | FILE_ACCESS_FLAGS.FILE_GENERIC_WRITE,
+ FILE_SHARE_MODE.FILE_SHARE_READ | FILE_SHARE_MODE.FILE_SHARE_WRITE,
+ null,
+ FILE_CREATION_DISPOSITION.OPEN_EXISTING,
+ FILE_FLAGS_AND_ATTRIBUTES.FILE_ATTRIBUTE_NORMAL, null
+ );
+
+ if (handle.IsInvalid)
+ throw new Exception(ErrorMessage);
+
+ var payloadBuffer = Marshal.AllocHGlobal(Marshal.SizeOf());
+ var payload = new BTHPS3PSM_GET_PSM_PATCHING { DeviceIndex = 0 };
+
+ try
+ {
+ Marshal.StructureToPtr(payload, payloadBuffer, false);
+
+ PInvoke.DeviceIoControl(
+ handle,
+ IOCTL_BTHPS3PSM_GET_PSM_PATCHING,
+ payloadBuffer.ToPointer(),
+ (uint)Marshal.SizeOf(),
+ payloadBuffer.ToPointer(),
+ (uint)Marshal.SizeOf(),
+ null,
+ null
+ );
+
+ payload = Marshal.PtrToStructure(payloadBuffer);
+ }
+ finally
+ {
+ Marshal.FreeHGlobal(payloadBuffer);
+ }
+
+ return payload.IsEnabled > 0;
+ }
+ set
+ {
+ using var handle = PInvoke.CreateFile(
+ BTHPS3PSM_CONTROL_DEVICE_PATH,
+ FILE_ACCESS_FLAGS.FILE_GENERIC_READ | FILE_ACCESS_FLAGS.FILE_GENERIC_WRITE,
+ FILE_SHARE_MODE.FILE_SHARE_READ | FILE_SHARE_MODE.FILE_SHARE_WRITE,
+ null,
+ FILE_CREATION_DISPOSITION.OPEN_EXISTING,
+ FILE_FLAGS_AND_ATTRIBUTES.FILE_ATTRIBUTE_NORMAL, null
+ );
+
+ if (handle.IsInvalid)
+ throw new Exception(ErrorMessage);
+
+ var payloadEnableBuffer = Marshal.AllocHGlobal(Marshal.SizeOf());
+ var payloadEnable = new BTHPS3PSM_ENABLE_PSM_PATCHING { DeviceIndex = 0 };
+ var payloadDisableBuffer = Marshal.AllocHGlobal(Marshal.SizeOf());
+ var payloadDisable = new BTHPS3PSM_DISABLE_PSM_PATCHING { DeviceIndex = 0 };
+
+ try
+ {
+ Marshal.StructureToPtr(payloadEnable, payloadEnableBuffer, false);
+ Marshal.StructureToPtr(payloadDisable, payloadDisableBuffer, false);
+
+ if (value)
+ PInvoke.DeviceIoControl(
+ handle,
+ IOCTL_BTHPS3PSM_ENABLE_PSM_PATCHING,
+ payloadEnableBuffer.ToPointer(),
+ (uint)Marshal.SizeOf(),
+ null,
+ 0,
+ null,
+ null
+ );
+ else
+ PInvoke.DeviceIoControl(
+ handle,
+ IOCTL_BTHPS3PSM_DISABLE_PSM_PATCHING,
+ payloadDisableBuffer.ToPointer(),
+ (uint)Marshal.SizeOf(),
+ null,
+ 0,
+ null,
+ null
+ );
+ }
+ finally
+ {
+ Marshal.FreeHGlobal(payloadEnableBuffer);
+ Marshal.FreeHGlobal(payloadDisableBuffer);
+ }
+ }
+ }
+
+ [StructLayout(LayoutKind.Sequential, Pack = 1)]
+ private struct BTHPS3PSM_ENABLE_PSM_PATCHING
+ {
+ public uint DeviceIndex;
+ }
+
+ [StructLayout(LayoutKind.Sequential, Pack = 1)]
+ private struct BTHPS3PSM_DISABLE_PSM_PATCHING
+ {
+ public uint DeviceIndex;
+ }
+
+ [StructLayout(LayoutKind.Sequential, Pack = 1, CharSet = CharSet.Unicode)]
+ private struct BTHPS3PSM_GET_PSM_PATCHING
+ {
+ public uint DeviceIndex;
+
+ public readonly uint IsEnabled;
+
+ [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 0xC8)]
+ public readonly string SymbolicLinkName;
+ }
+ }
+}
\ No newline at end of file
diff --git a/ControlApp/Models/Drivers/BthPS3ProfileDriver.cs b/ControlApp/Models/Drivers/BthPS3ProfileDriver.cs
new file mode 100644
index 00000000..8ad2ca71
--- /dev/null
+++ b/ControlApp/Models/Drivers/BthPS3ProfileDriver.cs
@@ -0,0 +1,32 @@
+using Microsoft.Win32;
+
+namespace Nefarius.DsHidMini.ControlApp.Models.Drivers
+{
+ public static class BthPS3ProfileDriver
+ {
+ private static string ParametersPath => "SYSTEM\\CurrentControlSet\\Services\\BthPS3\\Parameters";
+
+ ///
+ /// Gets or sets the RawPDO setting.
+ ///
+ public static bool RawPDO
+ {
+ get
+ {
+ using (var key = Registry.LocalMachine.OpenSubKey(ParametersPath))
+ {
+ if (int.TryParse(key?.GetValue("RawPDO").ToString(), out var result)) return result > 0;
+
+ return false;
+ }
+ }
+ set
+ {
+ using (var key = Registry.LocalMachine.OpenSubKey(ParametersPath, true))
+ {
+ key?.SetValue("RawPDO", value ? 1 : 0, RegistryValueKind.DWord);
+ }
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/ControlApp/Models/Drivers/DsHidMiniDriver.cs b/ControlApp/Models/Drivers/DsHidMiniDriver.cs
new file mode 100644
index 00000000..32ecd024
--- /dev/null
+++ b/ControlApp/Models/Drivers/DsHidMiniDriver.cs
@@ -0,0 +1,85 @@
+using System.ComponentModel;
+using Nefarius.DsHidMini.ControlApp.Helpers;
+using Nefarius.DsHidMini.ControlApp.Models.DshmConfigManager.Enums;
+using Nefarius.DsHidMini.ControlApp.Resources;
+using Nefarius.Utilities.DeviceManagement.PnP;
+
+namespace Nefarius.DsHidMini.ControlApp.Models.Drivers
+{
+ public static class DsHidMiniDriver
+ {
+ ///
+ /// Interface GUID common to all devices the DsHidMini driver supports.
+ ///
+ public static Guid DeviceInterfaceGuid => Guid.Parse("{399ED672-E0BD-4FB3-AB0C-4955B56FB86A}");
+
+ #region Read-only properties
+
+ ///
+ /// Unified Device Property exposing current battery status.
+ ///
+ public static DevicePropertyKey BatteryStatusProperty => CustomDeviceProperty.CreateCustomDeviceProperty(
+ Guid.Parse("{3FECF510-CC94-4FBE-8839-738201F84D59}"), 2,
+ typeof(byte));
+
+ public static DevicePropertyKey LastPairingStatusProperty => CustomDeviceProperty.CreateCustomDeviceProperty(
+ Guid.Parse("{3FECF510-CC94-4FBE-8839-738201F84D59}"), 3,
+ typeof(int));
+
+ public static DevicePropertyKey LastHostRequestStatusProperty => CustomDeviceProperty.CreateCustomDeviceProperty(
+ Guid.Parse("{3FECF510-CC94-4FBE-8839-738201F84D59}"), 5,
+ typeof(int));
+
+ #endregion
+
+ #region Common device properties
+
+ public static DevicePropertyKey HidDeviceModeProperty => CustomDeviceProperty.CreateCustomDeviceProperty(
+ Guid.Parse("{6D293077-C3D6-4062-9597-BE4389404C02}"), 2,
+ typeof(byte));
+
+ public static DevicePropertyKey HostAddressProperty => CustomDeviceProperty.CreateCustomDeviceProperty(
+ Guid.Parse("{0xa92f26ca, 0xeda7, 0x4b1d, {0x9d, 0xb2, 0x27, 0xb6, 0x8a, 0xa5, 0xa2, 0xeb}}"), 1,
+ typeof(ulong));
+
+ public static DevicePropertyKey DeviceAddressProperty => CustomDeviceProperty.CreateCustomDeviceProperty(
+ Guid.Parse("{0x2bd67d8b, 0x8beb, 0x48d5, {0x87, 0xe0, 0x6c, 0xda, 0x34, 0x28, 0x04, 0x0a}}"), 1,
+ typeof(string));
+
+ public static DevicePropertyKey BluetoothLastConnectedTimeProperty =>
+ CustomDeviceProperty.CreateCustomDeviceProperty(
+ Guid.Parse("{0x2bd67d8b, 0x8beb, 0x48d5, {0x87, 0xe0, 0x6c, 0xda, 0x34, 0x28, 0x04, 0x0a}}"), 11,
+ typeof(DateTimeOffset));
+
+ #endregion
+ }
+
+ ///
+ /// Battery status values.
+ ///
+ [TypeConverter(typeof(EnumDescriptionTypeConverter))]
+ public enum DsBatteryStatus : byte
+ {
+ [Description("Unknown")] Unknown = 0x00,
+ [Description("Dying")] Dying = 0x01,
+ [Description("Low")] Low = 0x02,
+ [Description("Medium")] Medium = 0x03,
+ [Description("High")] High = 0x04,
+ [Description("Full")] Full = 0x05,
+ [Description("Charging")] Charging = 0xEE,
+ [Description("Charged")] Charged = 0xEF
+ }
+
+ ///
+ /// HID device emulation modes.
+ ///
+ [TypeConverter(typeof(EnumDescriptionTypeConverter))]
+ public enum DsHidDeviceMode : byte
+ {
+ [Description("SDF (PCSX2)")] SDF = 0x01,
+ [Description("GPJ (Separated pressure)")] GPJ = 0x02,
+ [Description("SXS (Steam, RPCS3)")] SXS = 0x03,
+ [Description("DS4Windows")] DS4W = 0x04,
+ [Description("XInput")] XInput = 0x05
+ }
+}
\ No newline at end of file
diff --git a/ControlApp/Models/DshmConfigManager/DeviceData.cs b/ControlApp/Models/DshmConfigManager/DeviceData.cs
new file mode 100644
index 00000000..c9229218
--- /dev/null
+++ b/ControlApp/Models/DshmConfigManager/DeviceData.cs
@@ -0,0 +1,30 @@
+using Nefarius.DsHidMini.ControlApp.Models.DshmConfigManager.Enums;
+using Newtonsoft.Json;
+
+namespace Nefarius.DsHidMini.ControlApp.Models.DshmConfigManager
+{
+ public class DeviceData
+ {
+ public string DeviceMac { get; set; } = "0000000000";
+ public string CustomName { get; set; } = "DualShock 3";
+ public Guid GuidOfProfileToUse { get; set; } = ProfileData.DefaultGuid;
+ public BluetoothPairingMode BluetoothPairingMode { get; set; } = BluetoothPairingMode.Auto;
+ public string? PairingAddress { get; set; } = "";
+
+ [JsonIgnore] // PairOnHotReload should only be enabled temporarely to prevent pairing requests from being repeteadly sent on hot-reload
+ public bool PairOnHotReload { get; set; } = false;
+ public SettingsModes SettingsMode { get; set; } = SettingsModes.Global;
+
+ ///
+ /// Settings used when Device is in Custom Mode
+ ///
+ public DeviceSettings Settings { get; set; } = new();
+
+
+
+ public DeviceData(string deviceMac)
+ {
+ DeviceMac = deviceMac;
+ }
+ }
+}
\ No newline at end of file
diff --git a/ControlApp/Models/DshmConfigManager/DeviceSettings.cs b/ControlApp/Models/DshmConfigManager/DeviceSettings.cs
new file mode 100644
index 00000000..80bf6e24
--- /dev/null
+++ b/ControlApp/Models/DshmConfigManager/DeviceSettings.cs
@@ -0,0 +1,373 @@
+using System.Text.Json.Serialization;
+using Nefarius.DsHidMini.ControlApp.Models.DshmConfigManager.DshmConfig;
+using Nefarius.DsHidMini.ControlApp.Models.DshmConfigManager.DshmConfig.Enums;
+using Nefarius.DsHidMini.ControlApp.Models.DshmConfigManager.Enums;
+using Button = Nefarius.DsHidMini.ControlApp.Models.DshmConfigManager.Enums.Button;
+using LEDsMode = Nefarius.DsHidMini.ControlApp.Models.DshmConfigManager.Enums.LEDsMode;
+using PressureMode = Nefarius.DsHidMini.ControlApp.Models.DshmConfigManager.Enums.PressureMode;
+
+namespace Nefarius.DsHidMini.ControlApp.Models.DshmConfigManager
+{
+ public class ButtonsCombo
+ {
+ private int holdTime;
+
+ public bool IsEnabled { get; set; }
+ public int HoldTime
+ {
+ get => holdTime;
+ set => holdTime = (value >= 0) ? value : 0;
+ }
+
+ public Button[] ButtonCombo { get; init; } = new Button[3];
+
+ public ButtonsCombo() { }
+
+ public ButtonsCombo(ButtonsCombo comboToCopy)
+ {
+ copyCombo(comboToCopy);
+ }
+
+ public void copyCombo(ButtonsCombo comboToCopy)
+ {
+ IsEnabled = comboToCopy.IsEnabled;
+ HoldTime = comboToCopy.HoldTime;
+ for(int i = 0; i < ButtonCombo.Length; i++)
+ {
+ ButtonCombo[i] = comboToCopy.ButtonCombo[i];
+ }
+ }
+
+ }
+
+ public class DeviceSettings
+ {
+
+ public HidModeSettings HidMode { get; set; } = new();
+ public LedsSettings LEDs { get; set; } = new();
+ public WirelessSettings Wireless { get; set; } = new();
+ public SticksSettings Sticks { get; set; } = new();
+ public GeneralRumbleSettings GeneralRumble { get; set; } = new();
+ public OutputReportSettings OutputReport { get; set; } = new();
+ public LeftMotorRescalingSettings LeftMotorRescaling { get; set; } = new();
+ public AltRumbleModeSettings AltRumbleAdjusts { get; set; } = new();
+
+ public DeviceSettings()
+ {
+ this.ResetToDefault();
+ }
+
+ public void ResetToDefault()
+ {
+ HidMode.ResetToDefault();
+ LEDs.ResetToDefault();
+ Wireless.ResetToDefault();
+ Sticks.ResetToDefault();
+ GeneralRumble.ResetToDefault();
+ OutputReport.ResetToDefault();
+ LeftMotorRescaling.ResetToDefault();
+ AltRumbleAdjusts.ResetToDefault();
+ }
+
+ public static void CopySettings(DeviceSettings destiny, DeviceSettings source)
+ {
+ HidModeSettings.CopySettings(destiny.HidMode,source.HidMode);
+ LedsSettings.CopySettings(destiny.LEDs, source.LEDs);
+ WirelessSettings.CopySettings(destiny.Wireless, source.Wireless);
+ SticksSettings.CopySettings(destiny.Sticks, source.Sticks);
+ GeneralRumbleSettings.CopySettings(destiny.GeneralRumble, source.GeneralRumble);
+ OutputReportSettings.CopySettings(destiny.OutputReport, source.OutputReport);
+ LeftMotorRescalingSettings.CopySettings(destiny.LeftMotorRescaling, source.LeftMotorRescaling);
+ AltRumbleModeSettings.CopySettings(destiny.AltRumbleAdjusts, source.AltRumbleAdjusts);
+ }
+ }
+
+
+ public abstract class DeviceSubSettings
+ {
+ public abstract void ResetToDefault();
+ }
+
+ public class HidModeSettings : DeviceSubSettings
+ {
+ public SettingsContext SettingsContext { get; set; } = SettingsContext.XInput;
+ public PressureMode PressureExposureMode { get; set; } = PressureMode.Default;
+ public DPadMode DPadExposureMode { get; set; } = DPadMode.HAT;
+ public bool IsLEDsAsXInputSlotEnabled { get; set; } = false;
+ public bool PreventRemappingConflictsInSXSMode { get; set; } = false;
+ public bool PreventRemappingConflictsInDS4WMode { get; set; } = false;
+ public bool AllowAppsToOverrideLEDsInSXSMode { get; set; } = false;
+
+ public override void ResetToDefault()
+ {
+ CopySettings(this, new());
+ }
+
+ public static void CopySettings(HidModeSettings destiny, HidModeSettings source)
+ {
+ destiny.SettingsContext = source.SettingsContext;
+ destiny.PressureExposureMode = source.PressureExposureMode;
+ destiny.DPadExposureMode = source.DPadExposureMode;
+ destiny.IsLEDsAsXInputSlotEnabled = source.IsLEDsAsXInputSlotEnabled;
+ destiny.PreventRemappingConflictsInDS4WMode = source.PreventRemappingConflictsInDS4WMode;
+ destiny.PreventRemappingConflictsInSXSMode = source.PreventRemappingConflictsInSXSMode;
+ destiny.AllowAppsToOverrideLEDsInSXSMode = source.AllowAppsToOverrideLEDsInSXSMode;
+ }
+ }
+
+ public class LedsSettings : DeviceSubSettings
+ {
+ public LEDsMode LeDMode { get; set; } = LEDsMode.BatteryIndicatorPlayerIndex;
+ public bool AllowExternalLedsControl { get; set; } = false;
+ public All4LEDsCustoms LEDsCustoms { get; set; } = new();
+
+
+ public override void ResetToDefault()
+ {
+ CopySettings(this, new());
+ }
+
+ public static void CopySettings(LedsSettings destiny, LedsSettings source)
+ {
+ destiny.LeDMode = source.LeDMode;
+ destiny.AllowExternalLedsControl = source.AllowExternalLedsControl;
+ destiny.LEDsCustoms.CopyLEDsCustoms(source.LEDsCustoms);
+ }
+
+ public class All4LEDsCustoms
+ {
+ public singleLEDCustoms[] LED_x_Customs = new singleLEDCustoms[4];
+ public All4LEDsCustoms()
+ {
+ for (int i = 0; i < LED_x_Customs.Length; i++)
+ {
+ LED_x_Customs[i] = new(i);
+ }
+ }
+
+ public void CopyLEDsCustoms(All4LEDsCustoms customsToCopy)
+ {
+ for (int i = 0; i < LED_x_Customs.Length; i++)
+ {
+ LED_x_Customs[i].CopyCustoms(customsToCopy.LED_x_Customs[i]);
+ }
+ }
+
+ public void ResetLEDsCustoms()
+ {
+ for (int i = 0; i < LED_x_Customs.Length; i++)
+ {
+ LED_x_Customs[i].Reset();
+ }
+ }
+
+
+
+ public class singleLEDCustoms
+ {
+ [JsonIgnore(Condition = JsonIgnoreCondition.Always)]
+ public int LEDIndex { get; }
+ public bool IsLedEnabled { get; set; } = false;
+ public bool UseLEDEffects { get; set; } = false;
+
+ public byte Duration { get; set; } = 0x00;
+ public int CycleDuration { get; set; } = 0x4000;
+ public byte OnPeriodCycles { get; set; } = 0xFF;
+ public byte OffPeriodCycles { get; set; } = 0xFF;
+ public singleLEDCustoms(int ledIndex)
+ {
+ this.LEDIndex = ledIndex;
+ IsLedEnabled = LEDIndex == 0 ? true : false;
+ }
+
+ internal void Reset()
+ {
+ CopyCustoms(new(LEDIndex));
+ }
+
+ public void CopyCustoms(singleLEDCustoms copySource)
+ {
+ this.IsLedEnabled = copySource.IsLedEnabled;
+ this.UseLEDEffects = copySource.UseLEDEffects;
+ this.Duration = copySource.Duration;
+ this.CycleDuration = copySource.CycleDuration;
+ this.OnPeriodCycles = copySource.OnPeriodCycles;
+ this.OffPeriodCycles = copySource.OffPeriodCycles;
+ }
+ }
+ }
+
+ }
+
+ public class WirelessSettings : DeviceSubSettings
+ {
+ public bool IsWirelessIdleDisconnectEnabled { get; set; } = true;
+ public int WirelessIdleDisconnectTime { get; set; } = 300000; // 5 minutes
+ public ButtonsCombo QuickDisconnectCombo { get; set; } = new()
+ {
+ IsEnabled = true,
+ HoldTime = 1000,
+ ButtonCombo = new[] {Button.PS, Button.R1, Button.L1},
+ };
+
+ public override void ResetToDefault()
+ {
+ CopySettings(this,new());
+ }
+
+ public static void CopySettings(WirelessSettings destiny, WirelessSettings source)
+ {
+ destiny.IsWirelessIdleDisconnectEnabled = source.IsWirelessIdleDisconnectEnabled;
+ destiny.WirelessIdleDisconnectTime = source.WirelessIdleDisconnectTime;
+ destiny.QuickDisconnectCombo.copyCombo(source.QuickDisconnectCombo);
+ }
+ }
+
+ public class SticksSettings : DeviceSubSettings
+ {
+ public StickData LeftStickData { get; set; } = new();
+ public StickData RightStickData { get; set; } = new();
+
+ public override void ResetToDefault()
+ {
+ LeftStickData.Reset();
+ RightStickData.Reset();
+ }
+
+ public static void CopySettings(SticksSettings destiny, SticksSettings source)
+ {
+ destiny.LeftStickData.CopyStickDataFromOtherStick(source.LeftStickData);
+ destiny.RightStickData.CopyStickDataFromOtherStick(source.RightStickData);
+ }
+
+ public class StickData
+ {
+ public bool IsDeadZoneEnabled { get; set; } = true;
+ public int DeadZone { get; set; } = 0;
+
+ public bool InvertXAxis { get; set; } = false;
+ public bool InvertYAxis { get; set; } = false;
+
+ public StickData()
+ {
+
+ }
+
+ public void Reset()
+ {
+ CopyStickDataFromOtherStick(new());
+ }
+
+ public void CopyStickDataFromOtherStick(StickData copySource)
+ {
+ this.IsDeadZoneEnabled = copySource.IsDeadZoneEnabled;
+ this.DeadZone = copySource.DeadZone;
+ this.InvertXAxis = copySource.InvertXAxis;
+ this.InvertYAxis = copySource.InvertYAxis;
+ }
+
+ }
+
+ }
+
+ public class GeneralRumbleSettings : DeviceSubSettings
+ {
+
+ // -------------------------------------------- DEFAULT SETTINGS END
+
+ public bool IsLeftMotorDisabled { get; set; } = false;
+ public bool IsRightMotorDisabled { get; set; } = false;
+
+ public bool IsAltRumbleModeEnabled { get; set; } = false;
+ public bool AlwaysStartInNormalMode { get; set; } = false;
+ public bool IsAltModeToggleButtonComboEnabled { get; set; } = false;
+ public ButtonsCombo AltModeToggleButtonCombo { get; set; } = new()
+ {
+ IsEnabled = false,
+ HoldTime = 1000,
+ ButtonCombo = new[] {Button.PS, Button.Select, Button.Select},
+ };
+
+ public override void ResetToDefault()
+ {
+ CopySettings(this, new());
+ }
+
+ public static void CopySettings(GeneralRumbleSettings destiny, GeneralRumbleSettings source)
+ {
+ destiny.IsAltRumbleModeEnabled = source.IsAltRumbleModeEnabled;
+ destiny.IsLeftMotorDisabled = source.IsLeftMotorDisabled;
+ destiny.IsRightMotorDisabled = source.IsRightMotorDisabled;
+ destiny.AlwaysStartInNormalMode = source.IsAltModeToggleButtonComboEnabled;
+ destiny.AltModeToggleButtonCombo.copyCombo(source.AltModeToggleButtonCombo);
+ }
+ }
+
+ public class OutputReportSettings : DeviceSubSettings
+ {
+ public bool IsOutputReportRateControlEnabled { get; set; } = true;
+ public int MaxOutputRate { get; set; } = 150;
+ public bool IsOutputReportDeduplicatorEnabled { get; set; } = false;
+
+ public override void ResetToDefault()
+ {
+ CopySettings(this, new());
+ }
+
+ public static void CopySettings(OutputReportSettings destiny, OutputReportSettings source)
+ {
+ destiny.IsOutputReportDeduplicatorEnabled = source.IsOutputReportDeduplicatorEnabled;
+ destiny.IsOutputReportRateControlEnabled = source.IsOutputReportRateControlEnabled;
+ destiny.MaxOutputRate = source.MaxOutputRate;
+ }
+ }
+
+ public class LeftMotorRescalingSettings : DeviceSubSettings
+ {
+ public bool IsLeftMotorStrRescalingEnabled { get; set; } = true;
+ public int LeftMotorStrRescalingUpperRange { get; set; } = 255;
+ public int LeftMotorStrRescalingLowerRange { get; set; } = 64;
+
+
+ public override void ResetToDefault()
+ {
+ CopySettings(this, new());
+ }
+
+ public static void CopySettings(LeftMotorRescalingSettings destiny, LeftMotorRescalingSettings source)
+ {
+ destiny.IsLeftMotorStrRescalingEnabled = source.IsLeftMotorStrRescalingEnabled;
+ destiny.LeftMotorStrRescalingLowerRange = source.LeftMotorStrRescalingLowerRange;
+ destiny.LeftMotorStrRescalingUpperRange = source.LeftMotorStrRescalingUpperRange;
+ }
+ }
+
+ public class AltRumbleModeSettings : DeviceSubSettings
+ {
+ public int ForcedRightMotorHeavyThreshold { get; set; } = 230;
+ public int ForcedRightMotorLightThreshold { get; set; } = 230;
+ public bool IsForcedRightMotorHeavyThreasholdEnabled { get; set; } = false;
+ public bool IsForcedRightMotorLightThresholdEnabled { get; set; } = false;
+
+ public int RightRumbleConversionUpperRange { get; set; } = 140;
+ public int RightRumbleConversionLowerRange { get; set; } = 1;
+
+
+ public override void ResetToDefault()
+ {
+ CopySettings(this, new());
+ }
+
+ public static void CopySettings(AltRumbleModeSettings destiny, AltRumbleModeSettings source)
+ {
+ destiny.RightRumbleConversionLowerRange = source.RightRumbleConversionLowerRange;
+ destiny.RightRumbleConversionUpperRange = source.RightRumbleConversionUpperRange;
+ // Right rumble (light) threshold
+ destiny.IsForcedRightMotorLightThresholdEnabled = source.IsForcedRightMotorLightThresholdEnabled;
+ destiny.ForcedRightMotorLightThreshold = source.ForcedRightMotorLightThreshold;
+ // Left rumble (Heavy) threshold
+ destiny.IsForcedRightMotorHeavyThreasholdEnabled = source.IsForcedRightMotorHeavyThreasholdEnabled;
+ destiny.ForcedRightMotorHeavyThreshold = source.ForcedRightMotorHeavyThreshold;
+ }
+ }
+}
diff --git a/ControlApp/Models/DshmConfigManager/DshmConfig/DshmConfig.cs b/ControlApp/Models/DshmConfigManager/DshmConfig/DshmConfig.cs
new file mode 100644
index 00000000..5596a8a2
--- /dev/null
+++ b/ControlApp/Models/DshmConfigManager/DshmConfig/DshmConfig.cs
@@ -0,0 +1,179 @@
+using System.Text.Json;
+using System.Text.Json.Serialization;
+using Nefarius.DsHidMini.ControlApp.Models.DshmConfigManager.DshmConfig.Enums;
+
+using Serilog;
+
+using static Nefarius.DsHidMini.ControlApp.Models.DshmConfigManager.DshmConfig.DshmDeviceSettings;
+
+namespace Nefarius.DsHidMini.ControlApp.Models.DshmConfigManager.DshmConfig
+{
+
+ ///
+ /// DsHidMini driver settings for a specific Device (or global)
+ ///
+ public class DshmDeviceSettings
+ {
+ public HidDeviceMode? HIDDeviceMode { get; set; }// = DSHM_HidDeviceModes.DS4Windows;
+ public bool? DisableAutoPairing { get; set; }
+
+ public DevicePairingMode? DevicePairingMode { get; set; }
+ public bool? PairOnHotReload { get; set; } // = false;
+ public string? CustomPairingAddress { get; set; }
+ public bool? DisableWirelessIdleTimeout { get; set; }// = false;
+ public bool? IsOutputRateControlEnabled { get; set; }// = true;
+ public byte? OutputRateControlPeriodMs { get; set; }// = 150;
+ public bool? IsOutputDeduplicatorEnabled { get; set; }// = false;
+ public double? WirelessIdleTimeoutPeriodMs { get; set; }// = 300000;
+ public bool? IsQuickDisconnectComboEnabled { get; set; } = true;
+ public ButtonCombo QuickDisconnectCombo { get; set; } = new();
+
+
+ [JsonIgnore]
+ public DshmHidModeSettings ContextSettings { get; set; } = new();
+ public DshmHidModeSettings? SDF => HIDDeviceMode == HidDeviceMode.SDF ? ContextSettings : null;
+ public DshmHidModeSettings? GPJ => HIDDeviceMode == HidDeviceMode.GPJ ? ContextSettings : null;
+ public DshmHidModeSettings? SXS => HIDDeviceMode == HidDeviceMode.SXS ? ContextSettings : null;
+ public DshmHidModeSettings? DS4Windows => HIDDeviceMode == HidDeviceMode.DS4Windows ? ContextSettings : null;
+ public DshmHidModeSettings? XInput => HIDDeviceMode == HidDeviceMode.XInput ? ContextSettings : null;
+
+ public DshmDeviceSettings()
+ {
+
+ }
+
+ public class DeadZoneSettings
+ {
+ public bool? Apply
+ {
+ get;
+ set;
+ }
+ public byte? PolarValue { get; set; }// = 10.0;
+
+ }
+
+ public class HeavyRescaleSettings
+ {
+ public bool? IsEnabled { get; set; }// = true;
+ public byte? RescaleMinRange { get; set; }// = 64;
+ public byte? RescaleMaxRange { get; set; }// = 255;
+ }
+
+ public class AlternativeModeSettings
+ {
+ public bool? IsEnabled { get; set; }// = false;
+ public byte? RescaleMinRange { get; set; }// = 1;
+ public byte? RescaleMaxRange { get; set; }// = 160;
+ public ForcedRightAdjusts ForcedRight { get; set; } = new();
+ public ButtonCombo? ToggleCombo { get; set; } = new(); // = DSHM_QuickDisconnectCombo.PS_R1_L1
+ }
+
+ public class ButtonCombo
+ {
+ public bool? IsEnabled { get; set; }
+ public double? HoldTime { get; set; }
+ public int? Button1 { get; set; }
+ public int? Button2 { get; set; }
+ public int? Button3 { get; set; }
+ }
+
+ public class ForcedRightAdjusts
+ {
+ public bool? IsHeavyThresholdEnabled { get; set; }// = false;
+ public byte? HeavyThreshold { get; set; }// = 230;
+ public bool? IsLightThresholdEnabled { get; set; }// = false;
+ public byte? LightThreshold { get; set; }// = 230;
+ }
+
+ public class AllRumbleSettings
+ {
+ public bool? DisableLeft { get; set; }// = false;
+ public bool? DisableRight { get; set; }// = false;
+ public HeavyRescaleSettings HeavyRescale { get; set; } = new();
+ public AlternativeModeSettings AlternativeMode { get; set; } = new();
+ }
+
+ public class SingleLEDCustoms
+ {
+ public byte? TotalDuration { get; set; }// = 255;
+ public ushort? BasePortionDuration { get; set; }// = 255;
+ public byte? OffPortionMultiplier { get; set; }// = 0;
+ public byte? OnPortionMultiplier { get; set; }// = 255;
+ }
+
+ public class AllLEDSettings
+ {
+ public LEDsMode? Mode { get; set; }// = DSHM_LEDsModes.BatteryIndicatorPlayerIndex;
+ public DSHM_LEDsAuthority? Authority { get; set; }
+ public LEDsCustoms CustomPatterns { get; set; } = new();
+ }
+
+ public class LEDsCustoms
+ {
+ public byte? LEDFlags { get; set; } // = 0x2;
+ public SingleLEDCustoms Player1 { get; set; } = new();
+ public SingleLEDCustoms Player2 { get; set; } = new();
+ public SingleLEDCustoms Player3 { get; set; } = new();
+ public SingleLEDCustoms Player4 { get; set; } = new();
+ }
+
+ public class AxesFlipping
+ {
+ public bool? LeftX { get; set; }
+ public bool? LeftY { get; set; }
+ public bool? RightX { get; set; }
+ public bool? RightY { get; set; }
+ }
+ }
+
+ ///
+ /// DsHidMini driver settings related only to a given Hid Device Mode
+ ///
+ public class DshmHidModeSettings
+ {
+ [JsonIgnore]
+ public HidDeviceMode? HIDDeviceMode { get; set; }
+ public PressureMode? PressureExposureMode { get; set; }// = DSHM_PressureModes.Default;
+ public DPadExposureMode? DPadExposureMode { get; set; }// = DSHM_DPadExposureModes.Default;
+ public DeadZoneSettings DeadZoneLeft { get; set; } = new();
+ public DeadZoneSettings DeadZoneRight { get; set; } = new();
+ public AllRumbleSettings RumbleSettings { get; set; } = new();
+ public AllLEDSettings LEDSettings { get; set; } = new();
+ public AxesFlipping FlipAxis { get; set; } = new();
+ }
+
+ ///
+ /// A class representing the DsHidMini configuration disk file
+ ///
+ public class DshmConfiguration
+ {
+ public DshmDeviceSettings Global { get; set; } = new();
+ public List Devices { get; set; } = new();
+
+ ///
+ /// Updates the DsHidMini configuration file on disk accordingly to this object's settings
+ ///
+ /// If the update was successfully
+ public bool ApplyConfiguration()
+ {
+ Log.Logger.Debug("Converting DsHidMini configuration object to configuration file.");
+ return DshmConfigSerialization.UpdateDsHidMiniConfigFile(this);
+
+ }
+ }
+
+ ///
+ /// class representing a DsHidMini specific device data, containing its MAC address and Settings
+ ///
+ public class DshmDeviceData
+ {
+ public string DeviceAddress { get; set; }
+ public DshmDeviceSettings DeviceSettings { get; set; } = new();
+
+ }
+
+
+
+}
+
diff --git a/ControlApp/Models/DshmConfigManager/DshmConfig/DshmConfigSerialization.cs b/ControlApp/Models/DshmConfigManager/DshmConfig/DshmConfigSerialization.cs
new file mode 100644
index 00000000..3ec9d2b5
--- /dev/null
+++ b/ControlApp/Models/DshmConfigManager/DshmConfig/DshmConfigSerialization.cs
@@ -0,0 +1,100 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Text.Json.Serialization;
+using System.Text.Json;
+using System.Threading.Tasks;
+
+using Serilog;
+
+namespace Nefarius.DsHidMini.ControlApp.Models.DshmConfigManager.DshmConfig
+{
+ internal static class DshmConfigSerialization
+ {
+ private const string DISK = @"C:\";
+
+ private const string DSHM_FOLDER_PATH_IN_DISK = @"ProgramData\DsHidMini\";
+ public static string DshmFolderFullPath { get; } = $@"{DISK}{DSHM_FOLDER_PATH_IN_DISK}";
+
+ public static string DshmFileNameWithoutExtension { get; } = $@"DsHidMini";
+
+ public static string DshmConfigFileFormat { get; } = $@".json";
+
+
+
+ ///
+ /// Attempts to update the DsHidMini configuration file on disk by serializing a DshmConfiguration object into the proper dshidmini v3 format
+ ///
+ /// The DshmConfiguration object containing the desired dshidmini v3 settings
+ /// True if the update occurred successfully, false otherwise
+ public static bool UpdateDsHidMiniConfigFile(DshmConfiguration dshmConfig)
+ {
+ Log.Logger.Debug("Starting serialization of DsHidMini config object and saving to disk");
+ try
+ {
+ string dshmSerializedConfiguration = JsonSerializer.Serialize(dshmConfig, DshmConfigSerializerOptions);
+ System.IO.Directory.CreateDirectory(DshmFolderFullPath);
+ System.IO.File.WriteAllText($@"{DshmFolderFullPath}{DshmFileNameWithoutExtension}{DshmConfigFileFormat}", dshmSerializedConfiguration);
+ return true;
+ }
+ catch (Exception e)
+ {
+ Log.Logger.Error(e, "Serialization or saving to disk failed.");
+ return false;
+ }
+ }
+
+ public static JsonSerializerOptions DshmConfigSerializerOptions = new JsonSerializerOptions
+ {
+ WriteIndented = true,
+ DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull,
+ IncludeFields = true,
+
+ Converters =
+ {
+ new JsonStringEnumConverter(),
+ new DshmConfigCustomJsonConverter(),
+ }
+ };
+
+ ///
+ /// A custom converter necessary to serialiaze a DshmConfiguration object into the proper DsHidMini v3 format.
+ /// Removes starting and ending brackets from the Devices list and makes each object in the list have it's object name be their MAC address.
+ ///
+ public class DshmConfigCustomJsonConverter : JsonConverter
+ {
+ public override DshmConfiguration Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
+ {
+ throw new NotImplementedException();
+ }
+
+ public override void Write(
+ Utf8JsonWriter writer, DshmConfiguration instance, JsonSerializerOptions options)
+ {
+ writer.WriteStartObject();
+ writer.WritePropertyName(nameof(instance.Global));
+ var serializedGlobal = JsonSerializer.Serialize(instance.Global, options);
+ writer.WriteRawValue(serializedGlobal);
+
+ //JsonSerializer.Serialize(writer, new { instance.Global }, options);
+
+ writer.WritePropertyName(nameof(instance.Devices));
+ writer.WriteStartObject();
+ foreach (DshmDeviceData device in instance.Devices)
+ {
+ if (string.IsNullOrEmpty(device.DeviceAddress?.Trim()))
+ throw new JsonException("Expected non-null, non-empty Name");
+ writer.WritePropertyName(device.DeviceAddress);
+
+ var serializedCustomSettings = JsonSerializer.Serialize(device.DeviceSettings, options);
+ writer.WriteRawValue(serializedCustomSettings);
+ }
+ writer.WriteEndObject();
+
+ writer.WriteEndObject();
+
+ }
+ }
+ }
+}
diff --git a/ControlApp/Models/DshmConfigManager/DshmConfig/Enums/DSHMDriverEnums.cs b/ControlApp/Models/DshmConfigManager/DshmConfig/Enums/DSHMDriverEnums.cs
new file mode 100644
index 00000000..218c56ac
--- /dev/null
+++ b/ControlApp/Models/DshmConfigManager/DshmConfig/Enums/DSHMDriverEnums.cs
@@ -0,0 +1,68 @@
+namespace Nefarius.DsHidMini.ControlApp.Models.DshmConfigManager.DshmConfig.Enums
+{
+ public enum HidDeviceMode
+ {
+ SDF,
+ GPJ,
+ SXS,
+ DS4Windows,
+ XInput,
+ }
+
+ public enum DevicePairingMode
+ {
+ Auto,
+ Custom,
+ Disabled,
+ }
+
+ public enum PressureMode
+ {
+ Digital,
+ Analogue,
+ Default,
+ }
+
+ public enum DPadExposureMode
+ {
+ HAT,
+ IndividualButtons,
+ Default,
+ }
+
+ public enum LEDsMode
+ {
+ BatteryIndicatorPlayerIndex,
+ BatteryIndicatorBarGraph,
+ CustomPattern,
+ }
+
+ public enum Button
+ {
+ None,
+ PS,
+ START,
+ SELECT,
+ R1,
+ L1,
+ R2,
+ L2,
+ R3,
+ L3,
+ Triangle,
+ Circle,
+ Cross,
+ Square,
+ Up,
+ Right,
+ Down,
+ Left,
+ }
+
+ public enum DSHM_LEDsAuthority
+ {
+ Automatic,
+ Driver,
+ Application,
+ }
+}
diff --git a/ControlApp/Models/DshmConfigManager/DshmConfigManager.cs b/ControlApp/Models/DshmConfigManager/DshmConfigManager.cs
new file mode 100644
index 00000000..3fab63f8
--- /dev/null
+++ b/ControlApp/Models/DshmConfigManager/DshmConfigManager.cs
@@ -0,0 +1,333 @@
+using System.IO;
+using System.Text.Json;
+using System.Text.Json.Serialization;
+using Nefarius.DsHidMini.ControlApp.Models.DshmConfigManager.DshmConfig;
+using Nefarius.DsHidMini.ControlApp.Models.DshmConfigManager.Enums;
+using Serilog;
+using Serilog.Core;
+
+namespace Nefarius.DsHidMini.ControlApp.Models.DshmConfigManager
+{
+ ///
+ /// Class for managing user's dshidmini settings and applying them to the DsHidMini Configuration File
+ ///
+ public class DshmConfigManager
+ {
+
+ ///
+ /// Singleton instace of the DshmConfigManager's user data
+ ///
+ private static readonly DshmConfigManagerUserData dshmManagerUserData = DshmConfigManagerUserData.Instance;
+ ///
+ /// Raised when the DsHidMini Configuraton File on disk is updated
+ ///
+ public event EventHandler DshmConfigurationUpdated;
+ ///
+ /// Raised when the GlobalProfile is updated
+ ///
+ public event EventHandler GlobalProfileUpdated;
+
+
+ // ----------------------------------------------------------- PROPERTIES
+
+ ///
+ /// Profile used for all new controllers and those configured to use Global Settings Mode.
+ /// Reverts back to the Default Profile if the profile it's set to does not exist
+ ///
+ public ProfileData GlobalProfile
+ {
+ get
+ {
+ ProfileData gp = GetProfile(dshmManagerUserData.GlobalProfileGuid);
+ if (gp == null)
+ {
+ Log.Logger.Debug("Global profile set to non-existing profile");
+ Log.Logger.Debug("Reverting Global profile to default profile.");
+ dshmManagerUserData.GlobalProfileGuid = ProfileData.DefaultGuid;
+ GlobalProfileUpdated?.Invoke(this, new());
+ gp = ProfileData.DefaultProfile;
+ }
+ return gp;
+ }
+ set
+ {
+ Log.Logger.Debug($"Setting profile {value.ProfileName} as Global Profile");
+ dshmManagerUserData.GlobalProfileGuid = value.ProfileGuid;
+ GlobalProfileUpdated?.Invoke(this,new());
+ }
+
+ }
+
+
+ // ----------------------------------------------------------- CONSTRUCTOR
+
+ public DshmConfigManager()
+ {
+ FixDevicesWithBlankProfiles();
+ }
+
+ ///
+ /// Dshm Config Manager's User Data, containing the profile set as Global and Devices/Profiles datas
+ ///
+ private class DshmConfigManagerUserData
+ {
+ ///
+ /// Implicitly loads configuration from file.
+ ///
+ private static readonly Lazy AppConfigLazy =
+ new Lazy(() => JsonDshmUserData
+ .Load(
+ GlobalUserDataFileName,
+ true,
+ GlobalUserDataDirectory));
+
+ ///
+ /// Singleton instance of app configuration.
+ ///
+ public static DshmConfigManagerUserData Instance => AppConfigLazy.Value;
+
+ ///
+ /// Configuration file name
+ ///
+ [JsonIgnore]
+ public static string GlobalUserDataFileName => "DshmUserData";
+
+ public static string GlobalUserDataFolderName => "ControlApp";
+
+ public static string GlobalUserDataDirectory
+ {
+ get
+ {
+ var commonFolder = Environment.GetFolderPath(Environment.SpecialFolder.CommonApplicationData);
+ return Path.Combine(commonFolder, GlobalUserDataFolderName);
+ }
+ }
+ ///
+ /// Guid of the profile set as global
+ ///
+ public Guid GlobalProfileGuid { get; set; } = ProfileData.DefaultGuid;
+ ///
+ /// List of profiles datas
+ ///
+ public List Profiles { get; set; } = new();
+ ///
+ /// List of known devices datas
+ ///
+ public List Devices { get; set; } = new();
+
+
+ ///
+ /// Write changes to file.
+ ///
+ public void Save()
+ {
+ //
+ // Store (modified) configuration to disk
+ //
+ JsonDshmUserData.Save(
+ GlobalUserDataFileName,
+ this,
+ GlobalUserDataDirectory);
+ }
+
+ }
+
+ // ----------------------------------------------------------- METHODS
+
+ public void SaveChanges()
+ {
+ Log.Logger.Information("Saving DsHidMini User Data to disk.");
+ dshmManagerUserData.Save();
+ }
+
+ ///
+ /// Links Devices Datas back to the default profile if the profile they are set to use doesn't exist anymore,
+ /// also reverting them to the Global Settings Mode if in Profile Setting Mode
+ ///
+ private void FixDevicesWithBlankProfiles()
+ {
+ foreach(DeviceData device in dshmManagerUserData.Devices)
+ {
+ if(GetProfile(device.GuidOfProfileToUse) == null)
+ {
+ Log.Logger.Information($"Device {device.DeviceMac} linked to non-existing profile. Reverting link to default profile.");
+ device.GuidOfProfileToUse = ProfileData.DefaultGuid;
+ if (device.SettingsMode == SettingsModes.Profile)
+ {
+ Log.Logger.Information($"Device {device.DeviceMac} was in Profile Settings Mode while using a non-existing profile. Setting device back to Global Settings. ");
+ device.SettingsMode = SettingsModes.Global;
+ }
+
+ }
+ }
+ }
+
+ ///
+ /// If it exists, returns the Profile Data identified by the given GUID
+ ///
+ ///
+ /// The profile data of the given GUID if it exists, null otherwise
+ public ProfileData? GetProfile(Guid profileGuid)
+ {
+ ProfileData profile = null;
+
+ foreach(ProfileData p in GetListOfProfilesWithDefault())
+ {
+ if(p.ProfileGuid == profileGuid)
+ {
+ profile = p;
+ break;
+ }
+ }
+
+ if (profile == null)
+ {
+ Log.Logger.Debug($"No profile with GUID {profileGuid} found.");
+ }
+ return profile;
+ }
+
+ ///
+ /// Saves the DshmConfigManager configuration to disk and updates DsHidMini configuration file
+ ///
+ public void SaveChangesAndUpdateDsHidMiniConfigFile()
+ {
+ dshmManagerUserData.Save();
+ ApplySettings();
+ }
+
+ ///
+ /// Updates the DsHidMini configuration file on disk based on the global profile and each device's settings
+ ///
+ public void ApplySettings()
+ {
+ Log.Information("Updating DsHidMini configuration based on DsHidMini User Data");
+ Log.Debug("Building DsHidMini configuration object based on DsHidMini User Data");
+ var dshmConfiguration = new DshmConfiguration();
+ DshmManagerToDriverConversion.ConvertDeviceSettingsToDriverFormat(GlobalProfile.Settings, dshmConfiguration.Global);
+
+ foreach(DeviceData dev in dshmManagerUserData.Devices)
+ {
+ var dshmDeviceData = new DshmDeviceData();
+ dshmDeviceData.DeviceAddress = dev.DeviceMac;
+
+ // Disable BT auto-pairing if in Disabled BT Pairing Mode
+ dshmDeviceData.DeviceSettings.DisableAutoPairing =
+ (dev.BluetoothPairingMode == BluetoothPairingMode.Disabled) ? true : false;
+
+ dshmDeviceData.DeviceSettings.DevicePairingMode = DshmManagerToDriverConversion.PairingModeManagerToDriver[dev.BluetoothPairingMode];
+
+ dshmDeviceData.DeviceSettings.PairOnHotReload = dev.PairOnHotReload;
+
+ // If using custom BT Pairing Mode, set the pairing address to the desired one. Otherwise, leave it blank so DsHidMini auto-pairs to current BT host
+ dshmDeviceData.DeviceSettings.CustomPairingAddress =
+ (dev.BluetoothPairingMode == BluetoothPairingMode.Custom) ? dev.PairingAddress : null;
+
+ switch (dev.SettingsMode)
+ {
+ case SettingsModes.Custom:
+ DshmManagerToDriverConversion.ConvertDeviceSettingsToDriverFormat(dev.Settings,dshmDeviceData.DeviceSettings);
+ break;
+ case SettingsModes.Profile:
+ ProfileData devprof = GetProfile(dev.GuidOfProfileToUse);
+ DshmManagerToDriverConversion.ConvertDeviceSettingsToDriverFormat(devprof.Settings,dshmDeviceData.DeviceSettings);
+ break;
+
+ case SettingsModes.Global:
+ default:
+ // Device's in Global settings mode need to have empty settings so global settings are not overwritten
+ break;
+ }
+ dshmConfiguration.Devices.Add(dshmDeviceData);
+ }
+
+ Log.Logger.Debug("Configuration object built. Applying configuration.");
+ var updateStatus = dshmConfiguration.ApplyConfiguration();
+ DshmConfigurationUpdated?.Invoke(this, new DshmUpdatedEventArgs() { UpdatedSuccessfully = updateStatus});
+ }
+
+ public List GetListOfProfilesWithDefault()
+ {
+ var userProfilesPlusDefault = new List(dshmManagerUserData.Profiles);
+ userProfilesPlusDefault.Insert(0, ProfileData.DefaultProfile);
+ return userProfilesPlusDefault;
+ }
+
+ ///
+ /// Adds to the Profile List a new profile with the given name and settings based on the default profile
+ ///
+ /// Name of the new profile
+ /// The created profile
+ public ProfileData CreateProfile(string profileName)
+ {
+ ProfileData newProfile = new();
+ newProfile.ProfileName = profileName;
+ //newProfile.DiskFileName = profileName + ".json";
+ dshmManagerUserData.Profiles.Add(newProfile);
+ Log.Logger.Information($"Profile '{profileName}' created on DsHidMini User Data.");
+ return newProfile;
+ }
+
+ ///
+ /// Removes the given profile from the profile list.
+ /// Devices set to use it will be updated to use the default profile and will be reverted back to Global settings mode if in Profile settings mode
+ ///
+ /// The profile to be deleted
+ public void DeleteProfile(ProfileData profile)
+ {
+ Log.Logger.Information($"Deleting profile '{profile.ProfileName}'");
+ if (profile == ProfileData.DefaultProfile) // Never remove Default profile from the list
+ {
+ Log.Logger.Information($"Default Profile can't be deleted.");
+ return;
+ }
+ dshmManagerUserData.Profiles.Remove(profile);
+ FixDevicesWithBlankProfiles();
+ }
+
+ public SettingsContext GetDeviceExpectedHidMode(DeviceData dev)
+ {
+ switch (dev.SettingsMode)
+ {
+ case SettingsModes.Custom:
+ return dev.Settings.HidMode.SettingsContext;
+ break;
+ case SettingsModes.Profile:
+ return GetProfile(dev.GuidOfProfileToUse).Settings.HidMode.SettingsContext;
+ break;
+ case SettingsModes.Global:
+ default:
+ return GlobalProfile.Settings.HidMode.SettingsContext;
+ break;
+ }
+ }
+
+ ///
+ /// Gets the DsHidMini config. manager device data of a DsHidMini device. If it does not exist, a new one will be created for it first before returning
+ ///
+ /// The MAC address of the DsHidMini device
+ /// The device data of the DsHidMini device
+ public DeviceData GetDeviceData(string deviceMac)
+ {
+ Log.Logger.Information($"Getting data for device {deviceMac}.");
+ foreach (DeviceData dev in dshmManagerUserData.Devices)
+ {
+ if (dev.DeviceMac == deviceMac)
+ {
+ return dev;
+ }
+ }
+ Log.Logger.Information($"Data for Device {deviceMac} does not exist. Creating new.");
+ var newDevice = new DeviceData(deviceMac);
+ newDevice.DeviceMac = deviceMac;
+ dshmManagerUserData.Devices.Add(newDevice);
+ return newDevice;
+ }
+
+ public class DshmUpdatedEventArgs : EventArgs
+ {
+ public bool UpdatedSuccessfully;
+ }
+
+ }
+}
\ No newline at end of file
diff --git a/ControlApp/Models/DshmConfigManager/DshmTranslationUtils.cs b/ControlApp/Models/DshmConfigManager/DshmTranslationUtils.cs
new file mode 100644
index 00000000..89ab0893
--- /dev/null
+++ b/ControlApp/Models/DshmConfigManager/DshmTranslationUtils.cs
@@ -0,0 +1,250 @@
+using Nefarius.DsHidMini.ControlApp.Models.DshmConfigManager.DshmConfig;
+using Nefarius.DsHidMini.ControlApp.Models.DshmConfigManager.DshmConfig.Enums;
+using Nefarius.DsHidMini.ControlApp.Models.DshmConfigManager.Enums;
+using Nefarius.DsHidMini.ControlApp.Models.DshmConfigManager;
+using Button = Nefarius.DsHidMini.ControlApp.Models.DshmConfigManager.Enums.Button;
+using LEDsMode = Nefarius.DsHidMini.ControlApp.Models.DshmConfigManager.Enums.LEDsMode;
+using PressureMode = Nefarius.DsHidMini.ControlApp.Models.DshmConfigManager.Enums.PressureMode;
+using static Nefarius.DsHidMini.ControlApp.Models.DshmConfigManager.LedsSettings;
+
+namespace Nefarius.DsHidMini.ControlApp.Models.DshmConfigManager
+{
+ public class DshmManagerToDriverConversion
+ {
+ public static Dictionary HidDeviceMode = new()
+ {
+ {SettingsContext.Global , DshmConfig.Enums.HidDeviceMode.XInput},
+ {SettingsContext.General , DshmConfig.Enums.HidDeviceMode.XInput},
+ {SettingsContext.SDF , DshmConfig.Enums.HidDeviceMode.SDF},
+ {SettingsContext.GPJ , DshmConfig.Enums.HidDeviceMode.GPJ},
+ {SettingsContext.SXS , DshmConfig.Enums.HidDeviceMode.SXS},
+ {SettingsContext.DS4W , DshmConfig.Enums.HidDeviceMode.DS4Windows},
+ {SettingsContext.XInput , DshmConfig.Enums.HidDeviceMode.XInput},
+ };
+
+ //---------------------------------------------------- LEDsModes
+
+ public static Dictionary LedModeManagerToDriver = new()
+ {
+ { LEDsMode.BatteryIndicatorPlayerIndex, DshmConfig.Enums.LEDsMode.BatteryIndicatorPlayerIndex },
+ { LEDsMode.BatteryIndicatorBarGraph, DshmConfig.Enums.LEDsMode.BatteryIndicatorBarGraph },
+ { LEDsMode.CustomStatic, DshmConfig.Enums.LEDsMode.CustomPattern },
+ { LEDsMode.CustomPattern, DshmConfig.Enums.LEDsMode.CustomPattern },
+ };
+
+ //---------------------------------------------------- DPadModes
+
+ public static Dictionary DPadExposureModeManagerToDriver = new()
+ {
+ { DPadMode.Default, DPadExposureMode.Default },
+ { DPadMode.HAT, DPadExposureMode.HAT },
+ { DPadMode.Buttons, DPadExposureMode.IndividualButtons },
+ };
+
+ //---------------------------------------------------- PressureModes
+
+ public static Dictionary DsPressureModeManagerToDriver = new()
+ {
+ { PressureMode.Default, DshmConfig.Enums.PressureMode.Default },
+ { PressureMode.Analogue, DshmConfig.Enums.PressureMode.Analogue },
+ { PressureMode.Digital, DshmConfig.Enums.PressureMode.Digital },
+ };
+
+ public static Dictionary