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>.");
}
}
}
}
}