Please disclose if any significant portion of your mod was created using AI tools by adding the 'AI Generated' category. Failing to do so may result in the mod being removed from Thunderstore.
Decompiled source of NotSoLoud v1.0.1
NotSoLoud.dll
Decompiled 3 days agousing System; using System.Collections.Generic; using System.Diagnostics; using System.IO; using System.Reflection; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using System.Runtime.Versioning; using BepInEx; using BepInEx.Configuration; using BepInEx.Logging; using HarmonyLib; using Microsoft.CodeAnalysis; using UnityEngine; using UnityEngine.Audio; [assembly: CompilationRelaxations(8)] [assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)] [assembly: Debuggable(DebuggableAttribute.DebuggingModes.Default | DebuggableAttribute.DebuggingModes.DisableOptimizations | DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints | DebuggableAttribute.DebuggingModes.EnableEditAndContinue)] [assembly: AssemblyTitle("NotSoLoud")] [assembly: AssemblyDescription("Routes every unrouted Valheim sound effect - vanilla or modded - through the Sfx audio mixer group.")] [assembly: AssemblyConfiguration("")] [assembly: AssemblyCompany("")] [assembly: AssemblyProduct("NotSoLoud")] [assembly: AssemblyCopyright("Copyright © 2026")] [assembly: AssemblyTrademark("")] [assembly: ComVisible(false)] [assembly: Guid("7E6C6C3A-6E36-4B4E-9E1F-2E4E6E7C9F1D")] [assembly: AssemblyFileVersion("1.0.0.0")] [assembly: TargetFramework(".NETFramework,Version=v4.8", FrameworkDisplayName = ".NET Framework 4.8")] [assembly: AssemblyVersion("1.0.0.0")] [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 NotSoLoud { [BepInPlugin("OdinPlus.NotSoLoud", "NotSoLoud", "1.0.1")] public class NotSoLoudPlugin : BaseUnityPlugin { public enum Toggle { On = 1, Off = 0 } internal const string ModName = "NotSoLoud"; internal const string ModVersion = "1.0.1"; internal const string Author = "OdinPlus"; private const string ModGUID = "OdinPlus.NotSoLoud"; private static string ConfigFileName = "OdinPlus.NotSoLoud.cfg"; private static string ConfigFileFullPath; private readonly Harmony _harmony = new Harmony("OdinPlus.NotSoLoud"); public static readonly ManualLogSource NotSoLoudLogger; internal static ConfigEntry<Toggle> ModEnabled; internal static ConfigEntry<float> MaxCustomVolume; internal static ConfigEntry<Toggle> VerboseAudioLog; public void Awake() { //IL_004f: Unknown result type (might be due to invalid IL or missing references) //IL_0059: Expected O, but got Unknown ModEnabled = ((BaseUnityPlugin)this).Config.Bind<Toggle>("General", "Enabled", Toggle.On, "If on, routes every sound effect in the game - vanilla or from any mod - whose audio mixer group hasn't already been set, through Valheim's own Sfx mixer group, and, when a modded sound shares its clip name with a known vanilla sound, plays the real vanilla clip instead of the mod's own (often louder) copy. Vanilla sound effects are never touched. Turn off to disable this mod entirely without uninstalling it."); MaxCustomVolume = ((BaseUnityPlugin)this).Config.Bind<float>("General", "MaxCustomVolume", 0.4f, new ConfigDescription("Last-resort fallback ceiling applied to a modded sound effect's own linear volume, but ONLY when its clip name doesn't match any known vanilla sound (in which case the real vanilla clip and volume are used instead). This only matters for genuinely new custom content with nothing vanilla to compare against.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0f, 1f), Array.Empty<object>())); VerboseAudioLog = ((BaseUnityPlugin)this).Config.Bind<Toggle>("Debugging", "VerboseAudioLog", Toggle.Off, "If on, logs every known vanilla sound effect (prefab name, clip name, volume range) as it's discovered, and every substitution/clamp this mod actually performs. Off by default since it can be spammy - meant for troubleshooting."); _harmony.PatchAll(); NotSoLoudLogger.LogInfo((object)string.Format("{0} {1} loaded - normalizing unrouted/modded sound effects (fallback ceiling {2:0.00}).", "NotSoLoud", "1.0.1", MaxCustomVolume.Value)); } static NotSoLoudPlugin() { string configPath = Paths.ConfigPath; char directorySeparatorChar = Path.DirectorySeparatorChar; ConfigFileFullPath = configPath + directorySeparatorChar + ConfigFileName; NotSoLoudLogger = Logger.CreateLogSource("NotSoLoud"); } } [HarmonyPatch] internal static class ZSFX_Awake_Patch { [HarmonyPostfix] [HarmonyPatch(typeof(ZSFX), "Awake")] private static void Postfix(ZSFX __instance) { if (NotSoLoudPlugin.ModEnabled.Value == NotSoLoudPlugin.Toggle.Off) { return; } AudioSource component = ((Component)__instance).GetComponent<AudioSource>(); if ((Object)(object)component == (Object)null) { return; } string rootName = NameUtils.GetRootName(((Component)__instance).transform); if ((Object)(object)component.outputAudioMixerGroup != (Object)null) { VanillaAudioCalibration.Record(__instance, rootName); return; } if (VanillaAudioCalibration.TryMatch(__instance.m_audioClips, out var profile)) { __instance.m_audioClips = profile.Clips; __instance.m_minVol = profile.MinVol; __instance.m_maxVol = profile.MaxVol; __instance.m_minPitch = profile.MinPitch; __instance.m_maxPitch = profile.MaxPitch; NameUtils.LogSwappedOnce(rootName); } else { float value = NotSoLoudPlugin.MaxCustomVolume.Value; float num = Mathf.Min(__instance.m_minVol, value); float num2 = Mathf.Min(__instance.m_maxVol, value); if (num2 < __instance.m_maxVol || num < __instance.m_minVol) { NameUtils.LogNormalizedOnce(rootName, __instance.m_audioClips, __instance.m_maxVol, num2); __instance.m_minVol = num; __instance.m_maxVol = num2; } } SfxGroupResolver.RouteIfUnrouted(component); } } internal static class SfxGroupResolver { private static AudioMixerGroup _sfxGroup; internal static AudioMixerGroup Resolve() { if ((Object)(object)_sfxGroup != (Object)null) { return _sfxGroup; } AudioMan val = Object.FindFirstObjectByType<AudioMan>(); AudioMixer val2 = (((Object)(object)val != (Object)null) ? val.m_masterMixer : null); if ((Object)(object)val2 == (Object)null) { return null; } AudioMixerGroup[] array = val2.FindMatchingGroups("Sfx"); if (array.Length == 0) { array = val2.FindMatchingGroups("SFX"); } if (array.Length == 0) { return null; } _sfxGroup = array[0]; return _sfxGroup; } internal static void RouteIfUnrouted(AudioSource source) { if (!((Object)(object)source == (Object)null) && !((Object)(object)source.outputAudioMixerGroup != (Object)null)) { AudioMixerGroup val = Resolve(); if ((Object)(object)val != (Object)null) { source.outputAudioMixerGroup = val; } } } } internal static class VanillaAudioCalibration { internal readonly struct Profile { internal readonly AudioClip[] Clips; internal readonly float MinVol; internal readonly float MaxVol; internal readonly float MinPitch; internal readonly float MaxPitch; internal Profile(AudioClip[] clips, float minVol, float maxVol, float minPitch, float maxPitch) { Clips = clips; MinVol = minVol; MaxVol = maxVol; MinPitch = minPitch; MaxPitch = maxPitch; } } private static readonly Dictionary<string, Profile> Table = new Dictionary<string, Profile>(); private static string Normalize(string clipName) { return clipName?.Trim().ToLowerInvariant(); } internal static void Record(ZSFX zsfx, string rootName) { if (zsfx?.m_audioClips == null) { return; } Profile value = new Profile(zsfx.m_audioClips, zsfx.m_minVol, zsfx.m_maxVol, zsfx.m_minPitch, zsfx.m_maxPitch); AudioClip[] audioClips = zsfx.m_audioClips; foreach (AudioClip val in audioClips) { if ((Object)(object)val == (Object)null) { continue; } string text = Normalize(((Object)val).name); if (!string.IsNullOrEmpty(text) && !Table.ContainsKey(text)) { Table[text] = value; if (NotSoLoudPlugin.VerboseAudioLog.Value == NotSoLoudPlugin.Toggle.On) { NotSoLoudPlugin.NotSoLoudLogger.LogInfo((object)$"[known vanilla sound] prefab '{rootName}' clip '{((Object)val).name}' vol {zsfx.m_minVol:0.00}-{zsfx.m_maxVol:0.00}"); } } } } internal static bool TryMatch(AudioClip[] clips, out Profile profile) { profile = default(Profile); if (clips == null) { return false; } foreach (AudioClip val in clips) { if (!((Object)(object)val == (Object)null)) { string text = Normalize(((Object)val).name); if (!string.IsNullOrEmpty(text) && Table.TryGetValue(text, out profile)) { return true; } } } return false; } internal static bool TryMatch(AudioClip clip, out Profile profile) { profile = default(Profile); string text = (((Object)(object)clip != (Object)null) ? Normalize(((Object)clip).name) : null); return !string.IsNullOrEmpty(text) && Table.TryGetValue(text, out profile); } } internal static class NameUtils { private static readonly HashSet<string> LoggedKeys = new HashSet<string>(); internal static string GetRootName(Transform t) { return ((Object)t.root).name.Replace("(Clone)", "").Trim(); } private static void LogOnce(string key, string message) { if (LoggedKeys.Add(key)) { NotSoLoudPlugin.NotSoLoudLogger.LogInfo((object)message); } } internal static void LogSwappedOnce(string rootName) { LogOnce(rootName + "|swap", "'" + rootName + "' reuses a vanilla sound - playing the real vanilla clip instead of its own copy."); } internal static void LogNormalizedOnce(string rootName, AudioClip[] clips, float oldVol, float newVol) { string text = ((clips != null && clips.Length != 0 && (Object)(object)clips[0] != (Object)null) ? ((Object)clips[0]).name : "?"); LogOnce(rootName + "|vol|" + text, $"'{rootName}' (clip '{text}') doesn't match any known vanilla sound - capped {oldVol:0.00} -> {newVol:0.00} (fallback ceiling)."); } internal static void LogNormalizedOnce(string rootName, AudioClip clip, float oldVol, float newVol) { LogNormalizedOnce(rootName, (AudioClip[])(object)((!((Object)(object)clip != (Object)null)) ? null : new AudioClip[1] { clip }), oldVol, newVol); } internal static void LogSliderCorrectedOnce(string rootName) { LogOnce(rootName + "|slider", "'" + rootName + "' isn't routed through Valheim's mixer - applying the Sound Effects slider to it directly, every frame, so it still obeys the slider."); } } [HarmonyPatch] internal static class AudioSource_Play_Patch { private static void ClampAndRoute(AudioSource __instance) { if (NotSoLoudPlugin.ModEnabled.Value == NotSoLoudPlugin.Toggle.Off || (Object)(object)__instance == (Object)null) { return; } string rootName = NameUtils.GetRootName(((Component)__instance).transform); if (!((Object)(object)__instance.outputAudioMixerGroup != (Object)null)) { AudioClip clip = __instance.clip; VanillaAudioCalibration.Profile profile; float num = (VanillaAudioCalibration.TryMatch(clip, out profile) ? profile.MaxVol : NotSoLoudPlugin.MaxCustomVolume.Value); if (__instance.volume > num) { NameUtils.LogNormalizedOnce(rootName, clip, __instance.volume, num); __instance.volume = num; } SfxGroupResolver.RouteIfUnrouted(__instance); } } [HarmonyPrefix] [HarmonyPatch(typeof(AudioSource), "Play", new Type[] { })] private static void Play_Prefix(AudioSource __instance) { ClampAndRoute(__instance); } [HarmonyPrefix] [HarmonyPatch(typeof(AudioSource), "PlayDelayed")] private static void PlayDelayed_Prefix(AudioSource __instance) { ClampAndRoute(__instance); } [HarmonyPrefix] [HarmonyPatch(typeof(AudioSource), "PlayScheduled")] private static void PlayScheduled_Prefix(AudioSource __instance) { ClampAndRoute(__instance); } } [HarmonyPatch] internal static class AudioSource_PlayOneShot_Patch { [HarmonyPrefix] [HarmonyPatch(typeof(AudioSource), "PlayOneShot", new Type[] { typeof(AudioClip), typeof(float) })] private static void Prefix(AudioSource __instance, AudioClip clip, ref float volumeScale) { if (NotSoLoudPlugin.ModEnabled.Value == NotSoLoudPlugin.Toggle.Off || (Object)(object)__instance == (Object)null) { return; } string rootName = NameUtils.GetRootName(((Component)__instance).transform); if (!((Object)(object)__instance.outputAudioMixerGroup != (Object)null)) { VanillaAudioCalibration.Profile profile; float num = (VanillaAudioCalibration.TryMatch(clip, out profile) ? profile.MaxVol : NotSoLoudPlugin.MaxCustomVolume.Value); if (volumeScale > num) { NameUtils.LogNormalizedOnce(rootName, clip, volumeScale, num); volumeScale = num; } SfxGroupResolver.RouteIfUnrouted(__instance); } } } [HarmonyPatch] internal static class ZSFX_CustomUpdate_Patch { [HarmonyPostfix] [HarmonyPatch(typeof(ZSFX), "CustomUpdate")] private static void Postfix(ZSFX __instance) { if (NotSoLoudPlugin.ModEnabled.Value != NotSoLoudPlugin.Toggle.Off) { AudioSource component = ((Component)__instance).GetComponent<AudioSource>(); if (!((Object)(object)component == (Object)null) && component.isPlaying && !((Object)(object)component.outputAudioMixerGroup != (Object)null)) { string rootName = NameUtils.GetRootName(((Component)__instance).transform); component.volume *= AudioMan.GetSFXVolume(); NameUtils.LogSliderCorrectedOnce(rootName); } } } } }