Decompiled source of CustomMusicRandomizer v0.1.15

CustomMusicRandomizer.dll

Decompiled 19 hours ago
using System;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.Versioning;
using System.Security.Cryptography;
using System.Text;
using System.Threading.Tasks;
using BepInEx;
using BepInEx.Bootstrap;
using BepInEx.Configuration;
using BepInEx.Logging;
using HarmonyLib;
using NAudio.Wave;
using UnityEngine;
using UnityEngine.AddressableAssets;
using UnityEngine.AddressableAssets.ResourceLocators;
using UnityEngine.Audio;
using UnityEngine.Networking;
using UnityEngine.ResourceManagement.AsyncOperations;
using UnityEngine.ResourceManagement.ResourceLocations;
using UnityEngine.SceneManagement;

[assembly: CompilationRelaxations(8)]
[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
[assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)]
[assembly: TargetFramework(".NETStandard,Version=v2.1", FrameworkDisplayName = ".NET Standard 2.1")]
[assembly: AssemblyCompany("CustomMusicRandomizer")]
[assembly: AssemblyConfiguration("Release")]
[assembly: AssemblyFileVersion("0.1.15.0")]
[assembly: AssemblyInformationalVersion("0.1.15")]
[assembly: AssemblyProduct("CustomMusicRandomizer")]
[assembly: AssemblyTitle("CustomMusicRandomizer")]
[assembly: AssemblyVersion("0.1.15.0")]
namespace CustomMusicRandomizer;

[BepInPlugin("moriko.silksong.custommusicrandomizer", "Custom Music Randomizer", "0.1.15")]
[BepInDependency(/*Could not decode attribute arguments.*/)]
public sealed class CustomMusicRandomizerPlugin : BaseUnityPlugin
{
	private sealed class PendingMusicRequest
	{
		internal AudioManager Manager;

		internal MusicCue SourceCue;

		internal CustomTrack TargetTrack;

		internal float TransitionTime;

		internal float DelayTime;

		internal bool ApplySnapshot;

		internal float RequestedAt;
	}

	private sealed class ActiveFanfareUse
	{
		internal AudioSource Source;

		internal CustomTrack Track;
	}

	private sealed class DirectAreaDefinition
	{
		internal string SourceName { get; }

		internal string ClipName { get; }

		internal string Key { get; }

		internal DirectAreaDefinition(string sourceName, string clipName)
		{
			SourceName = sourceName;
			ClipName = clipName;
			Key = sourceName + "|" + clipName;
		}
	}

	private sealed class DirectAreaSourceUse
	{
		internal AudioSource Source;

		internal AudioClip NativeClip;

		internal string MappingKey;

		internal CustomTrack Track;
	}

	private sealed class MutedAudioSourceUse
	{
		internal AudioSource Source;

		internal bool WasMuted;
	}

	public const string PluginGuid = "moriko.silksong.custommusicrandomizer";

	public const string PluginName = "Custom Music Randomizer";

	public const string PluginVersion = "0.1.15";

	public const string RandomizerGuid = "moriko.silksong.randomizer";

	private const string ExistingMusicRandoGuid = "io.github.flibber-hk.musicrando";

	private const string ExhaustOrganExteriorMusicClipName = "atmos_exhaust_organ_outside_w_organ_music_2d";

	private static readonly HashSet<string> CatalogBossCueNames = new HashSet<string>(StringComparer.OrdinalIgnoreCase)
	{
		"BellBattle", "Boss Strive", "CloverDancers", "Coral King", "Creepy Boss Ambient", "Creepy Boss Main", "FinalJudge", "FirstWeaver", "FlowerBattle", "Hunter Queen Carmelita",
		"LaceBattle", "LostLace", "LostLace2", "Phantom", "Pinstress", "RipAndShred", "Seth Battle", "Silk Boss A", "Silk Boss Ambient A", "Silk Boss Ambient B",
		"Silk Boss B", "SongGolem", "Spinner", "SpinnerRage", "TormentedTrobbio", "Trobbio"
	};

	private static readonly HashSet<string> CatalogBattleCueNames = new HashSet<string>(StringComparer.OrdinalIgnoreCase) { "Cloak Battle", "Coral Tower Battle", "Enemy Battle Abyss", "Enemy Battle Grind", "Enemy Battle Mid", "Enemy Battle Small", "Grand Forum Battle", "SmallBattle" };

	private static readonly HashSet<string> CatalogMenuCueNames = new HashSet<string>(StringComparer.OrdinalIgnoreCase) { "Title" };

	private static readonly HashSet<string> CatalogAreaCueNames = new HashSet<string>(StringComparer.OrdinalIgnoreCase)
	{
		"Abyss", "Ambience Citadel Surrounds", "Aqueducts", "Bell_Surrounds", "Belltown", "Belltown Act3", "Belltown_Cursed", "Boneforest", "Bonetown", "Chapel",
		"CitadelHalls", "CitadelHalls Act3", "CitadelHang", "Cloverland", "Cogwork Core", "Coral Ruins", "Coral Tower Ambient", "Coral_Gorge", "Coral_River", "Cradle",
		"Crawl", "Deep Deep Docks", "Deep Docks", "Dustpens", "Enclave", "Greymoor", "Hunters Trail", "Memorium", "MistMaze", "MistMaze_Organ",
		"MossCave", "MossCave Act3", "Mosstown", "Peak", "Shadow", "Shellwood", "Shrine", "Slab", "Understore", "Vaults",
		"Ward", "Weaverlands", "Wilds", "Wisp"
	};

	private static readonly HashSet<string> SpecialCueNames = new HashSet<string>(StringComparer.OrdinalIgnoreCase) { "Abyss Ascent", "Abyss Tension", "Creepy Boss Ambient", "LaceDefeated", "LastDivePrologue", "Memory", "Red Memory", "Silk Boss Ambient A", "Silk Boss Ambient B", "SurfaceAscent" };

	private static readonly DirectAreaDefinition[] DirectAreaDefinitions = new DirectAreaDefinition[5]
	{
		new DirectAreaDefinition("RestArea", "H6-08 Safe"),
		new DirectAreaDefinition("RestArea", "Shrine Act 3"),
		new DirectAreaDefinition("FleaCaravan", "Fleatopia Paired Back A"),
		new DirectAreaDefinition("FleaCaravan", "Fleatopia Paired Back B"),
		new DirectAreaDefinition("FleaCaravan", "Flea Festival")
	};

	private static readonly FieldInfo ApplyMusicCueRoutineField = typeof(AudioManager).GetField("applyMusicCueRoutine", BindingFlags.Instance | BindingFlags.NonPublic);

	private readonly List<MusicCue> vanillaCues = new List<MusicCue>();

	private readonly List<CustomTrack> customTracks = new List<CustomTrack>();

	private readonly Dictionary<string, MusicTarget> musicMappings = new Dictionary<string, MusicTarget>(StringComparer.Ordinal);

	private readonly Dictionary<string, FanfareTarget> fanfareMappings = new Dictionary<string, FanfareTarget>(StringComparer.Ordinal);

	private readonly Dictionary<string, CustomTrack> directAreaMappings = new Dictionary<string, CustomTrack>(StringComparer.Ordinal);

	private readonly List<AudioClip> knownVanillaFanfares = new List<AudioClip>();

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

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

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

	private readonly Dictionary<CustomTrack, long> trackLastUsed = new Dictionary<CustomTrack, long>();

	private readonly List<ActiveFanfareUse> activeFanfares = new List<ActiveFanfareUse>();

	private readonly List<DirectAreaSourceUse> directAreaSources = new List<DirectAreaSourceUse>();

	private readonly List<MutedAudioSourceUse> mutedAuthoredMusicSources = new List<MutedAudioSourceUse>();

	private readonly Queue<CustomTrack> trackLoadQueue = new Queue<CustomTrack>();

	private readonly HashSet<CustomTrack> queuedTrackLoads = new HashSet<CustomTrack>();

	private ConfigEntry<bool> enabledConfig;

	private ConfigEntry<bool> includeVanillaConfig;

	private ConfigEntry<bool> includeCustomConfig;

	private ConfigEntry<bool> randomizeAreaConfig;

	private ConfigEntry<bool> randomizeBattleConfig;

	private ConfigEntry<bool> randomizeBossConfig;

	private ConfigEntry<bool> randomizeMenuConfig;

	private ConfigEntry<bool> randomizeFanfareConfig;

	private ConfigEntry<bool> showTrackNameConfig;

	private ConfigEntry<bool> useRandomizerSeedConfig;

	private ConfigEntry<int> standaloneSeedConfig;

	private ConfigEntry<int> generatedSeedConfig;

	private Harmony harmony;

	private AsyncOperationHandle<IList<MusicCue>> vanillaCueHandle;

	private bool hasVanillaCueHandle;

	private bool isReady;

	private bool isShuttingDown;

	private bool trackLoaderRunning;

	private bool startupMenuReapplied;

	private string customMusicRoot;

	private string mappingSignature;

	private string fanfareSignature;

	private string directAreaSignature;

	private string observedMusicKey;

	private string announcedTrackName;

	private float announcementStartedAt = float.NegativeInfinity;

	private GUIStyle announcementStyle;

	private GUIStyle announcementShadowStyle;

	private int announcementFontSize;

	private long trackUseSequence;

	private float nextCacheTrimAt;

	private CustomTrack currentCustomMusicTrack;

	private PendingMusicRequest pendingMusicRequest;

	private AudioSource routedMainMusicSource;

	private AudioMixerGroup originalMainMusicGroup;

	private AudioMixerGroup customMusicGroup;

	private AudioMixer musicGroupsMixer;

	private AudioMixerSnapshot normalMusicGroupsSnapshot;

	private AudioMixerSnapshot customMusicGroupsSnapshot;

	private bool loggedMusicRoutingFailure;

	private const float AnnouncementDuration = 10f;

	private const int LongFormCacheLimit = 6;

	private const int FanfareCacheLimit = 4;

	internal static CustomMusicRandomizerPlugin Instance { get; private set; }

	internal static ManualLogSource Log { get; private set; }

	private void Awake()
	{
		//IL_00aa: Unknown result type (might be due to invalid IL or missing references)
		//IL_00b4: Expected O, but got Unknown
		EmbeddedDependencyLoader.Install();
		Instance = this;
		Log = ((BaseUnityPlugin)this).Logger;
		BindConfig();
		if (!enabledConfig.Value)
		{
			((BaseUnityPlugin)this).Logger.LogInfo((object)"Custom Music Randomizer 0.1.15 is disabled. Set [General] Enabled to true and restart Silksong to opt in.");
			return;
		}
		EnsureGeneratedSeed();
		if (Chainloader.PluginInfos.ContainsKey("io.github.flibber-hk.musicrando"))
		{
			((BaseUnityPlugin)this).Logger.LogError((object)"flibber's MusicRando is also installed. Custom Music Randomizer will stay inactive to prevent both mods from replacing the same cue.");
			return;
		}
		if (!MusicCueFactory.IsSupported)
		{
			((BaseUnityPlugin)this).Logger.LogError((object)"The game's MusicCue layout was not recognized; custom music randomization is disabled.");
			return;
		}
		customMusicRoot = Path.Combine(Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location), "CustomMusic");
		CustomTrackLoader.EnsureFolders(customMusicRoot);
		harmony = new Harmony("moriko.silksong.custommusicrandomizer");
		harmony.PatchAll(typeof(CustomMusicRandomizerPlugin).Assembly);
		((MonoBehaviour)this).StartCoroutine(InitializePools());
		((BaseUnityPlugin)this).Logger.LogInfo((object)("Custom Music Randomizer 0.1.15 loaded. Custom tracks folder: " + customMusicRoot));
	}

	private void BindConfig()
	{
		//IL_0112: Unknown result type (might be due to invalid IL or missing references)
		//IL_0118: Expected O, but got Unknown
		//IL_012b: Unknown result type (might be due to invalid IL or missing references)
		//IL_0135: Expected O, but got Unknown
		enabledConfig = ((BaseUnityPlugin)this).Config.Bind<bool>("General", "Enabled", false, "Enable music and fanfare randomization. Disabled by default; restart Silksong after changing this setting.");
		includeVanillaConfig = ((BaseUnityPlugin)this).Config.Bind<bool>("Pools", "Include Vanilla Tracks", true, "Allow Silksong's original tracks in randomized destination pools.");
		includeCustomConfig = ((BaseUnityPlugin)this).Config.Bind<bool>("Pools", "Include Custom Tracks", true, "Allow compatible files from the CustomMusic folders in randomized destination pools.");
		randomizeAreaConfig = ((BaseUnityPlugin)this).Config.Bind<bool>("Categories", "Randomize Area Music", true, "Randomize exploration and area cues using the Area pool.");
		randomizeBattleConfig = ((BaseUnityPlugin)this).Config.Bind<bool>("Categories", "Randomize Battle Music", true, "Randomize generic combat and arena cues using the Battle pool.");
		randomizeBossConfig = ((BaseUnityPlugin)this).Config.Bind<bool>("Categories", "Randomize Boss Music", true, "Randomize named boss cues using the Boss pool.");
		randomizeMenuConfig = ((BaseUnityPlugin)this).Config.Bind<bool>("Categories", "Randomize Menu Music", true, "Randomize the title-screen cue using the Menu pool.");
		randomizeFanfareConfig = ((BaseUnityPlugin)this).Config.Bind<bool>("Categories", "Randomize Fanfares", true, "Randomize native battle-clear fanfares using the Fanfare pool.");
		ConfigDefinition val = new ConfigDefinition("Display", "Show Area Track Name");
		bool value = ((BaseUnityPlugin)this).Config.Bind<bool>(val, true, new ConfigDescription("Legacy song-name display setting.", (AcceptableValueBase)null, Array.Empty<object>())).Value;
		((BaseUnityPlugin)this).Config.Remove(val);
		showTrackNameConfig = ((BaseUnityPlugin)this).Config.Bind<bool>("Display", "Show Track Name", value, "Show selected Area, Battle, Boss, or Menu tracks in the bottom-left for ten seconds when their music cue changes.");
		useRandomizerSeedConfig = ((BaseUnityPlugin)this).Config.Bind<bool>("Seed", "Use Archipelago Seed", true, "Use the connected or saved randomizer room, team, and slot for deterministic mappings when available.");
		standaloneSeedConfig = ((BaseUnityPlugin)this).Config.Bind<int>("Seed", "Standalone Seed", 0, "Optional deterministic seed outside Archipelago. Zero uses a generated persistent seed.");
		generatedSeedConfig = ((BaseUnityPlugin)this).Config.Bind<int>("Internal", "Generated Seed", 0, "Persistent fallback seed generated by the mod.");
		((BaseUnityPlugin)this).Config.Save();
	}

	private void EnsureGeneratedSeed()
	{
		if (generatedSeedConfig.Value == 0)
		{
			generatedSeedConfig.Value = StableRandom.SeedFrom(Guid.NewGuid().ToString("N") + Environment.TickCount);
			if (generatedSeedConfig.Value == 0)
			{
				generatedSeedConfig.Value = 1;
			}
			((BaseUnityPlugin)this).Config.Save();
		}
	}

	private IEnumerator InitializePools()
	{
		vanillaCueHandle = Addressables.LoadAssetsAsync<MusicCue>("MusicCues", (Action<MusicCue>)null);
		hasVanillaCueHandle = true;
		yield return vanillaCueHandle;
		if ((int)vanillaCueHandle.Status != 1 || vanillaCueHandle.Result == null || vanillaCueHandle.Result.Count == 0)
		{
			if (vanillaCueHandle.IsValid())
			{
				Addressables.Release<IList<MusicCue>>(vanillaCueHandle);
			}
			hasVanillaCueHandle = false;
			List<IResourceLocation> list = FindMusicCueLocations();
			if (list.Count > 0)
			{
				vanillaCueHandle = Addressables.LoadAssetsAsync<MusicCue>((IList<IResourceLocation>)list, (Action<MusicCue>)null);
				hasVanillaCueHandle = true;
				yield return vanillaCueHandle;
			}
		}
		if (hasVanillaCueHandle && (int)vanillaCueHandle.Status == 1 && vanillaCueHandle.Result != null)
		{
			foreach (MusicCue cue in vanillaCueHandle.Result)
			{
				if ((Object)(object)cue != (Object)null && !vanillaCues.Any((MusicCue existing) => string.Equals(((Object)existing).name, ((Object)cue).name, StringComparison.Ordinal)))
				{
					vanillaCues.Add(cue);
				}
			}
		}
		if (vanillaCues.Count == 0)
		{
			((BaseUnityPlugin)this).Logger.LogError((object)"Could not discover the game's MusicCue assets. Vanilla music will pass through until this is resolved.");
		}
		CustomTrackLoader.DiscoverAll(customMusicRoot, customTracks, ((BaseUnityPlugin)this).Logger);
		vanillaCues.Sort((MusicCue left, MusicCue right) => string.Compare((left != null) ? ((Object)left).name : null, (right != null) ? ((Object)right).name : null, StringComparison.Ordinal));
		customTracks.Sort((CustomTrack left, CustomTrack right) => string.Compare(left.Id, right.Id, StringComparison.Ordinal));
		InvalidateMappings();
		EnsureMusicMappings();
		yield return LoadInitialMenuMusic();
		isReady = true;
		EnsureFanfareMappings();
		EnsureDirectAreaMappings();
		QueueKnownCustomFanfareLoads();
		QueueMappedDirectAreaLoads();
		((MonoBehaviour)this).StartCoroutine(ReapplyCurrentMenuCueWhenReady());
		int num = vanillaCues.Count((MusicCue cue2) => !ShouldPassThrough(cue2) && ClassifyCue(cue2) == MusicCategory.Area);
		int num2 = vanillaCues.Count((MusicCue cue2) => !ShouldPassThrough(cue2) && ClassifyCue(cue2) == MusicCategory.Battle);
		int num3 = vanillaCues.Count((MusicCue cue2) => !ShouldPassThrough(cue2) && ClassifyCue(cue2) == MusicCategory.Boss);
		int num4 = vanillaCues.Count((MusicCue cue2) => !ShouldPassThrough(cue2) && ClassifyCue(cue2) == MusicCategory.Menu);
		((BaseUnityPlugin)this).Logger.LogInfo((object)($"Music pools ready: {vanillaCues.Count} vanilla cues, " + $"{num} area, {num2} battle, " + $"{num3} boss, {num4} menu; " + $"{customTracks.Count((CustomTrack t) => t.Category == MusicCategory.Area)} custom area, " + $"{customTracks.Count((CustomTrack t) => t.Category == MusicCategory.Battle)} custom battle, " + $"{customTracks.Count((CustomTrack t) => t.Category == MusicCategory.Boss)} custom boss, " + $"{customTracks.Count((CustomTrack t) => t.Category == MusicCategory.Menu)} custom menu, " + $"{customTracks.Count((CustomTrack t) => t.Category == MusicCategory.Fanfare)} custom fanfare; " + $"{customTracks.Count((CustomTrack t) => t.IsLoaded)} audio clip" + ((customTracks.Count((CustomTrack t) => t.IsLoaded) == 1) ? string.Empty : "s") + " resident."));
	}

	private IEnumerator LoadInitialMenuMusic()
	{
		int loadedBefore = customTracks.Count((CustomTrack customTrack) => customTrack.IsLoaded);
		int safetyLimit = customTracks.Count + 1;
		for (int pass = 0; pass < safetyLimit; pass++)
		{
			EnsureMusicMappings();
			List<CustomTrack> list = (from customTrack in GetMappedCustomMusicTracks()
				where randomizeMenuConfig.Value && customTrack.Category == MusicCategory.Menu
				where !customTrack.IsLoaded && !customTrack.IsLoading && !customTrack.HasFailed
				select customTrack).ToList();
			if (list.Count == 0)
			{
				break;
			}
			bool failed = false;
			foreach (CustomTrack track in list)
			{
				yield return CustomTrackLoader.LoadTrack(customMusicRoot, track, ((BaseUnityPlugin)this).Logger);
				if (track.IsLoaded)
				{
					TouchResidentTrack(track);
				}
				failed |= track.HasFailed;
			}
			if (!failed)
			{
				break;
			}
			InvalidateMappings();
		}
		EnsureMusicMappings();
		int num = customTracks.Count((CustomTrack customTrack) => customTrack.IsLoaded);
		int num2 = GetMappedCustomMusicTracks().Count((CustomTrack customTrack) => customTrack.Category == MusicCategory.Menu);
		((BaseUnityPlugin)this).Logger.LogInfo((object)($"Prepared {num - loadedBefore} startup Menu " + "track" + ((num - loadedBefore == 1) ? string.Empty : "s") + " " + $"for {num2} custom Menu mapping" + ((num2 == 1) ? string.Empty : "s") + "; " + $"{customTracks.Count - num} indexed file" + ((customTracks.Count - num == 1) ? string.Empty : "s") + " remain disk-only."));
	}

	private List<CustomTrack> GetMappedCustomMusicTracks()
	{
		return (from target in musicMappings.Values
			select target.CustomTrack into track
			where track != null
			select track).Distinct().OrderBy<CustomTrack, string>((CustomTrack track) => track.Id, StringComparer.Ordinal).ToList();
	}

	private void QueueTrackLoad(CustomTrack track)
	{
		if (!isShuttingDown && track != null && !track.IsLoaded && !track.IsLoading && !track.HasFailed && queuedTrackLoads.Add(track))
		{
			trackLoadQueue.Enqueue(track);
			if (!trackLoaderRunning)
			{
				((MonoBehaviour)this).StartCoroutine(ProcessTrackLoadQueue());
			}
		}
	}

	private IEnumerator ProcessTrackLoadQueue()
	{
		trackLoaderRunning = true;
		try
		{
			while (!isShuttingDown && trackLoadQueue.Count > 0)
			{
				CustomTrack customTrack = trackLoadQueue.Dequeue();
				queuedTrackLoads.Remove(customTrack);
				if (customTrack != null && !customTrack.IsLoaded && !customTrack.IsLoading && !customTrack.HasFailed && (customTrack.Category == MusicCategory.Fanfare || pendingMusicRequest?.TargetTrack == customTrack || IsMappedDirectAreaTrack(customTrack)))
				{
					yield return LoadTrackAtRuntime(customTrack);
				}
			}
		}
		finally
		{
			trackLoaderRunning = false;
		}
	}

	private IEnumerator LoadTrackAtRuntime(CustomTrack track)
	{
		yield return CustomTrackLoader.LoadTrack(customMusicRoot, track, ((BaseUnityPlugin)this).Logger);
		if (!isShuttingDown)
		{
			if (track.IsLoaded)
			{
				TouchResidentTrack(track);
				ReplayPendingMusicRequest(track);
				TrimResidentTrackCache();
			}
			else if (track.HasFailed)
			{
				InvalidateMappings();
				EnsureMusicMappings();
				EnsureFanfareMappings();
				EnsureDirectAreaMappings();
				ReplayPendingMusicRequest(track);
				QueueKnownCustomFanfareLoads();
				QueueMappedDirectAreaLoads();
			}
		}
	}

	private void ReplayPendingMusicRequest(CustomTrack completedTrack)
	{
		PendingMusicRequest pendingMusicRequest = this.pendingMusicRequest;
		if (pendingMusicRequest != null && pendingMusicRequest.TargetTrack == completedTrack)
		{
			if ((Object)(object)pendingMusicRequest.Manager == (Object)null || (Object)(object)pendingMusicRequest.SourceCue == (Object)null)
			{
				this.pendingMusicRequest = null;
			}
			else if (!AudioManager.BlockAudioChange)
			{
				this.pendingMusicRequest = null;
				float num = Time.unscaledTime - pendingMusicRequest.RequestedAt;
				float num2 = Mathf.Max(0f, pendingMusicRequest.DelayTime - Mathf.Max(0f, num));
				pendingMusicRequest.Manager.ApplyMusicCue(pendingMusicRequest.SourceCue, num2, pendingMusicRequest.TransitionTime, pendingMusicRequest.ApplySnapshot);
			}
		}
	}

	private void TouchResidentTrack(CustomTrack track)
	{
		if (track != null && track.IsLoaded)
		{
			trackLastUsed[track] = ++trackUseSequence;
		}
	}

	private void TrimResidentTrackCache()
	{
		CleanupFinishedFanfares();
		TrimResidentTrackCategory(fanfare: false, 6);
		TrimResidentTrackCategory(fanfare: true, 4);
	}

	private void TrimResidentTrackCategory(bool fanfare, int limit)
	{
		List<CustomTrack> list = customTracks.Where((CustomTrack track) => track.IsLoaded && track.Category == MusicCategory.Fanfare == fanfare).ToList();
		while (list.Count > limit)
		{
			long value;
			CustomTrack customTrack = (from track in list
				where !IsTrackInUse(track)
				orderby (!trackLastUsed.TryGetValue(track, out value)) ? 0 : value
				select track).FirstOrDefault();
			if (customTrack == null)
			{
				break;
			}
			string id = customTrack.Id;
			customTrack.Unload();
			trackLastUsed.Remove(customTrack);
			list.Remove(customTrack);
			((BaseUnityPlugin)this).Logger.LogDebug((object)("Evicted custom audio clip from RAM: " + id));
		}
	}

	private bool IsTrackInUse(CustomTrack track)
	{
		if (track == null || !track.IsLoaded)
		{
			return false;
		}
		if (currentCustomMusicTrack == track || pendingMusicRequest?.TargetTrack == track)
		{
			return true;
		}
		if (enabledConfig.Value && randomizeAreaConfig.Value && IsMappedDirectAreaTrack(track))
		{
			return true;
		}
		if (activeFanfares.Any((ActiveFanfareUse use) => use.Track == track && IsAudioSourcePlaying(use.Source)))
		{
			return true;
		}
		if (directAreaSources.Any((DirectAreaSourceUse use) => IsDirectAreaTrackAssigned(use, track)))
		{
			return true;
		}
		try
		{
			GameManager silentInstance = GameManager.SilentInstance;
			object obj;
			if (silentInstance == null)
			{
				obj = null;
			}
			else
			{
				AudioManager audioManager = silentInstance.AudioManager;
				obj = ((audioManager != null) ? audioManager.MusicSources : null);
			}
			return ((IEnumerable<AudioSource>)obj)?.Any((AudioSource source) => (Object)(object)source != (Object)null && source.clip == track.Clip) ?? false;
		}
		catch
		{
			return true;
		}
	}

	private static bool IsDirectAreaTrackAssigned(DirectAreaSourceUse use, CustomTrack track)
	{
		if (use == null || use.Track != track || (Object)(object)use.Source == (Object)null)
		{
			return false;
		}
		try
		{
			return use.Source.clip == track.Clip;
		}
		catch
		{
			return true;
		}
	}

	private void CleanupFinishedFanfares()
	{
		activeFanfares.RemoveAll((ActiveFanfareUse use) => use == null || !IsAudioSourcePlaying(use.Source));
	}

	private void TrackActiveFanfare(AudioSource source, CustomTrack track)
	{
		if (!((Object)(object)source == (Object)null) && track != null && !activeFanfares.Any((ActiveFanfareUse use) => use.Source == source && use.Track == track))
		{
			activeFanfares.Add(new ActiveFanfareUse
			{
				Source = source,
				Track = track
			});
		}
	}

	private static bool IsAudioSourcePlaying(AudioSource source)
	{
		try
		{
			return (Object)(object)source != (Object)null && source.isPlaying;
		}
		catch
		{
			return false;
		}
	}

	private void QueueKnownCustomFanfareLoads()
	{
		if (!randomizeFanfareConfig.Value)
		{
			return;
		}
		foreach (CustomTrack item in (from target in fanfareMappings.Values
			select target.CustomTrack into track
			where track != null
			select track).Distinct())
		{
			QueueTrackLoad(item);
		}
	}

	private bool IsMappedDirectAreaTrack(CustomTrack track)
	{
		if (track == null || track.Category != MusicCategory.Area)
		{
			return false;
		}
		EnsureDirectAreaMappings();
		return directAreaMappings.Values.Any((CustomTrack mapped) => mapped == track);
	}

	private void QueueMappedDirectAreaLoads()
	{
		if (!enabledConfig.Value || !randomizeAreaConfig.Value)
		{
			return;
		}
		EnsureDirectAreaMappings();
		foreach (CustomTrack item in directAreaMappings.Values.Where((CustomTrack track) => track != null).Distinct())
		{
			QueueTrackLoad(item);
		}
	}

	private IEnumerator ReapplyCurrentMenuCueWhenReady()
	{
		if (startupMenuReapplied || !randomizeMenuConfig.Value)
		{
			yield break;
		}
		float timeoutAt = Time.realtimeSinceStartup + 5f;
		while (Time.realtimeSinceStartup < timeoutAt && !startupMenuReapplied)
		{
			GameManager silentInstance = GameManager.SilentInstance;
			AudioManager val = ((silentInstance != null) ? silentInstance.AudioManager : null);
			MusicCue val2 = ((val != null) ? val.CurrentMusicCue : null);
			MusicCue val3 = ((val2 != null) ? val2.ResolveAlternatives() : null);
			if ((Object)(object)val == (Object)null || (Object)(object)val2 == (Object)null || (Object)(object)val3 == (Object)null)
			{
				yield return null;
				continue;
			}
			if (!CatalogMenuCueNames.Contains(((Object)val3).name))
			{
				break;
			}
			if (!AudioManager.BlockAudioChange)
			{
				if (!startupMenuReapplied)
				{
					startupMenuReapplied = true;
					val.ApplyMusicCue(val2, 0f, 0f, false);
				}
				break;
			}
			yield return null;
		}
	}

	private List<IResourceLocation> FindMusicCueLocations()
	{
		Dictionary<string, IResourceLocation> dictionary = new Dictionary<string, IResourceLocation>(StringComparer.Ordinal);
		try
		{
			IList<IResourceLocation> list = default(IList<IResourceLocation>);
			foreach (IResourceLocator resourceLocator in Addressables.ResourceLocators)
			{
				foreach (object key in resourceLocator.Keys)
				{
					if (!(key is string) || !resourceLocator.Locate(key, typeof(MusicCue), ref list))
					{
						continue;
					}
					foreach (IResourceLocation item in list)
					{
						if (item != null && !dictionary.ContainsKey(item.InternalId))
						{
							dictionary.Add(item.InternalId, item);
						}
					}
				}
			}
		}
		catch (Exception arg)
		{
			((BaseUnityPlugin)this).Logger.LogError((object)$"Could not enumerate MusicCue Addressables: {arg}");
		}
		((BaseUnityPlugin)this).Logger.LogInfo((object)$"Discovered {dictionary.Count} MusicCue resource locations.");
		return dictionary.Values.ToList();
	}

	internal bool PrepareMusicCue(AudioManager manager, ref MusicCue musicCue, float delayTime, float transitionTime, bool applySnapshot)
	{
		if ((Object)(object)musicCue != (Object)null && ((Object)musicCue).name.StartsWith("CustomMusic::", StringComparison.Ordinal))
		{
			pendingMusicRequest = null;
			string customCueName = ((Object)musicCue).name;
			currentCustomMusicTrack = customTracks.FirstOrDefault((CustomTrack track) => track.IsLoaded && string.Equals("CustomMusic::" + track.Id, customCueName, StringComparison.Ordinal));
			TouchResidentTrack(currentCustomMusicTrack);
			if ((Object)(object)manager != (Object)null && (Object)(object)manager.CurrentMusicCue == (Object)(object)musicCue)
			{
				ApplyMusicRoutingForCue(manager, musicCue);
			}
			return true;
		}
		MusicCue sourceCue = musicCue;
		CustomTrack selectedTrack;
		CustomTrack deferredTrack;
		MusicCue val = ResolveReplacement(manager, musicCue, delayTime, out selectedTrack, out deferredTrack);
		if (deferredTrack != null)
		{
			pendingMusicRequest = new PendingMusicRequest
			{
				Manager = manager,
				SourceCue = sourceCue,
				TargetTrack = deferredTrack,
				TransitionTime = transitionTime,
				DelayTime = Mathf.Max(0f, delayTime),
				ApplySnapshot = applySnapshot,
				RequestedAt = Time.unscaledTime
			};
			CancelNativePendingMusicTransition(manager);
			QueueTrackLoad(deferredTrack);
			((BaseUnityPlugin)this).Logger.LogDebug((object)("Deferred music transition while loading: " + deferredTrack.Id));
			return false;
		}
		pendingMusicRequest = null;
		currentCustomMusicTrack = selectedTrack;
		if (selectedTrack != null)
		{
			TouchResidentTrack(selectedTrack);
		}
		musicCue = val;
		if ((Object)(object)manager != (Object)null && (Object)(object)manager.CurrentMusicCue == (Object)(object)val)
		{
			ApplyMusicRoutingForCue(manager, val);
		}
		return true;
	}

	private void CancelNativePendingMusicTransition(AudioManager manager)
	{
		if ((Object)(object)manager == (Object)null || ApplyMusicCueRoutineField == null)
		{
			return;
		}
		try
		{
			object? value = ApplyMusicCueRoutineField.GetValue(manager);
			Coroutine val = (Coroutine)((value is Coroutine) ? value : null);
			if (val != null)
			{
				((MonoBehaviour)manager).StopCoroutine(val);
				ApplyMusicCueRoutineField.SetValue(manager, null);
			}
		}
		catch (Exception ex)
		{
			((BaseUnityPlugin)this).Logger.LogWarning((object)("Could not cancel the previous delayed music transition: " + ex.Message));
		}
	}

	private MusicCue ResolveReplacement(AudioManager manager, MusicCue original, float delayTime, out CustomTrack selectedTrack, out CustomTrack deferredTrack)
	{
		selectedTrack = null;
		deferredTrack = null;
		if (!isReady || !enabledConfig.Value || (Object)(object)original == (Object)null || ((Object)original).name.StartsWith("CustomMusic::", StringComparison.Ordinal))
		{
			if ((Object)(object)original != (Object)null && !((Object)original).name.StartsWith("CustomMusic::", StringComparison.Ordinal))
			{
				RestoreMutedAuthoredMusicLayers();
			}
			return original;
		}
		MusicCue val = original.ResolveAlternatives();
		if ((Object)(object)val == (Object)null)
		{
			RestoreMutedAuthoredMusicLayers();
			return original;
		}
		if (ShouldPassThrough(val))
		{
			RestoreMutedAuthoredMusicLayers();
			ObserveMusic("passthrough|" + ((Object)val).name);
			return original;
		}
		original = val;
		MusicCategory musicCategory = ClassifyCue(original);
		if ((musicCategory == MusicCategory.Area && !randomizeAreaConfig.Value) || (musicCategory == MusicCategory.Battle && !randomizeBattleConfig.Value) || (musicCategory == MusicCategory.Boss && !randomizeBossConfig.Value) || (musicCategory == MusicCategory.Menu && !randomizeMenuConfig.Value))
		{
			RestoreMutedAuthoredMusicLayers();
			ObserveAndMaybeAnnounce(musicCategory, original, "native:" + ((Object)original).name, ((Object)original).name, delayTime);
			return original;
		}
		EnsureMusicMappings();
		if (!musicMappings.TryGetValue(((Object)original).name, out var value))
		{
			RestoreMutedAuthoredMusicLayers();
			ObserveAndMaybeAnnounce(musicCategory, original, "native:" + ((Object)original).name, ((Object)original).name, delayTime);
			return original;
		}
		if (value.CustomTrack != null && !EnsureCustomMusicRouting(manager))
		{
			RestoreMutedAuthoredMusicLayers();
			ObserveAndMaybeAnnounce(musicCategory, original, "native:" + ((Object)original).name, ((Object)original).name, delayTime);
			return original;
		}
		if (value.CustomTrack != null && !value.CustomTrack.IsLoaded)
		{
			deferredTrack = value.CustomTrack;
			return original;
		}
		MusicCue val2 = value.Resolve(original);
		if ((Object)(object)val2 == (Object)null)
		{
			RestoreMutedAuthoredMusicLayers();
			return original;
		}
		if ((Object)(object)val2 != (Object)(object)original && string.Equals(((Object)original).name, "MistMaze_Organ", StringComparison.OrdinalIgnoreCase))
		{
			MuteExhaustOrganExteriorMusicLayer();
		}
		else
		{
			RestoreMutedAuthoredMusicLayers();
		}
		if (value.CustomTrack != null)
		{
			selectedTrack = value.CustomTrack;
			EnsureNativeMusicLooping();
		}
		object obj;
		if (value.CustomTrack == null)
		{
			MusicCue vanillaCue = value.VanillaCue;
			obj = ((vanillaCue != null) ? ((Object)vanillaCue).name : null);
		}
		else
		{
			obj = Path.GetFileNameWithoutExtension(value.CustomTrack.Path);
		}
		string text = (string)obj;
		if (string.IsNullOrWhiteSpace(text))
		{
			text = ((Object)val2).name;
		}
		ObserveAndMaybeAnnounce(musicCategory, original, value.Id, text, delayTime);
		((BaseUnityPlugin)this).Logger.LogDebug((object)$"{musicCategory} music: {((Object)original).name} -> {value.Id}");
		return val2;
	}

	private void MuteExhaustOrganExteriorMusicLayer()
	{
		//IL_003a: Unknown result type (might be due to invalid IL or missing references)
		//IL_003f: Unknown result type (might be due to invalid IL or missing references)
		//IL_0058: Unknown result type (might be due to invalid IL or missing references)
		//IL_005d: Unknown result type (might be due to invalid IL or missing references)
		int num = 0;
		try
		{
			AudioSource[] array = Resources.FindObjectsOfTypeAll<AudioSource>();
			foreach (AudioSource source in array)
			{
				if ((Object)(object)source == (Object)null)
				{
					continue;
				}
				Scene scene = ((Component)source).gameObject.scene;
				if (!((Scene)(ref scene)).IsValid())
				{
					continue;
				}
				scene = ((Component)source).gameObject.scene;
				if (((Scene)(ref scene)).isLoaded && !((Object)(object)source.clip == (Object)null) && string.Equals(((Object)source.clip).name, "atmos_exhaust_organ_outside_w_organ_music_2d", StringComparison.OrdinalIgnoreCase) && source.isPlaying)
				{
					if (!mutedAuthoredMusicSources.Any((MutedAudioSourceUse use) => use != null && use.Source == source))
					{
						mutedAuthoredMusicSources.Add(new MutedAudioSourceUse
						{
							Source = source,
							WasMuted = source.mute
						});
					}
					source.mute = true;
					num++;
				}
			}
		}
		catch (Exception ex)
		{
			((BaseUnityPlugin)this).Logger.LogWarning((object)("Could not suppress the Exhaust Organ exterior music layer: " + ex.Message));
			return;
		}
		if (num > 0)
		{
			((BaseUnityPlugin)this).Logger.LogDebug((object)string.Format("Muted {0} authored Exhaust Organ exterior music source{1} while randomized music is active.", num, (num == 1) ? string.Empty : "s"));
		}
	}

	private void RestoreMutedAuthoredMusicLayers()
	{
		foreach (MutedAudioSourceUse mutedAuthoredMusicSource in mutedAuthoredMusicSources)
		{
			try
			{
				if ((Object)(object)mutedAuthoredMusicSource?.Source != (Object)null)
				{
					mutedAuthoredMusicSource.Source.mute = mutedAuthoredMusicSource.WasMuted;
				}
			}
			catch
			{
			}
		}
		mutedAuthoredMusicSources.Clear();
	}

	private void ObserveAndMaybeAnnounce(MusicCategory category, MusicCue source, string targetId, string trackName, float delayTime)
	{
		ObserveAndMaybeAnnounce(category, (source != null) ? ((Object)source).name : null, targetId, trackName, delayTime);
	}

	private void ObserveAndMaybeAnnounce(MusicCategory category, string sourceName, string targetId, string trackName, float delayTime)
	{
		string a = string.Join("|", category, sourceName ?? string.Empty, targetId ?? string.Empty);
		bool num = !string.Equals(a, observedMusicKey, StringComparison.Ordinal);
		observedMusicKey = a;
		if (num && showTrackNameConfig.Value && !string.IsNullOrWhiteSpace(trackName))
		{
			announcedTrackName = trackName;
			announcementStartedAt = Time.unscaledTime + Mathf.Max(0f, delayTime);
		}
	}

	private void ObserveMusic(string key)
	{
		observedMusicKey = key;
		ClearAnnouncement();
	}

	private void ClearAnnouncement()
	{
		announcedTrackName = null;
		announcementStartedAt = float.NegativeInfinity;
	}

	private void LateUpdate()
	{
		CustomTrack customTrack = pendingMusicRequest?.TargetTrack;
		if (customTrack != null && (customTrack.IsLoaded || customTrack.HasFailed) && !AudioManager.BlockAudioChange)
		{
			ReplayPendingMusicRequest(customTrack);
		}
		if (isReady && !isShuttingDown && !(Time.unscaledTime < nextCacheTrimAt))
		{
			nextCacheTrimAt = Time.unscaledTime + 1f;
			TrimResidentTrackCache();
		}
	}

	private void OnGUI()
	{
		//IL_00b0: Unknown result type (might be due to invalid IL or missing references)
		//IL_00b6: Expected O, but got Unknown
		//IL_00bd: Unknown result type (might be due to invalid IL or missing references)
		//IL_00c2: Unknown result type (might be due to invalid IL or missing references)
		//IL_00c4: Unknown result type (might be due to invalid IL or missing references)
		//IL_00c9: Unknown result type (might be due to invalid IL or missing references)
		//IL_0118: Unknown result type (might be due to invalid IL or missing references)
		//IL_0144: Unknown result type (might be due to invalid IL or missing references)
		//IL_0158: Unknown result type (might be due to invalid IL or missing references)
		//IL_016a: Unknown result type (might be due to invalid IL or missing references)
		//IL_017f: Unknown result type (might be due to invalid IL or missing references)
		//IL_01b1: Unknown result type (might be due to invalid IL or missing references)
		//IL_01c2: Unknown result type (might be due to invalid IL or missing references)
		if (isReady && enabledConfig != null && enabledConfig.Value && showTrackNameConfig != null && showTrackNameConfig.Value && !string.IsNullOrWhiteSpace(announcedTrackName))
		{
			float num = Time.unscaledTime - announcementStartedAt;
			if (!(num < 0f) && !(num >= 10f))
			{
				float num2 = Mathf.Min(Mathf.Clamp01(num / 0.18f), Mathf.Clamp01((10f - num) / 0.65f));
				int fontSize = Mathf.RoundToInt(Mathf.Clamp((float)Screen.height / 45f, 18f, 30f));
				EnsureAnnouncementStyles(fontSize);
				GUIContent val = new GUIContent(announcedTrackName);
				Vector2 val2 = announcementStyle.CalcSize(val);
				Rect safeArea = Screen.safeArea;
				float num3 = Mathf.Max(24f, (float)Screen.width * 0.02f);
				float num4 = Mathf.Max(24f, (float)Screen.height * 0.03f);
				float num5 = ((Rect)(ref safeArea)).xMin + num3;
				float num6 = (float)Screen.height - ((Rect)(ref safeArea)).yMin - num4 - val2.y;
				float num7 = Mathf.Max(120f, ((Rect)(ref safeArea)).width - num3 * 2f);
				Rect val3 = default(Rect);
				((Rect)(ref val3))..ctor(num5, num6, Mathf.Min(val2.x + 8f, num7), val2.y + 4f);
				Color color = GUI.color;
				GUI.color = new Color(1f, 1f, 1f, num2);
				GUI.Label(new Rect(((Rect)(ref val3)).x + 2f, ((Rect)(ref val3)).y + 2f, ((Rect)(ref val3)).width, ((Rect)(ref val3)).height), val, announcementShadowStyle);
				GUI.Label(val3, val, announcementStyle);
				GUI.color = color;
			}
		}
	}

	private void EnsureAnnouncementStyles(int fontSize)
	{
		//IL_002c: Unknown result type (might be due to invalid IL or missing references)
		//IL_0031: Unknown result type (might be due to invalid IL or missing references)
		//IL_0038: Unknown result type (might be due to invalid IL or missing references)
		//IL_003f: Unknown result type (might be due to invalid IL or missing references)
		//IL_0046: Unknown result type (might be due to invalid IL or missing references)
		//IL_004d: Unknown result type (might be due to invalid IL or missing references)
		//IL_0059: Expected O, but got Unknown
		//IL_0078: Unknown result type (might be due to invalid IL or missing references)
		//IL_0089: Unknown result type (might be due to invalid IL or missing references)
		//IL_0093: Expected O, but got Unknown
		//IL_00b2: Unknown result type (might be due to invalid IL or missing references)
		if (announcementStyle == null || announcementShadowStyle == null || announcementFontSize != fontSize)
		{
			announcementFontSize = fontSize;
			announcementStyle = new GUIStyle(GUI.skin.label)
			{
				alignment = (TextAnchor)6,
				fontSize = fontSize,
				fontStyle = (FontStyle)0,
				wordWrap = false,
				clipping = (TextClipping)1
			};
			announcementStyle.normal.textColor = new Color(0.96f, 0.94f, 0.88f, 1f);
			announcementShadowStyle = new GUIStyle(announcementStyle);
			announcementShadowStyle.normal.textColor = new Color(0f, 0f, 0f, 0.82f);
		}
	}

	private void EnsureNativeMusicLooping()
	{
		try
		{
			GameManager silentInstance = GameManager.SilentInstance;
			object obj;
			if (silentInstance == null)
			{
				obj = null;
			}
			else
			{
				AudioManager audioManager = silentInstance.AudioManager;
				obj = ((audioManager != null) ? audioManager.MusicSources : null);
			}
			AudioSource[] array = (AudioSource[])obj;
			if (array == null)
			{
				return;
			}
			AudioSource[] array2 = array;
			foreach (AudioSource val in array2)
			{
				if ((Object)(object)val != (Object)null)
				{
					val.loop = true;
				}
			}
		}
		catch
		{
		}
	}

	private bool EnsureCustomMusicRouting(AudioManager manager)
	{
		try
		{
			AudioSource[] array = ((manager != null) ? manager.MusicSources : null);
			AudioSource val = ((array != null && array.Length != 0) ? array[0] : null);
			if ((Object)(object)val == (Object)null)
			{
				return LogMusicRoutingFailure("the Main music source was unavailable");
			}
			if (val == routedMainMusicSource && (Object)(object)originalMainMusicGroup != (Object)null && (Object)(object)customMusicGroup != (Object)null && (Object)(object)musicGroupsMixer != (Object)null && (Object)(object)normalMusicGroupsSnapshot != (Object)null && (Object)(object)customMusicGroupsSnapshot != (Object)null)
			{
				return true;
			}
			RestoreNormalMusicRouting();
			ClearMusicRoutingReferences();
			AudioMixerGroup outputAudioMixerGroup = val.outputAudioMixerGroup;
			AudioMixer val2 = ((outputAudioMixerGroup != null) ? outputAudioMixerGroup.audioMixer : null);
			AudioMixerGroup val3 = ((val2 != null) ? val2.outputAudioMixerGroup : null);
			AudioMixer downstreamMixer = ((val3 != null) ? val3.audioMixer : null);
			AudioMixer obj = downstreamMixer;
			AudioMixerGroup val4 = ((obj != null) ? ((IEnumerable<AudioMixerGroup>)obj.FindMatchingGroups("Custom Music")).FirstOrDefault((Func<AudioMixerGroup, bool>)((AudioMixerGroup group) => (Object)(object)group != (Object)null && string.Equals(((Object)group).name, "Custom Music", StringComparison.Ordinal) && group.audioMixer == downstreamMixer)) : null);
			AudioMixer obj2 = downstreamMixer;
			AudioMixerSnapshot val5 = ((obj2 != null) ? obj2.FindSnapshot("Normal Music") : null);
			AudioMixer obj3 = downstreamMixer;
			AudioMixerSnapshot val6 = ((obj3 != null) ? obj3.FindSnapshot("Custom Music") : null);
			if ((Object)(object)outputAudioMixerGroup == (Object)null || (Object)(object)val2 == (Object)null || (Object)(object)val3 == (Object)null || (Object)(object)downstreamMixer == (Object)null || (Object)(object)val4 == (Object)null || (Object)(object)val5 == (Object)null || (Object)(object)val6 == (Object)null || !string.Equals(((Object)val3).name, "Normal Music", StringComparison.Ordinal) || !string.Equals(((Object)downstreamMixer).name, "Music Groups", StringComparison.Ordinal))
			{
				return LogMusicRoutingFailure("Silksong's Music Groups/Custom Music route was not found");
			}
			routedMainMusicSource = val;
			originalMainMusicGroup = outputAudioMixerGroup;
			customMusicGroup = val4;
			musicGroupsMixer = downstreamMixer;
			normalMusicGroupsSnapshot = val5;
			customMusicGroupsSnapshot = val6;
			loggedMusicRoutingFailure = false;
			((BaseUnityPlugin)this).Logger.LogInfo((object)"Custom songs use Silksong's single-source Custom Music mixer route.");
			return true;
		}
		catch (Exception ex)
		{
			return LogMusicRoutingFailure(ex.Message);
		}
	}

	private bool LogMusicRoutingFailure(string reason)
	{
		if (!loggedMusicRoutingFailure)
		{
			loggedMusicRoutingFailure = true;
			((BaseUnityPlugin)this).Logger.LogError((object)("Custom music mixer routing is unavailable (" + reason + "); using vanilla music instead."));
		}
		return false;
	}

	internal void ApplyMusicRoutingForCue(AudioManager manager, MusicCue cue)
	{
		if (!((Object)(object)cue != (Object)null) || !((Object)cue).name.StartsWith("CustomMusic::", StringComparison.Ordinal))
		{
			RestoreNormalMusicRouting();
			return;
		}
		if (!EnsureCustomMusicRouting(manager))
		{
			RestoreNormalMusicRouting();
			return;
		}
		try
		{
			routedMainMusicSource.outputAudioMixerGroup = customMusicGroup;
			customMusicGroupsSnapshot.TransitionTo(0f);
		}
		catch (Exception ex)
		{
			((BaseUnityPlugin)this).Logger.LogWarning((object)("Could not activate the Custom Music mixer route: " + ex.Message));
			RestoreNormalMusicRouting();
		}
	}

	private void RestoreNormalMusicRouting()
	{
		try
		{
			if ((Object)(object)routedMainMusicSource != (Object)null && (Object)(object)originalMainMusicGroup != (Object)null)
			{
				routedMainMusicSource.outputAudioMixerGroup = originalMainMusicGroup;
			}
			AudioMixerSnapshot obj = normalMusicGroupsSnapshot;
			if (obj != null)
			{
				obj.TransitionTo(0f);
			}
		}
		catch
		{
		}
	}

	internal void CancelPendingMusicAndRestoreNormalRouting(AudioManager manager)
	{
		pendingMusicRequest = null;
		currentCustomMusicTrack = null;
		CancelNativePendingMusicTransition(manager);
		RestoreMutedAuthoredMusicLayers();
		RestoreNormalMusicRouting();
	}

	internal void PrepareForLeavingMenu(AudioManager manager)
	{
		startupMenuReapplied = true;
		PendingMusicRequest obj = pendingMusicRequest;
		bool flag = obj != null && obj.TargetTrack?.Category == MusicCategory.Menu;
		CustomTrack customTrack = currentCustomMusicTrack;
		bool flag2 = customTrack != null && customTrack.Category == MusicCategory.Menu && (Object)(object)((manager != null) ? manager.CurrentMusicCue : null) != (Object)null && ((Object)manager.CurrentMusicCue).name.StartsWith("CustomMusic::", StringComparison.Ordinal);
		if (flag || flag2)
		{
			if (flag)
			{
				pendingMusicRequest = null;
			}
			CancelNativePendingMusicTransition(manager);
			if (flag2)
			{
				RestoreNormalMusicRouting();
			}
		}
	}

	private void ClearMusicRoutingReferences()
	{
		routedMainMusicSource = null;
		originalMainMusicGroup = null;
		customMusicGroup = null;
		musicGroupsMixer = null;
		normalMusicGroupsSnapshot = null;
		customMusicGroupsSnapshot = null;
	}

	internal bool TryPrepareDirectAreaSource(AudioSource source)
	{
		if (!TryGetDirectAreaSourceName(source, out var sourceName))
		{
			return false;
		}
		try
		{
			GameManager silentInstance = GameManager.SilentInstance;
			CancelPendingMusicAndRestoreNormalRouting((silentInstance != null) ? silentInstance.AudioManager : null);
			DirectAreaSourceUse directAreaSourceUse = directAreaSources.FirstOrDefault((DirectAreaSourceUse existing) => existing != null && existing.Source == source);
			if (!isReady || !enabledConfig.Value || !randomizeAreaConfig.Value)
			{
				RestoreDirectAreaSource(directAreaSourceUse);
				return true;
			}
			AudioClip original = source.clip;
			if ((Object)(object)original == (Object)null)
			{
				return true;
			}
			if (((Object)original).name.StartsWith("CustomMusic::", StringComparison.Ordinal))
			{
				EnsureDirectAreaMappings();
				CustomTrack value = null;
				if (directAreaSourceUse != null && directAreaSourceUse.MappingKey != null)
				{
					directAreaMappings.TryGetValue(directAreaSourceUse.MappingKey, out value);
				}
				if (directAreaSourceUse != null && directAreaSourceUse.Track != null && directAreaSourceUse.Track == value && directAreaSourceUse.Track.IsLoaded && original == directAreaSourceUse.Track.Clip)
				{
					TouchResidentTrack(directAreaSourceUse.Track);
					AnnounceDirectAreaTrack(directAreaSourceUse.MappingKey, directAreaSourceUse.Track);
					return true;
				}
				RestoreDirectAreaSource(directAreaSourceUse);
				original = source.clip;
				if ((Object)(object)original == (Object)null || ((Object)original).name.StartsWith("CustomMusic::", StringComparison.Ordinal))
				{
					return true;
				}
			}
			DirectAreaDefinition directAreaDefinition = DirectAreaDefinitions.FirstOrDefault((DirectAreaDefinition candidate) => string.Equals(candidate.SourceName, sourceName, StringComparison.Ordinal) && string.Equals(candidate.ClipName, ((Object)original).name, StringComparison.Ordinal));
			if (directAreaDefinition == null)
			{
				string text = sourceName + "|" + ((Object)original).name;
				if (loggedUnknownDirectAreaClips.Add(text))
				{
					((BaseUnityPlugin)this).Logger.LogWarning((object)("Leaving unknown direct area music unchanged: " + text));
				}
				return true;
			}
			if (directAreaSourceUse == null)
			{
				directAreaSourceUse = new DirectAreaSourceUse
				{
					Source = source
				};
				directAreaSources.Add(directAreaSourceUse);
			}
			directAreaSourceUse.NativeClip = original;
			directAreaSourceUse.MappingKey = directAreaDefinition.Key;
			directAreaSourceUse.Track = null;
			EnsureDirectAreaMappings();
			if (!directAreaMappings.TryGetValue(directAreaDefinition.Key, out var value2) || value2 == null)
			{
				AnnounceDirectAreaNative(directAreaDefinition.Key, original);
				return true;
			}
			if (!value2.IsLoaded)
			{
				QueueTrackLoad(value2);
				AnnounceDirectAreaNative(directAreaDefinition.Key, original);
				return true;
			}
			source.clip = value2.Clip;
			directAreaSourceUse.Track = value2;
			TouchResidentTrack(value2);
			AnnounceDirectAreaTrack(directAreaDefinition.Key, value2);
			((BaseUnityPlugin)this).Logger.LogDebug((object)("Direct Area music: " + directAreaDefinition.Key + " -> custom:" + value2.Id));
			return true;
		}
		catch (Exception ex)
		{
			((BaseUnityPlugin)this).Logger.LogWarning((object)("Could not prepare direct area music for " + sourceName + "; using vanilla: " + ex.Message));
			return true;
		}
	}

	private static bool TryGetDirectAreaSourceName(AudioSource source, out string sourceName)
	{
		sourceName = null;
		try
		{
			if ((Object)(object)source == (Object)null || !source.loop)
			{
				return false;
			}
			Transform transform = ((Component)source).transform;
			Transform val = ((transform != null) ? transform.parent : null);
			if ((Object)(object)val == (Object)null || !string.Equals(((Object)((Component)val).gameObject).name, "Music", StringComparison.Ordinal))
			{
				return false;
			}
			string candidateName = ((Object)((Component)source).gameObject).name;
			if (!DirectAreaDefinitions.Any((DirectAreaDefinition definition) => string.Equals(definition.SourceName, candidateName, StringComparison.Ordinal)))
			{
				return false;
			}
			sourceName = candidateName;
			return true;
		}
		catch
		{
			return false;
		}
	}

	private void AnnounceDirectAreaTrack(string mappingKey, CustomTrack track)
	{
		if (track != null)
		{
			string fileNameWithoutExtension = Path.GetFileNameWithoutExtension(track.Path);
			ObserveAndMaybeAnnounce(MusicCategory.Area, "direct:" + mappingKey, "custom:" + track.Id, fileNameWithoutExtension, 0f);
		}
	}

	private void AnnounceDirectAreaNative(string mappingKey, AudioClip clip)
	{
		ObserveAndMaybeAnnounce(MusicCategory.Area, "direct:" + mappingKey, "native:" + (((clip != null) ? ((Object)clip).name : null) ?? string.Empty), (clip != null) ? ((Object)clip).name : null, 0f);
	}

	private static void RestoreDirectAreaSource(DirectAreaSourceUse use)
	{
		if (use == null || (Object)(object)use.Source == (Object)null || (Object)(object)use.NativeClip == (Object)null || use.Track == null)
		{
			return;
		}
		try
		{
			if (use.Source.clip == use.Track.Clip)
			{
				use.Source.clip = use.NativeClip;
			}
			use.Track = null;
		}
		catch
		{
		}
	}

	private void RestoreDirectAreaSources()
	{
		foreach (DirectAreaSourceUse directAreaSource in directAreaSources)
		{
			RestoreDirectAreaSource(directAreaSource);
		}
	}

	internal void RegisterBattleScene(BattleScene battle)
	{
		if (!((Object)(object)battle == (Object)null))
		{
			if ((Object)(object)battle.musicCueNone != (Object)null && silenceCueNames.Add(((Object)battle.musicCueNone).name))
			{
				InvalidateMusicMappings();
			}
			RegisterVanillaFanfare(battle.battleEndClip);
			if (isReady)
			{
				EnsureMusicMappings();
				PrefetchFanfare(battle.battleEndClip);
			}
		}
	}

	internal void RegisterBattleSceneSafely(BattleScene battle)
	{
		try
		{
			RegisterBattleScene(battle);
		}
		catch (Exception arg)
		{
			((BaseUnityPlugin)this).Logger.LogError((object)$"Battle music discovery failed without interrupting the native battle controller: {arg}");
		}
	}

	internal AudioClip GetFanfareReplacement(AudioSource source, AudioClip original)
	{
		if (!isReady || !enabledConfig.Value || !randomizeFanfareConfig.Value || (Object)(object)source == (Object)null || (Object)(object)original == (Object)null)
		{
			return original;
		}
		if (((Object)original).name.StartsWith("CustomMusic::", StringComparison.Ordinal))
		{
			CustomTrack customTrack = customTracks.FirstOrDefault((CustomTrack track) => track.IsLoaded && track.Clip == original);
			if (customTrack != null)
			{
				TouchResidentTrack(customTrack);
				TrackActiveFanfare(source, customTrack);
			}
			return original;
		}
		try
		{
			GameManager silentInstance = GameManager.SilentInstance;
			AudioManager val = ((silentInstance != null) ? silentInstance.AudioManager : null);
			bool num = (Object)(object)val != (Object)null && source == val.FanfareEnemyBattleClear;
			bool flag = string.Equals(((Object)source).name, "Fanfare Enemy Battle Clear", StringComparison.Ordinal) || string.Equals(((Object)source).name, "Fanfare Boss Defeat", StringComparison.Ordinal);
			if (!num && !flag)
			{
				return original;
			}
		}
		catch
		{
			return original;
		}
		RegisterVanillaFanfare(original);
		EnsureFanfareMappings();
		if (!fanfareMappings.TryGetValue(((Object)original).name, out var value))
		{
			return original;
		}
		if (value.CustomTrack != null && !value.CustomTrack.IsLoaded)
		{
			QueueTrackLoad(value.CustomTrack);
			return original;
		}
		AudioClip val2 = value.Resolve();
		if ((Object)(object)val2 != (Object)null)
		{
			source.loop = false;
			if (value.CustomTrack != null)
			{
				TouchResidentTrack(value.CustomTrack);
				TrackActiveFanfare(source, value.CustomTrack);
			}
			((BaseUnityPlugin)this).Logger.LogDebug((object)("Fanfare: " + ((Object)original).name + " -> " + value.Id));
			return val2;
		}
		return original;
	}

	private void PrefetchFanfare(AudioClip original)
	{
		if (isReady && !((Object)(object)original == (Object)null) && enabledConfig.Value && randomizeFanfareConfig.Value)
		{
			RegisterVanillaFanfare(original);
			EnsureFanfareMappings();
			if (fanfareMappings.TryGetValue(((Object)original).name, out var value) && value.CustomTrack != null)
			{
				QueueTrackLoad(value.CustomTrack);
			}
		}
	}

	private void RegisterVanillaFanfare(AudioClip clip)
	{
		knownVanillaFanfares.RemoveAll((AudioClip existing) => (Object)(object)existing == (Object)null);
		if (!((Object)(object)clip == (Object)null) && !((Object)clip).name.StartsWith("CustomMusic::", StringComparison.Ordinal) && !knownVanillaFanfares.Any((AudioClip existing) => (Object)(object)existing != (Object)null && string.Equals(((Object)existing).name, ((Object)clip).name, StringComparison.Ordinal)))
		{
			knownVanillaFanfares.Add(clip);
			knownVanillaFanfares.Sort((AudioClip left, AudioClip right) => string.Compare((left != null) ? ((Object)left).name : null, (right != null) ? ((Object)right).name : null, StringComparison.Ordinal));
		}
	}

	private void EnsureMusicMappings()
	{
		MusicCategory[] array = new MusicCategory[4]
		{
			MusicCategory.Area,
			MusicCategory.Battle,
			MusicCategory.Boss,
			MusicCategory.Menu
		};
		Dictionary<MusicCategory, string> signatures = array.ToDictionary((MusicCategory category) => category, BuildMusicSignature);
		string a = string.Join("||", array.Select((MusicCategory category) => signatures[category]));
		if (!string.Equals(a, mappingSignature, StringComparison.Ordinal))
		{
			musicMappings.Clear();
			MusicCategory[] array2 = array;
			foreach (MusicCategory musicCategory in array2)
			{
				BuildCategoryMappings(musicCategory, signatures[musicCategory]);
			}
			mappingSignature = a;
		}
	}

	private void BuildCategoryMappings(MusicCategory category, string signature)
	{
		List<MusicCue> list = (from cue in vanillaCues
			where !ShouldPassThrough(cue)
			where ClassifyCue(cue) == category
			select cue).OrderBy<MusicCue, string>((MusicCue cue) => ((Object)cue).name, StringComparer.Ordinal).ToList();
		List<MusicTarget> list2 = new List<MusicTarget>();
		if (includeVanillaConfig.Value)
		{
			list2.AddRange(list.Select((MusicCue cue) => MusicTarget.FromVanilla(cue, category)));
		}
		if (includeCustomConfig.Value)
		{
			list2.AddRange(customTracks.Where((CustomTrack track) => track.Category == category && !track.HasFailed).Select(MusicTarget.FromCustom));
		}
		list2 = list2.OrderBy<MusicTarget, string>((MusicTarget target) => target.Id, StringComparer.Ordinal).ToList();
		if (list.Count != 0 && list2.Count != 0)
		{
			Shuffle(list2, new Random(StableRandom.SeedFrom(signature + "|" + category)));
			for (int num = 0; num < list.Count; num++)
			{
				MusicTarget value = list2[num % list2.Count];
				musicMappings[((Object)list[num]).name] = value;
			}
		}
	}

	private void EnsureDirectAreaMappings()
	{
		string text = BuildDirectAreaSignature();
		if (string.Equals(text, directAreaSignature, StringComparison.Ordinal))
		{
			return;
		}
		directAreaMappings.Clear();
		List<CustomTrack> list = new List<CustomTrack>();
		if (includeVanillaConfig.Value)
		{
			list.Add(null);
		}
		if (includeCustomConfig.Value)
		{
			list.AddRange(customTracks.Where((CustomTrack track) => track.Category == MusicCategory.Area && !track.HasFailed));
		}
		list = list.OrderBy<CustomTrack, string>((CustomTrack track) => track?.Id ?? string.Empty, StringComparer.Ordinal).ToList();
		if (list.Count > 0)
		{
			Shuffle(list, new Random(StableRandom.SeedFrom(text)));
			List<string> sourceNames = DirectAreaDefinitions.Select((DirectAreaDefinition definition) => definition.SourceName).Distinct<string>(StringComparer.Ordinal).ToList();
			int i;
			for (i = 0; i < sourceNames.Count; i++)
			{
				CustomTrack value = list[i % list.Count];
				foreach (DirectAreaDefinition item in DirectAreaDefinitions.Where((DirectAreaDefinition candidate) => string.Equals(candidate.SourceName, sourceNames[i], StringComparison.Ordinal)))
				{
					directAreaMappings[item.Key] = value;
				}
			}
		}
		directAreaSignature = text;
	}

	private string BuildDirectAreaSignature()
	{
		return string.Join("|", "direct-area", GetSeedIdentity(), includeVanillaConfig.Value, includeCustomConfig.Value, string.Join(",", DirectAreaDefinitions.Select((DirectAreaDefinition definition) => definition.Key)), string.Join(",", from track in customTracks
			where track.Category == MusicCategory.Area && !track.HasFailed
			select track.Id));
	}

	private void EnsureFanfareMappings()
	{
		knownVanillaFanfares.RemoveAll((AudioClip clip) => (Object)(object)clip == (Object)null);
		string text = BuildFanfareSignature();
		if (!string.Equals(text, fanfareSignature, StringComparison.Ordinal))
		{
			fanfareMappings.Clear();
			fanfareSignature = text;
		}
		foreach (AudioClip knownVanillaFanfare in knownVanillaFanfares)
		{
			if ((Object)(object)knownVanillaFanfare == (Object)null)
			{
				continue;
			}
			if (fanfareMappings.TryGetValue(((Object)knownVanillaFanfare).name, out var value))
			{
				if (value.CustomTrack != null || (Object)(object)value.VanillaClip != (Object)null)
				{
					continue;
				}
				fanfareMappings.Remove(((Object)knownVanillaFanfare).name);
			}
			List<FanfareTarget> list = new List<FanfareTarget>();
			if (includeVanillaConfig.Value)
			{
				list.Add(FanfareTarget.FromVanilla(knownVanillaFanfare));
			}
			if (includeCustomConfig.Value)
			{
				list.AddRange(customTracks.Where((CustomTrack track) => track.Category == MusicCategory.Fanfare && !track.HasFailed).Select(FanfareTarget.FromCustom));
			}
			list = list.OrderBy<FanfareTarget, string>((FanfareTarget target) => target.SortKey, StringComparer.Ordinal).ToList();
			if (list.Count != 0)
			{
				int index = new Random(StableRandom.SeedFrom(text + "|" + ((Object)knownVanillaFanfare).name)).Next(list.Count);
				FanfareTarget value2 = list[index];
				fanfareMappings[((Object)knownVanillaFanfare).name] = value2;
			}
		}
	}

	private MusicCategory ClassifyCue(MusicCue cue)
	{
		if ((Object)(object)cue != (Object)null && CatalogAreaCueNames.Contains(((Object)cue).name))
		{
			return MusicCategory.Area;
		}
		if ((Object)(object)cue != (Object)null && CatalogBattleCueNames.Contains(((Object)cue).name))
		{
			return MusicCategory.Battle;
		}
		if ((Object)(object)cue != (Object)null && CatalogMenuCueNames.Contains(((Object)cue).name))
		{
			return MusicCategory.Menu;
		}
		if ((Object)(object)cue != (Object)null && CatalogBossCueNames.Contains(((Object)cue).name))
		{
			return MusicCategory.Boss;
		}
		return MusicCategory.Area;
	}

	private bool ShouldPassThrough(MusicCue cue)
	{
		if ((Object)(object)cue == (Object)null || silenceCueNames.Contains(((Object)cue).name) || SpecialCueNames.Contains(((Object)cue).name) || string.Equals(((Object)cue).name, "None", StringComparison.OrdinalIgnoreCase))
		{
			return true;
		}
		if (!CatalogAreaCueNames.Contains(((Object)cue).name) && !CatalogBattleCueNames.Contains(((Object)cue).name) && !CatalogBossCueNames.Contains(((Object)cue).name) && !CatalogMenuCueNames.Contains(((Object)cue).name))
		{
			if (loggedUnknownCueNames.Add(((Object)cue).name))
			{
				((BaseUnityPlugin)this).Logger.LogWarning((object)("Leaving unknown music cue unchanged: " + ((Object)cue).name));
			}
			return true;
		}
		try
		{
			for (int i = 0; i < 6; i++)
			{
				MusicChannelInfo channelInfo = cue.GetChannelInfo((MusicChannels)i);
				if (channelInfo != null && channelInfo.IsEnabled)
				{
					return false;
				}
			}
			return true;
		}
		catch
		{
			return false;
		}
	}

	private string BuildMusicSignature(MusicCategory category)
	{
		return string.Join("|", "music", category, GetSeedIdentity(), includeVanillaConfig.Value, includeCustomConfig.Value, string.Join(",", from track in customTracks
			where track.Category == category && !track.HasFailed
			select track.Id));
	}

	private string BuildFanfareSignature()
	{
		return string.Join("|", "fanfare", GetSeedIdentity(), includeVanillaConfig.Value, includeCustomConfig.Value, randomizeFanfareConfig.Value, string.Join(",", from track in customTracks
			where track.Category == MusicCategory.Fanfare && !track.HasFailed
			select track.Id));
	}

	private string GetSeedIdentity()
	{
		int standaloneSeed = ((standaloneSeedConfig.Value != 0) ? standaloneSeedConfig.Value : generatedSeedConfig.Value);
		if (!useRandomizerSeedConfig.Value)
		{
			return "standalone:" + standaloneSeed;
		}
		return SeedIdentityProvider.GetIdentity(standaloneSeed);
	}

	private static void Shuffle<T>(IList<T> values, Random random)
	{
		for (int num = values.Count - 1; num > 0; num--)
		{
			int index = random.Next(num + 1);
			T value = values[num];
			values[num] = values[index];
			values[index] = value;
		}
	}

	private void InvalidateMappings()
	{
		InvalidateMusicMappings();
		fanfareSignature = null;
		directAreaSignature = null;
	}

	private void InvalidateMusicMappings()
	{
		mappingSignature = null;
	}

	private void OnDestroy()
	{
		//IL_00df: Unknown result type (might be due to invalid IL or missing references)
		isShuttingDown = true;
		pendingMusicRequest = null;
		trackLoadQueue.Clear();
		queuedTrackLoads.Clear();
		RestoreMutedAuthoredMusicLayers();
		RestoreNormalMusicRouting();
		ClearMusicRoutingReferences();
		Harmony obj = harmony;
		if (obj != null)
		{
			obj.UnpatchSelf();
		}
		harmony = null;
		RestoreDirectAreaSources();
		foreach (CustomTrack customTrack in customTracks)
		{
			customTrack.Dispose();
		}
		customTracks.Clear();
		trackLastUsed.Clear();
		activeFanfares.Clear();
		directAreaSources.Clear();
		mutedAuthoredMusicSources.Clear();
		directAreaMappings.Clear();
		if (hasVanillaCueHandle && vanillaCueHandle.IsValid())
		{
			Addressables.Release<IList<MusicCue>>(vanillaCueHandle);
		}
		hasVanillaCueHandle = false;
		if (Instance == this)
		{
			Instance = null;
			Log = null;
		}
		EmbeddedDependencyLoader.Uninstall();
	}
}
internal static class CustomTrackLoader
{
	private static readonly string[] SupportedExtensions = new string[5] { ".ogg", ".wav", ".mp3", ".m4a", ".flac" };

	internal static void EnsureFolders(string root)
	{
		Directory.CreateDirectory(root);
		foreach (MusicCategory value in Enum.GetValues(typeof(MusicCategory)))
		{
			Directory.CreateDirectory(Path.Combine(root, value.ToString()));
		}
	}

	internal static void DiscoverAll(string root, IList<CustomTrack> destination, ManualLogSource log)
	{
		EnsureFolders(root);
		PruneStaleM4aCache(root, log);
		foreach (MusicCategory value in Enum.GetValues(typeof(MusicCategory)))
		{
			string path = Path.Combine(root, value.ToString());
			string[] array;
			try
			{
				array = Directory.EnumerateFiles(path, "*", SearchOption.AllDirectories).Where(IsSupported).OrderBy<string, string>((string result) => result, StringComparer.OrdinalIgnoreCase)
					.ToArray();
			}
			catch (Exception ex)
			{
				log.LogWarning((object)$"Could not inventory custom {value} tracks: {ex.Message}");
				continue;
			}
			string[] array2 = array;
			foreach (string path2 in array2)
			{
				string text = Path.GetRelativePath(root, path2).Replace('\\', '/');
				string id = value.ToString() + ":" + text;
				destination.Add(new CustomTrack(id, path2, value));
			}
		}
		log.LogInfo((object)string.Format("Indexed {0} custom track file{1} without loading their audio into memory.", destination.Count, (destination.Count == 1) ? string.Empty : "s"));
	}

	private static void PruneStaleM4aCache(string root, ManualLogSource log)
	{
		string m4aCacheRoot = GetM4aCacheRoot();
		HashSet<string> hashSet = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
		bool flag = true;
		try
		{
			foreach (MusicCategory value in Enum.GetValues(typeof(MusicCategory)))
			{
				foreach (string item in Directory.EnumerateFiles(Path.Combine(root, value.ToString()), "*", SearchOption.AllDirectories))
				{
					if (string.Equals(Path.GetExtension(item), ".m4a", StringComparison.OrdinalIgnoreCase))
					{
						try
						{
							hashSet.Add(M4aCache.GetCachePath(item, m4aCacheRoot));
						}
						catch (Exception ex)
						{
							flag = false;
							log.LogWarning((object)("Could not inspect M4A track '" + item + "' while cleaning the cache: " + ex.Message));
						}
					}
				}
			}
		}
		catch (Exception ex2)
		{
			flag = false;
			log.LogWarning((object)("Could not inventory custom M4A tracks while cleaning the cache: " + ex2.Message));
		}
		if (!flag)
		{
			log.LogWarning((object)"Skipped stale M4A cache cleanup because the current M4A inventory was incomplete.");
			return;
		}
		int num = M4aCache.PruneStale(m4aCacheRoot, hashSet, delegate(string message)
		{
			log.LogWarning((object)message);
		});
		if (num > 0)
		{
			log.LogInfo((object)string.Format("Removed {0} stale M4A cache file{1}.", num, (num == 1) ? string.Empty : "s"));
		}
	}

	internal static IEnumerator LoadTrack(string root, CustomTrack track, ManualLogSource log)
	{
		if (track == null || !track.BeginLoading())
		{
			yield break;
		}
		string path = track.Path;
		MusicCategory category = track.Category;
		string text = path;
		if (string.Equals(Path.GetExtension(path), ".m4a", StringComparison.OrdinalIgnoreCase))
		{
			string m4aCacheRoot = GetM4aCacheRoot();
			string cachePath;
			try
			{
				cachePath = M4aCache.GetCachePath(path, m4aCacheRoot);
			}
			catch (Exception ex)
			{
				log.LogWarning((object)$"Could not inspect custom {category} M4A track '{path}': {ex.Message}");
				track.FailLoading();
				yield break;
			}
			if (!M4aCache.IsReady(cachePath))
			{
				string arg = Path.GetRelativePath(root, path).Replace('\\', '/');
				log.LogInfo((object)$"Preparing custom {category} M4A track: {arg}");
				Task<string> conversionTask;
				try
				{
					conversionTask = Task.Run(() => M4aCache.Prepare(path, cachePath));
				}
				catch (Exception ex2)
				{
					log.LogWarning((object)("Could not start M4A conversion for '" + path + "': " + ex2.Message));
					track.FailLoading();
					yield break;
				}
				while (!conversionTask.IsCompleted)
				{
					yield return null;
				}
				if (conversionTask.IsCanceled)
				{
					log.LogWarning((object)("M4A conversion was cancelled for custom track '" + path + "'."));
					track.FailLoading();
					yield break;
				}
				if (conversionTask.IsFaulted)
				{
					log.LogWarning((object)string.Format(arg2: (conversionTask.Exception?.GetBaseException())?.ToString() ?? "unknown error", format: "Could not convert custom {0} M4A track '{1}': {2}", arg0: category, arg1: path));
					track.FailLoading();
					yield break;
				}
				text = conversionTask.Result;
			}
			else
			{
				text = cachePath;
			}
		}
		AudioType audioType = GetAudioType(text);
		UnityWebRequest request = null;
		try
		{
			request = UnityWebRequestMultimedia.GetAudioClip(new Uri(text).AbsoluteUri, audioType);
			DownloadHandler downloadHandler = request.downloadHandler;
			DownloadHandlerAudioClip val = (DownloadHandlerAudioClip)(object)((downloadHandler is DownloadHandlerAudioClip) ? downloadHandler : null);
			if (val != null)
			{
				val.compressed = true;
				val.streamAudio = false;
			}
		}
		catch (Exception ex3)
		{
			log.LogWarning((object)$"Could not prepare custom {category} track '{path}': {ex3.Message}");
			UnityWebRequest obj = request;
			if (obj != null)
			{
				obj.Dispose();
			}
			track.FailLoading();
			yield break;
		}
		yield return request.SendWebRequest();
		if ((int)request.result != 1)
		{
			log.LogWarning((object)$"Could not load custom {category} track '{path}': {request.error}");
			request.Dispose();
			track.FailLoading();
			yield break;
		}
		AudioClip content;
		try
		{
			content = DownloadHandlerAudioClip.GetContent(request);
		}
		catch (Exception ex4)
		{
			log.LogWarning((object)$"Could not finish loading custom {category} track '{path}': {ex4.Message}");
			request.Dispose();
			track.FailLoading();
			yield break;
		}
		if ((Object)(object)content == (Object)null)
		{
			log.LogWarning((object)("Unity returned no audio clip for custom track '" + path + "'."));
			request.Dispose();
			track.FailLoading();
			yield break;
		}
		string arg2 = Path.GetRelativePath(root, path).Replace('\\', '/');
		((Object)content).name = "CustomMusic::" + track.Id;
		((Object)content).hideFlags = (HideFlags)61;
		request.Dispose();
		track.CompleteLoading(content);
		log.LogInfo((object)($"Loaded assigned custom {category} track: {arg2} " + $"({content.length:0.0}s, {content.loadType})"));
	}

	private static bool IsSupported(string path)
	{
		string extension = Path.GetExtension(path);
		return SupportedExtensions.Contains<string>(extension, StringComparer.OrdinalIgnoreCase);
	}

	private static string GetM4aCacheRoot()
	{
		return Path.Combine(Paths.CachePath, "CustomMusicRandomizer", "M4A");
	}

	private static AudioType GetAudioType(string path)
	{
		return (AudioType)(Path.GetExtension(path).ToLowerInvariant() switch
		{
			".ogg" => 14, 
			".wav" => 20, 
			".mp3" => 13, 
			".flac" => 0, 
			_ => 0, 
		});
	}
}
internal static class EmbeddedDependencyLoader
{
	private const string CoreIdentity = "NAudio.Core, Version=2.3.0.0, Culture=neutral, PublicKeyToken=e279aa5131008a41";

	private const string WasapiIdentity = "NAudio.Wasapi, Version=2.3.0.0, Culture=neutral, PublicKeyToken=e279aa5131008a41";

	private static readonly object SyncRoot = new object();

	private static bool installed;

	internal static void Install()
	{
		if (!installed)
		{
			AppDomain.CurrentDomain.AssemblyResolve += Resolve;
			installed = true;
		}
	}

	internal static void Uninstall()
	{
		if (installed)
		{
			AppDomain.CurrentDomain.AssemblyResolve -= Resolve;
			installed = false;
		}
	}

	private static Assembly Resolve(object sender, ResolveEventArgs args)
	{
		AssemblyName assemblyName;
		try
		{
			assemblyName = new AssemblyName(args.Name);
		}
		catch
		{
			return null;
		}
		string name;
		if (string.Equals(assemblyName.FullName, "NAudio.Core, Version=2.3.0.0, Culture=neutral, PublicKeyToken=e279aa5131008a41", StringComparison.OrdinalIgnoreCase))
		{
			name = "CustomMusicRandomizer.Dependencies.NAudio.Core.dll";
		}
		else
		{
			if (!string.Equals(assemblyName.FullName, "NAudio.Wasapi, Version=2.3.0.0, Culture=neutral, PublicKeyToken=e279aa5131008a41", StringComparison.OrdinalIgnoreCase))
			{
				return null;
			}
			name = "CustomMusicRandomizer.Dependencies.NAudio.Wasapi.dll";
		}
		Assembly executingAssembly = Assembly.GetExecutingAssembly();
		Assembly requestingAssembly = args.RequestingAssembly;
		if ((object)requestingAssembly != executingAssembly && (!string.Equals(assemblyName.FullName, "NAudio.Core, Version=2.3.0.0, Culture=neutral, PublicKeyToken=e279aa5131008a41", StringComparison.OrdinalIgnoreCase) || !string.Equals(requestingAssembly?.GetName().FullName, "NAudio.Wasapi, Version=2.3.0.0, Culture=neutral, PublicKeyToken=e279aa5131008a41", StringComparison.OrdinalIgnoreCase)))
		{
			return null;
		}
		Assembly[] assemblies = AppDomain.CurrentDomain.GetAssemblies();
		foreach (Assembly assembly in assemblies)
		{
			if (string.Equals(assembly.GetName().FullName, assemblyName.FullName, StringComparison.OrdinalIgnoreCase))
			{
				return assembly;
			}
		}
		lock (SyncRoot)
		{
			assemblies = AppDomain.CurrentDomain.GetAssemblies();
			foreach (Assembly assembly2 in assemblies)
			{
				if (string.Equals(assembly2.GetName().FullName, assemblyName.FullName, StringComparison.OrdinalIgnoreCase))
				{
					return assembly2;
				}
			}
			using Stream stream = executingAssembly.GetManifestResourceStream(name);
			if (stream == null)
			{
				return null;
			}
			using MemoryStream memoryStream = new MemoryStream();
			stream.CopyTo(memoryStream);
			return Assembly.Load(memoryStream.ToArray());
		}
	}
}
internal static class M4aCache
{
	private const int Mp3BitRate = 320000;

	private const string CacheFormatVersion = "m4a-mp3-v1";

	private const string ManagedFilePrefix = "cmr-m4a-v1-";

	internal static string GetCachePath(string sourcePath, string cacheRoot)
	{
		FileInfo fileInfo = new FileInfo(sourcePath);
		if (!fileInfo.Exists)
		{
			throw new FileNotFoundException("The M4A source file no longer exists.", sourcePath);
		}
		string s = string.Join("\n", "m4a-mp3-v1", Path.GetFullPath(sourcePath), fileInfo.Length.ToString(), fileInfo.LastWriteTimeUtc.Ticks.ToString(), 320000.ToString());
		byte[] array;
		using (SHA256 sHA = SHA256.Create())
		{
			array = sHA.ComputeHash(Encoding.UTF8.GetBytes(s));
		}
		StringBuilder stringBuilder = new StringBuilder(array.Length * 2);
		for (int i = 0; i < array.Length; i++)
		{
			stringBuilder.Append(array[i].ToString("x2"));
		}
		return Path.Combine(cacheRoot, "cmr-m4a-v1-" + stringBuilder?.ToString() + ".mp3");
	}

	internal static bool IsReady(string cachePath)
	{
		try
		{
			return File.Exists(cachePath) && new FileInfo(cachePath).Length > 4096;
		}
		catch
		{
			return false;
		}
	}

	internal static int PruneStale(string cacheRoot, ISet<string> activeCachePaths, Action<string> warning)
	{
		if (!Directory.Exists(cacheRoot))
		{
			return 0;
		}
		int num = 0;
		string[] files;
		try
		{
			files = Directory.GetFiles(cacheRoot, "*.mp3", SearchOption.TopDirectoryOnly);
		}
		catch (Exception ex)
		{
			warning?.Invoke("Could not inspect the M4A cache: " + ex.Message);
			return 0;
		}
		foreach (string text in files)
		{
			if (IsManagedCacheFile(text) && !activeCachePaths.Contains(text))
			{
				try
				{
					File.Delete(text);
					num++;
				}
				catch (Exception ex2)
				{
					warning?.Invoke("Could not remove stale M4A cache file '" + text + "': " + ex2.Message);
				}
			}
		}
		return num;
	}

	private static bool IsManagedCacheFile(string path)
	{
		string fileNameWithoutExtension = Path.GetFileNameWithoutExtension(path);
		if (!fileNameWithoutExtension.StartsWith("cmr-m4a-v1-", StringComparison.Ordinal) || fileNameWithoutExtension.Length != "cmr-m4a-v1-".Length + 64)
		{
			return false;
		}
		for (int i = "cmr-m4a-v1-".Length; i < fileNameWithoutExtension.Length; i++)
		{
			if (!Uri.IsHexDigit(fileNameWithoutExtension[i]))
			{
				return false;
			}
		}
		return true;
	}

	internal static string Prepare(string sourcePath, string cachePath)
	{
		//IL_003a: Unknown result type (might be due to invalid IL or missing references)
		//IL_0040: Expected O, but got Unknown
		if (IsReady(cachePath))
		{
			return cachePath;
		}
		Directory.CreateDirectory(Path.GetDirectoryName(cachePath));
		string text = cachePath + ".tmp-" + Guid.NewGuid().ToString("N") + ".mp3";
		try
		{
			MediaFoundationReader val = new MediaFoundationReader(sourcePath);
			try
			{
				MediaFoundationEncoder.EncodeToMp3((IWaveProvider)(object)val, text, 320000);
			}
			finally
			{
				((IDisposable)val)?.Dispose();
			}
			if (!IsReady(text))
			{
				throw new InvalidDataException("Windows Media Foundation produced an empty MP3 cache file.");
			}
			if (IsReady(cachePath))
			{
				File.Delete(text);
			}
			else
			{
				if (File.Exists(cachePath))
				{
					File.Delete(cachePath);
				}
				File.Move(text, cachePath);
			}
			return cachePath;
		}
		finally
		{
			if (File.Exists(text))
			{
				File.Delete(text);
			}
		}
	}
}
internal enum MusicCategory
{
	Area,
	Battle,
	Boss,
	Menu,
	Fanfare
}
internal sealed class CustomTrack : IDisposable
{
	private readonly Dictionary<int, MusicCue> cuesByTemplate = new Dictionary<int, MusicCue>();

	internal string Id { get; }

	internal string Path { get; }

	internal MusicCategory Category { get; }

	internal AudioClip Clip { get; private set; }

	internal bool IsLoaded => (Object)(object)Clip != (Object)null;

	internal bool IsLoading { get; private set; }

	internal bool HasFailed { get; private set; }

	internal CustomTrack(string id, string path, MusicCategory category)
	{
		Id = id;
		Path = path;
		Category = category;
	}

	internal bool BeginLoading()
	{
		if (IsLoaded || IsLoading || HasFailed)
		{
			return false;
		}
		IsLoading = true;
		return true;
	}

	internal void CompleteLoading(AudioClip clip)
	{
		IsLoading = false;
		if ((Object)(object)clip == (Object)null)
		{
			HasFailed = true;
			return;
		}
		Clip = clip;
		HasFailed = false;
	}

	internal void FailLoading()
	{
		IsLoading = false;
		HasFailed = true;
	}

	internal MusicCue GetCue(MusicCue template)
	{
		if ((Object)(object)Clip == (Object)null)
		{
			return null;
		}
		int key = (((Object)(object)template != (Object)null) ? ((Object)template).GetInstanceID() : 0);
		if (!cuesByTemplate.TryGetValue(key, out var value) || (Object)(object)value == (Object)null)
		{
			value = MusicCueFactory.Create(Id, Clip, template);
			cuesByTemplate[key] = value;
		}
		return value;
	}

	internal void Unload()
	{
		foreach (MusicCue value in cuesByTemplate.Values)
		{
			if ((Object)(object)value != (Object)null)
			{
				Object.Destroy((Object)(object)value);
			}
		}
		cuesByTemplate.Clear();
		if ((Object)(object)Clip != (Object)null)
		{
			Object.Destroy((Object)(object)Clip);
			Clip = null;
		}
		IsLoading = false;
	}

	public void Dispose()
	{
		Unload();
	}
}
internal sealed class MusicTarget
{
	internal string Id { get; }

	internal MusicCategory Category { get; }

	internal MusicCue VanillaCue { get; }

	internal CustomTrack CustomTrack { get; }

	private MusicTarget(string id, MusicCategory category, MusicCue vanillaCue, CustomTrack customTrack)
	{
		Id = id;
		Category = category;
		VanillaCue = vanillaCue;
		CustomTrack = customTrack;
	}

	internal static MusicTarget FromVanilla(MusicCue cue, MusicCategory category)
	{
		return new MusicTarget("vanilla:" + ((Object)cue).name, category, cue, null);
	}

	internal static MusicTarget FromCustom(CustomTrack track)
	{
		return new MusicTarget("custom:" + track.Id, track.Category, null, track);
	}

	internal MusicCue Resolve(MusicCue original)
	{
		if (!((Object)(object)VanillaCue != (Object)null))
		{
			return CustomTrack?.GetCue(original);
		}
		return VanillaCue;
	}
}
internal sealed class FanfareTarget
{
	internal string Id { get; }

	internal string SortKey { get; }

	internal AudioClip VanillaClip { get; }

	internal CustomTrack CustomTrack { get; }

	private FanfareTarget(string id, string sortKey, AudioClip vanillaClip, CustomTrack customTrack)
	{
		Id = id;
		SortKey = sortKey;
		VanillaClip = vanillaClip;
		CustomTrack = customTrack;
	}

	internal static FanfareTarget FromVanilla(AudioClip clip)
	{
		return new FanfareTarget("vanilla:" + ((Object)clip).name, ((Object)clip).name, clip, null);
	}

	internal static FanfareTarget FromCustom(CustomTrack track)
	{
		return new FanfareTarget("custom:" + track.Id, "CustomMusic::" + track.Id, null, track);
	}

	internal AudioClip Resolve()
	{
		if (!((Object)(object)VanillaClip != (Object)null))
		{
			return CustomTrack?.Clip;
		}
		return VanillaClip;
	}
}
internal static class MusicCueFactory
{
	private static readonly FieldInfo ChannelInfosField = typeof(MusicCue).GetField("channelInfos", BindingFlags.Instance | BindingFlags.NonPublic);

	private static readonly FieldInfo SnapshotField = typeof(MusicCue).GetField("snapshot", BindingFlags.Instance | BindingFlags.NonPublic);

	private static readonly FieldInfo ClipField = typeof(MusicChannelInfo).GetField("clip", BindingFlags.Instance | BindingFlags.NonPublic);

	private static readonly FieldInfo SyncField = typeof(MusicChannelInfo).GetField("sync", BindingFlags.Instance | BindingFlags.NonPublic);

	internal static bool IsSupported
	{
		get
		{
			if (ChannelInfosField != null && SnapshotField != null && ClipField != null)
			{
				return SyncField != null;
			}
			return false;
		}
	}

	internal static MusicCue Create(string trackId, AudioClip clip, MusicCue template)
	{
		//IL_0038: Unknown result type (might be due to invalid IL or missing references)
		//IL_003e: Expected O, but got Unknown
		if (!IsSupported || (Object)(object)clip == (Object)null)
		{
			return null;
		}
		MusicCue val = ScriptableObject.CreateInstance<MusicCue>();
		((Object)val).name = "CustomMusic::" + trackId;
		((Object)val).hideFlags = (HideFlags)61;
		MusicChannelInfo[] array = (MusicChannelInfo[])(object)new MusicChannelInfo[6];
		MusicChannelInfo val2 = new MusicChannelInfo();
		ClipField.SetValue(val2, clip);
		SyncField.SetValue(val2, (object)(MusicChannelSync)2);
		array[0] = val2;
		ChannelInfosField.SetValue(val, array);
		SnapshotField.SetValue(val, (template != null) ? template.Snapshot : null);
		return val;
	}
}
[HarmonyPatch(typeof(AudioManager), "ApplyMusicCue")]
internal static class ApplyMusicCuePatch
{
	private static bool Prefix(AudioManager __instance, ref MusicCue musicCue, float delayTime, float transitionTime, bool applySnapshot)
	{
		CustomMusicRandomizerPlugin instance = CustomMusicRandomizerPlugin.Instance;
		if ((Object)(object)instance != (Object)null && !AudioManager.BlockAudioChange)
		{
			return instance.PrepareMusicCue(__instance, ref musicCue, delayTime, transitionTime, applySnapshot);
		}
		return true;
	}
}
[HarmonyPatch]
internal static class BeginApplyMusicCueRoutingPatch
{
	private static MethodBase TargetMethod()
	{
		return AccessTools.Method(typeof(AudioManager), "BeginApplyMusicCue", new Type[2]
		{
			typeof(MusicCue),
			typeof(float)
		}, (Type[])null);
	}

	private static void Postfix(AudioManager __instance, MusicCue musicCue, ref IEnumerator __result)
	{
		if (__result != null)
		{
			__result = ApplyRoutingAfterNativeScheduling(__instance, musicCue, __result);
		}
	}

	private static IEnumerator ApplyRoutingAfterNativeScheduling(AudioManager manager, MusicCue cue, IEnumerator nativeRoutine)
	{
		int moveCount = 0;
		try
		{
			while (nativeRoutine.MoveNext())
			{
				moveCount++;
				if (moveCount == 2)
				{
					CustomMusicRandomizerPlugin.Instance?.ApplyMusicRoutingForCue(manager, cue);
				}
				yield return nativeRoutine.Current;
			}
		}
		finally
		{
			(nativeRoutine as IDisposable)?.Dispose();
		}
	}
}
[HarmonyPatch(typeof(AudioManager), "StopAndClearMusic")]
internal static class StopAndClearMusicRoutingPatch
{
	private static void Postfix(AudioManager __instance)
	{
		CustomMusicRandomizerPlugin.Instance?.CancelPendingMusicAndRestoreNormalRouting(__instance);
	}
}
[HarmonyPatch(typeof(AudioMixerExtensions), "TransitionToSafe")]
internal static class MenuExitMusicRoutingPatch
{
	private static readonly FieldInfo NoMusicSnapshotField = AccessTools.Field(typeof(GameManager), "noMusicSnapshot");

	private static void Prefix(AudioMixerSnapshot snapshot)
	{
		try
		{
			GameManager silentInstance = GameManager.SilentInstance;
			if (!((Object)(object)silentInstance == (Object)null) && !((Object)(object)snapshot == (Object)null))
			{
				object? obj = NoMusicSnapshotField?.GetValue(silentInstance);
				AudioMixerSnapshot val = (AudioMixerSnapshot)((obj is AudioMixerSnapshot) ? obj : null);
				if (snapshot == val)
				{
					PrepareForLeavingMenu(silentInstance);
				}
			}
		}
		catch (Exception ex)
		{
			ManualLogSource log = CustomMusicRandomizerPlugin.Log;
			if (log != null)
			{
				log.LogWarning((object)("Could not prepare the title music fade: " + ex.Message));
			}
		}
	}

	internal static void PrepareForLeavingMenu(GameManager manager = null)
	{
		try
		{
			manager = manager ?? GameManager.SilentInstance;
			CustomMusicRandomizerPlugin.Instance?.PrepareForLeavingMenu((manager != null) ? manager.AudioManager : null);
		}
		catch (Exception ex)
		{
			ManualLogSource log = CustomMusicRandomizerPlugin.Log;
			if (log != null)
			{
				log.LogWarning((object)("Could not prepare the title music fade: " + ex.Message));
			}
		}
	}
}
[HarmonyPatch(typeof(GameManager), "ContinueGame")]
internal static class ContinueGameMusicRoutingPatch
{
	private static void Prefix()
	{
		MenuExitMusicRoutingPatch.PrepareForLeavingMenu();
	}
}
[HarmonyPatch(typeof(GameManager), "StartNewGame")]
internal static class StartNewGameMusicRoutingPatch
{
	private static void Prefix()
	{
		MenuExitMusicRoutingPatch.PrepareForLeavingMenu();
	}
}
[HarmonyPatch(typeof(BattleScene), "Awake")]
internal static class BattleSceneAwakePatch
{
	private static void Postfix(BattleScene __instance)
	{
		CustomMusicRandomizerPlugin.Instance?.RegisterBattleSceneSafely(__instance);
	}
}
[HarmonyPatch]
internal static class FanfareAudioSourcePatch
{
	private static MethodBase TargetMethod()
	{
		return AccessTools.Method(typeof(AudioSource), "Play", Type.EmptyTypes, (Type[])null);
	}

	private static void Prefix(AudioSource __instance)
	{
		CustomMusicRandomizerPlugin instance = CustomMusicRandomizerPlugin.Instance;
		if ((Object)(object)instance != (Object)null && (Object)(object)__instance != (Object)null && (Object)(object)__instance.clip != (Object)null && !instance.TryPrepareDirectAreaSource(__instance))
		{
			__instance.clip = instance.GetFanfareReplacement(__instance, __instance.clip);
		}
	}
}
[HarmonyPatch]
internal static class FanfarePlayOneShotPatch
{
	private static MethodBase TargetMethod()
	{
		return AccessTools.Method(typeof(AudioSource), "PlayOneShot", new Type[2]
		{
			typeof(AudioClip),
			typeof(float)
		}, (Type[])null);
	}

	private static void Prefix(AudioSource __instance, ref AudioClip clip)
	{
		CustomMusicRandomizerPlugin instance = CustomMusicRandomizerPlugin.Instance;
		if ((Object)(object)instance != (Object)null)
		{
			clip = instance.GetFanfareReplacement(__instance, clip);
		}
	}
}
internal static class SeedIdentityProvider
{
	private static bool searched;

	private static Type archipelagoType;

	private static PropertyInfo archipelagoInstanceProperty;

	private static PropertyInfo roomSeedProperty;

	private static PropertyInfo teamProperty;

	private static PropertyInfo slotProperty;

	private static Type saveStateType;

	private static PropertyInfo saveStateInstanceProperty;

	private static FieldInfo savedRoomSeedField;

	private static FieldInfo savedTeamField;

	private static FieldInfo savedSlotField;

	internal static string GetIdentity(int standaloneSeed)
	{
		EnsureSearched();
		string text = TryGetLiveIdentity() ?? TryGetSavedIdentity();
		if (string.IsNullOrEmpty(text))
		{
			return "standalone:" + standaloneSeed;
		}
		return text;
	}

	private static void EnsureSearched()
	{
		if (searched)
		{
			return;
		}
		searched = true;
		try
		{
			Assembly[] assemblies = AppDomain.CurrentDomain.GetAssemblies();
			foreach (Assembly assembly in assemblies)
			{
				if (string.Equals(assembly.GetName().Name, "SilksongRandomizer", StringComparison.OrdinalIgnoreCase))
				{
					archipelagoType = assembly.GetType("SilksongRandomizer.Archipelago", throwOnError: false);
					saveStateType = assembly.GetType("SilksongRandomizer.SaveState", throwOnError: false);
					break;
				}
			}
			if (archipelagoType != null)
			{
				archipelagoInstanceProperty = archipelagoType.GetProperty("Instance", BindingFlags.Static | BindingFlags.Public);
				roomSeedProperty = archipelagoType.GetProperty("RoomSeed");
				teamProperty = archipelagoType.GetProperty("Team");
				slotProperty = archipelagoType.GetProperty("Slot");
			}
			if (saveStateType != null)
			{
				saveStateInstanceProperty = saveStateType.GetProperty("Instance", BindingFlags.Static | BindingFlags.Public);
				savedRoomSeedField = saveStateType.GetField("roomSeed");
				savedTeamField = saveStateType.GetField("team");
				savedSlotField = saveStateType.GetField("slot");
			}
		}
		catch
		{
			archipelagoType = null;
			saveStateType = null;
		}
	}

	private static string TryGetLiveIdentity()
	{
		try
		{
			object obj = archipelagoInstanceProperty?.GetValue(null);
			if (obj == null)
			{
				return null;
			}
			string room = roomSeedProperty?.GetValue(obj) as string;
			int team = Convert.ToInt32(teamProperty?.GetValue(obj) ?? ((object)(-1)));
			int slot = Convert.ToInt32(slotProperty?.GetValue(obj) ?? ((object)(-1)));
			return BuildIdentity(room, team, slot);
		}
		catch
		{
			return null;
		}
	}

	private static string TryGetSavedIdentity()
	{
		try
		{
			object obj = saveStateInstanceProperty?.GetValue(null);
			if (obj == null)
			{
				return null;
			}
			string room = savedRoomSeedField?.GetValue(obj) as string;
			int team = Convert.ToInt32(savedTeamField?.GetValue(obj) ?? ((object)(-1)));
			int slot = Convert.ToInt32(savedSlotField?.GetValue(obj) ?? ((object)(-1)));
			return BuildIdentity(room, team, slot);
		}
		catch
		{
			return null;
		}
	}

	private static string BuildIdentity(string room, int team, int slot)
	{
		if (!string.IsNullOrWhiteSpace(room) && team >= 0 && slot >= 0)
		{
			return $"archipelago:{room}|{team}|{slot}";
		}
		return null;
	}
}
internal static class StableRandom
{
	internal static int SeedFrom(string value)
	{
		uint num = 2166136261u;
		string text = value ?? string.Empty;
		for (int i = 0; i < text.Length; i++)
		{
			num ^= text[i];
			num *= 16777619;
		}
		return (int)num;
	}
}