using System; using System.Collections.Generic; using System.Diagnostics; using System.Globalization; using System.IO; using System.Reflection; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using System.Runtime.Versioning; using System.Text; using BepInEx; using BepInEx.Configuration; using BepInEx.ConfigurationManager; using HarmonyLib; using TMPro; using UnityEngine; using UnityEngine.Events; using UnityEngine.UI; using Valheim.SettingsGui; [assembly: CompilationRelaxations(8)] [assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)] [assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)] [assembly: AssemblyTitle("AmbienceSoundConfig")] [assembly: AssemblyDescription("")] [assembly: AssemblyConfiguration("")] [assembly: AssemblyCompany("")] [assembly: AssemblyProduct("Valheim_Ambience_Mute")] [assembly: AssemblyCopyright("Copyright © 2025")] [assembly: AssemblyTrademark("")] [assembly: ComVisible(false)] [assembly: Guid("f01ac134-75c0-45a2-be66-fe63019a4264")] [assembly: AssemblyFileVersion("1.0.0.0")] [assembly: TargetFramework(".NETFramework,Version=v4.7.2", FrameworkDisplayName = ".NET Framework 4.7.2")] [assembly: AssemblyVersion("1.0.0.0")] namespace BepInEx.ConfigurationManager { public sealed class ConfigurationManagerAttributes { public Action CustomDrawer; public bool? HideDefaultButton; } } namespace AmbienceSoundConfig { [BepInPlugin("com.draconicvelum.ambiencesoundconfig", "Ambience Sound Config", "2.7.8")] public class AmbienceSoundConfig : BaseUnityPlugin { private sealed class CustomSectionSnapshot { private readonly string _extraSfxSplit; private readonly string _extraClipsSplit; private CustomSectionSnapshot(string extraSfxSplit, string extraClipsSplit) { _extraSfxSplit = extraSfxSplit; _extraClipsSplit = extraClipsSplit; } internal static CustomSectionSnapshot Capture(string path) { if (string.IsNullOrEmpty(path) || !File.Exists(path)) { return new CustomSectionSnapshot(null, null); } string text = File.ReadAllText(path); return new CustomSectionSnapshot(ReadRawSection(text, "Extra SFX Split"), ReadRawSection(text, "Extra Clips Split")); } internal void Restore(string path) { if (!string.IsNullOrEmpty(path) && File.Exists(path)) { string text = File.ReadAllText(path); if (!string.IsNullOrWhiteSpace(_extraSfxSplit)) { text = RemoveSection(text, "Extra SFX Split"); } if (!string.IsNullOrWhiteSpace(_extraClipsSplit)) { text = RemoveSection(text, "Extra Clips Split"); } StringBuilder stringBuilder = new StringBuilder(text.TrimEnd(Array.Empty())); if (!string.IsNullOrWhiteSpace(_extraSfxSplit)) { stringBuilder.AppendLine(); stringBuilder.AppendLine(); stringBuilder.Append(_extraSfxSplit.TrimEnd(Array.Empty())); } if (!string.IsNullOrWhiteSpace(_extraClipsSplit)) { stringBuilder.AppendLine(); stringBuilder.AppendLine(); stringBuilder.Append(_extraClipsSplit.TrimEnd(Array.Empty())); } File.WriteAllText(path, stringBuilder.ToString() + Environment.NewLine); } } } public const string PluginVersion = "2.7.8"; public const string ExtraSfxSplitSection = "Extra SFX Split"; public const string ExtraClipSplitSection = "Extra Clips Split"; public static ConfigEntry ConfigVersion; public static ConfigEntry ReloadConfigButton; public static ConfigEntry VolumeSliderMax; public static ConfigEntry MasterVolume; public static ConfigEntry WindVolume; public static ConfigEntry OceanVolume; public static ConfigEntry AmbientLoopVolume; public static ConfigEntry ShieldHumVolume; public static ConfigEntry ExtraSfxList; public static ConfigEntry ExtraSfxVolume; public static ConfigEntry ExtraClipList; public static ConfigEntry ExtraClipVolume; public static ConfigEntry EnableSoundLogging; public static ConfigEntry LogOnlyUnique; public static string PluginConfigFilePath; private static readonly Harmony harmony = new Harmony("com.draconicvelum.ambiencesoundconfig"); private void Awake() { //IL_0092: Unknown result type (might be due to invalid IL or missing references) //IL_009c: Expected O, but got Unknown //IL_00cf: Unknown result type (might be due to invalid IL or missing references) //IL_00d9: Expected O, but got Unknown //IL_010c: Unknown result type (might be due to invalid IL or missing references) //IL_0116: Expected O, but got Unknown //IL_0149: Unknown result type (might be due to invalid IL or missing references) //IL_0153: Expected O, but got Unknown //IL_0186: Unknown result type (might be due to invalid IL or missing references) //IL_0190: Expected O, but got Unknown //IL_01c3: Unknown result type (might be due to invalid IL or missing references) //IL_01cd: Expected O, but got Unknown //IL_0200: Unknown result type (might be due to invalid IL or missing references) //IL_020a: Expected O, but got Unknown //IL_0261: Unknown result type (might be due to invalid IL or missing references) //IL_026b: Expected O, but got Unknown //IL_02c2: Unknown result type (might be due to invalid IL or missing references) //IL_02cc: Expected O, but got Unknown PluginConfigFilePath = ((BaseUnityPlugin)this).Config.ConfigFilePath; CustomSectionSnapshot startupCustomSections = CustomSectionSnapshot.Capture(PluginConfigFilePath); bool configNeedsUpdate = IsOlderConfigVersion(ReadConfigVersion(), "2.7.8"); ConfigVersion = ((BaseUnityPlugin)this).Config.Bind("General", "Config Version", "2.7.8", "Internal config version. The mod updates older config files to the latest format while keeping your values."); ReloadConfigButton = ((BaseUnityPlugin)this).Config.Bind("General", "Reload Config", false, new ConfigDescription("Button for compatible config manager mods. Reloads this config file and reapplies split SFX/clip volumes.", (AcceptableValueBase)null, new object[1] { new ConfigurationManagerAttributes { CustomDrawer = DrawReloadConfigButton, HideDefaultButton = true } })); VolumeSliderMax = ((BaseUnityPlugin)this).Config.Bind("General", "Volume Slider Max", 1f, new ConfigDescription("Config-only maximum value used by this mod's volume sliders and volume config entries. Default is 1.0; valid range is 0.0 to 5.0.", (AcceptableValueBase)(object)new AcceptableValueRange(0f, 5f), Array.Empty())); MasterVolume = ((BaseUnityPlugin)this).Config.Bind("Ambience", "Master Volume", 0.25f, new ConfigDescription("Controls overall ambience loudness multiplier.", (AcceptableValueBase)(object)new AcceptableValueRange(0f, 5f), Array.Empty())); WindVolume = ((BaseUnityPlugin)this).Config.Bind("Ambience", "Wind Volume", 1f, new ConfigDescription("Volume for wind ambience (multiplied by Master Volume).", (AcceptableValueBase)(object)new AcceptableValueRange(0f, 5f), Array.Empty())); OceanVolume = ((BaseUnityPlugin)this).Config.Bind("Ambience", "Ocean Volume", 1f, new ConfigDescription("Volume for ocean ambience (multiplied by Master Volume).", (AcceptableValueBase)(object)new AcceptableValueRange(0f, 5f), Array.Empty())); AmbientLoopVolume = ((BaseUnityPlugin)this).Config.Bind("Ambience", "Ambient Loop Volume", 1f, new ConfigDescription("Volume for background ambient loop (multiplied by Master Volume).", (AcceptableValueBase)(object)new AcceptableValueRange(0f, 5f), Array.Empty())); ShieldHumVolume = ((BaseUnityPlugin)this).Config.Bind("Ambience", "Shield Hum Volume", 1f, new ConfigDescription("Volume for shield dome hum (multiplied by Master Volume).", (AcceptableValueBase)(object)new AcceptableValueRange(0f, 5f), Array.Empty())); ExtraSfxList = ((BaseUnityPlugin)this).Config.Bind("Extra SFX", "Extra SFX Prefabs", "", "Legacy comma separated list of ZSFX prefab names affected by Extra SFX Volume. For per-sound volumes, add entries under [Extra SFX Split]."); ExtraSfxVolume = ((BaseUnityPlugin)this).Config.Bind("Extra SFX", "Extra SFX Volume", 1f, new ConfigDescription("Legacy shared volume multiplier for sound effects listed in Extra SFX Prefabs.", (AcceptableValueBase)(object)new AcceptableValueRange(0f, 5f), Array.Empty())); ExtraClipList = ((BaseUnityPlugin)this).Config.Bind("Extra Clips", "Extra Clip Names", "", "Legacy comma separated list of AudioClip names affected by Extra Clip Volume. For per-clip volumes, add entries under [Extra Clips Split]."); ExtraClipVolume = ((BaseUnityPlugin)this).Config.Bind("Extra Clips", "Extra Clip Volume", 1f, new ConfigDescription("Legacy shared volume multiplier for clip names listed in Extra Clip Names.", (AcceptableValueBase)(object)new AcceptableValueRange(0f, 5f), Array.Empty())); EnableSoundLogging = ((BaseUnityPlugin)this).Config.Bind("Logging", "Enable Sound Logging", false, "Logs all played SFX and AudioClips to console and a file next to this config."); LogOnlyUnique = ((BaseUnityPlugin)this).Config.Bind("Logging", "Log Only Unique", true, "If enabled, each sound name is logged only once per session."); VolumeSliderMax.SettingChanged += delegate { ClampAllVolumeConfigs(); AudioSourceFilter.Refresh(); AudioSourceFilter.ApplyToRunningSources(); ApplyAllVolumes(); SettingsInject.SyncUI(); SaveConfigPreservingCustomSections(((BaseUnityPlugin)this).Config); }; MasterVolume.SettingChanged += delegate { ClampVolumeConfig(MasterVolume); }; WindVolume.SettingChanged += delegate { ClampVolumeConfig(WindVolume); }; OceanVolume.SettingChanged += delegate { ClampVolumeConfig(OceanVolume); }; AmbientLoopVolume.SettingChanged += delegate { ClampVolumeConfig(AmbientLoopVolume); }; ShieldHumVolume.SettingChanged += delegate { ClampVolumeConfig(ShieldHumVolume); }; ExtraSfxList.SettingChanged += delegate { AudioSourceFilter.Refresh(); }; ExtraSfxVolume.SettingChanged += delegate { ClampVolumeConfig(ExtraSfxVolume); AudioSourceFilter.Refresh(); AudioSource[] array = Object.FindObjectsOfType(); for (int i = 0; i < array.Length; i++) { AudioSourceFilter.Apply(array[i]); } }; ExtraClipList.SettingChanged += delegate { AudioSourceFilter.Refresh(); AudioSourceFilter.ApplyToRunningSources(); }; ExtraClipVolume.SettingChanged += delegate { ClampVolumeConfig(ExtraClipVolume); AudioSourceFilter.Refresh(); AudioSourceFilter.ApplyToRunningSources(); }; ClampAllVolumeConfigs(); UpdateConfigFileIfNeeded(configNeedsUpdate, startupCustomSections); EnsureCustomVolumeSections(); AudioSourceFilter.Refresh(); SoundPlayLogger.Init(((BaseUnityPlugin)this).Config); harmony.PatchAll(typeof(AudioSource_DoAll_Patch)); harmony.PatchAll(typeof(AudioSource_PlayOneShotScale_Patch)); harmony.PatchAll(typeof(ZSFX_VolumeScale_Patch)); harmony.PatchAll(typeof(AudioMan_AmbienceVolume_Patch)); harmony.PatchAll(typeof(SettingsInject)); ((BaseUnityPlugin)this).Config.ConfigReloaded += OnReloaded; ((BaseUnityPlugin)this).Logger.LogInfo((object)"Ambience Sound Config (v2.7.8) loaded."); } private void OnReloaded(object sender, EventArgs e) { ClampAllVolumeConfigs(); EnsureCustomVolumeSections(); AudioSourceFilter.Refresh(); AudioSourceFilter.ApplyToRunningSources(); ApplyAllVolumes(); SettingsInject.SyncUI(); } public static void ReloadConfigFromManager() { try { ReloadConfigPreservingCustomSections(((ConfigEntryBase)ConfigVersion).ConfigFile); AudioSourceFilter.Refresh(); AudioSourceFilter.ApplyToRunningSources(); ApplyAllVolumes(); SettingsInject.SyncUI(); Debug.Log((object)"[AmbienceSoundConfig] Config reloaded from config manager button."); } catch (Exception ex) { Debug.LogWarning((object)("[AmbienceSoundConfig] Config manager reload failed: " + ex.Message)); } } private static void DrawReloadConfigButton(ConfigEntryBase entry) { if (GUILayout.Button("Reload Config", Array.Empty())) { ReloadConfigFromManager(); } } public static void ApplyAllVolumes() { AudioMan_AmbienceVolume_Patch.ApplyWindVolumeWrapper(); AudioMan_AmbienceVolume_Patch.ApplyOceanVolumeWrapper(); AudioMan_AmbienceVolume_Patch.ApplyAmbientLoopVolumeWrapper(); AudioMan_AmbienceVolume_Patch.ApplyShieldHumVolumeWrapper(); } public static float GetVolumeSliderMax() { return Mathf.Clamp(VolumeSliderMax.Value, 0f, 5f); } public static float ClampConfiguredVolume(float value) { return Mathf.Clamp(value, 0f, GetVolumeSliderMax()); } private static void ClampVolumeConfig(ConfigEntry entry) { float num = ClampConfiguredVolume(entry.Value); if (!Mathf.Approximately(entry.Value, num)) { entry.Value = num; } } private static void ClampAllVolumeConfigs() { ClampVolumeConfig(MasterVolume); ClampVolumeConfig(WindVolume); ClampVolumeConfig(OceanVolume); ClampVolumeConfig(AmbientLoopVolume); ClampVolumeConfig(ShieldHumVolume); ClampVolumeConfig(ExtraSfxVolume); ClampVolumeConfig(ExtraClipVolume); } private static string ReadConfigVersion() { if (string.IsNullOrEmpty(PluginConfigFilePath) || !File.Exists(PluginConfigFilePath)) { return null; } try { string[] array = File.ReadAllLines(PluginConfigFilePath); for (int i = 0; i < array.Length; i++) { string text = array[i].Trim(); if (text.StartsWith("Config Version =", StringComparison.OrdinalIgnoreCase)) { int num = text.IndexOf('='); if (num >= 0) { return text.Substring(num + 1).Trim(); } } } } catch (Exception ex) { Debug.LogWarning((object)("[AmbienceSoundConfig] Could not read config version: " + ex.Message)); } return null; } private static bool IsOlderConfigVersion(string currentVersion, string targetVersion) { if (string.IsNullOrEmpty(currentVersion)) { return File.Exists(PluginConfigFilePath); } if (!Version.TryParse(currentVersion, out Version result)) { return true; } if (!Version.TryParse(targetVersion, out Version result2)) { return false; } return result < result2; } private void UpdateConfigFileIfNeeded(bool configNeedsUpdate, CustomSectionSnapshot startupCustomSections) { if (!configNeedsUpdate) { return; } try { ConfigVersion.Value = "2.7.8"; SaveConfigPreservingCustomSections(((BaseUnityPlugin)this).Config, startupCustomSections); ((BaseUnityPlugin)this).Logger.LogInfo((object)"Updated config file to v2.7.8 while keeping existing values."); } catch (Exception ex) { ((BaseUnityPlugin)this).Logger.LogWarning((object)("Could not update config file: " + ex.Message)); } } internal static void EnsureCustomVolumeSections() { if (string.IsNullOrEmpty(PluginConfigFilePath)) { return; } try { if (File.Exists(PluginConfigFilePath)) { string text = RemoveLegacyEntryLines(File.ReadAllText(PluginConfigFilePath)); bool flag = text != File.ReadAllText(PluginConfigFilePath); StringBuilder stringBuilder = new StringBuilder(text.TrimEnd(Array.Empty())); if (!HasSection(text, "Extra SFX Split")) { AppendExtraSfxSplitSection(stringBuilder); flag = true; } if (!HasSection(text, "Extra Clips Split")) { AppendExtraClipsSplitSection(stringBuilder); flag = true; } if (flag) { File.WriteAllText(PluginConfigFilePath, stringBuilder.ToString() + Environment.NewLine); } } } catch (Exception ex) { Debug.LogWarning((object)("[AmbienceSoundConfig] Could not seed custom volume config sections: " + ex.Message)); } } internal static void SaveConfigPreservingCustomSections(ConfigFile config) { SaveConfigPreservingCustomSections(config, CustomSectionSnapshot.Capture(PluginConfigFilePath)); } private static void SaveConfigPreservingCustomSections(ConfigFile config, CustomSectionSnapshot snapshot) { config.Save(); snapshot.Restore(PluginConfigFilePath); EnsureCustomVolumeSections(); } private static void ReloadConfigPreservingCustomSections(ConfigFile config) { CustomSectionSnapshot customSectionSnapshot = CustomSectionSnapshot.Capture(PluginConfigFilePath); config.Reload(); customSectionSnapshot.Restore(PluginConfigFilePath); EnsureCustomVolumeSections(); } private static void AppendExtraSfxSplitSection(StringBuilder builder) { builder.AppendLine(); builder.AppendLine(); builder.AppendLine("[Extra SFX Split]"); builder.AppendLine("## Config-only per-sound SFX volumes. Add one ZSFX prefab per line."); builder.AppendLine("## Format: prefab_name = volume"); builder.AppendLine("## Example: sfx_boar_idle = 0.25"); } private static void AppendExtraClipsSplitSection(StringBuilder builder) { builder.AppendLine(); builder.AppendLine(); builder.AppendLine("[Extra Clips Split]"); builder.AppendLine("## Config-only per-clip volumes. Add one AudioClip name per line."); builder.AppendLine("## Format: clip_name = volume"); builder.AppendLine("## Example: MeadowAmbience = 0.25"); } private static string RemoveLegacyEntryLines(string text) { StringBuilder stringBuilder = new StringBuilder(); using (StringReader stringReader = new StringReader(text)) { string text2; while ((text2 = stringReader.ReadLine()) != null) { string text3 = text2.TrimStart(Array.Empty()); if (!text3.StartsWith("Extra SFX Entries =", StringComparison.OrdinalIgnoreCase) && !text3.StartsWith("Extra Clip Entries =", StringComparison.OrdinalIgnoreCase)) { stringBuilder.AppendLine(text2); } } } return stringBuilder.ToString().TrimEnd(Array.Empty()) + Environment.NewLine; } private static string ReadRawSection(string text, string sectionName) { StringBuilder stringBuilder = new StringBuilder(); bool flag = false; using (StringReader stringReader = new StringReader(text)) { string text2; while ((text2 = stringReader.ReadLine()) != null) { string text3 = text2.Trim(); if (text3.StartsWith("[") && text3.EndsWith("]")) { if (flag) { break; } flag = text3.Substring(1, text3.Length - 2).Trim().Equals(sectionName, StringComparison.OrdinalIgnoreCase); } if (flag) { stringBuilder.AppendLine(text2); } } } if (stringBuilder.Length <= 0) { return null; } return stringBuilder.ToString().TrimEnd(Array.Empty()) + Environment.NewLine; } private static string RemoveSection(string text, string sectionName) { StringBuilder stringBuilder = new StringBuilder(); bool flag = false; using (StringReader stringReader = new StringReader(text)) { string text2; while ((text2 = stringReader.ReadLine()) != null) { string text3 = text2.Trim(); if (text3.StartsWith("[") && text3.EndsWith("]")) { flag = text3.Substring(1, text3.Length - 2).Trim().Equals(sectionName, StringComparison.OrdinalIgnoreCase); } if (!flag) { stringBuilder.AppendLine(text2); } } } return stringBuilder.ToString(); } private static bool HasSection(string text, string sectionName) { using (StringReader stringReader = new StringReader(text)) { string text2; while ((text2 = stringReader.ReadLine()) != null) { text2 = text2.Trim(); if (text2.StartsWith("[") && text2.EndsWith("]") && text2.Substring(1, text2.Length - 2).Trim().Equals(sectionName, StringComparison.OrdinalIgnoreCase)) { return true; } } } return false; } } [HarmonyPatch(typeof(AudioMan))] public static class AudioMan_AmbienceVolume_Patch { private static readonly BindingFlags Flags = BindingFlags.Instance | BindingFlags.NonPublic; private static readonly Dictionary CachedFields = new Dictionary(); private static AudioSource GetPrivateSource(string fieldName) { if ((Object)(object)AudioMan.instance == (Object)null) { return null; } if (!CachedFields.TryGetValue(fieldName, out var value)) { value = typeof(AudioMan).GetField(fieldName, Flags); if (value != null) { CachedFields[fieldName] = value; } } object? obj = value?.GetValue(AudioMan.instance); return (AudioSource)((obj is AudioSource) ? obj : null); } public static void ApplyWindVolumeWrapper() { AudioSource privateSource = GetPrivateSource("m_windLoopSource"); if ((Object)(object)privateSource != (Object)null) { privateSource.volume = AmbienceSoundConfig.WindVolume.Value * AmbienceSoundConfig.MasterVolume.Value; } } public static void ApplyOceanVolumeWrapper() { AudioSource privateSource = GetPrivateSource("m_oceanAmbientSource"); if ((Object)(object)privateSource != (Object)null) { privateSource.volume = AmbienceSoundConfig.OceanVolume.Value * AmbienceSoundConfig.MasterVolume.Value; } } public static void ApplyAmbientLoopVolumeWrapper() { AudioSource privateSource = GetPrivateSource("m_ambientLoopSource"); if ((Object)(object)privateSource != (Object)null) { privateSource.volume = AmbienceSoundConfig.AmbientLoopVolume.Value * AmbienceSoundConfig.MasterVolume.Value; } } public static void ApplyShieldHumVolumeWrapper() { AudioSource privateSource = GetPrivateSource("m_shieldHumSource"); if ((Object)(object)privateSource != (Object)null) { privateSource.volume = AmbienceSoundConfig.ShieldHumVolume.Value * AmbienceSoundConfig.MasterVolume.Value; } } [HarmonyPostfix] [HarmonyPatch("UpdateWindAmbience")] public static void ApplyWindVolume(ref AudioSource ___m_windLoopSource) { ApplyWindVolumeWrapper(); } [HarmonyPostfix] [HarmonyPatch("UpdateOceanAmbiance")] public static void ApplyOceanVolume(ref AudioSource ___m_oceanAmbientSource) { ApplyOceanVolumeWrapper(); } [HarmonyPostfix] [HarmonyPatch("UpdateAmbientLoop")] public static void ApplyAmbientLoopVolume(ref AudioSource ___m_ambientLoopSource) { ApplyAmbientLoopVolumeWrapper(); } [HarmonyPostfix] [HarmonyPatch("UpdateShieldHum")] public static void ApplyShieldHumVolume(ref AudioSource ___m_shieldHumSource) { ApplyShieldHumVolumeWrapper(); } } internal static class AudioSourceFilter { private static readonly Dictionary _nameTargets = new Dictionary(StringComparer.OrdinalIgnoreCase); private static readonly Dictionary _clipTargets = new Dictionary(StringComparer.OrdinalIgnoreCase); private static readonly Dictionary _baseVolume = new Dictionary(); internal static bool HasClipTargets => _clipTargets.Count > 0; internal static void Refresh() { _nameTargets.Clear(); _clipTargets.Clear(); ParseSharedList(AmbienceSoundConfig.ExtraSfxList.Value, AmbienceSoundConfig.ExtraSfxVolume.Value, _nameTargets); ParseConfigVolumeSection("Extra SFX Split", _nameTargets); ParseSharedList(AmbienceSoundConfig.ExtraClipList.Value, AmbienceSoundConfig.ExtraClipVolume.Value, _clipTargets); ParseConfigVolumeSection("Extra Clips Split", _clipTargets); } internal static void Apply(AudioSource src) { if (!((Object)(object)src == (Object)null) && _nameTargets.Count != 0 && TryGetSfxMultiplier(((Object)((Component)src).gameObject).name, out var multiplier)) { ApplyVolume(src, multiplier); } } internal static void Apply(AudioSource src, ref float volume) { if (!((Object)(object)src == (Object)null) && _nameTargets.Count != 0 && TryGetSfxMultiplier(((Object)((Component)src).gameObject).name, out var multiplier)) { volume = Scale(volume, multiplier); } } internal static bool ShouldMuteClip(AudioClip clip) { float multiplier; return TryGetClipMultiplier(clip, out multiplier); } internal static bool TryGetClipMultiplier(AudioClip clip, out float multiplier) { multiplier = 1f; if ((Object)(object)clip != (Object)null) { return TryMatch(_clipTargets, ((Object)clip).name, out multiplier); } return false; } internal static void ApplyToRunningSources() { List list = new List(); foreach (KeyValuePair item in _baseVolume) { if ((Object)(object)item.Key == (Object)null) { list.Add(item.Key); } } foreach (AudioSource item2 in list) { _baseVolume.Remove(item2); } AudioSource[] array = Object.FindObjectsOfType(); foreach (AudioSource val in array) { if ((Object)(object)val == (Object)null || (Object)(object)val.clip == (Object)null) { continue; } float multiplier = 1f; if (!HasClipTargets || !TryGetClipMultiplier(val.clip, out multiplier)) { if (_baseVolume.ContainsKey(val)) { _baseVolume.Remove(val); } val.mute = false; continue; } if (!_baseVolume.ContainsKey(val)) { _baseVolume[val] = val.volume; } float num = _baseVolume[val]; if (multiplier <= 0f) { val.mute = true; continue; } val.mute = false; val.volume = Mathf.Clamp01(num * multiplier); } } private static bool TryGetSfxMultiplier(string goName, out float multiplier) { return TryMatch(_nameTargets, goName, out multiplier); } private static void ApplyVolume(AudioSource src, float mult) { if (mult <= 0f) { src.mute = true; return; } src.mute = false; src.volume = Mathf.Clamp01(src.volume * mult); } private static float Scale(float v, float mult) { if (mult <= 0f) { return 0f; } return v * mult; } private static bool TryMatch(Dictionary targets, string sourceName, out float multiplier) { multiplier = 1f; if (targets.Count == 0) { return false; } string text = Normalize(sourceName); if (targets.TryGetValue(text, out multiplier)) { return true; } foreach (KeyValuePair target in targets) { if (text.StartsWith(target.Key, StringComparison.OrdinalIgnoreCase) || text.IndexOf(target.Key, StringComparison.OrdinalIgnoreCase) >= 0) { multiplier = target.Value; return true; } } return false; } private static void ParseSharedList(string raw, float sharedVolume, Dictionary target) { if (string.IsNullOrEmpty(raw)) { return; } string[] array = raw.Split(',', '\n', '\r'); for (int i = 0; i < array.Length; i++) { string text = Normalize(array[i]); if (!string.IsNullOrEmpty(text)) { target[text] = AmbienceSoundConfig.ClampConfiguredVolume(sharedVolume); } } } private static void ParseConfigVolumeSection(string sectionName, Dictionary target) { string pluginConfigFilePath = AmbienceSoundConfig.PluginConfigFilePath; if (string.IsNullOrEmpty(pluginConfigFilePath) || !File.Exists(pluginConfigFilePath)) { return; } bool flag = false; string[] array = File.ReadAllLines(pluginConfigFilePath); for (int i = 0; i < array.Length; i++) { string text = array[i].Trim(); if (!string.IsNullOrEmpty(text) && !text.StartsWith("#")) { if (text.StartsWith("[") && text.EndsWith("]")) { flag = text.Substring(1, text.Length - 2).Trim().Equals(sectionName, StringComparison.OrdinalIgnoreCase); } else if (flag) { ParseVolumeLine(text, target); } } } } private static void ParseVolumeLine(string line, Dictionary target) { string text = line.Trim(); if (string.IsNullOrEmpty(text) || text.StartsWith("#")) { return; } string name = text; string text2 = null; int num = text.IndexOf('='); if (num < 0) { num = text.IndexOf(':'); } if (num >= 0) { name = text.Substring(0, num); text2 = text.Substring(num + 1); } name = Normalize(name); if (!string.IsNullOrEmpty(name)) { float value = 1f; if (!string.IsNullOrEmpty(text2) && float.TryParse(text2.Trim(), NumberStyles.Float, CultureInfo.InvariantCulture, out var result)) { value = result; } target[name] = AmbienceSoundConfig.ClampConfiguredVolume(value); } } private static string Normalize(string name) { if (string.IsNullOrEmpty(name)) { return string.Empty; } int num = name.IndexOf("(Clone)", StringComparison.OrdinalIgnoreCase); if (num >= 0) { name = name.Substring(0, num); } return name.Trim(); } } [HarmonyPatch(typeof(AudioSource))] internal static class AudioSource_DoAll_Patch { [HarmonyPostfix] [HarmonyPatch("Play", new Type[] { })] private static void Postfix_Play(AudioSource __instance) { SoundPlayLogger.Log("SFX", ((Object)((Component)__instance).gameObject).name, ((Component)__instance).gameObject); AudioSourceFilter.Apply(__instance); } [HarmonyPostfix] [HarmonyPatch("Play", new Type[] { typeof(ulong) })] private static void Postfix_PlayDelayed(AudioSource __instance) { AudioSourceFilter.Apply(__instance); } [HarmonyPostfix] [HarmonyPatch("PlayOneShot", new Type[] { typeof(AudioClip) })] private static void Postfix_PlayOneShot(AudioSource __instance) { AudioSourceFilter.Apply(__instance); } [HarmonyPrefix] [HarmonyPatch("set_volume")] private static void Prefix_SetVolume(AudioSource __instance, ref float value) { AudioSourceFilter.Apply(__instance, ref value); } } [HarmonyPatch(typeof(AudioSource))] internal static class AudioSource_PlayOneShotScale_Patch { [HarmonyPrefix] [HarmonyPatch("PlayOneShot", new Type[] { typeof(AudioClip), typeof(float) })] private static void Prefix(AudioSource __instance, AudioClip clip, ref float volumeScale) { SoundPlayLogger.Log("CLIP", (clip != null) ? ((Object)clip).name : null, Object.op_Implicit((Object)(object)__instance) ? ((Component)__instance).gameObject : null); if (!((Object)(object)clip == (Object)null) && AudioSourceFilter.HasClipTargets && AudioSourceFilter.TryGetClipMultiplier(clip, out var multiplier)) { volumeScale = ((multiplier <= 0f) ? 0f : (volumeScale * multiplier)); } } [HarmonyPrefix] [HarmonyPatch("PlayOneShot", new Type[] { typeof(AudioClip) })] private static void Prefix_NoScale(AudioSource __instance, AudioClip clip) { SoundPlayLogger.Log("CLIP", (clip != null) ? ((Object)clip).name : null, Object.op_Implicit((Object)(object)__instance) ? ((Component)__instance).gameObject : null); } } [HarmonyPatch] public static class SettingsInject { private static Transform _gridContainer; private static bool _built; private const string ContainerName = "AmbienceGridContainer"; internal static Slider _sMaster; internal static Slider _sWind; internal static Slider _sOcean; internal static Slider _sAmbientLoop; internal static Slider _sShieldHum; internal static Slider _sExtraSfx; internal static Slider _sExtraClip; internal static TextMeshProUGUI _vMaster; internal static TextMeshProUGUI _vWind; internal static TextMeshProUGUI _vOcean; internal static TextMeshProUGUI _vAmbientLoop; internal static TextMeshProUGUI _vShieldHum; internal static TextMeshProUGUI _vExtraSfx; internal static TextMeshProUGUI _vExtraClip; private static MethodBase TargetMethod() { Type type = typeof(AudioSettings); MethodBase methodBase = Find("Initialize") ?? Find("Awake") ?? Find("Start"); if (methodBase != null) { Debug.Log((object)("[AmbienceSoundConfig] UI inject patching → " + methodBase.Name)); return methodBase; } methodBase = Find("LoadSettings"); if (methodBase != null) { Debug.Log((object)"[AmbienceSoundConfig] UI inject patching → LoadSettings (legacy fallback for Valheim 0.221.4)"); return methodBase; } throw new Exception("[AmbienceSoundConfig] No compatible AudioSettings method found to patch."); MethodBase Find(string name) { return type.GetMethod(name, BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); } } [HarmonyPostfix] private static void Postfix_LoadSettings(AudioSettings __instance) { //IL_00e1: Unknown result type (might be due to invalid IL or missing references) //IL_00e6: Unknown result type (might be due to invalid IL or missing references) //IL_0118: Unknown result type (might be due to invalid IL or missing references) //IL_0125: Unknown result type (might be due to invalid IL or missing references) //IL_0136: Unknown result type (might be due to invalid IL or missing references) //IL_014b: Unknown result type (might be due to invalid IL or missing references) //IL_0160: Unknown result type (might be due to invalid IL or missing references) //IL_016b: Unknown result type (might be due to invalid IL or missing references) //IL_0176: Unknown result type (might be due to invalid IL or missing references) //IL_018b: Unknown result type (might be due to invalid IL or missing references) //IL_019f: Unknown result type (might be due to invalid IL or missing references) //IL_01a9: Unknown result type (might be due to invalid IL or missing references) //IL_01b4: Unknown result type (might be due to invalid IL or missing references) //IL_01be: Expected O, but got Unknown //IL_01c9: Unknown result type (might be due to invalid IL or missing references) //IL_01de: Unknown result type (might be due to invalid IL or missing references) //IL_01fc: Unknown result type (might be due to invalid IL or missing references) try { if (_built && (Object)(object)_gridContainer != (Object)null) { SyncUI(); return; } Slider value = Traverse.Create((object)__instance).Field("m_musicVolumeSlider").GetValue(); GameObject val = ((value != null) ? ((Component)value).gameObject : null); if ((Object)(object)val == (Object)null) { Debug.LogWarning((object)"[AmbienceSoundConfig] Could not get m_musicVolumeSlider."); return; } Toggle value2 = Traverse.Create((object)__instance).Field("m_continousMusic").GetValue(); Transform val2 = ((value2 != null) ? ((Component)value2).transform.parent : null) ?? val.transform.parent; Transform val3 = val2.Find("AmbienceGridContainer"); if ((Object)(object)val3 != (Object)null) { Object.DestroyImmediate((Object)(object)((Component)val3).gameObject); } GameObject val4 = new GameObject("AmbienceGridContainer", new Type[3] { typeof(RectTransform), typeof(GridLayoutGroup), typeof(ContentSizeFitter) }); val4.transform.SetParent(val2, false); int siblingIndex = (((Object)(object)value2 != (Object)null) ? ((Component)value2).transform.GetSiblingIndex() : val.transform.GetSiblingIndex()) + 1; val4.transform.SetSiblingIndex(siblingIndex); RectTransform component = val4.GetComponent(); component.anchorMin = new Vector2(0f, 1f); component.anchorMax = new Vector2(1f, 1f); component.pivot = new Vector2(0.5f, 1f); component.offsetMin = Vector2.zero; component.offsetMax = Vector2.zero; component.anchoredPosition = new Vector2(0f, 0f); component.sizeDelta = new Vector2(0f, 0f); GridLayoutGroup component2 = val4.GetComponent(); ((LayoutGroup)component2).padding = new RectOffset(0, 0, 0, 0); component2.cellSize = new Vector2(300f, 20f); component2.spacing = new Vector2(0f, 8f); component2.constraint = (Constraint)1; component2.constraintCount = 1; ((LayoutGroup)component2).childAlignment = (TextAnchor)0; ContentSizeFitter component3 = val4.GetComponent(); component3.verticalFit = (FitMode)2; component3.horizontalFit = (FitMode)0; _gridContainer = val4.transform; BindOrCreateSliders(_gridContainer, val); SyncUI(); _built = true; LayoutRebuilder.ForceRebuildLayoutImmediate(((Component)val2).GetComponent()); Debug.Log((object)"[AmbienceSoundConfig] AmbienceGridContainer built and anchored to top (no drift)."); } catch (Exception ex) { Debug.LogError((object)("[AmbienceSoundConfig] LoadSettings patch failed: " + ex)); } } private static void BindOrCreateSliders(Transform container, GameObject baseSlider) { CreateOrBind(container, baseSlider, "Ambience Master Volume", ref _sMaster, ref _vMaster, AmbienceSoundConfig.MasterVolume, delegate { AmbienceSoundConfig.ApplyAllVolumes(); }); CreateOrBind(container, baseSlider, "Wind Volume", ref _sWind, ref _vWind, AmbienceSoundConfig.WindVolume, delegate { AudioMan_AmbienceVolume_Patch.ApplyWindVolumeWrapper(); }); CreateOrBind(container, baseSlider, "Ocean Volume", ref _sOcean, ref _vOcean, AmbienceSoundConfig.OceanVolume, delegate { AudioMan_AmbienceVolume_Patch.ApplyOceanVolumeWrapper(); }); CreateOrBind(container, baseSlider, "Ambient Loop Volume", ref _sAmbientLoop, ref _vAmbientLoop, AmbienceSoundConfig.AmbientLoopVolume, delegate { AudioMan_AmbienceVolume_Patch.ApplyAmbientLoopVolumeWrapper(); }); CreateOrBind(container, baseSlider, "Shield Hum Volume", ref _sShieldHum, ref _vShieldHum, AmbienceSoundConfig.ShieldHumVolume, delegate { AudioMan_AmbienceVolume_Patch.ApplyShieldHumVolumeWrapper(); }); CreateOrBind(container, baseSlider, "Extra SFX Volume", ref _sExtraSfx, ref _vExtraSfx, AmbienceSoundConfig.ExtraSfxVolume, delegate { AudioSourceFilter.Refresh(); AudioSource[] array = Object.FindObjectsOfType(); for (int i = 0; i < array.Length; i++) { AudioSourceFilter.Apply(array[i]); } }); CreateOrBind(container, baseSlider, "Extra Clip Volume", ref _sExtraClip, ref _vExtraClip, AmbienceSoundConfig.ExtraClipVolume, delegate { AudioSourceFilter.Refresh(); AudioSourceFilter.ApplyToRunningSources(); }); } internal static void CreateOrBind(Transform parent, GameObject baseSlider, string labelText, ref Slider sliderRef, ref TextMeshProUGUI valueTextRef, ConfigEntry config, Action onChanged) { //IL_005a: Unknown result type (might be due to invalid IL or missing references) //IL_0072: 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) string text = "Ambience_" + labelText.Replace(" ", ""); Transform val2 = parent.Find(text); GameObject val3; TextMeshProUGUI[] componentsInChildren; if ((Object)(object)val2 == (Object)null) { val3 = Object.Instantiate(baseSlider, parent); ((Object)val3).name = text; val3.transform.localScale = Vector3.one; val3.SetActive(true); RectTransform component = val3.GetComponent(); ((Transform)component).localPosition = Vector3.zero; ((Transform)component).localRotation = Quaternion.identity; componentsInChildren = val3.GetComponentsInChildren(true); foreach (TextMeshProUGUI val4 in componentsInChildren) { if (!((Object)(object)((TMP_Text)val4).font == (Object)null)) { continue; } TextMeshProUGUI componentInChildren = baseSlider.GetComponentInChildren(true); if ((Object)(object)componentInChildren != (Object)null && (Object)(object)((TMP_Text)componentInChildren).font != (Object)null) { ((TMP_Text)val4).font = ((TMP_Text)componentInChildren).font; ((TMP_Text)val4).fontSharedMaterial = ((TMP_Text)componentInChildren).fontSharedMaterial; continue; } TMP_FontAsset[] array = Resources.FindObjectsOfTypeAll(); if (array.Length != 0) { ((TMP_Text)val4).font = array[0]; } } } else { val3 = ((Component)val2).gameObject; } Slider componentInChildren2 = val3.GetComponentInChildren(true); if ((Object)(object)componentInChildren2 == (Object)null) { Debug.LogError((object)("[AmbienceSoundConfig] Slider missing for " + labelText)); return; } TextMeshProUGUI val5 = null; TextMeshProUGUI valueText = null; componentsInChildren = val3.GetComponentsInChildren(true); foreach (TextMeshProUGUI val6 in componentsInChildren) { string text2 = ((Object)val6).name.ToLowerInvariant(); if (text2.Contains("value")) { valueText = val6; } else if (text2.Contains("text") || text2.Contains("label")) { val5 = val6; } } if ((Object)(object)val5 != (Object)null) { ((TMP_Text)val5).text = labelText; ((TMP_Text)val5).enableAutoSizing = true; ((TMP_Text)val5).fontSizeMin = 10f; ((TMP_Text)val5).fontSizeMax = 14f; ((Graphic)val5).raycastTarget = false; } if ((Object)(object)valueText != (Object)null) { ((Graphic)valueText).raycastTarget = false; ((TMP_Text)valueText).text = $"{Mathf.RoundToInt(config.Value * 100f)}%"; } componentInChildren2.minValue = 0f; componentInChildren2.maxValue = AmbienceSoundConfig.GetVolumeSliderMax(); ((UnityEventBase)componentInChildren2.onValueChanged).RemoveAllListeners(); ((UnityEvent)(object)componentInChildren2.onValueChanged).AddListener((UnityAction)delegate(float val) { float num = AmbienceSoundConfig.ClampConfiguredVolume(val); config.Value = num; AmbienceSoundConfig.SaveConfigPreservingCustomSections(((ConfigEntryBase)config).ConfigFile); if ((Object)(object)valueText != (Object)null) { ((TMP_Text)valueText).text = $"{Mathf.RoundToInt(num * 100f)}%"; } onChanged?.Invoke(num); }); sliderRef = componentInChildren2; valueTextRef = valueText; componentInChildren2.value = config.Value; } public static void SyncUI() { try { if ((Object)(object)_gridContainer == (Object)null || (Object)(object)((Component)_gridContainer).gameObject == (Object)null) { return; } if ((Object)(object)_sMaster != (Object)null) { _sMaster.maxValue = AmbienceSoundConfig.GetVolumeSliderMax(); _sMaster.SetValueWithoutNotify(AmbienceSoundConfig.MasterVolume.Value); if ((Object)(object)_vMaster != (Object)null) { ((TMP_Text)_vMaster).text = $"{Mathf.RoundToInt(_sMaster.value * 100f)}%"; } } if ((Object)(object)_sWind != (Object)null) { _sWind.maxValue = AmbienceSoundConfig.GetVolumeSliderMax(); _sWind.SetValueWithoutNotify(AmbienceSoundConfig.WindVolume.Value); if ((Object)(object)_vWind != (Object)null) { ((TMP_Text)_vWind).text = $"{Mathf.RoundToInt(_sWind.value * 100f)}%"; } } if ((Object)(object)_sOcean != (Object)null) { _sOcean.maxValue = AmbienceSoundConfig.GetVolumeSliderMax(); _sOcean.SetValueWithoutNotify(AmbienceSoundConfig.OceanVolume.Value); if ((Object)(object)_vOcean != (Object)null) { ((TMP_Text)_vOcean).text = $"{Mathf.RoundToInt(_sOcean.value * 100f)}%"; } } if ((Object)(object)_sAmbientLoop != (Object)null) { _sAmbientLoop.maxValue = AmbienceSoundConfig.GetVolumeSliderMax(); _sAmbientLoop.SetValueWithoutNotify(AmbienceSoundConfig.AmbientLoopVolume.Value); if ((Object)(object)_vAmbientLoop != (Object)null) { ((TMP_Text)_vAmbientLoop).text = $"{Mathf.RoundToInt(_sAmbientLoop.value * 100f)}%"; } } if ((Object)(object)_sShieldHum != (Object)null) { _sShieldHum.maxValue = AmbienceSoundConfig.GetVolumeSliderMax(); _sShieldHum.SetValueWithoutNotify(AmbienceSoundConfig.ShieldHumVolume.Value); if ((Object)(object)_vShieldHum != (Object)null) { ((TMP_Text)_vShieldHum).text = $"{Mathf.RoundToInt(_sShieldHum.value * 100f)}%"; } } if ((Object)(object)_sExtraSfx != (Object)null) { _sExtraSfx.maxValue = AmbienceSoundConfig.GetVolumeSliderMax(); _sExtraSfx.SetValueWithoutNotify(AmbienceSoundConfig.ExtraSfxVolume.Value); if ((Object)(object)_vExtraSfx != (Object)null) { ((TMP_Text)_vExtraSfx).text = $"{Mathf.RoundToInt(_sExtraSfx.value * 100f)}%"; } } if ((Object)(object)_sExtraClip != (Object)null) { _sExtraClip.maxValue = AmbienceSoundConfig.GetVolumeSliderMax(); _sExtraClip.SetValueWithoutNotify(AmbienceSoundConfig.ExtraClipVolume.Value); if ((Object)(object)_vExtraClip != (Object)null) { ((TMP_Text)_vExtraClip).text = $"{Mathf.RoundToInt(_sExtraClip.value * 100f)}%"; } } } catch (Exception ex) { Debug.LogError((object)("[AmbienceSoundConfig] SyncUI failed: " + ex)); } } private static GameObject FindDeepChild(Transform parent, string name) { Transform val = FindDeepChildTransform(parent, name); if (!Object.op_Implicit((Object)(object)val)) { return null; } return ((Component)val).gameObject; } private static Transform FindDeepChildTransform(Transform parent, string name) { //IL_000f: Unknown result type (might be due to invalid IL or missing references) //IL_0015: Expected O, but got Unknown foreach (Transform item in parent) { Transform val = item; if (((Object)val).name == name) { return val; } Transform val2 = FindDeepChildTransform(val, name); if ((Object)(object)val2 != (Object)null) { return val2; } } return null; } } internal static class SoundPlayLogger { private static readonly HashSet _seen = new HashSet(StringComparer.OrdinalIgnoreCase); private static string _logPath; internal static void Init(ConfigFile config) { try { _logPath = Path.Combine(Path.GetDirectoryName(config.ConfigFilePath), "AmbienceSoundConfig_SoundLog.txt"); } catch { _logPath = Path.Combine(Paths.ConfigPath, "AmbienceSoundConfig_SoundLog.txt"); } } internal static void Log(string type, string name, GameObject go = null) { if (!AmbienceSoundConfig.EnableSoundLogging.Value || string.IsNullOrEmpty(name) || (AmbienceSoundConfig.LogOnlyUnique.Value && !_seen.Add(type + "|" + name))) { return; } string text = DateTime.Now.ToString("HH:mm:ss.fff"); string text2 = (((Object)(object)go != (Object)null) ? ("[" + text + "] " + type + ": " + name + " @ " + ((Object)go).name) : ("[" + text + "] " + type + ": " + name)); Debug.Log((object)("[ASC-LOG] " + text2)); try { File.AppendAllText(_logPath, text2 + Environment.NewLine); } catch { } } } [HarmonyPatch(typeof(ZSFX))] internal static class ZSFX_VolumeScale_Patch { [HarmonyPostfix] [HarmonyPatch("CustomUpdate")] private static void Postfix(ZSFX __instance) { if (!AudioSourceFilter.HasClipTargets) { return; } AudioSource component = ((Component)__instance).GetComponent(); if (!((Object)(object)component == (Object)null) && !((Object)(object)component.clip == (Object)null)) { if (component.isPlaying) { SoundPlayLogger.Log("ZSFX", ((Object)component.clip).name, ((Component)__instance).gameObject); } float num = 1f; if (AudioSourceFilter.TryGetClipMultiplier(component.clip, out var multiplier)) { num = multiplier; } __instance.SetVolumeModifier((num <= 0f) ? 0f : num); } } } }