Decompiled source of MinecraftSoundReplacement v1.0.0

BepInEx/plugins/MinecraftSoundReplacement/MinecraftSoundReplacement.dll

Decompiled 15 hours 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 System.Threading;
using BepInEx;
using BepInEx.Configuration;
using BepInEx.Logging;
using HarmonyLib;
using Microsoft.CodeAnalysis;

[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("od.e")]
[assembly: AssemblyConfiguration("Release")]
[assembly: AssemblyDescription("Replaces How to Fish NPC dialogue and eating sounds with matching Minecraft audio.")]
[assembly: AssemblyFileVersion("1.0.0.0")]
[assembly: AssemblyInformationalVersion("1.0.0")]
[assembly: AssemblyProduct("MinecraftSoundReplacement")]
[assembly: AssemblyTitle("MinecraftSoundReplacement")]
[assembly: AssemblyVersion("1.0.0.0")]
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;
		}
	}
}
namespace HowToFish.MinecraftSoundReplacement
{
	internal static class AudioAssets
	{
		private readonly struct WaveFormat
		{
			public ushort Encoding { get; }

			public ushort Channels { get; }

			public uint SampleRate { get; }

			public ushort BlockAlign { get; }

			public ushort BitsPerSample { get; }

			public WaveFormat(ushort encoding, ushort channels, uint sampleRate, ushort blockAlign, ushort bitsPerSample)
			{
				Encoding = encoding;
				Channels = channels;
				SampleRate = sampleRate;
				BlockAlign = blockAlign;
				BitsPerSample = bitsPerSample;
			}
		}

		private readonly struct WaveData
		{
			public int SampleRate { get; }

			public int Channels { get; }

			public float[] Samples { get; }

			public WaveData(int sampleRate, int channels, float[] samples)
			{
				SampleRate = sampleRate;
				Channels = channels;
				Samples = samples;
			}
		}

		private const BindingFlags PublicInstance = BindingFlags.Instance | BindingFlags.Public;

		private const BindingFlags PublicStatic = BindingFlags.Static | BindingFlags.Public;

		private static int _nextNpcTalking = -1;

		private static int _nextEating = -1;

		private static int _nextDrinking = -1;

		private static int _nextBurp = -1;

		public static IReadOnlyList<LoadedAudioClip> NpcTalking { get; private set; } = Array.Empty<LoadedAudioClip>();

		public static IReadOnlyList<LoadedAudioClip> Eating { get; private set; } = Array.Empty<LoadedAudioClip>();

		public static IReadOnlyList<LoadedAudioClip> Drinking { get; private set; } = Array.Empty<LoadedAudioClip>();

		public static IReadOnlyList<LoadedAudioClip> Burps { get; private set; } = Array.Empty<LoadedAudioClip>();

		public static void Load(string pluginDirectory, float gain, ManualLogSource log)
		{
			Type obj = AccessTools.TypeByName("UnityEngine.AudioClip") ?? throw new TypeLoadException("UnityEngine.AudioClip was not found.");
			MethodInfo create = obj.GetMethods(BindingFlags.Static | BindingFlags.Public).Single((MethodInfo method) => method.Name == "Create" && method.GetParameters().Length == 5 && method.GetParameters()[0].ParameterType == typeof(string));
			MethodInfo setData = obj.GetMethods(BindingFlags.Instance | BindingFlags.Public).Single((MethodInfo method) => method.Name == "SetData" && method.GetParameters().Length == 2 && method.GetParameters()[0].ParameterType == typeof(float[]));
			string path = Path.Combine(pluginDirectory, "audio");
			NpcTalking = LoadCategory(Path.Combine(path, "talking"), "NPC dialogue", gain, create, setData, log);
			Eating = LoadCategory(Path.Combine(path, "eating"), "eating", gain, create, setData, log);
			Drinking = LoadCategory(Path.Combine(path, "drinking"), "drinking", gain, create, setData, log);
			Burps = LoadCategory(Path.Combine(path, "burp"), "burp", gain, create, setData, log);
		}

		public static LoadedAudioClip? NextNpcTalking()
		{
			return Next(NpcTalking, ref _nextNpcTalking);
		}

		public static LoadedAudioClip? NextEating()
		{
			return Next(Eating, ref _nextEating);
		}

		public static LoadedAudioClip? NextDrinking()
		{
			return Next(Drinking, ref _nextDrinking);
		}

		public static LoadedAudioClip? NextBurp()
		{
			return Next(Burps, ref _nextBurp);
		}

		private static IReadOnlyList<LoadedAudioClip> LoadCategory(string directory, string displayName, float gain, MethodInfo create, MethodInfo setData, ManualLogSource log)
		{
			if (!Directory.Exists(directory))
			{
				log.LogWarning((object)("No " + displayName + " audio directory found at '" + directory + "'; original sounds remain active."));
				return Array.Empty<LoadedAudioClip>();
			}
			List<LoadedAudioClip> list = new List<LoadedAudioClip>();
			bool flag = default(bool);
			foreach (string item in Directory.GetFiles(directory, "*.wav", SearchOption.TopDirectoryOnly).OrderBy<string, string>((string path) => path, StringComparer.OrdinalIgnoreCase))
			{
				try
				{
					WaveData waveData = ReadWave(item);
					NormalizeAndBoost(waveData.Samples, gain);
					int num = list.Count + 1;
					string text = $"MinecraftSoundReplacement_{displayName}_{num}";
					object obj = create.Invoke(null, new object[5]
					{
						text,
						waveData.Samples.Length / waveData.Channels,
						waveData.Channels,
						waveData.SampleRate,
						false
					}) ?? throw new InvalidOperationException("Unity returned a null AudioClip.");
					object obj2 = setData.Invoke(obj, new object[2] { waveData.Samples, 0 });
					int num2;
					if (obj2 is bool)
					{
						flag = (bool)obj2;
						num2 = 1;
					}
					else
					{
						num2 = 0;
					}
					if (((uint)num2 & (flag ? 1u : 0u)) == 0)
					{
						throw new InvalidOperationException("Unity AudioClip.SetData rejected the decoded samples.");
					}
					list.Add(new LoadedAudioClip(obj, num));
				}
				catch (Exception exception)
				{
					log.LogError((object)("Could not load " + displayName + " clip '" + item + "': " + Unwrap(exception).Message));
				}
			}
			if (list.Count == 0)
			{
				log.LogWarning((object)("No valid PCM WAV files were found for " + displayName + "; original sounds remain active."));
			}
			return list;
		}

		private static LoadedAudioClip? Next(IReadOnlyList<LoadedAudioClip> values, ref int counter)
		{
			if (values.Count == 0)
			{
				return null;
			}
			int num = Interlocked.Increment(ref counter) & 0x7FFFFFFF;
			return values[num % values.Count];
		}

		private static WaveData ReadWave(string path)
		{
			using FileStream fileStream = File.OpenRead(path);
			using BinaryReader binaryReader = new BinaryReader(fileStream);
			if (ReadFourCc(binaryReader) != "RIFF")
			{
				throw new InvalidDataException("Expected a RIFF header.");
			}
			binaryReader.ReadUInt32();
			if (ReadFourCc(binaryReader) != "WAVE")
			{
				throw new InvalidDataException("Expected WAVE data.");
			}
			WaveFormat? waveFormat = null;
			byte[] array = null;
			while (fileStream.Position + 8 <= fileStream.Length)
			{
				string text = ReadFourCc(binaryReader);
				uint num = binaryReader.ReadUInt32();
				long num2 = checked(fileStream.Position + num);
				if (num2 > fileStream.Length)
				{
					throw new InvalidDataException("Chunk '" + text + "' extends past the end of the file.");
				}
				if (text == "fmt ")
				{
					if (num < 16)
					{
						throw new InvalidDataException("The WAV format chunk is too short.");
					}
					ushort encoding = binaryReader.ReadUInt16();
					ushort channels = binaryReader.ReadUInt16();
					uint sampleRate = binaryReader.ReadUInt32();
					binaryReader.ReadUInt32();
					ushort blockAlign = binaryReader.ReadUInt16();
					ushort bitsPerSample = binaryReader.ReadUInt16();
					waveFormat = new WaveFormat(encoding, channels, sampleRate, blockAlign, bitsPerSample);
				}
				else if (text == "data")
				{
					if (num > int.MaxValue)
					{
						throw new InvalidDataException("The WAV data chunk is too large.");
					}
					array = binaryReader.ReadBytes((int)num);
					if (array.Length != (int)num)
					{
						throw new EndOfStreamException("The WAV data chunk ended unexpectedly.");
					}
				}
				fileStream.Position = num2 + (num & 1);
			}
			if (!waveFormat.HasValue || array == null)
			{
				throw new InvalidDataException("The WAV file must contain both 'fmt ' and 'data' chunks.");
			}
			return Decode(waveFormat.Value, array);
		}

		private static WaveData Decode(WaveFormat format, byte[] bytes)
		{
			if (format.Channels == 0 || format.Channels > 8 || format.SampleRate == 0 || format.SampleRate > 384000)
			{
				throw new InvalidDataException("The channel count or sample rate is invalid.");
			}
			int num = (format.BitsPerSample + 7) / 8;
			int num2 = checked(format.Channels * num);
			if (num == 0 || format.BlockAlign != num2 || bytes.Length % format.BlockAlign != 0)
			{
				throw new InvalidDataException("The WAV block alignment is invalid.");
			}
			float[] array = new float[bytes.Length / num];
			if (format.Encoding == 1)
			{
				DecodePcm(format.BitsPerSample, bytes, array);
			}
			else
			{
				if (format.Encoding != 3 || format.BitsPerSample != 32)
				{
					throw new InvalidDataException($"Unsupported WAV encoding {format.Encoding} at {format.BitsPerSample} bits. " + "Use uncompressed PCM (8/16/24/32-bit) or 32-bit IEEE float WAV.");
				}
				for (int i = 0; i < array.Length; i++)
				{
					float num3 = BitConverter.ToSingle(bytes, i * 4);
					array[i] = (float.IsNaN(num3) ? 0f : Math.Max(-1f, Math.Min(1f, num3)));
				}
			}
			return new WaveData((int)format.SampleRate, format.Channels, array);
		}

		private static void NormalizeAndBoost(float[] samples, float userGain)
		{
			if (samples.Length == 0)
			{
				return;
			}
			double num = 0.0;
			foreach (float num2 in samples)
			{
				num += (double)(num2 * num2);
			}
			double num3 = Math.Sqrt(num / (double)samples.Length);
			if (!(num3 < 1E-06))
			{
				double num4 = Math.Min(12.0, 0.18 / num3) * Math.Max(0.25, Math.Min(4.0, userGain));
				for (int j = 0; j < samples.Length; j++)
				{
					samples[j] = (float)(0.98 * Math.Tanh((double)samples[j] * num4 / 0.98));
				}
			}
		}

		private static void DecodePcm(ushort bitsPerSample, byte[] bytes, float[] samples)
		{
			switch (bitsPerSample)
			{
			case 8:
			{
				for (int l = 0; l < samples.Length; l++)
				{
					samples[l] = (float)(bytes[l] - 128) / 128f;
				}
				break;
			}
			case 16:
			{
				for (int j = 0; j < samples.Length; j++)
				{
					samples[j] = (float)BitConverter.ToInt16(bytes, j * 2) / 32768f;
				}
				break;
			}
			case 24:
			{
				for (int k = 0; k < samples.Length; k++)
				{
					int num = k * 3;
					int num2 = bytes[num] | (bytes[num + 1] << 8) | (bytes[num + 2] << 16);
					if ((num2 & 0x800000) != 0)
					{
						num2 |= -16777216;
					}
					samples[k] = (float)num2 / 8388608f;
				}
				break;
			}
			case 32:
			{
				for (int i = 0; i < samples.Length; i++)
				{
					samples[i] = (float)BitConverter.ToInt32(bytes, i * 4) / 2.1474836E+09f;
				}
				break;
			}
			default:
				throw new InvalidDataException($"Unsupported PCM bit depth {bitsPerSample}. Use 8, 16, 24, or 32-bit PCM WAV.");
			}
		}

		private static string ReadFourCc(BinaryReader reader)
		{
			return new string(reader.ReadChars(4));
		}

		private static Exception Unwrap(Exception exception)
		{
			while (exception is TargetInvocationException && exception.InnerException != null)
			{
				exception = exception.InnerException;
			}
			return exception;
		}
	}
	internal sealed class LoadedAudioClip
	{
		public object UnityClip { get; }

		public int CategoryIndex { get; }

		public LoadedAudioClip(object unityClip, int categoryIndex)
		{
			UnityClip = unityClip;
			CategoryIndex = categoryIndex;
		}
	}
	[BepInPlugin("od.e.howtofish.minecraftsoundreplacement", "Minecraft Sound Replacement", "1.0.0")]
	public sealed class MinecraftSoundReplacementPlugin : BaseUnityPlugin
	{
		public const string PluginGuid = "od.e.howtofish.minecraftsoundreplacement";

		public const string PluginName = "Minecraft Sound Replacement";

		public const string PluginVersion = "1.0.0";

		private const string FurModelReplaceGuid = "dev.codex.howtofish.furmodelreplace";

		private static readonly Guid SupportedGameMvid = new Guid("5bfce772-ee82-40f5-9e5b-e1cd1212f3dd");

		private const string SupportedUnityVersion = "6000.4.4f1";

		private const BindingFlags AllMembers = BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic;

		[ThreadStatic]
		private static int _npcEatingDepth;

		private static ManualLogSource _log = null;

		private static FieldInfo _npcMouthSource = null;

		private static FieldInfo _npcMouthVolume = null;

		private static FieldInfo _playerEatSource = null;

		private static FieldInfo _playerEatClip = null;

		private static FieldInfo _playerDrinkClip = null;

		private static FieldInfo _playerEatVolume = null;

		private static FieldInfo _playerDrinkVolume = null;

		private static FieldInfo _playerIsEating = null;

		private static FieldInfo _audioManagerClips = null;

		private static PropertyInfo _audioSourceClip = null;

		private static PropertyInfo _audioSourceIsPlaying = null;

		private Harmony? _harmony;

		private void Awake()
		{
			//IL_0039: Unknown result type (might be due to invalid IL or missing references)
			//IL_0043: Expected O, but got Unknown
			//IL_0218: Unknown result type (might be due to invalid IL or missing references)
			//IL_0222: Expected O, but got Unknown
			_log = ((BaseUnityPlugin)this).Logger;
			try
			{
				ConfigEntry<float> val = ((BaseUnityPlugin)this).Config.Bind<float>("Audio", "Gain", 1.5f, new ConfigDescription("Extra gain applied after loudness normalization. Restart the game after changing it.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0.25f, 4f), Array.Empty<object>()));
				AudioAssets.Load(Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location) ?? Paths.PluginPath, val.Value, ((BaseUnityPlugin)this).Logger);
				Type type = RequireType("NPC");
				Type type2 = RequireType("PlayerEating");
				Type type3 = RequireType("AudioManager");
				Type type4 = RequireType("UnityEngine.AudioSource");
				_npcMouthSource = RequireField(type, "_mouthSource");
				_npcMouthVolume = RequireField(type, "_mouthVol");
				_playerEatSource = RequireField(type2, "_eatSoundSource");
				_playerEatClip = RequireField(type2, "_eatSound");
				_playerDrinkClip = RequireField(type2, "_drinkSound");
				_playerEatVolume = RequireField(type2, "_eatSoundVolume");
				_playerDrinkVolume = RequireField(type2, "_drinkSoundVolume");
				_playerIsEating = RequireField(type2, "_localIsEating");
				_audioManagerClips = RequireField(type3, "_realClips");
				_audioSourceClip = RequireProperty(type4, "clip");
				_audioSourceIsPlaying = RequireProperty(type4, "isPlaying");
				MethodInfo methodInfo = RequireMethod(type, "SetNpcText", 4);
				Guid moduleVersionId = methodInfo.DeclaringType.Assembly.ManifestModule.ModuleVersionId;
				if (moduleVersionId != SupportedGameMvid)
				{
					((BaseUnityPlugin)this).Logger.LogWarning((object)("This game assembly differs from Steam build 24911270 " + $"(expected MVID {SupportedGameMvid}, found {moduleVersionId})."));
				}
				string text = RequireType("UnityEngine.Application").GetProperty("unityVersion", BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic)?.GetValue(null, null)?.ToString();
				if (!string.Equals(text, "6000.4.4f1", StringComparison.Ordinal))
				{
					((BaseUnityPlugin)this).Logger.LogWarning((object)("This mod targets Unity 6000.4.4f1; the running game reports " + (text ?? "<unknown>") + "."));
				}
				_harmony = new Harmony("od.e.howtofish.minecraftsoundreplacement");
				Patch(methodInfo, "ConfigureNpcTalking", null);
				MethodInfo original = AccessTools.EnumeratorMoveNext((MethodBase)RequireMethod(type, "EatEffects", 0)) ?? throw new MissingMethodException("NPC", "EatEffects iterator MoveNext");
				Patch(original, "BeforeNpcEating", "AfterNpcEating");
				Patch(RequireMethod(type2, "OnStartClient", 0), null, "ConfigurePlayerEating");
				Patch(RequireMethod(type2, "UpdateEating", 0), "PreparePlayerEatingSound", null);
				Patch(RequireMethod(type3, "Start", 0), null, "RegisterAudioClips");
				Patch(RequireMethod(type3, "PlayClipAt", 6), "ReplaceNpcEatingSound", null);
				Patch(RequireMethod(type3, "PlayPlayerClip", 6), "ReplacePlayerSwallow", null);
				((BaseUnityPlugin)this).Logger.LogInfo((object)($"Minecraft Sound Replacement is active with {AudioAssets.NpcTalking.Count} NPC dialogue, " + $"{AudioAssets.Eating.Count} eating, {AudioAssets.Drinking.Count} drinking, and " + $"{AudioAssets.Burps.Count} burp clip(s); normalized gain={val.Value:0.##}x."));
			}
			catch (Exception exception)
			{
				((BaseUnityPlugin)this).Logger.LogError((object)"Minecraft Sound Replacement could not install its patches.");
				((BaseUnityPlugin)this).Logger.LogError((object)Unwrap(exception));
				Harmony? harmony = _harmony;
				if (harmony != null)
				{
					harmony.UnpatchSelf();
				}
				_harmony = null;
			}
		}

		private void OnDestroy()
		{
			Harmony? harmony = _harmony;
			if (harmony != null)
			{
				harmony.UnpatchSelf();
			}
		}

		private void Patch(MethodInfo original, string? prefixName, string? postfixName)
		{
			_harmony.Patch((MethodBase)original, (prefixName == null) ? null : CreatePatch(prefixName), (postfixName == null) ? null : CreatePatch(postfixName), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null);
		}

		private static HarmonyMethod CreatePatch(string name)
		{
			//IL_0045: Unknown result type (might be due to invalid IL or missing references)
			//IL_004b: Expected O, but got Unknown
			return new HarmonyMethod(typeof(MinecraftSoundReplacementPlugin).GetMethod(name, BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic) ?? throw new MissingMethodException(typeof(MinecraftSoundReplacementPlugin).FullName, name), 0, (string[])null, new string[1] { "dev.codex.howtofish.furmodelreplace" }, (bool?)null);
		}

		private static void ConfigureNpcTalking(object __instance)
		{
			LoadedAudioClip loadedAudioClip = AudioAssets.NextNpcTalking();
			object value = _npcMouthSource.GetValue(__instance);
			if (loadedAudioClip != null && value != null)
			{
				_audioSourceClip.SetValue(value, loadedAudioClip.UnityClip, null);
				_npcMouthVolume.SetValue(__instance, 1f);
			}
		}

		private static void BeforeNpcEating()
		{
			_npcEatingDepth++;
		}

		private static void AfterNpcEating()
		{
			if (_npcEatingDepth > 0)
			{
				_npcEatingDepth--;
			}
		}

		private static void ConfigurePlayerEating(object __instance)
		{
			LoadedAudioClip loadedAudioClip = AudioAssets.NextEating();
			LoadedAudioClip loadedAudioClip2 = AudioAssets.NextDrinking();
			if (loadedAudioClip != null)
			{
				_playerEatClip.SetValue(__instance, loadedAudioClip.UnityClip);
				_playerEatVolume.SetValue(__instance, 1f);
			}
			if (loadedAudioClip2 != null)
			{
				_playerDrinkClip.SetValue(__instance, loadedAudioClip2.UnityClip);
				_playerDrinkVolume.SetValue(__instance, 1f);
			}
		}

		private static void PreparePlayerEatingSound(object __instance)
		{
			if (AudioAssets.Eating.Count == 0)
			{
				return;
			}
			object value = _playerIsEating.GetValue(__instance);
			if (!(value is bool) || !(bool)value)
			{
				return;
			}
			object value2 = _playerEatSource.GetValue(__instance);
			if (value2 == null)
			{
				return;
			}
			value = _audioSourceIsPlaying.GetValue(value2, null);
			bool flag = default(bool);
			int num;
			if (value is bool)
			{
				flag = (bool)value;
				num = 1;
			}
			else
			{
				num = 0;
			}
			if (((uint)num & (flag ? 1u : 0u)) == 0)
			{
				LoadedAudioClip loadedAudioClip = AudioAssets.NextEating();
				if (loadedAudioClip != null)
				{
					_playerEatClip.SetValue(__instance, loadedAudioClip.UnityClip);
					_playerEatVolume.SetValue(__instance, 1f);
					_playerDrinkVolume.SetValue(__instance, 1f);
				}
			}
		}

		private static void RegisterAudioClips()
		{
			if (!(_audioManagerClips.GetValue(null) is IDictionary dictionary))
			{
				_log.LogError((object)"Could not access AudioManager._realClips; NPC eating and final burp remain unchanged.");
				return;
			}
			Register(dictionary, "MinecraftEat", AudioAssets.Eating);
			Register(dictionary, "MinecraftDrink", AudioAssets.Drinking);
			Register(dictionary, "MinecraftBurp", AudioAssets.Burps);
		}

		private static void ReplaceNpcEatingSound(ref string clip, ref float volume)
		{
			if (_npcEatingDepth > 0 && AudioAssets.Eating.Count != 0)
			{
				LoadedAudioClip loadedAudioClip = AudioAssets.NextEating();
				if (loadedAudioClip != null)
				{
					clip = $"MinecraftEat{loadedAudioClip.CategoryIndex}";
					volume = Math.Max(volume, 1f);
				}
			}
		}

		private static void ReplacePlayerSwallow(ref string clip, ref float volume)
		{
			if (AudioAssets.Burps.Count != 0 && (string.Equals(clip, "Swallow", StringComparison.Ordinal) || clip.StartsWith("FurPlayerEating", StringComparison.Ordinal)))
			{
				LoadedAudioClip loadedAudioClip = AudioAssets.NextBurp();
				if (loadedAudioClip != null)
				{
					clip = $"MinecraftBurp{loadedAudioClip.CategoryIndex}";
					volume = Math.Max(volume, 1f);
				}
			}
		}

		private static void Register(IDictionary dictionary, string prefix, IReadOnlyList<LoadedAudioClip> clips)
		{
			foreach (LoadedAudioClip clip in clips)
			{
				dictionary[$"{prefix}{clip.CategoryIndex}"] = clip.UnityClip;
			}
		}

		private static Type RequireType(string typeName)
		{
			return AccessTools.TypeByName(typeName) ?? throw new TypeLoadException("Required runtime type '" + typeName + "' was not found.");
		}

		private static MethodInfo RequireMethod(Type type, string methodName, int parameterCount)
		{
			MethodInfo[] array = (from method in type.GetMethods(BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic)
				where method.Name == methodName && method.GetParameters().Length == parameterCount
				select method).ToArray();
			if (array.Length != 1)
			{
				throw new MissingMethodException(type.FullName, $"{methodName} with {parameterCount} parameter(s); found {array.Length}");
			}
			return array[0];
		}

		private static FieldInfo RequireField(Type type, string fieldName)
		{
			return type.GetField(fieldName, BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic) ?? throw new MissingFieldException(type.FullName, fieldName);
		}

		private static PropertyInfo RequireProperty(Type type, string propertyName)
		{
			return type.GetProperty(propertyName, BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic) ?? throw new MissingMemberException(type.FullName, propertyName);
		}

		private static Exception Unwrap(Exception exception)
		{
			while (exception is TargetInvocationException && exception.InnerException != null)
			{
				exception = exception.InnerException;
			}
			return exception;
		}
	}
}