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
{
}
}
}
}