Decompiled source of ValheimAudioControl v1.0.0

BepInEx/plugins/ValheimAudioControl/AudioLODControl.dll

Decompiled a week ago
using System;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.Versioning;
using System.Text;
using System.Text.RegularExpressions;
using BepInEx;
using BepInEx.Bootstrap;
using BepInEx.Configuration;
using HarmonyLib;
using Microsoft.CodeAnalysis;
using TMPro;
using UnityEngine;
using UnityEngine.Audio;
using UnityEngine.EventSystems;
using UnityEngine.Events;
using UnityEngine.Networking;
using UnityEngine.SceneManagement;
using UnityEngine.UI;
using Valheim.SettingsGui;

[assembly: CompilationRelaxations(8)]
[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
[assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)]
[assembly: TargetFramework(".NETFramework,Version=v4.7.2", FrameworkDisplayName = ".NET Framework 4.7.2")]
[assembly: AssemblyCompany("AudioLODControl")]
[assembly: AssemblyConfiguration("Release")]
[assembly: AssemblyFileVersion("1.0.0.0")]
[assembly: AssemblyInformationalVersion("1.0.0")]
[assembly: AssemblyProduct("AudioLODControl")]
[assembly: AssemblyTitle("AudioLODControl")]
[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 AudioLODControl
{
	internal static class AudioDebugMonitor
	{
		private struct PlaybackKey : IEquatable<PlaybackKey>
		{
			internal readonly int SourceId;

			internal readonly int ClipId;

			internal PlaybackKey(AudioSource source, AudioClip clip)
			{
				SourceId = (((Object)(object)source != (Object)null) ? ((Object)source).GetInstanceID() : 0);
				ClipId = (((Object)(object)clip != (Object)null) ? ((Object)clip).GetInstanceID() : 0);
			}

			public bool Equals(PlaybackKey other)
			{
				if (SourceId == other.SourceId)
				{
					return ClipId == other.ClipId;
				}
				return false;
			}

			public override bool Equals(object obj)
			{
				if (obj is PlaybackKey other)
				{
					return Equals(other);
				}
				return false;
			}

			public override int GetHashCode()
			{
				return (SourceId * 397) ^ ClipId;
			}
		}

		private static readonly HashSet<PlaybackKey> ActivePlayback = new HashSet<PlaybackKey>();

		private static readonly HashSet<PlaybackKey> PendingKeys = new HashSet<PlaybackKey>();

		private static readonly HashSet<string> PendingRowSet = new HashSet<string>(StringComparer.Ordinal);

		private static readonly List<string> PendingRows = new List<string>(64);

		private static readonly Dictionary<PlaybackKey, float> LastPlaybackTimes = new Dictionary<PlaybackKey, float>();

		private static float _nextFlushAt;

		private static float _lastMixerEditAt = -100f;

		private static string _lastMixerEditTarget = "(unknown mixer)";

		private static readonly HashSet<int> LoggedControlSources = new HashSet<int>();

		private static readonly Dictionary<int, float> LastRestartLogAt = new Dictionary<int, float>();

		internal static IEnumerator Run(Plugin plugin)
		{
			WaitForSecondsRealtime interval = new WaitForSecondsRealtime(1f);
			_nextFlushAt = Time.realtimeSinceStartup + 5f;
			while ((Object)(object)plugin != (Object)null && Plugin.DebugPlayingAudio != null && Plugin.DebugPlayingAudio.Value)
			{
				DiscoverUnhookedPlaybackStarts();
				float realtimeSinceStartup = Time.realtimeSinceStartup;
				if (realtimeSinceStartup >= _nextFlushAt)
				{
					FlushPendingStarts();
					_nextFlushAt = realtimeSinceStartup + 5f;
				}
				yield return interval;
			}
			FlushPendingStarts();
			if ((Object)(object)plugin != (Object)null)
			{
				plugin.FinishAudioDebugMonitor();
			}
		}

		internal static void ResetSnapshot()
		{
			ActivePlayback.Clear();
			PendingKeys.Clear();
			PendingRowSet.Clear();
			PendingRows.Clear();
			LastPlaybackTimes.Clear();
			_nextFlushAt = 0f;
			_lastMixerEditAt = -100f;
			_lastMixerEditTarget = "(unknown mixer)";
			LoggedControlSources.Clear();
			LastRestartLogAt.Clear();
		}

		internal static void NotifyMixerEdit(string target)
		{
			if (IsDebugEnabled())
			{
				float realtimeSinceStartup = Time.realtimeSinceStartup;
				if (realtimeSinceStartup - _lastMixerEditAt > 10f)
				{
					LoggedControlSources.Clear();
					LastRestartLogAt.Clear();
				}
				_lastMixerEditAt = realtimeSinceStartup;
				_lastMixerEditTarget = (string.IsNullOrWhiteSpace(target) ? "(unknown mixer)" : target);
			}
		}

		internal static void LogPlaybackControl(AudioSource source, string action, bool wasPlaying, float previousTime)
		{
			if ((Object)(object)source == (Object)null || !wasPlaying || !IsDebugEnabled())
			{
				return;
			}
			float num = Time.realtimeSinceStartup - _lastMixerEditAt;
			if (!(num < 0f) && !(num > 10f) && LoggedControlSources.Add(((Object)source).GetInstanceID()))
			{
				AudioGainAmplifier component = ((Component)source).GetComponent<AudioGainAmplifier>();
				float num2 = (((Object)(object)component != (Object)null) ? component.Amplification : 1f);
				float num3 = Mathf.Max(0f, source.volume * num2 * AudioListener.volume);
				if (!ShouldHideMuted() || !(num3 <= 0.0001f))
				{
					string text = (((Object)(object)source.clip != (Object)null) ? ((Object)source.clip).name : "(no clip)");
					float num4 = ((source.isPlaying && (Object)(object)source.clip != (Object)null) ? source.time : (-1f));
					Plugin.LogInfo("[Audio Debug] AudioSource." + action + " within " + num.ToString("0.0") + "s of mixer edit " + _lastMixerEditTarget + ": " + GetHierarchyPath(((Component)source).transform) + " | " + text + " | action=" + action + " | wasPlaying=true | time=" + ((previousTime >= 0f) ? (previousTime.ToString("0.00") + "s") : ((num4 >= 0f) ? (num4.ToString("0.00") + "s") : "-")) + " | volume=" + Mathf.RoundToInt(num3 * 100f) + "% | category=" + AudioEmitterRegistry.GetDebugCategory(source).ToString() + " | caller=" + GetExternalCaller());
				}
			}
		}

		internal static void LogPlaybackStarted(AudioSource source, AudioClip clip, float volumeScale = 1f, bool trackPersistentPlayback = true)
		{
			//IL_0024: Unknown result type (might be due to invalid IL or missing references)
			//IL_0029: Unknown result type (might be due to invalid IL or missing references)
			if ((Object)(object)source == (Object)null || !((Behaviour)source).enabled || !((Component)source).gameObject.activeInHierarchy)
			{
				return;
			}
			Scene scene = ((Component)source).gameObject.scene;
			if (!((Scene)(ref scene)).IsValid() || !IsDebugEnabled())
			{
				return;
			}
			float num = 1f;
			AudioGainAmplifier component = ((Component)source).GetComponent<AudioGainAmplifier>();
			if ((Object)(object)component != (Object)null)
			{
				num = component.Amplification;
			}
			float num2 = Mathf.Max(0f, source.volume * Mathf.Max(0f, volumeScale) * num * AudioListener.volume);
			if (!ShouldHideMuted() || !(num2 <= 0.0001f))
			{
				AudioCategory debugCategory = AudioEmitterRegistry.GetDebugCategory(source);
				string text = ((debugCategory == AudioCategory.Unassigned) ? "Other" : debugCategory.ToString());
				string text2 = (((Object)(object)clip != (Object)null) ? ((Object)clip).name : "(no clip)");
				PlaybackKey item = new PlaybackKey(source, clip);
				if (trackPersistentPlayback)
				{
					ActivePlayback.Add(item);
				}
				string item2 = $"{Mathf.RoundToInt(num2 * 100f)}% [{text}] {GetHierarchyPath(((Component)source).transform)} | {text2} | AudioSource #{((Object)source).GetInstanceID()}";
				if (PendingKeys.Add(item) && PendingRowSet.Add(item2))
				{
					PendingRows.Add(item2);
				}
			}
		}

		internal static void LogPlaybackRestarted(AudioSource source, float previousTime)
		{
			if ((Object)(object)source == (Object)null || !IsDebugEnabled())
			{
				return;
			}
			float num = Time.realtimeSinceStartup - _lastMixerEditAt;
			if (!(num < 0f) && !(num > 10f) && CanLogRestart(source))
			{
				AudioGainAmplifier component = ((Component)source).GetComponent<AudioGainAmplifier>();
				float num2 = (((Object)(object)component != (Object)null) ? component.Amplification : 1f);
				float num3 = Mathf.Max(0f, source.volume * num2 * AudioListener.volume);
				if (!ShouldHideMuted() || !(num3 <= 0.0001f))
				{
					string text = (((Object)(object)source.clip != (Object)null) ? ((Object)source.clip).name : "(no clip)");
					Plugin.LogInfo("[Audio Debug] AudioSource.Play restarted an active source: " + GetHierarchyPath(((Component)source).transform) + " | " + text + " | time=" + previousTime.ToString("0.00") + "→" + (source.isPlaying ? source.time.ToString("0.00") : "stopped") + "s | volume=" + Mathf.RoundToInt(num3 * 100f) + "% | category=" + AudioEmitterRegistry.GetDebugCategory(source).ToString() + " | caller=" + GetExternalCaller() + " | within " + num.ToString("0.0") + "s of mixer edit " + _lastMixerEditTarget);
				}
			}
		}

		internal static void LogEmitterPlaybackRestarted(string emitterName, AudioSource source, bool wasPlaying, int previousClipId, float previousTime)
		{
			if (wasPlaying && !((Object)(object)source == (Object)null) && IsDebugEnabled() && !((Object)(object)source.clip == (Object)null) && ((Object)source.clip).GetInstanceID() == previousClipId && source.isPlaying && !(source.time + 0.05f >= previousTime))
			{
				LogObservedPlaybackRestart(source, emitterName, previousTime, "ZSFX.Play (" + GetExternalCaller() + ")");
			}
		}

		private static void LogObservedPlaybackRestart(AudioSource source, string sourceKind, float previousTime, string caller)
		{
			if ((Object)(object)source == (Object)null || !IsDebugEnabled())
			{
				return;
			}
			float num = Time.realtimeSinceStartup - _lastMixerEditAt;
			if (!(num < 0f) && !(num > 10f) && CanLogRestart(source))
			{
				AudioGainAmplifier component = ((Component)source).GetComponent<AudioGainAmplifier>();
				float num2 = (((Object)(object)component != (Object)null) ? component.Amplification : 1f);
				float num3 = Mathf.Max(0f, source.volume * num2 * AudioListener.volume);
				if (!ShouldHideMuted() || !(num3 <= 0.0001f))
				{
					Plugin.LogInfo("[Audio Debug] Playback time restarted after mixer edit " + _lastMixerEditTarget + ": " + GetHierarchyPath(((Component)source).transform) + " | " + (((Object)(object)source.clip != (Object)null) ? ((Object)source.clip).name : "(no clip)") + " | source=" + sourceKind + " | time=" + previousTime.ToString("0.00") + "→" + source.time.ToString("0.00") + "s | volume=" + Mathf.RoundToInt(num3 * 100f) + "% | category=" + AudioEmitterRegistry.GetDebugCategory(source).ToString() + " | caller=" + caller + " | " + num.ToString("0.0") + "s after edit");
				}
			}
		}

		private static bool CanLogRestart(AudioSource source)
		{
			int instanceID = ((Object)source).GetInstanceID();
			float realtimeSinceStartup = Time.realtimeSinceStartup;
			if (LastRestartLogAt.TryGetValue(instanceID, out var value) && realtimeSinceStartup - value < 1f)
			{
				return false;
			}
			LastRestartLogAt[instanceID] = realtimeSinceStartup;
			return true;
		}

		private static void FlushPendingStarts()
		{
			if (PendingRows.Count != 0)
			{
				StringBuilder stringBuilder = new StringBuilder(64 + PendingRows.Count * 80);
				stringBuilder.Append("[Audio Debug] Sounds started in the last 5 seconds (unique: ").Append(PendingRows.Count).Append("):");
				for (int i = 0; i < PendingRows.Count; i++)
				{
					stringBuilder.AppendLine().Append("  ").Append(PendingRows[i]);
				}
				Plugin.LogInfo(stringBuilder.ToString());
				PendingRows.Clear();
				PendingKeys.Clear();
				PendingRowSet.Clear();
			}
		}

		private static void DiscoverUnhookedPlaybackStarts()
		{
			//IL_005b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0060: Unknown result type (might be due to invalid IL or missing references)
			AudioSource[] array = Object.FindObjectsByType<AudioSource>((FindObjectsSortMode)0);
			HashSet<PlaybackKey> currentlyPlaying = new HashSet<PlaybackKey>();
			foreach (AudioSource val in array)
			{
				if ((Object)(object)val == (Object)null || !((Behaviour)val).enabled || !((Component)val).gameObject.activeInHierarchy || !val.isPlaying)
				{
					continue;
				}
				Scene scene = ((Component)val).gameObject.scene;
				if (((Scene)(ref scene)).IsValid() && !((Object)(object)val.clip == (Object)null))
				{
					PlaybackKey playbackKey = new PlaybackKey(val, val.clip);
					currentlyPlaying.Add(playbackKey);
					float time = val.time;
					if (LastPlaybackTimes.TryGetValue(playbackKey, out var value) && time + 0.5f < value && !val.loop && AudioEmitterRegistry.GetDebugCategory(val) == AudioCategory.Ambient)
					{
						LogObservedPlaybackRestart(val, "sampled ambient AudioSource", value, "playback monitor; mixer=" + _lastMixerEditTarget);
					}
					LastPlaybackTimes[playbackKey] = time;
					if (!ActivePlayback.Contains(playbackKey))
					{
						LogPlaybackStarted(val, val.clip);
					}
				}
			}
			ActivePlayback.RemoveWhere((PlaybackKey key) => !currentlyPlaying.Contains(key));
		}

		private static bool IsDebugEnabled()
		{
			if (Plugin.DebugPlayingAudio != null)
			{
				return Plugin.DebugPlayingAudio.Value;
			}
			return false;
		}

		private static bool ShouldHideMuted()
		{
			if (Plugin.HideZeroVolumeAudioLogs != null)
			{
				return Plugin.HideZeroVolumeAudioLogs.Value;
			}
			return false;
		}

		private static string GetHierarchyPath(Transform transform)
		{
			if ((Object)(object)transform == (Object)null)
			{
				return "(destroyed source)";
			}
			List<string> list = new List<string>(6);
			Transform val = transform;
			while ((Object)(object)val != (Object)null && list.Count < 8)
			{
				list.Add(((Object)val).name);
				val = val.parent;
			}
			list.Reverse();
			return string.Join(" / ", list.ToArray());
		}

		private static string GetExternalCaller()
		{
			try
			{
				StackTrace stackTrace = new StackTrace(1, fNeedFileInfo: false);
				for (int i = 0; i < stackTrace.FrameCount; i++)
				{
					MethodBase methodBase = stackTrace.GetFrame(i)?.GetMethod();
					Type type = methodBase?.DeclaringType;
					if (!(methodBase == null) && !(type == null))
					{
						string text = type.FullName ?? string.Empty;
						if (!text.StartsWith("AudioLODControl.", StringComparison.Ordinal) && !text.StartsWith("Harmony", StringComparison.Ordinal) && !text.StartsWith("UnityEngine.", StringComparison.Ordinal))
						{
							return type.Name + "." + methodBase.Name;
						}
					}
				}
			}
			catch
			{
			}
			return "unknown";
		}
	}
	internal enum AudioCategory
	{
		Unassigned,
		Mobs,
		Players,
		Bosses,
		Weapons,
		Tools,
		Ambient,
		Interface,
		Others
	}
	internal sealed class AudioObjectMixer
	{
		internal readonly string Id;

		internal string DisplayName;

		internal string ModId;

		internal string ModDisplayName;

		internal AudioCategory OwnerCategory;

		internal readonly ConfigEntry<float> Volume;

		internal bool IsModded;

		internal WeakReference<GameObject> ObjectSource;

		internal readonly List<string> LegacyIds = new List<string>();

		internal readonly List<AudioPrefabChannel> Channels = new List<AudioPrefabChannel>();

		internal AudioObjectMixer(string id, string displayName, ConfigEntry<float> volume, bool isModded, GameObject objectSource, AudioCategory ownerCategory)
		{
			Id = id;
			DisplayName = AudioEmitterRegistry.StripDisplayBrackets(displayName);
			Volume = volume;
			IsModded = isModded;
			OwnerCategory = ownerCategory;
			if ((Object)(object)objectSource != (Object)null)
			{
				ObjectSource = new WeakReference<GameObject>(objectSource);
			}
		}
	}
	internal sealed class AudioPrefabChannel
	{
		internal string Id;

		internal readonly string DisplayName;

		internal AudioObjectMixer ObjectMixer;

		internal readonly ConfigEntry<float> Volume;

		internal AudioCategory Category;

		internal bool IsModded;

		internal string ModId;

		internal string ModDisplayName;

		internal AudioClip PreviewClip;

		internal AudioMixerGroup PreviewMixerGroup;

		internal float IndexedDurationSeconds;

		internal string IndexedAudioFilePath;

		internal bool PreviewClipIsOwned;

		internal string ObjectDisplayName
		{
			get
			{
				if (ObjectMixer == null)
				{
					return string.Empty;
				}
				return ObjectMixer.DisplayName;
			}
		}

		internal AudioPrefabChannel(string id, string displayName, AudioObjectMixer objectMixer, AudioCategory category, ConfigEntry<float> volume, bool isModded)
		{
			Id = id;
			DisplayName = AudioEmitterRegistry.StripDisplayBrackets(displayName);
			ObjectMixer = objectMixer;
			Category = category;
			Volume = volume;
			IsModded = isModded;
		}
	}
	internal sealed class AudioModIdentity
	{
		internal string Id;

		internal string DisplayName;
	}
	internal sealed class AudioEmitterState
	{
		internal readonly AudioPrefabChannel Channel;

		internal AudioCategory OwnerCategory;

		internal ItemDrop ItemOwner;

		internal AudioCategory ItemCategory;

		internal AudioCategory PlaybackCategory;

		internal AudioSource AppliedSource;

		internal AudioGainAmplifier Amplifier;

		internal bool IsDirectAudioSource;

		internal float BaseVolume;

		internal float LastAppliedVolume;

		internal float LastMultiplier = -1f;

		internal bool HasVolumeSnapshot;

		internal bool IsTrackedActive;

		internal AudioEmitterState(AudioPrefabChannel channel, AudioCategory ownerCategory, ItemDrop itemOwner, AudioCategory itemCategory)
		{
			Channel = channel;
			OwnerCategory = ownerCategory;
			ItemOwner = itemOwner;
			ItemCategory = itemCategory;
			PlaybackCategory = ((itemCategory != AudioCategory.Unassigned) ? itemCategory : ownerCategory);
		}
	}
	internal struct AudioCategoryScope
	{
		internal AudioCategory PreviousCategory;

		internal bool HadPreviousCategory;
	}
	internal static class AudioEmitterRegistry
	{
		private sealed class ReferenceEqualityComparer<T> : IEqualityComparer<T> where T : class
		{
			internal static readonly ReferenceEqualityComparer<T> Instance = new ReferenceEqualityComparer<T>();

			public bool Equals(T left, T right)
			{
				return left == right;
			}

			public int GetHashCode(T value)
			{
				if (value != null)
				{
					return RuntimeHelpers.GetHashCode(value);
				}
				return 0;
			}
		}

		[Serializable]
		private sealed class ModAudioIndex
		{
			public int version;

			public string language;

			public string[] preload;

			public ModAudioIndexEntry[] clips;
		}

		[Serializable]
		private sealed class ModAudioIndexEntry
		{
			public string key;

			public string file;

			public string voice;

			public float duration;
		}

		[Serializable]
		private sealed class ThunderstoreModManifest
		{
			public string name;

			public string description;

			public string[] dependencies;
		}

		private sealed class AudioObjectAlias
		{
			internal readonly string Marker;

			internal readonly string PrefabName;

			internal AudioObjectAlias(string marker, string prefabName)
			{
				Marker = marker;
				PrefabName = prefabName;
			}
		}

		private sealed class KnownAudioOwner
		{
			internal readonly string PrefabName;

			internal readonly string ObjectName;

			internal readonly string CanonicalId;

			internal readonly string LocalizationKey;

			internal string DisplayName;

			internal readonly AudioCategory Category;

			internal readonly WeakReference<GameObject> Prefab;

			internal readonly bool IsModded;

			internal KnownAudioOwner(string prefabName, string objectName, string canonicalId, string localizationKey, string displayName, AudioCategory category, GameObject prefab, bool isModded)
			{
				PrefabName = prefabName;
				ObjectName = objectName;
				CanonicalId = canonicalId;
				LocalizationKey = localizationKey;
				DisplayName = displayName;
				Category = category;
				if ((Object)(object)prefab != (Object)null)
				{
					Prefab = new WeakReference<GameObject>(prefab);
				}
				IsModded = isModded;
			}
		}

		private sealed class AmbientVolumeSnapshot
		{
			internal float BaseVolume;

			internal float LastAppliedVolume;

			internal float LastMultiplier = -1f;

			internal bool HasVolumeSnapshot;
		}

		private sealed class ModAudioBundleRequest
		{
			internal AssetBundle Bundle;

			internal AudioModIdentity Identity;

			internal string ObjectName;
		}

		private sealed class ReferenceComparer : IEqualityComparer<object>
		{
			internal static readonly ReferenceComparer Instance = new ReferenceComparer();

			public new bool Equals(object left, object right)
			{
				return left == right;
			}

			public int GetHashCode(object value)
			{
				return RuntimeHelpers.GetHashCode(value);
			}
		}

		private static readonly AudioObjectAlias[] AudioObjectAliases = new AudioObjectAlias[96]
		{
			new AudioObjectAlias("fallen_valkyrie", "FallenValkyrie"),
			new AudioObjectAlias("fallenvalkyrie", "FallenValkyrie"),
			new AudioObjectAlias("sea_serpent", "Serpent"),
			new AudioObjectAlias("stone_golem", "StoneGolem"),
			new AudioObjectAlias("greydwarf", "Greydwarf"),
			new AudioObjectAlias("greyling", "Greyling"),
			new AudioObjectAlias("deathsquito", "Deathsquito"),
			new AudioObjectAlias("sedge_warbler", "SedgeWarbler"),
			new AudioObjectAlias("sedgewarbler", "SedgeWarbler"),
			new AudioObjectAlias("willow_warbler", "WillowWarbler"),
			new AudioObjectAlias("willowwarbler", "WillowWarbler"),
			new AudioObjectAlias("coal_tit", "CoalTit"),
			new AudioObjectAlias("coaltit", "CoalTit"),
			new AudioObjectAlias("whitethroat", "Whitethroat"),
			new AudioObjectAlias("blackbird", "Blackbird"),
			new AudioObjectAlias("seagull", "Seagull"),
			new AudioObjectAlias("goblinking", "GoblinKing"),
			new AudioObjectAlias("eikthyr", "Eikthyr"),
			new AudioObjectAlias("elder", "gd_king"),
			new AudioObjectAlias("anciao", "gd_king"),
			new AudioObjectAlias("ancião", "gd_king"),
			new AudioObjectAlias("dragon_boss", "Dragon"),
			new AudioObjectAlias("moder", "Dragon"),
			new AudioObjectAlias("bonemass", "Bonemass"),
			new AudioObjectAlias("massa_ossea", "Bonemass"),
			new AudioObjectAlias("yagluth", "GoblinKing"),
			new AudioObjectAlias("queen", "SeekerQueen"),
			new AudioObjectAlias("rainha", "SeekerQueen"),
			new AudioObjectAlias("fader", "Fader"),
			new AudioObjectAlias("fallenvalkyrie", "FallenValkyrie"),
			new AudioObjectAlias("gd_king", "gd_king"),
			new AudioObjectAlias("abomination", "Abomination"),
			new AudioObjectAlias("stonegolem", "StoneGolem"),
			new AudioObjectAlias("seaserpent", "Serpent"),
			new AudioObjectAlias("sneck", "Serpent"),
			new AudioObjectAlias("greydwarf", "Greydwarf"),
			new AudioObjectAlias("boar", "Boar"),
			new AudioObjectAlias("deer", "Deer"),
			new AudioObjectAlias("moose", "Deer"),
			new AudioObjectAlias("elk", "Deer"),
			new AudioObjectAlias("alce", "Deer"),
			new AudioObjectAlias("cervo", "Deer"),
			new AudioObjectAlias("neck", "Neck"),
			new AudioObjectAlias("troll", "Troll"),
			new AudioObjectAlias("skeleton", "Skeleton"),
			new AudioObjectAlias("draugr", "Draugr"),
			new AudioObjectAlias("blobtar", "BlobTar"),
			new AudioObjectAlias("betume", "BlobTar"),
			new AudioObjectAlias("tar", "BlobTar"),
			new AudioObjectAlias("blob", "Blob"),
			new AudioObjectAlias("ooze", "Blob"),
			new AudioObjectAlias("slime", "Blob"),
			new AudioObjectAlias("gosma", "Blob"),
			new AudioObjectAlias("leech", "Leech"),
			new AudioObjectAlias("sanguessuga", "Leech"),
			new AudioObjectAlias("fenring", "Fenring"),
			new AudioObjectAlias("ulv", "Ulv"),
			new AudioObjectAlias("lox", "Lox"),
			new AudioObjectAlias("lobo", "Wolf"),
			new AudioObjectAlias("morcego", "Bat"),
			new AudioObjectAlias("foca", "Seal"),
			new AudioObjectAlias("seal", "Seal"),
			new AudioObjectAlias("larva", "Larva"),
			new AudioObjectAlias("larvae", "Larva"),
			new AudioObjectAlias("fuling", "Goblin"),
			new AudioObjectAlias("fulings", "Goblin"),
			new AudioObjectAlias("fulling", "Goblin"),
			new AudioObjectAlias("fullings", "Goblin"),
			new AudioObjectAlias("drake", "Hatchling"),
			new AudioObjectAlias("seeker", "Seeker"),
			new AudioObjectAlias("tick", "Tick"),
			new AudioObjectAlias("gjall", "Gjall"),
			new AudioObjectAlias("morgen", "Morgen"),
			new AudioObjectAlias("dverger", "Dverger"),
			new AudioObjectAlias("dvergr", "Dverger"),
			new AudioObjectAlias("asksvin", "Asksvin"),
			new AudioObjectAlias("surtling", "Surtling"),
			new AudioObjectAlias("wraith", "Wraith"),
			new AudioObjectAlias("hare", "Hare"),
			new AudioObjectAlias("hen", "Hen"),
			new AudioObjectAlias("chicken", "Chicken"),
			new AudioObjectAlias("crow", "Crow"),
			new AudioObjectAlias("raven", "Raven"),
			new AudioObjectAlias("gull", "Seagal"),
			new AudioObjectAlias("shark", "Shark"),
			new AudioObjectAlias("bat", "Bat"),
			new AudioObjectAlias("volture", "Volture"),
			new AudioObjectAlias("chaffinch", "Chaffinch"),
			new AudioObjectAlias("rookery", "Rookery"),
			new AudioObjectAlias("coaltit", "CoalTit"),
			new AudioObjectAlias("thrush", "Thrush"),
			new AudioObjectAlias("robin", "Robin"),
			new AudioObjectAlias("finch", "Finch"),
			new AudioObjectAlias("warbler", "Warbler"),
			new AudioObjectAlias("bird", "Bird"),
			new AudioObjectAlias("nest", "Rookery")
		};

		private static readonly AudioObjectAlias[] ExplicitAudioOwnerAliases = new AudioObjectAlias[11]
		{
			new AudioObjectAlias("babyseeker", "SeekerBrood"),
			new AudioObjectAlias("goblinshield", "Goblin"),
			new AudioObjectAlias("charred_mage", "Charred_Mage"),
			new AudioObjectAlias("charred_melee", "Charred_Melee"),
			new AudioObjectAlias("charred_twitcher", "Charred_Twitcher"),
			new AudioObjectAlias("seekerbrute", "SeekerBrute"),
			new AudioObjectAlias("egg_splash", "SeekerEgg"),
			new AudioObjectAlias("altar_charred", "CharredAltar"),
			new AudioObjectAlias("charred_altar", "CharredAltar"),
			new AudioObjectAlias("charred_spawner", "CharredSpawner"),
			new AudioObjectAlias("charredspawner", "CharredSpawner")
		};

		private static readonly HashSet<string> SoundActionNameTokens = new HashSet<string>(new string[59]
		{
			"alert", "alerted", "aggro", "attack", "attacked", "charge", "death", "die", "dying", "hit",
			"hurt", "wound", "wounded", "idle", "coins", "spawn", "spawning", "melee", "swing", "stab",
			"lift", "break", "broken", "bash", "impact", "splash", "destruction", "destroy", "explode", "explosion",
			"cast", "throw", "projectile", "launch", "footstep", "step", "walk", "run", "jump", "land",
			"fall", "breath", "breathe", "growl", "roar", "howl", "cry", "scream", "vocal", "voice",
			"taunt", "flee", "notice", "block", "equip", "eat", "sleep", "break", "shield"
		}, StringComparer.OrdinalIgnoreCase);

		private static readonly ConditionalWeakTable<ZSFX, AudioEmitterState> States = new ConditionalWeakTable<ZSFX, AudioEmitterState>();

		private static readonly ConditionalWeakTable<AudioSource, AudioEmitterState> DirectSourceStates = new ConditionalWeakTable<AudioSource, AudioEmitterState>();

		private static readonly ConditionalWeakTable<AudioSource, AmbientVolumeSnapshot> AmbientVolumeSnapshots = new ConditionalWeakTable<AudioSource, AmbientVolumeSnapshot>();

		private static readonly Dictionary<string, AudioPrefabChannel> ChannelsById = new Dictionary<string, AudioPrefabChannel>(StringComparer.OrdinalIgnoreCase);

		private static readonly Dictionary<string, AudioObjectMixer> ObjectMixersById = new Dictionary<string, AudioObjectMixer>(StringComparer.OrdinalIgnoreCase);

		private static readonly Dictionary<ConfigEntry<float>, AudioObjectMixer> ObjectMixersByVolume = new Dictionary<ConfigEntry<float>, AudioObjectMixer>(ReferenceEqualityComparer<ConfigEntry<float>>.Instance);

		private static readonly Dictionary<ConfigEntry<float>, AudioPrefabChannel> ChannelsByVolume = new Dictionary<ConfigEntry<float>, AudioPrefabChannel>(ReferenceEqualityComparer<ConfigEntry<float>>.Instance);

		private static readonly Dictionary<string, string> ObjectConfigIdentityByKey = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);

		private static readonly Dictionary<string, string> PrefabConfigIdentityByKey = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);

		private static readonly List<AudioObjectMixer> ObjectMixers = new List<AudioObjectMixer>();

		private static readonly HashSet<string> KnownAudioOwnerIds = new HashSet<string>(StringComparer.OrdinalIgnoreCase);

		private static readonly Dictionary<string, KnownAudioOwner> KnownAudioOwnersByToken = new Dictionary<string, KnownAudioOwner>(StringComparer.OrdinalIgnoreCase);

		private static readonly Dictionary<string, KnownAudioOwner> KnownAudioOwnersByCanonicalId = new Dictionary<string, KnownAudioOwner>(StringComparer.OrdinalIgnoreCase);

		private static readonly HashSet<string> AmbiguousKnownAudioOwnerTokens = new HashSet<string>(StringComparer.OrdinalIgnoreCase);

		private static readonly Dictionary<int, KnownAudioOwner> OwnersByEffectPrefabId = new Dictionary<int, KnownAudioOwner>();

		private static readonly HashSet<int> SharedEffectPrefabIds = new HashSet<int>();

		private static readonly Dictionary<string, KnownAudioOwner> OwnersByEffectPrefabName = new Dictionary<string, KnownAudioOwner>(StringComparer.OrdinalIgnoreCase);

		private static readonly HashSet<string> SharedEffectPrefabNames = new HashSet<string>(StringComparer.OrdinalIgnoreCase);

		private static readonly Dictionary<int, AudioPrefabChannel> ChannelsByClipId = new Dictionary<int, AudioPrefabChannel>();

		private static readonly Dictionary<string, AudioPrefabChannel> IndexedChannelsByClipName = new Dictionary<string, AudioPrefabChannel>(StringComparer.OrdinalIgnoreCase);

		private static readonly HashSet<string> AmbiguousIndexedClipNames = new HashSet<string>(StringComparer.OrdinalIgnoreCase);

		private static readonly Dictionary<string, AudioPrefabChannel> IndexedChannelsByFilePath = new Dictionary<string, AudioPrefabChannel>(StringComparer.OrdinalIgnoreCase);

		private static readonly HashSet<string> AmbiguousIndexedFilePaths = new HashSet<string>(StringComparer.OrdinalIgnoreCase);

		private static readonly Dictionary<string, long> ScannedAudioIndexes = new Dictionary<string, long>(StringComparer.OrdinalIgnoreCase);

		private static readonly HashSet<string> ScannedModAudioFiles = new HashSet<string>(StringComparer.OrdinalIgnoreCase);

		private static readonly HashSet<int> QueuedModAudioBundleIds = new HashSet<int>();

		private static readonly HashSet<int> ScannedModAudioBundleIds = new HashSet<int>();

		private static readonly HashSet<string> ScannedEmbeddedPcmAssemblies = new HashSet<string>(StringComparer.OrdinalIgnoreCase);

		private static readonly Queue<ModAudioBundleRequest> PendingModAudioBundles = new Queue<ModAudioBundleRequest>();

		private static readonly HashSet<int> OneShotContextCheckedIds = new HashSet<int>();

		private static readonly List<AudioPrefabChannel> Channels = new List<AudioPrefabChannel>();

		private static readonly HashSet<int> KnownEmitterClipIds = new HashSet<int>();

		private static readonly HashSet<int> InspectedZsfxIds = new HashSet<int>();

		private static readonly List<WeakReference<AudioEmitterState>> ActiveStates = new List<WeakReference<AudioEmitterState>>();

		private static readonly List<WeakReference<AudioEmitterState>> RegisteredZsfxStates = new List<WeakReference<AudioEmitterState>>();

		private static readonly List<WeakReference<AudioEmitterState>> DirectAudioStates = new List<WeakReference<AudioEmitterState>>();

		private static readonly List<WeakReference<AudioEmitterState>> AudioManagerAmbientStates = new List<WeakReference<AudioEmitterState>>();

		private static readonly Dictionary<Type, MethodInfo> HoverNameMethods = new Dictionary<Type, MethodInfo>();

		private static readonly Dictionary<Type, FieldInfo> LocalizedNameFields = new Dictionary<Type, FieldInfo>();

		private static readonly Dictionary<string, HashSet<string>> SearchLocalizationByKey = new Dictionary<string, HashSet<string>>(StringComparer.OrdinalIgnoreCase);

		private static bool _searchLocalizationIndexReady;

		private static readonly Dictionary<Assembly, AudioModIdentity> ModIdentitiesByAssembly = new Dictionary<Assembly, AudioModIdentity>();

		private static readonly Dictionary<string, AudioModIdentity> JotunnModOwnersByPrefabName = new Dictionary<string, AudioModIdentity>(StringComparer.OrdinalIgnoreCase);

		private static readonly HashSet<string> AmbiguousJotunnPrefabNames = new HashSet<string>(StringComparer.OrdinalIgnoreCase);

		private static readonly Dictionary<string, AudioModIdentity> ModOwnedPrefabIdentitiesByName = new Dictionary<string, AudioModIdentity>(StringComparer.OrdinalIgnoreCase);

		private static readonly HashSet<string> AmbiguousModOwnedPrefabNames = new HashSet<string>(StringComparer.OrdinalIgnoreCase);

		private static readonly HashSet<string> ModIdsWithAudioPrefabAssets = new HashSet<string>(StringComparer.OrdinalIgnoreCase);

		private static readonly List<AudioModIdentity> KnownModIdentities = new List<AudioModIdentity>();

		private static readonly HashSet<ConfigEntry<float>> PendingVolumeChanges = new HashSet<ConfigEntry<float>>(ReferenceEqualityComparer<ConfigEntry<float>>.Instance);

		private static Coroutine _volumeRefreshRoutine;

		private static bool _modIdentitiesInitialized;

		private static bool _completedLoadedAssetScan;

		private static int _newActiveStatesSincePrune;

		private static float _lastVolumeEditDebugLogAt = -10f;

		private static float _lastAmplifierDebugLogAt = -10f;

		private static bool _orphanObjectGroupsDirty;

		private static bool _isDiscoveringLoadedAssets;

		private static bool _isScanningModAudioBundles;

		private static bool _loadedAssetRescanRequested;

		private static bool _loadedAssetChannelsChanged;

		private static int _knownAudioOwnerVersion;

		private static int _consolidatedAudioOwnerVersion = -1;

		private static bool _suppressVolumeApply;

		private static float _lastLooseClipScanTime = float.NegativeInfinity;

		private static float _lastAudioIndexScanTime = float.NegativeInfinity;

		private static bool _isDiscoveringActiveScene;

		private static int _lastActiveSceneScanHandle = -1;

		[ThreadStatic]
		private static AudioCategory _scopedCategory;

		[ThreadStatic]
		private static bool _hasScopedCategory;

		private static readonly FieldInfo ItemDataField = AccessTools.Field(typeof(ItemDrop), "m_itemData");

		private static readonly FieldInfo SharedDataField = ((ItemDataField != null) ? AccessTools.Field(ItemDataField.FieldType, "m_shared") : null);

		private static readonly FieldInfo ItemTypeField = ((SharedDataField != null) ? AccessTools.Field(SharedDataField.FieldType, "m_itemType") : null);

		private static readonly FieldInfo WindLoopSourceField = AccessTools.Field(typeof(AudioMan), "m_windLoopSource");

		private static readonly FieldInfo AmbientLoopSourceField = AccessTools.Field(typeof(AudioMan), "m_ambientLoopSource");

		private static readonly FieldInfo OceanAmbientSourceField = AccessTools.Field(typeof(AudioMan), "m_oceanAmbientSource");

		private static readonly FieldInfo FootstepCharacterField = AccessTools.Field(typeof(FootStep), "m_character");

		private static readonly FieldInfo AnimationCharacterField = AccessTools.Field(typeof(CharacterAnimEvent), "m_character");

		private static readonly FieldInfo HumanoidRightItemField = AccessTools.Field(typeof(Humanoid), "m_rightItem");

		private static readonly FieldInfo HumanoidLeftItemField = AccessTools.Field(typeof(Humanoid), "m_leftItem");

		private static readonly FieldInfo ZNetScenePrefabsField = AccessTools.Field(typeof(ZNetScene), "m_prefabs");

		private static readonly FieldInfo ZNetSceneNonNetPrefabsField = AccessTools.Field(typeof(ZNetScene), "m_nonNetViewPrefabs");

		private static readonly FieldInfo ObjectDbItemsField = AccessTools.Field(typeof(ObjectDB), "m_items");

		private static readonly FieldInfo ObjectDbStatusEffectsField = AccessTools.Field(typeof(ObjectDB), "m_StatusEffects");

		private static readonly FieldInfo ZsfxAudioSourceField = AccessTools.Field(typeof(ZSFX), "m_audioSource");

		private static readonly FieldInfo EffectListPrefabsField = AccessTools.Field(typeof(EffectList), "m_effectPrefabs");

		private static readonly FieldInfo[] ZsfxAudioClipFields = FindZsfxAudioClipFields();

		private static MethodInfo _jotunnCustomPrefabMethod;

		private static bool _jotunnMethodSearched;

		private static MethodInfo _jotunnGetPrefabsMethod;

		private static PropertyInfo _jotunnPrefabProperty;

		private static bool _jotunnRegistrySearched;

		private static readonly HashSet<int> _loggedUnifiedAmbientSourceIds = new HashSet<int>();

		internal static bool IsDiscoveringLoadedAssets => _isDiscoveringLoadedAssets;

		internal static bool HasCompletedLoadedAssetScan => _completedLoadedAssetScan;

		internal static int LoadedAudioChannelCount => Channels.Count;

		internal static event Action ChannelsChanged;

		internal static void MarkStartupAudioCatalogScanFailed(Exception exception)
		{
			_isDiscoveringLoadedAssets = false;
			_loadedAssetRescanRequested = false;
			_completedLoadedAssetScan = true;
			Plugin.LogWarning("Audio catalog scan stopped with a partial catalog: " + ((exception != null) ? (exception.GetType().Name + ": " + exception.Message) : "unknown error"));
		}

		internal static void RequestLoadedAssetDiscovery()
		{
			if (!((Object)(object)Plugin.Instance == (Object)null) && Plugin.AudioMixerActive && !_completedLoadedAssetScan)
			{
				_completedLoadedAssetScan = false;
				if (_isDiscoveringLoadedAssets)
				{
					_loadedAssetRescanRequested = true;
				}
				else
				{
					((MonoBehaviour)Plugin.Instance).StartCoroutine(DiscoverLoadedEmitters());
				}
			}
		}

		internal static AudioCategoryScope PushFootstepCategory(FootStep footstep)
		{
			AudioCategoryScope result = new AudioCategoryScope
			{
				PreviousCategory = _scopedCategory,
				HadPreviousCategory = _hasScopedCategory
			};
			AudioCategory audioCategory = ClassifyOwner((Character)(((Object)(object)footstep != (Object)null && FootstepCharacterField != null) ? ((object)/*isinst with value type is only supported in some contexts*/) : ((object)(((Object)(object)footstep != (Object)null) ? FindCharacterOwner(((Component)footstep).transform) : null))));
			if (audioCategory != AudioCategory.Unassigned)
			{
				_scopedCategory = audioCategory;
				_hasScopedCategory = true;
			}
			return result;
		}

		internal static AudioCategoryScope PushAmbientCategory()
		{
			return PushCategory(AudioCategory.Ambient);
		}

		internal static AudioCategoryScope PushEquipActionCategory(Humanoid humanoid)
		{
			return PushCategory(ClassifyOwner((Character)(object)humanoid));
		}

		internal static AudioCategoryScope PushAnimationActionCategory(CharacterAnimEvent animationEvent, string actionName)
		{
			Character val = (Character)(((Object)(object)animationEvent != (Object)null && AnimationCharacterField != null) ? ((object)/*isinst with value type is only supported in some contexts*/) : ((object)(((Object)(object)animationEvent != (Object)null) ? FindCharacterOwner(((Component)animationEvent).transform) : null)));
			AudioCategory audioCategory = ClassifyOwner(val);
			if (val is Player && !string.IsNullOrEmpty(actionName) && (actionName.IndexOf("Attack", StringComparison.OrdinalIgnoreCase) >= 0 || actionName.IndexOf("Hit", StringComparison.OrdinalIgnoreCase) >= 0))
			{
				audioCategory = ClassifyHumanoidItem((Humanoid)(object)((val is Humanoid) ? val : null), audioCategory);
			}
			if (audioCategory == AudioCategory.Unassigned)
			{
				return default(AudioCategoryScope);
			}
			return PushCategory(audioCategory);
		}

		internal static void AssignFootstepEffectCategory(FootStep footstep, GameObject effect)
		{
			if (!Plugin.AudioMixerActive || (Object)(object)footstep == (Object)null || (Object)(object)effect == (Object)null)
			{
				return;
			}
			AudioCategory audioCategory = ClassifyOwner((Character)((FootstepCharacterField != null) ? ((object)/*isinst with value type is only supported in some contexts*/) : ((object)FindCharacterOwner(((Component)footstep).transform))));
			if (audioCategory == AudioCategory.Unassigned)
			{
				return;
			}
			bool flag = false;
			ZSFX[] componentsInChildren = effect.GetComponentsInChildren<ZSFX>(true);
			foreach (ZSFX val in componentsInChildren)
			{
				if ((Object)(object)val == (Object)null)
				{
					continue;
				}
				Register(val, notify: false);
				if (States.TryGetValue(val, out var value) && value != null)
				{
					value.OwnerCategory = audioCategory;
					value.PlaybackCategory = audioCategory;
					if (value.Channel.Category != audioCategory)
					{
						value.Channel.Category = audioCategory;
						flag = true;
					}
				}
			}
			AudioSource[] componentsInChildren2 = effect.GetComponentsInChildren<AudioSource>(true);
			foreach (AudioSource val2 in componentsInChildren2)
			{
				if ((Object)(object)val2 == (Object)null || (Object)(object)((Component)val2).GetComponentInParent<ZSFX>() != (Object)null)
				{
					continue;
				}
				Register(val2, notify: false);
				if (DirectSourceStates.TryGetValue(val2, out var value2) && value2 != null)
				{
					value2.OwnerCategory = audioCategory;
					value2.PlaybackCategory = audioCategory;
					if (value2.Channel.Category != audioCategory)
					{
						value2.Channel.Category = audioCategory;
						flag = true;
					}
					AddAudioManagerAmbientState(value2);
				}
			}
			if (flag)
			{
				NotifyChannelsChanged();
			}
		}

		private static void NotifyChannelsChanged()
		{
			if (_isDiscoveringLoadedAssets)
			{
				_loadedAssetChannelsChanged = true;
			}
			else
			{
				AudioEmitterRegistry.ChannelsChanged?.Invoke();
			}
		}

		private static AudioCategoryScope PushCategory(AudioCategory category)
		{
			AudioCategoryScope result = new AudioCategoryScope
			{
				PreviousCategory = _scopedCategory,
				HadPreviousCategory = _hasScopedCategory
			};
			_scopedCategory = category;
			_hasScopedCategory = category != AudioCategory.Unassigned;
			return result;
		}

		private static AudioCategory ClassifyHumanoidItem(Humanoid humanoid, AudioCategory fallback)
		{
			if ((Object)(object)humanoid == (Object)null)
			{
				return fallback;
			}
			try
			{
				AudioCategory audioCategory = ClassifyItemData(HumanoidRightItemField?.GetValue(humanoid));
				if (audioCategory == AudioCategory.Weapons || audioCategory == AudioCategory.Tools)
				{
					return audioCategory;
				}
				AudioCategory audioCategory2 = ClassifyItemData(HumanoidLeftItemField?.GetValue(humanoid));
				if (audioCategory2 == AudioCategory.Weapons || audioCategory2 == AudioCategory.Tools)
				{
					return audioCategory2;
				}
			}
			catch (Exception ex)
			{
				Plugin.LogDebug("Could not resolve the active item for an audio action: " + ex.Message);
			}
			return fallback;
		}

		private static AudioCategory ClassifyItemData(object itemData)
		{
			if (itemData == null || SharedDataField == null || ItemTypeField == null)
			{
				return AudioCategory.Unassigned;
			}
			try
			{
				object value = SharedDataField.GetValue(itemData);
				string text = ItemTypeField.GetValue(value)?.ToString();
				if (string.IsNullOrEmpty(text))
				{
					return AudioCategory.Unassigned;
				}
				if (text.IndexOf("Tool", StringComparison.OrdinalIgnoreCase) >= 0)
				{
					return AudioCategory.Tools;
				}
				if (text.IndexOf("Weapon", StringComparison.OrdinalIgnoreCase) >= 0 || text.Equals("Bow", StringComparison.OrdinalIgnoreCase) || text.Equals("Shield", StringComparison.OrdinalIgnoreCase))
				{
					return AudioCategory.Weapons;
				}
			}
			catch
			{
			}
			return AudioCategory.Unassigned;
		}

		internal static void PopCategoryScope(AudioCategoryScope scope)
		{
			_scopedCategory = scope.PreviousCategory;
			_hasScopedCategory = scope.HadPreviousCategory;
		}

		internal static void BeginWorldSceneEmitterDiscovery()
		{
			//IL_0015: Unknown result type (might be due to invalid IL or missing references)
			//IL_001a: Unknown result type (might be due to invalid IL or missing references)
			if (!((Object)(object)Plugin.Instance == (Object)null) && !_isDiscoveringActiveScene)
			{
				Scene activeScene = SceneManager.GetActiveScene();
				int handle = ((Scene)(ref activeScene)).handle;
				if (handle >= 0 && handle != _lastActiveSceneScanHandle)
				{
					_lastActiveSceneScanHandle = handle;
					((MonoBehaviour)Plugin.Instance).StartCoroutine(DiscoverActiveSceneEmitters());
				}
			}
		}

		private static IEnumerator DiscoverActiveSceneEmitters()
		{
			_isDiscoveringActiveScene = true;
			yield return null;
			bool changed = false;
			ZSFX[] emitters = Object.FindObjectsByType<ZSFX>((FindObjectsSortMode)0);
			for (int i = 0; i < emitters.Length; i++)
			{
				ZSFX val = emitters[i];
				if ((Object)(object)val != (Object)null)
				{
					changed |= Register(val, notify: false);
					AudioEmitterState state = GetState(val);
					AudioSource source = FindZsfxAudioSource(val);
					AttachAndApplyExistingSource(state, source);
				}
				if ((i + 1) % 16 == 0)
				{
					yield return (object)new WaitForSecondsRealtime(0.015f);
				}
			}
			AudioSource[] sources = Object.FindObjectsByType<AudioSource>((FindObjectsSortMode)0);
			for (int i = 0; i < sources.Length; i++)
			{
				AudioSource val2 = sources[i];
				if ((Object)(object)val2 != (Object)null)
				{
					Scene scene = ((Component)val2).gameObject.scene;
					if (((Scene)(ref scene)).IsValid())
					{
						changed |= Register(val2, notify: false);
					}
				}
				if ((i + 1) % 16 == 0)
				{
					yield return (object)new WaitForSecondsRealtime(0.015f);
				}
			}
			_isDiscoveringActiveScene = false;
			if (changed)
			{
				NotifyChannelsChanged();
			}
		}

		internal static bool Register(ZSFX sfx, bool notify = true)
		{
			if ((Object)(object)sfx == (Object)null)
			{
				return false;
			}
			MarkZsfxClipReferences(sfx);
			if (States.TryGetValue(sfx, out var value))
			{
				bool flag = RefreshOwnership(((Component)sfx).transform, value);
				flag |= UpdateObjectDisplayName(value.Channel.ObjectMixer, GetObjectSource(sfx, FindCharacterOwner(((Component)sfx).transform), ((Component)sfx).GetComponentInParent<ItemDrop>()));
				AudioSource source = FindZsfxAudioSource(sfx);
				AudioClip val = FindZsfxAudioClip(sfx, source);
				flag |= CapturePreview(value.Channel, source, val);
				AudioCategory audioCategory = ClassifyName(((Object)((Component)sfx).gameObject).name);
				if (audioCategory == AudioCategory.Unassigned)
				{
					audioCategory = ClassifyName(value.Channel.DisplayName);
				}
				if (audioCategory == AudioCategory.Unassigned || audioCategory == AudioCategory.Others)
				{
					AudioCategory audioCategory2 = ClassifySpecificName(value.Channel.ObjectDisplayName, (value.Channel.ObjectMixer != null) ? value.Channel.ObjectMixer.DisplayName : string.Empty);
					if (audioCategory2 != AudioCategory.Unassigned)
					{
						audioCategory = audioCategory2;
					}
				}
				if (audioCategory == AudioCategory.Unassigned && (Object)(object)val != (Object)null)
				{
					audioCategory = ClassifyClip(((Object)val).name);
				}
				if (audioCategory == AudioCategory.Unassigned && IsWorldAmbientEmitter(((Component)sfx).transform))
				{
					audioCategory = AudioCategory.Ambient;
				}
				AudioCategory audioCategory3 = (_hasScopedCategory ? _scopedCategory : ResolveOwnerOrAssetCategory(value.OwnerCategory, value.ItemCategory, audioCategory));
				if (audioCategory3 != AudioCategory.Unassigned && value.Channel.Category != audioCategory3)
				{
					value.Channel.Category = audioCategory3;
					flag = true;
					_orphanObjectGroupsDirty = true;
				}
				if (_hasScopedCategory)
				{
					value.PlaybackCategory = _scopedCategory;
				}
				AttachAndApplyExistingSource(value, source);
				if (notify && flag)
				{
					AudioEmitterRegistry.ChannelsChanged?.Invoke();
				}
				return flag;
			}
			Character val2 = FindCharacterOwner(((Component)sfx).transform);
			AudioCategory audioCategory4 = ClassifyOwner(val2);
			ItemDrop componentInParent = ((Component)sfx).GetComponentInParent<ItemDrop>();
			AudioCategory itemCategory = ClassifyItem(componentInParent);
			string prefabDisplayName = GetPrefabDisplayName(sfx, val2, componentInParent);
			GameObject objectSource = GetObjectSource(sfx, val2, componentInParent);
			string liveObjectName = GetLiveObjectName(objectSource);
			AudioModIdentity modIdentity = GetModIdentity(((Component)sfx).transform);
			bool flag2 = IsJotunnPrefab(sfx, val2, componentInParent) || modIdentity != null || HasThirdPartyComponent(((Component)sfx).transform);
			flag2 = flag2 || modIdentity != null;
			AudioSource val3 = FindZsfxAudioSource(sfx);
			AudioClip val4 = FindZsfxAudioClip(sfx, val3);
			AudioCategory audioCategory5 = ClassifyName(((Object)((Component)sfx).gameObject).name);
			if (audioCategory5 == AudioCategory.Unassigned)
			{
				audioCategory5 = ClassifyName(prefabDisplayName);
			}
			if (audioCategory5 == AudioCategory.Unassigned || audioCategory5 == AudioCategory.Others)
			{
				AudioCategory audioCategory6 = ClassifySpecificName(liveObjectName, ((Object)(object)val2 != (Object)null) ? CleanName(((Object)((Component)val2).gameObject).name) : string.Empty, ((Object)(object)componentInParent != (Object)null) ? CleanName(((Object)((Component)componentInParent).gameObject).name) : string.Empty);
				if (audioCategory6 != AudioCategory.Unassigned)
				{
					audioCategory5 = audioCategory6;
				}
			}
			if (audioCategory5 == AudioCategory.Unassigned && (Object)(object)val4 != (Object)null)
			{
				audioCategory5 = ClassifyClip(((Object)val4).name);
			}
			if (audioCategory5 == AudioCategory.Unassigned && IsWorldAmbientEmitter(((Component)sfx).transform))
			{
				audioCategory5 = AudioCategory.Ambient;
			}
			AudioCategory category = (_hasScopedCategory ? _scopedCategory : ResolveOwnerOrAssetCategory(audioCategory4, itemCategory, audioCategory5));
			bool changed;
			AudioPrefabChannel orCreateChannel = GetOrCreateChannel(MakeChannelId(prefabDisplayName), prefabDisplayName, liveObjectName, category, flag2, out changed, objectSource, modIdentity);
			if (orCreateChannel == null)
			{
				return false;
			}
			audioCategory4 = GetKnownMixerOwnerCategory(orCreateChannel, audioCategory4);
			category = (_hasScopedCategory ? _scopedCategory : orCreateChannel.Category);
			changed |= CapturePreview(orCreateChannel, val3, val4);
			AudioEmitterState audioEmitterState = new AudioEmitterState(orCreateChannel, audioCategory4, componentInParent, itemCategory)
			{
				PlaybackCategory = category,
				AppliedSource = val3,
				BaseVolume = (((Object)(object)val3 != (Object)null) ? val3.volume : 1f),
				LastAppliedVolume = (((Object)(object)val3 != (Object)null) ? val3.volume : 1f),
				HasVolumeSnapshot = ((Object)(object)val3 != (Object)null)
			};
			States.Add(sfx, audioEmitterState);
			RegisteredZsfxStates.Add(new WeakReference<AudioEmitterState>(audioEmitterState));
			AttachAndApplyExistingSource(audioEmitterState, val3);
			if (notify && changed)
			{
				AudioEmitterRegistry.ChannelsChanged?.Invoke();
			}
			return true;
		}

		internal static bool Register(AudioSource source, bool notify = true)
		{
			//IL_051b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0520: Unknown result type (might be due to invalid IL or missing references)
			if ((Object)(object)source == (Object)null)
			{
				return false;
			}
			if ((Object)(object)source.clip != (Object)null)
			{
				KnownEmitterClipIds.Add(((Object)source.clip).GetInstanceID());
			}
			if (DirectSourceStates.TryGetValue(source, out var value))
			{
				bool flag = RefreshOwnership(((Component)source).transform, value);
				flag |= UpdateObjectDisplayName(value.Channel.ObjectMixer, GetObjectSource(source, FindCharacterOwner(((Component)source).transform), ((Component)source).GetComponentInParent<ItemDrop>()));
				flag |= CapturePreview(value.Channel, source, source.clip);
				AudioCategory audioCategory = ClassifyName(value.Channel.DisplayName);
				if (audioCategory == AudioCategory.Unassigned || audioCategory == AudioCategory.Others)
				{
					audioCategory = ClassifySpecificName(value.Channel.ObjectDisplayName, (value.Channel.ObjectMixer != null) ? value.Channel.ObjectMixer.DisplayName : string.Empty);
				}
				if (audioCategory == AudioCategory.Unassigned && (Object)(object)source.clip != (Object)null)
				{
					audioCategory = ClassifyClip(((Object)source.clip).name);
				}
				if (audioCategory == AudioCategory.Unassigned && IsWorldAmbientEmitter(((Component)source).transform))
				{
					audioCategory = AudioCategory.Ambient;
				}
				AudioCategory audioCategory2 = ResolveOwnerOrAssetCategory(value.OwnerCategory, value.ItemCategory, audioCategory);
				if (audioCategory2 != AudioCategory.Unassigned && value.Channel.Category != audioCategory2)
				{
					value.Channel.Category = audioCategory2;
					flag = true;
					_orphanObjectGroupsDirty = true;
				}
				if (_hasScopedCategory)
				{
					value.PlaybackCategory = _scopedCategory;
				}
				AddAudioManagerAmbientState(value);
				AttachAndApplyExistingSource(value, source);
				if (notify && flag)
				{
					AudioEmitterRegistry.ChannelsChanged?.Invoke();
				}
				return flag;
			}
			if ((Object)(object)((Component)source).GetComponentInParent<ZSFX>() != (Object)null || IsNativeAmbientSource(source))
			{
				return false;
			}
			if ((Object)(object)source.clip == (Object)null)
			{
				return false;
			}
			Character val = FindCharacterOwner(((Component)source).transform);
			ItemDrop componentInParent = ((Component)source).GetComponentInParent<ItemDrop>();
			AudioCategory audioCategory3 = ClassifyOwner(val);
			AudioCategory itemCategory = ClassifyItem(componentInParent);
			string audioSourceDisplayName = GetAudioSourceDisplayName(source, val, componentInParent);
			GameObject objectSource = GetObjectSource(source, val, componentInParent);
			string liveObjectName = GetLiveObjectName(objectSource);
			AudioModIdentity modIdentity = GetModIdentity(((Component)source).transform);
			AudioCategory audioCategory4 = ClassifyName(audioSourceDisplayName);
			if (audioCategory4 == AudioCategory.Unassigned || audioCategory4 == AudioCategory.Others)
			{
				AudioCategory audioCategory5 = ClassifySpecificName(liveObjectName, ((Object)(object)val != (Object)null) ? CleanName(((Object)((Component)val).gameObject).name) : string.Empty, ((Object)(object)componentInParent != (Object)null) ? CleanName(((Object)((Component)componentInParent).gameObject).name) : string.Empty, ((Object)(object)((Component)source).transform.root != (Object)null) ? CleanName(((Object)((Component)((Component)source).transform.root).gameObject).name) : string.Empty);
				if (audioCategory5 != AudioCategory.Unassigned)
				{
					audioCategory4 = audioCategory5;
				}
			}
			if ((Object)(object)source.clip != (Object)null && audioCategory4 == AudioCategory.Unassigned)
			{
				audioCategory4 = ClassifyClip(((Object)source.clip).name);
			}
			if (audioCategory4 == AudioCategory.Unassigned && IsWorldAmbientEmitter(((Component)source).transform))
			{
				audioCategory4 = AudioCategory.Ambient;
			}
			AudioCategory category = (_hasScopedCategory ? _scopedCategory : ResolveOwnerOrAssetCategory(audioCategory3, itemCategory, audioCategory4));
			string id = "direct:" + audioSourceDisplayName.ToLowerInvariant();
			bool flag2 = IsJotunnPrefab(new string[5]
			{
				((Object)(object)val != (Object)null) ? CleanName(((Object)((Component)val).gameObject).name) : string.Empty,
				((Object)(object)componentInParent != (Object)null) ? CleanName(((Object)((Component)componentInParent).gameObject).name) : string.Empty,
				((Object)(object)((Component)source).transform.root != (Object)null) ? CleanName(((Object)((Component)((Component)source).transform.root).gameObject).name) : string.Empty,
				CleanName(((Object)((Component)source).gameObject).name),
				((Object)(object)source.clip != (Object)null) ? CleanName(((Object)source.clip).name) : string.Empty
			}) || modIdentity != null || HasThirdPartyComponent(((Component)source).transform);
			flag2 = flag2 || modIdentity != null;
			AudioPrefabChannel audioPrefabChannel = null;
			bool changed = false;
			if ((Object)(object)source.clip != (Object)null && ChannelsByClipId.TryGetValue(((Object)source.clip).GetInstanceID(), out var value2) && IsIndexedChannelCompatible(value2, val, componentInParent))
			{
				audioPrefabChannel = value2;
			}
			if (audioPrefabChannel == null)
			{
				audioPrefabChannel = GetOrCreateChannel(id, audioSourceDisplayName, liveObjectName, category, flag2, out changed, objectSource, modIdentity);
			}
			if (audioPrefabChannel == null)
			{
				return false;
			}
			changed |= SetChannelModIdentity(audioPrefabChannel, modIdentity, flag2);
			audioCategory3 = GetKnownMixerOwnerCategory(audioPrefabChannel, audioCategory3);
			category = (_hasScopedCategory ? _scopedCategory : audioPrefabChannel.Category);
			changed |= CapturePreview(audioPrefabChannel, source, source.clip);
			AudioEmitterState audioEmitterState = new AudioEmitterState(audioPrefabChannel, audioCategory3, componentInParent, itemCategory)
			{
				AppliedSource = source,
				PlaybackCategory = category,
				IsDirectAudioSource = true,
				BaseVolume = source.volume,
				LastAppliedVolume = source.volume,
				HasVolumeSnapshot = true
			};
			DirectSourceStates.Add(source, audioEmitterState);
			DirectAudioStates.Add(new WeakReference<AudioEmitterState>(audioEmitterState));
			AddAudioManagerAmbientState(audioEmitterState);
			if (!source.isPlaying)
			{
				Scene scene = ((Component)source).gameObject.scene;
				if (((Scene)(ref scene)).IsValid())
				{
					PrepareAudioSourceForPlayback(source);
				}
			}
			Apply(source, audioEmitterState, playbackStarted: false);
			TrackActiveState(audioEmitterState);
			if (notify && changed)
			{
				AudioEmitterRegistry.ChannelsChanged?.Invoke();
			}
			return true;
		}

		private static bool SetChannelModIdentity(AudioPrefabChannel channel, AudioModIdentity identity, bool isModded)
		{
			if (channel == null)
			{
				return false;
			}
			bool result = false;
			if (isModded && !channel.IsModded)
			{
				channel.IsModded = true;
				result = true;
			}
			if (identity == null)
			{
				if (isModded && string.IsNullOrWhiteSpace(channel.ModId))
				{
					channel.ModId = "mod:unknown";
					channel.ModDisplayName = Translations.Text("OtherMods");
					result = true;
				}
				return result;
			}
			if (string.IsNullOrWhiteSpace(channel.ModId) || string.Equals(channel.ModId, "mod:unknown", StringComparison.OrdinalIgnoreCase))
			{
				channel.ModId = identity.Id;
				channel.ModDisplayName = identity.DisplayName;
				result = true;
			}
			return result;
		}

		private static void EnsureModIdentities()
		{
			if (_modIdentitiesInitialized)
			{
				return;
			}
			_modIdentitiesInitialized = true;
			try
			{
				foreach (PluginInfo value in Chainloader.PluginInfos.Values)
				{
					if (value != null && !((Object)(object)value.Instance == (Object)null) && value.Metadata != null)
					{
						Assembly assembly = ((object)value.Instance).GetType().Assembly;
						if (!(assembly == null))
						{
							string id = (string.IsNullOrWhiteSpace(value.Metadata.GUID) ? assembly.GetName().Name : value.Metadata.GUID);
							string displayName = (string.IsNullOrWhiteSpace(value.Metadata.Name) ? MakeFriendlyModName(assembly.GetName().Name) : value.Metadata.Name);
							AudioModIdentity audioModIdentity = new AudioModIdentity
							{
								Id = id,
								DisplayName = displayName
							};
							ModIdentitiesByAssembly[assembly] = audioModIdentity;
							KnownModIdentities.Add(audioModIdentity);
						}
					}
				}
			}
			catch (Exception ex)
			{
				Plugin.LogWarning("Could not index loaded mod identities: " + ex.GetType().Name);
			}
		}

		private static AudioModIdentity GetModIdentity(Transform source)
		{
			if ((Object)(object)source == (Object)null)
			{
				return null;
			}
			EnsureModIdentities();
			Transform val = source;
			while ((Object)(object)val != (Object)null)
			{
				string text = NormalizeOwnerToken(CleanName(((Object)((Component)val).gameObject).name));
				if (text.Length >= 3 && !AmbiguousModOwnedPrefabNames.Contains(text) && ModOwnedPrefabIdentitiesByName.TryGetValue(text, out var value))
				{
					return value;
				}
				if (text.Length >= 3 && !AmbiguousJotunnPrefabNames.Contains(text) && JotunnModOwnersByPrefabName.TryGetValue(text, out var value2))
				{
					return value2;
				}
				MonoBehaviour[] components = ((Component)val).GetComponents<MonoBehaviour>();
				foreach (MonoBehaviour val2 in components)
				{
					if (!((Object)(object)val2 == (Object)null))
					{
						Assembly assembly = ((object)val2).GetType().Assembly;
						if (assembly != typeof(ZSFX).Assembly && assembly != typeof(Plugin).Assembly && ModIdentitiesByAssembly.TryGetValue(assembly, out var value3))
						{
							return value3;
						}
					}
				}
				val = val.parent;
			}
			return null;
		}

		private static AudioModIdentity GetModIdentityFromType(Type ownerType)
		{
			if (ownerType == null)
			{
				return null;
			}
			EnsureModIdentities();
			if (!ModIdentitiesByAssembly.TryGetValue(ownerType.Assembly, out var value))
			{
				return null;
			}
			return value;
		}

		private static AudioModIdentity GetModIdentityFromIndexPath(string pluginRoot, string indexPath)
		{
			EnsureModIdentities();
			try
			{
				string fullPath = Path.GetFullPath(indexPath);
				AudioModIdentity audioModIdentity = null;
				int num = -1;
				char directorySeparatorChar;
				foreach (PluginInfo value in Chainloader.PluginInfos.Values)
				{
					if (value == null || (Object)(object)value.Instance == (Object)null)
					{
						continue;
					}
					string directoryName = Path.GetDirectoryName(((object)value.Instance).GetType().Assembly.Location);
					if (!string.IsNullOrWhiteSpace(directoryName))
					{
						string text = Path.GetFullPath(directoryName).TrimEnd(Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar);
						directorySeparatorChar = Path.DirectorySeparatorChar;
						if (fullPath.StartsWith(text + directorySeparatorChar, StringComparison.OrdinalIgnoreCase) && text.Length > num)
						{
							audioModIdentity = ModIdentitiesByAssembly[((object)value.Instance).GetType().Assembly];
							num = text.Length;
						}
					}
				}
				ThunderstoreModManifest manifest = ReadModManifest(FindManifestForPath(fullPath, pluginRoot));
				if (audioModIdentity != null)
				{
					return IsAudioDialoguesVoicePack(manifest) ? new AudioModIdentity
					{
						Id = audioModIdentity.Id,
						DisplayName = "Audio Dialogues PT-BR"
					} : audioModIdentity;
				}
				string text2 = Path.GetFullPath(pluginRoot).TrimEnd(Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar);
				directorySeparatorChar = Path.DirectorySeparatorChar;
				string text3 = text2 + directorySeparatorChar;
				if (!fullPath.StartsWith(text3, StringComparison.OrdinalIgnoreCase))
				{
					return null;
				}
				string text4 = fullPath.Substring(text3.Length).Split(Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar)[0];
				if (string.IsNullOrWhiteSpace(text4) || text4.IndexOf('.') >= 0)
				{
					return null;
				}
				ThunderstoreModManifest thunderstoreModManifest = ReadModManifest(Path.Combine(pluginRoot, text4, "manifest.json"));
				string name = ((thunderstoreModManifest != null && !string.IsNullOrWhiteSpace(thunderstoreModManifest.name)) ? thunderstoreModManifest.name : text4);
				if (IsAudioDialoguesVoicePack(thunderstoreModManifest))
				{
					name = "Audio Dialogues PT-BR";
				}
				name = MakeFriendlyModName(name).Replace("PTBR", "PT-BR").Replace("Ptbr", "PT-BR");
				return new AudioModIdentity
				{
					Id = "path:" + text4.ToLowerInvariant(),
					DisplayName = name
				};
			}
			catch
			{
				return null;
			}
		}

		private static ThunderstoreModManifest ReadModManifest(string manifestPath)
		{
			try
			{
				if (File.Exists(manifestPath))
				{
					return JsonUtility.FromJson<ThunderstoreModManifest>(File.ReadAllText(manifestPath));
				}
			}
			catch
			{
			}
			return null;
		}

		private static string FindManifestForPath(string filePath, string pluginRoot)
		{
			try
			{
				string value = Path.GetFullPath(pluginRoot).TrimEnd(Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar);
				DirectoryInfo directoryInfo = new DirectoryInfo(Path.GetDirectoryName(Path.GetFullPath(filePath)));
				while (directoryInfo != null && directoryInfo.FullName.StartsWith(value, StringComparison.OrdinalIgnoreCase))
				{
					string text = Path.Combine(directoryInfo.FullName, "manifest.json");
					if (File.Exists(text))
					{
						return text;
					}
					directoryInfo = directoryInfo.Parent;
				}
			}
			catch
			{
			}
			return null;
		}

		private static bool IsAudioDialoguesVoicePack(ThunderstoreModManifest manifest)
		{
			if (manifest == null || manifest.dependencies == null)
			{
				return false;
			}
			for (int i = 0; i < manifest.dependencies.Length; i++)
			{
				if (!string.IsNullOrWhiteSpace(manifest.dependencies[i]) && manifest.dependencies[i].StartsWith("Tchernobill-AudioDialogues-", StringComparison.OrdinalIgnoreCase))
				{
					return true;
				}
			}
			return false;
		}

		private static AudioModIdentity GetModIdentityFromName(params string[] names)
		{
			EnsureModIdentities();
			if (names == null || KnownModIdentities.Count == 0)
			{
				return null;
			}
			AudioModIdentity result = null;
			int num = 3;
			for (int i = 0; i < KnownModIdentities.Count; i++)
			{
				AudioModIdentity audioModIdentity = KnownModIdentities[i];
				string[] array = new string[2] { audioModIdentity.Id, audioModIdentity.DisplayName };
				for (int j = 0; j < array.Length; j++)
				{
					string text = NormalizeModNameToken(array[j]);
					if (text.Length <= num)
					{
						continue;
					}
					for (int k = 0; k < names.Length; k++)
					{
						if (ContainsModNameToken(names[k], text))
						{
							result = audioModIdentity;
							num = text.Length;
							break;
						}
					}
				}
			}
			return result;
		}

		private static string NormalizeModNameToken(string value)
		{
			if (string.IsNullOrWhiteSpace(value))
			{
				return string.Empty;
			}
			return Regex.Replace(value.ToLowerInvariant(), "[^a-z0-9]+", " ").Trim();
		}

		private static bool ContainsModNameToken(string value, string token)
		{
			if (string.IsNullOrWhiteSpace(value) || string.IsNullOrWhiteSpace(token))
			{
				return false;
			}
			return (" " + NormalizeModNameToken(value) + " ").IndexOf(" " + token + " ", StringComparison.Ordinal) >= 0;
		}

		private static string MakeFriendlyModName(string name)
		{
			if (string.IsNullOrWhiteSpace(name))
			{
				return Translations.Text("OtherMods");
			}
			string text = name.Replace('_', ' ').Replace('-', ' ').Trim();
			if (text.Length <= 0)
			{
				return text;
			}
			return char.ToUpperInvariant(text[0]) + text.Substring(1);
		}

		private static AudioPrefabChannel GetOrCreateChannel(string id, string displayName, string objectDisplayName, AudioCategory category, bool isModded, out bool changed, GameObject objectSource = null, AudioModIdentity modIdentity = null, AudioObjectMixer assignedObjectMixer = null)
		{
			//IL_0246: Unknown result type (might be due to invalid IL or missing references)
			//IL_024d: Expected O, but got Unknown
			changed = false;
			if (modIdentity == null && isModded)
			{
				modIdentity = GetModIdentityFromName(displayName, objectDisplayName, id);
			}
			if (modIdentity == null && isModded)
			{
				modIdentity = new AudioModIdentity
				{
					Id = "mod:unknown",
					DisplayName = Translations.Text("OtherMods")
				};
			}
			isModded = isModded || modIdentity != null;
			bool changed2 = false;
			AudioObjectMixer audioObjectMixer = assignedObjectMixer ?? GetOrCreateObjectMixer(objectSource, objectDisplayName, displayName, isModded, out changed2);
			if (audioObjectMixer == null)
			{
				changed = changed2;
				return null;
			}
			changed |= changed2;
			if (!_hasScopedCategory && audioObjectMixer != null && audioObjectMixer.OwnerCategory != AudioCategory.Unassigned)
			{
				AudioCategory audioCategory = ResolveOwnerOrAssetCategory(audioObjectMixer.OwnerCategory, AudioCategory.Unassigned, category);
				if (audioCategory != AudioCategory.Unassigned)
				{
					category = audioCategory;
				}
			}
			string text = id + "|object:" + audioObjectMixer.Id;
			string text2 = text;
			if (ChannelsById.TryGetValue(text, out var value))
			{
				string text3 = ((modIdentity != null) ? modIdentity.Id : (isModded ? "mod:unknown" : null));
				string text4 = ((!string.IsNullOrWhiteSpace(value.ModId)) ? value.ModId : (value.IsModded ? "mod:unknown" : null));
				bool num;
				if (text3 != null)
				{
					if (text4 == null || string.Equals(text4, text3, StringComparison.OrdinalIgnoreCase))
					{
						goto IL_0164;
					}
					num = !string.Equals(text4, "mod:unknown", StringComparison.OrdinalIgnoreCase);
				}
				else
				{
					num = text4 != null;
				}
				if (num)
				{
					text2 = text + "|mod:" + (text3 ?? "vanilla").ToLowerInvariant();
				}
			}
			goto IL_0164;
			IL_0164:
			if (ChannelsById.TryGetValue(text2, out var value2))
			{
				if (category != AudioCategory.Unassigned && value2.Category != category)
				{
					value2.Category = category;
					changed = true;
					_orphanObjectGroupsDirty = true;
				}
				if (isModded && !value2.IsModded)
				{
					value2.IsModded = true;
					changed = true;
				}
				changed |= SetChannelModIdentity(value2, modIdentity, isModded);
				return value2;
			}
			float num2 = GetLegacyPrefabVolume(displayName, 1f);
			if (Mathf.Approximately(num2, 1f))
			{
				num2 = GetLegacyObjectChannelVolume(id, audioObjectMixer, 1f);
			}
			AcceptableValueRange<float> val = new AcceptableValueRange<float>(0f, 2f);
			ConfigDescription description = new ConfigDescription("Per-prefab volume: " + displayName + " (" + CategoryName(category) + ")", (AcceptableValueBase)(object)val, new object[1]
			{
				new ConfigurationManagerAttributes
				{
					Browsable = false
				}
			});
			ConfigEntry<float> val2 = BindUniqueVolumeEntry("Audio Prefabs", "Prefab_", text2, num2, description, PrefabConfigIdentityByKey, (ConfigEntry<float> entry) => ChannelsByVolume.ContainsKey(entry));
			if (val2 == null)
			{
				return null;
			}
			val2.SettingChanged += OnAudioVolumeChanged;
			value2 = new AudioPrefabChannel(text2, displayName, audioObjectMixer, category, val2, isModded);
			SetChannelModIdentity(value2, modIdentity, isModded);
			ChannelsByVolume.Add(val2, value2);
			ChannelsById.Add(text2, value2);
			Channels.Add(value2);
			audioObjectMixer.Channels.Add(value2);
			_orphanObjectGroupsDirty = true;
			changed = true;
			return value2;
		}

		private static AudioObjectMixer GetOrCreateObjectMixer(GameObject objectSource, string localizedName, string fallbackName, bool isModded, out bool changed)
		{
			return GetOrCreateObjectMixer(objectSource, localizedName, fallbackName, isModded, null, out changed);
		}

		private static AudioObjectMixer GetOrCreateObjectMixer(GameObject objectSource, string localizedName, string fallbackName, bool isModded, KnownAudioOwner knownOwner, out bool changed)
		{
			//IL_0368: Unknown result type (might be due to invalid IL or missing references)
			//IL_036f: Expected O, but got Unknown
			changed = false;
			string text = (((Object)(object)objectSource != (Object)null) ? CleanName(((Object)objectSource).name) : string.Empty);
			string groupId;
			string displayName;
			bool flag = TryResolveAmbientLocationGroup(text, fallbackName, out groupId, out displayName);
			bool flag2 = IsCaveBatAmbienceName(text) || IsCaveBatAmbienceName(fallbackName);
			bool flag3 = flag && string.Equals(groupId, "environment:caves", StringComparison.OrdinalIgnoreCase);
			string parentPrefabName = string.Empty;
			string displayName2 = string.Empty;
			bool flag4 = false;
			if (flag)
			{
				parentPrefabName = groupId;
				displayName2 = displayName;
				flag4 = true;
				knownOwner = null;
			}
			else if (flag2)
			{
				groupId = "environment:caves";
				displayName = Translations.Text("Cave");
				parentPrefabName = groupId;
				displayName2 = displayName;
				flag4 = true;
				knownOwner = null;
			}
			else if (TryResolveAudioParent(text, fallbackName, out parentPrefabName, out displayName2))
			{
				flag4 = true;
			}
			if (knownOwner == null && flag4 && !flag && !flag2)
			{
				knownOwner = FindKnownAudioOwner(parentPrefabName);
			}
			if (knownOwner == null && !flag && !flag2 && !string.IsNullOrWhiteSpace(text))
			{
				knownOwner = FindKnownAudioOwner(text);
			}
			string text2 = (flag4 ? parentPrefabName : text);
			if (!flag4 && !string.IsNullOrWhiteSpace(text2))
			{
				text2 = StripNumericVariantSuffix(text2);
			}
			string text3 = ((flag || flag2) ? ("object:" + groupId.ToLowerInvariant()) : ((knownOwner != null) ? ("object:" + knownOwner.CanonicalId) : ((((Object)(object)objectSource != (Object)null || flag4) && !string.IsNullOrWhiteSpace(text2)) ? ("object:" + text2.ToLowerInvariant()) : ("unbound:" + StripNumericVariantSuffix(string.IsNullOrWhiteSpace(fallbackName) ? "audio" : fallbackName.Trim()).ToLowerInvariant()))));
			string text4 = ((flag || flag2) ? displayName : ((knownOwner != null && !string.IsNullOrWhiteSpace(knownOwner.DisplayName)) ? knownOwner.DisplayName : (flag4 ? displayName2 : ((!string.IsNullOrWhiteSpace(localizedName)) ? StripNumericVariantSuffix(localizedName.Trim()) : ((!string.IsNullOrWhiteSpace(text)) ? StripNumericVariantSuffix(text) : StripNumericVariantSuffix(fallbackName))))));
			AudioCategory audioCategory = ((flag || flag2) ? AudioCategory.Ambient : (knownOwner?.Category ?? AudioCategory.Unassigned));
			if (audioCategory == AudioCategory.Unassigned || audioCategory == AudioCategory.Others)
			{
				AudioCategory audioCategory2 = ClassifySpecificName(text, text4, localizedName, fallbackName, parentPrefabName);
				if (audioCategory2 != AudioCategory.Unassigned)
				{
					audioCategory = audioCategory2;
				}
			}
			text4 = StripDisplayBrackets(text4);
			if (ObjectMixersById.TryGetValue(text3, out var value))
			{
				if ((Object)(object)objectSource != (Object)null && (value.ObjectSource == null || !value.ObjectSource.TryGetTarget(out var target) || target != objectSource))
				{
					value.ObjectSource = new WeakReference<GameObject>(objectSource);
				}
				if (!string.IsNullOrWhiteSpace(text4) && !string.Equals(value.DisplayName, text4, StringComparison.Ordinal))
				{
					value.DisplayName = text4;
					changed = true;
				}
				if (audioCategory != AudioCategory.Unassigned && value.OwnerCategory != audioCategory)
				{
					value.OwnerCategory = audioCategory;
					changed = true;
					_orphanObjectGroupsDirty = true;
				}
				if (isModded && !value.IsModded)
				{
					value.IsModded = true;
				}
				if (knownOwner != null)
				{
					AddLegacyObjectIds(value, knownOwner, text, parentPrefabName);
				}
				if (flag3 || flag2)
				{
					AddLegacyObjectId(value, "audio-family:amb_caves_bat_m");
				}
				return value;
			}
			AcceptableValueRange<float> val = new AcceptableValueRange<float>(0f, 2f);
			float legacyObjectVolume = GetLegacyObjectVolume(knownOwner, text, parentPrefabName, (flag3 || flag2) ? "audio-family:amb_caves_bat_m" : null);
			ConfigDescription description = new ConfigDescription("Master volume for object: " + text4, (AcceptableValueBase)(object)val, new object[1]
			{
				new ConfigurationManagerAttributes
				{
					Browsable = false
				}
			});
			ConfigEntry<float> val2 = BindUniqueVolumeEntry("Audio Object Masters", "Object_", text3, legacyObjectVolume, description, ObjectConfigIdentityByKey, (ConfigEntry<float> entry) => ObjectMixersByVolume.ContainsKey(entry));
			if (val2 == null)
			{
				return null;
			}
			val2.SettingChanged += OnAudioVolumeChanged;
			AudioObjectMixer audioObjectMixer = new AudioObjectMixer(text3, text4, val2, isModded, objectSource, audioCategory);
			if (knownOwner != null)
			{
				AddLegacyObjectIds(audioObjectMixer, knownOwner, text, parentPrefabName);
			}
			else
			{
				AddLegacyObjectId(audioObjectMixer, text2);
			}
			if (flag3 || flag2)
			{
				AddLegacyObjectId(audioObjectMixer, "audio-family:amb_caves_bat_m");
			}
			ObjectMixersByVolume.Add(val2, audioObjectMixer);
			ObjectMixersById.Add(text3, audioObjectMixer);
			ObjectMixers.Add(audioObjectMixer);
			changed = true;
			return audioObjectMixer;
		}

		private static AudioObjectMixer GetOrCreateModAudioMixer(AudioModIdentity identity, string objectName, out bool changed)
		{
			//IL_0108: Unknown result type (might be due to invalid IL or missing references)
			//IL_010f: Expected O, but got Unknown
			changed = false;
			if (identity == null)
			{
				return null;
			}
			objectName = StripDisplayBrackets(objectName);
			if (string.IsNullOrWhiteSpace(objectName))
			{
				objectName = identity.DisplayName;
			}
			string text = Regex.Replace(identity.Id ?? string.Empty, "[^a-zA-Z0-9]+", "_").Trim(new char[1] { '_' }).ToLowerInvariant();
			string text2 = NormalizeOwnerToken(objectName);
			if (text.Length == 0 || text2.Length == 0)
			{
				return null;
			}
			string text3 = "modobject:" + text + ":" + text2;
			if (ObjectMixersById.TryGetValue(text3, out var value))
			{
				if (!string.IsNullOrWhiteSpace(objectName) && !string.Equals(value.DisplayName, objectName, StringComparison.Ordinal))
				{
					value.DisplayName = objectName;
					changed = true;
				}
				value.ModId = identity.Id;
				value.ModDisplayName = identity.DisplayName;
				value.IsModded = true;
				return value;
			}
			AcceptableValueRange<float> val = new AcceptableValueRange<float>(0f, 2f);
			ConfigDescription description = new ConfigDescription("Master volume for mod audio object: " + objectName, (AcceptableValueBase)(object)val, new object[1]
			{
				new ConfigurationManagerAttributes
				{
					Browsable = false
				}
			});
			ConfigEntry<float> val2 = BindUniqueVolumeEntry("Audio Object Masters", "Object_", text3, 1f, description, ObjectConfigIdentityByKey, (ConfigEntry<float> entry) => ObjectMixersByVolume.ContainsKey(entry));
			if (val2 == null)
			{
				return null;
			}
			val2.SettingChanged += OnAudioVolumeChanged;
			AudioObjectMixer audioObjectMixer = new AudioObjectMixer(text3, objectName, val2, isModded: true, null, AudioCategory.Unassigned)
			{
				ModId = identity.Id,
				ModDisplayName = identity.DisplayName
			};
			ObjectMixersByVolume.Add(val2, audioObjectMixer);
			ObjectMixersById.Add(text3, audioObjectMixer);
			ObjectMixers.Add(audioObjectMixer);
			changed = true;
			return audioObjectMixer;
		}

		private static void AddLegacyObjectIds(AudioObjectMixer mixer, KnownAudioOwner owner, params string[] additionalNames)
		{
			if (mixer == null)
			{
				return;
			}
			AddLegacyObjectId(mixer, owner?.PrefabName);
			AddLegacyObjectId(mixer, owner?.ObjectName);
			if (additionalNames != null)
			{
				for (int i = 0; i < additionalNames.Length; i++)
				{
					AddLegacyObjectId(mixer, additionalNames[i]);
				}
			}
		}

		private static void AddLegacyObjectId(AudioObjectMixer mixer, string name)
		{
			if (mixer != null && !string.IsNullOrWhiteSpace(name))
			{
				string legacyId = "object:" + CleanName(name).ToLowerInvariant();
				AddLegacyObjectIdValue(mixer, legacyId);
			}
		}

		private static string GetUniqueConfigKey(string section, string prefix, string identity, Dictionary<string, string> identitiesByKey)
		{
			string text = prefix + Hash(identity).ToString("X8");
			string text2 = text;
			int num = 0;
			string value;
			while (identitiesByKey.TryGetValue(text2, out value) && !string.Equals(value, identity, StringComparison.OrdinalIgnoreCase))
			{
				num++;
				text2 = text + "_" + Hash(identity + "|collision|" + num).ToString("X8");
			}
			identitiesByKey[text2] = identity;
			return text2;
		}

		private static ConfigEntry<float> BindUniqueVolumeEntry(string section, string prefix, string identity, float defaultValue, ConfigDescription description, Dictionary<string, string> identitiesByKey, Func<ConfigEntry<float>, bool> isInUse)
		{
			string uniqueConfigKey = GetUniqueConfigKey(section, prefix, identity, identitiesByKey);
			string text = uniqueConfigKey;
			for (int i = 0; i < 1024; i++)
			{
				ConfigEntry<float> val = ((BaseUnityPlugin)Plugin.Instance).Config.Bind<float>(section, text, defaultValue, description);
				if (val != null && !isInUse(val))
				{
					identitiesByKey[text] = identity;
					return val;
				}
				string value = identity + "|runtime-config-collision|" + i.ToString(CultureInfo.InvariantCulture);
				text = uniqueConfigKey + "_Runtime" + Hash(value).ToString("X8");
				string value2;
				while (identitiesByKey.TryGetValue(text, out value2) && !string.Equals(value2, identity, StringComparison.OrdinalIgnoreCase))
				{
					i++;
					value = identity + "|runtime-config-collision|" + i.ToString(CultureInfo.InvariantCulture);
					text = uniqueConfigKey + "_Runtime" + Hash(value).ToString("X8");
				}
			}
			Plugin.LogError("Unable to allocate a unique audio volume setting for " + identity + ".");
			return null;
		}

		private static void AddLegacyObjectIdValue(AudioObjectMixer mixer, string legacyId)
		{
			if (mixer == null || string.IsNullOrWhiteSpace(legacyId))
			{
				return;
			}
			for (int i = 0; i < mixer.LegacyIds.Count; i++)
			{
				if (string.Equals(mixer.LegacyIds[i], legacyId, StringComparison.OrdinalIgnoreCase))
				{
					return;
				}
			}
			mixer.LegacyIds.Add(legacyId);
		}

		private static float GetLegacyObjectChannelVolume(string channelId, AudioObjectMixer objectMixer, float fallback)
		{
			if (objectMixer != null && objectMixer.LegacyIds.Count != 0)
			{
				Plugin instance = Plugin.Instance;
				if (((instance != null) ? ((BaseUnityPlugin)instance).Config : null) != null)
				{
					ConfigEntry<float> val = default(ConfigEntry<float>);
					for (int i = 0; i < objectMixer.LegacyIds.Count; i++)
					{
						string value = channelId + "|object:" + objectMixer.LegacyIds[i];
						string text = "Prefab_" + Hash(value).ToString("X8");
						if (((BaseUnityPlugin)Plugin.Instance).Config.TryGetEntry<float>("Audio Prefabs", text, ref val))
						{
							return Mathf.Clamp(val.Value, 0f, 2f);
						}
					}
					return fallback;
				}
			}
			return fallback;
		}

		private static float GetLegacyObjectVolume(KnownAudioOwner owner, params string[] additionalNames)
		{
			Plugin instance = Plugin.Instance;
			if (((instance != null) ? ((BaseUnityPlugin)instance).Config : null) == null)
			{
				return 1f;
			}
			List<string> list = new List<string>();
			if (owner != null)
			{
				list.Add(owner.PrefabName);
				list.Add(owner.ObjectName);
			}
			if (additionalNames != null)
			{
				for (int i = 0; i < additionalNames.Length; i++)
				{
					if (!string.IsNullOrWhiteSpace(additionalNames[i]))
					{
						list.Add(additionalNames[i]);
					}
				}
			}
			float result = 1f;
			bool flag = false;
			HashSet<string> hashSet = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
			ConfigEntry<float> val = default(ConfigEntry<float>);
			for (int j = 0; j < list.Count; j++)
			{
				string text = list[j];
				if (string.IsNullOrWhiteSpace(text))
				{
					continue;
				}
				string value = "object:" + CleanName(text).ToLowerInvariant();
				string text2 = "Object_" + Hash(value).ToString("X8");
				if (hashSet.Add(text2) && ((BaseUnityPlugin)Plugin.Instance).Config.TryGetEntry<float>("Audio Object Masters", text2, ref val))
				{
					float num = Mathf.Clamp(val.Value, 0f, 2f);
					if (!flag)
					{
						result = num;
						flag = true;
					}
					if (!Mathf.Approximately(num, 1f))
					{
						return num;
					}
				}
			}
			if (!flag)
			{
				return 1f;
			}
			return result;
		}

		internal static float GetOneShotMultiplier(AudioSource source, AudioClip clip)
		{
			if ((Object)(object)source == (Object)null || (Object)(object)clip == (Object)null || (Object)(object)((Component)source).GetComponentInParent<ZSFX>() != (Object)null || IsGeneratedUi(((Component)source).transform))
			{
				return 1f;
			}
			try
			{
				int instanceID = ((Object)clip).GetInstanceID();
				AudioPrefabChannel value;
				bool flag = ChannelsByClipId.TryGetValue(instanceID, out value);
				Character val = FindCharacterOwner(((Component)source).transform);
				ItemDrop componentInParent = ((Component)source).GetComponentInParent<ItemDrop>();
				bool flag2 = flag && !string.IsNullOrWhiteSpace(value.IndexedAudioFilePath);
				if (flag2 && !IsIndexedChannelCompatible(value, val, componentInParent))
				{
					flag = false;
					flag2 = false;
				}
				string text = (string.IsNullOrWhiteSpace(((Object)clip).name) ? "Unnamed audio clip" : ((Object)clip).name.Trim());
				bool isModded = !KnownEmitterClipIds.Contains(instanceID);
				AudioModIdentity modIdentity = GetModIdentity(((Component)source).transform);
				bool changed = false;
				if (!flag2)
				{
					if (modIdentity != null)
					{
						string modAudioObjectName = GetModAudioObjectName(((Component)source).transform, modIdentity);
						value = GetOrCreateModAudioChannel(clip, source, modIdentity, modAudioObjectName, out changed);
						flag = value != null;
					}
					else if (!flag)
					{
						AudioPrefabChannel channel = null;
						if (!_hasScopedCategory && TryGetIndexedChannel(text, out channel) && IsIndexedChannelCompatible(channel, val, componentInParent))
						{
							value = channel;
							flag = true;
							flag2 = true;
						}
						else
						{
							AudioCategory category = (_hasScopedCategory ? _scopedCategory : ClassifyClip(text));
							GameObject objectSource = GetObjectSource(source, val, componentInParent);
							value = GetOrCreateChannel("clip:" + text.ToLowerInvariant(), text, GetLiveObjectName(objectSource), category, isModded, out changed, objectSource, modIdentity);
						}
					}
				}
				if (value == null)
				{
					return 1f;
				}
				if (!flag2 && modIdentity == null)
				{
					changed |= SetChannelModIdentity(value, null, isModded);
				}
				if ((Object)(object)value.PreviewClip == (Object)null || value.PreviewClipIsOwned)
				{
					changed |= CapturePreview(value, source, clip);
				}
				if (!flag && !flag2 && modIdentity == null)
				{
					ChannelsByClipId[instanceID] = value;
				}
				if (changed)
				{
					AudioEmitterRegistry.ChannelsChanged?.Invoke();
				}
				CapturePreview(value, source, clip);
				if (OneShotContextCheckedIds.Add(instanceID))
				{
					AudioCategory audioCategory = ClassifyName(((Object)((Component)source).gameObject).name);
					if (audioCategory == AudioCategory.Unassigned && IsWorldAmbientEmitter(((Component)source).transform))
					{
						audioCategory = AudioCategory.Ambient;
					}
					if (audioCategory == AudioCategory.Unassigned && value.Category == AudioCategory.Ambient)
					{
						audioCategory = AudioCategory.Ambient;
					}
					if (audioCategory == AudioCategory.Unassigned && value.Category == AudioCategory.Interface)
					{
						audioCategory = AudioCategory.Interface;
					}
					AudioCategory audioCategory2 = ResolveOwnerOrAssetCategory(GetKnownMixerOwnerCategory(value, ClassifyOwner(val)), ClassifyItem(componentInParent), audioCategory);
					if (audioCategory2 != AudioCategory.Unassigned && value.Category != audioCategory2)
					{
						value.Category = audioCategory2;
						_orphanObjectGroupsDirty = true;
						AudioEmitterRegistry.ChannelsChanged?.Invoke();
					}
				}
				AudioEmitterState value2;
				bool flag3 = DirectSourceStates.TryGetValue(source, out value2);
				AudioCategory category2 = (_hasScopedCategory ? _scopedCategory : value.Category);
				float num = (_hasScopedCategory ? GetCategoryVolume(category2) : (flag3 ? 1f : GetCategoryVolume(category2)));
				float num2 = (flag3 ? 1f : GetObjectVolume(value.ObjectMixer));
				return Mathf.Clamp(value.Volume.Value, 0f, 2f) * num2 * num;
			}
			catch
			{
				return 1f;
			}
		}

		internal static void RegisterModAudioFromInstance(object instance, Type ownerType)
		{
			if (!Plugin.AudioMixerActive)
			{
				return;
			}
			ownerType = ownerType ?? instance?.GetType();
			AudioModIdentity modIdentityFromType = GetModIdentityFromType(ownerType);
			if (modIdentityFromType == null)
			{
				return;
			}
			string modAudioObjectName = GetModAudioObjectName(ownerType, modIdentityFromType);
			bool flag = false;
			try
			{
				Component val = (Component)((instance is Component) ? instance : null);
				if ((Object)(object)val != (Object)null)
				{
					AudioSource[] componentsInChildren = val.GetComponentsInChildren<AudioSource>(true);
					for (int i = 0; i < componentsInChildren.Length; i++)
					{
						if ((Object)(object)componentsInChildren[i] != (Object)null && (Object)(object)componentsInChildren[i].clip != (Object)null)
						{
							flag |= RegisterModAudioClip(componentsInChildren[i].clip, componentsInChildren[i], modIdentityFromType, modAudioObjectName);
						}
					}
				}
				HashSet<object> inspectedValues = new HashSet<object>(ReferenceEqualityComparer<object>.Instance);
				Type type = ownerType;
				while (type != null && type != typeof(object) && type != typeof(MonoBehaviour) && type != typeof(Behaviour) && type != typeof(Component) && type != typeof(Object))
				{
					FieldInfo[] fields = type.GetFields(BindingFlags.DeclaredOnly | BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic);
					foreach (FieldInfo fieldInfo in fields)
					{
						if (!IsModAudioDataType(fieldInfo.FieldType))
						{
							continue;
						}
						object value;
						try
						{
							if (!fieldInfo.IsStatic && instance == null)
							{
								continue;
							}
							value = fieldInfo.GetValue(fieldInfo.IsStatic ? null : instance);
							goto IL_00f4;
						}
						catch
						{
						}
						continue;
						IL_00f4:
						flag |= RegisterModAudioFieldValue(value, modIdentityFromType, modAudioObjectName, inspectedValues, 0);
					}
					type = type.BaseType;
				}
			}
			catch
			{
			}
			if (flag)
			{
				NotifyChannelsChanged();
			}
		}

		private static string GetModAudioObjectName(Type ownerType, AudioModIdentity identity)
		{
			if (ownerType == null)
			{
				if (identity == null)
				{
					return MakeFriendlyModName(string.Empty);
				}
				return identity.DisplayName;
			}
			try
			{
				foreach (PluginInfo value in Chainloader.PluginInfos.Values)
				{
					if (value != null && (Object)(object)value.Instance != (Object)null && ((object)value.Instance).GetType() == ownerType)
					{
						return identity.DisplayName;
					}
				}
			}
			catch
			{
			}
			return MakeFriendlyModName(ownerType.Name);
		}

		private static bool IsModAudioDataType(Type type)
		{
			return IsModAudioDataType(type, new HashSet<Type>(), 0);
		}

		private static bool IsModAudioDataType(Type type, HashSet<Type> visited, int depth)
		{
			if (type == null || depth > 4)
			{
				return false;
			}
			if (type == typeof(AudioClip) || typeof(AudioSource).IsAssignableFrom(type) || typeof(AssetBundle).IsAssignableFrom(type) || typeof(ZSFX).IsAssignableFrom(type))
			{
				return true;
			}
			if (type.IsArray)
			{
				return IsModAudioDataType(type.GetElementType(), visited, depth + 1);
			}
			if (type.IsGenericType)
			{
				Type[] genericArguments = type.GetGenericArguments();
				for (int i = 0; i < genericArguments.Length; i++)
				{
					if (IsModAudioDataType(genericArguments[i], visited, depth + 1))
					{
						return true;
					}
				}
			}
			if (type == typeof(string) || type.IsPrimitive || type.IsEnum || typeof(Object).IsAssignableFrom(type) || !visited.Add(type))
			{
				return false;
			}
			try
			{
				FieldInfo[] fields = type.GetFields(BindingFlags.DeclaredOnly | BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic);
				for (int j = 0; j < fields.Length; j++)
				{
					if (IsModAudioDataType(fields[j].FieldType, visited, depth + 1))
					{
						return true;
					}
				}
			}
			catch
			{
			}
			return false;
		}

		private static bool RegisterModAudioFieldValue(object value, AudioModIdentity identity, string objectName, HashSet<object> inspectedValues, int depth)
		{
			if (value == null || identity == null || depth > 5)
			{
				return false;
			}
			AudioClip val = (AudioClip)((value is AudioClip) ? value : null);
			if (val != null)
			{
				return RegisterModAudioClip(val, null, identity, objectName);
			}
			AudioSource val2 = (AudioSource)((value is AudioSource) ? value : null);
			if (val2 != null)
			{
				if ((Object)(object)val2 == (Object)null || (Object)(object)val2.clip == (Object)null)
				{
					return false;
				}
				return RegisterModAudioClip(val2.clip, val2, identity, objectName);
			}
			AssetBundle val3 = (AssetBundle)((value is AssetBundle) ? value : null);
			if (val3 != null)
			{
				QueueModAudioBundle(val3, identity, objectName);
				return false;
			}
			ZSFX val4 = (ZSFX)((value is ZSFX) ? value : null);
			if (val4 != null)
			{
				if ((Object)(object)val4 == (Object)null)
				{
					return false;
				}
				AudioSource source = FindZsfxAudioSource(val4);
				AudioClip val5 = FindZsfxAudioClip(val4, source);
				if ((Object)(object)val5 != (Object)null)
				{
					return RegisterModAudioClip(val5, source, identity, CleanName(((Object)((Component)val4).gameObject).name));
				}
				return false;
			}
			if (!(value is ValueType) && inspectedValues != null && !inspectedValues.Add(value))
			{
				return false;
			}
			bool flag = false;
			if (value is IEnumerable enumerable && !(value is string))
			{
				int i = 0;
				IEnumerator enumerator = null;
				try
				{
					enumerator = enumerable.GetEnumerator();
				}
				catch
				{
				}
				if (enumerator != null)
				{
					try
					{
						for (; i < 128; i++)
						{
							if (!enumerator.MoveNext())
							{
								break;
							}
							flag |= RegisterModAudioFieldValue(enumerator.Current, identity, objectName, inspectedValues, depth + 1);
						}
					}
					catch
					{
					}
					finally
					{
						(enumerator as IDisposable)?.Dispose();
					}
				}
				return flag;
			}
			Type type = value.GetType();
			if (!IsModAudioDataType(type))
			{
				return false;
			}
			try
			{
				Type type2 = type;
				while (type2 != null && type2 != typeof(object) && type2 != typeof(Object))
				{
					FieldInfo[] fields = type2.GetFields(BindingFlags.DeclaredOnly | BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
					foreach (FieldInfo fieldInfo in fields)
					{
						if (!fieldInfo.IsStatic && IsModAudioDataType(fieldInfo.FieldType))
						{
							object value2;
							try
							{
								value2 = fieldInfo.GetValue(value);
							}
							catch
							{
								continue;
							}
							flag |= RegisterModAudioFieldValue(value2, identity, objectName, inspectedValues, depth + 1);
						}
					}
					type2 = type2.BaseType;
				}
			}
			catch
			{
			}
			return flag;
		}

		private static bool RegisterModAudioClip(AudioClip clip, AudioSource source, AudioModIdentity identity, string objectName)
		{
			if ((Object)(object)clip == (Object)null || identity == null)
			{
				return false;
			}
			KnownEmitterClipIds.Add(((Object)clip).GetInstanceID());
			if (TryGetIndexedChannel(((Object)clip).name, out var channel))
			{
				bool result = CapturePreview(channel, source, clip);
				ChannelsByClipId[((Object)clip).GetInstanceID()] = channel;
				return result;
			}
			GetOrCreateModAudioChannel(clip, source, identity, objectName, out var changed);
			return changed;
		}

		private static AudioPrefabChannel GetOrCreateModAudioChannel(AudioClip clip, AudioSource source, AudioModIdentity identity, string objectName, out bool changed)
		{
			changed = false;
			if ((Object)(object)clip == (Object)null || identity == null)
			{
				return null;
			}
			AudioObjectMixer orCreateModAudioMixer = GetOrCreateModAudioMixer(identity, string.IsNullOrWhiteSpace(objectName) ? identity.DisplayName : objectName, out changed);
			if (orCreateModAudioMixer == null)
			{
				return null;
			}
			string text = Regex.Replace(identity.Id ?? string.Empty, "[^a-zA-Z0-9]+", "_").Trim(new char[1] { '_' }).ToLowerInvariant();
			string text2 = (string.IsNullOrWhiteSpace(((Object)clip).name) ? "Unnamed audio clip" : ((Object)clip).name.Trim());
			string id = "modclip:" + text + ":" + text2.ToLowerInvariant();
			AudioCategory audioCategory = ClassifyClip(text2);
			if (audioCategory == AudioCategory.Unassigned || audioCategory == AudioCategory.Others)
			{
				Character owner = (((Object)(object)source != (Object)null) ? FindCharacterOwner(((Component)source).transform) : null);
				ItemDrop item = (((Object)(object)source != (Object)null) ? ((Component)source).GetComponentInParent<ItemDrop>() : null);
				string text3 = (((Object)(object)source != (Object)null) ? GetLiveObjectName(GetObjectSource(source, owner, item)) : string.Empty);
				string text4 = (((Object)(object)source != (Object)null) ? GetAudioSourceDisplayName(source, owner, item) : string.Empty);
				AudioCategory audioCategory2 = ClassifySpecificName(objectName, text3, text4, ((Object)(object)source != (Object)null && (Object)(object)((Component)source).transform.root != (Object)null) ? CleanName(((Object)((Component)((Component)source).transform.root).gameObject).name) : string.Empty);
				if (audioCategory2 != AudioCategory.Unassigned)
				{
					audioCategory = audioCategory2;
				}
			}
			if (audioCategory == AudioCategory.Unassigned)
			{
				audioCategory = AudioCategory.Others;
			}
			bool changed2;
			AudioPrefabChannel orCreateChannel = GetOrCreateChannel(id, text2, orCreateModAudioMixer.DisplayName, audioCategory, isModded: true, out changed2, null, identity, orCreateModAudioMixer);
			changed |= changed2;
			if (orCreateChannel != null && ((Object)(object)orCreateChannel.PreviewClip == (Object)null || orCreateChannel.PreviewClipIsOwned))
			{
				changed |= CapturePreview(orCreateChannel, source, clip);
			}
			if (orCreateChannel != null)
			{
				ChannelsByClipId[((Object)clip).GetInstanceID()] = orCreateChannel;
			}
			return orCreateChannel;
		}

		private static string GetModAudioObjectName(Transform source, AudioModIdentity identity)
		{
			Transform val = source;
			while ((Object)(object)val != (Object)null)
			{
				MonoBehaviour[] components = ((Component)val).GetComponents<MonoBehaviour>();
				foreach (MonoBehaviour val2 in components)
				{
					if (!((Object)(object)val2 == (Object)null))
					{
						AudioModIdentity modIdentityFromType = GetModIdentityFromType(((object)val2).GetType());
						if (modIdentityFromType != null && string.Equals(modIdentityFromType.Id, identity.Id, StringComparison.OrdinalIgnoreCase))
						{
							return MakeFriendlyModName(((object)val2).GetType().Name);
						}
					}
				}
				val = val.parent;
			}
			return MakeFriendlyModName(identity.DisplayName);
		}

		private static bool RegisterLooseClip(AudioClip clip)
		{
			if ((Object)(object)clip == (Object)null)
			{
				return false;
			}
			try
			{
				int instanceID = ((Object)clip).GetInstanceID();
				if (KnownEmitterClipIds.Contains(instanceID))
				{
					return false;
				}
				string text = (string.IsNullOrWhiteSpace(((Object)clip).name) ? "Unnamed audio clip" : ((Object)clip).name.Trim());
				bool changed;
				if (!TryGetIndexedChannel(text, out var channel))
				{
					AudioCategory category = ClassifyClip(text);
					channel = GetOrCreateChannel("clip:" + text.ToLowerInvariant(), text, string.Empty, category, isModded: true, out changed, null, GetModIdentityFromName(text));
				}
				else
				{
					changed = false;
				}
				if (channel == null)
				{
					return false;
				}
				changed |= CapturePreview(channel, null, clip);
				ChannelsByClipId[instanceID] = channel;
				return changed;
			}
			catch
			{
				return false;
			}
		}

		private static void MarkZsfxClipReferences(ZSFX sfx)
		{
			if ((Object)(object)sfx == (Object)null || !InspectedZsfxIds.Add(((Object)sfx).GetInstanceID()))
			{
				return;
			}
			for (int i = 0; i < ZsfxAudioClipFields.Length; i++)
			{
				try
				{
					object value = ZsfxAudioClipFields[i].GetValue(sfx);
					AudioClip val = (AudioClip)((value is AudioClip) ? value : null);
					if (val != null)
					{
						MarkKnownClip(val);
					}
					else
					{
						if (!(value is IEnumerable enumerable))
						{
							continue;
						}
						foreach (object item in enumerable)
						{
							AudioClip val2 = (AudioClip)((item is AudioClip) ? item : null);
							if (val2 != null)
							{
								MarkKnownClip(val2);
							}
						}
						continue;
					}
				}
				catch
				{
				}
			}
		}

		private static void MarkKnownClip(AudioClip clip)
		{
			if ((Object)(object)clip != (Object)null)
			{
				KnownEmitterClipIds.Add(((Object)clip).GetInstanceID());
			}
		}

		private static bool IsGeneratedUi(Transform target)
		{
			Transform val = target;
			while ((Object)(object)val != (Object)null)
			{
				if (((Object)val).name.StartsWith("AudioLODControl_", StringComparison.Ordinal))
				{
					return true;
				}
				val = val.parent;
			}
			return false;
		}

		private static float GetLegacyPrefabVolume(string displayName, float fallback)
		{
			if ((Object)(object)Plugin.Instance == (Object)null || ((BaseUnityPlugin)Plugin.Instance).Config == null)
			{
				return fallback;
			}
			string text = displayName.ToLowerInvariant();
			ConfigEntry<float> val = default(ConfigEntry<float>);
			for (int i = 0; i <= 8; i++)
			{
				string value = i + ":" + text;
				string text2 = "Prefab_" + Hash(value).ToString("X8");
				if (((BaseUnityPlugin)Plugin.Instance).Config.TryGetEntry<float>("Aud