Decompiled source of MonitorTakeover v1.0.0

Y4NGZMonitorTakeover.dll

Decompiled 5 hours ago
using 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.Http;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.Versioning;
using System.Security;
using System.Security.Cryptography;
using System.Security.Permissions;
using System.Text;
using System.Text.RegularExpressions;
using System.Threading;
using System.Threading.Tasks;
using BepInEx;
using BepInEx.Bootstrap;
using BepInEx.Configuration;
using BepInEx.Logging;
using HarmonyLib;
using Microsoft.CodeAnalysis;
using TMPro;
using Unity.Collections;
using Unity.Netcode;
using UnityEngine;
using UnityEngine.Networking;
using UnityEngine.Rendering;
using UnityEngine.Rendering.HighDefinition;
using UnityEngine.SceneManagement;
using UnityEngine.UI;
using UnityEngine.Video;
using Y4NGZCompany.Bootstrap;
using Y4NGZCompany.Core;
using Y4NGZCompany.Core.Compat;
using Y4NGZCompany.Experience.UITheme;
using Y4NGZCompany.ShipSystems.MonitorRow;
using Y4NGZCompany.ShipSystems.Rendering;
using Y4NGZCompany.ShipSystems.Takeover;
using Y4NGZCore.Configuration;
using Y4NGZCore.Diagnostics;
using Y4NGZCore.Lifecycle;
using Y4NGZCore.Modules;
using Y4NGZCore.Modules.Layout;
using Y4NGZCore.Modules.Monitor;
using Y4NGZCore.Modules.Net;
using Y4NGZCore.Modules.Quota;
using Y4NGZCore.Modules.ShipSystems;

[assembly: CompilationRelaxations(8)]
[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
[assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)]
[assembly: IgnoresAccessChecksTo("AmazingAssets.TerrainToMesh")]
[assembly: IgnoresAccessChecksTo("Assembly-CSharp-firstpass")]
[assembly: IgnoresAccessChecksTo("Assembly-CSharp")]
[assembly: IgnoresAccessChecksTo("ClientNetworkTransform")]
[assembly: IgnoresAccessChecksTo("com.olegknyazev.softmask")]
[assembly: IgnoresAccessChecksTo("DissonanceVoip")]
[assembly: IgnoresAccessChecksTo("DunGen")]
[assembly: IgnoresAccessChecksTo("DunGen.Integration.ASPP")]
[assembly: IgnoresAccessChecksTo("DunGen.Integration.UnityNav")]
[assembly: IgnoresAccessChecksTo("EasyTextEffects")]
[assembly: IgnoresAccessChecksTo("Facepunch Transport for Netcode for GameObjects")]
[assembly: IgnoresAccessChecksTo("Facepunch.Steamworks.Win64")]
[assembly: IgnoresAccessChecksTo("Unity.AI.Navigation")]
[assembly: IgnoresAccessChecksTo("Unity.Animation.Rigging")]
[assembly: IgnoresAccessChecksTo("Unity.Animation.Rigging.DocCodeExamples")]
[assembly: IgnoresAccessChecksTo("Unity.Burst")]
[assembly: IgnoresAccessChecksTo("Unity.Burst.Unsafe")]
[assembly: IgnoresAccessChecksTo("Unity.Collections")]
[assembly: IgnoresAccessChecksTo("Unity.Collections.LowLevel.ILSupport")]
[assembly: IgnoresAccessChecksTo("Unity.InputSystem")]
[assembly: IgnoresAccessChecksTo("Unity.InputSystem.ForUI")]
[assembly: IgnoresAccessChecksTo("Unity.Jobs")]
[assembly: IgnoresAccessChecksTo("Unity.Mathematics")]
[assembly: IgnoresAccessChecksTo("Unity.Multiplayer.Tools.Common")]
[assembly: IgnoresAccessChecksTo("Unity.Multiplayer.Tools.MetricTypes")]
[assembly: IgnoresAccessChecksTo("Unity.Multiplayer.Tools.NetStats")]
[assembly: IgnoresAccessChecksTo("Unity.Multiplayer.Tools.NetStatsMonitor.Component")]
[assembly: IgnoresAccessChecksTo("Unity.Multiplayer.Tools.NetStatsMonitor.Configuration")]
[assembly: IgnoresAccessChecksTo("Unity.Multiplayer.Tools.NetStatsMonitor.Implementation")]
[assembly: IgnoresAccessChecksTo("Unity.Multiplayer.Tools.NetStatsReporting")]
[assembly: IgnoresAccessChecksTo("Unity.Multiplayer.Tools.NetworkProfiler.Runtime")]
[assembly: IgnoresAccessChecksTo("Unity.Multiplayer.Tools.NetworkSolutionInterface")]
[assembly: IgnoresAccessChecksTo("Unity.Netcode.Components")]
[assembly: IgnoresAccessChecksTo("Unity.Netcode.Runtime")]
[assembly: IgnoresAccessChecksTo("Unity.Networking.Transport")]
[assembly: IgnoresAccessChecksTo("Unity.ProBuilder.Csg")]
[assembly: IgnoresAccessChecksTo("Unity.ProBuilder")]
[assembly: IgnoresAccessChecksTo("Unity.ProBuilder.KdTree")]
[assembly: IgnoresAccessChecksTo("Unity.ProBuilder.Poly2Tri")]
[assembly: IgnoresAccessChecksTo("Unity.ProBuilder.Stl")]
[assembly: IgnoresAccessChecksTo("Unity.Profiling.Core")]
[assembly: IgnoresAccessChecksTo("Unity.RenderPipelines.Core.Runtime")]
[assembly: IgnoresAccessChecksTo("Unity.RenderPipelines.Core.ShaderLibrary")]
[assembly: IgnoresAccessChecksTo("Unity.RenderPipelines.HighDefinition.Config.Runtime")]
[assembly: IgnoresAccessChecksTo("Unity.RenderPipelines.HighDefinition.Runtime")]
[assembly: IgnoresAccessChecksTo("Unity.RenderPipelines.ShaderGraph.ShaderGraphLibrary")]
[assembly: IgnoresAccessChecksTo("Unity.Services.Authentication")]
[assembly: IgnoresAccessChecksTo("Unity.Services.Core.Analytics")]
[assembly: IgnoresAccessChecksTo("Unity.Services.Core.Components")]
[assembly: IgnoresAccessChecksTo("Unity.Services.Core.Configuration")]
[assembly: IgnoresAccessChecksTo("Unity.Services.Core.Device")]
[assembly: IgnoresAccessChecksTo("Unity.Services.Core")]
[assembly: IgnoresAccessChecksTo("Unity.Services.Core.Environments")]
[assembly: IgnoresAccessChecksTo("Unity.Services.Core.Environments.Internal")]
[assembly: IgnoresAccessChecksTo("Unity.Services.Core.Internal")]
[assembly: IgnoresAccessChecksTo("Unity.Services.Core.Networking")]
[assembly: IgnoresAccessChecksTo("Unity.Services.Core.Registration")]
[assembly: IgnoresAccessChecksTo("Unity.Services.Core.Scheduler")]
[assembly: IgnoresAccessChecksTo("Unity.Services.Core.Telemetry")]
[assembly: IgnoresAccessChecksTo("Unity.Services.Core.Threading")]
[assembly: IgnoresAccessChecksTo("Unity.Services.QoS")]
[assembly: IgnoresAccessChecksTo("Unity.Services.Relay")]
[assembly: IgnoresAccessChecksTo("Unity.TextMeshPro")]
[assembly: IgnoresAccessChecksTo("Unity.Timeline")]
[assembly: IgnoresAccessChecksTo("Unity.VisualEffectGraph.Runtime")]
[assembly: IgnoresAccessChecksTo("Unity.XR.CoreUtils")]
[assembly: IgnoresAccessChecksTo("Unity.XR.Management")]
[assembly: IgnoresAccessChecksTo("Unity.XR.OpenXR")]
[assembly: IgnoresAccessChecksTo("Unity.XR.OpenXR.Features.ConformanceAutomation")]
[assembly: IgnoresAccessChecksTo("Unity.XR.OpenXR.Features.MetaQuestSupport")]
[assembly: IgnoresAccessChecksTo("Unity.XR.OpenXR.Features.MockRuntime")]
[assembly: IgnoresAccessChecksTo("Unity.XR.OpenXR.Features.OculusQuestSupport")]
[assembly: IgnoresAccessChecksTo("Unity.XR.OpenXR.Features.RuntimeDebugger")]
[assembly: IgnoresAccessChecksTo("UnityEngine.ARModule")]
[assembly: IgnoresAccessChecksTo("UnityEngine.NVIDIAModule")]
[assembly: IgnoresAccessChecksTo("UnityEngine.SpatialTracking")]
[assembly: IgnoresAccessChecksTo("UnityEngine.UI")]
[assembly: IgnoresAccessChecksTo("UnityEngine.XR.LegacyInputHelpers")]
[assembly: TargetFramework(".NETStandard,Version=v2.1", FrameworkDisplayName = ".NET Standard 2.1")]
[assembly: AssemblyCompany("Y4NGZMonitorTakeover")]
[assembly: AssemblyConfiguration("Release")]
[assembly: AssemblyDescription("Y4NGZ monitor takeover: the quota milestone registry and its rewards, milestone unlock gating, the quota announcement, the monitor takeover presentation, and Mask Man")]
[assembly: AssemblyFileVersion("1.0.0.0")]
[assembly: AssemblyInformationalVersion("1.0.0+80b22fc4dbdb42201cfbaf50386770acb6cd33dd")]
[assembly: AssemblyProduct("Y4NGZMonitorTakeover")]
[assembly: AssemblyTitle("Y4NGZMonitorTakeover")]
[assembly: SecurityPermission(SecurityAction.RequestMinimum, SkipVerification = true)]
[assembly: AssemblyVersion("1.0.0.0")]
[module: UnverifiableCode]
[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 Y4NGZCompany.Core
{
	public enum ForcedQuotaCompletionResult
	{
		StartedImmediately,
		QueuedUntilOrbit,
		HostOnly,
		InvalidQuota,
		NotInRound,
		Busy,
		Unavailable
	}
	public static class QuotaProgressionDebugApi
	{
		public static ForcedQuotaCompletionResult ForceCompletedQuota(int completedQuota)
		{
			try
			{
				return QuotaTakeoverDebugController.ForceCompletedQuota(completedQuota);
			}
			catch (Exception arg)
			{
				ModuleLog.Takeover.LogWarning((object)$"QuotaProgressionDebugApi.ForceCompletedQuota failed: {arg}");
				return ForcedQuotaCompletionResult.Unavailable;
			}
		}
	}
	public readonly struct QuotaTokenGrantRequest
	{
		public readonly int CompletedQuota;

		public readonly int TokensPerPlayer;

		public readonly string ActionId;

		public readonly bool IsReplay;

		public QuotaTokenGrantRequest(int completedQuota, int tokensPerPlayer, string actionId)
			: this(completedQuota, tokensPerPlayer, actionId, isReplay: false)
		{
		}

		public QuotaTokenGrantRequest(int completedQuota, int tokensPerPlayer, string actionId, bool isReplay)
		{
			CompletedQuota = completedQuota;
			TokensPerPlayer = tokensPerPlayer;
			ActionId = actionId ?? string.Empty;
			IsReplay = isReplay;
		}
	}
	public static class QuotaProgressionApi
	{
		public const int TokenGrantProtocolVersion = 1;

		private static Func<QuotaTokenGrantRequest, bool> _tokenGrantHandler;

		public static bool IsTokenGrantCapabilityAvailable => _tokenGrantHandler != null;

		public static void RegisterTokenGrantHandler(Func<QuotaTokenGrantRequest, bool> handler)
		{
			_tokenGrantHandler = handler;
		}

		public static void UnregisterTokenGrantHandler(Func<QuotaTokenGrantRequest, bool> handler)
		{
			if (_tokenGrantHandler == handler)
			{
				_tokenGrantHandler = null;
			}
		}

		internal static bool TryGrantTokens(QuotaTokenGrantRequest request)
		{
			if (_tokenGrantHandler != null)
			{
				return _tokenGrantHandler(request);
			}
			return false;
		}
	}
	internal enum TakeoverSoundtrackMode
	{
		Off,
		Replace,
		Mix
	}
	internal readonly struct TakeoverSoundtrackPlanWire
	{
		internal readonly TakeoverSoundtrackMode Mode;

		internal readonly string Source;

		internal readonly string Hash;

		internal readonly float Volume;

		internal static readonly TakeoverSoundtrackPlanWire Off = new TakeoverSoundtrackPlanWire(TakeoverSoundtrackMode.Off, string.Empty, string.Empty, 0f);

		internal bool IsOff
		{
			get
			{
				if (Mode != TakeoverSoundtrackMode.Off)
				{
					return string.IsNullOrWhiteSpace(Source);
				}
				return true;
			}
		}

		internal TakeoverSoundtrackPlanWire(TakeoverSoundtrackMode mode, string source, string hash, float volume)
		{
			Mode = mode;
			Source = source ?? string.Empty;
			Hash = hash ?? string.Empty;
			Volume = volume;
		}
	}
	internal readonly struct TakeoverMediaPlanWire
	{
		internal readonly int Seed;

		internal readonly int MaxPlayers;

		internal readonly string[] Pool;

		internal readonly string[] Hashes;

		internal bool HasMultipleSources
		{
			get
			{
				if (Pool != null)
				{
					return Pool.Length > 1;
				}
				return false;
			}
		}

		internal TakeoverMediaPlanWire(int seed, int maxPlayers, string[] pool, string[] hashes)
		{
			Seed = seed;
			MaxPlayers = maxPlayers;
			Pool = pool ?? Array.Empty<string>();
			Hashes = hashes ?? Array.Empty<string>();
		}
	}
	internal readonly struct QuotaTakeoverPayload
	{
		internal readonly int Quota;

		internal readonly string Dialogue;

		internal readonly string MediaFile;

		internal readonly string MediaHash;

		internal readonly int SelectionId;

		internal readonly string AlarmAudioFile;

		internal readonly string AlarmAudioHash;

		internal readonly string MumbleAudioFiles;

		internal readonly string MumbleAudioHashes;

		internal QuotaTakeoverPayload(int quota, string dialogue, string mediaFile, string mediaHash, int selectionId)
			: this(quota, dialogue, mediaFile, mediaHash, selectionId, string.Empty, string.Empty, string.Empty, string.Empty)
		{
		}

		internal QuotaTakeoverPayload(int quota, string dialogue, string mediaFile, string mediaHash, int selectionId, string alarmAudioFile, string alarmAudioHash, string mumbleAudioFiles, string mumbleAudioHashes)
		{
			Quota = quota;
			Dialogue = dialogue ?? string.Empty;
			MediaFile = mediaFile ?? string.Empty;
			MediaHash = mediaHash ?? string.Empty;
			SelectionId = selectionId;
			AlarmAudioFile = alarmAudioFile ?? string.Empty;
			AlarmAudioHash = alarmAudioHash ?? string.Empty;
			MumbleAudioFiles = mumbleAudioFiles ?? string.Empty;
			MumbleAudioHashes = mumbleAudioHashes ?? string.Empty;
		}

		internal QuotaTakeoverPayload WithDialogue(string dialogue, int selectionId)
		{
			return new QuotaTakeoverPayload(Quota, dialogue, MediaFile, MediaHash, selectionId, AlarmAudioFile, AlarmAudioHash, MumbleAudioFiles, MumbleAudioHashes);
		}
	}
	internal readonly struct TakeoverAudioOverrideFile
	{
		internal readonly string Path;

		internal readonly string Hash;

		internal bool IsValid => !string.IsNullOrEmpty(Path);

		internal string CacheKey => (Path ?? string.Empty) + "|" + (Hash ?? string.Empty);

		internal TakeoverAudioOverrideFile(string path, string hash)
		{
			Path = path ?? string.Empty;
			Hash = hash ?? string.Empty;
		}
	}
	internal sealed class QuotaMilestoneConfig
	{
		internal readonly int Quota;

		internal readonly ConfigEntry<bool> Enable;

		internal readonly ConfigEntry<string> Dialogue;

		internal readonly ConfigEntry<string> DialogueFile;

		internal readonly ConfigEntry<string> MediaFile;

		internal readonly ConfigEntry<string> UnlockContracts;

		internal readonly ConfigEntry<string> UnlockShipUpgradePurchases;

		internal readonly ConfigEntry<string> UnlockConstellations;

		internal readonly ConfigEntry<string> UnlockSuits;

		internal readonly ConfigEntry<string> UnlockStoreItems;

		internal readonly ConfigEntry<int> GrantTokensPerPlayer;

		internal readonly ConfigEntry<int> GrantGroupCredits;

		internal readonly ConfigEntry<float> GrantShipFuel;

		internal IEnumerable<string> Contracts => Split(UnlockContracts.Value);

		internal IEnumerable<string> Upgrades => Split(UnlockShipUpgradePurchases.Value);

		internal IEnumerable<string> Constellations => Split(UnlockConstellations.Value);

		internal IEnumerable<string> Suits => Split(UnlockSuits.Value);

		internal IEnumerable<string> StoreItems => Split(UnlockStoreItems.Value);

		internal QuotaMilestoneConfig(ConfigFile config, int quota)
		{
			//IL_012a: Unknown result type (might be due to invalid IL or missing references)
			//IL_0134: Expected O, but got Unknown
			//IL_0157: Unknown result type (might be due to invalid IL or missing references)
			//IL_0161: Expected O, but got Unknown
			//IL_018c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0196: Expected O, but got Unknown
			Quota = quota;
			string text = MonitorTakeoverConfigSchemaMigration.QuotaSection(quota);
			Enable = config.Bind<bool>(text, "Enable", true, "Enable this quota milestone.");
			Dialogue = config.Bind<string>(text, "Dialogue", string.Empty, "Inline takeover dialogue. Use \\n for line breaks; blank uses built-in dialogue.");
			DialogueFile = config.Bind<string>(text, "DialogueFile", string.Empty, "Optional UTF-8 .txt file under BepInEx/config/Y4NGZCompany/MonitorTakeovers.");
			MediaFile = config.Bind<string>(text, "MediaFile", string.Empty, "Optional PNG, JPG, or MP4 filename in BepInEx/config/Y4NGZCompany/MonitorTakeovers used for this quota's takeover, or a full YouTube link (watch, Shorts, Live, Embed, or youtu.be). Files need an identical copy on every player or that player falls back to the built-in media; a YouTube link is downloaded and cached by each player in the background instead.");
			UnlockContracts = config.Bind<string>(text, "UnlockContracts", string.Empty, "Comma-separated stable contract IDs that begin rolling at this quota.");
			UnlockShipUpgradePurchases = config.Bind<string>(text, "UnlockShipUpgradePurchases", string.Empty, "Comma-separated stable ship-upgrade IDs unlocked for purchase at this quota.");
			UnlockConstellations = config.Bind<string>(text, "UnlockConstellations", string.Empty, "Comma-separated constellation names unlocked at this quota.");
			UnlockSuits = config.Bind<string>(text, "UnlockSuits", string.Empty, "Comma-separated suit unlockable names (as in StartOfRound.unlockablesList, e.g. Green suit) hidden from the suit rack until this quota.");
			UnlockStoreItems = config.Bind<string>(text, "UnlockStoreItems", string.Empty, "Comma-separated store item names (as in Terminal.buyableItemsList, e.g. Pro-flashlight) unavailable for purchase until this quota.");
			GrantTokensPerPlayer = config.Bind<int>(text, "GrantTokensPerPlayer", 3, new ConfigDescription("Tokens granted once per eligible player through Y4NGZUpgrades.", (AcceptableValueBase)(object)new AcceptableValueRange<int>(0, 1000), Array.Empty<object>()));
			GrantGroupCredits = config.Bind<int>(text, "GrantGroupCredits", 0, new ConfigDescription("Group credits granted once when this quota completes.", (AcceptableValueBase)(object)new AcceptableValueRange<int>(0, 100000), Array.Empty<object>()));
			GrantShipFuel = config.Bind<float>(text, "GrantShipFuel", 0f, new ConfigDescription("Ship fuel granted once when this quota completes.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0f, 1000f), Array.Empty<object>()));
		}

		private static IEnumerable<string> Split(string value)
		{
			return from item in (value ?? string.Empty).Split(new char[1] { ',' }, StringSplitOptions.RemoveEmptyEntries)
				select item.Trim() into item
				where item.Length > 0
				select item;
		}
	}
	internal static class QuotaProgressionRegistry
	{
		private sealed class ModuleGate : IQuotaUnlockGate
		{
			public bool IsFeatureUnlocked(string category, string stableId, int completedQuotas)
			{
				return category switch
				{
					"assignment" => IsContractUnlocked(stableId, completedQuotas), 
					"ship_upgrade" => IsShipUpgradePurchaseUnlocked(stableId, completedQuotas), 
					"constellation" => IsConstellationUnlocked(stableId, completedQuotas), 
					"suit" => IsSuitUnlocked(stableId), 
					"store_item" => IsStoreItemUnlocked(stableId), 
					_ => true, 
				};
			}

			public bool TryGetUnlockQuota(string category, string stableId, out int quota)
			{
				if (category == "constellation")
				{
					return TryGetConstellationUnlockQuota(stableId, out quota);
				}
				quota = 0;
				return false;
			}
		}

		private sealed class ModulePresentationPolicy : IQuotaPresentationPolicy
		{
			public bool RewardsCeremonyEnabled => QuotaProgressionRegistry.RewardsCeremonyEnabled;
		}

		internal sealed class QuotaMilestoneSummary
		{
			internal int Quota;

			internal bool Enabled;

			internal IReadOnlyList<string> Contracts;

			internal IReadOnlyList<string> ShipUpgrades;

			internal IReadOnlyList<string> Constellations;

			internal IReadOnlyList<string> Suits;

			internal IReadOnlyList<string> StoreItems;

			internal int TokensPerPlayer;

			internal int GroupCredits;

			internal float ShipFuel;
		}

		[CompilerGenerated]
		private static class <>O
		{
			public static Action<QuotaRolloverFinalized> <0>__OnQuotaRolloverFinalized;

			public static Action<string> <1>__OnRewardProviderAvailable;

			public static HandleNamedMessageDelegate <2>__OnPayloadMessage;

			public static HandleNamedMessageDelegate <3>__OnGateSnapshotMessage;

			public static HandleNamedMessageDelegate <4>__OnSoundtrackMessage;

			public static HandleNamedMessageDelegate <5>__OnMediaPlanMessage;

			public static Action<ulong> <6>__OnClientConnected;

			public static Func<string, string> <7>__Normalize;

			public static Func<char, bool> <8>__IsLetterOrDigit;
		}

		private const string LedgerSaveKey = "Y4NGZCompany.QuotaProgressionLedger.v1";

		private const string PayloadMessage = "Y4NGZCompany.QuotaTakeoverPayload.v2";

		private const string GateSnapshotMessage = "Y4NGZCompany.QuotaProgressionSnapshot.v2";

		private const string SoundtrackMessage = "Y4NGZCompany.QuotaTakeoverSoundtrack.v1";

		private const string MediaPlanMessage = "Y4NGZCompany.QuotaTakeoverMediaPlan.v1";

		private const int MaxPerMonitorPoolEntries = 6;

		private const int MaxPerMonitorPoolChars = 3000;

		private static readonly Dictionary<int, QuotaMilestoneConfig> Milestones = new Dictionary<int, QuotaMilestoneConfig>();

		private static readonly string[] MediaExtensions = new string[4] { ".png", ".jpg", ".jpeg", ".mp4" };

		private static readonly string[] AudioExtensions = new string[3] { ".mp3", ".wav", ".ogg" };

		private const int MaxOverrideFileNameChars = 120;

		private const int MaxYoutubeUrlChars = 500;

		private const int MaxMumbleOverrideClips = 12;

		private const int MaxMumbleOverrideNameChars = 1000;

		private const int MaxMumbleOverrideHashChars = 1000;

		private static ConfigEntry<bool> _enabled;

		private static ConfigEntry<bool> _retryPending;

		private static ConfigEntry<bool> _randomizeDialogue;

		private static ConfigEntry<bool> _randomizeWithoutReplacement;

		private static ConfigEntry<string> _defaultMediaFile;

		private static ConfigEntry<string> _mumbleAudioFiles;

		private static ConfigEntry<string> _alarmAudioFile;

		private static ConfigEntry<string> _soundtrackSource;

		private static ConfigEntry<TakeoverSoundtrackMode> _soundtrackMode;

		private static ConfigEntry<float> _soundtrackVolume;

		private static ConfigEntry<bool> _loopMedia;

		private static ConfigEntry<bool> _enableMediaAudio;

		private static ConfigEntry<bool> _useMediaLengthAsDuration;

		private static ConfigEntry<bool> _randomPerMonitorMedia;

		private static ConfigEntry<string> _perMonitorMediaPool;

		private static ConfigEntry<int> _maxConcurrentMediaPlayers;

		private static ConfigEntry<bool> _enableUnlockAnnouncement;

		private static ConfigEntry<float> _unlockAnnouncementSeconds;

		private static ConfigEntry<bool> _announceOnBothMonitors;

		private static ConfigEntry<bool> _rewardsCeremonyEnabled;

		private static ConfigEntry<bool> _ceremonyMirrorBothMonitors;

		private static ConfigEntry<float> _ceremonyTypewriterSecondsPerChar;

		private static ConfigEntry<bool> _ceremonyLineConfirmSting;

		private static ConfigEntry<bool> _ceremonyRestoreStatic;

		private static ManualLogSource _log;

		private static CustomMessagingManager _registeredMessaging;

		private static NetworkManager _registeredNetworkManager;

		private static QuotaTakeoverPayload _currentPayload;

		private static bool _hasCurrentPayload;

		private static TakeoverSoundtrackPlanWire _soundtrackPlan = TakeoverSoundtrackPlanWire.Off;

		private static TakeoverMediaPlanWire _mediaPlan;

		private static bool _hasAuthoritativeUpgradeSnapshot;

		private static readonly HashSet<string> AuthoritativeUnlockedUpgrades = new HashSet<string>(StringComparer.OrdinalIgnoreCase);

		private static readonly Dictionary<string, int> AuthoritativeConstellationUnlockQuotas = new Dictionary<string, int>(StringComparer.OrdinalIgnoreCase);

		private static readonly Dictionary<string, int> AuthoritativeSuitUnlockQuotas = new Dictionary<string, int>(StringComparer.OrdinalIgnoreCase);

		private static readonly Dictionary<string, int> AuthoritativeStoreItemUnlockQuotas = new Dictionary<string, int>(StringComparer.OrdinalIgnoreCase);

		private static int _authoritativeQuota;

		private static float _nextSnapshotAt;

		private static string _activeSaveName = string.Empty;

		private static string _cachedLedgerSave = string.Empty;

		private static HashSet<string> _cachedLedger;

		private static Action<ulong> _clientConnectedHandler;

		private static float _nextTokenReplayAt;

		private static int _tokenReplayAttemptsRemaining;

		private const float TokenReplayFirstDelaySeconds = 6f;

		private const float TokenReplayIntervalSeconds = 6f;

		private const int TokenReplayAttempts = 3;

		private static ModuleGate _moduleGate;

		private static ModulePresentationPolicy _modulePresentationPolicy;

		private static bool _rolloverSubscribed;

		private const string FuelLedgerKind = "fuel";

		private const string CreditsLedgerKind = "credits";

		internal static string MediaDirectory => Path.Combine(Y4NGZCompanyPaths.LocalDataDir, "MonitorTakeovers");

		internal static bool RandomizeDialogue => _randomizeDialogue?.Value ?? false;

		internal static bool LoopMedia => _loopMedia?.Value ?? true;

		internal static bool EnableMediaAudio => _enableMediaAudio?.Value ?? false;

		internal static bool UseMediaLengthAsDuration => _useMediaLengthAsDuration?.Value ?? false;

		internal static bool EnableUnlockAnnouncement => _enableUnlockAnnouncement?.Value ?? true;

		internal static float UnlockAnnouncementSeconds => _unlockAnnouncementSeconds?.Value ?? 4.5f;

		internal static bool AnnounceOnBothMonitors => _announceOnBothMonitors?.Value ?? false;

		internal static bool RewardsCeremonyEnabled => _rewardsCeremonyEnabled?.Value ?? true;

		internal static bool CeremonyMirrorBothMonitors => _ceremonyMirrorBothMonitors?.Value ?? true;

		internal static float CeremonyTypewriterSecondsPerChar => Mathf.Clamp(_ceremonyTypewriterSecondsPerChar?.Value ?? 0.03f, 0.005f, 0.2f);

		internal static bool CeremonyLineConfirmSting => _ceremonyLineConfirmSting?.Value ?? true;

		internal static bool CeremonyRestoreStatic => _ceremonyRestoreStatic?.Value ?? true;

		internal static TakeoverSoundtrackMode SoundtrackMode => _soundtrackMode?.Value ?? TakeoverSoundtrackMode.Off;

		internal static float SoundtrackVolume => Mathf.Clamp01(_soundtrackVolume?.Value ?? 0.8f);

		internal static bool RandomPerMonitorMedia => _randomPerMonitorMedia?.Value ?? false;

		internal static int MaxConcurrentMediaPlayers => Mathf.Clamp(_maxConcurrentMediaPlayers?.Value ?? 3, 1, 6);

		internal static string[] PerMonitorMediaPool()
		{
			return SplitSemicolonList(_perMonitorMediaPool?.Value);
		}

		internal static void Initialize(ConfigFile config, ManualLogSource log)
		{
			//IL_0132: Unknown result type (might be due to invalid IL or missing references)
			//IL_013c: Expected O, but got Unknown
			//IL_01e9: Unknown result type (might be due to invalid IL or missing references)
			//IL_01f3: Expected O, but got Unknown
			//IL_023c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0246: Expected O, but got Unknown
			//IL_02c5: Unknown result type (might be due to invalid IL or missing references)
			//IL_02cf: Expected O, but got Unknown
			_log = log;
			_enabled = config.Bind<bool>("General", "EnableQuotaProgression", true, "Enable quota-based unlock gates, rewards, and takeover presentation.");
			_retryPending = config.Bind<bool>("General", "RetryPendingOptionalRewards", true, "Retry optional rewards such as tokens when their provider becomes available later.");
			_randomizeDialogue = config.Bind<bool>("Presentation", "RandomizeDialogue", false, "Choose configured dialogue from any enabled quota instead of attaching dialogue to its quota.");
			_randomizeWithoutReplacement = config.Bind<bool>("Presentation", "RandomizeWithoutReplacement", true, "When randomizing, use every available passage once before the deterministic pool repeats.");
			_defaultMediaFile = config.Bind<string>("Media", "DefaultMediaFile", string.Empty, "Optional PNG, JPG, or MP4 filename in BepInEx/config/Y4NGZCompany/MonitorTakeovers used when a quota has no MediaFile, or a full YouTube link (watch, Shorts, Live, Embed, or youtu.be). Files need an identical copy on every player or that player falls back to the built-in media; a YouTube link is downloaded and cached by each player in the background instead.");
			_mumbleAudioFiles = config.Bind<string>("Audio", "MumbleAudioFiles", string.Empty, "Comma-separated MP3, WAV, or OGG filenames in BepInEx/config/Y4NGZCompany/MonitorTakeovers that replace the quota takeover's mumble voice clips. Every player in the lobby needs an identical copy or that player falls back to the built-in audio.");
			_alarmAudioFile = config.Bind<string>("Audio", "AlarmAudioFile", string.Empty, "Single MP3, WAV, or OGG filename in BepInEx/config/Y4NGZCompany/MonitorTakeovers that replaces the quota takeover's alarm sound. Every player in the lobby needs an identical copy or that player falls back to the built-in audio.");
			_soundtrackSource = config.Bind<string>("Audio", "SoundtrackSource", string.Empty, "Optional MP3, WAV, or OGG filename in BepInEx/config/Y4NGZCompany/MonitorTakeovers, or a full YouTube link, played as the quota takeover's soundtrack. Files need an identical copy on every player; a YouTube link is downloaded and cached by each player in the background. Ignored when SoundtrackMode is Off.");
			_soundtrackMode = config.Bind<TakeoverSoundtrackMode>("Audio", "SoundtrackMode", TakeoverSoundtrackMode.Off, "Off keeps the built-in takeover audio. Replace silences the power-down whine, the alarm, the drone bed, and the mumble voices for the whole takeover and plays only the soundtrack. Mix layers the soundtrack over them.");
			_soundtrackVolume = config.Bind<float>("Audio", "SoundtrackVolume", 0.8f, new ConfigDescription("Soundtrack volume (0 = silent, 1 = full). Independent of Mumble Volume.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0f, 1f), Array.Empty<object>()));
			_loopMedia = config.Bind<bool>("Media", "LoopMedia", true, "Loop configured MP4 media for the duration of the takeover.");
			_enableMediaAudio = config.Bind<bool>("Media", "EnableMediaAudio", false, "Play the configured MP4 audio track. Disabled by default so it does not compete with takeover speech.");
			_useMediaLengthAsDuration = config.Bind<bool>("Media", "UseMediaLengthAsDuration", false, "Hold the takeover for the configured video's full length instead of Takeover Duration. Applies only when the takeover media is a video (local MP4 or YouTube link); clamped to 30 minutes.");
			_randomPerMonitorMedia = config.Bind<bool>("Media", "RandomPerMonitorMedia", false, "Play different media on different ship monitors during the quota takeover. Off keeps every monitor on one shared video.");
			_perMonitorMediaPool = config.Bind<string>("Media", "PerMonitorMediaPool", string.Empty, "Semicolon-separated list of PNG/JPG/MP4 filenames in BepInEx/config/Y4NGZCompany/MonitorTakeovers and/or full YouTube links used when RandomPerMonitorMedia is on. Empty falls back to the quota's MediaFile and DefaultMediaFile. Files need an identical copy on every player; links are cached by each player. Example: clip1.mp4;https://youtu.be/abc;poster.png");
			_maxConcurrentMediaPlayers = config.Bind<int>("Media", "MaxConcurrentMediaPlayers", 3, new ConfigDescription("Maximum simultaneous video decoders during a per-monitor takeover. Extra monitors share the players in a fixed rotation.", (AcceptableValueBase)(object)new AcceptableValueRange<int>(1, 6), Array.Empty<object>()));
			_enableUnlockAnnouncement = config.Bind<bool>("Rewards Ceremony", "EnableUnlockAnnouncement", true, "After the takeover ends, show the quota's new unlocks and grants on the left large ship monitor.");
			_unlockAnnouncementSeconds = config.Bind<float>("Rewards Ceremony", "UnlockAnnouncementSeconds", 4.5f, new ConfigDescription("Seconds the unlock announcement stays on the monitor.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(2f, 15f), Array.Empty<object>()));
			_announceOnBothMonitors = config.Bind<bool>("Rewards Ceremony", "AnnounceOnBothMonitors", false, "Also show the unlock announcement on the right large ship monitor.");
			_rewardsCeremonyEnabled = config.Bind<bool>("Rewards Ceremony", "RewardsCeremonyEnabled", true, "Present the unlock announcement as a ceremony: each reward line types out with a tick per step and a confirm when it lands, and the display hold starts after typing finishes.");
			_ceremonyMirrorBothMonitors = config.Bind<bool>("Rewards Ceremony", "CeremonyMirrorBothMonitors", true, "During the rewards ceremony, mirror the announcement onto the right large ship monitor as well.");
			_ceremonyTypewriterSecondsPerChar = config.Bind<float>("Rewards Ceremony", "CeremonyTypewriterSecondsPerChar", 0.03f, new ConfigDescription("Seconds each typed character takes during the rewards ceremony.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0.005f, 0.2f), Array.Empty<object>()));
			_ceremonyLineConfirmSting = config.Bind<bool>("Rewards Ceremony", "CeremonyLineConfirmSting", true, "Play a confirm sting as each reward line finishes typing.");
			_ceremonyRestoreStatic = config.Bind<bool>("Rewards Ceremony", "CeremonyRestoreStatic", true, "End the rewards ceremony with a brief CRT static burst before the monitors return to their normal views instead of a hard cut.");
			Directory.CreateDirectory(MediaDirectory);
			Milestones.Clear();
			for (int i = 1; i <= 9; i++)
			{
				Milestones[i] = new QuotaMilestoneConfig(config, i);
			}
			if (_enabled.Value)
			{
				YoutubeVideoResolver.Prefetch(_defaultMediaFile?.Value);
				YoutubeVideoResolver.Prefetch(_soundtrackSource?.Value);
				foreach (QuotaMilestoneConfig value in Milestones.Values)
				{
					if (value.Enable.Value)
					{
						YoutubeVideoResolver.Prefetch(value.MediaFile.Value);
					}
				}
				string[] array = PerMonitorMediaPool();
				for (int j = 0; j < array.Length; j++)
				{
					YoutubeVideoResolver.Prefetch(array[j]);
				}
				WarnOnCommaSeparatedMediaPool();
			}
			WriteEffectiveAudit();
			EnsureModuleWiring();
		}

		internal static void EnsureModuleWiring()
		{
			if (_moduleGate == null)
			{
				_moduleGate = new ModuleGate();
				_modulePresentationPolicy = new ModulePresentationPolicy();
				ModuleCapabilityRegistry.Register("y4ngz.quota.unlocks", (object)_moduleGate);
				ModuleCapabilityRegistry.Register("y4ngz.quota.presentation_policy", (object)_modulePresentationPolicy);
			}
			if (!_rolloverSubscribed)
			{
				_rolloverSubscribed = true;
				QuotaRolloverNotifications.QuotaRolloverFinalized += OnQuotaRolloverFinalized;
				QuotaRewardBroker.RewardProviderAvailable += OnRewardProviderAvailable;
			}
		}

		private static void OnQuotaRolloverFinalized(QuotaRolloverFinalized rollover)
		{
			//IL_002e: Unknown result type (might be due to invalid IL or missing references)
			ConfigEntry<bool> enabled = _enabled;
			if (enabled != null && enabled.Value)
			{
				NetworkManager singleton = NetworkManager.Singleton;
				if (singleton != null && singleton.IsServer)
				{
					int num = Math.Max(0, rollover.CompletedQuotaCount);
					_currentPayload = BuildPayload(num);
					_hasCurrentPayload = true;
					SendPayload(_currentPayload);
					SendSoundtrack(BuildSoundtrackPlan());
					SendMediaPlan(BuildMediaPlan(_currentPayload));
					ApplyRewardsThroughQuota(num);
					QuotaProgressionSuitGate.OnUnlockStateMaybeChanged(num);
				}
			}
		}

		private static void OnRewardProviderAvailable(string rewardKind)
		{
			ConfigEntry<bool> enabled = _enabled;
			if (enabled == null || !enabled.Value)
			{
				return;
			}
			NetworkManager singleton = NetworkManager.Singleton;
			if (singleton != null && singleton.IsServer)
			{
				int num = (((Object)(object)TimeOfDay.Instance != (Object)null) ? Math.Max(0, TimeOfDay.Instance.timesFulfilledQuota) : 0);
				if (num >= 1)
				{
					ApplyRewardsThroughQuota(num);
				}
			}
		}

		internal static void Tick()
		{
			//IL_014e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0153: Unknown result type (might be due to invalid IL or missing references)
			//IL_0159: Expected O, but got Unknown
			//IL_0174: Unknown result type (might be due to invalid IL or missing references)
			//IL_0179: Unknown result type (might be due to invalid IL or missing references)
			//IL_017f: Expected O, but got Unknown
			//IL_019a: Unknown result type (might be due to invalid IL or missing references)
			//IL_019f: Unknown result type (might be due to invalid IL or missing references)
			//IL_01a5: Expected O, but got Unknown
			//IL_01c0: Unknown result type (might be due to invalid IL or missing references)
			//IL_01c5: Unknown result type (might be due to invalid IL or missing references)
			//IL_01cb: Expected O, but got Unknown
			NetworkManager singleton = NetworkManager.Singleton;
			if ((Object)(object)singleton == (Object)null || !singleton.IsListening || singleton.CustomMessagingManager == null)
			{
				return;
			}
			string text = CurrentSaveName();
			if (!string.Equals(_activeSaveName, text, StringComparison.Ordinal))
			{
				_activeSaveName = text;
				_hasCurrentPayload = false;
				_soundtrackPlan = TakeoverSoundtrackPlanWire.Off;
				_mediaPlan = default(TakeoverMediaPlanWire);
				_hasAuthoritativeUpgradeSnapshot = false;
				AuthoritativeUnlockedUpgrades.Clear();
				AuthoritativeConstellationUnlockQuotas.Clear();
				AuthoritativeSuitUnlockQuotas.Clear();
				AuthoritativeStoreItemUnlockQuotas.Clear();
				_authoritativeQuota = 0;
				_cachedLedger = null;
				_cachedLedgerSave = string.Empty;
				_tokenReplayAttemptsRemaining = 0;
				QuotaRolloverNotifications.ResetRunState();
			}
			CustomMessagingManager customMessagingManager = singleton.CustomMessagingManager;
			if (customMessagingManager != _registeredMessaging)
			{
				if (_registeredMessaging != null)
				{
					try
					{
						_registeredMessaging.UnregisterNamedMessageHandler("Y4NGZCompany.QuotaTakeoverPayload.v2");
					}
					catch
					{
					}
					try
					{
						_registeredMessaging.UnregisterNamedMessageHandler("Y4NGZCompany.QuotaProgressionSnapshot.v2");
					}
					catch
					{
					}
					try
					{
						_registeredMessaging.UnregisterNamedMessageHandler("Y4NGZCompany.QuotaTakeoverSoundtrack.v1");
					}
					catch
					{
					}
					try
					{
						_registeredMessaging.UnregisterNamedMessageHandler("Y4NGZCompany.QuotaTakeoverMediaPlan.v1");
					}
					catch
					{
					}
				}
				if ((Object)(object)_registeredNetworkManager != (Object)null && _clientConnectedHandler != null)
				{
					try
					{
						_registeredNetworkManager.OnClientConnectedCallback -= _clientConnectedHandler;
					}
					catch
					{
					}
				}
				object obj6 = <>O.<2>__OnPayloadMessage;
				if (obj6 == null)
				{
					HandleNamedMessageDelegate val = OnPayloadMessage;
					<>O.<2>__OnPayloadMessage = val;
					obj6 = (object)val;
				}
				customMessagingManager.RegisterNamedMessageHandler("Y4NGZCompany.QuotaTakeoverPayload.v2", (HandleNamedMessageDelegate)obj6);
				object obj7 = <>O.<3>__OnGateSnapshotMessage;
				if (obj7 == null)
				{
					HandleNamedMessageDelegate val2 = OnGateSnapshotMessage;
					<>O.<3>__OnGateSnapshotMessage = val2;
					obj7 = (object)val2;
				}
				customMessagingManager.RegisterNamedMessageHandler("Y4NGZCompany.QuotaProgressionSnapshot.v2", (HandleNamedMessageDelegate)obj7);
				object obj8 = <>O.<4>__OnSoundtrackMessage;
				if (obj8 == null)
				{
					HandleNamedMessageDelegate val3 = OnSoundtrackMessage;
					<>O.<4>__OnSoundtrackMessage = val3;
					obj8 = (object)val3;
				}
				customMessagingManager.RegisterNamedMessageHandler("Y4NGZCompany.QuotaTakeoverSoundtrack.v1", (HandleNamedMessageDelegate)obj8);
				object obj9 = <>O.<5>__OnMediaPlanMessage;
				if (obj9 == null)
				{
					HandleNamedMessageDelegate val4 = OnMediaPlanMessage;
					<>O.<5>__OnMediaPlanMessage = val4;
					obj9 = (object)val4;
				}
				customMessagingManager.RegisterNamedMessageHandler("Y4NGZCompany.QuotaTakeoverMediaPlan.v1", (HandleNamedMessageDelegate)obj9);
				_soundtrackPlan = TakeoverSoundtrackPlanWire.Off;
				_mediaPlan = default(TakeoverMediaPlanWire);
				_clientConnectedHandler = OnClientConnected;
				try
				{
					singleton.OnClientConnectedCallback += _clientConnectedHandler;
				}
				catch (Exception ex)
				{
					ManualLogSource log = _log;
					if (log != null)
					{
						log.LogWarning((object)("Quota progression client-connect hook failed: " + ex.Message));
					}
				}
				_registeredMessaging = customMessagingManager;
				_registeredNetworkManager = singleton;
				_nextSnapshotAt = 0f;
				_tokenReplayAttemptsRemaining = 0;
				if (!singleton.IsServer)
				{
					_hasAuthoritativeUpgradeSnapshot = false;
				}
			}
			if (singleton.IsServer && Time.realtimeSinceStartup >= _nextSnapshotAt)
			{
				_nextSnapshotAt = Time.realtimeSinceStartup + 5f;
				SendGateSnapshot();
				if ((Object)(object)TimeOfDay.Instance != (Object)null && TimeOfDay.Instance.timesFulfilledQuota > 0 && QuotaRewardLedger.ShouldRetryRewards(_retryPending?.Value ?? false, LoadLedger()))
				{
					ApplyRewardsThroughQuota(TimeOfDay.Instance.timesFulfilledQuota);
				}
			}
			if (singleton.IsServer && _tokenReplayAttemptsRemaining > 0 && Time.realtimeSinceStartup >= _nextTokenReplayAt)
			{
				_tokenReplayAttemptsRemaining--;
				_nextTokenReplayAt = Time.realtimeSinceStartup + 6f;
				ReplayLedgeredTokenGrants();
			}
		}

		private static void OnClientConnected(ulong clientId)
		{
			NetworkManager singleton = NetworkManager.Singleton;
			if (!((Object)(object)singleton == (Object)null) && singleton.IsServer && clientId != singleton.LocalClientId)
			{
				ConfigEntry<bool> enabled = _enabled;
				if (enabled != null && enabled.Value)
				{
					_tokenReplayAttemptsRemaining = 3;
					_nextTokenReplayAt = Time.realtimeSinceStartup + 6f;
				}
			}
		}

		private static void ReplayLedgeredTokenGrants()
		{
			ConfigEntry<bool> enabled = _enabled;
			if (enabled == null || !enabled.Value)
			{
				return;
			}
			NetworkManager singleton = NetworkManager.Singleton;
			if (singleton == null || !singleton.IsServer || !QuotaProgressionApi.IsTokenGrantCapabilityAvailable)
			{
				return;
			}
			int completedQuota = (((Object)(object)TimeOfDay.Instance != (Object)null) ? Math.Max(0, TimeOfDay.Instance.timesFulfilledQuota) : 0);
			if (completedQuota < 1)
			{
				return;
			}
			HashSet<string> ledger = LoadLedger();
			int num = 0;
			foreach (QuotaMilestoneConfig item in from m in Milestones.Values
				where m.Enable.Value && m.Quota <= completedQuota && m.GrantTokensPerPlayer.Value > 0
				orderby m.Quota
				select m)
			{
				string text = QuotaRewardLedger.RewardId(item.Quota, "tokens");
				if (QuotaRewardLedger.IsPaid(ledger, text) && QuotaProgressionApi.TryGrantTokens(new QuotaTokenGrantRequest(item.Quota, item.GrantTokensPerPlayer.Value, text, isReplay: true)))
				{
					num++;
				}
			}
			if (num > 0)
			{
				ManualLogSource log = _log;
				if (log != null)
				{
					log.LogInfo((object)$"Replayed {num} ledgered quota token grant(s) for connected players.");
				}
			}
		}

		internal static void OnQuotaCompleted()
		{
			ConfigEntry<bool> enabled = _enabled;
			if (enabled != null && enabled.Value)
			{
				NetworkManager singleton = NetworkManager.Singleton;
				if (singleton != null && singleton.IsServer)
				{
					int num = (((Object)(object)TimeOfDay.Instance != (Object)null) ? Math.Max(0, TimeOfDay.Instance.timesFulfilledQuota) : 0);
					_currentPayload = BuildPayload(num);
					_hasCurrentPayload = true;
					SendPayload(_currentPayload);
					SendSoundtrack(BuildSoundtrackPlan());
					SendMediaPlan(BuildMediaPlan(_currentPayload));
					ApplyRewardsThroughQuota(num);
					QuotaProgressionSuitGate.OnUnlockStateMaybeChanged(num);
				}
			}
		}

		internal static QuotaTakeoverPayload GetCurrentPayload(string[] builtInDialogue)
		{
			int num = (((Object)(object)TimeOfDay.Instance != (Object)null) ? Math.Max(0, TimeOfDay.Instance.timesFulfilledQuota) : 0);
			QuotaTakeoverPayload result = ((_hasCurrentPayload && _currentPayload.Quota == num) ? _currentPayload : BuildPayload(num));
			if (string.IsNullOrWhiteSpace(result.Dialogue) && builtInDialogue != null && builtInDialogue.Length != 0)
			{
				int num2 = (RandomizeDialogue ? GetRandomizedIndex(builtInDialogue.Length, num, "built-in") : (Math.Max(0, num - 1) % builtInDialogue.Length));
				result = result.WithDialogue(builtInDialogue[num2], num2);
			}
			return result;
		}

		internal static void PrepareHostTakeoverPayload(string[] builtInDialogue)
		{
			NetworkManager singleton = NetworkManager.Singleton;
			if (singleton != null && singleton.IsServer)
			{
				QuotaTakeoverPayload payload = (_currentPayload = GetCurrentPayload(builtInDialogue));
				_hasCurrentPayload = true;
				SendPayload(payload);
				SendSoundtrack(BuildSoundtrackPlan());
				SendMediaPlan(BuildMediaPlan(payload));
			}
		}

		internal static bool IsContractUnlocked(string contractId, int completedQuotas)
		{
			return IsNamedUnlockSatisfied(contractId, completedQuotas, (QuotaMilestoneConfig milestone) => milestone.Contracts);
		}

		internal static bool IsShipUpgradePurchaseUnlocked(string upgradeId, int completedQuotas)
		{
			if ((Object)(object)NetworkManager.Singleton != (Object)null && !NetworkManager.Singleton.IsServer && _hasAuthoritativeUpgradeSnapshot)
			{
				return AuthoritativeUnlockedUpgrades.Contains(Normalize(upgradeId));
			}
			return IsNamedUnlockSatisfied(upgradeId, completedQuotas, (QuotaMilestoneConfig milestone) => milestone.Upgrades);
		}

		internal static bool IsConstellationUnlocked(string name, int completedQuotas)
		{
			return IsNamedUnlockSatisfied(name, completedQuotas, (QuotaMilestoneConfig milestone) => milestone.Constellations);
		}

		internal static bool TryGetConstellationUnlockQuota(string name, out int quota)
		{
			quota = 0;
			ConfigEntry<bool> enabled = _enabled;
			if (enabled == null || !enabled.Value || string.IsNullOrWhiteSpace(name))
			{
				return false;
			}
			if ((Object)(object)NetworkManager.Singleton != (Object)null && !NetworkManager.Singleton.IsServer && _hasAuthoritativeUpgradeSnapshot)
			{
				return AuthoritativeConstellationUnlockQuotas.TryGetValue(Normalize(name), out quota);
			}
			foreach (QuotaMilestoneConfig item in from m in Milestones.Values
				where m.Enable.Value
				orderby m.Quota
				select m)
			{
				if (item.Constellations.Any((string item) => Normalize(item) == Normalize(name)))
				{
					quota = item.Quota;
					return true;
				}
			}
			return false;
		}

		internal static bool IsSuitUnlocked(string name)
		{
			return IsRuleUnlocked(name, AuthoritativeSuitUnlockQuotas, (QuotaMilestoneConfig milestone) => milestone.Suits);
		}

		internal static bool IsStoreItemUnlocked(string name)
		{
			return IsRuleUnlocked(name, AuthoritativeStoreItemUnlockQuotas, (QuotaMilestoneConfig milestone) => milestone.StoreItems);
		}

		private static bool IsRuleUnlocked(string name, Dictionary<string, int> authoritative, Func<QuotaMilestoneConfig, IEnumerable<string>> selector)
		{
			ConfigEntry<bool> enabled = _enabled;
			if (enabled == null || !enabled.Value || string.IsNullOrWhiteSpace(name))
			{
				return true;
			}
			if ((Object)(object)NetworkManager.Singleton != (Object)null && !NetworkManager.Singleton.IsServer && _hasAuthoritativeUpgradeSnapshot)
			{
				if (authoritative.TryGetValue(Normalize(name), out var value))
				{
					return _authoritativeQuota >= value;
				}
				return true;
			}
			int completedQuotas = (((Object)(object)TimeOfDay.Instance != (Object)null) ? Math.Max(0, TimeOfDay.Instance.timesFulfilledQuota) : 0);
			return IsNamedUnlockSatisfied(name, completedQuotas, selector);
		}

		internal static QuotaMilestoneSummary GetMilestoneSummary(int quota)
		{
			if (!Milestones.TryGetValue(quota, out var value) || value == null)
			{
				return null;
			}
			return new QuotaMilestoneSummary
			{
				Quota = value.Quota,
				Enabled = value.Enable.Value,
				Contracts = value.Contracts.ToArray(),
				ShipUpgrades = value.Upgrades.ToArray(),
				Constellations = value.Constellations.ToArray(),
				Suits = value.Suits.ToArray(),
				StoreItems = value.StoreItems.ToArray(),
				TokensPerPlayer = value.GrantTokensPerPlayer.Value,
				GroupCredits = value.GrantGroupCredits.Value,
				ShipFuel = value.GrantShipFuel.Value
			};
		}

		private static bool IsNamedUnlockSatisfied(string name, int completedQuotas, Func<QuotaMilestoneConfig, IEnumerable<string>> selector)
		{
			ConfigEntry<bool> enabled = _enabled;
			if (enabled == null || !enabled.Value || string.IsNullOrWhiteSpace(name))
			{
				return true;
			}
			int? num = null;
			foreach (QuotaMilestoneConfig item in from m in Milestones.Values
				where m.Enable.Value
				orderby m.Quota
				select m)
			{
				if (selector(item).Any((string item) => Normalize(item) == Normalize(name)))
				{
					num = item.Quota;
					break;
				}
			}
			if (num.HasValue)
			{
				return completedQuotas >= num.Value;
			}
			return true;
		}

		private static QuotaTakeoverPayload BuildPayload(int quota)
		{
			QuotaMilestoneConfig value;
			QuotaMilestoneConfig quotaMilestoneConfig = ((Milestones.TryGetValue(quota, out value) && value.Enable.Value) ? value : null);
			List<QuotaMilestoneConfig> list = (from item in Milestones.Values
				where item.Enable.Value && !string.IsNullOrWhiteSpace(ReadDialogue(item))
				orderby item.Quota
				select item).ToList();
			if (RandomizeDialogue && list.Count > 0)
			{
				int randomizedIndex = GetRandomizedIndex(list.Count, quota, "configured");
				quotaMilestoneConfig = list[randomizedIndex];
			}
			string dialogue = ((quotaMilestoneConfig != null) ? ReadDialogue(quotaMilestoneConfig) : string.Empty);
			QuotaMilestoneConfig value2;
			string text = ((Milestones.TryGetValue(quota, out value2) && value2.Enable.Value) ? value2.MediaFile.Value : string.Empty);
			if (string.IsNullOrWhiteSpace(text))
			{
				text = _defaultMediaFile?.Value ?? string.Empty;
			}
			YoutubeVideoResolver.Prefetch(text);
			string path;
			string mediaHash = ((!YoutubeVideoResolver.IsYoutubeUrl(text) && TryResolveMediaFile(text, out path)) ? ComputeSha256(path) : string.Empty);
			BuildAudioOverrideFields(out var alarmAudio, out var alarmAudioHash, out var mumbleAudio, out var mumbleAudioHashes);
			return new QuotaTakeoverPayload(quota, dialogue, text, mediaHash, quotaMilestoneConfig?.Quota ?? 0, alarmAudio, alarmAudioHash, mumbleAudio, mumbleAudioHashes);
		}

		private static void BuildAudioOverrideFields(out string alarmAudio, out string alarmAudioHash, out string mumbleAudio, out string mumbleAudioHashes)
		{
			alarmAudio = string.Empty;
			alarmAudioHash = string.Empty;
			string text = TruncateFixed((_alarmAudioFile?.Value ?? string.Empty).Trim(), 120);
			if (text.Length > 0 && TryResolveAudioFile(text, out var path))
			{
				string text2 = ComputeSha256(path);
				if (text2.Length > 0)
				{
					alarmAudio = text;
					alarmAudioHash = text2;
				}
			}
			List<string> list = new List<string>();
			List<string> list2 = new List<string>();
			int num = 0;
			int num2 = 0;
			string[] array = SplitList(_mumbleAudioFiles?.Value);
			foreach (string value in array)
			{
				if (list.Count >= 12)
				{
					break;
				}
				string text3 = TruncateFixed(value, 120);
				if (!TryResolveAudioFile(text3, out var path2))
				{
					continue;
				}
				string text4 = ComputeSha256(path2);
				if (text4.Length != 0)
				{
					int num3 = num + text3.Length + ((list.Count > 0) ? 1 : 0);
					int num4 = num2 + text4.Length + ((list2.Count > 0) ? 1 : 0);
					if (num3 > 1000 || num4 > 1000)
					{
						break;
					}
					list.Add(text3);
					list2.Add(text4);
					num = num3;
					num2 = num4;
				}
			}
			mumbleAudio = string.Join(",", list);
			mumbleAudioHashes = string.Join(",", list2);
		}

		private static string ReadDialogue(QuotaMilestoneConfig milestone)
		{
			if (milestone == null)
			{
				return string.Empty;
			}
			if (!string.IsNullOrWhiteSpace(milestone.DialogueFile.Value) && TryResolveFile(milestone.DialogueFile.Value, ".txt", out var path))
			{
				try
				{
					if (new FileInfo(path).Length <= 65536)
					{
						return File.ReadAllText(path).Trim();
					}
					ManualLogSource log = _log;
					if (log != null)
					{
						log.LogWarning((object)$"Quota {milestone.Quota} dialogue file exceeds 64 KB; using inline/built-in dialogue.");
					}
				}
				catch (Exception ex)
				{
					ManualLogSource log2 = _log;
					if (log2 != null)
					{
						log2.LogWarning((object)$"Quota {milestone.Quota} dialogue file failed: {ex.Message}");
					}
				}
			}
			return (milestone.Dialogue.Value ?? string.Empty).Replace("\\n", "\n").Trim();
		}

		internal static bool TryResolveMediaFile(string configured, out string path)
		{
			if (YoutubeVideoResolver.IsYoutubeUrl(configured))
			{
				return YoutubeVideoResolver.TryGetCachedVideo(configured, out path);
			}
			return TryResolveFile(configured, MediaExtensions, out path);
		}

		internal static bool TryResolveMediaFile(string configured, string expectedHash, out string path)
		{
			if (YoutubeVideoResolver.IsYoutubeUrl(configured))
			{
				return YoutubeVideoResolver.TryGetCachedVideo(configured, out path);
			}
			return TryResolveVerifiedFile(configured, MediaExtensions, expectedHash, "media", out path);
		}

		internal static bool TryResolveAudioFile(string configured, out string path)
		{
			return TryResolveFile(configured, AudioExtensions, out path);
		}

		internal static bool TryResolveAudioFile(string configured, string expectedHash, out string path)
		{
			return TryResolveVerifiedFile(configured, AudioExtensions, expectedHash, "audio", out path);
		}

		private static bool TryResolveVerifiedFile(string configured, string[] extensions, string expectedHash, string kind, out string path)
		{
			if (!TryResolveFile(configured, extensions, out path))
			{
				return false;
			}
			if (string.IsNullOrWhiteSpace(expectedHash))
			{
				return true;
			}
			if (string.Equals(ComputeSha256(path), expectedHash, StringComparison.OrdinalIgnoreCase))
			{
				return true;
			}
			ManualLogSource log = _log;
			if (log != null)
			{
				log.LogWarning((object)("Configured takeover " + kind + " hash mismatch for '" + Path.GetFileName(path) + "'; using bundled fallback."));
			}
			path = null;
			return false;
		}

		private static bool TryResolveFile(string configured, string extension, out string path)
		{
			return TryResolveFile(configured, new string[1] { extension }, out path);
		}

		private static bool TryResolveFile(string configured, string[] extensions, out string path)
		{
			path = null;
			if (string.IsNullOrWhiteSpace(configured))
			{
				return false;
			}
			string text = Path.GetFullPath(MediaDirectory).TrimEnd(Path.DirectorySeparatorChar) + Path.DirectorySeparatorChar;
			string fullPath = Path.GetFullPath(Path.Combine(text, configured.Trim()));
			if (!fullPath.StartsWith(text, StringComparison.OrdinalIgnoreCase) || !File.Exists(fullPath))
			{
				return false;
			}
			if (!extensions.Contains<string>(Path.GetExtension(fullPath), StringComparer.OrdinalIgnoreCase))
			{
				return false;
			}
			long num = (Path.GetExtension(fullPath).Equals(".mp4", StringComparison.OrdinalIgnoreCase) ? 536870912 : 33554432);
			if (new FileInfo(fullPath).Length > num)
			{
				ManualLogSource log = _log;
				if (log != null)
				{
					log.LogWarning((object)("Configured takeover media '" + Path.GetFileName(fullPath) + "' exceeds the size limit."));
				}
				return false;
			}
			path = fullPath;
			return true;
		}

		internal static TakeoverAudioOverrideFile ResolveTakeoverAlarmAudio(QuotaTakeoverPayload payload)
		{
			if (string.IsNullOrWhiteSpace(payload.AlarmAudioFile))
			{
				return default(TakeoverAudioOverrideFile);
			}
			if (TryResolveAudioFile(payload.AlarmAudioFile, payload.AlarmAudioHash, out var path))
			{
				return new TakeoverAudioOverrideFile(path, payload.AlarmAudioHash);
			}
			ManualLogSource log = _log;
			if (log != null)
			{
				log.LogWarning((object)("Takeover alarm audio override '" + payload.AlarmAudioFile + "' is missing or does not match the host copy; using the built-in alarm."));
			}
			return default(TakeoverAudioOverrideFile);
		}

		internal static List<TakeoverAudioOverrideFile> ResolveTakeoverMumbleAudio(QuotaTakeoverPayload payload)
		{
			List<TakeoverAudioOverrideFile> list = new List<TakeoverAudioOverrideFile>();
			string[] array = SplitList(payload.MumbleAudioFiles);
			string[] array2 = SplitList(payload.MumbleAudioHashes);
			if (array.Length == 0)
			{
				return list;
			}
			int num = 0;
			for (int i = 0; i < array.Length; i++)
			{
				string text = ((i < array2.Length) ? array2[i] : string.Empty);
				if (TryResolveAudioFile(array[i], text, out var path))
				{
					list.Add(new TakeoverAudioOverrideFile(path, text));
				}
				else
				{
					num++;
				}
			}
			if (num > 0)
			{
				ManualLogSource log = _log;
				if (log != null)
				{
					log.LogWarning((object)$"{num} of {array.Length} takeover mumble audio overrides are missing or do not match the host copies; those slots use the built-in clips.");
				}
			}
			return list;
		}

		private static string[] SplitList(string value)
		{
			return (from item in (value ?? string.Empty).Split(new char[1] { ',' }, StringSplitOptions.RemoveEmptyEntries)
				select item.Trim() into item
				where item.Length > 0
				select item).ToArray();
		}

		internal static string[] SplitSemicolonList(string value)
		{
			return (from item in (value ?? string.Empty).Split(new char[1] { ';' }, StringSplitOptions.RemoveEmptyEntries)
				select item.Trim() into item
				where item.Length > 0
				select item).ToArray();
		}

		internal static string[] SplitSemicolonListPreservingEmpty(string value)
		{
			string text = value ?? string.Empty;
			if (text.Length == 0)
			{
				return Array.Empty<string>();
			}
			return (from item in text.Split(';')
				select item.Trim()).ToArray();
		}

		private static void WarnOnCommaSeparatedMediaPool()
		{
			string text = _perMonitorMediaPool?.Value ?? string.Empty;
			if (text.IndexOf(';') < 0 && text.IndexOf(',') >= 0 && SplitList(text).Length >= 2)
			{
				ManualLogSource log = _log;
				if (log != null)
				{
					log.LogWarning((object)"[QuotaProgression] PerMonitorMediaPool looks comma-separated; this key uses ';' so that a YouTube link containing a comma stays intact. The whole value is being read as one entry.");
				}
			}
		}

		private static void ApplyRewardsThroughQuota(int completedQuota)
		{
			HashSet<string> ledger = LoadLedger();
			bool flag = false;
			foreach (QuotaMilestoneConfig milestone in from m in Milestones.Values
				where m.Enable.Value && m.Quota <= completedQuota
				orderby m.Quota
				select m)
			{
				flag |= ApplyReward(ledger, milestone, "credits", milestone.GrantGroupCredits.Value, providerAvailable: true, (float amount) => GrantCredits((int)amount));
				flag |= ApplyBrokeredReward(ledger, milestone, "fuel", "ship_fuel", milestone.GrantShipFuel.Value);
				flag |= ApplyReward(ledger, milestone, "tokens", milestone.GrantTokensPerPlayer.Value, QuotaProgressionApi.IsTokenGrantCapabilityAvailable, (float _) => QuotaProgressionApi.TryGrantTokens(new QuotaTokenGrantRequest(milestone.Quota, milestone.GrantTokensPerPlayer.Value, QuotaRewardLedger.RewardId(milestone.Quota, "tokens"))));
			}
			if (flag)
			{
				SaveLedger(ledger);
			}
		}

		private static bool ApplyBrokeredReward(HashSet<string> ledger, QuotaMilestoneConfig milestone, string ledgerKind, string coreRewardKind, float amount)
		{
			//IL_003f: Unknown result type (might be due to invalid IL or missing references)
			//IL_009a: Unknown result type (might be due to invalid IL or missing references)
			string rewardId = QuotaRewardLedger.RewardId(milestone.Quota, ledgerKind);
			bool wasDeferred = QuotaRewardLedger.IsDeferred(ledger, rewardId);
			QuotaRewardResult observed = (QuotaRewardResult)2;
			bool ledgerChanged;
			QuotaRewardOutcome quotaRewardOutcome = QuotaRewardLedger.Apply(ledger, rewardId, amount, providerAvailable: true, delegate(float offered)
			{
				//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_002a: 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)
				observed = QuotaRewardBroker.Offer(new QuotaRewardGrant(coreRewardKind, rewardId, milestone.Quota, (double)offered, wasDeferred));
				return QuotaRewardOutcome.Consumes(observed);
			}, out ledgerChanged);
			if (quotaRewardOutcome == QuotaRewardOutcome.Deferred && !wasDeferred)
			{
				ManualLogSource log = _log;
				if (log != null)
				{
					log.LogInfo((object)($"Quota {milestone.Quota} {ledgerKind} reward is owed but nothing could be " + $"granted yet ({observed}); it stays unpaid and retries."));
				}
			}
			else if (quotaRewardOutcome == QuotaRewardOutcome.Granted && wasDeferred)
			{
				ManualLogSource log2 = _log;
				if (log2 != null)
				{
					log2.LogInfo((object)$"Deferred quota {milestone.Quota} {ledgerKind} reward granted.");
				}
			}
			return ledgerChanged;
		}

		private static bool ApplyReward(HashSet<string> ledger, QuotaMilestoneConfig milestone, string kind, float amount, bool providerAvailable, Func<float, bool> apply)
		{
			string rewardId = QuotaRewardLedger.RewardId(milestone.Quota, kind);
			bool flag = QuotaRewardLedger.IsDeferred(ledger, rewardId);
			bool ledgerChanged;
			QuotaRewardOutcome quotaRewardOutcome = QuotaRewardLedger.Apply(ledger, rewardId, amount, providerAvailable, apply, out ledgerChanged);
			if (quotaRewardOutcome == QuotaRewardOutcome.Deferred && !flag)
			{
				ManualLogSource log = _log;
				if (log != null)
				{
					log.LogInfo((object)$"Quota {milestone.Quota} {kind} reward is owed but nothing could be granted yet; it stays unpaid and retries.");
				}
			}
			else if (quotaRewardOutcome == QuotaRewardOutcome.Granted && flag)
			{
				ManualLogSource log2 = _log;
				if (log2 != null)
				{
					log2.LogInfo((object)$"Deferred quota {milestone.Quota} {kind} reward granted.");
				}
			}
			return ledgerChanged;
		}

		private static bool GrantCredits(int amount)
		{
			Terminal val = Object.FindObjectOfType<Terminal>();
			if ((Object)(object)val == (Object)null)
			{
				return false;
			}
			val.groupCredits = Math.Max(0, val.groupCredits + amount);
			val.SyncGroupCreditsServerRpc(val.groupCredits, val.numberOfItemsInDropship);
			return true;
		}

		private static HashSet<string> LoadLedger()
		{
			string text = CurrentSaveName();
			if (_cachedLedger != null && string.Equals(_cachedLedgerSave, text, StringComparison.Ordinal))
			{
				return _cachedLedger;
			}
			bool flag = false;
			try
			{
				_cachedLedger = new HashSet<string>((ES3.Load<string>("Y4NGZCompany.QuotaProgressionLedger.v1", text, string.Empty) ?? string.Empty).Split(new char[1] { ';' }, StringSplitOptions.RemoveEmptyEntries), StringComparer.Ordinal);
			}
			catch (Exception ex)
			{
				flag = true;
				_cachedLedger = new HashSet<string>(StringComparer.Ordinal);
				ManualLogSource log = _log;
				if (log != null)
				{
					log.LogWarning((object)("Quota progression ledger load failed: " + ex.Message));
				}
			}
			_cachedLedgerSave = text;
			if (!flag && QuotaRewardLedger.HealFalsePaidFuelRewards(_cachedLedger))
			{
				ManualLogSource log2 = _log;
				if (log2 != null)
				{
					log2.LogInfo((object)"Quota progression ledger healed: fuel milestones recorded as paid before the grant predicate was fixed are owed again.");
				}
				SaveLedger(_cachedLedger);
			}
			return _cachedLedger;
		}

		private static void SaveLedger(HashSet<string> ledger)
		{
			_cachedLedger = ledger;
			_cachedLedgerSave = CurrentSaveName();
			try
			{
				ES3.Save<string>("Y4NGZCompany.QuotaProgressionLedger.v1", string.Join(";", ledger.OrderBy<string, string>((string item) => item, StringComparer.Ordinal)), _cachedLedgerSave);
			}
			catch (Exception ex)
			{
				ManualLogSource log = _log;
				if (log != null)
				{
					log.LogWarning((object)("Quota progression ledger save failed: " + ex.Message));
				}
			}
		}

		private unsafe static void SendPayload(QuotaTakeoverPayload payload)
		{
			//IL_002f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0035: 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)
			//IL_004d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0064: Unknown result type (might be due to invalid IL or missing references)
			//IL_0069: Unknown result type (might be due to invalid IL or missing references)
			//IL_008b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0090: Unknown result type (might be due to invalid IL or missing references)
			//IL_0097: Unknown result type (might be due to invalid IL or missing references)
			//IL_009c: Unknown result type (might be due to invalid IL or missing references)
			//IL_00ab: Unknown result type (might be due to invalid IL or missing references)
			//IL_00b0: Unknown result type (might be due to invalid IL or missing references)
			//IL_00b8: Unknown result type (might be due to invalid IL or missing references)
			//IL_00bd: Unknown result type (might be due to invalid IL or missing references)
			//IL_00cf: Unknown result type (might be due to invalid IL or missing references)
			//IL_00d4: Unknown result type (might be due to invalid IL or missing references)
			//IL_00e6: Unknown result type (might be due to invalid IL or missing references)
			//IL_00eb: Unknown result type (might be due to invalid IL or missing references)
			//IL_00f3: Unknown result type (might be due to invalid IL or missing references)
			//IL_00f9: Unknown result type (might be due to invalid IL or missing references)
			//IL_0106: Unknown result type (might be due to invalid IL or missing references)
			//IL_010c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0119: Unknown result type (might be due to invalid IL or missing references)
			//IL_011f: Unknown result type (might be due to invalid IL or missing references)
			//IL_012c: 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_013f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0145: Unknown result type (might be due to invalid IL or missing references)
			//IL_0152: Unknown result type (might be due to invalid IL or missing references)
			//IL_0158: Unknown result type (might be due to invalid IL or missing references)
			//IL_0165: Unknown result type (might be due to invalid IL or missing references)
			//IL_016b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0178: Unknown result type (might be due to invalid IL or missing references)
			NetworkManager singleton = NetworkManager.Singleton;
			CustomMessagingManager val = ((singleton != null) ? singleton.CustomMessagingManager : null);
			if (val == null)
			{
				return;
			}
			FastBufferWriter val2 = default(FastBufferWriter);
			((FastBufferWriter)(ref val2))..ctor(20480, (Allocator)2, -1);
			try
			{
				((FastBufferWriter)(ref val2)).WriteValueSafe<int>(ref payload.Quota, default(ForPrimitives));
				((FastBufferWriter)(ref val2)).WriteValueSafe<int>(ref payload.SelectionId, default(ForPrimitives));
				FixedString4096Bytes val3 = FixedString4096Bytes.op_Implicit(TruncateFixed(payload.Dialogue, 1000));
				FixedString512Bytes val4 = FixedString512Bytes.op_Implicit(TruncateFixed(payload.MediaFile, YoutubeVideoResolver.IsYoutubeUrl(payload.MediaFile) ? 500 : 120));
				FixedString128Bytes val5 = FixedString128Bytes.op_Implicit(payload.MediaHash);
				FixedString512Bytes val6 = FixedString512Bytes.op_Implicit(TruncateFixed(payload.AlarmAudioFile, 120));
				FixedString128Bytes val7 = FixedString128Bytes.op_Implicit(payload.AlarmAudioHash);
				FixedString4096Bytes val8 = FixedString4096Bytes.op_Implicit(TruncateFixed(payload.MumbleAudioFiles, 1000));
				FixedString4096Bytes val9 = FixedString4096Bytes.op_Implicit(TruncateFixed(payload.MumbleAudioHashes, 1000));
				((FastBufferWriter)(ref val2)).WriteValueSafe<FixedString4096Bytes>(ref val3, default(ForFixedStrings));
				((FastBufferWriter)(ref val2)).WriteValueSafe<FixedString512Bytes>(ref val4, default(ForFixedStrings));
				((FastBufferWriter)(ref val2)).WriteValueSafe<FixedString128Bytes>(ref val5, default(ForFixedStrings));
				((FastBufferWriter)(ref val2)).WriteValueSafe<FixedString512Bytes>(ref val6, default(ForFixedStrings));
				((FastBufferWriter)(ref val2)).WriteValueSafe<FixedString128Bytes>(ref val7, default(ForFixedStrings));
				((FastBufferWriter)(ref val2)).WriteValueSafe<FixedString4096Bytes>(ref val8, default(ForFixedStrings));
				((FastBufferWriter)(ref val2)).WriteValueSafe<FixedString4096Bytes>(ref val9, default(ForFixedStrings));
				val.SendNamedMessageToAll("Y4NGZCompany.QuotaTakeoverPayload.v2", val2, (NetworkDelivery)3);
			}
			finally
			{
				((IDisposable)(*(FastBufferWriter*)(&val2))/*cast due to .constrained prefix*/).Dispose();
			}
		}

		private unsafe static void OnPayloadMessage(ulong sender, FastBufferReader reader)
		{
			//IL_000a: Unknown result type (might be due to invalid IL or missing references)
			//IL_0010: 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_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_0036: Unknown result type (might be due to invalid IL or missing references)
			//IL_0043: Unknown result type (might be due to invalid IL or missing references)
			//IL_0049: Unknown result type (might be due to invalid IL or missing references)
			//IL_0056: Unknown result type (might be due to invalid IL or missing references)
			//IL_005c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0069: Unknown result type (might be due to invalid IL or missing references)
			//IL_006f: Unknown result type (might be due to invalid IL or missing references)
			//IL_007c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0082: 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_0095: Unknown result type (might be due to invalid IL or missing references)
			//IL_00a2: Unknown result type (might be due to invalid IL or missing references)
			//IL_00a8: Unknown result type (might be due to invalid IL or missing references)
			if (sender == 0L)
			{
				int quota = default(int);
				((FastBufferReader)(ref reader)).ReadValueSafe<int>(ref quota, default(ForPrimitives));
				int selectionId = default(int);
				((FastBufferReader)(ref reader)).ReadValueSafe<int>(ref selectionId, default(ForPrimitives));
				FixedString4096Bytes val = default(FixedString4096Bytes);
				((FastBufferReader)(ref reader)).ReadValueSafe<FixedString4096Bytes>(ref val, default(ForFixedStrings));
				FixedString512Bytes val2 = default(FixedString512Bytes);
				((FastBufferReader)(ref reader)).ReadValueSafe<FixedString512Bytes>(ref val2, default(ForFixedStrings));
				FixedString128Bytes val3 = default(FixedString128Bytes);
				((FastBufferReader)(ref reader)).ReadValueSafe<FixedString128Bytes>(ref val3, default(ForFixedStrings));
				FixedString512Bytes val4 = default(FixedString512Bytes);
				((FastBufferReader)(ref reader)).ReadValueSafe<FixedString512Bytes>(ref val4, default(ForFixedStrings));
				FixedString128Bytes val5 = default(FixedString128Bytes);
				((FastBufferReader)(ref reader)).ReadValueSafe<FixedString128Bytes>(ref val5, default(ForFixedStrings));
				FixedString4096Bytes val6 = default(FixedString4096Bytes);
				((FastBufferReader)(ref reader)).ReadValueSafe<FixedString4096Bytes>(ref val6, default(ForFixedStrings));
				FixedString4096Bytes val7 = default(FixedString4096Bytes);
				((FastBufferReader)(ref reader)).ReadValueSafe<FixedString4096Bytes>(ref val7, default(ForFixedStrings));
				_currentPayload = new QuotaTakeoverPayload(quota, ((object)(*(FixedString4096Bytes*)(&val))/*cast due to .constrained prefix*/).ToString(), ((object)(*(FixedString512Bytes*)(&val2))/*cast due to .constrained prefix*/).ToString(), ((object)(*(FixedString128Bytes*)(&val3))/*cast due to .constrained prefix*/).ToString(), selectionId, ((object)(*(FixedString512Bytes*)(&val4))/*cast due to .constrained prefix*/).ToString(), ((object)(*(FixedString128Bytes*)(&val5))/*cast due to .constrained prefix*/).ToString(), ((object)(*(FixedString4096Bytes*)(&val6))/*cast due to .constrained prefix*/).ToString(), ((object)(*(FixedString4096Bytes*)(&val7))/*cast due to .constrained prefix*/).ToString());
				_hasCurrentPayload = true;
				YoutubeVideoResolver.Prefetch(_currentPayload.MediaFile);
			}
		}

		internal static TakeoverSoundtrackPlanWire GetSoundtrackPlan()
		{
			return _soundtrackPlan;
		}

		private static TakeoverSoundtrackPlanWire BuildSoundtrackPlan()
		{
			TakeoverSoundtrackMode soundtrackMode = SoundtrackMode;
			string text = (_soundtrackSource?.Value ?? string.Empty).Trim();
			if (soundtrackMode == TakeoverSoundtrackMode.Off || text.Length == 0)
			{
				return TakeoverSoundtrackPlanWire.Off;
			}
			float soundtrackVolume = SoundtrackVolume;
			if (YoutubeVideoResolver.IsYoutubeUrl(text))
			{
				YoutubeVideoResolver.Prefetch(text);
				string normalizedUrl;
				string value = (YoutubeVideoResolver.TryNormalizeYoutubeUrl(text, out normalizedUrl) ? normalizedUrl : text);
				return new TakeoverSoundtrackPlanWire(soundtrackMode, TruncateFixed(value, 500), string.Empty, soundtrackVolume);
			}
			string text2 = TruncateFixed(text, 120);
			if (TryResolveAudioFile(text2, out var path))
			{
				string text3 = ComputeSha256(path);
				if (text3.Length > 0)
				{
					return new TakeoverSoundtrackPlanWire(soundtrackMode, text2, text3, soundtrackVolume);
				}
			}
			ManualLogSource log = _log;
			if (log != null)
			{
				log.LogWarning((object)("[QuotaProgression] Takeover soundtrack '" + text2 + "' is missing or unreadable on the host; the takeover uses the built-in audio."));
			}
			return TakeoverSoundtrackPlanWire.Off;
		}

		private unsafe static void SendSoundtrack(TakeoverSoundtrackPlanWire plan)
		{
			//IL_0038: Unknown result type (might be due to invalid IL or missing references)
			//IL_003e: 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_006b: Unknown result type (might be due to invalid IL or missing references)
			//IL_007b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0080: Unknown result type (might be due to invalid IL or missing references)
			//IL_0087: Unknown result type (might be due to invalid IL or missing references)
			//IL_008d: Unknown result type (might be due to invalid IL or missing references)
			//IL_009a: Unknown result type (might be due to invalid IL or missing references)
			//IL_00a0: Unknown result type (might be due to invalid IL or missing references)
			//IL_00b2: Unknown result type (might be due to invalid IL or missing references)
			//IL_00b8: Unknown result type (might be due to invalid IL or missing references)
			//IL_00c5: Unknown result type (might be due to invalid IL or missing references)
			_soundtrackPlan = plan;
			NetworkManager singleton = NetworkManager.Singleton;
			CustomMessagingManager val = ((singleton != null) ? singleton.CustomMessagingManager : null);
			if (val == null)
			{
				return;
			}
			FastBufferWriter val2 = default(FastBufferWriter);
			((FastBufferWriter)(ref val2))..ctor(2048, (Allocator)2, -1);
			try
			{
				int mode = (int)plan.Mode;
				((FastBufferWriter)(ref val2)).WriteValueSafe<int>(ref mode, default(ForPrimitives));
				FixedString512Bytes val3 = FixedString512Bytes.op_Implicit(TruncateFixed(plan.Source, YoutubeVideoResolver.IsYoutubeUrl(plan.Source) ? 500 : 120));
				FixedString128Bytes val4 = FixedString128Bytes.op_Implicit(plan.Hash ?? string.Empty);
				((FastBufferWriter)(ref val2)).WriteValueSafe<FixedString512Bytes>(ref val3, default(ForFixedStrings));
				((FastBufferWriter)(ref val2)).WriteValueSafe<FixedString128Bytes>(ref val4, default(ForFixedStrings));
				((FastBufferWriter)(ref val2)).WriteValueSafe<float>(ref plan.Volume, default(ForPrimitives));
				val.SendNamedMessageToAll("Y4NGZCompany.QuotaTakeoverSoundtrack.v1", val2, (NetworkDelivery)3);
			}
			finally
			{
				((IDisposable)(*(FastBufferWriter*)(&val2))/*cast due to .constrained prefix*/).Dispose();
			}
		}

		private unsafe static void OnSoundtrackMessage(ulong sender, FastBufferReader reader)
		{
			//IL_000a: Unknown result type (might be due to invalid IL or missing references)
			//IL_0010: 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_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_0036: Unknown result type (might be due to invalid IL or missing references)
			//IL_0043: Unknown result type (might be due to invalid IL or missing references)
			//IL_0049: Unknown result type (might be due to invalid IL or missing references)
			if (sender == 0L)
			{
				int num = default(int);
				((FastBufferReader)(ref reader)).ReadValueSafe<int>(ref num, default(ForPrimitives));
				FixedString512Bytes val = default(FixedString512Bytes);
				((FastBufferReader)(ref reader)).ReadValueSafe<FixedString512Bytes>(ref val, default(ForFixedStrings));
				FixedString128Bytes val2 = default(FixedString128Bytes);
				((FastBufferReader)(ref reader)).ReadValueSafe<FixedString128Bytes>(ref val2, default(ForFixedStrings));
				float num2 = default(float);
				((FastBufferReader)(ref reader)).ReadValueSafe<float>(ref num2, default(ForPrimitives));
				_soundtrackPlan = new TakeoverSoundtrackPlanWire(num switch
				{
					2 => TakeoverSoundtrackMode.Mix, 
					1 => TakeoverSoundtrackMode.Replace, 
					_ => TakeoverSoundtrackMode.Off, 
				}, ((object)(*(FixedString512Bytes*)(&val))/*cast due to .constrained prefix*/).ToString(), ((object)(*(FixedString128Bytes*)(&val2))/*cast due to .constrained prefix*/).ToString(), Mathf.Clamp01(num2));
				YoutubeVideoResolver.Prefetch(_soundtrackPlan.Source);
			}
		}

		internal static TakeoverMediaPlanWire GetMediaPlan()
		{
			return _mediaPlan;
		}

		private static TakeoverMediaPlanWire BuildMediaPlan(QuotaTakeoverPayload payload)
		{
			if (!RandomPerMonitorMedia)
			{
				return default(TakeoverMediaPlanWire);
			}
			int seed = Random.Range(int.MinValue, int.MaxValue);
			int maxConcurrentMediaPlayers = MaxConcurrentMediaPlayers;
			List<string> list = new List<string>();
			List<string> list2 = new List<string>();
			int num = 0;
			string[] array = PerMonitorMediaPool();
			foreach (string text in array)
			{
				if (list.Count >= 6)
				{
					break;
				}
				string text2;
				string text3;
				if (YoutubeVideoResolver.IsYoutubeUrl(text))
				{
					YoutubeVideoResolver.Prefetch(text);
					text2 = TruncateFixed(YoutubeVideoResolver.TryNormalizeYoutubeUrl(text, out var normalizedUrl) ? normalizedUrl : text, 500);
					text3 = string.Empty;
				}
				else
				{
					text2 = TruncateFixed(text, 120);
					if (!TryResolveMediaFile(text2, out var path))
					{
						continue;
					}
					text3 = ComputeSha256(path);
					if (text3.Length == 0)
					{
						continue;
					}
				}
				int num2 = num + text2.Length + ((list.Count > 0) ? 1 : 0);
				if (num2 > 3000)
				{
					break;
				}
				list.Add(text2);
				list2.Add(text3);
				num = num2;
			}
			if (list.Count == 0)
			{
				string text4 = payload.MediaFile ?? string.Empty;
				if (text4.Length == 0)
				{
					return new TakeoverMediaPlanWire(seed, maxConcurrentMediaPlayers, null, null);
				}
				list.Add(TruncateFixed(text4, YoutubeVideoResolver.IsYoutubeUrl(text4) ? 500 : 120));
				list2.Add(payload.MediaHash ?? string.Empty);
			}
			return new TakeoverMediaPlanWire(seed, maxConcurrentMediaPlayers, list.ToArray(), list2.ToArray());
		}

		private unsafe static void SendMediaPlan(TakeoverMediaPlanWire plan)
		{
			//IL_0035: Unknown result type (might be due to invalid IL or missing references)
			//IL_003b: Unknown result type (might be due to invalid IL or missing references)
			//IL_004d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0053: Unknown result type (might be due to invalid IL or missing references)
			//IL_007d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0082: Unknown result type (might be due to invalid IL or missing references)
			//IL_00a6: Unknown result type (might be due to invalid IL or missing references)
			//IL_00ab: Unknown result type (might be due to invalid IL or missing references)
			//IL_00b2: Unknown result type (might be due to invalid IL or missing references)
			//IL_00b8: Unknown result type (might be due to invalid IL or missing references)
			//IL_00c5: Unknown result type (might be due to invalid IL or missing references)
			//IL_00cb: Unknown result type (might be due to invalid IL or missing references)
			//IL_00d8: Unknown result type (might be due to invalid IL or missing references)
			_mediaPlan = plan;
			NetworkManager singleton = NetworkManager.Singleton;
			CustomMessagingManager val = ((singleton != null) ? singleton.CustomMessagingManager : null);
			if (val == null)
			{
				return;
			}
			FastBufferWriter val2 = default(FastBufferWriter);
			((FastBufferWriter)(ref val2))..ctor(20480, (Allocator)2, -1);
			try
			{
				((FastBufferWriter)(ref val2)).WriteValueSafe<int>(ref plan.Seed, default(ForPrimitives));
				((FastBufferWriter)(ref val2)).WriteValueSafe<int>(ref plan.MaxPlayers, default(ForPrimitives));
				FixedString4096Bytes val3 = FixedString4096Bytes.op_Implicit(TruncateFixed(string.Join(";", plan.Pool ?? Array.Empty<string>()), 3000));
				FixedString4096Bytes val4 = FixedString4096Bytes.op_Implicit(TruncateFixed(string.Join(";", plan.Hashes ?? Array.Empty<string>()), 3000));
				((FastBufferWriter)(ref val2)).WriteValueSafe<FixedString4096Bytes>(ref val3, default(ForFixedStrings));
				((FastBufferWriter)(ref val2)).WriteValueSafe<FixedString4096Bytes>(ref val4, default(ForFixedStrings));
				val.SendNamedMessageToAll("Y4NGZCompany.QuotaTakeoverMediaPlan.v1", val2, (NetworkDelivery)3);
			}
			finally
			{
				((IDisposable)(*(FastBufferWriter*)(&val2))/*cast due to .constrained prefix*/).Dispose();
			}
		}

		private unsafe static void OnMediaPlanMessage(ulong sender, FastBufferReader reader)
		{
			//IL_000a: Unknown result type (might be due to invalid IL or missing references)
			//IL_0010: 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_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_0036: Unknown result type (might be due to invalid IL or missing references)
			//IL_0043: Unknown result type (might be due to invalid IL or missing references)
			//IL_0049: Unknown result type (might be due to invalid IL or missing references)
			if (sender == 0L)
			{
				int seed = default(int);
				((FastBufferReader)(ref reader)).ReadValueSafe<int>(ref seed, default(ForPrimitives));
				int num = default(int);
				((FastBufferReader)(ref reader)).ReadValueSafe<int>(ref num, default(ForPrimitives));
				FixedString4096Bytes val = default(FixedString4096Bytes);
				((FastBufferReader)(ref reader)).ReadValueSafe<FixedString4096Bytes>(ref val, default(ForFixedStrings));
				FixedString4096Bytes val2 = default(FixedString4096Bytes);
				((FastBufferReader)(ref reader)).ReadValueSafe<FixedString4096Bytes>(ref val2, default(ForFixedStrings));
				string[] array = SplitSemicolonList(((object)(*(FixedString4096Bytes*)(&val))/*cast due to .constrained prefix*/).ToString());
				_mediaPlan = new TakeoverMediaPlanWire(seed, Mathf.Clamp(num, 1, 6), array, SplitSemicolonListPreservingEmpty(((object)(*(FixedString4096Bytes*)(&val2))/*cast due to .constrained prefix*/).ToString()));
				string[] array2 = array;
				for (int i = 0; i < array2.Length; i++)
				{
					YoutubeVideoResolver.Prefetch(array2[i]);
				}
			}
		}

		private unsafe static void SendGateSnapshot()
		{
			//IL_0127: 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_013f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0144: Unknown result type (might be due to invalid IL or missing references)
			//IL_0151: Unknown result type (might be due to invalid IL or missing references)
			//IL_0156: 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_0169: Unknown result type (might be due to invalid IL or missing references)
			//IL_0177: Unknown result type (might be due to invalid IL or missing references)
			//IL_017c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0184: Unknown result type (might be due to invalid IL or missing references)
			//IL_018a: Unknown result type (might be due to invalid IL or missing references)
			//IL_0197: Unknown result type (might be due to invalid IL or missing references)
			//IL_019d: Unknown result type (might be due to invalid IL or missing references)
			//IL_01aa: Unknown result type (might be due to invalid IL or missing references)
			//IL_01b0: 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_01c3: Unknown result type (might be due to invalid IL or missing references)
			//IL_01d0: Unknown result type (might be due to invalid IL or missing references)
			NetworkManager singleton = NetworkManager.Singleton;
			CustomMessagingManager val = ((singleton != null) ? singleton.CustomMessagingManager : null);
			if (val == null)
			{
				return;
			}
			NetworkManager singleton2 = NetworkManager.Singleton;
			if (singleton2 == null || !singleton2.IsServer)
			{
				return;
			}
			int quota = (((Object)(object)TimeOfDay.Instance != (Object)null) ? Math.Max(0, TimeOfDay.Instance.timesFulfilledQuota) : 0);
			string value = string.Join(",", (from id in QuotaUnlockCatalogs.GetStableIds("ship_upgrade")
				where IsNamedUnlockSatisfied(id, quota, (QuotaMilestoneConfig milestone) => milestone.Upgrades)
				select id).Select(Normalize));
			string value2 = BuildRuleString((QuotaMilestoneConfig milestone) => milestone.Constellations);
			string value3 = BuildRuleString((QuotaMilestoneConfig milestone) => milestone.Suits);
			string value4 = BuildRuleString((QuotaMilestoneConfig milestone) => milestone.StoreItems);
			FastBufferWriter val2 = default(FastBufferWriter);
			((FastBufferWriter)(ref val2))..ctor(17408, (Allocator)2, -1);
			try
			{
				((FastBufferWriter)(ref val2)).WriteValueSafe<int>(ref quota, default(ForPrimitives));
				FixedString4096Bytes val3 = FixedString4096Bytes.op_Implicit(TruncateFixed(value, 1000));
				FixedString4096Bytes val4 = FixedString4096Bytes.op_Implicit(TruncateFixed(value2, 1000));
				FixedString4096Bytes val5 = FixedString4096Bytes.op_Implicit(TruncateFixed(value3, 1000));
				FixedString4096Bytes val6 = FixedString4096Bytes.op_Implicit(TruncateFixed(value4, 1000));
				((FastBufferWriter)(ref val2)).WriteValueSafe<FixedString4096Bytes>(ref val3, default(ForFixedStrings));
				((FastBufferWriter)(ref val2)).WriteValueSafe<FixedString4096Bytes>(ref val4, default(ForFixedStrings));
				((FastBufferWriter)(ref val2)).WriteValueSafe<FixedString4096Bytes>(ref val5, default(ForFixedStrings));
				((FastBufferWriter)(ref val2)).WriteValueSafe<FixedString4096Bytes>(ref val6, default(ForFixedStrings));
				val.SendNamedMessageToAll("Y4NGZCompany.QuotaProgressionSnapshot.v2", val2, (NetworkDelivery)3);
			}
			finally
			{
				((IDisposable)(*(FastBufferWriter*)(&val2))/*cast due to .constrained prefix*/).Dispose();
			}
			QuotaProgressionSuitGate.OnUnlockStateMaybeChanged(quota);
		}

		private static string BuildRuleString(Func<QuotaMilestoneConfig, IEnumerable<string>> selector)
		{
			return string.Join(";", from @group in (from m in Milestones.Values
					where m.Enable.Value
					orderby m.Quota
					select m).SelectMany((QuotaMilestoneConfig m) => from name in selector(m)
					select Normalize(name) + "=" + m.Quota).GroupBy<string, string>((string entry) => entry.Substring(0, entry.IndexOf('=')), StringComparer.OrdinalIgnoreCase)
				select @group.First());
		}

		private static void ParseRuleString(string rules, Dictionary<string, int> target)
		{
			target.Clear();
			string[] array = (rules ?? string.Empty).Split(new char[1] { ';' }, StringSplitOptions.RemoveEmptyEntries);
			foreach (string text in array)
			{
				int num = text.IndexOf('=');
				if (num > 0 && int.TryParse(text.Substring(num + 1), out var result))
				{
					target[Normalize(text.Substring(0, num))] = result;
				}
			}
		}

		private unsafe static void OnGateSnapshotMessage(ulong sender, FastBufferReader reader)
		{
			//IL_000a: Unknown result type (might be due to invalid IL or missing references)
			//IL_0010: 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_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_0036: Unknown result type (might be due to invalid IL or missing references)
			//IL_0043: Unknown result type (might be due to invalid IL or missing references)
			//IL_0049: Unknown result type (might be due to invalid IL or missing references)
			//IL_0056: Unknown result type (might be due to invalid IL or missing references)
			//IL_005c: Unknown result type (might be due to invalid IL or missing references)
			if (sender == 0L)
			{
				int val = default(int);
				((FastBufferReader)(ref reader)).ReadValueSafe<int>(ref val, default(ForPrimitives));
				FixedString4096Bytes val2 = default(FixedString4096Bytes);
				((FastBufferReader)(ref reader)).ReadValueSafe<FixedString4096Bytes>(ref val2, default(ForFixedStrings));
				FixedString4096Bytes val3 = default(FixedString4096Bytes);
				((FastBufferReader)(ref reader)).ReadValueSafe<FixedString4096Bytes>(ref val3, default(ForFixedStrings));
				FixedString4096Bytes val4 = default(FixedString4096Bytes);
				((FastBufferReader)(ref reader)).ReadValueSafe<FixedString4096Bytes>(ref val4, default(ForFixedStrings));
				FixedString4096Bytes val5 = default(FixedString4096Bytes);
				((FastBufferReader)(ref reader)).ReadValueSafe<FixedString4096Bytes>(ref val5, default(ForFixedStrings));
				AuthoritativeUnlockedUpgrades.Clear();
				string[] array = ((object)(*(FixedString4096Bytes*)(&val2))/*cast due to .constrained prefix*/).ToString().Split(new char[1] { ',' }, StringSplitOptions.RemoveEmptyEntries);
				foreach (string value in array)
				{
					AuthoritativeUnlockedUpgrades.Add(Normalize(value));
				}
				ParseRuleString(((object)(*(FixedString4096Bytes*)(&val3))/*cast due to .constrained prefix*/).ToString(), AuthoritativeConstellationUnlockQuotas);
				ParseRuleString(((object)(*(FixedString4096Bytes*)(&val4))/*cast due to .constrained prefix*/).ToString(), AuthoritativeSuitUnlockQuotas);
				ParseRuleString(((object)(*(FixedString4096Bytes*)(&val5))/*cast due to .constrained prefix*/).ToString(), AuthoritativeStoreItemUnlockQuotas);
				_authoritativeQuota = Math.Max(0, val);
				_hasAuthoritativeUpgradeSnapshot = true;
				QuotaProgressionSuitGate.OnUnlockStateMaybeChanged(_authoritativeQuota);
			}
		}

		private static string Normalize(string value)
		{
			return new string((value ?? string.Empty).ToLowerInvariant().Where(char.IsLetterOrDigit).ToArray());
		}

		private static string CurrentSaveName()
		{
			return GameNetworkManager.Instance?.currentSaveFileName ?? "unsaved";
		}

		private static int StableHash(string value)
		{
			int num = 17;
			string text = value ?? string.Empty;
			foreach (char c in text)
			{
				num = num * 31 + c;
			}
			if (num != int.MinValue)
			{
				return num;
			}
			return 0;
		}

		private static void WriteEffectiveAudit()
		{
			try
			{
				string text = Y4NGZCompanyPaths.LocalDataFile("quota-progression-effective.tsv");
				List<string> list = new List<string> { "quota\tenabled\tdialogue\tmedia\tcontracts\tupgrades\tconstellations\tsuits\tstore_items\ttokens_per_player\tgroup_credits\tship_fuel" };
				foreach (QuotaMilestoneConfig item in Milestones.Values.OrderBy((QuotaMilestoneConfig m) => m.Quota))
				{
					string value = ((!string.IsNullOrWhiteSpace(item.DialogueFile.Value)) ? ("file:" + item.DialogueFile.Value) : ((!string.IsNullOrWhiteSpace(item.Dialogue.Value)) ? "inline" : "built-in"));
					list.Add(string.Join("\t", item.Quota.ToString(), item.Enable.Value.ToString(), EscapeAudit(value), EscapeAudit(item.MediaFile.Value), EscapeAudit(string.Join(",", item.Contracts)), EscapeAudit(string.Join(",", item.Upgrades)), EscapeAudit(string.Join(",", item.Constellations)), EscapeAudit(string.Join(",", item.Suits)), EscapeAudit(string.Join(",", item.StoreItems)), item.GrantTokensPerPlayer.Value.ToString(), item.GrantGroupCredits.Value.ToString(), item.GrantShipFuel.Value.ToString(CultureInfo.InvariantCulture)));
				}
				list.Add(string.Empty);
				list.Add("shared_key\tvalue");
				list.Add("DefaultMediaFile\t" + EscapeAudit(_defaultMediaFile?.Value));
				list.Add("MumbleAudioFiles\t" + EscapeAudit(_mumbleAudioFiles?.Value));
				list.Add("AlarmAudioFile\t" + EscapeAudit(_alarmAudioFile?.Value));
				list.Add("SoundtrackSource\t" + EscapeAudit(_soundtrackSource?.Value));
				list.Add("SoundtrackMode\t" + SoundtrackMode);
				list.Add("SoundtrackVolume\t" + SoundtrackVolume.ToString(CultureInfo.InvariantCulture));
				list.Add("RandomPerMonitorMedia\t" + RandomPerMonitorMedia);
				list.Add("PerMonitorMediaPool\t" + EscapeAudit(_perMonitorMediaPool?.Value));
				list.Add("MaxConcurrentMediaPlayers\t" + MaxConcurrentMediaPlayers);
				File.WriteAllLines(text, list);
				ManualLogSource log = _log;
				if (log != null)
				{
					log.LogInfo((object)$"Quota progression effective audit written to '{text}' ({Milestones.Count} milestones).");
				}
			}
			catch (Exception ex)
			{
				ManualLogSource log2 = _log;
				if (log2 != null)
				{
					log2.LogWarning((object)("Quota progression effective audit failed: " + ex.Message));
				}
			}
		}

		private static string EscapeAudit(string value)
		{
			return (value ?? string.Empty).Replace("\t", "\\t").Replace("\r", "\\r").Replace("\n", "\\n");
		}

		private static string TruncateFixed(string value, int maxChars)
		{
			value = value ?? string.Empty;
			if (value.Length > maxChars)
			{
				return value.Substring(0, maxChars);
			}
			return value;
		}

		private static int GetRandomizedIndex(int count, int quota, string salt)
		{
			if (count <= 1)
			{
				return 0;
			}
			ConfigEntry<bool> randomizeWithoutReplacement = _randomizeWithoutReplacement;
			if (randomizeWithoutReplacement == null || !randomizeWithoutReplacement.Value)
			{
				return Math.Abs(StableHash(CurrentSaveName() + ":" + salt + ":" + quota)) % count;
			}
			int[] array = Enumerable.Range(0, count).ToArray();
			Random random = new Random(Math.Abs(StableHash(CurrentSaveName() + ":" + salt)));
			for (int num = array.Length - 1; num > 0; num--)
			{
				int num2 = random.Next(0, num + 1);
				int num3 = array[num];
				array[num] = array[num2];
				array[num2] = num3;
			}
			return array[Math.Max(0, quota - 1) % count];
		}

		private static string ComputeSha256(string path)
		{
			try
			{
				using SHA256 sHA = SHA256.Create();
				using FileStream inputStream = File.OpenRead(path);
				return BitConverter.ToString(sHA.ComputeHash(inputStream)).Replace("-", string.Empty).ToLowerInvariant();
			}
			catch
			{
				return string.Empty;
			}
		}
	}
	internal static class QuotaProgressionSuitGate
	{
		private static readonly List<GameObject> HiddenSuits = new List<GameObject>();

		private static readonly Vector3 ParkedLocalPosition = new Vector3(0f, -500f, 0f);

		private static int _lastAppliedQuota = -1;

		private static bool _repositioning;

		internal static string SuitUnlockableName(UnlockableSuit suit)
		{
			List<UnlockableItem> list = StartOfRound.Instance?.unlockablesList?.unlockables;
			int num = (((Object)(object)suit != (Object)null) ? suit.syncedSuitID.Value : (-1));
			if (list == null || num < 0 || num >= list.Count)
			{
				return null;
			}
			return list[num]?.unlockableName;
		}

		private static void SetSuitHidden(UnlockableSuit suit, bool hidden)
		{
			//IL_007e: Unknown result type (might be due to invalid IL or missing references)
			Renderer[] componentsInChildren = ((Component)suit).gameObject.GetComponentsInChildren<Renderer>(true);
			for (int i = 0; i < componentsInChildren.Length; i++)
			{
				componentsInChildren[i].enabled = !hidden;
			}
			InteractTrigger[] componentsInChildren2 = ((Component)suit).gameObject.GetComponentsInChildren<InteractTrigger>(true);
			for (int i = 0; i < componentsInChildren2.Length; i++)
			{
				((Behaviour)componentsInChildren2[i]).enabled = !hidden;
			}
			Collider[] componentsInChildren3 = ((Component)suit).gameObject.GetComponentsInChildren<Collider>(true);
			for (int i = 0; i < componentsInChildren3.Length; i++)
			{
				componentsInChildren3[i].enabled = !hidden;
			}
			if (hidden)
			{
				((Component)suit).transform.localPosition = ParkedLocalPosition;
			}
		}

		internal static void ApplyToRack()
		{
			//IL_0067: Unknown result type (might be due to invalid IL or missing references)
			HiddenSuits.RemoveAll((GameObject item) => (Object)(object)item == (Object)null);
			UnlockableSuit[] array = Object.FindObjectsOfType<UnlockableSuit>();
			foreach (UnlockableSuit val in array)
			{
				string text = SuitUnlockableName(val);
				if (!string.IsNullOrWhiteSpace(text) && !QuotaProgressionRegistry.IsSuitUnlocked(text))
				{
					if (HiddenSuits.Contains(((Component)val).gameObject))
					{
						((Component)val).transform.localPosition = ParkedLocalPosition;
						continue;
					}
					SetSuitHidden(val, hidden: true);
					HiddenSuits.Add(((Component)val).gameObject);
				}
			}
		}

		internal static void OnUnlockStateMaybeChanged(int completedQuota)
		{
			if (completedQuota != _lastAppliedQuota)
			{
				_lastAppliedQuota = completedQuota;
				RefreshRack();
			}
		}

		internal static void RefreshRack()
		{
			StartOfRound instance = StartOfRound.Instance;
			if ((Object)(object)instance == (Object)null || _repositioning)
			{
				return;
			}
			foreach (GameObject hiddenSuit in HiddenSuits)
			{
				UnlockableSuit val = (((Object)(object)hiddenSuit != (Object)null) ? hiddenSuit.GetComponent<UnlockableSuit>() : null);
				if ((Object)(object)val != (Object)null)
				{
					SetSuitHidden(val, hidden: false);
				}
			}
			HiddenSuits.Clear();
			_repositioning = true;
			try
			{
				instance.PositionSuitsOnRack();
			}
			catch (Exception ex)
			{
				ModuleLog.Takeover.LogWarning((object)("Quota progression suit rack refresh failed: " + ex.Message));
			}
			finally
			{
				_repositioning = false;
			}
			ApplyToRack();
		}
	}
	internal static class QuotaProgressionUnlockPatches
	{
		private static TerminalNode _lockedNode;

		[HarmonyPatch(typeof(StartOfRound), "PositionSuitsOnRack")]
		[HarmonyPostfix]
		private static void OnPositionSuitsOnRack()
		{
			QuotaProgressionSuitGate.ApplyToRack();
		}

		[HarmonyPatch(typeof(Terminal), "ParsePlayerSentence")]
		[HarmonyPostfix]
		private static void OnParsePlayerSentence(Terminal __instance, ref TerminalNode __result)
		{
			if (TryGetLockedItemName(__instance, __result, out var itemName))
			{
				__result = BuildLockedNode(itemName);
			}
		}

		[HarmonyPatch(typeof(Terminal), "LoadNewNodeIfAffordable")]
		[HarmonyPrefix]
		private static bool OnLoadNewNodeIfAffordable(Terminal __instance, TerminalNode node)
		{
			if (!TryGetLockedItemName(__instance, node, out var itemName))
			{
				return true;
			}
			__instance.useCreditsCooldown = false;
			__instance.LoadNewNode(BuildLockedNode(itemName));
			return false;
		}

		private static bool TryGetLockedItemName(Terminal terminal, TerminalNode node, out string itemName)
		{
			itemName = null;
			if (terminal?.buyableItemsList == null || (Object)(object)node == (Object)null)
			{
				return false;
			}
			if (node.buyItemIndex < 0 || node.buyItemIndex >= terminal.buyableItemsList.Length)
			{
				return false;
			}
			Item val = terminal.buyableItemsList[node.buyItemIndex];
			if ((Object)(object)val == (Object)null || QuotaProgressionRegistry.IsStoreItemUnlocked(val.itemName))
			{
				return false;
			}
			itemName = val.itemName;
			return true;
		}

		private static TerminalNode BuildLockedNode(string itemName)
		{
			if ((Object)(object)_lockedNode == (Object)null)
			{
				_lockedNode = ScriptableObject.CreateInstance<TerminalNode>();
				((Object)_lockedNode).name = "Y4NGZQuotaLockedItem";
				_lockedNode.clearPreviousText = true;
				_lockedNode.buyItemIndex = -1;
				_lockedNode.shipUnlockableID = -1;
				_lockedNode.buyRerouteToMoon = -1;
				_lockedNode.creatureFileID = -1;
				_lockedNode.storyLogFileID = -1;
				_lockedNode.playSyncedClip = -1;
			}
			_lockedNode.displayText = "\n\n" + itemName + " is not currently available.\nThe Company will provision it after a future quota.\n\n";
			return _lockedNode;
		}
	}
	internal enum QuotaRewardOutcome
	{
		NotConfigured,
		AlreadyPaid,
		Granted,
		Deferred
	}
	internal static