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.Text; using BepInEx; using BepInEx.Configuration; using BepInEx.Logging; using HarmonyLib; using Photon.Pun; using Photon.Realtime; using UnityEngine; [assembly: CompilationRelaxations(8)] [assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)] [assembly: Debuggable(DebuggableAttribute.DebuggingModes.Default | DebuggableAttribute.DebuggingModes.DisableOptimizations | DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints | DebuggableAttribute.DebuggingModes.EnableEditAndContinue)] [assembly: AssemblyTitle("ClassLibrary1")] [assembly: AssemblyDescription("")] [assembly: AssemblyConfiguration("")] [assembly: AssemblyCompany("")] [assembly: AssemblyProduct("ClassLibrary1")] [assembly: AssemblyCopyright("Copyright © 2026")] [assembly: AssemblyTrademark("")] [assembly: ComVisible(false)] [assembly: Guid("47cf1faa-03e5-4dc1-abb6-d597599585b7")] [assembly: AssemblyFileVersion("1.0.0.0")] [assembly: TargetFramework(".NETFramework,Version=v4.8", FrameworkDisplayName = ".NET Framework 4.8")] [assembly: AssemblyVersion("1.0.0.0")] namespace REPOContacts; public static class PluginInfo { public const string PLUGIN_GUID = "com.axiom.repocontacts"; public const string PLUGIN_NAME = "R.E.P.O. Contacts"; public const string PLUGIN_VERSION = "1.1.0"; public const string AUTHOR = "3udah"; public const string DISCORD = "75b"; } [Serializable] public class PlayerRecord { public string SteamId; public string NickName; public string FirstSeen; public string LastSeen; public int SessionsTogether; public int TotalSecondsPlayed; public bool IsFavorite; public string Notes; } [Serializable] public class SessionRecord { public string SessionId; public string Date; public string LevelName; public int LevelReached; public int TotalValue; public bool RunSucceeded; public int DurationSeconds; public string PlayerSteamIdsCombined; } [Serializable] public class ContactsDatabase { public List Players = new List(); public List Sessions = new List(); } [BepInPlugin("com.axiom.repocontacts", "R.E.P.O. Contacts", "1.1.0")] public class ContactsPlugin : BaseUnityPlugin { internal static ManualLogSource Logger; internal static ConfigEntry ToggleKey; internal static ConfigEntry ShowJoinNotifications; private static ContactsDatabase _db = new ContactsDatabase(); private static readonly object _dbLock = new object(); private static readonly string SavePath = Path.Combine(Paths.ConfigPath, "REPOContacts"); private static readonly string SaveFile = Path.Combine(SavePath, "contacts.json"); private static SessionRecord _currentSession; private static float _sessionTimer = 0f; private static float _scanTimer = 0f; private static float _playTimeTimer = 0f; private static bool _uiVisible = false; private static string _searchQuery = ""; private static int _currentFilter = 0; private static PlayerRecord _selectedPlayer = null; private static int _playerDetailTab = 0; private static SessionRecord _selectedSession = null; private static Vector2 _mainScroll = Vector2.zero; private static Vector2 _detailScroll = Vector2.zero; private static Vector2 _sessionScroll = Vector2.zero; private static string _notesEditText = ""; private static bool _editingNotes = false; private static string _notificationText = ""; private static float _notificationTimer = 0f; private static float _notificationDuration = 5f; private static Type _runManagerType; private static Type _playerAvatarType; private static bool _typesResolved = false; private static HashSet _currentlyOnline = new HashSet(); private static Dictionary _playerPlayTime = new Dictionary(); private static Rect _windowRect = new Rect(20f, 20f, 520f, 600f); private static Texture2D _modernBg; private static Texture2D _modernHeader; private static Texture2D _modernBtn; private static Texture2D _modernBtnHover; private void Awake() { //IL_00b8: Unknown result type (might be due to invalid IL or missing references) Logger = ((BaseUnityPlugin)this).Logger; ToggleKey = ((BaseUnityPlugin)this).Config.Bind("General", "ToggleKey", (KeyCode)291, "Key to toggle the Contacts overlay"); ShowJoinNotifications = ((BaseUnityPlugin)this).Config.Bind("General", "ShowJoinNotifications", true, "Show notification when someone you know joins"); ((BaseUnityPlugin)this).Config.Bind("Credits", "Author", "3udah", "Mod author (read-only)"); ((BaseUnityPlugin)this).Config.Bind("Credits", "Discord", "75b", "Author Discord (read-only)"); Directory.CreateDirectory(SavePath); LoadDatabase(); ResolveTypes(); Logger.LogInfo((object)$"R.E.P.O. Contacts loaded! Press {ToggleKey.Value} to open. Fuck yeah."); Harmony.CreateAndPatchAll(typeof(ContactsPlugin), (string)null); } private void ResolveTypes() { _runManagerType = AccessTools.TypeByName("RunManager"); _playerAvatarType = AccessTools.TypeByName("PlayerAvatar"); _typesResolved = _runManagerType != null && _playerAvatarType != null; Logger.LogInfo((object)$"Type resolution: RunManager={_runManagerType != null}, PlayerAvatar={_playerAvatarType != null}"); } private void Update() { //IL_0006: Unknown result type (might be due to invalid IL or missing references) if (Input.GetKeyDown(ToggleKey.Value)) { _uiVisible = !_uiVisible; if (_uiVisible && _selectedPlayer != null) { _notesEditText = _selectedPlayer.Notes ?? ""; } } _scanTimer -= Time.deltaTime; if (_scanTimer <= 0f) { _scanTimer = 5f; ScanLobby(); } _playTimeTimer -= Time.deltaTime; if (_playTimeTimer <= 0f) { _playTimeTimer = 1f; UpdatePlayTime(); } if (_currentSession != null) { _sessionTimer += Time.deltaTime; _currentSession.DurationSeconds = (int)_sessionTimer; } if (_notificationTimer > 0f) { _notificationTimer -= Time.deltaTime; } } private void UpdatePlayTime() { if (!PhotonNetwork.IsConnected || !PhotonNetwork.InRoom) { return; } Player[] playerList = PhotonNetwork.PlayerList; foreach (Player val in playerList) { if (val == null || val.IsLocal) { continue; } string text = val.UserId; if (string.IsNullOrEmpty(text) && _playerAvatarType != null) { try { Array array = Object.FindObjectsOfType(_playerAvatarType); FieldInfo fieldInfo = AccessTools.Field(_playerAvatarType, "steamID"); if (fieldInfo != null) { foreach (object item in array) { Component val2 = (Component)((item is Component) ? item : null); if (!((Object)(object)val2 == (Object)null)) { PhotonView component = val2.GetComponent(); if ((Object)(object)component != (Object)null && component.Owner != null && component.Owner.ActorNumber == val.ActorNumber) { text = (string)fieldInfo.GetValue(val2); break; } } } } } catch { } } if (!string.IsNullOrEmpty(text)) { if (!_playerPlayTime.ContainsKey(text)) { _playerPlayTime[text] = 0f; } _playerPlayTime[text] += 1f; } } } private void ScanLobby() { if (!PhotonNetwork.IsConnected || !PhotonNetwork.InRoom) { return; } _currentlyOnline.Clear(); Dictionary dictionary = new Dictionary(); try { if (_playerAvatarType != null) { Array array = Object.FindObjectsOfType(_playerAvatarType); FieldInfo fieldInfo = AccessTools.Field(_playerAvatarType, "steamID"); if (fieldInfo != null) { foreach (object item in array) { Component val = (Component)((item is Component) ? item : null); if ((Object)(object)val == (Object)null) { continue; } PhotonView component = val.GetComponent(); if (!((Object)(object)component == (Object)null) && component.Owner != null) { string text = (string)fieldInfo.GetValue(val); if (!string.IsNullOrEmpty(text) && text != "0") { dictionary[component.Owner.ActorNumber] = text; } } } } } } catch { } Player[] playerList = PhotonNetwork.PlayerList; foreach (Player val2 in playerList) { if (val2 == null) { continue; } string steamId = val2.UserId; string text2 = (string.IsNullOrEmpty(val2.NickName) ? "Unknown" : val2.NickName); if (string.IsNullOrEmpty(steamId) && dictionary.ContainsKey(val2.ActorNumber)) { steamId = dictionary[val2.ActorNumber]; } if (string.IsNullOrEmpty(steamId) || steamId == "0") { steamId = "Actor_" + val2.ActorNumber; } _currentlyOnline.Add(steamId); if (val2.IsLocal) { continue; } lock (_dbLock) { PlayerRecord playerRecord = _db.Players.Find((PlayerRecord p) => p.SteamId == steamId); if (playerRecord == null) { playerRecord = new PlayerRecord { SteamId = steamId, NickName = text2, FirstSeen = DateTime.Now.ToString("yyyy-MM-dd HH:mm"), SessionsTogether = 0 }; _db.Players.Insert(0, playerRecord); } else if (ShowJoinNotifications.Value && playerRecord.SessionsTogether > 0) { ShowNotification($"\ud83d\udc4b {text2} joined!\nPlayed together {playerRecord.SessionsTogether} times"); } playerRecord.NickName = text2; playerRecord.LastSeen = DateTime.Now.ToString("yyyy-MM-dd HH:mm"); if (_currentSession != null && !_currentSession.PlayerSteamIdsCombined.Contains(steamId)) { SessionRecord currentSession = _currentSession; currentSession.PlayerSteamIdsCombined = currentSession.PlayerSteamIdsCombined + steamId + ","; } if (_playerPlayTime.ContainsKey(steamId)) { int num = (int)_playerPlayTime[steamId]; if (playerRecord.TotalSecondsPlayed < num) { playerRecord.TotalSecondsPlayed = num; } } SaveDatabase(); } } } private void ShowNotification(string text) { _notificationText = text; _notificationTimer = _notificationDuration; } [HarmonyPatch] [HarmonyPatch(typeof(RunManager), "UpdateLevel")] private static void StartSessionPostfix() { try { if (_currentSession == null && PhotonNetwork.IsConnected && PhotonNetwork.InRoom) { StartNewSession("Unknown Level"); } } catch { } } private static void StartNewSession(string levelName) { _currentSession = new SessionRecord { SessionId = Guid.NewGuid().ToString(), Date = DateTime.Now.ToString("yyyy-MM-dd HH:mm"), LevelName = levelName, LevelReached = 1, TotalValue = 0, RunSucceeded = false, DurationSeconds = 0, PlayerSteamIdsCombined = "" }; _sessionTimer = 0f; if (PhotonNetwork.IsConnected && PhotonNetwork.InRoom) { Player[] playerList = PhotonNetwork.PlayerList; foreach (Player val in playerList) { if (val == null || val.IsLocal) { continue; } string steamId = val.UserId; if (string.IsNullOrEmpty(steamId)) { steamId = "Actor_" + val.ActorNumber; } if (!_currentSession.PlayerSteamIdsCombined.Contains(steamId)) { SessionRecord currentSession = _currentSession; currentSession.PlayerSteamIdsCombined = currentSession.PlayerSteamIdsCombined + steamId + ","; } lock (_dbLock) { PlayerRecord playerRecord = _db.Players.Find((PlayerRecord p) => p.SteamId == steamId); if (playerRecord != null) { playerRecord.SessionsTogether++; } } } } lock (_dbLock) { _db.Sessions.Insert(0, _currentSession); SaveDatabase(); } } private Texture2D MakeTex(int width, int height, Color col) { //IL_0010: Unknown result type (might be due to invalid IL or missing references) //IL_0011: 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_002c: Expected O, but got Unknown Color[] array = (Color[])(object)new Color[width * height]; for (int i = 0; i < array.Length; i++) { array[i] = col; } Texture2D val = new Texture2D(width, height); val.SetPixels(array); val.Apply(); return val; } private void OnGUI() { //IL_00dc: 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_00f7: Expected O, but got Unknown //IL_00f2: Unknown result type (might be due to invalid IL or missing references) //IL_00f7: Unknown result type (might be due to invalid IL or missing references) //IL_0055: Unknown result type (might be due to invalid IL or missing references) //IL_007b: Unknown result type (might be due to invalid IL or missing references) //IL_00a1: Unknown result type (might be due to invalid IL or missing references) //IL_00c7: Unknown result type (might be due to invalid IL or missing references) if (_notificationTimer > 0f) { DrawNotification(); } if (_uiVisible) { if ((Object)(object)_modernBg == (Object)null) { _modernBg = MakeTex(1, 1, new Color(0.1f, 0.1f, 0.15f, 0.98f)); _modernHeader = MakeTex(1, 1, new Color(0.18f, 0.08f, 0.12f, 1f)); _modernBtn = MakeTex(1, 1, new Color(0.2f, 0.2f, 0.28f, 1f)); _modernBtnHover = MakeTex(1, 1, new Color(0.3f, 0.3f, 0.4f, 1f)); } _windowRect = GUI.Window(987654, _windowRect, new WindowFunction(DrawWindow), ""); } } private void DrawWindow(int id) { //IL_001f: Unknown result type (might be due to invalid IL or missing references) //IL_00b0: Unknown result type (might be due to invalid IL or missing references) GUI.DrawTexture(new Rect(0f, 0f, ((Rect)(ref _windowRect)).width, ((Rect)(ref _windowRect)).height), (Texture)(object)_modernBg); float width = ((Rect)(ref _windowRect)).width; float height = ((Rect)(ref _windowRect)).height; if (_selectedSession != null) { DrawSessionDetail(0f, 0f, width, height); } else if (_selectedPlayer != null) { DrawPlayerDetail(0f, 0f, width, height); } else { DrawMainList(0f, 0f, width, height); } GUI.DragWindow(new Rect(0f, 0f, width, 30f)); } private void DrawMainList(float x, float y, float w, float h) { //IL_0009: Unknown result type (might be due to invalid IL or missing references) //IL_0023: Unknown result type (might be due to invalid IL or missing references) //IL_0028: Unknown result type (might be due to invalid IL or missing references) //IL_0031: 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_0042: Expected O, but got Unknown //IL_0056: Unknown result type (might be due to invalid IL or missing references) //IL_006f: Unknown result type (might be due to invalid IL or missing references) //IL_0080: Unknown result type (might be due to invalid IL or missing references) //IL_0095: Unknown result type (might be due to invalid IL or missing references) //IL_009a: Unknown result type (might be due to invalid IL or missing references) //IL_00a4: Expected O, but got Unknown //IL_00be: Unknown result type (might be due to invalid IL or missing references) //IL_01bc: Unknown result type (might be due to invalid IL or missing references) //IL_01ec: Unknown result type (might be due to invalid IL or missing references) //IL_01f1: Unknown result type (might be due to invalid IL or missing references) //IL_0215: Unknown result type (might be due to invalid IL or missing references) //IL_021a: Unknown result type (might be due to invalid IL or missing references) //IL_021f: Unknown result type (might be due to invalid IL or missing references) //IL_022e: Unknown result type (might be due to invalid IL or missing references) //IL_0233: Unknown result type (might be due to invalid IL or missing references) //IL_023b: Unknown result type (might be due to invalid IL or missing references) //IL_0244: Unknown result type (might be due to invalid IL or missing references) //IL_024c: Unknown result type (might be due to invalid IL or missing references) //IL_0253: Unknown result type (might be due to invalid IL or missing references) //IL_025d: Expected O, but got Unknown //IL_0260: Expected O, but got Unknown //IL_0161: Unknown result type (might be due to invalid IL or missing references) //IL_0146: Unknown result type (might be due to invalid IL or missing references) //IL_0188: Unknown result type (might be due to invalid IL or missing references) //IL_0459: Unknown result type (might be due to invalid IL or missing references) //IL_046e: Unknown result type (might be due to invalid IL or missing references) //IL_0473: Unknown result type (might be due to invalid IL or missing references) //IL_047c: Unknown result type (might be due to invalid IL or missing references) //IL_0486: Expected O, but got Unknown //IL_0497: Unknown result type (might be due to invalid IL or missing references) //IL_04a6: Unknown result type (might be due to invalid IL or missing references) //IL_04ce: Unknown result type (might be due to invalid IL or missing references) //IL_030c: Unknown result type (might be due to invalid IL or missing references) //IL_03df: Unknown result type (might be due to invalid IL or missing references) GUI.DrawTexture(new Rect(x, y, w, 35f), (Texture)(object)_modernHeader); GUIStyle val = new GUIStyle(GUI.skin.label) { fontSize = 18, fontStyle = (FontStyle)1, alignment = (TextAnchor)4 }; GUI.color = new Color(1f, 0.4f, 0.4f, 1f); GUI.Label(new Rect(x, y + 5f, w, 30f), "R.E.P.O. CONTACTS", val); GUI.color = Color.white; GUIStyle val2 = new GUIStyle(GUI.skin.textField) { fontSize = 14 }; _searchQuery = GUI.TextField(new Rect(x + 15f, y + 45f, w - 30f, 25f), _searchQuery, val2); string[] array = new string[6] { "Recent", "Most Played", "Online", "Favorites", "Good Mates", "Once" }; float num = y + 77f; float num2 = (w - 30f) / (float)array.Length; for (int i = 0; i < array.Length; i++) { GUI.color = ((_currentFilter == i) ? new Color(0.4f, 0.2f, 0.2f, 1f) : new Color(0.15f, 0.15f, 0.2f, 1f)); if (GUI.Button(new Rect(x + 15f + (float)i * num2, num, num2 - 2f, 22f), array[i])) { _currentFilter = i; } } GUI.color = Color.white; List filteredPlayers = GetFilteredPlayers(); _mainScroll = GUI.BeginScrollView(new Rect(x + 10f, y + 110f, w - 20f, h - 120f), _mainScroll, new Rect(0f, 0f, w - 40f, (float)(filteredPlayers.Count * 65 + 10))); GUIStyle val3 = new GUIStyle(GUI.skin.button) { alignment = (TextAnchor)3, fontSize = 14, richText = true, padding = new RectOffset(10, 10, 5, 5) }; float num3 = 5f; foreach (PlayerRecord item in filteredPlayers) { bool flag = _currentlyOnline.Contains(item.SteamId); bool flag2 = false; if (PhotonNetwork.IsConnected && PhotonNetwork.InRoom) { Player masterClient = PhotonNetwork.MasterClient; if (masterClient != null && masterClient.UserId == item.SteamId) { flag2 = true; } } Texture2D val4 = (item.IsFavorite ? _modernBtnHover : (flag ? _modernBtn : _modernBg)); GUI.DrawTexture(new Rect(5f, num3, w - 50f, 55f), (Texture)(object)val4); string text = (flag ? "\ud83d\udfe2" : "⚪"); string text2 = (item.IsFavorite ? "⭐ " : ""); string text3 = (flag2 ? " \ud83d\udc51" : ""); string text4 = text + " " + text2 + "" + item.NickName + "" + text3 + "\n" + $"{item.SessionsTogether} sessions · {FormatTime(item.TotalSecondsPlayed)} · Last: {FormatRelativeTime(item.LastSeen)}"; if (GUI.Button(new Rect(5f, num3, w - 50f, 55f), text4, val3)) { _selectedPlayer = item; _notesEditText = item.Notes ?? ""; _playerDetailTab = 0; } num3 += 60f; } GUI.EndScrollView(); GUI.color = new Color(0.5f, 0.5f, 0.5f, 1f); GUIStyle val5 = new GUIStyle(GUI.skin.label) { fontSize = 10, alignment = (TextAnchor)4 }; GUI.Label(new Rect(x, y + h - 20f, w, 20f), string.Format("[{0}] Close · {1} players · Author: {2}", ToggleKey.Value, filteredPlayers.Count, "3udah"), val5); GUI.color = Color.white; } private void DrawPlayerDetail(float x, float y, float w, float h) { //IL_001c: Unknown result type (might be due to invalid IL or missing references) //IL_0036: 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_0044: Unknown result type (might be due to invalid IL or missing references) //IL_004d: Expected O, but got Unknown //IL_0065: Unknown result type (might be due to invalid IL or missing references) //IL_0097: Unknown result type (might be due to invalid IL or missing references) //IL_009c: Unknown result type (might be due to invalid IL or missing references) //IL_00a5: Unknown result type (might be due to invalid IL or missing references) //IL_00ad: Unknown result type (might be due to invalid IL or missing references) //IL_00b6: Expected O, but got Unknown //IL_00ca: 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_010a: Unknown result type (might be due to invalid IL or missing references) //IL_0209: Unknown result type (might be due to invalid IL or missing references) //IL_0231: Unknown result type (might be due to invalid IL or missing references) //IL_0236: Unknown result type (might be due to invalid IL or missing references) //IL_024e: Unknown result type (might be due to invalid IL or missing references) //IL_0253: Unknown result type (might be due to invalid IL or missing references) //IL_0258: Unknown result type (might be due to invalid IL or missing references) //IL_018b: Unknown result type (might be due to invalid IL or missing references) //IL_0170: Unknown result type (might be due to invalid IL or missing references) //IL_01b2: Unknown result type (might be due to invalid IL or missing references) if (_selectedPlayer == null) { return; } GUI.DrawTexture(new Rect(x, y, w, 35f), (Texture)(object)_modernHeader); GUIStyle val = new GUIStyle(GUI.skin.button) { fontSize = 14, alignment = (TextAnchor)3 }; if (GUI.Button(new Rect(x + 10f, y + 5f, 80f, 25f), "← Back", val)) { _selectedPlayer = null; _editingNotes = false; return; } GUIStyle val2 = new GUIStyle(GUI.skin.label) { fontSize = 16, fontStyle = (FontStyle)1, alignment = (TextAnchor)4 }; GUI.color = new Color(1f, 0.4f, 0.4f, 1f); GUI.Label(new Rect(x + 90f, y + 5f, w - 100f, 25f), _selectedPlayer.NickName.ToUpper(), val2); GUI.color = Color.white; string[] array = new string[3] { "Overview", "Sessions", "Notes" }; float num = y + 40f; float num2 = (w - 20f) / (float)array.Length; for (int i = 0; i < array.Length; i++) { GUI.color = ((_playerDetailTab == i) ? new Color(0.4f, 0.2f, 0.2f, 1f) : new Color(0.15f, 0.15f, 0.2f, 1f)); if (GUI.Button(new Rect(x + 10f + (float)i * num2, num, num2 - 2f, 25f), array[i])) { _playerDetailTab = i; if (i == 2) { _notesEditText = _selectedPlayer.Notes ?? ""; } } } GUI.color = Color.white; _detailScroll = GUI.BeginScrollView(new Rect(x + 10f, y + 75f, w - 20f, h - 85f), _detailScroll, new Rect(0f, 0f, w - 40f, h)); float startY = 10f; switch (_playerDetailTab) { case 0: startY = DrawPlayerOverview(startY, w); break; case 1: startY = DrawPlayerSessions(startY, w); break; case 2: startY = DrawPlayerNotes(startY, w); break; } GUI.EndScrollView(); } private float DrawPlayerOverview(float startY, float w) { //IL_0058: 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_006d: Unknown result type (might be due to invalid IL or missing references) //IL_0072: Unknown result type (might be due to invalid IL or missing references) //IL_007b: Unknown result type (might be due to invalid IL or missing references) //IL_0084: Expected O, but got Unknown //IL_0096: Unknown result type (might be due to invalid IL or missing references) //IL_00a3: Unknown result type (might be due to invalid IL or missing references) //IL_00c9: 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) //IL_011b: Unknown result type (might be due to invalid IL or missing references) //IL_0124: Expected O, but got Unknown //IL_01a3: Unknown result type (might be due to invalid IL or missing references) //IL_01cc: Unknown result type (might be due to invalid IL or missing references) bool flag = _currentlyOnline.Contains(_selectedPlayer.SteamId); string text = (flag ? "\ud83d\udfe2 ONLINE" : "\ud83d\udd34 OFFLINE"); GUI.color = (flag ? new Color(0.3f, 0.8f, 0.3f, 1f) : new Color(0.8f, 0.3f, 0.3f, 1f)); GUIStyle val = new GUIStyle(GUI.skin.label) { fontSize = 14, fontStyle = (FontStyle)1 }; GUI.Label(new Rect(10f, startY, w - 20f, 25f), text, val); GUI.color = Color.white; startY += 35f; if (GUI.Button(new Rect(10f, startY, w - 20f, 30f), "\ud83c\udf10 Open Steam Profile")) { Application.OpenURL("https://steamcommunity.com/profiles/" + _selectedPlayer.SteamId); } startY += 40f; GUIStyle val2 = new GUIStyle(GUI.skin.label) { fontSize = 13, richText = true }; string text2 = $"Sessions: {_selectedPlayer.SessionsTogether}\n" + "Time Together: " + FormatTime(_selectedPlayer.TotalSecondsPlayed) + "\nFirst Seen: " + _selectedPlayer.FirstSeen + "\nLast Seen: " + _selectedPlayer.LastSeen; GUI.Label(new Rect(10f, startY, w - 20f, 100f), text2, val2); startY += 110f; if (GUI.Button(new Rect(10f, startY, w - 20f, 30f), _selectedPlayer.IsFavorite ? "⭐ Remove Favorite" : "☆ Add Favorite")) { _selectedPlayer.IsFavorite = !_selectedPlayer.IsFavorite; lock (_dbLock) { SaveDatabase(); } } return startY + 40f; } private float DrawPlayerSessions(float startY, float w) { //IL_000b: Unknown result type (might be due to invalid IL or missing references) //IL_0010: Unknown result type (might be due to invalid IL or missing references) //IL_0018: 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_002a: Expected O, but got Unknown //IL_007e: Unknown result type (might be due to invalid IL or missing references) //IL_012e: Unknown result type (might be due to invalid IL or missing references) GUIStyle val = new GUIStyle(GUI.skin.button) { alignment = (TextAnchor)3, fontSize = 12, richText = true }; List list = _db.Sessions.Where((SessionRecord s) => s.PlayerSteamIdsCombined.Contains(_selectedPlayer.SteamId)).ToList(); if (list.Count == 0) { GUI.Label(new Rect(10f, startY, w - 20f, 30f), "No sessions recorded yet.", val); return startY + 35f; } foreach (SessionRecord item in list) { string text = (item.RunSucceeded ? "✅" : "❌"); string text2 = text + " " + item.Date + "\n" + $"Level {item.LevelReached} · {FormatTime(item.DurationSeconds)}"; if (GUI.Button(new Rect(10f, startY, w - 20f, 45f), text2, val)) { _selectedSession = item; } startY += 50f; } return startY; } private float DrawPlayerNotes(float startY, float w) { //IL_000b: Unknown result type (might be due to invalid IL or missing references) //IL_0010: Unknown result type (might be due to invalid IL or missing references) //IL_001a: Expected O, but got Unknown //IL_002c: Unknown result type (might be due to invalid IL or missing references) //IL_013f: Unknown result type (might be due to invalid IL or missing references) //IL_0144: Unknown result type (might be due to invalid IL or missing references) //IL_014d: Unknown result type (might be due to invalid IL or missing references) //IL_0157: Expected O, but got Unknown //IL_0065: Unknown result type (might be due to invalid IL or missing references) //IL_0097: 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_018d: Unknown result type (might be due to invalid IL or missing references) //IL_01b5: Unknown result type (might be due to invalid IL or missing references) GUIStyle val = new GUIStyle(GUI.skin.label) { fontSize = 13 }; GUI.Label(new Rect(10f, startY, w - 20f, 20f), "Personal Notes:", val); startY += 25f; if (_editingNotes) { _notesEditText = GUI.TextArea(new Rect(10f, startY, w - 20f, 150f), _notesEditText, 1000); startY += 155f; if (GUI.Button(new Rect(10f, startY, 80f, 25f), "Save")) { _selectedPlayer.Notes = _notesEditText; _editingNotes = false; lock (_dbLock) { SaveDatabase(); } } if (GUI.Button(new Rect(100f, startY, 80f, 25f), "Cancel")) { _editingNotes = false; _notesEditText = _selectedPlayer.Notes ?? ""; } } else { GUIStyle val2 = new GUIStyle(GUI.skin.box) { fontSize = 12, wordWrap = true }; string text = (string.IsNullOrEmpty(_selectedPlayer.Notes) ? "No notes yet." : _selectedPlayer.Notes); GUI.Box(new Rect(10f, startY, w - 20f, 150f), text, val2); startY += 155f; if (GUI.Button(new Rect(10f, startY, 100f, 25f), "✏\ufe0f Edit Notes")) { _editingNotes = true; _notesEditText = _selectedPlayer.Notes ?? ""; } } return startY + 35f; } private void DrawSessionDetail(float x, float y, float w, float h) { //IL_001c: Unknown result type (might be due to invalid IL or missing references) //IL_0036: 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_0044: Unknown result type (might be due to invalid IL or missing references) //IL_004d: Expected O, but got Unknown //IL_0065: Unknown result type (might be due to invalid IL or missing references) //IL_0091: Unknown result type (might be due to invalid IL or missing references) //IL_0096: Unknown result type (might be due to invalid IL or missing references) //IL_009f: Unknown result type (might be due to invalid IL or missing references) //IL_00a7: Unknown result type (might be due to invalid IL or missing references) //IL_00b0: Expected O, but got Unknown //IL_00c4: Unknown result type (might be due to invalid IL or missing references) //IL_00e9: Unknown result type (might be due to invalid IL or missing references) //IL_00fa: Unknown result type (might be due to invalid IL or missing references) //IL_0122: Unknown result type (might be due to invalid IL or missing references) //IL_0127: Unknown result type (might be due to invalid IL or missing references) //IL_0145: Unknown result type (might be due to invalid IL or missing references) //IL_014a: Unknown result type (might be due to invalid IL or missing references) //IL_014f: Unknown result type (might be due to invalid IL or missing references) //IL_0164: Unknown result type (might be due to invalid IL or missing references) //IL_0169: Unknown result type (might be due to invalid IL or missing references) //IL_0172: Unknown result type (might be due to invalid IL or missing references) //IL_017b: Expected O, but got Unknown //IL_021a: Unknown result type (might be due to invalid IL or missing references) //IL_0242: Unknown result type (might be due to invalid IL or missing references) //IL_030f: Unknown result type (might be due to invalid IL or missing references) if (_selectedSession == null) { return; } GUI.DrawTexture(new Rect(x, y, w, 35f), (Texture)(object)_modernHeader); GUIStyle val = new GUIStyle(GUI.skin.button) { fontSize = 14, alignment = (TextAnchor)3 }; if (GUI.Button(new Rect(x + 10f, y + 5f, 80f, 25f), "← Back", val)) { _selectedSession = null; return; } GUIStyle val2 = new GUIStyle(GUI.skin.label) { fontSize = 16, fontStyle = (FontStyle)1, alignment = (TextAnchor)4 }; GUI.color = new Color(1f, 0.4f, 0.4f, 1f); GUI.Label(new Rect(x + 90f, y + 5f, w - 100f, 25f), "SESSION DETAILS", val2); GUI.color = Color.white; _sessionScroll = GUI.BeginScrollView(new Rect(x + 10f, y + 45f, w - 20f, h - 55f), _sessionScroll, new Rect(0f, 0f, w - 40f, h + 200f)); float num = 10f; GUIStyle val3 = new GUIStyle(GUI.skin.label) { fontSize = 13, richText = true }; string text = "Date: " + _selectedSession.Date + "\n" + $"Level: {_selectedSession.LevelName} (Reached {_selectedSession.LevelReached})\n" + "Duration: " + FormatTime(_selectedSession.DurationSeconds) + "\nResult: " + (_selectedSession.RunSucceeded ? "✅ Success" : "❌ Failed"); GUI.Label(new Rect(10f, num, w - 20f, 100f), text, val3); num += 110f; GUI.Label(new Rect(10f, num, w - 20f, 20f), "\ud83d\udc65 Squad:", val3); num += 25f; if (!string.IsNullOrEmpty(_selectedSession.PlayerSteamIdsCombined)) { string[] array = _selectedSession.PlayerSteamIdsCombined.Split(new char[1] { ',' }); string[] array2 = array; foreach (string id in array2) { if (!string.IsNullOrEmpty(id)) { PlayerRecord playerRecord = _db.Players.Find((PlayerRecord p) => p.SteamId == id); string text2 = ((playerRecord != null) ? playerRecord.NickName : "Unknown Player"); if (GUI.Button(new Rect(10f, num, w - 20f, 25f), text2) && playerRecord != null) { _selectedPlayer = playerRecord; _selectedSession = null; _playerDetailTab = 0; } num += 30f; } } } GUI.EndScrollView(); } private void DrawNotification() { //IL_005f: Unknown result type (might be due to invalid IL or missing references) //IL_006c: Unknown result type (might be due to invalid IL or missing references) //IL_0083: Unknown result type (might be due to invalid IL or missing references) //IL_0088: Unknown result type (might be due to invalid IL or missing references) //IL_0091: Unknown result type (might be due to invalid IL or missing references) //IL_0099: Unknown result type (might be due to invalid IL or missing references) //IL_00a1: Unknown result type (might be due to invalid IL or missing references) //IL_00ab: Expected O, but got Unknown //IL_00bc: 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_00f5: Unknown result type (might be due to invalid IL or missing references) float num = 300f; float num2 = 80f; float num3 = (float)Screen.width - num - 20f; float num4 = 20f; float num5 = Mathf.Clamp(_notificationTimer / _notificationDuration, 0f, 1f); Color col = default(Color); ((Color)(ref col))..ctor(0.1f, 0.1f, 0.15f, num5 * 0.95f); Texture2D val = MakeTex(1, 1, col); GUI.DrawTexture(new Rect(num3, num4, num, num2), (Texture)(object)val); GUIStyle val2 = new GUIStyle(GUI.skin.label) { fontSize = 12, richText = true, wordWrap = true, alignment = (TextAnchor)3 }; GUI.color = new Color(1f, 1f, 1f, num5); GUI.Label(new Rect(num3 + 10f, num4 + 10f, num - 20f, num2 - 20f), _notificationText, val2); GUI.color = Color.white; } private List GetFilteredPlayers() { lock (_dbLock) { IEnumerable source = _db.Players.AsEnumerable(); if (!string.IsNullOrEmpty(_searchQuery)) { string q = _searchQuery.ToLower(); source = source.Where((PlayerRecord p) => p.NickName.ToLower().Contains(q)); } switch (_currentFilter) { case 0: source = source.OrderByDescending((PlayerRecord p) => p.LastSeen); break; case 1: source = source.OrderByDescending((PlayerRecord p) => p.SessionsTogether); break; case 2: source = source.Where((PlayerRecord p) => _currentlyOnline.Contains(p.SteamId)); break; case 3: source = source.Where((PlayerRecord p) => p.IsFavorite); break; case 4: source = source.OrderByDescending((PlayerRecord p) => p.SessionsTogether); break; case 5: source = source.Where((PlayerRecord p) => p.SessionsTogether <= 1); break; } return source.ToList(); } } private void LoadDatabase() { try { if (!File.Exists(SaveFile)) { Logger.LogWarning((object)"contacts.json not found. Creating new database."); _db = new ContactsDatabase(); return; } string text = File.ReadAllText(SaveFile); Logger.LogInfo((object)$"Read {text.Length} bytes from contacts.json."); _db = new ContactsDatabase(); if (text.Contains("\"Players\":[")) { int num = text.IndexOf("\"Players\":[") + 11; int num2 = text.IndexOf("],\"Sessions\""); if (num2 == -1) { num2 = text.LastIndexOf("]"); } if (num2 > num) { string text2 = text.Substring(num, num2 - num); string[] array = text2.Split(new string[1] { "},{" }, StringSplitOptions.None); string[] array2 = array; foreach (string text3 in array2) { string json = text3.Trim().TrimStart(new char[1] { '{' }).TrimEnd(new char[1] { '}' }); PlayerRecord playerRecord = new PlayerRecord { SteamId = ExtractJsonField(json, "SteamId"), NickName = ExtractJsonField(json, "NickName"), FirstSeen = ExtractJsonField(json, "FirstSeen"), LastSeen = ExtractJsonField(json, "LastSeen"), Notes = ExtractJsonField(json, "Notes") }; int.TryParse(ExtractJsonRaw(json, "SessionsTogether"), out playerRecord.SessionsTogether); int.TryParse(ExtractJsonRaw(json, "TotalSecondsPlayed"), out playerRecord.TotalSecondsPlayed); bool.TryParse(ExtractJsonRaw(json, "IsFavorite"), out playerRecord.IsFavorite); _db.Players.Add(playerRecord); } } } Logger.LogInfo((object)$"Loaded {_db.Players.Count} players."); } catch (Exception arg) { Logger.LogError((object)$"Failed to load database: {arg}"); _db = new ContactsDatabase(); } } private string ExtractJsonField(string json, string fieldName) { string text = "\"" + fieldName + "\":\""; int num = json.IndexOf(text); if (num == -1) { return ""; } num += text.Length; int num2 = json.IndexOf("\",", num); if (num2 == -1) { num2 = json.IndexOf("\"", num); } return json.Substring(num, num2 - num); } private string ExtractJsonRaw(string json, string fieldName) { string text = "\"" + fieldName + "\":"; int num = json.IndexOf(text); if (num == -1) { return ""; } num += text.Length; int num2 = json.IndexOf(",", num); if (num2 == -1) { num2 = json.IndexOf("}", num); } return json.Substring(num, num2 - num).Trim(); } private static void SaveDatabase() { try { StringBuilder stringBuilder = new StringBuilder(); stringBuilder.Append("{"); stringBuilder.Append("\"Players\":["); for (int i = 0; i < _db.Players.Count; i++) { PlayerRecord playerRecord = _db.Players[i]; if (i > 0) { stringBuilder.Append(","); } stringBuilder.Append("{"); stringBuilder.Append("\"SteamId\":\"" + EscapeJson(playerRecord.SteamId) + "\","); stringBuilder.Append("\"NickName\":\"" + EscapeJson(playerRecord.NickName) + "\","); stringBuilder.Append("\"FirstSeen\":\"" + EscapeJson(playerRecord.FirstSeen) + "\","); stringBuilder.Append("\"LastSeen\":\"" + EscapeJson(playerRecord.LastSeen) + "\","); stringBuilder.Append($"\"SessionsTogether\":{playerRecord.SessionsTogether},"); stringBuilder.Append($"\"TotalSecondsPlayed\":{playerRecord.TotalSecondsPlayed},"); stringBuilder.Append("\"IsFavorite\":" + (playerRecord.IsFavorite ? "true" : "false") + ","); stringBuilder.Append("\"Notes\":\"" + EscapeJson(playerRecord.Notes ?? "") + "\""); stringBuilder.Append("}"); } stringBuilder.Append("],"); stringBuilder.Append("\"Sessions\":["); for (int j = 0; j < _db.Sessions.Count; j++) { SessionRecord sessionRecord = _db.Sessions[j]; if (j > 0) { stringBuilder.Append(","); } stringBuilder.Append("{"); stringBuilder.Append("\"SessionId\":\"" + EscapeJson(sessionRecord.SessionId) + "\","); stringBuilder.Append("\"Date\":\"" + EscapeJson(sessionRecord.Date) + "\","); stringBuilder.Append("\"LevelName\":\"" + EscapeJson(sessionRecord.LevelName) + "\","); stringBuilder.Append($"\"LevelReached\":{sessionRecord.LevelReached},"); stringBuilder.Append($"\"TotalValue\":{sessionRecord.TotalValue},"); stringBuilder.Append("\"RunSucceeded\":" + (sessionRecord.RunSucceeded ? "true" : "false") + ","); stringBuilder.Append($"\"DurationSeconds\":{sessionRecord.DurationSeconds},"); stringBuilder.Append("\"PlayerSteamIdsCombined\":\"" + EscapeJson(sessionRecord.PlayerSteamIdsCombined ?? "") + "\""); stringBuilder.Append("}"); } stringBuilder.Append("]"); stringBuilder.Append("}"); string text = stringBuilder.ToString(); File.WriteAllText(SaveFile, text); Logger.LogInfo((object)$"[SAVE] Wrote {text.Length} bytes to contacts.json"); } catch (Exception ex) { Logger.LogError((object)("Failed to save database: " + ex.Message)); } } private static string EscapeJson(string str) { if (string.IsNullOrEmpty(str)) { return ""; } return str.Replace("\\", "\\\\").Replace("\"", "\\\"").Replace("\n", "\\n") .Replace("\r", "\\r"); } private static string FormatTime(int seconds) { if (seconds < 60) { return $"{seconds}s"; } if (seconds < 3600) { return $"{seconds / 60}m"; } return $"{seconds / 3600}h {seconds % 3600 / 60}m"; } private static string FormatRelativeTime(string lastSeen) { try { DateTime dateTime = DateTime.Parse(lastSeen); TimeSpan timeSpan = DateTime.Now - dateTime; if (timeSpan.TotalMinutes < 1.0) { return "just now"; } if (timeSpan.TotalMinutes < 60.0) { return $"{(int)timeSpan.TotalMinutes}m ago"; } if (timeSpan.TotalHours < 24.0) { return $"{(int)timeSpan.TotalHours}h ago"; } if (timeSpan.TotalDays < 7.0) { return $"{(int)timeSpan.TotalDays}d ago"; } return dateTime.ToString("MMM dd"); } catch { return lastSeen; } } }