using System; using System.Diagnostics; using System.IO; using System.Linq; using System.Reflection; using System.Runtime.CompilerServices; using System.Runtime.Versioning; using System.Threading.Tasks; using BepInEx; using BepInEx.Configuration; using BepInEx.Logging; using HarmonyLib; using Microsoft.CodeAnalysis; using UnityEngine; using XomNghien.Bootstrap; [assembly: CompilationRelaxations(8)] [assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)] [assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)] [assembly: TargetFramework(".NETFramework,Version=v4.7.2", FrameworkDisplayName = ".NET Framework 4.7.2")] [assembly: AssemblyCompany("XomNghienRuntimeUpdater")] [assembly: AssemblyConfiguration("Release")] [assembly: AssemblyFileVersion("2.1.0.0")] [assembly: AssemblyInformationalVersion("2.1.0+cede9045c60d8f4cbe5376fd902eda1df65f5555")] [assembly: AssemblyProduct("XomNghienRuntimeUpdater")] [assembly: AssemblyTitle("XomNghienRuntimeUpdater")] [assembly: AssemblyVersion("2.1.0.0")] [module: RefSafetyRules(11)] namespace Microsoft.CodeAnalysis { [CompilerGenerated] [Embedded] internal sealed class EmbeddedAttribute : Attribute { } } namespace System.Runtime.CompilerServices { [CompilerGenerated] [Embedded] [AttributeUsage(AttributeTargets.Class | AttributeTargets.Property | AttributeTargets.Field | AttributeTargets.Event | AttributeTargets.Parameter | AttributeTargets.ReturnValue | AttributeTargets.GenericParameter, AllowMultiple = false, Inherited = false)] internal sealed class NullableAttribute : Attribute { public readonly byte[] NullableFlags; public NullableAttribute(byte P_0) { NullableFlags = new byte[1] { P_0 }; } public NullableAttribute(byte[] P_0) { NullableFlags = P_0; } } [CompilerGenerated] [Embedded] [AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct | AttributeTargets.Method | AttributeTargets.Interface | AttributeTargets.Delegate, AllowMultiple = false, Inherited = false)] internal sealed class NullableContextAttribute : Attribute { public readonly byte Flag; public NullableContextAttribute(byte P_0) { Flag = P_0; } } [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 XomNghien.RuntimeUpdater { internal sealed class ManifestHandshake { private const string ManifestRpc = "ServerModBootstrap_Manifest_v1"; private const BindingFlags AllMembers = BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic; private static ManifestHandshake? _instance; private readonly ManualLogSource _log; public ManifestHandshake(ManualLogSource log) { _log = log; } public void Install(Harmony harmony) { //IL_006d: Unknown result type (might be due to invalid IL or missing references) //IL_0072: Unknown result type (might be due to invalid IL or missing references) //IL_0085: Expected O, but got Unknown _instance = this; Type type = AccessTools.TypeByName("ZNet") ?? throw new TypeLoadException("Valheim ZNet type was not found"); MethodInfo methodInfo = AccessTools.Method(type, "OnNewConnection", (Type[])null, (Type[])null) ?? throw new MissingMethodException(type.FullName, "OnNewConnection"); MethodInfo methodInfo2 = AccessTools.Method(typeof(ManifestHandshake), "OnNewConnectionPostfix", (Type[])null, (Type[])null) ?? throw new MissingMethodException("OnNewConnectionPostfix"); harmony.Patch((MethodBase)methodInfo, (HarmonyMethod)null, new HarmonyMethod(methodInfo2) { priority = 800 }, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); _log.LogInfo((object)"Installed the server manifest relay handshake"); } private static void OnNewConnectionPostfix(object __instance, object __0) { _instance?.OnNewConnection(__instance, __0); } private void OnNewConnection(object znet, object peer) { try { object obj = peer.GetType().GetField("m_rpc", BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic)?.GetValue(peer); if (obj == null) { return; } RpcReflectionBridge.RegisterString(obj, "ServerModBootstrap_Manifest_v1", (Action)ReceiveManifest); if (IsServer(znet)) { string text = BootstrapSynchronizer.ReadRelayManifest(); if (string.IsNullOrWhiteSpace(text)) { _log.LogWarning((object)"A client connected, but no validated manifest is available to relay"); return; } RpcReflectionBridge.InvokeString(obj, "ServerModBootstrap_Manifest_v1", text); _log.LogInfo((object)$"Relayed the manifest to a connecting client ({text.Length} characters)"); } } catch (Exception ex) { _log.LogError((object)("Manifest handshake failed: " + ex)); } } private static void ReceiveManifest(object _, string manifest) { RuntimeUpdaterPlugin.Instance?.QueueRelayedManifest(manifest); } private static bool IsServer(object znet) { object obj = znet.GetType().GetMethod("IsServer", BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic, null, Type.EmptyTypes, null)?.Invoke(znet, null); if (obj is bool) { return (bool)obj; } FieldInfo field = znet.GetType().GetField("m_isServer", BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic); obj = field?.GetValue(field.IsStatic ? null : znet); bool flag = default(bool); int num; if (obj is bool) { flag = (bool)obj; num = 1; } else { num = 0; } return (byte)((uint)num & (flag ? 1u : 0u)) != 0; } } [BepInPlugin("org.servermodbootstrap.runtime-updater", "Server Mod Bootstrap Runtime Updater", "2.1.0")] public sealed class RuntimeUpdaterPlugin : BaseUnityPlugin { public const string PluginGuid = "org.servermodbootstrap.runtime-updater"; public const string PluginName = "Server Mod Bootstrap Runtime Updater"; public const string PluginVersion = "2.1.0"; private ConfigEntry _enabled; private ConfigEntry _pollIntervalSeconds; private ConfigEntry _autoRestart; private ConfigEntry _restartDelaySeconds; private ConfigEntry _restartForConfigChanges; private Task? _check; private Task? _relayedCheck; private readonly object _relayLock = new object(); private string? _pendingManifest; private float _nextCheckAt; private float? _restartAt; private bool _restartRequested; private bool _isDedicatedServer; private bool _showRestartPrompt; private string _promptMessage = "Server mods were updated. Restart Valheim before reconnecting."; private Harmony? _harmony; internal static RuntimeUpdaterPlugin? Instance { get; private set; } private void Awake() { //IL_0050: Unknown result type (might be due to invalid IL or missing references) //IL_005a: Expected O, but got Unknown //IL_00a9: Unknown result type (might be due to invalid IL or missing references) //IL_00b3: Expected O, but got Unknown //IL_00ea: Unknown result type (might be due to invalid IL or missing references) //IL_00f4: Expected O, but got Unknown Instance = this; _enabled = ((BaseUnityPlugin)this).Config.Bind("Live updates", "Enabled", true, "Poll the server manifest while a dedicated server is running."); _pollIntervalSeconds = ((BaseUnityPlugin)this).Config.Bind("Live updates", "PollIntervalSeconds", 60, new ConfigDescription("Seconds between manifest checks.", (AcceptableValueBase)(object)new AcceptableValueRange(30, 3600), Array.Empty())); _autoRestart = ((BaseUnityPlugin)this).Config.Bind("Restart", "AutoRestartForModChanges", true, "Save and quit after installing a changed plugin set. The server supervisor must restart the process."); _restartDelaySeconds = ((BaseUnityPlugin)this).Config.Bind("Restart", "DelaySeconds", 60, new ConfigDescription("Delay before saving and quitting after a mod change.", (AcceptableValueBase)(object)new AcceptableValueRange(10, 1800), Array.Empty())); _restartForConfigChanges = ((BaseUnityPlugin)this).Config.Bind("Restart", "RestartForConfigChanges", false, "Also restart after config-only changes. Leave false for mods such as AzuAntiCheat that watch their config files."); _isDedicatedServer = IsDedicatedServer(); _harmony = new Harmony("org.servermodbootstrap.runtime-updater"); try { new ManifestHandshake(((BaseUnityPlugin)this).Logger).Install(_harmony); } catch (Exception ex) { ((BaseUnityPlugin)this).Logger.LogError((object)("Could not install the manifest handshake: " + ex)); } if (!_isDedicatedServer) { ((BaseUnityPlugin)this).Logger.LogInfo((object)"Client manifest relay receiver is ready; no client configuration is required"); return; } _nextCheckAt = Time.realtimeSinceStartup + (float)Math.Max(30, _pollIntervalSeconds.Value); ((BaseUnityPlugin)this).Logger.LogInfo((object)$"Live server mod polling enabled every {Math.Max(30, _pollIntervalSeconds.Value)} seconds"); } private void Update() { CompleteRelayedCheck(); StartRelayedCheck(); if (!_isDedicatedServer || !_enabled.Value || _restartRequested) { return; } if (_restartAt.HasValue) { if (Time.realtimeSinceStartup >= _restartAt.Value) { RestartServer(); } return; } CompleteCheck(); if (_check == null && Time.realtimeSinceStartup >= _nextCheckAt) { _nextCheckAt = Time.realtimeSinceStartup + (float)Math.Max(30, _pollIntervalSeconds.Value); _check = Task.Run((Func)BootstrapSynchronizer.StageConfiguredUpdate); } } internal void QueueRelayedManifest(string manifest) { if (!_isDedicatedServer && !string.IsNullOrWhiteSpace(manifest)) { lock (_relayLock) { _pendingManifest = manifest; } ((BaseUnityPlugin)this).Logger.LogInfo((object)"Received the server mod manifest; checking local managed mods"); } } private void StartRelayedCheck() { if (_isDedicatedServer || _relayedCheck != null) { return; } string manifest; lock (_relayLock) { manifest = _pendingManifest; _pendingManifest = null; } if (manifest != null) { _relayedCheck = Task.Run(() => BootstrapSynchronizer.StageRelayedManifest(manifest)); } } private void CompleteRelayedCheck() { if (_relayedCheck == null || !_relayedCheck.IsCompleted) { return; } Task relayedCheck = _relayedCheck; _relayedCheck = null; if (relayedCheck.IsFaulted) { string text = relayedCheck.Exception?.GetBaseException().Message ?? "Unknown manifest error"; ((BaseUnityPlugin)this).Logger.LogError((object)("The server manifest was rejected: " + text)); DisconnectClient(); ShowPrompt("The server's mod manifest was invalid. Connection stopped.\n\n" + text); return; } SynchronizationResult result = relayedCheck.Result; if (!result.Changed) { ((BaseUnityPlugin)this).Logger.LogInfo((object)"The installed managed mods already match the server"); return; } ((BaseUnityPlugin)this).Logger.LogWarning((object)("Staged server revision " + ShortRevision(result.Revision) + "; Valheim must restart before reconnecting")); WriteRestartMarker(result); DisconnectClient(); ShowPrompt("This server requires a different managed mod set.\n\nThe mods are downloaded and staged. Restart Valheim, then connect again."); } private void CompleteCheck() { if (_check == null || !_check.IsCompleted) { return; } Task check = _check; _check = null; if (check.IsFaulted) { ((BaseUnityPlugin)this).Logger.LogError((object)("Live mod synchronization failed: " + check.Exception?.GetBaseException())); return; } SynchronizationResult result = check.Result; if (!result.Changed) { return; } ((BaseUnityPlugin)this).Logger.LogInfo((object)("Installed managed revision " + ShortRevision(result.Revision))); if (result.PackagesChanged || (_restartForConfigChanges.Value && result.ConfigsChanged)) { WriteRestartMarker(result); if (!_autoRestart.Value) { ((BaseUnityPlugin)this).Logger.LogWarning((object)"Managed mods changed. A server restart is required; automatic restart is disabled."); return; } _restartAt = Time.realtimeSinceStartup + (float)Math.Max(10, _restartDelaySeconds.Value); ((BaseUnityPlugin)this).Logger.LogWarning((object)$"Managed mods changed. Saving and quitting for supervisor restart in {Math.Max(10, _restartDelaySeconds.Value)} seconds."); } } private void RestartServer() { _restartRequested = true; TrySaveWorld(); ((BaseUnityPlugin)this).Logger.LogWarning((object)"Quitting dedicated server so its supervisor can load the new managed mods."); Application.Quit(); } private void DisconnectClient() { try { Type type = (from assembly in AppDomain.CurrentDomain.GetAssemblies() select assembly.GetType("Game", throwOnError: false)).FirstOrDefault((Type type2) => type2 != null); if (!(type == null)) { object obj = type.GetField("instance", BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic)?.GetValue(null) ?? type.GetProperty("instance", BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic)?.GetValue(null, null); type.GetMethod("Logout", BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic, null, new Type[2] { typeof(bool), typeof(bool) }, null)?.Invoke(obj, new object[2] { true, true }); } } catch (Exception ex) { ((BaseUnityPlugin)this).Logger.LogWarning((object)("Could not disconnect after a managed mod update: " + ex.Message)); } } private void ShowPrompt(string message) { _promptMessage = message; _showRestartPrompt = true; } private void OnGUI() { //IL_0046: Unknown result type (might be due to invalid IL or missing references) //IL_0075: Unknown result type (might be due to invalid IL or missing references) //IL_00a9: Unknown result type (might be due to invalid IL or missing references) if (_showRestartPrompt && !_isDedicatedServer) { Rect val = default(Rect); ((Rect)(ref val))..ctor(((float)Screen.width - 520f) / 2f, ((float)Screen.height - 220f) / 2f, 520f, 220f); GUI.Box(val, "Server Mod Update"); GUI.Label(new Rect(((Rect)(ref val)).x + 24f, ((Rect)(ref val)).y + 48f, 472f, 100f), _promptMessage); if (GUI.Button(new Rect(((Rect)(ref val)).x + 150f, ((Rect)(ref val)).y + 164f, 220f, 36f), "Quit Valheim now")) { Application.Quit(); } } } private void OnDestroy() { Harmony? harmony = _harmony; if (harmony != null) { harmony.UnpatchSelf(); } if (Instance == this) { Instance = null; } } private void TrySaveWorld() { try { Type type = (from assembly in AppDomain.CurrentDomain.GetAssemblies() select assembly.GetType("ZNet", throwOnError: false)).FirstOrDefault((Type type2) => type2 != null); if (type == null) { ((BaseUnityPlugin)this).Logger.LogWarning((object)"Could not find ZNet; quitting without an explicit pre-restart save."); return; } object obj = type.GetField("instance", BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic)?.GetValue(null) ?? type.GetProperty("instance", BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic)?.GetValue(null, null); MethodInfo method = type.GetMethod("Save", BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic, null, new Type[1] { typeof(bool) }, null); if (obj == null || method == null) { ((BaseUnityPlugin)this).Logger.LogWarning((object)"Could not invoke ZNet.Save; Valheim's normal quit handling will be used."); return; } method.Invoke(obj, new object[1] { false }); ((BaseUnityPlugin)this).Logger.LogInfo((object)"Requested a world save before restart."); } catch (Exception ex) { ((BaseUnityPlugin)this).Logger.LogError((object)("Pre-restart world save failed: " + ex)); } } private static void WriteRestartMarker(SynchronizationResult result) { string text = Path.Combine(Paths.BepInExRootPath, "xom-bootstrap"); Directory.CreateDirectory(text); File.WriteAllText(Path.Combine(text, "restart-required"), $"revision={result.Revision}{Environment.NewLine}createdAt={DateTimeOffset.UtcNow:O}{Environment.NewLine}"); } private static string ShortRevision(string revision) { return revision.Substring(0, Math.Min(12, revision.Length)); } private static bool IsDedicatedServer() { return Environment.GetCommandLineArgs().Any((string argument) => argument.Equals("-batchmode", StringComparison.OrdinalIgnoreCase)); } } }