diff --git a/SecretAPI/Features/UserSettings/CustomButtonSetting.cs b/SecretAPI/Features/UserSettings/CustomButtonSetting.cs
new file mode 100644
index 0000000..9e12c26
--- /dev/null
+++ b/SecretAPI/Features/UserSettings/CustomButtonSetting.cs
@@ -0,0 +1,61 @@
+namespace SecretAPI.Features.UserSettings
+{
+ using System;
+ using global::UserSettings.ServerSpecific;
+ using SecretAPI.Interfaces;
+
+ ///
+ /// Wraps .
+ ///
+ public abstract class CustomButtonSetting : CustomSetting, ISetting
+ {
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The button base.
+ protected CustomButtonSetting(SSButton button)
+ : base(button)
+ {
+ Base = button;
+ }
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The ID of the button.
+ /// The setting's label.
+ /// The button text.
+ /// The time to hold.
+ /// The hint to show.
+ protected CustomButtonSetting(int? id, string label, string buttonText, float? holdTimeSeconds = null, string? hint = null)
+ : this(new SSButton(id, label, buttonText, holdTimeSeconds, hint))
+ {
+ }
+
+ ///
+ public new SSButton Base { get; }
+
+ ///
+ /// Gets the of the last press.
+ ///
+ public TimeSpan LastPress => Base.SyncLastPress.Elapsed;
+
+ ///
+ /// Gets or sets the text of the button.
+ ///
+ public string Text
+ {
+ get => Base.ButtonText;
+ set => Base.ButtonText = value;
+ }
+
+ ///
+ /// Gets or sets the amount of time to hold the button in seconds.
+ ///
+ public float HoldTime
+ {
+ get => Base.HoldTimeSeconds;
+ set => Base.HoldTimeSeconds = value;
+ }
+ }
+}
\ No newline at end of file
diff --git a/SecretAPI/Features/UserSettings/CustomDropdownSetting.cs b/SecretAPI/Features/UserSettings/CustomDropdownSetting.cs
new file mode 100644
index 0000000..7f7d3e1
--- /dev/null
+++ b/SecretAPI/Features/UserSettings/CustomDropdownSetting.cs
@@ -0,0 +1,58 @@
+namespace SecretAPI.Features.UserSettings
+{
+ using global::UserSettings.ServerSpecific;
+ using SecretAPI.Interfaces;
+
+ ///
+ /// Custom wrapper.
+ ///
+ public abstract class CustomDropdownSetting : CustomSetting, ISetting
+ {
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The base setting to create the wrapper with.
+ protected CustomDropdownSetting(SSDropdownSetting setting)
+ : base(setting)
+ {
+ Base = setting;
+ }
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The ID of the setting.
+ /// The setting's label.
+ /// The array of string options to give.
+ /// The default option (int index).
+ /// The entry type.
+ /// The hint to show.
+ protected CustomDropdownSetting(
+ int? id,
+ string label,
+ string[] options,
+ int defaultOptionIndex = 0,
+ SSDropdownSetting.DropdownEntryType entryType = SSDropdownSetting.DropdownEntryType.Regular,
+ string? hint = null)
+ : this(new SSDropdownSetting(id, label, options, defaultOptionIndex, entryType, hint))
+ {
+ }
+
+ ///
+ public new SSDropdownSetting Base { get; }
+
+ ///
+ /// Gets or sets the options.
+ ///
+ public string[] Options
+ {
+ get => Base.Options;
+ set => Base.Options = value;
+ }
+
+ ///
+ /// Gets the selected option as string.
+ ///
+ public string SelectedOption => Base.SyncSelectionText;
+ }
+}
\ No newline at end of file
diff --git a/SecretAPI/Features/UserSettings/CustomHeader.cs b/SecretAPI/Features/UserSettings/CustomHeader.cs
new file mode 100644
index 0000000..8fe2437
--- /dev/null
+++ b/SecretAPI/Features/UserSettings/CustomHeader.cs
@@ -0,0 +1,35 @@
+namespace SecretAPI.Features.UserSettings
+{
+ using global::UserSettings.ServerSpecific;
+ using SecretAPI.Interfaces;
+
+ ///
+ /// Wraps .
+ ///
+ public class CustomHeader : ISetting
+ {
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The label to show.
+ /// Reduced padding.
+ /// Hint displayed.
+ public CustomHeader(string label, bool reducedPadding = false, string? hint = null)
+ {
+ Base = new SSGroupHeader(label, reducedPadding, hint);
+ }
+
+ ///
+ /// Gets a for Gameplay purposes.
+ ///
+ public static CustomHeader Gameplay { get; } = new("Gameplay");
+
+ ///
+ /// Gets a for Example purposes.
+ ///
+ public static CustomHeader Examples { get; } = new("Examples");
+
+ ///
+ public SSGroupHeader Base { get; }
+ }
+}
\ No newline at end of file
diff --git a/SecretAPI/Features/UserSettings/CustomKeybindSetting.cs b/SecretAPI/Features/UserSettings/CustomKeybindSetting.cs
new file mode 100644
index 0000000..2a78ba6
--- /dev/null
+++ b/SecretAPI/Features/UserSettings/CustomKeybindSetting.cs
@@ -0,0 +1,43 @@
+namespace SecretAPI.Features.UserSettings
+{
+ using global::UserSettings.ServerSpecific;
+ using SecretAPI.Interfaces;
+ using UnityEngine;
+
+ ///
+ /// Wrapper for .
+ ///
+ public abstract class CustomKeybindSetting : CustomSetting, ISetting
+ {
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The setting to wrap.
+ protected CustomKeybindSetting(SSKeybindSetting setting)
+ : base(setting)
+ {
+ Base = setting;
+ }
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The ID of the setting.
+ /// The setting's label.
+ /// The suggested key.
+ /// Whether to prevent interaction in a GUI.
+ /// The hint to show.
+ protected CustomKeybindSetting(
+ int? id,
+ string label,
+ KeyCode suggestedKey = KeyCode.None,
+ bool preventInteractionOnGui = true,
+ string? hint = null)
+ : this(new SSKeybindSetting(id, label, suggestedKey, preventInteractionOnGui, hint))
+ {
+ }
+
+ ///
+ public new SSKeybindSetting Base { get; }
+ }
+}
\ No newline at end of file
diff --git a/SecretAPI/Features/UserSettings/CustomPlainTextSetting.cs b/SecretAPI/Features/UserSettings/CustomPlainTextSetting.cs
new file mode 100644
index 0000000..619f0ca
--- /dev/null
+++ b/SecretAPI/Features/UserSettings/CustomPlainTextSetting.cs
@@ -0,0 +1,81 @@
+namespace SecretAPI.Features.UserSettings
+{
+ using global::UserSettings.ServerSpecific;
+ using SecretAPI.Interfaces;
+ using TMPro;
+
+ ///
+ /// Wrapper for .
+ ///
+ public abstract class CustomPlainTextSetting : CustomSetting, ISetting
+ {
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The setting to create wrapper from.
+ protected CustomPlainTextSetting(SSPlaintextSetting setting)
+ : base(setting)
+ {
+ Base = setting;
+ }
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The ID of the setting.
+ /// The setting's label.
+ /// The placeholder to use for the setting.
+ /// The max allowed characters.
+ /// The content type.
+ /// The hint to display for the setting.
+ protected CustomPlainTextSetting(
+ int? id,
+ string label,
+ string placeholder = "...",
+ int characterLimit = 64,
+ TMP_InputField.ContentType contentType = TMP_InputField.ContentType.Standard,
+ string? hint = null)
+ : this(new SSPlaintextSetting(id, label, placeholder, characterLimit, contentType, hint))
+ {
+ }
+
+ ///
+ public new SSPlaintextSetting Base { get; }
+
+ ///
+ /// Gets or sets the synced input text.
+ ///
+ public string InputText
+ {
+ get => Base.SyncInputText;
+ set => Base.SyncInputText = value;
+ }
+
+ ///
+ /// Gets or sets the content type.
+ ///
+ public TMP_InputField.ContentType ContentType
+ {
+ get => Base.ContentType;
+ set => Base.ContentType = value;
+ }
+
+ ///
+ /// Gets or sets the placeholder.
+ ///
+ public string Placeholder
+ {
+ get => Base.Placeholder;
+ set => Base.Placeholder = value;
+ }
+
+ ///
+ /// Gets or sets the character limit.
+ ///
+ public int CharacterLimit
+ {
+ get => Base.CharacterLimit;
+ set => Base.CharacterLimit = value;
+ }
+ }
+}
\ No newline at end of file
diff --git a/SecretAPI/Features/UserSettings/CustomSetting.cs b/SecretAPI/Features/UserSettings/CustomSetting.cs
new file mode 100644
index 0000000..ce44a21
--- /dev/null
+++ b/SecretAPI/Features/UserSettings/CustomSetting.cs
@@ -0,0 +1,167 @@
+namespace SecretAPI.Features.UserSettings
+{
+ using System;
+ using System.Collections.Generic;
+ using System.Linq;
+ using global::UserSettings.ServerSpecific;
+ using LabApi.Features.Wrappers;
+ using Mirror;
+ using SecretAPI.Extensions;
+ using SecretAPI.Interfaces;
+
+ ///
+ /// Wraps .
+ ///
+ public abstract class CustomSetting : ISetting
+ {
+ private static readonly Dictionary> ReceivedPlayerSettings = [];
+
+ static CustomSetting()
+ {
+ SecretApi.Harmony?.PatchCategory(nameof(CustomSetting));
+
+ ServerSpecificSettingsSync.SendOnJoinFilter = null;
+ LabApi.Events.Handlers.PlayerEvents.Joined += ev => SendSettingsToPlayer(ev.Player);
+ LabApi.Events.Handlers.PlayerEvents.Left += ev => RemoveStoredPlayer(ev.Player);
+ LabApi.Events.Handlers.PlayerEvents.GroupChanged += ev => SendSettingsToPlayer(ev.Player);
+ ServerSpecificSettingsSync.ServerOnSettingValueReceived += OnSettingsUpdated;
+ }
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The setting to use for custom setting.
+ protected CustomSetting(ServerSpecificSettingBase setting)
+ {
+ Base = setting;
+ }
+
+ ///
+ /// Gets the registered custom settings.
+ ///
+ public static List CustomSettings { get; } = [];
+
+ ///
+ /// Gets a dictionary of player to their received custom settings.
+ ///
+ public static IReadOnlyDictionary> PlayerSettings => ReceivedPlayerSettings;
+
+ ///
+ public ServerSpecificSettingBase Base { get; }
+
+ ///
+ /// Gets the of the setting.
+ ///
+ public abstract CustomHeader Header { get; }
+
+ ///
+ /// Gets or sets the current label.
+ ///
+ public string Label
+ {
+ get => Base.Label;
+ set => Base.Label = value;
+ }
+
+ ///
+ /// Gets or sets the current id.
+ ///
+ public int Id
+ {
+ get => Base.SettingId;
+ set => Base.SettingId = value;
+ }
+
+ ///
+ /// Registers a collection of settings.
+ ///
+ /// The settings to register.
+ public static void Register(params CustomSetting[] settings) => CustomSettings.AddRange(settings);
+
+ ///
+ /// Registers a collection of settings.
+ ///
+ /// The settings to register.
+ public static void Register(IEnumerable settings) => CustomSettings.AddRange(settings);
+
+ ///
+ /// Gets a , used for validation.
+ ///
+ /// The type of the base setting.
+ /// The id of the setting.
+ /// The found matching the params, otherwise null.
+ public static CustomSetting? Get(Type type, int id)
+ => CustomSettings.FirstOrDefault(s => s.Base.SettingId == id && s.Base.GetType() == type);
+
+ ///
+ /// Checks if a player is able to view a setting.
+ ///
+ /// The player to check.
+ /// A value indicating whether a player is able to view the setting.
+ protected virtual bool CanView(Player player) => true;
+
+ ///
+ /// Creates a duplicate of the current setting. Used to properly sync values and implement .
+ ///
+ /// The duplicate setting created.
+ protected abstract CustomSetting CreateDuplicate();
+
+ ///
+ /// Handles the updating of a setting.
+ ///
+ /// The player to update.
+ protected abstract void HandleSettingUpdate(Player player);
+
+ private static void RemoveStoredPlayer(Player player) => ReceivedPlayerSettings.Remove(player);
+
+ private static void SendSettingsToPlayer(Player player, int version = 1)
+ {
+ IEnumerable hasAccess = CustomSettings.Where(s => s.CanView(player));
+ List ordered = [];
+ foreach (IGrouping grouping in hasAccess.GroupBy(setting => setting.Header))
+ {
+ ordered.Add(grouping.Key.Base);
+ ordered.AddRange(grouping.Select(setting => setting.Base));
+ }
+
+ ordered.AddRange(ServerSpecificSettingsSync.DefinedSettings);
+
+ ServerSpecificSettingsSync.SendToPlayer(player.ReferenceHub, [.. ordered], version);
+ }
+
+ private static void OnSettingsUpdated(ReferenceHub hub, ServerSpecificSettingBase settingBase)
+ {
+ if (hub.IsHost)
+ return;
+
+ Player player = Player.Get(hub);
+
+ CustomSetting? setting = CustomSettings.FirstOrDefault(s => s.Base.SettingId == settingBase.SettingId);
+ if (setting == null || !setting.CanView(player))
+ return;
+
+ CustomSetting newSettingPlayer = EnsurePlayerSpecificSetting(player, setting);
+
+ NetworkWriter entryWriter = new();
+ NetworkWriter valueWriter = new();
+ settingBase.SerializeEntry(entryWriter);
+ settingBase.SerializeValue(valueWriter);
+ newSettingPlayer.Base.DeserializeEntry(new NetworkReader(entryWriter.buffer));
+ newSettingPlayer.Base.DeserializeValue(new NetworkReader(valueWriter.buffer));
+ newSettingPlayer.HandleSettingUpdate(player);
+ }
+
+ private static CustomSetting EnsurePlayerSpecificSetting(Player player, CustomSetting toMatch)
+ {
+ List settings = ReceivedPlayerSettings.GetOrAdd(player, () => []);
+ CustomSetting? currentSetting = settings.FirstOrDefault(s => s.Id == toMatch.Id);
+ if (currentSetting == null)
+ {
+ currentSetting = toMatch.CreateDuplicate();
+ settings.Add(currentSetting);
+ }
+
+ return currentSetting;
+ }
+ }
+}
\ No newline at end of file
diff --git a/SecretAPI/Features/UserSettings/CustomSliderSetting.cs b/SecretAPI/Features/UserSettings/CustomSliderSetting.cs
new file mode 100644
index 0000000..2b53a1f
--- /dev/null
+++ b/SecretAPI/Features/UserSettings/CustomSliderSetting.cs
@@ -0,0 +1,87 @@
+namespace SecretAPI.Features.UserSettings
+{
+ using global::UserSettings.ServerSpecific;
+ using SecretAPI.Interfaces;
+
+ ///
+ /// Wrapper for .
+ ///
+ public abstract class CustomSliderSetting : CustomSetting, ISetting
+ {
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The setting to wrap.
+ protected CustomSliderSetting(SSSliderSetting setting)
+ : base(setting)
+ {
+ Base = setting;
+ }
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The ID of the setting.
+ /// The setting's label.
+ /// The slider's minimum value.
+ /// The slider's maximum value.
+ /// The default value for the slider.
+ /// Whether it should be an integer (false for float).
+ /// Value to string format.
+ /// The final display format.
+ /// The hint to display.
+ protected CustomSliderSetting(
+ int? id,
+ string label,
+ float minValue,
+ float maxValue,
+ float defaultValue = 0.0f,
+ bool integer = false,
+ string valueToStringFormat = "0.##",
+ string finalDisplayFormat = "{0}",
+ string? hint = null)
+ : this(new SSSliderSetting(id, label, minValue, maxValue, defaultValue, integer, valueToStringFormat, finalDisplayFormat, hint))
+ {
+ }
+
+ ///
+ public new SSSliderSetting Base { get; }
+
+ ///
+ /// Gets the synced value selected as a float.
+ ///
+ public float SelectedValueFloat => Base.SyncFloatValue;
+
+ ///
+ /// Gets the synced value selected as an integer.
+ ///
+ public int SelectedValueInt => Base.SyncIntValue;
+
+ ///
+ /// Gets or sets the minimum value of the setting.
+ ///
+ public float MinimumValue
+ {
+ get => Base.MinValue;
+ set => Base.MinValue = value;
+ }
+
+ ///
+ /// Gets or sets the maximum value of the setting.
+ ///
+ public float MaximumValue
+ {
+ get => Base.MaxValue;
+ set => Base.MaxValue = value;
+ }
+
+ ///
+ /// Gets or sets the default value of the setting.
+ ///
+ public float DefaultValue
+ {
+ get => Base.DefaultValue;
+ set => Base.DefaultValue = value;
+ }
+ }
+}
\ No newline at end of file
diff --git a/SecretAPI/Features/UserSettings/CustomTextAreaSetting.cs b/SecretAPI/Features/UserSettings/CustomTextAreaSetting.cs
new file mode 100644
index 0000000..1a5c536
--- /dev/null
+++ b/SecretAPI/Features/UserSettings/CustomTextAreaSetting.cs
@@ -0,0 +1,43 @@
+namespace SecretAPI.Features.UserSettings
+{
+ using global::UserSettings.ServerSpecific;
+ using SecretAPI.Interfaces;
+ using TMPro;
+
+ ///
+ /// Wrapper for .
+ ///
+ public abstract class CustomTextAreaSetting : CustomSetting, ISetting
+ {
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The setting to wrap.
+ protected CustomTextAreaSetting(SSTextArea setting)
+ : base(setting)
+ {
+ Base = setting;
+ }
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The ID of the setting.
+ /// The content of the setting.
+ /// The foldout mode.
+ /// The collapsed text.
+ /// The align for the text.
+ protected CustomTextAreaSetting(
+ int? id,
+ string content,
+ SSTextArea.FoldoutMode foldoutMode = SSTextArea.FoldoutMode.NotCollapsable,
+ string? collapsedText = null,
+ TextAlignmentOptions textAlignment = TextAlignmentOptions.TopLeft)
+ : this(new SSTextArea(id, content, foldoutMode, collapsedText, textAlignment))
+ {
+ }
+
+ ///
+ public new SSTextArea Base { get; }
+ }
+}
\ No newline at end of file
diff --git a/SecretAPI/Features/UserSettings/CustomTwoButtonSetting.cs b/SecretAPI/Features/UserSettings/CustomTwoButtonSetting.cs
new file mode 100644
index 0000000..73fb40e
--- /dev/null
+++ b/SecretAPI/Features/UserSettings/CustomTwoButtonSetting.cs
@@ -0,0 +1,53 @@
+namespace SecretAPI.Features.UserSettings
+{
+ using global::UserSettings.ServerSpecific;
+ using SecretAPI.Interfaces;
+
+ ///
+ /// Wrapper for .
+ ///
+ public abstract class CustomTwoButtonSetting : CustomSetting, ISetting
+ {
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The setting to wrap.
+ protected CustomTwoButtonSetting(SSTwoButtonsSetting button)
+ : base(button)
+ {
+ Base = button;
+ }
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The ID of the setting.
+ /// The setting's label.
+ /// The first option.
+ /// The second option.
+ /// Whether the second option should be default. Default: false.
+ /// The hint to show.
+ protected CustomTwoButtonSetting(int? id, string label, string optionA, string optionB, bool defaultIsB = false, string? hint = null)
+ : this(new SSTwoButtonsSetting(id, label, optionA, optionB, defaultIsB, hint))
+ {
+ }
+
+ ///
+ public new SSTwoButtonsSetting Base { get; }
+
+ ///
+ /// Gets a value indicating whether the selected option is currently the first.
+ ///
+ public bool IsOptionA => Base.SyncIsA;
+
+ ///
+ /// Gets a value indicating whether the selected option is currently the second.
+ ///
+ public bool IsOptionB => Base.SyncIsB;
+
+ ///
+ /// Gets a value indicating whether the selected option is currently the default.
+ ///
+ public bool IsDefault => Base.DefaultIsB ? IsOptionB : IsOptionA;
+ }
+}
\ No newline at end of file
diff --git a/SecretAPI/Features/UserSettings/ExampleSetting.cs b/SecretAPI/Features/UserSettings/ExampleSetting.cs
new file mode 100644
index 0000000..51d4843
--- /dev/null
+++ b/SecretAPI/Features/UserSettings/ExampleSetting.cs
@@ -0,0 +1,31 @@
+namespace SecretAPI.Features.UserSettings
+{
+ using LabApi.Features.Wrappers;
+ using UnityEngine;
+
+ ///
+ /// Example setting to use during testing.
+ ///
+ internal class ExampleSetting : CustomKeybindSetting
+ {
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ public ExampleSetting()
+ : base(900, "Example Kill Button", KeyCode.G)
+ {
+ }
+
+ ///
+ public override CustomHeader Header { get; } = CustomHeader.Examples;
+
+ ///
+ protected override CustomSetting CreateDuplicate() => new ExampleSetting();
+
+ ///
+ protected override void HandleSettingUpdate(Player player)
+ {
+ player.Kill();
+ }
+ }
+}
\ No newline at end of file
diff --git a/SecretAPI/Interfaces/ISetting.cs b/SecretAPI/Interfaces/ISetting.cs
new file mode 100644
index 0000000..18e9889
--- /dev/null
+++ b/SecretAPI/Interfaces/ISetting.cs
@@ -0,0 +1,17 @@
+namespace SecretAPI.Interfaces
+{
+ using UserSettings.ServerSpecific;
+
+ ///
+ /// Handles .
+ ///
+ /// The setting being wrapped.
+ public interface ISetting
+ where T : ServerSpecificSettingBase
+ {
+ ///
+ /// Gets the base of the setting.
+ ///
+ public T Base { get; }
+ }
+}
\ No newline at end of file
diff --git a/SecretAPI/Patches/Features/SettingsOriginalDefinitionFix.cs b/SecretAPI/Patches/Features/SettingsOriginalDefinitionFix.cs
new file mode 100644
index 0000000..08c2ad5
--- /dev/null
+++ b/SecretAPI/Patches/Features/SettingsOriginalDefinitionFix.cs
@@ -0,0 +1,25 @@
+namespace SecretAPI.Patches.Features
+{
+ using HarmonyLib;
+ using SecretAPI.Attribute;
+ using SecretAPI.Features.UserSettings;
+ using UserSettings.ServerSpecific;
+
+ ///
+ /// Fixes on custom settings.
+ ///
+ [HarmonyPatchCategory(nameof(CustomSetting))]
+ [HarmonyPatch(typeof(ServerSpecificSettingBase), nameof(ServerSpecificSettingBase.OriginalDefinition), MethodType.Getter)]
+ internal static class SettingsOriginalDefinitionFix
+ {
+#pragma warning disable SA1313
+ private static void Postfix(ServerSpecificSettingBase __instance, ref ServerSpecificSettingBase __result)
+#pragma warning restore SA1313
+ {
+ if (__result != null)
+ return;
+
+ __result = CustomSetting.Get(__instance.GetType(), __instance.SettingId)?.Base ?? null!;
+ }
+ }
+}
\ No newline at end of file
diff --git a/SecretAPI/Patches/Features/SettingsSyncValidateFix.cs b/SecretAPI/Patches/Features/SettingsSyncValidateFix.cs
new file mode 100644
index 0000000..ae2d5a5
--- /dev/null
+++ b/SecretAPI/Patches/Features/SettingsSyncValidateFix.cs
@@ -0,0 +1,25 @@
+namespace SecretAPI.Patches.Features
+{
+ using HarmonyLib;
+ using SecretAPI.Attribute;
+ using SecretAPI.Features.UserSettings;
+ using UserSettings.ServerSpecific;
+
+ ///
+ /// Fixes validation for .
+ ///
+ [HarmonyPatchCategory(nameof(CustomSetting))]
+ [HarmonyPatch(typeof(ServerSpecificSettingsSync), nameof(ServerSpecificSettingsSync.ServerPrevalidateClientResponse))]
+ internal static class SettingsSyncValidateFix
+ {
+#pragma warning disable SA1313
+ private static void Postfix(SSSClientResponse msg, ref bool __result)
+#pragma warning restore SA1313
+ {
+ if (__result)
+ return;
+
+ __result = CustomSetting.Get(msg.SettingType, msg.Id) != null;
+ }
+ }
+}
\ No newline at end of file
diff --git a/SecretAPI/SecretAPI.csproj b/SecretAPI/SecretAPI.csproj
index 1063db9..0251372 100644
--- a/SecretAPI/SecretAPI.csproj
+++ b/SecretAPI/SecretAPI.csproj
@@ -5,6 +5,7 @@
latest
enable
0.3.0
+ true
@@ -43,6 +44,7 @@
+