Decompiled source of HylandHelper v0.1.3

HylandHelper.BridgeMod.dll

Decompiled 5 hours ago
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Net;
using System.Net.Http;
using System.Net.Http.Headers;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.Versioning;
using System.Security.Cryptography;
using System.Text;
using System.Text.Json;
using System.Text.Json.Serialization;
using System.Text.RegularExpressions;
using System.Threading;
using System.Threading.Tasks;
using HylandHelper.BridgeContracts;
using HylandHelper.BridgeMod;
using MelonLoader;
using MelonLoader.Utils;
using Microsoft.CodeAnalysis;
using UnityEngine;

[assembly: CompilationRelaxations(8)]
[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
[assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)]
[assembly: InternalsVisibleTo("HylandHelper.BridgeMod.Tests")]
[assembly: MelonInfo(typeof(HylandHelperBridgeMod), "Hyland Helper Bridge", "0.1.3", "MadJag Studios", null)]
[assembly: TargetFramework(".NETCoreApp,Version=v6.0", FrameworkDisplayName = ".NET 6.0")]
[assembly: AssemblyCompany("MadJag Studios")]
[assembly: AssemblyConfiguration("Release")]
[assembly: AssemblyCopyright("Copyright © 2026 MadJag Studios")]
[assembly: AssemblyDescription("Hyland Helper bridge for Schedule I")]
[assembly: AssemblyFileVersion("0.1.3.0")]
[assembly: AssemblyInformationalVersion("0.1.3")]
[assembly: AssemblyProduct("Hyland Helper")]
[assembly: AssemblyTitle("HylandHelper.BridgeMod")]
[assembly: AssemblyVersion("0.1.3.0")]
[module: RefSafetyRules(11)]
namespace Microsoft.CodeAnalysis
{
	[CompilerGenerated]
	[Embedded]
	internal sealed class EmbeddedAttribute : Attribute
	{
	}
}
namespace System.Runtime.CompilerServices
{
	[CompilerGenerated]
	[Embedded]
	[AttributeUsage(AttributeTargets.Class | AttributeTargets.Property | AttributeTargets.Field | AttributeTargets.Event | AttributeTargets.Parameter | AttributeTargets.ReturnValue | AttributeTargets.GenericParameter, AllowMultiple = false, Inherited = false)]
	internal sealed class NullableAttribute : Attribute
	{
		public readonly byte[] NullableFlags;

		public NullableAttribute(byte P_0)
		{
			NullableFlags = new byte[1] { P_0 };
		}

		public NullableAttribute(byte[] P_0)
		{
			NullableFlags = P_0;
		}
	}
	[CompilerGenerated]
	[Embedded]
	[AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct | AttributeTargets.Method | AttributeTargets.Interface | AttributeTargets.Delegate, AllowMultiple = false, Inherited = false)]
	internal sealed class NullableContextAttribute : Attribute
	{
		public readonly byte Flag;

		public NullableContextAttribute(byte P_0)
		{
			Flag = P_0;
		}
	}
	[CompilerGenerated]
	[Embedded]
	[AttributeUsage(AttributeTargets.Module, AllowMultiple = false, Inherited = false)]
	internal sealed class RefSafetyRulesAttribute : Attribute
	{
		public readonly int Version;

		public RefSafetyRulesAttribute(int P_0)
		{
			Version = P_0;
		}
	}
}
namespace HylandHelper.BridgeContracts
{
	public static class BridgeContract
	{
		public const string SchemaVersion = "1.0.0";
	}
	public enum BridgeEnvelopeType
	{
		Heartbeat,
		Snapshot,
		Capabilities,
		SessionEvent,
		Ingest
	}
	public enum BridgeCapabilityStatus
	{
		Present,
		Partial,
		Unavailable,
		Degraded
	}
	public sealed class BridgeEnvelope
	{
		[JsonPropertyName("envelopeType")]
		public string EnvelopeType { get; set; } = "heartbeat";

		[JsonPropertyName("schemaVersion")]
		public string SchemaVersion { get; set; } = "1.0.0";

		[JsonPropertyName("modVersion")]
		public string ModVersion { get; set; } = "";

		[JsonPropertyName("gameVersion")]
		public string GameVersion { get; set; } = "";

		[JsonPropertyName("melonLoaderVersion")]
		public string MelonLoaderVersion { get; set; } = "";

		[JsonPropertyName("saveFingerprint")]
		public BridgeSaveFingerprint SaveFingerprint { get; set; } = new BridgeSaveFingerprint();

		[JsonPropertyName("sequence")]
		public long Sequence { get; set; }

		[JsonPropertyName("sentAt")]
		public string SentAt { get; set; } = "";

		[JsonPropertyName("capabilities")]
		public BridgeCapabilities Capabilities { get; set; } = new BridgeCapabilities();

		[JsonPropertyName("snapshot")]
		public BridgeSnapshot? Snapshot { get; set; }

		[JsonPropertyName("session")]
		public BridgeSessionEvent? Session { get; set; }
	}
	public sealed class BridgeSaveFingerprint
	{
		[JsonPropertyName("slotId")]
		public string SlotId { get; set; } = "";

		[JsonPropertyName("steamIdHash")]
		public string? SteamIdHash { get; set; }

		[JsonPropertyName("fingerprint")]
		public string Fingerprint { get; set; } = "";
	}
	public sealed class BridgeCapabilities
	{
		[JsonPropertyName("slices")]
		public Dictionary<string, BridgeCapabilitySlice> Slices { get; set; } = new Dictionary<string, BridgeCapabilitySlice>();

		[JsonPropertyName("features")]
		public Dictionary<string, BridgeFeatureCapability>? Features { get; set; }
	}
	public sealed class BridgeFeatureCapability
	{
		[JsonPropertyName("supported")]
		public bool Supported { get; set; }

		[JsonPropertyName("protocolVersion")]
		public int ProtocolVersion { get; set; }
	}
	public sealed class BridgeCapabilitySlice
	{
		[JsonPropertyName("status")]
		public string Status { get; set; } = "unavailable";

		[JsonPropertyName("source")]
		public string Source { get; set; } = "notSupported";

		[JsonPropertyName("reason")]
		public string? Reason { get; set; }

		[JsonPropertyName("gameDataType")]
		public string? GameDataType { get; set; }

		[JsonPropertyName("gameDataVersion")]
		public int? GameDataVersion { get; set; }
	}
	public sealed class BridgeSnapshot
	{
		[JsonPropertyName("capturedAt")]
		public string CapturedAt { get; set; } = "";

		[JsonPropertyName("status")]
		public string Status { get; set; } = "partial";

		[JsonPropertyName("slices")]
		public List<string> Slices { get; set; } = new List<string>();

		[JsonPropertyName("money")]
		public BridgeMoneySnapshot? Money { get; set; }

		[JsonPropertyName("rank")]
		public BridgeRankSnapshot? Rank { get; set; }

		[JsonPropertyName("time")]
		public BridgeTimeSnapshot? Time { get; set; }

		[JsonPropertyName("inventory")]
		public BridgeInventorySnapshot? Inventory { get; set; }

		[JsonPropertyName("properties")]
		public BridgePropertiesSnapshot? Properties { get; set; }

		[JsonPropertyName("products")]
		public BridgeProductsSnapshot? Products { get; set; }

		[JsonPropertyName("layout")]
		public BridgeLayoutSnapshot? Layout { get; set; }

		[JsonPropertyName("warnings")]
		public List<string> Warnings { get; set; } = new List<string>();
	}
	public sealed class BridgeMoneySnapshot
	{
		[JsonPropertyName("onlineBalance")]
		public decimal OnlineBalance { get; set; }

		[JsonPropertyName("networth")]
		public decimal Networth { get; set; }

		[JsonPropertyName("lifetimeEarnings")]
		public decimal LifetimeEarnings { get; set; }

		[JsonPropertyName("cashOnHandTotal")]
		public decimal CashOnHandTotal { get; set; }

		[JsonPropertyName("appProfileCash")]
		public decimal AppProfileCash { get; set; }

		[JsonPropertyName("appProfileCashSource")]
		public string AppProfileCashSource { get; set; } = "cashOnHandTotal";

		[JsonPropertyName("cashAggregation")]
		public BridgeCashAggregation? CashAggregation { get; set; }
	}
	public sealed class BridgeCashAggregation
	{
		[JsonPropertyName("includesPlayerInventory")]
		public bool IncludesPlayerInventory { get; set; }

		[JsonPropertyName("includesStorage")]
		public bool IncludesStorage { get; set; }

		[JsonPropertyName("missingContainers")]
		public List<string> MissingContainers { get; set; } = new List<string>();
	}
	public sealed class BridgeRankSnapshot
	{
		[JsonPropertyName("rank")]
		public string? Rank { get; set; }

		[JsonPropertyName("tier")]
		public int? Tier { get; set; }

		[JsonPropertyName("xp")]
		public decimal? Xp { get; set; }

		[JsonPropertyName("totalXp")]
		public decimal? TotalXp { get; set; }

		[JsonPropertyName("unlockedRegions")]
		public List<string> UnlockedRegions { get; set; } = new List<string>();
	}
	public sealed class BridgeTimeSnapshot
	{
		[JsonPropertyName("timeOfDay")]
		public decimal? TimeOfDay { get; set; }

		[JsonPropertyName("elapsedDays")]
		public int? ElapsedDays { get; set; }

		[JsonPropertyName("playtimeSeconds")]
		public decimal? PlaytimeSeconds { get; set; }
	}
	public sealed class BridgeInventorySnapshot
	{
		[JsonPropertyName("totalStacks")]
		public int TotalStacks { get; set; }

		[JsonPropertyName("totalQuantity")]
		public decimal TotalQuantity { get; set; }

		[JsonPropertyName("cashOnHandTotal")]
		public decimal CashOnHandTotal { get; set; }

		[JsonPropertyName("sources")]
		public List<BridgeInventorySourceSummary> Sources { get; set; } = new List<BridgeInventorySourceSummary>();

		[JsonPropertyName("items")]
		public List<BridgeInventoryItemStack> Items { get; set; } = new List<BridgeInventoryItemStack>();

		[JsonPropertyName("truncated")]
		public bool Truncated { get; set; }
	}
	public sealed class BridgeInventorySourceSummary
	{
		[JsonPropertyName("source")]
		public string Source { get; set; } = "";

		[JsonPropertyName("container")]
		public string? Container { get; set; }

		[JsonPropertyName("itemStacks")]
		public int ItemStacks { get; set; }

		[JsonPropertyName("cashOnHand")]
		public decimal CashOnHand { get; set; }
	}
	public sealed class BridgeInventoryItemStack
	{
		[JsonPropertyName("id")]
		public string Id { get; set; } = "";

		[JsonPropertyName("quantity")]
		public decimal Quantity { get; set; }

		[JsonPropertyName("source")]
		public string Source { get; set; } = "";

		[JsonPropertyName("container")]
		public string? Container { get; set; }

		[JsonPropertyName("dataType")]
		public string? DataType { get; set; }

		[JsonPropertyName("quality")]
		public string? Quality { get; set; }

		[JsonPropertyName("packagingId")]
		public string? PackagingId { get; set; }
	}
	public sealed class BridgePropertiesSnapshot
	{
		[JsonPropertyName("ownedCount")]
		public int OwnedCount { get; set; }

		[JsonPropertyName("totalCount")]
		public int TotalCount { get; set; }

		[JsonPropertyName("properties")]
		public List<BridgePropertySummary> Properties { get; set; } = new List<BridgePropertySummary>();
	}
	public sealed class BridgePropertySummary
	{
		[JsonPropertyName("code")]
		public string Code { get; set; } = "";

		[JsonPropertyName("kind")]
		public string Kind { get; set; } = "";

		[JsonPropertyName("isOwned")]
		public bool IsOwned { get; set; }

		[JsonPropertyName("objectCount")]
		public int ObjectCount { get; set; }

		[JsonPropertyName("employeeCount")]
		public int EmployeeCount { get; set; }

		[JsonPropertyName("launderingOperationCount")]
		public int LaunderingOperationCount { get; set; }
	}
	public sealed class BridgeLayoutSnapshot
	{
		[JsonPropertyName("properties")]
		public List<BridgeLayoutProperty> Properties { get; set; } = new List<BridgeLayoutProperty>();

		[JsonPropertyName("truncated")]
		public bool Truncated { get; set; }
	}
	public sealed class BridgeLayoutProperty
	{
		[JsonPropertyName("propertyCode")]
		public string PropertyCode { get; set; } = "";

		[JsonPropertyName("objects")]
		public List<BridgeLayoutObject> Objects { get; set; } = new List<BridgeLayoutObject>();

		[JsonPropertyName("truncated")]
		public bool Truncated { get; set; }
	}
	public sealed class BridgeLayoutObject
	{
		[JsonPropertyName("itemId")]
		public string? ItemId { get; set; }

		[JsonPropertyName("gridGuid")]
		public string? GridGuid { get; set; }

		[JsonPropertyName("x")]
		public int? X { get; set; }

		[JsonPropertyName("y")]
		public int? Y { get; set; }

		[JsonPropertyName("rotation")]
		public int? Rotation { get; set; }

		[JsonPropertyName("dataType")]
		public string DataType { get; set; } = "";
	}
	public sealed class BridgeProductsSnapshot
	{
		[JsonPropertyName("discoveredCount")]
		public int DiscoveredCount { get; set; }

		[JsonPropertyName("discoveredProducts")]
		public List<string> DiscoveredProducts { get; set; } = new List<string>();

		[JsonPropertyName("listedProducts")]
		public List<string> ListedProducts { get; set; } = new List<string>();

		[JsonPropertyName("activeMixOperation")]
		public BridgeActiveMixOperation? ActiveMixOperation { get; set; }

		[JsonPropertyName("isMixComplete")]
		public bool? IsMixComplete { get; set; }

		[JsonPropertyName("recipeCount")]
		public int RecipeCount { get; set; }

		[JsonPropertyName("recipes")]
		public List<BridgeMixRecipeSummary> Recipes { get; set; } = new List<BridgeMixRecipeSummary>();

		[JsonPropertyName("truncated")]
		public bool Truncated { get; set; }
	}
	public sealed class BridgeActiveMixOperation
	{
		[JsonPropertyName("productId")]
		public string? ProductId { get; set; }

		[JsonPropertyName("ingredientId")]
		public string? IngredientId { get; set; }
	}
	public sealed class BridgeMixRecipeSummary
	{
		[JsonPropertyName("product")]
		public string Product { get; set; } = "";

		[JsonPropertyName("mixer")]
		public string Mixer { get; set; } = "";

		[JsonPropertyName("output")]
		public string Output { get; set; } = "";
	}
	public sealed class BridgeSessionEvent
	{
		[JsonPropertyName("eventType")]
		public string EventType { get; set; } = "heartbeat";

		[JsonPropertyName("reason")]
		public string? Reason { get; set; }
	}
	public sealed class BridgePendingMix
	{
		[JsonPropertyName("mixId")]
		public string? MixId { get; set; }

		[JsonPropertyName("productId")]
		public string ProductId { get; set; } = "";

		[JsonPropertyName("ingredientIds")]
		public List<string> IngredientIds { get; set; } = new List<string>();

		[JsonPropertyName("name")]
		public string? Name { get; set; }

		[JsonPropertyName("capturedAt")]
		public string CapturedAt { get; set; } = "";

		[JsonPropertyName("source")]
		public string Source { get; set; } = "inGameTile";

		[JsonPropertyName("steps")]
		public List<BridgePendingMixStep>? Steps { get; set; }
	}
	public sealed class BridgePendingMixStep
	{
		[JsonPropertyName("ingredientId")]
		public string IngredientId { get; set; } = "";

		[JsonPropertyName("outputProductId")]
		public string? OutputProductId { get; set; }

		[JsonPropertyName("outputEffects")]
		public List<string>? OutputEffects { get; set; }
	}
	public static class PendingMixLimits
	{
		public const int MaxChainSteps = 64;

		public const int MaxEffectsPerStep = 12;

		public const int MaxStringLength = 64;

		public static string OverLimitReason(int stepCount)
		{
			return $"Save/Track unavailable: this mix has {stepCount} steps; the shared limit is {64}.";
		}
	}
	public static class PhoneRecipeLimits
	{
		public const int MaxRecipes = 50;

		public const int MaxChainSteps = 64;

		public const int MaxEffects = 8;

		public const int MaxStringLength = 64;

		public const int MaxBodyBytes = 73728;

		public const int StaleGraceHours = 24;
	}
	public sealed class BridgePhoneRecipe
	{
		[JsonPropertyName("recipeId")]
		public string RecipeId { get; set; } = "";

		[JsonPropertyName("name")]
		public string Name { get; set; } = "";

		[JsonPropertyName("productId")]
		public string ProductId { get; set; } = "";

		[JsonPropertyName("ingredientIds")]
		public List<string> IngredientIds { get; set; } = new List<string>();

		[JsonPropertyName("effects")]
		public List<string>? Effects { get; set; }

		[JsonPropertyName("effectsAvailable")]
		public bool EffectsAvailable { get; set; }

		[JsonPropertyName("sellPrice")]
		public double SellPrice { get; set; }

		[JsonPropertyName("profit")]
		public double Profit { get; set; }

		[JsonPropertyName("profitMargin")]
		public double ProfitMargin { get; set; }

		[JsonPropertyName("profileMode")]
		public string ProfileMode { get; set; } = "standard";

		[JsonPropertyName("source")]
		public string Source { get; set; } = "app-saved";

		[JsonPropertyName("publishedAt")]
		public string PublishedAt { get; set; } = "";

		[JsonPropertyName("expiresAt")]
		public string ExpiresAt { get; set; } = "";
	}
	public sealed class BridgePhoneRecipeSnapshot
	{
		[JsonPropertyName("schemaVersion")]
		public int SchemaVersion { get; set; } = 1;

		[JsonPropertyName("snapshotId")]
		public string SnapshotId { get; set; } = "";

		[JsonPropertyName("publishedAt")]
		public string PublishedAt { get; set; } = "";

		[JsonPropertyName("expiresAt")]
		public string ExpiresAt { get; set; } = "";

		[JsonPropertyName("omittedCount")]
		public int OmittedCount { get; set; }

		[JsonPropertyName("recipes")]
		public List<BridgePhoneRecipe> Recipes { get; set; } = new List<BridgePhoneRecipe>();
	}
	public sealed class BridgeExchangeRequest
	{
		[JsonPropertyName("envelope")]
		public BridgeEnvelope Envelope { get; set; } = new BridgeEnvelope();

		[JsonPropertyName("downlink")]
		public BridgeExchangeRequestDownlink Downlink { get; set; } = new BridgeExchangeRequestDownlink();
	}
	public sealed class BridgeExchangeRequestDownlink
	{
		[JsonPropertyName("phoneRecipes")]
		[JsonIgnore(Condition = JsonIgnoreCondition.Never)]
		public string? PhoneRecipes { get; set; }

		[JsonPropertyName("advisorDispatches")]
		[JsonIgnore(Condition = JsonIgnoreCondition.Never)]
		public string? AdvisorDispatches { get; set; }
	}
	public sealed class BridgeExchangeResponse
	{
		[JsonPropertyName("ingest")]
		public BridgeIngestAcknowledgement Ingest { get; set; } = new BridgeIngestAcknowledgement();

		[JsonPropertyName("downlink")]
		public BridgeExchangeResponseDownlink Downlink { get; set; } = new BridgeExchangeResponseDownlink();

		[JsonPropertyName("pollHintSeconds")]
		public int? PollHintSeconds { get; set; }
	}
	public sealed class BridgeIngestAcknowledgement
	{
		[JsonPropertyName("stored")]
		public bool Stored { get; set; }

		[JsonPropertyName("stale")]
		public bool Stale { get; set; }

		[JsonPropertyName("sequence")]
		public long Sequence { get; set; }

		[JsonPropertyName("currentSequence")]
		public long? CurrentSequence { get; set; }

		[JsonPropertyName("receivedAt")]
		public string? ReceivedAt { get; set; }

		[JsonPropertyName("gameVersionChanged")]
		public bool? GameVersionChanged { get; set; }
	}
	public sealed class BridgeExchangeResponseDownlink
	{
		[JsonPropertyName("phoneRecipes")]
		public BridgePhoneRecipeExchangeSlice PhoneRecipes { get; set; } = new BridgePhoneRecipeExchangeSlice();

		[JsonPropertyName("advisorDispatches")]
		public BridgeAdvisorDispatchExchangeSlice AdvisorDispatches { get; set; } = new BridgeAdvisorDispatchExchangeSlice();
	}
	public sealed class BridgePhoneRecipeExchangeSlice
	{
		[JsonPropertyName("state")]
		public string State { get; set; } = "unsupported";

		[JsonPropertyName("revision")]
		public string? Revision { get; set; }

		[JsonPropertyName("payload")]
		public BridgePhoneRecipeSnapshot? Payload { get; set; }

		[JsonPropertyName("retryAfterSeconds")]
		public int? RetryAfterSeconds { get; set; }
	}
	public sealed class BridgeAdvisorDispatchExchangeSlice
	{
		[JsonPropertyName("state")]
		public string State { get; set; } = "unsupported";

		[JsonPropertyName("revision")]
		public string? Revision { get; set; }

		[JsonPropertyName("payload")]
		public BridgeAdvisorDispatchPayload? Payload { get; set; }

		[JsonPropertyName("retryAfterSeconds")]
		public int? RetryAfterSeconds { get; set; }
	}
	public sealed class BridgeAdvisorDispatchPayload
	{
		[JsonPropertyName("scopeGeneration")]
		public string ScopeGeneration { get; set; } = "";

		[JsonPropertyName("capturedAt")]
		public string CapturedAt { get; set; } = "";

		[JsonPropertyName("dispatches")]
		public List<BridgeAdvisorDispatchRecord> Dispatches { get; set; } = new List<BridgeAdvisorDispatchRecord>();
	}
	public sealed class BridgeAdvisorDispatchRecord
	{
		[JsonPropertyName("id")]
		public string Id { get; set; } = "";

		[JsonPropertyName("createdAt")]
		public string CreatedAt { get; set; } = "";

		[JsonPropertyName("headline")]
		public string Headline { get; set; } = "";

		[JsonPropertyName("body")]
		public string Body { get; set; } = "";

		[JsonPropertyName("personaId")]
		public string PersonaId { get; set; } = "";

		[JsonPropertyName("voiceVersion")]
		public string VoiceVersion { get; set; } = "";

		[JsonPropertyName("triggerId")]
		public string TriggerId { get; set; } = "";
	}
	public sealed class BridgeExchangeError
	{
		[JsonPropertyName("error")]
		public BridgeStructuredError Error { get; set; } = new BridgeStructuredError();
	}
	public sealed class BridgeStructuredError
	{
		[JsonPropertyName("code")]
		public string Code { get; set; } = "";

		[JsonPropertyName("message")]
		public string Message { get; set; } = "";

		[JsonPropertyName("retriable")]
		public bool Retriable { get; set; }

		[JsonPropertyName("requiresRepair")]
		public string? RequiresRepair { get; set; }

		[JsonPropertyName("retryAfterSeconds")]
		public int? RetryAfterSeconds { get; set; }
	}
	public sealed class BridgeTileChallenge
	{
		[JsonPropertyName("version")]
		public int Version { get; set; }

		[JsonPropertyName("challenge")]
		public string Challenge { get; set; } = "";

		[JsonPropertyName("tileBuild")]
		public string TileBuild { get; set; } = "";

		[JsonPropertyName("protocolVersion")]
		public int ProtocolVersion { get; set; }
	}
	public static class GameDatasetIds
	{
		private static readonly Dictionary<string, string> CanonicalAliases = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase)
		{
			["ogkush"] = "og_kush",
			["sourdiesel"] = "sour_diesel",
			["greencrack"] = "green_crack",
			["granddaddypurple"] = "granddaddy_purple",
			["shroom"] = "shrooms",
			["energydrink"] = "energy_drink",
			["motoroil"] = "motor_oil",
			["megabean"] = "mega_bean",
			["flumedicine"] = "flu_medicine",
			["horsesemen"] = "horse_semen",
			["viagor"] = "viagra"
		};

		private static readonly Dictionary<string, string> FriendlyLabels = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase)
		{
			["og_kush"] = "OG Kush",
			["sour_diesel"] = "Sour Diesel",
			["green_crack"] = "Green Crack",
			["granddaddy_purple"] = "Granddaddy Purple",
			["shrooms"] = "Shrooms",
			["meth"] = "Meth",
			["cocaine"] = "Cocaine",
			["flu_medicine"] = "Flu Medicine",
			["energy_drink"] = "Energy Drink",
			["motor_oil"] = "Motor Oil",
			["mega_bean"] = "Mega Bean",
			["horse_semen"] = "Horse Semen",
			["viagra"] = "Viagor"
		};

		private static readonly HashSet<string> BaseProducts = new HashSet<string>(StringComparer.OrdinalIgnoreCase) { "og_kush", "sour_diesel", "green_crack", "granddaddy_purple", "meth", "cocaine", "shrooms" };

		public static string Canonicalize(string value)
		{
			if (string.IsNullOrWhiteSpace(value))
			{
				return value;
			}
			string text = value.Trim();
			if (!CanonicalAliases.TryGetValue(text, out string value2))
			{
				return text;
			}
			return value2;
		}

		public static bool IsBaseProduct(string value)
		{
			return BaseProducts.Contains(Canonicalize(value));
		}

		public static string FriendlyName(string value)
		{
			string text = Canonicalize(value);
			if (FriendlyLabels.TryGetValue(text, out string value2))
			{
				return value2;
			}
			string[] array = text.Replace('_', ' ').Split(' ', StringSplitOptions.RemoveEmptyEntries);
			for (int i = 0; i < array.Length; i++)
			{
				if (array[i].Length != 0)
				{
					array[i] = char.ToUpperInvariant(array[i][0]) + array[i].Substring(1);
				}
			}
			return string.Join(" ", array);
		}
	}
}
namespace HylandHelper.BridgeMod
{
	internal enum AdvisorDispatchCacheState
	{
		Empty,
		Fresh,
		Expired
	}
	internal sealed class AdvisorDispatchCacheRead
	{
		public AdvisorDispatchCacheState State { get; init; }

		public string? ScopeGeneration { get; init; }

		public string? Revision { get; init; }

		public DateTimeOffset? CapturedAt { get; init; }

		public List<BridgeAdvisorDispatchRecord> Dispatches { get; init; } = new List<BridgeAdvisorDispatchRecord>();
	}
	internal sealed record AdvisorDispatchCacheCheckpoint(bool Exists, byte[]? Bytes);
	internal sealed class AdvisorDispatchCache
	{
		private sealed class AdvisorDispatchCacheDocument
		{
			[JsonPropertyName("version")]
			public int Version { get; set; }

			[JsonPropertyName("saveFingerprint")]
			public string SaveFingerprint { get; set; } = "";

			[JsonPropertyName("scopeGeneration")]
			public string ScopeGeneration { get; set; } = "";

			[JsonPropertyName("revision")]
			public string Revision { get; set; } = "";

			[JsonPropertyName("capturedAt")]
			public string CapturedAt { get; set; } = "";

			[JsonPropertyName("dispatches")]
			public List<BridgeAdvisorDispatchRecord> Dispatches { get; set; } = new List<BridgeAdvisorDispatchRecord>();
		}

		public static readonly TimeSpan DispatchTtl = TimeSpan.FromDays(7.0);

		private const int MaxCacheBytes = 65536;

		private const int MaxDispatches = 20;

		private static readonly JsonSerializerOptions JsonOptions = new JsonSerializerOptions
		{
			PropertyNameCaseInsensitive = true,
			WriteIndented = true
		};

		private readonly string _path;

		private readonly Action<string, string> _replaceFile;

		private readonly Action<string> _deleteFile;

		public static string DefaultPath => Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.UserProfile), "AppData", "LocalLow", "TVGS", "Schedule I", "HylandHelper", "advisor-dispatches-cache.json");

		public AdvisorDispatchCache(string path, Action<string, string>? replaceFile = null, Action<string>? deleteFile = null)
		{
			_path = path;
			_replaceFile = replaceFile ?? ((Action<string, string>)delegate(string source, string destination)
			{
				File.Move(source, destination, overwrite: true);
			});
			_deleteFile = deleteFile ?? new Action<string>(File.Delete);
		}

		public bool Write(BridgeAdvisorDispatchPayload payload, string revision, DateTimeOffset now, string saveFingerprint)
		{
			if (!SaveGenerationIdentity.IsV2Fingerprint(saveFingerprint) || !CanWrite(payload, revision, now, out var capturedAt))
			{
				return false;
			}
			AdvisorDispatchCacheDocument value = new AdvisorDispatchCacheDocument
			{
				Version = 2,
				SaveFingerprint = saveFingerprint,
				ScopeGeneration = payload.ScopeGeneration,
				Revision = revision,
				CapturedAt = capturedAt.ToString("O"),
				Dispatches = payload.Dispatches
			};
			return WriteRawAtomic(JsonSerializer.SerializeToUtf8Bytes(value, JsonOptions));
		}

		internal static bool CanWrite(BridgeAdvisorDispatchPayload payload, string revision, DateTimeOffset now)
		{
			DateTimeOffset capturedAt;
			return CanWrite(payload, revision, now, out capturedAt);
		}

		private static bool CanWrite(BridgeAdvisorDispatchPayload payload, string revision, DateTimeOffset now, out DateTimeOffset capturedAt)
		{
			if (!IsSafePayload(payload, revision, out capturedAt))
			{
				return false;
			}
			DateTimeOffset result;
			return payload.Dispatches.All((BridgeAdvisorDispatchRecord record) => DateTimeOffset.TryParse(record.CreatedAt, out result) && now < result + DispatchTtl);
		}

		public AdvisorDispatchCacheRead Read(DateTimeOffset now, string saveFingerprint)
		{
			try
			{
				if (!SaveGenerationIdentity.IsV2Fingerprint(saveFingerprint) || !TryReadDocument(out AdvisorDispatchCacheDocument document) || !string.Equals(document.SaveFingerprint, saveFingerprint, StringComparison.Ordinal))
				{
					return Empty();
				}
				BridgeAdvisorDispatchPayload bridgeAdvisorDispatchPayload = new BridgeAdvisorDispatchPayload
				{
					ScopeGeneration = document.ScopeGeneration,
					CapturedAt = document.CapturedAt,
					Dispatches = (document.Dispatches ?? new List<BridgeAdvisorDispatchRecord>())
				};
				if (!IsSafePayload(bridgeAdvisorDispatchPayload, document.Revision, out var capturedAt))
				{
					return Empty();
				}
				DateTimeOffset result;
				List<BridgeAdvisorDispatchRecord> list = bridgeAdvisorDispatchPayload.Dispatches.Where((BridgeAdvisorDispatchRecord record) => DateTimeOffset.TryParse(record.CreatedAt, out result) && now < result + DispatchTtl).ToList();
				return new AdvisorDispatchCacheRead
				{
					State = ((bridgeAdvisorDispatchPayload.Dispatches.Count <= 0 || list.Count != 0) ? AdvisorDispatchCacheState.Fresh : AdvisorDispatchCacheState.Expired),
					ScopeGeneration = bridgeAdvisorDispatchPayload.ScopeGeneration,
					Revision = document.Revision,
					CapturedAt = capturedAt,
					Dispatches = list
				};
			}
			catch
			{
				return Empty();
			}
		}

		public bool Clear()
		{
			try
			{
				if (File.Exists(_path))
				{
					_deleteFile(_path);
				}
				return !File.Exists(_path);
			}
			catch
			{
				return false;
			}
		}

		public bool ClearForScope(string saveFingerprint)
		{
			try
			{
				if (!SaveGenerationIdentity.IsV2Fingerprint(saveFingerprint))
				{
					return false;
				}
				if (!File.Exists(_path))
				{
					return true;
				}
				if (!TryReadDocument(out AdvisorDispatchCacheDocument document) || !string.Equals(document.SaveFingerprint, saveFingerprint, StringComparison.Ordinal))
				{
					return true;
				}
				return Clear();
			}
			catch
			{
				return false;
			}
		}

		internal bool TryCapture(out AdvisorDispatchCacheCheckpoint checkpoint)
		{
			checkpoint = new AdvisorDispatchCacheCheckpoint(Exists: false, null);
			try
			{
				if (!File.Exists(_path))
				{
					return true;
				}
				if (!IsBoundedFile())
				{
					return false;
				}
				checkpoint = new AdvisorDispatchCacheCheckpoint(Exists: true, File.ReadAllBytes(_path));
				return true;
			}
			catch
			{
				return false;
			}
		}

		internal bool Restore(AdvisorDispatchCacheCheckpoint checkpoint)
		{
			if (!checkpoint.Exists || checkpoint.Bytes == null)
			{
				return Clear();
			}
			return WriteRawAtomic(checkpoint.Bytes);
		}

		private static AdvisorDispatchCacheRead Empty()
		{
			return new AdvisorDispatchCacheRead
			{
				State = AdvisorDispatchCacheState.Empty
			};
		}

		private static bool IsSafePayload(BridgeAdvisorDispatchPayload? payload, string? revision, out DateTimeOffset capturedAt)
		{
			capturedAt = default(DateTimeOffset);
			if (payload == null || !Bounded(payload.ScopeGeneration, 128) || !Bounded(revision, 128) || !DateTimeOffset.TryParse(payload.CapturedAt, out capturedAt) || payload.Dispatches == null || payload.Dispatches.Count > 20)
			{
				return false;
			}
			HashSet<string> hashSet = new HashSet<string>(StringComparer.Ordinal);
			foreach (BridgeAdvisorDispatchRecord dispatch in payload.Dispatches)
			{
				if (dispatch == null || !Bounded(dispatch.Id, 64) || !hashSet.Add(dispatch.Id) || !DateTimeOffset.TryParse(dispatch.CreatedAt, out var result) || result > capturedAt || capturedAt - result > DispatchTtl || !Bounded(dispatch.Headline, 60) || !Bounded(dispatch.Body, 512) || !Bounded(dispatch.PersonaId, 64) || !Bounded(dispatch.VoiceVersion, 64) || !Bounded(dispatch.TriggerId, 64))
				{
					return false;
				}
			}
			return true;
		}

		private bool WriteRawAtomic(byte[] bytes)
		{
			if (bytes.Length == 0 || bytes.Length > 65536)
			{
				return false;
			}
			if (ExistingBytesEqual(bytes))
			{
				return true;
			}
			string text = null;
			try
			{
				string directoryName = Path.GetDirectoryName(_path);
				if (!string.IsNullOrWhiteSpace(directoryName))
				{
					Directory.CreateDirectory(directoryName);
				}
				text = $"{_path}.{Guid.NewGuid():N}.tmp";
				using (FileStream fileStream = new FileStream(text, FileMode.CreateNew, FileAccess.Write, FileShare.None))
				{
					fileStream.Write(bytes, 0, bytes.Length);
					fileStream.Flush(flushToDisk: true);
				}
				_replaceFile(text, _path);
				text = null;
				return true;
			}
			catch
			{
				return false;
			}
			finally
			{
				if (text != null)
				{
					try
					{
						File.Delete(text);
					}
					catch
					{
					}
				}
			}
		}

		private bool ExistingBytesEqual(byte[] desired)
		{
			try
			{
				FileInfo fileInfo = new FileInfo(_path);
				return fileInfo.Exists && fileInfo.Length == desired.Length && File.ReadAllBytes(_path).SequenceEqual(desired);
			}
			catch
			{
				return false;
			}
		}

		private bool IsBoundedFile()
		{
			if (!File.Exists(_path))
			{
				return false;
			}
			long length = new FileInfo(_path).Length;
			if (length > 0)
			{
				return length <= 65536;
			}
			return false;
		}

		private bool TryReadDocument(out AdvisorDispatchCacheDocument document)
		{
			document = null;
			if (!IsBoundedFile())
			{
				return false;
			}
			byte[] array = File.ReadAllBytes(_path);
			using JsonDocument jsonDocument = JsonDocument.Parse(array);
			if (!HasExactProperties(jsonDocument.RootElement, "version", "saveFingerprint", "scopeGeneration", "revision", "capturedAt", "dispatches"))
			{
				return false;
			}
			AdvisorDispatchCacheDocument advisorDispatchCacheDocument = JsonSerializer.Deserialize<AdvisorDispatchCacheDocument>(array, JsonOptions);
			if (advisorDispatchCacheDocument == null || advisorDispatchCacheDocument.Version != 2 || !SaveGenerationIdentity.IsV2Fingerprint(advisorDispatchCacheDocument.SaveFingerprint))
			{
				return false;
			}
			document = advisorDispatchCacheDocument;
			return true;
		}

		private static bool HasExactProperties(JsonElement element, params string[] expected)
		{
			if (element.ValueKind != JsonValueKind.Object)
			{
				return false;
			}
			HashSet<string> hashSet = new HashSet<string>(expected, StringComparer.Ordinal);
			foreach (JsonProperty item in element.EnumerateObject())
			{
				if (!hashSet.Remove(item.Name))
				{
					return false;
				}
			}
			return hashSet.Count == 0;
		}

		private static bool Bounded(string? value, int maxLength)
		{
			if (!string.IsNullOrWhiteSpace(value))
			{
				return value.Length <= maxLength;
			}
			return false;
		}
	}
	internal interface IBridgeCadenceAuthority
	{
		bool TryAdmitAdaptiveFull(DateTimeOffset now);

		bool TryAdmitNonAdaptiveStart(DateTimeOffset now);

		bool TryAdmitHeartbeat(DateTimeOffset now, out bool adaptive);

		void NotifyStarted(BridgeEnvelopePlan plan, bool adaptive, DateTimeOffset now);

		void NotifyCompleted(BridgeEnvelopePlan plan, DateTimeOffset now);
	}
	internal sealed class BridgeCadenceController : IBridgeCadenceAuthority
	{
		internal static readonly TimeSpan FloorInterval = TimeSpan.FromSeconds(3.0);

		internal static readonly TimeSpan BurstInterval = TimeSpan.FromSeconds(3.0);

		internal static readonly TimeSpan BurstMaxDuration = TimeSpan.FromSeconds(30.0);

		internal static readonly TimeSpan ActiveVisibleInterval = TimeSpan.FromSeconds(7.0);

		internal static readonly TimeSpan HintLease = TimeSpan.FromSeconds(60.0);

		internal static readonly TimeSpan RuntimeObservationFloor = TimeSpan.FromSeconds(10.0);

		internal const int MaxAdaptiveStartsPerWindow = 20;

		private readonly TimeSpan _idle;

		private readonly bool _enabled;

		private DateTimeOffset _lastStart = DateTimeOffset.MinValue;

		private DateTimeOffset _lastCompleted = DateTimeOffset.MinValue;

		private DateTimeOffset _burst = DateTimeOffset.MinValue;

		private DateTimeOffset _lease = DateTimeOffset.MinValue;

		private DateTimeOffset _lastOverlayFull = DateTimeOffset.MinValue;

		private bool _visible;

		private bool _immediate;

		private readonly Queue<DateTimeOffset> _adaptive = new Queue<DateTimeOffset>();

		private string? _facts;

		private DateTimeOffset? _armed;

		internal bool HasPendingImmediate => _immediate;

		internal BridgeCadenceController(TimeSpan idleInterval, bool accelerationEnabled)
		{
			_idle = idleInterval;
			_enabled = accelerationEnabled;
		}

		internal void ObserveScreen(bool visible, bool openedTransition, DateTimeOffset now)
		{
			if (_enabled)
			{
				_visible = visible;
				if (openedTransition)
				{
					_immediate = true;
					_burst = now;
				}
			}
		}

		internal void NotifyHint(int? hint, bool acceptedFreshDelivery, DateTimeOffset now)
		{
			if (_enabled && acceptedFreshDelivery && hint.HasValue)
			{
				int valueOrDefault = hint.GetValueOrDefault();
				if (valueOrDefault >= 1 && valueOrDefault <= 60)
				{
					_lease = now + HintLease;
				}
			}
		}

		public bool TryAdmitAdaptiveFull(DateTimeOffset now)
		{
			return Admit(now, adaptive: true);
		}

		public bool TryAdmitNonAdaptiveStart(DateTimeOffset now)
		{
			return Admit(now, adaptive: false);
		}

		public bool TryAdmitHeartbeat(DateTimeOffset now, out bool adaptive)
		{
			adaptive = false;
			if (!_enabled)
			{
				return now >= _lastCompleted + _idle;
			}
			if (_immediate && Admit(now, adaptive: true))
			{
				adaptive = true;
				return true;
			}
			TimeSpan timeSpan = Interval(now);
			adaptive = timeSpan < _idle;
			DateTimeOffset dateTimeOffset = (adaptive ? (_lastStart + timeSpan) : (_lastCompleted + _idle));
			if (now >= dateTimeOffset)
			{
				return Admit(now, adaptive);
			}
			return false;
		}

		private TimeSpan Interval(DateTimeOffset now)
		{
			if (!_visible || !(now - _burst < BurstMaxDuration))
			{
				if (!_visible && !(now < _lease))
				{
					return _idle;
				}
				return ActiveVisibleInterval;
			}
			return BurstInterval;
		}

		private bool Admit(DateTimeOffset now, bool adaptive)
		{
			if (!_enabled)
			{
				return true;
			}
			if (now - _lastStart < FloorInterval)
			{
				return false;
			}
			while (_adaptive.Count > 0 && now - _adaptive.Peek() >= TimeSpan.FromMinutes(1.0))
			{
				_adaptive.Dequeue();
			}
			if (adaptive)
			{
				return _adaptive.Count < 20;
			}
			return true;
		}

		public void NotifyStarted(BridgeEnvelopePlan plan, bool adaptive, DateTimeOffset now)
		{
			if (plan.Kind == BridgeEnvelopeKind.FullSnapshot)
			{
				_armed = null;
				_lastOverlayFull = now;
			}
			if (_enabled)
			{
				_lastStart = now;
				if (adaptive)
				{
					_adaptive.Enqueue(now);
				}
				_immediate = false;
			}
		}

		public void NotifyCompleted(BridgeEnvelopePlan plan, DateTimeOffset now)
		{
			_lastCompleted = now;
		}

		internal void OnOverlayAccepted(LiveOverlayReadResult result, DateTimeOffset now)
		{
			if (result.Status != LiveOverlayReadStatus.Accepted || result.Document == null)
			{
				return;
			}
			string text = JsonSerializer.Serialize(result.Document.Facts, new JsonSerializerOptions
			{
				DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull
			});
			bool flag = text != _facts;
			_facts = text;
			if (result.Rotation || flag)
			{
				DateTimeOffset valueOrDefault = _armed.GetValueOrDefault();
				if (!_armed.HasValue)
				{
					valueOrDefault = now;
					_armed = valueOrDefault;
				}
			}
		}

		internal bool ShouldRequestRuntimeObservationFull(DateTimeOffset now)
		{
			if (!_armed.HasValue || now - _lastOverlayFull < RuntimeObservationFloor)
			{
				return false;
			}
			_armed = null;
			return true;
		}

		internal void Reset()
		{
			_lastStart = (_lastCompleted = (_burst = (_lease = DateTimeOffset.MinValue)));
			_visible = (_immediate = false);
			_adaptive.Clear();
			_armed = null;
			_facts = null;
		}
	}
	internal static class AcceptedFreshDelivery
	{
		internal static bool Evaluate(BridgeIngestAcknowledgement acknowledgement, BridgeDownlinkApplyOutcome outcome, string? requestKnownRevision, string? responseRevision)
		{
			bool flag = acknowledgement.Stored && !acknowledgement.Stale;
			if (flag)
			{
				bool flag2 = (uint)outcome <= 1u;
				flag = flag2;
			}
			if (flag && !string.IsNullOrWhiteSpace(requestKnownRevision) && !string.IsNullOrWhiteSpace(responseRevision))
			{
				return responseRevision != requestKnownRevision;
			}
			return false;
		}
	}
	internal sealed class BridgeClient : IDisposable
	{
		public const int DefaultMaxExchangeRequestBytes = 1048576;

		public const int DefaultMaxExchangeResponseBytes = 262144;

		private static readonly JsonSerializerOptions JsonOptions = new JsonSerializerOptions
		{
			DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull,
			PropertyNameCaseInsensitive = true
		};

		private readonly HttpClient _httpClient;

		private readonly Uri _baseUri;

		private readonly MelonBridgeLogger _logger;

		private readonly int _maxExchangeRequestBytes;

		private readonly int _maxExchangeResponseBytes;

		public BridgeClient(string baseUrl, MelonBridgeLogger logger)
			: this(baseUrl, logger, null)
		{
		}

		internal BridgeClient(string baseUrl, MelonBridgeLogger logger, HttpMessageHandler? handler, int maxExchangeRequestBytes = 1048576, int maxExchangeResponseBytes = 262144)
		{
			if (maxExchangeRequestBytes <= 0 || maxExchangeResponseBytes <= 0)
			{
				throw new ArgumentOutOfRangeException("maxExchangeRequestBytes");
			}
			_baseUri = new Uri(baseUrl.TrimEnd('/') + "/", UriKind.Absolute);
			_logger = logger;
			_maxExchangeRequestBytes = maxExchangeRequestBytes;
			_maxExchangeResponseBytes = maxExchangeResponseBytes;
			_httpClient = ((handler == null) ? new HttpClient() : new HttpClient(handler));
			_httpClient.Timeout = TimeSpan.FromSeconds(15.0);
		}

		public async Task<BridgeExchangeResponse> ExchangeAsync(string bridgeToken, BridgeExchangeRequest exchange, CancellationToken cancellationToken)
		{
			string text = JsonSerializer.Serialize(exchange, JsonOptions);
			if (Encoding.UTF8.GetByteCount(text) > _maxExchangeRequestBytes)
			{
				throw new BridgePayloadException("Bridge exchange request exceeded the local byte limit.");
			}
			using HttpRequestMessage request = new HttpRequestMessage(HttpMethod.Post, new Uri(_baseUri, "api/bridge/exchange"))
			{
				Content = new StringContent(text, Encoding.UTF8, "application/json")
			};
			request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", bridgeToken);
			using HttpResponseMessage response = await _httpClient.SendAsync(request, HttpCompletionOption.ResponseHeadersRead, cancellationToken).ConfigureAwait(continueOnCapturedContext: false);
			TimeSpan? retryAfter = ReadRetryAfter(response);
			byte[] array = await ReadBoundedBodyAsync(response, _maxExchangeResponseBytes, cancellationToken).ConfigureAwait(continueOnCapturedContext: false);
			if (!response.IsSuccessStatusCode)
			{
				throw CreateExchangeException(response.StatusCode, array, retryAfter);
			}
			try
			{
				return JsonSerializer.Deserialize<BridgeExchangeResponse>(array, JsonOptions) ?? throw new BridgePayloadException("Bridge exchange response was empty.");
			}
			catch (JsonException innerException)
			{
				throw new BridgePayloadException("Bridge exchange response was malformed.", innerException);
			}
		}

		public async Task<PendingMixPostResponse?> SendPendingMixAsync(string bridgeToken, BridgePendingMix mix, CancellationToken cancellationToken)
		{
			try
			{
				return await PostJsonAsync<PendingMixPostResponse>("api/bridge/mixes", mix, bridgeToken, cancellationToken).ConfigureAwait(continueOnCapturedContext: false);
			}
			catch (BridgeClientException ex) when (ex.IsUnauthorized)
			{
				throw;
			}
			catch (OperationCanceledException exception) when (!cancellationToken.IsCancellationRequested)
			{
				_logger.WarningThrottled("Pending mix POST quiet-failed.", exception);
				return null;
			}
			catch (Exception ex2) when (!(ex2 is OperationCanceledException))
			{
				_logger.WarningThrottled("Pending mix POST quiet-failed.", ex2);
				return null;
			}
		}

		public async Task<PairingSession> StartPairingAsync(BridgeSaveFingerprint saveFingerprint, CancellationToken cancellationToken)
		{
			PairingStartResponse pairingStartResponse = await PostJsonAsync<PairingStartResponse>("api/bridge/pairing/start", new { saveFingerprint }, null, cancellationToken).ConfigureAwait(continueOnCapturedContext: false);
			if (string.IsNullOrWhiteSpace(pairingStartResponse.PairingId) || string.IsNullOrWhiteSpace(pairingStartResponse.Code) || string.IsNullOrWhiteSpace(pairingStartResponse.ModSecret))
			{
				throw new InvalidOperationException("Pairing start response was incomplete.");
			}
			return new PairingSession(pairingStartResponse.PairingId, pairingStartResponse.Code, pairingStartResponse.ModSecret);
		}

		public async Task<PairingStatusResponse> PollPairingStatusAsync(PairingSession session, CancellationToken cancellationToken)
		{
			string path = "api/bridge/pairing/" + Uri.EscapeDataString(session.PairingId) + "/status";
			PairingStatusResponse pairingStatusResponse = await PostJsonAsync<PairingStatusResponse>(path, new
			{
				modSecret = session.ModSecret
			}, null, cancellationToken).ConfigureAwait(continueOnCapturedContext: false);
			if (!string.IsNullOrWhiteSpace(pairingStatusResponse.BridgeToken))
			{
				pairingStatusResponse.VerifiedScope = pairingStatusResponse.Scope?.ToPairingScope(session.PairingId) ?? throw new InvalidOperationException("Claimed pairing response omitted its verified scope.");
			}
			return pairingStatusResponse;
		}

		public Task SendHeartbeatAsync(string bridgeToken, BridgeEnvelope heartbeat, CancellationToken cancellationToken)
		{
			return PostJsonAsync<object>("api/bridge/ingest", heartbeat, bridgeToken, cancellationToken);
		}

		public async Task<LatestStateResponse?> GetLatestAsync(string bridgeToken, PairingScope scope, CancellationToken cancellationToken)
		{
			string relativeUri = $"api/bridge/latest?pairingId={Uri.EscapeDataString(scope.PairingId)}&deviceId={Uri.EscapeDataString(scope.DeviceId)}&profileId={Uri.EscapeDataString(scope.ProfileId)}";
			try
			{
				using HttpRequestMessage request = new HttpRequestMessage(HttpMethod.Get, new Uri(_baseUri, relativeUri));
				request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", bridgeToken);
				using HttpResponseMessage response = await _httpClient.SendAsync(request, HttpCompletionOption.ResponseHeadersRead, cancellationToken).ConfigureAwait(continueOnCapturedContext: false);
				if (response.StatusCode == HttpStatusCode.Unauthorized)
				{
					throw new BridgeClientException(HttpStatusCode.Unauthorized, "Latest poll was unauthorized.");
				}
				if (!response.IsSuccessStatusCode)
				{
					_logger.WarningThrottled($"Latest poll returned {response.StatusCode}.");
					return null;
				}
				return JsonSerializer.Deserialize<LatestStateResponse>(await ReadBoundedBodyAsync(response, _maxExchangeResponseBytes, cancellationToken).ConfigureAwait(continueOnCapturedContext: false), JsonOptions);
			}
			catch (BridgeClientException ex) when (ex.IsUnauthorized)
			{
				throw;
			}
			catch (Exception ex2) when (!(ex2 is OperationCanceledException))
			{
				_logger.WarningThrottled("Latest poll quiet-failed.", ex2);
				return null;
			}
		}

		public async Task<PhoneRecipePollResult?> GetPhoneRecipeAvailabilityAsync(string bridgeToken, CancellationToken cancellationToken)
		{
			_ = 1;
			try
			{
				using HttpRequestMessage request = new HttpRequestMessage(HttpMethod.Get, new Uri(_baseUri, "api/bridge/recipes/availability"));
				request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", bridgeToken);
				using HttpResponseMessage response = await _httpClient.SendAsync(request, HttpCompletionOption.ResponseHeadersRead, cancellationToken).ConfigureAwait(continueOnCapturedContext: false);
				if (response.StatusCode == HttpStatusCode.Unauthorized)
				{
					throw new BridgeClientException(HttpStatusCode.Unauthorized, "Phone recipe poll was unauthorized.");
				}
				if (response.StatusCode == HttpStatusCode.NotFound)
				{
					return new PhoneRecipePollResult(null, NotPublished: true);
				}
				if (!response.IsSuccessStatusCode)
				{
					_logger.WarningThrottled($"Phone recipe poll returned {response.StatusCode}.");
					return null;
				}
				BridgePhoneRecipeSnapshot bridgePhoneRecipeSnapshot = JsonSerializer.Deserialize<BridgePhoneRecipeSnapshot>(await ReadBoundedBodyAsync(response, _maxExchangeResponseBytes, cancellationToken).ConfigureAwait(continueOnCapturedContext: false), JsonOptions);
				return (bridgePhoneRecipeSnapshot == null) ? null : new PhoneRecipePollResult(bridgePhoneRecipeSnapshot, NotPublished: false);
			}
			catch (BridgeClientException ex) when (ex.IsUnauthorized)
			{
				throw;
			}
			catch (Exception ex2) when (!(ex2 is OperationCanceledException))
			{
				_logger.WarningThrottled("Phone recipe poll quiet-failed.", ex2);
				return null;
			}
		}

		public void Dispose()
		{
			_httpClient.Dispose();
		}

		private async Task<T> PostJsonAsync<T>(string path, object body, string? bearerToken, CancellationToken cancellationToken)
		{
			string content = JsonSerializer.Serialize(body, JsonOptions);
			using HttpRequestMessage request = new HttpRequestMessage(HttpMethod.Post, new Uri(_baseUri, path))
			{
				Content = new StringContent(content, Encoding.UTF8, "application/json")
			};
			if (!string.IsNullOrWhiteSpace(bearerToken))
			{
				request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", bearerToken);
			}
			using HttpResponseMessage response = await _httpClient.SendAsync(request, HttpCompletionOption.ResponseHeadersRead, cancellationToken).ConfigureAwait(continueOnCapturedContext: false);
			if (!response.IsSuccessStatusCode)
			{
				throw new BridgeClientException(response.StatusCode, $"{path} returned HTTP {response.StatusCode}.");
			}
			if (typeof(T) == typeof(object))
			{
				return (T)new object();
			}
			return JsonSerializer.Deserialize<T>(await ReadBoundedBodyAsync(response, _maxExchangeResponseBytes, cancellationToken).ConfigureAwait(continueOnCapturedContext: false), JsonOptions) ?? throw new InvalidOperationException(path + " returned an empty response.");
		}

		private static BridgeClientException CreateExchangeException(HttpStatusCode statusCode, byte[] body, TimeSpan? retryAfter)
		{
			BridgeStructuredError bridgeStructuredError = null;
			try
			{
				BridgeExchangeError bridgeExchangeError = JsonSerializer.Deserialize<BridgeExchangeError>(body, JsonOptions);
				if (bridgeExchangeError?.Error != null && IsSafeErrorCode(bridgeExchangeError.Error.Code))
				{
					bridgeStructuredError = bridgeExchangeError.Error;
				}
			}
			catch (JsonException)
			{
			}
			string text = bridgeStructuredError?.Code;
			string requiresRepair = (IsBounded(bridgeStructuredError?.RequiresRepair, 64) ? bridgeStructuredError.RequiresRepair : null);
			int? num = bridgeStructuredError?.RetryAfterSeconds;
			TimeSpan? obj;
			if (num.HasValue)
			{
				int valueOrDefault = num.GetValueOrDefault();
				if (valueOrDefault >= 0 && valueOrDefault <= 86400)
				{
					obj = TimeSpan.FromSeconds(bridgeStructuredError.RetryAfterSeconds.Value);
					goto IL_00cd;
				}
			}
			obj = null;
			goto IL_00cd;
			IL_00cd:
			TimeSpan? timeSpan = obj;
			string value = ((text == null) ? "HTTP_ERROR" : text);
			return new BridgeClientException(statusCode, $"Bridge exchange failed with HTTP {statusCode} ({value}).", text, bridgeStructuredError?.Retriable ?? false, requiresRepair, retryAfter ?? timeSpan);
		}

		private static bool IsSafeErrorCode(string? code)
		{
			switch (code)
			{
			case "SAVE_SCOPE_MISMATCH":
			case "BRIDGE_TOKEN_INVALID":
			case "PAIRING_SCOPE_MISMATCH":
			case "ENTITLEMENT_PROOF_REQUIRED":
			case "RATE_LIMITED":
				return true;
			default:
				return false;
			}
		}

		private static bool IsBounded(string? value, int maxLength)
		{
			if (!string.IsNullOrWhiteSpace(value))
			{
				return value.Length <= maxLength;
			}
			return false;
		}

		private static TimeSpan? ReadRetryAfter(HttpResponseMessage response)
		{
			RetryConditionHeaderValue retryAfter = response.Headers.RetryAfter;
			TimeSpan? timeSpan = retryAfter?.Delta;
			if (timeSpan.HasValue)
			{
				return timeSpan.GetValueOrDefault();
			}
			DateTimeOffset? dateTimeOffset = retryAfter?.Date;
			if (dateTimeOffset.HasValue)
			{
				DateTimeOffset valueOrDefault = dateTimeOffset.GetValueOrDefault();
				TimeSpan timeSpan2 = valueOrDefault - DateTimeOffset.UtcNow;
				return (timeSpan2 > TimeSpan.Zero) ? timeSpan2 : TimeSpan.Zero;
			}
			return null;
		}

		private static async Task<byte[]> ReadBoundedBodyAsync(HttpResponseMessage response, int maxBytes, CancellationToken cancellationToken)
		{
			long? contentLength = response.Content.Headers.ContentLength;
			if (contentLength.HasValue && contentLength.GetValueOrDefault() > 0 && response.Content.Headers.ContentLength > maxBytes)
			{
				throw new BridgePayloadException("Bridge response exceeded the local byte limit.");
			}
			byte[] result;
			await using (Stream source = await response.Content.ReadAsStreamAsync(cancellationToken).ConfigureAwait(continueOnCapturedContext: false))
			{
				using MemoryStream destination = new MemoryStream();
				byte[] buffer = new byte[Math.Min(8192, maxBytes + 1)];
				while (true)
				{
					int num = await source.ReadAsync(buffer.AsMemory(0, buffer.Length), cancellationToken).ConfigureAwait(continueOnCapturedContext: false);
					if (num == 0)
					{
						break;
					}
					if (destination.Length + num > maxBytes)
					{
						throw new BridgePayloadException("Bridge response exceeded the local byte limit.");
					}
					destination.Write(buffer, 0, num);
				}
				result = destination.ToArray();
			}
			return result;
		}
	}
	internal sealed class BridgeClientException : Exception
	{
		public HttpStatusCode StatusCode { get; }

		public string? Code { get; }

		public bool Retriable { get; }

		public string? RequiresRepair { get; }

		public TimeSpan? RetryAfter { get; }

		public bool IsUnauthorized => StatusCode == HttpStatusCode.Unauthorized;

		public bool IsUnsupported => StatusCode == HttpStatusCode.NotFound;

		public bool IsTransient
		{
			get
			{
				if (!Retriable && StatusCode != HttpStatusCode.TooManyRequests)
				{
					return StatusCode >= HttpStatusCode.InternalServerError;
				}
				return true;
			}
		}

		public BridgeClientException(HttpStatusCode statusCode, string message, string? code = null, bool retriable = false, string? requiresRepair = null, TimeSpan? retryAfter = null)
			: base(message)
		{
			StatusCode = statusCode;
			Code = code;
			Retriable = retriable;
			RequiresRepair = requiresRepair;
			RetryAfter = retryAfter;
		}
	}
	internal sealed class BridgePayloadException : Exception
	{
		public BridgePayloadException(string message)
			: base(message)
		{
		}

		public BridgePayloadException(string message, Exception innerException)
			: base(message, innerException)
		{
		}
	}
	internal sealed record PhoneRecipePollResult(BridgePhoneRecipeSnapshot? Snapshot, bool NotPublished);
	internal sealed record PairingSession(string PairingId, string Code, string ModSecret);
	internal sealed class PairingStartResponse
	{
		[JsonPropertyName("pairingId")]
		public string? PairingId { get; set; }

		[JsonPropertyName("code")]
		public string? Code { get; set; }

		[JsonPropertyName("modSecret")]
		public string? ModSecret { get; set; }
	}
	internal sealed class PairingStatusResponse
	{
		[JsonPropertyName("status")]
		public string Status { get; set; } = "";

		[JsonPropertyName("bridgeToken")]
		public string? BridgeToken { get; set; }

		[JsonPropertyName("scope")]
		public PairingScopeResponse? Scope { get; set; }

		[JsonIgnore]
		public PairingScope? VerifiedScope { get; set; }
	}
	internal sealed class PairingScopeResponse
	{
		[JsonPropertyName("deviceId")]
		public string DeviceId { get; set; } = "";

		[JsonPropertyName("profileId")]
		public string ProfileId { get; set; } = "";

		[JsonPropertyName("saveFingerprint")]
		public BridgeSaveFingerprint? SaveFingerprint { get; set; }

		public PairingScope ToPairingScope(string pairingId)
		{
			PairingScope obj = new PairingScope
			{
				PairingId = pairingId,
				DeviceId = DeviceId,
				ProfileId = ProfileId,
				SaveFingerprint = (SaveFingerprint ?? new BridgeSaveFingerprint())
			};
			if (!obj.IsValid())
			{
				throw new InvalidOperationException("Claimed pairing response contained an invalid verified scope.");
			}
			return obj;
		}
	}
	internal sealed class LatestStateResponse
	{
		[JsonPropertyName("sequence")]
		public long? Sequence { get; set; }
	}
	internal sealed class PendingMixPostResponse
	{
		[JsonPropertyName("accepted")]
		public bool Accepted { get; set; }

		[JsonPropertyName("deduped")]
		public bool Deduped { get; set; }

		[JsonPropertyName("mixId")]
		public string? MixId { get; set; }
	}
	internal sealed class BridgeCommandReader
	{
		private const long MaxCommandBytes = 8192L;

		private readonly MelonBridgeLogger _logger;

		private readonly string _path;

		private static readonly Regex IsoTimestampPattern = new Regex("^\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}(?:\\.\\d{1,9})?(?:Z|[+-]\\d{2}:\\d{2})$", RegexOptions.CultureInvariant);

		private static readonly JsonSerializerOptions JsonOptions = new JsonSerializerOptions
		{
			PropertyNameCaseInsensitive = true
		};

		public BridgeCommandReader(MelonBridgeLogger logger)
		{
			_logger = logger;
			_path = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.UserProfile), "AppData", "LocalLow", "TVGS", "Schedule I", "HylandHelper", "bridge-command.json");
		}

		public BridgeCommand? TakeCommand()
		{
			try
			{
				if (!File.Exists(_path))
				{
					return null;
				}
				BridgeCommand command = null;
				try
				{
					if (new FileInfo(_path).Length <= 8192)
					{
						if (!TryParseCommand(File.ReadAllText(_path), out command))
						{
							command = null;
						}
					}
					else
					{
						_logger.WarningThrottled("Bridge command file exceeded the safety limit; discarding.");
					}
				}
				catch (Exception exception)
				{
					_logger.WarningThrottled("Bridge command file was unreadable; discarding.", exception);
				}
				try
				{
					File.Delete(_path);
				}
				catch
				{
				}
				return command;
			}
			catch (Exception exception2)
			{
				_logger.WarningThrottled("Bridge command check failed.", exception2);
				return null;
			}
		}

		public bool TakeUnlink()
		{
			try
			{
				if (!File.Exists(_path))
				{
					return false;
				}
				string a = null;
				BridgeCommand command = null;
				try
				{
					if (new FileInfo(_path).Length <= 8192 && TryParseCommand(File.ReadAllText(_path), out command))
					{
						a = command?.Command;
					}
				}
				catch (Exception exception)
				{
					_logger.WarningThrottled("Bridge command file was unreadable; discarding.", exception);
				}
				if (IsSupported(command) && string.Equals(a, "saveMix", StringComparison.OrdinalIgnoreCase))
				{
					return false;
				}
				try
				{
					File.Delete(_path);
				}
				catch
				{
				}
				return string.Equals(a, "unlink", StringComparison.OrdinalIgnoreCase);
			}
			catch (Exception exception2)
			{
				_logger.WarningThrottled("Bridge unlink command check failed.", exception2);
				return false;
			}
		}

		private static bool IsSupported(BridgeCommand? command)
		{
			if (command == null || string.IsNullOrWhiteSpace(command.Command))
			{
				return false;
			}
			if (string.Equals(command.Command, "unlink", StringComparison.OrdinalIgnoreCase))
			{
				return true;
			}
			if (!SaveGenerationIdentity.IsV2Fingerprint(command.SaveFingerprint))
			{
				return false;
			}
			if (string.Equals(command.Command, "syncNow", StringComparison.OrdinalIgnoreCase))
			{
				if (command.Version == 1 && IsSafeSyncRequestId(command.RequestId) && IsUtcIsoTimestamp(command.RequestedAt))
				{
					return command.Mix == null;
				}
				return false;
			}
			if (!string.Equals(command.Command, "saveMix", StringComparison.OrdinalIgnoreCase) || !IsSafeRequestId(command.RequestId) || command.Mix == null)
			{
				return false;
			}
			BridgePendingMix mix = command.Mix;
			if (!IsBoundedString(mix.ProductId) || mix.IngredientIds == null || mix.IngredientIds.Count == 0 || mix.IngredientIds.Count > 64 || mix.IngredientIds.Exists((string id) => !IsBoundedString(id)) || (mix.Name != null && mix.Name.Length > 64) || !IsIsoTimestamp(mix.CapturedAt) || !string.Equals(mix.Source, "inGameTile", StringComparison.Ordinal) || mix.Steps == null || mix.Steps.Count != mix.IngredientIds.Count)
			{
				return false;
			}
			for (int num = 0; num < mix.Steps.Count; num++)
			{
				BridgePendingMixStep bridgePendingMixStep = mix.Steps[num];
				if (bridgePendingMixStep == null || !IsBoundedString(bridgePendingMixStep.IngredientId) || !string.Equals(bridgePendingMixStep.IngredientId, mix.IngredientIds[num], StringComparison.OrdinalIgnoreCase) || !IsBoundedString(bridgePendingMixStep.OutputProductId) || bridgePendingMixStep.OutputEffects == null || bridgePendingMixStep.OutputEffects.Count > 12 || bridgePendingMixStep.OutputEffects.Exists((string effect) => !IsBoundedString(effect)))
				{
					return false;
				}
			}
			return true;
		}

		private static bool TryParseCommand(string json, out BridgeCommand? command)
		{
			command = null;
			using JsonDocument jsonDocument = JsonDocument.Parse(json);
			JsonElement rootElement = jsonDocument.RootElement;
			if (rootElement.ValueKind != JsonValueKind.Object || !rootElement.TryGetProperty("command", out var value) || value.ValueKind != JsonValueKind.String)
			{
				return false;
			}
			string[] array = value.GetString()?.ToLowerInvariant() switch
			{
				"unlink" => new string[1] { "command" }, 
				"syncnow" => new string[5] { "command", "version", "saveFingerprint", "requestId", "requestedAt" }, 
				"savemix" => new string[4] { "command", "saveFingerprint", "requestId", "mix" }, 
				_ => Array.Empty<string>(), 
			};
			if (array.Length == 0 || !HasExactFields(rootElement, (IReadOnlyCollection<string>)(object)array))
			{
				return false;
			}
			command = JsonSerializer.Deserialize<BridgeCommand>(json, JsonOptions);
			return IsSupported(command);
		}

		private static bool HasExactFields(JsonElement root, IReadOnlyCollection<string> expected)
		{
			HashSet<string> hashSet = new HashSet<string>(expected, StringComparer.Ordinal);
			foreach (JsonProperty item in root.EnumerateObject())
			{
				if (!hashSet.Remove(item.Name))
				{
					return false;
				}
			}
			return hashSet.Count == 0;
		}

		private static bool IsSafeRequestId(string? requestId)
		{
			if (string.IsNullOrWhiteSpace(requestId) || requestId.Length > 64)
			{
				return false;
			}
			foreach (char c in requestId)
			{
				if (!char.IsLetterOrDigit(c) && c != '-' && c != '_')
				{
					return false;
				}
			}
			return true;
		}

		private static bool IsSafeSyncRequestId(string? requestId)
		{
			if (requestId == null || requestId.Length != 32)
			{
				return false;
			}
			for (int i = 0; i < requestId.Length; i++)
			{
				if (!char.IsLetterOrDigit(requestId[i]))
				{
					return false;
				}
			}
			return true;
		}

		private static bool IsBoundedString(string? value)
		{
			if (!string.IsNullOrWhiteSpace(value))
			{
				return value.Length <= 64;
			}
			return false;
		}

		private static bool IsIsoTimestamp(string? value)
		{
			DateTimeOffset result;
			if (!string.IsNullOrWhiteSpace(value) && value.Length >= 20 && value.Length <= 40 && IsoTimestampPattern.IsMatch(value))
			{
				return DateTimeOffset.TryParse(value, CultureInfo.InvariantCulture, DateTimeStyles.RoundtripKind, out result);
			}
			return false;
		}

		private static bool IsUtcIsoTimestamp(string? value)
		{
			if (IsIsoTimestamp(value))
			{
				return value.EndsWith("Z", StringComparison.Ordinal);
			}
			return false;
		}
	}
	internal sealed class BridgeCommand
	{
		[JsonPropertyName("command")]
		public string? Command { get; set; }

		[JsonPropertyName("version")]
		public int? Version { get; set; }

		[JsonPropertyName("saveFingerprint")]
		public string? SaveFingerprint { get; set; }

		[JsonPropertyName("requestId")]
		public string? RequestId { get; set; }

		[JsonPropertyName("requestedAt")]
		public string? RequestedAt { get; set; }

		[JsonPropertyName("mix")]
		public BridgePendingMix? Mix { get; set; }
	}
	internal sealed class BridgeCommandResultWriter
	{
		private readonly MelonBridgeLogger _logger;

		private readonly string _directory;

		public BridgeCommandResultWriter(MelonBridgeLogger logger)
		{
			_logger = logger;
			_directory = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.UserProfile), "AppData", "LocalLow", "TVGS", "Schedule I", "HylandHelper");
			try
			{
				Directory.CreateDirectory(_directory);
			}
			catch
			{
			}
		}

		public void Write(string requestId, string status, string saveFingerprint)
		{
			if (!IsSafeRequestId(requestId) || string.IsNullOrWhiteSpace(status) || !SaveGenerationIdentity.IsV2Fingerprint(saveFingerprint))
			{
				return;
			}
			string text = Path.Combine(_directory, "bridge-command-result-" + requestId + ".json");
			string text2 = $"{text}.{Guid.NewGuid():N}.tmp";
			try
			{
				Directory.CreateDirectory(_directory);
				File.WriteAllText(text2, JsonSerializer.Serialize(new
				{
					version = 1,
					requestId = requestId,
					status = status,
					saveFingerprint = saveFingerprint
				}));
				File.Move(text2, text, overwrite: true);
			}
			catch (Exception exception)
			{
				try
				{
					if (File.Exists(text2))
					{
						File.Delete(text2);
					}
				}
				catch
				{
				}
				_logger.WarningThrottled("Bridge command result write failed.", exception);
			}
		}

		private static bool IsSafeRequestId(string requestId)
		{
			if (string.IsNullOrWhiteSpace(requestId) || requestId.Length > 64)
			{
				return false;
			}
			foreach (char c in requestId)
			{
				if (!char.IsLetterOrDigit(c) && c != '-' && c != '_')
				{
					return false;
				}
			}
			return true;
		}
	}
	internal static class BridgeConstants
	{
		public const string BackendBaseUrl = "https://hyland-helper-backend.vercel.app";

		public const string ModVersion = "0.1.3";

		public const string TargetGameVersion = "0.4.5f2";

		public const string TargetMelonLoaderVersion = "0.7.3";

		public static readonly TimeSpan PairingPollInterval = TimeSpan.FromSeconds(5.0);

		public static readonly TimeSpan SnapshotInterval = TimeSpan.FromSeconds(30.0);

		public static readonly TimeSpan LatestPollInterval = TimeSpan.FromSeconds(30.0);

		public static readonly TimeSpan PhoneRecipePollInterval = TimeSpan.FromSeconds(30.0);

		public static readonly TimeSpan RetryBackoff = TimeSpan.FromSeconds(10.0);
	}
	internal enum BridgeDownlinkApplyOutcome
	{
		Applied,
		Cleared,
		Unchanged,
		NotPublished,
		TemporarilyUnavailable,
		Unsupported,
		NotConsented,
		EntitlementUnavailable,
		Malformed,
		PersistenceFailed
	}
	internal sealed class BridgeDownlinkCoordinator
	{
		private readonly PhoneRecipeCache _phoneRecipes;

		private readonly BridgeSettingsStore _settingsStore;

		private readonly BridgeLocalSettings _settings;

		private readonly Func<DateTimeOffset> _utcNow;

		private readonly AdvisorDispatchCache? _advisorDispatches;

		public bool PhoneRecipesRequireLegacyPoll { get; private set; }

		public bool AdvisorDispatchesSupported { get; private set; } = true;

		public BridgeDownlinkCoordinator(PhoneRecipeCache phoneRecipes, BridgeSettingsStore settingsStore, BridgeLocalSettings settings, Func<DateTimeOffset> utcNow, AdvisorDispatchCache? advisorDispatches = null)
		{
			_phoneRecipes = phoneRecipes;
			_settingsStore = settingsStore;
			_settings = settings;
			_utcNow = utcNow;
			_advisorDispatches = advisorDispatches;
		}

		public void PopulateKnownRevisions(BridgeExchangeRequestDownlink downlink)
		{
			downlink.PhoneRecipes = _settings.PhoneRecipesRevision;
			downlink.AdvisorDispatches = _settings.AdvisorDispatchesRevision;
		}

		public BridgeDownlinkApplyOutcome ApplyPhoneRecipes(BridgePhoneRecipeExchangeSlice? slice, string saveFingerprint)
		{
			if (!SaveGenerationIdentity.IsV2Fingerprint(saveFingerprint) || slice == null || string.IsNullOrWhiteSpace(slice.State))
			{
				return BridgeDownlinkApplyOutcome.Malformed;
			}
			switch (slice.State)
			{
			case "changed":
				if (!ValidRevision(slice.Revision) || slice.Payload == null)
				{
					return BridgeDownlinkApplyOutcome.Malformed;
				}
				PhoneRecipesRequireLegacyPoll = false;
				return ReplacePhoneRecipes(slice.Payload, slice.Revision, saveFingerprint);
			case "unchanged":
				if (!ValidRevision(slice.Revision) || !string.Equals(slice.Revision, _settings.PhoneRecipesRevision, StringComparison.Ordinal))
				{
					return BridgeDownlinkApplyOutcome.Malformed;
				}
				PhoneRecipesRequireLegacyPoll = false;
				return BridgeDownlinkApplyOutcome.Unchanged;
			case "cleared":
				if (!ValidRevision(slice.Revision))
				{
					return BridgeDownlinkApplyOutcome.Malformed;
				}
				PhoneRecipesRequireLegacyPoll = false;
				return ClearPhoneRecipes(slice.Revision, saveFingerprint);
			case "notPublished":
				if (slice.Revision != null || slice.Payload != null)
				{
					return BridgeDownlinkApplyOutcome.Malformed;
				}
				PhoneRecipesRequireLegacyPoll = false;
				return BridgeDownlinkApplyOutcome.NotPublished;
			case "temporarilyUnavailable":
				if (slice.Payload != null || (slice.Revision != null && !ValidRevision(slice.Revision)) || !ValidRetryAfter(slice.RetryAfterSeconds))
				{
					return BridgeDownlinkApplyOutcome.Malformed;
				}
				PhoneRecipesRequireLegacyPoll = false;
				_phoneRecipes.MarkOffline(_utcNow(), saveFingerprint);
				return BridgeDownlinkApplyOutcome.TemporarilyUnavailable;
			case "unsupported":
				if (slice.Payload != null || (slice.Revision != null && !ValidRevision(slice.Revision)) || slice.RetryAfterSeconds.HasValue)
				{
					return BridgeDownlinkApplyOutcome.Malformed;
				}
				PhoneRecipesRequireLegacyPoll = true;
				return BridgeDownlinkApplyOutcome.Unsupported;
			default:
				return BridgeDownlinkApplyOutcome.Malformed;
			}
		}

		public BridgeDownlinkApplyOutcome ApplyAdvisorDispatches(BridgeAdvisorDispatchExchangeSlice? slice, string saveFingerprint)
		{
			if (!SaveGenerationIdentity.IsV2Fingerprint(saveFingerprint) || _advisorDispatches == null || slice == null || string.IsNullOrWhiteSpace(slice.State))
			{
				return BridgeDownlinkApplyOutcome.Malformed;
			}
			switch (slice.State)
			{
			case "changed":
				if (!ValidRevision(slice.Revision) || slice.Payload == null)
				{
					return BridgeDownlinkApplyOutcome.Malformed;
				}
				AdvisorDispatchesSupported = true;
				return ReplaceAdvisorDispatches(slice.Payload, slice.Revision, saveFingerprint);
			case "unchanged":
				if (!ValidRevision(slice.Revision) || !string.Equals(slice.Revision, _settings.AdvisorDispatchesRevision, StringComparison.Ordinal))
				{
					return BridgeDownlinkApplyOutcome.Malformed;
				}
				AdvisorDispatchesSupported = true;
				return BridgeDownlinkApplyOutcome.Unchanged;
			case "cleared":
				if (!ValidRevision(slice.Revision))
				{
					return BridgeDownlinkApplyOutcome.Malformed;
				}
				AdvisorDispatchesSupported = true;
				return ClearAdvisorDispatches(slice.Revision, saveFingerprint);
			case "notConsented":
				if (slice.Payload != null || (slice.Revision != null && !ValidRevision(slice.Revision)))
				{
					return BridgeDownlinkApplyOutcome.Malformed;
				}
				AdvisorDispatchesSupported = true;
				return BridgeDownlinkApplyOutcome.NotConsented;
			case "entitlementUnavailable":
				if (slice.Payload != null || (slice.Revision != null && !ValidRevision(slice.Revision)))
				{
					return BridgeDownlinkApplyOutcome.Malformed;
				}
				AdvisorDispatchesSupported = true;
				return BridgeDownlinkApplyOutcome.EntitlementUnavailable;
			case "temporarilyUnavailable":
				if (slice.Payload != null || (slice.Revision != null && !ValidRevision(slice.Revision)) || !ValidRetryAfter(slice.RetryAfterSeconds))
				{
					return BridgeDownlinkApplyOutcome.Malformed;
				}
				AdvisorDispatchesSupported = true;
				return BridgeDownlinkApplyOutcome.TemporarilyUnavailable;
			case "unsupported":
				if (slice.Payload != null || (slice.Revision != null && !ValidRevision(slice.Revision)) || slice.RetryAfterSeconds.HasValue)
				{
					return BridgeDownlinkApplyOutcome.Malformed;
				}
				AdvisorDispatchesSupported = false;
				return BridgeDownlinkApplyOutcome.Unsupported;
			default:
				return BridgeDownlinkApplyOutcome.Malformed;
			}
		}

		public void ClearTokenScopedState()
		{
			_phoneRecipes.Clear();
			_advisorDispatches?.Clear();
			PhoneRecipesRequireLegacyPoll = false;
			AdvisorDispatchesSupported = true;
		}

		private BridgeDownlinkApplyOutcome ReplacePhoneRecipes(BridgePhoneRecipeSnapshot snapshot, string revision, string saveFingerprint)
		{
			if (!_phoneRecipes.TryCapture(out PhoneRecipeCacheCheckpoint checkpoint))
			{
				return BridgeDownlinkApplyOutcome.PersistenceFailed;
			}
			if (!_phoneRecipes.Write(snapshot, _utcNow(), saveFingerprint))
			{
				return BridgeDownlinkApplyOutcome.PersistenceFailed;
			}
			return PersistPhoneRevision(revision, checkpoint, BridgeDownlinkApplyOutcome.Applied);
		}

		private BridgeDownlinkApplyOutcome ClearPhoneRecipes(string revision, string saveFingerprint)
		{
			if (!_phoneRecipes.TryCapture(out PhoneRecipeCacheCheckpoint checkpoint))
			{
				return BridgeDownlinkApplyOutcome.PersistenceFailed;
			}
			if (!_phoneRecipes.ClearForScope(saveFingerprint))
			{
				return BridgeDownlinkApplyOutcome.PersistenceFailed;
			}
			return PersistPhoneRevision(revision, checkpoint, BridgeDownlinkApplyOutcome.Cleared);
		}

		private BridgeDownlinkApplyOutcome PersistPhoneRevision(string revision, PhoneRecipeCacheCheckpoint checkpoint, BridgeDownlinkApplyOutcome success)
		{
			string phoneRecipesRevision = _settings.PhoneRecipesRevision;
			_settings.PhoneRecipesRevision = revision;
			if (_settingsStore.Save(_settings))
			{
				return success;
			}
			_settings.PhoneRecipesRevision = phoneRecipesRevision;
			_phoneRecipes.Restore(checkpoint);
			return BridgeDownlinkApplyOutcome.PersistenceFailed;
		}

		private BridgeDownlinkApplyOutcome ReplaceAdvisorDispatches(BridgeAdvisorDispatchPayload payload, string revision, string saveFingerprint)
		{
			if (!AdvisorDispatchCache.CanWrite(payload, revision, _utcNow()))
			{
				return BridgeDownlinkApplyOutcome.Malformed;
			}
			if (_advisorDispatches == null || !_advisorDispatches.TryCapture(out AdvisorDispatchCacheCheckpoint checkpoint))
			{
				return BridgeDownlinkApplyOutcome.PersistenceFailed;
			}
			if (!_advisorDispatches.Write(payload, revision, _utcNow(), saveFingerprint))
			{
				return BridgeDownlinkApplyOutcome.PersistenceFailed;
			}
			return PersistAdvisorRevision(revision, checkpoint, BridgeDownlinkApplyOutcome.Applied);
		}

		private BridgeDownlinkApplyOutcome ClearAdvisorDispatches(string revision, string saveFingerprint)
		{
			if (_advisorDispatches == null || !_advisorDispatches.TryCapture(out AdvisorDispatchCacheCheckpoint checkpoint))
			{
				return BridgeDownlinkApplyOutcome.PersistenceFailed;
			}
			if (!_advisorDispatches.ClearForScope(saveFingerprint))
			{
				return BridgeDownlinkApplyOutcome.PersistenceFailed;
			}
			return PersistAdvisorRevision(revision, checkpoint, BridgeDownlinkApplyOutcome.Cleared);
		}

		private BridgeDownlinkApplyOutcome PersistAdvisorRevision(string revision, AdvisorDispatchCacheCheckpoint checkpoint, BridgeDownlinkApplyOutcome success)
		{
			string advisorDispatchesRevision = _settings.AdvisorDispatchesRevision;
			_settings.AdvisorDispatchesRevision = revision;
			if (_settingsStore.Save(_settings))
			{
				return success;
			}
			_settings.AdvisorDispatchesRevision = advisorDispatchesRevision;
			_advisorDispatches.Restore(checkpoint);
			return BridgeDownlinkApplyOutcome.PersistenceFailed;
		}

		private static bool ValidRevision(string? revision)
		{
			return PairingScope.IsBounded(revision, 128);
		}

		private static bool ValidRetryAfter(int? retryAfterSeconds)
		{
			if (retryAfterSeconds.HasValue)
			{
				if (retryAfterSeconds.HasValue)
				{
					int valueOrDefault = retryAfterSeconds.GetValueOrDefault();
					if (valueOrDefault >= 0)
					{
						return valueOrDefault <= 86400;
					}
				}
				return false;
			}
			return true;
		}
	}
	internal enum BridgeExchangeMode
	{
		Negotiating,
		Exchange,
		Legacy,
		Repair
	}
	internal sealed record BridgeAcknowledgementResult(long SequenceFloor, bool RequiresFullResynchronization);
	internal sealed class BridgeExchangeState
	{
		private DateTimeOffset _retryAt = DateTimeOffset.MinValue;

		private int _attempt;

		public BridgeExchangeMode Mode { get; private set; }

		public bool CanSendHeartbeat => Mode == BridgeExchangeMode.Exchange;

		public bool CanAttemptExchange(DateTimeOffset now)
		{
			BridgeExchangeMode mode = Mode;
			if ((uint)mode <= 1u)
			{
				return now >= _retryAt;
			}
			return false;
		}

		public BridgeAcknowledgementResult ApplySuccess(BridgeIngestAcknowledgement acknowledgement, long localSequence, DateTimeOffset now)
		{
			Mode = BridgeExchangeMode.Exchange;
			_attempt = 0;
			_retryAt = DateTimeOffset.MinValue;
			return new BridgeAcknowledgementResult(Math.Max(localSequence, Math.Max(acknowledgement.Sequence, acknowledgement.CurrentSequence.GetValueOrDefault())), acknowledgement.Stale || !acknowledgement.Stored);
		}

		public BridgeRetryDecision ApplyFailure(Exception exception, BridgeRetryPolicy policy, DateTimeOffset now)
		{
			BridgeRetryDecision bridgeRetryDecision = policy.Decide(exception, _attempt, now);
			switch (bridgeRetryDecision.Action)
			{
			case BridgeRetryAction.StickyLegacy:
				Mode = BridgeExchangeMode.Legacy;
				_retryAt = DateTimeOffset.MaxValue;
				break;
			case BridgeRetryAction.Repair:
				Mode = BridgeExchangeMode.Repair;
				_retryAt = DateTimeOffset.MaxValue;
				break;
			case BridgeRetryAction.Retry:
			case BridgeRetryAction.Resynchronize:
				_retryAt = now + (bridgeRetryDecision.Delay ?? BridgeRetryPolicy.MinDelay);
				_attempt++;
				break;
			}
			return bridgeRetryDecision;
		}

		public void EnterRepair()
		{
			Mode = BridgeExchangeMode.Repair;
			_retryAt = DateTimeOffset.MaxValue;
		}

		public void Reset()
		{
			Mode = BridgeExchangeMode.Negotiating;
			_retryAt = DateTimeOffset.MinValue;
			_attempt = 0;
		}
	}
	public static class BridgeFeatureAdvertisement
	{
		public const string PhoneRecipeAvailability = "phoneRecipeAvailability";

		public const int PhoneRecipeProtocolVersion = 1;

		public const string BridgeExchange = "bridgeExchange";

		public const string AdvisorDispatches = "advisorDispatches";

		public const int ExchangeProtocolVersion = 1;

		public const int AdvisorDispatchProtocolVersion = 1;

		public static Dictionary<string, BridgeFeatureCapability> Current(bool exchangeOperational = false, bool advisorOperational = false)
		{
			Dictionary<string, BridgeFeatureCapability> dictionary = new Dictionary<string, BridgeFeatureCapability> { ["phoneRecipeAvailability"] = new BridgeFeatureCapability
			{
				Supported = true,
				ProtocolVersion = 1
			} };
			if (exchangeOperational)
			{
				dictionary["bridgeExchange"] = new BridgeFeatureCapability
				{
					Supported = true,
					ProtocolVersion = 1
				};
			}
			if (exchangeOperational && advisorOperational)
			{
				dictionary["advisorDispatches"] = new BridgeFeatureCapability
				{
					Supported = true,
					ProtocolVersion = 1
				};
			}
			return dictionary;
		}
	}
	internal sealed class BridgeOperationalCapabilityState
	{
		private bool _exchangeOperational;

		private bool _advisorOperational;

		public bool Update(BridgeExchangeMode mode, bool tileReady, bool advisorAvailable = true)
		{
			bool flag = mode == BridgeExchangeMode.Exchange;
			bool flag2 = flag && tileReady && advisorAvailable;
			if (flag == _exchangeOperational && flag2 == _advisorOperational)
			{
				return false;
			}
			_exchangeOperational = flag;
			_advisorOperational = flag2;
			return true;
		}

		public Dictionary<string, BridgeFeatureCapability> Current()
		{
			return BridgeFeatureAdvertisement.Current(_exchangeOperational, _advisorOperational);
		}
	}
	public sealed class HylandHelperBridgeMod : MelonMod
	{
		private readonly OverlayState _overlay = new OverlayState();

		private CancellationTokenSource? _cts;

		private BridgeRunner? _runner;

		public override void OnInitializeMelon()
		{
			try
			{
				_cts = new CancellationTokenSource();
				MelonBridgeLogger logger = new MelonBridgeLogger(((MelonBase)this).LoggerInstance);
				BridgeOperationalCapabilityState bridgeOperationalCapabilityState = new BridgeOperationalCapabilityState();
				GameAdapter gameAdapter = new GameAdapter(logger, bridgeOperationalCapabilityState.Current);
				BridgeSettingsStore settingsStore = BridgeSettingsStore.Create(logger);
				BridgeClient bridgeClient = new BridgeClient("https://hyland-helper-backend.vercel.app", logger);
				_runner = new BridgeRunner(gameAdapter, bridgeClient, settingsStore, _overlay, logger, bridgeOperationalCapabilityState);
				_runner.RunAsync(_cts.Token);
				_overlay.SetStatus("Starting bridge...");
			}
			catch (Exception ex)
			{
				((MelonBase)this).LoggerInstance.Warning("Hyland Helper Bridge disabled during init: " + ex.Message);
				_overlay.SetStatus("Bridge disabled during init.");
			}
		}

		public override void OnDeinitializeMelon()
		{
			try
			{
				_cts?.Cancel();
				_runner?.Dispose();
				_cts?.Dispose();
			}
			catch
			{
			}
		}

		public override void OnGUI()
		{
			try
			{
				OverlaySnapshot overlaySnapshot = _overlay.Snapshot();
				if (overlaySnapshot.Status == null || (!overlaySnapshot.Status.Contains("synced") && !overlaySnapshot.Status.Contains("Paired")))
				{
					OverlayRenderer.Draw(_overlay);
				}
			}
			catch
			{
			}
		}
	}
	internal sealed class BridgePreferencesStore
	{
		public const int SchemaVersionCurrent = 1;

		public const int MaxPreferencesBytes = 4096;

		private readonly string _path;

		internal static string DefaultPath => Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.UserProfile), "AppData", "LocalLow", "TVGS", "Schedule I", "HylandHelper", "bridge-preferences.json");

		internal BridgePreferencesStore(string? path = null)
		{
			_path = path ?? DefaultPath;
		}

		internal bool ReadLiveOverlayEnabled()
		{
			return ReadEnabledFlag("liveOverlayEnabled");
		}

		internal bool ReadAdaptiveCadenceEnabled()
		{
			return ReadEnabledFlag("adaptiveCadenceEnabled");
		}

		private bool ReadEnabledFlag(string property)
		{
			try
			{
				FileInfo fileInfo = new FileInfo(_path);
				if (!fileInfo.Exists || fileInfo.Length <= 0 || fileInfo.Length > 4096)
				{
					return true;
				}
				byte[] array = File.ReadAllBytes(_path);
				if (array.Length == 0 || array.Length > 4096)
				{
					return true;
				}
				using JsonDocument jsonDocument = JsonDocument.Parse(array);
				JsonElement rootElement = jsonDocument.RootElement;
				if (rootElement.ValueKind != JsonValueKind.Object)
				{
					return true;
				}
				if (!rootElement.TryGetProperty(property, out var value))
				{
					return true;
				}
				return value.ValueKind != JsonValueKind.False;
			}
			catch
			{
				return true;
			}
		}

		internal bool Save(bool liveOverlayEnabled)
		{
			try
			{
				string directoryName = Path.GetDirectoryName(_path);
				if (!string.IsNullOrEmpty(directoryName))
				{
					Directory.CreateDirectory(directoryName);
				}
				string s = "{\"schemaVersion\":" + 1 + ",\"liveOverlayEnabled\":" + (liveOverlayEnabled ? "true" : "false") + "}";
				string text = $"{_path}.{Guid.NewGuid():N}.tmp";
				try
				{
					File.WriteAllBytes(text, Encoding.UTF8.GetBytes(s));
					File.Move(text, _path, overwrite: true);
				}
				finally
				{
					try
					{
						if (File.Exists(text))
						{
							File.Delete(text);
						}
					}
					catch
					{
					}
				}
				return true;
			}
			catch
			{
				return false;
			}
		}
	}
	internal enum BridgeRetryAction
	{
		Continue,
		Retry,
		Repair,
		StickyLegacy,
		Resynchronize,
		Stop
	}
	internal sealed record BridgeRetryDecision(BridgeRetryAction Action, TimeSpan? Delay = null);
	internal sealed class BridgeRetryPolicy
	{
		public static readonly TimeSpan MinDelay = TimeSpan.FromSeconds(2.0);

		public static readonly TimeSpan MaxDelay = TimeSpan.FromMinutes(2.0);

		private readonly Func<double> _nextJitter;

		public BridgeRetryPolicy(Func<double>? nextJitter = null)
		{
			_nextJitter = nextJitter ?? new Func<double>(Random.Shared.NextDouble);
		}

		public BridgeRetryDecision Decide(Exception exception, int attempt, DateTimeOffset now)
		{
			if ((exception is HttpRequestException || exception is TaskCanceledException) ? true : false)
			{
				return Retry(attempt, null);
			}
			if (exception is BridgePayloadException)
			{
				return Retry(attempt, null);
			}
			if (!(exception is BridgeClientException { StatusCode: var statusCode } ex))
			{
				return new BridgeRetryDecision(BridgeRetryAction.Stop);
			}
			switch (statusCode)
			{
			case HttpStatusCode.Unauthorized:
				return new BridgeRetryDecision(BridgeRetryAction.Repair);
			case HttpStatusCode.Forbidden:
			{
				string code = ex.Code;
				if ((code == "SAVE_SCOPE_MISMATCH" || code == "PAIRING_SCOPE_MISMATCH") ? true : false)
				{
					return new BridgeRetryDecision(BridgeRetryAction.Repair);
				}
				return new BridgeRetryDecision(BridgeRetryAction.Continue);
			}
			case HttpStatusCode.NotFound:
				return new BridgeRetryDecision(BridgeRetryAction.StickyLegacy);
			case HttpStatusCode.Conflict:
				return Retry(attempt, ex.RetryAfter, BridgeRetryAction.Resynchronize);
			case HttpStatusCode.TooManyRequests:
				return Retry(attempt, ex.RetryAfter);
			default:
				if (ex.IsTransient)
				{
					return Retry(attempt, ex.RetryAfter);
				}
				return new BridgeRetryDecision(BridgeRetryAction.Stop);
			}
		}

		private BridgeRetryDecision Retry(int attempt, TimeSpan? serverDelay, BridgeRetryAction action = BridgeRetryAction.Retry)
		{
			if (serverDelay.HasValue)
			{
				return new BridgeRetryDecision(action, Clamp(serverDelay.Value));
			}
			int num = Math.Clamp(attempt, 0, 16);
			double num2 = MinDelay.TotalSeconds * Math.Pow(2.0, num);
			double num3 = 0.5 + Math.Clamp(_nextJitter(), 0.0, 1.0) / 2.0;
			return new BridgeRetryDecision(action, Clamp(TimeSpan.FromSeconds(num2 * num3)));
		}

		private static TimeSpan Clamp(TimeSpan delay)
		{
			if (!(delay < MinDelay))
			{
				if (!(delay > MaxDelay))
				{
					return delay;
				}
				return MaxDelay;
			}
			return MinDelay;
		}
	}
	internal sealed class BridgeRunner : IDisposable
	{
		private readonly IBridgeGameAdapter _gameAdapter;

		private readonly BridgeClient _bridgeClient;

		private readonly BridgeSettingsStore _settingsStore;

		private readonly OverlayState _overlay;

		private readonly MelonBridgeLogger _logger;

		private readonly BridgeStatusWriter _statusWriter;

		private readonly BridgeCommandReader _commandReader;

		private readonly BridgeCommandResultWriter _commandResultWriter;

		private readonly MyMixesWriter _myMixesWriter;

		private readonly PhoneRecipePoller _phoneRecipePoller;

		private readonly TileChallengeState _tileChallenge;

		private readonly BridgeOperationalCapabilityState _capabilities;

		private readonly BridgeDownlinkCoordinator _downlinks;

		private readonly LocalRefreshInvalidationHub _refreshInvalidations = new LocalRefreshInvalidationHub();

		private readonly BridgeRetryPolicy _retryPolicy = new BridgeRetryPolicy();

		private readonly Func<DateTimeOffset> _utcNow;

		private readonly TimeSpan _exchangeCadence;

		private readonly TimeSpan _safetyCadence;

		private BridgeExchangeState _exchangeState = new BridgeExchangeState();

		private BridgeSnapshotCoordinator _snapshotCoordinator;

		private DateTimeOffset _nextMyMixes = DateTimeOffset.MinValue;

		private BridgeLocalSettings _settings;

		private PairingSession? _pairing;

		private PairingScope? _tokenScope;

		private DateTimeOffset _nextPairingPoll = DateTimeOffset.MinValue;

		private DateTimeOffset _nextLatestPoll = DateTimeOffset.MinValue;

		private string? _lastImmediateRequestId;

		private bool _disposed;

		private readonly LiveOverlayReader? _liveOverlayReader;

		private readonly LiveFactBaseline? _liveFactBaseline;

		private readonly LiveFactMerger? _liveFactMerger;

		private readonly ScreenActivityReader? _screenActivityReader;

		private readonly bool _adaptiveCadenceEnabled;

		private BridgeCadenceController? _cadence;

		private string? _activeGenerationV2;

		private string? _lastLiveOverlayGeneration;

		internal ILocalRefreshInvalidationSource RefreshInvalidations => _refreshInvalidations;

		internal BridgeExchangeMode CurrentMode => _exchangeState.Mode;

		internal BridgeRunner(IBridgeGameAdapter gameAdapter, BridgeClient bridgeClient, BridgeSettingsStore settingsStore, OverlayState overlay, MelonBridgeLogger logger, BridgeOperationalCapabilityState? capabilities = null)
			: this(gameAdapter, bridgeClient, settingsStore, overlay, logger, capabilities ?? new BridgeOperationalCapabilityState(), () => DateTimeOffset.UtcNow, new PhoneRecipeCache(PhoneRecipeCache.DefaultPath), new AdvisorDispatchCache(AdvisorDispatchCache.DefaultPath), new TileChallengeState(TileChallengeState.DefaultPath), new BridgeStatusWriter(logger), null, null, null, new BridgePreferencesStore().ReadLiveOverlayEnabled(), null, new BridgePreferencesStore().ReadAdaptiveCadenceEnabled())
		{
		}

		internal BridgeRunner(IBridgeGameAdapter gameAdapter, BridgeClient bridgeClient, BridgeSettingsStore settingsStore, OverlayState overlay, MelonBridgeLogger logger, BridgeOperationalCapabilityState capabilities, Func<DateTimeOffset> utcNow, PhoneRecipeCache phoneRecipeCache, AdvisorDispatchCache advisorDispatchCache, TileChallengeState tileChallenge, BridgeStatusWriter statusWriter, TimeSpan? exchangeCadence = null, TimeSpan? safetyCadence = null, MyMixesWriter? myMixesWriter = null, bool liveOverlayEnabled = true, string? liveOverlayPath = null, bool adaptiveCadenceEnabled = false, string? screenActivityPath = null)
		{
			_gameAdapter = gameAdapter;
			_bridgeClient = bridgeClient;
			_settingsStore = settingsStore;
			_overlay = overlay;
			_logger = logger;
			_capabilities = capabilities;
			_utcNow = utcNow;
			_exchangeCadence = exchangeCadence ?? BridgeConstants.SnapshotInterval;
			_safetyCadence = safetyCadence ?? TimeSpan.FromMinutes(5.0);
			_adaptiveCadenceEnabled = adaptiveCadenceEnabled;
			_statusWriter = statusWriter;
			_commandReader = new BridgeCommandReader(logger);
			_commandResultWriter = new BridgeCommandResultWriter(logger);
			_myMixesWriter = myMixesWriter ?? new MyMixesWriter(logger);
			_tileChallenge = tileChallenge;
			_snapshotCoordinator = NewSnapshotCoordinator();
			if (adaptiveCadenceEnabled)
			{
				_screenActivityReader = new ScreenActivityReader(() => _activeGenerationV2, () => _statusWriter.RuntimeSession, screenActivityPath);
			}
			_settings = _settingsStore.Load();
			_tokenScope = _settings.Scope;
			UpdateUnlinkAvailability();
			_downlinks = new BridgeDownlinkCoordinator(phoneRecipeCache, _settingsStore, _settings, _utcNow, advisorDispatchCache);
			_phoneRecipePoller = new PhoneRecipePoller(_bridgeClient, phoneRecipeCache, _utcNow, BridgeConstants.PhoneRecipePollInterval);
			_overlay.TileChallenge = _tileChallenge.Status;
			if (!liveOverlayEnabled)
			{
				return;
			}
			_liveOverlayReader = new LiveOverlayReader(() => _activeGenerationV2, () => _statusWriter.RuntimeSession, liveOverlayPath);
			_liveFactBaseline = new LiveFactBaseline();
			_liveFactMerger = new LiveFactMerger(_liveFactBaseline);
			if (gameAdapter is GameAdapter gameAdapter2)
			{
				gameAdapter2.SnapshotPostProcessor = (ParsedSnapshot parsed) => _liveFactMerger.Merge(parsed, _liveOverlayReader.TryGetCurrent(_utcNow(), out LiveOverlayDocument document) ? document : null);
			}
		}

		public async Task RunAsync(CancellationToken cancellationToken)
		{
			_logger.Info("Hyland Helper Bridge started.");
			while (!cancellationToken.IsCancellationRequested)
			{
				try
				{
					DetectedSave detectedSave = _gameAdapter.DetectCurrentSave();
					_overlay.SaveLine = detectedSave.StatusLine;
					if (_utcNow() >= _nextMyMixes && CanWriteMyMixes(detectedSave))
					{
						_nextMyMixes = _utcNow() + TimeSpan.FromSeconds(10.0);
						_myMixesWriter.Write(detectedSave);
					}
					if (string.IsNullOrWhiteSpace(_settings.BridgeToken))
					{
						if (!_commandReader.TakeUnlink())
						{
							await EnsurePairingAsync(detectedSave, cancellationToken).ConfigureAwait(continueOnCapturedContext: false);
						}
						else
						{
							ResetStoredToken("Unlinked from in-game menu; restarting pairing.");
						}
					}
					else
					{
						BridgeCommand bridgeCommand = _commandReader.TakeCommand();
						bool unlink = bridgeCommand != null && string.Equals(bridgeCommand.Command, "unlink", StringComparison.OrdinalIgnoreCase);
						await RunCycleAsync(detectedSave, bridgeCommand, unlink, cancellationToken).ConfigureAwait(continueOnCapturedContext: false);
					}
				}
				catch (OperationCanceledException) when (cancellationToken.IsCancellationRequested)
				{
					break;
				}
				catch (BridgeClientException ex2) when (ex2.IsUnauthorized)
				{
					ResetStoredToken("Stored bridge token was rejected; restarting pairing.");
					_logger.WarningThrottled("Stored bridge token was rejected; restarting pairing.", ex2);
				}
				catch (Exception exception)
				{
					_overlay.SetStatus("Bridge quiet-failed; retrying.");
					_logger.WarningThrottled("Bridge loop quiet-failed; retrying.", exception);
					await DelayQuietlyAsync(BridgeConstants.RetryBackoff, cancellationToken).ConfigureAwait(continueOnCapturedContext: false);
				}
				_statusWriter.Write(_overlay.Snapshot());
				await DelayQuietlyAsync(TimeSpan.FromSeconds(1.0), cancellationToken).ConfigureAwait(continueOnCapturedContext: false);
			}
		}

		internal async Task RunCycleAsync(DetectedSave save, BridgeCommand? command, bool unlink, CancellationToken cancellationToken)
		{
			if (unlink)
			{
				ResetStoredToken("Unlinked from in-game menu; restarting pairing.");
				return;
			}
			ObserveLiveOverlayGeneration(save);
			if (string.IsNullOrWhiteSpace(_settings.BridgeToken))
			{
				return;
			}
			if (_settings.RequiresGenerationRePair || _tokenScope == null)
			{
				_overlay.SetStatus("This install needs one re-pair to protect replacement save slots.");
				return;
			}
			if (!save.IsPairingEligible)
			{
				_overlay.SetStatus("Save generation details are not ready. Finish loading, then retry pairing.");
				return;
			}
			if (!_tokenScope.Matches(save.Fingerprint))
			{
				_exchangeState.EnterRepair();
				_overlay.SetStatus("Linked save changed; unlink and pair this playthrough again.");
				return;
			}
			if (command != null && !string.Equals(command.Command, "unlink", StringComparison.OrdinalIgnoreCase) && (!SaveGenerationIdentity.IsV2Fingerprint(command.SaveFingerprint) || !string.Equals(command.SaveFingerprint, save.Fingerprint.Fingerprint, StringComparison.Ordinal)))
			{
				_overlay.SetStatus("Ignored a command queued for a different save playthrough.");
				return;
			}
			try
			{
				PumpScreenActivity();
				PumpLiveOverlay();
				if (command != null && string.Equals(command.Command, "syncNow", StringComparison.OrdinalIgnoreCase) && !string.IsNullOrWhiteSpace(command.RequestId) && !string.Equals(command.RequestId, _lastImmediateRequestId, StringComparison.Ordinal))
				{
					_lastImmediateRequestId = command.RequestId;
					_snapshotCoordinator.RequestFull(BridgeFullSnapshotReason.SyncNow);
					_myMixesWriter.Invalidate(save.Fingerprint.Fingerprint);
					_refreshInvalidations.Invalidate(LocalRefreshSurface.ActiveSave | LocalRefreshSurface.MyMixes);
					_overlay.SetStatus("Save-complete sync requested.");
					_logger.Info("Save-complete sync requested.");
				}
				if (command != null && string.Equals(command.Command, "saveMix", StringComparison.OrdinalIgnoreCase) && command.Mix != null && !string.IsNullOrWhiteSpace(command.RequestId))
				{
					await SendPendingMixAsync(command, cancellationToken).ConfigureAwait(continueOnCapturedContext: false);
				}
				await RunTransportCycleAsync(save, cancellationToken).ConfigureAwait(continueOnCapturedContext: false);
			}
			catch (BridgeClientException ex) when (ex.IsUnauthorized)
			{
				ResetStoredToken("Stored bridge token was rejected; restarting pairing.");
				_logger.WarningThrottled("Stored bridge token was rejected; restarting pairing.", ex);
			}
			finally
			{
				_statusWriter.Write(_overlay.Snapshot());
			}
		}

		private async Task RunTransportCycleAsync(DetectedSave save, CancellationToken cancellationToken)
		{
			_tileChallenge.Refresh();
			if (save.IsFallback)
			{
				if (UpdateOperationalCapabilities(BridgeExchangeMode.Negotiating))
				{
					_snapshotCoordinator.RequestFull(BridgeFullSnapshotReason.CapabilityChanged);
				}
				_overlay.SetStatus("Waiting for a validated save.");
				return;
			}
			_snapshotCoordinator.ObserveSave(save, _tokenScope.SaveFingerprint);
			if (UpdateOperationalCapabilities(_exchangeState.Mode))
			{
				_snapshotCoordinator.RequestFull(BridgeFullSnapshotReason.CapabilityChanged);
			}
			if (_exchangeState.Mode == BridgeExchangeMode.Repair || _snapshotCoordinator.RepairRequired)
			{
				_exchangeState.EnterRepair();
				_overlay.SetStatus("Linked save changed; unlink and pair this playthrough again.");
				return;
			}
			if (_exchangeState.Mode == BridgeExchangeMode.Legacy)
			{
				await RunLegacyCycleAsync(save, cancellationToken).ConfigureAwait(continueOnCapturedContext: false);
				return;
			}
			if (!_exchangeState.CanAttemptExchange(_utcNow()))
			{
				if (_downlinks.PhoneRecipesRequireLegacyPoll)
				{
					await _phoneRecipePoller.PollIfDueAsync(_settings.BridgeToken, save.Fingerprint.Fingerprint, cancellationToken).ConfigureAwait(continueOnCapturedContext: false);
				}
				return;
			}
			BridgeEnvelopePlan plan = _snapshotCoordinator.TakeDue(_utcNow(), _exchangeState.Mode);
			if (plan.Kind == BridgeEnvelopeKind.None)
			{
				if (_downlinks.PhoneRecipesRequireLegacyPoll)
				{
					await _phoneRecipePoller.PollIfDueAsync(_settings.BridgeToken, save.Fingerprint.Fingerprint, cancellationToken).ConfigureAwait(continueOnCapturedContext: false);
				}
				return;
			}
			long sequence = _settingsStore.NextSequence(_settings, _utcNow());
			BridgeEnvelope envelope = ((plan.Kind == BridgeEnvelopeKind.Heartbeat) ? _gameAdapter.BuildHeartbeat(save, sequence) : _gameAdapter.BuildSnapshot(save, sequence));
			BridgeExchangeRequest request = new BridgeExchangeRequest
			{
				Envelope = envelope
			};
			_downlinks.PopulateKnownRevisions(request.Downlink);
			BridgeExchangeResponse bridgeExchangeResponse;
			try
			{
				bridgeExchangeResponse = await _bridgeClient.ExchangeAsync(_settings.BridgeToken, request, cancellationToken).ConfigureAwait(continueOnCapturedContext: false);
			}
			catch (BridgeClientException ex) when (ex.IsUnauthorized)
			{
				throw;
			}
			catch (Exception ex2) when (!(ex2 is OperationCanceledException) || !cancellationToken.IsCancellationRequested)
			{
				BridgeRetryDecision bridgeRetryDecision = _exchangeState.ApplyFailure(ex2, _retryPolicy, _utcNow());
				if (bridgeRetryDecision.Action == BridgeRetryAction.Resynchronize)
				{
					_snapshotCoordinator.RequestFull(BridgeFullSnapshotReason.StaleAcknowledgement);
				}
				if (UpdateOperationalCapabilities(_exchangeState.Mode))
				{
					_snapshotCoordinator.RequestFull(BridgeFullSnapshotReason.CapabilityChanged);
				}
				OverlayState overlay = _overlay;
				overlay.SetStatus(bridgeRetryDecision.Action switch
				{
					BridgeRetryAction.StickyLegacy => "Exchange unavailable; using legacy bridge mode.", 
					BridgeRetryAction.Repair => "Linked save changed; re-pair in the phone app to continue.", 
					_ => "Exchange temporarily unavailable; retrying quietly.", 
				});
				return;
			}
			DetectedSave detectedSave = _gameAdapter.DetectCurrentSave();
			if (!detectedSave.IsPairingEligible)
			{
				_overlay.SetStatus("Save generation details are not ready. Retrying before applying synced data.");
				_snapshotCoordinator.RequestFull(BridgeFullSnapshotReason.StaleAcknowledgement);
				return;
			}
			if (_tokenScope == null || !_tokenScope.Matches(detectedSave.Fingerprint))
			{
				_exchangeState.EnterRepair();
				_overlay.SetStatus("Linked save changed; unlink and pair this playthrough again.");
				return;
			}
			BridgeAcknowledgementResult bridgeAcknowledgementResult = _exchangeState.ApplySuccess(bridgeExchangeResponse.Ingest, _settings.LastSequence, _utcNow());
			if (bridgeAcknowledgementResult.SequenceFloor > _settings.LastSequence)
			{
				long lastSequence = _settings.LastSequence;
				_settings.LastSequence = bridgeAcknowledgementResult.SequenceFloor;
				if (!_settingsStore.Save(_settings))
				{
					_settings.LastSequence = lastSequence;
					_snapshotCoordinator.RequestFull(BridgeFullSnapshotReason.StaleAcknowledgement);
					_overlay.SetStatus("Sequence floor persistence failed; retrying full sync.");
					return;
				}
			}
			BridgeDownlinkApplyOutcome outcome = _downlinks.ApplyPhoneRecipes(bridgeExchangeResponse.Downlink.PhoneRecipes, save.Fingerprint.Fingerprint);
			BridgeDownlinkApplyOutcome outcome2 = _downlinks.ApplyAdvisorDispatches(bridgeExchangeResponse.Downlink.AdvisorDispatches, save.Fingerprint.Fingerprint);
			_cadence?.NotifyHint(bridgeExchangeResponse.PollHintSeconds, AcceptedFreshDelivery.Evaluate(bridgeExchangeResponse.Ingest, outcome, request.Downlink.PhoneRecipes, bridgeExchangeResponse.Downlink.PhoneRecipes?.Revision) || AcceptedFreshDelivery.Evaluate(bridgeExchangeResponse.Ingest, outcome2, request.Downlink.AdvisorDispatches, bridgeExchangeResponse.Downlink.AdvisorDispatches?.Revision), _utcNow());
			_snapshotCoordinator.Complete(plan, _utcNow());
			if (plan.Kind == BridgeEnvelopeKind.FullSnapshot && bridgeExchangeResponse.Ingest.Stored && !bridgeExchangeResponse.Ingest.Stale)
			{
				CommitDeliveredRuntimeBaselines();
			}
			if (plan.EnterRepairAfterSend || _snapshotCoordinator.RepairRequired)
			{
				_exchangeState.EnterRepair();
			}
			if (bridgeAcknowledgementResult.RequiresFullResynchronization)
			{
				_snapshotCoordinator.RequestFull(BridgeFullSnapshotReason.StaleAcknowledgement);
			}
			if (UpdateOperationalCapabilities(_exchangeState.Mode))
			{
				_snapshotCoordinator.RequestFull(BridgeFullSnapshotReason.CapabilityChanged);
			}
			if (_downlinks.PhoneRecipesRequireLegacyPoll)
			{
				await _phoneRecipePoller.PollIfDueAsync(_settings.BridgeToken, save.Fingerprint.Fingerprint, cancellationToken).ConfigureAwait(continueOnCapturedContext: false);
			}
			_overlay.Sequence = s

HylandHelper.PhoneTile.dll

Decompiled 5 hours ago
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Net.Http;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.Versioning;
using System.Security.Cryptography;
using System.Text;
using System.Text.Json;
using System.Text.Json.Serialization;
using System.Text.RegularExpressions;
using System.Threading;
using System.Threading.Tasks;
using HylandHelper.BridgeContracts;
using HylandHelper.BridgeMod;
using HylandHelper.PhoneTile;
using Il2CppScheduleOne.DevUtilities;
using Il2CppScheduleOne.UI;
using MelonLoader;
using Microsoft.CodeAnalysis;
using S1API.GameTime;
using S1API.Leveling;
using S1API.Lifecycle;
using S1API.Money;
using S1API.PhoneApp;
using S1API.Property;
using S1API.UI;
using S1API.Utils;
using UnityEngine;
using UnityEngine.UI;

[assembly: CompilationRelaxations(8)]
[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
[assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)]
[assembly: MelonInfo(typeof(PhoneTileMod), "Hyland Helper Phone Tile", "0.1.3", "MadJag Studios", null)]
[assembly: TargetFramework(".NETCoreApp,Version=v6.0", FrameworkDisplayName = ".NET 6.0")]
[assembly: AssemblyCompany("MadJag Studios")]
[assembly: AssemblyConfiguration("Release")]
[assembly: AssemblyCopyright("Copyright © 2026 MadJag Studios")]
[assembly: AssemblyDescription("Hyland Helper in-game phone tile for Schedule I")]
[assembly: AssemblyFileVersion("0.1.3.0")]
[assembly: AssemblyInformationalVersion("0.1.3")]
[assembly: AssemblyProduct("Hyland Helper")]
[assembly: AssemblyTitle("HylandHelper.PhoneTile")]
[assembly: AssemblyVersion("0.1.3.0")]
[module: RefSafetyRules(11)]
namespace Microsoft.CodeAnalysis
{
	[CompilerGenerated]
	[Embedded]
	internal sealed class EmbeddedAttribute : Attribute
	{
	}
}
namespace System.Runtime.CompilerServices
{
	[CompilerGenerated]
	[Embedded]
	[AttributeUsage(AttributeTargets.Class | AttributeTargets.Property | AttributeTargets.Field | AttributeTargets.Event | AttributeTargets.Parameter | AttributeTargets.ReturnValue | AttributeTargets.GenericParameter, AllowMultiple = false, Inherited = false)]
	internal sealed class NullableAttribute : Attribute
	{
		public readonly byte[] NullableFlags;

		public NullableAttribute(byte P_0)
		{
			NullableFlags = new byte[1] { P_0 };
		}

		public NullableAttribute(byte[] P_0)
		{
			NullableFlags = P_0;
		}
	}
	[CompilerGenerated]
	[Embedded]
	[AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct | AttributeTargets.Method | AttributeTargets.Interface | AttributeTargets.Delegate, AllowMultiple = false, Inherited = false)]
	internal sealed class NullableContextAttribute : Attribute
	{
		public readonly byte Flag;

		public NullableContextAttribute(byte P_0)
		{
			Flag = P_0;
		}
	}
	[CompilerGenerated]
	[Embedded]
	[AttributeUsage(AttributeTargets.Module, AllowMultiple = false, Inherited = false)]
	internal sealed class RefSafetyRulesAttribute : Attribute
	{
		public readonly int Version;

		public RefSafetyRulesAttribute(int P_0)
		{
			Version = P_0;
		}
	}
}
namespace HylandHelper.BridgeMod
{
	[Flags]
	internal enum LocalRefreshSurface
	{
		None = 0,
		ActiveSave = 1,
		MyMixes = 2,
		Status = 4,
		AdvisorDispatches = 8,
		PendingCommandResult = 0x10,
		LiveContext = 0x20
	}
	internal readonly record struct LocalFileSignature(bool Exists, long Length, long LastWriteUtcTicks)
	{
		internal static LocalFileSignature Missing => new LocalFileSignature(Exists: false, 0L, 0L);
	}
	internal enum LocalRefreshReason
	{
		Initial,
		Dirty,
		SignatureChanged,
		ForcedRecovery,
		Unchanged
	}
	internal interface ILocalRefreshInvalidationSource
	{
		LocalRefreshSurface Pending { get; }

		event Action<LocalRefreshSurface>? Invalidated;
	}
	internal sealed class LocalRefreshInvalidationHub : ILocalRefreshInvalidationSource
	{
		public LocalRefreshSurface Pending { get; private set; }

		public event Action<LocalRefreshSurface>? Invalidated;

		public void Invalidate(LocalRefreshSurface surfaces)
		{
			if (surfaces != LocalRefreshSurface.None)
			{
				LocalRefreshSurface localRefreshSurface = surfaces & ~Pending;
				Pending |= surfaces;
				if (localRefreshSurface != LocalRefreshSurface.None)
				{
					this.Invalidated?.Invoke(localRefreshSurface);
				}
			}
		}

		public bool Take(LocalRefreshSurface surface)
		{
			if ((Pending & surface) == 0)
			{
				return false;
			}
			Pending &= ~surface;
			return true;
		}

		public void Reset(LocalRefreshSurface surfaces)
		{
			Pending &= ~surfaces;
		}
	}
	internal sealed class LocalFileRefreshGate
	{
		private readonly TimeSpan _forcedRecoveryInterval;

		private LocalFileSignature _lastAccepted;

		private DateTimeOffset _lastAcceptedAt;

		private bool _hasAccepted;

		private bool _retryTransient;

		public LocalFileRefreshGate(TimeSpan forcedRecoveryInterval)
		{
			if (forcedRecoveryInterval <= TimeSpan.Zero)
			{
				throw new ArgumentOutOfRangeException("forcedRecoveryInterval");
			}
			_forcedRecoveryInterval = forcedRecoveryInterval;
		}

		public bool ShouldRefresh(LocalFileSignature signature, DateTimeOffset now, bool dirty, out LocalRefreshReason reason)
		{
			if (!_hasAccepted)
			{
				reason = LocalRefreshReason.Initial;
				return true;
			}
			if (dirty || _retryTransient)
			{
				reason = LocalRefreshReason.Dirty;
				return true;
			}
			if (signature != _lastAccepted)
			{
				reason = LocalRefreshReason.SignatureChanged;
				return true;
			}
			if (now - _lastAcceptedAt >= _forcedRecoveryInterval)
			{
				reason = LocalRefreshReason.ForcedRecovery;
				return true;
			}
			reason = LocalRefreshReason.Unchanged;
			return false;
		}

		public void Accept(LocalFileSignature signature, DateTimeOffset now)
		{
			_lastAccepted = signature;
			_lastAcceptedAt = now;
			_hasAccepted = true;
			_retryTransient = false;
		}

		public void RejectTransient()
		{
			_retryTransient = true;
		}

		public void Reset()
		{
			_lastAccepted = default(LocalFileSignature);
			_lastAcceptedAt = default(DateTimeOffset);
			_hasAccepted = false;
			_retryTransient = false;
		}
	}
	internal sealed record ProvenSaveIdentity(string SteamIdHash, string SlotId, string Fingerprint);
	internal static class SaveGenerationIdentity
	{
		internal const string FingerprintV2Prefix = "v2-";

		private const long MaxMetadataBytes = 1048576L;

		internal static bool TryCreate(string activeSaveDirectory, out ProvenSaveIdentity? identity, out string reason)
		{
			identity = null;
			reason = "generation-metadata-unavailable";
			try
			{
				if (!TryGetDirectoryIdentity(activeSaveDirectory, out string steamDirectory, out string slotId))
				{
					reason = "generation-save-path-invalid";
					return false;
				}
				if (!TryReadMetadata(activeSaveDirectory, out var creation) || !TryReadSeed(activeSaveDirectory, out var seed))
				{
					return false;
				}
				string text = Sha256(Path.GetFileName(steamDirectory));
				string value = string.Join("\n", "hyland-helper/save-fingerprint/v2", "steam=" + text, "slot=" + slotId.ToLowerInvariant(), "created=" + creation.ToString("yyyy-MM-dd'T'HH:mm:ss", CultureInfo.InvariantCulture), "seed=" + seed.ToString(CultureInfo.InvariantCulture));
				identity = new ProvenSaveIdentity(text, slotId, "v2-" + Sha256(value));
				reason = string.Empty;
				return true;
			}
			catch (IOException)
			{
				return false;
			}
			catch (JsonException)
			{
				return false;
			}
			catch (UnauthorizedAccessException)
			{
				return false;
			}
		}

		internal static bool IsV2Fingerprint(string? value)
		{
			if (value == null || value.Length != "v2-".Length + 64 || !value.StartsWith("v2-", StringComparison.Ordinal))
			{
				return false;
			}
			for (int i = "v2-".Length; i < value.Length; i++)
			{
				char c = value[i];
				if ((c < '0' || c > '9') && (c < 'a' || c > 'f'))
				{
					return false;
				}
			}
			return true;
		}

		private static bool TryGetDirectoryIdentity(string activeSaveDirectory, out string steamDirectory, out string slotId)
		{
			steamDirectory = string.Empty;
			slotId = string.Empty;
			if (string.IsNullOrWhiteSpace(activeSaveDirectory))
			{
				return false;
			}
			string path = Path.GetFullPath(activeSaveDirectory).TrimEnd(Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar);
			if (!Directory.Exists(path))
			{
				return false;
			}
			slotId = Path.GetFileName(path);
			if (!IsSaveSlot(slotId))
			{
				return false;
			}
			steamDirectory = Path.GetDirectoryName(path) ?? string.Empty;
			return !string.IsNullOrWhiteSpace(Path.GetFileName(steamDirectory));
		}

		private static bool TryReadMetadata(string saveDirectory, out DateTime creation)
		{
			creation = default(DateTime);
			if (!TryReadJson(Path.Combine(saveDirectory, "Metadata.json"), out JsonDocument document))
			{
				return false;
			}
			using (document)
			{
				JsonElement rootElement = document.RootElement;
				if (!HasString(rootElement, "DataType", "MetaData") || !HasInt32(rootElement, "DataVersion") || !HasNonEmptyString(rootElement, "GameVersion") || !HasNonEmptyString(rootElement, "CreationVersion") || !TryGetUniqueProperty(rootElement, "CreationDate", out var value) || value.ValueKind != JsonValueKind.Object || !HasNonEmptyString(value, "DataType") || !HasInt32(value, "DataVersion") || !HasNonEmptyString(value, "GameVersion") || !TryGetInt32(value, "Year", out var value2) || !TryGetInt32(value, "Month", out var value3) || !TryGetInt32(value, "Day", out var value4) || !TryGetInt32(value, "Hour", out var value5) || !TryGetInt32(value, "Minute", out var value6) || !TryGetInt32(value, "Second", out var value7))
				{
					return false;
				}
				try
				{
					creation = new DateTime(value2, value3, value4, value5, value6, value7, DateTimeKind.Unspecified);
					return true;
				}
				catch (ArgumentOutOfRangeException)
				{
					return false;
				}
			}
		}

		private static bool TryReadSeed(string saveDirectory, out long seed)
		{
			seed = 0L;
			if (!TryReadJson(Path.Combine(saveDirectory, "Game.json"), out JsonDocument document))
			{
				return false;
			}
			using (document)
			{
				JsonElement rootElement = document.RootElement;
				return HasString(rootElement, "DataType", "GameData") && HasInt32(rootElement, "DataVersion") && HasNonEmptyString(rootElement, "GameVersion") && TryGetInt64(rootElement, "Seed", out seed);
			}
		}

		private static bool TryReadJson(string path, out JsonDocument document)
		{
			document = null;
			FileInfo fileInfo = new FileInfo(path);
			if (!fileInfo.Exists || fileInfo.Length > 1048576)
			{
				return false;
			}
			using FileStream utf8Json = new FileStream(path, FileMode.Open, FileAccess.Read, FileShare.ReadWrite | FileShare.Delete);
			document = JsonDocument.Parse(utf8Json);
			return true;
		}

		private static bool HasString(JsonElement element, string propertyName, string expected)
		{
			if (TryGetUniqueProperty(element, propertyName, out var value) && value.ValueKind == JsonValueKind.String)
			{
				return string.Equals(value.GetString(), expected, StringComparison.Ordinal);
			}
			return false;
		}

		private static bool HasNonEmptyString(JsonElement element, string propertyName)
		{
			if (TryGetUniqueProperty(element, propertyName, out var value) && value.ValueKind == JsonValueKind.String)
			{
				return !string.IsNullOrWhiteSpace(value.GetString());
			}
			return false;
		}

		private static bool HasInt32(JsonElement element, string propertyName)
		{
			int value;
			return TryGetInt32(element, propertyName, out value);
		}

		private static bool TryGetInt32(JsonElement element, string propertyName, out int value)
		{
			value = 0;
			if (TryGetUniqueProperty(element, propertyName, out var value2) && value2.ValueKind == JsonValueKind.Number)
			{
				return value2.TryGetInt32(out value);
			}
			return false;
		}

		private static bool TryGetInt64(JsonElement element, string propertyName, out long value)
		{
			value = 0L;
			if (TryGetUniqueProperty(element, propertyName, out var value2) && value2.ValueKind == JsonValueKind.Number)
			{
				return value2.TryGetInt64(out value);
			}
			return false;
		}

		private static bool TryGetUniqueProperty(JsonElement element, string propertyName, out JsonElement value)
		{
			value = default(JsonElement);
			if (element.ValueKind != JsonValueKind.Object)
			{
				return false;
			}
			bool flag = false;
			foreach (JsonProperty item in element.EnumerateObject())
			{
				if (string.Equals(item.Name, propertyName, StringComparison.Ordinal))
				{
					if (flag)
					{
						value = default(JsonElement);
						return false;
					}
					flag = true;
					value = item.Value;
				}
			}
			return flag;
		}

		private static bool IsSaveSlot(string value)
		{
			if (!value.StartsWith("SaveGame_", StringComparison.OrdinalIgnoreCase) || value.Length == "SaveGame_".Length)
			{
				return false;
			}
			for (int i = "SaveGame_".Length; i < value.Length; i++)
			{
				if (value[i] < '0' || value[i] > '9')
				{
					return false;
				}
			}
			return true;
		}

		private static string Sha256(string value)
		{
			using SHA256 sHA = SHA256.Create();
			return Convert.ToHexString(sHA.ComputeHash(Encoding.UTF8.GetBytes(value))).ToLowerInvariant();
		}
	}
	internal sealed class LiveOverlayDocument
	{
		public const int SchemaVersionCurrent = 1;

		public const int MaxSerializedBytes = 8192;

		[JsonPropertyName("schemaVersion")]
		public int SchemaVersion { get; set; } = 1;

		[JsonPropertyName("playthroughGeneration")]
		public string PlaythroughGeneration { get; set; } = "";

		[JsonPropertyName("runtimeSession")]
		public string RuntimeSession { get; set; } = "";

		[JsonPropertyName("tileInstanceId")]
		public string TileInstanceId { get; set; } = "";

		[JsonPropertyName("loadEpoch")]
		public int LoadEpoch { get; set; }

		[JsonPropertyName("sequence")]
		public long Sequence { get; set; }

		[JsonPropertyName("observedAt")]
		public string ObservedAt { get; set; } = "";

		[JsonPropertyName("tileVersion")]
		public string TileVersion { get; set; } = "";

		[JsonPropertyName("s1ApiVersion")]
		public string S1ApiVersion { get; set; } = "";

		[JsonPropertyName("facts")]
		public LiveOverlayFacts Facts { get; set; } = new LiveOverlayFacts();
	}
	internal sealed class LiveOverlayFacts
	{
		[JsonPropertyName("rank")]
		public LiveRankFact? Rank { get; set; }

		[JsonPropertyName("elapsedDays")]
		public LiveElapsedDaysFact? ElapsedDays { get; set; }

		[JsonPropertyName("properties")]
		public LivePropertiesFact? Properties { get; set; }

		[JsonPropertyName("money")]
		public LiveMoneyFact? Money { get; set; }
	}
	internal sealed class LiveRankFact
	{
		[JsonPropertyName("scope")]
		public string Scope { get; set; } = "shared";

		[JsonPropertyName("rankName")]
		public string RankName { get; set; } = "";

		[JsonPropertyName("tier")]
		public int Tier { get; set; }

		[JsonPropertyName("xp")]
		public int Xp { get; set; }

		[JsonPropertyName("totalXp")]
		public int TotalXp { get; set; }
	}
	internal sealed class LiveElapsedDaysFact
	{
		[JsonPropertyName("scope")]
		public string Scope { get; set; } = "shared";

		[JsonPropertyName("value")]
		public int Value { get; set; }
	}
	internal sealed class LivePropertiesFact
	{
		[JsonPropertyName("scope")]
		public string Scope { get; set; } = "shared";

		[JsonPropertyName("codes")]
		public List<string> Codes { get; set; } = new List<string>();

		[JsonPropertyName("businessCodes")]
		public List<string> BusinessCodes { get; set; } = new List<string>();
	}
	internal sealed class LiveMoneyFact
	{
		[JsonPropertyName("cashOnHand")]
		public LiveMoneyValue CashOnHand { get; set; } = new LiveMoneyValue
		{
			Scope = "player"
		};

		[JsonPropertyName("onlineBalance")]
		public LiveMoneyValue OnlineBalance { get; set; } = new LiveMoneyValue();

		[JsonPropertyName("netWorth")]
		public LiveMoneyValue NetWorth { get; set; } = new LiveMoneyValue();
	}
	internal sealed class LiveMoneyValue
	{
		[JsonPropertyName("scope")]
		public string Scope { get; set; } = "shared";

		[JsonPropertyName("value")]
		public double Value { get; set; }
	}
	internal static class LiveOverlaySerializer
	{
		private static readonly JsonSerializerOptions Options = new JsonSerializerOptions
		{
			DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull
		};

		private static readonly string[] TopLevelKeys = new string[10] { "schemaVersion", "playthroughGeneration", "runtimeSession", "tileInstanceId", "loadEpoch", "sequence", "observedAt", "tileVersion", "s1ApiVersion", "facts" };

		private static readonly string[] FactKeys = new string[4] { "rank", "elapsedDays", "properties", "money" };

		private static readonly string[] RankKeys = new string[5] { "scope", "rankName", "tier", "xp", "totalXp" };

		private static readonly string[] ValueFactKeys = new string[2] { "scope", "value" };

		private static readonly string[] PropertiesKeys = new string[3] { "scope", "codes", "businessCodes" };

		private static readonly string[] MoneyKeys = new string[3] { "cashOnHand", "onlineBalance", "netWorth" };

		internal static bool TrySerialize(LiveOverlayDocument document, out byte[] utf8, out string? reason)
		{
			utf8 = JsonSerializer.SerializeToUtf8Bytes(document, Options);
			if (utf8.Length > 8192)
			{
				reason = "overBudget";
				utf8 = Array.Empty<byte>();
				return false;
			}
			reason = null;
			return true;
		}

		internal static bool TryParse(byte[] utf8, out LiveOverlayDocument? document, out string reason)
		{
			document = null;
			if (utf8 == null || utf8.Length == 0)
			{
				reason = "empty";
				return false;
			}
			if (utf8.Length > 8192)
			{
				reason = "overBudget";
				return false;
			}
			try
			{
				using JsonDocument jsonDocument = JsonDocument.Parse(utf8);
				JsonElement rootElement = jsonDocument.RootElement;
				if (rootElement.ValueKind != JsonValueKind.Object)
				{
					reason = "rootNotObject";
					return false;
				}
				if (!CheckExactKeys(rootElement, TopLevelKeys, requireAll: true, out reason))
				{
					return false;
				}
				LiveOverlayDocument liveOverlayDocument = new LiveOverlayDocument();
				if (!TryInt(rootElement, "schemaVersion", 1, 1, out var value, out reason))
				{
					return false;
				}
				liveOverlayDocument.SchemaVersion = value;
				JsonElement property = rootElement.GetProperty("playthroughGeneration");
				if (property.ValueKind != JsonValueKind.String || !SaveGenerationIdentity.IsV2Fingerprint(property.GetString()))
				{
					reason = "playthroughGeneration";
					return false;
				}
				liveOverlayDocument.PlaythroughGeneration = property.GetString();
				if (!TryBoundedString(rootElement, "runtimeSession", 1, 64, out string value2, out reason))
				{
					return false;
				}
				liveOverlayDocument.RuntimeSession = value2;
				if (!TryBoundedString(rootElement, "tileInstanceId", 32, 32, out string value3, out reason))
				{
					return false;
				}
				if (!IsLowerHex(value3))
				{
					reason = "tileInstanceId";
					return false;
				}
				liveOverlayDocument.TileInstanceId = value3;
				if (!TryInt(rootElement, "loadEpoch", 1, 1000000, out var value4, out reason))
				{
					return false;
				}
				liveOverlayDocument.LoadEpoch = value4;
				if (!TryLong(rootElement, "sequence", 1L, 999999999999L, out var value5, out reason))
				{
					return false;
				}
				liveOverlayDocument.Sequence = value5;
				if (!TryBoundedString(rootElement, "observedAt", 1, 40, out string value6, out reason))
				{
					return false;
				}
				if (!DateTimeOffset.TryParse(value6, CultureInfo.InvariantCulture, DateTimeStyles.RoundtripKind, out var _))
				{
					reason = "observedAt";
					return false;
				}
				liveOverlayDocument.ObservedAt = value6;
				if (!TryBoundedString(rootElement, "tileVersion", 1, 64, out string value7, out reason))
				{
					return false;
				}
				liveOverlayDocument.TileVersion = value7;
				if (!TryBoundedString(rootElement, "s1ApiVersion", 1, 64, out string value8, out reason))
				{
					return false;
				}
				liveOverlayDocument.S1ApiVersion = value8;
				JsonElement property2 = rootElement.GetProperty("facts");
				if (property2.ValueKind != JsonValueKind.Object)
				{
					reason = "facts";
					return false;
				}
				if (!CheckExactKeys(property2, FactKeys, requireAll: false, out reason))
				{
					return false;
				}
				if (property2.TryGetProperty("rank", out var value9))
				{
					if (!TryParseRank(value9, out LiveRankFact fact, out reason))
					{
						return false;
					}
					liveOverlayDocument.Facts.Rank = fact;
				}
				if (property2.TryGetProperty("elapsedDays", out var value10))
				{
					if (!TryParseElapsedDays(value10, out LiveElapsedDaysFact fact2, out reason))
					{
						return false;
					}
					liveOverlayDocument.Facts.ElapsedDays = fact2;
				}
				if (property2.TryGetProperty("properties", out var value11))
				{
					if (!TryParseProperties(value11, out LivePropertiesFact fact3, out reason))
					{
						return false;
					}
					liveOverlayDocument.Facts.Properties = fact3;
				}
				if (property2.TryGetProperty("money", out var value12))
				{
					if (!TryParseMoney(value12, out LiveMoneyFact fact4, out reason))
					{
						return false;
					}
					liveOverlayDocument.Facts.Money = fact4;
				}
				document = liveOverlayDocument;
				reason = "ok";
				return true;
			}
			catch (JsonException)
			{
				reason = "malformedJson";
				return false;
			}
		}

		private static bool TryParseRank(JsonElement element, out LiveRankFact? fact, out string reason)
		{
			fact = null;
			if (element.ValueKind != JsonValueKind.Object || !CheckExactKeys(element, RankKeys, requireAll: true, out reason))
			{
				reason = "rank";
				return false;
			}
			if (!IsScope(element, "shared"))
			{
				reason = "rank.scope";
				return false;
			}
			if (!TryBoundedString(element, "rankName", 1, 32, out string value, out string reason2))
			{
				reason = "rank.rankName";
				return false;
			}
			if (!TryInt(element, "tier", 1, 99999, out int value2, out reason2))
			{
				reason = "rank.tier";
				return false;
			}
			if (!TryInt(element, "xp", 0, 999999999, out int value3, out reason2))
			{
				reason = "rank.xp";
				return false;
			}
			if (!TryInt(element, "totalXp", 0, 999999999, out int value4, out reason2))
			{
				reason = "rank.totalXp";
				return false;
			}
			fact = new LiveRankFact
			{
				RankName = value,
				Tier = value2,
				Xp = value3,
				TotalXp = value4
			};
			reason = "ok";
			return true;
		}

		private static bool TryParseElapsedDays(JsonElement element, out LiveElapsedDaysFact? fact, out string reason)
		{
			fact = null;
			if (element.ValueKind != JsonValueKind.Object || !CheckExactKeys(element, ValueFactKeys, requireAll: true, out reason))
			{
				reason = "elapsedDays";
				return false;
			}
			if (!IsScope(element, "shared"))
			{
				reason = "elapsedDays.scope";
				return false;
			}
			if (!TryInt(element, "value", 0, 99999, out int value, out string _))
			{
				reason = "elapsedDays.value";
				return false;
			}
			fact = new LiveElapsedDaysFact
			{
				Value = value
			};
			reason = "ok";
			return true;
		}

		private static bool TryParseProperties(JsonElement element, out LivePropertiesFact? fact, out string reason)
		{
			fact = null;
			if (element.ValueKind != JsonValueKind.Object || !CheckExactKeys(element, PropertiesKeys, requireAll: true, out reason))
			{
				reason = "properties";
				return false;
			}
			if (!IsScope(element, "shared"))
			{
				reason = "properties.scope";
				return false;
			}
			if (!TryCodes(element, "codes", 64, out HashSet<string> codes))
			{
				reason = "properties.codes";
				return false;
			}
			if (!TryCodes(element, "businessCodes", 32, out HashSet<string> codes2))
			{
				reason = "properties.businessCodes";
				return false;
			}
			foreach (string item in codes2)
			{
				if (!codes.Contains(item))
				{
					reason = "properties.businessSubset";
					return false;
				}
			}
			fact = new LivePropertiesFact
			{
				Codes = new List<string>(codes),
				BusinessCodes = new List<string>(codes2)
			};
			reason = "ok";
			return true;
		}

		private static bool TryParseMoney(JsonElement element, out LiveMoneyFact? fact, out string reason)
		{
			fact = null;
			if (element.ValueKind != JsonValueKind.Object || !CheckExactKeys(element, MoneyKeys, requireAll: true, out reason))
			{
				reason = "money";
				return false;
			}
			if (!TryMoneyValue(element, "cashOnHand", "player", 0.0, out LiveMoneyValue value))
			{
				reason = "money.cashOnHand";
				return false;
			}
			if (!TryMoneyValue(element, "onlineBalance", "shared", 0.0, out LiveMoneyValue value2))
			{
				reason = "money.onlineBalance";
				return false;
			}
			if (!TryMoneyValue(element, "netWorth", "shared", -1000000000.0, out LiveMoneyValue value3))
			{
				reason = "money.netWorth";
				return false;
			}
			fact = new LiveMoneyFact
			{
				CashOnHand = value,
				OnlineBalance = value2,
				NetWorth = value3
			};
			reason = "ok";
			return true;
		}

		private static bool TryMoneyValue(JsonElement parent, string name, string scope, double minimum, out LiveMoneyValue? value)
		{
			value = null;
			if (!parent.TryGetProperty(name, out var value2) || value2.ValueKind != JsonValueKind.Object)
			{
				return false;
			}
			if (!CheckExactKeys(value2, ValueFactKeys, requireAll: true, out string _))
			{
				return false;
			}
			if (!IsScope(value2, scope))
			{
				return false;
			}
			if (!value2.TryGetProperty("value", out var value3) || value3.ValueKind != JsonValueKind.Number)
			{
				return false;
			}
			if (!decimal.TryParse(value3.GetRawText(), NumberStyles.Float, CultureInfo.InvariantCulture, out var result))
			{
				return false;
			}
			if (result != Math.Round(result, 2))
			{
				return false;
			}
			if (result < (decimal)minimum || result >= 1000000000m)
			{
				return false;
			}
			value = new LiveMoneyValue
			{
				Scope = scope,
				Value = (double)result
			};
			return true;
		}

		private static bool TryCodes(JsonElement parent, string name, int max, out HashSet<string> codes)
		{
			codes = new HashSet<string>(StringComparer.Ordinal);
			if (!parent.TryGetProperty(name, out var value) || value.ValueKind != JsonValueKind.Array)
			{
				return false;
			}
			int num = 0;
			foreach (JsonElement item in value.EnumerateArray())
			{
				if (++num > max)
				{
					return false;
				}
				if (item.ValueKind != JsonValueKind.String)
				{
					return false;
				}
				string text = item.GetString();
				if (string.IsNullOrEmpty(text) || text.Length > 48 || !IsCanonicalCode(text))
				{
					return false;
				}
				if (!codes.Add(text))
				{
					return false;
				}
			}
			return true;
		}

		internal static bool IsCanonicalCode(string code)
		{
			for (int i = 0; i < code.Length; i++)
			{
				bool flag;
				switch (code[i])
				{
				case '-':
				case '0':
				case '1':
				case '2':
				case '3':
				case '4':
				case '5':
				case '6':
				case '7':
				case '8':
				case '9':
				case '_':
				case 'a':
				case 'b':
				case 'c':
				case 'd':
				case 'e':
				case 'f':
				case 'g':
				case 'h':
				case 'i':
				case 'j':
				case 'k':
				case 'l':
				case 'm':
				case 'n':
				case 'o':
				case 'p':
				case 'q':
				case 'r':
				case 's':
				case 't':
				case 'u':
				case 'v':
				case 'w':
				case 'x':
				case 'y':
				case 'z':
					flag = true;
					break;
				default:
					flag = false;
					break;
				}
				if (!flag)
				{
					return false;
				}
			}
			return true;
		}

		private static bool IsLowerHex(string value)
		{
			for (int i = 0; i < value.Length; i++)
			{
				bool flag;
				switch (value[i])
				{
				case '0':
				case '1':
				case '2':
				case '3':
				case '4':
				case '5':
				case '6':
				case '7':
				case '8':
				case '9':
				case 'a':
				case 'b':
				case 'c':
				case 'd':
				case 'e':
				case 'f':
					flag = true;
					break;
				default:
					flag = false;
					break;
				}
				if (!flag)
				{
					return false;
				}
			}
			return true;
		}

		private static bool IsScope(JsonElement element, string expected)
		{
			if (element.TryGetProperty("scope", out var value) && value.ValueKind == JsonValueKind.String)
			{
				return value.GetString() == expected;
			}
			return false;
		}

		private static bool CheckExactKeys(JsonElement element, string[] allowed, bool requireAll, out string reason)
		{
			HashSet<string> hashSet = new HashSet<string>(StringComparer.Ordinal);
			foreach (JsonProperty item in element.EnumerateObject())
			{
				if (Array.IndexOf(allowed, item.Name) < 0)
				{
					reason = "unknownField:" + Bound(item.Name);
					return false;
				}
				if (!hashSet.Add(item.Name))
				{
					reason = "duplicateField:" + Bound(item.Name);
					return false;
				}
			}
			if (requireAll)
			{
				foreach (string text in allowed)
				{
					if (!hashSet.Contains(text))
					{
						reason = "missingField:" + text;
						return false;
					}
				}
			}
			reason = "ok";
			return true;
		}

		private static string Bound(string value)
		{
			if (value.Length > 32)
			{
				return value.Substring(0, 32);
			}
			return value;
		}

		private static bool TryBoundedString(JsonElement parent, string name, int minLength, int maxLength, out string value, out string reason)
		{
			value = "";
			if (!parent.TryGetProperty(name, out var value2) || value2.ValueKind != JsonValueKind.String)
			{
				reason = name;
				return false;
			}
			string text = value2.GetString() ?? "";
			if (text.Trim().Length == 0 || text.Length < minLength || text.Length > maxLength)
			{
				reason = name;
				return false;
			}
			value = text;
			reason = "ok";
			return true;
		}

		private static bool TryInt(JsonElement parent, string name, int minimum, int maximum, out int value, out string reason)
		{
			value = 0;
			if (!parent.TryGetProperty(name, out var value2) || value2.ValueKind != JsonValueKind.Number || !value2.TryGetInt32(out var value3) || value3 < minimum || value3 > maximum)
			{
				reason = name;
				return false;
			}
			value = value3;
			reason = "ok";
			return true;
		}

		private static bool TryLong(JsonElement parent, string name, long minimum, long maximum, out long value, out string reason)
		{
			value = 0L;
			if (!parent.TryGetProperty(name, out var value2) || value2.ValueKind != JsonValueKind.Number || !value2.TryGetInt64(out var value3) || value3 < minimum || value3 > maximum)
			{
				reason = name;
				return false;
			}
			value = value3;
			reason = "ok";
			return true;
		}
	}
	internal enum ScreenActivityScreen
	{
		PhoneRecipes,
		AdvisorDispatches
	}
	internal sealed class ScreenActivityDocument
	{
		public const int SchemaVersionCurrent = 1;

		public const int MaxSerializedBytes = 2048;

		[JsonPropertyName("schemaVersion")]
		public int SchemaVersion { get; set; } = 1;

		[JsonPropertyName("playthroughGeneration")]
		public string PlaythroughGeneration { get; set; } = "";

		[JsonPropertyName("runtimeSession")]
		public string RuntimeSession { get; set; } = "";

		[JsonPropertyName("tileInstanceId")]
		public string TileInstanceId { get; set; } = "";

		[JsonPropertyName("sequence")]
		public long Sequence { get; set; }

		[JsonPropertyName("updatedAt")]
		public string UpdatedAt { get; set; } = "";

		[JsonPropertyName("visibleScreen")]
		public string? VisibleScreen { get; set; }
	}
	internal static class ScreenActivitySerializer
	{
		private static readonly JsonSerializerOptions Options = new JsonSerializerOptions();

		private static readonly string[] Required = new string[6] { "schemaVersion", "playthroughGeneration", "runtimeSession", "tileInstanceId", "sequence", "updatedAt" };

		private static readonly string[] Allowed = new string[7] { "schemaVersion", "playthroughGeneration", "runtimeSession", "tileInstanceId", "sequence", "updatedAt", "visibleScreen" };

		internal static bool TrySerialize(ScreenActivityDocument document, out byte[] utf8, out string? reason)
		{
			utf8 = JsonSerializer.SerializeToUtf8Bytes(document, Options);
			if (utf8.Length > 2048)
			{
				utf8 = Array.Empty<byte>();
				reason = "overBudget";
				return false;
			}
			reason = null;
			return true;
		}

		internal static bool TryParse(byte[] utf8, out ScreenActivityDocument? document, out string? reason)
		{
			document = null;
			if (utf8 == null || utf8.Length == 0)
			{
				reason = "empty";
				return false;
			}
			if (utf8.Length > 2048)
			{
				reason = "overBudget";
				return false;
			}
			try
			{
				using JsonDocument jsonDocument = JsonDocument.Parse(utf8);
				JsonElement rootElement = jsonDocument.RootElement;
				if (rootElement.ValueKind != JsonValueKind.Object)
				{
					reason = "rootNotObject";
					return false;
				}
				if (!ExactKeys(rootElement, out reason))
				{
					return false;
				}
				if (!Int(rootElement, "schemaVersion", 1, 1, out var value) || !Text(rootElement, "playthroughGeneration", 1, 80, out string value2) || !IsGeneration(value2) || !Text(rootElement, "runtimeSession", 1, 64, out string value3) || !Text(rootElement, "tileInstanceId", 32, 32, out string value4) || !IsLowerHex(value4) || !Long(rootElement, "sequence", 1L, long.MaxValue, out var value5) || !Text(rootElement, "updatedAt", 1, 40, out string value6) || !DateTimeOffset.TryParse(value6, CultureInfo.InvariantCulture, DateTimeStyles.RoundtripKind, out var _))
				{
					reason = "invalidField";
					return false;
				}
				string visibleScreen = null;
				if (rootElement.TryGetProperty("visibleScreen", out var value7))
				{
					if (value7.ValueKind == JsonValueKind.Null)
					{
						visibleScreen = null;
					}
					else
					{
						if (value7.ValueKind != JsonValueKind.String || !ParseScreen(value7.GetString()).HasValue)
						{
							reason = "visibleScreen";
							return false;
						}
						visibleScreen = value7.GetString();
					}
				}
				document = new ScreenActivityDocument
				{
					SchemaVersion = value,
					PlaythroughGeneration = value2,
					RuntimeSession = value3,
					TileInstanceId = value4,
					Sequence = value5,
					UpdatedAt = value6,
					VisibleScreen = visibleScreen
				};
				reason = "ok";
				return true;
			}
			catch (JsonException)
			{
				reason = "malformedJson";
				return false;
			}
		}

		internal static ScreenActivityScreen? ParseScreen(string? value)
		{
			if (!(value == "phoneRecipes"))
			{
				if (value == "advisorDispatches")
				{
					return ScreenActivityScreen.AdvisorDispatches;
				}
				return null;
			}
			return ScreenActivityScreen.PhoneRecipes;
		}

		private static bool ExactKeys(JsonElement root, out string? reason)
		{
			HashSet<string> hashSet = new HashSet<string>(StringComparer.Ordinal);
			foreach (JsonProperty item2 in root.EnumerateObject())
			{
				if (Array.IndexOf(Allowed, item2.Name) < 0)
				{
					reason = "unknownField";
					return false;
				}
				if (!hashSet.Add(item2.Name))
				{
					reason = "duplicateField";
					return false;
				}
			}
			string[] required = Required;
			foreach (string item in required)
			{
				if (!hashSet.Contains(item))
				{
					reason = "missingField";
					return false;
				}
			}
			reason = null;
			return true;
		}

		private static bool Text(JsonElement root, string name, int min, int max, out string value)
		{
			value = "";
			if (!root.TryGetProperty(name, out var value2) || value2.ValueKind != JsonValueKind.String)
			{
				return false;
			}
			value = value2.GetString() ?? "";
			if (value.Trim().Length >= min)
			{
				return value.Length <= max;
			}
			return false;
		}

		private static bool Int(JsonElement root, string name, int min, int max, out int value)
		{
			value = 0;
			if (root.TryGetProperty(name, out var value2) && value2.ValueKind == JsonValueKind.Number && value2.TryGetInt32(out value) && value >= min)
			{
				return value <= max;
			}
			return false;
		}

		private static bool Long(JsonElement root, string name, long min, long max, out long value)
		{
			value = 0L;
			if (root.TryGetProperty(name, out var value2) && value2.ValueKind == JsonValueKind.Number && value2.TryGetInt64(out value) && value >= min)
			{
				return value <= max;
			}
			return false;
		}

		private static bool IsGeneration(string value)
		{
			if (value.Length == 67 && value.StartsWith("v2-", StringComparison.Ordinal))
			{
				return IsLowerHex(value.Substring(3, value.Length - 3));
			}
			return false;
		}

		private static bool IsLowerHex(string value)
		{
			for (int i = 0; i < value.Length; i++)
			{
				bool flag;
				switch (value[i])
				{
				case '0':
				case '1':
				case '2':
				case '3':
				case '4':
				case '5':
				case '6':
				case '7':
				case '8':
				case '9':
				case 'a':
				case 'b':
				case 'c':
				case 'd':
				case 'e':
				case 'f':
					flag = true;
					break;
				default:
					flag = false;
					break;
				}
				if (!flag)
				{
					return false;
				}
			}
			return true;
		}
	}
}
namespace HylandHelper.BridgeContracts
{
	public static class GameDatasetIds
	{
		private static readonly Dictionary<string, string> CanonicalAliases = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase)
		{
			["ogkush"] = "og_kush",
			["sourdiesel"] = "sour_diesel",
			["greencrack"] = "green_crack",
			["granddaddypurple"] = "granddaddy_purple",
			["shroom"] = "shrooms",
			["energydrink"] = "energy_drink",
			["motoroil"] = "motor_oil",
			["megabean"] = "mega_bean",
			["flumedicine"] = "flu_medicine",
			["horsesemen"] = "horse_semen",
			["viagor"] = "viagra"
		};

		private static readonly Dictionary<string, string> FriendlyLabels = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase)
		{
			["og_kush"] = "OG Kush",
			["sour_diesel"] = "Sour Diesel",
			["green_crack"] = "Green Crack",
			["granddaddy_purple"] = "Granddaddy Purple",
			["shrooms"] = "Shrooms",
			["meth"] = "Meth",
			["cocaine"] = "Cocaine",
			["flu_medicine"] = "Flu Medicine",
			["energy_drink"] = "Energy Drink",
			["motor_oil"] = "Motor Oil",
			["mega_bean"] = "Mega Bean",
			["horse_semen"] = "Horse Semen",
			["viagra"] = "Viagor"
		};

		private static readonly HashSet<string> BaseProducts = new HashSet<string>(StringComparer.OrdinalIgnoreCase) { "og_kush", "sour_diesel", "green_crack", "granddaddy_purple", "meth", "cocaine", "shrooms" };

		public static string Canonicalize(string value)
		{
			if (string.IsNullOrWhiteSpace(value))
			{
				return value;
			}
			string text = value.Trim();
			if (!CanonicalAliases.TryGetValue(text, out string value2))
			{
				return text;
			}
			return value2;
		}

		public static bool IsBaseProduct(string value)
		{
			return BaseProducts.Contains(Canonicalize(value));
		}

		public static string FriendlyName(string value)
		{
			string text = Canonicalize(value);
			if (FriendlyLabels.TryGetValue(text, out string value2))
			{
				return value2;
			}
			string[] array = text.Replace('_', ' ').Split(' ', StringSplitOptions.RemoveEmptyEntries);
			for (int i = 0; i < array.Length; i++)
			{
				if (array[i].Length != 0)
				{
					array[i] = char.ToUpperInvariant(array[i][0]) + array[i].Substring(1);
				}
			}
			return string.Join(" ", array);
		}
	}
	public static class PendingMixLimits
	{
		public const int MaxChainSteps = 64;

		public const int MaxEffectsPerStep = 12;

		public const int MaxStringLength = 64;

		public static string OverLimitReason(int stepCount)
		{
			return $"Save/Track unavailable: this mix has {stepCount} steps; the shared limit is {64}.";
		}
	}
	public static class PhoneRecipeLimits
	{
		public const int MaxRecipes = 50;

		public const int MaxChainSteps = 64;

		public const int MaxEffects = 8;

		public const int MaxStringLength = 64;

		public const int MaxBodyBytes = 73728;

		public const int StaleGraceHours = 24;
	}
	public sealed class BridgePhoneRecipe
	{
		[JsonPropertyName("recipeId")]
		public string RecipeId { get; set; } = "";

		[JsonPropertyName("name")]
		public string Name { get; set; } = "";

		[JsonPropertyName("productId")]
		public string ProductId { get; set; } = "";

		[JsonPropertyName("ingredientIds")]
		public List<string> IngredientIds { get; set; } = new List<string>();

		[JsonPropertyName("effects")]
		public List<string>? Effects { get; set; }

		[JsonPropertyName("effectsAvailable")]
		public bool EffectsAvailable { get; set; }

		[JsonPropertyName("sellPrice")]
		public double SellPrice { get; set; }

		[JsonPropertyName("profit")]
		public double Profit { get; set; }

		[JsonPropertyName("profitMargin")]
		public double ProfitMargin { get; set; }

		[JsonPropertyName("profileMode")]
		public string ProfileMode { get; set; } = "standard";

		[JsonPropertyName("source")]
		public string Source { get; set; } = "app-saved";

		[JsonPropertyName("publishedAt")]
		public string PublishedAt { get; set; } = "";

		[JsonPropertyName("expiresAt")]
		public string ExpiresAt { get; set; } = "";
	}
	public sealed class BridgePhoneRecipeSnapshot
	{
		[JsonPropertyName("schemaVersion")]
		public int SchemaVersion { get; set; } = 1;

		[JsonPropertyName("snapshotId")]
		public string SnapshotId { get; set; } = "";

		[JsonPropertyName("publishedAt")]
		public string PublishedAt { get; set; } = "";

		[JsonPropertyName("expiresAt")]
		public string ExpiresAt { get; set; } = "";

		[JsonPropertyName("omittedCount")]
		public int OmittedCount { get; set; }

		[JsonPropertyName("recipes")]
		public List<BridgePhoneRecipe> Recipes { get; set; } = new List<BridgePhoneRecipe>();
	}
}
namespace HylandHelper.PhoneTile
{
	internal sealed record AdvisorDispatchesDestinationCard(GameObject Root, Text UnreadText);
	internal static class AdvisorDispatchesDestinationCardUi
	{
		public static AdvisorDispatchesDestinationCard Create(Transform parent, Action onClick)
		{
			//IL_0015: Unknown result type (might be due to invalid IL or missing references)
			//IL_006c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0077: Unknown result type (might be due to invalid IL or missing references)
			//IL_008c: 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_00d3: Unknown result type (might be due to invalid IL or missing references)
			//IL_00e3: Unknown result type (might be due to invalid IL or missing references)
			//IL_00ee: Unknown result type (might be due to invalid IL or missing references)
			//IL_0103: Unknown result type (might be due to invalid IL or missing references)
			//IL_0117: Unknown result type (might be due to invalid IL or missing references)
			//IL_013c: Unknown result type (might be due to invalid IL or missing references)
			//IL_014d: 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_016d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0181: 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_01b5: Unknown result type (might be due to invalid IL or missing references)
			//IL_01c0: Unknown result type (might be due to invalid IL or missing references)
			//IL_01d4: Unknown result type (might be due to invalid IL or missing references)
			GameObject val = UIFactory.Panel("AdvisorDispatchesDestination", parent, new Color(0.23f, 0.24f, 0.38f), (Vector2?)null, (Vector2?)null, false);
			Image component = val.GetComponent<Image>();
			Button obj = val.AddComponent<Button>();
			((Selectable)obj).targetGraphic = (Graphic)(object)component;
			ButtonUtils.AddListener(obj, onClick);
			RectTransform component2 = ((Component)UIFactory.Text("AdvisorDispatchesDestinationTitle", "Advisor Dispatches", val.transform, 17, (TextAnchor)0, (FontStyle)0)).GetComponent<RectTransform>();
			component2.anchorMin = Vector2.zero;
			component2.anchorMax = Vector2.one;
			component2.offsetMin = new Vector2(18f, 19f);
			component2.offsetMax = new Vector2(-72f, -5f);
			Text obj2 = UIFactory.Text("AdvisorDispatchesDestinationSubtitle", "Incoming only", val.transform, 11, (TextAnchor)6, (FontStyle)0);
			((Graphic)obj2).color = new Color(0.68f, 0.72f, 0.79f);
			RectTransform component3 = ((Component)obj2).GetComponent<RectTransform>();
			component3.anchorMin = Vector2.zero;
			component3.anchorMax = Vector2.one;
			component3.offsetMin = new Vector2(18f, 5f);
			component3.offsetMax = new Vector2(-72f, -23f);
			Text val2 = UIFactory.Text("AdvisorDispatchesDestinationUnread", "0 unread", val.transform, 11, (TextAnchor)5, (FontStyle)0);
			((Graphic)val2).color = Color.white;
			RectTransform component4 = ((Component)val2).GetComponent<RectTransform>();
			component4.anchorMin = Vector2.zero;
			component4.anchorMax = Vector2.one;
			component4.offsetMin = new Vector2(-86f, 0f);
			component4.offsetMax = new Vector2(-28f, 0f);
			RectTransform component5 = ((Component)UIFactory.Text("AdvisorDispatchesDestinationArrow", "›", val.transform, 20, (TextAnchor)5, (FontStyle)0)).GetComponent<RectTransform>();
			component5.anchorMin = Vector2.zero;
			component5.anchorMax = Vector2.one;
			component5.offsetMin = Vector2.zero;
			component5.offsetMax = new Vector2(-12f, 0f);
			LayoutElement obj3 = val.GetComponent<LayoutElement>() ?? val.AddComponent<LayoutElement>();
			obj3.minHeight = 52f;
			obj3.preferredHeight = 52f;
			obj3.flexibleHeight = 0f;
			return new AdvisorDispatchesDestinationCard(val, val2);
		}

		public static void SetUnreadCount(Text unreadText, int count)
		{
			unreadText.text = $"{Math.Clamp(count, 0, 20)} unread";
		}
	}
	public sealed record AdvisorDispatchesHostView(AdvisorDispatchesRead Cache, AdvisorDispatchesUiSnapshot UiState, int UnreadCount, bool StorageAvailable, bool NotificationAttempted);
	public sealed class AdvisorDispatchesHostCoordinator
	{
		private readonly Func<DateTimeOffset, string?, AdvisorDispatchesRead> _readCache;

		private readonly AdvisorDispatchesUiState _uiState;

		private readonly AdvisorDispatchesNotificationCoordinator _notifications;

		private AdvisorDispatchesUiSnapshot? _lastKnownGoodUiState;

		private AdvisorDispatchesHostView? _lastKnownGoodView;

		private string? _activeSaveFingerprint;

		public AdvisorDispatchesHostCoordinator(Func<DateTimeOffset, string?, AdvisorDispatchesRead> readCache, AdvisorDispatchesUiState uiState, AdvisorDispatchesNotificationCoordinator notifications)
		{
			_readCache = readCache;
			_uiState = uiState;
			_notifications = notifications;
		}

		public void ObserveSaveScope(string? saveFingerprint)
		{
			string text = (SaveGenerationIdentity.IsV2Fingerprint(saveFingerprint) ? saveFingerprint : null);
			if (!string.Equals(_activeSaveFingerprint, text, StringComparison.Ordinal))
			{
				_activeSaveFingerprint = text;
				_lastKnownGoodUiState = null;
				_lastKnownGoodView = null;
			}
		}

		public AdvisorDispatchesHostView Poll(DateTimeOffset now)
		{
			AdvisorDispatchesRead advisorDispatchesRead = _readCache(now, _activeSaveFingerprint);
			AdvisorDispatchesReadState state = advisorDispatchesRead.State;
			if ((uint)(state - 3) <= 1u)
			{
				return BuildUnusable(advisorDispatchesRead);
			}
			if (_activeSaveFingerprint == null || advisorDispatchesRead.ScopeGeneration == null)
			{
				return BuildWithoutScope(advisorDispatchesRead);
			}
			HashSet<string> validIds = advisorDispatchesRead.Messages.Select((AdvisorDispatchMessage message) => message.Id).ToHashSet<string>(StringComparer.Ordinal);
			AdvisorDispatchesUiLoad advisorDispatchesUiLoad = _uiState.Load(_activeSaveFingerprint, advisorDispatchesRead.ScopeGeneration, validIds);
			if (!advisorDispatchesUiLoad.StorageAvailable)
			{
				if (_lastKnownGoodView != null && string.Equals(_lastKnownGoodView.UiState.ScopeGeneration, advisorDispatchesRead.ScopeGeneration, StringComparison.Ordinal))
				{
					return _lastKnownGoodView;
				}
				return BuildView(advisorDispatchesRead, new AdvisorDispatchesUiSnapshot(_activeSaveFingerprint, advisorDispatchesRead.ScopeGeneration, Array.Empty<string>(), Array.Empty<string>()), storageAvailable: false, notificationAttempted: false);
			}
			AdvisorDispatchesUiSnapshot state2 = advisorDispatchesUiLoad.State;
			AdvisorDispatchesHostView advisorDispatchesHostView = BuildView(advisorDispatchesRead, state2, storageAvailable: true, notificationAttempted: false);
			_lastKnownGoodUiState = state2;
			_lastKnownGoodView = advisorDispatchesHostView;
			if (!advisorDispatchesUiLoad.Repaired && advisorDispatchesRead.State == AdvisorDispatchesReadState.Fresh)
			{
				List<AdvisorDispatchMessage> newlyUnnotified = advisorDispatchesRead.Messages.Where((AdvisorDispatchMessage message) => !state2.NotifiedIds.Contains<string>(message.Id, StringComparer.Ordinal)).ToList();
				bool notificationAttempted = _notifications.TryNotify(newlyUnnotified, now, delegate(IReadOnlyCollection<string> ids)
				{
					if (!_uiState.TryMarkNotified(state2, ids, validIds, out AdvisorDispatchesUiSnapshot next))
					{
						return false;
					}
					state2 = next;
					_lastKnownGoodUiState = next;
					return true;
				});
				advisorDispatchesHostView = (_lastKnownGoodView = BuildView(advisorDispatchesRead, state2, storageAvailable: true, notificationAttempted));
			}
			return advisorDispatchesHostView;
		}

		public AdvisorDispatchesHostView OpenInbox(DateTimeOffset now)
		{
			if (_lastKnownGoodView == null)
			{
				return Poll(now);
			}
			AdvisorDispatchesHostView lastKnownGoodView = _lastKnownGoodView;
			if (lastKnownGoodView.Cache.ScopeGeneration == null)
			{
				return lastKnownGoodView;
			}
			HashSet<string> validIds = lastKnownGoodView.Cache.Messages.Select((AdvisorDispatchMessage message) => message.Id).ToHashSet<string>(StringComparer.Ordinal);
			string[] displayedValidatedIds = lastKnownGoodView.Cache.Messages.Select((AdvisorDispatchMessage message) => message.Id).ToArray();
			if (!_uiState.TryMarkRead(lastKnownGoodView.UiState, (IReadOnlyCollection<string>)(object)displayedValidatedIds, validIds, out AdvisorDispatchesUiSnapshot next))
			{
				return lastKnownGoodView;
			}
			AdvisorDispatchesHostView advisorDispatchesHostView = BuildView(lastKnownGoodView.Cache, next, lastKnownGoodView.StorageAvailable, notificationAttempted: false);
			_lastKnownGoodUiState = next;
			_lastKnownGoodView = advisorDispatchesHostView;
			return advisorDispatchesHostView;
		}

		private static AdvisorDispatchesHostView BuildView(AdvisorDispatchesRead cache, AdvisorDispatchesUiSnapshot state, bool storageAvailable, bool notificationAttempted)
		{
			return new AdvisorDispatchesHostView(cache, state, AdvisorDispatchesUiState.CountUnread(cache.Messages, state), storageAvailable, notificationAttempted);
		}

		private AdvisorDispatchesHostView BuildUnusable(AdvisorDispatchesRead cache)
		{
			AdvisorDispatchesUiSnapshot state = _lastKnownGoodUiState ?? new AdvisorDispatchesUiSnapshot(_activeSaveFingerprint ?? "unavailable", "unavailable", Array.Empty<string>(), Array.Empty<string>());
			return BuildView(cache with
			{
				Messages = Array.Empty<AdvisorDispatchMessage>()
			}, state, _lastKnownGoodView?.StorageAvailable ?? false, notificationAttempted: false);
		}

		private AdvisorDispatchesHostView BuildWithoutScope(AdvisorDispatchesRead cache)
		{
			AdvisorDispatchesUiSnapshot state = _lastKnownGoodUiState ?? new AdvisorDispatchesUiSnapshot(_activeSaveFingerprint ?? "unscoped", "unscoped", Array.Empty<string>(), Array.Empty<string>());
			return BuildView(cache with
			{
				Messages = Array.Empty<AdvisorDispatchMessage>()
			}, state, _lastKnownGoodView?.StorageAvailable ?? false, notificationAttempted: false);
		}
	}
	public interface IAdvisorDispatchNotificationSink
	{
		bool TryShow(string text);
	}
	public sealed class NoOpAdvisorDispatchNotificationSink : IAdvisorDispatchNotificationSink
	{
		public bool TryShow(string text)
		{
			return false;
		}
	}
	public sealed class AdvisorDispatchesNotificationCoordinator
	{
		public static readonly TimeSpan MinimumInterval = TimeSpan.FromSeconds(30.0);

		private readonly IAdvisorDispatchNotificationSink _sink;

		private DateTimeOffset? _lastNotificationAt;

		public AdvisorDispatchesNotificationCoordinator(IAdvisorDispatchNotificationSink sink)
		{
			_sink = sink;
		}

		public bool TryNotify(IReadOnlyList<AdvisorDispatchMessage> newlyUnnotified, DateTimeOffset now, Func<IReadOnlyCollection<string>, bool> persistNotifiedIds)
		{
			List<AdvisorDispatchMessage> list = (from @group in newlyUnnotified.Where((AdvisorDispatchMessage message) => message != null && !string.IsNullOrWhiteSpace(message.Id)).GroupBy<AdvisorDispatchMessage, string>((AdvisorDispatchMessage message) => message.Id, StringComparer.Ordinal)
				select @group.First()).Take(20).ToList();
			if (list.Count == 0 || (_lastNotificationAt.HasValue && now - _lastNotificationAt.Value < MinimumInterval))
			{
				return false;
			}
			string[] arg = list.Select((AdvisorDispatchMessage message) => message.Id).ToArray();
			bool flag;
			try
			{
				flag = persistNotifiedIds((IReadOnlyCollection<string>)(object)arg);
			}
			catch
			{
				flag = false;
			}
			if (!flag)
			{
				return false;
			}
			_lastNotificationAt = now;
			string text = AdvisorDispatchNotificationCopy.ForBatch(list);
			try
			{
				_sink.TryShow(text);
			}
			catch
			{
			}
			return true;
		}
	}
	public static class AdvisorDispatchNotificationCopy
	{
		public static string ForBatch(IReadOnlyList<AdvisorDispatchMessage> messages)
		{
			if (messages.Count == 0)
			{
				return "";
			}
			if (messages.Count != 1)
			{
				return $"{messages.Count} new Advisor Dispatches";
			}
			return messages[0].Headline;
		}
	}
	public enum AdvisorDispatchesReadState
	{
		Missing,
		Fresh,
		Expired,
		Malformed,
		Unavailable
	}
	public sealed record AdvisorDispatchMessage(string Id, string PersonaLabel, string LocalTimestamp, string Headline, string Body);
	public sealed record AdvisorDispatchesRead(AdvisorDispatchesReadState State, string? ScopeGeneration, string? Revision, DateTimeOffset? CapturedAt, IReadOnlyList<AdvisorDispatchMessage> Messages, string StatusText);
	public sealed record AdvisorDispatchesViewState(AdvisorDispatchesReadState State, IReadOnlyList<AdvisorDispatchMessage> Messages, AdvisorDispatchesUiSnapshot? UiState, int UnreadCount, string StatusText, bool StorageAvailable);
	public sealed class AdvisorDispatchesReader
	{
		private sealed record ParsedDispatch(string Id, DateTimeOffset CreatedAt, string Headline, string Body, string PersonaId);

		public const int MaxCacheBytes = 65536;

		public const int MaxDispatches = 20;

		public const int MaxScopeGenerationLength = 128;

		public const int MaxRevisionLength = 128;

		public const int MaxDispatchIdLength = 64;

		public const int MaxHeadlineLength = 60;

		public const int MaxBodyLength = 512;

		public const int MaxPersonaLength = 64;

		public const int MaxTimestampLength = 128;

		private static readonly UTF8Encoding StrictUtf8 = new UTF8Encoding(encoderShouldEmitUTF8Identifier: false, throwOnInvalidBytes: true);

		private static readonly HashSet<string> DocumentFields = new HashSet<string>(StringComparer.Ordinal) { "version", "saveFingerprint", "scopeGeneration", "revision", "capturedAt", "dispatches" };

		private static readonly HashSet<string> DispatchFields = new HashSet<string>(StringComparer.Ordinal) { "id", "createdAt", "headline", "body", "personaId", "voiceVersion", "triggerId" };

		private readonly string _path;

		private readonly Func<string, LocalFileSignature> _signatureReader;

		private readonly Func<string, byte[]> _readBytes;

		private readonly LocalFileRefreshGate _refreshGate;

		private AdvisorDispatchesRead? _lastRead;

		private string? _lastSaveFingerprint;

		public static string DefaultPath => Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.UserProfile), "AppData", "LocalLow", "TVGS", "Schedule I", "HylandHelper", "advisor-dispatches-cache.json");

		public AdvisorDispatchesReader(string path)
			: this(path, null, null, null)
		{
		}

		internal AdvisorDispatchesReader(string path, Func<string, LocalFileSignature>? signatureReader, Func<string, byte[]>? readBytes, TimeSpan? forcedRecovery)
		{
			_path = path;
			_signatureReader = signatureReader ?? new Func<string, LocalFileSignature>(ReadSignature);
			_readBytes = readBytes ?? new Func<string, byte[]>(File.ReadAllBytes);
			_refreshGate = new LocalFileRefreshGate(forcedRecovery ?? TimeSpan.FromSeconds(15.0));
		}

		public AdvisorDispatchesRead Read(DateTimeOffset now, string? activeSaveFingerprint)
		{
			if (!SaveGenerationIdentity.IsV2Fingerprint(activeSaveFingerprint))
			{
				return Result(AdvisorDispatchesReadState.Missing, "No Advisor Dispatches available yet.");
			}
			LocalFileSignature signature = _signatureReader(_path);
			if (string.Equals(_lastSaveFingerprint, activeSaveFingerprint, StringComparison.Ordinal) && _lastRead != null && !_refreshGate.ShouldRefresh(signature, now, dirty: false, out var _))
			{
				return _lastRead;
			}
			AdvisorDispatchesRead advisorDispatchesRead = (_lastRead = ReadCurrent(now, activeSaveFingerprint, signature));
			_lastSaveFingerprint = activeSaveFingerprint;
			if (advisorDispatchesRead.State == AdvisorDispatchesReadState.Unavailable)
			{
				_refreshGate.RejectTransient();
			}
			else
			{
				_refreshGate.Accept(signature, now);
			}
			return advisorDispatchesRead;
		}

		private AdvisorDispatchesRead ReadCurrent(DateTimeOffset now, string activeSaveFingerprint, LocalFileSignature signature)
		{
			if (!signature.Exists)
			{
				return Result(AdvisorDispatchesReadState.Missing, "No Advisor Dispatches available yet.");
			}
			byte[] array;
			try
			{
				if (signature.Length <= 0 || signature.Length > 65536)
				{
					return Result(AdvisorDispatchesReadState.Malformed, "Advisor Dispatches unavailable right now.");
				}
				array = _readBytes(_path);
			}
			catch
			{
				return Result(AdvisorDispatchesReadState.Unavailable, "Advisor Dispatches unavailable right now.");
			}
			if (array.Length == 0 || array.Length > 65536)
			{
				return Result(AdvisorDispatchesReadState.Malformed, "Advisor Dispatches unavailable right now.");
			}
			try
			{
				using JsonDocument jsonDocument = JsonDocument.Parse(StrictUtf8.GetBytes(StrictUtf8.GetString(array)));
				JsonElement rootElement = jsonDocument.RootElement;
				if (rootElement.ValueKind != JsonValueKind.Object || !HasExactFields(rootElement, DocumentFields))
				{
					return Malformed();
				}
				if (!TryGetInt(rootElement, "version", out var value) || value != 2 || !TryGetBoundedString(rootElement, "saveFingerprint", 67, out string value2) || !SaveGenerationIdentity.IsV2Fingerprint(value2) || !string.Equals(value2, activeSaveFingerprint, StringComparison.Ordinal) || !TryGetBoundedString(rootElement, "scopeGeneration", 128, out string value3) || !TryGetBoundedString(rootElement, "revision", 128, out string value4) || !TryGetTimestamp(rootElement, "capturedAt", out var value5) || rootElement.GetProperty("dispatches").ValueKind != JsonValueKind.Array)
				{
					return Malformed();
				}
				JsonElement property = rootElement.GetProperty("dispatches");
				if (property.GetArrayLength() > 20)
				{
					return Malformed();
				}
				HashSet<string> ids = new HashSet<string>(StringComparer.Ordinal);
				List<ParsedDispatch> list = new List<ParsedDispatch>(property.GetArrayLength());
				foreach (JsonElement item in property.EnumerateArray())
				{
					if (!TryParseDispatch(item, value5, ids, out ParsedDispatch dispatch))
					{
						return Malformed();
					}
					list.Add(dispatch);
				}
				List<AdvisorDispatchMessage> list2 = list.Where((ParsedDispatch parsedDispatch) => now < parsedDispatch.CreatedAt + TimeSpan.FromDays(7.0)).Select(Project).ToList();
				int num = ((list.Count <= 0 || list2.Count != 0) ? 1 : 2);
				return new AdvisorDispatchesRead(StatusText: (num == 2) ? "Dispatches expired. New dispatches unavailable." : ((list2.Count == 0) ? "No Advisor Dispatches yet." : "Incoming only"), State: (AdvisorDispatchesReadState)num, ScopeGeneration: value3, Revision: value4, CapturedAt: value5, Messages: list2);
			}
			catch
			{
				return Malformed();
			}
		}

		private static LocalFileSignature ReadSignature(string path)
		{
			try
			{
				FileInfo fileInfo = new FileInfo(path);
				return fileInfo.Exists ? new LocalFileSignature(Exists: true, fileInfo.Length, fileInfo.LastWriteTimeUtc.Ticks) : LocalFileSignature.Missing;
			}
			catch
			{
				return LocalFileSignature.Missing;
			}
		}

		private static AdvisorDispatchMessage Project(ParsedDispatch dispatch)
		{
			return new AdvisorDispatchMessage(dispatch.Id, PersonaLabel(dispatch.PersonaId), dispatch.CreatedAt.ToLocalTime().ToString("g", CultureInfo.CurrentCulture), dispatch.Headline, dispatch.Body);
		}

		private static string PersonaLabel(string personaId)
		{
			return personaId switch
			{
				"default" => "Advisor", 
				"roast" => "Roast Master", 
				"kingpin" => "Hardened Kingpin", 
				"accountant" => "Nervous Accountant", 
				"chemist" => "Shady Chemist", 
				_ => "Advisor", 
			};
		}

		private static bool TryParseDispatch(JsonElement element, DateTimeOffset capturedAt, HashSet<string> ids, out ParsedDispatch? dispatch)
		{
			dispatch = null;
			if (element.ValueKind != JsonValueKind.Object || !HasExactFields(element, DispatchFields) || !TryGetBoundedString(element, "id", 64, out string value) || !ids.Add(value) || !TryGetTimestamp(element, "createdAt", out var value2) || value2 > capturedAt || capturedAt - value2 > TimeSpan.FromDays(7.0) || !TryGetBoundedString(element, "headline", 60, out string value3) || !TryGetBoundedString(element, "body", 512, out string value4) || !TryGetBoundedString(element, "personaId", 64, out string value5) || !TryGetBoundedString(element, "voiceVersion", 64, out string value6) || !TryGetBoundedString(element, "triggerId", 64, out value6))
			{
				return false;
			}
			dispatch = new ParsedDispatch(value, value2, value3, value4, value5);
			return true;
		}

		private static bool HasExactFields(JsonElement element, HashSet<string> allowed)
		{
			HashSet<string> hashSet = new HashSet<string>(StringComparer.Ordinal);
			foreach (JsonProperty item in element.EnumerateObject())
			{
				if (!allowed.Contains(item.Name) || !hashSet.Add(item.Name))
				{
					return false;
				}
			}
			return hashSet.SetEquals(allowed);
		}

		private static bool TryGetInt(JsonElement element, string name, out int value)
		{
			value = 0;
			if (element.TryGetProperty(name, out var value2) && value2.ValueKind == JsonValueKind.Number)
			{
				return value2.TryGetInt32(out value);
			}
			return false;
		}

		private static bool TryGetBoundedString(JsonElement element, string name, int maxLength, out string value)
		{
			value = "";
			if (!element.TryGetProperty(name, out var value2) || value2.ValueKind != JsonValueKind.String)
			{
				return false;
			}
			value = value2.GetString() ?? "";
			if (!string.IsNullOrWhiteSpace(value))
			{
				return value.Length <= maxLength;
			}
			return false;
		}

		private static bool TryGetTimestamp(JsonElement element, string name, out DateTimeOffset value)
		{
			value = default(DateTimeOffset);
			if (!TryGetBoundedString(element, name, 128, out string value2))
			{
				return false;
			}
			return DateTimeOffset.TryParse(value2, CultureInfo.InvariantCulture, DateTimeStyles.None, out value);
		}

		private static AdvisorDispatchesRead Malformed()
		{
			return Result(AdvisorDispatchesReadState.Malformed, "Advisor Dispatches unavailable right now.");
		}

		private static AdvisorDispatchesRead Result(AdvisorDispatchesReadState state, string text)
		{
			return new AdvisorDispatchesRead(state, null, null, null, Array.Empty<AdvisorDispatchMessage>(), text);
		}
	}
	public sealed record AdvisorDispatchesUiSnapshot(string SaveFingerprint, string ScopeGeneration, IReadOnlyList<string> ReadIds, IReadOnlyList<string> NotifiedIds);
	public sealed record AdvisorDispatchesUiLoad(AdvisorDispatchesUiSnapshot State, bool StorageAvailable, bool Repaired);
	public sealed class AdvisorDispatchesUiState
	{
		private sealed class UiDocument
		{
			[JsonPropertyName("version")]
			public int Version { get; set; }

			[JsonPropertyName("saveFingerprint")]
			public string SaveFingerprint { get; set; } = "";

			[JsonPropertyName("scopeGeneration")]
			public string ScopeGeneration { get; set; } = "";

			[JsonPropertyName("readIds")]
			public string[] ReadIds { get; set; } = Array.Empty<string>();

			[JsonPropertyName("notifiedIds")]
			public string[] NotifiedIds { get; set; } = Array.Empty<string>();
		}

		public const int MaxStateBytes = 8192;

		public const int MaxIds = 20;

		public const int MaxIdLength = 64;

		public const int MaxScopeGenerationLength = 128;

		private static readonly HashSet<string> Fields = new HashSet<string>(StringComparer.Ordinal) { "version", "saveFingerprint", "scopeGeneration", "readIds", "notifiedIds" };

		private readonly string _path;

		private readonly Action<string, string> _replaceFile;

		private readonly Action<string> _deleteFile;

		private readonly Func<string, byte[]> _readFile;

		private readonly Func<string, LocalFileSignature> _signatureReader;

		private readonly Func<DateTimeOffset> _clock;

		private readonly LocalFileRefreshGate _refreshGate;

		private AdvisorDispatchesUiLoad? _lastLoad;

		private string? _lastSaveFingerprint;

		private string? _lastScopeGeneration;

		private string? _lastValidIds;

		public static string DefaultPath => Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.UserProfile), "AppData", "LocalLow", "TVGS", "Schedule I", "HylandHelper", "advisor-dispatches-ui.json");

		public AdvisorDispatchesUiState(string path)
			: this(path, null, null)
		{
		}

		internal AdvisorDispatchesUiState(string path, Action<string, string>? replaceFile, Action<string>? deleteFile, Func<string, byte[]>? readFile = null, Func<string, LocalFileSignature>? signatureReader = null, Func<DateTimeOffset>? clock = null, TimeSpan? forcedRecovery = null)
		{
			_path = path;
			_replaceFile = replaceFile ?? ((Action<string, string>)delegate(string source, string destination)
			{
				File.Move(source, destination, overwrite: true);
			});
			_deleteFile = deleteFile ?? new Action<string>(File.Delete);
			_readFile = readFile ?? new Func<string, byte[]>(File.ReadAllBytes);
			_signatureReader = signatureReader ?? new Func<string, LocalFileSignature>(ReadSignature);
			_clock = clock ?? ((Func<DateTimeOffset>)(() => DateTimeOffset.UtcNow));
			_refreshGate = new LocalFileRefreshGate(forcedRecovery ?? TimeSpan.FromSeconds(15.0));
		}

		public AdvisorDispatchesUiLoad Load(string saveFingerprint, string scopeGeneration, IReadOnlySet<string> validIds)
		{
			AdvisorDispatchesUiSnapshot advisorDispatchesUiSnapshot = Empty(saveFingerprint, scopeGeneration);
			if (!SaveGenerationIdentity.IsV2Fingerprint(saveFingerprint) || !IsBounded(scopeGeneration, 128) || !ValidIds(validIds))
			{
				return new AdvisorDispatchesUiLoad(advisorDispatchesUiSnapshot, StorageAvailable: false, Repaired: false);
			}
			LocalFileSignature signature = _signatureReader(_path);
			string text = string.Join("\u001f", validIds.OrderBy<string, string>((string id) => id, StringComparer.Ordinal));
			if (string.Equals(_lastSaveFingerprint, saveFingerprint, StringComparison.Ordinal) && string.Equals(_lastScopeGeneration, scopeGeneration, StringComparison.Ordinal) && string.Equals(_lastValidIds, text, StringComparison.Ordinal) && _lastLoad != null && !_refreshGate.ShouldRefresh(signature, _clock(), dirty: false, out var _))
			{
				return _lastLoad;
			}
			AdvisorDispatchesUiLoad result = (_lastLoad = LoadCurrent(advisorDispatchesUiSnapshot, saveFingerprint, scopeGeneration, validIds, signature));
			_lastSaveFingerprint = saveFingerprint;
			_lastScopeGeneration = scopeGeneration;
			_lastValidIds = text;
			_refreshGate.Accept(signature, _clock());
			return result;
		}

		private AdvisorDispatchesUiLoad LoadCurrent(AdvisorDispatchesUiSnapshot empty, string saveFingerprint, string scopeGeneration, IReadOnlySet<string> validIds, LocalFileSignature signature)
		{
			if (!signature.Exists)
			{
				return new AdvisorDispatchesUiLoad(empty, StorageAvailable: true, Repaired: false);
			}
			try
			{
				if (signature.Length <= 0 || signature.Length > 8192)
				{
					return new AdvisorDispatchesUiLoad(empty, StorageAvailable: false, Repaired: false);
				}
				if (!TryParse(_readFile(_path), out AdvisorDispatchesUiSnapshot snapshot))
				{
					return new AdvisorDispatchesUiLoad(empty, StorageAvailable: false, Repaired: false);
				}
				if (!string.Equals(snapshot.SaveFingerprint, saveFingerprint, StringComparison.Ordinal) || !string.Equals(snapshot.ScopeGeneration, scopeGeneration, StringComparison.Ordinal))
				{
					return new AdvisorDispatchesUiLoad(empty, StorageAvailable: true, Repaired: false);
				}
				AdvisorDispatchesUiSnapshot advisorDispatchesUiSnapshot = Reconcile(snapshot, saveFingerprint, scopeGeneration, validIds);
				if (Equivalent(snapshot, advisorDispatchesUiSnapshot))
				{
					return new AdvisorDispatchesUiLoad(advisorDispatchesUiSnapshot, StorageAvailable: true, Repaired: false);
				}
				return TryPersist(advisorDispatchesUiSnapshot) ? new AdvisorDispatchesUiLoad(advisorDispatchesUiSnapshot, StorageAvailable: true, Repaired: true) : new AdvisorDispatchesUiLoad(advisorDispatchesUiSnapshot, StorageAvailable: false, Repaired: true);
			}
			catch
			{
				return new AdvisorDispatchesUiLoad(empty, StorageAvailable: false, Repaired: false);
			}
		}

		public bool TryMarkRead(AdvisorDispatchesUiSnapshot current, IReadOnlyCollection<string> displayedValidatedIds, IReadOnlySet<string> validIds, out AdvisorDispatchesUiSnapshot next)
		{
			return TryMark(current, displayedValidatedIds, validIds, read: true, out next);
		}

		public bool TryMarkNotified(AdvisorDispatchesUiSnapshot current, IReadOnlyCollection<string> notifiedValidatedIds, IReadOnlySet<string> validIds, out AdvisorDispatchesUiSnapshot next)
		{
			return TryMark(current, notifiedValidatedIds, validIds, read: false, out next);
		}

		public static AdvisorDispatchesUiSnapshot Reconcile(AdvisorDispatchesUiSnapshot? persisted, string saveFingerprint, string scopeGeneration, IReadOnlySet<string> validIds)
		{
			if (persisted == null || !string.Equals(persisted.SaveFingerprint, saveFingerprint, StringComparison.Ordinal) || !string.Equals(persisted.ScopeGeneration, scopeGeneration, StringComparison.Ordinal))
			{
				return Empty(saveFingerprint, scopeGeneration);
			}
			return new AdvisorDispatchesUiSnapshot(saveFingerprint, scopeGeneration, Intersect(persisted.ReadIds, validIds), Intersect(persisted.NotifiedIds, validIds));
		}

		public static int CountUnread(IReadOnlyCollection<AdvisorDispatchMessage> displayedMessages, AdvisorDispatchesUiSnapshot state)
		{
			return displayedMessages.Count((AdvisorDispatchMessage message) => !state.ReadIds.Contains<string>(message.Id, StringComparer.Ordinal));
		}

		private bool TryMark(AdvisorDispatchesUiSnapshot current, IReadOnlyCollection<string> candidateIds, IReadOnlySet<string> validIds, bool read, out AdvisorDispatchesUiSnapshot next)
		{
			string[] second = candidateIds.Where((string id) => validIds.Contains(id) && IsBounded(id, 64)).Distinct<string>(StringComparer.Ordinal).Take(20)
				.ToArray();
			string[] array = (read ? current.ReadIds : current.NotifiedIds).Concat(second).Distinct<string>(StringComparer.Ordinal).Take(20)
				.OrderBy<string, string>((string id) => id, StringComparer.Ordinal)
				.ToArray();
			next = (read ? current with
			{
				ReadIds = array
			} : current with
			{
				NotifiedIds = array
			});
			if (Equivalent(current, next))
			{
				return true;
			}
			if (TryPersist(next))
			{
				_refreshGate.Reset();
				_lastLoad = null;
				return true;
			}
			next = current;
			return false;
		}

		private static LocalFileSignature ReadSignature(string path)
		{
			try
			{
				FileInfo fileInfo = new FileInfo(path);
				return fileInfo.Exists ? new LocalFileSignature(Exists: true, fileInfo.Length, fileInfo.LastWriteTimeUtc.Ticks) : LocalFileSignature.Missing;
			}
			catch
			{
				return LocalFileSignature.Missing;
			}
		}

		private bool TryPersist(AdvisorDispatchesUiSnapshot state)
		{
			try
			{
				if (!SaveGenerationIdentity.IsV2Fingerprint(state.SaveFingerprint) || !IsBounded(state.ScopeGeneration, 128))
				{
					return false;
				}
				byte[] array = JsonSerializer.SerializeToUtf8Bytes(new UiDocument
				{
					Version = 2,
					SaveFingerprint = state.SaveFingerprint,
					ScopeGeneration = state.ScopeGeneration,
					ReadIds = state.ReadIds.ToArray(),
					NotifiedIds = state.NotifiedIds.ToArray()
				}, new JsonSerializerOptions
				{
					WriteIndented = true
				});
				if (array.Length > 8192)
				{
					return false;
				}
				if (File.Exists(_path) && _readFile(_path).SequenceEqual(array))
				{
					return true;
				}
				string directoryName = Path.GetDirectoryName(_path);
				if (!string.IsNullOrWhiteSpace(directoryName))
				{
					Directory.CreateDirectory(directoryName);
				}
				string text = $"{_path}.{Guid.NewGuid():N}.tmp";
				try
				{
					using (FileStream fileStream = new FileStream(text, FileMode.CreateNew, FileAccess.Write, FileShare.None))
					{
						fileStream.Write(array, 0, array.Length);
						fileStream.Flush(flushToDisk: true);
					}
					_replaceFile(text, _path);
					text = "";
					return true;
				}
				finally
				{
					if (!string.IsNullOrEmpty(text))
					{
						try
						{
							File.Delete(text);
						}
						catch
						{
						}
					}
				}
			}
			catch
			{
				return false;
			}
		}

		private static bool TryParse(byte[] bytes, out AdvisorDispatchesUiSnapshot? snapshot)
		{
			snapshot = null;
			try
			{
				using JsonDocument jsonDocument = JsonDocument.Parse(bytes);
				JsonElement rootElement = jsonDocument.RootElement;
				if (rootElement.ValueKind != JsonValueKind.Object || !ExactFields(rootElement) || !rootElement.TryGetProperty("version", out var value) || value.ValueKind != JsonValueKind.Number || value.GetInt32() != 2 || !TrySaveFingerprint(rootElement, out string saveFingerprint) || !TryScope(rootElement, out string scope) || !TryIds(rootElement, "readIds", out IReadOnlyList<string> ids) || !TryIds(rootElement, "notifiedIds", out IReadOnlyList<string> ids2))
				{
					return false;
				}
				snapshot = new AdvisorDispatchesUiSnapshot(saveFingerprint, scope, ids, ids2);
				return true;
			}
			catch
			{
				return false;
			}
		}

		private static bool ExactFields(JsonElement root)
		{
			HashSet<string> hashSet = new HashSet<string>(StringComparer.Ordinal);
			foreach (JsonProperty item in root.EnumerateObject())
			{
				if (!Fields.Contains(item.Name) || !hashSet.Add(item.Name))
				{
					return false;
				}
			}
			return hashSet.SetEquals(Fields);
		}

		private static bool TryScope(JsonElement root, out string scope)
		{
			scope = "";
			if (!root.TryGetProperty("scopeGeneration", out var value) || value.ValueKind != JsonValueKind.String)
			{
				return false;
			}
			scope = value.GetString() ?? "";
			return IsBounded(scope, 128);
		}

		private static bool TrySaveFingerprint(JsonElement root, out string saveFingerprint)
		{
			saveFingerprint = "";
			if (!root.TryGetProperty("saveFingerprint", out var value) || value.ValueKind != JsonValueKind.String)
			{
				return false;
			}
			saveFingerprint = value.GetString() ?? "";
			return SaveGenerationIdentity.IsV2Fingerprint(saveFingerprint);
		}

		private static bool TryIds(JsonElement root, string name, out IReadOnlyList<string> ids)
		{
			ids = Array.Empty<string>();
			if (!root.TryGetProperty(name, out var value) || value.ValueKind != JsonValueKind.Array || value.GetArrayLength() > 20)
			{
				return false;
			}
			List<string> list = new List<string>();
			foreach (JsonElement item in value.EnumerateArray())
			{
				if (item.ValueKind != JsonValueKind.String)
				{
					return false;
				}
				string text = item.GetString() ?? "";
				if (!IsBounded(text, 64) || list.Contains<string>(text, StringComparer.Ordinal))
				{
					return false;
				}
				list.Add(text);
			}
			ids = list.OrderBy<string, string>((string id) => id, StringComparer.Ordinal).ToArray();
			return true;
		}

		private static IReadOnlyList<string> Intersect(IEnumerable<string> ids, IReadOnlySet<string> validIds)
		{
			return ids.Where(validIds.Contains).Distinct<string>(StringComparer.Ordinal).Take(20)
				.OrderBy<string, string>((string id) => id, StringComparer.Ordinal)
				.ToArray();
		}

		private static bool ValidIds(IEnumerable<string> ids)
		{
			if (ids.Count() <= 20)
			{
				return ids.All((string id) => IsBounded(id, 64));
			}
			return false;
		}

		private static bool IsBounded(string? value, int maxLength)
		{
			if (!string.IsNullOrWhiteSpace(value))
			{
				return value.Length <= maxLength;
			}
			return false;
		}

		private static AdvisorDispatchesUiSnapshot Empty(string saveFingerprint, string scope)
		{
			return new AdvisorDispatchesUiSnapshot(saveFingerprint, scope, Array.Empty<string>(), Array.Empty<string>());
		}

		private static bool Equivalent(AdvisorDispatchesUiSnapshot left, AdvisorDispatchesUiSnapshot right)
		{
			if (string.Equals(left.SaveFingerprint, right.SaveFingerprint, StringComparison.Ordinal) && string.Equals(left.ScopeGeneration, right.ScopeGeneration, StringComparison.Ordinal) && left.ReadIds.SequenceEqual<string>(right.ReadIds, StringComparer.Ordinal))
			{
				return left.NotifiedIds.SequenceEqual<string>(right.NotifiedIds, StringComparer.Ordinal);
			}
			return false;
		}
	}
	internal static class BoundedHttpResponse
	{
		public static async Task<byte[]> ReadBytesAsync(HttpResponseMessage response, int maxBytes, CancellationToken cancellationToken)
		{
			if (maxBytes <= 0)
			{
				throw new ArgumentOutOfRangeException("maxBytes");
			}
			long? contentLength = response.Content.Headers.ContentLength;
			if (contentLength.HasValue && contentLength.GetValueOrDefault() > 0 && response.Content.Headers.ContentLength > maxBytes)
			{
				throw new InvalidDataException("Backend response exceeded the local byte limit.");
			}
			byte[] result;
			await using (Stream source = await response.Content.ReadAsStreamAsync(cancellationToken).ConfigureAwait(continueOnCapturedContext: false))
			{
				using MemoryStream destination = new MemoryStream();
				byte[] buffer = new byte[Math.Min(8192, maxBytes + 1)];
				while (true)
				{
					int num = await source.ReadAsync(buffer.AsMemory(0, buffer.Length), cancellationToken).ConfigureAwait(continueOnCapturedContext: false);
					if (num == 0)
					{
						break;
					}
					if (destination.Length + num > maxBytes)
					{
						throw new InvalidDataException("Backend response exceeded the local byte limit.");
					}
					destination.Write(buffer, 0, num);
				}
				result = destination.ToArray();
			}
			return result;
		}
	}
	internal sealed class BridgeCommandFileWriter
	{
		private readonly string _path;

		public BridgeCommandFileWriter(string path)
		{
			_path = path;
		}

		public bool TryWrite(object command)
		{
			string text = null;
			try
			{
				if (File.Exists(_path))
				{
					return false;
				}
				string directoryName = Path.GetDirectoryName(_path);
				if (string.IsNullOrWhiteSpace(directoryName))
				{
					return false;
				}
				Directory.CreateDirectory(directoryName);
				text = $"{_path}.{Guid.NewGuid():N}.tmp";
				File.WriteAllText(text, JsonSerializer.Serialize(command));
				File.Move(text, _path, overwrite: false);
				text = null;
				return true;
			}
			catch
			{
				return false;
			}
			finally
			{
				if (text != null)
				{
					try
					{
						File.Delete(text);
					}
					catch
					{
					}
				}
			}
		}
	}
	internal sealed record BridgeCommandResult(string RequestId, string Status);
	internal static class BridgeCommandResultReader
	{
		private const int MaxResultBytes = 4096;

		private static readonly HashSet<string> Fields = new HashSet<string>(StringComparer.Ordinal) { "version", "requestId", "status", "saveFingerprint" };

		internal static IReadOnlyList<BridgeCommandResult> Take(string directory, string? activeSaveFingerprint)
		{
			if (!SaveGenerationIdentity.IsV2Fingerprint(activeSaveFingerprint) || string.IsNullOrWhiteSpace(directory) || !Directory.Exists(directory))
			{
				return Array.Empty<BridgeCommandResult>();
			}
			try
			{
				return TakePaths(Directory.GetFiles(directory, "bridge-command-result-*.json"), activeSaveFingerprint);
			}
			catch
			{
				return Array.Empty<BridgeCommandResult>();
			}
		}

		internal static IReadOnlyList<BridgeCommandResult> TakeKnown(string directory, IEnumerable<string> requestIds, string? activeSaveFingerprint)
		{
			if (!SaveGenerationIdentity.IsV2Fingerprint(activeSaveFingerprint) || string.IsNullOrWhiteSpace(directory))
			{
				return Array.Empty<BridgeCommandResult>();
			}
			List<string> list = new List<string>();
			foreach (string requestId in requestIds)
			{
				if (IsSafeRequestId(requestId))
				{
					string text = Path.Combine(directory, "bridge-command-result-" + requestId + ".json");
					if (File.Exists(text))
					{
						list.Add(text);
					}
				}
			}
			return TakePaths(list, activeSaveFingerprint);
		}

		internal static IReadOnlyList<BridgeCommandResult> TakePaths(IEnumerable<string> paths, string? activeSaveFingerprint)
		{
			List<BridgeCommandResult> list = new List<BridgeCommandResult>();
			if (!SaveGenerationIdentity.IsV2Fingerprint(activeSaveFingerprint))
			{
				return list;
			}
			foreach (string path in paths)
			{
				try
				{
					if (new FileInfo(path).Length <= 0 || new FileInfo(path).Length > 4096)
					{
						continue;
					}
					using JsonDocument jsonDocument = JsonDocument.Parse(File.ReadAllBytes(path));
					JsonElement rootElement = jsonDocument.RootElement;
					if (HasExactFields(rootElement) && TryInt(rootElement, "version", out var value) && value == 1 && TryString(rootElement, "requestId", 64, out string value2) && IsSafeRequestId(value2) && TryString(rootElement, "status", 32, out string value3) && IsSafeStatus(value3) && TryString(rootElement, "saveFingerprint", 67, out string value4) && SaveGenerationIdentity.IsV2Fingerprint(value4) && string.Equals(value4, activeSaveFingerprint, StringComparison.Ordinal) && string.Equals(Path.GetFileNameWithoutExtension(path), "bridge-command-result-" + value2, StringComparison.Ordinal))
					{
						list.Add(new BridgeCommandResult(value2, value3));
					}
				}
				catch
				{
				}
				finally
				{
					try
					{
						File.Delete(path);
					}
					catch
					{
					}
				}
			}
			return list;
		}

		private static bool HasExactFields(JsonElement root)
		{
			if (root.ValueKind != JsonValueKind.Object)
			{
				return false;
			}
			HashSet<string> hashSet = new HashSet<string>(Fields, StringComparer.Ordinal);
			foreach (JsonProperty item in root.EnumerateObject())
			{
				if (!hashSet.Remove(item.Name))
				{
					return false;
				}
			}
			return hashSet.Count == 0;
		}

		private static bool TryInt(JsonElement root, string name, out int value)
		{
			value = 0;
			if (root.TryGetProperty(name, out var value2) && value2.ValueKind == JsonValueKind.Number)
			{
				return value2.TryGetInt32(out value);
			}
			return false;
		}

		private static bool TryString(JsonElement root, string name, int maxLength, out string value)
		{
			value = "";
			if (!root.TryGetProperty(name, out var value2) || value2.ValueKind != JsonValueKind.String)
			{
				return false;
			}
			value = value2.GetString() ?? "";
			if (value.Length > 0)
			{
				return value.Length <= maxLength;
			}
			return false;
		}

		private static bool IsSafeRequestId(string requestId)
		{
			foreach (char c in requestId)
			{
				if (!char.IsLetterOrDigit(c) && c != '-' && c != '_')
				{
					return false;
				}
			}
			return true;
		}

		private static bool IsSafeStatus(string status)
		{
			switch (status)
			{
			case "sent":
			case "alreadySent":
			case "failed":
			case "unauthorized":
				return true;
			default:
				return false;
			}
		}
	}
	public sealed class BridgeStatus
	{
		public string? Status;

		public string? PairingCode;

		public string? SaveLine;

		public string? LatestLine;

		public long Sequence;

		public bool? CanUnlink;
	}
	internal sealed record BridgeStatusFileRead(BridgeStatus? Status, string? TileChallenge, LocalFileSignature Signature, bool RefreshAttempted, string? RuntimeSession = null);
	internal sealed class BridgeStatusFileReader
	{
		private const int MaxStatusBytes = 65536;

		private const int MaxRuntimeSessionLength = 64;

		private static readonly HashSet<string> ChallengeFields = new HashSet<string>(StringComparer.Ordinal) { "version", "challenge", "protocolVersion" };

		private readonly string _path;

		private readonly Func<string, LocalFileSignature> _signatureReader;

		private readonly Func<string, byte[]> _readBytes;

		private readonly LocalFileRefreshGate _gate;

		private BridgeStatusFileRead _last;

		internal BridgeStatusFileReader(string path, Func<string, LocalFileSignature>? signatureReader = null, Func<string, byte[]>? readBytes = null, TimeSpan? forcedRecovery = null)
		{
			_path = path;
			_signatureReader = signatureReader ?? new Func<string, LocalFileSignature>(ReadSignature);
			_readBytes = readBytes ?? new Func<string, byte[]>(File.ReadAllBytes);
			_gate = new LocalFileRefreshGate(forcedRecovery ?? TimeSpan.FromSeconds(15.0));
			_last = new BridgeStatusFileRead(null, null, LocalFileSignature.Missing, RefreshAttempted: false);
		}

		internal BridgeStatusFileRead ReadIfDue(DateTimeOffset now, bool dirty)
		{
			LocalFileSignature signature = _signatureReader(_path);
			if (!_gate.ShouldRefresh(signature, now, dirty, out var _))
			{
				return _last with
				{
					RefreshAttempted = false
				};
			}
			try
			{
				if (!signature.Exists || signature.Length <= 0 || signature.Length > 65536)
				{
					return Reject(signature);
				}
				byte[] array = _readBytes(_path);
				if (array.Length == 0 || array.Length > 65536)
				{
					return Reject(signature);
				}
				using JsonDocument jsonDocument = JsonDocument.Parse(array);
				if (jsonDocument.RootElement.ValueKind != JsonValueKind.Object)
				{
					return Reject(signature);
				}
				JsonElement rootElement = jsonDocument.RootElement;
				BridgeStatus status = new BridgeStatus
				{
					Status = GetString(rootElement, "status"),
					PairingCode = GetString(rootElement, "pairingCode"),
					SaveLine = GetString(rootElement, "saveLine"),
					LatestLine = GetString(rootElement, "latestLine"),
					Sequence = GetLong(rootElement, "sequence"),
					CanUnlink = GetBoolean(rootElement, "canUnlink")
				};
				if (!TryReadChallenge(rootElement, out string challenge))
				{
					challenge = null;
				}
				_gate.Accept(signature, now);
				_last = new BridgeStatusFileRead(status, challenge, signature, RefreshAttempted: true, ReadRuntimeSession(rootElement));
				return _last;
			}
			catch
			{
				return Reject(signature);
			}
		}

		private BridgeStatusFileRead Reject(LocalFileSignature signature)
		{
			_gate.RejectTransient();
			return new BridgeStatusFileRead(null, null, signature, RefreshAttempted: true);
		}

		private static LocalFileSignature ReadSignature(string path)
		{
			try
			{
				FileInfo fileInfo = new FileInfo(path);
				return fileInfo.Exists ? new LocalFileSignature(Exists: true, fileInfo.Length, fileInfo.LastWriteTimeUtc.Ticks) : LocalFileSignature.Missing;
			}
			catch
			{
				return LocalFileSignature.Missing;
			}
		}

		internal static bool TryReadChallenge(JsonElement root, out string? challenge)
		{
			challenge = null;
			if (!root.TryGetProperty("tileChallenge", out var value) || value.ValueKind != JsonValueKind.Object)
			{
				return false;
			}
			HashSet<string> hashSet = new HashSet<string>(StringComparer.Ordinal);
			foreach (JsonProperty item in value.EnumerateObject())
			{
				if (!ChallengeFields.Contains(item.Name) || !hashSet.Add(item.Name))
				{
					return false;
				}
			}
			if (!hashSet.SetEquals(ChallengeFields) || !value.TryGetProperty("version", out var value2) || value2.ValueKind != JsonValueKind.Number || value2.GetInt32() != 1 || !value.TryGetProperty("protocolVersion", out var value3) || value3.ValueKind != JsonValueKind.Number || value3.GetInt32() != 1 || !value.TryGetProperty("challenge", out var value4) || value4.ValueKind != JsonValueKind.String)
			{
				return false;
			}
			string text = value4.GetString();
			if (string.IsNullOrWhiteSpace(text) || text.Length > 128)
			{
				return false;
			}
			challenge = text;
			return true;
		}

		private static string? ReadRuntimeSession(JsonElement root)
		{
			string text = GetString(root, "runtimeSession")?.Trim();
			if (!string.IsNullOrEmpty(text) && text.Length <= 64)
			{
				return text;
			}
			return null;
		}

		private static string? GetString(JsonElement root, string name)
		{
			if (!root.TryGetProperty(name, out var value) || value.ValueKind != JsonValueKind.String)
			{
				return null;
			}
			return value.GetString();
		}

		private static long GetLong(JsonElement root, string name)
		{
			if (!root.TryGetProperty(name, out var value) || value.ValueKind != JsonValueKind.Number || !value.TryGetInt64(out var value2))
			{
				return 0L;
			}
			return value2;
		}

		private static bool? GetBoolean(JsonElement root, string name)
		{
			if (!root.TryGetProperty(name, out var value))
			{
				return null;
			}
			return value.ValueKind switch
			{
				JsonValueKind.True => true, 
				JsonValueKind.False => false, 
				_ => null, 
			};
		}
	}
	internal static class CurrentSaveSlotReader
	{
		private const string LoadManagerTypeName = "Il2CppScheduleOne.Persistence.LoadManager, Assembly-CSharp";

		private static readonly BindingFlags PublicInstanceOrStatic = BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public;

		private static readonly Regex SaveSlotPattern = new Regex("(?<![A-Za-z0-9_])SaveGame_[0-9]+(?![A-Za-z0-9_])", RegexOptions.IgnoreCase | RegexOptions.Compiled | RegexOptions.CultureInvariant);

		public static string? TryRead()
		{
			string source;
			return TryRead(out source);
		}

		public static string? TryRead(out string? source)
		{
			source = null;
			try
			{
				Type type = AppDomain.CurrentDomain.GetAssemblies().FirstOrDefault((Assembly assembly) => string.Equals(assembly.GetName().Name, "Assembly-CSharp", StringComparison.Ordinal))?.GetType("Il2CppScheduleOne.Persistence.LoadManager, Assembly-CSharp".Split(',')[0].Trim(), throwOnError: false);
				if (type == null)
				{
					source = "Assembly-CSharp LoadManager type unavailable";
					return null;
				}
				PropertyInfo propertyInfo = FindProperty(type, "Instance") ?? FindProperty(type, "instance");
				object obj = propertyInfo?.GetValue(null);
				if (obj == null)
				{
					source = ((propertyInfo == null) ? "LoadManager singleton property unavailable" : "LoadManager singleton instance unavailable");
					return null;
				}
				PropertyInfo propertyInfo2 = FindProperty(type, "ActiveSaveInfo");
				object obj2 = propertyInfo2?.GetValue(obj);
				if (obj2 == null && propertyInfo2 == null)
				{
					source = "LoadManager.ActiveSaveInfo property unavailable";
				}
				string text = ExtractSaveSlot(FindProperty(obj2?.GetType(), "SavePath")?.GetValue(obj2) as string);
				if (text != null)
				{
					source = "LoadManager.ActiveSaveInfo.SavePath";
					return text;
				}
				if (FindProperty(obj2?.GetType(), "SaveSlotNumber")?.GetValue(obj2) is int slotNumber)
				{
					text = FormatSaveSlotNumber(slotNumber);
					if (text != null)
					{
						source = "LoadManager.ActiveSaveInfo.SaveSlotNumber";
						return text;
					}
				}
				text = ExtractSaveSlot(FindProperty(type, "LoadedGameFolderPath")?.GetValue(obj) as string);
				if (text != null)
				{
					source = "LoadManager.LoadedGameFolderPath";
				}
				if (text == null && source == null)
				{
					source = "LoadManager identity properties contained no SaveGame_N value";
				}
				return text;
			}
			catch (Exception ex)
			{
				source = "LoadManager identity read failed: " + ex.GetType().Name;
			}
			return null;
		}

		public static bool TryReadActiveDirectory(out string? saveDirectory, out string? source)
		{
			saveDirectory = null;
			source = null;
			try
			{
				Type type = AppDomain.CurrentDomain.GetAssemblies().FirstOrDefault((Assembly assembly) => string.Equals(assembly.GetName().Name, "Assembly-CSharp", StringComparison.Ordinal))?.GetType("Il2CppScheduleOne.Persistence.LoadManager, Assembly-CSharp".Split(',')[0].Trim(), throwOnError: false);
				object obj = FindProperty(type, "Instance")?.GetValue(null) ?? FindProperty(type, "instance")?.GetValue(null);
				if (obj == null)
				{
					return false;
				}
				object obj2 = FindProperty(type, "ActiveSaveInfo")?.GetValue(obj);
				if (TryValidateActiveDirectory(FindProperty(obj2?.GetType(), "SavePath")?.GetValue(obj2) as string, out saveDirectory))
				{
					source = "active-save-path";
					return true;
				}
				if (TryValidateActiveDirectory(FindProperty(type, "LoadedGameFolderPath")?.GetValue(obj) as string, out saveDirectory))
				{
					source = "loaded-game-folder";
					return true;
				}
			}
			catch
			{
				source = "runtime-save-read-failed";
			}
			return false;
		}

		internal static bool TryValidateActiveDirectory(string? value, out string? saveDirectory)
		{
			saveDirectory = null;
			if (string.IsNullOrWhiteSpace(value))
			{
				return false;
			}
			try
			{
				string text = Path.GetFullPath(value).TrimEnd(Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar);
				if (!Directory.Exists(text) || ExtractSaveSlot(Path.GetFileName(text)) == null)
				{
					return false;
				}
				string directoryName = Path.GetDirectoryName(text);
				string path = (string.IsNullOrWhiteSpace(directoryName) ? null : Path.GetDirectoryName(directoryName));
				if (string.IsNullOrWhiteSpace(directoryName) || string.IsNullOrWhiteSpace(Path.GetFileName(directoryName)) || !string.Equals(Path.GetFileName(path), "Saves", StringComparison.OrdinalIgnoreCase))
				{
					return false;
				}
				saveDirectory = text;
				return true;
			}
			catch
			{
				return false;
			}
		}

		private static PropertyInfo? FindProperty(Type? type, string name)
		{
			Type type2 = type;
			while (type2 != null)
			{
				PropertyInfo property = type2.GetProperty(name, PublicInstanceOrStatic | BindingFlags.DeclaredOnly);
				if (property != null)
				{
					return property;
				}
				type2 = type2.BaseType;
			}
			return null;
		}

		public static string? FormatSaveSlotNumber(int slotNumber)
		{
			if (slotNumber < 1 || slotNumber > 99)
			{
				return null;
			}
			return $"SaveGame_{slotNumber}";
		}

		public static string? ExtractSaveSlot(string? value)
		{
			if (string.IsNullOrWhiteSpace(value))
			{
				return null;
			}
			Match match = SaveSlotPattern.Match(value);
			if (!match.Success)
			{
				return null;
			}
			return match.Value;
		}
	}
	internal enum DiscoveredMixBaseFilter
	{
		All,
		Weed,
		Meth,
		Cocaine,
		Shrooms
	}
	internal sealed class DiscoveredMixFilterState
	{
		public IReadOnlyList<DiscoveredMixBaseFilter> BaseFilters { get; private set; } = new DiscoveredMixBaseFilter[1];

		public IReadOnlyList<ObservedEffectFilter> EffectFilters { get; private set; } = new ObservedEffectFilter[1]
		{
			new ObservedEffectFilter
			{
				Key = "",
				Label = "All effects"
			}
		};

		public DiscoveredMixBaseFilter SelectedBase { get; private set; }

		public string SelectedEffect { get; private set; } = "";

		public bool PricedOnly { get; private set; }

		public void Refresh(IEnumerable<LocalMix> mixes)
		{
			List<LocalMix> mixes2 = mixes.ToList();
			BaseFilters = DiscoveredMixFilterCatalog.GetBaseFilters(mixes2);
			EffectFilters = ObservedEffects.GetFilters(mixes2);
			if (!BaseFilters.Contains(SelectedBase))
			{
				SelectedBase = DiscoveredMixBaseFilter.All;
			}
			if (!EffectFilters.Any((ObservedEffectFilter filter) => filter.Key == SelectedEffect))
			{
				SelectedEffect = "";
			}
		}

		public void SelectBase(DiscoveredMixBaseFilter filter)
		{
			if (BaseFilters.Contains(filter))
			{
				SelectedBase = filter;
			}
		}

		public void SelectEffect(string filterKey)
		{
			if (EffectFilters.Any((ObservedEffectFilter filter) => filter.Key == filterKey))
			{
				SelectedEffect = filterKey;
			}
		}

		public void SetPricedOnly(bool value)
		{
			PricedOnly = value;
		}

		public bool Matches(LocalMix mix, bool pricedAvailable)
		{
			if ((SelectedBase == DiscoveredMixBaseFilter.All || DiscoveredMixFilterCatalog.ClassifyBase(mix.Base) == SelectedBase) && ObservedEffects.Matches(mix, SelectedEffect))
			{
				return !PricedOnly || pricedAvailable;
			}
			return false;
		}
	}
	internal static class DiscoveredMixFilterCatalog
	{
		public static List<DiscoveredMixBaseFilter> GetBaseFilters(IEnumerable<LocalMix> mixes)
		{
			List<DiscoveredMixBaseFilter> list = new List<DiscoveredMixBaseFilter> { DiscoveredMixBaseFilter.All };
			foreach (LocalMix mix in mixes)
			{
				DiscoveredMixBaseFilter? discoveredMixBaseFilter = ClassifyBase(mix.Base);
				if (discoveredMixBaseFilter.HasValue && !list.Contains(discoveredMixBaseFilter.Value))
				{
					list.Add(discoveredMixBaseFilter.Value);
				}
			}
			return list;
		}

		public static DiscoveredMixBaseFilter? ClassifyBase(string baseId)
		{
			switch (baseId.Replace("_", "", StringComparison.Ordinal).ToLowerInvariant())
			{
			case "meth":
				return DiscoveredMixBaseFilter.Meth;
			case "cocaine":
				return DiscoveredMixBaseFilter.Cocaine;
			case "shrooms":
			case "shroom":
				return DiscoveredMixBaseFilter.Shrooms;
			case "ogkush":
			case "greencrack":
			case "sourdiesel":
			case "granddaddypurple":
				return DiscoveredMixBaseFilter.Weed;
			default:
				return null;
			}
		}

		public static string FilterLabel(DiscoveredMixBaseFilter filter)
		{
			return filter switch
			{
				DiscoveredMixBaseFilter.All => "All", 
				DiscoveredMixBaseFilter.Weed => "Weed", 
				DiscoveredMixBaseFilter.Meth => "Meth", 
				DiscoveredMixBaseFilter.Cocaine => "Cocaine", 
				DiscoveredMixBaseFilter.Shrooms => "Shrooms", 
				_ => filter.ToString(), 
			};
		}
	}
	public sealed class EconomicsRequestQueue<T>
	{
		public const int MaxConcurrent = 3;

		private readonly Queue<(string Key, T Value)> _pending = new Queue<(string, T)>();

		private readonly HashSet<string> _knownKeys = new HashSet<string>();

		private int _active;

		public int ActiveCount => _active;

		public int PendingCount => _pending.Count;

		public bool Enqueue(string key, T value)
		{
			if (!_knownKeys.Add(key))
			{
				return false;
			}
			_pending.Enqueue((key, value));
			return true;
		}

		public bool TryStart(out T value)
		{
			if (_active >= 3 || _pending.Count == 0)
			{
				value = default(T);
				return false;
			}
			(string, T) tuple = _pending.Dequeue();
			_active++;
			value = tuple.Item2;
			return true;
		}

		public void Complete(string key)
		{
			_knownKeys.Remove(key);
			if (_active > 0)
			{
				_active--;
			}
		}
	}
	public sealed class EconomicsResult
	{
		public string Key = "";

		public bool Available;

		public string Error = "Price unavailable";

		public double SellPrice;

		public double IngredientCost;

		public double BaseCostPerUnit;

		public double ProductionCost;

		public double Profit;

		public double ProfitMargin;
	}
	internal sealed class ObservedEconomicsRequest
	{
		public string Key = "";

		public bool Valid;

		public string Base = "";

		public List<string> Ingredients = new List<string>();

		public List<string> Effects = new List<string>();
	}
	internal static class EconomicsService
	{
		private const string Url = "https://hyland-helper-backend.vercel.app/api/calculator/mix-observed";

		private const int MaxResponseBytes = 65536;

		private static readonly HttpClient Http = new HttpClient
		{
			Timeout = TimeSpan.FromSeconds(15.0)
		};

		private static readonly object Gate = new object();

		private static readonly EconomicsRequestQueue<ObservedEconomicsRequest> Queue = new EconomicsRequestQueue<ObservedEconomicsRequest>();

		private static readonly Dictionary<string, EconomicsResult> Cache = new Dictionary<string, EconomicsResult>();

		private static readonly Queue<EconomicsResult> Completed = new Queue<EconomicsResult>();

		public static string CacheKey(LocalMix mix)
		{
			List<string> list;
			if (mix.Steps.Count != 0)
			{
				List<LocalMixStep> steps = mix.Steps;
				list = steps[steps.Count - 1].OutputEffects;
			}
			else
			{
				list = new List<string>();
			}
			List<string> observedFinalEffects = list;
			return JsonSerializer.Serialize(new
			{
				baseId = mix.Base,
				ingredients = mix.Ingredients,
				observedFinalEffects = observedFinalEffects
			});
		}

		public static bool TryGetCached(string key, out EconomicsResult result)
		{
			lock (Gate)
			{
				return Cache.TryGetValue(key, out result);
			}
		}

		public static void Enqueue(LocalMix mix)
		{
			ObservedEconomicsRequest observedEconomicsRequest = BuildRequest(mix);
			lock (Gate)
			{
				if (!Cache.ContainsKey(observedEconomicsRequest.Key))
				{
					if (!observedEconomicsRequest.Valid)
					{
						EconomicsResult economicsResult = Unavailable(observedEconomicsRequest.Key);
						Cache[observedEconomicsRequest.Key] = economicsResult;
						Completed.Enqueue(economicsResult);
					}
					else if (Queue.Enqueue(observedEconomicsRequest.Key, observedEconomicsRequest))
					{
						PumpLocked();
					}
				}
			}
		}

		public static bool TryTakeResult(out EconomicsResult result)
		{
			lock (Gate)
			{
				if (Completed.Count == 0)
				{
					result = null;
					return false;
				}
				result = Completed.Dequeue();
				return true;
			}
		}

		private static ObservedEconomicsRequest BuildRequest(LocalMix mix)
		{
			int num;
			if (mix.Steps.Count > 0)
			{
				List<LocalMixStep> ste