Please disclose if any significant portion of your mod was created using AI tools by adding the 'AI Generated' category. Failing to do so may result in the mod being removed from Thunderstore.
Decompiled source of SharePermissions v2.19.0
SharePermissions.dll
Decompiled 6 hours ago
The result has been truncated due to the large size, download it to view full contents!
using System; using System.Collections; using System.Collections.Generic; using System.Diagnostics; using System.Diagnostics.CodeAnalysis; using System.Globalization; using System.IO; using System.Linq; using System.Reflection; using System.Runtime.CompilerServices; using System.Runtime.Versioning; using System.Security; using System.Security.Cryptography; using System.Security.Permissions; using System.Text; using System.Text.RegularExpressions; using BepInEx; using BepInEx.Bootstrap; using BepInEx.Configuration; using BepInEx.Logging; using ExitGames.Client.Photon; using HarmonyLib; using MenuLib; using MenuLib.MonoBehaviors; using MenuLib.Structs; using Microsoft.CodeAnalysis; using POpusCodec.Enums; using Photon.Pun; using Photon.Realtime; using Photon.Voice; using Photon.Voice.Unity; using SharePermissions.Core; using SharePermissions.Net; using SharePermissions.Net.Commands; using SharePermissions.Patches; using SharePermissions.Players; using SharePermissions.UI; using SharePermissions.Voice; using Steamworks; using TMPro; using UnityEngine; using UnityEngine.Events; using UnityEngine.SceneManagement; using UnityEngine.TextCore; using UnityEngine.UI; [assembly: CompilationRelaxations(8)] [assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)] [assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)] [assembly: TargetFramework(".NETStandard,Version=v2.1", FrameworkDisplayName = ".NET Standard 2.1")] [assembly: IgnoresAccessChecksTo("")] [assembly: AssemblyCompany("RED")] [assembly: AssemblyConfiguration("Release")] [assembly: AssemblyFileVersion("2.19.0.0")] [assembly: AssemblyInformationalVersion("2.19.0+0920f41608d48aad1d2b21e9e65e164aa3568d24")] [assembly: AssemblyProduct("SharePermissions")] [assembly: AssemblyTitle("SharePermissions")] [assembly: SecurityPermission(SecurityAction.RequestMinimum, SkipVerification = true)] [assembly: AssemblyVersion("2.19.0.0")] [module: UnverifiableCode] [module: RefSafetyRules(11)] namespace Microsoft.CodeAnalysis { [CompilerGenerated] [Embedded] internal sealed class EmbeddedAttribute : Attribute { } } namespace System.Runtime.CompilerServices { [CompilerGenerated] [Embedded] [AttributeUsage(AttributeTargets.Class | AttributeTargets.Property | AttributeTargets.Field | AttributeTargets.Event | AttributeTargets.Parameter | AttributeTargets.ReturnValue | AttributeTargets.GenericParameter, AllowMultiple = false, Inherited = false)] internal sealed class NullableAttribute : Attribute { public readonly byte[] NullableFlags; public NullableAttribute(byte P_0) { NullableFlags = new byte[1] { P_0 }; } public NullableAttribute(byte[] P_0) { NullableFlags = P_0; } } [CompilerGenerated] [Embedded] [AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct | AttributeTargets.Method | AttributeTargets.Interface | AttributeTargets.Delegate, AllowMultiple = false, Inherited = false)] internal sealed class NullableContextAttribute : Attribute { public readonly byte Flag; public NullableContextAttribute(byte P_0) { Flag = P_0; } } [CompilerGenerated] [Embedded] [AttributeUsage(AttributeTargets.Module, AllowMultiple = false, Inherited = false)] internal sealed class RefSafetyRulesAttribute : Attribute { public readonly int Version; public RefSafetyRulesAttribute(int P_0) { Version = P_0; } } } namespace SharePermissions.Voice { internal sealed class MicRing { private readonly float[] buffer; private volatile int writeIndex; private volatile int readIndex; private volatile float level; internal float Level => level; internal int Available { get { int num = writeIndex; int num2 = readIndex; if (num < num2) { return buffer.Length - num2 + num; } return num - num2; } } internal MicRing(int capacity) { if (capacity < 2) { capacity = 2; } buffer = new float[capacity]; } internal void Write(float[] data, int count) { if (data != null && count > 0) { if (count > data.Length) { count = data.Length; } float num = 0f; for (int i = 0; i < count; i++) { num += Math.Abs(data[i]); } level = num / (float)count; int num2 = writeIndex; int num3 = buffer.Length - 1 - Available; if (count > num3) { count = num3; } for (int j = 0; j < count; j++) { buffer[num2] = data[j]; num2 = ((num2 + 1 != buffer.Length) ? (num2 + 1) : 0); } writeIndex = num2; } } internal bool Read(float[] destination) { if (destination == null || destination.Length == 0) { return false; } if (Available < destination.Length) { return false; } int num = readIndex; for (int i = 0; i < destination.Length; i++) { destination[i] = buffer[num]; num = ((num + 1 != buffer.Length) ? (num + 1) : 0); } readIndex = num; return true; } internal void Reset() { readIndex = 0; writeIndex = 0; level = 0f; } } [HarmonyPatch(typeof(MicWrapper), "Read")] internal static class MicTee { private const int RingCapacity = 48000; internal static readonly MicRing Ring = new MicRing(48000); private static volatile int samplingRate; private static volatile int channels; internal static int SamplingRate => samplingRate; internal static int Channels => channels; internal static bool IsLive { get { if (SamplingRate > 0) { return Channels > 0; } return false; } } internal static float Level => Ring.Level; [HarmonyPostfix] [HarmonyWrapSafe] private static void Read_Postfix(MicWrapper __instance, float[] buffer, bool __result) { if (__result && buffer != null && PrivateVoiceGate.WantCapture) { channels = __instance.Channels; samplingRate = __instance.SamplingRate; Ring.Write(buffer, buffer.Length); } } internal static void Flush() { Ring.Reset(); Plugin.Logger.LogDebug((object)"Private voice: mic tee flushed"); } } internal static class PrivateVoiceGate { internal static volatile bool WantCapture; } internal sealed class PeakLimiter { internal const float Ceiling = 0.891f; internal const float ReleaseSeconds = 0.25f; private float reduction = 1f; private float releaseCoefficient = 1f - (float)Math.Exp(-8.333333333333333E-05); internal void Configure(int sampleRate) { if (sampleRate > 0) { releaseCoefficient = 1f - (float)Math.Exp(-1.0 / (double)(0.25f * (float)sampleRate)); } } internal void Reset() { reduction = 1f; } internal void Process(float[] data, int channels, float gain) { if (data == null) { return; } if (channels < 1) { channels = 1; } float num = reduction; float num2 = releaseCoefficient; for (int i = 0; i + channels <= data.Length; i += channels) { float num3 = 0f; for (int j = 0; j < channels; j++) { float num4 = data[i + j] * gain; if (num4 < 0f) { num4 = 0f - num4; } if (num4 > num3) { num3 = num4; } } float num5 = ((num3 > 0.891f) ? (0.891f / num3) : 1f); if (num5 < num) { num = num5; } else { num += (1f - num) * num2; if (num > num5) { num = num5; } } float num6 = gain * num; for (int k = 0; k < channels; k++) { data[i + k] *= num6; } } reduction = num; } } internal static class PrivateSpeakers { private sealed class Entry { internal GameObject Go; internal Speaker Speaker; internal AudioSource Source; internal PrivateSpeakerGain Gain; internal int Actor; internal volatile float Amplitude; internal volatile int LastFrameTick; } private const int AmplitudeStaleMs = 300; private static readonly Dictionary<int, Entry> ByPlayerId = new Dictionary<int, Entry>(); private static readonly HashSet<Speaker> AmplitudeAttached = new HashSet<Speaker>(); private static float volume = 1f; internal static Speaker? Create(int playerId, object userData) { //IL_0100: Unknown result type (might be due to invalid IL or missing references) //IL_0106: Expected O, but got Unknown if (!(userData is int num) || num <= 0) { Plugin.Logger.LogWarning((object)$"Private voice: stream {playerId} carried no usable actor claim - refusing"); return null; } if (!PrivateVoiceChannel.IsMemberActor(num)) { Plugin.Logger.LogWarning((object)$"Private voice: stream from non-member actor {num} - refusing to play"); return null; } foreach (KeyValuePair<int, Entry> item in ByPlayerId) { if (item.Key != playerId && item.Value.Actor == num) { Plugin.Logger.LogWarning((object)$"Private voice: actor {num} already claimed by another stream - refusing"); return null; } } if (ByPlayerId.TryGetValue(playerId, out Entry value)) { return value.Speaker; } GameObject val = new GameObject($"SP_PrivSpeaker_a{num}"); Object.DontDestroyOnLoad((Object)(object)val); AudioSource val2 = val.AddComponent<AudioSource>(); val2.spatialBlend = 0f; val2.mute = false; val2.dopplerLevel = 0f; val2.priority = 0; val2.outputAudioMixerGroup = null; val2.bypassEffects = false; val2.bypassListenerEffects = true; val2.bypassReverbZones = true; PrivateSpeakerGain gain = val.AddComponent<PrivateSpeakerGain>(); Speaker val3 = val.AddComponent<Speaker>(); Entry entry = new Entry { Go = val, Speaker = val3, Source = val2, Gain = gain, Actor = num }; ByPlayerId[playerId] = entry; Apply(entry, volume); val3.OnRemoteVoiceRemoveAction = delegate(Speaker s) { ByPlayerId.Remove(playerId); if ((Object)(object)s != (Object)null && (Object)(object)((Component)s).gameObject != (Object)null) { Object.Destroy((Object)(object)((Component)s).gameObject); } }; Plugin.Logger.LogInfo((object)$"Private voice: speaker for actor {num}"); return val3; } internal static void AttachAmplitude(Speaker speaker) { if ((Object)(object)speaker == (Object)null) { return; } RemoteVoiceLink remoteVoice = speaker.RemoteVoice; if (remoteVoice == null) { return; } Entry entry = null; foreach (KeyValuePair<int, Entry> item in ByPlayerId) { if (item.Value.Speaker == speaker) { entry = item.Value; break; } } if (entry == null || !AmplitudeAttached.Add(speaker)) { return; } remoteVoice.FloatFrameDecoded += delegate(FrameOut<float> frame) { float[] buf = frame.Buf; if (buf != null && buf.Length != 0) { float num = 0f; for (int i = 0; i < buf.Length; i++) { num += ((buf[i] < 0f) ? (0f - buf[i]) : buf[i]); } entry.Amplitude = num / (float)buf.Length; entry.LastFrameTick = Environment.TickCount; } }; } internal static void DestroyAll() { foreach (KeyValuePair<int, Entry> item in ByPlayerId) { Entry value = item.Value; if ((Object)(object)value.Speaker != (Object)null) { value.Speaker.OnRemoteVoiceRemoveAction = null; } if ((Object)(object)value.Go != (Object)null) { Object.Destroy((Object)(object)value.Go); } } ByPlayerId.Clear(); AmplitudeAttached.Clear(); } internal static float AmplitudeFor(int actorNumber) { foreach (KeyValuePair<int, Entry> item in ByPlayerId) { if (item.Value.Actor == actorNumber) { Entry value = item.Value; if (Environment.TickCount - value.LastFrameTick > 300) { return 0f; } return value.Amplitude; } } return 0f; } internal static int[] SpeakingActors() { List<int> list = new List<int>(ByPlayerId.Count); foreach (KeyValuePair<int, Entry> item in ByPlayerId) { list.Add(item.Value.Actor); } return list.ToArray(); } internal static void SetVolume(float value) { if (value < 0f) { value = 0f; } float num = 3f; if (value > num) { value = num; } if (value == volume) { return; } volume = value; foreach (KeyValuePair<int, Entry> item in ByPlayerId) { Apply(item.Value, value); } } private static void Apply(Entry entry, float value) { if ((Object)(object)entry.Source != (Object)null) { entry.Source.volume = ((value > 1f) ? 1f : value); } if ((Object)(object)entry.Gain != (Object)null) { entry.Gain.Gain = ((value > 1f) ? value : 1f); } } } internal sealed class PrivateSpeakerGain : MonoBehaviour { private volatile float gain = 1f; private readonly PeakLimiter limiter = new PeakLimiter(); internal float Gain { get { return gain; } set { gain = ((value < 1f) ? 1f : value); } } private void Awake() { limiter.Configure(AudioSettings.outputSampleRate); } private void OnAudioFilterRead(float[] data, int channels) { float num = gain; if (num == 1f) { limiter.Reset(); } else { limiter.Process(data, channels, num); } } } internal sealed class PrivateVoiceClient : VoiceFollowClient { private const float ConnectWarnCooldownSeconds = 5f; private const float ConnectRetryCooldownSeconds = 3f; private const string MutedPropKey = "spm"; private static bool quitting; private string token = string.Empty; private bool? publishedMuted; private Recorder? recorder; private int userDataActor; private float nextConnectWarnAt; private float nextConnectAttemptAt; private const int OpusBitrate = 64000; internal static PrivateVoiceClient? Instance { get; private set; } internal static bool EverCreated { get; private set; } internal Recorder? PrivateRecorder => recorder; protected override bool LeaderInRoom { get { if (PhotonNetwork.InRoom) { return PrivateVoiceChannel.IsValidRoomName(token); } return false; } } protected override bool LeaderOfflineMode => PhotonNetwork.OfflineMode; internal static void Init() { //IL_0013: Unknown result type (might be due to invalid IL or missing references) //IL_0018: Unknown result type (might be due to invalid IL or missing references) //IL_0021: Expected O, but got Unknown if (!((Object)(object)Instance != (Object)null)) { GameObject val = new GameObject("SP_PrivateVoice") { hideFlags = (HideFlags)61 }; Object.DontDestroyOnLoad((Object)(object)val); Instance = val.AddComponent<PrivateVoiceClient>(); ((VoiceConnection)Instance).SpeakerLinked += PrivateSpeakers.AttachAmplitude; EverCreated = true; Application.quitting += delegate { quitting = true; }; Plugin.Logger.LogInfo((object)"Private voice client created"); } } internal void Provision(string roomName) { if (!PrivateVoiceChannel.IsValidRoomName(roomName)) { Plugin.Logger.LogWarning((object)"Private voice: refusing a malformed room name"); return; } PrivateVoiceGate.WantCapture = true; bool flag = PhotonNetwork.InRoom && PhotonNetwork.LocalPlayer != null && PhotonNetwork.LocalPlayer.ActorNumber > 0; if ((Object)(object)recorder == (Object)null) { if (flag && MicTee.IsLive) { CreateRecorder(); } } else if (flag) { SyncUserData(recorder); } bool flag2 = ((VoiceConnection)this).Client != null && ((LoadBalancingClient)((VoiceConnection)this).Client).IsConnected; if (roomName == token && flag2) { return; } if (token.Length > 0 && roomName != token) { token = string.Empty; SafeDisconnect(); return; } if (token.Length == 0) { if (flag2) { return; } token = roomName; } if (!(((VoiceConnection)this).Client == null || flag2) && PhotonNetwork.InRoom && !(Time.unscaledTime < nextConnectAttemptAt)) { nextConnectAttemptAt = Time.unscaledTime + 3f; if (!((VoiceFollowClient)this).ConnectAndJoinRoom() && Time.unscaledTime >= nextConnectWarnAt) { nextConnectWarnAt = Time.unscaledTime + 5f; Plugin.Logger.LogWarning((object)("Private voice: connect/join refused for room " + PrivateVoiceChannel.Fingerprint(roomName))); } } } protected override void Start() { ((VoiceFollowClient)this).Start(); if (PhotonNetwork.NetworkingClient != null) { PhotonNetwork.NetworkingClient.StateChanged += OnLeaderStateChanged; } } private void OnLeaderStateChanged(ClientState fromState, ClientState toState) { //IL_0001: Unknown result type (might be due to invalid IL or missing references) ((VoiceFollowClient)this).LeaderStateChanged(toState); } protected override void OnDestroy() { if (Instance == this && !quitting) { Plugin.Logger.LogWarning((object)"Private voice: SP_PrivateVoice is being destroyed - the private channel cannot run again this session"); } if (PhotonNetwork.NetworkingClient != null) { PhotonNetwork.NetworkingClient.StateChanged -= OnLeaderStateChanged; } ((VoiceFollowClient)this).OnDestroy(); } private void CreateRecorder() { //IL_0005: Unknown result type (might be due to invalid IL or missing references) //IL_000b: Expected O, but got Unknown //IL_00ae: Unknown result type (might be due to invalid IL or missing references) GameObject val = new GameObject("SP_PrivateRecorder"); val.transform.SetParent(((Component)this).transform, false); Recorder val2 = val.AddComponent<Recorder>(); PrivateVoiceDsp.Mirror(val); val2.SourceType = (InputSourceType)2; val2.MicrophoneType = (MicType)1; val2.InputFactory = () => (IAudioDesc)(object)new TeeAudioReader(); userDataActor = PhotonNetwork.LocalPlayer.ActorNumber; val2.UserData = userDataActor; val2.TransmitEnabled = false; val2.VoiceDetection = false; val2.Encrypt = true; val2.DebugEchoMode = false; val2.InterestGroup = 0; val2.TargetPlayers = null; val2.SamplingRate = NearestSupportedRate(MicTee.SamplingRate); val2.Bitrate = 64000; ((VoiceConnection)this).PrimaryRecorder = val2; if (!((VoiceConnection)this).AddRecorder(val2)) { Plugin.Logger.LogWarning((object)"Private voice: AddRecorder refused - the recorder will never transmit"); Object.Destroy((Object)(object)val); } else { recorder = val2; Plugin.Logger.LogInfo((object)$"Private voice recorder ready ({MicTee.SamplingRate} Hz, {MicTee.Channels} ch, Opus {64} kbps)"); } } private void SyncUserData(Recorder rec) { int actorNumber = PhotonNetwork.LocalPlayer.ActorNumber; if (actorNumber != userDataActor) { userDataActor = actorNumber; rec.UserData = actorNumber; Plugin.Logger.LogInfo((object)"Private voice: local actor number changed, rebinding recorder identity"); } } private static SamplingRate NearestSupportedRate(int hz) { //IL_0041: Unknown result type (might be due to invalid IL or missing references) //IL_0029: Unknown result type (might be due to invalid IL or missing references) //IL_004a: Unknown result type (might be due to invalid IL or missing references) //IL_0049: Unknown result type (might be due to invalid IL or missing references) //IL_0031: 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) if (hz <= 20000) { if (hz > 10000) { if (hz <= 14000) { return (SamplingRate)12000; } return (SamplingRate)16000; } return (SamplingRate)8000; } if (hz <= 36000) { return (SamplingRate)24000; } return (SamplingRate)48000; } internal void PublishMuted(bool muted) { //IL_000e: Unknown result type (might be due to invalid IL or missing references) //IL_0015: Invalid comparison between Unknown and I4 //IL_0066: Unknown result type (might be due to invalid IL or missing references) //IL_0071: Unknown result type (might be due to invalid IL or missing references) //IL_0085: Expected O, but got Unknown if (((VoiceConnection)this).Client == null || (int)((LoadBalancingClient)((VoiceConnection)this).Client).State != 9 || ((LoadBalancingClient)((VoiceConnection)this).Client).LocalPlayer == null) { publishedMuted = null; } else if (publishedMuted != muted) { publishedMuted = muted; ((LoadBalancingClient)((VoiceConnection)this).Client).LocalPlayer.SetCustomProperties(new Hashtable { [(object)"spm"] = muted }, (Hashtable)null, (WebFlags)null); } } internal bool RemoteMuted(int actorNumber) { LoadBalancingTransport client = ((VoiceConnection)this).Client; object obj; if (client == null) { obj = null; } else { Room currentRoom = ((LoadBalancingClient)client).CurrentRoom; obj = ((currentRoom != null) ? currentRoom.Players : null); } Dictionary<int, Player> dictionary = (Dictionary<int, Player>)obj; if (dictionary == null) { return false; } PlayerRegistry instance = PlayerRegistry.Instance; if (instance == null) { return false; } string text = instance.TryGetSteamId(actorNumber); if (string.IsNullOrEmpty(text)) { return false; } bool flag = default(bool); foreach (KeyValuePair<int, Player> item in dictionary) { Player value = item.Value; if (value == null || value.IsLocal || value.IsInactive || value.UserId != text) { continue; } int num; if (value.CustomProperties != null && ((Dictionary<object, object>)(object)value.CustomProperties).TryGetValue((object)"spm", out object value2)) { if (value2 is bool) { flag = (bool)value2; num = 1; } else { num = 0; } } else { num = 0; } return (byte)((uint)num & (flag ? 1u : 0u)) != 0; } return false; } internal bool ChannelLive(string expectedRoom) { //IL_000e: Unknown result type (might be due to invalid IL or missing references) //IL_0015: Invalid comparison between Unknown and I4 if (((VoiceConnection)this).Client == null || (int)((LoadBalancingClient)((VoiceConnection)this).Client).State != 9) { return false; } Room currentRoom = ((LoadBalancingClient)((VoiceConnection)this).Client).CurrentRoom; if (currentRoom != null && currentRoom.Name == expectedRoom) { return PrivateVoiceChannel.IsValidRoomName(currentRoom.Name); } return false; } private void SafeDisconnect() { if (((VoiceConnection)this).Client != null && ((LoadBalancingClient)((VoiceConnection)this).Client).IsConnected) { ((VoiceFollowClient)this).Disconnect(); } } internal void TearDown() { if ((Object)(object)recorder != (Object)null) { recorder.TransmitEnabled = false; } PrivateSpeakers.DestroyAll(); SafeDisconnect(); PrivateVoiceGate.WantCapture = false; MicTee.Flush(); token = string.Empty; nextConnectWarnAt = 0f; nextConnectAttemptAt = 0f; } protected override string GetVoiceRoomName() { return token; } protected override bool ConnectVoice() { //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_001c: Unknown result type (might be due to invalid IL or missing references) //IL_0026: Expected O, but got Unknown //IL_0049: Unknown result type (might be due to invalid IL or missing references) //IL_00a1: Unknown result type (might be due to invalid IL or missing references) //IL_00a6: Unknown result type (might be due to invalid IL or missing references) //IL_00b6: Unknown result type (might be due to invalid IL or missing references) //IL_00bb: Unknown result type (might be due to invalid IL or missing references) //IL_006a: Unknown result type (might be due to invalid IL or missing references) //IL_006f: Unknown result type (might be due to invalid IL or missing references) //IL_0071: Expected O, but got Unknown //IL_0076: Expected O, but got Unknown AppSettings val = null; ((LoadBalancingClient)((VoiceConnection)this).Client).ServerPortOverrides = PhotonNetwork.ServerPortOverrides; val = PhotonNetwork.PhotonServerSettings.AppSettings.CopyTo(new AppSettings()); if (!string.IsNullOrEmpty(PhotonNetwork.CloudRegion)) { val.FixedRegion = PhotonNetwork.CloudRegion; } ((LoadBalancingClient)((VoiceConnection)this).Client).SerializationProtocol = PhotonNetwork.NetworkingClient.SerializationProtocol; if (PhotonNetwork.AuthValues != null) { LoadBalancingTransport client = ((VoiceConnection)this).Client; if (((LoadBalancingClient)client).AuthValues == null) { AuthenticationValues val2 = new AuthenticationValues(); AuthenticationValues val3 = val2; ((LoadBalancingClient)client).AuthValues = val2; } ((LoadBalancingClient)((VoiceConnection)this).Client).AuthValues = PhotonNetwork.AuthValues.CopyTo(((LoadBalancingClient)((VoiceConnection)this).Client).AuthValues); } ((LoadBalancingClient)((VoiceConnection)this).Client).AuthMode = PhotonNetwork.NetworkingClient.AuthMode; ((LoadBalancingClient)((VoiceConnection)this).Client).EncryptionMode = PhotonNetwork.NetworkingClient.EncryptionMode; return ((VoiceConnection)this).ConnectUsingSettings(val); } protected override bool JoinVoiceRoom(string voiceRoomName) { //IL_0019: 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_0025: Unknown result type (might be due to invalid IL or missing references) //IL_002c: 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_003e: Unknown result type (might be due to invalid IL or missing references) //IL_0046: Expected O, but got Unknown //IL_0046: Unknown result type (might be due to invalid IL or missing references) //IL_004b: Unknown result type (might be due to invalid IL or missing references) //IL_0052: 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_0060: Unknown result type (might be due to invalid IL or missing references) //IL_006a: Expected O, but got Unknown //IL_006b: Expected O, but got Unknown if (!PrivateVoiceChannel.IsValidRoomName(voiceRoomName)) { Plugin.Logger.LogWarning((object)"Private voice: join refused, malformed room name"); return false; } RoomOptions roomOptions = new RoomOptions { IsVisible = false, IsOpen = true, MaxPlayers = 0, PlayerTtl = 2000, PublishUserId = true }; EnterRoomParams val = new EnterRoomParams { RoomName = voiceRoomName, RoomOptions = roomOptions, Lobby = new TypedLobby("spv", (LobbyType)0) }; Plugin.Logger.LogInfo((object)("Private voice: joining room " + PrivateVoiceChannel.Fingerprint(voiceRoomName))); return ((LoadBalancingClient)((VoiceConnection)this).Client).OpJoinOrCreateRoom(val); } protected override void OnOperationResponseReceived(OperationResponse operationResponse) { if (operationResponse.ReturnCode == 0) { ((VoiceFollowClient)this).OnOperationResponseReceived(operationResponse); return; } Plugin.Logger.LogWarning((object)$"Private voice: op {operationResponse.OperationCode} failed ({operationResponse.ReturnCode})"); if (operationResponse.OperationCode != 226) { ((VoiceFollowClient)this).OnOperationResponseReceived(operationResponse); return; } string roomName = token; try { token = PrivateVoiceChannel.Fingerprint(roomName); ((VoiceFollowClient)this).OnOperationResponseReceived(operationResponse); } finally { token = roomName; } } protected override Speaker? InstantiateSpeakerForRemoteVoice(int playerId, byte voiceId, object userData) { try { return PrivateSpeakers.Create(playerId, userData); } catch (Exception arg) { Plugin.Logger.LogError((object)$"Private voice: speaker construction failed: {arg}"); return null; } } } internal static class PrivateVoiceDriver { internal enum Health { Off, Provisioning, Live, Listening, Failed } private const float ReleaseGraceSeconds = 0.3f; private const float IntrusionConfirmSeconds = 2f; private const float MissingClientWarnCooldownSeconds = 5f; private static bool lastVoiceJoined; private static float releaseAt; private static float nextMissingClientWarnAt; private static string provisionedRoom = string.Empty; private static float unexpectedOccupantFirstSeenAt = -1f; private static PlayerVoiceChat? gameRecorderOwner; private static Recorder? gameRecorder; internal static Health State { get; private set; } = Health.Off; internal static void Tick() { int num = ((PhotonNetwork.InRoom && PhotonNetwork.LocalPlayer != null) ? PhotonNetwork.LocalPlayer.ActorNumber : 0); PrivateVoiceChannel.SetLocalMembership(num > 0 && PrivateVoiceChannel.IsMemberActor(num)); bool localActive = PrivateVoiceChannel.LocalActive; if (!localActive && provisionedRoom.Length > 0) { TearDown("mode off"); } RecorderTransmitPatch.Suppress = localActive || Time.realtimeSinceStartup < releaseAt; if (!localActive) { if (Time.realtimeSinceStartup < releaseAt) { ForceGameRecorderOff(); } State = Health.Off; return; } ForceGameRecorderOff(); PrivateSpeakers.SetVolume(GameVolume() * PrivateVoiceLocalPrefs.Factor); PrivateVoiceClient instance = PrivateVoiceClient.Instance; if ((Object)(object)instance != (Object)null) { instance.PublishMuted(LocalDisplayMuted()); string roomName = PrivateVoiceChannel.RoomName; if (provisionedRoom != roomName) { if (provisionedRoom.Length > 0) { TearDown("room rotated"); } else { MicTee.Flush(); } instance.Provision(roomName); provisionedRoom = roomName; State = Health.Provisioning; return; } if ((Object)(object)instance.PrivateRecorder == (Object)null) { instance.Provision(roomName); bool flag = instance.ChannelLive(roomName); if (!lastVoiceJoined && flag) { MicTee.Flush(); } lastVoiceJoined = flag; State = ((!flag) ? Health.Provisioning : Health.Listening); return; } bool flag2 = instance.ChannelLive(roomName); if (!lastVoiceJoined && flag2) { MicTee.Flush(); } if (lastVoiceJoined && !flag2) { Plugin.Logger.LogInfo((object)"Private voice: channel dropped, re-provisioning"); } lastVoiceJoined = flag2; if (!flag2) { instance.Provision(roomName); SetPrivateTransmit(on: false); State = Health.Provisioning; return; } AuditOccupants(); if (State == Health.Failed) { return; } Recorder privateRecorder = instance.PrivateRecorder; if ((Object)(object)privateRecorder != (Object)null && privateRecorder.RecordingEnabled) { if (privateRecorder.InterestGroup != 0) { privateRecorder.InterestGroup = 0; } if (privateRecorder.TargetPlayers != null) { privateRecorder.TargetPlayers = null; } } SetPrivateTransmit(GameMicAllows()); State = Health.Live; } else { if (Time.realtimeSinceStartup >= nextMissingClientWarnAt) { nextMissingClientWarnAt = Time.realtimeSinceStartup + 5f; Plugin.Logger.LogWarning((object)(PrivateVoiceClient.EverCreated ? "Private voice: the voice client was created at startup but no longer exists - something destroyed the SP_PrivateVoice object, so the channel cannot run" : "Private voice: the voice client was never created - PrivateVoiceClient.Init did not run or threw")); } State = Health.Failed; } } private static void AuditOccupants() { PrivateVoiceClient? instance = PrivateVoiceClient.Instance; object obj; if (instance == null) { obj = null; } else { LoadBalancingTransport client = ((VoiceConnection)instance).Client; if (client == null) { obj = null; } else { Room currentRoom = ((LoadBalancingClient)client).CurrentRoom; obj = ((currentRoom != null) ? currentRoom.Players : null); } } Dictionary<int, Player> dictionary = (Dictionary<int, Player>)obj; if (dictionary == null) { return; } PlayerRegistry instance2 = PlayerRegistry.Instance; if (instance2 == null) { return; } int[] array = PrivateVoiceChannel.MemberActors(); for (int i = 0; i < array.Length; i++) { if (instance2.TryGetSteamId(array[i]) == null) { return; } } bool flag = false; foreach (KeyValuePair<int, Player> item in dictionary) { Player value = item.Value; if (value == null || value.IsLocal || value.IsInactive) { continue; } string userId = value.UserId; if (string.IsNullOrEmpty(userId)) { continue; } bool flag2 = false; for (int j = 0; j < array.Length; j++) { if (instance2.TryGetSteamId(array[j]) == userId) { flag2 = true; break; } } if (!flag2) { flag = true; break; } } if (!flag) { unexpectedOccupantFirstSeenAt = -1f; return; } float realtimeSinceStartup = Time.realtimeSinceStartup; if (unexpectedOccupantFirstSeenAt < 0f) { unexpectedOccupantFirstSeenAt = realtimeSinceStartup; } else if (!(realtimeSinceStartup - unexpectedOccupantFirstSeenAt < 2f)) { Plugin.Logger.LogWarning((object)"Private voice: unexpected occupant persisted - muting the private channel"); SetPrivateTransmit(on: false); State = Health.Failed; } } private static bool GameMicAllows() { PlayerVoiceChat instance = PlayerVoiceChat.instance; if ((Object)(object)instance == (Object)null) { return false; } if (!instance.microphoneEnabled) { return false; } if ((Object)(object)DataDirector.instance == (Object)null) { return false; } if (DataDirector.instance.toggleMute) { return false; } if ((Object)(object)AudioManager.instance == (Object)null) { return false; } if (AudioManager.instance.pushToTalk && !SemiFunc.InputHold((InputKey)25)) { return false; } return true; } internal static bool LocalDisplayMuted() { PlayerVoiceChat instance = PlayerVoiceChat.instance; if ((Object)(object)instance == (Object)null || !instance.microphoneEnabled) { return true; } if (!((Object)(object)DataDirector.instance == (Object)null)) { return DataDirector.instance.toggleMute; } return true; } private static float GameVolume() { if ((Object)(object)DataDirector.instance == (Object)null) { return 1f; } float num = (float)DataDirector.instance.SettingValueFetch((Setting)13) * 0.01f; float num2 = (float)DataDirector.instance.SettingValueFetch((Setting)4) * 0.01f; float num3 = num * num2; if (num3 < 0f) { num3 = 0f; } if (num3 > 1f) { num3 = 1f; } return num3; } private static void SetPrivateTransmit(bool on) { Recorder val = PrivateVoiceClient.Instance?.PrivateRecorder; if ((Object)(object)val != (Object)null && val.TransmitEnabled != on) { val.TransmitEnabled = on; } } private static void ForceGameRecorderOff() { PlayerVoiceChat instance = PlayerVoiceChat.instance; if (!((Object)(object)instance == (Object)null)) { if ((Object)(object)gameRecorder == (Object)null || instance != gameRecorderOwner) { gameRecorderOwner = instance; gameRecorder = ((Component)instance).GetComponent<Recorder>(); } if ((Object)(object)gameRecorder != (Object)null) { RecorderTransmitPatch.ForceOff(gameRecorder); } } } internal static void TearDown(string reason) { if (provisionedRoom.Length != 0 || State != Health.Off) { Plugin.Logger.LogInfo((object)("Private voice: tearing down (" + reason + ")")); SetPrivateTransmit(on: false); PrivateVoiceClient.Instance?.TearDown(); provisionedRoom = string.Empty; lastVoiceJoined = false; unexpectedOccupantFirstSeenAt = -1f; State = Health.Off; releaseAt = Time.realtimeSinceStartup + 0.3f; } } } internal static class PrivateVoiceLocalPrefs { internal const int MaxVolumePercent = 300; internal static int VolumePercent => ModConfig.PrivateChannelVolume.Value; internal static int MicLiftDb { get { int value = ModConfig.PrivateChannelMicGain.Value; if (value >= 0) { if (value <= 40) { return value; } return 40; } return 0; } } internal static float Factor => (float)VolumePercent * 0.01f; internal static void SetVolumePercent(int value) { int num = ((value >= 0) ? ((value > 300) ? 300 : value) : 0); if (ModConfig.PrivateChannelVolume.Value != num) { ModConfig.PrivateChannelVolume.Value = num; } } internal static void SetMicLiftDb(int value) { int num = ((value >= 0) ? ((value > 40) ? 40 : value) : 0); if (ModConfig.PrivateChannelMicGain.Value != num) { ModConfig.PrivateChannelMicGain.Value = num; } } } internal static class PrivateVoiceDsp { internal const int DefaultMicGainDb = 20; internal const int MaxMicGainDb = 40; private static WebRtcAudioDsp? live; private static bool subscribed; internal static string LastResult { get; private set; } = "not attempted"; private static int MicGainDb { get { int value = ModConfig.PrivateChannelMicGain.Value; if (value >= 0) { if (value <= 40) { return value; } return 40; } return 0; } } internal static void Mirror(GameObject recorderObject) { PlayerVoiceChat instance = PlayerVoiceChat.instance; WebRtcAudioDsp val = (((Object)(object)instance != (Object)null) ? ((Component)instance).GetComponent<WebRtcAudioDsp>() : null); if ((Object)(object)val == (Object)null) { LastResult = "not mirrored (the game's recorder carries no WebRtcAudioDsp)"; Plugin.Logger.LogWarning((object)"Private voice: the game's recorder carries no WebRtcAudioDsp - the private stream keeps the raw microphone level and will stay quieter than the public channel"); return; } if (MicTee.Channels != 1) { LastResult = $"skipped (capture is {MicTee.Channels} ch, WebRtcAudioDsp is mono-only)"; Plugin.Logger.LogWarning((object)$"Private voice: capture is {MicTee.Channels} ch and WebRtcAudioDsp is mono-only - the DSP mirror is skipped and the private stream keeps the raw microphone level"); return; } WebRtcAudioDsp val2 = recorderObject.AddComponent<WebRtcAudioDsp>(); val2.AEC = val.AEC; val2.AecHighPass = val.AecHighPass; val2.ReverseStreamDelayMs = val.ReverseStreamDelayMs; val2.HighPass = val.HighPass; val2.NoiseSuppression = val.NoiseSuppression; val2.AGC = val.AGC; val2.AgcTargetLevel = val.AgcTargetLevel; val2.Bypass = val.Bypass; val2.AgcCompressionGain = MicGainDb; live = val2; if (!subscribed) { subscribed = true; ModConfig.PrivateChannelMicGain.SettingChanged += delegate { ApplyMicGainToLive(); }; } val2.VAD = false; LastResult = Describe(val2); Plugin.Logger.LogInfo((object)$"Private voice: DSP mirrored from the game's recorder (AGC {OnOff(val2.AGC)} target {val2.AgcTargetLevel} dBFS / max lift {val2.AgcCompressionGain} dB (game: {val.AgcCompressionGain}), noise suppression {OnOff(val2.NoiseSuppression)}, high pass {OnOff(val2.HighPass)}, AEC {OnOff(val2.AEC)}, VAD off by design, bypass {OnOff(val2.Bypass)})"); } private static void ApplyMicGainToLive() { WebRtcAudioDsp val = live; if (!((Object)(object)val == (Object)null)) { val.AgcCompressionGain = MicGainDb; LastResult = Describe(val); Plugin.Logger.LogInfo((object)$"Private voice: mic lift now up to {val.AgcCompressionGain} dB"); } } private static string Describe(WebRtcAudioDsp dsp) { return $"mirrored (AGC {OnOff(dsp.AGC)}, mic lift up to {dsp.AgcCompressionGain} dB, noise suppression {OnOff(dsp.NoiseSuppression)}, AEC {OnOff(dsp.AEC)})"; } private static string OnOff(bool value) { if (!value) { return "off"; } return "on"; } } internal static class PrivateVoicePresenceReporter { private static int lastReportedEpoch = -1; private static bool lastReportedConnected; internal static bool IsConnected(PrivateVoiceDriver.Health state) { if (state != PrivateVoiceDriver.Health.Live) { return state == PrivateVoiceDriver.Health.Listening; } return true; } internal static void Report(PrivateVoiceDriver.Health state) { if (!PrivateVoiceChannel.Active || !PrivateVoiceChannel.LocalActive) { lastReportedEpoch = -1; lastReportedConnected = false; return; } int epoch = PrivateVoiceChannel.Epoch; bool flag = IsConnected(state); if (epoch != lastReportedEpoch || flag != lastReportedConnected) { lastReportedEpoch = epoch; lastReportedConnected = flag; if (SemiFunc.IsMasterClient()) { PrivateVoiceSync.NoteLocalPresence(flag); return; } CommandSender.Send("PrivateVoicePresence", new object[2] { epoch.ToString(), flag ? "1" : "0" }, (ReceiverGroup)2); } } internal static void Reset() { lastReportedEpoch = -1; lastReportedConnected = false; } } internal sealed class TeeAudioReader : IAudioReader<float>, IDataReader<float>, IDisposable, IAudioDesc { public int SamplingRate => MicTee.SamplingRate; public int Channels => MicTee.Channels; public string? Error { get { if (!MicTee.IsLive) { return "mic tee not live"; } return null; } } public bool Read(float[] buffer) { return MicTee.Ring.Read(buffer); } public void Dispose() { } } } namespace SharePermissions.UI { internal sealed class BandRow : MonoBehaviour { private sealed class Slot { internal RectTransform Rect; internal TextMeshProUGUI Label; internal REPOButton? Button; internal int Order; internal string? Reserve; } private readonly List<Slot> slots = new List<Slot>(); private int settleFrames; internal static BandRow For(REPOPopupPage page) { BandRow component = ((Component)page).GetComponent<BandRow>(); if (!((Object)(object)component != (Object)null)) { return ((Component)page).gameObject.AddComponent<BandRow>(); } return component; } internal void Add(REPOButton button, int order, string? reserve = null) { Add(((REPOElement)button).rectTransform, button.labelTMP, button, order, reserve); } internal void Add(REPOLabel label, int order, string? reserve = null) { Add(((REPOElement)label).rectTransform, label.labelTMP, null, order, reserve); } private void Add(RectTransform rect, TextMeshProUGUI label, REPOButton? button, int order, string? reserve) { slots.Add(new Slot { Rect = rect, Label = label, Button = button, Order = order, Reserve = reserve }); slots.Sort((Slot a, Slot b) => a.Order.CompareTo(b.Order)); } internal void Relayout() { settleFrames = 2; Apply(); } private void LateUpdate() { if (settleFrames > 0) { settleFrames--; Apply(); } } private void Apply() { //IL_01ae: Unknown result type (might be due to invalid IL or missing references) //IL_01cb: Unknown result type (might be due to invalid IL or missing references) //IL_0194: Unknown result type (might be due to invalid IL or missing references) //IL_01eb: Unknown result type (might be due to invalid IL or missing references) slots.RemoveAll((Slot s) => (Object)(object)s.Rect == (Object)null || (Object)(object)s.Label == (Object)null); List<Slot> list = new List<Slot>(); foreach (Slot slot2 in slots) { if (((Component)slot2.Rect).gameObject.activeInHierarchy) { list.Add(slot2); } } if (list.Count == 0) { return; } Vector2[] array = (Vector2[])(object)new Vector2[list.Count]; float totalWidth = MeasureAll(list, 16f, array); float num = 6f; float num2 = ModStyle.BandRight - ModStyle.BandLeft; if (num2 <= 0f) { return; } float num3 = TabBandLayout.FitScale(totalWidth, num2 - num * (float)(list.Count - 1)); if (num3 < 1f) { MeasureAll(list, 16f * num3, array); } float[] array2 = new float[list.Count]; for (int num4 = 0; num4 < list.Count; num4++) { array2[num4] = array[num4].x; } float num5 = 0f; for (int num6 = 0; num6 < list.Count; num6++) { num5 = Mathf.Max(num5, array[num6].y); } float[] array3 = TabBandLayout.SpreadBandLeftEdges(array2, ModStyle.BandLeft, ModStyle.BandRight, num); for (int num7 = 0; num7 < list.Count; num7++) { Slot slot = list[num7]; if ((Object)(object)slot.Button != (Object)null) { MenuUiHelpers.PinButtonSize(slot.Button, new Vector2(array2[num7], num5)); } else { slot.Rect.sizeDelta = new Vector2(array2[num7], num5); ((TMP_Text)slot.Label).rectTransform.sizeDelta = new Vector2(array2[num7], num5); } ((Transform)slot.Rect).localPosition = new Vector3(array3[num7], 20f, 0f); } AlignCaptionsToOneLine(list); } private static void AlignCaptionsToOneLine(List<Slot> live) { //IL_007e: Unknown result type (might be due to invalid IL or missing references) //IL_0083: Unknown result type (might be due to invalid IL or missing references) //IL_0089: Unknown result type (might be due to invalid IL or missing references) //IL_009b: Unknown result type (might be due to invalid IL or missing references) if (live.Count < 2) { return; } float[] array = new float[live.Count]; for (int i = 0; i < live.Count; i++) { array[i] = CaptionCentre(live[i]); } float[] array2 = (float[])array.Clone(); Array.Sort(array2); float num = array2[array2.Length / 2]; for (int j = 0; j < live.Count; j++) { float num2 = num - array[j]; if (!(Mathf.Abs(num2) < 0.01f)) { RectTransform rect = live[j].Rect; Vector3 localPosition = ((Transform)rect).localPosition; localPosition.y = ((Transform)rect).localPosition.y + num2; ((Transform)rect).localPosition = localPosition; } } } private static float CaptionCentre(Slot s) { //IL_003e: Unknown result type (might be due to invalid IL or missing references) //IL_0043: Unknown result type (might be due to invalid IL or missing references) //IL_0047: 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_0075: Unknown result type (might be due to invalid IL or missing references) //IL_0079: 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_005e: 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_008a: Unknown result type (might be due to invalid IL or missing references) //IL_0095: Unknown result type (might be due to invalid IL or missing references) TextMeshProUGUI label = s.Label; RectTransform rectTransform = ((TMP_Text)label).rectTransform; ((TMP_Text)label).ForceMeshUpdate(false, false); TMP_TextInfo textInfo = ((TMP_Text)label).textInfo; float num; if (textInfo != null && textInfo.lineCount > 0) { num = textInfo.lineInfo[0].baseline; } else { Bounds textBounds = ((TMP_Text)label).textBounds; if (((Bounds)(ref textBounds)).extents.y > 0f) { textBounds = ((TMP_Text)label).textBounds; num = ((Bounds)(ref textBounds)).center.y; } else { Rect rect = rectTransform.rect; num = ((Rect)(ref rect)).center.y; } } return ((Transform)s.Rect).localPosition.y + ((Transform)rectTransform).localPosition.y + num; } private static float MeasureAll(List<Slot> live, float fontSize, Vector2[] sizes) { //IL_0064: Unknown result type (might be due to invalid IL or missing references) //IL_0069: Unknown result type (might be due to invalid IL or missing references) float num = 0f; for (int i = 0; i < live.Count; i++) { Slot slot = live[i]; ((TMP_Text)slot.Label).enableAutoSizing = false; ((TMP_Text)slot.Label).alignment = (TextAlignmentOptions)4097; ((TMP_Text)slot.Label).fontSize = fontSize; ((TMP_Text)slot.Label).ForceMeshUpdate(false, false); sizes[i] = ((TMP_Text)slot.Label).GetPreferredValues(slot.Reserve ?? ((TMP_Text)slot.Label).text); num += sizes[i].x; } return num; } } internal static class CaptionWidth { internal const int WideTenths = 28; private const int NarrowTenths = 10; internal static bool IsWide(char c) { if ((c < 'ᄀ' || c > 'ᅟ') && (c < '⺀' || c > '〾') && (c < 'ぁ' || c > '㏿') && (c < '㐀' || c > '䶿') && (c < '一' || c > '鿿') && (c < 'ꥠ' || c > '\ua97f') && (c < '가' || c > '힣') && (c < '豈' || c > '\ufaff') && (c < '︰' || c > '\ufe4f') && (c < '!' || c > '⦆')) { if (c >= '¢') { return c <= '₩'; } return false; } return true; } internal static int Tenths(string text) { int num = 0; foreach (char c in text) { num += (IsWide(c) ? 28 : 10); } return num; } internal static string Fit(string text, int budget) { if (Tenths(text) <= budget * 10) { return text; } int num = (budget - 1) * 10; int num2 = 0; int i; for (i = 0; i < text.Length; i++) { int num3 = num2 + (IsWide(text[i]) ? 28 : 10); if (num3 > num) { break; } num2 = num3; } if (i > 0 && char.IsHighSurrogate(text[i - 1])) { i--; } return text.Substring(0, i) + "…"; } } internal static class ColorPickerPage { private const float ContentWidth = 250f; private const float SwatchSize = 34f; private const float SwatchPitch = 38f; private const int SwatchesPerRow = 6; private static readonly IReadOnlyList<Color> FallbackPalette = (IReadOnlyList<Color>)(object)new Color[16] { ModStyle.RoleHost, ModStyle.RoleModerator, ModStyle.RoleModUser, ModStyle.RoleNone, ModStyle.SevBan, ModStyle.SevRevoke, ModStyle.SevGrant, ModStyle.SevFlood, Color.white, Color.red, Color.green, Color.blue, Color.yellow, Color.cyan, Color.magenta, Color.gray }; internal static void Open(string title, string description, Color current, Action<Color> onPicked, string? clearCaption = null, Action? onClear = null) { //IL_0077: 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) //IL_00ec: Unknown result type (might be due to invalid IL or missing references) REPOPopupPage page = MenuUiHelpers.CreatePage(title, delegate { }); MenuUiHelpers.AddLabel(page, description); MenuUiHelpers.AddLabel(page, "<size=75%><color=#6f7d88>" + Loc.T("Current") + "</color> <color=#" + NameColors.ToHex(current) + ">#" + NameColors.ToHex(current) + "</color></size>"); foreach (IReadOnlyList<Color> item in Rows(Palette())) { AddSwatchRow(page, item, Choose); } AddHexInput(page, current, Choose); if (onClear != null && clearCaption != null) { MenuUiHelpers.AddScrollViewButton(page, clearCaption, delegate { MenuUiHelpers.ClosePage(page); onClear(); }); } MenuUiHelpers.OpenPage(page); void Choose(Color color) { //IL_0012: Unknown result type (might be due to invalid IL or missing references) MenuUiHelpers.ClosePage(page); onPicked(color); } } private static void AddSwatchRow(REPOPopupPage page, IReadOnlyList<Color> colors, Action<Color> onChoose) { //IL_001c: Unknown result type (might be due to invalid IL or missing references) //IL_0030: Expected O, but got Unknown page.AddElementToScrollView((ScrollViewBuilderDelegate)delegate(Transform scrollView) { //IL_0018: Unknown result type (might be due to invalid IL or missing references) //IL_001e: Expected O, but got Unknown //IL_0024: Unknown result type (might be due to invalid IL or missing references) //IL_002a: Expected O, but got Unknown //IL_0033: 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_0049: Unknown result type (might be due to invalid IL or missing references) //IL_005e: Unknown result type (might be due to invalid IL or missing references) //IL_0081: 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_00aa: 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_00cf: Unknown result type (might be due to invalid IL or missing references) GameObject val = new GameObject("SharePermissions_SwatchRow", new Type[1] { typeof(RectTransform) }); RectTransform val2 = (RectTransform)val.transform; ((Transform)val2).SetParent(scrollView, false); val2.anchorMin = Vector2.zero; val2.anchorMax = Vector2.zero; val2.pivot = Vector2.zero; val2.sizeDelta = new Vector2(250f, 34f); for (int i = 0; i < colors.Count; i++) { Color color = colors[i]; REPOButton button = MenuAPI.CreateREPOButton("", (Action)delegate { //IL_000c: Unknown result type (might be due to invalid IL or missing references) onChoose(color); }, (Transform)(object)val2, new Vector2((float)i * 38f, 0f)); MenuUiHelpers.PinButtonSize(button, new Vector2(34f, 34f)); StyleSwatch(button, color); } return val2; }, 0f, 2f); } private static void StyleSwatch(REPOButton button, Color color) { //IL_0011: Unknown result type (might be due to invalid IL or missing references) //IL_004b: Unknown result type (might be due to invalid IL or missing references) //IL_0051: Unknown result type (might be due to invalid IL or missing references) //IL_0056: Unknown result type (might be due to invalid IL or missing references) //IL_0061: 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_006d: Unknown result type (might be due to invalid IL or missing references) //IL_006e: Unknown result type (might be due to invalid IL or missing references) //IL_0078: Unknown result type (might be due to invalid IL or missing references) //IL_007d: Unknown result type (might be due to invalid IL or missing references) Image component = ((Component)button).GetComponent<Image>(); if ((Object)(object)component != (Object)null) { ((Graphic)component).color = color; ((Graphic)component).raycastTarget = true; } if (!((Object)(object)button.menuButton == (Object)null)) { button.menuButton.resizeButton = false; button.menuButton.customColors = true; button.menuButton.colorNormal = NameColors.Dimmed(color, 0.75f); button.menuButton.colorHover = color; button.menuButton.colorClick = Color.Lerp(color, Color.white, 0.95f); } } private static void AddHexInput(REPOPopupPage page, Color current, Action<Color> onChoose) { //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_001c: Unknown result type (might be due to invalid IL or missing references) //IL_0030: Expected O, but got Unknown page.AddElementToScrollView((ScrollViewBuilderDelegate)delegate(Transform scrollView) { //IL_0030: Unknown result type (might be due to invalid IL or missing references) //IL_0042: 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) string text = Loc.T("Hex"); Action<string> obj = delegate(string value) { //IL_0010: Unknown result type (might be due to invalid IL or missing references) if (NameColors.TryParseHex(value, out var color)) { onChoose(color); } else { Plugin.Logger.LogInfo((object)("Name color ignored: '" + value + "' is not a #RRGGBB value")); } }; string text2 = "#" + NameColors.ToHex(current); REPOInputField val = MenuAPI.CreateREPOInputField(text, obj, scrollView, default(Vector2), true, text2, ""); ((TMP_Text)val.labelTMP).fontSize = 14f; return ((REPOElement)val).rectTransform; }, 4f, 2f); MenuUiHelpers.AddLabel(page, "<size=75%><color=#6f7d88>" + Loc.T("Or type a color as #RRGGBB and press enter.") + "</color></size>"); } private static IEnumerable<IReadOnlyList<Color>> Rows(IReadOnlyList<Color> colors) { for (int start = 0; start < colors.Count; start += 6) { yield return colors.Skip(start).Take(6).ToList(); } } private static IReadOnlyList<Color> Palette() { try { IReadOnlyList<Color> readOnlyList = GamePaletteRaw(); if (readOnlyList.Count > 0) { return readOnlyList; } } catch (Exception ex) { Plugin.Logger.LogWarning((object)("[SharePermissions] Game color palette unreadable, using the built-in one: " + ex.Message)); } return FallbackPalette; } [MethodImpl(MethodImplOptions.NoInlining)] private static IReadOnlyList<Color> GamePaletteRaw() { MetaManager instance = MetaManager.instance; if ((Object)(object)instance == (Object)null || instance.colors == null) { return Array.Empty<Color>(); } List<SemiColor> order = instance.colorsUIOrder; return (from c in instance.colors where (Object)(object)c != (Object)null orderby (order != null) ? order.IndexOf(c) : 0 select c.color).ToList(); } } internal sealed class ExtrasPanelController { private readonly REPOPopupPage page; private REPOScrollViewElement? channelHeaderElem; private REPOLabel? offHint; private REPOScrollViewElement? offHintElem; private string offHintCache = string.Empty; private REPOButton? privateToggle; private REPOScrollViewElement? privateToggleElem; private const int MaxOneOnOneRows = 8; private const float OneOnOneRescanSeconds = 0.25f; private REPOScrollViewElement? oneOnOneHintElem; private REPOScrollViewElement? oneOnOneAskHintElem; private readonly REPOButton?[] oneOnOneRows = (REPOButton?[])(object)new REPOButton[8]; private readonly REPOScrollViewElement?[] oneOnOneRowElems = (REPOScrollViewElement?[])(object)new REPOScrollViewElement[8]; private readonly int[] oneOnOneRowActors = new int[8]; private readonly string[] oneOnOneRowNames = new string[8]; private readonly bool[] oneOnOneRowIsTarget = new bool[8]; private readonly bool[] oneOnOneRowOffline = new bool[8]; private readonly int[] oneOnOneCandidates = new int[8]; private int oneOnOneCount; private float nextOneOnOneRescanAt; private float nextOneOnOneOverflowWarnAt; private REPOLabel? pendingLabel; private REPOScrollViewElement? pendingLabelElem; private bool pendingLabelForMe; private int pendingLabelActor; private REPOScrollViewElement? acceptButtonElem; private REPOScrollViewElement? declineButtonElem; private REPOScrollViewElement? cancelButtonElem; private REPOScrollViewElement? hangUpButtonElem; private REPOLabel? privateStatus; private REPOScrollViewElement? privateStatusElem; private bool privateTalking; private float privateTalkHoldUntil; private PrivateVoiceDriver.Health lastStatusState = (PrivateVoiceDriver.Health)(-1); private bool lastStatusTransmitting; private bool lastStatusShowTalkDot; private int lastToggleScope = -1; private REPOSlider? privateVolumeSlider; private REPOScrollViewElement? privateVolumeSliderElem; private REPOSlider? micLiftSlider; private REPOScrollViewElement? micLiftSliderElem; private REPOScrollViewElement? micLiftHintElem; private bool tabVisible; internal ExtrasPanelController(REPOPopupPage page) { this.page = page; } internal void Build() { //IL_0032: Unknown result type (might be due to invalid IL or missing references) //IL_0046: Expected O, but got Unknown //IL_00c6: Unknown result type (might be due to invalid IL or missing references) //IL_00da: Expected O, but got Unknown //IL_0115: Unknown result type (might be due to invalid IL or missing references) //IL_0129: Expected O, but got Unknown //IL_0217: Unknown result type (might be due to invalid IL or missing references) //IL_022b: Expected O, but got Unknown //IL_0249: Unknown result type (might be due to invalid IL or missing references) //IL_025d: Expected O, but got Unknown //IL_027b: Unknown result type (might be due to invalid IL or missing references) //IL_028f: Expected O, but got Unknown //IL_02ad: Unknown result type (might be due to invalid IL or missing references) //IL_02c1: Expected O, but got Unknown channelHeaderElem = AddLabel("<size=115%>" + Loc.T("Private channel") + "</size>"); page.AddElementToScrollView((ScrollViewBuilderDelegate)delegate(Transform sv) { //IL_0024: 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_006b: Unknown result type (might be due to invalid IL or missing references) privateToggle = MenuAPI.CreateREPOButton("", (Action)ModerationActions.TogglePrivateVoice, sv, default(Vector2)); ((TMP_Text)privateToggle.labelTMP).richText = true; ((TMP_Text)privateToggle.labelTMP).fontSize = 14f; MenuUiHelpers.PinButtonSize(privateToggle, new Vector2(250f, 24f)); return ((REPOElement)privateToggle).rectTransform; }, 0f, 2f); privateToggleElem = Elem((Component?)(object)privateToggle); oneOnOneHintElem = AddLabel("<size=75%><color=#6f7d88>" + Loc.T("Or with a single moderator - the others hear nothing:") + "</color></size>"); oneOnOneAskHintElem = AddLabel("<size=75%><color=#6f7d88>" + Loc.T("Ask for a 1 on 1 - the others hear nothing:") + "</color></size>"); for (int num = 0; num < oneOnOneRows.Length; num++) { int slot = num; page.AddElementToScrollView((ScrollViewBuilderDelegate)delegate(Transform sv) { //IL_0038: 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_009f: Unknown result type (might be due to invalid IL or missing references) oneOnOneRows[slot] = MenuAPI.CreateREPOButton("", (Action)delegate { OnOneOnOneClicked(slot); }, sv, default(Vector2)); ((TMP_Text)oneOnOneRows[slot].labelTMP).richText = true; ((TMP_Text)oneOnOneRows[slot].labelTMP).fontSize = 14f; MenuUiHelpers.PinButtonSize(oneOnOneRows[slot], new Vector2(250f, 24f)); return ((REPOElement)oneOnOneRows[slot]).rectTransform; }, 0f, 2f); oneOnOneRowElems[slot] = Elem((Component?)(object)oneOnOneRows[slot]); } page.AddElementToScrollView((ScrollViewBuilderDelegate)delegate(Transform sv) { //IL_0009: 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) pendingLabel = MenuAPI.CreateREPOLabel("", sv, default(Vector2)); ((TMP_Text)pendingLabel.labelTMP).richText = true; ((TMP_Text)pendingLabel.labelTMP).fontSize = 14f; return ((REPOElement)pendingLabel).rectTransform; }, 0f, 6f); pendingLabelElem = Elem((Component?)(object)pendingLabel); AddActionButton(Loc.T("Accept"), delegate { ModerationActions.AnswerOneOnOne(accept: true); }, out acceptButtonElem); AddActionButton(Loc.T("Decline"), delegate { ModerationActions.AnswerOneOnOne(accept: false); }, out declineButtonElem); AddActionButton(Loc.T("Cancel"), ModerationActions.CancelOneOnOne, out cancelButtonElem); AddActionButton(Loc.T("Hang up"), ModerationActions.HangUp, out hangUpButtonElem); page.AddElementToScrollView((ScrollViewBuilderDelegate)delegate(Transform sv) { //IL_0009: 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) offHint = MenuAPI.CreateREPOLabel("", sv, default(Vector2)); ((TMP_Text)offHint.labelTMP).richText = true; ((TMP_Text)offHint.labelTMP).fontSize = 14f; return ((REPOElement)offHint).rectTransform; }, 0f, 6f); offHintElem = Elem((Component?)(object)offHint); page.AddElementToScrollView((ScrollViewBuilderDelegate)delegate(Transform sv) { //IL_0009: 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) privateStatus = MenuAPI.CreateREPOLabel("", sv, default(Vector2)); ((TMP_Text)privateStatus.labelTMP).richText = true; ((TMP_Text)privateStatus.labelTMP).fontSize = 14f; return ((REPOElement)privateStatus).rectTransform; }, 0f, 2f); privateStatusElem = Elem((Component?)(object)privateStatus); page.AddElementToScrollView((ScrollViewBuilderDelegate)delegate(Transform sv) { //IL_0034: 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) string text = Loc.T("Private volume"); Action<int> action = OnPrivateVolumeChanged; int volumePercent = PrivateVoiceLocalPrefs.VolumePercent; privateVolumeSlider = MenuAPI.CreateREPOSlider(text, "", action, sv, default(Vector2), 0, 300, volumePercent, "", "%", (BarBehavior)0); return ((REPOElement)privateVolumeSlider).rectTransform; }, 0f, 2f); privateVolumeSliderElem = Elem((Component?)(object)privateVolumeSlider); page.AddElementToScrollView((ScrollViewBuilderDelegate)delegate(Transform sv) { //IL_0034: 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) string text = Loc.T("Mic lift"); Action<int> action = OnMicLiftChanged; int micLiftDb = PrivateVoiceLocalPrefs.MicLiftDb; micLiftSlider = MenuAPI.CreateREPOSlider(text, "", action, sv, default(Vector2), 0, 40, micLiftDb, "", " dB", (BarBehavior)0); return ((REPOElement)micLiftSlider).rectTransform; }, 0f, 2f); micLiftSliderElem = Elem((Component?)(object)micLiftSlider); micLiftHintElem = AddLabel("<size=75%><color=#6f7d88>" + Loc.T("How far a quiet microphone is lifted before it goes out on the channel. The game's own chat allows 9 dB.") + "</color></size>"); } internal void SetTabVisible(bool visible) { tabVisible = visible; if (!visible) { SetVisibility(channelHeaderElem, visible: false); SetVisibility(privateToggleElem, visible: false); HideOneOnOneRows(); SetVisibility(offHintElem, visible: false); SetVisibility(privateStatusElem, visible: false); SetVisibility(privateVolumeSliderElem, visible: false); SetVisibility(micLiftSliderElem, visible: false); SetVisibility(micLiftHintElem, visible: false); SetVisibility(pendingLabelElem, visible: false); SetVisibility(acceptButtonElem, visible: false); SetVisibility(declineButtonElem, visible: false); SetVisibility(cancelButtonElem, visible: false); SetVisibility(hangUpButtonElem, visible: false); } else { Tick(); } } internal void Tick() { if (!((Object)(object)page == (Object)null) && tabVisible) { bool flag = SemiFunc.IsMasterClient(); bool localActive = PrivateVoiceChannel.LocalActive; bool mayStartOneOnOne = ModerationActions.MayStartOneOnOne; int num; if (!flag) { num = (Voice1on1Local.HasInvite ? 1 : 0); } else if (Voice1on1Invites.Pending) { int target = Voice1on1Invites.Target; Player localPlayer = PhotonNetwork.LocalPlayer; num = ((target == ((localPlayer != null) ? localPlayer.ActorNumber : (-1))) ? 1 : 0); } else { num = 0; } bool flag2 = (byte)num != 0; int inviteFrom = (flag ? Voice1on1Invites.Requester : Voice1on1Local.InviteFrom); bool flag3 = !flag && Voice1on1Local.Waiting; bool flag4 = !flag && !localActive && !flag2 && !flag3 && (!mayStartOneOnOne || PrivateVoiceChannel.Active); SetVisibility(channelHeaderElem, visible: true); SetVisibility(privateToggleElem, flag); SetVisibility(offHintElem, flag4); SetVisibility(privateStatusElem, localActive); SetVisibility(privateVolumeSliderElem, localActive); SetVisibility(micLiftSliderElem, localActive); SetVisibility(micLiftHintElem, localActive); SetVisibility(pendingLabelElem, flag2 || flag3); SetVisibility(acceptButtonElem, flag2); SetVisibility(declineButtonElem, flag2); SetVisibility(cancelButtonElem, flag3); SetVisibility(hangUpButtonElem, !flag && ModerationActions.LocalMayHangUp); if (flag2 || flag3) { UpdatePendingLabel(flag2, inviteFrom); } if (flag) { UpdateChannelToggle(); UpdateOneOnOneRows(); } else if (mayStartOneOnOne && !PrivateVoiceChannel.Active && !flag2 && !flag3) { UpdateOneOnOneRows(); } else { HideOneOnOneRows(); } if (flag4) { UpdateOffHint(); } if (localActive) { UpdateStatus(); } else { lastStatusState = (PrivateVoiceDriver.Health)(-1); } } } private void UpdatePendingLabel(bool inviteForMe, int inviteFrom) { if (!((Object)(object)pendingLabel == (Object)null)) { int num = (inviteForMe ? inviteFrom : Voice1on1Local.WaitingOn); if (inviteForMe != pendingLabelForMe || num != pendingLabelActor) { pendingLabelForMe = inviteForMe; pendingLabelActor = num; string text = (inviteForMe ? Loc.T("{0} wants a 1 on 1", "<color=#4dd07a>" + ResolveModName(num) + "</color>") : Loc.T("Waiting for {0}", ResolveModName(num))); ((TMP_Text)pendingLabel.labelTMP).text = text; MenuUiHelpers.SizeWrappedLabel(pendingLabel, text, 250f); } } } private void UpdateChannelToggle() { if (!((Object)(object)privateToggle == (Object)null)) { int num = (PrivateVoiceChannel.Active ? ((PrivateVoiceSync.TargetActor > 0) ? 1 : ((PrivateVoiceSync.PairActors.A > 0) ? 2 : 3)) : 0); if (num != lastToggleScope) { lastToggleScope = num; string text = num switch { 0 => Loc.T("Start private channel:") + " <color=#6f7d88>" + Loc.T("all moderators") + "</color>", 1 => Loc.T("Private channel:") + " <color=#4dd07a>" + Loc.T("ON - 1 on 1") + "</color>", 2 => Loc.T("Private channel:") + " <color=#4dd07a>" + Loc.T("ON - 1 on 1 between moderators") + "</color>", _ => Loc.T("Private channel:") + " <color=#4dd07a>" + Loc.T("ON - all moderators") + "</color>", }; ((TMP_Text)privateToggle.labelTMP).text = text; } } } private void UpdateOneOnOneRows() { float unscaledTime = Time.unscaledTime; if (unscaledTime >= nextOneOnOneRescanAt) { nextOneOnOneRescanAt = unscaledTime + 0.25f; RescanOneOnOneCandidates(unscaledTime); } bool flag = SemiFunc.IsMasterClient(); SetVisibility(oneOnOneHintElem, flag && oneOnOneCount > 0); SetVisibility(oneOnOneAskHintElem, !flag && oneOnOneCount > 0); int num = (flag ? PrivateVoiceSync.TargetActor : 0); for (int i = 0; i < oneOnOneRows.Length; i++) { bool flag2 = i < oneOnOneCount; SetVisibility(oneOnOneRowElems[i], flag2); if (!flag2) { continue; } REPOButton val = oneOnOneRows[i]; if ((Object)(object)val == (Object)null) { continue; } int num2 = oneOnOneCandidates[i]; bool flag3 = num2 == num; bool flag4 = PrivateVoiceChannel.IsMemberOffline(num2); bool flag5 = false; if (num2 != oneOnOneRowActors[i]) { oneOnOneRowActors[i] = num2; oneOnOneRowNames[i] = ResolveModName(num2); flag5 = true; } if (flag3 != oneOnOneRowIsTarget[i]) { oneOnOneRowIsTarget[i] = flag3; flag5 = true; } if (flag4 != oneOnOneRowOffline[i]) { oneOnOneRowOffline[i] = flag4; flag5 = true; } if (flag5) { string text = ((!flag) ? Loc.T("1 on 1: {0}", oneOnOneRowNames[i]) : (flag3 ? Loc.T("Stop 1 on 1: {0}", "<color=#4dd07a>" + oneOnOneRowNames[i] + "</color>") : Loc.T("1 on 1: {0}", oneOnOneRowNames[i]))); if (flag4) { text = text + " <size=75%><color=#8a8a8a>" + Loc.T("(not on the channel)") + "</color></size>"; } ((TMP_Text)val.labelTMP).text = text; } } } private void RescanOneOnOneCandidates(float now) { int num = ((PhotonNetwork.LocalPlayer != null) ? PhotonNetwork.LocalPlayer.ActorNumber : (-1)); int num2 = ((PhotonNetwork.MasterClient != null) ? PhotonNetwork.MasterClient.ActorNumber : (-1)); bool flag = SemiFunc.IsMasterClient(); int num3 = 0; int num4 = 0; if (!flag && num2 > 0 && num2 != num) { oneOnOneCandidates[num3++] = num2; } int num5 = num3; if (flag) { int[] array = AuthorizedActors.Snapshot(); foreach (int num6 in array) { if (num6 != num) { if (num3 < oneOnOneCandidates.Length) { oneOnOneCandidates[num3++] = num6; } else { num4++; } } } } else { Room currentRoom = PhotonNetwork.CurrentRoom; if (currentRoom != null) { foreach (Player value in currentRoom.Players.Values) { if (value == null) { continue; } int actorNumber = value.ActorNumber; if (actorNumber != num && actorNumber != num2 && RoleResolver.ResolveByPlayer(value) == ModRole.Moderator) { if (num3 < oneOnOneCandidates.Length) { oneOnOneCandidates[num3++] = actorNumber; } else { num4++; } } } } } Array.Sort(oneOnOneCandidates, num5, num3 - num5); oneOnOneCount = num3; if (num4 > 0 && now >= nextOneOnOneOverflowWarnAt) { nextOneOnOneOverflowWarnAt = now + 30f; Plugin.Logger.LogWarning((object)$"[SharePermissions] Voice tab: {num4} moderator(s) beyond the {oneOnOneCandidates.Length} 1-on-1 rows are not shown"); } } private void HideOneOnOneRows() { SetVisibility(oneOnOneHintElem, visible: false); SetVisibility(oneOnOneAskHintElem, visible: false); for (int i = 0; i < oneOnOneRowElems.Length; i++) { SetVisibility(oneOnOneRowElems[i], visible: false); } } private void OnOneOnOneClicked(int slot) { int num = oneOnOneRowActors[slot]; if (num > 0) { if (SemiFunc.IsMasterClient()) { ModerationActions.ToggleOneOnOnePrivateVoice(num); } else { ModerationActions.RequestOneOnOne(num); } } } private static string ResolveModName(int actor) { return ModerationActions.ActorNickname(actor); } private void UpdateOffHint() { if (!((Object)(object)offHint == (Object)null) && offHintCache.Length == 0) { string text = (offHintCache = "<size=75%><color=#6f7d88>" + Loc.T("Off - the host opens the private channel.") + "</color></size>"); ((TMP_Text)offHint.labelTMP).text = text; MenuUiHelpers.SizeWrappedLabel(offHint, text, 250f); } } private void UpdateStatus() { if (!((Object)(object)privateStatus == (Object)null)) { float realtimeSinceStartup = Time.realtimeSinceStartup; PrivateVoiceClient instance = PrivateVoiceClient.Instance; bool flag = (Object)(object)instance != (Object)null && (Object)(object)instance.PrivateRecorder != (Object)null && instance.PrivateRecorder.TransmitEnabled; if (MicTee.Level > 0.005f) { privateTalking = true; privateTalkHoldUntil = realtimeSinceStartup + 0.18f; } else if (realtimeSinceStartup >= privateTalkHoldUntil) { privateTalking = false; } bool flag2 = privateTalking && flag && PrivateVoiceDriver.State == PrivateVoiceDriver.Health.Live; PrivateVoiceDriver.Health state = PrivateVoiceDriver.State; if (state != lastStatusState || flag != lastStatusTransmitting || flag2 != lastStatusShowTalkDot) { lastStatusState = state; lastStatusTransmitting = flag; lastStatusShowTalkDot = flag2; string text = state switch { PrivateVoiceDriver.Health.Live => flag ? ("<color=#4dd07a>" + Loc.T("live") + "</color>") : ("<color=#6f7d88>" + Loc.T("muted") + "</color>"), PrivateVoiceDriver.Health.Listening => "<color=#4dd07a>" + Loc.T("listening") + "</color>", PrivateVoiceDriver.Health.Provisioning => "<color=#e0b84d>" + Loc.T("connecting") + "</color>", PrivateVoiceDriver.Health.Failed => "<color=#ef5350>" + Loc.T("failed") + "</color>", _ => "<color=#6f7d88>" + Loc.T("off") + "</color>", }; string text2 = Loc.T("Private voice: {0}", text) + (flag2 ? " <color=#d9e8a8>●</color>" : string.Empty); ((TMP_Text)privateStatus.labelTMP).text = text2; MenuUiHelpers.SizeWrappedLabel(privateStatus, text2, 250f); } } } private static void OnPrivateVolumeChanged(int percent) { PrivateVoiceLocalPrefs.SetVolumePercent(percent); } private static void OnMicLiftChanged(int db) { PrivateVoiceLocalPrefs.SetMicLiftDb(db); } private REPOScrollViewElement? AddLabel(string richText) { //IL_0021: Unknown result type (might be due to invalid IL or missing references) //IL_0035: Expected O, but got Unknown REPOLabel made = null; page.AddElementToScrollView((ScrollViewBuilderDelegate)delegate(Transform sv) { //IL_0009: 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) REPOLabel val = MenuAPI.CreateREPOLabel(richText, sv, default(Vector2)); ((TMP_Text)val.labelTMP).richText = true; ((TMP_Text)val.labelTMP).fontSize = 14f; MenuUiHelpers.SizeWrappedLabel(val, richText, 250f); made = val; return ((REPOElement)val).rectTransform; }, 0f, 6f); return Elem((Component?)(object)made); } private REPOButton? AddActionButton(string caption, Action onClick, out REPOScrollViewElement? elem) { //IL_0028: Unknown result type (might be due to invalid IL or missing references) //IL_003c: Expected O, but got Unknown REPOButton made = null; page.AddElementToScrollView((ScrollViewBuilderDelegate)delegate(Transform sv) { //IL_0010: Unknown result type (might be due to invalid IL or missing references) //IL_0016: 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) made = MenuAPI.CreateREPOButton(caption, onClick, sv, default(Vector2)); ((TMP_Text)made.labelTMP).richText = true; ((TMP_Text)made.labelTMP).fontSize = 14f; MenuUiHelpers.PinButtonSize(made, new Vector2(250f, 24f)); return ((REPOElement)made).rectTransform; }, 0f, 2f); elem = Elem((Component?)(object)made); return made; } private static REPOScrollViewElement? Elem(Component? made) { if (!((Object)(object)made == (Object)null)) { return made.GetComponent<REPOScrollViewElement>(); } return null; } private static void SetVisibility(REPOScrollViewElement? elem, bool visible) { if ((Object)(object)elem != (Object)null && elem.visibility != visible) { elem.visibility = visible; } } } internal sealed class GhostRowDriver : MonoBehaviour { private static readonly Color TalkNameColor = new Color(0.6f, 0.6f, 0.4f); internal static readonly Color IdleNameColor = new Color(0.2f, 0.2f, 0.2f); private const float ColorLerpSpeed = 10f; private const float TalkThreshold = 0.005f; private const float HoldTime = 0.18f; private const float WobbleDegreesPerLoudness = 200f; private const float FocusPushAlong = 12.5f; private const float FocusPushPerp = 6f; private const float EyeRestX = 50f; private const float EyeRestY = 25f; private const float EyeNudge = 10f; private const float EyeLerpSpeed = 10f; private const float CursorFocusOffsetX = 18f; private const float CursorFocusOffsetY = 15f; private int actorNumber; private MenuPlayerListed? row; private TextMeshProUGUI? playerName; private MenuPlayerHead? head; private RectTransform? rowRect; private RectTransform? headRect; private RectTransform? headTransform; private RectTransform? eyesTransform; private bool facingRight = true; private int listSpotPrev = -1; private float talkUntil; internal void Initialize(int actor) { actorNumber = actor; } private void Awake() { row = ((Component)this).GetComponent<MenuPlayerListed>(); if ((Object)(object)row != (Object)null) { playerName = row.playerName; head = row.playerHead; rowRect = ((Component)row).GetComponent<RectTransform>(); if ((Object)(object)head != (Object)null) { headRect = ((Component)head).GetComponent<RectTransform>(); } } } private void Update() { float loud = ReadLoudness(); bool talking = UpdateTalkHold(loud); PublishTalkState(talking); if ((Object)(object)row != (Object)null) { ApplyFacing(row.listSpot); } ApplyWobble(loud); UpdateNameColor(talking); SyncEyeContact(); } private void SyncEyeContact() { try { SyncFocusPoint(); SyncCursorFocus(); SyncEyes(); } catch { } } private float ReadLoudness() { return VoiceChatPatch.AmplitudeFor(actorNumber); } private bool UpdateTalkHold(float loud) { if (loud > 0.005f) { talkUntil = Time.time + 0.18f; } return Time.time < talkUntil; } private void PublishTalkState(bool talking) { if (!((Object)(object)head == (Object)null)) { if (ShouldStampTalkStart(talking, head.isTalking)) { head.startedTalkingAtTime = Time.time; } head.isTalking = talking; } } private void ApplyWobble(float loud) { //IL_0038: Unknown result type (might be due to invalid IL or missing references) if (!((Object)(object)headTransform == (Object)null)) { ((Transform)headTransform).localEulerAngles = new Vector3(0f, 0f, facingRight ? (loud * 200f) : ((0f - loud) * 200f)); } } private void UpdateNameColor(bool talking) { //IL_001b: 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_0023: 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) if (!((Object)(object)playerName == (Object)null)) { ((Graphic)playerName).color = Color.Lerp(((Graphic)playerName).color, talking ? TalkNameColor : IdleNameColor, Time.deltaTime * 10f); } } private static bool ShouldStampTalkStart(bool talkingNow, bool wasTalking) { if (talkingNow) { return !wasTalking; } return false; } private static bool FacingIsRight(int listSpot) { return listSpot % 2 == 0; } private void ApplyFacing(int listSpot) { if (listSpot == listSpotPrev || (Object)(object)head == (Object)null) { return; } listSpotPrev = listSpot; facingRight = FacingIsRight(listSpot); Transform val = (facingRight ? head.headRight : head.headLeft); Transform val2 = (facingRight ? head.headLeft : head.headRight); if (!((Object)(object)val == (Object)null)) { if ((Object)(object)val2 != (Object)null) { ((Component)val2).gameObject.SetActive(false); } ((Component)val).gameObject.SetActive(true); headTransform = ((Component)val).GetComponent<RectTransform>(); Transform val3 = val.Find("Eyes"); eyesTransform = (((Object)(object)val3 != (Object)null) ? ((Component)val3).GetComponent<RectTransform>() : null); } } private void SyncFocusPoint() { //IL_0053: Unknown result type (might be due to invalid IL or missing references) //IL_005e: 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_006e: Unknown result type (might be due to invalid IL or missing references) //IL_0079: Unknown result type (might be due to invalid IL or missing references) //IL_0083: 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) //IL_008d: 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_00b4: Unknown result type (might be due to invalid IL or missing references) //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_00d2: Unknown result type (might be due to invalid IL or missing references) //IL_00d3: Unknown result type (might be due to invalid IL or missing references) //IL_00fb: Unknown result type (might be due to invalid IL or missing references) //IL_0100: Unknown result type (might be due to invalid IL or missing references) //IL_0105: Unknown result type (might be due to invalid IL or missing references) //IL_0111: Unknown result type (might be due to invalid IL or missing references) if (!((Object)(object)head == (Object)null) && !((Object)(object)head.focusPoint == (Object)null) && !((Object)(object)headTransform == (Object)null) && !((Object)(object)rowRect == (Object)null) && !((Object)(object)headRect == (Object)null)) { Vector3 val = ((Transform)rowRect).localPosition + ((Transform)headRect).localPosition + ((Transform)headTransform).localPosition * ((Transform)headRect).localScale.x; float z = ((Transform)headTransform).localEulerAngles.z; float num = (facingRight ? 12.5f : (-12.5f)); val += new Vector3(MenuPlayerHead.LengthDirX(num, z), MenuPlayerHead.LengthDirY(num, z), 0f); val += new Vector3(MenuPlayerHead.LengthDirX(6f, z + 90f), MenuPlayerHead.LengthDirY(6f, z + 90f), 0f); ((Transform)head.focusPoint).localPosition = val; } } private void SyncEyes() { //IL_00e1: Unknown result type (might be due to invalid IL or missing references) //IL_00f3: Unknown result type (might be due to invalid IL or missing references) //IL_00f4: Unknown result type (might be due to invalid IL or missing references) //IL_0100: Unknown result type (might be due to invalid IL or missing references) //IL_0105: Unknown result type (might be due to invalid IL or missing references) //IL_010a: Unknown result type (might be due to invalid IL or missing references) //IL_00ee: Unknown result type (might be due to invalid IL or missing references) //IL_0136: Unknown result type (might be due to invalid IL or missing references) //IL_013d: Unknown result type (might be due to invalid IL or missing references) //IL_0147: Unknown result type (might be due to invalid IL or missing references) //IL_014c: Unknown result type (might be due to invalid IL or missing references) //IL_0151: Unknown result type (might be due to invalid IL or missing references) //IL_015f: Unknown result type (might be due to invalid IL or missing references) //IL_0164: 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) if ((Object)(object)head == (Object)null || (Object)(object)eyesTransform == (Object)null || (Object)(object)head.focusPoint == (Object)null || (Object)(object)head.myFocusPoint == (Object)null) { return; } MenuPlayerHead val = null; float num = 0f; List<MenuPlayerHead> list = (((Object)(object)MenuManager.instance != (Object)null) ? MenuManager.instance.playerHeads : null); if (list != null) { for (int i = 0; i < list.Count; i++) { MenuPlayerHead val2 = list[i]; if (!((Object)(object)val2 == (Object)null) && !((Object)(object)val2 == (Object)(object)head) && val2.isTalking && val2.startedTalkingAtTime > num) { num = val2.startedTalkingAtTime; val = val2; } } } Vector3 val3 = (((Object)(object)val != (Object)null && (Object)(object)val.focusPoint != (Object)null) ? ((Transform)val.focusPoint).localPosition : ((Transform)head.myFocusPoint).localPosition); Vector3 val4 = val3 - ((Transform)head.focusPoint).localPosition; val4.z = 0f; Vector3 val5 = new Vector3(facingRight ? 50f : (-50f), 25f, 0f) + ((Vector3)(ref val4)).normalized * 10f; ((Transform)eyesTransform).localPosition = Vector3.Lerp(((Transform)eyesTransform).localPosition, val5, Time.deltaTime * 10f); } private void SyncCursorFocus() { //IL_0034: Unknown result type (might be due to invalid IL or missing references) //IL_003a: Invalid comparison between Unknown and I4 //IL_008f: Unknown result type (might be due to invalid IL or missing references) //IL_00a4: Unknown result type (might be due to invalid IL or missing references) //IL_00a9: Unknown result type (might be due to invalid IL or missing references) //IL_00ae: Unknown result type (might be due to invalid IL or missing references) //IL_00ba: Unknown result type (might be due to invalid IL or missing references) //IL_00c6: Unknown result type (might be due to invalid IL or missing references) //IL_00d7: Unknown result type (might be due to invalid IL or missing references) if (!((Object)(object)head == (Object)null) && !((Object)(object)head.myFocusPoint == (Object)null) && !((Object)(object)MenuManager.instance == (Object)null) && (int)MenuManager.instance.currentMenuPageIndex == 8 && !((Object)(object)MenuCursor.instance == (Object)null) && !((Object)(object)headRect == (Object)null) && !((Object)(object)((Transform)headRect).parent == (Object)null) && !((Object)(object)((Transform)headRect).parent.parent == (Object)null)) { Vector3 val = ((Component)MenuCursor.instance).transform.localPosition - ((Transform)headRect).parent.parent.localPosition; ((Transform)head.myFocusPoint).localPosition = new Vector3(val.x + 18f, val.y + 15f, 0f); } } } internal static class HangUpKey { internal static void Tick() { //IL_0005: Unknown result type (might be due to invalid IL or missing references) //IL_000a: Unknown result type (might be due to invalid IL or missing references) //IL_000b: 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) KeyCode value = ModConfig.Voice1on1HangUpKey.Value; if ((int)value != 0 && Input.GetKeyDown(value) && !SettingsPanelController.AnyKeyRowListening && !InvitePrompt.IsTyping() && ModerationActions.LocalMayHangUp) { ModerationActions.HangUp(); } } } internal static class HeadArt { internal enum Part { Dome, Eyes, Jaw, Topper } internal const string TextureNamePrefix = "SP Head "; private const string ResourcePrefix = "SharePermissions.Heads."; private const string ResourceSuffix = ".png"; private const string BodyTopperSuffix = "_topper_body"; private const string TopperSuffix = "_topper"; private const string FacingSuffix = "_facing"; private static Dictionary<string, string>? index; private static readonly HashSet<string> BodyToppers = new HashSet<string>(StringComparer.Ordinal); private static readonly HashSet<string> FacingToppers = new HashSet<string>(StringComparer.Ordinal); private static readonly Dictionary<string, Texture2D> Loaded = new Dictionary<string, Texture2D>(StringComparer.Ordinal); private static readonly Dictionary<string, float> TopperInk = new Dictionary<string, float>(StringComparer.Ordinal); private static readonly HashSet<string> Failed = new HashSet<string>(StringComparer.Ordinal); private static readonly Texture?[] Stock = (Texture?[])(object)new Texture[4]; internal static void Init() { int count = Index().Count; if (count == 0) { Plugin.Logger.LogWarning((object)"Lobby heads: no art is embedded - every head stays stock"); } else { Plugin.Logger.LogInfo((object)$"Lobby heads: {count} art parts embedded"); } } internal static Texture2D? Get(string id, Part part) { if (!(id == "default")) { return Load(id, part); } return null; } internal static Texture2D? PreviewPart(string id, Part part) { object obj = Get(id, part); if (obj == null) { if (part != Part.Topper) { return Load("default", part); } obj = null; } return (Texture2D?)obj; } internal static bool TopperIsBody(string id) { Index(); return BodyToppers.Contains(id); } internal static bool TopperFacesHead(string id) { Index(); return FacingToppers.Contains(id); } internal static float TopperInkTop(string id) { if (!TopperInk.TryGetValue(id, out var value)) { return 1f; } return value; } internal static bool IsModTexture(Texture? texture) { if ((Object)(object)texture != (Object)null) { return ((Object)texture).name.StartsWith("SP Head ", StringComparison.Ordinal); } return false; } internal static void RememberStock(Part part, Texture? texture) { if ((Object)(object)Stock[(int)part] == (Object)null && (Object)(object)texture != (Object)null && !IsModTexture(texture)) { Stock[(int)part] = texture; } } internal static Texture? StockTexture(Part part) { return Stock[(int)part]; } private static Texture2D? Load(string id, Part part) { string text = id + "_" + PartName(part); if (Loaded.TryGetValue(text, out Texture2D value) && (Object)(object)value != (Object)null) { return value; } if (Failed.Contains(text) || !Index().TryGetValue(text, out string value2)) { return null; } bool flag = part == Part.Topper; Texture2D val = Decode(value2, flag ? id : null); if ((Object)(object)val == (Object)null) { Failed.Add(text); Plugin.Logger.LogError((object)("Lobby heads: could not decode " + value2 + " - that part stays stock")); return null; } Loaded[text] = val; return val; } private static Texture2D? Decode(string resource, string? measureInkFor) { //IL_0031: Unknown result type (might be due to invalid IL or missing references) //IL_0037: Expected O, but got Unknown try { using Stream stream = typeof(HeadArt).Assembly.GetManifestResourceStream(resource); if (stream == null) { return null; } using MemoryStream memoryStream = new MemoryStream(); stream.CopyTo(memoryStream); Texture2D val = new Texture2D(2, 2, (TextureFormat)4, true); if (!ImageConversion.LoadImage(val, memoryStream.ToArray(), measureInkFor == null)) { Object.Destroy((Object)(object)val); return null; } if (measureInkFor != null) { TopperInk[measureInkFor] = InkTop(val); val.Apply(true, true); } ((Object)val).name = "SP Head " + resource.Substring("SharePermissions.Heads.".Length); ((Texture)val).wrapMode = (TextureWrapMode)1; ((Texture)val).filterMode = (FilterMode)2; ((Object)val).hideFlags = (HideFlags)61; return val; } catch (Exception ex) { Plugin.Logger.LogError((object)("Lobby heads: decoding " + resource + " threw: " + ex.Message)); return null; } } private static float InkTop(Texture2D texture) { Color32[] pixels = texture.GetPixels32(); int width = ((Texture)texture).width; int height = ((Texture)texture).height; for (int num = height - 1; num >= 0; num--) { int num2 = num * width; for (int i = 0; i < width; i++) { if (pixels[num2 + i].a >= 32) { return ((float)num + 1f) / (float)height; } } } return 0f; } private static Dictionary<string, string> Index() { if (index != null) { return index; } Dictionary<string, string> dictionary = new Dictionary<string, string>(StringComparer.Ordinal); string[] manifestResourceNames = typeof(HeadArt).Assembly.GetManifestResourceNames(); foreach (string text in manifestResourceNames) { if (text.StartsWith("SharePermissions.Heads.", StringComparison.Ordinal) && text.EndsWith(".png", StringComparison.Ordinal)) { string text2 = text.Substring("SharePermissions.Heads.".Length, text.Length - "SharePermissions.Heads.".Length - ".png".Length); bool flag = text2.EndsWith("_facing", StringComparison.Ordinal); if (flag) { text2 = text2.Substring(0, text2.Length - "_facing".Length); } string text3 = null; if (text2.EndsWith("_topper_body", StringComparison.Ordinal)) { text3 = text2.Substring(0, text2.Length - "_topper_body".Length); BodyToppers.Add(text3); text2 = text3 + "_" + PartName(Part.Topper); } else if (text2.EndsWith("_topper", StringComparison.Ordinal)) { text3 = text2.Substring(0, text2.Length - "_topper".Length); } if (flag && text3 != null) { FacingToppers.Add(text3); } dictionary[text2] = text; } } index = dictionary; return index; } private static string PartName(Part part) { return part switch { Part.Dome => "dome", Part.Eyes => "eyes", Part.Jaw => "jaw", _ => "topper", }; } } internal sealed class HeadMarkerComponent : MonoBehaviour { private const float AppearSeconds = 0.35f; private static readonly int EmissionColorId = Shader.PropertyToID("_EmissionColor"); private float spinDegreesPerSecond; private float pulsePeriodSeconds; private float bobAmplitude; private float bobPeriodSeconds; private Material? pulseMaterial; private Color baseColor; private bool emissionAvailable; private Vector3 basePosition; private float appearElapsed; private const float GlintSeconds = 0.25f; private Material? glintMaterial; private float glintPeriodSeconds; private float nextGlintAt; private float glintStartedAt = float.NegativeInfinity; private bool glintAvailable; private bool glintWasActive; private Color glintBaseEmission; private Material?[]? ownedMaterials; private bool materialsDestroyed; internal void Initialize(Material? material, Color color, float spinDps, float pulsePeriod, float bobAmp, float bobPeriod, Material? glintMat = null, float glintPeriod = 0f) { //IL_0008: Unknown result type (might be due to invalid IL or missing references) //IL_0009: Unknown result type (might be due to invalid IL or missing references) //IL_0034: 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_00ab: Unknown result type (might be due to invalid IL or missing references) //IL_00b0: Unknown result type (might be due to invalid IL or missing references) pulseMaterial = material; baseColor = color; spinDegreesPerSecond = spinDps; pulsePeriodSeconds = pulsePeriod; bobAmplitude = bobAmp; bobPeriodSeconds = bobPeriod; basePosition = ((Component)this).transform.localPosition; if ((Object)(object)material != (Object)null && material.HasProperty("_EmissionColor")) { material.EnableKeyword("_EMISSION"); emissionAvailable = true; } glintMaterial = glintMat; glintPeriodSeconds = glintPeriod; if ((Object)(object)glintMat != (Object)null && glintPeriod > 0f && glintMat.HasProperty("_EmissionColor")) { glintMat.EnableKeyword("_EMISSION"); glintBaseEmission = glintMat.GetColor("_EmissionColor"); glintAvailable = true; nextGlintAt = Time.time + glintPeriod * Random.Range(0.7f, 1.3f); } } internal void TakeOwnership(params Material?[] mats) { ownedMaterials = mats; } internal void DestroyOwnedMaterials() { if (materialsDestroyed || ownedMaterials == null) { return; } materialsDestroyed = true; Material[] array = ownedMaterials; foreach (Material val in array) { if ((Object)(object)val != (Object)null) { Object.Destroy((Object)(object)val); } } } private void OnDestroy() { DestroyOwnedMaterials(); } private void OnEnable() { //IL_0011: Unknown result type (might be due to invalid IL or missing references) appearElapsed = 0f; ((Component)this).transform.localScale = Vector3.zero; if (glintAvailable) { nextGlintAt = Time.time + glintPeriodSeconds * Random.Range(0.7f, 1.3f); } } private void Update() { //IL_003d: Unknown result type (might be due to invalid IL or missing references) //IL_005d: 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_00d9: Unknown result type (might be due to invalid IL or missing references) //IL_00de: 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)