using System; using System.Collections.Concurrent; using System.Collections.Generic; using System.Diagnostics; using System.IO; using System.Linq; using System.Reflection; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using System.Runtime.Versioning; using System.Security; using System.Security.Cryptography; using System.Security.Permissions; using System.Text; using System.Text.Json; using System.Threading; using System.Threading.Tasks; using Audio; using BepInEx; using BepInEx.Configuration; using BepInEx.Core.Logging.Interpolation; using BepInEx.Logging; using BepInEx.Unity.IL2CPP; using FMOD; using FMOD.Studio; using FMODUnity; using Fusion; using Fusion.Sockets; using HarmonyLib; using Il2CppInterop.Runtime; using Il2CppInterop.Runtime.InteropTypes; using Il2CppInterop.Runtime.InteropTypes.Arrays; using Il2CppSystem; using Microsoft.CodeAnalysis; using NLayer; using NVorbis; using Networking; using RadioLib.Net; using RoadsideResearch; using UnityEngine; [assembly: CompilationRelaxations(8)] [assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)] [assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)] [assembly: TargetFramework(".NETCoreApp,Version=v6.0", FrameworkDisplayName = "")] [assembly: AssemblyCompany("RadioLib")] [assembly: AssemblyConfiguration("Release")] [assembly: AssemblyFileVersion("1.0.0.0")] [assembly: AssemblyInformationalVersion("1.0.0")] [assembly: AssemblyProduct("RadioLib")] [assembly: AssemblyTitle("RadioLib")] [assembly: SecurityPermission(SecurityAction.RequestMinimum, SkipVerification = true)] [assembly: AssemblyVersion("1.0.0.0")] [module: UnverifiableCode] namespace Microsoft.CodeAnalysis { [CompilerGenerated] [Microsoft.CodeAnalysis.Embedded] internal sealed class EmbeddedAttribute : Attribute { } } namespace System.Runtime.CompilerServices { [CompilerGenerated] [Microsoft.CodeAnalysis.Embedded] [AttributeUsage(AttributeTargets.Class | AttributeTargets.Property | AttributeTargets.Field | AttributeTargets.Event | AttributeTargets.Parameter | AttributeTargets.ReturnValue | AttributeTargets.GenericParameter, AllowMultiple = false, Inherited = false)] internal sealed class NativeIntegerAttribute : Attribute { public readonly bool[] TransformFlags; public NativeIntegerAttribute() { TransformFlags = new bool[1] { true }; } public NativeIntegerAttribute(bool[] P_0) { TransformFlags = P_0; } } } namespace RadioLib { public static class AudioClipFactory { private struct ManagedSpanWrapper { public IntPtr begin; public int length; } [UnmanagedFunctionPointer(CallingConvention.Cdecl)] private unsafe delegate void CreateUserSoundInjectedFn(IntPtr nativePtr, ManagedSpanWrapper* name, int lengthSamples, int channels, int frequency, bool stream); private static readonly List _gcHandles = new List(); private static readonly List _clips = new List(); private static IntPtr _icallPtr = IntPtr.Zero; private static bool _resolved = false; internal static string InitStatus = "not yet initialized"; public static AudioClip CreateClip(TrackInfo track) { //IL_0424: Unknown result type (might be due to invalid IL or missing references) //IL_042a: Expected O, but got Unknown //IL_001b: Unknown result type (might be due to invalid IL or missing references) //IL_0021: Expected O, but got Unknown //IL_03b3: Unknown result type (might be due to invalid IL or missing references) //IL_03b9: Expected O, but got Unknown //IL_03ee: Unknown result type (might be due to invalid IL or missing references) //IL_03f4: Expected O, but got Unknown //IL_0320: Unknown result type (might be due to invalid IL or missing references) //IL_0327: Expected O, but got Unknown bool flag = default(bool); if (track.PcmData == null || track.PcmData.Length == 0) { ManualLogSource log = Plugin.Log; BepInExErrorLogInterpolatedStringHandler val = new BepInExErrorLogInterpolatedStringHandler(24, 1, ref flag); if (flag) { ((BepInExLogInterpolatedStringHandler)val).AppendLiteral("No PCM data for track '"); ((BepInExLogInterpolatedStringHandler)val).AppendFormatted(track.Name); ((BepInExLogInterpolatedStringHandler)val).AppendLiteral("'"); } log.LogError(val); return null; } if (!_resolved) { ResolveIcall(); } try { AudioClip val2 = AudioClip.Construct_Internal(); if ((Object)(object)val2 == (Object)null) { InitStatus += " | Construct_Internal=null"; Plugin.Log.LogError((object)"Construct_Internal returned null"); return null; } IntPtr intPtr = Marshal.ReadIntPtr(((Il2CppObjectBase)val2).Pointer + 16); InitStatus += $" | native=0x{intPtr:X}"; if (_icallPtr != IntPtr.Zero && intPtr != IntPtr.Zero) { CallInjected(intPtr, track.Name, track.LengthSamples, track.Channels, track.SampleRate); InitStatus += $" | after_icall_samples={val2.samples}"; } if (val2.samples == 0) { try { val2.CreateUserSound(track.Name, track.LengthSamples, track.Channels, track.SampleRate, false); InitStatus += $" | after_interop_samples={val2.samples}"; } catch (Exception ex) { InitStatus = InitStatus + " | interop_err=" + ex.GetType().Name; } } if (val2.samples == 0) { try { val2 = AudioClip.Create(track.Name, track.LengthSamples, track.Channels, track.SampleRate, false); if ((Object)(object)val2 != (Object)null) { InitStatus += $" | after_Create_samples={val2.samples}"; } else { InitStatus += " | Create=null"; } } catch (Exception ex2) { InitStatus = InitStatus + " | Create_err=" + ex2.GetType().Name; } } if ((Object)(object)val2 != (Object)null) { Il2CppStructArray val3 = new Il2CppStructArray((long)track.PcmData.Length); for (int i = 0; i < track.PcmData.Length; i++) { ((Il2CppArrayBase)(object)val3)[i] = track.PcmData[i]; } bool flag2 = val2.SetData(val3, 0); InitStatus += $" | SetData={flag2}"; if (flag2) { PinClip(val2); ManualLogSource log2 = Plugin.Log; BepInExInfoLogInterpolatedStringHandler val4 = new BepInExInfoLogInterpolatedStringHandler(34, 4, ref flag); if (flag) { ((BepInExLogInterpolatedStringHandler)val4).AppendLiteral("AudioClip ready: '"); ((BepInExLogInterpolatedStringHandler)val4).AppendFormatted(track.Name); ((BepInExLogInterpolatedStringHandler)val4).AppendLiteral("' "); ((BepInExLogInterpolatedStringHandler)val4).AppendFormatted(track.Channels); ((BepInExLogInterpolatedStringHandler)val4).AppendLiteral("ch "); ((BepInExLogInterpolatedStringHandler)val4).AppendFormatted(track.SampleRate); ((BepInExLogInterpolatedStringHandler)val4).AppendLiteral("Hz "); ((BepInExLogInterpolatedStringHandler)val4).AppendFormatted(track.LengthSamples); ((BepInExLogInterpolatedStringHandler)val4).AppendLiteral(" samples"); } log2.LogInfo(val4); return val2; } } ManualLogSource log3 = Plugin.Log; BepInExErrorLogInterpolatedStringHandler val = new BepInExErrorLogInterpolatedStringHandler(44, 1, ref flag); if (flag) { ((BepInExLogInterpolatedStringHandler)val).AppendLiteral("All AudioClip creation methods failed for '"); ((BepInExLogInterpolatedStringHandler)val).AppendFormatted(track.Name); ((BepInExLogInterpolatedStringHandler)val).AppendLiteral("'"); } log3.LogError(val); ManualLogSource log4 = Plugin.Log; val = new BepInExErrorLogInterpolatedStringHandler(12, 1, ref flag); if (flag) { ((BepInExLogInterpolatedStringHandler)val).AppendLiteral("Diagnostic: "); ((BepInExLogInterpolatedStringHandler)val).AppendFormatted(InitStatus); } log4.LogError(val); return null; } catch (Exception ex3) { ManualLogSource log5 = Plugin.Log; BepInExErrorLogInterpolatedStringHandler val = new BepInExErrorLogInterpolatedStringHandler(35, 2, ref flag); if (flag) { ((BepInExLogInterpolatedStringHandler)val).AppendLiteral("Failed to create AudioClip for '"); ((BepInExLogInterpolatedStringHandler)val).AppendFormatted(track.Name); ((BepInExLogInterpolatedStringHandler)val).AppendLiteral("': "); ((BepInExLogInterpolatedStringHandler)val).AppendFormatted(ex3); } log5.LogError(val); return null; } } private unsafe static void CallInjected(IntPtr nativePtr, string name, int lengthSamples, int channels, int frequency) { try { IntPtr intPtr = IL2CPP.ManagedStringToIl2Cpp(name); IntPtr begin = intPtr + 20; int length = Marshal.ReadInt32(intPtr + 16); ManagedSpanWrapper managedSpanWrapper = default(ManagedSpanWrapper); managedSpanWrapper.begin = begin; managedSpanWrapper.length = length; ManagedSpanWrapper managedSpanWrapper2 = managedSpanWrapper; Marshal.GetDelegateForFunctionPointer(_icallPtr)(nativePtr, &managedSpanWrapper2, lengthSamples, channels, frequency, stream: false); } catch (Exception ex) { InitStatus = InitStatus + " | icall_ex=" + ex.GetType().Name + ":" + ex.Message; } } private static void ResolveIcall() { //IL_00ac: Unknown result type (might be due to invalid IL or missing references) //IL_00b3: Expected O, but got Unknown _resolved = true; string[] array = new string[3] { "UnityEngine.AudioClip::CreateUserSound_Injected", "UnityEngine.AudioClip::CreateUserSound(System.String,System.Int32,System.Int32,System.Int32,System.Boolean)", "UnityEngine.AudioClip::CreateUserSound" }; bool flag = default(bool); foreach (string text in array) { _icallPtr = IL2CPP.il2cpp_resolve_icall(text); if (_icallPtr != IntPtr.Zero) { InitStatus = $"icall=0x{_icallPtr:X} ({text})"; ManualLogSource log = Plugin.Log; BepInExInfoLogInterpolatedStringHandler val = new BepInExInfoLogInterpolatedStringHandler(16, 2, ref flag); if (flag) { ((BepInExLogInterpolatedStringHandler)val).AppendLiteral("Resolved: "); ((BepInExLogInterpolatedStringHandler)val).AppendFormatted(text); ((BepInExLogInterpolatedStringHandler)val).AppendLiteral(" -> 0x"); ((BepInExLogInterpolatedStringHandler)val).AppendFormatted(_icallPtr, "X"); } log.LogInfo(val); return; } } InitStatus = "no icall found"; Plugin.Log.LogError((object)"Could not resolve any CreateUserSound icall"); } private static void PinClip(AudioClip clip) { //IL_001a: 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) IntPtr item = IL2CPP.il2cpp_gchandle_new(((Il2CppObjectBase)clip).Pointer, false); _gcHandles.Add(item); ((Object)clip).hideFlags = (HideFlags)(((Object)clip).hideFlags | 0x20); _clips.Add(clip); } } internal enum DecodePriority { Background, Prefetch, Immediate } internal static class DecodeService { private struct DecodeRequest { public TrackInfo Track; public string FilePath; public DecodePriority Priority; public CancellationTokenSource Cts; public long Sequence; } private static readonly object _lock = new object(); private static readonly List _queue = new List(); private static readonly ConcurrentDictionary _active = new ConcurrentDictionary(StringComparer.OrdinalIgnoreCase); private static readonly ManualResetEventSlim _signal = new ManualResetEventSlim(initialState: false); private static long _sequence; private static volatile bool _shutdown; private static Thread _worker; internal static void Start() { if (_worker == null) { _worker = new Thread(WorkerLoop) { Name = "RadioLib.Decode", IsBackground = true }; _worker.Start(); } } public static CancellationTokenSource Request(TrackInfo track, string filePath, DecodePriority priority) { if (track == null || string.IsNullOrEmpty(filePath)) { return null; } string hash = track.Hash; if (string.IsNullOrEmpty(hash)) { return null; } Start(); if (_active.TryGetValue(hash, out var value) && !value.IsCancellationRequested) { return value; } CancellationTokenSource cancellationTokenSource = new CancellationTokenSource(); _active[hash] = cancellationTokenSource; lock (_lock) { _queue.Add(new DecodeRequest { Track = track, FilePath = filePath, Priority = priority, Cts = cancellationTokenSource, Sequence = Interlocked.Increment(ref _sequence) }); } _signal.Set(); return cancellationTokenSource; } public static void Cancel(string hash) { if (!string.IsNullOrEmpty(hash) && _active.TryRemove(hash, out var value)) { value.Cancel(); } } public static void CancelAll() { foreach (KeyValuePair item in _active) { item.Value.Cancel(); } _active.Clear(); lock (_lock) { _queue.Clear(); } } internal static void Shutdown() { _shutdown = true; _signal.Set(); } private static void WorkerLoop() { //IL_0134: Unknown result type (might be due to invalid IL or missing references) //IL_013b: Expected O, but got Unknown //IL_018e: Unknown result type (might be due to invalid IL or missing references) //IL_0195: Expected O, but got Unknown //IL_01eb: Unknown result type (might be due to invalid IL or missing references) //IL_01f2: Expected O, but got Unknown //IL_006d: Unknown result type (might be due to invalid IL or missing references) //IL_0074: Expected O, but got Unknown bool flag = default(bool); while (!_shutdown) { _signal.Wait(); if (_shutdown) { break; } _signal.Reset(); DecodeRequest result; while (TryDequeueHighest(out result)) { if (result.Cts.IsCancellationRequested) { continue; } TrackInfo track = result.Track; CancellationToken token = result.Cts.Token; string extension = track.Extension; string filePath = result.FilePath; ManualLogSource log = Plugin.Log; BepInExInfoLogInterpolatedStringHandler val = new BepInExInfoLogInterpolatedStringHandler(17, 2, ref flag); if (flag) { ((BepInExLogInterpolatedStringHandler)val).AppendLiteral("Decoding '"); ((BepInExLogInterpolatedStringHandler)val).AppendFormatted(track.Name); ((BepInExLogInterpolatedStringHandler)val).AppendLiteral("' ("); ((BepInExLogInterpolatedStringHandler)val).AppendFormatted(result.Priority); ((BepInExLogInterpolatedStringHandler)val).AppendLiteral(")..."); } log.LogInfo(val); WavData wavData = null; try { wavData = extension switch { ".wav" => WavLoader.Load(filePath, Plugin.Log, token), ".mp3" => Mp3Loader.Load(filePath, Plugin.Log, token), ".ogg" => OggLoader.Load(filePath, Plugin.Log, token), _ => null, }; } catch (Exception ex) { ManualLogSource log2 = Plugin.Log; BepInExErrorLogInterpolatedStringHandler val2 = new BepInExErrorLogInterpolatedStringHandler(25, 2, ref flag); if (flag) { ((BepInExLogInterpolatedStringHandler)val2).AppendLiteral("DecodeService: '"); ((BepInExLogInterpolatedStringHandler)val2).AppendFormatted(track.Name); ((BepInExLogInterpolatedStringHandler)val2).AppendLiteral("' threw: "); ((BepInExLogInterpolatedStringHandler)val2).AppendFormatted(ex.Message); } log2.LogError(val2); } if (token.IsCancellationRequested) { ManualLogSource log3 = Plugin.Log; val = new BepInExInfoLogInterpolatedStringHandler(27, 1, ref flag); if (flag) { ((BepInExLogInterpolatedStringHandler)val).AppendLiteral("Decode of '"); ((BepInExLogInterpolatedStringHandler)val).AppendFormatted(track.Name); ((BepInExLogInterpolatedStringHandler)val).AppendLiteral("' was cancelled."); } log3.LogInfo(val); continue; } _active.TryRemove(track.Hash, out var _); if (wavData == null) { ManualLogSource log4 = Plugin.Log; BepInExWarningLogInterpolatedStringHandler val3 = new BepInExWarningLogInterpolatedStringHandler(27, 1, ref flag); if (flag) { ((BepInExLogInterpolatedStringHandler)val3).AppendLiteral("Decode of '"); ((BepInExLogInterpolatedStringHandler)val3).AppendFormatted(track.Name); ((BepInExLogInterpolatedStringHandler)val3).AppendLiteral("' returned null."); } log4.LogWarning(val3); } else { float[] samples = wavData.Samples; int sr = wavData.SampleRate; int ch = wavData.Channels; string hash = track.Hash; MainThread.Enqueue(delegate { RadioAPI.MarkTrackReady(hash, samples, sr, ch); }); } } } } private static bool TryDequeueHighest(out DecodeRequest result) { lock (_lock) { if (_queue.Count == 0) { result = default(DecodeRequest); return false; } int index = 0; for (int i = 1; i < _queue.Count; i++) { DecodeRequest decodeRequest = _queue[index]; DecodeRequest decodeRequest2 = _queue[i]; if (decodeRequest2.Priority > decodeRequest.Priority || (decodeRequest2.Priority == decodeRequest.Priority && decodeRequest2.Sequence < decodeRequest.Sequence)) { index = i; } } result = _queue[index]; _queue.RemoveAt(index); return true; } } } public static class FmodSoundFactory { internal static string InitStatus = "not yet initialized"; public static Sound CreateSound(TrackInfo track) { //IL_0255: Unknown result type (might be due to invalid IL or missing references) //IL_025b: Expected O, but got Unknown //IL_001b: Unknown result type (might be due to invalid IL or missing references) //IL_0021: Expected O, but got Unknown //IL_0290: Unknown result type (might be due to invalid IL or missing references) //IL_0296: Unknown result type (might be due to invalid IL or missing references) //IL_0298: 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_0054: Unknown result type (might be due to invalid IL or missing references) //IL_029b: Unknown result type (might be due to invalid IL or missing references) //IL_0057: Unknown result type (might be due to invalid IL or missing references) //IL_005c: Unknown result type (might be due to invalid IL or missing references) //IL_008a: Unknown result type (might be due to invalid IL or missing references) //IL_00c2: Unknown result type (might be due to invalid IL or missing references) //IL_00c7: Unknown result type (might be due to invalid IL or missing references) //IL_00c9: Unknown result type (might be due to invalid IL or missing references) //IL_00d0: Unknown result type (might be due to invalid IL or missing references) //IL_00d6: Unknown result type (might be due to invalid IL or missing references) //IL_00dc: Unknown result type (might be due to invalid IL or missing references) //IL_00e1: Unknown result type (might be due to invalid IL or missing references) //IL_00e3: Unknown result type (might be due to invalid IL or missing references) //IL_017a: Unknown result type (might be due to invalid IL or missing references) //IL_019c: Unknown result type (might be due to invalid IL or missing references) //IL_01a3: Expected O, but got Unknown //IL_0109: Unknown result type (might be due to invalid IL or missing references) //IL_0126: Unknown result type (might be due to invalid IL or missing references) //IL_012c: Expected O, but got Unknown //IL_021d: Unknown result type (might be due to invalid IL or missing references) //IL_021f: Unknown result type (might be due to invalid IL or missing references) //IL_0161: Unknown result type (might be due to invalid IL or missing references) //IL_0167: Unknown result type (might be due to invalid IL or missing references) //IL_0168: Unknown result type (might be due to invalid IL or missing references) //IL_0152: Unknown result type (might be due to invalid IL or missing references) bool flag = default(bool); if (track.PcmData == null || track.PcmData.Length == 0) { ManualLogSource log = Plugin.Log; BepInExErrorLogInterpolatedStringHandler val = new BepInExErrorLogInterpolatedStringHandler(24, 1, ref flag); if (flag) { ((BepInExLogInterpolatedStringHandler)val).AppendLiteral("No PCM data for track '"); ((BepInExLogInterpolatedStringHandler)val).AppendFormatted(track.Name); ((BepInExLogInterpolatedStringHandler)val).AppendLiteral("'"); } log.LogError(val); return default(Sound); } try { System coreSystem = RuntimeManager.CoreSystem; int num = track.PcmData.Length * 4; IntPtr intPtr = Marshal.AllocHGlobal(num); Marshal.Copy(track.PcmData, 0, intPtr, track.PcmData.Length); CREATESOUNDEXINFO val2 = default(CREATESOUNDEXINFO); val2.cbsize = Marshal.SizeOf(); val2.length = (uint)num; val2.numchannels = track.Channels; val2.defaultfrequency = track.SampleRate; val2.format = (SOUND_FORMAT)5; CREATESOUNDEXINFO val3 = val2; MODE val4 = (MODE)530705; Sound result = default(Sound); RESULT val5 = ((System)(ref coreSystem)).createSound(intPtr, val4, ref val3, ref result); if ((int)val5 != 0) { Marshal.FreeHGlobal(intPtr); InitStatus = $"createSound failed: {val5}"; ManualLogSource log2 = Plugin.Log; BepInExErrorLogInterpolatedStringHandler val = new BepInExErrorLogInterpolatedStringHandler(32, 2, ref flag); if (flag) { ((BepInExLogInterpolatedStringHandler)val).AppendLiteral("FMOD createSound failed for '"); ((BepInExLogInterpolatedStringHandler)val).AppendFormatted(track.Name); ((BepInExLogInterpolatedStringHandler)val).AppendLiteral("': "); ((BepInExLogInterpolatedStringHandler)val).AppendFormatted(val5); } log2.LogError(val); return default(Sound); } ((Sound)(ref result)).set3DMinMaxDistance(1f, 20f); track.NativePcmPtr = intPtr; InitStatus = "OK"; ManualLogSource log3 = Plugin.Log; BepInExInfoLogInterpolatedStringHandler val6 = new BepInExInfoLogInterpolatedStringHandler(32, 4, ref flag); if (flag) { ((BepInExLogInterpolatedStringHandler)val6).AppendLiteral("FMOD Sound created: '"); ((BepInExLogInterpolatedStringHandler)val6).AppendFormatted(track.Name); ((BepInExLogInterpolatedStringHandler)val6).AppendLiteral("' "); ((BepInExLogInterpolatedStringHandler)val6).AppendFormatted(track.Channels); ((BepInExLogInterpolatedStringHandler)val6).AppendLiteral("ch "); ((BepInExLogInterpolatedStringHandler)val6).AppendFormatted(track.SampleRate); ((BepInExLogInterpolatedStringHandler)val6).AppendLiteral("Hz "); ((BepInExLogInterpolatedStringHandler)val6).AppendFormatted(track.LengthSamples); ((BepInExLogInterpolatedStringHandler)val6).AppendLiteral("smp"); } log3.LogInfo(val6); return result; } catch (Exception ex) { InitStatus = "exception: " + ex.GetType().Name + ": " + ex.Message; ManualLogSource log4 = Plugin.Log; BepInExErrorLogInterpolatedStringHandler val = new BepInExErrorLogInterpolatedStringHandler(36, 2, ref flag); if (flag) { ((BepInExLogInterpolatedStringHandler)val).AppendLiteral("Failed to create FMOD Sound for '"); ((BepInExLogInterpolatedStringHandler)val).AppendFormatted(track.Name); ((BepInExLogInterpolatedStringHandler)val).AppendLiteral("': "); ((BepInExLogInterpolatedStringHandler)val).AppendFormatted(ex); } log4.LogError(val); return default(Sound); } } } public static class FolderScanner { private static readonly string[] SupportedExtensions = new string[3] { "*.wav", "*.mp3", "*.ogg" }; private const long MaxFileBytes = 20971520L; public static string PluginDirectory => Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location); public static void ScanAndRegister() { //IL_003e: Unknown result type (might be due to invalid IL or missing references) //IL_0044: Expected O, but got Unknown string text = Plugin.MusicFolderName?.Value; if (string.IsNullOrWhiteSpace(text)) { text = "Music"; } string text2 = Path.Combine(PluginDirectory, text); if (!Directory.Exists(text2)) { ManualLogSource log = Plugin.Log; bool flag = default(bool); BepInExInfoLogInterpolatedStringHandler val = new BepInExInfoLogInterpolatedStringHandler(49, 1, ref flag); if (flag) { ((BepInExLogInterpolatedStringHandler)val).AppendLiteral("No Music folder found at "); ((BepInExLogInterpolatedStringHandler)val).AppendFormatted(text2); ((BepInExLogInterpolatedStringHandler)val).AppendLiteral(" — skipping folder scan."); } log.LogInfo(val); return; } string[] directories = Directory.GetDirectories(text2); if (directories.Length != 0) { string[] array = directories; foreach (string obj in array) { string fileName = Path.GetFileName(obj); ScanFolder(obj, "radiolib.folder." + fileName, fileName); } } if (GetAudioFiles(text2).Length != 0) { ScanFolder(text2, "radiolib.folder.custom", "Custom Music"); } } private static string[] GetAudioFiles(string folderPath) { List list = new List(); string[] supportedExtensions = SupportedExtensions; foreach (string searchPattern in supportedExtensions) { list.AddRange(Directory.GetFiles(folderPath, searchPattern)); } string[] array = list.ToArray(); Array.Sort(array, (IComparer?)StringComparer.OrdinalIgnoreCase); return array; } private static void ScanFolder(string folderPath, string stationId, string stationName) { //IL_0199: Unknown result type (might be due to invalid IL or missing references) //IL_01a0: Expected O, but got Unknown //IL_0015: Unknown result type (might be due to invalid IL or missing references) //IL_001b: Expected O, but got Unknown //IL_009e: Unknown result type (might be due to invalid IL or missing references) //IL_00a5: Expected O, but got Unknown //IL_02e0: Unknown result type (might be due to invalid IL or missing references) //IL_02e7: Expected O, but got Unknown //IL_027b: Unknown result type (might be due to invalid IL or missing references) //IL_0281: Expected O, but got Unknown //IL_0130: Unknown result type (might be due to invalid IL or missing references) //IL_0137: Expected O, but got Unknown string[] audioFiles = GetAudioFiles(folderPath); bool flag = default(bool); if (audioFiles.Length == 0) { ManualLogSource log = Plugin.Log; BepInExInfoLogInterpolatedStringHandler val = new BepInExInfoLogInterpolatedStringHandler(31, 1, ref flag); if (flag) { ((BepInExLogInterpolatedStringHandler)val).AppendLiteral("No audio files in '"); ((BepInExLogInterpolatedStringHandler)val).AppendFormatted(folderPath); ((BepInExLogInterpolatedStringHandler)val).AppendLiteral("', skipping."); } log.LogInfo(val); return; } StationDefinition stationDefinition = new StationDefinition { Id = stationId, Name = stationName, Shuffle = false, Loop = true }; int num = 0; string[] array = audioFiles; foreach (string text in array) { FileInfo fileInfo = new FileInfo(text); if (fileInfo.Length > 20971520) { ManualLogSource log2 = Plugin.Log; BepInExWarningLogInterpolatedStringHandler val2 = new BepInExWarningLogInterpolatedStringHandler(32, 3, ref flag); if (flag) { ((BepInExLogInterpolatedStringHandler)val2).AppendLiteral("Skipped '"); ((BepInExLogInterpolatedStringHandler)val2).AppendFormatted(fileInfo.Name); ((BepInExLogInterpolatedStringHandler)val2).AppendLiteral("': "); ((BepInExLogInterpolatedStringHandler)val2).AppendFormatted(fileInfo.Length / 1024 / 1024); ((BepInExLogInterpolatedStringHandler)val2).AppendLiteral(" MB exceeds "); ((BepInExLogInterpolatedStringHandler)val2).AppendFormatted(20L); ((BepInExLogInterpolatedStringHandler)val2).AppendLiteral(" MB cap."); } log2.LogWarning(val2); continue; } ProbeResult probeResult = ProbeAudioFile(text); if (probeResult == null) { ManualLogSource log3 = Plugin.Log; BepInExWarningLogInterpolatedStringHandler val2 = new BepInExWarningLogInterpolatedStringHandler(54, 2, ref flag); if (flag) { ((BepInExLogInterpolatedStringHandler)val2).AppendLiteral("Skipped '"); ((BepInExLogInterpolatedStringHandler)val2).AppendFormatted(Path.GetFileName(text)); ((BepInExLogInterpolatedStringHandler)val2).AppendLiteral("' in station '"); ((BepInExLogInterpolatedStringHandler)val2).AppendFormatted(stationName); ((BepInExLogInterpolatedStringHandler)val2).AppendLiteral("' — probe returned no metadata."); } log3.LogWarning(val2); continue; } string hash; try { hash = ComputeSha256(text); } catch (Exception ex) { ManualLogSource log4 = Plugin.Log; BepInExWarningLogInterpolatedStringHandler val2 = new BepInExWarningLogInterpolatedStringHandler(26, 2, ref flag); if (flag) { ((BepInExLogInterpolatedStringHandler)val2).AppendLiteral("Skipped '"); ((BepInExLogInterpolatedStringHandler)val2).AppendFormatted(Path.GetFileName(text)); ((BepInExLogInterpolatedStringHandler)val2).AppendLiteral("' — hash failed: "); ((BepInExLogInterpolatedStringHandler)val2).AppendFormatted(ex.Message); } log4.LogWarning(val2); continue; } TrackInfo item = new TrackInfo { Name = Path.GetFileNameWithoutExtension(text), PcmData = null, SampleRate = probeResult.SampleRate, Channels = probeResult.Channels, LengthSamples = probeResult.LengthSamples, Hash = hash, Extension = Path.GetExtension(text).ToLowerInvariant(), SourcePath = text }; stationDefinition.Tracks.Add(item); num++; } if (num > 0) { RadioAPI.RegisterStation(stationDefinition); ManualLogSource log5 = Plugin.Log; BepInExInfoLogInterpolatedStringHandler val = new BepInExInfoLogInterpolatedStringHandler(42, 4, ref flag); if (flag) { ((BepInExLogInterpolatedStringHandler)val).AppendLiteral("Folder station '"); ((BepInExLogInterpolatedStringHandler)val).AppendFormatted(stationName); ((BepInExLogInterpolatedStringHandler)val).AppendLiteral("': "); ((BepInExLogInterpolatedStringHandler)val).AppendFormatted(num); ((BepInExLogInterpolatedStringHandler)val).AppendLiteral("/"); ((BepInExLogInterpolatedStringHandler)val).AppendFormatted(audioFiles.Length); ((BepInExLogInterpolatedStringHandler)val).AppendLiteral(" track(s) loaded from "); ((BepInExLogInterpolatedStringHandler)val).AppendFormatted(folderPath); } log5.LogInfo(val); } else { ManualLogSource log6 = Plugin.Log; BepInExErrorLogInterpolatedStringHandler val3 = new BepInExErrorLogInterpolatedStringHandler(96, 2, ref flag); if (flag) { ((BepInExLogInterpolatedStringHandler)val3).AppendLiteral("Station '"); ((BepInExLogInterpolatedStringHandler)val3).AppendFormatted(stationName); ((BepInExLogInterpolatedStringHandler)val3).AppendLiteral("' skipped: "); ((BepInExLogInterpolatedStringHandler)val3).AppendFormatted(audioFiles.Length); ((BepInExLogInterpolatedStringHandler)val3).AppendLiteral(" file(s) found but none decoded. Station will NOT appear in the radio wheel."); } log6.LogError(val3); } } private static ProbeResult ProbeAudioFile(string filePath) { return Path.GetExtension(filePath).ToLowerInvariant() switch { ".wav" => WavLoader.Probe(filePath, Plugin.Log), ".mp3" => Mp3Loader.Probe(filePath, Plugin.Log), ".ogg" => OggLoader.Probe(filePath, Plugin.Log), _ => null, }; } internal static string ComputeSha256(string filePath) { using SHA256 sHA = SHA256.Create(); using FileStream inputStream = File.OpenRead(filePath); return BytesToHex(sHA.ComputeHash(inputStream)); } internal static string ComputeSha256(byte[] data) { using SHA256 sHA = SHA256.Create(); return BytesToHex(sHA.ComputeHash(data)); } internal static string BytesToHex(byte[] bytes) { char[] array = new char[bytes.Length * 2]; for (int i = 0; i < bytes.Length; i++) { array[i * 2] = "0123456789abcdef"[bytes[i] >> 4]; array[i * 2 + 1] = "0123456789abcdef"[bytes[i] & 0xF]; } return new string(array); } } internal static class MainThread { private static readonly ConcurrentQueue _queue = new ConcurrentQueue(); private const int MaxPerFrame = 16; public static void Enqueue(Action action) { if (action != null) { _queue.Enqueue(action); } } internal static void ProcessQueue() { //IL_0028: Unknown result type (might be due to invalid IL or missing references) //IL_002e: Expected O, but got Unknown bool flag = default(bool); for (int i = 0; i < 16; i++) { if (!_queue.TryDequeue(out var result)) { break; } try { result(); } catch (Exception ex) { ManualLogSource log = Plugin.Log; BepInExErrorLogInterpolatedStringHandler val = new BepInExErrorLogInterpolatedStringHandler(21, 1, ref flag); if (flag) { ((BepInExLogInterpolatedStringHandler)val).AppendLiteral("MainThread dispatch: "); ((BepInExLogInterpolatedStringHandler)val).AppendFormatted(ex); } log.LogError(val); } } } } internal static class MemoryBudget { private static long _maxBytes = 209715200L; private static long _totalBytes; private static readonly LinkedList _lruOrder = new LinkedList(); private static readonly Dictionary> _lruNodes = new Dictionary>(StringComparer.OrdinalIgnoreCase); private static readonly Dictionary _bytesPerTrack = new Dictionary(StringComparer.OrdinalIgnoreCase); private static readonly HashSet _pinned = new HashSet(StringComparer.OrdinalIgnoreCase); public static void Configure(long maxBytes) { _maxBytes = maxBytes; } public static void RecordDecode(string hash, long pcmBytes) { if (string.IsNullOrEmpty(hash)) { return; } if (_lruNodes.TryGetValue(hash, out var value)) { if (_bytesPerTrack.TryGetValue(hash, out var value2)) { _totalBytes -= value2; } _lruOrder.Remove(value); _lruNodes.Remove(hash); } LinkedListNode value3 = _lruOrder.AddLast(hash); _lruNodes[hash] = value3; _bytesPerTrack[hash] = pcmBytes; _totalBytes += pcmBytes; EvictIfNeeded(); } public static void RecordAccess(string hash) { if (!string.IsNullOrEmpty(hash) && _lruNodes.TryGetValue(hash, out var value)) { _lruOrder.Remove(value); _lruNodes[hash] = _lruOrder.AddLast(hash); } } public static void Pin(string hash) { if (!string.IsNullOrEmpty(hash)) { _pinned.Add(hash); } } public static void Unpin(string hash) { if (!string.IsNullOrEmpty(hash)) { _pinned.Remove(hash); } } private static void EvictIfNeeded() { //IL_0087: Unknown result type (might be due to invalid IL or missing references) //IL_008e: Expected O, but got Unknown if (_totalBytes <= _maxBytes) { return; } LinkedListNode linkedListNode = _lruOrder.First; bool flag = default(bool); while (linkedListNode != null && _totalBytes > _maxBytes) { LinkedListNode next = linkedListNode.Next; string value = linkedListNode.Value; if (_pinned.Contains(value)) { linkedListNode = next; continue; } TrackInfo trackInfo = RadioAPI.TryGetTrackByHash(value); if (trackInfo != null) { trackInfo.Unload(); if (_bytesPerTrack.TryGetValue(value, out var value2)) { _totalBytes -= value2; _bytesPerTrack.Remove(value); ManualLogSource log = Plugin.Log; BepInExInfoLogInterpolatedStringHandler val = new BepInExInfoLogInterpolatedStringHandler(31, 4, ref flag); if (flag) { ((BepInExLogInterpolatedStringHandler)val).AppendLiteral("Evicted '"); ((BepInExLogInterpolatedStringHandler)val).AppendFormatted(trackInfo.Name); ((BepInExLogInterpolatedStringHandler)val).AppendLiteral("' ("); ((BepInExLogInterpolatedStringHandler)val).AppendFormatted(value2 / 1024 / 1024); ((BepInExLogInterpolatedStringHandler)val).AppendLiteral(" MB) — budget: "); ((BepInExLogInterpolatedStringHandler)val).AppendFormatted(_totalBytes / 1024 / 1024); ((BepInExLogInterpolatedStringHandler)val).AppendLiteral("/"); ((BepInExLogInterpolatedStringHandler)val).AppendFormatted(_maxBytes / 1024 / 1024); ((BepInExLogInterpolatedStringHandler)val).AppendLiteral(" MB"); } log.LogInfo(val); } } _lruOrder.Remove(linkedListNode); _lruNodes.Remove(value); linkedListNode = next; } } } public static class Mp3Loader { public static ProbeResult Probe(string filePath, ManualLogSource log = null) { //IL_0008: Unknown result type (might be due to invalid IL or missing references) //IL_000d: Unknown result type (might be due to invalid IL or missing references) //IL_0014: 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) //IL_006b: Expected O, but got Unknown try { using FileStream fileStream = File.OpenRead(filePath); MpegFile val = new MpegFile((Stream)fileStream); int sampleRate = val.SampleRate; int channels = val.Channels; int lengthSamples = (int)(val.Duration.TotalSeconds * (double)sampleRate); return new ProbeResult { SampleRate = sampleRate, Channels = channels, LengthSamples = lengthSamples }; } catch (Exception ex) { if (log != null) { bool flag = default(bool); BepInExWarningLogInterpolatedStringHandler val2 = new BepInExWarningLogInterpolatedStringHandler(25, 2, ref flag); if (flag) { ((BepInExLogInterpolatedStringHandler)val2).AppendLiteral("MP3 probe failed for '"); ((BepInExLogInterpolatedStringHandler)val2).AppendFormatted(filePath); ((BepInExLogInterpolatedStringHandler)val2).AppendLiteral("': "); ((BepInExLogInterpolatedStringHandler)val2).AppendFormatted(ex.Message); } log.LogWarning(val2); } return null; } } public static WavData Load(string filePath, ManualLogSource log = null, CancellationToken ct = default(CancellationToken)) { //IL_0008: Unknown result type (might be due to invalid IL or missing references) //IL_000e: Expected O, but got Unknown //IL_0141: Unknown result type (might be due to invalid IL or missing references) //IL_0148: Expected O, but got Unknown //IL_009d: Unknown result type (might be due to invalid IL or missing references) //IL_00a4: Expected O, but got Unknown bool flag = default(bool); try { using FileStream fileStream = File.OpenRead(filePath); MpegFile val = new MpegFile((Stream)fileStream); int sampleRate = val.SampleRate; int channels = val.Channels; List list = new List(); float[] array = new float[4096]; int num = 0; int num2; while ((num2 = val.ReadSamples(array, 0, array.Length)) > 0) { for (int i = 0; i < num2; i++) { list.Add(array[i]); } if (++num % 4 == 0 && ct.IsCancellationRequested) { return null; } } float[] array2 = list.ToArray(); int num3 = array2.Length / channels; ManualLogSource val2 = log; if (val2 != null) { BepInExInfoLogInterpolatedStringHandler val3 = new BepInExInfoLogInterpolatedStringHandler(24, 4, ref flag); if (flag) { ((BepInExLogInterpolatedStringHandler)val3).AppendLiteral("Loaded MP3: "); ((BepInExLogInterpolatedStringHandler)val3).AppendFormatted(Path.GetFileName(filePath)); ((BepInExLogInterpolatedStringHandler)val3).AppendLiteral(" — "); ((BepInExLogInterpolatedStringHandler)val3).AppendFormatted(channels); ((BepInExLogInterpolatedStringHandler)val3).AppendLiteral("ch, "); ((BepInExLogInterpolatedStringHandler)val3).AppendFormatted(sampleRate); ((BepInExLogInterpolatedStringHandler)val3).AppendLiteral("Hz, "); ((BepInExLogInterpolatedStringHandler)val3).AppendFormatted((float)num3 / (float)sampleRate, "F1"); ((BepInExLogInterpolatedStringHandler)val3).AppendLiteral("s"); } val2.LogInfo(val3); } return new WavData(array2, sampleRate, channels, num3); } catch (Exception ex) { ManualLogSource val2 = log; if (val2 != null) { BepInExErrorLogInterpolatedStringHandler val4 = new BepInExErrorLogInterpolatedStringHandler(25, 2, ref flag); if (flag) { ((BepInExLogInterpolatedStringHandler)val4).AppendLiteral("Failed to decode MP3 '"); ((BepInExLogInterpolatedStringHandler)val4).AppendFormatted(filePath); ((BepInExLogInterpolatedStringHandler)val4).AppendLiteral("': "); ((BepInExLogInterpolatedStringHandler)val4).AppendFormatted(ex.Message); } val2.LogError(val4); } return null; } } } public static class OggLoader { public static ProbeResult Probe(string filePath, ManualLogSource log = null) { //IL_0001: Unknown result type (might be due to invalid IL or missing references) //IL_0007: Expected O, but got Unknown //IL_0053: Unknown result type (might be due to invalid IL or missing references) //IL_005a: Expected O, but got Unknown try { VorbisReader val = new VorbisReader(filePath); try { int sampleRate = val.SampleRate; int channels = val.Channels; int lengthSamples = (int)val.TotalSamples; return new ProbeResult { SampleRate = sampleRate, Channels = channels, LengthSamples = lengthSamples }; } finally { ((IDisposable)val)?.Dispose(); } } catch (Exception ex) { if (log != null) { bool flag = default(bool); BepInExWarningLogInterpolatedStringHandler val2 = new BepInExWarningLogInterpolatedStringHandler(25, 2, ref flag); if (flag) { ((BepInExLogInterpolatedStringHandler)val2).AppendLiteral("OGG probe failed for '"); ((BepInExLogInterpolatedStringHandler)val2).AppendFormatted(filePath); ((BepInExLogInterpolatedStringHandler)val2).AppendLiteral("': "); ((BepInExLogInterpolatedStringHandler)val2).AppendFormatted(ex.Message); } log.LogWarning(val2); } return null; } } public static WavData Load(string filePath, ManualLogSource log = null, CancellationToken ct = default(CancellationToken)) { //IL_0001: Unknown result type (might be due to invalid IL or missing references) //IL_0007: Expected O, but got Unknown //IL_0137: Unknown result type (might be due to invalid IL or missing references) //IL_013e: Expected O, but got Unknown //IL_0093: Unknown result type (might be due to invalid IL or missing references) //IL_009a: Expected O, but got Unknown bool flag = default(bool); try { VorbisReader val = new VorbisReader(filePath); try { int sampleRate = val.SampleRate; int channels = val.Channels; List list = new List(); float[] array = new float[4096]; int num = 0; int num2; while ((num2 = val.ReadSamples(array, 0, array.Length)) > 0) { for (int i = 0; i < num2; i++) { list.Add(array[i]); } if (++num % 4 == 0 && ct.IsCancellationRequested) { return null; } } float[] array2 = list.ToArray(); int num3 = array2.Length / channels; ManualLogSource val2 = log; if (val2 != null) { BepInExInfoLogInterpolatedStringHandler val3 = new BepInExInfoLogInterpolatedStringHandler(24, 4, ref flag); if (flag) { ((BepInExLogInterpolatedStringHandler)val3).AppendLiteral("Loaded OGG: "); ((BepInExLogInterpolatedStringHandler)val3).AppendFormatted(Path.GetFileName(filePath)); ((BepInExLogInterpolatedStringHandler)val3).AppendLiteral(" — "); ((BepInExLogInterpolatedStringHandler)val3).AppendFormatted(channels); ((BepInExLogInterpolatedStringHandler)val3).AppendLiteral("ch, "); ((BepInExLogInterpolatedStringHandler)val3).AppendFormatted(sampleRate); ((BepInExLogInterpolatedStringHandler)val3).AppendLiteral("Hz, "); ((BepInExLogInterpolatedStringHandler)val3).AppendFormatted((float)num3 / (float)sampleRate, "F1"); ((BepInExLogInterpolatedStringHandler)val3).AppendLiteral("s"); } val2.LogInfo(val3); } return new WavData(array2, sampleRate, channels, num3); } finally { ((IDisposable)val)?.Dispose(); } } catch (Exception ex) { ManualLogSource val2 = log; if (val2 != null) { BepInExErrorLogInterpolatedStringHandler val4 = new BepInExErrorLogInterpolatedStringHandler(25, 2, ref flag); if (flag) { ((BepInExLogInterpolatedStringHandler)val4).AppendLiteral("Failed to decode OGG '"); ((BepInExLogInterpolatedStringHandler)val4).AppendFormatted(filePath); ((BepInExLogInterpolatedStringHandler)val4).AppendLiteral("': "); ((BepInExLogInterpolatedStringHandler)val4).AppendFormatted(ex.Message); } val2.LogError(val4); } return null; } } } [BepInPlugin("com.radiolib.roadsideresearch", "RadioLib", "1.1.0")] public class Plugin : BasePlugin { public const string GUID = "com.radiolib.roadsideresearch"; public const string Name = "RadioLib"; public const string Version = "1.1.0"; internal static ManualLogSource Log; internal static ConfigEntry Enabled; internal static ConfigEntry MusicFolderName; internal static ConfigEntry VerboseLogging; internal static ConfigEntry MemoryBudgetMB; internal static ConfigEntry CacheLimitMB; public override void Load() { //IL_0139: Unknown result type (might be due to invalid IL or missing references) //IL_013e: Unknown result type (might be due to invalid IL or missing references) //IL_0167: Unknown result type (might be due to invalid IL or missing references) //IL_016d: Expected O, but got Unknown //IL_00cd: Unknown result type (might be due to invalid IL or missing references) //IL_00d3: Expected O, but got Unknown Log = ((BasePlugin)this).Log; Enabled = ((BasePlugin)this).Config.Bind("General", "Enabled", true, "Master toggle. When off, no local stations are scanned and no sync patches are applied."); MusicFolderName = ((BasePlugin)this).Config.Bind("General", "MusicFolderName", "Music", "Folder name (relative to this plugin's directory) where custom music is loaded from. Subfolders become separate stations; files placed directly in this folder are grouped under the \"Custom Music\" station."); VerboseLogging = ((BasePlugin)this).Config.Bind("General", "VerboseLogging", false, "Emit extra log lines during folder scanning and network transfer. Leave off unless troubleshooting."); MemoryBudgetMB = ((BasePlugin)this).Config.Bind("Performance", "MemoryBudgetMB", 200, "Maximum megabytes of decoded PCM audio to keep in RAM. Tracks beyond this limit are evicted (least recently used first) and re-decoded on demand."); CacheLimitMB = ((BasePlugin)this).Config.Bind("Performance", "CacheLimitMB", 500, "Maximum megabytes of cached audio files on disk. Oldest files are deleted when the limit is exceeded."); bool flag = default(bool); BepInExInfoLogInterpolatedStringHandler val; if (!Enabled.Value) { ManualLogSource log = Log; val = new BepInExInfoLogInterpolatedStringHandler(48, 1, ref flag); if (flag) { ((BepInExLogInterpolatedStringHandler)val).AppendFormatted("RadioLib"); ((BepInExLogInterpolatedStringHandler)val).AppendLiteral(" disabled via config; skipping scan and patches."); } log.LogInfo(val); return; } MemoryBudget.Configure((long)MemoryBudgetMB.Value * 1024L * 1024); AudioCache.EvictOldEntries((long)CacheLimitMB.Value * 1024L * 1024); FolderScanner.ScanAndRegister(); Harmony val2 = new Harmony("com.radiolib.roadsideresearch"); val2.PatchAll(typeof(RadioPatches)); val2.PatchAll(typeof(RadioLibNet)); ManualLogSource log2 = Log; val = new BepInExInfoLogInterpolatedStringHandler(47, 3, ref flag); if (flag) { ((BepInExLogInterpolatedStringHandler)val).AppendFormatted("RadioLib"); ((BepInExLogInterpolatedStringHandler)val).AppendLiteral(" v"); ((BepInExLogInterpolatedStringHandler)val).AppendFormatted("1.1.0"); ((BepInExLogInterpolatedStringHandler)val).AppendLiteral(" loaded! "); ((BepInExLogInterpolatedStringHandler)val).AppendFormatted(RadioAPI.GetStations().Count); ((BepInExLogInterpolatedStringHandler)val).AppendLiteral(" local custom station(s) registered."); } log2.LogInfo(val); } } public static class RadioAPI { private static readonly List _stations = new List(); private static readonly Dictionary _tracksByHash = new Dictionary(StringComparer.OrdinalIgnoreCase); internal static int _vanillaStationCount = -1; public static int TotalStationCount => ((_vanillaStationCount >= 0) ? _vanillaStationCount : 0) + _stations.Count; public static int VanillaStationCount { get { if (_vanillaStationCount >= 0) { return _vanillaStationCount; } return 0; } } public static event Action TrackReady; public static void RegisterStation(StationDefinition station) { //IL_0041: Unknown result type (might be due to invalid IL or missing references) //IL_0047: Expected O, but got Unknown //IL_00f6: Unknown result type (might be due to invalid IL or missing references) //IL_00fd: Expected O, but got Unknown if (station == null || string.IsNullOrEmpty(station.Id)) { return; } bool flag = default(bool); foreach (StationDefinition station2 in _stations) { if (station2.Id == station.Id) { ManualLogSource log = Plugin.Log; BepInExWarningLogInterpolatedStringHandler val = new BepInExWarningLogInterpolatedStringHandler(40, 1, ref flag); if (flag) { ((BepInExLogInterpolatedStringHandler)val).AppendLiteral("Station '"); ((BepInExLogInterpolatedStringHandler)val).AppendFormatted(station.Id); ((BepInExLogInterpolatedStringHandler)val).AppendLiteral("' already registered, skipping."); } log.LogWarning(val); return; } } _stations.Add(station); foreach (TrackInfo track in station.Tracks) { if (!string.IsNullOrEmpty(track.Hash)) { _tracksByHash[track.Hash] = track; } } ManualLogSource log2 = Plugin.Log; BepInExInfoLogInterpolatedStringHandler val2 = new BepInExInfoLogInterpolatedStringHandler(77, 2, ref flag); if (flag) { ((BepInExLogInterpolatedStringHandler)val2).AppendLiteral("Registered station '"); ((BepInExLogInterpolatedStringHandler)val2).AppendFormatted(station.Name); ((BepInExLogInterpolatedStringHandler)val2).AppendLiteral("' with "); ((BepInExLogInterpolatedStringHandler)val2).AppendFormatted(station.Tracks.Count); ((BepInExLogInterpolatedStringHandler)val2).AppendLiteral(" track(s). FMOD sounds deferred to first playback."); } log2.LogInfo(val2); } public static void RegisterStation(string id, string name, float[] pcmData, int sampleRate, int channels) { int lengthSamples = pcmData.Length / channels; TrackInfo item = new TrackInfo { Name = name, PcmData = pcmData, SampleRate = sampleRate, Channels = channels, LengthSamples = lengthSamples }; RegisterStation(new StationDefinition { Id = id, Name = name, Tracks = { item } }); } public static IReadOnlyList GetStations() { return _stations; } public static bool IsCustomStation(int stationIndex) { if (_vanillaStationCount >= 0) { return stationIndex >= _vanillaStationCount; } return false; } public static StationDefinition GetCustomStation(int stationIndex) { if (!IsCustomStation(stationIndex)) { return null; } int num = stationIndex - _vanillaStationCount; if (num < 0 || num >= _stations.Count) { return null; } return _stations[num]; } public static int IndexOfStation(StationDefinition station) { int num = _stations.IndexOf(station); if (num < 0) { return -1; } return ((_vanillaStationCount >= 0) ? _vanillaStationCount : 0) + num; } internal static void ReplaceStationsFromManifest(IList newStations) { //IL_00fd: Unknown result type (might be due to invalid IL or missing references) //IL_0104: Expected O, but got Unknown foreach (StationDefinition station in _stations) { foreach (TrackInfo track in station.Tracks) { track.ReleaseNativeData(); } } _stations.Clear(); _tracksByHash.Clear(); foreach (StationDefinition newStation in newStations) { _stations.Add(newStation); foreach (TrackInfo track2 in newStation.Tracks) { if (!string.IsNullOrEmpty(track2.Hash)) { _tracksByHash[track2.Hash] = track2; } } } ManualLogSource log = Plugin.Log; bool flag = default(bool); BepInExInfoLogInterpolatedStringHandler val = new BepInExInfoLogInterpolatedStringHandler(30, 1, ref flag); if (flag) { ((BepInExLogInterpolatedStringHandler)val).AppendLiteral("Manifest applied: "); ((BepInExLogInterpolatedStringHandler)val).AppendFormatted(_stations.Count); ((BepInExLogInterpolatedStringHandler)val).AppendLiteral(" station(s)."); } log.LogInfo(val); } internal static TrackInfo TryGetTrackByHash(string hash) { if (string.IsNullOrEmpty(hash)) { return null; } _tracksByHash.TryGetValue(hash, out var value); return value; } internal static void MarkTrackReady(string hash, float[] pcm, int sampleRate, int channels) { //IL_005c: Unknown result type (might be due to invalid IL or missing references) //IL_0062: Expected O, but got Unknown TrackInfo trackInfo = TryGetTrackByHash(hash); if (trackInfo == null) { return; } trackInfo.PcmData = pcm; trackInfo.SampleRate = sampleRate; trackInfo.Channels = channels; trackInfo.LengthSamples = pcm.Length / Math.Max(1, channels); MemoryBudget.RecordDecode(hash, (long)pcm.Length * 4L); try { RadioAPI.TrackReady?.Invoke(trackInfo); } catch (Exception ex) { ManualLogSource log = Plugin.Log; bool flag = default(bool); BepInExErrorLogInterpolatedStringHandler val = new BepInExErrorLogInterpolatedStringHandler(26, 1, ref flag); if (flag) { ((BepInExLogInterpolatedStringHandler)val).AppendLiteral("TrackReady handler threw: "); ((BepInExLogInterpolatedStringHandler)val).AppendFormatted(ex); } log.LogError(val); } } internal static IEnumerable AllStations() { return _stations; } } public class RadioController { public struct PlaybackSnapshot { public uint RadioId; public int StationIndex; public int TrackIndex; public float ElapsedSeconds; } private static readonly Dictionary _controllers = new Dictionary(); private static readonly Dictionary _byNetworkId = new Dictionary(); private static bool _trackReadySubscribed; private Channel _channel; private Transform _transform; private Radio _radio; private uint _radioId; private int _currentCustomIndex = -1; private int _currentTrackIndex; private bool _isPlaying; private StationDefinition _currentStation; private Random _rng = new Random(); private float _volume = 0.5f; private CancellationTokenSource _decodeCts; private int _nextTrackIndex = -1; private bool _prefetchSent; private int _pendingTrackIndex = -1; private float _seekAfterReady; private static bool IsFollower(StationDefinition station) { if (station != null && station.RemoteStation) { return !RadioLibNet.IsHost; } return false; } public static RadioController Create(GameObject radioObject) { EnsureTrackReadyHook(); RadioController radioController = new RadioController(); radioController._transform = radioObject.transform; radioController._radio = radioObject.GetComponent(); radioController._radioId = TryReadNetworkIdRaw(radioController._radio); _controllers[((Il2CppObjectBase)radioObject).Pointer] = radioController; if (radioController._radioId != 0) { _byNetworkId[radioController._radioId] = radioController; } return radioController; } public static RadioController Get(GameObject radioObject) { _controllers.TryGetValue(((Il2CppObjectBase)radioObject).Pointer, out var value); return value; } public static void Remove(GameObject radioObject) { if (_controllers.TryGetValue(((Il2CppObjectBase)radioObject).Pointer, out var value)) { if (value._radioId != 0) { _byNetworkId.Remove(value._radioId); } value.Cleanup(); _controllers.Remove(((Il2CppObjectBase)radioObject).Pointer); } } private static uint TryReadNetworkIdRaw(Radio radio) { //IL_0021: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)radio == (Object)null) { return 0u; } try { NetworkObject @object = ((SimulationBehaviour)radio).Object; if ((Object)(object)@object == (Object)null) { return 0u; } return @object.Id.Raw; } catch { return 0u; } } public void PlayStation(int customIndex) { //IL_0021: Unknown result type (might be due to invalid IL or missing references) //IL_0027: Expected O, but got Unknown //IL_0075: Unknown result type (might be due to invalid IL or missing references) //IL_007b: Expected O, but got Unknown StationDefinition customStation = RadioAPI.GetCustomStation(customIndex); bool flag = default(bool); if (customStation == null || customStation.Tracks.Count == 0) { ManualLogSource log = Plugin.Log; BepInExWarningLogInterpolatedStringHandler val = new BepInExWarningLogInterpolatedStringHandler(47, 1, ref flag); if (flag) { ((BepInExLogInterpolatedStringHandler)val).AppendLiteral("PlayStation("); ((BepInExLogInterpolatedStringHandler)val).AppendFormatted(customIndex); ((BepInExLogInterpolatedStringHandler)val).AppendLiteral("): station is null or has no tracks"); } log.LogWarning(val); Stop(); return; } if (IsFollower(customStation)) { _currentCustomIndex = customIndex; _currentStation = customStation; return; } ManualLogSource log2 = Plugin.Log; BepInExInfoLogInterpolatedStringHandler val2 = new BepInExInfoLogInterpolatedStringHandler(40, 3, ref flag); if (flag) { ((BepInExLogInterpolatedStringHandler)val2).AppendLiteral("PlayStation("); ((BepInExLogInterpolatedStringHandler)val2).AppendFormatted(customIndex); ((BepInExLogInterpolatedStringHandler)val2).AppendLiteral("): playing '"); ((BepInExLogInterpolatedStringHandler)val2).AppendFormatted(customStation.Name); ((BepInExLogInterpolatedStringHandler)val2).AppendLiteral("' with "); ((BepInExLogInterpolatedStringHandler)val2).AppendFormatted(customStation.Tracks.Count); ((BepInExLogInterpolatedStringHandler)val2).AppendLiteral(" track(s)"); } log2.LogInfo(val2); _decodeCts?.Cancel(); _decodeCts = null; _nextTrackIndex = -1; _prefetchSent = false; _currentCustomIndex = customIndex; _currentStation = customStation; _currentTrackIndex = (customStation.Shuffle ? _rng.Next(customStation.Tracks.Count) : 0); PlayCurrentTrack(); BroadcastTrackSelect(); } public void Stop() { _decodeCts?.Cancel(); _decodeCts = null; _isPlaying = false; _currentCustomIndex = -1; _currentStation = null; _pendingTrackIndex = -1; _nextTrackIndex = -1; _prefetchSent = false; StopChannel(); } public void SetVolume(float volume) { //IL_001b: Unknown result type (might be due to invalid IL or missing references) _volume = volume; if (((Channel)(ref _channel)).hasHandle()) { ((Channel)(ref _channel)).setVolume(volume); } } public void CheckTrackAdvance() { //IL_002c: Unknown result type (might be due to invalid IL or missing references) if (!_isPlaying || _currentStation == null) { return; } Update3DPosition(); if (!((Channel)(ref _channel)).hasHandle()) { return; } bool flag = default(bool); ((Channel)(ref _channel)).isPlaying(ref flag); if (flag) { if (!IsFollower(_currentStation) && !_prefetchSent) { TryPrefetchNext(); } } else if (!IsFollower(_currentStation)) { _prefetchSent = false; _nextTrackIndex = -1; AdvanceTrack(); } } private void TryPrefetchNext() { //IL_0025: Unknown result type (might be due to invalid IL or missing references) if (_currentStation == null || _currentStation.Tracks.Count <= 1) { return; } uint num = default(uint); ((Channel)(ref _channel)).getPosition(ref num, (TIMEUNIT)1); TrackInfo trackInfo = _currentStation.Tracks[_currentTrackIndex]; if ((float)trackInfo.LengthSamples / (float)Math.Max(1, trackInfo.SampleRate) * 1000f - (float)num > 30000f) { return; } int num2 = PeekNextTrack(); if (num2 >= 0 && num2 < _currentStation.Tracks.Count) { TrackInfo trackInfo2 = _currentStation.Tracks[num2]; if (!trackInfo2.Ready && !string.IsNullOrEmpty(trackInfo2.SourcePath)) { DecodeService.Request(trackInfo2, trackInfo2.SourcePath, DecodePriority.Prefetch); } if (RadioLibNet.IsHost && !string.IsNullOrEmpty(trackInfo2.Hash)) { RadioLibNet.BroadcastToClients(MessageCodec.BuildPrefetchHint(trackInfo2.Hash)); } _prefetchSent = true; } } private int PeekNextTrack() { if (_nextTrackIndex >= 0) { return _nextTrackIndex; } if (_currentStation == null || _currentStation.Tracks.Count == 0) { return -1; } if (_currentStation.Shuffle && _currentStation.Tracks.Count > 1) { do { _nextTrackIndex = _rng.Next(_currentStation.Tracks.Count); } while (_nextTrackIndex == _currentTrackIndex); } else { _nextTrackIndex = _currentTrackIndex + 1; if (_nextTrackIndex >= _currentStation.Tracks.Count) { _nextTrackIndex = ((!_currentStation.Loop) ? (-1) : 0); } } return _nextTrackIndex; } private void Update3DPosition() { //IL_0022: Unknown result type (might be due to invalid IL or missing references) //IL_0027: Unknown result type (might be due to invalid IL or missing references) //IL_002a: Unknown result type (might be due to invalid IL or missing references) //IL_0032: Unknown result type (might be due to invalid IL or missing references) //IL_003f: Unknown result type (might be due to invalid IL or missing references) //IL_004c: Unknown result type (might be due to invalid IL or missing references) //IL_0057: Unknown result type (might be due to invalid IL or missing references) //IL_0058: Unknown result type (might be due to invalid IL or missing references) //IL_005b: Unknown result type (might be due to invalid IL or missing references) //IL_0085: Unknown result type (might be due to invalid IL or missing references) //IL_0086: Unknown result type (might be due to invalid IL or missing references) //IL_0091: Unknown result type (might be due to invalid IL or missing references) if (((Channel)(ref _channel)).hasHandle() && !((Object)(object)_transform == (Object)null)) { Vector3 position = _transform.position; VECTOR val = default(VECTOR); val.x = position.x; val.y = position.y; val.z = position.z; VECTOR val2 = val; val = default(VECTOR); val.x = 0f; val.y = 0f; val.z = 0f; VECTOR val3 = val; ((Channel)(ref _channel)).set3DAttributes(ref val2, ref val3); } } private void PlayCurrentTrack() { //IL_00c8: Unknown result type (might be due to invalid IL or missing references) //IL_00cd: Unknown result type (might be due to invalid IL or missing references) //IL_0193: Unknown result type (might be due to invalid IL or missing references) //IL_0198: Unknown result type (might be due to invalid IL or missing references) //IL_019c: Unknown result type (might be due to invalid IL or missing references) //IL_01a3: Unknown result type (might be due to invalid IL or missing references) //IL_01a9: Unknown result type (might be due to invalid IL or missing references) //IL_01b2: Unknown result type (might be due to invalid IL or missing references) //IL_01b7: Unknown result type (might be due to invalid IL or missing references) //IL_01b8: Unknown result type (might be due to invalid IL or missing references) //IL_00e5: Unknown result type (might be due to invalid IL or missing references) //IL_00ec: Expected O, but got Unknown //IL_008f: Unknown result type (might be due to invalid IL or missing references) //IL_0096: Expected O, but got Unknown //IL_0217: Unknown result type (might be due to invalid IL or missing references) //IL_022d: Unknown result type (might be due to invalid IL or missing references) //IL_0267: Unknown result type (might be due to invalid IL or missing references) //IL_026e: Expected O, but got Unknown //IL_01c5: Unknown result type (might be due to invalid IL or missing references) //IL_01cc: Expected O, but got Unknown //IL_011e: Unknown result type (might be due to invalid IL or missing references) //IL_0129: Unknown result type (might be due to invalid IL or missing references) //IL_012e: Unknown result type (might be due to invalid IL or missing references) //IL_01f7: Unknown result type (might be due to invalid IL or missing references) //IL_0143: Unknown result type (might be due to invalid IL or missing references) //IL_014a: Expected O, but got Unknown if (_currentStation == null) { return; } List tracks = _currentStation.Tracks; if (_currentTrackIndex < 0 || _currentTrackIndex >= tracks.Count) { Stop(); return; } TrackInfo trackInfo = tracks[_currentTrackIndex]; bool flag = default(bool); BepInExInfoLogInterpolatedStringHandler val; if (!trackInfo.Ready) { _pendingTrackIndex = _currentTrackIndex; if (!string.IsNullOrEmpty(trackInfo.SourcePath)) { _decodeCts?.Cancel(); _decodeCts = DecodeService.Request(trackInfo, trackInfo.SourcePath, DecodePriority.Immediate); } ManualLogSource log = Plugin.Log; val = new BepInExInfoLogInterpolatedStringHandler(35, 1, ref flag); if (flag) { ((BepInExLogInterpolatedStringHandler)val).AppendLiteral("Deferring play of '"); ((BepInExLogInterpolatedStringHandler)val).AppendFormatted(trackInfo.Name); ((BepInExLogInterpolatedStringHandler)val).AppendLiteral("' until decoded."); } log.LogInfo(val); return; } Sound fmodSound = trackInfo.FmodSound; if (!((Sound)(ref fmodSound)).hasHandle()) { ManualLogSource log2 = Plugin.Log; val = new BepInExInfoLogInterpolatedStringHandler(34, 1, ref flag); if (flag) { ((BepInExLogInterpolatedStringHandler)val).AppendLiteral("Lazy-creating FMOD sound for '"); ((BepInExLogInterpolatedStringHandler)val).AppendFormatted(trackInfo.Name); ((BepInExLogInterpolatedStringHandler)val).AppendLiteral("'..."); } log2.LogInfo(val); trackInfo.FmodSound = FmodSoundFactory.CreateSound(trackInfo); fmodSound = trackInfo.FmodSound; if (!((Sound)(ref fmodSound)).hasHandle()) { ManualLogSource log3 = Plugin.Log; BepInExWarningLogInterpolatedStringHandler val2 = new BepInExWarningLogInterpolatedStringHandler(34, 2, ref flag); if (flag) { ((BepInExLogInterpolatedStringHandler)val2).AppendLiteral("Track '"); ((BepInExLogInterpolatedStringHandler)val2).AppendFormatted(trackInfo.Name); ((BepInExLogInterpolatedStringHandler)val2).AppendLiteral("' has no FMOD sound. DIAG: "); ((BepInExLogInterpolatedStringHandler)val2).AppendFormatted(FmodSoundFactory.InitStatus); } log3.LogWarning(val2); Stop(); return; } } StopChannel(); System coreSystem = RuntimeManager.CoreSystem; RESULT val3 = ((System)(ref coreSystem)).playSound(trackInfo.FmodSound, default(ChannelGroup), false, ref _channel); if ((int)val3 != 0) { ManualLogSource log4 = Plugin.Log; BepInExErrorLogInterpolatedStringHandler val4 = new BepInExErrorLogInterpolatedStringHandler(30, 2, ref flag); if (flag) { ((BepInExLogInterpolatedStringHandler)val4).AppendLiteral("FMOD playSound failed for '"); ((BepInExLogInterpolatedStringHandler)val4).AppendFormatted(trackInfo.Name); ((BepInExLogInterpolatedStringHandler)val4).AppendLiteral("': "); ((BepInExLogInterpolatedStringHandler)val4).AppendFormatted(val3); } log4.LogError(val4); Stop(); return; } ((Channel)(ref _channel)).setVolume(_volume); ((Channel)(ref _channel)).set3DMinMaxDistance(1f, 20f); Update3DPosition(); _isPlaying = true; _pendingTrackIndex = -1; MemoryBudget.RecordAccess(trackInfo.Hash); MemoryBudget.Pin(trackInfo.Hash); ManualLogSource log5 = Plugin.Log; val = new BepInExInfoLogInterpolatedStringHandler(20, 1, ref flag); if (flag) { ((BepInExLogInterpolatedStringHandler)val).AppendLiteral("Now playing track '"); ((BepInExLogInterpolatedStringHandler)val).AppendFormatted(trackInfo.Name); ((BepInExLogInterpolatedStringHandler)val).AppendLiteral("'"); } log5.LogInfo(val); } private void AdvanceTrack() { if (_currentStation == null) { return; } List tracks = _currentStation.Tracks; if (tracks.Count == 0) { Stop(); return; } if (_nextTrackIndex >= 0 && _nextTrackIndex < tracks.Count) { _currentTrackIndex = _nextTrackIndex; } else if (_currentStation.Shuffle && tracks.Count > 1) { int num; do { num = _rng.Next(tracks.Count); } while (num == _currentTrackIndex); _currentTrackIndex = num; } else if (!_currentStation.Shuffle) { _currentTrackIndex++; } _nextTrackIndex = -1; _prefetchSent = false; if (_currentTrackIndex >= tracks.Count) { if (!_currentStation.Loop) { Stop(); return; } _currentTrackIndex = 0; } _decodeCts?.Cancel(); _decodeCts = null; PlayCurrentTrack(); BroadcastTrackSelect(); } private float GetCurrentTrackElapsed() { //IL_001c: Unknown result type (might be due to invalid IL or missing references) if (!((Channel)(ref _channel)).hasHandle()) { return 0f; } uint num = default(uint); ((Channel)(ref _channel)).getPosition(ref num, (TIMEUNIT)1); return (float)num / 1000f; } private void BroadcastTrackSelect() { if (RadioLibNet.IsHost && _currentStation != null && _radioId != 0) { int num = RadioAPI.IndexOfStation(_currentStation); if (num >= 0) { float currentTrackElapsed = GetCurrentTrackElapsed(); RadioLibNet.BroadcastToClients(MessageCodec.BuildTrackSelect(_radioId, num, _currentTrackIndex, currentTrackElapsed)); } } } public static void ApplyRemoteTrackSelect(uint radioId, int stationIndex, int trackIndex, float elapsedSeconds = 0f) { //IL_0019: Unknown result type (might be due to invalid IL or missing references) //IL_001f: Expected O, but got Unknown if (!_byNetworkId.TryGetValue(radioId, out var value)) { ManualLogSource log = Plugin.Log; bool flag = default(bool); BepInExWarningLogInterpolatedStringHandler val = new BepInExWarningLogInterpolatedStringHandler(61, 1, ref flag); if (flag) { ((BepInExLogInterpolatedStringHandler)val).AppendLiteral("ApplyRemoteTrackSelect: no radio with id="); ((BepInExLogInterpolatedStringHandler)val).AppendFormatted(radioId); ((BepInExLogInterpolatedStringHandler)val).AppendLiteral(" registered locally."); } log.LogWarning(val); } else { value.ApplyRemoteTrackSelectInternal(stationIndex, trackIndex, elapsedSeconds); } } private void ApplyRemoteTrackSelectInternal(int stationIndex, int trackIndex, float elapsedSeconds) { //IL_0014: Unknown result type (might be due to invalid IL or missing references) //IL_001a: Expected O, but got Unknown //IL_00b7: Unknown result type (might be due to invalid IL or missing references) StationDefinition customStation = RadioAPI.GetCustomStation(stationIndex); if (customStation == null) { ManualLogSource log = Plugin.Log; bool flag = default(bool); BepInExWarningLogInterpolatedStringHandler val = new BepInExWarningLogInterpolatedStringHandler(49, 1, ref flag); if (flag) { ((BepInExLogInterpolatedStringHandler)val).AppendLiteral("ApplyRemoteTrackSelect: station "); ((BepInExLogInterpolatedStringHandler)val).AppendFormatted(stationIndex); ((BepInExLogInterpolatedStringHandler)val).AppendLiteral(" missing locally."); } log.LogWarning(val); return; } _decodeCts?.Cancel(); _decodeCts = null; _currentStation = customStation; _currentCustomIndex = stationIndex; _currentTrackIndex = Math.Max(0, Math.Min(trackIndex, customStation.Tracks.Count - 1)); _seekAfterReady = elapsedSeconds; PlayCurrentTrack(); if (elapsedSeconds > 0f && ((Channel)(ref _channel)).hasHandle()) { ((Channel)(ref _channel)).setPosition((uint)(elapsedSeconds * 1000f), (TIMEUNIT)1); _seekAfterReady = 0f; } } public static void RefreshAllRadios() { //IL_0057: Unknown result type (might be due to invalid IL or missing references) //IL_005e: Expected O, but got Unknown int vanillaStationCount = RadioAPI.VanillaStationCount; int count = RadioAPI.GetStations().Count; int count2 = vanillaStationCount + count; bool flag = default(bool); foreach (KeyValuePair controller in _controllers) { Radio radio = controller.Value._radio; if ((Object)(object)radio == (Object)null) { continue; } try { RadioPatches.WriteStationCountExternal(radio, count2); } catch (Exception ex) { ManualLogSource log = Plugin.Log; BepInExErrorLogInterpolatedStringHandler val = new BepInExErrorLogInterpolatedStringHandler(18, 1, ref flag); if (flag) { ((BepInExLogInterpolatedStringHandler)val).AppendLiteral("RefreshAllRadios: "); ((BepInExLogInterpolatedStringHandler)val).AppendFormatted(ex); } log.LogError(val); } } } public static IEnumerable SnapshotActiveCustomPlayback() { foreach (KeyValuePair controller in _controllers) { RadioController value = controller.Value; if (value._isPlaying && value._currentStation != null) { int num = RadioAPI.IndexOfStation(value._currentStation); if (num >= 0 && value._radioId != 0) { yield return new PlaybackSnapshot { RadioId = value._radioId, StationIndex = num, TrackIndex = value._currentTrackIndex, ElapsedSeconds = value.GetCurrentTrackElapsed() }; } } } } private void StopChannel() { //IL_005c: Unknown result type (might be due to invalid IL or missing references) if (((Channel)(ref _channel)).hasHandle()) { if (_currentStation != null && _currentTrackIndex >= 0 && _currentTrackIndex < _currentStation.Tracks.Count) { MemoryBudget.Unpin(_currentStation.Tracks[_currentTrackIndex].Hash); } ((Channel)(ref _channel)).stop(); ((Channel)(ref _channel)).clearHandle(); } } private void Cleanup() { StopChannel(); } private static void EnsureTrackReadyHook() { if (!_trackReadySubscribed) { RadioAPI.TrackReady += OnTrackReady; _trackReadySubscribed = true; } } private static void OnTrackReady(TrackInfo track) { //IL_0096: Unknown result type (might be due to invalid IL or missing references) foreach (KeyValuePair controller in _controllers) { RadioController value = controller.Value; if (value._pendingTrackIndex >= 0 && value._currentStation != null && value._pendingTrackIndex < value._currentStation.Tracks.Count && value._currentStation.Tracks[value._pendingTrackIndex] == track) { value.PlayCurrentTrack(); if (value._seekAfterReady > 0f && ((Channel)(ref value._channel)).hasHandle()) { ((Channel)(ref value._channel)).setPosition((uint)(value._seekAfterReady * 1000f), (TIMEUNIT)1); value._seekAfterReady = 0f; } } } } } [HarmonyPatch] public static class RadioPatches { private static int _lastDispatchFrame = -1; private const int OFFSET_AMOUNT_OF_STATIONS = 128; private const int OFFSET_EVENT_INSTANCE = 216; private const int OFFSET_IS_RADIO_ON_LOCAL = 228; private const int OFFSET_RADIO_STATION_LOCAL = 236; private const int OFFSET_VOLUME_LOCAL = 244; private static int ReadStationCount(Radio radio) { return Marshal.ReadInt32(((Il2CppObjectBase)radio).Pointer + 128); } private static void WriteStationCount(Radio radio, int count) { Marshal.WriteInt32(((Il2CppObjectBase)radio).Pointer + 128, count); } internal static void WriteStationCountExternal(Radio radio, int count) { WriteStationCount(radio, count); } private static int ReadCurrentStation(Radio radio) { return Marshal.ReadInt32(((Il2CppObjectBase)radio).Pointer + 236); } private static float ReadVolume(Radio radio) { return BitConverter.Int32BitsToSingle(Marshal.ReadInt32(((Il2CppObjectBase)radio).Pointer + 244)); } private static bool ReadIsRadioOn(Radio radio) { return Marshal.ReadByte(((Il2CppObjectBase)radio).Pointer + 228) != 0; } [HarmonyPatch(typeof(Radio), "Spawned")] [HarmonyPostfix] public static void SpawnedPostfix(Radio __instance) { //IL_00e6: Unknown result type (might be due to invalid IL or missing references) //IL_00ed: Expected O, but got Unknown //IL_001f: Unknown result type (might be due to invalid IL or missing references) //IL_0025: Expected O, but got Unknown //IL_006e: Unknown result type (might be due to invalid IL or missing references) //IL_0074: Expected O, but got Unknown bool flag = default(bool); try { int num = ReadStationCount(__instance); if (RadioAPI._vanillaStationCount < 0) { RadioAPI._vanillaStationCount = num; ManualLogSource log = Plugin.Log; BepInExInfoLogInterpolatedStringHandler val = new BepInExInfoLogInterpolatedStringHandler(29, 1, ref flag); if (flag) { ((BepInExLogInterpolatedStringHandler)val).AppendLiteral("Vanilla radio station count: "); ((BepInExLogInterpolatedStringHandler)val).AppendFormatted(num); } log.LogInfo(val); } int count = RadioAPI.GetStations().Count; if (count > 0) { int count2 = num + count; WriteStationCount(__instance, count2); int num2 = ReadStationCount(__instance); ManualLogSource log2 = Plugin.Log; BepInExInfoLogInterpolatedStringHandler val = new BepInExInfoLogInterpolatedStringHandler(40, 3, ref flag); if (flag) { ((BepInExLogInterpolatedStringHandler)val).AppendLiteral("Expanded radio stations: "); ((BepInExLogInterpolatedStringHandler)val).AppendFormatted(num); ((BepInExLogInterpolatedStringHandler)val).AppendLiteral(" -> "); ((BepInExLogInterpolatedStringHandler)val).AppendFormatted(num2); ((BepInExLogInterpolatedStringHandler)val).AppendLiteral(" (+"); ((BepInExLogInterpolatedStringHandler)val).AppendFormatted(count); ((BepInExLogInterpolatedStringHandler)val).AppendLiteral(" custom)"); } log2.LogInfo(val); } if (RadioController.Get(((Component)__instance).gameObject) == null) { RadioController.Create(((Component)__instance).gameObject); } } catch (Exception ex) { ManualLogSource log3 = Plugin.Log; BepInExErrorLogInterpolatedStringHandler val2 = new BepInExErrorLogInterpolatedStringHandler(29, 1, ref flag); if (flag) { ((BepInExLogInterpolatedStringHandler)val2).AppendLiteral("RadioPatches.SpawnedPostfix: "); ((BepInExLogInterpolatedStringHandler)val2).AppendFormatted(ex); } log3.LogError(val2); } } [HarmonyPatch(typeof(Radio), "Despawned")] [HarmonyPrefix] public static void DespawnedPrefix(Radio __instance) { //IL_0018: Unknown result type (might be due to invalid IL or missing references) //IL_001e: Expected O, but got Unknown try { RadioController.Remove(((Component)__instance).gameObject); } catch (Exception ex) { ManualLogSource log = Plugin.Log; bool flag = default(bool); BepInExErrorLogInterpolatedStringHandler val = new BepInExErrorLogInterpolatedStringHandler(30, 1, ref flag); if (flag) { ((BepInExLogInterpolatedStringHandler)val).AppendLiteral("RadioPatches.DespawnedPrefix: "); ((BepInExLogInterpolatedStringHandler)val).AppendFormatted(ex); } log.LogError(val); } } [HarmonyPatch(typeof(Radio), "OnChangedRadioStation")] [HarmonyPostfix] public static void OnChangedRadioStationPostfix(Radio __instance) { //IL_0011: Unknown result type (might be due to invalid IL or missing references) //IL_0017: Expected O, but got Unknown //IL_00e4: Unknown result type (might be due to invalid IL or missing references) //IL_00eb: Expected O, but got Unknown //IL_00bd: Unknown result type (might be due to invalid IL or missing references) //IL_00c2: Unknown result type (might be due to invalid IL or missing references) //IL_0075: Unknown result type (might be due to invalid IL or missing references) //IL_007a: Unknown result type (might be due to invalid IL or missing references) //IL_00d0: Unknown result type (might be due to invalid IL or missing references) //IL_0088: Unknown result type (might be due to invalid IL or missing references) bool flag = default(bool); try { int num = ReadCurrentStation(__instance); ManualLogSource log = Plugin.Log; BepInExInfoLogInterpolatedStringHandler val = new BepInExInfoLogInterpolatedStringHandler(42, 2, ref flag); if (flag) { ((BepInExLogInterpolatedStringHandler)val).AppendLiteral("OnChangedRadioStation: station="); ((BepInExLogInterpolatedStringHandler)val).AppendFormatted(num); ((BepInExLogInterpolatedStringHandler)val).AppendLiteral(", isCustom="); ((BepInExLogInterpolatedStringHandler)val).AppendFormatted(RadioAPI.IsCustomStation(num)); } log.LogInfo(val); RadioController radioController = RadioController.Get(((Component)__instance).gameObject); if (radioController == null) { return; } if (RadioAPI.IsCustomStation(num)) { EventInstance value = Traverse.Create((object)__instance).Field("_eventInstance").GetValue(); if (((EventInstance)(ref value)).isValid()) { ((EventInstance)(ref value)).stop((STOP_MODE)1); } float volume = ReadVolume(__instance); radioController.SetVolume(volume); radioController.PlayStation(num); } else { radioController.Stop(); EventInstance value2 = Traverse.Create((object)__instance).Field("_eventInstance").GetValue(); if (((EventInstance)(ref value2)).isValid()) { ((EventInstance)(ref value2)).setPaused(false); } } } catch (Exception ex) { ManualLogSource log2 = Plugin.Log; BepInExErrorLogInterpolatedStringHandler val2 = new BepInExErrorLogInterpolatedStringHandler(43, 1, ref flag); if (flag) { ((BepInExLogInterpolatedStringHandler)val2).AppendLiteral("RadioPatches.OnChangedRadioStationPostfix: "); ((BepInExLogInterpolatedStringHandler)val2).AppendFormatted(ex); } log2.LogError(val2); } } [HarmonyPatch(typeof(Radio), "OnChangedVolume")] [HarmonyPostfix] public static void OnChangedVolumePostfix(Radio __instance) { //IL_002c: Unknown result type (might be due to invalid IL or missing references) //IL_0032: Expected O, but got Unknown try { RadioController radioController = RadioController.Get(((Component)__instance).gameObject); if (radioController != null) { float volume = ReadVolume(__instance); radioController.SetVolume(volume); } } catch (Exception ex) { ManualLogSource log = Plugin.Log; bool flag = default(bool); BepInExErrorLogInterpolatedStringHandler val = new BepInExErrorLogInterpolatedStringHandler(37, 1, ref flag); if (flag) { ((BepInExLogInterpolatedStringHandler)val).AppendLiteral("RadioPatches.OnChangedVolumePostfix: "); ((BepInExLogInterpolatedStringHandler)val).AppendFormatted(ex); } log.LogError(val); } } [HarmonyPatch(typeof(Radio), "OnChangedRadioOn")] [HarmonyPostfix] public static void OnChangedRadioOnPostfix(Radio __instance) { //IL_007e: Unknown result type (might be due to invalid IL or missing references) //IL_0085: Expected O, but got Unknown //IL_0043: Unknown result type (might be due to invalid IL or missing references) //IL_0048: Unknown result type (might be due to invalid IL or missing references) //IL_0055: Unknown result type (might be due to invalid IL or missing references) try { RadioController radioController = RadioController.Get(((Component)__instance).gameObject); if (radioController == null) { return; } bool num = ReadIsRadioOn(__instance); int num2 = ReadCurrentStation(__instance); if (!num) { radioController.Stop(); } else if (RadioAPI.IsCustomStation(num2)) { EventInstance value = Traverse.Create((object)__instance).Field("_eventInstance").GetValue(); if (((EventInstance)(ref value)).isValid()) { ((EventInstance)(ref value)).stop((STOP_MODE)1); } float volume = ReadVolume(__instance); radioController.SetVolume(volume); radioController.PlayStation(num2); } } catch (Exception ex) { ManualLogSource log = Plugin.Log; bool flag = default(bool); BepInExErrorLogInterpolatedStringHandler val = new BepInExErrorLogInterpolatedStringHandler(38, 1, ref flag); if (flag) { ((BepInExLogInterpolatedStringHandler)val).AppendLiteral("RadioPatches.OnChangedRadioOnPostfix: "); ((BepInExLogInterpolatedStringHandler)val).AppendFormatted(ex); } log.LogError(val); } } [HarmonyPatch(typeof(Radio), "Update")] [HarmonyPostfix] public static void UpdatePostfix(Radio __instance) { //IL_0054: Unknown result type (might be due to invalid IL or missing references) //IL_0059: Unknown result type (might be due to invalid IL or missing references) //IL_0067: Unknown result type (might be due to invalid IL or missing references) //IL_006d: Unknown result type (might be due to invalid IL or missing references) //IL_0077: Unknown result type (might be due to invalid IL or missing references) //IL_0070: Unknown result type (might be due to invalid IL or missing references) //IL_0072: Invalid comparison between Unknown and I4 try { int frameCount = Time.frameCount; if (frameCount != _lastDispatchFrame) { _lastDispatchFrame = frameCount; MainThread.ProcessQueue(); } RadioController.Get(((Component)__instance).gameObject)?.CheckTrackAdvance(); if (!ReadIsRadioOn(__instance) || !RadioAPI.IsCustomStation(ReadCurrentStation(__instance))) { return; } EventInstance value = Traverse.Create((object)__instance).Field("_eventInstance").GetValue(); if (((EventInstance)(ref value)).isValid()) { PLAYBACK_STATE val = default(PLAYBACK_STATE); ((EventInstance)(ref value)).getPlaybackState(ref val); if ((int)val == 0 || (int)val == 3) { ((EventInstance)(ref value)).stop((STOP_MODE)1); } } } catch { } } } public class TrackInfo { public string Name { get; set; } public float[] PcmData { get; set; } public int SampleRate { get; set; } public int Channels { get; set; } public int LengthSamples { get; set; } public Sound FmodSound { get; internal set; } public string Hash { get; set; } public string Extension { get; set; } public string SourcePath { get; set; } public bool Ready { get { if (PcmData != null) { return PcmData.Length != 0; } return false; } } internal IntPtr NativePcmPtr { get; set; } public void Unload() { ReleaseNativeData(); PcmData = null; } internal void ReleaseNativeData() { //IL_0029: Unknown result type (might be due to invalid IL or missing references) //IL_002e: Unknown result type (might be due to invalid IL or missing references) //IL_0039: Unknown result type (might be due to invalid IL or missing references) //IL_003e: 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) //IL_0048: Unknown result type (might be due to invalid IL or missing references) //IL_004d: Unknown result type (might be due to invalid IL or missing references) if (NativePcmPtr != IntPtr.Zero) { Marshal.FreeHGlobal(NativePcmPtr); NativePcmPtr = IntPtr.Zero; } Sound fmodSound = FmodSound; if (((Sound)(ref fmodSound)).hasHandle()) { fmodSound = FmodSound; ((Sound)(ref fmodSound)).release(); fmodSound = FmodSound; ((Sound)(ref fmodSound)).clearHandle(); } } } public class StationDefinition { public string Id { get; set; } public string Name { get; set; } public List Tracks { get; set; } = new List(); public bool Shuffle { get; set; } public bool Loop { get; set; } = true; public bool RemoteStation { get; set; } } public class WavData { public float[] Samples; public int SampleRate; public int Channels; public int LengthSamples; public WavData(float[] samples, int sampleRate, int channels, int lengthSamples) { Samples = samples; SampleRate = sampleRate; Channels = channels; LengthSamples = lengthSamples; } } public class ProbeResult { public int SampleRate; public int Channels; public int LengthSamples; } public static class WavLoader { public static WavData Load(string filePath, ManualLogSource log = null, CancellationToken ct = default(CancellationToken)) { //IL_0017: Unknown result type (might be due to invalid IL or missing references) //IL_001e: Expected O, but got Unknown //IL_007f: Unknown result type (might be due to invalid IL or missing references) //IL_0086: Expected O, but got Unknown //IL_00e9: Unknown result type (might be due to invalid IL or missing references) //IL_00f0: Expected O, but got Unknown //IL_0131: Unknown result type (might be due to invalid IL or missing references) //IL_0138: Expected O, but got Unknown //IL_0215: Unknown result type (might be due to invalid IL or missing references) //IL_021c: Expected O, but got Unknown //IL_01aa: Unknown result type (might be due to invalid IL or missing references) //IL_01b1: Expected O, but got Unknown //IL_035a: Unknown result type (might be due to invalid IL or missing references) //IL_0361: Expected O, but got Unknown //IL_03c6: Unknown result type (might be due to invalid IL or missing references) //IL_03cd: Expected O, but got Unknown byte[] array; bool flag = default(bool); ManualLogSource val; try { array = File.ReadAllBytes(filePath); } catch (Exception ex) { val = log; if (val != null) { BepInExErrorLogInterpolatedStringHandler val2 = new BepInExErrorLogInterpolatedStringHandler(28, 2, ref flag); if (flag) { ((BepInExLogInterpolatedStringHandler)val2).AppendLiteral("Failed to read WAV file '"); ((BepInExLogInterpolatedStringHandler)val2).AppendFormatted(filePath); ((BepInExLogInterpolatedStringHandler)val2).AppendLiteral("': "); ((BepInExLogInterpolatedStringHandler)val2).AppendFormatted(ex.Message); } val.LogError(val2); } return null; } if (ct.IsCancellationRequested) { return null; } if (array.Length < 44) { val = log; if (val != null) { BepInExErrorLogInterpolatedStringHandler val2 = new BepInExErrorLogInterpolatedStringHandler(20, 1, ref flag); if (flag) { ((BepInExLogInterpolatedStringHandler)val2).AppendLiteral("WAV file too small: "); ((BepInExLogInterpolatedStringHandler)val2).AppendFormatted(filePath); } val.LogError(val2); } return null; } string @string = Encoding.ASCII.GetString(array, 0, 4); string string2 = Encoding.ASCII.GetString(array, 8, 4); if (@string != "RIFF" || string2 != "WAVE") { val = log; if (val != null) { BepInExErrorLogInterpolatedStringHandler val2 = new BepInExErrorLogInterpolatedStringHandler(22, 1, ref flag); if (flag) { ((BepInExLogInterpolatedStringHandler)val2).AppendLiteral("Not a valid WAV file: "); ((BepInExLogInterpolatedStringHandler)val2).AppendFormatted(filePath); } val.LogError(val2); } return null; } int num = FindChunk(array, "fmt ", 12); if (num < 0) { val = log; if (val != null) { BepInExErrorLogInterpolatedStringHandler val2 = new BepInExErrorLogInterpolatedStringHandler(23, 1, ref flag); if (flag) { ((BepInExLogInterpolatedStringHandler)val2).AppendLiteral("No fmt chunk found in: "); ((BepInExLogInterpolatedStringHandler)val2).AppendFormatted(filePath); } val.LogError(val2); } return null; } BitConverter.ToInt32(array, num + 4); int num2 = num + 8; ushort num3 = BitConverter.ToUInt16(array, num2); ushort num4 = BitConverter.ToUInt16(array, num2 + 2); int num5 = BitConverter.ToInt32(array, num2 + 4); ushort num6 = BitConverter.ToUInt16(array, num2 + 14); if (num3 != 1 && num3 != 3) { val = log; if (val != null) { BepInExErrorLogInterpolatedStringHandler val2 = new BepInExErrorLogInterpolatedStringHandler(68, 2, ref flag); if (flag) { ((BepInExLogInterpolatedStringHandler)val2).AppendLiteral("Unsupported WAV format "); ((BepInExLogInterpolatedStringHandler)val2).AppendFormatted(num3); ((BepInExLogInterpolatedStringHandler)val2).AppendLiteral(" in: "); ((BepInExLogInterpolatedStringHandler)val2).AppendFormatted(filePath); ((BepInExLogInterpolatedStringHandler)val2).AppendLiteral(" (only PCM=1 and IEEE float=3 supported)"); } val.LogError(val2); } return null; } int num7 = FindChunk(array, "data", 12); if (num7 < 0) { val = log; if (val != null) { BepInExErrorLogInterpolatedStringHandler val2 = new BepInExErrorLogInterpolatedStringHandler(24, 1, ref flag); if (flag) { ((BepInExLogInterpolatedStringHandler)val2).AppendLiteral("No data chunk found in: "); ((BepInExLogInterpolatedStringHandler)val2).AppendFormatted(filePath); } val.LogError(val2); } return null; } int num8 = BitConverter.ToInt32(array, num7 + 4); int num9 = num7 + 8; if (num9 + num8 > array.Length) { num8 = array.Length - num9; } float[] array2; if (num3 == 1 && num6 == 16) { int num10 = num8 / 2; array2 = new float[num10]; for (int i = 0; i < num10; i++) { short num11 = BitConverter.ToInt16(array, num9 + i * 2); array2[i] = (float)num11 / 32768f; } } else if (num3 == 1 && num6 == 24) { int num12 = num8 / 3; array2 = new float[num12]; for (int j = 0; j < num12; j++) { int num13 = num9 + j * 3; int num14 = array[num13] | (array[num13 + 1] << 8) | (array[num13 + 2] << 16); if (num14 >= 8388608) { num14 -= 16777216; } array2[j] = (float)num14 / 8388608f; } } else { if (num3 != 3 || num6 != 32) { val = log; if (val != null) { BepInExErrorLogInterpolatedStringHandler val2 = new BepInExErrorLogInterpolatedStringHandler(39, 3, ref flag); if (flag) { ((BepInExLogInterpolatedStringHandler)val2).AppendLiteral("Unsupported bit depth "); ((BepInExLogInterpolatedStringHandler)val2).AppendFormatted(num6); ((BepInExLogInterpolatedStringHandler)val2).AppendLiteral(" for format "); ((BepInExLogInterpolatedStringHandler)val2).AppendFormatted(num3); ((BepInExLogInterpolatedStringHandler)val2).AppendLiteral(" in: "); ((BepInExLogInterpolatedStringHandler)val2).AppendFormatted(filePath); } val.LogError(val2); } return null; } int num15 = num8 / 4; array2 = new float[num15]; Buffer.BlockCopy(array, num9, array2, 0, num15 * 4); } int num16 = array2.Length / num4; val = log; if (val != null) { BepInExInfoLogInterpolatedStringHandler val3 = new BepInExInfoLogInterpolatedStringHandler(29, 5, ref flag); if (flag) { ((BepInExLogInterpolatedStringHandler)val3).AppendLiteral("Loaded WAV: "); ((BepInExLogInterpolatedStringHandler)val3).AppendFormatted(Path.GetFileName(filePath)); ((BepInExLogInterpolatedStringHandler)val3).AppendLiteral(" — "); ((BepInExLogInterpolatedStringHandler)val3).AppendFormatted(num4); ((BepInExLogInterpolatedStringHandler)val3).AppendLiteral("ch, "); ((BepInExLogInterpolatedStringHandler)val3).AppendFormatted(num5); ((BepInExLogInterpolatedStringHandler)val3).AppendLiteral("Hz, "); ((BepInExLogInterpolatedStringHandler)val3).AppendFormatted(num6); ((BepInExLogInterpolatedStringHandler)val3).AppendLiteral("bit, "); ((BepInExLogInterpolatedStringHandler)val3).AppendFormatted((float)num16 / (float)num5, "F1"); ((BepInExLogInterpolatedStringHandler)val3).AppendLiteral("s"); } val.LogInfo(val3); } return new WavData(array2, num5, num4, num16); } public static ProbeResult Probe(string filePath, ManualLogSource log = null) { //IL_017f: Unknown result type (might be due to invalid IL or missing references) //IL_0186: Expected O, but got Unknown try { long length; byte[] array; using (FileStream fileStream = File.OpenRead(filePath)) { length = fileStream.Length; int num = (int)Math.Min(length, 512L); array = new byte[num]; int num2; for (int i = 0; i < num; i += num2) { num2 = fileStream.Read(array, i, num - i); if (num2 == 0) { break; } } } if (array.Length < 44) { return null; } string @string = Encoding.ASCII.GetString(array, 0, 4); string string2 = Encoding.ASCII.GetString(array, 8, 4); if (@string != "RIFF" || string2 != "WAVE") { return null; } int num3 = FindChunk(array, "fmt ", 12); if (num3 < 0) { return null; } int num4 = num3 + 8; if (num4 + 16 > array.Length) { return null; } ushort num5 = BitConverter.ToUInt16(array, num4 + 2); int sampleRate = BitConverter.ToInt32(array, num4 + 4); ushort num6 = BitConverter.ToUInt16(array, num4 + 14); int num7 = FindChunk(array, "data", 12); int num8 = (int)((num7 < 0 || num7 + 8 > array.Length) ? (length - 44) : BitConverter.ToInt32(array, num7 + 4)); if (num6 == 0 || num5 == 0) { return null; } int num9 = num6 / 8; int lengthSamples = num8 / num9 / num5; return new ProbeResult { SampleRate = sampleRate, Channels = num5, LengthSamples = lengthSamples }; } catch (Exception ex) { if (log != null) { bool flag = default(bool); BepInExWarningLogInterpolatedStringHandler val = new BepInExWarningLogInterpolatedStringHandler(25, 2, ref flag); if (flag) { ((BepInExLogInterpolatedStringHandler)val).AppendLiteral("WAV probe failed for '"); ((BepInExLogInterpolatedStringHandler)val).AppendFormatted(filePath); ((BepInExLogInterpolatedStringHandler)val).AppendLiteral("': "); ((BepInExLogInterpolatedStringHandler)val).AppendFormatted(ex.Message); } log.LogWarning(val); } return null; } } private static int FindChunk(byte[] data, string chunkId, int startOffset) { byte[] bytes = Encoding.ASCII.GetBytes(chunkId); int num = startOffset; while (num + 8 <= data.Length) { if (data[num] == bytes[0] && data[num + 1] == bytes[1] && data[num + 2] == bytes[2] && data[num + 3] == bytes[3]) { return num; } int num2 = BitConverter.ToInt32(data, num + 4); if (num2 < 0) { break; } num += 8 + num2; if (num % 2 != 0) { num++; } } return -1; } } } namespace RadioLib.Net { internal static class AudioCache { private static string _cacheDir; public static string Root { get { //IL_0033: Unknown result type (might be due to invalid IL or missing references) //IL_0039: Expected O, but got Unknown if (_cacheDir == null) { _cacheDir = Path.Combine(FolderScanner.PluginDirectory, "Cache"); try { Directory.CreateDirectory(_cacheDir); } catch (Exception ex) { ManualLogSource log = Plugin.Log; bool flag = default(bool); BepInExWarningLogInterpolatedStringHandler val = new BepInExWarningLogInterpolatedStringHandler(31, 2, ref flag); if (flag) { ((BepInExLogInterpolatedStringHandler)val).AppendLiteral("Could not create cache dir '"); ((BepInExLogInterpolatedStringHandler)val).AppendFormatted(_cacheDir); ((BepInExLogInterpolatedStringHandler)val).AppendLiteral("': "); ((BepInExLogInterpolatedStringHandler)val).AppendFormatted(ex.Message); } log.LogWarning(val); } } return _cacheDir; } } public static string PathFor(string hashHex, string extension) { if (string.IsNullOrEmpty(extension)) { extension = ".bin"; } if (!extension.StartsWith(".")) { extension = "." + extension; } return Path.Combine(Root, hashHex + extension); } public static bool TryRead(string hashHex, string extension, out byte[] bytes) { //IL_002d: Unknown result type (might be due to invalid IL or missing references) //IL_0033: Expected O, but got Unknown bytes = null; string path = PathFor(hashHex, extension); if (!File.Exists(path)) { return false; } try { bytes = File.ReadAllBytes(path); return true; } catch (Exception ex) { ManualLogSource log = Plugin.Log; bool flag = default(bool); BepInExWarningLogInterpolatedStringHandler val = new BepInExWarningLogInterpolatedStringHandler(24, 2, ref flag); if (flag) { ((BepInExLogInterpolatedStringHandler)val).AppendLiteral("Cache read failed for "); ((BepInExLogInterpolatedStringHandler)val).AppendFormatted(hashHex); ((BepInExLogInterpolatedStringHandler)val).AppendLiteral(": "); ((BepInExLogInterpolatedStringHandler)val).AppendFormatted(ex.Message); } log.LogWarning(val); return false; } } public static void Store(string hashHex, string extension, byte[] bytes) { //IL_0024: Unknown result type (might be due to invalid IL or missing references) //IL_002a: Expected O, but got Unknown if (bytes == null || bytes.Length == 0) { return; } string path = PathFor(hashHex, extension); try { File.WriteAllBytes(path, bytes); } catch (Exception ex) { ManualLogSource log = Plugin.Log; bool flag = default(bool); BepInExWarningLogInterpolatedStringHandler val = new BepInExWarningLogInterpolatedStringHandler(25, 2, ref flag); if (flag) { ((BepInExLogInterpolatedStringHandler)val).AppendLiteral("Cache write failed for "); ((BepInExLogInterpolatedStringHandler)val).AppendFormatted(hashHex); ((BepInExLogInterpolatedStringHandler)val).AppendLiteral(": "); ((BepInExLogInterpolatedStringHandler)val).AppendFormatted(ex.Message); } log.LogWarning(val); } } public static void EvictOldEntries(long maxBytes) { //IL_0128: Unknown result type (might be due to invalid IL or missing references) //IL_012f: Expected O, but got Unknown //IL_00b1: Unknown result type (might be due to invalid IL or missing references) //IL_00b8: Expected O, but got Unknown if (maxBytes <= 0) { return; } bool flag = default(bool); try { DirectoryInfo directoryInfo = new DirectoryInfo(Root); if (!directoryInfo.Exists) { return; } FileInfo[] files = directoryInfo.GetFiles(); long num = files.Sum((FileInfo f) => f.Length); if (num <= maxBytes) { return; } Array.Sort(files, (FileInfo a, FileInfo b) => a.LastAccessTimeUtc.CompareTo(b.LastAccessTimeUtc)); FileInfo[] array = files; foreach (FileInfo fileInfo in array) { if (num <= maxBytes) { break; } try { long length = fileInfo.Length; fileInfo.Delete(); num -= length; ManualLogSource log = Plugin.Log; BepInExInfoLogInterpolatedStringHandler val = new BepInExInfoLogInterpolatedStringHandler(21, 2, ref flag); if (flag) { ((BepInExLogInterpolatedStringHandler)val).AppendLiteral("Cache evicted: "); ((BepInExLogInterpolatedStringHandler)val).AppendFormatted(fileInfo.Name); ((BepInExLogInterpolatedStringHandler)val).AppendLiteral(" ("); ((BepInExLogInterpolatedStringHandler)val).AppendFormatted(length / 1024); ((BepInExLogInterpolatedStringHandler)val).AppendLiteral(" KB)"); } log.LogInfo(val); } catch { } } } catch (Exception ex) { ManualLogSource log2 = Plugin.Log; BepInExWarningLogInterpolatedStringHandler val2 = new BepInExWarningLogInterpolatedStringHandler(23, 1, ref flag); if (flag) { ((BepInExLogInterpolatedStringHandler)val2).AppendLiteral("Cache eviction failed: "); ((BepInExLogInterpolatedStringHandler)val2).AppendFormatted(ex.Message); } log2.LogWarning(val2); } } } internal sealed class ManifestDto { public List Stations { get; set; } = new List(); } internal sealed class ManifestStation { public string Id { get; set; } public string Name { get; set; } public bool Shuffle { get; set; } public bool Loop { get; set; } public List Tracks { get; set; } = new List(); } internal sealed class ManifestTrack { public string Name { get; set; } public string Hash { get; set; } public string Ext { get; set; } public int Size { get; set; } } internal static class Manifest { private static readonly JsonSerializerOptions _opts = new JsonSerializerOptions { WriteIndented = false }; public static byte[] Serialize(IReadOnlyList stations) { ManifestDto manifestDto = new ManifestDto(); foreach (StationDefinition station in stations) { ManifestStation manifestStation = new ManifestStation { Id = station.Id, Name = station.Name, Shuffle = station.Shuffle, Loop = station.Loop }; foreach (TrackInfo track in station.Tracks) { if (string.IsNullOrEmpty(track.Hash)) { continue; } int size = 0; if (!string.IsNullOrEmpty(track.SourcePath)) { try { size = (int)new FileInfo(track.SourcePath).Length; } catch { } } manifestStation.Tracks.Add(new ManifestTrack { Name = track.Name, Hash = track.Hash, Ext = (track.Extension ?? ".mp3"), Size = size }); } manifestDto.Stations.Add(manifestStation); } return Encoding.UTF8.GetBytes(JsonSerializer.Serialize(manifestDto, _opts)); } public static List Deserialize(byte[] utf8Json, int offset, int length) { ManifestDto manifestDto = JsonSerializer.Deserialize(Encoding.UTF8.GetString(utf8Json, offset, length), _opts); List list = new List(); if (manifestDto?.Stations == null) { return list; } foreach (ManifestStation station in manifestDto.Stations) { StationDefinition stationDefinition = new StationDefinition { Id = station.Id, Name = station.Name, Shuffle = station.Shuffle, Loop = station.Loop, RemoteStation = true }; foreach (ManifestTrack track in station.Tracks) { stationDefinition.Tracks.Add(new TrackInfo { Name = track.Name, Hash = track.Hash, Extension = track.Ext }); } list.Add(stationDefinition); } return list; } } internal enum MsgType : byte { Manifest = 1, FileRequest, FileData, TrackSelect, PrefetchHint } internal static class MessageCodec { public const int HashByteLen = 32; public static byte[] BuildManifest(IReadOnlyList stations) { byte[] array = Manifest.Serialize(stations); byte[] array2 = new byte[1 + array.Length]; array2[0] = 1; Buffer.BlockCopy(array, 0, array2, 1, array.Length); return array2; } public static byte[] BuildFileRequest(string hashHex) { byte[] array = new byte[33]; array[0] = 2; WriteHashHex(hashHex, array, 1); return array; } public static byte[] BuildFileData(string hashHex, byte[] fileBytes) { if (fileBytes == null) { fileBytes = Array.Empty(); } byte[] array = new byte[33 + fileBytes.Length]; array[0] = 3; WriteHashHex(hashHex, array, 1); Buffer.BlockCopy(fileBytes, 0, array, 33, fileBytes.Length); return array; } public static byte[] BuildTrackSelect(uint radioId, int stationIndex, int trackIndex, float elapsedSeconds = 0f) { byte[] array = new byte[17] { 4, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }; WriteUInt32LE(radioId, array, 1); WriteInt32LE(stationIndex, array, 5); WriteInt32LE(trackIndex, array, 9); WriteFloat32LE(elapsedSeconds, array, 13); return array; } public static byte[] BuildPrefetchHint(string hashHex) { byte[] array = new byte[33]; array[0] = 5; WriteHashHex(hashHex, array, 1); return array; } public static bool TryParsePrefetchHint(ArraySegment seg, out string hashHex) { hashHex = null; if (seg.Count < 33) { return false; } hashHex = ReadHashHex(seg, 1); return true; } public static bool TryParseFileRequest(ArraySegment seg, out string hashHex) { hashHex = null; if (seg.Count < 33) { return false; } hashHex = ReadHashHex(seg, 1); return true; } public static bool TryParseFileData(ArraySegment seg, out string hashHex, out byte[] fileBytes) { hashHex = null; fileBytes = null; if (seg.Count < 33) { return false; } hashHex = ReadHashHex(seg, 1); int num = seg.Count - 1 - 32; fileBytes = new byte[num]; if (num > 0) { Buffer.BlockCopy(seg.Array, seg.Offset + 1 + 32, fileBytes, 0, num); } return true; } public static bool TryParseTrackSelect(ArraySegment seg, out uint radioId, out int stationIndex, out int trackIndex, out float elapsedSeconds) { radioId = 0u; stationIndex = 0; trackIndex = 0; elapsedSeconds = 0f; if (seg.Count < 13) { return false; } radioId = ReadUInt32LE(seg, 1); stationIndex = ReadInt32LE(seg, 5); trackIndex = ReadInt32LE(seg, 9); if (seg.Count >= 17) { elapsedSeconds = ReadFloat32LE(seg, 13); } return true; } private static void WriteUInt32LE(uint v, byte[] buf, int off) { buf[off] = (byte)(v & 0xFFu); buf[off + 1] = (byte)((v >> 8) & 0xFFu); buf[off + 2] = (byte)((v >> 16) & 0xFFu); buf[off + 3] = (byte)((v >> 24) & 0xFFu); } private static void WriteInt32LE(int v, byte[] buf, int off) { WriteUInt32LE((uint)v, buf, off); } private static uint ReadUInt32LE(ArraySegment seg, int innerOff) { int num = seg.Offset + innerOff; return (uint)(seg.Array[num] | (seg.Array[num + 1] << 8) | (seg.Array[num + 2] << 16) | (seg.Array[num + 3] << 24)); } private static int ReadInt32LE(ArraySegment seg, int innerOff) { return (int)ReadUInt32LE(seg, innerOff); } private static void WriteFloat32LE(float v, byte[] buf, int off) { byte[] bytes = BitConverter.GetBytes(v); buf[off] = bytes[0]; buf[off + 1] = bytes[1]; buf[off + 2] = bytes[2]; buf[off + 3] = bytes[3]; } private static float ReadFloat32LE(ArraySegment seg, int innerOff) { int startIndex = seg.Offset + innerOff; return BitConverter.ToSingle(seg.Array, startIndex); } private static void WriteHashHex(string hashHex, byte[] buf, int off) { for (int i = 0; i < 32; i++) { int num = ((i * 2 < hashHex.Length) ? HexVal(hashHex[i * 2]) : 0); int num2 = ((i * 2 + 1 < hashHex.Length) ? HexVal(hashHex[i * 2 + 1]) : 0); buf[off + i] = (byte)((num << 4) | num2); } } private static string ReadHashHex(ArraySegment seg, int innerOff) { char[] array = new char[64]; for (int i = 0; i < 32; i++) { byte b = seg.Array[seg.Offset + innerOff + i]; array[i * 2] = "0123456789abcdef"[b >> 4]; array[i * 2 + 1] = "0123456789abcdef"[b & 0xF]; } return new string(array); } private static int HexVal(char c) { if (c >= '0' && c <= '9') { return c - 48; } if (c >= 'a' && c <= 'f') { return c - 97 + 10; } if (c >= 'A' && c <= 'F') { return c - 65 + 10; } return 0; } } internal static class RadioLibNet { [HarmonyPatch(typeof(LargeDataManager), "OnReliableDataReceived")] internal static class OnReliableDataReceivedPatch { [HarmonyPrefix] public static bool Prefix(NetworkRunner runner, PlayerRef player, ReliableKey key, ArraySegment data) { //IL_0000: Unknown result type (might be due to invalid IL or missing references) //IL_0063: Unknown result type (might be due to invalid IL or missing references) //IL_006a: Expected O, but got Unknown //IL_0049: Unknown result type (might be due to invalid IL or missing references) if (!IsOurKey(key)) { return true; } try { int count = data.Count; byte[] array = new byte[count]; if (count > 0) { Il2CppArrayBase array2 = data.Array; int offset = data.Offset; for (int i = 0; i < count; i++) { array[i] = array2[offset + i]; } } Dispatch(player, new ArraySegment(array)); } catch (Exception ex) { ManualLogSource log = Plugin.Log; bool flag = default(bool); BepInExErrorLogInterpolatedStringHandler val = new BepInExErrorLogInterpolatedStringHandler(29, 1, ref flag); if (flag) { ((BepInExLogInterpolatedStringHandler)val).AppendLiteral("OnReliableDataReceivedPatch: "); ((BepInExLogInterpolatedStringHandler)val).AppendFormatted(ex); } log.LogError(val); } return false; } } [HarmonyPatch(typeof(FusionConnection), "OnPlayerJoined")] internal static class OnPlayerJoinedPatch { [HarmonyPostfix] public static void Postfix(NetworkRunner runner, PlayerRef player) { //IL_0052: Unknown result type (might be due to invalid IL or missing references) //IL_0058: Expected O, but got Unknown //IL_001a: Unknown result type (might be due to invalid IL or missing references) //IL_0033: Unknown result type (might be due to invalid IL or missing references) //IL_003f: Unknown result type (might be due to invalid IL or missing references) try { int playerId = ((PlayerRef)(ref player)).PlayerId; if (_connectedPlayerIndexes.Add(playerId)) { _connectedPlayers.Add(player); } if (IsHost && (Object)(object)runner != (Object)null && !((PlayerRef)(ref player)).Equals(runner.LocalPlayer)) { OnPlayerJoinedHost(player); } } catch (Exception ex) { ManualLogSource log = Plugin.Log; bool flag = default(bool); BepInExErrorLogInterpolatedStringHandler val = new BepInExErrorLogInterpolatedStringHandler(21, 1, ref flag); if (flag) { ((BepInExLogInterpolatedStringHandler)val).AppendLiteral("OnPlayerJoinedPatch: "); ((BepInExLogInterpolatedStringHandler)val).AppendFormatted(ex); } log.LogError(val); } } } [HarmonyPatch(typeof(FusionConnection), "OnPlayerLeft")] internal static class OnPlayerLeftPatch { [HarmonyPostfix] public static void Postfix(NetworkRunner runner, PlayerRef player) { //IL_005a: Unknown result type (might be due to invalid IL or missing references) //IL_0061: Expected O, but got Unknown //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) try { int playerId = ((PlayerRef)(ref player)).PlayerId; if (!_connectedPlayerIndexes.Remove(playerId)) { return; } for (int num = _connectedPlayers.Count - 1; num >= 0; num--) { PlayerRef val = _connectedPlayers[num]; if (((PlayerRef)(ref val)).PlayerId == playerId) { _connectedPlayers.RemoveAt(num); } } } catch (Exception ex) { ManualLogSource log = Plugin.Log; bool flag = default(bool); BepInExErrorLogInterpolatedStringHandler val2 = new BepInExErrorLogInterpolatedStringHandler(19, 1, ref flag); if (flag) { ((BepInExLogInterpolatedStringHandler)val2).AppendLiteral("OnPlayerLeftPatch: "); ((BepInExLogInterpolatedStringHandler)val2).AppendFormatted(ex); } log.LogError(val2); } } } private const int KeyMagic = 1380207689; private static readonly HashSet _connectedPlayerIndexes = new HashSet(); private static readonly List _connectedPlayers = new List(); public static bool IsHost { get { try { return RunnerHolder.IsHost(); } catch { return false; } } } public static NetworkRunner Runner { get { try { return RunnerHolder.Get(); } catch { return null; } } } private static ReliableKey OurKey() { //IL_0008: Unknown result type (might be due to invalid IL or missing references) return ReliableKey.FromInts(1380207689, 0, 0, 0); } private static bool IsOurKey(ReliableKey key) { //IL_0000: Unknown result type (might be due to invalid IL or missing references) try { int num = default(int); int num2 = default(int); ulong num3 = default(ulong); PhotonReliableKeyExtensions.GetIntLongs(key, ref num, ref num2, ref num3); return num == 1380207689; } catch { return false; } } private static Il2CppStructArray ToIl2Cpp(byte[] payload) { Il2CppStructArray val = new Il2CppStructArray((long)payload.Length); for (int i = 0; i < payload.Length; i++) { ((Il2CppArrayBase)(object)val)[i] = payload[i]; } return val; } public static void SendToHost(byte[] payload) { //IL_003e: Unknown result type (might be due to invalid IL or missing references) //IL_0044: Expected O, but got Unknown //IL_0021: Unknown result type (might be due to invalid IL or missing references) NetworkRunner runner = Runner; if ((Object)(object)runner == (Object)null) { Plugin.Log.LogWarning((object)"SendToHost: no runner."); return; } try { runner.SendReliableDataToServer(OurKey(), ToIl2Cpp(payload)); } catch (Exception ex) { ManualLogSource log = Plugin.Log; bool flag = default(bool); BepInExErrorLogInterpolatedStringHandler val = new BepInExErrorLogInterpolatedStringHandler(33, 1, ref flag); if (flag) { ((BepInExLogInterpolatedStringHandler)val).AppendLiteral("SendReliableDataToServer failed: "); ((BepInExLogInterpolatedStringHandler)val).AppendFormatted(ex.Message); } log.LogError(val); } } public static void SendToPlayer(PlayerRef player, byte[] payload) { //IL_003f: Unknown result type (might be due to invalid IL or missing references) //IL_0045: Expected O, but got Unknown //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) NetworkRunner runner = Runner; if ((Object)(object)runner == (Object)null) { Plugin.Log.LogWarning((object)"SendToPlayer: no runner."); return; } try { runner.SendReliableDataToPlayer(player, OurKey(), ToIl2Cpp(payload)); } catch (Exception ex) { ManualLogSource log = Plugin.Log; bool flag = default(bool); BepInExErrorLogInterpolatedStringHandler val = new BepInExErrorLogInterpolatedStringHandler(33, 1, ref flag); if (flag) { ((BepInExLogInterpolatedStringHandler)val).AppendLiteral("SendReliableDataToPlayer failed: "); ((BepInExLogInterpolatedStringHandler)val).AppendFormatted(ex.Message); } log.LogError(val); } } public static void BroadcastToClients(byte[] payload) { //IL_0017: Unknown result type (might be due to invalid IL or missing references) if (!IsHost) { return; } foreach (PlayerRef connectedPlayer in _connectedPlayers) { SendToPlayer(connectedPlayer, payload); } } private static void OnPlayerJoinedHost(PlayerRef player) { //IL_000d: Unknown result type (might be due to invalid IL or missing references) //IL_001e: Unknown result type (might be due to invalid IL or missing references) //IL_0024: Expected O, but got Unknown //IL_00d8: Unknown result type (might be due to invalid IL or missing references) //IL_00df: Expected O, but got Unknown //IL_0033: Unknown result type (might be due to invalid IL or missing references) //IL_008c: Unknown result type (might be due to invalid IL or missing references) bool flag = default(bool); try { IReadOnlyList stations = RadioAPI.GetStations(); byte[] array = MessageCodec.BuildManifest(stations); SendToPlayer(player, array); ManualLogSource log = Plugin.Log; BepInExInfoLogInterpolatedStringHandler val = new BepInExInfoLogInterpolatedStringHandler(50, 3, ref flag); if (flag) { ((BepInExLogInterpolatedStringHandler)val).AppendLiteral("Sent manifest to new player "); ((BepInExLogInterpolatedStringHandler)val).AppendFormatted(player); ((BepInExLogInterpolatedStringHandler)val).AppendLiteral(": "); ((BepInExLogInterpolatedStringHandler)val).AppendFormatted(stations.Count); ((BepInExLogInterpolatedStringHandler)val).AppendLiteral(" station(s), "); ((BepInExLogInterpolatedStringHandler)val).AppendFormatted(array.Length); ((BepInExLogInterpolatedStringHandler)val).AppendLiteral(" bytes."); } log.LogInfo(val); foreach (RadioController.PlaybackSnapshot item in RadioController.SnapshotActiveCustomPlayback()) { SendToPlayer(player, MessageCodec.BuildTrackSelect(item.RadioId, item.StationIndex, item.TrackIndex, item.ElapsedSeconds)); } } catch (Exception ex) { ManualLogSource log2 = Plugin.Log; BepInExErrorLogInterpolatedStringHandler val2 = new BepInExErrorLogInterpolatedStringHandler(20, 1, ref flag); if (flag) { ((BepInExLogInterpolatedStringHandler)val2).AppendLiteral("OnPlayerJoinedHost: "); ((BepInExLogInterpolatedStringHandler)val2).AppendFormatted(ex); } log2.LogError(val2); } } internal static void Dispatch(PlayerRef sender, ArraySegment data) { //IL_0154: Unknown result type (might be due to invalid IL or missing references) //IL_015b: Expected O, but got Unknown //IL_0084: Unknown result type (might be due to invalid IL or missing references) if (data.Count < 1) { return; } MsgType msgType = (MsgType)data.Array[data.Offset]; try { switch (msgType) { case MsgType.Manifest: { int length = data.Count - 1; RadioAPI.ReplaceStationsFromManifest(Manifest.Deserialize(data.Array, data.Offset + 1, length)); RadioController.RefreshAllRadios(); break; } case MsgType.FileRequest: { if (IsHost && MessageCodec.TryParseFileRequest(data, out var hashHex2)) { TransferManager.HandleFileRequest(sender, hashHex2); } break; } case MsgType.FileData: { if (!IsHost && MessageCodec.TryParseFileData(data, out var hashHex3, out var fileBytes)) { TransferManager.HandleFileData(hashHex3, fileBytes); } break; } case MsgType.TrackSelect: { if (IsHost || !MessageCodec.TryParseTrackSelect(data, out var radioId, out var stationIndex, out var trackIndex, out var elapsedSeconds)) { break; } RadioController.ApplyRemoteTrackSelect(radioId, stationIndex, trackIndex, elapsedSeconds); StationDefinition customStation = RadioAPI.GetCustomStation(stationIndex); if (customStation != null && trackIndex >= 0 && trackIndex < customStation.Tracks.Count) { TrackInfo trackInfo = customStation.Tracks[trackIndex]; if (!trackInfo.Ready) { TransferManager.RequestTrackOnDemand(trackInfo.Hash); } } break; } case MsgType.PrefetchHint: { if (!IsHost && MessageCodec.TryParsePrefetchHint(data, out var hashHex)) { TransferManager.RequestTrackOnDemand(hashHex, DecodePriority.Prefetch); } break; } } } catch (Exception ex) { ManualLogSource log = Plugin.Log; bool flag = default(bool); BepInExErrorLogInterpolatedStringHandler val = new BepInExErrorLogInterpolatedStringHandler(18, 2, ref flag); if (flag) { ((BepInExLogInterpolatedStringHandler)val).AppendLiteral("Dispatch("); ((BepInExLogInterpolatedStringHandler)val).AppendFormatted(msgType); ((BepInExLogInterpolatedStringHandler)val).AppendLiteral(") threw: "); ((BepInExLogInterpolatedStringHandler)val).AppendFormatted(ex); } log.LogError(val); } } } internal static class TransferManager { private static readonly HashSet _pendingRequests = new HashSet(StringComparer.OrdinalIgnoreCase); public static void RequestTrackOnDemand(string hash, DecodePriority priority = DecodePriority.Immediate) { //IL_0058: Unknown result type (might be due to invalid IL or missing references) //IL_005f: Expected O, but got Unknown //IL_010f: Unknown result type (might be due to invalid IL or missing references) //IL_0116: Expected O, but got Unknown //IL_00b4: Unknown result type (might be due to invalid IL or missing references) //IL_00bb: Expected O, but got Unknown if (string.IsNullOrEmpty(hash)) { return; } TrackInfo trackInfo = RadioAPI.TryGetTrackByHash(hash); if (trackInfo == null || trackInfo.Ready) { return; } bool flag = default(bool); if (AudioCache.TryRead(hash, trackInfo.Extension, out var bytes)) { string text = AudioCache.PathFor(hash, trackInfo.Extension); if (!File.Exists(text)) { try { File.WriteAllBytes(text, bytes); } catch (Exception ex) { ManualLogSource log = Plugin.Log; BepInExWarningLogInterpolatedStringHandler val = new BepInExWarningLogInterpolatedStringHandler(36, 2, ref flag); if (flag) { ((BepInExLogInterpolatedStringHandler)val).AppendLiteral("Could not materialize cache for '"); ((BepInExLogInterpolatedStringHandler)val).AppendFormatted(trackInfo.Name); ((BepInExLogInterpolatedStringHandler)val).AppendLiteral("': "); ((BepInExLogInterpolatedStringHandler)val).AppendFormatted(ex.Message); } log.LogWarning(val); return; } } DecodeService.Request(trackInfo, text, priority); ManualLogSource log2 = Plugin.Log; BepInExInfoLogInterpolatedStringHandler val2 = new BepInExInfoLogInterpolatedStringHandler(36, 1, ref flag); if (flag) { ((BepInExLogInterpolatedStringHandler)val2).AppendLiteral("Cache hit for '"); ((BepInExLogInterpolatedStringHandler)val2).AppendFormatted(trackInfo.Name); ((BepInExLogInterpolatedStringHandler)val2).AppendLiteral("' — decoding locally."); } log2.LogInfo(val2); } else if (_pendingRequests.Add(hash)) { RadioLibNet.SendToHost(MessageCodec.BuildFileRequest(hash)); ManualLogSource log3 = Plugin.Log; BepInExInfoLogInterpolatedStringHandler val2 = new BepInExInfoLogInterpolatedStringHandler(26, 2, ref flag); if (flag) { ((BepInExLogInterpolatedStringHandler)val2).AppendLiteral("Requested '"); ((BepInExLogInterpolatedStringHandler)val2).AppendFormatted(trackInfo.Name); ((BepInExLogInterpolatedStringHandler)val2).AppendLiteral("' ("); ((BepInExLogInterpolatedStringHandler)val2).AppendFormatted(hash.Substring(0, 8)); ((BepInExLogInterpolatedStringHandler)val2).AppendLiteral(") from host."); } log3.LogInfo(val2); } } public static void HandleFileRequest(PlayerRef requester, string hashHex) { //IL_000e: Unknown result type (might be due to invalid IL or missing references) //IL_000f: Unknown result type (might be due to invalid IL or missing references) //IL_003a: Unknown result type (might be due to invalid IL or missing references) //IL_0040: Expected O, but got Unknown //IL_0073: Unknown result type (might be due to invalid IL or missing references) TrackInfo trackInfo = RadioAPI.TryGetTrackByHash(hashHex); if (trackInfo == null || string.IsNullOrEmpty(trackInfo.SourcePath)) { ManualLogSource log = Plugin.Log; bool flag = default(bool); BepInExWarningLogInterpolatedStringHandler val = new BepInExWarningLogInterpolatedStringHandler(55, 1, ref flag); if (flag) { ((BepInExLogInterpolatedStringHandler)val).AppendLiteral("File request for unknown hash "); ((BepInExLogInterpolatedStringHandler)val).AppendFormatted(hashHex.Substring(0, 8)); ((BepInExLogInterpolatedStringHandler)val).AppendLiteral("; sending empty response."); } log.LogWarning(val); RadioLibNet.SendToPlayer(requester, MessageCodec.BuildFileData(hashHex, Array.Empty())); return; } string path = trackInfo.SourcePath; string name = trackInfo.Name; Task.Run(delegate { //IL_002b: Unknown result type (might be due to invalid IL or missing references) //IL_0031: Expected O, but got Unknown byte[] bytes; try { bytes = File.ReadAllBytes(path); } catch (Exception ex) { ManualLogSource log2 = Plugin.Log; bool flag2 = default(bool); BepInExWarningLogInterpolatedStringHandler val2 = new BepInExWarningLogInterpolatedStringHandler(19, 2, ref flag2); if (flag2) { ((BepInExLogInterpolatedStringHandler)val2).AppendLiteral("Could not read '"); ((BepInExLogInterpolatedStringHandler)val2).AppendFormatted(path); ((BepInExLogInterpolatedStringHandler)val2).AppendLiteral("': "); ((BepInExLogInterpolatedStringHandler)val2).AppendFormatted(ex.Message); } log2.LogWarning(val2); bytes = Array.Empty(); } byte[] msg = MessageCodec.BuildFileData(hashHex, bytes); MainThread.Enqueue(delegate { //IL_0006: Unknown result type (might be due to invalid IL or missing references) //IL_0020: Unknown result type (might be due to invalid IL or missing references) //IL_0026: Expected O, but got Unknown //IL_0070: Unknown result type (might be due to invalid IL or missing references) RadioLibNet.SendToPlayer(requester, msg); ManualLogSource log3 = Plugin.Log; bool flag3 = default(bool); BepInExInfoLogInterpolatedStringHandler val3 = new BepInExInfoLogInterpolatedStringHandler(28, 3, ref flag3); if (flag3) { ((BepInExLogInterpolatedStringHandler)val3).AppendLiteral("Sent '"); ((BepInExLogInterpolatedStringHandler)val3).AppendFormatted(name); ((BepInExLogInterpolatedStringHandler)val3).AppendLiteral("' ("); ((BepInExLogInterpolatedStringHandler)val3).AppendFormatted(bytes.Length); ((BepInExLogInterpolatedStringHandler)val3).AppendLiteral(" bytes) to player "); ((BepInExLogInterpolatedStringHandler)val3).AppendFormatted(requester); ((BepInExLogInterpolatedStringHandler)val3).AppendLiteral("."); } log3.LogInfo(val3); }); }); } public static void HandleFileData(string hashHex, byte[] fileBytes) { //IL_0020: Unknown result type (might be due to invalid IL or missing references) //IL_0026: Expected O, but got Unknown //IL_0066: Unknown result type (might be due to invalid IL or missing references) //IL_006c: Expected O, but got Unknown //IL_0146: Unknown result type (might be due to invalid IL or missing references) //IL_014d: Expected O, but got Unknown //IL_00b4: Unknown result type (might be due to invalid IL or missing references) //IL_00ba: Expected O, but got Unknown _pendingRequests.Remove(hashHex); TrackInfo trackInfo = RadioAPI.TryGetTrackByHash(hashHex); bool flag = default(bool); if (trackInfo == null) { ManualLogSource log = Plugin.Log; BepInExWarningLogInterpolatedStringHandler val = new BepInExWarningLogInterpolatedStringHandler(47, 1, ref flag); if (flag) { ((BepInExLogInterpolatedStringHandler)val).AppendLiteral("Received file data for unknown hash "); ((BepInExLogInterpolatedStringHandler)val).AppendFormatted(hashHex.Substring(0, 8)); ((BepInExLogInterpolatedStringHandler)val).AppendLiteral("; dropping."); } log.LogWarning(val); return; } if (fileBytes == null || fileBytes.Length == 0) { ManualLogSource log2 = Plugin.Log; BepInExWarningLogInterpolatedStringHandler val = new BepInExWarningLogInterpolatedStringHandler(43, 1, ref flag); if (flag) { ((BepInExLogInterpolatedStringHandler)val).AppendLiteral("Host returned empty data for '"); ((BepInExLogInterpolatedStringHandler)val).AppendFormatted(trackInfo.Name); ((BepInExLogInterpolatedStringHandler)val).AppendLiteral("' — skipping."); } log2.LogWarning(val); return; } string text = FolderScanner.ComputeSha256(fileBytes); if (!string.Equals(text, hashHex, StringComparison.OrdinalIgnoreCase)) { ManualLogSource log3 = Plugin.Log; BepInExWarningLogInterpolatedStringHandler val = new BepInExWarningLogInterpolatedStringHandler(38, 3, ref flag); if (flag) { ((BepInExLogInterpolatedStringHandler)val).AppendLiteral("Hash mismatch for '"); ((BepInExLogInterpolatedStringHandler)val).AppendFormatted(trackInfo.Name); ((BepInExLogInterpolatedStringHandler)val).AppendLiteral("': expected "); ((BepInExLogInterpolatedStringHandler)val).AppendFormatted(hashHex.Substring(0, 8)); ((BepInExLogInterpolatedStringHandler)val).AppendLiteral(", got "); ((BepInExLogInterpolatedStringHandler)val).AppendFormatted(text.Substring(0, 8)); ((BepInExLogInterpolatedStringHandler)val).AppendLiteral("."); } log3.LogWarning(val); return; } AudioCache.Store(hashHex, trackInfo.Extension, fileBytes); string filePath = AudioCache.PathFor(hashHex, trackInfo.Extension); DecodeService.Request(trackInfo, filePath, DecodePriority.Immediate); ManualLogSource log4 = Plugin.Log; BepInExInfoLogInterpolatedStringHandler val2 = new BepInExInfoLogInterpolatedStringHandler(32, 2, ref flag); if (flag) { ((BepInExLogInterpolatedStringHandler)val2).AppendLiteral("Received '"); ((BepInExLogInterpolatedStringHandler)val2).AppendFormatted(trackInfo.Name); ((BepInExLogInterpolatedStringHandler)val2).AppendLiteral("' ("); ((BepInExLogInterpolatedStringHandler)val2).AppendFormatted(fileBytes.Length); ((BepInExLogInterpolatedStringHandler)val2).AppendLiteral(" bytes) — decoding."); } log4.LogInfo(val2); } } }