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.Versioning; using BoneLib.BoneMenu; using LabFusion.Data; using LabFusion.Entities; using LabFusion.Network; using LabFusion.Player; using LabFusion.SDK.Metadata; using LabLink; using LabLink.Friends; using MelonLoader; using Microsoft.CodeAnalysis; using Newtonsoft.Json; using UnityEngine; [assembly: CompilationRelaxations(8)] [assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)] [assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)] [assembly: MelonInfo(typeof(LabLinkMain), "LabLink", "1.0.0", "LabLink Team", null)] [assembly: MelonGame("Stress Level Zero", "BONELAB")] [assembly: TargetFramework(".NETCoreApp,Version=v6.0", FrameworkDisplayName = ".NET 6.0")] [assembly: AssemblyVersion("0.0.0.0")] namespace Microsoft.CodeAnalysis { [CompilerGenerated] [Embedded] internal sealed class EmbeddedAttribute : Attribute { } } namespace System.Runtime.CompilerServices { [CompilerGenerated] [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] [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 LabLink { public class LabLinkMain : MelonMod { public static LabLinkMain? Instance; public static FriendManager? FriendManager; public static PlayerTargeter? PlayerTargeter; public static string ModDirectory = string.Empty; private static Page? _rootPage; private static Page? _friendsPage; public override void OnInitializeMelon() { Instance = this; ModDirectory = Path.Combine((string)(typeof(MelonUtils).GetProperty("GameDirectory", BindingFlags.Static | BindingFlags.Public)?.GetValue(null) ?? ""), "LabLink"); Directory.CreateDirectory(ModDirectory); FriendManager = new FriendManager(); FriendManager.Load(); PlayerTargeter = new PlayerTargeter(); SetupBoneMenu(); ((MelonBase)this).LoggerInstance.Msg("LabLink initialized!"); } private static void SetupBoneMenu() { //IL_000a: Unknown result type (might be due to invalid IL or missing references) //IL_0025: Unknown result type (might be due to invalid IL or missing references) //IL_0059: Unknown result type (might be due to invalid IL or missing references) //IL_008d: Unknown result type (might be due to invalid IL or missing references) _rootPage = Page.Root.CreatePage("LabLink", Color.cyan, 0, true); _rootPage.CreateFunction("Refresh Friends", Color.white, (Action)delegate { FriendManager?.RefreshNow(); RebuildFriendsPage(); }); _rootPage.CreateFunction("Add Friend (Point + Press A)", Color.green, (Action)delegate { if (PlayerTargeter != null && PlayerTargeter.IsTargeting) { PlayerTargeter.AddTargetedAsFriend(); } else { FriendManager?.Notify("Point at a player and try again."); } }); _friendsPage = _rootPage.CreatePage("Friends List", Color.yellow, 0, true); RebuildFriendsPage(); } public static void RebuildFriendsPage() { //IL_0023: Unknown result type (might be due to invalid IL or missing references) //IL_005e: Unknown result type (might be due to invalid IL or missing references) //IL_00a6: Unknown result type (might be due to invalid IL or missing references) //IL_00da: Unknown result type (might be due to invalid IL or missing references) //IL_0137: Unknown result type (might be due to invalid IL or missing references) //IL_0130: Unknown result type (might be due to invalid IL or missing references) //IL_013c: Unknown result type (might be due to invalid IL or missing references) //IL_017e: Unknown result type (might be due to invalid IL or missing references) //IL_0196: Unknown result type (might be due to invalid IL or missing references) //IL_0341: Unknown result type (might be due to invalid IL or missing references) //IL_0310: Unknown result type (might be due to invalid IL or missing references) //IL_01f8: Unknown result type (might be due to invalid IL or missing references) //IL_0239: Unknown result type (might be due to invalid IL or missing references) //IL_02b2: Unknown result type (might be due to invalid IL or missing references) //IL_02e3: Unknown result type (might be due to invalid IL or missing references) if (_friendsPage == null || FriendManager == null) { return; } _friendsPage.RemoveAll(); _friendsPage.CreateFunction("Refresh", Color.white, (Action)delegate { FriendManager.RefreshNow(); RebuildFriendsPage(); }); if (!NetworkLayerManager.LoggedIn) { _friendsPage.CreateFunction("Not connected to Fusion", Color.red, (Action)delegate { }); return; } IReadOnlyList friendStatuses = FriendManager.FriendStatuses; if (friendStatuses.Count == 0) { _friendsPage.CreateFunction("No friends added yet", Color.gray, (Action)delegate { }); _friendsPage.CreateFunction("Point at a player + press A", Color.gray, (Action)delegate { }); return; } foreach (FriendStatus status in friendStatuses) { Color val = (status.IsOnline ? Color.green : Color.red); string text = (status.IsOnline ? "ONLINE" : "OFFLINE"); Page val2 = _friendsPage.CreatePage(status.Entry.Username + " [" + text + "]", val, 0, true); val2.CreateFunction("Status: " + text, val, (Action)delegate { }); if (status.IsOnline && !string.IsNullOrEmpty(status.CurrentLobbyCode)) { val2.CreateFunction("Lobby: " + status.CurrentLobbyName, Color.white, (Action)delegate { }); val2.CreateFunction("Level: " + status.CurrentLevel, Color.white, (Action)delegate { }); val2.CreateFunction($"Players: {status.LobbyPlayerCount}/{status.LobbyMaxPlayers}", Color.white, (Action)delegate { }); val2.CreateFunction("JOIN", Color.green, (Action)delegate { FriendManager.JoinFriend(status); RebuildFriendsPage(); }); } else if (status.IsOnline) { val2.CreateFunction("In main menu (not in lobby)", Color.yellow, (Action)delegate { }); } val2.CreateFunction("Remove Friend", Color.red, (Action)delegate { FriendManager.RemoveFriend(status.Entry.PlatformID); RebuildFriendsPage(); }); } } public override void OnUpdate() { if (FriendManager != null) { if (Input.GetKeyDown((KeyCode)284)) { RebuildFriendsPage(); Menu.OpenPage(_rootPage); } FriendManager.Update(); PlayerTargeter?.Update(); } } } public class PlayerTargeter { private float _checkTimer; private const float CHECK_INTERVAL = 0.15f; private const float MAX_DISTANCE = 10f; public PlayerID? TargetedPlayer { get; private set; } public bool IsTargeting => TargetedPlayer != null; public void Update() { //IL_0052: Unknown result type (might be due to invalid IL or missing references) //IL_005d: Unknown result type (might be due to invalid IL or missing references) //IL_00d8: Unknown result type (might be due to invalid IL or missing references) //IL_00dd: Unknown result type (might be due to invalid IL or missing references) //IL_00df: Unknown result type (might be due to invalid IL or missing references) //IL_00e3: Unknown result type (might be due to invalid IL or missing references) //IL_00e8: 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_00f4: Unknown result type (might be due to invalid IL or missing references) //IL_00f9: Unknown result type (might be due to invalid IL or missing references) //IL_0106: Unknown result type (might be due to invalid IL or missing references) //IL_010b: Unknown result type (might be due to invalid IL or missing references) //IL_010d: Unknown result type (might be due to invalid IL or missing references) //IL_0112: Unknown result type (might be due to invalid IL or missing references) _checkTimer += Time.unscaledDeltaTime; if (_checkTimer < 0.15f) { return; } _checkTimer = 0f; TargetedPlayer = null; if (!NetworkInfo.HasServer) { return; } Camera main = Camera.main; if ((Object)(object)main == (Object)null) { return; } Ray val = default(Ray); ((Ray)(ref val))..ctor(((Component)main).transform.position, ((Component)main).transform.forward); float num = 10f; PlayerID targetedPlayer = null; NetworkPlayer val2 = default(NetworkPlayer); foreach (PlayerID playerID in PlayerIDManager.PlayerIDs) { if (!playerID.IsMe && NetworkPlayerManager.TryGetPlayer(playerID.SmallID, ref val2) && val2 != null && val2.RigRefs != null && !((Object)(object)val2.RigRefs.Head == (Object)null)) { Vector3 position = val2.RigRefs.Head.position; Vector3 val3 = Vector3.Project(position - ((Ray)(ref val)).origin, ((Ray)(ref val)).direction); float magnitude = ((Vector3)(ref val3)).magnitude; float num2 = Vector3.Distance(((Ray)(ref val)).origin + val3, position); if (magnitude < num && num2 < 0.5f) { num = magnitude; targetedPlayer = playerID; } } } TargetedPlayer = targetedPlayer; if (TargetedPlayer != null && Input.GetKeyDown((KeyCode)331)) { AddTargetedAsFriend(); } } public void AddTargetedAsFriend() { if (TargetedPlayer == null) { return; } string platformId = TargetedPlayer.PlatformID.ToString(); PlayerMetadata metadata = TargetedPlayer.Metadata; object obj; if (metadata == null) { obj = null; } else { MetadataVariable username = metadata.Username; obj = ((username != null) ? username.GetValueOrEmpty() : null); } if (obj == null) { obj = "Unknown"; } string text = (string)obj; if (LabLinkMain.FriendManager != null) { if (LabLinkMain.FriendManager.IsFriend(platformId)) { LabLinkMain.FriendManager.Notify(text + " is already your friend!"); } else { LabLinkMain.FriendManager.AddFriend(platformId, text); } } } } } namespace LabLink.UI { public static class FriendListUI { } } namespace LabLink.Friends { public class FriendEntry { public string Username { get; set; } = string.Empty; public string PlatformID { get; set; } = string.Empty; public DateTime AddedDate { get; set; } = DateTime.UtcNow; public string Notes { get; set; } = string.Empty; } public class FriendListData { public List Friends { get; set; } = new List(); } public class FriendStatus { public FriendEntry Entry; public bool IsOnline; public string CurrentLobbyName = string.Empty; public string CurrentLobbyCode = string.Empty; public int LobbyPlayerCount; public int LobbyMaxPlayers; public string CurrentLevel = string.Empty; public FriendStatus(FriendEntry entry) { Entry = entry; } } public struct LobbyScanResult { public LobbyMetadataInfo Metadata; public INetworkLobby Lobby; } public class FriendManager { private FriendListData _data = new FriendListData(); private List _statuses = new List(); private float _checkTimer; private const float CHECK_INTERVAL = 3f; private float _lobbyScanTimer; private const float LOBBY_SCAN_INTERVAL = 10f; private List _cachedLobbies = new List(); private Action? _onNotification; private Action? _onFriendsListChanged; private Dictionary _previousOnlineState = new Dictionary(); private string SavePath => Path.Combine(LabLinkMain.ModDirectory, "friends.json"); public IReadOnlyList FriendStatuses => _statuses; public IReadOnlyList Friends => _data.Friends; public event Action? OnNotification { add { _onNotification = (Action)Delegate.Combine(_onNotification, value); } remove { _onNotification = (Action)Delegate.Remove(_onNotification, value); } } public event Action? OnFriendsListChanged { add { _onFriendsListChanged = (Action)Delegate.Combine(_onFriendsListChanged, value); } remove { _onFriendsListChanged = (Action)Delegate.Remove(_onFriendsListChanged, value); } } public void Notify(string message) { _onNotification?.Invoke(message); } public void Load() { try { if (File.Exists(SavePath)) { string text = File.ReadAllText(SavePath); _data = JsonConvert.DeserializeObject(text) ?? new FriendListData(); } else { _data = new FriendListData(); } RebuildStatuses(); MelonLogger.Msg($"[LabLink] Loaded {_data.Friends.Count} friends."); } catch (Exception ex) { MelonLogger.Error("[LabLink] Failed to load friends: " + ex.Message); _data = new FriendListData(); } } public void Save() { try { string contents = JsonConvert.SerializeObject((object)_data, (Formatting)1); File.WriteAllText(SavePath, contents); } catch (Exception ex) { MelonLogger.Error("[LabLink] Failed to save friends: " + ex.Message); } } public bool AddFriend(string platformId, string username) { if (_data.Friends.Any((FriendEntry f) => f.PlatformID == platformId)) { return false; } FriendEntry item = new FriendEntry { PlatformID = platformId, Username = username, AddedDate = DateTime.UtcNow }; _data.Friends.Add(item); Save(); RebuildStatuses(); _onFriendsListChanged?.Invoke(); _onNotification?.Invoke("Added " + username + " as a friend!"); return true; } public bool AddFriendByUsername(string username) { string platformId = string.Empty; string username2 = string.Empty; bool flag = false; foreach (PlayerID playerID in PlayerIDManager.PlayerIDs) { string valueOrEmpty = playerID.Metadata.Username.GetValueOrEmpty(); if (string.Equals(valueOrEmpty, username, StringComparison.OrdinalIgnoreCase)) { platformId = playerID.PlatformID.ToString(); username2 = valueOrEmpty; flag = true; break; } } if (!flag) { foreach (LobbyScanResult cachedLobby in _cachedLobbies) { LobbyScanResult current2 = cachedLobby; LobbyInfo lobbyInfo = ((LobbyMetadataInfo)(ref current2.Metadata)).LobbyInfo; object obj; if (lobbyInfo == null) { obj = null; } else { PlayerList playerList = lobbyInfo.PlayerList; obj = ((playerList != null) ? playerList.Players : null); } if (obj == null) { continue; } PlayerInfo[] players = ((LobbyMetadataInfo)(ref current2.Metadata)).LobbyInfo.PlayerList.Players; foreach (PlayerInfo val in players) { if (string.Equals(val.Username, username, StringComparison.OrdinalIgnoreCase)) { platformId = val.PlatformID; username2 = val.Username; flag = true; break; } } if (flag) { break; } } } if (!flag) { return false; } return AddFriend(platformId, username2); } public bool RemoveFriend(string platformId) { if (_data.Friends.RemoveAll((FriendEntry f) => f.PlatformID == platformId) > 0) { Save(); RebuildStatuses(); _onFriendsListChanged?.Invoke(); return true; } return false; } public bool IsFriend(string platformId) { return _data.Friends.Any((FriendEntry f) => f.PlatformID == platformId); } private void RebuildStatuses() { _statuses.Clear(); foreach (FriendEntry friend in _data.Friends) { _statuses.Add(new FriendStatus(friend)); } UpdateStatuses(); } public void Update() { _checkTimer += Time.unscaledDeltaTime; if (_checkTimer >= 3f) { _checkTimer = 0f; UpdateStatuses(); } _lobbyScanTimer += Time.unscaledDeltaTime; if (_lobbyScanTimer >= 10f) { _lobbyScanTimer = 0f; ScanLobbies(); } } public void RefreshNow() { UpdateStatuses(); ScanLobbies(); _onNotification?.Invoke("Friend status refreshed!"); } private void UpdateStatuses() { if (!NetworkLayerManager.LoggedIn) { return; } foreach (FriendStatus status in _statuses) { bool isOnline = status.IsOnline; status.IsOnline = false; status.CurrentLobbyName = string.Empty; status.CurrentLobbyCode = string.Empty; status.LobbyPlayerCount = 0; status.CurrentLevel = string.Empty; foreach (PlayerID playerID in PlayerIDManager.PlayerIDs) { if (!(playerID.PlatformID.ToString() == status.Entry.PlatformID)) { continue; } status.IsOnline = true; if (!NetworkInfo.HasServer || LobbyInfoManager.LobbyInfo == null) { break; } LobbyInfo lobbyInfo = LobbyInfoManager.LobbyInfo; PlayerList playerList = lobbyInfo.PlayerList; PlayerInfo[] array = ((playerList != null) ? playerList.Players : null); if (array == null) { break; } PlayerInfo[] array2 = array; for (int i = 0; i < array2.Length; i++) { if (array2[i].PlatformID == status.Entry.PlatformID) { status.CurrentLobbyName = lobbyInfo.LobbyName ?? "Unknown"; status.CurrentLobbyCode = lobbyInfo.LobbyCode ?? string.Empty; status.LobbyPlayerCount = lobbyInfo.PlayerCount; status.LobbyMaxPlayers = lobbyInfo.MaxPlayers; status.CurrentLevel = lobbyInfo.LevelTitle ?? "Unknown"; break; } } break; } if (!status.IsOnline) { foreach (LobbyScanResult cachedLobby in _cachedLobbies) { LobbyScanResult current2 = cachedLobby; LobbyInfo lobbyInfo2 = ((LobbyMetadataInfo)(ref current2.Metadata)).LobbyInfo; object obj; if (lobbyInfo2 == null) { obj = null; } else { PlayerList playerList2 = lobbyInfo2.PlayerList; obj = ((playerList2 != null) ? playerList2.Players : null); } if (obj == null) { continue; } PlayerInfo[] array2 = ((LobbyMetadataInfo)(ref current2.Metadata)).LobbyInfo.PlayerList.Players; for (int i = 0; i < array2.Length; i++) { if (array2[i].PlatformID == status.Entry.PlatformID) { status.IsOnline = true; status.CurrentLobbyName = ((LobbyMetadataInfo)(ref current2.Metadata)).LobbyInfo.LobbyName ?? "Unknown"; status.CurrentLobbyCode = ((LobbyMetadataInfo)(ref current2.Metadata)).LobbyInfo.LobbyCode ?? string.Empty; status.LobbyPlayerCount = ((LobbyMetadataInfo)(ref current2.Metadata)).LobbyInfo.PlayerCount; status.LobbyMaxPlayers = ((LobbyMetadataInfo)(ref current2.Metadata)).LobbyInfo.MaxPlayers; status.CurrentLevel = ((LobbyMetadataInfo)(ref current2.Metadata)).LobbyInfo.LevelTitle ?? "Unknown"; break; } } if (status.IsOnline) { break; } } } if (isOnline != status.IsOnline) { if (status.IsOnline) { _onNotification?.Invoke(status.Entry.Username + " is now online!"); } else { _onNotification?.Invoke(status.Entry.Username + " went offline."); } } } } private void ScanLobbies() { if (!NetworkLayerManager.LoggedIn) { return; } try { IMatchmaker matchmaker = NetworkLayerManager.Layer.Matchmaker; if (matchmaker == null) { return; } matchmaker.RequestLobbies((Action)delegate(MatchmakerCallbackInfo callback) { //IL_000b: Unknown result type (might be due to invalid IL or missing references) //IL_0014: Unknown result type (might be due to invalid IL or missing references) //IL_0021: Unknown result type (might be due to invalid IL or missing references) //IL_0026: Unknown result type (might be due to invalid IL or missing references) //IL_0037: Unknown result type (might be due to invalid IL or missing references) //IL_0038: Unknown result type (might be due to invalid IL or missing references) //IL_003d: Unknown result type (might be due to invalid IL or missing references) //IL_0044: Unknown result type (might be due to invalid IL or missing references) _cachedLobbies.Clear(); if (callback.Lobbies != null) { LobbyInfo[] lobbies = callback.Lobbies; foreach (LobbyInfo val in lobbies) { _cachedLobbies.Add(new LobbyScanResult { Metadata = val.Metadata, Lobby = val.Lobby }); } UpdateStatuses(); } }); } catch (Exception ex) { MelonLogger.Warning("[LabLink] Lobby scan failed: " + ex.Message); } } public void JoinFriend(FriendStatus friend) { if (!friend.IsOnline || string.IsNullOrEmpty(friend.CurrentLobbyCode)) { _onNotification?.Invoke(friend.Entry.Username + " is not in a joinable lobby."); return; } try { NetworkHelper.JoinServerByCode(friend.CurrentLobbyCode); _onNotification?.Invoke("Joining " + friend.Entry.Username + "'s lobby..."); } catch (Exception ex) { MelonLogger.Error("[LabLink] Failed to join lobby: " + ex.Message); _onNotification?.Invoke("Failed to join lobby."); } } public List GetCachedLobbies() { return _cachedLobbies; } } }