Decompiled source of Cacophony v1.0.0

Cacophony.dll

Decompiled 2 weeks ago
using System;
using System.Collections;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Net;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.Versioning;
using System.Text;
using System.Threading;
using BepInEx;
using BepInEx.Configuration;
using Jotunn;
using Microsoft.CodeAnalysis;
using MyMods.Web;
using Newtonsoft.Json;
using UnityEngine;
using UnityEngine.Networking;

[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("Cacophony")]
[assembly: AssemblyConfiguration("Release")]
[assembly: AssemblyFileVersion("1.0.0.0")]
[assembly: AssemblyInformationalVersion("1.0.0+901b84ab23f2f10926b13865f9b40bb283490874")]
[assembly: AssemblyProduct("Cacophony")]
[assembly: AssemblyTitle("Cacophony")]
[assembly: AssemblyVersion("1.0.0.0")]
[module: RefSafetyRules(11)]
namespace Microsoft.CodeAnalysis
{
	[CompilerGenerated]
	[Embedded]
	internal sealed class EmbeddedAttribute : Attribute
	{
	}
}
namespace System.Runtime.CompilerServices
{
	[CompilerGenerated]
	[Embedded]
	[AttributeUsage(AttributeTargets.Module, AllowMultiple = false, Inherited = false)]
	internal sealed class RefSafetyRulesAttribute : Attribute
	{
		public readonly int Version;

		public RefSafetyRulesAttribute(int P_0)
		{
			Version = P_0;
		}
	}
}
namespace MyMods.Web
{
	public static class WebNav
	{
		public sealed class Link
		{
			public readonly string Id;

			public readonly string Label;

			public readonly int Port;

			public string Url => "http://127.0.0.1:" + Port + "/";

			public Link(string id, string label, int port)
			{
				Id = id;
				Label = label;
				Port = port;
			}
		}

		public static readonly Link[] Links = new Link[13]
		{
			new Link("monsters", "Monsters", 8777),
			new Link("searoute", "SeaRoute", 8778),
			new Link("longboat", "Harpoon Longboat", 8779),
			new Link("tradewind", "TradeWind", 8780),
			new Link("safeharbor", "Safe Harbor", 8781),
			new Link("hub", "DFG Hub", 8782),
			new Link("vault", "Viking Vault", 8783),
			new Link("qolvault", "QoL Vault Buttons", 8784),
			new Link("qolinventory", "QoL Inventory Buttons", 8785),
			new Link("cacophony", "Cacophony", 8787),
			new Link("ominous", "Ominous", 8788),
			new Link("pebble", "Pebble", 8789),
			new Link("trolls", "Trolls", 8790)
		};

		public const string Style = "nav.top{position:sticky;top:0;z-index:20;font-family:Segoe UI,Arial,sans-serif;background:#0d1219;border-bottom:1px solid #2b3b4c;padding:6px 14px;display:flex;align-items:center;gap:6px;flex-wrap:wrap}\nnav.top .brand{color:#7f8fa0;font-size:11px;text-transform:uppercase;letter-spacing:.6px;margin-right:8px}\nnav.top a{color:#9fc3dd;text-decoration:none;font-size:13px;padding:4px 9px;border-radius:5px}\nnav.top a:hover{background:#1a2530}\nnav.top a.here{background:#3a2b4c;border:1px solid #55406b;color:#efe0ff}\n";

		public static string Html(string currentId)
		{
			StringBuilder stringBuilder = new StringBuilder();
			stringBuilder.Append("<nav class='top'><span class='brand'>MyMods</span>");
			Link[] links = Links;
			foreach (Link link in links)
			{
				stringBuilder.Append("<a class='");
				if (link.Id == currentId)
				{
					stringBuilder.Append("here");
				}
				stringBuilder.Append("' href='").Append(link.Url).Append("'>")
					.Append(link.Label)
					.Append("</a>");
			}
			stringBuilder.Append("</nav>");
			return stringBuilder.ToString();
		}

		public static Link Find(string id)
		{
			Link[] links = Links;
			foreach (Link link in links)
			{
				if (link.Id == id)
				{
					return link;
				}
			}
			return null;
		}
	}
}
namespace Cacophony
{
	public class CacophonyAutoDestroy : MonoBehaviour
	{
		private static readonly HashSet<int> s_templates = new HashSet<int>();

		private float m_dieAt = -1f;

		public void MarkAsTemplate()
		{
			s_templates.Add(((Object)this).GetInstanceID());
		}

		public static void ForgetTemplate(GameObject template)
		{
			if ((Object)(object)template != (Object)null)
			{
				s_templates.Remove(((Object)template).GetInstanceID());
			}
		}

		private void Start()
		{
			if (s_templates.Contains(((Object)this).GetInstanceID()))
			{
				return;
			}
			float num = 0f;
			ZSFX component = ((Component)this).GetComponent<ZSFX>();
			if ((Object)(object)component != (Object)null && component.m_audioClips != null)
			{
				AudioClip[] audioClips = component.m_audioClips;
				foreach (AudioClip val in audioClips)
				{
					if ((Object)(object)val != (Object)null && val.length > num)
					{
						num = val.length;
					}
				}
			}
			m_dieAt = Time.time + num + 0.5f;
		}

		private void Update()
		{
			if (!(m_dieAt < 0f) && !(Time.time < m_dieAt))
			{
				Object.Destroy((Object)(object)((Component)this).gameObject);
			}
		}
	}
	[Serializable]
	public class CacophonyEntry
	{
		public string Prefab = "";

		public bool Enabled = true;

		public List<string> IdleSounds = new List<string>();

		public List<string> DeathSounds = new List<string>();

		public float Volume = 1f;

		public float MinDistance = 8f;

		public float MaxDistance = 45f;

		public float PitchJitter = 0.06f;

		public int MaxConcurrent = 1;

		public bool ReplaceVanillaIdle = true;

		public float IdleChance = -1f;

		public float IdleInterval = -1f;

		[JsonIgnore]
		public bool HasIdle
		{
			get
			{
				if (IdleSounds != null)
				{
					return IdleSounds.Count > 0;
				}
				return false;
			}
		}

		[JsonIgnore]
		public bool HasDeath
		{
			get
			{
				if (DeathSounds != null)
				{
					return DeathSounds.Count > 0;
				}
				return false;
			}
		}

		[JsonIgnore]
		public bool HasAny
		{
			get
			{
				if (!HasIdle)
				{
					return HasDeath;
				}
				return true;
			}
		}

		public void Normalize()
		{
			Prefab = (Prefab ?? "").Trim();
			IdleSounds = Clean(IdleSounds);
			DeathSounds = Clean(DeathSounds);
			Volume = Mathf01(Volume, 1f);
			MinDistance = Mathf01(MinDistance, 8f);
			MaxDistance = Mathf01(MaxDistance, 45f);
			PitchJitter = Mathf01(PitchJitter, 0.06f);
			MaxConcurrent = Math.Max(1, MaxConcurrent);
		}

		private static List<string> Clean(List<string> list)
		{
			List<string> list2 = new List<string>();
			if (list == null)
			{
				return list2;
			}
			HashSet<string> hashSet = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
			foreach (string item2 in list)
			{
				if (!string.IsNullOrWhiteSpace(item2))
				{
					string item = item2.Trim();
					if (hashSet.Add(item))
					{
						list2.Add(item);
					}
				}
			}
			return list2;
		}

		private static float Mathf01(float value, float fallback)
		{
			if (float.IsNaN(value) || float.IsInfinity(value))
			{
				return fallback;
			}
			if (!(value < 0f))
			{
				return value;
			}
			return 0f;
		}
	}
	[Serializable]
	public class CacophonyConfig
	{
		public bool Enabled = true;

		public float GlobalVolume = 1f;

		public List<CacophonyEntry> Entries = new List<CacophonyEntry>();

		public static readonly string[] Extensions = new string[2] { ".wav", ".mp3" };

		public static string PluginDir
		{
			get
			{
				string location = Assembly.GetExecutingAssembly().Location;
				if (!string.IsNullOrEmpty(location))
				{
					return Path.GetDirectoryName(location);
				}
				return ".";
			}
		}

		public static string FilePath => Path.Combine(PluginDir, "cacophony_config.json");

		public static string SoundsDir => Path.Combine(PluginDir, "sounds");

		public static CacophonyConfig Load()
		{
			CacophonyConfig cacophonyConfig;
			try
			{
				cacophonyConfig = ((!File.Exists(FilePath)) ? new CacophonyConfig() : (JsonConvert.DeserializeObject<CacophonyConfig>(File.ReadAllText(FilePath)) ?? new CacophonyConfig()));
			}
			catch (Exception ex)
			{
				Logger.LogWarning((object)("Cacophony: could not read the config, starting from defaults: " + ex.Message));
				cacophonyConfig = new CacophonyConfig();
			}
			cacophonyConfig.Normalize();
			return cacophonyConfig;
		}

		public void Save()
		{
			Normalize();
			string contents = JsonConvert.SerializeObject((object)this, (Formatting)1);
			File.WriteAllText(FilePath, contents);
		}

		public void Normalize()
		{
			Entries = Entries ?? new List<CacophonyEntry>();
			Dictionary<string, CacophonyEntry> dictionary = new Dictionary<string, CacophonyEntry>(StringComparer.OrdinalIgnoreCase);
			List<CacophonyEntry> list = new List<CacophonyEntry>();
			foreach (CacophonyEntry entry in Entries)
			{
				if (entry != null)
				{
					entry.Normalize();
					if (entry.Prefab.Length != 0 && !dictionary.ContainsKey(entry.Prefab))
					{
						dictionary[entry.Prefab] = entry;
						list.Add(entry);
					}
				}
			}
			Entries = list;
			if (float.IsNaN(GlobalVolume) || float.IsInfinity(GlobalVolume) || GlobalVolume < 0f)
			{
				GlobalVolume = 1f;
			}
		}

		public CacophonyEntry Find(string prefab)
		{
			foreach (CacophonyEntry entry in Entries)
			{
				if (string.Equals(entry.Prefab, prefab, StringComparison.OrdinalIgnoreCase))
				{
					return entry;
				}
			}
			return null;
		}

		public CacophonyEntry GetOrAdd(string prefab)
		{
			CacophonyEntry cacophonyEntry = Find(prefab);
			if (cacophonyEntry == null)
			{
				cacophonyEntry = new CacophonyEntry
				{
					Prefab = prefab
				};
				Entries.Add(cacophonyEntry);
			}
			return cacophonyEntry;
		}

		public List<string> ReferencedFiles()
		{
			List<string> list = new List<string>();
			HashSet<string> seen = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
			foreach (CacophonyEntry entry in Entries)
			{
				if (entry != null)
				{
					Collect(entry.IdleSounds, list, seen);
					Collect(entry.DeathSounds, list, seen);
				}
			}
			return list;
		}

		private static void Collect(List<string> source, List<string> into, HashSet<string> seen)
		{
			if (source == null)
			{
				return;
			}
			foreach (string item in source)
			{
				if (!string.IsNullOrWhiteSpace(item) && seen.Add(item))
				{
					into.Add(item);
				}
			}
		}

		public static List<string> AvailableFiles()
		{
			List<string> list = new List<string>();
			try
			{
				if (!Directory.Exists(SoundsDir))
				{
					return list;
				}
				string[] files = Directory.GetFiles(SoundsDir);
				foreach (string path in files)
				{
					string value = Path.GetExtension(path).ToLowerInvariant();
					if (Array.IndexOf(Extensions, value) >= 0)
					{
						list.Add(Path.GetFileName(path));
					}
				}
			}
			catch (Exception ex)
			{
				Logger.LogWarning((object)("Cacophony: could not list the sounds folder: " + ex.Message));
			}
			list.Sort(StringComparer.OrdinalIgnoreCase);
			return list;
		}
	}
	public class CreatureInfo
	{
		public string Name;

		public bool HasAI;

		public bool IsBoss;

		public string Faction;
	}
	public static class PrefabIndex
	{
		private static List<CreatureInfo> s_creatures;

		private static Dictionary<string, GameObject> s_byName;

		private static int s_prefabsSeen = -1;

		public static void Invalidate()
		{
			s_creatures = null;
			s_byName = null;
			s_prefabsSeen = -1;
		}

		public static List<CreatureInfo> Creatures()
		{
			//IL_00fd: Unknown result type (might be due to invalid IL or missing references)
			//IL_0102: Unknown result type (might be due to invalid IL or missing references)
			ZNetScene instance = ZNetScene.instance;
			if ((Object)(object)instance == (Object)null || instance.m_prefabs == null)
			{
				s_creatures = new List<CreatureInfo>();
				s_byName = new Dictionary<string, GameObject>(StringComparer.OrdinalIgnoreCase);
				s_prefabsSeen = -1;
				return s_creatures;
			}
			if (s_creatures != null && s_prefabsSeen == instance.m_prefabs.Count)
			{
				return s_creatures;
			}
			List<CreatureInfo> list = new List<CreatureInfo>();
			Dictionary<string, GameObject> dictionary = new Dictionary<string, GameObject>(StringComparer.OrdinalIgnoreCase);
			foreach (GameObject prefab in instance.m_prefabs)
			{
				if ((Object)(object)prefab == (Object)null)
				{
					continue;
				}
				string name = ((Object)prefab).name;
				if (!string.IsNullOrEmpty(name) && !dictionary.ContainsKey(name))
				{
					Character component = prefab.GetComponent<Character>();
					if (!((Object)(object)component == (Object)null))
					{
						dictionary[name] = prefab;
						list.Add(new CreatureInfo
						{
							Name = name,
							HasAI = ((Object)(object)component.GetBaseAI() != (Object)null),
							IsBoss = component.m_boss,
							Faction = ((object)component.GetFaction()/*cast due to .constrained prefix*/).ToString()
						});
					}
				}
			}
			list.Sort((CreatureInfo a, CreatureInfo b) => string.Compare(a.Name, b.Name, StringComparison.OrdinalIgnoreCase));
			s_creatures = list;
			s_byName = dictionary;
			s_prefabsSeen = instance.m_prefabs.Count;
			return s_creatures;
		}

		public static GameObject Find(string prefabName)
		{
			if (string.IsNullOrEmpty(prefabName))
			{
				return null;
			}
			Creatures();
			if (!s_byName.TryGetValue(prefabName, out var value))
			{
				return null;
			}
			return value;
		}
	}
	public static class CacophonyInstaller
	{
		private class Installed
		{
			public EffectList List;

			public EffectData[] Original;

			public GameObject Template;

			public BaseAI AI;

			public float OriginalChance;

			public float OriginalInterval;
		}

		private const string TemplateName = "CacophonyAudio";

		private static readonly List<Installed> s_installed = new List<Installed>();

		private static float s_globalVolume = 1f;

		public static int InstalledCount => s_installed.Count;

		public static void Undo()
		{
			foreach (Installed item in s_installed)
			{
				if (item.List != null)
				{
					item.List.m_effectPrefabs = item.Original;
				}
				if ((Object)(object)item.AI != (Object)null)
				{
					item.AI.m_idleSoundChance = item.OriginalChance;
					item.AI.m_idleSoundInterval = item.OriginalInterval;
				}
				if ((Object)(object)item.Template != (Object)null)
				{
					CacophonyAutoDestroy.ForgetTemplate(item.Template);
					Object.Destroy((Object)(object)item.Template);
				}
			}
			s_installed.Clear();
		}

		public static void Apply(CacophonyConfig config, ClipLibrary library, List<string> report)
		{
			Undo();
			if (config == null)
			{
				return;
			}
			s_globalVolume = config.GlobalVolume;
			if (!config.Enabled)
			{
				report.Add("disabled in the config");
				return;
			}
			ZNetScene instance = ZNetScene.instance;
			if ((Object)(object)instance == (Object)null || instance.m_prefabs == null || instance.m_prefabs.Count == 0)
			{
				report.Add("waiting for a world to load");
				return;
			}
			int num = 0;
			int num2 = 0;
			foreach (CacophonyEntry entry in config.Entries)
			{
				if (entry == null || !entry.Enabled || !entry.HasAny)
				{
					continue;
				}
				GameObject val = PrefabIndex.Find(entry.Prefab);
				if ((Object)(object)val == (Object)null)
				{
					report.Add("no prefab named '" + entry.Prefab + "'");
					num2++;
					continue;
				}
				Character component = val.GetComponent<Character>();
				if ((Object)(object)component == (Object)null)
				{
					report.Add(entry.Prefab + " has no Character component");
					num2++;
					continue;
				}
				BaseAI baseAI = component.GetBaseAI();
				if (entry.HasIdle)
				{
					if ((Object)(object)baseAI == (Object)null)
					{
						report.Add(entry.Prefab + " has no BaseAI, so it has no idle sound");
					}
					else
					{
						InstallIdle(val, baseAI, entry, library, report);
						num++;
					}
				}
				if (entry.HasDeath)
				{
					InstallDeath(val, component, entry, library, report);
					num++;
				}
			}
			report.Add(num + " effect(s) installed, " + num2 + " entr(y/ies) skipped");
			Logger.LogInfo((object)("Cacophony: " + report[report.Count - 1]));
		}

		private static void InstallIdle(GameObject prefab, BaseAI ai, CacophonyEntry entry, ClipLibrary library, List<string> report)
		{
			//IL_001e: Unknown result type (might be due to invalid IL or missing references)
			AudioClip[] array = Resolve(entry.IdleSounds, library, report);
			if (array.Length != 0)
			{
				EffectList val = (EffectList)(((object)ai.m_idleSound) ?? ((object)new EffectList()));
				Installed installed = new Installed
				{
					List = val,
					Original = val.m_effectPrefabs,
					AI = ai,
					OriginalChance = ai.m_idleSoundChance,
					OriginalInterval = ai.m_idleSoundInterval
				};
				if (entry.ReplaceVanillaIdle)
				{
					val.m_effectPrefabs = (EffectData[])(object)new EffectData[0];
				}
				installed.Template = BuildTemplate(prefab, entry, array);
				Append(val, installed.Template);
				s_installed.Add(installed);
				ai.m_idleSound = val;
				if (entry.IdleChance >= 0f)
				{
					ai.m_idleSoundChance = entry.IdleChance;
				}
				if (entry.IdleInterval > 0f)
				{
					ai.m_idleSoundInterval = entry.IdleInterval;
				}
				Logger.LogInfo((object)("Cacophony: " + entry.Prefab + " idle <- " + Describe(array)));
			}
		}

		private static void InstallDeath(GameObject prefab, Character character, CacophonyEntry entry, ClipLibrary library, List<string> report)
		{
			//IL_001e: Unknown result type (might be due to invalid IL or missing references)
			AudioClip[] array = Resolve(entry.DeathSounds, library, report);
			if (array.Length != 0)
			{
				EffectList val = (EffectList)(((object)character.m_deathEffects) ?? ((object)new EffectList()));
				Installed installed = new Installed
				{
					List = val,
					Original = val.m_effectPrefabs
				};
				installed.Template = BuildTemplate(prefab, entry, array);
				Append(val, installed.Template);
				s_installed.Add(installed);
				character.m_deathEffects = val;
				Logger.LogInfo((object)("Cacophony: " + entry.Prefab + " death <- " + Describe(array)));
			}
		}

		private static AudioClip[] Resolve(List<string> names, ClipLibrary library, List<string> report)
		{
			List<AudioClip> list = new List<AudioClip>();
			if (names == null)
			{
				return (AudioClip[])(object)new AudioClip[0];
			}
			foreach (string name in names)
			{
				AudioClip val = library.Get(name);
				if ((Object)(object)val == (Object)null)
				{
					report.Add("no usable clip for '" + name + "'");
				}
				else
				{
					list.Add(val);
				}
			}
			return list.ToArray();
		}

		private static string Describe(AudioClip[] clips)
		{
			List<string> list = new List<string>();
			foreach (AudioClip val in clips)
			{
				list.Add(((Object)val).name);
			}
			return string.Join(", ", list.ToArray());
		}

		private static void Append(EffectList list, GameObject template)
		{
			//IL_002a: Unknown result type (might be due to invalid IL or missing references)
			//IL_002f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0036: Unknown result type (might be due to invalid IL or missing references)
			//IL_003d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0044: Unknown result type (might be due to invalid IL or missing references)
			//IL_004b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0053: Expected O, but got Unknown
			EffectData[] array = (EffectData[])(((object)list.m_effectPrefabs) ?? ((object)new EffectData[0]));
			EffectData[] array2 = (EffectData[])(object)new EffectData[array.Length + 1];
			Array.Copy(array, array2, array.Length);
			array2[array.Length] = new EffectData
			{
				m_prefab = template,
				m_enabled = true,
				m_attach = false,
				m_follow = false,
				m_variant = -1
			};
			list.m_effectPrefabs = array2;
		}

		private static GameObject BuildTemplate(GameObject prefab, CacophonyEntry entry, AudioClip[] clips)
		{
			//IL_0005: Unknown result type (might be due to invalid IL or missing references)
			//IL_000b: Expected O, but got Unknown
			//IL_0029: Unknown result type (might be due to invalid IL or missing references)
			GameObject val = new GameObject("CacophonyAudio");
			val.transform.SetParent(prefab.transform, false);
			val.transform.position = prefab.transform.position;
			((Object)val).hideFlags = (HideFlags)1;
			AudioSource obj = val.AddComponent<AudioSource>();
			obj.playOnAwake = false;
			obj.loop = false;
			obj.spatialBlend = 1f;
			obj.rolloffMode = (AudioRolloffMode)0;
			obj.minDistance = entry.MinDistance;
			obj.maxDistance = Math.Max(entry.MinDistance + 1f, entry.MaxDistance);
			obj.dopplerLevel = 0f;
			obj.spread = 60f;
			ZSFX val2 = val.AddComponent<ZSFX>();
			val2.m_audioClips = clips;
			val2.m_playOnAwake = true;
			val2.m_maxConcurrentSources = entry.MaxConcurrent;
			val2.m_ignoreConcurrencyDistance = false;
			val2.m_minPitch = Math.Max(0.01f, 1f - entry.PitchJitter);
			val2.m_maxPitch = 1f + entry.PitchJitter;
			val2.m_maxVol = (val2.m_minVol = Mathf.Clamp01(entry.Volume * s_globalVolume));
			val2.m_minDelay = 0f;
			val2.m_maxDelay = 0f;
			float num = 0f;
			foreach (AudioClip val3 in clips)
			{
				if (val3.length > num)
				{
					num = val3.length;
				}
			}
			val2.m_fadeOutOnAwake = true;
			val2.m_fadeOutDuration = 0.2f;
			val2.m_fadeOutDelay = Math.Max(0f, num - val2.m_fadeOutDuration);
			val2.GenerateHash();
			val.AddComponent<CacophonyAutoDestroy>().MarkAsTemplate();
			return val;
		}
	}
	[BepInPlugin("com.dfg.cacophony", "Cacophony", "1.0.0")]
	[BepInDependency(/*Could not decode attribute arguments.*/)]
	public class CacophonyPlugin : BaseUnityPlugin
	{
		private class Slot<T>
		{
			public T Value;

			public Exception Error;
		}

		[Serializable]
		private class StateDto
		{
			public bool Enabled = true;

			public float GlobalVolume = 1f;

			public bool Loading;

			public int Loaded;

			public int Failed;

			public int Pending;

			public string CurrentFile;

			public int Installed;

			public bool InWorld;

			public int PrefabCount;

			public string SoundDir;

			public string ConfigPath;

			public List<CacophonyEntry> Entries = new List<CacophonyEntry>();

			public List<string> Available = new List<string>();

			public List<string> Report = new List<string>();

			public List<string> Errors = new List<string>();

			public List<CreatureDto> Creatures = new List<CreatureDto>();
		}

		[Serializable]
		private class CreatureDto
		{
			public string Name;

			public bool HasAI;

			public bool IsBoss;

			public string Faction;
		}

		public const string PluginGUID = "com.dfg.cacophony";

		public const string PluginName = "Cacophony";

		public const string PluginVersion = "1.0.0";

		internal static ConfigEntry<bool> s_webGuiEnabled;

		internal static ConfigEntry<int> s_webPort;

		internal static ConfigEntry<bool> s_debug;

		private readonly ConcurrentQueue<Action> m_mainThread = new ConcurrentQueue<Action>();

		private readonly ClipLibrary m_library = new ClipLibrary();

		private readonly List<string> m_report = new List<string>();

		private CacophonyConfig m_config;

		private CacophonyWebGui m_web;

		private GameObject m_preview;

		private AudioSource m_previewSource;

		private ZNetScene m_scene;

		private bool m_needClips;

		private bool m_needApply;

		private bool m_loading;

		private float m_applyAfter;

		private void Awake()
		{
			s_webGuiEnabled = ((BaseUnityPlugin)this).Config.Bind<bool>("General", "EnableWebGui", true, "Serve the assignment editor on localhost.");
			s_webPort = ((BaseUnityPlugin)this).Config.Bind<int>("General", "WebPort", 8787, "Port for the assignment editor.");
			s_debug = ((BaseUnityPlugin)this).Config.Bind<bool>("General", "Debug", false, "Log every install and every skipped entry.");
			try
			{
				if (!Directory.Exists(CacophonyConfig.SoundsDir))
				{
					Directory.CreateDirectory(CacophonyConfig.SoundsDir);
				}
			}
			catch (Exception ex)
			{
				Logger.LogWarning((object)("Cacophony: could not create the sounds folder: " + ex.Message));
			}
			m_config = CacophonyConfig.Load();
			m_web = new CacophonyWebGui(this, s_webPort.Value);
			if (s_webGuiEnabled.Value)
			{
				m_web.Start();
			}
			m_needClips = true;
			m_needApply = true;
			Logger.LogInfo((object)("Cacophony: sounds folder is " + CacophonyConfig.SoundsDir));
			Logger.LogInfo((object)("Cacophony: config is " + CacophonyConfig.FilePath));
		}

		private void OnDestroy()
		{
			if (m_web != null)
			{
				m_web.Stop();
				m_web = null;
			}
			CacophonyInstaller.Undo();
			if ((Object)(object)m_preview != (Object)null)
			{
				Object.Destroy((Object)(object)m_preview);
				m_preview = null;
			}
		}

		private void Update()
		{
			Drain();
			ZNetScene instance = ZNetScene.instance;
			if ((Object)(object)instance != (Object)(object)m_scene)
			{
				m_scene = instance;
				PrefabIndex.Invalidate();
				CacophonyInstaller.Undo();
				if ((Object)(object)instance != (Object)null)
				{
					m_needApply = true;
					m_applyAfter = Time.unscaledTime + 1f;
					Logger.LogInfo((object)("Cacophony: world loaded, " + instance.m_prefabs.Count + " prefabs"));
				}
			}
			if (m_needClips && !m_loading)
			{
				m_needClips = false;
				StartLoad();
			}
			else if (m_needApply && !m_loading && !((Object)(object)instance == (Object)null) && instance.m_prefabs != null && instance.m_prefabs.Count != 0 && !(Time.unscaledTime < m_applyAfter))
			{
				m_needApply = false;
				ApplyNow();
			}
		}

		private void Drain()
		{
			Action result;
			while (m_mainThread.TryDequeue(out result))
			{
				try
				{
					result();
				}
				catch (Exception ex)
				{
					Logger.LogError((object)("Cacophony: " + ex));
				}
			}
		}

		private void StartLoad()
		{
			m_loading = true;
			m_report.Clear();
			List<string> list = m_config.ReferencedFiles();
			int count = CacophonyConfig.AvailableFiles().Count;
			Logger.LogInfo((object)("Cacophony: loading " + list.Count + " referenced sound(s), " + count + " file(s) in the sounds folder"));
			((MonoBehaviour)this).StartCoroutine(m_library.LoadAll(list, OnClipsReady));
		}

		private void OnClipsReady()
		{
			m_loading = false;
			if (m_library.Failed > 0)
			{
				Logger.LogWarning((object)("Cacophony: " + m_library.Failed + " sound(s) could not be loaded. Last problem: " + (m_library.LastError ?? "unknown")));
			}
			if ((Object)(object)ZNetScene.instance != (Object)null)
			{
				m_needApply = true;
				m_applyAfter = Time.unscaledTime;
			}
		}

		private void ApplyNow()
		{
			List<string> list = new List<string>();
			CacophonyInstaller.Apply(m_config, m_library, list);
			m_report.Clear();
			m_report.AddRange(list);
			if (!s_debug.Value)
			{
				return;
			}
			foreach (string item in list)
			{
				Logger.LogInfo((object)("Cacophony: " + item));
			}
		}

		public void RequestReload()
		{
			CacophonyInstaller.Undo();
			m_needClips = true;
		}

		public void RequestApply()
		{
			m_needApply = true;
		}

		public void Preview(string fileName)
		{
			//IL_002b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0035: Expected O, but got Unknown
			AudioClip val = m_library.Get(fileName);
			if (!((Object)(object)val == (Object)null))
			{
				if ((Object)(object)m_preview == (Object)null)
				{
					m_preview = new GameObject("CacophonyPreview");
					Object.DontDestroyOnLoad((Object)(object)m_preview);
					m_previewSource = m_preview.AddComponent<AudioSource>();
					m_previewSource.playOnAwake = false;
					m_previewSource.spatialBlend = 0f;
				}
				m_previewSource.clip = val;
				m_previewSource.pitch = 1f;
				m_previewSource.Play();
			}
		}

		public void Enqueue(Action action)
		{
			if (action != null)
			{
				m_mainThread.Enqueue(action);
			}
		}

		public T Ask<T>(Func<T> work, T fallback)
		{
			if (work == null)
			{
				return fallback;
			}
			ManualResetEventSlim done = new ManualResetEventSlim(initialState: false);
			Slot<T> slot = new Slot<T>();
			m_mainThread.Enqueue(delegate
			{
				try
				{
					slot.Value = work();
				}
				catch (Exception error)
				{
					slot.Error = error;
				}
				finally
				{
					done.Set();
				}
			});
			if (!done.Wait(TimeSpan.FromSeconds(3.0)))
			{
				Logger.LogWarning((object)"Cacophony: the web GUI gave up waiting on the main thread");
				return fallback;
			}
			if (slot.Error != null)
			{
				Logger.LogWarning((object)("Cacophony: web GUI request failed: " + slot.Error.Message));
				return fallback;
			}
			return slot.Value;
		}

		public string BuildState()
		{
			ZNetScene instance = ZNetScene.instance;
			StateDto stateDto = new StateDto
			{
				Enabled = m_config.Enabled,
				GlobalVolume = m_config.GlobalVolume,
				Loading = m_loading,
				Loaded = m_library.Loaded,
				Failed = m_library.Failed,
				Pending = m_library.Pending,
				CurrentFile = m_library.CurrentFile,
				Installed = CacophonyInstaller.InstalledCount,
				InWorld = ((Object)(object)instance != (Object)null),
				PrefabCount = (((Object)(object)instance != (Object)null && instance.m_prefabs != null) ? instance.m_prefabs.Count : 0),
				SoundDir = CacophonyConfig.SoundsDir,
				ConfigPath = CacophonyConfig.FilePath,
				Entries = m_config.Entries,
				Available = CacophonyConfig.AvailableFiles(),
				Report = new List<string>(m_report)
			};
			foreach (string item in (stateDto.Entries.Count > 0) ? m_config.ReferencedFiles() : new List<string>())
			{
				string text = m_library.ErrorFor(item);
				if (text != null)
				{
					stateDto.Errors.Add(item + ": " + text);
				}
			}
			foreach (CreatureInfo item2 in PrefabIndex.Creatures())
			{
				stateDto.Creatures.Add(new CreatureDto
				{
					Name = item2.Name,
					HasAI = item2.HasAI,
					IsBoss = item2.IsBoss,
					Faction = item2.Faction
				});
			}
			return JsonConvert.SerializeObject((object)stateDto);
		}

		public void ApplyConfig(string json)
		{
			StateDto stateDto = JsonConvert.DeserializeObject<StateDto>(json);
			if (stateDto != null)
			{
				m_config.Enabled = stateDto.Enabled;
				m_config.GlobalVolume = stateDto.GlobalVolume;
				m_config.Entries = stateDto.Entries ?? new List<CacophonyEntry>();
				m_config.Normalize();
				m_config.Save();
				CacophonyInstaller.Undo();
				m_needClips = true;
				m_needApply = true;
			}
		}

		public void SaveConfig()
		{
			m_config.Normalize();
			m_config.Save();
		}
	}
	public class CacophonyWebGui
	{
		private readonly CacophonyPlugin m_plugin;

		private readonly int m_port;

		private HttpListener m_listener;

		private Thread m_thread;

		private volatile bool m_running;

		public bool IsRunning => m_running;

		public CacophonyWebGui(CacophonyPlugin plugin, int port)
		{
			m_plugin = plugin;
			m_port = port;
		}

		public void Start()
		{
			try
			{
				m_listener = new HttpListener();
				m_listener.Prefixes.Add("http://127.0.0.1:" + m_port + "/");
				m_listener.Start();
				m_running = true;
				m_thread = new Thread(Loop)
				{
					IsBackground = true
				};
				m_thread.Start();
				Logger.LogInfo((object)("Cacophony: web editor at http://127.0.0.1:" + m_port + "/"));
			}
			catch (Exception ex)
			{
				m_running = false;
				Logger.LogWarning((object)("Cacophony: could not start the web editor: " + ex.Message));
			}
		}

		public void Stop()
		{
			m_running = false;
			try
			{
				if (m_listener != null)
				{
					m_listener.Stop();
					m_listener.Close();
				}
			}
			catch
			{
			}
			m_listener = null;
		}

		private void Loop()
		{
			while (m_running && m_listener != null && m_listener.IsListening)
			{
				try
				{
					HttpListenerContext context = m_listener.GetContext();
					Handle(context);
				}
				catch
				{
				}
			}
		}

		private void Handle(HttpListenerContext context)
		{
			try
			{
				switch (context.Request.Url.AbsolutePath)
				{
				case "/":
				case "/index.html":
					Write(context, "text/html; charset=utf-8", Page.Html);
					break;
				case "/state":
					Write(context, "application/json; charset=utf-8", m_plugin.Ask(() => m_plugin.BuildState(), "{}"));
					break;
				case "/apply":
				{
					string body = ReadBody(context);
					string text2 = m_plugin.Ask(delegate
					{
						m_plugin.ApplyConfig(body);
						return (string)null;
					}, "the game did not respond in time");
					Write(context, "text/plain; charset=utf-8", text2 ?? "ok");
					break;
				}
				case "/save":
				{
					string text3 = m_plugin.Ask(delegate
					{
						m_plugin.SaveConfig();
						return (string)null;
					}, "the game did not respond in time");
					Write(context, "text/plain; charset=utf-8", text3 ?? "saved");
					break;
				}
				case "/reload":
					m_plugin.Enqueue(delegate
					{
						m_plugin.RequestReload();
					});
					Write(context, "text/plain; charset=utf-8", "reloading");
					break;
				case "/preview":
				{
					string file = context.Request.QueryString["file"];
					string text = m_plugin.Ask(delegate
					{
						m_plugin.Preview(file);
						return (string)null;
					}, "the game did not respond in time");
					Write(context, "text/plain; charset=utf-8", text ?? ("playing " + file));
					break;
				}
				default:
					context.Response.StatusCode = 404;
					context.Response.Close();
					break;
				}
			}
			catch (Exception ex)
			{
				try
				{
					context.Response.StatusCode = 500;
					Write(context, "text/plain; charset=utf-8", ex.Message);
				}
				catch
				{
					try
					{
						context.Response.Close();
					}
					catch
					{
					}
				}
			}
		}

		private static string ReadBody(HttpListenerContext context)
		{
			using StreamReader streamReader = new StreamReader(context.Request.InputStream, context.Request.ContentEncoding);
			return streamReader.ReadToEnd();
		}

		private static void Write(HttpListenerContext context, string contentType, string body)
		{
			byte[] bytes = Encoding.UTF8.GetBytes(body ?? "");
			context.Response.ContentType = contentType;
			context.Response.ContentEncoding = Encoding.UTF8;
			context.Response.ContentLength64 = bytes.Length;
			context.Response.OutputStream.Write(bytes, 0, bytes.Length);
			context.Response.OutputStream.Close();
		}
	}
	public class ClipLibrary
	{
		private readonly Dictionary<string, AudioClip> m_clips = new Dictionary<string, AudioClip>(StringComparer.OrdinalIgnoreCase);

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

		public bool Loading { get; private set; }

		public int Loaded { get; private set; }

		public int Failed { get; private set; }

		public string CurrentFile { get; private set; }

		public string LastError { get; private set; }

		public int Pending { get; private set; }

		public AudioClip Get(string fileName)
		{
			if (string.IsNullOrEmpty(fileName))
			{
				return null;
			}
			if (!m_clips.TryGetValue(fileName, out var value))
			{
				return null;
			}
			return value;
		}

		public bool IsMissing(string fileName)
		{
			if (!string.IsNullOrEmpty(fileName))
			{
				return !m_clips.ContainsKey(fileName);
			}
			return false;
		}

		public string ErrorFor(string fileName)
		{
			if (!m_errors.TryGetValue(fileName ?? "", out var value))
			{
				return null;
			}
			return value;
		}

		public void Reset()
		{
			foreach (AudioClip value in m_clips.Values)
			{
				if ((Object)(object)value != (Object)null)
				{
					Object.Destroy((Object)(object)value);
				}
			}
			m_clips.Clear();
			m_errors.Clear();
			Loaded = 0;
			Failed = 0;
			Pending = 0;
			CurrentFile = null;
			LastError = null;
		}

		public IEnumerator LoadAll(IEnumerable<string> fileNames, Action onComplete)
		{
			Reset();
			Loading = true;
			List<string> queue = new List<string>();
			foreach (string fileName in fileNames)
			{
				if (!string.IsNullOrWhiteSpace(fileName))
				{
					queue.Add(fileName.Trim());
				}
			}
			Pending = queue.Count;
			try
			{
				for (int i = 0; i < queue.Count; i++)
				{
					string name = (CurrentFile = queue[i]);
					if (m_clips.ContainsKey(name))
					{
						Loaded++;
						continue;
					}
					AudioClip val = null;
					string error = null;
					string path = Path.Combine(CacophonyConfig.SoundsDir, name);
					if (!File.Exists(path))
					{
						error = "not in the sounds folder";
					}
					else
					{
						string text2 = Path.GetExtension(name).ToLowerInvariant();
						if (text2 == ".wav")
						{
							try
							{
								val = LoadWav(name, path);
								if ((Object)(object)val == (Object)null)
								{
									error = "unsupported wav";
								}
							}
							catch (Exception ex)
							{
								error = ex.Message;
							}
						}
						else if (text2 == ".mp3")
						{
							AudioClip decoded = null;
							yield return LoadMp3(name, path, delegate(AudioClip result)
							{
								decoded = result;
							});
							val = decoded;
							if ((Object)(object)val == (Object)null)
							{
								error = "mp3 decode failed - try the file as wav instead";
							}
						}
						else
						{
							error = "unsupported type " + text2;
						}
					}
					if ((Object)(object)val != (Object)null)
					{
						Object.DontDestroyOnLoad((Object)(object)val);
						m_clips[name] = val;
						Loaded++;
					}
					else
					{
						m_errors[name] = error ?? "unknown error";
						LastError = name + ": " + m_errors[name];
						Failed++;
					}
				}
			}
			finally
			{
				ClipLibrary clipLibrary = this;
				clipLibrary.Loading = false;
				clipLibrary.CurrentFile = null;
				clipLibrary.Pending = 0;
			}
			onComplete?.Invoke();
		}

		private static AudioClip LoadWav(string name, string path)
		{
			byte[] array = File.ReadAllBytes(path);
			if (array.Length < 44 || !Tag(array, 0, "RIFF") || !Tag(array, 8, "WAVE"))
			{
				return null;
			}
			int num = 0;
			int num2 = 0;
			int num3 = 0;
			int num4 = 0;
			byte[] array2 = null;
			int num5 = 12;
			while (num5 + 8 <= array.Length)
			{
				string text = Encoding.ASCII.GetString(array, num5, 4);
				int num6 = BitConverter.ToInt32(array, num5 + 4);
				int num7 = num5 + 8;
				if (num6 < 0 || num7 > array.Length)
				{
					break;
				}
				if (num7 + num6 > array.Length)
				{
					num6 = array.Length - num7;
				}
				if (text == "fmt " && num7 + 16 <= array.Length)
				{
					num = BitConverter.ToUInt16(array, num7);
					num2 = BitConverter.ToUInt16(array, num7 + 2);
					num3 = BitConverter.ToInt32(array, num7 + 4);
					num4 = BitConverter.ToUInt16(array, num7 + 14);
					if (num == 65534 && num7 + 26 <= array.Length)
					{
						num = BitConverter.ToUInt16(array, num7 + 24);
					}
				}
				else if (text == "data")
				{
					array2 = new byte[num6];
					Array.Copy(array, num7, array2, 0, num6);
				}
				num5 = num7 + num6 + (num6 & 1);
			}
			if (array2 == null || num2 < 1 || num2 > 8 || num3 < 1)
			{
				return null;
			}
			int num8 = num4 / 8;
			if (num8 < 1)
			{
				return null;
			}
			int num9 = num8 * num2;
			int num10 = array2.Length / num9;
			if (num10 < 1)
			{
				return null;
			}
			float[] array3 = new float[num10 * num2];
			for (int i = 0; i < array3.Length; i++)
			{
				int num11 = i * num8;
				if (num11 + num8 > array2.Length)
				{
					break;
				}
				array3[i] = Decode(array2, num11, num, num4);
			}
			AudioClip obj = AudioClip.Create(name, num10, num2, num3, false);
			obj.SetData(array3, 0);
			return obj;
		}

		private static float Decode(byte[] data, int offset, int format, int bits)
		{
			if (format == 3)
			{
				return BitConverter.ToSingle(data, offset);
			}
			switch (bits)
			{
			case 8:
				return (float)(data[offset] - 128) / 128f;
			case 16:
				return (float)BitConverter.ToInt16(data, offset) / 32768f;
			case 24:
			{
				int num = data[offset] | (data[offset + 1] << 8) | (data[offset + 2] << 16);
				if ((num & 0x800000) != 0)
				{
					num |= -16777216;
				}
				return (float)num / 8388608f;
			}
			case 32:
				return (float)BitConverter.ToInt32(data, offset) / 2.1474836E+09f;
			default:
				throw new NotSupportedException(bits + "-bit wav");
			}
		}

		private static bool Tag(byte[] bytes, int offset, string tag)
		{
			if (offset + 4 > bytes.Length)
			{
				return false;
			}
			if (bytes[offset] == tag[0] && bytes[offset + 1] == tag[1] && bytes[offset + 2] == tag[2])
			{
				return bytes[offset + 3] == tag[3];
			}
			return false;
		}

		private static IEnumerator LoadMp3(string name, string path, Action<AudioClip> done)
		{
			string text = "file:///" + path.Replace('\\', '/').TrimStart('/');
			UnityWebRequest request = UnityWebRequestMultimedia.GetAudioClip(text, (AudioType)13);
			yield return request.SendWebRequest();
			if ((int)request.result != 1)
			{
				Logger.LogWarning((object)("Cacophony: mp3 '" + name + "' failed: " + request.error));
				request.Dispose();
				yield break;
			}
			AudioClip clip = DownloadHandlerAudioClip.GetContent(request);
			if ((Object)(object)clip == (Object)null)
			{
				request.Dispose();
				yield break;
			}
			((Object)clip).name = name;
			done(clip);
			yield return null;
			bool num = (Object)(object)clip != (Object)null;
			request.Dispose();
			if (!num)
			{
				Logger.LogWarning((object)("Cacophony: '" + name + "' did not survive disposing its loader. Convert it to wav."));
			}
		}
	}
	public static class Page
	{
		private const string ResourceName = "Cacophony.Page.html";

		private static string s_html;

		public static string Html
		{
			get
			{
				if (s_html != null)
				{
					return s_html;
				}
				try
				{
					using (Stream stream = Assembly.GetExecutingAssembly().GetManifestResourceStream("Cacophony.Page.html"))
					{
						if (stream == null)
						{
							throw new InvalidOperationException("missing embedded resource Cacophony.Page.html");
						}
						using StreamReader streamReader = new StreamReader(stream);
						s_html = streamReader.ReadToEnd();
					}
					s_html = s_html.Replace("{{NAV_STYLE}}", "nav.top{position:sticky;top:0;z-index:20;font-family:Segoe UI,Arial,sans-serif;background:#0d1219;border-bottom:1px solid #2b3b4c;padding:6px 14px;display:flex;align-items:center;gap:6px;flex-wrap:wrap}\nnav.top .brand{color:#7f8fa0;font-size:11px;text-transform:uppercase;letter-spacing:.6px;margin-right:8px}\nnav.top a{color:#9fc3dd;text-decoration:none;font-size:13px;padding:4px 9px;border-radius:5px}\nnav.top a:hover{background:#1a2530}\nnav.top a.here{background:#3a2b4c;border:1px solid #55406b;color:#efe0ff}\n").Replace("{{NAV}}", WebNav.Html("cacophony"));
				}
				catch (Exception ex)
				{
					Logger.LogError((object)("Cacophony: the editor page could not be read: " + ex.Message));
					s_html = "<!DOCTYPE html><html><body style='background:#14171b;color:#d6d9dd;font:13px Consolas,monospace;padding:20px'>The editor page could not be loaded. See the BepInEx log.</body></html>";
				}
				return s_html;
			}
		}
	}
}