Please disclose if any significant portion of your mod was created using AI tools by adding the 'AI Generated' category. Failing to do so may result in the mod being removed from Thunderstore.
Decompiled source of ModsmithCycle v0.1.6
ModsmithCycle.dll
Decompiled 5 days agousing 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 agousing 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 { } } } }