Decompiled source of TimeControl v1.0.2

plugins/TimeControl.dll

Decompiled 3 weeks ago
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using BepInEx;
using BepInEx.Configuration;
using BepInEx.Logging;
using HarmonyLib;
using UnityEngine;

[assembly: CompilationRelaxations(8)]
[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
[assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)]
[assembly: AssemblyTitle("TimeControl")]
[assembly: AssemblyDescription("Adjusts the Valheim day/night cycle length via a multiplier, with server-authoritative sync and an exact mod-version gate.")]
[assembly: AssemblyProduct("TimeControl")]
[assembly: AssemblyCompany("drummercraig")]
[assembly: ComVisible(false)]
[assembly: AssemblyFileVersion("1.0.1")]
[assembly: AssemblyVersion("1.0.1.0")]
namespace TimeControl;

[BepInPlugin("drummercraig.time_control", "TimeControl", "1.0.1")]
public class TimeControlPlugin : BaseUnityPlugin
{
	public const string PluginGuid = "drummercraig.time_control";

	public const string PluginName = "TimeControl";

	public const string PluginVersion = "1.0.1";

	private void Awake()
	{
		//IL_001b: Unknown result type (might be due to invalid IL or missing references)
		//IL_0030: Expected O, but got Unknown
		TimeControlConfig.Log = ((BaseUnityPlugin)this).Logger;
		TimeControlConfig.Bind(((BaseUnityPlugin)this).Config);
		ResilientPatcher.ApplyAll(new Harmony("drummercraig.time_control"), Assembly.GetExecutingAssembly(), ((BaseUnityPlugin)this).Logger);
		((BaseUnityPlugin)this).Logger.LogInfo((object)(string.Format("[TimeControl] v{0} loaded. DayLengthMultiplier={1:0.###} ", "1.0.1", TimeControlConfig.DayLengthMultiplier.Value) + "(local config; server value takes over when connected to a server)."));
	}
}
public static class TimeControlConfig
{
	public static ManualLogSource Log;

	public static ConfigEntry<float> DayLengthMultiplier;

	public const float MinMultiplier = 0.1f;

	public const float MaxMultiplier = 100f;

	public static float ServerMultiplier = -1f;

	public static float EffectiveMultiplier => Mathf.Clamp((ServerMultiplier >= 0f) ? ServerMultiplier : DayLengthMultiplier.Value, 0.1f, 100f);

	public static void Bind(ConfigFile config)
	{
		//IL_0067: Unknown result type (might be due to invalid IL or missing references)
		//IL_0071: Expected O, but got Unknown
		DayLengthMultiplier = config.Bind<float>("General", "DayLengthMultiplier", 4f, new ConfigDescription("Multiplier applied to the in-game day/night cycle length. 1.0 = vanilla (a full day is ~30 real minutes). 2.0 = days take twice as long; 4.0 = ~2 real hours per full day. Day and night both scale together (their ratio is fixed by Valheim and is preserved). When you join a server running TimeControl, the server's value is used instead of this one for that session, so everyone shares the same clock. Range: " + 0.1f + " to " + 100f + ".", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0.1f, 100f), Array.Empty<object>()));
		DayLengthMultiplier.SettingChanged += delegate
		{
			DayLength.ApplyEffectiveMultiplier();
		};
	}
}
public static class DayLength
{
	[HarmonyPatch(typeof(EnvMan), "Awake")]
	private static class EnvManAwakePatch
	{
		private static void Postfix()
		{
			ApplyEffectiveMultiplier();
		}
	}

	private static long _vanillaBaseSec = -1L;

	public static bool HasBase => _vanillaBaseSec > 0;

	public static long VanillaBaseSec => _vanillaBaseSec;

	public static void ApplyEffectiveMultiplier()
	{
		try
		{
			EnvMan instance = EnvMan.instance;
			if ((Object)(object)instance == (Object)null)
			{
				return;
			}
			if (_vanillaBaseSec < 0)
			{
				_vanillaBaseSec = instance.m_dayLengthSec;
			}
			float effectiveMultiplier = TimeControlConfig.EffectiveMultiplier;
			long num = (long)((float)_vanillaBaseSec * effectiveMultiplier);
			if (num < 1)
			{
				num = 1L;
			}
			if (instance.m_dayLengthSec != num)
			{
				instance.m_dayLengthSec = num;
				ManualLogSource log = TimeControlConfig.Log;
				if (log != null)
				{
					log.LogInfo((object)($"[TimeControl] Day length set to {num}s " + $"(vanilla base {_vanillaBaseSec}s x {effectiveMultiplier:0.###})."));
				}
			}
		}
		catch (Exception arg)
		{
			ManualLogSource log2 = TimeControlConfig.Log;
			if (log2 != null)
			{
				log2.LogError((object)$"[TimeControl] ApplyEffectiveMultiplier failed: {arg}");
			}
		}
	}
}
public static class NetworkSync
{
	[HarmonyPatch(typeof(ZNet), "OnNewConnection", new Type[] { typeof(ZNetPeer) })]
	private static class OnNewConnectionPatch
	{
		private static void Postfix(ZNetPeer peer, ZNet __instance)
		{
			try
			{
				if (__instance.IsServer())
				{
					peer.m_rpc.Register<string>("TimeControl_Version", (Action<ZRpc, string>)OnServerReceiveClientVersion);
					peer.m_rpc.Invoke("TimeControl_Version", new object[1] { "1.0.1" });
				}
				else
				{
					_serverSentVersion = false;
					peer.m_rpc.Register<string>("TimeControl_Version", (Action<ZRpc, string>)OnClientReceiveServerVersion);
					peer.m_rpc.Register<float>("TimeControl_DayLength", (Action<ZRpc, float>)OnClientReceiveDayLength);
					peer.m_rpc.Invoke("TimeControl_Version", new object[1] { "1.0.1" });
				}
			}
			catch (Exception arg)
			{
				ManualLogSource log = TimeControlConfig.Log;
				if (log != null)
				{
					log.LogError((object)$"[TimeControl] OnNewConnection handling failed: {arg}");
				}
			}
		}
	}

	[HarmonyPatch(typeof(ZNet), "RPC_PeerInfo")]
	private static class RpcPeerInfoPatch
	{
		private static bool Prefix(ZRpc rpc, ZNet __instance)
		{
			//IL_006e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0074: Invalid comparison between Unknown and I4
			try
			{
				if (__instance.IsServer())
				{
					if (!_validatedPeers.Contains(rpc))
					{
						ManualLogSource log = TimeControlConfig.Log;
						if (log != null)
						{
							log.LogWarning((object)"[TimeControl] Disconnecting client without a matching TimeControl version.");
						}
						rpc.Invoke("Error", new object[1] { 3 });
						return false;
					}
					SendDayLengthToPeer(rpc);
					return true;
				}
				if (!_serverSentVersion)
				{
					ManualLogSource log2 = TimeControlConfig.Log;
					if (log2 != null)
					{
						log2.LogWarning((object)"[TimeControl] Server is not running TimeControl. Disconnecting (incompatible version).");
					}
					if ((int)ZNet.GetConnectionStatus() != 3)
					{
						SetClientConnectionError((ConnectionStatus)3);
					}
					if ((Object)(object)Game.instance != (Object)null)
					{
						Game.instance.Logout(true, true);
					}
					return false;
				}
				return true;
			}
			catch (Exception arg)
			{
				ManualLogSource log3 = TimeControlConfig.Log;
				if (log3 != null)
				{
					log3.LogError((object)$"[TimeControl] RPC_PeerInfo gate failed, allowing vanilla handshake: {arg}");
				}
				return true;
			}
		}

		private static void SendDayLengthToPeer(ZRpc rpc)
		{
			try
			{
				rpc.Invoke("TimeControl_DayLength", new object[1] { TimeControlConfig.EffectiveMultiplier });
			}
			catch (Exception ex)
			{
				ManualLogSource log = TimeControlConfig.Log;
				if (log != null)
				{
					log.LogWarning((object)("[TimeControl] Failed to push day length: " + ex.Message));
				}
			}
		}
	}

	private const string RpcVersion = "TimeControl_Version";

	private const string RpcDayLength = "TimeControl_DayLength";

	private static readonly HashSet<ZRpc> _validatedPeers = new HashSet<ZRpc>();

	private static bool _serverSentVersion;

	private static void OnServerReceiveClientVersion(ZRpc rpc, string clientVersion)
	{
		try
		{
			if (string.Equals(clientVersion, "1.0.1", StringComparison.Ordinal))
			{
				_validatedPeers.Add(rpc);
				ManualLogSource log = TimeControlConfig.Log;
				if (log != null)
				{
					log.LogInfo((object)("[TimeControl] Client presented matching version " + clientVersion + "."));
				}
			}
			else
			{
				ManualLogSource log2 = TimeControlConfig.Log;
				if (log2 != null)
				{
					log2.LogWarning((object)("[TimeControl] Client version '" + clientVersion + "' != server '1.0.1'. Will be disconnected at peer-info handshake."));
				}
			}
		}
		catch (Exception arg)
		{
			ManualLogSource log3 = TimeControlConfig.Log;
			if (log3 != null)
			{
				log3.LogError((object)$"[TimeControl] OnServerReceiveClientVersion failed: {arg}");
			}
		}
	}

	private static void OnClientReceiveServerVersion(ZRpc rpc, string serverVersion)
	{
		try
		{
			_serverSentVersion = true;
			if (!string.Equals(serverVersion, "1.0.1", StringComparison.Ordinal))
			{
				ManualLogSource log = TimeControlConfig.Log;
				if (log != null)
				{
					log.LogWarning((object)("[TimeControl] Server version '" + serverVersion + "' != client '1.0.1'. Disconnecting (incompatible version)."));
				}
			}
			else
			{
				ManualLogSource log2 = TimeControlConfig.Log;
				if (log2 != null)
				{
					log2.LogInfo((object)("[TimeControl] Server version " + serverVersion + " matches."));
				}
			}
		}
		catch (Exception arg)
		{
			ManualLogSource log3 = TimeControlConfig.Log;
			if (log3 != null)
			{
				log3.LogError((object)$"[TimeControl] OnClientReceiveServerVersion failed: {arg}");
			}
		}
	}

	private static void OnClientReceiveDayLength(ZRpc rpc, float serverMultiplier)
	{
		try
		{
			TimeControlConfig.ServerMultiplier = serverMultiplier;
			ManualLogSource log = TimeControlConfig.Log;
			if (log != null)
			{
				log.LogInfo((object)$"[TimeControl] Adopting server day-length multiplier {serverMultiplier:0.###} for this session.");
			}
			DayLength.ApplyEffectiveMultiplier();
		}
		catch (Exception arg)
		{
			ManualLogSource log2 = TimeControlConfig.Log;
			if (log2 != null)
			{
				log2.LogError((object)$"[TimeControl] OnClientReceiveDayLength failed: {arg}");
			}
		}
	}

	private static void SetClientConnectionError(ConnectionStatus status)
	{
		//IL_001a: Unknown result type (might be due to invalid IL or missing references)
		AccessTools.Field(typeof(ZNet), "m_connectionStatus")?.SetValue(null, status);
	}
}
internal static class ResilientPatcher
{
	internal static void ApplyAll(Harmony harmony, Assembly assembly, ManualLogSource log)
	{
		if (harmony == null || assembly == null)
		{
			return;
		}
		int num = 0;
		int num2 = 0;
		List<string> list = null;
		Type[] typesFromAssembly = AccessTools.GetTypesFromAssembly(assembly);
		foreach (Type type in typesFromAssembly)
		{
			try
			{
				List<MethodInfo> list2 = harmony.CreateClassProcessor(type).Patch();
				if (list2 != null && list2.Count > 0)
				{
					num++;
					if (log != null)
					{
						log.LogDebug((object)$"[TimeControl] patched {type.Name} ({list2.Count} method(s)).");
					}
				}
			}
			catch (Exception ex)
			{
				num2++;
				(list ?? (list = new List<string>())).Add(type.Name);
				if (log != null)
				{
					log.LogWarning((object)("[TimeControl] SKIP patch class " + type.Name + ": " + ex.Message));
				}
			}
		}
		if (num2 == 0)
		{
			if (log != null)
			{
				log.LogInfo((object)$"[TimeControl] patches: {num} applied, 0 skipped.");
			}
		}
		else if (log != null)
		{
			log.LogWarning((object)(string.Format("[TimeControl] patches: {0} applied, {1} skipped ({2}). ", num, num2, string.Join(", ", list)) + "A skipped patch usually means a Valheim update changed a hooked method; that feature is disabled but the mod is still running."));
		}
	}
}