Decompiled source of CustomMusic v1.0.8

CustomMusic.dll

Decompiled a week ago
using System;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Runtime.Versioning;
using System.Security;
using System.Security.Cryptography;
using System.Security.Permissions;
using System.Text;
using System.Threading.Tasks;
using CustomMusic;
using HarmonyLib;
using Il2Cpp;
using Il2CppInterop.Runtime.InteropTypes.Arrays;
using Il2CppSystem;
using Il2CppSystem.Reflection;
using Il2CppTMPro;
using MelonLoader;
using MelonLoader.Preferences;
using MelonLoader.Utils;
using Microsoft.CodeAnalysis;
using NLayer;
using NVorbis;
using UnityEngine;
using UnityEngine.EventSystems;
using UnityEngine.Events;
using UnityEngine.SceneManagement;
using UnityEngine.UI;

[assembly: CompilationRelaxations(8)]
[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
[assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)]
[assembly: MelonInfo(typeof(CustomMusicMod), "CustomMusic", "1.0.8", "misha", null)]
[assembly: MelonGame("Kubunautilus", "BrutalistickVR")]
[assembly: MelonPriority(-100)]
[assembly: TargetFramework(".NETCoreApp,Version=v6.0", FrameworkDisplayName = ".NET 6.0")]
[assembly: AssemblyCompany("CustomMusic")]
[assembly: AssemblyConfiguration("Release")]
[assembly: AssemblyFileVersion("1.0.0.0")]
[assembly: AssemblyInformationalVersion("1.0.0+2d1a60d6d74d1b7059d3220dce1f21958635cd5c")]
[assembly: AssemblyProduct("CustomMusic")]
[assembly: AssemblyTitle("CustomMusic")]
[assembly: AssemblyMetadata("AI_Assisted_Creation", "This assembly was partially or fully created with the assistance of Generative AI (e.g., Code Suggestions, Refactoring, Documentation Generation).")]
[assembly: SecurityPermission(SecurityAction.RequestMinimum, SkipVerification = true)]
[assembly: AssemblyVersion("1.0.0.0")]
[module: UnverifiableCode]
[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 CustomMusic
{
	internal sealed class DecodedAudio
	{
		public string Name;

		public int Channels;

		public int SampleRate;

		public float[] Samples;

		public int LengthMs
		{
			get
			{
				if (Channels <= 0 || SampleRate <= 0 || Samples == null)
				{
					return 0;
				}
				return (int)((long)(Samples.Length / Channels) * 1000L / SampleRate);
			}
		}
	}
	internal sealed class PreparedTrack
	{
		public string PlayPath;

		public string DeviceType;

		public bool Temporary;
	}
	internal static class AudioDecode
	{
		private const string Tag = "[CustomMusic]";

		public static PreparedTrack Prepare(string path)
		{
			string text = Path.GetExtension(path).ToLowerInvariant();
			string text2 = TryGetCachedWav(path);
			if (text2 != null)
			{
				return new PreparedTrack
				{
					PlayPath = text2,
					DeviceType = "waveaudio",
					Temporary = true
				};
			}
			switch (text)
			{
			case ".ogg":
			{
				DecodedAudio audio3 = OggDecoder.Decode(path);
				return new PreparedTrack
				{
					PlayPath = WriteTempWav(path, audio3),
					DeviceType = "waveaudio",
					Temporary = true
				};
			}
			case ".mp3":
			{
				DecodedAudio audio2 = Mp3Decoder.Decode(path);
				return new PreparedTrack
				{
					PlayPath = WriteTempWav(path, audio2),
					DeviceType = "waveaudio",
					Temporary = true
				};
			}
			case ".wav":
			{
				if (WavDecoder.IsMciFriendly(path) && IsAscii(path))
				{
					return new PreparedTrack
					{
						PlayPath = path,
						DeviceType = "waveaudio",
						Temporary = false
					};
				}
				DecodedAudio audio = WavDecoder.Decode(path);
				return new PreparedTrack
				{
					PlayPath = WriteTempWav(path, audio),
					DeviceType = "waveaudio",
					Temporary = true
				};
			}
			default:
				return new PreparedTrack
				{
					PlayPath = path,
					DeviceType = "waveaudio",
					Temporary = false
				};
			}
		}

		private static bool IsAscii(string s)
		{
			for (int i = 0; i < s.Length; i++)
			{
				if (s[i] > '\u007f')
				{
					return false;
				}
			}
			return true;
		}

		private static string TryGetCachedWav(string source)
		{
			try
			{
				string text = Path.Combine(MelonEnvironment.UserDataDirectory, "CustomMusicCache");
				if (!Directory.Exists(text))
				{
					return null;
				}
				string text2 = Hash(source + "|" + File.GetLastWriteTimeUtc(source).Ticks + "|" + new FileInfo(source).Length);
				string text3 = Path.Combine(text, text2 + ".wav");
				if (File.Exists(text3) && new FileInfo(text3).Length > 44)
				{
					return text3;
				}
			}
			catch
			{
			}
			return null;
		}

		private static string WriteTempWav(string source, DecodedAudio audio)
		{
			if (audio == null || audio.Samples == null || audio.Samples.Length == 0)
			{
				throw new InvalidDataException("empty PCM data");
			}
			if (audio.Channels <= 0 || audio.SampleRate <= 0)
			{
				throw new InvalidDataException($"bad audio format: {audio.Channels} ch, {audio.SampleRate} Hz");
			}
			string text = Path.Combine(MelonEnvironment.UserDataDirectory, "CustomMusicCache");
			Directory.CreateDirectory(text);
			string text2 = Hash(source + "|" + File.GetLastWriteTimeUtc(source).Ticks + "|" + new FileInfo(source).Length);
			string text3 = Path.Combine(text, text2 + ".wav");
			if (File.Exists(text3) && new FileInfo(text3).Length > 44)
			{
				return text3;
			}
			string text4 = text3 + ".tmp";
			using (FileStream fileStream = new FileStream(text4, FileMode.Create, FileAccess.Write, FileShare.None, 65536))
			{
				using BinaryWriter binaryWriter = new BinaryWriter(fileStream, Encoding.ASCII);
				int channels = audio.Channels;
				int num = audio.Samples.Length / channels * channels * 2;
				binaryWriter.Write(Encoding.ASCII.GetBytes("RIFF"));
				binaryWriter.Write(36 + num);
				binaryWriter.Write(Encoding.ASCII.GetBytes("WAVE"));
				binaryWriter.Write(Encoding.ASCII.GetBytes("fmt "));
				binaryWriter.Write(16);
				binaryWriter.Write((short)1);
				binaryWriter.Write((short)channels);
				binaryWriter.Write(audio.SampleRate);
				binaryWriter.Write(audio.SampleRate * channels * 2);
				binaryWriter.Write((short)(channels * 2));
				binaryWriter.Write((short)16);
				binaryWriter.Write(Encoding.ASCII.GetBytes("data"));
				binaryWriter.Write(num);
				byte[] array = new byte[16384];
				int num2 = audio.Samples.Length;
				int num3;
				for (int i = 0; i < num2; i += num3)
				{
					num3 = Math.Min(8192, num2 - i);
					for (int j = 0; j < num3; j++)
					{
						float num4 = audio.Samples[i + j];
						if (num4 > 1f)
						{
							num4 = 1f;
						}
						else if (num4 < -1f)
						{
							num4 = -1f;
						}
						short num5 = (short)Math.Round((num4 >= 0f) ? (num4 * 32767f) : (num4 * 32768f));
						array[j * 2] = (byte)(num5 & 0xFF);
						array[j * 2 + 1] = (byte)((num5 >> 8) & 0xFF);
					}
					fileStream.Write(array, 0, num3 * 2);
				}
			}
			File.Move(text4, text3, overwrite: true);
			MelonLogger.Msg($"{"[CustomMusic]"} cached WAV: {Path.GetFileName(text3)} ({new FileInfo(text3).Length / 1024} KB)");
			return text3;
		}

		private static string Hash(string text)
		{
			using SHA1 sHA = SHA1.Create();
			byte[] array = sHA.ComputeHash(Encoding.UTF8.GetBytes(text));
			StringBuilder stringBuilder = new StringBuilder(array.Length * 2);
			byte[] array2 = array;
			foreach (byte b in array2)
			{
				stringBuilder.Append(b.ToString("x2"));
			}
			return stringBuilder.ToString();
		}
	}
	internal sealed class FloatBuffer
	{
		private float[] _data;

		public int Count;

		public FloatBuffer(int capacity)
		{
			_data = new float[Math.Max(4096, capacity)];
		}

		public void AddRange(float[] src, int count)
		{
			if (count > 0)
			{
				Ensure(Count + count);
				Array.Copy(src, 0, _data, Count, count);
				Count += count;
			}
		}

		public float[] Finish()
		{
			if (Count != _data.Length)
			{
				Array.Resize(ref _data, Count);
			}
			return _data;
		}

		private void Ensure(int needed)
		{
			if (needed > _data.Length)
			{
				int num;
				for (num = _data.Length; num < needed; num = ((num > 1073741823) ? int.MaxValue : (num * 2)))
				{
				}
				Array.Resize(ref _data, num);
			}
		}
	}
	internal static class CassetteSfx
	{
		private const string Tag = "[CustomMusic]";

		private const uint SND_ASYNC = 1u;

		private const uint SND_NODEFAULT = 2u;

		private static float _cachedInsertDuration = -1f;

		[DllImport("winmm.dll", CharSet = CharSet.Unicode)]
		private static extern bool PlaySoundW(string pszSound, IntPtr hmod, uint fdwSound);

		public static void EnsureSfxFiles()
		{
			if (!string.IsNullOrEmpty(TrackLibrary.Root))
			{
				EnsureSfxFilesTo(Path.Combine(TrackLibrary.Root, "sfx"));
			}
		}

		public static void EnsureSfxFilesTo(string sfxDir)
		{
			try
			{
				if (!string.IsNullOrEmpty(sfxDir))
				{
					if (!Directory.Exists(sfxDir))
					{
						Directory.CreateDirectory(sfxDir);
					}
					ExtractResourceIfMissing("tape_insert.wav", Path.Combine(sfxDir, "tape_insert.wav"));
					ExtractResourceIfMissing("tape_eject.wav", Path.Combine(sfxDir, "tape_eject.wav"));
				}
			}
			catch (Exception ex)
			{
				MelonLogger.Warning("[CustomMusic] auto-extract sfx failed: " + ex.Message);
			}
		}

		private static void ExtractResourceIfMissing(string resourceFileName, string destPath)
		{
			if (File.Exists(destPath))
			{
				return;
			}
			Assembly assembly = typeof(CassetteSfx).Assembly;
			string text = null;
			string[] manifestResourceNames = assembly.GetManifestResourceNames();
			foreach (string text2 in manifestResourceNames)
			{
				if (text2.EndsWith(resourceFileName, StringComparison.OrdinalIgnoreCase))
				{
					text = text2;
					break;
				}
			}
			if (text == null)
			{
				return;
			}
			using Stream stream = assembly.GetManifestResourceStream(text);
			if (stream != null)
			{
				using (FileStream destination = new FileStream(destPath, FileMode.Create, FileAccess.Write))
				{
					stream.CopyTo(destination);
				}
				MelonLogger.Msg("[CustomMusic] auto-extracted missing '" + resourceFileName + "' into UserData\\CustomMusic\\sfx");
			}
		}

		public static float GetInsertDuration()
		{
			if (_cachedInsertDuration > 0f)
			{
				return _cachedInsertDuration;
			}
			float num = ReadWavDuration("tape_insert.wav");
			if (num <= 0f)
			{
				num = 1.735f;
			}
			_cachedInsertDuration = num;
			MelonLogger.Msg($"{"[CustomMusic]"} tape_insert.wav duration: {num:F3}s");
			return num;
		}

		public static void InvalidateCache()
		{
			_cachedInsertDuration = -1f;
		}

		public static void PlayInsert()
		{
			if (Cfg.CassetteSfx.Value)
			{
				Play("tape_insert.wav");
			}
		}

		public static void PlayEject()
		{
			if (Cfg.CassetteSfx.Value)
			{
				Play("tape_eject.wav");
			}
		}

		private static void Play(string filename)
		{
			try
			{
				if (!string.IsNullOrEmpty(TrackLibrary.Root))
				{
					string text = Path.Combine(Path.Combine(TrackLibrary.Root, "sfx"), filename);
					if (!File.Exists(text))
					{
						text = Path.Combine(TrackLibrary.Root, filename);
					}
					if (File.Exists(text))
					{
						PlaySoundW(text, IntPtr.Zero, 3u);
					}
				}
			}
			catch (Exception ex)
			{
				MelonLogger.Warning($"{"[CustomMusic]"} SFX error '{filename}': {ex.Message}");
			}
		}

		private static float ReadWavDuration(string filename)
		{
			try
			{
				if (string.IsNullOrEmpty(TrackLibrary.Root))
				{
					return 0f;
				}
				string path = Path.Combine(TrackLibrary.Root, "sfx", filename);
				if (!File.Exists(path))
				{
					path = Path.Combine(TrackLibrary.Root, filename);
				}
				if (!File.Exists(path))
				{
					return 0f;
				}
				using FileStream fileStream = new FileStream(path, FileMode.Open, FileAccess.Read, FileShare.Read);
				using BinaryReader binaryReader = new BinaryReader(fileStream);
				if (fileStream.Length < 44)
				{
					return 0f;
				}
				byte[] bytes = binaryReader.ReadBytes(4);
				if (Encoding.ASCII.GetString(bytes) != "RIFF")
				{
					return 0f;
				}
				binaryReader.ReadInt32();
				byte[] bytes2 = binaryReader.ReadBytes(4);
				if (Encoding.ASCII.GetString(bytes2) != "WAVE")
				{
					return 0f;
				}
				int num = 0;
				int num2 = 0;
				int num3 = 0;
				while (fileStream.Position + 8 <= fileStream.Length)
				{
					byte[] bytes3 = binaryReader.ReadBytes(4);
					int num4 = binaryReader.ReadInt32();
					if (num4 < 0)
					{
						break;
					}
					string text = Encoding.ASCII.GetString(bytes3);
					long val = fileStream.Position + num4 + (num4 & 1);
					if (text == "fmt ")
					{
						binaryReader.ReadInt16();
						binaryReader.ReadInt16();
						num = binaryReader.ReadInt32();
						binaryReader.ReadInt32();
						num2 = binaryReader.ReadInt16();
					}
					else if (text == "data")
					{
						num3 = num4;
						break;
					}
					fileStream.Position = Math.Min(val, fileStream.Length);
				}
				if (num > 0 && num2 > 0 && num3 > 0)
				{
					return (float)num3 / (float)(num * num2);
				}
			}
			catch
			{
			}
			return 0f;
		}
	}
	public static class Cfg
	{
		public const string Category = "CustomMusic";

		private const int Schema = 2;

		public static MelonPreferences_Category Prefs;

		public static MelonPreferences_Entry<bool> Enabled;

		public static MelonPreferences_Entry<bool> ReplaceStock;

		public static MelonPreferences_Entry<bool> Autoplay;

		public static MelonPreferences_Entry<bool> Shuffle;

		public static MelonPreferences_Entry<int> Loop;

		public static MelonPreferences_Entry<bool> CassetteSfx;

		public static MelonPreferences_Entry<float> Crossfade;

		public static MelonPreferences_Entry<float> Volume;

		public static MelonPreferences_Entry<bool> Verbose;

		public static MelonPreferences_Entry<int> ConfigSchema;

		public static void Init()
		{
			Prefs = MelonPreferences.CreateCategory("CustomMusic", "Custom Music");
			ConfigSchema = Prefs.CreateEntry<int>("Schema", 2, "Config schema (do not edit)", (string)null, false, false, (ValueValidator)null, (string)null);
			Enabled = Prefs.CreateEntry<bool>("Enabled", true, "Mod enabled", (string)null, false, false, (ValueValidator)null, (string)null);
			ReplaceStock = Prefs.CreateEntry<bool>("ReplaceStock", true, "Silence the game's own music (menu, hub, missions)", (string)null, false, false, (ValueValidator)null, (string)null);
			Autoplay = Prefs.CreateEntry<bool>("Autoplay", true, "Start the playlist automatically once the game reaches the first scene (false = start paused)", (string)null, false, false, (ValueValidator)null, (string)null);
			Shuffle = Prefs.CreateEntry<bool>("Shuffle", false, "true = random order, false = play the list in file-name order (1, 2, 3, ...)", (string)null, false, false, (ValueValidator)null, (string)null);
			Loop = Prefs.CreateEntry<int>("Loop", 0, "Loop mode: 0 = Loop All (playlist), 1 = Loop Single (repeat track), 2 = Loop Off", (string)null, false, false, (ValueValidator)null, (string)null);
			CassetteSfx = Prefs.CreateEntry<bool>("CassetteSfx", true, "Play retro VHS cassette mechanical sounds on play, pause and stop", (string)null, false, false, (ValueValidator)null, (string)null);
			Crossfade = Prefs.CreateEntry<float>("Crossfade", 1f, "Fade time in seconds between tracks (0 = hard cut)", (string)null, false, false, (ValueValidator)null, (string)null);
			Volume = Prefs.CreateEntry<float>("Volume", 1f, "Extra volume multiplier on top of the game's Music slider (0..1)", (string)null, false, false, (ValueValidator)null, (string)null);
			Verbose = Prefs.CreateEntry<bool>("Verbose", true, "Log every track change and every blocked stock-music call", (string)null, false, false, (ValueValidator)null, (string)null);
			if (ConfigSchema.Value != 2)
			{
				MelonLogger.Msg("[CustomMusic] config schema changed, rewriting defaults");
				ConfigSchema.Value = 2;
			}
			MelonPreferences.Save();
		}
	}
	public class CustomMusicMod : MelonMod
	{
		public const string Tag = "[CustomMusic]";

		internal static PreparedTrack Current;

		internal static string CurrentName;

		internal static bool WantPlay;

		internal static bool Paused;

		private static readonly Random Rng;

		private static readonly HashSet<string> Bad;

		private static int _index;

		private static bool _loading;

		private static bool _waitingForInsert;

		private static bool _playbackActive;

		private static bool _booted;

		private static float _fade;

		private static float _fadeTarget;

		private static float _fadeTime;

		private static float _retryAt;

		private static float _playStartedAt;

		private static float _nextVolume;

		private static float _nextGameVolume;

		private static float _nextStatusLog;

		private static bool _uiProbed;

		private static float _nextEnsure;

		private static bool _resolverRegistered;

		public static bool IsPlaying
		{
			get
			{
				if (MciPlayer.IsOpen)
				{
					return MciPlayer.Mode == "playing";
				}
				return false;
			}
		}

		public static bool IsWaitingForInsert => _waitingForInsert;

		public static int PositionMs => MciPlayer.PositionMs;

		public static int LengthMs => MciPlayer.LengthMs;

		public static int CurrentIndex => _index;

		public static bool IsLoading => _loading;

		public static int BadCount => Bad.Count;

		static CustomMusicMod()
		{
			Rng = new Random();
			Bad = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
			_index = -1;
			_fade = 1f;
			_fadeTarget = 1f;
			_fadeTime = 0.25f;
			_retryAt = -1f;
			RegisterAssemblyResolver();
		}

		public override void OnEarlyInitializeMelon()
		{
			RegisterAssemblyResolver();
		}

		private static void RegisterAssemblyResolver()
		{
			if (!_resolverRegistered)
			{
				_resolverRegistered = true;
				AppDomain.CurrentDomain.AssemblyResolve += ResolveEmbeddedDependency;
			}
		}

		private static Assembly ResolveEmbeddedDependency(object sender, ResolveEventArgs args)
		{
			try
			{
				string name = new AssemblyName(args.Name).Name;
				if (string.Equals(name, "NLayer", StringComparison.OrdinalIgnoreCase) || string.Equals(name, "NVorbis", StringComparison.OrdinalIgnoreCase))
				{
					Assembly assembly = typeof(CustomMusicMod).Assembly;
					string text = "CustomMusic.libs." + name + ".dll";
					using (Stream stream = assembly.GetManifestResourceStream(text))
					{
						if (stream != null)
						{
							byte[] array = new byte[stream.Length];
							int num;
							for (int i = 0; i < array.Length; i += num)
							{
								num = stream.Read(array, i, array.Length - i);
								if (num <= 0)
								{
									break;
								}
							}
							MelonLogger.Msg($"{"[CustomMusic]"} successfully loaded embedded '{name}' ({array.Length} bytes)");
							return Assembly.Load(array);
						}
					}
					MelonLogger.Warning("[CustomMusic] embedded resource '" + text + "' is missing");
				}
			}
			catch (Exception ex)
			{
				MelonLogger.Warning($"{"[CustomMusic]"} ResolveEmbeddedDependency for {args.Name} failed: {ex.Message}");
			}
			return null;
		}

		public override void OnInitializeMelon()
		{
			RegisterAssemblyResolver();
			Cfg.Init();
			TrackLibrary.Root = Path.Combine(MelonEnvironment.UserDataDirectory, "CustomMusic");
			TrackLibrary.EnsureStarterFiles();
			TrackLibrary.Scan();
			MelonLogger.Msg("[CustomMusic] folder: " + TrackLibrary.Root);
			MelonLogger.Msg($"{"[CustomMusic]"} tracks: {TrackLibrary.Files.Count}");
			foreach (string file in TrackLibrary.Files)
			{
				MelonLogger.Msg("[CustomMusic]   " + Path.GetFileName(file));
			}
			PatchStock();
			PatchTabs();
			PatchClicks();
			MelonLogger.Msg($"{"[CustomMusic]"} ready (replaceStock={Cfg.ReplaceStock.Value}, shuffle={Cfg.Shuffle.Value})");
		}

		private void PatchTabs()
		{
			//IL_004a: Unknown result type (might be due to invalid IL or missing references)
			//IL_0057: Expected O, but got Unknown
			try
			{
				MethodInfo method = typeof(MenuUIManager).GetMethod("ChangeActiveTab", BindingFlags.Instance | BindingFlags.Public);
				if (method == null)
				{
					MelonLogger.Warning("[CustomMusic] MenuUIManager.ChangeActiveTab not found - settings plate will still work through pointer handlers");
					return;
				}
				((MelonBase)this).HarmonyInstance.Patch((MethodBase)method, (HarmonyMethod)null, new HarmonyMethod(typeof(CustomMusicMod).GetMethod("ChangeActiveTabPostfix", BindingFlags.Static | BindingFlags.NonPublic)), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null);
				MelonLogger.Msg("[CustomMusic] patched MenuUIManager.ChangeActiveTab");
			}
			catch (Exception ex)
			{
				MelonLogger.Warning("[CustomMusic] ChangeActiveTab patch failed (plate will use pointer handlers only): " + ex.Message);
			}
		}

		private static void ChangeActiveTabPostfix(int tabIndex)
		{
			MusicPanel.Hide();
		}

		private void PatchClicks()
		{
			//IL_005f: Unknown result type (might be due to invalid IL or missing references)
			//IL_006c: Expected O, but got Unknown
			try
			{
				MethodInfo method = typeof(Button).GetMethod("OnPointerClick", BindingFlags.Instance | BindingFlags.Public, null, new Type[1] { typeof(PointerEventData) }, null);
				if (method == null)
				{
					MelonLogger.Warning("[CustomMusic] Button.OnPointerClick not found - settings plate buttons will not respond");
					return;
				}
				((MelonBase)this).HarmonyInstance.Patch((MethodBase)method, (HarmonyMethod)null, new HarmonyMethod(typeof(CustomMusicMod).GetMethod("OnPointerClickPostfix", BindingFlags.Static | BindingFlags.NonPublic)), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null);
				MelonLogger.Msg("[CustomMusic] patched Button.OnPointerClick");
			}
			catch (Exception ex)
			{
				MelonLogger.Warning("[CustomMusic] Button.OnPointerClick patch failed: " + ex.Message);
			}
		}

		private static void OnPointerClickPostfix(Button __instance)
		{
			try
			{
				MusicPanel.OnButtonClicked(__instance);
			}
			catch (Exception ex)
			{
				MelonLogger.Warning("[CustomMusic] click dispatch failed: " + ex.Message);
			}
		}

		private void PatchStock()
		{
			//IL_0049: Unknown result type (might be due to invalid IL or missing references)
			//IL_0057: Expected O, but got Unknown
			try
			{
				MethodInfo method = typeof(MusicManager).GetMethod("StartMusic", BindingFlags.Instance | BindingFlags.Public);
				if (method == null)
				{
					MelonLogger.Warning("[CustomMusic] MusicManager.StartMusic not found - stock music will keep playing");
					return;
				}
				((MelonBase)this).HarmonyInstance.Patch((MethodBase)method, new HarmonyMethod(typeof(CustomMusicMod).GetMethod("StartMusicPrefix", BindingFlags.Static | BindingFlags.NonPublic)), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null);
				MelonLogger.Msg("[CustomMusic] patched MusicManager.StartMusic");
			}
			catch (Exception value)
			{
				MelonLogger.Error($"{"[CustomMusic]"} patch failed: {value}");
			}
		}

		private static bool StartMusicPrefix(bool __0)
		{
			if (!Cfg.Enabled.Value || !Cfg.ReplaceStock.Value)
			{
				return true;
			}
			if (TrackLibrary.Files.Count == 0)
			{
				return true;
			}
			if (Cfg.Verbose.Value)
			{
				MelonLogger.Msg($"{"[CustomMusic]"} blocked stock StartMusic(isMainMenu={__0})");
			}
			return false;
		}

		public override void OnUpdate()
		{
			if (Time.unscaledTime >= _nextEnsure)
			{
				_nextEnsure = Time.unscaledTime + 1f;
				MusicPanel.Ensure();
			}
			MusicPanel.Tick();
			if (Time.unscaledTime >= _nextGameVolume)
			{
				_nextGameVolume = Time.unscaledTime + 1f;
				MusicPanel.UpdateGameVolume();
			}
			MciPlayer.Poll();
			if (!Cfg.Enabled.Value)
			{
				if (MciPlayer.IsOpen)
				{
					Stop();
				}
				return;
			}
			UpdateFade();
			if (Cfg.Verbose.Value && MciPlayer.IsOpen && Time.unscaledTime >= _nextStatusLog)
			{
				_nextStatusLog = Time.unscaledTime + 10f;
				MelonLogger.Msg($"{"[CustomMusic]"} mci mode={MciPlayer.Mode} pos={MciPlayer.PositionMs}ms len={MciPlayer.LengthMs}ms");
			}
			if (!WantPlay || _loading || _waitingForInsert || Paused || Time.unscaledTime < _retryAt)
			{
				return;
			}
			if (!MciPlayer.IsOpen)
			{
				Next();
			}
			else if (_playbackActive && MciPlayer.Mode == "stopped")
			{
				bool flag = Time.unscaledTime - _playStartedAt > 2f;
				if ((MciPlayer.LengthMs > 0 && MciPlayer.PositionMs >= MciPlayer.LengthMs - 1500) || (MciPlayer.LengthMs <= 0 && flag) || (flag && MciPlayer.PositionMs == 0))
				{
					_playbackActive = false;
					MelonLogger.Msg($"{"[CustomMusic]"} track reached end (pos={MciPlayer.PositionMs}ms, len={MciPlayer.LengthMs}ms), moving to next");
					OnTrackFinished();
				}
			}
		}

		public override void OnApplicationQuit()
		{
			MciPlayer.Close();
		}

		private static void UpdateFade()
		{
			_fade = Mathf.MoveTowards(_fade, _fadeTarget, Time.unscaledDeltaTime / Mathf.Max(0.01f, _fadeTime));
			if (MciPlayer.IsOpen && !(Time.unscaledTime < _nextVolume))
			{
				_nextVolume = Time.unscaledTime + 0.05f;
				MciPlayer.SetVolume(Mathf.Clamp01(Cfg.Volume.Value) * MusicPanel.GameMusicVolume * _fade);
			}
		}

		public override void OnSceneWasInitialized(int buildIndex, string sceneName)
		{
			MelonLogger.Msg("[CustomMusic] scene '" + sceneName + "'");
			if (!_booted)
			{
				_booted = true;
				if (TrackLibrary.Files.Count > 0)
				{
					int idx = (Cfg.Shuffle.Value ? Rng.Next(TrackLibrary.Files.Count) : 0);
					if (Cfg.Autoplay.Value)
					{
						WantPlay = true;
						MelonCoroutines.Start(LoadAndPlay(idx, startPlaying: true));
					}
					else
					{
						WantPlay = false;
						Paused = true;
						MelonCoroutines.Start(LoadAndPlay(idx, startPlaying: false));
					}
				}
			}
			if (WantPlay && !_loading && !MciPlayer.IsOpen)
			{
				Next();
			}
			if (!_uiProbed)
			{
				MelonCoroutines.Start(ProbeLater(6f));
			}
		}

		private static IEnumerator ProbeLater(float delay)
		{
			yield return (object)new WaitForSecondsRealtime(delay);
			_uiProbed = true;
			MusicPanel.Ensure();
		}

		public static void Next()
		{
			int num = PickNext();
			if (num >= 0)
			{
				SwitchTrack(num);
			}
		}

		public static void Prev()
		{
			int num = PickPrev();
			if (num >= 0)
			{
				SwitchTrack(num);
			}
		}

		public static void SelectTrack(int idx)
		{
			if (idx >= 0 && idx < TrackLibrary.Files.Count)
			{
				SwitchTrack(idx);
			}
		}

		public static void SwitchTrack(int idx)
		{
			if (idx >= 0 && idx < TrackLibrary.Files.Count && !_loading)
			{
				bool num = (MciPlayer.IsOpen && MciPlayer.Mode == "playing") || _waitingForInsert || (WantPlay && !Paused);
				_waitingForInsert = false;
				_playbackActive = false;
				WantPlay = false;
				Paused = true;
				if (MciPlayer.IsOpen)
				{
					MciPlayer.Pause();
				}
				if (num)
				{
					CassetteSfx.PlayEject();
				}
				MelonCoroutines.Start(LoadAndPlay(idx, startPlaying: false));
			}
		}

		public static void TogglePlay()
		{
			if (!MciPlayer.IsOpen)
			{
				Resume();
			}
			else if (MciPlayer.Mode == "playing" || _waitingForInsert || (WantPlay && !Paused))
			{
				_waitingForInsert = false;
				_playbackActive = false;
				MciPlayer.Pause();
				Paused = true;
				WantPlay = false;
				CassetteSfx.PlayEject();
				MelonLogger.Msg("[CustomMusic] paused");
			}
			else
			{
				Paused = false;
				WantPlay = true;
				_waitingForInsert = true;
				_playbackActive = false;
				CassetteSfx.PlayInsert();
				float insertDuration = CassetteSfx.GetInsertDuration();
				MelonCoroutines.Start(DelayedPlay(insertDuration));
				MelonLogger.Msg($"{"[CustomMusic]"} starting: waiting {insertDuration:F3}s for tape_insert sfx");
			}
		}

		private static IEnumerator DelayedPlay(float delay)
		{
			_waitingForInsert = true;
			_playbackActive = false;
			yield return (object)new WaitForSecondsRealtime(delay);
			_waitingForInsert = false;
			if (WantPlay && !Paused && MciPlayer.IsOpen)
			{
				_fade = 0f;
				_fadeTarget = 1f;
				_fadeTime = Mathf.Max(0.01f, Cfg.Crossfade.Value);
				_nextVolume = 0f;
				MciPlayer.SetVolume(0f);
				if (MciPlayer.Mode == "paused")
				{
					MciPlayer.Resume();
				}
				else
				{
					MciPlayer.Play();
				}
				_playStartedAt = Time.unscaledTime;
				_playbackActive = true;
				MelonLogger.Msg("[CustomMusic] track playing after tape insert finished");
			}
		}

		private static void OnTrackFinished()
		{
			if (Cfg.Loop.Value == 1)
			{
				if (_index >= 0 && _index < TrackLibrary.Files.Count && !Bad.Contains(TrackLibrary.Files[_index]))
				{
					MelonCoroutines.Start(LoadAndPlay(_index, startPlaying: true));
					return;
				}
			}
			else if (Cfg.Loop.Value == 2 && !Cfg.Shuffle.Value && _index >= TrackLibrary.Files.Count - 1)
			{
				Stop();
				MelonLogger.Msg("[CustomMusic] playlist finished (Loop OFF)");
				return;
			}
			int num = PickNext();
			if (num >= 0)
			{
				MelonCoroutines.Start(LoadAndPlay(num, startPlaying: true));
			}
			else
			{
				Stop();
			}
		}

		public static void CycleLoop()
		{
			int num = (Cfg.Loop.Value + 1) % 3;
			Cfg.Loop.Value = num;
			MelonPreferences.Save();
			MelonLogger.Msg("[CustomMusic] loop=" + LoopModeName(num));
		}

		public static string LoopModeName(int mode)
		{
			return mode switch
			{
				1 => "ONE", 
				2 => "OFF", 
				_ => "ALL", 
			};
		}

		public static void ToggleShuffle()
		{
			Cfg.Shuffle.Value = !Cfg.Shuffle.Value;
			MelonPreferences.Save();
			MelonLogger.Msg("[CustomMusic] shuffle=" + (Cfg.Shuffle.Value ? "on" : "off"));
		}

		public static void ReloadLibrary()
		{
			TrackLibrary.Scan();
			Bad.Clear();
			CassetteSfx.InvalidateCache();
			MelonLogger.Msg($"{"[CustomMusic]"} rescan: {TrackLibrary.Files.Count} track(s)");
			if (WantPlay && !_loading && !MciPlayer.IsOpen)
			{
				Next();
			}
		}

		public static void Stop()
		{
			bool num = (MciPlayer.IsOpen && MciPlayer.Mode == "playing") || _waitingForInsert || (WantPlay && !Paused);
			_waitingForInsert = false;
			_playbackActive = false;
			WantPlay = false;
			Paused = true;
			_fade = 0f;
			_fadeTarget = 0f;
			MciPlayer.Stop();
			MciPlayer.Close();
			if (num)
			{
				CassetteSfx.PlayEject();
			}
			Current = null;
			CurrentName = null;
			MelonLogger.Msg("[CustomMusic] stopped");
		}

		public static void Resume()
		{
			WantPlay = true;
			Paused = false;
			if (!MciPlayer.IsOpen)
			{
				if (!_loading)
				{
					int num = ((_index >= 0) ? _index : PickNext());
					if (num >= 0)
					{
						PlayIndex(num);
					}
				}
			}
			else
			{
				_waitingForInsert = true;
				_playbackActive = false;
				CassetteSfx.PlayInsert();
				float insertDuration = CassetteSfx.GetInsertDuration();
				_playStartedAt = Time.unscaledTime;
				MelonCoroutines.Start(DelayedPlay(insertDuration));
			}
		}

		private static int PickPrev()
		{
			int count = TrackLibrary.Files.Count;
			if (count == 0 || Bad.Count >= count)
			{
				return -1;
			}
			int num = ((_index < 0) ? count : _index);
			for (int i = 1; i <= count; i++)
			{
				int num2 = ((num - i) % count + count) % count;
				if (!Bad.Contains(TrackLibrary.Files[num2]))
				{
					return num2;
				}
			}
			return -1;
		}

		private static int PickNext()
		{
			int count = TrackLibrary.Files.Count;
			if (count == 0 || Bad.Count >= count)
			{
				return -1;
			}
			if (count == 1)
			{
				if (!Bad.Contains(TrackLibrary.Files[0]))
				{
					return 0;
				}
				return -1;
			}
			if (!Cfg.Shuffle.Value)
			{
				for (int i = 1; i <= count; i++)
				{
					int num = (_index + i) % count;
					if (!Bad.Contains(TrackLibrary.Files[num]))
					{
						return num;
					}
				}
			}
			else
			{
				for (int j = 0; j < count * 2; j++)
				{
					int num2 = Rng.Next(count);
					if ((num2 != _index || count <= 1) && !Bad.Contains(TrackLibrary.Files[num2]))
					{
						return num2;
					}
				}
			}
			for (int k = 0; k < count; k++)
			{
				if (!Bad.Contains(TrackLibrary.Files[k]))
				{
					return k;
				}
			}
			return -1;
		}

		public static void PlayIndex(int idx)
		{
			if (idx >= 0 && idx < TrackLibrary.Files.Count && !_loading)
			{
				WantPlay = true;
				Paused = false;
				_waitingForInsert = true;
				_playbackActive = false;
				CassetteSfx.PlayInsert();
				float insertDuration = CassetteSfx.GetInsertDuration();
				MelonCoroutines.Start(LoadAndPlayDelayed(idx, insertDuration));
			}
		}

		private static IEnumerator LoadAndPlayDelayed(int idx, float delay)
		{
			_loading = true;
			_waitingForInsert = true;
			_playbackActive = false;
			float startTime = Time.unscaledTime;
			string path = TrackLibrary.Files[idx];
			MelonLogger.Msg($"{"[CustomMusic]"} loading '{Path.GetFileName(path)}' (waiting for tape_insert {delay:F2}s)");
			MciPlayer.Close();
			PreparedTrack prepared = null;
			yield return TrackLibrary.Prepare(path, delegate(PreparedTrack p)
			{
				prepared = p;
			});
			_loading = false;
			if (prepared == null)
			{
				_waitingForInsert = false;
				Bad.Add(path);
				yield break;
			}
			if (!MciPlayer.Open(prepared.PlayPath, prepared.DeviceType))
			{
				_waitingForInsert = false;
				Bad.Add(path);
				yield break;
			}
			_index = idx;
			Current = prepared;
			CurrentName = Path.GetFileNameWithoutExtension(path);
			float num = Time.unscaledTime - startTime;
			float num2 = delay - num;
			if (num2 > 0f)
			{
				yield return (object)new WaitForSecondsRealtime(num2);
			}
			_waitingForInsert = false;
			if (WantPlay && !Paused && MciPlayer.IsOpen)
			{
				_fade = 0f;
				_fadeTarget = 1f;
				_fadeTime = Mathf.Max(0.01f, Cfg.Crossfade.Value);
				_nextVolume = 0f;
				MciPlayer.SetVolume(0f);
				MciPlayer.Play();
				_playStartedAt = Time.unscaledTime;
				_playbackActive = true;
				MelonLogger.Msg($"{"[CustomMusic]"} playing '{CurrentName}' after tape insert ({(double)MciPlayer.LengthMs / 1000.0:F1}s)");
			}
		}

		private static IEnumerator LoadAndPlay(int idx, bool startPlaying)
		{
			_loading = true;
			_waitingForInsert = false;
			_playbackActive = false;
			string path = TrackLibrary.Files[idx];
			MelonLogger.Msg($"{"[CustomMusic]"} loading '{Path.GetFileName(path)}' (startPlaying={startPlaying})");
			MciPlayer.Close();
			PreparedTrack prepared = null;
			yield return TrackLibrary.Prepare(path, delegate(PreparedTrack p)
			{
				prepared = p;
			});
			_loading = false;
			if (prepared == null)
			{
				Bad.Add(path);
				if (Bad.Count >= TrackLibrary.Files.Count)
				{
					WantPlay = false;
					MelonLogger.Warning($"{"[CustomMusic]"} all {TrackLibrary.Files.Count} track(s) failed to load - playback stopped");
				}
				else
				{
					MelonLogger.Warning("[CustomMusic] '" + Path.GetFileName(path) + "' skipped, trying next in 0.75s");
					_retryAt = Time.unscaledTime + 0.75f;
				}
				yield break;
			}
			if (!MciPlayer.Open(prepared.PlayPath, prepared.DeviceType))
			{
				Bad.Add(path);
				if (Bad.Count >= TrackLibrary.Files.Count)
				{
					WantPlay = false;
					MelonLogger.Warning($"{"[CustomMusic]"} all {TrackLibrary.Files.Count} track(s) failed to play - playback stopped");
				}
				else
				{
					MelonLogger.Warning("[CustomMusic] '" + Path.GetFileName(path) + "' skipped, trying next in 0.75s");
					_retryAt = Time.unscaledTime + 0.75f;
				}
				yield break;
			}
			_index = idx;
			Current = prepared;
			CurrentName = Path.GetFileNameWithoutExtension(path);
			if (startPlaying)
			{
				Paused = false;
				WantPlay = true;
				_fade = 0f;
				_fadeTarget = 1f;
				_fadeTime = Mathf.Max(0.01f, Cfg.Crossfade.Value);
				_nextVolume = 0f;
				MciPlayer.SetVolume(0f);
				MciPlayer.Play();
				_playStartedAt = Time.unscaledTime;
				_playbackActive = true;
				MelonLogger.Msg($"{"[CustomMusic]"} playing '{CurrentName}' ({(double)MciPlayer.LengthMs / 1000.0:F1}s, {MciPlayer.DeviceType})");
			}
			else
			{
				Paused = true;
				WantPlay = false;
				_playbackActive = false;
				_fade = 1f;
				_fadeTarget = 1f;
				MciPlayer.SetVolume(Mathf.Clamp01(Cfg.Volume.Value) * MusicPanel.GameMusicVolume);
				MciPlayer.Stop();
				MelonLogger.Msg($"{"[CustomMusic]"} loaded '{CurrentName}' on pause ({(double)MciPlayer.LengthMs / 1000.0:F1}s)");
			}
		}
	}
	internal static class MciPlayer
	{
		private const string Tag = "[CustomMusic]";

		private const string Alias = "cm_track";

		private static int _lastVolume = -1;

		private static float _nextPoll;

		public static bool IsOpen { get; private set; }

		public static string CurrentPath { get; private set; }

		public static string DeviceType { get; private set; }

		public static int LengthMs { get; private set; }

		public static int PositionMs { get; private set; }

		public static string Mode { get; private set; } = "stopped";

		public static bool VolumeSupported { get; private set; } = true;

		[DllImport("winmm.dll", CharSet = CharSet.Unicode)]
		private static extern int mciSendStringW(string command, StringBuilder ret, int retLen, IntPtr hwnd);

		[DllImport("winmm.dll", CharSet = CharSet.Unicode)]
		private static extern bool mciGetErrorStringW(int error, StringBuilder buf, int len);

		[DllImport("winmm.dll")]
		private static extern int waveOutSetVolume(IntPtr hwo, uint dwVolume);

		[DllImport("kernel32.dll", CharSet = CharSet.Unicode)]
		private static extern int GetShortPathNameW(string longPath, StringBuilder shortPath, int size);

		public static bool Open(string path, string preferredType)
		{
			Close();
			if (string.IsNullOrEmpty(path) || !File.Exists(path))
			{
				MelonLogger.Warning("[CustomMusic] MCI: file not found: " + path);
				return false;
			}
			string text = MciPath(path);
			string text2 = "\"" + text + "\"";
			string text3 = (string.IsNullOrEmpty(preferredType) ? "waveaudio" : preferredType);
			int num = Send($"open {text2} type {text3} alias {"cm_track"}");
			string value = ((num == 0) ? null : Describe(num));
			if (num != 0 && text3 != "waveaudio")
			{
				Send("close cm_track");
				int num2 = Send("open " + text2 + " type waveaudio alias cm_track");
				if (num2 == 0)
				{
					num = 0;
					text3 = "waveaudio";
				}
				else
				{
					value = Describe(num2);
				}
			}
			if (num != 0 && text3 != "mpegvideo")
			{
				Send("close cm_track");
				int num3 = Send("open " + text2 + " type mpegvideo alias cm_track");
				if (num3 == 0)
				{
					num = 0;
					text3 = "mpegvideo";
				}
				else
				{
					value = Describe(num3);
				}
			}
			if (num != 0)
			{
				Send("close cm_track");
				int num4 = Send("open " + text2 + " alias cm_track");
				if (num4 == 0)
				{
					num = 0;
					text3 = "auto";
				}
				else
				{
					value = Describe(num4);
				}
			}
			if (num != 0)
			{
				MelonLogger.Warning($"{"[CustomMusic]"} MCI open failed for '{Path.GetFileName(path)}': {value}");
				return false;
			}
			IsOpen = true;
			CurrentPath = path;
			DeviceType = text3;
			Mode = "stopped";
			PositionMs = 0;
			LengthMs = 0;
			VolumeSupported = true;
			_lastVolume = -1;
			_nextPoll = 0f;
			Send("set cm_track time format milliseconds");
			LengthMs = ParseInt(SendWithRet("status cm_track length"));
			MelonLogger.Msg($"{"[CustomMusic]"} MCI open '{Path.GetFileName(path)}' type={text3} length={LengthMs}ms");
			return true;
		}

		public static void Play()
		{
			if (IsOpen)
			{
				if (Mode == "stopped")
				{
					Send("seek cm_track to start");
				}
				int num = Send("play cm_track");
				if (num != 0)
				{
					MelonLogger.Warning("[CustomMusic] MCI play failed: " + Describe(num));
					return;
				}
				Mode = "playing";
				PositionMs = 0;
				_nextPoll = 0f;
			}
		}

		public static void Pause()
		{
			if (IsOpen && Send("pause cm_track") == 0)
			{
				Mode = "paused";
			}
		}

		public static void Resume()
		{
			if (IsOpen)
			{
				int num = Send("resume cm_track");
				if (num != 0)
				{
					num = Send("play cm_track");
				}
				if (num == 0)
				{
					Mode = "playing";
					_nextPoll = 0f;
				}
			}
		}

		public static void Stop()
		{
			if (IsOpen)
			{
				Send("stop cm_track");
				Send("seek cm_track to start");
				Mode = "stopped";
				PositionMs = 0;
			}
		}

		public static void Close()
		{
			if (IsOpen)
			{
				Send("close cm_track");
			}
			IsOpen = false;
			CurrentPath = null;
			DeviceType = null;
			LengthMs = 0;
			PositionMs = 0;
			Mode = "stopped";
			_lastVolume = -1;
			try
			{
				waveOutSetVolume(IntPtr.Zero, uint.MaxValue);
			}
			catch
			{
			}
		}

		public static void Poll()
		{
			if (!IsOpen || Time.unscaledTime < _nextPoll)
			{
				return;
			}
			_nextPoll = Time.unscaledTime + 0.2f;
			if (Send("status cm_track mode", out var ret) != 0)
			{
				Mode = "stopped";
				PositionMs = LengthMs;
				return;
			}
			ret = (ret ?? "").Trim();
			if (ret.Length > 0)
			{
				Mode = ret;
			}
			PositionMs = ParseInt(SendWithRet("status cm_track position"));
		}

		public static void SetVolume(float volume01)
		{
			if (!IsOpen)
			{
				return;
			}
			float num = Math.Clamp(volume01, 0f, 1f);
			int num2 = (int)Math.Round(num * 1000f);
			if (num2 == _lastVolume)
			{
				return;
			}
			_lastVolume = num2;
			int num3 = Send($"setaudio {"cm_track"} volume to {num2}");
			if (num3 != 0 && VolumeSupported && DeviceType == "mpegvideo")
			{
				VolumeSupported = false;
				MelonLogger.Warning("[CustomMusic] MCI setaudio volume unavailable: " + Describe(num3));
			}
			uint num4 = (uint)Math.Round(num * 65535f);
			uint dwVolume = (num4 & 0xFFFF) | (num4 << 16);
			try
			{
				waveOutSetVolume(IntPtr.Zero, dwVolume);
			}
			catch
			{
			}
		}

		private static string MciPath(string path)
		{
			try
			{
				StringBuilder stringBuilder = new StringBuilder(1024);
				int shortPathNameW = GetShortPathNameW(path, stringBuilder, stringBuilder.Capacity);
				if (shortPathNameW > 0 && shortPathNameW < stringBuilder.Capacity && stringBuilder.Length > 0)
				{
					return stringBuilder.ToString();
				}
			}
			catch
			{
			}
			return path;
		}

		private static int Send(string command)
		{
			string ret;
			return Send(command, out ret);
		}

		private static int Send(string command, out string ret)
		{
			StringBuilder stringBuilder = new StringBuilder(512);
			int result = mciSendStringW(command, stringBuilder, stringBuilder.Capacity, IntPtr.Zero);
			ret = stringBuilder.ToString();
			return result;
		}

		private static string SendWithRet(string command)
		{
			if (Send(command, out var ret) != 0)
			{
				return "";
			}
			return (ret ?? "").Trim();
		}

		private static int ParseInt(string s)
		{
			if (!int.TryParse((s ?? "").Trim(), out var result))
			{
				return 0;
			}
			return result;
		}

		private static string Describe(int rc)
		{
			StringBuilder stringBuilder = new StringBuilder(256);
			try
			{
				mciGetErrorStringW(rc, stringBuilder, stringBuilder.Capacity);
			}
			catch
			{
			}
			string text = stringBuilder.ToString();
			return $"MCI error {rc}{((text.Length > 0) ? (": " + text) : "")}";
		}
	}
	internal static class Mp3Decoder
	{
		public static DecodedAudio Decode(string path)
		{
			//IL_0001: Unknown result type (might be due to invalid IL or missing references)
			//IL_0007: Expected O, but got Unknown
			MpegFile val = new MpegFile(path);
			try
			{
				int channels = val.Channels;
				int sampleRate = val.SampleRate;
				if (channels <= 0 || sampleRate <= 0)
				{
					throw new InvalidDataException("bad MP3 stream");
				}
				FloatBuffer floatBuffer = new FloatBuffer(InitialCapacity(val.Length, channels));
				float[] array = new float[channels * 4096];
				int count;
				while ((count = val.ReadSamples(array, 0, array.Length)) > 0)
				{
					floatBuffer.AddRange(array, count);
				}
				if (floatBuffer.Count == 0)
				{
					throw new InvalidDataException("MP3 decoded to zero samples");
				}
				return new DecodedAudio
				{
					Name = Path.GetFileNameWithoutExtension(path),
					Channels = channels,
					SampleRate = sampleRate,
					Samples = floatBuffer.Finish()
				};
			}
			finally
			{
				((IDisposable)val)?.Dispose();
			}
		}

		private static int InitialCapacity(long length, int channels)
		{
			if (length <= 0)
			{
				return 1048576;
			}
			long num = length * channels;
			if (num <= 0)
			{
				return 1048576;
			}
			if (num > 33554432)
			{
				num = 33554432L;
			}
			return (int)num;
		}
	}
	public static class MusicPanel
	{
		private const string Tag = "[CustomMusic]";

		private const int KindPlayPause = 0;

		private const int KindNext = 1;

		private const int KindPrev = 2;

		private const int KindShuffle = 3;

		private const int KindStop = 4;

		private const int KindReload = 5;

		private const int KindLoop = 6;

		private const int KindToggleView = 7;

		private const int KindPagePrev = 8;

		private const int KindPageNext = 9;

		private const int KindVolDown = 10;

		private const int KindVolUp = 11;

		private const int KindVolReset = 12;

		private const int KindSlotBase = 20;

		private const int SlotsPerPage = 5;

		public static GameObject Panel;

		private static MenuUIManager _ui;

		private static Button _tabButton;

		private static Image _tabImage;

		private static Color _tabNormalColor = Color.white;

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

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

		private static readonly List<Image> StockTabImages = new List<Image>();

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

		private static int _tabId;

		private static bool _listenerErrorLogged;

		private static GameObject _playerView;

		private static GameObject _playlistView;

		private static bool _showingPlaylist;

		private static int _playlistPage;

		private static TextMeshProUGUI _trackText;

		private static TextMeshProUGUI _statusText;

		private static TextMeshProUGUI _shuffleText;

		private static TextMeshProUGUI _loopText;

		private static TextMeshProUGUI _playText;

		private static TextMeshProUGUI _volText;

		private static TextMeshProUGUI _viewPlaylistBtnText;

		private static TextMeshProUGUI _playlistTitle;

		private static readonly Button[] _slotButtons = (Button[])(object)new Button[5];

		private static readonly TextMeshProUGUI[] _slotTexts = (TextMeshProUGUI[])(object)new TextMeshProUGUI[5];

		private static Button _pagePrevBtn;

		private static Button _pageNextBtn;

		private static float _nextRefresh;

		private static float _nextAttempt;

		private static bool _buildErrorLogged;

		private static GameObject _buildingPanel;

		private static GameObject _buildingTab;

		public static float GameMusicVolume { get; private set; } = 1f;

		public static void UpdateGameVolume()
		{
			try
			{
				MenuUIManager ui = _ui;
				Slider val = (((Object)(object)ui != (Object)null) ? ui.musicVolumeSlider : null);
				if ((Object)(object)val != (Object)null)
				{
					float num = Mathf.Clamp01(val.normalizedValue);
					if (Mathf.Abs(num - GameMusicVolume) > 0.01f)
					{
						MelonLogger.Msg($"{"[CustomMusic]"} game music slider: {num:F2}");
					}
					GameMusicVolume = num;
				}
			}
			catch
			{
			}
		}

		public static void Ensure()
		{
			if ((Object)(object)Panel != (Object)null || Time.unscaledTime < _nextAttempt)
			{
				return;
			}
			MenuUIManager val = UiProbe.FindMenu();
			if ((Object)(object)val == (Object)null || (Object)(object)val.settingsTab == (Object)null || val.settingsTabs == null || ((Il2CppArrayBase<GameObject>)(object)val.settingsTabs).Length == 0 || val.settingsTabButtons == null || ((Il2CppArrayBase<Button>)(object)val.settingsTabButtons).Length == 0)
			{
				return;
			}
			try
			{
				Build(val);
				_buildErrorLogged = false;
			}
			catch (Exception value)
			{
				DestroyPartial();
				Panel = null;
				_nextAttempt = Time.unscaledTime + 10f;
				if (!_buildErrorLogged)
				{
					_buildErrorLogged = true;
					MelonLogger.Error($"{"[CustomMusic]"} settings plate build failed: {value}");
				}
			}
		}

		public static void Tick()
		{
			if (!((Object)(object)Panel == (Object)null) && Panel.activeInHierarchy)
			{
				if (AnyStockPanelActive())
				{
					Hide();
				}
				else if (!(Time.unscaledTime < _nextRefresh))
				{
					_nextRefresh = Time.unscaledTime + 0.25f;
					Refresh();
				}
			}
		}

		public static void Show()
		{
			//IL_006f: Unknown result type (might be due to invalid IL or missing references)
			//IL_00b7: Unknown result type (might be due to invalid IL or missing references)
			if ((Object)(object)Panel == (Object)null || (Object)(object)_ui == (Object)null)
			{
				return;
			}
			Il2CppReferenceArray<GameObject> settingsTabs = _ui.settingsTabs;
			if (settingsTabs != null)
			{
				for (int i = 0; i < ((Il2CppArrayBase<GameObject>)(object)settingsTabs).Length; i++)
				{
					GameObject val = ((Il2CppArrayBase<GameObject>)(object)settingsTabs)[i];
					if ((Object)(object)val != (Object)null)
					{
						val.SetActive(false);
					}
				}
			}
			for (int j = 0; j < StockTabImages.Count; j++)
			{
				Image val2 = StockTabImages[j];
				if ((Object)(object)val2 != (Object)null)
				{
					((Graphic)val2).color = _tabNormalColor;
				}
			}
			_showingPlaylist = false;
			UpdateViewVisibility();
			Panel.SetActive(true);
			if ((Object)(object)_tabImage != (Object)null)
			{
				((Graphic)_tabImage).color = _ui.activeColor;
			}
			Refresh();
		}

		public static void Hide()
		{
			//IL_002a: Unknown result type (might be due to invalid IL or missing references)
			if ((Object)(object)Panel != (Object)null)
			{
				Panel.SetActive(false);
			}
			if ((Object)(object)_tabImage != (Object)null)
			{
				((Graphic)_tabImage).color = _tabNormalColor;
			}
		}

		public static void OnButtonClicked(Button btn)
		{
			if ((Object)(object)btn == (Object)null || (Object)(object)Panel == (Object)null)
			{
				return;
			}
			GameObject gameObject = ((Component)btn).gameObject;
			if ((Object)(object)gameObject == (Object)null)
			{
				return;
			}
			int instanceID = ((Object)gameObject).GetInstanceID();
			if (!LastClick.TryGetValue(instanceID, out var value) || !(Time.unscaledTime - value < 0.35f))
			{
				LastClick[instanceID] = Time.unscaledTime;
				int value2;
				if (instanceID == _tabId)
				{
					Show();
				}
				else if (ButtonKinds.TryGetValue(instanceID, out value2))
				{
					Dispatch(value2);
				}
				else if (StockTabIds.Contains(instanceID))
				{
					Hide();
				}
			}
		}

		private static void UpdateViewVisibility()
		{
			if ((Object)(object)_playerView != (Object)null)
			{
				_playerView.SetActive(!_showingPlaylist);
			}
			if ((Object)(object)_playlistView != (Object)null)
			{
				_playlistView.SetActive(_showingPlaylist);
			}
		}

		private static void Dispatch(int kind)
		{
			switch (kind)
			{
			case 0:
				CustomMusicMod.TogglePlay();
				break;
			case 1:
				CustomMusicMod.Next();
				break;
			case 2:
				CustomMusicMod.Prev();
				break;
			case 3:
				CustomMusicMod.ToggleShuffle();
				break;
			case 6:
				CustomMusicMod.CycleLoop();
				break;
			case 4:
				CustomMusicMod.Stop();
				break;
			case 5:
			{
				CustomMusicMod.ReloadLibrary();
				int num3 = Math.Max(0, (TrackLibrary.Files.Count - 1) / 5);
				if (_playlistPage > num3)
				{
					_playlistPage = num3;
				}
				break;
			}
			case 7:
				_showingPlaylist = !_showingPlaylist;
				UpdateViewVisibility();
				break;
			case 8:
				if (_playlistPage > 0)
				{
					_playlistPage--;
				}
				break;
			case 9:
			{
				int num4 = Math.Max(0, (TrackLibrary.Files.Count - 1) / 5);
				if (_playlistPage < num4)
				{
					_playlistPage++;
				}
				break;
			}
			case 10:
				Cfg.Volume.Value = Mathf.Clamp(Cfg.Volume.Value - 0.1f, 0f, 1f);
				MelonPreferences.Save();
				MelonLogger.Msg($"{"[CustomMusic]"} volume={Mathf.RoundToInt(Cfg.Volume.Value * 100f)}%");
				break;
			case 11:
				Cfg.Volume.Value = Mathf.Clamp(Cfg.Volume.Value + 0.1f, 0f, 1f);
				MelonPreferences.Save();
				MelonLogger.Msg($"{"[CustomMusic]"} volume={Mathf.RoundToInt(Cfg.Volume.Value * 100f)}%");
				break;
			case 12:
				Cfg.Volume.Value = ((Cfg.Volume.Value > 0.05f) ? 0f : 1f);
				MelonPreferences.Save();
				MelonLogger.Msg($"{"[CustomMusic]"} volume={Mathf.RoundToInt(Cfg.Volume.Value * 100f)}%");
				break;
			case 20:
			case 21:
			case 22:
			case 23:
			case 24:
			{
				int num = kind - 20;
				int num2 = _playlistPage * 5 + num;
				if (num2 >= 0 && num2 < TrackLibrary.Files.Count)
				{
					CustomMusicMod.SelectTrack(num2);
				}
				break;
			}
			}
			Refresh();
		}

		private static bool AnyStockPanelActive()
		{
			Il2CppReferenceArray<GameObject> val = (((Object)(object)_ui != (Object)null) ? _ui.settingsTabs : null);
			if (val == null)
			{
				return false;
			}
			for (int i = 0; i < ((Il2CppArrayBase<GameObject>)(object)val).Length; i++)
			{
				GameObject val2 = ((Il2CppArrayBase<GameObject>)(object)val)[i];
				if ((Object)(object)val2 != (Object)null && val2.activeSelf)
				{
					return true;
				}
			}
			return false;
		}

		private static void Build(MenuUIManager ui)
		{
			//IL_008f: Unknown result type (might be due to invalid IL or missing references)
			//IL_009c: Unknown result type (might be due to invalid IL or missing references)
			//IL_00a9: Unknown result type (might be due to invalid IL or missing references)
			//IL_00be: Unknown result type (might be due to invalid IL or missing references)
			//IL_00d2: Unknown result type (might be due to invalid IL or missing references)
			//IL_01cf: Unknown result type (might be due to invalid IL or missing references)
			//IL_01da: Unknown result type (might be due to invalid IL or missing references)
			//IL_01ef: Unknown result type (might be due to invalid IL or missing references)
			//IL_01fa: Unknown result type (might be due to invalid IL or missing references)
			//IL_0204: Unknown result type (might be due to invalid IL or missing references)
			//IL_0287: Unknown result type (might be due to invalid IL or missing references)
			//IL_0292: Unknown result type (might be due to invalid IL or missing references)
			//IL_02a7: Unknown result type (might be due to invalid IL or missing references)
			//IL_02b2: Unknown result type (might be due to invalid IL or missing references)
			//IL_02bc: Unknown result type (might be due to invalid IL or missing references)
			//IL_0402: Unknown result type (might be due to invalid IL or missing references)
			//IL_0411: Unknown result type (might be due to invalid IL or missing references)
			//IL_0439: Unknown result type (might be due to invalid IL or missing references)
			//IL_0448: Unknown result type (might be due to invalid IL or missing references)
			//IL_02f7: Unknown result type (might be due to invalid IL or missing references)
			//IL_0306: Unknown result type (might be due to invalid IL or missing references)
			//IL_0338: Unknown result type (might be due to invalid IL or missing references)
			//IL_0347: Unknown result type (might be due to invalid IL or missing references)
			//IL_037d: Unknown result type (might be due to invalid IL or missing references)
			//IL_038c: Unknown result type (might be due to invalid IL or missing references)
			//IL_03c2: Unknown result type (might be due to invalid IL or missing references)
			//IL_03d1: Unknown result type (might be due to invalid IL or missing references)
			//IL_048b: Unknown result type (might be due to invalid IL or missing references)
			//IL_049a: Unknown result type (might be due to invalid IL or missing references)
			//IL_04c2: Unknown result type (might be due to invalid IL or missing references)
			//IL_04d1: Unknown result type (might be due to invalid IL or missing references)
			//IL_0514: Unknown result type (might be due to invalid IL or missing references)
			//IL_0523: Unknown result type (might be due to invalid IL or missing references)
			//IL_0566: Unknown result type (might be due to invalid IL or missing references)
			//IL_0575: Unknown result type (might be due to invalid IL or missing references)
			//IL_059e: Unknown result type (might be due to invalid IL or missing references)
			//IL_05ad: Unknown result type (might be due to invalid IL or missing references)
			//IL_05f1: Unknown result type (might be due to invalid IL or missing references)
			//IL_0600: Unknown result type (might be due to invalid IL or missing references)
			//IL_0629: Unknown result type (might be due to invalid IL or missing references)
			//IL_0638: Unknown result type (might be due to invalid IL or missing references)
			//IL_0660: Unknown result type (might be due to invalid IL or missing references)
			//IL_066f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0697: Unknown result type (might be due to invalid IL or missing references)
			//IL_06a6: Unknown result type (might be due to invalid IL or missing references)
			//IL_06f7: Unknown result type (might be due to invalid IL or missing references)
			//IL_0706: Unknown result type (might be due to invalid IL or missing references)
			//IL_073c: Unknown result type (might be due to invalid IL or missing references)
			//IL_074b: Unknown result type (might be due to invalid IL or missing references)
			//IL_07da: Unknown result type (might be due to invalid IL or missing references)
			//IL_07e9: Unknown result type (might be due to invalid IL or missing references)
			//IL_0882: Unknown result type (might be due to invalid IL or missing references)
			//IL_0891: Unknown result type (might be due to invalid IL or missing references)
			//IL_08bd: Unknown result type (might be due to invalid IL or missing references)
			//IL_08cc: Unknown result type (might be due to invalid IL or missing references)
			//IL_08f4: Unknown result type (might be due to invalid IL or missing references)
			//IL_0903: Unknown result type (might be due to invalid IL or missing references)
			//IL_0844: Unknown result type (might be due to invalid IL or missing references)
			//IL_0a41: Unknown result type (might be due to invalid IL or missing references)
			//IL_0a50: Unknown result type (might be due to invalid IL or missing references)
			//IL_095b: Unknown result type (might be due to invalid IL or missing references)
			//IL_097b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0aa5: Unknown result type (might be due to invalid IL or missing references)
			//IL_0a08: Unknown result type (might be due to invalid IL or missing references)
			//IL_0a0d: Unknown result type (might be due to invalid IL or missing references)
			_ui = ui;
			GameObject settingsTab = ui.settingsTab;
			GameObject gameObject = ((Il2CppArrayBase<GameObject>)(object)ui.settingsTabs)[0].gameObject;
			GameObject gameObject2 = ((Component)((Il2CppArrayBase<Button>)(object)ui.settingsTabButtons)[0]).gameObject;
			ButtonKinds.Clear();
			StockTabIds.Clear();
			StockTabImages.Clear();
			LastClick.Clear();
			_tabId = 0;
			GameObject val = (_buildingPanel = Object.Instantiate<GameObject>(gameObject, settingsTab.transform, false));
			((Object)val).name = "CustomMusicSettings";
			RectTransform component = gameObject.GetComponent<RectTransform>();
			RectTransform component2 = val.GetComponent<RectTransform>();
			component2.anchorMin = component.anchorMin;
			component2.anchorMax = component.anchorMax;
			component2.pivot = component.pivot;
			component2.sizeDelta = new Vector2(340.16f, 433.31f);
			component2.anchoredPosition = new Vector2(-2.98f, 1.32f);
			for (int num = val.transform.childCount - 1; num >= 0; num--)
			{
				Object.DestroyImmediate((Object)(object)((Component)val.transform.GetChild(num)).gameObject);
			}
			Transform val2 = settingsTab.transform.Find("SettingsLabel/SettingsText");
			GameObject val3 = (((Object)(object)val2 != (Object)null) ? ((Component)val2).gameObject : null);
			if ((Object)(object)val3 == (Object)null)
			{
				TextMeshProUGUI componentInChildren = settingsTab.GetComponentInChildren<TextMeshProUGUI>(true);
				if ((Object)(object)componentInChildren != (Object)null)
				{
					val3 = ((Component)componentInChildren).gameObject;
				}
			}
			GameObject val4 = Object.Instantiate<GameObject>(gameObject, val.transform, false);
			((Object)val4).name = "PlayerView";
			for (int num2 = val4.transform.childCount - 1; num2 >= 0; num2--)
			{
				Object.DestroyImmediate((Object)(object)((Component)val4.transform.GetChild(num2)).gameObject);
			}
			Image component3 = val4.GetComponent<Image>();
			if ((Object)(object)component3 != (Object)null)
			{
				Object.DestroyImmediate((Object)(object)component3);
			}
			RectTransform component4 = val4.GetComponent<RectTransform>();
			component4.anchorMin = Vector2.zero;
			component4.anchorMax = Vector2.one;
			component4.pivot = new Vector2(0.5f, 0.5f);
			component4.sizeDelta = Vector2.zero;
			component4.anchoredPosition = Vector2.zero;
			_playerView = val4;
			GameObject val5 = Object.Instantiate<GameObject>(gameObject, val.transform, false);
			((Object)val5).name = "PlaylistView";
			for (int num3 = val5.transform.childCount - 1; num3 >= 0; num3--)
			{
				Object.DestroyImmediate((Object)(object)((Component)val5.transform.GetChild(num3)).gameObject);
			}
			Image component5 = val5.GetComponent<Image>();
			if ((Object)(object)component5 != (Object)null)
			{
				Object.DestroyImmediate((Object)(object)component5);
			}
			RectTransform component6 = val5.GetComponent<RectTransform>();
			component6.anchorMin = Vector2.zero;
			component6.anchorMax = Vector2.one;
			component6.pivot = new Vector2(0.5f, 0.5f);
			component6.sizeDelta = Vector2.zero;
			component6.anchoredPosition = Vector2.zero;
			_playlistView = val5;
			if ((Object)(object)val3 != (Object)null)
			{
				CreateLabel(val4.transform, val3, "CustomMusicTitle", "Custom Music", new Vector2(0f, 160f), new Vector2(320f, 32f), 24f, (TextAlignmentOptions)514);
				_trackText = CreateLabel(val4.transform, val3, "TrackLabel", "Track: —", new Vector2(0f, 126f), new Vector2(320f, 30f), 17f, (TextAlignmentOptions)514);
				_statusText = CreateLabel(val4.transform, val3, "StatusLabel", "Stopped", new Vector2(0f, 96f), new Vector2(320f, 26f), 14f, (TextAlignmentOptions)514);
				CreateLabel(val4.transform, val3, "HintLabel", "UserData\\CustomMusic  (mp3 / ogg / wav)", new Vector2(0f, -182f), new Vector2(330f, 26f), 11f, (TextAlignmentOptions)514);
			}
			CreateButton(val4.transform, gameObject2, "PrevButton", "<< Prev", new Vector2(-105f, 50f), new Vector2(90f, 40f), 2);
			Button val6 = CreateButton(val4.transform, gameObject2, "PlayPauseButton", "Play", new Vector2(0f, 50f), new Vector2(100f, 40f), 0);
			_playText = (((Object)(object)val6 != (Object)null) ? ((Component)val6).GetComponentInChildren<TextMeshProUGUI>(true) : null);
			CreateButton(val4.transform, gameObject2, "NextButton", "Next >>", new Vector2(105f, 50f), new Vector2(90f, 40f), 1);
			Button val7 = CreateButton(val4.transform, gameObject2, "ShuffleButton", "Shuffle: ON", new Vector2(-80f, 4f), new Vector2(145f, 40f), 3);
			_shuffleText = (((Object)(object)val7 != (Object)null) ? ((Component)val7).GetComponentInChildren<TextMeshProUGUI>(true) : null);
			Button val8 = CreateButton(val4.transform, gameObject2, "LoopButton", "Loop: ALL", new Vector2(80f, 4f), new Vector2(145f, 40f), 6);
			_loopText = (((Object)(object)val8 != (Object)null) ? ((Component)val8).GetComponentInChildren<TextMeshProUGUI>(true) : null);
			CreateButton(val4.transform, gameObject2, "VolDownButton", "Vol -", new Vector2(-105f, -42f), new Vector2(90f, 38f), 10);
			Button val9 = CreateButton(val4.transform, gameObject2, "VolButton", "Vol: 100%", new Vector2(0f, -42f), new Vector2(100f, 38f), 12);
			_volText = (((Object)(object)val9 != (Object)null) ? ((Component)val9).GetComponentInChildren<TextMeshProUGUI>(true) : null);
			CreateButton(val4.transform, gameObject2, "VolUpButton", "Vol +", new Vector2(105f, -42f), new Vector2(90f, 38f), 11);
			CreateButton(val4.transform, gameObject2, "StopButton", "Stop", new Vector2(-80f, -88f), new Vector2(145f, 38f), 4);
			CreateButton(val4.transform, gameObject2, "ReloadButton", "Reload folder", new Vector2(80f, -88f), new Vector2(145f, 38f), 5);
			Button val10 = CreateButton(val4.transform, gameObject2, "ViewPlaylistButton", "Playlist >>", new Vector2(0f, -134f), new Vector2(295f, 42f), 7);
			_viewPlaylistBtnText = (((Object)(object)val10 != (Object)null) ? ((Component)val10).GetComponentInChildren<TextMeshProUGUI>(true) : null);
			if ((Object)(object)val3 != (Object)null)
			{
				_playlistTitle = CreateLabel(val5.transform, val3, "PlaylistTitle", "Playlist", new Vector2(0f, 165f), new Vector2(320f, 34f), 20f, (TextAlignmentOptions)514);
				CreateLabel(val5.transform, val3, "PlaylistHint", "Click a track to play it immediately", new Vector2(0f, -192f), new Vector2(330f, 26f), 11f, (TextAlignmentOptions)514);
			}
			float[] array = new float[5] { 112f, 60f, 8f, -44f, -96f };
			for (int i = 0; i < 5; i++)
			{
				Button val11 = CreateButton(val5.transform, gameObject2, $"SlotButton_{i}", $"Track {i + 1}", new Vector2(0f, array[i]), new Vector2(305f, 42f), 20 + i);
				_slotButtons[i] = val11;
				if ((Object)(object)val11 != (Object)null)
				{
					TextMeshProUGUI componentInChildren2 = ((Component)val11).GetComponentInChildren<TextMeshProUGUI>(true);
					if ((Object)(object)componentInChildren2 != (Object)null)
					{
						((TMP_Text)componentInChildren2).alignment = (TextAlignmentOptions)513;
						((TMP_Text)componentInChildren2).margin = new Vector4(12f, 0f, 12f, 0f);
					}
					_slotTexts[i] = componentInChildren2;
				}
			}
			_pagePrevBtn = CreateButton(val5.transform, gameObject2, "PagePrevButton", "<< Page", new Vector2(-105f, -148f), new Vector2(90f, 42f), 8);
			CreateButton(val5.transform, gameObject2, "BackToPlayerButton", "Player", new Vector2(0f, -148f), new Vector2(100f, 42f), 7);
			_pageNextBtn = CreateButton(val5.transform, gameObject2, "PageNextButton", "Page >>", new Vector2(105f, -148f), new Vector2(90f, 42f), 9);
			Il2CppReferenceArray<Button> settingsTabButtons = ui.settingsTabButtons;
			for (int j = 0; j < ((Il2CppArrayBase<Button>)(object)settingsTabButtons).Length; j++)
			{
				Button val12 = ((Il2CppArrayBase<Button>)(object)settingsTabButtons)[j];
				if (!((Object)(object)val12 == (Object)null))
				{
					RectTransform component7 = ((Component)val12).GetComponent<RectTransform>();
					if ((Object)(object)component7 != (Object)null)
					{
						component7.sizeDelta = new Vector2(110f, 35.3f);
						component7.anchoredPosition = new Vector2(-180f + (float)j * 115f, 194.55f);
					}
					TextMeshProUGUI componentInChildren3 = ((Component)val12).GetComponentInChildren<TextMeshProUGUI>(true);
					if ((Object)(object)componentInChildren3 != (Object)null)
					{
						((TMP_Text)componentInChildren3).enableAutoSizing = true;
						((TMP_Text)componentInChildren3).fontSizeMin = 9f;
						((TMP_Text)componentInChildren3).fontSizeMax = 20f;
					}
					StockTabIds.Add(((Object)((Component)val12).gameObject).GetInstanceID());
					Image component8 = ((Component)val12).GetComponent<Image>();
					if ((Object)(object)component8 != (Object)null)
					{
						StockTabImages.Add(component8);
					}
					if (j == ((Il2CppArrayBase<Button>)(object)settingsTabButtons).Length - 1 && (Object)(object)component8 != (Object)null)
					{
						_tabNormalColor = ((Graphic)component8).color;
					}
				}
			}
			Button val13 = CreateButton(settingsTab.transform, gameObject2, "MusicTabButton", "Music", new Vector2(165f, 194.55f), new Vector2(110f, 35.3f), -1);
			if ((Object)(object)val13 != (Object)null)
			{
				((Selectable)val13).transition = (Transition)0;
				_tabButton = val13;
				_tabId = ((Object)((Component)val13).gameObject).GetInstanceID();
				_tabImage = ((Component)val13).GetComponent<Image>();
				if ((Object)(object)_tabImage != (Object)null)
				{
					((Graphic)_tabImage).color = _tabNormalColor;
				}
			}
			_buildingTab = (((Object)(object)val13 != (Object)null) ? ((Component)val13).gameObject : null);
			_buildingPanel = null;
			_buildingTab = null;
			Panel = val;
			_showingPlaylist = false;
			UpdateViewVisibility();
			val.SetActive(false);
			MelonLogger.Msg("[CustomMusic] settings plate built under '" + ((Object)settingsTab).name + "'");
		}

		private static Button CreateButton(Transform parent, GameObject template, string name, string label, Vector2 pos, Vector2 size, int kind)
		{
			//IL_0043: Unknown result type (might be due to invalid IL or missing references)
			//IL_0058: Unknown result type (might be due to invalid IL or missing references)
			//IL_006d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0078: Unknown result type (might be due to invalid IL or missing references)
			//IL_0080: Unknown result type (might be due to invalid IL or missing references)
			//IL_00b3: Unknown result type (might be due to invalid IL or missing references)
			//IL_00bd: Expected O, but got Unknown
			GameObject val = Object.Instantiate<GameObject>(template, parent, false);
			if ((Object)(object)val == (Object)null)
			{
				return null;
			}
			((Object)val).name = name;
			val.SetActive(true);
			RectTransform component = val.GetComponent<RectTransform>();
			if ((Object)(object)component != (Object)null)
			{
				component.anchorMin = new Vector2(0.5f, 0.5f);
				component.anchorMax = new Vector2(0.5f, 0.5f);
				component.pivot = new Vector2(0.5f, 0.5f);
				component.sizeDelta = size;
				component.anchoredPosition = pos;
			}
			Button btn = val.GetComponent<Button>();
			if ((Object)(object)btn == (Object)null)
			{
				btn = val.AddComponent<Button>();
			}
			btn.onClick = new ButtonClickedEvent();
			try
			{
				((UnityEvent)btn.onClick).AddListener(UnityAction.op_Implicit((Action)delegate
				{
					OnButtonClicked(btn);
				}));
			}
			catch (Exception ex)
			{
				if (!_listenerErrorLogged)
				{
					_listenerErrorLogged = true;
					MelonLogger.Warning("[CustomMusic] onClick listener unavailable (" + ex.Message + "); using Harmony clicks only");
				}
			}
			TextMeshProUGUI componentInChildren = val.GetComponentInChildren<TextMeshProUGUI>(true);
			if ((Object)(object)componentInChildren != (Object)null)
			{
				((TMP_Text)componentInChildren).text = label;
				((TMP_Text)componentInChildren).alignment = (TextAlignmentOptions)514;
				((TMP_Text)componentInChildren).enableAutoSizing = true;
				((TMP_Text)componentInChildren).fontSizeMin = 10f;
				((TMP_Text)componentInChildren).fontSizeMax = 20f;
				((Graphic)componentInChildren).raycastTarget = false;
			}
			if (kind >= 0)
			{
				ButtonKinds[((Object)val).GetInstanceID()] = kind;
			}
			return btn;
		}

		private static TextMeshProUGUI CreateLabel(Transform parent, GameObject template, string name, string text, Vector2 pos, Vector2 size, float fontSize, TextAlignmentOptions align)
		{
			//IL_003d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0052: Unknown result type (might be due to invalid IL or missing references)
			//IL_0067: Unknown result type (might be due to invalid IL or missing references)
			//IL_0072: Unknown result type (might be due to invalid IL or missing references)
			//IL_007a: Unknown result type (might be due to invalid IL or missing references)
			//IL_00a1: Unknown result type (might be due to invalid IL or missing references)
			GameObject val = Object.Instantiate<GameObject>(template, parent, false);
			if ((Object)(object)val == (Object)null)
			{
				return null;
			}
			((Object)val).name = name;
			val.SetActive(true);
			RectTransform component = val.GetComponent<RectTransform>();
			if ((Object)(object)component != (Object)null)
			{
				component.anchorMin = new Vector2(0.5f, 0.5f);
				component.anchorMax = new Vector2(0.5f, 0.5f);
				component.pivot = new Vector2(0.5f, 0.5f);
				component.sizeDelta = size;
				component.anchoredPosition = pos;
			}
			TextMeshProUGUI component2 = val.GetComponent<TextMeshProUGUI>();
			if ((Object)(object)component2 != (Object)null)
			{
				((TMP_Text)component2).text = text;
				((TMP_Text)component2).fontSize = fontSize;
				((TMP_Text)component2).alignment = align;
				((TMP_Text)component2).enableAutoSizing = false;
				((Graphic)component2).raycastTarget = false;
			}
			return component2;
		}

		private static void DestroyPartial()
		{
			if ((Object)(object)_buildingTab != (Object)null)
			{
				Object.DestroyImmediate((Object)(object)_buildingTab);
			}
			if ((Object)(object)_buildingPanel != (Object)null)
			{
				Object.DestroyImmediate((Object)(object)_buildingPanel);
			}
			_buildingTab = null;
			_buildingPanel = null;
			Panel = null;
			_playerView = null;
			_playlistView = null;
			_tabButton = null;
			_tabImage = null;
			_trackText = null;
			_statusText = null;
			_shuffleText = null;
			_loopText = null;
			_playText = null;
			_volText = null;
			_viewPlaylistBtnText = null;
			_playlistTitle = null;
			_pagePrevBtn = null;
			_pageNextBtn = null;
			for (int i = 0; i < 5; i++)
			{
				_slotButtons[i] = null;
				_slotTexts[i] = null;
			}
			ButtonKinds.Clear();
			StockTabIds.Clear();
			StockTabImages.Clear();
			LastClick.Clear();
		}

		private static void Refresh()
		{
			//IL_05bd: Unknown result type (might be due to invalid IL or missing references)
			//IL_05a7: Unknown result type (might be due to invalid IL or missing references)
			if ((Object)(object)_trackText != (Object)null)
			{
				PreparedTrack current = CustomMusicMod.Current;
				int currentIndex = CustomMusicMod.CurrentIndex;
				int count = TrackLibrary.Files.Count;
				if (current != null && currentIndex >= 0 && currentIndex < count)
				{
					((TMP_Text)_trackText).text = $"[{currentIndex + 1}/{count}] {CustomMusicMod.CurrentName}";
				}
				else if (current != null)
				{
					((TMP_Text)_trackText).text = "Track: " + CustomMusicMod.CurrentName;
				}
				else
				{
					((TMP_Text)_trackText).text = "Track: —";
				}
			}
			if ((Object)(object)_statusText != (Object)null)
			{
				if (!Cfg.Enabled.Value)
				{
					((TMP_Text)_statusText).text = "Mod disabled";
				}
				else if (TrackLibrary.Files.Count == 0)
				{
					((TMP_Text)_statusText).text = "No tracks in UserData\\CustomMusic";
				}
				else if (CustomMusicMod.IsLoading)
				{
					((TMP_Text)_statusText).text = "Loading...";
				}
				else if (CustomMusicMod.WantPlay && CustomMusicMod.IsPlaying)
				{
					int num = CustomMusicMod.PositionMs / 1000;
					int num2 = CustomMusicMod.LengthMs / 1000;
					string text = ((num2 > 0) ? $"{num / 60}:{num % 60:D2} / {num2 / 60}:{num2 % 60:D2}" : $"{num / 60}:{num % 60:D2}");
					((TMP_Text)_statusText).text = "Playing  " + text;
				}
				else if (CustomMusicMod.WantPlay && CustomMusicMod.Paused)
				{
					((TMP_Text)_statusText).text = "Paused";
				}
				else if (CustomMusicMod.WantPlay)
				{
					((TMP_Text)_statusText).text = "Starting...";
				}
				else
				{
					((TMP_Text)_statusText).text = "Stopped";
				}
				if (CustomMusicMod.BadCount > 0)
				{
					TextMeshProUGUI statusText = _statusText;
					((TMP_Text)statusText).text = ((TMP_Text)statusText).text + $" (bad: {CustomMusicMod.BadCount})";
				}
			}
			if ((Object)(object)_playText != (Object)null)
			{
				((TMP_Text)_playText).text = ((CustomMusicMod.IsPlaying || CustomMusicMod.IsWaitingForInsert) ? "Pause" : "Play");
			}
			if ((Object)(object)_volText != (Object)null)
			{
				((TMP_Text)_volText).text = $"Vol: {Mathf.RoundToInt(Cfg.Volume.Value * 100f)}%";
			}
			if ((Object)(object)_shuffleText != (Object)null)
			{
				((TMP_Text)_shuffleText).text = (Cfg.Shuffle.Value ? "Shuffle: ON" : "Shuffle: OFF");
			}
			if ((Object)(object)_loopText != (Object)null)
			{
				((TMP_Text)_loopText).text = "Loop: " + CustomMusicMod.LoopModeName(Cfg.Loop.Value);
			}
			if ((Object)(object)_viewPlaylistBtnText != (Object)null)
			{
				((TMP_Text)_viewPlaylistBtnText).text = $"Playlist ({TrackLibrary.Files.Count} tracks) >>";
			}
			if (!((Object)(object)_playlistTitle != (Object)null))
			{
				return;
			}
			int count2 = TrackLibrary.Files.Count;
			int num3 = Math.Max(0, (count2 - 1) / 5);
			if (_playlistPage > num3)
			{
				_playlistPage = num3;
			}
			if (_playlistPage < 0)
			{
				_playlistPage = 0;
			}
			((TMP_Text)_playlistTitle).text = ((count2 > 0) ? $"Playlist ({count2}) — Page {_playlistPage + 1}/{num3 + 1}" : "Playlist is empty");
			for (int i = 0; i < 5; i++)
			{
				int num4 = _playlistPage * 5 + i;
				Button val = _slotButtons[i];
				TextMeshProUGUI val2 = _slotTexts[i];
				if ((Object)(object)val == (Object)null)
				{
					continue;
				}
				if (num4 < count2)
				{
					((Component)val).gameObject.SetActive(true);
					string fileNameWithoutExtension = Path.GetFileNameWithoutExtension(TrackLibrary.Files[num4]);
					bool flag = num4 == CustomMusicMod.CurrentIndex;
					if ((Object)(object)val2 != (Object)null)
					{
						((TMP_Text)val2).text = (flag ? $"▶ {num4 + 1}. {fileNameWithoutExtension}" : $"  {num4 + 1}. {fileNameWithoutExtension}");
						((Graphic)val2).color = (Color)(flag ? new Color(1f, 0.85f, 0.2f) : Color.white);
					}
				}
				else
				{
					((Component)val).gameObject.SetActive(false);
				}
			}
			if ((Object)(object)_pagePrevBtn != (Object)null)
			{
				((Selectable)_pagePrevBtn).interactable = _playlistPage > 0;
			}
			if ((Object)(object)_pageNextBtn != (Object)null)
			{
				((Selectable)_pageNextBtn).interactable = _playlistPage < num3;
			}
		}
	}
	internal static class OggDecoder
	{
		public static DecodedAudio Decode(string path)
		{
			//IL_0001: Unknown result type (might be due to invalid IL or missing references)
			//IL_0007: Expected O, but got Unknown
			VorbisReader val = new VorbisReader(path);
			try
			{
				int channels = val.Channels;
				int sampleRate = val.SampleRate;
				if (channels <= 0 || sampleRate <= 0)
				{
					throw new InvalidDataException("bad OGG stream");
				}
				FloatBuffer floatBuffer = new FloatBuffer(InitialCapacity(val.TotalSamples, channels));
				float[] array = new float[channels * 4096];
				int count;
				while ((count = val.ReadSamples(array, 0, array.Length)) > 0)
				{
					floatBuffer.AddRange(array, count);
				}
				if (floatBuffer.Count == 0)
				{
					throw new InvalidDataException("OGG decoded to zero samples");
				}
				return new DecodedAudio
				{
					Name = Path.GetFileNameWithoutExtension(path),
					Channels = channels,
					SampleRate = sampleRate,
					Samples = floatBuffer.Finish()
				};
			}
			finally
			{
				((IDisposable)val)?.Dispose();
			}
		}

		private static int InitialCapacity(long totalSamples, int channels)
		{
			if (totalSamples <= 0)
			{
				return 1048576;
			}
			long num = totalSamples * channels;
			if (num <= 0)
			{
				return 1048576;
			}
			if (num > 33554432)
			{
				num = 33554432L;
			}
			return (int)num;
		}
	}
	internal static class TrackLibrary
	{
		private const string Tag = "[CustomMusic]";

		public static string Root;

		public static readonly List<string> Files = new List<string>();

		public static void EnsureStarterFiles()
		{
			try
			{
				if (!string.IsNullOrEmpty(Root))
				{
					if (!Directory.Exists(Root))
					{
						Directory.CreateDirectory(Root);
					}
					if (!File.Exists(Path.Combine(Root, "README.txt")))
					{
						WriteReadme();
					}
					CassetteSfx.EnsureSfxFiles();
				}
			}
			catch (Exception ex)
			{
				MelonLogger.Warning("[CustomMusic] EnsureStarterFiles error: " + ex.Message);
			}
		}

		public static void Scan()
		{
			Files.Clear();
			if (string.IsNullOrEmpty(Root) || !Directory.Exists(Root))
			{
				return;
			}
			string value = Path.Combine(Root, "sfx");
			List<string> list = new List<string>();
			HashSet<string> hashSet = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
			try
			{
				string[] files = Directory.GetFiles(Root, "*.*", SearchOption.AllDirectories);
				foreach (string text in files)
				{
					if (text.StartsWith(value, StringComparison.OrdinalIgnoreCase) || text.IndexOf("CustomMusicCache", StringComparison.OrdinalIgnoreCase) >= 0)
					{
						continue;
					}
					string text2 = Path.GetExtension(text).ToLowerInvariant();
					if (!(text2 != ".mp3") || !(text2 != ".ogg") || !(text2 != ".wav"))
					{
						string fileNameWithoutExtension = Path.GetFileNameWithoutExtension(text);
						if (!fileNameWithoutExtension.StartsWith("tape_", StringComparison.OrdinalIgnoreCase) && !fileNameWithoutExtension.StartsWith("sfx_", StringComparison.OrdinalIgnoreCase) && hashSet.Add(text))
						{
							list.Add(text);
						}
					}
				}
			}
			catch (Exception ex)
			{
				MelonLogger.Warning($"{"[CustomMusic]"} folder scan error '{Root}': {ex.Message}");
			}
			list.Sort(CompareTracks);
			Files.AddRange(list);
		}

		private static int CompareTracks(string a, string b)
		{
			int num = NaturalCompare(Path.GetFileNameWithoutExtension(a), Path.GetFileNameWithoutExtension(b));
			if (num != 0)
			{
				return num;
			}
			return string.Compare(a, b, StringComparison.OrdinalIgnoreCase);
		}

		private static int NaturalCompare(string a, string b)
		{
			int i = 0;
			int j = 0;
			while (i < a.Length && j < b.Length)
			{
				if (char.IsDigit(a[i]) && char.IsDigit(b[j]))
				{
					int num = i;
					int num2 = j;
					for (; i < a.Length && char.IsDigit(a[i]); i++)
					{
					}
					for (; j < b.Length && char.IsDigit(b[j]); j++)
					{
					}
					string text = a.Substring(num, i - num).TrimStart('0');
					string text2 = b.Substring(num2, j - num2).TrimStart('0');
					if (text.Length != text2.Length)
					{
						return text.Length - text2.Length;
					}
					int num3 = string.CompareOrdinal(text, text2);
					if (num3 != 0)
					{
						return num3;
					}
				}
				else
				{
					int num4 = char.ToUpperInvariant(a[i]).CompareTo(char.ToUpperInvariant(b[j]));
					if (num4 != 0)
					{
						return num4;
					}
					i++;
					j++;
				}
			}
			return a.Length - i - (b.Length - j);
		}

		public static IEnumerator Prepare(string path, Action<PreparedTrack> done)
		{
			Task<PreparedTrack> task = null;
			try
			{
				task = Task.Run(() => AudioDecode.Prepare(path));
			}
			catch (Exception ex)
			{
				MelonLogger.Warning($"{"[CustomMusic]"} '{Path.GetFileName(path)}': prepare start failed: {ex.GetBaseException().Message}");
			}
			while (task != null && !task.IsCompleted)
			{
				yield return null;
			}
			PreparedTrack obj = null;
			if (task != null)
			{
				if (task.IsFaulted)
				{
					Exception ex2 = ((task.Exception == null) ? null : task.Exception.GetBaseException());
					MelonLogger.Warning($"{"[CustomMusic]"} '{Path.GetFileName(path)}': prepare failed: {ex2?.Message ?? "unknown"}");
				}
				else if (task.IsCanceled)
				{
					MelonLogger.Warning("[CustomMusic] '" + Path.GetFileName(path) + "': prepare canceled");
				}
				else
				{
					obj = task.Result;
				}
			}
			done?.Invoke(obj);
		}

		private static void WriteReadme()
		{
			try
			{
				string contents = string.Join(Environment.NewLine, "CustomMusic - папка для своей музыки", "", "Кидай сюда .mp3, .ogg или .wav: " + Root, "Можно раскладывать по подпапкам - поиск рекурсивный, порядок по имени файла (2 раньше 10).", "", "Эта папка зависит от того, как запущена игра:", "  Thunderstore Mod Manager -> ...\\profiles\\<профиль>\\UserData\\CustomMusic", "  обычный запуск exe       -> <папка игры>\\UserData\\CustomMusic", "", "Настройки мода: UserData\\MelonPreferences.cfg, категория CustomMusic.", "", "Панель управления музыкой в игре появится в меню настроек.");
				File.WriteAllText(Path.Combine(Root, "README.txt"), contents);
			}
			catch
			{
			}
		}
	}
	public static class UiProbe
	{
		private const string Tag = "[CustomMusic]";

		private static readonly StringBuilder Sb = new StringBuilder();

		public static bool Dump(string why)
		{
			//IL_00dc: Unknown result type (might be due to invalid IL or missing references)
			//IL_00e1: Unknown result type (might be due to invalid IL or missing references)
			Sb.Clear();
			Line($"=== UI dump ({why}) {DateTime.Now:yyyy-MM-dd HH:mm:ss} ===");
			try
			{
				MenuUIManager val = FindMenu();
				if ((Object)(object)val == (Object)null)
				{
					Line("MenuUIManager: not found in loaded scene");
					Flush();
					return false;
				}
				Line($"MenuUIManager on '{((Object)((Component)val).gameObject).name}' id={((Object)val).GetInstanceID()}");
				Scene scene = ((Component)val).gameObject.scene;
				Line("scene='" + ((Scene)(ref scene)).name + "'");
				Line("canvasRectTransform=" + (((Object)(object)val.canvasRectTransform != (Object)null) ? ((Object)val.canvasRectTransform).name : "null"));
				Line("");
				Line("--- settingsTabs ---");
				Il2CppReferenceArray<GameObject> settingsTabs = val.settingsTabs;
				Line("count=" + ((settingsTabs == null) ? "null" : ((Il2CppArrayBase<GameObject>)(object)settingsTabs).Length.ToString()));
				if (settingsTabs != null)
				{
					for (int i = 0; i < ((Il2CppArrayBase<GameObject>)(object)settingsTabs).Length; i++)
					{
						GameObject val2 = ((Il2CppArrayBase<GameObject>)(object)settingsTabs)[i];
						if ((Object)(object)val2 == (Object)null)
						{
							Line($"[{i}] null");
							continue;
						}
						Line($"[{i}] '{((Object)val2).name}' active={val2.activeSelf} parent='{ParentName(val2)}' sibling={val2.transform.GetSiblingIndex()}");
					}
				}
				Line("");
				Line("--- settingsTabButtons ---");
				Il2CppReferenceArray<Button> settingsTabButtons = val.settingsTabButtons;
				Line("count=" + ((settingsTabButtons == null) ? "null" : ((Il2CppArrayBase<Button>)(object)settingsTabButtons).Length.ToString()));
				if (settingsTabButtons != null)
				{
					for (int j = 0; j < ((Il2CppArrayBase<Button>)(object)settingsTabButtons).Length; j++)
					{
						Button val3 = ((Il2CppArrayBase<Button>)(object)settingsTabButtons)[j];
						if ((Object)(object)val3 == (Object)null)
						{
							Line($"[{j}] null");
							continue;
						}
						Line($"[{j}] '{((Object)((Component)val3).gameObject).name}' active={((Component)val3).gameObject.activeSelf} interactable={((Selectable)val3).interactable} parent='{ParentName(((Component)val3).gameObject)}' sibling={((Component)val3).transform.GetSiblingIndex()}");
						Line("     comps=" + CompNames(((Component)val3).gameObject));
						DumpPersistentCalls(val3);
					}
				}
				Line("");
				Line("--- mainOptionsTab / settingsTab / cheatsTab ---");
				DumpShort("mainOptionsTab", val.mainOptionsTab);
				DumpShort("settingsTab", val.settingsTab);
				DumpShort("cheatsTab", val.cheatsTab);
				if (settingsTabButtons != null && ((Il2CppArrayBase<Button>)(object)settingsTabButtons).Length > 0 && (Object)(object)((Il2CppArrayBase<Button>)(object)settingsTabButtons)[0] != (Object)null)
				{
					Line("");
					Line("--- button row parent hierarchy (depth 3) ---");
					Transform parent = ((Component)((Il2CppArrayBase<Button>)(object)settingsTabButtons)[0]).transform.parent;
					if ((Object)(object)parent != (Object)null)
					{
						DumpGo(((Component)parent).gameObject, 0, 3);
					}
				}
				if (settingsTabs != null && ((Il2CppArrayBase<GameObject>)(object)settingsTabs).Length > 0 && (Object)(object)((Il2CppArrayBase<GameObject>)(object)settingsTabs)[0] != (Object)null)
				{
					Line("");
					Line("--- settingsTabs[0] hierarchy (depth 5) ---");
					DumpGo(((Il2CppArrayBase<GameObject>)(object)settingsTabs)[0], 0, 5);
				}
				Line("");
				Line("--- MenuUIManager object hierarchy (depth 3) ---");
				DumpGo(((Component)val).gameObject, 0, 3);
			}
			catch (Exception value)
			{
				Line($"DUMP FAILED: {value}");
			}
			Flush();
			return true;
		}

		internal static MenuUIManager FindMenu()
		{
			//IL_003b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0040: Unknown result type (might be due to invalid IL or missing references)
			MenuUIManager val = Object.FindObjectOfType<MenuUIManager>();
			if ((Object)(object)val != (Object)null)
			{
				return val;
			}
			Il2CppArrayBase<MenuUIManager> val2 = Resources.FindObjectsOfTypeAll<MenuUIManager>();
			if (val2 == null)
			{
				return null;
			}
			foreach (MenuUIManager item in val2)
			{
				if (!((Object)(object)item == (Object)null))
				{
					Scene scene = ((Component)item).gameObject.scene;
					if (((Scene)(ref scene)).IsValid() && !string.IsNullOrEmpty(((Scene)(ref scene)).name))
					{
						return item;
					}
				}
			}
			return null;
		}

		private static void DumpShort(string label, GameObject go)
		{
			if ((Object)(object)go == (Object)null)
			{
				Line(label + ": null");
				return;
			}
			Line($"{label}: '{((Object)go).name}' active={go.activeSelf} parent='{ParentName(go)}' sibling={go.transform.GetSiblingIndex()}");
			Line("     comps=" + CompNames(go));
		}

		private static void DumpPersistentCalls(Button b)
		{
			try
			{
				int persistentEventCount = ((UnityEventBase)b.onClick).GetPersistentEventCount();
				Line($"     onClick persistent calls: {persistentEventCount}");
				for (int i = 0; i < persistentEventCount; i++)
				{
					Object persistentTarget = ((UnityEventBase)b.onClick).GetPersistentTarget(i);
					string persistentMethodName = ((UnityEventBase)b.onClick).GetPersistentMethodName(i);
					string value = ((persistentTarget == (Object)null) ? "null" : (persistentTarget.name + " (" + ((object)persistentTarget).GetType().Name + ")"));
					Line($"       [{i}] {value} -> {persistentMethodName}");
				}
			}
			catch (Exception ex)
			{
				Line("     onClick dump failed: " + ex.Message);
			}
		}

		private static void DumpGo(GameObject go, int depth, int maxDepth)
		{
			//IL_0044: Unknown result type (might be due to invalid IL or missing references)
			//IL_005d: Unknown result type (might be due to invalid IL or missing references)
			if ((Object)(object)go == (Object)null)
			{
				return;
			}
			string value = new string(' ', depth * 2);
			RectTransform component = go.GetComponent<RectTransform>();
			string value2 = "";
			if ((Object)(object)component != (Object)null)
			{
				value2 = $" size={component.sizeDelta} pos={component.anchoredPosition}";
			}
			Line($"{value}{((Object)go).name}{(go.activeSelf ? "" : " [inactive]")}{value2} comps={CompNames(go)}");
			if (depth < maxDepth)
			{
				Transform transform = go.transform;
				for (int i = 0; i < transform.childCount; i++)
				{
					DumpGo(((Component)transform.GetChild(i)).gameObject, depth + 1, maxDepth);
				}
			}
		}

		private static string CompNames(GameObject go)
		{
			try
			{
				Il2CppArrayBase<Component> components = go.GetComponents<Component>();
				List<string> list = new List<string>();
				foreach (Component item in components)
				{
					if ((Object)(object)item == (Object)null)
					{
						list.Add("<null>");
						continue;
					}
					try
					{
						list.Add(((MemberInfo)((Object)item).GetIl2CppType()).Name);
					}
					catch
					{
						list.Add(((object)item).GetType().Name);
					}
				}
				return string.Join(",", list);
			}
			catch (Exception ex)
			{
				return "<comps failed: " + ex.Message + ">";
			}
		}

		private static string ParentName(GameObject go)
		{
			Transform parent = go.transform.parent;
			if (!((Object)(object)parent == (Object)null))
			{
				return ((Object)((Component)parent).gameObject).name;
			}
			return "<root>";
		}

		private static void Line(string s)
		{
			Sb.AppendLine(s);
		}

		private static void Flush()
		{
			try
			{
				MelonLogger.Msg($"{"[CustomMusic]"} UI dump:{Environment.NewLine}{Sb}");
			}
			catch (Exception ex)
			{
				MelonLogger.Error("[CustomMusic] UI dump failed: " + ex.Message);
			}
		}
	}
	internal static class WavDecoder
	{
		public static DecodedAudio Decode(string path)
		{
			byte[] array = File.ReadAllBytes(path);
			if (array.Length < 12 || FourCC(array, 0) != "RIFF" || FourCC(array, 8) != "WAVE")
			{
				throw new InvalidDataException("not a RIFF/WAVE file");
			}
			int num = -1;
			int num2 = 0;
			int num3 = -1;
			int num4 = 0;
			int num5 = 12;
			while (num5 + 8 <= array.Length)
			{
				string text = FourCC(array, num5);
				int num6 = I32(array, num5 + 4);
				num5 += 8;
				if (num6 < 0)
				{
					throw new InvalidDataException("negative chunk size");
				}
				if ((long)num5 + (long)num6 > array.Length)
				{
					if (!(text == "data"))
					{
						break;
					}
					num6 = array.Length - num5;
				}
				if (text == "fmt ")
				{
					num = num5;
					num2 = num6;
				}
				else if (text == "data")
				{
					num3 = num5;
					num4 = num6;
					break;
				}
				num5 += num6;
				if ((num6 & 1) != 0)
				{
					num5++;
				}
			}
			if (num < 0 || num2 < 16)
			{
				throw new InvalidDataException("missing fmt chunk");
			}
			if (num3 < 0 || num4 <= 0)
			{
				throw new InvalidDataException("missing data chunk");
			}
			ushort num7 = U16(array, num);
			ushort num8 = U16(array, num + 2);
			int num9 = I32(array, num + 4);
			int num10 = U16(array, num + 12);
			int num11 = U16(array, num + 14);
			if (num7 == 65534 && num2 >= 40)
			{
				num7 = U16(array, num + 24);
			}
			if (num8 == 0 || num9 <= 0)
			{
				throw new InvalidDataException("bad fmt chunk");
			}
			if (num11 == 0)
			{
				throw new InvalidDataException("zero bits per sample");
			}
			int num12 = (num11 + 7) / 8;
			if (num10 <= 0)
			{
				num10 = num8 * num12;
			}
			if (num10 < num8 * num12)
			{
				throw new InvalidDataException("bad block align");
			}
			int num13 = num4 / num10;
			if (num13 <= 0)
			{
				throw new InvalidDataException("no audio frames");
			}
			float[] array2 = new float[num13 * num8];
			switch (num7)
			{
			case 1:
				switch (num11)
				{
				case 8:
					Decode8(array, num3, num4, num13, num8, num10, array2);
					break;
				case 16:
					Decode16(array, num3, num13, num8, num10, array2);
					break;
				case 24:
					Decode24(array, num3, num13, num8, num10, array2);
					break;
				case 32:
					Decode32(array, num3, num13, num8, num10, array2);
					break;
				default:
					throw new InvalidDataException("unsupported PCM bit depth " + num11);
				}
				break;
			case 3:
				switch (num11)
				{
				case 32:
					DecodeFloat32(array, num3, num13, num8, num10, array2);
					break;
				case 64:
					DecodeFloat64(array, num3, num13, num8, num10, array2);
					break;
				default:
					throw new InvalidDataException("unsupported float bit depth " + num11);
				}
				break;
			default:
				throw new InvalidDataException("unsupported WAV format tag " + num7);
			}
			return new DecodedAudio
			{
				Name = Path.GetFileNameWithoutExtension(path),
				Channels = num8,
				SampleRate = num9,
				Samples = array2
			};
		}

		public static bool IsMciFriendly(string path)
		{
			try
			{
				using FileStream fileStream = new FileStream(path, FileMode.Open, FileAccess.Read, FileShare.Read);
				using BinaryReader binaryReader = new BinaryReader(fileStream);
				if (fileStream.Length < 44)
				{
					return false;
				}
				if (ReadFourCC(binaryReader) != "RIFF")
				{
					return false;
				}
				binaryReader.ReadInt32();
				if (ReadFourCC(binaryReader) != "WAVE")
				{
					return false;
				}
				while (fileStream.Position + 8 <= fileStream.Length)
				{
					string text = ReadFourCC(binaryReader);
					int num = binaryReader.ReadInt32();
					if (num < 0)
					{
						return false;
					}
					long val = fileStream.Position + num + (num & 1);
					if (text == "fmt ")
					{
						if (num < 16)
						{
							return false;
						}
						short num2 = binaryReader.ReadInt16();
						short num3 = binaryReader.ReadInt16();
						int num4 = binaryReader.ReadInt32();
						binaryReader.ReadInt32();
						binaryReader.ReadInt16();
						short num5 = binaryReader.ReadInt16();
						return num2 == 1 && (num5 == 8 || num5 == 16) && num3 >= 1 && num3 <= 2 && num4 >= 8000 && num4 <= 48000;
					}
					if (text == "data")
					{
						return false;
					}
					fileStream.Position = Math.Min(val, fileStream.Length);
				}
			}
			catch
			{
			}
			return false;
		}

		private static string ReadFourCC(BinaryReader br)
		{
			return new string(new char[4]
			{
				(char)br.ReadByte(),
				(char)br.ReadByte(),
				(char)br.ReadByte(),
				(char)br.ReadByte()
			});
		}

		private static void Decode8(byte[] b, int offset, int size, int frames, int channels, int blockAlign, float[] dst)
		{
			if (blockAlign == channels)
			{
				for (int i = 0; i < dst.Length; i++)
				{
					dst[i] = (float)(b[offset + i] - 128) / 128f;
				}
				return;
			}
			int num = offset;
			for (int j = 0; j < frames; j++)
			{
				for (int k = 0; k < channels; k++)
				{
					dst[j * channels + k] = (float)(b[num + k] - 128) / 128f;
				}
				num += blockAlign;
			}
		}

		private static void Decode16(byte[] b, int offset, int frames, int channels, int blockAlign, float[] dst)
		{
			if (blockAlign == channels * 2)
			{
				short[] array = new short[dst.Length];
				Buffer.BlockCopy(b, offset, array, 0, dst.Length * 2);
				for (int i = 0; i < array.Length; i++)
				{
					dst[i] = (float)array[i] / 32768f;
				}
				return;
			}
			int num = offset;
			for (int j = 0; j < frames; j++)
			{
				for (int k = 0; k < channels; k++)
				{
					dst[j * channels + k] = (float)(short)(b[num + k * 2] | (b[num + k * 2 + 1] << 8)) / 32768f;
				}
				num += blockAlign;
			}
		}

		private static void Decode24(byte[] b, int offset, int frames, int channels, int blockAlign, float[] dst)
		{
			int num = offset;
			for (int i = 0; i < frames; i++)
			{
				for (int j = 0; j < channels; j++)
				{
					int num2 = b[num] | (b[num + 1] << 8) | (b[num + 2] << 16);
					if ((num2 & 0x800000) != 0)
					{
						num2 |= -16777216;
					}
					dst[i * channels + j] = (float)num2 / 8388608f;
					num += 3;
				}
				num += blockAlign - channels * 3;
			}
		}

		private static void Decode32(byte[] b, int offset, int frames, int channels, int blockAlign, float[] dst)
		{
			int num = offset;
			for (int i = 0; i < frames; i++)
			{
				for (int j = 0; j < channels; j++)
				{
					int num2 = I32(b, num);
					dst[i * channels + j] = (float)num2 / 2.1474836E+09f;
					num += 4;
				}
				num += blockAlign - channels * 4;
			}
		}

		private static void DecodeFloat32(byte[] b, int offset, int frames, int channels, int blockAlign, float[] dst)
		{
			int num = offset;
			for (int i = 0; i < frames; i++)
			{
				for (int j = 0; j < channels; j++)
				{
					dst[i * channels + j] = BitConverter.ToSingle(b, num);
					num += 4;
				}
				num += blockAlign - channels * 4;
			}
		}

		private static void DecodeFloat64(byte[] b, int offset, int frames, int channels, int blockAlign, float[] dst)
		{
			int num = offset;
			for (int i = 0; i < frames; i++)
			{
				for (int j = 0; j < channels; j++)
				{
					dst[i * channels + j] = (float)BitConverter.ToDouble(b, num);
					num += 8;
				}
				num += blockAlign - channels * 8;
			}
		}

		private static string FourCC(byte[] b, int o)
		{
			return new string(new char[4]
			{
				(char)b[o],
				(char)b[o + 1],
				(char)b[o + 2],
				(char)b[o + 3]
			});
		}

		private static ushort U16(byte[] b, int o)
		{
			return (ushort)(b[o] | (b[o + 1] << 8));
		}

		private static int I32(byte[] b, int o)
		{
			return b[o] | (b[o + 1] << 8) | (b[o + 2] << 16) | (b[o + 3] << 24);
		}
	}
}