Decompiled source of PositionalAudio v2.0.0

OpenPA-2.0.0.dll

Decompiled 3 months ago
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.IO.MemoryMappedFiles;
using System.Linq;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Runtime.Versioning;
using System.Security;
using System.Security.Permissions;
using System.Text;
using System.Threading;
using Agents;
using BepInEx;
using BepInEx.Configuration;
using BepInEx.Core.Logging.Interpolation;
using BepInEx.Logging;
using BepInEx.Unity.IL2CPP;
using GTFO.API;
using Microsoft.CodeAnalysis;
using Player;
using SNetwork;
using UnityEngine;
using mumblelib;

[assembly: CompilationRelaxations(8)]
[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
[assembly: Debuggable(DebuggableAttribute.DebuggingModes.Default | DebuggableAttribute.DebuggingModes.DisableOptimizations | DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints | DebuggableAttribute.DebuggingModes.EnableEditAndContinue)]
[assembly: TargetFramework(".NETCoreApp,Version=v6.0", FrameworkDisplayName = ".NET 6.0")]
[assembly: AssemblyCompany("OpenPA")]
[assembly: AssemblyConfiguration("Debug")]
[assembly: AssemblyFileVersion("1.0.0.0")]
[assembly: AssemblyInformationalVersion("1.0.0")]
[assembly: AssemblyProduct("OpenPA")]
[assembly: AssemblyTitle("OpenPA")]
[assembly: SecurityPermission(SecurityAction.RequestMinimum, SkipVerification = true)]
[assembly: AssemblyVersion("1.0.0.0")]
[module: UnverifiableCode]
namespace Microsoft.CodeAnalysis
{
	[CompilerGenerated]
	[Microsoft.CodeAnalysis.Embedded]
	internal sealed class EmbeddedAttribute : Attribute
	{
	}
}
namespace System.Runtime.CompilerServices
{
	[CompilerGenerated]
	[Microsoft.CodeAnalysis.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]
	[Microsoft.CodeAnalysis.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;
		}
	}
}
namespace PositionalAudio
{
	public class OpenPAConfig
	{
		public static ConfigEntry<bool> configTalkState;

		public static ConfigEntry<bool> configVerbose;

		public static ConfigEntry<int> configIntensity { get; set; }
	}
	[BepInPlugin("net.devante.gtfo.positionalaudio", "PositionalAudio", "2.0.0")]
	public class Plugin : BasePlugin
	{
		public struct ClientCharID
		{
			public int PlayerID;
		}

		[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
		public struct ClientSendData
		{
			public int clientSlot;

			public bool clientStatus;
		}

		public interface LinkFileFactory
		{
			LinkFile Open();
		}

		public interface LinkFile : IDisposable
		{
			uint UIVersion { set; }

			Vector3 CharacterPosition { set; }

			Vector3 CharacterForward { set; }

			Vector3 CharacterTop { set; }

			string Name { set; }

			Vector3 CameraPosition { set; }

			Vector3 CameraForward { set; }

			Vector3 CameraTop { set; }

			string ID { set; }

			string Context { set; }

			string Description { set; }

			void Tick();
		}

		private MumbleLinkFile mumbleLink;

		private Timer gameStateCheckTimer;

		private Timer reportingTaskTimer;

		private string endData;

		public bool isPlayerInLevel = false;

		private Thread clientThread;

		public bool gameStarted = false;

		private volatile bool clientNoise = false;

		private static Random random = new Random();

		public override void Load()
		{
			OpenPAConfig.configTalkState = ((BasePlugin)this).Config.Bind<bool>("TalkState", "Enabled", false, "Whether or not the game should tap into the TalkState plugin for Mumble.");
			OpenPAConfig.configIntensity = ((BasePlugin)this).Config.Bind<int>("TalkState", "Refresh Rate", 120, "The amount of time in milliseconds that the plugin will check for TalkState changes. 120 is a good sweetspot, but you can lower this if it's not precise enough. You could also up it if your host has a bad CPU, since hosts will be a bit more stressed out in this process. I would stay between 30ms and 240ms.");
			OpenPAConfig.configVerbose = ((BasePlugin)this).Config.Bind<bool>("Verbose", "Enabled", false, "Enables debug logs in the BepInEx console. Can get very spammy, but useful for debugging.");
			LevelAPI.OnEnterLevel += CheckIfPlayerIsInLevel;
			SendDebugLog("Plugin is loaded!", verbose: false);
			gameStateCheckTimer = new Timer(CheckGameState, null, TimeSpan.Zero, TimeSpan.FromSeconds(3.0));
		}

		public override bool Unload()
		{
			clientThread.Abort();
			SendDebugLog("Plugin is unloading...", verbose: false);
			return ((BasePlugin)this).Unload();
		}

		public unsafe void CheckGameState(object state)
		{
			//IL_0001: Unknown result type (might be due to invalid IL or missing references)
			//IL_0006: Unknown result type (might be due to invalid IL or missing references)
			eGameStateName currentStateName = GameStateManager.CurrentStateName;
			string text = ((object)(eGameStateName)(ref currentStateName)).ToString();
			if (gameStarted && text != "InLevel" && text == "Inactive")
			{
				((BasePlugin)this).Unload();
			}
			if (text == "Generating" || text == "InLevel")
			{
				SendDebugLog("Game is now in the 'Generating' OR 'InLevel' state. (" + text + ")", verbose: false);
				mumbleLink = MumbleLinkFile.CreateOrOpen();
				Frame* ptr = mumbleLink.FramePtr();
				ptr->SetName("GTFO");
				ptr->uiVersion = 2u;
				string text2 = RandomString(16);
				SendDebugLog("Setting Mumble ID to " + text2, verbose: false);
				ptr->SetID(text2);
				SendDebugLog("Setting context to InLevel", verbose: false);
				ptr->SetContext("InLevel");
				SendDebugLog("Mumble Link Shared Memory Initialized", verbose: false);
				if (OpenPAConfig.configTalkState.Value)
				{
					PlayerStatus.PlayerStartedTalking(0, isTalking: false);
					PlayerStatus.PlayerStatusChanged += PlayerStatusChangedHandler;
					HostSync();
					FindTalkState();
					SendDebugLog("TalkState Shared Memory Initialized", verbose: false);
					gameStarted = true;
				}
				gameStateCheckTimer.Change(-1, -1);
				reportingTaskTimer = new Timer(FixedUpdated, null, TimeSpan.Zero, TimeSpan.FromMilliseconds(24.0));
			}
			else
			{
				SendDebugLog("Currently not in level. Reattempting.. (" + text + ")", verbose: true);
			}
		}

		public void SendDebugLog(string msg, bool verbose)
		{
			if (verbose)
			{
				if (OpenPAConfig.configVerbose.Value)
				{
					((BasePlugin)this).Log.LogInfo((object)msg);
				}
			}
			else
			{
				((BasePlugin)this).Log.LogInfo((object)msg);
			}
		}

		public void SendErrorLog(string msg, bool verbose)
		{
			if (verbose)
			{
				if (OpenPAConfig.configVerbose.Value)
				{
					((BasePlugin)this).Log.LogError((object)msg);
				}
			}
			else
			{
				((BasePlugin)this).Log.LogError((object)msg);
			}
		}

		private string RandomString(int len)
		{
			return new string((from s in Enumerable.Repeat("ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789", len)
				select s[random.Next(s.Length)]).ToArray());
		}

		public void CheckIfPlayerIsInLevel()
		{
			SendDebugLog("Player connected to level.", verbose: true);
			isPlayerInLevel = true;
		}

		private unsafe void FixedUpdated(object state)
		{
			//IL_0001: Unknown result type (might be due to invalid IL or missing references)
			//IL_0006: Unknown result type (might be due to invalid IL or missing references)
			//IL_0099: Unknown result type (might be due to invalid IL or missing references)
			//IL_00a0: Expected O, but got Unknown
			//IL_0150: Unknown result type (might be due to invalid IL or missing references)
			//IL_0164: Unknown result type (might be due to invalid IL or missing references)
			//IL_0169: Unknown result type (might be due to invalid IL or missing references)
			//IL_016e: Unknown result type (might be due to invalid IL or missing references)
			//IL_04bd: Unknown result type (might be due to invalid IL or missing references)
			//IL_04c4: Expected O, but got Unknown
			//IL_01cc: Unknown result type (might be due to invalid IL or missing references)
			//IL_022b: 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_0201: Unknown result type (might be due to invalid IL or missing references)
			//IL_021e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0289: 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_0246: Unknown result type (might be due to invalid IL or missing references)
			//IL_0260: Unknown result type (might be due to invalid IL or missing references)
			//IL_027d: Unknown result type (might be due to invalid IL or missing references)
			//IL_02f2: Unknown result type (might be due to invalid IL or missing references)
			//IL_02f8: Unknown result type (might be due to invalid IL or missing references)
			//IL_02fd: Unknown result type (might be due to invalid IL or missing references)
			//IL_02ba: Unknown result type (might be due to invalid IL or missing references)
			//IL_02cf: Unknown result type (might be due to invalid IL or missing references)
			//IL_02e7: Unknown result type (might be due to invalid IL or missing references)
			//IL_036e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0374: Unknown result type (might be due to invalid IL or missing references)
			//IL_0379: Unknown result type (might be due to invalid IL or missing references)
			//IL_032a: Unknown result type (might be due to invalid IL or missing references)
			//IL_0344: Unknown result type (might be due to invalid IL or missing references)
			//IL_0361: 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_03f2: Unknown result type (might be due to invalid IL or missing references)
			//IL_03f7: Unknown result type (might be due to invalid IL or missing references)
			//IL_03a6: Unknown result type (might be due to invalid IL or missing references)
			//IL_03c0: Unknown result type (might be due to invalid IL or missing references)
			//IL_03dd: Unknown result type (might be due to invalid IL or missing references)
			//IL_0424: Unknown result type (might be due to invalid IL or missing references)
			//IL_043e: Unknown result type (might be due to invalid IL or missing references)
			//IL_045b: Unknown result type (might be due to invalid IL or missing references)
			eGameStateName currentStateName = GameStateManager.CurrentStateName;
			string text = ((object)(eGameStateName)(ref currentStateName)).ToString();
			if (text != "InLevel" && text == "Inactive")
			{
				((BasePlugin)this).Unload();
			}
			bool flag = default(bool);
			if (text != "Generating" && text != "ReadyToStopElevatorRide" && text != "StopElevatorRide" && text != "ReadyToStartLevel" && text != "InLevel")
			{
				ManualLogSource log = ((BasePlugin)this).Log;
				BepInExWarningLogInterpolatedStringHandler val = new BepInExWarningLogInterpolatedStringHandler(44, 0, ref flag);
				if (flag)
				{
					((BepInExLogInterpolatedStringHandler)val).AppendLiteral("Expedition Aborted, Closing Link Connection.");
				}
				log.LogWarning(val);
				reportingTaskTimer.Change(-1, -1);
				gameStateCheckTimer = new Timer(CheckGameState, null, TimeSpan.Zero, TimeSpan.FromSeconds(3.0));
				mumbleLink.Dispose();
				mumbleLink = null;
				isPlayerInLevel = false;
			}
			else
			{
				if (!isPlayerInLevel)
				{
					return;
				}
				if (!PlayerManager.HasLocalPlayerAgent())
				{
					SendErrorLog("No PlayerAgent.", verbose: true);
					return;
				}
				PlayerAgent localPlayerAgent = PlayerManager.GetLocalPlayerAgent();
				Vector3 val2 = ((Agent)localPlayerAgent).EyePosition - new Vector3(0f, 1f, 0f);
				FPSCamera fPSCamera = localPlayerAgent.FPSCamera;
				if ((Object)(object)localPlayerAgent != (Object)null && (Object)(object)fPSCamera != (Object)null && text != null)
				{
					if (mumbleLink == null)
					{
						SendDebugLog("Initializing Load(). (mumbleLink == null).", verbose: false);
						((BasePlugin)this).Load();
					}
					Frame* ptr = mumbleLink.FramePtr();
					_ = fPSCamera.Position;
					if (true)
					{
						float* fixedElementField = ptr->fCameraPosition;
						*fixedElementField = fPSCamera.Position.x;
						ptr->fCameraPosition[1] = fPSCamera.Position.y;
						ptr->fCameraPosition[2] = fPSCamera.Position.z;
					}
					_ = fPSCamera.Forward;
					if (true)
					{
						float* fixedElementField2 = ptr->fCameraFront;
						*fixedElementField2 = fPSCamera.Forward.x;
						ptr->fCameraFront[1] = fPSCamera.Forward.y;
						ptr->fCameraFront[2] = fPSCamera.Forward.z;
					}
					currentStateName = GameStateManager.CurrentStateName;
					if (((object)(eGameStateName)(ref currentStateName)).ToString() == "InLevel")
					{
						float* fixedElementField3 = ptr->fAvatarPosition;
						*fixedElementField3 = val2.x;
						ptr->fAvatarPosition[1] = val2.y;
						ptr->fAvatarPosition[2] = val2.z;
					}
					else
					{
						_ = ((Agent)localPlayerAgent).Forward;
						currentStateName = GameStateManager.CurrentStateName;
						if (((object)(eGameStateName)(ref currentStateName)).ToString() != "InLevel")
						{
							float* fixedElementField4 = ptr->fAvatarPosition;
							*fixedElementField4 = fPSCamera.Position.x;
							ptr->fAvatarPosition[1] = fPSCamera.Position.y;
							ptr->fAvatarPosition[2] = fPSCamera.Position.z;
						}
					}
					_ = ((Agent)localPlayerAgent).Forward;
					currentStateName = GameStateManager.CurrentStateName;
					if (((object)(eGameStateName)(ref currentStateName)).ToString() == "InLevel")
					{
						float* fixedElementField5 = ptr->fAvatarFront;
						*fixedElementField5 = ((Agent)localPlayerAgent).Forward.x;
						ptr->fAvatarFront[1] = ((Agent)localPlayerAgent).Forward.y;
						ptr->fAvatarFront[2] = ((Agent)localPlayerAgent).Forward.z;
					}
					else
					{
						_ = ((Agent)localPlayerAgent).Forward;
						currentStateName = GameStateManager.CurrentStateName;
						if (((object)(eGameStateName)(ref currentStateName)).ToString() != "InLevel")
						{
							float* fixedElementField6 = ptr->fAvatarFront;
							*fixedElementField6 = fPSCamera.Forward.x;
							ptr->fAvatarFront[1] = fPSCamera.Forward.y;
							ptr->fAvatarFront[2] = fPSCamera.Forward.z;
						}
					}
					ptr->uiTick++;
				}
				else if (mumbleLink != null)
				{
					SendDebugLog("Closing Link Connection.", verbose: false);
					mumbleLink.Dispose();
					mumbleLink = null;
					isPlayerInLevel = false;
				}
				else
				{
					ManualLogSource log2 = ((BasePlugin)this).Log;
					BepInExErrorLogInterpolatedStringHandler val3 = new BepInExErrorLogInterpolatedStringHandler(22, 0, ref flag);
					if (flag)
					{
						((BepInExLogInterpolatedStringHandler)val3).AppendLiteral("An error has occurred.");
					}
					log2.LogError(val3);
				}
			}
		}

		public void FindTalkState()
		{
			//IL_0001: Unknown result type (might be due to invalid IL or missing references)
			//IL_0006: Unknown result type (might be due to invalid IL or missing references)
			eGameStateName currentStateName = GameStateManager.CurrentStateName;
			string text = ((object)(eGameStateName)(ref currentStateName)).ToString();
			clientThread = new Thread(ReadMemoryMappedFile);
			clientThread.Start();
			SendDebugLog("Initiated TalkState!", verbose: false);
			if (text != "Generating" && text != "ReadyToStopElevatorRide" && text != "StopElevatorRide" && text != "ReadyToStartLevel" && text != "InLevel")
			{
				SendDebugLog("Expedition Aborted, Closing TalkState Connection.", verbose: false);
				isPlayerInLevel = false;
				clientThread.Join();
			}
			void ReadMemoryMappedFile()
			{
				//IL_010e: Unknown result type (might be due to invalid IL or missing references)
				//IL_0113: Unknown result type (might be due to invalid IL or missing references)
				while (!isPlayerInLevel)
				{
					SendErrorLog("Player is not in level yet! Holding...", verbose: true);
					Thread.Sleep(5000);
				}
				while (!PlayerManager.HasLocalPlayerAgent())
				{
					SendErrorLog("No PlayerAgent.", verbose: true);
				}
				PlayerAgent localPlayerAgent = PlayerManager.GetLocalPlayerAgent();
				bool flag = false;
				bool flag2 = false;
				HashSet<string> hashSet = new HashSet<string> { "Generating", "ReadyToStopElevatorRide", "StopElevatorRide", "ReadyToStartLevel", "InLevel" };
				int value = OpenPAConfig.configIntensity.Value;
				try
				{
					using (MemoryMappedFile.OpenExisting("posaudio_mumlink"))
					{
						SendDebugLog("TalkState Enabled, and MemoryMappedFile found, continuing with operation.", verbose: true);
					}
				}
				catch (FileNotFoundException)
				{
					SendErrorLog("TalkState enabled, but MemoryMappedFile could not be found. Disabling TalkState for this session.", verbose: false);
					return;
				}
				catch (Exception)
				{
					SendErrorLog("An error occurred while initializing TalkState MMF. Aborting...", verbose: false);
					return;
				}
				SendDebugLog("Initiated RMMF!", verbose: false);
				while (true)
				{
					eGameStateName currentStateName2 = GameStateManager.CurrentStateName;
					if (!hashSet.Contains(((object)(eGameStateName)(ref currentStateName2)).ToString()))
					{
						break;
					}
					using (MemoryMappedFile memoryMappedFile2 = MemoryMappedFile.OpenExisting("posaudio_mumlink"))
					{
						using MemoryMappedViewAccessor memoryMappedViewAccessor = memoryMappedFile2.CreateViewAccessor(0L, 1024L);
						byte[] array = new byte[1024];
						memoryMappedViewAccessor.ReadArray(0L, array, 0, 1024);
						string text2 = Encoding.UTF8.GetString(array).TrimEnd('\0');
						if (SNet.LocalPlayer.IsMaster)
						{
							if (text2 == "Talking")
							{
								((Agent)localPlayerAgent).Noise = (NoiseType)3;
								SendDebugLog("Sending WALK type to localplayeragent.", verbose: true);
							}
						}
						else
						{
							if (text2 != "Talking")
							{
								if (!flag2)
								{
									SendDebugLog("Sending stop signal for '" + SteamManager.LocalPlayerName.ToString() + "'.", verbose: true);
									NetworkAPI.InvokeEvent<ClientSendData>("Client_Status", new ClientSendData
									{
										clientSlot = PlayerManager.GetLocalPlayerSlotIndex(),
										clientStatus = false
									}, (SNet_ChannelType)2);
									flag2 = true;
									flag = false;
								}
							}
							else if (text2 == "Talking" && !flag)
							{
								SendDebugLog("Sending talk signal for '" + localPlayerAgent.PlayerName + "'!", verbose: true);
								NetworkAPI.InvokeEvent<ClientSendData>("Client_Status", new ClientSendData
								{
									clientSlot = PlayerManager.GetLocalPlayerSlotIndex(),
									clientStatus = true
								}, (SNet_ChannelType)2);
								flag = true;
								flag2 = false;
							}
							flag = false;
						}
					}
					Thread.Sleep(value);
				}
			}
		}

		public void HostSync()
		{
			SendDebugLog("Checking HostSync...", verbose: true);
			if (NetworkAPI.IsEventRegistered("Client_Status"))
			{
				return;
			}
			NetworkAPI.RegisterEvent<ClientSendData>("Client_Status", (Action<ulong, ClientSendData>)delegate(ulong senderId, ClientSendData packet)
			{
				if (SNet.LocalPlayer.IsMaster)
				{
					SendDebugLog($"Message received from {senderId}: {packet.clientSlot}, {packet.clientStatus}.", verbose: true);
					if (packet.clientStatus)
					{
						PlayerStatus.PlayerStartedTalking(packet.clientSlot, isTalking: true);
					}
					else
					{
						PlayerStatus.PlayerStoppedTalking(packet.clientSlot);
					}
				}
				SendDebugLog("Triggered NetworkEvent.", verbose: true);
			});
			SendDebugLog("Registered NetworkEvent.", verbose: true);
		}

		public void PlayerStatusChangedHandler(object sender, PlayerStatusChangedEvent e)
		{
			int playerID = e.PlayerID;
			PlayerAgent val = null;
			if (e.IsTalking)
			{
				SendDebugLog($"Player {e.PlayerID} started talking.", verbose: true);
				if (PlayerManager.TryGetPlayerAgent(ref playerID, ref val))
				{
					((Agent)val).Noise = (NoiseType)3;
				}
			}
			else
			{
				SendDebugLog($"Player {e.PlayerID} stopped talking.", verbose: true);
			}
		}
	}
	public static class PlayerStatus
	{
		private static ConcurrentDictionary<int, bool> playerStatus = new ConcurrentDictionary<int, bool>();

		public static event EventHandler<PlayerStatusChangedEvent> PlayerStatusChanged;

		public static void PlayerStartedTalking(int playerID, bool isTalking)
		{
			playerStatus.AddOrUpdate(playerID, isTalking, (int key, bool oldValue) => isTalking);
			OnPlayerStatusChanged(new PlayerStatusChangedEvent(playerID, isTalking));
		}

		public static void PlayerStoppedTalking(int playerID)
		{
			bool value;
			bool flag = playerStatus.TryRemove(playerID, out value);
		}

		public static List<int> GetPlayersTalking()
		{
			return (from kv in playerStatus
				where kv.Value
				select kv.Key).ToList();
		}

		private static void OnPlayerStatusChanged(PlayerStatusChangedEvent e)
		{
			PlayerStatus.PlayerStatusChanged?.Invoke(null, e);
		}
	}
	public class PlayerStatusChangedEvent : EventArgs
	{
		public int PlayerID { get; }

		public bool IsTalking { get; }

		public PlayerStatusChangedEvent(int playerID, bool isTalking)
		{
			PlayerID = playerID;
			IsTalking = isTalking;
		}
	}
}
namespace mumblelib
{
	internal class Text
	{
		public static Encoding Encoding = System.Text.Encoding.Unicode;
	}
	[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
	public struct Frame
	{
		public uint uiVersion;

		public uint uiTick;

		public unsafe fixed float fAvatarPosition[3];

		public unsafe fixed float fAvatarFront[3];

		public unsafe fixed float fAvatarTop[3];

		public unsafe fixed ushort name[256];

		public unsafe fixed float fCameraPosition[3];

		public unsafe fixed float fCameraFront[3];

		public unsafe fixed float fCameraTop[3];

		public unsafe fixed ushort id[256];

		public uint context_len;

		public unsafe fixed byte context[256];

		public unsafe fixed ushort description[2048];

		public unsafe void SetName(string name)
		{
			fixed (Frame* ptr = &this)
			{
				byte[] bytes = Text.Encoding.GetBytes(name + "\0");
				Marshal.Copy(bytes, 0, new IntPtr(ptr->name), bytes.Length);
			}
		}

		public unsafe void SetDescription(string desc)
		{
			fixed (Frame* ptr = &this)
			{
				byte[] bytes = Text.Encoding.GetBytes(desc + "\0");
				Marshal.Copy(bytes, 0, new IntPtr(ptr->description), bytes.Length);
			}
		}

		public unsafe void SetID(string id)
		{
			fixed (Frame* ptr = &this)
			{
				byte[] bytes = Text.Encoding.GetBytes(id + "\0");
				Marshal.Copy(bytes, 0, new IntPtr(ptr->id), bytes.Length);
			}
		}

		public unsafe void SetContext(string context)
		{
			fixed (Frame* ptr = &this)
			{
				byte[] bytes = Encoding.UTF8.GetBytes(context);
				context_len = (uint)Math.Min(256, bytes.Length);
				Marshal.Copy(bytes, 0, new IntPtr(ptr->context), (int)context_len);
			}
		}
	}
	public class MumbleLinkFile : IDisposable
	{
		private readonly MemoryMappedFile memoryMappedFile;

		private unsafe readonly Frame* ptr;

		private bool disposed;

		private static string LinkFileName()
		{
			return "MumbleLink";
		}

		public unsafe MumbleLinkFile(MemoryMappedFile memoryMappedFile)
		{
			this.memoryMappedFile = memoryMappedFile ?? throw new ArgumentNullException("memoryMappedFile");
			byte* pointer = null;
			memoryMappedFile.CreateViewAccessor().SafeMemoryMappedViewHandle.AcquirePointer(ref pointer);
			ptr = (Frame*)pointer;
		}

		public unsafe Frame* FramePtr()
		{
			return ptr;
		}

		public static MumbleLinkFile CreateOrOpen()
		{
			return new MumbleLinkFile(MemoryMappedFile.CreateOrOpen(LinkFileName(), Marshal.SizeOf<Frame>()));
		}

		public void Dispose()
		{
			Dispose(disposing: true);
			GC.SuppressFinalize(this);
		}

		private void assertNotDisposed()
		{
			if (disposed)
			{
				throw new ObjectDisposedException(GetType().FullName);
			}
		}

		protected virtual void Dispose(bool disposing)
		{
			if (!disposed)
			{
				if (disposing)
				{
					memoryMappedFile.Dispose();
				}
				disposed = true;
			}
		}
	}
}