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 EchoMimics v0.1.1
BepInEx/plugins/EchoMimics/EchoMimics.dll
Decompiled 2 months agousing System; using System.Collections.Concurrent; using System.Collections.Generic; using System.Diagnostics; using System.Linq; using System.Net.Http; using System.Net.Http.Headers; using System.Reflection; using System.Runtime.CompilerServices; using System.Runtime.Versioning; using System.Text; using System.Threading; using System.Threading.Tasks; using BepInEx; using BepInEx.Configuration; using BepInEx.Logging; using Dissonance; using Dissonance.Audio.Capture; using GameNetcodeStuff; using HarmonyLib; using Microsoft.CodeAnalysis; using Newtonsoft.Json; using Unity.Collections; using Unity.Netcode; using UnityEngine; [assembly: CompilationRelaxations(8)] [assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)] [assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)] [assembly: TargetFramework(".NETStandard,Version=v2.1", FrameworkDisplayName = ".NET Standard 2.1")] [assembly: AssemblyCompany("EchoMimics")] [assembly: AssemblyConfiguration("Release")] [assembly: AssemblyDescription("Local context-aware voice mimics for Lethal Company")] [assembly: AssemblyFileVersion("0.1.1.0")] [assembly: AssemblyInformationalVersion("0.1.1+7f8ccba6c6b20c110fb4ce508ca0f4d2375192a0")] [assembly: AssemblyProduct("EchoMimics")] [assembly: AssemblyTitle("EchoMimics")] [assembly: AssemblyVersion("0.1.1.0")] [module: RefSafetyRules(11)] namespace Microsoft.CodeAnalysis { [CompilerGenerated] [Embedded] internal sealed class EmbeddedAttribute : Attribute { } } namespace System.Runtime.CompilerServices { [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 EchoMimics { internal static class AmbientCapture { private const int Rate = 16000; private const int LoopSeconds = 20; private const int MinSamples = 8000; private const int MaxSamples = 128000; private const int SilenceSamples = 11200; private static AudioClip _clip; private static string _device; private static int _readPosition; private static bool _speaking; private static int _silent; private static float _nextStart; private static readonly List<float> Utterance = new List<float>(64000); internal static void Tick() { if (!CanCapture()) { Stop(); return; } if ((Object)(object)_clip == (Object)null) { if (Time.unscaledTime >= _nextStart) { Start(); } return; } int position = Microphone.GetPosition(_device); if (position < 0) { Stop(); return; } int num = ((position >= _readPosition) ? (position - _readPosition) : (_clip.samples - _readPosition + position)); if (num <= 0 || num > _clip.samples) { return; } float[] array = new float[num * _clip.channels]; int num2 = Math.Min(num, _clip.samples - _readPosition); float[] array2 = new float[num2 * _clip.channels]; if (!_clip.GetData(array2, _readPosition)) { return; } Array.Copy(array2, 0, array, 0, array2.Length); if (num2 < num) { float[] array3 = new float[(num - num2) * _clip.channels]; if (!_clip.GetData(array3, 0)) { return; } Array.Copy(array3, 0, array, array2.Length, array3.Length); } _readPosition = position; Consume(array, _clip.channels); } private static bool CanCapture() { NetworkManager singleton = NetworkManager.Singleton; PlayerControllerB val = GameNetworkManager.Instance?.localPlayerController; if (Plugin.Enabled.Value && Plugin.CaptureConsent.Value && (Object)(object)singleton != (Object)null && singleton.IsListening && (Object)(object)val != (Object)null && val.isPlayerControlled) { return !val.isPlayerDead; } return false; } private static void Start() { try { _device = ResolveDevice(); _clip = Microphone.Start(_device, true, 20, 16000); _readPosition = 0; _speaking = false; _silent = 0; Utterance.Clear(); Plugin.Log.LogInfo((object)("Local voice capture started on '" + (_device ?? "system default") + "'.")); } catch (Exception ex) { Plugin.Log.LogWarning((object)("Voice capture unavailable: " + ex.Message)); _nextStart = Time.unscaledTime + 10f; } } internal static void Stop() { if ((Object)(object)_clip != (Object)null) { try { Microphone.End(_device); } catch { } Object.Destroy((Object)(object)_clip); } _clip = null; _speaking = false; _silent = 0; Utterance.Clear(); _nextStart = Time.unscaledTime + 2f; } private static void Consume(float[] input, int channels) { int num = input.Length / Math.Max(1, channels); for (int i = 0; i < num; i++) { float num2 = 0f; for (int j = 0; j < channels; j++) { num2 += input[i * channels + j]; } num2 /= (float)channels; if (Mathf.Abs(num2) >= Plugin.VadThreshold.Value) { _speaking = true; _silent = 0; } else if (_speaking) { _silent++; } if (_speaking) { Utterance.Add(num2); } if (_speaking && (_silent >= 11200 || Utterance.Count >= 128000)) { Finish(); } } } private static void Finish() { int num = Math.Max(0, Utterance.Count - _silent); if (num >= 8000) { MimicTransport.SendUtterance(AudioCodec.EncodeMonoWav(Utterance.GetRange(0, num).ToArray(), 16000)); } Utterance.Clear(); _speaking = false; _silent = 0; } private static string ResolveDevice() { string text = Plugin.InputDevice.Value?.Trim(); if (!string.IsNullOrEmpty(text)) { string[] devices = Microphone.devices; foreach (string text2 in devices) { if (string.Equals(text2, text, StringComparison.OrdinalIgnoreCase)) { return text2; } } } try { DissonanceComms obj = Object.FindObjectOfType<DissonanceComms>(); object obj2; if (obj == null) { obj2 = null; } else { IMicrophoneCapture microphoneCapture = obj.MicrophoneCapture; obj2 = ((microphoneCapture != null) ? microphoneCapture.Device : null); } string text3 = (string)obj2; if (!string.IsNullOrEmpty(text3)) { string[] devices = Microphone.devices; foreach (string text4 in devices) { if (string.Equals(text4, text3, StringComparison.OrdinalIgnoreCase)) { return text4; } } } } catch { } return null; } } internal static class AudioCodec { internal static byte[] EncodeMonoWav(float[] samples, int sampleRate) { if (samples == null || samples.Length == 0) { return null; } byte[] array = new byte[44 + samples.Length * 2]; WriteAscii(array, 0, "RIFF"); WriteInt(array, 4, array.Length - 8); WriteAscii(array, 8, "WAVE"); WriteAscii(array, 12, "fmt "); WriteInt(array, 16, 16); WriteShort(array, 20, 1); WriteShort(array, 22, 1); WriteInt(array, 24, sampleRate); WriteInt(array, 28, sampleRate * 2); WriteShort(array, 32, 2); WriteShort(array, 34, 16); WriteAscii(array, 36, "data"); WriteInt(array, 40, samples.Length * 2); for (int i = 0; i < samples.Length; i++) { WriteShort(array, 44 + i * 2, (short)Mathf.RoundToInt(Mathf.Clamp(samples[i], -0.98f, 0.98f) * 32767f)); } return array; } internal static bool DecodeWav(byte[] wav, out float[] samples, out int sampleRate) { samples = null; sampleRate = 0; if (wav == null || wav.Length < 44 || Encoding.ASCII.GetString(wav, 0, 4) != "RIFF") { return false; } int num = BitConverter.ToInt16(wav, 22); sampleRate = BitConverter.ToInt32(wav, 24); int num2 = BitConverter.ToInt16(wav, 34); if (num < 1 || num > 2 || num2 != 16) { return false; } int num3; for (int i = 12; i + 8 <= wav.Length; i += 8 + num3 + (num3 & 1)) { string text = Encoding.ASCII.GetString(wav, i, 4); num3 = BitConverter.ToInt32(wav, i + 4); if (!(text == "data") || num3 < 2 || i + 8 + num3 > wav.Length) { continue; } int num4 = num3 / (2 * num); samples = new float[num4]; for (int j = 0; j < num4; j++) { int num5 = 0; for (int k = 0; k < num; k++) { num5 += BitConverter.ToInt16(wav, i + 8 + (j * num + k) * 2); } samples[j] = (float)num5 / (32768f * (float)num); } if (sampleRate >= 8000) { return sampleRate <= 48000; } return false; } return false; } private static void WriteAscii(byte[] target, int offset, string value) { Encoding.ASCII.GetBytes(value).CopyTo(target, offset); } private static void WriteInt(byte[] target, int offset, int value) { BitConverter.GetBytes(value).CopyTo(target, offset); } private static void WriteShort(byte[] target, int offset, short value) { BitConverter.GetBytes(value).CopyTo(target, offset); } } internal sealed class RuntimeRequest { public string session_id; public string speaker_id; public string speaker_name; public string mimic_id; public string copied_player_id; public string copied_player_name; public string scene; public string language; public string audio_wav; } internal sealed class RuntimeResponse { public string heard { get; set; } = ""; public string reply { get; set; } = ""; public string audio_wav { get; set; } = ""; public string error { get; set; } = ""; public int sample_rate { get; set; } } internal static class LocalRuntimeClient { private static readonly HttpClient Client = new HttpClient { Timeout = TimeSpan.FromSeconds(75.0) }; internal static async Task<RuntimeResponse> TurnAsync(RuntimeRequest request) { return await PostAsync("/v1/turn", request).ConfigureAwait(continueOnCapturedContext: false); } internal static async Task<RuntimeResponse> ObserveAsync(RuntimeRequest request) { return await PostAsync("/v1/observe", request).ConfigureAwait(continueOnCapturedContext: false); } internal static async Task ClearSessionAsync(string sessionId) { string requestUri = Plugin.RuntimeUrl.Value.TrimEnd('/') + "/v1/session/clear"; using HttpRequestMessage message = new HttpRequestMessage(HttpMethod.Post, requestUri); string text = Plugin.RuntimeToken.Value?.Trim(); if (!string.IsNullOrEmpty(text)) { message.Headers.Authorization = new AuthenticationHeaderValue("Bearer", text); } message.Content = new StringContent(JsonConvert.SerializeObject((object)new { session_id = sessionId }), Encoding.UTF8, "application/json"); using HttpResponseMessage httpResponseMessage = await Client.SendAsync(message).ConfigureAwait(continueOnCapturedContext: false); httpResponseMessage.EnsureSuccessStatusCode(); } private static async Task<RuntimeResponse> PostAsync(string path, RuntimeRequest request) { string requestUri = Plugin.RuntimeUrl.Value.TrimEnd('/') + path; using HttpRequestMessage message = new HttpRequestMessage(HttpMethod.Post, requestUri); string text = Plugin.RuntimeToken.Value?.Trim(); if (!string.IsNullOrEmpty(text)) { message.Headers.Authorization = new AuthenticationHeaderValue("Bearer", text); } message.Content = new StringContent(JsonConvert.SerializeObject((object)request), Encoding.UTF8, "application/json"); using HttpResponseMessage response = await Client.SendAsync(message).ConfigureAwait(continueOnCapturedContext: false); RuntimeResponse runtimeResponse = JsonConvert.DeserializeObject<RuntimeResponse>(await response.Content.ReadAsStringAsync().ConfigureAwait(continueOnCapturedContext: false)) ?? new RuntimeResponse(); if (!response.IsSuccessStatusCode) { throw new InvalidOperationException(runtimeResponse.error ?? $"runtime HTTP {(int)response.StatusCode}"); } return runtimeResponse; } } internal sealed class MimicIdentity { internal MaskedPlayerEnemy Enemy; internal ulong CopiedClientId; internal string CopiedName; internal float NextReplyAt; } internal static class MimicDirector { private static readonly Dictionary<ulong, MimicIdentity> Mimics = new Dictionary<ulong, MimicIdentity>(); private static float _nextScan; internal static void Tick() { NetworkManager singleton = NetworkManager.Singleton; if (!((Object)(object)singleton == (Object)null) && singleton.IsServer && singleton.IsListening && !(Time.unscaledTime < _nextScan)) { _nextScan = Time.unscaledTime + 2f; MaskedPlayerEnemy[] array = Object.FindObjectsOfType<MaskedPlayerEnemy>(); for (int i = 0; i < array.Length; i++) { Register(array[i]); } ulong[] array2 = Mimics.Keys.Where((ulong id) => (Object)(object)Mimics[id].Enemy == (Object)null).ToArray(); foreach (ulong key in array2) { Mimics.Remove(key); } } } private static void Register(MaskedPlayerEnemy enemy) { if ((Object)(object)enemy == (Object)null || (Object)(object)((NetworkBehaviour)enemy).NetworkObject == (Object)null) { return; } ulong networkObjectId = ((NetworkBehaviour)enemy).NetworkObjectId; if (Mimics.ContainsKey(networkObjectId)) { return; } PlayerControllerB val = null; try { val = enemy.mimickingPlayer; } catch { } if ((Object)(object)val == (Object)null) { PlayerControllerB[] array = StartOfRound.Instance?.allPlayerScripts?.Where((PlayerControllerB p) => (Object)(object)p != (Object)null && p.isPlayerControlled).ToArray(); if (array != null && array.Length != 0) { val = array[(uint)(networkObjectId % (ulong)array.Length)]; } } if (!((Object)(object)val == (Object)null)) { Mimics[networkObjectId] = new MimicIdentity { Enemy = enemy, CopiedClientId = val.actualClientId, CopiedName = (val.playerUsername ?? "crewmate") }; Plugin.Log.LogInfo((object)$"Masked {networkObjectId} copied {val.playerUsername} ({val.actualClientId})."); } } internal static MimicIdentity ChooseForSpeaker(ulong clientId) { //IL_009d: 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) NetworkManager singleton = NetworkManager.Singleton; if ((Object)(object)singleton == (Object)null || !singleton.IsServer || !singleton.ConnectedClients.TryGetValue(clientId, out var value)) { return null; } NetworkObject playerObject = value.PlayerObject; PlayerControllerB val = ((playerObject != null) ? ((Component)playerObject).GetComponent<PlayerControllerB>() : null); if ((Object)(object)val == (Object)null) { return null; } MimicIdentity mimicIdentity = null; float num = float.MaxValue; foreach (MimicIdentity value2 in Mimics.Values) { if (!((Object)(object)value2.Enemy == (Object)null) && !((EnemyAI)value2.Enemy).isEnemyDead && !(Time.unscaledTime < value2.NextReplyAt)) { float num2 = Vector3.Distance(((Component)val).transform.position, ((Component)value2.Enemy).transform.position); if (num2 <= Plugin.HearingRange.Value && num2 < num) { mimicIdentity = value2; num = num2; } } } if (mimicIdentity == null || Random.value > Mathf.Clamp01(Plugin.ReplyChance.Value)) { return null; } mimicIdentity.NextReplyAt = Time.unscaledTime + Mathf.Max(2f, Plugin.ReplyCooldown.Value); return mimicIdentity; } internal static string BuildScene(ulong speakerId, MimicIdentity mimic) { //IL_005a: Unknown result type (might be due to invalid IL or missing references) //IL_0066: Unknown result type (might be due to invalid IL or missing references) PlayerControllerB val = ResolvePlayer(speakerId); List<string> list = new List<string>(); PlayerControllerB[] array = StartOfRound.Instance?.allPlayerScripts ?? Array.Empty<PlayerControllerB>(); foreach (PlayerControllerB val2 in array) { if (!((Object)(object)val2 == (Object)null) && val2.isPlayerControlled) { float num = Vector3.Distance(((Component)mimic.Enemy).transform.position, ((Component)val2).transform.position); if (num <= 45f) { list.Add(string.Format("{0}: {1}, {2:0}m away, {3}", val2.playerUsername, val2.isPlayerDead ? "dead" : "alive", num, val2.isInsideFactory ? "inside" : "outside")); } } } string text = StartOfRound.Instance?.currentLevel?.PlanetName ?? "unknown moon"; return "Moon: " + text + ". Mimic is " + (((EnemyAI)mimic.Enemy).isOutside ? "outside" : "inside") + ". Speaker is " + (((Object)(object)val != (Object)null && val.isInsideFactory) ? "inside" : "outside") + ". Nearby crew: " + string.Join("; ", list) + "."; } internal static PlayerControllerB ResolvePlayer(ulong clientId) { NetworkManager singleton = NetworkManager.Singleton; if ((Object)(object)singleton != (Object)null && singleton.ConnectedClients.TryGetValue(clientId, out var value)) { NetworkObject playerObject = value.PlayerObject; if (playerObject == null) { return null; } return ((Component)playerObject).GetComponent<PlayerControllerB>(); } return null; } internal static MaskedPlayerEnemy ResolveMimic(ulong networkId) { if (Mimics.TryGetValue(networkId, out var value) && (Object)(object)value.Enemy != (Object)null) { return value.Enemy; } MaskedPlayerEnemy[] array = Object.FindObjectsOfType<MaskedPlayerEnemy>(); foreach (MaskedPlayerEnemy val in array) { if (((NetworkBehaviour)val).NetworkObjectId == networkId) { return val; } } return null; } internal static void Reset() { Mimics.Clear(); _nextScan = 0f; } } internal static class MimicTransport { private sealed class IncomingSpeech { internal byte[] Data; internal int Received; internal ulong MimicId; internal float ExpiresAt; internal readonly HashSet<int> Offsets = new HashSet<int>(); } [CompilerGenerated] private static class <>O { public static HandleNamedMessageDelegate <0>__OnUtterance; public static HandleNamedMessageDelegate <1>__OnSpeechStart; public static HandleNamedMessageDelegate <2>__OnSpeechChunk; } private const string UtteranceMessage = "EchoMimics_Utterance_v1"; private const string SpeechStartMessage = "EchoMimics_SpeechStart_v1"; private const string SpeechChunkMessage = "EchoMimics_SpeechChunk_v1"; private const int MaxUtteranceBytes = 307200; private const int MaxSpeechBytes = 921600; private const int ChunkBytes = 7000; private static readonly SemaphoreSlim InferenceGate = new SemaphoreSlim(1, 1); private static readonly Dictionary<ulong, IncomingSpeech> Incoming = new Dictionary<ulong, IncomingSpeech>(); private static NetworkManager _registeredOn; private static ulong _nextTransferId = 1uL; private static string _sessionId = Guid.NewGuid().ToString("N"); internal static void Tick() { NetworkManager singleton = NetworkManager.Singleton; if ((Object)(object)singleton == (Object)null || !singleton.IsListening || singleton.CustomMessagingManager == null) { _registeredOn = null; Incoming.Clear(); return; } if ((Object)(object)_registeredOn != (Object)(object)singleton) { Register(singleton); } ulong[] array = (from p in Incoming where p.Value.ExpiresAt < Time.unscaledTime select p.Key).ToArray(); foreach (ulong key in array) { Incoming.Remove(key); } } private static void Register(NetworkManager nm) { //IL_005c: 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_0067: Expected O, but got Unknown //IL_0087: Unknown result type (might be due to invalid IL or missing references) //IL_008c: Unknown result type (might be due to invalid IL or missing references) //IL_0092: Expected O, but got Unknown //IL_00b2: 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_00bd: Expected O, but got Unknown try { nm.CustomMessagingManager.UnregisterNamedMessageHandler("EchoMimics_Utterance_v1"); } catch { } try { nm.CustomMessagingManager.UnregisterNamedMessageHandler("EchoMimics_SpeechStart_v1"); } catch { } try { nm.CustomMessagingManager.UnregisterNamedMessageHandler("EchoMimics_SpeechChunk_v1"); } catch { } CustomMessagingManager customMessagingManager = nm.CustomMessagingManager; object obj4 = <>O.<0>__OnUtterance; if (obj4 == null) { HandleNamedMessageDelegate val = OnUtterance; <>O.<0>__OnUtterance = val; obj4 = (object)val; } customMessagingManager.RegisterNamedMessageHandler("EchoMimics_Utterance_v1", (HandleNamedMessageDelegate)obj4); CustomMessagingManager customMessagingManager2 = nm.CustomMessagingManager; object obj5 = <>O.<1>__OnSpeechStart; if (obj5 == null) { HandleNamedMessageDelegate val2 = OnSpeechStart; <>O.<1>__OnSpeechStart = val2; obj5 = (object)val2; } customMessagingManager2.RegisterNamedMessageHandler("EchoMimics_SpeechStart_v1", (HandleNamedMessageDelegate)obj5); CustomMessagingManager customMessagingManager3 = nm.CustomMessagingManager; object obj6 = <>O.<2>__OnSpeechChunk; if (obj6 == null) { HandleNamedMessageDelegate val3 = OnSpeechChunk; <>O.<2>__OnSpeechChunk = val3; obj6 = (object)val3; } customMessagingManager3.RegisterNamedMessageHandler("EchoMimics_SpeechChunk_v1", (HandleNamedMessageDelegate)obj6); _registeredOn = nm; } internal unsafe static void SendUtterance(byte[] wav) { //IL_0063: 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) //IL_0088: Unknown result type (might be due to invalid IL or missing references) if (wav == null || wav.Length < 44 || wav.Length > 307200) { return; } NetworkManager singleton = NetworkManager.Singleton; if ((Object)(object)singleton == (Object)null || !singleton.IsListening || singleton.CustomMessagingManager == null) { return; } if (singleton.IsServer) { ProcessUtterance(singleton.LocalClientId, wav); return; } FastBufferWriter val = default(FastBufferWriter); ((FastBufferWriter)(ref val))..ctor(wav.Length + 16, (Allocator)2, -1); try { int num = wav.Length; ((FastBufferWriter)(ref val)).WriteValueSafe<int>(ref num, default(ForPrimitives)); ((FastBufferWriter)(ref val)).WriteBytesSafe(wav, wav.Length, 0); singleton.CustomMessagingManager.SendNamedMessage("EchoMimics_Utterance_v1", 0uL, val, (NetworkDelivery)4); } finally { ((IDisposable)(*(FastBufferWriter*)(&val))/*cast due to .constrained prefix*/).Dispose(); } } private static void OnUtterance(ulong senderId, FastBufferReader reader) { //IL_0030: Unknown result type (might be due to invalid IL or missing references) //IL_0036: Unknown result type (might be due to invalid IL or missing references) try { NetworkManager singleton = NetworkManager.Singleton; if (!((Object)(object)singleton == (Object)null) && singleton.IsServer && senderId != 0L && singleton.ConnectedClientsIds.Contains(senderId)) { int num = default(int); ((FastBufferReader)(ref reader)).ReadValueSafe<int>(ref num, default(ForPrimitives)); if (num >= 44 && num <= 307200 && ((FastBufferReader)(ref reader)).Length - ((FastBufferReader)(ref reader)).Position >= num) { byte[] wav = new byte[num]; ((FastBufferReader)(ref reader)).ReadBytesSafe(ref wav, num, 0); ProcessUtterance(senderId, wav); } } } catch (Exception ex) { Plugin.Log.LogWarning((object)("Rejected remote utterance: " + ex.Message)); } } private static void ProcessUtterance(ulong senderId, byte[] wav) { PlayerControllerB val = MimicDirector.ResolvePlayer(senderId); if (!((Object)(object)val == (Object)null)) { MimicIdentity mimicIdentity = MimicDirector.ChooseForSpeaker(senderId); string scene = ((mimicIdentity == null) ? BasicScene(val) : MimicDirector.BuildScene(senderId, mimicIdentity)); RuntimeRequest obj = new RuntimeRequest { session_id = _sessionId, speaker_id = senderId.ToString(), speaker_name = (val.playerUsername ?? $"Crewmate {senderId}"), scene = scene, language = Plugin.Language.Value, audio_wav = Convert.ToBase64String(wav) }; object obj2; if (mimicIdentity == null) { obj2 = null; } else { MaskedPlayerEnemy enemy = mimicIdentity.Enemy; obj2 = ((enemy != null) ? ((NetworkBehaviour)enemy).NetworkObjectId.ToString() : null); } if (obj2 == null) { obj2 = "none"; } obj.mimic_id = (string)obj2; obj.copied_player_id = mimicIdentity?.CopiedClientId.ToString() ?? senderId.ToString(); obj.copied_player_name = mimicIdentity?.CopiedName ?? val.playerUsername ?? "crewmate"; RunInference(obj, mimicIdentity); } } private static async Task RunInference(RuntimeRequest request, MimicIdentity mimic) { if (!(await InferenceGate.WaitAsync(0).ConfigureAwait(continueOnCapturedContext: false))) { return; } try { RuntimeResponse runtimeResponse = ((mimic != null) ? (await LocalRuntimeClient.TurnAsync(request).ConfigureAwait(continueOnCapturedContext: false)) : (await LocalRuntimeClient.ObserveAsync(request).ConfigureAwait(continueOnCapturedContext: false))); RuntimeResponse runtimeResponse2 = runtimeResponse; if (!string.IsNullOrWhiteSpace(runtimeResponse2.heard)) { Plugin.Log.LogInfo((object)("Heard " + request.speaker_name + ": " + runtimeResponse2.heard)); } if (mimic != null && !string.IsNullOrEmpty(runtimeResponse2.audio_wav)) { byte[] audio = Convert.FromBase64String(runtimeResponse2.audio_wav); if (audio.Length > 921600) { throw new InvalidOperationException("generated speech exceeded network limit"); } ulong mimicId = ((NetworkBehaviour)mimic.Enemy).NetworkObjectId; Plugin.MainThread.Enqueue(delegate { SpatialVoice.Play(mimicId, audio); BroadcastSpeech(mimicId, audio); }); } } catch (Exception ex) { Plugin.Log.LogWarning((object)("Local mimic inference: " + ex.Message)); } finally { InferenceGate.Release(); } } private static string BasicScene(PlayerControllerB speaker) { string text = StartOfRound.Instance?.currentLevel?.PlanetName ?? "unknown moon"; return "Moon: " + text + ". " + speaker.playerUsername + " is " + (speaker.isInsideFactory ? "inside the facility" : "outside") + "."; } private unsafe static void BroadcastSpeech(ulong mimicId, byte[] wav) { //IL_005e: Unknown result type (might be due to invalid IL or missing references) //IL_0064: Unknown result type (might be due to invalid IL or missing references) //IL_0071: Unknown result type (might be due to invalid IL or missing references) //IL_0077: Unknown result type (might be due to invalid IL or missing references) //IL_0089: Unknown result type (might be due to invalid IL or missing references) //IL_008f: Unknown result type (might be due to invalid IL or missing references) //IL_00a2: Unknown result type (might be due to invalid IL or missing references) //IL_00fe: 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_0111: Unknown result type (might be due to invalid IL or missing references) //IL_0117: Unknown result type (might be due to invalid IL or missing references) //IL_0124: Unknown result type (might be due to invalid IL or missing references) //IL_012a: 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) NetworkManager singleton = NetworkManager.Singleton; if ((Object)(object)singleton == (Object)null || !singleton.IsServer || singleton.CustomMessagingManager == null) { return; } ulong num = _nextTransferId++; FastBufferWriter val = default(FastBufferWriter); FastBufferWriter val2 = default(FastBufferWriter); foreach (ulong connectedClientsId in singleton.ConnectedClientsIds) { if (connectedClientsId == 0L) { continue; } ((FastBufferWriter)(ref val))..ctor(32, (Allocator)2, -1); try { ((FastBufferWriter)(ref val)).WriteValueSafe<ulong>(ref num, default(ForPrimitives)); ((FastBufferWriter)(ref val)).WriteValueSafe<ulong>(ref mimicId, default(ForPrimitives)); int num2 = wav.Length; ((FastBufferWriter)(ref val)).WriteValueSafe<int>(ref num2, default(ForPrimitives)); singleton.CustomMessagingManager.SendNamedMessage("EchoMimics_SpeechStart_v1", connectedClientsId, val, (NetworkDelivery)4); } finally { ((IDisposable)(*(FastBufferWriter*)(&val))/*cast due to .constrained prefix*/).Dispose(); } for (int i = 0; i < wav.Length; i += 7000) { int num3 = Math.Min(7000, wav.Length - i); byte[] array = new byte[num3]; Buffer.BlockCopy(wav, i, array, 0, num3); ((FastBufferWriter)(ref val2))..ctor(num3 + 32, (Allocator)2, -1); try { ((FastBufferWriter)(ref val2)).WriteValueSafe<ulong>(ref num, default(ForPrimitives)); ((FastBufferWriter)(ref val2)).WriteValueSafe<int>(ref i, default(ForPrimitives)); ((FastBufferWriter)(ref val2)).WriteValueSafe<int>(ref num3, default(ForPrimitives)); ((FastBufferWriter)(ref val2)).WriteBytesSafe(array, num3, 0); singleton.CustomMessagingManager.SendNamedMessage("EchoMimics_SpeechChunk_v1", connectedClientsId, val2, (NetworkDelivery)4); } finally { ((IDisposable)(*(FastBufferWriter*)(&val2))/*cast due to .constrained prefix*/).Dispose(); } } } } private static void OnSpeechStart(ulong senderId, FastBufferReader reader) { //IL_0021: 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: Unknown result type (might be due to invalid IL or missing references) //IL_003a: Unknown result type (might be due to invalid IL or missing references) //IL_0047: Unknown result type (might be due to invalid IL or missing references) //IL_004d: Unknown result type (might be due to invalid IL or missing references) NetworkManager singleton = NetworkManager.Singleton; if (!((Object)(object)singleton == (Object)null) && !singleton.IsServer && senderId == 0L) { ulong num = default(ulong); ((FastBufferReader)(ref reader)).ReadValueSafe<ulong>(ref num, default(ForPrimitives)); ulong mimicId = default(ulong); ((FastBufferReader)(ref reader)).ReadValueSafe<ulong>(ref mimicId, default(ForPrimitives)); int num2 = default(int); ((FastBufferReader)(ref reader)).ReadValueSafe<int>(ref num2, default(ForPrimitives)); if (num != 0L && num2 >= 44 && num2 <= 921600) { Incoming[num] = new IncomingSpeech { Data = new byte[num2], MimicId = mimicId, ExpiresAt = Time.unscaledTime + 15f }; } } } private static void OnSpeechChunk(ulong senderId, FastBufferReader reader) { //IL_0021: 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: Unknown result type (might be due to invalid IL or missing references) //IL_003a: Unknown result type (might be due to invalid IL or missing references) //IL_0047: Unknown result type (might be due to invalid IL or missing references) //IL_004d: Unknown result type (might be due to invalid IL or missing references) NetworkManager singleton = NetworkManager.Singleton; if ((Object)(object)singleton == (Object)null || singleton.IsServer || senderId != 0L) { return; } ulong key = default(ulong); ((FastBufferReader)(ref reader)).ReadValueSafe<ulong>(ref key, default(ForPrimitives)); int num = default(int); ((FastBufferReader)(ref reader)).ReadValueSafe<int>(ref num, default(ForPrimitives)); int num2 = default(int); ((FastBufferReader)(ref reader)).ReadValueSafe<int>(ref num2, default(ForPrimitives)); if (Incoming.TryGetValue(key, out var value) && num2 > 0 && num2 <= 7000 && num >= 0 && num + num2 <= value.Data.Length && value.Offsets.Add(num)) { byte[] src = new byte[num2]; ((FastBufferReader)(ref reader)).ReadBytesSafe(ref src, num2, 0); Buffer.BlockCopy(src, 0, value.Data, num, num2); value.Received += num2; if (value.Received == value.Data.Length) { Incoming.Remove(key); SpatialVoice.Play(value.MimicId, value.Data); } } } internal static void ResetSession() { string sessionId = _sessionId; _sessionId = Guid.NewGuid().ToString("N"); Incoming.Clear(); LocalRuntimeClient.ClearSessionAsync(sessionId).ContinueWith(delegate(Task task) { if (task.IsFaulted) { Plugin.Log.LogDebug((object)("Session cleanup skipped: " + task.Exception?.GetBaseException().Message)); } }); } } [BepInPlugin("com.testyee.echomimics", "EchoMimics", "0.1.1")] public sealed class Plugin : BaseUnityPlugin { public const string Guid = "com.testyee.echomimics"; public const string Name = "EchoMimics"; public const string Version = "0.1.1"; internal static ManualLogSource Log; internal static ConfigEntry<bool> Enabled; internal static ConfigEntry<bool> CaptureConsent; internal static ConfigEntry<string> RuntimeUrl; internal static ConfigEntry<string> RuntimeToken; internal static ConfigEntry<string> InputDevice; internal static ConfigEntry<float> VadThreshold; internal static ConfigEntry<float> HearingRange; internal static ConfigEntry<float> ReplyChance; internal static ConfigEntry<float> ReplyCooldown; internal static ConfigEntry<float> VoiceVolume; internal static ConfigEntry<string> Language; internal static readonly ConcurrentQueue<Action> MainThread = new ConcurrentQueue<Action>(); private Harmony _harmony; private void Awake() { //IL_0195: Unknown result type (might be due to invalid IL or missing references) //IL_019f: Expected O, but got Unknown Log = ((BaseUnityPlugin)this).Logger; Enabled = ((BaseUnityPlugin)this).Config.Bind<bool>("General", "Enabled", true, "Enable EchoMimics."); CaptureConsent = ((BaseUnityPlugin)this).Config.Bind<bool>("Privacy", "AllowVoiceCapture", false, "Required opt-in. Captures speech during active rounds for local transcription and voice cloning. Raw clips remain on the host computer."); RuntimeUrl = ((BaseUnityPlugin)this).Config.Bind<string>("Local runtime", "Url", "http://127.0.0.1:17841", "Loopback URL for the local EchoMimics runtime."); RuntimeToken = ((BaseUnityPlugin)this).Config.Bind<string>("Local runtime", "Token", "", "Optional bearer token shared with the local runtime."); InputDevice = ((BaseUnityPlugin)this).Config.Bind<string>("Voice", "InputDevice", "", "Microphone name; blank follows the game's selected input where possible."); VadThreshold = ((BaseUnityPlugin)this).Config.Bind<float>("Voice", "ActivityThreshold", 0.018f, "RMS threshold used to detect speech."); HearingRange = ((BaseUnityPlugin)this).Config.Bind<float>("Mimics", "HearingRange", 32f, "Maximum distance at which a Masked can hear a crewmate."); ReplyChance = ((BaseUnityPlugin)this).Config.Bind<float>("Mimics", "ReplyChance", 0.58f, "Chance that an eligible Masked answers an utterance."); ReplyCooldown = ((BaseUnityPlugin)this).Config.Bind<float>("Mimics", "ReplyCooldownSeconds", 8f, "Minimum time between replies from one Masked."); VoiceVolume = ((BaseUnityPlugin)this).Config.Bind<float>("Voice", "MimicVolume", 1.2f, "Spatial playback volume."); Language = ((BaseUnityPlugin)this).Config.Bind<string>("Voice", "Language", "en", "Whisper language code; blank enables detection."); _harmony = new Harmony("com.testyee.echomimics"); _harmony.PatchAll(); ((Component)this).gameObject.AddComponent<EchoMimicsHost>(); Log.LogInfo((object)string.Format("{0} {1} loaded. Voice capture consent: {2}", "EchoMimics", "0.1.1", CaptureConsent.Value)); } private void OnDestroy() { AmbientCapture.Stop(); Harmony harmony = _harmony; if (harmony != null) { harmony.UnpatchSelf(); } } } internal sealed class EchoMimicsHost : MonoBehaviour { private void Update() { Action result; while (Plugin.MainThread.TryDequeue(out result)) { try { result(); } catch (Exception ex) { Plugin.Log.LogWarning((object)("Main-thread action: " + ex.Message)); } } if (Plugin.Enabled.Value) { MimicTransport.Tick(); AmbientCapture.Tick(); MimicDirector.Tick(); SpatialVoice.Tick(); } } } [HarmonyPatch(typeof(StartOfRound), "ShipLeave")] internal static class ShipLeavePatch { private static void Prefix() { MimicDirector.Reset(); MimicTransport.ResetSession(); SpatialVoice.Reset(); } } [HarmonyPatch(typeof(RoundManager), "DespawnPropsAtEndOfRound")] internal static class RoundEndPatch { private static void Prefix() { AmbientCapture.Stop(); MimicDirector.Reset(); MimicTransport.ResetSession(); SpatialVoice.Reset(); } } internal static class SpatialVoice { private static readonly Dictionary<ulong, AudioSource> Sources = new Dictionary<ulong, AudioSource>(); internal static void Play(ulong mimicId, byte[] wav) { MaskedPlayerEnemy val = MimicDirector.ResolveMimic(mimicId); if (!((Object)(object)val == (Object)null) && AudioCodec.DecodeWav(wav, out var samples, out var sampleRate)) { AudioClip val2 = AudioClip.Create($"EchoMimics-{mimicId}", samples.Length, 1, sampleRate, false); val2.SetData(samples, 0); if (!Sources.TryGetValue(mimicId, out var value) || (Object)(object)value == (Object)null) { value = ((Component)val).gameObject.AddComponent<AudioSource>(); value.spatialBlend = 1f; value.rolloffMode = (AudioRolloffMode)1; value.minDistance = 2f; value.maxDistance = 45f; Sources[mimicId] = value; } value.volume = Mathf.Clamp(Plugin.VoiceVolume.Value, 0f, 2f); value.PlayOneShot(val2); Object.Destroy((Object)(object)val2, val2.length + 1f); } } internal static void Tick() { List<ulong> list = new List<ulong>(); foreach (KeyValuePair<ulong, AudioSource> source in Sources) { if ((Object)(object)source.Value == (Object)null) { list.Add(source.Key); } } foreach (ulong item in list) { Sources.Remove(item); } } internal static void Reset() { Sources.Clear(); } } public static class MyPluginInfo { public const string PLUGIN_GUID = "EchoMimics"; public const string PLUGIN_NAME = "EchoMimics"; public const string PLUGIN_VERSION = "0.1.1"; } }