using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Reflection.Emit;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Runtime.Versioning;
using System.Threading;
using BepInEx;
using BepInEx.Configuration;
using BepInEx.Logging;
using FistVR;
using HarmonyLib;
using UnityEngine;
using UnityEngine.SceneManagement;
[assembly: CompilationRelaxations(8)]
[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
[assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)]
[assembly: TargetFramework(".NETFramework,Version=v4.6", FrameworkDisplayName = ".NET Framework 4.6")]
[assembly: AssemblyCompany("AmbientMuter")]
[assembly: AssemblyConfiguration("Release")]
[assembly: AssemblyFileVersion("1.0.0.0")]
[assembly: AssemblyInformationalVersion("1.0.0+7ef8a9942f511624d5885d2e557eeb9603de1f63")]
[assembly: AssemblyProduct("AmbientMuter")]
[assembly: AssemblyTitle("AmbientMuter")]
[assembly: AssemblyVersion("1.0.0.0")]
namespace AmbientMuter;
internal static class AudioModGuards
{
internal const string AiykeGuid = "Aiyke.Audio_balance";
internal const string SirdoggyGuid = "Sirdoggy.DynamicAudioBalancing";
internal static void Apply(Harmony h)
{
TryPatch(h, typeof(Pool_SkipEmptyClipSet));
TryPatch(h, typeof(Aiyke_SkipMissingClip));
TryPatch(h, typeof(Sirdoggy_SkipLooseChamber));
}
private static void TryPatch(Harmony h, Type t)
{
try
{
h.PatchAll(t);
}
catch (Exception ex)
{
if (AmbientMuterPlugin.Verbose)
{
AmbientMuterPlugin.Log.LogInfo((object)("[AmbientMuter] audio guard skipped: " + t.Name + " — " + ex.Message));
}
}
}
internal static bool FirstClipMissing(AudioEvent clipSet)
{
if (clipSet != null && clipSet.Clips != null && clipSet.Clips.Count != 0)
{
return (Object)(object)clipSet.Clips[0] == (Object)null;
}
return true;
}
}
[HarmonyPatch(typeof(AudioSourcePool), "PlayClipVolumePitchOverride")]
internal static class Pool_SkipEmptyClipSet
{
[HarmonyPrefix]
[HarmonyPriority(800)]
private static bool Prefix(AudioEvent clipSet, ref FVRPooledAudioSource __result)
{
if (clipSet != null && clipSet.Clips != null)
{
return true;
}
__result = null;
return false;
}
}
[HarmonyPatch]
internal static class Aiyke_SkipMissingClip
{
private static MethodBase TargetMethod()
{
return AccessTools.Method("plugin.audio_balance:SM_AudioSourcePool_PlayClipVolumePitchOverride_prefix", (Type[])null, (Type[])null);
}
[HarmonyPrefix]
private static bool Prefix(AudioEvent clipSet)
{
return !AudioModGuards.FirstClipMissing(clipSet);
}
}
[HarmonyPatch]
internal static class Sirdoggy_SkipLooseChamber
{
private static Type _chamberTag;
private static MethodBase TargetMethod()
{
_chamberTag = AccessTools.TypeByName("Sirdoggy.DynamicAudioBalancing.Patches.Tags.ChamberTagAudioBalancing");
return AccessTools.Method("Sirdoggy.DynamicAudioBalancing.Patches.Firearms.FVRFireArmChamberPatcher:Fire_Postfix", (Type[])null, (Type[])null);
}
[HarmonyPrefix]
private static bool Prefix(FVRFireArmChamber __instance)
{
if ((Object)(object)__instance == (Object)null || (Object)(object)__instance.Firearm != (Object)null)
{
return true;
}
if ((object)_chamberTag == null)
{
return true;
}
return (Object)(object)((Component)__instance).GetComponent(_chamberTag) != (Object)null;
}
}
[BepInPlugin("h3vr.invent60.ambientmuter", "AmbientMuter", "2.2.2")]
[BepInDependency(/*Could not decode attribute arguments.*/)]
[BepInDependency(/*Could not decode attribute arguments.*/)]
public class AmbientMuterPlugin : BaseUnityPlugin
{
private ConfigEntry<bool> _cfgMuteMusic;
private ConfigEntry<bool> _cfgMuteAmbient;
private ConfigEntry<float> _cfgSweepDelay;
private ConfigEntry<bool> _cfgKeepFootsteps;
private ConfigEntry<bool> _cfgKeepInteractive;
private ConfigEntry<bool> _cfgMutePlayerHurt;
private ConfigEntry<bool> _cfgVerbose;
private ConfigEntry<bool> _cfgSpotify;
private ConfigEntry<float> _cfgSpotifyVolume;
private ConfigEntry<bool> _cfgSpotifyDuck;
private ConfigEntry<float> _cfgSpotifyDuckDepth;
internal static ManualLogSource Log;
internal static bool Verbose;
private Harmony _harmony;
private bool _tuned;
private int _tuneAttempts;
private AmbientMuterRunner _runner;
private SpotifyMusic _spotify;
internal static bool IsU120Plus;
private bool SpotifyOn
{
get
{
if (_cfgSpotify.Value)
{
return _spotify != null;
}
return false;
}
}
private string SpotifyStatePath => Path.Combine(Paths.ConfigPath, "h3vr.invent60.ambientmuter.spotify-volume");
private void Awake()
{
//IL_0060: Unknown result type (might be due to invalid IL or missing references)
//IL_006a: Expected O, but got Unknown
Log = ((BaseUnityPlugin)this).Logger;
BindConfig();
IsU120Plus = (object)AccessTools.TypeByName("FistVR.VaultFileDisplayMode") == null;
if (Verbose)
{
((BaseUnityPlugin)this).Logger.LogInfo((object)("[AmbientMuter] H3VR 1.0/U120+ install detected: " + IsU120Plus));
}
SceneManager.sceneLoaded += OnSceneLoaded;
_harmony = new Harmony("h3vr.invent60.ambientmuter");
_harmony.PatchAll(typeof(PitchCompensator));
try
{
_harmony.PatchAll(typeof(MagBoopTimerFix));
if (Verbose)
{
((BaseUnityPlugin)this).Logger.LogInfo((object)"[AmbientMuter] Optional compatibility patch applied.");
}
}
catch
{
}
CylinderRate.Apply(_harmony);
AudioModGuards.Apply(_harmony);
_runner = ((Component)this).gameObject.AddComponent<AmbientMuterRunner>();
_runner.Log = Log;
UpdateSpotify();
_cfgSpotify.SettingChanged += delegate
{
UpdateSpotify();
};
((BaseUnityPlugin)this).Logger.LogInfo((object)"[AmbientMuter] Loaded.");
}
private void UpdateSpotify()
{
//IL_0048: Unknown result type (might be due to invalid IL or missing references)
//IL_004e: Expected O, but got Unknown
bool value = _cfgSpotify.Value;
if (_spotify == null)
{
if ((!value && !File.Exists(SpotifyStatePath)) || !SpotifyMusic.LoadNative(Path.GetDirectoryName(((BaseUnityPlugin)this).Info.Location), ((BaseUnityPlugin)this).Logger))
{
return;
}
GameObject val = new GameObject("AmbientMuterMusic");
Object.DontDestroyOnLoad((Object)(object)val);
_spotify = val.AddComponent<SpotifyMusic>();
_spotify.Log = ((BaseUnityPlugin)this).Logger;
_spotify.StatePath = SpotifyStatePath;
}
_spotify.SetWanted(value);
}
private void BindConfig()
{
//IL_0156: Unknown result type (might be due to invalid IL or missing references)
//IL_0160: Expected O, but got Unknown
//IL_01b6: Unknown result type (might be due to invalid IL or missing references)
//IL_01c0: Expected O, but got Unknown
_cfgMuteMusic = ((BaseUnityPlugin)this).Config.Bind<bool>("Music", "Mute map music", true, "Zeros the FMOD music bus every frame, silencing background music in all maps (TNH, outdoor assault maps, etc.). Overrides any other mod that sets music volume.");
_cfgMuteAmbient = ((BaseUnityPlugin)this).Config.Bind<bool>("Ambient", "Mute ambient sounds", true, "Mutes map ambient AudioSources (wind loops, propaganda speakers, environment drones). Applied once per scene load after the sweep delay.");
_cfgSweepDelay = ((BaseUnityPlugin)this).Config.Bind<float>("Ambient", "Sweep delay seconds", 4f, "Seconds to wait after scene load before sweeping ambient sources. Increase if some ambient sounds are not being caught. (0 to 30)");
_cfgKeepInteractive = ((BaseUnityPlugin)this).Config.Bind<bool>("Ambient", "Keep interactive object sounds", true, "Buttons, levers, switches, and other interactive map objects keep their sounds. Disable to also silence these.");
_cfgKeepFootsteps = ((BaseUnityPlugin)this).Config.Bind<bool>("Player", "Keep footstep mod sounds", true, "Footstep mods (PlayerFootsteps, AlyxFootsteps, EFT Frame, BF4 Footsteps) are not silenced. Disable to mute footstep sounds too.");
_cfgMutePlayerHurt = ((BaseUnityPlugin)this).Config.Bind<bool>("Player", "Mute player hurt sounds", false, "Mutes pain and impact sounds when the player takes damage.");
_cfgVerbose = ((BaseUnityPlugin)this).Config.Bind<bool>("Debug", "Verbose logging", false, "Writes extra diagnostic detail to the BepInEx log. Leave off unless troubleshooting.");
Verbose = _cfgVerbose.Value;
SpotifyMusic.Verbose = Verbose;
_cfgSpotify = ((BaseUnityPlugin)this).Config.Bind<bool>("Spotify music", "Play Spotify as game music", false, "When the Spotify desktop app is running, whatever it plays becomes the game's background music: heard in VR and in recordings (OBS, ActionCam), with your playlists, shuffle, crossfade and EQ as set in Spotify. Map music and ambient sounds are muted while this is on. Spotify's own volume in the Windows mixer is turned down while the game runs, so you do not hear it twice, and is put back when the game closes (or on the next launch if the game crashed). To stay in step with the game, Spotify is held for a moment now and then: on Windows 11 its window stops responding during that moment and the music carries on seamlessly; on Windows 10 it pauses and resumes instead, and its fades can be heard. Needs Windows 10 version 2004 or newer.");
_cfgSpotifyVolume = ((BaseUnityPlugin)this).Config.Bind<float>("Spotify music", "Spotify music volume", 1f, new ConfigDescription("Loudness of the Spotify music in game. Example: 0.5 = half as loud. (0 to 1)", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0f, 1f), new object[0]));
_cfgSpotifyDuck = ((BaseUnityPlugin)this).Config.Bind<bool>("Spotify music", "Duck music under loud game sounds", false, "Turns the Spotify music down for a moment when a loud game sound (a gunshot, an explosion) would be fighting it, then brings it back. Only happens when the music is loud enough to compete: quiet music or quiet game sounds never duck. Example: true = gunshots cut through loud music, false = the music always stays at the same loudness.");
_cfgSpotifyDuckDepth = ((BaseUnityPlugin)this).Config.Bind<float>("Spotify music", "Duck depth", 10f, new ConfigDescription("How far the music may be turned down at most, in decibels. Example: 6 = at most about half as loud, 10 = a clear dip, 20 = nearly silent under gunfire. (0 to 30)", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0f, 30f), new object[0]));
}
private void Update()
{
float timeScale = Time.timeScale;
bool flag = timeScale < 0.9999f && timeScale > 0.001f;
bool wasScaled = PitchCompensator.WasScaled;
if (flag)
{
PitchCompensator.LastScale = timeScale;
}
PitchCompensator.CachedTimeScale = timeScale;
PitchCompensator.WasScaled = flag;
if (wasScaled && !flag)
{
PitchCompensator.RestoreScaledSources();
}
if (PitchCompensator.PatcherDetected && !_tuned && _tuneAttempts < 5 && Time.timeScale < 0.99f && Time.timeScale > 0.001f)
{
_tuneAttempts++;
MeasurePitchCompound();
_tuned = PitchCompensator.Initialized;
}
SpotifyMusic.Rate = timeScale;
SpotifyMusic.Volume = _cfgSpotifyVolume.Value;
SpotifyMusic.DuckOn = _cfgSpotifyDuck.Value;
SpotifyMusic.DuckMaxDb = _cfgSpotifyDuckDepth.Value;
if (_cfgMuteMusic.Value || SpotifyOn)
{
if (IsU120Plus)
{
MusicBusMuter.TryMute();
}
else
{
LegacyMusicBusMuter.TryMute();
}
}
}
private void MeasurePitchCompound()
{
//IL_0020: Unknown result type (might be due to invalid IL or missing references)
//IL_0026: Expected O, but got Unknown
try
{
float cachedTimeScale = PitchCompensator.CachedTimeScale;
if (cachedTimeScale <= 0.001f || cachedTimeScale >= 0.999f)
{
return;
}
GameObject val = new GameObject("__AMTmp");
AudioSource obj = val.AddComponent<AudioSource>();
obj.volume = 0f;
obj.mute = true;
obj.pitch = 1f;
float pitch = obj.pitch;
Object.Destroy((Object)(object)val);
if (pitch <= 0f)
{
return;
}
float num = Mathf.Log(cachedTimeScale);
float num2 = Mathf.Log(pitch);
if (!(Mathf.Abs(num) < 0.0001f))
{
int num3 = Mathf.RoundToInt(num2 / num);
if (num3 > 0)
{
PitchCompensator.CompoundLevels = num3;
PitchCompensator.ExtraCompounders = Mathf.Max(0, num3 - 1);
PitchCompensator.CompensationActive = true;
PitchCompensator.Initialized = true;
}
}
}
catch
{
}
}
private void OnSceneLoaded(Scene scene, LoadSceneMode mode)
{
if (IsU120Plus)
{
MusicBusMuter.Reset();
}
else
{
LegacyMusicBusMuter.Reset();
}
if (_cfgMuteAmbient.Value || SpotifyOn)
{
((MonoBehaviour)_runner).Invoke("RunMuteAmbient", _cfgSweepDelay.Value);
}
}
private void MuteAmbientSources()
{
//IL_0063: Unknown result type (might be due to invalid IL or missing references)
//IL_0068: Unknown result type (might be due to invalid IL or missing references)
bool value = _cfgKeepFootsteps.Value;
bool value2 = _cfgKeepInteractive.Value;
bool value3 = _cfgMutePlayerHurt.Value;
int num = 0;
int num2 = 0;
AudioSource[] array = Object.FindObjectsOfType<AudioSource>();
foreach (AudioSource val in array)
{
if ((Object)(object)((Component)val).GetComponent<FVRPooledAudioSource>() != (Object)null)
{
num2++;
continue;
}
if (value)
{
Scene scene = ((Component)val).gameObject.scene;
if (((Scene)(ref scene)).name == "DontDestroyOnLoad")
{
num2++;
continue;
}
}
if (value2 && (Object)(object)((Component)val).GetComponentInParent<FVRInteractiveObject>() != (Object)null)
{
num2++;
continue;
}
if (!value2 && (Object)(object)((Component)val).GetComponentInParent<FVRPhysicalObject>() != (Object)null)
{
num2++;
continue;
}
if ((Object)(object)((Component)val).GetComponentInParent<Sosig>() != (Object)null)
{
num2++;
continue;
}
if ((Object)(object)((Component)val).GetComponentInParent<FVRMovementManager>() != (Object)null && !value3)
{
num2++;
continue;
}
if (value && (Object)(object)((Component)val).GetComponentInParent<BaseUnityPlugin>() != (Object)null)
{
num2++;
continue;
}
val.mute = true;
num++;
}
((BaseUnityPlugin)this).Logger.LogInfo((object)$"[AmbientMuter] Scene sweep done. Muted: {num}, Kept: {num2}");
}
internal void RunMuteAmbientSources()
{
MuteAmbientSources();
}
}
internal static class MusicBusMuter
{
private static object _bus;
private static MethodInfo _setVolume;
private static bool _obtained;
private static bool _unavailable;
internal static void TryMute()
{
if (_unavailable || (!_obtained && !TryGetBus()))
{
return;
}
try
{
_setVolume.Invoke(_bus, new object[1] { 0f });
}
catch
{
_obtained = false;
}
}
private static bool TryGetBus()
{
try
{
Type type = Type.GetType("FMODUnity.RuntimeManager, Assembly-CSharp-firstpass");
if ((object)type == null)
{
_unavailable = true;
return false;
}
MethodInfo method = type.GetMethod("GetBus", new Type[1] { typeof(string) });
if ((object)method == null)
{
_unavailable = true;
return false;
}
_bus = method.Invoke(null, new object[1] { "bus:/Music" });
if (_bus == null)
{
_unavailable = true;
return false;
}
_setVolume = _bus.GetType().GetMethod("setVolume", new Type[1] { typeof(float) });
if ((object)_setVolume == null)
{
_unavailable = true;
return false;
}
_obtained = true;
return true;
}
catch
{
_unavailable = true;
return false;
}
}
internal static void Reset()
{
_obtained = false;
}
}
internal static class LegacyMusicBusMuter
{
private static object _bus;
private static MethodInfo _setVolume;
private static bool _obtained;
private static bool _unavailable;
internal static void TryMute()
{
if (_unavailable || (!_obtained && !TryGetBus()))
{
return;
}
try
{
_setVolume.Invoke(_bus, new object[1] { 0f });
}
catch
{
_obtained = false;
}
}
private static bool TryGetBus()
{
try
{
Type type = Type.GetType("FMODUnity.RuntimeManager, Assembly-CSharp-firstpass");
if ((object)type == null)
{
_unavailable = true;
return false;
}
MethodInfo method = type.GetMethod("GetBus", new Type[1] { typeof(string) });
if ((object)method == null)
{
_unavailable = true;
return false;
}
_bus = method.Invoke(null, new object[1] { "bus:/Music" });
if (_bus == null)
{
_unavailable = true;
return false;
}
_setVolume = _bus.GetType().GetMethod("setVolume", new Type[1] { typeof(float) });
if ((object)_setVolume == null)
{
_unavailable = true;
return false;
}
_obtained = true;
return true;
}
catch
{
_unavailable = true;
return false;
}
}
internal static void Reset()
{
_obtained = false;
}
}
internal sealed class AmbientMuterRunner : MonoBehaviour
{
internal ManualLogSource Log;
private AmbientMuterPlugin _plugin;
private int _detectAfterFrame = -1;
private void Awake()
{
_plugin = ((Component)this).GetComponent<AmbientMuterPlugin>();
}
private void Start()
{
_detectAfterFrame = Time.frameCount;
}
private void Update()
{
if (_detectAfterFrame >= 0 && Time.frameCount > _detectAfterFrame)
{
_detectAfterFrame = -1;
DetectPitchPatchers();
}
}
internal void RunMuteAmbient()
{
_plugin?.RunMuteAmbientSources();
}
internal void DetectPitchPatchers()
{
try
{
PropertyInfo property = typeof(AudioSource).GetProperty("pitch");
if ((object)property == null)
{
return;
}
MethodInfo setMethod = property.GetSetMethod();
if ((object)setMethod == null)
{
return;
}
Patches patchInfo = Harmony.GetPatchInfo((MethodBase)setMethod);
if (patchInfo == null)
{
return;
}
int num = 0;
foreach (Patch prefix in patchInfo.Prefixes)
{
if (!string.Equals(prefix.owner, "h3vr.invent60.ambientmuter", StringComparison.OrdinalIgnoreCase))
{
num++;
}
}
foreach (Patch postfix in patchInfo.Postfixes)
{
if (!string.Equals(postfix.owner, "h3vr.invent60.ambientmuter", StringComparison.OrdinalIgnoreCase))
{
num++;
}
}
foreach (Patch transpiler in patchInfo.Transpilers)
{
if (!string.Equals(transpiler.owner, "h3vr.invent60.ambientmuter", StringComparison.OrdinalIgnoreCase))
{
num++;
}
}
if (num > 0)
{
PitchCompensator.PatcherDetected = true;
}
}
catch
{
}
}
}
[HarmonyPatch]
public static class MagBoopTimerFix
{
private static MethodBase TargetMethod()
{
return AccessTools.Method("MagBoop.MagTriggerScript:Update", (Type[])null, (Type[])null);
}
[HarmonyTranspiler]
private static IEnumerable<CodeInstruction> Transpiler(IEnumerable<CodeInstruction> instructions)
{
MethodInfo getMethod = typeof(Time).GetProperty("fixedUnscaledDeltaTime").GetGetMethod();
List<CodeInstruction> list = instructions.ToList();
for (int i = 0; i < list.Count; i++)
{
if (list[i].opcode == OpCodes.Call && list[i].operand is MethodInfo methodInfo && (object)methodInfo.DeclaringType == typeof(Time) && methodInfo.Name == "get_fixedDeltaTime")
{
list[i].operand = getMethod;
}
}
return list;
}
}
public static class PitchCompensator
{
public static bool PatcherDetected = false;
public static bool Initialized = false;
public static bool CompensationActive = false;
public static int CompoundLevels = 0;
public static int ExtraCompounders = 0;
public static float CachedTimeScale = 1f;
public static bool WasScaled = false;
public static float LastScale = 1f;
private static readonly HashSet<AudioSource> _scaledSources = new HashSet<AudioSource>();
[HarmonyPatch(/*Could not decode attribute arguments.*/)]
[HarmonyPostfix]
public static void CompensatePitchRead(ref float __result)
{
if (Initialized && CompensationActive && !(CachedTimeScale >= 0.9999f) && !(CachedTimeScale <= 0.001f))
{
__result /= CachedTimeScale;
}
}
[HarmonyPatch(/*Could not decode attribute arguments.*/)]
[HarmonyPrefix]
public static void PreCompensatePitchSet(AudioSource __instance, ref float value)
{
if (!Initialized || !CompensationActive || CachedTimeScale >= 0.9999f || CachedTimeScale <= 0.001f)
{
return;
}
if (ExtraCompounders > 0)
{
for (int i = 0; i < ExtraCompounders; i++)
{
value /= CachedTimeScale;
}
}
if ((Object)(object)__instance != (Object)null)
{
_scaledSources.Add(__instance);
}
}
public static void RestoreScaledSources()
{
float lastScale = LastScale;
if (_scaledSources.Count == 0)
{
return;
}
if (lastScale <= 0.001f || lastScale >= 0.9999f)
{
_scaledSources.Clear();
return;
}
foreach (AudioSource scaledSource in _scaledSources)
{
if (!((Object)(object)scaledSource == (Object)null))
{
scaledSource.pitch /= lastScale;
}
}
_scaledSources.Clear();
}
}
internal static class CylinderRate
{
internal static void Apply(Harmony h)
{
TryPatch(h, typeof(Revolver_ArmRate));
TryPatch(h, typeof(Revolver_SpinRate));
TryPatch(h, typeof(Cylinder_Grab));
TryPatch(h, typeof(Cylinder_Release));
TryPatch(h, typeof(Cylinder_SpinStep));
TryPatch(h, typeof(LAPD_ArmRate));
TryPatch(h, typeof(LAPDCylinder_Grab));
TryPatch(h, typeof(LAPDCylinder_Release));
}
private static void TryPatch(Harmony h, Type t)
{
try
{
h.PatchAll(t);
}
catch (Exception ex)
{
if (AmbientMuterPlugin.Verbose)
{
AmbientMuterPlugin.Log.LogWarning((object)("[AmbientMuter] cylinder patch skipped: " + t.Name + " — " + ex.Message));
}
}
}
}
internal struct HandVelScale
{
private FVRViveHand _hand;
private Vector3 _lin;
private Vector3 _ang;
private static bool _active;
internal static HandVelScale Apply(FVRViveHand hand)
{
//IL_0042: Unknown result type (might be due to invalid IL or missing references)
//IL_0047: Unknown result type (might be due to invalid IL or missing references)
//IL_0054: Unknown result type (might be due to invalid IL or missing references)
//IL_0059: Unknown result type (might be due to invalid IL or missing references)
//IL_0065: Unknown result type (might be due to invalid IL or missing references)
//IL_006b: Unknown result type (might be due to invalid IL or missing references)
//IL_0070: Unknown result type (might be due to invalid IL or missing references)
//IL_007c: Unknown result type (might be due to invalid IL or missing references)
//IL_0082: Unknown result type (might be due to invalid IL or missing references)
//IL_0087: Unknown result type (might be due to invalid IL or missing references)
HandVelScale result = default(HandVelScale);
if (_active || (Object)(object)hand == (Object)null)
{
return result;
}
float timeScale = Time.timeScale;
if (timeScale >= 0.9999f || timeScale <= 0.001f)
{
return result;
}
result._hand = hand;
result._lin = hand.Input.VelLinearWorld;
result._ang = hand.Input.VelAngularWorld;
hand.Input.VelLinearWorld = result._lin / timeScale;
hand.Input.VelAngularWorld = result._ang / timeScale;
_active = true;
return result;
}
internal void Restore()
{
//IL_001b: Unknown result type (might be due to invalid IL or missing references)
//IL_0020: Unknown result type (might be due to invalid IL or missing references)
//IL_0031: Unknown result type (might be due to invalid IL or missing references)
//IL_0036: Unknown result type (might be due to invalid IL or missing references)
if (!((Object)(object)_hand == (Object)null))
{
_hand.Input.VelLinearWorld = _lin;
_hand.Input.VelAngularWorld = _ang;
_hand = null;
_active = false;
}
}
}
[HarmonyPatch(typeof(Revolver), "UpdateCylinderRelease")]
internal static class Revolver_ArmRate
{
[HarmonyPrefix]
private static void Prefix(Revolver __instance, out HandVelScale __state)
{
__state = HandVelScale.Apply(((FVRInteractiveObject)__instance).m_hand);
}
[HarmonyPostfix]
private static void Postfix(HandVelScale __state)
{
__state.Restore();
}
}
[HarmonyPatch(typeof(Revolver), "UpdateSpinning")]
internal static class Revolver_SpinRate
{
[HarmonyPrefix]
private static void Prefix(Revolver __instance, out HandVelScale __state)
{
__state = HandVelScale.Apply(((FVRInteractiveObject)__instance).m_hand);
}
[HarmonyPostfix]
private static void Postfix(HandVelScale __state)
{
__state.Restore();
}
}
[HarmonyPatch(typeof(RevolverCylinder), "UpdateInteraction")]
internal static class Cylinder_Grab
{
[HarmonyPrefix]
private static void Prefix(FVRViveHand hand, out HandVelScale __state)
{
__state = HandVelScale.Apply(hand);
}
[HarmonyPostfix]
private static void Postfix(HandVelScale __state)
{
__state.Restore();
}
}
[HarmonyPatch(typeof(RevolverCylinder), "EndInteraction")]
internal static class Cylinder_Release
{
[HarmonyPrefix]
private static void Prefix(FVRViveHand hand, out HandVelScale __state)
{
__state = HandVelScale.Apply(hand);
}
[HarmonyPostfix]
private static void Postfix(HandVelScale __state)
{
__state.Restore();
}
}
[HarmonyPatch(typeof(RevolverCylinder), "FVRUpdate")]
internal static class Cylinder_SpinStep
{
private static readonly FieldRef<RevolverCylinder, float> SpinVel = MakeRef();
private static FieldRef<RevolverCylinder, float> MakeRef()
{
try
{
return AccessTools.FieldRefAccess<RevolverCylinder, float>("m_fakeAngularVel");
}
catch
{
return null;
}
}
[HarmonyPrefix]
private static void Prefix(RevolverCylinder __instance, out float __state)
{
__state = 0f;
if (SpinVel != null)
{
float timeScale = Time.timeScale;
if (!(timeScale >= 0.9999f) && !(timeScale <= 0.001f))
{
__state = timeScale;
SpinVel.Invoke(__instance) *= timeScale;
}
}
}
[HarmonyPostfix]
private static void Postfix(RevolverCylinder __instance, float __state)
{
if (!(__state <= 0f) && SpinVel != null)
{
SpinVel.Invoke(__instance) /= __state;
}
}
}
[HarmonyPatch(typeof(LAPD2019), "UpdateCylinderRelease")]
internal static class LAPD_ArmRate
{
[HarmonyPrefix]
private static void Prefix(LAPD2019 __instance, out HandVelScale __state)
{
__state = HandVelScale.Apply(((FVRInteractiveObject)__instance).m_hand);
}
[HarmonyPostfix]
private static void Postfix(HandVelScale __state)
{
__state.Restore();
}
}
[HarmonyPatch(typeof(LAPD2019Cylinder), "UpdateInteraction")]
internal static class LAPDCylinder_Grab
{
[HarmonyPrefix]
private static void Prefix(FVRViveHand hand, out HandVelScale __state)
{
__state = HandVelScale.Apply(hand);
}
[HarmonyPostfix]
private static void Postfix(HandVelScale __state)
{
__state.Restore();
}
}
[HarmonyPatch(typeof(LAPD2019Cylinder), "EndInteraction")]
internal static class LAPDCylinder_Release
{
[HarmonyPrefix]
private static void Prefix(FVRViveHand hand, out HandVelScale __state)
{
__state = HandVelScale.Apply(hand);
}
[HarmonyPostfix]
private static void Postfix(HandVelScale __state)
{
__state.Restore();
}
}
internal sealed class SpotifyMusic : MonoBehaviour
{
private const string Exe = "Spotify.exe";
private const float SilentVolume = 0.0001f;
private const float Gain = 10000f;
private const float PauseAboveSec = 2f;
private const float ResumeBelowSec = 1f;
private const float DropAboveSec = 3f;
private const float PrimeSec = 0.1f;
private const int RampDiscardMs = 300;
private const float LeakLevel = 0.003f;
private const int MaxStep = 4;
private const int CmdPlay = 46;
private const int CmdPause = 47;
internal static SpotifyMusic Instance;
internal static volatile float Rate = 1f;
internal static volatile float Volume = 1f;
internal static volatile bool DuckOn = false;
internal static volatile float DuckMaxDb = 10f;
internal ManualLogSource Log;
internal string StatePath;
private static bool _loaded;
private static bool _loadFailed;
private Thread _thread;
private volatile bool _run;
private volatile bool _wanted;
private volatile bool _live;
private volatile bool _resilence;
private volatile int _discardUntil;
private int _sampleRate;
private uint _pid;
private bool _haveOrig;
private float _orig = 1f;
private bool _wePaused;
private bool _frozen;
private int _frozenAt;
private int _canFreeze = -1;
private int _seamsLogged;
private const int MinFrozenMs = 300;
private static readonly string[] SeamNames = new string[6] { "repaired", "repaired (searched)", "joined", "kept (low buffer)", "kept (timeout)", "no gap" };
private int _pausedAt;
private float _pausedBuffered;
private bool _failLogged;
private volatile int _calls;
private volatile int _lastPull;
private volatile int _exit;
private const int ExitOff = 0;
private const int ExitDiscard = 1;
private const int ExitHold = 2;
private const int ExitPrime = 3;
private const int ExitUnderrun = 4;
private const int ExitLeak = 5;
private const int ExitPlay = 6;
private const int ExitBig = 7;
private static readonly string[] ExitNames = new string[8] { "off", "discard", "hold", "prime", "underrun", "leak", "play", "block" };
internal static bool Verbose;
private int _lastStatus;
private bool _deafWarned;
private float[] _tmp;
private const float DuckGameMin = 0.1f;
private const float DuckMusicMin = 0.0316f;
private const float DuckMargin = 0.5f;
private const float GameHold = 0.3f;
private const float GameFall = 0.05f;
private const float MusicRelease = 0.25f;
private const float DuckRelease = 0.4f;
private float _gameEnv;
private float _gameHeld;
private float _musicEnv;
private float _duck = 1f;
internal volatile float DuckDeepest = 1f;
internal volatile float GamePeak;
internal volatile float MusicPeak;
private double _frac;
private bool _primed;
private MusicTap _tap;
private float _nextFind;
internal volatile string ListenerName = "none";
private bool _started;
private bool Holding
{
get
{
if (!_frozen)
{
return _wePaused;
}
return true;
}
}
[DllImport("kernel32", CharSet = CharSet.Unicode, SetLastError = true)]
private static extern IntPtr LoadLibraryW(string path);
[DllImport("bgmcap", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Unicode)]
private static extern uint BC_FindRoot(string exeName);
[DllImport("bgmcap", CallingConvention = CallingConvention.Cdecl)]
private static extern int BC_Start(uint rootPid, int sampleRate, int seconds);
[DllImport("bgmcap", CallingConvention = CallingConvention.Cdecl)]
private static extern void BC_Stop();
[DllImport("bgmcap", CallingConvention = CallingConvention.Cdecl)]
private static extern int BC_State();
[DllImport("bgmcap", CallingConvention = CallingConvention.Cdecl)]
private static extern int BC_Error();
[DllImport("bgmcap", CallingConvention = CallingConvention.Cdecl)]
private static extern int BC_MsSinceSound();
[DllImport("bgmcap", CallingConvention = CallingConvention.Cdecl)]
private static extern int BC_Available();
[DllImport("bgmcap", CallingConvention = CallingConvention.Cdecl)]
private static extern int BC_Peek(float[] dst, int skipFrames, int maxFrames);
[DllImport("bgmcap", CallingConvention = CallingConvention.Cdecl)]
private static extern void BC_Consume(int frames);
[DllImport("bgmcap", CallingConvention = CallingConvention.Cdecl)]
private static extern int BC_SetVolume(uint rootPid, float vol, out float prevVol);
[DllImport("bgmcap", CallingConvention = CallingConvention.Cdecl)]
private static extern int BC_EnsureVolume(uint rootPid, float vol, out float prevVol);
[DllImport("bgmcap", CallingConvention = CallingConvention.Cdecl)]
private static extern int BC_Media(uint rootPid, int cmd);
[DllImport("bgmcap", CallingConvention = CallingConvention.Cdecl)]
private static extern int BC_CanFreeze();
[DllImport("bgmcap", CallingConvention = CallingConvention.Cdecl)]
private static extern int BC_Freeze(uint rootPid);
[DllImport("bgmcap", CallingConvention = CallingConvention.Cdecl)]
private static extern int BC_Thaw();
[DllImport("bgmcap", CallingConvention = CallingConvention.Cdecl)]
private static extern int BC_SeamStats(out int lastGap, out float lastShift, out int lastReason);
internal static bool LoadNative(string pluginDir, ManualLogSource log)
{
if (_loaded)
{
return true;
}
if (_loadFailed)
{
return false;
}
string path = Path.Combine(pluginDir, "bgmcap.dll");
if (File.Exists(path) && LoadLibraryW(path) != IntPtr.Zero)
{
_loaded = true;
return true;
}
_loadFailed = true;
log.LogWarning((object)"[AmbientMuter] Spotify music unavailable: bgmcap.dll missing or not loadable next to the plugin.");
return false;
}
private void Awake()
{
_sampleRate = AudioSettings.outputSampleRate;
_tmp = new float[131080];
Instance = this;
}
internal void SetWanted(bool on)
{
_wanted = on;
if ((_thread == null || !_thread.IsAlive) && (on || File.Exists(StatePath)))
{
_run = true;
_thread = new Thread(ControlLoop);
_thread.IsBackground = true;
_thread.Start();
}
}
internal void Shutdown()
{
_wanted = false;
_run = false;
_thread?.Join(3000);
_thread = null;
}
private void Update()
{
if (!_wanted || ((Object)(object)_tap != (Object)null && ((Behaviour)_tap).isActiveAndEnabled) || Time.unscaledTime < _nextFind)
{
return;
}
_nextFind = Time.unscaledTime + 0.5f;
AudioListener[] array = Object.FindObjectsOfType<AudioListener>();
foreach (AudioListener val in array)
{
if (((Behaviour)val).isActiveAndEnabled)
{
_tap = ((Component)val).GetComponent<MusicTap>();
if ((Object)(object)_tap == (Object)null)
{
_tap = ((Component)val).gameObject.AddComponent<MusicTap>();
}
ListenerName = ((Object)((Component)val).gameObject).name;
return;
}
}
ListenerName = "none";
}
private void OnApplicationQuit()
{
Shutdown();
}
private void OnDestroy()
{
Shutdown();
}
private void ControlLoop()
{
try
{
if (File.Exists(StatePath) && TryReadState(out var v))
{
_orig = v;
_haveOrig = true;
}
int num = Environment.TickCount - 100000;
int num2 = num;
while (_run)
{
int tickCount = Environment.TickCount;
if (tickCount - num >= 2000)
{
num = tickCount;
uint num3 = BC_FindRoot("Spotify.exe");
if (num3 != _pid)
{
if (_pid != 0)
{
Detach(restore: false);
}
_pid = num3;
if (_pid != 0 && _wanted && Log != null)
{
Log.LogInfo((object)"[AmbientMuter] Spotify found.");
}
}
if (!_wanted)
{
if (_pid != 0)
{
Detach(restore: true);
}
if (!_haveOrig)
{
break;
}
}
}
if (_wanted && _pid != 0)
{
if (!_started)
{
StartCapture();
}
int num4 = BC_State();
if (num4 < 0)
{
ReportFailure();
}
else if (num4 > 0 && (_resilence || tickCount - num2 >= 1000))
{
num2 = tickCount;
_resilence = false;
Silence();
}
if (_live)
{
Backlog();
}
if (_live)
{
Status(tickCount);
}
}
Thread.Sleep(50);
}
}
catch (Exception ex)
{
if (Log != null)
{
Log.LogError((object)("[AmbientMuter] Spotify music stopped: " + ex.Message));
}
}
finally
{
try
{
if (_pid != 0)
{
Detach(restore: true);
}
}
catch
{
}
}
}
private void StartCapture()
{
_primed = false;
_frac = 0.0;
_started = BC_Start(_pid, _sampleRate, 10) >= 0;
}
private void ReportFailure()
{
if (!_failLogged)
{
_failLogged = true;
if (Log != null)
{
Log.LogWarning((object)("[AmbientMuter] Spotify music unavailable: audio capture failed (0x" + BC_Error().ToString("X8") + "). Needs Windows 10 version 2004 or newer."));
}
}
}
private void Silence()
{
float prevVol;
int num = BC_EnsureVolume(_pid, 0.0001f, out prevVol);
if (num < 0)
{
return;
}
if (num > 0)
{
_discardUntil = Environment.TickCount + 300;
if (!_haveOrig)
{
_orig = prevVol;
_haveOrig = true;
WriteState(_orig);
}
}
_live = true;
}
private void Backlog()
{
float num = (float)BC_Available() / (float)_sampleRate;
bool flag = BC_MsSinceSound() < 500;
bool flag2 = _calls > 0 && Environment.TickCount - _lastPull < 500;
if (_canFreeze < 0)
{
_canFreeze = BC_CanFreeze();
if (Log != null)
{
Log.LogInfo((object)((_canFreeze > 0) ? "[AmbientMuter] Spotify music: seamless mode (Spotify is held, not paused)." : "[AmbientMuter] Spotify music: Windows 10 mode (Spotify is paused and resumed; fades can be heard)."));
}
}
if (!Holding && flag && flag2 && num > 2f)
{
if ((_canFreeze > 0) ? (BC_Freeze(_pid) > 0) : (BC_Media(_pid, 47) > 0))
{
if (_canFreeze > 0)
{
_frozen = true;
}
else
{
_wePaused = true;
}
_pausedAt = (_frozenAt = Environment.TickCount);
_pausedBuffered = num;
}
}
else if (Holding && (num < 1f || !flag2 || NotDraining(num)))
{
if (_frozen && flag2 && Environment.TickCount - _frozenAt < 300)
{
return;
}
if (num > 1f)
{
BC_Consume(BC_Available() - (int)(1f * (float)_sampleRate));
}
Release();
}
if (!Holding && num > 3f)
{
BC_Consume(BC_Available() - (int)(1f * (float)_sampleRate));
}
LogSeams();
}
private void Release()
{
if (_frozen)
{
BC_Thaw();
_frozen = false;
}
if (_wePaused)
{
BC_Media(_pid, 46);
_wePaused = false;
}
}
private void LogSeams()
{
int lastGap;
float lastShift;
int lastReason;
int num = BC_SeamStats(out lastGap, out lastShift, out lastReason);
if (num != _seamsLogged && Log != null)
{
_seamsLogged = num;
if (lastReason == 1 || lastReason == 3 || lastReason == 4 || Verbose)
{
Log.LogInfo((object)("[AmbientMuter] Spotify music: join " + num + " " + SeamNames[(lastReason >= 0 && lastReason < SeamNames.Length) ? lastReason : 0] + ", gap " + lastGap + ", shift " + lastShift.ToString("0.00", CultureInfo.InvariantCulture) + " frames."));
}
}
}
private bool NotDraining(float buffered)
{
if (Rate <= 0.001f)
{
_pausedAt = Environment.TickCount;
_pausedBuffered = buffered;
return false;
}
if (buffered < _pausedBuffered - 0.05f)
{
_pausedAt = Environment.TickCount;
_pausedBuffered = buffered;
return false;
}
if (Environment.TickCount - _pausedAt < 3000)
{
return false;
}
if (Log != null && Verbose)
{
Log.LogInfo((object)("[AmbientMuter] Spotify music: backlog stuck at " + buffered.ToString("0.00") + " s, resuming Spotify."));
}
return true;
}
private void Status(int now)
{
if (Log != null && now - _lastStatus >= 5000)
{
_lastStatus = now;
if ((_calls <= 0 || now - _lastPull >= 500) && !_deafWarned)
{
_deafWarned = true;
Log.LogWarning((object)("[AmbientMuter] Spotify music: the game is not pulling audio from the player (calls " + _calls + ")."));
}
if (Verbose)
{
int exit = _exit;
Log.LogInfo((object)("[AmbientMuter] Spotify music: calls " + _calls + ", last " + ExitNames[(exit >= 0 && exit < ExitNames.Length) ? exit : 0] + ", buffered " + ((float)BC_Available() / (float)_sampleRate).ToString("0.00") + " s, held " + (_frozen ? "frozen" : (_wePaused ? "paused" : "no")) + ", ms since sound " + BC_MsSinceSound() + ", listener " + ListenerName + ", game peak " + Db(GamePeak) + ", music peak " + Db(MusicPeak) + ", duck " + Db(DuckDeepest)));
GamePeak = (MusicPeak = 0f);
DuckDeepest = 1f;
}
}
}
private static string Db(float v)
{
return ((v > 1E-06f) ? (20.0 * Math.Log10(v)) : (-120.0)).ToString("0.0", CultureInfo.InvariantCulture) + " dB";
}
private void Detach(bool restore)
{
_live = false;
if (_frozen)
{
BC_Thaw();
_frozen = false;
}
if (_started)
{
BC_Stop();
_started = false;
}
if (restore && _pid != 0)
{
if (_wePaused)
{
BC_Media(_pid, 46);
}
if (_haveOrig && BC_SetVolume(_pid, _orig, out var _) > 0)
{
_haveOrig = false;
try
{
File.Delete(StatePath);
}
catch
{
}
}
}
_wePaused = false;
_failLogged = false;
}
private bool TryReadState(out float v)
{
v = 1f;
try
{
string s = File.ReadAllText(StatePath).Trim();
v = float.Parse(s, CultureInfo.InvariantCulture);
if (v <= 0.0002f || v > 1f)
{
v = 1f;
}
return true;
}
catch
{
return false;
}
}
private void WriteState(float v)
{
try
{
File.WriteAllText(StatePath, v.ToString("R", CultureInfo.InvariantCulture));
}
catch
{
}
}
internal void Mix(float[] data, int channels)
{
_calls++;
_lastPull = Environment.TickCount;
if (!_live)
{
_primed = false;
_exit = 0;
return;
}
if (Environment.TickCount - _discardUntil < 0)
{
BC_Consume(BC_Available());
_primed = false;
_exit = 1;
return;
}
float num = Rate;
if (num <= 0.001f)
{
_exit = 2;
return;
}
if (num > 4f)
{
num = 4f;
}
int num2 = data.Length / channels;
int num3 = (int)(_frac + (double)((float)num2 * num)) + 2;
if (num3 * 2 > _tmp.Length)
{
_exit = 7;
return;
}
int num4 = BC_Available();
if (!_primed)
{
if (num4 < num3 + (int)(0.1f * (float)_sampleRate))
{
_exit = 3;
return;
}
_primed = true;
}
if (num4 < num3)
{
_primed = false;
_exit = 4;
return;
}
if (BC_Peek(_tmp, 0, num3) < num3)
{
_primed = false;
_exit = 4;
return;
}
for (int i = 0; i < num3 * 2; i++)
{
float num5 = _tmp[i];
if (num5 > 0.003f || num5 < -0.003f)
{
_resilence = true;
BC_Consume(num4);
_primed = false;
_exit = 5;
return;
}
}
float num6 = 10000f * Volume;
float duck = _duck;
float num7 = (Duck(data, num3, num6, num2) - duck) / (float)num2;
double num8 = _frac;
int num9 = 0;
while (num9 < num2)
{
int num10 = (int)num8;
float num11 = (float)(num8 - (double)num10);
int num12 = num10 * 2;
float num13 = num6 * (duck + num7 * (float)(num9 + 1));
float num14 = (_tmp[num12] + (_tmp[num12 + 2] - _tmp[num12]) * num11) * num13;
float num15 = (_tmp[num12 + 1] + (_tmp[num12 + 3] - _tmp[num12 + 1]) * num11) * num13;
if (num14 > 1f)
{
num14 = 1f;
}
else if (num14 < -1f)
{
num14 = -1f;
}
if (num15 > 1f)
{
num15 = 1f;
}
else if (num15 < -1f)
{
num15 = -1f;
}
int num16 = num9 * channels;
if (channels == 1)
{
data[num16] += 0.5f * (num14 + num15);
}
else
{
data[num16] += num14;
data[num16 + 1] += num15;
}
num9++;
num8 += (double)num;
}
int num17 = (int)num8;
_frac = num8 - (double)num17;
BC_Consume(num17);
_exit = 6;
}
private float Duck(float[] data, int need, float g, int frames)
{
float num = (float)frames / (float)_sampleRate;
double num2 = 0.0;
for (int i = 0; i < data.Length; i++)
{
num2 += (double)(data[i] * data[i]);
}
float num3 = (float)Math.Sqrt(num2 / (double)data.Length);
double num4 = 0.0;
for (int j = 0; j < need * 2; j++)
{
num4 += (double)(_tmp[j] * _tmp[j]);
}
float num5 = (float)Math.Sqrt(num4 / (double)(need * 2)) * g;
if (num3 >= _gameEnv)
{
_gameEnv = num3;
_gameHeld = 0f;
}
else if ((_gameHeld += num) > 0.3f)
{
_gameEnv *= (float)Math.Exp((0f - num) / 0.05f);
}
_musicEnv = ((num5 > _musicEnv) ? num5 : (_musicEnv * (float)Math.Exp((0f - num) / 0.25f)));
if (num3 > GamePeak)
{
GamePeak = num3;
}
if (num5 > MusicPeak)
{
MusicPeak = num5;
}
float num6 = 1f;
if (DuckOn && _gameEnv > 0.1f && _musicEnv > 0.0316f)
{
num6 = _gameEnv * 0.5f / _musicEnv;
float num7 = (float)Math.Pow(10.0, (double)(0f - DuckMaxDb) / 20.0);
if (num6 < num7)
{
num6 = num7;
}
if (num6 > 1f)
{
num6 = 1f;
}
}
_duck = ((num6 < _duck) ? num6 : (_duck + (num6 - _duck) * (1f - (float)Math.Exp((0f - num) / 0.4f))));
if (_duck < DuckDeepest)
{
DuckDeepest = _duck;
}
return _duck;
}
}
internal sealed class MusicTap : MonoBehaviour
{
private void OnAudioFilterRead(float[] data, int channels)
{
SpotifyMusic instance = SpotifyMusic.Instance;
if ((Object)(object)instance != (Object)null)
{
instance.Mix(data, channels);
}
}
}