Decompiled source of WristHub Quest v4.2.1
Mods\WristHub.Core.dll
Decompiled 2 months ago
The result has been truncated due to the large size, download it to view full contents!
using System; using System.Collections.Concurrent; using System.Collections.Generic; using System.Diagnostics; using System.IO; using System.IO.Compression; using System.Linq; using System.Net; using System.Net.Http; using System.Net.Http.Headers; using System.Numerics; using System.Reflection; using System.Runtime.CompilerServices; using System.Runtime.Versioning; using System.Security.Cryptography; using System.Text; using System.Text.Json; using System.Text.Json.Nodes; using System.Text.Json.Serialization; using System.Threading; using System.Threading.Tasks; using BoneHub.Api; using BoneHub.Avatars; using BoneHub.Configuration; using BoneHub.Downloads; using BoneHub.Installation; using BoneHub.Interaction; using BoneHub.Models; using BoneHub.Persistence; [assembly: CompilationRelaxations(8)] [assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)] [assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)] [assembly: TargetFramework(".NETCoreApp,Version=v8.0", FrameworkDisplayName = ".NET 8.0")] [assembly: AssemblyCompany("WristHub.Core")] [assembly: AssemblyConfiguration("Release")] [assembly: AssemblyFileVersion("4.2.1.0")] [assembly: AssemblyInformationalVersion("4.2.1")] [assembly: AssemblyProduct("WristHub.Core")] [assembly: AssemblyTitle("WristHub.Core")] [assembly: AssemblyVersion("4.2.1.0")] [module: RefSafetyRules(11)] namespace BoneHub { public sealed class BoneHubService : IDisposable { private readonly IModIoClient _client; private readonly DownloadManager _downloads; private readonly BoneHubOptions _options; private readonly SearchCache _cache; private readonly JsonFileStore<BoneHubState> _stateStore; private readonly SemaphoreSlim _stateGate = new SemaphoreSlim(1, 1); private BoneHubState _state = new BoneHubState(); public int FavoriteCount => _state.FavoriteModIds.Count; public IReadOnlyCollection<BoneHub.Persistence.InstalledModRecord> Installed => (IReadOnlyCollection<BoneHub.Persistence.InstalledModRecord>)(object)_state.InstalledMods.Values.ToArray(); public BoneHubPreferences Preferences => _state.Preferences; public IReadOnlyList<string> RecentlyEquippedAvatarBarcodes => _state.RecentlyEquippedAvatarBarcodes.ToArray(); public string LastEquippedAvatarBarcode { get { if (!string.IsNullOrWhiteSpace(_state.LastEquippedAvatarBarcode)) { return _state.LastEquippedAvatarBarcode; } return _state.RecentlyEquippedAvatarBarcodes.FirstOrDefault() ?? string.Empty; } } public TrashedModRecord? LastTrashed => _state.TrashHistory.LastOrDefault(); public IReadOnlyCollection<long> PendingDeletionModIds => (IReadOnlyCollection<long>)(object)_state.PendingDeletions.Select((PendingDeletionRecord item) => item.Mod.ModId).ToArray(); public bool ModIoReady => _options.CanUseModIo; public string ApiKeyFilePath => _options.ApiKeyFilePath; public event Action<DownloadJob>? DownloadChanged; public event Action? InstalledChanged; public BoneHubService(IModIoClient client, DownloadManager downloads, BoneHubOptions options) { _client = client; _downloads = downloads; _options = options; _cache = new SearchCache(Path.Combine(options.DataDirectory, "cache", "search")); _stateStore = new JsonFileStore<BoneHubState>(Path.Combine(options.DataDirectory, "state.json")); _downloads.JobChanged += OnJobChanged; } public async Task InitializeAsync(CancellationToken cancellationToken = default(CancellationToken)) { _state = await _stateStore.LoadAsync(cancellationToken).ConfigureAwait(continueOnCapturedContext: false); bool flag = false; if (_state.Preferences.MiniMapRange == MiniMapRange.TwentyFiveMeters) { _state.Preferences.MiniMapRange = MiniMapRange.ThirtyMeters; flag = true; } if (_state.Preferences.MotionPreferenceSchemaVersion < 1) { if (_state.Preferences.ComfortPreset != BoneHubComfortPreset.ReducedMotion && _state.Preferences.HologramEffects) { _state.Preferences.ReducedMotion = false; } _state.Preferences.MotionPreferenceSchemaVersion = 1; flag = true; } if (flag) { await _stateStore.SaveAsync(_state, cancellationToken).ConfigureAwait(continueOnCapturedContext: false); } } public async Task<ModIoPage<ModIoMod>> SearchAsync(ModSearchRequest request, CancellationToken cancellationToken = default(CancellationToken)) { ModSearchRequest normalized = request with { Query = request.Query.Trim(), Limit = Math.Clamp(request.Limit, 1, 100), Offset = Math.Max(request.Offset, 0) }; string key = $"{normalized.Query}|{normalized.Tag}|{normalized.Creator}|{normalized.Sort}|{normalized.Offset}|{normalized.Limit}|{_options.TargetPlatform}"; ModIoPage<ModIoMod> modIoPage = await _cache.ReadAsync(key, TimeSpan.FromMinutes(_options.CacheMinutes), cancellationToken).ConfigureAwait(continueOnCapturedContext: false); if (modIoPage != null) { return modIoPage; } try { ModIoPage<ModIoMod> page = await _client.SearchAsync(normalized, cancellationToken).ConfigureAwait(continueOnCapturedContext: false); await _cache.WriteAsync(key, page, cancellationToken).ConfigureAwait(continueOnCapturedContext: false); return page; } catch when (!cancellationToken.IsCancellationRequested) { ModIoPage<ModIoMod> modIoPage2 = await _cache.ReadAsync(key, TimeSpan.FromDays(7.0), cancellationToken).ConfigureAwait(continueOnCapturedContext: false); if (modIoPage2 != null) { return modIoPage2; } throw; } } public DownloadJob Install(ModIoMod mod, CancellationToken cancellationToken = default(CancellationToken)) { return _downloads.Enqueue(mod, cancellationToken); } public async Task<ModInstallPlan> GetInstallPlanAsync(ModIoMod root, CancellationToken cancellationToken = default(CancellationToken)) { if (!ModIoReady) { throw new InvalidOperationException("WristHub's online avatar service is unavailable."); } List<ModIoMod> ordered = new List<ModIoMod>(); HashSet<long> installed = new HashSet<long>(); HashSet<long> visited = new HashSet<long>(); HashSet<long> visiting = new HashSet<long>(); await VisitAsync(root, isRoot: true).ConfigureAwait(continueOnCapturedContext: false); return new ModInstallPlan(root, ordered, installed.OrderBy((long id) => id).ToArray()); async Task VisitAsync(ModIoMod mod, bool isRoot) { cancellationToken.ThrowIfCancellationRequested(); if (!visited.Contains(mod.Id)) { if (!visiting.Add(mod.Id)) { throw new InvalidDataException($"mod.io reported a dependency cycle involving mod {mod.Id}."); } if (visited.Count + visiting.Count > 32) { throw new InvalidDataException("This dependency graph exceeds WristHub's 32-mod safety limit."); } foreach (ModIoDependency item in (await _client.GetDependenciesAsync(mod.Id, cancellationToken).ConfigureAwait(continueOnCapturedContext: false)).Data.Where((ModIoDependency item) => item.ModId > 0).DistinctBy((ModIoDependency item) => item.ModId)) { if (_state.InstalledMods.ContainsKey(item.ModId)) { installed.Add(item.ModId); } else { await VisitAsync(await _client.GetModAsync(item.ModId, cancellationToken).ConfigureAwait(continueOnCapturedContext: false), isRoot: false).ConfigureAwait(continueOnCapturedContext: false); } } visiting.Remove(mod.Id); visited.Add(mod.Id); if (isRoot || !_state.InstalledMods.ContainsKey(mod.Id)) { ordered.Add(mod); } else { installed.Add(mod.Id); } } } } public async Task<IReadOnlyList<DownloadJob>> InstallWithDependenciesAsync(ModIoMod root, CancellationToken cancellationToken = default(CancellationToken)) { return (await GetInstallPlanAsync(root, cancellationToken).ConfigureAwait(continueOnCapturedContext: false)).OrderedMods.Select((ModIoMod mod) => Install(mod, cancellationToken)).ToArray(); } public bool CancelDownload(Guid jobId) { return _downloads.Cancel(jobId); } public bool IsFavorite(long modId) { return _state.FavoriteModIds.Contains(modId); } public bool IsInstalled(long modId) { return _state.InstalledMods.ContainsKey(modId); } public async Task ConfigureApiKeyAsync(string value, CancellationToken cancellationToken = default(CancellationToken)) { string normalized = value?.Trim() ?? string.Empty; if (!BoneHubOptions.IsValidApiKey(normalized)) { throw new InvalidDataException("The mod.io API key must contain exactly 32 letters or numbers."); } if (string.IsNullOrWhiteSpace(_options.ApiKeyFilePath)) { throw new InvalidOperationException("WristHub's API-key file path is unavailable."); } string path = Path.GetFullPath(_options.ApiKeyFilePath); Directory.CreateDirectory(Path.GetDirectoryName(path) ?? throw new InvalidOperationException("The API-key file has no parent directory.")); string temporary = path + ".tmp"; await File.WriteAllTextAsync(temporary, normalized, cancellationToken).ConfigureAwait(continueOnCapturedContext: false); File.Move(temporary, path, overwrite: true); _options.ApiKey = normalized; } public async Task SetFavoriteAsync(long modId, bool favorite, CancellationToken cancellationToken = default(CancellationToken)) { await _stateGate.WaitAsync(cancellationToken).ConfigureAwait(continueOnCapturedContext: false); try { if (favorite) { _state.FavoriteModIds.Add(modId); } else { _state.FavoriteModIds.Remove(modId); } await _stateStore.SaveAsync(_state, cancellationToken).ConfigureAwait(continueOnCapturedContext: false); } finally { _stateGate.Release(); } } public async Task<IReadOnlyList<ModIoMod>> GetFavoriteModsAsync(CancellationToken cancellationToken = default(CancellationToken)) { await _stateGate.WaitAsync(cancellationToken).ConfigureAwait(continueOnCapturedContext: false); long[] array; try { array = _state.FavoriteModIds.Take(50).ToArray(); } finally { _stateGate.Release(); } List<ModIoMod> results = new List<ModIoMod>(); long[] array2 = array; foreach (long modId in array2) { cancellationToken.ThrowIfCancellationRequested(); try { List<ModIoMod> list = results; list.Add(await _client.GetModAsync(modId, cancellationToken).ConfigureAwait(continueOnCapturedContext: false)); } catch when (!cancellationToken.IsCancellationRequested) { } } return results.OrderBy((ModIoMod mod) => mod.Name).ToArray(); } public Task<ModIoMod> GetModDetailsAsync(long modId, CancellationToken cancellationToken = default(CancellationToken)) { return _client.GetModAsync(modId, cancellationToken); } public async Task MarkViewedAsync(long modId, CancellationToken cancellationToken = default(CancellationToken)) { await _stateGate.WaitAsync(cancellationToken).ConfigureAwait(continueOnCapturedContext: false); try { _state.RecentlyViewedModIds.Remove(modId); _state.RecentlyViewedModIds.Insert(0, modId); if (_state.RecentlyViewedModIds.Count > 50) { _state.RecentlyViewedModIds.RemoveRange(50, _state.RecentlyViewedModIds.Count - 50); } await _stateStore.SaveAsync(_state, cancellationToken).ConfigureAwait(continueOnCapturedContext: false); } finally { _stateGate.Release(); } } public async Task MarkAvatarEquippedAsync(string barcode, CancellationToken cancellationToken = default(CancellationToken)) { string normalized = barcode?.Trim() ?? string.Empty; if (normalized.Length == 0) { return; } await _stateGate.WaitAsync(cancellationToken).ConfigureAwait(continueOnCapturedContext: false); try { _state.RecentlyEquippedAvatarBarcodes.RemoveAll((string item) => string.Equals(item, normalized, StringComparison.OrdinalIgnoreCase)); _state.RecentlyEquippedAvatarBarcodes.Insert(0, normalized); _state.LastEquippedAvatarBarcode = normalized; if (_state.RecentlyEquippedAvatarBarcodes.Count > 50) { _state.RecentlyEquippedAvatarBarcodes.RemoveRange(50, _state.RecentlyEquippedAvatarBarcodes.Count - 50); } await _stateStore.SaveAsync(_state, cancellationToken).ConfigureAwait(continueOnCapturedContext: false); } finally { _stateGate.Release(); } } public async Task ForgetAvatarBarcodesAsync(IEnumerable<string> barcodes, CancellationToken cancellationToken = default(CancellationToken)) { HashSet<string> removed = (from item in barcodes where !string.IsNullOrWhiteSpace(item) select item.Trim()).ToHashSet<string>(StringComparer.OrdinalIgnoreCase); if (removed.Count == 0) { return; } await _stateGate.WaitAsync(cancellationToken).ConfigureAwait(continueOnCapturedContext: false); try { _state.RecentlyEquippedAvatarBarcodes.RemoveAll(removed.Contains); if (removed.Contains(_state.LastEquippedAvatarBarcode)) { _state.LastEquippedAvatarBarcode = _state.RecentlyEquippedAvatarBarcodes.FirstOrDefault() ?? string.Empty; } await _stateStore.SaveAsync(_state, cancellationToken).ConfigureAwait(continueOnCapturedContext: false); } finally { _stateGate.Release(); } } public async Task SavePreferencesAsync(CancellationToken cancellationToken = default(CancellationToken)) { await _stateGate.WaitAsync(cancellationToken).ConfigureAwait(continueOnCapturedContext: false); try { if (!Enum.IsDefined(_state.Preferences.ComfortPreset)) { _state.Preferences.ComfortPreset = BoneHubComfortPreset.Default; } if (!Enum.IsDefined(_state.Preferences.AvatarBrowserStartupMode)) { _state.Preferences.AvatarBrowserStartupMode = BrowserStartupMode.AllInstalled; } if (!Enum.IsDefined(_state.Preferences.LastAvatarBrowseScope)) { _state.Preferences.LastAvatarBrowseScope = AvatarBrowseScope.AllInstalled; } if (!Enum.IsDefined(_state.Preferences.MiniMapRange)) { _state.Preferences.MiniMapRange = MiniMapRange.ThirtyMeters; } if (!Enum.IsDefined(_state.Preferences.MiniMapOrientation)) { _state.Preferences.MiniMapOrientation = MiniMapOrientation.HeadingUp; } if (!Enum.IsDefined(_state.Preferences.HologramTheme)) { _state.Preferences.HologramTheme = WristHubTheme.Cyan; } if (!Enum.IsDefined(_state.Preferences.WatchStyle)) { _state.Preferences.WatchStyle = WristWatchStyle.ClassicCircle; } if (!Enum.IsDefined(_state.Preferences.WatchAttachment)) { _state.Preferences.WatchAttachment = WristWatchAttachment.Adaptive; } if (!Enum.IsDefined(_state.Preferences.ClockOrientation)) { _state.Preferences.ClockOrientation = WristClockOrientation.ArmFixed; } _state.Preferences.MotionPreferenceSchemaVersion = Math.Max(1, _state.Preferences.MotionPreferenceSchemaVersion); _state.Preferences.WatchScale = Math.Clamp(_state.Preferences.WatchScale, 0.65f, 1.6f); _state.Preferences.WatchBodyScale = Math.Clamp(_state.Preferences.WatchBodyScale, 0.75f, 1.4f); _state.Preferences.DwellSeconds = 1f; _state.Preferences.OutwardOffset = Math.Clamp(_state.Preferences.OutwardOffset, 0.015f, 0.06f); _state.Preferences.FingerOffset = Math.Clamp(_state.Preferences.FingerOffset, -0.055f, 0.025f); _state.Preferences.WatchForearmOffset = Math.Clamp(_state.Preferences.WatchForearmOffset, -0.08f, 0.03f); _state.Preferences.WatchSleepLift = Math.Clamp(_state.Preferences.WatchSleepLift, 0f, 0.03f); _state.Preferences.CarouselDistance = Math.Clamp(_state.Preferences.CarouselDistance, 0.45f, 0.95f); _state.Preferences.HologramScale = Math.Clamp(_state.Preferences.HologramScale, 0.7f, 1.4f); _state.Preferences.EquipZoneScale = Math.Clamp(_state.Preferences.EquipZoneScale, 0.7f, 1.6f); _state.Preferences.HologramBrightness = Math.Clamp(_state.Preferences.HologramBrightness, 0.35f, 1.5f); foreach (CompassCalibrationRecord compassCalibration in _state.Preferences.CompassCalibrations) { compassCalibration.NorthYawDegrees = CompassHeadingMath.Normalize(compassCalibration.NorthYawDegrees); } while (_state.Preferences.CompassCalibrations.Count > 64) { CompassCalibrationRecord item = _state.Preferences.CompassCalibrations.OrderBy((CompassCalibrationRecord record) => record.LastUsedUtc).First(); _state.Preferences.CompassCalibrations.Remove(item); } if (_state.Preferences.SpawnerStateSchemaVersion < 2) { _state.Preferences.LegacySpawnableFavoritesBackup.Clear(); _state.Preferences.LegacySpawnableFavoritesBackup.AddRange(_state.Preferences.FavoriteSpawnableBarcodes); _state.Preferences.LegacySpawnableRecentsBackup.Clear(); _state.Preferences.LegacySpawnableRecentsBackup.AddRange(_state.Preferences.RecentSpawnableBarcodes); _state.Preferences.LastSpawnableMode = (string.IsNullOrWhiteSpace(_state.Preferences.LastSpawnableMode) ? "Recent" : _state.Preferences.LastSpawnableMode); BoneHubPreferences preferences = _state.Preferences; if (preferences.LastSpawnableGroup == null) { preferences.LastSpawnableGroup = string.Empty; } _state.Preferences.SpawnerStateSchemaVersion = 2; } TrimDistinct(_state.Preferences.FavoriteSpawnableBarcodes, 128); TrimDistinct(_state.Preferences.RecentSpawnableBarcodes, 64); TrimDistinct(_state.Preferences.LegacySpawnableFavoritesBackup, 128); TrimDistinct(_state.Preferences.LegacySpawnableRecentsBackup, 64); TrimDistinct(_state.Preferences.PinnedWristAppIds, 8); TrimDistinct(_state.Preferences.OpenedWristAppIds, 256); if (!Enum.IsDefined(_state.Preferences.WristAppLibraryTab)) { _state.Preferences.WristAppLibraryTab = WristAppLibraryTab.Installed; } _state.Preferences.WristAppSearchQuery = (_state.Preferences.WristAppSearchQuery ?? string.Empty).Trim(); if (_state.Preferences.WristAppSearchQuery.Length > 80) { _state.Preferences.WristAppSearchQuery = _state.Preferences.WristAppSearchQuery.Substring(0, 80); } await _stateStore.SaveAsync(_state, cancellationToken).ConfigureAwait(continueOnCapturedContext: false); } finally { _stateGate.Release(); } } private static void TrimDistinct(List<string> values, int maximum) { HashSet<string> hashSet = new HashSet<string>(StringComparer.OrdinalIgnoreCase); for (int num = values.Count - 1; num >= 0; num--) { string text = values[num]?.Trim() ?? string.Empty; if (text.Length == 0 || !hashSet.Add(text)) { values.RemoveAt(num); } else { values[num] = text; } } if (values.Count > maximum) { values.RemoveRange(maximum, values.Count - maximum); } } public InstalledModHealth VerifyInstalled(long modId) { if (!_state.InstalledMods.TryGetValue(modId, out BoneHub.Persistence.InstalledModRecord value)) { return new InstalledModHealth(Healthy: false, new string[1] { "WristHub has no installation record for this mod." }); } List<string> list = new List<string>(); foreach (string installedDirectory in value.InstalledDirectories) { string text; try { text = ValidateInstalledPath(installedDirectory); } catch (Exception ex) { list.Add(ex.Message); continue; } if (!Directory.Exists(text)) { list.Add("Missing folder: " + Path.GetFileName(text)); } else if (!PalletManifestLocator.HasManifest(text)) { list.Add("Missing pallet manifest: " + Path.GetFileName(text)); } } return new InstalledModHealth(list.Count == 0, list); } public async Task<ModUpdateInfo> CheckForUpdateAsync(long modId, CancellationToken cancellationToken = default(CancellationToken)) { if (!_state.InstalledMods.TryGetValue(modId, out BoneHub.Persistence.InstalledModRecord installed)) { throw new InvalidOperationException("This mod is not installed."); } ModIoMod mod = await _client.GetModAsync(modId, cancellationToken).ConfigureAwait(continueOnCapturedContext: false); ModIoFile modIoFile = await ModIoFileResolver.ResolveAsync(_client, mod, _options, cancellationToken).ConfigureAwait(continueOnCapturedContext: false); return new ModUpdateInfo(mod, modIoFile.Id != installed.FileId, installed.FileId, modIoFile.Id); } public async Task<IReadOnlyList<ModUpdateInfo>> CheckAllUpdatesAsync(CancellationToken cancellationToken = default(CancellationToken)) { List<ModUpdateInfo> results = new List<ModUpdateInfo>(); foreach (BoneHub.Persistence.InstalledModRecord item in _state.InstalledMods.Values.OrderBy((BoneHub.Persistence.InstalledModRecord item) => item.Name)) { cancellationToken.ThrowIfCancellationRequested(); List<ModUpdateInfo> list = results; list.Add(await CheckForUpdateAsync(item.ModId, cancellationToken).ConfigureAwait(continueOnCapturedContext: false)); } return results; } public async Task<DownloadJob> RepairOrUpdateAsync(long modId, CancellationToken cancellationToken = default(CancellationToken)) { return Install((await CheckForUpdateAsync(modId, cancellationToken).ConfigureAwait(continueOnCapturedContext: false)).Mod, cancellationToken); } public async Task<int> RepairSharingRegistrationsAsync(CancellationToken cancellationToken = default(CancellationToken)) { BoneHub.Persistence.InstalledModRecord[] array = _state.InstalledMods.Values.Where((BoneHub.Persistence.InstalledModRecord installedModRecord) => installedModRecord.ModId > 0 && installedModRecord.InstalledDirectories.Any(Directory.Exists) && (installedModRecord.Sharing == null || installedModRecord.Sharing.RegistrationVersion < 3 || installedModRecord.Sharing.SidecarManifestPaths.Count == 0 || installedModRecord.Sharing.SidecarManifestPaths.Any((string path) => !File.Exists(path)))).ToArray(); int repaired = 0; BoneHub.Persistence.InstalledModRecord[] array2 = array; foreach (BoneHub.Persistence.InstalledModRecord record in array2) { cancellationToken.ThrowIfCancellationRequested(); try { ModIoMod mod = await _client.GetModAsync(record.ModId, cancellationToken).ConfigureAwait(continueOnCapturedContext: false); ModIoFile modIoFile = ((record.FileId <= 0) ? (await ModIoFileResolver.ResolveAsync(_client, mod, _options, cancellationToken).ConfigureAwait(continueOnCapturedContext: false)) : (await _client.GetFileAsync(record.ModId, record.FileId, cancellationToken).ConfigureAwait(continueOnCapturedContext: false))); ModIoFile file = modIoFile; ModIoSharingMetadata sharing = await NetworkerRegistrationWriter.WriteAsync(mod, file, await ModIoFileResolver.ResolvePlatformFilesAsync(_client, record.ModId, cancellationToken).ConfigureAwait(continueOnCapturedContext: false), record.InstalledDirectories, _options.ModsDirectory, cancellationToken).ConfigureAwait(continueOnCapturedContext: false); await _stateGate.WaitAsync(cancellationToken).ConfigureAwait(continueOnCapturedContext: false); try { if (_state.InstalledMods.TryGetValue(record.ModId, out BoneHub.Persistence.InstalledModRecord value)) { value.Sharing = sharing; } await _stateStore.SaveAsync(_state, cancellationToken).ConfigureAwait(continueOnCapturedContext: false); } finally { _stateGate.Release(); } repaired++; } catch when (!cancellationToken.IsCancellationRequested) { } } if (repaired > 0) { this.InstalledChanged?.Invoke(); } return repaired; } public Task<TrashedModRecord> UninstallAsync(long modId, CancellationToken cancellationToken = default(CancellationToken)) { return UninstallCoreAsync(modId, null, null, wasSubscribed: false, cancellationToken); } public Task<TrashedModRecord> UninstallAsync(long modId, string? localName, IReadOnlyList<string>? localDirectories, CancellationToken cancellationToken = default(CancellationToken)) { return UninstallCoreAsync(modId, localName, localDirectories, wasSubscribed: false, cancellationToken); } public Task<TrashedModRecord> UninstallSubscribedAsync(long modId, string? localName, IReadOnlyList<string>? localDirectories, bool wasSubscribed, CancellationToken cancellationToken = default(CancellationToken)) { return UninstallCoreAsync(modId, localName, localDirectories, wasSubscribed, cancellationToken); } public async Task<PendingDeletionRecord> QueueDeletionAsync(long modId, string? localName, IReadOnlyList<string>? localDirectories, bool wasSubscribed, IEnumerable<string>? avatarBarcodes, CancellationToken cancellationToken = default(CancellationToken)) { await _stateGate.WaitAsync(cancellationToken).ConfigureAwait(continueOnCapturedContext: false); PendingDeletionRecord queued; try { PendingDeletionRecord pendingDeletionRecord = _state.PendingDeletions.FirstOrDefault((PendingDeletionRecord item) => item.Mod.ModId == modId); if (pendingDeletionRecord != null) { return pendingDeletionRecord; } _state.InstalledMods.TryGetValue(modId, out BoneHub.Persistence.InstalledModRecord value); IEnumerable<string> enumerable = localDirectories; object obj = enumerable; if (obj == null) { enumerable = value?.InstalledDirectories; obj = enumerable ?? Array.Empty<string>(); } List<string> list = ((IEnumerable<string>)obj).Where((string path) => !string.IsNullOrWhiteSpace(path)).Select(ValidateInstalledPath).Where(Directory.Exists) .Distinct<string>(StringComparer.OrdinalIgnoreCase) .ToList(); if (list.Count == 0) { throw new DirectoryNotFoundException("No installed avatar-pack folders were found to queue."); } if (value == null) { value = new BoneHub.Persistence.InstalledModRecord { ModId = modId, Name = (string.IsNullOrWhiteSpace(localName) ? "Local avatar pack" : localName.Trim()), InstalledDirectories = list, InstalledAt = DateTimeOffset.UtcNow }; } List<string> sidecarFiles = (from path in (value.Sharing?.SidecarManifestPaths ?? new List<string>()).Concat(FindRegistrationSidecars(list)) where !string.IsNullOrWhiteSpace(path) select path).Select(ValidateInstalledFile).Where(File.Exists).Distinct<string>(StringComparer.OrdinalIgnoreCase) .ToList(); List<string> barcodes = (from text in avatarBarcodes ?? Array.Empty<string>() where !string.IsNullOrWhiteSpace(text) select text.Trim()).Distinct<string>(StringComparer.OrdinalIgnoreCase).ToList(); queued = new PendingDeletionRecord { Mod = value, Directories = list, SidecarFiles = sidecarFiles, AvatarBarcodes = barcodes, WasSubscribed = wasSubscribed }; _state.PendingDeletions.Add(queued); _state.InstalledMods.Remove(modId); _state.RecentlyEquippedAvatarBarcodes.RemoveAll((string value2) => barcodes.Contains<string>(value2, StringComparer.OrdinalIgnoreCase)); if (barcodes.Contains<string>(_state.LastEquippedAvatarBarcode, StringComparer.OrdinalIgnoreCase)) { _state.LastEquippedAvatarBarcode = _state.RecentlyEquippedAvatarBarcodes.FirstOrDefault() ?? string.Empty; } await _stateStore.SaveAsync(_state, cancellationToken).ConfigureAwait(continueOnCapturedContext: false); } finally { _stateGate.Release(); } this.InstalledChanged?.Invoke(); return queued; } public async Task<PendingDeletionProcessResult> ProcessPendingDeletionsAsync(CancellationToken cancellationToken = default(CancellationToken)) { int completed = 0; List<string> problems = new List<string>(); await _stateGate.WaitAsync(cancellationToken).ConfigureAwait(continueOnCapturedContext: false); try { PendingDeletionRecord[] array = _state.PendingDeletions.ToArray(); foreach (PendingDeletionRecord pending in array) { cancellationToken.ThrowIfCancellationRequested(); string text = Path.Combine(Path.GetDirectoryName(Path.GetFullPath(_options.ModsDirectory).TrimEnd(Path.DirectorySeparatorChar)) ?? throw new InvalidOperationException("Mods directory has no parent."), ".wristhub-trash", $"{pending.Mod.ModId}-{DateTimeOffset.UtcNow:yyyyMMddHHmmss}-{Guid.NewGuid():N}"); Directory.CreateDirectory(text); List<TrashedDirectoryRecord> list = new List<TrashedDirectoryRecord>(); List<TrashedFileRecord> list2 = new List<TrashedFileRecord>(); try { for (int j = 0; j < pending.Directories.Count; j++) { string text2 = ValidateInstalledPath(pending.Directories[j]); if (Directory.Exists(text2)) { string text3 = Path.Combine(text, $"{j:D2}-{Path.GetFileName(text2)}"); Directory.Move(text2, text3); list.Add(new TrashedDirectoryRecord { OriginalPath = text2, TrashPath = text3 }); } } for (int k = 0; k < pending.SidecarFiles.Count; k++) { string text4 = ValidateInstalledFile(pending.SidecarFiles[k]); if (File.Exists(text4)) { string text5 = Path.Combine(text, $"manifest-{k:D2}-{Path.GetFileName(text4)}"); File.Move(text4, text5); list2.Add(new TrashedFileRecord { OriginalPath = text4, TrashPath = text5 }); } } if (list.Count > 0 || list2.Count > 0) { _state.TrashHistory.Add(new TrashedModRecord { Mod = pending.Mod, Directories = list, Files = list2, WasSubscribed = pending.WasSubscribed }); if (_state.TrashHistory.Count > 20) { _state.TrashHistory.RemoveRange(0, _state.TrashHistory.Count - 20); } } else if (Directory.Exists(text)) { Directory.Delete(text, recursive: false); } _state.PendingDeletions.Remove(pending); _state.InstalledMods.Remove(pending.Mod.ModId); _state.RecentlyEquippedAvatarBarcodes.RemoveAll((string value) => pending.AvatarBarcodes.Contains<string>(value, StringComparer.OrdinalIgnoreCase)); if (pending.AvatarBarcodes.Contains<string>(_state.LastEquippedAvatarBarcode, StringComparer.OrdinalIgnoreCase)) { _state.LastEquippedAvatarBarcode = _state.RecentlyEquippedAvatarBarcodes.FirstOrDefault() ?? string.Empty; } completed++; } catch (Exception ex) { for (int num = list.Count - 1; num >= 0; num--) { TrashedDirectoryRecord trashedDirectoryRecord = list[num]; if (Directory.Exists(trashedDirectoryRecord.TrashPath) && !Directory.Exists(trashedDirectoryRecord.OriginalPath)) { Directory.Move(trashedDirectoryRecord.TrashPath, trashedDirectoryRecord.OriginalPath); } } for (int num2 = list2.Count - 1; num2 >= 0; num2--) { TrashedFileRecord trashedFileRecord = list2[num2]; if (File.Exists(trashedFileRecord.TrashPath) && !File.Exists(trashedFileRecord.OriginalPath)) { File.Move(trashedFileRecord.TrashPath, trashedFileRecord.OriginalPath); } } try { if (Directory.Exists(text) && !Directory.EnumerateFileSystemEntries(text).Any()) { Directory.Delete(text); } } catch { } problems.Add(pending.Mod.Name + ": " + ex.Message); } } await _stateStore.SaveAsync(_state, cancellationToken).ConfigureAwait(continueOnCapturedContext: false); } finally { _stateGate.Release(); } if (completed > 0) { this.InstalledChanged?.Invoke(); } return new PendingDeletionProcessResult(completed, _state.PendingDeletions.Count, problems); } private async Task<TrashedModRecord> UninstallCoreAsync(long modId, string? localName, IReadOnlyList<string>? localDirectories, bool wasSubscribed, CancellationToken cancellationToken) { await _stateGate.WaitAsync(cancellationToken).ConfigureAwait(continueOnCapturedContext: false); try { BoneHub.Persistence.InstalledModRecord installed; bool wasTracked = _state.InstalledMods.TryGetValue(modId, out installed); if (!wasTracked) { List<string> list = (localDirectories ?? Array.Empty<string>()).Where((string path) => !string.IsNullOrWhiteSpace(path)).Distinct<string>(StringComparer.OrdinalIgnoreCase).ToList(); if (list.Count == 0) { throw new InvalidOperationException("WristHub could not identify this avatar pack's local folder."); } installed = new BoneHub.Persistence.InstalledModRecord { ModId = modId, Name = (string.IsNullOrWhiteSpace(localName) ? "Local avatar pack" : localName.Trim()), InstalledDirectories = list, InstalledAt = DateTimeOffset.UtcNow }; } BoneHub.Persistence.InstalledModRecord installedModRecord = installed ?? throw new InvalidOperationException("The avatar pack record is unavailable."); string text = Path.Combine(Path.GetDirectoryName(Path.GetFullPath(_options.ModsDirectory).TrimEnd(Path.DirectorySeparatorChar)) ?? throw new InvalidOperationException("Mods directory has no parent."), ".wristhub-trash", $"{modId}-{DateTimeOffset.UtcNow:yyyyMMddHHmmss}-{Guid.NewGuid():N}"); Directory.CreateDirectory(text); List<TrashedDirectoryRecord> moved = new List<TrashedDirectoryRecord>(); List<TrashedFileRecord> movedFiles = new List<TrashedFileRecord>(); TrashedModRecord stateRecord = null; try { for (int num = 0; num < installedModRecord.InstalledDirectories.Count; num++) { cancellationToken.ThrowIfCancellationRequested(); string text2 = ValidateInstalledPath(installedModRecord.InstalledDirectories[num]); if (Directory.Exists(text2)) { string text3 = Path.Combine(text, $"{num:D2}-{Path.GetFileName(text2)}"); Directory.Move(text2, text3); moved.Add(new TrashedDirectoryRecord { OriginalPath = text2, TrashPath = text3 }); } } string[] array = (installedModRecord.Sharing?.SidecarManifestPaths ?? new List<string>()).Concat(FindRegistrationSidecars(installedModRecord.InstalledDirectories)).Distinct<string>(StringComparer.OrdinalIgnoreCase).ToArray(); for (int num2 = 0; num2 < array.Length; num2++) { cancellationToken.ThrowIfCancellationRequested(); string text4 = ValidateInstalledFile(array[num2]); if (File.Exists(text4)) { string text5 = Path.Combine(text, $"manifest-{num2:D2}-{Path.GetFileName(text4)}"); File.Move(text4, text5); movedFiles.Add(new TrashedFileRecord { OriginalPath = text4, TrashPath = text5 }); } } if (moved.Count == 0) { throw new DirectoryNotFoundException("No installed avatar-pack folders were found to remove."); } TrashedModRecord record = new TrashedModRecord { Mod = installedModRecord, Directories = moved, Files = movedFiles, WasSubscribed = wasSubscribed }; stateRecord = record; _state.InstalledMods.Remove(modId); _state.TrashHistory.Add(record); if (_state.TrashHistory.Count > 20) { _state.TrashHistory.RemoveRange(0, _state.TrashHistory.Count - 20); } await _stateStore.SaveAsync(_state, cancellationToken).ConfigureAwait(continueOnCapturedContext: false); this.InstalledChanged?.Invoke(); return record; } catch { if (wasTracked) { _state.InstalledMods[modId] = installed; } else { _state.InstalledMods.Remove(modId); } if (stateRecord != null) { _state.TrashHistory.Remove(stateRecord); } for (int num3 = moved.Count - 1; num3 >= 0; num3--) { TrashedDirectoryRecord trashedDirectoryRecord = moved[num3]; if (Directory.Exists(trashedDirectoryRecord.TrashPath) && !Directory.Exists(trashedDirectoryRecord.OriginalPath)) { Directory.Move(trashedDirectoryRecord.TrashPath, trashedDirectoryRecord.OriginalPath); } } for (int num4 = movedFiles.Count - 1; num4 >= 0; num4--) { TrashedFileRecord trashedFileRecord = movedFiles[num4]; if (File.Exists(trashedFileRecord.TrashPath) && !File.Exists(trashedFileRecord.OriginalPath)) { File.Move(trashedFileRecord.TrashPath, trashedFileRecord.OriginalPath); } } throw; } } finally { _stateGate.Release(); } } public async Task<BoneHub.Persistence.InstalledModRecord> UndoLastUninstallAsync(CancellationToken cancellationToken = default(CancellationToken)) { await _stateGate.WaitAsync(cancellationToken).ConfigureAwait(continueOnCapturedContext: false); try { TrashedModRecord record = _state.TrashHistory.LastOrDefault() ?? throw new InvalidOperationException("There is no WristHub uninstall to undo."); if (_state.InstalledMods.ContainsKey(record.Mod.ModId)) { throw new InvalidOperationException("That mod is already installed again."); } List<TrashedDirectoryRecord> restored = new List<TrashedDirectoryRecord>(); List<TrashedFileRecord> restoredFiles = new List<TrashedFileRecord>(); try { foreach (TrashedDirectoryRecord directory in record.Directories) { cancellationToken.ThrowIfCancellationRequested(); string text = ValidateInstalledPath(directory.OriginalPath); string text2 = ValidateTrashPath(directory.TrashPath); if (Directory.Exists(text)) { throw new IOException("Cannot restore " + Path.GetFileName(text) + " because that folder already exists."); } if (!Directory.Exists(text2)) { throw new DirectoryNotFoundException("Trash data is missing for " + record.Mod.Name + "."); } Directory.Move(text2, text); restored.Add(new TrashedDirectoryRecord { OriginalPath = text, TrashPath = text2 }); } foreach (TrashedFileRecord file in record.Files) { cancellationToken.ThrowIfCancellationRequested(); string text3 = ValidateInstalledFile(file.OriginalPath); string text4 = ValidateTrashPath(file.TrashPath); if (File.Exists(text3)) { throw new IOException("Cannot restore " + Path.GetFileName(text3) + " because it already exists."); } if (!File.Exists(text4)) { throw new FileNotFoundException("Trash sidecar data is missing.", text4); } File.Move(text4, text3); restoredFiles.Add(new TrashedFileRecord { OriginalPath = text3, TrashPath = text4 }); } _state.InstalledMods[record.Mod.ModId] = record.Mod; _state.TrashHistory.Remove(record); await _stateStore.SaveAsync(_state, cancellationToken).ConfigureAwait(continueOnCapturedContext: false); this.InstalledChanged?.Invoke(); return record.Mod; } catch { _state.InstalledMods.Remove(record.Mod.ModId); if (!_state.TrashHistory.Contains(record)) { _state.TrashHistory.Add(record); } for (int num = restored.Count - 1; num >= 0; num--) { TrashedDirectoryRecord trashedDirectoryRecord = restored[num]; if (Directory.Exists(trashedDirectoryRecord.OriginalPath) && !Directory.Exists(trashedDirectoryRecord.TrashPath)) { Directory.Move(trashedDirectoryRecord.OriginalPath, trashedDirectoryRecord.TrashPath); } } for (int num2 = restoredFiles.Count - 1; num2 >= 0; num2--) { TrashedFileRecord trashedFileRecord = restoredFiles[num2]; if (File.Exists(trashedFileRecord.OriginalPath) && !File.Exists(trashedFileRecord.TrashPath)) { File.Move(trashedFileRecord.OriginalPath, trashedFileRecord.TrashPath); } } throw; } } finally { _stateGate.Release(); } } private void OnJobChanged(DownloadJob job) { if (job.State == DownloadState.Completed && job.Result != null) { PersistCompletedJobAsync(job, job.Result); } else { this.DownloadChanged?.Invoke(job); } } private async Task PersistCompletedJobAsync(DownloadJob job, BoneHub.Downloads.InstalledModRecord result) { await _stateGate.WaitAsync().ConfigureAwait(continueOnCapturedContext: false); try { _state.InstalledMods[result.ModId] = new BoneHub.Persistence.InstalledModRecord { ModId = result.ModId, FileId = result.FileId, Name = result.Name, Version = result.Version, Md5 = result.Md5, InstalledDirectories = result.InstalledDirectories.ToList(), Sharing = result.Sharing, InstalledAt = DateTimeOffset.UtcNow }; await _stateStore.SaveAsync(_state).ConfigureAwait(continueOnCapturedContext: false); this.DownloadChanged?.Invoke(job); this.InstalledChanged?.Invoke(); } finally { _stateGate.Release(); } } private string ValidateInstalledPath(string path) { string text = Path.GetFullPath(_options.ModsDirectory).TrimEnd(Path.DirectorySeparatorChar); string text2 = Path.GetFullPath(path).TrimEnd(Path.DirectorySeparatorChar); if (!text2.StartsWith(text + Path.DirectorySeparatorChar, StringComparison.OrdinalIgnoreCase) || string.Equals(text2, text, StringComparison.OrdinalIgnoreCase)) { throw new InvalidDataException("An installed-mod path falls outside BONELAB's Mods directory."); } return text2; } private string ValidateInstalledFile(string path) { string text = Path.GetFullPath(_options.ModsDirectory).TrimEnd(Path.DirectorySeparatorChar); string fullPath = Path.GetFullPath(path); if (!fullPath.StartsWith(text + Path.DirectorySeparatorChar, StringComparison.OrdinalIgnoreCase) || !string.Equals(Path.GetExtension(fullPath), ".manifest", StringComparison.OrdinalIgnoreCase)) { throw new InvalidDataException("A mod registration sidecar falls outside BONELAB's Mods directory."); } return fullPath; } private IReadOnlyList<string> FindRegistrationSidecars(IEnumerable<string> packageDirectories) { string[] source = packageDirectories.Select(Path.GetFullPath).ToArray(); List<string> list = new List<string>(); if (!Directory.Exists(_options.ModsDirectory)) { return list; } foreach (string item in Directory.EnumerateFiles(_options.ModsDirectory, "*.manifest", SearchOption.TopDirectoryOnly)) { try { using JsonDocument jsonDocument = JsonDocument.Parse(File.ReadAllText(item)); JsonElement rootElement = jsonDocument.RootElement; string propertyName = rootElement.GetProperty("root").GetProperty("ref").GetString() ?? "1"; string text = rootElement.GetProperty("objects").GetProperty(propertyName).GetProperty("palletPath") .GetString(); if (!string.IsNullOrWhiteSpace(text)) { string fullPallet = Path.GetFullPath(text); if (source.Any((string package) => fullPallet.StartsWith(package.TrimEnd(Path.DirectorySeparatorChar) + Path.DirectorySeparatorChar, StringComparison.OrdinalIgnoreCase))) { list.Add(item); } } } catch { } } return list; } private string ValidateTrashPath(string path) { string? path2 = Path.GetDirectoryName(Path.GetFullPath(_options.ModsDirectory).TrimEnd(Path.DirectorySeparatorChar)) ?? throw new InvalidOperationException("Mods directory has no parent."); string text = Path.GetFullPath(Path.Combine(path2, ".wristhub-trash")).TrimEnd(Path.DirectorySeparatorChar); string text2 = Path.GetFullPath(Path.Combine(path2, ".bonehub-trash")).TrimEnd(Path.DirectorySeparatorChar); string text3 = Path.GetFullPath(path).TrimEnd(Path.DirectorySeparatorChar); bool num = text3.StartsWith(text + Path.DirectorySeparatorChar, StringComparison.OrdinalIgnoreCase) && !string.Equals(text3, text, StringComparison.OrdinalIgnoreCase); bool flag = text3.StartsWith(text2 + Path.DirectorySeparatorChar, StringComparison.OrdinalIgnoreCase) && !string.Equals(text3, text2, StringComparison.OrdinalIgnoreCase); if (!num && !flag) { throw new InvalidDataException("A restore path falls outside WristHub Trash."); } return text3; } public void Dispose() { _downloads.JobChanged -= OnJobChanged; _downloads.Dispose(); (_client as IDisposable)?.Dispose(); _stateGate.Dispose(); } } } namespace BoneHub.Persistence { public sealed class BoneHubState { public HashSet<long> FavoriteModIds { get; init; } = new HashSet<long>(); public List<long> RecentlyViewedModIds { get; init; } = new List<long>(); public List<string> RecentlyEquippedAvatarBarcodes { get; init; } = new List<string>(); public string LastEquippedAvatarBarcode { get; set; } = string.Empty; public Dictionary<long, InstalledModRecord> InstalledMods { get; init; } = new Dictionary<long, InstalledModRecord>(); public BoneHubPreferences Preferences { get; init; } = new BoneHubPreferences(); public List<TrashedModRecord> TrashHistory { get; init; } = new List<TrashedModRecord>(); public List<PendingDeletionRecord> PendingDeletions { get; init; } = new List<PendingDeletionRecord>(); } public sealed class BoneHubPreferences { public int MotionPreferenceSchemaVersion { get; set; } public BoneHubComfortPreset ComfortPreset { get; set; } public bool WatchEnabled { get; set; } = true; public bool RaiseToOpen { get; set; } = true; public bool DoubleTapToOpen { get; set; } = true; public bool UseLeftWrist { get; set; } = true; public float WatchScale { get; set; } = 1f; public float WatchBodyScale { get; set; } = 1f; public WristWatchStyle WatchStyle { get; set; } public WristWatchAttachment WatchAttachment { get; set; } public WristClockOrientation ClockOrientation { get; set; } public float DwellSeconds { get; set; } = 1f; public float OutwardOffset { get; set; } = 0.029f; public float FingerOffset { get; set; } = -0.018f; public float WatchForearmOffset { get; set; } public float WatchSleepLift { get; set; } public bool ReducedMotion { get; set; } public bool HologramEffects { get; set; } = true; public bool HapticsEnabled { get; set; } = true; public WristNotificationFocusMode NotificationFocus { get; set; } public bool CallsBreakQuiet { get; set; } = true; public bool PrivateNotificationReveal { get; set; } = true; public bool SoundsEnabled { get; set; } = true; public float CarouselDistance { get; set; } = 0.62f; public float HologramScale { get; set; } = 1f; public float EquipZoneScale { get; set; } = 1f; public float HologramBrightness { get; set; } = 1f; public WristHubTheme HologramTheme { get; set; } public bool Use24HourClock { get; set; } public BrowserStartupMode AvatarBrowserStartupMode { get; set; } = BrowserStartupMode.AllInstalled; public AvatarBrowseScope LastAvatarBrowseScope { get; set; } public List<CompassCalibrationRecord> CompassCalibrations { get; init; } = new List<CompassCalibrationRecord>(); public MiniMapRange MiniMapRange { get; set; } = MiniMapRange.ThirtyMeters; public MiniMapOrientation MiniMapOrientation { get; set; } public List<string> FavoriteSpawnableBarcodes { get; init; } = new List<string>(); public List<string> RecentSpawnableBarcodes { get; init; } = new List<string>(); public int SpawnerStateSchemaVersion { get; set; } public string LastSpawnableMode { get; set; } = string.Empty; public string LastSpawnableGroup { get; set; } = string.Empty; public List<string> LegacySpawnableFavoritesBackup { get; init; } = new List<string>(); public List<string> LegacySpawnableRecentsBackup { get; init; } = new List<string>(); public List<string> PinnedWristAppIds { get; init; } = new List<string>(); public List<string> OpenedWristAppIds { get; init; } = new List<string>(); public WristAppLibraryTab WristAppLibraryTab { get; set; } public string WristAppSearchQuery { get; set; } = string.Empty; } public enum WristAppLibraryTab { Installed, Store, Updates } public enum WristHubTheme { Cyan, Graphite, Crimson, Emerald, Violet, Amber, Jolly } public enum WristWatchStyle { ClassicCircle, MetalSquare, SlimSquare, ProjectionCore, Invisible } public enum WristWatchAttachment { Adaptive, WristLocked } public enum WristClockOrientation { ArmFixed, HeadReadable } public sealed class CompassCalibrationRecord { public string SceneKey { get; init; } = string.Empty; public float NorthYawDegrees { get; set; } public DateTimeOffset LastUsedUtc { get; set; } = DateTimeOffset.UtcNow; } public enum BoneHubComfortPreset { Default, Seated, LargeTargets, ReducedMotion } public sealed class InstalledModRecord { public long ModId { get; init; } public long FileId { get; init; } public string Name { get; init; } = string.Empty; public string Version { get; init; } = string.Empty; public string Md5 { get; init; } = string.Empty; public List<string> InstalledDirectories { get; init; } = new List<string>(); public ModIoSharingMetadata? Sharing { get; set; } public DateTimeOffset InstalledAt { get; init; } } public sealed class TrashedModRecord { public Guid Id { get; init; } = Guid.NewGuid(); public InstalledModRecord Mod { get; init; } = new InstalledModRecord(); public List<TrashedDirectoryRecord> Directories { get; init; } = new List<TrashedDirectoryRecord>(); public List<TrashedFileRecord> Files { get; init; } = new List<TrashedFileRecord>(); public bool WasSubscribed { get; init; } public DateTimeOffset TrashedAt { get; init; } = DateTimeOffset.UtcNow; } public sealed class TrashedFileRecord { public string OriginalPath { get; init; } = string.Empty; public string TrashPath { get; init; } = string.Empty; } public sealed class TrashedDirectoryRecord { public string OriginalPath { get; init; } = string.Empty; public string TrashPath { get; init; } = string.Empty; } public sealed class PendingDeletionRecord { public InstalledModRecord Mod { get; init; } = new InstalledModRecord(); public List<string> Directories { get; init; } = new List<string>(); public List<string> SidecarFiles { get; init; } = new List<string>(); public List<string> AvatarBarcodes { get; init; } = new List<string>(); public bool WasSubscribed { get; init; } public DateTimeOffset QueuedAt { get; init; } = DateTimeOffset.UtcNow; } public sealed record PendingDeletionProcessResult(int Completed, int Remaining, IReadOnlyList<string> Problems); public sealed class JsonFileStore<T> where T : class, new() { private readonly string _path; private readonly SemaphoreSlim _gate = new SemaphoreSlim(1, 1); private readonly JsonSerializerOptions _json = new JsonSerializerOptions { WriteIndented = true, PropertyNameCaseInsensitive = true }; public JsonFileStore(string path) { _path = Path.GetFullPath(path); } public async Task<T> LoadAsync(CancellationToken cancellationToken = default(CancellationToken)) { await _gate.WaitAsync(cancellationToken).ConfigureAwait(continueOnCapturedContext: false); try { if (!File.Exists(_path)) { return new T(); } T result; await using (FileStream stream = File.OpenRead(_path)) { result = (await JsonSerializer.DeserializeAsync<T>(stream, _json, cancellationToken).ConfigureAwait(continueOnCapturedContext: false)) ?? new T(); } return result; } catch (JsonException) { string destFileName = _path + $".corrupt-{DateTimeOffset.UtcNow:yyyyMMddHHmmss}"; File.Move(_path, destFileName, overwrite: false); return new T(); } finally { _gate.Release(); } } public async Task SaveAsync(T value, CancellationToken cancellationToken = default(CancellationToken)) { await _gate.WaitAsync(cancellationToken).ConfigureAwait(continueOnCapturedContext: false); try { Directory.CreateDirectory(Path.GetDirectoryName(_path)); string temporary = _path + ".tmp"; await using (FileStream stream = new FileStream(temporary, FileMode.Create, FileAccess.Write, FileShare.None)) { await JsonSerializer.SerializeAsync(stream, value, _json, cancellationToken).ConfigureAwait(continueOnCapturedContext: false); await stream.FlushAsync(cancellationToken).ConfigureAwait(continueOnCapturedContext: false); } File.Move(temporary, _path, overwrite: true); } finally { _gate.Release(); } } } public sealed class SearchCache { public sealed class CachedSearch { public DateTimeOffset SavedAt { get; init; } public ModIoPage<ModIoMod> Page { get; init; } = new ModIoPage<ModIoMod>(); } private readonly string _directory; public SearchCache(string directory) { _directory = Path.GetFullPath(directory); } public async Task<ModIoPage<ModIoMod>?> ReadAsync(string key, TimeSpan maximumAge, CancellationToken cancellationToken = default(CancellationToken)) { CachedSearch cachedSearch = await new JsonFileStore<CachedSearch>(PathFor(key)).LoadAsync(cancellationToken).ConfigureAwait(continueOnCapturedContext: false); return (cachedSearch.SavedAt != default(DateTimeOffset) && DateTimeOffset.UtcNow - cachedSearch.SavedAt <= maximumAge) ? cachedSearch.Page : null; } public Task WriteAsync(string key, ModIoPage<ModIoMod> page, CancellationToken cancellationToken = default(CancellationToken)) { return new JsonFileStore<CachedSearch>(PathFor(key)).SaveAsync(new CachedSearch { SavedAt = DateTimeOffset.UtcNow, Page = page }, cancellationToken); } private string PathFor(string key) { Directory.CreateDirectory(_directory); byte[] inArray = SHA256.HashData(Encoding.UTF8.GetBytes(key)); return Path.Combine(_directory, Convert.ToHexString(inArray).ToLowerInvariant() + ".json"); } } } namespace BoneHub.Models { public sealed class ModIoPage<T> { [JsonPropertyName("data")] public List<T> Data { get; init; } = new List<T>(); [JsonPropertyName("result_count")] public int ResultCount { get; init; } [JsonPropertyName("result_offset")] public int ResultOffset { get; init; } [JsonPropertyName("result_limit")] public int ResultLimit { get; init; } [JsonPropertyName("result_total")] public int ResultTotal { get; init; } } public sealed class ModIoMod { [JsonPropertyName("id")] public long Id { get; init; } [JsonPropertyName("name")] public string Name { get; init; } = string.Empty; [JsonPropertyName("name_id")] public string NameId { get; init; } = string.Empty; [JsonPropertyName("summary")] public string Summary { get; init; } = string.Empty; [JsonPropertyName("description_plaintext")] public string Description { get; init; } = string.Empty; [JsonPropertyName("profile_url")] public string ProfileUrl { get; init; } = string.Empty; [JsonPropertyName("date_updated")] public long DateUpdated { get; init; } [JsonPropertyName("submitted_by")] public ModIoUser Author { get; init; } = new ModIoUser(); [JsonPropertyName("logo")] public ModIoLogo Logo { get; init; } = new ModIoLogo(); [JsonPropertyName("modfile")] public ModIoFile? Modfile { get; init; } [JsonPropertyName("stats")] public ModIoStats Stats { get; init; } = new ModIoStats(); [JsonPropertyName("tags")] public List<ModIoTag> Tags { get; init; } = new List<ModIoTag>(); [JsonPropertyName("maturity_option")] public int MaturityOption { get; init; } public bool HasTag(string value) { return Tags.Any((ModIoTag tag) => string.Equals(tag.Name, value, StringComparison.OrdinalIgnoreCase)); } } public sealed class ModIoUser { [JsonPropertyName("username")] public string Username { get; init; } = string.Empty; [JsonPropertyName("display_name_portal")] public string? DisplayNamePortal { get; init; } public string DisplayName { get { if (!string.IsNullOrWhiteSpace(DisplayNamePortal)) { return DisplayNamePortal; } return Username; } } } public sealed class ModIoLogo { [JsonPropertyName("original")] public string Original { get; init; } = string.Empty; [JsonPropertyName("thumb_320x180")] public string Thumbnail { get; init; } = string.Empty; } public sealed class ModIoStats { [JsonPropertyName("downloads_total")] public long DownloadsTotal { get; init; } [JsonPropertyName("subscribers_total")] public long SubscribersTotal { get; init; } [JsonPropertyName("ratings_weighted_aggregate")] public double Rating { get; init; } } public sealed class ModIoTag { [JsonPropertyName("name")] public string Name { get; init; } = string.Empty; } public sealed class ModIoFile { [JsonPropertyName("id")] public long Id { get; init; } [JsonPropertyName("mod_id")] public long ModId { get; init; } [JsonPropertyName("filename")] public string Filename { get; init; } = string.Empty; [JsonPropertyName("version")] public string Version { get; init; } = string.Empty; [JsonPropertyName("date_added")] public long DateAdded { get; init; } [JsonPropertyName("filesize")] public long FileSize { get; init; } [JsonPropertyName("filesize_uncompressed")] public long UncompressedSize { get; init; } [JsonPropertyName("virus_status")] public int VirusStatus { get; init; } [JsonPropertyName("virus_positive")] public int VirusPositive { get; init; } [JsonPropertyName("filehash")] public ModIoFileHash Hash { get; init; } = new ModIoFileHash(); [JsonPropertyName("download")] public ModIoDownload Download { get; init; } = new ModIoDownload(); [JsonPropertyName("platforms")] public List<ModIoPlatform> Platforms { get; init; } = new List<ModIoPlatform>(); } public sealed class ModIoFileHash { [JsonPropertyName("md5")] public string Md5 { get; init; } = string.Empty; } public sealed class ModIoDownload { [JsonPropertyName("binary_url")] public string BinaryUrl { get; init; } = string.Empty; [JsonPropertyName("date_expires")] public long DateExpires { get; init; } } public sealed class ModIoPlatform { [JsonPropertyName("platform")] public string Platform { get; init; } = string.Empty; [JsonPropertyName("status")] public int Status { get; init; } } public sealed class ModIoDependency { [JsonPropertyName("mod_id")] public long ModId { get; init; } [JsonPropertyName("date_added")] public long DateAdded { get; init; } } public enum ModSort { Trending, Newest, MostDownloaded, RecentlyUpdated } public sealed record ModSearchRequest(string Query, string? Tag = null, string? Creator = null, ModSort Sort = ModSort.Trending, int Offset = 0, int Limit = 20); public sealed record ModUpdateInfo(ModIoMod Mod, bool UpdateAvailable, long InstalledFileId, long LatestFileId); public sealed record ModInstallPlan(ModIoMod Root, IReadOnlyList<ModIoMod> OrderedMods, IReadOnlyList<long> AlreadyInstalledIds); public sealed record InstalledModHealth(bool Healthy, IReadOnlyList<string> Problems); } namespace BoneHub.Interaction { public enum AvatarBrowseScope { AllInstalled, Groups } public enum BrowserStartupMode { RememberLast, AllInstalled, Groups } public static class AvatarBrowseStartup { public static AvatarBrowseScope Resolve(BrowserStartupMode mode, AvatarBrowseScope lastScope) { return mode switch { BrowserStartupMode.RememberLast => Enum.IsDefined(lastScope) ? lastScope : AvatarBrowseScope.AllInstalled, BrowserStartupMode.Groups => AvatarBrowseScope.Groups, _ => AvatarBrowseScope.AllInstalled, }; } } public enum AvatarBrowserViewState { PushPrompt, Hub, Library, Pack, Downloads, Compass, CodeMods, MiniMap, Portals, Utilities, MonoDirector, ModStore, InstalledApps, Friends, PortalLevels, FusionServers, Spawnables, PackOptions, Options, Keyboard, Searching, Result, Downloading, PreparingPreview, Error } public enum AvatarPackSort { RecentlyUsed, Name, Creator } public sealed record AvatarPackEntry(long ModId, string Name, string Creator, string ThumbnailUrl, IReadOnlyList<AvatarEntry> Avatars, bool Installed, AvatarDownloadState DownloadState = AvatarDownloadState.None, double DownloadProgress = 0.0, string? Status = null) { public string Identity => "pack:" + ModId; public int AvatarCount => Avatars.Count((AvatarEntry entry) => entry.Installed && !string.IsNullOrWhiteSpace(entry.Barcode)); } public static class AvatarPackLibrary { public static IReadOnlyList<AvatarPackEntry> Group(IEnumerable<AvatarEntry> entries, IEnumerable<string>? recentBarcodes = null, AvatarPackSort sort = AvatarPackSort.RecentlyUsed) { Dictionary<string, int> recent = (recentBarcodes ?? Array.Empty<string>()).Select((string barcode, int index) => (barcode: barcode, index: index)).ToDictionary<(string, int), string, int>(((string barcode, int index) item) => item.barcode, ((string barcode, int index) item) => item.index, StringComparer.OrdinalIgnoreCase); IEnumerable<AvatarPackEntry> source = (from entry in entries group entry by entry.ModId).Select(delegate(IGrouping<long, AvatarEntry> group) { AvatarEntry[] array = group.OrderBy<AvatarEntry, string>((AvatarEntry item) => item.AvatarName, StringComparer.OrdinalIgnoreCase).ToArray(); AvatarEntry avatarEntry = array.OrderByDescending((AvatarEntry item) => item.Installed).First(); AvatarDownloadState downloadState = array.Select((AvatarEntry item) => item.DownloadState).OrderByDescending(DownloadRank).FirstOrDefault(); return new AvatarPackEntry(group.Key, avatarEntry.ModName, avatarEntry.Creator, avatarEntry.ThumbnailUrl, array, array.Any((AvatarEntry item) => item.Installed), downloadState, array.Max((AvatarEntry item) => item.DownloadProgress), array.Select((AvatarEntry item) => item.Status).FirstOrDefault((string value) => !string.IsNullOrWhiteSpace(value))); }); return sort switch { AvatarPackSort.Name => source.OrderBy<AvatarPackEntry, string>((AvatarPackEntry pack) => pack.Name, StringComparer.OrdinalIgnoreCase).ToArray(), AvatarPackSort.Creator => source.OrderBy<AvatarPackEntry, string>((AvatarPackEntry pack) => pack.Creator, StringComparer.OrdinalIgnoreCase).ThenBy<AvatarPackEntry, string>((AvatarPackEntry pack) => pack.Name, StringComparer.OrdinalIgnoreCase).ToArray(), _ => source.OrderBy((AvatarPackEntry pack) => RecentIndex(pack, recent)).ThenBy<AvatarPackEntry, string>((AvatarPackEntry pack) => pack.Name, StringComparer.OrdinalIgnoreCase).ToArray(), }; } private static int RecentIndex(AvatarPackEntry pack, IReadOnlyDictionary<string, int> recent) { int[] array = (from item in pack.Avatars where recent.ContainsKey(item.Barcode) select recent[item.Barcode]).ToArray(); if (array.Length != 0) { return array.Min(); } return int.MaxValue; } private static int DownloadRank(AvatarDownloadState state) { return state switch { AvatarDownloadState.Failed => 7, AvatarDownloadState.Installing => 6, AvatarDownloadState.Downloading => 5, AvatarDownloadState.Queued => 4, AvatarDownloadState.Cancelled => 3, AvatarDownloadState.Installed => 2, _ => 1, }; } } public sealed class FourRowPageCursor { public const int RowsPerPage = 4; public int Page { get; private set; } public int Count { get; private set; } public int PageCount => Math.Max(1, (Count + 4 - 1) / 4); public string PositionText => $"{Page + 1} / {PageCount}"; public int Reset(int count) { Count = Math.Max(0, count); Page = 0; return Page; } public int Keep(int count) { Count = Math.Max(0, count); Page = Math.Clamp(Page, 0, PageCount - 1); return Page; } public int Move(int direction) { if (PageCount <= 1 || direction == 0) { return Page; } Page = (Page + Math.Sign(direction) + PageCount) % PageCount; return Page; } } public sealed class FocusedResultCursor { public int Index { get; private set; } public int Count { get; private set; } public string PositionText { get { if (Count != 0) { return $"{Index + 1} / {Count}"; } return "0 / 0"; } } public bool CanMovePrevious => Count > 1; public bool CanMoveNext => Count > 1; public int Reset(int count) { Count = Math.Max(0, count); Index = 0; return Index; } public int Keep(int count, int preferredIndex) { Count = Math.Max(0, count); Index = ((Count != 0) ? Math.Clamp(preferredIndex, 0, Count - 1) : 0); return Index; } public int Move(int direction) { if (Count <= 1 || direction == 0) { return Index; } Index = (Index + Math.Sign(direction) + Count) % Count; return Index; } } public readonly record struct TabletRect(float X, float Y, float Width, float Height) { public float Left => X - Width * 0.5f; public float Right => X + Width * 0.5f; public float Bottom => Y - Height * 0.5f; public float Top => Y + Height * 0.5f; public bool IsInside(TabletRect parent) { if (Left >= parent.Left && Right <= parent.Right && Bottom >= parent.Bottom) { return Top <= parent.Top; } return false; } public bool Overlaps(TabletRect other) { if (Left < other.Right && Right > other.Left && Bottom < other.Top) { return Top > other.Bottom; } return false; } } public static class AvatarTabletLayout { public static readonly TabletRect Tablet = new TabletRect(0f, 0f, 340f, 210f); public static readonly TabletRect Search = new TabletRect(-34f, 82f, 150f, 30f); public static readonly TabletRect Close = new TabletRect(148f, 82f, 30f, 30f); public static readonly TabletRect Artwork = new TabletRect(-66f, -4f, 124f, 128f); public static readonly TabletRect Details = new TabletRect(65f, 3f, 126f, 112f); public static readonly TabletRect Previous = new TabletRect(-151f, 14f, 26f, 54f); public static readonly TabletRect Next = new TabletRect(151f, 14f, 26f, 54f); public static readonly TabletRect Action = new TabletRect(65f, -69f, 126f, 30f); public static readonly TabletRect Trash = new TabletRect(-66f, -91f, 116f, 18f); public static readonly TabletRect Progress = new TabletRect(65f, -91f, 126f, 14f); public static IReadOnlyList<TabletRect> ContentRects => new TabletRect[9] { Search, Close, Artwork, Details, Previous, Next, Action, Trash, Progress }; public static TabletRect PhysicalTouchRect(TabletRect rect) { return new TabletRect(rect.X, rect.Y, rect.Width * 1.1f, rect.Height * 1.2f); } } public static class ArcDashboardLayout { public static readonly TabletRect Presentation = new TabletRect(0f, 0f, 300f, 190f); public static readonly TabletRect Stage = new TabletRect(0f, 7f, 170f, 124f); public static readonly TabletRect LeftWing = new TabletRect(-122f, 16f, 50f, 146f); public static readonly TabletRect RightWing = new TabletRect(122f, 16f, 50f, 146f); public static readonly TabletRect Back = new TabletRect(-122f, 72f, 42f, 22f); public static readonly TabletRect Search = new TabletRect(-122f, 44f, 46f, 24f); public static readonly TabletRect Previous = new TabletRect(-122f, 7f, 42f, 42f); public static readonly TabletRect Scope = new TabletRect(-122f, -35f, 46f, 25f); public static readonly TabletRect Close = new TabletRect(122f, 72f, 30f, 26f); public static readonly TabletRect Pack = new TabletRect(122f, 44f, 46f, 24f); public static readonly TabletRect Next = new TabletRect(122f, 7f, 42f, 42f); public static readonly TabletRect Options = new TabletRect(122f, -35f, 46f, 25f); public static readonly TabletRect Name = new TabletRect(0f, 82f, 166f, 20f); public static readonly TabletRect Creator = new TabletRect(-45f, 65f, 78f, 14f); public static readonly TabletRect PackName = new TabletRect(45f, 65f, 92f, 14f); public static readonly TabletRect Height = new TabletRect(-54f, -53f, 94f, 16f); public static readonly TabletRect Position = new TabletRect(0f, -53f, 48f, 16f); public static readonly TabletRect Status = new TabletRect(54f, -53f, 94f, 18f); public static readonly TabletRect Action = new TabletRect(0f, -80f, 116f, 28f); public static readonly TabletRect Progress = Action; public static IReadOnlyList<TabletRect> TouchControls => new TabletRect[9] { Back, Search, Previous, Scope, Close, Pack, Next, Options, Action }; } public static class WristOsHomeHeaderLayout { public static readonly TabletRect Download = new TabletRect(-86f, 70f, 28f, 23f); public static readonly TabletRect Wordmark = new TabletRect(-26f, 70f, 86f, 20f); public static readonly TabletRect OsMark = new TabletRect(32f, 70f, 28f, 20f); public static readonly TabletRect AccentRail = new TabletRect(-19f, 56f, 120f, 1.5f); public static readonly TabletRect Settings = new TabletRect(66f, 70f, 28f, 23f); public static readonly TabletRect Close = new TabletRect(106f, 70f, 24f, 24f); public static IReadOnlyList<TabletRect> InteractiveControls => new TabletRect[3] { Download, Settings, Close }; } public static class WristSettingsLayout { public static readonly TabletRect Page = new TabletRect(0f, 0f, 326f, 226f); public static readonly TabletRect Header = new TabletRect(0f, 95f, 306f, 34f); public static readonly TabletRect LibraryCard = new TabletRect(0f, 47f, 298f, 50f); public static readonly TabletRect StyleCard = new TabletRect(0f, -6f, 298f, 50f); public static readonly TabletRect DeviceCard = new TabletRect(0f, -64f, 298f, 58f); public static readonly TabletRect StartingView = new TabletRect(-62f, 50f, 170f, 15f); public static readonly TabletRect StartingAction = new TabletRect(112f, 50f, 62f, 18f); public static readonly TabletRect Sort = new TabletRect(-62f, 31f, 170f, 15f); public static readonly TabletRect SortAction = new TabletRect(112f, 31f, 62f, 18f); public static readonly TabletRect Theme = new TabletRect(-62f, -3f, 170f, 15f); public static readonly TabletRect ThemeAction = new TabletRect(112f, -3f, 62f, 18f); public static readonly TabletRect Clock = new TabletRect(-62f, -22f, 170f, 15f); public static readonly TabletRect ClockAction = new TabletRect(112f, -22f, 62f, 18f); public static readonly TabletRect Watch = new TabletRect(-62f, -58f, 170f, 15f); public static readonly TabletRect WatchAction = new TabletRect(112f, -58f, 62f, 19f); public static readonly TabletRect Motion = new TabletRect(-62f, -80f, 170f, 15f); public static readonly TabletRect MotionAction = new TabletRect(112f, -80f, 62f, 19f); public static IReadOnlyList<TabletRect> Content => new TabletRect[15] { LibraryCard, StyleCard, DeviceCard, StartingView, StartingAction, Sort, SortAction, Theme, ThemeAction, Clock, ClockAction, Watch, WatchAction, Motion, MotionAction }; } public static class AvatarPackTabletLayout { public static readonly IReadOnlyList<TabletRect> HeaderControls = new TabletRect[5] { new TabletRect(-145f, 83f, 42f, 28f), new TabletRect(-90f, 83f, 64f, 28f), new TabletRect(-34f, 83f, 46f, 28f), new TabletRect(25f, 83f, 68f, 28f), new TabletRect(148f, 83f, 26f, 28f) }; public static readonly IReadOnlyList<TabletRect> Rows = (from index in Enumerable.Range(0, 4) select new TabletRect(-36f, 50f - (float)index * 34f, 228f, 30f)).ToArray(); public static readonly IReadOnlyList<TabletRect> DeleteRows = (from index in Enumerable.Range(0, 4) select new TabletRect(125f, 50f - (float)index * 34f, 52f, 30f)).ToArray(); public static readonly TabletRect Status = new TabletRect(0f, -72f, 250f, 10f); public static readonly IReadOnlyList<TabletRect> FooterControls = new TabletRect[3] { new TabletRect(-135f, -91f, 50f, 26f), new TabletRect(0f, -91f, 90f, 24f), new TabletRect(135f, -91f, 50f, 26f) }; public static IReadOnlyList<TabletRect> AllControls => HeaderControls.Concat(Rows).Concat(DeleteRows).Append(Status) .Concat(FooterControls) .ToArray(); } public sealed class PresentationGeneration { public int Value { get; private set; } public int Next() { return ++Value; } public bool IsCurrent(int generation) { return generation == Value; } } public sealed class SurfaceInputRearmGate { private readonly double _clearSeconds; private double _clearSince = -1.0; public bool Required { get; private set; } public SurfaceInputRearmGate(double clearSeconds = 0.1) { _clearSeconds = Math.Max(0.0, clearSeconds); } public void Require() { Required = true; _clearSince = -1.0; } public bool CanInteract(bool touchingAnyActiveControl, double nowSeconds) { if (!Required) { return true; } if (touchingAnyActiveControl) { _clearSince = -1.0; return false; } if (_clearSince < 0.0) { _clearSince = nowSeconds; return _clearSeconds <= 0.0; } if (nowSeconds - _clearSince < _clearSeconds) { return false; } Required = false; _clearSince = -1.0; return true; } } public sealed class AvatarSearchInputSession { public int Generation { get; private set; } public bool IsOpen { get; private set; } public string InitialValue { get; private set; } = string.Empty; public int Open(string? initialValue) { InitialValue = initialValue?.Trim() ?? string.Empty; IsOpen = true; return ++Generation; } public bool TrySubmit(int generation, string? value, out string query) { query = value?.Trim() ?? string.Empty; if (!IsOpen || generation != Generation) { return false; } IsOpen = false; return true; } public bool Cancel(int generation) { if (!IsOpen || generation != Generation) { return false; } IsOpen = false; return true; } } public sealed class TabletKeyboardBuffer { public const int MaximumLength = 64; public string Value { get; private set; } = string.Empty; public bool Shifted { get; private set; } public void Set(string? value) { Value = (((value ?? string.Empty).Length <= 64) ? (value ?? string.Empty) : value.Substring(0, 64)); } public bool Append(char character) { if (Value.Length >= 64 || char.IsControl(character)) { return false; } Value += (Shifted ? char.ToUpperInvariant(character) : char.ToLowerInvariant(character)); return true; } public bool AppendSpace() { if (Value.Length >= 64 || Value.EndsWith(' ')) { return false; } Value += " "; return true; } public bool Backspace() { if (Value.Length == 0) { return false; } string value = Value; Value = value.Substring(0, value.Length - 1); return true; } public void Clear() { Value = string.Empty; } public void ToggleShift() { Shifted = !Shifted; } public string VisibleValue(int maximumCharacters = 36) { int num = Math.Clamp(maximumCharacters, 4, 64); if (Value.Length > num) { string value = Value; int num2 = num - 1; int length = value.Length; int num3 = length - num2; return "…" + value.Substring(num3, length - num3); } return Value; } } public static class PreviewDragMath { public static bool IsInside(Vector3 localPoint, Vector3 halfExtents) { if (MathF.Abs(localPoint.X) <= MathF.Max(0f, halfExtents.X) && MathF.Abs(localPoint.Y) <= MathF.Max(0f, halfExtents.Y)) { return MathF.Abs(localPoint.Z) <= MathF.Max(0f, halfExtents.Z); } return false; } public static bool PinchActive(float thumbIndexDistance, bool alreadyDragging, float beginDistance = 0.03f, float releaseDistance = 0.045f) { if (float.IsFinite(thumbIndexDistance)) { return thumbIndexDistance <= (alreadyDragging ? MathF.Max(beginDistance, releaseDistance) : beginDistance); } return false; } public static bool GripActive(float gripForce, bool alreadyDragging, float beginForce = 0.62f, float releaseForce = 0.28f) { if (float.IsFinite(gripForce)) { return gripForce >= (alreadyDragging ? MathF.Min(beginForce, releaseForce) : beginForce); } return false; } public static bool IsChestRelease(Vector3 previewPosition, Vector3 chestPosition, float radius) { if (radius > 0f) { return Vector3.Distance(previewPosition, chestPosition) <= radius; } return false; } public static Vector3 LimitAnchorOffset(Vector3 offset, float maximumDistance) { if (!float.IsFinite(offset.X) || !float.IsFinite(offset.Y) || !float.IsFinite(offset.Z)) { return Vector3.Zero; } float num = Math.Clamp(maximumDistance, 0.01f, 0.2f); float num2 = offset.LengthSquared(); if (!float.IsFinite(num2) || num2 <= 0f) { return Vector3.Zero; } if (num2 <= num * num) { return offset; } return Vector3.Normalize(offset) * num; } } public static class ChestEquipGuideMath { public static bool ResolveLocked(float distance, bool wasLocked, float radius, float releaseMultiplier = 1.12f) { if (!float.IsFinite(distance) || !float.IsFinite(radius) || radius <= 0f) { return false; } float num = (wasLocked ? (radius * Math.Clamp(releaseMultiplier, 1f, 1.5f)) : radius); return distance <= num; } public static float Proximity(float distance, float radius, float visibleRadiusMultiplier = 2.75f) { if (!float.IsFinite(distance) || !float.IsFinite(radius) || radius <= 0f) { return 0f; } float num = radius * Math.Clamp(visibleRadiusMultiplier, 1.1f, 6f); if (distance <= radius) { return 1f; } return Math.Clamp(1f - (distance - radius) / (num - radius), 0f, 1f); } } public sealed class LookAwayDismissal { public double AwaySeconds { get; private set; } public bool Update(bool observed, double deltaSeconds, double delaySeconds = 0.35) { if (observed) { AwaySeconds = 0.0; return false; } AwaySeconds += Math.Clamp(deltaSeconds, 0.0, 0.25); return AwaySeconds >= Math.Max(0.05, delaySeconds); } public void Reset() { AwaySeconds = 0.0; } } public readonly record struct AvatarPreviewMetrics(string Barcode, float GameplayHeightMeters, bool Valid) { public string DisplayText { get { if (!Valid || !float.IsFinite(GameplayHeightMeters) || !(GameplayHeightMeters > 0f)) { return "Height unavailable"; } if (!(GameplayHeightMeters < 10f)) { if (GameplayHeightMeters < 100f) { return $"{GameplayHeightMeters:0.0} m"; } return $"{GameplayHeightMeters:#,##0} m"; } return $"{GameplayHeightMeters:0.00} m"; } } public string HeightDisplayText { get { if (!Valid) { return "Avatar height unavailable"; } return "Avatar height " + DisplayText; } } } public enum AvatarPreviewVisualSource { Unavailable, FullColor, NativePreviewMesh } public enum AvatarPreviewHeightSource { Unavailable, AvatarMetadata, RendererBounds, PreviewMeshBounds } public readonly record struct AvatarResolvedHeight(float Meters, AvatarPreviewHeightSource Source) { public bool Valid => AvatarPreviewMath.IsDisplayableGameplayHeight(Meters); } public readonly record struct AvatarPreviewRouteMetadata(AvatarPreviewVisualSource VisualSource, AvatarPreviewHeightSource HeightSource, bool InteractionReady); public readonly record struct AvatarPreviewPlacement(float UniformScale, Vector3 Offset) { public bool Valid { get { if (float.IsFinite(UniformScale) && UniformScale > 0f && float.IsFinite(Offset.X) && float.IsFinite(Offset.Y)) { return float.IsFinite(Offset.Z); } return false; } } } public static class AvatarPreviewMath { public const float DisplayHeightMeters = 0.16f; public const float DisplayWidthMeters = 0.135f; public const float DisplayDepthMeters = 0.1f; public const float FrontFacingYawDegrees = 180f; public static float NormalizeScale(float visualHeightMeters, float displayHeightMeters = 0.16f) { if (!IsUsableHeight(visualHeightMeters)) { return 1f; } return Math.Clamp(displayHeightMeters, 0.05f, 0.3f) / visualHeightMeters; } public static bool IsUsableHeight(float value) { if (float.IsFinite(value)) { if (value >= 0.05f) { return value <= 100f; } return false; } return false; } public static bool IsDisplayableGameplayHeight(float value) { if (float.IsFinite(value)) { if (value >= 0.01f) { return value <= 10000f; } return false; } return false; } public static float NormalizeBoundsScale(Vector3 visualSize) { if (!float.IsFinite(visualSize.Y) || visualSize.Y <= 0.0001f) { return 1f; } return 0.16f / visualSize.Y; } public static AvatarPreviewPlacement FitBounds(Vector3 visualCenter, Vector3 visualSize) { float num = NormalizeBoundsScale(visualSize); return new AvatarPreviewPlacement(num, -visualCenter * num); } public static AvatarResolvedHeight ResolveHeight(float avatarMetadataHeight, float rendererBoundsHeight, float previewMeshBoundsHeight = 0f) { if (IsUsableHeight(avatarMetadataHeight)) { return new AvatarResolvedHeight(avatarMetadataHeight, AvatarPreviewHeightSource.AvatarMetadata); } if (IsUsableHeight(rendererBoundsHeight)) { return new AvatarResolvedHeight(rendererBoundsHeight, AvatarPreviewHeightSource.RendererBounds); } if (IsUsableHeight(previewMeshBoundsHeight)) { return new AvatarResolvedHeight(previewMeshBoundsHeight, AvatarPreviewHeightSource.PreviewMeshBounds); } return new AvatarResolvedHeight(0f, AvatarPreviewHeightSource.Unavailable); } public static AvatarResolvedHeight ResolveGameplayHeight(float selectedAvatarHeight, float fallbackAvatarHeight) { if (IsDisplayableGameplayHeight(selectedAvatarHeight)) { return new AvatarResolvedHeight(selectedAvatarHeight, AvatarPreviewHeightSource.AvatarMetadata); } if (IsDisplayableGameplayHeight(fallbackAvatarHeight)) { return new AvatarResolvedHeight(fallbackAvatarHeight, AvatarPreviewHeightSource.AvatarMetadata); } return new AvatarResolvedHeight(0f, AvatarPreviewHeightSource.Unavailable); } public static bool IsVisualRendererType(string? typeName) { if (!string.IsNullOrWhiteSpace(typeName) && !typeName.Contains("Particle", StringComparison.OrdinalIgnoreCase) && !typeName.Contains("Trail", StringComparison.OrdinalIgnoreCase)) { return !typeName.Contains("LineRenderer", StringComparison.OrdinalIgnoreCase); } return false; } public static bool IsSafePreview(float visualHeightMeters, int rendererCount, int vertexCount, bool constrained) { int num = (constrained ? 24 : 64); int num2 = (constrained ? 350000 : 1250000); if (IsUsableHeight(visualHeightMeters) && rendererCount > 0 && rendererCount <= num && vertexCount >= 0) { return vertexCount <= num2; } return false; } } public static class AvatarUiScaleMath { public const float ReferencePlayerHeightMeters = 1.75f; public static float ResolveMenuScale(float playerHeightMeters) { if (!AvatarPreviewMath.IsUsableHeight(playerHeightMeters)) { return 1f; } return playerHeightMeters / 1.75f; } public static float ResolveDetachedContentScale(float tabletWorldScale) { if (!float.IsFinite(tabletWorldScale) || !(tabletWorldScale > 0.0001f)) { return 1f; } return tabletWorldScale; } } public readonly record struct NativeMenuPlacement(Vector3 Position, Vector3 Forward, float DistanceMeters); public static class NativeMenuPlacementMath { public const float ReferenceDistanceMeters = 1.05f; public static Vector3 ResolveForward(Vector3 headForward, Vector3 fallbackForward) { Vector3 value = new Vector3(headForward.X, 0f, headForward.Z); if (value.LengthSquared() < 0.04f) { value = new Vector3(fallbackForward.X, 0f, fallbackForward.Z); } if (value.LengthSquared() < 0.0001f) { value = Vector3.UnitZ; } return Vector3.Normalize(value); } public static NativeMenuPlacement Resolve(Vector3 headPosition, Vector3 headForward, float avatarHeightMeters) { Vector3 vector = ResolveForward(headForward, Vector3.UnitZ); float num = Math.Clamp(AvatarUiScaleMath.ResolveMenuScale(avatarHeightMeters), 0.65f, 1.75f); float num2 = 1.05f * num; return new NativeMenuPlacement(headPosition + vector * num2 - Vector3.UnitY * (0.06f * num), vector, num2); } } public static class DownloadPresentationMath { public static double Smooth(double displayed, double authoritative, double deltaSeconds, double speed = 8.0) { double num = Math.Clamp(displayed, 0.0, 1.0); double num2 = Math.Clamp(authoritative, 0.0, 1.0); if (num2 <= num || deltaSeconds <= 0.0) { return Math.Min(num, (num2 < num) ? num : num2); } double num3 = 1.0 - Math.Exp((0.0 - Math.Max(0.1, speed)) * Math.Min(deltaSeconds, 0.25)); double num4 = num + (num2 - num) * num3; if (!(num2 - num4 < 0.0005)) { return Math.Min(num4, num2); } return num2; } } public static class FingertipProbeMath { public static Vector3 ControllerTip(Vector3 position, Vector3 forward, float reachMeters = 0.075f) { Vector3 vector = ((forward.LengthSquared() > 0.0001f) ? Vector3.Normalize(forward) : Vector3.UnitZ); return position + vector * Math.Clamp(reachMeters, 0f, 0.15f); } public static Vector3 Extrapolate(Vector3 intermediate, Vector3 distal, float distalFraction = 0.65f) { Vector3 value = distal - intermediate; float num = value.Length(); if (!(num > 0.0001f)) { return distal; } return distal + Vector3.Normalize(value) * num * Math.Clamp(distalFraction, 0f, 1.5f); } } public static class WorldTextFacingMath { public const float FrontFacingLocalYawDegrees = 0f; public static bool IsFrontFacing(float localYawDegrees) { return Math.Abs(Math.IEEERemainder(localYawDegrees - 0f, 360.0)) < 0.0010000000474974513; } public static bool FacesViewer(Vector3 rootForward, Vector3 directionToViewer, float localYawDegrees) { if (rootForward.LengthSquared() < 0.0001f || directionToViewer.LengthSquared() < 0.0001f) { return false; } Vector3 vector = Vector3.Normalize(rootForward); Vector3 value = Vector3.Cross(Vector3.UnitY, vector); value = ((!(value.LengthSquared() < 0.0001f)) ? Vector3.Normalize(value) : Vector3.UnitX); float x = localYawDegrees * (float)Math.PI / 180f; return Vector3.Dot(Vector3.Normalize(-vector * MathF.Cos(x) - value * MathF.Sin(x)), Vector3.Normalize(directionToViewer)) > 0.999f; } } public enum AvatarRestoreDecision { NativeSaveOwnsSelection, WaitForNativeSave, RestorePrivateFallback, NoRestore } public static class AvatarRestorePolicy { public static AvatarRestoreDecision Decide(bool nativeSaveReadable, bool nativeSaveWaitExpired, string? privateFallbackBarcode) { if (nativeSaveReadable) { return AvatarRestoreDecision.NativeSaveOwnsSelection; } if (!nativeSaveWaitExpired) { return AvatarRestoreDecision.WaitForNativeSave; } if (!string.IsNullOrWhiteSpace(privateFallbackBarcode)) { return AvatarRestoreDecision.RestorePrivateFallback; } return AvatarRestoreDecision.NoRestore; } } public enum AvatarRigCompatibility { UnverifiedAllowed, Verified, Invalid } public readonly record struct AvatarRigValidationResult(AvatarRigCompatibility Compatibility, string Reason) { public bool CanEquip => Compatibility != AvatarRigCompatibility.Invalid; public bool IsVerified => Compatibility == AvatarRigCompatibility.Verified; public static AvatarRigValidationResult Ready() { return new AvatarRigValidationResult(AvatarRigCompatibility.Verified, string.Empty); } public static AvatarRigValidationResult Allow(string reason) { return new AvatarRigValidationResult(AvatarRigCompatibility.UnverifiedAllowed, reason); } public static AvatarRigValidationResult Reject(string reason) { return new AvatarRigValidationResult(AvatarRigCompatibility.Invalid, reason); } } public enum AvatarSharingState { Unregistered, Registering, Ready, Announced, PlatformLimited, Failed } public readonly record struct AvatarPlatformAvailability(long? WindowsFileId, long? AndroidFileId) { public bool HasWindows { get { long? windowsFileId = WindowsFileId; if (windowsFileId.HasValue) { return windowsFileId.GetValueOrDefault() > 0; } return false; } } public bool HasAndroid { get { long? androidFileId = AndroidFileId; if (androidFileId.HasValue) { return androidFileId.GetValueOrDefault() > 0; } return false; } } public bool HasAny { get { if (!HasWindows) { return HasAndroid; } return true; } } public bool IsLimited { get { if (HasAny) { if (HasWindows) { return !HasAndroid; } return true; } return false; } } public string Badge { get { if (!HasWindows || !HasAndroid) { if (!HasWindows) { if (!HasAndroid) { return "LOCAL ONLY"; } return "QUEST ONLY"; } return "PC ONLY"; } return "PC + QUEST"; } } } public readonly record struct CompassPresentation(float HeadingDegrees, float SmoothedHeadingDegrees, float NorthOffsetDegrees, string Cardinal, string SceneKey, bool HasCalibration); public static class CompassHeadingMath { private static readonly string[] Cardinals = new string[8] { "N", "NE", "E", "SE", "S", "SW", "W", "NW" }; public static float Normalize(float degrees) { if (!float.IsFinite(degrees)) { return 0f; } float num = degrees % 360f; if (!(num < 0f)) { return num; } return num + 360f; } public static float Heading(float worldYawDegrees, float northOffsetDegrees) { return Normalize(worldYawDegrees - northOffsetDegrees); } public static float SignedDelta(float fromDegrees, float toDegrees) { float num = Normalize(toDegrees) - Normalize(fromDegrees); if (num > 180f) { num -= 360f; } if (num < -180f) { num += 360f; } return num; } public static float Smooth(float current, float target, float deltaSeconds, float response = 12f) { if (!float.IsFinite(deltaSeconds) || deltaSeconds <= 0f) { return Normalize(current); } float num = 1f - MathF.Exp((0f - MathF.Max(0.01f, response)) * deltaSeconds); return Normalize(current + SignedDelta(current, target) * num); } public static string Cardinal(float headingDegrees) { int num = (int)MathF.Floor((Normalize(headingDegrees) + 22.5f) / 45f) % Cardinals.Length; return Cardinals[num]; } public static float NeedleRotation(float headingDegrees) { return 0f - Normalize(headingDegrees); } } public static class CompassCalibrationStore { public const int MaximumRecords = 64; public static CompassCalibrationRecord? Find(IReadOnlyList<CompassCalibrationRecord> records, string sceneKey) { return records.FirstOrDefault((CompassCalibrationRecord record) => string.Equals(record.SceneKey, sceneKey, StringComparison.OrdinalIgnoreCase)); } public static void Upsert(List<CompassCalibrationRecord> records, string sceneKey, float northYaw, DateTimeOffset usedAt) { string sceneKey2 = (string.IsNullOrWhiteSpace(sceneKey) ? "unknown-scene" : sceneKey.Trim()); CompassCalibrationRecord compassCalibrationRecord = Find(records, sceneKey2); if (compassCalibrationRecord != null) { compassCalibrationRecord.NorthYawDegrees = CompassHeadingMath.Normalize(northYaw); compassCalibrationRecord.LastUsedUtc = usedAt; } else { records.Add(new CompassCalibrationRecord { SceneKey = sceneKey2, NorthYawDegrees = CompassHeadingMath.Normalize(northYaw), LastUsedUtc = usedAt }); } while (records.Count > 64) { CompassCalibrationRecord item = records.OrderBy((CompassCalibrationRecord record) => record.LastUsedUtc).First(); records.Remove(item); } } } public enum HologramCarouselRole { Previous, Selected, Next } public enum HologramAvatarMaterialMode { BlueHologram, FullColor } public readonly record struct HologramCarouselSlot(HologramCarouselRole Role, int Index, string Identity, float HorizontalOffset, float Scale, HologramAvatarMaterialMode MaterialMode, bool InteractionReady); public static class HologramCarouselPresentation { public const float SideOffsetMeters = 0.105f; public const float SideScale = 0.62f; public static IReadOnlyList<HologramCarouselSlot> Build(IReadOnlyList<string> identities, int selectedIndex) { if (identities.Count == 0) { return Array.Empty<HologramCarouselSlot>(); } int num = Wrap(selectedIndex, identities.Count); if (identities.Count != 1) { int index = Wrap(num - 1, identities.Count); int index2 = Wrap(num + 1, identities.Count); return new HologramCarouselSlot[3] { Slot(HologramCarouselRole.Previous, index, identities[index], -0.105f, 0.62f, interactive: false), Slot(HologramCarouselRole.Selected, num, identities[num], 0f, 1f, interactive: true), Slot(HologramCarouselRole.Next, index2, identities[index2], 0.105f, 0.62f, interactive: false) }; } return new HologramCarouselSlot[1] { Slot(HologramCarouselRole.Selected, num, identities[num], 0f, 1f, interactive: true) }; } public static int Wrap(int index, int count) { if (count <= 0) { return 0; } int num = index % count; if (num >= 0) { return num; } return num + count; } private static HologramCarouselSlot Slot(HologramCarouselRole role, int index, string identity, float offset, float scale, bool interactive) { return new HologramCarouselSlot(role, index, identity, offset, scale, interactive ? HologramAvatarMaterialMode.FullColor : HologramAvatarMaterialMode.BlueHologram, interactive); } } public enum CinematicProjectionPhase { Hidden, Charging, RingExpansion, SideAvatarAssembly, CenterAvatarAssembly, Stable, CarouselTransition, PanelTransition, Collapsing, TrackingGrace } public readonly record struct CinematicProjectionProgress(CinematicProjectionPhase Phase, float Emitter, float Rings, float SideAvatars, float CenterAvatar, float Controls); public static class CinematicProjectionTimeline { public const float ChargeEnd = 0.15f; public const float RingEnd = 0.35f; public const float SideEnd = 0.45f; public const float CenterEnd = 0.55f; public const float CollapseSeconds = 0.35f; public static CinematicProjectionProgress Resolve(float elapsed, bool reducedMotion) { if (reducedMotion) { return new CinematicProjectionProgress(CinematicProjectionPhase.Stable, 1f, 1f, 1f, 1f, 1f); } float num = Math.Max(0f, elapsed); return new CinematicProjectionProgress((num < 0.15f) ? CinematicProjectionPhase.Charging : ((num < 0.35f) ? CinematicProjectionPhase.RingExpansion : ((num < 0.45f) ? CinematicProjectionPhase.SideAvatarAssembly : ((num < 0.55f) ? CinematicProjectionPhase.CenterAvatarAssembly : CinematicProjectionPhase.Stable))), Smooth(num / 0.15f), Smooth((num - 0.0825f) / 0.26749998f), Smooth((num - 0.22749999f) / 0.2225f), Smooth((num - 0.35f) / 0.20000002f), Smooth((num - 0.35099998f) / 0.19900003f)); } private static float Smooth(float value) { float num = Math.Clamp(value, 0f, 1f); return num * num * (3f - 2f * num); } } public static class HoloInteractionMath { public static int WrapIndex(int current, int delta, int count) { if (count <= 0) { return 0; } return ((current + delta) % count + count) % count; } public static float SmoothStep01(float value) { float num = Math.Clamp(value, 0f, 1f); return num * num * (3f - 2f * num); } public static bool IsIntentionalFlick(float lateralVelocity, float threshold) { return Math.Abs(lateralVelocity) >= Math.Max(0f, threshold); } } public enum HoloPerformanceTier { Full, Balanced, Minimal } public sealed class HoloPerformanceGovernor { private readonly bool _constrained; private double _averageFrameMs; private int _slowSamples; private int _fastSamples; public HoloPerformanceTier Tier { get; private set; } public int RendererLimit => Tier switch { HoloPerformanceTier.Full => 64, HoloPerformanceTier.Balanced => 24, _ => 12, }; public bool EffectsAllowed => Tier != HoloPerformanceTier.Minimal; public double AverageFrameMs => _averageFrameMs; public HoloPerformanceGovernor(bool constrained) { _constrained = constrained; Tier = (constrained ? HoloPerformanceTier.Balanced : HoloPerformanceTier.Full); } public bool Sample(double frameMilliseconds) { if (!_constrained || double.IsNaN(frameMilliseconds) || double.IsInfinity(frameMilliseconds)) { return false; } double num = Math.Clamp(frameMilliseconds, 1.0, 100.0); _averageFrameMs = ((_averageFrameMs <= 0.0) ? num : (_averageFrameMs * 0.94 + num * 0.06)); if (_averageFrameMs >= 21.0) { _slowSamples++; _fastSamples = 0; } else if (_averageFrameMs <= 15.5) { _fastSamples++; _slowSamples = 0; } else { _slowSamples = Math.Max(0, _slowSamples - 1); _fastSamples = Math.Max(0, _fastSamples - 1); } if (Tier == HoloPerformanceTier.Balanced && _slowSamples >= 45) { Tier = HoloPerformanceTier.Minimal; _slowSamples = 0; return true; } if (Tier == HoloPerformanceTier.Minimal && _fastSamples >= 240) { Tier = HoloPerformanceTier.Balanced; _fastSamples = 0; return true; } return false; } } public enum InstalledPackFocusState { None, Downloading, WaitingForPallet, Ready, TimedOut } public sealed class InstalledPackFocus { public long ModId { get; private set; } public int Generation { get; private set; } public double DeadlineSeconds { get; private set; } public InstalledPackFocusState State { get; private set; } public string SelectedBarcode { get; private set; } = string.Empty; public IReadOnlyList<string> InstallDirectories { get; private set; } = Array.Empty<string>(); public bool Active => State != InstalledPackFocusState.None; public int Begin(long modId, double nowSeconds, IEnumerable<string>? installDirectories = null, double timeoutSeconds = 12.0) { Generation++; ModId = modId; DeadlineSeconds = nowSeconds + Math.Max(1.0, timeoutSeconds); State = InstalledPackFocusState.WaitingForPallet; SelectedBarcode = string.Empty; InstallDirectories = (installDirectories ?? Array.Empty<string>()).Where((string path) => !string.IsNullOrWhiteSpace(path)).Distinct<string>(StringComparer.OrdinalIgnoreCase).ToArray(); return Generation; } public int BeginDownload(long modId) { Generation++; ModId = modId; DeadlineSeconds = double.PositiveInfinity; State = InstalledPackFocusState.Downloading; SelectedBarcode = string.Empty; InstallDirectories = Array.Empty<string>(); return Generation; } public void MarkDownloaded(long modId, double nowSeconds, IEnumerable<string>? installDirectories, double timeoutSeconds = 12.0) { if (modId == ModId && State != InstalledPackFocusState.None) { UpdateDirectories(modId, installDirectories); DeadlineSeconds = nowSeconds + Math.Max(1.0, timeoutSeconds); State = InstalledPackFocusState.WaitingForPallet; } } public void UpdateDirectories(long modId, IEnumerable<string>? installDirectories) { if (modId == ModId && State != InstalledPackFocusState.None) { InstallDirectories = (installDirectories ?? Array.Empty<string>()).Where((string path) => !string.IsNullOrWhiteSpace(path)).Distinct<string>(StringComparer.OrdinalIgnoreCase).ToArray(); } } public bool TryBind(long modId, IEnumerable<string?> barcodes) { if (modId != ModId || State == InstalledPackFocusState.None) { return false; } string text = barcodes.FirstOrDefault((string value) => !string.IsNullOrWhiteSpace(value))?.Trim(); if (string.IsNullOrWhiteSpace(text)) { return false; } SelectedBarcode = text; State = InstalledPackFocusState.Ready; return true; } public bool UpdateTimeout(double nowSeconds) { if (State != InstalledPackFocusState.WaitingForPallet || nowSeconds < DeadlineSeconds) { return false; } State = InstalledPackFocusState.TimedOut; return true; } public void Retry(double nowSeconds, double timeoutSeconds = 12.0) { if (Active) { DeadlineSeconds = nowSeconds + Math.Max(1.0, timeoutSeconds); State = InstalledPackFocusState.WaitingForPallet; } } public void Clear() { ModId = 0L; DeadlineSeconds = 0.0; State = InstalledPackFocusState.None; SelectedBarcode = string.Empty; InstallDirectories = Array.Empty<string>(); } } public sealed class LibraryRefreshGate { public bool Pending { get; private set; } public double NotBeforeSeconds { get; private set; } public LibraryRefreshGate(bool initiallyPending = true) { Pending = initiallyPending; } public void Request(double nowSeconds, double debounceSeconds = 0.35) { double num = nowSeconds + Math.Max(0.0, debounceSeconds); NotBeforeSeconds = (Pending ? Math.Max(NotBeforeSeconds, num) : num); Pending = true; } public bool TryConsume(double nowSeconds) { if (!Pending || nowSeconds < NotBeforeSeconds) { return false; } Pending = false; NotBeforeSeconds = 0.0; return true; } } public readonly record struct MiniMapOverlayColor(byte R, byte G, byte B, byte A = byte.MaxValue) { public static readonly MiniMapOverlayColor Outline = new MiniMapOverlayColor(0, 15, 24, 235); } public enum MiniMapPeerRole { Player, Operator, Owner } public static class MiniMapPeerColors { public static readonly MiniMapOverlayColor Cyan = new MiniMapOverlayColor(44, 220, byte.MaxValue); public static readonly MiniMapOverlayColor OperatorViolet = new MiniMapOverlayColor(190, 112, byte.MaxValue); public static readonly MiniMapOverlayColor OwnerAm
Mods\WristHub.dll
Decompiled 2 months ago
The result has been truncated due to the large size, download it to view full contents!
using System; using System.Collections; using System.Collections.Concurrent; using System.Collections.Generic; using System.Diagnostics; using System.Globalization; using System.IO; using System.Linq; using System.Net; using System.Net.Http; using System.Numerics; using System.Reflection; using System.Runtime.CompilerServices; using System.Runtime.Versioning; using System.Security.Cryptography; using System.Text; using System.Text.Json; using System.Threading; using System.Threading.Tasks; using BoneHub.Api; using BoneHub.Avatars; using BoneHub.Compatibility; using BoneHub.Configuration; using BoneHub.Downloads; using BoneHub.Installation; using BoneHub.Interaction; using BoneHub.Mod; using BoneHub.Models; using BoneHub.Persistence; using BoneLib; using BoneLib.BoneMenu; using HarmonyLib; using Il2CppCysharp.Threading.Tasks; using Il2CppInterop.Runtime.InteropTypes; using Il2CppInterop.Runtime.InteropTypes.Arrays; using Il2CppSLZ.Bonelab; using Il2CppSLZ.Bonelab.SaveData; using Il2CppSLZ.Marrow; using Il2CppSLZ.Marrow.Data; using Il2CppSLZ.Marrow.Forklift.Model; using Il2CppSLZ.Marrow.Interaction; using Il2CppSLZ.Marrow.Pool; using Il2CppSLZ.Marrow.SaveData; using Il2CppSLZ.Marrow.SceneStreaming; using Il2CppSLZ.Marrow.Utilities; using Il2CppSLZ.Marrow.Warehouse; using Il2CppSLZ.VRMK; using Il2CppSystem; using Il2CppSystem.Collections.Generic; using Il2CppTMPro; using LabFusion.Entities; using LabFusion.Marrow; using LabFusion.Marrow.Extenders; using LabFusion.Marrow.Patching; using LabFusion.Marrow.Pool; using LabFusion.Menu; using LabFusion.Network; using LabFusion.Player; using LabFusion.RPC; using LabFusion.Representation; using LabFusion.SDK.Lobbies; using LabFusion.SDK.Modules; using LabFusion.Scene; using LabFusion.Senders; using MelonLoader; using MelonLoader.Preferences; using MelonLoader.Utils; using UnityEngine; using UnityEngine.Networking; using UnityEngine.Rendering; using UnityEngine.SceneManagement; using UnityEngine.UI; using WristHub.SDK; [assembly: CompilationRelaxations(8)] [assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)] [assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)] [assembly: MelonInfo(typeof(BoneHubMod), "WristHubV4", "4.2.1", "WristHub contributors", null)] [assembly: MelonGame("Stress Level Zero", "BONELAB")] [assembly: AssemblyMetadata("WristHubPlatform", "QuestStandalone")] [assembly: TargetFramework(".NETCoreApp,Version=v8.0", FrameworkDisplayName = ".NET 8.0")] [assembly: AssemblyCompany("WristHub")] [assembly: AssemblyConfiguration("Release")] [assembly: AssemblyFileVersion("4.2.1.0")] [assembly: AssemblyInformationalVersion("4.2.1")] [assembly: AssemblyProduct("WristHub")] [assembly: AssemblyTitle("WristHub")] [assembly: AssemblyVersion("4.2.1.0")] [module: RefSafetyRules(11)] namespace BoneHub.Mod; internal sealed class BoneHubAvatarBrowser : IDisposable { private sealed class AppLibraryTile { public TouchButton Button { get; init; } public RawImage Artwork { get; init; } public TMP_Text Monogram { get; init; } public TMP_Text Name { get; init; } public TMP_Text Badge { get; init; } public WristAppLibraryItem? Item { get; set; } public string ArtworkIdentity { get; set; } = string.Empty; } private sealed record WristAppLibraryItem(WristHubApp? App, ThunderstorePackageEntry? Package) { public string Identity { get { WristHubApp? app = App; return ((app != null) ? app.Id : null) ?? ("package:" + (Package?.PackageNamespace ?? string.Empty)); } } public string Name { get { WristHubApp? app = App; return ((app != null) ? app.Name : null) ?? Package?.Name ?? "Unknown app"; } } public string Author { get { WristHubApp? app = App; return ((app != null) ? app.Author : null) ?? Package?.Author ?? "Unknown developer"; } } public string Version { get { WristHubApp? app = App; return ((app != null) ? app.Version : null) ?? Package?.Version ?? string.Empty; } } public string Description { get { WristHubApp? app = App; object obj; if (app == null) { obj = null; } else { WristHubStoreProfile store = app.Store; obj = ((store != null) ? store.Tagline : null); } if (obj == null) { WristHubApp? app2 = App; if (app2 == null) { obj = null; } else { WristHubCommunityProfile community = app2.Community; obj = ((community != null) ? community.Summary : null); } if (obj == null) { WristHubApp? app3 = App; obj = ((app3 != null) ? app3.Description : null) ?? Package?.Description ?? string.Empty; } } return (string)obj; } } } private enum ButtonIconKind { None, Back, Forward, Close, Search, Settings, Download, Avatar, Apps, Map, Compass, Portal, Director, Fusion, Players, Gamemode, QuickJoin, Home, Refresh, Equip, Delete, Join } private sealed class TouchButton { public Transform Space { get; init; } public GameObject Root { get; init; } public Image Panel { get; init; } public TMP_Text Label { get; init; } public Vector3 Center { get; set; } public Vector3 Half { get; init; } public Action Action { get; init; } public bool UsesThemeColor { get; init; } public float ThemeStrength { get; init; } public TouchDebounce Debounce { get; } = new TouchDebounce(); public bool IsAlive { get { try { return (Object)(object)Root != (Object)null && (Object)(object)Space != (Object)null && (Object)(object)Panel != (Object)null && (Object)(object)Label != (Object)null; } catch { return false; } } } public bool Contains(Vector3 worldPoint, float hitInflation = 1f) { //IL_0036: Unknown result type (might be due to invalid IL or missing references) //IL_0037: Unknown result type (might be due to invalid IL or missing references) //IL_003d: Unknown result type (might be due to invalid IL or missing references) //IL_0042: Unknown result type (might be due to invalid IL or missing references) //IL_0047: Unknown result type (might be due to invalid IL or missing references) //IL_0059: Unknown result type (might be due to invalid IL or missing references) //IL_0065: Unknown result type (might be due to invalid IL or missing references) //IL_0073: Unknown result type (might be due to invalid IL or missing references) //IL_007f: Unknown result type (might be due to invalid IL or missing references) //IL_008d: Unknown result type (might be due to invalid IL or missing references) //IL_0099: Unknown result type (might be due to invalid IL or missing references) try { if ((Object)(object)Root == (Object)null || (Object)(object)Space == (Object)null || !Root.activeInHierarchy) { return false; } Vector3 val = Space.InverseTransformPoint(worldPoint) - Center; float num = Mathf.Clamp(hitInflation, 1f, 1.6f); return Mathf.Abs(val.x) <= Half.x * num && Mathf.Abs(val.y) <= Half.y * num && Mathf.Abs(val.z) <= Half.z * num; } catch { return false; } } } private enum AvatarPreviewStage { ResolvingCrate, NativeRequested, NativeReady, FullColorRequested, Ready, Failed } private sealed class AvatarPreviewSession { public int Generation { get; init; } public string Barcode { get; init; } = string.Empty; public string PalletRevision { get; set; } = string.Empty; public AvatarPreviewStage Stage { get; set; } public int NativeAttempt { get; set; } public bool NativeResolved { get; set; } public bool FullColorRequested { get; set; } public bool Cancelled { get; set; } public float Deadline { get; init; } public PreviewCallbackLease Callbacks { get; } = new PreviewCallbackLease(); } private sealed record RetiredPreviewCallbacks(PreviewCallbackLease Lease, float ReleaseAt); private sealed record CachedNativeAvatarPreview(Mesh Mesh, string Revision); private sealed record NativeAvatarPrefetch(PreviewCallbackLease Lease, float ExpiresAt); private sealed class QuestBootstrapScheduler { private readonly Stopwatch _clock = new Stopwatch(); private long _phaseStarted; public double TotalMilliseconds => _clock.Elapsed.TotalMilliseconds; public void Start() { _clock.Restart(); _phaseStarted = 0L; } public double FinishPhase() { long elapsedMilliseconds = _clock.ElapsedMilliseconds; long num = elapsedMilliseconds - _phaseStarted; _phaseStarted = elapsedMilliseconds; return num; } } private sealed class PackRow { public TouchButton Button { get; init; } public TouchButton DeleteButton { get; init; } public RawImage Artwork { get; init; } public TMP_Text Name { get; init; } public TMP_Text Creator { get; init; } public TMP_Text State { get; init; } public string Identity { get; set; } = string.Empty; public int Generation { get; set; } public bool IsAlive { get { if (Button.IsAlive && DeleteButton.IsAlive && (Object)(object)Artwork != (Object)null && (Object)(object)Name != (Object)null && (Object)(object)Creator != (Object)null) { return (Object)(object)State != (Object)null; } return false; } } } private sealed class FriendRowVisual { public Image Portrait { get; init; } public TMP_Text Initials { get; init; } public TMP_Text Name { get; init; } public TMP_Text Detail { get; init; } public TMP_Text Presence { get; init; } } private enum IconPrimitiveType { Line, Ring, Disc, Box } private readonly record struct IconPrimitive(IconPrimitiveType Type, Vector2 A, Vector2 B, float Radius, float Stroke); private enum ModStoreCatalog { Thunderstore, ModIo } private enum PortalPlacementStage { None, LocalEntrance, LocalExit, WorldEntrance, FusionServerEntrance } private sealed class SpawnGeometryProfile { public Bounds LocalBounds { get; init; } public Vector3 HandleDirectionLocal { get; init; } public Quaternion PreviewRotation { get; init; } public string Source { get; init; } = "fallback"; public int PalletRevision { get; init; } public bool Melee { get; init; } } private sealed class FrozenSpawnRecord { public GameObject Root { get; init; } public ReversibleFreezeState Physics { get; init; } public float CreatedAt { get; init; } public float Deadline { get; init; } public bool HandWasAway { get; set; } public float NextInteractionCheckAt { get; set; } } private enum WristKeyboardPurpose { Avatars, CodeMods, ModStore, WristApps, Spawnables, PortalLevels, FusionServerCode, CodeModString, SdkText, FriendsMessage, FriendCode, FriendRecovery, FriendLobbySearch, FriendNickname, FriendNote, FusionLobbySetting } private enum CodeModCatalogMode { All, Community, Managers, BoneMenu, Loaded } private sealed class SpawnLabRow { public TouchButton Button { get; init; } public string Identity { get; set; } = string.Empty; public bool IsAlive => Button.IsAlive; } private sealed record SpawnLabBrowserItem(SpawnableEntry? Entry, SpawnableGroupEntry? Group, ModIoMod? OnlineMod) { public string Identity { get { SpawnableEntry? entry = Entry; object obj = ((entry != null) ? entry.Identity : null); if (obj == null) { SpawnableGroupEntry? obj2 = Group; obj = ((obj2 != null) ? obj2.Identity : null); if (obj == null) { if (OnlineMod != null) { return "online:" + OnlineMod.Id; } obj = string.Empty; } } return (string)obj; } } } private sealed class SpawnOwnershipRecord { public GameObject Spawned { get; init; } public string Barcode { get; init; } = string.Empty; public ushort? FusionEntityId { get; init; } public bool NetworkOwned { get; init; } public long RequestId { get; init; } public int Generation { get; init; } public bool Orphaned { get; init; } } private sealed class OsControlRow { public TouchButton Main { get; init; } public TouchButton Minus { get; init; } public TouchButton Plus { get; init; } public TMP_Text Detail { get; init; } public object? Source { get; set; } } private sealed class MiniMapPeerVisual { public int PeerId { get; set; } = -1; public bool Active { get; set; } public Vector3 TargetWorldPosition { get; set; } public Vector3 WorldPosition { get; set; } public float TargetHeadingDegrees { get; set; } public float HeadingDegrees { get; set; } public MiniMapPeerRole Role { get; set; } public Transform? TrackingTransform { get; set; } public bool HasDisplayPose { get; set; } public float LastSeenAt { get; set; } } private sealed record CodeModSnapshot(string Name, string Detail, object? Page, object? Element = null); private sealed record LoadedCodeModSnapshot(string Name, string Author, string Version); private sealed class ReflectedManagerControl { private readonly Func<string> _detail; private readonly Action _activate; private readonly Action<int>? _adjust; public string Label { get; } public string Detail => _detail(); public bool Adjustable => _adjust != null; public ReflectedManagerControl(string label, Func<string> detail, Action activate, Action<int>? adjust = null) { Label = label; _detail = detail; _activate = activate; _adjust = adjust; } public void Activate() { _activate(); } public void Adjust(int direction) { _adjust?.Invoke(direction); } } private sealed class RuntimeSdkHost : IWristHubHost { private readonly BoneHubAvatarBrowser _owner; public WristHubRuntimeInfo Runtime => new WristHubRuntimeInfo(true, _owner._constrainedRendering, IsFusionConnected(), ((object)_owner._preferences.HologramTheme/*cast due to .constrained prefix*/).ToString(), 4); public RuntimeSdkHost(BoneHubAvatarBrowser owner) { _owner = owner; } public void Notify(string appId, string message, WristHubNotificationKind kind) { //IL_0002: Unknown result type (might be due to invalid IL or missing references) WristNotificationService.PublishSdk(appId, message, kind); } public void Notify(string appId, WristHubNotificationDefinition notification) { WristNotificationService.PublishSdk(appId, notification); } public void RequestOpen(string appId) { _owner._sdkOpenRequests.Enqueue(appId); } } private readonly record struct SdkNotification(string AppId, string Message, WristHubNotificationKind Kind) { [CompilerGenerated] public void Deconstruct(out string AppId, out string Message, out WristHubNotificationKind Kind) { //IL_0012: Unknown result type (might be due to invalid IL or missing references) //IL_0018: Expected I4, but got Unknown AppId = this.AppId; Message = this.Message; Kind = (WristHubNotificationKind)(int)this.Kind; } } private sealed class RuntimeWristOsApp : IWristOsApp { private readonly Action _open; public WristOsAppId Id { get; } public string DisplayName { get; } public bool IsAvailable => true; public int NotificationCount => 0; public RuntimeWristOsApp(WristOsAppId id, string name, Action open) { //IL_0007: Unknown result type (might be due to invalid IL or missing references) //IL_0008: Unknown result type (might be due to invalid IL or missing references) Id = id; DisplayName = name; _open = open; } public void Open() { _open(); } public void Close() { } public void Tick(double nowSeconds) { } public bool HandleBack() { return false; } } private const int AppLibraryTilesPerPage = 8; private readonly List<AppLibraryTile> _appLibraryTiles = new List<AppLibraryTile>(8); private readonly List<AppLibraryTile> _homeAppTiles = new List<AppLibraryTile>(9); private readonly List<WristAppLibraryItem> _appLibraryItems = new List<WristAppLibraryItem>(); private readonly List<ThunderstorePackageEntry> _sdkStorePackages = new List<ThunderstorePackageEntry>(); private readonly Dictionary<string, Texture2D> _sdkRichIcons = new Dictionary<string, Texture2D>(StringComparer.OrdinalIgnoreCase); private readonly Queue<string> _sdkRichIconOrder = new Queue<string>(); private GameObject? _osHomePageOne; private GameObject? _osHomePageTwo; private TMP_Text? _osHomePageDotsOne; private TMP_Text? _osHomePageDotsTwo; private int _osHomePageIndex; private GameObject? _appLibraryPage; private GameObject? _appLibraryGridRoot; private GameObject? _appLibraryDetailRoot; private TMP_Text? _appLibraryTitle; private TMP_Text? _appLibraryPosition; private TMP_Text? _appLibraryRestartBanner; private TouchButton? _appLibraryInstalledTab; private TouchButton? _appLibraryStoreTab; private TouchButton? _appLibraryUpdatesTab; private TouchButton? _appLibraryManageButton; private TMP_Text? _appDetailName; private TMP_Text? _appDetailAuthor; private TMP_Text? _appDetailDescription; private TMP_Text? _appDetailStatus; private TouchButton? _appDetailAction; private TouchButton? _appDetailPin; private TouchButton? _appDetailUninstall; private WristAppLibraryItem? _appLibraryDetailItem; private int _appLibraryPageIndex; private bool _appLibraryManageMode; private float _nextAppLibraryRefresh; private int _appArtworkGeneration; private bool _appLibrarySdkEventsAttached; private int _homeSwipeProbeId; private float _homeSwipeStartX; private const float ChestRadius = 0.22f; private static readonly Color Glass = new Color(0.012f, 0.027f, 0.04f, 1f); private static readonly Color Surface = new Color(0.025f, 0.075f, 0.105f, 0.96f); private static readonly Color Green = new Color(0.32f, 0.9f, 0.62f, 1f); private static readonly Color Amber = new Color(1f, 0.67f, 0.22f, 1f); private static readonly Color Red = new Color(1f, 0.38f, 0.46f, 1f); private readonly BoneHubService _service; private readonly BoneHubRuntime _runtime; private readonly BoneHubThumbnailCache _thumbnails; private readonly ConcurrentQueue<Action> _mainThread; private readonly BoneHubPreferences _preferences; private readonly bool _constrainedRendering; private bool _dashboardBuildPending; private bool _questBootstrapStarted; private bool _questOpenPending; private float _nextQuestPortalAttachAt; private bool _dashboardPagesBuilt; private readonly Instance _log; private readonly LobbyAvatarSharingBridge _sharing; private readonly List<TouchButton> _buttons = new List<TouchButton>(); private readonly List<TouchButton> _activeButtons = new List<TouchButton>(); private float _nextActiveButtonRefresh; private int _activeButtonSourceCount = -1; private readonly Dictionary<int, TouchButton> _touchCaptures = new Dictionary<int, TouchButton>(); private readonly HashSet<int> _activeProbeIds = new HashSet<int>(); private readonly List<int> _staleCaptureIds = new List<int>(2); private readonly Dictionary<long, ModIoMod> _onlineMods = new Dictionary<long, ModIoMod>(); private readonly Dictionary<long, DownloadJob> _jobs = new Dictionary<long, DownloadJob>(); private readonly Dictionary<Guid, DownloadState> _observedDownloadStates = new Dictionary<Guid, DownloadState>(); private readonly Dictionary<long, IReadOnlyList<Guid>> _downloadBatches = new Dictionary<long, IReadOnlyList<Guid>>(); private readonly Dictionary<string, AvatarPreviewMetrics> _previewMetrics = new Dictionary<string, AvatarPreviewMetrics>(StringComparer.OrdinalIgnoreCase); private readonly Dictionary<string, AvatarPreviewRouteMetadata> _previewRoutes = new Dictionary<string, AvatarPreviewRouteMetadata>(StringComparer.OrdinalIgnoreCase); private readonly Dictionary<string, AvatarRigValidationResult> _rigValidations = new Dictionary<string, AvatarRigValidationResult>(StringComparer.OrdinalIgnoreCase); private readonly Dictionary<int, RetiredPreviewCallbacks> _retiredPreviewCallbacks = new Dictionary<int, RetiredPreviewCallbacks>(); private readonly Dictionary<string, CachedNativeAvatarPreview> _nativeAvatarPreviewCache = new Dictionary<string, CachedNativeAvatarPreview>(StringComparer.OrdinalIgnoreCase); private readonly LinkedList<string> _nativeAvatarPreviewLru = new LinkedList<string>(); private readonly Dictionary<string, NativeAvatarPrefetch> _nativeAvatarPrefetches = new Dictionary<string, NativeAvatarPrefetch>(StringComparer.OrdinalIgnoreCase); private readonly HashSet<string> _pcvrWarmedAvatarAssets = new HashSet<string>(StringComparer.OrdinalIgnoreCase); private readonly HashSet<long> _startingDownloads = new HashSet<long>(); private readonly BoneHubFingertipProbeProvider _fingertips = BoneHubFingertipProbeProvider.Shared; private readonly TabletKeyboardBuffer _keyboard = new TabletKeyboardBuffer(); private readonly LookAwayDismissal _promptDismissal = new LookAwayDismissal(); private readonly FocusedResultCursor _cursor = new FocusedResultCursor(); private readonly FourRowPageCursor _packCursor = new FourRowPageCursor(); private readonly PresentationGeneration _artworkGeneration = new PresentationGeneration(); private readonly ErrorLogThrottle _runtimeErrorThrottle = new ErrorLogThrottle(); private readonly SurfaceInputRearmGate _inputRearm = new SurfaceInputRearmGate(0.1); private readonly InstalledPackFocus _installedFocus = new InstalledPackFocus(); private readonly BoneHubForearmProjectionAnchorProvider _projectionAnchors = new BoneHubForearmProjectionAnchorProvider(); private readonly List<PackRow> _packRows = new List<PackRow>(); private readonly Action _projectionTickAction; private readonly Action _projectionRecoveryAction; private readonly Action _touchTickAction; private readonly Action _touchRecoveryAction; private readonly Action _installationFocusTickAction; private readonly Action _previewTickAction; private readonly Action _previewRecoveryAction; private readonly Action _downloadTickAction; private readonly Action _sharingTickAction; private readonly Action _compassTickAction; private readonly Action<PortalLinkState> _portalStateReceiver; private readonly Action<int> _portalRemoveReceiver; private IReadOnlyList<AvatarEntry> _entries = Array.Empty<AvatarEntry>(); private IReadOnlyList<AvatarEntry> _mergedEntries = Array.Empty<AvatarEntry>(); private IReadOnlyList<AvatarEntry> _onlineEntries = Array.Empty<AvatarEntry>(); private IReadOnlyList<AvatarEntry> _localAvatarEntries = Array.Empty<AvatarEntry>(); private bool _localAvatarEntriesDirty = true; private IReadOnlyList<AvatarPackEntry> _packs = Array.Empty<AvatarPackEntry>(); private GameObject? _root; private GameObject? _launcher; private GameObject? _hub; private GameObject? _browser; private GameObject? _canvasRoot; private GameObject? _packPage; private GameObject? _detailPage; private GameObject? _optionsPage; private GameObject? _watchPlacementPage; private GameObject? _downloadsPage; private GameObject? _packOptionsPage; private GameObject? _keyboardPage; private GameObject? _deletePage; private GameObject? _compassPage; private GameObject? _denseGlassChrome; private GameObject? _denseGlassBackground; private GameObject? _denseGlassOverlay; private GameObject? _hubPhysicalSurface; private GameObject? _browserPhysicalSurface; private readonly List<(Collider Surface, Collider Ignored)> _physicalUiIgnoredPairs = new List<(Collider, Collider)>(); private readonly List<(Collider Surface, Collider Hand)> _physicalUiDeferredPairs = new List<(Collider, Collider)>(); private readonly List<(Collider Surface, Collider Hand)> _physicalUiArmedPairs = new List<(Collider, Collider)>(); private PhysicMaterial? _physicalUiMaterial; private int _physicalUiRigId; private int _physicalUiColliderSignature; private bool _physicalUiLeftWrist; private bool _physicalUiFilterActive; private float _nextPhysicalUiFilterRefresh; private bool _wristStabilizerValid; private int _wristStabilizerRigId; private Vector3 _wristStabilizerRigPosition; private Quaternion _wristStabilizerRigRotation = Quaternion.identity; private GameObject? _leftArcWing; private GameObject? _rightArcWing; private GameObject? _metadataArc; private CanvasGroup? _canvasGroup; private TMP_Text? _packTitle; private TMP_Text? _packStatus; private TMP_Text? _packPosition; private TMP_Text? _packSearchText; private TMP_Text? _sortText; private TMP_Text? _wristText; private TMP_Text? _scaleText; private TMP_Text? _motionText; private TMP_Text? _themeText; private TMP_Text? _clockText; private TMP_Text? _tapModeText; private TMP_Text? _startingViewText; private TMP_Text? _watchForearmText; private TMP_Text? _watchSleepLiftText; private TMP_Text? _watchStyleText; private readonly Dictionary<WristWatchStyle, TouchButton> _watchStyleButtons = new Dictionary<WristWatchStyle, TouchButton>(); private TMP_Text? _watchAttachmentText; private TMP_Text? _watchBodyScaleText; private TMP_Text? _scopeText; private TMP_Text? _downloadsSummary; private TMP_Text? _deleteTitle; private TMP_Text? _deleteDetails; private TMP_Text? _keyboardText; private TMP_Text? _keyboardTitle; private TMP_Text? _keyboardShiftText; private TMP_Text? _searchText; private TMP_Text? _compassHeadingText; private TMP_Text? _compassCalibrationText; private RectTransform? _compassNeedle; private RectTransform? _compassDecorRing; private TMP_Text? _positionText; private TMP_Text? _selectedName; private TMP_Text? _creatorText; private TMP_Text? _modText; private TMP_Text? _statusText; private TMP_Text? _heightText; private GameObject? _heightRuler; private GameObject? _progressRoot; private RectTransform? _progressFill; private TMP_Text? _progressText; private TMP_Text? _actionLabel; private Image? _actionPanel; private TouchButton? _trashButton; private RawImage? _centralArtwork; private Texture2D? _placeholderTexture; private Texture2D? _roundedTexture; private Sprite? _roundedSprite; private BoneHubForearmProjection? _hologramProjection; private BoneHubHologramCarousel? _hologramCarousel; private BoneHubChestEquipGuide? _chestEquipGuide; private TouchButton? _actionButton; private GameObject? _previewShell; private GameObject? _previewFallback; private GameObject? _previewModel; private Material? _nativePreviewMaterial; private AvatarPreviewSession? _previewSession; private PreviewResourceHandle? _avatarPreviewResources; private readonly List<Mesh> _ownedPreviewMeshes = new List<Mesh>(); private BoneHubAvatar? _previewAvatar; private BoneHubAvatar? _pendingPreviewAvatar; private string _loadedPreviewBarcode = string.Empty; private string _requestedPreviewBarcode = string.Empty; private float _previewLoadAt; private bool _previewReady; private bool _previewInteractionReady; private AvatarPreviewVisualSource _previewVisualSource; private bool _previewHeld; private Hand? _dragHand; private bool _dragIsLeft; private bool _dragUsesPinch; private Vector3 _dragLocalOffset; private Quaternion _dragRotationOffset = Quaternion.identity; private float _dragTrackingLostAt = -1f; private bool _dragWasInsideChest; private bool _previewReturning; private float _returnStarted; private Vector3 _returnFrom; private Quaternion _returnRotation; private int _previewGeneration; private float _nextPreviewCallbackMaintenanceAt; private int _searchGeneration; private string _query = string.Empty; private bool _downloadsOnly; private string _selectedIdentity = string.Empty; private string _trashConfirmIdentity = string.Empty; private bool _deleteReturnToPackPage; private bool _trashInProgress; private long? _selectedPackId; private AvatarPackSort _packSort; private AvatarBrowseScope _scope; private AvatarBrowseScope _scopeBeforeSearch; private Guid _progressJobId; private double _displayedProgress; private double _targetProgress; private int _lastProgressPercent = -1; private AvatarDownloadState? _lastProgressState; private AvatarBrowserViewState _viewState = (AvatarBrowserViewState)2; private AvatarBrowserViewState _viewStateBeforeKeyboard = (AvatarBrowserViewState)2; private bool _disposed; private bool _wasPlayerDead; private AvatarSharingState _sharingState; private BoneHubAvatar? _pendingSharingAvatar; private ModIoSharingMetadata? _pendingSharingMetadata; private string _sharingRetryBarcode = string.Empty; private float _nextSharingCheckAt; private float _sharingDeadline; private bool _sharingRefreshRequested; private string _carouselIdentity = string.Empty; private float _hubControlsReadyAt; private HologramDashboardLifecycle _lifecycle; private float _lifecycleStartedAt; private bool _closeNotificationPending; private HologramDashboardProgress _closeVisualStart = new HologramDashboardProgress((HologramProjectionPhase)5, 1f, 1f, 1f, 1f, 1f, true); private string _compassSceneKey = "unknown-scene"; private float _compassSmoothedHeading; private bool _compassHeadingValid; private int _lastCompassDegree = -1; private const int FriendRowsPerPage = 3; private GameObject? _friendsPage; private TMP_Text? _friendsTitle; private TMP_Text? _friendsStatus; private TMP_Text? _friendsCodeText; private TMP_Text? _friendsEmptyState; private TMP_Text? _friendsPageIndicator; private readonly List<TouchButton> _friendTabs = new List<TouchButton>(4); private readonly List<TouchButton> _friendRows = new List<TouchButton>(3); private readonly List<FriendRowVisual> _friendRowVisuals = new List<FriendRowVisual>(3); private readonly List<TouchButton> _friendActions = new List<TouchButton>(4); private NativeSocialSnapshot _friendSnapshot = new NativeSocialSnapshot(Available: true, InLobby: false, Array.Empty<NativeFriendPerson>(), Array.Empty<NativeFriendPerson>(), Array.Empty<NativeFriendPerson>(), Array.Empty<NativeFriendPerson>(), Array.Empty<NativeFriendPerson>(), "WRISTHUB FRIENDS • OFFLINE CACHE"); private NativeFriendListKind _friendListKind; private int _friendPageIndex; private int _selectedFriendIndex = -1; private ulong _friendMessageTarget; private ulong _friendEditTarget; private bool _friendManageMode; private float _nextFriendRefresh; private string _friendLobbyQuery = string.Empty; private IReadOnlyList<NativeFriendPerson> _visibleFriendList = Array.Empty<NativeFriendPerson>(); private readonly HashSet<ulong> _knownIncomingFriendRequests = new HashSet<ulong>(); private float _nextFriendNotificationPoll; private int _pendingFriendRequestCount; private GameObject? _fusionHomePage; private GameObject? _fusionPlayersPage; private TMP_Text? _fusionHomeTitle; private TMP_Text? _fusionHomeSummary; private TMP_Text? _fusionHomeStatus; private TouchButton? _fusionDisconnectButton; private readonly List<TouchButton> _fusionPlayerRows = new List<TouchButton>(4); private readonly List<RawImage> _fusionPlayerImages = new List<RawImage>(4); private readonly List<TMP_Text> _fusionPlayerMonograms = new List<TMP_Text>(4); private readonly int[] _fusionPlayerImageGenerations = new int[4]; private readonly string[] _fusionPlayerImageKeys = new string[4]; private FusionServerEntry? _fusionPlayersSource; private int _fusionPlayerPageIndex; private TMP_Text? _fusionPlayerPosition; private TMP_Text? _fusionPlayerStatus; private GameObject? _fusionHostingPage; private FusionLobbySettingsAdapter? _fusionHosting; private readonly List<TouchButton> _fusionHostingRows = new List<TouchButton>(4); private TMP_Text? _fusionHostingPosition; private TMP_Text? _fusionHostingStatus; private int _fusionHostingPageIndex; private FusionLobbySetting? _fusionTextSetting; private readonly Dictionary<ButtonIconKind, Sprite> _buttonIconSprites = new Dictionary<ButtonIconKind, Sprite>(); private readonly List<Texture2D> _buttonIconTextures = new List<Texture2D>(); private Sprite? _miniMapRingSprite; private Sprite? _miniMapMaskSprite; private readonly ThunderstoreAssistantBridge _thunderstoreStore = new ThunderstoreAssistantBridge(); private readonly ModIoSubscriptionSyncAdapter _modIoSubscriptionSync = new ModIoSubscriptionSyncAdapter(); private readonly List<ThunderstorePackageEntry> _modStoreResults = new List<ThunderstorePackageEntry>(); private readonly List<ModIoMod> _itemStoreResults = new List<ModIoMod>(); private GameObject? _modStorePage; private TMP_Text? _modStoreName; private TMP_Text? _modStoreAuthor; private TMP_Text? _modStoreDescription; private TMP_Text? _modStorePosition; private TMP_Text? _modStoreStatus; private TouchButton? _modStoreAction; private TouchButton? _modStoreSyncAction; private TouchButton? _modStoreSecondaryAction; private TouchButton? _modStoreThunderstoreTab; private TouchButton? _modStoreModIoTab; private string _thunderstoreQuery = string.Empty; private int _thunderstoreIndex; private string _itemStoreQuery = string.Empty; private int _itemStoreIndex; private float _nextModStoreRefresh; private ModStoreCatalog _modStoreCatalog; private int _itemStoreSearchGeneration; private bool _itemStoreSearching; private long _itemStorePendingModId; private readonly Dictionary<long, float> _itemStoreNextRegistrationScan = new Dictionary<long, float>(); private readonly MonoDirectorRuntimeBridge _monoDirector = new MonoDirectorRuntimeBridge(); private GameObject? _monoDirectorPage; private TMP_Text? _monoDirectorStatus; private TMP_Text? _monoDirectorVersion; private TouchButton? _monoDirectorInstallButton; private TouchButton? _monoDirectorResetButton; private float _nextMonoDirectorRefresh; private float _monoDirectorCommandReadyAt; private float _monoDirectorConfirmationDeadline; private string _pendingMonoDirectorCommand = string.Empty; private float _monoDirectorResetConfirmUntil; private readonly RaycastHit[] _portalRayHits = (RaycastHit[])(object)new RaycastHit[12]; private readonly PortalTraversalGuard _portalEntranceGuard = new PortalTraversalGuard(); private readonly PortalTraversalGuard _portalExitGuard = new PortalTraversalGuard(); private readonly List<PortalDestinationEntry> _portalLevels = new List<PortalDestinationEntry>(); private readonly List<PortalDestinationEntry> _installedPortalLevels = new List<PortalDestinationEntry>(); private readonly List<FusionServerEntry> _fusionServers = new List<FusionServerEntry>(); private readonly List<TouchButton> _fusionServerRows = new List<TouchButton>(); private readonly List<RawImage> _fusionServerRowArtwork = new List<RawImage>(); private readonly List<TMP_Text> _fusionServerRowNames = new List<TMP_Text>(); private readonly List<TMP_Text> _fusionServerRowDetails = new List<TMP_Text>(); private readonly List<TMP_Text> _fusionServerRowPopulation = new List<TMP_Text>(); private readonly string[] _fusionServerRowArtworkCodes = new string[4]; private readonly int[] _fusionServerRowArtworkGenerations = new int[4]; private readonly Dictionary<string, LevelCrate> _portalLevelCrates = new Dictionary<string, LevelCrate>(StringComparer.OrdinalIgnoreCase); private readonly Dictionary<long, ModIoMod> _portalOnlineMods = new Dictionary<long, ModIoMod>(); private GameObject? _portalsPage; private GameObject? _portalLevelsPage; private GameObject? _fusionServersPage; private GameObject? _fusionServerDetailsPage; private TMP_Text? _portalStatus; private TMP_Text? _portalModeText; private TouchButton? _portalLocalButton; private TouchButton? _portalWorldButton; private TouchButton? _portalRemoveButton; private TouchButton? _portalLevelsButton; private TouchButton? _portalServersButton; private TouchButton? _portalLevelActionButton; private TMP_Text? _portalLevelName; private TMP_Text? _portalLevelDetails; private TMP_Text? _portalLevelPosition; private TMP_Text? _portalLevelStatus; private TouchButton? _fusionServerActionButton; private TouchButton? _fusionServerJoinButton; private RawImage? _fusionServerArtwork; private TMP_Text? _fusionServerListPosition; private TMP_Text? _fusionServerListStatus; private TMP_Text? _fusionServerName; private TMP_Text? _fusionServerDetails; private TMP_Text? _fusionServerPosition; private TMP_Text? _fusionServerStatus; private TMP_Text? _fusionServerDescription; private TMP_Text? _fusionServerMembers; private PortalPlacementStage _portalPlacementStage; private PortalEndpoint? _pendingPortalEntrance; private PortalLinkState? _activePortal; private bool _activePortalLocallyCreated; private int _portalGeneration; private bool _portalTriggerHeld; private float _portalPointerReadyAt; private LineRenderer? _portalPointerLine; private GameObject? _portalPointerReticle; private Renderer? _portalPointerRenderer; private Material? _portalMaterial; private MaterialPropertyBlock? _portalPropertyBlock; private BoneHubPortalVisual? _portalVisual; private Vector3 _previousPortalHead; private bool _hasPreviousPortalHead; private float _nextPortalLevelScan; private string _selectedPortalLevelBarcode = string.Empty; private int _portalLevelIndex; private string _portalLevelQuery = string.Empty; private int _portalLevelSearchGeneration; private int _fusionServerRequestGeneration; private int _fusionQuickJoinGeneration; private bool _fusionServerRefreshPending; private float _lastFusionServerRefreshAt = -100f; private float _fusionServerRefreshDeadline; private int _fusionServerIndex; private int _fusionServerPageIndex; private string _selectedFusionServerCode = string.Empty; private string _fusionServerCodeInput = string.Empty; private float _nextPortalStatusRefresh; private int _fusionServerArtworkGeneration; private string _fusionServerArtworkCode = string.Empty; private long _pendingPortalLevelModId; private float _pendingPortalLevelDeadline; private float _nextPortalInstallScan; private readonly HashSet<string> _portalLevelBarcodesBeforeDownload = new HashSet<string>(StringComparer.OrdinalIgnoreCase); private IEnumerator<SpawnableCrate>? _spawnLabCatalogEnumerator; private readonly Dictionary<string, long> _spawnLabPalletOwners = new Dictionary<string, long>(StringComparer.OrdinalIgnoreCase); private readonly HashSet<string> _spawnLabBarcodesBeforeDownload = new HashSet<string>(StringComparer.OrdinalIgnoreCase); private string _selectedSpawnLabIdentity = string.Empty; private bool _spawnLabOnlineSearching; private bool _spawnLabInstallRescanStarted; private float _nextSpawnLabPreviewAt; private SpawnableEntry? _pendingSpawnLabPreview; private string _spawnLabPreviewRequestIdentity = string.Empty; private string _spawnLabLoadedPreviewIdentity = string.Empty; private string _spawnLabColorUpgradeIdentity = string.Empty; private SpawnDeliveryMode _spawnLabMode; private readonly Dictionary<SpawnDeliveryMode, TouchButton> _spawnLabModeButtons = new Dictionary<SpawnDeliveryMode, TouchButton>(); private float _nextSpawnLabSubmissionAt; private bool _spawnLabCatalogRequested; private float _nextSpawnLabCatalogRetry; private readonly Dictionary<long, SpawnLabRequestRecord> _spawnLabRequests = new Dictionary<long, SpawnLabRequestRecord>(); private readonly Dictionary<long, SpawnOwnershipRecord> _unresolvedSpawnLabOrphanRisks = new Dictionary<long, SpawnOwnershipRecord>(); private readonly List<FrozenSpawnRecord> _frozenSpawnLabObjects = new List<FrozenSpawnRecord>(); private readonly PooleeLifecycleRegistry _spawnLabLifecycle = PooleeLifecycleRegistry.Shared; private readonly ISpawnApiAdapter _localSpawnAdapter = new NativeAssetSpawnerAdapter(); private readonly ISpawnApiAdapter _fusionSpawnAdapter = new LabFusionSpawnAdapter(); private readonly Collider[] _spawnResultFallbackColliders = (Collider[])(object)new Collider[96]; private readonly Dictionary<string, SpawnGeometryProfile> _spawnGeometryProfiles = new Dictionary<string, SpawnGeometryProfile>(StringComparer.OrdinalIgnoreCase); private long _nextSpawnLabRequestId; private int _spawnPlacementGeneration; private float _nextSpawnRequestMaintenanceAt; private SpawnerInteractionState _spawnInteractionState; private SpawnPreviewState _spawnPreviewState = (SpawnPreviewState)3; private bool _spawnPlacementSurfaceSupported; private bool _spawnPlacementSurfaceValid; private string _spawnPlacementInvalidReason = "NO SURFACE"; private SpawnOwnershipRecord? _spawnLabDeleteOwnership; private ushort? _spawnLabDeleteFusionEntity; private GameObject? _utilitiesPage; private TouchButton? _wallGrabUtilityButton; private TouchButton? _godModeUtilityButton; private TouchButton? _superStrengthUtilityButton; private TouchButton? _infiniteAmmoUtilityButton; private TouchButton? _headlightUtilityButton; private TMP_Text? _utilitiesStatus; private float _nextUtilitiesRefresh; private static TMP_FontAsset? _osWordmarkFont; private static bool _osWordmarkFontResolved; private readonly List<OsControlRow> _codeModRows = new List<OsControlRow>(4); private readonly List<CodeModSnapshot> _codeModItems = new List<CodeModSnapshot>(); private readonly List<LoadedCodeModSnapshot> _loadedCodeMods = new List<LoadedCodeModSnapshot>(); private readonly ConcurrentQueue<string> _sdkOpenRequests = new ConcurrentQueue<string>(); private readonly ConcurrentQueue<SdkNotification> _sdkNotifications = new ConcurrentQueue<SdkNotification>(); private readonly List<IWristHubAppProvider> _sdkAutoProviders = new List<IWristHubAppProvider>(); private const int MiniMapTextureSize = 32; private readonly Color[] _miniMapPixels = (Color[])(object)new Color[1024]; private readonly float[] _miniMapHeights = new float[1024]; private readonly byte[] _miniMapCellState = new byte[1024]; private readonly List<MiniMapPeerVisual> _miniMapPeerVisuals = new List<MiniMapPeerVisual>(16); private readonly List<GameObject> _miniMapPortalDots = new List<GameObject>(2); private readonly List<SpawnableEntry> _spawnableEntries = new List<SpawnableEntry>(); private readonly List<SpawnableEntry> _spawnableCatalog = new List<SpawnableEntry>(); private readonly List<SpawnableEntry> _spawnableCatalogBuild = new List<SpawnableEntry>(); private readonly List<SpawnLabBrowserItem> _spawnableBrowserItems = new List<SpawnLabBrowserItem>(); private readonly List<SpawnLabRow> _spawnableRows = new List<SpawnLabRow>(6); private readonly Dictionary<long, ModIoMod> _spawnableOnlineMods = new Dictionary<long, ModIoMod>(); private readonly Dictionary<string, SpawnableCrate> _spawnableCrates = new Dictionary<string, SpawnableCrate>(StringComparer.OrdinalIgnoreCase); private readonly Dictionary<string, SpawnableCrate> _spawnableCratesBuild = new Dictionary<string, SpawnableCrate>(StringComparer.OrdinalIgnoreCase); private readonly Dictionary<string, HandheldEligibilityResult> _spawnHandEligibility = new Dictionary<string, HandheldEligibilityResult>(StringComparer.OrdinalIgnoreCase); private readonly Dictionary<string, Quaternion> _spawnPreviewPresentationRotations = new Dictionary<string, Quaternion>(StringComparer.OrdinalIgnoreCase); private readonly Dictionary<int, SpawnCallbackLease> _spawnPreviewCallbackLeases = new Dictionary<int, SpawnCallbackLease>(); private readonly RaycastHit[] _miniMapHits = (RaycastHit[])(object)new RaycastHit[12]; private readonly RaycastHit[] _spawnPointerHits = (RaycastHit[])(object)new RaycastHit[64]; private readonly List<GameObject> _spawnedObjects = new List<GameObject>(32); private readonly List<SpawnOwnershipRecord> _spawnOwnership = new List<SpawnOwnershipRecord>(64); private readonly List<GameObject> _jollyThemeObjects = new List<GameObject>(160); private Texture2D? _jollyNullbodyTexture; private readonly WristOsRouter _osRouter = new WristOsRouter(); private WristOsAppId? _pendingQuestAppOpen; private float _pendingQuestAppOpenAt; private float _nextQuestOsMaintenanceAt; private readonly FusionMiniMapBridge _miniMapFusion = new FusionMiniMapBridge(); private readonly List<FusionMiniMapPeer> _miniMapPeers = new List<FusionMiniMapPeer>(16); private readonly List<Renderer> _miniMapSuppressedRenderers = new List<Renderer>(64); private GameObject? _codeModsPage; private GameObject? _miniMapPage; private GameObject? _spawnablesPage; private GameObject? _spawnLabMorePanel; private GameObject? _notificationsPage; private GameObject? _osToolsPage; private TMP_Text? _osStatusText; private TMP_Text? _osWordmarkText; private TMP_Text? _osWordmarkGlowText; private TMP_Text? _osSuffixText; private TMP_Text? _osSuffixGlowText; private TouchButton? _osFriendsButton; private TMP_Text? _codeModTitle; private TMP_Text? _codeModPosition; private TouchButton? _codeModCommunityButton; private TMP_Text? _miniMapHeading; private TMP_Text? _miniMapRangeText; private TMP_Text? _miniMapScanText; private TouchButton? _miniMapModeButton; private TMP_Text? _miniMapRangeValue; private TMP_Text? _spawnableName; private TMP_Text? _spawnableDetails; private TMP_Text? _spawnablePosition; private TMP_Text? _spawnableStatus; private TMP_Text? _notificationText; private TMP_Text? _notificationTitleText; private TMP_Text? _notificationMetaText; private readonly List<TouchButton> _notificationActionButtons = new List<TouchButton>(2); private TouchButton? _notificationFocusButton; private IReadOnlyList<WristNotification> _notificationItems = Array.Empty<WristNotification>(); private int _notificationIndex; private GameObject? _spawnablePreview; private GameObject? _spawnableNativePreview; private GameObject? _spawnPlacementPreview; private GameObject? _spawnPointerReticle; private LineRenderer? _spawnPointerLine; private LineRenderer? _spawnPreviewPullTether; private LineRenderer? _spawnPreviewPullRing; private TouchButton? _spawnActionButton; private TouchButton? _spawnInHandButton; private TouchButton? _spawnableModeButton; private TouchButton? _spawnableFavoriteButton; private TouchButton? _spawnDeleteButton; private TouchButton? _spawnLabBackButton; private Mesh? _spawnablePreviewMesh; private PreviewResourceHandle? _spawnablePreviewResources; private Material? _spawnWorldPreviewMaterial; private int _spawnWorldPreviewLayer; private MiniMapPeerOverlay? _miniMapPeerOverlay; private RectTransform? _miniMapViewport; private RectTransform? _miniMapGeometryRoot; private Image? _miniMapLayoutImage; private Texture2D? _miniMapLayoutTexture; private Sprite? _miniMapLayoutSprite; private GameObject? _miniMapCaptureObject; private Camera? _miniMapCaptureCamera; private RenderTexture? _miniMapCaptureTexture; private RenderTexture? _miniMapCaptureBackTexture; private RawImage? _miniMapSceneImage; private RectTransform? _miniMapBearingRoot; private RectTransform? _miniMapMarkerRoot; private object? _activeBoneMenuPage; private object? _managerControlCachePage; private IReadOnlyList<ReflectedManagerControl> _managerControlCache = Array.Empty<ReflectedManagerControl>(); private float _managerControlCacheUntil; private object? _activeCodeModStringElement; private WristHubApp? _activeSdkApp; private WristHubPage? _activeSdkPage; private WristHubTextInput? _activeSdkText; private RuntimeSdkHost? _sdkHost; private bool _showLegacyBoneMenu; private bool _optionsReturnToHub; private string _latestSdkNotification = string.Empty; private WristKeyboardPurpose _wristKeyboardPurpose; private string _codeModQuery = string.Empty; private CodeModCatalogMode _codeModCatalogMode; private string _spawnableQuery = string.Empty; private TouchButton? _keyboardSubmitButton; private int _codeModPageIndex; private int _spawnableIndex; private int _spawnablePageIndex; private int _spawnablePreviewGeneration; private int _spawnableSearchGeneration; private int _spawnableManifestCount = -1; private int _spawnableCatalogCursor; private int _spawnableCatalogLastRefreshCursor; private int _spawnableCatalogGeneration; private bool _spawnPlacementActive; private bool _spawnDeleteActive; private bool _spawnTriggerHeld; private float _spawnTriggerReleasedAt; private bool _spawnableCatalogBuilding; private Hand? _spawnPlacementHand; private float _spawnPlacementYaw; private float _spawnPlacementDistance = 1.5f; private GameObject? _pendingGrabObject; private Hand? _pendingGrabHand; private SpawnableEntry? _pendingGrabEntry; private Grip? _pendingGrabSelectedGrip; private long _pendingGrabRequestId; private SpawnAttachmentPhysicsState? _pendingGrabPhysics; private readonly Queue<PendingSpawnAttachment> _pendingSpawnAttachmentQueue = new Queue<PendingSpawnAttachment>(); private float _pendingGrabDeadline; private float _nextPendingGrabAttempt; private bool _pendingGrabSnatchStarted; private bool _pendingGrabActivationConfirmed; private float _pendingGrabSnatchAt; private int _pendingGrabStableGripId; private float _pendingGrabGripStableAt; private bool _pendingGrabAttachFallbackAttempted; private float _pendingGrabStartedAt; private GameObject? _latchedGrabObject; private Hand? _latchedGrabHand; private Grip? _latchedGrabGrip; private SpawnableEntry? _latchedGrabEntry; private long _latchedGrabRequestId; private bool _latchedGrabSawNeutral; private bool _latchedGrabSawPress; private bool _latchedGrabActivationConfirmed; private bool _latchedGrabReleaseOnGripUp; private float _nextLatchedGrabRepair; private float _latchedGrabDetachedAt; private float _latchedGrabDeadline; private float _spawnPointerReadyAt; private Material? _spawnPointerMaterial; private Material? _spawnPreviewPullMaterial; private MaterialPropertyBlock? _spawnPointerBlock; private Renderer? _spawnPointerRenderer; private bool _spawnPreviewHeld; private bool _spawnPreviewRequiresGripRelease; private bool _spawnPreviewReleaseIsLeft; private bool _spawnPreviewReturning; private bool _spawnPreviewUsesPinch; private bool _spawnPreviewIsLeft; private bool _spawnPreviewHalfPulse; private bool _spawnPreviewLeftGripWasActive; private bool _spawnPreviewRightGripWasActive; private bool _spawnPreviewLeftPinchWasActive; private bool _spawnPreviewRightPinchWasActive; private Hand? _spawnPreviewHand; private SpawnableEntry? _spawnPreviewPullEntry; private Transform? _spawnPreviewHomeParent; private Vector3 _spawnPreviewHomeLocalPosition; private Quaternion _spawnPreviewHomeLocalRotation = Quaternion.identity; private Vector3 _spawnPreviewHomeLocalScale = Vector3.one; private Bounds _spawnPreviewLocalBounds; private bool _spawnPreviewBoundsValid; private Vector3 _spawnPreviewDetachedBaseScale = Vector3.one; private Vector3 _spawnPreviewDragLocalOffset; private Quaternion _spawnPreviewDragRotationOffset = Quaternion.identity; private Vector3 _spawnPreviewReturnFrom; private Quaternion _spawnPreviewReturnRotation = Quaternion.identity; private Vector3 _spawnPreviewReturnScale = Vector3.one; private float _spawnPreviewTrackingLostAt = -1f; private float _spawnPreviewReturnStarted; private float _spawnPreviewGrabReadyAt; private bool _spawnPreviewMaterialized; private int _spawnPreviewLastPercent = -1; private Quaternion _spawnPreviewBaseLocalRotation = Quaternion.identity; private float _spawnPreviewYaw; private float _spawnPreviewPitch; private float _spawnPreviewLastInteractionAt; private bool _spawnPreviewRotateActive; private Hand? _spawnPreviewRotateHand; private Vector3 _spawnPreviewRotateLastEuler; private SpawnableBrowseMode _spawnableMode; private string _spawnableActiveGroup = string.Empty; private long _pendingSpawnableModId; private float _pendingSpawnableDeadline; private float _nextSpawnableInstallScan; private float _nextCodeModRefresh; private float _nextLoadedCodeModScan; private float _nextSdkProviderScan; private string _lastCodeModCatalogSignature = string.Empty; private float _nextMiniMapPeerRefresh; private float _nextMiniMapOverlayRender; private float _nextMiniMapOverlayRecovery; private bool _miniMapOverlayDiagnosticLogged; private bool _miniMapOverlayRecovering; private int _lastMiniMapDegree = -1; private int _lastMiniMapPeerCount = -1; private int _miniMapRasterCursor; private int _miniMapRasterSamplesRemaining; private bool _miniMapTextureDirty; private float _nextMiniMapTextureUpload; private float _nextMiniMapSceneCapture; private bool _miniMapCaptureValid; private Vector3 _miniMapCaptureCenter; private Vector3 _miniMapPendingCaptureCenter; private Vector3 _miniMapScanOrigin; private float _miniMapReferenceFloorY; private bool _miniMapScanOriginValid; private Color Cyan => WristHubThemePalette.Accent(_preferences.HologramTheme); private Color DimCyan => WristHubThemePalette.Dim(_preferences.HologramTheme); public bool IsOpen { get { if ((Object)(object)_browser != (Object)null) { return _browser.activeSelf; } return false; } } public bool IsLauncherOpen { get { if ((Object)(object)_root != (Object)null) { return _root.activeSelf; } return false; } } public bool IsClosing => (int)_lifecycle == 3; public string Query => _query; private string ActiveModStoreQuery { get { if (_modStoreCatalog != ModStoreCatalog.ModIo) { return _thunderstoreQuery; } return _itemStoreQuery; } } private int ActiveModStoreIndex { get { if (_modStoreCatalog != ModStoreCatalog.ModIo) { return _thunderstoreIndex; } return _itemStoreIndex; } set { if (_modStoreCatalog == ModStoreCatalog.ModIo) { _itemStoreIndex = value; } else { _thunderstoreIndex = value; } } } private int PendingSpawnLabRequestCount => _spawnLabRequests.Values.Count(delegate(SpawnLabRequestRecord request) { //IL_000e: Unknown result type (might be due to invalid IL or missing references) //IL_0013: Unknown result type (might be due to invalid IL or missing references) //IL_0014: Unknown result type (might be due to invalid IL or missing references) //IL_0016: Invalid comparison between Unknown and I4 bool flag = !request.Terminal; if (flag) { SpawnRequestState state = request.State; bool flag2 = (int)state <= 6; flag = flag2; } return flag; }); private bool SpawnLabHasPendingRequests => PendingSpawnLabRequestCount > 0; public event Action? Closed; private void AttachAppLibrarySdkEvents() { if (!_appLibrarySdkEventsAttached) { WristHubApi.AppsChanged += OnAppLibrarySdkAppsChanged; _appLibrarySdkEventsAttached = true; } } private void OnAppLibrarySdkAppsChanged() { _mainThread.Enqueue(delegate { if (!_disposed) { _nextAppLibraryRefresh = 0f; RefreshHomeAppTiles(); GameObject? appLibraryPage = _appLibraryPage; if (appLibraryPage != null && appLibraryPage.activeSelf) { RefreshAppLibrary(force: true); } } }); } private void BuildWristOsHomePageTwo(Transform parent) { //IL_0015: Unknown result type (might be due to invalid IL or missing references) //IL_001f: Expected O, but got Unknown //IL_0055: Unknown result type (might be due to invalid IL or missing references) //IL_005f: Unknown result type (might be due to invalid IL or missing references) //IL_0094: Unknown result type (might be due to invalid IL or missing references) //IL_009f: Unknown result type (might be due to invalid IL or missing references) //IL_00d6: Unknown result type (might be due to invalid IL or missing references) //IL_00ea: Unknown result type (might be due to invalid IL or missing references) //IL_00f0: Unknown result type (might be due to invalid IL or missing references) //IL_0131: Unknown result type (might be due to invalid IL or missing references) //IL_014a: Unknown result type (might be due to invalid IL or missing references) //IL_017a: Unknown result type (might be due to invalid IL or missing references) //IL_0185: Unknown result type (might be due to invalid IL or missing references) if (!((Object)(object)_hub == (Object)null)) { _osHomePageTwo = new GameObject("WristHubOS Home Page Two"); _osHomePageTwo.transform.SetParent(parent, false); CreateText(_osHomePageTwo.transform, "APPS", new TabletRect(0f, 73f, 150f, 22f), 14f, Color.white, (TextAlignmentOptions)514, (FontStyles)1); CreateText(_osHomePageTwo.transform, "WRISTHUBOS LIBRARY", new TabletRect(0f, 59f, 150f, 10f), 5.2f, Cyan, (TextAlignmentOptions)514, (FontStyles)1); CreateButton(_hub, _osHomePageTwo.transform, "<", new Vector3(-0.14f, 0.07f, 0.008f), new Vector3(0.026f, 0.024f, 0.014f), DimCyan, delegate { ShowOsHomePage(0); }, 16f); CreateImage(_osHomePageTwo.transform, "Phone App Shelf", new TabletRect(0f, -7f, 282f, 122f), new Color(0.008f, 0.045f, 0.068f, 0.8f)); _osHomePageDotsTwo = CreateText(_osHomePageTwo.transform, "○ ●", new TabletRect(0f, -75f, 70f, 14f), 7f, Cyan, (TextAlignmentOptions)514, (FontStyles)1); for (int num = 0; num < 9; num++) { BuildHomeAppTile(num); } _osHomePageTwo.SetActive(false); RefreshHomeAppTiles(); RefreshJollyThemeDecorations(); } } private void BuildHomeAppTile(int index) { //IL_0077: Unknown result type (might be due to invalid IL or missing references) //IL_008b: Unknown result type (might be due to invalid IL or missing references) //IL_00a4: Unknown result type (might be due to invalid IL or missing references) //IL_00e7: Unknown result type (might be due to invalid IL or missing references) //IL_0100: Unknown result type (might be due to invalid IL or missing references) //IL_0130: Unknown result type (might be due to invalid IL or missing references) //IL_013b: Unknown result type (might be due to invalid IL or missing references) //IL_016c: Unknown result type (might be due to invalid IL or missing references) //IL_0177: Unknown result type (might be due to invalid IL or missing references) //IL_01ae: Unknown result type (might be due to invalid IL or missing references) //IL_01b8: Unknown result type (might be due to invalid IL or missing references) //IL_0217: Unknown result type (might be due to invalid IL or missing references) //IL_0221: Unknown result type (might be due to invalid IL or missing references) if (!((Object)(object)_hub == (Object)null) && !((Object)(object)_osHomePageTwo == (Object)null)) { int num = index % 3; int num2 = index / 3; float num3 = -0.084f + (float)num * 0.084f; float num4 = 0.033f - (float)num2 * 0.043f; int captured = index; TouchButton touchButton = CreateButton(_hub, _osHomePageTwo.transform, string.Empty, new Vector3(num3, num4, 0.008f), new Vector3(0.058f, 0.037f, 0.014f), new Color(0.025f, 0.13f, 0.18f, 0.94f), delegate { ActivateHomeAppTile(captured); }, 6f); CreateImage(touchButton.Root.transform, "Rounded Icon Plate", new TabletRect(0f, 5f, 27f, 27f), new Color(0.05f, 0.25f, 0.32f, 0.95f)); RawImage artwork = CreateRawImage(touchButton.Root.transform, "App Icon", new TabletRect(0f, 5f, 23f, 23f), _placeholderTexture, Color.clear); TMP_Text monogram = CreateText(touchButton.Root.transform, string.Empty, new TabletRect(0f, 5f, 25f, 22f), 8f, Cyan, (TextAlignmentOptions)514, (FontStyles)1); TMP_Text val = CreateText(touchButton.Root.transform, string.Empty, new TabletRect(0f, -13f, 52f, 10f), 5.2f, Color.white, (TextAlignmentOptions)514, (FontStyles)1); val.enableAutoSizing = true; val.fontSizeMin = 3.4f; val.fontSizeMax = 5.2f; val.overflowMode = (TextOverflowModes)1; TMP_Text badge = CreateText(touchButton.Root.transform, string.Empty, new TabletRect(20f, 14f, 20f, 8f), 4.8f, Amber, (TextAlignmentOptions)514, (FontStyles)1); _homeAppTiles.Add(new AppLibraryTile { Button = touchButton, Artwork = artwork, Monogram = monogram, Name = val, Badge = badge }); } } private void ShowOsHomePage(int page) { _osHomePageIndex = Math.Clamp(page, 0, 1); GameObject? osToolsPage = _osToolsPage; if (osToolsPage != null) { osToolsPage.SetActive(false); } if (_osHomePageIndex == 1 && (Object)(object)_osHomePageTwo == (Object)null) { GameObject? osHomePageOne = _osHomePageOne; if ((Object)(object)((osHomePageOne != null) ? osHomePageOne.transform.parent : null) != (Object)null) { RunGuarded("Quest second Home page preparation", delegate { BuildWristOsHomePageTwo(_osHomePageOne.transform.parent); }); } } GameObject? osHomePageOne2 = _osHomePageOne; if (osHomePageOne2 != null) { osHomePageOne2.SetActive(_osHomePageIndex == 0); } GameObject? osHomePageTwo = _osHomePageTwo; if (osHomePageTwo != null) { osHomePageTwo.SetActive(_osHomePageIndex == 1); } if (_osHomePageIndex == 1) { RefreshHomeAppTiles(); } RequireInputRearm(); } private void RefreshHomeAppTiles() { //IL_00df: Unknown result type (might be due to invalid IL or missing references) //IL_0108: Unknown result type (might be due to invalid IL or missing references) if (_homeAppTiles.Count == 0) { return; } Dictionary<string, WristHubApp> dictionary = (from app in WristHubApi.GetRegisteredApps() where app.Available select app).ToDictionary<WristHubApp, string>((WristHubApp app) => app.Id, StringComparer.OrdinalIgnoreCase); for (int num = 0; num < _homeAppTiles.Count; num++) { AppLibraryTile appLibraryTile = _homeAppTiles[num]; if (num == 0) { int count = dictionary.Count; appLibraryTile.Item = null; appLibraryTile.Button.Root.SetActive(true); appLibraryTile.Name.text = "APPS"; appLibraryTile.Monogram.text = "▦"; appLibraryTile.Badge.text = ((count > 0) ? count.ToString() : string.Empty); ((Graphic)appLibraryTile.Artwork).color = Color.clear; ((Graphic)appLibraryTile.Button.Panel).color = new Color(0.1f, 0.43f, 0.55f, 1f); } else { int num2 = num - 1; if (num2 >= _preferences.PinnedWristAppIds.Count || !dictionary.TryGetValue(_preferences.PinnedWristAppIds[num2], out var value)) { appLibraryTile.Button.Root.SetActive(false); appLibraryTile.Item = null; } else { WristAppLibraryItem item = new WristAppLibraryItem(value, FindSdkPackage(value)); appLibraryTile.Button.Root.SetActive(true); PaintAppTile(appLibraryTile, item); } } } } private void ActivateHomeAppTile(int slot) { if (slot == 0) { ShowAppLibraryPage(); } else if (slot >= 0 && slot < _homeAppTiles.Count) { WristHubApp val = _homeAppTiles[slot].Item?.App; if (val != null) { LaunchSdkApp(val); } } } private void BuildAppLibraryPage() { //IL_0023: Unknown result type (might be due to invalid IL or missing references) //IL_002d: Expected O, but got Unknown //IL_006e: Unknown result type (might be due to invalid IL or missing references) //IL_0087: Unknown result type (might be due to invalid IL or missing references) //IL_00b7: Unknown result type (might be due to invalid IL or missing references) //IL_00c1: Unknown result type (might be due to invalid IL or missing references) //IL_00fc: Unknown result type (might be due to invalid IL or missing references) //IL_0110: Unknown result type (might be due to invalid IL or missing references) //IL_0116: Unknown result type (might be due to invalid IL or missing references) //IL_0158: Unknown result type (might be due to invalid IL or missing references) //IL_016c: Unknown result type (might be due to invalid IL or missing references) //IL_0172: Unknown result type (might be due to invalid IL or missing references) //IL_01b4: Unknown result type (might be due to invalid IL or missing references) //IL_01c8: Unknown result type (might be due to invalid IL or missing references) //IL_01cd: Unknown result type (might be due to invalid IL or missing references) //IL_0210: Unknown result type (might be due to invalid IL or missing references) //IL_0224: Unknown result type (might be due to invalid IL or missing references) //IL_022a: Unknown result type (might be due to invalid IL or missing references) //IL_0271: Unknown result type (might be due to invalid IL or missing references) //IL_0285: Unknown result type (might be due to invalid IL or missing references) //IL_028b: Unknown result type (might be due to invalid IL or missing references) //IL_02d2: Unknown result type (might be due to invalid IL or missing references) //IL_02e6: Unknown result type (might be due to invalid IL or missing references) //IL_02ec: Unknown result type (might be due to invalid IL or missing references) //IL_0333: Unknown result type (might be due to invalid IL or missing references) //IL_0347: Unknown result type (might be due to invalid IL or missing references) //IL_034c: Unknown result type (might be due to invalid IL or missing references) //IL_0391: Unknown result type (might be due to invalid IL or missing references) //IL_039b: Unknown result type (might be due to invalid IL or missing references) //IL_03b6: Unknown result type (might be due to invalid IL or missing references) //IL_03c0: Expected O, but got Unknown //IL_0415: Unknown result type (might be due to invalid IL or missing references) //IL_0429: Unknown result type (might be due to invalid IL or missing references) //IL_042f: Unknown result type (might be due to invalid IL or missing references) //IL_0470: Unknown result type (might be due to invalid IL or missing references) //IL_047b: Unknown result type (might be due to invalid IL or missing references) //IL_04b6: Unknown result type (might be due to invalid IL or missing references) //IL_04ca: Unknown result type (might be due to invalid IL or missing references) //IL_04d0: Unknown result type (might be due to invalid IL or missing references) if (!((Object)(object)_browser == (Object)null) && !((Object)(object)_canvasRoot == (Object)null)) { _appLibraryPage = new GameObject("WristHubOS Installed Apps"); _appLibraryPage.transform.SetParent(_canvasRoot.transform, false); CreateImage(_appLibraryPage.transform, "Apps Glass", new TabletRect(0f, 0f, 310f, 188f), new Color(0.005f, 0.023f, 0.037f, 0.975f)); _appLibraryTitle = CreateText(_appLibraryPage.transform, "INSTALLED APPS", new TabletRect(-24f, 80f, 150f, 22f), 14f, Color.white, (TextAlignmentOptions)514, (FontStyles)1); CreateButton(_browser, _appLibraryPage.transform, "BACK", new Vector3(-0.137f, 0.08f, 0.008f), new Vector3(0.036f, 0.023f, 0.014f), DimCyan, BackFromAppLibrary, 7f); CreateButton(_browser, _appLibraryPage.transform, "SEARCH", new Vector3(0.094f, 0.08f, 0.008f), new Vector3(0.052f, 0.023f, 0.014f), DimCyan, OpenAppLibraryKeyboard, 6.5f); CreateButton(_browser, _appLibraryPage.transform, "X", new Vector3(0.139f, 0.08f, 0.008f), new Vector3(0.026f, 0.023f, 0.014f), Red, Close, 15f); _appLibraryInstalledTab = CreateButton(_browser, _appLibraryPage.transform, "INSTALLED", new Vector3(-0.093f, 0.055f, 0.008f), new Vector3(0.061f, 0.02f, 0.012f), Cyan, delegate { SetAppLibraryTab((WristAppLibraryTab)0); }, 5.7f); _appLibraryStoreTab = CreateButton(_browser, _appLibraryPage.transform, "STORE", new Vector3(-0.03f, 0.055f, 0.008f), new Vector3(0.052f, 0.02f, 0.012f), DimCyan, delegate { SetAppLibraryTab((WristAppLibraryTab)1); }, 5.8f); _appLibraryUpdatesTab = CreateButton(_browser, _appLibraryPage.transform, "UPDATES", new Vector3(0.031f, 0.055f, 0.008f), new Vector3(0.064f, 0.02f, 0.012f), DimCyan, delegate { SetAppLibraryTab((WristAppLibraryTab)2); }, 5.7f); _appLibraryManageButton = CreateButton(_browser, _appLibraryPage.transform, "MANAGE", new Vector3(0.105f, 0.055f, 0.008f), new Vector3(0.06f, 0.02f, 0.012f), Surface, ToggleAppLibraryManageMode, 5.2f); _appLibraryRestartBanner = CreateText(_appLibraryPage.transform, string.Empty, new TabletRect(0f, -64f, 220f, 11f), 5.6f, Amber, (TextAlignmentOptions)514, (FontStyles)1); _appLibraryGridRoot = new GameObject("App Grid"); _appLibraryGridRoot.transform.SetParent(_appLibraryPage.transform, false); for (int num = 0; num < 8; num++) { BuildAppLibraryTile(num); } CreateButton(_browser, _appLibraryGridRoot.transform, "<", new Vector3(-0.127f, -0.082f, 0.008f), new Vector3(0.04f, 0.021f, 0.012f), DimCyan, delegate { MoveAppLibraryPage(-1); }, 15f); _appLibraryPosition = CreateText(_appLibraryGridRoot.transform, "1 / 1", new TabletRect(0f, -82f, 70f, 16f), 7f, Cyan, (TextAlignmentOptions)514, (FontStyles)1); CreateButton(_browser, _appLibraryGridRoot.transform, ">", new Vector3(0.127f, -0.082f, 0.008f), new Vector3(0.04f, 0.021f, 0.012f), DimCyan, delegate { MoveAppLibraryPage(1); }, 15f); BuildAppLibraryDetail(); _appLibraryPage.SetActive(false); } } private void BuildAppLibraryTile(int index) { //IL_0077: Unknown result type (might be due to invalid IL or missing references) //IL_008b: Unknown result type (might be due to invalid IL or missing references) //IL_00a4: Unknown result type (might be due to invalid IL or missing references) //IL_00e7: Unknown result type (might be due to invalid IL or missing references) //IL_0100: Unknown result type (might be due to invalid IL or missing references) //IL_0130: Unknown result type (might be due to invalid IL or missing references) //IL_013b: Unknown result type (might be due to invalid IL or missing references) //IL_016c: Unknown result type (might be due to invalid IL or missing references) //IL_0177: Unknown result type (might be due to invalid IL or missing references) //IL_01ae: Unknown result type (might be due to invalid IL or missing references) //IL_01b8: Unknown result type (might be due to invalid IL or missing references) //IL_0217: Unknown result type (might be due to invalid IL or missing references) //IL_0221: Unknown result type (might be due to invalid IL or missing references) if (!((Object)(object)_browser == (Object)null) && !((Object)(object)_appLibraryGridRoot == (Object)null)) { int num = index % 4; int num2 = index / 4; float num3 = -0.108f + (float)num * 0.072f; float num4 = 0.02f - (float)num2 * 0.052f; int captured = index; TouchButton touchButton = CreateButton(_browser, _appLibraryGridRoot.transform, string.Empty, new Vector3(num3, num4, 0.008f), new Vector3(0.058f, 0.047f, 0.012f), new Color(0.022f, 0.11f, 0.155f, 0.98f), delegate { ActivateAppLibraryTile(captured); }, 5f); CreateImage(touchButton.Root.transform, "App Icon Plate", new TabletRect(0f, 7f, 29f, 29f), new Color(0.045f, 0.23f, 0.3f, 0.98f)); RawImage artwork = CreateRawImage(touchButton.Root.transform, "App Artwork", new TabletRect(0f, 7f, 25f, 25f), _placeholderTexture, Color.clear); TMP_Text monogram = CreateText(touchButton.Root.transform, string.Empty, new TabletRect(0f, 7f, 25f, 22f), 8f, Cyan, (TextAlignmentOptions)514, (FontStyles)1); TMP_Text val = CreateText(touchButton.Root.transform, string.Empty, new TabletRect(0f, -15f, 52f, 11f), 5.1f, Color.white, (TextAlignmentOptions)514, (FontStyles)1); val.enableAutoSizing = true; val.fontSizeMin = 3.2f; val.fontSizeMax = 5.1f; val.overflowMode = (TextOverflowModes)1; TMP_Text badge = CreateText(touchButton.Root.transform, string.Empty, new TabletRect(18f, 18f, 22f, 8f), 3.9f, Amber, (TextAlignmentOptions)514, (FontStyles)1); _appLibraryTiles.Add(new AppLibraryTile { Button = touchButton, Artwork = artwork, Monogram = monogram, Name = val, Badge = badge }); } } private void BuildAppLibraryDetail() { //IL_0023: Unknown result type (might be due to invalid IL or missing references) //IL_002d: Expected O, but got Unknown //IL_006e: Unknown result type (might be due to invalid IL or missing references) //IL_0087: Unknown result type (might be due to invalid IL or missing references) //IL_00b7: Unknown result type (might be due to invalid IL or missing references) //IL_00c1: Unknown result type (might be due to invalid IL or missing references) //IL_00fb: Unknown result type (might be due to invalid IL or missing references) //IL_0106: Unknown result type (might be due to invalid IL or missing references) //IL_0140: Unknown result type (might be due to invalid IL or missing references) //IL_015e: Unknown result type (might be due to invalid IL or missing references) //IL_0198: Unknown result type (might be due to invalid IL or missing references) //IL_01a2: Unknown result type (might be due to invalid IL or missing references) //IL_01dd: Unknown result type (might be due to invalid IL or missing references) //IL_01f1: Unknown result type (might be due to invalid IL or missing references) //IL_01f7: Unknown result type (might be due to invalid IL or missing references) //IL_023a: Unknown result type (might be due to invalid IL or missing references) //IL_024e: Unknown result type (might be due to invalid IL or missing references) //IL_0253: Unknown result type (might be due to invalid IL or missing references) //IL_029a: Unknown result type (might be due to invalid IL or missing references) //IL_02ae: Unknown result type (might be due to invalid IL or missing references) //IL_02b4: Unknown result type (might be due to invalid IL or missing references) //IL_02fb: Unknown result type (might be due to invalid IL or missing references) //IL_030f: Unknown result type (might be due to invalid IL or missing references) //IL_0314: Unknown result type (might be due to invalid IL or missing references) if (!((Object)(object)_browser == (Object)null) && !((Object)(object)_appLibraryPage == (Object)null)) { _appLibraryDetailRoot = new GameObject("App Details"); _appLibraryDetailRoot.transform.SetParent(_appLibraryPage.transform, false); CreateImage(_appLibraryDetailRoot.transform, "Details Card", new TabletRect(0f, -8f, 254f, 116f), new Color(0.018f, 0.085f, 0.115f, 0.98f)); _appDetailName = CreateText(_appLibraryDetailRoot.transform, string.Empty, new TabletRect(0f, 31f, 220f, 24f), 13f, Color.white, (TextAlignmentOptions)514, (FontStyles)1); _appDetailAuthor = CreateText(_appLibraryDetailRoot.transform, string.Empty, new TabletRect(0f, 12f, 220f, 15f), 7.5f, Cyan, (TextAlignmentOptions)514, (FontStyles)0); _appDetailDescription = CreateText(_appLibraryDetailRoot.transform, string.Empty, new TabletRect(0f, -13f, 218f, 30f), 7.2f, new Color(0.78f, 0.88f, 0.92f, 1f), (TextAlignmentOptions)514, (FontStyles)0); _appDetailStatus = CreateText(_appLibraryDetailRoot.transform, string.Empty, new TabletRect(0f, -39f, 218f, 15f), 6.6f, Amber, (TextAlignmentOptions)514, (FontStyles)1); CreateButton(_browser, _appLibraryDetailRoot.transform, "GRID", new Vector3(-0.113f, -0.073f, 0.008f), new Vector3(0.046f, 0.024f, 0.012f), DimCyan, CloseAppLibraryDetail, 7f); _appDetailPin = CreateButton(_browser, _appLibraryDetailRoot.transform, "PIN", new Vector3(-0.058f, -0.073f, 0.008f), new Vector3(0.054f, 0.024f, 0.012f), Surface, ToggleSelectedAppPin, 7f); _appDetailAction = CreateButton(_browser, _appLibraryDetailRoot.transform, "OPEN", new Vector3(0.015f, -0.073f, 0.008f), new Vector3(0.086f, 0.024f, 0.012f), Cyan, ActivateAppDetailAction, 7f); _appDetailUninstall = CreateButton(_browser, _appLibraryDetailRoot.transform, "UNINSTALL", new Vector3(0.104f, -0.073f, 0.008f), new Vector3(0.07f, 0.024f, 0.012f), Red, UninstallSelectedSdkApp, 5.8f); _appLibraryDetailRoot.SetActive(false); } } private void ShowAppLibraryPage() { if ((Object)(object)_appLibraryPage == (Object)null) { RunGuarded("Quest Installed Apps preparation", BuildAppLibraryPage); } _nextSdkProviderScan = 0f; DiscoverSdkAppProviders(force: true); ShowWristOsPage(_appLibraryPage, (AvatarBrowserViewState)12); _appLibraryDetailItem = null; GameObject? appLibraryDetailRoot = _appLibraryDetailRoot; if (appLibraryDetailRoot != null) { appLibraryDetailRoot.SetActive(false); } GameObject? appLibraryGridRoot = _appLibraryGridRoot; if (appLibraryGridRoot != null) { appLibraryGridRoot.SetActive(true); } _nextAppLibraryRefresh = 0f; RefreshAppLibrary(force: true); _log.Msg("WristHubOS Installed Apps opened."); } private void BackFromAppLibrary() { GameObject? appLibraryDetailRoot = _appLibraryDetailRoot; if (appLibraryDetailRoot != null && appLibraryDetailRoot.activeSelf) { CloseAppLibraryDetail(); return; } ShowHub(); ShowOsHomePage(1); } private void OpenAppLibraryKeyboard() { _wristKeyboardPurpose = WristKeyboardPurpose.WristApps; OpenWristOsKeyboard(_preferences.WristAppSearchQuery, "SEARCH WRIST APPS"); } private void SubmitAppLibrarySearch(string query) { _preferences.WristAppSearchQuery = query.Trim(); _appLibraryPageIndex = 0; SavePreferences(); ShowAppLibraryPage(); } private void SetAppLibraryTab(WristAppLibraryTab tab) { //IL_0006: Unknown result type (might be due to invalid IL or missing references) _preferences.WristAppLibraryTab = tab; _appLibraryPageIndex = 0; _appLibraryDetailItem = null; GameObject? appLibraryDetailRoot = _appLibraryDetailRoot; if (appLibraryDetailRoot != null) { appLibraryDetailRoot.SetActive(false); } GameObject? appLibraryGridRoot = _appLibraryGridRoot; if (appLibraryGridRoot != null) { appLibraryGridRoot.SetActive(true); } SavePreferences(); RefreshAppLibrary(force: true); } private void ToggleAppLibraryManageMode() { _appLibraryManageMode = !_appLibraryManageMode; RefreshAppLibrary(force: true); } private void MoveAppLibraryPage(int direction) { int num = Math.Max(1, (int)Math.Ceiling((double)_appLibraryItems.Count / 8.0)); _appLibraryPageIndex = (_appLibraryPageIndex + Math.Sign(direction) + num) % num; PaintAppLibraryGrid(); } private void RefreshAppLibrary(bool force) { GameObject? appLibraryPage = _appLibraryPage; if (appLibraryPage != null && appLibraryPage.activeSelf && (force || !(Time.unscaledTime < _nextAppLibraryRefresh))) { _nextAppLibraryRefresh = Time.unscaledTime + 1f; _sdkStorePackages.Clear(); if (_thunderstoreStore.Available) { _sdkStorePackages.AddRange(_thunderstoreStore.SnapshotWristHubApps(_preferences.WristAppSearchQuery, _constrainedRendering)); } BuildAppLibraryItems(); PaintAppLibraryGrid(); PaintAppLibraryTabs(); GameObject? appLibraryDetailRoot = _appLibraryDetailRoot; if (appLibraryDetailRoot != null && appLibraryDetailRoot.activeSelf) { PaintAppLibraryDetail(); } } } private void BuildAppLibraryItems() { //IL_0044: Unknown result type (might be due to invalid IL or missing references) //IL_0117: Unknown result type (might be due to invalid IL or missing references) //IL_011d: Invalid comparison between Unknown and I4 _appLibraryItems.Clear(); string query = _preferences.WristAppSearchQuery; WristHubApp[] array = (from val in WristHubApi.GetRegisteredApps() where val.Available && AppMatches(val, query) select val).ToArray(); if ((int)_preferences.WristAppLibraryTab == 0) { WristHubApp[] array2 = array; foreach (WristHubApp app in array2) { _appLibraryItems.Add(new WristAppLibraryItem(app, FindSdkPackage(app))); } foreach (ThunderstorePackageEntry package in _sdkStorePackages.Where((ThunderstorePackageEntry thunderstorePackageEntry) => thunderstorePackageEntry.Installed || thunderstorePackageEntry.Pending)) { if (!_appLibraryItems.Any((WristAppLibraryItem item) => item.Package?.PackageNamespace.Equals(package.PackageNamespace, StringComparison.OrdinalIgnoreCase) ?? false)) { _appLibraryItems.Add(new WristAppLibraryItem(null, package)); } } } else { IEnumerable<ThunderstorePackageEntry> enumerable; if ((int)_preferences.WristAppLibraryTab != 2) { IEnumerable<ThunderstorePackageEntry> sdkStorePackages = _sdkStorePackages; enumerable = sdkStorePackages; } else { enumerable = _sdkStorePackages.Where((ThunderstorePackageEntry thunderstorePackageEntry) => thunderstorePackageEntry.HasUpdate || thunderstorePackageEntry.Pending); } foreach (ThunderstorePackageEntry item in enumerable) { _appLibraryItems.Add(new WristAppLibraryItem(FindSdkApp(item), item)); } } _appLibraryItems.Sort((WristAppLibraryItem left, WristAppLibraryItem right) => string.Compare(left.Name, right.Name, StringComparison.OrdinalIgnoreCase)); } private void PaintAppLibraryTabs() { //IL_0019: Unknown result type (might be due to invalid IL or missing references) //IL_004c: Unknown result type (might be due to invalid IL or missing references) //IL_0052: Invalid comparison between Unknown and I4 //IL_0029: Unknown result type (might be due to invalid IL or missing references) //IL_0021: Unknown result type (might be due to invalid IL or missing references) //IL_0080: Unknown result type (might be due to invalid IL or missing references) //IL_0086: Invalid comparison between Unknown and I4 //IL_005d: Unknown result type (might be due to invalid IL or missing references) //IL_0055: Unknown result type (might be due to invalid IL or missing references) //IL_0090: Unknown result type (might be due to invalid IL or missing references) //IL_0089: Unknown result type (might be due to invalid IL or missing references) //IL_0105: Unknown result type (might be due to invalid IL or missing references) //IL_010a: Unknown result type (might be due to invalid IL or missing references) //IL_010b: Unknown result type (might be due to invalid IL or missing references) //IL_010d: Invalid comparison between Unknown and I4 //IL_010f: Unknown result type (might be due to invalid IL or missing references) //IL_0111: Invalid comparison between Unknown and I4 //IL_00e0: Unknown result type (might be due to invalid IL or missing references) //IL_00d9: Unknown result type (might be due to invalid IL or missing references) if (_appLibraryInstalledTab != null) { ((Graphic)_appLibraryInstalledTab.Panel).color = (((int)_preferences.WristAppLibraryTab == 0) ? Cyan : DimCyan); } if (_appLibraryStoreTab != null) { ((Graphic)_appLibraryStoreTab.Panel).color = (((int)_preferences.WristAppLibraryTab == 1) ? Cyan : DimCyan); } if (_appLibraryUpdatesTab != null) { ((Graphic)_appLibraryUpdatesTab.Panel).color = (((int)_preferences.WristAppLibraryTab == 2) ? Amber : DimCyan); } if (_appLibraryManageButton != null) { _appLibraryManageButton.Label.text = (_appLibraryManageMode ? "DONE" : "MANAGE"); ((Graphic)_appLibraryManageButton.Panel).color = (_appLibraryManageMode ? Amber : Surface); } if ((Object)(object)_appLibraryTitle != (Object)null) { TMP_Text appLibraryTitle = _appLibraryTitle; WristAppLibraryTab wristAppLibraryTab = _preferences.WristAppLibraryTab; string text = (((int)wristAppLibraryTab == 1) ? "APP STORE" : (((int)wristAppLibraryTab != 2) ? "INSTALLED APPS" : "APP UPDATES")); appLibraryTitle.text = text; } if ((Object)(object)_appLibraryRestartBanner != (Object)null) { _appLibraryRestartBanner.text = (_sdkStorePackages.Any((ThunderstorePackageEntry package) => package.Pending) ? "RESTART BONELAB TO FINISH QUEUED APP CHANGES" : string.Empty); } } private void PaintAppLibraryGrid() { int num = Math.Max(1, (int)Math.Ceiling((double)_appLibraryItems.Count / 8.0)); _appLibraryPageIndex = Math.Clamp(_appLibraryPageIndex, 0, num - 1); if ((Object)(object)_appLibraryPosition != (Object)null) { _appLibraryPosition.text = $"{_appLibraryPageIndex + 1} / {num}"; } for (int i = 0; i < _appLibraryTiles.Count; i++) { int num2 = _appLibraryPageIndex * 8 + i; AppLibraryTile appLibraryTile = _appLibraryTiles[i]; if (num2 >= _appLibraryItems.Count) { appLibraryTile.Button.Root.SetActive(false); appLibraryTile.Item = null; } else { appLibraryTile.Button.Root.SetActive(true); PaintAppTile(appLibraryTile, _appLibraryItems[num2]); } } } private void PaintAppTile(AppLibraryTile tile, WristAppLibraryItem item) { //IL_00bd: Unknown result type (might be due to invalid IL or missing references) //IL_00a2: Unknown result type (might be due to invalid IL or missing references) //IL_0087: Unknown result type (might be due to invalid IL or missing references) tile.Item = item; tile.Name.text = item.Name; TMP_Text monogram = tile.Monogram; WristHubApp? app = item.App; monogram.text = AppMonogram((app != null) ? app.Icon : null, item.Name); Image panel = tile.Button.Panel; ThunderstorePackageEntry? package = item.Package; ? color; if ((object)package == null || !package.Pending) { ThunderstorePackageEntry? package2 = item.Package; color = (((object)package2 != null && package2.HasUpdate) ? new Color(0.3f, 0.22f, 0.06f, 1f) : new Color(0.026f, 0.15f, 0.2f, 0.98f)); } else { color = new Color(0.38f, 0.24f, 0.05f, 1f); } ((Graphic)panel).color = (Color)color; TMP_Text badge = tile.Badge; ThunderstorePackageEntry? package3 = item.Package; object text; if ((object)package3 == null || !package3.Pending) { ThunderstorePackageEntry? package4 = item.Package; if ((object)package4 == null || !package4.HasUpdate) { if (item.App == null || _preferences.OpenedWristAppIds.Contains<string>(item.App.Id, StringComparer.OrdinalIgnoreCase)) { WristHubApp? app2 = item.App; text = ((app2 != null && app2.Notifications > 0) ? item.App.Notifications.ToString() : string.Empty); } else { text = "NEW"; } } else { text = "UPDATE"; } } else { text = "QUEUED"; } badge.text = (string)text; AssignAppArtwork(tile, item); } private void AssignAppArtwork(AppLibraryTile tile, WristAppLibraryItem item) { //IL_0030: Unknown result type (might be due to invalid IL or missing references) //IL_0133: Unknown result type (might be due to invalid IL or missing references) //IL_0074: Unknown result type (might be due to invalid IL or missing references) //IL_0079: Unknown result type (might be due to invalid IL or missing references) //IL_009a: Expected O, but got Unknown tile.ArtworkIdentity = item.Identity; ((Graphic)tile.Artwork).color = Color.clear; WristHubApp? app = item.App; WristHubIconData val = ((app != null) ? app.RichIcon : null); if (val != null) { if (!_sdkRichIcons.TryGetValue(item.App.Id, out Texture2D value)) { try { value = new Texture2D(2, 2, (TextureFormat)4, false) { name = "WristHub App Icon " + SafeSdkId(item.App.Id) }; if (!ImageConversion.LoadImage(value, Il2CppStructArray<byte>.op_Implicit(val.Png.ToArray()), true) || ((Texture)value).width > 256 || ((Texture)value).height > 256) { Object.Destroy((Object)(object)value); return; } _sdkRichIcons[item.App.Id] = value; _sdkRichIconOrder.Enqueue(item.App.Id); TrimSdkRichIconCache(); } catch { return; } } tile.Artwork.texture = (Texture)(object)value; ((Graphic)tile.Artwork).color = Color.white; tile.Monogram.text = string.Empty; return; } string text = item.Package?.IconUrl; if (string.IsNullOrWhiteSpace(text)) { return; } string identity = tile.ArtworkIdentity; int generation = _appArtworkGeneration; _thumbnails.Load(text, delegate(Texture2D texture) { //IL_005e: Unknown result type (might be due to invalid IL or missing references) if (!_disposed && generation == _appArtworkGeneration && !(tile.ArtworkIdentity != identity) && !((Object)(object)texture == (Object)null)) { tile.Artwork.texture = (Texture)(object)texture; ((Graphic)tile.Artwork).color = Color.white; tile.Monogram.text = string.Empty; } }); } private void ActivateAppLibraryTile(int slot) { //IL_002e: Unknown result type (might be due to invalid IL or missing references) if (slot < 0 || slot >= _appLibraryTiles.Count) { return; } WristAppLibraryItem item = _appLibraryTiles[slot].Item; if ((object)item == null) { return; } if ((int)_preferences.WristAppLibraryTab == 0 && !_appLibraryManageMode && item.App != null) { LaunchSdkApp(item.App); return; } _appLibraryDetailItem = item; GameObject? appLibraryGridRoot = _appLibraryGridRoot; if (appLibraryGridRoot != null) { appLibraryGridRoot.SetActive(false); } GameObject? appLibraryDetailRoot = _appLibraryDetailRoot; if (appLibraryDetailRoot != null) { appLibraryDetailRoot.SetActive(true); } PaintAppLibraryDetail(); RequireInputRearm(); } private void PaintAppLibraryDetail() { //IL_0180: Unknown result type (might be due to invalid IL or missing references) //IL_0179: Unknown result type (might be due to invalid IL or missing references) //IL_0172: Unknown result type (might be due to invalid IL or missing references) //IL_0211: Unknown result type (might be due to invalid IL or missing references) //IL_0218: Unknown result type (might be due to invalid IL or missing references) WristAppLibraryItem appLibraryDetailItem = _appLibraryDetailItem; if (!(appLibraryDetailItem == null)) { if ((Object)(object)_appDetailName != (Object)null) { _appDetailName.text = appLibraryDetailItem.Name; } if ((Object)(object)_appDetailAuthor != (Object)null) { _appDetailAuthor.text = $"By {appLibraryDetailItem.Author} • v{appLibraryDetailItem.Version} • {AppPlatformLabel(appLibraryDetailItem)}"; } if ((Object)(object)_appDetailDescription != (Object)null) { _appDetailDescription.text = (string.IsNullOrWhiteSpace(appLibraryDetailItem.Description) ? "WristHub SDK app" : appLibraryDetailItem.Description); } ThunderstorePackageEntry package = appLibraryDetailItem.Package; if ((Object)(object)_appDetailStatus != (Object)null) { _appDetailStatus.text = ((package == null) ? "PACKAGE NOT LINKED • INSTALLED APP" : (package.Pending ? "QUEUED • RESTART REQUIRED" : (package.HasUpdate ? "UPDATE AVAILABLE" : ((!package.Installed) ? "CODE APP • RUNS WITH NORMAL MOD PERMISSIONS AFTER RESTART" : ((appLibraryDetailItem.App == null) ? "INSTALLED • RESTART OR APP DID NOT REGISTER" : "INSTALLED • READY"))))); ((Graphic)_appDetailStatus).color = ((((object)package != null && package.Pending) || ((object)package != null && package.HasUpdate)) ? Amber : ((appLibraryDetailItem.App != null) ? Green : Cyan)); } if (_appDetailAction != null) { _appDetailAction.Label.text = (((object)package != null && package.Pending) ? "QUEUED" : (((object)package != null && package.HasUpdate) ? "UPDATE" : (((object)package != null && !package.Installed) ? "INSTALL" : ((appLibraryDetailItem.App != null) ? "OPEN" : "RESTART")))); ((Graphic)_appDetailAction.Panel).color = ((((object)package != null && package.HasUpdate) || ((object)package != null && !package.Installed)) ? Amber : Cyan); } bool flag = appLibraryDetailItem.App != null; if (_appDetailPin != null) { _appDetailPin.Root.SetActive(flag); _appDetailPin.Label.text = ((flag && _preferences.PinnedWristAppIds.Contains<string>(appLibraryDetailItem.App.Id, StringComparer.OrdinalIgnoreCase)) ? "UNPIN" : "PIN HOME"); } if (_appDetailUninstall != null) { _appDetailUninstall.Root.SetActive((object)package != null && package.Installed && !package.Pending); } } } private void CloseAppLibraryDetail() { _appLibraryDetailItem = null; GameObject? appLibraryDetailRoot = _appLibraryDetailRoot; if (appLibraryDetailRoot != null) { appLibraryDetailRoot.SetActive(false); } GameObject? appLibraryGridRoot = _appLibraryGridRoot; if (appLibraryGridRoot != null) { appLibraryGridRoot.SetActive(true); } RequireInputRearm(); } private void ActivateAppDetailAction() { //IL_0095: Unknown result type (might be due to invalid IL or missing references) //IL_008e: Unknown result type (might be due to invalid IL or missing references) WristAppLibraryItem appLibraryDetailItem = _appLibraryDetailItem; if (appLibraryDetailItem == null) { return; } ThunderstorePackageEntry package = appLibraryDetailItem.Package; if ((object)package != null && package.Pending) { return; } ThunderstorePackageEntry package2 = appLibraryDetailItem.Package; if ((object)package2 != null && (!package2.Installed || package2.HasUpdate)) { string message; bool flag = _thunderstoreStore.TryQueueInstall((package2.FullName.Length == 0) ? package2.Name : package2.FullName, out message); if ((Object)(object)_appDetailStatus != (Object)null) { _appDetailStatus.text = message; ((Graphic)_appDetailStatus).color = (flag ? Amber : Red); } _nextAppLibraryRefresh = Time.unscaledTime + 0.25f; } else if (appLibraryDetailItem.App != null) { LaunchSdkApp(appLibraryDetailItem.App); } } private void ToggleSelectedAppPin() { //IL_0092: Unknown result type (might be due to invalid IL or missing references) WristHubApp app = _appLibraryDetailItem?.App; if (app == null) { return; } int num = _preferences.PinnedWristAppIds.FindIndex((string value) => value.Equals(app.Id, StringComparison.OrdinalIgnoreCase)); if (num >= 0) { _preferences.PinnedWristAppIds.RemoveAt(num); } else { if (_preferences.PinnedWristAppIds.Count >= 8) { if ((Object)(object)_appDetailStatus != (Object)null) { _appDetailStatus.text = "HOME IS FULL • UNPIN AN APP FIRST"; ((Graphic)_appDetailStatus).color = Amber; } return; } _preferences.PinnedWristAppIds.Add(app.Id); } SavePreferences(); RefreshHomeAppTiles(); PaintAppLibraryDetail(); } private void UninstallSelectedSdkApp() { //IL_0061: Unknown result type (might be due to invalid IL or missing references) //IL_005a: Unknown result type (might be due to invalid IL or missing references) ThunderstorePackageEntry thunderstorePackageEntry = _appLibraryDetailItem?.Package; if (thunderstorePackageEntry == null) { return; } string message; bool flag = _thunderstoreStore.TryQueueUninstall(thunderstorePackageEntry.PackageNamespace, out message); if ((Object)(object)_appDetailStatus != (Object)null) { _appDetailStatus.text = message; ((Graphic)_appDetailStatus).color = (flag ? Amber : Red); } if (flag) { WristHubApp app = _appLibraryDetailItem?.App; if (app != null) { _preferences.PinnedWristAppIds.RemoveAll((string value) => value.Equals(app.Id, StringComparison.OrdinalIgnoreCase)); SavePreferences(); RefreshHomeAppTiles(); } } _nextAppLibraryRefresh = Time.unscaledTime + 0.25f; } private void LaunchSdkApp(WristHubApp app) { if (!_preferences.OpenedWristAppIds.Contains<string>(app.Id, StringComparer.OrdinalIgnoreCase)) { _preferences.OpenedWristAppIds.Add(app.Id); SavePreferences(); } OpenRegisteredSdkApp(app.Id); } private ThunderstorePackageEntry? FindSdkPackage(WristHubApp app) { if (app.Store != (WristHubStoreProfile)null) { return _sdkStorePackages.FirstOrDefault((ThunderstorePackageEntry package) => package.PackageNamespace.Equals(app.Store.ThunderstoreNamespace, StringComparison.OrdinalIgnoreCase)); } string normalizedName = NormalizeAppIdentity(app.Name); string normalizedAuthor = NormalizeAppIdentity(app.Author); return _sdkStorePackages.FirstOrDefault((ThunderstorePackageEntry package) => NormalizeAppIdentity(package.Name) == normalizedName && (normalizedAuthor.Length == 0 || NormalizeAppIdentity(package.Author) == normalizedAuthor)); } private WristHubApp? FindSdkApp(ThunderstorePackageEntry package) { return ((IEnumerable<WristHubApp>)WristHubApi.GetRegisteredApps()).FirstOrDefault((Func<WristHubApp, bool>)delegate(WristHubApp app) { WristHubStoreProfile store = app.Store; return (store != null && store.ThunderstoreNamespace.Equals(package.PackageNamespace, StringComparison.OrdinalIgnoreCase)) || (NormalizeAppIdentity(app.Name) == NormalizeAppIdentity(package.Name) && NormalizeAppIdentity(app.Author) == NormalizeAppIdentity(package.Author)); }); } private static bool AppMatches(WristHubApp app, string query) { if (!string.IsNullOrWhiteSpace(query) && !app.Name.Contains(query, StringComparison.OrdinalIgnoreCase) && !app.Author.Contains(query, StringComparison.OrdinalIgnoreCase) && !app.Description.Contains(query, StringComparison.OrdinalIgnoreCase)) { WristHubStoreProfile store = app.Store; if (store == null) { return false; } return store.Tagline.Contains(query, StringComparison.OrdinalIgnoreCase); } return true; } private static string NormalizeAppIdentity(string value) { return new string(value.Where(char.IsLetterOrDigit).Select(char.ToUpperInvariant).ToArray()); } private string AppPlatformLabel(WristAppLibraryItem item) { //IL_002b: Unknown result type (might be due to invalid IL or missing references) //IL_0030: Unknown result type (might be due to invalid IL or missing references) //IL_0031: Unknown result type (might be due to invalid IL or missing references) //IL_0033: Unknown result type (might be due to invalid IL or missing references) //IL_0045: Expected I4, but got Unknown WristHubApp? app = item.App; WristHubStoreProfile val = ((app != null) ? app.Store : null); if (val == null) { if (!_constrainedRendering) { return "PCVR"; } return "QUEST"; } WristHubStorePlatform platforms = val.Platforms; return (platforms - 1) switch { 2 => "PCVR + QUEST", 0 => "PCVR", 1 => "QUEST", _ => "PLATFORM UNKNOWN", }; } private void TrimSdkRichIconCache() { int num = (_constrainedRendering ? 24 : 64); while (_sdkRichIcons.Count > num && _sdkRichIconOrder.Count > 0) { string key = _sdkRichIconOrder.Dequeue(); if (!_sdkRichIcons.Remove(key, out Texture2D value)) { continue; } try { if ((Object)(object)value != (Object)null) { Object.Destroy((Object)(object)value); } } catch { } } } private static string AppMonogram(string? preferred, string name) { string text = preferred?.Trim() ?? string.Empty; int length = text.Length; if (length > 0 && length <= 3) { return text.ToUpperInvariant(); } string[] array = name.Split(' ', StringSplitOptions.RemoveEmptyEntries); if (array.Length < 2) { if (name.Length != 0) { return name.Substring(0, Math.Min(2, name.Length)).ToUpperInvariant(); } return "APP"; } return string.Concat(array[0][0], array[1][0]).ToUpperInvariant(); } private void TickAppLibrary() { TickOsHomeSwipe(); GameObject? appLibraryPage = _appLibraryPage; if (appLibraryPage != null && appLibraryPage.activeSelf) { RefreshAppLibrary(force: false); } } private void TickOsHomeSwipe() { //IL_0095: Unknown result type (might be due to invalid IL or missing references) //IL_009a: Unknown result type (might be due to invalid IL or missing references) //IL_009f: Unknown result type (might be due to invalid IL or missing references) //IL_00ae: Unknown result type (might be due to invalid IL or missing references) //IL_00c0: Unknown result type (might be due to invalid IL or missing references) //IL_010b: Unknown result type (might be due to invalid IL or missing references) //IL_00da: Unknown result type (might be due to invalid IL or missing references) //IL_0126: Unknown result type (might be due to invalid IL or missing references) GameObject? osToolsPage = _osToolsPage; if (osToolsPage != null && osToolsPage.activeSelf) { _homeSwipeProbeId = 0; return; } GameObject? hub = _hub; if (hub == null || !hub.activeSelf) { _homeSwipeProbeId = 0; return; } bool flag = false; foreach (BoneHubFingertipProbe probe in _fingertips.GetProbes()) { if ((Object)(object)probe.Hand == (Object)null || !WristInteractionMath.IsOppositeHand(probe.IsLeft, _preferences.UseLeftWrist)) { continue; } Vector3 val = _hub.transform.InverseTransformPoint(probe.Position); int instanceID = ((Object)probe.Hand).GetInstanceID(); int num; if (Math.Abs(val.z) <= 0.025f) { float y = val.y; if (y >= 0.045f && y <= 0.092f) { num = ((Math.Abs(val.x) <= 0.085f) ? 1 : 0); goto IL_00f2; } } num = 0; goto IL_00f2; IL_00f2: bool flag2 = (byte)num != 0; if (_homeSwipeProbeId == 0 && flag2) { _homeSwipeProbeId = instanceID; _homeSwipeStartX = val.x; flag = true; break; } if (_homeSwipeProbeId == instanceID) { flag = true; float num2 = val.x - _homeSwipeStartX; if (Math.Abs(num2) >= 0.045f) { ShowOsHomePage((num2 < 0f) ? 1 : 0); _homeSwipeProbeId = -instanceID; } break; } } if (!flag) { _homeSwipeProbeId = 0; } } private void ResetAppLibraryReferences(bool destroyOwnedObjects) { if (_appLibrarySdkEventsAttached) { WristHubApi.AppsChanged -= OnAppLibrarySdkAppsChanged; _appLibrarySdkEventsAttached = false; } _appArtworkGeneration++; if (destroyOwnedObjects) { foreach (Texture2D value in _sdkRichIcons.Values) { try { if ((Object)(object)value != (Object)null) { Object.Destroy((Object)(object)value); } } catch { } } } _sdkRichIcons.Clear(); _sdkRichIconOrder.Clear(); _appLibraryTiles.Clear(); _homeAppTiles.Clear(); _appLibraryItems.Clear(); _sdkStorePackages.Clear(); _osHomePageOne = null; _osHomePageTwo = null; _osHomePageDotsOne = null; _osHomePageDotsTwo = null; _appLibraryPage = null; _appLibraryGridRoot = null; _appLibraryDetailRoot = null; _appLibraryTitle = null; _appLibraryPosition = null; _appLibraryRestartBanner = null; _appLibraryInstalledTab = null; _appLibrary
Mods\WristHub.SDK.dll
Decompiled 2 months agousing System; using System.Collections.Generic; using System.Collections.ObjectModel; using System.Diagnostics; using System.Linq; using System.Reflection; using System.Runtime.CompilerServices; using System.Runtime.Versioning; using System.Threading; [assembly: CompilationRelaxations(8)] [assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)] [assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)] [assembly: InternalsVisibleTo("WristHub")] [assembly: InternalsVisibleTo("BoneHub.Tests")] [assembly: TargetFramework(".NETCoreApp,Version=v8.0", FrameworkDisplayName = ".NET 8.0")] [assembly: AssemblyCompany("WristHub.SDK")] [assembly: AssemblyConfiguration("Release")] [assembly: AssemblyDescription("Stable public app and control API for WristHubOS.")] [assembly: AssemblyFileVersion("1.0.0.0")] [assembly: AssemblyInformationalVersion("4.2.1")] [assembly: AssemblyProduct("WristHub.SDK")] [assembly: AssemblyTitle("WristHub.SDK")] [assembly: AssemblyVersion("1.0.0.0")] [module: RefSafetyRules(11)] namespace WristHub.SDK; public static class WristHubApi { public const int ApiVersion = 4; private static readonly object Sync = new object(); private static readonly Dictionary<string, WristHubApp> Apps = new Dictionary<string, WristHubApp>(StringComparer.OrdinalIgnoreCase); private static IWristHubHost? _host; public static bool IsWristHubAvailable { get { lock (Sync) { return _host != null; } } } public static WristHubRuntimeInfo Runtime { get { lock (Sync) { return _host?.Runtime ?? WristHubRuntimeInfo.Unavailable; } } } public static int RegisteredAppCount { get { lock (Sync) { return Apps.Count; } } } public static event Action? AppsChanged; public static event Action<WristHubRuntimeInfo>? RuntimeChanged; public static WristHubApp RegisterApp(WristHubAppDefinition definition) { ArgumentNullException.ThrowIfNull(definition, "definition"); definition.Validate(); WristHubApp wristHubApp; lock (Sync) { if (Apps.ContainsKey(definition.Id)) { throw new InvalidOperationException("A WristHub app with id '" + definition.Id + "' is already registered."); } wristHubApp = new WristHubApp(definition, OnAppChanged, Unregister); Apps.Add(definition.Id, wristHubApp); } SafeRaise(WristHubApi.AppsChanged); return wristHubApp; } public static WristHubApp RegisterApp(string id, string name, string author, string version, string description = "", string icon = "APP") { return RegisterApp(new WristHubAppDefinition(id, name, author, version, description, icon)); } public static WristHubApp RegisterProvider(IWristHubAppProvider provider) { ArgumentNullException.ThrowIfNull(provider, "provider"); WristHubApp wristHubApp = provider.CreateWristHubApp() ?? throw new InvalidOperationException("The WristHub app provider returned no app."); lock (Sync) { if (!Apps.TryGetValue(wristHubApp.Id, out WristHubApp value) || wristHubApp != value) { throw new InvalidOperationException("The provider must return an app registered through WristHubApi.RegisterApp."); } return wristHubApp; } } public static IReadOnlyList<WristHubApp> GetRegisteredApps() { lock (Sync) { return new ReadOnlyCollection<WristHubApp>(Apps.Values.OrderBy<WristHubApp, string>((WristHubApp app) => app.Name, StringComparer.OrdinalIgnoreCase).ToArray()); } } public static void Notify(string appId, string message, WristHubNotificationKind kind = WristHubNotificationKind.Info) { if (string.IsNullOrWhiteSpace(message)) { return; } IWristHubHost host; lock (Sync) { host = _host; } try { host?.Notify(appId?.Trim() ?? string.Empty, message.Trim(), kind); } catch { } } public static void Notify(string appId, WristHubNotificationDefinition notification) { ArgumentNullException.ThrowIfNull(notification, "notification"); notification.Validate(); IWristHubHost host; lock (Sync) { host = _host; } try { host?.Notify(appId?.Trim() ?? string.Empty, notification); } catch { } } public static void OpenApp(string appId) { IWristHubHost host; lock (Sync) { host = _host; } try { host?.RequestOpen(appId?.Trim() ?? string.Empty); } catch { } } internal static void AttachHost(IWristHubHost host) { ArgumentNullException.ThrowIfNull(host, "host"); lock (Sync) { _host = host; } SafeRaise(WristHubApi.RuntimeChanged, host.Runtime); SafeRaise(WristHubApi.AppsChanged); } internal static void DetachHost(IWristHubHost host) { lock (Sync) { if (_host != host) { return; } _host = null; } SafeRaise(WristHubApi.RuntimeChanged, WristHubRuntimeInfo.Unavailable); } private static void Unregister(WristHubApp app) { lock (Sync) { if (Apps.TryGetValue(app.Id, out WristHubApp value) && value == app) { Apps.Remove(app.Id); } } SafeRaise(WristHubApi.AppsChanged); } private static void OnAppChanged() { SafeRaise(WristHubApi.AppsChanged); } private static void SafeRaise(Action? callback) { if (callback == null) { return; } Delegate[] invocationList = callback.GetInvocationList(); for (int i = 0; i < invocationList.Length; i++) { Action action = (Action)invocationList[i]; try { action(); } catch { } } } private static void SafeRaise<T>(Action<T>? callback, T value) { if (callback == null) { return; } Delegate[] invocationList = callback.GetInvocationList(); for (int i = 0; i < invocationList.Length; i++) { Action<T> action = (Action<T>)invocationList[i]; try { action(value); } catch { } } } } internal interface IWristHubHost { WristHubRuntimeInfo Runtime { get; } void Notify(string appId, string message, WristHubNotificationKind kind); void Notify(string appId, WristHubNotificationDefinition notification); void RequestOpen(string appId); } public readonly record struct WristHubRuntimeInfo(bool Available, bool IsQuest, bool IsFusionConnected, string Theme, int ApiVersion) { public static WristHubRuntimeInfo Unavailable => new WristHubRuntimeInfo(Available: false, IsQuest: false, IsFusionConnected: false, "Cyan", 4); } public enum WristHubNotificationKind { Info, Success, Warning, Error } public enum WristHubNotificationPriority { Routine, Normal, Important, Urgent } public enum WristHubNotificationPrivacy { Public, PrivateReveal } public sealed record WristHubNotificationAction(string Id, string Label, Action? Activated = null) { internal void Validate() { WristHubAppDefinition.ValidateText(Id, "Id", 48); WristHubAppDefinition.ValidateText(Label, "Label", 24); } } public sealed record WristHubNotificationDefinition(string Title, string Message, WristHubNotificationKind Kind = WristHubNotificationKind.Info, WristHubNotificationPriority Priority = WristHubNotificationPriority.Normal, WristHubNotificationPrivacy Privacy = WristHubNotificationPrivacy.PrivateReveal, string Icon = "APP", string DeepLink = "", TimeSpan? Lifetime = null, IReadOnlyList<WristHubNotificationAction>? Actions = null) { internal void Validate() { WristHubAppDefinition.ValidateText(Title, "Title", 64); WristHubAppDefinition.ValidateText(Message, "Message", 512); if (!string.IsNullOrWhiteSpace(Icon)) { WristHubAppDefinition.ValidateText(Icon, "Icon", 12); } if (!string.IsNullOrWhiteSpace(DeepLink)) { WristHubAppDefinition.ValidateText(DeepLink, "DeepLink", 128); } TimeSpan? lifetime = Lifetime; if (lifetime.HasValue) { TimeSpan valueOrDefault = lifetime.GetValueOrDefault(); if (valueOrDefault < TimeSpan.FromSeconds(2.0) || valueOrDefault > TimeSpan.FromDays(30.0)) { throw new ArgumentOutOfRangeException("Lifetime"); } } if ((Actions?.Count ?? 0) > 2) { throw new ArgumentOutOfRangeException("Actions"); } if (Actions == null) { return; } foreach (WristHubNotificationAction action in Actions) { action.Validate(); } } } public enum WristHubAccent { Theme, Neutral, Success, Warning, Danger } public enum WristHubCommunityCategory { Utility, Gameplay, Social, Accessibility, Developer, Other } public interface IWristHubAppProvider { WristHubApp CreateWristHubApp(); } [Flags] public enum WristHubStorePlatform { None = 0, PcVr = 1, Quest = 2, All = 3 } public sealed record WristHubStoreProfile(string ThunderstoreNamespace, WristHubCommunityCategory Category, string Tagline, WristHubStorePlatform Platforms = WristHubStorePlatform.All, string Homepage = "", string SupportContact = "") { internal void Validate() { ValidateNamespace(ThunderstoreNamespace); WristHubAppDefinition.ValidateText(Tagline, "Tagline", 120); if (Platforms == WristHubStorePlatform.None || (Platforms & ~WristHubStorePlatform.All) != WristHubStorePlatform.None) { throw new ArgumentOutOfRangeException("Platforms"); } ValidateOptional(Homepage, "Homepage", 240); ValidateOptional(SupportContact, "SupportContact", 96); } internal static void ValidateNamespace(string value) { WristHubAppDefinition.ValidateText(value, "ThunderstoreNamespace", 128); int num = value.IndexOf('-'); if (num <= 0 || num == value.Length - 1 || value.Any(delegate(char character) { bool flag = char.IsLetterOrDigit(character); if (!flag) { bool flag2 = ((character == '-' || character == '_') ? true : false); flag = flag2; } return !flag; })) { throw new ArgumentException("Use the Thunderstore Team-Package namespace without a version.", "ThunderstoreNamespace"); } } private static void ValidateOptional(string? value, string name, int length) { if (!string.IsNullOrEmpty(value) && value.Length > length) { throw new ArgumentOutOfRangeException(name); } } } public sealed class WristHubIconData { private const int MaximumBytes = 524288; private readonly byte[] _png; public ReadOnlyMemory<byte> Png => _png; private WristHubIconData(byte[] png) { _png = png; } public static WristHubIconData FromPng(ReadOnlySpan<byte> png) { int length = png.Length; if ((length < 24 || length > 524288) ? true : false) { throw new ArgumentOutOfRangeException("png"); } if (png[0] != 137 || png[1] != 80 || png[2] != 78 || png[3] != 71 || png[4] != 13 || png[5] != 10 || png[6] != 26 || png[7] != 10) { throw new ArgumentException("Icon data must be a PNG image.", "png"); } return new WristHubIconData(png.ToArray()); } } public sealed record WristHubCommunityProfile(WristHubCommunityCategory Category, string Summary, string Homepage = "", string SupportContact = "", IReadOnlyList<string>? Tags = null) { internal void Validate() { WristHubAppDefinition.ValidateText(Summary, "Summary", 160); ValidateOptional(Homepage, "Homepage", 240); ValidateOptional(SupportContact, "SupportContact", 96); IReadOnlyList<string> tags = Tags; if (tags != null && tags.Count > 8) { throw new ArgumentOutOfRangeException("Tags"); } if (Tags == null) { return; } foreach (string tag in Tags) { WristHubAppDefinition.ValidateText(tag, "Tags", 24); } } private static void ValidateOptional(string? value, string name, int length) { if (!string.IsNullOrEmpty(value) && value.Length > length) { throw new ArgumentOutOfRangeException(name); } } } public sealed record WristHubAppDefinition(string Id, string Name, string Author, string Version, string Description = "", string Icon = "APP") { internal void Validate() { ValidateToken(Id, "Id", 64); ValidateText(Name, "Name", 48); ValidateText(Author, "Author", 48); ValidateText(Version, "Version", 24); string description = Description; if (description != null && description.Length > 240) { throw new ArgumentOutOfRangeException("Description"); } string icon = Icon; if (icon != null && icon.Length > 8) { throw new ArgumentOutOfRangeException("Icon"); } } internal static void ValidateToken(string value, string name, int length) { ValidateText(value, name, length); if (value.Any(delegate(char character) { bool flag = char.IsLetterOrDigit(character); if (!flag) { bool flag2 = ((character == '-' || character == '.' || character == '_') ? true : false); flag = flag2; } return !flag; })) { throw new ArgumentException("Use only letters, numbers, dots, dashes, and underscores.", name); } } internal static void ValidateText(string value, string name, int length) { if (string.IsNullOrWhiteSpace(value)) { throw new ArgumentException("Value is required.", name); } if (value.Length > length) { throw new ArgumentOutOfRangeException(name); } } } public sealed class WristHubApp : IDisposable { private readonly Action _changed; private readonly Action<WristHubApp> _dispose; private int _disposed; public string Id { get; } public string Name { get; } public string Author { get; } public string Version { get; } public string Description { get; } public string Icon { get; } public WristHubPage Root { get; } public bool IsDisposed => _disposed != 0; public Func<bool>? IsAvailable { get; set; } public Func<int>? NotificationCount { get; set; } public WristHubCommunityProfile? Community { get; private set; } public WristHubStoreProfile? Store { get; private set; } public WristHubIconData? RichIcon { get; private set; } public Action? Opened { get; set; } public Action? Closed { get; set; } public bool Available { get { try { return !IsDisposed && (IsAvailable?.Invoke() ?? true); } catch { return false; } } } public int Notifications { get { try { return Math.Clamp(NotificationCount?.Invoke() ?? 0, 0, 99); } catch { return 0; } } } internal WristHubApp(WristHubAppDefinition definition, Action changed, Action<WristHubApp> dispose) { Id = definition.Id.Trim(); Name = definition.Name.Trim(); Author = definition.Author.Trim(); Version = definition.Version.Trim(); Description = definition.Description?.Trim() ?? string.Empty; Icon = (string.IsNullOrWhiteSpace(definition.Icon) ? "APP" : definition.Icon.Trim()); _changed = changed; _dispose = dispose; Root = new WristHubPage("root", Name, null, changed); } public WristHubApp PublishToCommunity(WristHubCommunityProfile profile) { ArgumentNullException.ThrowIfNull(profile, "profile"); profile.Validate(); Community = profile with { Summary = profile.Summary.Trim(), Homepage = (profile.Homepage?.Trim() ?? string.Empty), SupportContact = (profile.SupportContact?.Trim() ?? string.Empty), Tags = profile.Tags?.Select((string tag) => tag.Trim()).Distinct<string>(StringComparer.OrdinalIgnoreCase).ToArray() }; _changed(); return this; } public WristHubApp PublishToStore(WristHubStoreProfile profile) { ArgumentNullException.ThrowIfNull(profile, "profile"); profile.Validate(); Store = profile with { ThunderstoreNamespace = profile.ThunderstoreNamespace.Trim(), Tagline = profile.Tagline.Trim(), Homepage = (profile.Homepage?.Trim() ?? string.Empty), SupportContact = (profile.SupportContact?.Trim() ?? string.Empty) }; _changed(); return this; } public WristHubApp SetIcon(WristHubIconData icon) { ArgumentNullException.ThrowIfNull(icon, "icon"); RichIcon = icon; _changed(); return this; } public void RemoveFromCommunity() { if (!(Community == null)) { Community = null; _changed(); } } public void Refresh() { _changed(); } internal void NotifyOpened() { try { Opened?.Invoke(); } catch { } } internal void NotifyClosed() { try { Closed?.Invoke(); } catch { } } public void Dispose() { if (Interlocked.Exchange(ref _disposed, 1) == 0) { _dispose(this); } } } public sealed class WristHubPage { private readonly object _sync = new object(); private readonly List<WristHubControl> _controls = new List<WristHubControl>(); private readonly Action _changed; public string Id { get; } public string Title { get; set; } public WristHubPage? Parent { get; } public IReadOnlyList<WristHubControl> Controls { get { lock (_sync) { return _controls.ToArray(); } } } internal WristHubPage(string id, string title, WristHubPage? parent, Action changed) { WristHubAppDefinition.ValidateToken(id, "id", 64); WristHubAppDefinition.ValidateText(title, "title", 64); Id = id.Trim(); Title = title.Trim(); Parent = parent; _changed = changed; } public WristHubPage AddPage(string id, string title, string description = "") { WristHubPage wristHubPage = new WristHubPage(id, title, this, _changed); Add(new WristHubPageLink(id, title, wristHubPage, description)); return wristHubPage; } public WristHubAction AddButton(string id, string label, Action action, string description = "", WristHubAccent accent = WristHubAccent.Theme) { return Add(new WristHubAction(id, label, action, description, accent)); } public WristHubToggle AddToggle(string id, string label, Func<bool> read, Action<bool> write, string description = "") { return Add(new WristHubToggle(id, label, read, write, description)); } public WristHubSlider AddSlider(string id, string label, double minimum, double maximum, double step, Func<double> read, Action<double> write, string description = "") { return Add(new WristHubSlider(id, label, minimum, maximum, step, read, write, description)); } public WristHubChoice AddChoice(string id, string label, IReadOnlyList<string> choices, Func<int> read, Action<int> write, string description = "") { return Add(new WristHubChoice(id, label, choices, read, write, description)); } public WristHubTextInput AddText(string id, string label, Func<string> read, Action<string> write, string description = "") { return Add(new WristHubTextInput(id, label, read, write, description)); } public WristHubColorInput AddColor(string id, string label, Func<WristHubColorValue> read, Action<WristHubColorValue> write, IReadOnlyList<WristHubColorValue>? palette = null, string description = "") { return Add(new WristHubColorInput(id, label, read, write, palette, description)); } public WristHubLabel AddLabel(string id, string label, Func<string> read, string description = "") { return Add(new WristHubLabel(id, label, read, description)); } public T Add<T>(T control) where T : WristHubControl { ArgumentNullException.ThrowIfNull(control, "control"); lock (_sync) { if (_controls.Any((WristHubControl existing) => existing.Id.Equals(control.Id, StringComparison.OrdinalIgnoreCase))) { throw new InvalidOperationException($"Control id '{control.Id}' already exists on page '{Id}'."); } _controls.Add(control); } _changed(); return control; } public bool Remove(string id) { bool flag; lock (_sync) { flag = _controls.RemoveAll((WristHubControl control) => control.Id.Equals(id, StringComparison.OrdinalIgnoreCase)) > 0; } if (flag) { _changed(); } return flag; } } public abstract class WristHubControl { public string Id { get; } public string Label { get; set; } public string Description { get; set; } public WristHubAccent Accent { get; set; } public Func<bool>? IsEnabled { get; set; } public bool Enabled { get { try { return IsEnabled?.Invoke() ?? true; } catch { return false; } } } protected WristHubControl(string id, string label, string description, WristHubAccent accent = WristHubAccent.Theme) { WristHubAppDefinition.ValidateToken(id, "id", 64); WristHubAppDefinition.ValidateText(label, "label", 64); Id = id.Trim(); Label = label.Trim(); Description = description?.Trim() ?? string.Empty; Accent = accent; } } public sealed class WristHubAction : WristHubControl { private readonly Action _action; internal WristHubAction(string id, string label, Action action, string description, WristHubAccent accent) : base(id, label, description, accent) { _action = action ?? throw new ArgumentNullException("action"); } public void Invoke() { _action(); } } public sealed class WristHubToggle : WristHubControl { private readonly Func<bool> _read; private readonly Action<bool> _write; public bool Value => _read(); internal WristHubToggle(string id, string label, Func<bool> read, Action<bool> write, string description) : base(id, label, description) { _read = read; _write = write; } public void Toggle() { _write(!Value); } } public sealed class WristHubSlider : WristHubControl { private readonly Func<double> _read; private readonly Action<double> _write; public double Minimum { get; } public double Maximum { get; } public double Step { get; } public double Value => Math.Clamp(_read(), Minimum, Maximum); internal WristHubSlider(string id, string label, double min, double max, double step, Func<double> read, Action<double> write, string description) : base(id, label, description) { if (!double.IsFinite(min) || !double.IsFinite(max) || max <= min) { throw new ArgumentOutOfRangeException("max"); } if (!double.IsFinite(step) || step <= 0.0) { throw new ArgumentOutOfRangeException("step"); } Minimum = min; Maximum = max; Step = step; _read = read; _write = write; } public void Adjust(int direction) { _write(Math.Clamp(Value + (double)Math.Sign(direction) * Step, Minimum, Maximum)); } } public sealed class WristHubChoice : WristHubControl { private readonly Func<int> _read; private readonly Action<int> _write; public IReadOnlyList<string> Choices { get; } public int Index => Math.Clamp(_read(), 0, Choices.Count - 1); public string Value => Choices[Index]; internal WristHubChoice(string id, string label, IReadOnlyList<string> choices, Func<int> read, Action<int> write, string description) : base(id, label, description) { Choices = (from choice in choices?.Where((string choice) => !string.IsNullOrWhiteSpace(choice)) select choice.Trim()).ToArray() ?? throw new ArgumentNullException("choices"); if (Choices.Count == 0) { throw new ArgumentException("At least one choice is required.", "choices"); } _read = read; _write = write; } public void Adjust(int direction) { _write((Index + Math.Sign(direction) + Choices.Count) % Choices.Count); } } public sealed class WristHubTextInput : WristHubControl { private readonly Func<string> _read; private readonly Action<string> _write; public string Value => _read() ?? string.Empty; internal WristHubTextInput(string id, string label, Func<string> read, Action<string> write, string description) : base(id, label, description) { _read = read; _write = write; } public void Set(string value) { _write(value ?? string.Empty); } } public readonly record struct WristHubColorValue(byte Red, byte Green, byte Blue, byte Alpha = byte.MaxValue) { public string Hex => $"#{Red:X2}{Green:X2}{Blue:X2}{Alpha:X2}"; public static WristHubColorValue Cyan => new WristHubColorValue(38, 221, byte.MaxValue); public static WristHubColorValue Graphite => new WristHubColorValue(173, 184, 194); public static WristHubColorValue Crimson => new WristHubColorValue(byte.MaxValue, 61, 79); public static WristHubColorValue Emerald => new WristHubColorValue(61, 240, 140); public static WristHubColorValue Violet => new WristHubColorValue(173, 107, byte.MaxValue); public static WristHubColorValue Amber => new WristHubColorValue(byte.MaxValue, 168, 46); } public sealed class WristHubColorInput : WristHubControl { private static readonly WristHubColorValue[] DefaultPalette = new WristHubColorValue[6] { WristHubColorValue.Cyan, WristHubColorValue.Graphite, WristHubColorValue.Crimson, WristHubColorValue.Emerald, WristHubColorValue.Violet, WristHubColorValue.Amber }; private readonly Func<WristHubColorValue> _read; private readonly Action<WristHubColorValue> _write; public IReadOnlyList<WristHubColorValue> Palette { get; } public WristHubColorValue Value => _read(); internal WristHubColorInput(string id, string label, Func<WristHubColorValue> read, Action<WristHubColorValue> write, IReadOnlyList<WristHubColorValue>? palette, string description) : base(id, label, description) { _read = read; _write = write; Palette = ((palette != null && palette.Count > 0) ? palette.ToArray() : DefaultPalette); } public void Set(WristHubColorValue value) { _write(value); } public void Adjust(int direction) { WristHubColorValue value = Value; int val = -1; for (int i = 0; i < Palette.Count; i++) { if (Palette[i].Equals(value)) { val = i; break; } } val = (Math.Max(0, val) + Math.Sign(direction) + Palette.Count) % Palette.Count; _write(Palette[val]); } } public sealed class WristHubLabel : WristHubControl { private readonly Func<string> _read; public string Value => _read() ?? string.Empty; internal WristHubLabel(string id, string label, Func<string> read, string description) : base(id, label, description, WristHubAccent.Neutral) { _read = read; } } public sealed class WristHubPageLink : WristHubControl { public WristHubPage Page { get; } internal WristHubPageLink(string id, string label, WristHubPage page, string description) : base(id, label, description) { Page = page; } }