Decompiled source of HTFCustomRadio v1.3.0

plugins/HTFCustomRadio.dll

Decompiled an hour 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 BepInEx;
using BepInEx.Configuration;
using BepInEx.Logging;
using FishNet;
using FishNet.Broadcast;
using FishNet.Managing.Client;
using FishNet.Managing.Server;
using FishNet.Serializing;
using FishNet.Transporting;
using HTF.Shared;
using HarmonyLib;
using Microsoft.CodeAnalysis;
using UnityEngine;
using UnityEngine.InputSystem;
using UnityEngine.InputSystem.Controls;
using UnityEngine.Networking;

[assembly: CompilationRelaxations(8)]
[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
[assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)]
[assembly: TargetFramework(".NETFramework,Version=v4.7.2", FrameworkDisplayName = ".NET Framework 4.7.2")]
[assembly: AssemblyCompany("HTFCustomRadio")]
[assembly: AssemblyConfiguration("Release")]
[assembly: AssemblyFileVersion("1.3.0.0")]
[assembly: AssemblyInformationalVersion("1.3.0")]
[assembly: AssemblyProduct("HTFCustomRadio")]
[assembly: AssemblyTitle("HTFCustomRadio")]
[assembly: AssemblyVersion("1.3.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 HTF.Shared
{
	internal static class AudioLoading
	{
		internal static readonly string[] SupportedExtensions = new string[3] { ".ogg", ".wav", ".mp3" };

		internal static bool IsSupported(string path)
		{
			string ext = Path.GetExtension(path);
			return Array.Exists(SupportedExtensions, (string e) => string.Equals(e, ext, StringComparison.OrdinalIgnoreCase));
		}

		internal static AudioType TypeOf(string path)
		{
			return (AudioType)(Path.GetExtension(path)?.ToLowerInvariant() switch
			{
				".ogg" => 14, 
				".wav" => 20, 
				".mp3" => 13, 
				_ => 0, 
			});
		}

		internal static IEnumerator Load(string source, bool stream, Action<AudioClip> onLoaded, Action<string> onError)
		{
			bool isRemote = source.StartsWith("http://", StringComparison.OrdinalIgnoreCase) || source.StartsWith("https://", StringComparison.OrdinalIgnoreCase);
			string text;
			if (isRemote)
			{
				text = source;
			}
			else
			{
				if (!File.Exists(source))
				{
					onError?.Invoke("No existe el archivo: " + source);
					yield break;
				}
				text = new Uri(Path.GetFullPath(source)).AbsoluteUri;
			}
			AudioType val = TypeOf(source);
			if ((int)val == 0)
			{
				onError?.Invoke("Formato no soportado: " + source);
				yield break;
			}
			UnityWebRequest request = UnityWebRequestMultimedia.GetAudioClip(text, val);
			try
			{
				DownloadHandler downloadHandler = request.downloadHandler;
				DownloadHandlerAudioClip val2 = (DownloadHandlerAudioClip)(object)((downloadHandler is DownloadHandlerAudioClip) ? downloadHandler : null);
				if (val2 != null)
				{
					val2.streamAudio = stream || isRemote;
					val2.compressed = false;
				}
				yield return request.SendWebRequest();
				if ((int)request.result != 1)
				{
					onError?.Invoke(request.error + " (" + source + ")");
					yield break;
				}
				AudioClip content;
				try
				{
					content = DownloadHandlerAudioClip.GetContent(request);
				}
				catch (Exception ex)
				{
					onError?.Invoke(ex.Message + " (" + source + ")");
					yield break;
				}
				if ((Object)(object)content == (Object)null || (int)content.loadState == 3)
				{
					onError?.Invoke("Unity no pudo descodificar " + Path.GetFileName(source) + ". Si es un MP3 con etiquetas raras, reexpórtalo a OGG.");
					yield break;
				}
				((Object)content).name = (isRemote ? source : Path.GetFileNameWithoutExtension(source));
				onLoaded?.Invoke(content);
			}
			finally
			{
				((IDisposable)request)?.Dispose();
			}
		}
	}
}
namespace HTFCustomRadio
{
	internal class Station
	{
		public string Name;

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

		public bool IsStream;

		public AudioSource Source;

		public float Frequency;

		public int Index;

		public bool Loading;

		public float LastStart = -99f;

		public float ExpectedEnd;

		public int RecentAdvances;

		public float WindowStart;

		public bool Paused;

		public AudioClip OwnedClip;

		public string CurrentTrack
		{
			get
			{
				if (Tracks.Count != 0)
				{
					return Path.GetFileNameWithoutExtension(Tracks[Index % Tracks.Count]);
				}
				return "(vacía)";
			}
		}
	}
	[BepInPlugin("com.hatsuneexo.htfcustomradio", "HTF Custom Radio", "1.3.0")]
	[BepInProcess("How to Fish.exe")]
	public class Plugin : BaseUnityPlugin
	{
		public const string Guid = "com.hatsuneexo.htfcustomradio";

		internal static ManualLogSource Log;

		internal static Plugin Instance;

		internal static ConfigEntry<bool> Shuffle;

		internal static ConfigEntry<int> KeepOriginalStations;

		internal static ConfigEntry<string> StreamStations;

		internal static ConfigEntry<bool> AnnounceTrack;

		internal static ConfigEntry<string> MusicFolders;

		internal static ConfigEntry<bool> SplitSubfolders;

		internal static ConfigEntry<bool> UseMyMusicFolder;

		internal static ConfigEntry<Key> ReloadKey;

		internal static ConfigEntry<Key> SkipKey;

		private readonly List<Station> _stations = new List<Station>();

		private readonly List<Station> _live = new List<Station>();

		private object _lastRadio;

		private static readonly Random Rng = new Random();

		private const float MinSecondsBetweenTracks = 1f;

		private const int MaxAdvancesPerMinute = 30;

		private const float ResyncToleranceSeconds = 2.5f;

		private const float BroadcastInterval = 8f;

		private float _nextBroadcast;

		private readonly HashSet<string> _missingTracks = new HashSet<string>(StringComparer.OrdinalIgnoreCase);

		private void Awake()
		{
			//IL_0053: Unknown result type (might be due to invalid IL or missing references)
			//IL_005d: Expected O, but got Unknown
			//IL_0198: Unknown result type (might be due to invalid IL or missing references)
			//IL_019e: Expected O, but got Unknown
			Log = ((BaseUnityPlugin)this).Logger;
			Instance = this;
			Shuffle = ((BaseUnityPlugin)this).Config.Bind<bool>("General", "Shuffle", true, "Reproduce las pistas de cada emisora en orden aleatorio.");
			KeepOriginalStations = ((BaseUnityPlugin)this).Config.Bind<int>("General", "KeepOriginalStations", 0, new ConfigDescription("Cuántas emisoras originales del juego dejar intactas antes de empezar a sustituir.", (AcceptableValueBase)(object)new AcceptableValueRange<int>(0, 8), Array.Empty<object>()));
			AnnounceTrack = ((BaseUnityPlugin)this).Config.Bind<bool>("General", "AnnounceTrack", true, "Escribe en el log qué pista empieza a sonar en cada emisora.");
			UseMyMusicFolder = ((BaseUnityPlugin)this).Config.Bind<bool>("General", "UseMyMusicFolder", true, "Usa tu carpeta de música de Windows como origen de emisoras, sin configurar nada. Cada subcarpeta suya se convierte en una emisora.");
			MusicFolders = ((BaseUnityPlugin)this).Config.Bind<string>("General", "MusicFolders", "", "Carpetas de tu disco que quieres usar como emisoras, separadas por ';'. Ejemplo: C:\\Users\\Usuario\\Music;D:\\Descargas\\Lofi No hace falta copiar ni mover nada: el mod lee de donde estén.");
			SplitSubfolders = ((BaseUnityPlugin)this).Config.Bind<bool>("General", "SplitSubfolders", true, "Si una carpeta de MusicFolders tiene subcarpetas, cada subcarpeta se convierte en una emisora distinta. Si lo apagas, toda la carpeta es una sola emisora.");
			SkipKey = ((BaseUnityPlugin)this).Config.Bind<Key>("General", "SkipKey", (Key)103, "Salta a la siguiente canción de la emisora que estés escuchando en ese momento.");
			ReloadKey = ((BaseUnityPlugin)this).Config.Bind<Key>("General", "ReloadKey", (Key)102, "Vuelve a leer las carpetas sin cerrar el juego. Útil mientras montas tus emisoras.");
			StreamStations = ((BaseUnityPlugin)this).Config.Bind<string>("Streaming", "StreamStations", "", "Emisoras de internet, formato Nombre=URL separadas por ';'. La URL debe apuntar a un flujo de audio directo (.mp3/.ogg), no a una página web. Ejemplo: Lofi=https://ejemplo.com/stream.mp3;Jazz=https://otro.com/jazz.ogg");
			Sync.Enabled = ((BaseUnityPlugin)this).Config.Bind<bool>("Multijugador", "SyncWithHost", false, "EXPERIMENTAL. El anfitrión decide qué canción suena y todos la oyen a la vez. Requiere que TODOS tengáis el mod y los mismos archivos de música: por la red solo viaja el nombre de la canción, no el audio. Desactivado por defecto porque no ha podido probarse en una partida real con varios jugadores.");
			Sync.EnsureSerializers();
			DiscoverStations();
			if (_stations.Count == 0)
			{
				Log.LogInfo((object)"Ninguna emisora configurada. Crea una carpeta por emisora en BepInEx/plugins/HTFCustomRadio/Stations/ y mete ahí tus .ogg o .mp3");
			}
			Harmony val = new Harmony("com.hatsuneexo.htfcustomradio");
			val.PatchAll(Assembly.GetExecutingAssembly());
			Log.LogInfo((object)(val.GetPatchedMethods().Any() ? "Enganchado a Radio.OnStartClient: coge una radio en la partida." : "AVISO: no encuentro Radio.OnStartClient. El mod no hará nada."));
		}

		private void DiscoverStations()
		{
			string pluginPath = Paths.PluginPath;
			if (Directory.Exists(pluginPath))
			{
				foreach (string item in Directory.GetDirectories(pluginPath, "Stations", SearchOption.AllDirectories).SelectMany(Directory.GetDirectories).OrderBy<string, string>((string d) => Path.GetFileName(d), StringComparer.OrdinalIgnoreCase))
				{
					List<string> list = Directory.GetFiles(item, "*.*", SearchOption.AllDirectories).Where(AudioLoading.IsSupported).OrderBy<string, string>((string f) => Path.GetFileName(f), StringComparer.OrdinalIgnoreCase)
						.ToList();
					if (list.Count == 0)
					{
						Log.LogWarning((object)("Emisora '" + Path.GetFileName(item) + "' sin pistas soportadas (.ogg/.wav/.mp3)."));
						continue;
					}
					_stations.Add(new Station
					{
						Name = Path.GetFileName(item),
						Tracks = list
					});
				}
			}
			foreach (string item2 in FoldersToScan())
			{
				List<string> list2 = (SplitSubfolders.Value ? Directory.GetDirectories(item2).OrderBy<string, string>((string d) => d, StringComparer.OrdinalIgnoreCase).ToList() : new List<string>());
				foreach (string item3 in (list2.Count > 0) ? list2 : new List<string> { item2 })
				{
					List<string> list3 = SafeFiles(item3);
					if (list3.Count != 0)
					{
						_stations.Add(new Station
						{
							Name = Path.GetFileName(item3.TrimEnd(new char[1] { Path.DirectorySeparatorChar })),
							Tracks = list3
						});
					}
				}
			}
			string[] array = StreamStations.Value.Split(new char[1] { ';' });
			for (int num = 0; num < array.Length; num++)
			{
				string[] array2 = array[num].Split(new char[1] { '=' }, 2);
				if (array2.Length == 2)
				{
					string text = array2[0].Trim();
					string text2 = array2[1].Trim();
					if (text.Length != 0 && text2.Length != 0)
					{
						_stations.Add(new Station
						{
							Name = text,
							IsStream = true,
							Tracks = new List<string> { text2 }
						});
					}
				}
			}
			if (_stations.Count > 0)
			{
				Log.LogInfo((object)string.Format("{0} emisora(s): {1}", _stations.Count, string.Join(", ", _stations.Select((Station s) => s.Name))));
			}
		}

		private List<string> FoldersToScan()
		{
			List<string> result = new List<string>();
			HashSet<string> seen = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
			if (UseMyMusicFolder.Value)
			{
				string folderPath = Environment.GetFolderPath(Environment.SpecialFolder.MyMusic);
				if (!string.IsNullOrEmpty(folderPath) && Directory.Exists(folderPath))
				{
					Add(folderPath, warnIfMissing: false);
					Log.LogInfo((object)("Usando tu carpeta de música: " + folderPath));
				}
			}
			string[] array = MusicFolders.Value.Split(new char[1] { ';' });
			for (int i = 0; i < array.Length; i++)
			{
				Add(array[i], warnIfMissing: true);
			}
			return result;
			void Add(string path, bool warnIfMissing)
			{
				if (!string.IsNullOrEmpty(path))
				{
					string fullPath;
					try
					{
						fullPath = Path.GetFullPath(path.Trim().Trim(new char[1] { '"' }));
					}
					catch
					{
						Log.LogWarning((object)("Ruta inválida: " + path));
						return;
					}
					if (!Directory.Exists(fullPath))
					{
						if (warnIfMissing)
						{
							Log.LogWarning((object)("No existe la carpeta: " + fullPath));
						}
					}
					else if (seen.Add(fullPath))
					{
						result.Add(fullPath);
					}
				}
			}
		}

		private List<string> SafeFiles(string dir)
		{
			try
			{
				return Directory.GetFiles(dir, "*.*", SearchOption.AllDirectories).Where(AudioLoading.IsSupported).OrderBy<string, string>((string f) => Path.GetFileName(f), StringComparer.OrdinalIgnoreCase)
					.ToList();
			}
			catch (Exception ex)
			{
				Log.LogWarning((object)("No pude leer " + dir + ": " + ex.Message));
				return new List<string>();
			}
		}

		private void HandleSkipKey()
		{
			//IL_0010: Unknown result type (might be due to invalid IL or missing references)
			Keyboard current = Keyboard.current;
			if (current == null)
			{
				return;
			}
			KeyControl val = current[SkipKey.Value];
			if (val == null || !((ButtonControl)val).wasPressedThisFrame)
			{
				return;
			}
			if (_live.Count == 0)
			{
				Log.LogInfo((object)"No hay ninguna emisora activa. Coge una radio primero.");
				return;
			}
			Station station = null;
			float num = 0.01f;
			foreach (Station item in _live)
			{
				if (!((Object)(object)item.Source == (Object)null) && !(item.Source.volume <= num))
				{
					num = item.Source.volume;
					station = item;
				}
			}
			if (station == null)
			{
				Log.LogInfo((object)"No estás sintonizado en ninguna emisora; gira el dial.");
			}
			else if (station.IsStream)
			{
				Log.LogInfo((object)("[" + station.Name + "] es una emisora de internet: no hay pistas que saltar."));
			}
			else if (!station.Loading)
			{
				if (station.Tracks.Count < 2)
				{
					Log.LogInfo((object)("[" + station.Name + "] solo tiene una canción; añade más a la carpeta."));
					return;
				}
				Advance(station);
				station.ExpectedEnd = 0f;
				station.RecentAdvances = 0;
				station.Paused = false;
				((MonoBehaviour)this).StartCoroutine(PlayCurrent(station));
			}
		}

		private void HandleReloadKey()
		{
			//IL_0010: Unknown result type (might be due to invalid IL or missing references)
			Keyboard current = Keyboard.current;
			if (current == null)
			{
				return;
			}
			KeyControl val = current[ReloadKey.Value];
			if (val != null && ((ButtonControl)val).wasPressedThisFrame)
			{
				((MonoBehaviour)this).StopAllCoroutines();
				_live.Clear();
				_stations.Clear();
				DiscoverStations();
				if (_lastRadio != null)
				{
					TakeOverRadio(_lastRadio);
					Log.LogInfo((object)"Emisoras recargadas.");
				}
				else
				{
					Log.LogInfo((object)"Emisoras recargadas. Coge una radio para oírlas.");
				}
			}
		}

		internal void TakeOverRadio(object radio)
		{
			_lastRadio = radio;
			if (_stations.Count == 0)
			{
				return;
			}
			((MonoBehaviour)this).StopAllCoroutines();
			foreach (Station station2 in _stations)
			{
				station2.Loading = false;
				station2.Paused = false;
				station2.RecentAdvances = 0;
			}
			if (AccessTools.Field(radio.GetType(), "_channels")?.GetValue(radio) is Array { Length: not 0 } array)
			{
				Log.LogInfo((object)$"Radio encontrada con {array.Length} canal(es).");
				_live.Clear();
				int num = Mathf.Clamp(KeepOriginalStations.Value, 0, array.Length);
				for (int i = num; i < array.Length; i++)
				{
					int num2 = i - num;
					if (num2 < _stations.Count)
					{
						object value = array.GetValue(i);
						object? obj = AccessTools.Field(value.GetType(), "_channelSource")?.GetValue(value);
						AudioSource val = (AudioSource)((obj is AudioSource) ? obj : null);
						if ((Object)(object)val == (Object)null)
						{
							Log.LogWarning((object)$"Canal {i} sin AudioSource, lo dejo como está.");
							continue;
						}
						object obj2 = AccessTools.Field(value.GetType(), "_frequency")?.GetValue(value);
						Station station = _stations[num2];
						station.Source = val;
						station.Frequency = ((obj2 is float num3) ? num3 : 0f);
						station.Index = (Shuffle.Value ? Rng.Next(station.Tracks.Count) : 0);
						_live.Add(station);
						Log.LogInfo((object)$"  {station.Frequency:0.0} -> {station.Name} ({station.Tracks.Count} pista(s))");
						((MonoBehaviour)this).StartCoroutine(PlayCurrent(station));
						continue;
					}
					break;
				}
			}
			else
			{
				Log.LogWarning((object)"La radio no expone canales; ¿cambió el juego?");
			}
		}

		private IEnumerator PlayCurrent(Station station, float startAt = 0f)
		{
			if (station.Loading || (Object)(object)station.Source == (Object)null)
			{
				yield break;
			}
			station.Loading = true;
			string path = station.Tracks[station.Index % station.Tracks.Count];
			yield return AudioLoading.Load(path, station.IsStream, delegate(AudioClip clip)
			{
				if (!((Object)(object)station.Source == (Object)null))
				{
					if ((Object)(object)station.OwnedClip != (Object)null && (Object)(object)station.OwnedClip != (Object)(object)clip)
					{
						Object.Destroy((Object)(object)station.OwnedClip);
					}
					station.OwnedClip = clip;
					station.Source.clip = clip;
					station.Source.loop = station.IsStream;
					station.Source.time = Mathf.Clamp(startAt, 0f, Mathf.Max(0f, clip.length - 0.1f));
					station.Source.Play();
					station.LastStart = Time.unscaledTime;
					station.ExpectedEnd = Time.unscaledTime + Mathf.Max(1f, clip.length - station.Source.time);
					if (AnnounceTrack.Value)
					{
						Log.LogInfo((object)("[" + station.Name + "] suena '" + ((Object)clip).name + "'"));
					}
					Sync.Announce(station.Name, path, station.Source.time);
				}
			}, delegate(string msg)
			{
				Log.LogWarning((object)("[" + station.Name + "] " + msg));
				Advance(station);
			});
			station.Loading = false;
		}

		private bool AllowAdvance(Station station)
		{
			if (station.Paused)
			{
				return false;
			}
			float unscaledTime = Time.unscaledTime;
			if (unscaledTime - station.WindowStart > 60f)
			{
				station.WindowStart = unscaledTime;
				station.RecentAdvances = 0;
			}
			station.RecentAdvances++;
			if (station.RecentAdvances <= 30)
			{
				return true;
			}
			station.Paused = true;
			Log.LogError((object)($"[{station.Name}] ha cambiado de pista {station.RecentAdvances} veces en un " + "minuto, lo cual no es normal. Emisora detenida para no agotar la memoria. Pulsa la tecla de recarga para reactivarla."));
			return false;
		}

		internal void ApplySync(string stationName, string trackName, float position)
		{
			Station station = _live.FirstOrDefault((Station s) => string.Equals(s.Name, stationName, StringComparison.OrdinalIgnoreCase));
			if (station == null || station.IsStream)
			{
				return;
			}
			int num = station.Tracks.FindIndex((string t) => string.Equals(Path.GetFileNameWithoutExtension(t), trackName, StringComparison.OrdinalIgnoreCase));
			if (num < 0)
			{
				if (_missingTracks.Add(trackName))
				{
					Log.LogWarning((object)("[" + stationName + "] el anfitrión puso '" + trackName + "' y tú no la tienes. Para oír lo mismo necesitáis los mismos archivos."));
				}
			}
			else
			{
				AudioSource source = station.Source;
				if (station.Index != num || !((Object)(object)source != (Object)null) || !source.isPlaying || !(Mathf.Abs(source.time - position) < 2.5f))
				{
					station.Index = num;
					station.Paused = false;
					station.RecentAdvances = 0;
					((MonoBehaviour)this).StartCoroutine(PlayCurrent(station, position));
				}
			}
		}

		private void BroadcastState()
		{
			if (!Sync.IsHost || Time.unscaledTime < _nextBroadcast)
			{
				return;
			}
			_nextBroadcast = Time.unscaledTime + 8f;
			foreach (Station item in _live)
			{
				if (!item.IsStream && !((Object)(object)item.Source == (Object)null) && !((Object)(object)item.Source.clip == (Object)null) && item.Source.isPlaying)
				{
					Sync.Announce(item.Name, item.Tracks[item.Index % item.Tracks.Count], item.Source.time);
				}
			}
		}

		private void Advance(Station station)
		{
			if (station.Tracks.Count != 0)
			{
				station.Index = ((Shuffle.Value && station.Tracks.Count > 1) ? NextRandomDifferent(station) : ((station.Index + 1) % station.Tracks.Count));
			}
		}

		private static int NextRandomDifferent(Station station)
		{
			int num = Rng.Next(station.Tracks.Count - 1);
			if (num < station.Index)
			{
				return num;
			}
			return num + 1;
		}

		private void Update()
		{
			HandleReloadKey();
			HandleSkipKey();
			Sync.TryRegisterClient();
			BroadcastState();
			if (Sync.FollowingHost)
			{
				return;
			}
			foreach (Station item in _live)
			{
				if (!item.Loading && !item.IsStream)
				{
					AudioSource source = item.Source;
					if (!((Object)(object)source == (Object)null) && !((Object)(object)source.clip == (Object)null) && ((Behaviour)source).isActiveAndEnabled && !source.isPlaying && !(Time.unscaledTime < item.ExpectedEnd - 0.25f) && AllowAdvance(item))
					{
						Advance(item);
						((MonoBehaviour)this).StartCoroutine(PlayCurrent(item));
					}
				}
			}
		}
	}
	[HarmonyPatch]
	internal static class RadioPatch
	{
		private static MethodBase TargetMethod()
		{
			Type type = AccessTools.TypeByName("Radio");
			if (!(type == null))
			{
				return AccessTools.Method(type, "OnStartClient", (Type[])null, (Type[])null);
			}
			return null;
		}

		private static bool Prepare()
		{
			return TargetMethod() != null;
		}

		private static void Postfix(object __instance)
		{
			try
			{
				Plugin.Instance?.TakeOverRadio(__instance);
			}
			catch (Exception arg)
			{
				Plugin.Log.LogError((object)$"No pude tomar la radio: {arg}");
			}
		}
	}
	public struct RadioSyncBroadcast : IBroadcast
	{
		public string Station;

		public string Track;

		public float Position;
	}
	internal static class Sync
	{
		internal static ConfigEntry<bool> Enabled;

		private static bool _serializersReady;

		private static bool _clientRegistered;

		private static bool _broken;

		internal static bool IsHost => InstanceFinder.IsServerStarted;

		internal static bool FollowingHost
		{
			get
			{
				if (Enabled != null && Enabled.Value && _clientRegistered)
				{
					return !IsHost;
				}
				return false;
			}
		}

		internal static void EnsureSerializers()
		{
			if (_serializersReady || _broken)
			{
				return;
			}
			try
			{
				GenericWriter<RadioSyncBroadcast>.SetWrite((Action<Writer, RadioSyncBroadcast>)delegate(Writer writer, RadioSyncBroadcast value)
				{
					writer.WriteRadioSync(value);
				});
				GenericReader<RadioSyncBroadcast>.SetRead((Func<Reader, RadioSyncBroadcast>)((Reader reader) => reader.ReadRadioSync()));
				_serializersReady = true;
			}
			catch (Exception ex)
			{
				_broken = true;
				Plugin.Log.LogWarning((object)("No pude registrar el serializador de red: " + ex.Message + ". La sincronización queda desactivada; la radio sigue funcionando en local."));
			}
		}

		internal static void TryRegisterClient()
		{
			if (_clientRegistered || _broken || Enabled == null || !Enabled.Value)
			{
				return;
			}
			ClientManager clientManager = InstanceFinder.ClientManager;
			if ((Object)(object)clientManager == (Object)null)
			{
				return;
			}
			try
			{
				clientManager.RegisterBroadcast<RadioSyncBroadcast>((Action<RadioSyncBroadcast, Channel>)OnReceived);
				_clientRegistered = true;
				Plugin.Log.LogInfo((object)"Sincronización activa: escuchando al anfitrión.");
			}
			catch (Exception ex)
			{
				_broken = true;
				Plugin.Log.LogWarning((object)("No pude activar la sincronización: " + ex.Message + ". La radio sigue funcionando en local."));
			}
		}

		internal static void Unregister()
		{
			if (!_clientRegistered)
			{
				return;
			}
			try
			{
				ClientManager clientManager = InstanceFinder.ClientManager;
				if (clientManager != null)
				{
					clientManager.UnregisterBroadcast<RadioSyncBroadcast>((Action<RadioSyncBroadcast, Channel>)OnReceived);
				}
			}
			catch
			{
			}
			_clientRegistered = false;
		}

		internal static void Announce(string station, string trackPath, float position)
		{
			if (_broken || Enabled == null || !Enabled.Value || !IsHost)
			{
				return;
			}
			ServerManager serverManager = InstanceFinder.ServerManager;
			if ((Object)(object)serverManager == (Object)null)
			{
				return;
			}
			try
			{
				serverManager.Broadcast<RadioSyncBroadcast>(new RadioSyncBroadcast
				{
					Station = station,
					Track = Path.GetFileNameWithoutExtension(trackPath),
					Position = position
				}, true, (Channel)0);
			}
			catch (Exception ex)
			{
				_broken = true;
				Plugin.Log.LogWarning((object)("Fallo enviando la sincronización: " + ex.Message + ". Cada uno oirá su propia música."));
			}
		}

		private static void OnReceived(RadioSyncBroadcast message, Channel channel)
		{
			if (IsHost)
			{
				return;
			}
			try
			{
				Plugin.Instance?.ApplySync(message.Station, message.Track, message.Position);
			}
			catch (Exception ex)
			{
				Plugin.Log.LogWarning((object)("No pude aplicar la sincronización: " + ex.Message));
			}
		}
	}
	public static class RadioSyncSerializer
	{
		public static void WriteRadioSync(this Writer writer, RadioSyncBroadcast value)
		{
			if (writer != null)
			{
				writer.WriteString(value.Station);
				writer.WriteString(value.Track);
				writer.WriteSingle(value.Position);
			}
		}

		public static RadioSyncBroadcast ReadRadioSync(this Reader reader)
		{
			return new RadioSyncBroadcast
			{
				Station = reader.ReadString(),
				Track = reader.ReadString(),
				Position = reader.ReadSingle()
			};
		}
	}
}