Please disclose if your mod was created primarily 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 Cron Job v1.12.0
CronJob.dll
Decompiled a year agousing System; using System.Collections.Generic; using System.ComponentModel; using System.Diagnostics; using System.Globalization; using System.IO; using System.Linq; using System.Reflection; using System.Runtime.CompilerServices; using System.Runtime.Serialization; using System.Runtime.Versioning; using System.Security; using System.Security.Permissions; using System.Text; using BepInEx; using BepInEx.Bootstrap; using BepInEx.Logging; using Cronos; using HarmonyLib; using Microsoft.CodeAnalysis; using UnityEngine; using YamlDotNet.Serialization; using YamlDotNet.Serialization.NamingConventions; [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("CronJob")] [assembly: AssemblyConfiguration("Release")] [assembly: AssemblyFileVersion("1.0.0.0")] [assembly: AssemblyInformationalVersion("1.0.0+05f30f41dc4f285e3424a8d17d297196f00efe31")] [assembly: AssemblyProduct("CronJob")] [assembly: AssemblyTitle("CronJob")] [assembly: SecurityPermission(SecurityAction.RequestMinimum, SkipVerification = true)] [assembly: AssemblyVersion("1.0.0.0")] [module: UnverifiableCode] [module: RefSafetyRules(11)] namespace Microsoft.CodeAnalysis { [CompilerGenerated] [Microsoft.CodeAnalysis.Embedded] internal sealed class EmbeddedAttribute : Attribute { } } namespace System.Runtime.CompilerServices { [CompilerGenerated] [Microsoft.CodeAnalysis.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] [Microsoft.CodeAnalysis.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] [Microsoft.CodeAnalysis.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 CronJob { public class CronData { [DefaultValue("UTC")] public string timezone = "UTC"; [DefaultValue(10f)] public float interval = 10f; public List<CronEntryData> jobs = new List<CronEntryData>(); public List<CronEntryData> zone = new List<CronEntryData>(); public List<CronEntryData> join = new List<CronEntryData>(); [DefaultValue(true)] public bool logJobs = true; [DefaultValue(true)] public bool logZone = true; [DefaultValue(true)] public bool logJoin = true; [DefaultValue(true)] public bool logSkipped = true; [DefaultValue("true")] public string discordConnector = "true"; } public class CronEntryData { [DefaultValue("")] public string command = ""; [DefaultValue(null)] public string[]? commands; [DefaultValue("")] public string schedule = ""; [DefaultValue(null)] public float? inactive; [DefaultValue(null)] public float? chance; [DefaultValue(false)] public bool avoidPlayers; [DefaultValue(false)] public bool useGameTime; [DefaultValue(null)] public bool? log; [DefaultValue("")] public string biomes = ""; [DefaultValue("")] public string locations = ""; [DefaultValue("")] public string objects = ""; [DefaultValue("")] public string bannedObjects = ""; } public class CronGeneralJob : CronBaseJob { public string Schedule = data.schedule; public bool UseGameTime = data.useGameTime; public CronGeneralJob(CronEntryData data) : base(data) { } } public class CronZoneJob : CronBaseJob { public string Schedule = data.schedule; public bool AvoidPlayers = data.avoidPlayers; public Biome Biomes = Parse.ToBiomes(data.biomes); public HashSet<string> Locations = Parse.ToSet(data.locations); public HashSet<int> Objects = Parse.ToHashSet(data.objects); public HashSet<int> BannedObjects = Parse.ToHashSet(data.bannedObjects); public CronZoneJob(CronEntryData data) : base(data) { }//IL_001f: Unknown result type (might be due to invalid IL or missing references) //IL_0024: Unknown result type (might be due to invalid IL or missing references) } public class CronJoinJob : CronBaseJob { public CronJoinJob(CronEntryData data) : base(data) { } } public abstract class CronBaseJob { public string[] Commands = data.commands ?? new string[1] { data.command }; public float? Chance = data.chance; public bool? Log = data.log; protected CronBaseJob(CronEntryData data) { } } [BepInPlugin("cron_job", "Cron Job", "1.12")] public class CronJob : BaseUnityPlugin { public const string GUID = "cron_job"; public const string NAME = "Cron Job"; public const string VERSION = "1.12"; private static ManualLogSource? Logs; private float timer; public static ManualLogSource Log => Logs; public void Awake() { //IL_0010: Unknown result type (might be due to invalid IL or missing references) Logs = ((BaseUnityPlugin)this).Logger; new Harmony("cron_job").PatchAll(); } public void Start() { DiscordHook.Init(); CronManager.SetupWatcher(); } public void LateUpdate() { if (Object.op_Implicit((Object)(object)ZNet.instance)) { timer -= Time.deltaTime; if (timer <= 0f) { timer = CronManager.Interval; CronManager.Execute(); TrackManager.Track(); } } } } [HarmonyPatch] public class CronManager { public static string FileName = "cron.yaml"; public static string FilePath = Path.Combine(Paths.ConfigPath, FileName); public static List<CronGeneralJob> Jobs = new List<CronGeneralJob>(); public static List<CronZoneJob> ZoneJobs = new List<CronZoneJob>(); public static List<CronJoinJob> JoinJobs = new List<CronJoinJob>(); public static float Interval = 10f; public static bool LogJobs = true; public static bool LogZone = true; public static bool LogJoin = true; public static bool LogSkipped = true; public static string DiscordConnector = "CronJob"; public static DateTime Previous = DateTime.UtcNow; public static DateTime PreviousGameTime = new DateTime(Year2000, DateTimeKind.Utc); public static TimeZoneInfo TimeZone = TimeZoneInfo.Utc; private static readonly Random random = new Random(); private static readonly long Year2000 = new DateTime(2000, 1, 1).Ticks; private static HashSet<ZNetPeer> HandledPeers = new HashSet<ZNetPeer>(); private static DateTime? Parse(string value, DateTime? next = null) { CronFormat format = ((value.Split(new char[1] { ' ' }).Length == 6) ? CronFormat.IncludeSeconds : CronFormat.Standard); return CronExpression.Parse(value, format).GetNextOccurrence(next ?? DateTime.UtcNow, TimeZone); } private static bool Roll(float? chance) { if (!chance.HasValue || chance >= 1f || chance == 0f) { return true; } return random.NextDouble() < (double?)chance; } public static void Execute() { DateTime utcNow = DateTime.UtcNow; DateTime dateTime = new DateTime(Year2000 + (long)(ZNet.instance.GetTimeSeconds() / (double)EnvMan.instance.m_dayLengthSec * 864000000000.0), DateTimeKind.Utc); foreach (CronGeneralJob job in Jobs) { DateTime dateTime2 = (job.UseGameTime ? dateTime : utcNow); DateTime dateTime3 = (job.UseGameTime ? PreviousGameTime : Previous); if (!(dateTime2 < dateTime3)) { DateTime value = dateTime2; DateTime? dateTime4 = Parse(job.Schedule, dateTime3); if (!(value < dateTime4)) { RunJob(job.Commands, job.Chance, job.Log); } } } Previous = utcNow; PreviousGameTime = dateTime; } private static void RunJob(string[] commands, float? chance, bool? log) { if (Roll(chance)) { string[] array = commands; foreach (string text in array) { ((Terminal)Console.instance).TryRunCommand(text, false, false); if (log ?? LogJobs) { Log("Executing: " + text); } } } else { if (!LogSkipped) { return; } string[] array = commands; foreach (string text2 in array) { if (log ?? LogJobs) { Log("Skipped: " + text2); } } } } private static void Log(string message) { CronJob.Log.LogInfo((object)message); if (DiscordConnector != "") { DiscordHook.SendMessage(DiscordConnector, message); } } public static bool Execute(Vector2i zone, bool hasPlayer, DateTime? previous) { //IL_000e: Unknown result type (might be due to invalid IL or missing references) //IL_000f: Unknown result type (might be due to invalid IL or missing references) //IL_00d7: Unknown result type (might be due to invalid IL or missing references) //IL_00dc: Unknown result type (might be due to invalid IL or missing references) //IL_00e1: Unknown result type (might be due to invalid IL or missing references) //IL_00f2: Unknown result type (might be due to invalid IL or missing references) //IL_011c: Unknown result type (might be due to invalid IL or missing references) //IL_012d: Unknown result type (might be due to invalid IL or missing references) //IL_0132: Unknown result type (might be due to invalid IL or missing references) //IL_0280: Unknown result type (might be due to invalid IL or missing references) //IL_0164: Unknown result type (might be due to invalid IL or missing references) //IL_0175: Unknown result type (might be due to invalid IL or missing references) //IL_017a: Unknown result type (might be due to invalid IL or missing references) //IL_02ef: Unknown result type (might be due to invalid IL or missing references) //IL_02a2: Unknown result type (might be due to invalid IL or missing references) //IL_01ac: Unknown result type (might be due to invalid IL or missing references) //IL_01bd: Unknown result type (might be due to invalid IL or missing references) //IL_01c2: Unknown result type (might be due to invalid IL or missing references) //IL_036b: Unknown result type (might be due to invalid IL or missing references) //IL_01f4: Unknown result type (might be due to invalid IL or missing references) //IL_0205: 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_0239: Unknown result type (might be due to invalid IL or missing references) //IL_024a: Unknown result type (might be due to invalid IL or missing references) //IL_024f: Unknown result type (might be due to invalid IL or missing references) List<CronZoneJob> list = ZoneJobs.Where((CronZoneJob cron) => !cron.AvoidPlayers || !hasPlayer).ToList(); if (list.Count == 0) { return false; } DateTime utcNow = DateTime.UtcNow; ZoneSystem instance = ZoneSystem.instance; ZDOMan instance2 = ZDOMan.instance; WorldGenerator instance3 = WorldGenerator.instance; foreach (CronZoneJob cron2 in list) { DateTime value = utcNow; DateTime? dateTime = Parse(cron2.Schedule, previous); if (value < dateTime) { continue; } Vector3 pos = ZoneSystem.GetZonePos(zone); if (((int)cron2.Biomes != 0 && (instance3.GetBiome(pos.x, pos.y, 0.02f, false) & cron2.Biomes) == 0 && (instance3.GetBiome(pos.x + 32f, pos.y + 32f, 0.02f, false) & cron2.Biomes) == 0 && (instance3.GetBiome(pos.x + 32f, pos.y - 32f, 0.02f, false) & cron2.Biomes) == 0 && (instance3.GetBiome(pos.x - 32f, pos.y + 32f, 0.02f, false) & cron2.Biomes) == 0 && (instance3.GetBiome(pos.x - 32f, pos.y + 32f, 0.02f, false) & cron2.Biomes) == 0) || (cron2.Locations.Count > 0 && (!instance.m_locationInstances.TryGetValue(zone, out var value2) || !cron2.Locations.Contains(value2.m_location?.m_prefabName ?? "")))) { continue; } if (cron2.Objects.Count > 0) { int num = instance2.SectorToIndex(zone); if (num < 0 || num >= instance2.m_objectsBySector.Length) { continue; } List<ZDO> list2 = instance2.m_objectsBySector[num]; if (list2 == null || list2.All((ZDO zdo) => !cron2.Objects.Contains(zdo.m_prefab))) { continue; } } if (cron2.BannedObjects.Count > 0) { int num2 = instance2.SectorToIndex(zone); if (num2 < 0 || num2 >= instance2.m_objectsBySector.Length) { continue; } List<ZDO> list3 = instance2.m_objectsBySector[num2]; if (list3 == null || list3.Any((ZDO zdo) => cron2.BannedObjects.Contains(zdo.m_prefab))) { continue; } } RunJob(cron2.Commands.Select((string cmd) => cmd.Replace("<i>", zone.x.ToString()).Replace("<j>", zone.y.ToString()).Replace("<x>", pos.x.ToString("F2", CultureInfo.InvariantCulture)) .Replace("<y>", pos.y.ToString("F2", CultureInfo.InvariantCulture)) .Replace("<z>", pos.z.ToString("F2", CultureInfo.InvariantCulture))).ToArray(), cron2.Chance, cron2.Log); } return true; } [HarmonyPatch(typeof(ZNet), "RPC_CharacterID")] [HarmonyPostfix] private static void AddPeer(ZNet __instance, ZRpc rpc, ZDOID characterID) { if (!__instance.IsServer() || ((ZDOID)(ref characterID)).IsNone()) { return; } ZNetPeer peer = __instance.GetPeer(rpc); if (HandledPeers.Contains(peer)) { return; } HandledPeers.Add(peer); foreach (CronJoinJob joinJob in JoinJobs) { RunJob(joinJob.Commands.Select((string cmd) => cmd.Replace("<name>", peer.m_playerName).Replace("<pname>", peer.m_playerName).Replace("<first>", peer.m_playerName.Split(new char[1] { ' ' })[0]) .Replace("<id>", ((ZDOID)(ref peer.m_characterID)).UserID.ToString()) .Replace("<pid>", peer.m_rpc.GetSocket().GetHostName()) .Replace("<pchar>", ((ZDOID)(ref peer.m_characterID)).UserID.ToString()) .Replace("<x>", peer.m_refPos.x.ToString("F2", CultureInfo.InvariantCulture)) .Replace("<y>", peer.m_refPos.y.ToString("F2", CultureInfo.InvariantCulture)) .Replace("<z>", peer.m_refPos.z.ToString("F2", CultureInfo.InvariantCulture))).ToArray(), joinJob.Chance, joinJob.Log); } } [HarmonyPatch(typeof(ZNet), "Disconnect")] [HarmonyPostfix] private static void Disconnect(ZNetPeer peer) { HandledPeers.Remove(peer); } [HarmonyPatch(typeof(Chat), "Awake")] [HarmonyPostfix] private static void ChatAwake() { if (File.Exists(FilePath)) { FromFile(); return; } string contents = Data.Serializer().Serialize((object)new CronData()); File.WriteAllText(FilePath, contents); } private static bool ParseTimeZone(string timezone) { timezone = timezone.ToLower(); foreach (TimeZoneInfo systemTimeZone in TimeZoneInfo.GetSystemTimeZones()) { if (systemTimeZone.Id.ToLower() == timezone || systemTimeZone.DisplayName.ToLower() == timezone) { TimeZone = systemTimeZone; return true; } } foreach (TimeZoneInfo systemTimeZone2 in TimeZoneInfo.GetSystemTimeZones()) { if (systemTimeZone2.Id.ToLower().Contains(timezone) || systemTimeZone2.DisplayName.ToLower().Contains(timezone)) { TimeZone = systemTimeZone2; return true; } } return false; } public static void FromFile() { if (!File.Exists(FilePath)) { File.WriteAllText(FilePath, Data.Serializer().Serialize((object)new CronData())); } try { CronData cronData = Data.Read<CronData>(FilePath); Interval = cronData.interval; if (ParseTimeZone(cronData.timezone)) { CronJob.Log.LogInfo((object)("Selected time zone " + TimeZone.Id + " / " + TimeZone.DisplayName + ".")); } else { CronJob.Log.LogWarning((object)("Time zone " + cronData.timezone + " not found, using UTC. Possible time zones are:")); foreach (TimeZoneInfo systemTimeZone in TimeZoneInfo.GetSystemTimeZones()) { CronJob.Log.LogWarning((object)(systemTimeZone.Id + " / " + systemTimeZone.DisplayName)); } } LogJobs = cronData.logJobs; LogZone = cronData.logZone; LogJoin = cronData.logJoin; LogSkipped = cronData.logSkipped; cronData.join.ForEach(ReplaceParameters); cronData.zone.ForEach(ReplaceParameters); cronData.jobs.ForEach(ReplaceParameters); cronData.zone.ForEach(VerifyParameterExists); cronData.zone.ForEach(VerifyInactiveIsNull); cronData.jobs.ForEach(VerifyInactiveIsNull); cronData.jobs = SkipWithoutSchedule(cronData.jobs); cronData.zone = SkipWithoutSchedule(cronData.zone); DiscordConnector = cronData.discordConnector; if (DiscordConnector == "true") { DiscordConnector = "cronjob"; } else if (DiscordConnector == "false") { DiscordConnector = ""; } Jobs = cronData.jobs.Select((CronEntryData s) => new CronGeneralJob(s)).ToList(); CronJob.Log.LogInfo((object)$"Reloading {Jobs.Count} cron jobs."); ZoneJobs = cronData.zone.Select((CronEntryData s) => new CronZoneJob(s)).ToList(); CronJob.Log.LogInfo((object)$"Reloading {ZoneJobs.Count} zone cron jobs."); JoinJobs = cronData.join.Select((CronEntryData s) => new CronJoinJob(s)).ToList(); CronJob.Log.LogInfo((object)$"Reloading {JoinJobs.Count} join jobs."); } catch (Exception ex) { CronJob.Log.LogError((object)ex.StackTrace); } } private static void ReplaceParameters(CronEntryData entry) { if (entry.command.Contains("$$")) { CronJob.Log.LogWarning((object)("$$ is deprecated, use <> instead. Command: " + entry.command)); entry.command = entry.command.Replace("$$i", "<i>").Replace("$$I", "<i>").Replace("$$j", "<j>") .Replace("$$J", "<j>") .Replace("$$x", "<x>") .Replace("$$X", "<x>") .Replace("$$y", "<y>") .Replace("$$Y", "<y>") .Replace("$$z", "<z>") .Replace("$$Z", "<>>") .Replace("$$id", "<id>") .Replace("$$ID", "<id>") .Replace("$$name", "<name>") .Replace("$$NAME", "<name>") .Replace("$$first", "<first>") .Replace("$$FIRST", "<first>"); } } private static void VerifyParameterExists(CronEntryData entry) { if (!entry.command.Contains("<") && !entry.command.Contains(">")) { CronJob.Log.LogWarning((object)("Command " + entry.command + " does not contain parameters, should this be general job instead?")); } } private static void VerifyInactiveIsNull(CronEntryData entry) { if (entry.inactive.HasValue) { CronJob.Log.LogWarning((object)("Inactive time is deprecated and can be removed. Command: " + entry.command)); } } private static List<CronEntryData> SkipWithoutSchedule(List<CronEntryData> entries) { return entries.Where(delegate(CronEntryData entry) { int num; if (entry.schedule != null) { num = ((entry.schedule != "") ? 1 : 0); if (num != 0) { goto IL_003d; } } else { num = 0; } CronJob.Log.LogWarning((object)("Command " + entry.command + " does not have a schedule, skipping.")); goto IL_003d; IL_003d: return (byte)num != 0; }).ToList(); } public static void SetupWatcher() { Data.SetupWatcher(FileName, FromFile); } } public class Data : MonoBehaviour { public static bool SkipWatch; public static void SetupWatcher(string pattern, Action action) { Action action2 = action; FileSystemWatcher fileSystemWatcher = new FileSystemWatcher(Paths.ConfigPath, pattern); fileSystemWatcher.Created += delegate { skippableAction(); }; fileSystemWatcher.Changed += delegate { skippableAction(); }; fileSystemWatcher.Renamed += delegate { skippableAction(); }; fileSystemWatcher.Deleted += delegate { skippableAction(); }; fileSystemWatcher.IncludeSubdirectories = true; fileSystemWatcher.SynchronizingObject = ThreadingHelper.SynchronizingObject; fileSystemWatcher.EnableRaisingEvents = true; void skippableAction() { if (SkipWatch) { SkipWatch = false; } else { action2(); } } } public static IDeserializer Deserializer() { //IL_0000: Unknown result type (might be due to invalid IL or missing references) //IL_000f: Expected O, but got Unknown return ((BuilderSkeleton<DeserializerBuilder>)new DeserializerBuilder()).WithNamingConvention(CamelCaseNamingConvention.Instance).Build(); } public static ISerializer Serializer() { //IL_0000: Unknown result type (might be due to invalid IL or missing references) //IL_000f: Expected O, but got Unknown return ((BuilderSkeleton<SerializerBuilder>)new SerializerBuilder()).WithNamingConvention(CamelCaseNamingConvention.Instance).WithIndentedSequences().ConfigureDefaultValuesHandling((DefaultValuesHandling)1) .DisableAliases() .Build(); } public static T Deserialize<T>(string raw) where T : new() { return Deserializer().Deserialize<T>(raw); } public static string Serialize<T>(T data) where T : new() { return Serializer().Serialize((object)(T)((data != null) ? ((object)data) : ((object)new T()))); } public static T Read<T>(string file) where T : new() { return Deserialize<T>(File.ReadAllText(file)); } } public static class DiscordHook { public const string GUID = "nwesterhausen.DiscordConnector"; private static Assembly? assembly; private static MethodInfo? sendMessage; private static Type? eventType; private static Dictionary<string, object> messageTypes = new Dictionary<string, object>(); public static void SendMessage(string type, string message) { if (sendMessage == null || eventType == null) { return; } if (!messageTypes.ContainsKey(type)) { try { object value = Enum.Parse(eventType, type, ignoreCase: true); messageTypes[type] = value; } catch { CronJob.Log.LogError((object)("Invalid message type " + type)); return; } } sendMessage.Invoke(null, new object[2] { messageTypes[type], message }); } public static void Init() { if (!Chainloader.PluginInfos.TryGetValue("nwesterhausen.DiscordConnector", out var value)) { return; } assembly = ((object)value.Instance).GetType().Assembly; eventType = assembly.GetType("DiscordConnector.Webhook+Event"); if (eventType == null) { CronJob.Log.LogError((object)"Failed to get DiscordConnector.WebHook+Event"); return; } Type type = assembly.GetType("DiscordConnector.DiscordApi"); if (type == null) { CronJob.Log.LogError((object)"Failed to get DiscordConnector.DiscordApi"); return; } sendMessage = AccessTools.Method(type, "SendMessage", new Type[2] { eventType, typeof(string) }, (Type[])null); if (sendMessage == null) { CronJob.Log.LogError((object)"Failed to get DiscordConnector.DiscordApi.SendMessage"); } else { CronJob.Log.LogInfo((object)"DiscordConnector initialized"); } } } public class Parse { public static string[] Split(string arg, bool removeEmpty = true, char split = ',') { return (from s in arg.Split(new char[1] { split }) select s.Trim() into s where !removeEmpty || s != "" select s).ToArray(); } public static HashSet<int> ToHashSet(string arg) { return new HashSet<int>(from s in Split(arg) select StringExtensionMethods.GetStableHashCode(s)); } public static HashSet<string> ToSet(string arg) { return new HashSet<string>(Split(arg)); } public static Biome ToBiomes(string biomeStr) { //IL_0001: Unknown result type (might be due to invalid IL or missing references) //IL_0057: Unknown result type (might be due to invalid IL or missing references) //IL_001f: Unknown result type (might be due to invalid IL or missing references) //IL_0020: Unknown result type (might be due to invalid IL or missing references) //IL_0022: Unknown result type (might be due to invalid IL or missing references) //IL_0023: Unknown result type (might be due to invalid IL or missing references) //IL_0030: Unknown result type (might be due to invalid IL or missing references) //IL_0033: Unknown result type (might be due to invalid IL or missing references) //IL_0034: Unknown result type (might be due to invalid IL or missing references) Biome val = (Biome)0; string[] array = Split(biomeStr); foreach (string text in array) { if (Enum.TryParse<Biome>(text, ignoreCase: true, out Biome result)) { val |= result; continue; } if (int.TryParse(text, out var result2)) { val = (Biome)(val + result2); continue; } throw new InvalidOperationException("Invalid biome " + text + "."); } return val; } } [HarmonyPatch] public class TrackManager { public static string FileNameJob = "cron_last.yaml"; public static string FileNameZone = "cron_track.yaml"; public static string FilePathJob = Path.Combine(Paths.ConfigPath, FileNameJob); public static string FilePathZone = Path.Combine(Paths.ConfigPath, FileNameZone); public static Dictionary<Vector2i, DateTime> ZoneTimestamps = new Dictionary<Vector2i, DateTime>(); private static readonly HashSet<Vector2i> Zones = new HashSet<Vector2i>(); private static readonly HashSet<Vector2i> PlayerZones = new HashSet<Vector2i>(); [HarmonyPatch(typeof(Chat), "Awake")] [HarmonyPostfix] public static void ChatAwake() { if (File.Exists(FilePathJob)) { Dictionary<string, long> dictionary = Data.Read<Dictionary<string, long>>(FilePathJob); if (dictionary.TryGetValue("world", out var value)) { CronManager.Previous = new DateTime(value, DateTimeKind.Utc); } if (dictionary.TryGetValue("game", out var value2)) { CronManager.PreviousGameTime = new DateTime(value2, DateTimeKind.Utc); } } if (File.Exists(FilePathZone)) { ZoneTrackFromFile(); } } [HarmonyPatch(typeof(ZNet), "SaveWorldThread")] [HarmonyPrefix] public static void OnSave() { Dictionary<string, long> dictionary = new Dictionary<string, long> { { "world", CronManager.Previous.Ticks + 10000000 }, { "game", CronManager.PreviousGameTime.Ticks + 10000000 } }; string contents = Data.Serializer().Serialize((object)dictionary); File.WriteAllText(FilePathJob, contents); if (ZoneTimestamps.Count == 0) { if (File.Exists(FilePathZone)) { File.Delete(FilePathZone); } return; } Dictionary<string, long> dictionary2 = ZoneTimestamps.ToDictionary((KeyValuePair<Vector2i, DateTime> kvp) => $"{kvp.Key.x},{kvp.Key.y}", (KeyValuePair<Vector2i, DateTime> kvp) => kvp.Value.Ticks); contents = Data.Serializer().Serialize((object)dictionary2); File.WriteAllText(FilePathZone, contents); } public static void ZoneTrackFromFile() { ZoneTimestamps = new Dictionary<Vector2i, DateTime>(); if (!File.Exists(FilePathZone)) { return; } try { ZoneTimestamps = ((IEnumerable<KeyValuePair<string, long>>)Data.Read<Dictionary<string, long>>(FilePathZone)).ToDictionary((Func<KeyValuePair<string, long>, Vector2i>)delegate(KeyValuePair<string, long> kvp) { //IL_0028: Unknown result type (might be due to invalid IL or missing references) string[] array = kvp.Key.Split(new char[1] { ',' }); return new Vector2i(int.Parse(array[0]), int.Parse(array[1])); }, (Func<KeyValuePair<string, long>, DateTime>)((KeyValuePair<string, long> kvp) => new DateTime(kvp.Value, DateTimeKind.Utc))); CronJob.Log.LogInfo((object)$"Reloading {ZoneTimestamps.Count} zone last runs."); } catch (Exception ex) { CronJob.Log.LogError((object)ex.StackTrace); } } public static void Track() { //IL_0033: Unknown result type (might be due to invalid IL or missing references) //IL_005d: Unknown result type (might be due to invalid IL or missing references) //IL_008f: Unknown result type (might be due to invalid IL or missing references) //IL_0094: Unknown result type (might be due to invalid IL or missing references) //IL_0096: Unknown result type (might be due to invalid IL or missing references) //IL_0099: Unknown result type (might be due to invalid IL or missing references) if (CronManager.ZoneJobs.Count == 0) { return; } HashSet<Vector2i> playerZones = GetPlayerZones(); Zones.Clear(); if (!ZNet.instance.IsDedicated()) { TrackPeer(Zones, ZNet.instance.GetReferencePosition()); } foreach (ZNetPeer peer in ZNet.instance.GetPeers()) { TrackPeer(Zones, peer.GetRefPos()); } foreach (Vector2i zone in Zones) { Poke(zone, playerZones.Contains(zone)); } } private static void Poke(Vector2i zone, bool hasPlayer) { //IL_0005: Unknown result type (might be due to invalid IL or missing references) //IL_001d: Unknown result type (might be due to invalid IL or missing references) //IL_0029: Unknown result type (might be due to invalid IL or missing references) //IL_0038: Unknown result type (might be due to invalid IL or missing references) DateTime? previous = (ZoneTimestamps.ContainsKey(zone) ? new DateTime?(ZoneTimestamps[zone]) : null); if (CronManager.Execute(zone, hasPlayer, previous)) { ZoneTimestamps[zone] = DateTime.UtcNow; } } private static void TrackPeer(HashSet<Vector2i> zones, Vector3 pos) { //IL_0006: Unknown result type (might be due to invalid IL or missing references) //IL_0007: Unknown result type (might be due to invalid IL or missing references) //IL_000c: Unknown result type (might be due to invalid IL or missing references) //IL_001b: Unknown result type (might be due to invalid IL or missing references) //IL_0066: Unknown result type (might be due to invalid IL or missing references) //IL_0026: Unknown result type (might be due to invalid IL or missing references) //IL_0057: Unknown result type (might be due to invalid IL or missing references) //IL_003d: Unknown result type (might be due to invalid IL or missing references) //IL_0047: Unknown result type (might be due to invalid IL or missing references) ZoneSystem instance = ZoneSystem.instance; Vector2i zone = ZoneSystem.GetZone(pos); int num = instance.m_activeArea + instance.m_activeDistantArea; Vector2i val = default(Vector2i); for (int i = zone.y - num; i <= zone.y + num; i++) { for (int j = zone.x - num; j <= zone.x + num; j++) { ((Vector2i)(ref val))..ctor(j, i); if (instance.IsZoneGenerated(val)) { zones.Add(val); } } } } private static HashSet<Vector2i> GetPlayerZones() { //IL_0020: Unknown result type (might be due to invalid IL or missing references) //IL_0025: Unknown result type (might be due to invalid IL or missing references) //IL_0050: Unknown result type (might be due to invalid IL or missing references) //IL_0055: Unknown result type (might be due to invalid IL or missing references) PlayerZones.Clear(); if (!ZNet.instance.IsDedicated()) { PlayerZones.Add(ZoneSystem.GetZone(ZNet.instance.GetReferencePosition())); } foreach (ZNetPeer peer in ZNet.instance.GetPeers()) { PlayerZones.Add(ZoneSystem.GetZone(peer.GetRefPos())); } return PlayerZones; } } } namespace Cronos { internal static class CalendarHelper { private const int DaysPerWeekCount = 7; private const long TicksPerMillisecond = 10000L; private const long TicksPerSecond = 10000000L; private const long TicksPerMinute = 600000000L; private const long TicksPerHour = 36000000000L; private const long TicksPerDay = 864000000000L; private const int DaysPerYear = 365; private const int DaysPer4Years = 1461; private const int DaysPer100Years = 36524; private const int DaysPer400Years = 146097; private static readonly int[] DaysToMonth365 = new int[13] { 0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334, 365 }; private static readonly int[] DaysToMonth366 = new int[13] { 0, 31, 60, 91, 121, 152, 182, 213, 244, 274, 305, 335, 366 }; private static readonly int[] DaysInMonth = new int[13] { -1, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 }; public static bool IsGreaterThan(int year1, int month1, int day1, int year2, int month2, int day2) { if (year1 != year2) { return year1 > year2; } if (month1 != month2) { return month1 > month2; } if (day2 != day1) { return day1 > day2; } return false; } public static long DateTimeToTicks(int year, int month, int day, int hour, int minute, int second) { int[] array = ((year % 4 == 0 && (year % 100 != 0 || year % 400 == 0)) ? DaysToMonth366 : DaysToMonth365); int num = year - 1; return (num * 365 + num / 4 - num / 100 + num / 400 + array[month - 1] + day - 1) * 864000000000L + ((long)hour * 3600L + (long)minute * 60L + second) * 10000000; } public static void FillDateTimeParts(long ticks, out int second, out int minute, out int hour, out int day, out int month, out int year) { second = (int)(ticks / 10000000 % 60); if (ticks % 10000000 != 0L) { second++; } minute = (int)(ticks / 600000000 % 60); hour = (int)(ticks / 36000000000L % 24); int num = (int)(ticks / 864000000000L); int num2 = num / 146097; num -= num2 * 146097; int num3 = num / 36524; if (num3 == 4) { num3 = 3; } num -= num3 * 36524; int num4 = num / 1461; num -= num4 * 1461; int num5 = num / 365; if (num5 == 4) { num5 = 3; } year = num2 * 400 + num3 * 100 + num4 * 4 + num5 + 1; num -= num5 * 365; int[] array = ((num5 == 3 && (num4 != 24 || num3 == 3)) ? DaysToMonth366 : DaysToMonth365); month = (num >> 5) + 1; while (num >= array[month]) { month++; } day = num - array[month - 1] + 1; } public static DayOfWeek GetDayOfWeek(int year, int month, int day) { int[] array = ((year % 4 == 0 && (year % 100 != 0 || year % 400 == 0)) ? DaysToMonth366 : DaysToMonth365); int num = year - 1; return (DayOfWeek)((int)((num * 365 + num / 4 - num / 100 + num / 400 + array[month - 1] + day - 1) * 864000000000L / 864000000000L + 1) % 7); } public static int GetDaysInMonth(int year, int month) { if (month != 2 || year % 4 != 0) { return DaysInMonth[month]; } if (year % 100 == 0 && year % 400 != 0) { return 28; } return 29; } public static int MoveToNearestWeekDay(int year, int month, int day) { DayOfWeek dayOfWeek = GetDayOfWeek(year, month, day); if (dayOfWeek != DayOfWeek.Saturday && dayOfWeek != 0) { return day; } if (dayOfWeek != 0) { if (day != CronField.DaysOfMonth.First) { return day - 1; } return day + 2; } if (day != GetDaysInMonth(year, month)) { return day + 1; } return day - 2; } public static bool IsNthDayOfWeek(int day, int n) { if (day - 7 * n < CronField.DaysOfMonth.First) { return day - 7 * (n - 1) >= CronField.DaysOfMonth.First; } return false; } public static bool IsLastDayOfWeek(int year, int month, int day) { return day + 7 > GetDaysInMonth(year, month); } } internal sealed class CronExpression : IEquatable<CronExpression> { private const long NotFound = 0L; private const int MaxYear = 2499; public static readonly CronExpression Yearly = Parse("0 0 1 1 *", CronFormat.Standard); public static readonly CronExpression Weekly = Parse("0 0 * * 0", CronFormat.Standard); public static readonly CronExpression Monthly = Parse("0 0 1 * *", CronFormat.Standard); public static readonly CronExpression Daily = Parse("0 0 * * *", CronFormat.Standard); public static readonly CronExpression Hourly = Parse("0 * * * *", CronFormat.Standard); public static readonly CronExpression EveryMinute = Parse("* * * * *", CronFormat.Standard); public static readonly CronExpression EverySecond = Parse("* * * * * *", CronFormat.IncludeSeconds); private static readonly TimeZoneInfo UtcTimeZone = TimeZoneInfo.Utc; private static readonly int[] DeBruijnPositions = new int[64] { 0, 1, 2, 53, 3, 7, 54, 27, 4, 38, 41, 8, 34, 55, 48, 28, 62, 5, 39, 46, 44, 42, 22, 9, 24, 35, 59, 56, 49, 18, 29, 11, 63, 52, 6, 26, 37, 40, 33, 47, 61, 45, 43, 21, 23, 58, 17, 10, 51, 25, 36, 32, 60, 20, 57, 16, 50, 31, 19, 15, 30, 14, 13, 12 }; private readonly long _second; private readonly long _minute; private readonly int _hour; private readonly int _dayOfMonth; private readonly short _month; private readonly byte _dayOfWeek; private readonly byte _nthDayOfWeek; private readonly byte _lastMonthOffset; private readonly CronExpressionFlag _flags; internal CronExpression(long second, long minute, int hour, int dayOfMonth, short month, byte dayOfWeek, byte nthDayOfWeek, byte lastMonthOffset, CronExpressionFlag flags) { _second = second; _minute = minute; _hour = hour; _dayOfMonth = dayOfMonth; _month = month; _dayOfWeek = dayOfWeek; _nthDayOfWeek = nthDayOfWeek; _lastMonthOffset = lastMonthOffset; _flags = flags; } public static CronExpression Parse(string expression) { return Parse(expression, CronFormat.Standard); } public static CronExpression Parse(string expression, CronFormat format) { if (string.IsNullOrEmpty(expression)) { throw new ArgumentNullException("expression"); } return CronParser.Parse(expression, format); } public static bool TryParse(string expression, out CronExpression cronExpression) { return TryParse(expression, CronFormat.Standard, out cronExpression); } public static bool TryParse(string expression, CronFormat format, out CronExpression cronExpression) { if (expression == null) { throw new ArgumentNullException("expression"); } try { cronExpression = Parse(expression, format); return true; } catch (CronFormatException) { cronExpression = null; return false; } } public DateTime? GetNextOccurrence(DateTime fromUtc, bool inclusive = false) { if (fromUtc.Kind != DateTimeKind.Utc) { ThrowWrongDateTimeKindException("fromUtc"); } if (fromUtc.Year > 2499) { ThrowDateTimeExceedsMaxException("fromUtc"); } long num = FindOccurrence(fromUtc.Ticks, inclusive); if (num == 0L) { return null; } return new DateTime(num, DateTimeKind.Utc); } public IEnumerable<DateTime> GetOccurrences(DateTime fromUtc, DateTime toUtc, bool fromInclusive = true, bool toInclusive = false) { if (fromUtc > toUtc) { ThrowFromShouldBeLessThanToException("fromUtc", "toUtc"); } if (fromUtc.Year > 2499) { ThrowDateTimeExceedsMaxException("fromUtc"); } if (toUtc.Year > 2499) { ThrowDateTimeExceedsMaxException("toUtc"); } DateTime? occurrence = GetNextOccurrence(fromUtc, fromInclusive); while (occurrence < toUtc || (occurrence == toUtc && toInclusive)) { yield return occurrence.Value; occurrence = GetNextOccurrence(occurrence.Value); } } public DateTime? GetNextOccurrence(DateTime fromUtc, TimeZoneInfo zone, bool inclusive = false) { if (fromUtc.Kind != DateTimeKind.Utc) { ThrowWrongDateTimeKindException("fromUtc"); } if (fromUtc.Year > 2499) { ThrowDateTimeExceedsMaxException("fromUtc"); } if (zone == null) { ThrowArgumentNullException("zone"); } if (zone == UtcTimeZone) { long num = FindOccurrence(fromUtc.Ticks, inclusive); if (num == 0L) { return null; } return new DateTime(num, DateTimeKind.Utc); } DateTimeOffset fromUtc2 = new DateTimeOffset(fromUtc); return GetOccurrenceConsideringTimeZone(fromUtc2, zone, inclusive)?.UtcDateTime; } public IEnumerable<DateTime> GetOccurrences(DateTime fromUtc, DateTime toUtc, TimeZoneInfo zone, bool fromInclusive = true, bool toInclusive = false) { if (fromUtc > toUtc) { ThrowFromShouldBeLessThanToException("fromUtc", "toUtc"); } if (fromUtc.Year > 2499) { ThrowDateTimeExceedsMaxException("fromUtc"); } if (toUtc.Year > 2499) { ThrowDateTimeExceedsMaxException("toUtc"); } DateTime? occurrence = GetNextOccurrence(fromUtc, zone, fromInclusive); while (occurrence < toUtc || (occurrence == toUtc && toInclusive)) { yield return occurrence.Value; occurrence = GetNextOccurrence(occurrence.Value, zone); } } public DateTimeOffset? GetNextOccurrence(DateTimeOffset from, TimeZoneInfo zone, bool inclusive = false) { if (from.Year > 2499) { ThrowDateTimeExceedsMaxException("from"); } if (zone == null) { ThrowArgumentNullException("zone"); } if (zone == UtcTimeZone) { long num = FindOccurrence(from.UtcTicks, inclusive); if (num == 0L) { return null; } return new DateTimeOffset(num, TimeSpan.Zero); } return GetOccurrenceConsideringTimeZone(from, zone, inclusive); } public IEnumerable<DateTimeOffset> GetOccurrences(DateTimeOffset from, DateTimeOffset to, TimeZoneInfo zone, bool fromInclusive = true, bool toInclusive = false) { if (from > to) { ThrowFromShouldBeLessThanToException("from", "to"); } if (from.Year > 2499) { ThrowDateTimeExceedsMaxException("from"); } if (to.Year > 2499) { ThrowDateTimeExceedsMaxException("to"); } DateTimeOffset? occurrence = GetNextOccurrence(from, zone, fromInclusive); while (occurrence < to || (occurrence == to && toInclusive)) { yield return occurrence.Value; occurrence = GetNextOccurrence(occurrence.Value, zone); } } public override string ToString() { StringBuilder stringBuilder = new StringBuilder(); if (_second != 1) { AppendFieldValue(stringBuilder, CronField.Seconds, _second).Append(' '); } AppendFieldValue(stringBuilder, CronField.Minutes, _minute).Append(' '); AppendFieldValue(stringBuilder, CronField.Hours, _hour).Append(' '); AppendDayOfMonth(stringBuilder, _dayOfMonth).Append(' '); AppendFieldValue(stringBuilder, CronField.Months, _month).Append(' '); AppendDayOfWeek(stringBuilder, _dayOfWeek); return stringBuilder.ToString(); } public bool Equals(CronExpression other) { if (other == null) { return false; } if (_second == other._second && _minute == other._minute && _hour == other._hour && _dayOfMonth == other._dayOfMonth && _month == other._month && _dayOfWeek == other._dayOfWeek && _nthDayOfWeek == other._nthDayOfWeek && _lastMonthOffset == other._lastMonthOffset) { return _flags == other._flags; } return false; } public override bool Equals(object obj) { return Equals(obj as CronExpression); } public override int GetHashCode() { long second = _second; int num = second.GetHashCode() * 397; second = _minute; int num2 = (((((num ^ second.GetHashCode()) * 397) ^ _hour) * 397) ^ _dayOfMonth) * 397; short month = _month; int num3 = (num2 ^ month.GetHashCode()) * 397; byte dayOfWeek = _dayOfWeek; int num4 = (num3 ^ dayOfWeek.GetHashCode()) * 397; dayOfWeek = _nthDayOfWeek; int num5 = (num4 ^ dayOfWeek.GetHashCode()) * 397; dayOfWeek = _lastMonthOffset; return ((num5 ^ dayOfWeek.GetHashCode()) * 397) ^ (int)_flags; } public static bool operator ==(CronExpression left, CronExpression right) { return object.Equals(left, right); } public static bool operator !=(CronExpression left, CronExpression right) { return !object.Equals(left, right); } private DateTimeOffset? GetOccurrenceConsideringTimeZone(DateTimeOffset fromUtc, TimeZoneInfo zone, bool inclusive) { if (!DateTimeHelper.IsRound(fromUtc)) { fromUtc = DateTimeHelper.FloorToSeconds(fromUtc); inclusive = false; } DateTimeOffset dateTimeOffset = TimeZoneInfo.ConvertTime(fromUtc, zone); DateTime dateTime = dateTimeOffset.DateTime; if (TimeZoneHelper.IsAmbiguousTime(zone, dateTime)) { TimeSpan offset = dateTimeOffset.Offset; TimeSpan utcOffset = zone.GetUtcOffset(dateTime); if (utcOffset != offset) { TimeSpan daylightOffset = TimeZoneHelper.GetDaylightOffset(zone, dateTime); DateTime dateTime2 = TimeZoneHelper.GetDaylightTimeEnd(zone, dateTime, daylightOffset).DateTime; long num = FindOccurrence(dateTime.Ticks, dateTime2.Ticks, inclusive); if (num != 0L) { return new DateTimeOffset(num, daylightOffset); } dateTime = TimeZoneHelper.GetStandardTimeStart(zone, dateTime, daylightOffset).DateTime; inclusive = true; } DateTime dateTime3 = TimeZoneHelper.GetAmbiguousIntervalEnd(zone, dateTime).DateTime; if (HasFlag(CronExpressionFlag.Interval)) { long num2 = FindOccurrence(dateTime.Ticks, dateTime3.Ticks - 1, inclusive); if (num2 != 0L) { return new DateTimeOffset(num2, utcOffset); } } dateTime = dateTime3; inclusive = true; } long num3 = FindOccurrence(dateTime.Ticks, inclusive); if (num3 == 0L) { return null; } DateTime dateTime4 = new DateTime(num3, DateTimeKind.Unspecified); if (zone.IsInvalidTime(dateTime4)) { return TimeZoneHelper.GetDaylightTimeStart(zone, dateTime4); } if (TimeZoneHelper.IsAmbiguousTime(zone, dateTime4)) { TimeSpan daylightOffset2 = TimeZoneHelper.GetDaylightOffset(zone, dateTime4); return new DateTimeOffset(dateTime4, daylightOffset2); } return new DateTimeOffset(dateTime4, zone.GetUtcOffset(dateTime4)); } private long FindOccurrence(long startTimeTicks, long endTimeTicks, bool startInclusive) { long num = FindOccurrence(startTimeTicks, startInclusive); if (num == 0L || num > endTimeTicks) { return 0L; } return num; } private long FindOccurrence(long ticks, bool startInclusive) { if (!startInclusive) { ticks++; } CalendarHelper.FillDateTimeParts(ticks, out var second, out var minute, out var hour, out var day, out var month, out var year); int firstSet = GetFirstSet(_dayOfMonth); int fieldValue = second; int fieldValue2 = minute; int fieldValue3 = hour; int fieldValue4 = day; int fieldValue5 = month; int num = year; if (!GetBit(_second, fieldValue) && !Move(_second, ref fieldValue)) { fieldValue2++; } if (!GetBit(_minute, fieldValue2) && !Move(_minute, ref fieldValue2)) { fieldValue3++; } if (!GetBit(_hour, fieldValue3) && !Move(_hour, ref fieldValue3)) { fieldValue4++; } if (HasFlag(CronExpressionFlag.NearestWeekday)) { fieldValue4 = CronField.DaysOfMonth.First; } if ((GetBit(_dayOfMonth, fieldValue4) || Move(_dayOfMonth, ref fieldValue4)) && GetBit(_month, fieldValue5)) { goto IL_00f6; } goto IL_01bc; IL_00f6: while (fieldValue4 <= GetLastDayOfMonth(num, fieldValue5)) { if (HasFlag(CronExpressionFlag.DayOfMonthLast)) { fieldValue4 = GetLastDayOfMonth(num, fieldValue5); } int num2 = fieldValue4; if (HasFlag(CronExpressionFlag.NearestWeekday)) { fieldValue4 = CalendarHelper.MoveToNearestWeekDay(num, fieldValue5, fieldValue4); } if (IsDayOfWeekMatch(num, fieldValue5, fieldValue4)) { if (!CalendarHelper.IsGreaterThan(num, fieldValue5, fieldValue4, year, month, day)) { if (fieldValue3 <= hour) { if (fieldValue2 > minute) { goto IL_017d; } goto IL_018a; } } else { fieldValue3 = GetFirstSet(_hour); } fieldValue2 = GetFirstSet(_minute); goto IL_017d; } goto IL_01a5; IL_01a5: fieldValue4 = num2; if (!Move(_dayOfMonth, ref fieldValue4)) { break; } continue; IL_018a: long num3 = CalendarHelper.DateTimeToTicks(num, fieldValue5, fieldValue4, fieldValue3, fieldValue2, fieldValue); if (num3 >= ticks) { return num3; } goto IL_01a5; IL_017d: fieldValue = GetFirstSet(_second); goto IL_018a; } goto IL_01bc; IL_01bc: if (!Move(_month, ref fieldValue5) && ++num >= 2499) { return 0L; } fieldValue4 = firstSet; goto IL_00f6; } private static bool Move(long fieldBits, ref int fieldValue) { if (fieldBits >> ++fieldValue == 0L) { fieldValue = GetFirstSet(fieldBits); return false; } fieldValue += GetFirstSet(fieldBits >> fieldValue); return true; } private int GetLastDayOfMonth(int year, int month) { return CalendarHelper.GetDaysInMonth(year, month) - _lastMonthOffset; } private bool IsDayOfWeekMatch(int year, int month, int day) { if ((HasFlag(CronExpressionFlag.DayOfWeekLast) && !CalendarHelper.IsLastDayOfWeek(year, month, day)) || (HasFlag(CronExpressionFlag.NthDayOfWeek) && !CalendarHelper.IsNthDayOfWeek(day, _nthDayOfWeek))) { return false; } if (_dayOfWeek == CronField.DaysOfWeek.AllBits) { return true; } DayOfWeek dayOfWeek = CalendarHelper.GetDayOfWeek(year, month, day); return ((_dayOfWeek >> (int)dayOfWeek) & 1) != 0; } private static int GetFirstSet(long value) { ulong num = (ulong)((value & -value) * 157587932685088877L) >> 58; return DeBruijnPositions[num]; } private bool HasFlag(CronExpressionFlag value) { return (_flags & value) != 0; } private static StringBuilder AppendFieldValue(StringBuilder expressionBuilder, CronField field, long fieldValue) { if (field.AllBits == fieldValue) { return expressionBuilder.Append('*'); } if (field == CronField.DaysOfWeek) { fieldValue &= ~(1 << field.Last); } int firstSet = GetFirstSet(fieldValue); while (true) { expressionBuilder.Append(firstSet); if (fieldValue >> ++firstSet == 0L) { break; } expressionBuilder.Append(','); firstSet = GetFirstSet(fieldValue >> firstSet << firstSet); } return expressionBuilder; } private StringBuilder AppendDayOfMonth(StringBuilder expressionBuilder, int domValue) { if (HasFlag(CronExpressionFlag.DayOfMonthLast)) { expressionBuilder.Append('L'); if (_lastMonthOffset != 0) { expressionBuilder.Append($"-{_lastMonthOffset}"); } } else { AppendFieldValue(expressionBuilder, CronField.DaysOfMonth, (uint)domValue); } if (HasFlag(CronExpressionFlag.NearestWeekday)) { expressionBuilder.Append('W'); } return expressionBuilder; } private void AppendDayOfWeek(StringBuilder expressionBuilder, int dowValue) { AppendFieldValue(expressionBuilder, CronField.DaysOfWeek, dowValue); if (HasFlag(CronExpressionFlag.DayOfWeekLast)) { expressionBuilder.Append('L'); } else if (HasFlag(CronExpressionFlag.NthDayOfWeek)) { expressionBuilder.Append($"#{_nthDayOfWeek}"); } } [MethodImpl(MethodImplOptions.NoInlining)] private static void ThrowFromShouldBeLessThanToException(string fromName, string toName) { throw new ArgumentException("The value of the " + fromName + " argument should be less than the value of the " + toName + " argument.", fromName); } [MethodImpl(MethodImplOptions.NoInlining)] private static void ThrowWrongDateTimeKindException(string paramName) { throw new ArgumentException("The supplied DateTime must have the Kind property set to Utc", paramName); } [MethodImpl(MethodImplOptions.NoInlining)] private static void ThrowDateTimeExceedsMaxException(string paramName) { throw new ArgumentException($"The supplied DateTime is after the supported year of {2499}.", paramName); } [MethodImpl(MethodImplOptions.NoInlining)] private static void ThrowArgumentNullException(string paramName) { throw new ArgumentNullException(paramName); } private static bool GetBit(long value, int index) { return (value & (1L << index)) != 0; } } [Flags] internal enum CronExpressionFlag : byte { DayOfMonthLast = 1, DayOfWeekLast = 2, Interval = 4, NearestWeekday = 8, NthDayOfWeek = 0x10 } internal sealed class CronField { private static readonly string[] MonthNames; private static readonly string[] DayOfWeekNames; private static readonly int[] MonthNamesArray; private static readonly int[] DayOfWeekNamesArray; public static readonly CronField DaysOfWeek; public static readonly CronField Months; public static readonly CronField DaysOfMonth; public static readonly CronField Hours; public static readonly CronField Minutes; public static readonly CronField Seconds; public readonly string Name; public readonly int First; public readonly int Last; public readonly int[] Names; public readonly bool CanDefineInterval; public readonly long AllBits; static CronField() { MonthNames = new string[13] { null, "JAN", "FEB", "MAR", "APR", "MAY", "JUN", "JUL", "AUG", "SEP", "OCT", "NOV", "DEC" }; DayOfWeekNames = new string[8] { "SUN", "MON", "TUE", "WED", "THU", "FRI", "SAT", "SUN" }; MonthNamesArray = new int[MonthNames.Length]; DayOfWeekNamesArray = new int[DayOfWeekNames.Length]; DaysOfWeek = new CronField("Days of week", 0, 7, DayOfWeekNamesArray, canDefineInterval: false); Months = new CronField("Months", 1, 12, MonthNamesArray, canDefineInterval: false); DaysOfMonth = new CronField("Days of month", 1, 31, null, canDefineInterval: false); Hours = new CronField("Hours", 0, 23, null, canDefineInterval: true); Minutes = new CronField("Minutes", 0, 59, null, canDefineInterval: true); Seconds = new CronField("Seconds", 0, 59, null, canDefineInterval: true); for (int i = 1; i < MonthNames.Length; i++) { string text = MonthNames[i].ToUpperInvariant(); _ = new char[3] { text[0], text[1], text[2] }; int num = (int)(text[0] | ((uint)text[1] << 8) | ((uint)text[2] << 16)); MonthNamesArray[i] = num; } for (int j = 0; j < DayOfWeekNames.Length; j++) { string text2 = DayOfWeekNames[j].ToUpperInvariant(); _ = new char[3] { text2[0], text2[1], text2[2] }; int num2 = (int)(text2[0] | ((uint)text2[1] << 8) | ((uint)text2[2] << 16)); DayOfWeekNamesArray[j] = num2; } } private CronField(string name, int first, int last, int[] names, bool canDefineInterval) { Name = name; First = first; Last = last; Names = names; CanDefineInterval = canDefineInterval; for (int i = First; i <= Last; i++) { AllBits |= 1L << i; } } public override string ToString() { return Name; } } [Flags] internal enum CronFormat { Standard = 0, IncludeSeconds = 1 } [Serializable] internal class CronFormatException : FormatException { private const string BaseMessage = "The given cron expression has an invalid format."; public CronFormatException() : this("The given cron expression has an invalid format.") { } public CronFormatException(string message) : this(message, null) { } public CronFormatException(string message, Exception innerException) : base("The given cron expression has an invalid format. " + message, innerException) { } internal CronFormatException(CronField field, string message) : this(string.Format("{0} {1}: {2}", "The given cron expression has an invalid format.", field, message)) { } protected CronFormatException(SerializationInfo info, StreamingContext context) : base(info, context) { } } internal static class CronParser { private const int MinNthDayOfWeek = 1; private const int MaxNthDayOfWeek = 5; private const int SundayBits = 129; public unsafe static CronExpression Parse(string expression, CronFormat format) { fixed (char* ptr = expression) { char* pointer = ptr; SkipWhiteSpaces(ref pointer); if (Accept(ref pointer, '@')) { CronExpression cronExpression = ParseMacro(ref pointer); SkipWhiteSpaces(ref pointer); if (cronExpression == null || !IsEndOfString(*pointer)) { ThrowFormatException("Macro: Unexpected character '{0}' on position {1}.", *pointer, pointer - ptr); } return cronExpression; } long value = 0L; byte nthWeekDay = 0; byte lastDayOffset = 0; CronExpressionFlag flags = (CronExpressionFlag)0; if (format == CronFormat.IncludeSeconds) { value = ParseField(CronField.Seconds, ref pointer, ref flags); ParseWhiteSpace(CronField.Seconds, ref pointer); } else { SetBit(ref value, CronField.Seconds.First); } long minute = ParseField(CronField.Minutes, ref pointer, ref flags); ParseWhiteSpace(CronField.Minutes, ref pointer); int hour = (int)ParseField(CronField.Hours, ref pointer, ref flags); ParseWhiteSpace(CronField.Hours, ref pointer); int dayOfMonth = (int)ParseDayOfMonth(ref pointer, ref flags, ref lastDayOffset); ParseWhiteSpace(CronField.DaysOfMonth, ref pointer); short month = (short)ParseField(CronField.Months, ref pointer, ref flags); ParseWhiteSpace(CronField.Months, ref pointer); byte b = (byte)ParseDayOfWeek(ref pointer, ref flags, ref nthWeekDay); ParseEndOfString(ref pointer); if ((b & 0x81u) != 0) { b = (byte)(b | 0x81u); } return new CronExpression(value, minute, hour, dayOfMonth, month, b, nthWeekDay, lastDayOffset, flags); } } private unsafe static void SkipWhiteSpaces(ref char* pointer) { while (IsWhiteSpace(*pointer)) { pointer++; } } private unsafe static void ParseWhiteSpace(CronField prevField, ref char* pointer) { if (!IsWhiteSpace(*pointer)) { ThrowFormatException(prevField, "Unexpected character '{0}'.", *pointer); } SkipWhiteSpaces(ref pointer); } private unsafe static void ParseEndOfString(ref char* pointer) { if (!IsWhiteSpace(*pointer) && !IsEndOfString(*pointer)) { ThrowFormatException(CronField.DaysOfWeek, "Unexpected character '{0}'.", *pointer); } SkipWhiteSpaces(ref pointer); if (!IsEndOfString(*pointer)) { ThrowFormatException("Unexpected character '{0}'.", *pointer); } } private unsafe static CronExpression ParseMacro(ref char* pointer) { switch (ToUpper(*(pointer++))) { case 65: if (AcceptCharacter(ref pointer, 'N') && AcceptCharacter(ref pointer, 'N') && AcceptCharacter(ref pointer, 'U') && AcceptCharacter(ref pointer, 'A') && AcceptCharacter(ref pointer, 'L') && AcceptCharacter(ref pointer, 'L') && AcceptCharacter(ref pointer, 'Y')) { return CronExpression.Yearly; } return null; case 68: if (AcceptCharacter(ref pointer, 'A') && AcceptCharacter(ref pointer, 'I') && AcceptCharacter(ref pointer, 'L') && AcceptCharacter(ref pointer, 'Y')) { return CronExpression.Daily; } return null; case 69: if (AcceptCharacter(ref pointer, 'V') && AcceptCharacter(ref pointer, 'E') && AcceptCharacter(ref pointer, 'R') && AcceptCharacter(ref pointer, 'Y') && Accept(ref pointer, '_')) { if (AcceptCharacter(ref pointer, 'M') && AcceptCharacter(ref pointer, 'I') && AcceptCharacter(ref pointer, 'N') && AcceptCharacter(ref pointer, 'U') && AcceptCharacter(ref pointer, 'T') && AcceptCharacter(ref pointer, 'E')) { return CronExpression.EveryMinute; } if (*(pointer - 1) != '_') { return null; } if (AcceptCharacter(ref pointer, 'S') && AcceptCharacter(ref pointer, 'E') && AcceptCharacter(ref pointer, 'C') && AcceptCharacter(ref pointer, 'O') && AcceptCharacter(ref pointer, 'N') && AcceptCharacter(ref pointer, 'D')) { return CronExpression.EverySecond; } } return null; case 72: if (AcceptCharacter(ref pointer, 'O') && AcceptCharacter(ref pointer, 'U') && AcceptCharacter(ref pointer, 'R') && AcceptCharacter(ref pointer, 'L') && AcceptCharacter(ref pointer, 'Y')) { return CronExpression.Hourly; } return null; case 77: if (AcceptCharacter(ref pointer, 'O') && AcceptCharacter(ref pointer, 'N') && AcceptCharacter(ref pointer, 'T') && AcceptCharacter(ref pointer, 'H') && AcceptCharacter(ref pointer, 'L') && AcceptCharacter(ref pointer, 'Y')) { return CronExpression.Monthly; } if (ToUpper(*(pointer - 1)) == 77 && AcceptCharacter(ref pointer, 'I') && AcceptCharacter(ref pointer, 'D') && AcceptCharacter(ref pointer, 'N') && AcceptCharacter(ref pointer, 'I') && AcceptCharacter(ref pointer, 'G') && AcceptCharacter(ref pointer, 'H') && AcceptCharacter(ref pointer, 'T')) { return CronExpression.Daily; } return null; case 87: if (AcceptCharacter(ref pointer, 'E') && AcceptCharacter(ref pointer, 'E') && AcceptCharacter(ref pointer, 'K') && AcceptCharacter(ref pointer, 'L') && AcceptCharacter(ref pointer, 'Y')) { return CronExpression.Weekly; } return null; case 89: if (AcceptCharacter(ref pointer, 'E') && AcceptCharacter(ref pointer, 'A') && AcceptCharacter(ref pointer, 'R') && AcceptCharacter(ref pointer, 'L') && AcceptCharacter(ref pointer, 'Y')) { return CronExpression.Yearly; } return null; default: pointer--; return null; } } private unsafe static long ParseField(CronField field, ref char* pointer, ref CronExpressionFlag flags) { if (Accept(ref pointer, '*') || Accept(ref pointer, '?')) { if (field.CanDefineInterval) { flags |= CronExpressionFlag.Interval; } return ParseStar(field, ref pointer); } int low = ParseValue(field, ref pointer); long num = ParseRange(field, ref pointer, low, ref flags); if (Accept(ref pointer, ',')) { num |= ParseList(field, ref pointer, ref flags); } return num; } private unsafe static long ParseDayOfMonth(ref char* pointer, ref CronExpressionFlag flags, ref byte lastDayOffset) { CronField daysOfMonth = CronField.DaysOfMonth; if (Accept(ref pointer, '*') || Accept(ref pointer, '?')) { return ParseStar(daysOfMonth, ref pointer); } if (AcceptCharacter(ref pointer, 'L')) { return ParseLastDayOfMonth(daysOfMonth, ref pointer, ref flags, ref lastDayOffset); } int num = ParseValue(daysOfMonth, ref pointer); if (AcceptCharacter(ref pointer, 'W')) { flags |= CronExpressionFlag.NearestWeekday; return GetBit(num); } long num2 = ParseRange(daysOfMonth, ref pointer, num, ref flags); if (Accept(ref pointer, ',')) { num2 |= ParseList(daysOfMonth, ref pointer, ref flags); } return num2; } private unsafe static long ParseDayOfWeek(ref char* pointer, ref CronExpressionFlag flags, ref byte nthWeekDay) { CronField daysOfWeek = CronField.DaysOfWeek; if (Accept(ref pointer, '*') || Accept(ref pointer, '?')) { return ParseStar(daysOfWeek, ref pointer); } int num = ParseValue(daysOfWeek, ref pointer); if (AcceptCharacter(ref pointer, 'L')) { return ParseLastWeekDay(num, ref flags); } if (Accept(ref pointer, '#')) { return ParseNthWeekDay(daysOfWeek, ref pointer, num, ref flags, out nthWeekDay); } long num2 = ParseRange(daysOfWeek, ref pointer, num, ref flags); if (Accept(ref pointer, ',')) { num2 |= ParseList(daysOfWeek, ref pointer, ref flags); } return num2; } private unsafe static long ParseStar(CronField field, ref char* pointer) { if (!Accept(ref pointer, '/')) { return field.AllBits; } return ParseStep(field, ref pointer, field.First, field.Last); } private unsafe static long ParseList(CronField field, ref char* pointer, ref CronExpressionFlag flags) { int low = ParseValue(field, ref pointer); long num = ParseRange(field, ref pointer, low, ref flags); while (Accept(ref pointer, ',')) { num |= ParseList(field, ref pointer, ref flags); } return num; } private unsafe static long ParseRange(CronField field, ref char* pointer, int low, ref CronExpressionFlag flags) { if (!Accept(ref pointer, '-')) { if (!Accept(ref pointer, '/')) { return GetBit(low); } if (field.CanDefineInterval) { flags |= CronExpressionFlag.Interval; } return ParseStep(field, ref pointer, low, field.Last); } if (field.CanDefineInterval) { flags |= CronExpressionFlag.Interval; } int num = ParseValue(field, ref pointer); if (Accept(ref pointer, '/')) { return ParseStep(field, ref pointer, low, num); } return GetBits(field, low, num, 1); } private unsafe static long ParseStep(CronField field, ref char* pointer, int low, int high) { int step = ParseNumber(field, ref pointer, 1, field.Last); return GetBits(field, low, high, step); } private unsafe static long ParseLastDayOfMonth(CronField field, ref char* pointer, ref CronExpressionFlag flags, ref byte lastMonthOffset) { flags |= CronExpressionFlag.DayOfMonthLast; if (Accept(ref pointer, '-')) { lastMonthOffset = (byte)ParseNumber(field, ref pointer, 0, field.Last - 1); } if (AcceptCharacter(ref pointer, 'W')) { flags |= CronExpressionFlag.NearestWeekday; } return field.AllBits; } private unsafe static long ParseNthWeekDay(CronField field, ref char* pointer, int dayOfWeek, ref CronExpressionFlag flags, out byte nthDayOfWeek) { nthDayOfWeek = (byte)ParseNumber(field, ref pointer, 1, 5); flags |= CronExpressionFlag.NthDayOfWeek; return GetBit(dayOfWeek); } private static long ParseLastWeekDay(int dayOfWeek, ref CronExpressionFlag flags) { flags |= CronExpressionFlag.DayOfWeekLast; return GetBit(dayOfWeek); } private unsafe static bool Accept(ref char* pointer, char character) { if (*pointer == character) { pointer++; return true; } return false; } private unsafe static bool AcceptCharacter(ref char* pointer, char character) { if (ToUpper(*pointer) == character) { pointer++; return true; } return false; } private unsafe static int ParseNumber(CronField field, ref char* pointer, int low, int high) { int number = GetNumber(ref pointer, null); if (number == -1 || number < low || number > high) { ThrowFormatException(field, "Value must be a number between {0} and {1} (all inclusive).", low, high); } return number; } private unsafe static int ParseValue(CronField field, ref char* pointer) { int number = GetNumber(ref pointer, field.Names); if (number == -1 || number < field.First || number > field.Last) { ThrowFormatException(field, "Value must be a number between {0} and {1} (all inclusive).", field.First, field.Last); } return number; } private static long GetBits(CronField field, int num1, int num2, int step) { if (num2 < num1) { return GetReversedRangeBits(field, num1, num2, step); } if (step == 1) { return (1L << num2 + 1) - (1L << num1); } return GetRangeBits(num1, num2, step); } private static long GetRangeBits(int low, int high, int step) { long value = 0L; for (int i = low; i <= high; i += step) { SetBit(ref value, i); } return value; } private static long GetReversedRangeBits(CronField field, int num1, int num2, int step) { int num3 = field.Last; if (field == CronField.DaysOfWeek) { num3--; } long rangeBits = GetRangeBits(num1, num3, step); num1 = field.First + step - (num3 - num1) % step - 1; return rangeBits | GetRangeBits(num1, num2, step); } private static long GetBit(int num1) { return 1L << num1; } private unsafe static int GetNumber(ref char* pointer, int[] names) { if (IsDigit(*pointer)) { int numeric = GetNumeric(*(pointer++)); if (!IsDigit(*pointer)) { return numeric; } numeric = numeric * 10 + GetNumeric(*(pointer++)); if (!IsDigit(*pointer)) { return numeric; } return -1; } if (names == null) { return -1; } if (!IsLetter(*pointer)) { return -1; } int num = ToUpper(*(pointer++)); if (!IsLetter(*pointer)) { return -1; } num |= ToUpper(*(pointer++)) << 8; if (!IsLetter(*pointer)) { return -1; } num |= ToUpper(*(pointer++)) << 16; int num2 = names.Length; for (int i = 0; i < num2; i++) { if (num == names[i]) { return i; } } return -1; } private static void SetBit(ref long value, int index) { value |= 1L << index; } private static bool IsEndOfString(int code) { return code == 0; } private static bool IsWhiteSpace(int code) { if (code != 9) { return code == 32; } return true; } private static bool IsDigit(int code) { if (code >= 48) { return code <= 57; } return false; } private static bool IsLetter(int code) { if (code < 65 || code > 90) { if (code >= 97) { return code <= 122; } return false; } return true; } private static int GetNumeric(int code) { return code - 48; } private static int ToUpper(int code) { if (code >= 97 && code <= 122) { return code - 32; } return code; } [MethodImpl(MethodImplOptions.NoInlining)] private static void ThrowFormatException(CronField field, string format, params object[] args) { throw new CronFormatException(field, string.Format(CultureInfo.CurrentCulture, format, args)); } [MethodImpl(MethodImplOptions.NoInlining)] private static void ThrowFormatException(string format, params object[] args) { throw new CronFormatException(string.Format(CultureInfo.CurrentCulture, format, args)); } } internal static class DateTimeHelper { private static readonly TimeSpan OneSecond = TimeSpan.FromSeconds(1.0); public static DateTimeOffset FloorToSeconds(DateTimeOffset dateTimeOffset) { return dateTimeOffset.AddTicks(-GetExtraTicks(dateTimeOffset.Ticks)); } public static bool IsRound(DateTimeOffset dateTimeOffset) { return GetExtraTicks(dateTimeOffset.Ticks) == 0; } private static long GetExtraTicks(long ticks) { TimeSpan oneSecond = OneSecond; return ticks % oneSecond.Ticks; } } internal static class TimeZoneHelper { public static bool IsAmbiguousTime(TimeZoneInfo zone, DateTime ambiguousTime) { return zone.IsAmbiguousTime(ambiguousTime.AddTicks(1L)); } public static TimeSpan GetDaylightOffset(TimeZoneInfo zone, DateTime ambiguousDateTime) { TimeSpan[] ambiguousOffsets = GetAmbiguousOffsets(zone, ambiguousDateTime); TimeSpan utcOffset = zone.GetUtcOffset(ambiguousDateTime); if (ambiguousOffsets[0] != utcOffset) { return ambiguousOffsets[0]; } return ambiguousOffsets[1]; } public static DateTimeOffset GetDaylightTimeStart(TimeZoneInfo zone, DateTime invalidDateTime) { DateTime dateTime = new DateTime(invalidDateTime.Year, invalidDateTime.Month, invalidDateTime.Day, invalidDateTime.Hour, invalidDateTime.Minute, 0, 0, invalidDateTime.Kind); while (zone.IsInvalidTime(dateTime)) { dateTime = dateTime.AddMinutes(1.0); } TimeSpan utcOffset = zone.GetUtcOffset(dateTime); return new DateTimeOffset(dateTime, utcOffset); } public static DateTimeOffset GetStandardTimeStart(TimeZoneInfo zone, DateTime ambiguousTime, TimeSpan daylightOffset) { DateTime dstTransitionEndDateTime = GetDstTransitionEndDateTime(zone, ambiguousTime); TimeSpan utcOffset = zone.GetUtcOffset(ambiguousTime); return new DateTimeOffset(dstTransitionEndDateTime, daylightOffset).ToOffset(utcOffset); } public static DateTimeOffset GetAmbiguousIntervalEnd(TimeZoneInfo zone, DateTime ambiguousTime) { DateTime dstTransitionEndDateTime = GetDstTransitionEndDateTime(zone, ambiguousTime); TimeSpan utcOffset = zone.GetUtcOffset(ambiguousTime); return new DateTimeOffset(dstTransitionEndDateTime, utcOffset); } public static DateTimeOffset GetDaylightTimeEnd(TimeZoneInfo zone, DateTime ambiguousTime, TimeSpan daylightOffset) { return new DateTimeOffset(GetDstTransitionEndDateTime(zone, ambiguousTime).AddTicks(-1L), daylightOffset); } private static TimeSpan[] GetAmbiguousOffsets(TimeZoneInfo zone, DateTime ambiguousTime) { return zone.GetAmbiguousTimeOffsets(ambiguousTime.AddTicks(1L)); } private static DateTime GetDstTransitionEndDateTime(TimeZoneInfo zone, DateTime ambiguousDateTime) { DateTime dateTime = new DateTime(ambiguousDateTime.Year, ambiguousDateTime.Month, ambiguousDateTime.Day, ambiguousDateTime.Hour, ambiguousDateTime.Minute, 0, 0, ambiguousDateTime.Kind); while (zone.IsAmbiguousTime(dateTime)) { dateTime = dateTime.AddMinutes(1.0); } return dateTime; } } }