Decompiled source of REPOContacts v1.1.0

REPOContacts.dll

Decompiled 18 hours ago
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<PlayerRecord> Players = new List<PlayerRecord>();

	public List<SessionRecord> Sessions = new List<SessionRecord>();
}
[BepInPlugin("com.axiom.repocontacts", "R.E.P.O. Contacts", "1.1.0")]
public class ContactsPlugin : BaseUnityPlugin
{
	internal static ManualLogSource Logger;

	internal static ConfigEntry<KeyCode> ToggleKey;

	internal static ConfigEntry<bool> 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<string> _currentlyOnline = new HashSet<string>();

	private static Dictionary<string, float> _playerPlayTime = new Dictionary<string, float>();

	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<KeyCode>("General", "ToggleKey", (KeyCode)291, "Key to toggle the Contacts overlay");
		ShowJoinNotifications = ((BaseUnityPlugin)this).Config.Bind<bool>("General", "ShowJoinNotifications", true, "Show notification when someone you know joins");
		((BaseUnityPlugin)this).Config.Bind<string>("Credits", "Author", "3udah", "Mod author (read-only)");
		((BaseUnityPlugin)this).Config.Bind<string>("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<PhotonView>();
								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<int, string> dictionary = new Dictionary<int, string>();
		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<PhotonView>();
						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<PlayerRecord> 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 + "<b>" + item.NickName + "</b>" + text3 + "\n" + $"<size=10>{item.SessionsTogether} sessions · {FormatTime(item.TotalSecondsPlayed)} · Last: {FormatRelativeTime(item.LastSeen)}</size>";
			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 = $"<b>Sessions:</b> {_selectedPlayer.SessionsTogether}\n" + "<b>Time Together:</b> " + FormatTime(_selectedPlayer.TotalSecondsPlayed) + "\n<b>First Seen:</b> " + _selectedPlayer.FirstSeen + "\n<b>Last Seen:</b> " + _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<SessionRecord> 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 + " <b>" + item.Date + "</b>\n" + $"<size=10>Level {item.LevelReached} · {FormatTime(item.DurationSeconds)}</size>";
			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 = "<b>Date:</b> " + _selectedSession.Date + "\n" + $"<b>Level:</b> {_selectedSession.LevelName} (Reached {_selectedSession.LevelReached})\n" + "<b>Duration:</b> " + FormatTime(_selectedSession.DurationSeconds) + "\n<b>Result:</b> " + (_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), "<b>\ud83d\udc65 Squad:</b>", 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<PlayerRecord> GetFilteredPlayers()
	{
		lock (_dbLock)
		{
			IEnumerable<PlayerRecord> 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;
		}
	}
}