Decompiled source of FATE MusicPlayer v0.4.3

BepInEx/plugins/FateMusicPlayer/FateMusicPlayer.dll

Decompiled 3 days ago
using System;
using System.Collections;
using System.Collections.Generic;
using System.Globalization;
using System.IO;
using System.IO.Compression;
using System.Linq;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.Serialization;
using System.Runtime.Serialization.Json;
using System.Text;
using System.Threading;
using System.Windows.Forms;
using BepInEx;
using BepInEx.Configuration;
using BepInEx.Logging;
using HarmonyLib;
using UnityEngine;
using UnityEngine.Audio;
using UnityEngine.Networking;

[assembly: CompilationRelaxations(8)]
[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
[assembly: AssemblyVersion("0.0.0.0")]
namespace FateMusicPlayer;

internal sealed class AudioLoader : MonoBehaviour
{
	private const float EdgeLead = 1.2f;

	private const float EdgeLinger = 0.8f;

	private const float EdgeRepeatGap = 0.3f;

	private static AudioLoader host;

	private static ManualLogSource log;

	private static string playingSong;

	private static float playingUntil;

	private static string lastEdgeSong;

	private static float lastEdgeAt = -99f;

	private static bool listenerWasPaused;

	private readonly Dictionary<string, AudioClip> cache = new Dictionary<string, AudioClip>(StringComparer.OrdinalIgnoreCase);

	private readonly Dictionary<string, float> requested = new Dictionary<string, float>(StringComparer.OrdinalIgnoreCase);

	private readonly Dictionary<string, float> failed = new Dictionary<string, float>(StringComparer.OrdinalIgnoreCase);

	private string loadingPath;

	private float loadingStartedAt;

	private int previewRequest;

	private readonly List<UnityWebRequest> live = new List<UnityWebRequest>();

	private AudioSource previewSource;

	private AudioSource edgeTail;

	private AudioSource edgeHead;

	private Coroutine playingRoutine;

	internal static AudioLoader Host => host;

	internal static void Create(ManualLogSource logger)
	{
		EnsureHost(logger);
	}

	internal static AudioLoader EnsureHost(ManualLogSource logger)
	{
		//IL_002e: Unknown result type (might be due to invalid IL or missing references)
		//IL_0034: Expected O, but got Unknown
		if (logger != null)
		{
			log = logger;
		}
		if ((Object)(object)host != (Object)null)
		{
			return host;
		}
		GameObject val = new GameObject("FATE Music Player audio");
		Object.DontDestroyOnLoad((Object)(object)val);
		host = val.AddComponent<AudioLoader>();
		if (log != null)
		{
			log.LogInfo((object)"Audio host ready for previews.");
		}
		return host;
	}

	internal static AudioClip Cached(string path)
	{
		if ((Object)(object)host == (Object)null || string.IsNullOrEmpty(path))
		{
			return null;
		}
		AudioClip value;
		return host.cache.TryGetValue(path, out value) ? value : null;
	}

	internal static void Request(string path)
	{
		if (!((Object)(object)host == (Object)null) && !string.IsNullOrEmpty(path) && !host.cache.ContainsKey(path) && (!host.requested.TryGetValue(path, out var value) || !(Time.unscaledTime - value < 5f)) && (!host.failed.TryGetValue(path, out var value2) || !(Time.unscaledTime - value2 < 10f)))
		{
			host.requested[path] = Time.unscaledTime;
			((MonoBehaviour)host).StartCoroutine(host.Load(path, null));
		}
	}

	internal static bool IsPlaying(string songId)
	{
		return songId != null && playingSong == songId && Time.unscaledTime < playingUntil;
	}

	internal static void StopPreview()
	{
		if ((Object)(object)host != (Object)null)
		{
			host.Halt();
		}
	}

	internal static void PreviewClip(string songId, AudioClip clip, float seconds)
	{
		if (!((Object)(object)clip == (Object)null))
		{
			AudioLoader audioLoader = EnsureHost(null);
			if (!((Object)(object)audioLoader == (Object)null))
			{
				audioLoader.PlayPreview(songId, clip, seconds);
			}
		}
	}

	internal static void Preview(string songId, string path, float seconds)
	{
		if (string.IsNullOrEmpty(path) || (Object)(object)EnsureHost(null) == (Object)null)
		{
			return;
		}
		AudioClip val = Cached(path);
		if ((Object)(object)val != (Object)null)
		{
			host.PlayPreview(songId, val, seconds);
		}
		else
		{
			if (host.loadingPath == path && Time.unscaledTime - host.loadingStartedAt < 1.5f)
			{
				return;
			}
			host.loadingPath = path;
			host.loadingStartedAt = Time.unscaledTime;
			int request = ++host.previewRequest;
			((MonoBehaviour)host).StartCoroutine(host.Load(path, delegate(AudioClip loaded)
			{
				if ((Object)(object)loaded != (Object)null && host.previewRequest == request)
				{
					host.PlayPreview(songId, loaded, seconds);
				}
			}));
		}
	}

	internal static void PreviewLoopEdge(string songId, string path, AudioClip clip, float fade)
	{
		AudioLoader audioLoader = EnsureHost(null);
		if ((Object)(object)audioLoader == (Object)null)
		{
			return;
		}
		host = audioLoader;
		if ((Object)(object)clip == (Object)null)
		{
			if (string.IsNullOrEmpty(path))
			{
				return;
			}
			AudioClip val = Cached(path);
			if (!((Object)(object)val != (Object)null))
			{
				if (host.loadingPath == path && Time.unscaledTime - host.loadingStartedAt < 1.5f)
				{
					return;
				}
				host.loadingPath = path;
				host.loadingStartedAt = Time.unscaledTime;
				int request = ++host.previewRequest;
				((MonoBehaviour)host).StartCoroutine(host.Load(path, delegate(AudioClip loaded)
				{
					if ((Object)(object)loaded != (Object)null && host.previewRequest == request)
					{
						PreviewLoopEdge(songId, path, loaded, fade);
					}
				}));
				return;
			}
			clip = val;
		}
		if (!(songId == lastEdgeSong) || !(Time.unscaledTime - lastEdgeAt < 0.3f))
		{
			lastEdgeSong = songId;
			lastEdgeAt = Time.unscaledTime;
			host.PlayEdge(songId, clip, fade);
		}
	}

	private void PlayPreview(string songId, AudioClip clip, float seconds)
	{
		Halt();
		if ((Object)(object)previewSource == (Object)null)
		{
			previewSource = NewSource("FATE Music Player preview");
		}
		previewSource.clip = clip;
		previewSource.loop = false;
		GameMusic.Route(previewSource);
		previewSource.volume = GameMusic.MusicScale;
		GameMusic.MutePlayback();
		BeginAudible();
		try
		{
			previewSource.time = 0f;
		}
		catch
		{
		}
		previewSource.Play();
		playingSong = songId;
		playingUntil = Time.unscaledTime + Mathf.Max(0.5f, seconds);
		playingRoutine = ((MonoBehaviour)this).StartCoroutine(StopAfter(seconds));
		if (log != null)
		{
			log.LogInfo((object)("Preview: " + ((Object)clip).name + " (" + clip.length.ToString("0.0") + "s) for " + Format(seconds) + "s"));
		}
	}

	private void PlayEdge(string songId, AudioClip clip, float fade)
	{
		Halt();
		if ((Object)(object)edgeTail == (Object)null)
		{
			edgeTail = NewSource("FATE Music Player loop tail");
		}
		if ((Object)(object)edgeHead == (Object)null)
		{
			edgeHead = NewSource("FATE Music Player loop head");
		}
		GameMusic.Route(edgeTail);
		GameMusic.Route(edgeHead);
		float length = clip.length;
		fade = Mathf.Clamp(fade, 0f, Mathf.Max(0f, length * 0.5f));
		float num = Mathf.Max(0f, length - fade - 1.2f);
		float num2 = Mathf.Max(0.05f, length - fade - num);
		float num3 = Mathf.Min(0.8f, Mathf.Max(0.2f, fade));
		GameMusic.MutePlayback();
		BeginAudible();
		playingSong = songId;
		playingUntil = Time.unscaledTime + num2 + fade + num3;
		playingRoutine = ((MonoBehaviour)this).StartCoroutine(EdgeBlend(clip, fade, num, num2, num3));
		if (log != null)
		{
			log.LogInfo((object)("Loop blend: " + ((Object)clip).name + " (clip " + length.ToString("0.0") + "s, overlap " + fade.ToString("0.0") + "s)"));
		}
	}

	private IEnumerator EdgeBlend(AudioClip clip, float fade, float tailStart, float tailFor, float linger)
	{
		float scale = GameMusic.MusicScale;
		edgeTail.clip = clip;
		edgeTail.loop = false;
		edgeTail.volume = scale;
		try
		{
			edgeTail.time = tailStart;
		}
		catch
		{
			edgeTail.time = 0f;
		}
		edgeTail.Play();
		float tailEnd = Time.unscaledTime + tailFor;
		while (Time.unscaledTime < tailEnd)
		{
			yield return null;
		}
		edgeHead.clip = clip;
		edgeHead.loop = false;
		edgeHead.volume = ((fade <= 0.001f) ? scale : 0f);
		try
		{
			edgeHead.time = 0f;
		}
		catch
		{
		}
		edgeHead.Play();
		if (fade > 0.001f)
		{
			float start = Time.unscaledTime;
			while (true)
			{
				float t = (Time.unscaledTime - start) / fade;
				if (t >= 1f)
				{
					break;
				}
				edgeHead.volume = scale * t;
				edgeTail.volume = scale * (1f - t);
				yield return null;
			}
		}
		edgeTail.Stop();
		edgeHead.volume = scale;
		float lingerEnd = Time.unscaledTime + linger;
		while (Time.unscaledTime < lingerEnd)
		{
			yield return null;
		}
		edgeHead.Stop();
		playingSong = null;
		playingUntil = 0f;
		GameMusic.RestorePlayback();
		EndAudible();
		playingRoutine = null;
	}

	private IEnumerator StopAfter(float seconds)
	{
		float end = Time.unscaledTime + Mathf.Max(0.5f, seconds);
		while (Time.unscaledTime < end)
		{
			yield return null;
		}
		if ((Object)(object)previewSource != (Object)null)
		{
			previewSource.Stop();
		}
		playingSong = null;
		playingUntil = 0f;
		GameMusic.RestorePlayback();
		EndAudible();
		playingRoutine = null;
	}

	private void Halt()
	{
		previewRequest++;
		if (playingRoutine != null)
		{
			((MonoBehaviour)this).StopCoroutine(playingRoutine);
			playingRoutine = null;
		}
		if ((Object)(object)previewSource != (Object)null)
		{
			previewSource.Stop();
		}
		if ((Object)(object)edgeTail != (Object)null)
		{
			edgeTail.Stop();
		}
		if ((Object)(object)edgeHead != (Object)null)
		{
			edgeHead.Stop();
		}
		playingSong = null;
		playingUntil = 0f;
		GameMusic.RestorePlayback();
		EndAudible();
	}

	private static void BeginAudible()
	{
		listenerWasPaused = AudioListener.pause;
		if (AudioListener.pause)
		{
			AudioListener.pause = false;
		}
	}

	private static void EndAudible()
	{
		if (listenerWasPaused)
		{
			AudioListener.pause = true;
		}
		listenerWasPaused = false;
	}

	private void Update()
	{
		if (playingSong != null)
		{
			if (AudioListener.pause)
			{
				AudioListener.pause = false;
			}
			if ((Object)(object)previewSource != (Object)null)
			{
				previewSource.volume = GameMusic.MusicScale;
			}
		}
	}

	private AudioSource NewSource(string name)
	{
		//IL_0002: Unknown result type (might be due to invalid IL or missing references)
		//IL_0008: Expected O, but got Unknown
		GameObject val = new GameObject(name);
		val.transform.SetParent(((Component)this).transform, false);
		AudioSource val2 = val.AddComponent<AudioSource>();
		val2.playOnAwake = false;
		val2.loop = false;
		val2.spatialBlend = 0f;
		return val2;
	}

	private IEnumerator Load(string path, Action<AudioClip> done)
	{
		AudioType type = AudioTypeFor(path);
		if ((int)type == 0)
		{
			if (log != null)
			{
				log.LogWarning((object)("Unsupported audio file: " + path));
			}
			Fail(path);
			done?.Invoke(null);
			yield break;
		}
		string uri = new Uri(Path.GetFullPath(path)).AbsoluteUri;
		UnityWebRequest request = UnityWebRequestMultimedia.GetAudioClip(uri, type);
		yield return request.SendWebRequest();
		if (request.error != null)
		{
			if (log != null)
			{
				log.LogWarning((object)("Could not read " + path + ": " + request.error + " (will try again in 10s)"));
			}
			Fail(path);
			request.Dispose();
			done?.Invoke(null);
			yield break;
		}
		DownloadHandler downloadHandler = request.downloadHandler;
		DownloadHandlerAudioClip handler = (DownloadHandlerAudioClip)(object)((downloadHandler is DownloadHandlerAudioClip) ? downloadHandler : null);
		AudioClip clip = ((handler == null) ? null : handler.audioClip);
		if ((Object)(object)clip == (Object)null)
		{
			if (log != null)
			{
				log.LogWarning((object)("No audio in " + path + " (will try again in 10s)"));
			}
			Fail(path);
			request.Dispose();
			done?.Invoke(null);
		}
		else
		{
			((Object)clip).name = Path.GetFileNameWithoutExtension(path);
			cache[path] = clip;
			failed.Remove(path);
			live.Add(request);
			FinishLoading(path);
			done?.Invoke(clip);
		}
	}

	private void Fail(string path)
	{
		cache.Remove(path);
		failed[path] = Time.unscaledTime;
		FinishLoading(path);
	}

	private void FinishLoading(string path)
	{
		if (string.Equals(loadingPath, path, StringComparison.OrdinalIgnoreCase))
		{
			loadingPath = null;
		}
	}

	private static string Format(float seconds)
	{
		return seconds.ToString("0.0");
	}

	private void OnDestroy()
	{
		GameMusic.RestorePlayback();
		EndAudible();
		foreach (UnityWebRequest item in live)
		{
			if (item != null)
			{
				item.Dispose();
			}
		}
		live.Clear();
		host = null;
	}

	internal static AudioType AudioTypeFor(string path)
	{
		//IL_0067: Unknown result type (might be due to invalid IL or missing references)
		//IL_006a: Unknown result type (might be due to invalid IL or missing references)
		//IL_0055: Unknown result type (might be due to invalid IL or missing references)
		//IL_005a: Unknown result type (might be due to invalid IL or missing references)
		//IL_005f: Unknown result type (might be due to invalid IL or missing references)
		//IL_0063: Unknown result type (might be due to invalid IL or missing references)
		switch (Path.GetExtension(path).ToLowerInvariant())
		{
		case ".ogg":
			return (AudioType)14;
		case ".wav":
			return (AudioType)20;
		case ".mp3":
			return (AudioType)13;
		case ".aiff":
		case ".aif":
			return (AudioType)2;
		default:
			return (AudioType)0;
		}
	}
}
[BepInDependency(/*Could not decode attribute arguments.*/)]
[BepInPlugin("mikhailkillszombies.musicplayer", "FATE Music Player", "0.4.3")]
public sealed class FateMusicPlayerPlugin : BaseUnityPlugin
{
	internal const string Guid = "mikhailkillszombies.musicplayer";

	internal const string Version = "0.4.3";

	internal static ManualLogSource LogSource;

	internal static bool ModEnabled = true;

	internal static string LastToast;

	internal static float LastToastAt;

	private ConfigEntry<bool> enabledSetting;

	internal static void Toast(string message)
	{
		LastToast = message;
		LastToastAt = Time.unscaledTime;
	}

	private void Awake()
	{
		//IL_0099: Unknown result type (might be due to invalid IL or missing references)
		//IL_00a3: Expected O, but got Unknown
		LogSource = ((BaseUnityPlugin)this).Logger;
		enabledSetting = ((BaseUnityPlugin)this).Config.Bind<bool>("General", "Enabled", true, "Let the music player choose tracks for the slots you edited.");
		ModEnabled = enabledSetting.Value;
		enabledSetting.SettingChanged += delegate
		{
			ModEnabled = enabledSetting.Value;
		};
		AudioLoader.Create(((BaseUnityPlugin)this).Logger);
		Library.Init(Paths.ConfigPath, Path.GetDirectoryName(((BaseUnityPlugin)this).Info.Location), ((BaseUnityPlugin)this).Logger);
		ModMenuBridge.Init(((BaseUnityPlugin)this).Logger);
		MusicRuntime.Init(((BaseUnityPlugin)this).Logger, new Harmony("mikhailkillszombies.musicplayer"));
		((BaseUnityPlugin)this).Logger.LogInfo((object)"FATE Music Player 0.4.3 loaded; waiting for the game's audio manager.");
	}

	private void Update()
	{
		MusicRuntime.Tick();
	}
}
internal sealed class BucketInfo
{
	public int Value;

	public string Name;

	public AudioClip[] Clips;

	public bool Loop;

	public float Volume;

	public string EditorName;
}
internal static class GameMusic
{
	internal static bool Bound;

	private static Type managerType;

	private static Type dataType;

	private static Type clipNameType;

	private static PropertyInfo instanceProperty;

	private static FieldInfo instanceField;

	private static FieldInfo musicListField;

	private static FieldInfo channelsField;

	private static FieldInfo currentMusicField;

	private static FieldInfo stoppedField;

	private static FieldInfo dataClipNameField;

	private static FieldInfo dataClipsField;

	private static FieldInfo dataLoopField;

	private static FieldInfo dataVolumeField;

	private static FieldInfo dataEditorNameField;

	private static MethodInfo freeMusicSourceMethod;

	private static Type levelType;

	private static FieldInfo levelSourceField;

	private static Type menuType;

	private static FieldInfo mixerField;

	private static PropertyInfo settingsSaveProperty;

	private static FieldInfo settingsMusicVolumeField;

	private static AudioMixerGroup musicGroup;

	private static object musicGroupFor;

	private static bool musicGroupTried;

	private static bool warnedNoGroup;

	private static ManualLogSource logSource;

	private static float settingsVolume = 1f;

	private static float nextSettingsRead;

	private static readonly List<KeyValuePair<AudioSource, float>> muted = new List<KeyValuePair<AudioSource, float>>();

	internal static Type ManagerType => managerType;

	internal static Type ClipNameType => clipNameType;

	internal static float MusicScale
	{
		get
		{
			if ((Object)(object)MusicGroup() != (Object)null)
			{
				return 1f;
			}
			float unscaledTime = Time.unscaledTime;
			if (unscaledTime < nextSettingsRead)
			{
				return settingsVolume;
			}
			nextSettingsRead = unscaledTime + 0.25f;
			try
			{
				object obj = ((settingsSaveProperty == null) ? null : settingsSaveProperty.GetValue(null, null));
				if (obj != null && settingsMusicVolumeField != null)
				{
					settingsVolume = Mathf.Clamp01(Convert.ToSingle(settingsMusicVolumeField.GetValue(obj)));
				}
			}
			catch
			{
			}
			return settingsVolume;
		}
	}

	internal static bool Bind(ManualLogSource log)
	{
		if (log != null)
		{
			logSource = log;
		}
		if (Bound)
		{
			return true;
		}
		managerType = AccessTools.TypeByName("Tableflip.AudioManager");
		dataType = AccessTools.TypeByName("AudioData");
		clipNameType = AccessTools.TypeByName("AudioData+AudioClipName");
		if (managerType == null || dataType == null || clipNameType == null)
		{
			if (log != null)
			{
				log.LogWarning((object)"FATE AudioManager was not found yet; music slots are unavailable.");
			}
			return false;
		}
		instanceProperty = FindStaticProperty(managerType, "Instance");
		instanceField = FindInstanceField(managerType, managerType);
		musicListField = AccessTools.Field(managerType, "mAudioDataMusicList");
		channelsField = AccessTools.Field(managerType, "audioSourceMusicChannels");
		currentMusicField = AccessTools.Field(managerType, "currentMusicData");
		stoppedField = AccessTools.Field(managerType, "hasStoppedByScript");
		dataClipNameField = AccessTools.Field(dataType, "mAudioClipName");
		dataClipsField = AccessTools.Field(dataType, "mAudioClip");
		dataLoopField = AccessTools.Field(dataType, "mLoop");
		dataVolumeField = AccessTools.Field(dataType, "mVolume");
		dataEditorNameField = AccessTools.Field(dataType, "editorName");
		freeMusicSourceMethod = AccessTools.Method(managerType, "GetFreeMusicAudioSource", new Type[0], (Type[])null);
		mixerField = AccessTools.Field(managerType, "masterMixer");
		Type type = AccessTools.TypeByName("Tableflip.SaveData");
		settingsSaveProperty = ((type == null) ? null : type.GetProperty("SettingsSave", BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic));
		settingsMusicVolumeField = ((settingsSaveProperty == null) ? null : AccessTools.Field(settingsSaveProperty.PropertyType, "musicVolume"));
		levelType = AccessTools.TypeByName("Tableflip.LevelGenerator");
		levelSourceField = ((levelType == null) ? null : AccessTools.Field(levelType, "audioSource"));
		menuType = AccessTools.TypeByName("Tableflip.MainMenuUI");
		Bound = instanceProperty != null && musicListField != null && channelsField != null;
		if (!Bound && log != null)
		{
			log.LogWarning((object)"FATE AudioManager layout changed; music slots are unavailable.");
		}
		else if (Bound && log != null)
		{
			log.LogInfo((object)("AudioManager instance read from " + ((instanceField != null) ? ("field " + instanceField.Name) : "the Instance property") + "; music list field " + ((musicListField != null) ? "found" : "missing") + "."));
		}
		return Bound;
	}

	private static PropertyInfo FindStaticProperty(Type type, string name)
	{
		Type type2 = type;
		while (type2 != null)
		{
			PropertyInfo property = type2.GetProperty(name, BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic);
			if (property != null)
			{
				return property;
			}
			type2 = type2.BaseType;
		}
		return null;
	}

	internal static object Manager()
	{
		try
		{
			if (instanceField != null)
			{
				return instanceField.GetValue(null);
			}
			return (instanceProperty == null) ? null : instanceProperty.GetValue(null, null);
		}
		catch
		{
			return null;
		}
	}

	private static FieldInfo FindInstanceField(Type type, Type instanceType)
	{
		Type type2 = type;
		while (type2 != null)
		{
			FieldInfo[] fields = type2.GetFields(BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic);
			FieldInfo[] array = fields;
			foreach (FieldInfo fieldInfo in array)
			{
				if (fieldInfo.FieldType == instanceType && fieldInfo.Name.IndexOf("instance", StringComparison.OrdinalIgnoreCase) >= 0)
				{
					return fieldInfo;
				}
			}
			type2 = type2.BaseType;
		}
		type2 = type;
		while (type2 != null)
		{
			FieldInfo[] fields = type2.GetFields(BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic);
			FieldInfo[] array = fields;
			foreach (FieldInfo fieldInfo in array)
			{
				if (fieldInfo.FieldType == instanceType)
				{
					return fieldInfo;
				}
			}
			type2 = type2.BaseType;
		}
		return null;
	}

	internal static List<BucketInfo> Buckets()
	{
		List<BucketInfo> list = new List<BucketInfo>();
		if (!Bound)
		{
			return list;
		}
		object obj = Manager();
		if (obj == null || musicListField == null)
		{
			return list;
		}
		if (!(musicListField.GetValue(obj) is Array array))
		{
			return list;
		}
		foreach (object item in array)
		{
			if (item != null)
			{
				object obj2 = ((dataClipNameField == null) ? null : dataClipNameField.GetValue(item));
				if (obj2 != null)
				{
					BucketInfo bucketInfo = new BucketInfo();
					bucketInfo.Value = Convert.ToInt32(obj2);
					bucketInfo.Name = obj2.ToString();
					bucketInfo.Clips = (AudioClip[])((dataClipsField == null) ? new AudioClip[0] : (((object)(dataClipsField.GetValue(item) as AudioClip[])) ?? ((object)new AudioClip[0])));
					bucketInfo.Loop = dataLoopField == null || Convert.ToBoolean(dataLoopField.GetValue(item));
					bucketInfo.Volume = ((dataVolumeField == null) ? 1f : Convert.ToSingle(dataVolumeField.GetValue(item)));
					bucketInfo.EditorName = ((dataEditorNameField == null) ? null : (dataEditorNameField.GetValue(item) as string));
					list.Add(bucketInfo);
				}
			}
		}
		return list;
	}

	internal static BucketInfo FindBucket(int value)
	{
		foreach (BucketInfo item in Buckets())
		{
			if (item.Value == value)
			{
				return item;
			}
		}
		return null;
	}

	internal static AudioMixerGroup MusicGroup()
	{
		object obj = Manager();
		if (obj == null)
		{
			return null;
		}
		if (object.ReferenceEquals(musicGroupFor, obj) && ((Object)(object)musicGroup != (Object)null || musicGroupTried))
		{
			return musicGroup;
		}
		musicGroupFor = obj;
		musicGroupTried = true;
		musicGroup = null;
		if (mixerField == null)
		{
			return null;
		}
		try
		{
			object? value = mixerField.GetValue(obj);
			AudioMixer val = (AudioMixer)((value is AudioMixer) ? value : null);
			if ((Object)(object)val == (Object)null)
			{
				return null;
			}
			AudioMixerGroup[] array = val.FindMatchingGroups("Music");
			if (array != null && array.Length > 0)
			{
				musicGroup = array[0];
			}
		}
		catch
		{
			musicGroup = null;
		}
		if ((Object)(object)musicGroup == (Object)null && !warnedNoGroup)
		{
			warnedNoGroup = true;
			if (logSource != null)
			{
				logSource.LogWarning((object)"The game's Music mixer group was not found; the in-game music volume slider cannot reach this mod's music.");
			}
		}
		return musicGroup;
	}

	internal static void Route(AudioSource source)
	{
		if ((Object)(object)source == (Object)null)
		{
			return;
		}
		AudioMixerGroup val = MusicGroup();
		if ((Object)(object)val == (Object)null)
		{
			return;
		}
		try
		{
			if ((Object)(object)source.outputAudioMixerGroup != (Object)(object)val)
			{
				source.outputAudioMixerGroup = val;
			}
		}
		catch
		{
		}
	}

	internal static AudioSource FreeMusicSource()
	{
		object obj = Manager();
		if (obj == null || freeMusicSourceMethod == null)
		{
			return null;
		}
		try
		{
			object? obj2 = freeMusicSourceMethod.Invoke(obj, null);
			return (AudioSource)((obj2 is AudioSource) ? obj2 : null);
		}
		catch
		{
			return null;
		}
	}

	internal static AudioSource[] MusicChannels()
	{
		object obj = Manager();
		if (obj == null || channelsField == null)
		{
			return (AudioSource[])(object)new AudioSource[0];
		}
		try
		{
			return (AudioSource[])(((object)(channelsField.GetValue(obj) as AudioSource[])) ?? ((object)new AudioSource[0]));
		}
		catch
		{
			return (AudioSource[])(object)new AudioSource[0];
		}
	}

	internal static void StopAllMusic()
	{
		LoopPlayer.Stop();
		AudioSource[] array = MusicChannels();
		foreach (AudioSource val in array)
		{
			if ((Object)(object)val != (Object)null)
			{
				val.Stop();
			}
		}
	}

	internal static void MutePlayback()
	{
		if (muted.Count > 0)
		{
			return;
		}
		LoopPlayer.SetSilenced(value: true);
		AudioSource[] array = MusicChannels();
		foreach (AudioSource val in array)
		{
			if (!((Object)(object)val == (Object)null) && !(val.volume <= 0f))
			{
				muted.Add(new KeyValuePair<AudioSource, float>(val, val.volume));
				val.volume = 0f;
			}
		}
	}

	internal static void RestorePlayback()
	{
		for (int i = 0; i < muted.Count; i++)
		{
			AudioSource key = muted[i].Key;
			if ((Object)(object)key != (Object)null)
			{
				key.volume = muted[i].Value;
			}
		}
		muted.Clear();
		LoopPlayer.SetSilenced(value: false);
	}

	internal static void RememberSlot(int bucketValue, bool stopped)
	{
		object obj = Manager();
		if (obj == null)
		{
			return;
		}
		try
		{
			if (currentMusicField != null)
			{
				currentMusicField.SetValue(obj, Enum.ToObject(clipNameType, bucketValue));
			}
			if (stoppedField != null)
			{
				stoppedField.SetValue(obj, stopped);
			}
		}
		catch
		{
		}
	}

	private static List<MethodInfo> StopMethods()
	{
		List<MethodInfo> list = new List<MethodInfo>();
		string[] array = new string[2] { "StopMusic", "ForceStopMusic" };
		foreach (string text in array)
		{
			MethodInfo methodInfo = AccessTools.Method(managerType, text, new Type[0], (Type[])null);
			if (methodInfo != null)
			{
				list.Add(methodInfo);
			}
			MethodInfo methodInfo2 = ((clipNameType == null) ? null : AccessTools.Method(managerType, text, new Type[1] { clipNameType }, (Type[])null));
			if (methodInfo2 != null)
			{
				list.Add(methodInfo2);
			}
		}
		return list;
	}

	internal static int DungeonMusicValue()
	{
		if (clipNameType == null)
		{
			return -1;
		}
		try
		{
			return Convert.ToInt32(Enum.Parse(clipNameType, "DungeonMusic"));
		}
		catch
		{
			return -1;
		}
	}

	internal static void SilenceDungeonSource(object level)
	{
		if (level == null || levelSourceField == null)
		{
			return;
		}
		try
		{
			object? value = levelSourceField.GetValue(level);
			AudioSource val = (AudioSource)((value is AudioSource) ? value : null);
			if ((Object)(object)val != (Object)null)
			{
				val.Stop();
			}
		}
		catch
		{
		}
	}

	internal static bool Patch(Harmony harmony, ManualLogSource log)
	{
		//IL_00ba: Unknown result type (might be due to invalid IL or missing references)
		//IL_00c8: Expected O, but got Unknown
		//IL_0140: Unknown result type (might be due to invalid IL or missing references)
		//IL_014e: Expected O, but got Unknown
		//IL_034a: Unknown result type (might be due to invalid IL or missing references)
		//IL_0357: Expected O, but got Unknown
		//IL_02b6: Unknown result type (might be due to invalid IL or missing references)
		//IL_02c3: Expected O, but got Unknown
		//IL_0211: Unknown result type (might be due to invalid IL or missing references)
		//IL_021e: Expected O, but got Unknown
		if (!Bind(log))
		{
			return false;
		}
		bool result = false;
		string[] array = new string[2] { "PlayMusic", "ForcePlayMusic" };
		foreach (string text in array)
		{
			MethodInfo methodInfo = AccessTools.Method(managerType, text, new Type[2]
			{
				clipNameType,
				typeof(bool)
			}, (Type[])null);
			if (methodInfo == null)
			{
				if (log != null)
				{
					log.LogWarning((object)("AudioManager." + text + " was not found; that slot keeps playing vanilla music."));
				}
				continue;
			}
			try
			{
				harmony.Patch((MethodBase)methodInfo, new HarmonyMethod(typeof(MusicEngine), "BeforePlayMusic", (Type[])null), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null);
				result = true;
			}
			catch (Exception ex)
			{
				if (log != null)
				{
					log.LogError((object)("Could not patch AudioManager." + text + ": " + ex.Message));
				}
			}
		}
		foreach (MethodInfo item in StopMethods())
		{
			try
			{
				harmony.Patch((MethodBase)item, new HarmonyMethod(typeof(MusicEngine), "BeforeGameStopsMusic", (Type[])null), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null);
				result = true;
			}
			catch (Exception ex)
			{
				if (log != null)
				{
					log.LogWarning((object)("Could not patch AudioManager." + item.Name + ": " + ex.Message));
				}
			}
		}
		MethodInfo methodInfo2 = ((levelType == null) ? null : AccessTools.Method(levelType, "PlayDungeonMusic", new Type[0], (Type[])null));
		if (methodInfo2 == null)
		{
			if (log != null)
			{
				log.LogWarning((object)"LevelGenerator.PlayDungeonMusic was not found; songs in the dungeon slot cannot play.");
			}
		}
		else
		{
			try
			{
				harmony.Patch((MethodBase)methodInfo2, (HarmonyMethod)null, new HarmonyMethod(typeof(MusicEngine), "AfterDungeonMusic", (Type[])null), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null);
				result = true;
			}
			catch (Exception ex)
			{
				if (log != null)
				{
					log.LogWarning((object)("Could not patch LevelGenerator.PlayDungeonMusic: " + ex.Message));
				}
			}
		}
		MethodInfo methodInfo3 = ((menuType == null) ? null : AccessTools.Method(menuType, "Start", new Type[0], (Type[])null));
		if (methodInfo3 == null)
		{
			if (log != null)
			{
				log.LogWarning((object)"MainMenuUI.Start was not found; the title screen keeps its own music.");
			}
		}
		else
		{
			try
			{
				harmony.Patch((MethodBase)methodInfo3, (HarmonyMethod)null, new HarmonyMethod(typeof(MusicEngine), "AfterMainMenuMusic", (Type[])null), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null);
				result = true;
			}
			catch (Exception ex)
			{
				if (log != null)
				{
					log.LogWarning((object)("Could not patch MainMenuUI.Start: " + ex.Message));
				}
			}
		}
		MethodInfo methodInfo4 = AccessTools.Method(managerType, "Update", new Type[0], (Type[])null);
		if (methodInfo4 == null)
		{
			if (log != null)
			{
				log.LogWarning((object)"AudioManager.Update was not found; the mod's own ticker object drives the player instead.");
			}
		}
		else
		{
			try
			{
				harmony.Patch((MethodBase)methodInfo4, (HarmonyMethod)null, new HarmonyMethod(typeof(MusicRuntime), "AfterAudioManagerUpdate", (Type[])null), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null);
				result = true;
			}
			catch (Exception ex)
			{
				if (log != null)
				{
					log.LogWarning((object)("Could not hook AudioManager.Update: " + ex.Message));
				}
			}
		}
		return result;
	}
}
[DataContract]
public sealed class SongSetting
{
	[DataMember(Name = "id")]
	public string Id;

	[DataMember(Name = "enabled")]
	public bool Enabled;

	[DataMember(Name = "selected")]
	public bool Selected;

	[DataMember(Name = "loopSeconds")]
	public float? LoopSeconds;
}
[DataContract]
public sealed class UserSong
{
	[DataMember(Name = "id")]
	public string Id;

	[DataMember(Name = "title")]
	public string Title;

	[DataMember(Name = "bucket")]
	public string Bucket;

	[DataMember(Name = "file")]
	public string File;

	[DataMember(Name = "loop")]
	public bool? Loop;

	[DataMember(Name = "volume")]
	public float? Volume;

	[DataMember(Name = "loopSeconds")]
	public float? LoopSeconds;
}
[DataContract]
public sealed class LibraryFile
{
	[DataMember(Name = "version")]
	public int Version = 1;

	[DataMember(Name = "settings")]
	public List<SongSetting> Settings = new List<SongSetting>();

	[DataMember(Name = "userSongs")]
	public List<UserSong> Songs = new List<UserSong>();
}
[DataContract]
public sealed class PackFile
{
	[DataMember(Name = "id")]
	public string Id;

	[DataMember(Name = "name")]
	public string Name;

	[DataMember(Name = "author")]
	public string Author;

	[DataMember(Name = "version")]
	public string Version;

	[DataMember(Name = "songs")]
	public List<PackSong> Songs;
}
[DataContract]
public sealed class PackSong
{
	[DataMember(Name = "title")]
	public string Title;

	[DataMember(Name = "category")]
	public string Category;

	[DataMember(Name = "bucket")]
	public string Bucket;

	[DataMember(Name = "file")]
	public string File;

	[DataMember(Name = "loop")]
	public bool? Loop;

	[DataMember(Name = "volume")]
	public float? Volume;

	[DataMember(Name = "loopSeconds")]
	public float? LoopSeconds;
}
internal sealed class Song
{
	public string Id;

	public string Title;

	public string Category;

	public string BucketName;

	public int BucketValue;

	public string PackId;

	public string FilePath;

	public AudioClip GameClip;

	public bool Loop = true;

	public float Volume = 1f;

	public float LoopSeconds;

	public bool Enabled = true;

	public bool Selected;

	public bool IsCustom => (Object)(object)GameClip == (Object)null;
}
internal static class Library
{
	private const float StepTenth = 0.1f;

	private const float StepOne = 1f;

	internal const float MaxLoopSeconds = 10f;

	private static readonly List<Song> songs = new List<Song>();

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

	private static LibraryFile data = new LibraryFile();

	private static string configPath;

	private static string packsPath;

	private static string pluginPath;

	private static ManualLogSource log;

	private static bool savePending;

	private static float saveAt;

	internal static string PacksPath => packsPath;

	internal static string ConfigPath => configPath;

	internal static List<Song> Songs => songs;

	internal static bool HasGameSongs => songs.Any((Song song) => (Object)(object)song.GameClip != (Object)null);

	internal static List<string> Categories => categories;

	internal static LibraryFile Data => data;

	internal static void Init(string configDirectory, string pluginDirectory, ManualLogSource logger)
	{
		log = logger;
		configPath = Path.Combine(configDirectory, "library.json");
		pluginPath = pluginDirectory;
		packsPath = Path.Combine(pluginDirectory, "packs");
		try
		{
			Directory.CreateDirectory(packsPath);
		}
		catch (Exception ex)
		{
			log.LogWarning((object)("Could not create " + packsPath + ": " + ex.Message));
		}
		foreach (string item in PackRoots())
		{
			log.LogInfo((object)("Looking for song packs in " + item));
		}
		Load();
		Scan();
	}

	internal static List<Song> SongsForBucket(int bucketValue)
	{
		return songs.Where((Song song) => song.BucketValue == bucketValue).ToList();
	}

	internal static List<int> CategorySlots(string category)
	{
		List<int> list = new List<int>();
		foreach (Song song in songs)
		{
			if (song != null && string.Equals(song.Category, category, StringComparison.Ordinal) && !list.Contains(song.BucketValue))
			{
				list.Add(song.BucketValue);
			}
		}
		list.Sort();
		return list;
	}

	private static void Load()
	{
		//IL_0035: Unknown result type (might be due to invalid IL or missing references)
		//IL_003b: Expected O, but got Unknown
		data = new LibraryFile();
		if (!File.Exists(configPath))
		{
			return;
		}
		try
		{
			using FileStream fileStream = File.OpenRead(configPath);
			DataContractJsonSerializer val = new DataContractJsonSerializer(typeof(LibraryFile));
			data = ((LibraryFile)((XmlObjectSerializer)val).ReadObject((Stream)fileStream)) ?? new LibraryFile();
		}
		catch (Exception ex)
		{
			log.LogWarning((object)("Could not read " + configPath + ": " + ex.Message + ". Starting with defaults."));
			data = new LibraryFile();
		}
		if (data.Settings == null)
		{
			data.Settings = new List<SongSetting>();
		}
		if (data.Songs == null)
		{
			data.Songs = new List<UserSong>();
		}
	}

	internal static void SaveLater()
	{
		savePending = true;
		saveAt = Time.unscaledTime + 0.5f;
	}

	internal static void FlushSaves()
	{
		if (savePending && !(Time.unscaledTime < saveAt))
		{
			savePending = false;
			Save();
		}
	}

	internal static void Save()
	{
		//IL_0028: Unknown result type (might be due to invalid IL or missing references)
		try
		{
			Directory.CreateDirectory(Path.GetDirectoryName(configPath));
			using FileStream fileStream = File.Create(configPath);
			((XmlObjectSerializer)new DataContractJsonSerializer(typeof(LibraryFile))).WriteObject((Stream)fileStream, (object)data);
		}
		catch (Exception ex)
		{
			log.LogWarning((object)("Could not save " + configPath + ": " + ex.Message));
		}
	}

	internal static void Scan()
	{
		DropDuplicateImports();
		songs.Clear();
		categories.Clear();
		Dictionary<string, SongSetting> dictionary = new Dictionary<string, SongSetting>(StringComparer.OrdinalIgnoreCase);
		foreach (SongSetting setting in data.Settings)
		{
			if (setting != null && !string.IsNullOrEmpty(setting.Id))
			{
				dictionary[setting.Id] = setting;
			}
		}
		foreach (BucketInfo item in MusicBuckets())
		{
			string category = CategoryForBucket(item);
			for (int i = 0; i < item.Clips.Length; i++)
			{
				AudioClip val = item.Clips[i];
				if (!((Object)(object)val == (Object)null))
				{
					Song song = new Song();
					song.Id = "game:" + item.Value + ":" + i + ":" + Sanitize(((Object)val).name);
					song.Title = FriendlyTitle(((Object)val).name);
					song.Category = category;
					song.BucketName = item.Name;
					song.BucketValue = item.Value;
					song.GameClip = val;
					song.Loop = item.Loop;
					song.Volume = item.Volume;
					song.LoopSeconds = 0f;
					Add(song, dictionary);
				}
			}
			if (item.Clips.Length == 0 && !string.IsNullOrEmpty(item.EditorName))
			{
				Song song2 = new Song();
				song2.Id = "slot:" + item.Value;
				song2.Title = FriendlyTitle(item.EditorName);
				song2.Category = category;
				song2.BucketName = item.Name;
				song2.BucketValue = item.Value;
				song2.Loop = item.Loop;
				song2.Volume = item.Volume;
				song2.LoopSeconds = 0f;
				song2.Enabled = false;
				Add(song2, dictionary);
			}
		}
		foreach (UserSong song3 in data.Songs)
		{
			if (song3 != null && !string.IsNullOrEmpty(song3.File))
			{
				Add(UserSongRow(song3), dictionary);
			}
		}
		foreach (KeyValuePair<string, List<Song>> item2 in ScanPacks())
		{
			foreach (Song item3 in item2.Value)
			{
				Add(item3, dictionary);
			}
		}
		HashSet<string> live = new HashSet<string>(songs.Select((Song song3) => song3.Id), StringComparer.OrdinalIgnoreCase);
		int num = data.Settings.RemoveAll((SongSetting setting) => setting != null && !string.IsNullOrEmpty(setting.Id) && setting.Id.StartsWith("pack:", StringComparison.OrdinalIgnoreCase) && !live.Contains(setting.Id));
		if (num > 0)
		{
			log.LogInfo((object)("Removed " + num + " setting(s) belonging to uninstalled packs."));
			Save();
		}
		foreach (string category2 in categories)
		{
			List<Song> list = songs;
			Predicate<Song> match = (Song row) => row != null && row.Category == category2;
			List<Song> list2 = list.FindAll(match);
			List<int> list3 = new List<int>();
			foreach (Song item4 in list2)
			{
				if (!list3.Contains(item4.BucketValue))
				{
					list3.Add(item4.BucketValue);
				}
			}
			list3.Sort();
			string text = category2 + ": " + list2.Count + " song(s)";
			if (list3.Count > 0)
			{
				text = text + " in slot(s) " + string.Join(", ", list3.ConvertAll((int value) => value.ToString()).ToArray());
			}
			log.LogInfo((object)text);
		}
		log.LogInfo((object)("Music library: " + songs.Count + " song(s) in " + categories.Count + " category(ies)."));
	}

	private static Song UserSongRow(UserSong user)
	{
		Song song = new Song();
		song.Id = user.Id;
		song.Title = (string.IsNullOrEmpty(user.Title) ? Path.GetFileNameWithoutExtension(user.File) : user.Title);
		song.FilePath = user.File;
		BucketInfo bucketInfo = ResolveBucket(user.Bucket);
		song.BucketName = ((bucketInfo != null) ? bucketInfo.Name : user.Bucket);
		song.BucketValue = bucketInfo?.Value ?? BucketValue(user.Bucket);
		song.Category = ((bucketInfo != null) ? CategoryForBucket(bucketInfo) : CategoryForBucketName(user.Bucket));
		song.Loop = !user.Loop.HasValue || user.Loop.Value;
		song.Volume = ((!user.Volume.HasValue) ? 1f : Mathf.Clamp01(user.Volume.Value));
		song.LoopSeconds = ((!user.LoopSeconds.HasValue) ? 0f : Mathf.Clamp(user.LoopSeconds.Value, 0f, 10f));
		return song;
	}

	private static void DropDuplicateImports()
	{
		HashSet<string> hashSet = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
		int num = 0;
		for (int num2 = data.Songs.Count - 1; num2 >= 0; num2--)
		{
			UserSong song = data.Songs[num2];
			if (song != null && !string.IsNullOrEmpty(song.File) && !hashSet.Add(ImportKey(song)))
			{
				data.Songs.RemoveAt(num2);
				data.Settings.RemoveAll((SongSetting setting) => setting != null && string.Equals(setting.Id, song.Id, StringComparison.OrdinalIgnoreCase));
				num++;
			}
		}
		if (num != 0)
		{
			log.LogInfo((object)("Removed " + num + " duplicate song import(s)."));
			Save();
		}
	}

	private static string ImportKey(UserSong song)
	{
		return FileKey(song.File) + "|" + PlaceKey(song.Bucket);
	}

	private static string FileKey(string path)
	{
		try
		{
			return Path.GetFullPath(path).TrimEnd('\\', '/');
		}
		catch
		{
			return path;
		}
	}

	private static string PlaceKey(string bucket)
	{
		int num = BucketValue(bucket);
		return (num >= 0) ? ("slot:" + num) : ("name:" + (bucket ?? string.Empty));
	}

	private static void Add(Song song, Dictionary<string, SongSetting> settings)
	{
		if (settings.TryGetValue(song.Id, out var value))
		{
			song.Enabled = value.Enabled;
			song.Selected = value.Selected;
			if (value.LoopSeconds.HasValue)
			{
				song.LoopSeconds = Mathf.Clamp(value.LoopSeconds.Value, 0f, 10f);
			}
		}
		if (!songs.Any((Song existing) => existing.Id == song.Id))
		{
			songs.Add(song);
		}
		if (!categories.Contains(song.Category))
		{
			categories.Add(song.Category);
		}
	}

	private static List<string> PackRoots()
	{
		List<string> list = new List<string>();
		if (!string.IsNullOrEmpty(packsPath))
		{
			list.Add(packsPath);
		}
		string text = PluginsDirectory(pluginPath);
		if (text != null)
		{
			try
			{
				string text2 = Path.Combine(text, "FateMusicPlayer", "packs");
				if (Directory.Exists(text2))
				{
					list.Add(text2);
				}
				string[] directories = Directory.GetDirectories(text);
				foreach (string path in directories)
				{
					string text3 = Path.Combine(path, "FateMusicPlayer", "packs");
					if (Directory.Exists(text3))
					{
						list.Add(text3);
					}
				}
			}
			catch (Exception ex)
			{
				log.LogWarning((object)("Could not list " + text + ": " + ex.Message));
			}
		}
		List<string> list2 = new List<string>();
		foreach (string item in list)
		{
			bool flag = false;
			foreach (string item2 in list2)
			{
				if (string.Equals(item2, item, StringComparison.OrdinalIgnoreCase))
				{
					flag = true;
					break;
				}
			}
			if (!flag)
			{
				list2.Add(item);
			}
		}
		return list2;
	}

	private static string PluginsDirectory(string directory)
	{
		string text = directory;
		for (int i = 0; i < 6; i++)
		{
			if (string.IsNullOrEmpty(text))
			{
				break;
			}
			string text2 = text.TrimEnd('\\', '/');
			if (string.Equals(Path.GetFileName(text2), "plugins", StringComparison.OrdinalIgnoreCase))
			{
				return text2;
			}
			text = Path.GetDirectoryName(text2);
		}
		return null;
	}

	private static IEnumerable<KeyValuePair<string, List<Song>>> ScanPacks()
	{
		//IL_00ab: Unknown result type (might be due to invalid IL or missing references)
		List<KeyValuePair<string, List<Song>>> list = new List<KeyValuePair<string, List<Song>>>();
		HashSet<string> hashSet = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
		foreach (string item in PackRoots())
		{
			string[] directories;
			try
			{
				directories = Directory.GetDirectories(item);
			}
			catch (Exception ex)
			{
				log.LogWarning((object)("Could not look in " + item + ": " + ex.Message));
				continue;
			}
			string[] array = directories;
			foreach (string text in array)
			{
				string path = Path.Combine(text, "pack.json");
				if (!File.Exists(path))
				{
					continue;
				}
				PackFile packFile;
				try
				{
					using FileStream fileStream = File.OpenRead(path);
					packFile = (PackFile)((XmlObjectSerializer)new DataContractJsonSerializer(typeof(PackFile))).ReadObject((Stream)fileStream);
				}
				catch (Exception ex)
				{
					log.LogWarning((object)("Skipping pack " + Path.GetFileName(text) + ": " + ex.Message));
					continue;
				}
				if (packFile == null || packFile.Songs == null)
				{
					continue;
				}
				string text2 = (string.IsNullOrEmpty(packFile.Id) ? Path.GetFileName(text) : Sanitize(packFile.Id));
				if (!hashSet.Add(text2))
				{
					continue;
				}
				List<Song> list2 = new List<Song>();
				foreach (PackSong song2 in packFile.Songs)
				{
					if (song2 != null && !string.IsNullOrEmpty(song2.File))
					{
						string text3 = Path.Combine(text, song2.File);
						if (!File.Exists(text3))
						{
							log.LogWarning((object)("Pack " + text2 + " is missing " + song2.File));
							continue;
						}
						Song song = new Song();
						song.Id = "pack:" + text2 + ":" + Sanitize(song2.File);
						song.Title = (string.IsNullOrEmpty(song2.Title) ? Path.GetFileNameWithoutExtension(song2.File) : song2.Title);
						BucketInfo bucketInfo = ResolveBucket(song2.Bucket);
						song.Category = ((bucketInfo != null) ? CategoryForBucket(bucketInfo) : (string.IsNullOrEmpty(song2.Category) ? ("Pack: " + text2) : song2.Category));
						song.BucketName = ((bucketInfo != null) ? bucketInfo.Name : song2.Bucket);
						song.BucketValue = bucketInfo?.Value ?? BucketValue(song2.Bucket);
						song.PackId = text2;
						song.FilePath = text3;
						song.Loop = !song2.Loop.HasValue || song2.Loop.Value;
						song.Volume = ((!song2.Volume.HasValue) ? 1f : Mathf.Clamp01(song2.Volume.Value));
						song.LoopSeconds = ((!song2.LoopSeconds.HasValue) ? 0f : Mathf.Clamp(song2.LoopSeconds.Value, 0f, 10f));
						list2.Add(song);
					}
				}
				list.Add(new KeyValuePair<string, List<Song>>(text2, list2));
				if (list2.Count > 0)
				{
					log.LogInfo((object)("Pack " + text2 + ": " + list2.Count + " song(s) from " + text));
				}
				else
				{
					log.LogWarning((object)("Pack " + text2 + " in " + text + " has no song it can play."));
				}
			}
		}
		return list;
	}

	internal static bool SetEnabled(Song song, bool enabled)
	{
		song.Enabled = enabled;
		Remember(song);
		return true;
	}

	internal static void ResetCategory(string category)
	{
		ResetWhere((Song song) => song.Category == category);
	}

	internal static void ResetAll()
	{
		ResetWhere(null);
	}

	private static void ResetWhere(Func<Song, bool> match)
	{
		foreach (Song song in songs)
		{
			if (match == null || match(song))
			{
				song.Enabled = true;
				song.Selected = false;
				Remember(song);
			}
		}
		Save();
	}

	internal static void SetLoopSeconds(Song song, float seconds)
	{
		float fade = (song.LoopSeconds = Mathf.Round(Mathf.Clamp(seconds, 0f, 10f) / 0.1f) * 0.1f);
		Remember(song, save: false);
		SaveLater();
		AudioLoader.PreviewLoopEdge(song.Id, song.FilePath, song.GameClip, fade);
	}

	internal static bool RemoveUserSong(Song song)
	{
		if (song == null || string.IsNullOrEmpty(song.Id) || (Object)(object)song.GameClip != (Object)null)
		{
			return false;
		}
		if (!string.IsNullOrEmpty(song.PackId))
		{
			return false;
		}
		int num = data.Songs.RemoveAll((UserSong entry) => entry != null && string.Equals(entry.Id, song.Id, StringComparison.OrdinalIgnoreCase));
		data.Settings.RemoveAll((SongSetting setting) => setting != null && string.Equals(setting.Id, song.Id, StringComparison.OrdinalIgnoreCase));
		if (num == 0)
		{
			return false;
		}
		Save();
		songs.RemoveAll((Song row) => row != null && string.Equals(row.Id, song.Id, StringComparison.OrdinalIgnoreCase));
		if (!songs.Any((Song row) => row != null && row.Category == song.Category))
		{
			categories.Remove(song.Category);
		}
		log.LogInfo((object)("Removed " + song.Title + " from " + song.Category + "."));
		return true;
	}

	internal static Song AddUserSong(string path, string category, string bucket, out List<string> replaced)
	{
		replaced = new List<string>();
		UserSong userSong = new UserSong();
		userSong.Id = "user:" + DateTime.UtcNow.Ticks + ":" + Sanitize(Path.GetFileName(path));
		userSong.Title = Path.GetFileNameWithoutExtension(path);
		userSong.Bucket = bucket;
		userSong.File = path;
		userSong.Loop = true;
		userSong.Volume = 1f;
		string b = ImportKey(userSong);
		for (int num = data.Songs.Count - 1; num >= 0; num--)
		{
			UserSong old = data.Songs[num];
			if (old != null && !string.IsNullOrEmpty(old.File) && string.Equals(ImportKey(old), b, StringComparison.OrdinalIgnoreCase))
			{
				replaced.Add(old.Id);
				data.Songs.RemoveAt(num);
				data.Settings.RemoveAll((SongSetting setting) => setting != null && string.Equals(setting.Id, old.Id, StringComparison.OrdinalIgnoreCase));
				songs.RemoveAll((Song row) => row != null && string.Equals(row.Id, old.Id, StringComparison.OrdinalIgnoreCase));
			}
		}
		data.Songs.Add(userSong);
		Save();
		Song song = UserSongRow(userSong);
		Dictionary<string, SongSetting> dictionary = new Dictionary<string, SongSetting>(StringComparer.OrdinalIgnoreCase);
		foreach (SongSetting setting in data.Settings)
		{
			if (setting != null && !string.IsNullOrEmpty(setting.Id))
			{
				dictionary[setting.Id] = setting;
			}
		}
		Add(song, dictionary);
		log.LogInfo((object)("Added " + song.Title + " to " + song.Category + "."));
		return song;
	}

	internal static string BucketForCategory(string category)
	{
		foreach (Song song in songs)
		{
			if (song.Category == category)
			{
				return song.BucketName;
			}
		}
		return null;
	}

	private static BucketInfo ResolveBucket(string text)
	{
		if (string.IsNullOrEmpty(text))
		{
			return null;
		}
		if (int.TryParse(text, out var result))
		{
			return GameMusic.FindBucket(result);
		}
		List<BucketInfo> list = MusicBuckets();
		foreach (BucketInfo item in list)
		{
			if (string.Equals(item.Name, text, StringComparison.OrdinalIgnoreCase))
			{
				return item;
			}
		}
		foreach (BucketInfo item2 in list)
		{
			if (string.Equals(CategoryForBucket(item2), text, StringComparison.OrdinalIgnoreCase))
			{
				return item2;
			}
		}
		foreach (BucketInfo item3 in list)
		{
			string a = CategoryForBucket(item3).Replace(" Music", string.Empty);
			if (string.Equals(a, text, StringComparison.OrdinalIgnoreCase))
			{
				return item3;
			}
		}
		return null;
	}

	internal static int BucketValue(string name)
	{
		if (string.IsNullOrEmpty(name))
		{
			return -1;
		}
		if (int.TryParse(name, out var result))
		{
			return result;
		}
		foreach (BucketInfo item in GameMusic.Buckets())
		{
			if (string.Equals(item.Name, name, StringComparison.OrdinalIgnoreCase))
			{
				return item.Value;
			}
		}
		return -1;
	}

	private static void Remember(Song song)
	{
		Remember(song, save: true);
	}

	private static void Remember(Song song, bool save)
	{
		SongSetting songSetting = data.Settings.FirstOrDefault((SongSetting s) => s != null && string.Equals(s.Id, song.Id, StringComparison.OrdinalIgnoreCase));
		if (song.Enabled && !song.Selected && !(song.LoopSeconds > 0.01f))
		{
			if (songSetting != null)
			{
				data.Settings.Remove(songSetting);
			}
			if (save)
			{
				Save();
			}
			return;
		}
		if (songSetting == null)
		{
			songSetting = new SongSetting();
			songSetting.Id = song.Id;
			data.Settings.Add(songSetting);
		}
		songSetting.Enabled = song.Enabled;
		songSetting.Selected = song.Selected;
		songSetting.LoopSeconds = ((song.LoopSeconds > 0.01f) ? new float?(song.LoopSeconds) : ((float?)null));
		if (save)
		{
			Save();
		}
	}

	internal static string FriendlyTitle(string raw)
	{
		if (string.IsNullOrEmpty(raw))
		{
			return "Untitled";
		}
		string text = raw.Replace('_', ' ').Replace('-', ' ').Trim();
		StringBuilder stringBuilder = new StringBuilder(text.Length + 8);
		for (int i = 0; i < text.Length; i++)
		{
			char c = text[i];
			if (i > 0 && char.IsUpper(c) && !char.IsUpper(text[i - 1]) && text[i - 1] != ' ')
			{
				stringBuilder.Append(' ');
			}
			stringBuilder.Append(c);
		}
		return stringBuilder.ToString();
	}

	private static List<BucketInfo> MusicBuckets()
	{
		List<BucketInfo> list = GameMusic.Buckets();
		List<BucketInfo> list2 = new List<BucketInfo>();
		foreach (BucketInfo item in list)
		{
			if (IsMusic(item))
			{
				list2.Add(item);
			}
		}
		if (list2.Count == 0)
		{
			list2 = list;
		}
		list2.Sort(CompareBuckets);
		return list2;
	}

	private static bool IsMusic(BucketInfo bucket)
	{
		if (bucket == null)
		{
			return false;
		}
		if (IsIntro(bucket))
		{
			return true;
		}
		if (Contains(bucket.Name, "Music"))
		{
			return true;
		}
		return Contains(bucket.EditorName, "Music");
	}

	private static bool IsIntro(BucketInfo bucket)
	{
		if (bucket == null)
		{
			return false;
		}
		if (Contains(bucket.Name, "Intro"))
		{
			return true;
		}
		return Contains(bucket.EditorName, "Intro");
	}

	private static int CompareBuckets(BucketInfo left, BucketInfo right)
	{
		int num = Rank(left).CompareTo(Rank(right));
		if (num != 0)
		{
			return num;
		}
		return left.Value.CompareTo(right.Value);
	}

	private static int Rank(BucketInfo bucket)
	{
		if (IsIntro(bucket))
		{
			return 0;
		}
		if (IsTown(bucket))
		{
			return 1;
		}
		if (Contains(bucket.Name, "Dungeon"))
		{
			return 2;
		}
		return 3;
	}

	private static bool IsTown(BucketInfo bucket)
	{
		return Contains(bucket.Name, "Temple") || Contains(bucket.Name, "Town");
	}

	private static bool Contains(string text, string value)
	{
		return !string.IsNullOrEmpty(text) && text.IndexOf(value, StringComparison.OrdinalIgnoreCase) >= 0;
	}

	private static string CategoryForBucket(BucketInfo bucket)
	{
		if (IsIntro(bucket))
		{
			return "Intro Music";
		}
		if (IsTown(bucket))
		{
			return "Town Music";
		}
		if (Contains(bucket.Name, "Dungeon"))
		{
			return "Dungeon Music";
		}
		if (Contains(bucket.Name, "FateChoice"))
		{
			return "Fate Choice Music";
		}
		if (!string.IsNullOrEmpty(bucket.EditorName))
		{
			return FriendlyTitle(bucket.EditorName);
		}
		return CategoryForBucketName(bucket.Name);
	}

	internal static string CategoryForBucketName(string bucketName)
	{
		if (!string.IsNullOrEmpty(bucketName))
		{
			string text = bucketName.ToLowerInvariant();
			if (text.Contains("intro"))
			{
				return "Intro Music";
			}
			if (text.Contains("dungeon"))
			{
				return "Dungeons";
			}
			if (text.Contains("templefate") || text.Contains("town") || text.Contains("village"))
			{
				return "Town";
			}
			if (text.Contains("fatechoice"))
			{
				return "Fate events";
			}
			if (text.StartsWith("music_") || text.Contains("mainmenu") || text.Contains("menu"))
			{
				return "Main menu";
			}
			if (text.Contains("boss"))
			{
				return "Bosses";
			}
			return "Other music";
		}
		return "Other music";
	}

	internal static string Sanitize(string text)
	{
		if (string.IsNullOrEmpty(text))
		{
			return "item";
		}
		StringBuilder stringBuilder = new StringBuilder(text.Length);
		foreach (char c in text)
		{
			stringBuilder.Append((char.IsLetterOrDigit(c) || c == '.' || c == '_' || c == '-') ? c : '_');
		}
		string text2 = stringBuilder.ToString();
		return (text2.Length > 48) ? text2.Substring(0, 48) : text2;
	}
}
internal sealed class LoopPlayer : MonoBehaviour
{
	private static LoopPlayer host;

	private AudioSource first;

	private AudioSource second;

	private AudioClip clip;

	private float level = 1f;

	private float fade;

	private int active;

	private bool fading;

	private float fadeStartedAt;

	private bool silenced;

	internal static bool Running => (Object)(object)host != (Object)null && (Object)(object)host.clip != (Object)null;

	private float Effective => level * GameMusic.MusicScale;

	internal static void Play(AudioClip clip, float volume, float fadeSeconds)
	{
		//IL_002c: Unknown result type (might be due to invalid IL or missing references)
		//IL_0032: Expected O, but got Unknown
		if (!((Object)(object)clip == (Object)null))
		{
			if ((Object)(object)host == (Object)null)
			{
				GameObject val = new GameObject("FATE Music Player loop");
				Object.DontDestroyOnLoad((Object)(object)val);
				host = val.AddComponent<LoopPlayer>();
				host.first = host.NewSource("loop A");
				host.second = host.NewSource("loop B");
			}
			host.clip = clip;
			host.level = Mathf.Clamp01(volume);
			host.fade = Mathf.Clamp(fadeSeconds, 0f, Mathf.Max(0f, clip.length * 0.5f));
			host.active = 0;
			host.fading = false;
			host.silenced = false;
			GameMusic.Route(host.first);
			GameMusic.Route(host.second);
			host.first.Stop();
			host.second.Stop();
			host.first.clip = clip;
			host.first.loop = false;
			host.first.volume = host.Effective;
			host.first.time = 0f;
			host.first.Play();
		}
	}

	internal static void Stop()
	{
		if (!((Object)(object)host == (Object)null))
		{
			host.clip = null;
			host.fading = false;
			host.first.Stop();
			host.second.Stop();
		}
	}

	internal static void SetSilenced(bool value)
	{
		if (!((Object)(object)host == (Object)null))
		{
			host.silenced = value;
			if ((Object)(object)host.first != (Object)null)
			{
				host.first.volume = (value ? 0f : host.Effective);
			}
			if ((Object)(object)host.second != (Object)null)
			{
				host.second.volume = 0f;
			}
		}
	}

	private AudioSource NewSource(string name)
	{
		//IL_0002: Unknown result type (might be due to invalid IL or missing references)
		//IL_0008: Expected O, but got Unknown
		GameObject val = new GameObject(name);
		val.transform.SetParent(((Component)this).transform, false);
		AudioSource val2 = val.AddComponent<AudioSource>();
		val2.playOnAwake = false;
		val2.loop = false;
		val2.spatialBlend = 0f;
		return val2;
	}

	private void Update()
	{
		if ((Object)(object)clip == (Object)null || silenced)
		{
			return;
		}
		AudioSource val = ((active == 0) ? first : second);
		AudioSource val2 = ((active == 0) ? second : first);
		if (!fading)
		{
			val.volume = Effective;
			float num = clip.length - val.time;
			if (!(num > fade))
			{
				if (fade <= 0.001f)
				{
					val.time = 0f;
					return;
				}
				val2.clip = clip;
				val2.loop = false;
				val2.time = 0f;
				val2.volume = 0f;
				val2.Play();
				fading = true;
				fadeStartedAt = Time.unscaledTime;
			}
		}
		else
		{
			float num2 = (Time.unscaledTime - fadeStartedAt) / fade;
			if (num2 >= 1f)
			{
				val.Stop();
				val.volume = 0f;
				val2.volume = Effective;
				active = 1 - active;
				fading = false;
			}
			else
			{
				float effective = Effective;
				val2.volume = effective * num2;
				val.volume = effective * (1f - num2);
			}
		}
	}

	private void OnDestroy()
	{
		host = null;
	}
}
internal static class ModMenuBridge
{
	private const int MenuCap = 256;

	private const int PacksRows = 3;

	private static object module;

	private static float nextAttempt;

	private static float rebuildAt = -1f;

	private static float missingSince = -1f;

	private static bool warnedMissing;

	private static bool warnedLegacy;

	private static bool warnedNoRows;

	private static ManualLogSource log;

	private static MethodInfo trackRow;

	private static MethodInfo removeRow;

	private static MethodInfo simpleToggle;

	private static MethodInfo simpleAction;

	private static MethodInfo simpleSlider;

	internal static void Init(ManualLogSource logger)
	{
		log = logger;
	}

	internal static void Tick()
	{
		if (module == null)
		{
			if (!(Time.unscaledTime < nextAttempt))
			{
				nextAttempt = Time.unscaledTime + 2f;
				Register();
			}
		}
		else if (rebuildAt > 0f && Time.unscaledTime >= rebuildAt)
		{
			rebuildAt = -1f;
			Register();
		}
	}

	internal static void RebuildSoon()
	{
		rebuildAt = Time.unscaledTime + 0.25f;
	}

	private static void Register()
	{
		try
		{
			Type type = AccessTools.TypeByName("ZombieGhost.ModMenu.ModMenuApi");
			MethodInfo methodInfo = ((type == null) ? null : type.GetMethod("RegisterModule", new Type[2]
			{
				typeof(string),
				typeof(string)
			}));
			if (type == null || methodInfo == null)
			{
				if (missingSince < 0f)
				{
					missingSince = Time.unscaledTime;
				}
				else if (!warnedMissing && Time.unscaledTime - missingSince > 10f)
				{
					warnedMissing = true;
					if (log != null)
					{
						log.LogWarning((object)("FATE Mod Menu API was not found (" + ((type == null) ? "ZombieGhost.ModMenu.ModMenuApi type" : "RegisterModule method") + "); the player has no settings screen. Install FATE_ModMenu to configure it."));
					}
				}
				return;
			}
			Dispose();
			module = methodInfo.Invoke(null, new object[2] { "mikhailkillszombies.musicplayer", "FATE Music Player" });
			if (module != null)
			{
				trackRow = (removeRow = (simpleToggle = (simpleAction = (simpleSlider = null))));
				Type type2 = module.GetType();
				MethodInfo method = type2.GetMethod("RegisterNote");
				MethodInfo method2 = type2.GetMethod("RegisterTrack");
				MethodInfo method3 = type2.GetMethod("RegisterWideAction");
				if (method2 != null && method3 != null)
				{
					RegisterTrackRows(type2, method2, method3, method, type);
				}
				else
				{
					RegisterSimpleRows(type2, method, type);
				}
				missingSince = -1f;
				warnedMissing = false;
			}
		}
		catch (Exception ex)
		{
			if (log != null)
			{
				log.LogWarning((object)("Could not register with FATE Mod Menu: " + ex.Message));
			}
			module = null;
		}
	}

	private static void RegisterTrackRows(Type type, MethodInfo track, MethodInfo wide, MethodInfo note, Type api)
	{
		MethodInfo method = type.GetMethod("SetCategoryResetCallback");
		MethodInfo method2 = type.GetMethod("SetResetCallback");
		MethodInfo method3 = type.GetMethod("RegisterTrackRemoveAction");
		trackRow = track;
		removeRow = method3;
		simpleToggle = (simpleAction = (simpleSlider = null));
		if (method != null)
		{
			method.Invoke(module, new object[1]
			{
				new Action<string>(Library.ResetCategory)
			});
		}
		if (method2 != null)
		{
			method2.Invoke(module, new object[1]
			{
				new Action(Library.ResetAll)
			});
		}
		List<Song> songs = Library.Songs;
		List<string> list = songs.Select((Song song) => song.Category).Distinct().ToList();
		int num = 253 - list.Count;
		int num2 = 0;
		foreach (string item in list)
		{
			string label = item;
			foreach (Song item2 in songs.Where((Song s) => s.Category == label))
			{
				if (num2 >= num)
				{
					break;
				}
				num2++;
				RegisterSongRow(track, method3, item2);
			}
			wide.Invoke(module, new object[4]
			{
				Id(label, "add"),
				"ADD SONG",
				(Action)delegate
				{
					AddSong(label);
				},
				label
			});
		}
		RegisterPackRows(wide, note, num2, num, songs.Count);
		if (log != null)
		{
			log.LogInfo((object)("Registered with FATE Mod Menu: " + num2 + " song row(s) in " + list.Count + " category(ies)."));
		}
	}

	private static void RegisterSimpleRows(Type type, MethodInfo note, Type api)
	{
		MethodInfo method = type.GetMethod("RegisterToggle");
		MethodInfo method2 = type.GetMethod("RegisterAction");
		MethodInfo method3 = type.GetMethod("RegisterSlider");
		if (method == null || method2 == null)
		{
			if (!warnedNoRows)
			{
				warnedNoRows = true;
				if (log != null)
				{
					log.LogWarning((object)"FATE Mod Menu has no RegisterToggle/RegisterAction; the player has no settings screen.");
				}
			}
			Dispose();
			return;
		}
		simpleToggle = method;
		simpleAction = method2;
		simpleSlider = method3;
		trackRow = (removeRow = null);
		if (!warnedLegacy)
		{
			warnedLegacy = true;
			if (log != null)
			{
				log.LogWarning((object)string.Concat("FATE Mod Menu API ", ApiVersion(api), " is older than the player: showing the compact song list (three rows per song). Install FATE_ModMenu 0.4.0 for one row per song."));
			}
		}
		List<Song> songs = Library.Songs;
		List<string> list = songs.Select((Song song) => song.Category).Distinct().ToList();
		int num = 84;
		int num2 = 0;
		foreach (string item in list)
		{
			string label = item;
			foreach (Song item2 in songs.Where((Song s) => s.Category == label))
			{
				if (num2 >= num)
				{
					break;
				}
				num2++;
				RegisterSimpleSong(method, method2, method3, item2);
			}
			method2.Invoke(module, new object[4]
			{
				Id(label, "add"),
				"ADD SONG",
				(Action)delegate
				{
					AddSong(label);
				},
				label
			});
		}
		RegisterPackRows(method2, note, num2, num, songs.Count);
		if (log != null)
		{
			log.LogInfo((object)("Registered with FATE Mod Menu (compact): " + num2 + " song row(s) in " + list.Count + " category(ies)."));
		}
	}

	private static void RegisterSimpleSong(MethodInfo toggle, MethodInfo action, MethodInfo slider, Song song)
	{
		toggle.Invoke(module, new object[5]
		{
			Id("on", song.Id),
			song.Title,
			(Func<bool>)(() => song.Enabled),
			(Action<bool>)delegate(bool value)
			{
				Library.SetEnabled(song, value);
			},
			song.Category
		});
		action.Invoke(module, new object[4]
		{
			Id("preview", song.Id),
			"   → Play first 5 seconds",
			(Action)delegate
			{
				Preview(song);
			},
			song.Category
		});
		if (!(slider == null))
		{
			slider.Invoke(module, new object[10]
			{
				Id("loop", song.Id),
				"   → Loop timer (seconds)",
				(Func<float>)(() => song.LoopSeconds),
				(Action<float>)delegate(float value)
				{
					Library.SetLoopSeconds(song, value);
				},
				0f,
				10f,
				0.1f,
				song.Category,
				null,
				1600f
			});
		}
	}

	private static void RegisterSongRow(MethodInfo registerTrack, MethodInfo removeAction, Song song)
	{
		string text = Id("song", song.Id);
		registerTrack.Invoke(module, new object[15]
		{
			text,
			song.Title,
			(Func<bool>)(() => song.Enabled),
			(Action<bool>)delegate(bool value)
			{
				Library.SetEnabled(song, value);
			},
			(Action)delegate
			{
				Preview(song);
			},
			(Action)delegate
			{
				AudioLoader.StopPreview();
			},
			(Func<bool>)(() => AudioLoader.IsPlaying(song.Id)),
			null,
			null,
			(Func<float>)(() => song.LoopSeconds),
			(Action<float>)delegate(float value)
			{
				Library.SetLoopSeconds(song, value);
			},
			0f,
			10f,
			0.1f,
			song.Category
		});
		if (!(removeAction == null) && song.IsCustom && string.IsNullOrEmpty(song.PackId))
		{
			removeAction.Invoke(module, new object[2]
			{
				text,
				(Action)delegate
				{
					RemoveSong(song);
				}
			});
		}
	}

	private static void RegisterPackRows(MethodInfo action, MethodInfo note, int shown, int budget, int total)
	{
		action.Invoke(module, new object[4]
		{
			"packs.export",
			"EXPORT SONG PACK",
			(Action)delegate
			{
				PackExporter.Export();
			},
			"Song packs"
		});
		action.Invoke(module, new object[4]
		{
			"packs.rescan",
			"RESCAN SONG PACKS",
			(Action)delegate
			{
				Library.Scan();
				RebuildSoon();
				Toast("Song packs rescanned.");
			},
			"Song packs"
		});
		if (!(note == null))
		{
			note.Invoke(module, new object[3] { "packs.note", "A pack installs into BepInEx/plugins/FateMusicPlayer/packs and adds its songs without touching yours. Removing the package removes its songs again.", "Song packs" });
			if (shown >= budget && total > shown)
			{
				note.Invoke(module, new object[3]
				{
					"limit.note",
					"This list is capped at " + shown + " songs by the menu's 256-setting limit; the rest stay in the pack or library but are not shown.",
					"Song packs"
				});
			}
		}
	}

	private static object ApiVersion(Type api)
	{
		try
		{
			PropertyInfo propertyInfo = ((api == null) ? null : api.GetProperty("ApiVersion"));
			return (propertyInfo == null) ? "?" : propertyInfo.GetValue(null, null);
		}
		catch
		{
			return "?";
		}
	}

	private static void Preview(Song song)
	{
		if (song == null)
		{
			return;
		}
		if (song.IsCustom)
		{
			if (log != null)
			{
				log.LogInfo((object)("Preview asked for " + song.Title + " (file: " + song.FilePath + ")"));
			}
			AudioLoader.Preview(song.Id, song.FilePath, 5f);
		}
		else if ((Object)(object)song.GameClip == (Object)null)
		{
			if (log != null)
			{
				log.LogWarning((object)("Preview asked for " + song.Title + " but it has no game clip."));
			}
			Toast("That song has nothing to preview.");
		}
		else
		{
			if (log != null)
			{
				log.LogInfo((object)("Preview asked for " + song.Title + " (game clip)"));
			}
			AudioLoader.PreviewClip(song.Id, song.GameClip, 5f);
		}
	}

	private static void RemoveSong(Song song)
	{
		string rowId = ((song == null) ? null : Id("song", song.Id));
		if (!Library.RemoveUserSong(song))
		{
			Toast("That song could not be removed.");
			return;
		}
		AudioLoader.StopPreview();
		if (!DropRow(rowId))
		{
			RebuildSoon();
		}
		Toast("Removed " + song.Title + ".");
	}

	private static bool DropRow(string rowId)
	{
		if (module == null || string.IsNullOrEmpty(rowId))
		{
			return false;
		}
		try
		{
			IList list = Settings();
			if (list == null)
			{
				return false;
			}
			object obj = FindRow(list, rowId);
			if (obj == null)
			{
				return false;
			}
			FieldInfo fieldInfo = FindField(obj.GetType(), "Removed");
			if (fieldInfo != null)
			{
				fieldInfo.SetValue(obj, true);
			}
			list.Remove(obj);
			RequestRefresh();
			return true;
		}
		catch (Exception ex)
		{
			if (log != null)
			{
				log.LogWarning((object)("Could not take the row out in place, rebuilding the list instead: " + ex.Message));
			}
			return false;
		}
	}

	private static IList Settings()
	{
		if (module == null)
		{
			return null;
		}
		FieldInfo field = module.GetType().GetField("Settings", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
		return (field == null) ? null : (field.GetValue(module) as IList);
	}

	private static object FindRow(IList settings, string rowId)
	{
		if (settings == null || string.IsNullOrEmpty(rowId))
		{
			return null;
		}
		foreach (object setting in settings)
		{
			if (setting != null)
			{
				PropertyInfo property = setting.GetType().GetProperty("Id");
				if (!(property == null) && string.Equals(property.GetValue(setting, null) as string, rowId, StringComparison.Ordinal))
				{
					return setting;
				}
			}
		}
		return null;
	}

	private static bool AppendSongRow(Song song)
	{
		if (module == null || song == null)
		{
			return false;
		}
		try
		{
			if (trackRow != null)
			{
				RegisterSongRow(trackRow, removeRow, song);
				MoveBeforeAddRow(Id("song", song.Id), Id(song.Category, "add"));
			}
			else
			{
				if (!(simpleToggle != null) || !(simpleAction != null))
				{
					return false;
				}
				RegisterSimpleSong(simpleToggle, simpleAction, simpleSlider, song);
				MoveBeforeAddRow(Id("on", song.Id), Id(song.Category, "add"));
				MoveBeforeAddRow(Id("preview", song.Id), Id(song.Category, "add"));
				MoveBeforeAddRow(Id("loop", song.Id), Id(song.Category, "add"));
			}
			RequestRefresh();
			return true;
		}
		catch (Exception ex)
		{
			if (log != null)
			{
				log.LogWarning((object)("Could not add the row in place, rebuilding the list instead: " + ex.Message));
			}
			return false;
		}
	}

	private static void MoveBeforeAddRow(string rowId, string addRowId)
	{
		IList list = Settings();
		if (list != null)
		{
			object obj = FindRow(list, rowId);
			object obj2 = FindRow(list, addRowId);
			if (obj != null && obj2 != null)
			{
				int index = list.IndexOf(obj2);
				list.Remove(obj);
				list.Insert(index, obj);
			}
		}
	}

	private static FieldInfo FindField(Type type, string name)
	{
		Type type2 = type;
		while (type2 != null)
		{
			FieldInfo field = type2.GetField(name, BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
			if (field != null)
			{
				return field;
			}
			type2 = type2.BaseType;
		}
		return null;
	}

	private static void RequestRefresh()
	{
		Type type = AccessTools.TypeByName("ZombieGhost.ModMenu.ModMenuPlugin");
		MethodInfo methodInfo = ((type == null) ? null : AccessTools.Method(type, "RequestRefresh", new Type[0], (Type[])null));
		if (methodInfo == null)
		{
			return;
		}
		try
		{
			methodInfo.Invoke(null, null);
		}
		catch
		{
		}
	}

	private static void AddSong(string category)
	{
		//IL_0043: Unknown result type (might be due to invalid IL or missing references)
		//IL_0049: Invalid comparison between Unknown and I4
		string text = Library.BucketForCategory(category);
		if (string.IsNullOrEmpty(text))
		{
			Toast("This category has no game music slot to play in.");
			return;
		}
		string text2 = Dialogs.PickAudioFile();
		if (string.IsNullOrEmpty(text2))
		{
			return;
		}
		if ((int)AudioLoader.AudioTypeFor(text2) == 0)
		{
			Toast("Use an .ogg, .wav or .mp3 file.");
			return;
		}
		List<string> replaced;
		Song song = Library.AddUserSong(text2, category, text, out replaced);
		foreach (string item in replaced)
		{
			DropRow(Id("song", item));
			DropRow(Id("on", item));
			DropRow(Id("preview", item));
			DropRow(Id("loop", item));
		}
		if (song == null || !AppendSongRow(song))
		{
			RebuildSoon();
		}
		Toast("Added " + Path.GetFileName(text2) + ".");
	}

	private static string Id(string prefix, string value)
	{
		string text = (prefix + "." + value).Replace('|', '_');
		return (text.Length > 96) ? text.Substring(0, 96) : text;
	}

	private static void Toast(string message)
	{
		FateMusicPlayerPlugin.Toast(message);
	}

	private static void Dispose()
	{
		if (module != null)
		{
			try
			{
				(module as IDisposable).Dispose();
			}
			catch
			{
			}
			module = null;
		}
	}
}
internal static class MusicEngine
{
	private static float lastComplaint;

	private static bool BeforePlayMusic(object __instance, object[] __args)
	{
		try
		{
			if (!FateMusicPlayerPlugin.ModEnabled)
			{
				return true;
			}
			if (__args == null || __args.Length < 2)
			{
				return true;
			}
			int bucketValue = Convert.ToInt32(__args[0]);
			bool stopOthers = Convert.ToBoolean(__args[1]);
			return Intercept(bucketValue, stopOthers);
		}
		catch (Exception ex)
		{
			Complain("playing music failed, falling back to vanilla: " + ex.Message, throttle: true);
			return true;
		}
	}

	internal static bool Intercept(int bucketValue, bool stopOthers)
	{
		List<Song> list = Library.SongsForBucket(bucketValue);
		if (list.Count == 0)
		{
			LoopPlayer.Stop();
			return true;
		}
		List<Song> list2 = list.Where((Song song2) => song2.Enabled).ToList();
		Song song = list2.FirstOrDefault((Song song2) => song2.Selected);
		if (!list.Any((Song song2) => song2.IsCustom) && song == null && list2.Count == list.Count)
		{
			LoopPlayer.Stop();
			return true;
		}
		if (list2.Count == 0)
		{
			GameMusic.StopAllMusic();
			GameMusic.RememberSlot(bucketValue, stopped: true);
			return false;
		}
		Song chosen = song ?? list2[Random.Range(0, list2.Count)];
		return Play(chosen, bucketValue, stopOthers);
	}

	private static bool Play(Song chosen, int bucketValue, bool stopOthers)
	{
		AudioClip val = chosen.GameClip;
		if ((Object)(object)val == (Object)null && chosen.IsCustom)
		{
			val = AudioLoader.Cached(chosen.FilePath);
			if ((Object)(object)val == (Object)null)
			{
				AudioLoader.Request(chosen.FilePath);
				if (stopOthers)
				{
					GameMusic.StopAllMusic();
				}
				GameMusic.RememberSlot(bucketValue, stopped: false);
				return false;
			}
		}
		if ((Object)(object)val == (Object)null)
		{
			LoopPlayer.Stop();
			return true;
		}
		if (stopOthers)
		{
			GameMusic.StopAllMusic();
		}
		if (chosen.Loop && chosen.LoopSeconds > 0.01f)
		{
			LoopPlayer.Play(val, chosen.Volume, chosen.LoopSeconds);
			GameMusic.RememberSlot(bucketValue, stopped: true);
			return false;
		}
		LoopPlayer.Stop();
		AudioSource val2 = GameMusic.FreeMusicSource();
		if ((Object)(object)val2 == (Object)null)
		{
			return false;
		}
		val2.clip = val;
		val2.loop = chosen.Loop;
		val2.volume = Mathf.Clamp01(chosen.Volume);
		val2.time = 0f;
		val2.Play();
		GameMusic.RememberSlot(bucketValue, stopped: false);
		return false;
	}

	private static void AfterDungeonMusic(object __instance)
	{
		try
		{
			int num = GameMusic.DungeonMusicValue();
			if (num >= 0)
			{
				List<Song> source = Library.SongsForBucket(num);
				List<Song> list = source.Where((Song song) => (Object)(object)song.GameClip != (Object)null || !string.IsNullOrEmpty(song.FilePath)).ToList();
				if (list.Count != 0 && (list.Any((Song song) => (Object)(object)song.GameClip == (Object)null) || list.Any((Song song) => !song.Enabled || song.Selected)))
				{
					GameMusic.SilenceDungeonSource(__instance);
					Intercept(num, stopOthers: true);
				}
			}
		}
		catch (Exception ex)
		{
			Complain("taking over the dungeon music failed, the floor's own track plays: " + ex.Message, throttle: false);
		}
	}

	private static void AfterMainMenuMusic()
	{
		try
		{
			List<int> list = Library.CategorySlots("Intro Music");
			if (list.Count == 0)
			{
				return;
			}
			List<Song> list2 = new List<Song>();
			foreach (int item in list)
			{
				list2.AddRange(Library.SongsForBucket(item));
			}
			List<Song> list3 = list2.Where((Song song2) => (Object)(object)song2.GameClip != (Object)null || !string.IsNullOrEmpty(song2.FilePath)).ToList();
			if (list3.Count == 0 || (!list3.Any((Song song2) => song2.IsCustom) && !list3.Any((Song song2) => !song2.Enabled || song2.Selected)))
			{
				return;
			}
			List<Song> list4 = list3.Where((Song song2) => song2.Enabled).ToList();
			if (list4.Count == 0)
			{
				GameMusic.StopAllMusic();
				return;
			}
			List<Song> list5 = (list4.Any((Song song2) => song2.IsCustom) ? list4.Where((Song song2) => song2.IsCustom).ToList() : list4);
			Song song = list5.FirstOrDefault((Song song2) => song2.Selected) ?? list5[Random.Range(0, list5.Count)];
			GameMusic.StopAllMusic();
			Play(song, song.BucketValue, stopOthers: true);
			if (FateMusicPlayerPlugin.LogSource != null)
			{
				FateMusicPlayerPlugin.LogSource.LogInfo((object)("Title music: " + song.Title + " (slot " + song.BucketValue + ")."));
			}
		}
		catch (Exception ex)
		{
			Complain("taking over the title music failed, the game's own plays: " + ex.Message, throttle: false);
		}
	}

	private static void BeforeGameStopsMusic()
	{
		LoopPlayer.Stop();
	}

	internal static void Complain(string message, bool throttle)
	{
		if (!throttle || !(Time.unscaledTime - lastComplaint < 10f))
		{
			lastComplaint = Time.unscaledTime;
			if (FateMusicPlayerPlugin.LogSource != null)
			{
				FateMusicPlayerPlugin.LogSource.LogWarning((object)message);
			}
		}
	}
}
internal static class MusicRuntime
{
	private static ManualLogSource log;

	private static Harmony harmony;

	private static bool patched;

	private static bool scanned;

	private static bool tickerCreated;

	private static float nextPatchAttempt;

	private static float nextTick;

	private static float nextScan;

	private static int emptyScans;

	internal static void Init(ManualLogSource logger, Harmony instance)
	{
		log = logger;
		harmony = instance;
		EnsureTicker();
		patched = GameMusic.Patch(harmony, log);
		nextPatchAttempt = Time.unscaledTime + 3f;
	}

	private static void EnsureTicker()
	{
		//IL_001a: Unknown result type (might be due to invalid IL or missing references)
		//IL_0020: Expected O, but got Unknown
		if (!tickerCreated)
		{
			tickerCreated = true;
			GameObject val = new GameObject("FATE Music Player ticker");
			Object.DontDestroyOnLoad((Object)(object)val);
			val.AddComponent<MusicTicker>();
		}
	}

	internal static void Tick()
	{
		float unscaledTime = Time.unscaledTime;
		if (unscaledTime < nextTick)
		{
			return;
		}
		nextTick = unscaledTime + 0.2f;
		EnsureTicker();
		AudioLoader.EnsureHost(log);
		if (!patched && unscaledTime >= nextPatchAttempt)
		{
			nextPatchAttempt = unscaledTime + 3f;
			patched = GameMusic.Patch(harmony, log);
		}
		if (GameMusic.Manager() != null)
		{
			if (!scanned)
			{
				scanned = true;
				Library.Scan();
				ModMenuBridge.RebuildSoon();
				if (log != null)
				{
					log.LogInfo((object)("Music library built: " + Library.Songs.Count + " song(s) in " + Library.Categories.Count + " category(ies)."));
				}
			}
			else if (!Library.HasGameSongs && emptyScans < 30 && unscaledTime >= nextScan)
			{
				nextScan = unscaledTime + 2f;
				emptyScans++;
				Library.Scan();
				if (Library.HasGameSongs)
				{
					ModMenuBridge.RebuildSoon();
					if (log != null)
					{
						log.LogInfo((object)("Music library built after the audio manager filled in: " + Library.Songs.Count + " song(s) in " + Library.Categories.Count + " category(ies)."));
					}
				}
			}
		}
		Library.FlushSaves();
		ModMenuBridge.Tick();
	}

	internal static void AfterAudioManagerUpdate()
	{
		Tick();
	}

	internal static void Rescan()
	{
		scanned = true;
		Library.Scan();
		ModMenuBridge.RebuildSoon();
	}
}
internal sealed class MusicTicker : MonoBehaviour
{
	private void Update()
	{
		MusicRuntime.Tick();
	}
}
internal static class Dialogs
{
	internal static string PickAudioFile()
	{
		string result = null;
		Run(delegate
		{
			//IL_0001: Unknown result type (might be due to invalid IL or missing references)
			//IL_0007: Expected O, but got Unknown
			//IL_0031: Unknown result type (might be due to invalid IL or missing references)
			//IL_0037: Invalid comparison between Unknown and I4
			OpenFileDialog val = new OpenFileDialog();
			try
			{
				((FileDialog)val).Title = "Add a song";
				((FileDialog)val).Filter = "Audio files (*.ogg;*.wav;*.mp3;*.aiff)|*.ogg;*.wav;*.mp3;*.aiff|All files (*.*)|*.*";
				((FileDialog)val).CheckFileExists = true;
				val.Multiselect = false;
				if ((int)((CommonDialog)val).ShowDialog() == 1)
				{
					result = ((FileDialog)val).FileName;
				}
			}
			finally
			{
				((IDisposable)val)?.Dispose();
			}
		});
		return result;
	}

	internal static string PickExportFile(string suggested)
	{
		string result = null;
		Run(delegate
		{
			//IL_0001: Unknown result type (might be due to invalid IL or missing references)
			//IL_0007: Expected O, but got Unknown
			//IL_0036: Unknown result type (might be due to invalid IL or missing references)
			//IL_003c: Invalid comparison between Unknown and I4
			SaveFileDialog val = new SaveFileDialog();
			try
			{
				((FileDialog)val).Title = "Export song pack";
				((FileDialog)val).Filter = "Thunderstore package (*.zip)|*.zip";
				((FileDialog)val).FileName = suggested;
				val.OverwritePrompt = true;
				if ((int)((CommonDialog)val).ShowDialog() == 1)
				{
					result = ((FileDialog)val).FileName;
				}
			}
			finally
			{
				((IDisposable)val)?.Dispose();
			}
		});
		return result;
	}

	private static void Run(Action work)
	{
		Exception failure = null;
		Thread thread = new Thread((ThreadStart)delegate
		{
			try
			{
				work();
			}
			catch (Exception ex)
			{
				failure = ex;
			}
		});
		thread.IsBackground = true;
		thread.SetApartmentState(ApartmentState.STA);
		thread.Start();
		thread.Join();
		if (failure != null && FateMusicPlayerPlugin.LogSource != null)
		{
			FateMusicPlayerPlugin.LogSource.LogError((object)("The file window could not open: " + failure.Message));
		}
	}
}
internal static class PackExporter
{
	private const string Author = "MikhailKillsZombies";

	private const string ModName = "FATE_MusicPlayer";

	private const string ModId = "MikhailKillsZombies-FATE_MusicPlayer";

	internal static string Export()
	{
		List<Song> list = Library.Songs.Where((Song song) => (Object)(object)song.GameClip == (Object)null && string.IsNullOrEmpty(song.PackId) && !string.IsNullOrEmpty(song.FilePath)).ToList();
		if (list.Count == 0)
		{
			Message("There are no songs of your own to export yet. Use ADD SONG under a category first.");
			return null;
		}
		string text = Dialogs.PickExportFile("FATE Music Pack.zip");
		if (string.IsNullOrEmpty(text))
		{
			return null;
		}
		try
		{
			string text2 = Library.Sanitize(Path.GetFileNameWithoutExtension(text));
			if (string.IsNullOrEmpty(text2))
			{
				text2 = "SongPack";
			}
			int num = WritePack(text, text2, list);
			Message("Exported " + num + " song(s) to " + text + ". Requires MikhailKillsZombies-FATE_MusicPlayer-0.4.3.");
			return text;
		}
		catch (Exception ex)
		{
			if (FateMusicPlayerPlugin.LogSource != null)
			{
				FateMusicPlayerPlugin.LogSource.LogError((object)("Export failed: " + ex));
			}
			Message("Export failed: " + ex.Message);
			return null;
		}
	}

	private static int WritePack(string zipPath, string packId, List<Song> songs)
	{
		string text = "BepInEx/plugins/FateMusicPlayer/packs/" + packId + "/";
		List<KeyValuePair<Song, string>> list = new List<KeyValuePair<Song, string>>();
		HashSet<string> hashSet = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
		int num = 0;
		foreach (Song song in songs)
		{
			if (!File.Exists(song.FilePath))
			{
				num++;
				if (FateMusicPlayerPlugin.LogSource != null)
				{
					FateMusicPlayerPlugin.LogSource.LogWarning((object)("Not exporting " + song.Title + ": " + song.FilePath + " is no longer there."));
				}
				continue;
			}
			string fileName = Path.GetFileName(song.FilePath);
			string text2 = fileName;
			int num2 = 2;
			while (!hashSet.Add(text2))
			{
				text2 = Path.GetFileNameWithoutExtension(fileName) + "-" + num2 + Path.GetExtension(fileName);
				num2++;
			}
			list.Add(new KeyValuePair<Song, string>(song, text2));
		}
		if (list.Count == 0)
		{
			throw new IOException("none of the song files are still on disk");
		}
		byte[] array = new byte[81920];
		using (FileStream stream = File.Create(zipPath))
		{
			using ZipArchive zipArchive = new ZipArchive(stream, ZipArchiveMode.Create);
			WriteEntry(zipArchive, "manifest.json", BuildManifest(packId, list.Count));
			WriteEntry(zipArchive, "README.md", BuildReadme(packId, list));
			WriteEntry(zipArchive, text + "pack.json", BuildPackJson(packId, list));
			foreach (KeyValuePair<Song, string> item in list)
			{
				ZipArchiveEntry zipArchiveEntry = zipArchive.CreateEntry(text + item.Value, CompressionLevel.Optimal);
				using Stream stream2 = zipArchiveEntry.Open();
				using FileStream fileStream = File.OpenRead(item.Key.FilePath);
				int count;
				while ((count = fileStream.Read(array, 0, array.Length)) > 0)
				{
					stream2.Write(array, 0, count);
				}
			}
		}
		Verify(zipPath, text);
		if (num > 0)
		{
			Message("Skipped " + num + " song(s) whose file is no longer on disk.");
		}
		return list.Count;
	}

	private static void Verify(string zipPath, string packRoot)
	{
		string[] array = new string[3]
		{
			"manifest.json",
			"README.md",
			packRoot + "pack.json"
		};
		using ZipArchive zipArchive = new ZipArchive(File.OpenRead(zipPath), ZipArchiveMode.Read);
		string[] array2 = array;
		foreach (string text in array2)
		{
			if (zipArchive.GetEntry(text) == null)
			{
				throw new InvalidDataException("the package is missing " + text);
			}
		}
		using StreamReader streamReader = new StreamReader(zipArchive.GetEntry("manifest.json").Open());
		string text2 = streamReader.ReadToEnd();
		if (text2.IndexOf("MikhailKillsZombies-FATE_MusicPlayer-", StringComparison.Ordinal) < 0)
		{
			throw new InvalidDataException("the package does not depend on MikhailKillsZombies-FATE_MusicPlayer");
		}
		string text = ManifestName(text2);
		if (!IsThunderstoreName(text))
		{
			throw new InvalidDataException("Thunderstore cannot accept the package name \"" + text + "\"");
		}
	}

	private static void WriteEntry(ZipArchive archive, string name, string content)
	{
		ZipArchiveEntry zipArchiveEntry = archive.CreateEntry(name, CompressionLevel.Optimal);
		using StreamWriter streamWriter = new StreamWriter(zipArchiveEntry.Open(), new UTF8Encoding(encoderShouldEmitUTF8Identifier: false));
		streamWriter.Write(content);
	}

	private static string BuildPackJson(string packId, List<KeyValuePair<Song, string>> songs)
	{
		StringBuilder stringBuilder = new StringBuilder();
		stringBuilder.Append("{\n");
		stringBuilder.Append("  \"id\": \"").Append(Json(packId)).Append("\",\n");
		stringBuilder.Append("  \"name\": \"").Append(Json(packId)).Append("\",\n");
		stringBuilder.Append("  \"author\": \"\",\n");
		stringBuilder.Append("  \"version\": \"1.0.0\",\n");
		stringBuilder.Append("  \"songs\": [\n");
		for (int i = 0; i < songs.Count; i++)
		{
			Song key = songs[i].Key;
			stringBuilder.Append("    {\n");
			stringBuilder.Append("      \"title\": \"").Append(Json(string.IsNullOrEmpty(key.Title) ? songs[i].Value : key.Title)).Append("\",\n");
			stringBuilder.Append("      \"category\": \"").Append(Json(key.Category)).Append("\",\n");
			stringBuilder.Append("      \"bucket\": \"").Append(Json(key.BucketName)).Append("\",\n");
			stringBuilder.Append("      \"file\": \"").Append(Json(songs[i].Value)).Append("\",\n");
			stringBuilder.Append("      \"loop\": ").Append(key.Loop ? "true" : "false").Append(",\n");
			stringBuilder.Append("      \"volume\": ").Append(Mathf.Clamp01(key.Volume).ToString("0.###", CultureInfo.InvariantCulture)).Append(",\n");
			stringBuilder.Append("      \"loopSeconds\": ").Append(Mathf.Clamp(key.LoopSeconds, 0f, 10f).ToString("0.0", CultureInfo.InvariantCulture)).Append("\n");
			stringBuilder.Append("    }").Append((i == songs.Count - 1) ? "\n" : ",\n");
		}
		stringBuilder.Append("  ]\n}\n");
		return stringBuilder.ToString();
	}

	private static string BuildManifest(string packId, int count)
	{
		return "{\n  \"name\": \"" + Json(PackageName(packId)) + "\",\n  \"version_number\": \"1.0.0\",\n  \"website_url\": \"\",\n  \"description\": \"Song pack for FATE Music Player: " + count + " track(s).\",\n  \"dependencies\": [\n    \"MikhailKillsZombies-FATE_MusicPlayer-0.4.3\"\n  ]\n}\n";
	}

	private static string PackageName(string packId)
	{
		StringBuilder stringBuilder = new StringBuilder(packId?.Length ?? 0);
		string text = packId ?? "SongPack";
		foreach (char c in text)
		{
			stringBuilder.Append(IsNameCharacter(c) ? c : '_');
		}
		string text2 = stringBuilder.ToString().Trim(new char[1] { '_' });
		if (text2.Length > 64)
		{
			text2 = text2.Substring(0, 64);
		}
		return (text2.Length < 3) ? "SongPack" : text2;
	}

	private static bool IsNameCharacter(char character)
	{
		return (character >= 'a' && character <= 'z') || (character >= 'A' && character <= 'Z') || (character >= '0' && character <= '9') || character == '_';
	}

	private static bool IsThunderstoreName(string name)
	{
		if (string.IsNullOrEmpty(name) || name.Length > 128)
		{
			return false;
		}
		foreach (char character in name)
		{
			if (!IsNameCharacter(character))
			{
				return false;
			}
		}
		return true;
	}

	private static string ManifestName(string manifest)
	{
		int num = manifest.IndexOf("\"name\": \"", StringComparison.Ordinal);
		if (num < 0)
		{
			return null;
		}
		num += "\"name\": \"".Length;
		int num2 = manifest.IndexOf('"', num);
		return (num2 < 0) ? null : manifest.Substring(num, num2 - num);
	}

	private static string BuildReadme(string packId, List<KeyValuePair<Song, string>> songs)
	{
		StringBuilder stringBuilder = new StringBuilder();
		stringBuilder.Append("# ").Append(packId).Append("\n\n");
		stringBuilder.Append("A song pack for **FATE Music Player**. Install it with r2modman or Thunderstore Mod Manager.\n\n");
		stringBuilder.Append("It declares MikhailKillsZombies-FATE_MusicPlayer-0.4.3 as a dependency, so the player is installed with it.\n\n");
		stringBuilder.Append("Its songs appear in their own categories and play in the location slots they were exported for. ");
		stringBuilder.Append("It adds rows only; it never changes the songs that already exist, and removing this package removes exactly these tracks again.\n\n");
		stringBuilder.Append("## Songs\n\n");
		foreach (KeyValuePair<Song, string> song in songs)
		{
			stringBuilder.Append("- ").Append(string.IsNullOrEmpty(song.Key.Title) ? song.Value : song.Key.Title).Append("\n");
		}
		return stringBuilder.ToString();
	}

	private static string Json(string value)
	{
		if (value == null)
		{
			return "";
		}
		return value.Replace("\\", "\\\\").Replace("\"", "\\\"").Replace("\n", "\\n")
			.Replace("\r", "");
	}

	internal static void Message(string text)
	{
		if (FateMusicPlayerPlugin.LogSource != null)
		{
			FateMusicPlayerPlugin.LogSource.LogInfo((object)text);
		}
		FateMusicPlayerPlugin.Toast(text);
	}
}