Decompiled source of Dopple But with fusion v1.0.2

DopplebutwithFusion.dll

Decompiled 5 hours ago
using System;
using System.Collections;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.Versioning;
using System.Text;
using BoneLib.Notifications;
using DoppelNames;
using Il2CppSLZ.Marrow;
using Il2CppSLZ.Marrow.Warehouse;
using LabFusion.Data;
using LabFusion.Entities;
using LabFusion.Network;
using LabFusion.Network.Serialization;
using LabFusion.Player;
using LabFusion.Representation;
using LabFusion.SDK.Modules;
using LabFusion.UI;
using MelonLoader;
using MelonLoader.Preferences;
using Microsoft.CodeAnalysis;
using UnityEngine;

[assembly: CompilationRelaxations(8)]
[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
[assembly: Debuggable(DebuggableAttribute.DebuggingModes.Default | DebuggableAttribute.DebuggingModes.DisableOptimizations | DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints | DebuggableAttribute.DebuggingModes.EnableEditAndContinue)]
[assembly: MelonInfo(typeof(Mod), "DoppelNames", "1.0.0", "you", null)]
[assembly: MelonGame("Stress Level Zero", "BONELAB")]
[assembly: TargetFramework(".NETCoreApp,Version=v6.0", FrameworkDisplayName = ".NET 6.0")]
[assembly: AssemblyCompany("DoppelNames")]
[assembly: AssemblyConfiguration("Debug")]
[assembly: AssemblyFileVersion("1.0.0.0")]
[assembly: AssemblyInformationalVersion("1.0.0")]
[assembly: AssemblyProduct("DoppelNames")]
[assembly: AssemblyTitle("DoppelNames")]
[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 DoppelNames
{
	internal static class FusionFakes
	{
		private sealed class Fake
		{
			public ulong PlatformID;

			public byte SmallID;

			public PlayerID Id;

			public string Name;

			public string Barcode;

			public SerializedAvatarStats Stats;

			public RigSkeleton Skeleton;

			public readonly RigPose Pose = new RigPose();

			public float Created;

			public int Failures;

			public int PosesSent;

			public float NextReport;

			public readonly Dictionary<ulong, float> Introduced = new Dictionary<ulong, float>();
		}

		private const ulong FakePlatformBase = 940053923165634560uL;

		private static ulong counter;

		private const float IntroDelay = 1.5f;

		private const float PoseDelay = 0.6f;

		private static readonly List<Fake> all = new List<Fake>();

		private static readonly Dictionary<ulong, float> firstSeen = new Dictionary<ulong, float>();

		private static bool warnedNoIds;

		public static string LastReason = "";

		[MethodImpl(MethodImplOptions.NoInlining)]
		public static bool IsHosting()
		{
			try
			{
				return NetworkInfo.HasServer && NetworkInfo.IsHost;
			}
			catch
			{
				return false;
			}
		}

		[MethodImpl(MethodImplOptions.NoInlining)]
		public static object Add(string name, object rig)
		{
			//IL_0194: Unknown result type (might be due to invalid IL or missing references)
			//IL_01ee: Unknown result type (might be due to invalid IL or missing references)
			//IL_01f8: Expected O, but got Unknown
			//IL_021c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0226: Expected O, but got Unknown
			RigManager val = (RigManager)((rig is RigManager) ? rig : null);
			if ((Object)(object)val == (Object)null)
			{
				LastReason = "the bot has no rig yet";
				return null;
			}
			if ((Object)(object)val.avatar == (Object)null)
			{
				LastReason = "the bot has no avatar yet";
				return null;
			}
			if ((Object)(object)val.physicsRig == (Object)null)
			{
				LastReason = "the bot has no physics rig yet";
				return null;
			}
			if (val.AvatarCrate == null)
			{
				LastReason = "the bot's avatar has no crate";
				return null;
			}
			string iD = ((ScannableReference)val.AvatarCrate).Barcode.ID;
			if (string.IsNullOrEmpty(iD))
			{
				LastReason = "the bot's avatar has no barcode";
				return null;
			}
			PlayerID localID = PlayerIDManager.LocalID;
			if (localID == null)
			{
				LastReason = "your own player id is missing";
				return null;
			}
			byte? uniquePlayerID = PlayerIDManager.GetUniquePlayerID();
			if (!uniquePlayerID.HasValue)
			{
				LastReason = "no free player slot";
				if (!warnedNoIds)
				{
					warnedNoIds = true;
					MelonLogger.Warning("DoppelNames: no free player slot for a server-side bot.");
				}
				return null;
			}
			Dictionary<string, string> dictionary = new Dictionary<string, string>(localID.Metadata.Metadata.LocalDictionary);
			dictionary["Username"] = name;
			dictionary["Nickname"] = "";
			dictionary["Description"] = "";
			dictionary["Loading"] = "false";
			dictionary["PermissionLevel"] = ((object)(PermissionLevel)0/*cast due to .constrained prefix*/).ToString();
			Fake fake = new Fake
			{
				PlatformID = 940053923165634560L + ++counter,
				SmallID = uniquePlayerID.Value,
				Name = name,
				Barcode = iD,
				Stats = new SerializedAvatarStats(val.avatar),
				Created = Time.unscaledTime
			};
			fake.Id = new PlayerID(fake.PlatformID, fake.SmallID, dictionary, new List<string>());
			PlayerIDManager.ReserveSmallID(fake.SmallID);
			all.Add(fake);
			MelonLogger.Msg($"DoppelNames: server-side bot '{name}' created (player slot {fake.SmallID}).");
			return fake;
		}

		[MethodImpl(MethodImplOptions.NoInlining)]
		public static void Tick(object handle, object rig)
		{
			//IL_02bf: Unknown result type (might be due to invalid IL or missing references)
			//IL_02c9: Expected O, but got Unknown
			//IL_0308: Unknown result type (might be due to invalid IL or missing references)
			Fake fake = (Fake)handle;
			if (fake.Failures > 40)
			{
				return;
			}
			try
			{
				float unscaledTime = Time.unscaledTime;
				List<ulong> list = Recipients(unscaledTime);
				if (list.Count == 0)
				{
					if (unscaledTime >= fake.NextReport)
					{
						fake.NextReport = unscaledTime + 10f;
						MelonLogger.Msg("DoppelNames: '" + fake.Name + "': waiting, no other players in the lobby.");
					}
					return;
				}
				foreach (ulong item in list)
				{
					if (!fake.Introduced.ContainsKey(item) && !(unscaledTime - firstSeen[item] < 1.5f))
					{
						bool flag = unscaledTime - fake.Created < 4.5f;
						SendJoin(fake, item, flag);
						fake.Introduced[item] = unscaledTime;
						MelonLogger.Msg($"DoppelNames: introduced '{fake.Name}' (slot {fake.SmallID}) to player {item}" + (flag ? " with a join notice." : "."));
					}
				}
				RigManager val = (RigManager)((rig is RigManager) ? rig : null);
				if ((Object)(object)val == (Object)null)
				{
					return;
				}
				List<ulong> list2 = new List<ulong>();
				foreach (KeyValuePair<ulong, float> item2 in fake.Introduced)
				{
					if (unscaledTime - item2.Value >= 0.6f && list.Contains(item2.Key))
					{
						list2.Add(item2.Key);
					}
				}
				if (list2.Count == 0)
				{
					if (unscaledTime >= fake.NextReport)
					{
						fake.NextReport = unscaledTime + 10f;
						MelonLogger.Msg($"DoppelNames: '{fake.Name}': {list.Count} player(s) here, none ready for poses yet.");
					}
					return;
				}
				if (fake.Skeleton == null)
				{
					fake.Skeleton = new RigSkeleton(val);
				}
				fake.Pose.ReadSkeleton(fake.Skeleton);
				PlayerPoseUpdateData val2 = PlayerPoseUpdateData.Create(fake.Pose);
				NetWriter val3 = NetWriter.Create(val2.GetSize());
				try
				{
					val2.Serialize((INetSerializer)(object)val3);
					NetMessage val4 = NetMessage.Create(NativeMessageTag.PlayerPoseUpdate, val3, CommonMessageRoutes.UnreliableToOtherClients, (byte?)fake.SmallID);
					try
					{
						foreach (ulong item3 in list2)
						{
							MessageSender.SendFromServer(item3, (NetworkChannel)1, val4);
						}
					}
					finally
					{
						((IDisposable)val4)?.Dispose();
					}
				}
				finally
				{
					((IDisposable)val3)?.Dispose();
				}
				fake.PosesSent++;
				if (unscaledTime >= fake.NextReport)
				{
					fake.NextReport = unscaledTime + 10f;
					MelonLogger.Msg($"DoppelNames: '{fake.Name}': {fake.PosesSent} pose updates sent so far to {list2.Count} player(s).");
				}
			}
			catch (Exception ex)
			{
				fake.Skeleton = null;
				if (++fake.Failures == 41)
				{
					MelonLogger.Warning("DoppelNames: server-side bot '" + fake.Name + "' stopped updating: " + ex.Message);
				}
			}
		}

		[MethodImpl(MethodImplOptions.NoInlining)]
		public static void Remove(object handle)
		{
			Fake fake = (Fake)handle;
			all.Remove(fake);
			try
			{
				List<ulong> list = Recipients(Time.unscaledTime);
				foreach (ulong item in list)
				{
					if (fake.Introduced.ContainsKey(item))
					{
						SendLeave(fake, item);
					}
				}
			}
			catch (Exception ex)
			{
				MelonLogger.Warning("DoppelNames: could not announce a bot leaving: " + ex.Message);
			}
			PlayerIDManager.UnreserveSmallID(fake.SmallID);
		}

		[MethodImpl(MethodImplOptions.NoInlining)]
		public static void Forget(object handle)
		{
			Fake fake = (Fake)handle;
			all.Remove(fake);
			PlayerIDManager.UnreserveSmallID(fake.SmallID);
		}

		private static void SendJoin(Fake f, ulong recipient, bool notify)
		{
			//IL_0030: Unknown result type (might be due to invalid IL or missing references)
			ConnectionResponseData val = ConnectionResponseData.Create(f.Id, f.Barcode, f.Stats, notify);
			NetWriter val2 = NetWriter.Create();
			try
			{
				NetSerializerExtensions.SerializeValue<ConnectionResponseData>((INetSerializer)(object)val2, ref val);
				NetMessage val3 = NetMessage.Create(NativeMessageTag.ConnectionResponse, val2, CommonMessageRoutes.None, (byte?)null);
				try
				{
					MessageSender.SendFromServer(recipient, (NetworkChannel)0, val3);
				}
				finally
				{
					((IDisposable)val3)?.Dispose();
				}
			}
			finally
			{
				((IDisposable)val2)?.Dispose();
			}
		}

		private static void SendLeave(Fake f, ulong recipient)
		{
			//IL_0028: Unknown result type (might be due to invalid IL or missing references)
			DisconnectMessageData val = DisconnectMessageData.Create(f.PlatformID, "");
			NetWriter val2 = NetWriter.Create();
			try
			{
				NetSerializerExtensions.SerializeValue<DisconnectMessageData>((INetSerializer)(object)val2, ref val);
				NetMessage val3 = NetMessage.Create(NativeMessageTag.Disconnect, val2, CommonMessageRoutes.None, (byte?)null);
				try
				{
					MessageSender.SendFromServer(recipient, (NetworkChannel)0, val3);
				}
				finally
				{
					((IDisposable)val3)?.Dispose();
				}
			}
			finally
			{
				((IDisposable)val2)?.Dispose();
			}
		}

		private static List<ulong> Recipients(float now)
		{
			List<ulong> list = new List<ulong>();
			HashSet<ulong> hashSet = new HashSet<ulong>();
			foreach (PlayerID playerID in PlayerIDManager.PlayerIDs)
			{
				if (!playerID.IsMe)
				{
					ulong platformID = playerID.PlatformID;
					hashSet.Add(platformID);
					list.Add(platformID);
					if (!firstSeen.ContainsKey(platformID))
					{
						firstSeen[platformID] = now;
					}
				}
			}
			List<ulong> list2 = null;
			foreach (ulong key in firstSeen.Keys)
			{
				if (!hashSet.Contains(key))
				{
					(list2 ?? (list2 = new List<ulong>())).Add(key);
				}
			}
			if (list2 != null)
			{
				foreach (ulong item in list2)
				{
					firstSeen.Remove(item);
					foreach (Fake item2 in all)
					{
						item2.Introduced.Remove(item);
					}
				}
			}
			return list;
		}
	}
	public class Mod : MelonMod
	{
		private sealed class Entry
		{
			public object Bot;

			public string Name;

			public bool Puppet;

			public bool NetAnnounced;

			public int Id;

			public float Born;

			public object TagHandle;

			public object Fake;

			public float FakeNextTry;

			public bool FakeWarned;
		}

		internal struct Inbound
		{
			public int Owner;

			public byte[] Bytes;
		}

		private MelonPreferences_Entry<bool> prefEnabled;

		private MelonPreferences_Entry<bool> prefTags;

		private MelonPreferences_Entry<bool> prefPopups;

		private MelonPreferences_Entry<bool> prefServer;

		private FieldInfo botsF;

		private FieldInfo nameIsF;

		private FieldInfo netOwnerF;

		private FieldInfo netIdF;

		private FieldInfo rigF;

		private bool doppelReady;

		private float nextResolve;

		private readonly Dictionary<IntPtr, Entry> tracked = new Dictionary<IntPtr, Entry>();

		private readonly Dictionary<int, string> remoteNames = new Dictionary<int, string>();

		private readonly Random rng = new Random();

		private float nextPoll;

		private float nextSnapshot;

		private float nextFakeTick;

		private float quietUntil;

		private bool fusionPresent;

		private bool fusionReady;

		private bool loggedError;

		internal static readonly ConcurrentQueue<Inbound> Inbox = new ConcurrentQueue<Inbound>();

		private const byte KindJoin = 1;

		private const byte KindLeave = 2;

		private const byte KindSnapshot = 3;

		private const BindingFlags Any = BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic;

		private static readonly string[] Names = new string[111]
		{
			"Timmy", "Jake", "Mason", "Liam", "Noah", "Ethan", "Logan", "Lucas", "Owen", "Caleb",
			"Dylan", "Tyler", "Ryan", "Aiden", "Jayden", "Carter", "Wyatt", "Hunter", "Eli", "Landon",
			"Connor", "Colton", "Brody", "Gavin", "Austin", "Chase", "Cole", "Nolan", "Jace", "Parker",
			"Blake", "Declan", "Finn", "Grant", "Henry", "Isaac", "Jasper", "Kai", "Leo", "Miles",
			"Nate", "Oscar", "Pete", "Quinn", "Rex", "Sam", "Theo", "Vince", "Zane", "Max",
			"Emma", "Olivia", "Ava", "Mia", "Chloe", "Ella", "Lily", "Zoe", "Nora", "Hazel",
			"Piper", "Ruby", "Stella", "Violet", "Willow", "Ivy", "Maya", "Layla", "Aria", "Brooke",
			"Cara", "Dana", "Erin", "Faith", "Grace", "Hannah", "Iris", "Jade", "Kylie", "Lena",
			"Molly", "Nina", "Paige", "Riley", "Sadie", "Tessa", "Uma", "Vera", "Wendy", "Alex",
			"Casey", "Drew", "Jamie", "Jordan", "Morgan", "Sky", "Taylor", "Avery", "Bobby", "Danny",
			"Eddie", "Freddie", "Georgie", "Jimmy", "Kenny", "Larry", "Mikey", "Ricky", "Tommy", "Willy",
			"Zach"
		};

		public override void OnInitializeMelon()
		{
			MelonPreferences_Category val = MelonPreferences.CreateCategory("DoppelNames");
			prefEnabled = val.CreateEntry<bool>("Enabled", true, "Turn the whole add-on on or off", (string)null, false, false, (ValueValidator)null, (string)null);
			prefTags = val.CreateEntry<bool>("NameTags", true, "Show name tags above bots (needs LabFusion)", (string)null, false, false, (ValueValidator)null, (string)null);
			prefPopups = val.CreateEntry<bool>("JoinLeavePopups", true, "Show 'has joined/left the server' pop-ups", (string)null, false, false, (ValueValidator)null, (string)null);
			prefServer = val.CreateEntry<bool>("ServerSideBots", true, "When you are the host: make bots show up for players who do NOT have this mod (experimental)", (string)null, false, false, (ValueValidator)null, (string)null);
			((MelonBase)this).LoggerInstance.Msg("DoppelNames loaded. Waiting for Doppel.");
		}

		public override void OnLateInitializeMelon()
		{
			fusionPresent = AppDomain.CurrentDomain.GetAssemblies().Any((Assembly a) => a.GetName().Name == "LabFusion");
			if (!fusionPresent)
			{
				((MelonBase)this).LoggerInstance.Warning("LabFusion is not installed: pop-ups will work, but name tags need LabFusion.");
				return;
			}
			try
			{
				fusionReady = FusionBridge.Start();
			}
			catch (Exception ex)
			{
				((MelonBase)this).LoggerInstance.Warning("Could not register the network message; names stay on your screen only. " + ex.Message);
			}
		}

		public override void OnDeinitializeMelon()
		{
			foreach (Entry value in tracked.Values)
			{
				DestroyTag(value);
				DropFake(value, quiet: false);
			}
			tracked.Clear();
			if (fusionReady)
			{
				try
				{
					FusionBridge.Stop();
				}
				catch
				{
				}
			}
		}

		public override void OnSceneWasUnloaded(int buildIndex, string sceneName)
		{
			quietUntil = Time.unscaledTime + 4f;
		}

		public override void OnUpdate()
		{
			if (!prefEnabled.Value)
			{
				if (tracked.Count <= 0)
				{
					return;
				}
				foreach (Entry value in tracked.Values)
				{
					DestroyTag(value);
					DropFake(value, quiet: false);
				}
				tracked.Clear();
				return;
			}
			float unscaledTime = Time.unscaledTime;
			if (!doppelReady)
			{
				if (!(unscaledTime < nextResolve))
				{
					nextResolve = unscaledTime + 2f;
					doppelReady = ResolveDoppel();
				}
				return;
			}
			try
			{
				if (fusionReady)
				{
					DrainInbox();
				}
				if (unscaledTime >= nextPoll)
				{
					nextPoll = unscaledTime + 0.2f;
					Poll(unscaledTime);
				}
				if (fusionReady && unscaledTime >= nextSnapshot)
				{
					nextSnapshot = unscaledTime + 4f;
					if (!ServerSideActive())
					{
						SendSnapshot();
					}
				}
				if (unscaledTime >= nextFakeTick)
				{
					nextFakeTick = unscaledTime + 0.05f;
					TickFakes();
				}
			}
			catch (Exception ex)
			{
				if (!loggedError)
				{
					loggedError = true;
					((MelonBase)this).LoggerInstance.Error("DoppelNames hit an error (logged once): " + ex);
				}
			}
		}

		public override void OnLateUpdate()
		{
			if (!doppelReady || !fusionPresent || rigF == null || !prefEnabled.Value || !prefTags.Value)
			{
				return;
			}
			foreach (Entry value in tracked.Values)
			{
				if (value.TagHandle != null)
				{
					try
					{
						FusionTags.Follow(value.TagHandle, rigF.GetValue(value.Bot));
					}
					catch
					{
					}
				}
			}
		}

		private static Type FindType(string full)
		{
			Assembly[] assemblies = AppDomain.CurrentDomain.GetAssemblies();
			foreach (Assembly assembly in assemblies)
			{
				try
				{
					Type type = assembly.GetType(full, throwOnError: false);
					if (type != null)
					{
						return type;
					}
				}
				catch
				{
				}
			}
			return null;
		}

		private bool ResolveDoppel()
		{
			Type type = FindType("ThirdEyeBonelabMod.Bots.BotManager");
			Type type2 = FindType("ThirdEyeBonelabMod.Bots.Bot");
			if (type == null || type2 == null)
			{
				return false;
			}
			botsF = type.GetField("bots", BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic);
			nameIsF = type2.GetField("nameIs", BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic);
			netOwnerF = type2.GetField("netOwner", BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic);
			netIdF = type2.GetField("netId", BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic);
			rigF = type2.GetField("rig", BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic);
			if (botsF == null || netOwnerF == null || netIdF == null)
			{
				((MelonBase)this).LoggerInstance.Error("Found Doppel, but it looks like a different version than 1.4.0 (a field this add-on needs is missing). Names are disabled.");
				nextResolve = float.MaxValue;
				return false;
			}
			((MelonBase)this).LoggerInstance.Msg("Hooked into Doppel.");
			return true;
		}

		private void Poll(float now)
		{
			if (!(botsF.GetValue(null) is IDictionary dictionary))
			{
				return;
			}
			List<KeyValuePair<IntPtr, object>> list = new List<KeyValuePair<IntPtr, object>>(dictionary.Count);
			foreach (DictionaryEntry item in dictionary)
			{
				list.Add(new KeyValuePair<IntPtr, object>((IntPtr)item.Key, item.Value));
			}
			HashSet<IntPtr> hashSet = new HashSet<IntPtr>(list.Select((KeyValuePair<IntPtr, object> k) => k.Key));
			bool flag = now < quietUntil;
			foreach (IntPtr item2 in tracked.Keys.ToList())
			{
				if (hashSet.Contains(item2))
				{
					continue;
				}
				Entry entry = tracked[item2];
				tracked.Remove(item2);
				DestroyTag(entry);
				DropFake(entry, quiet: false);
				if (!(entry.Puppet || flag) && !string.IsNullOrEmpty(entry.Name))
				{
					Popup(entry.Name + " has left the server");
					if (fusionReady && entry.NetAnnounced && entry.Id > 0 && !ServerSideActive())
					{
						FusionSend(Pack(2, entry.Id, entry.Name));
					}
				}
			}
			foreach (KeyValuePair<IntPtr, object> item3 in list)
			{
				if (!tracked.TryGetValue(item3.Key, out var value))
				{
					value = new Entry
					{
						Bot = item3.Value,
						Born = now,
						Puppet = IsPuppet(item3.Value)
					};
					tracked[item3.Key] = value;
					if (!value.Puppet)
					{
						value.Name = PickName();
						nameIsF?.SetValue(value.Bot, value.Name);
						if (!flag)
						{
							Popup(value.Name + " has joined the server");
						}
					}
				}
				if (value.Puppet)
				{
					ResolvePuppetName(value);
				}
				else
				{
					EnsureFake(value, now);
					if (!value.NetAnnounced && !ServerSideActive())
					{
						TryAnnounceOverNetwork(value, now, flag);
					}
				}
				EnsureTag(value);
			}
		}

		private bool ServerSideActive()
		{
			if (!fusionPresent || !prefServer.Value)
			{
				return false;
			}
			try
			{
				return FusionFakes.IsHosting();
			}
			catch
			{
				return false;
			}
		}

		private void EnsureFake(Entry e, float now)
		{
			if (e.Fake != null || e.Puppet || string.IsNullOrEmpty(e.Name) || now < e.FakeNextTry || !ServerSideActive())
			{
				return;
			}
			e.FakeNextTry = now + 0.5f;
			try
			{
				e.Fake = FusionFakes.Add(e.Name, rigF?.GetValue(e.Bot));
				if (e.Fake == null && !e.FakeWarned && now - e.Born > 5f)
				{
					e.FakeWarned = true;
					((MelonBase)this).LoggerInstance.Warning("Server-side bot '" + e.Name + "' still not created: " + FusionFakes.LastReason);
				}
			}
			catch (Exception ex)
			{
				e.FakeNextTry = now + 5f;
				((MelonBase)this).LoggerInstance.Warning("Could not make a server-side bot: " + ex.Message);
			}
		}

		private void DropFake(Entry e, bool quiet)
		{
			if (e.Fake == null)
			{
				return;
			}
			object fake = e.Fake;
			e.Fake = null;
			try
			{
				if (quiet || !FusionFakes.IsHosting())
				{
					FusionFakes.Forget(fake);
				}
				else
				{
					FusionFakes.Remove(fake);
				}
			}
			catch (Exception ex)
			{
				((MelonBase)this).LoggerInstance.Warning("Could not remove a server-side bot: " + ex.Message);
			}
		}

		private void TickFakes()
		{
			if (!fusionPresent)
			{
				return;
			}
			bool flag = ServerSideActive();
			foreach (Entry value in tracked.Values)
			{
				if (value.Fake == null)
				{
					continue;
				}
				if (!flag)
				{
					DropFake(value, quiet: false);
					continue;
				}
				try
				{
					FusionFakes.Tick(value.Fake, rigF?.GetValue(value.Bot));
				}
				catch
				{
				}
			}
		}

		private bool IsPuppet(object bot)
		{
			return Convert.ToInt32(netOwnerF.GetValue(bot)) >= 0;
		}

		private void ResolvePuppetName(Entry e)
		{
			if (string.IsNullOrEmpty(e.Name))
			{
				int num = Convert.ToInt32(netOwnerF.GetValue(e.Bot));
				int num2 = Convert.ToInt32(netIdF.GetValue(e.Bot));
				if (remoteNames.TryGetValue((num << 8) | (num2 & 0xFF), out var value))
				{
					e.Name = value;
					nameIsF?.SetValue(e.Bot, value);
				}
			}
		}

		private void TryAnnounceOverNetwork(Entry e, float now, bool quiet)
		{
			if (!fusionReady)
			{
				e.NetAnnounced = true;
			}
			else
			{
				if (!FusionBridge.IsOnline())
				{
					return;
				}
				int num = Convert.ToInt32(netIdF.GetValue(e.Bot));
				if (num <= 0 || num > 255)
				{
					if (now - e.Born > 8f)
					{
						e.NetAnnounced = true;
					}
					return;
				}
				e.Id = num;
				e.NetAnnounced = true;
				if (!quiet)
				{
					FusionSend(Pack(1, num, e.Name));
				}
			}
		}

		private void SendSnapshot()
		{
			if (!FusionBridge.IsOnline())
			{
				return;
			}
			List<KeyValuePair<int, string>> list = new List<KeyValuePair<int, string>>();
			foreach (Entry value in tracked.Values)
			{
				if (!value.Puppet && !string.IsNullOrEmpty(value.Name))
				{
					int num = Convert.ToInt32(netIdF.GetValue(value.Bot));
					if (num > 0 && num <= 255)
					{
						value.Id = num;
						list.Add(new KeyValuePair<int, string>(num, value.Name));
					}
				}
			}
			if (list.Count != 0)
			{
				FusionSend(PackSnapshot(list));
			}
		}

		private static void FusionSend(byte[] payload)
		{
			try
			{
				FusionBridge.Send(payload);
			}
			catch
			{
			}
		}

		private void DrainInbox()
		{
			Inbound result;
			while (Inbox.TryDequeue(out result))
			{
				try
				{
					Handle(result.Owner, result.Bytes);
				}
				catch (Exception ex)
				{
					((MelonBase)this).LoggerInstance.Warning("Bad message: " + ex.Message);
				}
			}
		}

		private void Handle(int owner, byte[] bytes)
		{
			using BinaryReader binaryReader = new BinaryReader(new MemoryStream(bytes));
			byte b = binaryReader.ReadByte();
			int num = binaryReader.ReadByte();
			if (b == 3)
			{
				HashSet<int> keep = new HashSet<int>();
				for (int i = 0; i < num; i++)
				{
					int num2 = binaryReader.ReadByte();
					string text = Clean(binaryReader.ReadString());
					if (text.Length != 0)
					{
						int num3 = (owner << 8) | num2;
						keep.Add(num3);
						remoteNames[num3] = text;
					}
				}
				{
					foreach (int item in remoteNames.Keys.Where((int k) => k >> 8 == owner && !keep.Contains(k)).ToList())
					{
						remoteNames.Remove(item);
					}
					return;
				}
			}
			if (num < 1)
			{
				return;
			}
			int num4 = binaryReader.ReadByte();
			string text2 = Clean(binaryReader.ReadString());
			if (text2.Length == 0)
			{
				return;
			}
			switch (b)
			{
			case 1:
				remoteNames[(owner << 8) | num4] = text2;
				if (Time.unscaledTime >= quietUntil)
				{
					Popup(text2 + " has joined the server");
				}
				break;
			case 2:
				remoteNames.Remove((owner << 8) | num4);
				if (Time.unscaledTime >= quietUntil)
				{
					Popup(text2 + " has left the server");
				}
				break;
			}
		}

		private static byte[] Pack(byte kind, int id, string name)
		{
			using MemoryStream memoryStream = new MemoryStream();
			using BinaryWriter binaryWriter = new BinaryWriter(memoryStream);
			binaryWriter.Write(kind);
			binaryWriter.Write((byte)1);
			binaryWriter.Write((byte)id);
			binaryWriter.Write(name);
			binaryWriter.Flush();
			return memoryStream.ToArray();
		}

		private static byte[] PackSnapshot(List<KeyValuePair<int, string>> items)
		{
			using MemoryStream memoryStream = new MemoryStream();
			using BinaryWriter binaryWriter = new BinaryWriter(memoryStream);
			binaryWriter.Write((byte)3);
			binaryWriter.Write((byte)Math.Min(items.Count, 255));
			foreach (KeyValuePair<int, string> item in items.Take(255))
			{
				binaryWriter.Write((byte)item.Key);
				binaryWriter.Write(item.Value);
			}
			binaryWriter.Flush();
			return memoryStream.ToArray();
		}

		private static string Clean(string s)
		{
			if (string.IsNullOrEmpty(s))
			{
				return "";
			}
			StringBuilder stringBuilder = new StringBuilder();
			foreach (char c in s)
			{
				if (char.IsLetterOrDigit(c) || c == ' ' || c == '_' || c == '-' || c == '.')
				{
					stringBuilder.Append(c);
				}
				if (stringBuilder.Length >= 20)
				{
					break;
				}
			}
			return stringBuilder.ToString().Trim();
		}

		private void Popup(string message)
		{
			//IL_0024: Unknown result type (might be due to invalid IL or missing references)
			//IL_0029: Unknown result type (might be due to invalid IL or missing references)
			//IL_002f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0034: Unknown result type (might be due to invalid IL or missing references)
			//IL_0039: Unknown result type (might be due to invalid IL or missing references)
			//IL_003b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0040: Unknown result type (might be due to invalid IL or missing references)
			//IL_0045: Unknown result type (might be due to invalid IL or missing references)
			//IL_0047: Unknown result type (might be due to invalid IL or missing references)
			//IL_004c: 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)
			//IL_0063: Expected O, but got Unknown
			((MelonBase)this).LoggerInstance.Msg(message);
			if (!prefPopups.Value)
			{
				return;
			}
			try
			{
				Notifier.Send(new Notification
				{
					Title = NotificationText.op_Implicit("Server"),
					Message = NotificationText.op_Implicit(message),
					Type = (NotificationType)0,
					PopupLength = 2.5f,
					ShowTitleOnPopup = true
				});
			}
			catch
			{
			}
		}

		private void EnsureTag(Entry e)
		{
			if (!fusionPresent)
			{
				return;
			}
			if (!prefTags.Value || string.IsNullOrEmpty(e.Name))
			{
				if (e.TagHandle != null)
				{
					DestroyTag(e);
				}
				return;
			}
			try
			{
				if (e.TagHandle == null)
				{
					e.TagHandle = FusionTags.Create(e.Name);
				}
				else
				{
					FusionTags.SetName(e.TagHandle, e.Name);
				}
			}
			catch (Exception ex)
			{
				if (!loggedError)
				{
					loggedError = true;
					((MelonBase)this).LoggerInstance.Error("Could not make a name tag (logged once): " + ex);
				}
			}
		}

		private void DestroyTag(Entry e)
		{
			if (e.TagHandle != null)
			{
				try
				{
					FusionTags.Destroy(e.TagHandle);
				}
				catch
				{
				}
				e.TagHandle = null;
			}
		}

		private string PickName()
		{
			HashSet<string> hashSet = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
			foreach (Entry value in tracked.Values)
			{
				if (!string.IsNullOrEmpty(value.Name))
				{
					hashSet.Add(value.Name);
				}
			}
			foreach (string value2 in remoteNames.Values)
			{
				hashSet.Add(value2);
			}
			for (int i = 0; i < 40; i++)
			{
				string text = Names[rng.Next(Names.Length)];
				if (!hashSet.Contains(text))
				{
					return text;
				}
			}
			return Names[rng.Next(Names.Length)] + rng.Next(2, 99);
		}
	}
	public class NamesData : INetSerializable
	{
		public byte[] bytes;

		public int? GetSize()
		{
			return ((bytes != null) ? bytes.Length : 0) + 8;
		}

		public void Serialize(INetSerializer serializer)
		{
			serializer.SerializeValue(ref bytes);
		}
	}
	public class NamesMessage : ModuleMessageHandler
	{
		protected override void OnHandleMessage(ReceivedMessage received)
		{
			try
			{
				byte? sender = ((ReceivedMessage)(ref received)).Sender;
				if (!sender.HasValue)
				{
					return;
				}
				object obj = sender.Value;
				int owner;
				if (obj is IConvertible convertible)
				{
					owner = convertible.ToInt32(null);
				}
				else
				{
					object obj2 = obj.GetType().GetProperty("SmallID")?.GetValue(obj);
					if (obj2 == null)
					{
						return;
					}
					owner = Convert.ToInt32(obj2);
				}
				NamesData namesData = ((ReceivedMessage)(ref received)).ReadData<NamesData>();
				if (namesData?.bytes != null && namesData.bytes.Length >= 2 && namesData.bytes.Length <= 8192)
				{
					Mod.Inbox.Enqueue(new Mod.Inbound
					{
						Owner = owner,
						Bytes = namesData.bytes
					});
				}
			}
			catch (Exception ex)
			{
				MelonLogger.Warning("DoppelNames: could not read a message: " + ex.Message);
			}
		}
	}
	internal static class FusionBridge
	{
		private const long Tag = 7391645520138472951L;

		[MethodImpl(MethodImplOptions.NoInlining)]
		public static bool Start()
		{
			if (ModuleMessageManager.TagToHandlerLookup.TryGetValue(7391645520138472951L, out var value))
			{
				ModuleMessageManager.TagToHandlerLookup.Remove(7391645520138472951L);
				ModuleMessageManager.Handlers.Remove(value);
				ModuleMessageManager.TypeToHandlerLookup.Remove(((object)value).GetType());
			}
			NamesMessage namesMessage = new NamesMessage();
			FieldInfo field = typeof(ModuleMessageHandler).GetField("_tag", BindingFlags.Instance | BindingFlags.NonPublic);
			if (field == null)
			{
				throw new Exception("this Fusion keeps its handlers' numbers some other way");
			}
			field.SetValue(namesMessage, 7391645520138472951L);
			PropertyInfo property = typeof(MessageHandler).GetProperty("NetAttributes", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
			if (property != null)
			{
				property.SetValue(namesMessage, Array.CreateInstance(property.PropertyType.GetElementType(), 0));
			}
			ModuleMessageManager.Handlers.Add((ModuleMessageHandler)(object)namesMessage);
			ModuleMessageManager.TypeToHandlerLookup[typeof(NamesMessage)] = (ModuleMessageHandler)(object)namesMessage;
			ModuleMessageManager.TagToHandlerLookup[7391645520138472951L] = (ModuleMessageHandler)(object)namesMessage;
			return true;
		}

		[MethodImpl(MethodImplOptions.NoInlining)]
		public static void Stop()
		{
			if (ModuleMessageManager.TagToHandlerLookup.TryGetValue(7391645520138472951L, out var value))
			{
				ModuleMessageManager.TagToHandlerLookup.Remove(7391645520138472951L);
				ModuleMessageManager.Handlers.Remove(value);
				ModuleMessageManager.TypeToHandlerLookup.Remove(((object)value).GetType());
			}
		}

		[MethodImpl(MethodImplOptions.NoInlining)]
		public static bool IsOnline()
		{
			try
			{
				return NetworkInfo.HasServer && PlayerIDManager.HasOtherPlayers;
			}
			catch
			{
				return false;
			}
		}

		[MethodImpl(MethodImplOptions.NoInlining)]
		public static void Send(byte[] payload)
		{
			//IL_000f: Unknown result type (might be due to invalid IL or missing references)
			NamesData namesData = new NamesData
			{
				bytes = payload
			};
			MessageRelay.RelayModule<NamesMessage, NamesData>(namesData, CommonMessageRoutes.ReliableToOtherClients);
		}
	}
	internal static class FusionTags
	{
		private sealed class Handle
		{
			public RigHeadUI HeadUI;

			public RigNameTag Tag;

			public int Failures;
		}

		[MethodImpl(MethodImplOptions.NoInlining)]
		public static object Create(string name)
		{
			//IL_0001: Unknown result type (might be due to invalid IL or missing references)
			//IL_0006: Unknown result type (might be due to invalid IL or missing references)
			//IL_000f: Expected O, but got Unknown
			//IL_000f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0015: Expected O, but got Unknown
			RigNameTag val = new RigNameTag
			{
				Username = name
			};
			RigHeadUI val2 = new RigHeadUI();
			val2.RegisterElement((IPopupLayoutElement)(object)val);
			val2.Spawn();
			return new Handle
			{
				HeadUI = val2,
				Tag = val
			};
		}

		[MethodImpl(MethodImplOptions.NoInlining)]
		public static void SetName(object handle, string name)
		{
			Handle handle2 = (Handle)handle;
			if (handle2.Tag.Username != name)
			{
				handle2.Tag.Username = name;
			}
		}

		[MethodImpl(MethodImplOptions.NoInlining)]
		public static void Follow(object handle, object rig)
		{
			Handle handle2 = (Handle)handle;
			if (handle2.Failures > 30)
			{
				return;
			}
			RigManager val = (RigManager)((rig is RigManager) ? rig : null);
			if ((Object)(object)val == (Object)null)
			{
				return;
			}
			try
			{
				handle2.HeadUI.UpdateScale(val);
				handle2.HeadUI.UpdateTransform(val);
			}
			catch (Exception ex)
			{
				if (++handle2.Failures == 31)
				{
					MelonLogger.Warning("DoppelNames: a name tag stopped following its bot: " + ex.Message);
				}
			}
		}

		[MethodImpl(MethodImplOptions.NoInlining)]
		public static void Destroy(object handle)
		{
			Handle handle2 = (Handle)handle;
			handle2.HeadUI.Despawn();
		}
	}
}