├── .gitattributes ├── .github └── FUNDING.yml ├── .gitignore ├── BSIndexHapticFix.sln ├── BSIndexHapticFix ├── BSIndexHapticFix.csproj ├── Components │ └── HapticEmulator.cs ├── Directory.Build.props ├── Directory.Build.targets ├── HarmonyPatches │ └── POpenVRHelper_TriggerHapticPulse.cs ├── Plugin.cs ├── Properties │ └── AssemblyInfo.cs └── manifest.json ├── LICENSE └── README.md /.gitattributes: -------------------------------------------------------------------------------- 1 | # Auto detect text files and perform LF normalization 2 | * text=auto 3 | -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | # These are supported funding model platforms 2 | 3 | github: # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2] 4 | patreon: BeatSaberPlus 5 | open_collective: # Replace with a single Open Collective username 6 | ko_fi: # Replace with a single Ko-fi username 7 | tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel 8 | community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry 9 | liberapay: # Replace with a single Liberapay username 10 | issuehunt: # Replace with a single IssueHunt username 11 | otechie: # Replace with a single Otechie username 12 | custom: # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2'] 13 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | [Ll]ibrary/ 2 | [Tt]emp/ 3 | [Oo]bj/ 4 | [Bb]uild/ 5 | [Bb]in/ 6 | [Bb]uilds/ 7 | Assets/AssetStoreTools* 8 | 9 | # Visual Studio cache directory 10 | .vs/ 11 | 12 | # Autogenerated VS/MD/Consulo solution and project files 13 | ExportedObj/ 14 | .consulo/ 15 | *.unityproj 16 | *.suo 17 | *.tmp 18 | *.user 19 | *.userprefs 20 | *.pidb 21 | *.booproj 22 | *.svd 23 | *.pdb 24 | *.opendb 25 | *.VC.db 26 | 27 | # Unity3D generated meta files 28 | *.pidb.meta 29 | *.pdb.meta 30 | 31 | # Unity3D Generated File On Crash Reports 32 | sysinfo.txt 33 | 34 | # Builds 35 | *.apk 36 | *.unitypackage 37 | -------------------------------------------------------------------------------- /BSIndexHapticFix.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio Version 16 4 | VisualStudioVersion = 16.0.31729.503 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "BSIndexHapticFix", "BSIndexHapticFix\BSIndexHapticFix.csproj", "{71A6A586-1683-4545-9295-A1AF472480DA}" 7 | EndProject 8 | Global 9 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 10 | Debug|Any CPU = Debug|Any CPU 11 | Release|Any CPU = Release|Any CPU 12 | EndGlobalSection 13 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 14 | {71A6A586-1683-4545-9295-A1AF472480DA}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 15 | {71A6A586-1683-4545-9295-A1AF472480DA}.Debug|Any CPU.Build.0 = Debug|Any CPU 16 | {71A6A586-1683-4545-9295-A1AF472480DA}.Release|Any CPU.ActiveCfg = Release|Any CPU 17 | {71A6A586-1683-4545-9295-A1AF472480DA}.Release|Any CPU.Build.0 = Release|Any CPU 18 | EndGlobalSection 19 | GlobalSection(SolutionProperties) = preSolution 20 | HideSolutionNode = FALSE 21 | EndGlobalSection 22 | GlobalSection(ExtensibilityGlobals) = postSolution 23 | SolutionGuid = {0D12CF74-A946-4E2A-9103-6E1D8CC943B6} 24 | EndGlobalSection 25 | EndGlobal 26 | -------------------------------------------------------------------------------- /BSIndexHapticFix/BSIndexHapticFix.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | Debug 5 | AnyCPU 6 | 8.0.30703 7 | 2.0 8 | {71A6A586-1683-4545-9295-A1AF472480DA} 9 | Library 10 | Properties 11 | BSIndexHapticFix 12 | BSIndexHapticFix 13 | v4.7.2 14 | 512 15 | true 16 | portable 17 | ..\Refs 18 | $(LocalRefsDir) 19 | $(MSBuildProjectDirectory)\ 20 | 21 | prompt 22 | 4 23 | 24 | 25 | false 26 | bin\Debug\ 27 | DEBUG;TRACE 28 | 29 | 30 | true 31 | bin\Release\ 32 | prompt 33 | 4 34 | 35 | 36 | True 37 | 38 | 39 | True 40 | True 41 | 42 | 43 | 44 | $(BeatSaberDir)\Libs\0Harmony.dll 45 | False 46 | False 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | $(BeatSaberDir)\Beat Saber_Data\Managed\Main.dll 56 | False 57 | 58 | 59 | $(BeatSaberDir)\Beat Saber_Data\Managed\HMLib.dll 60 | False 61 | 62 | 63 | $(BeatSaberDir)\Beat Saber_Data\Managed\HMUI.dll 64 | False 65 | 66 | 67 | $(BeatSaberDir)\Beat Saber_Data\Managed\IPA.Loader.dll 68 | False 69 | 70 | 71 | $(BeatSaberDir)\Beat Saber_Data\Managed\Unity.TextMeshPro.dll 72 | False 73 | 74 | 75 | $(BeatSaberDir)\Beat Saber_Data\Managed\UnityEngine.dll 76 | False 77 | 78 | 79 | $(BeatSaberDir)\Beat Saber_Data\Managed\UnityEngine.CoreModule.dll 80 | False 81 | 82 | 83 | $(BeatSaberDir)\Beat Saber_Data\Managed\UnityEngine.UI.dll 84 | False 85 | 86 | 87 | $(BeatSaberDir)\Beat Saber_Data\Managed\UnityEngine.UIElementsModule.dll 88 | False 89 | 90 | 91 | $(BeatSaberDir)\Beat Saber_Data\Managed\UnityEngine.UIModule.dll 92 | False 93 | 94 | 95 | $(BeatSaberDir)\Beat Saber_Data\Managed\UnityEngine.VRModule.dll 96 | False 97 | 98 | 99 | $(BeatSaberDir)\Beat Saber_Data\Managed\UnityEngine.XRModule.dll 100 | False 101 | False 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 1.2.3 121 | runtime; build; native; contentfiles; analyzers; buildtransitive 122 | all 123 | 124 | 125 | 126 | -------------------------------------------------------------------------------- /BSIndexHapticFix/Components/HapticEmulator.cs: -------------------------------------------------------------------------------- 1 | using System.Collections; 2 | using System.Collections.Generic; 3 | using UnityEngine; 4 | 5 | namespace BSIndexHapticFix.Components 6 | { 7 | /// 8 | /// Haptic emulator for a single controller 9 | /// 10 | public class HapticEmulator : MonoBehaviour 11 | { 12 | /// 13 | /// Target XR Node 14 | /// 15 | public UnityEngine.XR.XRNode Node { get; private set; } 16 | /// 17 | /// Is it an Index knuckle? 18 | /// 19 | public bool IsIndexKnuckle { get; private set; } = false; 20 | /// 21 | /// Is left hand 22 | /// 23 | public bool IsLeftHand => Node == UnityEngine.XR.XRNode.LeftHand; 24 | /// 25 | /// Is right hand 26 | /// 27 | public bool IsRightHand => Node == UnityEngine.XR.XRNode.RightHand; 28 | 29 | //////////////////////////////////////////////////////////////////////////// 30 | //////////////////////////////////////////////////////////////////////////// 31 | 32 | /// 33 | /// Matching XR input device 34 | /// 35 | private UnityEngine.XR.InputDevice? m_Device = null; 36 | /// 37 | /// Remaining haptic time 38 | /// 39 | private float m_OpenVREmulatedHapticRemainingTime = 0f; 40 | /// 41 | /// Remaining haptic strength 42 | /// 43 | private float m_OpenVREmulatedHapticAmplitude = 0f; 44 | 45 | //////////////////////////////////////////////////////////////////////////// 46 | //////////////////////////////////////////////////////////////////////////// 47 | 48 | /// 49 | /// Start is called before the first frame update 50 | /// 51 | public void Init(UnityEngine.XR.XRNode p_Node) 52 | { 53 | Node = p_Node; 54 | 55 | /// Bind callbacks 56 | UnityEngine.XR.InputDevices.deviceConnected += InputDevices_deviceConnected; 57 | UnityEngine.XR.InputDevices.deviceDisconnected += InputDevices_deviceDisconnected; 58 | 59 | /// In case of late init, search device manually 60 | var l_AvailableDevicesAtNode = new List(); 61 | UnityEngine.XR.InputDevices.GetDevicesAtXRNode(Node, l_AvailableDevicesAtNode); 62 | l_AvailableDevicesAtNode.ForEach(x => InputDevices_deviceConnected(x)); 63 | 64 | GameObject.DontDestroyOnLoad(gameObject); 65 | } 66 | /// 67 | /// On component destroy 68 | /// 69 | private void OnDestroy() 70 | { 71 | /// Remove callbacks 72 | UnityEngine.XR.InputDevices.deviceDisconnected -= InputDevices_deviceDisconnected; 73 | UnityEngine.XR.InputDevices.deviceConnected -= InputDevices_deviceConnected; 74 | } 75 | 76 | //////////////////////////////////////////////////////////////////////////// 77 | //////////////////////////////////////////////////////////////////////////// 78 | 79 | public void SetHaptics(float p_Amplitude, float p_Duration) 80 | { 81 | float l_VibrationStrength = 2f; 82 | 83 | p_Duration *= l_VibrationStrength; 84 | p_Amplitude *= l_VibrationStrength; 85 | 86 | if (p_Amplitude <= 0.01f) 87 | return; 88 | 89 | m_OpenVREmulatedHapticRemainingTime = p_Duration; 90 | m_OpenVREmulatedHapticAmplitude = Mathf.Clamp01(p_Amplitude); 91 | } 92 | 93 | //////////////////////////////////////////////////////////////////////////// 94 | //////////////////////////////////////////////////////////////////////////// 95 | 96 | /// 97 | /// On XR device connected 98 | /// 99 | /// New device 100 | private void InputDevices_deviceConnected(UnityEngine.XR.InputDevice p_Device) 101 | { 102 | var l_RequiredCharacteristics = UnityEngine.XR.InputDeviceCharacteristics.Controller 103 | | UnityEngine.XR.InputDeviceCharacteristics.TrackedDevice 104 | | UnityEngine.XR.InputDeviceCharacteristics.HeldInHand 105 | | (IsLeftHand ? UnityEngine.XR.InputDeviceCharacteristics.Left : UnityEngine.XR.InputDeviceCharacteristics.Right); 106 | 107 | /// Check for matching role 108 | if (!p_Device.isValid || (p_Device.characteristics & l_RequiredCharacteristics) != l_RequiredCharacteristics) 109 | return; 110 | 111 | m_Device = p_Device; 112 | IsIndexKnuckle = m_Device.Value.name.ToLower().Contains("knuckles"); 113 | 114 | if (IsIndexKnuckle) 115 | StartCoroutine(Coroutine_OpenVRHaptics()); 116 | 117 | Plugin.Logger.Debug($"Device found \"{p_Device.manufacturer}\"-\"{p_Device.name}\" with role \"{p_Device.characteristics}\" serial {p_Device.serialNumber} is index knuckle? {IsIndexKnuckle}"); 118 | } 119 | /// 120 | /// On XR device disconnected 121 | /// 122 | /// Disconnected device 123 | private void InputDevices_deviceDisconnected(UnityEngine.XR.InputDevice p_Device) 124 | { 125 | /// Check for matching role 126 | if (!m_Device.HasValue || m_Device != p_Device) 127 | return; 128 | 129 | m_Device = null; 130 | StopAllCoroutines(); 131 | 132 | Plugin.Logger.Debug($"[OPVR.VRController] Device lost \"{p_Device.manufacturer}\"-\"{p_Device.name}\" with role \"{p_Device.characteristics}\""); 133 | } 134 | 135 | //////////////////////////////////////////////////////////////////////////// 136 | //////////////////////////////////////////////////////////////////////////// 137 | 138 | /// 139 | /// Handles the haptic process every 1/80 second. 140 | /// 141 | /// 142 | private IEnumerator Coroutine_OpenVRHaptics() 143 | { 144 | const float s_Rate = 1 / 80f; 145 | 146 | var l_Waiter = new WaitForSecondsRealtime(s_Rate); 147 | while (true) 148 | { 149 | m_OpenVREmulatedHapticRemainingTime -= s_Rate; 150 | 151 | if (m_OpenVREmulatedHapticRemainingTime > 0f) 152 | m_Device?.SendHapticImpulse(0, m_OpenVREmulatedHapticAmplitude); 153 | 154 | yield return l_Waiter; 155 | } 156 | } 157 | } 158 | } 159 | -------------------------------------------------------------------------------- /BSIndexHapticFix/Directory.Build.props: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | true 6 | true 7 | true 8 | 9 | 10 | false 11 | true 12 | true 13 | 14 | -------------------------------------------------------------------------------- /BSIndexHapticFix/Directory.Build.targets: -------------------------------------------------------------------------------- 1 |  2 | 4 | 5 | 6 | 2.0 7 | 8 | false 9 | 10 | $(OutputPath)$(AssemblyName) 11 | 12 | $(OutputPath)Final 13 | True 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | $(AssemblyName) 36 | $(ArtifactName)-$(PluginVersion) 37 | $(ArtifactName)-bs$(GameVersion) 38 | $(ArtifactName)-$(CommitHash) 39 | 40 | 41 | 42 | 43 | 44 | 45 | $(AssemblyName) 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | $(AssemblyName) 60 | $(OutDir)zip\ 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | $(BeatSaberDir)\Plugins 74 | True 75 | Unable to copy assembly to game folder, did you set 'BeatSaberDir' correctly in your 'csproj.user' file? Plugins folder doesn't exist: '$(PluginDir)'. 76 | 77 | Unable to copy to Plugins folder, '$(BeatSaberDir)' does not appear to be a Beat Saber game install. 78 | 79 | Unable to copy to Plugins folder, 'BeatSaberDir' has not been set in your 'csproj.user' file. 80 | False 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | $(BeatSaberDir)\IPA\Pending\Plugins 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | -------------------------------------------------------------------------------- /BSIndexHapticFix/HarmonyPatches/POpenVRHelper_TriggerHapticPulse.cs: -------------------------------------------------------------------------------- 1 | using HarmonyLib; 2 | using UnityEngine.XR; 3 | 4 | namespace BSIndexHapticFix.HarmonyPatches 5 | { 6 | [HarmonyPatch(typeof(OpenVRHelper))] 7 | [HarmonyPatch(nameof(OpenVRHelper.TriggerHapticPulse))] 8 | internal class POpenVRHelper_TriggerHapticPulse 9 | { 10 | static bool Prefix(XRNode node, float duration, float strength, float frequency) 11 | { 12 | if (node == XRNode.LeftHand && Plugin.LeftHapticEmulator && Plugin.LeftHapticEmulator.IsIndexKnuckle) 13 | { 14 | Plugin.LeftHapticEmulator.SetHaptics(strength, duration); 15 | 16 | /// Skip original method 17 | return false; 18 | } 19 | 20 | if (node == XRNode.RightHand && Plugin.RightHapticEmulator && Plugin.RightHapticEmulator.IsIndexKnuckle) 21 | { 22 | Plugin.RightHapticEmulator.SetHaptics(strength, duration); 23 | 24 | /// Skip original method 25 | return false; 26 | } 27 | 28 | /// Forward to base method 29 | return true; 30 | } 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /BSIndexHapticFix/Plugin.cs: -------------------------------------------------------------------------------- 1 | using HarmonyLib; 2 | using IPA; 3 | using System.Reflection; 4 | using UnityEngine; 5 | 6 | namespace BSIndexHapticFix 7 | { 8 | /// 9 | /// Plugin main class 10 | /// 11 | [Plugin(RuntimeOptions.SingleStartInit)] 12 | public class Plugin 13 | { 14 | /// 15 | /// Logger instance 16 | /// 17 | internal static IPA.Logging.Logger Logger { get; private set; } 18 | /// 19 | /// Harmony ID for patches 20 | /// 21 | internal const string HarmonyID = "com.github.hardcpp.bsindexhapticfix"; 22 | /// 23 | /// Left controller haptic emulator 24 | /// 25 | internal static Components.HapticEmulator LeftHapticEmulator; 26 | /// 27 | /// Right controller haptic emulator 28 | /// 29 | internal static Components.HapticEmulator RightHapticEmulator; 30 | 31 | //////////////////////////////////////////////////////////////////////////// 32 | //////////////////////////////////////////////////////////////////////////// 33 | 34 | /// 35 | /// Harmony patch holder 36 | /// 37 | private static Harmony m_Harmony; 38 | 39 | //////////////////////////////////////////////////////////////////////////// 40 | //////////////////////////////////////////////////////////////////////////// 41 | 42 | /// 43 | /// Called when the plugin is first loaded by IPA (either when the game starts or when the plugin is enabled if it starts disabled). 44 | /// [Init] methods that use a Constructor or called before regular methods like InitWithConfig. 45 | /// Only use [Init] with one Constructor. 46 | /// 47 | [Init] 48 | public void Init(IPA.Logging.Logger p_Logger) 49 | { 50 | Logger = p_Logger; 51 | 52 | Logger.Info("BSIndexHapticFix initialized."); 53 | } 54 | 55 | //////////////////////////////////////////////////////////////////////////// 56 | //////////////////////////////////////////////////////////////////////////// 57 | 58 | /// 59 | /// On application start 60 | /// 61 | [OnStart] 62 | public void OnApplicationStart() 63 | { 64 | try 65 | { 66 | Logger.Debug("OnApplicationStart"); 67 | 68 | m_Harmony = new Harmony(HarmonyID); 69 | m_Harmony.PatchAll(Assembly.GetExecutingAssembly()); 70 | 71 | LeftHapticEmulator = new GameObject("BSIndexHapticFix_Left").AddComponent(); 72 | LeftHapticEmulator.Init(UnityEngine.XR.XRNode.LeftHand); 73 | 74 | RightHapticEmulator = new GameObject("BSIndexHapticFix_Right").AddComponent(); 75 | RightHapticEmulator.Init(UnityEngine.XR.XRNode.RightHand); 76 | } 77 | catch (System.Exception p_Exception) 78 | { 79 | Logger.Critical(p_Exception); 80 | } 81 | } 82 | /// 83 | /// On application quit 84 | /// 85 | [OnExit] 86 | public void OnApplicationQuit() 87 | { 88 | try 89 | { 90 | Logger.Debug("OnApplicationQuit"); 91 | 92 | m_Harmony.UnpatchAll(HarmonyID); 93 | 94 | GameObject.Destroy(LeftHapticEmulator); 95 | GameObject.Destroy(RightHapticEmulator); 96 | } 97 | catch (System.Exception p_Exception) 98 | { 99 | Logger.Critical(p_Exception); 100 | } 101 | } 102 | } 103 | } 104 | -------------------------------------------------------------------------------- /BSIndexHapticFix/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | using System.Runtime.CompilerServices; 3 | using System.Runtime.InteropServices; 4 | 5 | // General Information about an assembly is controlled through the following 6 | // set of attributes. Change these attribute values to modify the information 7 | // associated with an assembly. 8 | [assembly: AssemblyTitle("BSIndexHapticFix")] 9 | [assembly: AssemblyDescription("")] 10 | [assembly: AssemblyConfiguration("")] 11 | [assembly: AssemblyCompany("")] 12 | [assembly: AssemblyProduct("BSIndexHapticFix")] 13 | [assembly: AssemblyCopyright("Copyright © 2021")] 14 | [assembly: AssemblyTrademark("")] 15 | [assembly: AssemblyCulture("")] 16 | 17 | // Setting ComVisible to false makes the types in this assembly not visible 18 | // to COM components. If you need to access a type in this assembly from 19 | // COM, set the ComVisible attribute to true on that type. 20 | [assembly: ComVisible(false)] 21 | 22 | // The following GUID is for the ID of the typelib if this project is exposed to COM 23 | [assembly: Guid("6bf8f645-643e-4239-8eda-38337bb12868")] 24 | 25 | // Version information for an assembly consists of the following four values: 26 | // 27 | // Major Version 28 | // Minor Version 29 | // Build Number 30 | // Revision 31 | // 32 | // You can specify all the values or you can default the Build and Revision Numbers 33 | // by using the '*' as shown below: 34 | // [assembly: AssemblyVersion("1.0.*")] 35 | [assembly: AssemblyVersion("0.0.2")] 36 | [assembly: AssemblyFileVersion("0.0.2")] 37 | -------------------------------------------------------------------------------- /BSIndexHapticFix/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://raw.githubusercontent.com/bsmg/BSIPA-MetadataFileSchema/master/Schema.json", 3 | "id": "BSIndexHapticFix", 4 | "name": "BSIndexHapticFix", 5 | "author": "HardCPP#1985", 6 | "version": "0.0.2", 7 | "description": "A simple mod that fix index users haptics on knuckles", 8 | "gameVersion": "1.18.0", 9 | "dependsOn": { 10 | "BSIPA": "^4.0.5" 11 | }, 12 | "features": [] 13 | } -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 HardCPP 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # BSIndexHapticFix 2 | Fix haptics on index knucles 3 | --------------------------------------------------------------------------------