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 BlackBoxAudioLog v0.1.0
BepInEx/plugins/BlackBoxAudioLog/BlackBoxAudioLog.dll
Decompiled a day agousing System; using System.Collections.Generic; using System.Diagnostics; using System.Linq; using System.Reflection; using System.Runtime.CompilerServices; using System.Runtime.Versioning; using BepInEx; using BepInEx.Configuration; using Dissonance; using Dissonance.Audio.Capture; using GameNetcodeStuff; using HarmonyLib; using LethalLib.Modules; using Microsoft.CodeAnalysis; using NAudio.Wave; 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("BlackBoxAudioLog")] [assembly: AssemblyConfiguration("Release")] [assembly: AssemblyFileVersion("0.1.0.0")] [assembly: AssemblyInformationalVersion("0.1.0+56153eb1e2dca9088104bb612534bcb5ea72f93b")] [assembly: AssemblyProduct("BlackBoxAudioLog")] [assembly: AssemblyTitle("BlackBoxAudioLog")] [assembly: AssemblyVersion("0.1.0.0")] [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 BlackBoxAudioLog { public sealed class Recorder : IMicrophoneSubscriber { public const int Rate = 12000; public const int MaxSeconds = 30; private readonly object gate = new object(); private readonly short[] ring; private int head; private int count; private double phase; public volatile bool Enabled; public volatile bool Speaking; public Recorder(int seconds) { ring = new short[Math.Clamp(seconds, 5, 30) * 12000]; } public void ReceiveMicrophoneData(ArraySegment<float> buffer, WaveFormat format) { if (!Enabled || buffer.Array == null || format.Channels < 1 || format.SampleRate < 12000) { return; } lock (gate) { for (int i = 0; i + format.Channels <= buffer.Count; i += format.Channels) { phase += 12000.0; if (phase < (double)format.SampleRate) { continue; } phase -= format.SampleRate; float num = 0f; if (Speaking) { for (int j = 0; j < format.Channels; j++) { num += buffer.Array[buffer.Offset + i + j] / (float)format.Channels; } } ring[head] = (short)(Math.Clamp(num, -1f, 1f) * 32767f); head = (head + 1) % ring.Length; count = Math.Min(count + 1, ring.Length); } } } public byte[] Snapshot() { lock (gate) { byte[] array = new byte[count * 2]; for (int i = 0; i < count; i++) { short num = ring[(head - count + ring.Length + i) % ring.Length]; array[i * 2] = (byte)num; array[i * 2 + 1] = (byte)(num >> 8); } return array; } } public void Reset() { lock (gate) { Array.Clear(ring, 0, ring.Length); head = (count = 0); phase = 0.0; } } } public static class TapeAudio { public static float[] Render(byte[] bytes) { int num = 12000; int num2 = 3000; float[] array = new float[num + bytes.Length / 2 + num2]; Random random = new Random(8128); for (int i = 0; i < num; i++) { array[i] = (float)(random.NextDouble() * 2.0 - 1.0) * ((i < 120) ? 0.4f : 0.045f); } float num3 = 0f; float num4 = 0f; for (int j = 0; j < bytes.Length / 2; j++) { float num5 = (float)(short)(bytes[j * 2] | (bytes[j * 2 + 1] << 8)) / 32768f; num4 += 0.09f * (num5 - num4); num3 += 0.55f * (num5 - num4 - num3); array[num + j] = Math.Clamp(num3 * 1.7f, -0.8f, 0.8f) + (float)(random.NextDouble() * 2.0 - 1.0) * 0.006f; } for (int k = 0; k < num2; k++) { array[array.Length - num2 + k] = (float)(random.NextDouble() * 2.0 - 1.0) * 0.22f * (1f - (float)k / (float)num2); } return array; } } public sealed class ChunkAssembly { public const int ChunkSize = 4096; public const int MaxBytes = 720000; private readonly byte[][] parts; private int received; public int Length { get; } public ChunkAssembly(int length) { if (length < 0 || length > 720000 || length % 2 != 0) { throw new ArgumentOutOfRangeException("length"); } Length = length; parts = new byte[(length + 4096 - 1) / 4096][]; } public bool Add(int index, byte[] bytes) { if (index < 0 || index >= parts.Length || bytes.Length != Math.Min(4096, Length - index * 4096)) { return false; } if (parts[index] == null) { parts[index] = bytes; received++; } return true; } public byte[]? Complete() { if (received != parts.Length) { return null; } byte[] array = new byte[Length]; for (int i = 0; i < parts.Length; i++) { Buffer.BlockCopy(parts[i], 0, array, i * 4096, parts[i].Length); } return array; } } public sealed class Cassette : MonoBehaviour { public AudioSource Speaker; public double LastStart = -1.0; public AudioClip? Clip; private void Awake() { Speaker = ((Component)this).gameObject.AddComponent<AudioSource>(); Speaker.playOnAwake = false; Speaker.spatialBlend = 1f; Speaker.minDistance = 1.5f; Speaker.maxDistance = 18f; Speaker.rolloffMode = (AudioRolloffMode)1; Speaker.dopplerLevel = 0f; } public void Apply(TapeState state, byte[]? audio, double time) { ((Component)this).GetComponent<GrabbableObject>().SetScrapValue(state.Value); ScanNodeProperties componentInChildren = ((Component)this).GetComponentInChildren<ScanNodeProperties>(); componentInChildren.headerText = "Black box: " + state.Name; componentInChildren.subText = ((audio == null) ? "Recovering recording..." : "Final transmission"); if (state.Start <= 0.0) { Speaker.Stop(); LastStart = -1.0; } else if (audio != null && LastStart != state.Start) { LastStart = state.Start; if (!Object.op_Implicit((Object)(object)Clip)) { float[] array = TapeAudio.Render(audio); Clip = AudioClip.Create("Black box recording", array.Length, 1, 12000, false); Clip.SetData(array, 0); } Speaker.clip = Clip; Speaker.volume = Plugin.Instance.Volume.Value; double num = Math.Max(0.0, time - state.Start); if (num < (double)Clip.length) { Speaker.time = (float)num; Speaker.Play(); Plugin.Instance.Log("Spatial playback started for tape " + state.Id + " at " + num.ToString("F2") + "s; clip=" + Clip.length + "s."); } } } private void OnDestroy() { if (Object.op_Implicit((Object)(object)Clip)) { Object.Destroy((Object)(object)Clip); } } } public static class Content { public static Item Tape; private static Material? shell; private static Material? label; private static Material? reel; public static void Create() { //IL_011d: Unknown result type (might be due to invalid IL or missing references) //IL_0122: Unknown result type (might be due to invalid IL or missing references) //IL_013b: Unknown result type (might be due to invalid IL or missing references) //IL_0140: 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_01fb: Unknown result type (might be due to invalid IL or missing references) //IL_0219: Unknown result type (might be due to invalid IL or missing references) //IL_0237: Unknown result type (might be due to invalid IL or missing references) //IL_0251: Unknown result type (might be due to invalid IL or missing references) //IL_0265: Unknown result type (might be due to invalid IL or missing references) //IL_028e: Unknown result type (might be due to invalid IL or missing references) //IL_02a2: Unknown result type (might be due to invalid IL or missing references) //IL_02d4: Unknown result type (might be due to invalid IL or missing references) //IL_02e8: Unknown result type (might be due to invalid IL or missing references) //IL_031a: Unknown result type (might be due to invalid IL or missing references) //IL_031f: Unknown result type (might be due to invalid IL or missing references) //IL_0331: Unknown result type (might be due to invalid IL or missing references) //IL_0339: Unknown result type (might be due to invalid IL or missing references) //IL_0340: Unknown result type (might be due to invalid IL or missing references) //IL_037e: Unknown result type (might be due to invalid IL or missing references) //IL_0384: Expected O, but got Unknown //IL_0436: Unknown result type (might be due to invalid IL or missing references) //IL_0445: Unknown result type (might be due to invalid IL or missing references) //IL_03ba: Unknown result type (might be due to invalid IL or missing references) //IL_03f1: Unknown result type (might be due to invalid IL or missing references) //IL_03ec: Unknown result type (might be due to invalid IL or missing references) //IL_03d6: Unknown result type (might be due to invalid IL or missing references) if (Object.op_Implicit((Object)(object)Tape)) { return; } Tape = ScriptableObject.CreateInstance<Item>(); ((Object)Tape).name = "BlackBoxAudioLogCassette"; Tape.itemName = "Black box tape"; Tape.isScrap = true; Tape.minValue = 30; Tape.maxValue = 51; Tape.weight = 1.02f; Tape.requiresBattery = false; Tape.canBeGrabbedBeforeGameStart = true; Tape.grabAnim = "HoldLung"; Tape.useAnim = (Tape.pocketAnim = (Tape.throwAnim = "")); Tape.toolTips = new string[1] { "Play / stop tape : [LMB]" }; Tape.syncUseFunction = false; Tape.meshVariants = Array.Empty<Mesh>(); Tape.materialVariants = Array.Empty<Material>(); Tape.clinkAudios = Array.Empty<AudioClip>(); Tape.verticalOffset = 0.04f; Tape.restingRotation = Vector3.zero; Tape.positionOffset = new Vector3(0f, 0.02f, 0.04f); GameObject val = NetworkPrefabs.CreateNetworkPrefab(((Object)Tape).name); Object.DontDestroyOnLoad((Object)(object)((Component)val.transform.root).gameObject); ((Object)val).hideFlags = (HideFlags)0; val.tag = "PhysicsProp"; val.layer = 6; PhysicsProp val2 = val.AddComponent<PhysicsProp>(); ((GrabbableObject)val2).itemProperties = Tape; ((GrabbableObject)val2).grabbable = true; ((GrabbableObject)val2).grabbableToEnemies = false; ((GrabbableObject)val2).propBody = val.AddComponent<Rigidbody>(); ((GrabbableObject)val2).propBody.isKinematic = true; BoxCollider val3 = val.AddComponent<BoxCollider>(); val3.size = new Vector3(0.28f, 0.065f, 0.18f); ((GrabbableObject)val2).propColliders = (Collider[])(object)new Collider[1] { (Collider)val3 }; shell = Material(new Color(0.075f, 0.085f, 0.075f)); label = Material(new Color(0.7f, 0.44f, 0.12f)); reel = Material(new Color(0.25f, 0.27f, 0.24f)); Part(val.transform, "Recorder shell", Vector3.zero, new Vector3(0.28f, 0.06f, 0.18f), shell); Part(val.transform, "Company label", new Vector3(0f, 0.031f, 0.046f), new Vector3(0.24f, 0.004f, 0.05f), label); for (int i = -1; i <= 1; i += 2) { Part(val.transform, "Tape reel", new Vector3((float)i * 0.065f, 0.034f, -0.025f), new Vector3(0.055f, 0.008f, 0.055f), reel); } val.AddComponent<Cassette>(); ((GrabbableObject)val2).mainObjectRenderer = val.GetComponentInChildren<MeshRenderer>(); GameObject val4 = new GameObject("ScanNode"); val4.transform.SetParent(val.transform, false); val4.layer = 22; val4.AddComponent<BoxCollider>().size = val3.size; ScanNodeProperties obj = val4.AddComponent<ScanNodeProperties>(); obj.headerText = "Black box tape"; obj.subText = "Final transmission"; obj.nodeType = 2; obj.maxRange = 13; obj.minRange = 1; Texture2D val5 = new Texture2D(32, 32); Color[] array = (Color[])(object)new Color[1024]; for (int j = 0; j < 32; j++) { for (int k = 0; k < 32; k++) { array[j * 32 + k] = (Color)((j > 6 && j < 26 && k > 2 && k < 30) ? ((j > 19) ? new Color(0.8f, 0.5f, 0.12f) : new Color(0.2f, 0.22f, 0.2f)) : Color.clear); } } val5.SetPixels(array); val5.Apply(); Tape.itemIcon = Sprite.Create(val5, new Rect(0f, 0f, 32f, 32f), new Vector2(0.5f, 0.5f)); Tape.spawnPrefab = val; Items.RegisterItem(Tape); Plugin.Instance.Log("Cassette prefab registered."); } private static Material Material(Color c) { //IL_0018: 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_001e: Unknown result type (might be due to invalid IL or missing references) //IL_0025: Expected O, but got Unknown return new Material(Shader.Find("HDRP/Lit") ?? Shader.Find("Standard")) { color = c }; } private static void Part(Transform parent, string name, Vector3 pos, Vector3 scale, Material material) { //IL_0020: 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) GameObject obj = GameObject.CreatePrimitive((PrimitiveType)3); ((Object)obj).name = name; obj.transform.SetParent(parent, false); obj.transform.localPosition = pos; obj.transform.localScale = scale; obj.layer = 6; Object.Destroy((Object)(object)obj.GetComponent<Collider>()); obj.GetComponent<Renderer>().sharedMaterial = material; } public static void Cleanup() { if (Object.op_Implicit((Object)(object)Tape)) { Object.Destroy((Object)(object)Tape.spawnPrefab); if (Object.op_Implicit((Object)(object)Tape.itemIcon)) { Object.Destroy((Object)(object)Tape.itemIcon.texture); Object.Destroy((Object)(object)Tape.itemIcon); } Object.Destroy((Object)(object)Tape); Object.Destroy((Object)(object)shell); Object.Destroy((Object)(object)label); Object.Destroy((Object)(object)reel); } } } [BepInPlugin("blackbox.audiolog", "Black Box Audio Log", "0.1.0")] [BepInDependency("evaisa.lethallib", "1.2.0")] public sealed class Plugin : BaseUnityPlugin { public const string Id = "blackbox.audiolog"; public static Plugin Instance; public ConfigEntry<bool> Record; public ConfigEntry<bool> AttractEnemies; public ConfigEntry<int> Seconds; public ConfigEntry<int> MinValue; public ConfigEntry<int> MaxValue; public ConfigEntry<int> TapeLimit; public ConfigEntry<float> Volume; public Session Session; private Harmony patches; private void Awake() { //IL_0064: Unknown result type (might be due to invalid IL or missing references) //IL_006e: Expected O, but got Unknown //IL_009b: Unknown result type (might be due to invalid IL or missing references) //IL_00a5: Expected O, but got Unknown //IL_00d2: Unknown result type (might be due to invalid IL or missing references) //IL_00dc: Expected O, but got Unknown //IL_0106: Unknown result type (might be due to invalid IL or missing references) //IL_0110: Expected O, but got Unknown //IL_0165: Unknown result type (might be due to invalid IL or missing references) //IL_016f: Expected O, but got Unknown //IL_0186: Unknown result type (might be due to invalid IL or missing references) //IL_0190: Expected O, but got Unknown Instance = this; ((Object)((Component)this).gameObject).hideFlags = (HideFlags)61; Object.DontDestroyOnLoad((Object)(object)((Component)this).gameObject); Record = ((BaseUnityPlugin)this).Config.Bind<bool>("Recording", "Enabled", true, "Record local transmitted microphone audio in memory and share final moments with the lobby on death. False produces static-only tapes. Never writes audio to disk."); Seconds = ((BaseUnityPlugin)this).Config.Bind<int>("Recording", "Seconds", 20, new ConfigDescription("Rolling buffer duration; restart required.", (AcceptableValueBase)(object)new AcceptableValueRange<int>(5, 30), Array.Empty<object>())); MinValue = ((BaseUnityPlugin)this).Config.Bind<int>("Host", "MinimumValue", 30, new ConfigDescription("Minimum tape scrap value.", (AcceptableValueBase)(object)new AcceptableValueRange<int>(0, 1000), Array.Empty<object>())); MaxValue = ((BaseUnityPlugin)this).Config.Bind<int>("Host", "MaximumValue", 50, new ConfigDescription("Maximum tape scrap value.", (AcceptableValueBase)(object)new AcceptableValueRange<int>(0, 1000), Array.Empty<object>())); TapeLimit = ((BaseUnityPlugin)this).Config.Bind<int>("Host", "MaximumTapes", 16, new ConfigDescription("Maximum live tapes; additional deaths do not create tapes until space is available.", (AcceptableValueBase)(object)new AcceptableValueRange<int>(1, 32), Array.Empty<object>())); AttractEnemies = ((BaseUnityPlugin)this).Config.Bind<bool>("Host", "AttractEnemies", true, "Playback emits audible noise for hearing-sensitive enemies."); Volume = ((BaseUnityPlugin)this).Config.Bind<float>("Playback", "Volume", 0.8f, new ConfigDescription("Local playback volume.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0f, 1f), Array.Empty<object>())); Session = new Session(this); patches = new Harmony("blackbox.audiolog"); patches.PatchAll(); Log("Initialized. Recording=" + Record.Value + "; microphone audio remains in session memory."); } private void Start() { Content.Create(); } private void Update() { Session.Tick(); } public void Log(string message) { ((BaseUnityPlugin)this).Logger.LogInfo((object)message); } public void Error(Exception e) { ((BaseUnityPlugin)this).Logger.LogError((object)e); } private void OnDestroy() { Session?.Dispose(); Harmony obj = patches; if (obj != null) { obj.UnpatchSelf(); } Content.Cleanup(); } } [HarmonyPatch(typeof(GameNetworkManager), "Start")] internal static class InitializePatch { private static void Prefix() { Content.Create(); } } [HarmonyPatch(typeof(GrabbableObject), "ItemActivate")] internal static class ActivatePatch { private static void Postfix(GrabbableObject __instance, bool buttonDown) { if (buttonDown && ((NetworkBehaviour)__instance).IsOwner && Object.op_Implicit((Object)(object)((Component)__instance).GetComponent<Cassette>())) { Plugin.Instance.Session.RequestPlay(((NetworkBehaviour)__instance).NetworkObjectId); } } } public sealed class TapeState { public ulong Id; public ulong Owner; public string Name = "Unknown"; public int Value; public int Length = -1; public double Start; public double Created; } public sealed class Message { public string Kind = ""; public ulong Id; public int Index; public int Length; public byte[]? Data; public TapeState[]? Tapes; } public sealed class Session : IDisposable { private const string Channel = "blackbox/audio/1"; private readonly Plugin plugin; public readonly Recorder Recorder; private NetworkManager? manager; private DissonanceComms? comms; private readonly Dictionary<ulong, TapeState> tapes = new Dictionary<ulong, TapeState>(); private readonly Dictionary<ulong, byte[]> audio = new Dictionary<ulong, byte[]>(); private readonly Dictionary<ulong, ChunkAssembly> partial = new Dictionary<ulong, ChunkAssembly>(); private readonly Dictionary<ulong, bool> dead = new Dictionary<ulong, bool>(); private readonly Dictionary<ulong, Vector3> lastPosition = new Dictionary<ulong, Vector3>(); private readonly Dictionary<ulong, ulong> deathTapes = new Dictionary<ulong, ulong>(); private readonly Dictionary<ulong, double> requests = new Dictionary<ulong, double>(); private readonly Dictionary<(ulong, string, ulong), double> rate = new Dictionary<(ulong, string, ulong), double>(); private readonly Queue<(ulong, Message)> outgoing = new Queue<(ulong, Message)>(); private readonly HashSet<(ulong, ulong)> transfers = new HashSet<(ulong, ulong)>(); private byte[]? pending; private bool localDead; private double nextState; private double nextNoise; private float sendBudget; private float nextVoiceCheck; private readonly Collider[] noiseHits = (Collider[])(object)new Collider[128]; private bool Host { get { if ((Object)(object)manager != (Object)null) { return manager.IsServer; } return false; } } private double Now { get { //IL_001e: 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) if (!((Object)(object)manager != (Object)null)) { return 0.0; } NetworkTime serverTime = manager.ServerTime; return ((NetworkTime)(ref serverTime)).Time; } } public Session(Plugin p) { plugin = p; Recorder = new Recorder(p.Seconds.Value); } public void Tick() { //IL_006c: Unknown result type (might be due to invalid IL or missing references) //IL_0076: Expected O, but got Unknown NetworkManager val = NetworkManager.Singleton; if (Object.op_Implicit((Object)(object)val) && (!val.IsListening || val.CustomMessagingManager == null)) { val = null; } if ((Object)(object)val != (Object)(object)manager) { Dispose(); manager = val; if (Object.op_Implicit((Object)(object)manager)) { manager.CustomMessagingManager.RegisterNamedMessageHandler("blackbox/audio/1", new HandleNamedMessageDelegate(Receive)); plugin.Log("Session transport registered."); } } if (!Object.op_Implicit((Object)(object)manager) || !Object.op_Implicit((Object)(object)StartOfRound.Instance)) { return; } PlayerControllerB local = GameNetworkManager.Instance?.localPlayerController; DissonanceComms val2 = comms; if (Time.unscaledTime >= nextVoiceCheck) { nextVoiceCheck = Time.unscaledTime + 1f; val2 = Object.FindObjectOfType<DissonanceComms>(); } if ((Object)(object)val2 != (Object)(object)comms) { if (Object.op_Implicit((Object)(object)comms)) { comms.UnsubscribeFromRecordedAudio((IMicrophoneSubscriber)(object)Recorder); } Recorder.Reset(); comms = val2; if (Object.op_Implicit((Object)(object)comms)) { comms.SubscribeToRecordedAudio((IMicrophoneSubscriber)(object)Recorder); plugin.Log("Subscribed to microphone recording stream."); } } bool flag = Object.op_Implicit((Object)(object)local) && local.isPlayerDead; if (flag && !localDead) { pending = (plugin.Record.Value ? Recorder.Snapshot() : Array.Empty<byte>()); Recorder.Reset(); } if (!flag && localDead) { pending = null; Recorder.Reset(); } localDead = flag; Recorder.Enabled = plugin.Record.Value && Object.op_Implicit((Object)(object)local) && local.isPlayerControlled && !flag; Recorder.Speaking = Recorder.Enabled && Object.op_Implicit((Object)(object)comms) && !comms.IsMuted && ((Channels<RoomChannel, string>)(object)comms.RoomChannels).Count + ((Channels<PlayerChannel, string>)(object)comms.PlayerChannels).Count > 0; if (!plugin.Record.Value) { Recorder.Reset(); } if (Host) { HostTick(); } if (pending != null && Object.op_Implicit((Object)(object)local)) { TapeState tapeState = tapes.Values.FirstOrDefault((TapeState t) => t.Owner == local.actualClientId && t.Length < 0 && Now - t.Created < 15.0); if (tapeState != null) { if (Host) { FinishAudio(tapeState.Id, pending); } else { QueueAudio(0uL, "upload", tapeState.Id, pending); } pending = null; } } foreach (TapeState value4 in tapes.Values) { if (manager.SpawnManager.SpawnedObjects.TryGetValue(value4.Id, out var value)) { Cassette component = ((Component)value).GetComponent<Cassette>(); if (Object.op_Implicit((Object)(object)component)) { component.Apply(value4, audio.TryGetValue(value4.Id, out byte[] value2) ? value2 : null, Now); } } if (!Host && value4.Length >= 0 && !audio.ContainsKey(value4.Id) && (!requests.TryGetValue(value4.Id, out var value3) || Now - value3 > 20.0)) { requests[value4.Id] = Now; Send(0uL, new Message { Kind = "fetch", Id = value4.Id }); } } sendBudget = Math.Min(sendBudget + Time.unscaledDeltaTime * 24f, 4f); while (sendBudget >= 1f && outgoing.Count > 0) { (ulong, Message) tuple = outgoing.Dequeue(); sendBudget -= 1f; if (manager.ConnectedClientsIds.Contains(tuple.Item1) || !Host) { Send(tuple.Item1, tuple.Item2); } if (tuple.Item2.Length == 0 || (tuple.Item2.Index + 1) * 4096 >= tuple.Item2.Length) { transfers.Remove((tuple.Item1, tuple.Item2.Id)); } } } private void HostTick() { //IL_0230: Unknown result type (might be due to invalid IL or missing references) //IL_0582: Unknown result type (might be due to invalid IL or missing references) //IL_009d: Unknown result type (might be due to invalid IL or missing references) //IL_0096: Unknown result type (might be due to invalid IL or missing references) //IL_0619: 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) //IL_00ab: 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_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_00c1: Unknown result type (might be due to invalid IL or missing references) PlayerControllerB[] allPlayerScripts = StartOfRound.Instance.allPlayerScripts; foreach (PlayerControllerB val in allPlayerScripts) { if (Object.op_Implicit((Object)(object)val) && (val.isPlayerControlled || val.isPlayerDead)) { ulong actualClientId = val.actualClientId; bool value; bool flag = dead.TryGetValue(actualClientId, out value) && value; if (val.isPlayerDead && !flag && tapes.Count < plugin.TapeLimit.Value) { Vector3 value2; Vector3 val2 = (lastPosition.TryGetValue(actualClientId, out value2) ? value2 : ((Component)val).transform.position); GameObject obj = Object.Instantiate<GameObject>(Content.Tape.spawnPrefab, val2 + Vector3.up * 0.1f, Quaternion.identity); obj.GetComponent<NetworkObject>().Spawn(false); GrabbableObject component = obj.GetComponent<GrabbableObject>(); int num = Random.Range(plugin.MinValue.Value, Math.Max(plugin.MinValue.Value, plugin.MaxValue.Value) + 1); component.SetScrapValue(num); component.isInFactory = val.isInsideFactory; TapeState tapeState = new TapeState { Id = ((NetworkBehaviour)component).NetworkObjectId, Owner = actualClientId, Name = ((val.playerUsername.Length > 32) ? val.playerUsername.Substring(0, 32) : val.playerUsername), Value = num, Created = Now }; tapes[tapeState.Id] = tapeState; deathTapes[actualClientId] = tapeState.Id; plugin.Log("Spawned death tape " + tapeState.Id + " for " + tapeState.Name + " ($" + num + ")."); nextState = 0.0; } if (!val.isPlayerDead) { lastPosition[actualClientId] = ((Component)val).transform.position; deathTapes.Remove(actualClientId); } dead[actualClientId] = val.isPlayerDead; } } Cassette[] array = Object.FindObjectsOfType<Cassette>(); for (int i = 0; i < array.Length; i++) { GrabbableObject component2 = ((Component)array[i]).GetComponent<GrabbableObject>(); if (((NetworkBehaviour)component2).IsSpawned && !tapes.ContainsKey(((NetworkBehaviour)component2).NetworkObjectId) && tapes.Count < plugin.TapeLimit.Value) { tapes[((NetworkBehaviour)component2).NetworkObjectId] = new TapeState { Id = ((NetworkBehaviour)component2).NetworkObjectId, Value = component2.scrapValue, Name = "Recovered", Created = Now, Length = 0 }; audio[((NetworkBehaviour)component2).NetworkObjectId] = Array.Empty<byte>(); } } TapeState[] array2 = tapes.Values.ToArray(); foreach (TapeState tapeState2 in array2) { if (!manager.SpawnManager.SpawnedObjects.ContainsKey(tapeState2.Id)) { tapes.Remove(tapeState2.Id); audio.Remove(tapeState2.Id); partial.Remove(tapeState2.Id); requests.Remove(tapeState2.Id); continue; } if (tapeState2.Length < 0 && Now - tapeState2.Created > 15.0) { FinishAudio(tapeState2.Id, Array.Empty<byte>()); } if (tapeState2.Start > 0.0 && tapeState2.Length >= 0 && Now - tapeState2.Start > 1.25 + (double)tapeState2.Length / 24000.0) { tapeState2.Start = 0.0; } } if (Now >= nextState) { nextState = Now + 2.0; Broadcast(new Message { Kind = "state", Tapes = tapes.Values.ToArray() }); } if (!(Now >= nextNoise)) { return; } nextNoise = Now + 1.0; if (!plugin.AttractEnemies.Value || !Object.op_Implicit((Object)(object)RoundManager.Instance)) { return; } INoiseListener val3 = default(INoiseListener); foreach (TapeState item in tapes.Values.Where((TapeState t) => t.Start > 0.0)) { if (!manager.SpawnManager.SpawnedObjects.TryGetValue(item.Id, out var value3)) { continue; } bool flag2 = ((Component)value3).GetComponent<GrabbableObject>().isInShipRoom && StartOfRound.Instance.hangarDoorsClosed; int num2 = Physics.OverlapSphereNonAlloc(((Component)value3).transform.position, (float)(flag2 ? 9 : 18), noiseHits, 8912896); HashSet<INoiseListener> hashSet = new HashSet<INoiseListener>(); for (int num3 = 0; num3 < num2; num3++) { try { if (Object.op_Implicit((Object)(object)noiseHits[num3]) && ((Component)((Component)noiseHits[num3]).transform).TryGetComponent<INoiseListener>(ref val3) && hashSet.Add(val3)) { EnemyAI component3 = ((Component)noiseHits[num3]).GetComponent<EnemyAI>(); if (!flag2 || (Object.op_Implicit((Object)(object)component3) && component3.isInsidePlayerShip)) { val3.DetectNoise(((Component)value3).transform.position, 0.65f, 0, 0); } } } catch (Exception e) { plugin.Error(e); } finally { noiseHits[num3] = null; } } } } public void RequestPlay(ulong id) { if (Object.op_Implicit((Object)(object)manager)) { if (Host) { Play(manager.LocalClientId, id); return; } Send(0uL, new Message { Kind = "play", Id = id }); } } private void Play(ulong sender, ulong id) { if (tapes.TryGetValue(id, out TapeState value) && value.Length >= 0 && Allowed(sender, "play", id, 0.5) && manager.SpawnManager.SpawnedObjects.TryGetValue(id, out var value2)) { GrabbableObject component = ((Component)value2).GetComponent<GrabbableObject>(); if (Object.op_Implicit((Object)(object)component.playerHeldBy) && component.playerHeldBy.actualClientId == sender && !component.playerHeldBy.isPlayerDead) { value.Start = ((value.Start > 0.0) ? 0.0 : Now); Broadcast(new Message { Kind = "state", Tapes = tapes.Values.ToArray() }); plugin.Log("Tape " + id + ((value.Start > 0.0) ? " playing." : " stopped.")); } } } private bool Allowed(ulong sender, string kind, ulong id, double interval) { (ulong, string, ulong) key = (sender, kind, id); if (rate.TryGetValue(key, out var value) && Now - value < interval) { return false; } rate[key] = Now; return true; } private void Receive(ulong sender, FastBufferReader reader) { if (((FastBufferReader)(ref reader)).Length > 16000) { return; } try { string text = default(string); ((FastBufferReader)(ref reader)).ReadValueSafe(ref text, false); Message message = JsonConvert.DeserializeObject<Message>(text); if (message == null) { return; } if (Host) { byte[] value; ulong value2; TapeState value3; if (message.Kind == "play") { Play(sender, message.Id); } else if (message.Kind == "fetch" && audio.TryGetValue(message.Id, out value) && Allowed(sender, "fetch", message.Id, 15.0) && outgoing.Count < 4096) { QueueAudio(sender, "audio", message.Id, value); } else if (message.Kind == "upload" && deathTapes.TryGetValue(sender, out value2) && value2 == message.Id && tapes.TryGetValue(message.Id, out value3) && value3.Length < 0 && Now - value3.Created < 15.0) { ReceiveAudio(message); } } else { if (sender != 0L) { return; } TapeState value4; if (message.Kind == "state" && message.Tapes != null && message.Tapes.Length <= 32) { HashSet<ulong> keep = new HashSet<ulong>(message.Tapes.Select((TapeState t) => t.Id)); ulong[] array = tapes.Keys.Where((ulong id) => !keep.Contains(id)).ToArray(); foreach (ulong key in array) { tapes.Remove(key); audio.Remove(key); partial.Remove(key); requests.Remove(key); } TapeState[] array2 = message.Tapes; foreach (TapeState tapeState in array2) { if (tapeState.Length >= -1 && tapeState.Length <= 720000) { tapes[tapeState.Id] = tapeState; } } } else if (message.Kind == "audio" && tapes.TryGetValue(message.Id, out value4) && value4.Length == message.Length) { ReceiveAudio(message); } } } catch (Exception e) { plugin.Error(e); } } private void ReceiveAudio(Message m) { if (audio.ContainsKey(m.Id) || m.Length < 0 || m.Length > 720000 || m.Length % 2 != 0) { return; } if (m.Length == 0) { FinishAudio(m.Id, Array.Empty<byte>()); } else { if (m.Data == null || m.Data.Length > 4096) { return; } if (!partial.TryGetValue(m.Id, out ChunkAssembly value)) { value = (partial[m.Id] = new ChunkAssembly(m.Length)); } if (value.Length == m.Length && value.Add(m.Index, m.Data)) { byte[] array = value.Complete(); if (array != null) { FinishAudio(m.Id, array); } } } } private void FinishAudio(ulong id, byte[] bytes) { audio[id] = bytes; partial.Remove(id); if (tapes.TryGetValue(id, out TapeState value)) { value.Length = bytes.Length; } if (Host) { nextState = 0.0; } plugin.Log("Tape " + id + " audio ready: " + bytes.Length + " bytes."); } private void QueueAudio(ulong target, string kind, ulong id, byte[] bytes) { if (!transfers.Add((target, id))) { return; } if (bytes.Length == 0) { outgoing.Enqueue((target, new Message { Kind = kind, Id = id, Length = 0 })); return; } int num = 0; int num2 = 0; while (num < bytes.Length) { byte[] array = new byte[Math.Min(4096, bytes.Length - num)]; Buffer.BlockCopy(bytes, num, array, 0, array.Length); outgoing.Enqueue((target, new Message { Kind = kind, Id = id, Length = bytes.Length, Index = num2, Data = array })); num += 4096; num2++; } } private void Broadcast(Message m) { foreach (ulong connectedClientsId in manager.ConnectedClientsIds) { if (connectedClientsId != manager.LocalClientId) { Send(connectedClientsId, m); } } } private unsafe void Send(ulong target, Message m) { //IL_0050: Unknown result type (might be due to invalid IL or missing references) if (!Object.op_Implicit((Object)(object)manager) || !manager.IsListening) { return; } string text = JsonConvert.SerializeObject((object)m); FastBufferWriter val = default(FastBufferWriter); ((FastBufferWriter)(ref val))..ctor(text.Length * 4 + 16, (Allocator)2, -1); try { ((FastBufferWriter)(ref val)).WriteValueSafe(text, false); manager.CustomMessagingManager.SendNamedMessage("blackbox/audio/1", target, val, (NetworkDelivery)4); } finally { ((IDisposable)(*(FastBufferWriter*)(&val))/*cast due to .constrained prefix*/).Dispose(); } } public void Dispose() { Recorder.Enabled = false; if (Object.op_Implicit((Object)(object)comms)) { comms.UnsubscribeFromRecordedAudio((IMicrophoneSubscriber)(object)Recorder); } if (Object.op_Implicit((Object)(object)manager) && manager.CustomMessagingManager != null) { manager.CustomMessagingManager.UnregisterNamedMessageHandler("blackbox/audio/1"); } Cassette[] array = Object.FindObjectsOfType<Cassette>(); foreach (Cassette cassette in array) { if (Object.op_Implicit((Object)(object)cassette.Speaker)) { cassette.Speaker.Stop(); } if (Object.op_Implicit((Object)(object)cassette.Clip)) { Object.Destroy((Object)(object)cassette.Clip); } cassette.LastStart = -1.0; } manager = null; comms = null; Recorder.Reset(); tapes.Clear(); audio.Clear(); partial.Clear(); dead.Clear(); lastPosition.Clear(); deathTapes.Clear(); requests.Clear(); rate.Clear(); outgoing.Clear(); transfers.Clear(); pending = null; localDead = false; nextState = (nextNoise = 0.0); sendBudget = 0f; } } }