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 TimegatedProgression v0.7.5
TimegatedProgression.dll
Decompiled 3 weeks agousing System; using System.Collections; using System.Collections.Generic; using System.Diagnostics; using System.Globalization; using System.IO; using System.IO.Compression; using System.Linq; using System.Net; using System.Reflection; using System.Runtime.CompilerServices; using System.Runtime.Serialization; using System.Runtime.Versioning; using System.Text; using BepInEx; using BepInEx.Bootstrap; using BepInEx.Configuration; using HarmonyLib; using JetBrains.Annotations; using Microsoft.CodeAnalysis; using ServerSync; using TMPro; using UnityEngine; [assembly: CompilationRelaxations(8)] [assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)] [assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)] [assembly: TargetFramework(".NETFramework,Version=v4.8", FrameworkDisplayName = ".NET Framework 4.8")] [assembly: AssemblyCompany("TimegatedProgression")] [assembly: AssemblyConfiguration("Release")] [assembly: AssemblyFileVersion("0.1.0.0")] [assembly: AssemblyInformationalVersion("0.1.0")] [assembly: AssemblyProduct("TimegatedProgression")] [assembly: AssemblyTitle("TimegatedProgression")] [assembly: AssemblyVersion("0.1.0.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 TimegatedProgression { internal static class ModGuard { [HarmonyPatch(typeof(ZNet), "OnNewConnection")] internal static class SendManifest { private static void Prefix(ZNetPeer peer, ZNet __instance) { if (peer == null || peer.m_rpc == null) { return; } if (__instance.IsServer()) { Failures.Remove(peer.m_rpc); peer.m_rpc.Register<string>("TimegatedProgression_ModGuard", (Action<ZRpc, string>)ReceiveManifest); return; } peer.m_rpc.Register<string>("TimegatedProgression_ModGuardReason", (Action<ZRpc, string>)delegate(ZRpc rpc, string text) { LastRejectReason = text; }); peer.m_rpc.Invoke("TimegatedProgression_ModGuard", new object[1] { LocalManifest() }); } } [HarmonyPatch(typeof(ZNet), "RPC_PeerInfo")] internal static class RejectPeer { private static bool Prefix(ZRpc rpc, ZNet __instance) { if (!__instance.IsServer() || Enabled == null || !Enabled.Value) { return true; } if (!Failures.TryGetValue(rpc, out var value)) { return true; } rpc.Invoke("TimegatedProgression_ModGuardReason", new object[1] { RejectMessage.Value + "\n\n" + value }); rpc.Invoke("Error", new object[1] { 774101985 }); Failures.Remove(rpc); return false; } } [HarmonyPatch(typeof(ZNet), "Disconnect")] internal static class ForgetPeer { private static void Prefix(ZNetPeer peer) { if (peer != null && peer.m_rpc != null) { Failures.Remove(peer.m_rpc); } } } [HarmonyPatch(typeof(FejdStartup), "ShowConnectError")] internal static class ShowReason { private static void Postfix(FejdStartup __instance) { //IL_0000: Unknown result type (might be due to invalid IL or missing references) //IL_000a: Invalid comparison between Unknown and I4 if ((int)ZNet.GetConnectionStatus() == 774101985 && !((Object)(object)__instance.m_connectionFailedError == (Object)null)) { __instance.m_connectionFailedError.text = (string.IsNullOrEmpty(LastRejectReason) ? "Your installed mods do not match this server's." : LastRejectReason); } } } private const string ManifestRpc = "TimegatedProgression_ModGuard"; private const string ReasonRpc = "TimegatedProgression_ModGuardReason"; internal const int RejectStatus = 774101985; internal const string DefaultRejectMessage = "Your installed mods do not match this server's."; internal static ConfigEntry<bool> Enabled; internal static ConfigEntry<bool> StrictVersions; internal static ConfigEntry<string> VersionLockedMods; internal static ConfigEntry<string> ServerOnlyMods; internal static ConfigEntry<string> ClientOnlyMods; internal static ConfigEntry<string> RejectMessage; private static readonly Dictionary<ZRpc, string> Failures = new Dictionary<ZRpc, string>(); internal static string LastRejectReason; internal static void Bind(Plugin plugin, Func<ConfigEntry<bool>, ConfigEntry<bool>> syncedBool, Func<ConfigEntry<string>, ConfigEntry<string>> syncedString) { Enabled = syncedBool(((BaseUnityPlugin)plugin).Config.Bind<bool>("1 - Mod Guard", "Enabled", false, "Refuse players whose installed mods do not match the server's. Off by default: turn it on only once ServerOnlyMods and ClientOnlyMods are filled in, because a wrong list locks everyone out, including you.")); StrictVersions = syncedBool(((BaseUnityPlugin)plugin).Config.Bind<bool>("1 - Mod Guard", "StrictVersions", true, "Require every mod's version to match the server's, not just the mod to be present. Turn this off to check versions only for the mods in VersionLockedMods.")); VersionLockedMods = syncedString(((BaseUnityPlugin)plugin).Config.Bind<string>("1 - Mod Guard", "VersionLockedMods", "", "Comma-separated plugin GUIDs whose version must match the server's even when StrictVersions is off. Every other mod only has to be present.")); ServerOnlyMods = syncedString(((BaseUnityPlugin)plugin).Config.Bind<string>("1 - Mod Guard", "ServerOnlyMods", "", "Comma-separated plugin GUIDs the server runs that players are NOT expected to have. Anything listed here is left out of the required list.")); ClientOnlyMods = syncedString(((BaseUnityPlugin)plugin).Config.Bind<string>("1 - Mod Guard", "ClientOnlyMods", "", "Comma-separated plugin GUIDs players may run that the server does not. Under a strict whitelist anything not listed here and not on the server gets the player refused.")); RejectMessage = syncedString(((BaseUnityPlugin)plugin).Config.Bind<string>("1 - Mod Guard", "RejectMessage", "Your installed mods do not match this server's.", "Shown to a refused player, above the list of what is wrong.")); } private static HashSet<string> Split(ConfigEntry<string> entry) { HashSet<string> hashSet = new HashSet<string>(StringComparer.OrdinalIgnoreCase); if (entry == null || string.IsNullOrEmpty(entry.Value)) { return hashSet; } string[] array = entry.Value.Split(new char[1] { ',' }); for (int i = 0; i < array.Length; i++) { string text = array[i].Trim(); if (text.Length > 0) { hashSet.Add(text); } } return hashSet; } private static string LocalManifest() { return string.Join(",", Chainloader.PluginInfos.Values.Select((PluginInfo p) => p.Metadata.GUID + "=" + p.Metadata.Version).OrderBy<string, string>((string s) => s, StringComparer.OrdinalIgnoreCase).ToArray()); } private static Dictionary<string, string> Parse(string manifest) { Dictionary<string, string> dictionary = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase); if (string.IsNullOrEmpty(manifest)) { return dictionary; } string[] array = manifest.Split(new char[1] { ',' }); foreach (string text in array) { int num = text.IndexOf('='); if (num > 0) { dictionary[text.Substring(0, num).Trim()] = text.Substring(num + 1).Trim(); } } return dictionary; } private static string Judge(string clientManifest) { Dictionary<string, string> dictionary = Parse(clientManifest); Dictionary<string, string> dictionary2 = Parse(LocalManifest()); HashSet<string> hashSet = Split(ServerOnlyMods); HashSet<string> hashSet2 = Split(ClientOnlyMods); HashSet<string> hashSet3 = Split(VersionLockedMods); List<string> list = new List<string>(); List<string> list2 = new List<string>(); List<string> list3 = new List<string>(); foreach (KeyValuePair<string, string> item in dictionary2) { if (!hashSet.Contains(item.Key)) { if (!dictionary.TryGetValue(item.Key, out var value)) { list.Add(item.Key); } else if ((StrictVersions.Value || hashSet3.Contains(item.Key)) && value != item.Value) { list2.Add(item.Key + " (you have " + value + ", server has " + item.Value + ")"); } } } foreach (KeyValuePair<string, string> item2 in dictionary) { if (!dictionary2.ContainsKey(item2.Key) && !hashSet2.Contains(item2.Key)) { list3.Add(item2.Key); } } if (list.Count == 0 && list2.Count == 0 && list3.Count == 0) { return null; } List<string> list4 = new List<string>(); if (list.Count > 0) { list4.Add("Missing: " + string.Join(", ", list.ToArray())); } if (list2.Count > 0) { list4.Add("Wrong version: " + string.Join(", ", list2.ToArray())); } if (list3.Count > 0) { list4.Add("Not allowed here: " + string.Join(", ", list3.ToArray())); } return string.Join("\n", list4.ToArray()); } private static void ReceiveManifest(ZRpc rpc, string manifest) { string hostName = rpc.GetSocket().GetHostName(); string text = Judge(manifest); if (text == null) { Debug.Log((object)("[TimegatedProgression] ModGuard: " + hostName + " matches the server's mod set.")); return; } if (Enabled == null || !Enabled.Value) { Debug.LogWarning((object)("[TimegatedProgression] ModGuard is OFF (audit only). " + hostName + " would have been refused:\n" + text)); return; } Failures[rpc] = text; Debug.LogWarning((object)("[TimegatedProgression] ModGuard refusing " + hostName + ":\n" + text)); } } [BepInPlugin("TimegatedProgression", "TimegatedProgression", "0.7.5")] [BepInProcess("valheim.exe")] [BepInProcess("valheim_server.exe")] public class Plugin : BaseUnityPlugin { internal class Gate { public int Order; public string Biome; public ConfigEntry<bool> Enabled; public ConfigEntry<string> UnlockAt; public ConfigEntry<string> LockedMessage; public ConfigEntry<string> Announcement; } public const string Guid = "TimegatedProgression"; public const string Name = "TimegatedProgression"; public const string Version = "0.7.5"; private const string AnnounceRpc = "TimegatedProgression_Announce"; private const string DefaultLockedMessage = "The altar lies silent. This foe will not answer your call until {time}."; private const string DefaultAnnouncement = "The ravens stir. A great foe of the {biome} may now be challenged."; internal static Plugin Instance; internal ConfigSync Sync; internal CustomSyncedValue<string> UnlockedOrders; internal readonly Dictionary<int, Gate> Gates = new Dictionary<int, Gate>(); private ConfigEntry<bool> lockConfig; private ConfigEntry<string> timeZoneId; private ConfigEntry<string> timeZoneLabel; private ConfigEntry<int> utcOffsetHours; private ConfigEntry<int> checkIntervalSeconds; private ConfigEntry<bool> blockUnlisted; private ConfigEntry<bool> blockSiteGates; private ConfigEntry<int> messageLineLength; private ConfigEntry<int> messageSizeAdjust; private ConfigEntry<float> announcementHoldSeconds; private ConfigEntry<string> discordWebhook; private ConfigEntry<int> discordNotifyMinutesBefore; private Harmony harmony; private DateTimeOffset lastCheckUtc = DateTimeOffset.MinValue; private FileSystemWatcher configWatcher; private volatile bool configDirty; private float lastReloadTime; private readonly HashSet<int> warnedUnlisted = new HashSet<int>(); private static readonly Dictionary<string, string> TimeZoneAliases = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase) { { "Central Standard Time", "America/Chicago" }, { "Eastern Standard Time", "America/New_York" }, { "Mountain Standard Time", "America/Denver" }, { "Pacific Standard Time", "America/Los_Angeles" }, { "UTC", "Etc/UTC" } }; private void Awake() { //IL_02c7: Unknown result type (might be due to invalid IL or missing references) //IL_02d1: Expected O, but got Unknown Instance = this; Sync = new ConfigSync("TimegatedProgression") { DisplayName = "TimegatedProgression", CurrentVersion = "0.7.5", MinimumRequiredVersion = "0.7.5", ModRequired = true }; lockConfig = ((BaseUnityPlugin)this).Config.Bind<bool>("1 - General", "Lock Configuration", true, "If on, only server admins can change these settings. [Synced with Server]"); Sync.AddLockingConfigEntry<bool>(lockConfig); timeZoneId = Synced<string>(((BaseUnityPlugin)this).Config.Bind<string>("1 - General", "TimeZone", "Central Standard Time", "Time zone the UnlockAt times are written in. Either \"Central Standard Time\" (the Windows name) or \"America/Chicago\" (the Linux name) works on both, because the one the host does not know falls through to its partner. Handles daylight saving, so CDT is covered. Leave empty to use UtcOffsetHours instead.")); utcOffsetHours = Synced<int>(((BaseUnityPlugin)this).Config.Bind<int>("1 - General", "UtcOffsetHours", -6, "Hours ahead of UTC, used only when TimeZone is empty or unknown to the host. -6 is CST; daylight saving is NOT applied, so unlocks land an hour late during CDT.")); timeZoneLabel = Synced<string>(((BaseUnityPlugin)this).Config.Bind<string>("1 - General", "TimeZoneLabel", "CST", "Written in brackets after a time, for example CST. Leave it empty to show no label at all. This is only a label: the times themselves come from TimeZone, so during daylight saving the clock shifts but this text does not.")); checkIntervalSeconds = Synced<int>(((BaseUnityPlugin)this).Config.Bind<int>("1 - General", "CheckIntervalSeconds", 60, "How often the server re-checks the schedule. Also how late an unlock announcement can be.")); blockUnlisted = Synced<bool>(((BaseUnityPlugin)this).Config.Bind<bool>("1 - General", "BlockUnlistedBosses", false, "Block any boss that has no numbered section below, such as bosses added by other mods.")); blockSiteGates = Synced<bool>(((BaseUnityPlugin)this).Config.Bind<bool>("1 - General", "BlockSiteGates", true, "Some boss sites need an offering at a second altar before the boss can be summoned at all. Keep this on so that step follows the same schedule as its boss. Turning it off locks only the summon itself, which lets players spend those materials early.")); messageLineLength = Synced<int>(((BaseUnityPlugin)this).Config.Bind<int>("1 - General", "MessageLineLength", 52, "On-screen messages are wrapped to roughly this many characters per line, because the game does not wrap them itself. Put \\n in a message to force your own line break.")); messageSizeAdjust = Synced<int>(((BaseUnityPlugin)this).Config.Bind<int>("1 - General", "MessageSizeAdjust", -8, "Points added to or taken off the font size of this mod's messages only. Negative is smaller. 0 leaves the game's own size alone. Smaller text fits more per line, so raise MessageLineLength to match.")); announcementHoldSeconds = Synced<float>(((BaseUnityPlugin)this).Config.Bind<float>("1 - General", "AnnouncementHoldSeconds", 6f, "How long an unlock announcement stays on screen. The game fades centre messages after four seconds, so anything longer is held by redrawing the message before it fades.")); discordWebhook = ((BaseUnityPlugin)this).Config.Bind<string>("1 - General", "DiscordWebhookURL", "", "Optional Discord webhook for announcements. Read by the server only and never sent to players."); discordNotifyMinutesBefore = ((BaseUnityPlugin)this).Config.Bind<int>("1 - General", "DiscordNotifyMinutesBefore", 30, "Post a Discord reminder this many minutes before an unlock (0 = off). Server only."); UnlockedOrders = new CustomSyncedValue<string>(Sync, "unlocked_orders", ""); ModGuard.Bind(this, Synced<bool>, Synced<string>); RegisterGate(1, "Meadows"); RegisterGate(2, "Black Forest"); RegisterGate(3, "Swamp"); RegisterGate(4, "Mountains"); RegisterGate(5, "Plains"); RegisterGate(6, "Mistlands"); RegisterGate(7, "Ashlands"); RegisterGate(8, "Deep North"); harmony = new Harmony("TimegatedProgression"); harmony.PatchAll(); ((MonoBehaviour)this).StartCoroutine(ServerLoop()); WatchConfigFile(); } private void WatchConfigFile() { try { string configFilePath = ((BaseUnityPlugin)this).Config.ConfigFilePath; string directoryName = Path.GetDirectoryName(configFilePath); string fileName = Path.GetFileName(configFilePath); if (!string.IsNullOrEmpty(directoryName) && !string.IsNullOrEmpty(fileName)) { configWatcher = new FileSystemWatcher(directoryName, fileName) { NotifyFilter = (NotifyFilters.FileName | NotifyFilters.Size | NotifyFilters.LastWrite) }; configWatcher.Changed += delegate { configDirty = true; }; configWatcher.Created += delegate { configDirty = true; }; configWatcher.Renamed += delegate { configDirty = true; }; configWatcher.EnableRaisingEvents = true; ((BaseUnityPlugin)this).Logger.LogInfo((object)("Watching " + configFilePath + " for edits.")); } } catch (Exception ex) { ((BaseUnityPlugin)this).Logger.LogWarning((object)("Could not watch the config file, so edits will need a restart: " + ex.Message)); } } private void Update() { if (!configDirty || Time.realtimeSinceStartup - lastReloadTime < 1f) { return; } configDirty = false; lastReloadTime = Time.realtimeSinceStartup; if (Sync != null && !Sync.IsSourceOfTruth) { return; } try { ((BaseUnityPlugin)this).Config.Reload(); lastCheckUtc = DateTimeOffset.MinValue; ((BaseUnityPlugin)this).Logger.LogInfo((object)"Config reloaded from disk; new schedule is live."); } catch (Exception ex) { ((BaseUnityPlugin)this).Logger.LogWarning((object)("Config reload failed, keeping the settings already loaded: " + ex.Message)); } } private ConfigEntry<T> Synced<T>(ConfigEntry<T> entry) { Sync.AddConfigEntry<T>(entry).SynchronizedConfig = true; return entry; } private void RegisterGate(int order, string biome) { string text = $"{order + 1} - {biome} boss"; Gates[order] = new Gate { Order = order, Biome = biome, Enabled = Synced<bool>(((BaseUnityPlugin)this).Config.Bind<bool>(text, "Enabled", true, "Lock the " + biome + " boss until UnlockAt.")), UnlockAt = Synced<string>(((BaseUnityPlugin)this).Config.Bind<string>(text, "UnlockAt", "", "When this boss may be summoned, written as yyyy-MM-dd HH:mm[:ss] in the configured time zone. Empty means it stays locked.")), LockedMessage = Synced<string>(((BaseUnityPlugin)this).Config.Bind<string>(text, "LockedMessage", "The altar lies silent. This foe will not answer your call until {time}.", "Shown to a player who tries to summon this boss too early. {time} becomes the unlock time, {biome} becomes the biome name.")), Announcement = Synced<string>(((BaseUnityPlugin)this).Config.Bind<string>(text, "UnlockedAnnouncement", "The ravens stir. A great foe of the {biome} may now be challenged.", "Shown to everyone online when this boss unlocks. {biome} becomes the biome name.")) }; } private static string AliasFor(string id) { if (TimeZoneAliases.TryGetValue(id, out var value)) { return value; } foreach (KeyValuePair<string, string> timeZoneAlias in TimeZoneAliases) { if (string.Equals(timeZoneAlias.Value, id, StringComparison.OrdinalIgnoreCase)) { return timeZoneAlias.Key; } } return null; } private TimeSpan OffsetAt(DateTime localOrUtc) { string text = timeZoneId.Value?.Trim(); if (!string.IsNullOrEmpty(text)) { try { return TimeZoneInfo.FindSystemTimeZoneById(text).GetUtcOffset(localOrUtc); } catch (Exception ex) { string text2 = AliasFor(text); if (text2 != null) { try { return TimeZoneInfo.FindSystemTimeZoneById(text2).GetUtcOffset(localOrUtc); } catch { } } ((BaseUnityPlugin)this).Logger.LogWarning((object)("TimeZone '" + text + "' not usable on this machine (" + ex.GetType().Name + ") and no known alias resolved; falling back to UtcOffsetHours, which does not follow daylight saving.")); } } return TimeSpan.FromHours(utcOffsetHours.Value); } internal bool TryGetUnlockUtc(Gate gate, out DateTimeOffset unlockUtc) { unlockUtc = default(DateTimeOffset); string text = gate.UnlockAt.Value?.Trim(); if (string.IsNullOrEmpty(text)) { return false; } string[] formats = new string[3] { "yyyy-MM-dd", "yyyy-MM-dd HH:mm", "yyyy-MM-dd HH:mm:ss" }; if (!DateTime.TryParseExact(text, formats, CultureInfo.InvariantCulture, DateTimeStyles.None, out var result)) { ((BaseUnityPlugin)this).Logger.LogWarning((object)$"Section {gate.Order + 1}: UnlockAt '{text}' is not a valid date. Use yyyy-MM-dd HH:mm."); return false; } unlockUtc = new DateTimeOffset(DateTime.SpecifyKind(result, DateTimeKind.Unspecified), OffsetAt(result)).ToUniversalTime(); return true; } internal string FormatLocal(DateTimeOffset utc) { TimeSpan offset = OffsetAt(utc.UtcDateTime); string text = ((timeZoneLabel.Value == null) ? "" : timeZoneLabel.Value.Trim()); return utc.ToOffset(offset).ToString("MMMM d, yyyy 'at' HH:mm", CultureInfo.InvariantCulture) + ((text.Length > 0) ? (" (" + text + ")") : ""); } internal string FormatForScreen(string text) { if (string.IsNullOrEmpty(text)) { return text; } int num = Math.Max(20, messageLineLength.Value); StringBuilder stringBuilder = new StringBuilder(); string[] array = text.Replace("\\n", "\n").Split(new char[1] { '\n' }); foreach (string obj in array) { int num2 = 0; bool flag = true; string[] array2 = obj.Split(new char[1] { ' ' }); foreach (string text2 in array2) { if (text2.Length != 0) { if (!flag && num2 + 1 + text2.Length > num) { stringBuilder.Append('\n'); num2 = 0; flag = true; } if (!flag) { stringBuilder.Append(' '); num2++; } stringBuilder.Append(text2); num2 += text2.Length; flag = false; } } stringBuilder.Append('\n'); } string text3 = stringBuilder.ToString().TrimEnd(new char[1] { '\n' }); int value = messageSizeAdjust.Value; if (value != 0) { text3 = "<size=" + ((value > 0) ? "+" : "") + value.ToString(CultureInfo.InvariantCulture) + ">" + text3 + "</size>"; } return text3; } internal IEnumerator HoldAnnouncement(string text) { float wait; for (float shownFor = 4f; shownFor < announcementHoldSeconds.Value; shownFor += wait) { wait = Math.Min(3.5f, announcementHoldSeconds.Value - shownFor); yield return (object)new WaitForSeconds(wait); if ((Object)(object)MessageHud.instance == (Object)null) { break; } MessageHud.instance.ShowMessage((MessageType)2, text, 0, (Sprite)null, false, false); } } private IEnumerator ServerLoop() { while ((Object)(object)ZNet.instance == (Object)null || !ZNet.instance.IsServer() || (Object)(object)ZoneSystem.instance == (Object)null) { yield return (object)new WaitForSeconds(1f); } while (true) { try { Evaluate(); } catch (Exception ex) { ((BaseUnityPlugin)this).Logger.LogError((object)("Schedule check failed: " + ex)); } yield return (object)new WaitForSeconds((float)Mathf.Max(5, checkIntervalSeconds.Value)); } } private void Evaluate() { DateTimeOffset utcNow = DateTimeOffset.UtcNow; List<int> list = new List<int>(); foreach (Gate item in Gates.Values.OrderBy((Gate g) => g.Order)) { bool flag = BossTable.IsDefeated(item.Order, null); if (!item.Enabled.Value || flag) { list.Add(item.Order); } else { if (!TryGetUnlockUtc(item, out var unlockUtc)) { continue; } if (utcNow >= unlockUtc) { list.Add(item.Order); if (Crossed(unlockUtc, utcNow)) { string text = item.Announcement.Value.Replace("{biome}", item.Biome).Replace("{time}", FormatLocal(unlockUtc)); Announce(text); SendDiscord(text); } } else if (discordNotifyMinutesBefore.Value > 0 && Crossed(unlockUtc.AddMinutes(-discordNotifyMinutesBefore.Value), utcNow)) { SendDiscord("The " + item.Biome + " boss unlocks at " + FormatLocal(unlockUtc) + "."); } } } string text2 = string.Join(",", list.Select((int o) => o.ToString(CultureInfo.InvariantCulture)).ToArray()); if (UnlockedOrders.Value != text2) { UnlockedOrders.Value = text2; } lastCheckUtc = utcNow; } private bool Crossed(DateTimeOffset moment, DateTimeOffset now) { if (lastCheckUtc != DateTimeOffset.MinValue && moment > lastCheckUtc) { return moment <= now; } return false; } private void Announce(string text) { if (ZRoutedRpc.instance != null) { ZRoutedRpc.instance.InvokeRoutedRPC(0L, "TimegatedProgression_Announce", new object[1] { text }); ((BaseUnityPlugin)this).Logger.LogInfo((object)("Announced: " + text)); } } private void SendDiscord(string message) { string text = discordWebhook.Value?.Trim(); if (string.IsNullOrEmpty(text)) { return; } string data = "{\"content\": \"" + message.Replace("\\", "\\\\").Replace("\"", "\\\"").Replace("\r", "\\r") .Replace("\n", "\\n") + "\"}"; try { using WebClient webClient = new WebClient(); webClient.Headers[HttpRequestHeader.ContentType] = "application/json"; webClient.Encoding = Encoding.UTF8; webClient.UploadString(text, "POST", data); } catch (Exception ex) { ((BaseUnityPlugin)this).Logger.LogError((object)("Discord post failed: " + ex.Message)); } } internal bool IsUnlocked(int order) { string value = UnlockedOrders.Value; if (string.IsNullOrEmpty(value)) { return false; } string[] array = value.Split(new char[1] { ',' }); for (int i = 0; i < array.Length; i++) { if (int.TryParse(array[i], NumberStyles.Integer, CultureInfo.InvariantCulture, out var result) && result == order) { return true; } } return false; } internal bool ShouldBlock(OfferingBowl bowl, out string message) { message = null; if ((Object)(object)bowl == (Object)null) { return false; } Character val = (((Object)(object)bowl.m_bossPrefab != (Object)null) ? bowl.m_bossPrefab.GetComponent<Character>() : null); int order; if ((Object)(object)val != (Object)null && val.m_boss) { order = BossTable.ResolveOrder(val); if (BossTable.IsDefeated(order, val)) { return false; } } else { if (!blockSiteGates.Value || !BossTable.TryResolveSiteGate(bowl, out order)) { return false; } if (BossTable.IsDefeated(order, null)) { return false; } if ((Object)(object)ZoneSystem.instance != (Object)null && ZoneSystem.instance.GetGlobalKey(bowl.m_setGlobalKey)) { return false; } } if (!Gates.TryGetValue(order, out var value)) { if (warnedUnlisted.Add(order)) { ((BaseUnityPlugin)this).Logger.LogWarning((object)($"Boss with kill order {order} has no section in the config; " + (blockUnlisted.Value ? "blocking it (BlockUnlistedBosses is on)." : "allowing it."))); } if (!blockUnlisted.Value) { return false; } message = FormatForScreen("The altar lies silent. This foe will not answer your call until {time}.".Replace("{time}", "some time").Replace("{biome}", "this land")); return true; } if (!value.Enabled.Value || IsUnlocked(order)) { return false; } message = FormatForScreen(value.LockedMessage.Value.Replace("{biome}", value.Biome).Replace("{time}", TryGetUnlockUtc(value, out var unlockUtc) ? FormatLocal(unlockUtc) : "some time")); return true; } } internal static class BossTable { private static readonly Dictionary<string, int> OrderByPrefab = new Dictionary<string, int>(); private static readonly Dictionary<string, int> OrderByOfferingItem = new Dictionary<string, int>(); private static readonly Dictionary<int, HashSet<string>> KeysByOrder = new Dictionary<int, HashSet<string>>(); private static bool scanned; internal static int ResolveOrder(Character boss) { if ((Object)(object)boss == (Object)null || !boss.m_boss) { return 0; } if (boss.m_bossOrder > 0) { return boss.m_bossOrder; } ScanOnce(); string key = ((Object)((Component)boss).gameObject).name.Replace("(Clone)", ""); if (!OrderByPrefab.TryGetValue(key, out var value)) { return 0; } return value; } internal static bool TryResolveSiteGate(OfferingBowl bowl, out int order) { order = 0; if ((Object)(object)bowl == (Object)null || (Object)(object)bowl.m_bossItem == (Object)null || string.IsNullOrEmpty(bowl.m_setGlobalKey)) { return false; } ScanOnce(); if (OrderByOfferingItem.TryGetValue(bowl.m_bossItem.m_itemData.m_shared.m_name, out order)) { return order > 0; } return false; } internal static bool IsDefeated(int order, Character boss) { if ((Object)(object)ZoneSystem.instance == (Object)null) { return false; } if ((Object)(object)boss != (Object)null && !string.IsNullOrEmpty(boss.m_defeatSetGlobalKey) && ZoneSystem.instance.GetGlobalKey(boss.m_defeatSetGlobalKey)) { return true; } ScanOnce(); if (!KeysByOrder.TryGetValue(order, out var value)) { return false; } foreach (string item in value) { if (ZoneSystem.instance.GetGlobalKey(item)) { return true; } } return false; } private static void AddKey(int order, string key) { if (order > 0 && !string.IsNullOrEmpty(key)) { if (!KeysByOrder.TryGetValue(order, out var value)) { value = (KeysByOrder[order] = new HashSet<string>()); } value.Add(key); } } private static void ScanOnce() { if (scanned || (Object)(object)ZNetScene.instance == (Object)null) { return; } List<Character> list = new List<Character>(); List<OfferingBowl> list2 = new List<OfferingBowl>(); foreach (GameObject prefab in ZNetScene.instance.m_prefabs) { if (!((Object)(object)prefab == (Object)null)) { Character component = prefab.GetComponent<Character>(); if ((Object)(object)component != (Object)null && component.m_boss) { list.Add(component); } list2.AddRange(prefab.GetComponentsInChildren<OfferingBowl>(true)); } } foreach (Character item in list) { if (item.m_bossOrder > 0) { OrderByPrefab[((Object)((Component)item).gameObject).name] = item.m_bossOrder; AddKey(item.m_bossOrder, item.m_defeatSetGlobalKey); } } foreach (Character item2 in list) { if (item2.m_bossOrder > 0) { continue; } string name = ((Object)((Component)item2).gameObject).name; foreach (Character item3 in list) { if (item3.m_bossOrder > 0 && ((Object)((Component)item3).gameObject).name.StartsWith(name + "_", StringComparison.Ordinal)) { OrderByPrefab[name] = item3.m_bossOrder; AddKey(item3.m_bossOrder, item2.m_defeatSetGlobalKey); break; } } } foreach (OfferingBowl item4 in list2) { if ((Object)(object)item4 == (Object)null || (Object)(object)item4.m_bossItem == (Object)null || (Object)(object)item4.m_bossPrefab == (Object)null) { continue; } Character component2 = item4.m_bossPrefab.GetComponent<Character>(); if (!((Object)(object)component2 == (Object)null) && component2.m_boss) { int value; int num = ((component2.m_bossOrder > 0) ? component2.m_bossOrder : (OrderByPrefab.TryGetValue(((Object)((Component)component2).gameObject).name, out value) ? value : 0)); if (num > 0) { OrderByOfferingItem[item4.m_bossItem.m_itemData.m_shared.m_name] = num; } } } scanned = true; } } [HarmonyPatch(typeof(ZNetScene), "Awake")] internal static class RegisterAnnounceRpc { private static void Postfix() { if (ZRoutedRpc.instance != null) { ZRoutedRpc.instance.Register<string>("TimegatedProgression_Announce", (Action<long, string>)OnAnnounce); } } private static void OnAnnounce(long sender, string message) { if (!((Object)(object)MessageHud.instance == (Object)null)) { Plugin instance = Plugin.Instance; string text = (((Object)(object)instance != (Object)null) ? instance.FormatForScreen(message) : message); MessageHud.instance.ShowMessage((MessageType)2, text, 0, (Sprite)null, false, true); if ((Object)(object)instance != (Object)null) { ((MonoBehaviour)instance).StartCoroutine(instance.HoldAnnouncement(text)); } } } } [HarmonyPatch(typeof(OfferingBowl), "UseItem")] internal static class BlockEarlyOffering { private static bool Prefix(OfferingBowl __instance, Humanoid user, ItemData item, ref bool __result) { Plugin instance = Plugin.Instance; if ((Object)(object)instance == (Object)null || (Object)(object)user == (Object)null || item == null || __instance.m_useItemStands) { return true; } if ((Object)(object)__instance.m_bossItem == (Object)null || item.m_shared.m_name != __instance.m_bossItem.m_itemData.m_shared.m_name) { return true; } if (!instance.ShouldBlock(__instance, out var message)) { return true; } ((Character)user).Message((MessageType)2, message, 0, (Sprite)null, false); __result = true; return false; } } [HarmonyPatch(typeof(OfferingBowl), "Interact")] internal static class BlockEarlyInteract { private static bool Prefix(OfferingBowl __instance, Humanoid user, bool hold, ref bool __result) { Plugin instance = Plugin.Instance; if ((Object)(object)instance == (Object)null || (Object)(object)user == (Object)null || hold || !__instance.m_useItemStands) { return true; } if (!instance.ShouldBlock(__instance, out var message)) { return true; } ((Character)user).Message((MessageType)2, message, 0, (Sprite)null, false); __result = false; return false; } } } 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; } } } namespace ServerSync { [PublicAPI] internal abstract class OwnConfigEntryBase { public object? LocalBaseValue; public bool SynchronizedConfig = true; public abstract ConfigEntryBase BaseConfig { get; } } [PublicAPI] internal class SyncedConfigEntry<T>(ConfigEntry<T> sourceConfig) : OwnConfigEntryBase() { public readonly ConfigEntry<T> SourceConfig = sourceConfig; public override ConfigEntryBase BaseConfig => (ConfigEntryBase)(object)SourceConfig; public T Value { get { return SourceConfig.Value; } set { SourceConfig.Value = value; } } public void AssignLocalValue(T value) { if (LocalBaseValue == null) { Value = value; } else { LocalBaseValue = value; } } } internal abstract class CustomSyncedValueBase { public object? LocalBaseValue; public readonly string Identifier; public readonly Type Type; private object? boxedValue; protected bool localIsOwner; public readonly int Priority; public object? BoxedValue { get { return boxedValue; } set { boxedValue = value; this.ValueChanged?.Invoke(); } } public event Action? ValueChanged; protected CustomSyncedValueBase(ConfigSync configSync, string identifier, Type type, int priority) { Priority = priority; Identifier = identifier; Type = type; configSync.AddCustomValue(this); localIsOwner = configSync.IsSourceOfTruth; configSync.SourceOfTruthChanged += delegate(bool truth) { localIsOwner = truth; }; } } [PublicAPI] internal sealed class CustomSyncedValue<T> : CustomSyncedValueBase { public T Value { get { return (T)base.BoxedValue; } set { base.BoxedValue = value; } } public CustomSyncedValue(ConfigSync configSync, string identifier, T value = default(T), int priority = 0) : base(configSync, identifier, typeof(T), priority) { Value = value; } public void AssignLocalValue(T value) { if (localIsOwner) { Value = value; } else { LocalBaseValue = value; } } } internal class ConfigurationManagerAttributes { [UsedImplicitly] public bool? ReadOnly = false; } [PublicAPI] internal class ConfigSync { [HarmonyPatch(typeof(ZRpc), "HandlePackage")] private static class SnatchCurrentlyHandlingRPC { public static ZRpc? currentRpc; [HarmonyPrefix] private static void Prefix(ZRpc __instance) { currentRpc = __instance; } } [HarmonyPatch(typeof(ZNet), "Awake")] internal static class RegisterRPCPatch { [HarmonyPostfix] private static void Postfix(ZNet __instance) { isServer = __instance.IsServer(); foreach (ConfigSync configSync2 in configSyncs) { ZRoutedRpc.instance.Register<ZPackage>(configSync2.Name + " ConfigSync", (Action<long, ZPackage>)configSync2.RPC_FromOtherClientConfigSync); if (isServer) { configSync2.InitialSyncDone = true; Debug.Log((object)("Registered '" + configSync2.Name + " ConfigSync' RPC - waiting for incoming connections")); } } if (isServer) { ((MonoBehaviour)__instance).StartCoroutine(WatchAdminListChanges()); } static void SendAdmin(List<ZNetPeer> peers, bool isAdmin) { ZPackage package = ConfigsToPackage(null, null, new PackageEntry[1] { new PackageEntry { section = "Internal", key = "lockexempt", type = typeof(bool), value = isAdmin } }); ConfigSync configSync = configSyncs.First(); if (configSync != null) { ((MonoBehaviour)ZNet.instance).StartCoroutine(configSync.sendZPackage(peers, package)); } } static IEnumerator WatchAdminListChanges() { MethodInfo listContainsId = AccessTools.DeclaredMethod(typeof(ZNet), "ListContainsId", (Type[])null, (Type[])null); SyncedList adminList = (SyncedList)AccessTools.DeclaredField(typeof(ZNet), "m_adminList").GetValue(ZNet.instance); List<string> CurrentList = new List<string>(adminList.GetList()); while (true) { yield return (object)new WaitForSeconds(30f); if (!adminList.GetList().SequenceEqual(CurrentList)) { CurrentList = new List<string>(adminList.GetList()); List<ZNetPeer> adminPeer = ZNet.instance.GetPeers().Where(delegate(ZNetPeer p) { string hostName = p.m_rpc.GetSocket().GetHostName(); return ((object)listContainsId == null) ? adminList.Contains(hostName) : ((bool)listContainsId.Invoke(ZNet.instance, new object[2] { adminList, hostName })); }).ToList(); List<ZNetPeer> nonAdminPeer = ZNet.instance.GetPeers().Except(adminPeer).ToList(); SendAdmin(nonAdminPeer, isAdmin: false); SendAdmin(adminPeer, isAdmin: true); } } } } } [HarmonyPatch(typeof(ZNet), "OnNewConnection")] private static class RegisterClientRPCPatch { [HarmonyPostfix] private static void Postfix(ZNet __instance, ZNetPeer peer) { if (__instance.IsServer()) { return; } foreach (ConfigSync configSync in configSyncs) { peer.m_rpc.Register<ZPackage>(configSync.Name + " ConfigSync", (Action<ZRpc, ZPackage>)configSync.RPC_FromServerConfigSync); } } } private class ParsedConfigs { public readonly Dictionary<OwnConfigEntryBase, object?> configValues = new Dictionary<OwnConfigEntryBase, object>(); public readonly Dictionary<CustomSyncedValueBase, object?> customValues = new Dictionary<CustomSyncedValueBase, object>(); } [HarmonyPatch(typeof(ZNet), "Shutdown")] private class ResetConfigsOnShutdown { [HarmonyPostfix] private static void Postfix() { ProcessingServerUpdate = true; foreach (ConfigSync configSync in configSyncs) { configSync.resetConfigsFromServer(); configSync.IsSourceOfTruth = true; configSync.InitialSyncDone = false; } ProcessingServerUpdate = false; } } [HarmonyPatch(typeof(ZNet), "RPC_PeerInfo")] private class SendConfigsAfterLogin { private class BufferingSocket : ZPlayFabSocket, ISocket { public volatile bool finished = false; public volatile int versionMatchQueued = -1; public readonly List<ZPackage> Package = new List<ZPackage>(); public readonly ISocket Original; public BufferingSocket(ISocket original) { Original = original; ((ZPlayFabSocket)this)..ctor(); } public bool IsConnected() { return Original.IsConnected(); } public ZPackage Recv() { return Original.Recv(); } public int GetSendQueueSize() { return Original.GetSendQueueSize(); } public int GetCurrentSendRate() { return Original.GetCurrentSendRate(); } public bool IsHost() { return Original.IsHost(); } public void Dispose() { Original.Dispose(); } public bool GotNewData() { return Original.GotNewData(); } public void Close() { Original.Close(); } public string GetEndPointString() { return Original.GetEndPointString(); } public void GetAndResetStats(out int totalSent, out int totalRecv) { Original.GetAndResetStats(ref totalSent, ref totalRecv); } public void GetConnectionQuality(out float localQuality, out float remoteQuality, out int ping, out float outByteSec, out float inByteSec) { Original.GetConnectionQuality(ref localQuality, ref remoteQuality, ref ping, ref outByteSec, ref inByteSec); } public ISocket Accept() { return Original.Accept(); } public int GetHostPort() { return Original.GetHostPort(); } public bool Flush() { return Original.Flush(); } public string GetHostName() { return Original.GetHostName(); } public void VersionMatch() { if (finished) { Original.VersionMatch(); } else { versionMatchQueued = Package.Count; } } public void Send(ZPackage pkg) { //IL_0057: Unknown result type (might be due to invalid IL or missing references) //IL_005d: Expected O, but got Unknown int pos = pkg.GetPos(); pkg.SetPos(0); int num = pkg.ReadInt(); if ((num == StringExtensionMethods.GetStableHashCode("PeerInfo") || num == StringExtensionMethods.GetStableHashCode("RoutedRPC") || num == StringExtensionMethods.GetStableHashCode("ZDOData")) && !finished) { ZPackage val = new ZPackage(pkg.GetArray()); val.SetPos(pos); Package.Add(val); } else { pkg.SetPos(pos); Original.Send(pkg); } } } [HarmonyPriority(800)] [HarmonyPrefix] private static void Prefix(ref Dictionary<Assembly, BufferingSocket>? __state, ZNet __instance, ZRpc rpc) { //IL_0078: Unknown result type (might be due to invalid IL or missing references) //IL_007e: Invalid comparison between Unknown and I4 if (!__instance.IsServer()) { return; } BufferingSocket bufferingSocket = new BufferingSocket(rpc.GetSocket()); AccessTools.DeclaredField(typeof(ZRpc), "m_socket").SetValue(rpc, bufferingSocket); object? obj = AccessTools.DeclaredMethod(typeof(ZNet), "GetPeer", new Type[1] { typeof(ZRpc) }, (Type[])null).Invoke(__instance, new object[1] { rpc }); ZNetPeer val = (ZNetPeer)((obj is ZNetPeer) ? obj : null); if (val != null && (int)ZNet.m_onlineBackend > 0) { FieldInfo fieldInfo = AccessTools.DeclaredField(typeof(ZNetPeer), "m_socket"); object? value = fieldInfo.GetValue(val); ZPlayFabSocket val2 = (ZPlayFabSocket)((value is ZPlayFabSocket) ? value : null); if (val2 != null) { typeof(ZPlayFabSocket).GetField("m_remotePlayerId").SetValue(bufferingSocket, val2.m_remotePlayerId); } fieldInfo.SetValue(val, bufferingSocket); } if (__state == null) { __state = new Dictionary<Assembly, BufferingSocket>(); } __state[Assembly.GetExecutingAssembly()] = bufferingSocket; } [HarmonyPostfix] private static void Postfix(Dictionary<Assembly, BufferingSocket> __state, ZNet __instance, ZRpc rpc) { ZNetPeer peer; if (__instance.IsServer()) { object obj = AccessTools.DeclaredMethod(typeof(ZNet), "GetPeer", new Type[1] { typeof(ZRpc) }, (Type[])null).Invoke(__instance, new object[1] { rpc }); peer = (ZNetPeer)((obj is ZNetPeer) ? obj : null); if (peer == null) { SendBufferedData(); } else { ((MonoBehaviour)__instance).StartCoroutine(sendAsync()); } } void SendBufferedData() { if (rpc.GetSocket() is BufferingSocket bufferingSocket) { AccessTools.DeclaredField(typeof(ZRpc), "m_socket").SetValue(rpc, bufferingSocket.Original); object? obj2 = AccessTools.DeclaredMethod(typeof(ZNet), "GetPeer", new Type[1] { typeof(ZRpc) }, (Type[])null).Invoke(__instance, new object[1] { rpc }); ZNetPeer val = (ZNetPeer)((obj2 is ZNetPeer) ? obj2 : null); if (val != null) { AccessTools.DeclaredField(typeof(ZNetPeer), "m_socket").SetValue(val, bufferingSocket.Original); } } BufferingSocket bufferingSocket2 = __state[Assembly.GetExecutingAssembly()]; bufferingSocket2.finished = true; for (int i = 0; i < bufferingSocket2.Package.Count; i++) { if (i == bufferingSocket2.versionMatchQueued) { bufferingSocket2.Original.VersionMatch(); } bufferingSocket2.Original.Send(bufferingSocket2.Package[i]); } if (bufferingSocket2.Package.Count == bufferingSocket2.versionMatchQueued) { bufferingSocket2.Original.VersionMatch(); } } IEnumerator sendAsync() { foreach (ConfigSync configSync in configSyncs) { List<PackageEntry> entries = new List<PackageEntry>(); if (configSync.CurrentVersion != null) { entries.Add(new PackageEntry { section = "Internal", key = "serverversion", type = typeof(string), value = configSync.CurrentVersion }); } MethodInfo listContainsId = AccessTools.DeclaredMethod(typeof(ZNet), "ListContainsId", (Type[])null, (Type[])null); SyncedList adminList = (SyncedList)AccessTools.DeclaredField(typeof(ZNet), "m_adminList").GetValue(ZNet.instance); entries.Add(new PackageEntry { section = "Internal", key = "lockexempt", type = typeof(bool), value = (((object)listContainsId == null) ? ((object)adminList.Contains(rpc.GetSocket().GetHostName())) : listContainsId.Invoke(ZNet.instance, new object[2] { adminList, rpc.GetSocket().GetHostName() })) }); ZPackage package = ConfigsToPackage(configSync.allConfigs.Select((OwnConfigEntryBase c) => c.BaseConfig), configSync.allCustomValues, entries, partial: false); yield return ((MonoBehaviour)__instance).StartCoroutine(configSync.sendZPackage(new List<ZNetPeer> { peer }, package)); } SendBufferedData(); } } } private class PackageEntry { public string section = null; public string key = null; public Type type = null; public object? value; } [HarmonyPatch(typeof(ConfigEntryBase), "GetSerializedValue")] private static class PreventSavingServerInfo { [HarmonyPrefix] private static bool Prefix(ConfigEntryBase __instance, ref string __result) { OwnConfigEntryBase ownConfigEntryBase = configData(__instance); if (ownConfigEntryBase == null || isWritableConfig(ownConfigEntryBase)) { return true; } __result = TomlTypeConverter.ConvertToString(ownConfigEntryBase.LocalBaseValue, __instance.SettingType); return false; } } [HarmonyPatch(typeof(ConfigEntryBase), "SetSerializedValue")] private static class PreventConfigRereadChangingValues { [HarmonyPrefix] private static bool Prefix(ConfigEntryBase __instance, string value) { OwnConfigEntryBase ownConfigEntryBase = configData(__instance); if (ownConfigEntryBase == null || ownConfigEntryBase.LocalBaseValue == null) { return true; } try { ownConfigEntryBase.LocalBaseValue = TomlTypeConverter.ConvertToValue(value, __instance.SettingType); } catch (Exception ex) { Debug.LogWarning((object)$"Config value of setting \"{__instance.Definition}\" could not be parsed and will be ignored. Reason: {ex.Message}; Value: {value}"); } return false; } } private class InvalidDeserializationTypeException : Exception { public string expected = null; public string received = null; public string field = ""; } public static bool ProcessingServerUpdate; public readonly string Name; public string? DisplayName; public string? CurrentVersion; public string? MinimumRequiredVersion; public bool ModRequired = false; private bool? forceConfigLocking; private bool isSourceOfTruth = true; private static readonly HashSet<ConfigSync> configSyncs; private readonly HashSet<OwnConfigEntryBase> allConfigs = new HashSet<OwnConfigEntryBase>(); private HashSet<CustomSyncedValueBase> allCustomValues = new HashSet<CustomSyncedValueBase>(); private static bool isServer; private static bool lockExempt; private OwnConfigEntryBase? lockedConfig = null; private const byte PARTIAL_CONFIGS = 1; private const byte FRAGMENTED_CONFIG = 2; private const byte COMPRESSED_CONFIG = 4; private readonly Dictionary<string, SortedDictionary<int, byte[]>> configValueCache = new Dictionary<string, SortedDictionary<int, byte[]>>(); private readonly List<KeyValuePair<long, string>> cacheExpirations = new List<KeyValuePair<long, string>>(); private static long packageCounter; public bool IsLocked { get { bool? flag = forceConfigLocking; bool num; if (!flag.HasValue) { if (lockedConfig == null) { goto IL_0052; } num = ((IConvertible)lockedConfig.BaseConfig.BoxedValue).ToInt32(CultureInfo.InvariantCulture) != 0; } else { num = flag == true; } if (!num) { goto IL_0052; } int result = ((!lockExempt) ? 1 : 0); goto IL_0053; IL_0052: result = 0; goto IL_0053; IL_0053: return (byte)result != 0; } set { forceConfigLocking = value; } } public bool IsAdmin => lockExempt || isSourceOfTruth; public bool IsSourceOfTruth { get { return isSourceOfTruth; } private set { if (value != isSourceOfTruth) { isSourceOfTruth = value; this.SourceOfTruthChanged?.Invoke(value); } } } public bool InitialSyncDone { get; private set; } = false; public event Action<bool>? SourceOfTruthChanged; private event Action? lockedConfigChanged; static ConfigSync() { ProcessingServerUpdate = false; configSyncs = new HashSet<ConfigSync>(); lockExempt = false; packageCounter = 0L; RuntimeHelpers.RunClassConstructor(typeof(VersionCheck).TypeHandle); } public ConfigSync(string name) { Name = name; configSyncs.Add(this); new VersionCheck(this); } public SyncedConfigEntry<T> AddConfigEntry<T>(ConfigEntry<T> configEntry) { OwnConfigEntryBase ownConfigEntryBase = configData((ConfigEntryBase)(object)configEntry); SyncedConfigEntry<T> syncedEntry = ownConfigEntryBase as SyncedConfigEntry<T>; if (syncedEntry == null) { syncedEntry = new SyncedConfigEntry<T>(configEntry); AccessTools.DeclaredField(typeof(ConfigDescription), "<Tags>k__BackingField").SetValue(((ConfigEntryBase)configEntry).Description, new object[1] { new ConfigurationManagerAttributes() }.Concat(((ConfigEntryBase)configEntry).Description.Tags ?? Array.Empty<object>()).Concat(new SyncedConfigEntry<T>[1] { syncedEntry }).ToArray()); configEntry.SettingChanged += delegate { if (!ProcessingServerUpdate && syncedEntry.SynchronizedConfig) { Broadcast(0L, (ConfigEntryBase)configEntry); } }; allConfigs.Add(syncedEntry); } return syncedEntry; } public SyncedConfigEntry<T> AddLockingConfigEntry<T>(ConfigEntry<T> lockingConfig) where T : IConvertible { if (lockedConfig != null) { throw new Exception("Cannot initialize locking ConfigEntry twice"); } lockedConfig = AddConfigEntry<T>(lockingConfig); lockingConfig.SettingChanged += delegate { this.lockedConfigChanged?.Invoke(); }; return (SyncedConfigEntry<T>)lockedConfig; } internal void AddCustomValue(CustomSyncedValueBase customValue) { if (allCustomValues.Select((CustomSyncedValueBase v) => v.Identifier).Concat(new string[1] { "serverversion" }).Contains(customValue.Identifier)) { throw new Exception("Cannot have multiple settings with the same name or with a reserved name (serverversion)"); } allCustomValues.Add(customValue); allCustomValues = new HashSet<CustomSyncedValueBase>(allCustomValues.OrderByDescending((CustomSyncedValueBase v) => v.Priority)); customValue.ValueChanged += delegate { if (!ProcessingServerUpdate) { Broadcast(0L, customValue); } }; } private void RPC_FromServerConfigSync(ZRpc rpc, ZPackage package) { lockedConfigChanged += serverLockedSettingChanged; IsSourceOfTruth = false; if (HandleConfigSyncRPC(0L, package, clientUpdate: false)) { InitialSyncDone = true; } } private void RPC_FromOtherClientConfigSync(long sender, ZPackage package) { HandleConfigSyncRPC(sender, package, clientUpdate: true); } private bool HandleConfigSyncRPC(long sender, ZPackage package, bool clientUpdate) { //IL_0076: Unknown result type (might be due to invalid IL or missing references) //IL_007d: Expected O, but got Unknown //IL_0250: Unknown result type (might be due to invalid IL or missing references) //IL_0257: Expected O, but got Unknown //IL_01ea: Unknown result type (might be due to invalid IL or missing references) //IL_01f1: Expected O, but got Unknown try { if (isServer && IsLocked) { ZRpc? currentRpc = SnatchCurrentlyHandlingRPC.currentRpc; object obj; if (currentRpc == null) { obj = null; } else { ISocket socket = currentRpc.GetSocket(); obj = ((socket != null) ? socket.GetHostName() : null); } string text = (string)obj; if (text != null) { MethodInfo methodInfo = AccessTools.DeclaredMethod(typeof(ZNet), "ListContainsId", (Type[])null, (Type[])null); SyncedList val = (SyncedList)AccessTools.DeclaredField(typeof(ZNet), "m_adminList").GetValue(ZNet.instance); if (!(((object)methodInfo == null) ? val.Contains(text) : ((bool)methodInfo.Invoke(ZNet.instance, new object[2] { val, text })))) { return false; } } } cacheExpirations.RemoveAll(delegate(KeyValuePair<long, string> kv) { if (kv.Key < DateTimeOffset.Now.Ticks) { configValueCache.Remove(kv.Value); return true; } return false; }); byte b = package.ReadByte(); if ((b & 2) != 0) { long num = package.ReadLong(); string text2 = sender.ToString() + num; if (!configValueCache.TryGetValue(text2, out SortedDictionary<int, byte[]> value)) { value = new SortedDictionary<int, byte[]>(); configValueCache[text2] = value; cacheExpirations.Add(new KeyValuePair<long, string>(DateTimeOffset.Now.AddSeconds(60.0).Ticks, text2)); } int key = package.ReadInt(); int num2 = package.ReadInt(); value.Add(key, package.ReadByteArray()); if (value.Count < num2) { return false; } configValueCache.Remove(text2); package = new ZPackage(value.Values.SelectMany((byte[] a) => a).ToArray()); b = package.ReadByte(); } ProcessingServerUpdate = true; if ((b & 4) != 0) { byte[] buffer = package.ReadByteArray(); MemoryStream stream = new MemoryStream(buffer); MemoryStream memoryStream = new MemoryStream(); using (DeflateStream deflateStream = new DeflateStream(stream, CompressionMode.Decompress)) { deflateStream.CopyTo(memoryStream); } package = new ZPackage(memoryStream.ToArray()); b = package.ReadByte(); } if ((b & 1) == 0) { resetConfigsFromServer(); } ParsedConfigs parsedConfigs = ReadConfigsFromPackage(package); ConfigFile val2 = null; bool saveOnConfigSet = false; foreach (KeyValuePair<OwnConfigEntryBase, object> configValue in parsedConfigs.configValues) { if (!isServer && configValue.Key.LocalBaseValue == null) { configValue.Key.LocalBaseValue = configValue.Key.BaseConfig.BoxedValue; } if (val2 == null) { val2 = configValue.Key.BaseConfig.ConfigFile; saveOnConfigSet = val2.SaveOnConfigSet; val2.SaveOnConfigSet = false; } configValue.Key.BaseConfig.BoxedValue = configValue.Value; } if (val2 != null) { val2.SaveOnConfigSet = saveOnConfigSet; val2.Save(); } foreach (KeyValuePair<CustomSyncedValueBase, object> customValue in parsedConfigs.customValues) { if (!isServer) { CustomSyncedValueBase key2 = customValue.Key; if (key2.LocalBaseValue == null) { key2.LocalBaseValue = customValue.Key.BoxedValue; } } customValue.Key.BoxedValue = customValue.Value; } Debug.Log((object)string.Format("Received {0} configs and {1} custom values from {2} for mod {3}", parsedConfigs.configValues.Count, parsedConfigs.customValues.Count, (isServer || clientUpdate) ? $"client {sender}" : "the server", DisplayName ?? Name)); if (!isServer) { serverLockedSettingChanged(); } return true; } finally { ProcessingServerUpdate = false; } } private ParsedConfigs ReadConfigsFromPackage(ZPackage package) { ParsedConfigs parsedConfigs = new ParsedConfigs(); Dictionary<string, OwnConfigEntryBase> dictionary = allConfigs.Where((OwnConfigEntryBase c) => c.SynchronizedConfig).ToDictionary((OwnConfigEntryBase c) => c.BaseConfig.Definition.Section + "_" + c.BaseConfig.Definition.Key, (OwnConfigEntryBase c) => c); Dictionary<string, CustomSyncedValueBase> dictionary2 = allCustomValues.ToDictionary((CustomSyncedValueBase c) => c.Identifier, (CustomSyncedValueBase c) => c); int num = package.ReadInt(); for (int num2 = 0; num2 < num; num2++) { string text = package.ReadString(); string text2 = package.ReadString(); string text3 = package.ReadString(); Type type = Type.GetType(text3); if (text3 == "" || type != null) { object obj; try { obj = ((text3 == "") ? null : ReadValueWithTypeFromZPackage(package, type)); } catch (InvalidDeserializationTypeException ex) { Debug.LogWarning((object)("Got unexpected struct internal type " + ex.received + " for field " + ex.field + " struct " + text3 + " for " + text2 + " in section " + text + " for mod " + (DisplayName ?? Name) + ", expecting " + ex.expected)); continue; } OwnConfigEntryBase value2; if (text == "Internal") { CustomSyncedValueBase value; if (text2 == "serverversion") { if (obj?.ToString() != CurrentVersion) { Debug.LogWarning((object)("Received server version is not equal: server version = " + (obj?.ToString() ?? "null") + "; local version = " + (CurrentVersion ?? "unknown"))); } } else if (text2 == "lockexempt") { if (obj is bool flag) { lockExempt = flag; } } else if (dictionary2.TryGetValue(text2, out value)) { if ((text3 == "" && (!value.Type.IsValueType || Nullable.GetUnderlyingType(value.Type) != null)) || GetZPackageTypeString(value.Type) == text3) { parsedConfigs.customValues[value] = obj; continue; } Debug.LogWarning((object)("Got unexpected type " + text3 + " for internal value " + text2 + " for mod " + (DisplayName ?? Name) + ", expecting " + value.Type.AssemblyQualifiedName)); } } else if (dictionary.TryGetValue(text + "_" + text2, out value2)) { Type type2 = configType(value2.BaseConfig); if ((text3 == "" && (!type2.IsValueType || Nullable.GetUnderlyingType(type2) != null)) || GetZPackageTypeString(type2) == text3) { parsedConfigs.configValues[value2] = obj; continue; } Debug.LogWarning((object)("Got unexpected type " + text3 + " for " + text2 + " in section " + text + " for mod " + (DisplayName ?? Name) + ", expecting " + type2.AssemblyQualifiedName)); } else { Debug.LogWarning((object)("Received unknown config entry " + text2 + " in section " + text + " for mod " + (DisplayName ?? Name) + ". This may happen if client and server versions of the mod do not match.")); } continue; } Debug.LogWarning((object)("Got invalid type " + text3 + ", abort reading of received configs")); return new ParsedConfigs(); } return parsedConfigs; } private static bool isWritableConfig(OwnConfigEntryBase config) { ConfigSync configSync = configSyncs.FirstOrDefault((ConfigSync cs) => cs.allConfigs.Contains(config)); if (configSync == null) { return true; } return configSync.IsSourceOfTruth || !config.SynchronizedConfig || config.LocalBaseValue == null || (!configSync.IsLocked && (config != configSync.lockedConfig || lockExempt)); } private void serverLockedSettingChanged() { foreach (OwnConfigEntryBase allConfig in allConfigs) { configAttribute<ConfigurationManagerAttributes>(allConfig.BaseConfig).ReadOnly = !isWritableConfig(allConfig); } } private void resetConfigsFromServer() { ConfigFile val = null; bool saveOnConfigSet = false; foreach (OwnConfigEntryBase item in allConfigs.Where((OwnConfigEntryBase config) => config.LocalBaseValue != null)) { if (val == null) { val = item.BaseConfig.ConfigFile; saveOnConfigSet = val.SaveOnConfigSet; val.SaveOnConfigSet = false; } item.BaseConfig.BoxedValue = item.LocalBaseValue; item.LocalBaseValue = null; } if (val != null) { val.SaveOnConfigSet = saveOnConfigSet; } foreach (CustomSyncedValueBase item2 in allCustomValues.Where((CustomSyncedValueBase config) => config.LocalBaseValue != null)) { item2.BoxedValue = item2.LocalBaseValue; item2.LocalBaseValue = null; } lockedConfigChanged -= serverLockedSettingChanged; serverLockedSettingChanged(); } private IEnumerator<bool> distributeConfigToPeers(ZNetPeer peer, ZPackage package) { ZRoutedRpc rpc = ZRoutedRpc.instance; if (rpc == null) { yield break; } byte[] data = package.GetArray(); if (data != null && data.LongLength > 250000) { int fragments = (int)(1 + (data.LongLength - 1) / 250000); long packageIdentifier = ++packageCounter; int fragment = 0; while (fragment < fragments) { foreach (bool item in waitForQueue()) { yield return item; } if (peer.m_socket.IsConnected()) { ZPackage fragmentedPackage = new ZPackage(); fragmentedPackage.Write((byte)2); fragmentedPackage.Write(packageIdentifier); fragmentedPackage.Write(fragment); fragmentedPackage.Write(fragments); fragmentedPackage.Write(data.Skip(250000 * fragment).Take(250000).ToArray()); SendPackage(fragmentedPackage); if (fragment != fragments - 1) { yield return true; } int num = fragment + 1; fragment = num; continue; } break; } yield break; } foreach (bool item2 in waitForQueue()) { yield return item2; } SendPackage(package); void SendPackage(ZPackage pkg) { string text = Name + " ConfigSync"; if (isServer) { peer.m_rpc.Invoke(text, new object[1] { pkg }); } else { rpc.InvokeRoutedRPC(peer.m_server ? 0 : peer.m_uid, text, new object[1] { pkg }); } } IEnumerable<bool> waitForQueue() { float timeout = Time.time + 30f; while (peer.m_socket.GetSendQueueSize() > 20000) { if (Time.time > timeout) { Debug.Log((object)$"Disconnecting {peer.m_uid} after 30 seconds config sending timeout"); peer.m_rpc.Invoke("Error", new object[1] { (object)(ConnectionStatus)5 }); ZNet.instance.Disconnect(peer); break; } yield return false; } } } private IEnumerator sendZPackage(long target, ZPackage package) { if (!Object.op_Implicit((Object)(object)ZNet.instance)) { return Enumerable.Empty<object>().GetEnumerator(); } List<ZNetPeer> list = (List<ZNetPeer>)AccessTools.DeclaredField(typeof(ZRoutedRpc), "m_peers").GetValue(ZRoutedRpc.instance); if (target != 0) { list = list.Where((ZNetPeer p) => p.m_uid == target).ToList(); } return sendZPackage(list, package); } private IEnumerator sendZPackage(List<ZNetPeer> peers, ZPackage package) { if (!Object.op_Implicit((Object)(object)ZNet.instance)) { yield break; } byte[] rawData = package.GetArray(); if (rawData != null && rawData.LongLength > 10000) { ZPackage compressedPackage = new ZPackage(); compressedPackage.Write((byte)4); MemoryStream output = new MemoryStream(); using (DeflateStream deflateStream = new DeflateStream(output, CompressionLevel.Optimal)) { deflateStream.Write(rawData, 0, rawData.Length); } compressedPackage.Write(output.ToArray()); package = compressedPackage; } List<IEnumerator<bool>> writers = (from p in peers where p.IsReady() select distributeConfigToPeers(p, package)).ToList(); writers.RemoveAll((IEnumerator<bool> writer) => !writer.MoveNext()); while (writers.Count > 0) { yield return null; writers.RemoveAll((IEnumerator<bool> writer) => !writer.MoveNext()); } } private void Broadcast(long target, params ConfigEntryBase[] configs) { if (!IsLocked || isServer) { ZPackage package = ConfigsToPackage(configs); ZNet instance = ZNet.instance; if (instance != null) { ((MonoBehaviour)instance).StartCoroutine(sendZPackage(target, package)); } } } private void Broadcast(long target, params CustomSyncedValueBase[] customValues) { if (!IsLocked || isServer) { ZPackage package = ConfigsToPackage(null, customValues); ZNet instance = ZNet.instance; if (instance != null) { ((MonoBehaviour)instance).StartCoroutine(sendZPackage(target, package)); } } } private static OwnConfigEntryBase? configData(ConfigEntryBase config) { return config.Description.Tags?.OfType<OwnConfigEntryBase>().SingleOrDefault(); } public static SyncedConfigEntry<T>? ConfigData<T>(ConfigEntry<T> config) { return ((ConfigEntryBase)config).Description.Tags?.OfType<SyncedConfigEntry<T>>().SingleOrDefault(); } private static T configAttribute<T>(ConfigEntryBase config) { return config.Description.Tags.OfType<T>().First(); } private static Type configType(ConfigEntryBase config) { return configType(config.SettingType); } private static Type configType(Type type) { return type.IsEnum ? Enum.GetUnderlyingType(type) : type; } private static ZPackage ConfigsToPackage(IEnumerable<ConfigEntryBase>? configs = null, IEnumerable<CustomSyncedValueBase>? customValues = null, IEnumerable<PackageEntry>? packageEntries = null, bool partial = true) { //IL_0051: Unknown result type (might be due to invalid IL or missing references) //IL_0057: Expected O, but got Unknown List<ConfigEntryBase> list = configs?.Where((ConfigEntryBase config) => configData(config).SynchronizedConfig).ToList() ?? new List<ConfigEntryBase>(); List<CustomSyncedValueBase> list2 = customValues?.ToList() ?? new List<CustomSyncedValueBase>(); ZPackage val = new ZPackage(); val.Write((byte)(partial ? 1 : 0)); val.Write(list.Count + list2.Count + (packageEntries?.Count() ?? 0)); foreach (PackageEntry item in packageEntries ?? Array.Empty<PackageEntry>()) { AddEntryToPackage(val, item); } foreach (CustomSyncedValueBase item2 in list2) { AddEntryToPackage(val, new PackageEntry { section = "Internal", key = item2.Identifier, type = item2.Type, value = item2.BoxedValue }); } foreach (ConfigEntryBase item3 in list) { AddEntryToPackage(val, new PackageEntry { section = item3.Definition.Section, key = item3.Definition.Key, type = configType(item3), value = item3.BoxedValue }); } return val; } private static void AddEntryToPackage(ZPackage package, PackageEntry entry) { package.Write(entry.section); package.Write(entry.key); package.Write((entry.value == null) ? "" : GetZPackageTypeString(entry.type)); AddValueToZPackage(package, entry.value); } private static string GetZPackageTypeString(Type type) { return type.AssemblyQualifiedName; } private static void AddValueToZPackage(ZPackage package, object? value) { Type type = value?.GetType(); if (value is Enum) { value = ((IConvertible)value).ToType(Enum.GetUnderlyingType(value.GetType()), CultureInfo.InvariantCulture); } else { if (value is ICollection collection) { package.Write(collection.Count); { foreach (object item in collection) { AddValueToZPackage(package, item); } return; } } if ((object)type != null && type.IsValueType && !type.IsPrimitive) { FieldInfo[] fields = type.GetFields(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); package.Write(fields.Length); FieldInfo[] array = fields; foreach (FieldInfo fieldInfo in array) { package.Write(GetZPackageTypeString(fieldInfo.FieldType)); AddValueToZPackage(package, fieldInfo.GetValue(value)); } return; } } ZRpc.Serialize(new object[1] { value }, ref package); } private static object ReadValueWithTypeFromZPackage(ZPackage package, Type type) { if ((object)type != null && type.IsValueType && !type.IsPrimitive && !type.IsEnum) { FieldInfo[] fields = type.GetFields(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); int num = package.ReadInt(); if (num != fields.Length) { throw new InvalidDeserializationTypeException { received = $"(field count: {num})", expected = $"(field count: {fields.Length})" }; } object uninitializedObject = FormatterServices.GetUninitializedObject(type); FieldInfo[] array = fields; foreach (FieldInfo fieldInfo in array) { string text = package.ReadString(); if (text != GetZPackageTypeString(fieldInfo.FieldType)) { throw new InvalidDeserializationTypeException { received = text, expected = GetZPackageTypeString(fieldInfo.FieldType), field = fieldInfo.Name }; } fieldInfo.SetValue(uninitializedObject, ReadValueWithTypeFromZPackage(package, fieldInfo.FieldType)); } return uninitializedObject; } if (type.IsGenericType && type.GetGenericTypeDefinition() == typeof(Dictionary<, >)) { int num2 = package.ReadInt(); IDictionary dictionary = (IDictionary)Activator.CreateInstance(type); Type type2 = typeof(KeyValuePair<, >).MakeGenericType(type.GenericTypeArguments); FieldInfo field = type2.GetField("key", BindingFlags.Instance | BindingFlags.NonPublic); FieldInfo field2 = type2.GetField("value", BindingFlags.Instance | BindingFlags.NonPublic); for (int j = 0; j < num2; j++) { object obj = ReadValueWithTypeFromZPackage(package, type2); dictionary.Add(field.GetValue(obj), field2.GetValue(obj)); } return dictionary; } if (type != typeof(List<string>) && type.IsGenericType) { Type type3 = typeof(ICollection<>).MakeGenericType(type.GenericTypeArguments[0]); if ((object)type3 != null && type3.IsAssignableFrom(type)) { int num3 = package.ReadInt(); object obj2 = Activator.CreateInstance(type); MethodInfo method = type3.GetMethod("Add"); for (int k = 0; k < num3; k++) { method.Invoke(obj2, new object[1] { ReadValueWithTypeFromZPackage(package, type.GenericTypeArguments[0]) }); } return obj2; } } ParameterInfo parameterInfo = (ParameterInfo)FormatterServices.GetUninitializedObject(typeof(ParameterInfo)); AccessTools.DeclaredField(typeof(ParameterInfo), "ClassImpl").SetValue(parameterInfo, type); List<object> source = new List<object>(); ZRpc.Deserialize(new ParameterInfo[2] { null, parameterInfo }, package, ref source); return source.First(); } } [PublicAPI] [HarmonyPatch] internal class VersionCheck { private static readonly HashSet<VersionCheck> versionChecks; private static readonly Dictionary<string, string> notProcessedNames; public string Name; private string? displayName; private string? currentVersion; private string? minimumRequiredVersion; public bool ModRequired = true; private string? ReceivedCurrentVersion; private string? ReceivedMinimumRequiredVersion; private readonly List<ZRpc> ValidatedClients = new List<ZRpc>(); private ConfigSync? ConfigSync; public string DisplayName { get { return displayName ?? Name; } set { displayName = value; } } public string CurrentVersion { get { return currentVersion ?? "0.0.0"; } set { currentVersion = value; } } public string MinimumRequiredVersion { get { return minimumRequiredVersion ?? (ModRequired ? CurrentVersion : "0.0.0"); } set { minimumRequiredVersion = value; } } private static void PatchServerSync() { //IL_005e: Unknown result type (might be due to invalid IL or missing references) //IL_0064: Expected O, but got Unknown Patches patchInfo = PatchProcessor.GetPatchInfo((MethodBase)AccessTools.DeclaredMethod(typeof(ZNet), "Awake", (Type[])null, (Type[])null)); if (patchInfo != null && patchInfo.Postfixes.Count((Patch p) => p.PatchMethod.DeclaringType == typeof(ConfigSync.RegisterRPCPatch)) > 0) { return; } Harmony val = new Harmony("org.bepinex.helpers.ServerSync"); foreach (Type item in from t in typeof(ConfigSync).GetNestedTypes(BindingFlags.NonPublic).Concat(new Type[1] { typeof(VersionCheck) }) where t.IsClass select t) { val.PatchAll(item); } } static VersionCheck() { versionChecks = new HashSet<VersionCheck>(); notProcessedNames = new Dictionary<string, string>(); typeof(ThreadingHelper).GetMethod("StartSyncInvoke").Invoke(ThreadingHelper.Instance, new object[1] { new Action(PatchServerSync) }); } public VersionCheck(string name) { Name = name; ModRequired = true; versionChecks.Add(this); } public VersionCheck(ConfigSync configSync) { ConfigSync = configSync; Name = ConfigSync.Name; versionChecks.Add(this); } public void Initialize() { ReceivedCurrentVersion = null; ReceivedMinimumRequiredVersion = null; if (ConfigSync != null) { Name = ConfigSync.Name; DisplayName = ConfigSync.DisplayName; CurrentVersion = ConfigSync.CurrentVersion; MinimumRequiredVersion = ConfigSync.MinimumRequiredVersion; ModRequired = ConfigSync.ModRequired; } } private bool IsVersionOk() { if (ReceivedMinimumRequiredVersion == null || ReceivedCurrentVersion == null) { return !ModRequired; } bool flag = new Version(CurrentVersion) >= new Version(ReceivedMinimumRequiredVersion); bool flag2 = new Version(ReceivedCurrentVersion) >= new Version(MinimumRequiredVersion); return flag && flag2; } private string ErrorClient() { if (ReceivedMinimumRequiredVersion == null) { return DisplayName + " is not installed on the server."; } return (new Version(CurrentVersion) >= new Version(ReceivedMinimumRequiredVersion)) ? (DisplayName + " may not be higher than version " + ReceivedCurrentVersion + ". You have version " + CurrentVersion + ".") : (DisplayName + " needs to be at least version " + ReceivedMinimumRequiredVersion + ". You have version " + CurrentVersion + "."); } private string ErrorServer(ZRpc rpc) { return "Disconnect: The client (" + rpc.GetSocket().GetHostName() + ") doesn't have the correct " + DisplayName + " version " + MinimumRequiredVersion; } private string Error(ZRpc? rpc = null) { return (rpc == null) ? ErrorClient() : ErrorServer(rpc); } private static VersionCheck[] GetFailedClient() { return versionChecks.Where((VersionCheck check) => !check.IsVersionOk()).ToArray(); } private static VersionCheck[] GetFailedServer(ZRpc rpc) { return versionChecks.Where((VersionCheck check) => check.ModRequired && !check.ValidatedClients.Contains(rpc)).ToArray(); } private static void Logout() { Game.instance.Logout(true, true); AccessTools.DeclaredField(typeof(ZNet), "m_connectionStatus").SetValue(null, (object)(ConnectionStatus)3); } private static void DisconnectClient(ZRpc rpc) { rpc.Invoke("Error", new object[1] { 3 }); } private static void CheckVersion(ZRpc rpc, ZPackage pkg) { CheckVersion(rpc, pkg, null); } private static void CheckVersion(ZRpc rpc, ZPackage pkg, Action<ZRpc, ZPackage>? original) { string text = pkg.ReadString(); string text2 = pkg.ReadString(); string text3 = pkg.ReadString(); bool flag = false; foreach (VersionCheck versionCheck in versionChecks) { if (!(text != versionCheck.Name)) { Debug.Log((object)("Received " + versionCheck.DisplayName + " version " + text3 + " and minimum version " + text2 + " from the " + (ZNet.instance.IsServer() ? "client" : "server") + ".")); versionCheck.ReceivedMinimumRequiredVersion = text2; versionCheck.ReceivedCurrentVersion = text3; if (ZNet.instance.IsServer() && versionCheck.IsVersionOk()) { versionCheck.ValidatedClients.Add(rpc); } flag = true; } } if (flag) { return; } pkg.SetPos(0); if (original != null) { original(rpc, pkg); if (pkg.GetPos() == 0) { notProcessedNames.Add(text, text3); } } } [HarmonyPatch(typeof(ZNet), "RPC_PeerInfo")] [HarmonyPrefix] private static bool RPC_PeerInfo(ZRpc rpc, ZNet __instance) { VersionCheck[] array = (__instance.IsServer() ? GetFailedServer(rpc) : GetFailedClient()); if (array.Length == 0) { return true; } VersionCheck[] array2 = array; foreach (VersionCheck versionCheck in array2) { Debug.LogWarning((object)versionCheck.Error(rpc)); } if (__instance.IsServer()) { DisconnectClient(rpc); } else { Logout(); } return false; } [HarmonyPatch(typeof(ZNet), "OnNewConnection")] [HarmonyPrefix] private static void RegisterAndCheckVersion(ZNetPeer peer, ZNet __instance) { //IL_018e: Unknown result type (might be due to invalid IL or missing references) //IL_0195: Expected O, but got Unknown notProcessedNames.Clear(); IDictionary dictionary = (IDictionary)typeof(ZRpc).GetField("m_functions", BindingFlags.Instance | BindingFlags.NonPublic).GetValue(peer.m_rpc); if (dictionary.Contains(StringExtensionMethods.GetStableHashCode("ServerSync VersionCheck"))) { object obj = dictionary[StringExtensionMethods.GetStableHashCode("ServerSync VersionCheck")]; Action<ZRpc, ZPackage> action = (Action<ZRpc, ZPackage>)obj.GetType().GetField("m_action", BindingFlags.Instance | BindingFlags.NonPublic).GetValue(obj); peer.m_rpc.Register<ZPackage>("ServerSync VersionCheck", (Action<ZRpc, ZPackage>)delegate(ZRpc rpc, ZPackage pkg) { CheckVersion(rpc, pkg, action); }); } else { peer.m_rpc.Register<ZPackage>("ServerSync VersionCheck", (Action<ZRpc, ZPackage>)CheckVersion); } foreach (VersionCheck versionCheck in versionChecks) { versionCheck.Initialize(); if (versionCheck.ModRequired || __instance.IsServer()) { Debug.Log((object)("Sending " + versionCheck.DisplayName + " version " + versionCheck.CurrentVersion + " and minimum version " + versionCheck.MinimumRequiredVersion + " to the " + (__instance.IsServer() ? "client" : "server") + ".")); ZPackage val = new ZPackage(); val.Write(versionCheck.Name); val.Write(versionCheck.MinimumRequiredVersion); val.Write(versionCheck.CurrentVersion); peer.m_rpc.Invoke("ServerSync VersionCheck", new object[1] { val }); } } } [HarmonyPatch(typeof(ZNet), "Disconnect")] [HarmonyPrefix] private static void RemoveDisconnected(ZNetPeer peer, ZNet __instance) { if (!__instance.IsServer()) { return; } foreach (VersionCheck versionCheck in versionChecks) { versionCheck.ValidatedClients.Remove(peer.m_rpc); } } [HarmonyPatch(typeof(FejdStartup), "ShowConnectError")] [HarmonyPostfix] private static void ShowConnectionError(FejdStartup __instance) { //IL_000e: Unknown result type (might be due to invalid IL or missing references) //IL_0014: Invalid comparison between Unknown and I4 //IL_0186: Unknown result type (might be due to invalid IL or missing references) //IL_018b: Unknown result type (might be due to invalid IL or missing references) //IL_0199: Unknown result type (might be due to invalid IL or missing references) //IL_01de: Unknown result type (might be due to invalid IL or missing references) //IL_01ea: Unknown result type (might be due to invalid IL or missing references) //IL_01f8: Unknown result type (might be due to invalid IL or missing references) //IL_020a: Unknown result type (might be due to invalid IL or missing references) //IL_0219: Unknown result type (might be due to invalid IL or missing references) //IL_021e: Unknown result type (might be due to invalid IL or missing references) //IL_0229: Unknown result type (might be due to invalid IL or missing references) if (!__instance.m_connectionFailedPanel.activeSelf || (int)ZNet.GetConnectionStatus() != 3) { return; } bool flag = false; VersionCheck[] failedClient = GetFailedClient(); if (failedClient.Length != 0) { string text = string.Join("\n", failedClient.Select((VersionCheck check) => check.Error())); TMP_Text connectionFailedError = __instance.m_connectionFailedError; connectionFailedError.text = connectionFailedError.text + "\n" + text; flag = true; } foreach (KeyValuePair<string, string> item in notProcessedNames.OrderBy<KeyValuePair<string, string>, string>((KeyValuePair<string, string> kv) => kv.Key)) { if (!__instance.m_connectionFailedError.text.Contains(item.Key)) { TMP_Text connectionFailedError2 = __instance.m_connectionFailedError; connectionFailedError2.text = connectionFailedError2.text + "\nServer expects you to have " + item.Key + " (Version: " + item.Value + ") installed."; flag = true; } } if (flag) { RectTransform component = ((Component)__instance.m_connectionFailedPanel.transform.Find("Image")).GetComponent<RectTransform>(); Vector2 sizeDelta = component.sizeDelta; sizeDelta.x = 675f; component.sizeDelta = sizeDelta; __instance.m_connectionFailedError.ForceMeshUpdate(false, false); float num = __instance.m_connectionFailedError.renderedHeight + 105f; RectTransform component2 = ((Component)((Component)component).transform.Find("ButtonOk")).GetComponent<RectTransform>(); component2.anchoredPosition = new Vector2(component2.anchoredPosition.x, component2.anchoredPosition.y - (num - component.sizeDelta.y) / 2f); sizeDelta = component.sizeDelta; sizeDelta.y = num; component.sizeDelta = sizeDelta; } } } }