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
{
}
}