using System; using System.Collections; using System.Collections.Concurrent; using System.Collections.Generic; using System.Diagnostics; using System.IO; using System.Reflection; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using System.Runtime.Versioning; using System.Threading; using BepInEx; using BepInEx.Logging; using FishNet; using FishNet.Broadcast; using FishNet.Connection; using FishNet.Managing.Object; using FishNet.Object; using FishNet.Serializing; using FishNet.Transporting; using UnityEngine; using UnityEngine.Networking; [assembly: CompilationRelaxations(8)] [assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)] [assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)] [assembly: TargetFramework(".NETFramework,Version=v4.7.2", FrameworkDisplayName = ".NET Framework 4.7.2")] [assembly: AssemblyCompany("HowToFishCustomRadio")] [assembly: AssemblyConfiguration("Release")] [assembly: AssemblyFileVersion("1.0.0.0")] [assembly: AssemblyInformationalVersion("1.0.0")] [assembly: AssemblyProduct("HowToFishCustomRadio")] [assembly: AssemblyTitle("HowToFishCustomRadio")] [assembly: AssemblyVersion("1.0.0.0")] namespace CustomRadioFramework; public struct MusicChunkBroadcast : IBroadcast { public int trackId; public int chunkIndex; public int totalChunks; public byte[] data; } public struct MusicControlBroadcast : IBroadcast { public bool isPaused; public float timePosition; } public struct MusicVolumeBroadcast : IBroadcast { public float volume; } public static class CustomSerializers { public static void WriteMusicChunkBroadcast(this Writer writer, MusicChunkBroadcast msg) { writer.Write(msg.trackId); writer.Write(msg.chunkIndex); writer.Write(msg.totalChunks); writer.Write(msg.data); } public static MusicChunkBroadcast ReadMusicChunkBroadcast(this Reader reader) { return new MusicChunkBroadcast { trackId = reader.Read(), chunkIndex = reader.Read(), totalChunks = reader.Read(), data = reader.Read() }; } public static void WriteMusicControlBroadcast(this Writer writer, MusicControlBroadcast msg) { writer.Write(msg.isPaused); writer.Write(msg.timePosition); } public static MusicControlBroadcast ReadMusicControlBroadcast(this Reader reader) { return new MusicControlBroadcast { isPaused = reader.Read(), timePosition = reader.Read() }; } public static void WriteMusicVolumeBroadcast(this Writer writer, MusicVolumeBroadcast msg) { writer.Write(msg.volume); } public static MusicVolumeBroadcast ReadMusicVolumeBroadcast(this Reader reader) { return new MusicVolumeBroadcast { volume = reader.Read() }; } } [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Auto)] public class OpenFileName { public int structSize; public IntPtr hwndOwner = IntPtr.Zero; public IntPtr hInstance = IntPtr.Zero; public string filter; public string customFilter; public int maxCustFilter; public int filterIndex; public string file; public int maxFile; public string fileTitle; public int maxFileTitle; public string initialDir; public string title; public int flags; public short fileOffset; public short fileExtension; public string defExt; public IntPtr custData = IntPtr.Zero; public IntPtr hook = IntPtr.Zero; public string templateName; public IntPtr reservedPtr = IntPtr.Zero; public int reservedInt; public int flagsEx; } public class LocalDialog { [DllImport("Comdlg32.dll", CharSet = CharSet.Auto, SetLastError = true, ThrowOnUnmappableChar = true)] public static extern bool GetOpenFileName([In][Out] OpenFileName ofn); } [BepInPlugin("com.customradio.howtofish", "Custom Radio Framework", "0.1.2")] public class Plugin : BaseUnityPlugin { public static ManualLogSource Log; public static float LocalIgnoreNetworkTime; private CustomRadioNetworkService networkService; private CustomRadioAudioController audioController; private CustomRadioUI uiManager; private void Awake() { Log = ((BaseUnityPlugin)this).Logger; Log.LogInfo((object)"Custom Radio v0.1.2 (Standard) загружен. Защита Steamworks включена."); CleanupOldTempFiles(); audioController = new CustomRadioAudioController(this); networkService = new CustomRadioNetworkService(this, audioController); uiManager = new CustomRadioUI(this, networkService, audioController); ((MonoBehaviour)this).StartCoroutine(WaitForNetwork()); } private IEnumerator WaitForNetwork() { while ((Object)(object)InstanceFinder.NetworkManager == (Object)null || (Object)(object)InstanceFinder.ClientManager == (Object)null) { yield return null; } networkService.RegisterCallbacks(); } private void Update() { uiManager.HandleInput(); networkService.ProcessTimeouts(); } private void OnGUI() { uiManager.DrawUI(); } private void OnDestroy() { networkService?.UnregisterCallbacks(); audioController?.Dispose(); CleanupOldTempFiles(); } private void CleanupOldTempFiles() { try { string[] files = Directory.GetFiles(Application.temporaryCachePath, "radio_sync_*.mp3"); for (int i = 0; i < files.Length; i++) { File.Delete(files[i]); } } catch { } } } public class CustomRadioAudioController : IDisposable { private Plugin plugin; public AudioSource Source { get; private set; } public bool IsPaused { get; private set; } public float CurrentVolume { get; set; } = 1f; public CustomRadioAudioController(Plugin plugin) { this.plugin = plugin; } public void EnsureRadioExists() { //IL_010e: Unknown result type (might be due to invalid IL or missing references) //IL_0137: Unknown result type (might be due to invalid IL or missing references) //IL_0080: 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) if ((Object)(object)Source != (Object)null) { return; } GameObject val = null; GameObject val2 = GameObject.Find("LocalPlayer"); float num = float.MaxValue; if ((Object)(object)InstanceFinder.ClientManager != (Object)null) { foreach (NetworkObject value in ((ManagedObjects)InstanceFinder.ClientManager.Objects).Spawned.Values) { if (((Object)((Component)value).gameObject).name.IndexOf("Radio", StringComparison.OrdinalIgnoreCase) >= 0) { if (!((Object)(object)val2 != (Object)null)) { val = ((Component)value).gameObject; break; } float num2 = Vector3.Distance(val2.transform.position, ((Component)value).transform.position); if (num2 < num) { num = num2; val = ((Component)value).gameObject; } } } } if ((Object)(object)val == (Object)null && (Object)(object)val2 != (Object)null) { val = val2; } if ((Object)(object)val != (Object)null) { Transform val3 = val.transform.Find("CustomRadioSource"); GameObject val4 = (GameObject)(((Object)(object)val3 != (Object)null) ? ((object)((Component)val3).gameObject) : ((object)new GameObject("CustomRadioSource"))); val4.transform.SetParent(val.transform); val4.transform.localPosition = Vector3.zero; Source = val4.GetComponent() ?? val4.AddComponent(); Source.spatialBlend = 1f; Source.minDistance = 3f; Source.maxDistance = 40f; Source.rolloffMode = (AudioRolloffMode)1; Source.volume = CurrentVolume; } } public void PlayLocalFile(string path) { ((MonoBehaviour)plugin).StartCoroutine(LoadAndPlayAudio("file:///" + path.Replace("\\", "/"))); } private IEnumerator LoadAndPlayAudio(string url) { UnityWebRequest www = UnityWebRequestMultimedia.GetAudioClip(url, (AudioType)13); try { yield return www.SendWebRequest(); if ((Object)(object)Source == (Object)null) { yield break; } if ((int)www.result == 1) { AudioClip content = DownloadHandlerAudioClip.GetContent(www); if ((Object)(object)Source.clip != (Object)null) { Object.Destroy((Object)(object)Source.clip); } Source.clip = content; IsPaused = false; Source.Play(); } } finally { ((IDisposable)www)?.Dispose(); } } public void ApplyControlState(bool pauseState, float timePosition) { if (!((Object)(object)Source == (Object)null)) { IsPaused = pauseState; Source.time = timePosition; if (IsPaused) { Source.Pause(); } else { Source.Play(); } } } public void ApplyVolume(float vol) { CurrentVolume = vol; if ((Object)(object)Source != (Object)null) { Source.volume = CurrentVolume; } } public void Dispose() { if ((Object)(object)Source != (Object)null && (Object)(object)Source.clip != (Object)null) { Object.Destroy((Object)(object)Source.clip); } } } public class CustomRadioNetworkService { private class DownloadContext { public FileStream Stream; public int ExpectedChunks; public HashSet ReceivedChunks; public float LastActivityTime; public string TempPath; } public const int CHUNK_SIZE = 8192; private const int MAX_FILE_SIZE = 15728640; private const float DOWNLOAD_TIMEOUT = 120f; private Plugin plugin; private CustomRadioAudioController audio; private bool isRegistered; private Dictionary activeDownloads = new Dictionary(); public CustomRadioNetworkService(Plugin plugin, CustomRadioAudioController audio) { this.plugin = plugin; this.audio = audio; } public void RegisterCallbacks() { if (!isRegistered) { GenericWriter.SetWrite((Action)CustomSerializers.WriteMusicChunkBroadcast); GenericReader.SetRead((Func)CustomSerializers.ReadMusicChunkBroadcast); GenericWriter.SetWrite((Action)CustomSerializers.WriteMusicControlBroadcast); GenericReader.SetRead((Func)CustomSerializers.ReadMusicControlBroadcast); GenericWriter.SetWrite((Action)CustomSerializers.WriteMusicVolumeBroadcast); GenericReader.SetRead((Func)CustomSerializers.ReadMusicVolumeBroadcast); InstanceFinder.ClientManager.RegisterBroadcast((Action)OnClientReceiveChunk); InstanceFinder.ServerManager.RegisterBroadcast((Action)OnServerReceiveChunk, true); InstanceFinder.ClientManager.RegisterBroadcast((Action)OnClientReceiveControl); InstanceFinder.ServerManager.RegisterBroadcast((Action)OnServerReceiveControl, true); InstanceFinder.ClientManager.RegisterBroadcast((Action)OnClientReceiveVolume); InstanceFinder.ServerManager.RegisterBroadcast((Action)OnServerReceiveVolume, true); isRegistered = true; } } public void UnregisterCallbacks() { if (!isRegistered) { return; } if ((Object)(object)InstanceFinder.ClientManager != (Object)null) { InstanceFinder.ClientManager.UnregisterBroadcast((Action)OnClientReceiveChunk); InstanceFinder.ClientManager.UnregisterBroadcast((Action)OnClientReceiveControl); InstanceFinder.ClientManager.UnregisterBroadcast((Action)OnClientReceiveVolume); } if ((Object)(object)InstanceFinder.ServerManager != (Object)null) { InstanceFinder.ServerManager.UnregisterBroadcast((Action)OnServerReceiveChunk); InstanceFinder.ServerManager.UnregisterBroadcast((Action)OnServerReceiveControl); InstanceFinder.ServerManager.UnregisterBroadcast((Action)OnServerReceiveVolume); } foreach (DownloadContext value in activeDownloads.Values) { value.Stream.Dispose(); if (File.Exists(value.TempPath)) { File.Delete(value.TempPath); } } activeDownloads.Clear(); } public void SendFile(string path) { ((MonoBehaviour)plugin).StartCoroutine(StreamFileCoroutine(path)); } private IEnumerator StreamFileCoroutine(string path) { if ((Object)(object)InstanceFinder.ClientManager == (Object)null) { yield break; } FileInfo fileInfo = new FileInfo(path); if (fileInfo.Length > 15728640) { yield break; } int trackId = Random.Range(1000, 999999); int totalChunks = Mathf.CeilToInt((float)fileInfo.Length / 8192f); using FileStream fs = new FileStream(path, FileMode.Open, FileAccess.Read); byte[] buffer = new byte[8192]; int chunkIndex = 0; int num; while ((num = fs.Read(buffer, 0, buffer.Length)) > 0) { byte[] array; if (num < 8192) { array = new byte[num]; Buffer.BlockCopy(buffer, 0, array, 0, num); } else { array = buffer; } InstanceFinder.ClientManager.Broadcast(new MusicChunkBroadcast { trackId = trackId, chunkIndex = chunkIndex, totalChunks = totalChunks, data = array }, (Channel)0); chunkIndex++; yield return (object)new WaitForSeconds(0.03f); } } public void SendControl(bool isPaused, float time) { if ((Object)(object)InstanceFinder.ClientManager != (Object)null) { InstanceFinder.ClientManager.Broadcast(new MusicControlBroadcast { isPaused = isPaused, timePosition = time }, (Channel)0); } } public void SendVolume(float vol) { if ((Object)(object)InstanceFinder.ClientManager != (Object)null) { InstanceFinder.ClientManager.Broadcast(new MusicVolumeBroadcast { volume = vol }, (Channel)0); } } private void OnServerReceiveChunk(NetworkConnection conn, MusicChunkBroadcast msg, Channel channel) { if (msg.data != null && msg.data.Length <= 9216) { InstanceFinder.ServerManager.Broadcast(msg, true, (Channel)0); } } private void OnServerReceiveControl(NetworkConnection conn, MusicControlBroadcast msg, Channel channel) { InstanceFinder.ServerManager.Broadcast(msg, true, (Channel)0); } private void OnServerReceiveVolume(NetworkConnection conn, MusicVolumeBroadcast msg, Channel channel) { InstanceFinder.ServerManager.Broadcast(msg, true, (Channel)0); } private void OnClientReceiveChunk(MusicChunkBroadcast msg, Channel channel) { if (!activeDownloads.TryGetValue(msg.trackId, out var value)) { string text = Path.Combine(Application.temporaryCachePath, $"radio_sync_{msg.trackId}.mp3"); value = new DownloadContext { TempPath = text, Stream = new FileStream(text, FileMode.Create, FileAccess.Write, FileShare.None), ExpectedChunks = msg.totalChunks, ReceivedChunks = new HashSet(), LastActivityTime = Time.realtimeSinceStartup }; activeDownloads[msg.trackId] = value; } value.Stream.Seek((long)msg.chunkIndex * 8192L, SeekOrigin.Begin); value.Stream.Write(msg.data, 0, msg.data.Length); value.ReceivedChunks.Add(msg.chunkIndex); value.LastActivityTime = Time.realtimeSinceStartup; if (value.ReceivedChunks.Count >= value.ExpectedChunks) { value.Stream.Dispose(); activeDownloads.Remove(msg.trackId); audio.PlayLocalFile(value.TempPath); } } private void OnClientReceiveControl(MusicControlBroadcast msg, Channel channel) { if (!(Time.realtimeSinceStartup - Plugin.LocalIgnoreNetworkTime < 0.8f)) { audio.ApplyControlState(msg.isPaused, msg.timePosition); } } private void OnClientReceiveVolume(MusicVolumeBroadcast msg, Channel channel) { if (!(Time.realtimeSinceStartup - Plugin.LocalIgnoreNetworkTime < 0.8f)) { audio.ApplyVolume(msg.volume); } } public void ProcessTimeouts() { if (activeDownloads.Count == 0) { return; } List list = null; float realtimeSinceStartup = Time.realtimeSinceStartup; foreach (KeyValuePair activeDownload in activeDownloads) { if (realtimeSinceStartup - activeDownload.Value.LastActivityTime > 120f) { if (list == null) { list = new List(); } list.Add(activeDownload.Key); } } if (list == null) { return; } foreach (int item in list) { DownloadContext downloadContext = activeDownloads[item]; downloadContext.Stream.Dispose(); if (File.Exists(downloadContext.TempPath)) { File.Delete(downloadContext.TempPath); } activeDownloads.Remove(item); } } } public class CustomRadioUI { private Plugin plugin; private CustomRadioNetworkService network; private CustomRadioAudioController audio; private bool showUI; private bool isDialogOpen; private ConcurrentQueue pendingFiles = new ConcurrentQueue(); private float lastSeekSendTime; private float lastVolumeSendTime; private GUIStyle windowStyle; private GUIStyle btnStyle; private GUIStyle labelStyle; private GUIStyle sliderTrackStyle; private GUIStyle sliderThumbStyle; private bool stylesInitialized; public CustomRadioUI(Plugin plugin, CustomRadioNetworkService network, CustomRadioAudioController audio) { this.plugin = plugin; this.network = network; this.audio = audio; } private void InitStyles() { //IL_0014: Unknown result type (might be due to invalid IL or missing references) //IL_001e: Expected O, but got Unknown //IL_0040: Unknown result type (might be due to invalid IL or missing references) //IL_005a: Unknown result type (might be due to invalid IL or missing references) //IL_0094: Unknown result type (might be due to invalid IL or missing references) //IL_009e: Expected O, but got Unknown //IL_00c0: Unknown result type (might be due to invalid IL or missing references) //IL_00f1: Unknown result type (might be due to invalid IL or missing references) //IL_0122: Unknown result type (might be due to invalid IL or missing references) //IL_013c: Unknown result type (might be due to invalid IL or missing references) //IL_015d: Unknown result type (might be due to invalid IL or missing references) //IL_0167: Expected O, but got Unknown //IL_0186: Unknown result type (might be due to invalid IL or missing references) //IL_01b4: Unknown result type (might be due to invalid IL or missing references) //IL_01be: Expected O, but got Unknown //IL_01e0: Unknown result type (might be due to invalid IL or missing references) //IL_020a: Unknown result type (might be due to invalid IL or missing references) //IL_0214: Expected O, but got Unknown //IL_0236: Unknown result type (might be due to invalid IL or missing references) //IL_0253: Unknown result type (might be due to invalid IL or missing references) if (!stylesInitialized) { windowStyle = new GUIStyle(GUI.skin.box); windowStyle.normal.background = MakeTex(2, 2, new Color(0.08f, 0.08f, 0.08f, 0.95f)); windowStyle.normal.textColor = Color.white; windowStyle.alignment = (TextAnchor)1; windowStyle.fontStyle = (FontStyle)1; windowStyle.fontSize = 14; btnStyle = new GUIStyle(GUI.skin.button); btnStyle.normal.background = MakeTex(2, 2, new Color(0.18f, 0.18f, 0.18f, 1f)); btnStyle.hover.background = MakeTex(2, 2, new Color(0.35f, 0.35f, 0.35f, 1f)); btnStyle.active.background = MakeTex(2, 2, new Color(0.5f, 0.5f, 0.5f, 1f)); btnStyle.normal.textColor = Color.white; btnStyle.fontStyle = (FontStyle)1; labelStyle = new GUIStyle(GUI.skin.label); labelStyle.normal.textColor = new Color(0.85f, 0.85f, 0.85f, 1f); labelStyle.alignment = (TextAnchor)4; labelStyle.fontSize = 13; sliderTrackStyle = new GUIStyle(GUI.skin.horizontalSlider); sliderTrackStyle.normal.background = MakeTex(2, 2, new Color(0.2f, 0.2f, 0.2f, 1f)); sliderTrackStyle.fixedHeight = 6f; sliderThumbStyle = new GUIStyle(GUI.skin.horizontalSliderThumb); sliderThumbStyle.normal.background = MakeTex(2, 2, new Color(0.8f, 0.8f, 0.8f, 1f)); sliderThumbStyle.hover.background = MakeTex(2, 2, Color.white); sliderThumbStyle.fixedWidth = 14f; sliderThumbStyle.fixedHeight = 14f; stylesInitialized = true; } } private Texture2D MakeTex(int width, int height, Color col) { //IL_000f: Unknown result type (might be due to invalid IL or missing references) //IL_0010: Unknown result type (might be due to invalid IL or missing references) //IL_0021: Unknown result type (might be due to invalid IL or missing references) //IL_0026: Unknown result type (might be due to invalid IL or missing references) //IL_002d: Unknown result type (might be due to invalid IL or missing references) //IL_0034: Expected O, but got Unknown Color[] array = (Color[])(object)new Color[width * height]; for (int i = 0; i < array.Length; i++) { array[i] = col; } Texture2D val = new Texture2D(width, height); val.SetPixels(array); val.Apply(); return val; } public void HandleInput() { if (Input.GetKeyDown((KeyCode)121)) { showUI = !showUI; audio.EnsureRadioExists(); } if (pendingFiles.TryDequeue(out var result)) { network.SendFile(result); } } public void DrawUI() { //IL_0023: 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_00ce: Unknown result type (might be due to invalid IL or missing references) //IL_00f9: Unknown result type (might be due to invalid IL or missing references) //IL_0187: Unknown result type (might be due to invalid IL or missing references) //IL_020e: Unknown result type (might be due to invalid IL or missing references) //IL_0275: Unknown result type (might be due to invalid IL or missing references) //IL_02e8: Unknown result type (might be due to invalid IL or missing references) //IL_0337: Unknown result type (might be due to invalid IL or missing references) if (!showUI) { return; } InitStyles(); GUI.Box(new Rect(20f, 20f, 320f, 200f), "\nC U S T O M R A D I O", windowStyle); if (GUI.Button(new Rect(40f, 55f, 280f, 25f), "Загрузить MP3", btnStyle)) { OpenWindowsExplorerSafe(); } AudioSource source = audio.Source; if (!((Object)(object)source != (Object)null) || !((Object)(object)source.clip != (Object)null)) { return; } string text = FormatTime(source.time) + " / " + FormatTime(source.clip.length); GUI.Label(new Rect(40f, 85f, 280f, 20f), text, labelStyle); GUI.changed = false; float time = GUI.HorizontalSlider(new Rect(40f, 105f, 280f, 15f), source.time, 0f, source.clip.length, sliderTrackStyle, sliderThumbStyle); if (GUI.changed) { source.time = time; Plugin.LocalIgnoreNetworkTime = Time.realtimeSinceStartup; if (Time.realtimeSinceStartup - lastSeekSendTime > 0.1f) { lastSeekSendTime = Time.realtimeSinceStartup; network.SendControl(audio.IsPaused, time); } } if (GUI.Button(new Rect(100f, 130f, 45f, 25f), "<<", btnStyle)) { float time2 = (source.time = Mathf.Max(0f, source.time - 15f)); Plugin.LocalIgnoreNetworkTime = Time.realtimeSinceStartup; network.SendControl(audio.IsPaused, time2); } string text2 = (audio.IsPaused ? "Play" : "Pause"); if (GUI.Button(new Rect(155f, 130f, 50f, 25f), text2, btnStyle)) { bool flag = !audio.IsPaused; audio.ApplyControlState(flag, source.time); Plugin.LocalIgnoreNetworkTime = Time.realtimeSinceStartup; network.SendControl(flag, source.time); } if (GUI.Button(new Rect(215f, 130f, 45f, 25f), ">>", btnStyle)) { float time3 = (source.time = Mathf.Min(source.clip.length, source.time + 15f)); Plugin.LocalIgnoreNetworkTime = Time.realtimeSinceStartup; network.SendControl(audio.IsPaused, time3); } GUI.Label(new Rect(40f, 165f, 70f, 20f), $"Vol: {Mathf.RoundToInt(audio.CurrentVolume * 100f)}%", labelStyle); GUI.changed = false; float vol = GUI.HorizontalSlider(new Rect(110f, 170f, 210f, 15f), audio.CurrentVolume, 0f, 1f, sliderTrackStyle, sliderThumbStyle); if (GUI.changed) { audio.ApplyVolume(vol); Plugin.LocalIgnoreNetworkTime = Time.realtimeSinceStartup; if (Time.realtimeSinceStartup - lastVolumeSendTime > 0.1f) { lastVolumeSendTime = Time.realtimeSinceStartup; network.SendVolume(vol); } } } private string FormatTime(float timeInSeconds) { int num = Mathf.FloorToInt(timeInSeconds / 60f); int num2 = Mathf.FloorToInt(timeInSeconds % 60f); return $"{num:00}:{num2:00}"; } private void OpenWindowsExplorerSafe() { if (isDialogOpen) { return; } isDialogOpen = true; new Thread((ThreadStart)delegate { OpenFileName openFileName = new OpenFileName(); openFileName.structSize = Marshal.SizeOf(openFileName); openFileName.filter = "MP3 Files (*.mp3)\0*.mp3\0"; openFileName.file = new string(new char[1024]); openFileName.maxFile = openFileName.file.Length; openFileName.title = "Выберите песню для радио"; openFileName.flags = 528392; if (LocalDialog.GetOpenFileName(openFileName)) { pendingFiles.Enqueue(openFileName.file.TrimEnd(new char[1])); } isDialogOpen = false; }).Start(); } }