Decompiled source of Aurora Season 2 v2.0.9

BepInEx/plugins/AuroraQuickConnect.dll

Decompiled 14 hours ago
using System;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Net;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Runtime.Versioning;
using BepInEx;
using BepInEx.Configuration;
using BepInEx.Logging;
using HarmonyLib;
using Microsoft.CodeAnalysis;
using UnityEngine;
using UnityEngine.Events;
using UnityEngine.UI;

[assembly: CompilationRelaxations(8)]
[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
[assembly: Debuggable(DebuggableAttribute.DebuggingModes.Default | DebuggableAttribute.DebuggingModes.DisableOptimizations | DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints | DebuggableAttribute.DebuggingModes.EnableEditAndContinue)]
[assembly: AssemblyTitle("AuroraQuickConnect")]
[assembly: AssemblyDescription("")]
[assembly: AssemblyConfiguration("")]
[assembly: AssemblyCompany("")]
[assembly: AssemblyProduct("AuroraQuickConnect")]
[assembly: AssemblyCopyright("Copyright ©  2026")]
[assembly: AssemblyTrademark("")]
[assembly: ComVisible(false)]
[assembly: Guid("b320a5f6-47c1-4fb0-a279-c04835cbb413")]
[assembly: AssemblyFileVersion("1.0.0.0")]
[assembly: TargetFramework(".NETFramework,Version=v4.7.2", FrameworkDisplayName = ".NET Framework 4.7.2")]
[assembly: AssemblyVersion("1.0.0.0")]
namespace Microsoft.CodeAnalysis
{
	[CompilerGenerated]
	[Microsoft.CodeAnalysis.Embedded]
	internal sealed class EmbeddedAttribute : Attribute
	{
	}
}
namespace System.Runtime.CompilerServices
{
	[CompilerGenerated]
	[Microsoft.CodeAnalysis.Embedded]
	[AttributeUsage(AttributeTargets.Class | AttributeTargets.Property | AttributeTargets.Field | AttributeTargets.Event | AttributeTargets.Parameter | AttributeTargets.ReturnValue | AttributeTargets.GenericParameter, AllowMultiple = false, Inherited = false)]
	internal sealed class NullableAttribute : Attribute
	{
		public readonly byte[] NullableFlags;

		public NullableAttribute(byte P_0)
		{
			NullableFlags = new byte[1] { P_0 };
		}

		public NullableAttribute(byte[] P_0)
		{
			NullableFlags = P_0;
		}
	}
	[CompilerGenerated]
	[Microsoft.CodeAnalysis.Embedded]
	[AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct | AttributeTargets.Method | AttributeTargets.Interface | AttributeTargets.Delegate, AllowMultiple = false, Inherited = false)]
	internal sealed class NullableContextAttribute : Attribute
	{
		public readonly byte Flag;

		public NullableContextAttribute(byte P_0)
		{
			Flag = P_0;
		}
	}
}
namespace AuroraQuickConnect
{
	[BepInPlugin("com.aurora.quickconnect", "AuroraQuickConnect", "1.0.0")]
	public sealed class Class1 : BaseUnityPlugin
	{
		private static class Joiner
		{
			public static bool TryJoin(FejdStartup fs, string host, int port, string password)
			{
				string text = $"{host}:{port}";
				Ui.TryFillJoinByIp(fs, text, password);
				if (SmartInvoke.TryInvokeFirstMatch(fs, new string[8] { "OnJoinIPConnect", "OnJoinIpConnect", "JoinServerIP", "JoinServerIp", "JoinServer", "ConnectToServer", "ConnectToIP", "ConnectToIp" }, SmartInvoke.Candidates(new object[0], new object[1] { text }, new object[2] { text, password }, new object[2] { host, port }, new object[3] { host, port, password })))
				{
					return true;
				}
				if (TryJoinViaMatchmaking(text))
				{
					return true;
				}
				if (TryJoinViaZNet(host, port, text))
				{
					return true;
				}
				return false;
			}

			private static bool TryJoinViaMatchmaking(string hostAndPort)
			{
				try
				{
					Type type = AccessTools.TypeByName("ZSteamMatchmaking");
					if (type == null)
					{
						return false;
					}
					object obj = AccessTools.Property(type, "instance")?.GetValue(null) ?? AccessTools.Field(type, "instance")?.GetValue(null);
					if (obj == null)
					{
						return false;
					}
					if (SmartInvoke.TryInvokeFirstMatch(obj, new string[6] { "QueueServerJoin", "JoinServer", "JoinServerByIp", "JoinServerByIP", "JoinIP", "JoinIp" }, SmartInvoke.Candidates(new object[1] { hostAndPort })))
					{
						Log.LogInfo((object)"Aurora QuickConnect: join triggered via ZSteamMatchmaking.");
						return true;
					}
				}
				catch (Exception ex)
				{
					Log.LogDebug((object)("TryJoinViaMatchmaking failed: " + ex.GetType().Name + ": " + ex.Message));
				}
				return false;
			}

			private static bool TryJoinViaZNet(string host, int port, string hostAndPort)
			{
				try
				{
					Type type = AccessTools.TypeByName("ZNet");
					if (type == null)
					{
						return false;
					}
					object obj = AccessTools.Property(type, "instance")?.GetValue(null) ?? AccessTools.Field(type, "instance")?.GetValue(null);
					if (obj == null)
					{
						return false;
					}
					IPEndPoint iPEndPoint = new IPEndPoint(IPAddress.Parse(host), port);
					if (SmartInvoke.TryInvokeFirstMatch(obj, new string[6] { "Connect", "StartClient", "StartClientConnect", "ConnectToServer", "ConnectToIP", "ConnectToIp" }, SmartInvoke.Candidates(new object[2] { host, port }, new object[1] { hostAndPort }, new object[1] { iPEndPoint })))
					{
						Log.LogInfo((object)"Aurora QuickConnect: join triggered via ZNet.");
						return true;
					}
				}
				catch (Exception ex)
				{
					Log.LogDebug((object)("TryJoinViaZNet failed: " + ex.GetType().Name + ": " + ex.Message));
				}
				return false;
			}

			public static void DumpJoinMethods(FejdStartup fs)
			{
				try
				{
					DumpTypeMethods("FejdStartup", ((object)fs).GetType(), "join");
					DumpTypeMethods("FejdStartup", ((object)fs).GetType(), "connect");
					Type type = AccessTools.TypeByName("ZSteamMatchmaking");
					if (type != null)
					{
						DumpTypeMethods("ZSteamMatchmaking", type, "join");
					}
					Type type2 = AccessTools.TypeByName("ZNet");
					if (type2 != null)
					{
						DumpTypeMethods("ZNet", type2, "connect");
					}
				}
				catch (Exception ex)
				{
					Log.LogWarning((object)("DumpJoinMethods failed: " + ex.GetType().Name + ": " + ex.Message));
				}
			}

			private static void DumpTypeMethods(string label, Type t, string filter)
			{
				string filter2 = filter;
				string[] array = (from m in (from m in t.GetMethods(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic)
						where m.Name.IndexOf(filter2, StringComparison.OrdinalIgnoreCase) >= 0
						orderby m.Name
						select m).Take(60)
					select m.Name + "(" + string.Join(", ", from p in m.GetParameters()
						select p.ParameterType.Name) + ")").ToArray();
				Log.LogInfo((object)("---- " + label + " methods matching '" + filter2 + "' (up to 60) ----"));
				string[] array2 = array;
				foreach (string text in array2)
				{
					Log.LogInfo((object)text);
				}
				Log.LogInfo((object)"--------------------------------------------------------");
			}
		}

		private static class SmartInvoke
		{
			public static object[][] Candidates(params object[][] candidates)
			{
				return candidates;
			}

			public static bool TryInvokeFirstMatch(object instance, string[] methodNames, object[][] candidates)
			{
				Type type = instance.GetType();
				MethodInfo[] methods = type.GetMethods(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
				foreach (string name in methodNames)
				{
					foreach (MethodInfo item in methods.Where((MethodInfo x) => x.Name == name))
					{
						foreach (object[] candidate in candidates)
						{
							if (!TryBuildArgs(item, candidate, out object[] args))
							{
								continue;
							}
							try
							{
								item.Invoke(instance, args);
								Log.LogInfo((object)("Invoked " + type.Name + "." + item.Name + "(" + string.Join(", ", from p in item.GetParameters()
									select p.ParameterType.Name) + ")"));
								return true;
							}
							catch (Exception ex)
							{
								Log.LogDebug((object)("Invoke failed " + type.Name + "." + item.Name + ": " + ex.GetType().Name + ": " + ex.Message));
							}
						}
					}
				}
				return false;
			}

			private static bool TryBuildArgs(MethodInfo method, object[] candidate, out object[] args)
			{
				args = Array.Empty<object>();
				ParameterInfo[] parameters = method.GetParameters();
				if (parameters.Length != candidate.Length)
				{
					return false;
				}
				object[] array = new object[parameters.Length];
				for (int i = 0; i < parameters.Length; i++)
				{
					Type parameterType = parameters[i].ParameterType;
					object obj = candidate[i];
					if (obj == null)
					{
						if (parameterType.IsValueType)
						{
							return false;
						}
						array[i] = null;
						continue;
					}
					Type type = obj.GetType();
					if (parameterType.IsAssignableFrom(type))
					{
						array[i] = obj;
						continue;
					}
					if (parameterType == typeof(ushort) && obj is int num && num >= 0 && num <= 65535)
					{
						array[i] = (ushort)num;
						continue;
					}
					if (parameterType == typeof(int) && obj is ushort num2)
					{
						array[i] = (int)num2;
						continue;
					}
					if (parameterType == typeof(string))
					{
						array[i] = obj.ToString() ?? "";
						continue;
					}
					return false;
				}
				args = array;
				return true;
			}
		}

		private static class Ui
		{
			private const string AuroraButtonName = "AuroraQuickConnect__PlayAuroraButton";

			public static void TryInjectPlayAuroraButtonAboveStart(FejdStartup fs, Action onClick)
			{
				//IL_015f: Unknown result type (might be due to invalid IL or missing references)
				//IL_0169: Expected O, but got Unknown
				//IL_0178: Unknown result type (might be due to invalid IL or missing references)
				//IL_0182: Expected O, but got Unknown
				if ((Object)(object)fs == (Object)null || ((Component)fs).GetComponentsInChildren<Transform>(true).Any((Transform t) => ((Object)t).name == "AuroraQuickConnect__PlayAuroraButton"))
				{
					return;
				}
				Button[] componentsInChildren = ((Component)fs).GetComponentsInChildren<Button>(true);
				if (componentsInChildren == null || componentsInChildren.Length == 0)
				{
					Log.LogWarning((object)"No UI Buttons found under FejdStartup. Can't inject Play Aurora.");
					return;
				}
				Button val = ((IEnumerable<Button>)componentsInChildren).FirstOrDefault((Func<Button, bool>)((Button b) => LabelContains(((Component)b).gameObject, "Start Game"))) ?? ((IEnumerable<Button>)componentsInChildren).FirstOrDefault((Func<Button, bool>)((Button b) => LabelContains(((Component)b).gameObject, "Start"))) ?? ((IEnumerable<Button>)componentsInChildren).FirstOrDefault((Func<Button, bool>)((Button b) => HasAnyLabel(((Component)b).gameObject))) ?? componentsInChildren[0];
				Transform parent = ((Component)val).transform.parent;
				GameObject val2 = Object.Instantiate<GameObject>(((Component)val).gameObject, parent);
				((Object)val2).name = "AuroraQuickConnect__PlayAuroraButton";
				val2.SetActive(true);
				val2.transform.SetSiblingIndex(0);
				SetButtonLabel(val2, _buttonText);
				Button component = val2.GetComponent<Button>();
				if ((Object)(object)component == (Object)null)
				{
					Object.Destroy((Object)(object)val2);
					return;
				}
				component.onClick = new ButtonClickedEvent();
				((UnityEvent)component.onClick).AddListener(new UnityAction(onClick.Invoke));
				Log.LogInfo((object)("Injected '" + _buttonText + "' button (template='" + ((Object)val).name + "')."));
			}

			public static bool TryInvokeVoidBestEffort(object instance, string methodName)
			{
				string methodName2 = methodName;
				try
				{
					Type type = instance.GetType();
					MethodInfo[] array = (from m in type.GetMethods(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic)
						where m.Name == methodName2 && m.ReturnType == typeof(void)
						select m).ToArray();
					if (array.Length == 0)
					{
						return false;
					}
					MethodInfo methodInfo = array.FirstOrDefault((MethodInfo m) => m.GetParameters().Length == 0);
					if (methodInfo != null)
					{
						methodInfo.Invoke(instance, Array.Empty<object>());
						return true;
					}
					MethodInfo methodInfo2 = array.FirstOrDefault((MethodInfo m) => m.GetParameters().Length == 1);
					if (methodInfo2 != null)
					{
						Type parameterType = methodInfo2.GetParameters()[0].ParameterType;
						object obj = null;
						if (parameterType == typeof(bool))
						{
							obj = false;
						}
						else if (parameterType == typeof(int))
						{
							obj = 0;
						}
						else if (parameterType == typeof(float))
						{
							obj = 0f;
						}
						else
						{
							if (!(parameterType == typeof(string)))
							{
								return false;
							}
							obj = "";
						}
						methodInfo2.Invoke(instance, new object[1] { obj });
						return true;
					}
				}
				catch
				{
				}
				return false;
			}

			public static bool ClickButtonByLabel(FejdStartup fs, string[] include, string[] exclude)
			{
				Button val = FindButtonByLabel(fs, include, exclude);
				if ((Object)(object)val == (Object)null)
				{
					return false;
				}
				ButtonClickedEvent onClick = val.onClick;
				if (onClick != null)
				{
					((UnityEvent)onClick).Invoke();
				}
				return true;
			}

			private static Button? FindButtonByLabel(FejdStartup fs, string[] include, string[] exclude)
			{
				if ((Object)(object)fs == (Object)null)
				{
					return null;
				}
				Button[] componentsInChildren = ((Component)fs).GetComponentsInChildren<Button>(true);
				if (componentsInChildren == null)
				{
					return null;
				}
				Button[] array = componentsInChildren;
				foreach (Button val in array)
				{
					if ((Object)(object)val == (Object)null || !((Component)val).gameObject.activeInHierarchy)
					{
						continue;
					}
					string anyLabelText = GetAnyLabelText(((Component)val).gameObject);
					if (!string.IsNullOrEmpty(anyLabelText))
					{
						string lower = anyLabelText.ToLowerInvariant();
						if (!exclude.Any((string e) => lower.Contains(e.ToLowerInvariant())) && include.All((string i) => lower.Contains(i.ToLowerInvariant())))
						{
							return val;
						}
					}
				}
				return null;
			}

			public static void TryFillJoinByIp(FejdStartup fs, string hostAndPort, string password)
			{
				TrySetInputByFieldName(fs, new string[7] { "m_joinIPAddress", "m_joinIpAddress", "m_joinIP", "m_joinIp", "m_serverIP", "m_serverAddress", "m_serverHost" }, hostAndPort);
				if (!string.IsNullOrEmpty(password))
				{
					TrySetInputByFieldName(fs, new string[4] { "m_joinPassword", "m_password", "m_serverPassword", "m_joinServerPassword" }, password);
				}
			}

			private static void TrySetInputByFieldName(object instance, string[] fieldNames, string value)
			{
				foreach (string text in fieldNames)
				{
					try
					{
						FieldInfo fieldInfo = AccessTools.Field(instance.GetType(), text);
						if (fieldInfo == null)
						{
							continue;
						}
						object value2 = fieldInfo.GetValue(instance);
						if (value2 != null)
						{
							InputField val = (InputField)((value2 is InputField) ? value2 : null);
							if (val != null)
							{
								val.text = value;
								break;
							}
							Type type = value2.GetType();
							if (type.Name == "TMP_InputField")
							{
								type.GetProperty("text", BindingFlags.Instance | BindingFlags.Public)?.SetValue(value2, value);
								break;
							}
						}
					}
					catch
					{
					}
				}
			}

			private static bool LabelContains(GameObject go, string text)
			{
				string anyLabelText = GetAnyLabelText(go);
				return anyLabelText != null && anyLabelText.IndexOf(text, StringComparison.OrdinalIgnoreCase) >= 0;
			}

			private static bool HasAnyLabel(GameObject go)
			{
				return !string.IsNullOrEmpty(GetAnyLabelText(go));
			}

			private static string? GetAnyLabelText(GameObject go)
			{
				if ((Object)(object)go == (Object)null)
				{
					return null;
				}
				try
				{
					Component val = ((IEnumerable<Component>)go.GetComponentsInChildren<Component>(true)).FirstOrDefault((Func<Component, bool>)((Component c) => (Object)(object)c != (Object)null && (((object)c).GetType().Name == "TMP_Text" || ((object)c).GetType().Name == "TextMeshProUGUI")));
					if ((Object)(object)val != (Object)null)
					{
						string text = ((object)val).GetType().GetProperty("text", BindingFlags.Instance | BindingFlags.Public)?.GetValue(val) as string;
						if (!string.IsNullOrEmpty(text))
						{
							return text;
						}
					}
				}
				catch
				{
				}
				Text componentInChildren = go.GetComponentInChildren<Text>(true);
				if ((Object)(object)componentInChildren != (Object)null && !string.IsNullOrEmpty(componentInChildren.text))
				{
					return componentInChildren.text;
				}
				return null;
			}

			private static void SetButtonLabel(GameObject buttonGo, string label)
			{
				if ((Object)(object)buttonGo == (Object)null)
				{
					return;
				}
				try
				{
					Component val = ((IEnumerable<Component>)buttonGo.GetComponentsInChildren<Component>(true)).FirstOrDefault((Func<Component, bool>)((Component c) => (Object)(object)c != (Object)null && (((object)c).GetType().Name == "TMP_Text" || ((object)c).GetType().Name == "TextMeshProUGUI")));
					if ((Object)(object)val != (Object)null)
					{
						((object)val).GetType().GetProperty("text", BindingFlags.Instance | BindingFlags.Public)?.SetValue(val, label);
						return;
					}
				}
				catch
				{
				}
				Text componentInChildren = buttonGo.GetComponentInChildren<Text>(true);
				if ((Object)(object)componentInChildren != (Object)null)
				{
					componentInChildren.text = label;
				}
			}
		}

		[CompilerGenerated]
		private sealed class <ConnectToServerRoutine>d__32 : IEnumerator<object>, IDisposable, IEnumerator
		{
			private int <>1__state;

			private object <>2__current;

			public FejdStartup fs;

			private string <hostAndPort>5__1;

			private int <attempt>5__2;

			object IEnumerator<object>.Current
			{
				[DebuggerHidden]
				get
				{
					return <>2__current;
				}
			}

			object IEnumerator.Current
			{
				[DebuggerHidden]
				get
				{
					return <>2__current;
				}
			}

			[DebuggerHidden]
			public <ConnectToServerRoutine>d__32(int <>1__state)
			{
				this.<>1__state = <>1__state;
			}

			[DebuggerHidden]
			void IDisposable.Dispose()
			{
				<hostAndPort>5__1 = null;
				<>1__state = -2;
			}

			private bool MoveNext()
			{
				//IL_0061: Unknown result type (might be due to invalid IL or missing references)
				//IL_006b: Expected O, but got Unknown
				//IL_0151: Unknown result type (might be due to invalid IL or missing references)
				//IL_015b: Expected O, but got Unknown
				switch (<>1__state)
				{
				default:
					return false;
				case 0:
					<>1__state = -1;
					<>2__current = null;
					<>1__state = 1;
					return true;
				case 1:
					<>1__state = -1;
					if (_uiSettleDelay > 0f)
					{
						<>2__current = (object)new WaitForSecondsRealtime(_uiSettleDelay);
						<>1__state = 2;
						return true;
					}
					goto IL_007b;
				case 2:
					<>1__state = -1;
					goto IL_007b;
				case 3:
					{
						<>1__state = -1;
						<attempt>5__2++;
						break;
					}
					IL_007b:
					<hostAndPort>5__1 = $"{_host}:{_port}";
					Log.LogInfo((object)("Aurora QuickConnect: attempting direct join to " + <hostAndPort>5__1));
					<attempt>5__2 = 1;
					break;
				}
				if (<attempt>5__2 <= _joinAttempts)
				{
					if (<attempt>5__2 == 1)
					{
						Log.LogInfo((object)$"Join attempt 1/{_joinAttempts}");
					}
					else
					{
						Log.LogInfo((object)$"Join attempt {<attempt>5__2}/{_joinAttempts}");
					}
					if (Joiner.TryJoin(fs, _host, _port, _password))
					{
						Log.LogInfo((object)"Aurora QuickConnect: join triggered successfully.");
						return false;
					}
					<>2__current = (object)new WaitForSecondsRealtime(_joinAttemptInterval);
					<>1__state = 3;
					return true;
				}
				Log.LogWarning((object)"Aurora QuickConnect: Failed to start connection (no compatible join method found).");
				if (_debugDumpOnFail)
				{
					Log.LogWarning((object)"Dumping join diagnostics (Debug.DumpJoinMethodsOnFail=true) ...");
					Joiner.DumpJoinMethods(fs);
				}
				return false;
			}

			bool IEnumerator.MoveNext()
			{
				//ILSpy generated this explicit interface implementation from .override directive in MoveNext
				return this.MoveNext();
			}

			[DebuggerHidden]
			void IEnumerator.Reset()
			{
				throw new NotSupportedException();
			}
		}

		public const string PluginGuid = "com.aurora.quickconnect";

		public const string PluginName = "AuroraQuickConnect";

		public const string PluginVersion = "1.0.0";

		private static ManualLogSource Log = null;

		private static Harmony _harmony = null;

		private static bool _pending;

		private static Coroutine? _connectRoutine;

		private static string _host = "85.206.119.102";

		private static int _port = 2456;

		private static string _password = "";

		private static string _buttonText = "Play Aurora";

		private static int _joinAttempts = 10;

		private static float _joinAttemptInterval = 0.25f;

		private static float _uiSettleDelay = 0.15f;

		private static bool _debugDumpOnFail = true;

		private ConfigEntry<string> _cfgHost = null;

		private ConfigEntry<int> _cfgPort = null;

		private ConfigEntry<string> _cfgPassword = null;

		private ConfigEntry<string> _cfgButtonText = null;

		private ConfigEntry<int> _cfgJoinAttempts = null;

		private ConfigEntry<float> _cfgJoinAttemptInterval = null;

		private ConfigEntry<float> _cfgUiSettleDelay = null;

		private ConfigEntry<bool> _cfgDebugDumpOnFail = null;

		private void Awake()
		{
			//IL_0019: Unknown result type (might be due to invalid IL or missing references)
			//IL_0023: Expected O, but got Unknown
			Log = ((BaseUnityPlugin)this).Logger;
			try
			{
				SetupConfig();
				_harmony = new Harmony("com.aurora.quickconnect");
				PatchIfExists(typeof(FejdStartup), "Start", null, "FejdStartup_Start_Postfix");
				PatchIfExists(typeof(FejdStartup), "OnCharacterStart", "FejdStartup_OnCharacterStart_Prefix");
				PatchIfExists(typeof(FejdStartup), "OnBack", null, "FejdStartup_Back_Postfix");
				PatchIfExists(typeof(FejdStartup), "OnBackButton", null, "FejdStartup_Back_Postfix");
				PatchIfExists(typeof(FejdStartup), "OnCancel", null, "FejdStartup_Back_Postfix");
				Log.LogInfo((object)string.Format("{0} {1} loaded. Target={2}:{3} (password={4}).", "AuroraQuickConnect", "1.0.0", _host, _port, string.IsNullOrEmpty(_password) ? "none" : "set"));
			}
			catch (Exception arg)
			{
				Log.LogError((object)string.Format("{0} failed to load: {1}", "AuroraQuickConnect", arg));
			}
		}

		private void OnDestroy()
		{
			try
			{
				Harmony harmony = _harmony;
				if (harmony != null)
				{
					harmony.UnpatchSelf();
				}
			}
			catch
			{
			}
		}

		private void SetupConfig()
		{
			_cfgHost = ((BaseUnityPlugin)this).Config.Bind<string>("Server", "Host", "85.206.119.102", "Server IP / hostname for Aurora.");
			_cfgPort = ((BaseUnityPlugin)this).Config.Bind<int>("Server", "Port", 2456, "Server port for Aurora.");
			_cfgPassword = ((BaseUnityPlugin)this).Config.Bind<string>("Server", "Password", "", "Server password (leave blank if none).");
			_cfgButtonText = ((BaseUnityPlugin)this).Config.Bind<string>("UI", "ButtonText", "Play Aurora", "Text shown on the main-menu button.");
			_cfgJoinAttempts = ((BaseUnityPlugin)this).Config.Bind<int>("Connect", "JoinAttempts", 10, "How many times to try triggering a join (safe retries, no spam).");
			_cfgJoinAttemptInterval = ((BaseUnityPlugin)this).Config.Bind<float>("Connect", "AttemptIntervalSeconds", 0.25f, "Seconds between join attempts.");
			_cfgUiSettleDelay = ((BaseUnityPlugin)this).Config.Bind<float>("Connect", "UiSettleDelaySeconds", 0.15f, "Small delay after clicking Start, before we trigger the join.");
			_cfgDebugDumpOnFail = ((BaseUnityPlugin)this).Config.Bind<bool>("Debug", "DumpJoinMethodsOnFail", true, "If join cannot be triggered, print join/connect method info to the log.");
			ApplyConfigSnapshot();
			_cfgHost.SettingChanged += delegate
			{
				ApplyConfigSnapshot();
			};
			_cfgPort.SettingChanged += delegate
			{
				ApplyConfigSnapshot();
			};
			_cfgPassword.SettingChanged += delegate
			{
				ApplyConfigSnapshot();
			};
			_cfgButtonText.SettingChanged += delegate
			{
				ApplyConfigSnapshot();
			};
			_cfgJoinAttempts.SettingChanged += delegate
			{
				ApplyConfigSnapshot();
			};
			_cfgJoinAttemptInterval.SettingChanged += delegate
			{
				ApplyConfigSnapshot();
			};
			_cfgUiSettleDelay.SettingChanged += delegate
			{
				ApplyConfigSnapshot();
			};
			_cfgDebugDumpOnFail.SettingChanged += delegate
			{
				ApplyConfigSnapshot();
			};
			try
			{
				((BaseUnityPlugin)this).Config.Save();
				Log.LogInfo((object)("Config ready: " + ((BaseUnityPlugin)this).Config.ConfigFilePath));
			}
			catch (Exception ex)
			{
				Log.LogWarning((object)("Could not save config file: " + ex.GetType().Name + ": " + ex.Message));
			}
		}

		private void ApplyConfigSnapshot()
		{
			_host = _cfgHost.Value?.Trim() ?? "85.206.119.102";
			_port = _cfgPort.Value;
			if (_port <= 0 || _port > 65535)
			{
				_port = 2456;
			}
			_password = _cfgPassword.Value ?? "";
			_buttonText = (string.IsNullOrWhiteSpace(_cfgButtonText.Value) ? "Play Aurora" : _cfgButtonText.Value);
			_joinAttempts = _cfgJoinAttempts.Value;
			if (_joinAttempts < 1)
			{
				_joinAttempts = 1;
			}
			if (_joinAttempts > 50)
			{
				_joinAttempts = 50;
			}
			_joinAttemptInterval = _cfgJoinAttemptInterval.Value;
			if (_joinAttemptInterval < 0.05f)
			{
				_joinAttemptInterval = 0.05f;
			}
			if (_joinAttemptInterval > 5f)
			{
				_joinAttemptInterval = 5f;
			}
			_uiSettleDelay = _cfgUiSettleDelay.Value;
			if (_uiSettleDelay < 0f)
			{
				_uiSettleDelay = 0f;
			}
			if (_uiSettleDelay > 5f)
			{
				_uiSettleDelay = 5f;
			}
			_debugDumpOnFail = _cfgDebugDumpOnFail.Value;
		}

		private static void PatchIfExists(Type type, string methodName, string? prefix = null, string? postfix = null)
		{
			//IL_0070: 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)
			try
			{
				MethodInfo methodInfo = AccessTools.Method(type, methodName, (Type[])null, (Type[])null);
				if (methodInfo == null)
				{
					ManualLogSource log = Log;
					if (log != null)
					{
						log.LogWarning((object)("[PatchIfExists] " + type.Name + "." + methodName + " not found on this client build. Skipping."));
					}
				}
				else
				{
					HarmonyMethod val = ((prefix == null) ? ((HarmonyMethod)null) : new HarmonyMethod(typeof(Class1), prefix, (Type[])null));
					HarmonyMethod val2 = ((postfix == null) ? ((HarmonyMethod)null) : new HarmonyMethod(typeof(Class1), postfix, (Type[])null));
					_harmony.Patch((MethodBase)methodInfo, val, val2, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null);
					ManualLogSource log2 = Log;
					if (log2 != null)
					{
						log2.LogInfo((object)("Patched " + type.Name + "." + methodName));
					}
				}
			}
			catch (Exception ex)
			{
				ManualLogSource log3 = Log;
				if (log3 != null)
				{
					log3.LogWarning((object)("[PatchIfExists] Failed patching " + type.Name + "." + methodName + ": " + ex.GetType().Name + ": " + ex.Message));
				}
			}
		}

		private static void FejdStartup_Start_Postfix(FejdStartup __instance)
		{
			FejdStartup __instance2 = __instance;
			try
			{
				Ui.TryInjectPlayAuroraButtonAboveStart(__instance2, delegate
				{
					OnPlayAuroraClicked(__instance2);
				});
			}
			catch (Exception arg)
			{
				ManualLogSource log = Log;
				if (log != null)
				{
					log.LogError((object)$"Button injection exception: {arg}");
				}
			}
		}

		private static bool FejdStartup_OnCharacterStart_Prefix(FejdStartup __instance)
		{
			if (!_pending)
			{
				return true;
			}
			_pending = false;
			Log.LogInfo((object)$"OnCharacterStart intercepted (Play Aurora) -> joining {_host}:{_port}");
			if (_connectRoutine != null)
			{
				try
				{
					((MonoBehaviour)__instance).StopCoroutine(_connectRoutine);
				}
				catch
				{
				}
				_connectRoutine = null;
			}
			_connectRoutine = ((MonoBehaviour)__instance).StartCoroutine(ConnectToServerRoutine(__instance));
			return false;
		}

		private static void FejdStartup_Back_Postfix()
		{
			_pending = false;
		}

		private static void OnPlayAuroraClicked(FejdStartup fs)
		{
			Log.LogInfo((object)"Play Aurora clicked.");
			_pending = true;
			if (!Ui.TryInvokeVoidBestEffort(fs, "OnStartGame") && !Ui.TryInvokeVoidBestEffort(fs, "OnStart") && !Ui.ClickButtonByLabel(fs, new string[2] { "start", "game" }, Array.Empty<string>()))
			{
				Log.LogWarning((object)"Couldn't open Character Select automatically. Click Valheim's Start Game once, then click Play Aurora again.");
				_pending = false;
			}
		}

		[IteratorStateMachine(typeof(<ConnectToServerRoutine>d__32))]
		private static IEnumerator ConnectToServerRoutine(FejdStartup fs)
		{
			//yield-return decompiler failed: Unexpected instruction in Iterator.Dispose()
			return new <ConnectToServerRoutine>d__32(0)
			{
				fs = fs
			};
		}
	}
}

BepInEx/plugins/AuroraWardDecay.dll

Decompiled 14 hours ago
using System;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Runtime.Versioning;
using System.Text;
using BepInEx;
using BepInEx.Configuration;
using HarmonyLib;
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("AuroraWardDecay")]
[assembly: AssemblyDescription("")]
[assembly: AssemblyConfiguration("")]
[assembly: AssemblyCompany("")]
[assembly: AssemblyProduct("AuroraWardDecay")]
[assembly: AssemblyCopyright("Copyright ©  2025")]
[assembly: AssemblyTrademark("")]
[assembly: ComVisible(false)]
[assembly: Guid("91245976-b7c2-4fcd-88a9-505fe801b8e8")]
[assembly: AssemblyFileVersion("1.0.0.0")]
[assembly: TargetFramework(".NETFramework,Version=v4.7.2", FrameworkDisplayName = ".NET Framework 4.7.2")]
[assembly: AssemblyVersion("1.0.0.0")]
namespace AuroraWardDecay
{
	public class Class1
	{
	}
}
namespace AuroraWardDecayMod
{
	[BepInPlugin("aurora.warddecay", "AuroraWardDecay", "1.0.5")]
	public class AuroraWardDecayPlugin : BaseUnityPlugin
	{
		[CompilerGenerated]
		private sealed class <ServerSweepLoop>d__12 : IEnumerator<object>, IDisposable, IEnumerator
		{
			private int <>1__state;

			private object <>2__current;

			public AuroraWardDecayPlugin <>4__this;

			private PrivateArea[] <wards>5__1;

			private long <nowTicks>5__2;

			private long <maxAge>5__3;

			private PrivateArea[] <>s__4;

			private int <>s__5;

			private PrivateArea <pa>5__6;

			private ZNetView <nview>5__7;

			private ZDO <zdo>5__8;

			private long <lastTouched>5__9;

			private Exception <ex>5__10;

			object IEnumerator<object>.Current
			{
				[DebuggerHidden]
				get
				{
					return <>2__current;
				}
			}

			object IEnumerator.Current
			{
				[DebuggerHidden]
				get
				{
					return <>2__current;
				}
			}

			[DebuggerHidden]
			public <ServerSweepLoop>d__12(int <>1__state)
			{
				this.<>1__state = <>1__state;
			}

			[DebuggerHidden]
			void IDisposable.Dispose()
			{
				<wards>5__1 = null;
				<>s__4 = null;
				<pa>5__6 = null;
				<nview>5__7 = null;
				<zdo>5__8 = null;
				<ex>5__10 = null;
				<>1__state = -2;
			}

			private bool MoveNext()
			{
				//IL_0031: Unknown result type (might be due to invalid IL or missing references)
				//IL_003b: Expected O, but got Unknown
				switch (<>1__state)
				{
				default:
					return false;
				case 0:
					<>1__state = -1;
					break;
				case 1:
					<>1__state = -1;
					if ((Object)(object)ZNet.instance == (Object)null || !ZNet.instance.IsServer())
					{
						break;
					}
					try
					{
						<wards>5__1 = Object.FindObjectsOfType<PrivateArea>();
						if (<wards>5__1 == null || <wards>5__1.Length == 0)
						{
							break;
						}
						<nowTicks>5__2 = NowTicks();
						<maxAge>5__3 = DaysToTicks(DecayDays.Value);
						<>s__4 = <wards>5__1;
						for (<>s__5 = 0; <>s__5 < <>s__4.Length; <>s__5++)
						{
							<pa>5__6 = <>s__4[<>s__5];
							if (Object.op_Implicit((Object)(object)<pa>5__6))
							{
								<nview>5__7 = ((Component)<pa>5__6).GetComponent<ZNetView>();
								if (Object.op_Implicit((Object)(object)<nview>5__7) && <nview>5__7.IsValid())
								{
									<zdo>5__8 = <nview>5__7.GetZDO();
									if (<zdo>5__8 != null)
									{
										<lastTouched>5__9 = <zdo>5__8.GetLong("aurora_lastTouched_ticks", 0L);
										if (<lastTouched>5__9 <= 0)
										{
											<zdo>5__8.Set("aurora_lastTouched_ticks", <nowTicks>5__2);
										}
										else
										{
											if (<nowTicks>5__2 - <lastTouched>5__9 >= <maxAge>5__3)
											{
												Object.Destroy((Object)(object)((Component)<pa>5__6).gameObject);
											}
											<nview>5__7 = null;
											<zdo>5__8 = null;
											<pa>5__6 = null;
										}
									}
								}
							}
						}
						<>s__4 = null;
						<wards>5__1 = null;
					}
					catch (Exception ex)
					{
						<ex>5__10 = ex;
						((BaseUnityPlugin)<>4__this).Logger.LogError((object)$"Ward decay sweep failed: {<ex>5__10}");
					}
					break;
				}
				<>2__current = (object)new WaitForSeconds(CheckIntervalSeconds.Value);
				<>1__state = 1;
				return true;
			}

			bool IEnumerator.MoveNext()
			{
				//ILSpy generated this explicit interface implementation from .override directive in MoveNext
				return this.MoveNext();
			}

			[DebuggerHidden]
			void IEnumerator.Reset()
			{
				throw new NotSupportedException();
			}
		}

		public const string PluginGuid = "aurora.warddecay";

		public const string PluginName = "AuroraWardDecay";

		public const string PluginVersion = "1.0.5";

		internal const string ZdoKeyLastTouchedTicks = "aurora_lastTouched_ticks";

		internal static ConfigEntry<float> CheckIntervalSeconds;

		internal static ConfigEntry<double> DecayDays;

		internal static ConfigEntry<bool> RefreshOnInteractOnly;

		internal static ConfigEntry<bool> EnableHoverText;

		internal static ConfigEntry<string> HoverFormat;

		private Harmony _harmony;

		private void Awake()
		{
			//IL_00b7: Unknown result type (might be due to invalid IL or missing references)
			//IL_00c1: Expected O, but got Unknown
			CheckIntervalSeconds = ((BaseUnityPlugin)this).Config.Bind<float>("General", "CheckIntervalSeconds", 30f, "How often the server checks for expired wards.");
			DecayDays = ((BaseUnityPlugin)this).Config.Bind<double>("General", "DecayDays", 7.0, "Days without interaction before a ward expires.");
			RefreshOnInteractOnly = ((BaseUnityPlugin)this).Config.Bind<bool>("General", "RefreshOnInteractOnly", true, "If true, only Interact() refreshes the timer (placement always seeds).");
			EnableHoverText = ((BaseUnityPlugin)this).Config.Bind<bool>("General", "EnableHoverText", true, "Show remaining time on ward hover.");
			HoverFormat = ((BaseUnityPlugin)this).Config.Bind<string>("General", "HoverFormat", "<color=yellow><b>Upkeep:</b></color> {0}", "Format for remaining time; {0}=time left.");
			_harmony = new Harmony("aurora.warddecay");
			_harmony.PatchAll();
			((MonoBehaviour)this).StartCoroutine(ServerSweepLoop());
			((BaseUnityPlugin)this).Logger.LogInfo((object)"AuroraWardDecay 1.0.5 loaded");
		}

		private void OnDestroy()
		{
			try
			{
				Harmony harmony = _harmony;
				if (harmony != null)
				{
					harmony.UnpatchSelf();
				}
			}
			catch
			{
			}
		}

		[IteratorStateMachine(typeof(<ServerSweepLoop>d__12))]
		private IEnumerator ServerSweepLoop()
		{
			//yield-return decompiler failed: Unexpected instruction in Iterator.Dispose()
			return new <ServerSweepLoop>d__12(0)
			{
				<>4__this = this
			};
		}

		internal static long NowTicks()
		{
			return (((Object)(object)ZNet.instance != (Object)null) ? ZNet.instance.GetTime() : DateTime.UtcNow).ToUniversalTime().Ticks;
		}

		internal static long DaysToTicks(double days)
		{
			return TimeSpan.FromDays(days).Ticks;
		}

		internal static string FormatRemaining(long nowTicks, long lastTouchedTicks, double days)
		{
			long num = DaysToTicks(days);
			long num2 = ((lastTouchedTicks > 0) ? Math.Max(0L, num - (nowTicks - lastTouchedTicks)) : num);
			if (num2 <= 0)
			{
				return "expired";
			}
			TimeSpan timeSpan = TimeSpan.FromTicks(num2);
			if (timeSpan.TotalDays >= 1.0)
			{
				return $"{(int)timeSpan.TotalDays}d {timeSpan.Hours}h";
			}
			if (timeSpan.TotalHours >= 1.0)
			{
				return $"{(int)timeSpan.TotalHours}h {timeSpan.Minutes}m";
			}
			return $"{timeSpan.Minutes}m {timeSpan.Seconds}s";
		}
	}
	internal static class SeedHelper
	{
		public static void Seed(PrivateArea pa)
		{
			if (!Object.op_Implicit((Object)(object)pa))
			{
				return;
			}
			ZNetView component = ((Component)pa).GetComponent<ZNetView>();
			if (Object.op_Implicit((Object)(object)component) && component.IsValid())
			{
				ZDO zDO = component.GetZDO();
				if (zDO != null && zDO.GetLong("aurora_lastTouched_ticks", 0L) == 0)
				{
					zDO.Set("aurora_lastTouched_ticks", AuroraWardDecayPlugin.NowTicks());
				}
			}
		}
	}
	[HarmonyPatch(typeof(PrivateArea), "Awake")]
	public static class Patch_PrivateArea_Awake_Seed
	{
		private static void Postfix(PrivateArea __instance)
		{
			SeedHelper.Seed(__instance);
		}
	}
	[HarmonyPatch(typeof(PrivateArea), "Interact")]
	public static class Patch_PrivateArea_Interact
	{
		private static void Postfix(PrivateArea __instance, bool __result, Humanoid human, bool hold, bool alt)
		{
			if (!__result || !AuroraWardDecayPlugin.RefreshOnInteractOnly.Value)
			{
				return;
			}
			ZNetView component = ((Component)__instance).GetComponent<ZNetView>();
			if (Object.op_Implicit((Object)(object)component) && component.IsValid())
			{
				ZDO zDO = component.GetZDO();
				if (zDO != null)
				{
					zDO.Set("aurora_lastTouched_ticks", AuroraWardDecayPlugin.NowTicks());
				}
			}
		}
	}
	[HarmonyPatch(typeof(PrivateArea), "GetHoverText")]
	public static class Patch_PrivateArea_GetHoverText
	{
		private static void Postfix(PrivateArea __instance, ref string __result)
		{
			if (!AuroraWardDecayPlugin.EnableHoverText.Value)
			{
				return;
			}
			ZNetView component = ((Component)__instance).GetComponent<ZNetView>();
			if (!Object.op_Implicit((Object)(object)component) || !component.IsValid())
			{
				return;
			}
			ZDO zDO = component.GetZDO();
			if (zDO != null)
			{
				long @long = zDO.GetLong("aurora_lastTouched_ticks", 0L);
				long nowTicks = AuroraWardDecayPlugin.NowTicks();
				string arg = AuroraWardDecayPlugin.FormatRemaining(nowTicks, @long, AuroraWardDecayPlugin.DecayDays.Value);
				StringBuilder stringBuilder = new StringBuilder(__result ?? string.Empty);
				if (!stringBuilder.ToString().EndsWith("\n"))
				{
					stringBuilder.Append('\n');
				}
				stringBuilder.AppendFormat(AuroraWardDecayPlugin.HoverFormat.Value, arg);
				__result = stringBuilder.ToString();
			}
		}
	}
}