Decompiled source of WristHub v2.0.1

Mods\WristHub.Core.dll

Decompiled a day 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;
using Microsoft.CodeAnalysis;

[assembly: CompilationRelaxations(8)]
[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
[assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)]
[assembly: TargetFramework(".NETCoreApp,Version=v6.0", FrameworkDisplayName = ".NET 6.0")]
[assembly: AssemblyCompany("WristHub.Core")]
[assembly: AssemblyConfiguration("Release")]
[assembly: AssemblyFileVersion("2.0.1.0")]
[assembly: AssemblyInformationalVersion("2.0.1")]
[assembly: AssemblyProduct("WristHub.Core")]
[assembly: AssemblyTitle("WristHub.Core")]
[assembly: AssemblyVersion("2.0.1.0")]
[module: RefSafetyRules(11)]
namespace Microsoft.CodeAnalysis
{
	[CompilerGenerated]
	[Embedded]
	internal sealed class EmbeddedAttribute : Attribute
	{
	}
}
namespace System.Runtime.CompilerServices
{
	[CompilerGenerated]
	[Embedded]
	[AttributeUsage(AttributeTargets.Class | AttributeTargets.Property | AttributeTargets.Field | AttributeTargets.Event | AttributeTargets.Parameter | AttributeTargets.ReturnValue | AttributeTargets.GenericParameter, AllowMultiple = false, Inherited = false)]
	internal sealed class NullableAttribute : Attribute
	{
		public readonly byte[] NullableFlags;

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

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

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

		public RefSafetyRulesAttribute(int P_0)
		{
			Version = P_0;
		}
	}
}
namespace 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);
		}

		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 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;
				}
				_state.Preferences.WatchScale = Math.Clamp(_state.Preferences.WatchScale, 0.65f, 1.6f);
				_state.Preferences.DwellSeconds = Math.Clamp(_state.Preferences.DwellSeconds, 1.5f, 1.5f);
				_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.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);
				await _stateStore.SaveAsync(_state, cancellationToken).ConfigureAwait(continueOnCapturedContext: false);
			}
			finally
			{
				_stateGate.Release();
			}
		}

		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 BoneHubComfortPreset ComfortPreset { get; set; }

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

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

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

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

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

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

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

		public bool ReducedMotion { get; set; }

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

		public bool HapticsEnabled { 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 BrowserStartupMode AvatarBrowserStartupMode { get; set; } = BrowserStartupMode.AllInstalled;

		public AvatarBrowseScope LastAvatarBrowseScope { get; set; }
	}
	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,
		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 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 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 VisualHeightMeters, bool Valid)
	{
		public string DisplayText
		{
			get
			{
				if (!Valid || !float.IsFinite(VisualHeightMeters) || !(VisualHeightMeters > 0f))
				{
					return "Height unavailable";
				}
				if (VisualHeightMeters < 10f)
				{
					return $"{VisualHeightMeters:0.00} m";
				}
				return $"{VisualHeightMeters:0.0} m";
			}
		}

		public string GameDisplayText
		{
			get
			{
				if (!Valid)
				{
					return "Game height unavailable";
				}
				return "Game 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.IsUsableHeight(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 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 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 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 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; } = true;

		public double NotBeforeSeconds { get; private set; }

		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 TouchBounds(float CenterX, float CenterY, float CenterZ, float HalfX, float HalfY, float HalfZ)
	{
		public bool Contains(float x, float y, float z)
		{
			if (Math.Abs(x - CenterX) <= Math.Max(0f, HalfX) && Math.Abs(y - CenterY) <= Math.Max(0f, HalfY))
			{
				return Math.Abs(z - CenterZ) <= Math.Max(0f, HalfZ);
			}
			return false;
		}
	}
	public sealed class TouchDebounce
	{
		public bool Latched { get; private set; }

		public bool Update(bool touching)
		{
			if (!touching)
			{
				Latched = false;
				return false;
			}
			if (Latched)
			{
				return false;
			}
			Latched = true;
			return true;
		}
	}
	public sealed class ErrorLogThrottle
	{
		private readonly Dictionary<string, double> _lastLogged = new Dictionary<string, double>(StringComparer.Ordinal);

		public bool ShouldLog(string signature, double nowSeconds, double intervalSeconds = 5.0)
		{
			if (string.IsNullOrWhiteSpace(signature))
			{
				signature = "unknown";
			}
			if (_lastLogged.TryGetValue(signature, out var value) && nowSeconds - value < Math.Max(0.1, intervalSeconds))
			{
				return false;
			}
			_lastLogged[signature] = nowSeconds;
			if (_lastLogged.Count > 64)
			{
				double cutoff = nowSeconds - Math.Max(30.0, intervalSeconds * 4.0);
				string[] array = (from item in _lastLogged
					where item.Value < cutoff
					select item.Key).ToArray();
				foreach (string key in array)
				{
					_lastLogged.Remove(key);
				}
			}
			return true;
		}
	}
	public static class PreviewShellRecovery
	{
		public static bool RequiresRebuild(bool shellAlive, bool fallbackAlive)
		{
			if (shellAlive)
			{
				return !fallbackAlive;
			}
			return true;
		}

		public static bool ShouldSchedule(string? selectedBarcode, string? loadedBarcode, string? requestedBarcode)
		{
			if (string.IsNullOrWhiteSpace(selectedBarcode))
			{
				return false;
			}
			if (!string.Equals(selectedBarcode, loadedBarcode, StringComparison.OrdinalIgnoreCase))
			{
				return !string.Equals(selectedBarcode, requestedBarcode, StringComparison.OrdinalIgnoreCase);
			}
			return false;
		}
	}
	public readonly record struct WristActivationState(bool Armed, double RingProgress, bool Activate);
	public static class WristActivationMath
	{
		public const double ActivationSeconds = 1.5;

		public static WristActivationState Evaluate(double observedSeconds, double armSeconds)
		{
			double num = Math.Max(0.0, observedSeconds);
			double num2 = ((double.IsFinite(armSeconds) && armSeconds > 0.0) ? armSeconds : 1.5);
			double num3 = Math.Clamp(num / num2, 0.0, 1.0);
			return new WristActivationState(num > 0.0, num3, num3 >= 1.0);
		}
	}
	public sealed class WristRearmGate
	{
		public const double RequiredLookAwaySeconds = 0.25;

		private double _lookAwaySeconds;

		public bool RequiresLookAway { get; private set; }

		public void RequireLookAway()
		{
			RequiresLookAway = true;
			_lookAwaySeconds = 0.0;
		}

		public bool Update(bool observed, double deltaSeconds)
		{
			if (!RequiresLookAway)
			{
				return true;
			}
			if (observed)
			{
				_lookAwaySeconds = 0.0;
			}
			else
			{
				_lookAwaySeconds += Math.Max(0.0, deltaSeconds);
			}
			if (_lookAwaySeconds >= 0.25)
			{
				RequiresLookAway = false;
				_lookAwaySeconds = 0.0;
			}
			return !RequiresLookAway;
		}
	}
}
namespace BoneHub.Installation
{
	public static class NetworkerRegistrationWriter
	{
		private sealed record PalletInfo(string Barcode, string Version, string Author);

		private const long GameId = 3809L;

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

		public static async Task<ModIoSharingMetadata> WriteAsync(ModIoMod mod, ModIoFile installedFile, ModIoFileResolver.PlatformFiles platformFiles, IReadOnlyList<string> installedDirectories, string modsDirectory, CancellationToken cancellationToken = default(CancellationToken))
		{
			string root = Path.GetFullPath(modsDirectory).TrimEnd(Path.DirectorySeparatorChar);
			List<string> sidecars = new List<string>();
			List<string> pallets = new List<string>();
			foreach (string installedDirectory in installedDirectories)
			{
				string package = ValidateChild(installedDirectory, root);
				await WriteModInfoAsync(package, mod, installedFile, platformFiles, cancellationToken).ConfigureAwait(continueOnCapturedContext: false);
				foreach (string palletPath in PalletManifestLocator.FindAll(package, SearchOption.TopDirectoryOnly))
				{
					cancellationToken.ThrowIfCancellationRequested();
					PalletInfo pallet = await ReadPalletAsync(palletPath, cancellationToken).ConfigureAwait(continueOnCapturedContext: false);
					string manifestPath = Path.Combine(root, SafeFileName(pallet.Barcode) + ".manifest");
					string catalogPath = Directory.EnumerateFiles(package, "catalog_*.json", SearchOption.TopDirectoryOnly).FirstOrDefault() ?? Path.Combine(package, "catalog_" + pallet.Barcode + ".json");
					JsonObject value = BuildManifest(pallet, palletPath, catalogPath, mod, installedFile, platformFiles);
					await WriteAtomicAsync(manifestPath, value, cancellationToken).ConfigureAwait(continueOnCapturedContext: false);
					sidecars.Add(manifestPath);
					pallets.Add(pallet.Barcode);
				}
			}
			string warning = ((platformFiles.Windows == null || platformFiles.Android == null) ? ("This pack has no active file for " + ((platformFiles.Windows == null) ? "PC" : "Quest") + "; Fusion sharing is disabled for that platform.") : null);
			return new ModIoSharingMetadata
			{
				RegistrationVersion = 3,
				ModId = mod.Id,
				ModSlug = NonEmpty(mod.NameId, SafeSlug(mod.Name, mod.Id)),
				WindowsFileId = platformFiles.Windows?.Id,
				AndroidFileId = platformFiles.Android?.Id,
				SidecarManifestPaths = sidecars,
				PalletBarcodes = pallets,
				Warning = warning
			};
		}

		private static JsonObject BuildManifest(PalletInfo pallet, string palletPath, string catalogPath, ModIoMod mod, ModIoFile installedFile, ModIoFileResolver.PlatformFiles files)
		{
			JsonObject jsonObject = new JsonObject();
			int num = 3;
			string text = null;
			string text2 = null;
			JsonObject jsonObject2 = new JsonObject();
			if (files.Windows != null)
			{
				text = num++.ToString();
				jsonObject["pc"] = Ref(text, "mod-target-modio#0");
			}
			if (files.Android != null)
			{
				text2 = num++.ToString();
				jsonObject["android"] = Ref(text2, "mod-target-modio#0");
			}
			string text3 = text ?? text2;
			if (text3 != null)
			{
				jsonObject[NetworkerKey(mod, installedFile)] = Ref(text3, "mod-target-modio#0");
			}
			string text4 = DateTimeOffset.UtcNow.ToUnixTimeMilliseconds().ToString();
			jsonObject2["1"] = new JsonObject
			{
				["palletBarcode"] = pallet.Barcode,
				["palletPath"] = Path.GetFullPath(palletPath),
				["catalogPath"] = Path.GetFullPath(catalogPath),
				["version"] = NonEmpty(pallet.Version, "0.0.0"),
				["installedDate"] = text4,
				["updateDate"] = text4,
				["modListing"] = Ref("2", "mod-listing#0"),
				["active"] = true,
				["isa"] = Type("pallet-manifest#0")
			};
			jsonObject2["2"] = new JsonObject
			{
				["barcode"] = pallet.Barcode,
				["title"] = NonEmpty(mod.NameId, SafeSlug(mod.Name, mod.Id)),
				["description"] = NonEmpty(mod.Summary, string.Empty),
				["author"] = NonEmpty(mod.Author.DisplayName, NonEmpty(pallet.Author, "Unknown")),
				["version"] = NonEmpty(installedFile.Version, "0.0.0"),
				["thumbnailUrl"] = NonEmpty(mod.Logo.Thumbnail, string.Empty),
				["targets"] = jsonObject,
				["isa"] = Type("mod-listing#0")
			};
			if (files.Windows != null && text != null)
			{
				jsonObject2[text] = Target(mod.Id, files.Windows.Id);
			}
			if (files.Android != null && text2 != null)
			{
				jsonObject2[text2] = Target(mod.Id, files.Android.Id);
			}
			return new JsonObject
			{
				["version"] = 2,
				["root"] = Ref("1", "pallet-manifest#0"),
				["objects"] = jsonObject2
			};
		}

		private static async Task WriteModInfoAsync(string package, ModIoMod mod, ModIoFile installedFile, ModIoFileResolver.PlatformFiles files, CancellationToken cancellationToken)
		{
			JsonArray value = new JsonArray(((IEnumerable<ModIoTag>)mod.Tags).Select((Func<ModIoTag, JsonNode>)((ModIoTag tag) => Sanitize(tag.Name))).ToArray());
			JsonObject value2 = new JsonObject
			{
				["isValidMod"] = true,
				["downloading"] = false,
				["mature"] = mod.MaturityOption != 0,
				["isTracked"] = true,
				["modId"] = NonEmpty(mod.NameId, SafeSlug(mod.Name, mod.Id)),
				["thumbnailLink"] = NonEmpty(mod.Logo.Thumbnail, string.Empty),
				["modName"] = NonEmpty(mod.Name, "Avatar Pack"),
				["modSummary"] = NonEmpty(mod.Summary, string.Empty),
				["fileName"] = NonEmpty(installedFile.Filename, "modfile.zip"),
				["windowsDownloadLink"] = files.Windows?.Download.BinaryUrl ?? "nothing",
				["androidDownloadLink"] = files.Android?.Download.BinaryUrl ?? "nothing",
				["directDownloadLink"] = "nothing",
				["modDownloadPercentage"] = 100,
				["numericalId"] = mod.Id.ToString(),
				["version"] = NonEmpty(installedFile.Version, "0.0.0"),
				["tags"] = value,
				["author"] = NonEmpty(mod.Author.DisplayName, "Unknown"),
				["structureVersion"] = 4,
				["temp"] = false
			};
			await WriteAtomicAsync(Path.Combine(package, "modinfo.json"), value2, cancellationToken).ConfigureAwait(continueOnCapturedContext: false);
		}

		private static async Task<PalletInfo> ReadPalletAsync(string path, CancellationToken cancellationToken)
		{
			JsonNode? obj = JsonNode.Parse(await File.ReadAllTextAsync(path, cancellationToken).ConfigureAwait(continueOnCapturedContext: false)) ?? throw new InvalidDataException("The pallet manifest is empty.");
			string propertyName = obj["root"]?["ref"]?.GetValue<string>() ?? "1";
			JsonNode jsonNode = obj["objects"]?[propertyName] ?? throw new InvalidDataException("The pallet root is missing.");
			string obj2 = jsonNode["barcode"]?.GetValue<string>()?.Trim();
			if (string.IsNullOrWhiteSpace(obj2))
			{
				throw new InvalidDataException("The pallet barcode is missing.");
			}
			return new PalletInfo(obj2, jsonNode["version"]?.GetValue<string>() ?? "0.0.0", jsonNode["author"]?.GetValue<string>() ?? "Unknown");
		}

		private static string NetworkerKey(ModIoMod mod, ModIoFile file)
		{
			string[] array = (from tag in mod.Tags
				select Sanitize(tag.Name) into tag
				where tag.Length > 0
				select tag).ToArray();
			return $"networker;{mod.MaturityOption != 0};False;{file.FileSize};{Sanitize(file.Filename)};4;{Sanitize(mod.Name)};{array.Length}" + ((array.Length == 0) ? string.Empty : (";" + string.Join(";", array)));
		}

		private static JsonObject Ref(string value, string type)
		{
			return new JsonObject
			{
				["ref"] = value,
				["type"] = type
			};
		}

		private static JsonObject Type(string type)
		{
			return new JsonObject { ["type"] = type };
		}

		private static JsonObject Target(long modId, long fileId)
		{
			return new JsonObject
			{
				["thumbnailOverride"] = string.Empty,
				["gameId"] = 3809L,
				["modId"] = modId,
				["modfileId"] = fileId,
				["isa"] = Type("mod-target-modio#0")
			};
		}

		private static string NonEmpty(string? value, string fallback)
		{
			if (!string.IsNullOrWhiteSpace(value))
			{
				return value.Trim();
			}
			return fallback;
		}

		private static string SafeSlug(string? name, long modId)
		{
			string text = new string((from character in (name ?? string.Empty).Trim().ToLowerInvariant()
				select (!char.IsLetterOrDigit(character)) ? '-' : character).ToArray()).Trim('-');
			if (text.Length != 0)
			{
				return text;
			}
			return "wristhub-mod-" + modId;
		}

		private static string Sanitize(string? value)
		{
			return (value ?? string.Empty).Replace(';', ' ').Trim();
		}

		private static string SafeFileName(string value)
		{
			return string.Concat(value.Select((char character) => (!Path.GetInvalidFileNameChars().Contains(character)) ? character : '_'));
		}

		private static string ValidateChild(string path, string root)
		{
			string text = Path.GetFullPath(path).TrimEnd(Path.DirectorySeparatorChar);
			if (!text.StartsWith(root + Path.DirectorySeparatorChar, StringComparison.OrdinalIgnoreCase))
			{
				throw new InvalidDataException("An installed directory is outside BONELAB's Mods directory.");
			}
			return text;
		}

		private static async Task WriteAtomicAsync(string path, JsonNode value, CancellationToken cancellationToken)
		{
			string temp = path + ".wristhub.tmp";
			try
			{
				await File.WriteAllTextAsync(temp, value.ToJsonString(JsonOptions), cancellationToken).ConfigureAwait(continueOnCapturedContext: false);
				File.Move(temp, path, overwrite: true);
			}
			finally
			{
				if (File.Exists(temp))
				{
					File.Delete(temp);
				}
			}
		}
	}
	public static class PalletManifestLocator
	{
		public static bool IsManifestFileName(string? fileName)
		{
			if (string.IsNullOrWhiteSpace(fileName))
			{
				return false;
			}
			if (!fileName.Equals("pallet.json", StringComparison.OrdinalIgnoreCase))
			{
				return fileName.EndsWith(".pallet.json", StringComparison.OrdinalIgnoreCase);
			}
			return true;
		}

		public static IReadOnlyList<string> FindAll(string directory, SearchOption searchOption)
		{
			if (!Directory.Exists(directory))
			{
				return Array.Empty<string>();
			}
			return (from path in Directory.EnumerateFiles(directory, "*.json", searchOption)
				where IsManifestFileName(Path.GetFileName(path))
				select path).Distinct<string>(StringComparer.OrdinalIgnoreCase).ToArray();
		}

		public static bool HasManifest(string directory)
		{
			return FindAll(directory, SearchOption.TopDirectoryOnly).Count > 0;
		}
	}
	public sealed class SafeModInstaller
	{
		private static readonly HashSet<string> BlockedExtensions = new HashSet<string>(StringComparer.OrdinalIgnoreCase)
		{
			".exe", ".dll", ".com", ".bat", ".cmd", ".ps1", ".vbs", ".js", ".jar", ".sh",
			".so", ".dylib"
		};

		private readonly BoneHubOptions _options;

		public SafeModInstaller(BoneHubOptions options)
		{
			_options = options;
		}

		public async Task<IReadOnlyList<string>> InstallAsync(string zipPath, long modId, CancellationToken cancellationToken = default(CancellationToken))
		{
			string modsRoot = EnsureDirectory(_options.ModsDirectory);
			string path = $"{modId}-{Guid.NewGuid():N}";
			string? directoryName = Path.GetDirectoryName(modsRoot.TrimEnd(Path.DirectorySeparatorChar));
			string text = Path.Combine(directoryName, ".bonehub-staging");
			string text2 = Path.Combine(directoryName, ".bonehub-backups");
			RecoverInterruptedTransactions(modsRoot, text2);
			DeleteStaleStaging(text);
			string stagingRoot = Path.Combine(text, path);
			string backupRoot = Path.Combine(text2, path);
			Directory.CreateDirectory(stagingRoot);
			try
			{
				await ExtractValidatedAsync(zipPath, stagingRoot, cancellationToken).ConfigureAwait(continueOnCapturedContext: false);
				List<string> list = FindPackageRoots(stagingRoot);
				if (list.Count == 0)
				{
					throw new InvalidDataException("The archive has no pallet manifest (pallet.json or *.pallet.json) and is not a BONELAB content mod.");
				}
				Directory.CreateDirectory(backupRoot);
				List<string> list2 = new List<string>();
				List<(string, string)> list3 = new List<(string, string)>();
				try
				{
					foreach (string item in list)
					{
						cancellationToken.ThrowIfCancellationRequested();
						string fileName = Path.GetFileName(item.TrimEnd(Path.DirectorySeparatorChar));
						ValidateFolderName(fileName);
						string text3 = Path.Combine(modsRoot, fileName);
						string text4 = null;
						if (Directory.Exists(text3))
						{
							text4 = Path.Combine(backupRoot, fileName);
							Directory.Move(text3, text4);
						}
						try
						{
							Directory.Move(item, text3);
							list3.Add((text3, text4));
						}
						catch
						{
							if (text4 != null && Directory.Exists(text4) && !Directory.Exists(text3))
							{
								Directory.Move(text4, text3);
							}
							throw;
						}
						list2.Add(text3);
					}
					return list2;
				}
				catch
				{
					for (int num = list3.Count - 1; num >= 0; num--)
					{
						(string, string) tuple = list3[num];
						if (Directory.Exists(tuple.Item1))
						{
							Directory.Delete(tuple.Item1, recursive: true);
						}
						if (tuple.Item2 != null && Directory.Exists(tuple.Item2))
						{
							Directory.Move(tuple.Item2, tuple.Item1);
						}
					}
					throw;
				}
			}
			finally
			{
				TryDeleteDirectory(stagingRoot);
				TryDeleteDirectory(backupRoot);
			}
		}

		private async Task ExtractValidatedAsync(string zipPath, string destination, CancellationToken cancellationToken)
		{
			await using FileStream zipStream = File.OpenRead(zipPath);
			using ZipArchive archive = new ZipArchive(zipStream, ZipArchiveMode.Read, leaveOpen: false);
			long extractedBytes = 0L;
			string destinationPrefix = Path.GetFullPath(destination) + Path.DirectorySeparatorChar;
			foreach (ZipArchiveEntry entry in archive.Entries)
			{
				cancellationToken.ThrowIfCancellationRequested();
				if (IsSymbolicLink(entry))
				{
					throw new InvalidDataException("Archive contains a symbolic link: " + entry.FullName);
				}
				string fullPath = Path.GetFullPath(Path.Combine(destination, entry.FullName));
				if (!fullPath.StartsWith(destinationPrefix, StringComparison.OrdinalIgnoreCase))
				{
					throw new InvalidDataException("Archive path escapes the install directory: " + entry.FullName);
				}
				if (string.IsNullOrEmpty(entry.Name))
				{
					Directory.CreateDirectory(fullPath);
					continue;
				}
				if (BlockedExtensions.Contains(Path.GetExtension(entry.Name)))
				{
					throw new InvalidDataException("Archive contains blocked executable content: " + entry.FullName);
				}
				extractedBytes = checked(extractedBytes + entry.Length);
				if (extractedBytes > _options.MaximumExtractedBytes)
				{
					throw new InvalidDataException("Archive exceeds the configured extracted-size limit.");
				}
				Directory.CreateDirectory(Path.GetDirectoryName(fullPath));
				await using Stream input = entry.Open();
				await using FileStream output = new FileStream(fullPath, FileMode.CreateNew, FileAccess.Write, FileShare.None);
				byte[] buffer = new byte[131072];
				long writtenForEntry = 0L;
				while (true)
				{
					int num = await input.ReadAsync(buffer.AsMemory(0, buffer.Length), cancellationToken).ConfigureAwait(continueOnCapturedContext: false);
					if (num != 0)
					{
						writtenForEntry = checked(writtenForEntry + num);
						if (extractedBytes - entry.Length + writtenForEntry > _options.MaximumExtractedBytes)
						{
							throw new InvalidDataException("Archive exceeds the configured extracted-size limit.");
						}
						await output.WriteAsync(buffer.AsMemory(0, num), cancellationToken).ConfigureAwait(continueOnCapturedContext: false);
						continue;
					}
					break;
				}
			}
		}

		private static List<string> FindPackageRoots(string stagingRoot)
		{
			List<string> roots = (from path in PalletManifestLocator.FindAll(stagingRoot, SearchOption.AllDirectories)
				select Path.GetDirectoryName(path) into path
				where !string.Equals(path, stagingRoot, StringComparison.OrdinalIgnoreCase)
				select path).Distinct<string>(StringComparer.OrdinalIgnoreCase).ToList();
			return roots.Where((string candidate) => !roots.Any((string other) => !string.Equals(candidate, other, StringComparison.OrdinalIgnoreCase) && candidate.StartsWith(other + Path.DirectorySeparatorChar, StringComparison.OrdinalIgnoreCase))).ToList();
		}

		private static bool IsSymbolicLink(ZipArchiveEntry entry)
		{
			return ((entry.ExternalAttributes >> 16) & 0xF000) == 40960;
		}

		private static string EnsureDirectory(string path)
		{
			string fullPath = Path.GetFullPath(path);
			Directory.CreateDirectory(fullPath);
			return fullPath;
		}

		private static void ValidateFolderName(string name)
		{
			bool flag = string.IsNullOrWhiteSpace(name);
			if (!flag)
			{
				bool flag2 = ((name == "

Mods\WristHub.dll

Decompiled a day ago
using System;
using System.Collections;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
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.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 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.Forklift.Model;
using Il2CppSLZ.Marrow.SaveData;
using Il2CppSLZ.Marrow.Warehouse;
using Il2CppSLZ.VRMK;
using Il2CppSystem;
using Il2CppSystem.Collections.Generic;
using Il2CppTMPro;
using MelonLoader;
using MelonLoader.Utils;
using Microsoft.CodeAnalysis;
using UnityEngine;
using UnityEngine.Rendering;
using UnityEngine.UI;

[assembly: CompilationRelaxations(8)]
[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
[assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)]
[assembly: MelonInfo(typeof(BoneHubMod), "WristHub", "2.0.1", "WristHub contributors", null)]
[assembly: MelonGame("Stress Level Zero", "BONELAB")]
[assembly: TargetFramework(".NETCoreApp,Version=v6.0", FrameworkDisplayName = ".NET 6.0")]
[assembly: AssemblyCompany("WristHub")]
[assembly: AssemblyConfiguration("Release")]
[assembly: AssemblyFileVersion("2.0.1.0")]
[assembly: AssemblyInformationalVersion("2.0.1")]
[assembly: AssemblyProduct("WristHub")]
[assembly: AssemblyTitle("WristHub")]
[assembly: AssemblyVersion("2.0.1.0")]
[module: RefSafetyRules(11)]
namespace Microsoft.CodeAnalysis
{
	[CompilerGenerated]
	[Embedded]
	internal sealed class EmbeddedAttribute : Attribute
	{
	}
}
namespace System.Runtime.CompilerServices
{
	[CompilerGenerated]
	[Embedded]
	[AttributeUsage(AttributeTargets.Class | AttributeTargets.Property | AttributeTargets.Field | AttributeTargets.Event | AttributeTargets.Parameter | AttributeTargets.ReturnValue | AttributeTargets.GenericParameter, AllowMultiple = false, Inherited = false)]
	internal sealed class NullableAttribute : Attribute
	{
		public readonly byte[] NullableFlags;

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

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

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

		public RefSafetyRulesAttribute(int P_0)
		{
			Version = P_0;
		}
	}
}
namespace BoneHub.Mod
{
	internal sealed class BoneHubAvatarBrowser : IDisposable
	{
		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; init; }

			public Vector3 Half { get; init; }

			public Action Action { 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)
			{
				//IL_0033: Unknown result type (might be due to invalid IL or missing references)
				//IL_0034: Unknown result type (might be due to invalid IL or missing references)
				//IL_003a: Unknown result type (might be due to invalid IL or missing references)
				//IL_003f: Unknown result type (might be due to invalid IL or missing references)
				//IL_0044: Unknown result type (might be due to invalid IL or missing references)
				//IL_0045: Unknown result type (might be due to invalid IL or missing references)
				//IL_0051: Unknown result type (might be due to invalid IL or missing references)
				//IL_005d: Unknown result type (might be due to invalid IL or missing references)
				//IL_0069: Unknown result type (might be due to invalid IL or missing references)
				//IL_0075: Unknown result type (might be due to invalid IL or missing references)
				//IL_0081: 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;
					return Mathf.Abs(val.x) <= Half.x && Mathf.Abs(val.y) <= Half.y && Mathf.Abs(val.z) <= Half.z;
				}
				catch
				{
					return false;
				}
			}
		}

		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 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 Cyan = new Color(0.2f, 0.9f, 1f, 1f);

		private static readonly Color DimCyan = new Color(0.055f, 0.25f, 0.32f, 1f);

		private static readonly Color Green = new Color(0.32f, 0.9f, 0.62f, 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 readonly Instance _log;

		private readonly LobbyAvatarSharingBridge _sharing;

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

		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<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 HashSet<long> _startingDownloads = new HashSet<long>();

		private readonly BoneHubFingertipProbeProvider _fingertips = new BoneHubFingertipProbeProvider();

		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 IReadOnlyList<AvatarEntry> _entries = Array.Empty<AvatarEntry>();

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

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

		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? _keyboardPage;

		private GameObject? _deletePage;

		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? _startingViewText;

		private TMP_Text? _scopeText;

		private TMP_Text? _deleteTitle;

		private TMP_Text? _deleteDetails;

		private TMP_Text? _keyboardText;

		private TMP_Text? _keyboardShiftText;

		private TMP_Text? _searchText;

		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 TouchButton? _actionButton;

		private GameObject? _previewShell;

		private GameObject? _previewFallback;

		private GameObject? _previewModel;

		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 _previewReturning;

		private float _returnStarted;

		private Vector3 _returnFrom;

		private Quaternion _returnRotation;

		private int _previewGeneration;

		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;

		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 string Query => _query;

		public event Action? Closed;

		public BoneHubAvatarBrowser(BoneHubService service, BoneHubRuntime runtime, BoneHubThumbnailCache thumbnails, ConcurrentQueue<Action> mainThread, BoneHubPreferences preferences, bool constrainedRendering, Instance log, LobbyAvatarSharingBridge? sharing = null)
		{
			//IL_0095: Unknown result type (might be due to invalid IL or missing references)
			//IL_009f: Expected O, but got Unknown
			//IL_00a0: Unknown result type (might be due to invalid IL or missing references)
			//IL_00aa: Expected O, but got Unknown
			//IL_00ab: Unknown result type (might be due to invalid IL or missing references)
			//IL_00b5: Expected O, but got Unknown
			//IL_00b6: Unknown result type (might be due to invalid IL or missing references)
			//IL_00c0: Expected O, but got Unknown
			//IL_00c1: Unknown result type (might be due to invalid IL or missing references)
			//IL_00cb: Expected O, but got Unknown
			//IL_00cc: Unknown result type (might be due to invalid IL or missing references)
			//IL_00d6: Expected O, but got Unknown
			//IL_00e0: Unknown result type (might be due to invalid IL or missing references)
			//IL_00ea: Expected O, but got Unknown
			//IL_00eb: Unknown result type (might be due to invalid IL or missing references)
			//IL_00f5: Expected O, but got Unknown
			//IL_0159: 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_019f: Unknown result type (might be due to invalid IL or missing references)
			_service = service;
			_runtime = runtime;
			_thumbnails = thumbnails;
			_mainThread = mainThread;
			_preferences = preferences;
			_constrainedRendering = constrainedRendering;
			_log = log;
			_sharing = sharing ?? new LobbyAvatarSharingBridge(log);
			_projectionTickAction = TickProjectionStage;
			_projectionRecoveryAction = RecoverProjectionStage;
			_touchTickAction = TickTouchStage;
			_touchRecoveryAction = ResetTouchButtons;
			_installationFocusTickAction = UpdateInstalledPackFocus;
			_previewTickAction = ProcessPendingPreview;
			_previewRecoveryAction = RecoverPreviewStage;
			_downloadTickAction = UpdateDownloadProgress;
			_sharingTickAction = TickSharingRegistration;
		}

		public void OpenLauncher()
		{
			//IL_0078: Unknown result type (might be due to invalid IL or missing references)
			EnsureBuilt();
			if (!((Object)(object)_root == (Object)null) && !((Object)(object)_launcher == (Object)null) && !((Object)(object)_browser == (Object)null))
			{
				FollowWrist(immediate: true);
				_root.SetActive(true);
				_launcher.SetActive(true);
				if ((Object)(object)_hub != (Object)null)
				{
					_hub.SetActive(false);
				}
				_browser.SetActive(false);
				_viewState = (AvatarBrowserViewState)0;
				_promptDismissal.Reset();
				RefreshEntries();
				RequireInputRearm();
			}
		}

		public void Open()
		{
			//IL_0080: Unknown result type (might be due to invalid IL or missing references)
			//IL_008b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0090: Unknown result type (might be due to invalid IL or missing references)
			//IL_0095: Unknown result type (might be due to invalid IL or missing references)
			//IL_009c: Unknown result type (might be due to invalid IL or missing references)
			//IL_00a1: Unknown result type (might be due to invalid IL or missing references)
			//IL_00a8: Unknown result type (might be due to invalid IL or missing references)
			OpenLauncher();
			if (!((Object)(object)_launcher == (Object)null) && !((Object)(object)_browser == (Object)null))
			{
				_launcher.SetActive(false);
				if ((Object)(object)_hub != (Object)null)
				{
					_hub.SetActive(false);
				}
				_browser.SetActive(true);
				StartHologramProjection();
				_query = string.Empty;
				_downloadsOnly = false;
				_selectedPackId = null;
				_scope = AvatarBrowseStartup.Resolve(_preferences.AvatarBrowserStartupMode, _preferences.LastAvatarBrowseScope);
				_scopeBeforeSearch = _scope;
				_viewState = (AvatarBrowserViewState)2;
				RememberScope();
				RefreshEntries();
				ShowScopePage();
			}
		}

		public void OpenSearch()
		{
			//IL_006e: 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_007e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0083: Unknown result type (might be due to invalid IL or missing references)
			//IL_008a: Unknown result type (might be due to invalid IL or missing references)
			//IL_008f: Unknown result type (might be due to invalid IL or missing references)
			OpenLauncher();
			if (!((Object)(object)_launcher == (Object)null) && !((Object)(object)_browser == (Object)null))
			{
				_launcher.SetActive(false);
				if ((Object)(object)_hub != (Object)null)
				{
					_hub.SetActive(false);
				}
				_browser.SetActive(true);
				StartHologramProjection();
				_selectedPackId = null;
				_scope = AvatarBrowseStartup.Resolve(_preferences.AvatarBrowserStartupMode, _preferences.LastAvatarBrowseScope);
				_scopeBeforeSearch = _scope;
				RefreshEntries();
				OpenKeyboard();
			}
		}

		public void Close()
		{
			if (_previewHeld)
			{
				EndPreviewDrag(equipIfAtChest: false);
			}
			ResetTouchButtons();
			ResetTrashConfirmation();
			CancelPendingSharingRegistration();
			if ((Object)(object)_root != (Object)null)
			{
				_root.SetActive(false);
			}
			if ((Object)(object)_browser != (Object)null)
			{
				_browser.SetActive(false);
			}
			if ((Object)(object)_launcher != (Object)null)
			{
				_launcher.SetActive(false);
			}
			if ((Object)(object)_hub != (Object)null)
			{
				_hub.SetActive(false);
			}
			if ((Object)(object)_previewShell != (Object)null)
			{
				_previewShell.SetActive(false);
			}
			_hologramProjection?.Hide();
			_artworkGeneration.Next();
			_previewGeneration++;
			_requestedPreviewBarcode = string.Empty;
			_pendingPreviewAvatar = null;
			_promptDismissal.Reset();
			this.Closed?.Invoke();
		}

		public void SetQuery(string value)
		{
			//IL_00c5: Unknown result type (might be due to invalid IL or missing references)
			//IL_012d: Unknown result type (might be due to invalid IL or missing references)
			//IL_017d: Unknown result type (might be due to invalid IL or missing references)
			if ((Object)(object)_canvasGroup != (Object)null)
			{
				_canvasGroup.alpha = 1f;
			}
			if ((Object)(object)_root != (Object)null)
			{
				_root.SetActive(true);
			}
			if ((Object)(object)_launcher != (Object)null)
			{
				_launcher.SetActive(false);
			}
			if ((Object)(object)_hub != (Object)null)
			{
				_hub.SetActive(false);
			}
			if ((Object)(object)_browser != (Object)null)
			{
				_browser.SetActive(true);
			}
			_query = value?.Trim() ?? string.Empty;
			_downloadsOnly = false;
			_onlineEntries = Array.Empty<AvatarEntry>();
			_viewState = (AvatarBrowserViewState)((_query.Length == 0) ? 2 : 6);
			_log.Msg($"Avatar search submitted ({_query.Length} characters). Query text is not logged.");
			RefreshEntries();
			if (_query.Length == 0)
			{
				ShowScopePage();
			}
			else
			{
				_scope = (AvatarBrowseScope)0;
				_selectedPackId = null;
				RefreshEntries();
				ShowDetailPage();
			}
			int generation = ++_searchGeneration;
			if (_query.Length != 0 && _service.ModIoReady)
			{
				SetStatus("Searching BONELAB mod.io…", Cyan);
				SearchOnlineAsync(_query, generation);
			}
		}

		public void Refresh()
		{
			RefreshEntries();
			RefreshVisiblePage();
		}

		public void Select(string identity)
		{
			AvatarEntry val = ((IEnumerable<AvatarEntry>)_entries).FirstOrDefault((Func<AvatarEntry, bool>)((AvatarEntry item) => string.Equals(item.Identity, identity, StringComparison.OrdinalIgnoreCase)));
			if (!(val == (AvatarEntry)null))
			{
				_selectedIdentity = val.Identity;
				_cursor.Keep(_entries.Count, IndexOf(val.Identity));
				ShowDetailPage();
				RefreshSelection(val);
			}
		}

		public void OnDownloadChanged(DownloadJob job)
		{
			//IL_0018: Unknown result type (might be due to invalid IL or missing references)
			//IL_001d: Unknown result type (might be due to invalid IL or missing references)
			//IL_001e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0020: Unknown result type (might be due to invalid IL or missing references)
			//IL_0022: Invalid comparison between Unknown and I4
			//IL_0045: Unknown result type (might be due to invalid IL or missing references)
			//IL_004b: Invalid comparison between Unknown and I4
			//IL_00bc: 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_00c2: Unknown result type (might be due to invalid IL or missing references)
			//IL_00c4: Unknown result type (might be due to invalid IL or missing references)
			//IL_00c6: Invalid comparison between Unknown and I4
			//IL_013c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0141: Unknown result type (might be due to invalid IL or missing references)
			//IL_0142: Unknown result type (might be due to invalid IL or missing references)
			//IL_0144: Unknown result type (might be due to invalid IL or missing references)
			//IL_0156: Expected I4, but got Unknown
			//IL_0160: Unknown result type (might be due to invalid IL or missing references)
			//IL_015a: Unknown result type (might be due to invalid IL or missing references)
			//IL_0165: Unknown result type (might be due to invalid IL or missing references)
			//IL_016d: Unknown result type (might be due to invalid IL or missing references)
			//IL_016f: Unknown result type (might be due to invalid IL or missing references)
			//IL_016a: Unknown result type (might be due to invalid IL or missing references)
			_jobs[job.Mod.Id] = job;
			DownloadState state = job.State;
			if (state - 5 <= 2)
			{
				_startingDownloads.Remove(job.Mod.Id);
			}
			if ((int)job.State == 5 && _installedFocus.Active && _installedFocus.ModId == job.Mod.Id)
			{
				InstalledPackFocus installedFocus = _installedFocus;
				long id = job.Mod.Id;
				double num = Time.unscaledTime;
				InstalledModRecord result = job.Result;
				installedFocus.MarkDownloaded(id, num, (IEnumerable<string>)((result != null) ? result.InstalledDirectories : null), 12.0);
				_log.Msg("Downloaded root pack completed; waiting for its runtime avatar crates.");
			}
			else
			{
				state = job.State;
				bool flag = state - 6 <= 1;
				if (flag && _installedFocus.ModId == job.Mod.Id)
				{
					_installedFocus.Clear();
				}
			}
			RefreshEntries();
			AvatarEntry? obj = Selected();
			if (((obj != null) ? new long?(obj.ModId) : ((long?)null)) == job.Mod.Id)
			{
				state = job.State;
				_viewState = (AvatarBrowserViewState)((state - 5) switch
				{
					1 => 10, 
					0 => 9, 
					2 => 7, 
					_ => 8, 
				});
				RefreshVisiblePage();
			}
		}

		public void OnRuntimeStatusChanged(string status)
		{
			//IL_006d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0066: Unknown result type (might be due to invalid IL or missing references)
			//IL_005f: Unknown result type (might be due to invalid IL or missing references)
			GameObject? browser = _browser;
			if (browser != null && browser.activeSelf && !string.IsNullOrWhiteSpace(status))
			{
				bool flag = status.Contains("failed", StringComparison.OrdinalIgnoreCase) || status.Contains("could not", StringComparison.OrdinalIgnoreCase) || status.Contains("rejected", StringComparison.OrdinalIgnoreCase);
				SetStatus(status, flag ? Red : (status.Contains("sharing", StringComparison.OrdinalIgnoreCase) ? Cyan : Green));
			}
		}

		public void OnAvatarsChanged(long modId, IReadOnlyList<BoneHubAvatar> avatars)
		{
			//IL_0062: Unknown result type (might be due to invalid IL or missing references)
			//IL_01b6: Unknown result type (might be due to invalid IL or missing references)
			_previewMetrics.Clear();
			_previewRoutes.Clear();
			_rigValidations.Clear();
			if (avatars.Any((BoneHubAvatar avatar) => string.Equals(avatar.Barcode, _loadedPreviewBarcode, StringComparison.OrdinalIgnoreCase)))
			{
				_loadedPreviewBarcode = string.Empty;
				_requestedPreviewBarcode = string.Empty;
				_pendingPreviewAvatar = null;
				_previewReady = false;
				_previewInteractionReady = false;
				_previewVisualSource = (AvatarPreviewVisualSource)0;
			}
			_fingertips.Invalidate();
			_projectionAnchors.Invalidate();
			_hologramProjection?.BeginTrackingGrace();
			RefreshEntries();
			if (_installedFocus.TryBind(modId, avatars.Select((BoneHubAvatar avatar) => avatar.Barcode)))
			{
				_selectedPackId = modId;
				_query = string.Empty;
				_onlineEntries = Array.Empty<AvatarEntry>();
				_downloadsOnly = false;
				IReadOnlyList<BoneHubAvatar> avatars2 = _runtime.GetAvatars(modId);
				_entries = ((avatars2.Count > 0) ? avatars2 : avatars).Select((BoneHubAvatar avatar) => avatar.ToEntry()).OrderBy<AvatarEntry, string>((AvatarEntry entry) => entry.AvatarName, StringComparer.OrdinalIgnoreCase).ToArray();
				_selectedIdentity = _entries.First((AvatarEntry entry) => string.Equals(entry.Barcode, _installedFocus.SelectedBarcode, StringComparison.OrdinalIgnoreCase)).Identity;
				_cursor.Keep(_entries.Count, IndexOf(_selectedIdentity));
				ShowDetailPage();
				SetStatus("Downloaded - ready to equip", Green);
				_log.Msg($"Downloaded avatar pack bound to {avatars.Count} runtime crate{((avatars.Count == 1) ? string.Empty : "s")}; Equip is ready.");
				return;
			}
			if (avatars.Count > 0)
			{
				AvatarEntry? obj = Selected();
				if (obj != null && obj.ModId == modId)
				{
					AvatarEntry? obj2 = Selected();
					if (string.IsNullOrWhiteSpace((obj2 != null) ? obj2.Barcode : null))
					{
						_selectedIdentity = avatars[0].ToEntry().Identity;
						RefreshEntries();
					}
				}
			}
			RefreshVisiblePage();
		}

		public void OnSceneChanged()
		{
			//IL_0097: Unknown result type (might be due to invalid IL or missing references)
			_fingertips.Invalidate();
			_projectionAnchors.Invalidate();
			_hologramProjection?.BeginTrackingGrace();
			ResetTouchButtons();
			if ((Object)(object)_root == (Object)null)
			{
				return;
			}
			bool reportRecovery = PreviewShellRecovery.RequiresRebuild((Object)(object)_previewShell != (Object)null, (Object)(object)_previewFallback != (Object)null);
			if (EnsurePreviewShell(reportRecovery))
			{
				ClearPreviewModel();
				_loadedPreviewBarcode = string.Empty;
				_requestedPreviewBarcode = string.Empty;
				_pendingPreviewAvatar = null;
				_previewReady = false;
				_previewInteractionReady = false;
				_previewVisualSource = (AvatarPreviewVisualSource)0;
				GameObject? browser = _browser;
				if (browser != null && browser.activeSelf)
				{
					RefreshFocusedResult();
				}
			}
		}

		public void Tick()
		{
			_hologramProjection?.Tick(_preferences.ReducedMotion);
			if (IsLocalPlayerDead())
			{
				if (!_wasPlayerDead && IsLauncherOpen)
				{
					_log.Msg("WristHub closed because the local player died.");
					Close();
				}
				_wasPlayerDead = true;
				return;
			}
			_wasPlayerDead = false;
			if (IsLauncherOpen && !((Object)(object)Player.Head == (Object)null))
			{
				RunGuarded("projection", _projectionTickAction, _projectionRecoveryAction);
				RunGuarded("touch", _touchTickAction, _touchRecoveryAction);
				RunGuarded("installation-focus", _installationFocusTickAction);
				RunGuarded("preview", _previewTickAction, _previewRecoveryAction);
				RunGuarded("download", _downloadTickAction);
				RunGuarded("sharing", _sharingTickAction, CancelPendingSharingRegistration);
			}
		}

		private void TickProjectionStage()
		{
			FollowWrist();
			UpdateHologramProjection();
		}

		private void RecoverProjectionStage()
		{
			_projectionAnchors.Invalidate();
			_hologramProjection?.BeginTrackingGrace();
		}

		private void TickTouchStage()
		{
			UpdatePushPromptVisibility();
			PollButtons();
		}

		private void RecoverPreviewStage()
		{
			EndPreviewDrag(equipIfAtChest: false);
		}

		public void LateTick()
		{
			if (!IsLauncherOpen || (Object)(object)Player.Head == (Object)null)
			{
				return;
			}
			try
			{
				UpdatePreview();
			}
			catch (Exception ex)
			{
				string text = "late-drag|" + ex.GetType().FullName;
				if (_runtimeErrorThrottle.ShouldLog(text, (double)Environment.TickCount64 / 1000.0, 5.0))
				{
					_log.Error($"WristHub preview drag recovered: {ex}");
				}
				EndPreviewDrag(equipIfAtChest: false);
			}
		}

		private void RunGuarded(string subsystem, Action action, Action? recover = null)
		{
			try
			{
				action();
			}
			catch (Exception ex)
			{
				string text = ex.StackTrace?.Split('\n').FirstOrDefault()?.Trim() ?? "no-stack";
				string text2 = subsystem + "|" + ex.GetType().FullName + "|" + text;
				if (_runtimeErrorThrottle.ShouldLog(text2, (double)Environment.TickCount64 / 1000.0, 5.0))
				{
					_log.Error($"WristHub {subsystem} update recovered (repeats suppressed): {ex}");
				}
				try
				{
					recover?.Invoke();
				}
				catch
				{
				}
			}
		}

		public void Dispose()
		{
			if (!_disposed)
			{
				_disposed = true;
				_searchGeneration++;
				ResetUiReferences(destroyOwnedObjects: true);
			}
		}

		private void EnsureBuilt()
		{
			if (!((Object)(object)_root != (Object)null))
			{
				ResetUiReferences(destroyOwnedObjects: true);
				BuildTablet();
			}
		}

		private void ResetUiReferences(bool destroyOwnedObjects)
		{
			foreach (PackRow packRow in _packRows)
			{
				packRow.Generation++;
			}
			ResetTouchButtons();
			_buttons.Clear();
			_packRows.Clear();
			_touchCaptures.Clear();
			_activeProbeIds.Clear();
			_staleCaptureIds.Clear();
			_artworkGeneration.Next();
			_previewGeneration++;
			if (destroyOwnedObjects)
			{
				try
				{
					if ((Object)(object)_previewModel != (Object)null)
					{
						Object.Destroy((Object)(object)_previewModel);
					}
				}
				catch
				{
				}
				foreach (Mesh ownedPreviewMesh in _ownedPreviewMeshes)
				{
					try
					{
						if ((Object)(object)ownedPreviewMesh != (Object)null)
						{
							Object.Destroy((Object)(object)ownedPreviewMesh);
						}
					}
					catch
					{
					}
				}
				try
				{
					if ((Object)(object)_previewShell != (Object)null)
					{
						Object.Destroy((Object)(object)_previewShell);
					}
				}
				catch
				{
				}
				try
				{
					if ((Object)(object)_root != (Object)null)
					{
						Object.Destroy((Object)(object)_root);
					}
				}
				catch
				{
				}
				try
				{
					if ((Object)(object)_placeholderTexture != (Object)null)
					{
						Object.Destroy((Object)(object)_placeholderTexture);
					}
				}
				catch
				{
				}
				try
				{
					if ((Object)(object)_roundedSprite != (Object)null)
					{
						Object.Destroy((Object)(object)_roundedSprite);
					}
				}
				catch
				{
				}
				try
				{
					if ((Object)(object)_roundedTexture != (Object)null)
					{
						Object.Destroy((Object)(object)_roundedTexture);
					}
				}
				catch
				{
				}
				try
				{
					_hologramProjection?.Dispose();
				}
				catch
				{
				}
			}
			_ownedPreviewMeshes.Clear();
			_hologramProjection = null;
			_root = null;
			_launcher = null;
			_hub = null;
			_browser = null;
			_canvasRoot = null;
			_packPage = null;
			_detailPage = null;
			_optionsPage = null;
			_keyboardPage = null;
			_deletePage = null;
			_canvasGroup = null;
			_packTitle = null;
			_packStatus = null;
			_packPosition = null;
			_packSearchText = null;
			_sortText = null;
			_wristText = null;
			_scaleText = null;
			_motionText = null;
			_startingViewText = null;
			_scopeText = null;
			_deleteTitle = null;
			_deleteDetails = null;
			_keyboardText = null;
			_keyboardShiftText = null;
			_searchText = null;
			_positionText = null;
			_selectedName = null;
			_creatorText = null;
			_modText = null;
			_statusText = null;
			_heightText = null;
			_heightRuler = null;
			_progressRoot = null;
			_progressFill = null;
			_progressText = null;
			_actionLabel = null;
			_actionPanel = null;
			_trashButton = null;
			_centralArtwork = null;
			_placeholderTexture = null;
			_roundedTexture = null;
			_roundedSprite = null;
			_actionButton = null;
			_previewShell = null;
			_previewFallback = null;
			_previewModel = null;
			_previewAvatar = null;
			_pendingPreviewAvatar = null;
			_loadedPreviewBarcode = string.Empty;
			_requestedPreviewBarcode = string.Empty;
			_previewReady = false;
			_previewInteractionReady = false;
			_previewHeld = false;
			_dragHand = null;
			_previewReturning = false;
			CancelPendingSharingRegistration();
		}

		private void BuildTablet()
		{
			//IL_0034: Unknown result type (might be due to invalid IL or missing references)
			//IL_0043: Unknown result type (might be due to invalid IL or missing references)
			//IL_0063: Unknown result type (might be due to invalid IL or missing references)
			//IL_0088: Unknown result type (might be due to invalid IL or missing references)
			//IL_0092: Expected O, but got Unknown
			//IL_00a9: Unknown result type (might be due to invalid IL or missing references)
			//IL_00b3: Expected O, but got Unknown
			//IL_00e9: Unknown result type (might be due to invalid IL or missing references)
			//IL_0103: Unknown result type (might be due to invalid IL or missing references)
			//IL_0117: Unknown result type (might be due to invalid IL or missing references)
			//IL_011c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0164: Unknown result type (might be due to invalid IL or missing references)
			//IL_016e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0182: Unknown result type (might be due to invalid IL or missing references)
			//IL_0187: Unknown result type (might be due to invalid IL or missing references)
			//IL_01a9: Unknown result type (might be due to invalid IL or missing references)
			//IL_01b3: Expected O, but got Unknown
			//IL_01df: Unknown result type (might be due to invalid IL or missing references)
			//IL_01f3: Unknown result type (might be due to invalid IL or missing references)
			//IL_01f8: Unknown result type (might be due to invalid IL or missing references)
			//IL_0248: Unknown result type (might be due to invalid IL or missing references)
			//IL_0252: Unknown result type (might be due to invalid IL or missing references)
			//IL_0289: Unknown result type (might be due to invalid IL or missing references)
			//IL_029d: Unknown result type (might be due to invalid IL or missing references)
			//IL_02a2: Unknown result type (might be due to invalid IL or missing references)
			//IL_02e4: Unknown result type (might be due to invalid IL or missing references)
			//IL_02f8: Unknown result type (might be due to invalid IL or missing references)
			//IL_02fd: Unknown result type (might be due to invalid IL or missing references)
			//IL_033f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0353: Unknown result type (might be due to invalid IL or missing references)
			//IL_0358: Unknown result type (might be due to invalid IL or missing references)
			//IL_039a: Unknown result type (might be due to invalid IL or missing references)
			//IL_03ae: Unknown result type (might be due to invalid IL or missing references)
			//IL_03c7: Unknown result type (might be due to invalid IL or missing references)
			//IL_0410: Unknown result type (might be due to invalid IL or missing references)
			//IL_0444: Unknown result type (might be due to invalid IL or missing references)
			//IL_044e: Expected O, but got Unknown
			//IL_0489: Unknown result type (might be due to invalid IL or missing references)
			//IL_049d: 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_04d2: Unknown result type (might be due to invalid IL or missing references)
			//IL_04e6: Unknown result type (might be due to invalid IL or missing references)
			//IL_04eb: Unknown result type (might be due to invalid IL or missing references)
			//IL_0542: Unknown result type (might be due to invalid IL or missing references)
			//IL_0556: Unknown result type (might be due to invalid IL or missing references)
			//IL_0577: Unknown result type (might be due to invalid IL or missing references)
			//IL_0590: Unknown result type (might be due to invalid IL or missing references)
			//IL_05a6: Unknown result type (might be due to invalid IL or missing references)
			//IL_05b0: Expected O, but got Unknown
			//IL_05f1: Unknown result type (might be due to invalid IL or missing references)
			//IL_05fb: Unknown result type (might be due to invalid IL or missing references)
			//IL_063b: Unknown result type (might be due to invalid IL or missing references)
			//IL_064f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0654: Unknown result type (might be due to invalid IL or missing references)
			//IL_069b: Unknown result type (might be due to invalid IL or missing references)
			//IL_06af: Unknown result type (might be due to invalid IL or missing references)
			//IL_06b4: Unknown result type (might be due to invalid IL or missing references)
			//IL_0707: Unknown result type (might be due to invalid IL or missing references)
			//IL_071b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0720: Unknown result type (might be due to invalid IL or missing references)
			//IL_0767: Unknown result type (might be due to invalid IL or missing references)
			//IL_077b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0780: Unknown result type (might be due to invalid IL or missing references)
			//IL_07c7: Unknown result type (might be due to invalid IL or missing references)
			//IL_07db: Unknown result type (might be due to invalid IL or missing references)
			//IL_07e0: Unknown result type (might be due to invalid IL or missing references)
			//IL_083f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0853: Unknown result type (might be due to invalid IL or missing references)
			//IL_0858: Unknown result type (might be due to invalid IL or missing references)
			//IL_0899: Unknown result type (might be due to invalid IL or missing references)
			//IL_08a3: Unknown result type (might be due to invalid IL or missing references)
			//IL_08e3: Unknown result type (might be due to invalid IL or missing references)
			//IL_08f7: Unknown result type (might be due to invalid IL or missing references)
			//IL_08fc: Unknown result type (might be due to invalid IL or missing references)
			//IL_093d: Unknown result type (might be due to invalid IL or missing references)
			//IL_095b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0976: Unknown result type (might be due to invalid IL or missing references)
			//IL_0980: Expected O, but got Unknown
			//IL_09c1: Unknown result type (might be due to invalid IL or missing references)
			//IL_09da: Unknown result type (might be due to invalid IL or missing references)
			//IL_0a10: Unknown result type (might be due to invalid IL or missing references)
			//IL_0a24: Unknown result type (might be due to invalid IL or missing references)
			//IL_0a29: Unknown result type (might be due to invalid IL or missing references)
			//IL_0a70: Unknown result type (might be due to invalid IL or missing references)
			//IL_0a84: Unknown result type (might be due to invalid IL or missing references)
			//IL_0a89: Unknown result type (might be due to invalid IL or missing references)
			//IL_0ade: Unknown result type (might be due to invalid IL or missing references)
			//IL_0af2: Unknown result type (might be due to invalid IL or missing references)
			//IL_0af7: Unknown result type (might be due to invalid IL or missing references)
			//IL_0b3e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0b52: Unknown result type (might be due to invalid IL or missing references)
			//IL_0b57: Unknown result type (might be due to invalid IL or missing references)
			//IL_0b9d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0ba2: Unknown result type (might be due to invalid IL or missing references)
			//IL_0ba7: Unknown result type (might be due to invalid IL or missing references)
			//IL_0bac: Unknown result type (might be due to invalid IL or missing references)
			//IL_0bb1: Unknown result type (might be due to invalid IL or missing references)
			//IL_0be9: Unknown result type (might be due to invalid IL or missing references)
			//IL_0bee: Unknown result type (might be due to invalid IL or missing references)
			//IL_0bf3: Unknown result type (might be due to invalid IL or missing references)
			//IL_0bf8: Unknown result type (might be due to invalid IL or missing references)
			//IL_0bfd: Unknown result type (might be due to invalid IL or missing references)
			//IL_0c35: Unknown result type (might be due to invalid IL or missing references)
			//IL_0c3a: Unknown result type (might be due to invalid IL or missing references)
			//IL_0c3f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0c44: Unknown result type (might be due to invalid IL or missing references)
			//IL_0c49: Unknown result type (might be due to invalid IL or missing references)
			//IL_0c76: Unknown result type (might be due to invalid IL or missing references)
			//IL_0c8f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0cbf: Unknown result type (might be due to invalid IL or missing references)
			//IL_0cca: Unknown result type (might be due to invalid IL or missing references)
			//IL_0cf6: Unknown result type (might be due to invalid IL or missing references)
			//IL_0d0f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0d3f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0d49: Unknown result type (might be due to invalid IL or missing references)
			//IL_0d83: Unknown result type (might be due to invalid IL or missing references)
			//IL_0d8d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0dc7: Unknown result type (might be due to invalid IL or missing references)
			//IL_0de5: Unknown result type (might be due to invalid IL or missing references)
			//IL_0e1f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0e29: Unknown result type (might be due to invalid IL or missing references)
			//IL_0e69: Unknown result type (might be due to invalid IL or missing references)
			//IL_0e73: Unknown result type (might be due to invalid IL or missing references)
			//IL_0ea5: Unknown result type (might be due to invalid IL or missing references)
			//IL_0eaa: Unknown result type (might be due to invalid IL or missing references)
			//IL_0eaf: Unknown result type (might be due to invalid IL or missing references)
			//IL_0eb4: Unknown result type (might be due to invalid IL or missing references)
			//IL_0eb9: Unknown result type (might be due to invalid IL or missing references)
			//IL_0f18: Unknown result type (might be due to invalid IL or missing references)
			//IL_0f1d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0f22: Unknown result type (might be due to invalid IL or missing references)
			//IL_0f27: Unknown result type (might be due to invalid IL or missing references)
			//IL_0f2c: Unknown result type (might be due to invalid IL or missing references)
			_roundedTexture = CreateRoundedTexture();
			_roundedSprite = Sprite.Create(_roundedTexture, new Rect(0f, 0f, (float)((Texture)_roundedTexture).width, (float)((Texture)_roundedTexture).height), new Vector2(0.5f, 0.5f), 100f, 0u, (SpriteMeshType)0, new Vector4(16f, 16f, 16f, 16f));
			((Object)_roundedSprite).name = "WristHub Rounded UI Sprite";
			_root = new GameObject("[WristHub] Wrist Avatar Browser");
			Object.DontDestroyOnLoad((Object)(object)_root);
			BuildHologramProjection();
			_launcher = new GameObject("Push Prompt");
			_launcher.transform.SetParent(_root.transform, false);
			_launcher.transform.localPosition = new Vector3(0f, -0.085f, 0f);
			CreatePanel(_launcher.transform, "Push Prompt Glass", Vector3.zero, new Vector3(0.15f, 0.07f, 0.006f), Glass, (PrimitiveType)3);
			GameObject val = CreateCanvas(_launcher.transform, "Push Prompt UI", 150f, 70f, 0.007f);
			CreateButton(_launcher.transform, val.transform, "PUSH ME", Vector3.forward * 0.007f, new Vector3(0.13f, 0.05f, 0.014f), Cyan, ShowHub, 24f);
			_hub = new GameObject("WristHub Module Hub");
			_hub.transform.SetParent(_root.transform, false);
			CreatePanel(_hub.transform, "Hub Glass", Vector3.zero, new Vector3(0.25f, 0.164f, 0.008f), Glass, (PrimitiveType)3);
			GameObject val2 = CreateCanvas(_hub.transform, "Hub UI", 250f, 164f, 0.009f);
			CreateText(val2.transform, "WRISTHUB", new TabletRect(-62f, 64f, 100f, 22f), 19f, Color.white, (TextAlignmentOptions)4097, (FontStyles)1);
			CreateButton(_hub.transform, val2.transform, "X", new Vector3(0.108f, 0.064f, 0.008f), new Vector3(0.026f, 0.026f, 0.014f), Red, Close, 19f);
			CreateButton(_hub.transform, val2.transform, "AVATARS", new Vector3(0f, 0.027f, 0.008f), new Vector3(0.214f, 0.038f, 0.014f), Cyan, Open, 19f);
			CreateButton(_hub.transform, val2.transform, "SEARCH AVATARS", new Vector3(0f, -0.02f, 0.008f), new Vector3(0.214f, 0.038f, 0.014f), DimCyan, OpenSearch, 18f);
			TouchButton touchButton = CreateButton(_hub.transform, val2.transform, "MORE COMING SOON", new Vector3(0f, -0.066f, 0.008f), new Vector3(0.214f, 0.032f, 0.014f), new Color(0.06f, 0.1f, 0.12f, 1f), delegate
			{
			}, 14f);
			((Graphic)touchButton.Label).color = new Color(0.45f, 0.55f, 0.6f, 1f);
			_buttons.Remove(touchButton);
			_hub.SetActive(false);
			_placeholderTexture = CreatePlaceholderTexture();
			_browser = new GameObject("Avatar Browser Tablet 34x21cm");
			_browser.transform.SetParent(_root.transform, false);
			CreatePanel(_browser.transform, "Tablet Shadow", new Vector3(0.006f, -0.007f, -0.006f), new Vector3(0.35f, 0.22f, 0.009f), new Color(0f, 0f, 0f, 0.7f), (PrimitiveType)3);
			CreatePanel(_browser.transform, "Tablet Bezel", Vector3.zero, new Vector3(0.34f, 0.21f, 0.01f), Glass, (PrimitiveType)3);
			_canvasRoot = CreateCanvas(_browser.transform, "WristHub Tablet Canvas", 340f, 210f, 0.006f);
			_canvasGroup = _canvasRoot.AddComponent<CanvasGroup>();
			((Graphic)CreateRawImage(_canvasRoot.transform, "WristHub Background", AvatarTabletLayout.Tablet, BoneHubTheme.Background ?? _placeholderTexture, Color.white)).raycastTarget = false;
			((Graphic)CreateImage(_canvasRoot.transform, "Calm Dark Overlay", AvatarTabletLayout.Tablet, new Color(0.005f, 0.024f, 0.038f, 0.72f))).raycastTarget = false;
			_packPage = new GameObject("Mod Pack Library Page");
			_packPage.transform.SetParent(_canvasRoot.transform, false);
			_packTitle = CreateText(_packPage.transform, "GROUPS", new TabletRect(92f, 83f, 64f, 25f), 15f, Color.white, (TextAlignmentOptions)514, (FontStyles)1);
			CreateButton(_browser.transform, _packPage.transform, "BACK", new Vector3(-0.145f, 0.083f, 0.007f), new Vector3(0.042f, 0.028f, 0.014f), DimCyan, BackFromPackPage, 9f);
			TouchButton touchButton2 = CreateButton(_browser.transform, _packPage.transform, "SEARCH", new Vector3(-0.09f, 0.083f, 0.007f), new Vector3(0.064f, 0.028f, 0.014f), DimCyan, OpenKeyboard, 11f);
			_packSearchText = touchButton2.Label;
			CreateButton(_browser.transform, _packPage.transform, "ALL", new Vector3(-0.034f, 0.083f, 0.007f), new Vector3(0.046f, 0.028f, 0.014f), Cyan, ShowAllAvatars, 12f);
			CreateButton(_browser.transform, _packPage.transform, "OPTIONS", new Vector3(0.025f, 0.083f, 0.007f), new Vector3(0.068f, 0.028f, 0.014f), DimCyan, ShowOptionsPage, 9f);
			CreateButton(_browser.transform, _packPage.transform, "X", new Vector3(0.148f, 0.083f, 0.007f), new Vector3(0.026f, 0.028f, 0.014f), Red, Close, 19f);
			for (int num = 0; num < 4; num++)
			{
				CreatePackRow(num);
			}
			CreateButton(_browser.transform, _packPage.transform, "<", new Vector3(-0.135f, -0.091f, 0.007f), new Vector3(0.05f, 0.026f, 0.014f), DimCyan, delegate
			{
				MovePackPage(-1);
			}, 22f);
			_packPosition = CreateText(_packPage.transform, "1 / 1", new TabletRect(0f, -91f, 90f, 24f), 14f, Cyan, (TextAlignmentOptions)514, (FontStyles)1);
			CreateButton(_browser.transform, _packPage.transform, ">", new Vector3(0.135f, -0.091f, 0.007f), new Vector3(0.05f, 0.026f, 0.014f), DimCyan, delegate
			{
				MovePackPage(1);
			}, 22f);
			_packStatus = CreateText(_packPage.transform, string.Empty, new TabletRect(0f, -72f, 250f, 10f), 11f, new Color(0.7f, 0.82f, 0.88f, 1f), (TextAlignmentOptions)514, (FontStyles)0);
			_detailPage = new GameObject("Focused Avatar Page");
			_detailPage.transform.SetParent(_canvasRoot.transform, false);
			CreateImage(_detailPage.transform, "Header", new TabletRect(0f, 82f, 324f, 34f), new Color(0.02f, 0.09f, 0.13f, 0.96f));
			CreateButton(_browser.transform, _detailPage.transform, "BACK", new Vector3(-0.145f, 0.083f, 0.007f), new Vector3(0.042f, 0.028f, 0.014f), DimCyan, BackFromDetailPage, 9f);
			TouchButton touchButton3 = CreateButton(_browser.transform, _detailPage.transform, "SEARCH", new Vector3(-0.09f, 0.083f, 0.007f), new Vector3(0.064f, 0.028f, 0.014f), DimCyan, OpenKeyboard, 11f);
			_searchText = touchButton3.Label;
			CreateButton(_browser.transform, _detailPage.transform, "OPTIONS", new Vector3(-0.027f, 0.083f, 0.007f), new Vector3(0.058f, 0.028f, 0.014f), DimCyan, ShowOptionsPage, 8f);
			TouchButton touchButton4 = CreateButton(_browser.transform, _detailPage.transform, "ALL", new Vector3(0.048f, 0.083f, 0.007f), new Vector3(0.088f, 0.028f, 0.014f), Cyan, ToggleScopeOrDownloads, 10f);
			_scopeText = touchButton4.Label;
			CreateButton(_browser.transform, _detailPage.transform, "X", PixelCenter(AvatarTabletLayout.Close), PixelSize(AvatarTabletLayout.Close), Red, Close, 21f);
			CreateButton(_browser.transform, _detailPage.transform, "<", PixelCenter(AvatarTabletLayout.Previous), PixelSize(AvatarTabletLayout.Previous), DimCyan, delegate
			{
				MoveFocused(-1);
			}, 30f);
			CreateButton(_browser.transform, _detailPage.transform, ">", PixelCenter(AvatarTabletLayout.Next), PixelSize(AvatarTabletLayout.Next), DimCyan, delegate
			{
				MoveFocused(1);
			}, 30f);
			CreateImage(_detailPage.transform, "Preview Well", AvatarTabletLayout.Artwork, new Color(0.018f, 0.07f, 0.095f, 0.94f));
			_centralArtwork = CreateRawImage(_detailPage.transform, "Selected Artwork", new TabletRect(-66f, -4f, 116f, 120f), _placeholderTexture, Color.white);
			((Graphic)_centralArtwork).raycastTarget = false;
			CreateImage(_detailPage.transform, "Details Surface", AvatarTabletLayout.Details, new Color(0.02f, 0.065f, 0.09f, 0.94f));
			_selectedName = CreateText(_detailPage.transform, "Select an avatar", new TabletRect(65f, 45f, 112f, 32f), 18f, Color.white, (TextAlignmentOptions)257, (FontStyles)1);
			_creatorText = CreateText(_detailPage.transform, string.Empty, new TabletRect(65f, 22f, 112f, 16f), 12f, Cyan, (TextAlignmentOptions)4097, (FontStyles)0);
			_modText = CreateText(_detailPage.transform, string.Empty, new TabletRect(65f, 3f, 112f, 20f), 11f, new Color(0.72f, 0.82f, 0.88f, 1f), (TextAlignmentOptions)257, (FontStyles)0);
			_positionText = CreateText(_detailPage.transform, "0 / 0", new TabletRect(65f, -18f, 112f, 16f), 12f, Cyan, (TextAlignmentOptions)4097, (FontStyles)1);
			CreateTabletHeightRuler();
			_statusText = CreateText(_detailPage.transform, "Ready", new TabletRect(65f, -42f, 112f, 26f), 12f, Cyan, (TextAlignmentOptions)257, (FontStyles)1);
			_actionButton = CreateButton(_browser.transform, _detailPage.transform, "EQUIP", PixelCenter(AvatarTabletLayout.Action), PixelSize(AvatarTabletLayout.Action), Cyan, ActivateSelected, 18f);
			_actionLabel = _actionButton.Label;
			_actionPanel = _actionButton.Panel;
			_trashButton = CreateButton(_browser.transform, _detailPage.transform, "DELETE PACK", PixelCenter(AvatarTabletLayout.Trash), PixelSize(AvatarTabletLayout.Trash), Red, ShowDeleteConfirmation, 8f);
			((Object)_trashButton.Root).name = "Trash Avatar Pack";
			CreateTabletProgressBar();
			BuildOptionsPage();
			BuildKeyboardPage();
			BuildDeletePage();
			_packPage.SetActive(true);
			_detailPage.SetActive(false);
			GameObject? optionsPage = _optionsPage;
			if (optionsPage != null)
			{
				optionsPage.SetActive(false);
			}
			GameObject? keyboardPage = _keyboardPage;
			if (keyboardPage != null)
			{
				keyboardPage.SetActive(false);
			}
			GameObject? deletePage = _deletePage;
			if (deletePage != null)
			{
				deletePage.SetActive(false);
			}
			_browser.SetActive(false);
			CreatePreviewShell();
			_root.SetActive(false);
		}

		private void BuildHologramProjection()
		{
			if (_hologramProjection == null)
			{
				_hologramProjection = new BoneHubForearmProjection(_log);
			}
		}

		private void StartHologramProjection()
		{
			if (!_preferences.HologramEffects)
			{
				if ((Object)(object)_canvasGroup != (Object)null)
				{
					_canvasGroup.alpha = 1f;
				}
				_hologramProjection?.Hide(immediate: true);
			}
			else
			{
				if ((Object)(object)_canvasGroup != (Object)null)
				{
					_canvasGroup.alpha = (_preferences.ReducedMotion ? 1f : 0.35f);
				}
				_hologramProjection?.Show();
			}
		}

		private void UpdateHologramProjection()
		{
			//IL_0082: 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_017b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0186: Unknown result type (might be due to invalid IL or missing references)
			//IL_019b: Unknown result type (might be due to invalid IL or missing references)
			if (_hologramProjection == null)
			{
				return;
			}
			GameObject? browser = _browser;
			object obj;
			if (browser == null || !browser.activeSelf)
			{
				GameObject? hub = _hub;
				obj = ((hub != null && hub.activeSelf) ? _hub.transform : null);
			}
			else
			{
				obj = _browser.transform;
			}
			Transform val = (Transform)obj;
			if (!_preferences.HologramEffects)
			{
				if ((Object)(object)_canvasGroup != (Object)null)
				{
					_canvasGroup.alpha = 1f;
				}
				if ((Object)(object)val != (Object)null)
				{
					val.localPosition = Vector3.zero;
					val.localScale = Vector3.one;
				}
				_hologramProjection.Hide(immediate: true);
				return;
			}
			if ((Object)(object)val == (Object)null)
			{
				_hologramProjection.Hide();
				return;
			}
			float heightRatio = AvatarUiScaleMath.ResolveMenuScale(CurrentPlayerHeight());
			if (!_projectionAnchors.TryGet(_preferences.UseLeftWrist, heightRatio, out var anchor))
			{
				_hologramProjection.BeginTrackingGrace();
				_hologramProjection.UpdateTrackingLost();
				return;
			}
			_hologramProjection.UpdatePose(anchor, val, _preferences.ReducedMotion, _preferences.HologramBrightness);
			float num = (_preferences.ReducedMotion ? 1f : _hologramProjection.RevealProgress);
			if ((Object)(object)_canvasGroup != (Object)null)
			{
				_canvasGroup.alpha = Mathf.Lerp(0.35f, 1f, num);
			}
			val.localPosition = new Vector3(0f, -0.004f * (1f - num), 0f);
			val.localScale = Vector3.one * Mathf.Lerp(0.985f, 1f, num);
		}

		private void BuildDeletePage()
		{
			//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_0105: Unknown result type (might be due to invalid IL or missing references)
			//IL_013e: Unknown result type (might be due to invalid IL or missing references)
			//IL_015c: 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_01ac: Unknown result type (might be due to invalid IL or missing references)
			//IL_01b1: Unknown result type (might be due to invalid IL or missing references)
			//IL_01f8: Unknown result type (might be due to invalid IL or missing references)
			//IL_020c: 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)
			if (!((Object)(object)_browser == (Object)null) && !((Object)(object)_canvasRoot == (Object)null))
			{
				_deletePage = new GameObject("Delete Pack Confirmation Page");
				_deletePage.transform.SetParent(_canvasRoot.transform, false);
				CreateImage(_deletePage.transform, "Delete Header", new TabletRect(0f, 76f, 310f, 38f), new Color(0.3f, 0.035f, 0.055f, 0.98f));
				_deleteTitle = CreateText(_deletePage.transform, "DELETE AVATAR PACK?", new TabletRect(0f, 76f, 280f, 28f), 20f, Color.white, (TextAlignmentOptions)514, (FontStyles)1);
				_deleteDetails = CreateText(_deletePage.transform, string.Empty, new TabletRect(0f, 15f, 280f, 72f), 15f, Color.white, (TextAlignmentOptions)514, (FontStyles)0);
				CreateText(_deletePage.transform, "Locked packs finish safely on the next BONELAB launch. Undo stays available.", new TabletRect(0f, -37f, 280f, 34f), 11f, new Color(0.75f, 0.82f, 0.86f, 1f), (TextAlignmentOptions)514, (FontStyles)0);
				CreateButton(_browser.transform, _deletePage.transform, "CANCEL", new Vector3(-0.082f, -0.077f, 0.007f), new Vector3(0.13f, 0.038f, 0.014f), DimCyan, CancelDelete, 16f);
				CreateButton(_browser.transform, _deletePage.transform, "DELETE PACK", new Vector3(0.082f, -0.077f, 0.007f), new Vector3(0.13f, 0.038f, 0.014f), Red, TrashSelected, 16f);
				_deletePage.SetActive(false);
			}
		}

		private void ShowHub()
		{
			//IL_0050: Unknown result type (might be due to invalid IL or missing references)
			if ((Object)(object)_launcher != (Object)null)
			{
				_launcher.SetActive(false);
			}
			if ((Object)(object)_hub != (Object)null)
			{
				_hub.SetActive(true);
			}
			if ((Object)(object)_browser != (Object)null)
			{
				_browser.SetActive(false);
			}
			_viewState = (AvatarBrowserViewState)1;
			StartHologramProjection();
			ResetTouchButtons();
			RequireInputRearm();
			_log.Msg("WristHub wrist module hub opened.");
		}

		private void CreatePackRow(int index)
		{
			//IL_0080: 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_0099: Unknown result type (might be due to invalid IL or missing references)
			//IL_010c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0117: Unknown result type (might be due to invalid IL or missing references)
			//IL_0146: Unknown result type (might be due to invalid IL or missing references)
			//IL_0150: Unknown result type (might be due to invalid IL or missing references)
			//IL_0186: Unknown result type (might be due to invalid IL or missing references)
			//IL_01a4: Unknown result type (might be due to invalid IL or missing references)
			//IL_01da: Unknown result type (might be due to invalid IL or missing references)
			//IL_01e4: Unknown result type (might be due to invalid IL or missing references)
			//IL_0223: Unknown result type (might be due to invalid IL or missing references)
			//IL_0237: Unknown result type (might be due to invalid IL or missing references)
			//IL_023c: Unknown result type (might be due to invalid IL or missing references)
			if (!((Object)(object)_browser == (Object)null) && !((Object)(object)_packPage == (Object)null) && !((Object)(object)_placeholderTexture == (Object)null))
			{
				float num = 50f - (float)index * 34f;
				TouchButton touchButton = CreateButton(_browser.transform, _packPage.transform, string.Empty, new Vector3(-0.036f, num / 1000f, 0.007f), new Vector3(0.228f, 0.03f, 0.014f), Surface, delegate
				{
					OpenPackRow(index);
				}, 1f);
				RawImage artwork = CreateRawImage(touchButton.Root.transform, $"Pack {index + 1} Artwork", new TabletRect(-96f, 0f, 26f, 24f), _placeholderTexture, Color.white);
				TMP_Text name = CreateText(touchButton.Root.transform, string.Empty, new TabletRect(-28f, 5f, 104f, 13f), 12f, Color.white, (TextAlignmentOptions)4097, (FontStyles)1);
				TMP_Text creator = CreateText(touchButton.Root.transform, string.Empty, new TabletRect(-24f, -7f, 112f, 10f), 9f, new Color(0.65f, 0.8f, 0.86f, 1f), (TextAlignmentOptions)4097, (FontStyles)0);
				TMP_Text state = CreateText(touchButton.Root.transform, string.Empty, new TabletRect(78f, 0f, 54f, 18f), 9f, Cyan, (TextAlignmentOptions)4100, (FontStyles)1);
				TouchButton deleteButton = CreateButton(_browser.transform, _packPage.transform, "DELETE", new Vector3(0.125f, num / 1000f, 0.007f), new Vector3(0.052f, 0.03f, 0.014f), Red, delegate
				{
					ShowDeleteConfirmationForPack(index);
				}, 8f);
				_packRows.Add(new PackRow
				{
					Button = touchButton,
					DeleteButton = deleteButton,
					Artwork = artwork,
					Name = name,
					Creator = creator,
					State = state
				});
			}
		}

		private void BuildOptionsPage()
		{
			//IL_0023: Unknown result type (might be due to invalid IL or missing references)
			//IL_002d: Expected O, but got Unknown
			//IL_006d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0077: Unknown result type (might be due to invalid IL or missing references)
			//IL_00b3: Unknown result type (might be due to invalid IL or missing references)
			//IL_00c7: Unknown result type (might be due to invalid IL or missing references)
			//IL_00cc: Unknown result type (might be due to invalid IL or missing references)
			//IL_0113: Unknown result type (might be due to invalid IL or missing references)
			//IL_0127: Unknown result type (might be due to invalid IL or missing references)
			//IL_012c: Unknown result type (might be due to invalid IL or missing references)
			//IL_016d: 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_01b7: Unknown result type (might be due to invalid IL or missing references)
			//IL_01cb: Unknown result type (might be due to invalid IL or missing references)
			//IL_01d0: Unknown result type (might be due to invalid IL or missing references)
			//IL_0211: Unknown result type (might be due to invalid IL or missing references)
			//IL_021b: Unknown result type (might be due to invalid IL or missing references)
			//IL_025b: Unknown result type (might be due to invalid IL or missing references)
			//IL_026f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0274: Unknown result type (might be due to invalid IL or missing references)
			//IL_02b5: Unknown result type (might be due to invalid IL or missing references)
			//IL_02bf: Unknown result type (might be due to invalid IL or missing references)
			//IL_02ff: Unknown result type (might be due to invalid IL or missing references)
			//IL_0313: Unknown result type (might be due to invalid IL or missing references)
			//IL_0318: Unknown result type (might be due to invalid IL or missing references)
			//IL_0359: Unknown result type (might be due to invalid IL or missing references)
			//IL_0363: Unknown result type (might be due to invalid IL or missing references)
			//IL_03a3: Unknown result type (might be due to invalid IL or missing references)
			//IL_03b7: Unknown result type (might be due to invalid IL or missing references)
			//IL_03bc: Unknown result type (might be due to invalid IL or missing references)
			//IL_0403: Unknown result type (might be due to invalid IL or missing references)
			//IL_0417: Unknown result type (might be due to invalid IL or missing references)
			//IL_041c: Unknown result type (might be due to invalid IL or missing references)
			//IL_045d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0467: Unknown result type (might be due to invalid IL or missing references)
			//IL_04a7: Unknown result type (might be due to invalid IL or missing references)
			//IL_04bb: Unknown result type (might be due to invalid IL or missing references)
			//IL_04c0: Unknown result type (might be due to invalid IL or missing references)
			if (!((Object)(object)_browser == (Object)null) && !((Object)(object)_canvasRoot == (Object)null))
			{
				_optionsPage = new GameObject("WristHub Options Page");
				_optionsPage.transform.SetParent(_canvasRoot.transform, false);
				CreateText(_optionsPage.transform, "SETTINGS", new TabletRect(-92f, 82f, 112f, 28f), 18f, Color.white, (TextAlignmentOptions)4097, (FontStyles)1);
				CreateButton(_browser.transform, _optionsPage.transform, "BACK", new Vector3(0.108f, 0.082f, 0.007f), new Vector3(0.064f, 0.028f, 0.014f), DimCyan, ShowScopePage, 12f);
				CreateButton(_browser.transform, _optionsPage.transform, "X", new Vector3(0.151f, 0.082f, 0.007f), new Vector3(0.026f, 0.028f, 0.014f), Red, Close, 19f);
				_startingViewText = CreateText(_optionsPage.transform, string.Empty, new TabletRect(-58f, 49f, 194f, 20f), 13f, Color.white, (TextAlignmentOptions)4097, (FontStyles)1);
				CreateButton(_browser.transform, _optionsPage.transform, "CHANGE", new Vector3(0.112f, 0.049f, 0.007f), new Vector3(0.072f, 0.026f, 0.014f), Cyan, CycleStartingView, 10f);
				_sortText = CreateText(_optionsPage.transform, string.Empty, new TabletRect(-58f, 19f, 194f, 20f), 13f, Color.white, (TextAlignmentOptions)4097, (FontStyles)0);
				CreateButton(_browser.transform, _optionsPage.transform, "CHANGE", new Vector3(0.112f, 0.019f, 0.007f), new Vector3(0.072f, 0.026f, 0.014f), DimCyan, CyclePackSort, 10f);
				_wristText = CreateText(_optionsPage.transform, string.Empty, new TabletRect(-58f, -11f, 194f, 20f), 13f, Color.white, (TextAlignmentOptions)4097, (FontStyles)0);
				CreateButton(_browser.transform, _optionsPage.transform, "SWITCH", new Vector3(0.112f, -0.011f, 0.007f), new Vector3(0.072f, 0.026f, 0.014f), DimCyan, ToggleWrist, 10f);
				_scaleText = CreateText(_optionsPage.transform, string.Empty, new TabletRect(-58f, -41f, 194f, 20f), 13f, Color.white, (TextAlignmentOptions)4097, (FontStyles)0);
				CreateButton(_browser.transform, _optionsPage.transform, "-", new Vector3(0.086f, -0.041f, 0.007f), new Vector3(0.036f, 0.026f, 0.014f), DimCyan, delegate
				{
					ChangeScale(-0.1f);
				}, 18f);
				CreateButton(_browser.transform, _optionsPage.transform, "+", new Vector3(0.138f, -0.041f, 0.007f), new Vector3(0.036f, 0.026f, 0.014f), DimCyan, delegate
				{
					ChangeScale(0.1f);
				}, 18f);
				_motionText = CreateText(_optionsPage.transform, string.Empty, new TabletRect(-58f, -71f, 194f, 20f), 13f, Color.white, (TextAlignmentOptions)4097, (FontStyles)0);
				CreateButton(_browser.transform, _optionsPage.transform, "TOGGLE", new Vector3(0.112f, -0.071f, 0.007f), new Vector3(0.072f, 0.026f, 0.014f), DimCyan, ToggleMotion, 10f);
				RefreshOptionLabels();
			}
		}

		private void BuildKeyboardPage()
		{
			//IL_0023: Unknown result type (might be due to invalid IL or missing references)
			//IL_002d: Expected O, but got Unknown
			//IL_006d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0077: Unknown result type (might be due to invalid IL or missing references)
			//IL_00b3: Unknown result type (might be due to invalid IL or missing references)
			//IL_00c7: Unknown result type (might be due to invalid IL or missing references)
			//IL_00cc: Unknown result type (might be due to invalid IL or missing references)
			//IL_010d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0112: Unknown result type (might be due to invalid IL or missing references)
			//IL_0142: Unknown result type (might be due to invalid IL or missing references)
			//IL_014c: Unknown result type (might be due to invalid IL or missing references)
			//IL_018c: Unknown result type (might be due to invalid IL or missing references)
			//IL_01a0: Unknown result type (might be due to invalid IL or missing references)
			//IL_01a5: Unknown result type (might be due to invalid IL or missing references)
			//IL_0249: Unknown result type (might be due to invalid IL or missing references)
			//IL_025d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0262: Unknown result type (might be due to invalid IL or missing references)
			//IL_02b5: Unknown result type (might be due to invalid IL or missing references)
			//IL_02c9: Unknown result type (might be due to invalid IL or missing references)
			//IL_02ce: Unknown result type (might be due to invalid IL or missing references)
			//IL_0315: Unknown result type (might be due to invalid IL or missing references)
			//IL_0329: Unknown result type (might be due to invalid IL or missing references)
			//IL_032e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0375: Unknown result type (might be due to invalid IL or missing references)
			//IL_0389: Unknown result type (might be due to invalid IL or missing references)
			//IL_038e: Unknown result type (might be due to invalid IL or missing references)
			if (!((Object)(object)_browser == (Object)null) && !((Object)(object)_canvasRoot == (Object)null))
			{
				_keyboardPage = new GameObject("WristHub Tablet Keyboard");
				_keyboardPage.transform.SetParent(_canvasRoot.transform, false);
				CreateText(_keyboardPage.transform, "SEARCH AVATARS", new TabletRect(-70f, 84f, 180f, 24f), 17f, Color.white, (TextAlignmentOptions)4097, (FontStyles)1);
				CreateButton(_browser.transform, _keyboardPage.transform, "X", new Vector3(0.151f, 0.084f, 0.007f), new Vector3(0.026f, 0.026f, 0.014f), Red, CancelKeyboard, 19f);
				CreateImage(_keyboardPage.transform, "Search Input", new TabletRect(-35f, 55f, 236f, 30f), Surface);
				_keyboardText = CreateText(_keyboardPage.transform, "Type an avatar name", new TabletRect(-35f, 55f, 218f, 24f), 15f, Color.white, (TextAlignmentOptions)4097, (FontStyles)0);
				CreateButton(_browser.transform, _keyboardPage.transform, "SEARCH", new Vector3(0.125f, 0.055f, 0.007f), new Vector3(0.064f, 0.03f, 0.014f), Cyan, SubmitKeyboard, 10f);
				CreateKeyboardRow("QWERTYUIOP", -135f, 24f, 27f, 3f);
				CreateKeyboardRow("ASDFGHJKL", -120f, -6f, 27f, 3f);
				CreateKeyboardRow("ZXCVBNM", -105f, -36f, 27f, 3f);
				TouchButton touchButton = CreateButton(_browser.transform, _keyboardPage.transform, "SHIFT", new Vector3(-0.139f, -0.074f, 0.007f), new Vector3(0.052f, 0.026f, 0.014f), DimCyan, ToggleKeyboardShift, 10f);
				_keyboardShiftText = touchButton.Label;
				CreateButton(_browser.transform, _keyboardPage.transform, "SPACE", new Vector3(-0.073f, -0.074f, 0.007f), new Vector3(0.072f, 0.026f, 0.014f), DimCyan, AppendKeyboardSpace, 10f);
				CreateButton(_browser.transform, _keyboardPage.transform, "BACK", new Vector3(0f, -0.074f, 0.007f), new Vector3(0.062f, 0.026f, 0.014f), DimCyan, BackspaceKeyboard, 10f);
				CreateButton(_browser.transform, _keyboardPage.transform, "CLEAR", new Vector3(0.061f, -0.074f, 0.007f), new Vector3(0.05f, 0.026f, 0.014f), DimCyan, ClearKeyboard, 9f);
				_keyboardPage.SetActive(false);
			}
		}

		private void CreateKeyboardRow(string keys, float startX, float y, float width, float gap)
		{
			//IL_007e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0095: Unknown result type (might be due to invalid IL or missing references)
			//IL_009a: Unknown result type (might be due to invalid IL or missing references)
			if ((Object)(object)_browser == (Object)null || (Object)(object)_keyboardPage == (Object)null)
			{
				return;
			}
			for (int i = 0; i < keys.Length; i++)
			{
				char key = keys[i];
				float num = startX + (float)i * (width + gap);
				CreateButton(_browser.transform, _keyboardPage.transform, key.ToString(), new Vector3(num / 1000f, y / 1000f, 0.007f), new Vector3(width / 1000f, 0.026f, 0.014f), DimCyan, delegate
				{
					AppendKeyboardCharacter(key);
				}, 12f);
			}
		}

		private void HandlePackSearch()
		{
			//IL_0013: Unknown result type (might be due to invalid IL or missing references)
			//IL_0018: Unknown result type (might be due to invalid IL or missing references)
			//IL_0019: Unknown result type (might be due to invalid IL or missing references)
			//IL_001b: Unknown result type (might be due to invalid IL or missing references)
			//IL_001d: Invalid comparison between Unknown and I4
			bool flag = _query.Length > 0;
			if (!flag)
			{
				AvatarBrowserViewState viewState = _viewState;
				bool flag2 = viewState - 6 <= 1;
				flag = flag2;
			}
			if (flag)
			{
				OpenKeyboard();
			}
			else
			{
				OpenSearchPage();
			}
		}

		private void OpenSearchPage()
		{
			OpenKeyboard();
		}

		private void BackFromPackPage()
		{
			//IL_0013: Unknown result type (might be due to invalid IL or missing references)
			//IL_0018: Unknown result type (might be due to invalid IL or missing references)
			//IL_0019: Unknown result type (might be due to invalid IL or missing references)
			//IL_001b: Unknown result type (might be due to invalid IL or missing references)
			//IL_001d: Invalid comparison between Unknown and I4
			//IL_0055: Unknown result type (might be due to invalid IL or missing references)
			bool flag = _query.Length > 0;
			if (!flag)
			{
				AvatarBrowserViewState viewState = _viewState;
				bool flag2 = viewState - 6 <= 1;
				flag = flag2;
			}
			if (flag)
			{
				_query = string.Empty;
				_downloadsOnly = false;
				_onlineEntries = Array.Empty<AvatarEntry>();
				_selectedPackId = null;
				_viewState = (AvatarBrowserViewState)2;
				RefreshEntries();
				ShowPackPage();
			}
			else
			{
				ShowHub();
			}
		}

		private void BackFromDetailPage()
		{
			//IL_0058: Unknown result type (might be due to invalid IL or missing references)
			//IL_005e: Invalid comparison between Unknown and I4
			//IL_002d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0032: Unknown result type (might be due to invalid IL or missing references)
			//IL_0045: Unknown result type (might be due to invalid IL or missing references)
			if (_query.Length > 0)
			{
				_query = string.Empty;
				_downloadsOnly = false;
				_onlineEntries = Array.Empty<AvatarEntry>();
				_scope = _scopeBeforeSearch;
				_selectedPackId = null;
				_viewState = (AvatarBrowserViewState)2;
				RefreshEntries();
				ShowScopePage();
			}
			else if ((int)_scope == 1 && _selectedPackId.HasValue)
			{
				ShowPackPage();
			}
			else
			{
				ShowHub();
			}
		}

		private void ShowScopePage()
		{
			//IL_0001: Unknown result type (might be due to invalid IL or missing references)
			//IL_0007: Invalid comparison between Unknown and I4
			if ((int)_scope == 1)
			{
				ShowPackPage();
			}
			else
			{
				ShowAllAvatars();
			}
		}

		private void ShowAllAvatars()
		{
			//IL_0002: Unknown result type (might be due to invalid IL or missing references)
			_scope = (AvatarBrowseScope)0;
			_selectedPackId = null;
			RememberScope();
			RefreshEntries();
			if (!_entries.Any((AvatarEntry entry) => entry.Identity == _selectedIdentity))
			{
				AvatarEntry? obj = _entries.FirstOrDefault();
				_selectedIdentity = ((obj != null) ? obj.Identity : null) ?? string.Empty;
			}
			_cursor.Keep(_entries.Count, IndexOf(_selectedIdentity));
			ShowDetailPage();
		}

		private void ShowGroups()
		{
			//IL_0002: Unknown result type (might be due to invalid IL or missing references)
			_scope = (AvatarBrowseScope)1;
			_selectedPackId = null;
			RememberScope();
			ShowPackPage();
		}

		private void ToggleBrowseScope()
		{
			//IL_0001: Unknown result type (might be due to invalid IL or missing references)
			if ((int)_scope == 0)
			{
				ShowGroups();
			}
			else
			{
				ShowAllAvatars();
			}
		}

		private void ToggleScopeOrDownloads()
		{
			if (_query.Length == 0)
			{
				ToggleBrowseScope();
				return;
			}
			_downloadsOnly = !_downloadsOnly;
			_selectedPackId = null;
			RefreshEntries();
			AvatarEntry? obj = _entries.FirstOrDefault();
			_selectedIdentity = ((obj != null) ? obj.Identity : null) ?? string.Empty;
			_cursor.Reset(_entries.Count);
			RefreshFocusedResult();
			_log.Msg(_downloadsOnly ? $"Downloadable search section opened with {_entries.Count} results." : $"All search results reopened with {_entries.Count} results.");
		}

		private void RememberScope()
		{
			//IL_0016: Unknown result type (might be due to invalid IL or missing references)
			if (_query.Length <= 0)
			{
				_preferences.LastAvatarBrowseScope = _scope;
				SavePreferences();
			}
		}

		private void ShowPackPage()
		{
			if (!((Object)(object)_browser == (Object)null) && _browser.activeSelf && !_previewHeld)
			{
				GameObject? packPage = _packPage;
				if (packPage != null)
				{
					packPage.SetActive(true);
				}
				GameObject? detailPage = _detailPage;
				if (detailPage != null)
				{
					detailPage.SetActive(false);
				}
				GameObject? optionsPage = _optionsPage;
				if (optionsPage != null)
				{
					optionsPage.SetActive(false);
				}
				GameObject? keyboardPage = _keyboardPage;
				if (keyboardPage != null)
				{
					keyboardPage.SetActive(false);
				}
				GameObject? deletePage = _deletePage;
				if (deletePage != null)
				{
					deletePage.SetActive(false);
				}
				_previewGeneration++;
				_loadedPreviewBarcode = string.Empty;
				_requestedPreviewBarcode = string.Empty;
				_pendingPreviewAvatar = null;
				ClearPreviewModel();
				if ((Object)(object)_previewShell != (Object)null)
				{
					_previewShell.SetActive(false);
				}
				RefreshEntries();
				RefreshPackRows();
				RequireInputRearm();
			}
		}

		private void ShowDetailPage()
		{
			//IL_008d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0093: Invalid comparison between Unknown and I4
			//IL_0085: Unknown result type (might be due to invalid IL or missing references)
			//IL_0097: Unknown result type (might be due to invalid IL or missing references)
			if (!((Object)(object)_browser == (Object)null) && _browser.activeSelf)
			{
				GameObject? packPage = _packPage;
				if (packPage != null)
				{
					packPage.SetActive(false);
				}
				GameObject? detailPage = _detailPage;
				if (detailPage != null)
				{
					detailPage.SetActive(true);
				}
				GameObject? optionsPage = _optionsPage;
				if (optionsPage != null)
				{
					optionsPage.SetActive(false);
				}
				GameObject? keyboardPage = _keyboardPage;
				if (keyboardPage != null)
				{
					keyboardPage.SetActive(false);
				}
				GameObject? deletePage = _deletePage;
				if (deletePage != null)
				{
					deletePage.SetActive(false);
				}
				if (_query.Length == 0)
				{
					_viewState = (AvatarBrowserViewState)3;
				}
				else if ((int)_viewState != 6)
				{
					_viewState = (AvatarBrowserViewState)7;
				}
				RefreshFocusedResult();
				RequireInputRearm();
			}
		}

		private void ShowOptionsPage()
		{
			//IL_00a7: Unknown result type (might be due to invalid IL or missing references)
			GameObject? packPage = _packPage;
			if (packPage != null)
			{
				packPage.SetActive(false);
			}
			GameObject? detailPage = _detailPage;
			if (detailPage != null)
			{
				detailPage.SetActive(false);
			}
			GameObject? optionsPage = _optionsPage;
			if (optionsPage != null)
			{
				optionsPage.SetActive(true);
			}
			GameObject? keyboardPage = _keyboardPage;
			if (keyboardPage != null)
			{
				keyboardPage.SetActive(false);
			}
			GameObject? deletePage = _deletePage;
			if (deletePage != null)
			{
				deletePage.SetActive(false);
			}
			_previewGeneration++;
			_loadedPreviewBarcode = string.Empty;
			_requestedPreviewBarcode = string.Empty;
			_pendingPreviewAvatar = null;
			ClearPreviewModel();
			if ((Object)(object)_previewShell != (Object)null)
			{
				_previewShell.SetActive(false);
			}
			_viewState = (AvatarBrowserViewState)4;
			RefreshOptionLabels();
			RequireInputRearm();
		}

		private void RefreshVisiblePage()
		{
			if ((Object)(object)_detailPage != (Object)null && _detailPage.activeSelf)
			{
				RefreshFocusedResult();
			}
			else if ((Object)(object)_packPage != (Object)null && _packPage.activeSelf)
			{
				RefreshPackRows();
			}
			else if ((Object)(object)_optionsPage != (Object)null && _optionsPage.activeSelf)
			{
				RefreshOptionLabels();
			}
		}

		private void RefreshPackRows()
		{
			//IL_0054: 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_005b: Unknown result type (might be due to invalid IL or missing references)
			//IL_005e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0060: Invalid comparison between Unknown and I4
			//IL_010f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0115: Invalid comparison between Unknown and I4
			//IL_03e5: Unknown result type (might be due to invalid IL or missing references)
			//IL_03eb: Invalid comparison between Unknown and I4
			//IL_0409: Unknown result type (might be due to invalid IL or missing references)
			//IL_0402: Unknown result type (might be due to invalid IL or missing references)
			//IL_03fb: Unknown result type (might be due to invalid IL or missing references)
			if ((Object)(object)_packPage == (Object)null || !_packPage.activeSelf || (Object)(object)_placeholderTexture == (Object)null)
			{
				return;
			}
			_packCursor.Keep(_packs.Count);
			bool flag = _query.Length > 0;
			if (!flag)
			{
				AvatarBrowserViewState viewState = _viewState;
				bool flag2 = viewState - 6 <= 1;
				flag = flag2;
			}
			bool flag3 = flag;
			if ((Object)(object)_packTitle != (Object)null)
			{
				_packTitle.text = ((_query.Length > 0) ? "RESULTS" : "GROUPS");
			}
			if ((Object)(object)_packSearchText != (Object)null)
			{
				_packSearchText.text = "SEARCH";
			}
			if ((Object)(object)_packPosition != (Object)null)
			{
				_packPosition.text = _packCursor.PositionText;
			}
			if ((Object)(object)_packStatus != (Object)null)
			{
				_packStatus.text = ((flag3 && _query.Length == 0) ? "Touch the search field, type, and submit to search automatically" : (((int)_viewState == 6 && _query.Length > 0) ? "Searching BONELAB mod.io..." : ((_packs.Count != 0) ? $"{_packs.Count} pack{((_packs.Count == 1) ? string.Empty : "s")}  -  touch a row to open" : ((_query.Length == 0) ? "No installed avatar packs found" : "No matching avatars found"))));
			}
			int num = _packCursor.Page * 4;
			int num2 = Math.Min(4, _packRows.Count);
			for (int i = 0; i < num2; i++)
			{
				PackRow row = _packRows[i];
				if (!row.IsAlive)
				{
					continue;
				}
				row.Generation++;
				int num3 = num + i;
				if (num3 >= _packs.Count)
				{
					row.Identity = string.Empty;
					row.Button.Root.SetActive(false);
					row.DeleteButton.Root.SetActive(false);
					continue;
				}
				AvatarPackEntry pack = _packs[num3];
				row.Identity = pack.Identity;
				row.Button.Root.SetActive(true);
				row.DeleteButton.Root.SetActive(pack.Installed && pack.AvatarCount > 0);
				row.Name.text = pack.Name;
				row.Creator.text = "By " + pack.Creator;
				row.State.text = (pack.Installed ? $"{pack.AvatarCount} AVATAR{((pack.AvatarCount == 1) ? string.Empty : "S")}" : CardStatus(pack.Avatars[0]));
				((Graphic)row.State).color = (((int)pack.DownloadState == 5) ? Red : (pack.Installed ? Green : Cyan));
				row.Artwork.texture = (Texture)(object)_placeholderTexture;
				int generation = row.Generation;
				if (string.IsNullOrWhiteSpace(pack.ThumbnailUrl))
				{
					continue;
				}
				_thumbnails.Load(pack.ThumbnailUrl, delegate(Texture2D texture)
				{
					if (!_disposed && row.IsAlive && row.Generation == generation && row.Identity == pack.Identity && (Object)(object)texture != (Object)null)
					{
						row.Artwork.texture = (Texture)(object)texture;
					}
				});
			}
		}

		private void OpenPackRow(int slot)
		{
			int num = _packCursor.Page * 4 + slot;
			if (num >= 0 && num < _packs.Count)
			{
				AvatarPackEntry val = _packs[num];
				_selectedPackId = val.ModId;
				_entries = val.Avatars;
				AvatarEntry? obj = _entries.FirstOrDefault();
				_selectedIdentity = ((obj != null) ? obj.Identity : null) ?? string.Empty;
				_cursor.Reset(_entries.Count);
				_log.Msg($"Avatar pack opened ({val.AvatarCount} installed avatars). Pack name is not logged.");
				ShowDetailPage();
			}
		}

		private void ShowDeleteConfirmationForPack(int slot)
		{
			int num = _packCursor.Page * 4 + slot;
			if (num >= 0 && num < _packs.Count)
			{
				AvatarPackEntry val = _packs[num];
				AvatarEntry val2 = ((IEnumerable<AvatarEntry>)val.Avatars).FirstOrDefault((Func<AvatarEntry, bool>)((AvatarEntry entry) => entry.Installed && !string.IsNullOrWhiteSpace(entry.Barcode)));
				if (!(val2 == (AvatarEntry)null))
				{
					_selectedPackId = val.ModId;
					_entries = val.Avatars;
					_selectedIdentity = val2.Identity;
					_cursor.Reset(_entries.Count);
					_deleteReturnToPackPage = true;
					ShowDeleteConfirmation();
				}
			}
		}

		private void MovePackPage(int direction)
		{
			_packCursor.Move(direction);
			RefreshPackRows();
		}

		private void SetPackSort(AvatarPackSort sort)
		{
			//IL_0001: Unknown result type (might be due to invalid IL or missing references)
			//IL_0002: Unknown result type (might be due to invalid IL or missing references)
			_packSort = sort;
			_selectedPackId = null;
			RefreshEntries();
			ShowPackPage();
		}

		private void CyclePackSort()
		{
			//IL_0001: Unknown result type (might be due to invalid IL or missing references)
			//IL_0006: Unknown result type (might be due to invalid IL or missing references)
			//IL_0007: Unknown result type (might be due to invalid IL or missing references)
			//IL_0011: Unknown result type (might be due to invalid IL or missing references)
			//IL_000a: Unknown result type (might be due to invalid IL or missing references)
			//IL_000c: Invalid comparison between Unknown and I4
			//IL_001b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0015: Unknown result type (might be due to invalid IL or missing references)
			//IL_0019: Unknown result type (might be due to invalid IL or missing references)
			AvatarPackSort packSort = _packSort;
			AvatarPackSort packSort2 = (((int)packSort == 0) ? ((AvatarPackSort)1) : (((int)packSort != 1) ? ((AvatarPackSort)0) : ((AvatarPackSort)2)));
			SetPackSort(packSort2);
			ShowOptionsPage();
		}

		private void CycleStartingView()
		{
			//IL_000d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0012: 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_0015: Invalid comparison between Unknown and I4
			//IL_001e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0017: Unknown result type (might be due to invalid IL or missing references)
			//IL_0019: Invalid comparison between Unknown and I4
			//IL_0028: Unknown result type (might be due to invalid IL or missing references)
			//IL_0022: Unknown result type (might be due to invalid IL or missing references)
			//IL_0026: Unknown result type (might be due to invalid IL or missing references)
			BoneHubPreferences preferences = _preferences;
			BrowserStartupMode avatarBrowserStartupMode = _preferences.AvatarBrowserStartupMode;
			BrowserStartupMode avatarBrowserStartupMode2 = (((int)avatarBrowserStartupMode == 1) ? ((BrowserStartupMode)2) : (((int)avatarBrowserStartupMode != 2) ? ((BrowserStartupMode)1) : ((BrowserStartupMode)0)));
			preferences.AvatarBrowserStartupMode = avatarBrowserStartupMode2;
			RefreshOptionLabels();
			SavePreferences();
		}

		private void ToggleWrist()
		{
			_preferences.UseLeftWrist = !_preferences.UseLeftWrist;
			_fingertips.Invalidate();
			FollowWrist(immediate: true);
			RefreshOptionLabels();
			SavePreferences();
		}

		private void ChangeScale(float amount)
		{
			_preferences.WatchScale = Mathf.Clamp(_preferences.WatchScale + amount, 0.65f, 1.6f);
			FollowWrist(immediate: true);
			RefreshOptionLabels();
			SavePreferences();
		}

		private void ToggleMotion()
		{
			_preferences.ReducedMotion = !_preferences.ReducedMotion;
			RefreshOptionLabels();
			SavePreferences();
		}

		private void RefreshOptionLabels()
		{
			//IL_001b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0020: Unknown result type (might be due to invalid IL or missing references)
			//IL_0021: Unknown result type (might be due to invalid IL or missing references)
			//IL_006b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0024: Unknown result type (might be due to invalid IL or missing references)
			//IL_0026: Invalid comparison between Unknown and I4
			if ((Object)(object)_startingViewText != (Object)null)
			{
				TMP_Text startingViewText = _startingViewText;
				BrowserStartupMode avatarBrowserStartupMode = _preferences.AvatarBrowserStartupMode;
				string text = (((int)avatarBrowserStartupMode == 0) ? "Remember last" : (((int)avatarBrowserStartupMode != 2) ? "All avatars" : "Groups"));
				startingViewText.text = "Starting view: " + text;
			}
			if ((Object)(object)_sortText != (Object)null)
			{
				_sortText.text = "Library order: " + (((int)_packSort == 0) ? "Recent" : ((object)Unsafe.As<AvatarPackSort, AvatarPackSort>(ref _packSort)/*cast due to .constrained prefix*/).ToString());
			}
			if ((Object)(object)_wristText != (Object)null)
			{
				_wristText.text = "Wrist: " + (_preferences.UseLeftWrist ? "Left" : "Right");
			}
			if ((Object)(object)_scaleText != (Object)null)
			{
				_scaleText.text = $"UI scale: {_preferences.WatchScale:0.0}x";
			}
			if ((Object)(object)_motionText != (Object)null)
			{
				_motionText.text = "Reduced motion: " + (_preferences.ReducedMotion ? "On" : "Off");
			}
		}

		private void SavePreferences()
		{
			SavePreferencesGuardedAsync();
		}

		private async Task SavePreferencesGuardedAsync()
		{
			try
			{
				await _service.SavePreferencesAsync(default(CancellationToken)).ConfigureAwait(continueOnCapturedContext: false);
			}
			catch (Exception ex)
			{
				Exception exception = ex;
				_mainThread.Enqueue(delegate
				{
					_log.Error("Could not save WristHub tablet options: " + exception.Message);
				});
			}
		}

		private void CreateTabletHeightRuler()
		{
			//IL_0015: Unknown result type (might be due to invalid IL or missing references)
			//IL_001f: Expected O, but got Unknown
			//IL_0060: 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_0140: 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_00f8: 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_00fe: Unknown result type (might be due to invalid IL or missing references)
			if (!((Object)(object)_detailPage == (Object)null))
			{
				_heightRuler = new GameObject("Avatar Height Ruler");
				_heightRuler.transform.SetParent(_detailPage.transform, false);
				CreateImage(_heightRuler.transform, "Ruler Line", new TabletRect(-10f, -2f, 2f, 108f), DimCyan);
				TabletRect bounds = default(TabletRect);
				for (int i = 0; i < 7; i++)
				{
					float num = -56f + (float)i * 18f;
					Transform transform = _heightRuler.transform;
					bool flag = ((i == 0 || i == 6) ? true : false);
					float num2 = (flag ? (-15f) : (-14f));
					float num3 = num;
					bool flag2 = ((i == 0 || i == 6) ? true : false);
					((TabletRect)(ref bounds))..ctor(num2, num3, flag2 ? 12f : 8f, 2f);
					bool flag3 = ((i == 0 || i == 6) ? true : false);
					CreateImage(transform, "Ruler Tick", bounds, flag3 ? Cyan : DimCyan);
				}
				_heightText = CreateText(_heightRuler.transform, "Height after download", new TabletRect(-66f, -72f, 116f, 16f), 10f, Cyan, (TextAlignmentOptions)514, (FontStyles)1);
			}
		}

		private void CreateTabletProgressBar()
		{
			//IL_0015: Unknown result type (might be due to invalid IL or missing references)
			//IL_001f: Expected O, but got Unknown
			//IL_003b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0040: Unknown result type (might be due to invalid IL or missing references)
			//IL_0052: Unknown result type (might be due to invalid IL or missing references)
			//IL_0067: Unknown result type (might be due to invalid IL or missing references)
			//IL_00a3: Unknown result type (might be due to invalid IL or missing references)
			//IL_00cb: Unknown result type (might be due to invalid IL or missing references)
			//IL_0115: Unknown result type (might be due to invalid IL or missing references)
			//IL_011f: Unknown result type (might be due to invalid IL or missing references)
			if (!((Object)(object)_detailPage == (Object)null))
			{
				_progressRoot = new GameObject("Download Progress Capsule");
				_progressRoot.transform.SetParent(_detailPage.transform, false);
				TabletRect progress = AvatarTabletLayout.Progress;
				CreateImage(_progressRoot.transform, "Progress Track", progress, new Color(0.025f, 0.105f, 0.145f, 0.98f));
				Image val = CreateImage(_progressRoot.transform, "Progress Fill", new TabletRect(((TabletRect)(ref progress)).Left + 0.25f, ((TabletRect)(ref progress)).Y, 0.5f, ((TabletRect)(ref progress)).Height), new Color(Cyan.r, Cyan.g, Cyan.b, 0.82f));
				_progressFill = ((Graphic)val).rectTransform;
				_progressText = CreateText(_progressRoot.transform, string.Empty, new TabletRect(((TabletRect)(ref progress)).X, ((TabletRect)(ref progress)).Y, ((TabletRect)(ref progress)).Width - 8f, ((TabletRect)(ref progress)).Height), 8.5f, Color.white, (TextAlignmentOptions)514, (FontStyles)1);
				_progressRoot.SetActive(false);
			}
		}

		private void RefreshEntries()
		{
			//IL_007e: Unknown result type (might be due to invalid IL or missing references)
			//IL_014c: Unknown result type (might be due to invalid IL or missing references)
			AvatarEntry[] array = (from avatar in _runtime.GetAllAvatars()
				select avatar.ToEntry()).ToArray();
			IEnumerable<AvatarEntry> enumerable = ((IEnumerable<AvatarEntry>)_onlineEntries).Select((Func<AvatarEntry, AvatarEntry>)delegate(AvatarEntry entry)
			{
				//IL_0041: Unknown result type (might be due to invalid IL or missing references)
				//IL_0046: Unknown result type (might be due to invalid IL or missing references)
				if (_runtime.IsModInstalled(entry.ModId))
				{
					AvatarEntry obj = entry.<Clone>$();
					obj.set_Installed(true);
					obj.set_DownloadState((AvatarDownloadState)4);
					return obj;
				}
				DownloadJob value;
				return (!_jobs.TryGetValue(entry.ModId, out value)) ? entry : entry.WithDownload(Map(value.State), value.Progress, value.Error);
			});
			_mergedEntries = AvatarLibrary.Merge((IEnumerable<AvatarEntry>)array, enumerable, _query, (IEnumerable<string>)_service.RecentlyEquippedAvatarBarcodes);
			_packs = AvatarPackLibrary.Group((IEnumerable<AvatarEntry>)_mergedEntries, (IEnumerable<string>)_service.RecentlyEquippedAvatarBarcodes, _packSort);
			AvatarPackEntry val = (_selectedPackId.HasValue ? ((IEnumerable<AvatarPackEntry>)_packs).FirstOrDefault((Func<AvatarPackEntry, bool>)((AvatarPackEntry pack) => pack.ModId == _selectedPackId.Value)) : null);
			AvatarEntry[] array2 = (_selectedPackId.HasValue ? (from avatar in _runtime.GetAvatars(_selectedPackId.Value)
				select avatar.ToEntry()).OrderBy<AvatarEntry, string>((AvatarEntry entry) => entry.AvatarName, StringComparer.OrdinalIgnoreCase).ToArray() : Array.Empty<AvatarEntry>());
			if (val == (AvatarPackEntry)null && array2.Length != 0)
			{
				val = AvatarPackLibrary.Group((IEnumerable<AvatarEntry>)array2, (IEnumerable<string>)_service.RecentlyEquippedAvatarBarcodes, _packSort).FirstOrDefault();
			}
			IReadOnlyList<AvatarEntry> readOnlyList = (IReadOnlyList<AvatarEntry>)((_query.Length > 0 && _downloadsOnly) ? ((IEnumerable)AvatarLibrary.Downloadable((IEnumerable<AvatarEntry>)_mergedEntries)) : ((IEnumerable)_mergedEntries));
			object entries;
			if (array2.Length == 0)
			{
				entries = ((val != null) ? val.Avatars : null) ?? readOnlyList;
			}
			else
			{
				IReadOnlyList<AvatarEntry> readOnlyList2 = array2;
				entries = readOnlyList2;
			}
			_entries = (IReadOnlyList<AvatarEntry>)entries;
			if (_entries.Count == 0)
			{
				_selectedIdentity = string.Empty;
			}
			else if (!_entries.Any((AvatarEntry entry) => entry.Identity == _selectedIdentity))
			{
				_selectedIdentity = _entries[0].Identity;
			}
			_cursor.Keep(_entries.Count, IndexOf(_selectedIdentity));
			if ((Object)(object)_searchText != (Object)null)
			{
				_searchText.text = "SEARCH";
			}
		}

		private void RefreshFocusedResult()
		{
			//IL_005d: Unknown result type (might be due to invalid IL or missing references)
			//IL_012e: 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_019e: Invalid comparison between Unknown and I4
			//IL_0254: Unknown result type (might be due to invalid IL or missing references)
			//IL_025a: Invalid comparison between Unknown and I4
			//IL_0277: Unknown result type (might be due to invalid IL or missing references)
			if ((Object)(object)_browser == (Object)null || !_browser.activeSelf)
			{
				return;
			}
			if ((Object)(object)_searchText != (Object)null)
			{
				_searchText.text = "SEARCH";
			}
			if ((Object)(object)_scopeText != (Object)null)
			{
				_scopeText.text = ((_query.Length <= 0) ? (((int)_scope == 0) ? "ALL AVATARS" : "GROUPS") : (_downloadsOnly ? "ALL RESULTS" : "DOWNLOADS"));
			}
			if ((Object)(object)_positionText != (Object)null)
			{
				_positionText.text = _cursor.PositionText;
			}
			AvatarEntry val = Selected();
			if (val != (AvatarEntry)null)
			{
				if (_actionButton != null)
				{
					_actionButton.Root.SetActive(true);
				}
				RefreshSelection(val);
				return;
			}
			_artworkGeneration.Next();
			_previewGeneration++;
			_previewAvatar = null;
			_loadedPreviewBarcode = string.Empty;
			_requestedPreviewBarcode = string.Empty;
			_pendingPreviewAvatar = null;
			_previewReady = false;
			_previewInteractionReady = false;
			_previewVisualSource = (AvatarPreviewVisualSource)0;
			ClearPreviewModel();
			if ((Object)(object)_previewShell != (Object)null)
			{
				_previewShell.SetActive(false);
			}
			if ((Object)(object)_centralArtwork != (Object)null)
			{
				((Component)_centralArtwork).gameObject.SetActive(true);
				_centralArtwork.texture = (Texture)(object)_placeholderTexture;
			}
			if ((Object)(object)_selectedName != (Object)null)
			{
				_selectedName.text = (((int)_viewState == 6) ? "Searching..." : (_downloadsOnly ? "No downloads found" : "No avatars found"));
			}
			if ((Object)(object)_creatorText !=