Decompiled source of ModsmithCycle v0.1.6

ModsmithCycle.dll

Decompiled 5 days ago
using System;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.Versioning;
using System.Text;
using BepInEx;
using BepInEx.Configuration;
using HarmonyLib;
using Microsoft.CodeAnalysis;
using TMPro;
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: TargetFramework(".NETStandard,Version=v2.1", FrameworkDisplayName = ".NET Standard 2.1")]
[assembly: AssemblyCompany("ModsmithCycle")]
[assembly: AssemblyConfiguration("Debug")]
[assembly: AssemblyFileVersion("0.1.5.0")]
[assembly: AssemblyInformationalVersion("0.1.5")]
[assembly: AssemblyProduct("ModsmithCycle")]
[assembly: AssemblyTitle("ModsmithCycle")]
[assembly: AssemblyVersion("0.1.5.0")]
[module: RefSafetyRules(11)]
namespace Microsoft.CodeAnalysis
{
	[CompilerGenerated]
	[Embedded]
	internal sealed class EmbeddedAttribute : Attribute
	{
	}
}
namespace System.Runtime.CompilerServices
{
	[CompilerGenerated]
	[Embedded]
	[AttributeUsage(AttributeTargets.Module, AllowMultiple = false, Inherited = false)]
	internal sealed class RefSafetyRulesAttribute : Attribute
	{
		public readonly int Version;

		public RefSafetyRulesAttribute(int P_0)
		{
			Version = P_0;
		}
	}
}
namespace ModsmithCycle
{
	[BepInPlugin("MaddCatter.ModsmithCycle", "Modsmith Cycle", "0.1.5")]
	public sealed class ModsmithCyclePlugin : BaseUnityPlugin
	{
		private sealed class ResumeMarker
		{
			public string ResumeToken;

			public long CreatedUtcTicks;

			public string TargetKind;

			public string CharacterName;

			public string CharacterFileName;

			public string WorldName;

			public string WorldUid;

			public bool RestoreHostedSession;

			public bool PublicServer;

			public bool CrossplayServer;

			public string Serialize()
			{
				Dictionary<string, string> source = new Dictionary<string, string>(StringComparer.Ordinal)
				{
					["Header"] = "ModsmithCycle.Resume.v1",
					["ResumeToken"] = ResumeToken ?? "",
					["CreatedUtcTicks"] = CreatedUtcTicks.ToString(CultureInfo.InvariantCulture),
					["TargetKind"] = TargetKind ?? "",
					["CharacterName"] = Encode(CharacterName),
					["CharacterFileName"] = Encode(CharacterFileName),
					["WorldName"] = Encode(WorldName),
					["WorldUid"] = Encode(WorldUid),
					["RestoreHostedSession"] = (RestoreHostedSession ? "1" : "0"),
					["PublicServer"] = (PublicServer ? "1" : "0"),
					["CrossplayServer"] = (CrossplayServer ? "1" : "0")
				};
				return string.Join(Environment.NewLine, source.Select((KeyValuePair<string, string> kv) => kv.Key + "=" + kv.Value)) + Environment.NewLine;
			}

			public static ResumeMarker Parse(IEnumerable<string> lines)
			{
				Dictionary<string, string> values = ParseValues(lines);
				ResumeMarker resumeMarker = new ResumeMarker();
				resumeMarker.ResumeToken = Get(values, "ResumeToken");
				long.TryParse(Get(values, "CreatedUtcTicks"), NumberStyles.Integer, CultureInfo.InvariantCulture, out resumeMarker.CreatedUtcTicks);
				resumeMarker.TargetKind = Get(values, "TargetKind");
				resumeMarker.CharacterName = Decode(Get(values, "CharacterName"));
				resumeMarker.CharacterFileName = Decode(Get(values, "CharacterFileName"));
				resumeMarker.WorldName = Decode(Get(values, "WorldName"));
				resumeMarker.WorldUid = Decode(Get(values, "WorldUid"));
				resumeMarker.RestoreHostedSession = Get(values, "RestoreHostedSession") == "1";
				resumeMarker.PublicServer = Get(values, "PublicServer") == "1";
				resumeMarker.CrossplayServer = Get(values, "CrossplayServer") == "1";
				return resumeMarker;
			}
		}

		private sealed class LaunchRequest
		{
			public int ParentProcessId;

			public string SteamExecutablePath;

			public string[] ProfileArguments;

			public string RequestPath;

			public string Serialize()
			{
				List<string> list = new List<string>
				{
					"MODSMITH_CYCLE_LAUNCH_V2",
					"ParentProcessId=" + ParentProcessId.ToString(CultureInfo.InvariantCulture),
					"SteamExecutablePath=" + Encode(SteamExecutablePath)
				};
				string[] array = ProfileArguments ?? Array.Empty<string>();
				foreach (string value in array)
				{
					list.Add("Argument=" + Encode(value));
				}
				return string.Join(Environment.NewLine, list) + Environment.NewLine;
			}
		}

		public const string PluginGuid = "MaddCatter.ModsmithCycle";

		public const string PluginName = "Modsmith Cycle";

		public const string PluginVersion = "0.1.5";

		private const string ResumeArgument = "--modsmith-resume=";

		private const string ActiveResumeFilename = "ModsmithCycle.resume.active";

		private const string ProfileFilename = "ModsmithCycle.resume";

		private const string LaunchFilename = "ModsmithCycle.launch";

		private const float NoticeDurationSeconds = 5f;

		private ConfigEntry<int> _countdownSeconds;

		private ConfigEntry<bool> _autoResume;

		private ConfigEntry<bool> _resumeHostedSession;

		private ConfigEntry<bool> _autoReconnectRemoteServer;

		private ConfigEntry<bool> _requireNoOtherPlayers;

		private ConfigEntry<int> _maximumResumeAgeSeconds;

		private ConfigEntry<KeyboardShortcut> _restartShortcut;

		private ConfigEntry<string> _steamExecutableOverride;

		private bool _countdownActive;

		private bool _restartStarted;

		private float _restartAt;

		private string _noticeText;

		private float _noticeUntil;

		private Button _nativeRestartButton;

		private TMP_Text _nativeRestartButtonText;

		private TMP_Text _nativeRestartNoticeText;

		private RectTransform _nativeRestartTopOrnament;

		private string ResumePath => Path.Combine(Paths.ConfigPath, "ModsmithCycle.resume");

		private string ActiveResumePath => Path.Combine(Paths.ConfigPath, "ModsmithCycle.resume.active");

		private string LaunchPath => Path.Combine(Paths.ConfigPath, "ModsmithCycle.launch");

		private void Awake()
		{
			//IL_0025: Unknown result type (might be due to invalid IL or missing references)
			//IL_002f: Expected O, but got Unknown
			//IL_00e4: Unknown result type (might be due to invalid IL or missing references)
			//IL_00ee: Expected O, but got Unknown
			//IL_0117: Unknown result type (might be due to invalid IL or missing references)
			_countdownSeconds = ((BaseUnityPlugin)this).Config.Bind<int>("Restart", "CountdownSeconds", 5, new ConfigDescription("Seconds allowed to cancel a restart.", (AcceptableValueBase)(object)new AcceptableValueRange<int>(1, 30), Array.Empty<object>()));
			_autoResume = ((BaseUnityPlugin)this).Config.Bind<bool>("Restart", "AutoResumeLocalWorld", true, "Automatically reopen the same local world after restart.");
			_resumeHostedSession = ((BaseUnityPlugin)this).Config.Bind<bool>("Restart", "ResumeHostedSession", true, "Restore hosted/public/crossplay settings for a locally hosted world.");
			_autoReconnectRemoteServer = ((BaseUnityPlugin)this).Config.Bind<bool>("Restart", "AutoReconnectRemoteServer", false, "Reserved for future remote-server reconnect support. Currently returns to the menu after restart.");
			_requireNoOtherPlayers = ((BaseUnityPlugin)this).Config.Bind<bool>("Safety", "RequireNoOtherPlayers", true, "Block a locally hosted restart while another player is connected.");
			_maximumResumeAgeSeconds = ((BaseUnityPlugin)this).Config.Bind<int>("Safety", "MaximumResumeAgeSeconds", 180, new ConfigDescription("Maximum age of a resume request.", (AcceptableValueBase)(object)new AcceptableValueRange<int>(30, 900), Array.Empty<object>()));
			_restartShortcut = ((BaseUnityPlugin)this).Config.Bind<KeyboardShortcut>("Hotkeys", "Restart", new KeyboardShortcut((KeyCode)290, (KeyCode[])(object)new KeyCode[1] { (KeyCode)306 }), "Optional restart shortcut used while the Escape menu is open.");
			_steamExecutableOverride = ((BaseUnityPlugin)this).Config.Bind<string>("Compatibility", "SteamExecutableOverride", string.Empty, "Optional full path to Steam.exe for nonstandard installations.");
			ReportPreviousHelperFailure();
			TryPrepareResume();
			((BaseUnityPlugin)this).Logger.LogInfo((object)"Modsmith Cycle 0.1.5 loaded.");
		}

		private void Update()
		{
			//IL_004d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0052: Unknown result type (might be due to invalid IL or missing references)
			if (_countdownActive)
			{
				if (Input.GetKeyDown((KeyCode)27))
				{
					CancelCountdown();
					return;
				}
				if (Time.unscaledTime >= _restartAt)
				{
					_countdownActive = false;
					BeginRestart();
					return;
				}
			}
			KeyboardShortcut value = _restartShortcut.Value;
			if (((KeyboardShortcut)(ref value)).IsDown() && IsRestartMenuAvailable())
			{
				StartCountdown();
			}
			EnsureNativeRestartButton();
			RefreshNativeRestartUi();
		}

		private void OnGUI()
		{
			if (IsRestartMenuAvailable())
			{
			}
		}

		private bool IsRestartMenuAvailable()
		{
			if (_restartStarted)
			{
				return false;
			}
			if ((Object)(object)Player.m_localPlayer == (Object)null)
			{
				return false;
			}
			Menu instance = Menu.instance;
			if ((Object)(object)instance == (Object)null || (Object)(object)instance.m_root == (Object)null)
			{
				return false;
			}
			return ((Component)instance.m_root).gameObject.activeInHierarchy;
		}

		private void EnsureNativeRestartButton()
		{
			//IL_00f2: Unknown result type (might be due to invalid IL or missing references)
			//IL_00fc: Expected O, but got Unknown
			//IL_010f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0119: Expected O, but got Unknown
			//IL_062e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0640: Unknown result type (might be due to invalid IL or missing references)
			//IL_0650: Unknown result type (might be due to invalid IL or missing references)
			//IL_05ae: Unknown result type (might be due to invalid IL or missing references)
			//IL_05ba: Unknown result type (might be due to invalid IL or missing references)
			//IL_05ca: Unknown result type (might be due to invalid IL or missing references)
			//IL_06d2: Unknown result type (might be due to invalid IL or missing references)
			//IL_06de: Unknown result type (might be due to invalid IL or missing references)
			//IL_06ee: Unknown result type (might be due to invalid IL or missing references)
			//IL_0353: Unknown result type (might be due to invalid IL or missing references)
			//IL_0362: Unknown result type (might be due to invalid IL or missing references)
			//IL_0371: Unknown result type (might be due to invalid IL or missing references)
			//IL_0385: Unknown result type (might be due to invalid IL or missing references)
			//IL_038f: Unknown result type (might be due to invalid IL or missing references)
			//IL_03ba: Unknown result type (might be due to invalid IL or missing references)
			//IL_03a6: Unknown result type (might be due to invalid IL or missing references)
			//IL_03d0: Unknown result type (might be due to invalid IL or missing references)
			//IL_03dc: Unknown result type (might be due to invalid IL or missing references)
			//IL_0278: Unknown result type (might be due to invalid IL or missing references)
			//IL_0293: Unknown result type (might be due to invalid IL or missing references)
			//IL_02ae: Unknown result type (might be due to invalid IL or missing references)
			//IL_02c9: Unknown result type (might be due to invalid IL or missing references)
			//IL_02da: Unknown result type (might be due to invalid IL or missing references)
			//IL_04dc: Unknown result type (might be due to invalid IL or missing references)
			//IL_04eb: Unknown result type (might be due to invalid IL or missing references)
			//IL_0502: Unknown result type (might be due to invalid IL or missing references)
			//IL_0519: Unknown result type (might be due to invalid IL or missing references)
			Menu instance = Menu.instance;
			if ((Object)(object)instance == (Object)null || (Object)(object)instance.m_root == (Object)null)
			{
				return;
			}
			if ((Object)(object)_nativeRestartButton == (Object)null)
			{
				object member = GetMember(instance, "m_settingsButton");
				Button val = (Button)((member is Button) ? member : null);
				if ((Object)(object)val == (Object)null)
				{
					return;
				}
				GameObject val2 = Object.Instantiate<GameObject>(((Component)val).gameObject, ((Component)val).transform.parent);
				((Object)val2).name = "ModsmithCycleRestartButton";
				val2.SetActive(true);
				_nativeRestartButton = val2.GetComponent<Button>();
				LayoutElement val3 = val2.GetComponent<LayoutElement>();
				if ((Object)(object)val3 == (Object)null)
				{
					val3 = val2.AddComponent<LayoutElement>();
				}
				val3.ignoreLayout = true;
				if ((Object)(object)_nativeRestartButton == (Object)null)
				{
					return;
				}
				_nativeRestartButtonText = ((Component)_nativeRestartButton).GetComponentInChildren<TMP_Text>(true);
				_nativeRestartButton.onClick = new ButtonClickedEvent();
				((UnityEvent)_nativeRestartButton.onClick).AddListener(new UnityAction(OnNativeRestartButtonClicked));
				if ((Object)(object)_nativeRestartTopOrnament == (Object)null)
				{
					Transform val4 = (((Object)(object)((Component)val).transform.parent != (Object)null) ? ((Component)val).transform.parent.parent : null);
					Transform val5 = null;
					if ((Object)(object)val4 != (Object)null)
					{
						for (int i = 0; i < val4.childCount; i++)
						{
							Transform child = val4.GetChild(i);
							if (string.Equals(((Object)child).name, "ornament", StringComparison.OrdinalIgnoreCase))
							{
								val5 = child;
								break;
							}
						}
					}
					if ((Object)(object)val5 != (Object)null)
					{
						GameObject val6 = Object.Instantiate<GameObject>(((Component)val5).gameObject, ((Component)val5).transform.parent);
						((Object)val6).name = "ModsmithCycleTopOrnament";
						LayoutElement val7 = val6.GetComponent<LayoutElement>();
						if ((Object)(object)val7 == (Object)null)
						{
							val7 = val6.AddComponent<LayoutElement>();
						}
						val7.ignoreLayout = true;
						Graphic[] componentsInChildren = val6.GetComponentsInChildren<Graphic>(true);
						foreach (Graphic val8 in componentsInChildren)
						{
							val8.raycastTarget = false;
						}
						ref RectTransform nativeRestartTopOrnament = ref _nativeRestartTopOrnament;
						Transform transform = val6.transform;
						nativeRestartTopOrnament = (RectTransform)(object)((transform is RectTransform) ? transform : null);
						if ((Object)(object)_nativeRestartTopOrnament != (Object)null)
						{
							_nativeRestartTopOrnament.anchorMin = new Vector2(0.5f, 0.5f);
							_nativeRestartTopOrnament.anchorMax = new Vector2(0.5f, 0.5f);
							_nativeRestartTopOrnament.pivot = new Vector2(0.5f, 0.5f);
							_nativeRestartTopOrnament.anchoredPosition = new Vector2(-120f, -170f);
							((Transform)_nativeRestartTopOrnament).localScale = Vector3.one;
						}
					}
				}
				Transform transform2 = val2.transform;
				RectTransform val9 = (RectTransform)(object)((transform2 is RectTransform) ? transform2 : null);
				Transform transform3 = ((Component)val).transform;
				RectTransform val10 = (RectTransform)(object)((transform3 is RectTransform) ? transform3 : null);
				object member2 = GetMember(instance, "m_quitButton");
				Button val11 = (Button)((member2 is Button) ? member2 : null);
				RectTransform val12 = (RectTransform)(((Object)(object)val11 != (Object)null) ? /*isinst with value type is only supported in some contexts*/: null);
				if ((Object)(object)val9 != (Object)null && (Object)(object)val10 != (Object)null)
				{
					val9.anchorMin = val10.anchorMin;
					val9.anchorMax = val10.anchorMax;
					val9.pivot = val10.pivot;
					val9.sizeDelta = new Vector2(300f, val10.sizeDelta.y);
					float num = (((Object)(object)val12 != (Object)null) ? (val12.anchoredPosition.y - 145f) : (val10.anchoredPosition.y - 230f));
					val9.anchoredPosition = new Vector2(val10.anchoredPosition.x, num);
				}
				if ((Object)(object)_nativeRestartNoticeText == (Object)null && (Object)(object)_nativeRestartButtonText != (Object)null)
				{
					GameObject val13 = Object.Instantiate<GameObject>(((Component)_nativeRestartButtonText).gameObject, ((Component)_nativeRestartButton).transform.parent);
					((Object)val13).name = "ModsmithCycleRestartNotice";
					LayoutElement val14 = val13.GetComponent<LayoutElement>();
					if ((Object)(object)val14 == (Object)null)
					{
						val14 = val13.AddComponent<LayoutElement>();
					}
					val14.ignoreLayout = true;
					_nativeRestartNoticeText = val13.GetComponent<TMP_Text>();
					_nativeRestartNoticeText.text = string.Empty;
					_nativeRestartNoticeText.alignment = (TextAlignmentOptions)514;
					_nativeRestartNoticeText.enableAutoSizing = false;
					_nativeRestartNoticeText.fontSize = _nativeRestartButtonText.fontSize * 0.8f;
					RectTransform component = val13.GetComponent<RectTransform>();
					if ((Object)(object)val9 != (Object)null)
					{
						component.anchorMin = val9.anchorMin;
						component.anchorMax = val9.anchorMax;
						component.pivot = new Vector2(0.5f, 0.5f);
						component.sizeDelta = new Vector2(520f, 50f);
					}
					((Component)_nativeRestartNoticeText).gameObject.SetActive(false);
				}
			}
			if ((Object)(object)_nativeRestartButton != (Object)null)
			{
				Transform transform4 = ((Component)_nativeRestartButton).transform;
				RectTransform val15 = (RectTransform)(object)((transform4 is RectTransform) ? transform4 : null);
				object member3 = GetMember(instance, "m_quitButton");
				Button val16 = (Button)((member3 is Button) ? member3 : null);
				RectTransform val17 = (RectTransform)(((Object)(object)val16 != (Object)null) ? /*isinst with value type is only supported in some contexts*/: null);
				if ((Object)(object)val15 != (Object)null && (Object)(object)val17 != (Object)null)
				{
					val15.anchoredPosition = new Vector2(val17.anchoredPosition.x, val17.anchoredPosition.y - 145f);
				}
			}
			if ((Object)(object)_nativeRestartTopOrnament != (Object)null)
			{
				object member4 = GetMember(instance, "m_quitButton");
				Button val18 = (Button)((member4 is Button) ? member4 : null);
				RectTransform val19 = (RectTransform)(((Object)(object)val18 != (Object)null) ? /*isinst with value type is only supported in some contexts*/: null);
				if ((Object)(object)val19 != (Object)null)
				{
					_nativeRestartTopOrnament.anchoredPosition = new Vector2(val19.anchoredPosition.x - 50f, val19.anchoredPosition.y - 60f);
				}
			}
			if ((Object)(object)_nativeRestartNoticeText != (Object)null)
			{
				Transform transform5 = _nativeRestartNoticeText.transform;
				RectTransform val20 = (RectTransform)(object)((transform5 is RectTransform) ? transform5 : null);
				object member5 = GetMember(instance, "m_quitButton");
				Button val21 = (Button)((member5 is Button) ? member5 : null);
				RectTransform val22 = (RectTransform)(((Object)(object)val21 != (Object)null) ? /*isinst with value type is only supported in some contexts*/: null);
				if ((Object)(object)val20 != (Object)null && (Object)(object)val22 != (Object)null)
				{
					val20.anchoredPosition = new Vector2(val22.anchoredPosition.x, val22.anchoredPosition.y - 195f);
				}
			}
			if ((Object)(object)_nativeRestartButtonText != (Object)null)
			{
				_nativeRestartButtonText.text = (_countdownActive ? $"CANCEL RESTART ({Mathf.Max(0, Mathf.CeilToInt(_restartAt - Time.unscaledTime))})" : "SAVE & RESTART MODDED");
			}
			if ((Object)(object)_nativeRestartButton != (Object)null)
			{
				((Component)_nativeRestartButton).gameObject.SetActive(IsRestartMenuAvailable());
			}
		}

		private void RefreshNativeRestartUi()
		{
			//IL_00c7: Unknown result type (might be due to invalid IL or missing references)
			if ((Object)(object)_nativeRestartButtonText != (Object)null)
			{
				_nativeRestartButtonText.text = (_countdownActive ? $"CANCEL RESTART ({Mathf.Max(0, Mathf.CeilToInt(_restartAt - Time.unscaledTime))})" : "SAVE & RESTART MODDED");
			}
			if ((Object)(object)_nativeRestartNoticeText != (Object)null)
			{
				bool flag = !string.IsNullOrEmpty(_noticeText) && Time.unscaledTime < _noticeUntil;
				((Component)_nativeRestartNoticeText).gameObject.SetActive(flag);
				if (flag)
				{
					_nativeRestartNoticeText.text = _noticeText;
					_nativeRestartNoticeText.alignment = (TextAlignmentOptions)514;
					_nativeRestartNoticeText.margin = Vector4.zero;
					bool flag2 = string.Equals(_noticeText, "Restart cancelled.", StringComparison.Ordinal);
					_nativeRestartNoticeText.fontSize = _nativeRestartButtonText.fontSize * (flag2 ? 1f : 0.8f);
				}
			}
		}

		private void StartCountdown()
		{
			if (!_restartStarted)
			{
				if (!ValidateRestart(out var error))
				{
					ShowNotice(error);
					return;
				}
				_countdownActive = true;
				_restartAt = Time.unscaledTime + (float)_countdownSeconds.Value;
				ShowNotice("Restart queued. Press Escape to cancel.\nOr click the button again.");
			}
		}

		private void CancelCountdown()
		{
			_countdownActive = false;
			ShowNotice("Restart cancelled.");
		}

		private void OnNativeRestartButtonClicked()
		{
			if (_countdownActive)
			{
				CancelCountdown();
			}
			else
			{
				StartCountdown();
			}
		}

		private bool ValidateRestart(out string error)
		{
			error = null;
			string helperPath = GetHelperPath();
			if (!File.Exists(helperPath))
			{
				error = "ModsmithCycle.Restarter.exe is missing from the plugin folder.";
				return false;
			}
			if (_requireNoOtherPlayers.Value)
			{
				int connectedPeerCount = GetConnectedPeerCount();
				if (connectedPeerCount > 0)
				{
					error = "Restart blocked while another player is connected.";
					return false;
				}
			}
			return true;
		}

		private void BeginRestart()
		{
			if (_restartStarted)
			{
				return;
			}
			if (!ValidateRestart(out var error))
			{
				ShowNotice(error);
				return;
			}
			try
			{
				_restartStarted = true;
				string token = Guid.NewGuid().ToString("N");
				ResumeMarker resumeMarker = BuildResumeMarker(token);
				AtomicWrite(ResumePath, resumeMarker.Serialize());
				LaunchRequest launchRequest = BuildLaunchRequest(token);
				AtomicWrite(LaunchPath, launchRequest.Serialize());
				StartRestarter(launchRequest);
				ShowNotice("Saving and restarting Valheim...");
				Application.Quit();
			}
			catch (Exception ex)
			{
				_restartStarted = false;
				((BaseUnityPlugin)this).Logger.LogError((object)ex);
				ShowNotice("Restart failed: " + ex.Message);
			}
		}

		private ResumeMarker BuildResumeMarker(string token)
		{
			ResumeMarker resumeMarker = new ResumeMarker
			{
				ResumeToken = token,
				CreatedUtcTicks = DateTime.UtcNow.Ticks,
				TargetKind = "Menu"
			};
			if (_autoResume.Value)
			{
				object obj = TryGetHostedWorld();
				if (obj != null)
				{
					resumeMarker.TargetKind = "LocalWorld";
					resumeMarker.WorldName = Convert.ToString(GetMember(obj, "m_name"), CultureInfo.InvariantCulture);
					resumeMarker.WorldUid = Convert.ToString(GetMember(obj, "m_uid"), CultureInfo.InvariantCulture);
					resumeMarker.RestoreHostedSession = _resumeHostedSession.Value;
					TryCaptureHostedSettings(resumeMarker);
				}
				else if (_autoReconnectRemoteServer.Value)
				{
					((BaseUnityPlugin)this).Logger.LogWarning((object)"Remote-server auto reconnect is not available in this build; restart will return to the menu.");
				}
			}
			object obj2 = TryGetCurrentPlayerProfile();
			if (obj2 != null)
			{
				resumeMarker.CharacterName = Convert.ToString(GetMember(obj2, "m_playerName"), CultureInfo.InvariantCulture);
				MethodInfo methodInfo = AccessTools.Method(obj2.GetType(), "GetFilename", (Type[])null, (Type[])null);
				if (methodInfo != null)
				{
					try
					{
						resumeMarker.CharacterFileName = Convert.ToString(methodInfo.Invoke(obj2, null), CultureInfo.InvariantCulture);
					}
					catch
					{
					}
				}
			}
			return resumeMarker;
		}

		private LaunchRequest BuildLaunchRequest(string token)
		{
			string text = TryFindSteamExecutable();
			if (string.IsNullOrEmpty(text))
			{
				throw new FileNotFoundException("Steam.exe could not be located.");
			}
			string[] array = Environment.GetCommandLineArgs().Skip(1).ToArray();
			List<string> list = new List<string>();
			for (int i = 0; i < array.Length; i++)
			{
				string text2 = array[i];
				if (text2.StartsWith("--modsmith-resume=", StringComparison.OrdinalIgnoreCase))
				{
					continue;
				}
				if (IsManagedDoorstopArgument(text2))
				{
					if (i + 1 < array.Length && !array[i + 1].StartsWith("-", StringComparison.Ordinal))
					{
						i++;
					}
				}
				else
				{
					list.Add(text2);
				}
			}
			AddActiveProfileLoaderArguments(list);
			list.Add("--modsmith-resume=" + token);
			return new LaunchRequest
			{
				ParentProcessId = Process.GetCurrentProcess().Id,
				SteamExecutablePath = text,
				ProfileArguments = list.ToArray(),
				RequestPath = LaunchPath
			};
		}

		private void StartRestarter(LaunchRequest request)
		{
			ProcessStartInfo startInfo = new ProcessStartInfo
			{
				FileName = GetHelperPath(),
				Arguments = QuoteWindowsArgument(request.RequestPath),
				WorkingDirectory = (Path.GetDirectoryName(GetHelperPath()) ?? Directory.GetCurrentDirectory()),
				UseShellExecute = false,
				CreateNoWindow = true,
				WindowStyle = ProcessWindowStyle.Hidden
			};
			Process.Start(startInfo);
		}

		private string GetHelperPath()
		{
			string location = Assembly.GetExecutingAssembly().Location;
			return Path.Combine(Path.GetDirectoryName(location) ?? string.Empty, "ModsmithCycle.Restarter.exe");
		}

		private string TryFindSteamExecutable()
		{
			string text = _steamExecutableOverride.Value?.Trim();
			if (!string.IsNullOrEmpty(text) && File.Exists(text))
			{
				return text;
			}
			try
			{
				Process[] processesByName = Process.GetProcessesByName("steam");
				foreach (Process process in processesByName)
				{
					try
					{
						string text2 = process.MainModule?.FileName;
						if (!string.IsNullOrEmpty(text2) && File.Exists(text2))
						{
							return text2;
						}
					}
					catch
					{
					}
					finally
					{
						process.Dispose();
					}
				}
			}
			catch
			{
			}
			string[] array = new string[2]
			{
				Environment.GetFolderPath(Environment.SpecialFolder.ProgramFilesX86),
				Environment.GetFolderPath(Environment.SpecialFolder.ProgramFiles)
			};
			string[] array2 = array;
			foreach (string text3 in array2)
			{
				if (!string.IsNullOrEmpty(text3))
				{
					string text4 = Path.Combine(text3, "Steam", "Steam.exe");
					if (File.Exists(text4))
					{
						return text4;
					}
				}
			}
			return null;
		}

		private static bool IsManagedDoorstopArgument(string arg)
		{
			if (string.IsNullOrEmpty(arg))
			{
				return false;
			}
			string text = arg.TrimStart('-').ToLowerInvariant();
			return text.StartsWith("doorstop-enable", StringComparison.Ordinal) || text.StartsWith("doorstop-target", StringComparison.Ordinal) || text.StartsWith("doorstop_target", StringComparison.Ordinal) || text.StartsWith("doorstop_enabled", StringComparison.Ordinal);
		}

		private static void AddActiveProfileLoaderArguments(List<string> arguments)
		{
			string bepInExRootPath = Paths.BepInExRootPath;
			string item = Path.Combine(Paths.BepInExAssemblyDirectory, "BepInEx.Preloader.dll");
			string path = Path.Combine(Path.GetDirectoryName(Paths.BepInExRootPath) ?? Paths.BepInExRootPath, ".doorstop_version");
			if (File.Exists(path) && File.ReadAllText(path).Trim().StartsWith("4", StringComparison.Ordinal))
			{
				arguments.Add("--doorstop-enabled");
				arguments.Add("true");
				arguments.Add("--doorstop-target-assembly");
				arguments.Add(item);
			}
			else
			{
				arguments.Add("--doorstop-enable");
				arguments.Add("true");
				arguments.Add("--doorstop-target");
				arguments.Add(item);
			}
		}

		private void TryPrepareResume()
		{
			string resumeTokenFromCommandLine = GetResumeTokenFromCommandLine();
			if (string.IsNullOrEmpty(resumeTokenFromCommandLine) || !File.Exists(ResumePath))
			{
				return;
			}
			ResumeMarker resumeMarker;
			try
			{
				resumeMarker = ResumeMarker.Parse(File.ReadAllLines(ResumePath));
			}
			catch (Exception ex)
			{
				((BaseUnityPlugin)this).Logger.LogWarning((object)("Could not parse resume marker: " + ex.Message));
				return;
			}
			if (!string.Equals(resumeMarker.ResumeToken, resumeTokenFromCommandLine, StringComparison.Ordinal))
			{
				return;
			}
			if ((DateTime.UtcNow - new DateTime(resumeMarker.CreatedUtcTicks, DateTimeKind.Utc)).TotalSeconds > (double)_maximumResumeAgeSeconds.Value)
			{
				DeleteIfPresent(ResumePath);
				return;
			}
			try
			{
				if (File.Exists(ActiveResumePath))
				{
					File.Delete(ActiveResumePath);
				}
				File.Move(ResumePath, ActiveResumePath);
			}
			catch
			{
				return;
			}
			((MonoBehaviour)this).StartCoroutine(ResumeWhenReady(resumeMarker));
		}

		private IEnumerator ResumeWhenReady(ResumeMarker marker)
		{
			float deadline = Time.realtimeSinceStartup + 120f;
			FejdStartup startup = null;
			while (Time.realtimeSinceStartup < deadline)
			{
				startup = FejdStartup.instance;
				if ((Object)(object)startup != (Object)null)
				{
					break;
				}
				yield return null;
			}
			if ((Object)(object)startup == (Object)null)
			{
				yield break;
			}
			bool resumeSucceeded;
			if (string.Equals(marker.TargetKind, "LocalWorld", StringComparison.Ordinal))
			{
				try
				{
					startup.OnStartGame();
				}
				catch (Exception ex)
				{
					Exception ex2 = ex;
					((BaseUnityPlugin)this).Logger.LogWarning((object)("Resume could not activate Start Game: " + ex2.Message));
					yield break;
				}
				while (Time.realtimeSinceStartup < deadline)
				{
					object profiles = GetMember(startup, "m_profiles");
					if (profiles is ICollection profileCollection && profileCollection.Count > 0)
					{
						break;
					}
					yield return null;
				}
				object loadedProfiles = GetMember(startup, "m_profiles");
				if (!(loadedProfiles is ICollection loadedProfileCollection) || loadedProfileCollection.Count == 0)
				{
					((BaseUnityPlugin)this).Logger.LogWarning((object)"Resume timed out waiting for character profiles.");
					yield break;
				}
				if (!TryRestoreCharacter(marker))
				{
					((BaseUnityPlugin)this).Logger.LogWarning((object)"Saved character could not be restored; stopping at the menu.");
					yield break;
				}
				MethodInfo showStartGame = AccessTools.Method(((object)startup).GetType(), "ShowStartGame", (Type[])null, (Type[])null);
				if (showStartGame == null)
				{
					((BaseUnityPlugin)this).Logger.LogWarning((object)"Resume could not find Valheim's world selection method.");
					yield break;
				}
				try
				{
					showStartGame.Invoke(startup, null);
				}
				catch (Exception ex3)
				{
					((BaseUnityPlugin)this).Logger.LogWarning((object)("Resume could not open world selection: " + ex3.Message));
					yield break;
				}
				while (Time.realtimeSinceStartup < deadline)
				{
					object worlds = GetMember(startup, "m_worlds");
					if (worlds is ICollection worldCollection && worldCollection.Count > 0)
					{
						break;
					}
					yield return null;
				}
				object loadedWorlds = GetMember(startup, "m_worlds");
				if (!(loadedWorlds is ICollection loadedWorldCollection) || loadedWorldCollection.Count == 0)
				{
					((BaseUnityPlugin)this).Logger.LogWarning((object)"Resume timed out waiting for worlds.");
					yield break;
				}
				resumeSucceeded = TryResumeLocalWorld(marker);
			}
			else
			{
				resumeSucceeded = true;
			}
			if (resumeSucceeded)
			{
				DeleteIfPresent(ActiveResumePath);
			}
		}

		private bool TryRestoreCharacter(ResumeMarker marker)
		{
			if (string.IsNullOrEmpty(marker.CharacterFileName))
			{
				return false;
			}
			FejdStartup instance = FejdStartup.instance;
			if ((Object)(object)instance == (Object)null)
			{
				return false;
			}
			object member = GetMember(instance, "m_profiles");
			if (!(member is IEnumerable enumerable))
			{
				return false;
			}
			foreach (object item in enumerable)
			{
				if (item == null)
				{
					continue;
				}
				MethodInfo methodInfo = AccessTools.Method(item.GetType(), "GetFilename", (Type[])null, (Type[])null);
				if (methodInfo == null)
				{
					continue;
				}
				string text;
				try
				{
					text = Convert.ToString(methodInfo.Invoke(item, null), CultureInfo.InvariantCulture);
				}
				catch
				{
					continue;
				}
				if (!string.Equals(text, marker.CharacterFileName, StringComparison.OrdinalIgnoreCase))
				{
					continue;
				}
				object member2 = GetMember(item, "m_fileSource");
				if (member2 == null)
				{
					return false;
				}
				MethodInfo methodInfo2 = AccessTools.Method(((object)instance).GetType(), "SelectCharacter", (Type[])null, (Type[])null);
				if (methodInfo2 == null)
				{
					return false;
				}
				try
				{
					methodInfo2.Invoke(instance, new object[2] { text, member2 });
					((BaseUnityPlugin)this).Logger.LogInfo((object)("Restored character profile \"" + text + "\"."));
					return true;
				}
				catch (Exception ex)
				{
					((BaseUnityPlugin)this).Logger.LogWarning((object)("Character resume failed: " + ex.Message));
					return false;
				}
			}
			((BaseUnityPlugin)this).Logger.LogWarning((object)("Saved character profile \"" + marker.CharacterFileName + "\" could not be found."));
			return false;
		}

		private bool TryResumeLocalWorld(ResumeMarker marker)
		{
			FejdStartup instance = FejdStartup.instance;
			if ((Object)(object)instance == (Object)null)
			{
				return false;
			}
			World val = FindWorld(marker.WorldName, marker.WorldUid);
			if (val == null)
			{
				((BaseUnityPlugin)this).Logger.LogWarning((object)("Saved world could not be resolved; stopping at the menu. Name=\"" + marker.WorldName + "\" UID=\"" + marker.WorldUid + "\"."));
				return false;
			}
			SetMember(instance, "m_world", val);
			RestoreHostedSettings(instance, marker);
			MethodInfo methodInfo = AccessTools.Method(((object)instance).GetType(), "OnWorldStart", (Type[])null, (Type[])null) ?? AccessTools.Method(((object)instance).GetType(), "StartGame", (Type[])null, (Type[])null);
			if (methodInfo != null)
			{
				try
				{
					methodInfo.Invoke(instance, null);
					return true;
				}
				catch (Exception ex)
				{
					((BaseUnityPlugin)this).Logger.LogWarning((object)("Local-world resume failed: " + ex.Message));
				}
			}
			return false;
		}

		private World FindWorld(string name, string uid)
		{
			FejdStartup instance = FejdStartup.instance;
			if ((Object)(object)instance == (Object)null)
			{
				return null;
			}
			object member = GetMember(instance, "m_worlds");
			if (!(member is IEnumerable enumerable))
			{
				return null;
			}
			foreach (object item in enumerable)
			{
				World val = (World)((item is World) ? item : null);
				if (val != null)
				{
					string b = Convert.ToString(GetMember(val, "m_name"), CultureInfo.InvariantCulture);
					string b2 = Convert.ToString(GetMember(val, "m_uid"), CultureInfo.InvariantCulture);
					if ((!string.IsNullOrEmpty(uid) && string.Equals(uid, b2, StringComparison.Ordinal)) || (!string.IsNullOrEmpty(name) && string.Equals(name, b, StringComparison.Ordinal)))
					{
						return val;
					}
				}
			}
			return null;
		}

		private object TryGetHostedWorld()
		{
			ZNet instance = ZNet.instance;
			if ((Object)(object)instance == (Object)null)
			{
				return null;
			}
			MethodInfo methodInfo = AccessTools.Method(((object)instance).GetType(), "GetWorldIfIsHost", (Type[])null, (Type[])null);
			if (methodInfo == null)
			{
				return null;
			}
			try
			{
				return methodInfo.Invoke(instance, null);
			}
			catch
			{
				return null;
			}
		}

		private object TryGetCurrentPlayerProfile()
		{
			Game instance = Game.instance;
			if ((Object)(object)instance == (Object)null)
			{
				return null;
			}
			MethodInfo methodInfo = AccessTools.Method(((object)instance).GetType(), "GetPlayerProfile", (Type[])null, (Type[])null);
			try
			{
				return methodInfo?.Invoke(instance, null);
			}
			catch
			{
				return null;
			}
		}

		private int GetConnectedPeerCount()
		{
			ZNet instance = ZNet.instance;
			if ((Object)(object)instance == (Object)null)
			{
				return 0;
			}
			MethodInfo methodInfo = AccessTools.Method(((object)instance).GetType(), "GetPeerConnections", (Type[])null, (Type[])null);
			try
			{
				object obj = methodInfo?.Invoke(instance, null);
				if (obj is ICollection collection)
				{
					return Math.Max(0, collection.Count - 1);
				}
			}
			catch
			{
			}
			return 0;
		}

		private void TryCaptureHostedSettings(ResumeMarker marker)
		{
			ZNet instance = ZNet.instance;
			if (!((Object)(object)instance == (Object)null))
			{
				marker.PublicServer = ZNet.IsOpenServer();
				marker.CrossplayServer = false;
			}
		}

		private void RestoreHostedSettings(object startup, ResumeMarker marker)
		{
			if (!marker.RestoreHostedSession)
			{
				return;
			}
			FejdStartup val = (FejdStartup)((startup is FejdStartup) ? startup : null);
			if (!((Object)(object)val == (Object)null))
			{
				if ((Object)(object)val.m_publicServerToggle != (Object)null)
				{
					val.m_publicServerToggle.isOn = marker.PublicServer;
				}
				if ((Object)(object)val.m_crossplayServerToggle != (Object)null)
				{
					val.m_crossplayServerToggle.isOn = marker.CrossplayServer;
				}
			}
		}

		private static string GetResumeTokenFromCommandLine()
		{
			string[] commandLineArgs = Environment.GetCommandLineArgs();
			foreach (string text in commandLineArgs)
			{
				if (text.StartsWith("--modsmith-resume=", StringComparison.OrdinalIgnoreCase))
				{
					return text.Substring("--modsmith-resume=".Length);
				}
			}
			return null;
		}

		private void ReportPreviousHelperFailure()
		{
			string path = LaunchPath + ".error";
			if (!File.Exists(path))
			{
				return;
			}
			try
			{
				string text = File.ReadAllText(path);
				((BaseUnityPlugin)this).Logger.LogWarning((object)("Previous restart helper reported: " + text));
				File.Delete(path);
			}
			catch
			{
			}
		}

		private void ShowNotice(string text)
		{
			_noticeText = text;
			_noticeUntil = Time.unscaledTime + 5f;
			((BaseUnityPlugin)this).Logger.LogInfo((object)text);
		}

		private static object GetStaticMember(Type type, string name)
		{
			if (type == null)
			{
				return null;
			}
			FieldInfo fieldInfo = AccessTools.Field(type, name);
			if (fieldInfo != null)
			{
				return fieldInfo.GetValue(null);
			}
			return AccessTools.Property(type, name)?.GetValue(null, null);
		}

		private static object GetMember(object instance, string name)
		{
			if (instance == null)
			{
				return null;
			}
			Type type = instance.GetType();
			FieldInfo fieldInfo = AccessTools.Field(type, name);
			if (fieldInfo != null)
			{
				return fieldInfo.GetValue(instance);
			}
			return AccessTools.Property(type, name)?.GetValue(instance, null);
		}

		private static void SetMember(object instance, string name, object value)
		{
			if (instance == null)
			{
				return;
			}
			Type type = instance.GetType();
			FieldInfo fieldInfo = AccessTools.Field(type, name);
			if (fieldInfo != null)
			{
				fieldInfo.SetValue(instance, value);
				return;
			}
			PropertyInfo propertyInfo = AccessTools.Property(type, name);
			if (propertyInfo != null && propertyInfo.CanWrite)
			{
				propertyInfo.SetValue(instance, value, null);
			}
		}

		private static void AtomicWrite(string path, string text)
		{
			string text2 = path + ".tmp";
			File.WriteAllText(text2, text, new UTF8Encoding(encoderShouldEmitUTF8Identifier: false));
			if (File.Exists(path))
			{
				File.Delete(path);
			}
			File.Move(text2, path);
		}

		private static void DeleteIfPresent(string path)
		{
			try
			{
				if (File.Exists(path))
				{
					File.Delete(path);
				}
			}
			catch
			{
			}
		}

		internal static string QuoteWindowsArgument(string value)
		{
			if (string.IsNullOrEmpty(value))
			{
				return "\"\"";
			}
			if (value.All((char c2) => !char.IsWhiteSpace(c2) && c2 != '"'))
			{
				return value;
			}
			StringBuilder stringBuilder = new StringBuilder();
			stringBuilder.Append('"');
			int num = 0;
			foreach (char c in value)
			{
				switch (c)
				{
				case '\\':
					num++;
					break;
				case '"':
					stringBuilder.Append('\\', num * 2 + 1);
					stringBuilder.Append('"');
					num = 0;
					break;
				default:
					stringBuilder.Append('\\', num);
					num = 0;
					stringBuilder.Append(c);
					break;
				}
			}
			stringBuilder.Append('\\', num * 2);
			stringBuilder.Append('"');
			return stringBuilder.ToString();
		}

		private static Dictionary<string, string> ParseValues(IEnumerable<string> lines)
		{
			Dictionary<string, string> dictionary = new Dictionary<string, string>(StringComparer.Ordinal);
			foreach (string line in lines)
			{
				int num = line.IndexOf('=');
				if (num > 0)
				{
					dictionary[line.Substring(0, num)] = line.Substring(num + 1);
				}
			}
			return dictionary;
		}

		private static string Get(Dictionary<string, string> values, string key)
		{
			string value;
			return values.TryGetValue(key, out value) ? value : string.Empty;
		}

		private static string Encode(string value)
		{
			return Convert.ToBase64String(Encoding.UTF8.GetBytes(value ?? string.Empty));
		}

		private static string Decode(string value)
		{
			if (string.IsNullOrEmpty(value))
			{
				return string.Empty;
			}
			try
			{
				return Encoding.UTF8.GetString(Convert.FromBase64String(value));
			}
			catch
			{
				return string.Empty;
			}
		}
	}
}

ModsmithCycle.Restarter.exe

Decompiled 5 days ago
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.Versioning;
using System.Text;
using System.Threading;
using Microsoft.CodeAnalysis;

[assembly: CompilationRelaxations(8)]
[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
[assembly: Debuggable(DebuggableAttribute.DebuggingModes.Default | DebuggableAttribute.DebuggingModes.DisableOptimizations | DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints | DebuggableAttribute.DebuggingModes.EnableEditAndContinue)]
[assembly: TargetFramework(".NETFramework,Version=v4.8", FrameworkDisplayName = ".NET Framework 4.8")]
[assembly: AssemblyCompany("ModsmithCycle.Restarter")]
[assembly: AssemblyConfiguration("Debug")]
[assembly: AssemblyFileVersion("0.1.3.0")]
[assembly: AssemblyInformationalVersion("0.1.3")]
[assembly: AssemblyProduct("ModsmithCycle.Restarter")]
[assembly: AssemblyTitle("ModsmithCycle.Restarter")]
[assembly: AssemblyVersion("0.1.3.0")]
[module: RefSafetyRules(11)]
namespace Microsoft.CodeAnalysis
{
	[CompilerGenerated]
	[Embedded]
	internal sealed class EmbeddedAttribute : Attribute
	{
	}
}
namespace System.Runtime.CompilerServices
{
	[CompilerGenerated]
	[Embedded]
	[AttributeUsage(AttributeTargets.Module, AllowMultiple = false, Inherited = false)]
	internal sealed class RefSafetyRulesAttribute : Attribute
	{
		public readonly int Version;

		public RefSafetyRulesAttribute(int P_0)
		{
			Version = P_0;
		}
	}
}
namespace ModsmithCycle.Restarter
{
	internal static class Program
	{
		private sealed class LaunchRequest
		{
			public int ParentProcessId;

			public string SteamExecutablePath;

			public string[] ProfileArguments;

			public static LaunchRequest Load(string path)
			{
				string[] array = File.ReadAllLines(path);
				if (array.Length == 0 || !string.Equals(array[0], "MODSMITH_CYCLE_LAUNCH_V2", StringComparison.Ordinal))
				{
					throw new InvalidDataException("Unsupported restart request.");
				}
				LaunchRequest launchRequest = new LaunchRequest();
				List<string> list = new List<string>();
				bool flag = false;
				bool flag2 = false;
				for (int i = 1; i < array.Length; i++)
				{
					string text = array[i];
					int num = text.IndexOf('=');
					if (num <= 0)
					{
						continue;
					}
					string a = text.Substring(0, num);
					string text2 = text.Substring(num + 1);
					if (string.Equals(a, "ParentProcessId", StringComparison.Ordinal))
					{
						if (!int.TryParse(text2, NumberStyles.Integer, CultureInfo.InvariantCulture, out launchRequest.ParentProcessId))
						{
							throw new InvalidDataException("Invalid parent process id.");
						}
						flag = true;
					}
					else if (string.Equals(a, "SteamExecutablePath", StringComparison.Ordinal))
					{
						launchRequest.SteamExecutablePath = Decode(text2);
						flag2 = true;
					}
					else if (string.Equals(a, "Argument", StringComparison.Ordinal))
					{
						list.Add(Decode(text2));
					}
				}
				if (!flag)
				{
					throw new InvalidDataException("Missing parent process id.");
				}
				if (!flag2 || string.IsNullOrEmpty(launchRequest.SteamExecutablePath))
				{
					throw new InvalidDataException("Missing Steam executable path.");
				}
				launchRequest.ProfileArguments = list.ToArray();
				return launchRequest;
			}

			private static string Get(Dictionary<string, string> values, string key)
			{
				string value;
				return values.TryGetValue(key, out value) ? value : string.Empty;
			}

			private static string Decode(string value)
			{
				if (string.IsNullOrEmpty(value))
				{
					return string.Empty;
				}
				return Encoding.UTF8.GetString(Convert.FromBase64String(value));
			}
		}

		private const int ParentExitTimeoutMilliseconds = 120000;

		private const int SteamAppId = 892970;

		[STAThread]
		private static int Main(string[] args)
		{
			string text = ((args != null && args.Length != 0) ? args[0] : null);
			try
			{
				if (string.IsNullOrEmpty(text) || !File.Exists(text))
				{
					throw new FileNotFoundException("Restart request file was not found.", text);
				}
				LaunchRequest launchRequest = LaunchRequest.Load(text);
				WaitForParent(launchRequest.ParentProcessId);
				Thread.Sleep(1500);
				if (!File.Exists(launchRequest.SteamExecutablePath))
				{
					throw new FileNotFoundException("Steam.exe was not found.", launchRequest.SteamExecutablePath);
				}
				ProcessStartInfo startInfo = new ProcessStartInfo
				{
					FileName = launchRequest.SteamExecutablePath,
					Arguments = BuildSteamCommandLine(launchRequest.ProfileArguments),
					WorkingDirectory = Path.GetDirectoryName(launchRequest.SteamExecutablePath),
					UseShellExecute = false
				};
				Process.Start(startInfo);
				DeleteIfPresent(text);
				DeleteIfPresent(text + ".error");
				return 0;
			}
			catch (Exception exception)
			{
				TryWriteError(text, exception);
				return 1;
			}
		}

		private static void WaitForParent(int processId)
		{
			if (processId <= 0)
			{
				return;
			}
			try
			{
				using Process process = Process.GetProcessById(processId);
				if (!process.WaitForExit(120000))
				{
					throw new TimeoutException("Valheim did not exit before the restart timeout.");
				}
			}
			catch (ArgumentException)
			{
			}
		}

		internal static string BuildSteamCommandLine(IEnumerable<string> profileArguments)
		{
			StringBuilder stringBuilder = new StringBuilder();
			stringBuilder.Append("-applaunch ");
			stringBuilder.Append(892970.ToString(CultureInfo.InvariantCulture));
			foreach (string item in profileArguments ?? Enumerable.Empty<string>())
			{
				stringBuilder.Append(' ');
				stringBuilder.Append(QuoteArgument(item));
			}
			return stringBuilder.ToString();
		}

		internal static string QuoteArgument(string argument)
		{
			if (string.IsNullOrEmpty(argument))
			{
				return "\"\"";
			}
			if (!argument.Any((char c2) => char.IsWhiteSpace(c2) || c2 == '"'))
			{
				return argument;
			}
			StringBuilder stringBuilder = new StringBuilder();
			stringBuilder.Append('"');
			int num = 0;
			foreach (char c in argument)
			{
				switch (c)
				{
				case '\\':
					num++;
					break;
				case '"':
					stringBuilder.Append('\\', num * 2 + 1);
					stringBuilder.Append('"');
					num = 0;
					break;
				default:
					stringBuilder.Append('\\', num);
					num = 0;
					stringBuilder.Append(c);
					break;
				}
			}
			stringBuilder.Append('\\', num * 2);
			stringBuilder.Append('"');
			return stringBuilder.ToString();
		}

		private static void DeleteIfPresent(string path)
		{
			try
			{
				if (!string.IsNullOrEmpty(path) && File.Exists(path))
				{
					File.Delete(path);
				}
			}
			catch
			{
			}
		}

		private static void TryWriteError(string requestPath, Exception exception)
		{
			if (string.IsNullOrEmpty(requestPath))
			{
				return;
			}
			try
			{
				File.WriteAllText(requestPath + ".error", DateTime.UtcNow.ToString("O", CultureInfo.InvariantCulture) + Environment.NewLine + exception.GetType().FullName + ": " + exception.Message + Environment.NewLine, new UTF8Encoding(encoderShouldEmitUTF8Identifier: false));
			}
			catch
			{
			}
		}
	}
}