Decompiled source of LethalPhonesCustomNumbers v1.2.1

LethalPhonesCustomNumbers.dll

Decompiled 2 weeks ago
using System;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.Versioning;
using BepInEx;
using BepInEx.Bootstrap;
using BepInEx.Configuration;
using BepInEx.Logging;
using GameNetcodeStuff;
using HarmonyLib;
using Scoops;
using Scoops.customization;
using Scoops.misc;
using Scoops.service;
using TMPro;
using Unity.Netcode;
using UnityEngine;
using UnityEngine.Events;
using UnityEngine.UI;

[assembly: CompilationRelaxations(8)]
[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
[assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)]
[assembly: TargetFramework(".NETStandard,Version=v2.1", FrameworkDisplayName = ".NET Standard 2.1")]
[assembly: AssemblyCompany("LethalPhonesCustomNumbers")]
[assembly: AssemblyConfiguration("Release")]
[assembly: AssemblyFileVersion("1.2.1.0")]
[assembly: AssemblyInformationalVersion("1.2.1")]
[assembly: AssemblyProduct("LethalPhonesCustomNumbers")]
[assembly: AssemblyTitle("LethalPhonesCustomNumbers")]
[assembly: AssemblyVersion("1.2.1.0")]
namespace LethalPhonesCustomNumbers;

[HarmonyPatch(typeof(PhoneNetworkHandler))]
public class ClientPhonePatch
{
	[HarmonyPatch("CreateNewPhone")]
	[HarmonyPrefix]
	public static void CreateNewPhonePrefix(ref string skinId)
	{
		string text = ConfigManager.ClientRequestedNumber.Value;
		if (!string.IsNullOrEmpty(text) && text.Length > 0)
		{
			if (text.Length < 4)
			{
				text = text.PadLeft(4, '0');
			}
			skinId = skinId + "|" + text;
			Plugin.Logger.LogInfo((object)("Client appended requested number to skinId: " + skinId));
		}
	}
}
[HarmonyPatch(typeof(Clipboard), "UpdateText")]
internal static class ClipboardSyncPatch
{
	[HarmonyPostfix]
	private static void UpdateTextPostfix(Clipboard __instance)
	{
		if ((Object)(object)__instance == (Object)null || (Object)(object)__instance.textArea == (Object)null)
		{
			Plugin.Logger.LogWarning((object)"Phonebook clipboard update completed without a readable text area.");
			return;
		}
		string[] array = (from entry in ((TMP_Text)__instance.textArea).text.Split(new char[1] { '\n' }, StringSplitOptions.RemoveEmptyEntries)
			select entry.Trim() into entry
			where entry.Length > 0
			select entry).ToArray();
		string arg = ((array.Length == 0) ? "<empty>" : string.Join(", ", array));
		Plugin.Logger.LogInfo((object)$"Phonebook clipboard synchronized ({array.Length}): {arg}");
	}
}
public static class ConfigManager
{
	public static ConfigEntry<string> SteamIdMappingConfig;

	public static Dictionary<ulong, short> SteamIdToNumber = new Dictionary<ulong, short>();

	public static ConfigFile Config { get; private set; }

	public static ConfigEntry<string> ClientRequestedNumber { get; private set; }

	public static void Init(ConfigFile config)
	{
		Config = config;
		ClientRequestedNumber = Config.Bind<string>("Client", "RequestedNumber", "", "Your requested 4-digit phone number. Leave empty for a random number.");
		SteamIdMappingConfig = config.Bind<string>("General", "SteamIdMapping", "76561198000000000:1234,76561198000000001:5678", "Comma-separated list of SteamID:Number mappings.");
		ParseMapping();
	}

	public static void ParseMapping()
	{
		SteamIdToNumber.Clear();
		string value = SteamIdMappingConfig.Value;
		if (string.IsNullOrWhiteSpace(value))
		{
			return;
		}
		string[] array = value.Split(',');
		for (int i = 0; i < array.Length; i++)
		{
			string[] array2 = array[i].Split(':');
			if (array2.Length == 2 && ulong.TryParse(array2[0].Trim(), out var result) && short.TryParse(array2[1].Trim(), out var result2))
			{
				SteamIdToNumber[result] = result2;
				Plugin.Logger.LogInfo((object)$"Loaded CustomNumbers map: {result} -> {result2}");
			}
		}
	}
}
[HarmonyPatch(typeof(CustomizationManager))]
public class CustomizationUIPatch
{
	private static TMP_InputField numberInputField;

	private static readonly string[] TAB_NAMES = new string[3] { "SkinButton", "CharmButton", "RingtoneButton" };

	[HarmonyPatch("SpawnCustomizationGUI")]
	[HarmonyPostfix]
	public static void SpawnCustomizationGUIPostfix()
	{
		object? obj = AccessTools.Field(typeof(CustomizationManager), "customizationPanel")?.GetValue(null);
		Transform val = (Transform)((obj is Transform) ? obj : null);
		if ((Object)(object)val == (Object)null)
		{
			Plugin.Logger.LogError((object)"CustomNumbers: customizationPanel not found!");
			return;
		}
		Transform val2 = val.Find("Panel");
		if ((Object)(object)val2 == (Object)null)
		{
			Plugin.Logger.LogError((object)"CustomNumbers: 'Panel' not found!");
			return;
		}
		Plugin.Logger.LogInfo((object)"=== CustomNumbers: Panel Hierarchy Dump ===");
		DumpHierarchy(val2, "  ");
		Plugin.Logger.LogInfo((object)"=== CustomNumbers: End Dump ===");
		List<Button> list = FindTabButtons(val2);
		if (list.Count > 0)
		{
			Plugin.Logger.LogInfo((object)$"CustomNumbers: Found {list.Count} tab button(s)");
			CreatePhoneNumberTab(val2, list);
		}
		else
		{
			Plugin.Logger.LogError((object)"CustomNumbers: No tab buttons found — cannot create UI!");
		}
	}

	private static List<Button> FindTabButtons(Transform panel)
	{
		return (from b in ((Component)panel).GetComponentsInChildren<Button>(true)
			where TAB_NAMES.Contains(((Object)((Component)b).gameObject).name)
			orderby ((Component)b).GetComponent<RectTransform>().anchoredPosition.y descending
			select b).ToList();
	}

	private static void CreatePhoneNumberTab(Transform panel, List<Button> tabButtons)
	{
		//IL_00a3: Unknown result type (might be due to invalid IL or missing references)
		//IL_00ae: Unknown result type (might be due to invalid IL or missing references)
		//IL_00b9: Unknown result type (might be due to invalid IL or missing references)
		//IL_00cb: Unknown result type (might be due to invalid IL or missing references)
		//IL_00d7: Unknown result type (might be due to invalid IL or missing references)
		//IL_00ea: Unknown result type (might be due to invalid IL or missing references)
		//IL_00f6: Unknown result type (might be due to invalid IL or missing references)
		//IL_012c: Unknown result type (might be due to invalid IL or missing references)
		//IL_0136: Expected O, but got Unknown
		//IL_0034: Unknown result type (might be due to invalid IL or missing references)
		//IL_004a: Unknown result type (might be due to invalid IL or missing references)
		//IL_017f: Unknown result type (might be due to invalid IL or missing references)
		//IL_0184: Unknown result type (might be due to invalid IL or missing references)
		//IL_0191: 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_01b7: Unknown result type (might be due to invalid IL or missing references)
		//IL_01ce: Unknown result type (might be due to invalid IL or missing references)
		//IL_01d2: Unknown result type (might be due to invalid IL or missing references)
		//IL_024d: Unknown result type (might be due to invalid IL or missing references)
		//IL_0257: Expected O, but got Unknown
		//IL_0284: Unknown result type (might be due to invalid IL or missing references)
		//IL_0289: Unknown result type (might be due to invalid IL or missing references)
		//IL_028c: Expected O, but got Unknown
		//IL_0291: Expected O, but got Unknown
		//IL_02bd: Unknown result type (might be due to invalid IL or missing references)
		Button obj = tabButtons.First();
		Button val = tabButtons.Last();
		RectTransform component = ((Component)obj).GetComponent<RectTransform>();
		float num = 31f;
		if (tabButtons.Count >= 2)
		{
			float y = ((Component)tabButtons[0]).GetComponent<RectTransform>().anchoredPosition.y;
			float y2 = ((Component)tabButtons[1]).GetComponent<RectTransform>().anchoredPosition.y;
			num = Mathf.Abs(y - y2);
			Plugin.Logger.LogInfo((object)$"CustomNumbers: Tab spacing = {num}");
		}
		GameObject obj2 = Object.Instantiate<GameObject>(((Component)val).gameObject, ((Component)val).transform.parent);
		((Object)obj2).name = "PhoneNumberTab";
		RectTransform component2 = obj2.GetComponent<RectTransform>();
		float num2 = component.anchoredPosition.x - component.sizeDelta.x * component.pivot.x;
		float num3 = num2 + component2.sizeDelta.x * component2.pivot.x;
		component2.anchoredPosition = new Vector2(num3, component.anchoredPosition.y + num);
		Plugin.Logger.LogInfo((object)$"CustomNumbers: Skin left edge={num2}, our X={num3}");
		Button component3 = obj2.GetComponent<Button>();
		component3.onClick = new ButtonClickedEvent();
		((Selectable)component3).interactable = true;
		TextMeshProUGUI componentInChildren = obj2.GetComponentInChildren<TextMeshProUGUI>();
		if ((Object)(object)componentInChildren == (Object)null)
		{
			Plugin.Logger.LogError((object)"CustomNumbers: Cloned tab has no TextMeshProUGUI!");
			return;
		}
		((TMP_Text)componentInChildren).text = "[ Phone # ]";
		TMP_FontAsset font = ((TMP_Text)componentInChildren).font;
		float fontSize = ((TMP_Text)componentInChildren).fontSize;
		Color color = ((Graphic)componentInChildren).color;
		Material fontSharedMaterial = ((TMP_Text)componentInChildren).fontSharedMaterial;
		FontStyles fontStyle = ((TMP_Text)componentInChildren).fontStyle;
		Plugin.Logger.LogInfo((object)$"CustomNumbers: Tab style — font={((font != null) ? ((Object)font).name : null)} size={fontSize} color={color}");
		GameObject phoneDisplay = CreatePhoneNumberDisplay(panel, font, fontSize, color, fontSharedMaterial, fontStyle);
		phoneDisplay.SetActive(false);
		Transform phoneModel = panel.Find("PhoneDisplayModel");
		Transform charmDisplay = panel.Find("CharmDisplay");
		Transform audioDisplay = panel.Find("AudioDisplay");
		Transform nextBtn = panel.Find("NextButton");
		Transform prevBtn = panel.Find("PrevButton");
		((UnityEvent)component3.onClick).AddListener((UnityAction)delegate
		{
			if ((Object)(object)phoneModel != (Object)null)
			{
				((Component)phoneModel).gameObject.SetActive(false);
			}
			if ((Object)(object)charmDisplay != (Object)null)
			{
				((Component)charmDisplay).gameObject.SetActive(false);
			}
			if ((Object)(object)audioDisplay != (Object)null)
			{
				((Component)audioDisplay).gameObject.SetActive(false);
			}
			if ((Object)(object)nextBtn != (Object)null)
			{
				((Component)nextBtn).gameObject.SetActive(false);
			}
			if ((Object)(object)prevBtn != (Object)null)
			{
				((Component)prevBtn).gameObject.SetActive(false);
			}
			phoneDisplay.SetActive(true);
		});
		UnityAction val2 = default(UnityAction);
		foreach (Button tabButton in tabButtons)
		{
			ButtonClickedEvent onClick = ((Component)tabButton).GetComponent<Button>().onClick;
			UnityAction obj3 = val2;
			if (obj3 == null)
			{
				UnityAction val3 = delegate
				{
					phoneDisplay.SetActive(false);
					if ((Object)(object)nextBtn != (Object)null)
					{
						((Component)nextBtn).gameObject.SetActive(true);
					}
					if ((Object)(object)prevBtn != (Object)null)
					{
						((Component)prevBtn).gameObject.SetActive(true);
					}
				};
				UnityAction val4 = val3;
				val2 = val3;
				obj3 = val4;
			}
			((UnityEvent)onClick).AddListener(obj3);
		}
		Plugin.Logger.LogInfo((object)$"CustomNumbers: [ Phone # ] tab created at y={component2.anchoredPosition.y}");
	}

	private static GameObject CreatePhoneNumberDisplay(Transform panel, TMP_FontAsset font, float fontSize, Color color, Material fontMat, FontStyles fontStyle)
	{
		//IL_0005: Unknown result type (might be due to invalid IL or missing references)
		//IL_000b: Expected O, but got Unknown
		//IL_001f: Unknown result type (might be due to invalid IL or missing references)
		//IL_002a: Unknown result type (might be due to invalid IL or missing references)
		//IL_003f: Unknown result type (might be due to invalid IL or missing references)
		//IL_0053: Unknown result type (might be due to invalid IL or missing references)
		//IL_0062: Unknown result type (might be due to invalid IL or missing references)
		//IL_0067: Unknown result type (might be due to invalid IL or missing references)
		//IL_0079: Unknown result type (might be due to invalid IL or missing references)
		//IL_008a: 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_00b4: Unknown result type (might be due to invalid IL or missing references)
		//IL_00c8: Unknown result type (might be due to invalid IL or missing references)
		//IL_00d2: 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_012e: Unknown result type (might be due to invalid IL or missing references)
		//IL_0134: Expected O, but got Unknown
		//IL_0157: Unknown result type (might be due to invalid IL or missing references)
		//IL_016c: Unknown result type (might be due to invalid IL or missing references)
		//IL_0181: Unknown result type (might be due to invalid IL or missing references)
		//IL_0195: Unknown result type (might be due to invalid IL or missing references)
		//IL_01a4: Unknown result type (might be due to invalid IL or missing references)
		//IL_01aa: Expected O, but got Unknown
		//IL_01c6: Unknown result type (might be due to invalid IL or missing references)
		//IL_01d2: Unknown result type (might be due to invalid IL or missing references)
		//IL_01de: Unknown result type (might be due to invalid IL or missing references)
		//IL_01ea: Unknown result type (might be due to invalid IL or missing references)
		//IL_0200: Unknown result type (might be due to invalid IL or missing references)
		//IL_0205: Unknown result type (might be due to invalid IL or missing references)
		//IL_0217: Unknown result type (might be due to invalid IL or missing references)
		//IL_021e: Unknown result type (might be due to invalid IL or missing references)
		//IL_0229: Unknown result type (might be due to invalid IL or missing references)
		//IL_0234: Unknown result type (might be due to invalid IL or missing references)
		//IL_023e: Unknown result type (might be due to invalid IL or missing references)
		//IL_0248: Unknown result type (might be due to invalid IL or missing references)
		//IL_026c: Unknown result type (might be due to invalid IL or missing references)
		//IL_026d: Unknown result type (might be due to invalid IL or missing references)
		//IL_027d: Unknown result type (might be due to invalid IL or missing references)
		//IL_02cc: Unknown result type (might be due to invalid IL or missing references)
		//IL_02d1: 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)
		//IL_02ea: Unknown result type (might be due to invalid IL or missing references)
		//IL_02f5: Unknown result type (might be due to invalid IL or missing references)
		//IL_0300: Unknown result type (might be due to invalid IL or missing references)
		//IL_030a: Unknown result type (might be due to invalid IL or missing references)
		//IL_0314: Unknown result type (might be due to invalid IL or missing references)
		//IL_033a: Unknown result type (might be due to invalid IL or missing references)
		//IL_0361: Unknown result type (might be due to invalid IL or missing references)
		//IL_03d3: Unknown result type (might be due to invalid IL or missing references)
		GameObject val = new GameObject("PhoneNumberDisplay");
		val.transform.SetParent(panel, false);
		RectTransform obj = val.AddComponent<RectTransform>();
		obj.anchorMin = Vector2.zero;
		obj.anchorMax = Vector2.one;
		obj.offsetMin = new Vector2(150f, 60f);
		obj.offsetMax = new Vector2(-150f, -60f);
		GameObject val2 = new GameObject("Title");
		val2.transform.SetParent(val.transform, false);
		RectTransform obj2 = val2.AddComponent<RectTransform>();
		obj2.anchorMin = new Vector2(0.5f, 0.5f);
		obj2.anchorMax = new Vector2(0.5f, 0.5f);
		obj2.anchoredPosition = new Vector2(0f, 50f);
		obj2.sizeDelta = new Vector2(400f, 50f);
		val2.AddComponent<CanvasRenderer>();
		TextMeshProUGUI val3 = val2.AddComponent<TextMeshProUGUI>();
		((TMP_Text)val3).font = font;
		((TMP_Text)val3).fontSize = fontSize * 1.5f;
		((Graphic)val3).color = color;
		if ((Object)(object)fontMat != (Object)null)
		{
			((TMP_Text)val3).fontSharedMaterial = fontMat;
		}
		((TMP_Text)val3).alignment = (TextAlignmentOptions)514;
		((TMP_Text)val3).enableWordWrapping = false;
		((TMP_Text)val3).text = "Phone Number";
		GameObject val4 = new GameObject("PhoneNumberInput");
		val4.transform.SetParent(val.transform, false);
		RectTransform obj3 = val4.AddComponent<RectTransform>();
		obj3.anchorMin = new Vector2(0.5f, 0.5f);
		obj3.anchorMax = new Vector2(0.5f, 0.5f);
		obj3.anchoredPosition = new Vector2(0f, -20f);
		obj3.sizeDelta = new Vector2(400f, 70f);
		GameObject val5 = new GameObject("Text Area");
		val5.transform.SetParent(val4.transform, false);
		RectTransform val6 = val5.AddComponent<RectTransform>();
		val6.anchorMin = Vector2.zero;
		val6.anchorMax = Vector2.one;
		val6.offsetMin = Vector2.zero;
		val6.offsetMax = Vector2.zero;
		val5.AddComponent<RectMask2D>();
		GameObject val7 = new GameObject("Placeholder");
		val7.transform.SetParent(val5.transform, false);
		RectTransform obj4 = val7.AddComponent<RectTransform>();
		obj4.anchorMin = Vector2.zero;
		obj4.anchorMax = Vector2.one;
		obj4.offsetMin = Vector2.zero;
		obj4.offsetMax = Vector2.zero;
		val7.AddComponent<CanvasRenderer>();
		TextMeshProUGUI val8 = val7.AddComponent<TextMeshProUGUI>();
		((TMP_Text)val8).font = font;
		((TMP_Text)val8).fontSize = fontSize * 2.5f;
		Color color2 = color;
		color2.a = 0.4f;
		((Graphic)val8).color = color2;
		if ((Object)(object)fontMat != (Object)null)
		{
			((TMP_Text)val8).fontSharedMaterial = fontMat;
		}
		((TMP_Text)val8).alignment = (TextAlignmentOptions)514;
		((TMP_Text)val8).fontStyle = (FontStyles)2;
		((TMP_Text)val8).enableWordWrapping = false;
		((TMP_Text)val8).overflowMode = (TextOverflowModes)0;
		((TMP_Text)val8).text = "[ 0000 ]";
		GameObject val9 = new GameObject("Text");
		val9.transform.SetParent(val5.transform, false);
		RectTransform obj5 = val9.AddComponent<RectTransform>();
		obj5.anchorMin = Vector2.zero;
		obj5.anchorMax = Vector2.one;
		obj5.offsetMin = Vector2.zero;
		obj5.offsetMax = Vector2.zero;
		val9.AddComponent<CanvasRenderer>();
		TextMeshProUGUI val10 = val9.AddComponent<TextMeshProUGUI>();
		((TMP_Text)val10).font = font;
		((TMP_Text)val10).fontSize = fontSize * 2.5f;
		((Graphic)val10).color = color;
		if ((Object)(object)fontMat != (Object)null)
		{
			((TMP_Text)val10).fontSharedMaterial = fontMat;
		}
		((TMP_Text)val10).alignment = (TextAlignmentOptions)514;
		((TMP_Text)val10).fontStyle = fontStyle;
		((TMP_Text)val10).enableWordWrapping = false;
		((TMP_Text)val10).overflowMode = (TextOverflowModes)0;
		numberInputField = val4.AddComponent<TMP_InputField>();
		numberInputField.textViewport = val6;
		numberInputField.textComponent = (TMP_Text)(object)val10;
		numberInputField.placeholder = (Graphic)(object)val8;
		numberInputField.characterLimit = 4;
		numberInputField.contentType = (ContentType)2;
		numberInputField.pointSize = fontSize * 2.5f;
		numberInputField.caretColor = color;
		numberInputField.customCaretColor = true;
		string value = ConfigManager.ClientRequestedNumber.Value;
		if (!string.IsNullOrEmpty(value))
		{
			numberInputField.text = value;
		}
		Plugin.Logger.LogInfo((object)"CustomNumbers: Phone number display created");
		return val;
	}

	private static void DumpHierarchy(Transform t, string indent)
	{
		//IL_00b3: Unknown result type (might be due to invalid IL or missing references)
		//IL_00c1: Unknown result type (might be due to invalid IL or missing references)
		//IL_00cf: 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)
		string text = "";
		Component[] components = ((Component)t).GetComponents<Component>();
		foreach (Component val in components)
		{
			if ((Object)(object)val != (Object)null && !(val is Transform) && !(val is RectTransform))
			{
				text = text + " [" + ((object)val).GetType().Name + "]";
			}
		}
		TextMeshProUGUI component = ((Component)t).GetComponent<TextMeshProUGUI>();
		string text2 = (((Object)(object)component != (Object)null) ? (" text='" + ((TMP_Text)component).text + "'") : "");
		RectTransform component2 = ((Component)t).GetComponent<RectTransform>();
		string text3 = (((Object)(object)component2 != (Object)null) ? $" pos={component2.anchoredPosition} size={component2.sizeDelta} anchors=({component2.anchorMin},{component2.anchorMax})" : "");
		Plugin.Logger.LogInfo((object)(indent + ((Object)t).name + text + text2 + text3));
		for (int j = 0; j < t.childCount; j++)
		{
			DumpHierarchy(t.GetChild(j), indent + "  ");
		}
	}

	[HarmonyPatch("SaveCustomization")]
	[HarmonyPostfix]
	public static void SaveCustomizationPostfix()
	{
		if ((Object)(object)numberInputField != (Object)null)
		{
			string text = numberInputField.text;
			if (text.Length > 0 && text.Length < 4)
			{
				text = text.PadLeft(4, '0');
			}
			ConfigManager.ClientRequestedNumber.Value = text;
			ConfigManager.Config.Save();
			Plugin.Logger.LogInfo((object)("CustomNumbers: Saved phone number: " + text));
		}
	}
}
internal static class DelayedPhoneInitializationPatch
{
	private const float InitializationTimeoutSeconds = 60f;

	private const float RegistrationRetrySeconds = 2f;

	private static readonly MethodBase ConnectMethod = AccessTools.Method(typeof(PlayerControllerB), "ConnectClientToPlayerObject", (Type[])null, (Type[])null);

	private static readonly Type UpstreamPatchType = typeof(PhoneNetworkHandler).Assembly.GetType("Scoops.patch.PlayerPhonePatch", throwOnError: false);

	private static readonly MethodInfo UpstreamInitPhone = ((UpstreamPatchType == null) ? null : UpstreamPatchType.GetMethod("InitPhone", BindingFlags.Static | BindingFlags.NonPublic, null, new Type[1] { typeof(PlayerControllerB).MakeByRefType() }, null));

	private static readonly MethodInfo GuardedPostfix = AccessTools.Method(typeof(DelayedPhoneInitializationPatch), "ConnectClientToPlayerObjectPostfix", (Type[])null, (Type[])null);

	private static readonly HashSet<int> StartedPlayers = new HashSet<int>();

	private static readonly HashSet<ulong> AcknowledgedPhoneIds = new HashSet<ulong>();

	private static ConfigEntry<bool> harnessEnabled;

	private static ConfigEntry<float> harnessDelaySeconds;

	private static bool installed;

	internal static void Install(Harmony harmony)
	{
		//IL_00b2: Unknown result type (might be due to invalid IL or missing references)
		//IL_00bf: Expected O, but got Unknown
		if (installed)
		{
			return;
		}
		harnessEnabled = ((BaseUnityPlugin)Plugin.Instance).Config.Bind<bool>("Runtime Test Harness", "Enabled", false, "Enables CustomNumbers timing tests. Keep false during normal play.");
		harnessDelaySeconds = ((BaseUnityPlugin)Plugin.Instance).Config.Bind<float>("Runtime Test Harness", "Phone Initialization Delay Seconds", 0f, "Optional guarded phone initialization delay used only by the runtime harness.");
		if (harmony == null || ConnectMethod == null || UpstreamInitPhone == null || GuardedPostfix == null)
		{
			Plugin.Logger.LogError((object)"Could not resolve the exact LethalPhones 1.3.23 phone initialization methods; its original postfix was left untouched.");
			return;
		}
		bool flag = HasPostfix(Harmony.GetPatchInfo(ConnectMethod), GuardedPostfix);
		if (!flag)
		{
			harmony.Patch(ConnectMethod, (HarmonyMethod)null, new HarmonyMethod(GuardedPostfix), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null);
			flag = HasPostfix(Harmony.GetPatchInfo(ConnectMethod), GuardedPostfix);
		}
		if (!flag)
		{
			Plugin.Logger.LogError((object)"The guarded phone initialization postfix could not be installed; LethalPhones' original postfix was left untouched.");
			return;
		}
		harmony.Unpatch(ConnectMethod, UpstreamInitPhone);
		if (HasPostfix(Harmony.GetPatchInfo(ConnectMethod), UpstreamInitPhone))
		{
			harmony.Unpatch(ConnectMethod, GuardedPostfix);
			Plugin.Logger.LogError((object)"LethalPhones' immediate phone initialization postfix could not be removed; the guarded postfix was rolled back to avoid duplicate initialization.");
		}
		else
		{
			installed = true;
			Plugin.Logger.LogInfo((object)"Installed guarded phone initialization, then removed only LethalPhones' exact immediate postfix.");
		}
	}

	private static bool HasPostfix(Patches patches, MethodInfo method)
	{
		if (patches == null || method == null)
		{
			return false;
		}
		foreach (Patch postfix in patches.Postfixes)
		{
			if (postfix.PatchMethod == method)
			{
				return true;
			}
		}
		return false;
	}

	private static void ConnectClientToPlayerObjectPostfix(PlayerControllerB __instance)
	{
		if ((Object)(object)__instance == (Object)null)
		{
			Plugin.Logger.LogWarning((object)"Skipped guarded phone initialization because the player object was null.");
			return;
		}
		int instanceID = ((Object)__instance).GetInstanceID();
		if (!StartedPlayers.Add(instanceID))
		{
			Plugin.Logger.LogInfo((object)("Guarded phone initialization is already active or complete for " + __instance.playerUsername + "."));
		}
		else
		{
			((MonoBehaviour)__instance).StartCoroutine(InitializeWhenReady(__instance, instanceID));
		}
	}

	private static IEnumerator InitializeWhenReady(PlayerControllerB player, int instanceId)
	{
		float num = ((harnessEnabled != null && harnessEnabled.Value && harnessDelaySeconds != null) ? Mathf.Max(0f, harnessDelaySeconds.Value) : 0f);
		if (num > 0f)
		{
			Plugin.Logger.LogInfo((object)$"Runtime harness delaying guarded phone initialization by {num:0.###} seconds.");
			float delayEnd = Time.realtimeSinceStartup + num;
			while ((Object)(object)player != (Object)null && Time.realtimeSinceStartup < delayEnd)
			{
				yield return null;
			}
		}
		if ((Object)(object)player == (Object)null)
		{
			StartedPlayers.Remove(instanceId);
			yield break;
		}
		float deadline = Time.realtimeSinceStartup + 60f;
		string missing = "startup prerequisites";
		PhoneNetworkHandler handler = null;
		PlayerPhone phone = null;
		while ((Object)(object)player != (Object)null && Time.realtimeSinceStartup < deadline && !TryGetNetworkPrerequisites(player, out handler, out phone, out missing))
		{
			yield return null;
		}
		if ((Object)(object)player == (Object)null || (Object)(object)handler == (Object)null || (Object)(object)phone == (Object)null)
		{
			StartedPlayers.Remove(instanceId);
			string arg = (((Object)(object)player == (Object)null) ? "<destroyed player>" : player.playerUsername);
			Plugin.Logger.LogError((object)($"Guarded phone registration timed out after {60f:0} seconds for {arg}; " + "still missing " + missing + "."));
			yield break;
		}
		AcknowledgedPhoneIds.Remove(((NetworkBehaviour)phone).NetworkObjectId);
		bool setupInvoked = false;
		bool setupFailed = false;
		float nextRegistrationAttempt = 0f;
		int attempts = 0;
		while ((Object)(object)player != (Object)null && (Object)(object)phone != (Object)null && Time.realtimeSinceStartup < deadline)
		{
			if (!AcknowledgedPhoneIds.Contains(((NetworkBehaviour)phone).NetworkObjectId) && Time.realtimeSinceStartup >= nextRegistrationAttempt)
			{
				attempts++;
				try
				{
					handler.CreateNewPhone(((NetworkBehaviour)phone).NetworkObjectId, CustomizationManager.SelectedSkin, CustomizationManager.SelectedCharm, CustomizationManager.SelectedRingtone);
					Plugin.Logger.LogInfo((object)$"Guarded phone registration request {attempts} sent for {player.playerUsername} ({((NetworkBehaviour)phone).NetworkObjectId}).");
				}
				catch (Exception arg2)
				{
					Plugin.Logger.LogError((object)$"Guarded phone registration request {attempts} failed for {player.playerUsername}: {arg2}");
				}
				nextRegistrationAttempt = Time.realtimeSinceStartup + 2f;
			}
			if (!setupInvoked && !setupFailed && ModelAnchorsReady(player, out missing))
			{
				try
				{
					object[] parameters = new object[1] { player };
					UpstreamInitPhone.Invoke(null, parameters);
					setupInvoked = true;
					Plugin.Logger.LogInfo((object)("Guarded LethalPhones model/input setup completed for " + player.playerUsername + "."));
				}
				catch (TargetInvocationException ex)
				{
					setupFailed = true;
					Exception arg3 = ex.InnerException ?? ex;
					Plugin.Logger.LogError((object)$"Guarded LethalPhones model/input setup failed for {player.playerUsername}: {arg3}");
				}
				catch (Exception arg4)
				{
					setupFailed = true;
					Plugin.Logger.LogError((object)$"Guarded LethalPhones model/input setup failed for {player.playerUsername}: {arg4}");
				}
			}
			bool flag = AcknowledgedPhoneIds.Contains(((NetworkBehaviour)phone).NetworkObjectId);
			if (flag && setupInvoked)
			{
				Plugin.Logger.LogInfo((object)$"Guarded phone initialization acknowledged for {player.playerUsername} after {attempts} request(s).");
				yield break;
			}
			if (flag && setupFailed)
			{
				Plugin.Logger.LogError((object)("Phone number registration was acknowledged for " + player.playerUsername + ", but LethalPhones model/input setup did not complete."));
				yield break;
			}
			yield return null;
		}
		bool flag2 = (Object)(object)phone != (Object)null && AcknowledgedPhoneIds.Contains(((NetworkBehaviour)phone).NetworkObjectId);
		if (!flag2)
		{
			StartedPlayers.Remove(instanceId);
		}
		string text = (((Object)(object)player == (Object)null) ? "<destroyed player>" : player.playerUsername);
		Plugin.Logger.LogError((object)("Guarded phone initialization timed out for " + text + ": " + $"number acknowledged={flag2}, model/input setup complete={setupInvoked}, last missing={missing}."));
	}

	private static bool TryGetNetworkPrerequisites(PlayerControllerB player, out PhoneNetworkHandler handler, out PlayerPhone phone, out string missing)
	{
		handler = PhoneNetworkHandler.Instance;
		phone = null;
		if ((Object)(object)handler == (Object)null || !((NetworkBehaviour)handler).IsSpawned || (Object)(object)((NetworkBehaviour)handler).NetworkManager == (Object)null)
		{
			missing = "the spawned PhoneNetworkHandler";
			return false;
		}
		Transform val = ((Component)player).transform.Find("PhonePrefab(Clone)");
		if ((Object)(object)val == (Object)null)
		{
			missing = "the player's PhonePrefab(Clone) child";
			return false;
		}
		phone = ((Component)val).GetComponent<PlayerPhone>();
		NetworkObject component = ((Component)val).GetComponent<NetworkObject>();
		if ((Object)(object)phone == (Object)null || (Object)(object)component == (Object)null || !component.IsSpawned)
		{
			missing = "the spawned PlayerPhone NetworkObject";
			return false;
		}
		if (!((Component)phone).transform.IsChildOf(((Component)player).transform))
		{
			missing = "the PlayerPhone parent relationship";
			return false;
		}
		if (((NetworkBehaviour)handler).NetworkManager.SpawnManager == null || !((NetworkBehaviour)handler).NetworkManager.SpawnManager.SpawnedObjects.TryGetValue(component.NetworkObjectId, out var value) || (Object)(object)value != (Object)(object)component)
		{
			missing = "the PlayerPhone entry in SpawnManager";
			return false;
		}
		if ((Object)(object)GameNetworkManager.Instance == (Object)null || (Object)(object)GameNetworkManager.Instance.localPlayerController == (Object)null)
		{
			missing = "the local player controller";
			return false;
		}
		missing = null;
		return true;
	}

	private static bool ModelAnchorsReady(PlayerControllerB player, out string missing)
	{
		if ((Object)(object)GameNetworkManager.Instance.localPlayerController == (Object)(object)player)
		{
			if ((Object)(object)player.playerGlobalHead == (Object)null || (Object)(object)player.localArmsTransform == (Object)null || (Object)(object)player.localArmsTransform.Find("shoulder.L/arm.L_upper/arm.L_lower/hand.L/LocalPhoneModel(Clone)") == (Object)null)
			{
				missing = "the local phone model anchors";
				return false;
			}
		}
		else if ((Object)(object)player.lowerSpine == (Object)null || (Object)(object)player.lowerSpine.Find("spine.002/spine.003/shoulder.L/arm.L_upper/arm.L_lower/hand.L/ServerPhoneModel(Clone)") == (Object)null)
		{
			missing = "the remote phone model anchors";
			return false;
		}
		missing = null;
		return true;
	}

	internal static void Acknowledge(PlayerPhone phone)
	{
		PlayerControllerB val = (((Object)(object)GameNetworkManager.Instance == (Object)null) ? null : GameNetworkManager.Instance.localPlayerController);
		if (!((Object)(object)phone == (Object)null) && !((Object)(object)val == (Object)null) && ((Component)phone).transform.IsChildOf(((Component)val).transform))
		{
			AcknowledgedPhoneIds.Add(((NetworkBehaviour)phone).NetworkObjectId);
		}
	}

	internal static void ResetSession()
	{
		StartedPlayers.Clear();
		AcknowledgedPhoneIds.Clear();
	}

	internal static void Forget(PlayerControllerB player)
	{
		if (!((Object)(object)player == (Object)null))
		{
			StartedPlayers.Remove(((Object)player).GetInstanceID());
			Transform val = ((Component)player).transform.Find("PhonePrefab(Clone)");
			PlayerPhone val2 = (((Object)(object)val == (Object)null) ? null : ((Component)val).GetComponent<PlayerPhone>());
			if ((Object)(object)val2 != (Object)null)
			{
				AcknowledgedPhoneIds.Remove(((NetworkBehaviour)val2).NetworkObjectId);
			}
		}
	}
}
[HarmonyPatch(typeof(PlayerControllerB), "OnDestroy")]
internal static class DelayedPhoneInitializationCleanupPatch
{
	[HarmonyPostfix]
	private static void OnDestroyPostfix(PlayerControllerB __instance)
	{
		DelayedPhoneInitializationPatch.Forget(__instance);
	}
}
[HarmonyPatch(typeof(MenuManager))]
public class ForceUIPatch
{
	[HarmonyPatch("Start")]
	[HarmonyPostfix]
	public static void StartPostfix(MenuManager __instance)
	{
		if (!__instance.isInitScene)
		{
			((MonoBehaviour)__instance).StartCoroutine(DelayedMainMenuInjection());
		}
	}

	private static IEnumerator DelayedMainMenuInjection()
	{
		yield return (object)new WaitForSeconds(0f);
		GameObject val = GameObject.Find("MenuContainer");
		if (!Object.op_Implicit((Object)(object)val))
		{
			yield break;
		}
		Transform val2 = val.transform.Find("MainButtons");
		if (!Object.op_Implicit((Object)(object)val2) || (Object)(object)((IEnumerable<TextMeshProUGUI>)((Component)val2).GetComponentsInChildren<TextMeshProUGUI>()).FirstOrDefault((Func<TextMeshProUGUI, bool>)((TextMeshProUGUI t) => ((TMP_Text)t).text == "> Personalize Phone")) != (Object)null)
		{
			yield break;
		}
		Transform val3 = val2.Find("QuitButton");
		if (!Object.op_Implicit((Object)(object)val3))
		{
			yield break;
		}
		Plugin.Logger.LogInfo((object)"LethalPhonesCustomNumbers: Forcing Customization GUI to spawn because LethalPhones skipped it...");
		MethodInfo methodInfo = typeof(CustomizationManager).Assembly.GetType("Scoops.customization.CustomizationMenuUtils")?.GetMethod("InjectMenu", BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic);
		if (methodInfo != null)
		{
			methodInfo.Invoke(null, new object[2]
			{
				val2,
				((Component)val3).gameObject
			});
			object? obj = AccessTools.Field(typeof(CustomizationManager), "customizationPanel")?.GetValue(null);
			Transform val4 = (Transform)((obj is Transform) ? obj : null);
			if ((Object)(object)val4 != (Object)null)
			{
				Plugin.Logger.LogInfo((object)"Dumping CustomizationPanel hierarchy:");
				DumpTransform(val4, "");
			}
		}
		else
		{
			Plugin.Logger.LogError((object)"LethalPhonesCustomNumbers: Failed to find CustomizationMenuUtils.InjectMenu via reflection!");
		}
	}

	private static void DumpTransform(Transform t, string indent)
	{
		Plugin.Logger.LogInfo((object)(indent + ((Object)t).name + " (" + ((object)t).GetType().Name + ")"));
		Component[] components = ((Component)t).GetComponents<Component>();
		foreach (Component val in components)
		{
			if ((Object)(object)val != (Object)null && ((object)val).GetType() != typeof(Transform))
			{
				Plugin.Logger.LogInfo((object)(indent + " - " + ((object)val).GetType().Name));
			}
		}
		for (int j = 0; j < t.childCount; j++)
		{
			DumpTransform(t.GetChild(j), indent + "  ");
		}
	}
}
[HarmonyPatch(typeof(PhoneNetworkHandler))]
internal static class PhoneNetworkHandlerPatch
{
	private sealed class DictionaryRecoverySnapshot
	{
		internal readonly List<KeyValuePair<short, ulong>> NumberEntries = new List<KeyValuePair<short, ulong>>();

		internal readonly List<KeyValuePair<short, PhoneBehavior>> ObjectEntries = new List<KeyValuePair<short, PhoneBehavior>>();

		internal short PreferredNumber = -1;

		internal bool HasEntries
		{
			get
			{
				if (NumberEntries.Count <= 0)
				{
					return ObjectEntries.Count > 0;
				}
				return true;
			}
		}
	}

	private const int MaximumFourDigitNumber = 10000;

	private const float PhoneListResolveTimeoutSeconds = 60f;

	private const int MaximumPhoneListRefreshAttempts = 3;

	private static readonly FieldInfo PhoneNumberDictField = AccessTools.Field(typeof(PhoneNetworkHandler), "phoneNumberDict");

	private static readonly FieldInfo PhoneObjectDictField = AccessTools.Field(typeof(PhoneNetworkHandler), "phoneObjectDict");

	private static int phoneListGeneration;

	private static int phoneListRefreshAttempts;

	private static PhoneNetworkHandler phoneListHandler;

	private static NetworkManager phoneListNetworkManager;

	private static readonly Dictionary<ulong, short> ReadyPhoneNumbers = new Dictionary<ulong, short>();

	[HarmonyPatch("OnNetworkSpawn")]
	[HarmonyPostfix]
	private static void OnNetworkSpawnPostfix(PhoneNetworkHandler __instance)
	{
		phoneListGeneration++;
		phoneListRefreshAttempts = 0;
		phoneListHandler = __instance;
		phoneListNetworkManager = (((Object)(object)__instance == (Object)null) ? null : ((NetworkBehaviour)__instance).NetworkManager);
		ReadyPhoneNumbers.Clear();
		DelayedPhoneInitializationPatch.ResetSession();
	}

	[HarmonyPatch("CreateNewPhoneNumberServerRpc")]
	[HarmonyPrefix]
	private static bool CreateNewPhoneNumberServerRpcPrefix(ulong phoneId, ref string skinId, string charmId, string ringtoneId, PhoneNetworkHandler __instance, ref ServerRpcParams serverRpcParams)
	{
		if ((Object)(object)__instance == (Object)null)
		{
			Plugin.Logger.LogError((object)"Custom number RPC received without a PhoneNetworkHandler instance; using LethalPhones fallback.");
			return true;
		}
		if (!((NetworkBehaviour)__instance).IsServer || !RpcExecutionStage.IsExecute((NetworkBehaviour)(object)__instance))
		{
			return true;
		}
		string requestedNumber = ExtractRequestedNumber(ref skinId);
		ulong senderClientId = serverRpcParams.Receive.SenderClientId;
		if (!TryResolveRegistration(__instance, phoneId, senderClientId, out var networkObject, out var phone, out var senderPlayer, out var useLethalPhonesFallback))
		{
			return useLethalPhonesFallback;
		}
		if (PhoneNumberDictField == null || PhoneObjectDictField == null)
		{
			Plugin.Logger.LogError((object)"LethalPhones phone dictionaries were not found; using its original number assignment.");
			return true;
		}
		Dictionary<short, ulong> dictionary = PhoneNumberDictField.GetValue(__instance) as Dictionary<short, ulong>;
		Dictionary<short, PhoneBehavior> dictionary2 = PhoneObjectDictField.GetValue(__instance) as Dictionary<short, PhoneBehavior>;
		if (dictionary == null || dictionary2 == null)
		{
			Plugin.Logger.LogError((object)"LethalPhones phone dictionaries were unavailable; using its original number assignment.");
			return true;
		}
		if (TryGetConsistentRegistration(phoneId, phone, dictionary, dictionary2, out var phoneNumber))
		{
			ResynchronizeExistingRegistration(__instance, phoneId, senderClientId, skinId, charmId, ringtoneId, networkObject, phone, phoneNumber);
			return false;
		}
		DictionaryRecoverySnapshot dictionaryRecoverySnapshot = CaptureAndRemoveStaleEntries(phoneId, phone, dictionary, dictionary2);
		int value = Config.maxPhoneNumber.Value;
		if (value <= 0)
		{
			RestoreDictionaryEntries(dictionaryRecoverySnapshot, dictionary, dictionary2);
			Plugin.Logger.LogError((object)$"Rejecting phone registration because maxPhoneNumber is {value}; it must be at least 1.");
			return false;
		}
		int num = Math.Min(value, 10000);
		if (value > 10000)
		{
			Plugin.Logger.LogWarning((object)$"maxPhoneNumber is {value}; CustomNumbers restricts player phones to the four-digit range 0000-9999.");
		}
		ulong playerSteamId = senderPlayer.playerSteamId;
		if (!TryChooseNumber(requestedNumber, playerSteamId, senderClientId, phoneId, num, dictionaryRecoverySnapshot.PreferredNumber, dictionary, dictionary2, out var phoneNumber2, out var allocationSource))
		{
			RestoreDictionaryEntries(dictionaryRecoverySnapshot, dictionary, dictionary2);
			Plugin.Logger.LogError((object)$"Cannot register phone {phoneId}: all {num} allowed numbers are occupied or reserved.");
			return false;
		}
		ulong ownerClientId = networkObject.OwnerClientId;
		short phoneNumber3 = ((PhoneBehavior)phone).phoneNumber;
		string phoneSkinId = ((PhoneBehavior)phone).phoneSkinId;
		string phoneCharmId = ((PhoneBehavior)phone).phoneCharmId;
		string phoneRingtoneId = ((PhoneBehavior)phone).phoneRingtoneId;
		bool ownershipChanged = false;
		bool numberAdded = false;
		bool objectAdded = false;
		try
		{
			if (ownerClientId != senderClientId)
			{
				networkObject.ChangeOwnership(senderClientId);
				ownershipChanged = true;
			}
			((PhoneBehavior)phone).phoneSkinId = skinId;
			((PhoneBehavior)phone).phoneCharmId = charmId;
			((PhoneBehavior)phone).phoneRingtoneId = ringtoneId;
			dictionary.Add(phoneNumber2, phoneId);
			numberAdded = true;
			dictionary2.Add(phoneNumber2, (PhoneBehavior)(object)phone);
			objectAdded = true;
			((PhoneBehavior)phone).SetNewPhoneNumberClientRpc(phoneNumber2);
			__instance.RequestClientUpdates();
			Plugin.Logger.LogInfo((object)($"Registered phone {phoneId} for {senderPlayer.playerUsername} ({playerSteamId}) as {phoneNumber2:D4} " + $"from {allocationSource}; synchronized {dictionary2.Count} phone(s)."));
			return false;
		}
		catch (Exception arg)
		{
			RollBackRegistration(__instance, phoneId, phoneNumber2, ownerClientId, phoneNumber3, phoneSkinId, phoneCharmId, phoneRingtoneId, ownershipChanged, numberAdded, objectAdded, networkObject, phone, dictionary, dictionary2, dictionaryRecoverySnapshot);
			Plugin.Logger.LogError((object)$"Custom number registration failed for phone {phoneId}; the partial registration was rolled back. {arg}");
			return false;
		}
	}

	[HarmonyPatch("UpdateAllPhonesListClientRpc")]
	[HarmonyPrefix]
	private static bool UpdateAllPhonesListClientRpcPrefix(ulong[] phoneIds, PhoneNetworkHandler __instance)
	{
		if ((Object)(object)__instance == (Object)null || ((NetworkBehaviour)__instance).IsServer)
		{
			return true;
		}
		NetworkManager networkManager = ((NetworkBehaviour)__instance).NetworkManager;
		if ((Object)(object)phoneListHandler != (Object)(object)__instance || (Object)(object)phoneListNetworkManager != (Object)(object)networkManager)
		{
			phoneListGeneration++;
			phoneListRefreshAttempts = 0;
			phoneListHandler = __instance;
			phoneListNetworkManager = networkManager;
			ReadyPhoneNumbers.Clear();
			DelayedPhoneInitializationPatch.ResetSession();
		}
		int num = ++phoneListGeneration;
		ulong[] array = ((phoneIds == null) ? new ulong[0] : ((ulong[])phoneIds.Clone()));
		((MonoBehaviour)__instance).StartCoroutine(ApplyPhoneListWhenSpawned(__instance, array, num));
		Plugin.Logger.LogInfo((object)$"Deferred client phone-list generation {num} until {array.Length} referenced phone(s) are spawned.");
		return false;
	}

	private static IEnumerator ApplyPhoneListWhenSpawned(PhoneNetworkHandler handler, ulong[] phoneIds, int generation)
	{
		float deadline = Time.realtimeSinceStartup + 60f;
		List<PhoneBehavior> resolved = new List<PhoneBehavior>(phoneIds.Length);
		List<ulong> unresolved = new List<ulong>();
		while (generation == phoneListGeneration && (Object)(object)handler != (Object)null && (Object)(object)handler == (Object)(object)phoneListHandler && (Object)(object)((NetworkBehaviour)handler).NetworkManager == (Object)(object)phoneListNetworkManager)
		{
			resolved.Clear();
			unresolved.Clear();
			NetworkManager networkManager = ((NetworkBehaviour)handler).NetworkManager;
			if ((Object)(object)networkManager == (Object)null || networkManager.SpawnManager == null)
			{
				unresolved.AddRange(phoneIds);
			}
			else
			{
				foreach (ulong num in phoneIds)
				{
					if (!networkManager.SpawnManager.SpawnedObjects.TryGetValue(num, out var value) || (Object)(object)value == (Object)null || !value.IsSpawned)
					{
						unresolved.Add(num);
						continue;
					}
					PhoneBehavior component = ((Component)value).GetComponent<PhoneBehavior>();
					if ((Object)(object)component == (Object)null || !ReadyPhoneNumbers.TryGetValue(num, out var value2) || component.phoneNumber != value2)
					{
						unresolved.Add(num);
					}
					else
					{
						resolved.Add(component);
					}
				}
			}
			if (unresolved.Count == 0)
			{
				if (generation == phoneListGeneration && !((Object)(object)handler != (Object)(object)PhoneNetworkHandler.Instance))
				{
					if (PhoneNetworkHandler.allPhoneBehaviors == null)
					{
						PhoneNetworkHandler.allPhoneBehaviors = new List<PhoneBehavior>();
					}
					PhoneNetworkHandler.allPhoneBehaviors.Clear();
					PhoneNetworkHandler.allPhoneBehaviors.AddRange(resolved);
					phoneListRefreshAttempts = 0;
					PhoneNetworkHandler.phoneListUpdateEvent.Invoke();
					Plugin.Logger.LogInfo((object)($"Applied client phone-list generation {generation} with {resolved.Count} spawned phone(s); " + "clipboard and switchboard listeners were notified."));
				}
				break;
			}
			if (Time.realtimeSinceStartup >= deadline)
			{
				if ((Object)(object)handler == (Object)(object)phoneListHandler && (Object)(object)((NetworkBehaviour)handler).NetworkManager == (Object)(object)phoneListNetworkManager && phoneListRefreshAttempts < 3)
				{
					phoneListRefreshAttempts++;
					Plugin.Logger.LogWarning((object)($"Phone-list generation {generation} still has unresolved IDs after {60f:0} seconds: " + string.Format("{0}. Requesting fresh snapshot {1}/{2}.", string.Join(", ", unresolved), phoneListRefreshAttempts, 3)));
					try
					{
						handler.RequestPhoneListUpdates();
						break;
					}
					catch (Exception arg)
					{
						Plugin.Logger.LogError((object)$"Fresh phone-list request failed: {arg}");
						break;
					}
				}
				Plugin.Logger.LogError((object)($"Phone-list generation {generation} timed out without overwriting the current list. " + "Unresolved network object IDs: " + string.Join(", ", unresolved)));
				break;
			}
			yield return null;
		}
	}

	internal static void MarkPhoneReady(PhoneBehavior phone, short number)
	{
		if (!((Object)(object)phone == (Object)null) && !((Object)(object)((NetworkBehaviour)phone).NetworkManager == (Object)null) && (!((Object)(object)phoneListNetworkManager != (Object)null) || !((Object)(object)((NetworkBehaviour)phone).NetworkManager != (Object)(object)phoneListNetworkManager)))
		{
			ReadyPhoneNumbers[((NetworkBehaviour)phone).NetworkObjectId] = number;
			PlayerPhone val = (PlayerPhone)(object)((phone is PlayerPhone) ? phone : null);
			if (val != null)
			{
				DelayedPhoneInitializationPatch.Acknowledge(val);
			}
		}
	}

	private static string ExtractRequestedNumber(ref string skinId)
	{
		if (string.IsNullOrEmpty(skinId))
		{
			return null;
		}
		int num = skinId.IndexOf('|');
		if (num < 0)
		{
			return null;
		}
		string result = skinId.Substring(num + 1).Trim();
		skinId = skinId.Substring(0, num);
		return result;
	}

	private static bool TryResolveRegistration(PhoneNetworkHandler handler, ulong phoneId, ulong clientId, out NetworkObject networkObject, out PlayerPhone phone, out PlayerControllerB senderPlayer, out bool useLethalPhonesFallback)
	{
		networkObject = null;
		phone = null;
		senderPlayer = null;
		useLethalPhonesFallback = false;
		NetworkManager networkManager = ((NetworkBehaviour)handler).NetworkManager;
		if ((Object)(object)networkManager == (Object)null || networkManager.SpawnManager == null || !networkManager.SpawnManager.SpawnedObjects.TryGetValue(phoneId, out networkObject) || (Object)(object)networkObject == (Object)null || !networkObject.IsSpawned || networkObject.NetworkObjectId != phoneId)
		{
			Plugin.Logger.LogWarning((object)$"Rejected phone {phoneId}: its spawned NetworkObject is unavailable.");
			return false;
		}
		PhoneBehavior component = ((Component)networkObject).GetComponent<PhoneBehavior>();
		if ((Object)(object)component == (Object)null)
		{
			Plugin.Logger.LogWarning((object)$"Rejected phone {phoneId}: the requested NetworkObject is not a LethalPhones PhoneBehavior.");
			return false;
		}
		phone = (PlayerPhone)(object)((component is PlayerPhone) ? component : null);
		if ((Object)(object)phone == (Object)null)
		{
			useLethalPhonesFallback = clientId == 0L && networkObject.OwnerClientId == 0;
			if (useLethalPhonesFallback)
			{
				Plugin.Logger.LogInfo((object)$"Server-owned non-player phone {phoneId} is using LethalPhones registration.");
			}
			else
			{
				Plugin.Logger.LogWarning((object)$"Rejected non-player phone {phoneId}: sender {clientId} or ownership did not match the server.");
			}
			return false;
		}
		senderPlayer = FindPlayer(clientId);
		if ((Object)(object)senderPlayer == (Object)null)
		{
			Plugin.Logger.LogWarning((object)$"Rejected phone {phoneId}: sender client {clientId} has no active player object.");
			return false;
		}
		if ((Object)(object)((NetworkBehaviour)senderPlayer).NetworkObject == (Object)null || ((NetworkBehaviour)senderPlayer).NetworkObject.OwnerClientId != clientId)
		{
			Plugin.Logger.LogWarning((object)$"Rejected phone {phoneId}: sender client {clientId} does not own player {senderPlayer.playerUsername}.");
			return false;
		}
		if ((Object)(object)((Component)phone).GetComponentInParent<PlayerControllerB>() != (Object)(object)senderPlayer || !((Component)phone).transform.IsChildOf(((Component)senderPlayer).transform))
		{
			Plugin.Logger.LogWarning((object)$"Rejected phone {phoneId}: it is not parented to sender {senderPlayer.playerUsername} ({clientId}).");
			return false;
		}
		return true;
	}

	private static PlayerControllerB FindPlayer(ulong clientId)
	{
		if ((Object)(object)StartOfRound.Instance == (Object)null || StartOfRound.Instance.allPlayerScripts == null)
		{
			return null;
		}
		PlayerControllerB[] allPlayerScripts = StartOfRound.Instance.allPlayerScripts;
		foreach (PlayerControllerB val in allPlayerScripts)
		{
			if ((Object)(object)val != (Object)null && val.actualClientId == clientId)
			{
				return val;
			}
		}
		return null;
	}

	private static bool TryGetConsistentRegistration(ulong phoneId, PlayerPhone phone, Dictionary<short, ulong> phoneNumberDict, Dictionary<short, PhoneBehavior> phoneObjectDict, out short phoneNumber)
	{
		int num = 0;
		int num2 = 0;
		short num3 = -1;
		short num4 = -1;
		foreach (KeyValuePair<short, ulong> item in phoneNumberDict)
		{
			if (item.Value == phoneId)
			{
				num++;
				num3 = item.Key;
			}
		}
		foreach (KeyValuePair<short, PhoneBehavior> item2 in phoneObjectDict)
		{
			if ((Object)(object)item2.Value == (Object)(object)phone)
			{
				num2++;
				num4 = item2.Key;
			}
		}
		phoneNumber = num3;
		if (num == 1 && num2 == 1 && num3 == num4 && phoneNumberDict.TryGetValue(num3, out var value) && value == phoneId && phoneObjectDict.TryGetValue(num3, out var value2))
		{
			return (Object)(object)value2 == (Object)(object)phone;
		}
		return false;
	}

	private static void ResynchronizeExistingRegistration(PhoneNetworkHandler handler, ulong phoneId, ulong clientId, string skinId, string charmId, string ringtoneId, NetworkObject networkObject, PlayerPhone phone, short phoneNumber)
	{
		ulong ownerClientId = networkObject.OwnerClientId;
		string phoneSkinId = ((PhoneBehavior)phone).phoneSkinId;
		string phoneCharmId = ((PhoneBehavior)phone).phoneCharmId;
		string phoneRingtoneId = ((PhoneBehavior)phone).phoneRingtoneId;
		bool flag = false;
		try
		{
			if (ownerClientId != clientId)
			{
				networkObject.ChangeOwnership(clientId);
				flag = true;
			}
			((PhoneBehavior)phone).phoneSkinId = skinId;
			((PhoneBehavior)phone).phoneCharmId = charmId;
			((PhoneBehavior)phone).phoneRingtoneId = ringtoneId;
			((PhoneBehavior)phone).SetNewPhoneNumberClientRpc(phoneNumber);
			handler.RequestClientUpdates();
			Plugin.Logger.LogInfo((object)($"Duplicate registration for phone {phoneId} is already complete as {phoneNumber:D4}; " + "ownership and clients were synchronized idempotently."));
		}
		catch (Exception arg)
		{
			((PhoneBehavior)phone).phoneSkinId = phoneSkinId;
			((PhoneBehavior)phone).phoneCharmId = phoneCharmId;
			((PhoneBehavior)phone).phoneRingtoneId = phoneRingtoneId;
			try
			{
				if (flag && networkObject.IsSpawned)
				{
					networkObject.ChangeOwnership(ownerClientId);
				}
				((PhoneBehavior)phone).SetNewPhoneNumberClientRpc(phoneNumber);
				handler.RequestClientUpdates();
			}
			catch (Exception arg2)
			{
				Plugin.Logger.LogError((object)$"Duplicate phone {phoneId} rollback also failed: {arg2}");
			}
			Plugin.Logger.LogError((object)$"Failed to resynchronize duplicate phone {phoneId}; ownership and cosmetics were rolled back. {arg}");
		}
	}

	private static DictionaryRecoverySnapshot CaptureAndRemoveStaleEntries(ulong phoneId, PlayerPhone phone, Dictionary<short, ulong> phoneNumberDict, Dictionary<short, PhoneBehavior> phoneObjectDict)
	{
		DictionaryRecoverySnapshot dictionaryRecoverySnapshot = new DictionaryRecoverySnapshot();
		foreach (KeyValuePair<short, ulong> item in phoneNumberDict)
		{
			if (item.Value == phoneId)
			{
				dictionaryRecoverySnapshot.NumberEntries.Add(item);
			}
		}
		foreach (KeyValuePair<short, PhoneBehavior> item2 in phoneObjectDict)
		{
			if ((Object)(object)item2.Value == (Object)(object)phone)
			{
				dictionaryRecoverySnapshot.ObjectEntries.Add(item2);
			}
		}
		foreach (KeyValuePair<short, ulong> numberEntry in dictionaryRecoverySnapshot.NumberEntries)
		{
			foreach (KeyValuePair<short, PhoneBehavior> objectEntry in dictionaryRecoverySnapshot.ObjectEntries)
			{
				if (numberEntry.Key == objectEntry.Key)
				{
					dictionaryRecoverySnapshot.PreferredNumber = numberEntry.Key;
					break;
				}
			}
			if (dictionaryRecoverySnapshot.PreferredNumber >= 0)
			{
				break;
			}
		}
		if (dictionaryRecoverySnapshot.PreferredNumber < 0 && dictionaryRecoverySnapshot.NumberEntries.Count > 0)
		{
			dictionaryRecoverySnapshot.PreferredNumber = dictionaryRecoverySnapshot.NumberEntries[0].Key;
		}
		else if (dictionaryRecoverySnapshot.PreferredNumber < 0 && dictionaryRecoverySnapshot.ObjectEntries.Count > 0)
		{
			dictionaryRecoverySnapshot.PreferredNumber = dictionaryRecoverySnapshot.ObjectEntries[0].Key;
		}
		foreach (KeyValuePair<short, ulong> numberEntry2 in dictionaryRecoverySnapshot.NumberEntries)
		{
			if (phoneNumberDict.TryGetValue(numberEntry2.Key, out var value) && value == phoneId)
			{
				phoneNumberDict.Remove(numberEntry2.Key);
			}
		}
		foreach (KeyValuePair<short, PhoneBehavior> objectEntry2 in dictionaryRecoverySnapshot.ObjectEntries)
		{
			if (phoneObjectDict.TryGetValue(objectEntry2.Key, out var value2) && (Object)(object)value2 == (Object)(object)phone)
			{
				phoneObjectDict.Remove(objectEntry2.Key);
			}
		}
		if (dictionaryRecoverySnapshot.HasEntries)
		{
			Plugin.Logger.LogWarning((object)($"Repairing stale registration state for phone {phoneId}; removed " + $"{dictionaryRecoverySnapshot.NumberEntries.Count} number entry/entries and {dictionaryRecoverySnapshot.ObjectEntries.Count} object entry/entries transactionally."));
		}
		return dictionaryRecoverySnapshot;
	}

	private static void RestoreDictionaryEntries(DictionaryRecoverySnapshot snapshot, Dictionary<short, ulong> phoneNumberDict, Dictionary<short, PhoneBehavior> phoneObjectDict)
	{
		if (snapshot == null || !snapshot.HasEntries)
		{
			return;
		}
		foreach (KeyValuePair<short, ulong> numberEntry in snapshot.NumberEntries)
		{
			if (!phoneNumberDict.ContainsKey(numberEntry.Key))
			{
				phoneNumberDict.Add(numberEntry.Key, numberEntry.Value);
			}
		}
		foreach (KeyValuePair<short, PhoneBehavior> objectEntry in snapshot.ObjectEntries)
		{
			if (!phoneObjectDict.ContainsKey(objectEntry.Key))
			{
				phoneObjectDict.Add(objectEntry.Key, objectEntry.Value);
			}
		}
	}

	private static bool TryChooseNumber(string requestedNumber, ulong steamId, ulong clientId, ulong phoneId, int numberSpace, short recoveredNumber, Dictionary<short, ulong> phoneNumberDict, Dictionary<short, PhoneBehavior> phoneObjectDict, out short phoneNumber, out string allocationSource)
	{
		phoneNumber = -1;
		allocationSource = null;
		if (recoveredNumber >= 0 && recoveredNumber < numberSpace && IsAvailable(recoveredNumber, phoneNumberDict, phoneObjectDict))
		{
			phoneNumber = recoveredNumber;
			allocationSource = "recovered registration";
			return true;
		}
		if (!string.IsNullOrEmpty(requestedNumber))
		{
			if (TryParseFourDigitNumber(requestedNumber, numberSpace, out var number) && IsAvailable(number, phoneNumberDict, phoneObjectDict))
			{
				phoneNumber = number;
				allocationSource = "requested number";
				return true;
			}
			Plugin.Logger.LogWarning((object)("Requested number '" + requestedNumber + "' is invalid, reserved, out of range, or already occupied; using a deterministic fallback."));
		}
		if (steamId != 0L && ConfigManager.SteamIdToNumber.TryGetValue(steamId, out var value))
		{
			if (value >= 0 && value < numberSpace && IsAvailable(value, phoneNumberDict, phoneObjectDict))
			{
				phoneNumber = value;
				allocationSource = "Steam mapping";
				return true;
			}
			Plugin.Logger.LogWarning((object)$"Mapped number {value:D4} for Steam ID {steamId} is unavailable or out of range; using its deterministic fallback.");
		}
		if (TryFindAvailableNumber((int)(((steamId != 0L) ? steamId : MixFallbackSeed(clientId, phoneId)) % (ulong)numberSpace), numberSpace, phoneNumberDict, phoneObjectDict, out phoneNumber))
		{
			allocationSource = ((steamId != 0L) ? "Steam ID hash" : "client/object hash");
			return true;
		}
		return false;
	}

	private static bool TryParseFourDigitNumber(string value, int numberSpace, out short number)
	{
		number = -1;
		if (value.Length != 4)
		{
			return false;
		}
		for (int i = 0; i < value.Length; i++)
		{
			if (value[i] < '0' || value[i] > '9')
			{
				return false;
			}
		}
		if (short.TryParse(value, out number) && number >= 0)
		{
			return number < numberSpace;
		}
		return false;
	}

	private static bool TryFindAvailableNumber(int start, int numberSpace, Dictionary<short, ulong> phoneNumberDict, Dictionary<short, PhoneBehavior> phoneObjectDict, out short phoneNumber)
	{
		for (int i = 0; i < numberSpace; i++)
		{
			short num = (short)((start + i) % numberSpace);
			if (IsAvailable(num, phoneNumberDict, phoneObjectDict))
			{
				phoneNumber = num;
				return true;
			}
		}
		phoneNumber = -1;
		return false;
	}

	private static bool IsAvailable(short availableNumber, Dictionary<short, ulong> phoneNumberDict, Dictionary<short, PhoneBehavior> phoneObjectDict)
	{
		short result;
		bool flag = short.TryParse(Config.switchboardNumber.Value, out result) && availableNumber == result;
		if (!phoneNumberDict.ContainsKey(availableNumber) && !phoneObjectDict.ContainsKey(availableNumber))
		{
			return !flag;
		}
		return false;
	}

	private static ulong MixFallbackSeed(ulong clientId, ulong phoneId)
	{
		long num = (long)clientId ^ ((long)phoneId + -7046029254386353131L + (long)(clientId << 6) + (long)(clientId >> 2));
		long num2 = (num ^ (num >>> 30)) * -4658895280553007687L;
		long num3 = (num2 ^ (num2 >>> 27)) * -7723592293110705685L;
		return (ulong)(num3 ^ (num3 >>> 31));
	}

	private static bool TryFindNumberByPhoneId(Dictionary<short, ulong> phoneNumberDict, ulong phoneId, out short phoneNumber)
	{
		foreach (KeyValuePair<short, ulong> item in phoneNumberDict)
		{
			if (item.Value == phoneId)
			{
				phoneNumber = item.Key;
				return true;
			}
		}
		phoneNumber = -1;
		return false;
	}

	private static bool TryFindNumberByPhone(Dictionary<short, PhoneBehavior> phoneObjectDict, PhoneBehavior phone, out short phoneNumber)
	{
		foreach (KeyValuePair<short, PhoneBehavior> item in phoneObjectDict)
		{
			if ((Object)(object)item.Value == (Object)(object)phone)
			{
				phoneNumber = item.Key;
				return true;
			}
		}
		phoneNumber = -1;
		return false;
	}

	private static void RollBackRegistration(PhoneNetworkHandler handler, ulong phoneId, short phoneNumber, ulong originalOwner, short originalNumber, string originalSkin, string originalCharm, string originalRingtone, bool ownershipChanged, bool numberAdded, bool objectAdded, NetworkObject networkObject, PlayerPhone phone, Dictionary<short, ulong> phoneNumberDict, Dictionary<short, PhoneBehavior> phoneObjectDict, DictionaryRecoverySnapshot recoverySnapshot)
	{
		if (objectAdded && phoneObjectDict.TryGetValue(phoneNumber, out var value) && (Object)(object)value == (Object)(object)phone)
		{
			phoneObjectDict.Remove(phoneNumber);
		}
		if (numberAdded && phoneNumberDict.TryGetValue(phoneNumber, out var value2) && value2 == phoneId)
		{
			phoneNumberDict.Remove(phoneNumber);
		}
		((PhoneBehavior)phone).phoneNumber = originalNumber;
		((PhoneBehavior)phone).phoneSkinId = originalSkin;
		((PhoneBehavior)phone).phoneCharmId = originalCharm;
		((PhoneBehavior)phone).phoneRingtoneId = originalRingtone;
		RestoreDictionaryEntries(recoverySnapshot, phoneNumberDict, phoneObjectDict);
		try
		{
			if (ownershipChanged && (Object)(object)networkObject != (Object)null && networkObject.IsSpawned)
			{
				networkObject.ChangeOwnership(originalOwner);
			}
			if ((Object)(object)handler != (Object)null && ((NetworkBehaviour)handler).IsServer && (Object)(object)phone != (Object)null && ((NetworkBehaviour)phone).IsSpawned)
			{
				((PhoneBehavior)phone).SetNewPhoneNumberClientRpc(originalNumber);
				handler.RequestClientUpdates();
			}
		}
		catch (Exception arg)
		{
			Plugin.Logger.LogError((object)$"Phone {phoneId} rollback synchronization also failed: {arg}");
		}
	}
}
[BepInPlugin("com.ashby.lethalphonescustomnumbers", "LethalPhonesCustomNumbers", "1.2.1")]
[BepInDependency(/*Could not decode attribute arguments.*/)]
public class Plugin : BaseUnityPlugin
{
	public const string PluginGuid = "com.ashby.lethalphonescustomnumbers";

	public const string PluginName = "LethalPhonesCustomNumbers";

	public const string PluginVersion = "1.2.1";

	private const string LegacyPluginGuid = "com.ashby.lethalphonespermanumber";

	private Harmony _harmony;

	public static Plugin Instance { get; private set; }

	public static ManualLogSource Logger { get; private set; }

	public Dictionary<ulong, short> CustomNumbersMap { get; private set; } = new Dictionary<ulong, short>();

	private void Awake()
	{
		//IL_0035: Unknown result type (might be due to invalid IL or missing references)
		//IL_003f: Expected O, but got Unknown
		Instance = this;
		Logger = Logger.CreateLogSource("LethalPhonesCustomNumbers");
		Logger.LogInfo((object)"Plugin LethalPhonesCustomNumbers is loaded!");
		ConfigManager.Init(((BaseUnityPlugin)this).Config);
		_harmony = new Harmony("com.ashby.lethalphonescustomnumbers");
		_harmony.PatchAll();
		DelayedPhoneInitializationPatch.Install(_harmony);
		Logger.LogInfo((object)"Patches applied successfully.");
	}

	private void Start()
	{
		if (Chainloader.PluginInfos.ContainsKey("com.ashby.lethalphonespermanumber"))
		{
			Harmony.UnpatchID("com.ashby.lethalphonespermanumber");
			Logger.LogWarning((object)"Detected legacy LethalPhonesPermaNumber. Its overlapping Harmony patches were disabled; remove that legacy plugin from the modpack.");
		}
	}
}
internal static class RpcExecutionStage
{
	private static readonly FieldInfo ExecutionStageField = AccessTools.Field(typeof(NetworkBehaviour), "__rpc_exec_stage");

	private static bool missingFieldLogged;

	internal static bool IsExecute(NetworkBehaviour behaviour)
	{
		if ((Object)(object)behaviour == (Object)null || ExecutionStageField == null)
		{
			if (!missingFieldLogged)
			{
				missingFieldLogged = true;
				Plugin.Logger.LogError((object)"Unity Netcode RPC execution stage could not be resolved; CustomNumbers will not bypass generated RPC wrappers.");
			}
			return false;
		}
		object value = ExecutionStageField.GetValue(behaviour);
		if (value != null)
		{
			return string.Equals(value.ToString(), "Execute", StringComparison.Ordinal);
		}
		return false;
	}
}
[HarmonyPatch(typeof(PhoneBehavior), "SetNewPhoneNumberClientRpc")]
internal static class PhoneNumberReadyPatch
{
	[HarmonyPrefix]
	private static void Prefix(PhoneBehavior __instance, out bool __state)
	{
		__state = RpcExecutionStage.IsExecute((NetworkBehaviour)(object)__instance);
	}

	[HarmonyPostfix]
	private static void Postfix(PhoneBehavior __instance, short number, bool __state)
	{
		if (__state)
		{
			PhoneNetworkHandlerPatch.MarkPhoneReady(__instance, number);
		}
	}
}
[HarmonyPatch(typeof(PhoneBehavior), "PropogateInformationClientRpc")]
internal static class PhoneInformationReadyPatch
{
	[HarmonyPrefix]
	private static void Prefix(PhoneBehavior __instance, out bool __state)
	{
		__state = RpcExecutionStage.IsExecute((NetworkBehaviour)(object)__instance);
	}

	[HarmonyPostfix]
	private static void Postfix(PhoneBehavior __instance, short number, bool __state)
	{
		if (__state)
		{
			PhoneNetworkHandlerPatch.MarkPhoneReady(__instance, number);
		}
	}
}
public static class PluginInfo
{
	public const string PLUGIN_GUID = "LethalPhonesCustomNumbers";

	public const string PLUGIN_NAME = "LethalPhonesCustomNumbers";

	public const string PLUGIN_VERSION = "1.2.1";
}