using System;
using System.CodeDom.Compiler;
using System.Collections.Generic;
using System.Configuration;
using System.Diagnostics;
using System.IO;
using System.Net.Sockets;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Security;
using System.Security.Permissions;
using System.Text.RegularExpressions;
using BepInEx;
using BepInEx.Configuration;
using BepInEx.Logging;
using FistVR;
using HarmonyLib;
using Microsoft.CodeAnalysis;
using Microsoft.Win32;
using PSVR2Toolkit;
using UnityEngine;
[assembly: CompilationRelaxations(8)]
[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
[assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)]
[assembly: AssemblyCompany("Niko666")]
[assembly: AssemblyConfiguration("Release")]
[assembly: AssemblyDescription("Implemented some Adaptive Trigger and Headset Rumble effects for chad PSVR2 enjoyers. REQUIRES PSVR2 TOOLKIT TO USE!")]
[assembly: AssemblyFileVersion("2.1.0.0")]
[assembly: AssemblyInformationalVersion("2.1.0")]
[assembly: AssemblyProduct("Niko666.Adaptive_Trigger_For_PSVR2")]
[assembly: AssemblyTitle("Adaptive_Trigger_For_PSVR2")]
[assembly: SecurityPermission(SecurityAction.RequestMinimum, SkipVerification = true)]
[assembly: AssemblyVersion("2.1.0.0")]
[module: UnverifiableCode]
[module: RefSafetyRules(11)]
namespace Microsoft.CodeAnalysis
{
[CompilerGenerated]
[Embedded]
internal sealed class EmbeddedAttribute : Attribute
{
}
}
namespace System.Runtime.CompilerServices
{
[CompilerGenerated]
[Embedded]
[AttributeUsage(AttributeTargets.Module, AllowMultiple = false, Inherited = false)]
internal sealed class RefSafetyRulesAttribute : Attribute
{
public readonly int Version;
public RefSafetyRulesAttribute(int P_0)
{
Version = P_0;
}
}
}
namespace BepInEx
{
[AttributeUsage(AttributeTargets.Class, Inherited = false, AllowMultiple = false)]
[Conditional("CodeGeneration")]
internal sealed class BepInAutoPluginAttribute : Attribute
{
public BepInAutoPluginAttribute(string id = null, string name = null, string version = null)
{
}
}
}
namespace BepInEx.Preloader.Core.Patching
{
[AttributeUsage(AttributeTargets.Class, Inherited = false, AllowMultiple = false)]
[Conditional("CodeGeneration")]
internal sealed class PatcherAutoPluginAttribute : Attribute
{
public PatcherAutoPluginAttribute(string id = null, string name = null, string version = null)
{
}
}
}
namespace PSVR2Toolkit
{
public enum VRControllerType : byte
{
Left,
Right,
Both
}
public enum TriggerEffectKind
{
Off,
Feedback,
Weapon,
Vibration,
MultiplePositionFeedback,
SlopeFeedback,
MultiplePositionVibration
}
public struct TriggerEffectSpec : IEquatable<TriggerEffectSpec>
{
public TriggerEffectKind Kind;
public byte A;
public byte B;
public byte C;
public byte D;
public bool IsOff => Kind == TriggerEffectKind.Off;
public static TriggerEffectSpec Off()
{
return new TriggerEffectSpec
{
Kind = TriggerEffectKind.Off
};
}
public static TriggerEffectSpec Feedback(byte position, byte strength)
{
return new TriggerEffectSpec
{
Kind = TriggerEffectKind.Feedback,
A = position,
B = strength
};
}
public static TriggerEffectSpec Slope(byte startPosition, byte endPosition, byte startStrength, byte endStrength)
{
return new TriggerEffectSpec
{
Kind = TriggerEffectKind.SlopeFeedback,
A = startPosition,
B = endPosition,
C = startStrength,
D = endStrength
};
}
public static TriggerEffectSpec Vibration(byte position, byte amplitude, byte frequency)
{
return new TriggerEffectSpec
{
Kind = TriggerEffectKind.Vibration,
A = position,
B = amplitude,
C = frequency
};
}
public bool Equals(TriggerEffectSpec other)
{
if (Kind == other.Kind && A == other.A && B == other.B && C == other.C)
{
return D == other.D;
}
return false;
}
public override bool Equals(object obj)
{
if (obj is TriggerEffectSpec)
{
return Equals((TriggerEffectSpec)obj);
}
return false;
}
public override int GetHashCode()
{
return ((int)Kind * 397) ^ (A | (B << 8) | (C << 16) | (D << 24));
}
public override string ToString()
{
return Kind.ToString() + "(" + A + "," + B + "," + C + "," + D + ")";
}
}
[StructLayout(LayoutKind.Explicit, Size = 48)]
public struct ScePadTriggerEffectCommandData
{
[FieldOffset(0)]
public byte feedbackPosition;
[FieldOffset(1)]
public byte feedbackStrength;
[FieldOffset(0)]
public byte weaponStartPosition;
[FieldOffset(1)]
public byte weaponEndPosition;
[FieldOffset(2)]
public byte weaponStrength;
[FieldOffset(0)]
public byte vibrationPosition;
[FieldOffset(1)]
public byte vibrationAmplitude;
[FieldOffset(2)]
public byte vibrationFrequency;
[FieldOffset(0)]
public byte slopeStartPosition;
[FieldOffset(1)]
public byte slopeEndPosition;
[FieldOffset(2)]
public byte slopeStartStrength;
[FieldOffset(3)]
public byte slopeEndStrength;
[FieldOffset(0)]
public byte multiplePositionFeedbackStrength0;
[FieldOffset(1)]
public byte multiplePositionFeedbackStrength1;
[FieldOffset(2)]
public byte multiplePositionFeedbackStrength2;
[FieldOffset(3)]
public byte multiplePositionFeedbackStrength3;
[FieldOffset(4)]
public byte multiplePositionFeedbackStrength4;
[FieldOffset(5)]
public byte multiplePositionFeedbackStrength5;
[FieldOffset(6)]
public byte multiplePositionFeedbackStrength6;
[FieldOffset(7)]
public byte multiplePositionFeedbackStrength7;
[FieldOffset(8)]
public byte multiplePositionFeedbackStrength8;
[FieldOffset(9)]
public byte multiplePositionFeedbackStrength9;
[FieldOffset(0)]
public byte multiplePositionVibrationFrequency;
[FieldOffset(1)]
public byte multiplePositionVibrationAmplitude0;
[FieldOffset(2)]
public byte multiplePositionVibrationAmplitude1;
[FieldOffset(3)]
public byte multiplePositionVibrationAmplitude2;
[FieldOffset(4)]
public byte multiplePositionVibrationAmplitude3;
[FieldOffset(5)]
public byte multiplePositionVibrationAmplitude4;
[FieldOffset(6)]
public byte multiplePositionVibrationAmplitude5;
[FieldOffset(7)]
public byte multiplePositionVibrationAmplitude6;
[FieldOffset(8)]
public byte multiplePositionVibrationAmplitude7;
[FieldOffset(9)]
public byte multiplePositionVibrationAmplitude8;
[FieldOffset(10)]
public byte multiplePositionVibrationAmplitude9;
}
[StructLayout(LayoutKind.Sequential, Pack = 4)]
public struct ScePadTriggerEffectCommand
{
public int mode;
public uint padding;
public ScePadTriggerEffectCommandData commandData;
}
public interface IPSVR2ToolkitBackend
{
string Name { get; }
bool SupportsHmdRumble { get; }
bool Connected { get; }
bool Init();
void Shutdown();
void SetTriggerEffect(VRControllerType controller, TriggerEffectSpec effect);
void SetHmdRumble(byte rumbleHz);
void Tick();
}
internal sealed class CapiBackend : IPSVR2ToolkitBackend
{
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
private delegate int InitDelegate();
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
private delegate void DeinitDelegate();
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
[return: MarshalAs(UnmanagedType.I1)]
private delegate bool GetDriverActiveDelegate();
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
private delegate int SetTriggerEffectDelegate(VRControllerType controllerType, ref ScePadTriggerEffectCommand command);
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
private delegate int SetHmdRumbleDelegate(byte rumbleHz);
private const string CapiFileName = "psvr2_toolkit_capi.dll";
private const string PathFileName = "psvr2tk_capi_path.txt";
private const uint LOAD_WITH_ALTERED_SEARCH_PATH = 8u;
private const int RESULT_OK = 0;
private const int RESULT_DRIVER_INACTIVE = -1;
private const int RESULT_NO_SLOT = -2;
private readonly ManualLogSource _log;
private IntPtr _module;
private InitDelegate _init;
private DeinitDelegate _deinit;
private GetDriverActiveDelegate _getDriverActive;
private SetTriggerEffectDelegate _setTriggerEffect;
private SetHmdRumbleDelegate _setHmdRumble;
private bool _connected;
private bool _driverActiveLogged;
private bool _driverInactiveReported;
private bool _errorReported;
public string Name => "CAPI";
public bool SupportsHmdRumble => _setHmdRumble != null;
public bool Connected => _connected;
public string LoadedPath { get; private set; }
[DllImport("kernel32.dll", CharSet = CharSet.Unicode, SetLastError = true)]
private static extern IntPtr LoadLibraryExW(string lpFileName, IntPtr hFile, uint dwFlags);
[DllImport("kernel32.dll", CharSet = CharSet.Ansi, ExactSpelling = true, SetLastError = true)]
private static extern IntPtr GetProcAddress(IntPtr hModule, string lpProcName);
[DllImport("kernel32.dll", SetLastError = true)]
[return: MarshalAs(UnmanagedType.Bool)]
private static extern bool FreeLibrary(IntPtr hModule);
private CapiBackend(ManualLogSource log)
{
_log = log;
}
public static CapiBackend TryCreate(ManualLogSource log)
{
CapiBackend capiBackend = new CapiBackend(log);
string dllPath;
try
{
if (!capiBackend.FindCapiLibrary(out dllPath, out var searched))
{
log.LogWarning((object)("[CAPI] Could not find psvr2_toolkit_capi.dll. Searched: " + searched));
return null;
}
}
catch (Exception ex)
{
log.LogError((object)("[CAPI] Library discovery failed: " + ex));
return null;
}
capiBackend._module = LoadLibraryExW(dllPath, IntPtr.Zero, 8u);
if (capiBackend._module == IntPtr.Zero)
{
log.LogError((object)("[CAPI] LoadLibraryEx failed for '" + dllPath + "' (Win32 error " + Marshal.GetLastWin32Error() + ")."));
return null;
}
capiBackend.LoadedPath = dllPath;
capiBackend._init = capiBackend.Bind<InitDelegate>("psvr2_toolkit_init");
capiBackend._deinit = capiBackend.Bind<DeinitDelegate>("psvr2_toolkit_deinit");
capiBackend._setTriggerEffect = capiBackend.Bind<SetTriggerEffectDelegate>("psvr2_toolkit_set_trigger_effect");
if (capiBackend._init == null || capiBackend._deinit == null || capiBackend._setTriggerEffect == null)
{
capiBackend.Shutdown();
log.LogError((object)("[CAPI] '" + dllPath + "' is missing required exports (init/deinit/set_trigger_effect)."));
return null;
}
capiBackend._getDriverActive = capiBackend.Bind<GetDriverActiveDelegate>("psvr2_toolkit_get_driver_active");
capiBackend._setHmdRumble = capiBackend.Bind<SetHmdRumbleDelegate>("psvr2_toolkit_set_hmd_rumble");
if (capiBackend._setHmdRumble == null)
{
log.LogWarning((object)"[CAPI] This psvr2_toolkit_capi.dll has no headset rumble export - headset vibration will be unavailable.");
}
return capiBackend;
}
public bool Init()
{
int num = _init();
_connected = true;
switch (num)
{
case 0:
_log.LogInfo((object)("[CAPI] Initialized using '" + LoadedPath + "'."));
return true;
case -2:
_connected = false;
_log.LogWarning((object)"[CAPI] psvr2_toolkit_init() returned -2 (no free CAPI slot).");
return false;
default:
_log.LogWarning((object)("[CAPI] psvr2_toolkit_init() returned " + num + "; the PSVR2 driver is probably not running yet, effects will start working once it is."));
return true;
}
}
public void Shutdown()
{
if (_module != IntPtr.Zero)
{
if (_connected && _deinit != null)
{
try
{
_deinit();
}
catch (Exception ex)
{
_log.LogWarning((object)("[CAPI] deinit failed: " + ex.Message));
}
}
FreeLibrary(_module);
_module = IntPtr.Zero;
}
_connected = false;
_init = null;
_deinit = null;
_getDriverActive = null;
_setTriggerEffect = null;
_setHmdRumble = null;
}
public void SetTriggerEffect(VRControllerType controller, TriggerEffectSpec effect)
{
if (!_connected || _setTriggerEffect == null)
{
return;
}
ScePadTriggerEffectCommand command = new ScePadTriggerEffectCommand
{
mode = (int)effect.Kind
};
switch (effect.Kind)
{
case TriggerEffectKind.Feedback:
command.commandData.feedbackPosition = effect.A;
command.commandData.feedbackStrength = effect.B;
break;
case TriggerEffectKind.Weapon:
command.commandData.weaponStartPosition = effect.A;
command.commandData.weaponEndPosition = effect.B;
command.commandData.weaponStrength = effect.C;
break;
case TriggerEffectKind.Vibration:
command.commandData.vibrationPosition = effect.A;
command.commandData.vibrationAmplitude = effect.B;
command.commandData.vibrationFrequency = effect.C;
break;
case TriggerEffectKind.SlopeFeedback:
command.commandData.slopeStartPosition = effect.A;
command.commandData.slopeEndPosition = effect.B;
command.commandData.slopeStartStrength = effect.C;
command.commandData.slopeEndStrength = effect.D;
break;
case TriggerEffectKind.MultiplePositionFeedback:
command.commandData.multiplePositionFeedbackStrength0 = effect.A;
command.commandData.multiplePositionFeedbackStrength1 = effect.B;
command.commandData.multiplePositionFeedbackStrength2 = effect.C;
command.commandData.multiplePositionFeedbackStrength3 = effect.D;
break;
}
int num = _setTriggerEffect(controller, ref command);
switch (num)
{
case -1:
if (!_driverInactiveReported)
{
_driverInactiveReported = true;
_log.LogWarning((object)"[CAPI] The PSVR2 driver reports no active device - effects will be applied again once the headset is up.");
}
break;
default:
if (!_errorReported)
{
_errorReported = true;
_log.LogWarning((object)("[CAPI] psvr2_toolkit_set_trigger_effect returned " + num + "."));
}
break;
case 0:
break;
}
}
public void SetHmdRumble(byte rumbleHz)
{
if (_connected && _setHmdRumble != null)
{
if (rumbleHz > 25)
{
rumbleHz = 25;
}
_setHmdRumble(rumbleHz);
}
}
public void Tick()
{
if (_connected && _getDriverActive != null && !_driverActiveLogged)
{
bool num = _getDriverActive();
_driverActiveLogged = true;
if (!num)
{
_log.LogWarning((object)"[CAPI] Connected, but the PSVR2 driver is not active yet (start SteamVR / your headset).");
}
}
}
public bool IsDriverActive()
{
if (!_connected || _getDriverActive == null)
{
return false;
}
try
{
return _getDriverActive();
}
catch
{
return false;
}
}
private T Bind<T>(string exportName) where T : class
{
IntPtr procAddress = GetProcAddress(_module, exportName);
if (procAddress == IntPtr.Zero)
{
return null;
}
return (T)(object)Marshal.GetDelegateForFunctionPointer(procAddress, typeof(T));
}
private bool FindCapiLibrary(out string dllPath, out string searched)
{
List<string> list = new List<string>();
List<string> list2 = new List<string>();
try
{
foreach (string pathFileDirectory in GetPathFileDirectories(list2))
{
AddCandidate(list, pathFileDirectory);
}
}
catch (Exception ex)
{
list2.Add("psvr2tk_capi_path.txt scan failed: " + ex.Message);
}
try
{
foreach (string steamDirectory in GetSteamDirectories(list2))
{
AddCandidate(list, steamDirectory);
}
}
catch (Exception ex2)
{
list2.Add("Steam library scan failed: " + ex2.Message);
}
string baseDirectory = GetBaseDirectory(list2);
AddCandidate(list, baseDirectory);
AddCandidate(list, TryGetCurrentDirectory(list2));
if (!string.IsNullOrEmpty(baseDirectory))
{
AddCandidate(list, Path.Combine(baseDirectory, "plugins"));
}
HashSet<string> hashSet = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
foreach (string item in list)
{
if (string.IsNullOrEmpty(item))
{
continue;
}
string fullPath;
try
{
fullPath = Path.GetFullPath(item.Trim());
}
catch
{
continue;
}
if (!string.IsNullOrEmpty(fullPath) && hashSet.Add(fullPath))
{
string text = Path.Combine(fullPath, "psvr2_toolkit_capi.dll");
if (File.Exists(text))
{
dllPath = text;
searched = fullPath;
return true;
}
}
}
dllPath = null;
searched = string.Join("; ", list2.ToArray());
return false;
}
private static void AddCandidate(List<string> candidates, string directory)
{
if (!string.IsNullOrEmpty(directory))
{
candidates.Add(directory);
}
}
private static string GetBaseDirectory(List<string> notes)
{
try
{
string location = Assembly.GetExecutingAssembly().Location;
if (!string.IsNullOrEmpty(location))
{
string directoryName = Path.GetDirectoryName(location);
if (!string.IsNullOrEmpty(directoryName))
{
return directoryName;
}
}
}
catch (Exception ex)
{
notes.Add("plugin location unavailable: " + ex.Message);
}
try
{
string baseDirectory = AppDomain.CurrentDomain.BaseDirectory;
if (!string.IsNullOrEmpty(baseDirectory))
{
return baseDirectory;
}
}
catch
{
}
return null;
}
private static string TryGetCurrentDirectory(List<string> notes)
{
try
{
return Environment.CurrentDirectory;
}
catch (Exception ex)
{
notes.Add("working directory unavailable: " + ex.Message);
return null;
}
}
private static IEnumerable<string> GetPathFileDirectories(List<string> notes)
{
foreach (string tempFolder in GetTempFolders())
{
string text = Path.Combine(tempFolder, "psvr2tk_capi_path.txt");
string text2 = null;
try
{
if (File.Exists(text))
{
text2 = File.ReadAllText(text);
}
}
catch (Exception ex)
{
notes.Add(text + " (unreadable: " + ex.Message + ")");
continue;
}
if (string.IsNullOrEmpty(text2))
{
notes.Add(text + " (missing)");
continue;
}
string text3 = null;
string[] array = text2.Split(new char[1] { '\n' });
for (int i = 0; i < array.Length; i++)
{
string text4 = array[i].Trim().Trim('"', '\'', '\ufeff', '\r');
if (text4.Length > 0)
{
text3 = text4;
break;
}
}
if (string.IsNullOrEmpty(text3))
{
notes.Add(text + " (empty)");
continue;
}
notes.Add(text + " -> " + text3);
yield return text3;
}
}
private static IEnumerable<string> GetTempFolders()
{
List<string> list = new List<string>();
AddTemp(list, SafeGetTempPath());
AddTemp(list, GetEnvironmentVariableSafe("TMP"));
AddTemp(list, GetEnvironmentVariableSafe("TEMP"));
string environmentVariableSafe = GetEnvironmentVariableSafe("USERPROFILE");
if (!string.IsNullOrEmpty(environmentVariableSafe))
{
AddTemp(list, Path.Combine(Path.Combine(environmentVariableSafe, "AppData"), Path.Combine("Local", "Temp")));
}
string environmentVariableSafe2 = GetEnvironmentVariableSafe("WINDIR");
if (!string.IsNullOrEmpty(environmentVariableSafe2))
{
AddTemp(list, Path.Combine(environmentVariableSafe2, "Temp"));
}
return list;
}
private static string GetEnvironmentVariableSafe(string name)
{
try
{
return Environment.GetEnvironmentVariable(name);
}
catch
{
return null;
}
}
private static void AddTemp(List<string> folders, string folder)
{
if (string.IsNullOrEmpty(folder))
{
return;
}
foreach (string folder2 in folders)
{
if (string.Equals(folder2, folder, StringComparison.OrdinalIgnoreCase))
{
return;
}
}
folders.Add(folder);
}
private static string SafeGetTempPath()
{
try
{
return Path.GetTempPath();
}
catch
{
return null;
}
}
private static IEnumerable<string> GetSteamDirectories(List<string> notes)
{
List<string> list = new List<string>();
string text = ReadRegistryString("HKEY_CURRENT_USER\\Software\\Valve\\Steam", "SteamPath");
if (!string.IsNullOrEmpty(text))
{
list.Add(text);
}
string text2 = ReadRegistryString("HKEY_LOCAL_MACHINE\\SOFTWARE\\WOW6432Node\\Valve\\Steam", "InstallPath");
if (!string.IsNullOrEmpty(text2))
{
list.Add(text2);
}
text2 = ReadRegistryString("HKEY_LOCAL_MACHINE\\SOFTWARE\\Valve\\Steam", "InstallPath");
if (!string.IsNullOrEmpty(text2))
{
list.Add(text2);
}
list.Add("C:\\Program Files (x86)\\Steam");
HashSet<string> libraries = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
foreach (string item in list)
{
string cleanRoot;
try
{
cleanRoot = Path.GetFullPath(item.Replace('/', '\\'));
}
catch
{
continue;
}
if (libraries.Add(cleanRoot))
{
yield return Path.Combine(cleanRoot, "steamapps\\common\\PlayStation VR2 App\\SteamVR_Plug-In\\bin\\win64");
}
string text3 = Path.Combine(Path.Combine(cleanRoot, "steamapps"), "libraryfolders.vdf");
if (!File.Exists(text3))
{
continue;
}
string[] libraryPaths = ParseLibraryFolders(text3, notes);
foreach (string text4 in libraryPaths)
{
if (libraries.Add(text4))
{
yield return Path.Combine(text4, "steamapps\\common\\PlayStation VR2 App\\SteamVR_Plug-In\\bin\\win64");
}
}
}
}
private static string[] ParseLibraryFolders(string libraryFile, List<string> notes)
{
List<string> list = new List<string>();
try
{
foreach (Match item in Regex.Matches(File.ReadAllText(libraryFile), "\"path\"\\s+\"([^\"]+)\"", RegexOptions.IgnoreCase))
{
string text = item.Groups[1].Value.Replace("\\\\", "\\").Replace('/', '\\');
if (!string.IsNullOrEmpty(text))
{
list.Add(text);
}
}
}
catch (Exception ex)
{
notes.Add(libraryFile + " (unreadable: " + ex.Message + ")");
}
return list.ToArray();
}
private static string ReadRegistryString(string keyPath, string valueName)
{
try
{
if (keyPath.StartsWith("HKEY_CURRENT_USER", StringComparison.OrdinalIgnoreCase))
{
using RegistryKey registryKey = Registry.CurrentUser.OpenSubKey(keyPath.Substring("HKEY_CURRENT_USER\\".Length));
return (registryKey == null) ? null : (registryKey.GetValue(valueName) as string);
}
if (keyPath.StartsWith("HKEY_LOCAL_MACHINE", StringComparison.OrdinalIgnoreCase))
{
using (RegistryKey registryKey2 = Registry.LocalMachine.OpenSubKey(keyPath.Substring("HKEY_LOCAL_MACHINE\\".Length)))
{
return (registryKey2 == null) ? null : (registryKey2.GetValue(valueName) as string);
}
}
}
catch
{
}
return null;
}
}
internal sealed class IpcBackend : IPSVR2ToolkitBackend
{
private const int ServerPort = 3364;
private const ushort ClientIpcVersion = 3;
private const int CmdClientRequestHandshake = 2;
private const int CmdServerHandshakeResult = 3;
private const int CmdClientTriggerEffectOff = 6;
private const int CmdClientTriggerEffectFeedback = 7;
private const int CmdClientTriggerEffectWeapon = 8;
private const int CmdClientTriggerEffectVibration = 9;
private const int CmdClientTriggerEffectMultiplePositionFeedback = 10;
private const int CmdClientTriggerEffectSlopeFeedback = 11;
private const int CmdClientTriggerEffectMultiplePositionVibration = 12;
private const int HandshakeFailed = 0;
private const int HandshakeSuccess = 1;
private const int HandshakeOutdated = 2;
private readonly ManualLogSource _log;
private readonly object _sendLock = new object();
private TcpClient _client;
private NetworkStream _stream;
private bool _connected;
private int _nextReconnectAttempt;
private ushort _negotiatedVersion = 3;
private bool _loggedProtocolHint;
public string Name => "legacy IPC";
public bool SupportsHmdRumble => false;
public bool Connected => _connected;
private IpcBackend(ManualLogSource log)
{
_log = log;
}
public static IpcBackend TryCreate(ManualLogSource log)
{
IpcBackend ipcBackend = new IpcBackend(log);
if (!ipcBackend.TryConnect())
{
ipcBackend.DisposeSocket();
return null;
}
return ipcBackend;
}
public bool Init()
{
return _connected;
}
public void Shutdown()
{
_connected = false;
DisposeSocket();
}
public void SetTriggerEffect(VRControllerType controller, TriggerEffectSpec effect)
{
if (_connected)
{
int num;
byte[] array;
switch (effect.Kind)
{
case TriggerEffectKind.Feedback:
num = 7;
array = new byte[3]
{
(byte)controller,
effect.A,
effect.B
};
break;
case TriggerEffectKind.Weapon:
num = 8;
array = new byte[4]
{
(byte)controller,
effect.A,
effect.B,
effect.C
};
break;
case TriggerEffectKind.Vibration:
num = 9;
array = new byte[4]
{
(byte)controller,
effect.A,
effect.B,
effect.C
};
break;
case TriggerEffectKind.SlopeFeedback:
num = 11;
array = new byte[5]
{
(byte)controller,
effect.A,
effect.B,
effect.C,
effect.D
};
break;
case TriggerEffectKind.MultiplePositionFeedback:
num = 10;
array = new byte[11]
{
(byte)controller,
effect.A,
effect.B,
effect.C,
effect.D,
0,
0,
0,
0,
0,
0
};
break;
case TriggerEffectKind.MultiplePositionVibration:
num = 12;
array = new byte[11]
{
(byte)controller,
effect.A,
effect.B,
effect.C,
effect.D,
0,
0,
0,
0,
0,
0
};
break;
default:
num = 6;
array = new byte[1] { (byte)controller };
break;
}
byte[] array2 = new byte[8 + array.Length];
array2[0] = (byte)(num & 0xFF);
array2[1] = (byte)((num >> 8) & 0xFF);
array2[4] = (byte)(array.Length & 0xFF);
array2[5] = (byte)((array.Length >> 8) & 0xFF);
array2[6] = (byte)((array.Length >> 16) & 0xFF);
array2[7] = (byte)((array.Length >> 24) & 0xFF);
Buffer.BlockCopy(array, 0, array2, 8, array.Length);
Send(array2);
}
}
public void SetHmdRumble(byte rumbleHz)
{
if (!_loggedProtocolHint)
{
_loggedProtocolHint = true;
_log.LogInfo((object)"[IPC] The legacy protocol has no headset rumble command - headset vibration stays disabled while the fallback is active.");
}
}
public void Tick()
{
if (_connected)
{
try
{
Socket client = _client.Client;
if (client.Poll(0, SelectMode.SelectRead) && client.Available == 0)
{
_log.LogWarning((object)"[IPC] Connection to the PSVR2 Toolkit IPC server was lost.");
_connected = false;
DisposeSocket();
}
return;
}
catch
{
_connected = false;
DisposeSocket();
return;
}
}
if (Environment.TickCount >= _nextReconnectAttempt)
{
_nextReconnectAttempt = Environment.TickCount + 5000;
if (TryConnect())
{
_log.LogMessage((object)"[IPC] Reconnected to the PSVR2 Toolkit IPC server.");
}
}
}
private bool TryConnect()
{
try
{
_client = new TcpClient();
_client.NoDelay = true;
IAsyncResult asyncResult = _client.BeginConnect("127.0.0.1", 3364, null, null);
if (!asyncResult.AsyncWaitHandle.WaitOne(500))
{
DisposeSocket();
return false;
}
_client.EndConnect(asyncResult);
_stream = _client.GetStream();
_stream.ReadTimeout = 500;
_stream.WriteTimeout = 500;
if (!Handshake(3))
{
DisposeSocket();
return false;
}
_connected = true;
return true;
}
catch (Exception)
{
DisposeSocket();
return false;
}
}
private bool Handshake(ushort version)
{
ushort num5;
for (int i = 0; i < 2; version = num5, i++)
{
byte[] array = new byte[8]
{
(byte)(version & 0xFF),
(byte)((version >> 8) & 0xFF),
0,
0,
0,
0,
0,
0
};
int num = 0;
try
{
num = Process.GetCurrentProcess().Id;
}
catch
{
}
array[4] = (byte)(num & 0xFF);
array[5] = (byte)((num >> 8) & 0xFF);
array[6] = (byte)((num >> 16) & 0xFF);
array[7] = (byte)((num >> 24) & 0xFF);
byte[] array2 = new byte[8 + array.Length];
array2[0] = 2;
array2[1] = 0;
array2[4] = (byte)array.Length;
Buffer.BlockCopy(array, 0, array2, 8, array.Length);
if (!Send(array2))
{
return false;
}
byte[] array3 = new byte[8];
if (!ReadExact(array3, array3.Length))
{
return false;
}
int num2 = array3[0] | (array3[1] << 8);
int num3 = array3[4] | (array3[5] << 8) | (array3[6] << 16) | (array3[7] << 24);
if (num2 != 3 || num3 < 4 || num3 > 64)
{
_log.LogWarning((object)"[IPC] Unexpected handshake response from the PSVR2 Toolkit IPC server.");
return false;
}
byte[] array4 = new byte[num3];
if (!ReadExact(array4, array4.Length))
{
return false;
}
int num4 = array4[0];
num5 = (ushort)(array4[2] | (array4[3] << 8));
switch (num4)
{
case 1:
_negotiatedVersion = num5;
_log.LogMessage((object)("[IPC] Connected to the PSVR2 Toolkit IPC server (protocol v" + num5 + ")."));
return true;
case 2:
if (num5 < version)
{
continue;
}
break;
}
if (num4 == 0)
{
_log.LogWarning((object)"[IPC] The PSVR2 Toolkit IPC server refused the handshake (another client may already be connected on this port).");
return false;
}
return false;
}
return false;
}
private bool Send(byte[] buffer)
{
lock (_sendLock)
{
if (_stream == null)
{
return false;
}
try
{
_stream.Write(buffer, 0, buffer.Length);
return true;
}
catch (Exception)
{
_connected = false;
DisposeSocket();
return false;
}
}
}
private bool ReadExact(byte[] buffer, int count)
{
if (_stream == null)
{
return false;
}
int num;
for (int i = 0; i < count; i += num)
{
try
{
num = _stream.Read(buffer, i, count - i);
}
catch (Exception)
{
return false;
}
if (num <= 0)
{
return false;
}
}
return true;
}
private void DisposeSocket()
{
try
{
if (_stream != null)
{
_stream.Close();
}
}
catch
{
}
try
{
if (_client != null)
{
_client.Close();
}
}
catch
{
}
_stream = null;
_client = null;
}
}
public static class PSVR2ToolkitBridge
{
private static IPSVR2ToolkitBackend _backend;
public static IPSVR2ToolkitBackend Backend => _backend;
public static bool IsReady
{
get
{
if (_backend != null)
{
return _backend.Connected;
}
return false;
}
}
public static bool Initialize(ManualLogSource log)
{
if (_backend != null)
{
return _backend.Connected;
}
try
{
CapiBackend capiBackend = CapiBackend.TryCreate(log);
if (capiBackend != null)
{
if (capiBackend.Init())
{
_backend = capiBackend;
log.LogInfo((object)"[PSVR2] Using the CAPI backend.");
return true;
}
capiBackend.Shutdown();
}
IpcBackend ipcBackend = IpcBackend.TryCreate(log);
if (ipcBackend != null && ipcBackend.Init())
{
_backend = ipcBackend;
log.LogInfo((object)"[PSVR2] Using the legacy IPC backend (PSVR2 Toolkit predates the CAPI). Headset vibration is unavailable on this backend.");
return true;
}
}
catch (Exception ex)
{
log.LogError((object)("[PSVR2] Backend setup failed: " + ex));
Shutdown();
return false;
}
log.LogError((object)"[PSVR2] No usable PSVR2 Toolkit backend found. Install or update PSVR2 Toolkit (adaptive triggers need it).");
return false;
}
public static void Shutdown()
{
if (_backend != null)
{
_backend.Shutdown();
_backend = null;
}
}
public static void SetTriggerEffect(VRControllerType controller, TriggerEffectSpec effect)
{
if (_backend != null)
{
_backend.SetTriggerEffect(controller, effect);
}
}
public static void SetHmdRumble(byte rumbleHz)
{
if (_backend != null && _backend.SupportsHmdRumble)
{
_backend.SetHmdRumble(rumbleHz);
}
}
public static void Tick()
{
if (_backend != null)
{
_backend.Tick();
}
}
}
}
namespace Niko666
{
[BepInProcess("h3vr.exe")]
[BepInPlugin("Niko666.Adaptive_Trigger_For_PSVR2", "Adaptive_Trigger_For_PSVR2", "2.1.0")]
public class AdaptiveTrigger : BaseUnityPlugin
{
public enum HeadsetVibrationType
{
Disabled,
OnHit,
OnRecoil,
Both
}
private struct ConfigSnapshot
{
public VRControllerType Controllers;
public HeadsetVibrationType HmdVibration;
public byte HmdFrequency;
public float HmdShotDecay;
public float HmdHitDecay;
public bool DualStage;
public bool NoEffectWhenEmpty;
public byte Clicky;
public byte RecoilStrength;
public float RecoilDuration;
public bool VibrationRecoil;
public byte VibrationFrequency;
public bool OverridePos;
public byte OverrideStart;
public byte OverrideEnd;
public float RefreshInterval;
}
private sealed class HandState
{
public readonly bool IsRight;
public FVRViveHand Hand;
public FVRFireArm Firearm;
public AttachableFirearm Attachable;
public TriggerEffectSpec LastSent;
public bool HasLastSent;
public float LastSendTime;
public TriggerEffectSpec RecoilSpec;
public float RecoilUntil;
public bool RecoilActive;
public VRControllerType Controller
{
get
{
if (!IsRight)
{
return VRControllerType.Left;
}
return VRControllerType.Right;
}
}
public bool EffectsEnabled
{
get
{
if (_cfg.Controllers != VRControllerType.Both)
{
return _cfg.Controllers == VRControllerType.Right == IsRight;
}
return true;
}
}
public HandState(bool isRight)
{
IsRight = isRight;
}
}
internal static class SceneLoadPatch
{
[HarmonyPatch(typeof(SteamVR_LoadLevel), "Begin")]
[HarmonyPrefix]
public static bool BeginPatch()
{
ReloadConfigFromDisk();
ReleaseAllHands();
_hmdLevel = 0f;
SetHmdRumbleLevel(0f);
_suppressShotEvent = false;
UnsubscribeShotEvent();
return true;
}
}
internal static class HitEffectPatch
{
[HarmonyPatch(typeof(FVRPlayerBody), "HitEffect")]
[HarmonyPostfix]
public static void Postfix()
{
if (_cfg.HmdVibration == HeadsetVibrationType.OnHit || _cfg.HmdVibration == HeadsetVibrationType.Both)
{
AddHmdRumbleImpulse(1f, _cfg.HmdHitDecay);
}
}
}
internal static class AttachableClosedBoltFirePatch
{
[HarmonyPatch(typeof(AttachableClosedBoltWeapon), "Fire", new Type[] { typeof(bool) })]
[HarmonyPrefix]
public static void Prefix()
{
_suppressShotEvent = true;
_suppressShotTime = Time.unscaledTime;
}
[HarmonyPatch(typeof(AttachableClosedBoltWeapon), "Fire", new Type[] { typeof(bool) })]
[HarmonyPostfix]
public static void Postfix(AttachableFirearm __instance)
{
_suppressShotEvent = false;
FireShotFeedback(null, __instance);
}
}
internal static class AttachableTubeFedFirePatch
{
[HarmonyPatch(typeof(AttachableTubeFed), "Fire", new Type[] { typeof(bool) })]
[HarmonyPrefix]
public static void Prefix()
{
_suppressShotEvent = true;
_suppressShotTime = Time.unscaledTime;
}
[HarmonyPatch(typeof(AttachableTubeFed), "Fire", new Type[] { typeof(bool) })]
[HarmonyPostfix]
public static void Postfix(AttachableFirearm __instance)
{
_suppressShotEvent = false;
FireShotFeedback(null, __instance);
}
}
internal static class AttachableBreakActionsFirePatch
{
[HarmonyPatch(typeof(AttachableBreakActions), "Fire", new Type[] { typeof(bool) })]
[HarmonyPrefix]
public static void Prefix()
{
_suppressShotEvent = true;
_suppressShotTime = Time.unscaledTime;
}
[HarmonyPatch(typeof(AttachableBreakActions), "Fire", new Type[] { typeof(bool) })]
[HarmonyPostfix]
public static void Postfix(AttachableFirearm __instance)
{
_suppressShotEvent = false;
FireShotFeedback(null, __instance);
}
}
[CompilerGenerated]
private static class <>O
{
public static ShotFired <0>__OnShotFired;
}
public static ConfigEntry<VRControllerType> ControllerToUse;
public static ConfigEntry<HeadsetVibrationType> HeadsetVibration;
public static ConfigEntry<byte> HeadsetVibrationFrequency;
public static ConfigEntry<float> HeadsetVibrationDecayTime;
public static ConfigEntry<float> HeadsetHitVibrationTime;
public static ConfigEntry<bool> AllowDualStageTriggerEffect;
public static ConfigEntry<bool> DisableEffectWhenEmpty;
public static ConfigEntry<byte> ClickyEffectStrength;
public static ConfigEntry<byte> RecoilFeedbackStrength;
public static ConfigEntry<float> RecoilKickHoldTime;
public static ConfigEntry<bool> UseVibrationFeedbackForRecoil;
public static ConfigEntry<byte> VibrationFrequency;
public static ConfigEntry<bool> OverrideTriggerEffectPos;
public static ConfigEntry<byte> OverrideStartPos;
public static ConfigEntry<byte> OverrideEndPos;
public static ConfigEntry<float> TriggerEffectRefreshInterval;
private static ConfigSnapshot _cfg;
private static readonly HandState _leftHand = new HandState(isRight: false);
private static readonly HandState _rightHand = new HandState(isRight: true);
private static FVRSceneSettings _subscribedSettings;
private static bool _suppressShotEvent;
private static float _suppressShotTime;
private static bool _dualStageUnavailable;
private static float _hmdLevel;
private static float _hmdDecayDuration = 0.4f;
private static int _hmdRumbleSent = -1;
private float _lastBackendTick;
private int _updateErrors;
private static string _configFilePath;
private static long _configFileStamp = -1L;
public const string Id = "Niko666.Adaptive_Trigger_For_PSVR2";
public static AdaptiveTrigger Instance { get; private set; }
internal static ManualLogSource Logger { get; private set; }
public static string Name => "Adaptive_Trigger_For_PSVR2";
public static string Version => "2.1.0";
private void BindConfig()
{
ControllerToUse = ((BaseUnityPlugin)this).Config.Bind<VRControllerType>("General", "ControllerToUse", VRControllerType.Both, "Enable Adaptive Trigger effect on selected controllers only. (Both, Left, Right)");
HeadsetVibration = ((BaseUnityPlugin)this).Config.Bind<HeadsetVibrationType>("General", "HeadsetVibration", HeadsetVibrationType.Both, "Enable headset vibration effect. (Disabled, OnHit, OnRecoil, Both)");
HeadsetVibrationFrequency = ((BaseUnityPlugin)this).Config.Bind<byte>("General", "HeadsetVibrationFrequency", (byte)15, "Headset vibration frequency. (1-25)");
HeadsetVibrationDecayTime = ((BaseUnityPlugin)this).Config.Bind<float>("General", "HeadsetVibrationDecayTime", 0.4f, "How long the headset keeps buzzing after a shot, in seconds. New shots restart it, so rapid fire stays at full strength instead of fading out. (0.05-5)");
HeadsetHitVibrationTime = ((BaseUnityPlugin)this).Config.Bind<float>("General", "HeadsetHitVibrationTime", 3f, "How long the headset buzzes after you take a hit, in seconds. (0.1-10)");
AllowDualStageTriggerEffect = ((BaseUnityPlugin)this).Config.Bind<bool>("General", "AllowDualStageTriggerEffect", true, "Enable dual stage trigger effect. This will attempt to add a little bit of resistance before the each \"stage\" of the trigger. (I have no idea how a real Dual Stage trigger feels so sorry if it doesn't feel right)");
DisableEffectWhenEmpty = ((BaseUnityPlugin)this).Config.Bind<bool>("General", "DisableEffectWhenEmpty", false, "Disable trigger effect when the gun is empty (no round in the chamber, the cylinder or the magazine). Still an approximation for exotic weapons, so expect the occasional lie.");
ClickyEffectStrength = ((BaseUnityPlugin)this).Config.Bind<byte>("General", "ClickyEffectStrength", (byte)4, "Effect strength of clicky trigger effect. Going too high might cause weak recoil effect. (1-8)");
RecoilFeedbackStrength = ((BaseUnityPlugin)this).Config.Bind<byte>("General", "RecoilFeedbackStrength", (byte)8, "Effect strength of firearm recoil effect. Set to 0 to disable the recoil kick entirely. (0-8)");
RecoilKickHoldTime = ((BaseUnityPlugin)this).Config.Bind<float>("General", "RecoilKickHoldTime", 0.02f, "How long (seconds) the recoil kick stays the applied trigger effect before the clicky resistance is restored. Lower values do not work reliably: the two commands land in the same driver update cycle and only the last one is pushed to the controller, so the kick is never (or only sometimes) applied. Clamped to a 0.02 minimum. (0.02-0.5)");
UseVibrationFeedbackForRecoil = ((BaseUnityPlugin)this).Config.Bind<bool>("General", "UseVibrationFeedbackForRecoil", false, "Use vibration-based feedback for recoil effect. By default the mod use force-based feedback to emulate the recoil \"kick\" effect, but it doesn't work well with extremely high RPM weapons when doing full-auto shooting. Turning this option on will make the trigger vibrates instead of kicking, which is more suitable for full-auto shooting but worse the feeling when single-shot. ");
VibrationFrequency = ((BaseUnityPlugin)this).Config.Bind<byte>("General", "VibrationFrequency", (byte)50, "Vibration frequency for recoil effect when 'UseVibrationFeedbackForRecoil' is enabled. (1-255)");
OverrideTriggerEffectPos = ((BaseUnityPlugin)this).Config.Bind<bool>("General", "OverrideTriggerEffectPos", false, "Override trigger effect position with user set values instead of reading from firearm's trigger thresholds. Not recommend but could be useful if you want to. Also this will disable dual stage trigger effect and empty effect.");
OverrideStartPos = ((BaseUnityPlugin)this).Config.Bind<byte>("General", "OverrideStartPos", (byte)2, "Override start position of the trigger effect. (0-9)");
OverrideEndPos = ((BaseUnityPlugin)this).Config.Bind<byte>("General", "OverrideEndPos", (byte)7, "Override end position of the trigger effect. (0-9)");
TriggerEffectRefreshInterval = ((BaseUnityPlugin)this).Config.Bind<float>("General", "TriggerEffectRefreshInterval", 1f, "Trigger effects are only sent to the controller when they change, and re-sent every N seconds as a safety net. Higher = fewer USB packets / better performance, lower = safer against effects getting dropped. (0.1-10)");
AddSettingChangedListener<VRControllerType>(ControllerToUse);
AddSettingChangedListener<HeadsetVibrationType>(HeadsetVibration);
AddSettingChangedListener<byte>(HeadsetVibrationFrequency);
AddSettingChangedListener<float>(HeadsetVibrationDecayTime);
AddSettingChangedListener<float>(HeadsetHitVibrationTime);
AddSettingChangedListener<bool>(AllowDualStageTriggerEffect);
AddSettingChangedListener<bool>(DisableEffectWhenEmpty);
AddSettingChangedListener<byte>(ClickyEffectStrength);
AddSettingChangedListener<byte>(RecoilFeedbackStrength);
AddSettingChangedListener<float>(RecoilKickHoldTime);
AddSettingChangedListener<bool>(UseVibrationFeedbackForRecoil);
AddSettingChangedListener<byte>(VibrationFrequency);
AddSettingChangedListener<bool>(OverrideTriggerEffectPos);
AddSettingChangedListener<byte>(OverrideStartPos);
AddSettingChangedListener<byte>(OverrideEndPos);
AddSettingChangedListener<float>(TriggerEffectRefreshInterval);
}
private static void AddSettingChangedListener<T>(ConfigEntry<T> entry)
{
entry.SettingChanged += delegate
{
ReloadConfigSnapshot();
};
}
private static void ReloadConfigFromDisk()
{
try
{
if ((Object)(object)Instance != (Object)null && ((BaseUnityPlugin)Instance).Config != null)
{
((BaseUnityPlugin)Instance).Config.Reload();
}
}
catch (Exception ex)
{
if (Logger != null)
{
Logger.LogWarning((object)("Config reload failed: " + ex.Message));
}
}
ReloadConfigSnapshot();
RememberConfigFileStamp();
}
private static void InitConfigWatcher()
{
try
{
if ((Object)(object)Instance != (Object)null && ((BaseUnityPlugin)Instance).Config != null)
{
_configFilePath = ((BaseUnityPlugin)Instance).Config.ConfigFilePath;
}
}
catch
{
}
RememberConfigFileStamp();
}
private static void RememberConfigFileStamp()
{
if (string.IsNullOrEmpty(_configFilePath))
{
return;
}
try
{
_configFileStamp = (File.Exists(_configFilePath) ? File.GetLastWriteTimeUtc(_configFilePath).Ticks : (-1));
}
catch
{
_configFileStamp = -1L;
}
}
private static void CheckConfigFileChanged()
{
if (string.IsNullOrEmpty(_configFilePath))
{
return;
}
try
{
if (File.Exists(_configFilePath) && File.GetLastWriteTimeUtc(_configFilePath).Ticks != _configFileStamp)
{
Logger.LogMessage((object)"Config file changed on disk, reloading it.");
ReloadConfigFromDisk();
}
}
catch
{
}
}
private static void ReloadConfigSnapshot()
{
_cfg = new ConfigSnapshot
{
Controllers = ControllerToUse.Value,
HmdVibration = HeadsetVibration.Value,
HmdFrequency = (byte)Mathf.Clamp((int)HeadsetVibrationFrequency.Value, 0, 25),
HmdShotDecay = Mathf.Clamp(HeadsetVibrationDecayTime.Value, 0.05f, 5f),
HmdHitDecay = Mathf.Clamp(HeadsetHitVibrationTime.Value, 0.1f, 10f),
DualStage = AllowDualStageTriggerEffect.Value,
NoEffectWhenEmpty = DisableEffectWhenEmpty.Value,
Clicky = (byte)Mathf.Clamp((int)ClickyEffectStrength.Value, 0, 8),
RecoilStrength = (byte)Mathf.Clamp((int)RecoilFeedbackStrength.Value, 0, 8),
RecoilDuration = Mathf.Clamp(RecoilKickHoldTime.Value, 0f, 0.5f),
VibrationRecoil = UseVibrationFeedbackForRecoil.Value,
VibrationFrequency = VibrationFrequency.Value,
OverridePos = OverrideTriggerEffectPos.Value,
OverrideStart = (byte)Mathf.Clamp((int)OverrideStartPos.Value, 0, 9),
OverrideEnd = (byte)Mathf.Clamp((int)OverrideEndPos.Value, 0, 9),
RefreshInterval = Mathf.Clamp(TriggerEffectRefreshInterval.Value, 0.1f, 10f)
};
}
private static HandState StateFor(FVRViveHand hand)
{
if (!hand.IsThisTheRightHand)
{
return _leftHand;
}
return _rightHand;
}
public void Awake()
{
Instance = this;
Logger = ((BaseUnityPlugin)this).Logger;
BindConfig();
ReloadConfigSnapshot();
InitConfigWatcher();
try
{
if (!PSVR2ToolkitBridge.Initialize(Logger))
{
Logger.LogError((object)"Adaptive Trigger For PSVR2 is inactive: no usable PSVR2 Toolkit backend.\nYou need PSVR2 Toolkit installed (and the PSVR2 driver running) for this mod to do anything.");
return;
}
Logger.LogMessage((object)("Using the " + PSVR2ToolkitBridge.Backend.Name + " backend."));
ApplyPatches();
Logger.LogMessage((object)("Fuk U Sony! Sent from Niko666.Adaptive_Trigger_For_PSVR2 " + Version));
}
catch (Exception ex)
{
Logger.LogError((object)("Adaptive Trigger For PSVR2 failed to start: " + ex));
}
}
public void OnDestroy()
{
ReleaseAllHands();
SetHmdRumbleLevel(0f);
PSVR2ToolkitBridge.SetHmdRumble(0);
PSVR2ToolkitBridge.Shutdown();
UnsubscribeShotEvent();
Logger.LogMessage((object)"PSVR2 Toolkit released. It is now safe to turn off your computer.");
}
public void Update()
{
try
{
float unscaledTime = Time.unscaledTime;
if (unscaledTime - _lastBackendTick >= 1f)
{
_lastBackendTick = unscaledTime;
PSVR2ToolkitBridge.Tick();
EnsureShotEventSubscription();
CheckConfigFileChanged();
}
if (_suppressShotEvent && unscaledTime - _suppressShotTime > 0.25f)
{
_suppressShotEvent = false;
}
if (PSVR2ToolkitBridge.IsReady)
{
UpdateHeadsetRumble();
EvaluateHands(unscaledTime);
}
}
catch (Exception ex)
{
if (_updateErrors < 3)
{
_updateErrors++;
Logger.LogError((object)("Adaptive Trigger update failed: " + ex));
}
}
}
private static void EnsureShotEventSubscription()
{
//IL_0048: Unknown result type (might be due to invalid IL or missing references)
//IL_004d: Unknown result type (might be due to invalid IL or missing references)
//IL_0053: Expected O, but got Unknown
FVRSceneSettings val = null;
try
{
val = GM.CurrentSceneSettings;
}
catch
{
}
if ((Object)(object)val == (Object)(object)_subscribedSettings)
{
return;
}
UnsubscribeShotEvent();
_subscribedSettings = val;
if (!((Object)(object)_subscribedSettings != (Object)null))
{
return;
}
try
{
FVRSceneSettings subscribedSettings = _subscribedSettings;
object obj2 = <>O.<0>__OnShotFired;
if (obj2 == null)
{
ShotFired val2 = OnShotFired;
<>O.<0>__OnShotFired = val2;
obj2 = (object)val2;
}
subscribedSettings.ShotFiredEvent += (ShotFired)obj2;
}
catch (Exception ex)
{
Logger.LogWarning((object)("Failed to subscribe to the shot event: " + ex.Message));
}
}
private static void UnsubscribeShotEvent()
{
//IL_0022: Unknown result type (might be due to invalid IL or missing references)
//IL_0027: Unknown result type (might be due to invalid IL or missing references)
//IL_002d: Expected O, but got Unknown
if (!((Object)(object)_subscribedSettings != (Object)null))
{
return;
}
try
{
FVRSceneSettings subscribedSettings = _subscribedSettings;
object obj = <>O.<0>__OnShotFired;
if (obj == null)
{
ShotFired val = OnShotFired;
<>O.<0>__OnShotFired = val;
obj = (object)val;
}
subscribedSettings.ShotFiredEvent -= (ShotFired)obj;
}
catch
{
}
_subscribedSettings = null;
}
internal static void OnShotFired(FVRFireArm firearm)
{
if (!((Object)(object)firearm == (Object)null))
{
if (_suppressShotEvent)
{
_suppressShotEvent = false;
}
else
{
FireShotFeedback(firearm, null);
}
}
}
private static void FireShotFeedback(FVRFireArm firearm, AttachableFirearm attachable)
{
if ((Object)(object)firearm == (Object)null && (Object)(object)attachable == (Object)null)
{
return;
}
bool flag = _cfg.RecoilStrength > 0;
bool flag2 = _cfg.HmdVibration == HeadsetVibrationType.OnRecoil || _cfg.HmdVibration == HeadsetVibrationType.Both;
if (!flag && !flag2)
{
return;
}
bool flag3 = false;
flag3 |= TryRecoil(_leftHand, firearm, attachable, flag);
flag3 |= TryRecoil(_rightHand, firearm, attachable, flag);
if (!flag3 && flag && (Object)(object)attachable == (Object)null)
{
FVRViveHand hand = ((FVRInteractiveObject)firearm).m_hand;
if ((Object)(object)hand != (Object)null)
{
ApplyRecoil(StateFor(hand));
flag3 = true;
}
}
if (flag3 && flag2)
{
AddHmdRumbleImpulse(1f, _cfg.HmdShotDecay);
}
}
private static bool TryRecoil(HandState state, FVRFireArm firearm, AttachableFirearm attachable, bool wantRecoil)
{
if ((Object)(object)state.Hand == (Object)null)
{
return false;
}
if (!(((Object)(object)attachable != (Object)null) ? ((Object)(object)state.Attachable == (Object)(object)attachable) : ((Object)(object)state.Firearm == (Object)(object)firearm)))
{
return false;
}
if (wantRecoil)
{
ApplyRecoil(state);
}
return true;
}
private static void ApplyRecoil(HandState state)
{
if (state != null && state.EffectsEnabled)
{
TriggerEffectSpec triggerEffectSpec = ((!_cfg.VibrationRecoil) ? TriggerEffectSpec.Feedback(0, _cfg.RecoilStrength) : TriggerEffectSpec.Vibration(0, _cfg.RecoilStrength, _cfg.VibrationFrequency));
float unscaledTime = Time.unscaledTime;
if (state.RecoilActive)
{
RestoreResistance(state, unscaledTime);
}
Send(state, triggerEffectSpec);
state.RecoilSpec = triggerEffectSpec;
state.RecoilActive = true;
state.RecoilUntil = unscaledTime + KickHoldTime();
}
}
private static float KickHoldTime()
{
float num = _cfg.RecoilDuration;
if (num < 0.02f)
{
num = 0.02f;
}
if (_cfg.VibrationRecoil && num < 0.05f)
{
num = 0.05f;
}
return num;
}
private static void RestoreResistance(HandState state, float now)
{
state.RecoilActive = false;
state.RecoilUntil = 0f;
Send(state, ComputeSpec(state, now));
}
private static void EvaluateHands(float now)
{
FVRMovementManager val = null;
try
{
val = GM.CurrentMovementManager;
}
catch
{
}
FVRViveHand[] array = (((Object)(object)val == (Object)null) ? null : val.Hands);
if (array == null)
{
ReleaseAllHands();
return;
}
for (int i = 0; i < array.Length; i++)
{
ResolveContext(array[i]);
}
DeduplicateSharedFirearm();
foreach (FVRViveHand val2 in array)
{
if (!((Object)(object)val2 == (Object)null))
{
HandState handState = StateFor(val2);
if (handState.RecoilActive && now >= handState.RecoilUntil)
{
RestoreResistance(handState, now);
}
else
{
SendIfChanged(handState, ComputeSpec(handState, now));
}
}
}
}
private static void ResolveContext(FVRViveHand hand)
{
if ((Object)(object)hand == (Object)null)
{
return;
}
HandState handState = StateFor(hand);
handState.Hand = hand;
handState.Firearm = null;
handState.Attachable = null;
FVRInteractiveObject currentInteractable = hand.CurrentInteractable;
if ((Object)(object)currentInteractable == (Object)null)
{
return;
}
FVRFireArm val = (FVRFireArm)(object)((currentInteractable is FVRFireArm) ? currentInteractable : null);
if ((Object)(object)val != (Object)null)
{
handState.Firearm = val;
return;
}
FVRAlternateGrip val2 = (FVRAlternateGrip)(object)((currentInteractable is FVRAlternateGrip) ? currentInteractable : null);
if ((Object)(object)val2 != (Object)null)
{
AttachableForegrip lastGrabbedInGrip = val2.LastGrabbedInGrip;
AttachableFirearmInterface val3 = (AttachableFirearmInterface)(object)((lastGrabbedInGrip is AttachableFirearmInterface) ? lastGrabbedInGrip : null);
if ((Object)(object)val3 != (Object)null)
{
handState.Attachable = val3.FA;
}
}
else
{
AttachableFirearmPhysicalObject val4 = (AttachableFirearmPhysicalObject)(object)((currentInteractable is AttachableFirearmPhysicalObject) ? currentInteractable : null);
if ((Object)(object)val4 != (Object)null)
{
handState.Attachable = val4.FA;
}
}
}
private static void DeduplicateSharedFirearm()
{
FVRFireArm firearm = _leftHand.Firearm;
if ((Object)(object)firearm == (Object)null || (Object)(object)firearm != (Object)(object)_rightHand.Firearm)
{
return;
}
FVRViveHand hand = ((FVRInteractiveObject)firearm).m_hand;
if (!((Object)(object)hand == (Object)null))
{
if ((Object)(object)hand == (Object)(object)_leftHand.Hand)
{
_rightHand.Firearm = null;
}
else if ((Object)(object)hand == (Object)(object)_rightHand.Hand)
{
_leftHand.Firearm = null;
}
}
}
private static TriggerEffectSpec ComputeSpec(HandState state, float now)
{
if (!state.EffectsEnabled)
{
return TriggerEffectSpec.Off();
}
if ((Object)(object)state.Firearm == (Object)null && (Object)(object)state.Attachable == (Object)null)
{
return TriggerEffectSpec.Off();
}
if (state.RecoilActive && now < state.RecoilUntil)
{
return state.RecoilSpec;
}
return BuildResistanceSpec(state);
}
private static TriggerEffectSpec BuildResistanceSpec(HandState state)
{
if (_cfg.OverridePos)
{
return TriggerEffectSpec.Slope(_cfg.OverrideStart, _cfg.OverrideEnd, 1, _cfg.Clicky);
}
FVRFireArm val = state.Firearm;
if ((Object)(object)val == (Object)null && (Object)(object)state.Attachable != (Object)null)
{
val = state.Attachable.OverrideFA;
}
if ((Object)(object)val == (Object)null)
{
return TriggerEffectSpec.Slope(1, 6, 1, _cfg.Clicky);
}
return SpecFromFirearm(val);
}
private static TriggerEffectSpec SpecFromFirearm(FVRFireArm firearm)
{
byte b = 2;
byte b2 = 7;
bool flag = false;
ClosedBoltWeapon val = (ClosedBoltWeapon)(object)((firearm is ClosedBoltWeapon) ? firearm : null);
if (val == null)
{
OpenBoltReceiver val2 = (OpenBoltReceiver)(object)((firearm is OpenBoltReceiver) ? firearm : null);
if (val2 == null)
{
Handgun val3 = (Handgun)(object)((firearm is Handgun) ? firearm : null);
if (val3 == null)
{
TubeFedShotgun val4 = (TubeFedShotgun)(object)((firearm is TubeFedShotgun) ? firearm : null);
if (val4 == null)
{
BoltActionRifle val5 = (BoltActionRifle)(object)((firearm is BoltActionRifle) ? firearm : null);
if (val5 == null)
{
if (!(firearm is BreakActionWeapon))
{
if (!(firearm is Revolver))
{
SingleActionRevolver val6 = (SingleActionRevolver)(object)((firearm is SingleActionRevolver) ? firearm : null);
if (val6 == null)
{
RevolvingShotgun val7 = (RevolvingShotgun)(object)((firearm is RevolvingShotgun) ? firearm : null);
if (val7 == null)
{
LAPD2019 val8 = (LAPD2019)(object)((firearm is LAPD2019) ? firearm : null);
if (val8 == null)
{
BAP val9 = (BAP)(object)((firearm is BAP) ? firearm : null);
if (val9 == null)
{
if (!(firearm is PotatoGun))
{
GrappleGun val10 = (GrappleGun)(object)((firearm is GrappleGun) ? firearm : null);
if (val10 == null)
{
Airgun val11 = (Airgun)(object)((firearm is Airgun) ? firearm : null);
if (val11 == null)
{
CarlGustaf val12 = (CarlGustaf)(object)((firearm is CarlGustaf) ? firearm : null);
if (val12 == null)
{
RailTater val13 = (RailTater)(object)((firearm is RailTater) ? firearm : null);
if (val13 == null)
{
FlameThrower val14 = (FlameThrower)(object)((firearm is FlameThrower) ? firearm : null);
if (val14 == null)
{
sblp val15 = (sblp)(object)((firearm is sblp) ? firearm : null);
if (val15 != null)
{
b = FromThreshold(val15.TriggerResetThreshold, 1);
b2 = FromThreshold(val15.TriggerFiringThreshold, 1);
}
else
{
b = 1;
b2 = 6;
}
}
else
{
b = 2;
b2 = FromThreshold(val14.TriggerFiringThreshold, 1);
}
}
else
{
b = FromThreshold(val13.TriggerResetThreshold, 1);
b2 = FromThreshold(val13.TriggerFiringThreshold, 1);
flag = true;
}
}
else
{
b = FromThreshold(val12.TriggerResetThreshold, 1);
b2 = FromThreshold(val12.TriggerFiringThreshold, 1);
flag = true;
}
}
else
{
b = FromThreshold(val11.TriggerResetThreshold, 1);
b2 = FromThreshold(val11.TriggerFiringThreshold, 1);
flag = true;
}
}
else
{
b = FromThreshold(val10.TriggerResetThreshold, 1);
b2 = FromThreshold(val10.TriggerBreakThreshold, 1);
flag = true;
}
}
else
{
b = 3;
b2 = 6;
flag = true;
}
}
else
{
b = FromThreshold(val9.TriggerResetThreshold, 1);
b2 = FromThreshold(val9.TriggerFiringThreshold, 1);
flag = true;
}
}
else
{
b = FromThreshold(val8.TriggerResetThreshold, 1);
b2 = FromThreshold(val8.TriggerFireThreshold, 1);
flag = true;
}
}
else
{
b = FromThreshold(val7.TriggerResetThreshold, 1);
b2 = FromThreshold(val7.TriggerFiringThreshold, 1);
flag = true;
}
}
else
{
b = FromThreshold(val6.TriggerThreshold, 2);
b2 = FromThreshold(val6.TriggerThreshold, 1);
flag = true;
}
}
else
{
b = 1;
b2 = 8;
flag = true;
}
}
else
{
b = 3;
b2 = 6;
flag = true;
}
}
else
{
b = FromThreshold(val5.TriggerResetThreshold, 1);
b2 = FromThreshold(val5.TriggerFiringThreshold, 1);
flag = true;
}
}
else
{
b = FromThreshold(val4.TriggerResetThreshold, 1);
b2 = FromThreshold(val4.TriggerBreakThreshold, 1);
flag = true;
}
}
else
{
b = FromThreshold(val3.TriggerResetThreshold, 1);
b2 = FromThreshold(val3.TriggerBreakThreshold, 1);
flag = true;
}
}
else
{
b = FromThreshold(val2.TriggerResetThreshold, 1);
b2 = FromThreshold(val2.TriggerFiringThreshold, 1);
flag = true;
}
}
else
{
byte b3 = FromThreshold(val.TriggerResetThreshold, 1);
byte b4 = FromThreshold(val.TriggerFiringThreshold, 1);
b = b3;
b2 = b4;
if (_cfg.DualStage && val.UsesDualStageFullAuto && !_dualStageUnavailable)
{
try
{
if (val.m_triggerFloat < val.TriggerFiringThreshold)
{
b = b3;
b2 = b4;
}
else
{
b = FromThreshold(val.TriggerFiringThreshold, 1);
b2 = FromThreshold(val.TriggerDualStageThreshold, 1);
}
}
catch (Exception)
{
_dualStageUnavailable = true;
b = b3;
b2 = b4;
}
}
flag = true;
}
if (flag && _cfg.NoEffectWhenEmpty && IsWeaponEmpty(firearm))
{
return TriggerEffectSpec.Off();
}
return TriggerEffectSpec.Slope(b, b2, 1, _cfg.Clicky);
}
private static byte FromThreshold(float threshold, int subtract)
{
return (byte)Clamp9(Clamp9((int)(threshold * 10f) - subtract) - 1);
}
private static int Clamp9(int value)
{
if (value < 0)
{
return 0;
}
if (value <= 9)
{
return value;
}
return 9;
}
private static bool IsWeaponEmpty(FVRFireArm firearm)
{
List<FVRFireArmChamber> chambers = firearm.GetChambers();
if (chambers != null)
{
for (int i = 0; i < chambers.Count; i++)
{
FVRFireArmChamber val = chambers[i];
if ((Object)(object)val != (Object)null && val.IsFull && !val.IsSpent)
{
return false;
}
}
}
FVRFireArmMagazine magazine = firearm.Magazine;
if ((Object)(object)magazine != (Object)null && magazine.HasARound())
{
return false;
}
return true;
}
private static void Send(HandState state, TriggerEffectSpec spec)
{
PSVR2ToolkitBridge.SetTriggerEffect(state.Controller, spec);
state.LastSent = spec;
state.HasLastSent = true;
state.LastSendTime = Time.unscaledTime;
}
private static void SendIfChanged(HandState state, TriggerEffectSpec spec)
{
if (!state.HasLastSent || !state.LastSent.Equals(spec) || (!spec.IsOff && !(Time.unscaledTime - state.LastSendTime < _cfg.RefreshInterval)))
{
Send(state, spec);
}
}
private static void ReleaseAllHands()
{
bool flag = _leftHand.HasLastSent && !_leftHand.LastSent.IsOff;
bool flag2 = _rightHand.HasLastSent && !_rightHand.LastSent.IsOff;
if (flag || flag2 || (Object)(object)_leftHand.Firearm != (Object)null || (Object)(object)_leftHand.Attachable != (Object)null || (Object)(object)_rightHand.Firearm != (Object)null || (Object)(object)_rightHand.Attachable != (Object)null || _leftHand.RecoilActive || _rightHand.RecoilActive)
{
if (flag)
{
PSVR2ToolkitBridge.SetTriggerEffect(VRControllerType.Left, TriggerEffectSpec.Off());
}
if (flag2)
{
PSVR2ToolkitBridge.SetTriggerEffect(VRControllerType.Right, TriggerEffectSpec.Off());
}
PSVR2ToolkitBridge.SetTriggerEffect(VRControllerType.Both, TriggerEffectSpec.Off());
_leftHand.HasLastSent = false;
_leftHand.RecoilUntil = 0f;
_leftHand.RecoilActive = false;
_leftHand.Firearm = null;
_leftHand.Attachable = null;
_rightHand.HasLastSent = false;
_rightHand.RecoilUntil = 0f;
_rightHand.RecoilActive = false;
_rightHand.Firearm = null;
_rightHand.Attachable = null;
}
}
private static void AddHmdRumbleImpulse(float strength, float duration)
{
if (_cfg.HmdVibration != HeadsetVibrationType.Disabled)
{
if (strength > _hmdLevel)
{
_hmdLevel = ((strength > 1f) ? 1f : strength);
}
_hmdDecayDuration = ((duration < 0.02f) ? 0.02f : duration);
}
}
private static void UpdateHeadsetRumble()
{
if (!PSVR2ToolkitBridge.Backend.SupportsHmdRumble)
{
return;
}
if (_hmdLevel > 0f)
{
_hmdLevel -= Time.deltaTime / _hmdDecayDuration;
if (_hmdLevel < 0f)
{
_hmdLevel = 0f;
}
}
SetHmdRumbleLevel(_hmdLevel);
}
private static void SetHmdRumbleLevel(float level)
{
int num = ((!(level <= 0.001f)) ? Mathf.Clamp(Mathf.RoundToInt((float)(int)_cfg.HmdFrequency * level), 1, 25) : 0);
if (num != _hmdRumbleSent)
{
_hmdRumbleSent = num;
PSVR2ToolkitBridge.SetHmdRumble((byte)num);
}
}
private static void ApplyPatches()
{
SafePatchAll(typeof(SceneLoadPatch));
SafePatchAll(typeof(HitEffectPatch));
SafePatchAll(typeof(AttachableClosedBoltFirePatch));
SafePatchAll(typeof(AttachableTubeFedFirePatch));
SafePatchAll(typeof(AttachableBreakActionsFirePatch));
}
private static void SafePatchAll(Type patchType)
{
try
{
Harmony.CreateAndPatchAll(patchType, (string)null);
}
catch (Exception ex)
{
Logger.LogError((object)("Could not apply " + patchType.Name + " (did a game update change it?): " + ex.Message));
}
}
}
}
namespace plugin.Properties
{
[CompilerGenerated]
[GeneratedCode("Microsoft.VisualStudio.Editors.SettingsDesigner.SettingsSingleFileGenerator", "17.14.0.0")]
internal sealed class Settings : ApplicationSettingsBase
{
private static Settings defaultInstance = (Settings)(object)SettingsBase.Synchronized((SettingsBase)(object)new Settings());
public static Settings Default => defaultInstance;
}
}