Decompiled source of SZtyrmod v1.0.36

BepInEx\plugins\SZtyrmod\SZtyrmod.dll

Decompiled a week ago
using System;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.Versioning;
using BepInEx;
using BepInEx.Bootstrap;
using BepInEx.Configuration;
using BepInEx.Logging;
using HarmonyLib;
using Microsoft.CodeAnalysis;
using Newtonsoft.Json;
using UnityEngine;
using UnityEngine.Networking;
using UnityEngine.SceneManagement;

[assembly: CompilationRelaxations(8)]
[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
[assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)]
[assembly: TargetFramework(".NETStandard,Version=v2.1", FrameworkDisplayName = ".NET Standard 2.1")]
[assembly: AssemblyCompany("SZtyrmod")]
[assembly: AssemblyConfiguration("Release")]
[assembly: AssemblyFileVersion("1.0.0.0")]
[assembly: AssemblyInformationalVersion("1.0.0+6968621772e9047c42c05f3702f721ce77bfb213")]
[assembly: AssemblyProduct("SZtyrmod")]
[assembly: AssemblyTitle("SZtyrmod")]
[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.Class | AttributeTargets.Property | AttributeTargets.Field | AttributeTargets.Event | AttributeTargets.Parameter | AttributeTargets.ReturnValue | AttributeTargets.GenericParameter, AllowMultiple = false, Inherited = false)]
	internal sealed class NullableAttribute : Attribute
	{
		public readonly byte[] NullableFlags;

		public NullableAttribute(byte P_0)
		{
			NullableFlags = new byte[1] { P_0 };
		}

		public NullableAttribute(byte[] P_0)
		{
			NullableFlags = P_0;
		}
	}
	[CompilerGenerated]
	[Embedded]
	[AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct | AttributeTargets.Method | AttributeTargets.Interface | AttributeTargets.Delegate, AllowMultiple = false, Inherited = false)]
	internal sealed class NullableContextAttribute : Attribute
	{
		public readonly byte Flag;

		public NullableContextAttribute(byte P_0)
		{
			Flag = P_0;
		}
	}
	[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 RepoMonsterAudio
{
	[BepInPlugin("pl.local.sztyrmod", "SZtyrmod", "1.0.36")]
	public sealed class Plugin : BaseUnityPlugin
	{
		public const string PluginGuid = "pl.local.sztyrmod";

		public const string PluginName = "SZtyrmod";

		public const string PluginVersion = "1.0.36";

		private static ConfigEntry<bool>? EnabledConfig;

		private static ConfigEntry<bool>? DebugConfig;

		private static ConfigEntry<string>? RulesFileConfig;

		private static ConfigEntry<bool>? FixSoundApiConfig;

		private Harmony? _harmony;

		private static int _scanCount;

		private static int _lastEnemyCount = -1;

		internal static ManualLogSource Log { get; private set; } = null;

		internal static bool DebugLogging => DebugConfig?.Value ?? false;

		internal static bool Enabled => EnabledConfig?.Value ?? false;

		internal static string PluginDirectory { get; private set; } = string.Empty;

		private void Awake()
		{
			//IL_00b3: Unknown result type (might be due to invalid IL or missing references)
			//IL_00bd: Expected O, but got Unknown
			Log = ((BaseUnityPlugin)this).Logger;
			PluginDirectory = Path.GetDirectoryName(((BaseUnityPlugin)this).Info.Location) ?? Paths.PluginPath;
			EnabledConfig = ((BaseUnityPlugin)this).Config.Bind<bool>("General", "Enabled", true, "Enable configured monster-sound replacements.");
			RulesFileConfig = ((BaseUnityPlugin)this).Config.Bind<string>("General", "RulesFile", "sounds/replacements.json", "Path to the replacement rules file, relative to RepoMonsterAudio.dll.");
			DebugConfig = ((BaseUnityPlugin)this).Config.Bind<bool>("Diagnostics", "DebugLogging", false, "Write a rate-limited report of discovered monster clips and replacements.");
			FixSoundApiConfig = ((BaseUnityPlugin)this).Config.Bind<bool>("Compatibility", "FixSoundApiRepoNullSource", true, "Guard the known null-source prefix in loaforcsSoundAPI_REPO 1.0.1 when it is installed.");
			_harmony = new Harmony("pl.local.sztyrmod");
			try
			{
				_harmony.PatchAll();
				Log.LogInfo((object)"Harmony patches applied.");
				LogPatchStatus();
			}
			catch (Exception arg)
			{
				Log.LogError((object)$"Harmony patch setup failed: {arg}");
			}
			Log.LogInfo((object)"BepInEx runtime initialized; starting enemy scan and rule loading.");
			SceneManager.sceneLoaded += OnSceneLoaded;
			TryInstallSoundApiCompatibility(_harmony);
			AudioCatalogLoader.Begin();
			ScanEnemiesOnce();
		}

		private static void LogPatchStatus()
		{
			MethodInfo method = AccessTools.Method(typeof(EnemyParent), "Setup", (Type[])null, (Type[])null);
			MethodInfo method2 = AccessTools.Method(typeof(Sound), "Play", new Type[5]
			{
				typeof(Vector3),
				typeof(float),
				typeof(float),
				typeof(float),
				typeof(float)
			}, (Type[])null);
			MethodInfo method3 = AccessTools.Method(typeof(Sound), "PlayLoop", new Type[5]
			{
				typeof(bool),
				typeof(float),
				typeof(float),
				typeof(float),
				typeof(float)
			}, (Type[])null);
			Log.LogInfo((object)("Patch verification: " + $"EnemyParent.Setup={IsPatchedByUs(method)}, " + $"Sound.Play={IsPatchedByUs(method2)}, " + $"Sound.PlayLoop={IsPatchedByUs(method3)}; " + $"debug={DebugLogging}."));
		}

		private static bool IsPatchedByUs(MethodBase? method)
		{
			Patches val = ((method == null) ? null : Harmony.GetPatchInfo(method));
			if (val != null)
			{
				return val.Owners.Contains("pl.local.sztyrmod");
			}
			return false;
		}

		internal static void ScanEnemiesOnce()
		{
			//IL_007c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0081: Unknown result type (might be due to invalid IL or missing references)
			try
			{
				HashSet<EnemyParent> hashSet = new HashSet<EnemyParent>();
				EnemyDirector instance = EnemyDirector.instance;
				if ((Object)(object)instance != (Object)null)
				{
					foreach (EnemyParent item in instance.enemiesSpawned)
					{
						if ((Object)(object)item != (Object)null)
						{
							hashSet.Add(item);
						}
					}
				}
				EnemyParent[] array = Resources.FindObjectsOfTypeAll<EnemyParent>();
				foreach (EnemyParent val in array)
				{
					if ((Object)(object)val != (Object)null)
					{
						Scene scene = ((Component)val).gameObject.scene;
						if (((Scene)(ref scene)).IsValid())
						{
							hashSet.Add(val);
						}
					}
				}
				int num = 0;
				foreach (EnemyParent item2 in hashSet)
				{
					num += EnemySoundRegistry.Register(item2);
				}
				EnemySoundRegistry.RemoveDestroyed();
				_scanCount++;
				if (DebugLogging && (num > 0 || hashSet.Count != _lastEnemyCount || _scanCount % 5 == 0))
				{
					Log.LogInfo((object)($"Enemy scan: roots={hashSet.Count}, director={(Object)(object)instance != (Object)null}, " + $"registered={num}, tracked sounds={EnemySoundRegistry.SoundCount}, " + $"sources={EnemySoundRegistry.SourceCount}."));
				}
				_lastEnemyCount = hashSet.Count;
			}
			catch (Exception arg)
			{
				Log.LogError((object)$"Enemy scanner failed: {arg}");
			}
		}

		internal static IEnumerator LoadRules()
		{
			return AudioCatalog.Reload(RulesFileConfig?.Value ?? "sounds/replacements.json");
		}

		internal static void TryInstallSoundApiCompatibility(Harmony harmony)
		{
			//IL_00fd: Unknown result type (might be due to invalid IL or missing references)
			//IL_0102: Unknown result type (might be due to invalid IL or missing references)
			//IL_0116: Expected O, but got Unknown
			ConfigEntry<bool>? fixSoundApiConfig = FixSoundApiConfig;
			if (fixSoundApiConfig == null || !fixSoundApiConfig.Value || !Chainloader.PluginInfos.TryGetValue("me.loaforc.soundapi.repo", out var value) || value.Metadata.Version.ToString() != "1.0.1")
			{
				return;
			}
			try
			{
				MethodInfo methodInfo = AccessTools.Method(AccessTools.TypeByName("loaforcsSoundAPI.REPO.Patches.SoundPatch"), "ResetAudioInfoFetched", (Type[])null, (Type[])null);
				MethodInfo methodInfo2 = AccessTools.Method(typeof(Sound), "PlayLoop", new Type[5]
				{
					typeof(bool),
					typeof(float),
					typeof(float),
					typeof(float),
					typeof(float)
				}, (Type[])null);
				if (methodInfo == null || methodInfo2 == null)
				{
					Log.LogWarning((object)"SoundAPI REPO is present, but its loop-reset patch was not found.");
					return;
				}
				harmony.Unpatch((MethodBase)methodInfo2, (HarmonyPatchType)1, "me.loaforc.soundapi.repo");
				harmony.Patch((MethodBase)methodInfo2, new HarmonyMethod(typeof(SoundApiCompatibility), "SafeResetAudioInfoFetched", (Type[])null)
				{
					priority = 800
				}, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null);
				Log.LogInfo((object)"Installed null-safe compatibility guard for SoundAPI REPO loop handling.");
			}
			catch (Exception ex)
			{
				Log.LogWarning((object)("Could not install SoundAPI REPO compatibility guard: " + ex.Message));
			}
		}

		private void OnDestroy()
		{
			SceneManager.sceneLoaded -= OnSceneLoaded;
			Log.LogInfo((object)"BepInEx plugin component was destroyed; static Harmony patches remain active.");
		}

		private static void OnSceneLoaded(Scene scene, LoadSceneMode mode)
		{
			//IL_0011: Unknown result type (might be due to invalid IL or missing references)
			Log.LogInfo((object)$"Scene loaded: '{((Scene)(ref scene)).name}' ({mode}).");
		}
	}
	internal sealed class AudioCatalogLoader : MonoBehaviour
	{
		private static AudioCatalogLoader? _instance;

		private bool _started;

		internal static void Begin()
		{
			//IL_0013: Unknown result type (might be due to invalid IL or missing references)
			//IL_0018: Unknown result type (might be due to invalid IL or missing references)
			//IL_001e: Expected O, but got Unknown
			if (!((Object)(object)_instance != (Object)null))
			{
				GameObject val = new GameObject("SZtyrmod Audio Loader");
				Object.DontDestroyOnLoad((Object)val);
				_instance = val.AddComponent<AudioCatalogLoader>();
				_instance.StartLoading();
			}
		}

		private void Start()
		{
			StartLoading();
		}

		private void StartLoading()
		{
			if (!_started)
			{
				_started = true;
				Plugin.Log.LogInfo((object)"Persistent audio loader started.");
				((MonoBehaviour)this).StartCoroutine(Plugin.LoadRules());
			}
		}

		private void OnDestroy()
		{
			if ((Object)(object)_instance == (Object)(object)this)
			{
				_instance = null;
			}
		}
	}
	[Serializable]
	public sealed class ReplacementFile
	{
		public string path = string.Empty;

		public int weight = 1;
	}
	[Serializable]
	public sealed class ReplacementRuleDefinition
	{
		public string enemy = "*";

		public string[] clips = Array.Empty<string>();

		public ReplacementFile[] sounds = Array.Empty<ReplacementFile>();

		public float volumeMultiplier = 1f;

		public float pitchMultiplier = 1f;
	}
	[Serializable]
	public sealed class ReplacementRuleFile
	{
		public ReplacementRuleDefinition[] rules = Array.Empty<ReplacementRuleDefinition>();
	}
	internal sealed class LoadedReplacementRule
	{
		internal string EnemyPattern = "*";

		internal string[] ClipPatterns = Array.Empty<string>();

		internal List<LoadedReplacementFile> Sounds = new List<LoadedReplacementFile>();

		internal float VolumeMultiplier = 1f;

		internal float PitchMultiplier = 1f;

		internal bool Matches(string enemyName, string clipName)
		{
			if (Wildcard.Match(enemyName, EnemyPattern))
			{
				return ClipPatterns.Any((string pattern) => Wildcard.Match(clipName, pattern));
			}
			return false;
		}
	}
	internal sealed class LoadedReplacementFile
	{
		internal AudioClip Clip;

		internal int Weight;
	}
	internal static class AudioCatalog
	{
		private static readonly List<LoadedReplacementRule> Rules = new List<LoadedReplacementRule>();

		private static readonly List<AudioClip> OwnedClips = new List<AudioClip>();

		private static readonly List<AudioClip> RandomMonsterClips = new List<AudioClip>();

		private static readonly Dictionary<EnemyParent, AudioClip> AssignedMonsterClips = new Dictionary<EnemyParent, AudioClip>();

		private static readonly Random Random = new Random();

		private static bool _ready;

		internal static bool TryPick(string enemyName, AudioClip original, out AudioClip replacement, out float volumeMultiplier, out float pitchMultiplier)
		{
			replacement = null;
			volumeMultiplier = 1f;
			pitchMultiplier = 1f;
			if (!Plugin.Enabled || !_ready || (Object)(object)original == (Object)null)
			{
				return false;
			}
			foreach (LoadedReplacementRule rule in Rules)
			{
				if (!rule.Matches(enemyName, ((Object)original).name) || rule.Sounds.Count == 0)
				{
					continue;
				}
				int maxValue = rule.Sounds.Sum((LoadedReplacementFile sound) => Math.Max(1, sound.Weight));
				int num = Random.Next(maxValue);
				foreach (LoadedReplacementFile sound in rule.Sounds)
				{
					num -= Math.Max(1, sound.Weight);
					if (num < 0)
					{
						replacement = sound.Clip;
						volumeMultiplier = rule.VolumeMultiplier;
						pitchMultiplier = rule.PitchMultiplier;
						EnemyAudioDiagnostics.Report(enemyName, original, replacement);
						return true;
					}
				}
			}
			return false;
		}

		internal static bool TryPickForMonster(EnemyParent? owner, string enemyName, AudioClip original, out AudioClip replacement, out float volumeMultiplier, out float pitchMultiplier)
		{
			replacement = null;
			volumeMultiplier = 1f;
			pitchMultiplier = 1f;
			if (!Plugin.Enabled || !_ready || (Object)(object)original == (Object)null)
			{
				return false;
			}
			if (TryPick(enemyName, original, out replacement, out volumeMultiplier, out pitchMultiplier))
			{
				return true;
			}
			if ((Object)(object)owner != (Object)null && RandomMonsterClips.Count > 0)
			{
				if (!AssignedMonsterClips.TryGetValue(owner, out replacement) || (Object)(object)replacement == (Object)null)
				{
					replacement = RandomMonsterClips[Random.Next(RandomMonsterClips.Count)];
					AssignedMonsterClips[owner] = replacement;
					if (Plugin.DebugLogging)
					{
						Plugin.Log.LogInfo((object)("Assigned '" + ((Object)replacement).name + "' to monster '" + owner.enemyName + "' for this round."));
					}
				}
				return true;
			}
			return false;
		}

		internal static IEnumerator Reload(string configuredPath)
		{
			Dispose();
			string rulesPath = TryGetPathInsidePlugin(configuredPath);
			if (rulesPath == null)
			{
				Plugin.Log.LogError((object)("RulesFile must stay inside the mod folder: " + configuredPath));
				yield break;
			}
			IEnumerator randomLoader = LoadRandomMonsterFolder();
			while (randomLoader.MoveNext())
			{
				yield return randomLoader.Current;
			}
			Plugin.Log.LogInfo((object)("Loading replacement rules from: " + rulesPath));
			if (!File.Exists(rulesPath))
			{
				Plugin.Log.LogWarning((object)("Replacement rules were not found: " + rulesPath + ". Random-folder mode is still available."));
				_ready = true;
				yield break;
			}
			ReplacementRuleFile replacementRuleFile;
			try
			{
				replacementRuleFile = JsonConvert.DeserializeObject<ReplacementRuleFile>(File.ReadAllText(rulesPath));
			}
			catch (Exception ex)
			{
				Plugin.Log.LogError((object)("Could not parse replacement rules: " + ex.Message));
				yield break;
			}
			if (replacementRuleFile?.rules == null || replacementRuleFile.rules.Length == 0)
			{
				Plugin.Log.LogWarning((object)"Replacement rules file contains no rules.");
				_ready = true;
				yield break;
			}
			Dictionary<string, AudioClip> clipCache = new Dictionary<string, AudioClip>(StringComparer.OrdinalIgnoreCase);
			int skippedRules = 0;
			ReplacementRuleDefinition[] rules = replacementRuleFile.rules;
			foreach (ReplacementRuleDefinition replacementRuleDefinition in rules)
			{
				if (replacementRuleDefinition == null || replacementRuleDefinition.clips == null || replacementRuleDefinition.clips.Length == 0 || replacementRuleDefinition.sounds == null || replacementRuleDefinition.sounds.Length == 0)
				{
					skippedRules++;
					continue;
				}
				LoadedReplacementRule rule = new LoadedReplacementRule
				{
					EnemyPattern = (string.IsNullOrWhiteSpace(replacementRuleDefinition.enemy) ? "*" : replacementRuleDefinition.enemy),
					ClipPatterns = replacementRuleDefinition.clips.Where((string pattern) => !string.IsNullOrWhiteSpace(pattern)).ToArray(),
					VolumeMultiplier = Mathf.Max(0f, replacementRuleDefinition.volumeMultiplier),
					PitchMultiplier = Mathf.Max(0.01f, replacementRuleDefinition.pitchMultiplier)
				};
				ReplacementFile[] sounds = replacementRuleDefinition.sounds;
				foreach (ReplacementFile definitionFile in sounds)
				{
					if (definitionFile == null || string.IsNullOrWhiteSpace(definitionFile.path))
					{
						continue;
					}
					string audioPath = TryGetPathInsidePlugin(Path.Combine("sounds", definitionFile.path));
					if (audioPath == null || !File.Exists(audioPath))
					{
						audioPath = TryGetPathInsidePlugin(definitionFile.path);
					}
					if (audioPath == null || !File.Exists(audioPath))
					{
						Plugin.Log.LogWarning((object)("Replacement audio was not found: " + definitionFile.path));
						continue;
					}
					if (!clipCache.TryGetValue(audioPath, out AudioClip value))
					{
						AudioType? audioType = GetAudioType(audioPath);
						if (!audioType.HasValue)
						{
							Plugin.Log.LogWarning((object)("Unsupported replacement format: " + definitionFile.path));
							continue;
						}
						UnityWebRequest request = UnityWebRequestMultimedia.GetAudioClip(new Uri(audioPath).AbsoluteUri, audioType.Value);
						try
						{
							yield return request.SendWebRequest();
							if ((int)request.result != 1)
							{
								Plugin.Log.LogWarning((object)("Could not load replacement audio '" + definitionFile.path + "': " + request.error));
								continue;
							}
							value = DownloadHandlerAudioClip.GetContent(request);
							((Object)value).name = "RepoMonsterAudio_" + Path.GetFileNameWithoutExtension(audioPath);
							clipCache.Add(audioPath, value);
							OwnedClips.Add(value);
						}
						finally
						{
							((IDisposable)request)?.Dispose();
						}
					}
					rule.Sounds.Add(new LoadedReplacementFile
					{
						Clip = value,
						Weight = Math.Max(1, definitionFile.weight)
					});
				}
				if (rule.ClipPatterns.Length == 0 || rule.Sounds.Count == 0)
				{
					skippedRules++;
				}
				else
				{
					Rules.Add(rule);
				}
			}
			_ready = true;
			Plugin.Log.LogInfo((object)($"Loaded {RandomMonsterClips.Count} random monster clip(s), {Rules.Count} replacement rule(s), " + $"{OwnedClips.Count} total audio clip(s); skipped {skippedRules} invalid rule(s)."));
		}

		private static IEnumerator LoadRandomMonsterFolder()
		{
			string text = TryGetPathInsidePlugin(Path.Combine("sounds", "random"));
			if (text == null || !Directory.Exists(text))
			{
				Plugin.Log.LogInfo((object)"Random monster audio folder was not found; using configured rules only.");
				yield break;
			}
			string[] audioPaths = (from path in Directory.GetFiles(text, "*.*", SearchOption.AllDirectories)
				where GetAudioType(path).HasValue
				select path).OrderBy<string, string>((string path) => path, StringComparer.OrdinalIgnoreCase).ToArray();
			Plugin.Log.LogInfo((object)$"Found {audioPaths.Length} random monster audio file(s); loading them now.");
			string[] array = audioPaths;
			foreach (string audioPath in array)
			{
				AudioType? audioType = GetAudioType(audioPath);
				UnityWebRequest request = UnityWebRequestMultimedia.GetAudioClip(new Uri(audioPath).AbsoluteUri, audioType.Value);
				try
				{
					request.timeout = 10;
					yield return request.SendWebRequest();
					if ((int)request.result != 1)
					{
						Plugin.Log.LogWarning((object)("Could not load random monster audio '" + Path.GetFileName(audioPath) + "': " + request.error));
						continue;
					}
					AudioClip content = DownloadHandlerAudioClip.GetContent(request);
					((Object)content).name = "RepoMonsterAudio_Random_" + Path.GetFileNameWithoutExtension(audioPath);
					RandomMonsterClips.Add(content);
					OwnedClips.Add(content);
					_ready = true;
					if (Plugin.DebugLogging && RandomMonsterClips.Count % 25 == 0)
					{
						Plugin.Log.LogInfo((object)$"Loaded {RandomMonsterClips.Count}/{audioPaths.Length} random monster audio file(s).");
					}
				}
				finally
				{
					((IDisposable)request)?.Dispose();
				}
			}
			Plugin.Log.LogInfo((object)$"Random monster audio loading finished: {RandomMonsterClips.Count}/{audioPaths.Length} file(s) ready.");
		}

		internal static void Dispose()
		{
			_ready = false;
			Rules.Clear();
			RandomMonsterClips.Clear();
			AssignedMonsterClips.Clear();
			foreach (AudioClip ownedClip in OwnedClips)
			{
				if ((Object)(object)ownedClip != (Object)null)
				{
					Object.Destroy((Object)(object)ownedClip);
				}
			}
			OwnedClips.Clear();
		}

		private static string? TryGetPathInsidePlugin(string relativePath)
		{
			try
			{
				string fullPath = Path.GetFullPath(Plugin.PluginDirectory);
				string fullPath2 = Path.GetFullPath(Path.Combine(fullPath, relativePath));
				return fullPath2.StartsWith(fullPath + Path.DirectorySeparatorChar, StringComparison.OrdinalIgnoreCase) ? fullPath2 : null;
			}
			catch
			{
				return null;
			}
		}

		private static AudioType? GetAudioType(string path)
		{
			return Path.GetExtension(path).ToLowerInvariant() switch
			{
				".ogg" => (AudioType)14, 
				".wav" => (AudioType)20, 
				".mp3" => (AudioType)13, 
				_ => null, 
			};
		}
	}
	internal static class Wildcard
	{
		internal static bool Match(string value, string pattern)
		{
			value = value ?? string.Empty;
			pattern = (string.IsNullOrWhiteSpace(pattern) ? "*" : pattern);
			int num = 0;
			int i = 0;
			int num2 = -1;
			int num3 = 0;
			while (num < value.Length)
			{
				if (i < pattern.Length && (pattern[i] == '?' || char.ToUpperInvariant(pattern[i]) == char.ToUpperInvariant(value[num])))
				{
					num++;
					i++;
					continue;
				}
				if (i < pattern.Length && pattern[i] == '*')
				{
					num2 = i++;
					num3 = num;
					continue;
				}
				if (num2 >= 0)
				{
					i = num2 + 1;
					num = ++num3;
					continue;
				}
				return false;
			}
			for (; i < pattern.Length && pattern[i] == '*'; i++)
			{
			}
			return i == pattern.Length;
		}
	}
	internal static class EnemySoundRegistry
	{
		private static readonly Dictionary<Sound, EnemyParent> Sounds = new Dictionary<Sound, EnemyParent>();

		private static readonly Dictionary<AudioSource, EnemyParent> Sources = new Dictionary<AudioSource, EnemyParent>();

		internal static int SoundCount => Sounds.Count;

		internal static int SourceCount => Sources.Count;

		internal static int Register(EnemyParent enemyParent)
		{
			int num = Sounds.Count + Sources.Count;
			AudioSource[] componentsInChildren = ((Component)enemyParent).GetComponentsInChildren<AudioSource>(true);
			foreach (AudioSource key in componentsInChildren)
			{
				Sources[key] = enemyParent;
			}
			MonoBehaviour[] componentsInChildren2 = ((Component)enemyParent).GetComponentsInChildren<MonoBehaviour>(true);
			for (int i = 0; i < componentsInChildren2.Length; i++)
			{
				RegisterSoundFields(componentsInChildren2[i], enemyParent);
			}
			return Sounds.Count + Sources.Count - num;
		}

		internal static bool TryGetOwner(Sound sound, out EnemyParent owner)
		{
			if (Sounds.TryGetValue(sound, out EnemyParent value) && (Object)(object)value != (Object)null)
			{
				owner = value;
				return true;
			}
			owner = null;
			return false;
		}

		internal static bool TryGetOwner(AudioSource source, out EnemyParent owner)
		{
			if (Sources.TryGetValue(source, out EnemyParent value) && (Object)(object)value != (Object)null)
			{
				owner = value;
				return true;
			}
			owner = ((Component)source).GetComponentInParent<EnemyParent>(true);
			if ((Object)(object)owner != (Object)null)
			{
				Sources[source] = owner;
				return true;
			}
			return false;
		}

		internal static void RemoveDestroyed()
		{
			Sound[] array = Sounds.Keys.Where((Sound sound) => sound == null || !Object.op_Implicit((Object)(object)Sounds[sound])).ToArray();
			foreach (Sound key in array)
			{
				Sounds.Remove(key);
			}
			AudioSource[] array2 = Sources.Keys.Where((AudioSource source) => (Object)(object)source == (Object)null || !Object.op_Implicit((Object)(object)Sources[source])).ToArray();
			foreach (AudioSource key2 in array2)
			{
				Sources.Remove(key2);
			}
		}

		private static void RegisterSoundFields(MonoBehaviour component, EnemyParent owner)
		{
			Type type = ((object)component).GetType();
			while (type != null)
			{
				FieldInfo[] fields = type.GetFields(BindingFlags.DeclaredOnly | BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
				foreach (FieldInfo fieldInfo in fields)
				{
					try
					{
						object value = fieldInfo.GetValue(component);
						Sound val = (Sound)((value is Sound) ? value : null);
						if (val != null)
						{
							RegisterSound(val, owner);
						}
						else
						{
							if (!(value is IEnumerable<Sound> enumerable))
							{
								continue;
							}
							foreach (Sound item in enumerable)
							{
								if (item != null)
								{
									RegisterSound(item, owner);
								}
							}
							continue;
						}
					}
					catch (Exception ex)
					{
						if (Plugin.DebugLogging)
						{
							Plugin.Log.LogWarning((object)("Could not inspect " + ((object)component).GetType().Name + "." + fieldInfo.Name + ": " + ex.Message));
						}
					}
				}
				type = type.BaseType;
			}
		}

		private static void RegisterSound(Sound sound, EnemyParent owner)
		{
			Sounds[sound] = owner;
			if ((Object)(object)sound.Source != (Object)null)
			{
				Sources[sound.Source] = owner;
			}
		}
	}
	[HarmonyPatch(typeof(EnemyParent), "Awake")]
	internal static class EnemyRegistrationPatch
	{
		[HarmonyPostfix]
		private static void RegisterEnemySounds(EnemyParent __instance)
		{
			int num = EnemySoundRegistry.Register(__instance);
			if (Plugin.DebugLogging)
			{
				Plugin.Log.LogInfo((object)$"EnemyParent.Awake: '{__instance.enemyName}', registered={num}.");
			}
		}
	}
	[HarmonyPatch(typeof(EnemyParent), "Setup")]
	internal static class EnemySetupRegistrationPatch
	{
		[HarmonyPostfix]
		private static void RegisterEnemyAfterSetupStarted(EnemyParent __instance)
		{
			int num = EnemySoundRegistry.Register(__instance);
			if (Plugin.DebugLogging)
			{
				Plugin.Log.LogInfo((object)($"EnemyParent.Setup: '{__instance.enemyName}', registered={num}, " + $"tracked sounds={EnemySoundRegistry.SoundCount}, " + $"sources={EnemySoundRegistry.SourceCount}."));
			}
		}
	}
	[HarmonyPatch(typeof(Enemy), "Start")]
	internal static class EnemyInstanceRegistrationPatch
	{
		[HarmonyPostfix]
		private static void RegisterEnemyInstance(Enemy __instance)
		{
			EnemyParent val = __instance.EnemyParent ?? ((Component)__instance).GetComponentInParent<EnemyParent>(true);
			if ((Object)(object)val == (Object)null)
			{
				if (Plugin.DebugLogging)
				{
					Plugin.Log.LogWarning((object)("Enemy.Start: '" + ((Object)__instance).name + "' has no EnemyParent."));
				}
				return;
			}
			int num = EnemySoundRegistry.Register(val);
			if (Plugin.DebugLogging)
			{
				Plugin.Log.LogInfo((object)($"Enemy.Start: '{val.enemyName}', registered={num}, " + $"tracked sounds={EnemySoundRegistry.SoundCount}."));
			}
		}
	}
	internal struct SoundOverrideState
	{
		internal bool Applied;

		internal AudioClip[]? Sounds;

		internal float Volume;

		internal float Pitch;
	}
	internal static class EnemySoundCallContext
	{
		internal static string? FindEnemyCaller()
		{
			StackFrame[] array = new StackTrace(3, fNeedFileInfo: false).GetFrames() ?? Array.Empty<StackFrame>();
			for (int i = 0; i < array.Length; i++)
			{
				Type type = array[i].GetMethod()?.DeclaringType;
				if (type?.Assembly == typeof(Enemy).Assembly && type.Name.StartsWith("Enemy", StringComparison.Ordinal))
				{
					return type.Name;
				}
			}
			return null;
		}
	}
	internal static class EnemySoundIdentity
	{
		internal static bool TryGetName(Sound sound, out string enemyName)
		{
			if (EnemySoundRegistry.TryGetOwner(sound, out EnemyParent owner))
			{
				enemyName = owner.enemyName;
				return true;
			}
			string text = EnemySoundCallContext.FindEnemyCaller();
			if (text != null)
			{
				enemyName = text;
				return true;
			}
			AudioClip[] array = sound.Sounds ?? Array.Empty<AudioClip>();
			for (int i = 0; i < array.Length; i++)
			{
				if (TryGetName(array[i], out enemyName))
				{
					return true;
				}
			}
			enemyName = string.Empty;
			return false;
		}

		internal static bool TryGetName(AudioSource source, AudioClip clip, out string enemyName)
		{
			if (EnemySoundRegistry.TryGetOwner(source, out EnemyParent owner))
			{
				enemyName = owner.enemyName;
				return true;
			}
			return TryGetName(clip, out enemyName);
		}

		private static bool TryGetName(AudioClip? clip, out string enemyName)
		{
			if ((Object)(object)clip != (Object)null && ((Object)clip).name.StartsWith("enemy ", StringComparison.OrdinalIgnoreCase))
			{
				string text = ((Object)clip).name.Substring("enemy ".Length);
				int num = text.IndexOf(' ');
				enemyName = ((num > 0) ? text.Substring(0, num) : text);
				return !string.IsNullOrWhiteSpace(enemyName);
			}
			enemyName = string.Empty;
			return false;
		}
	}
	[HarmonyPatch(typeof(Sound))]
	internal static class EnemySoundPatches
	{
		private struct LoopParameterState
		{
			internal float Volume;

			internal float Pitch;
		}

		private static readonly Dictionary<Sound, LoopParameterState> LoopParameters = new Dictionary<Sound, LoopParameterState>();

		[HarmonyPrefix]
		[HarmonyPatch("Play", new Type[]
		{
			typeof(Vector3),
			typeof(float),
			typeof(float),
			typeof(float),
			typeof(float)
		})]
		private static void ReplacePlaySound(Sound __instance, out SoundOverrideState __state)
		{
			Apply(__instance, isLoop: false, out __state);
		}

		[HarmonyPostfix]
		[HarmonyPatch("Play", new Type[]
		{
			typeof(Vector3),
			typeof(float),
			typeof(float),
			typeof(float),
			typeof(float)
		})]
		private static void RestorePlaySound(Sound __instance, SoundOverrideState __state)
		{
			Restore(__instance, __state, isLoop: false);
		}

		[HarmonyFinalizer]
		[HarmonyPatch("Play", new Type[]
		{
			typeof(Vector3),
			typeof(float),
			typeof(float),
			typeof(float),
			typeof(float)
		})]
		private static Exception? RestorePlaySoundOnException(Sound __instance, SoundOverrideState __state, Exception? __exception)
		{
			Restore(__instance, __state, isLoop: false);
			return __exception;
		}

		[HarmonyPrefix]
		[HarmonyPatch("PlayLoop", new Type[]
		{
			typeof(bool),
			typeof(float),
			typeof(float),
			typeof(float),
			typeof(float)
		})]
		private static void ReplaceLoopSound(Sound __instance, out SoundOverrideState __state)
		{
			Apply(__instance, isLoop: true, out __state);
		}

		[HarmonyPostfix]
		[HarmonyPatch("PlayLoop", new Type[]
		{
			typeof(bool),
			typeof(float),
			typeof(float),
			typeof(float),
			typeof(float)
		})]
		private static void RestoreLoopSound(Sound __instance, SoundOverrideState __state)
		{
			Restore(__instance, __state, isLoop: true);
		}

		[HarmonyFinalizer]
		[HarmonyPatch("PlayLoop", new Type[]
		{
			typeof(bool),
			typeof(float),
			typeof(float),
			typeof(float),
			typeof(float)
		})]
		private static Exception? RestoreLoopSoundOnException(Sound __instance, SoundOverrideState __state, Exception? __exception)
		{
			Restore(__instance, __state, isLoop: true);
			return __exception;
		}

		private static void Apply(Sound sound, bool isLoop, out SoundOverrideState state)
		{
			state = default(SoundOverrideState);
			EnemyAudioDiagnostics.ReportSoundObserved(sound, isLoop);
			if (sound.Sounds == null || !EnemySoundIdentity.TryGetName(sound, out string enemyName))
			{
				return;
			}
			AudioClip[] sounds = sound.Sounds;
			AudioClip[] array = (AudioClip[])(object)new AudioClip[sounds.Length];
			bool flag = false;
			float num = 1f;
			float num2 = 1f;
			for (int i = 0; i < sounds.Length; i++)
			{
				AudioClip val = (array[i] = sounds[i]);
				EnemyParent owner2;
				EnemyParent owner = (EnemySoundRegistry.TryGetOwner(sound, out owner2) ? owner2 : null);
				if ((Object)(object)val != (Object)null && AudioCatalog.TryPickForMonster(owner, enemyName, val, out AudioClip replacement, out float volumeMultiplier, out float pitchMultiplier))
				{
					array[i] = replacement;
					num = volumeMultiplier;
					num2 = pitchMultiplier;
					flag = true;
				}
			}
			if (!flag)
			{
				if (isLoop && LoopParameters.TryGetValue(sound, out var value))
				{
					sound.Volume = value.Volume;
					sound.Pitch = value.Pitch;
					LoopParameters.Remove(sound);
				}
				return;
			}
			state = new SoundOverrideState
			{
				Applied = true,
				Sounds = sounds,
				Volume = sound.Volume,
				Pitch = sound.Pitch
			};
			sound.Sounds = array;
			if (isLoop)
			{
				if (!LoopParameters.TryGetValue(sound, out var value2))
				{
					value2 = new LoopParameterState
					{
						Volume = sound.Volume,
						Pitch = sound.Pitch
					};
					LoopParameters.Add(sound, value2);
				}
				sound.Volume = value2.Volume * num;
				sound.Pitch = value2.Pitch * num2;
				if ((Object)(object)sound.Source != (Object)null)
				{
					SoundFieldAccess.ResetAudioInfoFetched(sound);
				}
			}
			else
			{
				sound.Volume *= num;
				sound.Pitch *= num2;
			}
		}

		private static void Restore(Sound sound, SoundOverrideState state, bool isLoop)
		{
			if (state.Applied)
			{
				sound.Sounds = state.Sounds;
				if (!isLoop)
				{
					sound.Volume = state.Volume;
					sound.Pitch = state.Pitch;
				}
			}
		}
	}
	[HarmonyPatch(typeof(AudioSource))]
	internal static class EnemyAudioSourcePatches
	{
		[HarmonyPrefix]
		[HarmonyPatch("PlayOneShot", new Type[] { typeof(AudioClip) })]
		private static void ReplaceOneShotWithoutVolume(AudioSource __instance, ref AudioClip clip)
		{
			EnemyAudioDiagnostics.ReportOneShotObserved(__instance, clip);
			EnemyParent owner2;
			EnemyParent owner = (EnemySoundRegistry.TryGetOwner(__instance, out owner2) ? owner2 : null);
			if ((Object)(object)clip != (Object)null && EnemySoundIdentity.TryGetName(__instance, clip, out string enemyName) && AudioCatalog.TryPickForMonster(owner, enemyName, clip, out AudioClip replacement, out float _, out float _))
			{
				clip = replacement;
			}
		}

		[HarmonyPrefix]
		[HarmonyPatch("PlayOneShot", new Type[]
		{
			typeof(AudioClip),
			typeof(float)
		})]
		private static void ReplaceOneShot(AudioSource __instance, ref AudioClip clip, ref float volumeScale)
		{
			EnemyAudioDiagnostics.ReportOneShotObserved(__instance, clip);
			EnemyParent owner2;
			EnemyParent owner = (EnemySoundRegistry.TryGetOwner(__instance, out owner2) ? owner2 : null);
			if ((Object)(object)clip != (Object)null && EnemySoundIdentity.TryGetName(__instance, clip, out string enemyName) && AudioCatalog.TryPickForMonster(owner, enemyName, clip, out AudioClip replacement, out float volumeMultiplier, out float _))
			{
				clip = replacement;
				volumeScale *= volumeMultiplier;
			}
		}
	}
	internal static class SoundApiCompatibility
	{
		internal static void SafeResetAudioInfoFetched(Sound __instance)
		{
			if (__instance != null && (Object)(object)__instance.Source != (Object)null && (Object)(object)__instance.Source.clip == (Object)null)
			{
				SoundFieldAccess.ResetAudioInfoFetched(__instance);
			}
		}
	}
	internal static class SoundFieldAccess
	{
		private static readonly FieldInfo? AudioInfoFetched = AccessTools.Field(typeof(Sound), "AudioInfoFetched");

		internal static void ResetAudioInfoFetched(Sound sound)
		{
			AudioInfoFetched?.SetValue(sound, false);
		}
	}
	internal static class EnemyAudioDiagnostics
	{
		private static readonly HashSet<string> Seen = new HashSet<string>(StringComparer.OrdinalIgnoreCase);

		private static readonly HashSet<Sound> ObservedSounds = new HashSet<Sound>();

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

		internal static void ReportSoundObserved(Sound sound, bool isLoop)
		{
			if (Plugin.DebugLogging && ObservedSounds.Add(sound))
			{
				string text = ((sound.Sounds == null) ? "none" : string.Join(", ", from clip in sound.Sounds
					where (Object)(object)clip != (Object)null
					select ((Object)clip).name));
				string text2 = EnemySoundCallContext.FindEnemyCaller() ?? "none";
				Plugin.Log.LogInfo((object)("Observed Sound." + (isLoop ? "PlayLoop" : "Play") + ": clips=[" + text + "], enemy caller=" + text2 + "."));
			}
		}

		internal static void ReportOneShotObserved(AudioSource source, AudioClip? clip)
		{
			if (Plugin.DebugLogging && !((Object)(object)clip == (Object)null))
			{
				string item = ((Object)source).name + ":" + ((Object)clip).name;
				if (ObservedOneShots.Add(item))
				{
					Plugin.Log.LogInfo((object)("Observed AudioSource.PlayOneShot: source='" + ((Object)source).name + "', clip='" + ((Object)clip).name + "'."));
				}
			}
		}

		internal static void Report(string enemyName, AudioClip original, AudioClip replacement)
		{
			if (Plugin.DebugLogging)
			{
				string item = enemyName + ":" + ((Object)original).name + ":" + ((Object)replacement).name;
				if (Seen.Add(item))
				{
					Plugin.Log.LogInfo((object)("Replacing '" + ((Object)original).name + "' for enemy '" + enemyName + "' with '" + ((Object)replacement).name + "'."));
				}
			}
		}
	}
}