using System; using System.Collections.Generic; using System.Diagnostics; using System.IO; using System.Reflection; using System.Runtime.CompilerServices; using System.Runtime.Versioning; using BepInEx; using BepInEx.Configuration; using BepInEx.Logging; using Concentus; using Concentus.Enums; using Microsoft.CodeAnalysis; using NAudio.Wave; using UnityEngine; [assembly: CompilationRelaxations(8)] [assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)] [assembly: Debuggable(DebuggableAttribute.DebuggingModes.Default | DebuggableAttribute.DebuggingModes.DisableOptimizations | DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints | DebuggableAttribute.DebuggingModes.EnableEditAndContinue)] [assembly: TargetFramework(".NETFramework,Version=v4.7.2", FrameworkDisplayName = "")] [assembly: AssemblyCompany("ProximityVoice")] [assembly: AssemblyConfiguration("Debug")] [assembly: AssemblyFileVersion("1.0.0.0")] [assembly: AssemblyInformationalVersion("1.0.0")] [assembly: AssemblyProduct("ProximityVoice")] [assembly: AssemblyTitle("ProximityVoice")] [assembly: AssemblyVersion("1.0.0.0")] [module: RefSafetyRules(11)] namespace Microsoft.CodeAnalysis { [CompilerGenerated] [Microsoft.CodeAnalysis.Embedded] internal sealed class EmbeddedAttribute : Attribute { } } namespace System.Runtime.CompilerServices { [CompilerGenerated] [Microsoft.CodeAnalysis.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 ProximityVoice { public interface IMicCapture { bool Running { get; } void Start(); void Stop(); void PollFrames(List frames); } public static class MicCapture { public static IMicCapture Create() { try { if (NAudioMicCapture.IsAvailable()) { Plugin.Log.LogInfo((object)"Mic capture backend: NAudio (WaveIn)."); return new NAudioMicCapture(); } Plugin.Log.LogWarning((object)"NAudio reports no capture devices; using Unity Microphone."); } catch (Exception ex) { Plugin.Log.LogWarning((object)("NAudio capture unavailable (" + ex.GetType().Name + "); using Unity Microphone.")); } return new UnityMicCapture(); } } public sealed class NAudioMicCapture : IMicCapture { private const int BytesPerSample = 2; private static readonly int BytesPerFrame = 1920; private const int MaxQueuedFrames = 50; private WaveInEvent _wave; private readonly object _lock = new object(); private readonly Queue _frames = new Queue(); private byte[] _accum = new byte[BytesPerFrame * 4]; private int _accumLen; public bool Running { get; private set; } public static bool IsAvailable() { try { return WaveInEvent.DeviceCount > 0; } catch { return false; } } public void Start() { //IL_0011: 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_002c: Unknown result type (might be due to invalid IL or missing references) //IL_0035: Unknown result type (might be due to invalid IL or missing references) //IL_003f: Expected O, but got Unknown //IL_0040: Unknown result type (might be due to invalid IL or missing references) //IL_004e: Expected O, but got Unknown if (!Running) { _wave = new WaveInEvent { DeviceNumber = ResolveDevice(VoiceConfig.MicDevice.Value), WaveFormat = new WaveFormat(48000, 16, 1), BufferMilliseconds = 40 }; _wave.DataAvailable += OnData; _accumLen = 0; lock (_lock) { _frames.Clear(); } _wave.StartRecording(); Running = true; } } public void Stop() { if (!Running) { return; } Running = false; try { _wave.StopRecording(); } catch { } if (_wave != null) { _wave.DataAvailable -= OnData; _wave.Dispose(); _wave = null; } lock (_lock) { _frames.Clear(); } } public void PollFrames(List frames) { lock (_lock) { while (_frames.Count > 0) { frames.Add(_frames.Dequeue()); } } } private void OnData(object sender, WaveInEventArgs e) { EnsureCapacity(_accumLen + e.BytesRecorded); Buffer.BlockCopy(e.Buffer, 0, _accum, _accumLen, e.BytesRecorded); _accumLen += e.BytesRecorded; int num = 0; while (_accumLen - num >= BytesPerFrame) { short[] array = new short[960]; for (int i = 0; i < 960; i++) { int num2 = num + i * 2; array[i] = (short)(_accum[num2] | (_accum[num2 + 1] << 8)); } num += BytesPerFrame; lock (_lock) { if (_frames.Count >= 50) { _frames.Dequeue(); } _frames.Enqueue(array); } } int num3 = _accumLen - num; if (num3 > 0 && num > 0) { Buffer.BlockCopy(_accum, num, _accum, 0, num3); } _accumLen = num3; } private void EnsureCapacity(int needed) { if (_accum.Length < needed) { int num = Math.Max(needed, _accum.Length * 2); byte[] array = new byte[num]; Buffer.BlockCopy(_accum, 0, array, 0, _accumLen); _accum = array; } } private static int ResolveDevice(string name) { //IL_0015: Unknown result type (might be due to invalid IL or missing references) //IL_001a: Unknown result type (might be due to invalid IL or missing references) if (string.IsNullOrEmpty(name)) { return -1; } for (int i = 0; i < WaveInEvent.DeviceCount; i++) { WaveInCapabilities capabilities = WaveInEvent.GetCapabilities(i); if (((WaveInCapabilities)(ref capabilities)).ProductName == name) { return i; } } Plugin.Log.LogWarning((object)("Mic device \"" + name + "\" not found; using system default.")); return -1; } } public sealed class SpeakerVoice { private const int RingSeconds = 2; private readonly int _ringSize = 96000; private readonly GameObject _go; private readonly AudioSource _source; private readonly VoiceDecoder _decoder = new VoiceDecoder(); private readonly float[] _ring; private readonly object _lock = new object(); private int _writeIdx; private int _readIdx; private int _count; public float LastActiveTime; public SpeakerVoice(long speakerId) { //IL_004c: Unknown result type (might be due to invalid IL or missing references) //IL_0056: Expected O, but got Unknown //IL_0120: Unknown result type (might be due to invalid IL or missing references) //IL_012a: Expected O, but got Unknown _ring = new float[_ringSize]; _go = new GameObject("PV_Speaker_" + speakerId); Object.DontDestroyOnLoad((Object)(object)_go); _source = _go.AddComponent(); _source.spatialBlend = 1f; _source.rolloffMode = (AudioRolloffMode)1; _source.minDistance = VoiceConfig.FullVolumeRange.Value; _source.maxDistance = VoiceConfig.HearingRange.Value; _source.bypassReverbZones = true; _source.bypassListenerEffects = true; _source.dopplerLevel = 0f; _source.loop = true; _source.clip = AudioClip.Create("PV_Stream_" + speakerId, _ringSize, 1, 48000, true, new PCMReaderCallback(OnPcmRead)); _source.Play(); } public void SetPosition(Vector3 pos) { //IL_000b: Unknown result type (might be due to invalid IL or missing references) _go.transform.position = pos; } public void PushPacket(byte[] packet) { short[] pcmOut; int num = _decoder.Decode(packet, out pcmOut); if (num <= 0) { return; } float value = VoiceConfig.Volume.Value; lock (_lock) { for (int i = 0; i < num; i++) { if (_count >= _ringSize) { break; } _ring[_writeIdx] = (float)pcmOut[i] / 32767f * value; _writeIdx = (_writeIdx + 1) % _ringSize; _count++; } } LastActiveTime = Time.realtimeSinceStartup; } private void OnPcmRead(float[] data) { lock (_lock) { for (int i = 0; i < data.Length; i++) { if (_count > 0) { data[i] = _ring[_readIdx]; _readIdx = (_readIdx + 1) % _ringSize; _count--; } else { data[i] = 0f; } } } } public void Destroy() { _decoder.Dispose(); if ((Object)(object)_go != (Object)null) { Object.Destroy((Object)(object)_go); } } } public sealed class UnityMicCapture : IMicCapture { private const int ClipSeconds = 1; private AudioClip _clip; private string _device; private int _readPos; private readonly float[] _scratch = new float[960]; private readonly List _accum = new List(1920); public bool Running => (Object)(object)_clip != (Object)null; public void Start() { if (!Running) { _device = (string.IsNullOrEmpty(VoiceConfig.MicDevice.Value) ? null : VoiceConfig.MicDevice.Value); if (Microphone.devices == null || Microphone.devices.Length == 0) { Plugin.Log.LogWarning((object)"No microphone devices found; capture disabled."); return; } _clip = Microphone.Start(_device, true, 1, 48000); _readPos = 0; _accum.Clear(); } } public void Stop() { if (Running) { Microphone.End(_device); _clip = null; _accum.Clear(); } } public void PollFrames(List frames) { if (!Running) { return; } int position = Microphone.GetPosition(_device); if (position < 0) { return; } int samples = _clip.samples; int num = position - _readPos; if (num < 0) { num += samples; } if (num <= 0) { return; } int num2 = Mathf.Min(num, samples - _readPos); AppendFromClip(_readPos, num2); if (num > num2) { AppendFromClip(0, num - num2); } _readPos = position; while (_accum.Count >= 960) { short[] array = new short[960]; for (int i = 0; i < 960; i++) { array[i] = FloatToPcm(_accum[i]); } _accum.RemoveRange(0, 960); frames.Add(array); } } private void AppendFromClip(int offset, int count) { int num = count; int num2 = offset; while (num > 0) { int num3 = Mathf.Min(num, _scratch.Length); _clip.GetData(_scratch, num2); for (int i = 0; i < num3; i++) { _accum.Add(_scratch[i]); } num2 += num3; if (num2 >= _clip.samples) { num2 -= _clip.samples; } num -= num3; } } private static short FloatToPcm(float f) { f = Mathf.Clamp(f, -1f, 1f); return (short)Mathf.RoundToInt(f * 32767f); } } public static class VoiceHud { private const int W = 26; private const int H = 18; private const float DbFloor = -60f; private static Texture2D _body; private static Texture2D[] _waves; private static GUIStyle _label; private static readonly float[] WaveRadius = new float[3] { 11f, 14f, 17f }; private static readonly float[] WaveHalf = new float[3] { 2.5f, 4.5f, 7f }; private static readonly float[] WaveThreshold = new float[3] { 0.06f, 0.33f, 0.62f }; public static void Draw(float level, bool gateOpen) { //IL_0065: 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_0080: Unknown result type (might be due to invalid IL or missing references) //IL_008b: Unknown result type (might be due to invalid IL or missing references) //IL_010d: Unknown result type (might be due to invalid IL or missing references) //IL_00da: Unknown result type (might be due to invalid IL or missing references) //IL_00e5: Unknown result type (might be due to invalid IL or missing references) EnsureTextures(); float num = (gateOpen ? Mathf.Clamp01(level * 4f) : 0f); float value = VoiceConfig.HudScale.Value; float num2 = 26f * value; float num3 = 18f * value; float value2 = VoiceConfig.HudX.Value; float num4 = (float)Screen.height - VoiceConfig.HudBottom.Value; Rect val = default(Rect); ((Rect)(ref val))..ctor(value2, num4, num2, num3); Color color = GUI.color; GUI.color = new Color(1f, 1f, 1f, 0.95f); GUI.DrawTexture(val, (Texture)(object)_body); for (int i = 0; i < _waves.Length; i++) { float num5 = Mathf.Clamp01((num - WaveThreshold[i]) / 0.15f); if (!(num5 <= 0f)) { GUI.color = new Color(1f, 1f, 1f, num5); GUI.DrawTexture(val, (Texture)(object)_waves[i]); } } GUI.color = color; } public static void DrawCalibration(float db, float thresholdDb, bool gateOpen) { //IL_0007: Unknown result type (might be due to invalid IL or missing references) //IL_000c: 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_0061: 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_0094: Unknown result type (might be due to invalid IL or missing references) //IL_00e0: 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_0149: Unknown result type (might be due to invalid IL or missing references) //IL_012e: Unknown result type (might be due to invalid IL or missing references) //IL_015f: Unknown result type (might be due to invalid IL or missing references) //IL_0197: Unknown result type (might be due to invalid IL or missing references) //IL_01c5: Unknown result type (might be due to invalid IL or missing references) //IL_01d2: Unknown result type (might be due to invalid IL or missing references) //IL_01e2: Unknown result type (might be due to invalid IL or missing references) //IL_01e7: Unknown result type (might be due to invalid IL or missing references) //IL_0213: Unknown result type (might be due to invalid IL or missing references) //IL_023b: Unknown result type (might be due to invalid IL or missing references) EnsureLabel(); Color color = GUI.color; float num = 380f; float num2 = 104f; float num3 = ((float)Screen.width - num) * 0.5f; float num4 = (float)Screen.height * 0.16f; Texture2D whiteTexture = Texture2D.whiteTexture; GUI.color = new Color(0f, 0f, 0f, 0.72f); GUI.DrawTexture(new Rect(num3, num4, num, num2), (Texture)(object)whiteTexture); GUI.color = Color.white; GUI.Label(new Rect(num3 + 14f, num4 + 8f, num - 28f, 22f), "Mic Calibration", _label); float num5 = num3 + 14f; float num6 = num4 + 40f; float num7 = num - 28f; float num8 = 18f; GUI.color = new Color(1f, 1f, 1f, 0.14f); GUI.DrawTexture(new Rect(num5, num6, num7, num8), (Texture)(object)whiteTexture); float num9 = Mathf.Clamp01(Mathf.InverseLerp(-60f, 0f, db)); GUI.color = (gateOpen ? new Color(0.3f, 0.95f, 0.4f, 0.95f) : new Color(0.6f, 0.6f, 0.6f, 0.85f)); GUI.DrawTexture(new Rect(num5, num6, num7 * num9, num8), (Texture)(object)whiteTexture); float num10 = Mathf.Clamp01(Mathf.InverseLerp(-60f, 0f, thresholdDb)); GUI.color = new Color(1f, 0.32f, 0.32f, 1f); GUI.DrawTexture(new Rect(num5 + num7 * num10 - 1f, num6 - 4f, 2f, num8 + 8f), (Texture)(object)whiteTexture); GUI.color = Color.white; KeyCode value = VoiceConfig.CalibrateKey.Value; string arg = ((object)(KeyCode)(ref value)).ToString(); GUI.Label(new Rect(num3 + 14f, num4 + 64f, num - 28f, 22f), $"Level {db:0.0} dB · Gate {thresholdDb:0} dB · [ ] / scroll · {arg} exit", _label); GUI.color = color; } public static void DrawGainAdjust(float db, float micGain, bool gateOpen) { //IL_0007: Unknown result type (might be due to invalid IL or missing references) //IL_000c: 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_0061: 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_0094: Unknown result type (might be due to invalid IL or missing references) //IL_00e0: 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_012b: Unknown result type (might be due to invalid IL or missing references) //IL_0141: Unknown result type (might be due to invalid IL or missing references) //IL_0197: Unknown result type (might be due to invalid IL or missing references) //IL_017c: Unknown result type (might be due to invalid IL or missing references) //IL_01c5: Unknown result type (might be due to invalid IL or missing references) //IL_01d2: Unknown result type (might be due to invalid IL or missing references) //IL_01e2: Unknown result type (might be due to invalid IL or missing references) //IL_01e7: Unknown result type (might be due to invalid IL or missing references) //IL_0213: Unknown result type (might be due to invalid IL or missing references) //IL_023b: Unknown result type (might be due to invalid IL or missing references) EnsureLabel(); Color color = GUI.color; float num = 380f; float num2 = 104f; float num3 = ((float)Screen.width - num) * 0.5f; float num4 = (float)Screen.height * 0.16f; Texture2D whiteTexture = Texture2D.whiteTexture; GUI.color = new Color(0f, 0f, 0f, 0.72f); GUI.DrawTexture(new Rect(num3, num4, num, num2), (Texture)(object)whiteTexture); GUI.color = Color.white; GUI.Label(new Rect(num3 + 14f, num4 + 8f, num - 28f, 22f), "Mic Gain", _label); float num5 = num3 + 14f; float num6 = num4 + 40f; float num7 = num - 28f; float num8 = 18f; GUI.color = new Color(1f, 1f, 1f, 0.14f); GUI.DrawTexture(new Rect(num5, num6, num7, num8), (Texture)(object)whiteTexture); float num9 = Mathf.Clamp01(Mathf.InverseLerp(1f, 12f, micGain)); GUI.color = new Color(0.4f, 0.7f, 1f, 0.95f); GUI.DrawTexture(new Rect(num5, num6, num7 * num9, num8), (Texture)(object)whiteTexture); float num10 = Mathf.Clamp01(Mathf.InverseLerp(-60f, 0f, db)); GUI.color = (gateOpen ? new Color(0.3f, 0.95f, 0.4f, 0.95f) : new Color(0.6f, 0.6f, 0.6f, 0.85f)); GUI.DrawTexture(new Rect(num5 + num7 * num10 - 1f, num6 - 4f, 2f, num8 + 8f), (Texture)(object)whiteTexture); GUI.color = Color.white; KeyCode value = VoiceConfig.MicGainKey.Value; string arg = ((object)(KeyCode)(ref value)).ToString(); GUI.Label(new Rect(num3 + 14f, num4 + 64f, num - 28f, 22f), $"Gain {micGain:0.0}x · Level {db:0.0} dB · scroll / Up Down · {arg} exit", _label); GUI.color = color; } public static void DrawRebindPrompt() { //IL_0007: Unknown result type (might be due to invalid IL or missing references) //IL_000c: 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_0061: Unknown result type (might be due to invalid IL or missing references) //IL_0073: 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_0082: Expected O, but got Unknown //IL_0089: Unknown result type (might be due to invalid IL or missing references) //IL_00a3: Unknown result type (might be due to invalid IL or missing references) //IL_00b7: Unknown result type (might be due to invalid IL or missing references) //IL_00bc: Unknown result type (might be due to invalid IL or missing references) //IL_00c5: Unknown result type (might be due to invalid IL or missing references) //IL_00cf: Expected O, but got Unknown //IL_00ea: Unknown result type (might be due to invalid IL or missing references) //IL_0104: Unknown result type (might be due to invalid IL or missing references) //IL_0116: Unknown result type (might be due to invalid IL or missing references) EnsureLabel(); Color color = GUI.color; float num = 460f; float num2 = 74f; float num3 = ((float)Screen.width - num) * 0.5f; float num4 = (float)Screen.height * 0.16f; Texture2D whiteTexture = Texture2D.whiteTexture; GUI.color = new Color(0f, 0f, 0f, 0.78f); GUI.DrawTexture(new Rect(num3, num4, num, num2), (Texture)(object)whiteTexture); GUIStyle val = new GUIStyle(_label) { alignment = (TextAnchor)4 }; val.normal.textColor = Color.white; GUI.Label(new Rect(num3, num4 + 12f, num, 24f), "Press a key or mouse button for Push-to-Talk", val); GUIStyle val2 = new GUIStyle(val) { fontSize = 12, fontStyle = (FontStyle)0 }; val2.normal.textColor = new Color(1f, 1f, 1f, 0.7f); GUI.Label(new Rect(num3, num4 + 42f, num, 20f), "Esc to cancel", val2); GUI.color = color; } private static void EnsureLabel() { //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_0027: Unknown result type (might be due to invalid IL or missing references) //IL_0034: Expected O, but got Unknown //IL_003e: Unknown result type (might be due to invalid IL or missing references) if (_label == null) { _label = new GUIStyle(GUI.skin.label) { fontSize = 14, fontStyle = (FontStyle)1 }; _label.normal.textColor = Color.white; } } private static void EnsureTextures() { if ((Object)(object)_body != (Object)null) { return; } _body = Build((int x, int y) => IsBody(x, y)); _waves = (Texture2D[])(object)new Texture2D[WaveRadius.Length]; for (int i = 0; i < _waves.Length; i++) { int wave = i; _waves[i] = Build((int x, int y) => IsWave(x, y, wave)); } } private static bool IsBody(int x, int y) { if (x >= 2 && x <= 4 && Mathf.Abs((float)y - 8.5f) <= 3f) { return true; } if (x >= 4 && x <= 9) { float num = 2.5f + (float)(x - 4) * 1.1f; if (Mathf.Abs((float)y - 8.5f) <= num) { return true; } } return false; } private static bool IsWave(int x, int y, int k) { if (x < 10) { return false; } float num = (float)x - 2f; float num2 = (float)y - 8.5f; if (num <= 0f || Mathf.Abs(num2) > WaveHalf[k]) { return false; } float num3 = Mathf.Sqrt(num * num + num2 * num2); return Mathf.Abs(num3 - WaveRadius[k]) <= 0.9f; } private static Texture2D Build(Func on) { //IL_0007: Unknown result type (might be due to invalid IL or missing references) //IL_000c: Unknown result type (might be due to invalid IL or missing references) //IL_0015: Expected O, but got Unknown //IL_005a: Unknown result type (might be due to invalid IL or missing references) //IL_0057: Unknown result type (might be due to invalid IL or missing references) //IL_005f: Unknown result type (might be due to invalid IL or missing references) Texture2D val = new Texture2D(26, 18, (TextureFormat)5, false) { filterMode = (FilterMode)0 }; Color[] array = (Color[])(object)new Color[468]; Color val2 = default(Color); ((Color)(ref val2))..ctor(1f, 1f, 1f, 0f); for (int i = 0; i < 18; i++) { for (int j = 0; j < 26; j++) { array[i * 26 + j] = (on(j, i) ? Color.white : val2); } } val.SetPixels(array); val.Apply(); return val; } } public sealed class VoiceEncoder : IDisposable { private readonly IOpusEncoder _enc; private readonly byte[] _out = new byte[4000]; public VoiceEncoder() { _enc = OpusCodecFactory.CreateEncoder(48000, 1, (OpusApplication)2048, (TextWriter)null); _enc.Bitrate = 24000; } public byte[] Encode(short[] pcmFrame) { try { int num = _enc.Encode((ReadOnlySpan)pcmFrame, 960, (Span)_out, _out.Length); if (num <= 0) { return null; } byte[] array = new byte[num]; Buffer.BlockCopy(_out, 0, array, 0, num); return array; } catch { return null; } } public void Dispose() { ((IDisposable)_enc)?.Dispose(); } } public sealed class VoiceDecoder : IDisposable { private readonly IOpusDecoder _dec; private readonly short[] _pcm = new short[960]; public VoiceDecoder() { _dec = OpusCodecFactory.CreateDecoder(48000, 1, (TextWriter)null); } public int Decode(byte[] packet, out short[] pcmOut) { pcmOut = _pcm; try { return _dec.Decode((ReadOnlySpan)packet, (Span)_pcm, 960, false); } catch { return 0; } } public void Dispose() { ((IDisposable)_dec)?.Dispose(); } } public enum MicMode { Toggle, PushToTalk } public static class VoiceConfig { public const int SampleRate = 48000; public const int Channels = 1; public const int FrameMs = 20; public const int FrameSamples = 960; public const int Bitrate = 24000; public static ConfigEntry Mode; public static ConfigEntry PttReleaseTail; public static ConfigEntry PushToTalkKey; public static ConfigEntry CalibrateKey; public static ConfigEntry MicGainKey; public static ConfigEntry RebindKey; public static ConfigEntry FullVolumeRange; public static ConfigEntry HearingRange; public static ConfigEntry Volume; public static ConfigEntry MicGain; public static ConfigEntry MicDevice; public static ConfigEntry NoiseGateDb; public static ConfigEntry HudX; public static ConfigEntry HudBottom; public static ConfigEntry HudScale; public static ConfigEntry SelfTest; public static void Init(ConfigFile cfg) { Mode = cfg.Bind("General", "MicMode", MicMode.PushToTalk, "Toggle = press the talk key to turn the mic on/off. PushToTalk = hold the talk key to talk."); PttReleaseTail = cfg.Bind("General", "PushToTalkReleaseTail", 0.5f, "Push-to-talk only: keep transmitting this many seconds after releasing the key, so the end of your sentence isn't cut off by capture latency."); PushToTalkKey = cfg.Bind("General", "PushToTalkKey", (KeyCode)117, "Talk key (used by both modes). Mouse buttons work too: Mouse3/Mouse4 are the side buttons, Mouse2 = middle. Any Unity KeyCode is accepted."); CalibrateKey = cfg.Bind("General", "CalibrateKey", (KeyCode)287, "Toggles the in-game mic calibration overlay (live dB + adjustable noise-gate threshold)."); MicGainKey = cfg.Bind("General", "MicGainKey", (KeyCode)285, "Toggles the mic gain overlay. While open, scroll the mouse wheel (or Up/Down arrows) to raise/lower how loud your own microphone is sent to others."); RebindKey = cfg.Bind("General", "RebindKey", (KeyCode)288, "Press this, then press any key or mouse button to set it as the talk key (Esc cancels)."); FullVolumeRange = cfg.Bind("General", "FullVolumeRange", 20f, "Distance (metres) within which a speaker is heard at full volume, with no falloff. Beyond this, volume fades linearly to silence at HearingRange."); HearingRange = cfg.Bind("General", "HearingRange", 80f, "Max distance (metres) at which other players can hear you. Voice plays at full volume within FullVolumeRange, then fades linearly to silence at this distance. Used for server-side culling too."); Volume = cfg.Bind("Audio", "Volume", 1f, "Playback volume multiplier for incoming voices."); MicGain = cfg.Bind("Audio", "MicGain", 4f, "Amplification applied to your OWN microphone before sending (1 = no change). Raise this if others can barely hear you. Adjust live in the calibration overlay (CalibrateKey) with the Up/Down arrow keys."); MicDevice = cfg.Bind("Audio", "MicDevice", "", "Microphone device name. Leave empty to use the system default."); NoiseGateDb = cfg.Bind("Audio", "NoiseGateDb", -50f, "Noise gate threshold in dBFS: audio quieter than this is not transmitted. Use the in-game calibration overlay (CalibrateKey) to set it visually. -80 = off."); HudX = cfg.Bind("HUD", "MicIndicatorX", 60f, "Horizontal pixel position of the mic level indicator (from the left)."); HudBottom = cfg.Bind("HUD", "MicIndicatorBottom", 300f, "Vertical position of the mic level indicator, as pixels up from the screen bottom."); HudScale = cfg.Bind("HUD", "MicIndicatorScale", 2f, "Size multiplier for the mic level indicator."); SelfTest = cfg.Bind("Debug", "SelfTest", false, "Loopback test: also play your OWN voice back locally (through the full Opus pipeline) so you can judge audio quality solo. Leave OFF for normal play."); } } public static class VoiceRouter { private sealed class RateLimiter { private sealed class Bucket { public float Tokens; public float LastSeen; } private const float BurstSeconds = 1.5f; private const float ResetAfterSilence = 10f; private readonly Dictionary _buckets = new Dictionary(); public bool Allow(long senderPeerId) { float realtimeSinceStartup = Time.realtimeSinceStartup; float num = Mathf.Max(0.02f, 0.02f); float num2 = 1f / num; float num3 = Mathf.Max(3f, num2 * 1.5f); if (!_buckets.TryGetValue(senderPeerId, out var value)) { value = new Bucket { Tokens = num3, LastSeen = realtimeSinceStartup }; _buckets[senderPeerId] = value; } if (realtimeSinceStartup - value.LastSeen > 10f) { value.Tokens = num3; } else { value.Tokens = Mathf.Min(num3, value.Tokens + (realtimeSinceStartup - value.LastSeen) * num2); } value.LastSeen = realtimeSinceStartup; if (value.Tokens < 1f) { return false; } value.Tokens -= 1f; return true; } } private const string RpcUp = "PV_Up"; private const string RpcDown = "PV_Down"; private const int MaxOpusPayloadBytes = 1275; private const long LoopbackId = long.MinValue; private static bool _registered; private static readonly Dictionary Speakers = new Dictionary(); private static readonly RateLimiter Limiter = new RateLimiter(); public static bool Ready => _registered && ZRoutedRpc.instance != null; public static void EnsureRegistered() { if (!_registered && ZRoutedRpc.instance != null) { ZRoutedRpc.instance.Register("PV_Up", (Action)OnUpstream); ZRoutedRpc.instance.Register("PV_Down", (Action)OnDownstream); _registered = true; Plugin.Log.LogInfo((object)"VoiceRouter registered RPCs."); } } public static void Reset() { _registered = false; foreach (SpeakerVoice value in Speakers.Values) { value.Destroy(); } Speakers.Clear(); } public static void SendFrame(byte[] opus, Vector3 pos) { //IL_0037: Unknown result type (might be due to invalid IL or missing references) //IL_003d: Expected O, but got Unknown //IL_003e: 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_0058: Unknown result type (might be due to invalid IL or missing references) //IL_002f: Unknown result type (might be due to invalid IL or missing references) if (!Ready || opus == null) { return; } if (VoiceConfig.SelfTest.Value) { PlayLocal(long.MinValue, pos, opus); } ZPackage val = new ZPackage(); val.Write(pos.x); val.Write(pos.y); val.Write(pos.z); val.Write(opus); if ((Object)(object)ZNet.instance != (Object)null && ZNet.instance.IsServer()) { val.SetPos(0); OnUpstream(ZDOMan.GetSessionID(), val); return; } ZNetPeer val2 = (((Object)(object)ZNet.instance != (Object)null) ? ZNet.instance.GetServerPeer() : null); if (val2 != null) { ZRoutedRpc.instance.InvokeRoutedRPC(val2.m_uid, "PV_Up", new object[1] { val }); } } private static void OnUpstream(long sender, ZPackage pkg) { //IL_00a2: Unknown result type (might be due to invalid IL or missing references) //IL_00a7: Unknown result type (might be due to invalid IL or missing references) //IL_00a8: Unknown result type (might be due to invalid IL or missing references) //IL_00ad: Unknown result type (might be due to invalid IL or missing references) //IL_0131: 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_0137: Unknown result type (might be due to invalid IL or missing references) //IL_013c: Unknown result type (might be due to invalid IL or missing references) //IL_00c2: Unknown result type (might be due to invalid IL or missing references) //IL_0155: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)ZNet.instance == (Object)null || !ZNet.instance.IsServer() || !Limiter.Allow(sender) || !TryReadFrame(pkg, out var pos, out var opus)) { return; } float value = VoiceConfig.HearingRange.Value; float num = value * value; Vector3 val; foreach (ZNetPeer peer in ZNet.instance.GetPeers()) { if (peer != null && peer.m_uid != sender) { val = peer.m_refPos - pos; if (!(((Vector3)(ref val)).sqrMagnitude > num)) { ZPackage val2 = BuildDown(sender, pos, opus); ZRoutedRpc.instance.InvokeRoutedRPC(peer.m_uid, "PV_Down", new object[1] { val2 }); } } } Player localPlayer = Player.m_localPlayer; if (!((Object)(object)localPlayer != (Object)null)) { return; } long sessionID = ZDOMan.GetSessionID(); if (sessionID != sender) { val = ((Component)localPlayer).transform.position - pos; if (((Vector3)(ref val)).sqrMagnitude <= num) { PlayLocal(sender, pos, opus); } } } private static void OnDownstream(long sender, ZPackage pkg) { //IL_001b: Unknown result type (might be due to invalid IL or missing references) //IL_0020: Unknown result type (might be due to invalid IL or missing references) //IL_002f: Unknown result type (might be due to invalid IL or missing references) //IL_0040: Unknown result type (might be due to invalid IL or missing references) long speakerId; Vector3 pos; byte[] opus; try { speakerId = pkg.ReadLong(); pos = new Vector3(pkg.ReadSingle(), pkg.ReadSingle(), pkg.ReadSingle()); opus = pkg.ReadByteArray(); } catch { return; } if (IsValidFrame(pos, opus)) { PlayLocal(speakerId, pos, opus); } } private static bool TryReadFrame(ZPackage pkg, out Vector3 pos, out byte[] opus) { //IL_0002: Unknown result type (might be due to invalid IL or missing references) //IL_001f: Unknown result type (might be due to invalid IL or missing references) //IL_0024: Unknown result type (might be due to invalid IL or missing references) //IL_003b: Unknown result type (might be due to invalid IL or missing references) pos = default(Vector3); opus = null; try { pos = new Vector3(pkg.ReadSingle(), pkg.ReadSingle(), pkg.ReadSingle()); opus = pkg.ReadByteArray(); } catch { return false; } return IsValidFrame(pos, opus); } private static bool IsValidFrame(Vector3 pos, byte[] opus) { //IL_001d: 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_0037: Unknown result type (might be due to invalid IL or missing references) if (opus == null || opus.Length == 0 || opus.Length > 1275) { return false; } if (!IsFinite(pos.x) || !IsFinite(pos.y) || !IsFinite(pos.z)) { return false; } return true; } private static bool IsFinite(float f) { return !float.IsNaN(f) && !float.IsInfinity(f); } private static void PlayLocal(long speakerId, Vector3 pos, byte[] opus) { //IL_002c: Unknown result type (might be due to invalid IL or missing references) if (!Speakers.TryGetValue(speakerId, out var value)) { value = new SpeakerVoice(speakerId); Speakers[speakerId] = value; } value.SetPosition(pos); value.PushPacket(opus); } private static ZPackage BuildDown(long speakerId, Vector3 pos, byte[] opus) { //IL_0001: Unknown result type (might be due to invalid IL or missing references) //IL_0007: Expected O, but got Unknown //IL_0010: Unknown result type (might be due to invalid IL or missing references) //IL_001d: 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) ZPackage val = new ZPackage(); val.Write(speakerId); val.Write(pos.x); val.Write(pos.y); val.Write(pos.z); val.Write(opus); return val; } public static void PruneIdle(float idleSeconds) { if (Speakers.Count == 0) { return; } float realtimeSinceStartup = Time.realtimeSinceStartup; List list = null; foreach (KeyValuePair speaker in Speakers) { if (realtimeSinceStartup - speaker.Value.LastActiveTime > idleSeconds) { (list ?? (list = new List())).Add(speaker.Key); } } if (list == null) { return; } foreach (long item in list) { Speakers[item].Destroy(); Speakers.Remove(item); } } } [BepInPlugin("Nubedeu.proximityvoice", "ProximityVoice", "0.1.0")] public class Plugin : BaseUnityPlugin { public const string PluginGUID = "Nubedeu.proximityvoice"; public const string PluginName = "ProximityVoice"; public const string PluginVersion = "0.1.0"; public static ManualLogSource Log; private IMicCapture _mic; private readonly List _frameBuf = new List(); private VoiceEncoder _encoder; private bool _wasCapturing; private bool _micOn; private bool _calibrating; private bool _gainAdjust; private bool _rebinding; private static KeyCode[] _rebindCandidates; private float _micLevel; private float _curDb; private bool _gateOpen; private float _gateOpenUntil; private float _pttHeldUntil; private const float GateHold = 0.25f; private static KeyCode[] RebindCandidates { get { //IL_0032: Unknown result type (might be due to invalid IL or missing references) //IL_0037: Unknown result type (might be due to invalid IL or missing references) //IL_0038: Unknown result type (might be due to invalid IL or missing references) //IL_003a: Invalid comparison between Unknown and I4 //IL_0043: Unknown result type (might be due to invalid IL or missing references) if (_rebindCandidates == null) { List list = new List(); foreach (KeyCode value in Enum.GetValues(typeof(KeyCode))) { if ((int)value > 0) { list.Add(value); } } _rebindCandidates = list.ToArray(); } return _rebindCandidates; } } private void Awake() { Log = ((BaseUnityPlugin)this).Logger; VoiceConfig.Init(((BaseUnityPlugin)this).Config); _mic = MicCapture.Create(); _encoder = new VoiceEncoder(); Log.LogInfo((object)"ProximityVoice loaded."); } private void Update() { //IL_003e: 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_0065: Unknown result type (might be due to invalid IL or missing references) //IL_00b5: Unknown result type (might be due to invalid IL or missing references) //IL_007a: Unknown result type (might be due to invalid IL or missing references) //IL_010b: Unknown result type (might be due to invalid IL or missing references) //IL_01f4: Unknown result type (might be due to invalid IL or missing references) //IL_0175: Unknown result type (might be due to invalid IL or missing references) //IL_0258: Unknown result type (might be due to invalid IL or missing references) //IL_0432: Unknown result type (might be due to invalid IL or missing references) //IL_0437: Unknown result type (might be due to invalid IL or missing references) //IL_046f: Unknown result type (might be due to invalid IL or missing references) VoiceRouter.EnsureRegistered(); if (!VoiceRouter.Ready) { return; } Player localPlayer = Player.m_localPlayer; if (_rebinding) { HandleRebind(); return; } if (Input.GetKeyDown(VoiceConfig.RebindKey.Value)) { _rebinding = true; return; } KeyCode value = VoiceConfig.PushToTalkKey.Value; if (VoiceConfig.Mode.Value == MicMode.PushToTalk) { if (Input.GetKey(value)) { _pttHeldUntil = Time.time + VoiceConfig.PttReleaseTail.Value; } _micOn = Time.time <= _pttHeldUntil; } else if (Input.GetKeyDown(value)) { _micOn = !_micOn; if ((Object)(object)MessageHud.instance != (Object)null) { MessageHud.instance.ShowMessage((MessageType)1, _micOn ? "VOIP Ligado" : "VOIP Desligado", 0, (Sprite)null, false); } } if (Input.GetKeyDown(VoiceConfig.MicGainKey.Value)) { _gainAdjust = !_gainAdjust; } if (_gainAdjust) { float num = 0f; if (Input.GetKeyDown((KeyCode)273)) { num += 0.5f; } if (Input.GetKeyDown((KeyCode)274)) { num -= 0.5f; } float y = Input.mouseScrollDelta.y; if (y > 0f) { num += 0.5f; } else if (y < 0f) { num -= 0.5f; } if (num != 0f) { VoiceConfig.MicGain.Value = Mathf.Clamp(VoiceConfig.MicGain.Value + num, 1f, 20f); } } if (Input.GetKeyDown(VoiceConfig.CalibrateKey.Value)) { _calibrating = !_calibrating; } if (_calibrating) { float num2 = 0f; if (Input.GetKeyDown((KeyCode)91)) { num2 -= 1f; } if (Input.GetKeyDown((KeyCode)93)) { num2 += 1f; } float y2 = Input.mouseScrollDelta.y; if (y2 > 0f) { num2 += 1f; } else if (y2 < 0f) { num2 -= 1f; } if (num2 != 0f) { VoiceConfig.NoiseGateDb.Value = Mathf.Clamp(VoiceConfig.NoiseGateDb.Value + num2, -80f, 0f); } } bool flag = (_micOn || _calibrating || _gainAdjust) && (Object)(object)localPlayer != (Object)null; float num3 = 0f; if (flag) { if (!_mic.Running) { _mic.Start(); } _frameBuf.Clear(); _mic.PollFrames(_frameBuf); ApplyMicGain(_frameBuf, VoiceConfig.MicGain.Value); foreach (short[] item in _frameBuf) { for (int i = 0; i < item.Length; i++) { float num4 = (float)Mathf.Abs((int)item[i]) * 3.0517578E-05f; if (num4 > num3) { num3 = num4; } } } if (Dbfs(num3) >= VoiceConfig.NoiseGateDb.Value) { _gateOpenUntil = Time.time + 0.25f; } _gateOpen = Time.time <= _gateOpenUntil; if (_micOn && _gateOpen && (Object)(object)localPlayer != (Object)null) { Vector3 position = ((Component)localPlayer).transform.position; foreach (short[] item2 in _frameBuf) { byte[] array = _encoder.Encode(item2); if (array != null) { VoiceRouter.SendFrame(array, position); } } } } else if (_wasCapturing) { _mic.Stop(); _gateOpen = false; } float num5 = (flag ? num3 : 0f); if (num5 > _micLevel) { _micLevel = num5; } else { _micLevel = Mathf.MoveTowards(_micLevel, num5, Time.deltaTime * 1.5f); } _curDb = Dbfs(_micLevel); _wasCapturing = flag; VoiceRouter.PruneIdle(2f); } private static void ApplyMicGain(List frames, float gain) { if (gain == 1f) { return; } foreach (short[] frame in frames) { for (int i = 0; i < frame.Length; i++) { int num = (int)((float)frame[i] * gain); if (num > 32767) { num = 32767; } else if (num < -32768) { num = -32768; } frame[i] = (short)num; } } } private static float Dbfs(float amp) { return (amp <= 0.0001f) ? (-80f) : Mathf.Max(-80f, 20f * Mathf.Log10(amp)); } private void HandleRebind() { //IL_003b: 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_0042: Invalid comparison between Unknown and I4 //IL_0044: 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_005d: Unknown result type (might be due to invalid IL or missing references) //IL_0074: Unknown result type (might be due to invalid IL or missing references) if (Input.GetKeyDown((KeyCode)27)) { _rebinding = false; } else { if (!Input.anyKeyDown) { return; } KeyCode[] rebindCandidates = RebindCandidates; for (int i = 0; i < rebindCandidates.Length; i++) { KeyCode val = rebindCandidates[i]; if ((int)val != 27 && val != VoiceConfig.RebindKey.Value && Input.GetKeyDown(val)) { VoiceConfig.PushToTalkKey.Value = val; _rebinding = false; if ((Object)(object)MessageHud.instance != (Object)null) { MessageHud.instance.ShowMessage((MessageType)1, "Talk key: " + ((object)(KeyCode)(ref val)).ToString(), 0, (Sprite)null, false); } break; } } } } private void OnGUI() { if ((Object)(object)Player.m_localPlayer == (Object)null) { return; } if (_rebinding) { VoiceHud.DrawRebindPrompt(); return; } if (_micOn) { VoiceHud.Draw(_micLevel, _gateOpen); } if (_calibrating) { VoiceHud.DrawCalibration(_curDb, VoiceConfig.NoiseGateDb.Value, _gateOpen); } if (_gainAdjust) { VoiceHud.DrawGainAdjust(_curDb, VoiceConfig.MicGain.Value, _gateOpen); } } private void OnDestroy() { _mic.Stop(); _encoder?.Dispose(); VoiceRouter.Reset(); } } }