Decompiled source of WristHub Quest v4.2.1

Mods\WristHub.Core.dll

Decompiled 2 months ago
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.IO.Compression;
using System.Linq;
using System.Net;
using System.Net.Http;
using System.Net.Http.Headers;
using System.Numerics;
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.Nodes;
using System.Text.Json.Serialization;
using System.Threading;
using System.Threading.Tasks;
using BoneHub.Api;
using BoneHub.Avatars;
using BoneHub.Configuration;
using BoneHub.Downloads;
using BoneHub.Installation;
using BoneHub.Interaction;
using BoneHub.Models;
using BoneHub.Persistence;

[assembly: CompilationRelaxations(8)]
[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
[assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)]
[assembly: TargetFramework(".NETCoreApp,Version=v8.0", FrameworkDisplayName = ".NET 8.0")]
[assembly: AssemblyCompany("WristHub.Core")]
[assembly: AssemblyConfiguration("Release")]
[assembly: AssemblyFileVersion("4.2.1.0")]
[assembly: AssemblyInformationalVersion("4.2.1")]
[assembly: AssemblyProduct("WristHub.Core")]
[assembly: AssemblyTitle("WristHub.Core")]
[assembly: AssemblyVersion("4.2.1.0")]
[module: RefSafetyRules(11)]
namespace BoneHub
{
	public sealed class BoneHubService : IDisposable
	{
		private readonly IModIoClient _client;

		private readonly DownloadManager _downloads;

		private readonly BoneHubOptions _options;

		private readonly SearchCache _cache;

		private readonly JsonFileStore<BoneHubState> _stateStore;

		private readonly SemaphoreSlim _stateGate = new SemaphoreSlim(1, 1);

		private BoneHubState _state = new BoneHubState();

		public int FavoriteCount => _state.FavoriteModIds.Count;

		public IReadOnlyCollection<BoneHub.Persistence.InstalledModRecord> Installed => (IReadOnlyCollection<BoneHub.Persistence.InstalledModRecord>)(object)_state.InstalledMods.Values.ToArray();

		public BoneHubPreferences Preferences => _state.Preferences;

		public IReadOnlyList<string> RecentlyEquippedAvatarBarcodes => _state.RecentlyEquippedAvatarBarcodes.ToArray();

		public string LastEquippedAvatarBarcode
		{
			get
			{
				if (!string.IsNullOrWhiteSpace(_state.LastEquippedAvatarBarcode))
				{
					return _state.LastEquippedAvatarBarcode;
				}
				return _state.RecentlyEquippedAvatarBarcodes.FirstOrDefault() ?? string.Empty;
			}
		}

		public TrashedModRecord? LastTrashed => _state.TrashHistory.LastOrDefault();

		public IReadOnlyCollection<long> PendingDeletionModIds => (IReadOnlyCollection<long>)(object)_state.PendingDeletions.Select((PendingDeletionRecord item) => item.Mod.ModId).ToArray();

		public bool ModIoReady => _options.CanUseModIo;

		public string ApiKeyFilePath => _options.ApiKeyFilePath;

		public event Action<DownloadJob>? DownloadChanged;

		public event Action? InstalledChanged;

		public BoneHubService(IModIoClient client, DownloadManager downloads, BoneHubOptions options)
		{
			_client = client;
			_downloads = downloads;
			_options = options;
			_cache = new SearchCache(Path.Combine(options.DataDirectory, "cache", "search"));
			_stateStore = new JsonFileStore<BoneHubState>(Path.Combine(options.DataDirectory, "state.json"));
			_downloads.JobChanged += OnJobChanged;
		}

		public async Task InitializeAsync(CancellationToken cancellationToken = default(CancellationToken))
		{
			_state = await _stateStore.LoadAsync(cancellationToken).ConfigureAwait(continueOnCapturedContext: false);
			bool flag = false;
			if (_state.Preferences.MiniMapRange == MiniMapRange.TwentyFiveMeters)
			{
				_state.Preferences.MiniMapRange = MiniMapRange.ThirtyMeters;
				flag = true;
			}
			if (_state.Preferences.MotionPreferenceSchemaVersion < 1)
			{
				if (_state.Preferences.ComfortPreset != BoneHubComfortPreset.ReducedMotion && _state.Preferences.HologramEffects)
				{
					_state.Preferences.ReducedMotion = false;
				}
				_state.Preferences.MotionPreferenceSchemaVersion = 1;
				flag = true;
			}
			if (flag)
			{
				await _stateStore.SaveAsync(_state, cancellationToken).ConfigureAwait(continueOnCapturedContext: false);
			}
		}

		public async Task<ModIoPage<ModIoMod>> SearchAsync(ModSearchRequest request, CancellationToken cancellationToken = default(CancellationToken))
		{
			ModSearchRequest normalized = request with
			{
				Query = request.Query.Trim(),
				Limit = Math.Clamp(request.Limit, 1, 100),
				Offset = Math.Max(request.Offset, 0)
			};
			string key = $"{normalized.Query}|{normalized.Tag}|{normalized.Creator}|{normalized.Sort}|{normalized.Offset}|{normalized.Limit}|{_options.TargetPlatform}";
			ModIoPage<ModIoMod> modIoPage = await _cache.ReadAsync(key, TimeSpan.FromMinutes(_options.CacheMinutes), cancellationToken).ConfigureAwait(continueOnCapturedContext: false);
			if (modIoPage != null)
			{
				return modIoPage;
			}
			try
			{
				ModIoPage<ModIoMod> page = await _client.SearchAsync(normalized, cancellationToken).ConfigureAwait(continueOnCapturedContext: false);
				await _cache.WriteAsync(key, page, cancellationToken).ConfigureAwait(continueOnCapturedContext: false);
				return page;
			}
			catch when (!cancellationToken.IsCancellationRequested)
			{
				ModIoPage<ModIoMod> modIoPage2 = await _cache.ReadAsync(key, TimeSpan.FromDays(7.0), cancellationToken).ConfigureAwait(continueOnCapturedContext: false);
				if (modIoPage2 != null)
				{
					return modIoPage2;
				}
				throw;
			}
		}

		public DownloadJob Install(ModIoMod mod, CancellationToken cancellationToken = default(CancellationToken))
		{
			return _downloads.Enqueue(mod, cancellationToken);
		}

		public async Task<ModInstallPlan> GetInstallPlanAsync(ModIoMod root, CancellationToken cancellationToken = default(CancellationToken))
		{
			if (!ModIoReady)
			{
				throw new InvalidOperationException("WristHub's online avatar service is unavailable.");
			}
			List<ModIoMod> ordered = new List<ModIoMod>();
			HashSet<long> installed = new HashSet<long>();
			HashSet<long> visited = new HashSet<long>();
			HashSet<long> visiting = new HashSet<long>();
			await VisitAsync(root, isRoot: true).ConfigureAwait(continueOnCapturedContext: false);
			return new ModInstallPlan(root, ordered, installed.OrderBy((long id) => id).ToArray());
			async Task VisitAsync(ModIoMod mod, bool isRoot)
			{
				cancellationToken.ThrowIfCancellationRequested();
				if (!visited.Contains(mod.Id))
				{
					if (!visiting.Add(mod.Id))
					{
						throw new InvalidDataException($"mod.io reported a dependency cycle involving mod {mod.Id}.");
					}
					if (visited.Count + visiting.Count > 32)
					{
						throw new InvalidDataException("This dependency graph exceeds WristHub's 32-mod safety limit.");
					}
					foreach (ModIoDependency item in (await _client.GetDependenciesAsync(mod.Id, cancellationToken).ConfigureAwait(continueOnCapturedContext: false)).Data.Where((ModIoDependency item) => item.ModId > 0).DistinctBy((ModIoDependency item) => item.ModId))
					{
						if (_state.InstalledMods.ContainsKey(item.ModId))
						{
							installed.Add(item.ModId);
						}
						else
						{
							await VisitAsync(await _client.GetModAsync(item.ModId, cancellationToken).ConfigureAwait(continueOnCapturedContext: false), isRoot: false).ConfigureAwait(continueOnCapturedContext: false);
						}
					}
					visiting.Remove(mod.Id);
					visited.Add(mod.Id);
					if (isRoot || !_state.InstalledMods.ContainsKey(mod.Id))
					{
						ordered.Add(mod);
					}
					else
					{
						installed.Add(mod.Id);
					}
				}
			}
		}

		public async Task<IReadOnlyList<DownloadJob>> InstallWithDependenciesAsync(ModIoMod root, CancellationToken cancellationToken = default(CancellationToken))
		{
			return (await GetInstallPlanAsync(root, cancellationToken).ConfigureAwait(continueOnCapturedContext: false)).OrderedMods.Select((ModIoMod mod) => Install(mod, cancellationToken)).ToArray();
		}

		public bool CancelDownload(Guid jobId)
		{
			return _downloads.Cancel(jobId);
		}

		public bool IsFavorite(long modId)
		{
			return _state.FavoriteModIds.Contains(modId);
		}

		public bool IsInstalled(long modId)
		{
			return _state.InstalledMods.ContainsKey(modId);
		}

		public async Task ConfigureApiKeyAsync(string value, CancellationToken cancellationToken = default(CancellationToken))
		{
			string normalized = value?.Trim() ?? string.Empty;
			if (!BoneHubOptions.IsValidApiKey(normalized))
			{
				throw new InvalidDataException("The mod.io API key must contain exactly 32 letters or numbers.");
			}
			if (string.IsNullOrWhiteSpace(_options.ApiKeyFilePath))
			{
				throw new InvalidOperationException("WristHub's API-key file path is unavailable.");
			}
			string path = Path.GetFullPath(_options.ApiKeyFilePath);
			Directory.CreateDirectory(Path.GetDirectoryName(path) ?? throw new InvalidOperationException("The API-key file has no parent directory."));
			string temporary = path + ".tmp";
			await File.WriteAllTextAsync(temporary, normalized, cancellationToken).ConfigureAwait(continueOnCapturedContext: false);
			File.Move(temporary, path, overwrite: true);
			_options.ApiKey = normalized;
		}

		public async Task SetFavoriteAsync(long modId, bool favorite, CancellationToken cancellationToken = default(CancellationToken))
		{
			await _stateGate.WaitAsync(cancellationToken).ConfigureAwait(continueOnCapturedContext: false);
			try
			{
				if (favorite)
				{
					_state.FavoriteModIds.Add(modId);
				}
				else
				{
					_state.FavoriteModIds.Remove(modId);
				}
				await _stateStore.SaveAsync(_state, cancellationToken).ConfigureAwait(continueOnCapturedContext: false);
			}
			finally
			{
				_stateGate.Release();
			}
		}

		public async Task<IReadOnlyList<ModIoMod>> GetFavoriteModsAsync(CancellationToken cancellationToken = default(CancellationToken))
		{
			await _stateGate.WaitAsync(cancellationToken).ConfigureAwait(continueOnCapturedContext: false);
			long[] array;
			try
			{
				array = _state.FavoriteModIds.Take(50).ToArray();
			}
			finally
			{
				_stateGate.Release();
			}
			List<ModIoMod> results = new List<ModIoMod>();
			long[] array2 = array;
			foreach (long modId in array2)
			{
				cancellationToken.ThrowIfCancellationRequested();
				try
				{
					List<ModIoMod> list = results;
					list.Add(await _client.GetModAsync(modId, cancellationToken).ConfigureAwait(continueOnCapturedContext: false));
				}
				catch when (!cancellationToken.IsCancellationRequested)
				{
				}
			}
			return results.OrderBy((ModIoMod mod) => mod.Name).ToArray();
		}

		public Task<ModIoMod> GetModDetailsAsync(long modId, CancellationToken cancellationToken = default(CancellationToken))
		{
			return _client.GetModAsync(modId, cancellationToken);
		}

		public async Task MarkViewedAsync(long modId, CancellationToken cancellationToken = default(CancellationToken))
		{
			await _stateGate.WaitAsync(cancellationToken).ConfigureAwait(continueOnCapturedContext: false);
			try
			{
				_state.RecentlyViewedModIds.Remove(modId);
				_state.RecentlyViewedModIds.Insert(0, modId);
				if (_state.RecentlyViewedModIds.Count > 50)
				{
					_state.RecentlyViewedModIds.RemoveRange(50, _state.RecentlyViewedModIds.Count - 50);
				}
				await _stateStore.SaveAsync(_state, cancellationToken).ConfigureAwait(continueOnCapturedContext: false);
			}
			finally
			{
				_stateGate.Release();
			}
		}

		public async Task MarkAvatarEquippedAsync(string barcode, CancellationToken cancellationToken = default(CancellationToken))
		{
			string normalized = barcode?.Trim() ?? string.Empty;
			if (normalized.Length == 0)
			{
				return;
			}
			await _stateGate.WaitAsync(cancellationToken).ConfigureAwait(continueOnCapturedContext: false);
			try
			{
				_state.RecentlyEquippedAvatarBarcodes.RemoveAll((string item) => string.Equals(item, normalized, StringComparison.OrdinalIgnoreCase));
				_state.RecentlyEquippedAvatarBarcodes.Insert(0, normalized);
				_state.LastEquippedAvatarBarcode = normalized;
				if (_state.RecentlyEquippedAvatarBarcodes.Count > 50)
				{
					_state.RecentlyEquippedAvatarBarcodes.RemoveRange(50, _state.RecentlyEquippedAvatarBarcodes.Count - 50);
				}
				await _stateStore.SaveAsync(_state, cancellationToken).ConfigureAwait(continueOnCapturedContext: false);
			}
			finally
			{
				_stateGate.Release();
			}
		}

		public async Task ForgetAvatarBarcodesAsync(IEnumerable<string> barcodes, CancellationToken cancellationToken = default(CancellationToken))
		{
			HashSet<string> removed = (from item in barcodes
				where !string.IsNullOrWhiteSpace(item)
				select item.Trim()).ToHashSet<string>(StringComparer.OrdinalIgnoreCase);
			if (removed.Count == 0)
			{
				return;
			}
			await _stateGate.WaitAsync(cancellationToken).ConfigureAwait(continueOnCapturedContext: false);
			try
			{
				_state.RecentlyEquippedAvatarBarcodes.RemoveAll(removed.Contains);
				if (removed.Contains(_state.LastEquippedAvatarBarcode))
				{
					_state.LastEquippedAvatarBarcode = _state.RecentlyEquippedAvatarBarcodes.FirstOrDefault() ?? string.Empty;
				}
				await _stateStore.SaveAsync(_state, cancellationToken).ConfigureAwait(continueOnCapturedContext: false);
			}
			finally
			{
				_stateGate.Release();
			}
		}

		public async Task SavePreferencesAsync(CancellationToken cancellationToken = default(CancellationToken))
		{
			await _stateGate.WaitAsync(cancellationToken).ConfigureAwait(continueOnCapturedContext: false);
			try
			{
				if (!Enum.IsDefined(_state.Preferences.ComfortPreset))
				{
					_state.Preferences.ComfortPreset = BoneHubComfortPreset.Default;
				}
				if (!Enum.IsDefined(_state.Preferences.AvatarBrowserStartupMode))
				{
					_state.Preferences.AvatarBrowserStartupMode = BrowserStartupMode.AllInstalled;
				}
				if (!Enum.IsDefined(_state.Preferences.LastAvatarBrowseScope))
				{
					_state.Preferences.LastAvatarBrowseScope = AvatarBrowseScope.AllInstalled;
				}
				if (!Enum.IsDefined(_state.Preferences.MiniMapRange))
				{
					_state.Preferences.MiniMapRange = MiniMapRange.ThirtyMeters;
				}
				if (!Enum.IsDefined(_state.Preferences.MiniMapOrientation))
				{
					_state.Preferences.MiniMapOrientation = MiniMapOrientation.HeadingUp;
				}
				if (!Enum.IsDefined(_state.Preferences.HologramTheme))
				{
					_state.Preferences.HologramTheme = WristHubTheme.Cyan;
				}
				if (!Enum.IsDefined(_state.Preferences.WatchStyle))
				{
					_state.Preferences.WatchStyle = WristWatchStyle.ClassicCircle;
				}
				if (!Enum.IsDefined(_state.Preferences.WatchAttachment))
				{
					_state.Preferences.WatchAttachment = WristWatchAttachment.Adaptive;
				}
				if (!Enum.IsDefined(_state.Preferences.ClockOrientation))
				{
					_state.Preferences.ClockOrientation = WristClockOrientation.ArmFixed;
				}
				_state.Preferences.MotionPreferenceSchemaVersion = Math.Max(1, _state.Preferences.MotionPreferenceSchemaVersion);
				_state.Preferences.WatchScale = Math.Clamp(_state.Preferences.WatchScale, 0.65f, 1.6f);
				_state.Preferences.WatchBodyScale = Math.Clamp(_state.Preferences.WatchBodyScale, 0.75f, 1.4f);
				_state.Preferences.DwellSeconds = 1f;
				_state.Preferences.OutwardOffset = Math.Clamp(_state.Preferences.OutwardOffset, 0.015f, 0.06f);
				_state.Preferences.FingerOffset = Math.Clamp(_state.Preferences.FingerOffset, -0.055f, 0.025f);
				_state.Preferences.WatchForearmOffset = Math.Clamp(_state.Preferences.WatchForearmOffset, -0.08f, 0.03f);
				_state.Preferences.WatchSleepLift = Math.Clamp(_state.Preferences.WatchSleepLift, 0f, 0.03f);
				_state.Preferences.CarouselDistance = Math.Clamp(_state.Preferences.CarouselDistance, 0.45f, 0.95f);
				_state.Preferences.HologramScale = Math.Clamp(_state.Preferences.HologramScale, 0.7f, 1.4f);
				_state.Preferences.EquipZoneScale = Math.Clamp(_state.Preferences.EquipZoneScale, 0.7f, 1.6f);
				_state.Preferences.HologramBrightness = Math.Clamp(_state.Preferences.HologramBrightness, 0.35f, 1.5f);
				foreach (CompassCalibrationRecord compassCalibration in _state.Preferences.CompassCalibrations)
				{
					compassCalibration.NorthYawDegrees = CompassHeadingMath.Normalize(compassCalibration.NorthYawDegrees);
				}
				while (_state.Preferences.CompassCalibrations.Count > 64)
				{
					CompassCalibrationRecord item = _state.Preferences.CompassCalibrations.OrderBy((CompassCalibrationRecord record) => record.LastUsedUtc).First();
					_state.Preferences.CompassCalibrations.Remove(item);
				}
				if (_state.Preferences.SpawnerStateSchemaVersion < 2)
				{
					_state.Preferences.LegacySpawnableFavoritesBackup.Clear();
					_state.Preferences.LegacySpawnableFavoritesBackup.AddRange(_state.Preferences.FavoriteSpawnableBarcodes);
					_state.Preferences.LegacySpawnableRecentsBackup.Clear();
					_state.Preferences.LegacySpawnableRecentsBackup.AddRange(_state.Preferences.RecentSpawnableBarcodes);
					_state.Preferences.LastSpawnableMode = (string.IsNullOrWhiteSpace(_state.Preferences.LastSpawnableMode) ? "Recent" : _state.Preferences.LastSpawnableMode);
					BoneHubPreferences preferences = _state.Preferences;
					if (preferences.LastSpawnableGroup == null)
					{
						preferences.LastSpawnableGroup = string.Empty;
					}
					_state.Preferences.SpawnerStateSchemaVersion = 2;
				}
				TrimDistinct(_state.Preferences.FavoriteSpawnableBarcodes, 128);
				TrimDistinct(_state.Preferences.RecentSpawnableBarcodes, 64);
				TrimDistinct(_state.Preferences.LegacySpawnableFavoritesBackup, 128);
				TrimDistinct(_state.Preferences.LegacySpawnableRecentsBackup, 64);
				TrimDistinct(_state.Preferences.PinnedWristAppIds, 8);
				TrimDistinct(_state.Preferences.OpenedWristAppIds, 256);
				if (!Enum.IsDefined(_state.Preferences.WristAppLibraryTab))
				{
					_state.Preferences.WristAppLibraryTab = WristAppLibraryTab.Installed;
				}
				_state.Preferences.WristAppSearchQuery = (_state.Preferences.WristAppSearchQuery ?? string.Empty).Trim();
				if (_state.Preferences.WristAppSearchQuery.Length > 80)
				{
					_state.Preferences.WristAppSearchQuery = _state.Preferences.WristAppSearchQuery.Substring(0, 80);
				}
				await _stateStore.SaveAsync(_state, cancellationToken).ConfigureAwait(continueOnCapturedContext: false);
			}
			finally
			{
				_stateGate.Release();
			}
		}

		private static void TrimDistinct(List<string> values, int maximum)
		{
			HashSet<string> hashSet = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
			for (int num = values.Count - 1; num >= 0; num--)
			{
				string text = values[num]?.Trim() ?? string.Empty;
				if (text.Length == 0 || !hashSet.Add(text))
				{
					values.RemoveAt(num);
				}
				else
				{
					values[num] = text;
				}
			}
			if (values.Count > maximum)
			{
				values.RemoveRange(maximum, values.Count - maximum);
			}
		}

		public InstalledModHealth VerifyInstalled(long modId)
		{
			if (!_state.InstalledMods.TryGetValue(modId, out BoneHub.Persistence.InstalledModRecord value))
			{
				return new InstalledModHealth(Healthy: false, new string[1] { "WristHub has no installation record for this mod." });
			}
			List<string> list = new List<string>();
			foreach (string installedDirectory in value.InstalledDirectories)
			{
				string text;
				try
				{
					text = ValidateInstalledPath(installedDirectory);
				}
				catch (Exception ex)
				{
					list.Add(ex.Message);
					continue;
				}
				if (!Directory.Exists(text))
				{
					list.Add("Missing folder: " + Path.GetFileName(text));
				}
				else if (!PalletManifestLocator.HasManifest(text))
				{
					list.Add("Missing pallet manifest: " + Path.GetFileName(text));
				}
			}
			return new InstalledModHealth(list.Count == 0, list);
		}

		public async Task<ModUpdateInfo> CheckForUpdateAsync(long modId, CancellationToken cancellationToken = default(CancellationToken))
		{
			if (!_state.InstalledMods.TryGetValue(modId, out BoneHub.Persistence.InstalledModRecord installed))
			{
				throw new InvalidOperationException("This mod is not installed.");
			}
			ModIoMod mod = await _client.GetModAsync(modId, cancellationToken).ConfigureAwait(continueOnCapturedContext: false);
			ModIoFile modIoFile = await ModIoFileResolver.ResolveAsync(_client, mod, _options, cancellationToken).ConfigureAwait(continueOnCapturedContext: false);
			return new ModUpdateInfo(mod, modIoFile.Id != installed.FileId, installed.FileId, modIoFile.Id);
		}

		public async Task<IReadOnlyList<ModUpdateInfo>> CheckAllUpdatesAsync(CancellationToken cancellationToken = default(CancellationToken))
		{
			List<ModUpdateInfo> results = new List<ModUpdateInfo>();
			foreach (BoneHub.Persistence.InstalledModRecord item in _state.InstalledMods.Values.OrderBy((BoneHub.Persistence.InstalledModRecord item) => item.Name))
			{
				cancellationToken.ThrowIfCancellationRequested();
				List<ModUpdateInfo> list = results;
				list.Add(await CheckForUpdateAsync(item.ModId, cancellationToken).ConfigureAwait(continueOnCapturedContext: false));
			}
			return results;
		}

		public async Task<DownloadJob> RepairOrUpdateAsync(long modId, CancellationToken cancellationToken = default(CancellationToken))
		{
			return Install((await CheckForUpdateAsync(modId, cancellationToken).ConfigureAwait(continueOnCapturedContext: false)).Mod, cancellationToken);
		}

		public async Task<int> RepairSharingRegistrationsAsync(CancellationToken cancellationToken = default(CancellationToken))
		{
			BoneHub.Persistence.InstalledModRecord[] array = _state.InstalledMods.Values.Where((BoneHub.Persistence.InstalledModRecord installedModRecord) => installedModRecord.ModId > 0 && installedModRecord.InstalledDirectories.Any(Directory.Exists) && (installedModRecord.Sharing == null || installedModRecord.Sharing.RegistrationVersion < 3 || installedModRecord.Sharing.SidecarManifestPaths.Count == 0 || installedModRecord.Sharing.SidecarManifestPaths.Any((string path) => !File.Exists(path)))).ToArray();
			int repaired = 0;
			BoneHub.Persistence.InstalledModRecord[] array2 = array;
			foreach (BoneHub.Persistence.InstalledModRecord record in array2)
			{
				cancellationToken.ThrowIfCancellationRequested();
				try
				{
					ModIoMod mod = await _client.GetModAsync(record.ModId, cancellationToken).ConfigureAwait(continueOnCapturedContext: false);
					ModIoFile modIoFile = ((record.FileId <= 0) ? (await ModIoFileResolver.ResolveAsync(_client, mod, _options, cancellationToken).ConfigureAwait(continueOnCapturedContext: false)) : (await _client.GetFileAsync(record.ModId, record.FileId, cancellationToken).ConfigureAwait(continueOnCapturedContext: false)));
					ModIoFile file = modIoFile;
					ModIoSharingMetadata sharing = await NetworkerRegistrationWriter.WriteAsync(mod, file, await ModIoFileResolver.ResolvePlatformFilesAsync(_client, record.ModId, cancellationToken).ConfigureAwait(continueOnCapturedContext: false), record.InstalledDirectories, _options.ModsDirectory, cancellationToken).ConfigureAwait(continueOnCapturedContext: false);
					await _stateGate.WaitAsync(cancellationToken).ConfigureAwait(continueOnCapturedContext: false);
					try
					{
						if (_state.InstalledMods.TryGetValue(record.ModId, out BoneHub.Persistence.InstalledModRecord value))
						{
							value.Sharing = sharing;
						}
						await _stateStore.SaveAsync(_state, cancellationToken).ConfigureAwait(continueOnCapturedContext: false);
					}
					finally
					{
						_stateGate.Release();
					}
					repaired++;
				}
				catch when (!cancellationToken.IsCancellationRequested)
				{
				}
			}
			if (repaired > 0)
			{
				this.InstalledChanged?.Invoke();
			}
			return repaired;
		}

		public Task<TrashedModRecord> UninstallAsync(long modId, CancellationToken cancellationToken = default(CancellationToken))
		{
			return UninstallCoreAsync(modId, null, null, wasSubscribed: false, cancellationToken);
		}

		public Task<TrashedModRecord> UninstallAsync(long modId, string? localName, IReadOnlyList<string>? localDirectories, CancellationToken cancellationToken = default(CancellationToken))
		{
			return UninstallCoreAsync(modId, localName, localDirectories, wasSubscribed: false, cancellationToken);
		}

		public Task<TrashedModRecord> UninstallSubscribedAsync(long modId, string? localName, IReadOnlyList<string>? localDirectories, bool wasSubscribed, CancellationToken cancellationToken = default(CancellationToken))
		{
			return UninstallCoreAsync(modId, localName, localDirectories, wasSubscribed, cancellationToken);
		}

		public async Task<PendingDeletionRecord> QueueDeletionAsync(long modId, string? localName, IReadOnlyList<string>? localDirectories, bool wasSubscribed, IEnumerable<string>? avatarBarcodes, CancellationToken cancellationToken = default(CancellationToken))
		{
			await _stateGate.WaitAsync(cancellationToken).ConfigureAwait(continueOnCapturedContext: false);
			PendingDeletionRecord queued;
			try
			{
				PendingDeletionRecord pendingDeletionRecord = _state.PendingDeletions.FirstOrDefault((PendingDeletionRecord item) => item.Mod.ModId == modId);
				if (pendingDeletionRecord != null)
				{
					return pendingDeletionRecord;
				}
				_state.InstalledMods.TryGetValue(modId, out BoneHub.Persistence.InstalledModRecord value);
				IEnumerable<string> enumerable = localDirectories;
				object obj = enumerable;
				if (obj == null)
				{
					enumerable = value?.InstalledDirectories;
					obj = enumerable ?? Array.Empty<string>();
				}
				List<string> list = ((IEnumerable<string>)obj).Where((string path) => !string.IsNullOrWhiteSpace(path)).Select(ValidateInstalledPath).Where(Directory.Exists)
					.Distinct<string>(StringComparer.OrdinalIgnoreCase)
					.ToList();
				if (list.Count == 0)
				{
					throw new DirectoryNotFoundException("No installed avatar-pack folders were found to queue.");
				}
				if (value == null)
				{
					value = new BoneHub.Persistence.InstalledModRecord
					{
						ModId = modId,
						Name = (string.IsNullOrWhiteSpace(localName) ? "Local avatar pack" : localName.Trim()),
						InstalledDirectories = list,
						InstalledAt = DateTimeOffset.UtcNow
					};
				}
				List<string> sidecarFiles = (from path in (value.Sharing?.SidecarManifestPaths ?? new List<string>()).Concat(FindRegistrationSidecars(list))
					where !string.IsNullOrWhiteSpace(path)
					select path).Select(ValidateInstalledFile).Where(File.Exists).Distinct<string>(StringComparer.OrdinalIgnoreCase)
					.ToList();
				List<string> barcodes = (from text in avatarBarcodes ?? Array.Empty<string>()
					where !string.IsNullOrWhiteSpace(text)
					select text.Trim()).Distinct<string>(StringComparer.OrdinalIgnoreCase).ToList();
				queued = new PendingDeletionRecord
				{
					Mod = value,
					Directories = list,
					SidecarFiles = sidecarFiles,
					AvatarBarcodes = barcodes,
					WasSubscribed = wasSubscribed
				};
				_state.PendingDeletions.Add(queued);
				_state.InstalledMods.Remove(modId);
				_state.RecentlyEquippedAvatarBarcodes.RemoveAll((string value2) => barcodes.Contains<string>(value2, StringComparer.OrdinalIgnoreCase));
				if (barcodes.Contains<string>(_state.LastEquippedAvatarBarcode, StringComparer.OrdinalIgnoreCase))
				{
					_state.LastEquippedAvatarBarcode = _state.RecentlyEquippedAvatarBarcodes.FirstOrDefault() ?? string.Empty;
				}
				await _stateStore.SaveAsync(_state, cancellationToken).ConfigureAwait(continueOnCapturedContext: false);
			}
			finally
			{
				_stateGate.Release();
			}
			this.InstalledChanged?.Invoke();
			return queued;
		}

		public async Task<PendingDeletionProcessResult> ProcessPendingDeletionsAsync(CancellationToken cancellationToken = default(CancellationToken))
		{
			int completed = 0;
			List<string> problems = new List<string>();
			await _stateGate.WaitAsync(cancellationToken).ConfigureAwait(continueOnCapturedContext: false);
			try
			{
				PendingDeletionRecord[] array = _state.PendingDeletions.ToArray();
				foreach (PendingDeletionRecord pending in array)
				{
					cancellationToken.ThrowIfCancellationRequested();
					string text = Path.Combine(Path.GetDirectoryName(Path.GetFullPath(_options.ModsDirectory).TrimEnd(Path.DirectorySeparatorChar)) ?? throw new InvalidOperationException("Mods directory has no parent."), ".wristhub-trash", $"{pending.Mod.ModId}-{DateTimeOffset.UtcNow:yyyyMMddHHmmss}-{Guid.NewGuid():N}");
					Directory.CreateDirectory(text);
					List<TrashedDirectoryRecord> list = new List<TrashedDirectoryRecord>();
					List<TrashedFileRecord> list2 = new List<TrashedFileRecord>();
					try
					{
						for (int j = 0; j < pending.Directories.Count; j++)
						{
							string text2 = ValidateInstalledPath(pending.Directories[j]);
							if (Directory.Exists(text2))
							{
								string text3 = Path.Combine(text, $"{j:D2}-{Path.GetFileName(text2)}");
								Directory.Move(text2, text3);
								list.Add(new TrashedDirectoryRecord
								{
									OriginalPath = text2,
									TrashPath = text3
								});
							}
						}
						for (int k = 0; k < pending.SidecarFiles.Count; k++)
						{
							string text4 = ValidateInstalledFile(pending.SidecarFiles[k]);
							if (File.Exists(text4))
							{
								string text5 = Path.Combine(text, $"manifest-{k:D2}-{Path.GetFileName(text4)}");
								File.Move(text4, text5);
								list2.Add(new TrashedFileRecord
								{
									OriginalPath = text4,
									TrashPath = text5
								});
							}
						}
						if (list.Count > 0 || list2.Count > 0)
						{
							_state.TrashHistory.Add(new TrashedModRecord
							{
								Mod = pending.Mod,
								Directories = list,
								Files = list2,
								WasSubscribed = pending.WasSubscribed
							});
							if (_state.TrashHistory.Count > 20)
							{
								_state.TrashHistory.RemoveRange(0, _state.TrashHistory.Count - 20);
							}
						}
						else if (Directory.Exists(text))
						{
							Directory.Delete(text, recursive: false);
						}
						_state.PendingDeletions.Remove(pending);
						_state.InstalledMods.Remove(pending.Mod.ModId);
						_state.RecentlyEquippedAvatarBarcodes.RemoveAll((string value) => pending.AvatarBarcodes.Contains<string>(value, StringComparer.OrdinalIgnoreCase));
						if (pending.AvatarBarcodes.Contains<string>(_state.LastEquippedAvatarBarcode, StringComparer.OrdinalIgnoreCase))
						{
							_state.LastEquippedAvatarBarcode = _state.RecentlyEquippedAvatarBarcodes.FirstOrDefault() ?? string.Empty;
						}
						completed++;
					}
					catch (Exception ex)
					{
						for (int num = list.Count - 1; num >= 0; num--)
						{
							TrashedDirectoryRecord trashedDirectoryRecord = list[num];
							if (Directory.Exists(trashedDirectoryRecord.TrashPath) && !Directory.Exists(trashedDirectoryRecord.OriginalPath))
							{
								Directory.Move(trashedDirectoryRecord.TrashPath, trashedDirectoryRecord.OriginalPath);
							}
						}
						for (int num2 = list2.Count - 1; num2 >= 0; num2--)
						{
							TrashedFileRecord trashedFileRecord = list2[num2];
							if (File.Exists(trashedFileRecord.TrashPath) && !File.Exists(trashedFileRecord.OriginalPath))
							{
								File.Move(trashedFileRecord.TrashPath, trashedFileRecord.OriginalPath);
							}
						}
						try
						{
							if (Directory.Exists(text) && !Directory.EnumerateFileSystemEntries(text).Any())
							{
								Directory.Delete(text);
							}
						}
						catch
						{
						}
						problems.Add(pending.Mod.Name + ": " + ex.Message);
					}
				}
				await _stateStore.SaveAsync(_state, cancellationToken).ConfigureAwait(continueOnCapturedContext: false);
			}
			finally
			{
				_stateGate.Release();
			}
			if (completed > 0)
			{
				this.InstalledChanged?.Invoke();
			}
			return new PendingDeletionProcessResult(completed, _state.PendingDeletions.Count, problems);
		}

		private async Task<TrashedModRecord> UninstallCoreAsync(long modId, string? localName, IReadOnlyList<string>? localDirectories, bool wasSubscribed, CancellationToken cancellationToken)
		{
			await _stateGate.WaitAsync(cancellationToken).ConfigureAwait(continueOnCapturedContext: false);
			try
			{
				BoneHub.Persistence.InstalledModRecord installed;
				bool wasTracked = _state.InstalledMods.TryGetValue(modId, out installed);
				if (!wasTracked)
				{
					List<string> list = (localDirectories ?? Array.Empty<string>()).Where((string path) => !string.IsNullOrWhiteSpace(path)).Distinct<string>(StringComparer.OrdinalIgnoreCase).ToList();
					if (list.Count == 0)
					{
						throw new InvalidOperationException("WristHub could not identify this avatar pack's local folder.");
					}
					installed = new BoneHub.Persistence.InstalledModRecord
					{
						ModId = modId,
						Name = (string.IsNullOrWhiteSpace(localName) ? "Local avatar pack" : localName.Trim()),
						InstalledDirectories = list,
						InstalledAt = DateTimeOffset.UtcNow
					};
				}
				BoneHub.Persistence.InstalledModRecord installedModRecord = installed ?? throw new InvalidOperationException("The avatar pack record is unavailable.");
				string text = Path.Combine(Path.GetDirectoryName(Path.GetFullPath(_options.ModsDirectory).TrimEnd(Path.DirectorySeparatorChar)) ?? throw new InvalidOperationException("Mods directory has no parent."), ".wristhub-trash", $"{modId}-{DateTimeOffset.UtcNow:yyyyMMddHHmmss}-{Guid.NewGuid():N}");
				Directory.CreateDirectory(text);
				List<TrashedDirectoryRecord> moved = new List<TrashedDirectoryRecord>();
				List<TrashedFileRecord> movedFiles = new List<TrashedFileRecord>();
				TrashedModRecord stateRecord = null;
				try
				{
					for (int num = 0; num < installedModRecord.InstalledDirectories.Count; num++)
					{
						cancellationToken.ThrowIfCancellationRequested();
						string text2 = ValidateInstalledPath(installedModRecord.InstalledDirectories[num]);
						if (Directory.Exists(text2))
						{
							string text3 = Path.Combine(text, $"{num:D2}-{Path.GetFileName(text2)}");
							Directory.Move(text2, text3);
							moved.Add(new TrashedDirectoryRecord
							{
								OriginalPath = text2,
								TrashPath = text3
							});
						}
					}
					string[] array = (installedModRecord.Sharing?.SidecarManifestPaths ?? new List<string>()).Concat(FindRegistrationSidecars(installedModRecord.InstalledDirectories)).Distinct<string>(StringComparer.OrdinalIgnoreCase).ToArray();
					for (int num2 = 0; num2 < array.Length; num2++)
					{
						cancellationToken.ThrowIfCancellationRequested();
						string text4 = ValidateInstalledFile(array[num2]);
						if (File.Exists(text4))
						{
							string text5 = Path.Combine(text, $"manifest-{num2:D2}-{Path.GetFileName(text4)}");
							File.Move(text4, text5);
							movedFiles.Add(new TrashedFileRecord
							{
								OriginalPath = text4,
								TrashPath = text5
							});
						}
					}
					if (moved.Count == 0)
					{
						throw new DirectoryNotFoundException("No installed avatar-pack folders were found to remove.");
					}
					TrashedModRecord record = new TrashedModRecord
					{
						Mod = installedModRecord,
						Directories = moved,
						Files = movedFiles,
						WasSubscribed = wasSubscribed
					};
					stateRecord = record;
					_state.InstalledMods.Remove(modId);
					_state.TrashHistory.Add(record);
					if (_state.TrashHistory.Count > 20)
					{
						_state.TrashHistory.RemoveRange(0, _state.TrashHistory.Count - 20);
					}
					await _stateStore.SaveAsync(_state, cancellationToken).ConfigureAwait(continueOnCapturedContext: false);
					this.InstalledChanged?.Invoke();
					return record;
				}
				catch
				{
					if (wasTracked)
					{
						_state.InstalledMods[modId] = installed;
					}
					else
					{
						_state.InstalledMods.Remove(modId);
					}
					if (stateRecord != null)
					{
						_state.TrashHistory.Remove(stateRecord);
					}
					for (int num3 = moved.Count - 1; num3 >= 0; num3--)
					{
						TrashedDirectoryRecord trashedDirectoryRecord = moved[num3];
						if (Directory.Exists(trashedDirectoryRecord.TrashPath) && !Directory.Exists(trashedDirectoryRecord.OriginalPath))
						{
							Directory.Move(trashedDirectoryRecord.TrashPath, trashedDirectoryRecord.OriginalPath);
						}
					}
					for (int num4 = movedFiles.Count - 1; num4 >= 0; num4--)
					{
						TrashedFileRecord trashedFileRecord = movedFiles[num4];
						if (File.Exists(trashedFileRecord.TrashPath) && !File.Exists(trashedFileRecord.OriginalPath))
						{
							File.Move(trashedFileRecord.TrashPath, trashedFileRecord.OriginalPath);
						}
					}
					throw;
				}
			}
			finally
			{
				_stateGate.Release();
			}
		}

		public async Task<BoneHub.Persistence.InstalledModRecord> UndoLastUninstallAsync(CancellationToken cancellationToken = default(CancellationToken))
		{
			await _stateGate.WaitAsync(cancellationToken).ConfigureAwait(continueOnCapturedContext: false);
			try
			{
				TrashedModRecord record = _state.TrashHistory.LastOrDefault() ?? throw new InvalidOperationException("There is no WristHub uninstall to undo.");
				if (_state.InstalledMods.ContainsKey(record.Mod.ModId))
				{
					throw new InvalidOperationException("That mod is already installed again.");
				}
				List<TrashedDirectoryRecord> restored = new List<TrashedDirectoryRecord>();
				List<TrashedFileRecord> restoredFiles = new List<TrashedFileRecord>();
				try
				{
					foreach (TrashedDirectoryRecord directory in record.Directories)
					{
						cancellationToken.ThrowIfCancellationRequested();
						string text = ValidateInstalledPath(directory.OriginalPath);
						string text2 = ValidateTrashPath(directory.TrashPath);
						if (Directory.Exists(text))
						{
							throw new IOException("Cannot restore " + Path.GetFileName(text) + " because that folder already exists.");
						}
						if (!Directory.Exists(text2))
						{
							throw new DirectoryNotFoundException("Trash data is missing for " + record.Mod.Name + ".");
						}
						Directory.Move(text2, text);
						restored.Add(new TrashedDirectoryRecord
						{
							OriginalPath = text,
							TrashPath = text2
						});
					}
					foreach (TrashedFileRecord file in record.Files)
					{
						cancellationToken.ThrowIfCancellationRequested();
						string text3 = ValidateInstalledFile(file.OriginalPath);
						string text4 = ValidateTrashPath(file.TrashPath);
						if (File.Exists(text3))
						{
							throw new IOException("Cannot restore " + Path.GetFileName(text3) + " because it already exists.");
						}
						if (!File.Exists(text4))
						{
							throw new FileNotFoundException("Trash sidecar data is missing.", text4);
						}
						File.Move(text4, text3);
						restoredFiles.Add(new TrashedFileRecord
						{
							OriginalPath = text3,
							TrashPath = text4
						});
					}
					_state.InstalledMods[record.Mod.ModId] = record.Mod;
					_state.TrashHistory.Remove(record);
					await _stateStore.SaveAsync(_state, cancellationToken).ConfigureAwait(continueOnCapturedContext: false);
					this.InstalledChanged?.Invoke();
					return record.Mod;
				}
				catch
				{
					_state.InstalledMods.Remove(record.Mod.ModId);
					if (!_state.TrashHistory.Contains(record))
					{
						_state.TrashHistory.Add(record);
					}
					for (int num = restored.Count - 1; num >= 0; num--)
					{
						TrashedDirectoryRecord trashedDirectoryRecord = restored[num];
						if (Directory.Exists(trashedDirectoryRecord.OriginalPath) && !Directory.Exists(trashedDirectoryRecord.TrashPath))
						{
							Directory.Move(trashedDirectoryRecord.OriginalPath, trashedDirectoryRecord.TrashPath);
						}
					}
					for (int num2 = restoredFiles.Count - 1; num2 >= 0; num2--)
					{
						TrashedFileRecord trashedFileRecord = restoredFiles[num2];
						if (File.Exists(trashedFileRecord.OriginalPath) && !File.Exists(trashedFileRecord.TrashPath))
						{
							File.Move(trashedFileRecord.OriginalPath, trashedFileRecord.TrashPath);
						}
					}
					throw;
				}
			}
			finally
			{
				_stateGate.Release();
			}
		}

		private void OnJobChanged(DownloadJob job)
		{
			if (job.State == DownloadState.Completed && job.Result != null)
			{
				PersistCompletedJobAsync(job, job.Result);
			}
			else
			{
				this.DownloadChanged?.Invoke(job);
			}
		}

		private async Task PersistCompletedJobAsync(DownloadJob job, BoneHub.Downloads.InstalledModRecord result)
		{
			await _stateGate.WaitAsync().ConfigureAwait(continueOnCapturedContext: false);
			try
			{
				_state.InstalledMods[result.ModId] = new BoneHub.Persistence.InstalledModRecord
				{
					ModId = result.ModId,
					FileId = result.FileId,
					Name = result.Name,
					Version = result.Version,
					Md5 = result.Md5,
					InstalledDirectories = result.InstalledDirectories.ToList(),
					Sharing = result.Sharing,
					InstalledAt = DateTimeOffset.UtcNow
				};
				await _stateStore.SaveAsync(_state).ConfigureAwait(continueOnCapturedContext: false);
				this.DownloadChanged?.Invoke(job);
				this.InstalledChanged?.Invoke();
			}
			finally
			{
				_stateGate.Release();
			}
		}

		private string ValidateInstalledPath(string path)
		{
			string text = Path.GetFullPath(_options.ModsDirectory).TrimEnd(Path.DirectorySeparatorChar);
			string text2 = Path.GetFullPath(path).TrimEnd(Path.DirectorySeparatorChar);
			if (!text2.StartsWith(text + Path.DirectorySeparatorChar, StringComparison.OrdinalIgnoreCase) || string.Equals(text2, text, StringComparison.OrdinalIgnoreCase))
			{
				throw new InvalidDataException("An installed-mod path falls outside BONELAB's Mods directory.");
			}
			return text2;
		}

		private string ValidateInstalledFile(string path)
		{
			string text = Path.GetFullPath(_options.ModsDirectory).TrimEnd(Path.DirectorySeparatorChar);
			string fullPath = Path.GetFullPath(path);
			if (!fullPath.StartsWith(text + Path.DirectorySeparatorChar, StringComparison.OrdinalIgnoreCase) || !string.Equals(Path.GetExtension(fullPath), ".manifest", StringComparison.OrdinalIgnoreCase))
			{
				throw new InvalidDataException("A mod registration sidecar falls outside BONELAB's Mods directory.");
			}
			return fullPath;
		}

		private IReadOnlyList<string> FindRegistrationSidecars(IEnumerable<string> packageDirectories)
		{
			string[] source = packageDirectories.Select(Path.GetFullPath).ToArray();
			List<string> list = new List<string>();
			if (!Directory.Exists(_options.ModsDirectory))
			{
				return list;
			}
			foreach (string item in Directory.EnumerateFiles(_options.ModsDirectory, "*.manifest", SearchOption.TopDirectoryOnly))
			{
				try
				{
					using JsonDocument jsonDocument = JsonDocument.Parse(File.ReadAllText(item));
					JsonElement rootElement = jsonDocument.RootElement;
					string propertyName = rootElement.GetProperty("root").GetProperty("ref").GetString() ?? "1";
					string text = rootElement.GetProperty("objects").GetProperty(propertyName).GetProperty("palletPath")
						.GetString();
					if (!string.IsNullOrWhiteSpace(text))
					{
						string fullPallet = Path.GetFullPath(text);
						if (source.Any((string package) => fullPallet.StartsWith(package.TrimEnd(Path.DirectorySeparatorChar) + Path.DirectorySeparatorChar, StringComparison.OrdinalIgnoreCase)))
						{
							list.Add(item);
						}
					}
				}
				catch
				{
				}
			}
			return list;
		}

		private string ValidateTrashPath(string path)
		{
			string? path2 = Path.GetDirectoryName(Path.GetFullPath(_options.ModsDirectory).TrimEnd(Path.DirectorySeparatorChar)) ?? throw new InvalidOperationException("Mods directory has no parent.");
			string text = Path.GetFullPath(Path.Combine(path2, ".wristhub-trash")).TrimEnd(Path.DirectorySeparatorChar);
			string text2 = Path.GetFullPath(Path.Combine(path2, ".bonehub-trash")).TrimEnd(Path.DirectorySeparatorChar);
			string text3 = Path.GetFullPath(path).TrimEnd(Path.DirectorySeparatorChar);
			bool num = text3.StartsWith(text + Path.DirectorySeparatorChar, StringComparison.OrdinalIgnoreCase) && !string.Equals(text3, text, StringComparison.OrdinalIgnoreCase);
			bool flag = text3.StartsWith(text2 + Path.DirectorySeparatorChar, StringComparison.OrdinalIgnoreCase) && !string.Equals(text3, text2, StringComparison.OrdinalIgnoreCase);
			if (!num && !flag)
			{
				throw new InvalidDataException("A restore path falls outside WristHub Trash.");
			}
			return text3;
		}

		public void Dispose()
		{
			_downloads.JobChanged -= OnJobChanged;
			_downloads.Dispose();
			(_client as IDisposable)?.Dispose();
			_stateGate.Dispose();
		}
	}
}
namespace BoneHub.Persistence
{
	public sealed class BoneHubState
	{
		public HashSet<long> FavoriteModIds { get; init; } = new HashSet<long>();

		public List<long> RecentlyViewedModIds { get; init; } = new List<long>();

		public List<string> RecentlyEquippedAvatarBarcodes { get; init; } = new List<string>();

		public string LastEquippedAvatarBarcode { get; set; } = string.Empty;

		public Dictionary<long, InstalledModRecord> InstalledMods { get; init; } = new Dictionary<long, InstalledModRecord>();

		public BoneHubPreferences Preferences { get; init; } = new BoneHubPreferences();

		public List<TrashedModRecord> TrashHistory { get; init; } = new List<TrashedModRecord>();

		public List<PendingDeletionRecord> PendingDeletions { get; init; } = new List<PendingDeletionRecord>();
	}
	public sealed class BoneHubPreferences
	{
		public int MotionPreferenceSchemaVersion { get; set; }

		public BoneHubComfortPreset ComfortPreset { get; set; }

		public bool WatchEnabled { get; set; } = true;

		public bool RaiseToOpen { get; set; } = true;

		public bool DoubleTapToOpen { get; set; } = true;

		public bool UseLeftWrist { get; set; } = true;

		public float WatchScale { get; set; } = 1f;

		public float WatchBodyScale { get; set; } = 1f;

		public WristWatchStyle WatchStyle { get; set; }

		public WristWatchAttachment WatchAttachment { get; set; }

		public WristClockOrientation ClockOrientation { get; set; }

		public float DwellSeconds { get; set; } = 1f;

		public float OutwardOffset { get; set; } = 0.029f;

		public float FingerOffset { get; set; } = -0.018f;

		public float WatchForearmOffset { get; set; }

		public float WatchSleepLift { get; set; }

		public bool ReducedMotion { get; set; }

		public bool HologramEffects { get; set; } = true;

		public bool HapticsEnabled { get; set; } = true;

		public WristNotificationFocusMode NotificationFocus { get; set; }

		public bool CallsBreakQuiet { get; set; } = true;

		public bool PrivateNotificationReveal { get; set; } = true;

		public bool SoundsEnabled { get; set; } = true;

		public float CarouselDistance { get; set; } = 0.62f;

		public float HologramScale { get; set; } = 1f;

		public float EquipZoneScale { get; set; } = 1f;

		public float HologramBrightness { get; set; } = 1f;

		public WristHubTheme HologramTheme { get; set; }

		public bool Use24HourClock { get; set; }

		public BrowserStartupMode AvatarBrowserStartupMode { get; set; } = BrowserStartupMode.AllInstalled;

		public AvatarBrowseScope LastAvatarBrowseScope { get; set; }

		public List<CompassCalibrationRecord> CompassCalibrations { get; init; } = new List<CompassCalibrationRecord>();

		public MiniMapRange MiniMapRange { get; set; } = MiniMapRange.ThirtyMeters;

		public MiniMapOrientation MiniMapOrientation { get; set; }

		public List<string> FavoriteSpawnableBarcodes { get; init; } = new List<string>();

		public List<string> RecentSpawnableBarcodes { get; init; } = new List<string>();

		public int SpawnerStateSchemaVersion { get; set; }

		public string LastSpawnableMode { get; set; } = string.Empty;

		public string LastSpawnableGroup { get; set; } = string.Empty;

		public List<string> LegacySpawnableFavoritesBackup { get; init; } = new List<string>();

		public List<string> LegacySpawnableRecentsBackup { get; init; } = new List<string>();

		public List<string> PinnedWristAppIds { get; init; } = new List<string>();

		public List<string> OpenedWristAppIds { get; init; } = new List<string>();

		public WristAppLibraryTab WristAppLibraryTab { get; set; }

		public string WristAppSearchQuery { get; set; } = string.Empty;
	}
	public enum WristAppLibraryTab
	{
		Installed,
		Store,
		Updates
	}
	public enum WristHubTheme
	{
		Cyan,
		Graphite,
		Crimson,
		Emerald,
		Violet,
		Amber,
		Jolly
	}
	public enum WristWatchStyle
	{
		ClassicCircle,
		MetalSquare,
		SlimSquare,
		ProjectionCore,
		Invisible
	}
	public enum WristWatchAttachment
	{
		Adaptive,
		WristLocked
	}
	public enum WristClockOrientation
	{
		ArmFixed,
		HeadReadable
	}
	public sealed class CompassCalibrationRecord
	{
		public string SceneKey { get; init; } = string.Empty;

		public float NorthYawDegrees { get; set; }

		public DateTimeOffset LastUsedUtc { get; set; } = DateTimeOffset.UtcNow;
	}
	public enum BoneHubComfortPreset
	{
		Default,
		Seated,
		LargeTargets,
		ReducedMotion
	}
	public sealed class InstalledModRecord
	{
		public long ModId { get; init; }

		public long FileId { get; init; }

		public string Name { get; init; } = string.Empty;

		public string Version { get; init; } = string.Empty;

		public string Md5 { get; init; } = string.Empty;

		public List<string> InstalledDirectories { get; init; } = new List<string>();

		public ModIoSharingMetadata? Sharing { get; set; }

		public DateTimeOffset InstalledAt { get; init; }
	}
	public sealed class TrashedModRecord
	{
		public Guid Id { get; init; } = Guid.NewGuid();

		public InstalledModRecord Mod { get; init; } = new InstalledModRecord();

		public List<TrashedDirectoryRecord> Directories { get; init; } = new List<TrashedDirectoryRecord>();

		public List<TrashedFileRecord> Files { get; init; } = new List<TrashedFileRecord>();

		public bool WasSubscribed { get; init; }

		public DateTimeOffset TrashedAt { get; init; } = DateTimeOffset.UtcNow;
	}
	public sealed class TrashedFileRecord
	{
		public string OriginalPath { get; init; } = string.Empty;

		public string TrashPath { get; init; } = string.Empty;
	}
	public sealed class TrashedDirectoryRecord
	{
		public string OriginalPath { get; init; } = string.Empty;

		public string TrashPath { get; init; } = string.Empty;
	}
	public sealed class PendingDeletionRecord
	{
		public InstalledModRecord Mod { get; init; } = new InstalledModRecord();

		public List<string> Directories { get; init; } = new List<string>();

		public List<string> SidecarFiles { get; init; } = new List<string>();

		public List<string> AvatarBarcodes { get; init; } = new List<string>();

		public bool WasSubscribed { get; init; }

		public DateTimeOffset QueuedAt { get; init; } = DateTimeOffset.UtcNow;
	}
	public sealed record PendingDeletionProcessResult(int Completed, int Remaining, IReadOnlyList<string> Problems);
	public sealed class JsonFileStore<T> where T : class, new()
	{
		private readonly string _path;

		private readonly SemaphoreSlim _gate = new SemaphoreSlim(1, 1);

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

		public JsonFileStore(string path)
		{
			_path = Path.GetFullPath(path);
		}

		public async Task<T> LoadAsync(CancellationToken cancellationToken = default(CancellationToken))
		{
			await _gate.WaitAsync(cancellationToken).ConfigureAwait(continueOnCapturedContext: false);
			try
			{
				if (!File.Exists(_path))
				{
					return new T();
				}
				T result;
				await using (FileStream stream = File.OpenRead(_path))
				{
					result = (await JsonSerializer.DeserializeAsync<T>(stream, _json, cancellationToken).ConfigureAwait(continueOnCapturedContext: false)) ?? new T();
				}
				return result;
			}
			catch (JsonException)
			{
				string destFileName = _path + $".corrupt-{DateTimeOffset.UtcNow:yyyyMMddHHmmss}";
				File.Move(_path, destFileName, overwrite: false);
				return new T();
			}
			finally
			{
				_gate.Release();
			}
		}

		public async Task SaveAsync(T value, CancellationToken cancellationToken = default(CancellationToken))
		{
			await _gate.WaitAsync(cancellationToken).ConfigureAwait(continueOnCapturedContext: false);
			try
			{
				Directory.CreateDirectory(Path.GetDirectoryName(_path));
				string temporary = _path + ".tmp";
				await using (FileStream stream = new FileStream(temporary, FileMode.Create, FileAccess.Write, FileShare.None))
				{
					await JsonSerializer.SerializeAsync(stream, value, _json, cancellationToken).ConfigureAwait(continueOnCapturedContext: false);
					await stream.FlushAsync(cancellationToken).ConfigureAwait(continueOnCapturedContext: false);
				}
				File.Move(temporary, _path, overwrite: true);
			}
			finally
			{
				_gate.Release();
			}
		}
	}
	public sealed class SearchCache
	{
		public sealed class CachedSearch
		{
			public DateTimeOffset SavedAt { get; init; }

			public ModIoPage<ModIoMod> Page { get; init; } = new ModIoPage<ModIoMod>();
		}

		private readonly string _directory;

		public SearchCache(string directory)
		{
			_directory = Path.GetFullPath(directory);
		}

		public async Task<ModIoPage<ModIoMod>?> ReadAsync(string key, TimeSpan maximumAge, CancellationToken cancellationToken = default(CancellationToken))
		{
			CachedSearch cachedSearch = await new JsonFileStore<CachedSearch>(PathFor(key)).LoadAsync(cancellationToken).ConfigureAwait(continueOnCapturedContext: false);
			return (cachedSearch.SavedAt != default(DateTimeOffset) && DateTimeOffset.UtcNow - cachedSearch.SavedAt <= maximumAge) ? cachedSearch.Page : null;
		}

		public Task WriteAsync(string key, ModIoPage<ModIoMod> page, CancellationToken cancellationToken = default(CancellationToken))
		{
			return new JsonFileStore<CachedSearch>(PathFor(key)).SaveAsync(new CachedSearch
			{
				SavedAt = DateTimeOffset.UtcNow,
				Page = page
			}, cancellationToken);
		}

		private string PathFor(string key)
		{
			Directory.CreateDirectory(_directory);
			byte[] inArray = SHA256.HashData(Encoding.UTF8.GetBytes(key));
			return Path.Combine(_directory, Convert.ToHexString(inArray).ToLowerInvariant() + ".json");
		}
	}
}
namespace BoneHub.Models
{
	public sealed class ModIoPage<T>
	{
		[JsonPropertyName("data")]
		public List<T> Data { get; init; } = new List<T>();

		[JsonPropertyName("result_count")]
		public int ResultCount { get; init; }

		[JsonPropertyName("result_offset")]
		public int ResultOffset { get; init; }

		[JsonPropertyName("result_limit")]
		public int ResultLimit { get; init; }

		[JsonPropertyName("result_total")]
		public int ResultTotal { get; init; }
	}
	public sealed class ModIoMod
	{
		[JsonPropertyName("id")]
		public long Id { get; init; }

		[JsonPropertyName("name")]
		public string Name { get; init; } = string.Empty;

		[JsonPropertyName("name_id")]
		public string NameId { get; init; } = string.Empty;

		[JsonPropertyName("summary")]
		public string Summary { get; init; } = string.Empty;

		[JsonPropertyName("description_plaintext")]
		public string Description { get; init; } = string.Empty;

		[JsonPropertyName("profile_url")]
		public string ProfileUrl { get; init; } = string.Empty;

		[JsonPropertyName("date_updated")]
		public long DateUpdated { get; init; }

		[JsonPropertyName("submitted_by")]
		public ModIoUser Author { get; init; } = new ModIoUser();

		[JsonPropertyName("logo")]
		public ModIoLogo Logo { get; init; } = new ModIoLogo();

		[JsonPropertyName("modfile")]
		public ModIoFile? Modfile { get; init; }

		[JsonPropertyName("stats")]
		public ModIoStats Stats { get; init; } = new ModIoStats();

		[JsonPropertyName("tags")]
		public List<ModIoTag> Tags { get; init; } = new List<ModIoTag>();

		[JsonPropertyName("maturity_option")]
		public int MaturityOption { get; init; }

		public bool HasTag(string value)
		{
			return Tags.Any((ModIoTag tag) => string.Equals(tag.Name, value, StringComparison.OrdinalIgnoreCase));
		}
	}
	public sealed class ModIoUser
	{
		[JsonPropertyName("username")]
		public string Username { get; init; } = string.Empty;

		[JsonPropertyName("display_name_portal")]
		public string? DisplayNamePortal { get; init; }

		public string DisplayName
		{
			get
			{
				if (!string.IsNullOrWhiteSpace(DisplayNamePortal))
				{
					return DisplayNamePortal;
				}
				return Username;
			}
		}
	}
	public sealed class ModIoLogo
	{
		[JsonPropertyName("original")]
		public string Original { get; init; } = string.Empty;

		[JsonPropertyName("thumb_320x180")]
		public string Thumbnail { get; init; } = string.Empty;
	}
	public sealed class ModIoStats
	{
		[JsonPropertyName("downloads_total")]
		public long DownloadsTotal { get; init; }

		[JsonPropertyName("subscribers_total")]
		public long SubscribersTotal { get; init; }

		[JsonPropertyName("ratings_weighted_aggregate")]
		public double Rating { get; init; }
	}
	public sealed class ModIoTag
	{
		[JsonPropertyName("name")]
		public string Name { get; init; } = string.Empty;
	}
	public sealed class ModIoFile
	{
		[JsonPropertyName("id")]
		public long Id { get; init; }

		[JsonPropertyName("mod_id")]
		public long ModId { get; init; }

		[JsonPropertyName("filename")]
		public string Filename { get; init; } = string.Empty;

		[JsonPropertyName("version")]
		public string Version { get; init; } = string.Empty;

		[JsonPropertyName("date_added")]
		public long DateAdded { get; init; }

		[JsonPropertyName("filesize")]
		public long FileSize { get; init; }

		[JsonPropertyName("filesize_uncompressed")]
		public long UncompressedSize { get; init; }

		[JsonPropertyName("virus_status")]
		public int VirusStatus { get; init; }

		[JsonPropertyName("virus_positive")]
		public int VirusPositive { get; init; }

		[JsonPropertyName("filehash")]
		public ModIoFileHash Hash { get; init; } = new ModIoFileHash();

		[JsonPropertyName("download")]
		public ModIoDownload Download { get; init; } = new ModIoDownload();

		[JsonPropertyName("platforms")]
		public List<ModIoPlatform> Platforms { get; init; } = new List<ModIoPlatform>();
	}
	public sealed class ModIoFileHash
	{
		[JsonPropertyName("md5")]
		public string Md5 { get; init; } = string.Empty;
	}
	public sealed class ModIoDownload
	{
		[JsonPropertyName("binary_url")]
		public string BinaryUrl { get; init; } = string.Empty;

		[JsonPropertyName("date_expires")]
		public long DateExpires { get; init; }
	}
	public sealed class ModIoPlatform
	{
		[JsonPropertyName("platform")]
		public string Platform { get; init; } = string.Empty;

		[JsonPropertyName("status")]
		public int Status { get; init; }
	}
	public sealed class ModIoDependency
	{
		[JsonPropertyName("mod_id")]
		public long ModId { get; init; }

		[JsonPropertyName("date_added")]
		public long DateAdded { get; init; }
	}
	public enum ModSort
	{
		Trending,
		Newest,
		MostDownloaded,
		RecentlyUpdated
	}
	public sealed record ModSearchRequest(string Query, string? Tag = null, string? Creator = null, ModSort Sort = ModSort.Trending, int Offset = 0, int Limit = 20);
	public sealed record ModUpdateInfo(ModIoMod Mod, bool UpdateAvailable, long InstalledFileId, long LatestFileId);
	public sealed record ModInstallPlan(ModIoMod Root, IReadOnlyList<ModIoMod> OrderedMods, IReadOnlyList<long> AlreadyInstalledIds);
	public sealed record InstalledModHealth(bool Healthy, IReadOnlyList<string> Problems);
}
namespace BoneHub.Interaction
{
	public enum AvatarBrowseScope
	{
		AllInstalled,
		Groups
	}
	public enum BrowserStartupMode
	{
		RememberLast,
		AllInstalled,
		Groups
	}
	public static class AvatarBrowseStartup
	{
		public static AvatarBrowseScope Resolve(BrowserStartupMode mode, AvatarBrowseScope lastScope)
		{
			return mode switch
			{
				BrowserStartupMode.RememberLast => Enum.IsDefined(lastScope) ? lastScope : AvatarBrowseScope.AllInstalled, 
				BrowserStartupMode.Groups => AvatarBrowseScope.Groups, 
				_ => AvatarBrowseScope.AllInstalled, 
			};
		}
	}
	public enum AvatarBrowserViewState
	{
		PushPrompt,
		Hub,
		Library,
		Pack,
		Downloads,
		Compass,
		CodeMods,
		MiniMap,
		Portals,
		Utilities,
		MonoDirector,
		ModStore,
		InstalledApps,
		Friends,
		PortalLevels,
		FusionServers,
		Spawnables,
		PackOptions,
		Options,
		Keyboard,
		Searching,
		Result,
		Downloading,
		PreparingPreview,
		Error
	}
	public enum AvatarPackSort
	{
		RecentlyUsed,
		Name,
		Creator
	}
	public sealed record AvatarPackEntry(long ModId, string Name, string Creator, string ThumbnailUrl, IReadOnlyList<AvatarEntry> Avatars, bool Installed, AvatarDownloadState DownloadState = AvatarDownloadState.None, double DownloadProgress = 0.0, string? Status = null)
	{
		public string Identity => "pack:" + ModId;

		public int AvatarCount => Avatars.Count((AvatarEntry entry) => entry.Installed && !string.IsNullOrWhiteSpace(entry.Barcode));
	}
	public static class AvatarPackLibrary
	{
		public static IReadOnlyList<AvatarPackEntry> Group(IEnumerable<AvatarEntry> entries, IEnumerable<string>? recentBarcodes = null, AvatarPackSort sort = AvatarPackSort.RecentlyUsed)
		{
			Dictionary<string, int> recent = (recentBarcodes ?? Array.Empty<string>()).Select((string barcode, int index) => (barcode: barcode, index: index)).ToDictionary<(string, int), string, int>(((string barcode, int index) item) => item.barcode, ((string barcode, int index) item) => item.index, StringComparer.OrdinalIgnoreCase);
			IEnumerable<AvatarPackEntry> source = (from entry in entries
				group entry by entry.ModId).Select(delegate(IGrouping<long, AvatarEntry> group)
			{
				AvatarEntry[] array = group.OrderBy<AvatarEntry, string>((AvatarEntry item) => item.AvatarName, StringComparer.OrdinalIgnoreCase).ToArray();
				AvatarEntry avatarEntry = array.OrderByDescending((AvatarEntry item) => item.Installed).First();
				AvatarDownloadState downloadState = array.Select((AvatarEntry item) => item.DownloadState).OrderByDescending(DownloadRank).FirstOrDefault();
				return new AvatarPackEntry(group.Key, avatarEntry.ModName, avatarEntry.Creator, avatarEntry.ThumbnailUrl, array, array.Any((AvatarEntry item) => item.Installed), downloadState, array.Max((AvatarEntry item) => item.DownloadProgress), array.Select((AvatarEntry item) => item.Status).FirstOrDefault((string value) => !string.IsNullOrWhiteSpace(value)));
			});
			return sort switch
			{
				AvatarPackSort.Name => source.OrderBy<AvatarPackEntry, string>((AvatarPackEntry pack) => pack.Name, StringComparer.OrdinalIgnoreCase).ToArray(), 
				AvatarPackSort.Creator => source.OrderBy<AvatarPackEntry, string>((AvatarPackEntry pack) => pack.Creator, StringComparer.OrdinalIgnoreCase).ThenBy<AvatarPackEntry, string>((AvatarPackEntry pack) => pack.Name, StringComparer.OrdinalIgnoreCase).ToArray(), 
				_ => source.OrderBy((AvatarPackEntry pack) => RecentIndex(pack, recent)).ThenBy<AvatarPackEntry, string>((AvatarPackEntry pack) => pack.Name, StringComparer.OrdinalIgnoreCase).ToArray(), 
			};
		}

		private static int RecentIndex(AvatarPackEntry pack, IReadOnlyDictionary<string, int> recent)
		{
			int[] array = (from item in pack.Avatars
				where recent.ContainsKey(item.Barcode)
				select recent[item.Barcode]).ToArray();
			if (array.Length != 0)
			{
				return array.Min();
			}
			return int.MaxValue;
		}

		private static int DownloadRank(AvatarDownloadState state)
		{
			return state switch
			{
				AvatarDownloadState.Failed => 7, 
				AvatarDownloadState.Installing => 6, 
				AvatarDownloadState.Downloading => 5, 
				AvatarDownloadState.Queued => 4, 
				AvatarDownloadState.Cancelled => 3, 
				AvatarDownloadState.Installed => 2, 
				_ => 1, 
			};
		}
	}
	public sealed class FourRowPageCursor
	{
		public const int RowsPerPage = 4;

		public int Page { get; private set; }

		public int Count { get; private set; }

		public int PageCount => Math.Max(1, (Count + 4 - 1) / 4);

		public string PositionText => $"{Page + 1} / {PageCount}";

		public int Reset(int count)
		{
			Count = Math.Max(0, count);
			Page = 0;
			return Page;
		}

		public int Keep(int count)
		{
			Count = Math.Max(0, count);
			Page = Math.Clamp(Page, 0, PageCount - 1);
			return Page;
		}

		public int Move(int direction)
		{
			if (PageCount <= 1 || direction == 0)
			{
				return Page;
			}
			Page = (Page + Math.Sign(direction) + PageCount) % PageCount;
			return Page;
		}
	}
	public sealed class FocusedResultCursor
	{
		public int Index { get; private set; }

		public int Count { get; private set; }

		public string PositionText
		{
			get
			{
				if (Count != 0)
				{
					return $"{Index + 1} / {Count}";
				}
				return "0 / 0";
			}
		}

		public bool CanMovePrevious => Count > 1;

		public bool CanMoveNext => Count > 1;

		public int Reset(int count)
		{
			Count = Math.Max(0, count);
			Index = 0;
			return Index;
		}

		public int Keep(int count, int preferredIndex)
		{
			Count = Math.Max(0, count);
			Index = ((Count != 0) ? Math.Clamp(preferredIndex, 0, Count - 1) : 0);
			return Index;
		}

		public int Move(int direction)
		{
			if (Count <= 1 || direction == 0)
			{
				return Index;
			}
			Index = (Index + Math.Sign(direction) + Count) % Count;
			return Index;
		}
	}
	public readonly record struct TabletRect(float X, float Y, float Width, float Height)
	{
		public float Left => X - Width * 0.5f;

		public float Right => X + Width * 0.5f;

		public float Bottom => Y - Height * 0.5f;

		public float Top => Y + Height * 0.5f;

		public bool IsInside(TabletRect parent)
		{
			if (Left >= parent.Left && Right <= parent.Right && Bottom >= parent.Bottom)
			{
				return Top <= parent.Top;
			}
			return false;
		}

		public bool Overlaps(TabletRect other)
		{
			if (Left < other.Right && Right > other.Left && Bottom < other.Top)
			{
				return Top > other.Bottom;
			}
			return false;
		}
	}
	public static class AvatarTabletLayout
	{
		public static readonly TabletRect Tablet = new TabletRect(0f, 0f, 340f, 210f);

		public static readonly TabletRect Search = new TabletRect(-34f, 82f, 150f, 30f);

		public static readonly TabletRect Close = new TabletRect(148f, 82f, 30f, 30f);

		public static readonly TabletRect Artwork = new TabletRect(-66f, -4f, 124f, 128f);

		public static readonly TabletRect Details = new TabletRect(65f, 3f, 126f, 112f);

		public static readonly TabletRect Previous = new TabletRect(-151f, 14f, 26f, 54f);

		public static readonly TabletRect Next = new TabletRect(151f, 14f, 26f, 54f);

		public static readonly TabletRect Action = new TabletRect(65f, -69f, 126f, 30f);

		public static readonly TabletRect Trash = new TabletRect(-66f, -91f, 116f, 18f);

		public static readonly TabletRect Progress = new TabletRect(65f, -91f, 126f, 14f);

		public static IReadOnlyList<TabletRect> ContentRects => new TabletRect[9] { Search, Close, Artwork, Details, Previous, Next, Action, Trash, Progress };

		public static TabletRect PhysicalTouchRect(TabletRect rect)
		{
			return new TabletRect(rect.X, rect.Y, rect.Width * 1.1f, rect.Height * 1.2f);
		}
	}
	public static class ArcDashboardLayout
	{
		public static readonly TabletRect Presentation = new TabletRect(0f, 0f, 300f, 190f);

		public static readonly TabletRect Stage = new TabletRect(0f, 7f, 170f, 124f);

		public static readonly TabletRect LeftWing = new TabletRect(-122f, 16f, 50f, 146f);

		public static readonly TabletRect RightWing = new TabletRect(122f, 16f, 50f, 146f);

		public static readonly TabletRect Back = new TabletRect(-122f, 72f, 42f, 22f);

		public static readonly TabletRect Search = new TabletRect(-122f, 44f, 46f, 24f);

		public static readonly TabletRect Previous = new TabletRect(-122f, 7f, 42f, 42f);

		public static readonly TabletRect Scope = new TabletRect(-122f, -35f, 46f, 25f);

		public static readonly TabletRect Close = new TabletRect(122f, 72f, 30f, 26f);

		public static readonly TabletRect Pack = new TabletRect(122f, 44f, 46f, 24f);

		public static readonly TabletRect Next = new TabletRect(122f, 7f, 42f, 42f);

		public static readonly TabletRect Options = new TabletRect(122f, -35f, 46f, 25f);

		public static readonly TabletRect Name = new TabletRect(0f, 82f, 166f, 20f);

		public static readonly TabletRect Creator = new TabletRect(-45f, 65f, 78f, 14f);

		public static readonly TabletRect PackName = new TabletRect(45f, 65f, 92f, 14f);

		public static readonly TabletRect Height = new TabletRect(-54f, -53f, 94f, 16f);

		public static readonly TabletRect Position = new TabletRect(0f, -53f, 48f, 16f);

		public static readonly TabletRect Status = new TabletRect(54f, -53f, 94f, 18f);

		public static readonly TabletRect Action = new TabletRect(0f, -80f, 116f, 28f);

		public static readonly TabletRect Progress = Action;

		public static IReadOnlyList<TabletRect> TouchControls => new TabletRect[9] { Back, Search, Previous, Scope, Close, Pack, Next, Options, Action };
	}
	public static class WristOsHomeHeaderLayout
	{
		public static readonly TabletRect Download = new TabletRect(-86f, 70f, 28f, 23f);

		public static readonly TabletRect Wordmark = new TabletRect(-26f, 70f, 86f, 20f);

		public static readonly TabletRect OsMark = new TabletRect(32f, 70f, 28f, 20f);

		public static readonly TabletRect AccentRail = new TabletRect(-19f, 56f, 120f, 1.5f);

		public static readonly TabletRect Settings = new TabletRect(66f, 70f, 28f, 23f);

		public static readonly TabletRect Close = new TabletRect(106f, 70f, 24f, 24f);

		public static IReadOnlyList<TabletRect> InteractiveControls => new TabletRect[3] { Download, Settings, Close };
	}
	public static class WristSettingsLayout
	{
		public static readonly TabletRect Page = new TabletRect(0f, 0f, 326f, 226f);

		public static readonly TabletRect Header = new TabletRect(0f, 95f, 306f, 34f);

		public static readonly TabletRect LibraryCard = new TabletRect(0f, 47f, 298f, 50f);

		public static readonly TabletRect StyleCard = new TabletRect(0f, -6f, 298f, 50f);

		public static readonly TabletRect DeviceCard = new TabletRect(0f, -64f, 298f, 58f);

		public static readonly TabletRect StartingView = new TabletRect(-62f, 50f, 170f, 15f);

		public static readonly TabletRect StartingAction = new TabletRect(112f, 50f, 62f, 18f);

		public static readonly TabletRect Sort = new TabletRect(-62f, 31f, 170f, 15f);

		public static readonly TabletRect SortAction = new TabletRect(112f, 31f, 62f, 18f);

		public static readonly TabletRect Theme = new TabletRect(-62f, -3f, 170f, 15f);

		public static readonly TabletRect ThemeAction = new TabletRect(112f, -3f, 62f, 18f);

		public static readonly TabletRect Clock = new TabletRect(-62f, -22f, 170f, 15f);

		public static readonly TabletRect ClockAction = new TabletRect(112f, -22f, 62f, 18f);

		public static readonly TabletRect Watch = new TabletRect(-62f, -58f, 170f, 15f);

		public static readonly TabletRect WatchAction = new TabletRect(112f, -58f, 62f, 19f);

		public static readonly TabletRect Motion = new TabletRect(-62f, -80f, 170f, 15f);

		public static readonly TabletRect MotionAction = new TabletRect(112f, -80f, 62f, 19f);

		public static IReadOnlyList<TabletRect> Content => new TabletRect[15]
		{
			LibraryCard, StyleCard, DeviceCard, StartingView, StartingAction, Sort, SortAction, Theme, ThemeAction, Clock,
			ClockAction, Watch, WatchAction, Motion, MotionAction
		};
	}
	public static class AvatarPackTabletLayout
	{
		public static readonly IReadOnlyList<TabletRect> HeaderControls = new TabletRect[5]
		{
			new TabletRect(-145f, 83f, 42f, 28f),
			new TabletRect(-90f, 83f, 64f, 28f),
			new TabletRect(-34f, 83f, 46f, 28f),
			new TabletRect(25f, 83f, 68f, 28f),
			new TabletRect(148f, 83f, 26f, 28f)
		};

		public static readonly IReadOnlyList<TabletRect> Rows = (from index in Enumerable.Range(0, 4)
			select new TabletRect(-36f, 50f - (float)index * 34f, 228f, 30f)).ToArray();

		public static readonly IReadOnlyList<TabletRect> DeleteRows = (from index in Enumerable.Range(0, 4)
			select new TabletRect(125f, 50f - (float)index * 34f, 52f, 30f)).ToArray();

		public static readonly TabletRect Status = new TabletRect(0f, -72f, 250f, 10f);

		public static readonly IReadOnlyList<TabletRect> FooterControls = new TabletRect[3]
		{
			new TabletRect(-135f, -91f, 50f, 26f),
			new TabletRect(0f, -91f, 90f, 24f),
			new TabletRect(135f, -91f, 50f, 26f)
		};

		public static IReadOnlyList<TabletRect> AllControls => HeaderControls.Concat(Rows).Concat(DeleteRows).Append(Status)
			.Concat(FooterControls)
			.ToArray();
	}
	public sealed class PresentationGeneration
	{
		public int Value { get; private set; }

		public int Next()
		{
			return ++Value;
		}

		public bool IsCurrent(int generation)
		{
			return generation == Value;
		}
	}
	public sealed class SurfaceInputRearmGate
	{
		private readonly double _clearSeconds;

		private double _clearSince = -1.0;

		public bool Required { get; private set; }

		public SurfaceInputRearmGate(double clearSeconds = 0.1)
		{
			_clearSeconds = Math.Max(0.0, clearSeconds);
		}

		public void Require()
		{
			Required = true;
			_clearSince = -1.0;
		}

		public bool CanInteract(bool touchingAnyActiveControl, double nowSeconds)
		{
			if (!Required)
			{
				return true;
			}
			if (touchingAnyActiveControl)
			{
				_clearSince = -1.0;
				return false;
			}
			if (_clearSince < 0.0)
			{
				_clearSince = nowSeconds;
				return _clearSeconds <= 0.0;
			}
			if (nowSeconds - _clearSince < _clearSeconds)
			{
				return false;
			}
			Required = false;
			_clearSince = -1.0;
			return true;
		}
	}
	public sealed class AvatarSearchInputSession
	{
		public int Generation { get; private set; }

		public bool IsOpen { get; private set; }

		public string InitialValue { get; private set; } = string.Empty;

		public int Open(string? initialValue)
		{
			InitialValue = initialValue?.Trim() ?? string.Empty;
			IsOpen = true;
			return ++Generation;
		}

		public bool TrySubmit(int generation, string? value, out string query)
		{
			query = value?.Trim() ?? string.Empty;
			if (!IsOpen || generation != Generation)
			{
				return false;
			}
			IsOpen = false;
			return true;
		}

		public bool Cancel(int generation)
		{
			if (!IsOpen || generation != Generation)
			{
				return false;
			}
			IsOpen = false;
			return true;
		}
	}
	public sealed class TabletKeyboardBuffer
	{
		public const int MaximumLength = 64;

		public string Value { get; private set; } = string.Empty;

		public bool Shifted { get; private set; }

		public void Set(string? value)
		{
			Value = (((value ?? string.Empty).Length <= 64) ? (value ?? string.Empty) : value.Substring(0, 64));
		}

		public bool Append(char character)
		{
			if (Value.Length >= 64 || char.IsControl(character))
			{
				return false;
			}
			Value += (Shifted ? char.ToUpperInvariant(character) : char.ToLowerInvariant(character));
			return true;
		}

		public bool AppendSpace()
		{
			if (Value.Length >= 64 || Value.EndsWith(' '))
			{
				return false;
			}
			Value += " ";
			return true;
		}

		public bool Backspace()
		{
			if (Value.Length == 0)
			{
				return false;
			}
			string value = Value;
			Value = value.Substring(0, value.Length - 1);
			return true;
		}

		public void Clear()
		{
			Value = string.Empty;
		}

		public void ToggleShift()
		{
			Shifted = !Shifted;
		}

		public string VisibleValue(int maximumCharacters = 36)
		{
			int num = Math.Clamp(maximumCharacters, 4, 64);
			if (Value.Length > num)
			{
				string value = Value;
				int num2 = num - 1;
				int length = value.Length;
				int num3 = length - num2;
				return "…" + value.Substring(num3, length - num3);
			}
			return Value;
		}
	}
	public static class PreviewDragMath
	{
		public static bool IsInside(Vector3 localPoint, Vector3 halfExtents)
		{
			if (MathF.Abs(localPoint.X) <= MathF.Max(0f, halfExtents.X) && MathF.Abs(localPoint.Y) <= MathF.Max(0f, halfExtents.Y))
			{
				return MathF.Abs(localPoint.Z) <= MathF.Max(0f, halfExtents.Z);
			}
			return false;
		}

		public static bool PinchActive(float thumbIndexDistance, bool alreadyDragging, float beginDistance = 0.03f, float releaseDistance = 0.045f)
		{
			if (float.IsFinite(thumbIndexDistance))
			{
				return thumbIndexDistance <= (alreadyDragging ? MathF.Max(beginDistance, releaseDistance) : beginDistance);
			}
			return false;
		}

		public static bool GripActive(float gripForce, bool alreadyDragging, float beginForce = 0.62f, float releaseForce = 0.28f)
		{
			if (float.IsFinite(gripForce))
			{
				return gripForce >= (alreadyDragging ? MathF.Min(beginForce, releaseForce) : beginForce);
			}
			return false;
		}

		public static bool IsChestRelease(Vector3 previewPosition, Vector3 chestPosition, float radius)
		{
			if (radius > 0f)
			{
				return Vector3.Distance(previewPosition, chestPosition) <= radius;
			}
			return false;
		}

		public static Vector3 LimitAnchorOffset(Vector3 offset, float maximumDistance)
		{
			if (!float.IsFinite(offset.X) || !float.IsFinite(offset.Y) || !float.IsFinite(offset.Z))
			{
				return Vector3.Zero;
			}
			float num = Math.Clamp(maximumDistance, 0.01f, 0.2f);
			float num2 = offset.LengthSquared();
			if (!float.IsFinite(num2) || num2 <= 0f)
			{
				return Vector3.Zero;
			}
			if (num2 <= num * num)
			{
				return offset;
			}
			return Vector3.Normalize(offset) * num;
		}
	}
	public static class ChestEquipGuideMath
	{
		public static bool ResolveLocked(float distance, bool wasLocked, float radius, float releaseMultiplier = 1.12f)
		{
			if (!float.IsFinite(distance) || !float.IsFinite(radius) || radius <= 0f)
			{
				return false;
			}
			float num = (wasLocked ? (radius * Math.Clamp(releaseMultiplier, 1f, 1.5f)) : radius);
			return distance <= num;
		}

		public static float Proximity(float distance, float radius, float visibleRadiusMultiplier = 2.75f)
		{
			if (!float.IsFinite(distance) || !float.IsFinite(radius) || radius <= 0f)
			{
				return 0f;
			}
			float num = radius * Math.Clamp(visibleRadiusMultiplier, 1.1f, 6f);
			if (distance <= radius)
			{
				return 1f;
			}
			return Math.Clamp(1f - (distance - radius) / (num - radius), 0f, 1f);
		}
	}
	public sealed class LookAwayDismissal
	{
		public double AwaySeconds { get; private set; }

		public bool Update(bool observed, double deltaSeconds, double delaySeconds = 0.35)
		{
			if (observed)
			{
				AwaySeconds = 0.0;
				return false;
			}
			AwaySeconds += Math.Clamp(deltaSeconds, 0.0, 0.25);
			return AwaySeconds >= Math.Max(0.05, delaySeconds);
		}

		public void Reset()
		{
			AwaySeconds = 0.0;
		}
	}
	public readonly record struct AvatarPreviewMetrics(string Barcode, float GameplayHeightMeters, bool Valid)
	{
		public string DisplayText
		{
			get
			{
				if (!Valid || !float.IsFinite(GameplayHeightMeters) || !(GameplayHeightMeters > 0f))
				{
					return "Height unavailable";
				}
				if (!(GameplayHeightMeters < 10f))
				{
					if (GameplayHeightMeters < 100f)
					{
						return $"{GameplayHeightMeters:0.0} m";
					}
					return $"{GameplayHeightMeters:#,##0} m";
				}
				return $"{GameplayHeightMeters:0.00} m";
			}
		}

		public string HeightDisplayText
		{
			get
			{
				if (!Valid)
				{
					return "Avatar height unavailable";
				}
				return "Avatar height " + DisplayText;
			}
		}
	}
	public enum AvatarPreviewVisualSource
	{
		Unavailable,
		FullColor,
		NativePreviewMesh
	}
	public enum AvatarPreviewHeightSource
	{
		Unavailable,
		AvatarMetadata,
		RendererBounds,
		PreviewMeshBounds
	}
	public readonly record struct AvatarResolvedHeight(float Meters, AvatarPreviewHeightSource Source)
	{
		public bool Valid => AvatarPreviewMath.IsDisplayableGameplayHeight(Meters);
	}
	public readonly record struct AvatarPreviewRouteMetadata(AvatarPreviewVisualSource VisualSource, AvatarPreviewHeightSource HeightSource, bool InteractionReady);
	public readonly record struct AvatarPreviewPlacement(float UniformScale, Vector3 Offset)
	{
		public bool Valid
		{
			get
			{
				if (float.IsFinite(UniformScale) && UniformScale > 0f && float.IsFinite(Offset.X) && float.IsFinite(Offset.Y))
				{
					return float.IsFinite(Offset.Z);
				}
				return false;
			}
		}
	}
	public static class AvatarPreviewMath
	{
		public const float DisplayHeightMeters = 0.16f;

		public const float DisplayWidthMeters = 0.135f;

		public const float DisplayDepthMeters = 0.1f;

		public const float FrontFacingYawDegrees = 180f;

		public static float NormalizeScale(float visualHeightMeters, float displayHeightMeters = 0.16f)
		{
			if (!IsUsableHeight(visualHeightMeters))
			{
				return 1f;
			}
			return Math.Clamp(displayHeightMeters, 0.05f, 0.3f) / visualHeightMeters;
		}

		public static bool IsUsableHeight(float value)
		{
			if (float.IsFinite(value))
			{
				if (value >= 0.05f)
				{
					return value <= 100f;
				}
				return false;
			}
			return false;
		}

		public static bool IsDisplayableGameplayHeight(float value)
		{
			if (float.IsFinite(value))
			{
				if (value >= 0.01f)
				{
					return value <= 10000f;
				}
				return false;
			}
			return false;
		}

		public static float NormalizeBoundsScale(Vector3 visualSize)
		{
			if (!float.IsFinite(visualSize.Y) || visualSize.Y <= 0.0001f)
			{
				return 1f;
			}
			return 0.16f / visualSize.Y;
		}

		public static AvatarPreviewPlacement FitBounds(Vector3 visualCenter, Vector3 visualSize)
		{
			float num = NormalizeBoundsScale(visualSize);
			return new AvatarPreviewPlacement(num, -visualCenter * num);
		}

		public static AvatarResolvedHeight ResolveHeight(float avatarMetadataHeight, float rendererBoundsHeight, float previewMeshBoundsHeight = 0f)
		{
			if (IsUsableHeight(avatarMetadataHeight))
			{
				return new AvatarResolvedHeight(avatarMetadataHeight, AvatarPreviewHeightSource.AvatarMetadata);
			}
			if (IsUsableHeight(rendererBoundsHeight))
			{
				return new AvatarResolvedHeight(rendererBoundsHeight, AvatarPreviewHeightSource.RendererBounds);
			}
			if (IsUsableHeight(previewMeshBoundsHeight))
			{
				return new AvatarResolvedHeight(previewMeshBoundsHeight, AvatarPreviewHeightSource.PreviewMeshBounds);
			}
			return new AvatarResolvedHeight(0f, AvatarPreviewHeightSource.Unavailable);
		}

		public static AvatarResolvedHeight ResolveGameplayHeight(float selectedAvatarHeight, float fallbackAvatarHeight)
		{
			if (IsDisplayableGameplayHeight(selectedAvatarHeight))
			{
				return new AvatarResolvedHeight(selectedAvatarHeight, AvatarPreviewHeightSource.AvatarMetadata);
			}
			if (IsDisplayableGameplayHeight(fallbackAvatarHeight))
			{
				return new AvatarResolvedHeight(fallbackAvatarHeight, AvatarPreviewHeightSource.AvatarMetadata);
			}
			return new AvatarResolvedHeight(0f, AvatarPreviewHeightSource.Unavailable);
		}

		public static bool IsVisualRendererType(string? typeName)
		{
			if (!string.IsNullOrWhiteSpace(typeName) && !typeName.Contains("Particle", StringComparison.OrdinalIgnoreCase) && !typeName.Contains("Trail", StringComparison.OrdinalIgnoreCase))
			{
				return !typeName.Contains("LineRenderer", StringComparison.OrdinalIgnoreCase);
			}
			return false;
		}

		public static bool IsSafePreview(float visualHeightMeters, int rendererCount, int vertexCount, bool constrained)
		{
			int num = (constrained ? 24 : 64);
			int num2 = (constrained ? 350000 : 1250000);
			if (IsUsableHeight(visualHeightMeters) && rendererCount > 0 && rendererCount <= num && vertexCount >= 0)
			{
				return vertexCount <= num2;
			}
			return false;
		}
	}
	public static class AvatarUiScaleMath
	{
		public const float ReferencePlayerHeightMeters = 1.75f;

		public static float ResolveMenuScale(float playerHeightMeters)
		{
			if (!AvatarPreviewMath.IsUsableHeight(playerHeightMeters))
			{
				return 1f;
			}
			return playerHeightMeters / 1.75f;
		}

		public static float ResolveDetachedContentScale(float tabletWorldScale)
		{
			if (!float.IsFinite(tabletWorldScale) || !(tabletWorldScale > 0.0001f))
			{
				return 1f;
			}
			return tabletWorldScale;
		}
	}
	public readonly record struct NativeMenuPlacement(Vector3 Position, Vector3 Forward, float DistanceMeters);
	public static class NativeMenuPlacementMath
	{
		public const float ReferenceDistanceMeters = 1.05f;

		public static Vector3 ResolveForward(Vector3 headForward, Vector3 fallbackForward)
		{
			Vector3 value = new Vector3(headForward.X, 0f, headForward.Z);
			if (value.LengthSquared() < 0.04f)
			{
				value = new Vector3(fallbackForward.X, 0f, fallbackForward.Z);
			}
			if (value.LengthSquared() < 0.0001f)
			{
				value = Vector3.UnitZ;
			}
			return Vector3.Normalize(value);
		}

		public static NativeMenuPlacement Resolve(Vector3 headPosition, Vector3 headForward, float avatarHeightMeters)
		{
			Vector3 vector = ResolveForward(headForward, Vector3.UnitZ);
			float num = Math.Clamp(AvatarUiScaleMath.ResolveMenuScale(avatarHeightMeters), 0.65f, 1.75f);
			float num2 = 1.05f * num;
			return new NativeMenuPlacement(headPosition + vector * num2 - Vector3.UnitY * (0.06f * num), vector, num2);
		}
	}
	public static class DownloadPresentationMath
	{
		public static double Smooth(double displayed, double authoritative, double deltaSeconds, double speed = 8.0)
		{
			double num = Math.Clamp(displayed, 0.0, 1.0);
			double num2 = Math.Clamp(authoritative, 0.0, 1.0);
			if (num2 <= num || deltaSeconds <= 0.0)
			{
				return Math.Min(num, (num2 < num) ? num : num2);
			}
			double num3 = 1.0 - Math.Exp((0.0 - Math.Max(0.1, speed)) * Math.Min(deltaSeconds, 0.25));
			double num4 = num + (num2 - num) * num3;
			if (!(num2 - num4 < 0.0005))
			{
				return Math.Min(num4, num2);
			}
			return num2;
		}
	}
	public static class FingertipProbeMath
	{
		public static Vector3 ControllerTip(Vector3 position, Vector3 forward, float reachMeters = 0.075f)
		{
			Vector3 vector = ((forward.LengthSquared() > 0.0001f) ? Vector3.Normalize(forward) : Vector3.UnitZ);
			return position + vector * Math.Clamp(reachMeters, 0f, 0.15f);
		}

		public static Vector3 Extrapolate(Vector3 intermediate, Vector3 distal, float distalFraction = 0.65f)
		{
			Vector3 value = distal - intermediate;
			float num = value.Length();
			if (!(num > 0.0001f))
			{
				return distal;
			}
			return distal + Vector3.Normalize(value) * num * Math.Clamp(distalFraction, 0f, 1.5f);
		}
	}
	public static class WorldTextFacingMath
	{
		public const float FrontFacingLocalYawDegrees = 0f;

		public static bool IsFrontFacing(float localYawDegrees)
		{
			return Math.Abs(Math.IEEERemainder(localYawDegrees - 0f, 360.0)) < 0.0010000000474974513;
		}

		public static bool FacesViewer(Vector3 rootForward, Vector3 directionToViewer, float localYawDegrees)
		{
			if (rootForward.LengthSquared() < 0.0001f || directionToViewer.LengthSquared() < 0.0001f)
			{
				return false;
			}
			Vector3 vector = Vector3.Normalize(rootForward);
			Vector3 value = Vector3.Cross(Vector3.UnitY, vector);
			value = ((!(value.LengthSquared() < 0.0001f)) ? Vector3.Normalize(value) : Vector3.UnitX);
			float x = localYawDegrees * (float)Math.PI / 180f;
			return Vector3.Dot(Vector3.Normalize(-vector * MathF.Cos(x) - value * MathF.Sin(x)), Vector3.Normalize(directionToViewer)) > 0.999f;
		}
	}
	public enum AvatarRestoreDecision
	{
		NativeSaveOwnsSelection,
		WaitForNativeSave,
		RestorePrivateFallback,
		NoRestore
	}
	public static class AvatarRestorePolicy
	{
		public static AvatarRestoreDecision Decide(bool nativeSaveReadable, bool nativeSaveWaitExpired, string? privateFallbackBarcode)
		{
			if (nativeSaveReadable)
			{
				return AvatarRestoreDecision.NativeSaveOwnsSelection;
			}
			if (!nativeSaveWaitExpired)
			{
				return AvatarRestoreDecision.WaitForNativeSave;
			}
			if (!string.IsNullOrWhiteSpace(privateFallbackBarcode))
			{
				return AvatarRestoreDecision.RestorePrivateFallback;
			}
			return AvatarRestoreDecision.NoRestore;
		}
	}
	public enum AvatarRigCompatibility
	{
		UnverifiedAllowed,
		Verified,
		Invalid
	}
	public readonly record struct AvatarRigValidationResult(AvatarRigCompatibility Compatibility, string Reason)
	{
		public bool CanEquip => Compatibility != AvatarRigCompatibility.Invalid;

		public bool IsVerified => Compatibility == AvatarRigCompatibility.Verified;

		public static AvatarRigValidationResult Ready()
		{
			return new AvatarRigValidationResult(AvatarRigCompatibility.Verified, string.Empty);
		}

		public static AvatarRigValidationResult Allow(string reason)
		{
			return new AvatarRigValidationResult(AvatarRigCompatibility.UnverifiedAllowed, reason);
		}

		public static AvatarRigValidationResult Reject(string reason)
		{
			return new AvatarRigValidationResult(AvatarRigCompatibility.Invalid, reason);
		}
	}
	public enum AvatarSharingState
	{
		Unregistered,
		Registering,
		Ready,
		Announced,
		PlatformLimited,
		Failed
	}
	public readonly record struct AvatarPlatformAvailability(long? WindowsFileId, long? AndroidFileId)
	{
		public bool HasWindows
		{
			get
			{
				long? windowsFileId = WindowsFileId;
				if (windowsFileId.HasValue)
				{
					return windowsFileId.GetValueOrDefault() > 0;
				}
				return false;
			}
		}

		public bool HasAndroid
		{
			get
			{
				long? androidFileId = AndroidFileId;
				if (androidFileId.HasValue)
				{
					return androidFileId.GetValueOrDefault() > 0;
				}
				return false;
			}
		}

		public bool HasAny
		{
			get
			{
				if (!HasWindows)
				{
					return HasAndroid;
				}
				return true;
			}
		}

		public bool IsLimited
		{
			get
			{
				if (HasAny)
				{
					if (HasWindows)
					{
						return !HasAndroid;
					}
					return true;
				}
				return false;
			}
		}

		public string Badge
		{
			get
			{
				if (!HasWindows || !HasAndroid)
				{
					if (!HasWindows)
					{
						if (!HasAndroid)
						{
							return "LOCAL ONLY";
						}
						return "QUEST ONLY";
					}
					return "PC ONLY";
				}
				return "PC + QUEST";
			}
		}
	}
	public readonly record struct CompassPresentation(float HeadingDegrees, float SmoothedHeadingDegrees, float NorthOffsetDegrees, string Cardinal, string SceneKey, bool HasCalibration);
	public static class CompassHeadingMath
	{
		private static readonly string[] Cardinals = new string[8] { "N", "NE", "E", "SE", "S", "SW", "W", "NW" };

		public static float Normalize(float degrees)
		{
			if (!float.IsFinite(degrees))
			{
				return 0f;
			}
			float num = degrees % 360f;
			if (!(num < 0f))
			{
				return num;
			}
			return num + 360f;
		}

		public static float Heading(float worldYawDegrees, float northOffsetDegrees)
		{
			return Normalize(worldYawDegrees - northOffsetDegrees);
		}

		public static float SignedDelta(float fromDegrees, float toDegrees)
		{
			float num = Normalize(toDegrees) - Normalize(fromDegrees);
			if (num > 180f)
			{
				num -= 360f;
			}
			if (num < -180f)
			{
				num += 360f;
			}
			return num;
		}

		public static float Smooth(float current, float target, float deltaSeconds, float response = 12f)
		{
			if (!float.IsFinite(deltaSeconds) || deltaSeconds <= 0f)
			{
				return Normalize(current);
			}
			float num = 1f - MathF.Exp((0f - MathF.Max(0.01f, response)) * deltaSeconds);
			return Normalize(current + SignedDelta(current, target) * num);
		}

		public static string Cardinal(float headingDegrees)
		{
			int num = (int)MathF.Floor((Normalize(headingDegrees) + 22.5f) / 45f) % Cardinals.Length;
			return Cardinals[num];
		}

		public static float NeedleRotation(float headingDegrees)
		{
			return 0f - Normalize(headingDegrees);
		}
	}
	public static class CompassCalibrationStore
	{
		public const int MaximumRecords = 64;

		public static CompassCalibrationRecord? Find(IReadOnlyList<CompassCalibrationRecord> records, string sceneKey)
		{
			return records.FirstOrDefault((CompassCalibrationRecord record) => string.Equals(record.SceneKey, sceneKey, StringComparison.OrdinalIgnoreCase));
		}

		public static void Upsert(List<CompassCalibrationRecord> records, string sceneKey, float northYaw, DateTimeOffset usedAt)
		{
			string sceneKey2 = (string.IsNullOrWhiteSpace(sceneKey) ? "unknown-scene" : sceneKey.Trim());
			CompassCalibrationRecord compassCalibrationRecord = Find(records, sceneKey2);
			if (compassCalibrationRecord != null)
			{
				compassCalibrationRecord.NorthYawDegrees = CompassHeadingMath.Normalize(northYaw);
				compassCalibrationRecord.LastUsedUtc = usedAt;
			}
			else
			{
				records.Add(new CompassCalibrationRecord
				{
					SceneKey = sceneKey2,
					NorthYawDegrees = CompassHeadingMath.Normalize(northYaw),
					LastUsedUtc = usedAt
				});
			}
			while (records.Count > 64)
			{
				CompassCalibrationRecord item = records.OrderBy((CompassCalibrationRecord record) => record.LastUsedUtc).First();
				records.Remove(item);
			}
		}
	}
	public enum HologramCarouselRole
	{
		Previous,
		Selected,
		Next
	}
	public enum HologramAvatarMaterialMode
	{
		BlueHologram,
		FullColor
	}
	public readonly record struct HologramCarouselSlot(HologramCarouselRole Role, int Index, string Identity, float HorizontalOffset, float Scale, HologramAvatarMaterialMode MaterialMode, bool InteractionReady);
	public static class HologramCarouselPresentation
	{
		public const float SideOffsetMeters = 0.105f;

		public const float SideScale = 0.62f;

		public static IReadOnlyList<HologramCarouselSlot> Build(IReadOnlyList<string> identities, int selectedIndex)
		{
			if (identities.Count == 0)
			{
				return Array.Empty<HologramCarouselSlot>();
			}
			int num = Wrap(selectedIndex, identities.Count);
			if (identities.Count != 1)
			{
				int index = Wrap(num - 1, identities.Count);
				int index2 = Wrap(num + 1, identities.Count);
				return new HologramCarouselSlot[3]
				{
					Slot(HologramCarouselRole.Previous, index, identities[index], -0.105f, 0.62f, interactive: false),
					Slot(HologramCarouselRole.Selected, num, identities[num], 0f, 1f, interactive: true),
					Slot(HologramCarouselRole.Next, index2, identities[index2], 0.105f, 0.62f, interactive: false)
				};
			}
			return new HologramCarouselSlot[1] { Slot(HologramCarouselRole.Selected, num, identities[num], 0f, 1f, interactive: true) };
		}

		public static int Wrap(int index, int count)
		{
			if (count <= 0)
			{
				return 0;
			}
			int num = index % count;
			if (num >= 0)
			{
				return num;
			}
			return num + count;
		}

		private static HologramCarouselSlot Slot(HologramCarouselRole role, int index, string identity, float offset, float scale, bool interactive)
		{
			return new HologramCarouselSlot(role, index, identity, offset, scale, interactive ? HologramAvatarMaterialMode.FullColor : HologramAvatarMaterialMode.BlueHologram, interactive);
		}
	}
	public enum CinematicProjectionPhase
	{
		Hidden,
		Charging,
		RingExpansion,
		SideAvatarAssembly,
		CenterAvatarAssembly,
		Stable,
		CarouselTransition,
		PanelTransition,
		Collapsing,
		TrackingGrace
	}
	public readonly record struct CinematicProjectionProgress(CinematicProjectionPhase Phase, float Emitter, float Rings, float SideAvatars, float CenterAvatar, float Controls);
	public static class CinematicProjectionTimeline
	{
		public const float ChargeEnd = 0.15f;

		public const float RingEnd = 0.35f;

		public const float SideEnd = 0.45f;

		public const float CenterEnd = 0.55f;

		public const float CollapseSeconds = 0.35f;

		public static CinematicProjectionProgress Resolve(float elapsed, bool reducedMotion)
		{
			if (reducedMotion)
			{
				return new CinematicProjectionProgress(CinematicProjectionPhase.Stable, 1f, 1f, 1f, 1f, 1f);
			}
			float num = Math.Max(0f, elapsed);
			return new CinematicProjectionProgress((num < 0.15f) ? CinematicProjectionPhase.Charging : ((num < 0.35f) ? CinematicProjectionPhase.RingExpansion : ((num < 0.45f) ? CinematicProjectionPhase.SideAvatarAssembly : ((num < 0.55f) ? CinematicProjectionPhase.CenterAvatarAssembly : CinematicProjectionPhase.Stable))), Smooth(num / 0.15f), Smooth((num - 0.0825f) / 0.26749998f), Smooth((num - 0.22749999f) / 0.2225f), Smooth((num - 0.35f) / 0.20000002f), Smooth((num - 0.35099998f) / 0.19900003f));
		}

		private static float Smooth(float value)
		{
			float num = Math.Clamp(value, 0f, 1f);
			return num * num * (3f - 2f * num);
		}
	}
	public static class HoloInteractionMath
	{
		public static int WrapIndex(int current, int delta, int count)
		{
			if (count <= 0)
			{
				return 0;
			}
			return ((current + delta) % count + count) % count;
		}

		public static float SmoothStep01(float value)
		{
			float num = Math.Clamp(value, 0f, 1f);
			return num * num * (3f - 2f * num);
		}

		public static bool IsIntentionalFlick(float lateralVelocity, float threshold)
		{
			return Math.Abs(lateralVelocity) >= Math.Max(0f, threshold);
		}
	}
	public enum HoloPerformanceTier
	{
		Full,
		Balanced,
		Minimal
	}
	public sealed class HoloPerformanceGovernor
	{
		private readonly bool _constrained;

		private double _averageFrameMs;

		private int _slowSamples;

		private int _fastSamples;

		public HoloPerformanceTier Tier { get; private set; }

		public int RendererLimit => Tier switch
		{
			HoloPerformanceTier.Full => 64, 
			HoloPerformanceTier.Balanced => 24, 
			_ => 12, 
		};

		public bool EffectsAllowed => Tier != HoloPerformanceTier.Minimal;

		public double AverageFrameMs => _averageFrameMs;

		public HoloPerformanceGovernor(bool constrained)
		{
			_constrained = constrained;
			Tier = (constrained ? HoloPerformanceTier.Balanced : HoloPerformanceTier.Full);
		}

		public bool Sample(double frameMilliseconds)
		{
			if (!_constrained || double.IsNaN(frameMilliseconds) || double.IsInfinity(frameMilliseconds))
			{
				return false;
			}
			double num = Math.Clamp(frameMilliseconds, 1.0, 100.0);
			_averageFrameMs = ((_averageFrameMs <= 0.0) ? num : (_averageFrameMs * 0.94 + num * 0.06));
			if (_averageFrameMs >= 21.0)
			{
				_slowSamples++;
				_fastSamples = 0;
			}
			else if (_averageFrameMs <= 15.5)
			{
				_fastSamples++;
				_slowSamples = 0;
			}
			else
			{
				_slowSamples = Math.Max(0, _slowSamples - 1);
				_fastSamples = Math.Max(0, _fastSamples - 1);
			}
			if (Tier == HoloPerformanceTier.Balanced && _slowSamples >= 45)
			{
				Tier = HoloPerformanceTier.Minimal;
				_slowSamples = 0;
				return true;
			}
			if (Tier == HoloPerformanceTier.Minimal && _fastSamples >= 240)
			{
				Tier = HoloPerformanceTier.Balanced;
				_fastSamples = 0;
				return true;
			}
			return false;
		}
	}
	public enum InstalledPackFocusState
	{
		None,
		Downloading,
		WaitingForPallet,
		Ready,
		TimedOut
	}
	public sealed class InstalledPackFocus
	{
		public long ModId { get; private set; }

		public int Generation { get; private set; }

		public double DeadlineSeconds { get; private set; }

		public InstalledPackFocusState State { get; private set; }

		public string SelectedBarcode { get; private set; } = string.Empty;

		public IReadOnlyList<string> InstallDirectories { get; private set; } = Array.Empty<string>();

		public bool Active => State != InstalledPackFocusState.None;

		public int Begin(long modId, double nowSeconds, IEnumerable<string>? installDirectories = null, double timeoutSeconds = 12.0)
		{
			Generation++;
			ModId = modId;
			DeadlineSeconds = nowSeconds + Math.Max(1.0, timeoutSeconds);
			State = InstalledPackFocusState.WaitingForPallet;
			SelectedBarcode = string.Empty;
			InstallDirectories = (installDirectories ?? Array.Empty<string>()).Where((string path) => !string.IsNullOrWhiteSpace(path)).Distinct<string>(StringComparer.OrdinalIgnoreCase).ToArray();
			return Generation;
		}

		public int BeginDownload(long modId)
		{
			Generation++;
			ModId = modId;
			DeadlineSeconds = double.PositiveInfinity;
			State = InstalledPackFocusState.Downloading;
			SelectedBarcode = string.Empty;
			InstallDirectories = Array.Empty<string>();
			return Generation;
		}

		public void MarkDownloaded(long modId, double nowSeconds, IEnumerable<string>? installDirectories, double timeoutSeconds = 12.0)
		{
			if (modId == ModId && State != InstalledPackFocusState.None)
			{
				UpdateDirectories(modId, installDirectories);
				DeadlineSeconds = nowSeconds + Math.Max(1.0, timeoutSeconds);
				State = InstalledPackFocusState.WaitingForPallet;
			}
		}

		public void UpdateDirectories(long modId, IEnumerable<string>? installDirectories)
		{
			if (modId == ModId && State != InstalledPackFocusState.None)
			{
				InstallDirectories = (installDirectories ?? Array.Empty<string>()).Where((string path) => !string.IsNullOrWhiteSpace(path)).Distinct<string>(StringComparer.OrdinalIgnoreCase).ToArray();
			}
		}

		public bool TryBind(long modId, IEnumerable<string?> barcodes)
		{
			if (modId != ModId || State == InstalledPackFocusState.None)
			{
				return false;
			}
			string text = barcodes.FirstOrDefault((string value) => !string.IsNullOrWhiteSpace(value))?.Trim();
			if (string.IsNullOrWhiteSpace(text))
			{
				return false;
			}
			SelectedBarcode = text;
			State = InstalledPackFocusState.Ready;
			return true;
		}

		public bool UpdateTimeout(double nowSeconds)
		{
			if (State != InstalledPackFocusState.WaitingForPallet || nowSeconds < DeadlineSeconds)
			{
				return false;
			}
			State = InstalledPackFocusState.TimedOut;
			return true;
		}

		public void Retry(double nowSeconds, double timeoutSeconds = 12.0)
		{
			if (Active)
			{
				DeadlineSeconds = nowSeconds + Math.Max(1.0, timeoutSeconds);
				State = InstalledPackFocusState.WaitingForPallet;
			}
		}

		public void Clear()
		{
			ModId = 0L;
			DeadlineSeconds = 0.0;
			State = InstalledPackFocusState.None;
			SelectedBarcode = string.Empty;
			InstallDirectories = Array.Empty<string>();
		}
	}
	public sealed class LibraryRefreshGate
	{
		public bool Pending { get; private set; }

		public double NotBeforeSeconds { get; private set; }

		public LibraryRefreshGate(bool initiallyPending = true)
		{
			Pending = initiallyPending;
		}

		public void Request(double nowSeconds, double debounceSeconds = 0.35)
		{
			double num = nowSeconds + Math.Max(0.0, debounceSeconds);
			NotBeforeSeconds = (Pending ? Math.Max(NotBeforeSeconds, num) : num);
			Pending = true;
		}

		public bool TryConsume(double nowSeconds)
		{
			if (!Pending || nowSeconds < NotBeforeSeconds)
			{
				return false;
			}
			Pending = false;
			NotBeforeSeconds = 0.0;
			return true;
		}
	}
	public readonly record struct MiniMapOverlayColor(byte R, byte G, byte B, byte A = byte.MaxValue)
	{
		public static readonly MiniMapOverlayColor Outline = new MiniMapOverlayColor(0, 15, 24, 235);
	}
	public enum MiniMapPeerRole
	{
		Player,
		Operator,
		Owner
	}
	public static class MiniMapPeerColors
	{
		public static readonly MiniMapOverlayColor Cyan = new MiniMapOverlayColor(44, 220, byte.MaxValue);

		public static readonly MiniMapOverlayColor OperatorViolet = new MiniMapOverlayColor(190, 112, byte.MaxValue);

		public static readonly MiniMapOverlayColor OwnerAm

Mods\WristHub.dll

Decompiled 2 months ago
using System;
using System.Collections;
using System.Collections.Concurrent;
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.Numerics;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.Versioning;
using System.Security.Cryptography;
using System.Text;
using System.Text.Json;
using System.Threading;
using System.Threading.Tasks;
using BoneHub.Api;
using BoneHub.Avatars;
using BoneHub.Compatibility;
using BoneHub.Configuration;
using BoneHub.Downloads;
using BoneHub.Installation;
using BoneHub.Interaction;
using BoneHub.Mod;
using BoneHub.Models;
using BoneHub.Persistence;
using BoneLib;
using BoneLib.BoneMenu;
using HarmonyLib;
using Il2CppCysharp.Threading.Tasks;
using Il2CppInterop.Runtime.InteropTypes;
using Il2CppInterop.Runtime.InteropTypes.Arrays;
using Il2CppSLZ.Bonelab;
using Il2CppSLZ.Bonelab.SaveData;
using Il2CppSLZ.Marrow;
using Il2CppSLZ.Marrow.Data;
using Il2CppSLZ.Marrow.Forklift.Model;
using Il2CppSLZ.Marrow.Interaction;
using Il2CppSLZ.Marrow.Pool;
using Il2CppSLZ.Marrow.SaveData;
using Il2CppSLZ.Marrow.SceneStreaming;
using Il2CppSLZ.Marrow.Utilities;
using Il2CppSLZ.Marrow.Warehouse;
using Il2CppSLZ.VRMK;
using Il2CppSystem;
using Il2CppSystem.Collections.Generic;
using Il2CppTMPro;
using LabFusion.Entities;
using LabFusion.Marrow;
using LabFusion.Marrow.Extenders;
using LabFusion.Marrow.Patching;
using LabFusion.Marrow.Pool;
using LabFusion.Menu;
using LabFusion.Network;
using LabFusion.Player;
using LabFusion.RPC;
using LabFusion.Representation;
using LabFusion.SDK.Lobbies;
using LabFusion.SDK.Modules;
using LabFusion.Scene;
using LabFusion.Senders;
using MelonLoader;
using MelonLoader.Preferences;
using MelonLoader.Utils;
using UnityEngine;
using UnityEngine.Networking;
using UnityEngine.Rendering;
using UnityEngine.SceneManagement;
using UnityEngine.UI;
using WristHub.SDK;

[assembly: CompilationRelaxations(8)]
[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
[assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)]
[assembly: MelonInfo(typeof(BoneHubMod), "WristHubV4", "4.2.1", "WristHub contributors", null)]
[assembly: MelonGame("Stress Level Zero", "BONELAB")]
[assembly: AssemblyMetadata("WristHubPlatform", "QuestStandalone")]
[assembly: TargetFramework(".NETCoreApp,Version=v8.0", FrameworkDisplayName = ".NET 8.0")]
[assembly: AssemblyCompany("WristHub")]
[assembly: AssemblyConfiguration("Release")]
[assembly: AssemblyFileVersion("4.2.1.0")]
[assembly: AssemblyInformationalVersion("4.2.1")]
[assembly: AssemblyProduct("WristHub")]
[assembly: AssemblyTitle("WristHub")]
[assembly: AssemblyVersion("4.2.1.0")]
[module: RefSafetyRules(11)]
namespace BoneHub.Mod;

internal sealed class BoneHubAvatarBrowser : IDisposable
{
	private sealed class AppLibraryTile
	{
		public TouchButton Button { get; init; }

		public RawImage Artwork { get; init; }

		public TMP_Text Monogram { get; init; }

		public TMP_Text Name { get; init; }

		public TMP_Text Badge { get; init; }

		public WristAppLibraryItem? Item { get; set; }

		public string ArtworkIdentity { get; set; } = string.Empty;
	}

	private sealed record WristAppLibraryItem(WristHubApp? App, ThunderstorePackageEntry? Package)
	{
		public string Identity
		{
			get
			{
				WristHubApp? app = App;
				return ((app != null) ? app.Id : null) ?? ("package:" + (Package?.PackageNamespace ?? string.Empty));
			}
		}

		public string Name
		{
			get
			{
				WristHubApp? app = App;
				return ((app != null) ? app.Name : null) ?? Package?.Name ?? "Unknown app";
			}
		}

		public string Author
		{
			get
			{
				WristHubApp? app = App;
				return ((app != null) ? app.Author : null) ?? Package?.Author ?? "Unknown developer";
			}
		}

		public string Version
		{
			get
			{
				WristHubApp? app = App;
				return ((app != null) ? app.Version : null) ?? Package?.Version ?? string.Empty;
			}
		}

		public string Description
		{
			get
			{
				WristHubApp? app = App;
				object obj;
				if (app == null)
				{
					obj = null;
				}
				else
				{
					WristHubStoreProfile store = app.Store;
					obj = ((store != null) ? store.Tagline : null);
				}
				if (obj == null)
				{
					WristHubApp? app2 = App;
					if (app2 == null)
					{
						obj = null;
					}
					else
					{
						WristHubCommunityProfile community = app2.Community;
						obj = ((community != null) ? community.Summary : null);
					}
					if (obj == null)
					{
						WristHubApp? app3 = App;
						obj = ((app3 != null) ? app3.Description : null) ?? Package?.Description ?? string.Empty;
					}
				}
				return (string)obj;
			}
		}
	}

	private enum ButtonIconKind
	{
		None,
		Back,
		Forward,
		Close,
		Search,
		Settings,
		Download,
		Avatar,
		Apps,
		Map,
		Compass,
		Portal,
		Director,
		Fusion,
		Players,
		Gamemode,
		QuickJoin,
		Home,
		Refresh,
		Equip,
		Delete,
		Join
	}

	private sealed class TouchButton
	{
		public Transform Space { get; init; }

		public GameObject Root { get; init; }

		public Image Panel { get; init; }

		public TMP_Text Label { get; init; }

		public Vector3 Center { get; set; }

		public Vector3 Half { get; init; }

		public Action Action { get; init; }

		public bool UsesThemeColor { get; init; }

		public float ThemeStrength { get; init; }

		public TouchDebounce Debounce { get; } = new TouchDebounce();

		public bool IsAlive
		{
			get
			{
				try
				{
					return (Object)(object)Root != (Object)null && (Object)(object)Space != (Object)null && (Object)(object)Panel != (Object)null && (Object)(object)Label != (Object)null;
				}
				catch
				{
					return false;
				}
			}
		}

		public bool Contains(Vector3 worldPoint, float hitInflation = 1f)
		{
			//IL_0036: Unknown result type (might be due to invalid IL or missing references)
			//IL_0037: Unknown result type (might be due to invalid IL or missing references)
			//IL_003d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0042: Unknown result type (might be due to invalid IL or missing references)
			//IL_0047: Unknown result type (might be due to invalid IL or missing references)
			//IL_0059: Unknown result type (might be due to invalid IL or missing references)
			//IL_0065: Unknown result type (might be due to invalid IL or missing references)
			//IL_0073: Unknown result type (might be due to invalid IL or missing references)
			//IL_007f: Unknown result type (might be due to invalid IL or missing references)
			//IL_008d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0099: Unknown result type (might be due to invalid IL or missing references)
			try
			{
				if ((Object)(object)Root == (Object)null || (Object)(object)Space == (Object)null || !Root.activeInHierarchy)
				{
					return false;
				}
				Vector3 val = Space.InverseTransformPoint(worldPoint) - Center;
				float num = Mathf.Clamp(hitInflation, 1f, 1.6f);
				return Mathf.Abs(val.x) <= Half.x * num && Mathf.Abs(val.y) <= Half.y * num && Mathf.Abs(val.z) <= Half.z * num;
			}
			catch
			{
				return false;
			}
		}
	}

	private enum AvatarPreviewStage
	{
		ResolvingCrate,
		NativeRequested,
		NativeReady,
		FullColorRequested,
		Ready,
		Failed
	}

	private sealed class AvatarPreviewSession
	{
		public int Generation { get; init; }

		public string Barcode { get; init; } = string.Empty;

		public string PalletRevision { get; set; } = string.Empty;

		public AvatarPreviewStage Stage { get; set; }

		public int NativeAttempt { get; set; }

		public bool NativeResolved { get; set; }

		public bool FullColorRequested { get; set; }

		public bool Cancelled { get; set; }

		public float Deadline { get; init; }

		public PreviewCallbackLease Callbacks { get; } = new PreviewCallbackLease();
	}

	private sealed record RetiredPreviewCallbacks(PreviewCallbackLease Lease, float ReleaseAt);

	private sealed record CachedNativeAvatarPreview(Mesh Mesh, string Revision);

	private sealed record NativeAvatarPrefetch(PreviewCallbackLease Lease, float ExpiresAt);

	private sealed class QuestBootstrapScheduler
	{
		private readonly Stopwatch _clock = new Stopwatch();

		private long _phaseStarted;

		public double TotalMilliseconds => _clock.Elapsed.TotalMilliseconds;

		public void Start()
		{
			_clock.Restart();
			_phaseStarted = 0L;
		}

		public double FinishPhase()
		{
			long elapsedMilliseconds = _clock.ElapsedMilliseconds;
			long num = elapsedMilliseconds - _phaseStarted;
			_phaseStarted = elapsedMilliseconds;
			return num;
		}
	}

	private sealed class PackRow
	{
		public TouchButton Button { get; init; }

		public TouchButton DeleteButton { get; init; }

		public RawImage Artwork { get; init; }

		public TMP_Text Name { get; init; }

		public TMP_Text Creator { get; init; }

		public TMP_Text State { get; init; }

		public string Identity { get; set; } = string.Empty;

		public int Generation { get; set; }

		public bool IsAlive
		{
			get
			{
				if (Button.IsAlive && DeleteButton.IsAlive && (Object)(object)Artwork != (Object)null && (Object)(object)Name != (Object)null && (Object)(object)Creator != (Object)null)
				{
					return (Object)(object)State != (Object)null;
				}
				return false;
			}
		}
	}

	private sealed class FriendRowVisual
	{
		public Image Portrait { get; init; }

		public TMP_Text Initials { get; init; }

		public TMP_Text Name { get; init; }

		public TMP_Text Detail { get; init; }

		public TMP_Text Presence { get; init; }
	}

	private enum IconPrimitiveType
	{
		Line,
		Ring,
		Disc,
		Box
	}

	private readonly record struct IconPrimitive(IconPrimitiveType Type, Vector2 A, Vector2 B, float Radius, float Stroke);

	private enum ModStoreCatalog
	{
		Thunderstore,
		ModIo
	}

	private enum PortalPlacementStage
	{
		None,
		LocalEntrance,
		LocalExit,
		WorldEntrance,
		FusionServerEntrance
	}

	private sealed class SpawnGeometryProfile
	{
		public Bounds LocalBounds { get; init; }

		public Vector3 HandleDirectionLocal { get; init; }

		public Quaternion PreviewRotation { get; init; }

		public string Source { get; init; } = "fallback";

		public int PalletRevision { get; init; }

		public bool Melee { get; init; }
	}

	private sealed class FrozenSpawnRecord
	{
		public GameObject Root { get; init; }

		public ReversibleFreezeState Physics { get; init; }

		public float CreatedAt { get; init; }

		public float Deadline { get; init; }

		public bool HandWasAway { get; set; }

		public float NextInteractionCheckAt { get; set; }
	}

	private enum WristKeyboardPurpose
	{
		Avatars,
		CodeMods,
		ModStore,
		WristApps,
		Spawnables,
		PortalLevels,
		FusionServerCode,
		CodeModString,
		SdkText,
		FriendsMessage,
		FriendCode,
		FriendRecovery,
		FriendLobbySearch,
		FriendNickname,
		FriendNote,
		FusionLobbySetting
	}

	private enum CodeModCatalogMode
	{
		All,
		Community,
		Managers,
		BoneMenu,
		Loaded
	}

	private sealed class SpawnLabRow
	{
		public TouchButton Button { get; init; }

		public string Identity { get; set; } = string.Empty;

		public bool IsAlive => Button.IsAlive;
	}

	private sealed record SpawnLabBrowserItem(SpawnableEntry? Entry, SpawnableGroupEntry? Group, ModIoMod? OnlineMod)
	{
		public string Identity
		{
			get
			{
				SpawnableEntry? entry = Entry;
				object obj = ((entry != null) ? entry.Identity : null);
				if (obj == null)
				{
					SpawnableGroupEntry? obj2 = Group;
					obj = ((obj2 != null) ? obj2.Identity : null);
					if (obj == null)
					{
						if (OnlineMod != null)
						{
							return "online:" + OnlineMod.Id;
						}
						obj = string.Empty;
					}
				}
				return (string)obj;
			}
		}
	}

	private sealed class SpawnOwnershipRecord
	{
		public GameObject Spawned { get; init; }

		public string Barcode { get; init; } = string.Empty;

		public ushort? FusionEntityId { get; init; }

		public bool NetworkOwned { get; init; }

		public long RequestId { get; init; }

		public int Generation { get; init; }

		public bool Orphaned { get; init; }
	}

	private sealed class OsControlRow
	{
		public TouchButton Main { get; init; }

		public TouchButton Minus { get; init; }

		public TouchButton Plus { get; init; }

		public TMP_Text Detail { get; init; }

		public object? Source { get; set; }
	}

	private sealed class MiniMapPeerVisual
	{
		public int PeerId { get; set; } = -1;

		public bool Active { get; set; }

		public Vector3 TargetWorldPosition { get; set; }

		public Vector3 WorldPosition { get; set; }

		public float TargetHeadingDegrees { get; set; }

		public float HeadingDegrees { get; set; }

		public MiniMapPeerRole Role { get; set; }

		public Transform? TrackingTransform { get; set; }

		public bool HasDisplayPose { get; set; }

		public float LastSeenAt { get; set; }
	}

	private sealed record CodeModSnapshot(string Name, string Detail, object? Page, object? Element = null);

	private sealed record LoadedCodeModSnapshot(string Name, string Author, string Version);

	private sealed class ReflectedManagerControl
	{
		private readonly Func<string> _detail;

		private readonly Action _activate;

		private readonly Action<int>? _adjust;

		public string Label { get; }

		public string Detail => _detail();

		public bool Adjustable => _adjust != null;

		public ReflectedManagerControl(string label, Func<string> detail, Action activate, Action<int>? adjust = null)
		{
			Label = label;
			_detail = detail;
			_activate = activate;
			_adjust = adjust;
		}

		public void Activate()
		{
			_activate();
		}

		public void Adjust(int direction)
		{
			_adjust?.Invoke(direction);
		}
	}

	private sealed class RuntimeSdkHost : IWristHubHost
	{
		private readonly BoneHubAvatarBrowser _owner;

		public WristHubRuntimeInfo Runtime => new WristHubRuntimeInfo(true, _owner._constrainedRendering, IsFusionConnected(), ((object)_owner._preferences.HologramTheme/*cast due to .constrained prefix*/).ToString(), 4);

		public RuntimeSdkHost(BoneHubAvatarBrowser owner)
		{
			_owner = owner;
		}

		public void Notify(string appId, string message, WristHubNotificationKind kind)
		{
			//IL_0002: Unknown result type (might be due to invalid IL or missing references)
			WristNotificationService.PublishSdk(appId, message, kind);
		}

		public void Notify(string appId, WristHubNotificationDefinition notification)
		{
			WristNotificationService.PublishSdk(appId, notification);
		}

		public void RequestOpen(string appId)
		{
			_owner._sdkOpenRequests.Enqueue(appId);
		}
	}

	private readonly record struct SdkNotification(string AppId, string Message, WristHubNotificationKind Kind)
	{
		[CompilerGenerated]
		public void Deconstruct(out string AppId, out string Message, out WristHubNotificationKind Kind)
		{
			//IL_0012: Unknown result type (might be due to invalid IL or missing references)
			//IL_0018: Expected I4, but got Unknown
			AppId = this.AppId;
			Message = this.Message;
			Kind = (WristHubNotificationKind)(int)this.Kind;
		}
	}

	private sealed class RuntimeWristOsApp : IWristOsApp
	{
		private readonly Action _open;

		public WristOsAppId Id { get; }

		public string DisplayName { get; }

		public bool IsAvailable => true;

		public int NotificationCount => 0;

		public RuntimeWristOsApp(WristOsAppId id, string name, Action open)
		{
			//IL_0007: Unknown result type (might be due to invalid IL or missing references)
			//IL_0008: Unknown result type (might be due to invalid IL or missing references)
			Id = id;
			DisplayName = name;
			_open = open;
		}

		public void Open()
		{
			_open();
		}

		public void Close()
		{
		}

		public void Tick(double nowSeconds)
		{
		}

		public bool HandleBack()
		{
			return false;
		}
	}

	private const int AppLibraryTilesPerPage = 8;

	private readonly List<AppLibraryTile> _appLibraryTiles = new List<AppLibraryTile>(8);

	private readonly List<AppLibraryTile> _homeAppTiles = new List<AppLibraryTile>(9);

	private readonly List<WristAppLibraryItem> _appLibraryItems = new List<WristAppLibraryItem>();

	private readonly List<ThunderstorePackageEntry> _sdkStorePackages = new List<ThunderstorePackageEntry>();

	private readonly Dictionary<string, Texture2D> _sdkRichIcons = new Dictionary<string, Texture2D>(StringComparer.OrdinalIgnoreCase);

	private readonly Queue<string> _sdkRichIconOrder = new Queue<string>();

	private GameObject? _osHomePageOne;

	private GameObject? _osHomePageTwo;

	private TMP_Text? _osHomePageDotsOne;

	private TMP_Text? _osHomePageDotsTwo;

	private int _osHomePageIndex;

	private GameObject? _appLibraryPage;

	private GameObject? _appLibraryGridRoot;

	private GameObject? _appLibraryDetailRoot;

	private TMP_Text? _appLibraryTitle;

	private TMP_Text? _appLibraryPosition;

	private TMP_Text? _appLibraryRestartBanner;

	private TouchButton? _appLibraryInstalledTab;

	private TouchButton? _appLibraryStoreTab;

	private TouchButton? _appLibraryUpdatesTab;

	private TouchButton? _appLibraryManageButton;

	private TMP_Text? _appDetailName;

	private TMP_Text? _appDetailAuthor;

	private TMP_Text? _appDetailDescription;

	private TMP_Text? _appDetailStatus;

	private TouchButton? _appDetailAction;

	private TouchButton? _appDetailPin;

	private TouchButton? _appDetailUninstall;

	private WristAppLibraryItem? _appLibraryDetailItem;

	private int _appLibraryPageIndex;

	private bool _appLibraryManageMode;

	private float _nextAppLibraryRefresh;

	private int _appArtworkGeneration;

	private bool _appLibrarySdkEventsAttached;

	private int _homeSwipeProbeId;

	private float _homeSwipeStartX;

	private const float ChestRadius = 0.22f;

	private static readonly Color Glass = new Color(0.012f, 0.027f, 0.04f, 1f);

	private static readonly Color Surface = new Color(0.025f, 0.075f, 0.105f, 0.96f);

	private static readonly Color Green = new Color(0.32f, 0.9f, 0.62f, 1f);

	private static readonly Color Amber = new Color(1f, 0.67f, 0.22f, 1f);

	private static readonly Color Red = new Color(1f, 0.38f, 0.46f, 1f);

	private readonly BoneHubService _service;

	private readonly BoneHubRuntime _runtime;

	private readonly BoneHubThumbnailCache _thumbnails;

	private readonly ConcurrentQueue<Action> _mainThread;

	private readonly BoneHubPreferences _preferences;

	private readonly bool _constrainedRendering;

	private bool _dashboardBuildPending;

	private bool _questBootstrapStarted;

	private bool _questOpenPending;

	private float _nextQuestPortalAttachAt;

	private bool _dashboardPagesBuilt;

	private readonly Instance _log;

	private readonly LobbyAvatarSharingBridge _sharing;

	private readonly List<TouchButton> _buttons = new List<TouchButton>();

	private readonly List<TouchButton> _activeButtons = new List<TouchButton>();

	private float _nextActiveButtonRefresh;

	private int _activeButtonSourceCount = -1;

	private readonly Dictionary<int, TouchButton> _touchCaptures = new Dictionary<int, TouchButton>();

	private readonly HashSet<int> _activeProbeIds = new HashSet<int>();

	private readonly List<int> _staleCaptureIds = new List<int>(2);

	private readonly Dictionary<long, ModIoMod> _onlineMods = new Dictionary<long, ModIoMod>();

	private readonly Dictionary<long, DownloadJob> _jobs = new Dictionary<long, DownloadJob>();

	private readonly Dictionary<Guid, DownloadState> _observedDownloadStates = new Dictionary<Guid, DownloadState>();

	private readonly Dictionary<long, IReadOnlyList<Guid>> _downloadBatches = new Dictionary<long, IReadOnlyList<Guid>>();

	private readonly Dictionary<string, AvatarPreviewMetrics> _previewMetrics = new Dictionary<string, AvatarPreviewMetrics>(StringComparer.OrdinalIgnoreCase);

	private readonly Dictionary<string, AvatarPreviewRouteMetadata> _previewRoutes = new Dictionary<string, AvatarPreviewRouteMetadata>(StringComparer.OrdinalIgnoreCase);

	private readonly Dictionary<string, AvatarRigValidationResult> _rigValidations = new Dictionary<string, AvatarRigValidationResult>(StringComparer.OrdinalIgnoreCase);

	private readonly Dictionary<int, RetiredPreviewCallbacks> _retiredPreviewCallbacks = new Dictionary<int, RetiredPreviewCallbacks>();

	private readonly Dictionary<string, CachedNativeAvatarPreview> _nativeAvatarPreviewCache = new Dictionary<string, CachedNativeAvatarPreview>(StringComparer.OrdinalIgnoreCase);

	private readonly LinkedList<string> _nativeAvatarPreviewLru = new LinkedList<string>();

	private readonly Dictionary<string, NativeAvatarPrefetch> _nativeAvatarPrefetches = new Dictionary<string, NativeAvatarPrefetch>(StringComparer.OrdinalIgnoreCase);

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

	private readonly HashSet<long> _startingDownloads = new HashSet<long>();

	private readonly BoneHubFingertipProbeProvider _fingertips = BoneHubFingertipProbeProvider.Shared;

	private readonly TabletKeyboardBuffer _keyboard = new TabletKeyboardBuffer();

	private readonly LookAwayDismissal _promptDismissal = new LookAwayDismissal();

	private readonly FocusedResultCursor _cursor = new FocusedResultCursor();

	private readonly FourRowPageCursor _packCursor = new FourRowPageCursor();

	private readonly PresentationGeneration _artworkGeneration = new PresentationGeneration();

	private readonly ErrorLogThrottle _runtimeErrorThrottle = new ErrorLogThrottle();

	private readonly SurfaceInputRearmGate _inputRearm = new SurfaceInputRearmGate(0.1);

	private readonly InstalledPackFocus _installedFocus = new InstalledPackFocus();

	private readonly BoneHubForearmProjectionAnchorProvider _projectionAnchors = new BoneHubForearmProjectionAnchorProvider();

	private readonly List<PackRow> _packRows = new List<PackRow>();

	private readonly Action _projectionTickAction;

	private readonly Action _projectionRecoveryAction;

	private readonly Action _touchTickAction;

	private readonly Action _touchRecoveryAction;

	private readonly Action _installationFocusTickAction;

	private readonly Action _previewTickAction;

	private readonly Action _previewRecoveryAction;

	private readonly Action _downloadTickAction;

	private readonly Action _sharingTickAction;

	private readonly Action _compassTickAction;

	private readonly Action<PortalLinkState> _portalStateReceiver;

	private readonly Action<int> _portalRemoveReceiver;

	private IReadOnlyList<AvatarEntry> _entries = Array.Empty<AvatarEntry>();

	private IReadOnlyList<AvatarEntry> _mergedEntries = Array.Empty<AvatarEntry>();

	private IReadOnlyList<AvatarEntry> _onlineEntries = Array.Empty<AvatarEntry>();

	private IReadOnlyList<AvatarEntry> _localAvatarEntries = Array.Empty<AvatarEntry>();

	private bool _localAvatarEntriesDirty = true;

	private IReadOnlyList<AvatarPackEntry> _packs = Array.Empty<AvatarPackEntry>();

	private GameObject? _root;

	private GameObject? _launcher;

	private GameObject? _hub;

	private GameObject? _browser;

	private GameObject? _canvasRoot;

	private GameObject? _packPage;

	private GameObject? _detailPage;

	private GameObject? _optionsPage;

	private GameObject? _watchPlacementPage;

	private GameObject? _downloadsPage;

	private GameObject? _packOptionsPage;

	private GameObject? _keyboardPage;

	private GameObject? _deletePage;

	private GameObject? _compassPage;

	private GameObject? _denseGlassChrome;

	private GameObject? _denseGlassBackground;

	private GameObject? _denseGlassOverlay;

	private GameObject? _hubPhysicalSurface;

	private GameObject? _browserPhysicalSurface;

	private readonly List<(Collider Surface, Collider Ignored)> _physicalUiIgnoredPairs = new List<(Collider, Collider)>();

	private readonly List<(Collider Surface, Collider Hand)> _physicalUiDeferredPairs = new List<(Collider, Collider)>();

	private readonly List<(Collider Surface, Collider Hand)> _physicalUiArmedPairs = new List<(Collider, Collider)>();

	private PhysicMaterial? _physicalUiMaterial;

	private int _physicalUiRigId;

	private int _physicalUiColliderSignature;

	private bool _physicalUiLeftWrist;

	private bool _physicalUiFilterActive;

	private float _nextPhysicalUiFilterRefresh;

	private bool _wristStabilizerValid;

	private int _wristStabilizerRigId;

	private Vector3 _wristStabilizerRigPosition;

	private Quaternion _wristStabilizerRigRotation = Quaternion.identity;

	private GameObject? _leftArcWing;

	private GameObject? _rightArcWing;

	private GameObject? _metadataArc;

	private CanvasGroup? _canvasGroup;

	private TMP_Text? _packTitle;

	private TMP_Text? _packStatus;

	private TMP_Text? _packPosition;

	private TMP_Text? _packSearchText;

	private TMP_Text? _sortText;

	private TMP_Text? _wristText;

	private TMP_Text? _scaleText;

	private TMP_Text? _motionText;

	private TMP_Text? _themeText;

	private TMP_Text? _clockText;

	private TMP_Text? _tapModeText;

	private TMP_Text? _startingViewText;

	private TMP_Text? _watchForearmText;

	private TMP_Text? _watchSleepLiftText;

	private TMP_Text? _watchStyleText;

	private readonly Dictionary<WristWatchStyle, TouchButton> _watchStyleButtons = new Dictionary<WristWatchStyle, TouchButton>();

	private TMP_Text? _watchAttachmentText;

	private TMP_Text? _watchBodyScaleText;

	private TMP_Text? _scopeText;

	private TMP_Text? _downloadsSummary;

	private TMP_Text? _deleteTitle;

	private TMP_Text? _deleteDetails;

	private TMP_Text? _keyboardText;

	private TMP_Text? _keyboardTitle;

	private TMP_Text? _keyboardShiftText;

	private TMP_Text? _searchText;

	private TMP_Text? _compassHeadingText;

	private TMP_Text? _compassCalibrationText;

	private RectTransform? _compassNeedle;

	private RectTransform? _compassDecorRing;

	private TMP_Text? _positionText;

	private TMP_Text? _selectedName;

	private TMP_Text? _creatorText;

	private TMP_Text? _modText;

	private TMP_Text? _statusText;

	private TMP_Text? _heightText;

	private GameObject? _heightRuler;

	private GameObject? _progressRoot;

	private RectTransform? _progressFill;

	private TMP_Text? _progressText;

	private TMP_Text? _actionLabel;

	private Image? _actionPanel;

	private TouchButton? _trashButton;

	private RawImage? _centralArtwork;

	private Texture2D? _placeholderTexture;

	private Texture2D? _roundedTexture;

	private Sprite? _roundedSprite;

	private BoneHubForearmProjection? _hologramProjection;

	private BoneHubHologramCarousel? _hologramCarousel;

	private BoneHubChestEquipGuide? _chestEquipGuide;

	private TouchButton? _actionButton;

	private GameObject? _previewShell;

	private GameObject? _previewFallback;

	private GameObject? _previewModel;

	private Material? _nativePreviewMaterial;

	private AvatarPreviewSession? _previewSession;

	private PreviewResourceHandle? _avatarPreviewResources;

	private readonly List<Mesh> _ownedPreviewMeshes = new List<Mesh>();

	private BoneHubAvatar? _previewAvatar;

	private BoneHubAvatar? _pendingPreviewAvatar;

	private string _loadedPreviewBarcode = string.Empty;

	private string _requestedPreviewBarcode = string.Empty;

	private float _previewLoadAt;

	private bool _previewReady;

	private bool _previewInteractionReady;

	private AvatarPreviewVisualSource _previewVisualSource;

	private bool _previewHeld;

	private Hand? _dragHand;

	private bool _dragIsLeft;

	private bool _dragUsesPinch;

	private Vector3 _dragLocalOffset;

	private Quaternion _dragRotationOffset = Quaternion.identity;

	private float _dragTrackingLostAt = -1f;

	private bool _dragWasInsideChest;

	private bool _previewReturning;

	private float _returnStarted;

	private Vector3 _returnFrom;

	private Quaternion _returnRotation;

	private int _previewGeneration;

	private float _nextPreviewCallbackMaintenanceAt;

	private int _searchGeneration;

	private string _query = string.Empty;

	private bool _downloadsOnly;

	private string _selectedIdentity = string.Empty;

	private string _trashConfirmIdentity = string.Empty;

	private bool _deleteReturnToPackPage;

	private bool _trashInProgress;

	private long? _selectedPackId;

	private AvatarPackSort _packSort;

	private AvatarBrowseScope _scope;

	private AvatarBrowseScope _scopeBeforeSearch;

	private Guid _progressJobId;

	private double _displayedProgress;

	private double _targetProgress;

	private int _lastProgressPercent = -1;

	private AvatarDownloadState? _lastProgressState;

	private AvatarBrowserViewState _viewState = (AvatarBrowserViewState)2;

	private AvatarBrowserViewState _viewStateBeforeKeyboard = (AvatarBrowserViewState)2;

	private bool _disposed;

	private bool _wasPlayerDead;

	private AvatarSharingState _sharingState;

	private BoneHubAvatar? _pendingSharingAvatar;

	private ModIoSharingMetadata? _pendingSharingMetadata;

	private string _sharingRetryBarcode = string.Empty;

	private float _nextSharingCheckAt;

	private float _sharingDeadline;

	private bool _sharingRefreshRequested;

	private string _carouselIdentity = string.Empty;

	private float _hubControlsReadyAt;

	private HologramDashboardLifecycle _lifecycle;

	private float _lifecycleStartedAt;

	private bool _closeNotificationPending;

	private HologramDashboardProgress _closeVisualStart = new HologramDashboardProgress((HologramProjectionPhase)5, 1f, 1f, 1f, 1f, 1f, true);

	private string _compassSceneKey = "unknown-scene";

	private float _compassSmoothedHeading;

	private bool _compassHeadingValid;

	private int _lastCompassDegree = -1;

	private const int FriendRowsPerPage = 3;

	private GameObject? _friendsPage;

	private TMP_Text? _friendsTitle;

	private TMP_Text? _friendsStatus;

	private TMP_Text? _friendsCodeText;

	private TMP_Text? _friendsEmptyState;

	private TMP_Text? _friendsPageIndicator;

	private readonly List<TouchButton> _friendTabs = new List<TouchButton>(4);

	private readonly List<TouchButton> _friendRows = new List<TouchButton>(3);

	private readonly List<FriendRowVisual> _friendRowVisuals = new List<FriendRowVisual>(3);

	private readonly List<TouchButton> _friendActions = new List<TouchButton>(4);

	private NativeSocialSnapshot _friendSnapshot = new NativeSocialSnapshot(Available: true, InLobby: false, Array.Empty<NativeFriendPerson>(), Array.Empty<NativeFriendPerson>(), Array.Empty<NativeFriendPerson>(), Array.Empty<NativeFriendPerson>(), Array.Empty<NativeFriendPerson>(), "WRISTHUB FRIENDS • OFFLINE CACHE");

	private NativeFriendListKind _friendListKind;

	private int _friendPageIndex;

	private int _selectedFriendIndex = -1;

	private ulong _friendMessageTarget;

	private ulong _friendEditTarget;

	private bool _friendManageMode;

	private float _nextFriendRefresh;

	private string _friendLobbyQuery = string.Empty;

	private IReadOnlyList<NativeFriendPerson> _visibleFriendList = Array.Empty<NativeFriendPerson>();

	private readonly HashSet<ulong> _knownIncomingFriendRequests = new HashSet<ulong>();

	private float _nextFriendNotificationPoll;

	private int _pendingFriendRequestCount;

	private GameObject? _fusionHomePage;

	private GameObject? _fusionPlayersPage;

	private TMP_Text? _fusionHomeTitle;

	private TMP_Text? _fusionHomeSummary;

	private TMP_Text? _fusionHomeStatus;

	private TouchButton? _fusionDisconnectButton;

	private readonly List<TouchButton> _fusionPlayerRows = new List<TouchButton>(4);

	private readonly List<RawImage> _fusionPlayerImages = new List<RawImage>(4);

	private readonly List<TMP_Text> _fusionPlayerMonograms = new List<TMP_Text>(4);

	private readonly int[] _fusionPlayerImageGenerations = new int[4];

	private readonly string[] _fusionPlayerImageKeys = new string[4];

	private FusionServerEntry? _fusionPlayersSource;

	private int _fusionPlayerPageIndex;

	private TMP_Text? _fusionPlayerPosition;

	private TMP_Text? _fusionPlayerStatus;

	private GameObject? _fusionHostingPage;

	private FusionLobbySettingsAdapter? _fusionHosting;

	private readonly List<TouchButton> _fusionHostingRows = new List<TouchButton>(4);

	private TMP_Text? _fusionHostingPosition;

	private TMP_Text? _fusionHostingStatus;

	private int _fusionHostingPageIndex;

	private FusionLobbySetting? _fusionTextSetting;

	private readonly Dictionary<ButtonIconKind, Sprite> _buttonIconSprites = new Dictionary<ButtonIconKind, Sprite>();

	private readonly List<Texture2D> _buttonIconTextures = new List<Texture2D>();

	private Sprite? _miniMapRingSprite;

	private Sprite? _miniMapMaskSprite;

	private readonly ThunderstoreAssistantBridge _thunderstoreStore = new ThunderstoreAssistantBridge();

	private readonly ModIoSubscriptionSyncAdapter _modIoSubscriptionSync = new ModIoSubscriptionSyncAdapter();

	private readonly List<ThunderstorePackageEntry> _modStoreResults = new List<ThunderstorePackageEntry>();

	private readonly List<ModIoMod> _itemStoreResults = new List<ModIoMod>();

	private GameObject? _modStorePage;

	private TMP_Text? _modStoreName;

	private TMP_Text? _modStoreAuthor;

	private TMP_Text? _modStoreDescription;

	private TMP_Text? _modStorePosition;

	private TMP_Text? _modStoreStatus;

	private TouchButton? _modStoreAction;

	private TouchButton? _modStoreSyncAction;

	private TouchButton? _modStoreSecondaryAction;

	private TouchButton? _modStoreThunderstoreTab;

	private TouchButton? _modStoreModIoTab;

	private string _thunderstoreQuery = string.Empty;

	private int _thunderstoreIndex;

	private string _itemStoreQuery = string.Empty;

	private int _itemStoreIndex;

	private float _nextModStoreRefresh;

	private ModStoreCatalog _modStoreCatalog;

	private int _itemStoreSearchGeneration;

	private bool _itemStoreSearching;

	private long _itemStorePendingModId;

	private readonly Dictionary<long, float> _itemStoreNextRegistrationScan = new Dictionary<long, float>();

	private readonly MonoDirectorRuntimeBridge _monoDirector = new MonoDirectorRuntimeBridge();

	private GameObject? _monoDirectorPage;

	private TMP_Text? _monoDirectorStatus;

	private TMP_Text? _monoDirectorVersion;

	private TouchButton? _monoDirectorInstallButton;

	private TouchButton? _monoDirectorResetButton;

	private float _nextMonoDirectorRefresh;

	private float _monoDirectorCommandReadyAt;

	private float _monoDirectorConfirmationDeadline;

	private string _pendingMonoDirectorCommand = string.Empty;

	private float _monoDirectorResetConfirmUntil;

	private readonly RaycastHit[] _portalRayHits = (RaycastHit[])(object)new RaycastHit[12];

	private readonly PortalTraversalGuard _portalEntranceGuard = new PortalTraversalGuard();

	private readonly PortalTraversalGuard _portalExitGuard = new PortalTraversalGuard();

	private readonly List<PortalDestinationEntry> _portalLevels = new List<PortalDestinationEntry>();

	private readonly List<PortalDestinationEntry> _installedPortalLevels = new List<PortalDestinationEntry>();

	private readonly List<FusionServerEntry> _fusionServers = new List<FusionServerEntry>();

	private readonly List<TouchButton> _fusionServerRows = new List<TouchButton>();

	private readonly List<RawImage> _fusionServerRowArtwork = new List<RawImage>();

	private readonly List<TMP_Text> _fusionServerRowNames = new List<TMP_Text>();

	private readonly List<TMP_Text> _fusionServerRowDetails = new List<TMP_Text>();

	private readonly List<TMP_Text> _fusionServerRowPopulation = new List<TMP_Text>();

	private readonly string[] _fusionServerRowArtworkCodes = new string[4];

	private readonly int[] _fusionServerRowArtworkGenerations = new int[4];

	private readonly Dictionary<string, LevelCrate> _portalLevelCrates = new Dictionary<string, LevelCrate>(StringComparer.OrdinalIgnoreCase);

	private readonly Dictionary<long, ModIoMod> _portalOnlineMods = new Dictionary<long, ModIoMod>();

	private GameObject? _portalsPage;

	private GameObject? _portalLevelsPage;

	private GameObject? _fusionServersPage;

	private GameObject? _fusionServerDetailsPage;

	private TMP_Text? _portalStatus;

	private TMP_Text? _portalModeText;

	private TouchButton? _portalLocalButton;

	private TouchButton? _portalWorldButton;

	private TouchButton? _portalRemoveButton;

	private TouchButton? _portalLevelsButton;

	private TouchButton? _portalServersButton;

	private TouchButton? _portalLevelActionButton;

	private TMP_Text? _portalLevelName;

	private TMP_Text? _portalLevelDetails;

	private TMP_Text? _portalLevelPosition;

	private TMP_Text? _portalLevelStatus;

	private TouchButton? _fusionServerActionButton;

	private TouchButton? _fusionServerJoinButton;

	private RawImage? _fusionServerArtwork;

	private TMP_Text? _fusionServerListPosition;

	private TMP_Text? _fusionServerListStatus;

	private TMP_Text? _fusionServerName;

	private TMP_Text? _fusionServerDetails;

	private TMP_Text? _fusionServerPosition;

	private TMP_Text? _fusionServerStatus;

	private TMP_Text? _fusionServerDescription;

	private TMP_Text? _fusionServerMembers;

	private PortalPlacementStage _portalPlacementStage;

	private PortalEndpoint? _pendingPortalEntrance;

	private PortalLinkState? _activePortal;

	private bool _activePortalLocallyCreated;

	private int _portalGeneration;

	private bool _portalTriggerHeld;

	private float _portalPointerReadyAt;

	private LineRenderer? _portalPointerLine;

	private GameObject? _portalPointerReticle;

	private Renderer? _portalPointerRenderer;

	private Material? _portalMaterial;

	private MaterialPropertyBlock? _portalPropertyBlock;

	private BoneHubPortalVisual? _portalVisual;

	private Vector3 _previousPortalHead;

	private bool _hasPreviousPortalHead;

	private float _nextPortalLevelScan;

	private string _selectedPortalLevelBarcode = string.Empty;

	private int _portalLevelIndex;

	private string _portalLevelQuery = string.Empty;

	private int _portalLevelSearchGeneration;

	private int _fusionServerRequestGeneration;

	private int _fusionQuickJoinGeneration;

	private bool _fusionServerRefreshPending;

	private float _lastFusionServerRefreshAt = -100f;

	private float _fusionServerRefreshDeadline;

	private int _fusionServerIndex;

	private int _fusionServerPageIndex;

	private string _selectedFusionServerCode = string.Empty;

	private string _fusionServerCodeInput = string.Empty;

	private float _nextPortalStatusRefresh;

	private int _fusionServerArtworkGeneration;

	private string _fusionServerArtworkCode = string.Empty;

	private long _pendingPortalLevelModId;

	private float _pendingPortalLevelDeadline;

	private float _nextPortalInstallScan;

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

	private IEnumerator<SpawnableCrate>? _spawnLabCatalogEnumerator;

	private readonly Dictionary<string, long> _spawnLabPalletOwners = new Dictionary<string, long>(StringComparer.OrdinalIgnoreCase);

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

	private string _selectedSpawnLabIdentity = string.Empty;

	private bool _spawnLabOnlineSearching;

	private bool _spawnLabInstallRescanStarted;

	private float _nextSpawnLabPreviewAt;

	private SpawnableEntry? _pendingSpawnLabPreview;

	private string _spawnLabPreviewRequestIdentity = string.Empty;

	private string _spawnLabLoadedPreviewIdentity = string.Empty;

	private string _spawnLabColorUpgradeIdentity = string.Empty;

	private SpawnDeliveryMode _spawnLabMode;

	private readonly Dictionary<SpawnDeliveryMode, TouchButton> _spawnLabModeButtons = new Dictionary<SpawnDeliveryMode, TouchButton>();

	private float _nextSpawnLabSubmissionAt;

	private bool _spawnLabCatalogRequested;

	private float _nextSpawnLabCatalogRetry;

	private readonly Dictionary<long, SpawnLabRequestRecord> _spawnLabRequests = new Dictionary<long, SpawnLabRequestRecord>();

	private readonly Dictionary<long, SpawnOwnershipRecord> _unresolvedSpawnLabOrphanRisks = new Dictionary<long, SpawnOwnershipRecord>();

	private readonly List<FrozenSpawnRecord> _frozenSpawnLabObjects = new List<FrozenSpawnRecord>();

	private readonly PooleeLifecycleRegistry _spawnLabLifecycle = PooleeLifecycleRegistry.Shared;

	private readonly ISpawnApiAdapter _localSpawnAdapter = new NativeAssetSpawnerAdapter();

	private readonly ISpawnApiAdapter _fusionSpawnAdapter = new LabFusionSpawnAdapter();

	private readonly Collider[] _spawnResultFallbackColliders = (Collider[])(object)new Collider[96];

	private readonly Dictionary<string, SpawnGeometryProfile> _spawnGeometryProfiles = new Dictionary<string, SpawnGeometryProfile>(StringComparer.OrdinalIgnoreCase);

	private long _nextSpawnLabRequestId;

	private int _spawnPlacementGeneration;

	private float _nextSpawnRequestMaintenanceAt;

	private SpawnerInteractionState _spawnInteractionState;

	private SpawnPreviewState _spawnPreviewState = (SpawnPreviewState)3;

	private bool _spawnPlacementSurfaceSupported;

	private bool _spawnPlacementSurfaceValid;

	private string _spawnPlacementInvalidReason = "NO SURFACE";

	private SpawnOwnershipRecord? _spawnLabDeleteOwnership;

	private ushort? _spawnLabDeleteFusionEntity;

	private GameObject? _utilitiesPage;

	private TouchButton? _wallGrabUtilityButton;

	private TouchButton? _godModeUtilityButton;

	private TouchButton? _superStrengthUtilityButton;

	private TouchButton? _infiniteAmmoUtilityButton;

	private TouchButton? _headlightUtilityButton;

	private TMP_Text? _utilitiesStatus;

	private float _nextUtilitiesRefresh;

	private static TMP_FontAsset? _osWordmarkFont;

	private static bool _osWordmarkFontResolved;

	private readonly List<OsControlRow> _codeModRows = new List<OsControlRow>(4);

	private readonly List<CodeModSnapshot> _codeModItems = new List<CodeModSnapshot>();

	private readonly List<LoadedCodeModSnapshot> _loadedCodeMods = new List<LoadedCodeModSnapshot>();

	private readonly ConcurrentQueue<string> _sdkOpenRequests = new ConcurrentQueue<string>();

	private readonly ConcurrentQueue<SdkNotification> _sdkNotifications = new ConcurrentQueue<SdkNotification>();

	private readonly List<IWristHubAppProvider> _sdkAutoProviders = new List<IWristHubAppProvider>();

	private const int MiniMapTextureSize = 32;

	private readonly Color[] _miniMapPixels = (Color[])(object)new Color[1024];

	private readonly float[] _miniMapHeights = new float[1024];

	private readonly byte[] _miniMapCellState = new byte[1024];

	private readonly List<MiniMapPeerVisual> _miniMapPeerVisuals = new List<MiniMapPeerVisual>(16);

	private readonly List<GameObject> _miniMapPortalDots = new List<GameObject>(2);

	private readonly List<SpawnableEntry> _spawnableEntries = new List<SpawnableEntry>();

	private readonly List<SpawnableEntry> _spawnableCatalog = new List<SpawnableEntry>();

	private readonly List<SpawnableEntry> _spawnableCatalogBuild = new List<SpawnableEntry>();

	private readonly List<SpawnLabBrowserItem> _spawnableBrowserItems = new List<SpawnLabBrowserItem>();

	private readonly List<SpawnLabRow> _spawnableRows = new List<SpawnLabRow>(6);

	private readonly Dictionary<long, ModIoMod> _spawnableOnlineMods = new Dictionary<long, ModIoMod>();

	private readonly Dictionary<string, SpawnableCrate> _spawnableCrates = new Dictionary<string, SpawnableCrate>(StringComparer.OrdinalIgnoreCase);

	private readonly Dictionary<string, SpawnableCrate> _spawnableCratesBuild = new Dictionary<string, SpawnableCrate>(StringComparer.OrdinalIgnoreCase);

	private readonly Dictionary<string, HandheldEligibilityResult> _spawnHandEligibility = new Dictionary<string, HandheldEligibilityResult>(StringComparer.OrdinalIgnoreCase);

	private readonly Dictionary<string, Quaternion> _spawnPreviewPresentationRotations = new Dictionary<string, Quaternion>(StringComparer.OrdinalIgnoreCase);

	private readonly Dictionary<int, SpawnCallbackLease> _spawnPreviewCallbackLeases = new Dictionary<int, SpawnCallbackLease>();

	private readonly RaycastHit[] _miniMapHits = (RaycastHit[])(object)new RaycastHit[12];

	private readonly RaycastHit[] _spawnPointerHits = (RaycastHit[])(object)new RaycastHit[64];

	private readonly List<GameObject> _spawnedObjects = new List<GameObject>(32);

	private readonly List<SpawnOwnershipRecord> _spawnOwnership = new List<SpawnOwnershipRecord>(64);

	private readonly List<GameObject> _jollyThemeObjects = new List<GameObject>(160);

	private Texture2D? _jollyNullbodyTexture;

	private readonly WristOsRouter _osRouter = new WristOsRouter();

	private WristOsAppId? _pendingQuestAppOpen;

	private float _pendingQuestAppOpenAt;

	private float _nextQuestOsMaintenanceAt;

	private readonly FusionMiniMapBridge _miniMapFusion = new FusionMiniMapBridge();

	private readonly List<FusionMiniMapPeer> _miniMapPeers = new List<FusionMiniMapPeer>(16);

	private readonly List<Renderer> _miniMapSuppressedRenderers = new List<Renderer>(64);

	private GameObject? _codeModsPage;

	private GameObject? _miniMapPage;

	private GameObject? _spawnablesPage;

	private GameObject? _spawnLabMorePanel;

	private GameObject? _notificationsPage;

	private GameObject? _osToolsPage;

	private TMP_Text? _osStatusText;

	private TMP_Text? _osWordmarkText;

	private TMP_Text? _osWordmarkGlowText;

	private TMP_Text? _osSuffixText;

	private TMP_Text? _osSuffixGlowText;

	private TouchButton? _osFriendsButton;

	private TMP_Text? _codeModTitle;

	private TMP_Text? _codeModPosition;

	private TouchButton? _codeModCommunityButton;

	private TMP_Text? _miniMapHeading;

	private TMP_Text? _miniMapRangeText;

	private TMP_Text? _miniMapScanText;

	private TouchButton? _miniMapModeButton;

	private TMP_Text? _miniMapRangeValue;

	private TMP_Text? _spawnableName;

	private TMP_Text? _spawnableDetails;

	private TMP_Text? _spawnablePosition;

	private TMP_Text? _spawnableStatus;

	private TMP_Text? _notificationText;

	private TMP_Text? _notificationTitleText;

	private TMP_Text? _notificationMetaText;

	private readonly List<TouchButton> _notificationActionButtons = new List<TouchButton>(2);

	private TouchButton? _notificationFocusButton;

	private IReadOnlyList<WristNotification> _notificationItems = Array.Empty<WristNotification>();

	private int _notificationIndex;

	private GameObject? _spawnablePreview;

	private GameObject? _spawnableNativePreview;

	private GameObject? _spawnPlacementPreview;

	private GameObject? _spawnPointerReticle;

	private LineRenderer? _spawnPointerLine;

	private LineRenderer? _spawnPreviewPullTether;

	private LineRenderer? _spawnPreviewPullRing;

	private TouchButton? _spawnActionButton;

	private TouchButton? _spawnInHandButton;

	private TouchButton? _spawnableModeButton;

	private TouchButton? _spawnableFavoriteButton;

	private TouchButton? _spawnDeleteButton;

	private TouchButton? _spawnLabBackButton;

	private Mesh? _spawnablePreviewMesh;

	private PreviewResourceHandle? _spawnablePreviewResources;

	private Material? _spawnWorldPreviewMaterial;

	private int _spawnWorldPreviewLayer;

	private MiniMapPeerOverlay? _miniMapPeerOverlay;

	private RectTransform? _miniMapViewport;

	private RectTransform? _miniMapGeometryRoot;

	private Image? _miniMapLayoutImage;

	private Texture2D? _miniMapLayoutTexture;

	private Sprite? _miniMapLayoutSprite;

	private GameObject? _miniMapCaptureObject;

	private Camera? _miniMapCaptureCamera;

	private RenderTexture? _miniMapCaptureTexture;

	private RenderTexture? _miniMapCaptureBackTexture;

	private RawImage? _miniMapSceneImage;

	private RectTransform? _miniMapBearingRoot;

	private RectTransform? _miniMapMarkerRoot;

	private object? _activeBoneMenuPage;

	private object? _managerControlCachePage;

	private IReadOnlyList<ReflectedManagerControl> _managerControlCache = Array.Empty<ReflectedManagerControl>();

	private float _managerControlCacheUntil;

	private object? _activeCodeModStringElement;

	private WristHubApp? _activeSdkApp;

	private WristHubPage? _activeSdkPage;

	private WristHubTextInput? _activeSdkText;

	private RuntimeSdkHost? _sdkHost;

	private bool _showLegacyBoneMenu;

	private bool _optionsReturnToHub;

	private string _latestSdkNotification = string.Empty;

	private WristKeyboardPurpose _wristKeyboardPurpose;

	private string _codeModQuery = string.Empty;

	private CodeModCatalogMode _codeModCatalogMode;

	private string _spawnableQuery = string.Empty;

	private TouchButton? _keyboardSubmitButton;

	private int _codeModPageIndex;

	private int _spawnableIndex;

	private int _spawnablePageIndex;

	private int _spawnablePreviewGeneration;

	private int _spawnableSearchGeneration;

	private int _spawnableManifestCount = -1;

	private int _spawnableCatalogCursor;

	private int _spawnableCatalogLastRefreshCursor;

	private int _spawnableCatalogGeneration;

	private bool _spawnPlacementActive;

	private bool _spawnDeleteActive;

	private bool _spawnTriggerHeld;

	private float _spawnTriggerReleasedAt;

	private bool _spawnableCatalogBuilding;

	private Hand? _spawnPlacementHand;

	private float _spawnPlacementYaw;

	private float _spawnPlacementDistance = 1.5f;

	private GameObject? _pendingGrabObject;

	private Hand? _pendingGrabHand;

	private SpawnableEntry? _pendingGrabEntry;

	private Grip? _pendingGrabSelectedGrip;

	private long _pendingGrabRequestId;

	private SpawnAttachmentPhysicsState? _pendingGrabPhysics;

	private readonly Queue<PendingSpawnAttachment> _pendingSpawnAttachmentQueue = new Queue<PendingSpawnAttachment>();

	private float _pendingGrabDeadline;

	private float _nextPendingGrabAttempt;

	private bool _pendingGrabSnatchStarted;

	private bool _pendingGrabActivationConfirmed;

	private float _pendingGrabSnatchAt;

	private int _pendingGrabStableGripId;

	private float _pendingGrabGripStableAt;

	private bool _pendingGrabAttachFallbackAttempted;

	private float _pendingGrabStartedAt;

	private GameObject? _latchedGrabObject;

	private Hand? _latchedGrabHand;

	private Grip? _latchedGrabGrip;

	private SpawnableEntry? _latchedGrabEntry;

	private long _latchedGrabRequestId;

	private bool _latchedGrabSawNeutral;

	private bool _latchedGrabSawPress;

	private bool _latchedGrabActivationConfirmed;

	private bool _latchedGrabReleaseOnGripUp;

	private float _nextLatchedGrabRepair;

	private float _latchedGrabDetachedAt;

	private float _latchedGrabDeadline;

	private float _spawnPointerReadyAt;

	private Material? _spawnPointerMaterial;

	private Material? _spawnPreviewPullMaterial;

	private MaterialPropertyBlock? _spawnPointerBlock;

	private Renderer? _spawnPointerRenderer;

	private bool _spawnPreviewHeld;

	private bool _spawnPreviewRequiresGripRelease;

	private bool _spawnPreviewReleaseIsLeft;

	private bool _spawnPreviewReturning;

	private bool _spawnPreviewUsesPinch;

	private bool _spawnPreviewIsLeft;

	private bool _spawnPreviewHalfPulse;

	private bool _spawnPreviewLeftGripWasActive;

	private bool _spawnPreviewRightGripWasActive;

	private bool _spawnPreviewLeftPinchWasActive;

	private bool _spawnPreviewRightPinchWasActive;

	private Hand? _spawnPreviewHand;

	private SpawnableEntry? _spawnPreviewPullEntry;

	private Transform? _spawnPreviewHomeParent;

	private Vector3 _spawnPreviewHomeLocalPosition;

	private Quaternion _spawnPreviewHomeLocalRotation = Quaternion.identity;

	private Vector3 _spawnPreviewHomeLocalScale = Vector3.one;

	private Bounds _spawnPreviewLocalBounds;

	private bool _spawnPreviewBoundsValid;

	private Vector3 _spawnPreviewDetachedBaseScale = Vector3.one;

	private Vector3 _spawnPreviewDragLocalOffset;

	private Quaternion _spawnPreviewDragRotationOffset = Quaternion.identity;

	private Vector3 _spawnPreviewReturnFrom;

	private Quaternion _spawnPreviewReturnRotation = Quaternion.identity;

	private Vector3 _spawnPreviewReturnScale = Vector3.one;

	private float _spawnPreviewTrackingLostAt = -1f;

	private float _spawnPreviewReturnStarted;

	private float _spawnPreviewGrabReadyAt;

	private bool _spawnPreviewMaterialized;

	private int _spawnPreviewLastPercent = -1;

	private Quaternion _spawnPreviewBaseLocalRotation = Quaternion.identity;

	private float _spawnPreviewYaw;

	private float _spawnPreviewPitch;

	private float _spawnPreviewLastInteractionAt;

	private bool _spawnPreviewRotateActive;

	private Hand? _spawnPreviewRotateHand;

	private Vector3 _spawnPreviewRotateLastEuler;

	private SpawnableBrowseMode _spawnableMode;

	private string _spawnableActiveGroup = string.Empty;

	private long _pendingSpawnableModId;

	private float _pendingSpawnableDeadline;

	private float _nextSpawnableInstallScan;

	private float _nextCodeModRefresh;

	private float _nextLoadedCodeModScan;

	private float _nextSdkProviderScan;

	private string _lastCodeModCatalogSignature = string.Empty;

	private float _nextMiniMapPeerRefresh;

	private float _nextMiniMapOverlayRender;

	private float _nextMiniMapOverlayRecovery;

	private bool _miniMapOverlayDiagnosticLogged;

	private bool _miniMapOverlayRecovering;

	private int _lastMiniMapDegree = -1;

	private int _lastMiniMapPeerCount = -1;

	private int _miniMapRasterCursor;

	private int _miniMapRasterSamplesRemaining;

	private bool _miniMapTextureDirty;

	private float _nextMiniMapTextureUpload;

	private float _nextMiniMapSceneCapture;

	private bool _miniMapCaptureValid;

	private Vector3 _miniMapCaptureCenter;

	private Vector3 _miniMapPendingCaptureCenter;

	private Vector3 _miniMapScanOrigin;

	private float _miniMapReferenceFloorY;

	private bool _miniMapScanOriginValid;

	private Color Cyan => WristHubThemePalette.Accent(_preferences.HologramTheme);

	private Color DimCyan => WristHubThemePalette.Dim(_preferences.HologramTheme);

	public bool IsOpen
	{
		get
		{
			if ((Object)(object)_browser != (Object)null)
			{
				return _browser.activeSelf;
			}
			return false;
		}
	}

	public bool IsLauncherOpen
	{
		get
		{
			if ((Object)(object)_root != (Object)null)
			{
				return _root.activeSelf;
			}
			return false;
		}
	}

	public bool IsClosing => (int)_lifecycle == 3;

	public string Query => _query;

	private string ActiveModStoreQuery
	{
		get
		{
			if (_modStoreCatalog != ModStoreCatalog.ModIo)
			{
				return _thunderstoreQuery;
			}
			return _itemStoreQuery;
		}
	}

	private int ActiveModStoreIndex
	{
		get
		{
			if (_modStoreCatalog != ModStoreCatalog.ModIo)
			{
				return _thunderstoreIndex;
			}
			return _itemStoreIndex;
		}
		set
		{
			if (_modStoreCatalog == ModStoreCatalog.ModIo)
			{
				_itemStoreIndex = value;
			}
			else
			{
				_thunderstoreIndex = value;
			}
		}
	}

	private int PendingSpawnLabRequestCount => _spawnLabRequests.Values.Count(delegate(SpawnLabRequestRecord request)
	{
		//IL_000e: Unknown result type (might be due to invalid IL or missing references)
		//IL_0013: Unknown result type (might be due to invalid IL or missing references)
		//IL_0014: Unknown result type (might be due to invalid IL or missing references)
		//IL_0016: Invalid comparison between Unknown and I4
		bool flag = !request.Terminal;
		if (flag)
		{
			SpawnRequestState state = request.State;
			bool flag2 = (int)state <= 6;
			flag = flag2;
		}
		return flag;
	});

	private bool SpawnLabHasPendingRequests => PendingSpawnLabRequestCount > 0;

	public event Action? Closed;

	private void AttachAppLibrarySdkEvents()
	{
		if (!_appLibrarySdkEventsAttached)
		{
			WristHubApi.AppsChanged += OnAppLibrarySdkAppsChanged;
			_appLibrarySdkEventsAttached = true;
		}
	}

	private void OnAppLibrarySdkAppsChanged()
	{
		_mainThread.Enqueue(delegate
		{
			if (!_disposed)
			{
				_nextAppLibraryRefresh = 0f;
				RefreshHomeAppTiles();
				GameObject? appLibraryPage = _appLibraryPage;
				if (appLibraryPage != null && appLibraryPage.activeSelf)
				{
					RefreshAppLibrary(force: true);
				}
			}
		});
	}

	private void BuildWristOsHomePageTwo(Transform parent)
	{
		//IL_0015: Unknown result type (might be due to invalid IL or missing references)
		//IL_001f: Expected O, but got Unknown
		//IL_0055: Unknown result type (might be due to invalid IL or missing references)
		//IL_005f: Unknown result type (might be due to invalid IL or missing references)
		//IL_0094: Unknown result type (might be due to invalid IL or missing references)
		//IL_009f: Unknown result type (might be due to invalid IL or missing references)
		//IL_00d6: Unknown result type (might be due to invalid IL or missing references)
		//IL_00ea: Unknown result type (might be due to invalid IL or missing references)
		//IL_00f0: Unknown result type (might be due to invalid IL or missing references)
		//IL_0131: Unknown result type (might be due to invalid IL or missing references)
		//IL_014a: Unknown result type (might be due to invalid IL or missing references)
		//IL_017a: Unknown result type (might be due to invalid IL or missing references)
		//IL_0185: Unknown result type (might be due to invalid IL or missing references)
		if (!((Object)(object)_hub == (Object)null))
		{
			_osHomePageTwo = new GameObject("WristHubOS Home Page Two");
			_osHomePageTwo.transform.SetParent(parent, false);
			CreateText(_osHomePageTwo.transform, "APPS", new TabletRect(0f, 73f, 150f, 22f), 14f, Color.white, (TextAlignmentOptions)514, (FontStyles)1);
			CreateText(_osHomePageTwo.transform, "WRISTHUBOS LIBRARY", new TabletRect(0f, 59f, 150f, 10f), 5.2f, Cyan, (TextAlignmentOptions)514, (FontStyles)1);
			CreateButton(_hub, _osHomePageTwo.transform, "<", new Vector3(-0.14f, 0.07f, 0.008f), new Vector3(0.026f, 0.024f, 0.014f), DimCyan, delegate
			{
				ShowOsHomePage(0);
			}, 16f);
			CreateImage(_osHomePageTwo.transform, "Phone App Shelf", new TabletRect(0f, -7f, 282f, 122f), new Color(0.008f, 0.045f, 0.068f, 0.8f));
			_osHomePageDotsTwo = CreateText(_osHomePageTwo.transform, "○  ●", new TabletRect(0f, -75f, 70f, 14f), 7f, Cyan, (TextAlignmentOptions)514, (FontStyles)1);
			for (int num = 0; num < 9; num++)
			{
				BuildHomeAppTile(num);
			}
			_osHomePageTwo.SetActive(false);
			RefreshHomeAppTiles();
			RefreshJollyThemeDecorations();
		}
	}

	private void BuildHomeAppTile(int index)
	{
		//IL_0077: Unknown result type (might be due to invalid IL or missing references)
		//IL_008b: Unknown result type (might be due to invalid IL or missing references)
		//IL_00a4: Unknown result type (might be due to invalid IL or missing references)
		//IL_00e7: Unknown result type (might be due to invalid IL or missing references)
		//IL_0100: Unknown result type (might be due to invalid IL or missing references)
		//IL_0130: Unknown result type (might be due to invalid IL or missing references)
		//IL_013b: Unknown result type (might be due to invalid IL or missing references)
		//IL_016c: Unknown result type (might be due to invalid IL or missing references)
		//IL_0177: Unknown result type (might be due to invalid IL or missing references)
		//IL_01ae: Unknown result type (might be due to invalid IL or missing references)
		//IL_01b8: Unknown result type (might be due to invalid IL or missing references)
		//IL_0217: Unknown result type (might be due to invalid IL or missing references)
		//IL_0221: Unknown result type (might be due to invalid IL or missing references)
		if (!((Object)(object)_hub == (Object)null) && !((Object)(object)_osHomePageTwo == (Object)null))
		{
			int num = index % 3;
			int num2 = index / 3;
			float num3 = -0.084f + (float)num * 0.084f;
			float num4 = 0.033f - (float)num2 * 0.043f;
			int captured = index;
			TouchButton touchButton = CreateButton(_hub, _osHomePageTwo.transform, string.Empty, new Vector3(num3, num4, 0.008f), new Vector3(0.058f, 0.037f, 0.014f), new Color(0.025f, 0.13f, 0.18f, 0.94f), delegate
			{
				ActivateHomeAppTile(captured);
			}, 6f);
			CreateImage(touchButton.Root.transform, "Rounded Icon Plate", new TabletRect(0f, 5f, 27f, 27f), new Color(0.05f, 0.25f, 0.32f, 0.95f));
			RawImage artwork = CreateRawImage(touchButton.Root.transform, "App Icon", new TabletRect(0f, 5f, 23f, 23f), _placeholderTexture, Color.clear);
			TMP_Text monogram = CreateText(touchButton.Root.transform, string.Empty, new TabletRect(0f, 5f, 25f, 22f), 8f, Cyan, (TextAlignmentOptions)514, (FontStyles)1);
			TMP_Text val = CreateText(touchButton.Root.transform, string.Empty, new TabletRect(0f, -13f, 52f, 10f), 5.2f, Color.white, (TextAlignmentOptions)514, (FontStyles)1);
			val.enableAutoSizing = true;
			val.fontSizeMin = 3.4f;
			val.fontSizeMax = 5.2f;
			val.overflowMode = (TextOverflowModes)1;
			TMP_Text badge = CreateText(touchButton.Root.transform, string.Empty, new TabletRect(20f, 14f, 20f, 8f), 4.8f, Amber, (TextAlignmentOptions)514, (FontStyles)1);
			_homeAppTiles.Add(new AppLibraryTile
			{
				Button = touchButton,
				Artwork = artwork,
				Monogram = monogram,
				Name = val,
				Badge = badge
			});
		}
	}

	private void ShowOsHomePage(int page)
	{
		_osHomePageIndex = Math.Clamp(page, 0, 1);
		GameObject? osToolsPage = _osToolsPage;
		if (osToolsPage != null)
		{
			osToolsPage.SetActive(false);
		}
		if (_osHomePageIndex == 1 && (Object)(object)_osHomePageTwo == (Object)null)
		{
			GameObject? osHomePageOne = _osHomePageOne;
			if ((Object)(object)((osHomePageOne != null) ? osHomePageOne.transform.parent : null) != (Object)null)
			{
				RunGuarded("Quest second Home page preparation", delegate
				{
					BuildWristOsHomePageTwo(_osHomePageOne.transform.parent);
				});
			}
		}
		GameObject? osHomePageOne2 = _osHomePageOne;
		if (osHomePageOne2 != null)
		{
			osHomePageOne2.SetActive(_osHomePageIndex == 0);
		}
		GameObject? osHomePageTwo = _osHomePageTwo;
		if (osHomePageTwo != null)
		{
			osHomePageTwo.SetActive(_osHomePageIndex == 1);
		}
		if (_osHomePageIndex == 1)
		{
			RefreshHomeAppTiles();
		}
		RequireInputRearm();
	}

	private void RefreshHomeAppTiles()
	{
		//IL_00df: Unknown result type (might be due to invalid IL or missing references)
		//IL_0108: Unknown result type (might be due to invalid IL or missing references)
		if (_homeAppTiles.Count == 0)
		{
			return;
		}
		Dictionary<string, WristHubApp> dictionary = (from app in WristHubApi.GetRegisteredApps()
			where app.Available
			select app).ToDictionary<WristHubApp, string>((WristHubApp app) => app.Id, StringComparer.OrdinalIgnoreCase);
		for (int num = 0; num < _homeAppTiles.Count; num++)
		{
			AppLibraryTile appLibraryTile = _homeAppTiles[num];
			if (num == 0)
			{
				int count = dictionary.Count;
				appLibraryTile.Item = null;
				appLibraryTile.Button.Root.SetActive(true);
				appLibraryTile.Name.text = "APPS";
				appLibraryTile.Monogram.text = "▦";
				appLibraryTile.Badge.text = ((count > 0) ? count.ToString() : string.Empty);
				((Graphic)appLibraryTile.Artwork).color = Color.clear;
				((Graphic)appLibraryTile.Button.Panel).color = new Color(0.1f, 0.43f, 0.55f, 1f);
			}
			else
			{
				int num2 = num - 1;
				if (num2 >= _preferences.PinnedWristAppIds.Count || !dictionary.TryGetValue(_preferences.PinnedWristAppIds[num2], out var value))
				{
					appLibraryTile.Button.Root.SetActive(false);
					appLibraryTile.Item = null;
				}
				else
				{
					WristAppLibraryItem item = new WristAppLibraryItem(value, FindSdkPackage(value));
					appLibraryTile.Button.Root.SetActive(true);
					PaintAppTile(appLibraryTile, item);
				}
			}
		}
	}

	private void ActivateHomeAppTile(int slot)
	{
		if (slot == 0)
		{
			ShowAppLibraryPage();
		}
		else if (slot >= 0 && slot < _homeAppTiles.Count)
		{
			WristHubApp val = _homeAppTiles[slot].Item?.App;
			if (val != null)
			{
				LaunchSdkApp(val);
			}
		}
	}

	private void BuildAppLibraryPage()
	{
		//IL_0023: Unknown result type (might be due to invalid IL or missing references)
		//IL_002d: Expected O, but got Unknown
		//IL_006e: Unknown result type (might be due to invalid IL or missing references)
		//IL_0087: Unknown result type (might be due to invalid IL or missing references)
		//IL_00b7: Unknown result type (might be due to invalid IL or missing references)
		//IL_00c1: Unknown result type (might be due to invalid IL or missing references)
		//IL_00fc: Unknown result type (might be due to invalid IL or missing references)
		//IL_0110: Unknown result type (might be due to invalid IL or missing references)
		//IL_0116: 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_016c: Unknown result type (might be due to invalid IL or missing references)
		//IL_0172: Unknown result type (might be due to invalid IL or missing references)
		//IL_01b4: Unknown result type (might be due to invalid IL or missing references)
		//IL_01c8: Unknown result type (might be due to invalid IL or missing references)
		//IL_01cd: Unknown result type (might be due to invalid IL or missing references)
		//IL_0210: Unknown result type (might be due to invalid IL or missing references)
		//IL_0224: Unknown result type (might be due to invalid IL or missing references)
		//IL_022a: Unknown result type (might be due to invalid IL or missing references)
		//IL_0271: Unknown result type (might be due to invalid IL or missing references)
		//IL_0285: Unknown result type (might be due to invalid IL or missing references)
		//IL_028b: Unknown result type (might be due to invalid IL or missing references)
		//IL_02d2: Unknown result type (might be due to invalid IL or missing references)
		//IL_02e6: Unknown result type (might be due to invalid IL or missing references)
		//IL_02ec: Unknown result type (might be due to invalid IL or missing references)
		//IL_0333: Unknown result type (might be due to invalid IL or missing references)
		//IL_0347: Unknown result type (might be due to invalid IL or missing references)
		//IL_034c: Unknown result type (might be due to invalid IL or missing references)
		//IL_0391: Unknown result type (might be due to invalid IL or missing references)
		//IL_039b: Unknown result type (might be due to invalid IL or missing references)
		//IL_03b6: Unknown result type (might be due to invalid IL or missing references)
		//IL_03c0: Expected O, but got Unknown
		//IL_0415: Unknown result type (might be due to invalid IL or missing references)
		//IL_0429: Unknown result type (might be due to invalid IL or missing references)
		//IL_042f: Unknown result type (might be due to invalid IL or missing references)
		//IL_0470: Unknown result type (might be due to invalid IL or missing references)
		//IL_047b: Unknown result type (might be due to invalid IL or missing references)
		//IL_04b6: Unknown result type (might be due to invalid IL or missing references)
		//IL_04ca: Unknown result type (might be due to invalid IL or missing references)
		//IL_04d0: Unknown result type (might be due to invalid IL or missing references)
		if (!((Object)(object)_browser == (Object)null) && !((Object)(object)_canvasRoot == (Object)null))
		{
			_appLibraryPage = new GameObject("WristHubOS Installed Apps");
			_appLibraryPage.transform.SetParent(_canvasRoot.transform, false);
			CreateImage(_appLibraryPage.transform, "Apps Glass", new TabletRect(0f, 0f, 310f, 188f), new Color(0.005f, 0.023f, 0.037f, 0.975f));
			_appLibraryTitle = CreateText(_appLibraryPage.transform, "INSTALLED APPS", new TabletRect(-24f, 80f, 150f, 22f), 14f, Color.white, (TextAlignmentOptions)514, (FontStyles)1);
			CreateButton(_browser, _appLibraryPage.transform, "BACK", new Vector3(-0.137f, 0.08f, 0.008f), new Vector3(0.036f, 0.023f, 0.014f), DimCyan, BackFromAppLibrary, 7f);
			CreateButton(_browser, _appLibraryPage.transform, "SEARCH", new Vector3(0.094f, 0.08f, 0.008f), new Vector3(0.052f, 0.023f, 0.014f), DimCyan, OpenAppLibraryKeyboard, 6.5f);
			CreateButton(_browser, _appLibraryPage.transform, "X", new Vector3(0.139f, 0.08f, 0.008f), new Vector3(0.026f, 0.023f, 0.014f), Red, Close, 15f);
			_appLibraryInstalledTab = CreateButton(_browser, _appLibraryPage.transform, "INSTALLED", new Vector3(-0.093f, 0.055f, 0.008f), new Vector3(0.061f, 0.02f, 0.012f), Cyan, delegate
			{
				SetAppLibraryTab((WristAppLibraryTab)0);
			}, 5.7f);
			_appLibraryStoreTab = CreateButton(_browser, _appLibraryPage.transform, "STORE", new Vector3(-0.03f, 0.055f, 0.008f), new Vector3(0.052f, 0.02f, 0.012f), DimCyan, delegate
			{
				SetAppLibraryTab((WristAppLibraryTab)1);
			}, 5.8f);
			_appLibraryUpdatesTab = CreateButton(_browser, _appLibraryPage.transform, "UPDATES", new Vector3(0.031f, 0.055f, 0.008f), new Vector3(0.064f, 0.02f, 0.012f), DimCyan, delegate
			{
				SetAppLibraryTab((WristAppLibraryTab)2);
			}, 5.7f);
			_appLibraryManageButton = CreateButton(_browser, _appLibraryPage.transform, "MANAGE", new Vector3(0.105f, 0.055f, 0.008f), new Vector3(0.06f, 0.02f, 0.012f), Surface, ToggleAppLibraryManageMode, 5.2f);
			_appLibraryRestartBanner = CreateText(_appLibraryPage.transform, string.Empty, new TabletRect(0f, -64f, 220f, 11f), 5.6f, Amber, (TextAlignmentOptions)514, (FontStyles)1);
			_appLibraryGridRoot = new GameObject("App Grid");
			_appLibraryGridRoot.transform.SetParent(_appLibraryPage.transform, false);
			for (int num = 0; num < 8; num++)
			{
				BuildAppLibraryTile(num);
			}
			CreateButton(_browser, _appLibraryGridRoot.transform, "<", new Vector3(-0.127f, -0.082f, 0.008f), new Vector3(0.04f, 0.021f, 0.012f), DimCyan, delegate
			{
				MoveAppLibraryPage(-1);
			}, 15f);
			_appLibraryPosition = CreateText(_appLibraryGridRoot.transform, "1 / 1", new TabletRect(0f, -82f, 70f, 16f), 7f, Cyan, (TextAlignmentOptions)514, (FontStyles)1);
			CreateButton(_browser, _appLibraryGridRoot.transform, ">", new Vector3(0.127f, -0.082f, 0.008f), new Vector3(0.04f, 0.021f, 0.012f), DimCyan, delegate
			{
				MoveAppLibraryPage(1);
			}, 15f);
			BuildAppLibraryDetail();
			_appLibraryPage.SetActive(false);
		}
	}

	private void BuildAppLibraryTile(int index)
	{
		//IL_0077: Unknown result type (might be due to invalid IL or missing references)
		//IL_008b: Unknown result type (might be due to invalid IL or missing references)
		//IL_00a4: Unknown result type (might be due to invalid IL or missing references)
		//IL_00e7: Unknown result type (might be due to invalid IL or missing references)
		//IL_0100: Unknown result type (might be due to invalid IL or missing references)
		//IL_0130: Unknown result type (might be due to invalid IL or missing references)
		//IL_013b: Unknown result type (might be due to invalid IL or missing references)
		//IL_016c: Unknown result type (might be due to invalid IL or missing references)
		//IL_0177: Unknown result type (might be due to invalid IL or missing references)
		//IL_01ae: Unknown result type (might be due to invalid IL or missing references)
		//IL_01b8: Unknown result type (might be due to invalid IL or missing references)
		//IL_0217: Unknown result type (might be due to invalid IL or missing references)
		//IL_0221: Unknown result type (might be due to invalid IL or missing references)
		if (!((Object)(object)_browser == (Object)null) && !((Object)(object)_appLibraryGridRoot == (Object)null))
		{
			int num = index % 4;
			int num2 = index / 4;
			float num3 = -0.108f + (float)num * 0.072f;
			float num4 = 0.02f - (float)num2 * 0.052f;
			int captured = index;
			TouchButton touchButton = CreateButton(_browser, _appLibraryGridRoot.transform, string.Empty, new Vector3(num3, num4, 0.008f), new Vector3(0.058f, 0.047f, 0.012f), new Color(0.022f, 0.11f, 0.155f, 0.98f), delegate
			{
				ActivateAppLibraryTile(captured);
			}, 5f);
			CreateImage(touchButton.Root.transform, "App Icon Plate", new TabletRect(0f, 7f, 29f, 29f), new Color(0.045f, 0.23f, 0.3f, 0.98f));
			RawImage artwork = CreateRawImage(touchButton.Root.transform, "App Artwork", new TabletRect(0f, 7f, 25f, 25f), _placeholderTexture, Color.clear);
			TMP_Text monogram = CreateText(touchButton.Root.transform, string.Empty, new TabletRect(0f, 7f, 25f, 22f), 8f, Cyan, (TextAlignmentOptions)514, (FontStyles)1);
			TMP_Text val = CreateText(touchButton.Root.transform, string.Empty, new TabletRect(0f, -15f, 52f, 11f), 5.1f, Color.white, (TextAlignmentOptions)514, (FontStyles)1);
			val.enableAutoSizing = true;
			val.fontSizeMin = 3.2f;
			val.fontSizeMax = 5.1f;
			val.overflowMode = (TextOverflowModes)1;
			TMP_Text badge = CreateText(touchButton.Root.transform, string.Empty, new TabletRect(18f, 18f, 22f, 8f), 3.9f, Amber, (TextAlignmentOptions)514, (FontStyles)1);
			_appLibraryTiles.Add(new AppLibraryTile
			{
				Button = touchButton,
				Artwork = artwork,
				Monogram = monogram,
				Name = val,
				Badge = badge
			});
		}
	}

	private void BuildAppLibraryDetail()
	{
		//IL_0023: Unknown result type (might be due to invalid IL or missing references)
		//IL_002d: Expected O, but got Unknown
		//IL_006e: Unknown result type (might be due to invalid IL or missing references)
		//IL_0087: Unknown result type (might be due to invalid IL or missing references)
		//IL_00b7: Unknown result type (might be due to invalid IL or missing references)
		//IL_00c1: Unknown result type (might be due to invalid IL or missing references)
		//IL_00fb: Unknown result type (might be due to invalid IL or missing references)
		//IL_0106: Unknown result type (might be due to invalid IL or missing references)
		//IL_0140: Unknown result type (might be due to invalid IL or missing references)
		//IL_015e: Unknown result type (might be due to invalid IL or missing references)
		//IL_0198: Unknown result type (might be due to invalid IL or missing references)
		//IL_01a2: Unknown result type (might be due to invalid IL or missing references)
		//IL_01dd: Unknown result type (might be due to invalid IL or missing references)
		//IL_01f1: Unknown result type (might be due to invalid IL or missing references)
		//IL_01f7: Unknown result type (might be due to invalid IL or missing references)
		//IL_023a: Unknown result type (might be due to invalid IL or missing references)
		//IL_024e: Unknown result type (might be due to invalid IL or missing references)
		//IL_0253: Unknown result type (might be due to invalid IL or missing references)
		//IL_029a: Unknown result type (might be due to invalid IL or missing references)
		//IL_02ae: Unknown result type (might be due to invalid IL or missing references)
		//IL_02b4: Unknown result type (might be due to invalid IL or missing references)
		//IL_02fb: Unknown result type (might be due to invalid IL or missing references)
		//IL_030f: Unknown result type (might be due to invalid IL or missing references)
		//IL_0314: Unknown result type (might be due to invalid IL or missing references)
		if (!((Object)(object)_browser == (Object)null) && !((Object)(object)_appLibraryPage == (Object)null))
		{
			_appLibraryDetailRoot = new GameObject("App Details");
			_appLibraryDetailRoot.transform.SetParent(_appLibraryPage.transform, false);
			CreateImage(_appLibraryDetailRoot.transform, "Details Card", new TabletRect(0f, -8f, 254f, 116f), new Color(0.018f, 0.085f, 0.115f, 0.98f));
			_appDetailName = CreateText(_appLibraryDetailRoot.transform, string.Empty, new TabletRect(0f, 31f, 220f, 24f), 13f, Color.white, (TextAlignmentOptions)514, (FontStyles)1);
			_appDetailAuthor = CreateText(_appLibraryDetailRoot.transform, string.Empty, new TabletRect(0f, 12f, 220f, 15f), 7.5f, Cyan, (TextAlignmentOptions)514, (FontStyles)0);
			_appDetailDescription = CreateText(_appLibraryDetailRoot.transform, string.Empty, new TabletRect(0f, -13f, 218f, 30f), 7.2f, new Color(0.78f, 0.88f, 0.92f, 1f), (TextAlignmentOptions)514, (FontStyles)0);
			_appDetailStatus = CreateText(_appLibraryDetailRoot.transform, string.Empty, new TabletRect(0f, -39f, 218f, 15f), 6.6f, Amber, (TextAlignmentOptions)514, (FontStyles)1);
			CreateButton(_browser, _appLibraryDetailRoot.transform, "GRID", new Vector3(-0.113f, -0.073f, 0.008f), new Vector3(0.046f, 0.024f, 0.012f), DimCyan, CloseAppLibraryDetail, 7f);
			_appDetailPin = CreateButton(_browser, _appLibraryDetailRoot.transform, "PIN", new Vector3(-0.058f, -0.073f, 0.008f), new Vector3(0.054f, 0.024f, 0.012f), Surface, ToggleSelectedAppPin, 7f);
			_appDetailAction = CreateButton(_browser, _appLibraryDetailRoot.transform, "OPEN", new Vector3(0.015f, -0.073f, 0.008f), new Vector3(0.086f, 0.024f, 0.012f), Cyan, ActivateAppDetailAction, 7f);
			_appDetailUninstall = CreateButton(_browser, _appLibraryDetailRoot.transform, "UNINSTALL", new Vector3(0.104f, -0.073f, 0.008f), new Vector3(0.07f, 0.024f, 0.012f), Red, UninstallSelectedSdkApp, 5.8f);
			_appLibraryDetailRoot.SetActive(false);
		}
	}

	private void ShowAppLibraryPage()
	{
		if ((Object)(object)_appLibraryPage == (Object)null)
		{
			RunGuarded("Quest Installed Apps preparation", BuildAppLibraryPage);
		}
		_nextSdkProviderScan = 0f;
		DiscoverSdkAppProviders(force: true);
		ShowWristOsPage(_appLibraryPage, (AvatarBrowserViewState)12);
		_appLibraryDetailItem = null;
		GameObject? appLibraryDetailRoot = _appLibraryDetailRoot;
		if (appLibraryDetailRoot != null)
		{
			appLibraryDetailRoot.SetActive(false);
		}
		GameObject? appLibraryGridRoot = _appLibraryGridRoot;
		if (appLibraryGridRoot != null)
		{
			appLibraryGridRoot.SetActive(true);
		}
		_nextAppLibraryRefresh = 0f;
		RefreshAppLibrary(force: true);
		_log.Msg("WristHubOS Installed Apps opened.");
	}

	private void BackFromAppLibrary()
	{
		GameObject? appLibraryDetailRoot = _appLibraryDetailRoot;
		if (appLibraryDetailRoot != null && appLibraryDetailRoot.activeSelf)
		{
			CloseAppLibraryDetail();
			return;
		}
		ShowHub();
		ShowOsHomePage(1);
	}

	private void OpenAppLibraryKeyboard()
	{
		_wristKeyboardPurpose = WristKeyboardPurpose.WristApps;
		OpenWristOsKeyboard(_preferences.WristAppSearchQuery, "SEARCH WRIST APPS");
	}

	private void SubmitAppLibrarySearch(string query)
	{
		_preferences.WristAppSearchQuery = query.Trim();
		_appLibraryPageIndex = 0;
		SavePreferences();
		ShowAppLibraryPage();
	}

	private void SetAppLibraryTab(WristAppLibraryTab tab)
	{
		//IL_0006: Unknown result type (might be due to invalid IL or missing references)
		_preferences.WristAppLibraryTab = tab;
		_appLibraryPageIndex = 0;
		_appLibraryDetailItem = null;
		GameObject? appLibraryDetailRoot = _appLibraryDetailRoot;
		if (appLibraryDetailRoot != null)
		{
			appLibraryDetailRoot.SetActive(false);
		}
		GameObject? appLibraryGridRoot = _appLibraryGridRoot;
		if (appLibraryGridRoot != null)
		{
			appLibraryGridRoot.SetActive(true);
		}
		SavePreferences();
		RefreshAppLibrary(force: true);
	}

	private void ToggleAppLibraryManageMode()
	{
		_appLibraryManageMode = !_appLibraryManageMode;
		RefreshAppLibrary(force: true);
	}

	private void MoveAppLibraryPage(int direction)
	{
		int num = Math.Max(1, (int)Math.Ceiling((double)_appLibraryItems.Count / 8.0));
		_appLibraryPageIndex = (_appLibraryPageIndex + Math.Sign(direction) + num) % num;
		PaintAppLibraryGrid();
	}

	private void RefreshAppLibrary(bool force)
	{
		GameObject? appLibraryPage = _appLibraryPage;
		if (appLibraryPage != null && appLibraryPage.activeSelf && (force || !(Time.unscaledTime < _nextAppLibraryRefresh)))
		{
			_nextAppLibraryRefresh = Time.unscaledTime + 1f;
			_sdkStorePackages.Clear();
			if (_thunderstoreStore.Available)
			{
				_sdkStorePackages.AddRange(_thunderstoreStore.SnapshotWristHubApps(_preferences.WristAppSearchQuery, _constrainedRendering));
			}
			BuildAppLibraryItems();
			PaintAppLibraryGrid();
			PaintAppLibraryTabs();
			GameObject? appLibraryDetailRoot = _appLibraryDetailRoot;
			if (appLibraryDetailRoot != null && appLibraryDetailRoot.activeSelf)
			{
				PaintAppLibraryDetail();
			}
		}
	}

	private void BuildAppLibraryItems()
	{
		//IL_0044: 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_011d: Invalid comparison between Unknown and I4
		_appLibraryItems.Clear();
		string query = _preferences.WristAppSearchQuery;
		WristHubApp[] array = (from val in WristHubApi.GetRegisteredApps()
			where val.Available && AppMatches(val, query)
			select val).ToArray();
		if ((int)_preferences.WristAppLibraryTab == 0)
		{
			WristHubApp[] array2 = array;
			foreach (WristHubApp app in array2)
			{
				_appLibraryItems.Add(new WristAppLibraryItem(app, FindSdkPackage(app)));
			}
			foreach (ThunderstorePackageEntry package in _sdkStorePackages.Where((ThunderstorePackageEntry thunderstorePackageEntry) => thunderstorePackageEntry.Installed || thunderstorePackageEntry.Pending))
			{
				if (!_appLibraryItems.Any((WristAppLibraryItem item) => item.Package?.PackageNamespace.Equals(package.PackageNamespace, StringComparison.OrdinalIgnoreCase) ?? false))
				{
					_appLibraryItems.Add(new WristAppLibraryItem(null, package));
				}
			}
		}
		else
		{
			IEnumerable<ThunderstorePackageEntry> enumerable;
			if ((int)_preferences.WristAppLibraryTab != 2)
			{
				IEnumerable<ThunderstorePackageEntry> sdkStorePackages = _sdkStorePackages;
				enumerable = sdkStorePackages;
			}
			else
			{
				enumerable = _sdkStorePackages.Where((ThunderstorePackageEntry thunderstorePackageEntry) => thunderstorePackageEntry.HasUpdate || thunderstorePackageEntry.Pending);
			}
			foreach (ThunderstorePackageEntry item in enumerable)
			{
				_appLibraryItems.Add(new WristAppLibraryItem(FindSdkApp(item), item));
			}
		}
		_appLibraryItems.Sort((WristAppLibraryItem left, WristAppLibraryItem right) => string.Compare(left.Name, right.Name, StringComparison.OrdinalIgnoreCase));
	}

	private void PaintAppLibraryTabs()
	{
		//IL_0019: Unknown result type (might be due to invalid IL or missing references)
		//IL_004c: Unknown result type (might be due to invalid IL or missing references)
		//IL_0052: Invalid comparison between Unknown and I4
		//IL_0029: Unknown result type (might be due to invalid IL or missing references)
		//IL_0021: Unknown result type (might be due to invalid IL or missing references)
		//IL_0080: Unknown result type (might be due to invalid IL or missing references)
		//IL_0086: Invalid comparison between Unknown and I4
		//IL_005d: Unknown result type (might be due to invalid IL or missing references)
		//IL_0055: Unknown result type (might be due to invalid IL or missing references)
		//IL_0090: Unknown result type (might be due to invalid IL or missing references)
		//IL_0089: Unknown result type (might be due to invalid IL or missing references)
		//IL_0105: Unknown result type (might be due to invalid IL or missing references)
		//IL_010a: Unknown result type (might be due to invalid IL or missing references)
		//IL_010b: Unknown result type (might be due to invalid IL or missing references)
		//IL_010d: Invalid comparison between Unknown and I4
		//IL_010f: Unknown result type (might be due to invalid IL or missing references)
		//IL_0111: Invalid comparison between Unknown and I4
		//IL_00e0: Unknown result type (might be due to invalid IL or missing references)
		//IL_00d9: Unknown result type (might be due to invalid IL or missing references)
		if (_appLibraryInstalledTab != null)
		{
			((Graphic)_appLibraryInstalledTab.Panel).color = (((int)_preferences.WristAppLibraryTab == 0) ? Cyan : DimCyan);
		}
		if (_appLibraryStoreTab != null)
		{
			((Graphic)_appLibraryStoreTab.Panel).color = (((int)_preferences.WristAppLibraryTab == 1) ? Cyan : DimCyan);
		}
		if (_appLibraryUpdatesTab != null)
		{
			((Graphic)_appLibraryUpdatesTab.Panel).color = (((int)_preferences.WristAppLibraryTab == 2) ? Amber : DimCyan);
		}
		if (_appLibraryManageButton != null)
		{
			_appLibraryManageButton.Label.text = (_appLibraryManageMode ? "DONE" : "MANAGE");
			((Graphic)_appLibraryManageButton.Panel).color = (_appLibraryManageMode ? Amber : Surface);
		}
		if ((Object)(object)_appLibraryTitle != (Object)null)
		{
			TMP_Text appLibraryTitle = _appLibraryTitle;
			WristAppLibraryTab wristAppLibraryTab = _preferences.WristAppLibraryTab;
			string text = (((int)wristAppLibraryTab == 1) ? "APP STORE" : (((int)wristAppLibraryTab != 2) ? "INSTALLED APPS" : "APP UPDATES"));
			appLibraryTitle.text = text;
		}
		if ((Object)(object)_appLibraryRestartBanner != (Object)null)
		{
			_appLibraryRestartBanner.text = (_sdkStorePackages.Any((ThunderstorePackageEntry package) => package.Pending) ? "RESTART BONELAB TO FINISH QUEUED APP CHANGES" : string.Empty);
		}
	}

	private void PaintAppLibraryGrid()
	{
		int num = Math.Max(1, (int)Math.Ceiling((double)_appLibraryItems.Count / 8.0));
		_appLibraryPageIndex = Math.Clamp(_appLibraryPageIndex, 0, num - 1);
		if ((Object)(object)_appLibraryPosition != (Object)null)
		{
			_appLibraryPosition.text = $"{_appLibraryPageIndex + 1} / {num}";
		}
		for (int i = 0; i < _appLibraryTiles.Count; i++)
		{
			int num2 = _appLibraryPageIndex * 8 + i;
			AppLibraryTile appLibraryTile = _appLibraryTiles[i];
			if (num2 >= _appLibraryItems.Count)
			{
				appLibraryTile.Button.Root.SetActive(false);
				appLibraryTile.Item = null;
			}
			else
			{
				appLibraryTile.Button.Root.SetActive(true);
				PaintAppTile(appLibraryTile, _appLibraryItems[num2]);
			}
		}
	}

	private void PaintAppTile(AppLibraryTile tile, WristAppLibraryItem item)
	{
		//IL_00bd: Unknown result type (might be due to invalid IL or missing references)
		//IL_00a2: Unknown result type (might be due to invalid IL or missing references)
		//IL_0087: Unknown result type (might be due to invalid IL or missing references)
		tile.Item = item;
		tile.Name.text = item.Name;
		TMP_Text monogram = tile.Monogram;
		WristHubApp? app = item.App;
		monogram.text = AppMonogram((app != null) ? app.Icon : null, item.Name);
		Image panel = tile.Button.Panel;
		ThunderstorePackageEntry? package = item.Package;
		? color;
		if ((object)package == null || !package.Pending)
		{
			ThunderstorePackageEntry? package2 = item.Package;
			color = (((object)package2 != null && package2.HasUpdate) ? new Color(0.3f, 0.22f, 0.06f, 1f) : new Color(0.026f, 0.15f, 0.2f, 0.98f));
		}
		else
		{
			color = new Color(0.38f, 0.24f, 0.05f, 1f);
		}
		((Graphic)panel).color = (Color)color;
		TMP_Text badge = tile.Badge;
		ThunderstorePackageEntry? package3 = item.Package;
		object text;
		if ((object)package3 == null || !package3.Pending)
		{
			ThunderstorePackageEntry? package4 = item.Package;
			if ((object)package4 == null || !package4.HasUpdate)
			{
				if (item.App == null || _preferences.OpenedWristAppIds.Contains<string>(item.App.Id, StringComparer.OrdinalIgnoreCase))
				{
					WristHubApp? app2 = item.App;
					text = ((app2 != null && app2.Notifications > 0) ? item.App.Notifications.ToString() : string.Empty);
				}
				else
				{
					text = "NEW";
				}
			}
			else
			{
				text = "UPDATE";
			}
		}
		else
		{
			text = "QUEUED";
		}
		badge.text = (string)text;
		AssignAppArtwork(tile, item);
	}

	private void AssignAppArtwork(AppLibraryTile tile, WristAppLibraryItem item)
	{
		//IL_0030: Unknown result type (might be due to invalid IL or missing references)
		//IL_0133: Unknown result type (might be due to invalid IL or missing references)
		//IL_0074: Unknown result type (might be due to invalid IL or missing references)
		//IL_0079: Unknown result type (might be due to invalid IL or missing references)
		//IL_009a: Expected O, but got Unknown
		tile.ArtworkIdentity = item.Identity;
		((Graphic)tile.Artwork).color = Color.clear;
		WristHubApp? app = item.App;
		WristHubIconData val = ((app != null) ? app.RichIcon : null);
		if (val != null)
		{
			if (!_sdkRichIcons.TryGetValue(item.App.Id, out Texture2D value))
			{
				try
				{
					value = new Texture2D(2, 2, (TextureFormat)4, false)
					{
						name = "WristHub App Icon " + SafeSdkId(item.App.Id)
					};
					if (!ImageConversion.LoadImage(value, Il2CppStructArray<byte>.op_Implicit(val.Png.ToArray()), true) || ((Texture)value).width > 256 || ((Texture)value).height > 256)
					{
						Object.Destroy((Object)(object)value);
						return;
					}
					_sdkRichIcons[item.App.Id] = value;
					_sdkRichIconOrder.Enqueue(item.App.Id);
					TrimSdkRichIconCache();
				}
				catch
				{
					return;
				}
			}
			tile.Artwork.texture = (Texture)(object)value;
			((Graphic)tile.Artwork).color = Color.white;
			tile.Monogram.text = string.Empty;
			return;
		}
		string text = item.Package?.IconUrl;
		if (string.IsNullOrWhiteSpace(text))
		{
			return;
		}
		string identity = tile.ArtworkIdentity;
		int generation = _appArtworkGeneration;
		_thumbnails.Load(text, delegate(Texture2D texture)
		{
			//IL_005e: Unknown result type (might be due to invalid IL or missing references)
			if (!_disposed && generation == _appArtworkGeneration && !(tile.ArtworkIdentity != identity) && !((Object)(object)texture == (Object)null))
			{
				tile.Artwork.texture = (Texture)(object)texture;
				((Graphic)tile.Artwork).color = Color.white;
				tile.Monogram.text = string.Empty;
			}
		});
	}

	private void ActivateAppLibraryTile(int slot)
	{
		//IL_002e: Unknown result type (might be due to invalid IL or missing references)
		if (slot < 0 || slot >= _appLibraryTiles.Count)
		{
			return;
		}
		WristAppLibraryItem item = _appLibraryTiles[slot].Item;
		if ((object)item == null)
		{
			return;
		}
		if ((int)_preferences.WristAppLibraryTab == 0 && !_appLibraryManageMode && item.App != null)
		{
			LaunchSdkApp(item.App);
			return;
		}
		_appLibraryDetailItem = item;
		GameObject? appLibraryGridRoot = _appLibraryGridRoot;
		if (appLibraryGridRoot != null)
		{
			appLibraryGridRoot.SetActive(false);
		}
		GameObject? appLibraryDetailRoot = _appLibraryDetailRoot;
		if (appLibraryDetailRoot != null)
		{
			appLibraryDetailRoot.SetActive(true);
		}
		PaintAppLibraryDetail();
		RequireInputRearm();
	}

	private void PaintAppLibraryDetail()
	{
		//IL_0180: Unknown result type (might be due to invalid IL or missing references)
		//IL_0179: Unknown result type (might be due to invalid IL or missing references)
		//IL_0172: Unknown result type (might be due to invalid IL or missing references)
		//IL_0211: Unknown result type (might be due to invalid IL or missing references)
		//IL_0218: Unknown result type (might be due to invalid IL or missing references)
		WristAppLibraryItem appLibraryDetailItem = _appLibraryDetailItem;
		if (!(appLibraryDetailItem == null))
		{
			if ((Object)(object)_appDetailName != (Object)null)
			{
				_appDetailName.text = appLibraryDetailItem.Name;
			}
			if ((Object)(object)_appDetailAuthor != (Object)null)
			{
				_appDetailAuthor.text = $"By {appLibraryDetailItem.Author}  •  v{appLibraryDetailItem.Version}  •  {AppPlatformLabel(appLibraryDetailItem)}";
			}
			if ((Object)(object)_appDetailDescription != (Object)null)
			{
				_appDetailDescription.text = (string.IsNullOrWhiteSpace(appLibraryDetailItem.Description) ? "WristHub SDK app" : appLibraryDetailItem.Description);
			}
			ThunderstorePackageEntry package = appLibraryDetailItem.Package;
			if ((Object)(object)_appDetailStatus != (Object)null)
			{
				_appDetailStatus.text = ((package == null) ? "PACKAGE NOT LINKED • INSTALLED APP" : (package.Pending ? "QUEUED • RESTART REQUIRED" : (package.HasUpdate ? "UPDATE AVAILABLE" : ((!package.Installed) ? "CODE APP • RUNS WITH NORMAL MOD PERMISSIONS AFTER RESTART" : ((appLibraryDetailItem.App == null) ? "INSTALLED • RESTART OR APP DID NOT REGISTER" : "INSTALLED • READY")))));
				((Graphic)_appDetailStatus).color = ((((object)package != null && package.Pending) || ((object)package != null && package.HasUpdate)) ? Amber : ((appLibraryDetailItem.App != null) ? Green : Cyan));
			}
			if (_appDetailAction != null)
			{
				_appDetailAction.Label.text = (((object)package != null && package.Pending) ? "QUEUED" : (((object)package != null && package.HasUpdate) ? "UPDATE" : (((object)package != null && !package.Installed) ? "INSTALL" : ((appLibraryDetailItem.App != null) ? "OPEN" : "RESTART"))));
				((Graphic)_appDetailAction.Panel).color = ((((object)package != null && package.HasUpdate) || ((object)package != null && !package.Installed)) ? Amber : Cyan);
			}
			bool flag = appLibraryDetailItem.App != null;
			if (_appDetailPin != null)
			{
				_appDetailPin.Root.SetActive(flag);
				_appDetailPin.Label.text = ((flag && _preferences.PinnedWristAppIds.Contains<string>(appLibraryDetailItem.App.Id, StringComparer.OrdinalIgnoreCase)) ? "UNPIN" : "PIN HOME");
			}
			if (_appDetailUninstall != null)
			{
				_appDetailUninstall.Root.SetActive((object)package != null && package.Installed && !package.Pending);
			}
		}
	}

	private void CloseAppLibraryDetail()
	{
		_appLibraryDetailItem = null;
		GameObject? appLibraryDetailRoot = _appLibraryDetailRoot;
		if (appLibraryDetailRoot != null)
		{
			appLibraryDetailRoot.SetActive(false);
		}
		GameObject? appLibraryGridRoot = _appLibraryGridRoot;
		if (appLibraryGridRoot != null)
		{
			appLibraryGridRoot.SetActive(true);
		}
		RequireInputRearm();
	}

	private void ActivateAppDetailAction()
	{
		//IL_0095: Unknown result type (might be due to invalid IL or missing references)
		//IL_008e: Unknown result type (might be due to invalid IL or missing references)
		WristAppLibraryItem appLibraryDetailItem = _appLibraryDetailItem;
		if (appLibraryDetailItem == null)
		{
			return;
		}
		ThunderstorePackageEntry package = appLibraryDetailItem.Package;
		if ((object)package != null && package.Pending)
		{
			return;
		}
		ThunderstorePackageEntry package2 = appLibraryDetailItem.Package;
		if ((object)package2 != null && (!package2.Installed || package2.HasUpdate))
		{
			string message;
			bool flag = _thunderstoreStore.TryQueueInstall((package2.FullName.Length == 0) ? package2.Name : package2.FullName, out message);
			if ((Object)(object)_appDetailStatus != (Object)null)
			{
				_appDetailStatus.text = message;
				((Graphic)_appDetailStatus).color = (flag ? Amber : Red);
			}
			_nextAppLibraryRefresh = Time.unscaledTime + 0.25f;
		}
		else if (appLibraryDetailItem.App != null)
		{
			LaunchSdkApp(appLibraryDetailItem.App);
		}
	}

	private void ToggleSelectedAppPin()
	{
		//IL_0092: Unknown result type (might be due to invalid IL or missing references)
		WristHubApp app = _appLibraryDetailItem?.App;
		if (app == null)
		{
			return;
		}
		int num = _preferences.PinnedWristAppIds.FindIndex((string value) => value.Equals(app.Id, StringComparison.OrdinalIgnoreCase));
		if (num >= 0)
		{
			_preferences.PinnedWristAppIds.RemoveAt(num);
		}
		else
		{
			if (_preferences.PinnedWristAppIds.Count >= 8)
			{
				if ((Object)(object)_appDetailStatus != (Object)null)
				{
					_appDetailStatus.text = "HOME IS FULL • UNPIN AN APP FIRST";
					((Graphic)_appDetailStatus).color = Amber;
				}
				return;
			}
			_preferences.PinnedWristAppIds.Add(app.Id);
		}
		SavePreferences();
		RefreshHomeAppTiles();
		PaintAppLibraryDetail();
	}

	private void UninstallSelectedSdkApp()
	{
		//IL_0061: Unknown result type (might be due to invalid IL or missing references)
		//IL_005a: Unknown result type (might be due to invalid IL or missing references)
		ThunderstorePackageEntry thunderstorePackageEntry = _appLibraryDetailItem?.Package;
		if (thunderstorePackageEntry == null)
		{
			return;
		}
		string message;
		bool flag = _thunderstoreStore.TryQueueUninstall(thunderstorePackageEntry.PackageNamespace, out message);
		if ((Object)(object)_appDetailStatus != (Object)null)
		{
			_appDetailStatus.text = message;
			((Graphic)_appDetailStatus).color = (flag ? Amber : Red);
		}
		if (flag)
		{
			WristHubApp app = _appLibraryDetailItem?.App;
			if (app != null)
			{
				_preferences.PinnedWristAppIds.RemoveAll((string value) => value.Equals(app.Id, StringComparison.OrdinalIgnoreCase));
				SavePreferences();
				RefreshHomeAppTiles();
			}
		}
		_nextAppLibraryRefresh = Time.unscaledTime + 0.25f;
	}

	private void LaunchSdkApp(WristHubApp app)
	{
		if (!_preferences.OpenedWristAppIds.Contains<string>(app.Id, StringComparer.OrdinalIgnoreCase))
		{
			_preferences.OpenedWristAppIds.Add(app.Id);
			SavePreferences();
		}
		OpenRegisteredSdkApp(app.Id);
	}

	private ThunderstorePackageEntry? FindSdkPackage(WristHubApp app)
	{
		if (app.Store != (WristHubStoreProfile)null)
		{
			return _sdkStorePackages.FirstOrDefault((ThunderstorePackageEntry package) => package.PackageNamespace.Equals(app.Store.ThunderstoreNamespace, StringComparison.OrdinalIgnoreCase));
		}
		string normalizedName = NormalizeAppIdentity(app.Name);
		string normalizedAuthor = NormalizeAppIdentity(app.Author);
		return _sdkStorePackages.FirstOrDefault((ThunderstorePackageEntry package) => NormalizeAppIdentity(package.Name) == normalizedName && (normalizedAuthor.Length == 0 || NormalizeAppIdentity(package.Author) == normalizedAuthor));
	}

	private WristHubApp? FindSdkApp(ThunderstorePackageEntry package)
	{
		return ((IEnumerable<WristHubApp>)WristHubApi.GetRegisteredApps()).FirstOrDefault((Func<WristHubApp, bool>)delegate(WristHubApp app)
		{
			WristHubStoreProfile store = app.Store;
			return (store != null && store.ThunderstoreNamespace.Equals(package.PackageNamespace, StringComparison.OrdinalIgnoreCase)) || (NormalizeAppIdentity(app.Name) == NormalizeAppIdentity(package.Name) && NormalizeAppIdentity(app.Author) == NormalizeAppIdentity(package.Author));
		});
	}

	private static bool AppMatches(WristHubApp app, string query)
	{
		if (!string.IsNullOrWhiteSpace(query) && !app.Name.Contains(query, StringComparison.OrdinalIgnoreCase) && !app.Author.Contains(query, StringComparison.OrdinalIgnoreCase) && !app.Description.Contains(query, StringComparison.OrdinalIgnoreCase))
		{
			WristHubStoreProfile store = app.Store;
			if (store == null)
			{
				return false;
			}
			return store.Tagline.Contains(query, StringComparison.OrdinalIgnoreCase);
		}
		return true;
	}

	private static string NormalizeAppIdentity(string value)
	{
		return new string(value.Where(char.IsLetterOrDigit).Select(char.ToUpperInvariant).ToArray());
	}

	private string AppPlatformLabel(WristAppLibraryItem item)
	{
		//IL_002b: Unknown result type (might be due to invalid IL or missing references)
		//IL_0030: Unknown result type (might be due to invalid IL or missing references)
		//IL_0031: Unknown result type (might be due to invalid IL or missing references)
		//IL_0033: Unknown result type (might be due to invalid IL or missing references)
		//IL_0045: Expected I4, but got Unknown
		WristHubApp? app = item.App;
		WristHubStoreProfile val = ((app != null) ? app.Store : null);
		if (val == null)
		{
			if (!_constrainedRendering)
			{
				return "PCVR";
			}
			return "QUEST";
		}
		WristHubStorePlatform platforms = val.Platforms;
		return (platforms - 1) switch
		{
			2 => "PCVR + QUEST", 
			0 => "PCVR", 
			1 => "QUEST", 
			_ => "PLATFORM UNKNOWN", 
		};
	}

	private void TrimSdkRichIconCache()
	{
		int num = (_constrainedRendering ? 24 : 64);
		while (_sdkRichIcons.Count > num && _sdkRichIconOrder.Count > 0)
		{
			string key = _sdkRichIconOrder.Dequeue();
			if (!_sdkRichIcons.Remove(key, out Texture2D value))
			{
				continue;
			}
			try
			{
				if ((Object)(object)value != (Object)null)
				{
					Object.Destroy((Object)(object)value);
				}
			}
			catch
			{
			}
		}
	}

	private static string AppMonogram(string? preferred, string name)
	{
		string text = preferred?.Trim() ?? string.Empty;
		int length = text.Length;
		if (length > 0 && length <= 3)
		{
			return text.ToUpperInvariant();
		}
		string[] array = name.Split(' ', StringSplitOptions.RemoveEmptyEntries);
		if (array.Length < 2)
		{
			if (name.Length != 0)
			{
				return name.Substring(0, Math.Min(2, name.Length)).ToUpperInvariant();
			}
			return "APP";
		}
		return string.Concat(array[0][0], array[1][0]).ToUpperInvariant();
	}

	private void TickAppLibrary()
	{
		TickOsHomeSwipe();
		GameObject? appLibraryPage = _appLibraryPage;
		if (appLibraryPage != null && appLibraryPage.activeSelf)
		{
			RefreshAppLibrary(force: false);
		}
	}

	private void TickOsHomeSwipe()
	{
		//IL_0095: Unknown result type (might be due to invalid IL or missing references)
		//IL_009a: Unknown result type (might be due to invalid IL or missing references)
		//IL_009f: Unknown result type (might be due to invalid IL or missing references)
		//IL_00ae: Unknown result type (might be due to invalid IL or missing references)
		//IL_00c0: Unknown result type (might be due to invalid IL or missing references)
		//IL_010b: Unknown result type (might be due to invalid IL or missing references)
		//IL_00da: Unknown result type (might be due to invalid IL or missing references)
		//IL_0126: Unknown result type (might be due to invalid IL or missing references)
		GameObject? osToolsPage = _osToolsPage;
		if (osToolsPage != null && osToolsPage.activeSelf)
		{
			_homeSwipeProbeId = 0;
			return;
		}
		GameObject? hub = _hub;
		if (hub == null || !hub.activeSelf)
		{
			_homeSwipeProbeId = 0;
			return;
		}
		bool flag = false;
		foreach (BoneHubFingertipProbe probe in _fingertips.GetProbes())
		{
			if ((Object)(object)probe.Hand == (Object)null || !WristInteractionMath.IsOppositeHand(probe.IsLeft, _preferences.UseLeftWrist))
			{
				continue;
			}
			Vector3 val = _hub.transform.InverseTransformPoint(probe.Position);
			int instanceID = ((Object)probe.Hand).GetInstanceID();
			int num;
			if (Math.Abs(val.z) <= 0.025f)
			{
				float y = val.y;
				if (y >= 0.045f && y <= 0.092f)
				{
					num = ((Math.Abs(val.x) <= 0.085f) ? 1 : 0);
					goto IL_00f2;
				}
			}
			num = 0;
			goto IL_00f2;
			IL_00f2:
			bool flag2 = (byte)num != 0;
			if (_homeSwipeProbeId == 0 && flag2)
			{
				_homeSwipeProbeId = instanceID;
				_homeSwipeStartX = val.x;
				flag = true;
				break;
			}
			if (_homeSwipeProbeId == instanceID)
			{
				flag = true;
				float num2 = val.x - _homeSwipeStartX;
				if (Math.Abs(num2) >= 0.045f)
				{
					ShowOsHomePage((num2 < 0f) ? 1 : 0);
					_homeSwipeProbeId = -instanceID;
				}
				break;
			}
		}
		if (!flag)
		{
			_homeSwipeProbeId = 0;
		}
	}

	private void ResetAppLibraryReferences(bool destroyOwnedObjects)
	{
		if (_appLibrarySdkEventsAttached)
		{
			WristHubApi.AppsChanged -= OnAppLibrarySdkAppsChanged;
			_appLibrarySdkEventsAttached = false;
		}
		_appArtworkGeneration++;
		if (destroyOwnedObjects)
		{
			foreach (Texture2D value in _sdkRichIcons.Values)
			{
				try
				{
					if ((Object)(object)value != (Object)null)
					{
						Object.Destroy((Object)(object)value);
					}
				}
				catch
				{
				}
			}
		}
		_sdkRichIcons.Clear();
		_sdkRichIconOrder.Clear();
		_appLibraryTiles.Clear();
		_homeAppTiles.Clear();
		_appLibraryItems.Clear();
		_sdkStorePackages.Clear();
		_osHomePageOne = null;
		_osHomePageTwo = null;
		_osHomePageDotsOne = null;
		_osHomePageDotsTwo = null;
		_appLibraryPage = null;
		_appLibraryGridRoot = null;
		_appLibraryDetailRoot = null;
		_appLibraryTitle = null;
		_appLibraryPosition = null;
		_appLibraryRestartBanner = null;
		_appLibraryInstalledTab = null;
		_appLibrary

Mods\WristHub.SDK.dll

Decompiled 2 months ago
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Diagnostics;
using System.Linq;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.Versioning;
using System.Threading;

[assembly: CompilationRelaxations(8)]
[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
[assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)]
[assembly: InternalsVisibleTo("WristHub")]
[assembly: InternalsVisibleTo("BoneHub.Tests")]
[assembly: TargetFramework(".NETCoreApp,Version=v8.0", FrameworkDisplayName = ".NET 8.0")]
[assembly: AssemblyCompany("WristHub.SDK")]
[assembly: AssemblyConfiguration("Release")]
[assembly: AssemblyDescription("Stable public app and control API for WristHubOS.")]
[assembly: AssemblyFileVersion("1.0.0.0")]
[assembly: AssemblyInformationalVersion("4.2.1")]
[assembly: AssemblyProduct("WristHub.SDK")]
[assembly: AssemblyTitle("WristHub.SDK")]
[assembly: AssemblyVersion("1.0.0.0")]
[module: RefSafetyRules(11)]
namespace WristHub.SDK;

public static class WristHubApi
{
	public const int ApiVersion = 4;

	private static readonly object Sync = new object();

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

	private static IWristHubHost? _host;

	public static bool IsWristHubAvailable
	{
		get
		{
			lock (Sync)
			{
				return _host != null;
			}
		}
	}

	public static WristHubRuntimeInfo Runtime
	{
		get
		{
			lock (Sync)
			{
				return _host?.Runtime ?? WristHubRuntimeInfo.Unavailable;
			}
		}
	}

	public static int RegisteredAppCount
	{
		get
		{
			lock (Sync)
			{
				return Apps.Count;
			}
		}
	}

	public static event Action? AppsChanged;

	public static event Action<WristHubRuntimeInfo>? RuntimeChanged;

	public static WristHubApp RegisterApp(WristHubAppDefinition definition)
	{
		ArgumentNullException.ThrowIfNull(definition, "definition");
		definition.Validate();
		WristHubApp wristHubApp;
		lock (Sync)
		{
			if (Apps.ContainsKey(definition.Id))
			{
				throw new InvalidOperationException("A WristHub app with id '" + definition.Id + "' is already registered.");
			}
			wristHubApp = new WristHubApp(definition, OnAppChanged, Unregister);
			Apps.Add(definition.Id, wristHubApp);
		}
		SafeRaise(WristHubApi.AppsChanged);
		return wristHubApp;
	}

	public static WristHubApp RegisterApp(string id, string name, string author, string version, string description = "", string icon = "APP")
	{
		return RegisterApp(new WristHubAppDefinition(id, name, author, version, description, icon));
	}

	public static WristHubApp RegisterProvider(IWristHubAppProvider provider)
	{
		ArgumentNullException.ThrowIfNull(provider, "provider");
		WristHubApp wristHubApp = provider.CreateWristHubApp() ?? throw new InvalidOperationException("The WristHub app provider returned no app.");
		lock (Sync)
		{
			if (!Apps.TryGetValue(wristHubApp.Id, out WristHubApp value) || wristHubApp != value)
			{
				throw new InvalidOperationException("The provider must return an app registered through WristHubApi.RegisterApp.");
			}
			return wristHubApp;
		}
	}

	public static IReadOnlyList<WristHubApp> GetRegisteredApps()
	{
		lock (Sync)
		{
			return new ReadOnlyCollection<WristHubApp>(Apps.Values.OrderBy<WristHubApp, string>((WristHubApp app) => app.Name, StringComparer.OrdinalIgnoreCase).ToArray());
		}
	}

	public static void Notify(string appId, string message, WristHubNotificationKind kind = WristHubNotificationKind.Info)
	{
		if (string.IsNullOrWhiteSpace(message))
		{
			return;
		}
		IWristHubHost host;
		lock (Sync)
		{
			host = _host;
		}
		try
		{
			host?.Notify(appId?.Trim() ?? string.Empty, message.Trim(), kind);
		}
		catch
		{
		}
	}

	public static void Notify(string appId, WristHubNotificationDefinition notification)
	{
		ArgumentNullException.ThrowIfNull(notification, "notification");
		notification.Validate();
		IWristHubHost host;
		lock (Sync)
		{
			host = _host;
		}
		try
		{
			host?.Notify(appId?.Trim() ?? string.Empty, notification);
		}
		catch
		{
		}
	}

	public static void OpenApp(string appId)
	{
		IWristHubHost host;
		lock (Sync)
		{
			host = _host;
		}
		try
		{
			host?.RequestOpen(appId?.Trim() ?? string.Empty);
		}
		catch
		{
		}
	}

	internal static void AttachHost(IWristHubHost host)
	{
		ArgumentNullException.ThrowIfNull(host, "host");
		lock (Sync)
		{
			_host = host;
		}
		SafeRaise(WristHubApi.RuntimeChanged, host.Runtime);
		SafeRaise(WristHubApi.AppsChanged);
	}

	internal static void DetachHost(IWristHubHost host)
	{
		lock (Sync)
		{
			if (_host != host)
			{
				return;
			}
			_host = null;
		}
		SafeRaise(WristHubApi.RuntimeChanged, WristHubRuntimeInfo.Unavailable);
	}

	private static void Unregister(WristHubApp app)
	{
		lock (Sync)
		{
			if (Apps.TryGetValue(app.Id, out WristHubApp value) && value == app)
			{
				Apps.Remove(app.Id);
			}
		}
		SafeRaise(WristHubApi.AppsChanged);
	}

	private static void OnAppChanged()
	{
		SafeRaise(WristHubApi.AppsChanged);
	}

	private static void SafeRaise(Action? callback)
	{
		if (callback == null)
		{
			return;
		}
		Delegate[] invocationList = callback.GetInvocationList();
		for (int i = 0; i < invocationList.Length; i++)
		{
			Action action = (Action)invocationList[i];
			try
			{
				action();
			}
			catch
			{
			}
		}
	}

	private static void SafeRaise<T>(Action<T>? callback, T value)
	{
		if (callback == null)
		{
			return;
		}
		Delegate[] invocationList = callback.GetInvocationList();
		for (int i = 0; i < invocationList.Length; i++)
		{
			Action<T> action = (Action<T>)invocationList[i];
			try
			{
				action(value);
			}
			catch
			{
			}
		}
	}
}
internal interface IWristHubHost
{
	WristHubRuntimeInfo Runtime { get; }

	void Notify(string appId, string message, WristHubNotificationKind kind);

	void Notify(string appId, WristHubNotificationDefinition notification);

	void RequestOpen(string appId);
}
public readonly record struct WristHubRuntimeInfo(bool Available, bool IsQuest, bool IsFusionConnected, string Theme, int ApiVersion)
{
	public static WristHubRuntimeInfo Unavailable => new WristHubRuntimeInfo(Available: false, IsQuest: false, IsFusionConnected: false, "Cyan", 4);
}
public enum WristHubNotificationKind
{
	Info,
	Success,
	Warning,
	Error
}
public enum WristHubNotificationPriority
{
	Routine,
	Normal,
	Important,
	Urgent
}
public enum WristHubNotificationPrivacy
{
	Public,
	PrivateReveal
}
public sealed record WristHubNotificationAction(string Id, string Label, Action? Activated = null)
{
	internal void Validate()
	{
		WristHubAppDefinition.ValidateText(Id, "Id", 48);
		WristHubAppDefinition.ValidateText(Label, "Label", 24);
	}
}
public sealed record WristHubNotificationDefinition(string Title, string Message, WristHubNotificationKind Kind = WristHubNotificationKind.Info, WristHubNotificationPriority Priority = WristHubNotificationPriority.Normal, WristHubNotificationPrivacy Privacy = WristHubNotificationPrivacy.PrivateReveal, string Icon = "APP", string DeepLink = "", TimeSpan? Lifetime = null, IReadOnlyList<WristHubNotificationAction>? Actions = null)
{
	internal void Validate()
	{
		WristHubAppDefinition.ValidateText(Title, "Title", 64);
		WristHubAppDefinition.ValidateText(Message, "Message", 512);
		if (!string.IsNullOrWhiteSpace(Icon))
		{
			WristHubAppDefinition.ValidateText(Icon, "Icon", 12);
		}
		if (!string.IsNullOrWhiteSpace(DeepLink))
		{
			WristHubAppDefinition.ValidateText(DeepLink, "DeepLink", 128);
		}
		TimeSpan? lifetime = Lifetime;
		if (lifetime.HasValue)
		{
			TimeSpan valueOrDefault = lifetime.GetValueOrDefault();
			if (valueOrDefault < TimeSpan.FromSeconds(2.0) || valueOrDefault > TimeSpan.FromDays(30.0))
			{
				throw new ArgumentOutOfRangeException("Lifetime");
			}
		}
		if ((Actions?.Count ?? 0) > 2)
		{
			throw new ArgumentOutOfRangeException("Actions");
		}
		if (Actions == null)
		{
			return;
		}
		foreach (WristHubNotificationAction action in Actions)
		{
			action.Validate();
		}
	}
}
public enum WristHubAccent
{
	Theme,
	Neutral,
	Success,
	Warning,
	Danger
}
public enum WristHubCommunityCategory
{
	Utility,
	Gameplay,
	Social,
	Accessibility,
	Developer,
	Other
}
public interface IWristHubAppProvider
{
	WristHubApp CreateWristHubApp();
}
[Flags]
public enum WristHubStorePlatform
{
	None = 0,
	PcVr = 1,
	Quest = 2,
	All = 3
}
public sealed record WristHubStoreProfile(string ThunderstoreNamespace, WristHubCommunityCategory Category, string Tagline, WristHubStorePlatform Platforms = WristHubStorePlatform.All, string Homepage = "", string SupportContact = "")
{
	internal void Validate()
	{
		ValidateNamespace(ThunderstoreNamespace);
		WristHubAppDefinition.ValidateText(Tagline, "Tagline", 120);
		if (Platforms == WristHubStorePlatform.None || (Platforms & ~WristHubStorePlatform.All) != WristHubStorePlatform.None)
		{
			throw new ArgumentOutOfRangeException("Platforms");
		}
		ValidateOptional(Homepage, "Homepage", 240);
		ValidateOptional(SupportContact, "SupportContact", 96);
	}

	internal static void ValidateNamespace(string value)
	{
		WristHubAppDefinition.ValidateText(value, "ThunderstoreNamespace", 128);
		int num = value.IndexOf('-');
		if (num <= 0 || num == value.Length - 1 || value.Any(delegate(char character)
		{
			bool flag = char.IsLetterOrDigit(character);
			if (!flag)
			{
				bool flag2 = ((character == '-' || character == '_') ? true : false);
				flag = flag2;
			}
			return !flag;
		}))
		{
			throw new ArgumentException("Use the Thunderstore Team-Package namespace without a version.", "ThunderstoreNamespace");
		}
	}

	private static void ValidateOptional(string? value, string name, int length)
	{
		if (!string.IsNullOrEmpty(value) && value.Length > length)
		{
			throw new ArgumentOutOfRangeException(name);
		}
	}
}
public sealed class WristHubIconData
{
	private const int MaximumBytes = 524288;

	private readonly byte[] _png;

	public ReadOnlyMemory<byte> Png => _png;

	private WristHubIconData(byte[] png)
	{
		_png = png;
	}

	public static WristHubIconData FromPng(ReadOnlySpan<byte> png)
	{
		int length = png.Length;
		if ((length < 24 || length > 524288) ? true : false)
		{
			throw new ArgumentOutOfRangeException("png");
		}
		if (png[0] != 137 || png[1] != 80 || png[2] != 78 || png[3] != 71 || png[4] != 13 || png[5] != 10 || png[6] != 26 || png[7] != 10)
		{
			throw new ArgumentException("Icon data must be a PNG image.", "png");
		}
		return new WristHubIconData(png.ToArray());
	}
}
public sealed record WristHubCommunityProfile(WristHubCommunityCategory Category, string Summary, string Homepage = "", string SupportContact = "", IReadOnlyList<string>? Tags = null)
{
	internal void Validate()
	{
		WristHubAppDefinition.ValidateText(Summary, "Summary", 160);
		ValidateOptional(Homepage, "Homepage", 240);
		ValidateOptional(SupportContact, "SupportContact", 96);
		IReadOnlyList<string> tags = Tags;
		if (tags != null && tags.Count > 8)
		{
			throw new ArgumentOutOfRangeException("Tags");
		}
		if (Tags == null)
		{
			return;
		}
		foreach (string tag in Tags)
		{
			WristHubAppDefinition.ValidateText(tag, "Tags", 24);
		}
	}

	private static void ValidateOptional(string? value, string name, int length)
	{
		if (!string.IsNullOrEmpty(value) && value.Length > length)
		{
			throw new ArgumentOutOfRangeException(name);
		}
	}
}
public sealed record WristHubAppDefinition(string Id, string Name, string Author, string Version, string Description = "", string Icon = "APP")
{
	internal void Validate()
	{
		ValidateToken(Id, "Id", 64);
		ValidateText(Name, "Name", 48);
		ValidateText(Author, "Author", 48);
		ValidateText(Version, "Version", 24);
		string description = Description;
		if (description != null && description.Length > 240)
		{
			throw new ArgumentOutOfRangeException("Description");
		}
		string icon = Icon;
		if (icon != null && icon.Length > 8)
		{
			throw new ArgumentOutOfRangeException("Icon");
		}
	}

	internal static void ValidateToken(string value, string name, int length)
	{
		ValidateText(value, name, length);
		if (value.Any(delegate(char character)
		{
			bool flag = char.IsLetterOrDigit(character);
			if (!flag)
			{
				bool flag2 = ((character == '-' || character == '.' || character == '_') ? true : false);
				flag = flag2;
			}
			return !flag;
		}))
		{
			throw new ArgumentException("Use only letters, numbers, dots, dashes, and underscores.", name);
		}
	}

	internal static void ValidateText(string value, string name, int length)
	{
		if (string.IsNullOrWhiteSpace(value))
		{
			throw new ArgumentException("Value is required.", name);
		}
		if (value.Length > length)
		{
			throw new ArgumentOutOfRangeException(name);
		}
	}
}
public sealed class WristHubApp : IDisposable
{
	private readonly Action _changed;

	private readonly Action<WristHubApp> _dispose;

	private int _disposed;

	public string Id { get; }

	public string Name { get; }

	public string Author { get; }

	public string Version { get; }

	public string Description { get; }

	public string Icon { get; }

	public WristHubPage Root { get; }

	public bool IsDisposed => _disposed != 0;

	public Func<bool>? IsAvailable { get; set; }

	public Func<int>? NotificationCount { get; set; }

	public WristHubCommunityProfile? Community { get; private set; }

	public WristHubStoreProfile? Store { get; private set; }

	public WristHubIconData? RichIcon { get; private set; }

	public Action? Opened { get; set; }

	public Action? Closed { get; set; }

	public bool Available
	{
		get
		{
			try
			{
				return !IsDisposed && (IsAvailable?.Invoke() ?? true);
			}
			catch
			{
				return false;
			}
		}
	}

	public int Notifications
	{
		get
		{
			try
			{
				return Math.Clamp(NotificationCount?.Invoke() ?? 0, 0, 99);
			}
			catch
			{
				return 0;
			}
		}
	}

	internal WristHubApp(WristHubAppDefinition definition, Action changed, Action<WristHubApp> dispose)
	{
		Id = definition.Id.Trim();
		Name = definition.Name.Trim();
		Author = definition.Author.Trim();
		Version = definition.Version.Trim();
		Description = definition.Description?.Trim() ?? string.Empty;
		Icon = (string.IsNullOrWhiteSpace(definition.Icon) ? "APP" : definition.Icon.Trim());
		_changed = changed;
		_dispose = dispose;
		Root = new WristHubPage("root", Name, null, changed);
	}

	public WristHubApp PublishToCommunity(WristHubCommunityProfile profile)
	{
		ArgumentNullException.ThrowIfNull(profile, "profile");
		profile.Validate();
		Community = profile with
		{
			Summary = profile.Summary.Trim(),
			Homepage = (profile.Homepage?.Trim() ?? string.Empty),
			SupportContact = (profile.SupportContact?.Trim() ?? string.Empty),
			Tags = profile.Tags?.Select((string tag) => tag.Trim()).Distinct<string>(StringComparer.OrdinalIgnoreCase).ToArray()
		};
		_changed();
		return this;
	}

	public WristHubApp PublishToStore(WristHubStoreProfile profile)
	{
		ArgumentNullException.ThrowIfNull(profile, "profile");
		profile.Validate();
		Store = profile with
		{
			ThunderstoreNamespace = profile.ThunderstoreNamespace.Trim(),
			Tagline = profile.Tagline.Trim(),
			Homepage = (profile.Homepage?.Trim() ?? string.Empty),
			SupportContact = (profile.SupportContact?.Trim() ?? string.Empty)
		};
		_changed();
		return this;
	}

	public WristHubApp SetIcon(WristHubIconData icon)
	{
		ArgumentNullException.ThrowIfNull(icon, "icon");
		RichIcon = icon;
		_changed();
		return this;
	}

	public void RemoveFromCommunity()
	{
		if (!(Community == null))
		{
			Community = null;
			_changed();
		}
	}

	public void Refresh()
	{
		_changed();
	}

	internal void NotifyOpened()
	{
		try
		{
			Opened?.Invoke();
		}
		catch
		{
		}
	}

	internal void NotifyClosed()
	{
		try
		{
			Closed?.Invoke();
		}
		catch
		{
		}
	}

	public void Dispose()
	{
		if (Interlocked.Exchange(ref _disposed, 1) == 0)
		{
			_dispose(this);
		}
	}
}
public sealed class WristHubPage
{
	private readonly object _sync = new object();

	private readonly List<WristHubControl> _controls = new List<WristHubControl>();

	private readonly Action _changed;

	public string Id { get; }

	public string Title { get; set; }

	public WristHubPage? Parent { get; }

	public IReadOnlyList<WristHubControl> Controls
	{
		get
		{
			lock (_sync)
			{
				return _controls.ToArray();
			}
		}
	}

	internal WristHubPage(string id, string title, WristHubPage? parent, Action changed)
	{
		WristHubAppDefinition.ValidateToken(id, "id", 64);
		WristHubAppDefinition.ValidateText(title, "title", 64);
		Id = id.Trim();
		Title = title.Trim();
		Parent = parent;
		_changed = changed;
	}

	public WristHubPage AddPage(string id, string title, string description = "")
	{
		WristHubPage wristHubPage = new WristHubPage(id, title, this, _changed);
		Add(new WristHubPageLink(id, title, wristHubPage, description));
		return wristHubPage;
	}

	public WristHubAction AddButton(string id, string label, Action action, string description = "", WristHubAccent accent = WristHubAccent.Theme)
	{
		return Add(new WristHubAction(id, label, action, description, accent));
	}

	public WristHubToggle AddToggle(string id, string label, Func<bool> read, Action<bool> write, string description = "")
	{
		return Add(new WristHubToggle(id, label, read, write, description));
	}

	public WristHubSlider AddSlider(string id, string label, double minimum, double maximum, double step, Func<double> read, Action<double> write, string description = "")
	{
		return Add(new WristHubSlider(id, label, minimum, maximum, step, read, write, description));
	}

	public WristHubChoice AddChoice(string id, string label, IReadOnlyList<string> choices, Func<int> read, Action<int> write, string description = "")
	{
		return Add(new WristHubChoice(id, label, choices, read, write, description));
	}

	public WristHubTextInput AddText(string id, string label, Func<string> read, Action<string> write, string description = "")
	{
		return Add(new WristHubTextInput(id, label, read, write, description));
	}

	public WristHubColorInput AddColor(string id, string label, Func<WristHubColorValue> read, Action<WristHubColorValue> write, IReadOnlyList<WristHubColorValue>? palette = null, string description = "")
	{
		return Add(new WristHubColorInput(id, label, read, write, palette, description));
	}

	public WristHubLabel AddLabel(string id, string label, Func<string> read, string description = "")
	{
		return Add(new WristHubLabel(id, label, read, description));
	}

	public T Add<T>(T control) where T : WristHubControl
	{
		ArgumentNullException.ThrowIfNull(control, "control");
		lock (_sync)
		{
			if (_controls.Any((WristHubControl existing) => existing.Id.Equals(control.Id, StringComparison.OrdinalIgnoreCase)))
			{
				throw new InvalidOperationException($"Control id '{control.Id}' already exists on page '{Id}'.");
			}
			_controls.Add(control);
		}
		_changed();
		return control;
	}

	public bool Remove(string id)
	{
		bool flag;
		lock (_sync)
		{
			flag = _controls.RemoveAll((WristHubControl control) => control.Id.Equals(id, StringComparison.OrdinalIgnoreCase)) > 0;
		}
		if (flag)
		{
			_changed();
		}
		return flag;
	}
}
public abstract class WristHubControl
{
	public string Id { get; }

	public string Label { get; set; }

	public string Description { get; set; }

	public WristHubAccent Accent { get; set; }

	public Func<bool>? IsEnabled { get; set; }

	public bool Enabled
	{
		get
		{
			try
			{
				return IsEnabled?.Invoke() ?? true;
			}
			catch
			{
				return false;
			}
		}
	}

	protected WristHubControl(string id, string label, string description, WristHubAccent accent = WristHubAccent.Theme)
	{
		WristHubAppDefinition.ValidateToken(id, "id", 64);
		WristHubAppDefinition.ValidateText(label, "label", 64);
		Id = id.Trim();
		Label = label.Trim();
		Description = description?.Trim() ?? string.Empty;
		Accent = accent;
	}
}
public sealed class WristHubAction : WristHubControl
{
	private readonly Action _action;

	internal WristHubAction(string id, string label, Action action, string description, WristHubAccent accent)
		: base(id, label, description, accent)
	{
		_action = action ?? throw new ArgumentNullException("action");
	}

	public void Invoke()
	{
		_action();
	}
}
public sealed class WristHubToggle : WristHubControl
{
	private readonly Func<bool> _read;

	private readonly Action<bool> _write;

	public bool Value => _read();

	internal WristHubToggle(string id, string label, Func<bool> read, Action<bool> write, string description)
		: base(id, label, description)
	{
		_read = read;
		_write = write;
	}

	public void Toggle()
	{
		_write(!Value);
	}
}
public sealed class WristHubSlider : WristHubControl
{
	private readonly Func<double> _read;

	private readonly Action<double> _write;

	public double Minimum { get; }

	public double Maximum { get; }

	public double Step { get; }

	public double Value => Math.Clamp(_read(), Minimum, Maximum);

	internal WristHubSlider(string id, string label, double min, double max, double step, Func<double> read, Action<double> write, string description)
		: base(id, label, description)
	{
		if (!double.IsFinite(min) || !double.IsFinite(max) || max <= min)
		{
			throw new ArgumentOutOfRangeException("max");
		}
		if (!double.IsFinite(step) || step <= 0.0)
		{
			throw new ArgumentOutOfRangeException("step");
		}
		Minimum = min;
		Maximum = max;
		Step = step;
		_read = read;
		_write = write;
	}

	public void Adjust(int direction)
	{
		_write(Math.Clamp(Value + (double)Math.Sign(direction) * Step, Minimum, Maximum));
	}
}
public sealed class WristHubChoice : WristHubControl
{
	private readonly Func<int> _read;

	private readonly Action<int> _write;

	public IReadOnlyList<string> Choices { get; }

	public int Index => Math.Clamp(_read(), 0, Choices.Count - 1);

	public string Value => Choices[Index];

	internal WristHubChoice(string id, string label, IReadOnlyList<string> choices, Func<int> read, Action<int> write, string description)
		: base(id, label, description)
	{
		Choices = (from choice in choices?.Where((string choice) => !string.IsNullOrWhiteSpace(choice))
			select choice.Trim()).ToArray() ?? throw new ArgumentNullException("choices");
		if (Choices.Count == 0)
		{
			throw new ArgumentException("At least one choice is required.", "choices");
		}
		_read = read;
		_write = write;
	}

	public void Adjust(int direction)
	{
		_write((Index + Math.Sign(direction) + Choices.Count) % Choices.Count);
	}
}
public sealed class WristHubTextInput : WristHubControl
{
	private readonly Func<string> _read;

	private readonly Action<string> _write;

	public string Value => _read() ?? string.Empty;

	internal WristHubTextInput(string id, string label, Func<string> read, Action<string> write, string description)
		: base(id, label, description)
	{
		_read = read;
		_write = write;
	}

	public void Set(string value)
	{
		_write(value ?? string.Empty);
	}
}
public readonly record struct WristHubColorValue(byte Red, byte Green, byte Blue, byte Alpha = byte.MaxValue)
{
	public string Hex => $"#{Red:X2}{Green:X2}{Blue:X2}{Alpha:X2}";

	public static WristHubColorValue Cyan => new WristHubColorValue(38, 221, byte.MaxValue);

	public static WristHubColorValue Graphite => new WristHubColorValue(173, 184, 194);

	public static WristHubColorValue Crimson => new WristHubColorValue(byte.MaxValue, 61, 79);

	public static WristHubColorValue Emerald => new WristHubColorValue(61, 240, 140);

	public static WristHubColorValue Violet => new WristHubColorValue(173, 107, byte.MaxValue);

	public static WristHubColorValue Amber => new WristHubColorValue(byte.MaxValue, 168, 46);
}
public sealed class WristHubColorInput : WristHubControl
{
	private static readonly WristHubColorValue[] DefaultPalette = new WristHubColorValue[6]
	{
		WristHubColorValue.Cyan,
		WristHubColorValue.Graphite,
		WristHubColorValue.Crimson,
		WristHubColorValue.Emerald,
		WristHubColorValue.Violet,
		WristHubColorValue.Amber
	};

	private readonly Func<WristHubColorValue> _read;

	private readonly Action<WristHubColorValue> _write;

	public IReadOnlyList<WristHubColorValue> Palette { get; }

	public WristHubColorValue Value => _read();

	internal WristHubColorInput(string id, string label, Func<WristHubColorValue> read, Action<WristHubColorValue> write, IReadOnlyList<WristHubColorValue>? palette, string description)
		: base(id, label, description)
	{
		_read = read;
		_write = write;
		Palette = ((palette != null && palette.Count > 0) ? palette.ToArray() : DefaultPalette);
	}

	public void Set(WristHubColorValue value)
	{
		_write(value);
	}

	public void Adjust(int direction)
	{
		WristHubColorValue value = Value;
		int val = -1;
		for (int i = 0; i < Palette.Count; i++)
		{
			if (Palette[i].Equals(value))
			{
				val = i;
				break;
			}
		}
		val = (Math.Max(0, val) + Math.Sign(direction) + Palette.Count) % Palette.Count;
		_write(Palette[val]);
	}
}
public sealed class WristHubLabel : WristHubControl
{
	private readonly Func<string> _read;

	public string Value => _read() ?? string.Empty;

	internal WristHubLabel(string id, string label, Func<string> read, string description)
		: base(id, label, description, WristHubAccent.Neutral)
	{
		_read = read;
	}
}
public sealed class WristHubPageLink : WristHubControl
{
	public WristHubPage Page { get; }

	internal WristHubPageLink(string id, string label, WristHubPage page, string description)
		: base(id, label, description)
	{
		Page = page;
	}
}