using System;
using System.Collections;
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.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 RadioUploadChunkMsg : IBroadcast
{
public int trackId;
public int chunkIndex;
public int totalChunks;
public byte[] data;
}
public struct RadioPlayChunkMsg : IBroadcast
{
public int trackId;
public int chunkIndex;
public int totalChunks;
public byte[] data;
}
public struct RadioControlMsg : IBroadcast
{
public bool isPaused;
public float timePosition;
}
public struct RadioVolumeMsg : IBroadcast
{
public float volume;
}
public struct SyncRolesMsg : IBroadcast
{
public int[] modIds;
}
public static class CustomSerializers
{
public static void WriteUploadChunk(this Writer writer, RadioUploadChunkMsg msg)
{
writer.Write<int>(msg.trackId);
writer.Write<int>(msg.chunkIndex);
writer.Write<int>(msg.totalChunks);
writer.Write<byte[]>(msg.data);
}
public static RadioUploadChunkMsg ReadUploadChunk(this Reader reader)
{
return new RadioUploadChunkMsg
{
trackId = reader.Read<int>(),
chunkIndex = reader.Read<int>(),
totalChunks = reader.Read<int>(),
data = reader.Read<byte[]>()
};
}
public static void WritePlayChunk(this Writer writer, RadioPlayChunkMsg msg)
{
writer.Write<int>(msg.trackId);
writer.Write<int>(msg.chunkIndex);
writer.Write<int>(msg.totalChunks);
writer.Write<byte[]>(msg.data);
}
public static RadioPlayChunkMsg ReadPlayChunk(this Reader reader)
{
return new RadioPlayChunkMsg
{
trackId = reader.Read<int>(),
chunkIndex = reader.Read<int>(),
totalChunks = reader.Read<int>(),
data = reader.Read<byte[]>()
};
}
public static void WriteControl(this Writer writer, RadioControlMsg msg)
{
writer.Write<bool>(msg.isPaused);
writer.Write<float>(msg.timePosition);
}
public static RadioControlMsg ReadControl(this Reader reader)
{
return new RadioControlMsg
{
isPaused = reader.Read<bool>(),
timePosition = reader.Read<float>()
};
}
public static void WriteVolume(this Writer writer, RadioVolumeMsg msg)
{
writer.Write<float>(msg.volume);
}
public static RadioVolumeMsg ReadVolume(this Reader reader)
{
return new RadioVolumeMsg
{
volume = reader.Read<float>()
};
}
public static void WriteSyncRoles(this Writer writer, SyncRolesMsg msg)
{
if (msg.modIds == null)
{
writer.Write<int>(0);
return;
}
writer.Write<int>(msg.modIds.Length);
int[] modIds = msg.modIds;
foreach (int num in modIds)
{
writer.Write<int>(num);
}
}
public static SyncRolesMsg ReadSyncRoles(this Reader reader)
{
int num = reader.Read<int>();
int[] array = new int[num];
for (int i = 0; i < num; i++)
{
array[i] = reader.Read<int>();
}
return new SyncRolesMsg
{
modIds = array
};
}
}
[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", "1.1.0")]
public class Plugin : BaseUnityPlugin
{
private const string _coreWatermark = "Architecture & NetCode by Stasochec";
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 v1.1.0 (Safe & Fast Edition) loaded. [Architecture & NetCode by Stasochec]");
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");
string[] files2 = Directory.GetFiles(Application.temporaryCachePath, "moderation_*.mp3");
string[] array = files;
for (int i = 0; i < array.Length; i++)
{
File.Delete(array[i]);
}
array = files2;
for (int i = 0; i < array.Length; i++)
{
File.Delete(array[i]);
}
}
catch
{
}
}
}
public class CustomRadioAudioController : IDisposable
{
private Plugin plugin;
private int _mainLoadToken;
private int _previewLoadToken;
public AudioSource Source { get; private set; }
public AudioSource PreviewSource { 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<AudioSource>() ?? val4.AddComponent<AudioSource>();
Source.spatialBlend = 1f;
Source.minDistance = 3f;
Source.maxDistance = 40f;
Source.rolloffMode = (AudioRolloffMode)1;
Source.volume = CurrentVolume;
}
}
private void EnsurePreviewExists()
{
//IL_0042: Unknown result type (might be due to invalid IL or missing references)
if (!((Object)(object)PreviewSource != (Object)null))
{
GameObject val = GameObject.Find("LocalPlayer");
if ((Object)(object)val != (Object)null)
{
Transform val2 = val.transform.Find("RadioPreviewSource");
GameObject val3 = (GameObject)(((Object)(object)val2 != (Object)null) ? ((object)((Component)val2).gameObject) : ((object)new GameObject("RadioPreviewSource")));
val3.transform.SetParent(val.transform);
PreviewSource = val3.GetComponent<AudioSource>() ?? val3.AddComponent<AudioSource>();
PreviewSource.spatialBlend = 0f;
PreviewSource.volume = 0.5f;
}
}
}
public void PlayMain(string path)
{
EnsureRadioExists();
int currentToken = ++_mainLoadToken;
string url = "file:///" + path.Replace("\\", "/");
((MonoBehaviour)plugin).StartCoroutine(LoadAndPlayAudio(Source, url, isMain: true, () => _mainLoadToken == currentToken));
}
public void PlayPreview(string path)
{
EnsurePreviewExists();
int currentToken = ++_previewLoadToken;
string url = "file:///" + path.Replace("\\", "/");
((MonoBehaviour)plugin).StartCoroutine(LoadAndPlayAudio(PreviewSource, url, isMain: false, () => _previewLoadToken == currentToken));
}
public void StopPreview()
{
_previewLoadToken++;
if ((Object)(object)PreviewSource != (Object)null)
{
PreviewSource.Stop();
}
}
private IEnumerator LoadAndPlayAudio(AudioSource targetSource, string url, bool isMain, Func<bool> isLoadValid)
{
UnityWebRequest www = UnityWebRequestMultimedia.GetAudioClip(url, (AudioType)13);
try
{
yield return www.SendWebRequest();
if ((Object)(object)targetSource == (Object)null || !isLoadValid())
{
yield break;
}
if ((int)www.result == 1)
{
AudioClip content = DownloadHandlerAudioClip.GetContent(www);
if ((Object)(object)targetSource.clip != (Object)null)
{
Object.Destroy((Object)(object)targetSource.clip);
}
targetSource.clip = content;
if (isMain)
{
IsPaused = false;
}
targetSource.Play();
}
}
finally
{
((IDisposable)www)?.Dispose();
}
}
public void ApplyControlState(bool pauseState, float timePosition)
{
if (!((Object)(object)Source == (Object)null) && !float.IsNaN(timePosition) && !float.IsInfinity(timePosition))
{
IsPaused = pauseState;
Source.time = timePosition;
if (IsPaused)
{
Source.Pause();
}
else
{
Source.Play();
}
}
}
public void ApplyVolume(float vol)
{
if (!float.IsNaN(vol) && !float.IsInfinity(vol))
{
CurrentVolume = Mathf.Clamp(vol, 0f, 1f);
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);
}
if ((Object)(object)PreviewSource != (Object)null && (Object)(object)PreviewSource.clip != (Object)null)
{
Object.Destroy((Object)(object)PreviewSource.clip);
}
}
}
public class CustomRadioNetworkService
{
public class PendingTrack
{
public int TrackId;
public string Path;
}
private class DownloadContext
{
public FileStream Stream;
public int ExpectedChunks;
public HashSet<int> ReceivedChunks;
public float LastActivityTime;
public string TempPath;
}
public const int CHUNK_SIZE = 12288;
private const float TARGET_BPS = 204800f;
private const float DOWNLOAD_TIMEOUT = 120f;
private const int MAX_FILE_SIZE = 15728640;
private const int MAX_CHUNKS = 2000;
private Plugin plugin;
private CustomRadioAudioController audio;
private bool isRegistered;
private Dictionary<int, DownloadContext> activeDownloads = new Dictionary<int, DownloadContext>();
private Dictionary<int, DownloadContext> activeUploads = new Dictionary<int, DownloadContext>();
private List<PendingTrack> moderationQueue = new List<PendingTrack>();
public HashSet<int> ServerModerators = new HashSet<int>();
public bool IsLocalModerator;
public float UploadProgress;
public float DownloadProgress;
public CustomRadioNetworkService(Plugin plugin, CustomRadioAudioController audio)
{
this.plugin = plugin;
this.audio = audio;
}
public List<PendingTrack> GetModerationQueue()
{
return moderationQueue;
}
public void RegisterCallbacks()
{
if (isRegistered)
{
return;
}
GenericWriter<RadioUploadChunkMsg>.SetWrite((Action<Writer, RadioUploadChunkMsg>)CustomSerializers.WriteUploadChunk);
GenericReader<RadioUploadChunkMsg>.SetRead((Func<Reader, RadioUploadChunkMsg>)CustomSerializers.ReadUploadChunk);
GenericWriter<RadioPlayChunkMsg>.SetWrite((Action<Writer, RadioPlayChunkMsg>)CustomSerializers.WritePlayChunk);
GenericReader<RadioPlayChunkMsg>.SetRead((Func<Reader, RadioPlayChunkMsg>)CustomSerializers.ReadPlayChunk);
GenericWriter<RadioControlMsg>.SetWrite((Action<Writer, RadioControlMsg>)CustomSerializers.WriteControl);
GenericReader<RadioControlMsg>.SetRead((Func<Reader, RadioControlMsg>)CustomSerializers.ReadControl);
GenericWriter<RadioVolumeMsg>.SetWrite((Action<Writer, RadioVolumeMsg>)CustomSerializers.WriteVolume);
GenericReader<RadioVolumeMsg>.SetRead((Func<Reader, RadioVolumeMsg>)CustomSerializers.ReadVolume);
GenericWriter<SyncRolesMsg>.SetWrite((Action<Writer, SyncRolesMsg>)CustomSerializers.WriteSyncRoles);
GenericReader<SyncRolesMsg>.SetRead((Func<Reader, SyncRolesMsg>)CustomSerializers.ReadSyncRoles);
InstanceFinder.ClientManager.RegisterBroadcast<RadioPlayChunkMsg>((Action<RadioPlayChunkMsg, Channel>)OnClientReceivePlayChunk);
InstanceFinder.ClientManager.RegisterBroadcast<RadioControlMsg>((Action<RadioControlMsg, Channel>)OnClientReceiveControl);
InstanceFinder.ClientManager.RegisterBroadcast<RadioVolumeMsg>((Action<RadioVolumeMsg, Channel>)OnClientReceiveVolume);
InstanceFinder.ClientManager.RegisterBroadcast<SyncRolesMsg>((Action<SyncRolesMsg, Channel>)OnClientReceiveRoles);
InstanceFinder.ServerManager.RegisterBroadcast<RadioUploadChunkMsg>((Action<NetworkConnection, RadioUploadChunkMsg, Channel>)OnServerReceiveUploadChunk, true);
InstanceFinder.ServerManager.RegisterBroadcast<RadioControlMsg>((Action<NetworkConnection, RadioControlMsg, Channel>)OnServerReceiveControl, true);
InstanceFinder.ServerManager.RegisterBroadcast<RadioVolumeMsg>((Action<NetworkConnection, RadioVolumeMsg, Channel>)OnServerReceiveVolume, true);
InstanceFinder.ServerManager.OnRemoteConnectionState += delegate(NetworkConnection conn, RemoteConnectionStateArgs args)
{
//IL_0000: Unknown result type (might be due to invalid IL or missing references)
//IL_0001: Unknown result type (might be due to invalid IL or missing references)
//IL_0007: Invalid comparison between Unknown and I4
//IL_0010: Unknown result type (might be due to invalid IL or missing references)
//IL_0011: Unknown result type (might be due to invalid IL or missing references)
if ((int)args.ConnectionState == 2)
{
BroadcastRoles();
}
else if ((int)args.ConnectionState == 0)
{
ToggleModerator(conn.ClientId, state: false);
}
};
isRegistered = true;
}
public void UnregisterCallbacks()
{
if (isRegistered)
{
if ((Object)(object)InstanceFinder.ClientManager != (Object)null)
{
InstanceFinder.ClientManager.UnregisterBroadcast<RadioPlayChunkMsg>((Action<RadioPlayChunkMsg, Channel>)OnClientReceivePlayChunk);
InstanceFinder.ClientManager.UnregisterBroadcast<RadioControlMsg>((Action<RadioControlMsg, Channel>)OnClientReceiveControl);
InstanceFinder.ClientManager.UnregisterBroadcast<RadioVolumeMsg>((Action<RadioVolumeMsg, Channel>)OnClientReceiveVolume);
InstanceFinder.ClientManager.UnregisterBroadcast<SyncRolesMsg>((Action<SyncRolesMsg, Channel>)OnClientReceiveRoles);
}
if ((Object)(object)InstanceFinder.ServerManager != (Object)null)
{
InstanceFinder.ServerManager.UnregisterBroadcast<RadioUploadChunkMsg>((Action<NetworkConnection, RadioUploadChunkMsg, Channel>)OnServerReceiveUploadChunk);
InstanceFinder.ServerManager.UnregisterBroadcast<RadioControlMsg>((Action<NetworkConnection, RadioControlMsg, Channel>)OnServerReceiveControl);
InstanceFinder.ServerManager.UnregisterBroadcast<RadioVolumeMsg>((Action<NetworkConnection, RadioVolumeMsg, Channel>)OnServerReceiveVolume);
}
}
}
public void ToggleModerator(int clientId, bool state)
{
if (InstanceFinder.IsServerStarted)
{
if (state)
{
ServerModerators.Add(clientId);
}
else
{
ServerModerators.Remove(clientId);
}
BroadcastRoles();
}
}
private void BroadcastRoles()
{
if (InstanceFinder.IsServerStarted)
{
int[] modIds = ServerModerators.ToArray();
InstanceFinder.ServerManager.Broadcast<SyncRolesMsg>(new SyncRolesMsg
{
modIds = modIds
}, true, (Channel)0);
}
}
private void OnClientReceiveRoles(SyncRolesMsg msg, Channel channel)
{
if (!((Object)(object)InstanceFinder.ClientManager == (Object)null) && !(InstanceFinder.ClientManager.Connection == (NetworkConnection)null))
{
int clientId = InstanceFinder.ClientManager.Connection.ClientId;
IsLocalModerator = msg.modIds != null && msg.modIds.Contains(clientId);
}
}
public void SendFile(string path)
{
((MonoBehaviour)plugin).StartCoroutine(ClientUploadCoroutine(path));
}
private IEnumerator ClientUploadCoroutine(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 / 12288f);
UploadProgress = 0.01f;
using (FileStream fs = new FileStream(path, FileMode.Open, FileAccess.Read))
{
byte[] buffer = new byte[12288];
int chunkIndex = 0;
float allowance = 12288f;
float lastTime = Time.realtimeSinceStartup;
while (true)
{
int num;
int bytesRead = (num = fs.Read(buffer, 0, buffer.Length));
if (num <= 0)
{
break;
}
UploadProgress = (float)chunkIndex / (float)totalChunks;
while (allowance < (float)bytesRead)
{
yield return null;
float realtimeSinceStartup = Time.realtimeSinceStartup;
allowance += (realtimeSinceStartup - lastTime) * 204800f;
lastTime = realtimeSinceStartup;
}
if (allowance > 49152f)
{
allowance = 49152f;
}
byte[] array;
if (bytesRead < 12288)
{
array = new byte[bytesRead];
Buffer.BlockCopy(buffer, 0, array, 0, bytesRead);
}
else
{
array = buffer;
}
InstanceFinder.ClientManager.Broadcast<RadioUploadChunkMsg>(new RadioUploadChunkMsg
{
trackId = trackId,
chunkIndex = chunkIndex,
totalChunks = totalChunks,
data = array
}, (Channel)0);
allowance -= (float)bytesRead;
chunkIndex++;
yield return null;
}
}
UploadProgress = 0f;
}
private void OnServerReceiveUploadChunk(NetworkConnection conn, RadioUploadChunkMsg msg, Channel channel)
{
if (msg.totalChunks <= 0 || msg.totalChunks > 2000 || msg.chunkIndex < 0 || msg.chunkIndex >= msg.totalChunks || msg.data == null || msg.data.Length > 13312)
{
return;
}
int num = Mathf.Abs(msg.trackId);
if (!activeUploads.TryGetValue(num, out var value))
{
if (activeUploads.Count >= 5)
{
return;
}
string text = Path.Combine(Application.temporaryCachePath, $"moderation_{num}.mp3");
value = new DownloadContext
{
TempPath = text,
Stream = new FileStream(text, FileMode.Create, FileAccess.Write, FileShare.None),
ExpectedChunks = msg.totalChunks,
ReceivedChunks = new HashSet<int>(),
LastActivityTime = Time.realtimeSinceStartup
};
activeUploads[num] = value;
}
value.Stream.Seek((long)msg.chunkIndex * 12288L, 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)
{
return;
}
value.Stream.Dispose();
activeUploads.Remove(num);
if (moderationQueue.Count >= 10)
{
if (File.Exists(value.TempPath))
{
File.Delete(value.TempPath);
}
}
else
{
moderationQueue.Add(new PendingTrack
{
TrackId = num,
Path = value.TempPath
});
}
}
public void ApproveTrack(PendingTrack track, float initialVolume)
{
moderationQueue.Remove(track);
InstanceFinder.ServerManager.Broadcast<RadioVolumeMsg>(new RadioVolumeMsg
{
volume = initialVolume
}, true, (Channel)0);
((MonoBehaviour)plugin).StartCoroutine(ServerStreamCoroutine(track.Path, track.TrackId));
}
public void DenyTrack(PendingTrack track)
{
moderationQueue.Remove(track);
if (File.Exists(track.Path))
{
File.Delete(track.Path);
}
}
private IEnumerator ServerStreamCoroutine(string path, int trackId)
{
if ((Object)(object)InstanceFinder.ServerManager == (Object)null)
{
yield break;
}
FileInfo fileInfo = new FileInfo(path);
int totalChunks = Mathf.CeilToInt((float)fileInfo.Length / 12288f);
using (FileStream fs = new FileStream(path, FileMode.Open, FileAccess.Read))
{
byte[] buffer = new byte[12288];
int chunkIndex = 0;
float allowance = 12288f;
float lastTime = Time.realtimeSinceStartup;
while (true)
{
int num;
int bytesRead = (num = fs.Read(buffer, 0, buffer.Length));
if (num <= 0)
{
break;
}
while (allowance < (float)bytesRead)
{
yield return null;
float realtimeSinceStartup = Time.realtimeSinceStartup;
allowance += (realtimeSinceStartup - lastTime) * 204800f;
lastTime = realtimeSinceStartup;
}
if (allowance > 49152f)
{
allowance = 49152f;
}
byte[] array;
if (bytesRead < 12288)
{
array = new byte[bytesRead];
Buffer.BlockCopy(buffer, 0, array, 0, bytesRead);
}
else
{
array = buffer;
}
InstanceFinder.ServerManager.Broadcast<RadioPlayChunkMsg>(new RadioPlayChunkMsg
{
trackId = trackId,
chunkIndex = chunkIndex,
totalChunks = totalChunks,
data = array
}, true, (Channel)0);
allowance -= (float)bytesRead;
chunkIndex++;
yield return null;
}
}
if (File.Exists(path))
{
File.Delete(path);
}
}
private void OnClientReceivePlayChunk(RadioPlayChunkMsg msg, Channel channel)
{
if (msg.totalChunks > 0 && msg.totalChunks <= 2000 && msg.chunkIndex >= 0 && msg.chunkIndex < msg.totalChunks)
{
int num = Mathf.Abs(msg.trackId);
if (!activeDownloads.TryGetValue(num, out var value))
{
string text = Path.Combine(Application.temporaryCachePath, $"radio_sync_{num}.mp3");
value = new DownloadContext
{
TempPath = text,
Stream = new FileStream(text, FileMode.Create, FileAccess.Write, FileShare.None),
ExpectedChunks = msg.totalChunks,
ReceivedChunks = new HashSet<int>(),
LastActivityTime = Time.realtimeSinceStartup
};
activeDownloads[num] = value;
}
value.Stream.Seek((long)msg.chunkIndex * 12288L, SeekOrigin.Begin);
value.Stream.Write(msg.data, 0, msg.data.Length);
value.ReceivedChunks.Add(msg.chunkIndex);
value.LastActivityTime = Time.realtimeSinceStartup;
DownloadProgress = (float)value.ReceivedChunks.Count / (float)value.ExpectedChunks;
if (value.ReceivedChunks.Count >= value.ExpectedChunks)
{
DownloadProgress = 0f;
value.Stream.Dispose();
activeDownloads.Remove(num);
audio.PlayMain(value.TempPath);
}
}
}
public void SendControl(bool isPaused, float time)
{
if ((Object)(object)InstanceFinder.ClientManager != (Object)null)
{
InstanceFinder.ClientManager.Broadcast<RadioControlMsg>(new RadioControlMsg
{
isPaused = isPaused,
timePosition = time
}, (Channel)0);
}
}
public void SendVolume(float vol)
{
if ((Object)(object)InstanceFinder.ClientManager != (Object)null)
{
InstanceFinder.ClientManager.Broadcast<RadioVolumeMsg>(new RadioVolumeMsg
{
volume = vol
}, (Channel)0);
}
}
private void OnServerReceiveControl(NetworkConnection conn, RadioControlMsg msg, Channel channel)
{
if (!float.IsNaN(msg.timePosition) && !float.IsInfinity(msg.timePosition) && !(msg.timePosition < 0f) && (conn == InstanceFinder.ClientManager.Connection || ServerModerators.Contains(conn.ClientId)))
{
InstanceFinder.ServerManager.Broadcast<RadioControlMsg>(msg, true, (Channel)0);
}
}
private void OnServerReceiveVolume(NetworkConnection conn, RadioVolumeMsg msg, Channel channel)
{
if (!float.IsNaN(msg.volume) && !float.IsInfinity(msg.volume) && !(msg.volume < 0f) && !(msg.volume > 1f) && (conn == InstanceFinder.ClientManager.Connection || ServerModerators.Contains(conn.ClientId)))
{
InstanceFinder.ServerManager.Broadcast<RadioVolumeMsg>(msg, true, (Channel)0);
}
}
private void OnClientReceiveControl(RadioControlMsg msg, Channel channel)
{
if (!(Time.realtimeSinceStartup - Plugin.LocalIgnoreNetworkTime < 0.8f))
{
audio.ApplyControlState(msg.isPaused, msg.timePosition);
}
}
private void OnClientReceiveVolume(RadioVolumeMsg msg, Channel channel)
{
if (!(Time.realtimeSinceStartup - Plugin.LocalIgnoreNetworkTime < 0.8f))
{
audio.ApplyVolume(msg.volume);
}
}
public void ProcessTimeouts()
{
float realtimeSinceStartup = Time.realtimeSinceStartup;
List<int> list = null;
foreach (KeyValuePair<int, DownloadContext> activeUpload in activeUploads)
{
if (realtimeSinceStartup - activeUpload.Value.LastActivityTime > 120f)
{
if (list == null)
{
list = new List<int>();
}
list.Add(activeUpload.Key);
}
}
if (list != null)
{
foreach (int item in list)
{
DownloadContext downloadContext = activeUploads[item];
downloadContext.Stream.Dispose();
if (File.Exists(downloadContext.TempPath))
{
File.Delete(downloadContext.TempPath);
}
activeUploads.Remove(item);
}
}
List<int> list2 = null;
foreach (KeyValuePair<int, DownloadContext> activeDownload in activeDownloads)
{
if (realtimeSinceStartup - activeDownload.Value.LastActivityTime > 120f)
{
if (list2 == null)
{
list2 = new List<int>();
}
list2.Add(activeDownload.Key);
}
}
if (list2 != null)
{
foreach (int item2 in list2)
{
DownloadContext downloadContext2 = activeDownloads[item2];
downloadContext2.Stream.Dispose();
if (File.Exists(downloadContext2.TempPath))
{
File.Delete(downloadContext2.TempPath);
}
activeDownloads.Remove(item2);
}
}
if (activeDownloads.Count == 0)
{
DownloadProgress = 0f;
}
if (activeUploads.Count == 0)
{
UploadProgress = 0f;
}
}
}
public class CustomRadioUI
{
private Plugin plugin;
private CustomRadioNetworkService network;
private CustomRadioAudioController audio;
private bool showUI;
private bool showRolesUI;
private bool isDialogOpen;
private ConcurrentQueue<string> pendingFiles = new ConcurrentQueue<string>();
private float lastSeekSendTime;
private float lastVolumeSendTime;
private GUIStyle windowStyle;
private GUIStyle btnStyle;
private GUIStyle labelStyle;
private GUIStyle sliderTrackStyle;
private GUIStyle sliderThumbStyle;
private bool stylesInitialized;
private bool isRu;
private Dictionary<int, string> _nameCache = new Dictionary<int, string>();
public CustomRadioUI(Plugin plugin, CustomRadioNetworkService network, CustomRadioAudioController audio)
{
//IL_0032: Unknown result type (might be due to invalid IL or missing references)
//IL_0039: Invalid comparison between Unknown and I4
//IL_003b: Unknown result type (might be due to invalid IL or missing references)
//IL_0042: Invalid comparison between Unknown and I4
//IL_0044: Unknown result type (might be due to invalid IL or missing references)
//IL_004a: Invalid comparison between Unknown and I4
this.plugin = plugin;
this.network = network;
this.audio = audio;
isRu = (int)Application.systemLanguage == 30 || (int)Application.systemLanguage == 38 || (int)Application.systemLanguage == 3;
}
private string T(string ru, string en)
{
if (!isRu)
{
return en;
}
return ru;
}
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);
}
}
private string GetPlayerName(NetworkConnection conn)
{
if (_nameCache.TryGetValue(conn.ClientId, out var value))
{
return value;
}
string text = string.Format("{0} #{1}", T("Игрок", "Player"), conn.ClientId);
if ((Object)(object)conn.FirstObject != (Object)null)
{
MonoBehaviour[] componentsInChildren = ((Component)conn.FirstObject).GetComponentsInChildren<MonoBehaviour>();
string[] array = new string[10] { "Username", "Nickname", "PlayerName", "playerName", "nickName", "_username", "_nickname", "_playerName", "steamName", "SteamName" };
BindingFlags bindingAttr = BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic;
MonoBehaviour[] array2 = componentsInChildren;
foreach (MonoBehaviour val in array2)
{
if ((Object)(object)val == (Object)null)
{
continue;
}
Type type = ((object)val).GetType();
if (type.Namespace != null && (type.Namespace.StartsWith("UnityEngine") || type.Namespace.StartsWith("FishNet")))
{
continue;
}
string[] array3 = array;
foreach (string name in array3)
{
PropertyInfo property = type.GetProperty(name, bindingAttr);
if (property != null && property.PropertyType == typeof(string))
{
string text2 = (string)property.GetValue(val);
if (!string.IsNullOrEmpty(text2) && !text2.Contains("(Clone)"))
{
text = $"{text2} (ID: {conn.ClientId})";
break;
}
}
FieldInfo field = type.GetField(name, bindingAttr);
if (field != null && field.FieldType == typeof(string))
{
string text3 = (string)field.GetValue(val);
if (!string.IsNullOrEmpty(text3) && !text3.Contains("(Clone)"))
{
text = $"{text3} (ID: {conn.ClientId})";
break;
}
}
}
if (text != string.Format("{0} #{1}", T("Игрок", "Player"), conn.ClientId))
{
break;
}
}
}
_nameCache[conn.ClientId] = text;
return text;
}
public void DrawUI()
{
//IL_0039: Unknown result type (might be due to invalid IL or missing references)
//IL_0062: Unknown result type (might be due to invalid IL or missing references)
//IL_0120: Unknown result type (might be due to invalid IL or missing references)
//IL_0171: Unknown result type (might be due to invalid IL or missing references)
//IL_0553: Unknown result type (might be due to invalid IL or missing references)
//IL_023b: Unknown result type (might be due to invalid IL or missing references)
//IL_026c: Unknown result type (might be due to invalid IL or missing references)
//IL_02ff: Unknown result type (might be due to invalid IL or missing references)
//IL_05bd: Unknown result type (might be due to invalid IL or missing references)
//IL_0601: Unknown result type (might be due to invalid IL or missing references)
//IL_0389: Unknown result type (might be due to invalid IL or missing references)
//IL_0730: Unknown result type (might be due to invalid IL or missing references)
//IL_03f3: Unknown result type (might be due to invalid IL or missing references)
//IL_0468: Unknown result type (might be due to invalid IL or missing references)
//IL_04b7: Unknown result type (might be due to invalid IL or missing references)
//IL_077a: Unknown result type (might be due to invalid IL or missing references)
//IL_07c2: Unknown result type (might be due to invalid IL or missing references)
//IL_0697: Unknown result type (might be due to invalid IL or missing references)
//IL_090c: Unknown result type (might be due to invalid IL or missing references)
//IL_0834: Unknown result type (might be due to invalid IL or missing references)
//IL_0877: Unknown result type (might be due to invalid IL or missing references)
//IL_0960: Unknown result type (might be due to invalid IL or missing references)
//IL_09c2: Unknown result type (might be due to invalid IL or missing references)
//IL_0a31: Unknown result type (might be due to invalid IL or missing references)
//IL_0a85: Unknown result type (might be due to invalid IL or missing references)
if (!showUI)
{
return;
}
InitStyles();
bool flag = InstanceFinder.IsServerStarted || network.IsLocalModerator;
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(285f, 25f, 45f, 20f), isRu ? "RU" : "EN", btnStyle))
{
isRu = !isRu;
}
string text = ((network.UploadProgress > 0f) ? T($"Отправка... {Mathf.RoundToInt(network.UploadProgress * 100f)}%", $"Uploading... {Mathf.RoundToInt(network.UploadProgress * 100f)}%") : T("Загрузить MP3 на сервер", "Upload MP3 to Server"));
if (GUI.Button(new Rect(40f, 55f, 280f, 25f), text, btnStyle) && network.UploadProgress == 0f)
{
OpenWindowsExplorerSafe();
}
if (network.DownloadProgress > 0f)
{
GUI.Label(new Rect(40f, 110f, 280f, 20f), T($"⬇ Скачивание трека... {Mathf.RoundToInt(network.DownloadProgress * 100f)}%", $"⬇ Downloading track... {Mathf.RoundToInt(network.DownloadProgress * 100f)}%"), labelStyle);
}
else
{
AudioSource source = audio.Source;
if ((Object)(object)source != (Object)null && (Object)(object)source.clip != (Object)null)
{
string text2 = FormatTime(source.time) + " / " + FormatTime(source.clip.length);
GUI.Label(new Rect(40f, 85f, 280f, 20f), text2, labelStyle);
GUI.enabled = flag;
GUI.changed = false;
float time = GUI.HorizontalSlider(new Rect(40f, 105f, 280f, 15f), source.time, 0f, source.clip.length, sliderTrackStyle, sliderThumbStyle);
if (GUI.changed && flag)
{
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) && flag)
{
float time2 = (source.time = Mathf.Max(0f, source.time - 15f));
Plugin.LocalIgnoreNetworkTime = Time.realtimeSinceStartup;
network.SendControl(audio.IsPaused, time2);
}
string text3 = (audio.IsPaused ? "Play" : "Pause");
if (GUI.Button(new Rect(155f, 130f, 50f, 25f), text3, btnStyle) && flag)
{
bool flag2 = !audio.IsPaused;
audio.ApplyControlState(flag2, source.time);
Plugin.LocalIgnoreNetworkTime = Time.realtimeSinceStartup;
network.SendControl(flag2, source.time);
}
if (GUI.Button(new Rect(215f, 130f, 45f, 25f), ">>", btnStyle) && flag)
{
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 && flag)
{
audio.ApplyVolume(vol);
Plugin.LocalIgnoreNetworkTime = Time.realtimeSinceStartup;
if (Time.realtimeSinceStartup - lastVolumeSendTime > 0.1f)
{
lastVolumeSendTime = Time.realtimeSinceStartup;
network.SendVolume(vol);
}
}
GUI.enabled = true;
}
}
if (!InstanceFinder.IsServerStarted)
{
return;
}
GUI.Box(new Rect(360f, 20f, 320f, 290f), "\n" + T("М О Д Е Р А Ц И Я ( Х О С Т )", "M O D E R A T I O N ( H O S T )"), windowStyle);
string text4 = (showRolesUI ? T("Вернуться к трекам", "Back to tracks") : T("Игроки и роли", "Players & Roles"));
if (GUI.Button(new Rect(380f, 55f, 280f, 25f), text4, btnStyle))
{
showRolesUI = !showRolesUI;
}
if (showRolesUI)
{
int num3 = 90;
GUI.Label(new Rect(380f, (float)num3, 280f, 20f), T("Выдача прав на управление радио:", "Radio management permissions:"), labelStyle);
num3 += 25;
{
foreach (NetworkConnection value in InstanceFinder.ServerManager.Clients.Values)
{
if (!(value == InstanceFinder.ClientManager.Connection))
{
string playerName = GetPlayerName(value);
bool flag3 = network.ServerModerators.Contains(value.ClientId);
bool flag4 = GUI.Toggle(new Rect(380f, (float)num3, 280f, 20f), flag3, " " + playerName + " - " + T("Модератор", "Mod"));
if (flag4 != flag3)
{
network.ToggleModerator(value.ClientId, flag4);
}
num3 += 25;
}
}
return;
}
}
List<CustomRadioNetworkService.PendingTrack> moderationQueue = network.GetModerationQueue();
if (moderationQueue.Count == 0)
{
GUI.Label(new Rect(370f, 90f, 300f, 30f), T("Очередь треков пуста", "Track queue is empty"), labelStyle);
return;
}
int num4 = 90;
for (int i = 0; i < Mathf.Min(moderationQueue.Count, 3); i++)
{
CustomRadioNetworkService.PendingTrack pendingTrack = moderationQueue[i];
GUI.Label(new Rect(370f, (float)num4, 120f, 25f), string.Format("{0} #{1}", T("Трек", "Track"), pendingTrack.TrackId), labelStyle);
if (GUI.Button(new Rect(490f, (float)num4, 55f, 25f), T("Слушать", "Preview"), btnStyle))
{
audio.PlayPreview(pendingTrack.Path);
}
float initialVolume = (((Object)(object)audio.PreviewSource != (Object)null) ? audio.PreviewSource.volume : 0.5f);
if (GUI.Button(new Rect(555f, (float)num4, 40f, 25f), "OK", btnStyle))
{
audio.StopPreview();
network.ApproveTrack(pendingTrack, initialVolume);
}
if (GUI.Button(new Rect(605f, (float)num4, 40f, 25f), "Del", btnStyle))
{
audio.StopPreview();
network.DenyTrack(pendingTrack);
}
num4 += 35;
}
if (!((Object)(object)audio.PreviewSource != (Object)null) || !((Object)(object)audio.PreviewSource.clip != (Object)null))
{
return;
}
GUI.Label(new Rect(380f, 195f, 280f, 20f), T("Настройка перед запуском у всех:", "Settings before playing for everyone:"), labelStyle);
string text5 = (audio.PreviewSource.isPlaying ? "Pause" : "Play");
if (GUI.Button(new Rect(380f, 215f, 60f, 20f), text5, btnStyle))
{
if (audio.PreviewSource.isPlaying)
{
audio.PreviewSource.Pause();
}
else
{
audio.PreviewSource.Play();
}
}
GUI.changed = false;
float time4 = GUI.HorizontalSlider(new Rect(450f, 218f, 210f, 15f), audio.PreviewSource.time, 0f, audio.PreviewSource.clip.length, sliderTrackStyle, sliderThumbStyle);
if (GUI.changed)
{
audio.PreviewSource.time = time4;
}
GUI.Label(new Rect(380f, 245f, 60f, 20f), $"Vol: {Mathf.RoundToInt(audio.PreviewSource.volume * 100f)}%", labelStyle);
GUI.changed = false;
float volume = GUI.HorizontalSlider(new Rect(450f, 248f, 210f, 15f), audio.PreviewSource.volume, 0f, 1f, sliderTrackStyle, sliderThumbStyle);
if (GUI.changed)
{
audio.PreviewSource.volume = volume;
}
}
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 = T("Выберите песню для радио", "Select a song for the radio");
openFileName.flags = 528392;
if (LocalDialog.GetOpenFileName(openFileName))
{
pendingFiles.Enqueue(openFileName.file.TrimEnd(new char[1]));
}
isDialogOpen = false;
}).Start();
}
}