Decompiled source of PlayerSync v1.0.2

PlayerSync.dll

Decompiled 2 days ago
using System;
using System.Collections.Generic;
using System.Diagnostics;
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 BepInEx.Logging;
using BepInEx.Unity.IL2CPP;
using Gear;
using Globals;
using HarmonyLib;
using Il2CppInterop.Runtime.Injection;
using Il2CppInterop.Runtime.InteropTypes;
using Il2CppInterop.Runtime.InteropTypes.Arrays;
using Il2CppSystem;
using Il2CppSystem.Collections.Generic;
using Microsoft.CodeAnalysis;
using Player;
using PlayerSync.Interop;
using PlayerSync.Network;
using PlayerSync.Network.Impl;
using PlayerSync.Sync.Ammo;
using PlayerSync.Sync.Stamina;
using SNetwork;
using UnityEngine;

[assembly: CompilationRelaxations(8)]
[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
[assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)]
[assembly: TargetFramework(".NETCoreApp,Version=v6.0", FrameworkDisplayName = "")]
[assembly: AssemblyCompany("PlayerSync")]
[assembly: AssemblyConfiguration("Release")]
[assembly: AssemblyFileVersion("1.0.0.0")]
[assembly: AssemblyInformationalVersion("1.0.0")]
[assembly: AssemblyProduct("PlayerSync")]
[assembly: AssemblyTitle("PlayerSync")]
[assembly: AssemblyVersion("1.0.0.0")]
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 PlayerSync
{
	internal static class ConfigMgr
	{
		private static readonly ConfigFile Conf;

		private static readonly FileSystemWatcher? ConfigWatcher;

		private static readonly ConfigEntry<float> SyncFrequencyConf;

		private static readonly ConfigEntry<bool> DebugConf;

		public static bool Processed { get; private set; }

		public static float SyncFrequency => SyncFrequencyConf.Value;

		public static float SyncInterval => 1f / SyncFrequency;

		public static bool Debug => DebugConf.Value;

		public static void Init()
		{
			Logger.Info($"debug={Debug}");
			Process();
		}

		public static void Process()
		{
			Processed = true;
		}

		static ConfigMgr()
		{
			//IL_0029: Unknown result type (might be due to invalid IL or missing references)
			//IL_0033: Expected O, but got Unknown
			//IL_00cd: Unknown result type (might be due to invalid IL or missing references)
			//IL_00d7: Expected O, but got Unknown
			string text = "PlayerSync.cfg";
			string text2 = Path.Combine(Paths.ConfigPath, text);
			Logger.Info("cfgPath = " + text2);
			Conf = new ConfigFile(text2, true);
			ConfigWatcher = new FileSystemWatcher(Paths.ConfigPath, text)
			{
				NotifyFilter = NotifyFilters.LastWrite,
				EnableRaisingEvents = true
			};
			ConfigWatcher.Changed += async delegate
			{
				ConfigWatcher.EnableRaisingEvents = false;
				await Task.Delay(500);
				Logger.Debug("Reloading config...");
				Conf.Reload();
				await Task.Delay(250);
				Process();
				await Task.Delay(250);
				ConfigWatcher.EnableRaisingEvents = true;
			};
			int num = 1;
			string text3 = $"({num++}) Advanced";
			SyncFrequencyConf = Conf.Bind<float>(text3, "Sync Frequency (Hz)", 20f, new ConfigDescription("Frequency in Hz to send player sync data", (AcceptableValueBase)(object)new AcceptableValueRange<float>(1f, 60f), Array.Empty<object>()));
			text3 = "(Z) Dev";
			DebugConf = Conf.Bind<bool>(text3, "Enable Debug Logs", false, "debug logging");
		}
	}
	[HarmonyPatch]
	public static class Events
	{
		public static event Action? OnSessionEnd;

		public static event Action? OnSessionStart;

		public static event Action? OnAnyPlayerDeath;

		public static event Action? OnCheckpointReload;

		public static event Action? OnWeaponFire;

		public static event Action? OnWeaponReload;

		public static event Action<SNet_Player>? OnPlayerJoinLobby;

		[HarmonyPatch(typeof(GS_ReadyToStopElevatorRide), "Enter")]
		[HarmonyPostfix]
		private static void GS_ReadyToStopElevatorRide_Enter()
		{
			Events.OnSessionStart?.Invoke();
		}

		[HarmonyPatch(typeof(PLOC_Downed), "SyncEnter")]
		[HarmonyPostfix]
		private static void PLOC_Downed_SyncEnter()
		{
			Events.OnAnyPlayerDeath?.Invoke();
		}

		[HarmonyPatch(typeof(RundownManager), "EndGameSession")]
		[HarmonyPrefix]
		private static void EndGameSession()
		{
			Events.OnSessionEnd?.Invoke();
		}

		[HarmonyPatch(typeof(SNet_SessionHub), "LeaveHub")]
		[HarmonyPrefix]
		private static void LeaveHub()
		{
			Events.OnSessionEnd?.Invoke();
		}

		[HarmonyPatch(typeof(SNet_SessionHub), "OnJoinedLobby")]
		[HarmonyPrefix]
		private static void PlayerJoin(SNet_Player player)
		{
			Events.OnPlayerJoinLobby?.Invoke(player);
		}

		[HarmonyPatch(typeof(CheckpointManager), "OnStateChange")]
		[HarmonyPrefix]
		private static void CheckpointStateChange(pCheckpointState oldState, pCheckpointState newState)
		{
			//IL_0000: Unknown result type (might be due to invalid IL or missing references)
			//IL_0001: Unknown result type (might be due to invalid IL or missing references)
			//IL_0007: Invalid comparison between Unknown and I4
			if ((int)newState.lastInteraction == 2)
			{
				Events.OnCheckpointReload?.Invoke();
			}
		}

		[HarmonyPatch(typeof(BulletWeapon), "Fire")]
		[HarmonyPostfix]
		private static void BulletWeaponFire()
		{
			Events.OnWeaponFire?.Invoke();
		}

		[HarmonyPatch(typeof(PlayerInventoryLocal), "DoReload")]
		[HarmonyPostfix]
		private static void PlayerReload()
		{
			Events.OnWeaponReload?.Invoke();
		}

		[HarmonyPatch(typeof(Shotgun), "Fire")]
		[HarmonyPostfix]
		private static void ShotgunFire()
		{
			Events.OnWeaponFire?.Invoke();
		}
	}
	internal static class Logger
	{
		private static ManualLogSource _mLogSource;

		public static bool Ready => _mLogSource != null;

		public static void Setup()
		{
			_mLogSource = Logger.CreateLogSource("io.takina.gtfo.PlayerSync");
		}

		public static void SetupFromInit(ManualLogSource logSource)
		{
			_mLogSource = logSource;
		}

		private static string Format(object data)
		{
			return data.ToString();
		}

		public static void Debug(object msg)
		{
			if (ConfigMgr.Debug)
			{
				_mLogSource.LogInfo((object)(" [DEBUG] " + Format(msg)));
			}
		}

		public static void Debug(string fmt, params object[] args)
		{
			if (ConfigMgr.Debug)
			{
				_mLogSource.LogInfo((object)("[DEBUG] " + Format(string.Format(fmt, args))));
			}
		}

		public static void Info(object msg)
		{
			_mLogSource.LogInfo((object)Format(msg));
		}

		public static void Info(string fmt, params object[] args)
		{
			_mLogSource.LogInfo((object)Format(string.Format(fmt, args)));
		}

		public static void Warn(object msg)
		{
			_mLogSource.LogWarning((object)Format(msg));
		}

		public static void Warn(string fmt, params object[] args)
		{
			_mLogSource.LogWarning((object)Format(string.Format(fmt, args)));
		}

		public static void Error(object msg)
		{
			_mLogSource.LogError((object)Format(msg));
		}

		public static void Error(string fmt, params object[] args)
		{
			_mLogSource.LogError((object)Format(string.Format(fmt, args)));
		}

		public static void Fatal(object msg)
		{
			_mLogSource.LogFatal((object)Format(msg));
		}

		public static void Fatal(string fmt, params object[] args)
		{
			_mLogSource.LogFatal((object)Format(string.Format(fmt, args)));
		}
	}
	[BepInPlugin("io.takina.gtfo.PlayerSync", "PlayerSync", "1.0.2")]
	public class Plugin : BasePlugin
	{
		public const string NAME = "PlayerSync";

		public const string GUID = "io.takina.gtfo.PlayerSync";

		public const string VERSION = "1.0.2";

		public static readonly PlugVersion PlugVersion = new PlugVersion("1.0.2");

		public static GameObject? PluginObject;

		public event Action? OnManagersSetup;

		public override void Load()
		{
			//IL_0019: Unknown result type (might be due to invalid IL or missing references)
			//IL_005e: Expected O, but got Unknown
			Logger.Setup();
			Logger.Info("PlayerSync [io.takina.gtfo.PlayerSync @ 1.0.2]");
			Harmony val = new Harmony("io.takina.gtfo.PlayerSync");
			ConfigMgr.Init();
			RegisterIl2CppTypes();
			OnManagersSetup += Initialize;
			Global.OnAllManagersSetup += Action.op_Implicit(this.OnManagersSetup);
			Logger.Info("Patching...");
			PatchAll(val);
			Logger.Info("Finished Patching");
		}

		private static void RegisterIl2CppTypes()
		{
			ClassInjector.RegisterTypeInIl2Cpp<PeerInfoManager>();
			ClassInjector.RegisterTypeInIl2Cpp<AmmoSync>();
			ClassInjector.RegisterTypeInIl2Cpp<StaminaSync>();
		}

		private static void PatchAll(Harmony h)
		{
			h.PatchAll(typeof(Net));
			h.PatchAll(typeof(Events));
			h.PatchAll(typeof(AmmoSync));
		}

		private void Initialize()
		{
			//IL_0005: Unknown result type (might be due to invalid IL or missing references)
			//IL_000f: Expected O, but got Unknown
			PluginObject = new GameObject("io.takina.gtfo.PlayerSync");
			Object.DontDestroyOnLoad((Object)(object)PluginObject);
			PluginObject.AddComponent<PeerInfoManager>();
			PluginObject.AddComponent<AmmoSync>();
			PluginObject.AddComponent<StaminaSync>();
		}

		public override bool Unload()
		{
			return true;
		}
	}
	public static class Util
	{
		public static void OffsetDevFloat32(ref DevFloat32 val, float offset)
		{
			val.internalValue += offset;
		}
	}
}
namespace PlayerSync.Sync.Stamina
{
	public struct StaminaInfo
	{
		public float Stamina;

		public byte[] Serialize()
		{
			byte[] array = new byte[Unsafe.SizeOf<StaminaInfo>()];
			MemoryMarshal.Write(array, ref this);
			return array;
		}

		public static StaminaInfo Deserialize(byte[] data)
		{
			return MemoryMarshal.Read<StaminaInfo>(data);
		}
	}
	[RegisterIl2Cpp]
	public class StaminaSync : MonoBehaviour
	{
		public const byte PacketId = 83;

		private static float _nextSyncTime = 0f;

		private static readonly Dictionary<ulong, StaminaInfo> StaminaInfos = new Dictionary<ulong, StaminaInfo>();

		private void Awake()
		{
			Net.RegisterHandler(83, HandlePacket);
		}

		private void OnDestroy()
		{
			Net.UnregisterHandler(83);
		}

		private void Update()
		{
			if (Clock.Time < _nextSyncTime)
			{
				return;
			}
			_nextSyncTime = Clock.Time + ConfigMgr.SyncInterval;
			SNet_SessionHub sessionHub = SNet.SessionHub;
			ulong[] array = StaminaInfos.Keys.ToArray();
			foreach (ulong num in array)
			{
				if (!sessionHub.IsPlayerInHub(NetHelper.GetPlayerByID(num)))
				{
					StaminaInfos.Remove(num);
				}
			}
			NetHelper.InvokeWithAllPlayers(SendStaminaInfo);
		}

		public static bool GetStaminaInfo(SNet_Player player, out StaminaInfo info)
		{
			return StaminaInfos.TryGetValue(player.Lookup, out info);
		}

		private static void SendStaminaInfo(SNet_Player target)
		{
			if (PeerInfoManager.Supported(target) && GetLocalStaminaInfo(out var info))
			{
				Net.SendBytes(info.Serialize(), 83, target);
			}
		}

		private static bool GetLocalStaminaInfo(out StaminaInfo info)
		{
			info = default(StaminaInfo);
			PlayerAgent localPlayerAgent = PlayerManager.GetLocalPlayerAgent();
			PlayerStamina val = ((localPlayerAgent != null) ? localPlayerAgent.Stamina : null);
			if ((Object)(object)val == (Object)null)
			{
				return false;
			}
			info.Stamina = val.Stamina;
			return true;
		}

		private static void HandlePacket(byte[] data, SNet_Player? sender)
		{
			StaminaInfo value = StaminaInfo.Deserialize(data);
			StaminaInfos[sender.Lookup] = value;
		}
	}
}
namespace PlayerSync.Sync.Ammo
{
	public struct AmmoClipInfo
	{
		public ulong Lookup;

		public int StandardClipAmmo;

		public int SpecialClipAmmo;

		public int ClassClipAmmo;

		public int ResourcePackClipAmmo;

		public int ConsumableClipAmmo;

		public int GetClip(InventorySlot slot)
		{
			//IL_0000: Unknown result type (might be due to invalid IL or missing references)
			//IL_0002: Unknown result type (might be due to invalid IL or missing references)
			//IL_001c: Expected I4, but got Unknown
			//IL_0059: Unknown result type (might be due to invalid IL or missing references)
			return (slot - 1) switch
			{
				0 => StandardClipAmmo, 
				1 => SpecialClipAmmo, 
				2 => ClassClipAmmo, 
				3 => ResourcePackClipAmmo, 
				4 => ConsumableClipAmmo, 
				_ => throw new ArgumentException($"Invalid inventory slot: {slot}"), 
			};
		}

		public byte[] Serialize()
		{
			byte[] array = new byte[Unsafe.SizeOf<AmmoClipInfo>()];
			MemoryMarshal.Write(array, ref this);
			return array;
		}

		public static AmmoClipInfo Deserialize(byte[] data)
		{
			return MemoryMarshal.Read<AmmoClipInfo>(data);
		}
	}
	[RegisterIl2Cpp]
	[HarmonyPatch]
	public class AmmoSync : MonoBehaviour
	{
		public const byte PacketId = 65;

		private static float _nextSyncTime = 0f;

		private static readonly List<InventorySlot> SyncedSlots = new List<InventorySlot>
		{
			(InventorySlot)1,
			(InventorySlot)2,
			(InventorySlot)3,
			(InventorySlot)4,
			(InventorySlot)5
		};

		private void Awake()
		{
			Net.RegisterHandler(65, HandlePacket);
			Events.OnWeaponFire += SendAmmoUpdateToAll;
			Events.OnWeaponReload += SendAmmoUpdateToAll;
		}

		private void OnDestroy()
		{
			Net.UnregisterHandler(65);
			Events.OnWeaponFire -= SendAmmoUpdateToAll;
			Events.OnWeaponReload -= SendAmmoUpdateToAll;
		}

		[HarmonyPatch(typeof(PlayerBackpackManager), "SendLocalAmmoData")]
		[HarmonyPrefix]
		[HarmonyPriority(600)]
		private static bool Prefix__PlayerBackpackManager_SendLocalAmmoData(PlayerBackpackManager __instance)
		{
			return !NetHelper.InvokeWithAllPlayers(SendAmmoData);
		}

		private static void SendAmmoUpdateToAll()
		{
			NetHelper.InvokeWithAllPlayers(SendAmmoData);
		}

		private static void SendAmmoData(SNet_Player target)
		{
			//IL_0118: Unknown result type (might be due to invalid IL or missing references)
			//IL_011d: Unknown result type (might be due to invalid IL or missing references)
			//IL_022a: Unknown result type (might be due to invalid IL or missing references)
			if (_nextSyncTime > Clock.Time)
			{
				return;
			}
			_nextSyncTime = Clock.Time + ConfigMgr.SyncInterval;
			if (!PlayerBackpackManager.m_hasLocalPlayerBackpack)
			{
				return;
			}
			Logger.Debug($"Sending ammo data for player '{target.GetName()}' ({target.Lookup}) clipSupport={PeerInfoManager.Supported(target)}");
			bool flag = PeerInfoManager.Supported(target);
			PlayerBackpackManager.m_ammoSyncTimer = Clock.Time + PlayerBackpackManager.m_ammoSyncDelay;
			PlayerBackpackManager.m_ammoSyncTimerConstant = Clock.Time + PlayerBackpackManager.m_ammoSyncDelayConstant;
			Enumerator<ulong, PlayerBackpack> enumerator = PlayerBackpackManager.Current.m_backpacks.GetEnumerator();
			while (enumerator.MoveNext())
			{
				KeyValuePair<ulong, PlayerBackpack> current = enumerator.Current;
				if (current.Value.Owner.Lookup == SNet.LocalPlayer.Lookup || (SNet.IsMaster && current.Value.Owner.IsBot))
				{
					PlayerAmmoStorage ammoStorage = current.Value.AmmoStorage;
					pAmmoStorageData storageData = ammoStorage.GetStorageData();
					if (SNet.IsMaster && current.Value.Owner.IsBot)
					{
						current.Value.OnStorageUpdatedCallback?.Invoke(current.Value);
					}
					if (flag)
					{
						AmmoClipInfo clipInfo = GetClipInfo(current.Value);
						Util.OffsetDevFloat32(ref storageData.standardAmmo, (float)(-clipInfo.StandardClipAmmo) * ammoStorage.StandardAmmo.CostOfBullet);
						Util.OffsetDevFloat32(ref storageData.specialAmmo, (float)(-clipInfo.SpecialClipAmmo) * ammoStorage.SpecialAmmo.CostOfBullet);
						Util.OffsetDevFloat32(ref storageData.classAmmo, (float)(-clipInfo.ClassClipAmmo) * ammoStorage.ClassAmmo.CostOfBullet);
						Util.OffsetDevFloat32(ref storageData.resourcePackAmmo, (float)(-clipInfo.ResourcePackClipAmmo) * ammoStorage.ResourcePackAmmo.CostOfBullet);
						Util.OffsetDevFloat32(ref storageData.consumableAmmo, (float)(-clipInfo.ConsumableClipAmmo) * ammoStorage.ConsumableAmmo.CostOfBullet);
						Net.SendBytes(clipInfo.Serialize(), 65, target);
					}
					PlayerBackpackManager.Current.m_ammoStoragePacket.Send(storageData, (SNet_ChannelType)2, target);
				}
			}
		}

		private static void HandlePacket(byte[] data, SNet_Player? sender)
		{
			if (!((Object)(object)sender == (Object)null))
			{
				ApplyClipInfo(AmmoClipInfo.Deserialize(data));
			}
		}

		private static AmmoClipInfo GetClipInfo(PlayerBackpack backpack)
		{
			AmmoClipInfo result = default(AmmoClipInfo);
			PlayerAmmoStorage ammoStore = backpack.AmmoStorage;
			result.Lookup = backpack.Owner.Lookup;
			result.StandardClipAmmo = GetClip((InventorySlot)1);
			result.SpecialClipAmmo = GetClip((InventorySlot)2);
			result.ClassClipAmmo = GetClip((InventorySlot)3);
			result.ResourcePackClipAmmo = GetClip((InventorySlot)4);
			result.ConsumableClipAmmo = GetClip((InventorySlot)5);
			return result;
			int GetClip(InventorySlot slot)
			{
				//IL_0006: Unknown result type (might be due to invalid IL or missing references)
				return Mathf.RoundToInt(ammoStore.GetClipAmmoFromSlot(slot));
			}
		}

		private static void ApplyClipInfo(AmmoClipInfo clipInfo)
		{
			//IL_00e8: Unknown result type (might be due to invalid IL or missing references)
			//IL_00ed: Unknown result type (might be due to invalid IL or missing references)
			//IL_00ef: Unknown result type (might be due to invalid IL or missing references)
			//IL_0115: Unknown result type (might be due to invalid IL or missing references)
			Logger.Debug($"Applying clip info for player with lookup {clipInfo.Lookup}, with data: standard={clipInfo.StandardClipAmmo}, special={clipInfo.SpecialClipAmmo}, class={clipInfo.ClassClipAmmo}, resourcePack={clipInfo.ResourcePackClipAmmo}, consumable={clipInfo.ConsumableClipAmmo}");
			PlayerBackpack val = default(PlayerBackpack);
			if (!PlayerBackpackManager.TryGetBackpack(clipInfo.Lookup, ref val))
			{
				return;
			}
			BackpackItem val2 = default(BackpackItem);
			foreach (InventorySlot syncedSlot in SyncedSlots)
			{
				if (val.TryGetBackpackItem(syncedSlot, ref val2))
				{
					ItemEquippable val3 = ((Il2CppObjectBase)val2.Instance).TryCast<ItemEquippable>();
					if ((Object)(object)val3 != (Object)null)
					{
						val3.SetCurrentClip(clipInfo.GetClip(syncedSlot));
					}
				}
			}
		}
	}
}
namespace PlayerSync.Network
{
	[HarmonyPatch]
	public class Net
	{
		private const ushort NetKeyBytes = 20556;

		private const int SNetChannel = 1;

		private static readonly Dictionary<byte, Action<byte[], SNet_Player?>> MessageHandlers = new Dictionary<byte, Action<byte[], SNet_Player>>();

		public static bool RegisterHandler(byte packetId, Action<byte[], SNet_Player?> handler)
		{
			if (MessageHandlers.TryAdd(packetId, handler))
			{
				return true;
			}
			Logger.Error($"Handler for packetId={packetId} is already registered!");
			return false;
		}

		public static bool UnregisterHandler(byte packetId)
		{
			return MessageHandlers.Remove(packetId);
		}

		[HarmonyPatch(typeof(SNet_Replication), "RecieveBytes")]
		[HarmonyPrefix]
		private static bool OnReceive(Il2CppStructArray<byte> bytes, uint size, ulong messagerID)
		{
			if (!IsNetMessage(bytes))
			{
				return true;
			}
			byte b = ((Il2CppArrayBase<byte>)(object)bytes)[2];
			if (MessageHandlers.TryGetValue(b, out Action<byte[], SNet_Player> value))
			{
				byte[] array = new byte[size - 3];
				Array.Copy(Il2CppArrayBase<byte>.op_Implicit((Il2CppArrayBase<byte>)(object)bytes), 3L, array, 0L, size - 3);
				SNet_Player playerByID = NetHelper.GetPlayerByID(messagerID);
				value(array, playerByID);
			}
			else
			{
				Logger.Error($"Net (key={20556:X}) received message for packetId={b} with no handler, are we on the same version?");
			}
			return false;
		}

		public static void SendBytes(byte[] data, byte packetId, SNet_Player target)
		{
			SendBytes(PrepareData(data, packetId), target);
		}

		public static void SendBytes(byte[] data, byte packetId, List<SNet_Player> targets)
		{
			SendBytes(PrepareData(data, packetId), targets);
		}

		private static void SendBytes(byte[] bytes, SNet_Player target)
		{
			Il2CppStructArray<byte> val = new Il2CppStructArray<byte>(bytes);
			SNet.Core.SendBytes(val, (SNet_SendQuality)2, 1, target);
		}

		private static void SendBytes(byte[] bytes, List<SNet_Player> targets)
		{
			Il2CppStructArray<byte> val = new Il2CppStructArray<byte>(bytes);
			SNet.Core.SendBytes(val, (SNet_SendQuality)2, 1, targets);
		}

		private static byte[] PrepareData(byte[] data, byte packetId = 0)
		{
			byte[] array = new byte[3 + data.Length];
			((Il2CppArrayBase<byte>)(object)BitConverter.GetBytes((ushort)20556)).CopyTo(array, 0);
			array[2] = packetId;
			data.CopyTo(array, 3);
			return array;
		}

		private static bool IsNetMessage(Il2CppStructArray<byte> bytes)
		{
			if (((Il2CppArrayBase<byte>)(object)bytes).Length < 2)
			{
				return false;
			}
			return BitConverter.ToUInt16(bytes, 0) == 20556;
		}
	}
	public static class NetHelper
	{
		public static SNet_Player? GetPlayerByID(ulong id)
		{
			Enumerator<SNet_Player> enumerator = SNet.LobbyPlayers.GetEnumerator();
			while (enumerator.MoveNext())
			{
				SNet_Player current = enumerator.Current;
				if (current.Lookup == id)
				{
					return current;
				}
			}
			return null;
		}

		public static bool InvokeWithAllPlayers(Action<SNet_Player> sendMethod)
		{
			if (!SNet.IsInLobby)
			{
				return false;
			}
			Enumerator<SNet_Player> enumerator = SNet.Lobby.Players.GetEnumerator();
			while (enumerator.MoveNext())
			{
				SNet_Player current = enumerator.Current;
				if (!current.IsBot && !current.IsLocal)
				{
					sendMethod(current);
				}
			}
			return true;
		}
	}
	public readonly record struct PlugVersion
	{
		public readonly byte Major;

		public readonly byte Minor;

		public readonly byte Patch;

		public PlugVersion()
		{
			Major = 0;
			Minor = 0;
			Patch = 0;
		}

		public PlugVersion(byte major, byte minor, byte patch)
		{
			Major = 0;
			Minor = 0;
			Patch = 0;
			Major = major;
			Minor = minor;
			Patch = patch;
		}

		public PlugVersion(string version)
		{
			Major = 0;
			Minor = 0;
			Patch = 0;
			string[] array = version.Split('.');
			byte result;
			byte result2;
			byte result3;
			if (array.Length != 3)
			{
				Logger.Error("Invalid version string '" + version + "', expected format 'major.minor.patch'.");
			}
			else if (!byte.TryParse(array[0], out result))
			{
				Logger.Error($"Invalid major version '{array[0]}' in version string '{version}'.");
			}
			else if (!byte.TryParse(array[1], out result2))
			{
				Logger.Error($"Invalid minor version '{array[1]}' in version string '{version}'.");
			}
			else if (!byte.TryParse(array[2], out result3))
			{
				Logger.Error($"Invalid patch version '{array[2]}' in version string '{version}'.");
			}
			else
			{
				Major = result;
				Minor = result2;
				Patch = result3;
			}
		}

		public PlugVersion(byte[] version)
		{
			Major = 0;
			Minor = 0;
			Patch = 0;
			if (version.Length < 3)
			{
				Logger.Error($"Invalid version byte array of length {version.Length}, expected at least 3.");
			}
			else
			{
				Major = version[0];
				Minor = version[1];
				Patch = version[2];
			}
		}

		public PlugVersion(byte[] data, int offset)
		{
			Major = 0;
			Minor = 0;
			Patch = 0;
			if (data.Length < offset + 3)
			{
				Logger.Error($"Invalid version byte array of length {data.Length} with offset {offset}, expected at least {offset + 3}.");
			}
			else
			{
				Major = data[offset];
				Minor = data[offset + 1];
				Patch = data[offset + 2];
			}
		}

		public byte[] ToByteArray()
		{
			return new byte[3] { Major, Minor, Patch };
		}

		public static bool operator >(PlugVersion a, PlugVersion b)
		{
			if (a.Major != b.Major)
			{
				return a.Major > b.Major;
			}
			if (a.Minor != b.Minor)
			{
				return a.Minor > b.Minor;
			}
			return a.Patch > b.Patch;
		}

		public static bool operator <(PlugVersion a, PlugVersion b)
		{
			if (a.Major != b.Major)
			{
				return a.Major < b.Major;
			}
			if (a.Minor != b.Minor)
			{
				return a.Minor < b.Minor;
			}
			return a.Patch < b.Patch;
		}

		public override string ToString()
		{
			return $"{Major}.{Minor}.{Patch}";
		}
	}
}
namespace PlayerSync.Network.Impl
{
	public static class NetImpl
	{
	}
	[RegisterIl2Cpp]
	public class PeerInfoManager : MonoBehaviour
	{
		public enum PeerSupport
		{
			Unknown,
			Supported,
			NotSupported
		}

		public class PeerInfo
		{
			public PeerSupport Support;

			public PlugVersion PlugVersion = new PlugVersion();

			public SNet_Player? Player;

			public byte RequestCount { get; private set; }

			public bool MaxRequestsReached => RequestCount >= 7;

			public void IncrementRequestCount()
			{
				RequestCount++;
			}
		}

		public const byte PacketId = byte.MaxValue;

		public const byte KeyRequest = 105;

		public const byte KeyResponse = 103;

		public const int MaxRequestCount = 7;

		private const float PeerInfoUpdateInterval = 4f;

		private float _timeSinceLastUpdate;

		public static PeerInfoManager? Instance { get; private set; }

		private Dictionary<ulong, PeerInfo> PeerInfos { get; } = new Dictionary<ulong, PeerInfo>();


		private void Awake()
		{
			if ((Object)(object)Instance == (Object)null)
			{
				Instance = this;
			}
			else if ((Object)(object)Instance != (Object)(object)this)
			{
				Object.Destroy((Object)(object)this);
				Logger.Warn("Multiple instances of LobbyInfo detected, this should not happen!");
				return;
			}
			Net.RegisterHandler(byte.MaxValue, HandlePacket);
			Events.OnPlayerJoinLobby += SendPeerInfoRequestSafe;
		}

		private void OnDestroy()
		{
			if ((Object)(object)Instance == (Object)(object)this)
			{
				Instance = null;
			}
			Net.UnregisterHandler(byte.MaxValue);
			Events.OnPlayerJoinLobby -= SendPeerInfoRequestSafe;
		}

		private void Update()
		{
			_timeSinceLastUpdate += Time.deltaTime;
			if (_timeSinceLastUpdate >= 4f)
			{
				UpdateLobbyInfo();
				_timeSinceLastUpdate = 0f;
			}
		}

		public static bool Supported(SNet_Player player)
		{
			if ((Object)(object)Instance == (Object)null)
			{
				return false;
			}
			if (Instance.PeerInfos.TryGetValue(player.Lookup, out PeerInfo value))
			{
				return value.Support == PeerSupport.Supported;
			}
			return false;
		}

		public static bool SupportUnknown(SNet_Player player)
		{
			if ((Object)(object)Instance == (Object)null)
			{
				return false;
			}
			if (Instance.PeerInfos.TryGetValue(player.Lookup, out PeerInfo value))
			{
				return value.Support == PeerSupport.Unknown;
			}
			return false;
		}

		public static bool Unsupported(SNet_Player player)
		{
			if ((Object)(object)Instance == (Object)null)
			{
				return false;
			}
			if (Instance.PeerInfos.TryGetValue(player.Lookup, out PeerInfo value))
			{
				return value.Support == PeerSupport.NotSupported;
			}
			return false;
		}

		private void UpdateLobbyInfo()
		{
			SNet_SessionHub sessionHub = SNet.SessionHub;
			foreach (ulong item in PeerInfos.Keys.ToList())
			{
				if (!sessionHub.IsPlayerInHub(PeerInfos[item].Player))
				{
					DefaultInterpolatedStringHandler defaultInterpolatedStringHandler = new DefaultInterpolatedStringHandler(58, 2);
					defaultInterpolatedStringHandler.AppendLiteral("Player '");
					SNet_Player? player = PeerInfos[item].Player;
					defaultInterpolatedStringHandler.AppendFormatted(((player != null) ? player.NickName : null) ?? "???");
					defaultInterpolatedStringHandler.AppendLiteral("' (");
					defaultInterpolatedStringHandler.AppendFormatted(item);
					defaultInterpolatedStringHandler.AppendLiteral(") is not in lobby anymore, removing their info.");
					Logger.Debug(defaultInterpolatedStringHandler.ToStringAndClear());
					PeerInfos.Remove(item);
				}
			}
			if (SNet.IsInLobby)
			{
				NetHelper.InvokeWithAllPlayers(RequestInfoFromPlayer);
			}
		}

		private void RequestInfoFromPlayer(SNet_Player player)
		{
			if (PeerInfos.TryGetValue(player.Lookup, out PeerInfo value))
			{
				if (value.Support != 0)
				{
					return;
				}
				if (value.MaxRequestsReached)
				{
					value.Support = PeerSupport.NotSupported;
					Logger.Info($"Player '{player.GetName()}' ({player.Lookup}) reached max request count without response, marking as not supported.");
					return;
				}
			}
			SendPeerInfoRequestSafe(player);
		}

		private void SendPeerInfoRequestSafe(SNet_Player player)
		{
			if (!player.IsBot && !player.IsLocal)
			{
				if (!PeerInfos.TryGetValue(player.Lookup, out PeerInfo value))
				{
					value = new PeerInfo();
					PeerInfos[player.Lookup] = value;
				}
				SendPeerInfoRequest(player);
				value.IncrementRequestCount();
				PeerInfo peerInfo = value;
				if (peerInfo.Player == null)
				{
					peerInfo.Player = player;
				}
				Logger.Debug($"Sent request for info to player '{player.GetName()}' ({player.Lookup}), RequestCount({value.RequestCount} of {7})");
			}
		}

		private static void SendPeerInfoRequest(SNet_Player player)
		{
			if (!((Object)(object)player == (Object)null) && !player.IsBot)
			{
				Net.SendBytes(new byte[1] { 105 }, byte.MaxValue, player);
			}
		}

		private static void SendPeerInfoResponse(SNet_Player player)
		{
			if (!((Object)(object)player == (Object)null) && !player.IsBot)
			{
				byte[] array = new byte[4] { 103, 0, 0, 0 };
				Array.Copy(Plugin.PlugVersion.ToByteArray(), 0, array, 1, 3);
				Net.SendBytes(array, byte.MaxValue, player);
			}
		}

		private void HandlePacket(byte[] data, SNet_Player? sender)
		{
			if ((Object)(object)sender == (Object)null)
			{
				Logger.Error("Received client info exchange packet with null sender, ignoring!");
				return;
			}
			switch (data[0])
			{
			case 105:
				SendPeerInfoResponse(sender);
				break;
			case 103:
			{
				if (data.Length < 4)
				{
					Logger.Error($"Received peer info response of invalid length {data.Length}, expected 4.");
					break;
				}
				if (!PeerInfos.TryGetValue(sender.Lookup, out PeerInfo value))
				{
					value = new PeerInfo();
					PeerInfos[sender.Lookup] = value;
				}
				value.Support = PeerSupport.Supported;
				value.PlugVersion = new PlugVersion(data, 1);
				value.Player = sender;
				Logger.Info($"Received peer info response from player '{sender.GetName()}' ({sender.Lookup}) version={value.PlugVersion}");
				break;
			}
			}
		}
	}
}
namespace PlayerSync.Interop
{
	[AttributeUsage(AttributeTargets.Class, Inherited = false)]
	public class RegisterIl2CppAttribute : Attribute
	{
	}
}