Decompiled source of PeakSelfHostedPhoton Voice Fix v1.0.0

PeakSelfHostedPhotonVoiceFix.dll

Decompiled 2 months ago
using System;
using System.Diagnostics;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.Versioning;
using BepInEx;
using BepInEx.Logging;
using HarmonyLib;
using Microsoft.CodeAnalysis;

[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("PeakSelfHostedPhotonVoiceFix")]
[assembly: AssemblyConfiguration("Release")]
[assembly: AssemblyDescription("Makes PEAK voice chat work on a self-hosted Photon server.")]
[assembly: AssemblyFileVersion("1.0.0.0")]
[assembly: AssemblyInformationalVersion("1.0.0+0bb38800ed9e8a3ff671f16e7e0f33249f10ec01")]
[assembly: AssemblyProduct("PeakSelfHostedPhoton Voice Fix")]
[assembly: AssemblyTitle("PeakSelfHostedPhotonVoiceFix")]
[assembly: AssemblyVersion("1.0.0.0")]
[module: RefSafetyRules(11)]
namespace Microsoft.CodeAnalysis
{
	[CompilerGenerated]
	[Embedded]
	internal sealed class EmbeddedAttribute : Attribute
	{
	}
}
namespace System.Runtime.CompilerServices
{
	[CompilerGenerated]
	[Embedded]
	[AttributeUsage(AttributeTargets.Module, AllowMultiple = false, Inherited = false)]
	internal sealed class RefSafetyRulesAttribute : Attribute
	{
		public readonly int Version;

		public RefSafetyRulesAttribute(int P_0)
		{
			Version = P_0;
		}
	}
}
namespace PeakSelfHostedPhotonVoiceFix
{
	[BepInPlugin("peak.selfhostedphoton.voicefix", "PeakSelfHostedPhoton Voice Fix", "1.0.0")]
	public class Plugin : BaseUnityPlugin
	{
		public const string PluginGuid = "peak.selfhostedphoton.voicefix";

		public const string PluginName = "PeakSelfHostedPhoton Voice Fix";

		public const string PluginVersion = "1.0.0";

		internal static ManualLogSource Log;

		private void Awake()
		{
			//IL_0035: Unknown result type (might be due to invalid IL or missing references)
			//IL_003b: Expected O, but got Unknown
			Log = ((BaseUnityPlugin)this).Logger;
			Diag.Enabled = ((BaseUnityPlugin)this).Config.Bind<bool>("Diagnostics", "LogFrameCounters", false, "Log voice frame counters every 250 frames (built / accepted / refused / client state). Only useful when working out why voice is silent. Off by default - it writes a line roughly every five seconds while anyone is talking.").Value;
			Harmony harmony = new Harmony("peak.selfhostedphoton.voicefix");
			int num = 0 + TryPatch(harmony, typeof(CppCompatPatch), CppCompatPatch.ResolveTarget()) + TryPatch(harmony, typeof(Transport2Patch), Transport2Patch.ResolveTarget()) + TryPatch(harmony, typeof(LegacyProtocolPatch), LegacyProtocolPatch.ResolveTarget());
			if (Diag.Enabled)
			{
				TryPatch(harmony, typeof(RaiseEventProbe), RaiseEventProbe.ResolveTarget());
			}
			if (num == 0)
			{
				((BaseUnityPlugin)this).Logger.LogError((object)"No voice frame builder found. Photon Voice must have changed - voice will still break on GpBinaryV16.");
			}
		}

		private int TryPatch(Harmony harmony, Type patchClass, MethodBase target)
		{
			if (target == null)
			{
				((BaseUnityPlugin)this).Logger.LogWarning((object)("Skipped " + patchClass.Name + ": target method not found."));
				return 0;
			}
			try
			{
				harmony.PatchAll(patchClass);
				((BaseUnityPlugin)this).Logger.LogInfo((object)("Patched " + target.DeclaringType.FullName + "." + target.Name));
				return 1;
			}
			catch (Exception ex)
			{
				((BaseUnityPlugin)this).Logger.LogError((object)("Failed to patch " + target.DeclaringType.FullName + "." + target.Name + ": " + ex));
				return 0;
			}
		}
	}
	[HarmonyPatch]
	internal static class CppCompatPatch
	{
		internal static MethodBase ResolveTarget()
		{
			Type type = AccessTools.TypeByName("Photon.Voice.LoadBalancingTransport2");
			if (type == null)
			{
				return null;
			}
			ConstructorInfo[] constructors = type.GetConstructors(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
			foreach (ConstructorInfo constructorInfo in constructors)
			{
				ParameterInfo[] parameters = constructorInfo.GetParameters();
				if (parameters.Length == 3 && parameters[2].ParameterType == typeof(bool) && parameters[2].Name == "cppCompatibilityMode")
				{
					return constructorInfo;
				}
			}
			return null;
		}

		[HarmonyTargetMethod]
		private static MethodBase TargetMethod()
		{
			return ResolveTarget();
		}

		[HarmonyPrefix]
		private static void Prefix(ref bool cppCompatibilityMode)
		{
			if (!cppCompatibilityMode)
			{
				cppCompatibilityMode = true;
				Plugin.Log.LogInfo((object)"Forced cppCompatibilityMode=true: voice frames will use Unreliable instead of UnreliableUnsequenced, which Photon Server 4 does not understand.");
			}
		}
	}
	[HarmonyPatch]
	internal static class Transport2Patch
	{
		private static Type _sliceType;

		private static FieldInfo _fBuffer;

		private static FieldInfo _fCount;

		private static FieldInfo _fOffset;

		private static MethodInfo _mRelease;

		private static bool _initialised;

		private static bool _reported;

		internal static MethodBase ResolveTarget()
		{
			Type type = AccessTools.TypeByName("Photon.Voice.LoadBalancingTransport2");
			if (!(type == null))
			{
				return AccessTools.Method(type, "buildFrameMessage", (Type[])null, (Type[])null);
			}
			return null;
		}

		[HarmonyTargetMethod]
		private static MethodBase TargetMethod()
		{
			return ResolveTarget();
		}

		private static void Init()
		{
			_initialised = true;
			_sliceType = AccessTools.TypeByName("ExitGames.Client.Photon.ByteArraySlice");
			if (!(_sliceType == null))
			{
				_fBuffer = AccessTools.Field(_sliceType, "Buffer");
				_fCount = AccessTools.Field(_sliceType, "Count");
				_fOffset = AccessTools.Field(_sliceType, "Offset");
				_mRelease = AccessTools.Method(_sliceType, "Release", (Type[])null, (Type[])null);
			}
		}

		[HarmonyPostfix]
		private static void Postfix(ref object __result)
		{
			if (__result == null)
			{
				return;
			}
			if (!_initialised)
			{
				Init();
			}
			if (_sliceType == null || _fBuffer == null || _fCount == null || _fOffset == null || !_sliceType.IsInstanceOfType(__result) || !(_fBuffer.GetValue(__result) is byte[] array))
			{
				return;
			}
			int num = (int)_fCount.GetValue(__result);
			int num2 = (int)_fOffset.GetValue(__result);
			if (num <= 0 || num2 < 0 || num2 + num > array.Length)
			{
				return;
			}
			byte[] array2 = new byte[num];
			Buffer.BlockCopy(array, num2, array2, 0, num);
			if (_mRelease != null)
			{
				try
				{
					_mRelease.Invoke(__result, null);
				}
				catch
				{
				}
			}
			__result = array2;
			Diag.FramesBuilt++;
			if (!_reported)
			{
				_reported = true;
				Plugin.Log.LogInfo((object)("Voice frames are now sent as byte[] instead of ByteArraySlice (" + num + " bytes in the first frame)."));
			}
		}
	}
	internal static class Diag
	{
		internal static bool Enabled;

		internal static long FramesBuilt;

		internal static long RaiseOk;

		internal static long RaiseFail;
	}
	[HarmonyPatch]
	internal static class RaiseEventProbe
	{
		private static PropertyInfo _stateProp;

		private static bool _init;

		internal static MethodBase ResolveTarget()
		{
			Type type = AccessTools.TypeByName("Photon.Realtime.LoadBalancingClient");
			if (!(type == null))
			{
				return AccessTools.Method(type, "OpRaiseEvent", (Type[])null, (Type[])null);
			}
			return null;
		}

		[HarmonyTargetMethod]
		private static MethodBase TargetMethod()
		{
			return ResolveTarget();
		}

		[HarmonyPostfix]
		private static void Postfix(object __instance, bool __result)
		{
			try
			{
				if (__instance == null || __instance.GetType().Name != "LoadBalancingTransport2")
				{
					return;
				}
				if (__result)
				{
					Diag.RaiseOk++;
				}
				else
				{
					Diag.RaiseFail++;
				}
				if ((Diag.RaiseOk + Diag.RaiseFail) % 250 != 0L)
				{
					return;
				}
				if (!_init)
				{
					_init = true;
					_stateProp = AccessTools.Property(__instance.GetType(), "State");
				}
				string text = "?";
				if (_stateProp != null)
				{
					try
					{
						text = _stateProp.GetValue(__instance, null).ToString();
					}
					catch
					{
					}
				}
				Plugin.Log.LogInfo((object)("voice: built=" + Diag.FramesBuilt + " raiseOk=" + Diag.RaiseOk + " raiseFail=" + Diag.RaiseFail + " state=" + text));
			}
			catch
			{
			}
		}
	}
	[HarmonyPatch]
	internal static class LegacyProtocolPatch
	{
		private static bool _reported;

		internal static MethodBase ResolveTarget()
		{
			Type type = AccessTools.TypeByName("Photon.Voice.PhotonTransportProtocol");
			if (!(type == null))
			{
				return AccessTools.Method(type, "buildFrameMessage", (Type[])null, (Type[])null);
			}
			return null;
		}

		[HarmonyTargetMethod]
		private static MethodBase TargetMethod()
		{
			return ResolveTarget();
		}

		[HarmonyPostfix]
		private static void Postfix(object[] __result)
		{
			if (__result == null || __result.Length < 3 || !(__result[2] is ArraySegment<byte>))
			{
				return;
			}
			ArraySegment<byte> arraySegment = (ArraySegment<byte>)__result[2];
			if (arraySegment.Array != null)
			{
				byte[] array = new byte[arraySegment.Count];
				Buffer.BlockCopy(arraySegment.Array, arraySegment.Offset, array, 0, arraySegment.Count);
				__result[2] = array;
				if (!_reported)
				{
					_reported = true;
					Plugin.Log.LogInfo((object)"Voice frames (legacy path) are now sent as byte[] instead of ArraySegment<byte>.");
				}
			}
		}
	}
}