Decompiled source of NewRadioTracks v0.4.1

BepInEx/plugins/NewRadioTracks/FishingRadioExpansion.dll

Decompiled a day 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.Logging;
using HarmonyLib;
using Microsoft.CodeAnalysis;
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("FishingRadioExpansion")]
[assembly: AssemblyConfiguration("Release")]
[assembly: AssemblyFileVersion("0.1.0.0")]
[assembly: AssemblyInformationalVersion("0.1.0")]
[assembly: AssemblyProduct("FishingRadioExpansion")]
[assembly: AssemblyTitle("FishingRadioExpansion")]
[assembly: AssemblyVersion("0.1.0.0")]
[module: RefSafetyRules(11)]
namespace Microsoft.CodeAnalysis
{
	[CompilerGenerated]
	[Embedded]
	internal sealed class EmbeddedAttribute : Attribute
	{
	}
}
namespace System.Runtime.CompilerServices
{
	[CompilerGenerated]
	[Embedded]
	[AttributeUsage(AttributeTargets.Class | AttributeTargets.Property | AttributeTargets.Field | AttributeTargets.Event | AttributeTargets.Parameter | AttributeTargets.ReturnValue | AttributeTargets.GenericParameter, AllowMultiple = false, Inherited = false)]
	internal sealed class NullableAttribute : Attribute
	{
		public readonly byte[] NullableFlags;

		public NullableAttribute(byte P_0)
		{
			NullableFlags = new byte[1] { P_0 };
		}

		public NullableAttribute(byte[] P_0)
		{
			NullableFlags = P_0;
		}
	}
	[CompilerGenerated]
	[Embedded]
	[AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct | AttributeTargets.Method | AttributeTargets.Interface | AttributeTargets.Delegate, AllowMultiple = false, Inherited = false)]
	internal sealed class NullableContextAttribute : Attribute
	{
		public readonly byte Flag;

		public NullableContextAttribute(byte P_0)
		{
			Flag = P_0;
		}
	}
	[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 FishingRadioExpansion
{
	[BepInPlugin("collinj.fishingradioexpansion", "Fishing Radio Expansion", "0.1.0")]
	public sealed class Plugin : BaseUnityPlugin
	{
		public const string PluginGuid = "collinj.fishingradioexpansion";

		public const string PluginName = "Fishing Radio Expansion";

		public const string PluginVersion = "0.1.0";

		internal static ManualLogSource Log;

		internal static Plugin Instance;

		private Harmony _harmony;

		private void Awake()
		{
			//IL_0017: Unknown result type (might be due to invalid IL or missing references)
			//IL_0021: Expected O, but got Unknown
			Instance = this;
			Log = ((BaseUnityPlugin)this).Logger;
			_harmony = new Harmony("collinj.fishingradioexpansion");
			_harmony.PatchAll();
			RadioReflection.Resolve();
			RadioPatches.VerifyTargets();
			((MonoBehaviour)this).StartCoroutine(AudioLibrary.LoadBundledTracks());
			((BaseUnityPlugin)this).Logger.LogInfo((object)"Fishing Radio Expansion 0.1.0 loaded.");
		}

		internal void RunCoroutine(IEnumerator routine)
		{
			((MonoBehaviour)this).StartCoroutine(routine);
		}

		private void OnDestroy()
		{
			try
			{
				Harmony harmony = _harmony;
				if (harmony != null)
				{
					harmony.UnpatchSelf();
				}
			}
			catch
			{
			}
		}
	}
	internal static class AudioLibrary
	{
		internal sealed class Track
		{
			public string Name = "";

			public string FileName = "";

			public AudioClip? Clip;
		}

		internal static readonly Track[] Tracks = new Track[6]
		{
			new Track
			{
				Name = "Dockside",
				FileName = "Dockside.ogg"
			},
			new Track
			{
				Name = "Fishing 3",
				FileName = "Fishing3.ogg"
			},
			new Track
			{
				Name = "Lock In",
				FileName = "LockIn.ogg"
			},
			new Track
			{
				Name = "Casino Royale",
				FileName = "CasinoRoyale.ogg"
			},
			new Track
			{
				Name = "Let it Ride!",
				FileName = "LetItRide.ogg"
			},
			new Track
			{
				Name = "Tropicalada",
				FileName = "Tropicalada.ogg"
			}
		};

		internal static bool Ready { get; private set; }

		internal static IEnumerator LoadBundledTracks()
		{
			Ready = false;
			string directoryName = Path.GetDirectoryName(typeof(Plugin).Assembly.Location);
			string musicDir = Path.Combine(directoryName, "music");
			for (int i = 0; i < Tracks.Length; i++)
			{
				Track track = Tracks[i];
				string text = Path.Combine(musicDir, track.FileName);
				if (!File.Exists(text))
				{
					Plugin.Log.LogError((object)("Missing radio track: " + text));
					continue;
				}
				string absoluteUri = new Uri(text).AbsoluteUri;
				UnityWebRequest req = UnityWebRequestMultimedia.GetAudioClip(absoluteUri, (AudioType)14);
				try
				{
					yield return req.SendWebRequest();
					if ((int)req.result != 1)
					{
						Plugin.Log.LogError((object)("Could not load " + track.FileName + ": " + req.error));
						continue;
					}
					AudioClip content = DownloadHandlerAudioClip.GetContent(req);
					((Object)content).name = "FishingRadioExpansion - " + track.Name;
					track.Clip = content;
					Plugin.Log.LogInfo((object)$"Loaded radio track: {track.Name} ({content.length:0.0}s)");
				}
				finally
				{
					((IDisposable)req)?.Dispose();
				}
			}
			Ready = true;
			RadioInjector.PatchTrackedRadios();
		}
	}
	internal static class RadioInjector
	{
		private sealed class InjectionState
		{
			public int OriginalCount;

			public float WidthFactor = 1f;

			public float[] CustomFrequencies = Array.Empty<float>();
		}

		private static readonly List<WeakReference> Tracked = new List<WeakReference>();

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

		internal static void Track(object radio)
		{
			//IL_0005: Unknown result type (might be due to invalid IL or missing references)
			//IL_0041: Unknown result type (might be due to invalid IL or missing references)
			if (radio == null)
			{
				return;
			}
			int instanceID = ((Object)radio).GetInstanceID();
			for (int num = Tracked.Count - 1; num >= 0; num--)
			{
				object target = Tracked[num].Target;
				if (target == null)
				{
					Tracked.RemoveAt(num);
				}
				else if (((Object)target).GetInstanceID() == instanceID)
				{
					return;
				}
			}
			Tracked.Add(new WeakReference(radio));
		}

		internal static void PatchTrackedRadios()
		{
			for (int num = Tracked.Count - 1; num >= 0; num--)
			{
				object target = Tracked[num].Target;
				if (target == null)
				{
					Tracked.RemoveAt(num);
				}
				else
				{
					TryInject(target);
				}
			}
		}

		internal static void TryInject(object radio)
		{
			//IL_0029: Unknown result type (might be due to invalid IL or missing references)
			//IL_00ae: Unknown result type (might be due to invalid IL or missing references)
			//IL_00b3: Unknown result type (might be due to invalid IL or missing references)
			//IL_00b7: Unknown result type (might be due to invalid IL or missing references)
			//IL_00be: Unknown result type (might be due to invalid IL or missing references)
			if (!AudioLibrary.Ready || RadioReflection.RadioChannelType == null)
			{
				return;
			}
			Array channels = RadioReflection.GetChannels(radio);
			if (channels == null || channels.Length == 0)
			{
				return;
			}
			int instanceID = ((Object)radio).GetInstanceID();
			if (States.ContainsKey(instanceID))
			{
				return;
			}
			AudioSource source = RadioReflection.GetSource(channels.GetValue(0));
			if (!Object.op_Implicit((Object)(object)source))
			{
				Plugin.Log.LogWarning((object)"Radio channel has no AudioSource; custom stations were not added.");
				return;
			}
			List<object> list = new List<object>();
			List<float> list2 = new List<float>();
			for (int i = 0; i < channels.Length; i++)
			{
				object value = channels.GetValue(i);
				if (value != null)
				{
					list.Add(value);
					list2.Add(RadioReflection.GetFrequency(value));
				}
			}
			Vector2 band = RadioReflection.GetBand(radio);
			float[] array = ChooseFrequencies(list2, band.x, band.y, AudioLibrary.Tracks.Length);
			for (int j = 0; j < AudioLibrary.Tracks.Length; j++)
			{
				AudioClip clip = AudioLibrary.Tracks[j].Clip;
				if (Object.op_Implicit((Object)(object)clip))
				{
					AudioSource val = CloneAudioSource(source, radio, j);
					val.clip = clip;
					val.loop = true;
					val.playOnAwake = false;
					val.volume = 0f;
					object obj = Activator.CreateInstance(RadioReflection.RadioChannelType);
					RadioReflection.ChannelSourceField.SetValue(obj, val);
					RadioReflection.SetFrequency(obj, array[j]);
					RadioReflection.SetMuted(obj, muted: true);
					list.Add(obj);
				}
			}
			Array array2 = Array.CreateInstance(RadioReflection.RadioChannelType, list.Count);
			for (int k = 0; k < list.Count; k++)
			{
				array2.SetValue(list[k], k);
			}
			RadioReflection.ChannelsField.SetValue(radio, array2);
			List<float> list3 = (from v in list.Select(RadioReflection.GetFrequency)
				orderby v
				select v).ToList();
			float num = float.MaxValue;
			for (int num2 = 1; num2 < list3.Count; num2++)
			{
				num = Mathf.Min(num, list3[num2] - list3[num2 - 1]);
			}
			float num3 = Mathf.Clamp(num / 3f, 0.12f, 1f);
			States[instanceID] = new InjectionState
			{
				OriginalCount = channels.Length,
				WidthFactor = num3,
				CustomFrequencies = array
			};
			Plugin.Log.LogInfo((object)($"Added {AudioLibrary.Tracks.Length} radio stations. " + "Frequencies: " + string.Join(", ", array.Select((float f) => f.ToString("0.00"))) + ". " + $"Width factor: {num3:0.00}"));
		}

		internal static bool TryGetState(object radio, out float widthFactor)
		{
			widthFactor = 1f;
			Object val = (Object)((radio is Object) ? radio : null);
			if (val == null)
			{
				return false;
			}
			if (!States.TryGetValue(val.GetInstanceID(), out InjectionState value))
			{
				return false;
			}
			widthFactor = value.WidthFactor;
			return true;
		}

		private static float[] ChooseFrequencies(List<float> existing, float min, float max, int count)
		{
			List<float> list = (from f in existing
				where f >= min && f <= max
				orderby f
				select f).ToList();
			List<float> list2 = new List<float>();
			for (int num = 0; num < count; num++)
			{
				float num2 = min;
				float num3 = -1f;
				for (float num4 = min + 0.15f; num4 <= max - 0.15f; num4 += 0.05f)
				{
					float num5 = float.MaxValue;
					foreach (float item in list)
					{
						num5 = Mathf.Min(num5, Mathf.Abs(num4 - item));
					}
					foreach (float item2 in list2)
					{
						num5 = Mathf.Min(num5, Mathf.Abs(num4 - item2));
					}
					num5 = Mathf.Min(num5, (num4 - min) * 1.35f);
					num5 = Mathf.Min(num5, (max - num4) * 1.35f);
					if (num5 > num3)
					{
						num3 = num5;
						num2 = num4;
					}
				}
				num2 = Mathf.Round(num2 * 20f) / 20f;
				list2.Add(num2);
			}
			return list2.OrderBy((float f) => f).ToArray();
		}

		private static AudioSource CloneAudioSource(AudioSource template, object radio, int index)
		{
			//IL_0082: Unknown result type (might be due to invalid IL or missing references)
			//IL_0088: Expected O, but got Unknown
			//IL_0038: Unknown result type (might be due to invalid IL or missing references)
			//IL_004e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0064: Unknown result type (might be due to invalid IL or missing references)
			GameObject gameObject = ((Component)template).gameObject;
			GameObject val;
			if (!Object.op_Implicit((Object)(object)gameObject.GetComponentInChildren(RadioReflection.RadioType, true)))
			{
				val = Object.Instantiate<GameObject>(gameObject, gameObject.transform.parent);
				val.transform.localPosition = gameObject.transform.localPosition;
				val.transform.localRotation = gameObject.transform.localRotation;
				val.transform.localScale = gameObject.transform.localScale;
			}
			else
			{
				val = new GameObject($"Fishing Radio Custom Channel {index + 1}");
				val.transform.SetParent(gameObject.transform, false);
				AudioSource to = val.AddComponent<AudioSource>();
				CopyAudioSettings(template, to);
			}
			((Object)val).name = $"Fishing Radio Custom Channel {index + 1}";
			AudioSource component = val.GetComponent<AudioSource>();
			component.volume = 0f;
			return component;
		}

		private static void CopyAudioSettings(AudioSource from, AudioSource to)
		{
			//IL_008d: Unknown result type (might be due to invalid IL or missing references)
			to.outputAudioMixerGroup = from.outputAudioMixerGroup;
			to.mute = false;
			to.bypassEffects = from.bypassEffects;
			to.bypassListenerEffects = from.bypassListenerEffects;
			to.bypassReverbZones = from.bypassReverbZones;
			to.priority = from.priority;
			to.pitch = from.pitch;
			to.panStereo = from.panStereo;
			to.spatialBlend = from.spatialBlend;
			to.reverbZoneMix = from.reverbZoneMix;
			to.dopplerLevel = from.dopplerLevel;
			to.spread = from.spread;
			to.rolloffMode = from.rolloffMode;
			to.minDistance = from.minDistance;
			to.maxDistance = from.maxDistance;
			to.ignoreListenerVolume = from.ignoreListenerVolume;
			to.ignoreListenerPause = from.ignoreListenerPause;
		}

		internal static void RecomputeVolumes(object radio)
		{
			if (!TryGetState(radio, out var widthFactor))
			{
				return;
			}
			Array channels = RadioReflection.GetChannels(radio);
			if (channels == null)
			{
				return;
			}
			float tunedFrequency = RadioReflection.GetTunedFrequency(radio);
			float radioVolume = RadioReflection.GetRadioVolume(radio);
			float num = 0f;
			for (int i = 0; i < channels.Length; i++)
			{
				object value = channels.GetValue(i);
				if (value != null)
				{
					float frequency = RadioReflection.GetFrequency(value);
					float num2 = Mathf.Abs(tunedFrequency - frequency) / Mathf.Max(0.01f, widthFactor) - 0.5f;
					num2 = Mathf.Clamp01(1f - num2);
					float num3 = num2 * radioVolume;
					RadioReflection.SetChannelVolume(value, num3);
					RadioReflection.SetMuted(value, num3 < 0.015f);
					if (num2 > num)
					{
						num = num2;
					}
				}
			}
			try
			{
				object? obj = RadioReflection.NoiseSourceField?.GetValue(radio);
				AudioSource val = (AudioSource)((obj is AudioSource) ? obj : null);
				if (val != null)
				{
					val.volume = (1f - num) * 0.075f;
				}
			}
			catch
			{
			}
		}
	}
	[HarmonyPatch]
	internal static class RadioStartPatch
	{
		private static MethodBase? TargetMethod()
		{
			RadioReflection.Resolve();
			return RadioReflection.OnStartClientMethod;
		}

		private static void Postfix(object __instance)
		{
			try
			{
				RadioInjector.Track(__instance);
				RadioInjector.TryInject(__instance);
			}
			catch (Exception ex)
			{
				Plugin.Log.LogWarning((object)("Radio startup patch failed: " + ex.Message));
			}
		}
	}
	[HarmonyPatch]
	internal static class RadioVolumePatch
	{
		private static MethodBase? TargetMethod()
		{
			RadioReflection.Resolve();
			return RadioReflection.ApplyVolumeMethod;
		}

		private static void Postfix(object __instance)
		{
			try
			{
				RadioInjector.RecomputeVolumes(__instance);
			}
			catch (Exception ex)
			{
				Plugin.Log.LogWarning((object)("Radio volume patch failed: " + ex.Message));
			}
		}
	}
	internal static class RadioPatches
	{
		internal static void VerifyTargets()
		{
			if (RadioReflection.RadioType == null)
			{
				Plugin.Log.LogError((object)"Radio type was not found.");
			}
			if (RadioReflection.OnStartClientMethod == null)
			{
				Plugin.Log.LogError((object)"Radio.OnStartClient was not found.");
			}
			if (RadioReflection.ApplyVolumeMethod == null)
			{
				Plugin.Log.LogError((object)"Radio.ApplyVolume was not found.");
			}
		}
	}
	internal static class RadioReflection
	{
		internal static Type? RadioType;

		internal static Type? RadioChannelType;

		internal static FieldInfo? ChannelsField;

		internal static FieldInfo? ChannelSourceField;

		internal static FieldInfo? ChannelFrequencyField;

		internal static FieldInfo? FrequencyRangeField;

		internal static FieldInfo? LocalFrequencyField;

		internal static FieldInfo? NetworkFrequencyField;

		internal static FieldInfo? RadioVolumeField;

		internal static FieldInfo? NoiseSourceField;

		internal static MethodInfo? ApplyVolumeMethod;

		internal static MethodInfo? OnStartClientMethod;

		internal static MethodInfo? ToggleMuteMethod;

		internal static MethodInfo? SetVolMethod;

		internal static PropertyInfo? IsMutedProperty;

		internal static bool Resolved { get; private set; }

		internal static void Resolve()
		{
			if (Resolved)
			{
				return;
			}
			Resolved = true;
			RadioType = AccessTools.TypeByName("Radio");
			RadioChannelType = AccessTools.TypeByName("RadioChannel");
			if (RadioType == null || RadioChannelType == null)
			{
				Plugin.Log.LogError((object)"Could not find Radio/RadioChannel. The game may have updated.");
				return;
			}
			ChannelsField = AccessTools.Field(RadioType, "_channels");
			ChannelSourceField = AccessTools.Field(RadioChannelType, "_channelSource");
			ChannelFrequencyField = AccessTools.Field(RadioChannelType, "_frequency");
			FrequencyRangeField = AccessTools.Field(RadioType, "FreqMinMax");
			LocalFrequencyField = AccessTools.Field(RadioType, "_localFrequency");
			NetworkFrequencyField = AccessTools.Field(RadioType, "_frequency");
			RadioVolumeField = AccessTools.Field(RadioType, "_radioVol");
			NoiseSourceField = AccessTools.Field(RadioType, "_noiseSource");
			ApplyVolumeMethod = AccessTools.Method(RadioType, "ApplyVolume", (Type[])null, (Type[])null);
			OnStartClientMethod = AccessTools.Method(RadioType, "OnStartClient", (Type[])null, (Type[])null);
			ToggleMuteMethod = AccessTools.Method(RadioChannelType, "ToggleMute", (Type[])null, (Type[])null);
			SetVolMethod = AccessTools.Method(RadioChannelType, "SetVol", (Type[])null, (Type[])null);
			IsMutedProperty = AccessTools.Property(RadioChannelType, "IsMuted");
			if (ChannelsField == null || ChannelSourceField == null || ChannelFrequencyField == null)
			{
				Plugin.Log.LogError((object)"Could not find Radio/RadioChannel. The game may have updated.");
			}
		}

		internal static Array? GetChannels(object radio)
		{
			try
			{
				return ChannelsField?.GetValue(radio) as Array;
			}
			catch
			{
				return null;
			}
		}

		internal static AudioSource? GetSource(object channel)
		{
			try
			{
				object? obj = ChannelSourceField?.GetValue(channel);
				return (AudioSource?)((obj is AudioSource) ? obj : null);
			}
			catch
			{
				return null;
			}
		}

		internal static float GetFrequency(object channel)
		{
			try
			{
				return (ChannelFrequencyField?.GetValue(channel) is float num) ? num : 0f;
			}
			catch
			{
				return 0f;
			}
		}

		internal static void SetFrequency(object channel, float frequency)
		{
			ChannelFrequencyField?.SetValue(channel, frequency);
		}

		internal static Vector2 GetBand(object radio)
		{
			//IL_0035: Unknown result type (might be due to invalid IL or missing references)
			//IL_001c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0021: Unknown result type (might be due to invalid IL or missing references)
			//IL_0022: Unknown result type (might be due to invalid IL or missing references)
			//IL_0023: Unknown result type (might be due to invalid IL or missing references)
			//IL_003b: Unknown result type (might be due to invalid IL or missing references)
			try
			{
				object obj = FrequencyRangeField?.GetValue(radio);
				if (obj is Vector2)
				{
					return (Vector2)obj;
				}
			}
			catch
			{
			}
			return new Vector2(88f, 108f);
		}

		internal static float GetTunedFrequency(object radio)
		{
			try
			{
				if (LocalFrequencyField?.GetValue(radio) is float num && num >= 70f && num <= 130f)
				{
					return num;
				}
			}
			catch
			{
			}
			try
			{
				object obj2 = NetworkFrequencyField?.GetValue(radio);
				if (obj2 != null && obj2.GetType().GetProperty("Value", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic)?.GetValue(obj2) is float result)
				{
					return result;
				}
			}
			catch
			{
			}
			return 98f;
		}

		internal static float GetRadioVolume(object radio)
		{
			try
			{
				object obj = RadioVolumeField?.GetValue(radio);
				if (obj is float)
				{
					return (float)obj;
				}
			}
			catch
			{
			}
			return 0.3f;
		}

		internal static void SetChannelVolume(object channel, float volume)
		{
			try
			{
				if (SetVolMethod != null)
				{
					SetVolMethod.Invoke(channel, new object[1] { volume });
					return;
				}
			}
			catch
			{
			}
			AudioSource source = GetSource(channel);
			if (Object.op_Implicit((Object)(object)source))
			{
				source.volume = volume;
			}
		}

		internal static void SetMuted(object channel, bool muted)
		{
			try
			{
				bool flag = false;
				if (IsMutedProperty?.GetValue(channel) is bool flag2)
				{
					flag = flag2;
				}
				if (flag != muted)
				{
					ToggleMuteMethod?.Invoke(channel, new object[2] { muted, 0f });
				}
			}
			catch
			{
			}
		}
	}
}