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(); } private static readonly List Tracked = new List(); private static readonly Dictionary States = new Dictionary(); 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 list = new List(); List list2 = new List(); 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 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 existing, float min, float max, int count) { List list = (from f in existing where f >= min && f <= max orderby f select f).ToList(); List list2 = new List(); 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.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(); CopyAudioSettings(template, to); } ((Object)val).name = $"Fishing Radio Custom Channel {index + 1}"; AudioSource component = val.GetComponent(); 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 { } } } }