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();
}
}
}