Decompiled source of Custom Audio Output Device v1.0.2

plugins/Soundheim.dll

Decompiled a week ago
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Diagnostics;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Runtime.Versioning;
using System.Threading.Tasks;
using BepInEx;
using BepInEx.Configuration;
using GUIFramework;
using HarmonyLib;
using Microsoft.CodeAnalysis;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using Soundheim.Audio;
using TMPro;
using UnityEngine;
using UnityEngine.Events;
using UnityEngine.UI;
using Valheim.SettingsGui;

[assembly: CompilationRelaxations(8)]
[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
[assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)]
[assembly: AssemblyTitle("Custom Audio Output Device")]
[assembly: AssemblyProduct("Custom Audio Output Device")]
[assembly: AssemblyFileVersion("1.0.2.0")]
[assembly: TargetFramework(".NETStandard,Version=v2.1", FrameworkDisplayName = ".NET Standard 2.1")]
[assembly: AssemblyVersion("1.0.2.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 Soundheim
{
	internal sealed class AudioDeviceTooltips(GameObject prefab, UITooltip selected)
	{
		internal static AudioDeviceTooltips? Create(TMP_Dropdown dropdown, Transform parent)
		{
			//IL_002a: Unknown result type (might be due to invalid IL or missing references)
			//IL_0030: Expected O, but got Unknown
			//IL_0083: Unknown result type (might be due to invalid IL or missing references)
			//IL_0088: Unknown result type (might be due to invalid IL or missing references)
			//IL_009a: Unknown result type (might be due to invalid IL or missing references)
			//IL_00b4: Unknown result type (might be due to invalid IL or missing references)
			//IL_00bb: Unknown result type (might be due to invalid IL or missing references)
			//IL_00cd: Unknown result type (might be due to invalid IL or missing references)
			//IL_00e2: Unknown result type (might be due to invalid IL or missing references)
			//IL_00f7: Unknown result type (might be due to invalid IL or missing references)
			//IL_0101: Unknown result type (might be due to invalid IL or missing references)
			//IL_011c: Unknown result type (might be due to invalid IL or missing references)
			//IL_012c: Unknown result type (might be due to invalid IL or missing references)
			//IL_013a: Unknown result type (might be due to invalid IL or missing references)
			//IL_0144: Expected O, but got Unknown
			//IL_0182: Unknown result type (might be due to invalid IL or missing references)
			//IL_019a: Unknown result type (might be due to invalid IL or missing references)
			TMP_Text captionText = dropdown.captionText;
			if ((Object)(object)captionText == (Object)null)
			{
				return null;
			}
			GameObject val = new GameObject("Audio device tooltip template", new Type[1] { typeof(RectTransform) });
			val.SetActive(false);
			val.transform.SetParent(parent, false);
			GameObject val2 = new GameObject("Panel", new Type[4]
			{
				typeof(RectTransform),
				typeof(Image),
				typeof(VerticalLayoutGroup),
				typeof(ContentSizeFitter)
			});
			val2.transform.SetParent(val.transform, false);
			RectTransform component = val2.GetComponent<RectTransform>();
			Vector2 val3 = default(Vector2);
			((Vector2)(ref val3))..ctor(0.5f, 0.5f);
			component.anchorMax = val3;
			component.anchorMin = val3;
			component.pivot = new Vector2(0f, 1f);
			component.anchoredPosition = new Vector2(16f, -16f);
			component.sizeDelta = new Vector2(400f, 0f);
			Image component2 = val2.GetComponent<Image>();
			((Graphic)component2).color = new Color(0f, 0f, 0f, 0.9f);
			((Graphic)component2).raycastTarget = false;
			VerticalLayoutGroup component3 = val2.GetComponent<VerticalLayoutGroup>();
			((LayoutGroup)component3).padding = new RectOffset(12, 12, 8, 8);
			((HorizontalOrVerticalLayoutGroup)component3).spacing = 4f;
			bool childControlWidth = (((HorizontalOrVerticalLayoutGroup)component3).childControlHeight = true);
			((HorizontalOrVerticalLayoutGroup)component3).childControlWidth = childControlWidth;
			((HorizontalOrVerticalLayoutGroup)component3).childForceExpandWidth = true;
			((HorizontalOrVerticalLayoutGroup)component3).childForceExpandHeight = false;
			val2.GetComponent<ContentSizeFitter>().verticalFit = (FitMode)2;
			TMP_Text val4 = CreateLabel((Transform)(object)component, captionText, "Topic", ((Graphic)captionText).color, (TextAlignmentOptions)514);
			TMP_Text val5 = CreateLabel((Transform)(object)component, captionText, "Text", Color.white, (TextAlignmentOptions)513);
			val.AddComponent<AudioTooltipSize>().Initialize(component, component3, val4, val5);
			UITooltip selected = Attach(((Component)dropdown).gameObject, val, captionText.text);
			return new AudioDeviceTooltips(val, selected);
		}

		private static TMP_Text CreateLabel(Transform parent, TMP_Text style, string name, Color color, TextAlignmentOptions alignment)
		{
			//IL_0014: Unknown result type (might be due to invalid IL or missing references)
			//IL_0019: Unknown result type (might be due to invalid IL or missing references)
			//IL_0050: Unknown result type (might be due to invalid IL or missing references)
			//IL_0057: Unknown result type (might be due to invalid IL or missing references)
			GameObject val = new GameObject(name, new Type[1] { typeof(RectTransform) });
			val.transform.SetParent(parent, false);
			TextMeshProUGUI obj = val.AddComponent<TextMeshProUGUI>();
			((TMP_Text)obj).font = style.font;
			((TMP_Text)obj).fontSharedMaterial = style.fontSharedMaterial;
			((TMP_Text)obj).fontSize = style.fontSizeMax;
			((Graphic)obj).color = color;
			((TMP_Text)obj).alignment = alignment;
			((TMP_Text)obj).richText = false;
			((Graphic)obj).raycastTarget = false;
			((TMP_Text)obj).textWrappingMode = (TextWrappingModes)1;
			return (TMP_Text)(object)obj;
		}

		internal void SetSelection(string name)
		{
			//IL_001c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0022: Unknown result type (might be due to invalid IL or missing references)
			if (((Behaviour)selected).enabled)
			{
				selected.Set("Output device", name, (RectTransform)null, default(Vector2));
			}
			else
			{
				selected.m_text = name;
			}
		}

		internal void SetExpanded(TMP_Dropdown dropdown, bool expanded)
		{
			((Behaviour)selected).enabled = !expanded;
			if (!expanded)
			{
				return;
			}
			Toggle[] componentsInChildren = ((Component)dropdown).GetComponentsInChildren<Toggle>();
			foreach (Toggle val in componentsInChildren)
			{
				TMP_Text componentInChildren = ((Component)val).GetComponentInChildren<TMP_Text>();
				if ((Object)(object)componentInChildren != (Object)null)
				{
					Attach(((Component)val).gameObject, prefab, componentInChildren.text);
				}
			}
		}

		private static UITooltip Attach(GameObject target, GameObject prefab, string name)
		{
			UITooltip obj = target.GetComponent<UITooltip>() ?? target.AddComponent<UITooltip>();
			obj.m_tooltipPrefab = prefab;
			obj.m_topic = "Output device";
			obj.m_text = name;
			return obj;
		}
	}
	internal sealed class AudioTooltipSize : MonoBehaviour
	{
		[SerializeField]
		private RectTransform? panel;

		[SerializeField]
		private VerticalLayoutGroup? layout;

		[SerializeField]
		private TMP_Text[] labels = Array.Empty<TMP_Text>();

		internal void Initialize(RectTransform panel, VerticalLayoutGroup layout, params TMP_Text[] labels)
		{
			this.panel = panel;
			this.layout = layout;
			this.labels = labels;
		}

		private void OnEnable()
		{
			//IL_0041: Unknown result type (might be due to invalid IL or missing references)
			if (!((Object)(object)panel == (Object)null) && !((Object)(object)layout == (Object)null))
			{
				float num = 0f;
				TMP_Text[] array = labels;
				foreach (TMP_Text obj in array)
				{
					float x = obj.GetPreferredValues(obj.text, float.PositiveInfinity, float.PositiveInfinity).x;
					num = Mathf.Max(num, x);
				}
				num = Mathf.Min(400f, Mathf.Ceil(num) + (float)((LayoutGroup)layout).padding.horizontal);
				panel.SetSizeWithCurrentAnchors((Axis)0, num);
				LayoutRebuilder.ForceRebuildLayoutImmediate(panel);
			}
		}
	}
	internal sealed class NativeAudioSettings : MonoBehaviour
	{
		[HarmonyPatch(typeof(AudioSettings), "Initialize")]
		private static class InitializePatch
		{
			private static void Postfix(AudioSettings __instance, Toggle ___m_continousMusic, Slider ___m_volumeSlider, TMP_Text ___m_volumeText)
			{
				Attach(__instance, ___m_continousMusic, ___m_volumeSlider, ___m_volumeText);
			}
		}

		[HarmonyPatch(typeof(AudioSettings), "OnTabOpen")]
		private static class NavigationPatch
		{
			private static void Postfix(AudioSettings __instance, Toggle ___m_continousMusic, Button backButton, Button okButton)
			{
				((Component)__instance).GetComponent<NativeAudioSettings>()?.SetNavigation(___m_continousMusic, backButton, okButton);
			}
		}

		[HarmonyPatch(typeof(AudioSettings), "OnOkAsync")]
		private static class SavePatch
		{
			private static void Prefix()
			{
				Plugin.Instance?.Save();
			}
		}

		[HarmonyPatch(typeof(AudioSettings), "OnBack")]
		private static class RevertPatch
		{
			private static void Postfix()
			{
				Plugin.Instance?.Revert();
			}
		}

		private GuiDropdown? dropdown;

		private TMP_Text? status;

		private AudioDeviceTooltips? tooltips;

		private readonly List<string> identifiers = new List<string>();

		private IReadOnlyList<AudioDevice>? shownDevices;

		private string shownSelection = "";

		private Settings? settings;

		private Plugin? plugin;

		private RectTransform? rowRect;

		private RectTransform? parentRect;

		private RectTransform? previousRect;

		private RectTransform? sliderRect;

		private RectTransform? volumeRect;

		private RectTransform? labelRect;

		private readonly Vector3[] corners = (Vector3[])(object)new Vector3[4];

		internal static void Attach(AudioSettings audio, Toggle lastControl, Slider volume, TMP_Text volumeText)
		{
			if ((Object)(object)Plugin.Instance == (Object)null || (Object)(object)((Component)audio).GetComponent<NativeAudioSettings>() != (Object)null)
			{
				return;
			}
			Settings componentInParent = ((Component)audio).GetComponentInParent<Settings>();
			GraphicsSettings componentInChildren = ((Component)componentInParent).GetComponentInChildren<GraphicsSettings>(true);
			object obj2;
			if ((Object)(object)componentInChildren != (Object)null)
			{
				object? obj = AccessTools.Field(typeof(GraphicsSettings), "m_resolutionDropdown")?.GetValue(componentInChildren);
				GuiDropdown val = (GuiDropdown)((obj is GuiDropdown) ? obj : null);
				if (val != null)
				{
					obj2 = val;
					goto IL_0062;
				}
			}
			obj2 = null;
			goto IL_0062;
			IL_0062:
			GuiDropdown val2 = (GuiDropdown)obj2;
			TMP_Text componentInChildren2 = ((Component)lastControl).GetComponentInChildren<TMP_Text>(true);
			if ((Object)(object)val2 == (Object)null || (Object)(object)componentInChildren2 == (Object)null)
			{
				Plugin.Instance.ReportError("Custom Audio Output Device could not find Valheim's native audio label or dropdown. Check compatibility with this Valheim version.");
				return;
			}
			NativeAudioSettings nativeAudioSettings = ((Component)audio).gameObject.AddComponent<NativeAudioSettings>();
			nativeAudioSettings.settings = componentInParent;
			nativeAudioSettings.plugin = Plugin.Instance;
			nativeAudioSettings.sliderRect = ((Component)volume).GetComponent<RectTransform>();
			nativeAudioSettings.volumeRect = volumeText.rectTransform;
			nativeAudioSettings.Create(audio, lastControl, val2, componentInChildren2);
			Plugin.Instance.Refresh();
		}

		private void Create(AudioSettings audio, Toggle lastControl, GuiDropdown template, TMP_Text font)
		{
			//IL_002c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0032: Expected O, but got Unknown
			//IL_005d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0072: Unknown result type (might be due to invalid IL or missing references)
			//IL_0087: 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_014d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0158: Unknown result type (might be due to invalid IL or missing references)
			//IL_016d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0178: Unknown result type (might be due to invalid IL or missing references)
			//IL_018c: Unknown result type (might be due to invalid IL or missing references)
			//IL_019c: Unknown result type (might be due to invalid IL or missing references)
			//IL_01a6: Expected O, but got Unknown
			//IL_01ae: Unknown result type (might be due to invalid IL or missing references)
			//IL_01bc: Unknown result type (might be due to invalid IL or missing references)
			RectTransform component = ((Component)audio).GetComponent<RectTransform>();
			GameObject val = new GameObject("Soundheim output device", new Type[2]
			{
				typeof(RectTransform),
				typeof(LayoutElement)
			});
			val.transform.SetParent((Transform)(object)component, false);
			val.GetComponent<LayoutElement>().ignoreLayout = true;
			RectTransform component2 = val.GetComponent<RectTransform>();
			component2.anchorMin = new Vector2(0f, 1f);
			component2.anchorMax = new Vector2(1f, 1f);
			component2.pivot = new Vector2(0.5f, 1f);
			rowRect = component2;
			parentRect = component;
			previousRect = ((Component)lastControl).GetComponent<RectTransform>();
			PositionRow();
			component2.sizeDelta = new Vector2(-48f, 112f);
			TMP_Text val2 = Label(val.transform, font, "Output device", 0f, 0f, 0.42f, 40f);
			val2.alignment = (TextAlignmentOptions)4100;
			labelRect = val2.rectTransform;
			dropdown = Object.Instantiate<GuiDropdown>(template, val.transform);
			((Object)dropdown).name = "Output device";
			((Component)dropdown).gameObject.SetActive(false);
			RectTransform component3 = ((Component)dropdown).GetComponent<RectTransform>();
			component3.anchorMin = new Vector2(0.44f, 1f);
			component3.anchorMax = Vector2.one;
			component3.pivot = new Vector2(0.5f, 1f);
			component3.anchoredPosition = Vector2.zero;
			component3.sizeDelta = new Vector2(0f, 40f);
			((TMP_Dropdown)dropdown).onValueChanged = new DropdownEvent();
			GuiDropdown? obj = dropdown;
			Navigation navigation = default(Navigation);
			((Navigation)(ref navigation)).mode = (Mode)4;
			((Selectable)obj).navigation = navigation;
			((TMP_Dropdown)dropdown).ClearOptions();
			((UnityEvent<int>)(object)((TMP_Dropdown)dropdown).onValueChanged).AddListener((UnityAction<int>)Selected);
			dropdown.OnExpandedStateChange += Expanded;
			TMP_Text[] componentsInChildren = ((Component)dropdown).GetComponentsInChildren<TMP_Text>(true);
			foreach (TMP_Text val3 in componentsInChildren)
			{
				if ((Object)(object)val3 != (Object)(object)((TMP_Dropdown)dropdown).captionText && (Object)(object)val3 != (Object)(object)((TMP_Dropdown)dropdown).itemText && ((Object)(object)((TMP_Dropdown)dropdown).template == (Object)null || !val3.transform.IsChildOf((Transform)(object)((TMP_Dropdown)dropdown).template)))
				{
					((Behaviour)val3).enabled = false;
				}
			}
			ConfigureDeviceText(((TMP_Dropdown)dropdown).captionText);
			ConfigureDeviceText(((TMP_Dropdown)dropdown).itemText);
			status = Label(val.transform, font, "", 0f, 48f, 1f, 64f);
			status.fontSize = Math.Min(font.fontSize, 16f);
			status.enableAutoSizing = false;
			status.alignment = (TextAlignmentOptions)257;
			status.textWrappingMode = (TextWrappingModes)1;
			status.overflowMode = (TextOverflowModes)1;
			tooltips = AudioDeviceTooltips.Create((TMP_Dropdown)(object)dropdown, val.transform);
			if (tooltips == null)
			{
				Plugin.Instance?.ReportTooltipUnavailable();
			}
			RefreshOptions();
			((Component)dropdown).gameObject.SetActive(true);
			PositionRow();
		}

		private static void ConfigureDeviceText(TMP_Text? text)
		{
			//IL_004f: Unknown result type (might be due to invalid IL or missing references)
			//IL_005a: Unknown result type (might be due to invalid IL or missing references)
			//IL_006f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0083: Unknown result type (might be due to invalid IL or missing references)
			if (!((Object)(object)text == (Object)null))
			{
				text.richText = false;
				text.textWrappingMode = (TextWrappingModes)0;
				text.overflowMode = (TextOverflowModes)1;
				text.enableAutoSizing = true;
				text.fontSizeMax = text.fontSize;
				text.fontSizeMin = Math.Min(16f, text.fontSizeMax);
				RectTransform rectTransform = text.rectTransform;
				rectTransform.anchorMin = Vector2.zero;
				rectTransform.anchorMax = Vector2.one;
				rectTransform.offsetMin = new Vector2(16f, 0f);
				rectTransform.offsetMax = new Vector2(-16f, 0f);
			}
		}

		private static TMP_Text Label(Transform parent, TMP_Text style, string text, float x, float y, float width, float height)
		{
			//IL_0024: Unknown result type (might be due to invalid IL or missing references)
			//IL_002a: Expected O, but got Unknown
			//IL_0063: Unknown result type (might be due to invalid IL or missing references)
			//IL_006f: Unknown result type (might be due to invalid IL or missing references)
			//IL_00aa: 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_00d1: Unknown result type (might be due to invalid IL or missing references)
			//IL_00e0: Unknown result type (might be due to invalid IL or missing references)
			//IL_00ee: Unknown result type (might be due to invalid IL or missing references)
			GameObject val = new GameObject((text.Length > 0) ? text : "Audio output status", new Type[1] { typeof(RectTransform) });
			val.transform.SetParent(parent, false);
			TextMeshProUGUI obj = val.AddComponent<TextMeshProUGUI>();
			((TMP_Text)obj).font = style.font;
			((TMP_Text)obj).fontSharedMaterial = style.fontSharedMaterial;
			((TMP_Text)obj).fontSize = style.fontSize;
			((TMP_Text)obj).fontStyle = style.fontStyle;
			((Graphic)obj).color = ((Graphic)style).color;
			((TMP_Text)obj).richText = false;
			((Graphic)obj).raycastTarget = false;
			((TMP_Text)obj).alignment = (TextAlignmentOptions)4097;
			((TMP_Text)obj).text = text;
			RectTransform component = val.GetComponent<RectTransform>();
			component.anchorMin = new Vector2(0f, 1f);
			component.anchorMax = new Vector2(width, 1f);
			component.pivot = new Vector2(0f, 1f);
			component.anchoredPosition = new Vector2(x, 0f - y);
			component.sizeDelta = new Vector2(0f - x, height);
			return (TMP_Text)(object)obj;
		}

		private void Selected(int index)
		{
			if (index >= 0 && index < identifiers.Count)
			{
				plugin?.Preview(identifiers[index]);
			}
			if ((Object)(object)dropdown != (Object)null && index >= 0 && index < ((TMP_Dropdown)dropdown).options.Count)
			{
				tooltips?.SetSelection(((TMP_Dropdown)dropdown).options[index].text);
			}
		}

		private void Expanded(bool expanded)
		{
			if ((Object)(object)settings != (Object)null)
			{
				settings.BlockNavigation(expanded);
			}
			if ((Object)(object)status != (Object)null)
			{
				((Behaviour)status).enabled = !expanded;
			}
			if ((Object)(object)dropdown != (Object)null)
			{
				tooltips?.SetExpanded((TMP_Dropdown)(object)dropdown, expanded);
			}
		}

		private void LateUpdate()
		{
			PositionRow();
		}

		private void PositionRow()
		{
			//IL_0049: Unknown result type (might be due to invalid IL or missing references)
			//IL_004e: Unknown result type (might be due to invalid IL or missing references)
			//IL_005e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0063: Unknown result type (might be due to invalid IL or missing references)
			//IL_007f: Unknown result type (might be due to invalid IL or missing references)
			//IL_00e0: Unknown result type (might be due to invalid IL or missing references)
			//IL_00e5: Unknown result type (might be due to invalid IL or missing references)
			//IL_00f5: Unknown result type (might be due to invalid IL or missing references)
			//IL_00fa: 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_0127: Unknown result type (might be due to invalid IL or missing references)
			//IL_0137: Unknown result type (might be due to invalid IL or missing references)
			//IL_013c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0163: Unknown result type (might be due to invalid IL or missing references)
			//IL_016a: Unknown result type (might be due to invalid IL or missing references)
			//IL_0181: Unknown result type (might be due to invalid IL or missing references)
			//IL_0191: Unknown result type (might be due to invalid IL or missing references)
			//IL_01b7: Unknown result type (might be due to invalid IL or missing references)
			//IL_01df: Unknown result type (might be due to invalid IL or missing references)
			//IL_01e6: Unknown result type (might be due to invalid IL or missing references)
			//IL_01f8: Unknown result type (might be due to invalid IL or missing references)
			//IL_0209: Unknown result type (might be due to invalid IL or missing references)
			//IL_0225: Unknown result type (might be due to invalid IL or missing references)
			//IL_0264: Unknown result type (might be due to invalid IL or missing references)
			//IL_026b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0283: Unknown result type (might be due to invalid IL or missing references)
			//IL_02aa: Unknown result type (might be due to invalid IL or missing references)
			if ((Object)(object)rowRect == (Object)null || (Object)(object)parentRect == (Object)null || (Object)(object)previousRect == (Object)null)
			{
				return;
			}
			previousRect.GetWorldCorners(corners);
			float y = ((Transform)parentRect).InverseTransformPoint(corners[0]).y;
			Rect rect = parentRect.rect;
			float num = y - ((Rect)(ref rect)).yMax - 20f;
			rowRect.anchoredPosition = new Vector2(0f, num);
			if (!((Object)(object)sliderRect == (Object)null) && !((Object)(object)volumeRect == (Object)null) && !((Object)(object)labelRect == (Object)null) && !((Object)(object)dropdown == (Object)null))
			{
				sliderRect.GetWorldCorners(corners);
				float x = ((Transform)rowRect).InverseTransformPoint(corners[0]).x;
				rect = rowRect.rect;
				float num2 = x - ((Rect)(ref rect)).xMin;
				volumeRect.GetWorldCorners(corners);
				float x2 = ((Transform)rowRect).InverseTransformPoint(corners[2]).x;
				rect = rowRect.rect;
				float num3 = x2 - ((Rect)(ref rect)).xMin;
				RectTransform? obj = labelRect;
				RectTransform? obj2 = labelRect;
				Vector2 val = default(Vector2);
				((Vector2)(ref val))..ctor(0f, 1f);
				obj2.anchorMax = val;
				obj.anchorMin = val;
				labelRect.pivot = new Vector2(0f, 1f);
				labelRect.anchoredPosition = Vector2.zero;
				labelRect.sizeDelta = new Vector2(Math.Max(0f, num2 - 16f), 40f);
				RectTransform component = ((Component)dropdown).GetComponent<RectTransform>();
				((Vector2)(ref val))..ctor(0f, 1f);
				component.anchorMax = val;
				component.anchorMin = val;
				component.pivot = new Vector2(0f, 1f);
				component.anchoredPosition = new Vector2(num2, 0f);
				component.sizeDelta = new Vector2(Math.Max(200f, num3 - num2), 40f);
				if ((Object)(object)status != (Object)null)
				{
					RectTransform rectTransform = status.rectTransform;
					RectTransform rectTransform2 = status.rectTransform;
					((Vector2)(ref val))..ctor(0f, 1f);
					rectTransform2.anchorMax = val;
					rectTransform.anchorMin = val;
					status.rectTransform.anchoredPosition = new Vector2(num2, -48f);
					status.rectTransform.sizeDelta = new Vector2(Math.Max(200f, num3 - num2), 64f);
				}
			}
		}

		private void Update()
		{
			if (!((Object)(object)plugin == (Object)null) && !((Object)(object)dropdown == (Object)null))
			{
				if (!((TMP_Dropdown)dropdown).IsExpanded && (shownDevices != plugin.Devices || shownSelection != plugin.Selection))
				{
					RefreshOptions();
				}
				if ((Object)(object)status != (Object)null)
				{
					status.text = plugin.Status;
				}
			}
		}

		private void RefreshOptions()
		{
			if ((Object)(object)plugin == (Object)null || (Object)(object)dropdown == (Object)null)
			{
				return;
			}
			shownDevices = plugin.Devices;
			shownSelection = plugin.Selection;
			identifiers.Clear();
			identifiers.Add("");
			List<string> list = new List<string> { "System default" };
			foreach (AudioDevice device in plugin.Devices)
			{
				identifiers.Add(device.Id);
				list.Add(device.Name);
			}
			int num = identifiers.IndexOf(plugin.Selection);
			if (num < 0)
			{
				num = identifiers.Count;
				identifiers.Add(plugin.Selection);
				list.Add("Selected device (disconnected)");
			}
			((TMP_Dropdown)dropdown).ClearOptions();
			((TMP_Dropdown)dropdown).AddOptions(list);
			((TMP_Dropdown)dropdown).SetValueWithoutNotify(num);
			((TMP_Dropdown)dropdown).RefreshShownValue();
			tooltips?.SetSelection(list[num]);
		}

		internal void SetNavigation(Toggle previous, Button back, Button ok)
		{
			if (!((Object)(object)dropdown == (Object)null))
			{
				GuiUtils.SetNavigationDown((Selectable)(object)previous, (Selectable)(object)dropdown);
				GuiUtils.SetNavigationUp((Selectable)(object)dropdown, (Selectable)(object)previous);
				GuiUtils.SetNavigationDown((Selectable)(object)dropdown, (Selectable)(object)back);
				GuiUtils.SetNavigationUp((Selectable)(object)back, (Selectable)(object)dropdown);
				GuiUtils.SetNavigationUp((Selectable)(object)ok, (Selectable)(object)dropdown);
			}
		}

		private void OnEnable()
		{
			plugin?.Refresh();
		}

		private void OnDisable()
		{
			if ((Object)(object)dropdown != (Object)null && ((TMP_Dropdown)dropdown).IsExpanded)
			{
				((TMP_Dropdown)dropdown).Hide();
				Expanded(expanded: false);
			}
		}

		private void OnDestroy()
		{
			if ((Object)(object)dropdown != (Object)null)
			{
				dropdown.OnExpandedStateChange -= Expanded;
			}
			plugin?.Revert();
		}
	}
	[BepInPlugin("augusdogus.mods.Soundheim", "Custom Audio Output Device", "1.0.2")]
	public sealed class Plugin : BaseUnityPlugin
	{
		private sealed class PollResult(int revision, AudioResult<IReadOnlyList<AudioDevice>> devices, AudioResult<string> route)
		{
			public int Revision { get; } = revision;

			public AudioResult<IReadOnlyList<AudioDevice>> Devices { get; } = devices;

			public AudioResult<string> Route { get; } = route;
		}

		public const string PluginId = "augusdogus.mods.Soundheim";

		public const string PluginName = "Custom Audio Output Device";

		public const string PluginVersion = "1.0.2";

		private ConfigEntry<string>? preference;

		private IAudioBackend? backend;

		private Task<PollResult>? pending;

		private float nextPoll;

		private int revision;

		private string lastError = "";

		private readonly Harmony harmony = new Harmony("augusdogus.mods.Soundheim");

		internal static Plugin? Instance { get; private set; }

		internal IReadOnlyList<AudioDevice> Devices { get; private set; } = Array.Empty<AudioDevice>();

		internal string Selection { get; private set; } = "";

		internal string Status { get; private set; } = "Reading audio outputs...";

		private void Awake()
		{
			//IL_0000: Unknown result type (might be due to invalid IL or missing references)
			//IL_0006: Invalid comparison between Unknown and I4
			//IL_0016: Unknown result type (might be due to invalid IL or missing references)
			//IL_001d: Invalid comparison between Unknown and I4
			//IL_0042: Unknown result type (might be due to invalid IL or missing references)
			//IL_0048: Invalid comparison between Unknown and I4
			if ((int)SystemInfo.graphicsDeviceType == 4)
			{
				((Behaviour)this).enabled = false;
				return;
			}
			using Process process = Process.GetCurrentProcess();
			if ((int)Application.platform == 13)
			{
				backend = new PulseAudioBackend(process.Id, new Pactl().Run);
			}
			else
			{
				if ((int)Application.platform != 2)
				{
					((BaseUnityPlugin)this).Logger.LogWarning((object)"Custom Audio Output Device supports Linux and Windows only.");
					((Behaviour)this).enabled = false;
					return;
				}
				backend = new WindowsAudioBackend(process.Id);
			}
			preference = ((BaseUnityPlugin)this).Config.Bind<string>("Audio", "Output device", "", "Stable output identifier. Empty follows the system default. Select a device in Settings > Audio.");
			Selection = preference.Value;
			preference.SettingChanged += PreferenceChanged;
			Instance = this;
			harmony.PatchAll(typeof(NativeAudioSettings).Assembly);
			((BaseUnityPlugin)this).Logger.LogInfo((object)"Custom Audio Output Device loaded. Select your output in Settings > Audio.");
		}

		internal void Preview(string id)
		{
			Selection = id;
			revision++;
			nextPoll = 0f;
			Status = "Applying output preference...";
		}

		internal void Save()
		{
			if (preference != null)
			{
				preference.Value = Selection;
			}
		}

		internal void Revert()
		{
			if (preference != null && Selection != preference.Value)
			{
				Preview(preference.Value);
			}
		}

		internal void Refresh()
		{
			nextPoll = 0f;
		}

		internal void ReportTooltipUnavailable()
		{
			((BaseUnityPlugin)this).Logger.LogWarning((object)"Audio device tooltips could not find the dropdown's text style. Device selection remains available.");
		}

		private void PreferenceChanged(object sender, EventArgs args)
		{
			if (preference != null)
			{
				Preview(preference.Value);
			}
		}

		private void Update()
		{
			if (backend == null)
			{
				return;
			}
			if (pending != null && pending.IsCompleted)
			{
				Task<PollResult> task = pending;
				pending = null;
				if (task.IsFaulted)
				{
					ReportError(task.Exception?.GetBaseException().Message ?? "Unknown audio error.");
				}
				else
				{
					PollResult result = task.GetAwaiter().GetResult();
					if (result.Devices is AudioResult<IReadOnlyList<AudioDevice>>.Success success)
					{
						Devices = success.Value;
					}
					else if (result.Devices is AudioResult<IReadOnlyList<AudioDevice>>.Failure failure)
					{
						ReportError(failure.Message);
					}
					if (result.Revision == revision)
					{
						if (result.Route is AudioResult<string>.Success success2)
						{
							Status = success2.Value;
							lastError = "";
						}
						else if (result.Route is AudioResult<string>.Failure failure2)
						{
							ReportError(failure2.Message);
						}
					}
				}
			}
			if (pending == null && !(Time.unscaledTime < nextPoll))
			{
				nextPoll = Time.unscaledTime + 5f;
				string selected = Selection;
				int requestRevision = revision;
				IAudioBackend selectedBackend = backend;
				pending = Task.Run(() => new PollResult(requestRevision, selectedBackend.ListOutputs(), selectedBackend.Route(selected)));
			}
		}

		internal void ReportError(string message)
		{
			Status = "Couldn't update audio output. Retrying automatically. Details are in the game log.";
			if (!(message == lastError))
			{
				lastError = message;
				((BaseUnityPlugin)this).Logger.LogWarning((object)message);
			}
		}

		private void OnDestroy()
		{
			if (preference != null)
			{
				preference.SettingChanged -= PreferenceChanged;
			}
			harmony.UnpatchSelf();
			if ((Object)(object)Instance == (Object)(object)this)
			{
				Instance = null;
			}
		}
	}
}
namespace Soundheim.Audio
{
	internal sealed class AudioDevice(string id, string name)
	{
		public string Id { get; } = id;

		public string Name { get; } = name;
	}
	internal abstract class AudioResult<T>
	{
		internal sealed class Success(T value) : AudioResult<T>()
		{
			public T Value { get; } = value;
		}

		internal sealed class Failure(string message) : AudioResult<T>()
		{
			public string Message { get; } = message;
		}

		private AudioResult()
		{
		}
	}
	internal interface IAudioBackend
	{
		AudioResult<IReadOnlyList<AudioDevice>> ListOutputs();

		AudioResult<string> Route(string deviceId);
	}
	internal static class AudioBoundary
	{
		public static AudioResult<T> Run<T>(Func<T> action)
		{
			try
			{
				return new AudioResult<T>.Success(action());
			}
			catch (Exception ex) when (ex is IOException || ex is UnauthorizedAccessException || ex is Win32Exception || ex is InvalidOperationException || ex is ArgumentException || ex is TimeoutException || ex is FormatException || ex is JsonException || ex is ExternalException || ex is NotSupportedException || ex is DllNotFoundException || ex is EntryPointNotFoundException)
			{
				return new AudioResult<T>.Failure(ex.Message);
			}
		}
	}
	internal sealed class Pactl
	{
		private const string SteamLauncher = "/usr/bin/steam-runtime-launch-client";

		private static ProcessStartInfo StartInfo(string[] arguments, bool host)
		{
			ProcessStartInfo processStartInfo = new ProcessStartInfo(host ? "/usr/bin/steam-runtime-launch-client" : "pactl")
			{
				UseShellExecute = false,
				RedirectStandardOutput = true,
				RedirectStandardError = true,
				CreateNoWindow = true
			};
			string[] array;
			if (host)
			{
				array = new string[4] { "--alongside-steam", "--env=LC_ALL=C", "--", "pactl" };
				foreach (string item in array)
				{
					processStartInfo.ArgumentList.Add(item);
				}
			}
			array = arguments;
			foreach (string item2 in array)
			{
				processStartInfo.ArgumentList.Add(item2);
			}
			processStartInfo.Environment["LC_ALL"] = "C";
			processStartInfo.Environment.Remove("LD_PRELOAD");
			return processStartInfo;
		}

		public string Run(params string[] arguments)
		{
			using Process process = new Process
			{
				StartInfo = StartInfo(arguments, host: false)
			};
			try
			{
				try
				{
					process.Start();
				}
				catch (Win32Exception ex) when (ex.NativeErrorCode == 2 && File.Exists("/usr/bin/steam-runtime-launch-client"))
				{
					process.StartInfo = StartInfo(arguments, host: true);
					process.Start();
				}
			}
			catch (Win32Exception ex2)
			{
				throw new InvalidOperationException($"Cannot launch the audio command '{process.StartInfo.FileName}' (error {ex2.NativeErrorCode}): {ex2.Message}. Check that PulseAudio client tools are installed and Steam's launcher service is running.", ex2);
			}
			Task<string> task = process.StandardOutput.ReadToEndAsync();
			Task<string> task2 = process.StandardError.ReadToEndAsync();
			if (!process.WaitForExit(3000))
			{
				try
				{
					process.Kill();
				}
				catch (InvalidOperationException)
				{
				}
				throw new TimeoutException("The audio server did not respond within 3 seconds. Check your audio service, then refresh.");
			}
			if (process.ExitCode != 0)
			{
				throw new InvalidOperationException("pactl " + string.Join(" ", arguments) + " failed: " + task2.GetAwaiter().GetResult().Trim());
			}
			return task.GetAwaiter().GetResult();
		}
	}
	internal sealed class PulseAudioBackend(int processId, Func<string[], string> command) : IAudioBackend
	{
		internal sealed class Sink(uint index, AudioDevice device)
		{
			public uint Index { get; } = index;

			public AudioDevice Device { get; } = device;
		}

		public AudioResult<IReadOnlyList<AudioDevice>> ListOutputs()
		{
			return AudioBoundary.Run((Func<IReadOnlyList<AudioDevice>>)(() => (from sink in ReadSinks(command(new string[3] { "--format=json", "list", "sinks" }))
				select sink.Device).ToArray()));
		}

		public AudioResult<string> Route(string deviceId)
		{
			return AudioBoundary.Run(delegate
			{
				string target = ((deviceId.Length == 0) ? command(new string[1] { "get-default-sink" }).Trim() : deviceId);
				Sink sink = ReadSinks(command(new string[3] { "--format=json", "list", "sinks" })).FirstOrDefault((Sink candidate) => candidate.Device.Id == target);
				if (sink == null)
				{
					return "Selected output is disconnected. Keeping your preference and waiting for it to return.";
				}
				JArray obj = JArray.Parse(command(new string[3] { "--format=json", "list", "sink-inputs" }));
				int num = 0;
				foreach (JToken item in obj)
				{
					JToken obj2 = item[(object)"properties"];
					if (int.TryParse((string)((obj2 != null) ? obj2[(object)"application.process.id"] : null), out var result) && result == processId)
					{
						num++;
						uint num2 = RequiredIndex(item, "index");
						if (RequiredIndex(item, "sink") != sink.Index)
						{
							command(new string[3]
							{
								"move-sink-input",
								num2.ToString(CultureInfo.InvariantCulture),
								sink.Device.Id
							});
						}
					}
				}
				return (num != 0) ? "" : "Waiting for Valheim's audio stream. Your selection will apply when it starts.";
			});
		}

		internal static IReadOnlyList<Sink> ReadSinks(string json)
		{
			List<Sink> list = new List<Sink>();
			foreach (JToken item in JArray.Parse(json))
			{
				string text = (string)item[(object)"name"];
				if (string.IsNullOrWhiteSpace(text))
				{
					throw new FormatException("The audio server returned an output without a name. Refresh to try again.");
				}
				string name = ((string)item[(object)"description"]) ?? text;
				list.Add(new Sink(RequiredIndex(item, "index"), new AudioDevice(text, name)));
			}
			return list;
		}

		private static uint RequiredIndex(JToken item, string name)
		{
			//IL_000e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0014: Invalid comparison between Unknown and I4
			JToken obj = item[(object)name];
			if (obj == null || (int)obj.Type != 6 || !uint.TryParse(((object)item[(object)name])?.ToString(), out var result))
			{
				throw new FormatException("The audio server returned an invalid " + name + ". Refresh to try again.");
			}
			return result;
		}
	}
	internal sealed class WindowsAudioBackend(int processId) : IAudioBackend
	{
		public AudioResult<IReadOnlyList<AudioDevice>> ListOutputs()
		{
			return AudioBoundary.Run(WindowsOutputs.List);
		}

		public AudioResult<string> Route(string deviceId)
		{
			return AudioBoundary.Run(delegate
			{
				using (new WindowsCom.Apartment())
				{
					IReadOnlyList<AudioDevice> readOnlyList = WindowsOutputs.List();
					if (deviceId.Length <= 0 || readOnlyList.Any((AudioDevice device) => device.Id == deviceId))
					{
						bool flag = false;
						if (deviceId.Length > 0)
						{
							IReadOnlyList<string> readOnlyList2 = WindowsAudioSessions.ActiveOutputs((uint)processId, readOnlyList);
							if (readOnlyList2.Count == 0)
							{
								return "Waiting for Valheim's audio stream. Your selection will apply when it starts.";
							}
							flag = readOnlyList2.Any((string id) => !string.Equals(id, deviceId, StringComparison.OrdinalIgnoreCase));
						}
						using WindowsAudioPolicy windowsAudioPolicy = new WindowsAudioPolicy();
						windowsAudioPolicy.Apply((uint)processId, deviceId, flag);
						return flag ? "Applying saved audio output..." : "";
					}
					return "Selected output is disconnected. Keeping your preference and waiting for it to return.";
				}
			});
		}
	}
	internal sealed class WindowsAudioPolicy : IDisposable
	{
		[UnmanagedFunctionPointer(CallingConvention.StdCall)]
		private delegate int SetEndpoint(IntPtr self, uint pid, int flow, int role, IntPtr device);

		[UnmanagedFunctionPointer(CallingConvention.StdCall)]
		private delegate int GetEndpoint(IntPtr self, uint pid, int flow, int role, out IntPtr device);

		private sealed class HString : IDisposable
		{
			public IntPtr Pointer { get; }

			public HString(string value)
			{
				if (value.Length != 0)
				{
					WindowsCom.Check(WindowsCreateString(value, (uint)value.Length, out var handle), "Prepare Windows audio identifier");
					Pointer = handle;
				}
			}

			public void Dispose()
			{
				if (Pointer != IntPtr.Zero)
				{
					WindowsDeleteString(Pointer);
				}
			}
		}

		private readonly WindowsCom factory;

		private const string RuntimeClass = "Windows.Media.Internal.AudioPolicyConfig";

		private const string Prefix = "\\\\?\\SWD#MMDEVAPI#";

		private const string Suffix = "#{e6327cad-dcec-4949-ae8a-991e976a79d2}";

		[DllImport("combase.dll", ExactSpelling = true)]
		private static extern int WindowsCreateString([MarshalAs(UnmanagedType.LPWStr)] string value, uint length, out IntPtr handle);

		[DllImport("combase.dll", ExactSpelling = true)]
		private static extern int WindowsDeleteString(IntPtr handle);

		[DllImport("combase.dll", ExactSpelling = true)]
		private static extern IntPtr WindowsGetStringRawBuffer(IntPtr handle, out uint length);

		[DllImport("combase.dll", ExactSpelling = true)]
		private static extern int RoGetActivationFactory(IntPtr name, ref Guid iid, out IntPtr factory);

		public WindowsAudioPolicy()
		{
			using HString hString = new HString("Windows.Media.Internal.AudioPolicyConfig");
			Guid iid = new Guid("AB3D4648-E242-459F-B02F-541C70306324");
			IntPtr pointer;
			int num = RoGetActivationFactory(hString.Pointer, ref iid, out pointer);
			if (num < 0)
			{
				Guid iid2 = new Guid("2A59116D-6C4F-45E0-A74F-707E3FEF9258");
				num = RoGetActivationFactory(hString.Pointer, ref iid2, out pointer);
			}
			WindowsCom.Check(num, "Open Windows per-app audio policy");
			factory = new WindowsCom(pointer);
		}

		private string Read(uint pid, int role)
		{
			WindowsCom.Check(factory.Method<GetEndpoint>(26)(factory.Pointer, pid, 0, role, out var device), "Read Valheim output preference");
			try
			{
				uint length;
				IntPtr ptr = WindowsGetStringRawBuffer(device, out length);
				return (length == 0) ? "" : (Marshal.PtrToStringUni(ptr, checked((int)length)) ?? "");
			}
			finally
			{
				WindowsDeleteString(device);
			}
		}

		private int Write(uint pid, int role, string endpoint)
		{
			using HString hString = new HString(endpoint);
			return factory.Method<SetEndpoint>(25)(factory.Pointer, pid, 0, role, hString.Pointer);
		}

		public void Apply(uint pid, string id, bool refresh = false)
		{
			Apply((id.Length == 0) ? "" : ("\\\\?\\SWD#MMDEVAPI#" + id + "#{e6327cad-dcec-4949-ae8a-991e976a79d2}"), (int role) => Read(pid, role), (int role, string value) => Write(pid, role, value), refresh);
		}

		internal static void Apply(string endpoint, Func<int, string> read, Func<int, string, int> write, bool refresh)
		{
			string console = read(0);
			string multimedia = read(1);
			bool flag = string.Equals(console, endpoint, StringComparison.OrdinalIgnoreCase) && string.Equals(multimedia, endpoint, StringComparison.OrdinalIgnoreCase);
			if (!flag || refresh)
			{
				if (flag && endpoint.Length > 0)
				{
					WritePair("");
				}
				WritePair(endpoint);
			}
			void WritePair(string value)
			{
				int num = write(0, value);
				if (num >= 0)
				{
					num = write(1, value);
				}
				if (num < 0)
				{
					int num2 = write(0, console);
					int num3 = write(1, multimedia);
					if (num2 < 0 || num3 < 0)
					{
						throw new COMException($"Windows could not apply or restore Valheim's output preferences (console 0x{num2:X8}, multimedia 0x{num3:X8}). Select System default or use Windows Volume mixer.", num);
					}
					WindowsCom.Check(num, "Set Valheim output (previous preferences restored)");
				}
			}
		}

		public void Dispose()
		{
			factory.Dispose();
		}
	}
	internal static class WindowsAudioSessions
	{
		[UnmanagedFunctionPointer(CallingConvention.StdCall)]
		private delegate int GetDevice(IntPtr self, [MarshalAs(UnmanagedType.LPWStr)] string id, out IntPtr device);

		[UnmanagedFunctionPointer(CallingConvention.StdCall)]
		private delegate int Activate(IntPtr self, ref Guid iid, uint context, IntPtr parameters, out IntPtr instance);

		[UnmanagedFunctionPointer(CallingConvention.StdCall)]
		private delegate int GetEnumerator(IntPtr self, out IntPtr sessions);

		[UnmanagedFunctionPointer(CallingConvention.StdCall)]
		private delegate int GetCount(IntPtr self, out int count);

		[UnmanagedFunctionPointer(CallingConvention.StdCall)]
		private delegate int GetSession(IntPtr self, int index, out IntPtr session);

		[UnmanagedFunctionPointer(CallingConvention.StdCall)]
		private delegate int GetProcessId(IntPtr self, out uint pid);

		[UnmanagedFunctionPointer(CallingConvention.StdCall)]
		private delegate int GetState(IntPtr self, out int state);

		internal static IReadOnlyList<string> ActiveOutputs(uint processId, IReadOnlyList<AudioDevice> outputs)
		{
			Guid clsid = new Guid("BCDE0395-E52F-467C-8E3D-C4579291692E");
			Guid iid = new Guid("A95664D2-9614-4F35-A746-DE8DB63617E6");
			WindowsCom.Check(WindowsCom.CoCreateInstance(ref clsid, IntPtr.Zero, 1u, ref iid, out var instance), "Discover Valheim audio sessions");
			using WindowsCom windowsCom = new WindowsCom(instance);
			List<string> list = new List<string>();
			foreach (AudioDevice output in outputs)
			{
				WindowsCom.Check(windowsCom.Method<GetDevice>(5)(instance, output.Id, out var device), "Open Windows audio output for session inspection");
				using WindowsCom windowsCom2 = new WindowsCom(device);
				Guid iid2 = new Guid("77AA99A0-1BD6-484F-8BC7-2C654C9A9B6F");
				WindowsCom.Check(windowsCom2.Method<Activate>(3)(device, ref iid2, 1u, IntPtr.Zero, out var instance2), "Inspect Windows audio sessions");
				using WindowsCom windowsCom3 = new WindowsCom(instance2);
				WindowsCom.Check(windowsCom3.Method<GetEnumerator>(5)(instance2, out var sessions), "Enumerate Windows audio sessions");
				using WindowsCom windowsCom4 = new WindowsCom(sessions);
				WindowsCom.Check(windowsCom4.Method<GetCount>(3)(sessions, out var count), "Count Windows audio sessions");
				for (int i = 0; i < count; i++)
				{
					WindowsCom.Check(windowsCom4.Method<GetSession>(4)(sessions, i, out var session), "Read Windows audio session");
					using (new WindowsCom(session))
					{
						Guid iid3 = new Guid("BFB7FF88-7239-4FC9-8FA2-07C950BE9C6D");
						WindowsCom.Check(Marshal.QueryInterface(session, in iid3, out var ppv), "Inspect audio session owner");
						using WindowsCom windowsCom5 = new WindowsCom(ppv);
						uint pid;
						int num = windowsCom5.Method<GetProcessId>(14)(ppv, out pid);
						WindowsCom.Check(num, "Read audio session process ID");
						if (num == 0 && pid == processId)
						{
							WindowsCom.Check(windowsCom5.Method<GetState>(3)(ppv, out var state), "Read Valheim audio session state");
							if (state == 1)
							{
								list.Add(output.Id);
								break;
							}
						}
					}
				}
			}
			return list;
		}
	}
	internal sealed class WindowsCom(IntPtr pointer) : IDisposable
	{
		internal sealed class Apartment : IDisposable
		{
			private readonly bool initialized;

			public Apartment()
			{
				int num = RoInitialize(1u);
				if (num != -2147417850)
				{
					Check(num, "Initialize Windows audio COM");
				}
				initialized = num >= 0;
			}

			public void Dispose()
			{
				if (initialized)
				{
					RoUninitialize();
				}
			}
		}

		public IntPtr Pointer { get; private set; } = pointer;

		public T Method<T>(int slot) where T : Delegate
		{
			return Marshal.GetDelegateForFunctionPointer<T>(Marshal.ReadIntPtr(Marshal.ReadIntPtr(Pointer), slot * IntPtr.Size));
		}

		public void Dispose()
		{
			if (!(Pointer == IntPtr.Zero))
			{
				Marshal.Release(Pointer);
				Pointer = IntPtr.Zero;
			}
		}

		public static void Check(int result, string operation)
		{
			if (result < 0)
			{
				throw new COMException($"{operation} failed (0x{result:X8}). Check Windows audio settings and refresh.", result);
			}
		}

		[DllImport("combase.dll", ExactSpelling = true)]
		private static extern int RoInitialize(uint mode);

		[DllImport("combase.dll", ExactSpelling = true)]
		private static extern void RoUninitialize();

		[DllImport("ole32.dll", ExactSpelling = true)]
		internal static extern int CoCreateInstance(ref Guid clsid, IntPtr outer, uint context, ref Guid iid, out IntPtr instance);
	}
	internal static class WindowsOutputs
	{
		[UnmanagedFunctionPointer(CallingConvention.StdCall)]
		private delegate int EnumEndpoints(IntPtr self, int flow, uint mask, out IntPtr collection);

		[UnmanagedFunctionPointer(CallingConvention.StdCall)]
		private delegate int GetCount(IntPtr self, out uint count);

		[UnmanagedFunctionPointer(CallingConvention.StdCall)]
		private delegate int GetItem(IntPtr self, uint index, out IntPtr device);

		[UnmanagedFunctionPointer(CallingConvention.StdCall)]
		private delegate int GetId(IntPtr self, out IntPtr id);

		[UnmanagedFunctionPointer(CallingConvention.StdCall)]
		private delegate int OpenProperties(IntPtr self, uint access, out IntPtr properties);

		[UnmanagedFunctionPointer(CallingConvention.StdCall)]
		private delegate int GetValue(IntPtr self, ref PropertyKey key, out PropVariant value);

		private struct PropertyKey
		{
			public Guid Format;

			public uint Id;
		}

		[StructLayout(LayoutKind.Explicit, Size = 24)]
		private struct PropVariant
		{
			[FieldOffset(0)]
			public ushort Type;

			[FieldOffset(8)]
			public IntPtr String;
		}

		[DllImport("ole32.dll", ExactSpelling = true)]
		private static extern int PropVariantClear(ref PropVariant value);

		public static IReadOnlyList<AudioDevice> List()
		{
			using (new WindowsCom.Apartment())
			{
				Guid clsid = new Guid("BCDE0395-E52F-467C-8E3D-C4579291692E");
				Guid iid = new Guid("A95664D2-9614-4F35-A746-DE8DB63617E6");
				WindowsCom.Check(WindowsCom.CoCreateInstance(ref clsid, IntPtr.Zero, 1u, ref iid, out var instance), "Discover Windows outputs");
				using WindowsCom windowsCom = new WindowsCom(instance);
				WindowsCom.Check(windowsCom.Method<EnumEndpoints>(3)(instance, 0, 1u, out var collection), "List Windows outputs");
				using WindowsCom windowsCom2 = new WindowsCom(collection);
				WindowsCom.Check(windowsCom2.Method<GetCount>(3)(collection, out var count), "Count Windows outputs");
				List<AudioDevice> list = new List<AudioDevice>();
				for (uint num = 0u; num < count; num++)
				{
					WindowsCom.Check(windowsCom2.Method<GetItem>(4)(collection, num, out var device), "Read Windows output");
					using WindowsCom windowsCom3 = new WindowsCom(device);
					WindowsCom.Check(windowsCom3.Method<GetId>(5)(device, out var id), "Read Windows output ID");
					string text;
					try
					{
						text = Marshal.PtrToStringUni(id) ?? throw new InvalidOperationException("Windows returned an empty output ID.");
					}
					finally
					{
						Marshal.FreeCoTaskMem(id);
					}
					WindowsCom.Check(windowsCom3.Method<OpenProperties>(4)(device, 0u, out var properties), "Read Windows output properties");
					using WindowsCom windowsCom4 = new WindowsCom(properties);
					PropertyKey key = new PropertyKey
					{
						Format = new Guid("A45C254E-DF1C-4EFD-8020-67D146A850E0"),
						Id = 14u
					};
					WindowsCom.Check(windowsCom4.Method<GetValue>(5)(properties, ref key, out var value), "Read Windows output name");
					try
					{
						string name = ((value.Type == 31) ? (Marshal.PtrToStringUni(value.String) ?? text) : text);
						list.Add(new AudioDevice(text, name));
					}
					finally
					{
						PropVariantClear(ref value);
					}
				}
				return list;
			}
		}
	}
}