using System;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Reflection;
using System.Reflection.Emit;
using System.Runtime.CompilerServices;
using System.Runtime.Versioning;
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("Debug")]
[assembly: AssemblyFileVersion("1.0.0.0")]
[assembly: AssemblyInformationalVersion("1.0.0")]
[assembly: AssemblyProduct("AmbientMuter")]
[assembly: AssemblyTitle("AmbientMuter")]
[assembly: AssemblyVersion("1.0.0.0")]
namespace AmbientMuter;
[BepInPlugin("h3vr.invent60.ambientmuter", "AmbientMuter", "2.1.0")]
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;
internal static ManualLogSource Log;
private Harmony _harmony;
private bool _calibrated;
private int _calibrationAttempts;
private AmbientMuterRunner _runner;
internal static bool IsU120Plus;
private void Awake()
{
//IL_0059: Unknown result type (might be due to invalid IL or missing references)
//IL_0063: Expected O, but got Unknown
Log = ((BaseUnityPlugin)this).Logger;
BindConfig();
IsU120Plus = (object)AccessTools.TypeByName("FistVR.VaultFileDisplayMode") == null;
((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(CursedLLPitchFix));
try
{
_harmony.PatchAll(typeof(MagBoopTimerFix));
((BaseUnityPlugin)this).Logger.LogInfo((object)"[AmbientMuter] MagBoop slow-mo timer fix applied.");
}
catch
{
}
_runner = ((Component)this).gameObject.AddComponent<AmbientMuterRunner>();
_runner.Log = Log;
((BaseUnityPlugin)this).Logger.LogInfo((object)"[AmbientMuter] Loaded.");
}
private void BindConfig()
{
_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.");
}
private void Update()
{
float timeScale = Time.timeScale;
bool flag = timeScale < 0.9999f && timeScale > 0.001f;
bool wasInSlowMo = CursedLLPitchFix.WasInSlowMo;
if (flag)
{
CursedLLPitchFix.LastSlowTimeScale = timeScale;
}
CursedLLPitchFix.CachedTimeScale = timeScale;
CursedLLPitchFix.WasInSlowMo = flag;
if (wasInSlowMo && !flag)
{
CursedLLPitchFix.RestoreScaledSources();
}
if (CursedLLPitchFix.PatcherDetected && !_calibrated && _calibrationAttempts < 5 && Time.timeScale < 0.99f && Time.timeScale > 0.001f)
{
_calibrationAttempts++;
MeasurePitchCompound();
_calibrated = CursedLLPitchFix.Initialized;
}
if (_cfgMuteMusic.Value)
{
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 = CursedLLPitchFix.CachedTimeScale;
if (cachedTimeScale <= 0.001f || cachedTimeScale >= 0.999f)
{
return;
}
GameObject val = new GameObject("__AMCalib");
AudioSource val2 = val.AddComponent<AudioSource>();
val2.volume = 0f;
val2.mute = true;
val2.pitch = 1f;
float pitch = val2.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)
{
CursedLLPitchFix.CompoundLevels = num3;
CursedLLPitchFix.ExtraCompounders = Mathf.Max(0, num3 - 1);
CursedLLPitchFix.CursedActive = true;
CursedLLPitchFix.Initialized = true;
((BaseUnityPlugin)this).Logger.LogInfo((object)$"[AmbientMuter] CursedLL calibrated: {num3} compound level(s), {CursedLLPitchFix.ExtraCompounders} extra (ts={cachedTimeScale:F3})");
}
}
}
catch (Exception ex)
{
((BaseUnityPlugin)this).Logger.LogWarning((object)("[AmbientMuter] CursedLL calibration error: " + ex.Message));
}
}
private void OnSceneLoaded(Scene scene, LoadSceneMode mode)
{
if (IsU120Plus)
{
MusicBusMuter.Reset();
}
else
{
LegacyMusicBusMuter.Reset();
}
if (_cfgMuteAmbient.Value)
{
((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;
}
FVRMovementManager componentInParent = ((Component)val).GetComponentInParent<FVRMovementManager>();
if ((Object)(object)componentInParent != (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 void Awake()
{
_plugin = ((Component)this).GetComponent<AmbientMuterPlugin>();
}
private void Start()
{
((MonoBehaviour)this).StartCoroutine(DetectCursedLL());
}
internal void RunMuteAmbient()
{
_plugin?.RunMuteAmbientSources();
}
internal IEnumerator DetectCursedLL()
{
yield return null;
try
{
PropertyInfo property = typeof(AudioSource).GetProperty("pitch");
if ((object)property == null)
{
yield break;
}
MethodInfo setMethod = property.GetSetMethod();
if ((object)setMethod == null)
{
yield break;
}
Patches patchInfo = Harmony.GetPatchInfo((MethodBase)setMethod);
if (patchInfo == null)
{
yield break;
}
int num = 0;
foreach (Patch prefix in patchInfo.Prefixes)
{
if (!string.Equals(prefix.owner, "h3vr.invent60.ambientmuter", StringComparison.OrdinalIgnoreCase))
{
Log.LogInfo((object)("[AmbientMuter] pitch-setter prefix: " + prefix.owner));
num++;
}
}
foreach (Patch postfix in patchInfo.Postfixes)
{
if (!string.Equals(postfix.owner, "h3vr.invent60.ambientmuter", StringComparison.OrdinalIgnoreCase))
{
Log.LogInfo((object)("[AmbientMuter] pitch-setter postfix: " + postfix.owner));
num++;
}
}
foreach (Patch transpiler in patchInfo.Transpilers)
{
if (!string.Equals(transpiler.owner, "h3vr.invent60.ambientmuter", StringComparison.OrdinalIgnoreCase))
{
Log.LogInfo((object)("[AmbientMuter] pitch-setter transpiler: " + transpiler.owner));
num++;
}
}
if (num > 0)
{
CursedLLPitchFix.PatcherDetected = true;
Log.LogInfo((object)("[AmbientMuter] pitch-setter patcher(s) found (" + num + "). Calibration deferred to first slow-mo."));
}
else
{
Log.LogInfo((object)"[AmbientMuter] CursedLL pitch fix: not needed");
}
}
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 CursedLLPitchFix
{
public static bool PatcherDetected = false;
public static bool Initialized = false;
public static bool CursedActive = false;
public static int CompoundLevels = 0;
public static int ExtraCompounders = 0;
public static float CachedTimeScale = 1f;
public static bool WasInSlowMo = false;
public static float LastSlowTimeScale = 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 && CursedActive && !(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 || !CursedActive || 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 lastSlowTimeScale = LastSlowTimeScale;
if (_scaledSources.Count == 0)
{
return;
}
if (lastSlowTimeScale <= 0.001f || lastSlowTimeScale >= 0.9999f)
{
_scaledSources.Clear();
return;
}
foreach (AudioSource scaledSource in _scaledSources)
{
if (!((Object)(object)scaledSource == (Object)null))
{
scaledSource.pitch /= lastSlowTimeScale;
}
}
_scaledSources.Clear();
}
}