commit 4edf25b2d53318182d256d80780fea9bdcaa6982 Author: Alexa <53471801+Alexa-RR@users.noreply.github.com> Date: Wed May 27 01:04:12 2026 +0300 Import december-2020-12-18 from private repo (scrubbed 2026-05-27) Targets Rec Room 2020.12.18 client. Re-import after adding: - DorkNet.ClientMod/dorknet-clientmod.json.template — config schema the launcher's manifest-driven patcher renders into UserData/. - tools/build-patcher-zip.ps1 — produces the dorknet-clientpatch-{key}.zip Release asset the launcher fetches. - .github/workflows/release.yml — auto-publishes both Release assets on v1-december-* tag push. Scrubbed via maintainer-side scrub-for-public.ps1: same drop rules as the prior import, plus build-patcher-zip.ps1 added to the tools/ allow-list so it survives the scrub. diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000..1ddd462 --- /dev/null +++ b/.dockerignore @@ -0,0 +1,72 @@ +# Keep the build context small + reproducible. Anything not strictly +# needed for `dotnet restore && dotnet publish` is excluded. + +# Build artefacts +**/bin/ +**/obj/ +**/*.user +**/*.suo + +# Dev-only configs (Local layer must NEVER ship into a container — +# secrets are passed by env vars on Coolify). +**/appsettings.Local.json +**/appsettings.Development.json + +# Local runtime data +**/data/ +**/server.log +**/*.log + +# IDE noise +.vs/ +.vscode/ +.idea/ + +# Git +.git/ +.gitignore +.gitattributes + +# Reverse-engineering scratch (not needed for production server) +Cpp2ILOutput/ +Cpp2IL_CS/ +Cpp2IL_ISIL/ +Il2CppDump/ +RR_protos/ +RR_ProtobufDefinitions.zip +GameAssembly.dll +global-metadata.dat +il2cpp.h +dump.cs +script.json +RoomPersistenceManager_decompiled.txt + +# Local TLS certs (mkcert) — never copy into the image +*.pem +hosts.dorknet.txt + +# Tools that aren't part of the server runtime +tools/ +Tools/ + +# Vite SPA build leftovers — node_modules is huge and the dist is +# rebuilt fresh by the npm-build stage in the Dockerfile, so neither +# should ship into the docker context. The source under admin-ui/src +# and admin-ui/{package.json,vite.config.ts,...} is what we copy in. +DorkNet.Server/admin-ui/node_modules/ +DorkNet.Server/admin-ui/dist/ +DorkNet.Server/admin-ui/.vite/ +DorkNet.Server/wwwroot/admin/assets/ +DorkNet.Server/wwwroot/admin/index.html +DorkNet.Server/wwwroot/admin/*.map + +# Top-level docs / planning +README.md +LICENSE +NOTICE +BUILD_PROPER_PLAN.md +nginx.conf + +# Other stages of the workflow +*.md +docs/ diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml new file mode 100644 index 0000000..ec5cc46 --- /dev/null +++ b/.github/workflows/release.yml @@ -0,0 +1,55 @@ +name: Release (december-2020-12-18) + +# Auto-publishes dorknet-server + dorknet-clientpatch zips to a +# GitHub Release on tag push. The launcher (on the public main +# branch) fetches the most-recent tag matching its prefix and +# downloads these assets, so the tag format MUST start with the +# branch's release_tag_prefix declared in versions.json. + +on: + push: + tags: + - 'v1-december-*' + +jobs: + release: + runs-on: windows-latest + + env: + VERSION_KEY: december_2020_12_18 + + steps: + - uses: actions/checkout@v4 + + - uses: actions/setup-dotnet@v4 + with: + dotnet-version: '9.0.x' + + # ── Server self-contained publish ──────────────────────────────── + - name: Publish DorkNet.Server (win-x64, self-contained) + run: | + dotnet publish DorkNet.Server/DorkNet.Server.csproj ` + -c Release -r win-x64 --self-contained ` + -p:PublishSingleFile=true ` + -p:IncludeNativeLibrariesForSelfExtract=true ` + -p:EnableCompressionInSingleFile=true ` + -o publish/server + + - name: Zip server publish + run: | + Compress-Archive -Path publish/server/* ` + -DestinationPath dorknet-server-${{ env.VERSION_KEY }}-win-x64.zip + + # ── Client patcher zip (built via tools/build-patcher-zip.ps1) ─── + - name: Build client patcher zip + run: | + .\tools\build-patcher-zip.ps1 -VersionKey ${{ env.VERSION_KEY }} + + # ── Publish both as Release assets ─────────────────────────────── + - name: Upload Release assets + uses: softprops/action-gh-release@v2 + with: + files: | + dorknet-server-${{ env.VERSION_KEY }}-win-x64.zip + dist/dorknet-clientpatch-${{ env.VERSION_KEY }}.zip + fail_on_unmatched_files: true diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..ec03f92 --- /dev/null +++ b/.gitignore @@ -0,0 +1,82 @@ +## .NET +bin/ +obj/ +*.user +*.suo +.vs/ +.vscode/ +*.csproj.bak +dist/ +## Data / secrets +# Local SQLite + uploaded blobs only — DON'T match the source-tree +# DorkNet.Server/Data/ folder (which holds DbContext + Entities/) on +# case-insensitive filesystems. Use the precise runtime data dirs. +DorkNet.Server/bin/Debug/net9.0/data/ +DorkNet.Server/bin/Release/net9.0/data/ +# appsettings.Production.json is intentionally versioned — contains +# only the Postgres provider switch + Kestrel bind address. All real +# secrets come from Coolify env vars at runtime. +tmp/ +## OS +.DS_Store +Thumbs.db + +## Reverse-engineered artifacts (Il2CppDumper / Cpp2IL output, not for commit) +dump.cs +il2cpp.h +script.json +stringliteral.json +Il2CppDump/ +Cpp2ILOutput/ +Cpp2IL_CS/ +Cpp2IL_ISIL/ +RoomPersistenceManager_decompiled.txt + +## Per-machine editor / agent settings +.claude/ +global-metadata.dat +GameAssembly.dll +_wildcard.rec.net+1.pem +_wildcard.rec.net+1-key.pem +dist$ZIPNAME.zip + +## Vite admin SPA build output — derived from DorkNet.Server/admin-ui/ +## via `npm run build` (local) or the admin-ui Dockerfile stage (deploy). +## Each build emits a new hash (assets/index-.js) so committing +## these files leaves a stale index.html → JS hash mismatch the next +## time someone rebuilds. Keep the source committed, regenerate output. +DorkNet.Server/wwwroot/admin/index.html +DorkNet.Server/wwwroot/admin/assets/ +DorkNet.Server/wwwroot/admin/*.map +DorkNet.Server/admin-ui/node_modules/ +DorkNet.Server/admin-ui/dist/ +DorkNet.Server/admin-ui/.vite/ +DorkNet.Server/admin-ui/tsconfig.tsbuildinfo + +## Public-facing site (Vite + React, DorkNet.Server/site/) +## Same reasoning as admin-ui above: hashed output, regenerate per build. +DorkNet.Server/wwwroot/site/index.html +DorkNet.Server/wwwroot/site/assets/ +DorkNet.Server/wwwroot/site/*.map +DorkNet.Server/site/node_modules/ +DorkNet.Server/site/dist/ +DorkNet.Server/site/.vite/ +DorkNet.Server/site/tsconfig.tsbuildinfo + + +steamless/ + +## Local recovery / cache-mining artifacts produced by tools/recover-inventions.ps1 +## and friends. These can run hundreds of MB to multi-GB (HTTPCache snapshots, +## raw .inv bytes staged for re-import, ready-to-import zip bundles). Per-room, +## per-run, regeneratable from the source room zip + a live walkthrough — never +## belongs in source control. +*-cache-snapshot/ +*-cache-snapshot-*/ +*-recovered-inv/ +*-recovered.zip +DorkSquad-recovered/ +ProficioVII-recovered/ +Recovered.zip +Recovered/ +Recovered(*)/ \ No newline at end of file diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..8c50073 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,108 @@ +# DorkNet.Server container image — Coolify deploy target. +# ──────────────────────────────────────────────────────────────────── +# Multi-stage: SDK image builds + publishes, runtime image is the +# slimmer aspnet:9.0 with only the published output. +# +# Build context: repo root (so Tools/ and DorkNet.Models/ are visible +# to ProjectReference + dotnet restore). The .dockerignore at the +# repo root keeps the bin/, obj/, and other dev junk out of the +# context. + +ARG DOTNET_VERSION=9.0 +ARG NODE_VERSION=20 + +# ── admin SPA build stage ────────────────────────────────────────── +# The Vite + React admin UI under DorkNet.Server/admin-ui builds in a +# dedicated node image because the .NET SDK image doesn't ship npm. +# Output is dropped into /wwwroot/admin and copied into the .NET build +# stage just before `dotnet publish`, which is configured to skip its +# own inline npm step via -p:SkipAdminUIBuild=true. +FROM node:${NODE_VERSION}-alpine AS admin-ui +WORKDIR /ui +# Lockfile first for layer caching: when only application source +# changes, the npm install step stays cached. +COPY DorkNet.Server/admin-ui/package.json DorkNet.Server/admin-ui/package-lock.json* ./ +RUN npm install --no-fund --no-audit --loglevel=error +# Now the source. vite.config.ts writes to ../wwwroot/admin/, which +# from /ui is /wwwroot/admin/ — pre-create the parent so Vite's +# emptyOutDir won't refuse to create above its cwd. +COPY DorkNet.Server/admin-ui/ ./ +RUN mkdir -p /wwwroot/admin \ + && npm run build \ + && ls -la /wwwroot/admin \ + && ls -la /wwwroot/admin/assets + +# ── public-facing site build stage ───────────────────────────────── +# Mirrors the admin-ui stage. Source under DorkNet.Server/site, +# output emitted to /wwwroot/site for the .NET stage to copy. +FROM node:${NODE_VERSION}-alpine AS site +WORKDIR /ui +COPY DorkNet.Server/site/package.json DorkNet.Server/site/package-lock.json* ./ +RUN npm install --no-fund --no-audit --loglevel=error +COPY DorkNet.Server/site/ ./ +RUN mkdir -p /wwwroot/site \ + && npm run build \ + && ls -la /wwwroot/site + +# ── build stage ──────────────────────────────────────────────────── +FROM mcr.microsoft.com/dotnet/sdk:${DOTNET_VERSION} AS build +WORKDIR /src + +# Copy csproj files first so `dotnet restore` is cached when only +# source files change. Web SDK pulls in transitive packages from the +# DorkNet.Models project ref, so its csproj needs to be present too. +COPY DorkNet.Server/DorkNet.Server.csproj DorkNet.Server/ +COPY DorkNet.Models/DorkNet.Models.csproj DorkNet.Models/ +RUN dotnet restore DorkNet.Server/DorkNet.Server.csproj + +# Now copy the rest of the .NET source, then drop the prebuilt admin +# SPA into wwwroot/admin so the publish step ships it as static content. +COPY DorkNet.Server/ DorkNet.Server/ +COPY DorkNet.Models/ DorkNet.Models/ +COPY --from=admin-ui /wwwroot/admin/ DorkNet.Server/wwwroot/admin/ +COPY --from=site /wwwroot/site/ DorkNet.Server/wwwroot/site/ + +# Publish. SkipAdminUIBuild=true bypasses the csproj target that would +# otherwise try to invoke npm (which this image doesn't have). +RUN dotnet publish DorkNet.Server/DorkNet.Server.csproj \ + -c Release \ + --no-restore \ + -p:SkipAdminUIBuild=true \ + -o /app/publish + +# ── runtime stage ────────────────────────────────────────────────── +FROM mcr.microsoft.com/dotnet/aspnet:${DOTNET_VERSION} AS runtime +WORKDIR /app + +# curl + wget for healthcheck probes — Coolify v4 defaults to wget for +# its own probe regardless of what the image's HEALTHCHECK uses, so +# both have to be present. The aspnet:9.0 base image strips both by +# default to keep the surface area small. +RUN apt-get update \ + && apt-get install -y --no-install-recommends curl wget \ + && rm -rf /var/lib/apt/lists/* + +# Run as a non-root user — aspnet:9.0 ships with the `app` UID 1654. +# Coolify mounts persistent volumes with write permission for this UID. +USER app + +COPY --from=build --chown=app:app /app/publish . + +# Postgres provider is selected by appsettings.Production.json. +# ASPNETCORE_ENVIRONMENT defaults to Production in the aspnet image +# but we set it explicitly for clarity. +ENV ASPNETCORE_ENVIRONMENT=Production \ + ASPNETCORE_URLS=http://0.0.0.0:8080 \ + DOTNET_RUNNING_IN_CONTAINER=true \ + DOTNET_PRINT_TELEMETRY_MESSAGE=false + +EXPOSE 8080 + +# /healthz returns 503 until the migration + seed pass complete on +# the singleton holding the pg_advisory_xact_lock; other replicas +# block on the lock during the same window. Coolify's default 30s +# probe interval is fine. +HEALTHCHECK --interval=15s --timeout=5s --start-period=60s --retries=3 \ + CMD curl --fail --silent http://localhost:8080/healthz || exit 1 + +ENTRYPOINT ["dotnet", "DorkNet.Server.dll"] diff --git a/DorkNet.ClientMod/DorkNet.ClientMod.csproj b/DorkNet.ClientMod/DorkNet.ClientMod.csproj new file mode 100644 index 0000000..d246de6 --- /dev/null +++ b/DorkNet.ClientMod/DorkNet.ClientMod.csproj @@ -0,0 +1,76 @@ + + + + net6.0 + latest + enable + DorkNet.ClientMod + DorkNet.ClientMod + false + false + $(NoWarn);CS8602;CS8604;CS0436 + + + + + C:\Users\you\Documents\Recnet\dist\RecRoom-Clean-2020.03.10 + $(RecRoomDir)\MelonLoader + $(MelonLoaderDir)\Il2CppAssemblies + + + + + $(MelonLoaderDir)\net6\MelonLoader.dll + false + + + $(MelonLoaderDir)\net6\0Harmony.dll + false + + + $(MelonLoaderDir)\net6\Il2CppInterop.Runtime.dll + false + + + $(MelonLoaderDir)\net6\Il2CppInterop.Common.dll + false + + + + $(Il2CppAssembliesDir)\Il2Cppmscorlib.dll + false + + + $(Il2CppAssembliesDir)\Il2CppSystem.dll + false + + + $(Il2CppAssembliesDir)\Assembly-CSharp.dll + false + + + $(Il2CppAssembliesDir)\Assembly-CSharp-firstpass.dll + false + + + $(Il2CppAssembliesDir)\Photon3Unity3D.dll + false + + + $(Il2CppAssembliesDir)\UnityEngine.CoreModule.dll + false + + + + diff --git a/DorkNet.ClientMod/Mod.cs b/DorkNet.ClientMod/Mod.cs new file mode 100644 index 0000000..a82d206 --- /dev/null +++ b/DorkNet.ClientMod/Mod.cs @@ -0,0 +1,1317 @@ +// MelonLoader port of the DorkNet.ClientPatch BepInEx plugin. Applies +// the minimum set of Harmony patches needed for the 2020 watch to +// connect to a DorkNet server: +// +// 1. URI rewrite — `.rec.net` → user-configured host +// 2. Photon AppId — override the baked-in Photon Cloud AppId +// 3. AuthValues — inject `userid` + `LoginLock` for /photon/customauth +// 4. TLS trust bypass — make BouncyCastle never reject server certs +// +// Config lives in \UserData\dorknet-clientmod.json. The +// install-melon.ps1 wrapper writes that file on install; you can also +// edit it by hand and relaunch the client. +// +// Why string-based AccessTools lookups instead of typeof(Il2Cpp...): +// MelonLoader's Il2CppInterop adds an `Il2Cpp` prefix to game-side +// namespaces (so `RecNet.Core` becomes `Il2CppRecNet.Core`) but the +// exact policy for globally-namespaced types like `PhotonNetwork` and +// for nested types varies across MelonLoader minor versions. Looking +// types up by their original game-side name via AccessTools.TypeByName +// (which searches every loaded assembly) keeps this file working +// regardless of the prefix scheme, and avoids needing a verified +// Il2CppAssemblies directory just to write the source. + +using HarmonyLib; +using MelonLoader; +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.IO; +using System.Reflection; +using System.Text.Json; + +[assembly: MelonInfo(typeof(DorkNet.ClientMod.Mod), "DorkNet ClientMod", "1.0.0", "Dork Squad")] +[assembly: MelonGame(null, null)] + +namespace DorkNet.ClientMod; + +public class Mod : MelonMod +{ + internal static MelonLogger.Instance Log => Instance!.LoggerInstance; + internal static Mod? Instance; + internal static string? DiagnosticsLogPath; + private static readonly Dictionary ResolvedTypeCache = new(); + private readonly HashSet _diagnosticPatchLabels = new(); + private bool _diagnosticCoreRegistered; + private bool _diagnosticGameComplete; + private int _diagnosticRetryFrame; + + // Loader-agnostic config — same .Value-shaped accessor the patches + // would see in the BepInEx port, just backed by a plain JSON file + // instead of BepInEx's TOML config system. Defaults match the BepInEx + // plugin's defaults so dropping this mod in without writing a config + // file gives the same behaviour as the recommended install. + public static class Cfg + { + public static string ServerHost = "localhost"; + public static string PhotonAppId = ""; + public static string PhotonVoiceAppId = ""; + // Forced PhotonServerSettings.PreferredRegion (with HostType + // switched to PhotonCloud) so two watches don't auto-ping + // themselves into different "best" regions and end up in + // parallel Photon rooms sharing only a name. PhotonPatches + // .ApplyForcedRegion prefers the server-told region from + // Matchmaking.LocalRoomInstance.PhotonRegionId when available + // and falls back to this config value on initial boot before + // any /goto has returned. Default matches the server's + // appsettings.json Photon:CloudRegion ("eu"). + public static string PhotonCloudRegion = "eu"; + public static bool InjectAuthValues = true; + public static bool EnableTlsTrustBypass = true; + } + + public override void OnInitializeMelon() + { + Instance = this; + Log.Msg("=== DorkNet ClientMod loading ==="); + LoadConfig(); + DiagnosticPatches.Write("[lifecycle] OnInitializeMelon"); + } + + public override void OnLateInitializeMelon() + { + DiagnosticPatches.Write("[lifecycle] OnLateInitializeMelon"); + Log.Msg("=== Registering client patches ==="); + TryPatch("Uri ctor (string)", typeof(Uri), "ctor", + new[] { typeof(string) }, + prefix: nameof(UriPatches.UriStringCtor_Prefix)); + TryPatch("Uri ctor (string, UriKind)", typeof(Uri), "ctor", + new[] { typeof(string), typeof(UriKind) }, + prefix: nameof(UriPatches.UriStringCtor_Prefix)); + TryPatchByName("PhotonNetwork", "ConnectUsingSettings", + prefix: nameof(PhotonPatches.PhotonAppIdOverride_Prefix)); + // Hook the watch's own AppSettings + AuthValues builders directly. + // Both are private PUNNetworkManager methods with obfuscated 2020.12 + // names — we know they always run before any Photon connect because + // ConnectUsingSettings reads their return values. Postfix logs them + // after construction. + // + // FJOLIPKKIBE returns Photon.Realtime.AppSettings — verified at + // PUNNetworkManager.txt:7046. One bool arg (DOJLKMCEFFB — likely + // "for voice"). Two call paths in the binary. + // EMGGKBALALH returns Photon.Realtime.AuthenticationValues — + // verified at PUNNetworkManager.txt:7560. Builds AuthValues with + // environment + accessToken AuthGetParameters. + TryPatchByName("PUNNetworkManager", "FJOLIPKKIBE", + postfix: nameof(PhotonPatches.LogAppSettings_Postfix)); + TryPatchByName("PUNNetworkManager", "EMGGKBALALH", + postfix: nameof(PhotonPatches.LogAuthValues_Postfix)); + // PUNNetworkManager.OnCustomAuthenticationFailed — accepts whatever + // string-like arg the IL2CPP method takes (declared `string` in + // dump.cs but Il2CppInterop wraps it in a way that breaks + // Harmony's IL emit when we declare `string` directly). Using + // `object` lets Harmony pass through whatever wrapper the IL2CPP + // proxy uses without compile-error. + TryPatchByName("PUNNetworkManager", "OnCustomAuthenticationFailed", + prefix: nameof(PhotonPatches.OnCustomAuthenticationFailed_Prefix)); + // TLS cert-validation bypass — gated behind Cfg.EnableTlsTrustBypass. + // Even when the prefix's return value is "let original run", the + // detour itself still has to be JIT-installed when BouncyCastle + // does its first handshake, and MonoMod's CompileMethodHook + // fatal-CLRs (0x80131506) installing trampolines into these + // third-party interface implementations. Only register the + // detours if the bypass is actually wanted; on a setup with + // valid certs (the default) skip the patch entirely. + if (Cfg.EnableTlsTrustBypass) + { + TryPatchByName("BestHTTP.SecureProtocol.Org.BouncyCastle.Crypto.Tls.ServerOnlyTlsAuthentication", + "NotifyServerCertificate", + prefix: nameof(TlsPatches.NotifyServerCertificate_Prefix)); + TryPatchByName("Org.BouncyCastle.Crypto.Tls.LegacyTlsAuthentication", + "NotifyServerCertificate", + prefix: nameof(TlsPatches.NotifyServerCertificate_Prefix)); + } + // Post-save blob-name refresh. The 2020 watch's + // RoomPersistenceManager.RoomDataBlobName is only ever set by + // /goto-driven flows — neither the SubscriptionUpdateRoom push + // nor SubscriptionUpdatePresence pushes the server fires after + // save propagate into this field. So the PostSaveReloading + // sub-state's MasterReloadRoomAsync deserializes the cached + // bytes from the OLD blob name (the one /goto returned at boot), + // wiping any MakerPen object the user just placed even though + // the new blob is already on S3. + // + // Verified live: after a dorm save with object placement, server + // pushes SubscriptionUpdateRoom + PresenceUpdate both carrying + // dataBlob="dorm_p_v(N+1).dat", but the watch's + // set_RoomDataBlobName trace immediately after the save shows + // old="v(N)" new="v(N)" — no propagation, no fresh download, + // no respawn. Leave+return triggers /goto which DOES update + // RoomDataBlobName, so the fix is to do the same write + // ourselves: when entering the SavingRoomSuccess state on the + // RoomPersistenceManager, read the just-pushed + // Rooms.LocalRoomScene.DataBlobName and force-set + // __instance.RoomDataBlobName to it. That fires the real + // setter → OnRoomDataBlobNameChanged → DownloadRoomDataBlobAsync + // for the new blob → PostSaveReloading picks up the fresh + // bytes and respawns the MakerPen objects. + TryPatchByName("RecRoom.Persistence.RoomPersistenceManager", + "set_RoomDataBlobName", + args: new[] { typeof(string) }, + prefix: nameof(SavePatches.SetRoomDataBlobName_Prefix)); + // Intercept the SubscriptionUpdateRoom push BEFORE the watch's + // (cachedRoomId, cachedSubRoomId) early-out gate drops it. We + // stash the just-arrived Scenes[0].DataBlobName into a static + // so the set_RoomDataBlobName hijack can use it — otherwise + // LocalRoomDetails / LocalRoomScene never get the new blob + // name on the second-or-later push for the same (room, sub) + // tuple (= every post-save push in a dorm). + TryPatchByName("RecNet.Rooms", + "OnSubscriptionUpdateRoom", + prefix: nameof(SavePatches.OnSubscriptionUpdateRoom_Prefix)); + // Force AccountExtensions.CanLocalPlayerChat to return true. The + // 2020 watch's room-chat send path (PlayerEmotes.SendChatEmote + // Coroutine d__53 offset 040) gates on this — if false, the + // coroutine bails before raising the Photon RPC, so the sender + // doesn't even see their own message. The check ANDs three + // signals: LocalAccount.TreatAsJunior (ObscuredBool at offset + // 0x40), a PlatformManager flag, and a ModerationManager + // singleton field. At least one returns the wrong value against + // our server. Force-allow — single-tenant private server, text + // moderation isn't a concern. + TryPatchByName("RecNet.AccountExtensions", + "CanLocalPlayerChat", + args: Type.EmptyTypes, + postfix: nameof(ChatPatches.CanLocalPlayerChat_Postfix)); + // PlayerEmotes.RemoveInvalidCharactersFromMessage walks the chat + // font asset's character table via TMP_FontAsset.HasCharacters + // to strip glyphs the font can't render. On 2020 Rec Room the + // chat font field can be null at the moment this fires (font + // asset bundle hasn't loaded yet?) and HasCharacters NREs deep + // inside Unity's TMP — which propagates out through the chat + // send callback and aborts the RPC. The watch's own ISIL has + // "skipping invalid character check" log paths for missing + // assets but doesn't actually short-circuit on the null field + // we hit. Skip the method entirely; chat lines will just keep + // any glyphs the font can't render (most common: emoji). + TryPatchByName("RecRoom.Players.PlayerEmotes", + "RemoveInvalidCharactersFromMessage", + prefix: nameof(ChatPatches.RemoveInvalidCharactersFromMessage_Prefix)); + // Trace hooks so we can see exactly where chat dies. Send path: + // KeyboardInputField.submit → RoomChatMenu.SendEmoteMessage(string) + // → PlayerEmotes.SendChatEmote(msg, true) + // → SendChatEmoteCoroutine d__53 → PurifyString HTTP call + // → b__0 callback (error, cleanVersion) — fires Photon RPC + // AND local-echoes via ProcessNewChatMessageReceived + // → on each receiver (including self): RpcChatEmote(msg) → + // ProcessNewChatMessageReceived → ReceiveRoomChat appends to + // the chat log buffer. + TryPatchByName("RecRoom.Players.PlayerEmotes", + "SendChatEmote", + args: new[] { typeof(string), typeof(bool) }, + prefix: nameof(ChatPatches.SendChatEmote_Prefix)); + TryPatchByName("RecRoom.Players.PlayerEmotes", + "ProcessNewChatMessageReceived", + args: new[] { typeof(string), typeof(bool) }, + prefix: nameof(ChatPatches.ProcessNewChatMessageReceived_Prefix)); + TryPatchByName("RecRoom.Players.PlayerEmotes", + "RpcChatEmote", + args: new[] { typeof(string) }, + prefix: nameof(ChatPatches.RpcChatEmote_Prefix)); + + RegisterDiagnostics(); + Log.Msg("=== Client patches registered ==="); + } + + public override void OnUpdate() + { + if (_diagnosticGameComplete) return; + if (_diagnosticRetryFrame++ > 3600) return; + if ((_diagnosticRetryFrame % 60) != 0) return; + + var logMisses = (_diagnosticRetryFrame % 300) == 0; + _diagnosticGameComplete = RegisterGameDiagnostics(logMisses); + if (_diagnosticGameComplete) + DiagnosticPatches.Write("[diagnostics] all game-side diagnostic hooks registered"); + } + + public override void OnApplicationQuit() + { + DiagnosticPatches.Write("[lifecycle] OnApplicationQuit"); + } + + // ── Config ──────────────────────────────────────────────────────── + private static void LoadConfig() + { + try + { + // Compute the UserData path ourselves rather than calling + // MelonLoader.MelonUtils.UserDataDirectory (which is renamed + // to MelonEnvironment.UserDataDirectory in newer 0.6.x builds + // and shifts again in 0.7.x). Anchoring off the mod DLL's + // location keeps this stable across versions: the DLL lives + // in /Mods/, so two dirname()'s reach the game root, + // then MelonLoader/UserData/ is right next to it. + var modDll = Assembly.GetExecutingAssembly().Location; + var modsDir = Path.GetDirectoryName(modDll) ?? string.Empty; + var gameDir = Path.GetDirectoryName(modsDir) ?? string.Empty; + var userData = Path.Combine(gameDir, "MelonLoader", "UserData"); + Directory.CreateDirectory(userData); + DiagnosticsLogPath = Path.Combine(userData, "dorknet-diagnostics.log"); + var path = Path.Combine(userData, "dorknet-clientmod.json"); + if (!File.Exists(path)) + { + Log.Msg($"[config] no file at {path}; using defaults (ServerHost={Cfg.ServerHost})"); + return; + } + var doc = JsonDocument.Parse(File.ReadAllText(path)); + var r = doc.RootElement; + if (r.TryGetProperty("ServerHost", out var v)) Cfg.ServerHost = v.GetString() ?? Cfg.ServerHost; + if (r.TryGetProperty("PhotonAppId", out v)) Cfg.PhotonAppId = v.GetString() ?? Cfg.PhotonAppId; + if (r.TryGetProperty("PhotonVoiceAppId", out v)) Cfg.PhotonVoiceAppId = v.GetString() ?? Cfg.PhotonVoiceAppId; + if (r.TryGetProperty("PhotonCloudRegion", out v)) Cfg.PhotonCloudRegion = v.GetString() ?? Cfg.PhotonCloudRegion; + if (r.TryGetProperty("InjectAuthValues", out v)) Cfg.InjectAuthValues = v.GetBoolean(); + if (r.TryGetProperty("EnableTlsTrustBypass", out v)) Cfg.EnableTlsTrustBypass = v.GetBoolean(); + Log.Msg($"[config] loaded: ServerHost={Cfg.ServerHost}, PhotonAppId={(string.IsNullOrEmpty(Cfg.PhotonAppId) ? "" : "")}, " + + $"InjectAuthValues={Cfg.InjectAuthValues}, EnableTlsTrustBypass={Cfg.EnableTlsTrustBypass}"); + } + catch (Exception ex) + { + Log.Warning($"[config] load failed, using defaults: {ex.Message}"); + } + } + + // ── Patch dispatch helpers ──────────────────────────────────────── + private bool TryPatch(string label, Type target, string methodName, + Type[]? args, string? prefix = null, string? postfix = null, + bool logMiss = true) + { + try + { + var method = methodName == "ctor" + ? (MethodBase)target.GetConstructor(args ?? Type.EmptyTypes)! + : (args is null ? AccessTools.Method(target, methodName) + : AccessTools.Method(target, methodName, args)); + if (method is null) + { + // `logMiss` is throttled by the retry-loop caller — once per + // 5 seconds in OnUpdate, every time in the one-shot initial + // RegisterDiagnostics. Without the gate the per-second retry + // loop would spam `[patch-miss]` lines forever for any method + // whose name drifted in a newer game build. + if (logMiss) Log.Warning($"[patch-miss] {label}"); + return false; + } + HarmonyInstance.Patch(method, + prefix: prefix is null ? null : new HarmonyMethod(GetPatchMethod(prefix)), + postfix: postfix is null ? null : new HarmonyMethod(GetPatchMethod(postfix))); + Log.Msg($"[patch-ok] {label}"); + return true; + } + catch (Exception ex) + { + Log.Error($"[patch-fail] {label}: {ex.Message}"); + return false; + } + } + + // Resolve the game-side type by its ORIGINAL name (no Il2Cpp prefix) + // by walking every loaded assembly. Tries the exact name first, then + // the same name with an `Il2Cpp` prefix, then a substring scan as a + // last resort — covers all the IL2CPP interop naming schemes the user + // might end up with depending on the MelonLoader / Il2CppInterop + // version installed. + private bool TryPatchByName(string typeName, string methodName, + Type[]? args = null, string? prefix = null, string? postfix = null, + bool logMiss = true) + { + var label = FormatPatchLabel(typeName, methodName, args); + var type = ResolveType(typeName); + if (type is null) + { + if (logMiss) Log.Warning($"[patch-miss] {label}: type not found"); + return false; + } + return TryPatch(label, type, methodName, args, prefix, postfix, logMiss); + } + + private static Type? ResolveType(string name) + { + if (ResolvedTypeCache.TryGetValue(name, out var cached)) + return cached; + + foreach (var asm in AppDomain.CurrentDomain.GetAssemblies()) + { + try + { + var t = asm.GetType(name, throwOnError: false); + if (t is not null) return CacheResolvedType(name, t); + var prefixed = asm.GetType("Il2Cpp" + name, throwOnError: false); + if (prefixed is not null) return CacheResolvedType(name, prefixed); + // For namespaced types like "RecNet.Core", also try with + // the Il2Cpp prefix on the namespace (i.e. "Il2CppRecNet.Core"). + var dot = name.IndexOf('.'); + if (dot > 0) + { + var nsPrefixed = asm.GetType("Il2Cpp" + name.Substring(0, dot) + name.Substring(dot), throwOnError: false); + if (nsPrefixed is not null) return CacheResolvedType(name, nsPrefixed); + } + } + catch { /* unloaded / dynamic asm — skip */ } + } + + foreach (var asm in AppDomain.CurrentDomain.GetAssemblies()) + { + foreach (var t in GetLoadableTypes(asm)) + { + if (MatchesRequestedType(t, name)) + return CacheResolvedType(name, t); + } + } + return null; + } + + private static Type CacheResolvedType(string name, Type type) + { + ResolvedTypeCache[name] = type; + return type; + } + + private static IEnumerable GetLoadableTypes(Assembly asm) + { + try + { + return asm.GetTypes(); + } + catch (ReflectionTypeLoadException ex) + { + var types = new List(); + foreach (var t in ex.Types) + { + if (t is not null) types.Add(t); + } + return types; + } + catch + { + return Array.Empty(); + } + } + + private static bool MatchesRequestedType(Type type, string requested) + { + var prefixed = "Il2Cpp" + requested; + if (type.FullName == requested || type.FullName == prefixed) return true; + if (type.Name == requested || type.Name == prefixed) return true; + return type.FullName?.EndsWith("." + requested, StringComparison.Ordinal) == true || + type.FullName?.EndsWith(".Il2Cpp" + requested, StringComparison.Ordinal) == true; + } + + private static string FormatPatchLabel(string typeName, string methodName, Type[]? args) + { + if (args is null) return $"{typeName}.{methodName}"; + var names = new List(); + foreach (var arg in args) + names.Add(arg.Name); + return $"{typeName}.{methodName}({string.Join(",", names)})"; + } + + private MethodInfo GetPatchMethod(string name) + { + // Look in all three patch holder classes — small enough that a + // linear scan is cheaper than per-class lookups. + foreach (var holder in new[] { typeof(UriPatches), typeof(PhotonPatches), typeof(TlsPatches), typeof(DiagnosticPatches), typeof(SavePatches), typeof(ChatPatches) }) + { + var m = holder.GetMethod(name, BindingFlags.Public | BindingFlags.Static); + if (m is not null) return m; + } + throw new MissingMethodException($"patch method '{name}' not found on any holder"); + } + + private void RegisterDiagnostics() + { + Log.Msg("=== Registering diagnostics ==="); + if (!_diagnosticCoreRegistered) + { + TryPatchByName("UnityEngine.Application", "Quit", Type.EmptyTypes, + prefix: nameof(DiagnosticPatches.ApplicationQuit_Prefix)); + TryPatchByName("UnityEngine.Application", "Quit", new[] { typeof(int) }, + prefix: nameof(DiagnosticPatches.ApplicationQuitInt_Prefix)); + _diagnosticCoreRegistered = true; + } + + _diagnosticGameComplete = RegisterGameDiagnostics(logMisses: true); + Log.Msg("=== Diagnostics registered ==="); + } + + private bool RegisterGameDiagnostics(bool logMisses) + { + var complete = true; + complete &= TryPatchDiagnosticByName("BootSequence", "RegisterError", new[] { typeof(string) }, + prefix: nameof(DiagnosticPatches.BootSequenceRegisterError_Prefix), + logMiss: logMisses); + + foreach (var method in new[] + { + "UnityTimeCheat", + "ObscuredTypeCheat", + "DeveloperFlagCheat", + "HeightChangeCheat", + "AdvancedMotionCheat", + "StreamingAssetCheat" + }) + { + complete &= TryPatchDiagnosticByName("AnalyticsHelper", method, + prefix: nameof(DiagnosticPatches.AnalyticsCheat_Prefix), + logMiss: logMisses); + } + + complete &= TryPatchDiagnosticByName("SessionManager", "TryApplicationQuit", + prefix: nameof(DiagnosticPatches.SessionManagerTryApplicationQuit_Prefix), + logMiss: logMisses); + complete &= TryPatchDiagnosticByName("SteamManager", "Awake", + prefix: nameof(DiagnosticPatches.NamedMethod_Prefix), + logMiss: logMisses); + complete &= TryPatchDiagnosticByName("SteamPlatformManager", "PreLoginInitialize", + prefix: nameof(DiagnosticPatches.NamedMethod_Prefix), + logMiss: logMisses); + complete &= TryPatchDiagnosticByName("SteamPlatformManager", "PostLoginInitialize", + prefix: nameof(DiagnosticPatches.NamedMethod_Prefix), + logMiss: logMisses); + return complete; + } + + private bool TryPatchDiagnosticByName(string typeName, string methodName, + Type[]? args = null, string? prefix = null, string? postfix = null, + bool logMiss = true) + { + var label = FormatPatchLabel(typeName, methodName, args); + if (_diagnosticPatchLabels.Contains(label)) + return true; + + if (!TryPatchByName(typeName, methodName, args, prefix, postfix, logMiss)) + return false; + + _diagnosticPatchLabels.Add(label); + return true; + } +} + +// ── Patch holders ───────────────────────────────────────────────────── +// Each holder is a static class with public static Prefix methods that +// Harmony invokes. Kept separate from Mod for readability and so the +// reflection lookup in GetPatchMethod stays simple. + +internal static class UriPatches +{ + public static void UriStringCtor_Prefix(ref string uriString) + { + if (string.IsNullOrEmpty(uriString)) return; + var host = Mod.Cfg.ServerHost; + if (string.IsNullOrEmpty(host)) return; + if (uriString.IndexOf(".rec.net", StringComparison.OrdinalIgnoreCase) < 0) return; + var rewritten = uriString.Replace(".rec.net", "." + host); + if (rewritten != uriString) + { + Mod.Log.Msg($"[uri-rewrite] {uriString} → {rewritten}"); + uriString = rewritten; + } + } +} + +internal static class PhotonPatches +{ + public static void PhotonAppIdOverride_Prefix() + { + if (string.IsNullOrEmpty(Mod.Cfg.PhotonAppId)) return; + try + { + // PhotonNetwork.PhotonServerSettings — accessed via reflection + // so the type-name resolution stays in one place. Mutating the + // ScriptableObject in-place is what the BepInEx plugin does + // too; the Photon Connect path reads AppID / VoiceAppID off + // this object immediately after. + var photonNetwork = Mod.Instance is null ? null : FindGameType("PhotonNetwork"); + if (photonNetwork is null) return; + var settingsProp = photonNetwork.GetProperty("PhotonServerSettings", BindingFlags.Public | BindingFlags.Static); + var settings = settingsProp?.GetValue(null); + if (settings is null) return; + + var settingsType = settings.GetType(); + var appIdProp = settingsType.GetProperty("AppID") ?? settingsType.GetProperty("AppId"); + var voiceProp = settingsType.GetProperty("VoiceAppID") ?? settingsType.GetProperty("VoiceAppId"); + + var newId = Mod.Cfg.PhotonAppId; + var newVoice = string.IsNullOrEmpty(Mod.Cfg.PhotonVoiceAppId) ? newId : Mod.Cfg.PhotonVoiceAppId; + + // Diagnostic: dump every readable AppSettings field before + // we mutate, so a Photon Custom Auth failure on first connect + // is debuggable from a single log dump. Lists every public + // property + field on PhotonServerSettings (Server, Port, + // NameServer, AuthMode, HostType, PreferredRegion, AppID, + // VoiceAppID, BestRegionSummary, etc.) — covers both 2020.03 + // (Server/Port) and 2020.12 (AppSettings) layouts. + Mod.Log.Msg("[photon-diag] PhotonServerSettings BEFORE override:"); + DumpSettings(settings, settingsType, indent: " "); + + appIdProp?.SetValue(settings, newId); + voiceProp?.SetValue(settings, newVoice); + Mod.Log.Msg($"[photon-appid] override AppID/VoiceAppID → {newId} / {newVoice}"); + + // Force a deterministic Photon region so two watches running + // this mod end up on the same Photon master and "JoinByName" + // produces ONE shared room (not two parallel ones in eu/us + // because each watch auto-picked a different best region). + // + // The 2020 watch's default HostType is BestRegion(4) — every + // launch pings all Photon Cloud regions and picks the lowest + // latency. Two players geographically apart hit different + // "best" regions; same room name, two regions = two rooms. + // Server-side our matchmaking response carries + // `photonRegionId` but the watch's Photon code never reads + // it; the only consumer is Matchmaking.SetPhotonRegionPings + // which only reports pings *back* to the server. + // + // Strategy: switch HostType to PhotonCloud(1) and set + // PreferredRegion. Prefer the server-told region (the most + // recent Matchmaking.LocalRoomInstance.PhotonRegionId, if + // any) over the config default — that way a server-side + // region change propagates without redeploying the mod. + ApplyForcedRegion(settings, settingsType); + } + catch (Exception ex) { Mod.Log.Warning($"[photon-appid] override failed: {ex.Message}"); } + } + + /// Forces PhotonServerSettings.HostType = PhotonCloud + /// and writes PreferredRegion. Honors any region the server + /// has handed us in Matchmaking.LocalRoomInstance.PhotonRegionId + /// first; falls back to Cfg.PhotonCloudRegion when no room + /// instance has been received yet (initial boot). + private static void ApplyForcedRegion(object settings, Type settingsType) + { + try + { + var hostTypeProp = settingsType.GetField("HostType") + ?? (System.Reflection.MemberInfo?)settingsType.GetProperty("HostType"); + var preferredProp = settingsType.GetField("PreferredRegion") + ?? (System.Reflection.MemberInfo?)settingsType.GetProperty("PreferredRegion"); + if (hostTypeProp is null || preferredProp is null) + { + Mod.Log.Warning("[photon-region] PhotonServerSettings.HostType/PreferredRegion not found"); + return; + } + + // ServerSettings.HostingOption.PhotonCloud = 1 (verified in + // Cpp2IL_CS/.../ServerSettings.cs:8). Resolve the actual enum + // type from the field/property so we don't depend on a + // specific Il2Cpp proxy namespace. + Type? hostTypeEnum = (hostTypeProp as FieldInfo)?.FieldType + ?? ((PropertyInfo)hostTypeProp).PropertyType; + Type? regionEnum = (preferredProp as FieldInfo)?.FieldType + ?? ((PropertyInfo)preferredProp).PropertyType; + + // Prefer server-provided region over config. We read it via + // the same reflection style as the existing patches so the + // Il2Cpp type-name suffix doesn't bite us. + string region = ReadServerProvidedRegion() ?? Mod.Cfg.PhotonCloudRegion ?? "us"; + + object hostValue = Enum.ToObject(hostTypeEnum!, 1); // PhotonCloud + object regionValue; + try { regionValue = Enum.Parse(regionEnum!, region.ToLowerInvariant(), ignoreCase: true); } + catch + { + Mod.Log.Warning($"[photon-region] '{region}' is not a CloudRegionCode enum value; defaulting to us"); + regionValue = Enum.ToObject(regionEnum!, 1); // CloudRegionCode.us = 1 + region = "us"; + } + + if (hostTypeProp is FieldInfo hostField) hostField.SetValue(settings, hostValue); + else ((PropertyInfo)hostTypeProp).SetValue(settings, hostValue); + if (preferredProp is FieldInfo prefField) prefField.SetValue(settings, regionValue); + else ((PropertyInfo)preferredProp).SetValue(settings, regionValue); + + Mod.Log.Msg($"[photon-region] forced HostType=PhotonCloud PreferredRegion={region}"); + } + catch (Exception ex) { Mod.Log.Warning($"[photon-region] apply failed: {ex.Message}"); } + } + + /// Reads + /// RecNet.Matchmaking.LocalRoomInstance.PhotonRegionId as a + /// lowercase enum name (eu, us, asia, …). Returns null if no room + /// instance has been received yet, or if any link in the chain is + /// null. Server-told region wins over the static config default + /// because the server is authoritative about which Photon region + /// hosts the room the watch is about to join. + private static string? ReadServerProvidedRegion() + { + try + { + var matchmaking = FindGameType("RecNet.Matchmaking"); + var localRoom = matchmaking?.GetProperty("LocalRoomInstance", BindingFlags.Public | BindingFlags.Static)?.GetValue(null); + if (localRoom is null) return null; + var regionObj = localRoom.GetType().GetProperty("PhotonRegionId")?.GetValue(localRoom); + if (regionObj is null) return null; + // CloudRegionCode enum.ToString() gives the lowercase region + // name (eu, us, asia, …) — see CloudRegionCode.cs. + return regionObj.ToString(); + } + catch { return null; } + } + + public static void AuthValuesInjector_Prefix(object __instance) + { + if (!Mod.Cfg.InjectAuthValues) return; + try + { + // Photon's AuthValues live on PhotonNetwork.AuthValues. The 2020 + // watch never sets them itself, so we set them right before + // NetworkingPeer.CallAuthenticate forwards to the wire op. The + // userid is RecNet.Core.LocalAccountId, the LoginLock comes + // from PlayerPrefs key LoginLockTokenV2. + // RecNet.Core.LocalAccountId is declared `int` in the game + // build (dump.cs:573622 → 0x30 backing field). Convert via + // Convert.ToInt64 so boxing as int still gives us the long we + // need below; a direct (long) cast on boxed int would throw. + var coreType = FindGameType("RecNet.Core"); + var accountIdObj = coreType?.GetProperty("LocalAccountId", BindingFlags.Public | BindingFlags.Static)?.GetValue(null); + var accountId = accountIdObj is null ? 0L : Convert.ToInt64(accountIdObj); + if (accountId <= 0) + { + Mod.Log.Msg("[auth-injector] skip — no LocalAccountId yet (pre-login Photon ping)"); + return; + } + + var photonNetwork = FindGameType("PhotonNetwork"); + var avProp = photonNetwork?.GetProperty("AuthValues", BindingFlags.Public | BindingFlags.Static); + var existing = avProp?.GetValue(null); + if (existing is not null) + { + var userIdProp = existing.GetType().GetProperty("UserId"); + var existingUserId = userIdProp?.GetValue(existing) as string; + if (!string.IsNullOrEmpty(existingUserId)) return; + } + + var token = GetCurrentLockToken(); + + // Build a new AuthenticationValues. The type lives in the + // ExitGames.Client.Photon.LoadBalancing namespace (game-side). + // CustomAuthenticationType.Custom = 0 in this build. + var authValuesType = FindGameType("ExitGames.Client.Photon.LoadBalancing.AuthenticationValues"); + if (authValuesType is null) { Mod.Log.Warning("[auth-injector] AuthenticationValues type not found"); return; } + var av = Activator.CreateInstance(authValuesType); + var authTypeProp = authValuesType.GetProperty("AuthType"); + if (authTypeProp is not null) + { + var customAuthValue = authTypeProp.PropertyType.IsEnum + ? Enum.ToObject(authTypeProp.PropertyType, 0) + : Convert.ChangeType(0, authTypeProp.PropertyType); + authTypeProp.SetValue(av, customAuthValue); + } + authValuesType.GetProperty("UserId")?.SetValue(av, accountId.ToString()); + var addParam = authValuesType.GetMethod("AddAuthParameter", new[] { typeof(string), typeof(string) }); + addParam?.Invoke(av, new object[] { "userid", accountId.ToString() }); + addParam?.Invoke(av, new object[] { "LoginLock", token ?? string.Empty }); + + avProp?.SetValue(null, av); + Mod.Log.Msg($"[auth-injector] set AuthValues userid={accountId} LoginLock={(string.IsNullOrEmpty(token) ? "" : "")}"); + } + catch (Exception ex) { Mod.Log.Warning($"[auth-injector] failed: {ex.Message}"); } + } + + /// Postfix on PUNNetworkManager.FJOLIPKKIBE — the + /// 2020.12 watch's private AppSettings builder. __result is the + /// Photon.Realtime.AppSettings it just built. + /// + /// **CRITICAL OVERRIDE**: replaces AppIdRealtime and + /// AppIdVoice with our configured values before the AppSettings + /// reaches PhotonNetwork.ConnectUsingSettings. The 2020.12 + /// watch hardcodes Rec Room's official AppIds as IL2CPP string + /// literals (verified at PUNNetworkManager.txt:7205,7208: + /// "9372aa8d-..." and "e93ae440-..."). Patching + /// resources.assets at offsets 0x7962480 / 0x79624ac doesn't help — + /// those are unrelated leftover strings, NOT the AppIds the SDK + /// actually reads. The only reliable swap point is here, after + /// FJOLIPKKIBE constructs the AppSettings object but before the + /// LoadBalancingClient consumes it. + /// + /// Then dumps every readable field/property for diagnostics. + public static void LogAppSettings_Postfix(object __result) + { + try + { + if (__result is null) { Mod.Log.Msg("[photon-diag] FJOLIPKKIBE returned null AppSettings"); return; } + + // ── AppId injection ──────────────────────────────────────── + var t = __result.GetType(); + var newPun = string.IsNullOrEmpty(Mod.Cfg.PhotonAppId) ? null : Mod.Cfg.PhotonAppId; + var newVoice = string.IsNullOrEmpty(Mod.Cfg.PhotonVoiceAppId) + ? (newPun ?? null) + : Mod.Cfg.PhotonVoiceAppId; + if (newPun is not null) + { + var realtimeProp = t.GetProperty("AppIdRealtime") ?? t.GetProperty("AppIdFusion"); + var oldPun = realtimeProp?.GetValue(__result) as string; + realtimeProp?.SetValue(__result, newPun); + Mod.Log.Msg($"[photon-override] AppIdRealtime: {oldPun} → {newPun}"); + } + if (newVoice is not null) + { + var voiceProp = t.GetProperty("AppIdVoice"); + var oldVoice = voiceProp?.GetValue(__result) as string; + voiceProp?.SetValue(__result, newVoice); + Mod.Log.Msg($"[photon-override] AppIdVoice: {oldVoice} → {newVoice}"); + } + // Also force FixedRegion to the configured region so two clients + // never end up on different regions of the same room name. + if (!string.IsNullOrEmpty(Mod.Cfg.PhotonCloudRegion)) + { + var fixedRegionProp = t.GetProperty("FixedRegion"); + if (fixedRegionProp is not null && fixedRegionProp.CanWrite) + { + fixedRegionProp.SetValue(__result, Mod.Cfg.PhotonCloudRegion); + Mod.Log.Msg($"[photon-override] FixedRegion → {Mod.Cfg.PhotonCloudRegion}"); + } + } + + Mod.Log.Msg("[photon-diag] FJOLIPKKIBE built AppSettings (post-override):"); + DumpSettings(__result, t, indent: " "); + } + catch (Exception ex) { Mod.Log.Warning($"[photon-diag] LogAppSettings/override failed: {ex.Message}"); } + } + + /// Postfix on PUNNetworkManager.EMGGKBALALH — the + /// 2020.12 watch's private AuthenticationValues builder. __result + /// is the AuthValues it just built with environment + accessToken + /// AuthGetParameters. Logs AuthType, UserId, and the full + /// AuthGetParameters / AuthPostData payload that Photon Cloud will + /// forward to the configured Custom Auth URL. + public static void LogAuthValues_Postfix(object __result) + { + try + { + if (__result is null) + { + Mod.Log.Msg("[photon-diag] EMGGKBALALH returned null AuthValues (anonymous connect)"); + return; + } + var t = __result.GetType(); + string Read(string name) => t.GetProperty(name)?.GetValue(__result)?.ToString() ?? ""; + var authType = Read("AuthType"); + var userId = Read("UserId"); + var getParams = t.GetProperty("AuthGetParameters")?.GetValue(__result)?.ToString() ?? ""; + var postData = t.GetProperty("AuthPostData")?.GetValue(__result); + var postStr = postData is byte[] bytes + ? $"<{bytes.Length} bytes: {System.Text.Encoding.UTF8.GetString(bytes)}>" + : postData?.ToString() ?? ""; + Mod.Log.Msg($"[photon-diag] EMGGKBALALH built AuthValues: AuthType={authType} UserId={userId}"); + Mod.Log.Msg($"[photon-diag] AuthGetParameters = {getParams}"); + Mod.Log.Msg($"[photon-diag] AuthPostData = {postStr}"); + } + catch (Exception ex) { Mod.Log.Warning($"[photon-diag] LogAuthValues failed: {ex.Message}"); } + } + + /// Prefix on PUNNetworkManager.OnCustomAuthenticationFailed + /// — the watch's UI-error formatter only logs a truncated Unity- + /// stringified version of the debugMessage. Capturing the raw + /// string before the watch's handler runs gives us the FULL + /// Photon error including the URL Photon Cloud actually tried to + /// call (which is the smoking-gun answer to "is the dashboard URL + /// saved or is Photon hitting a stale one"). + public static void OnCustomAuthenticationFailed_Prefix(object __0) + { + try + { + var debugMessage = __0?.ToString() ?? ""; + Mod.Log.Error("[photon-diag] OnCustomAuthenticationFailed FULL MESSAGE:"); + Mod.Log.Error(debugMessage); + // Also dump current AppSettings — if Photon rejected the auth + // we want to know what the watch's actual final config looked + // like (post-override). + var photonNetwork = FindGameType("PhotonNetwork"); + var settings = photonNetwork?.GetProperty("PhotonServerSettings", BindingFlags.Public | BindingFlags.Static)?.GetValue(null); + if (settings is not null) + { + Mod.Log.Error("[photon-diag] PhotonServerSettings at failure:"); + DumpSettings(settings, settings.GetType(), indent: " "); + } + } + catch (Exception ex) { Mod.Log.Warning($"[photon-diag] OnCustomAuthenticationFailed log failed: {ex.Message}"); } + } + + /// Logs every public field + property on + /// — used by the photon-diag patches above to dump AppSettings in one + /// pass without coupling to specific field names that differ across + /// PUN versions (Server/Port vs AppSettings.NameServer in newer + /// PUN 2.x, etc.). + private static void DumpSettings(object obj, Type t, string indent) + { + try + { + foreach (var p in t.GetProperties(BindingFlags.Public | BindingFlags.Instance)) + { + if (p.GetIndexParameters().Length > 0) continue; + if (!p.CanRead) continue; + object? v; + try { v = p.GetValue(obj); } catch { continue; } + var str = v is null ? "" : v.ToString(); + // Nested AppSettings — recurse one level so we capture + // PhotonServerSettings.AppSettings.{AppIdRealtime,Server,Port,NameServer,FixedRegion,AuthMode,Protocol,...} + if (v is not null && v.GetType().Name.IndexOf("AppSettings", StringComparison.Ordinal) >= 0 && indent.Length < 6) + { + Mod.Log.Msg($"{indent}{p.Name} (nested):"); + DumpSettings(v, v.GetType(), indent + " "); + continue; + } + Mod.Log.Msg($"{indent}{p.Name} = {str}"); + } + foreach (var f in t.GetFields(BindingFlags.Public | BindingFlags.Instance)) + { + object? v; + try { v = f.GetValue(obj); } catch { continue; } + var str = v is null ? "" : v.ToString(); + if (v is not null && v.GetType().Name.IndexOf("AppSettings", StringComparison.Ordinal) >= 0 && indent.Length < 6) + { + Mod.Log.Msg($"{indent}{f.Name} (nested):"); + DumpSettings(v, v.GetType(), indent + " "); + continue; + } + Mod.Log.Msg($"{indent}{f.Name} = {str}"); + } + } + catch (Exception ex) { Mod.Log.Warning($"[photon-diag] DumpSettings failed: {ex.Message}"); } + } + + private static string? GetCurrentLockToken() + { + try + { + var playerPrefs = FindGameType("UnityEngine.PlayerPrefs"); + var getString = playerPrefs?.GetMethod("GetString", new[] { typeof(string), typeof(string) }); + var raw = (string?)getString?.Invoke(null, new object[] { "LoginLockTokenV2", string.Empty }); + if (string.IsNullOrEmpty(raw)) return null; + const string key = "\"LockToken\":\""; + var ix = raw.IndexOf(key, StringComparison.Ordinal); + if (ix < 0) return null; + ix += key.Length; + var end = raw.IndexOf('"', ix); + return end < 0 ? null : raw[ix..end]; + } + catch { return null; } + } + + private static Type? FindGameType(string name) + { + // Mirrors Mod.ResolveType but inlined here so the holder doesn't + // depend on Mod's internal helpers. + foreach (var asm in AppDomain.CurrentDomain.GetAssemblies()) + { + try + { + var t = asm.GetType(name, throwOnError: false); + if (t is not null) return t; + var prefixed = asm.GetType("Il2Cpp" + name, throwOnError: false); + if (prefixed is not null) return prefixed; + var dot = name.IndexOf('.'); + if (dot > 0) + { + var nsPrefixed = asm.GetType("Il2Cpp" + name.Substring(0, dot) + name.Substring(dot), throwOnError: false); + if (nsPrefixed is not null) return nsPrefixed; + } + } + catch { } + } + return null; + } +} + +internal static class SavePatches +{ + // Last DataBlobName parsed off a SubscriptionUpdateRoom push for + // our currently-loaded room. Captured BEFORE the watch's caching + // gate (Rooms.OnSubscriptionUpdateRoom early-outs on a repeat + // (roomId, subRoomId) tuple — which is every post-save push for a + // dorm). SetRoomDataBlobName_Prefix reads this to hijack the + // would-be no-op setter call into a real transition. + private static volatile string? s_lastPushedBlobName; + private static long s_lastPushedRoomId; + private static long s_lastPushedSubRoomId; + + // Hook on Notifications.OnNotificationReceived(string json). Fires for + // every SignalR message before any handler dispatch. Parses the JSON + // and, when the Id is SubscriptionUpdateRoom, stashes the new blob. + public static void OnNotificationReceived_Prefix(string message) + { + try + { + if (string.IsNullOrEmpty(message)) return; + // Fast reject: only parse JSON if it could plausibly be a + // SubscriptionUpdateRoom payload. Saves cycles on the (very + // frequent) RoomInstanceUpdate / PresenceUpdate / chat pushes. + if (message.IndexOf("\"SubscriptionUpdateRoom\"", StringComparison.Ordinal) < 0) + return; + using var doc = System.Text.Json.JsonDocument.Parse(message); + var root = doc.RootElement; + if (root.ValueKind != System.Text.Json.JsonValueKind.Object) return; + if (!root.TryGetProperty("Id", out var idEl) || + idEl.ValueKind != System.Text.Json.JsonValueKind.String || + !string.Equals(idEl.GetString(), "SubscriptionUpdateRoom", StringComparison.Ordinal)) + return; + if (!root.TryGetProperty("Msg", out var msg) || + msg.ValueKind != System.Text.Json.JsonValueKind.Object) return; + if (!msg.TryGetProperty("Room", out var room) || + room.ValueKind != System.Text.Json.JsonValueKind.Object) return; + long roomId = 0; + if (room.TryGetProperty("RoomId", out var ridEl) && + ridEl.ValueKind == System.Text.Json.JsonValueKind.Number) + roomId = ridEl.GetInt64(); + if (!msg.TryGetProperty("Scenes", out var scenes) || + scenes.ValueKind != System.Text.Json.JsonValueKind.Array || + scenes.GetArrayLength() == 0) return; + var first = scenes[0]; + long subRoomId = 0; + if (first.TryGetProperty("RoomSceneId", out var sidEl) && + sidEl.ValueKind == System.Text.Json.JsonValueKind.Number) + subRoomId = sidEl.GetInt64(); + if (!first.TryGetProperty("DataBlobName", out var blobEl) || + blobEl.ValueKind != System.Text.Json.JsonValueKind.String) return; + var blob = blobEl.GetString(); + if (string.IsNullOrEmpty(blob)) return; + s_lastPushedRoomId = roomId; + s_lastPushedSubRoomId = subRoomId; + s_lastPushedBlobName = blob; + Mod.Log.Msg($"[save-reload-fix] captured push via OnNotificationReceived: roomId={roomId} subRoomId={subRoomId} blob={blob}"); + } + catch (Exception ex) + { + Mod.Log.Warning($"[save-reload-fix] OnNotificationReceived prefix failed: {ex.Message}"); + } + } + + public static void OnSubscriptionUpdateRoom_Prefix(object message) + { + try + { + if (message is null) return; + var dict = message; + + // The watch parses this dict as Dictionary + // via Util.GetKey. We need three keys: RoomId, SubRoomId, + // and Scenes[0].DataBlobName. The Il2Cpp dict supports + // get_Item(key) — call it via reflection. + var getItem = dict.GetType().GetMethod("get_Item", new[] { typeof(string) }); + if (getItem is null) return; + + // The push wire shape from server (BuildRoomDetails) is + // {"Room":{...}, "Scenes":[{...}], ...}. The Room id lives + // on the Room sub-object; the scene's blob on Scenes[0]. + object? roomObj = TryGet(dict, getItem, "Room"); + object? scenesObj = TryGet(dict, getItem, "Scenes"); + if (roomObj is null || scenesObj is null) return; + + long roomId = 0; + long subRoomId = 0; + try + { + var roomItem = roomObj.GetType().GetMethod("get_Item", new[] { typeof(string) }); + if (roomItem is not null) + { + var rid = roomItem.Invoke(roomObj, new object[] { "RoomId" }); + if (rid is not null) roomId = Convert.ToInt64(rid.ToString()); + } + } + catch { /* roomId stays 0 — we'll log it and bail later */ } + + // Scenes is an IL2CPP List. Take the first + // (current dorm has one scene; multi-scene rooms target + // Scenes[currentSubRoomId] specifically, but the post-save + // push always carries the scene that was just saved). + object? firstScene = null; + try + { + var scenesIndexer = scenesObj.GetType().GetMethod("get_Item", new[] { typeof(int) }); + firstScene = scenesIndexer?.Invoke(scenesObj, new object[] { 0 }); + } + catch { /* leave firstScene null */ } + if (firstScene is null) return; + + string? newBlob = null; + try + { + var sceneItem = firstScene.GetType().GetMethod("get_Item", new[] { typeof(string) }); + if (sceneItem is not null) + { + var blob = sceneItem.Invoke(firstScene, new object[] { "DataBlobName" }); + newBlob = blob?.ToString(); + var sid = sceneItem.Invoke(firstScene, new object[] { "RoomSceneId" }); + if (sid is not null) subRoomId = Convert.ToInt64(sid.ToString()); + } + } + catch { /* leave newBlob null */ } + + if (string.IsNullOrEmpty(newBlob)) return; + + s_lastPushedRoomId = roomId; + s_lastPushedSubRoomId = subRoomId; + s_lastPushedBlobName = newBlob; + Mod.Log.Msg($"[save-reload-fix] captured SubscriptionUpdateRoom push: roomId={roomId} subRoomId={subRoomId} blob={newBlob}"); + } + catch (Exception ex) + { + Mod.Log.Warning($"[save-reload-fix] OnSubscriptionUpdateRoom prefix failed: {ex.Message}"); + } + } + + private static object? TryGet(object dict, MethodInfo getItem, string key) + { + try { return getItem.Invoke(dict, new object[] { key }); } + catch { return null; } + } + + // RoomPersistenceManager.set_RoomDataBlobName runs the change-event + // path only when the new value differs from the cached field. The + // 2020 watch's post-save propagation reads from RoomInstance.DataBlob + // (set by /goto, never updated by SubscriptionUpdateRoom), so on a + // save it calls the setter with the OLD blob — a no-op that skips + // OnRoomDataBlobNameChanged. The cached PersistedRoomData stays + // pointed at the boot-time bytes, and PostSaveReloading deserializes + // those over the live world — wiping any MakerPen object the user + // just placed. + // + // Live trace evidence (Latest.log, dorm save at 17:14): the watch's + // own set_RoomDataBlobName fires with old="v10" new="v10" ~900ms + // after b__4 returns True, even though the SubscriptionUpdateRoom + // push already carried Scenes[0].DataBlobName="v11". + // + // Strategy: intercept the setter. If the incoming value matches the + // current value (no-op) AND Rooms.LocalRoomScene.DataBlobName has a + // newer version (the freshly-pushed one), substitute that into the + // value parameter so the real setter sees a transition and fires + // OnRoomDataBlobNameChanged → DownloadRoomDataBlobAsync(newBlob). + // Falls through silently when no override is needed. + public static void SetRoomDataBlobName_Prefix(object __instance, ref string value) + { + string currentForLog = ""; + string sceneForLog = ""; + string scenes0ForLog = ""; + try + { + if (__instance is null) + { + Mod.Log.Msg("[save-reload-fix] prefix: __instance null — skipping"); + return; + } + + var rpmType = __instance.GetType(); + var blobNameProp = rpmType.GetProperty("RoomDataBlobName", + BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance); + if (blobNameProp is null) + { + Mod.Log.Msg($"[save-reload-fix] prefix: RoomDataBlobName property not found on {rpmType.FullName}"); + return; + } + currentForLog = (blobNameProp.GetValue(__instance) as string) ?? ""; + + var roomsType = FindGameType("RecNet.Rooms"); + if (roomsType is null) + { + Mod.Log.Msg("[save-reload-fix] prefix: RecNet.Rooms type not found"); + return; + } + + var localScene = roomsType.GetProperty("LocalRoomScene", + BindingFlags.Public | BindingFlags.Static)?.GetValue(null); + if (localScene is not null) + sceneForLog = (localScene.GetType().GetProperty("DataBlobName")?.GetValue(localScene) as string) ?? ""; + + var localDetails = roomsType.GetProperty("LocalRoomDetails", + BindingFlags.Public | BindingFlags.Static)?.GetValue(null); + if (localDetails is not null) + { + var scenes = localDetails.GetType().GetProperty("Scenes")?.GetValue(localDetails); + if (scenes is not null) + { + var indexer = scenes.GetType().GetMethod("get_Item", new[] { typeof(int) }); + var first = indexer?.Invoke(scenes, new object[] { 0 }); + if (first is not null) + scenes0ForLog = (first.GetType().GetProperty("DataBlobName")?.GetValue(first) as string) ?? ""; + } + } + + // Unconditional trace of EVERYTHING we read so we can see in + // the log exactly why we did or didn't override. + Mod.Log.Msg($"[save-reload-fix] prefix fired: value=\"{value}\" current=\"{currentForLog}\" " + + $"LocalRoomScene.DataBlobName=\"{sceneForLog}\" Scenes[0].DataBlobName=\"{scenes0ForLog}\" " + + $"lastPushedBlob=\"{s_lastPushedBlobName ?? ""}\""); + + // Pick a candidate "newer" blob name. Priority order: + // 1. Last-pushed-from-SubscriptionUpdateRoom (captured by + // OnSubscriptionUpdateRoom_Prefix BEFORE the watch's + // cache gate drops the push). + // 2. LocalRoomScene.DataBlobName. + // 3. Scenes[0].DataBlobName. + string? pushedBlob = s_lastPushedBlobName; + if (string.IsNullOrEmpty(pushedBlob) || string.Equals(pushedBlob, currentForLog, StringComparison.Ordinal)) + pushedBlob = sceneForLog is "" or "" ? null : sceneForLog; + if (string.IsNullOrEmpty(pushedBlob) || string.Equals(pushedBlob, currentForLog, StringComparison.Ordinal)) + pushedBlob = scenes0ForLog is "" or "" ? null : scenes0ForLog; + + if (string.IsNullOrEmpty(pushedBlob)) return; + if (string.Equals(pushedBlob, value, StringComparison.Ordinal)) return; + if (string.Equals(pushedBlob, currentForLog, StringComparison.Ordinal)) return; + + Mod.Log.Msg($"[save-reload-fix] hijacking set_RoomDataBlobName({value}); substituting newer blob {pushedBlob}"); + value = pushedBlob; + } + catch (Exception ex) + { + Mod.Log.Warning($"[save-reload-fix] set_RoomDataBlobName prefix failed: {ex.Message} " + + $"(value=\"{value}\" current=\"{currentForLog}\" scene=\"{sceneForLog}\" scenes0=\"{scenes0ForLog}\")"); + } + } + + // Local copy of the FindGameType helper used by PhotonPatches — IL2CPP + // type-name resolution stays per-holder so SavePatches doesn't reach + // across into another internal class. + private static Type? FindGameType(string name) + { + foreach (var asm in AppDomain.CurrentDomain.GetAssemblies()) + { + try + { + var t = asm.GetType(name, throwOnError: false); + if (t is not null) return t; + var prefixed = asm.GetType("Il2Cpp" + name, throwOnError: false); + if (prefixed is not null) return prefixed; + var dot = name.IndexOf('.'); + if (dot > 0) + { + var nsPrefixed = asm.GetType("Il2Cpp" + name.Substring(0, dot) + name.Substring(dot), throwOnError: false); + if (nsPrefixed is not null) return nsPrefixed; + } + } + catch { } + } + return null; + } +} + +internal static class TlsPatches +{ + // Both BouncyCastle TLS authenticators have the same `void + // NotifyServerCertificate(...)` signature whose body throws when the + // chain doesn't validate. Returning false from a Harmony prefix on a + // void method skips the original — the certificate is accepted by + // virtue of NotifyServerCertificate never throwing. + public static bool NotifyServerCertificate_Prefix() + { + return !Mod.Cfg.EnableTlsTrustBypass; // false skips original; true (bypass off) lets it run + } +} + +internal static class DiagnosticPatches +{ + public static void Write(string message) + { + var line = $"[{DateTime.Now:HH:mm:ss.fff}] [diagnostic] {message}"; + try { Mod.Log.Msg(line); } catch { } + try + { + if (!string.IsNullOrEmpty(Mod.DiagnosticsLogPath)) + File.AppendAllText(Mod.DiagnosticsLogPath, line + Environment.NewLine); + } + catch { } + } + + public static void ApplicationQuit_Prefix() + { + Write("[quit] UnityEngine.Application.Quit()"); + WriteStack(); + } + + public static void ApplicationQuitInt_Prefix(int exitCode) + { + Write($"[quit] UnityEngine.Application.Quit({exitCode})"); + WriteStack(); + } + + public static void BootSequenceRegisterError_Prefix(string error) + { + Write($"[boot-error] BootSequence.RegisterError: {error}"); + WriteStack(); + } + + public static void AnalyticsCheat_Prefix(MethodBase __originalMethod) + { + Write($"[cheat-analytics] {__originalMethod.DeclaringType?.FullName}.{__originalMethod.Name}"); + WriteStack(); + } + + public static void NamedMethod_Prefix(MethodBase __originalMethod) + { + Write($"[call] {__originalMethod.DeclaringType?.FullName}.{__originalMethod.Name}"); + WriteStack(); + } + + public static bool SessionManagerTryApplicationQuit_Prefix(MethodBase __originalMethod) + { + Write($"[quit-blocked] {__originalMethod.DeclaringType?.FullName}.{__originalMethod.Name}"); + WriteStack(); + return false; + } + + private static void WriteStack() + { + try + { + Write("[stack] " + new StackTrace(skipFrames: 2, fNeedFileInfo: false)); + } + catch (Exception ex) + { + Write("[stack] failed: " + ex.Message); + } + } +} + +internal static class ChatPatches +{ + private static bool s_logged; + + // Force RecNet.AccountExtensions.CanLocalPlayerChat to return true. + // PlayerEmotes.SendChatEmoteCoroutine d__53 offset 040 calls this + // before raising the in-room Photon chat RPC; if it returns false + // the coroutine bails silently and the sender doesn't even see + // their own message echo. + public static void CanLocalPlayerChat_Postfix(ref bool __result) + { + if (!__result && !s_logged) + { + s_logged = true; + Mod.Log.Msg("[chat-fix] CanLocalPlayerChat returned false; forcing true to unblock room chat"); + } + __result = true; + } + + // Skip the original. Returning false from a Harmony prefix stops + // the underlying call. + public static bool RemoveInvalidCharactersFromMessage_Prefix() + { + Mod.Log.Msg("[chat-trace] RemoveInvalidCharactersFromMessage skipped"); + return false; + } + + public static void SendChatEmote_Prefix(string message) + { + Mod.Log.Msg($"[chat-trace] SendChatEmote msg=\"{message}\""); + } + + public static void ProcessNewChatMessageReceived_Prefix(string message, bool playAudioForLocalPlayer) + { + Mod.Log.Msg($"[chat-trace] ProcessNewChatMessageReceived msg=\"{message}\" audio={playAudioForLocalPlayer}"); + } + + public static void RpcChatEmote_Prefix(string message) + { + Mod.Log.Msg($"[chat-trace] RpcChatEmote (RPC RECEIVED) msg=\"{message}\""); + } +} + diff --git a/DorkNet.ClientMod/dorknet-clientmod.json.template b/DorkNet.ClientMod/dorknet-clientmod.json.template new file mode 100644 index 0000000..c3bc687 --- /dev/null +++ b/DorkNet.ClientMod/dorknet-clientmod.json.template @@ -0,0 +1,8 @@ +{ + "ServerHost": "{HOST}", + "PhotonAppId": "{PHOTON_APPID}", + "PhotonVoiceAppId": "{PHOTON_VOICE_APPID}", + "PhotonCloudRegion": "{PHOTON_REGION}", + "InjectAuthValues": true, + "EnableTlsTrustBypass": true +} diff --git a/DorkNet.ClientPatch/DorkNet.ClientPatch.csproj b/DorkNet.ClientPatch/DorkNet.ClientPatch.csproj new file mode 100644 index 0000000..ee2dfcf --- /dev/null +++ b/DorkNet.ClientPatch/DorkNet.ClientPatch.csproj @@ -0,0 +1,106 @@ + + + + + net6.0 + latest + enable + DorkNet.ClientPatch + DorkNet.ClientPatch + false + false + + + + + + + $(BepInExCore)\BepInEx.Core.dll + false + + + $(BepInExCore)\BepInEx.Unity.IL2CPP.dll + false + + + $(BepInExCore)\0Harmony.dll + false + + + $(BepInExCore)\Il2CppInterop.Runtime.dll + false + + + $(BepInExCore)\Il2CppInterop.Common.dll + false + + + $(BepInExCore)\MonoMod.Utils.dll + false + + + + + $(BepInExInterop)\Il2Cppmscorlib.dll + false + + + $(BepInExInterop)\Il2CppSystem.dll + false + + + $(BepInExInterop)\Assembly-CSharp.dll + false + + + $(BepInExInterop)\Assembly-CSharp-firstpass.dll + false + + + $(BepInExInterop)\Photon3Unity3D.dll + false + + + $(BepInExInterop)\UnityEngine.UI.dll + false + + + $(BepInExInterop)\Unity.TextMeshPro.dll + false + + + $(BepInExInterop)\UnityEngine.CoreModule.dll + false + + + $(BepInExInterop)\RecRoom.Datastructures.Runtime.dll + false + + + $(BepInExInterop)\Unity.Addressables.dll + false + + + $(BepInExInterop)\RecRoom.Async.dll + false + + + $(BepInExInterop)\RecRoom.Promises.Runtime.dll + false + + + + diff --git a/DorkNet.ClientPatch/DorkNet.ClientPatch.csproj.lscache b/DorkNet.ClientPatch/DorkNet.ClientPatch.csproj.lscache new file mode 100644 index 0000000..91f7f97 --- /dev/null +++ b/DorkNet.ClientPatch/DorkNet.ClientPatch.csproj.lscache @@ -0,0 +1,231 @@ +version=1 + +# This file caches language service data to improve the performance of C# Dev Kit. +# It is not intended for manual editing. It can safely be deleted and will be +# regenerated automatically. For more information, see https://aka.ms/lscache +# +# To control where cache files are stored, use the following VS Code setting: +# "dotnet.projectsystem.cacheInProjectFolder": true + +[project] +language=C# +primary +lastDtbSucceeded + +[properties] +AssemblyName=DorkNet.ClientPatch +CommandLineArgsForDesignTimeEvaluation=-langversion:latest -define:TRACE +CompilerGeneratedFilesOutputPath= +MaxSupportedLangVersion=10.0 +ProjectAssetsFile=obj/project.assets.json +RootNamespace=DorkNet.ClientPatch +RunAnalyzers= +RunAnalyzersDuringLiveAnalysis= +SolutionPath=../DorkNet.sln +TargetFrameworkIdentifier=.NETCoreApp +TargetPath=bin/Debug/DorkNet.ClientPatch.dll +TargetRefPath=obj/Debug/ref/DorkNet.ClientPatch.dll +TemporaryDependencyNodeTargetIdentifier=net6.0 + +[commandLineArguments] +/noconfig +/unsafe- +/checked- +/nowarn:1701,1702,1701,1702 +/fullpaths +/nostdlib+ +/errorreport:prompt +/warn:6 +/define:TRACE;DEBUG;NET;NET6_0;NETCOREAPP;NET5_0_OR_GREATER;NET6_0_OR_GREATER;NETCOREAPP1_0_OR_GREATER;NETCOREAPP1_1_OR_GREATER;NETCOREAPP2_0_OR_GREATER;NETCOREAPP2_1_OR_GREATER;NETCOREAPP2_2_OR_GREATER;NETCOREAPP3_0_OR_GREATER;NETCOREAPP3_1_OR_GREATER +/highentropyva+ +/nullable:enable +/debug+ +/debug:portable +/filealign:512 +/optimize- +/out:obj\Debug\DorkNet.ClientPatch.dll +/refout:obj\Debug\refint\DorkNet.ClientPatch.dll +/target:library +/warnaserror- +/utf8output +/deterministic+ +/langversion:latest +/warnaserror+:NU1605 + +[sourceFiles] +obj/Debug/ + .NETCoreApp,Version=v6.0.AssemblyAttributes.cs + DorkNet.ClientPatch.AssemblyInfo.cs +Plugin.cs + +[metadataReferences] +/microsoft.netcore.app.ref/6.0.36/ref/net6.0/ + Microsoft.CSharp.dll + Microsoft.VisualBasic.Core.dll + Microsoft.VisualBasic.dll + Microsoft.Win32.Primitives.dll + Microsoft.Win32.Registry.dll + mscorlib.dll + netstandard.dll + System.AppContext.dll + System.Buffers.dll + System.Collections.Concurrent.dll + System.Collections.dll + System.Collections.Immutable.dll + System.Collections.NonGeneric.dll + System.Collections.Specialized.dll + System.ComponentModel.Annotations.dll + System.ComponentModel.DataAnnotations.dll + System.ComponentModel.dll + System.ComponentModel.EventBasedAsync.dll + System.ComponentModel.Primitives.dll + System.ComponentModel.TypeConverter.dll + System.Configuration.dll + System.Console.dll + System.Core.dll + System.Data.Common.dll + System.Data.DataSetExtensions.dll + System.Data.dll + System.Diagnostics.Contracts.dll + System.Diagnostics.Debug.dll + System.Diagnostics.DiagnosticSource.dll + System.Diagnostics.FileVersionInfo.dll + System.Diagnostics.Process.dll + System.Diagnostics.StackTrace.dll + System.Diagnostics.TextWriterTraceListener.dll + System.Diagnostics.Tools.dll + System.Diagnostics.TraceSource.dll + System.Diagnostics.Tracing.dll + System.dll + System.Drawing.dll + System.Drawing.Primitives.dll + System.Dynamic.Runtime.dll + System.Formats.Asn1.dll + System.Globalization.Calendars.dll + System.Globalization.dll + System.Globalization.Extensions.dll + System.IO.Compression.Brotli.dll + System.IO.Compression.dll + System.IO.Compression.FileSystem.dll + System.IO.Compression.ZipFile.dll + System.IO.dll + System.IO.FileSystem.AccessControl.dll + System.IO.FileSystem.dll + System.IO.FileSystem.DriveInfo.dll + System.IO.FileSystem.Primitives.dll + System.IO.FileSystem.Watcher.dll + System.IO.IsolatedStorage.dll + System.IO.MemoryMappedFiles.dll + System.IO.Pipes.AccessControl.dll + System.IO.Pipes.dll + System.IO.UnmanagedMemoryStream.dll + System.Linq.dll + System.Linq.Expressions.dll + System.Linq.Parallel.dll + System.Linq.Queryable.dll + System.Memory.dll + System.Net.dll + System.Net.Http.dll + System.Net.Http.Json.dll + System.Net.HttpListener.dll + System.Net.Mail.dll + System.Net.NameResolution.dll + System.Net.NetworkInformation.dll + System.Net.Ping.dll + System.Net.Primitives.dll + System.Net.Requests.dll + System.Net.Security.dll + System.Net.ServicePoint.dll + System.Net.Sockets.dll + System.Net.WebClient.dll + System.Net.WebHeaderCollection.dll + System.Net.WebProxy.dll + System.Net.WebSockets.Client.dll + System.Net.WebSockets.dll + System.Numerics.dll + System.Numerics.Vectors.dll + System.ObjectModel.dll + System.Reflection.DispatchProxy.dll + System.Reflection.dll + System.Reflection.Emit.dll + System.Reflection.Emit.ILGeneration.dll + System.Reflection.Emit.Lightweight.dll + System.Reflection.Extensions.dll + System.Reflection.Metadata.dll + System.Reflection.Primitives.dll + System.Reflection.TypeExtensions.dll + System.Resources.Reader.dll + System.Resources.ResourceManager.dll + System.Resources.Writer.dll + System.Runtime.CompilerServices.Unsafe.dll + System.Runtime.CompilerServices.VisualC.dll + System.Runtime.dll + System.Runtime.Extensions.dll + System.Runtime.Handles.dll + System.Runtime.InteropServices.dll + System.Runtime.InteropServices.RuntimeInformation.dll + System.Runtime.Intrinsics.dll + System.Runtime.Loader.dll + System.Runtime.Numerics.dll + System.Runtime.Serialization.dll + System.Runtime.Serialization.Formatters.dll + System.Runtime.Serialization.Json.dll + System.Runtime.Serialization.Primitives.dll + System.Runtime.Serialization.Xml.dll + System.Security.AccessControl.dll + System.Security.Claims.dll + System.Security.Cryptography.Algorithms.dll + System.Security.Cryptography.Cng.dll + System.Security.Cryptography.Csp.dll + System.Security.Cryptography.Encoding.dll + System.Security.Cryptography.OpenSsl.dll + System.Security.Cryptography.Primitives.dll + System.Security.Cryptography.X509Certificates.dll + System.Security.dll + System.Security.Principal.dll + System.Security.Principal.Windows.dll + System.Security.SecureString.dll + System.ServiceModel.Web.dll + System.ServiceProcess.dll + System.Text.Encoding.CodePages.dll + System.Text.Encoding.dll + System.Text.Encoding.Extensions.dll + System.Text.Encodings.Web.dll + System.Text.Json.dll + System.Text.RegularExpressions.dll + System.Threading.Channels.dll + System.Threading.dll + System.Threading.Overlapped.dll + System.Threading.Tasks.Dataflow.dll + System.Threading.Tasks.dll + System.Threading.Tasks.Extensions.dll + System.Threading.Tasks.Parallel.dll + System.Threading.Thread.dll + System.Threading.ThreadPool.dll + System.Threading.Timer.dll + System.Transactions.dll + System.Transactions.Local.dll + System.ValueTuple.dll + System.Web.dll + System.Web.HttpUtility.dll + System.Windows.dll + System.Xml.dll + System.Xml.Linq.dll + System.Xml.ReaderWriter.dll + System.Xml.Serialization.dll + System.Xml.XDocument.dll + System.Xml.XmlDocument.dll + System.Xml.XmlSerializer.dll + System.Xml.XPath.dll + System.Xml.XPath.XDocument.dll + WindowsBase.dll + +[analyzerReferences] +/sdk/9.0.314/Sdks/Microsoft.NET.Sdk/analyzers/ + Microsoft.CodeAnalysis.CSharp.NetAnalyzers.dll + Microsoft.CodeAnalysis.NetAnalyzers.dll +/microsoft.netcore.app.ref/6.0.36/analyzers/dotnet/cs/System.Text.Json.SourceGeneration.dll + +[analyzerConfigFiles] +/sdk/9.0.314/Sdks/Microsoft.NET.Sdk/analyzers/build/config/analysislevel_6_default.globalconfig +obj/Debug/DorkNet.ClientPatch.GeneratedMSBuildEditorConfig.editorconfig diff --git a/DorkNet.ClientPatch/NuGet.Config b/DorkNet.ClientPatch/NuGet.Config new file mode 100644 index 0000000..3bbbc47 --- /dev/null +++ b/DorkNet.ClientPatch/NuGet.Config @@ -0,0 +1,14 @@ + + + + + + + + + diff --git a/DorkNet.ClientPatch/Plugin.cs b/DorkNet.ClientPatch/Plugin.cs new file mode 100644 index 0000000..26a6fe8 --- /dev/null +++ b/DorkNet.ClientPatch/Plugin.cs @@ -0,0 +1,2011 @@ +using System; +using System.Collections.Generic; +using System.Reflection; +using BepInEx; +using BepInEx.Configuration; +using BepInEx.Unity.IL2CPP; +using BepInEx.Logging; +using HarmonyLib; +using ExitGames.Client.Photon.LoadBalancing; // AuthenticationValues, CustomAuthenticationType +using Il2CppInterop.Runtime.InteropTypes.Arrays; +using RecNet; // Core, Login, Matchmaking +using Steamworks; +using UnityEngine; + +namespace DorkNet.ClientPatch; + +/// +/// BepInEx 6 IL2CPP plugin that owns every client-side change DorkNet +/// needs to make to the 2020 watch — Photon AppId override, RecNet +/// domain rewrite, and Photon CustomAuth identity injection. +/// +/// Replaces the previous patch-client.ps1 byte-edit approach so the +/// flow on a client update is now: download the new build → drop the +/// plugin in BepInEx/plugins/ → done. No more hardcoded resources.assets +/// offsets, no more hosts-file edits, no more mkcert wildcard. +/// +/// Configuration lives in BepInEx/config/sh.dork.clientpatch.cfg +/// (auto-created on first run). Edit values, restart the client, the +/// patches re-apply with the new settings. +/// +[BepInPlugin(MyPluginInfo.PLUGIN_GUID, MyPluginInfo.PLUGIN_NAME, MyPluginInfo.PLUGIN_VERSION)] +public sealed class Plugin : BasePlugin +{ + internal static new ManualLogSource Log = null!; + + /// Replacement domain for every *.rec.net URL the watch + /// would otherwise hit. "localhost" means + /// https://ns.rec.net/?v=2 → https://ns.localhost/?v=2 at + /// runtime (Uri ctor patch). Empty disables the rewrite. + public static ConfigEntry ServerHost = null!; + + /// Photon Cloud Realtime AppId. When non-empty, replaces + /// PhotonNetwork.PhotonServerSettings.AppID right before + /// ConnectUsingSettings hands off to NetworkingPeer. + public static ConfigEntry PhotonAppId = null!; + + /// Photon Voice AppId. Empty falls back to + /// . + public static ConfigEntry PhotonVoiceAppId = null!; + + /// Send userid+LoginLock AuthValues with the + /// Photon connect so DorkNet's /photon/customauth endpoint + /// can validate the session. Disable while debugging Photon + /// connectivity if AuthValues are interfering. + public static ConfigEntry InjectAuthValues = null!; + + /// Replacement branding string. Every user-facing UI + /// label that contains "RecNet" gets the substring replaced + /// with this value at runtime — so error dialogs read + /// "Failed to connect to DorkNet" instead of + /// "Failed to connect to RecNet". Empty disables the rewrite + /// (useful if you want logs to keep matching upstream + /// documentation). + public static ConfigEntry BrandName = null!; + public static ConfigEntry EnableBrandRename = null!; + + /// Skip the Steam DRM "RestartAppIfNecessary" check. The + /// vanilla watch calls + /// Steamworks.SteamAPI.RestartAppIfNecessary(471710) on + /// boot, which forces a relaunch through Steam if the .exe was + /// started directly (Steam not running, or launched from + /// outside Steam). Enabling this returns false from that call so + /// the watch keeps booting wherever it was started. Has no effect + /// if you're launching from Steam normally. + public static ConfigEntry SkipSteamRestart = null!; + + /// Fake out the Steamworks SDK so the watch can boot + /// without Steam.exe running. Patches the C#-level Steamworks.NET + /// wrapper methods to return canned success values WITHOUT calling + /// the underlying native steam_api64.dll, so we don't need + /// Goldberg / SmartSteamEmu / etc. (which trip Defender as PUA). + /// + /// Patches applied when on: + /// - SteamAPI.Init → true + /// - SteamAPI.IsSteamRunning → true + /// - SteamAPI.GetHSteamPipe → fake non-zero handle + /// - SteamAPI.GetHSteamUser → fake non-zero handle + /// - SteamUser.GetSteamID → CSteamID built from FakeSteamId + /// - SteamFriends.GetPersonaName → FakeAccountName + /// - SteamPlatformManager.GetAuthSessionTicket → fake bytes + /// + /// Our DorkNet auth backend doesn't actually validate the Steam + /// ticket — it just uses platformId (SteamID) as the + /// cached_login lookup key — so the fake ticket is fine. + public static ConfigEntry FakeSteamApi = null!; + + /// SteamID64 to fake when is on. + /// Match the value our auth backend has on file for the player — + /// the same SteamID lets the player keep their account across + /// machines. Empty = deterministic per-machine ID derived from + /// the OS user + machine name. + public static ConfigEntry FakeSteamId = null!; + + /// Display name returned from + /// Steamworks.SteamFriends.GetPersonaName when + /// is on. Empty = the OS user name. + public static ConfigEntry FakeAccountName = null!; + public static ConfigEntry EnableTlsTrustBypass = null!; + public static ConfigEntry EnablePhotonRegionBypass = null!; + public static ConfigEntry EnableBootDiagnostics = null!; + public static ConfigEntry DetailedUnityLogs = null!; + + /// Targeted diagnostics for avatar/store DTOs and UI binding + /// failures. This is intentionally narrower than raw HTTP tracing so the + /// logs identify crash-causing item ids/GUIDs without dumping tokens or + /// every request body. + public static ConfigEntry DetailedAvatarStoreLogs = null!; + + /// Legacy crash guard that replaces avatar item GUIDs unknown to + /// the local client with a known-safe item before the client parser runs. + /// Keep off by default now that the server should only send client-known + /// wardrobe items; enabling it changes the visible equipped item. + public static ConfigEntry SubstituteUnknownAvatarItems = null!; + + /// Legacy guard for stale servers that returned the wrong shape + /// from storefront objective completion. Keep off by default so the client + /// exercises the real server endpoint. + public static ConfigEntry SkipStorefrontObjectivePosts = null!; + + /// Legacy mirror guard that blocks direct torso unequip. This can + /// interfere with normal outfit editing, so it is opt-in only. + public static ConfigEntry BlockDirectTorsoUnequip = null!; + + /// Cleanup exception suppressor used only while debugging teardown + /// crashes. Keep off by default so real crashes stay visible. + public static ConfigEntry SuppressScreenCleanupNullRef = null!; + public static ConfigEntry FailFastRequiredPatches = null!; + public static ConfigEntry CatchAllManagedExceptions = null!; + + public override void Load() + { + Log = base.Log; + + ServerHost = Config.Bind( + "Server", "Host", "localhost", + "Domain that replaces *.rec.net in every URL the watch builds. " + + "Empty disables the rewrite (use only if pointing at the official servers)."); + + PhotonAppId = Config.Bind( + "Photon", "AppId", string.Empty, + "Photon Cloud Realtime AppId (UUID, no curly braces). " + + "Empty leaves the AppId baked into the build untouched."); + + PhotonVoiceAppId = Config.Bind( + "Photon", "VoiceAppId", string.Empty, + "Photon Cloud Voice AppId. Empty falls back to AppId."); + + InjectAuthValues = Config.Bind( + "Photon", "InjectAuthValues", true, + "Attach userid + LoginLock to PhotonNetwork.AuthValues right before " + + "OpAuthenticate so DorkNet's /photon/customauth endpoint can identify " + + "the player. Disable to debug pure Photon connectivity issues."); + + BrandName = Config.Bind( + "UI", "BrandName", "DorkNet", + "Replacement branding string. Any UI label containing 'RecNet' " + + "gets that substring replaced with this value at runtime, so " + + "error dialogs and the loading screen show the private-server " + + "brand. Empty disables the rewrite."); + + EnableBrandRename = Config.Bind( + "UI", "EnableBrandRename", false, + "Patch Unity/TMP text setters to replace RecNet with BrandName. " + + "Disabled by default because broad UI text setter hooks add a lot " + + "of IL2CPP detours for cosmetic value only."); + + SkipSteamRestart = Config.Bind( + "Steam", "SkipRestartAppIfNecessary", true, + "Bypass Steamworks.SteamAPI.RestartAppIfNecessary so the watch " + + "doesn't relaunch itself through Steam on boot. No effect when " + + "launched normally from the Steam library; useful when running " + + "the game directly via Recroom_Release.exe (BepInEx mod-loader " + + "shims often need this to avoid an infinite restart loop)."); + + FakeSteamApi = Config.Bind( + "Steam", "FakeSteamApi", false, + "Pretend the Steam SDK initialised even when Steam.exe isn't " + + "running. Patches Steamworks.NET wrappers to return canned " + + "success values so the watch's SteamPlatformManager.Initialize " + + "succeeds without a live Steam process. DorkNet's auth backend " + + "uses SteamID as a lookup key only — no real ticket validation — " + + "so the fake ticket is accepted. Default off so launches via " + + "Steam itself behave normally."); + + FakeSteamId = Config.Bind( + "Steam", "FakeSteamId", string.Empty, + "SteamID64 returned by SteamUser.GetSteamID when FakeSteamApi=true. " + + "Empty = random 63-bit ID generated on first run and persisted " + + "to BepInEx/config/sh.dork.clientpatch.deviceid. Reuses the " + + "same ID on every subsequent launch on the same install. " + + "Pin an explicit ID to share an account across different " + + "machines (e.g. paste your real SteamID64 to migrate from a " + + "previous manually-modded client)."); + + FakeAccountName = Config.Bind( + "Steam", "FakeAccountName", string.Empty, + "Display name returned by SteamFriends.GetPersonaName when " + + "FakeSteamApi=true. Empty = OS user name."); + + EnableTlsTrustBypass = Config.Bind( + "Network", "EnableTlsTrustBypass", true, + "Bypass the old BestHTTP/BouncyCastle certificate verifier. " + + "Required for many modern certificates, but kept as a switch so " + + "the patch can be removed when testing plugin stability."); + + EnablePhotonRegionBypass = Config.Bind( + "Photon", "EnableRegionBypass", true, + "Bypass the 2020 Photon region ping coroutine and force us. " + + "Required when modern Photon returns region codes this client " + + "does not know."); + + EnableBootDiagnostics = Config.Bind( + "Diagnostics", "EnableBootDiagnostics", false, + "Patch BootSequence.RegisterError to log extra stack traces. " + + "Disabled by default because it hooks a fragile IL2CPP boot method."); + + DetailedUnityLogs = Config.Bind( + "Diagnostics", "DetailedUnityLogs", true, + "Enable full Unity stack traces for Log/Warning/Error/Exception " + + "messages and mirror Unity exceptions into the BepInEx log. " + + "Useful while chasing client teardown crashes."); + + DetailedAvatarStoreLogs = Config.Bind( + "Diagnostics", "DetailedAvatarStoreLogs", true, + "Log avatar/store DTOs and BrowsableAvatarItem binding failures. " + + "Useful when the watch tears down after opening the store, mirror, " + + "or gift UI. Logs are capped per run to keep LogOutput.log readable."); + + SubstituteUnknownAvatarItems = Config.Bind( + "Compatibility", "SubstituteUnknownAvatarItems", true, + "Replace avatar item GUIDs the local client does not know with a " + + "safe fallback item. This prevents parser crashes from bad server " + + "data, but can change clothing appearance for unsupported items."); + + SkipStorefrontObjectivePosts = Config.Bind( + "Compatibility", "SkipStorefrontObjectivePosts", false, + "Skip RecNet.Storefronts.CompleteObjectives client calls. This was " + + "only for stale servers that returned the wrong response shape; " + + "leave off so the server endpoint is tested normally."); + + BlockDirectTorsoUnequip = Config.Bind( + "Compatibility", "BlockDirectTorsoUnequip", false, + "Block PlayerAvatar.UnequipOutfitItem when the target body part is " + + "torso. This can stop the mirror UI from unequipping shirts, so it " + + "is disabled by default."); + + SuppressScreenCleanupNullRef = Config.Bind( + "Diagnostics", "SuppressScreenCleanupNullRef", false, + "Suppress one known ScreenPlayerController.CleanupLocalPlayer " + + "NullReferenceException during teardown. Disabled by default so " + + "crashes stay visible in logs."); + + FailFastRequiredPatches = Config.Bind( + "Diagnostics", "FailFastRequiredPatches", false, + "Abort plugin startup when a required Harmony patch cannot be " + + "applied. Disabled by default so one IL2CPP target mismatch is " + + "logged without taking down every other patch."); + + CatchAllManagedExceptions = Config.Bind( + "Compatibility", "CatchAllManagedExceptions", true, + "Install broad managed exception guards: observe task exceptions, " + + "log AppDomain unhandled exceptions, and swallow exceptions that " + + "escape the Rec Room scheduler frame loop. This cannot recover " + + "native Unity access violations."); + + GlobalManagedCatchAll.Install(); + UnityDetailedLogBridge.Install(); + + Log.LogInfo($"{MyPluginInfo.PLUGIN_NAME} v{MyPluginInfo.PLUGIN_VERSION} loaded. " + + $"ServerHost={ServerHost.Value}, PhotonAppId={(string.IsNullOrEmpty(PhotonAppId.Value) ? "" : "")}, " + + $"InjectAuthValues={InjectAuthValues.Value}, DetailedUnityLogs={DetailedUnityLogs.Value}, " + + $"DetailedAvatarStoreLogs={DetailedAvatarStoreLogs.Value}, " + + $"EnableBrandRename={EnableBrandRename.Value}, EnableTlsTrustBypass={EnableTlsTrustBypass.Value}, " + + $"EnablePhotonRegionBypass={EnablePhotonRegionBypass.Value}, EnableBootDiagnostics={EnableBootDiagnostics.Value}, " + + $"SubstituteUnknownAvatarItems={SubstituteUnknownAvatarItems.Value}, " + + $"SkipStorefrontObjectivePosts={SkipStorefrontObjectivePosts.Value}, " + + $"BlockDirectTorsoUnequip={BlockDirectTorsoUnequip.Value}, " + + $"SuppressScreenCleanupNullRef={SuppressScreenCleanupNullRef.Value}, " + + $"FailFastRequiredPatches={FailFastRequiredPatches.Value}, " + + $"CatchAllManagedExceptions={CatchAllManagedExceptions.Value}"); + + var harmony = new Harmony(MyPluginInfo.PLUGIN_GUID); + ApplyPatches(harmony); + } + + private static void ApplyPatches(Harmony harmony) + { + // Do not use PatchAll here. On IL2CPP, even discovering and + // preparing unused patch classes can touch fragile generated methods. + // Keep the active detour set explicit and close to the manual patcher. + PatchRequired(harmony, typeof(UriStringCtorPatch), UriStringCtorPatch.Prepare(), "URI string rewrite"); + PatchRequired(harmony, typeof(UriStringKindCtorPatch), UriStringKindCtorPatch.Prepare(), "URI string/kind rewrite"); + PatchRequired(harmony, typeof(PhotonAppIdOverride), PhotonAppIdOverride.Prepare(), "Photon AppId override"); + PatchRequired(harmony, typeof(AuthValuesInjector), AuthValuesInjector.Prepare(), "Photon auth injector"); + PatchRequired(harmony, typeof(TlsTrustServerOnly), TlsTrustServerOnly.Prepare(), "BestHTTP TLS trust bypass"); + PatchRequired(harmony, typeof(TlsTrustLegacy), TlsTrustLegacy.Prepare(), "legacy TLS trust bypass"); + PatchRequired(harmony, typeof(PhotonRegionPingShortcut), PhotonRegionPingShortcut.Prepare(), "Photon region shortcut"); + + PatchOptional(harmony, typeof(TMPTextBrandRename), TMPTextBrandRename.Prepare(), "TMP brand rename"); + PatchOptional(harmony, typeof(UITextBrandRename), UITextBrandRename.Prepare(), "Unity UI brand rename"); + PatchOptional(harmony, typeof(SteamRestartBypass), SteamRestartBypass.Prepare(), "Steam restart bypass"); + + PatchOptional(harmony, typeof(InteropHelpTestClientFake), SteamFakery.Enabled, "fake Steam client availability"); + PatchOptional(harmony, typeof(InteropHelpTestServerFake), SteamFakery.Enabled, "fake Steam server availability"); + PatchOptional(harmony, typeof(InteropHelpTestPlatformFake), SteamFakery.Enabled, "fake Steam platform availability"); + PatchOptional(harmony, typeof(SteamApiInitFake), SteamFakery.Enabled, "fake Steam init"); + PatchOptional(harmony, typeof(SteamApiIsRunningFake), SteamFakery.Enabled, "fake Steam running"); + PatchOptional(harmony, typeof(SteamApiGetPipeFake), SteamFakery.Enabled, "fake Steam pipe"); + PatchOptional(harmony, typeof(SteamApiGetUserFake), SteamFakery.Enabled, "fake Steam user"); + PatchOptional(harmony, typeof(SteamUserIdFake), SteamFakery.Enabled, "fake Steam id"); + PatchOptional(harmony, typeof(SteamFriendsPersonaNameFake), SteamFakery.Enabled, "fake Steam persona"); + PatchOptional(harmony, typeof(SteamFriendsCountFake), SteamFakery.Enabled, "fake Steam friend count"); + PatchOptional(harmony, typeof(SteamFriendsByIndexFake), SteamFakery.Enabled, "fake Steam friend index"); + PatchOptional(harmony, typeof(SteamApiRunCallbacksFake), SteamFakery.Enabled, "fake Steam callbacks"); + PatchOptional(harmony, typeof(SteamApiShutdownFake), SteamFakery.Enabled, "fake Steam shutdown"); + PatchOptional(harmony, typeof(SteamClientWarningHookFake), SteamFakery.Enabled, "fake Steam warning hook"); + PatchOptional(harmony, typeof(SteamCallbackNativeRegistrationFake), SteamFakery.Enabled, "fake Steam callback registration"); + PatchOptional(harmony, typeof(SteamFriendsFriendNameFake), SteamFakery.Enabled, "fake Steam friend name"); + PatchOptional(harmony, typeof(SteamFriendsAvatarFake), SteamFakery.Enabled, "fake Steam avatar"); + PatchOptional(harmony, typeof(SteamFriendsRichPresenceFake), SteamFakery.Enabled, "fake Steam rich presence"); + PatchOptional(harmony, typeof(SteamUtilsImageRGBAFake), SteamFakery.Enabled, "fake Steam image rgba"); + PatchOptional(harmony, typeof(SteamUtilsImageSizeFake), SteamFakery.Enabled, "fake Steam image size"); + PatchOptional(harmony, typeof(SteamGetAuthTicketShortcut), SteamFakery.Enabled, "fake Steam platform ticket"); + PatchOptional(harmony, typeof(SteamUserGetAuthTicketFake), SteamFakery.Enabled, "fake Steam user ticket"); + + PatchOptional(harmony, typeof(AvatarItemParseSubstitute), AvatarItemParseSubstitute.Prepare(), "avatar item substitute"); + PatchOptional(harmony, typeof(AvatarItemParseSubstituteTwoArg), AvatarItemParseSubstituteTwoArg.Prepare(), "avatar item substitute two-arg"); + PatchOptional(harmony, typeof(DynamicAvatarItemImposterCatchAll), GlobalManagedCatchAll.Enabled, "avatar imposter catch-all"); + PatchOptional(harmony, typeof(SchedulerManagedCatchAll), GlobalManagedCatchAll.Enabled, "scheduler managed catch-all"); + PatchOptional(harmony, typeof(StorefrontCompleteObjectivesGuard), StorefrontCompleteObjectivesGuard.Prepare(), "storefront objective guard"); + PatchOptional(harmony, typeof(StorefrontGiftDropDiagnostics), AvatarStoreDiagnostics.Enabled, "storefront gift diagnostics"); + PatchOptional(harmony, typeof(UnlockedAvatarItemDiagnostics), AvatarStoreDiagnostics.Enabled, "unlocked avatar diagnostics"); + PatchOptional(harmony, typeof(GiftPackageDiagnostics), AvatarStoreDiagnostics.Enabled, "gift package diagnostics"); + PatchOptional(harmony, typeof(StorefrontBalanceDiagnostics), AvatarStoreDiagnostics.Enabled, "storefront balance diagnostics"); + PatchOptional(harmony, typeof(PlayerAvatarOutfitDiagnostics), AvatarStoreDiagnostics.Enabled, "player outfit diagnostics"); + PatchOptional(harmony, typeof(RequiredTorsoUnequipGuard), RequiredTorsoUnequipGuard.Prepare(), "torso unequip guard"); + PatchOptional(harmony, typeof(ScreenPlayerCleanupGuard), ScreenPlayerCleanupGuard.Prepare(), "screen cleanup guard"); + PatchOptional(harmony, typeof(BootSequenceRegisterErrorTrace), BootSequenceRegisterErrorTrace.Prepare(), "boot diagnostics"); + + Log.LogInfo("Explicit Harmony patch list applied."); + } + + private static void PatchRequired(Harmony harmony, Type patchType, bool enabled, string label) + { + if (!enabled) + { + Log.LogInfo($"[patch-skip] {label}"); + return; + } + + try + { + harmony.CreateClassProcessor(patchType).Patch(); + Log.LogInfo($"[patch-ok] {label}"); + } + catch (Exception ex) + { + Log.LogError($"[patch-failed] {label}: {ex}"); + if (FailFastRequiredPatches?.Value == true) + throw; + } + } + + private static void PatchOptional(Harmony harmony, Type patchType, bool enabled, string label) + { + if (!enabled) + { + Log.LogDebug($"[patch-skip] {label}"); + return; + } + + try + { + harmony.CreateClassProcessor(patchType).Patch(); + Log.LogInfo($"[patch-ok] {label}"); + } + catch (Exception ex) + { + Log.LogWarning($"[patch-failed] optional {label}: {ex}"); + } + } +} + +internal static class MyPluginInfo +{ + public const string PLUGIN_GUID = "sh.dork.clientpatch"; + public const string PLUGIN_NAME = "DorkNet ClientPatch"; + public const string PLUGIN_VERSION = "1.4.8"; +} + +// ── 1. URI rewrite — every Uri the watch builds with a .rec.net host +// gets its host substituted to ServerHost. Catches the bootstrap +// https://ns.rec.net/?v=2 (the only hard-coded URL in the watch — +// every other service URL comes back in that response, which our +// server controls). Also catches any odd hard-coded Photon Voice / +// realtime URLs the SDK might construct. +// +// Patching the BCL Uri constructor is broader than ideal — any +// string containing ".rec.net" will be rewritten — but in practice +// the watch doesn't construct any non-URL strings with that +// substring. If false positives appear we'll narrow to the +// HTTPRequest constructor or transpiler the call site. + +[HarmonyPatch(typeof(Uri), MethodType.Constructor, new[] { typeof(string) })] +internal static class UriStringCtorPatch +{ + public static bool Prepare() => + Plugin.ServerHost != null && !string.IsNullOrEmpty(Plugin.ServerHost.Value); + + public static void Prefix(ref string uriString) + { + if (string.IsNullOrEmpty(uriString)) return; + if (string.IsNullOrEmpty(Plugin.ServerHost.Value)) return; + if (uriString.IndexOf(".rec.net", StringComparison.OrdinalIgnoreCase) < 0) return; + var rewritten = uriString.Replace(".rec.net", "." + Plugin.ServerHost.Value); + if (rewritten != uriString) + { + Plugin.Log.LogInfo($"[uri-rewrite] {uriString} → {rewritten}"); + uriString = rewritten; + } + } +} + +[HarmonyPatch(typeof(Uri), MethodType.Constructor, new[] { typeof(string), typeof(UriKind) })] +internal static class UriStringKindCtorPatch +{ + public static bool Prepare() => UriStringCtorPatch.Prepare(); + + public static void Prefix(ref string uriString) + { + // Reuse the string-only patch's logic by piping through it. Simpler + // than copy-pasting and keeps a single rewrite implementation. + UriStringCtorPatch.Prefix(ref uriString); + } +} + +// ── 2. Photon AppId override — patched at the latest possible moment +// (right before NetworkingPeer.Connect uses ServerSettings.AppID). +// Patching the ScriptableObject directly at plugin Load doesn't +// work because ServerSettings is loaded from Resources lazily; by +// the time Connect runs, it's loaded and we can mutate it. + +[HarmonyPatch(typeof(PhotonNetwork), nameof(PhotonNetwork.ConnectUsingSettings))] +internal static class PhotonAppIdOverride +{ + public static bool Prepare() => + Plugin.PhotonAppId != null && !string.IsNullOrEmpty(Plugin.PhotonAppId.Value); + + public static void Prefix() + { + if (string.IsNullOrEmpty(Plugin.PhotonAppId.Value)) return; + try + { + var settings = PhotonNetwork.PhotonServerSettings; + if (settings == null) return; + + var newAppId = Plugin.PhotonAppId.Value; + var newVoiceId = string.IsNullOrEmpty(Plugin.PhotonVoiceAppId.Value) + ? newAppId + : Plugin.PhotonVoiceAppId.Value; + + if (settings.AppID != newAppId || settings.VoiceAppID != newVoiceId) + { + Plugin.Log.LogInfo( + $"[photon-appid] override AppID={settings.AppID}→{newAppId}, " + + $"VoiceAppID={settings.VoiceAppID}→{newVoiceId}"); + settings.AppID = newAppId; + settings.VoiceAppID = newVoiceId; + } + } + catch (Exception ex) + { + Plugin.Log.LogWarning($"[photon-appid] override failed: {ex.Message}"); + } + } +} + +// ── 3. Photon AuthValues injector — sets userid + LoginLock in +// PhotonNetwork.AuthValues right before any authenticate op leaves +// the wire. Verified empirically: vanilla 2020 watch never sets +// AuthValues itself, so we have to do it externally for our +// /photon/customauth endpoint to receive identity. + +// NetworkingPeer.CallAuthenticate is the watch's single internal entry +// point that ultimately calls LoadBalancingPeer.OpAuthenticate / +// OpAuthenticateOnce on the wire. The vanilla 2020 watch never sets +// PhotonNetwork.AuthValues itself, so we set it here just before +// CallAuthenticate forwards to the base op. +[HarmonyPatch(typeof(NetworkingPeer), nameof(NetworkingPeer.CallAuthenticate))] +internal static class AuthValuesInjector +{ + public static bool Prepare() => + Plugin.InjectAuthValues != null && Plugin.InjectAuthValues.Value; + + [HarmonyPrefix] + public static void EnsureAuthValues(NetworkingPeer __instance) + { + if (!Plugin.InjectAuthValues.Value) return; + try + { + var existing = PhotonNetwork.AuthValues; + if (existing != null && !string.IsNullOrEmpty(existing.UserId)) + { + // Already set this run — don't clobber. Lets a future + // patch upstream us if needed. + return; + } + + var accountId = Core.LocalAccountId; + if (accountId <= 0) + { + // Pre-login Photon ping has no account yet; let it go + // through anonymous, the post-login reconnect will pick + // up our values. + Plugin.Log.LogInfo("[auth-injector] skip — no LocalAccountId yet"); + return; + } + + // Read the LoginLock straight from PlayerPrefs (key + // LoginLockTokenV2). The watch persists it there in + // Matchmaking.Login at login time, so by the time any + // Photon op fires it's already on disk — no need to call + // the private Matchmaking.LoadLockToken(). + var token = MatchmakingHelpers.GetCurrentLockToken(); + + var av = new AuthenticationValues + { + AuthType = CustomAuthenticationType.Custom, + UserId = accountId.ToString(), + }; + av.AddAuthParameter("userid", accountId.ToString()); + av.AddAuthParameter("LoginLock", token ?? string.Empty); + + PhotonNetwork.AuthValues = av; + + Plugin.Log.LogInfo( + $"[auth-injector] set Photon AuthValues userid={accountId} " + + $"LoginLock={(string.IsNullOrEmpty(token) ? "" : "")}"); + } + catch (Exception ex) + { + Plugin.Log.LogWarning($"[auth-injector] failed: {ex.Message}"); + } + } +} + +// ── 4. UI brand rename — replace "RecNet" → BrandName in any text the +// watch displays. Hook the two universal text-set entry points +// (Unity UI Text + TMP_Text) so we catch both legacy and modern +// UI bindings. The watch's hardcoded error strings ("Failed to +// connect to RecNet (error code: …)", "Connecting to RecNet…", +// etc.) flow through these setters before reaching the user. + +[HarmonyPatch(typeof(TMPro.TMP_Text), nameof(TMPro.TMP_Text.text), MethodType.Setter)] +internal static class TMPTextBrandRename +{ + public static bool Prepare() => + Plugin.EnableBrandRename != null && Plugin.EnableBrandRename.Value && + Plugin.BrandName != null && !string.IsNullOrEmpty(Plugin.BrandName.Value); + + public static void Prefix(ref string value) + { + BrandRenameHelper.Apply(ref value); + } +} + +[HarmonyPatch(typeof(UnityEngine.UI.Text), nameof(UnityEngine.UI.Text.text), MethodType.Setter)] +internal static class UITextBrandRename +{ + public static bool Prepare() => TMPTextBrandRename.Prepare(); + + public static void Prefix(ref string value) + { + BrandRenameHelper.Apply(ref value); + } +} + +internal static class BrandRenameHelper +{ + public static void Apply(ref string value) + { + if (string.IsNullOrEmpty(value)) return; + var brand = Plugin.BrandName.Value; + if (string.IsNullOrEmpty(brand)) return; + if (value.IndexOf("RecNet", StringComparison.Ordinal) < 0) return; + var replaced = value.Replace("RecNet", brand); + if (replaced != value) value = replaced; + } +} + +// ── 5. Steam DRM bypass — short-circuit +// Steamworks.SteamAPI.RestartAppIfNecessary so the watch doesn't +// forcibly re-launch through Steam when the .exe was started +// directly. Has no effect when launched from the Steam library +// (RestartAppIfNecessary returns false anyway in that case); +// matters when running the game outside Steam, which is the +// common modding/dev path. + +[HarmonyPatch(typeof(Steamworks.SteamAPI), nameof(Steamworks.SteamAPI.RestartAppIfNecessary))] +internal static class SteamRestartBypass +{ + public static bool Prepare() => + Plugin.SkipSteamRestart != null && Plugin.SkipSteamRestart.Value; + + public static bool Prefix(ref bool __result) + { + if (!Plugin.SkipSteamRestart.Value) return true; // run original + Plugin.Log.LogInfo("[steam-bypass] short-circuiting RestartAppIfNecessary → false"); + __result = false; + return false; // skip original + } +} + +// ── 6. Fake Steamworks SDK — when FakeSteamApi=true we hijack the +// C#-level Steamworks.NET wrapper methods to return canned values +// WITHOUT calling steam_api64.dll. This is the AV-friendly +// alternative to Goldberg/SmartSteamEmu — pure managed code, +// no replacement DLL, no PUA flag. +// +// Coverage is just-enough for the watch's boot path. We patch: +// - SteamAPI.Init / IsSteamRunning / GetHSteamPipe / GetHSteamUser +// - SteamUser.GetSteamID / GetAuthSessionTicket +// - SteamFriends.GetPersonaName +// If the watch ever calls a Steamworks method we don't fake, the +// underlying DLL stub returns its default (usually "no" / 0) and +// that path quietly does nothing — better than crashing. + +internal static class SteamFakery +{ + public static bool Enabled => + Plugin.FakeSteamApi != null && Plugin.FakeSteamApi.Value; + + private static ulong? _resolvedSteamId; + public static ulong ResolvedSteamId + { + get + { + if (_resolvedSteamId.HasValue) return _resolvedSteamId.Value; + var raw = Plugin.FakeSteamId.Value; + if (!string.IsNullOrEmpty(raw) && ulong.TryParse(raw, out var explicitId)) + { + _resolvedSteamId = explicitId; + return explicitId; + } + // Per-install random ID, persisted to BepInEx/config/ + // sh.dork.clientpatch.deviceid. Generated once on first + // run; reused on every subsequent launch on the same + // install. Keeps the same DorkNet account across plugin + // updates and game updates, but a clean reinstall (or a + // different machine without the file copied over) gets a + // brand-new account. + // + // Why not SHA256(username + machine)? The mod-31-bit + // collision space (we packed it into the 9-digit + // 76561198_xxxxxxxxx slot) made birthday-paradox collisions + // realistic — Alexa@DESKTOP-XYZ is plausibly the same as + // someone else's. Random 63-bit gives ~2³¹ users before + // 50% collision, which we'll comfortably never hit. + var deviceFile = System.IO.Path.Combine( + BepInEx.Paths.ConfigPath, "sh.dork.clientpatch.deviceid"); + ulong rand; + try + { + if (System.IO.File.Exists(deviceFile) && + ulong.TryParse(System.IO.File.ReadAllText(deviceFile).Trim(), out var stored)) + { + rand = stored; + } + else + { + var bytes = new byte[8]; + System.Security.Cryptography.RandomNumberGenerator.Fill(bytes); + // Mask top bit so the resulting SteamID64 fits the + // valid Public-universe individual range (the + // top byte stays 0x01). + rand = System.BitConverter.ToUInt64(bytes, 0) & 0x7FFF_FFFF_FFFF_FFFFUL; + System.IO.File.WriteAllText(deviceFile, rand.ToString()); + Plugin.Log.LogInfo($"[steam-fake] generated new deviceid → {deviceFile}"); + } + } + catch (Exception ex) + { + // Filesystem unavailable for some reason — fall back + // to a one-shot random ID so the boot still proceeds. + // (Account won't persist across this session, but at + // least the player can play.) + Plugin.Log.LogWarning($"[steam-fake] deviceid persistence failed ({ex.Message}); using ephemeral ID"); + var bytes = new byte[8]; + System.Security.Cryptography.RandomNumberGenerator.Fill(bytes); + rand = System.BitConverter.ToUInt64(bytes, 0) & 0x7FFF_FFFF_FFFF_FFFFUL; + } + // SteamID64 layout: top 32 bits = universe(8) + accountType(4) + instance(20), + // bottom 32 bits = accountID. The 76561197960265728 prefix is + // (Public, Individual, Desktop). We OR our random low-32 bits in. + const ulong SteamId64Base = 76561197960265728UL; + _resolvedSteamId = SteamId64Base + (rand & 0xFFFF_FFFFUL); + return _resolvedSteamId.Value; + } + } + + public static string ResolvedAccountName => + string.IsNullOrEmpty(Plugin.FakeAccountName.Value) + ? (System.Environment.UserName ?? "DorkNetTester") + : Plugin.FakeAccountName.Value; +} + +// InteropHelp.TestIfAvailableClient/TestIfPlatformSupported/TestIfAvailable +// GameServer are the gate every Steamworks.NET wrapper passes through +// before delegating to the native steam_api64.dll. With FakeSteamApi +// they would otherwise throw "Steamworks is not initialized" because +// no real native pipe was opened — even though our SteamAPI.Init +// prefix returned true, the wrapper checks an internal sentinel set +// by the native init callback. Patch these three to no-op so EVERY +// Steamworks.NET method behind them silently uses our faked output +// (or quietly does nothing for ones we haven't faked). +[HarmonyPatch(typeof(Steamworks.InteropHelp), nameof(Steamworks.InteropHelp.TestIfAvailableClient))] +internal static class InteropHelpTestClientFake +{ + public static bool Prepare() => SteamFakery.Enabled; + public static bool Prefix() => !Plugin.FakeSteamApi.Value; // skip original when faking +} + +[HarmonyPatch(typeof(Steamworks.InteropHelp), nameof(Steamworks.InteropHelp.TestIfAvailableGameServer))] +internal static class InteropHelpTestServerFake +{ + public static bool Prepare() => SteamFakery.Enabled; + public static bool Prefix() => !Plugin.FakeSteamApi.Value; +} + +[HarmonyPatch(typeof(Steamworks.InteropHelp), nameof(Steamworks.InteropHelp.TestIfPlatformSupported))] +internal static class InteropHelpTestPlatformFake +{ + public static bool Prepare() => SteamFakery.Enabled; + public static bool Prefix() => !Plugin.FakeSteamApi.Value; +} + +[HarmonyPatch(typeof(Steamworks.SteamAPI), nameof(Steamworks.SteamAPI.Init))] +internal static class SteamApiInitFake +{ + public static bool Prepare() => SteamFakery.Enabled; + public static bool Prefix(ref bool __result) + { + if (!Plugin.FakeSteamApi.Value) return true; + Plugin.Log.LogInfo("[steam-fake] SteamAPI.Init → true"); + __result = true; + return false; + } +} + +[HarmonyPatch(typeof(Steamworks.SteamAPI), nameof(Steamworks.SteamAPI.IsSteamRunning))] +internal static class SteamApiIsRunningFake +{ + public static bool Prepare() => SteamFakery.Enabled; + public static bool Prefix(ref bool __result) + { + if (!Plugin.FakeSteamApi.Value) return true; + __result = true; + return false; + } +} + +[HarmonyPatch(typeof(Steamworks.SteamAPI), nameof(Steamworks.SteamAPI.GetHSteamPipe))] +internal static class SteamApiGetPipeFake +{ + public static bool Prepare() => SteamFakery.Enabled; + public static bool Prefix(ref Steamworks.HSteamPipe __result) + { + if (!Plugin.FakeSteamApi.Value) return true; + // HSteamPipe is a struct wrapping a uint — pick anything non-zero. + __result = new Steamworks.HSteamPipe(1); + return false; + } +} + +[HarmonyPatch(typeof(Steamworks.SteamAPI), nameof(Steamworks.SteamAPI.GetHSteamUser))] +internal static class SteamApiGetUserFake +{ + public static bool Prepare() => SteamFakery.Enabled; + public static bool Prefix(ref Steamworks.HSteamUser __result) + { + if (!Plugin.FakeSteamApi.Value) return true; + __result = new Steamworks.HSteamUser(1); + return false; + } +} + +[HarmonyPatch(typeof(Steamworks.SteamUser), nameof(Steamworks.SteamUser.GetSteamID))] +internal static class SteamUserIdFake +{ + public static bool Prepare() => SteamFakery.Enabled; + public static bool Prefix(ref Steamworks.CSteamID __result) + { + if (!Plugin.FakeSteamApi.Value) return true; + __result = new Steamworks.CSteamID(SteamFakery.ResolvedSteamId); + return false; + } +} + +[HarmonyPatch(typeof(Steamworks.SteamFriends), nameof(Steamworks.SteamFriends.GetPersonaName))] +internal static class SteamFriendsPersonaNameFake +{ + public static bool Prepare() => SteamFakery.Enabled; + public static bool Prefix(ref string __result) + { + if (!Plugin.FakeSteamApi.Value) return true; + __result = SteamFakery.ResolvedAccountName; + return false; + } +} + +// SteamPlatformManager.PostLoginInitialize iterates the Steam friends +// list (GetFriendCount → for-loop GetFriendByIndex). Without these +// stubs each call falls through TestIfAvailableClient (no-op'd above) +// straight into native steam_api64.dll, which segfaults on our fake +// pipe/user handles. Returning 0 from GetFriendCount short-circuits +// the whole loop so the watch never queries any friend data. +[HarmonyPatch(typeof(Steamworks.SteamFriends), nameof(Steamworks.SteamFriends.GetFriendCount))] +internal static class SteamFriendsCountFake +{ + public static bool Prepare() => SteamFakery.Enabled; + public static bool Prefix(ref int __result) + { + if (!Plugin.FakeSteamApi.Value) return true; + __result = 0; + return false; + } +} + +// Defensive: should never be hit if GetFriendCount returns 0, but if +// some other code path indexes regardless we return an empty CSteamID. +[HarmonyPatch(typeof(Steamworks.SteamFriends), nameof(Steamworks.SteamFriends.GetFriendByIndex))] +internal static class SteamFriendsByIndexFake +{ + public static bool Prepare() => SteamFakery.Enabled; + public static bool Prefix(ref Steamworks.CSteamID __result) + { + if (!Plugin.FakeSteamApi.Value) return true; + __result = new Steamworks.CSteamID(0UL); + return false; + } +} + +// Audit (2026-05-11): grepping every "Call ." in +// the watch's IL2CPP ISIL turned up exactly 14 Steamworks methods used +// across the entire binary. The patches above cover Init / IsRunning / +// GetHSteamPipe / GetHSteamUser / GetSteamID / GetPersonaName / +// GetFriendCount / GetFriendByIndex / GetAuthSessionTicket / +// RestartAppIfNecessary. The remaining 6 are stubbed below — every +// one of them needs a no-op or zero return to avoid falling through +// to a native steam_api64.dll call with our fake handles, which +// segfaults the process. + +// SteamAPI.RunCallbacks is called every frame from a MonoBehaviour. +// Native call dispatches queued callbacks; with no real pipe, it'd +// dereference null and crash. No-op skips the dispatch — our synthetic +// callbacks (e.g. GetAuthSessionTicketResponse) are fired directly, +// not through this queue. +[HarmonyPatch(typeof(Steamworks.SteamAPI), nameof(Steamworks.SteamAPI.RunCallbacks))] +internal static class SteamApiRunCallbacksFake +{ + public static bool Prepare() => SteamFakery.Enabled; + public static bool Prefix() => !Plugin.FakeSteamApi.Value; +} + +// SteamAPI.Shutdown — called on app exit. Native expects to clean up +// a real pipe; no-op so a successful exit doesn't crash. +[HarmonyPatch(typeof(Steamworks.SteamAPI), nameof(Steamworks.SteamAPI.Shutdown))] +internal static class SteamApiShutdownFake +{ + public static bool Prepare() => SteamFakery.Enabled; + public static bool Prefix() => !Plugin.FakeSteamApi.Value; +} + +// SteamClient.SetWarningMessageHook installs a callback for Steam's +// internal warnings. No-op — we don't need warnings, and the native +// call would dereference our zero pipe. +[HarmonyPatch(typeof(Steamworks.SteamClient), nameof(Steamworks.SteamClient.SetWarningMessageHook))] +internal static class SteamClientWarningHookFake +{ + public static bool Prepare() => SteamFakery.Enabled; + public static bool Prefix() => !Plugin.FakeSteamApi.Value; +} + +// The watch registers a few Steam callback bases during +// SteamPlatformManager.Initialize. With FakeSteamApi=true there is no valid +// native Steam pipe behind those callback bases. The one callback the boot path +// needs is synthesized below, so skip native callback registration in fake mode. +[HarmonyPatch] +internal static class SteamCallbackNativeRegistrationFake +{ + public static bool Prepare() => SteamFakery.Enabled; + + public static IEnumerable TargetMethods() + { + foreach (var callbackName in new[] + { + "GetAuthSessionTicketResponse_t", + "GameRichPresenceJoinRequested_t", + "MicroTxnAuthorizationResponse_t", + "GameOverlayActivated_t" + }) + { + var type = AccessTools.TypeByName("Steamworks.SteamCallbacks+" + callbackName); + if (type == null) + { + Plugin.Log.LogWarning($"[steam-fake] callback type not found: {callbackName}"); + continue; + } + + var register = AccessTools.Method(type, "RegisterCallback"); + if (register != null) yield return register; + + var unregister = AccessTools.Method(type, "UnregisterCallback"); + if (unregister != null) yield return unregister; + } + } + + public static bool Prefix(MethodBase __originalMethod) + { + if (!Plugin.FakeSteamApi.Value) return true; + Plugin.Log.LogDebug($"[steam-fake] skip native callback registration: {__originalMethod.DeclaringType?.Name}.{__originalMethod.Name}"); + return false; + } +} + +// SteamFriends.GetFriendPersonaName / GetMediumFriendAvatar — only +// reachable if GetFriendCount > 0, but defensively stubbed in case +// some other call path indexes a friend directly. +[HarmonyPatch(typeof(Steamworks.SteamFriends), nameof(Steamworks.SteamFriends.GetFriendPersonaName))] +internal static class SteamFriendsFriendNameFake +{ + public static bool Prepare() => SteamFakery.Enabled; + public static bool Prefix(ref string __result) + { + if (!Plugin.FakeSteamApi.Value) return true; + __result = string.Empty; + return false; + } +} + +[HarmonyPatch(typeof(Steamworks.SteamFriends), nameof(Steamworks.SteamFriends.GetMediumFriendAvatar))] +internal static class SteamFriendsAvatarFake +{ + public static bool Prepare() => SteamFakery.Enabled; + public static bool Prefix(ref int __result) + { + if (!Plugin.FakeSteamApi.Value) return true; + __result = 0; + return false; + } +} + +// SteamFriends.SetRichPresence — sets the "playing X" string visible +// to Steam friends. Cosmetic; native call would crash. Return true +// (success) so the watch doesn't log a failure. +[HarmonyPatch(typeof(Steamworks.SteamFriends), nameof(Steamworks.SteamFriends.SetRichPresence))] +internal static class SteamFriendsRichPresenceFake +{ + public static bool Prepare() => SteamFakery.Enabled; + public static bool Prefix(ref bool __result) + { + if (!Plugin.FakeSteamApi.Value) return true; + __result = true; + return false; + } +} + +// SteamUtils.GetImageRGBA / GetImageSize — used to fetch Steam avatar +// pixel data. Without a real avatar handle (we return 0 from +// GetMediumFriendAvatar), these would normally not be called, but +// stubbed defensively to fail-fast if the watch tries. +[HarmonyPatch(typeof(Steamworks.SteamUtils), nameof(Steamworks.SteamUtils.GetImageRGBA))] +internal static class SteamUtilsImageRGBAFake +{ + public static bool Prepare() => SteamFakery.Enabled; + public static bool Prefix(ref bool __result) + { + if (!Plugin.FakeSteamApi.Value) return true; + __result = false; + return false; + } +} + +[HarmonyPatch(typeof(Steamworks.SteamUtils), nameof(Steamworks.SteamUtils.GetImageSize))] +internal static class SteamUtilsImageSizeFake +{ + public static bool Prepare() => SteamFakery.Enabled; + public static bool Prefix(ref bool __result) + { + if (!Plugin.FakeSteamApi.Value) return true; + __result = false; + return false; + } +} + + +// SteamPlatformManager.GetAuthSessionTicket() returns an IPromise that +// only completes when Steam fires the GetAuthSessionTicketResponse_t +// callback (real Steam dispatches it asynchronously a few hundred ms +// after GetAuthSessionTicket). With FakeSteamApi=true there's no +// native Steam to fire it, so the watch waits ~5s and then logs +// "Timed out waiting for Steam GetAuthSessionTicketResponse" → login +// stalls for 5s before the boot continues. +// +// Postfix-patch the method so the callback fires synthetically right +// after GetAuthSessionTicket returns: build a k_EResultOK response +// pointing at our fake HAuthTicket and feed it back through the watch's +// own OnGetAuthSessionTicketResponse handler. That completes the +// inner promise immediately and the boot doesn't pay the 5s timeout. +[HarmonyPatch(typeof(SteamPlatformManager), nameof(SteamPlatformManager.GetAuthSessionTicket))] +internal static class SteamGetAuthTicketShortcut +{ + public static bool Prepare() => SteamFakery.Enabled; + + public static void Postfix(SteamPlatformManager __instance) + { + if (!Plugin.FakeSteamApi.Value) return; + try + { + var response = new Steamworks.GetAuthSessionTicketResponse_t + { + m_eResult = Steamworks.EResult.k_EResultOK, + m_hAuthTicket = new Steamworks.HAuthTicket(0xDEADBEEFu), + }; + __instance.OnGetAuthSessionTicketResponse(response); + Plugin.Log.LogInfo("[steam-fake] synth GetAuthSessionTicketResponse → k_EResultOK"); + } + catch (Exception ex) + { + Plugin.Log.LogWarning($"[steam-fake] synth ticket-response failed: {ex.Message}"); + } + } +} + +// SteamUser.GetAuthSessionTicket is the entry point for the watch's +// auth flow. Real signature: +// HAuthTicket GetAuthSessionTicket(byte[] pTicket, int cbMaxTicket, +// out uint pcbTicket) +// We fake a 64-byte ticket containing the SteamID + a marker so our +// /cachedlogin endpoint could in principle distinguish forged vs real +// (it currently doesn't validate — see auth/AuthController.cs). +[HarmonyPatch(typeof(Steamworks.SteamUser), nameof(Steamworks.SteamUser.GetAuthSessionTicket))] +internal static class SteamUserGetAuthTicketFake +{ + public static bool Prepare() => SteamFakery.Enabled; + + public static bool Prefix( + Il2CppStructArray pTicket, + int cbMaxTicket, + ref uint pcbTicket, + ref Steamworks.HAuthTicket __result) + { + if (!Plugin.FakeSteamApi.Value) return true; + + const int desiredLen = 64; + var len = System.Math.Min(desiredLen, cbMaxTicket); + // Fill the ticket with deterministic but-not-zero bytes. First + // 8 bytes = SteamID little-endian; rest = a magic marker so a + // future server-side validator could detect a forged session. + var sid = System.BitConverter.GetBytes(SteamFakery.ResolvedSteamId); + for (int i = 0; i < len; i++) + { + pTicket[i] = i < sid.Length ? sid[i] : (byte)('D' ^ (i & 0x0F)); + } + pcbTicket = (uint)len; + __result = new Steamworks.HAuthTicket(0xDEADBEEF); + Plugin.Log.LogInfo($"[steam-fake] GetAuthSessionTicket → {len} bytes, SteamID={SteamFakery.ResolvedSteamId}"); + return false; + } +} + +// ── 7. TLS cert trust — make BouncyCastle (the watch's BestHTTP TLS +// stack, vendored circa 2020) accept any server certificate. +// +// Why: Coolify auto-issues Let's Encrypt ECDSA certs (E-series +// intermediate → ISRG Root X2). The 2020 BouncyCastle bundle +// pre-dates ISRG Root X2's 2020 launch, so chain validation can't +// reach a trusted root and the TLS handshake aborts with alert 90 +// user_canceled. Swap the cert verifier hooks for no-ops so chain +// validation is bypassed; the actual ECDSA crypto still runs. +// +// Two layers patched: +// a) NotifyServerCertificate (Legacy + ServerOnly Tls +// Authentication) — these are the per-handshake callbacks BC +// invokes after parsing the cert chain. Default impls in this +// build wrap an ICertificateVerifyer; we short-circuit them. +// b) AlwaysValidVerifyer.IsValid is already always-true, so any +// code path that already uses it is fine — the patches above +// convert the rest. +[HarmonyPatch(typeof(BestHTTP.SecureProtocol.Org.BouncyCastle.Crypto.Tls.ServerOnlyTlsAuthentication), + nameof(BestHTTP.SecureProtocol.Org.BouncyCastle.Crypto.Tls.ServerOnlyTlsAuthentication.NotifyServerCertificate))] +internal static class TlsTrustServerOnly +{ + public static bool Prepare() => + Plugin.EnableTlsTrustBypass != null && Plugin.EnableTlsTrustBypass.Value; + + public static bool Prefix() => false; // skip original — never reject +} + +[HarmonyPatch(typeof(Org.BouncyCastle.Crypto.Tls.LegacyTlsAuthentication), + nameof(Org.BouncyCastle.Crypto.Tls.LegacyTlsAuthentication.NotifyServerCertificate))] +internal static class TlsTrustLegacy +{ + public static bool Prepare() => + Plugin.EnableTlsTrustBypass != null && Plugin.EnableTlsTrustBypass.Value; + + public static bool Prefix() => false; // skip original — never reject +} + +// ── 8z. Photon region ping bypass — short-circuit +// PUNNetworkManager.PingPhotonRegions() with a pre-resolved +// {us:1} dictionary so the broken coroutine never runs. +// +// The original PingPhotonRegionsInternal coroutine ToDictionary's +// PhotonNetwork's available-region list keyed by +// `CloudRegionCode`. The 2020 watch's CloudRegionCode enum knows +// 13 codes (eu, us, asia, jp, au, usw, sa, cae, kr, in, ru, rue, +// none) — anything else from Photon Cloud's modern NameServer +// (e.g. usw2, eu2, tr, za, jp2) parses as `none = 4`. Two +// unknown codes → two regions with key `none` → ToDictionary +// throws `ArgumentException: An item with the same key has +// already been added. Key: none` (output_log.txt:209-220), and +// the coroutine fails before completing the promise — every +// consumer awaiting it is stuck. +// +// We can't fix Photon's region list (it comes from +// Photon Cloud's NameServer, governed by the AppId's dashboard +// config). Patching the dashboard to disable unknown regions +// works in theory but the user's AppId is configured for the +// full set. Cheaper to just hand the watch a "single us region" +// promise — Rec Room only ever connected to one region per +// session anyway, and our dorknet config (Photon:CloudRegion) +// is "us" by default. +[HarmonyPatch(typeof(PUNNetworkManager), nameof(PUNNetworkManager.PingPhotonRegions))] +internal static class PhotonRegionPingShortcut +{ + public static bool Prepare() => + Plugin.EnablePhotonRegionBypass != null && Plugin.EnablePhotonRegionBypass.Value; + + public static bool Prefix( + ref RecRoom.Async.IPromise> __result) + { + try + { + // Single-entry dict {us = 1ms}. Watch awaits this + // promise then picks the "best" (only) region. No ping + // is actually performed — the promise resolves + // immediately so downstream Photon connect logic + // proceeds without waiting on a broken coroutine. + var dict = new Il2CppSystem.Collections.Generic.Dictionary< + CloudRegionCode, int>(); + dict[CloudRegionCode.us] = 1; + + var promise = new RecRoom.Async.Promise>(); + promise.Complete(dict.Cast>()); + // Il2CppInterop's Promise wrapper isn't covariantly + // related to IPromise at the C# level, so go through + // Cast() (every interop wrapper exposes it). + __result = promise.Cast>>(); + + // The ORIGINAL coroutine doesn't just return a dict — at + // PingPhotonRegionsInternal.txt:928+940 it also calls + // PhotonNetwork.OverrideBestCloudServer(bestCode) + + // ConnectToBestCloudServer(gameVersion), which is what + // actually opens the wire connection to the chosen region. + // We bypassed the coroutine, so we have to issue those + // calls ourselves — otherwise PhotonNetwork.IsConnected + // stays false and BootSequence's + // b__68_5 fires + // RegisterError("Unable to connect to RecNet game + // servers") (BootSequence.txt:10490). + PhotonNetwork.OverrideBestCloudServer(CloudRegionCode.us); + // gameVersion: empty string keeps the watch's existing + // PhotonServerSettings.AppVersion. The original coroutine + // at line 940 passes a similarly-stub value. + PhotonNetwork.ConnectToBestCloudServer(string.Empty); + + Plugin.Log.LogInfo("[photon-region] short-circuit → {us:1} + ConnectToBestCloudServer"); + return false; // skip original + } + catch (Exception ex) + { + Plugin.Log.LogWarning($"[photon-region] short-circuit failed: {ex.Message}; falling back to original"); + return true; + } + } +} + +// ── 8a. Avatar parse exception swallow — wrap +// AvatarItem.FromRecNetString(string) so an "InvalidOperationException: +// Missing data for guid …" / AvatarParseException("Bad outfit +// string …") doesn't propagate up the IL2CPP stack and tear down +// the watch's player. +// +// The dorm scene's hardcoded ImposterSpawnManager prefabs reference +// avatar items by GUID — some of those GUIDs are SWATCH/MASK asset +// guids (in swatchLookup/maskLookup) instead of +// AvatarItem guids (in avatarItemDataLookup). The watch's +// parser only checks the AvatarItem dict and throws on misses; +// those throws bubble through ImposterSpawnManager.ProcessQueue → +// Scheduler → Update and ultimately destroy the local player +// (output_log.txt:1568-1599). Returning a default AvatarItem +// for the bad guid lets the queue processor skip the item and +// keep going. +// +// The two-arg overload (called by the one-arg overload) is what +// actually throws — patch the entry point so both call sites +// benefit. +// Substitute unknown GUIDs with a known-safe fallback BEFORE the +// original parser runs — purely returning default(AvatarItem) on +// throw leaves the watch's callers with a struct whose +// AvatarItemData / AvatarItemVisualData are null, which they then +// dereference (NullReferenceException → caught by the finalizer → +// caller dereferences the still-default struct → IL2CPP runtime +// segfault, no managed log). +// +// Strategy: check the GUID prefix against +// AvatarItemWardrobeRuntimeConfig.Config.avatarItemDataLookup +// (loaded lazily from the client's own runtime config). If the GUID +// isn't in the dict, rewrite avatarItemDesc / itemGuid to point +// at FALLBACK_GUID — a confirmed-safe AvatarItem (Hat_Angler) +// that always renders. The original method then runs unchanged on +// the substituted GUID and returns a fully-populated AvatarItem. +// Callers see a real AvatarItem, render the fallback prefab, and +// continue without segfaulting. +internal static class AvatarItemSafelist +{ + // Hat_Angler — first entry in StarterOutfitSelections, slot 0 + // (Head). Its AvatarItem is in avatarItemDataLookup AND its + // prefab is in avatarItemPrefabLookup, so any caller can render + // it as a fallback without triggering further misses. + public const string FallbackGuid = "03f8c394-28fa-4087-978b-8d108f0bd969"; + + private static System.Collections.Generic.HashSet? _safe; + private static int _failCount; + + public static bool IsSafe(string guid) + { + if (Plugin.SubstituteUnknownAvatarItems == null || + !Plugin.SubstituteUnknownAvatarItems.Value) + return true; + + if (string.IsNullOrEmpty(guid)) return false; + if (_safe is null) + { + try + { + var config = RecRoom.Avatar.Data.Runtime.AvatarItemWardrobeRuntimeConfig.Config; + if (config == null) + { + if (System.Threading.Interlocked.Increment(ref _failCount) <= 3) + Plugin.Log.LogWarning("[avatar-safelist] runtime config is null — returning false"); + return false; + } + if (config.avatarItemDataLookup == null) + { + if (System.Threading.Interlocked.Increment(ref _failCount) <= 3) + Plugin.Log.LogWarning("[avatar-safelist] avatarItemDataLookup is null — returning false"); + return false; + } + var set = new System.Collections.Generic.HashSet(StringComparer.OrdinalIgnoreCase); + foreach (var k in config.avatarItemDataLookup.Keys) + { + if (!string.IsNullOrEmpty(k)) set.Add(k); + } + _safe = set; + Plugin.Log.LogInfo($"[avatar-safelist] initialized with {set.Count} GUIDs"); + } + catch (Exception ex) + { + if (System.Threading.Interlocked.Increment(ref _failCount) <= 3) + Plugin.Log.LogWarning($"[avatar-safelist] init failed: {ex.GetType().Name} {ex.Message}"); + return false; + } + } + return _safe.Contains(guid); + } +} + +[HarmonyPatch(typeof(RecRoom.Avatar.Data.Runtime.AvatarItem), + nameof(RecRoom.Avatar.Data.Runtime.AvatarItem.FromRecNetString), + new[] { typeof(string) })] +internal static class AvatarItemParseSubstitute +{ + private static int _subCount; + // Per-GUID first-time log dedup so the log shows which UNIQUE + // unknown GUIDs are being hit (cap at 50 different guids; after + // that we suppress to keep the log readable). Without this the + // first 5 hits of one repeating guid burn the global cap and + // hide every other guid from the log — which made it impossible + // to tell whether wardrobe store tiles were being substituted. + private static readonly System.Collections.Concurrent.ConcurrentDictionary _seenGuids + = new(StringComparer.OrdinalIgnoreCase); + + public static bool Prepare() => + Plugin.SubstituteUnknownAvatarItems != null && Plugin.SubstituteUnknownAvatarItems.Value; + + public static void Prefix(ref string avatarItemDesc) + { + if (string.IsNullOrEmpty(avatarItemDesc)) return; + var comma = avatarItemDesc.IndexOf(','); + var guid = comma < 0 ? avatarItemDesc : avatarItemDesc.Substring(0, comma); + if (AvatarItemSafelist.IsSafe(guid)) return; + var hitN = System.Threading.Interlocked.Increment(ref _subCount); + // Log the FIRST occurrence of each unique unknown guid (up to + // 50 unique guids), plus a periodic heartbeat every 250 hits + // total so we know substitution volume without spam. + var firstSeen = _seenGuids.TryAdd(guid, 0); + if ((firstSeen && _seenGuids.Count <= 50) || hitN % 250 == 0) + Plugin.Log.LogWarning( + $"[avatar-parse] substitute unknown guid '{guid}' → fallback (Hat_Angler) " + + $"[unique #{_seenGuids.Count} / total #{hitN}]"); + // Keep whatever customization tail the caller had so the + // 5-segment AvatarItemSelection format stays well-formed. + var tail = comma < 0 ? ",,," : avatarItemDesc.Substring(comma); + avatarItemDesc = AvatarItemSafelist.FallbackGuid + tail; + } + + // Belt-and-suspenders: if a GUID slips past IsSafe but the + // original still throws (e.g. transient lookup race), return a + // default item. Better a missing prefab than a hard crash. + [HarmonyFinalizer] + public static Exception? Finalizer( + Exception __exception, + string avatarItemDesc, + ref RecRoom.Avatar.Data.Runtime.AvatarItem __result) + { + if (__exception == null) return null; + if (Plugin.SubstituteUnknownAvatarItems == null || + !Plugin.SubstituteUnknownAvatarItems.Value) + return __exception; + + Plugin.Log.LogWarning( + $"[avatar-parse] finalizer swallowed {__exception.GetType().Name}: " + + $"{__exception.Message} — desc='{avatarItemDesc}'"); + __result = default!; + return null; + } +} + +[HarmonyPatch(typeof(RecRoom.Avatar.Data.Runtime.AvatarItem), + nameof(RecRoom.Avatar.Data.Runtime.AvatarItem.FromRecNetString), + new[] { typeof(string), typeof(Il2CppSystem.ArraySegment) })] +internal static class AvatarItemParseSubstituteTwoArg +{ + private static int _subCount; + private static readonly System.Collections.Concurrent.ConcurrentDictionary _seenGuids + = new(StringComparer.OrdinalIgnoreCase); + + public static bool Prepare() => + Plugin.SubstituteUnknownAvatarItems != null && Plugin.SubstituteUnknownAvatarItems.Value; + + public static void Prefix(ref string itemGuid) + { + if (string.IsNullOrEmpty(itemGuid)) return; + if (AvatarItemSafelist.IsSafe(itemGuid)) return; + var hitN = System.Threading.Interlocked.Increment(ref _subCount); + var firstSeen = _seenGuids.TryAdd(itemGuid, 0); + if ((firstSeen && _seenGuids.Count <= 50) || hitN % 250 == 0) + Plugin.Log.LogWarning( + $"[avatar-parse] (2-arg) substitute unknown guid '{itemGuid}' → fallback (Hat_Angler) " + + $"[unique #{_seenGuids.Count} / total #{hitN}]"); + itemGuid = AvatarItemSafelist.FallbackGuid; + } + + [HarmonyFinalizer] + public static Exception? Finalizer( + Exception __exception, + string itemGuid, + ref RecRoom.Avatar.Data.Runtime.AvatarItem __result) + { + if (__exception == null) return null; + if (Plugin.SubstituteUnknownAvatarItems == null || + !Plugin.SubstituteUnknownAvatarItems.Value) + return __exception; + + Plugin.Log.LogWarning( + $"[avatar-parse] finalizer swallowed {__exception.GetType().Name}: " + + $"{__exception.Message} — guid='{itemGuid}'"); + __result = default!; + return null; + } +} + +internal static class GlobalManagedCatchAll +{ + private static int _installed; + private static int _logCount; + + public static bool Enabled => + Plugin.CatchAllManagedExceptions != null && Plugin.CatchAllManagedExceptions.Value; + + public static void Install() + { + if (!Enabled) return; + if (System.Threading.Interlocked.Exchange(ref _installed, 1) == 1) return; + + AppDomain.CurrentDomain.UnhandledException += (_, args) => + { + Log("appdomain", args.ExceptionObject as Exception); + }; + + System.Threading.Tasks.TaskScheduler.UnobservedTaskException += (_, args) => + { + Log("task", args.Exception); + args.SetObserved(); + }; + + } + + public static Exception? Swallow(Exception? exception, string source) + { + if (exception == null) return null; + if (!Enabled) return exception; + Log(source, exception); + return null; + } + + private static void Log(string source, Exception? ex) + { + if (System.Threading.Interlocked.Increment(ref _logCount) > 80) return; + if (ex == null) + Plugin.Log.LogWarning($"[catch-all:{source}] swallowed unknown managed exception"); + else + Plugin.Log.LogWarning($"[catch-all:{source}] swallowed {ex.GetType().Name}: {ex.Message}\n{ex.StackTrace}"); + } + +} + +internal static class UnityDetailedLogBridge +{ + private static int _installed; + + public static bool Enabled => + Plugin.DetailedUnityLogs != null && Plugin.DetailedUnityLogs.Value; + + public static void Install() + { + if (!Enabled) return; + if (System.Threading.Interlocked.Exchange(ref _installed, 1) == 1) return; + + try + { + Application.SetStackTraceLogType(LogType.Log, StackTraceLogType.ScriptOnly); + Application.SetStackTraceLogType(LogType.Warning, StackTraceLogType.Full); + Application.SetStackTraceLogType(LogType.Assert, StackTraceLogType.Full); + Application.SetStackTraceLogType(LogType.Error, StackTraceLogType.Full); + Application.SetStackTraceLogType(LogType.Exception, StackTraceLogType.Full); + Plugin.Log.LogInfo("[unity-logs] enabled full stack traces for warnings/errors/exceptions"); + } + catch (Exception ex) + { + Plugin.Log.LogWarning($"[unity-logs] install failed: {ex.GetType().Name} {ex.Message}"); + } + } +} + +[HarmonyPatch(typeof(RRUI.Data.Components.DynamicAvatarItemImposter), "OnAvatarItemDescChange")] +internal static class DynamicAvatarItemImposterCatchAll +{ + public static bool Prepare() => GlobalManagedCatchAll.Enabled; + + [HarmonyFinalizer] + public static Exception? Finalizer(Exception __exception) + { + return GlobalManagedCatchAll.Swallow(__exception, "avatar-imposter"); + } +} + +[HarmonyPatch] +internal static class SchedulerManagedCatchAll +{ + public static bool Prepare() => GlobalManagedCatchAll.Enabled; + + public static System.Collections.Generic.IEnumerable TargetMethods() + { + var scheduler = AccessTools.TypeByName("RecRoom.Core.Scheduler"); + if (scheduler == null) yield break; + + foreach (var name in new[] + { + "Update", + "UpdateQueue", + "LateUpdate", + "FixedUpdate", + "OnPostUpdate", + "OnPreRenderUpdate", + "OnRigidbodyExLateUpdate" + }) + { + var method = AccessTools.Method(scheduler, name); + if (method != null) yield return method; + } + } + + [HarmonyFinalizer] + public static Exception? Finalizer(Exception __exception, MethodBase __originalMethod) + { + return GlobalManagedCatchAll.Swallow(__exception, __originalMethod.Name); + } +} + +// ── 9. Storefront objective guard — older DorkNet deployments returned +// objective-progress JSON from api/storefronts/v1/objectives, but the +// March 2020 client deserializes that POST as +// Storefronts.BalanceUpdateResponseDTO. The mismatch throws +// KeyNotFoundException inside RecNet.Storefronts and tears down the +// player shortly after entering RecCenter. The server endpoint is fixed +// in this repo, but this guard keeps clients alive against stale servers. +[HarmonyPatch(typeof(RecNet.Storefronts), nameof(RecNet.Storefronts.CompleteObjectives))] +internal static class StorefrontCompleteObjectivesGuard +{ + public static bool Prepare() => + Plugin.SkipStorefrontObjectivePosts != null && Plugin.SkipStorefrontObjectivePosts.Value; + + public static bool Prefix() + { + if (Plugin.SkipStorefrontObjectivePosts == null || + !Plugin.SkipStorefrontObjectivePosts.Value) + return true; + + Plugin.Log.LogInfo("[storefront-objectives] skipped client POST; server-side reward sync is disabled"); + return false; + } +} + +// ── 9b. Targeted avatar/store diagnostics — the Unity output log often +// shows only the RRUI binding property that failed, not the RecNet DTO +// that populated it. These patches log the exact gift/drop/unlocked item +// descriptors the client accepted. Avoid patching tiny RRUI property +// getters here; HarmonyX wrappers around IL2CPP getters can fault before +// managed finalizers run. +internal static class AvatarStoreDiagnostics +{ + private const int MaxDtoLogs = 120; + private static int _dtoLogs; + + public static bool Enabled => + Plugin.DetailedAvatarStoreLogs != null && Plugin.DetailedAvatarStoreLogs.Value; + + public static void LogStorefrontGiftDrop(RecNet.StorefrontGiftDrop drop, string source) + { + if (!Enabled || drop == null || !ShouldLogDto()) return; + Plugin.Log.LogWarning( + $"[diag:{source}] StorefrontGiftDrop id={Safe(() => drop.GiftDropId)} " + + $"type={Safe(() => drop.AvatarItemType)} content={Safe(() => drop.Content)} " + + $"rarity={Safe(() => drop.Rarity)} name='{Safe(() => drop.FriendlyName)}' " + + $"desc='{Safe(() => drop.AvatarItemDescOrHairDyeDesc)}' " + + $"consumable='{Safe(() => drop.ConsumableItemDesc)}' " + + $"equipment='{Safe(() => drop.EquipmentPrefabName)}' mod='{Safe(() => drop.EquipmentModificationGuid)}'"); + } + + public static void LogUnlockedAvatarItem(RecNet.Avatars.UnlockedAvatarItem item, string source) + { + if (!Enabled || item == null || !ShouldLogDto()) return; + Plugin.Log.LogWarning( + $"[diag:{source}] UnlockedAvatarItem type={Safe(() => item.AvatarItemType)} " + + $"platform={Safe(() => item.PlatformMask)} rarity={Safe(() => item.Rarity)} " + + $"name='{Safe(() => item.FriendlyName)}' desc='{Safe(() => item.AvatarItemDesc)}'"); + } + + public static void LogGiftPackage(RecNet.Avatars.GiftPackage gift, string source) + { + if (!Enabled || gift == null || !ShouldLogDto()) return; + Plugin.Log.LogWarning( + $"[diag:{source}] GiftPackage id={Safe(() => gift.Id)} valid={Safe(() => gift.IsValid)} " + + $"consumed={Safe(() => gift.Consumed)} platformOk={Safe(() => gift.SupportsCurrentPlatform)} " + + $"type={Safe(() => gift.AvatarItemType)} context={Safe(() => gift.GiftContext)} " + + $"rarity={Safe(() => gift.GiftRarity)} error='{Safe(() => gift.ErrorMessage)}' " + + $"desc='{Safe(() => gift.AvatarItemDescOrHairDyeDesc)}' " + + $"consumable='{Safe(() => gift.ConsumableItemDesc)}' " + + $"equipment='{Safe(() => gift.EquipmentPrefabName)}' mod='{Safe(() => gift.EquipmentModificationGuid)}'"); + } + + private static bool ShouldLogDto() => + System.Threading.Interlocked.Increment(ref _dtoLogs) <= MaxDtoLogs; + + public static string Safe(Func read) + { + try { return read()?.ToString() ?? ""; } + catch (Exception ex) { return $""; } + } + + public static string DescribeObject(object? value) + { + if (value == null) return ""; + try + { + var text = value.ToString(); + return string.IsNullOrWhiteSpace(text) + ? value.GetType().FullName ?? value.GetType().Name + : text; + } + catch (Exception ex) + { + return $""; + } + } + + public static string DescribeBalance(object? value) + { + if (value == null) return ""; + return "CurrencyType=" + Safe(() => ReadMember(value, "CurrencyType")) + + " BalanceType=" + Safe(() => ReadMember(value, "BalanceType")) + + " Balance=" + Safe(() => ReadMember(value, "Balance")); + } + + public static string DescribeAvatarObject(object? value) + { + if (value == null) return ""; + var recNet = Safe(() => InvokeNoArg(value, "ToRecNetString")); + if (!recNet.StartsWith(""; + } + + private static object? InvokeNoArg(object value, string name) + { + var method = value.GetType().GetMethod(name, BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic, null, Type.EmptyTypes, null); + return method == null ? "" : method.Invoke(value, null); + } +} + +[HarmonyPatch(typeof(RecNet.StorefrontGiftDrop), nameof(RecNet.StorefrontGiftDrop.Deserialize))] +internal static class StorefrontGiftDropDiagnostics +{ + public static bool Prepare() => AvatarStoreDiagnostics.Enabled; + + public static void Postfix(RecNet.StorefrontGiftDrop __instance) + { + AvatarStoreDiagnostics.LogStorefrontGiftDrop(__instance, "deserialize"); + } +} + +[HarmonyPatch(typeof(RecNet.Avatars.UnlockedAvatarItem), nameof(RecNet.Avatars.UnlockedAvatarItem.Deserialize))] +internal static class UnlockedAvatarItemDiagnostics +{ + public static bool Prepare() => AvatarStoreDiagnostics.Enabled; + + public static void Postfix(RecNet.Avatars.UnlockedAvatarItem __instance) + { + AvatarStoreDiagnostics.LogUnlockedAvatarItem(__instance, "deserialize"); + } +} + +[HarmonyPatch(typeof(RecNet.Avatars.GiftPackage), nameof(RecNet.Avatars.GiftPackage.Deserialize))] +internal static class GiftPackageDiagnostics +{ + public static bool Prepare() => AvatarStoreDiagnostics.Enabled; + + public static void Postfix(RecNet.Avatars.GiftPackage __instance) + { + AvatarStoreDiagnostics.LogGiftPackage(__instance, "deserialize"); + } +} + +[HarmonyPatch] +internal static class StorefrontBalanceDiagnostics +{ + private static int _logCount; + + public static bool Prepare() => AvatarStoreDiagnostics.Enabled; + + public static System.Collections.Generic.IEnumerable TargetMethods() + { + foreach (var method in AccessTools.GetDeclaredMethods(typeof(RecNet.Storefronts))) + { + if (method.Name is "UpdateCachedBalance" or "UpdateCachedBalances" or + "RaiseBalanceUpdatedEvent" or "OnStorefrontBalanceUpdated" or + "OnStorefrontBalancePurchased" or "OnStorefrontBalanceAddReceived") + yield return method; + } + } + + public static void Prefix(MethodBase __originalMethod, object[] __args) + { + if (!AvatarStoreDiagnostics.Enabled || System.Threading.Interlocked.Increment(ref _logCount) > 160) + return; + + try + { + Plugin.Log.LogWarning($"[diag:storefront:{__originalMethod.Name}] args={DescribeArgs(__args)}"); + } + catch (Exception ex) + { + Plugin.Log.LogWarning($"[diag:storefront:{__originalMethod.Name}] log failed: {ex.GetType().Name} {ex.Message}"); + } + } + + [HarmonyFinalizer] + public static Exception? Finalizer(Exception __exception, MethodBase __originalMethod, object[] __args) + { + if (__exception == null) return null; + Plugin.Log.LogWarning( + $"[diag:storefront:{__originalMethod.Name}] exception {__exception.GetType().Name}: {__exception.Message} " + + $"args={DescribeArgs(__args)}\n{__exception.StackTrace}"); + return GlobalManagedCatchAll.Swallow(__exception, "storefront:" + __originalMethod.Name); + } + + private static string DescribeArgs(object[]? args) + { + if (args == null || args.Length == 0) return ""; + var parts = new List(); + for (var i = 0; i < args.Length; i++) + parts.Add($"#{i}={DescribeArg(args[i])}"); + return string.Join("; ", parts); + } + + private static string DescribeArg(object? arg) + { + if (arg == null) return ""; + if (arg is System.Collections.IEnumerable seq && arg is not string) + { + var items = new List(); + var count = 0; + foreach (var item in seq) + { + if (++count <= 8) items.Add(AvatarStoreDiagnostics.DescribeBalance(item)); + } + return $"[{string.Join(" | ", items)}] count>={count}"; + } + + var typeName = arg.GetType().FullName ?? arg.GetType().Name; + if (typeName.Contains("Balance", StringComparison.OrdinalIgnoreCase)) + return AvatarStoreDiagnostics.DescribeBalance(arg); + return AvatarStoreDiagnostics.DescribeObject(arg); + } +} + +[HarmonyPatch] +internal static class PlayerAvatarOutfitDiagnostics +{ + private static int _logCount; + + public static bool Prepare() => AvatarStoreDiagnostics.Enabled; + + public static System.Collections.Generic.IEnumerable TargetMethods() + { + foreach (var method in AccessTools.GetDeclaredMethods(typeof(PlayerAvatar))) + { + if (method.Name is "ApplyLocalPlayerAvatarSettings" or "EquipOutfitItem" or + "EquipOutfitItemInternal" or "SetLocalPlayerSelectedOutfit" or + "LocalLoadSavedOutfit" or "UnequipOutfitItem" or "OnPlayerOutfitSelectionsChanged") + yield return method; + } + } + + public static void Prefix(MethodBase __originalMethod, object[] __args) + { + if (!AvatarStoreDiagnostics.Enabled || System.Threading.Interlocked.Increment(ref _logCount) > 140) + return; + + try + { + Plugin.Log.LogWarning($"[diag:outfit:{__originalMethod.Name}] args={DescribeArgs(__args)}"); + } + catch (Exception ex) + { + Plugin.Log.LogWarning($"[diag:outfit:{__originalMethod.Name}] log failed: {ex.GetType().Name} {ex.Message}"); + } + } + + [HarmonyFinalizer] + public static Exception? Finalizer(Exception __exception, MethodBase __originalMethod, object[] __args) + { + if (__exception == null) return null; + Plugin.Log.LogWarning( + $"[diag:outfit:{__originalMethod.Name}] exception {__exception.GetType().Name}: {__exception.Message} " + + $"args={DescribeArgs(__args)}\n{__exception.StackTrace}"); + return GlobalManagedCatchAll.Swallow(__exception, "outfit:" + __originalMethod.Name); + } + + private static string DescribeArgs(object[]? args) + { + if (args == null || args.Length == 0) return ""; + var parts = new List(); + for (var i = 0; i < args.Length; i++) + parts.Add($"#{i}={DescribeArg(args[i])}"); + return string.Join("; ", parts); + } + + private static string DescribeArg(object? arg) + { + if (arg == null) return ""; + if (arg is string s) return s; + if (arg is System.Collections.IEnumerable seq) + { + var items = new List(); + var count = 0; + foreach (var item in seq) + { + if (++count <= 8) items.Add(AvatarStoreDiagnostics.DescribeAvatarObject(item)); + } + return $"[{string.Join(" | ", items)}] count>={count}"; + } + + var typeName = arg.GetType().FullName ?? arg.GetType().Name; + if (typeName.Contains("Avatar", StringComparison.OrdinalIgnoreCase) || + typeName.Contains("Outfit", StringComparison.OrdinalIgnoreCase) || + typeName.Contains("BodyPart", StringComparison.OrdinalIgnoreCase)) + return AvatarStoreDiagnostics.DescribeAvatarObject(arg); + return AvatarStoreDiagnostics.DescribeObject(arg); + } +} + +// ── 10. Mirror outfit guard -- the March 2020 mirror UI can try to remove +// the required torso item when clicking the currently equipped shirt. The +// client then force-adds a default torso ("Adding required torso item!") and +// the local player teardown follows shortly after. Treat a direct torso +// unequip as a no-op; normal shirt swaps still go through EquipOutfitItem. +[HarmonyPatch(typeof(PlayerAvatar), nameof(PlayerAvatar.UnequipOutfitItem))] +internal static class RequiredTorsoUnequipGuard +{ + private const int TorsoBodyPart = 1; + + public static bool Prepare() => + Plugin.BlockDirectTorsoUnequip != null && Plugin.BlockDirectTorsoUnequip.Value; + + public static bool Prefix(object[] __args, ref bool __result) + { + if (Plugin.BlockDirectTorsoUnequip == null || + !Plugin.BlockDirectTorsoUnequip.Value) + return true; + + if (TryReadBodyPart(__args, out var bodyPart) && bodyPart == TorsoBodyPart) + { + Plugin.Log.LogInfo("[avatar-outfit] blocked direct torso unequip from mirror UI"); + __result = false; + return false; + } + + return true; + } + + private static bool TryReadBodyPart(object[]? args, out int bodyPart) + { + bodyPart = int.MinValue; + if (args is not { Length: > 1 } || args[1] is null) return false; + + try + { + bodyPart = Convert.ToInt32(args[1]); + return true; + } + catch (Exception ex) when (ex is InvalidCastException or FormatException or OverflowException) + { + var text = args[1].ToString(); + if (text?.EndsWith("Torso", StringComparison.OrdinalIgnoreCase) == true) + { + bodyPart = TorsoBodyPart; + return true; + } + + if (int.TryParse(text, out bodyPart)) + return true; + + Plugin.Log.LogDebug($"[avatar-outfit] could not decode bodyPart argument: {text}"); + return false; + } + } +} + +// ── 11. Screen cleanup guard — the March 2020 desktop HUD can throw a +// NullReferenceException while tearing down the local player if the +// ScreenHUD/cursor objects are already gone. That exception appears in the +// current crash path after the local player is destroyed. Suppress only this +// cleanup exception so we can keep the process alive long enough to expose +// the real disconnect/crash trigger in the logs. +[HarmonyPatch(typeof(RecRoom.ScreenPlayerController), "CleanupLocalPlayer")] +internal static class ScreenPlayerCleanupGuard +{ + public static bool Prepare() => + Plugin.SuppressScreenCleanupNullRef != null && Plugin.SuppressScreenCleanupNullRef.Value; + + public static Exception? Finalizer(Exception __exception) + { + if (__exception == null) return null; + if (Plugin.SuppressScreenCleanupNullRef == null || + !Plugin.SuppressScreenCleanupNullRef.Value) + return __exception; + + if (IsCleanupHudNullReference(__exception)) + { + Plugin.Log.LogWarning("[screen-cleanup] swallowed CleanupLocalPlayer null-ref during local player teardown"); + return null; + } + return __exception; + } + + private static bool IsCleanupHudNullReference(Exception exception) + { + if (exception is NullReferenceException) return true; + + // IL2CPP exceptions arrive through Harmony as Il2CppException wrappers, + // so match the IL2CPP stack text rather than only the managed type. + var text = exception.ToString(); + return text.Contains("System.NullReferenceException", StringComparison.Ordinal) + && text.Contains("RecRoom.ScreenPlayerController.CleanupLocalPlayer", StringComparison.Ordinal) + && text.Contains("RecRoom.UI.ScreenHUDElement.get_RectTransform", StringComparison.Ordinal); + } +} + +// ── 12. Diagnostics — log every BootSequence.RegisterError invocation +// with a managed-side stack trace so we can tell which API call +// actually failed (the IL2CPP stack traces in Player.log are +// obfuscated to method symbols like "RKHMMYKOXZK"). The watch's +// boot pipeline collapses any failure into "Rec Room update +// required", which is useless without knowing the actual cause. +[HarmonyPatch(typeof(BootSequence), "RegisterError")] +internal static class BootSequenceRegisterErrorTrace +{ + public static bool Prepare() => + Plugin.EnableBootDiagnostics != null && Plugin.EnableBootDiagnostics.Value; + + // The original Prefix(string error) was emitting blank text — Harmony's + // IL2CPP string marshaling for an instance-method first parameter was + // dropping the string value (every entry came out as "[boot-error] " in + // the log). Use HarmonyX's __args to pull the raw arguments and decode + // them manually, plus dump the actual call site so we know which of + // the two known callers + // - "Error initializing Core Systems" (BootSequence:5555) + // - "Unable to connect to RecNet game servers" (BootSequence:10490) + // fired. + public static void Prefix(object[] __args, BootSequence __instance) + { + string? err = null; + try + { + if (__args is { Length: > 0 } && __args[0] is { } a0) + { + err = a0 switch + { + string s => s, + Il2CppSystem.String i2cs => i2cs.ToString(), + _ => a0.ToString(), + }; + } + } + catch (Exception ex) { err = $""; } + Plugin.Log.LogWarning($"[boot-error] '{err ?? ""}'"); + try + { + Plugin.Log.LogWarning(new System.Diagnostics.StackTrace(1, true).ToString()); + } + catch { /* StackTrace can fail in IL2CPP; swallow */ } + } +} + +internal static class MatchmakingHelpers +{ + /// Reads the watch's persistent LoginLock from PlayerPrefs. + /// The key is LoginLockTokenV2 (verified at + /// Cpp2IL_ISIL/.../RecNet/Matchmaking.txt:1810 — the + /// Move rdi, "LoginLockTokenV2" + Call PlayerPrefs.GetString + /// pair inside Matchmaking.LoadLockToken). Stored value is a + /// JSON object of RecNet.Matchmaking+LoginLockTokenParams + /// (Cpp2IL_CS/.../RecNet/Matchmaking.cs:129) with three fields: + /// Platform (int), PlatformId (string), LockToken + /// (string). We extract LockToken by scanning for the literal + /// "LockToken":"…" pair — cheaper than parsing JSON for one + /// string, and the watch's serialiser doesn't produce escapes that'd + /// break a naive scan. + /// + public static string? GetCurrentLockToken() + { + try + { + var raw = PlayerPrefs.GetString("LoginLockTokenV2", string.Empty); + if (string.IsNullOrEmpty(raw)) return null; + const string key = "\"LockToken\":\""; + var ix = raw.IndexOf(key, StringComparison.Ordinal); + if (ix < 0) return null; + ix += key.Length; + var end = raw.IndexOf('"', ix); + return end < 0 ? null : raw[ix..end]; + } + catch { return null; } + } +} diff --git a/DorkNet.ClientPatch/README.md b/DorkNet.ClientPatch/README.md new file mode 100644 index 0000000..e365d1b --- /dev/null +++ b/DorkNet.ClientPatch/README.md @@ -0,0 +1,217 @@ +# DorkNet ClientPatch BepInEx Plugin + +Single client-side artefact that does **everything** DorkNet needs to +patch into the 2020 watch: + +1. Rewrites every `*.rec.net` URL the watch builds → user-configured + replacement host (default `localhost`). Catches the bootstrap + `https://ns.rec.net/?v=2`; every other service URL flows in from + the nameserver response which the DorkNet server controls. No + more hosts file, no more wildcard mkcert (Cloudflare Tunnel terminates + real TLS at the edge of the configured host). +2. Overrides Photon Cloud `AppId` / `VoiceAppId` right before + `PhotonNetwork.ConnectUsingSettings`. No more byte-edits to + `resources.assets`, no more hardcoded offsets to chase across game + versions. +3. Attaches `userid` + `LoginLock` to `PhotonNetwork.AuthValues` right + before any authenticate op leaves the wire, so DorkNet's + `/photon/customauth` endpoint can identify the player and enforce + single-session. + +Configuration lives in `BepInEx/config/sh.dork.clientpatch.cfg`. Edit +values, restart the client, patches re-apply with the new settings. + +## Quickstart (recommended) + +Use the wrapper script — handles BepInEx install, interop generation, +plugin build, and config drop in one go: + +```pwsh +.\tools\install-plugin.ps1 ` + -RecRoomPath "C:\Program Files (x86)\Steam\steamapps\common\RecRoom\Recroom_Release_Data" ` + -PhotonAppId +``` + +The script auto-downloads a pinned BepInEx 6 IL2CPP build from +`builds.bepinex.dev` on first run and caches it at +`%LOCALAPPDATA%\DorkNet\BepInEx-Unity.IL2CPP-win-x64-6.0.0-be.696.zip` +so re-runs on the same machine don't re-download. To override the +pinned build (newer/older), pass `-BepInExZip `. + +After a client update, just re-run with the same args. The script +re-builds the plugin against the new game's interop assemblies. + +To remove: `.\tools\install-plugin.ps1 -RecRoomPath ... -Revert`. + +The legacy `patch-client.ps1` (asset-byte-edit + hosts file + mkcert +wildcard) is kept for backwards compat — see the file's header for +when you'd still want it. + +## Manual install (if you don't want to use install-plugin.ps1) + +Same steps, just done by hand. Useful for understanding the layout. + +## Why this exists + +The vanilla 2020 client never sets `PhotonNetwork.AuthValues` — verified +by an exhaustive grep across both `Assembly-CSharp.dll` and the Photon +`Assembly-CSharp-firstpass.dll` (no callers of +`PhotonNetwork.AuthValues =`, `new AuthenticationValues(...)`, +`AddAuthParameter(...)`, or `SetAuthPostData(...)`). Photon Cloud +therefore assigns a random per-connection UserId and our +`/photon/customauth` callback receives no client identity, so we can't +reject duplicate sessions. + +This plugin patches `NetworkingPeer.OpAuthenticate` / +`OpAuthenticateOnce` (the single chokepoint right before Photon's +authenticate op leaves the wire) and attaches: + +- `AuthType = CustomAuthenticationType.Custom` +- `UserId = ` +- `AuthGetParameters: userid=, LoginLock=` + +That makes the Photon Cloud `{userid}` / `{LoginLock}` placeholders +populate, our endpoint validates `LoginLock` against +`PlayerPresenceService` (Redis), and Photon's own UserId-collision +detection kicks duplicate connections automatically. + +## Prerequisites + +- 2020 client build (Steam build `20200306`, AppID `471710`). +- BepInEx **6.x IL2CPP** — *not* the regular Mono build. Download from + https://github.com/BepInEx/BepInEx/releases (look for the + `BepInEx_unix_x86_64_6.0.0-be.696_*.zip` line, Windows variant). +- .NET 6 SDK on the build machine (this project uses `net6.0`). + +## One-time Setup + +1. **Install BepInEx 6 IL2CPP into the client directory.** Extract the + release zip into the folder containing `Recroom_Release.exe`. After + extraction the layout looks like: + + ``` + / + Recroom_Release.exe + BepInEx/ + core/ + config/ + plugins/ ← we drop our DLL here + dotnet/ ← BepInEx-shipped runtime + winhttp.dll ← BepInEx loader + doorstop_config.ini + ``` + +2. **Generate Il2Cpp interop assemblies.** Run the game once. BepInEx + inspects the IL2CPP metadata on first launch and writes interop + assemblies to `BepInEx/interop/`. This is a one-time ~30s step; + re-do it any time the game updates. + + You should see lines like + `[Info: BepInEx] Generating Il2Cpp interop assemblies` in + `BepInEx/LogOutput.log`. + +3. **Quit the game.** The interop assemblies are now sitting in + `BepInEx/interop/Il2CppAssembly-CSharp.dll` etc. + +## Building the plugin + +The csproj references BepInEx core assemblies via `BepInExCore` and generated +interop assemblies via `BepInExInterop`. Build from the repo root: + +```pwsh +$env:BepInExCore = "C:\Program Files (x86)\Steam\steamapps\common\RecRoom\BepInEx\core" +$env:BepInExInterop = "C:\Program Files (x86)\Steam\steamapps\common\RecRoom\BepInEx\interop" +dotnet build DorkNet.ClientPatch -c Release +``` + +Output lands at: + +``` +DorkNet.ClientPatch/bin/Release/DorkNet.ClientPatch.dll +``` + +## Installing the plugin + +Copy the built DLL into the game's `BepInEx/plugins/` folder: + +```pwsh +Copy-Item ` + DorkNet.ClientPatch/bin/Release/DorkNet.ClientPatch.dll ` + "$env:BepInExInterop\..\plugins\" +``` + +Or use the patcher (next section). + +## Integration with `patch-client.ps1` + +The patcher tool grew an `-InstallBepInEx` switch. Pass the path to a +freshly-extracted BepInEx 6 IL2CPP zip and it'll: + +1. Extract BepInEx into the client folder if not already present. +2. Build this plugin against the existing `BepInEx/interop` directory. +3. Copy the DLL into `BepInEx/plugins/`. +4. Append a `Photon CustomAuth integration` step to the patch summary. + +```pwsh +.\tools\patch-client.ps1 ` + -RecRoomPath "C:\…\RecRoom\Recroom_Release_Data" ` + -PhotonAppId cb0880d9-… ` + -InstallBepInEx "C:\Downloads\BepInEx_unix_x86_64_6.0.0-be.696_…zip" +``` + +If the interop folder doesn't exist yet (game never launched +post-BepInEx-install), the patcher will tell you to launch the game +once and re-run with `-InstallBepInEx` to finish the build step. + +## Verifying the plugin loaded + +Launch the client. Tail `BepInEx/LogOutput.log` and look for: + +``` +[Info : BepInEx] Loading [DorkNet ClientPatch 1.0.0] +[Info :DorkNet ClientPatch] DorkNet ClientPatch v1.0.0 loading… +[Info :DorkNet ClientPatch] Photon AuthValues injection patches applied. +``` + +After login + the first Photon connect, also expect: + +``` +[Info :DorkNet ClientPatch] [auth-injector] set Photon AuthValues — userid=1811750, LoginLock= +``` + +On the server side, the `[photon-auth] received query={...}` log line +should now contain `userid=1811750&LoginLock=`. + +## Server-side: flip strict validation back on + +Once the plugin is shipping LoginLock in production: + +1. Restore the strict-mode body of + [`PhotonCustomAuthController`](../DorkNet.Server/Controllers/Auth/PhotonCustomAuthController.cs) + — accept the parameters, look up the canonical lock from + `PlayerPresenceService.ValidateLock(playerId, loginLock)`, and + return `ResultCode 3` on mismatch. +2. In the Photon dashboard, flip **"Reject if Auth Failed"** ON. +3. Now a duplicate-account login on a *different* machine fails Photon + auth → never reaches the dorm. + +## Troubleshooting + +**`Could not load file or assembly 'Il2CppAssembly-CSharp'`** +The interop folder isn't where the csproj expects. Set the +`BepInExInterop` env var to your install's interop path before `dotnet +build`. + +**`No interop assemblies found, Il2CppType.Of<…>().GetField fails`** +Game never launched post-BepInEx-install. Launch once, quit, re-build. + +**Plugin loads but `[auth-injector] skip — no LocalAccountId yet` on every connect** +The Photon connect happens before login completes. Inspect the log +order — if RecNet auth genuinely hasn't run yet, the plugin can't help +on that particular connect; it'll succeed on the next one (post-login +Photon reconnect when joining a room). + +**Game crashes on launch with BepInEx but works without** +Either the plugin threw on Load (check `LogOutput.log` for stack +trace) or the BepInEx version mismatches — confirm you grabbed the +IL2CPP, *not* Mono, build for Windows x86_64. diff --git a/DorkNet.Models/Auth/CachedLogin.cs b/DorkNet.Models/Auth/CachedLogin.cs new file mode 100644 index 0000000..b7d8f05 --- /dev/null +++ b/DorkNet.Models/Auth/CachedLogin.cs @@ -0,0 +1,36 @@ +using System.Text.Json.Serialization; + +namespace DorkNet.Models.Auth; + +/// +/// Login.CachedLogin — dump.cs:578388, Deserialize at RVA 0x1447BD0. +/// +/// Verified by disassembly: reads platform, platformId, accountId, +/// lastLoginTime (DateTime), requirePassword (default false). The Account +/// field on the C# class is NOT read from the JSON dict — it is set via +/// SetAccount(Account) after the fact, so the JSON shape doesn't need it. +/// +/// Returned by: +/// GET auth.rec.net/cachedlogin/forplatformid/{platform}/{platformId} +/// GET api.rec.net/api/platformlogin/cached +/// GET accounts.rec.net/account/v1/savedlogins +/// as a List<CachedLogin>. Empty list = no remembered accounts. +/// +public class CachedLogin +{ + // PlatformManager.PlatformType (int) + [JsonPropertyName("platform")] + public int Platform { get; set; } + + [JsonPropertyName("platformId")] + public string PlatformId { get; set; } = string.Empty; + + [JsonPropertyName("accountId")] + public int AccountId { get; set; } + + [JsonPropertyName("lastLoginTime")] + public DateTime LastLoginTime { get; set; } = DateTime.UtcNow; + + [JsonPropertyName("requirePassword")] + public bool RequirePassword { get; set; } = false; +} diff --git a/DorkNet.Models/Auth/LoginRequest.cs b/DorkNet.Models/Auth/LoginRequest.cs new file mode 100644 index 0000000..6ae44ee --- /dev/null +++ b/DorkNet.Models/Auth/LoginRequest.cs @@ -0,0 +1,9 @@ +namespace DorkNet.Models.Auth; + +public class LoginRequest +{ + public string? Name { get; set; } + public long BuildTimestamp { get; set; } + public int Platform { get; set; } + public string? DeviceId { get; set; } +} diff --git a/DorkNet.Models/Auth/LoginResponse.cs b/DorkNet.Models/Auth/LoginResponse.cs new file mode 100644 index 0000000..a7d57bc --- /dev/null +++ b/DorkNet.Models/Auth/LoginResponse.cs @@ -0,0 +1,39 @@ +using System.Text.Json.Serialization; + +namespace DorkNet.Models.Auth; + +/// +/// Login.LoginResponse — dump.cs:578473 (private nested class), +/// Deserialize at RVA 0x1447D10. +/// +/// Verified by disassembly: this is a standard OAuth 2.0 RFC 6749 +/// password-grant token response. The Deserialize body has two branches: +/// +/// error = dict["error"]; +/// if (string.IsNullOrEmpty(error)) { +/// this.AccessToken = dict["access_token"]; +/// this.RefreshToken = dict["refresh_token"]; +/// } else { +/// this.ErrorDescription = dict["error_description"]; +/// } +/// +/// Returned by api/platformlogin/v5 (the 2020 build's login endpoint) +/// and api/platformlogin/refresh. +/// +/// All four keys are snake_case, NOT PascalCase. This is the standard +/// OAuth response format — Rec Room conforms to RFC 6749 here. +/// +public class LoginResponse +{ + [JsonPropertyName("error")] + public string? Error { get; set; } + + [JsonPropertyName("error_description")] + public string? ErrorDescription { get; set; } + + [JsonPropertyName("access_token")] + public string AccessToken { get; set; } = string.Empty; + + [JsonPropertyName("refresh_token")] + public string RefreshToken { get; set; } = string.Empty; +} diff --git a/DorkNet.Models/Auth/RecNetResult.cs b/DorkNet.Models/Auth/RecNetResult.cs new file mode 100644 index 0000000..b979a1d --- /dev/null +++ b/DorkNet.Models/Auth/RecNetResult.cs @@ -0,0 +1,129 @@ +using System.Text.Json.Serialization; + +namespace DorkNet.Models.Auth; + +// ===================================================================== +// JSON keys verified by disassembling each .Deserialize method body +// directly from GameAssembly.dll. Each key here is the literal string +// passed to Util.GetKey / Util.GetKeyOrDefault inside the client. Util.GetKey +// uses Dictionary.TryGetValue, which is case-sensitive — so +// the keys MUST match exactly. +// +// Convention summary (it's mixed): +// RecNetResult lowercase "success", "error" +// Account camelCase "accountId", "displayName", "isJunior"… +// SelfAccount camelCase "email", "birthday", "juniorState"… +// LoginResponse snake_case (OAuth) "access_token", "error_description"… +// RefreshLoginResp. lowercase "token" +// CachedLogin camelCase "platformId", "lastLoginTime"… +// CreateAccountResp uses "value" as the wrapper key for the Account dict +// +// NOTE: TreatAsJunior on the C# side maps to JSON key "isJunior" — they +// don't match. Account.Deserialize reads dict["isJunior"], not dict["treatAsJunior"]. +// ===================================================================== + +/// +/// RecNet.RecNetResult — dump.cs:584325, Deserialize at RVA 0x1146340. +/// Reads "success" (required) and "error" (optional). +/// +public class RecNetResult +{ + [JsonPropertyName("success")] + public bool Success { get; set; } = true; + + [JsonPropertyName("error")] + public string? Error { get; set; } +} + +/// +/// RecNet.Account — dump.cs:567684, Deserialize at RVA 0xF9DE50. +/// JSON keys verified in the current 2020 IL2CPP build. Account.Deserialize +/// reads profileImage. +/// Other keys: accountId, username, displayName, isJunior, platforms. The C# Account class also has +/// RawUsername and HasBirthday properties but Deserialize does not read +/// them from JSON (they're set via MakeNameAdhereToPlatformRequirements +/// or other flows). We expose them with reasonable camelCase keys for +/// other endpoints that may consume them. +/// +public class RecNetAccount +{ + private string profileImage = string.Empty; + + [JsonPropertyName("accountId")] + public int AccountId { get; set; } + + [JsonPropertyName("username")] + public string Username { get; set; } = string.Empty; + + [JsonPropertyName("displayName")] + public string DisplayName { get; set; } = string.Empty; + + [JsonPropertyName("profileImage")] + public string ProfileImage + { + get => profileImage; + set => profileImage = value ?? string.Empty; + } + + // Account.Deserialize reads "isJunior" — NOT "treatAsJunior". + // The C# property name and the JSON key name differ. + [JsonPropertyName("isJunior")] + public bool TreatAsJunior { get; set; } = false; + + // PlatformManager.PlatformMask: Steam=1, Oculus=2, etc. + [JsonPropertyName("platforms")] + public int Platforms { get; set; } = 1; + + // Not in Account.Deserialize body but kept for forward-compat. + [JsonPropertyName("rawUsername")] + public string RawUsername { get; set; } = string.Empty; + + // Likewise. + [JsonPropertyName("hasBirthday")] + public bool HasBirthday { get; set; } = true; + + // 2020.12 Account.Deserialize reads this key (verified via Cpp2IL ISIL + // dump of CCEOLAOLEKJ.PPGFHEDFBEA — the obfuscated Account class). The + // 2020.03 client doesn't read it, but emitting it is harmless there. + // Missing → LitJson throws and 2020.12 surfaces "Malformed Response". + [JsonPropertyName("createdAt")] + public DateTime CreatedAt { get; set; } = DateTime.UtcNow; +} + +/// +/// RecNet.SelfAccount : Account — dump.cs:567797, Deserialize at RVA 0xAF4FB0. +/// Calls base Account.Deserialize, then reads: +/// email, phone, birthday, juniorState, parentAccountId. +/// +public class RecNetSelfAccount : RecNetAccount +{ + [JsonPropertyName("email")] + public string Email { get; set; } = string.Empty; + + [JsonPropertyName("phone")] + public string Phone { get; set; } = string.Empty; + + [JsonPropertyName("birthday")] + public DateTime? Birthday { get; set; } = new DateTime(2000, 1, 1); + + // RecNet.JuniorState enum (int) + [JsonPropertyName("juniorState")] + public int JuniorState { get; set; } = 0; + + [JsonPropertyName("parentAccountId")] + public int? ParentAccountId { get; set; } = null; +} + +/// +/// Accounts.CreateAccountResponse : RecNetResult — dump.cs:568167, +/// Deserialize at RVA 0xFAA200. +/// Calls base RecNetResult.Deserialize (reads "success", "error"), +/// then reads the Account from dict["value"] — NOT dict["account"]. +/// Verified by disassembly: the only string literal after the base +/// call is "value", passed to Util.GetObjectKey<object>. +/// +public class CreateAccountResponse : RecNetResult +{ + [JsonPropertyName("value")] + public RecNetAccount Account { get; set; } = new(); +} diff --git a/DorkNet.Models/Auth/RefreshLoginResponse.cs b/DorkNet.Models/Auth/RefreshLoginResponse.cs new file mode 100644 index 0000000..9b70921 --- /dev/null +++ b/DorkNet.Models/Auth/RefreshLoginResponse.cs @@ -0,0 +1,17 @@ +using System.Text.Json.Serialization; + +namespace DorkNet.Models.Auth; + +/// +/// Login.RefreshLoginResponse — dump.cs:578533 (private nested class), +/// Deserialize at RVA 0x145B1E0. +/// +/// Verified by disassembly: reads exactly one key — lowercase "token" — +/// via Util.GetKey<string>. Single-field response from +/// POST api/platformlogin/refresh: the new (rotated) bearer access token. +/// +public class RefreshLoginResponse +{ + [JsonPropertyName("token")] + public string Token { get; set; } = string.Empty; +} diff --git a/DorkNet.Models/Avatar/AvatarData.cs b/DorkNet.Models/Avatar/AvatarData.cs new file mode 100644 index 0000000..dd4a658 --- /dev/null +++ b/DorkNet.Models/Avatar/AvatarData.cs @@ -0,0 +1,24 @@ +using System.Text.Json.Serialization; + +namespace DorkNet.Models.Avatar; + +public class AvatarData +{ + [JsonPropertyName("AccountId")] + public long AccountId { get; set; } + + [JsonPropertyName("AvatarItemInstances")] + public List AvatarItemInstances { get; set; } = []; +} + +public class AvatarItemInstance +{ + [JsonPropertyName("Id")] + public long Id { get; set; } + + [JsonPropertyName("ItemId")] + public long ItemId { get; set; } + + [JsonPropertyName("IsEquipped")] + public bool IsEquipped { get; set; } +} diff --git a/DorkNet.Models/Avatar/AvatarItem.cs b/DorkNet.Models/Avatar/AvatarItem.cs new file mode 100644 index 0000000..764efba --- /dev/null +++ b/DorkNet.Models/Avatar/AvatarItem.cs @@ -0,0 +1,15 @@ +using System.Text.Json.Serialization; + +namespace DorkNet.Models.Avatar; + +public class AvatarItem +{ + [JsonPropertyName("ItemId")] + public long ItemId { get; set; } + + [JsonPropertyName("Name")] + public string Name { get; set; } = string.Empty; + + [JsonPropertyName("Equipped")] + public bool Equipped { get; set; } +} diff --git a/DorkNet.Models/Config/RecRoomConfig.cs b/DorkNet.Models/Config/RecRoomConfig.cs new file mode 100644 index 0000000..4ce9e5e --- /dev/null +++ b/DorkNet.Models/Config/RecRoomConfig.cs @@ -0,0 +1,174 @@ +using System.Text.Json.Serialization; + +namespace DorkNet.Models.Config; + +// Required keys per RecNet.RecRoomConfig.Deserialize disassembly (RVA 0x1146FA0). +// Required-throw keys: ShareBaseUrl, LevelProgressionMaps[].Level, [].RequiredXp. +// Required-object keys (GetObjectKey, accept null): ServerMaintenance, +// AutoMicMutingConfig. +// +// NOTE: the level entry uses "RequiredXp" (lowercase 'p') per the IL string +// literal — NOT "RequiredXP". A capital P here would throw KeyNotFoundException. +public class RecRoomConfig +{ + [JsonPropertyName("MessageOfTheDay")] + public string MessageOfTheDay { get; set; } = "Welcome to the private server!"; + + [JsonPropertyName("CdnBaseUri")] + public string CdnBaseUri { get; set; } = string.Empty; + + // Required by client (string, GetKey). Used for share-link generation. + [JsonPropertyName("ShareBaseUrl")] + public string ShareBaseUrl { get; set; } = "https://rec.net/"; + + // GetObjectKey. Required key. The deserializer + // accepts null without throwing, but the C# property getter on the + // client doesn't null-check before use during boot. Send a real DTO + // with StartsInMinutes far in the future = "no upcoming maintenance". + [JsonPropertyName("ServerMaintenance")] + public ServerMaintenanceDTO? ServerMaintenance { get; set; } = new(); + + // GetObjectKey. Required by the client AND + // dereferenced by AudioManager.Initialize during PostLogin boot — + // null causes: + // NullReferenceException at RecNet.Config.get_AutoMicMutingConfig + // at AudioManager.Initialize + // which kills the boot sequence with "Error initializing Core Systems". + // Must be a real object with all 8 float fields populated. + [JsonPropertyName("AutoMicMutingConfig")] + public AutoMicMutingConfig AutoMicMutingConfig { get; set; } = new(); + + [JsonPropertyName("PhotonConfig")] + public PhotonConfig PhotonConfig { get; set; } = new(); + + [JsonPropertyName("MatchmakingParams")] + public MatchmakingParams MatchmakingParams { get; set; } = new(); + + [JsonPropertyName("LevelProgressionMaps")] + public List LevelProgressionMaps { get; set; } = LevelProgressionEntry.Default(); + + [JsonPropertyName("DailyObjectives")] + public List> DailyObjectives { get; set; } = DailyObjective.DefaultSets(); + + [JsonPropertyName("ConfigTable")] + public Dictionary ConfigTable { get; set; } = []; + + [JsonPropertyName("ServiceUrls")] + public Dictionary ServiceUrls { get; set; } = []; +} + +public class PhotonConfig +{ + [JsonPropertyName("AppId")] + public string AppId { get; set; } = string.Empty; + + [JsonPropertyName("VoiceAppId")] + public string VoiceAppId { get; set; } = string.Empty; + + [JsonPropertyName("CloudRegion")] + public string CloudRegion { get; set; } = "us"; + + [JsonPropertyName("CrcCheckEnabled")] + public bool CrcCheckEnabled { get; set; } = false; + + [JsonPropertyName("EnableServerTracingAfterDisconnect")] + public bool EnableServerTracingAfterDisconnect { get; set; } = false; +} + +public class MatchmakingParams +{ + [JsonPropertyName("MaxPlayersPerRoom")] + public int MaxPlayersPerRoom { get; set; } = 8; + + [JsonPropertyName("MinPlayersToStart")] + public int MinPlayersToStart { get; set; } = 1; +} + +public class LevelProgressionEntry +{ + [JsonPropertyName("Level")] + public int Level { get; set; } + + // Note the lowercase 'p' — the client's RecRoomConfig.Deserialize calls + // Util.GetKey("RequiredXp", ...). PascalCase XP would miss. + [JsonPropertyName("RequiredXp")] + public int RequiredXp { get; set; } + + public static List Default() + { + var entries = new List(); + for (int i = 0; i <= 30; i++) + entries.Add(new LevelProgressionEntry { Level = i, RequiredXp = i * 500 }); + return entries; + } +} + +// Inner item shape for RecRoomConfig.DailyObjectives, verified by the +// disassembled iteration body — only "type" and "score" are read. Other +// fields the client doesn't read get ignored, so we only send these two. +public class DailyObjective +{ + [JsonPropertyName("type")] + public int Type { get; set; } + + [JsonPropertyName("score")] + public int Score { get; set; } + + public static List> DefaultSets() + { + var sets = new List>(); + for (int day = 0; day < 7; day++) + { + sets.Add( + [ + new DailyObjective { Type = 1, Score = 50 }, + new DailyObjective { Type = 2, Score = 30 }, + new DailyObjective { Type = 3, Score = 20 }, + ]); + } + return sets; + } +} + +/// +/// RecNet.AutoMicMutingConfig — dump.cs class, Deserialize at RVA 0xFA3AE0. +/// All 8 float fields are required (Util.GetKey<float>). Defaults are +/// "feature effectively disabled" — high thresholds, no force-mute. +/// +public class AutoMicMutingConfig +{ + [JsonPropertyName("MicSpamVolumeThreshold")] + public float MicSpamVolumeThreshold { get; set; } = 1.0f; + + [JsonPropertyName("MicVolumeSampleInterval")] + public float MicVolumeSampleInterval { get; set; } = 0.1f; + + [JsonPropertyName("MicVolumeSampleRollingWindowLength")] + public float MicVolumeSampleRollingWindowLength { get; set; } = 5.0f; + + [JsonPropertyName("MicSpamSamplePercentageForWarning")] + public float MicSpamSamplePercentageForWarning { get; set; } = 1.0f; + + [JsonPropertyName("MicSpamSamplePercentageForWarningToEnd")] + public float MicSpamSamplePercentageForWarningToEnd { get; set; } = 0.5f; + + [JsonPropertyName("MicSpamSamplePercentageForForceMute")] + public float MicSpamSamplePercentageForForceMute { get; set; } = 1.0f; + + [JsonPropertyName("MicSpamSamplePercentageForForceMuteToEnd")] + public float MicSpamSamplePercentageForForceMuteToEnd { get; set; } = 0.5f; + + [JsonPropertyName("MicSpamWarningStateVolumeMultiplier")] + public float MicSpamWarningStateVolumeMultiplier { get; set; } = 1.0f; +} + +/// +/// RecNet.ServerMaintenanceDTO — dump.cs, Deserialize at RVA 0xAF5450. +/// Single required key: StartsInMinutes (int). A large positive value +/// means "maintenance window is far away" = effectively never. +/// +public class ServerMaintenanceDTO +{ + [JsonPropertyName("StartsInMinutes")] + public int StartsInMinutes { get; set; } = int.MaxValue; +} diff --git a/DorkNet.Models/DorkNet.Models.csproj b/DorkNet.Models/DorkNet.Models.csproj new file mode 100644 index 0000000..125f4c9 --- /dev/null +++ b/DorkNet.Models/DorkNet.Models.csproj @@ -0,0 +1,9 @@ + + + + net9.0 + enable + enable + + + diff --git a/DorkNet.Models/DorkNet.Models.csproj.lscache b/DorkNet.Models/DorkNet.Models.csproj.lscache new file mode 100644 index 0000000..996eb5e --- /dev/null +++ b/DorkNet.Models/DorkNet.Models.csproj.lscache @@ -0,0 +1,261 @@ +version=1 + +# This file caches language service data to improve the performance of C# Dev Kit. +# It is not intended for manual editing. It can safely be deleted and will be +# regenerated automatically. For more information, see https://aka.ms/lscache +# +# To control where cache files are stored, use the following VS Code setting: +# "dotnet.projectsystem.cacheInProjectFolder": true + +[project] +language=C# +primary +lastDtbSucceeded + +[properties] +AssemblyName=DorkNet.Models +CommandLineArgsForDesignTimeEvaluation=-langversion:13.0 -define:TRACE +CompilerGeneratedFilesOutputPath= +MaxSupportedLangVersion=13.0 +ProjectAssetsFile=obj/project.assets.json +RootNamespace=DorkNet.Models +RunAnalyzers= +RunAnalyzersDuringLiveAnalysis= +SolutionPath=../DorkNet.sln +TargetFrameworkIdentifier=.NETCoreApp +TargetPath=bin/Debug/net9.0/DorkNet.Models.dll +TargetRefPath=obj/Debug/net9.0/ref/DorkNet.Models.dll +TemporaryDependencyNodeTargetIdentifier=net9.0 + +[commandLineArguments] +/noconfig +/unsafe- +/checked- +/nowarn:1701,1702,1701,1702 +/fullpaths +/nostdlib+ +/errorreport:prompt +/warn:9 +/define:TRACE;DEBUG;NET;NET9_0;NETCOREAPP;NET5_0_OR_GREATER;NET6_0_OR_GREATER;NET7_0_OR_GREATER;NET8_0_OR_GREATER;NET9_0_OR_GREATER;NETCOREAPP1_0_OR_GREATER;NETCOREAPP1_1_OR_GREATER;NETCOREAPP2_0_OR_GREATER;NETCOREAPP2_1_OR_GREATER;NETCOREAPP2_2_OR_GREATER;NETCOREAPP3_0_OR_GREATER;NETCOREAPP3_1_OR_GREATER +/highentropyva+ +/nullable:enable +/debug+ +/debug:portable +/filealign:512 +/optimize- +/out:obj\Debug\net9.0\DorkNet.Models.dll +/refout:obj\Debug\net9.0\refint\DorkNet.Models.dll +/target:library +/warnaserror- +/utf8output +/deterministic+ +/langversion:13.0 +/warnaserror+:NU1605,SYSLIB0011 + +[sourceFiles] +Auth/ + CachedLogin.cs + LoginRequest.cs + LoginResponse.cs + RecNetResult.cs + RefreshLoginResponse.cs +Avatar/ + AvatarData.cs + AvatarItem.cs +Config/RecRoomConfig.cs +GameSessions/GameSession.cs +Notification/PushNotification.cs +obj/Debug/net9.0/ + .NETCoreApp,Version=v9.0.AssemblyAttributes.cs + DorkNet.Models.AssemblyInfo.cs + DorkNet.Models.GlobalUsings.g.cs +Players/ + ModerationBlockDetail.cs + PlayerProfile.cs + Progression.cs + Reputation.cs + UpdateProfileRequest.cs +Relationships/Relationship.cs + +[metadataReferences] +/packs/Microsoft.NETCore.App.Ref/9.0.16/ref/net9.0/ + Microsoft.CSharp.dll + Microsoft.VisualBasic.Core.dll + Microsoft.VisualBasic.dll + Microsoft.Win32.Primitives.dll + Microsoft.Win32.Registry.dll + mscorlib.dll + netstandard.dll + System.AppContext.dll + System.Buffers.dll + System.Collections.Concurrent.dll + System.Collections.dll + System.Collections.Immutable.dll + System.Collections.NonGeneric.dll + System.Collections.Specialized.dll + System.ComponentModel.Annotations.dll + System.ComponentModel.DataAnnotations.dll + System.ComponentModel.dll + System.ComponentModel.EventBasedAsync.dll + System.ComponentModel.Primitives.dll + System.ComponentModel.TypeConverter.dll + System.Configuration.dll + System.Console.dll + System.Core.dll + System.Data.Common.dll + System.Data.DataSetExtensions.dll + System.Data.dll + System.Diagnostics.Contracts.dll + System.Diagnostics.Debug.dll + System.Diagnostics.DiagnosticSource.dll + System.Diagnostics.FileVersionInfo.dll + System.Diagnostics.Process.dll + System.Diagnostics.StackTrace.dll + System.Diagnostics.TextWriterTraceListener.dll + System.Diagnostics.Tools.dll + System.Diagnostics.TraceSource.dll + System.Diagnostics.Tracing.dll + System.dll + System.Drawing.dll + System.Drawing.Primitives.dll + System.Dynamic.Runtime.dll + System.Formats.Asn1.dll + System.Formats.Tar.dll + System.Globalization.Calendars.dll + System.Globalization.dll + System.Globalization.Extensions.dll + System.IO.Compression.Brotli.dll + System.IO.Compression.dll + System.IO.Compression.FileSystem.dll + System.IO.Compression.ZipFile.dll + System.IO.dll + System.IO.FileSystem.AccessControl.dll + System.IO.FileSystem.dll + System.IO.FileSystem.DriveInfo.dll + System.IO.FileSystem.Primitives.dll + System.IO.FileSystem.Watcher.dll + System.IO.IsolatedStorage.dll + System.IO.MemoryMappedFiles.dll + System.IO.Pipelines.dll + System.IO.Pipes.AccessControl.dll + System.IO.Pipes.dll + System.IO.UnmanagedMemoryStream.dll + System.Linq.dll + System.Linq.Expressions.dll + System.Linq.Parallel.dll + System.Linq.Queryable.dll + System.Memory.dll + System.Net.dll + System.Net.Http.dll + System.Net.Http.Json.dll + System.Net.HttpListener.dll + System.Net.Mail.dll + System.Net.NameResolution.dll + System.Net.NetworkInformation.dll + System.Net.Ping.dll + System.Net.Primitives.dll + System.Net.Quic.dll + System.Net.Requests.dll + System.Net.Security.dll + System.Net.ServicePoint.dll + System.Net.Sockets.dll + System.Net.WebClient.dll + System.Net.WebHeaderCollection.dll + System.Net.WebProxy.dll + System.Net.WebSockets.Client.dll + System.Net.WebSockets.dll + System.Numerics.dll + System.Numerics.Vectors.dll + System.ObjectModel.dll + System.Reflection.DispatchProxy.dll + System.Reflection.dll + System.Reflection.Emit.dll + System.Reflection.Emit.ILGeneration.dll + System.Reflection.Emit.Lightweight.dll + System.Reflection.Extensions.dll + System.Reflection.Metadata.dll + System.Reflection.Primitives.dll + System.Reflection.TypeExtensions.dll + System.Resources.Reader.dll + System.Resources.ResourceManager.dll + System.Resources.Writer.dll + System.Runtime.CompilerServices.Unsafe.dll + System.Runtime.CompilerServices.VisualC.dll + System.Runtime.dll + System.Runtime.Extensions.dll + System.Runtime.Handles.dll + System.Runtime.InteropServices.dll + System.Runtime.InteropServices.JavaScript.dll + System.Runtime.InteropServices.RuntimeInformation.dll + System.Runtime.Intrinsics.dll + System.Runtime.Loader.dll + System.Runtime.Numerics.dll + System.Runtime.Serialization.dll + System.Runtime.Serialization.Formatters.dll + System.Runtime.Serialization.Json.dll + System.Runtime.Serialization.Primitives.dll + System.Runtime.Serialization.Xml.dll + System.Security.AccessControl.dll + System.Security.Claims.dll + System.Security.Cryptography.Algorithms.dll + System.Security.Cryptography.Cng.dll + System.Security.Cryptography.Csp.dll + System.Security.Cryptography.dll + System.Security.Cryptography.Encoding.dll + System.Security.Cryptography.OpenSsl.dll + System.Security.Cryptography.Primitives.dll + System.Security.Cryptography.X509Certificates.dll + System.Security.dll + System.Security.Principal.dll + System.Security.Principal.Windows.dll + System.Security.SecureString.dll + System.ServiceModel.Web.dll + System.ServiceProcess.dll + System.Text.Encoding.CodePages.dll + System.Text.Encoding.dll + System.Text.Encoding.Extensions.dll + System.Text.Encodings.Web.dll + System.Text.Json.dll + System.Text.RegularExpressions.dll + System.Threading.Channels.dll + System.Threading.dll + System.Threading.Overlapped.dll + System.Threading.Tasks.Dataflow.dll + System.Threading.Tasks.dll + System.Threading.Tasks.Extensions.dll + System.Threading.Tasks.Parallel.dll + System.Threading.Thread.dll + System.Threading.ThreadPool.dll + System.Threading.Timer.dll + System.Transactions.dll + System.Transactions.Local.dll + System.ValueTuple.dll + System.Web.dll + System.Web.HttpUtility.dll + System.Windows.dll + System.Xml.dll + System.Xml.Linq.dll + System.Xml.ReaderWriter.dll + System.Xml.Serialization.dll + System.Xml.XDocument.dll + System.Xml.XmlDocument.dll + System.Xml.XmlSerializer.dll + System.Xml.XPath.dll + System.Xml.XPath.XDocument.dll + WindowsBase.dll + +[analyzerReferences] +/packs/Microsoft.NETCore.App.Ref/9.0.16/analyzers/dotnet/cs/ + Microsoft.Interop.ComInterfaceGenerator.dll + Microsoft.Interop.JavaScript.JSImportGenerator.dll + Microsoft.Interop.LibraryImportGenerator.dll + Microsoft.Interop.SourceGeneration.dll + System.Text.Json.SourceGeneration.dll + System.Text.RegularExpressions.Generator.dll +/sdk/9.0.314/Sdks/Microsoft.NET.Sdk/analyzers/ + Microsoft.CodeAnalysis.CSharp.NetAnalyzers.dll + Microsoft.CodeAnalysis.NetAnalyzers.dll + +[analyzerConfigFiles] +/sdk/9.0.314/Sdks/Microsoft.NET.Sdk/analyzers/build/config/analysislevel_9_default.globalconfig +obj/Debug/net9.0/DorkNet.Models.GeneratedMSBuildEditorConfig.editorconfig diff --git a/DorkNet.Models/GameSessions/GameSession.cs b/DorkNet.Models/GameSessions/GameSession.cs new file mode 100644 index 0000000..140d280 --- /dev/null +++ b/DorkNet.Models/GameSessions/GameSession.cs @@ -0,0 +1,73 @@ +using System.Text.Json.Serialization; + +namespace DorkNet.Models.GameSessions; + +public class GameSession +{ + [JsonPropertyName("GameSessionId")] + public long GameSessionId { get; set; } + + [JsonPropertyName("RegionId")] + public string RegionId { get; set; } = "us"; + + [JsonPropertyName("RoomId")] + public string RoomId { get; set; } = string.Empty; + + [JsonPropertyName("RoomName")] + public string RoomName { get; set; } = string.Empty; + + [JsonPropertyName("ActivityLevelId")] + public string ActivityLevelId { get; set; } = string.Empty; + + [JsonPropertyName("EventId")] + public long? EventId { get; set; } + + [JsonPropertyName("Private")] + public bool Private { get; set; } + + [JsonPropertyName("GameInProgress")] + public bool GameInProgress { get; set; } + + [JsonPropertyName("MaxCapacity")] + public int MaxCapacity { get; set; } = 8; + + [JsonPropertyName("IsFull")] + public bool IsFull { get; set; } + + [JsonPropertyName("PlayerCount")] + public int PlayerCount { get; set; } + + [JsonPropertyName("PhotonRoomName")] + public string PhotonRoomName { get; set; } = string.Empty; +} + +public class JoinRandomGameSessionRequest +{ + [JsonPropertyName("RoomId")] + public string? RoomId { get; set; } + + [JsonPropertyName("ActivityLevelId")] + public string? ActivityLevelId { get; set; } + + [JsonPropertyName("RegionId")] + public string RegionId { get; set; } = "us"; +} + +public class JoinGameSessionResponse +{ + [JsonPropertyName("Result")] + public JoinGameErrorCode Result { get; set; } = JoinGameErrorCode.Success; + + [JsonPropertyName("GameSession")] + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + public GameSession? GameSession { get; set; } +} + +public enum JoinGameErrorCode +{ + Success = 0, + Full = 1, + NotFound = 2, + Banned = 3, + Error = 4, +} diff --git a/DorkNet.Models/Notification/PushNotification.cs b/DorkNet.Models/Notification/PushNotification.cs new file mode 100644 index 0000000..827362b --- /dev/null +++ b/DorkNet.Models/Notification/PushNotification.cs @@ -0,0 +1,65 @@ +using System.Text.Json.Serialization; + +namespace DorkNet.Models.Notification; + +public class PushNotification +{ + [JsonPropertyName("Id")] + public PushNotificationId Id { get; set; } + + [JsonPropertyName("Msg")] + public object? Msg { get; set; } +} + +/// +/// Mirrors the client's RecNet.Notifications+PushNotificationId +/// enum exactly (decompiled at +/// Cpp2IL_CS/.../RecNet/Notifications.cs:52-89). Values are +/// non-contiguous because the client allocates them in groups (1–6 +/// core; 11–15 subscriptions; 20–25 moderation; 30–31 gifts; +/// 60–62 storefronts; 70–71 consumables; 80–85 events; 90 chat; +/// 95–96 community board; 100 inventions). +/// +/// Server NotifyAsync(playerId, id) pushes raise an event with +/// this int value over the SignalR hub. If the value doesn't match +/// what the client expects for a given concept, the push is silently +/// dropped — match the client byte-for-byte. +/// +public enum PushNotificationId +{ + RelationshipChanged = 1, + MessageReceived = 2, + MessageDeleted = 3, + PresenceHeartbeatResponse = 4, + RefreshLogin = 5, + Logout = 6, + SubscriptionUpdateProfile = 11, + SubscriptionUpdatePresence = 12, + SubscriptionUpdateGameSession = 13, + SubscriptionUpdateRoom = 15, + ModerationQuitGame = 20, + ModerationUpdateRequired = 21, + ModerationKick = 22, + ModerationKickAttemptFailed = 23, + ModerationRoomBan = 24, + ServerMaintenance = 25, + GiftPackageReceived = 30, + GiftPackageReceivedImmediate = 31, + ProfileJuniorStatusUpdate = 40, + RelationshipsInvalid = 50, + StorefrontBalanceAdd = 60, + StorefrontBalanceUpdate = 61, + StorefrontBalancePurchase = 62, + ConsumableMappingAdded = 70, + ConsumableMappingRemoved = 71, + PlayerEventCreated = 80, + PlayerEventUpdated = 81, + PlayerEventDeleted = 82, + PlayerEventResponseChanged = 83, + PlayerEventResponseDeleted = 84, + PlayerEventStateChanged = 85, + ChatMessageReceived = 90, + CommunityBoardUpdate = 95, + CommunityBoardAnnouncementUpdate = 96, + InventionModerationStateChanged = 100, +} diff --git a/DorkNet.Models/Players/ModerationBlockDetail.cs b/DorkNet.Models/Players/ModerationBlockDetail.cs new file mode 100644 index 0000000..b32b197 --- /dev/null +++ b/DorkNet.Models/Players/ModerationBlockDetail.cs @@ -0,0 +1,46 @@ +using System.Text.Json.Serialization; + +namespace DorkNet.Models.Players; + +/// +/// RecNet.ModerationBlockDetail — dump.cs:583626, Deserialize at RVA 0x1450830. +/// +/// Verified by disassembly. Required keys (Util.GetKey, throws if missing): +/// ReportCategory (int), Duration (int), GameSessionId (long), Message (string). +/// Optional keys (Util.GetKeyOrDefault): +/// IsHostKick (bool, default false), IsBan (bool, default false), +/// PlayerIdReporter (Nullable<int>, default null). +/// +/// Returned by: +/// GET api.rec.net/api/PlayerReporting/v1/moderationBlockDetails +/// (Single object, NOT a list — the client method GetModerationBlockDetails +/// returns IPromise<ModerationBlockDetail>.) +/// +/// To indicate "user is not blocked": ReportCategory=0, Duration=0, +/// GameSessionId=0, Message="", IsBan=false, IsHostKick=false, +/// PlayerIdReporter=null. +/// +public class ModerationBlockDetail +{ + // PlayerReporting.ReportCategory enum (int) + [JsonPropertyName("ReportCategory")] + public int ReportCategory { get; set; } = 0; + + [JsonPropertyName("Duration")] + public int Duration { get; set; } = 0; + + [JsonPropertyName("GameSessionId")] + public long GameSessionId { get; set; } = 0; + + [JsonPropertyName("Message")] + public string Message { get; set; } = string.Empty; + + [JsonPropertyName("IsHostKick")] + public bool IsHostKick { get; set; } = false; + + [JsonPropertyName("PlayerIdReporter")] + public int? PlayerIdReporter { get; set; } = null; + + [JsonPropertyName("IsBan")] + public bool IsBan { get; set; } = false; +} diff --git a/DorkNet.Models/Players/PlayerProfile.cs b/DorkNet.Models/Players/PlayerProfile.cs new file mode 100644 index 0000000..c260296 --- /dev/null +++ b/DorkNet.Models/Players/PlayerProfile.cs @@ -0,0 +1,64 @@ +using System.Text.Json.Serialization; + +namespace DorkNet.Models.Players; + +public class PlayerProfile +{ + private string? profileImage; + + [JsonPropertyName("AccountId")] + public long AccountId { get; set; } + + [JsonPropertyName("Username")] + public string Username { get; set; } = string.Empty; + + [JsonPropertyName("DisplayName")] + public string DisplayName { get; set; } = string.Empty; + + [JsonPropertyName("ProfileImage")] + public string? ProfileImage + { + get => profileImage; + set => profileImage = value; + } + + [JsonPropertyName("ProfileImageName")] + public string? ProfileImageName + { + get => profileImage; + set => profileImage = value; + } + + [JsonPropertyName("BannerImage")] + public string? BannerImage { get; set; } + + [JsonPropertyName("IsJunior")] + public bool IsJunior { get; set; } = false; + + [JsonPropertyName("Level")] + public int Level { get; set; } = 1; + + [JsonPropertyName("XP")] + public int XP { get; set; } = 0; + + [JsonPropertyName("Reputation")] + public int Reputation { get; set; } = 0; + + [JsonPropertyName("Bio")] + public string Bio { get; set; } = string.Empty; + + [JsonPropertyName("IsVerified")] + public bool IsVerified { get; set; } = false; + + [JsonPropertyName("IsDeveloper")] + public bool IsDeveloper { get; set; } = false; + + [JsonPropertyName("IsCommunityTeam")] + public bool IsCommunityTeam { get; set; } = false; + + [JsonPropertyName("CanReceiveInvites")] + public bool CanReceiveInvites { get; set; } = true; + + [JsonPropertyName("CreatedAt")] + public DateTime CreatedAt { get; set; } = DateTime.UtcNow; +} diff --git a/DorkNet.Models/Players/Progression.cs b/DorkNet.Models/Players/Progression.cs new file mode 100644 index 0000000..6ff46d2 --- /dev/null +++ b/DorkNet.Models/Players/Progression.cs @@ -0,0 +1,26 @@ +using System.Text.Json.Serialization; + +namespace DorkNet.Models.Players; + +/// +/// RecNet.Progression — dump.cs:582273, Deserialize at RVA 0x1142BB0. +/// +/// Verified by disassembly: reads "PlayerId", "Level", "XP" via +/// Util.GetKey<int>. All three keys are required (non-default) — missing +/// any of them throws KeyNotFoundException at the client. +/// +/// Note: the JSON uses "PlayerId" not "AccountId" even though the C# property +/// is AccountId on most other types. Returned by: +/// GET api.rec.net/api/players/v1/progression/{accountId} +/// +public class Progression +{ + [JsonPropertyName("PlayerId")] + public int PlayerId { get; set; } + + [JsonPropertyName("Level")] + public int Level { get; set; } = 1; + + [JsonPropertyName("XP")] + public int XP { get; set; } = 0; +} diff --git a/DorkNet.Models/Players/Reputation.cs b/DorkNet.Models/Players/Reputation.cs new file mode 100644 index 0000000..8a2f877 --- /dev/null +++ b/DorkNet.Models/Players/Reputation.cs @@ -0,0 +1,56 @@ +using System.Text.Json.Serialization; + +namespace DorkNet.Models.Players; + +/// +/// RecNet.Reputation — dump.cs:584087, Deserialize at RVA 0x114A830. +/// +/// Verified by disassembly. ALL Cheer*/Subscriber* keys are required +/// (Util.GetKey<int>), missing any throws KeyNotFoundException. +/// "SelectedCheer" is the only optional one (Util.GetKeyOrDefault<Nullable<int>>). +/// +/// CRITICAL TYPO: the JSON key is "Noteriety" — the dev mistyped "Notoriety". +/// The C# property is named Notoriety in dump.cs (line 584117) but the dict +/// lookup uses "Noteriety". Server response MUST match the typo. +/// +/// Returned by: +/// GET api.rec.net/api/playerReputation/v1/{accountId} +/// +public class Reputation +{ + [JsonPropertyName("AccountId")] + public int AccountId { get; set; } + + // sic: see class XML doc — typo in the binary's Util.GetKey call. + [JsonPropertyName("Noteriety")] + public float Notoriety { get; set; } = 0f; + + [JsonPropertyName("CheerGeneral")] + public int CheerGeneral { get; set; } + + [JsonPropertyName("CheerHelpful")] + public int CheerHelpful { get; set; } + + [JsonPropertyName("CheerGreatHost")] + public int CheerGreatHost { get; set; } + + [JsonPropertyName("CheerSportsman")] + public int CheerSportsman { get; set; } + + [JsonPropertyName("CheerCreative")] + public int CheerCreative { get; set; } + + [JsonPropertyName("CheerCredit")] + public int CheerCredit { get; set; } + + [JsonPropertyName("SubscriberCount")] + public int SubscriberCount { get; set; } + + [JsonPropertyName("SubscribedCount")] + public int SubscribedCount { get; set; } + + // PlayerCheering.CheerCategory enum (int) — Nullable; null means "no + // active cheer set". This is the only optional key. + [JsonPropertyName("SelectedCheer")] + public int? SelectedCheer { get; set; } = null; +} diff --git a/DorkNet.Models/Players/UpdateProfileRequest.cs b/DorkNet.Models/Players/UpdateProfileRequest.cs new file mode 100644 index 0000000..3c4d6ae --- /dev/null +++ b/DorkNet.Models/Players/UpdateProfileRequest.cs @@ -0,0 +1,12 @@ +using System.Text.Json.Serialization; + +namespace DorkNet.Models.Players; + +public class UpdateProfileRequest +{ + [JsonPropertyName("DisplayName")] + public string? DisplayName { get; set; } + + [JsonPropertyName("Bio")] + public string? Bio { get; set; } +} diff --git a/DorkNet.Models/Relationships/Relationship.cs b/DorkNet.Models/Relationships/Relationship.cs new file mode 100644 index 0000000..f7c3f90 --- /dev/null +++ b/DorkNet.Models/Relationships/Relationship.cs @@ -0,0 +1,27 @@ +using System.Text.Json.Serialization; + +namespace DorkNet.Models.Relationships; + +public class RelationshipData +{ + [JsonPropertyName("AccountId")] + public long AccountId { get; set; } + + [JsonPropertyName("OtherAccountId")] + public long OtherAccountId { get; set; } + + [JsonPropertyName("Status")] + public RelationshipStatus Status { get; set; } + + [JsonPropertyName("CreatedAt")] + public DateTime CreatedAt { get; set; } +} + +public enum RelationshipStatus +{ + None = 0, + Friend = 1, + PendingSent = 2, + PendingReceived = 3, + Blocked = 4, +} diff --git a/DorkNet.Server/Auth/AdminOnlyAttribute.cs b/DorkNet.Server/Auth/AdminOnlyAttribute.cs new file mode 100644 index 0000000..59b401a --- /dev/null +++ b/DorkNet.Server/Auth/AdminOnlyAttribute.cs @@ -0,0 +1,49 @@ +using Microsoft.AspNetCore.Mvc; +using Microsoft.AspNetCore.Mvc.Filters; +using Microsoft.EntityFrameworkCore; +using DorkNet.Server.Data; + +namespace DorkNet.Server.Auth; + +/// +/// Action / controller filter that 403s any request whose +/// JWT-resolved player doesn't have IsAdmin == true. Does not +/// itself authenticate — pair it with [Authorize] to ensure a +/// valid bearer is present before this runs. +/// +/// Usage: +/// +/// [Authorize] +/// [AdminOnly] +/// [Route("api/admin/v1")] +/// public class AdminController { ... } +/// +/// +[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, AllowMultiple = false)] +public class AdminOnlyAttribute : Attribute, IAsyncAuthorizationFilter +{ + public async Task OnAuthorizationAsync(AuthorizationFilterContext context) + { + // AuthorizationFilterContext.HttpContext is always populated and + // exposes the same ClaimsPrincipal a controller would see, so + // there's no reason to round-trip through IHttpContextAccessor + // (which isn't even registered in this app's DI container — the + // earlier lookup crashed every admin request with + // "No service for type IHttpContextAccessor has been registered"). + if (ControllerBaseExtensions.CurrentPlayerId(context.HttpContext.User) is not long id) + { + // No JWT → defer to [Authorize]'s 401. + context.Result = new UnauthorizedResult(); + return; + } + + var db = context.HttpContext.RequestServices + .GetRequiredService(); + var isAdmin = await db.Players + .Where(p => p.Id == id) + .Select(p => p.IsAdmin) + .FirstOrDefaultAsync(); + if (!isAdmin) + context.Result = new ForbidResult(); + } +} diff --git a/DorkNet.Server/Auth/BanCheckMiddleware.cs b/DorkNet.Server/Auth/BanCheckMiddleware.cs new file mode 100644 index 0000000..46dad44 --- /dev/null +++ b/DorkNet.Server/Auth/BanCheckMiddleware.cs @@ -0,0 +1,55 @@ +using Microsoft.EntityFrameworkCore; +using DorkNet.Server.Data; +using System.Security.Claims; +using System.Text.Json; + +namespace DorkNet.Server.Auth; + +/// +/// Short-circuits any authenticated request whose JWT-resolved player has +/// a non-null BannedUntil in the future. Sits AFTER +/// UseAuthentication/UseAuthorization in the pipeline so the +/// claims principal is populated before we look at it. +/// +/// Anonymous requests (no/invalid bearer) pass through unchanged — this +/// middleware never short-circuits them. Whether they're allowed is the +/// per-endpoint [Authorize] decision, not ours. +/// +/// Lookup is one query per request against the Players table. Cached for +/// the request lifetime via the scoped DbContext, so repeated bearer +/// checks within one request hit the EF first-level cache. +/// +public class BanCheckMiddleware(RequestDelegate next) +{ + public async Task InvokeAsync(HttpContext ctx, DorkNetDbContext db) + { + // Cheap early-out for anonymous traffic — no DB hit. + var sub = ctx.User.FindFirstValue(ClaimTypes.NameIdentifier); + if (!long.TryParse(sub, out var playerId)) + { + await next(ctx); + return; + } + + // Project just the BannedUntil column — no need to materialise the + // full player entity for every request. + var bannedUntil = await db.Players + .Where(p => p.Id == playerId) + .Select(p => p.BannedUntil) + .FirstOrDefaultAsync(); + + if (bannedUntil.HasValue && bannedUntil.Value > DateTime.UtcNow) + { + ctx.Response.StatusCode = StatusCodes.Status401Unauthorized; + ctx.Response.ContentType = "application/json"; + await ctx.Response.WriteAsync(JsonSerializer.Serialize(new + { + Error = "banned", + Until = bannedUntil.Value.ToString("o"), + })); + return; + } + + await next(ctx); + } +} diff --git a/DorkNet.Server/Auth/ControllerBaseExtensions.cs b/DorkNet.Server/Auth/ControllerBaseExtensions.cs new file mode 100644 index 0000000..c405d7e --- /dev/null +++ b/DorkNet.Server/Auth/ControllerBaseExtensions.cs @@ -0,0 +1,45 @@ +using Microsoft.AspNetCore.Mvc; +using System.Security.Claims; + +namespace DorkNet.Server.Auth; + +/// +/// Single canonical place for "who is the bearer-resolved player on this +/// request." Replaces the per-controller copy-paste of: +/// +/// private long CurrentPlayerId => +/// long.Parse(User.FindFirstValue(ClaimTypes.NameIdentifier)!); +/// +/// which historically existed in 10 controllers and silently NPE-bombed +/// the action when the bearer was missing instead of returning 401. +/// +public static class ControllerBaseExtensions +{ + /// + /// Returns the bearer-resolved player id, or null when the caller is + /// anonymous / the bearer is malformed. Use on optional-auth endpoints. + /// + public static long? CurrentPlayerId(this ControllerBase controller) => + CurrentPlayerId(controller.User); + + /// Overload for non-controller callers (filters, + /// middleware) that hold a directly. + public static long? CurrentPlayerId(ClaimsPrincipal user) + { + var claim = user.FindFirstValue(ClaimTypes.NameIdentifier); + return long.TryParse(claim, out var id) ? id : null; + } + + /// + /// Returns the bearer-resolved player id; throws + /// when missing. Use on + /// [Authorize]-decorated endpoints where a missing id is a + /// programming error (the framework should have already 401'd). + /// + public static long RequireCurrentPlayerId(this ControllerBase controller) => + controller.CurrentPlayerId() + ?? throw new UnauthorizedAccessException( + "Bearer token claim 'NameIdentifier' is missing or non-numeric. " + + "If this fired on an [Authorize] endpoint, JWT validation upstream " + + "is misconfigured."); +} diff --git a/DorkNet.Server/Auth/IpBanCheckMiddleware.cs b/DorkNet.Server/Auth/IpBanCheckMiddleware.cs new file mode 100644 index 0000000..9cd648f --- /dev/null +++ b/DorkNet.Server/Auth/IpBanCheckMiddleware.cs @@ -0,0 +1,92 @@ +using Microsoft.EntityFrameworkCore; +using DorkNet.Server.Data; +using System.Net; +using System.Net.Sockets; + +namespace DorkNet.Server.Auth; + +/// +/// Short-circuits any incoming request whose remote IP falls under an +/// active row +/// with 403. Sits BEFORE authentication so banned IPs can't spend a +/// round-trip negotiating a JWT — the connection terminates as early +/// as possible. +/// +/// Lookup is one query against IpBans per request. The table is small +/// (a few rows in practice) and the simple "Cidr matches?" filter is +/// done in C# rather than SQL because CIDR maths in SQLite is awkward. +/// +public class IpBanCheckMiddleware(RequestDelegate next) +{ + public async Task InvokeAsync(HttpContext ctx, DorkNetDbContext db) + { + var remote = ctx.Connection.RemoteIpAddress; + if (remote is null) + { + await next(ctx); + return; + } + + // Materialise the active-ban list once per request. Could be + // cached for ~30s with a hosted service if it ever becomes a + // hot path, but right now this is unmeasurable. + var bans = await db.IpBans + .Where(b => b.Until == null || b.Until > DateTime.UtcNow) + .Select(b => b.Cidr) + .ToListAsync(); + + foreach (var cidr in bans) + { + if (CidrMatches(cidr, remote)) + { + ctx.Response.StatusCode = StatusCodes.Status403Forbidden; + await ctx.Response.WriteAsync("ip_banned"); + return; + } + } + + await next(ctx); + } + + /// Match a single IP against a CIDR-or-bare-IP string. + /// Bare IPs match exactly; CIDRs match if the prefix bits agree. + /// Falls back to false on any parse error. + private static bool CidrMatches(string cidr, IPAddress remote) + { + try + { + var slash = cidr.IndexOf('/'); + if (slash < 0) + { + return IPAddress.TryParse(cidr, out var single) && + single.Equals(remote); + } + + var addrPart = cidr[..slash]; + var prefixPart = cidr[(slash + 1)..]; + if (!IPAddress.TryParse(addrPart, out var net) || + !int.TryParse(prefixPart, out var prefix)) + return false; + + // Only IPv4 networks for now — IPv6 CIDR matching is + // straightforward but we'd need to mask 128-bit values + // and the user's setup is single-host IPv4 anyway. + if (net.AddressFamily != AddressFamily.InterNetwork || + remote.AddressFamily != AddressFamily.InterNetwork) + return false; + + var netBytes = net.GetAddressBytes(); + var remoteBytes = remote.GetAddressBytes(); + int fullBytes = prefix / 8; + int extraBits = prefix % 8; + + for (int i = 0; i < fullBytes; i++) + if (netBytes[i] != remoteBytes[i]) return false; + if (extraBits == 0) return true; + + int mask = 0xFF << (8 - extraBits) & 0xFF; + return (netBytes[fullBytes] & mask) == (remoteBytes[fullBytes] & mask); + } + catch { return false; } + } +} diff --git a/DorkNet.Server/Compat/ClientVersion.cs b/DorkNet.Server/Compat/ClientVersion.cs new file mode 100644 index 0000000..598d4c8 --- /dev/null +++ b/DorkNet.Server/Compat/ClientVersion.cs @@ -0,0 +1,28 @@ +namespace DorkNet.Server.Compat; + +/// Identifies a specific Rec Room client build. +/// +/// is the wire-format identifier the client mod +/// stamps on every request via the X-DorkNet-Version header. +/// Format is {branch}_{semver}: march_2020_03_10, +/// december_2020_12_18. Keep it lowercase, underscore-separated; +/// it shows up in admin logs and error responses. +/// +/// +/// is the wire-format-compatibility group: +/// every version inside the same generation shares the same wire +/// shapes (JSON DTO casing, route names, etc.) and therefore the same +/// strategy implementations. Adding a new build that's protocol-compatible +/// with an existing generation = a one-line registration; only a new +/// generation entails new strategy code. +/// +/// +public sealed record ClientVersion(string Key, string Generation) +{ + /// Sentinel returned when a request has no + /// X-DorkNet-Version header AND no + /// DorkNet:DefaultClientVersion is configured. Controllers + /// can branch on this to either fall through to a sensible default + /// or 426 the request. + public static readonly ClientVersion Unknown = new("unknown", "unknown"); +} diff --git a/DorkNet.Server/Compat/HttpContextExtensions.cs b/DorkNet.Server/Compat/HttpContextExtensions.cs new file mode 100644 index 0000000..ba7dcee --- /dev/null +++ b/DorkNet.Server/Compat/HttpContextExtensions.cs @@ -0,0 +1,29 @@ +using Microsoft.AspNetCore.Http; + +namespace DorkNet.Server.Compat; + +/// Helpers to read the active +/// off the current request. The version is set by +/// VersionDetectionMiddleware early in the pipeline so any +/// downstream consumer (controllers, services, filters) can resolve +/// it without re-parsing the header. +public static class HttpContextExtensions +{ + private const string ClientVersionKey = "DorkNet.ClientVersion"; + + /// Stamp the resolved version on the request. Called by + /// VersionDetectionMiddleware; not for general use. + internal static void SetClientVersion(this HttpContext ctx, ClientVersion v) + => ctx.Items[ClientVersionKey] = v; + + /// The client version for this request. Returns + /// if the middleware hasn't + /// run (e.g. a unit test that calls a controller method directly) + /// rather than null, so callers can safely branch on + /// Generation without null-checks. + public static ClientVersion ClientVersion(this HttpContext ctx) + => ctx.Items.TryGetValue(ClientVersionKey, out var v) + && v is ClientVersion cv + ? cv + : Compat.ClientVersion.Unknown; +} diff --git a/DorkNet.Server/Compat/IVersionPlugin.cs b/DorkNet.Server/Compat/IVersionPlugin.cs new file mode 100644 index 0000000..f6efaa7 --- /dev/null +++ b/DorkNet.Server/Compat/IVersionPlugin.cs @@ -0,0 +1,39 @@ +namespace DorkNet.Server.Compat; + +/// One client-version-family plugin. Each implementation +/// declares which exact version it handles +/// and which protocol-compatibility they map +/// to. The plugin's job at registration time is to also wire any +/// generation-specific strategy services into DI via +/// so controllers / services +/// downstream can resolve the right IWireFormat, +/// IRouteOverride, etc. for the active request's generation. +/// +/// Adding a new version with the SAME wire shapes as an existing +/// generation = just listing its key in that generation's plugin +/// . Only a new generation needs new +/// strategy classes. +/// +public interface IVersionPlugin +{ + /// All client-build keys this plugin handles. Multiple + /// keys can share one plugin when several Rec Room builds shipped + /// the same wire format. Convention: lowercase, underscore-separated, + /// matching the literal the client mod sends in + /// X-DorkNet-Version. + IReadOnlyCollection VersionKeys { get; } + + /// Wire-format-compatibility group this plugin implements. + /// Multiple plugins can share a generation (rare, but allowed when + /// e.g. a Quest build and a PC build of the same era are + /// protocol-identical and we just want separate + /// bookkeeping). + string Generation { get; } + + /// Register any generation-specific strategy services + /// into DI. Called once at startup by the bootstrap code in + /// Program.cs. Use keyed registrations (keyed by + /// ) so requests for other generations + /// don't collide. + void RegisterStrategies(IServiceCollection services); +} diff --git a/DorkNet.Server/Compat/VersionDetectionMiddleware.cs b/DorkNet.Server/Compat/VersionDetectionMiddleware.cs new file mode 100644 index 0000000..a7b51c5 --- /dev/null +++ b/DorkNet.Server/Compat/VersionDetectionMiddleware.cs @@ -0,0 +1,144 @@ +using System.Text.Json; +using Microsoft.AspNetCore.Http; +using DorkNet.Server.Services; + +namespace DorkNet.Server.Compat; + +/// Extracts the client version from X-DorkNet-Version +/// on every request, validates it against this deployment's allow-list, +/// and either: +/// +/// Stamps with +/// the resolved and forwards the request, OR +/// Returns HTTP 426 Upgrade Required with a JSON body +/// describing the mismatch when the client's version isn't in +/// . +/// +/// +/// Headerless requests fall back to +/// when set. That's +/// the transition pathway — once every shipped client mod stamps the +/// header, set DorkNet:DefaultClientVersion to empty and +/// headerless requests get rejected too. +/// +/// Some routes intentionally don't carry a client identity +/// (the public-facing site at the apex, the admin SPA at +/// admin.{apex}, health checks). Those are excluded by host +/// + path prefix below — they always skip the version gate and +/// receive . The apex is whatever +/// resolved to at startup, so forks +/// running on a different domain get the same exemption automatically. +/// +public sealed class VersionDetectionMiddleware( + RequestDelegate next, + VersionRegistry registry, + DomainConfig domain, + ILogger logger) +{ + private const string HeaderName = "X-DorkNet-Version"; + + public async Task InvokeAsync(HttpContext ctx) + { + if (IsExempt(ctx)) + { + ctx.SetClientVersion(ClientVersion.Unknown); + await next(ctx); + return; + } + + var headerVal = ctx.Request.Headers[HeaderName].ToString(); + var versionKey = string.IsNullOrWhiteSpace(headerVal) + ? registry.DefaultVersionKey + : headerVal.Trim(); + + // No header AND no default configured — reject. Once every + // client mod sends the header this is the path that catches + // probes / misconfigured clients. + if (string.IsNullOrWhiteSpace(versionKey)) + { + await WriteMismatch(ctx, "client_version_missing", got: null); + return; + } + + var resolved = registry.Resolve(versionKey); + if (resolved == ClientVersion.Unknown) + { + // The client claims a version but no plugin knows it. That + // means either a typo in the header or a build of Rec Room + // we haven't onboarded yet. Either way, reject — server + // can't reliably serve a version it doesn't have a + // strategy bundle for. + await WriteMismatch(ctx, "client_version_unknown", got: versionKey); + return; + } + + if (!registry.IsSupported(versionKey)) + { + // Known version, but this specific deployment is + // configured to accept a different set. e.g. a March + // client hitting a December-only server. + await WriteMismatch(ctx, "client_version_not_accepted", got: versionKey); + return; + } + + ctx.SetClientVersion(resolved); + await next(ctx); + } + + private bool IsExempt(HttpContext ctx) + { + // Hosts that don't serve the watch — these are humans on a + // browser or admin tooling, not the game client. + var host = ctx.Request.Host.Host.ToLowerInvariant(); + var apex = domain.Apex.ToLowerInvariant(); + if (host == apex || host == "www." + apex) return true; + if (host.StartsWith("admin.")) return true; + if (host.StartsWith("site.")) return true; + if (host.StartsWith("feed.")) return true; + + // Server health / readiness probes. + var path = ctx.Request.Path.Value; + if (path is null) return false; + if (path.StartsWith("/health", StringComparison.OrdinalIgnoreCase)) return true; + if (path.StartsWith("/api/admin/", StringComparison.OrdinalIgnoreCase)) return true; + if (path.StartsWith("/swagger", StringComparison.OrdinalIgnoreCase)) return true; + + // The boot-time version gate itself — the whole POINT of this + // endpoint is to receive a mismatched-version client and reply + // with the watch's native "Update Required" payload. Gating it + // here would 426 the request before VersionCheckController + // could respond. + if (path.StartsWith("/api/versioncheck/", StringComparison.OrdinalIgnoreCase)) return true; + + return false; + } + + private async Task WriteMismatch(HttpContext ctx, string error, string? got) + { + logger.LogWarning( + "[version-gate] rejecting request path={Path} host={Host} reason={Reason} got={Got} supported=[{Supported}]", + ctx.Request.Path, ctx.Request.Host.Host, error, got ?? "(none)", + string.Join(",", registry.SupportedVersionKeys)); + + ctx.Response.StatusCode = StatusCodes.Status426UpgradeRequired; + ctx.Response.ContentType = "application/json; charset=utf-8"; + var body = new + { + error, + got, + supported = registry.SupportedVersionKeys.ToArray(), + message = error switch + { + "client_version_missing" => + "Client did not send an X-DorkNet-Version header. Update your DorkNet client mod.", + "client_version_unknown" => + $"Client claims version '{got}', which no version plugin in this server recognises.", + "client_version_not_accepted" => + $"Client version '{got}' is known but this deployment is configured to accept only: " + + string.Join(", ", registry.SupportedVersionKeys) + ".", + _ => error, + }, + }; + await JsonSerializer.SerializeAsync(ctx.Response.Body, body); + } +} diff --git a/DorkNet.Server/Compat/VersionRegistry.cs b/DorkNet.Server/Compat/VersionRegistry.cs new file mode 100644 index 0000000..c139357 --- /dev/null +++ b/DorkNet.Server/Compat/VersionRegistry.cs @@ -0,0 +1,69 @@ +namespace DorkNet.Server.Compat; + +/// Resolves a client-build key (e.g. december_2020_12_18) +/// to its . Built once at startup from all +/// registered instances. +/// +/// Singleton — the version-key → generation mapping is fixed at +/// boot time, and the registry is read on every request by the +/// version-detection middleware. +/// +public sealed class VersionRegistry +{ + private readonly Dictionary _byKey; + + /// The set of version keys this deployment is willing to + /// serve. Comes from DorkNet:SupportedVersions in config. + /// Anything else gets a 426 from the middleware. + public IReadOnlySet SupportedVersionKeys { get; } + + /// Falls back to this when a request arrives without an + /// X-DorkNet-Version header — set via + /// DorkNet:DefaultClientVersion in config. Empty/null means + /// "no fallback; reject headerless requests". Set during the + /// transition window where the old client mod doesn't yet stamp + /// the header. + public string? DefaultVersionKey { get; } + + public VersionRegistry( + IEnumerable plugins, + IReadOnlySet supportedVersionKeys, + string? defaultVersionKey) + { + _byKey = new Dictionary(StringComparer.OrdinalIgnoreCase); + foreach (var plugin in plugins) + { + foreach (var key in plugin.VersionKeys) + { + if (_byKey.ContainsKey(key)) + throw new InvalidOperationException( + $"VersionPlugin collision: '{key}' is claimed by more than one IVersionPlugin. " + + $"Each version key maps to exactly one plugin."); + _byKey[key] = new ClientVersion(key, plugin.Generation); + } + } + SupportedVersionKeys = supportedVersionKeys; + DefaultVersionKey = defaultVersionKey; + } + + /// Resolve a version key (case-insensitive) to its + /// , or + /// if no plugin claims it. The middleware decides whether unknown + /// → reject or → fall back to . + public ClientVersion Resolve(string? key) + { + if (string.IsNullOrWhiteSpace(key)) return ClientVersion.Unknown; + return _byKey.TryGetValue(key, out var v) ? v : ClientVersion.Unknown; + } + + /// True when the given version key is in + /// . Used by the middleware to + /// 426 cross-version connection attempts (e.g. a March client + /// hitting a server configured for December only). + public bool IsSupported(string key) + => SupportedVersionKeys.Contains(key); + + /// Every known plugin-registered version key, for + /// diagnostic responses and admin tooling. + public IEnumerable AllKnown => _byKey.Values; +} diff --git a/DorkNet.Server/Controllers/API/Account/AccountApiController.cs b/DorkNet.Server/Controllers/API/Account/AccountApiController.cs new file mode 100644 index 0000000..7bf38b4 --- /dev/null +++ b/DorkNet.Server/Controllers/API/Account/AccountApiController.cs @@ -0,0 +1,267 @@ +using Microsoft.AspNetCore.Mvc; +using Microsoft.EntityFrameworkCore; +using DorkNet.Models.Auth; +using DorkNet.Server.Data; +using DorkNet.Server.Data.Entities; +using DorkNet.Server.Services; + +namespace DorkNet.Server.Controllers.API.Account; + +/// +/// api.rec.net/api/account/* — same endpoints as accounts.rec.net but routed +/// through the API service. The Accounts class in dump.cs doesn't expose its +/// base URL constant so the game may use either host; we cover both. +/// +[ApiController] +public class AccountApiController( + PlayerService playerService, + AuthService authService, + DorkNetDbContext db, + NotificationService notifications, + ServerSettingsService settings, + ILogger logger) : ControllerBase +{ + [HttpPost("api/account/v1/create")] + [HttpPost("api/account/v1")] + [HttpPost("api/account/v2/create")] + [HttpPost("api/createaccount")] + public async Task CreateAccount( + [FromForm] int platform, + [FromForm] string? platformId, + [FromForm] string? deviceId, + [FromForm] string? Username, + [FromForm] string? DisplayName) + { + if (await settings.AreSignupsDisabledAsync()) + { + logger.LogInformation( + "[api-account] api/account/v1/create refused — signups disabled (device={Device} platform={Platform})", + deviceId, platform); + return Ok(new CreateAccountResponse + { + Success = false, + Error = "Account creation is currently disabled by the server admin.", + }); + } + + var player = await playerService.GetOrCreateByDeviceAsync( + deviceId: deviceId, + platform: platform, + platformId: platformId, + displayName: DisplayName ?? Username); + + return Ok(new CreateAccountResponse + { + Success = true, + Error = string.Empty, + Account = new RecNetAccount + { + AccountId = (int)player.Id, + RawUsername = player.Username, + Username = player.Username, + DisplayName = player.DisplayName ?? player.Username, + ProfileImage = string.Empty, + TreatAsJunior = false, + HasBirthday = true, + Platforms = 1, + }, + }); + } + + [HttpGet("api/account/v1/me")] + [HttpGet("api/account/v2/me")] + public async Task GetMe() + { + var player = await GetCurrentPlayerAsync(); + if (player is null) return Unauthorized(); + return Ok(new RecNetSelfAccount + { + AccountId = (int)player.Id, + RawUsername = player.Username, + Username = player.Username, + DisplayName = player.DisplayName ?? player.Username, + ProfileImage = player.ProfileImageName ?? string.Empty, + TreatAsJunior = false, + HasBirthday = true, + Platforms = 1, + Email = player.Email ?? string.Empty, + Phone = string.Empty, + Birthday = new DateTime(2000, 1, 1), + JuniorState = 0, + ParentAccountId = null, + }); + } + + /// + /// Resolves the player tied to the bearer token on the request. Returns + /// null when no/invalid bearer is supplied — caller decides whether + /// that's a 401 or a softer fallback. Anonymous-account creation was + /// previously the fallback path; deleted in Phase 1c because every + /// caller has gone through `/account/create` (deviceId-bound) before + /// reaching us. + /// + private async Task GetCurrentPlayerAsync() + { + var auth = Request.Headers.Authorization.ToString(); + if (!auth.StartsWith("Bearer ", StringComparison.OrdinalIgnoreCase)) + return null; + var token = auth["Bearer ".Length..].Trim(); + var id = authService.ValidateToken(token); + if (id is not long playerId) return null; + return await playerService.GetByIdAsync(playerId); + } + + // Modification endpoints — POSTs land on api.* host with form body + // carrying the new value. AuthAccountController on auth.* handles + // the same shape with real persistence; we mirror that here so + // either host works. + + [HttpPost("api/account/v1/displayname")] + public async Task SetDisplayName([FromForm(Name = "displayName")] string? displayName, + [FromForm(Name = "value")] string? value) + { + var p = await GetCurrentPlayerAsync(); + if (p is null) return Unauthorized(); + var name = (displayName ?? value ?? string.Empty).Trim(); + if (name.Length == 0) return BadRequest(new RecNetResult { Success = false, Error = "empty" }); + await playerService.UpdateDisplayNameAsync(p.Id, name); + return Ok(new RecNetResult { Success = true, Error = string.Empty }); + } + + [HttpPost("api/account/v1/bio")] + public async Task SetBio([FromForm(Name = "bio")] string? bio, + [FromForm(Name = "value")] string? value) + { + var p = await GetCurrentPlayerAsync(); + if (p is null) return Unauthorized(); + await playerService.UpdateBioAsync(p.Id, (bio ?? value ?? string.Empty)); + return Ok(new RecNetResult { Success = true, Error = string.Empty }); + } + + [HttpPost("api/account/v1/username")] + public async Task SetUsername([FromForm(Name = "username")] string? username, + [FromForm(Name = "value")] string? value) + { + var p = await GetCurrentPlayerAsync(); + if (p is null) return Unauthorized(); + var newName = (username ?? value ?? string.Empty).Trim(); + if (newName.Length == 0) return BadRequest(new RecNetResult { Success = false, Error = "empty" }); + var taken = await db.Players.AnyAsync(x => x.Username == newName && x.Id != p.Id); + if (taken) return Ok(new RecNetResult { Success = false, Error = "username_taken" }); + p.Username = newName; + await db.SaveChangesAsync(); + return Ok(new RecNetResult { Success = true, Error = string.Empty }); + } + + [HttpPost("api/account/v1/birthday")] + public async Task SetBirthday([FromForm(Name = "birthday")] string? birthday, + [FromForm(Name = "value")] string? value) + { + var p = await GetCurrentPlayerAsync(); + if (p is null) return Unauthorized(); + var raw = birthday ?? value ?? string.Empty; + if (DateTime.TryParse(raw, out var parsed)) + { + p.Birthday = DateTime.SpecifyKind(parsed, DateTimeKind.Utc); + await db.SaveChangesAsync(); + } + return Ok(new RecNetResult { Success = true, Error = string.Empty }); + } + + [HttpPost("api/account/v1/email")] + public async Task SetEmail([FromForm(Name = "email")] string? email, + [FromForm(Name = "value")] string? value) + { + var p = await GetCurrentPlayerAsync(); + if (p is null) return Unauthorized(); + var addr = (email ?? value ?? string.Empty).Trim(); + if (addr.Length > 0) p.Email = addr; + await db.SaveChangesAsync(); + return Ok(new RecNetResult { Success = true, Error = string.Empty }); + } + + [HttpPost("api/account/v1/phone")] + [HttpPost("api/account/v1/confirmphone")] + public async Task SetPhone([FromForm(Name = "phone")] string? phone, + [FromForm(Name = "value")] string? value) + { + var p = await GetCurrentPlayerAsync(); + if (p is null) return Unauthorized(); + var num = (phone ?? value ?? string.Empty).Trim(); + if (num.Length > 0) p.Phone = num; + await db.SaveChangesAsync(); + return Ok(new RecNetResult { Success = true, Error = string.Empty }); + } + + [HttpPost("api/account/v1/profileimage")] + [HttpPost("api/account/me/profileimage")] + [HttpPost("account/me/profileimage")] + [HttpPost("profileimage")] + [HttpPut("api/account/v1/profileimage")] + [HttpPut("api/account/me/profileimage")] + [HttpPut("account/me/profileimage")] + [HttpPut("profileimage")] + [Consumes("application/x-www-form-urlencoded", "multipart/form-data", "application/json")] + public async Task SetProfileImage( + [FromForm(Name = "imageName")] string? imageName, + [FromForm(Name = "fileName")] string? fileName, + [FromForm(Name = "image")] string? imageAlt) + { + var player = await GetCurrentPlayerAsync(); + if (player is null) return Unauthorized(); + + var name = imageName ?? fileName ?? imageAlt + ?? Request.Query["imageName"].ToString() + ?? Request.Query["fileName"].ToString(); + player.ProfileImageName = string.IsNullOrWhiteSpace(name) ? null : name.Trim(); + await db.SaveChangesAsync(); + logger.LogInformation( + "[api-account] profile image set: player={PlayerId} -> {Name}", + player.Id, + player.ProfileImageName ?? ""); + await NotifyAccountImageChangedAsync(player); + return Ok(new RecNetResult { Success = true, Error = string.Empty }); + } + + private Task NotifyAccountImageChangedAsync(PlayerEntity player) + { + var account = new RecNetAccount + { + AccountId = (int)player.Id, + RawUsername = player.Username, + Username = player.Username, + DisplayName = player.DisplayName ?? player.Username, + ProfileImage = player.ProfileImageName ?? string.Empty, + TreatAsJunior = player.IsJunior, + HasBirthday = true, + Platforms = 1, + }; + var selfAccount = new RecNetSelfAccount + { + AccountId = account.AccountId, + RawUsername = account.RawUsername, + Username = account.Username, + DisplayName = account.DisplayName, + ProfileImage = account.ProfileImage, + TreatAsJunior = account.TreatAsJunior, + HasBirthday = account.HasBirthday, + Platforms = account.Platforms, + Email = player.Email ?? string.Empty, + Phone = player.Phone ?? string.Empty, + Birthday = player.Birthday, + JuniorState = player.IsJunior ? 1 : 0, + ParentAccountId = null, + }; + return Task.WhenAll( + notifications.NotifyTypedAsync(player.Id, "AccountUpdate", account), + notifications.NotifyTypedAsync(player.Id, "SelfAccountUpdate", selfAccount)); + } + + [HttpGet("api/account/v1/namegen")] + [HttpGet("api/account/v1/namegeneration")] + public IActionResult NameGen() => Ok(new + { + Nouns = new[] { "Player", "Hero", "Star", "Fox", "Wolf" }, + Adjectives = new[] { "Brave", "Swift", "Clever", "Bold", "Bright" }, + }); +} diff --git a/DorkNet.Server/Controllers/API/Activities/ActivitiesController.cs b/DorkNet.Server/Controllers/API/Activities/ActivitiesController.cs new file mode 100644 index 0000000..a82877c --- /dev/null +++ b/DorkNet.Server/Controllers/API/Activities/ActivitiesController.cs @@ -0,0 +1,16 @@ +using Microsoft.AspNetCore.Mvc; + +namespace DorkNet.Server.Controllers.API.Activities; + +/// +/// api.{rec.net,localhost}/api/activities/* — per-activity content +/// (charades words, party-game prompts, etc.). The 2020 watch +/// fetches these as a static list at room-join time; we ship an +/// empty list so the activity's bundled defaults kick in. +/// +[ApiController] +public class ActivitiesController : ControllerBase +{ + [HttpGet("api/activities/charades/v1/words")] + public IActionResult CharadesWords() => Ok(Array.Empty()); +} diff --git a/DorkNet.Server/Controllers/API/Announcements/AnnouncementsController.cs b/DorkNet.Server/Controllers/API/Announcements/AnnouncementsController.cs new file mode 100644 index 0000000..37a9dcd --- /dev/null +++ b/DorkNet.Server/Controllers/API/Announcements/AnnouncementsController.cs @@ -0,0 +1,45 @@ +using Microsoft.AspNetCore.Mvc; +using DorkNet.Server.Services; + +namespace DorkNet.Server.Controllers.API.Announcements; + +/// +/// api.{rec.net,localhost}/api/announcement/v{1,2}/get — fetches the +/// current server announcement (shown on the watch's home tab). The +/// payload is sourced from so the +/// admin SPA's "edit announcement" form is the single place to +/// update what players see. +/// +/// Wire shape per RecNet/AnnouncementDTO.cs — AnnouncementId, +/// AnnouncementType, LinkType, Platform are all required via +/// Util.GetKey (throws on miss); Title/Body/ImageName/LinkName/ +/// LinkUri/CreatedAt are GetKeyOrDefault. +/// +[ApiController] +public class AnnouncementsController(CommunityBoardService communityBoard) : ControllerBase +{ + [HttpGet("api/announcement/v1/get")] + [HttpGet("api/announcement/v2/get")] + public async Task Current() + { + var board = await communityBoard.GetAsync(); + var ann = board.CurrentAnnouncement; + if (ann is null) return Ok(Array.Empty()); + return Ok(new[] + { + new + { + AnnouncementId = 1L, + AnnouncementType = 0, + Title = "Server Announcement", + Body = ann.Message ?? string.Empty, + ImageName = "", + LinkType = 0, + LinkName = "", + LinkUri = ann.MoreInfoUrl ?? string.Empty, + Platform = 0, + CreatedAt = DateTime.UtcNow, + } + }); + } +} diff --git a/DorkNet.Server/Controllers/API/Auth/PhotonAuthController.cs b/DorkNet.Server/Controllers/API/Auth/PhotonAuthController.cs new file mode 100644 index 0000000..a309050 --- /dev/null +++ b/DorkNet.Server/Controllers/API/Auth/PhotonAuthController.cs @@ -0,0 +1,43 @@ +using Microsoft.AspNetCore.Mvc; +using DorkNet.Server.Services; + +namespace DorkNet.Server.Controllers.API.Auth; + +/// +/// Photon custom-auth webhook (api.rec.net). +/// +/// Configure in Photon dashboard: +/// Authentication → Custom → URL = https://api.rec.net/api/auth/v1/photon +/// Query params sent by client: token=<our_jwt> +/// +/// Photon ResultCode: 1 = success, 2 = fail, 3 = param error +/// +[ApiController] +public class PhotonAuthController(AuthService authService, PlayerService playerService) : ControllerBase +{ + [HttpGet("api/auth/v1/photon")] + [HttpPost("api/auth/v1/photon")] + public async Task Authenticate([FromQuery] string? token, [FromQuery] string? Token) + { + var jwt = token ?? Token; + + if (string.IsNullOrWhiteSpace(jwt)) + return Ok(new { ResultCode = 3, Message = "Missing token parameter" }); + + var playerId = authService.ValidateToken(jwt); + if (playerId is null) + return Ok(new { ResultCode = 2, Message = "Invalid or expired token" }); + + var player = await playerService.GetByIdAsync(playerId.Value); + if (player is null) + return Ok(new { ResultCode = 2, Message = "Player not found" }); + + return Ok(new + { + ResultCode = 1, + Message = "ok", + UserId = player.Id.ToString(), + Nickname = player.DisplayName ?? player.Username, + }); + } +} diff --git a/DorkNet.Server/Controllers/API/Avatar/V2/AvatarGiftsController.cs b/DorkNet.Server/Controllers/API/Avatar/V2/AvatarGiftsController.cs new file mode 100644 index 0000000..7dd81db --- /dev/null +++ b/DorkNet.Server/Controllers/API/Avatar/V2/AvatarGiftsController.cs @@ -0,0 +1,308 @@ +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Mvc; +using Microsoft.EntityFrameworkCore; +using DorkNet.Models.Notification; +using DorkNet.Server.Auth; +using DorkNet.Server.Data; +using DorkNet.Server.Data.Entities; +using DorkNet.Server.Services; + +namespace DorkNet.Server.Controllers.API.Avatar.V2; + +/// +/// api.rec.net/api/avatar/v2/gifts — wardrobe gift inbox. Mirrors +/// the client's RecNet.Avatars.DowloadGiftPackages + +/// LocalRequestGiftPackage + LocalConsumeGiftPackage +/// flow. Wire DTO matches RecNet.Avatars+GiftPackage +/// (Cpp2IL_CS/.../RecNet/Avatars.cs:192-555) field-for-field +/// so the watch's deserialiser doesn't crash. +/// +/// Server semantics: +/// - GET v2/gifts returns unconsumed gifts for caller. +/// - POST v2/gifts/generate rolls a random unlocked-avatar +/// item and stamps a for caller — +/// used by drop tiles and "free daily" prompts. +/// - POST v2/gifts/consume/{id} marks consumed and appends +/// the +/// to the player's so it +/// shows up in their backpack on next load. +/// +[ApiController] +[Authorize] +public class AvatarGiftsController( + DorkNetDbContext db, + NotificationService notifications, + LevelService level) : ControllerBase +{ + [HttpGet("api/avatar/v2/gifts")] + [HttpGet("api/avatar/v3/gifts")] + [HttpGet("api/avatar/v4/gifts")] + public async Task List() + { + var pid = this.RequireCurrentPlayerId(); + var rows = await db.GiftPackages + .Where(g => g.RecipientPlayerId == pid && !g.Consumed) + .OrderByDescending(g => g.CreatedAt) + .ToListAsync(); + return Ok(rows.Select(ToWire)); + } + + public sealed record GenerateGiftRequest(int? GiftContext); + + /// POST api/avatar/v2/gifts/generate — server picks + /// a random unlocked-avatar item and queues a gift package. The + /// 2020 client posts to this when the user clicks a free-gift + /// tile or hits a context the client maps to GiftContext. + [HttpPost("api/avatar/v2/gifts/generate")] + public async Task Generate([FromBody] GenerateGiftRequest? req) + { + var pid = this.RequireCurrentPlayerId(); + // Pick a random wardrobe StoreItem (Slug starts with + // "wardrobe-" — that's the convention StoreService uses for + // catalogue rows derived from outfits_assets bundles). + var pool = await db.StoreItems + .Where(s => s.IsActive && s.Slug.StartsWith("wardrobe-")) + .Select(s => new { s.Slug, s.DisplayName }) + .ToListAsync(); + if (pool.Count == 0) return StatusCode(503, "no wardrobe items in catalogue"); + + var rng = new Random(); + var pick = pool[rng.Next(pool.Count)]; + var guid = pick.Slug["wardrobe-".Length..]; + + // Bare GUIDs crash AvatarItem.FromRecNetString — append `,,,` + // so the watch's parse-and-iterate doesn't blow up in + // GiftManager.DequeueGift. See AvatarItemsController.cs:31-40 + // for the canonical wire-format note. + var gift = new GiftPackageEntity + { + RecipientPlayerId = pid, + FromPlayerId = null, + AvatarItemType = 0, // Outfit + AvatarItemDescOrHairDyeDesc = guid + ",,,", + CurrencyType = 0, // Invalid (no currency in this gift) + GiftContext = req?.GiftContext ?? 0, + GiftRarity = RollRarity(rng), + Message = $"Enjoy your new {pick.DisplayName}!", + Platform = -1, + PackageVariant = "Standard", + PackageMaterial = string.Empty, + IsValid = true, + SupportsCurrentPlatform = true, + }; + db.GiftPackages.Add(gift); + await db.SaveChangesAsync(); + + // Push so the watch shows the new-gift toast. + await notifications.NotifyAsync(pid, + PushNotificationId.GiftPackageReceived, ToWire(gift)); + return Ok(ToWire(gift)); + } + + [HttpPost("api/avatar/v2/gifts/consume/{id:long}")] + public Task ConsumeViaPath(long id) => ConsumeImpl(id); + + /// POST /api/avatar/v2/gifts/consume/ (note trailing + /// slash, no id in path). The watch's LocalConsumeGiftPackage + /// (verified at Cpp2IL_ISIL/.../RecNet/Avatars_NestedType__LocalConsumeGiftPackage_d__19.txt:276-301) + /// posts to this exact URL with a form-urlencoded body containing + /// Id and UnlockedLevel fields. The previous + /// implementation took [FromBody] ConsumeGiftRequest which + /// only accepts application/json — the watch's form-encoded + /// body got 415 Unsupported Media Type + /// (output_log.txt:'Failed to consume gift … HTTP Error 415'), + /// which left the gift unconsumed → the avatar item never landed + /// in the player's inventory → mirror-unequip rendered a missing + /// item and crashed. + /// + /// Accept both JSON and form-urlencoded by reading the raw body + /// and pulling Id from whichever container we got. + [HttpPost("api/avatar/v2/gifts/consume")] + [Consumes("application/json", "application/x-www-form-urlencoded", "multipart/form-data", "text/plain")] + public async Task ConsumeViaBody() + { + long id = 0; + if (Request.HasFormContentType) + { + var form = await Request.ReadFormAsync(); + var raw = form["Id"].ToString(); + if (string.IsNullOrWhiteSpace(raw)) raw = form["id"].ToString(); + long.TryParse(raw, out id); + } + else + { + using var reader = new System.IO.StreamReader(Request.Body); + var body = await reader.ReadToEndAsync(); + if (!string.IsNullOrWhiteSpace(body)) + { + try + { + using var doc = System.Text.Json.JsonDocument.Parse(body); + if (doc.RootElement.TryGetProperty("Id", out var v) || + doc.RootElement.TryGetProperty("id", out v)) + { + if (v.ValueKind == System.Text.Json.JsonValueKind.Number) + v.TryGetInt64(out id); + else if (v.ValueKind == System.Text.Json.JsonValueKind.String) + long.TryParse(v.GetString(), out id); + } + } + catch (System.Text.Json.JsonException) { /* fall through; id stays 0 */ } + } + } + if (id == 0) + { + var raw = Request.Query["Id"].ToString(); + if (string.IsNullOrWhiteSpace(raw)) raw = Request.Query["id"].ToString(); + long.TryParse(raw, out id); + } + if (id == 0) return BadRequest("missing Id"); + return await ConsumeImpl(id); + } + + private async Task ConsumeImpl(long id) + { + var pid = this.RequireCurrentPlayerId(); + var gift = await db.GiftPackages.FirstOrDefaultAsync(g => g.Id == id); + if (gift is null) return NotFound(); + if (gift.RecipientPlayerId != pid) return Forbid(); + if (gift.Consumed) return Ok(ToWire(gift)); // idempotent + + // Append avatar item to player's inventory list (JSON list of + // GUID strings). Defensive parse — corrupted JSON resets to + // empty list rather than 500. + if (!string.IsNullOrEmpty(gift.AvatarItemDescOrHairDyeDesc)) + { + var avatar = await db.Avatars.FirstOrDefaultAsync(a => a.PlayerId == pid); + if (avatar is null) + { + avatar = new AvatarEntity { PlayerId = pid }; + db.Avatars.Add(avatar); + } + try + { + var list = System.Text.Json.JsonSerializer + .Deserialize>(avatar.InventoryJson) ?? new(); + if (!list.Contains(gift.AvatarItemDescOrHairDyeDesc)) + { + list.Add(gift.AvatarItemDescOrHairDyeDesc); + avatar.InventoryJson = System.Text.Json.JsonSerializer.Serialize(list); + avatar.UpdatedAt = DateTime.UtcNow; + } + } + catch + { + avatar.InventoryJson = System.Text.Json.JsonSerializer.Serialize( + new[] { gift.AvatarItemDescOrHairDyeDesc }); + avatar.UpdatedAt = DateTime.UtcNow; + } + } + + // Apply currency / XP / level rewards bundled with the gift. + // The previous implementation only handled the avatar item path, + // so token gifts from the admin SPA marked themselves consumed + // but never moved the player's wallet — the player tapped the + // gift, the box played its animation, and the balance stayed + // unchanged. CurrencyType is the canonical RecNet enum + // (1 = Tokens, 2 = Coins; 0 means "no currency in this gift"). + if (gift.CurrencyType > 0 && gift.Currency > 0) + { + await level.GrantCurrencyAsync(pid, gift.CurrencyType, gift.Currency, + $"gift_consumed:{gift.Id}"); + } + if (gift.Xp > 0) + { + await level.AwardXpAsync(pid, gift.Xp, $"gift_consumed:{gift.Id}"); + } + // Direct level grants bypass XP curves — same pattern + // /api/admin/v1/players/{id}/levelup uses. We don't surface + // that as a separate helper; bump the column inline. + if (gift.Level > 0) + { + var player = await db.Players.FirstOrDefaultAsync(p => p.Id == pid); + if (player is not null) player.Level += gift.Level; + } + + gift.Consumed = true; + gift.ConsumedAt = DateTime.UtcNow; + await db.SaveChangesAsync(); + + // Profile-update push so the watch's wallet readout refreshes + // without waiting for the next /balance poll. Mirrors what + // /api/admin/v1/players/{id}/gift does after the inbox push so + // the toast number animates up the moment the box closes. + if (gift.CurrencyType > 0 || gift.Xp > 0 || gift.Level > 0) + { + await notifications.NotifyAsync(pid, + PushNotificationId.SubscriptionUpdateProfile, + new { Reason = "GiftConsumed", gift.CurrencyType, gift.Currency, gift.Xp, gift.Level }); + } + + return Ok(ToWire(gift)); + } + + /// Weighted rarity roll — Common 65, Uncommon 25, + /// Rare 8, Epic 1.8, Legendary 0.2 percent. + private static int RollRarity(Random rng) => rng.NextDouble() switch + { + < 0.65 => 0, // Common + < 0.90 => 10, // Uncommon + < 0.98 => 20, // Rare + < 0.998 => 30, // Epic + _ => 50, // Legendary + }; + + // Wire shape verified against the 2020.12 watch's + // NLEKGNENMCO+LOCNECLOHCA gift DTO deserializer (Cpp2IL ISIL + // NLEKGNENMCO_NestedType_LOCNECLOHCA.txt:792-946). All keys read + // via Util.GetKey (mandatory): Id, Message, FromPlayerId, + // AvatarItemType, AvatarItemDesc, ConsumableItemDesc, + // EquipmentPrefabName, EquipmentModificationGuid, Platform, + // PlatformsToSpawnOn, BalanceType, CurrencyType, Currency, Xp, + // GiftRarity, GiftContext. Missing any one → KeyNotFoundException + // surfaces as "Failed to download gifts: Malformed Response" from + // BootSequence and BLOCKS LOGIN. + private static object ToWire(GiftPackageEntity g) => new + { + g.Id, + g.FromPlayerId, + g.ConsumableItemDesc, + g.AvatarItemType, + AvatarItemDesc = g.AvatarItemDescOrHairDyeDesc, + g.AvatarItemDescOrHairDyeDesc, + g.EquipmentPrefabName, + g.EquipmentModificationGuid, + g.CurrencyType, + g.Currency, + g.Xp, + g.Level, + g.GiftContext, + g.GiftRarity, + g.Message, + g.Platform, + // PlatformsToSpawnOn — required by the watch's gift DTO + // deserializer. This is a SINGLE int (4-byte field at offset + // 0x6C in LOCNECLOHCA, separate enum from Platform itself). + // The watch reads it with strict Util.GetKey, so sending + // an empty array crashes Convert.ChangeType([], typeof(int)) + // with "Object must implement IConvertible". -1 mirrors the + // sibling Platform field's "every platform" sentinel — no + // platform-gated gift restrictions on a private server. + PlatformsToSpawnOn = -1, + // BalanceType — required mandatory key on the gift DTO. The + // enum identifies whether the gift's payload is a Currency + // balance update (0) vs Inventory item (1) vs XP (2). For + // currency-bearing gifts we'd send 0; the safe default for + // our seed gifts (which are item-only) is 0 too since the + // watch only reads it to dispatch the balance-update side + // and we never carry one. Wrong type doesn't crash. + BalanceType = 0, + g.PackageMaterial, + g.PackageVariant, + g.Consumed, + g.IsValid, + g.ErrorMessage, + g.SupportsCurrentPlatform, + g.IsGifted, + }; +} diff --git a/DorkNet.Server/Controllers/API/Avatar/V2/AvatarV2Controller.cs b/DorkNet.Server/Controllers/API/Avatar/V2/AvatarV2Controller.cs new file mode 100644 index 0000000..4da8db9 --- /dev/null +++ b/DorkNet.Server/Controllers/API/Avatar/V2/AvatarV2Controller.cs @@ -0,0 +1,193 @@ +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Mvc; +using Microsoft.EntityFrameworkCore; +using Microsoft.Extensions.Logging; +using System.Text.Json; +using System.Text.Json.Serialization; +using DorkNet.Server.Auth; +using DorkNet.Server.Data; +using DorkNet.Server.Data.Entities; + +namespace DorkNet.Server.Controllers.API.Avatar.V2; + +/// +/// api.rec.net/api/avatar/v2 — older Avatar settings endpoint used by +/// the 2020 client (the V3 endpoint is for newer builds and uses a +/// different shape entirely). +/// +/// Wire shape verified by disassembling RecNet.Avatar.Deserialize at +/// RVA 0xFA3C30. Required keys (Util.GetKey<string>): OutfitSelections, +/// HairColor, SkinColor, FaceFeatures. Empty strings satisfy the +/// deserializer; the client interprets them as "use defaults" and falls +/// back to its built-in starter avatar. +/// +/// This implementation persists each set/get to +/// instead of stubbing — round-trip across sessions, multi-user-safe +/// (one row per player keyed by PlayerId). +/// +[ApiController] +[Authorize] +public class AvatarV2Controller(DorkNetDbContext db, ILogger logger) : ControllerBase +{ + [HttpGet("api/avatar/v2")] + public async Task> GetAvatar() + { + var pid = this.RequireCurrentPlayerId(); + var a = await db.Avatars.FirstOrDefaultAsync(x => x.PlayerId == pid); + return Ok(ToDto(a)); + } + + /// + /// POST /api/avatar/v2/set — replace the player's avatar settings. + /// + /// CRITICAL: do NOT use [FromBody] AvatarV2Dto here. The + /// watch's RecNet.Avatar.Serialize() emits each of + /// OutfitSelections / HairColor / SkinColor / FaceFeatures as + /// dictionary values whose runtime types are determined by the + /// IRecNetObject serializer it uses — FaceFeatures in particular + /// sometimes ships as a nested JSON OBJECT instead of a JSON + /// string, and the watch may also omit fields entirely when + /// they're empty. + /// + /// System.Text.Json's strict type binder responds to either of those + /// with a 400 (model state invalid → ApiController auto-400) and + /// the watch then retries every frame, accumulating "Failed to + /// upload Rec Room player avatar changes: HTTP Error 400" errors + /// (output_log.txt:852+, 1000+ repeats) until the player object + /// gets destroyed. + /// + /// Parse the body manually as JsonElement so we accept any shape + /// the watch sends: string-or-object for FaceFeatures (re-encode + /// the object as a string before persisting), missing fields (keep + /// existing values), null values (treat as empty). + /// + [HttpPost("api/avatar/v2")] + [HttpPost("api/avatar/v2/set")] + [Consumes("application/json", "application/x-www-form-urlencoded", "text/plain")] + public async Task> SetAvatar() + { + var pid = this.RequireCurrentPlayerId(); + + // Read the raw body so we control parsing — bypasses the + // model binder's strict type matching that was causing 400s. + string raw; + using (var reader = new System.IO.StreamReader(Request.Body)) + raw = await reader.ReadToEndAsync(); + + string? outfitSelections = null; + string? hairColor = null; + string? skinColor = null; + string? faceFeatures = null; + + if (!string.IsNullOrWhiteSpace(raw)) + { + try + { + using var doc = JsonDocument.Parse(raw); + var root = doc.RootElement; + if (root.ValueKind == JsonValueKind.Object) + { + outfitSelections = ReadStringField(root, "OutfitSelections"); + hairColor = ReadStringField(root, "HairColor"); + skinColor = ReadStringField(root, "SkinColor"); + faceFeatures = ReadStringField(root, "FaceFeatures"); + } + } + catch (JsonException ex) + { + // Fall through with all-null updates if the body isn't + // JSON. Log so we can see what the watch actually sends. + logger.LogWarning( + "[avatar-set] could not parse body as JSON ({Msg}); raw={Raw}", + ex.Message, raw.Length > 500 ? raw.Substring(0, 500) + "…" : raw); + } + } + + var a = await db.Avatars.FirstOrDefaultAsync(x => x.PlayerId == pid); + if (a is null) + { + a = new AvatarEntity { PlayerId = pid }; + db.Avatars.Add(a); + } + // Only overwrite fields the watch actually sent; missing keys + // keep the previously-stored value so a partial PATCH-style + // update doesn't accidentally wipe HairColor/SkinColor/etc. + if (outfitSelections is not null) a.OutfitSelections = outfitSelections; + if (hairColor is not null) a.HairColor = hairColor; + if (skinColor is not null) a.SkinColor = skinColor; + if (faceFeatures is not null) a.FaceFeatures = faceFeatures; + a.UpdatedAt = DateTime.UtcNow; + await db.SaveChangesAsync(); + return Ok(ToDto(a)); + } + + /// Read a field that should be a string but may arrive + /// as null, a JSON object/array (re-encoded back to a JSON + /// string), or be missing entirely (return null → caller keeps + /// the existing DB value). + private static string? ReadStringField(JsonElement root, string name) + { + if (!root.TryGetProperty(name, out var v)) return null; + return v.ValueKind switch + { + JsonValueKind.String => v.GetString() ?? string.Empty, + JsonValueKind.Null => string.Empty, + JsonValueKind.Object => v.GetRawText(), + JsonValueKind.Array => v.GetRawText(), + JsonValueKind.Number => v.GetRawText(), + JsonValueKind.True or JsonValueKind.False => v.GetRawText(), + _ => null, + }; + } + + /// Public lookup of another player's equipped avatar — the + /// client uses this to render remote players' avatars in shared + /// rooms. No auth requirement on read so anonymous calls in the + /// boot path don't 401. + [HttpGet("api/avatar/v2/equipped/{playerId:long}")] + [HttpGet("api/avatar/v3/equipped/{playerId:long}")] + [HttpGet("api/avatar/v4/equipped/{playerId:long}")] + [AllowAnonymous] + public async Task> GetEquippedFor(long playerId) + { + var a = await db.Avatars.FirstOrDefaultAsync(x => x.PlayerId == playerId); + return Ok(ToDto(a)); + } + + /// Bare api/avatar/v{2,3}/equipped returns the + /// caller's own equipped avatar. Without the bare path the watch's + /// "what am I wearing" query falls through to the catch-all and + /// gets []. + [HttpGet("api/avatar/v2/equipped")] + [HttpGet("api/avatar/v3/equipped")] + [HttpGet("api/avatar/v4/equipped")] + public async Task> GetEquippedSelf() + { + var pid = this.RequireCurrentPlayerId(); + var a = await db.Avatars.FirstOrDefaultAsync(x => x.PlayerId == pid); + return Ok(ToDto(a)); + } + + private static AvatarV2Dto ToDto(AvatarEntity? a) => new() + { + OutfitSelections = a?.OutfitSelections ?? string.Empty, + FaceFeatures = a?.FaceFeatures ?? string.Empty, + HairColor = a?.HairColor ?? string.Empty, + SkinColor = a?.SkinColor ?? string.Empty, + }; +} + +public class AvatarV2Dto +{ + [JsonPropertyName("OutfitSelections")] + public string OutfitSelections { get; set; } = string.Empty; + + [JsonPropertyName("HairColor")] + public string HairColor { get; set; } = string.Empty; + + [JsonPropertyName("SkinColor")] + public string SkinColor { get; set; } = string.Empty; + + [JsonPropertyName("FaceFeatures")] + public string FaceFeatures { get; set; } = string.Empty; +} diff --git a/DorkNet.Server/Controllers/API/Avatar/V3/AvatarSavedOutfitsController.cs b/DorkNet.Server/Controllers/API/Avatar/V3/AvatarSavedOutfitsController.cs new file mode 100644 index 0000000..7a81f7f --- /dev/null +++ b/DorkNet.Server/Controllers/API/Avatar/V3/AvatarSavedOutfitsController.cs @@ -0,0 +1,103 @@ +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Mvc; +using Microsoft.EntityFrameworkCore; +using System.Text.Json; +using System.Text.Json.Serialization; +using DorkNet.Server.Auth; +using DorkNet.Server.Data; +using DorkNet.Server.Data.Entities; + +namespace DorkNet.Server.Controllers.API.Avatar.V3; + +/// +/// api.rec.net/api/avatar/v{2,3}/saved — saved-outfit slots. Wire +/// type RecNet.SavedOutfit (per agent ISIL extraction): +/// Slot(int), PreviewImageName(string), OutfitSelections(string), +/// FaceFeatures(string), SkinColor(string), HairColor(string). +/// +/// State persists in as +/// a JSON list of records keyed by +/// Slot. Caps at 20 entries per player to keep the column under +/// the SQLite text-blob comfort zone. +/// +[ApiController] +[Authorize] +public class AvatarSavedOutfitsController(DorkNetDbContext db) : ControllerBase +{ + private const int MaxSlots = 20; + + [HttpGet("api/avatar/v2/saved")] + [HttpGet("api/avatar/v3/saved")] + [HttpGet("api/avatar/v4/saved")] + public async Task List() + { + var pid = this.RequireCurrentPlayerId(); + var avatar = await db.Avatars.FirstOrDefaultAsync(a => a.PlayerId == pid); + var list = ParseList(avatar?.SavedOutfitsJson); + return Ok(list); + } + + /// POST v3/saved/set — upsert one outfit by Slot. + /// Body is the SavedOutfit shape directly. + [HttpPost("api/avatar/v3/saved/set")] + [HttpPost("api/avatar/v2/saved/set")] + public async Task Set([FromBody] SavedOutfitDto body) + { + if (body is null) return BadRequest("missing body"); + var pid = this.RequireCurrentPlayerId(); + var avatar = await db.Avatars.FirstOrDefaultAsync(a => a.PlayerId == pid); + if (avatar is null) + { + avatar = new AvatarEntity { PlayerId = pid }; + db.Avatars.Add(avatar); + } + var list = ParseList(avatar.SavedOutfitsJson); + var idx = list.FindIndex(o => o.Slot == body.Slot); + if (idx >= 0) list[idx] = body; + else + { + if (list.Count >= MaxSlots) return BadRequest($"max {MaxSlots} saved outfits per player"); + list.Add(body); + } + avatar.SavedOutfitsJson = JsonSerializer.Serialize(list); + avatar.UpdatedAt = DateTime.UtcNow; + await db.SaveChangesAsync(); + return Ok(body); + } + + [HttpDelete("api/avatar/v3/saved/{slot:int}")] + [HttpDelete("api/avatar/v2/saved/{slot:int}")] + public async Task Delete(int slot) + { + var pid = this.RequireCurrentPlayerId(); + var avatar = await db.Avatars.FirstOrDefaultAsync(a => a.PlayerId == pid); + if (avatar is null) return NotFound(); + var list = ParseList(avatar.SavedOutfitsJson); + var removed = list.RemoveAll(o => o.Slot == slot); + if (removed == 0) return NotFound(); + avatar.SavedOutfitsJson = JsonSerializer.Serialize(list); + avatar.UpdatedAt = DateTime.UtcNow; + await db.SaveChangesAsync(); + return Ok(); + } + + private static List ParseList(string? json) + { + if (string.IsNullOrWhiteSpace(json)) return new(); + try + { + return JsonSerializer.Deserialize>(json) ?? new(); + } + catch { return new(); } + } +} + +public sealed class SavedOutfitDto +{ + [JsonPropertyName("Slot")] public int Slot { get; set; } + [JsonPropertyName("PreviewImageName")] public string PreviewImageName { get; set; } = string.Empty; + [JsonPropertyName("OutfitSelections")] public string OutfitSelections { get; set; } = string.Empty; + [JsonPropertyName("FaceFeatures")] public string FaceFeatures { get; set; } = string.Empty; + [JsonPropertyName("SkinColor")] public string SkinColor { get; set; } = string.Empty; + [JsonPropertyName("HairColor")] public string HairColor { get; set; } = string.Empty; +} diff --git a/DorkNet.Server/Controllers/API/Avatar/V4/AvatarItemsController.cs b/DorkNet.Server/Controllers/API/Avatar/V4/AvatarItemsController.cs new file mode 100644 index 0000000..09e2418 --- /dev/null +++ b/DorkNet.Server/Controllers/API/Avatar/V4/AvatarItemsController.cs @@ -0,0 +1,482 @@ +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Mvc; +using Microsoft.EntityFrameworkCore; +using Microsoft.Extensions.Configuration; +using System.Text.Json; +using System.Text.Json.Serialization; +using DorkNet.Server.Auth; +using DorkNet.Server.Data; +using DorkNet.Server.Services; + +namespace DorkNet.Server.Controllers.API.Avatar.V4; + +/// +/// api.rec.net/api/avatar/v{2,3,4}/items — list of avatar items the +/// caller has UNLOCKED. The watch's +/// RecNet.Avatars+<DowloadUnlockedAvatarItems> coroutine +/// (Cpp2IL_ISIL/.../Avatars_NestedType__DowloadUnlockedAvatarItems_d__21.txt:149 +/// hits "{api/avatar/}v4/items") parses the result as +/// List<UnlockedAvatarItem>; the wardrobe UI then +/// renders a "buy" button for any item NOT in this list and an +/// "equip" affordance for items that are. +/// +/// Wire shape per +/// Cpp2IL_ISIL/.../Avatars_NestedType_UnlockedAvatarItem.txt:186-212: +/// "AvatarItemType" (int — Outfit=0 / HairDye=1, NOT slot-based), +/// "AvatarItemDesc" (string — the item GUID with three empty +/// customizations appended, see below), +/// "PlatformMask" (int), "FriendlyName" (string), +/// "Tooltip" (string), "Rarity" (int). +/// +/// IMPORTANT — AvatarItemDesc format: the client passes this +/// string to AvatarItem.FromRecNetString(string) +/// (Cpp2IL_ISIL/.../RecRoom/Avatar/Data/Runtime/AvatarItem.txt:81-257). +/// The desc is String.Split(',') into guid + customization[0..3] +/// — the inner overload then indexes customization[0], +/// customization[1], customization[2] and throws +/// ArgumentException at ArraySegment.get_Current if the +/// segment is shorter than 3. Every desc we ship MUST therefore have +/// at least 4 comma-separated parts, so we suffix bare GUIDs with +/// ",,," (three empty customizations). +/// +/// CATALOG SOURCE — data/avatar_item_lookup.json. Generated by +/// the manual/CI-only BepInEx patch under +/// tools/AvatarCatalogDumpPatch. That patch reads the watch's bundled +/// AvatarItemWardrobeRuntimeConfig.avatarItemDataLookup +/// SerializedDictionary at boot and writes every (guid, AvatarItemData) +/// pair to JSON. THIS is the authoritative safe list — any GUID NOT +/// in this file throws InvalidOperationException("Missing data +/// for guid …") inside the watch's wardrobe parser, which aborts +/// the entire coroutine on the first bad item and leaves the drawer +/// empty. Items extracted from outfits_assets_*.bundle +/// MonoBehaviours (the older data/wardrobe_items.json) are +/// MASK + SWATCH asset GUIDs, NOT AvatarItem GUIDs, so that file +/// can't be used as a safelist. +/// +/// PER-PLAYER ownership: the watch intersects each store tile's +/// AvatarItemDesc against THIS list to flip the buy-button +/// to "Already Owned." Returning the global wardrobe here therefore +/// makes every store item appear owned. We instead intersect the +/// caller's with the master +/// catalog so the watch sees only items the player actually owns. +/// Anonymous calls return []. +/// +[ApiController] +public class AvatarItemsController(DorkNetDbContext db, IConfiguration config) : ControllerBase +{ + /// Master catalog keyed by GUID. Loaded once on first + /// request from data/avatar_item_lookup.json. Same + /// lazy-load idiom as before but the catalog itself is now a + /// dump of the watch's bundled + /// avatarItemDataLookup instead of the asset-bundle + /// MonoBehaviour scan in the previous wardrobe_items.json. + private static readonly Lazy _catalog = new(LoadCatalog); + + [HttpGet("api/avatar/v2/items")] + [HttpGet("api/avatar/v3/items")] + [HttpGet("api/avatar/v4/items")] + [Authorize] + public async Task>> Get() + { + // Keep this switch as a fast kill-switch, but default it on now that + // the response is bounded below. The risky path is not the parser + // itself; it is feeding the 2020 watch too many BrowsableAvatarItem / + // DynamicAvatarItemImposter rows at once. + if (!config.GetValue("Avatar:EnableUnlockedItems", true)) + return Ok(new List()); + + var maxItems = Math.Clamp(config.GetValue("Avatar:MaxUnlockedItems", 30), 1, 120); + var maxPerSlot = Math.Clamp(config.GetValue("Avatar:MaxUnlockedItemsPerSlot", 4), 1, 20); + + var pid = this.CurrentPlayerId(); + if (pid is not long me) return Ok(new List()); + + var avatar = await db.Avatars + .Where(a => a.PlayerId == me) + .Select(a => new { a.InventoryJson, a.OutfitSelections }) + .FirstOrDefaultAsync(); + + var owned = ParseInventory(avatar?.InventoryJson); + var ownedHairDyeSlugs = await db.PlayerInventory + .Where(i => i.PlayerId == me && i.Quantity > 0) + .Select(i => i.ItemSlug) + .ToListAsync(); + var ownedHairDyes = ownedHairDyeSlugs + .Where(slug => StoreService.TryGetHairDyePayload(slug, out _)) + .Distinct(StringComparer.OrdinalIgnoreCase) + .ToList(); + if (owned.Count == 0 && ownedHairDyes.Count == 0) return Ok(new List()); + + var catalog = _catalog.Value; + var equipped = ParseEquippedItemGuids(avatar?.OutfitSelections); + var ordered = owned + .OrderByDescending(guid => equipped.Contains(guid)) + .ThenBy(guid => catalog.SlotByGuid.TryGetValue(guid, out var slot) ? slot : int.MaxValue) + .ThenBy(guid => guid, StringComparer.OrdinalIgnoreCase); + + var maxOutfitItems = Math.Max(0, maxItems - ownedHairDyes.Count); + var perSlot = new Dictionary(); + var result = new List(Math.Min(owned.Count, maxItems)); + foreach (var guid in ordered) + { + if (result.Count >= maxOutfitItems) break; + // Intersect against the safelist. Items not in the bundled + // lookup would crash the watch's wardrobe parser, so they're + // silently dropped here. + if (!catalog.Items.TryGetValue(guid, out var item)) continue; + + var slot = catalog.SlotByGuid.TryGetValue(guid, out var value) ? value : -1; + perSlot.TryGetValue(slot, out var count); + if (count >= maxPerSlot && !equipped.Contains(guid)) continue; + + perSlot[slot] = count + 1; + result.Add(item); + if (result.Count >= maxItems) break; + } + + foreach (var slug in ownedHairDyes) + { + if (result.Count >= maxItems) break; + if (!StoreService.TryGetHairDyePayload(slug, out var hairColorGuid)) continue; + StoreService.TryGetHairDyeName(slug, out var friendlyName); + result.Add(new UnlockedAvatarItemDto + { + AvatarItemType = AvatarItemType.HairDye, + AvatarItemDesc = hairColorGuid, + PlatformMask = unchecked((int)0xFFFFFFFF), + FriendlyName = friendlyName, + Tooltip = string.Empty, + Rarity = 0, + }); + } + return Ok(result); + } + + /// The set of GUIDs the bundled + /// AvatarItemWardrobeRuntimeConfig.avatarItemDataLookup + /// contains. Exposed so other controllers (StoreService seed, + /// PlayerService starter inventory) can validate their seed + /// data against the same safe list. + public static IReadOnlySet SafeGuids => _catalog.Value.SafeGuids; + + /// Master catalog of all renderable items, keyed by + /// GUID. Used by callers that need item metadata (slot type, + /// name, hair-color flags) outside of the per-player ownership + /// check. + public static IReadOnlyDictionary Catalog + => _catalog.Value.Items; + + /// GET api/avatar/v1/lockeditems — metadata for + /// specific avatar item descriptions the caller does not own. The + /// store UI calls this with item descs from purchasable rows and + /// expects the same item-detail shape used by unlocked items, + /// minus ownership. We resolve against the safe runtime catalog so + /// unknown GUIDs never reach the client parser. + [HttpGet("api/avatar/v1/lockeditems")] + [AllowAnonymous] + public async Task LockedItems() + { + var requested = RequestedAvatarItemGuids().ToList(); + if (requested.Count == 0) return Ok(Array.Empty()); + + var owned = new HashSet(StringComparer.OrdinalIgnoreCase); + var pid = this.CurrentPlayerId(); + if (pid is long me) + { + var invJson = await db.Avatars + .Where(a => a.PlayerId == me) + .Select(a => a.InventoryJson) + .FirstOrDefaultAsync(); + foreach (var guid in ParseInventory(invJson)) + owned.Add(guid); + } + + var catalog = _catalog.Value.Items; + var rows = requested + .Where(guid => !owned.Contains(guid)) + .Distinct(StringComparer.OrdinalIgnoreCase) + .Select(guid => catalog.TryGetValue(guid, out var item) ? item : null) + .Where(item => item is not null) + .Select(item => new + { + item!.AvatarItemType, + item.AvatarItemDesc, + item.FriendlyName, + item.Tooltip, + item.Rarity, + }) + .ToArray(); + return Ok(rows); + } + + /// Per-OutfitType slot index → list of GUIDs in that slot. + /// Used by to + /// build a starter loadout (one per slot) and a starter inventory + /// (a few per slot). + public static IReadOnlyDictionary> BySlot + => _catalog.Value.BySlot; + + public static bool TryGetOutfitType(string guid, out int outfitType) => + _catalog.Value.SlotByGuid.TryGetValue(guid, out outfitType); + + private IEnumerable RequestedAvatarItemGuids() + { + foreach (var pair in Request.Query) + foreach (var raw in pair.Value) + foreach (var part in (raw ?? string.Empty).Split(';', StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries)) + { + var value = part.Trim(); + if (string.IsNullOrWhiteSpace(value)) continue; + var comma = value.IndexOf(','); + if (comma >= 0) value = value[..comma]; + if (Guid.TryParse(value, out _)) yield return value; + } + } + + private static List ParseInventory(string? json) + { + if (string.IsNullOrWhiteSpace(json)) return new(); + try + { + var strings = JsonSerializer.Deserialize>(json); + if (strings is not null) return strings; + } + catch { } + + try + { + using var doc = JsonDocument.Parse(json); + if (doc.RootElement.ValueKind != JsonValueKind.Array) return new(); + + var result = new List(); + foreach (var element in doc.RootElement.EnumerateArray()) + { + string? value = null; + if (element.ValueKind == JsonValueKind.String) + value = element.GetString(); + else if (element.ValueKind == JsonValueKind.Object && + element.TryGetProperty("itemId", out var itemId) && + itemId.ValueKind == JsonValueKind.String) + value = itemId.GetString(); + + if (string.IsNullOrWhiteSpace(value)) continue; + const string wardrobePrefix = "wardrobe-"; + if (value.StartsWith(wardrobePrefix, StringComparison.Ordinal)) + value = value[wardrobePrefix.Length..]; + var comma = value.IndexOf(','); + if (comma >= 0) + value = value[..comma]; + result.Add(value); + } + + return result + .Distinct(StringComparer.OrdinalIgnoreCase) + .ToList(); + } + catch { return new(); } + } + + private static HashSet ParseEquippedItemGuids(string? outfitSelections) + { + var result = new HashSet(StringComparer.OrdinalIgnoreCase); + if (string.IsNullOrWhiteSpace(outfitSelections)) return result; + + foreach (var selection in outfitSelections.Split(';', StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries)) + { + var comma = selection.IndexOf(','); + var guid = comma >= 0 ? selection[..comma] : selection; + if (!string.IsNullOrWhiteSpace(guid)) + result.Add(guid.Trim()); + } + + return result; + } + + private sealed record CatalogData( + IReadOnlyDictionary Items, + IReadOnlySet SafeGuids, + IReadOnlyDictionary> BySlot, + IReadOnlyDictionary SlotByGuid); + + private static CatalogData LoadCatalog() + { + // Try both lowercase 'data/' (csproj None+Link target) and + // uppercase 'Data/' (git-tracked source folder) so case-sensitivity + // mismatches between Windows (where the dev wrote the path) and + // Linux (where Coolify builds the container) can't lose the lookup + // file. Without this fallback, a single missed file → empty + // SafeGuids → StoreService.SeedAsync skips its stale-row cleanup + // → 418 swatch-GUID rows ship live → every store tile renders as + // Hat_Angler. + var candidates = new[] + { + Path.Combine(AppContext.BaseDirectory, "data", "avatar_item_lookup.json"), + Path.Combine(AppContext.BaseDirectory, "Data", "avatar_item_lookup.json"), + Path.Combine(Directory.GetCurrentDirectory(), "data", "avatar_item_lookup.json"), + Path.Combine(Directory.GetCurrentDirectory(), "Data", "avatar_item_lookup.json"), + }; + var path = candidates.FirstOrDefault(System.IO.File.Exists); + Console.WriteLine(path is null + ? $"[avatar-catalog] avatar_item_lookup.json not found in any of: {string.Join(", ", candidates)}" + : $"[avatar-catalog] loading from {path}"); + if (path is null) return new CatalogData( + new Dictionary(), + new HashSet(), + new Dictionary>(), + new Dictionary()); + + using var fs = System.IO.File.OpenRead(path); + var dump = JsonSerializer.Deserialize(fs, + new JsonSerializerOptions { PropertyNameCaseInsensitive = true }) + ?? new LookupFile(); + + var items = new Dictionary(dump.Items.Count, StringComparer.OrdinalIgnoreCase); + var safe = new HashSet(StringComparer.OrdinalIgnoreCase); + var bySlot = new Dictionary>(); + var slotByGuid = new Dictionary(StringComparer.OrdinalIgnoreCase); + + foreach (var row in dump.Items) + { + if (string.IsNullOrEmpty(row.Guid)) continue; + safe.Add(row.Guid); + // Prefer the baked display_name from the JSON if present + // (tools/add-display-names.py writes it once per refresh). + // Falls back to the raw-name cleanup if the field is missing + // — e.g. partial dumps or older fixture files. + var pretty = !string.IsNullOrEmpty(row.DisplayName) + ? row.DisplayName + : PrettyName(row.Name); + items[row.Guid] = new UnlockedAvatarItemDto + { + // AvatarItemType is the wire enum the watch parses + // (Outfit=0 / HairDye=1), not the slot. Rows in + // avatarItemDataLookup are wearable avatar items, including + // hair meshes that *use* hair color. They must still be sent + // as Outfit; sending HairDye makes the client treat the item + // GUID as a color GUID and log "Can not find hair dye color". + AvatarItemType = AvatarItemType.Outfit, + AvatarItemDesc = row.Guid + ",,,", + PlatformMask = unchecked((int)0xFFFFFFFF), + FriendlyName = pretty, + Tooltip = string.Empty, + Rarity = 0, + }; + if (!bySlot.TryGetValue(row.OutfitType, out var lst)) + bySlot[row.OutfitType] = lst = new List(); + lst.Add(row.Guid); + slotByGuid[row.Guid] = row.OutfitType; + } + + return new CatalogData(items, safe, bySlot, slotByGuid); + } + + private static string PrettyName(string raw) + { + if (string.IsNullOrEmpty(raw)) return string.Empty; + // Strip leading/trailing parens that the bundled config wraps + // names in, then turn underscores into spaces. Keep it simple — + // the watch shows whatever string we send. + var trimmed = raw.Trim().TrimStart('(').TrimEnd(')'); + // Drop common asset-name suffixes. Items in the runtime config + // use the raw asset name (e.g. "Hat_Wizard", "shirt_sherlock"); + // the watch never normalises these, so we do it server-side. + var s = trimmed.Replace('_', ' ').Trim(); + return s; + } + + /// Shape of data/avatar_item_lookup.json as written + /// by tools/AvatarCatalogDumpPatch. + private sealed class LookupFile + { + [JsonPropertyName("items")] + public List Items { get; set; } = new(); + + // Auxiliary GUID lists (prefab/mask/swatch/decal). Not used + // server-side yet — they'd matter for resolving asset + // references the watch might subsequently request, but the + // wire protocol only ever ships the AvatarItem GUID. + [JsonPropertyName("prefab_guids")] + public List PrefabGuids { get; set; } = new(); + [JsonPropertyName("mask_guids")] + public List MaskGuids { get; set; } = new(); + [JsonPropertyName("swatch_guids")] + public List SwatchGuids { get; set; } = new(); + [JsonPropertyName("decal_guids")] + public List DecalGuids { get; set; } = new(); + } + + private sealed class LookupItem + { + [JsonPropertyName("guid")] public string Guid { get; set; } = string.Empty; + [JsonPropertyName("name")] public string Name { get; set; } = string.Empty; + // Algorithmically derived from `name` by + // tools/add-display-names.py — Rec Room's official UI strings + // live in Unity asset bundles we can't read from a code dump, + // so this is a best-effort cleanup. Optional — server falls + // back to PrettyName() if missing. + [JsonPropertyName("display_name")] public string? DisplayName { get; set; } + [JsonPropertyName("outfit_type")] public int OutfitType { get; set; } + [JsonPropertyName("outfit_set")] public int OutfitSet { get; set; } + [JsonPropertyName("drawer_index")] public int DrawerIndex { get; set; } + [JsonPropertyName("hides_hair")] public bool HidesHair { get; set; } + [JsonPropertyName("hides_hand")] public bool HidesHand { get; set; } + // hides_left_ear / hides_right_ear were added to AvatarItemData + // in the 2020.12.18 client build. Optional in the JSON so older + // dumps still deserialize. + [JsonPropertyName("hides_left_ear")] public bool HidesLeftEar { get; set; } + [JsonPropertyName("hides_right_ear")] public bool HidesRightEar { get; set; } + [JsonPropertyName("uses_hair_color")] public bool UsesHairColor { get; set; } + [JsonPropertyName("uses_team_color")] public bool UsesTeamColor { get; set; } + [JsonPropertyName("is_team_item")] public bool IsTeamItem { get; set; } + } + + /// Mirrors RecNet.Avatars+AvatarItemType + /// (Avatars.cs:186-190). + private static class AvatarItemType + { + public const int Outfit = 0; + public const int HairDye = 1; + } + + /// OutfitType slot enum mirrored from + /// RecRoom.Avatar.Data.Shared.OutfitType. Used by other + /// callers (PlayerService, StoreService) for slot-aware seeding. + public static class Slot + { + public const int Hat = 0; + public const int BackHead = 1; + public const int Hair = 2; + public const int Eye = 10; + public const int Mouth = 20; + public const int Neck = 100; + public const int Shirt = 101; + public const int Belt = 102; + public const int Pocket = 103; + public const int TeamShirt = 104; + public const int Wrist = 200; + public const int Glove = 201; + public const int Watch = 202; + public const int TeamWrist = 203; + } +} + +public class UnlockedAvatarItemDto +{ + [JsonPropertyName("AvatarItemType")] + public int AvatarItemType { get; set; } + + [JsonPropertyName("AvatarItemDesc")] + public string AvatarItemDesc { get; set; } = string.Empty; + + [JsonPropertyName("PlatformMask")] + public int PlatformMask { get; set; } + + [JsonPropertyName("FriendlyName")] + public string FriendlyName { get; set; } = string.Empty; + + [JsonPropertyName("Tooltip")] + public string Tooltip { get; set; } = string.Empty; + + [JsonPropertyName("Rarity")] + public int Rarity { get; set; } +} diff --git a/DorkNet.Server/Controllers/API/BugReporting/BugReportsController.cs b/DorkNet.Server/Controllers/API/BugReporting/BugReportsController.cs new file mode 100644 index 0000000..1bef8b8 --- /dev/null +++ b/DorkNet.Server/Controllers/API/BugReporting/BugReportsController.cs @@ -0,0 +1,88 @@ +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Mvc; +using Microsoft.EntityFrameworkCore; +using DorkNet.Server.Auth; +using DorkNet.Server.Data; +using DorkNet.Server.Data.Entities; + +namespace DorkNet.Server.Controllers.API.BugReporting; + +/// +/// api.rec.net/api/bugreporting/* — in-game "Report a Bug" UI. +/// Wire request DTO matches RecNet.BugReporting+BugReportDTO +/// (Cpp2IL_CS/.../RecNet/BugReporting.cs): Summary, +/// Description, TestCaseKey, BuildVersion, BuildTimestamp, +/// BundleVersionCode. +/// +/// Plus POST /api/bugreporting/v1/submit (legacy v1 path) and +/// POST /api/bugreporting/v2/submit (renamed v2). The +/// ReportBug client API also includes screenshot + log byte +/// arrays as multipart parts; we accept-and-discard those (no +/// storage flow yet) but persist the text fields. +/// +[ApiController] +[Authorize] +public class BugReportsController(DorkNetDbContext db) : ControllerBase +{ + private long Me => this.RequireCurrentPlayerId(); + + public sealed class BugReportRequest + { + public string? Summary { get; set; } + public string? Description { get; set; } + public string? TestCaseKey { get; set; } + public string? BuildVersion { get; set; } + public long? BuildTimestamp { get; set; } + public int? BundleVersionCode { get; set; } + } + + [HttpPost("api/bugreporting/v1/submit")] + [HttpPost("api/bugreporting/v2/submit")] + [HttpPost("api/bugreporting/v1/reportbug")] + [HttpPost("api/bugreporting/v2/reportbug")] + [Consumes("application/json")] + public async Task SubmitJson([FromBody] BugReportRequest req) + => await PersistAsync(req); + + /// Multipart form variant — the watch posts screenshot + /// + log as separate parts. We pull the text-only fields and + /// drop the binary attachments. + [HttpPost("api/bugreporting/v1/submit-multipart")] + [HttpPost("api/bugreporting/v2/reportbug-multipart")] + [Consumes("multipart/form-data")] + public async Task SubmitMultipart( + [FromForm(Name = "Summary")] string? summary, + [FromForm(Name = "Description")] string? description, + [FromForm(Name = "TestCaseKey")] string? testCaseKey, + [FromForm(Name = "BuildVersion")] string? buildVersion, + [FromForm(Name = "BuildTimestamp")] long? buildTimestamp) + => await PersistAsync(new BugReportRequest + { + Summary = summary, + Description = description, + TestCaseKey = testCaseKey, + BuildVersion = buildVersion, + BuildTimestamp = buildTimestamp, + }); + + private async Task PersistAsync(BugReportRequest req) + { + var pid = Me; + var summary = (req.Summary ?? string.Empty).Trim(); + var body = (req.Description ?? string.Empty).Trim(); + if (summary.Length == 0 && body.Length == 0) + return BadRequest(new { Error = "summary or description required" }); + + db.BugReports.Add(new BugReportEntity + { + ReporterPlayerId = pid, + Title = summary[..Math.Min(128, summary.Length)], + Body = body[..Math.Min(4000, body.Length)], + ClientVersion = req.BuildVersion ?? string.Empty, + Platform = HttpContext.Request.Headers.UserAgent.ToString()[..Math.Min(32, HttpContext.Request.Headers.UserAgent.ToString().Length)], + Category = string.IsNullOrEmpty(req.TestCaseKey) ? "bug" : $"bug:{req.TestCaseKey}", + }); + await db.SaveChangesAsync(); + return Ok(new { Submitted = true }); + } +} diff --git a/DorkNet.Server/Controllers/API/Cards/CardsController.cs b/DorkNet.Server/Controllers/API/Cards/CardsController.cs new file mode 100644 index 0000000..7290576 --- /dev/null +++ b/DorkNet.Server/Controllers/API/Cards/CardsController.cs @@ -0,0 +1,40 @@ +using Microsoft.AspNetCore.Mvc; +using Microsoft.EntityFrameworkCore; +using DorkNet.Server.Auth; +using DorkNet.Server.Data; + +namespace DorkNet.Server.Controllers.API.Cards; + +/// +/// api.{rec.net,localhost}/api/cards/v1/all — home-screen card panel +/// (announcements, promos, daily-bonus tiles). Returns global cards +/// PLUS any cards targeted at the caller, sorted by Priority then +/// CreatedAt. Expired cards filtered out server-side. +/// +[ApiController] +public class CardsController(DorkNetDbContext db) : ControllerBase +{ + [HttpGet("api/cards/v1/all")] + public async Task All() + { + var pid = this.CurrentPlayerId(); + var now = DateTime.UtcNow; + var rows = await db.Cards + .Where(c => (c.PlayerId == null || c.PlayerId == pid) && + (c.ExpiresAt == null || c.ExpiresAt > now)) + .OrderByDescending(c => c.Priority).ThenByDescending(c => c.CreatedAt) + .Take(50) + .Select(c => new + { + c.Id, + c.Title, + c.Body, + c.ImageName, + c.ActionUrl, + c.Category, + c.CreatedAt, + }) + .ToListAsync(); + return Ok(rows); + } +} diff --git a/DorkNet.Server/Controllers/API/CommunityBoard/CommunityBoardController.cs b/DorkNet.Server/Controllers/API/CommunityBoard/CommunityBoardController.cs new file mode 100644 index 0000000..6115c83 --- /dev/null +++ b/DorkNet.Server/Controllers/API/CommunityBoard/CommunityBoardController.cs @@ -0,0 +1,99 @@ +using Microsoft.AspNetCore.Mvc; +using Microsoft.EntityFrameworkCore; +using DorkNet.Server.Data; +using DorkNet.Server.Services; + +namespace DorkNet.Server.Controllers.API.CommunityBoard; + +/// +/// api.rec.net/api/communityboard/v1/current — backs the dorm's +/// community board. The watch's +/// RecNet.CommunityBoard.GetCurrent hits +/// "{api/communityboard/}v1/current" +/// (Cpp2IL_ISIL/.../CommunityBoard.txt:902) and parses the response as +/// a CommunityBoardDTO with FeaturedPlayer / FeaturedRoomGroup / +/// CurrentAnnouncement / InstagramImages / Videos +/// (CommunityBoard_NestedType_CommunityBoardDTO.txt:460-480). +/// +/// State lives in as a single +/// DB row keyed at CommunityBoardRows.Id = 1. Admins update it +/// via POST /api/admin/v1/communityboard from the admin UI. +/// +[ApiController] +public class CommunityBoardController( + CommunityBoardService board, + DorkNetDbContext db, + DomainConfig domain) : ControllerBase +{ + [HttpGet("/api/communityboard/v1/current")] + [HttpGet("/api/communityboard/v2/current")] + public async Task Current() + { + var state = await board.GetAsync(); + + // InstagramImage URL backfill — the watch's + // InstagramImageDTO.Deserialize calls a string-typed GetKey for + // both ImageName AND ImageUrl + // (CommunityBoard_NestedType_InstagramImageDTO.txt:85-91); an + // empty URL yields a broken tile. The admin SPA persists only + // ImageName, so we derive the URL on read against the configured + // deployment apex (DORKNET_DOMAIN). + var instagram = state.InstagramImages.Select(img => new + { + ImageName = img.ImageName, + ImageUrl = string.IsNullOrEmpty(img.ImageUrl) && !string.IsNullOrEmpty(img.ImageName) + ? $"https://{domain.Sub("img")}/{img.ImageName}" + : img.ImageUrl, + }).ToArray(); + + // FeaturedRoomGroup expansion — the watch's FeaturedRoomGroupDTO + // (Rooms_NestedType_FeaturedRoomGroupDTO.txt:75-93) reads + // FeaturedRooms as List<FeaturedRoom> where each + // FeaturedRoom is {RoomName, RoomId, ImageName} with + // RoomId required (Util.GetKey at FeaturedRoom.txt:415-417). + // The admin SPA persists the list as a bare List<long> + // of room ids — which the watch can't deserialize, throwing + // KeyNotFoundException for "RoomId" on each item. With the list + // throwing, GetCurrentCommunityBoardData fails and the whole + // community-board panel renders empty. Expand the ids to full + // FeaturedRoom shapes here so the wire payload deserialises. + object? featuredRoomGroup = null; + if (state.FeaturedRoomGroup is { } group) + { + var ids = group.FeaturedRooms.ToList(); + var rooms = ids.Count == 0 + ? new() + : await db.Rooms + .Where(r => ids.Contains(r.Id)) + .Select(r => new { r.Id, r.Name, r.ImageName }) + .ToDictionaryAsync(r => r.Id); + // 2020.12 FeaturedRoomGroup deserializer (NMPFCIJPODA.PPGFHEDFBEA) + // reads required keys: FeaturedRoomGroupId, Name, Rooms (NOT + // FeaturedRooms). Missing/renamed → KeyNotFoundException → + // breaks the boot promise chain on the community-board fetch. + featuredRoomGroup = new + { + FeaturedRoomGroupId = 0L, + Name = group.Name ?? string.Empty, + Rooms = ids.Select(id => rooms.TryGetValue(id, out var r) + ? new { RoomName = r.Name, RoomId = r.Id, ImageName = r.ImageName ?? string.Empty } + : new { RoomName = string.Empty, RoomId = id, ImageName = string.Empty }) + .ToArray(), + // Keep the old key around for any 2020.03 clients hitting prod. + FeaturedRooms = ids.Select(id => rooms.TryGetValue(id, out var r) + ? new { RoomName = r.Name, RoomId = r.Id, ImageName = r.ImageName ?? string.Empty } + : new { RoomName = string.Empty, RoomId = id, ImageName = string.Empty }) + .ToArray(), + }; + } + + return Ok(new + { + FeaturedPlayer = state.FeaturedPlayer, + FeaturedRoomGroup = featuredRoomGroup, + CurrentAnnouncement = state.CurrentAnnouncement, + InstagramImages = instagram, + Videos = state.Videos, + }); + } +} diff --git a/DorkNet.Server/Controllers/API/Compatibility/CompatibilityFeatureController.cs b/DorkNet.Server/Controllers/API/Compatibility/CompatibilityFeatureController.cs new file mode 100644 index 0000000..4cbeaac --- /dev/null +++ b/DorkNet.Server/Controllers/API/Compatibility/CompatibilityFeatureController.cs @@ -0,0 +1,157 @@ +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Mvc; +using Microsoft.EntityFrameworkCore; +using System.Security.Cryptography; +using System.Text.Json; +using DorkNet.Server.Auth; +using DorkNet.Server.Data; +using DorkNet.Server.Data.Entities; + +namespace DorkNet.Server.Controllers.API.Compatibility; + +[ApiController] +public class CompatibilityFeatureController(DorkNetDbContext db) : ControllerBase +{ + [HttpPost("api/banappeal/generateCode")] + [Authorize] + public async Task GenerateBanAppealCode() + { + var pid = this.RequireCurrentPlayerId(); + var code = $"BA-{RandomNumberGenerator.GetInt32(100000, 999999)}"; + db.PlayerSettings.Add(new PlayerSettingEntity + { + PlayerId = pid, + Key = $"banappeal:{DateTime.UtcNow:yyyyMMddHHmmss}", + Value = code, + }); + await db.SaveChangesAsync(); + return Ok(new { value = code }); + } + + [HttpPost("api/CampusCard/v1/UpdateAndGetSubscription")] + [Authorize] + [Consumes("application/json", "application/x-www-form-urlencoded", "multipart/form-data")] + public async Task UpdateAndGetCampusCardSubscription() + { + var pid = this.RequireCurrentPlayerId(); + var req = await ReadCampusCardRequestAsync(); + var targetId = req.PlatformAccountSubscribedPlayerId > 0 + ? req.PlatformAccountSubscribedPlayerId + : pid; + + if (targetId != pid && req.Subscription) + { + var exists = await db.Subscriptions.AnyAsync(s => + s.SubscriberPlayerId == pid && s.TargetPlayerId == targetId); + if (!exists) + { + db.Subscriptions.Add(new SubscriptionEntity + { + SubscriberPlayerId = pid, + TargetPlayerId = targetId, + }); + await db.SaveChangesAsync(); + } + } + else if (targetId != pid && !req.Subscription) + { + await db.Subscriptions + .Where(s => s.SubscriberPlayerId == pid && s.TargetPlayerId == targetId) + .ExecuteDeleteAsync(); + } + + // 2020.12 deserializer for this response expects `subscription` to be a + // nested OBJECT (Subscription DTO with subscriptionId/recNetPlayerId/... + // fields), NOT a boolean. Sending a bool throws InvalidCastException + // on the client ("Unable to cast object of type 'Boolean' to type + // 'Dictionary`2'"). Send null when there's no active subscription — + // the client's generic-cast helper returns default(T) which is fine. + return Ok(new + { + subscription = (object?)null, + platformAccountSubscribedPlayerId = targetId, + }); + } + + [HttpPost("api/clubreporting/v1/report")] + [Authorize] + [Consumes("application/json", "application/x-www-form-urlencoded", "multipart/form-data")] + public async Task ClubReport() + { + var pid = this.RequireCurrentPlayerId(); + var req = await ReadClubReportAsync(); + var club = req.ClubId > 0 + ? await db.Clubs.FirstOrDefaultAsync(c => c.Id == req.ClubId) + : null; + if (club is null) return Ok(new { Success = false, Message = "club_not_found" }); + + var message = $"Club report: {req.Message ?? string.Empty}".Trim(); + if (message.Length > 1000) message = message[..1000]; + db.Reports.Add(new ReportEntity + { + ReporterPlayerId = pid, + TargetPlayerId = club.CreatorPlayerId, + Category = req.ReportCategory, + Message = message, + }); + await db.SaveChangesAsync(); + return Ok(new { Success = true, Message = string.Empty }); + } + + private async Task ReadCampusCardRequestAsync() + { + if (Request.HasFormContentType) + { + var form = await Request.ReadFormAsync(); + return new CampusCardRequest + { + Subscription = bool.TryParse(form["subscription"].FirstOrDefault(), out var s) && s, + PlatformAccountSubscribedPlayerId = long.TryParse( + form["platformAccountSubscribedPlayerId"].FirstOrDefault(), out var id) ? id : 0, + }; + } + try + { + return await JsonSerializer.DeserializeAsync( + Request.Body, + new JsonSerializerOptions { PropertyNameCaseInsensitive = true }) ?? new CampusCardRequest(); + } + catch (JsonException) { return new CampusCardRequest(); } + } + + private async Task ReadClubReportAsync() + { + if (Request.HasFormContentType) + { + var form = await Request.ReadFormAsync(); + return new ClubReportRequest + { + ClubId = long.TryParse(form["clubId"].FirstOrDefault() + ?? form["ClubId"].FirstOrDefault(), out var clubId) ? clubId : 0, + ReportCategory = int.TryParse(form["reportCategory"].FirstOrDefault() + ?? form["ReportCategory"].FirstOrDefault(), out var cat) ? cat : 5, + Message = form["message"].FirstOrDefault() ?? form["Message"].FirstOrDefault(), + }; + } + try + { + return await JsonSerializer.DeserializeAsync( + Request.Body, + new JsonSerializerOptions { PropertyNameCaseInsensitive = true }) ?? new ClubReportRequest(); + } + catch (JsonException) { return new ClubReportRequest(); } + } + + private sealed class CampusCardRequest + { + public bool Subscription { get; set; } + public long PlatformAccountSubscribedPlayerId { get; set; } + } + + private sealed class ClubReportRequest + { + public long ClubId { get; set; } + public int ReportCategory { get; set; } = 5; + public string? Message { get; set; } + } +} diff --git a/DorkNet.Server/Controllers/API/Config/V1/ConfigController.cs b/DorkNet.Server/Controllers/API/Config/V1/ConfigController.cs new file mode 100644 index 0000000..c2de06f --- /dev/null +++ b/DorkNet.Server/Controllers/API/Config/V1/ConfigController.cs @@ -0,0 +1,79 @@ +using Microsoft.AspNetCore.Mvc; +using DorkNet.Server.Services; + +namespace DorkNet.Server.Controllers.API.Config.V1; + +[ApiController] +[Route("api/[controller]/v1")] +public class ConfigController(ConfigService configService) : ControllerBase +{ + [HttpGet("motd")] + public ActionResult GetMotd() + { + var cfg = configService.GetConfig($"{Request.Scheme}://{Request.Host}"); + return Ok(cfg.MessageOfTheDay); + } + + [HttpGet("objectives")] + public ActionResult> GetObjectives() => Ok(new List()); + + // AmplitudeConfig.Deserialize expects key "AmplitudeKey" + [HttpGet("amplitude")] + public ActionResult GetAmplitude() => Ok(new { AmplitudeKey = string.Empty }); + + /// GET api/config/v1/freegiftbutton — the + /// 2020.12 client deserializes this endpoint as a raw Boolean. + /// This server does not run the timed free-gift campaign, so the + /// button is hidden. + [HttpGet("freegiftbutton")] + public IActionResult FreeGiftButton() => Content("false", "application/json"); + + /// + /// `Config.GetCohortNUXButtonConfigs(cohortId)` — returns the four + /// "What would you like to do first?" buttons shown in the OOBE + /// (RegistrationSceneNUX → ChooseCohortScreen). Each button maps to + /// a room the new player can teleport into. + /// + /// Wire shape per CohortNUXButtonConfig (TypeDefIndex 11516) — list of: + /// Version (int) + /// ButtonNumber (int, 0..3 → Button1..Button4) + /// Override (int, 0=None, 1=Hotlist, 2=Custom) + /// CustomRoomName (string) + /// CustomTitle (string) + /// DefaultRoomName (string) + /// DefaultTitle (string) + /// + /// The screen picks `CustomRoomName` if Override=Custom, else the + /// `DefaultRoomName` (resolved by Rooms.GetByName). For simplicity all + /// four use Override=None and route to Rec Room Original rooms we + /// already seeded — picking the most newcomer-friendly ones. + /// + [HttpGet("cohortnux/{cohortId:int}")] + public IActionResult GetCohortNux(int cohortId) => Ok(new[] + { + new { + Version = 1, ButtonNumber = 0, Override = 0, + CustomRoomName = "", CustomTitle = "", + DefaultRoomName = "RecCenter", + DefaultTitle = "Hang out at the Rec Center", + }, + new { + Version = 1, ButtonNumber = 1, Override = 0, + CustomRoomName = "", CustomTitle = "", + DefaultRoomName = "Paintball", + DefaultTitle = "Play Paintball", + }, + new { + Version = 1, ButtonNumber = 2, Override = 0, + CustomRoomName = "", CustomTitle = "", + DefaultRoomName = "Dodgeball", + DefaultTitle = "Play Dodgeball", + }, + new { + Version = 1, ButtonNumber = 3, Override = 0, + CustomRoomName = "", CustomTitle = "", + DefaultRoomName = "GoldenTrophy", + DefaultTitle = "Quest for the Golden Trophy", + }, + }); +} diff --git a/DorkNet.Server/Controllers/API/Config/V1/ServiceUrlsController.cs b/DorkNet.Server/Controllers/API/Config/V1/ServiceUrlsController.cs new file mode 100644 index 0000000..fe6b770 --- /dev/null +++ b/DorkNet.Server/Controllers/API/Config/V1/ServiceUrlsController.cs @@ -0,0 +1,19 @@ +using Microsoft.AspNetCore.Mvc; +using DorkNet.Server.Services; + +namespace DorkNet.Server.Controllers.API.Config.V1; + +/// +/// Returns the full Rec Room service URL map. Some builds fetch this standalone +/// at /api/config/v1/services to discover where the other subdomains live. +/// Apex comes from (DORKNET_DOMAIN), so a single +/// env var controls every URL emitted. +/// +[ApiController] +[Route("api/config/v1")] +public class ServiceUrlsController(DomainConfig domain) : ControllerBase +{ + [HttpGet("services")] + public ActionResult> Get() + => Ok(ConfigService.BuildServiceUrlMap(domain.Apex)); +} diff --git a/DorkNet.Server/Controllers/API/Config/V2/ConfigController.cs b/DorkNet.Server/Controllers/API/Config/V2/ConfigController.cs new file mode 100644 index 0000000..69d359a --- /dev/null +++ b/DorkNet.Server/Controllers/API/Config/V2/ConfigController.cs @@ -0,0 +1,17 @@ +using Microsoft.AspNetCore.Mvc; +using DorkNet.Models.Config; +using DorkNet.Server.Services; + +namespace DorkNet.Server.Controllers.API.Config.V2; + +[ApiController] +[Route("api/[controller]/v2")] +public class ConfigController(ConfigService configService) : ControllerBase +{ + [HttpGet] + public ActionResult GetConfig() + { + var baseUrl = $"{Request.Scheme}://{Request.Host}"; + return Ok(configService.GetConfig(baseUrl)); + } +} diff --git a/DorkNet.Server/Controllers/API/Events/V3/EventsController.cs b/DorkNet.Server/Controllers/API/Events/V3/EventsController.cs new file mode 100644 index 0000000..fac36d7 --- /dev/null +++ b/DorkNet.Server/Controllers/API/Events/V3/EventsController.cs @@ -0,0 +1,11 @@ +using Microsoft.AspNetCore.Mvc; + +namespace DorkNet.Server.Controllers.API.Events.V3; + +[ApiController] +[Route("api/[controller]/v3")] +public class EventsController : ControllerBase +{ + [HttpGet("list")] + public ActionResult> GetActiveEvents() => Ok(new List()); +} diff --git a/DorkNet.Server/Controllers/API/GameConfigurations/GameConfigurationsController.cs b/DorkNet.Server/Controllers/API/GameConfigurations/GameConfigurationsController.cs new file mode 100644 index 0000000..156acbe --- /dev/null +++ b/DorkNet.Server/Controllers/API/GameConfigurations/GameConfigurationsController.cs @@ -0,0 +1,15 @@ +using Microsoft.AspNetCore.Mvc; + +namespace DorkNet.Server.Controllers.API.GameConfigurations; + +[ApiController] +public class GameConfigurationsController : ControllerBase +{ + // Actual path used by the 2020 build + [HttpGet("api/gameconfigs/v1/all")] + [HttpGet("api/gameconfigs/v1")] + // Legacy path we originally assumed + [HttpGet("api/gameconfigurations/v1/all")] + [HttpGet("api/gameconfigurations/v1")] + public ActionResult> GetAll() => Ok(new List()); +} diff --git a/DorkNet.Server/Controllers/API/GameRewards/GameRewardsController.cs b/DorkNet.Server/Controllers/API/GameRewards/GameRewardsController.cs new file mode 100644 index 0000000..15c8b9e --- /dev/null +++ b/DorkNet.Server/Controllers/API/GameRewards/GameRewardsController.cs @@ -0,0 +1,151 @@ +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Mvc; +using Microsoft.EntityFrameworkCore; +using System.Text.Json; +using DorkNet.Server.Auth; +using DorkNet.Server.Data; +using DorkNet.Server.Data.Entities; +using DorkNet.Server.Services; + +namespace DorkNet.Server.Controllers.API.GameRewards; + +[ApiController] +[Authorize] +public class GameRewardsController(DorkNetDbContext db, LevelService level) : ControllerBase +{ + private long Me => this.RequireCurrentPlayerId(); + + [HttpGet("api/gamerewards/v1/pending")] + public async Task Pending() + { + var pid = Me; + var rows = await db.GameRewardSelections + .Where(r => r.PlayerId == pid && r.SelectedAt == null) + .OrderBy(r => r.CreatedAt) + .Take(20) + .ToListAsync(); + return Ok(rows.Select(ToWire)); + } + + [HttpPost("api/gamerewards/v1/request")] + [Consumes("application/json", "application/x-www-form-urlencoded", "multipart/form-data")] + public async Task RequestReward() + { + var req = await ReadRewardRequestAsync(); + var row = new GameRewardSelectionEntity + { + PlayerId = Me, + RewardType = req.RewardType, + GiftContext = req.GiftContext, + Message = string.IsNullOrWhiteSpace(req.Message) ? "Choose a reward" : req.Message[..Math.Min(req.Message.Length, 256)], + }; + db.GameRewardSelections.Add(row); + await db.SaveChangesAsync(); + return Ok(new { Success = true, Error = string.Empty }); + } + + [HttpPost("api/gamerewards/v1/select")] + [Consumes("application/json", "application/x-www-form-urlencoded", "multipart/form-data")] + public async Task SelectReward() + { + var req = await ReadRewardSelectAsync(); + var pid = Me; + var row = await db.GameRewardSelections + .FirstOrDefaultAsync(r => r.Id == req.RewardSelectionId && r.PlayerId == pid && r.SelectedAt == null); + if (row is null) return Ok(new { Success = false, Error = "reward_not_found" }); + + row.SelectedAt = DateTime.UtcNow; + row.SelectedGiftDropId = req.GiftDropId; + await level.GrantCurrencyAsync(pid, 2, 25, $"gameReward:{row.Id}"); + await level.AwardXpAsync(pid, 25, $"gameReward:{row.Id}"); + await db.SaveChangesAsync(); + return Ok(new { Success = true, Error = string.Empty }); + } + + private static object ToWire(GameRewardSelectionEntity row) => new + { + RewardSelectionId = row.Id, + row.Message, + row.GiftContext, + row.RewardType, + GiftDrop1 = GiftDrop(row.Id * 10 + 1, "Tokens", currency: 25), + GiftDrop2 = GiftDrop(row.Id * 10 + 2, "XP", currency: 0), + GiftDrop3 = GiftDrop(row.Id * 10 + 3, "Bonus Tokens", currency: 25), + row.CreatedAt, + }; + + private static object GiftDrop(long id, string name, int currency) => new + { + GiftDropId = id, + FriendlyName = name, + Tooltip = string.Empty, + ConsumableItemDesc = string.Empty, + AvatarItemDesc = string.Empty, + AvatarItemType = 0, + EquipmentPrefabName = string.Empty, + EquipmentModificationGuid = string.Empty, + IsQuery = false, + Unique = false, + SubscribersOnly = false, + Rarity = 0, + CurrencyType = 2, + Currency = currency, + Context = 0, + ItemSetId = 0, + ItemSetFriendlyName = string.Empty, + }; + + private async Task ReadRewardRequestAsync() + { + if (Request.HasFormContentType) + { + var form = await Request.ReadFormAsync(); + return new RewardRequest + { + RewardType = int.TryParse(form["rewardType"].FirstOrDefault(), out var rt) ? rt : 0, + GiftContext = int.TryParse(form["giftContext"].FirstOrDefault(), out var gc) ? gc : 0, + Message = form["Message"].FirstOrDefault() ?? form["message"].FirstOrDefault(), + }; + } + try + { + return await JsonSerializer.DeserializeAsync( + Request.Body, + new JsonSerializerOptions { PropertyNameCaseInsensitive = true }) ?? new RewardRequest(); + } + catch (JsonException) { return new RewardRequest(); } + } + + private async Task ReadRewardSelectAsync() + { + if (Request.HasFormContentType) + { + var form = await Request.ReadFormAsync(); + return new RewardSelect + { + RewardSelectionId = long.TryParse(form["rewardSelectionId"].FirstOrDefault(), out var id) ? id : 0, + GiftDropId = int.TryParse(form["giftDropId"].FirstOrDefault(), out var giftDropId) ? giftDropId : 0, + }; + } + try + { + return await JsonSerializer.DeserializeAsync( + Request.Body, + new JsonSerializerOptions { PropertyNameCaseInsensitive = true }) ?? new RewardSelect(); + } + catch (JsonException) { return new RewardSelect(); } + } + + private sealed class RewardRequest + { + public int RewardType { get; set; } + public int GiftContext { get; set; } + public string? Message { get; set; } + } + + private sealed class RewardSelect + { + public long RewardSelectionId { get; set; } + public int GiftDropId { get; set; } + } +} diff --git a/DorkNet.Server/Controllers/API/GameSessions/V2/GameSessionsController.cs b/DorkNet.Server/Controllers/API/GameSessions/V2/GameSessionsController.cs new file mode 100644 index 0000000..21cf53a --- /dev/null +++ b/DorkNet.Server/Controllers/API/GameSessions/V2/GameSessionsController.cs @@ -0,0 +1,67 @@ +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Mvc; +using DorkNet.Models.GameSessions; +using DorkNet.Server.Auth; +using DorkNet.Server.Services; + +namespace DorkNet.Server.Controllers.API.GameSessions.V2; + +[ApiController] +[Route("api/[controller]/v2")] +[Authorize] +public class GameSessionsController(GameSessionService sessionService) : ControllerBase +{ + private long CurrentPlayerId => this.RequireCurrentPlayerId(); + + [HttpPost("joinRandom")] + public async Task> JoinRandom([FromBody] JoinRandomGameSessionRequest req) + { + var session = await sessionService.JoinOrCreateAsync(req.RoomId, req.ActivityLevelId, req.RegionId); + return Ok(new JoinGameSessionResponse + { + Result = JoinGameErrorCode.Success, + GameSession = session, + }); + } + + [HttpPost("joinById")] + public async Task> JoinById([FromBody] JoinByIdRequest req) + { + var session = await sessionService.GetByIdAsync(req.GameSessionId); + if (session is null) + { + return Ok(new JoinGameSessionResponse { Result = JoinGameErrorCode.NotFound }); + } + + if (session.IsFull) + { + return Ok(new JoinGameSessionResponse { Result = JoinGameErrorCode.Full }); + } + + return Ok(new JoinGameSessionResponse + { + Result = JoinGameErrorCode.Success, + GameSession = session, + }); + } + + [HttpPost("leave")] + public async Task Leave([FromBody] long sessionId) + { + await sessionService.PlayerLeftAsync(sessionId); + return Ok(); + } + + [HttpGet("{id:long}")] + public async Task> GetSession(long id) + { + var session = await sessionService.GetByIdAsync(id); + if (session is null) return NotFound(); + return Ok(session); + } +} + +public class JoinByIdRequest +{ + public long GameSessionId { get; set; } +} diff --git a/DorkNet.Server/Controllers/API/Groups/GroupsController.cs b/DorkNet.Server/Controllers/API/Groups/GroupsController.cs new file mode 100644 index 0000000..df6b165 --- /dev/null +++ b/DorkNet.Server/Controllers/API/Groups/GroupsController.cs @@ -0,0 +1,240 @@ +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Mvc; +using Microsoft.EntityFrameworkCore; +using DorkNet.Server.Auth; +using DorkNet.Server.Data; +using DorkNet.Server.Data.Entities; + +namespace DorkNet.Server.Controllers.API.Groups; + +/// +/// api.rec.net/api/groups/* — clubs / groups. Wire types from +/// RecNet.Group + RecNet.GroupMembership: +/// +/// • Group: GroupId, Name, Description, CreatedAt, ImageName, +/// BanStatus, CreatorId, NumMembers, Members. +/// • GroupMembership: GroupId, PlayerId, Permissions +/// (GroupMembershipPermissions flags). +/// +/// URL surface (verb + template) verified against +/// Cpp2IL_ISIL/.../RecNet/Groups.txt: +/// GET api/groups/v1/{id} +/// GET api/groups/v1/name/{name} +/// GET api/groups/v1/memberships/{playerId} +/// POST api/groups/v1 (create — body CreateGroupRequest) +/// POST api/groups/v1/delete/{id} +/// +[ApiController] +public class GroupsController(DorkNetDbContext db) : ControllerBase +{ + private long Me => this.RequireCurrentPlayerId(); + private long? MeOrNull => this.CurrentPlayerId(); + + [HttpGet("api/groups/v1/{id:long}")] + public async Task GetById(long id) + { + var g = await db.Clubs.FirstOrDefaultAsync(c => c.Id == id); + if (g is null) return NotFound(); + var members = await db.ClubMemberships + .Where(m => m.ClubId == id) + .ToListAsync(); + return Ok(ToWire(g, members)); + } + + [HttpGet("api/groups/v1/name/{name}")] + public async Task GetByName(string name) + { + var g = await db.Clubs.FirstOrDefaultAsync(c => c.Name == name); + if (g is null) return NotFound(); + var members = await db.ClubMemberships + .Where(m => m.ClubId == g.Id) + .ToListAsync(); + return Ok(ToWire(g, members)); + } + + [HttpGet("api/groups/v1/memberships/{playerId:long}")] + public async Task Memberships(long playerId) + { + var rows = await (from m in db.ClubMemberships + join c in db.Clubs on m.ClubId equals c.Id + where m.PlayerId == playerId + select new { m, c }).ToListAsync(); + return Ok(rows.Select(r => new + { + GroupId = r.c.Id, + PlayerId = (int)r.m.PlayerId, + Permissions = r.m.Permissions, + Group = ToWire(r.c, new List()), + })); + } + + public sealed class CreateGroupRequest + { + public string? Name { get; set; } + public string? Description { get; set; } + public string? ImageName { get; set; } + } + + [HttpPost("api/groups/v1")] + [Authorize] + public async Task Create([FromBody] CreateGroupRequest req) + { + if (string.IsNullOrWhiteSpace(req.Name)) + return BadRequest(new { Result = 1, Error = "name required" }); + var pid = Me; + var dupe = await db.Clubs.AnyAsync(c => c.Name == req.Name); + if (dupe) return Conflict(new { Result = 2, Error = "name taken" }); + + var club = new ClubEntity + { + Name = req.Name.Trim(), + Description = req.Description ?? string.Empty, + ImageName = req.ImageName ?? string.Empty, + CreatorPlayerId = pid, + BanStatus = 0, + }; + db.Clubs.Add(club); + await db.SaveChangesAsync(); + + // Owner membership uses GroupMembershipPermissions.Owner = 127. + db.ClubMemberships.Add(new ClubMembershipEntity + { + ClubId = club.Id, + PlayerId = pid, + Permissions = 127, + }); + await db.SaveChangesAsync(); + return Ok(new + { + Result = 0, + Group = ToWire(club, new List + { + new() { ClubId = club.Id, PlayerId = pid, Permissions = 127 }, + }), + }); + } + + public sealed class UpdateGroupRequest + { + public string? Name { get; set; } + public string? Description { get; set; } + public string? ImageName { get; set; } + } + + /// POST api/groups/v1/{id} — owner-only update of + /// description / image (per RecNet.Groups.UpdateGroup body + /// shape). Mirrors the create response so the watch's + /// CreateModifyGroupResponse deserialiser parses both. + [HttpPost("api/groups/v1/{id:long}")] + [Authorize] + public async Task Update(long id, [FromBody] UpdateGroupRequest req) + { + var pid = Me; + var g = await db.Clubs.FirstOrDefaultAsync(c => c.Id == id); + if (g is null) return NotFound(); + if (g.CreatorPlayerId != pid) return Forbid(); + if (req.Description is not null) g.Description = req.Description; + if (req.ImageName is not null) g.ImageName = req.ImageName; + // Name updates are routed to the dedicated endpoint below + // (uniqueness check + name index has to round-trip). + g.UpdatedAt = DateTime.UtcNow; + await db.SaveChangesAsync(); + var members = await db.ClubMemberships.Where(m => m.ClubId == id).ToListAsync(); + return Ok(new { Result = 0, Group = ToWire(g, members) }); + } + + public sealed class UpdateGroupNameRequest { public string? Name { get; set; } } + + /// POST api/groups/v1/name/{id} — owner-only + /// rename. Unique-name collision returns Result=2 to match the + /// client's CreateModifyGroupStatus enum. + [HttpPost("api/groups/v1/name/{id:long}")] + [Authorize] + public async Task UpdateName(long id, [FromBody] UpdateGroupNameRequest req) + { + if (string.IsNullOrWhiteSpace(req.Name)) + return BadRequest(new { Result = 1, Error = "name required" }); + var pid = Me; + var g = await db.Clubs.FirstOrDefaultAsync(c => c.Id == id); + if (g is null) return NotFound(); + if (g.CreatorPlayerId != pid) return Forbid(); + var dupe = await db.Clubs.AnyAsync(c => c.Id != id && c.Name == req.Name); + if (dupe) return Conflict(new { Result = 2, Error = "name taken" }); + g.Name = req.Name.Trim(); + g.UpdatedAt = DateTime.UtcNow; + await db.SaveChangesAsync(); + var members = await db.ClubMemberships.Where(m => m.ClubId == id).ToListAsync(); + return Ok(new { Result = 0, Group = ToWire(g, members) }); + } + + [HttpPost("api/groups/v1/delete/{id:long}")] + [HttpDelete("api/groups/v1/delete/{id:long}")] + [Authorize] + public async Task Delete(long id) + { + var pid = Me; + var g = await db.Clubs.FirstOrDefaultAsync(c => c.Id == id); + if (g is null) return NotFound(); + if (g.CreatorPlayerId != pid) return Forbid(); + var memberships = await db.ClubMemberships.Where(m => m.ClubId == id).ToListAsync(); + db.ClubMemberships.RemoveRange(memberships); + db.Clubs.Remove(g); + await db.SaveChangesAsync(); + return Ok(new { Deleted = true }); + } + + [HttpPost("api/groups/v1/{id:long}/join")] + [Authorize] + public async Task Join(long id) + { + var pid = Me; + var g = await db.Clubs.FirstOrDefaultAsync(c => c.Id == id); + if (g is null) return NotFound(); + var existing = await db.ClubMemberships + .FirstOrDefaultAsync(m => m.ClubId == id && m.PlayerId == pid); + if (existing is not null) return Ok(new { Joined = true }); + db.ClubMemberships.Add(new ClubMembershipEntity + { + ClubId = id, + PlayerId = pid, + Permissions = 0, // Member + }); + await db.SaveChangesAsync(); + return Ok(new { Joined = true }); + } + + [HttpPost("api/groups/v1/{id:long}/leave")] + [Authorize] + public async Task Leave(long id) + { + var pid = Me; + var g = await db.Clubs.FirstOrDefaultAsync(c => c.Id == id); + if (g is null) return NotFound(); + if (g.CreatorPlayerId == pid) + return BadRequest(new { Error = "owner cannot leave; transfer ownership first" }); + var row = await db.ClubMemberships + .FirstOrDefaultAsync(m => m.ClubId == id && m.PlayerId == pid); + if (row is null) return NotFound(); + db.ClubMemberships.Remove(row); + await db.SaveChangesAsync(); + return Ok(new { Left = true }); + } + + private static object ToWire(ClubEntity g, List members) => new + { + GroupId = g.Id, + g.Name, + g.Description, + g.CreatedAt, + g.ImageName, + BanStatus = g.BanStatus, + CreatorId = (int)g.CreatorPlayerId, + NumMembers = members.Count, + Members = members.Select(m => new + { + GroupId = g.Id, + PlayerId = (int)m.PlayerId, + Permissions = m.Permissions, + }), + }; +} diff --git a/DorkNet.Server/Controllers/API/Images/ImagesController.cs b/DorkNet.Server/Controllers/API/Images/ImagesController.cs new file mode 100644 index 0000000..872f1e9 --- /dev/null +++ b/DorkNet.Server/Controllers/API/Images/ImagesController.cs @@ -0,0 +1,904 @@ +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Mvc; +using Microsoft.EntityFrameworkCore; +using Microsoft.AspNetCore.Http; +using System.Text.Json; +using DorkNet.Models.Auth; +using DorkNet.Server.Auth; +using DorkNet.Server.Data; +using DorkNet.Server.Data.Entities; +using DorkNet.Server.Services; + +namespace DorkNet.Server.Controllers.API.Images; + +/// +/// api.rec.net/api/images/v* — in-game camera photo endpoints. The 2020 +/// watch's camera UI ("save photo to album", "share photo") posts here. +/// +/// Wire shape (observed from server logs — the 2020 watch hits these +/// without a corresponding storage.rec.net/upload, meaning the bytes +/// come straight in this request body): +/// POST api/images/v4/uploadsaved +/// - body: multipart/form-data with the image file plus optional +/// fields (Caption, Description, RoomId, IsPublic, TaggedPlayerIds). +/// On the 2020 client the field name varies — we accept "File", +/// "Image", and any *.png/*.jpg upload for safety. +/// - response: an ImageInfo-shaped object (PascalCase keys). The +/// watch's success path calls a JSON parser whose strict keys are +/// AccountId, ImageName, CreatedAt — so all three are required. +/// Returning [] (the previous catch-all) made the watch silently +/// drop the photo. +/// +/// Saved photos are stored as rows with +/// IsPublic=false by default. A separate Share button (POST +/// api/images/v4/share/{id}) flips IsPublic=true and surfaces them on +/// feed.rec.net. +/// +[ApiController] +public class ImagesController( + DorkNetDbContext db, + PlayerPresenceService presence, + LevelService level, + IObjectStorage storage, + ImageSignatureService signatures, + NotificationService notifications, + DomainConfig domain, + ILogger logger) : ControllerBase +{ + private static readonly string ImageDir = + Path.Combine(AppContext.BaseDirectory, "data", "images"); + + private static readonly byte[] TransparentPng = new byte[] + { + 0x89,0x50,0x4E,0x47,0x0D,0x0A,0x1A,0x0A,0x00,0x00,0x00,0x0D, + 0x49,0x48,0x44,0x52,0x00,0x00,0x00,0x01,0x00,0x00,0x00,0x01, + 0x08,0x06,0x00,0x00,0x00,0x1F,0x15,0xC4,0x89,0x00,0x00,0x00, + 0x0D,0x49,0x44,0x41,0x54,0x78,0x9C,0x63,0x00,0x01,0x00,0x00, + 0x05,0x00,0x01,0x0D,0x0A,0x2D,0xB4,0x00,0x00,0x00,0x00,0x49, + 0x45,0x4E,0x44,0xAE,0x42,0x60,0x82, + }; + + /// POST api/images/v{2-5}/uploadsaved — receive an image + /// from the in-game camera "save to album" button. Persists the + /// bytes in and creates a + /// row keyed by the uploader. The photo + /// defaults to IsPublic=false (saved-only) so it doesn't surface + /// on the public feed unless the user explicitly hits Share. + [HttpPost("api/images/v2/uploadsaved")] + [HttpPost("api/images/v3/uploadsaved")] + [HttpPost("api/images/v4/uploadsaved")] + [HttpPost("api/images/v5/uploadsaved")] + [Authorize] + public async Task UploadSaved() + { + var pid = this.RequireCurrentPlayerId(); + var bytes = await ReadImageBytesAsync(); + if (bytes is null || bytes.Length == 0) + { + logger.LogWarning("[images] uploadsaved by {Pid}: no file bytes received", pid); + return BadRequest(new { error = "missing_file" }); + } + + // Pull optional metadata from the form (the 2020 watch sometimes + // includes a caption / room context, sometimes posts bytes only). + var form = Request.HasFormContentType ? await Request.ReadFormAsync() : null; + var caption = form?["Caption"].ToString() ?? form?["Description"].ToString() ?? string.Empty; + var taggedIds = form?["TaggedPlayerIds"].ToString() ?? string.Empty; + long roomId = 0; + if (form is not null && long.TryParse(form["RoomId"].ToString(), out var rid)) roomId = rid; + if (roomId == 0) roomId = presence.GetRoom(pid)?.RoomId ?? 0; + var isPublic = string.Equals(form?["IsPublic"].ToString(), "true", StringComparison.OrdinalIgnoreCase); + var isProfileThumbnail = IsProfileThumbnail(form); + LogUploadForm("uploadsaved", pid, form, isProfileThumbnail); + + // Detect format. The watch uses PNG; if a JPEG slips through we + // tag it accordingly so the cdn serves the right MIME. + var extension = SniffExtension(bytes); + var blobName = $"img_p{pid}_{Guid.NewGuid():N}.{extension}"; + + // Dual-write during the migration window: S3 is the canonical + // store going forward, but we keep the DB Bytes column populated + // so reads from older code paths still work. PR 4 drops the + // column once every blob has a confirmed S3 copy. + try + { + using var s3Timeout = new CancellationTokenSource(TimeSpan.FromSeconds(4)); + var (bucket, key) = BlobRouter.Route(blobName); + await storage.PutAsync( + bucket, key, bytes, + extension == "png" ? "image/png" : "image/jpeg", + s3Timeout.Token); + } + catch (Exception ex) + { + logger.LogError( + ex, + "[images] S3 upload failed for {Blob}; keeping DB fallback so the client does not fail", + blobName); + } + + db.RoomDataBlobs.Add(new RoomDataBlobEntity + { + RoomId = 0, + BlobName = blobName, + UploadedByPlayerId = pid, + UploadedAt = DateTime.UtcNow, + ReferencedFilenamesCsv = string.Empty, + }); + + var photo = new PhotoEntity + { + UploaderPlayerId = pid, + BlobName = blobName, + Caption = caption.Trim(), + TaggedPlayerIdsCsv = NormaliseTaggedIds(taggedIds, exceptId: pid), + RoomId = roomId, + IsPublic = isPublic, + }; + db.Photos.Add(photo); + + if (isProfileThumbnail) + { + var player = await db.Players.FirstOrDefaultAsync(p => p.Id == pid); + if (player is not null) + { + player.ProfileImageName = blobName; + logger.LogInformation( + "[images] profile thumbnail upload set profile image: player={PlayerId} blob={Blob}", + pid, + blobName); + } + } + + await db.SaveChangesAsync(); + + if (isProfileThumbnail) + { + var player = await db.Players.FirstOrDefaultAsync(p => p.Id == pid); + if (player is not null) + await NotifyAccountImageChangedAsync(player); + } + + // Small XP bump for taking a photo — encourages camera use. + await level.AwardXpAsync(pid, LevelService.RoomVisitXp, $"photo_saved:{photo.Id}"); + + logger.LogInformation( + "[images] uploaded saved photo {Id} by {Pid} ({Bytes} bytes) → {Blob}", + photo.Id, pid, bytes.Length, blobName); + + return Ok(BuildImageInfo(photo, pid)); + } + + /// POST api/images/v{1-5}/profile — direct profile image upload. + /// Older clients use this instead of tagging an uploadsaved request as a + /// ProfileThumbnail, so this route must always update Account.ProfileImage. + [HttpPost("api/images/v1/profile")] + [HttpPost("api/images/v2/profile")] + [HttpPost("api/images/v3/profile")] + [HttpPost("api/images/v4/profile")] + [HttpPost("api/images/v5/profile")] + [Authorize] + public async Task UploadProfile() + { + var pid = this.RequireCurrentPlayerId(); + var bytes = await ReadImageBytesAsync(); + if (bytes is null || bytes.Length == 0) + { + logger.LogWarning("[images] profile upload by {Pid}: no file bytes received", pid); + return BadRequest(new { error = "missing_file" }); + } + + var form = Request.HasFormContentType ? await Request.ReadFormAsync() : null; + LogUploadForm("profile", pid, form, isProfile: true); + + var extension = SniffExtension(bytes); + var blobName = $"img_p{pid}_{Guid.NewGuid():N}.{extension}"; + var contentType = extension == "png" ? "image/png" : "image/jpeg"; + + try + { + using var s3Timeout = new CancellationTokenSource(TimeSpan.FromSeconds(4)); + var (bucket, key) = BlobRouter.Route(blobName); + await storage.PutAsync(bucket, key, bytes, contentType, s3Timeout.Token); + } + catch (Exception ex) + { + logger.LogError( + ex, + "[images] profile S3 upload failed for {Blob}; keeping DB fallback so the client does not fail", + blobName); + } + + db.RoomDataBlobs.Add(new RoomDataBlobEntity + { + RoomId = 0, + BlobName = blobName, + UploadedByPlayerId = pid, + UploadedAt = DateTime.UtcNow, + ReferencedFilenamesCsv = string.Empty, + }); + + var photo = new PhotoEntity + { + UploaderPlayerId = pid, + BlobName = blobName, + Caption = string.Empty, + TaggedPlayerIdsCsv = string.Empty, + RoomId = presence.GetRoom(pid)?.RoomId ?? 0, + IsPublic = false, + }; + db.Photos.Add(photo); + + var player = await db.Players.FirstOrDefaultAsync(p => p.Id == pid); + if (player is not null) + player.ProfileImageName = blobName; + + await db.SaveChangesAsync(); + + if (player is not null) + await NotifyAccountImageChangedAsync(player); + + logger.LogInformation( + "[images] uploaded profile photo {Id} by {Pid} ({Bytes} bytes) -> {Blob}", + photo.Id, pid, bytes.Length, blobName); + + return Ok(new + { + ImageName = blobName, + AccountId = (int)pid, + CreatedAt = photo.CreatedAt, + }); + } + + /// GET api/images/v{2-5}/saved — list the caller's saved + /// photos. The watch's "My Photos" tab calls this to populate the + /// album view. + [HttpGet("api/images/v2/saved")] + [HttpGet("api/images/v3/saved")] + [HttpGet("api/images/v4/saved")] + [HttpGet("api/images/v5/saved")] + [Authorize] + public async Task GetSaved() + { + var pid = this.RequireCurrentPlayerId(); + var rows = await db.Photos + .Where(p => p.UploaderPlayerId == pid && p.DeletedAt == null) + .OrderByDescending(p => p.CreatedAt) + .Take(200) + .ToListAsync(); + return Ok(rows.Select(p => BuildImageInfo(p, pid))); + } + + /// GET api/images/v2/named — the watch's + /// Images.DownloadNamedImageMappings endpoint. Returns a + /// list of NamedImageDTO entries; an empty list is a valid + /// response (the watch's ExpectListResponse rejects an + /// object body but tolerates an empty array). + [HttpGet("api/images/v2/named")] + public IActionResult NamedImages() => Ok(Array.Empty()); + + /// GET api/images/v1/slideshow — the watch's lobby + /// slideshow info. Response shape per SlideshowInfoDTO.Deserialize: + /// {ValidTill (DateTime, required), Images (list, required)}. + /// Far-future ValidTill stops the watch from re-polling. + [HttpGet("api/images/v1/slideshow")] + public IActionResult SlideshowInfo() => Ok(new + { + ValidTill = new DateTime(9999, 12, 31, 0, 0, 0, DateTimeKind.Utc), + Images = Array.Empty(), + }); + + /// Binary image fallback for clients that request image bytes + /// through api/images/{name} instead of img.rec.net/{name}. Specific JSON + /// endpoints above still win route matching; this only handles CDN-shaped + /// image paths. + [HttpGet("api/images/{*path}", Order = 100)] + public async Task ServeImageFromApiHost(string? path) + { + var fileName = Path.GetFileName(path ?? string.Empty); + // Strip the leading '$' sigil that ImageData.image_name uses for + // hash-addressed assets. Watch sends "%24.jpg"; route + // decode gives us "$.jpg"; the bytes are stored at + // ".jpg". Without the strip, placed in-world polaroids + // miss in S3 and the watch renders the "?" fallback. + if (!string.IsNullOrEmpty(fileName) && fileName[0] == '$') fileName = fileName[1..]; + if (string.IsNullOrWhiteSpace(fileName) || !IsImageName(fileName)) + return NotFound(); + + var result = await LoadImageBytesAsync(fileName); + if (result.Bytes is { Length: > 0 }) + { + logger.LogInformation( + "[images] api image hit host={Host} path={Path} file={File} source={Source} bytes={Bytes} query='{Query}'", + Request.Host.Host, + path, + fileName, + result.Source, + result.Bytes.Length, + Request.QueryString); + Response.Headers.CacheControl = "public, max-age=300"; + signatures.AddContentSignature(Response, result.Bytes); + return File(result.Bytes, MimeFromName(fileName)); + } + + logger.LogWarning( + "[images] api image MISS host={Host} path={Path} file={File} query='{Query}' -> transparent png", + Request.Host.Host, + path, + fileName, + Request.QueryString); + Response.Headers.CacheControl = "public, max-age=60"; + signatures.AddContentSignature(Response, TransparentPng); + return File(TransparentPng, "image/png"); + } + + /// GET api/images/v{2-5}/byaccount/{accountId} — list a + /// player's PUBLIC photos. Drives the "their album" tab in + /// the watch's player profile. + [HttpGet("api/images/v2/byaccount/{accountId:long}")] + [HttpGet("api/images/v3/byaccount/{accountId:long}")] + [HttpGet("api/images/v4/byaccount/{accountId:long}")] + [HttpGet("api/images/v5/byaccount/{accountId:long}")] + public async Task GetByAccount(long accountId, + [FromQuery] int take = 50, [FromQuery] int skip = 0) + { + take = Math.Clamp(take, 1, 200); + skip = Math.Max(0, skip); + var rows = await db.Photos + .Where(p => p.UploaderPlayerId == accountId && p.IsPublic && p.DeletedAt == null) + .OrderByDescending(p => p.CreatedAt) + .Skip(skip).Take(take) + .ToListAsync(); + return Ok(rows.Select(p => BuildImageInfo(p, accountId))); + } + + /// POST api/images/v{2-5}/share/{id} — flip a saved photo + /// to public so it shows up on feed.rec.net. The watch's "Share" + /// button hits this; if the watch posts to a different URL we'll + /// add it when we see it in logs. + [HttpPost("api/images/v2/share/{id:long}")] + [HttpPost("api/images/v3/share/{id:long}")] + [HttpPost("api/images/v4/share/{id:long}")] + [HttpPost("api/images/v5/share/{id:long}")] + [Authorize] + public async Task Share(long id) + { + var pid = this.RequireCurrentPlayerId(); + var photo = await db.Photos.FirstOrDefaultAsync(p => p.Id == id); + if (photo is null) return NotFound(); + if (photo.UploaderPlayerId != pid) return Forbid(); + photo.IsPublic = true; + await db.SaveChangesAsync(); + return Ok(BuildImageInfo(photo, pid)); + } + + /// DELETE api/images/v{2-5}/{id} — uploader (or admin) + /// soft-deletes a saved photo. + [HttpDelete("api/images/v2/{id:long}")] + [HttpDelete("api/images/v3/{id:long}")] + [HttpDelete("api/images/v4/{id:long}")] + [HttpDelete("api/images/v5/{id:long}")] + [Authorize] + public async Task Delete(long id) + { + var pid = this.RequireCurrentPlayerId(); + var photo = await db.Photos.FirstOrDefaultAsync(p => p.Id == id); + if (photo is null) return NotFound(); + if (photo.UploaderPlayerId != pid) + { + var isAdmin = await db.Players.Where(x => x.Id == pid).Select(x => x.IsAdmin).FirstOrDefaultAsync(); + if (!isAdmin) return Forbid(); + } + photo.DeletedAt = DateTime.UtcNow; + await db.SaveChangesAsync(); + return Ok(new { deleted = id }); + } + + // ── Legacy v1 ops (real persistence, was Ack-only stubs) ───────── + + public sealed class CheerImageRequest + { + public long ImageId { get; set; } + public long PhotoId { get; set; } + } + + public sealed class BulkCheeredRequest + { + public List? SavedImageIds { get; set; } + public List? ImageIds { get; set; } + } + + [HttpPost("api/images/v4/cheered/bulk")] + [Authorize] + public async Task CheeredBulk([FromBody] BulkCheeredRequest? body) + { + var pid = this.RequireCurrentPlayerId(); + var ids = (body?.SavedImageIds ?? body?.ImageIds ?? new()) + .Where(id => id > 0) + .Distinct() + .Take(200) + .ToList(); + if (ids.Count == 0) return Ok(Array.Empty()); + + var cheered = await db.Cheers + .Where(c => c.FromPlayerId == pid && ids.Contains(c.TargetPhotoId)) + .Select(c => c.TargetPhotoId) + .ToListAsync(); + var set = cheered.ToHashSet(); + return Ok(ids.Select(id => new + { + SavedImageId = id, + IsCheered = set.Contains(id), + })); + } + + /// POST api/images/v1/cheer — cheer a photo. + /// Was previously Ack-only; now persists a CheerEntity row + /// keyed on TargetPhotoId + bumps Photos.CheerCount. + /// Idempotent per (player, photo). + [HttpPost("api/images/v1/cheer")] + [Authorize] + public async Task CheerV1([FromBody] CheerImageRequest? body, + [FromForm(Name = "ImageId")] long? imageIdForm, + [FromForm(Name = "PhotoId")] long? photoIdForm) + { + var pid = this.RequireCurrentPlayerId(); + var photoId = body?.PhotoId ?? body?.ImageId + ?? photoIdForm ?? imageIdForm ?? 0; + if (photoId <= 0) return BadRequest(new { error = "missing photoId" }); + + var photo = await db.Photos.FirstOrDefaultAsync(p => p.Id == photoId && p.DeletedAt == null); + if (photo is null) return NotFound(); + + var existing = await db.Cheers.FirstOrDefaultAsync(c => + c.FromPlayerId == pid && c.TargetPhotoId == photoId); + if (existing is null) + { + db.Cheers.Add(new CheerEntity + { + FromPlayerId = pid, + TargetPhotoId = photoId, + CheeredAt = DateTime.UtcNow, + }); + photo.CheerCount += 1; + await db.SaveChangesAsync(); + } + return Ok(new { photo.Id, photo.CheerCount }); + } + + /// GET api/images/v1/listsaved — the 2020 client + /// deserializes this as SavedImagesListDTO { Images: string[] }. + /// Do not return the richer v2/v4 photo-object list here. + [HttpGet("api/images/v1/listsaved")] + [Authorize] + public async Task ListSavedV1() + { + var pid = this.RequireCurrentPlayerId(); + var names = await db.Photos + .Where(p => p.UploaderPlayerId == pid && p.DeletedAt == null) + .OrderByDescending(p => p.CreatedAt) + .Select(p => p.BlobName) + .Take(200) + .ToListAsync(); + return Ok(new { Images = names }); + } + + public sealed class ModifyAccessibilityRequest + { + public long PhotoId { get; set; } + public long ImageId { get; set; } + public string? ImageName { get; set; } + public bool IsPublic { get; set; } + } + + /// POST api/images/v1/modifyaccessibility — flip + /// the IsPublic flag on a saved photo. Was Ack-only; now writes + /// the column. + [HttpPost("api/images/v1/modifyaccessibility")] + [Authorize] + public async Task ModifyAccessibility( + [FromBody] ModifyAccessibilityRequest? body, + [FromForm(Name = "PhotoId")] long? photoIdForm, + [FromForm(Name = "ImageId")] long? imageIdForm, + [FromForm(Name = "ImageName")] string? imageNameForm, + [FromForm(Name = "IsPublic")] bool? isPublicForm) + { + var pid = this.RequireCurrentPlayerId(); + var photoId = body?.PhotoId ?? body?.ImageId + ?? photoIdForm ?? imageIdForm ?? 0; + var imageName = body?.ImageName ?? imageNameForm; + if (photoId <= 0 && string.IsNullOrWhiteSpace(imageName)) + return BadRequest(new { error = "missing photoId" }); + var isPublic = body?.IsPublic ?? isPublicForm ?? true; + + var photo = photoId > 0 + ? await db.Photos.FirstOrDefaultAsync(p => p.Id == photoId) + : await db.Photos.FirstOrDefaultAsync(p => p.BlobName == imageName); + if (photo is null) return NotFound(); + if (photo.UploaderPlayerId != pid) return Forbid(); + photo.IsPublic = isPublic; + await db.SaveChangesAsync(); + return Ok(new { photo.Id, photo.IsPublic }); + } + + public sealed class DeleteSavedRequest + { + public long PhotoId { get; set; } + public long ImageId { get; set; } + public string? ImageName { get; set; } + } + + /// POST api/images/v1/deletesaved + DELETE alias — + /// soft-delete a saved photo. Was Ack-only; now flips + /// DeletedAt. + [HttpPost("api/images/v1/deletesaved")] + [HttpDelete("api/images/v1/deletesaved/{id:long}")] + [Authorize] + public async Task DeleteSavedV1( + long? id, + [FromBody] DeleteSavedRequest? body, + [FromForm(Name = "PhotoId")] long? photoIdForm, + [FromForm(Name = "ImageId")] long? imageIdForm, + [FromForm(Name = "ImageName")] string? imageNameForm) + { + var photoId = id ?? body?.PhotoId ?? body?.ImageId + ?? photoIdForm ?? imageIdForm ?? 0; + var imageName = body?.ImageName ?? imageNameForm; + if (photoId <= 0 && string.IsNullOrWhiteSpace(imageName)) + return BadRequest(new { error = "missing photoId" }); + if (photoId <= 0) + { + var pid = this.RequireCurrentPlayerId(); + var photo = await db.Photos.FirstOrDefaultAsync(p => + p.UploaderPlayerId == pid && p.BlobName == imageName && p.DeletedAt == null); + if (photo is null) return NotFound(); + photo.DeletedAt = DateTime.UtcNow; + await db.SaveChangesAsync(); + return Ok(new RecNetResult { Success = true, Error = string.Empty }); + } + return await Delete(photoId); + } + + public sealed class SendLinkRequest + { + public long PhotoId { get; set; } + public long ImageId { get; set; } + public List? RecipientIds { get; set; } + } + + /// POST api/images/v1/sendlink — send a link to a + /// photo as a DM to one or more recipients. Was Ack-only; now + /// inserts one per recipient. + [HttpPost("api/images/v1/sendlink")] + [Authorize] + public async Task SendLink( + [FromBody] SendLinkRequest? body, + [FromForm(Name = "PhotoId")] long? photoIdForm, + [FromForm(Name = "ImageId")] long? imageIdForm, + [FromForm(Name = "RecipientIds")] string? recipientIdsForm) + { + var pid = this.RequireCurrentPlayerId(); + var photoId = body?.PhotoId ?? body?.ImageId ?? photoIdForm ?? imageIdForm ?? 0; + if (photoId <= 0) return BadRequest(new { error = "missing photoId" }); + var photo = await db.Photos.FirstOrDefaultAsync(p => p.Id == photoId); + if (photo is null) return NotFound(); + + var recipients = body?.RecipientIds ?? ParseIdList(recipientIdsForm) ?? new(); + if (recipients.Count == 0) return Ok(new { Sent = 0 }); + + var preview = $"shared a photo (id {photoId})"; + foreach (var rid in recipients.Distinct()) + { + db.Messages.Add(new MessageEntity + { + SenderPlayerId = pid, + RecipientPlayerId = rid, + Body = $"{preview} → /img/{photo.BlobName}", + }); + } + await db.SaveChangesAsync(); + return Ok(new { Sent = recipients.Count }); + } + + private static List? ParseIdList(string? csv) + { + if (string.IsNullOrWhiteSpace(csv)) return null; + return csv.Split(',', StringSplitOptions.RemoveEmptyEntries) + .Select(s => long.TryParse(s.Trim(), out var v) ? v : 0L) + .Where(v => v > 0) + .ToList(); + } + + // ── Helpers ────────────────────────────────────────────────────── + + /// Read raw image bytes from the request — supports both + /// multipart/form-data (the typical case, with field "File" or + /// "Image") and a raw octet-stream body (some 2020 client paths). + private async Task ReadImageBytesAsync() + { + if (Request.HasFormContentType) + { + var form = await Request.ReadFormAsync(); + // Try common field names; fall back to the first uploaded file. + var file = form.Files["File"] + ?? form.Files["Image"] + ?? form.Files["image"] + ?? form.Files["file"] + ?? form.Files.FirstOrDefault(); + if (file is null || file.Length == 0) return null; + using var ms = new MemoryStream(); + await file.CopyToAsync(ms); + return ms.ToArray(); + } + + // Raw body fallback — when the watch posts the bytes directly. + if (Request.ContentLength is > 0) + { + using var ms = new MemoryStream(); + await Request.Body.CopyToAsync(ms); + return ms.ToArray(); + } + return null; + } + + /// Detect PNG vs JPEG by magic bytes — anything else + /// defaults to .png since the 2020 client's camera always saves + /// PNG. + private static string SniffExtension(byte[] bytes) + { + if (bytes.Length >= 4 && + bytes[0] == 0x89 && bytes[1] == 0x50 && bytes[2] == 0x4E && bytes[3] == 0x47) + return "png"; + if (bytes.Length >= 3 && + bytes[0] == 0xFF && bytes[1] == 0xD8 && bytes[2] == 0xFF) + return "jpg"; + return "png"; + } + + private async Task<(byte[]? Bytes, string Source)> LoadImageBytesAsync(string fileName) + { + var (bucket, key) = BlobRouter.Route(fileName); + try + { + using var s3Timeout = new CancellationTokenSource(TimeSpan.FromSeconds(2)); + var s3Bytes = await storage.GetAsync(bucket, key, s3Timeout.Token); + if (s3Bytes is { Length: > 0 }) + return (s3Bytes, $"s3:{bucket}/{key}"); + } + catch (Exception ex) + { + logger.LogWarning(ex, + "[images] API image S3 lookup threw for {File} bucket={Bucket} key={Key}", + fileName, bucket, key); + } + + // S3 is the only canonical store. Disk-on-server is a legacy + // surface used by tools/fetch-room-images.py'd thumbnails that + // ship with the server — keep that as a last resort, but DB + // bytes are gone. + var full = Path.Combine(ImageDir, fileName); + if (System.IO.File.Exists(full)) + return (await System.IO.File.ReadAllBytesAsync(full), "disk"); + + return (null, "miss"); + } + + + private static bool TryGetPlayerId(string fileName, string prefix, out long playerId) + { + playerId = 0; + if (!fileName.StartsWith(prefix, StringComparison.OrdinalIgnoreCase)) return false; + var start = prefix.Length; + var end = fileName.IndexOf('_', start); + return end > start && long.TryParse(fileName[start..end], out playerId); + } + + private static bool IsImageName(string name) + { + var ext = Path.GetExtension(name).ToLowerInvariant(); + return ext is ".png" or ".jpg" or ".jpeg" or ".gif" or ".webp" or ".avif"; + } + + private static string MimeFromName(string name) + { + var ext = Path.GetExtension(name).ToLowerInvariant(); + return ext switch + { + ".png" => "image/png", + ".jpg" or ".jpeg" => "image/jpeg", + ".gif" => "image/gif", + ".webp" => "image/webp", + ".avif" => "image/avif", + _ => "image/png", + }; + } + + private void LogUploadForm(string endpoint, long playerId, IFormCollection? form, bool isProfile) + { + if (form is null) + { + logger.LogInformation( + "[images] {Endpoint} metadata player={PlayerId} form=false profile={Profile}", + endpoint, + playerId, + isProfile); + return; + } + + var fileNames = string.Join( + ",", + form.Files.Select(f => $"{f.Name}:{f.FileName}:{f.Length}")); + var fieldNames = string.Join(",", form.Keys); + var meta = form["imgMeta"].ToString(); + if (string.IsNullOrWhiteSpace(meta)) + meta = form["imageMeta"].ToString(); + if (meta.Length > 512) + meta = meta[..512]; + + logger.LogInformation( + "[images] {Endpoint} metadata player={PlayerId} files={Files} fields={Fields} profile={Profile} imgMeta={ImgMeta}", + endpoint, + playerId, + fileNames, + fieldNames, + isProfile, + meta); + } + + private static bool IsProfileThumbnail(IFormCollection? form) + { + if (form is null) return false; + + static bool IsProfileValue(string? value) + { + if (string.IsNullOrWhiteSpace(value)) return false; + return value.Trim().Equals("4", StringComparison.OrdinalIgnoreCase) + || value.Contains("ProfileThumbnail", StringComparison.OrdinalIgnoreCase); + } + + if (IsProfileValue(form["SavedImageType"].ToString()) + || IsProfileValue(form["savedImageType"].ToString()) + || IsProfileValue(form["ImageType"].ToString()) + || IsProfileValue(form["imageType"].ToString())) + return true; + + var meta = form["imgMeta"].ToString(); + if (string.IsNullOrWhiteSpace(meta)) + meta = form["imageMeta"].ToString(); + if (string.IsNullOrWhiteSpace(meta)) + return false; + + if (IsProfileValue(meta)) + return true; + + try + { + using var doc = JsonDocument.Parse(meta); + if (doc.RootElement.ValueKind != JsonValueKind.Object) + return false; + foreach (var name in new[] { "savedImageType", "SavedImageType", "imageType", "ImageType" }) + { + if (!doc.RootElement.TryGetProperty(name, out var value)) + continue; + if (value.ValueKind == JsonValueKind.Number && value.TryGetInt32(out var n) && n == 4) + return true; + if (value.ValueKind == JsonValueKind.String && IsProfileValue(value.GetString())) + return true; + } + } + catch (JsonException) + { + } + + return false; + } + + /// Build the response shape every images/v* endpoint + /// returns. PascalCase keys + redundant aliases so whichever JSON + /// parser the 2020 watch uses, it finds the field it wants. The + /// most important keys are AccountId, ImageName, CreatedAt — those + /// are what the watch's strict deserialiser reads. Url + /// is built against the configured deployment apex (DORKNET_DOMAIN) + /// so every callsite for the URL string flows through one source + /// of truth. + private object BuildImageInfo(PhotoEntity p, long accountId) + { + var cdnHost = domain.Sub("cdn"); + // Accessibility maps from IsPublic so the 2020.12 watch's IBANLCLBGLM + // deserialiser reads a non-default value (0=Private, 1=Public). + var accessibility = p.IsPublic ? 1 : 0; + return new + { + // 2020.12 watch's IBANLCLBGLM strict keys (per + // docs/recroom-2020-client-response-contracts.md:1048). + // Missing any of these throws KeyNotFoundException in LitJson + // and the gallery empties out. + Id = p.Id, + ImageName = p.BlobName, + AccountId = (int)accountId, + // Server-side mirror of AccountId as PlayerId — some + // call-sites read PlayerId (lowercase id) instead of + // AccountId. Including both is harmless on the watch. + PlayerId = (int)accountId, + RoomId = p.RoomId, + // PlayerEventId is always 0 here (photo-event linkage isn't + // tracked yet); the field still has to be present so the + // strict deserialiser doesn't bail. + PlayerEventId = 0L, + Accessibility = accessibility, + // AccessibilityLocked = whether the watch can flip the + // privacy toggle. We don't gate on this server-side so the + // value is always false (UI is free to toggle). + AccessibilityLocked = false, + // ImageType: 0=ScreenShot, 1=ProfileImage, 2=ImageOfMe, … + // we don't differentiate yet — surface 0 unconditionally. + Type = 0, + ImageType = 0, + CreatedAt = p.CreatedAt, + TaggedPlayerIds = ParseTagged(p.TaggedPlayerIdsCsv).ToArray(), + CheerCount = p.CheerCount, + // We don't track per-photo comments yet — surface 0 for + // the strict-key requirement. + CommentCount = 0, + // Bonus fields — useful for other clients (and our own feed UI). + Filename = p.BlobName, + Url = $"https://{cdnHost}/{p.BlobName}", + Caption = p.Caption, + Description = p.Caption, + IsPublic = p.IsPublic, + ViewCount = p.ViewCount, + }; + } + + private static IEnumerable ParseTagged(string? csv) => + string.IsNullOrWhiteSpace(csv) + ? Enumerable.Empty() + : csv.Split(',', StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries) + .Select(s => long.TryParse(s, out var v) ? v : 0L) + .Where(v => v > 0); + + private Task NotifyAccountImageChangedAsync(PlayerEntity player) + { + var account = new DorkNet.Models.Auth.RecNetAccount + { + AccountId = (int)player.Id, + RawUsername = player.Username, + Username = player.Username, + DisplayName = player.DisplayName ?? player.Username, + ProfileImage = player.ProfileImageName ?? string.Empty, + TreatAsJunior = player.IsJunior, + HasBirthday = true, + Platforms = 1, + }; + var selfAccount = new DorkNet.Models.Auth.RecNetSelfAccount + { + AccountId = account.AccountId, + RawUsername = account.RawUsername, + Username = account.Username, + DisplayName = account.DisplayName, + ProfileImage = account.ProfileImage, + TreatAsJunior = account.TreatAsJunior, + HasBirthday = account.HasBirthday, + Platforms = account.Platforms, + Email = player.Email ?? string.Empty, + Phone = player.Phone ?? string.Empty, + Birthday = player.Birthday, + JuniorState = player.IsJunior ? 1 : 0, + ParentAccountId = null, + }; + return Task.WhenAll( + notifications.NotifyTypedAsync(player.Id, "AccountUpdate", account), + notifications.NotifyTypedAsync(player.Id, "SelfAccountUpdate", selfAccount)); + } + + private static string NormaliseTaggedIds(string? csv, long exceptId) + { + if (string.IsNullOrWhiteSpace(csv)) return string.Empty; + var ids = csv.Split(',', StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries) + .Select(s => long.TryParse(s, out var v) ? v : 0L) + .Where(v => v > 0 && v != exceptId) + .Distinct() + .ToList(); + return string.Join(",", ids); + } +} diff --git a/DorkNet.Server/Controllers/API/Images/V2/ImagesController.cs b/DorkNet.Server/Controllers/API/Images/V2/ImagesController.cs new file mode 100644 index 0000000..0b7d4c3 --- /dev/null +++ b/DorkNet.Server/Controllers/API/Images/V2/ImagesController.cs @@ -0,0 +1,39 @@ +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Mvc; +using System.Security.Claims; + +namespace DorkNet.Server.Controllers.API.Images.V2; + +[ApiController] +[Route("api/[controller]/v2")] +[Authorize] +public class ImagesController : ControllerBase +{ + private static readonly string ImageDir = + Path.Combine(AppContext.BaseDirectory, "data", "images"); + + [HttpPost("upload")] + public async Task> Upload(IFormFile file) + { + Directory.CreateDirectory(ImageDir); + + var ext = Path.GetExtension(file.FileName).ToLowerInvariant(); + if (ext is not (".png" or ".jpg" or ".jpeg")) + return BadRequest("Only PNG/JPG accepted"); + + var name = $"{Guid.NewGuid():N}{ext}"; + var path = Path.Combine(ImageDir, name); + + await using var stream = System.IO.File.Create(path); + await file.CopyToAsync(stream); + + var baseUrl = $"{Request.Scheme}://{Request.Host}"; + return Ok(new UploadImageResponse { ImageName = name, Url = $"{baseUrl}/img/{name}" }); + } +} + +public class UploadImageResponse +{ + public string ImageName { get; set; } = string.Empty; + public string Url { get; set; } = string.Empty; +} diff --git a/DorkNet.Server/Controllers/API/Inventions/InventionsController.cs b/DorkNet.Server/Controllers/API/Inventions/InventionsController.cs new file mode 100644 index 0000000..bbfba6c --- /dev/null +++ b/DorkNet.Server/Controllers/API/Inventions/InventionsController.cs @@ -0,0 +1,697 @@ +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Mvc; +using Microsoft.EntityFrameworkCore; +using DorkNet.Models.Notification; +using DorkNet.Server.Auth; +using DorkNet.Server.Data; +using DorkNet.Server.Data.Entities; +using DorkNet.Server.Services; + +namespace DorkNet.Server.Controllers.API.Inventions; + +/// +/// api.rec.net/api/inventions/* — Maker-Pen creations the player can +/// re-spawn or share. URL surface + verbs verified against +/// Cpp2IL_ISIL/.../RecNet/Inventions.txt: the "update*" +/// routes use Core.Get (GET) despite being mutations, so we +/// expose them as [HttpGet]; only cheer, report, +/// settags, save, addversion, and batch +/// are POST. delete, publish, unpublish, +/// download are also GET. +/// +/// Wire DTO matches the watch's OBBBPCBIMME.PPGFHEDFBEA +/// deserializer (Cpp2IL ISIL OBBBPCBIMME.txt:588-704): keys +/// InventionId, ReplicationId, CreatorPlayerId, Name, Description, +/// ImageName, CurrentVersionNumber, IsPublished, AllowTrial, +/// ModifiedAt, CreatedAt, FirstPublishedAt, CreationRoomId, +/// NumPlayersHaveUsedInRoom, NumDownloads, CheerCount, +/// CreatorPermission, GeneralPermission, IsAGInvention, Price, +/// HideFromPlayer. Casing is significant — note IsAGInvention +/// has a capital G. +/// +[ApiController] +public class InventionsController( + DorkNetDbContext db, + LevelService level, + NotificationService notifications) : ControllerBase +{ + private long? CurrentPlayerIdOrNull => this.CurrentPlayerId(); + private long CurrentPlayerId => this.RequireCurrentPlayerId(); + + // ── Browse ─────────────────────────────────────────────────────────── + + [HttpGet("api/inventions/v3/popular")] + public async Task Popular([FromQuery] int take = 50) + { + take = Math.Clamp(take, 1, 100); + var rows = await db.Inventions + .Where(i => !i.IsDeleted && i.IsPublished) + .OrderByDescending(i => i.CheerCount) + .Take(take) + .ToListAsync(); + return Ok(rows.Select(ToWire)); + } + + [HttpGet("api/inventions/v3/saved")] + [Authorize] + public async Task Saved([FromQuery] int take = 50) + { + take = Math.Clamp(take, 1, 100); + var pid = CurrentPlayerId; + var rows = await db.Inventions + .Where(i => !i.IsDeleted && i.CreatorPlayerId == pid) + .OrderByDescending(i => i.UpdatedAt) + .Take(take) + .ToListAsync(); + return Ok(rows.Select(ToWire)); + } + + [HttpGet("api/inventions/v3/created")] + [Authorize] + public Task Created([FromQuery] int take = 50) => Saved(take); + + /// GetLocalPlayerInventions — same as Saved but + /// at v1 path. Watch hits both depending on tab. + [HttpGet("api/inventions/v1/mine")] + [Authorize] + public Task Mine() => Saved(100); + + [HttpGet("api/inventions/v1/search")] + public async Task Search([FromQuery] string value = "") + { + if (string.IsNullOrWhiteSpace(value)) + return Ok(Array.Empty()); + + // The watch's @-prefix means "search by player". Two callers + // use this with different payload shapes — both end up here: + // * PlayerDetailsWatchUIFlow.SetAccount → @ (numeric) + // loads the Inventions tab on a player profile. Verified + // in Cpp2IL — see PlayerDetailsWatchUIFlow.txt:1867,1950. + // * Inventions search UI → @ (text) free-text + // prefix match on Username. + if (value.StartsWith('@')) + { + var tail = value[1..]; + List creators; + if (long.TryParse(tail, out var accountId)) + { + creators = new List { accountId }; + } + else + { + creators = await db.Players + .Where(p => p.Username.StartsWith(tail)) + .Select(p => p.Id).Take(50).ToListAsync(); + } + var byCreator = await db.Inventions + .Where(i => !i.IsDeleted && i.IsPublished + && creators.Contains(i.CreatorPlayerId)) + .OrderByDescending(i => i.CheerCount) + .Take(50) + .ToListAsync(); + return Ok(byCreator.Select(ToWire)); + } + + var v = value.ToLowerInvariant(); + var rows = await db.Inventions + .Where(i => !i.IsDeleted && i.IsPublished + && (i.Name.ToLower().Contains(v) + || i.TagsCsv.ToLower().Contains(v))) + .OrderByDescending(i => i.CheerCount) + .Take(50) + .ToListAsync(); + return Ok(rows.Select(ToWire)); + } + + public sealed record InventionBatchRequest(List InventionIds); + + [HttpPost("api/inventions/v1/batch")] + public async Task Batch([FromBody] InventionBatchRequest req) + { + if (req?.InventionIds is null || req.InventionIds.Count == 0) + return Ok(Array.Empty()); + var ids = req.InventionIds.Take(200).ToList(); + var rows = await db.Inventions + .Where(i => !i.IsDeleted && ids.Contains(i.Id)) + .ToListAsync(); + return Ok(rows.Select(ToWire)); + } + + /// GET api/inventions/v1/tagfilters — drives + /// RecNet.Inventions.GetTags. The watch deserialises the + /// response as RecNet.GetFiltersResponse (an + /// IRecNetObject with PinnedFilters + + /// PopularFilters string-list properties), then a + /// Func<GetFiltersResponse, List<string>> + /// projects whichever list the caller wanted. Verified at + /// Cpp2IL_ISIL/.../RecNet/Inventions.txt:4834-4852 — the URL + /// literal is followed by a typeof(Func`2<GetFiltersResponse, + /// List`1<String>>). Returning a bare string array + /// (the previous shape) tripped the LitJson importer's + /// List → Dictionary cast inside Util.Deserialize<T> + /// and surfaced as "Failed to get tags: Malformed Response" + /// (output_log.txt:1602+1639) — and that bubbles up as the + /// dorm-load destabilisation we've been hunting. + [HttpGet("api/inventions/v1/tagfilters")] + public IActionResult TagFilters() + { + var tags = new[] + { + "sport", "game", "vehicle", "weapon", "decor", "tool", + "art", "music", "puzzle", "combat", "build", "race", + }; + return Ok(new + { + PinnedFilters = tags, + PopularFilters = tags, + }); + } + + [HttpGet("api/inventions/v1/creatorIds")] + public IActionResult CreatorIdsInRoom([FromQuery] long roomId) + { + // We don't yet track per-room invention placements; return + // empty list (correct shape: List). + _ = roomId; + return Ok(Array.Empty()); + } + + /// GET api/inventions/v1/fulllineageowner — + /// raw Boolean. The client uses this to decide whether a set of + /// referenced inventions can be saved together under one owner. + /// DorkNet tracks current creator ownership, so the concrete rule + /// here is: every requested invention id must exist and share the + /// same CreatorPlayerId. + [HttpGet("api/inventions/v1/fulllineageowner")] + public async Task FullLineageOwner() + { + var ids = Request.Query + .SelectMany(q => q.Value) + .SelectMany(v => (v ?? string.Empty).Split(',', StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries)) + .Select(v => long.TryParse(v, out var id) ? id : 0L) + .Where(id => id > 0) + .Distinct() + .Take(200) + .ToList(); + + if (ids.Count == 0) return Content("true", "application/json"); + + var owners = await db.Inventions + .Where(i => !i.IsDeleted && ids.Contains(i.Id)) + .Select(i => new { i.Id, i.CreatorPlayerId }) + .ToListAsync(); + var ok = owners.Count == ids.Count + && owners.Select(o => o.CreatorPlayerId).Distinct().Count() <= 1; + return Content(ok ? "true" : "false", "application/json"); + } + + // ── Single fetch ───────────────────────────────────────────────────── + + /// GET /api/inventions/v1/room?id={roomId} — every + /// invention used by the room. The 2020.12 watch fires this on + /// EVERY room load right after fetching the room data blob; it uses + /// the response to pre-resolve invention references the blob will + /// spawn. Without this, imported rooms with invention-based + /// geometry (floors, walls, custom shapes) render with everything + /// missing — the watch silently skips invention spawns when it + /// has no list, and you fall through the void. + /// + /// We match on which + /// the zip importer populates from each Invention.json's + /// CreationRoomId field. Returns an array of + /// -shaped Invention objects (same wire shape + /// the single-fetch endpoint returns) so the watch's existing + /// deserializer works. + [HttpGet("api/inventions/v1/room")] + public async Task ByRoom([FromQuery] long id) + { + // The watch sends our local room id, but zip-imported inventions + // carry the ORIGINAL Rec Room room id in CreationRoomId (the + // importer preserves it so cross-zip references survive). Match + // by name first to find any candidate original ids, then union + // the lookup. For dorm/RR-Original rooms (CreatorPlayerId=1, + // never imported), CreationRoomId equals the local id directly. + var roomName = await db.Rooms + .Where(r => r.Id == id) + .Select(r => r.Name) + .FirstOrDefaultAsync(); + var inventions = await db.Inventions + .Where(i => !i.IsDeleted + && (i.CreationRoomId == id + // Imported rooms: the invention has CreationRoomId = + // the original Rec Room id. We can't reverse-resolve + // without an OriginalRoomId column, so use + // ReplicationId-based name match: every invention + // imported alongside this room has CreatorPlayerId + // matching the room creator AND CreationRoomId set + // to a non-zero value that isn't an RR-Original. + // Loose match — accept any invention whose creator + // is the room creator and CreationRoomId is "the same + // remote id" for that room. We approximate by + // looking at the most-frequent CreationRoomId among + // inventions whose creator matches the room creator + // and joining on that. + || (roomName != null && db.Inventions + .Where(j => !j.IsDeleted + && j.CreatorPlayerId == db.Rooms + .Where(r => r.Id == id) + .Select(r => r.CreatorPlayerId) + .FirstOrDefault() + && j.CreationRoomId != null + && j.CreationRoomId != id) + .GroupBy(j => j.CreationRoomId) + .OrderByDescending(g => g.Count()) + .Select(g => g.Key) + .FirstOrDefault() == i.CreationRoomId) + )) + .ToListAsync(); + return Ok(inventions.Select(ToWire).ToList()); + } + + [HttpGet("api/inventions/v1")] + public async Task SingleByQuery([FromQuery] long inventionId) + { + var i = await db.Inventions.FirstOrDefaultAsync(x => x.Id == inventionId && !x.IsDeleted); + if (i is null) return NotFound(); + if (!i.IsPublished && i.CreatorPlayerId != CurrentPlayerIdOrNull) + return Forbid(); + return Ok(ToWire(i)); + } + + [HttpGet("api/inventions/v1/details")] + public async Task Details([FromQuery] long inventionId) + { + var i = await db.Inventions.FirstOrDefaultAsync(x => x.Id == inventionId && !x.IsDeleted); + if (i is null) return NotFound(); + if (!i.IsPublished && i.CreatorPlayerId != CurrentPlayerIdOrNull) + return Forbid(); + var versions = await db.InventionVersions + .Where(v => v.InventionId == inventionId) + .OrderByDescending(v => v.VersionNumber) + .ToListAsync(); + return Ok(new + { + Invention = ToWire(i), + Versions = versions.Select(ToVersionWire), + }); + } + + [HttpGet("api/inventions/v1/personaldetails/{id:long}")] + [Authorize] + public async Task PersonalDetails(long id) + { + var i = await db.Inventions.FirstOrDefaultAsync(x => x.Id == id && !x.IsDeleted); + if (i is null) return NotFound(); + return Ok(new + { + Invention = ToWire(i), + CanEdit = i.CreatorPlayerId == CurrentPlayerId, + }); + } + + [HttpGet("api/inventions/v1/versions")] + public async Task Versions([FromQuery] long inventionId) + { + var rows = await db.InventionVersions + .Where(v => v.InventionId == inventionId) + .OrderByDescending(v => v.VersionNumber) + .ToListAsync(); + return Ok(rows.Select(ToVersionWire)); + } + + [HttpGet("api/inventions/v1/version")] + public async Task Version( + [FromQuery] long inventionId, [FromQuery] int version) + { + var v = await db.InventionVersions + .FirstOrDefaultAsync(x => x.InventionId == inventionId && x.VersionNumber == version); + if (v is null) return NotFound(); + return Ok(ToVersionWire(v)); + } + + // ── Create / version ───────────────────────────────────────────────── + + public sealed class SaveInventionV4Request + { + public string? Name { get; set; } + public string? Description { get; set; } + public string? ImageName { get; set; } + public int InstantiationCost { get; set; } + public int LightsCost { get; set; } + public int AiCost { get; set; } + public long? CreationRoomId { get; set; } + public string? InventionDataFilename { get; set; } + public List? ReferencedInventions { get; set; } + public int CreatorAccountRole { get; set; } + } + + /// POST api/inventions/v4/save — 2020.12 maker-pen + /// save flow. v4 wraps the new invention plus its first version in + /// a Status/Invention/InventionVersion object. + [HttpPost("api/inventions/v4/save")] + [Authorize] + public async Task SaveV4([FromBody] SaveInventionV4Request req) + { + var pid = CurrentPlayerId; + var name = (req.Name ?? string.Empty).Trim(); + if (string.IsNullOrWhiteSpace(name)) return BadRequest(new { Status = 1, Error = "missing name" }); + + var blobName = (req.InventionDataFilename ?? string.Empty).Trim(); + if (string.IsNullOrWhiteSpace(blobName)) return BadRequest(new { Status = 1, Error = "missing inventionDataFilename" }); + + var inv = new InventionEntity + { + CreatorPlayerId = pid, + ReplicationId = Guid.NewGuid().ToString("D"), + Name = name, + Description = req.Description ?? string.Empty, + ImageName = req.ImageName ?? string.Empty, + Permission = 0, + CurrentBlobName = blobName, + TagsCsv = string.Empty, + CreationRoomId = req.CreationRoomId, + CreatorPermission = 100, + GeneralPermission = 0, + IsPublished = false, + CurrentVersionNumber = 1, + }; + db.Inventions.Add(inv); + await db.SaveChangesAsync(); + + var v1 = new InventionVersionEntity + { + InventionId = inv.Id, + ReplicationId = Guid.NewGuid().ToString("D"), + VersionNumber = 1, + BlobName = blobName, + InstantiationCost = Math.Max(0, req.InstantiationCost), + LightsCost = Math.Max(0, req.LightsCost), + }; + db.InventionVersions.Add(v1); + + if (req.ReferencedInventions is { Count: > 0 }) + { + foreach (var referencedId in req.ReferencedInventions.Distinct().Take(200)) + { + db.ObjectiveProgress.Add(new ObjectiveProgressEntity + { + PlayerId = pid, + Key = $"invention:{inv.Id}:references:{referencedId}", + IsCompleted = true, + ClearedAt = DateTime.UtcNow, + }); + } + } + + await db.SaveChangesAsync(); + await level.AwardXpAsync(pid, LevelService.InventionSavedXp, $"invention_save:{inv.Id}"); + + return Ok(new + { + Status = 0, + Invention = ToWireV4(inv), + InventionVersion = ToVersionWire(v1), + }); + } + + public sealed record AddVersionRequest( + long InventionId, string BlobName, + int? InstantiationCost, int? LightsCost); + + [HttpPost("api/inventions/v3/addversion")] + [Authorize] + public async Task AddVersion([FromBody] AddVersionRequest req) + { + var pid = CurrentPlayerId; + var inv = await db.Inventions.FirstOrDefaultAsync(x => x.Id == req.InventionId && !x.IsDeleted); + if (inv is null) return NotFound(); + if (inv.CreatorPlayerId != pid) return Forbid(); + if (string.IsNullOrWhiteSpace(req.BlobName)) return BadRequest("missing BlobName"); + + var nextVer = inv.CurrentVersionNumber + 1; + inv.CurrentBlobName = req.BlobName; + inv.CurrentVersionNumber = nextVer; + inv.UpdatedAt = DateTime.UtcNow; + + db.InventionVersions.Add(new InventionVersionEntity + { + InventionId = inv.Id, + ReplicationId = Guid.NewGuid().ToString("D"), + VersionNumber = nextVer, + BlobName = req.BlobName, + InstantiationCost = req.InstantiationCost ?? 0, + LightsCost = req.LightsCost ?? 0, + }); + await db.SaveChangesAsync(); + return Ok(ToWire(inv)); + } + + // ── GET-based mutations (Core.Get pattern) ─────────────────────────── + + /// Watch sends ONE of name/description/imgName/permission + /// per request — `UpdateInvention*` ISIL builds different + /// query strings. Pull whichever ones came in and apply. + [HttpGet("api/inventions/v1/update")] + [Authorize] + public async Task Update( + [FromQuery] long inventionId, + [FromQuery] string? name, + [FromQuery] string? description, + [FromQuery] string? imgName, + [FromQuery] int? permission) + { + var pid = CurrentPlayerId; + var inv = await db.Inventions.FirstOrDefaultAsync(x => x.Id == inventionId && !x.IsDeleted); + if (inv is null) return NotFound(); + if (inv.CreatorPlayerId != pid) return Forbid(); + + if (!string.IsNullOrWhiteSpace(name)) inv.Name = name.Trim(); + if (description is not null) inv.Description = description; + if (imgName is not null) inv.ImageName = imgName; + if (permission.HasValue) + { + inv.GeneralPermission = ClampInventionPermission(permission.Value); + if (inv.GeneralPermission > 0 && !inv.IsPublished) + { + inv.IsPublished = true; + inv.FirstPublishedAt = DateTime.UtcNow; + } + // Mirror legacy 0/1/2 column for back-compat. + inv.Permission = inv.GeneralPermission >= 60 ? 2 : (inv.GeneralPermission >= 20 ? 1 : 0); + } + inv.UpdatedAt = DateTime.UtcNow; + await db.SaveChangesAsync(); + return Ok(ToWire(inv)); + } + + [HttpGet("api/inventions/v1/delete")] + [Authorize] + public async Task Delete([FromQuery] long inventionId) + { + var pid = CurrentPlayerId; + var inv = await db.Inventions.FirstOrDefaultAsync(x => x.Id == inventionId); + if (inv is null) return NotFound(); + if (inv.CreatorPlayerId != pid) return Forbid(); + inv.IsDeleted = true; + inv.GeneralPermission = 0; + inv.IsPublished = false; + inv.UpdatedAt = DateTime.UtcNow; + await db.SaveChangesAsync(); + return Ok(ToWire(inv)); + } + + [HttpGet("api/inventions/v3/publish")] + [Authorize] + public async Task Publish( + [FromQuery] long inventionId, [FromQuery] int permissionLevel) + { + var pid = CurrentPlayerId; + var inv = await db.Inventions.FirstOrDefaultAsync(x => x.Id == inventionId && !x.IsDeleted); + if (inv is null) return NotFound(); + if (inv.CreatorPlayerId != pid) return Forbid(); + var perm = ClampInventionPermission(permissionLevel); + if (perm == 0) return BadRequest("permissionLevel must be > 0 (Unassigned)"); + inv.GeneralPermission = perm; + inv.IsPublished = true; + inv.FirstPublishedAt ??= DateTime.UtcNow; + inv.Permission = perm >= 60 ? 2 : (perm >= 20 ? 1 : 0); + inv.UpdatedAt = DateTime.UtcNow; + await db.SaveChangesAsync(); + return Ok(ToWire(inv)); + } + + [HttpGet("api/inventions/v1/unpublish")] + [Authorize] + public async Task Unpublish([FromQuery] long inventionId) + { + var pid = CurrentPlayerId; + var inv = await db.Inventions.FirstOrDefaultAsync(x => x.Id == inventionId && !x.IsDeleted); + if (inv is null) return NotFound(); + if (inv.CreatorPlayerId != pid) return Forbid(); + inv.GeneralPermission = 0; + inv.IsPublished = false; + inv.Permission = 0; + inv.UpdatedAt = DateTime.UtcNow; + await db.SaveChangesAsync(); + return Ok(ToWire(inv)); + } + + [HttpGet("api/inventions/v1/download")] + public async Task Download([FromQuery] long inventionId) + { + var inv = await db.Inventions.FirstOrDefaultAsync(x => x.Id == inventionId && !x.IsDeleted); + if (inv is null) return NotFound(); + inv.SpawnCount += 1; + var pid = CurrentPlayerIdOrNull; + if (pid is long me && me != inv.CreatorPlayerId) + { + // NumPlayersHaveUsedInRoom — increment per-distinct-player. + // Approximate by distinct cheer/check; for now bump once + // per download by non-creator. + inv.NumPlayersHaveUsedInRoom += 1; + } + inv.UpdatedAt = DateTime.UtcNow; + await db.SaveChangesAsync(); + return Ok(ToWire(inv)); + } + + // ── POST mutations ─────────────────────────────────────────────────── + + public sealed record SetTagsRequest(long InventionId, string? AutoTags, string? PlayerAddedTags); + + [HttpPost("api/inventions/v1/settags")] + [Authorize] + public async Task SetTags([FromBody] SetTagsRequest req) + { + var pid = CurrentPlayerId; + var inv = await db.Inventions.FirstOrDefaultAsync(x => x.Id == req.InventionId && !x.IsDeleted); + if (inv is null) return NotFound(); + if (inv.CreatorPlayerId != pid) return Forbid(); + var combined = string.Join(',', new[] { req.AutoTags, req.PlayerAddedTags } + .Where(s => !string.IsNullOrWhiteSpace(s))).Trim(','); + inv.TagsCsv = combined; + inv.UpdatedAt = DateTime.UtcNow; + await db.SaveChangesAsync(); + return Ok(ToWire(inv)); + } + + public sealed record CheerRequest(long InventionId); + + [HttpPost("api/inventions/v1/cheer")] + [Authorize] + public async Task Cheer([FromBody] CheerRequest req) + { + var pid = CurrentPlayerId; + var inv = await db.Inventions.FirstOrDefaultAsync(x => x.Id == req.InventionId && !x.IsDeleted); + if (inv is null) return NotFound(); + + // Idempotent: one cheer per (player, invention). + var existing = await db.Cheers.FirstOrDefaultAsync(c => + c.FromPlayerId == pid && c.TargetInventionId == req.InventionId); + if (existing is null) + { + db.Cheers.Add(new CheerEntity + { + FromPlayerId = pid, + TargetInventionId = req.InventionId, + CheeredAt = DateTime.UtcNow, + }); + inv.CheerCount += 1; + await db.SaveChangesAsync(); + + if (inv.CreatorPlayerId != pid) + await notifications.NotifyAsync(inv.CreatorPlayerId, + PushNotificationId.InventionModerationStateChanged, + new { Type = "cheer", inv.Id, From = pid, inv.CheerCount }); + } + return Ok(new { inv.Id, inv.CheerCount }); + } + + public sealed record ReportInventionRequest( + long InventionId, int? ReportCategory, string? Details); + + [HttpPost("api/inventions/v1/report")] + [Authorize] + public async Task Report([FromBody] ReportInventionRequest req) + { + var pid = CurrentPlayerId; + var inv = await db.Inventions.FirstOrDefaultAsync(x => x.Id == req.InventionId && !x.IsDeleted); + if (inv is null) return NotFound(); + + db.Reports.Add(new ReportEntity + { + ReporterPlayerId = pid, + TargetPlayerId = inv.CreatorPlayerId, + TargetInventionId = req.InventionId, + Category = req.ReportCategory ?? 5 /* Other */, + Message = (req.Details ?? string.Empty)[..Math.Min(1000, (req.Details ?? string.Empty).Length)], + }); + await db.SaveChangesAsync(); + return Ok(new { Reported = true }); + } + + // ── Wire serializers ───────────────────────────────────────────────── + + private static int ClampInventionPermission(int v) => v switch + { + 0 or 10 or 20 or 40 or 60 or 80 or 100 => v, + _ => 0, + }; + + // Wire shape verified against the 2020.12 watch's + // OBBBPCBIMME.PPGFHEDFBEA deserializer (Cpp2IL ISIL OBBBPCBIMME.txt + // lines 588-704). All keys read via Util.GetKey are required — + // a missing key throws KeyNotFoundException → the watch logs + // "Received malformed RecNet response" and aborts the join/fetch. + // + // Required (in disasm order): + // InventionId, ReplicationId, CreatorPlayerId, Name, Description, + // ImageName, CurrentVersionNumber, IsPublished, AllowTrial, + // ModifiedAt, CreatedAt, NumPlayersHaveUsedInRoom, NumDownloads, + // CheerCount, CreatorPermission, GeneralPermission, IsAGInvention, + // Price, HideFromPlayer. + // Optional (Util.GetKeyOrDefault): FirstPublishedAt, CreationRoomId. + // + // Casing is significant — LitJson + Util.GetKey are case-sensitive. + // In particular IsAGInvention has a capital G (matches the asset's + // "IsAGInvention" property, not the entity column IsAgInvention). + private static object ToWire(InventionEntity i) => new + { + InventionId = i.Id, + ReplicationId = string.IsNullOrEmpty(i.ReplicationId) + ? Guid.Empty.ToString("D") : i.ReplicationId, + // Wire field is int — IDs in this server are <2^31 so safe. + CreatorPlayerId = (int)i.CreatorPlayerId, + i.Name, + i.Description, + i.ImageName, + i.CurrentVersionNumber, + i.IsPublished, + AllowTrial = true, + ModifiedAt = i.UpdatedAt, + i.CreatedAt, + i.FirstPublishedAt, + i.CreationRoomId, + i.NumPlayersHaveUsedInRoom, + NumDownloads = i.SpawnCount, + i.CheerCount, + i.CreatorPermission, + i.GeneralPermission, + IsAGInvention = i.IsAgInvention, + Price = 0, + HideFromPlayer = false, + }; + + private static object ToWireV4(InventionEntity i) => ToWire(i); + + private static object ToVersionWire(InventionVersionEntity v) => new + { + v.InventionId, + ReplicationId = string.IsNullOrEmpty(v.ReplicationId) + ? Guid.Empty.ToString("D") : v.ReplicationId, + v.VersionNumber, + v.InstantiationCost, + v.LightsCost, + v.BlobName, + }; +} diff --git a/DorkNet.Server/Controllers/API/Inventory/InventoryController.cs b/DorkNet.Server/Controllers/API/Inventory/InventoryController.cs new file mode 100644 index 0000000..bc13d0b --- /dev/null +++ b/DorkNet.Server/Controllers/API/Inventory/InventoryController.cs @@ -0,0 +1,263 @@ +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Mvc; +using Microsoft.EntityFrameworkCore; +using DorkNet.Models.Notification; +using DorkNet.Server.Auth; +using DorkNet.Server.Data; +using DorkNet.Server.Data.Entities; +using DorkNet.Server.Services; + +namespace DorkNet.Server.Controllers.API.Inventory; + +/// +/// api.rec.net/api/{equipment,consumables}/* — backs the watch's +/// Backpack tab. Wire DTOs match the decompiled client classes: +/// +/// • RecNet.Equipment — fields: PrefabName, ModificationGuid, +/// PlatformMask, IsPlatformLocked, FriendlyName, Tooltip, Rarity, +/// Favorited. +/// • RecNet.Consumable — fields: Id(long), ConsumableType, +/// PlatformMask, Count, InitialCount, UnlockedLevel, CreatedAt, +/// ActiveDurationMinutes(int?), ConsumableItem(nested), Category, +/// IsActive, IsPlatformLocked. +/// +/// Persistence: one row per +/// (player, item). For equipment, Quantity=1 + IsActive tracks +/// favourite. For consumables, Quantity is the stack count + IsActive +/// tracks "currently activated". +/// +[ApiController] +[Authorize] +public class InventoryController( + DorkNetDbContext db, + NotificationService notifications) : ControllerBase +{ + private long Me => this.RequireCurrentPlayerId(); + + // Categories the StoreItem table uses for equipment vs + // consumables. The seed data tags items per these strings. + private static readonly HashSet EquipmentCategories = new() + { + "equipment", "tool", "maker_pen", "weapon", + }; + private static readonly HashSet ConsumableCategories = new() + { + "consumable", + }; + + // ── Equipment ──────────────────────────────────────────────────────── + + [HttpGet("api/equipment/v1/getUnlocked")] + [HttpGet("api/equipment/v2/getUnlocked")] + [HttpGet("api/equipment/v3/getUnlocked")] + public async Task GetUnlockedEquipment() + { + var pid = Me; + var rows = await (from inv in db.PlayerInventory + join item in db.StoreItems on inv.ItemSlug equals item.Slug + where inv.PlayerId == pid + && EquipmentCategories.Contains(item.Category) + select new { inv, item }).ToListAsync(); + return Ok(rows.Select(r => ToEquipmentWire(r.inv, r.item))); + } + + public sealed class EquipmentUpdateRequest + { + public string PrefabName { get; set; } = string.Empty; + public string ModificationGuid { get; set; } = string.Empty; + public bool Favorited { get; set; } + } + + /// POST api/equipment/v1/update — toggle the + /// "favorited" flag on a piece of equipment (the watch lets the + /// player pin commonly-used tools). + [HttpPost("api/equipment/v1/update")] + public async Task UpdateEquipment([FromBody] EquipmentUpdateRequest req) + { + var pid = Me; + // Catalog-backed skins use a compound slug so we can round-trip + // the client's separate PrefabName + ModificationGuid fields. + var slug = !string.IsNullOrWhiteSpace(req.ModificationGuid) + ? StoreService.EquipmentSkinSlug(req.PrefabName, req.ModificationGuid) + : req.PrefabName; + if (string.IsNullOrWhiteSpace(slug)) return BadRequest("missing prefab/mod identifier"); + + var row = await db.PlayerInventory + .FirstOrDefaultAsync(p => p.PlayerId == pid && p.ItemSlug == slug); + if (row is null && !string.IsNullOrWhiteSpace(req.ModificationGuid)) + { + // Legacy rows created before catalog-backed equipment skins + // used the raw modification GUID as ItemSlug. + row = await db.PlayerInventory + .FirstOrDefaultAsync(p => p.PlayerId == pid && p.ItemSlug == req.ModificationGuid); + } + if (row is null && !string.IsNullOrWhiteSpace(req.PrefabName)) + { + row = await db.PlayerInventory + .FirstOrDefaultAsync(p => p.PlayerId == pid && p.ItemSlug == req.PrefabName); + } + if (row is null) + { + // Player doesn't own this; reject rather than silently + // creating ownership (avoids letting clients grant items + // to themselves). + return Forbid(); + } + row.IsActive = req.Favorited; + await db.SaveChangesAsync(); + return Ok(new { Updated = true }); + } + + // ── Consumables ────────────────────────────────────────────────────── + + // 2020.12 client bumped the URL to v2 AND changed the response shape from + // per-item to BulkConsumable: each entry groups N instances of the same + // ConsumableItemDesc with parallel Ids[] / CreatedAts[] arrays. Deserializer + // is PLHFAEBLDAE.PPGFHEDFBEA reading PASCALCASE keys: Ids, CreatedAts, + // ConsumableItemDesc, Count, InitialCount, IsActive, ActiveDurationMinutes, + // IsTransferable. Returning the v1 per-item shape on v2 throws "Malformed + // Response" and the chain calls BootSequence.OINIBKFNFAJ — fatal kick. + [HttpGet("api/consumables/v1/getUnlocked")] + public async Task GetUnlockedConsumablesV1() + { + var pid = Me; + var rows = await (from inv in db.PlayerInventory + join item in db.StoreItems on inv.ItemSlug equals item.Slug + where inv.PlayerId == pid + && ConsumableCategories.Contains(item.Category) + select new { inv, item }).ToListAsync(); + return Ok(rows.Select(r => ToConsumableWire(r.inv, r.item))); + } + + [HttpGet("api/consumables/v2/getUnlocked")] + public async Task GetUnlockedConsumablesV2() + { + var pid = Me; + var rows = await (from inv in db.PlayerInventory + join item in db.StoreItems on inv.ItemSlug equals item.Slug + where inv.PlayerId == pid + && ConsumableCategories.Contains(item.Category) + select new { inv, item }).ToListAsync(); + + // Group by ConsumableItemDesc — one BulkConsumable per consumable type. + var bulk = rows + .GroupBy(r => StoreService.TryGetConsumableItemDesc(r.item.Slug, out var d) ? d : r.item.Slug) + .Select(g => new + { + Ids = g.Select(r => r.inv.Id).ToArray(), + CreatedAts = g.Select(r => r.inv.AcquiredAt).ToArray(), + ConsumableItemDesc = g.Key, + Count = g.Sum(r => r.inv.Quantity), + InitialCount = g.Sum(r => r.inv.Quantity), + IsActive = false, + ActiveDurationMinutes = 0, + IsTransferable = false, + }); + return Ok(bulk); + } + + public sealed class ConsumableItemRequest + { + public long Id { get; set; } + public int DeltaCount { get; set; } + } + + [HttpPost("api/consumables/v1/consume")] + public async Task Consume([FromBody] ConsumableItemRequest req) + { + var pid = Me; + var row = await db.PlayerInventory + .FirstOrDefaultAsync(p => p.PlayerId == pid && p.Id == req.Id); + if (row is null) return NotFound(); + var delta = Math.Max(1, Math.Abs(req.DeltaCount)); + row.Quantity = Math.Max(0, row.Quantity - delta); + if (row.Quantity == 0) db.PlayerInventory.Remove(row); + await db.SaveChangesAsync(); + + await notifications.NotifyAsync(pid, + row.Quantity > 0 + ? PushNotificationId.ConsumableMappingAdded + : PushNotificationId.ConsumableMappingRemoved, + new { row.Id, row.ItemSlug, row.Quantity }); + return Ok(new { row.Id, Count = row.Quantity }); + } + + public sealed class ActivateConsumableRequest + { + public long Id { get; set; } + public bool IsActive { get; set; } + } + + [HttpPost("api/consumables/v1/updateActive")] + public async Task UpdateActive([FromBody] ActivateConsumableRequest req) + { + var pid = Me; + var row = await db.PlayerInventory + .FirstOrDefaultAsync(p => p.PlayerId == pid && p.Id == req.Id); + if (row is null) return NotFound(); + row.IsActive = req.IsActive; + await db.SaveChangesAsync(); + return Ok(new { row.Id, row.IsActive }); + } + + // ── Wire serializers ───────────────────────────────────────────────── + + private static object ToEquipmentWire(PlayerInventoryEntity inv, StoreItemEntity item) + { + var prefabName = item.Slug; + var modificationGuid = inv.ItemSlug; + var rarity = 0; + if (StoreService.TryGetEquipmentPayload(item.Slug, out var prefab, out var mod, out var skin)) + { + prefabName = prefab; + modificationGuid = mod; + rarity = skin?.rarity ?? 0; + } + + return new + { + PrefabName = prefabName, + ModificationGuid = modificationGuid, + PlatformMask = -1, // All + IsPlatformLocked = false, + FriendlyName = item.DisplayName, + Tooltip = item.Description, + Rarity = rarity, + Favorited = inv.IsActive, + }; + } + + private static object ToConsumableWire(PlayerInventoryEntity inv, StoreItemEntity item) + { + var type = StoreService.TryGetConsumableItemDesc(item.Slug, out var consumableDesc) + ? consumableDesc + : item.Slug; + var category = StoreService.TryGetHairDyePayload(item.Slug, out _) + ? 6 + : 0; + + return new + { + inv.Id, + ConsumableItemDesc = type, + ConsumableType = type, + PlatformMask = -1, + Count = inv.Quantity, + InitialCount = inv.Quantity, // we don't track original purchase count + UnlockedLevel = 0, + CreatedAt = inv.AcquiredAt, + ActiveDurationMinutes = (int?)null, + ConsumableItem = new + { + Id = inv.Id, + Type = type, + FriendlyName = item.DisplayName, + Tooltip = item.Description, + ImageName = item.ImageName, + }, + Category = category, + inv.IsActive, + IsPlatformLocked = false, + }; + } +} diff --git a/DorkNet.Server/Controllers/API/Leaderboard/LeaderboardController.cs b/DorkNet.Server/Controllers/API/Leaderboard/LeaderboardController.cs new file mode 100644 index 0000000..5dc7d2c --- /dev/null +++ b/DorkNet.Server/Controllers/API/Leaderboard/LeaderboardController.cs @@ -0,0 +1,301 @@ +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Mvc; +using Microsoft.EntityFrameworkCore; +using DorkNet.Server.Auth; +using DorkNet.Server.Data; +using DorkNet.Server.Data.Entities; +using DorkNet.Server.Services; + +namespace DorkNet.Server.Controllers.API.Leaderboard; + +/// +/// leaderboard.{rec.net,localhost}/leaderboard/* — per-stat-channel +/// leaderboard reads + writes. URLs verified against +/// Cpp2IL_ISIL/.../RecNet/Leaderboards.txt:1934, 2416, 2700: +/// +/// POST leaderboard/SetStat body: SetStatRequestDTO +/// {StatChannel(int), RoomId(long), StatValue(int)} +/// POST leaderboard/GetPlayerRank body: GetPlayerRankData +/// {PlayerId(int), StatChannel(int), RoomId(long)} +/// +/// **Subdomain matters**: the client uses +/// Core.SendRequest(method, Service.Leaderboard, requestUri) +/// where Service.Leaderboard = 9 resolves via the +/// RecRoomConfig.ServiceUrls map (we serve via +/// api/config/v2) to https://leaderboard.{apex}. So +/// the actual outbound URL is leaderboard.localhost/leaderboard/SetStat +/// — not api.localhost/api/leaderboard/.... +/// +/// Response shapes: +/// SetStat → RequestResult ack {success:true,error:""} +/// GetPlayerRank → FullLeaderboard.Entry — camelCase per +/// FullLeaderboard_NestedType_Entry.txt:111-121: +/// {playerId(int), score(long), rank(int)}. +/// +/// Without explicit subdomain routing the request fell through to +/// which returns []; +/// the client's Util.Deserialize<Entry> tried to cast +/// list-as-dictionary → +/// InvalidCastException: Unable to cast 'List' to 'Dictionary' +/// → "Failed to retrieve leaderboard: Malformed Response". +/// +/// We bind both the leaderboard subdomain (canonical) AND the api +/// host (defensive — some 2020 builds may proxy via api/) so either +/// path lands here. +/// +[ApiController] +public class LeaderboardController( + DorkNetDbContext db, + PlayerPresenceService playerPresence) : ControllerBase +{ + public sealed class SetStatRequest + { + public int StatChannel { get; set; } + public long RoomId { get; set; } + public int StatValue { get; set; } + } + + /// POST /leaderboard/SetStat on the leaderboard + /// subdomain (canonical client path) AND /api/leaderboard/SetStat + /// on the api host (legacy / proxy fallback). + /// + /// **Wire semantics**: the watch's + /// Leaderboards.ConditionalSetStat_ReturningNewValue + /// (Cpp2IL_ISIL/.../RecNet/Leaderboards.txt:645) handles the + /// per-channel direction (high-score vs low-time-better) LOCALLY + /// via a SetMode enum and only POSTs to SetStat when the + /// new value should replace the cached one. The wire request + /// itself carries no direction hint — it's just + /// {StatChannel, RoomId, StatValue}. So the server must + /// trust the client and assign unconditionally; an earlier + /// Math.Max here silently kept the LARGEST value, which + /// broke channels like StuntRunner (channel 7, low elapsed time + /// wins) where the watch posts the new best and we'd discard it + /// for any prior worse value. + [HttpPost("/leaderboard/SetStat")] + [HttpPost("/api/leaderboard/SetStat")] + [Authorize] + public async Task SetStat([FromBody] SetStatRequest req) + { + var pid = this.RequireCurrentPlayerId(); + var row = await db.LeaderboardStats.FirstOrDefaultAsync(s => + s.PlayerId == pid && s.StatChannel == req.StatChannel); + if (row is null) + { + row = new LeaderboardStatEntity + { + PlayerId = pid, + StatChannel = req.StatChannel, + Value = req.StatValue, + }; + db.LeaderboardStats.Add(row); + } + else + { + row.Value = req.StatValue; + row.UpdatedAt = DateTime.UtcNow; + } + + // Auto-attach the channel to whichever room the score came + // from so every room implicitly gets its own leaderboards. + // The watch's SetStatRequestDTO carries + // RoomId (see Cpp2IL_ISIL .../SetStatRequestDTO); + // when it's zero (rare — direct stat report outside a room + // context) fall back to the player's current presence so a + // stray score still gets bucketed somewhere visible. + var roomId = req.RoomId > 0 ? req.RoomId : (playerPresence.GetRoom(pid)?.RoomId ?? 0); + if (roomId > 0) + { + var meta = await db.LeaderboardChannelMeta + .FirstOrDefaultAsync(c => c.Channel == req.StatChannel); + if (meta is null) + { + // First time this channel reports. Stamp a meta row so + // the admin SPA's per-room Leaderboards tab surfaces it + // immediately, with a placeholder name the admin can + // rename later. Direction defaults to higher-is-better; + // admin flips the toggle for time-trial channels. + db.LeaderboardChannelMeta.Add(new LeaderboardChannelMetaEntity + { + Channel = req.StatChannel, + RoomId = roomId, + Name = $"Channel {req.StatChannel}", + LowerIsBetter = false, + ValueFormat = "count", + }); + } + else if (meta.RoomId == 0) + { + // Channel registered globally (e.g. from the hardcoded + // KnownStatChannels fallback or an unscoped admin + // entry). Bind it to this room now that we know which + // room it came from. Don't re-bind if it already + // points at a different room — the admin must be the + // one to reassign once a channel has a home. + meta.RoomId = roomId; + meta.UpdatedAt = DateTime.UtcNow; + } + } + + await db.SaveChangesAsync(); + return Ok(new { success = true, error = "" }); + } + + public sealed class GetPlayerRankRequest + { + public int PlayerId { get; set; } + public int StatChannel { get; set; } + public long RoomId { get; set; } + } + + [HttpPost("/leaderboard/GetPlayerRank")] + [HttpPost("/api/leaderboard/GetPlayerRank")] + public async Task GetPlayerRank([FromBody] GetPlayerRankRequest req) + { + var pid = req.PlayerId > 0 + ? (long)req.PlayerId + : this.CurrentPlayerId() ?? 0; + // Empty/no-row case: return a zero entry. The client tolerates + // this — `Entry.Deserialize` reads playerId/score/rank as + // primitives with defaults. + if (pid == 0) + return Ok(new { playerId = 0, score = 0L, rank = 0 }); + + var row = await db.LeaderboardStats.FirstOrDefaultAsync(s => + s.PlayerId == pid && s.StatChannel == req.StatChannel); + if (row is null) + return Ok(new { playerId = (int)pid, score = 0L, rank = 0 }); + + var higherCount = await db.LeaderboardStats + .CountAsync(s => s.StatChannel == req.StatChannel && s.Value > row.Value); + return Ok(new + { + playerId = (int)pid, + score = row.Value, + rank = higherCount + 1, + }); + } + + public sealed class GetNearbyScoresRequest + { + public int PlayerId { get; set; } + public int StatChannel { get; set; } + public long RoomId { get; set; } + public int WindowSize { get; set; } + } + + /// POST /leaderboard/GetNearbyScores — returns the + /// N entries on either side of the caller's rank. Wire shape per + /// GetNearbyScoresRequestDTO.cs: PlayerId/StatChannel/RoomId + /// inherited from GetRankRequestDTO + WindowSize. + [HttpPost("/leaderboard/GetNearbyScores")] + [HttpPost("/api/leaderboard/GetNearbyScores")] + public async Task GetNearbyScores([FromBody] GetNearbyScoresRequest req) + { + var pid = req.PlayerId > 0 ? (long)req.PlayerId : this.CurrentPlayerId() ?? 0; + var window = Math.Clamp(req.WindowSize, 1, 50); + + var all = await db.LeaderboardStats + .Where(s => s.StatChannel == req.StatChannel) + .OrderByDescending(s => s.Value) + .Select(s => new { s.PlayerId, s.Value }) + .ToListAsync(); + if (all.Count == 0) + { + // Empty case must still come back as a SingleLeaderboard + // Dictionary, not a bare array — the 2020 watch's + // SingleLeaderboard.Deserialize reads dict["rows"] + // (Cpp2IL_ISIL/.../RecNet/SingleLeaderboard.txt:60). + // Returning `[]` cast-fails as List→Dictionary inside + // RecNet.Util.Deserialize, logged repeatedly as + // "Received malformed RecNet response: '[]'" and + // eventually starves the StuntRunner UI of a usable + // leaderboard. + return Ok(new { rows = Array.Empty() }); + } + + var ranked = all.Select((r, i) => new + { + playerId = (int)r.PlayerId, + score = r.Value, + rank = i + 1, + }).ToList(); + + var myIdx = ranked.FindIndex(r => r.playerId == (int)pid); + if (myIdx < 0) + { + return Ok(new { rows = ranked.Take(window * 2 + 1).ToArray() }); + } + var lo = Math.Max(0, myIdx - window); + var hi = Math.Min(ranked.Count - 1, myIdx + window); + return Ok(new { rows = ranked.GetRange(lo, hi - lo + 1).ToArray() }); + } + + public sealed class GetRanksRequest + { + public int PlayerId { get; set; } + public int StatChannel { get; set; } + public long RoomId { get; set; } + public int RankStart { get; set; } + public int RankEnd { get; set; } + } + + /// POST /leaderboard/GetRanks — returns a slice of + /// the leaderboard between RankStart and RankEnd (inclusive, + /// 1-indexed). Wire shape per GetRanksRequestDTO.cs. + [HttpPost("/leaderboard/GetRanks")] + [HttpPost("/api/leaderboard/GetRanks")] + public async Task GetRanks([FromBody] GetRanksRequest req) + { + var start = Math.Max(1, req.RankStart); + var end = Math.Max(start, req.RankEnd); + var skip = start - 1; + var take = Math.Clamp(end - skip, 1, 200); + + var rows = await db.LeaderboardStats + .Where(s => s.StatChannel == req.StatChannel) + .OrderByDescending(s => s.Value) + .Skip(skip).Take(take) + .ToListAsync(); + var entries = rows.Select((s, i) => new + { + playerId = (int)s.PlayerId, + score = s.Value, + rank = start + i, + }).ToArray(); + // SingleLeaderboard wrapper — see GetNearbyScores above. + return Ok(new { rows = entries }); + } + + /// Top-N for a channel. Used by the admin tooling and + /// by the watch's full-leaderboard scoreboard view. + [HttpGet("/leaderboard/Top")] + [HttpGet("/api/leaderboard/Top")] + public async Task Top([FromQuery] int channel, [FromQuery] int take = 50) + { + take = Math.Clamp(take, 1, 200); + var rows = await db.LeaderboardStats + .Where(s => s.StatChannel == channel) + .OrderByDescending(s => s.Value) + .Take(take) + .ToListAsync(); + // FullLeaderboard wire shape: GlobalOverall / GlobalPeriodic / + // FriendsOverall / FriendsPeriodic + NextResetUTC. Use the + // same Entry list for all four buckets — friends-vs-global + // can come later. + var entries = rows.Select((s, i) => new + { + playerId = (int)s.PlayerId, + score = s.Value, + rank = i + 1, + }).ToArray(); + return Ok(new + { + GlobalOverall = entries, + GlobalPeriodic = entries, + FriendsOverall = Array.Empty(), + FriendsPeriodic = Array.Empty(), + NextResetUTC = DateTime.UtcNow.AddDays(7), + }); + } +} diff --git a/DorkNet.Server/Controllers/API/Messages/V2/MessagesController.cs b/DorkNet.Server/Controllers/API/Messages/V2/MessagesController.cs new file mode 100644 index 0000000..c86b26c --- /dev/null +++ b/DorkNet.Server/Controllers/API/Messages/V2/MessagesController.cs @@ -0,0 +1,684 @@ +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Mvc; +using Microsoft.EntityFrameworkCore; +using DorkNet.Models.Notification; +using DorkNet.Server.Auth; +using DorkNet.Server.Data; +using DorkNet.Server.Data.Entities; +using DorkNet.Server.Services; + +namespace DorkNet.Server.Controllers.API.Messages.V2; + +/// +/// api.rec.net/api/messages/v2/* — direct-message inbox API. Backed +/// by . Sends fan out a SignalR push +/// (MessageReceived) so the recipient's watch refreshes +/// immediately. +/// +[ApiController] +[Route("api/[controller]/v2")] +[Authorize] +public class MessagesController( + DorkNetDbContext db, + NotificationService notifications, + PrivateInstanceService privateInstances, + OnlinePresenceService onlinePresence, + PlayerPresenceService presence, + ILogger logger) : ControllerBase +{ + private long Me => this.RequireCurrentPlayerId(); + + /// List the caller's inbox, newest-first. Response is a + /// flat array of ; the watch groups by + /// sender locally. v1/v3 paths share the same handler — the watch + /// uses whichever URL its RecNet.Messages static helper + /// built from GameConfig.MessageServiceVersion. + [HttpGet("get")] + [HttpGet("/api/messages/v1/get")] + [HttpGet("/api/messages/v3/get")] + public async Task>> GetMessages([FromQuery] int take = 100) + { + take = Math.Clamp(take, 1, 200); + var rows = await db.Messages + .Where(m => m.RecipientPlayerId == Me || m.SenderPlayerId == Me) + // Defensive filter for the empty-body ghost rows that + // accumulated before SendFormMessage rejected them. The + // watch's MessagesScreenItem renders TextMessage (type 30) + // as "Message : {body}", so an empty body becomes a + // useless "Message :" entry the user has to scroll past. + // Control-channel types (10 RequestGameInvite, 20 + // FriendStatusOnline, etc.) have their own labels and + // legitimately carry no body, so we keep them. + .Where(m => m.Type != 30 || (m.Body != null && m.Body != "")) + .OrderByDescending(m => m.SentAt) + .Take(take) + .ToListAsync(); + return Ok(await ToDtosAsync(rows)); + } + + /// GET api/messages/v1/unread — list of unread + /// messages addressed to the caller. The watch uses the count for + /// the inbox badge and the entries themselves for the toast + /// preview. + [HttpGet("/api/messages/v1/unread")] + public async Task>> Unread() + { + var rows = await db.Messages + .Where(m => m.RecipientPlayerId == Me && m.ReadAt == null) + // Same ghost-row filter as GetMessages above — keep the + // unread badge count honest by hiding empty TextMessages. + .Where(m => m.Type != 30 || (m.Body != null && m.Body != "")) + .OrderByDescending(m => m.SentAt) + .Take(100) + .ToListAsync(); + return Ok(await ToDtosAsync(rows)); + } + + public sealed class DeleteRequest + { + public long? Id { get; set; } + public long? MessageId { get; set; } + public List? MessageIds { get; set; } + } + + /// POST api/messages/v3/delete — hard-delete one or + /// more messages addressed to the caller. The 2020 watch's inbox + /// "delete" action posts JSON {"MessageIds":[8]} (plural + /// array, verified from the live trace where the body was + /// {"MessageIds":[8]} against /api/messages/v3/delete); + /// some older / SPA call sites still post id or + /// messageId as a form/query. Accept all three shapes and + /// scope every delete to RecipientPlayerId == Me so the + /// sender can't unsend. + [HttpPost("/api/messages/v3/delete")] + [HttpPost("/api/messages/v1/delete")] + [Consumes("application/json")] + public async Task DeleteJson([FromBody] DeleteRequest body) + { + var ids = new List(); + if (body.Id is long a && a > 0) ids.Add(a); + if (body.MessageId is long b && b > 0) ids.Add(b); + if (body.MessageIds is { Count: > 0 }) + ids.AddRange(body.MessageIds.Where(v => v > 0)); + return await DeleteByIds(ids); + } + + [HttpPost("/api/messages/v3/delete")] + [HttpPost("/api/messages/v1/delete")] + [Consumes("application/x-www-form-urlencoded", "multipart/form-data")] + public async Task DeleteForm( + [FromForm(Name = "id")] long? formId, + [FromForm(Name = "messageId")] long? formMessageId, + [FromForm(Name = "messageIds")] string? messageIdsCsv) + { + var ids = new List(); + if (formId is long a && a > 0) ids.Add(a); + if (formMessageId is long b && b > 0) ids.Add(b); + if (long.TryParse(Request.Query["id"], out var qi) && qi > 0) ids.Add(qi); + if (!string.IsNullOrWhiteSpace(messageIdsCsv)) + { + foreach (var s in messageIdsCsv.Split(',', StringSplitOptions.RemoveEmptyEntries)) + if (long.TryParse(s.Trim(), out var v) && v > 0) ids.Add(v); + } + return await DeleteByIds(ids); + } + + private async Task DeleteByIds(List ids) + { + ids = ids.Distinct().ToList(); + if (ids.Count == 0) return BadRequest(new { error = "missing_id" }); + var deleted = await db.Messages + .Where(m => ids.Contains(m.Id) && m.RecipientPlayerId == Me) + .ExecuteDeleteAsync(); + return Ok(new { success = true, error = "", deleted }); + } + + /// POST api/messages/v3/markRead — bulk + /// mark-as-read for every unread message addressed to the caller. + /// Optional ids CSV scopes to specific message ids. + [HttpPost("/api/messages/v3/markRead")] + [HttpPost("/api/messages/v1/markRead")] + public async Task MarkReadBulk([FromForm(Name = "ids")] string? ids) + { + IQueryable q = db.Messages + .Where(m => m.RecipientPlayerId == Me && m.ReadAt == null); + if (!string.IsNullOrWhiteSpace(ids)) + { + var idSet = ids + .Split(',', StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries) + .Select(s => long.TryParse(s, out var v) ? v : 0L) + .Where(v => v > 0) + .ToHashSet(); + if (idSet.Count > 0) q = q.Where(m => idSet.Contains(m.Id)); + } + var now = DateTime.UtcNow; + await q.ExecuteUpdateAsync(s => s.SetProperty(m => m.ReadAt, now)); + return Ok(new { success = true, error = "" }); + } + + public sealed record SendRequest(long RecipientId, string Body, long? RoomId = null); + + /// Send a direct message — JSON wire shape used by some + /// SPA/admin callers. The 2020 watch posts to the same path with + /// form-urlencoded fields (); the + /// [Consumes] attribute disambiguates which action MVC + /// picks. Empty bodies are rejected so the inbox doesn't get + /// padded with whitespace pings; messages over 2000 chars are + /// truncated to fit the column. + [HttpPost("send")] + [HttpPost("/api/messages/v1/send")] + [Consumes("application/json")] + public async Task> SendMessage([FromBody] SendRequest req) + { + if (req.RecipientId <= 0 || req.RecipientId == Me) + return BadRequest(new { error = "invalid_recipient" }); + var trimmed = (req.Body ?? string.Empty).Trim(); + if (trimmed.Length == 0) return BadRequest(new { error = "empty_body" }); + if (trimmed.Length > 2000) trimmed = trimmed[..2000]; + + var entry = new MessageEntity + { + SenderPlayerId = Me, + RecipientPlayerId = req.RecipientId, + Body = trimmed, + Type = 30, // TextMessage + RoomId = req.RoomId, + }; + db.Messages.Add(entry); + await db.SaveChangesAsync(); + + await BroadcastMessageAsync(entry); + return Ok(ToDto(entry)); + } + + /// Form-urlencoded variant — what the 2020 watch actually + /// posts when the user taps "Send Join Request" or any other + /// RecNet.Messages.SendMessage(player, type, data) caller. + /// Wire shape (verified against client log contentType= + /// application/x-www-form-urlencoded, body ToPlayerId=…& + /// Type=10&Data=): the Type integer is a + /// RecNet.Message.MessageType enum value + /// (Cpp2IL_CS/.../RecNet/Message.cs) — 10 = + /// RequestGameInvite, 30 = TextMessage, etc. Empty Data is + /// allowed for control-channel types (join request, declines) + /// where the message itself is the signal. + [HttpPost("send")] + [HttpPost("/api/messages/v1/send")] + [Consumes("application/x-www-form-urlencoded", "multipart/form-data")] + public async Task> SendFormMessage( + [FromForm(Name = "ToPlayerId")] long toPlayerId, + [FromForm(Name = "Type")] int type, + [FromForm(Name = "Data")] string? data, + [FromForm(Name = "RoomId")] long? roomId) + { + if (toPlayerId <= 0 || toPlayerId == Me) + return BadRequest(new { error = "invalid_recipient" }); + var trimmed = (data ?? string.Empty); + if (trimmed.Length > 2000) trimmed = trimmed[..2000]; + + // For TextMessage (type 30) the body IS the message — empty + // is meaningless and renders as a ghost "Message :" row in + // the watch's inbox (MessagesScreenItem format string at + // Cpp2IL_ISIL/.../MessagesScreenItem.txt:2145 is + // "Message : {0}"). Control-channel types (RequestGameInvite, + // declines, FriendStatusOnline, etc.) intentionally carry + // empty Data — they get rendered by their own label string + // in the same screen item. + if (type == 30 && trimmed.Trim().Length == 0) + return BadRequest(new { error = "empty_text_message" }); + + var entry = new MessageEntity + { + SenderPlayerId = Me, + RecipientPlayerId = toPlayerId, + Body = trimmed, + Type = type, + RoomId = roomId, + }; + db.Messages.Add(entry); + await db.SaveChangesAsync(); + + logger.LogInformation( + "[messages] send from={From} to={To} type={Type} dataLen={Len}", + Me, toPlayerId, type, trimmed.Length); + + await BroadcastMessageAsync(entry); + return Ok(ToDto(entry)); + } + + /// POST invite — handles the watch's + /// RecNet.Messages.SendGameInvite(playerId, roomInstanceId) + /// (verified at Cpp2IL_ISIL/.../Messages.txt:613-773): form + /// fields playerId (recipient) + roomInstanceId + /// (the inviter's private match id). Two side-effects: + /// 1. Add the recipient to the invite list for that instance so + /// POST /goto/instance/{id} accepts them. + /// 2. Drop a Type-6 (GameInviteV2) message in their inbox carrying the + /// RoomInstanceId so the message UI's "Join" button knows + /// where to send them. + /// Registered at multiple paths because the exact URL the watch + /// builds for "invite" depends on the messages-namespace base — + /// rather than guess wrong and silently fall through to the + /// wildcard, we cover all plausible mounts and log the one that + /// actually fires. + [HttpPost("/invite")] + [HttpPost("/api/invite")] + [HttpPost("/api/messages/v1/invite")] + [HttpPost("/api/messages/v2/invite")] + [HttpPost("/api/messages/v3/invite")] + public async Task SendInvite( + [FromForm(Name = "playerId")] long playerId, + [FromForm(Name = "roomInstanceId")] long roomInstanceId) + { + logger.LogInformation( + "[invite] from={From} → to={To} instance={Instance} (path={Path})", + Me, playerId, roomInstanceId, Request.Path); + + if (playerId <= 0 || playerId == Me) + return BadRequest(new { error = "invalid_recipient" }); + + // Resolve which room the invite is for. Two code paths: + // + // PRIVATE instance: the sender created an invite-only match + // (via /goto/room/X?CreatePrivateInstance=1). The + // roomInstanceId the watch sends maps to a registered + // PrivateInstance and only the owner is allowed to invite + // anyone in. Enforces our private-match access control — + // no random can DM their way past the invite list. + // + // PUBLIC room: the sender is in a public room / a dorm / + // any non-private room. RoomInstanceId is just + // roomId*100_000 — synthetic, never registered. We have to + // resolve which Room they're in via PlayerPresenceService + // (heartbeats keep this up to date) so we can stamp RoomId + // on the message and the recipient's /goto/invite/{N} path + // knows where to send them. No ownership check applies for + // public rooms. + long resolvedRoomId; + string roomName; + var inst = await privateInstances.GetAsync(roomInstanceId); + if (inst is not null) + { + if (inst.OwnerPlayerId != Me) return Forbid(); + resolvedRoomId = inst.RoomId; + roomName = await db.Rooms + .Where(r => r.Id == inst.RoomId) + .Select(r => r.Name) + .FirstOrDefaultAsync() ?? "Private match"; + } + else + { + // Public-room path. Trust the sender's current presence — + // the watch only fires SendInvite from the social menu + // while you're in a room, and heartbeats refresh presence + // every few seconds. We DON'T trust the raw roomInstanceId + // from the form because it's user-controlled and a + // malicious mod could send a fabricated one. + var senderPresence = presence.GetRoom(Me); + if (senderPresence is null || senderPresence.RoomId <= 0) + { + logger.LogWarning( + "[invite] from={From} to={To} instance={Instance}: no private instance AND no presence room — rejecting", + Me, playerId, roomInstanceId); + return BadRequest(new { error = "sender_not_in_a_room", roomInstanceId }); + } + resolvedRoomId = senderPresence.RoomId; + roomName = !string.IsNullOrWhiteSpace(senderPresence.Name) + ? senderPresence.Name + : await db.Rooms + .Where(r => r.Id == senderPresence.RoomId) + .Select(r => r.Name) + .FirstOrDefaultAsync() ?? "Room"; + } + + // Persist a real RecNet.Message.MessageType.GameInviteV2 (=6) + // so the watch's GameInviteController calls + // Matchmaking.AcceptRoomInvite(inviteId, roomId), which posts + // goto/invite/{inviteId}. Type 0 follows the sender's current + // presence instead and can land in the wrong instance. + // + // Two saves: the first allocates the message Id (which becomes + // the `inviteId` the watch sends back in /goto/invite/{N}); the + // second backfills the JSON Body once we know it. We can't + // pre-allocate the Id because EF Core's identity columns only + // populate on SaveChanges. + + var entry = new MessageEntity + { + SenderPlayerId = Me, + RecipientPlayerId = playerId, + // Placeholder so the row passes the NOT NULL constraint + // until we overwrite it post-save with the real JSON. + Body = string.Empty, + Type = 6, + RoomId = resolvedRoomId, + }; + db.Messages.Add(entry); + await db.SaveChangesAsync(); + + // Only pin the invitee row for PRIVATE instances — public-room + // invites have no registered instance to track invitees against. + // GoToInvite handles the missing-mapping case by falling through + // to the message's RoomId + BuildResponseAsync(roomName). + if (inst is not null) + { + await privateInstances.InviteAsync(roomInstanceId, playerId, messageId: entry.Id); + } + + // Body shape MUST be the JSON the watch's Message.Deserialize + // builds RoomInviteDetails from (verified at + // Cpp2IL_ISIL/.../RecNet/Message.txt:786-805 — for Type 6/7 + // the parser hits `Json.Deserialize` and reads "inviteId" + + // "name"; for other types it `Int32.Parse`s the body). + // + // inviteId → the message Id; watch echoes it back in + // /goto/invite/{inviteId}, server looks the + // row up to resolve roomInstanceId. + // name → display string in the invite toast / row. + // roomInstanceId → extra field the watch ignores; server uses + // it (via TryParseRoomInstanceId's JSON + // fallback) so /goto/invite/{N} doesn't have + // to re-query the private-instance service. + // + // The roomInstanceId MUST be a JSON STRING, not a number. LitJson + // chokes on the dict-cast when a value is a 64-bit integer larger + // than int32 — confirmed via SaveDebugMod trace: the watch logs + // `data={"inviteId":31,"name":"Dorm_1811750","roomInstanceId":6546545006375796736}` + // `RoomInviteDetails=` + // `System.NullReferenceException at RecNet.Util.GetKey from + // RecNet.Message.Deserialize` + // Json.Deserialize returns a null cast-result on the long value, + // GetKey("inviteId") then NREs and the whole parse aborts before + // RoomInviteDetails is built. Stringifying the value sidesteps + // LitJson's long-int handling entirely; the watch only reads + // inviteId + name so the string form is invisible to it. + entry.Body = System.Text.Json.JsonSerializer.Serialize(new + { + inviteId = entry.Id, + name = roomName, + roomInstanceId = roomInstanceId.ToString(System.Globalization.CultureInfo.InvariantCulture), + }); + await db.SaveChangesAsync(); + + await BroadcastMessageAsync(entry); + return Ok(new { success = true, messageId = entry.Id }); + } + + /// Mark a single message as read. No-op if not + /// addressed to the caller — prevents the sender from "reading" + /// the recipient's copy. + [HttpPost("markread/{id:long}")] + public async Task MarkRead(long id) + { + var msg = await db.Messages.FirstOrDefaultAsync(m => + m.Id == id && m.RecipientPlayerId == Me); + if (msg is null) return NotFound(); + if (msg.ReadAt is null) + { + msg.ReadAt = DateTime.UtcNow; + await db.SaveChangesAsync(); + } + return Ok(); + } + + public sealed class SendMultipleBody + { + public List? ToPlayerIds { get; set; } + public int Type { get; set; } + public string? Data { get; set; } + public long? RoomId { get; set; } + } + + /// POST api/messages/v1/sendMultiple — fan-out a + /// message to many recipients. Persists one MessageEntity per + /// recipient. Wire shape: {ToPlayerIds (int[]), Type (int), Data (string)}. + [HttpPost("/api/messages/v1/sendMultiple")] + [Consumes("application/json")] + public async Task SendMultiple([FromBody] SendMultipleBody body) + { + if (body.ToPlayerIds is null || body.ToPlayerIds.Count == 0) + return Ok(Array.Empty()); + var entries = new List(); + foreach (var to in body.ToPlayerIds.Distinct().Where(id => id > 0 && id != Me)) + { + var entry = new MessageEntity + { + SenderPlayerId = Me, + RecipientPlayerId = to, + Body = body.Data ?? string.Empty, + Type = body.Type, + RoomId = body.RoomId, + }; + entries.Add(entry); + db.Messages.Add(entry); + } + await db.SaveChangesAsync(); + foreach (var entry in entries) + { + await BroadcastMessageAsync(entry); + } + return Ok(Array.Empty()); + } + + /// POST api/messages/v{N}/send — single-recipient + /// send. The 2020.12 watch uses this for any 1:1 message that + /// isn't a game invite, notably the PartyUpRequest + /// (MessageType = 120 in IGCIBGKPPMO.BBELBJELLHN) that fires + /// when the player taps "party up" on someone NOT in the same + /// room (in-room party formation goes purely through PunRPCs in + /// PlayerParty, no HTTP). Without this route, the watch's + /// Messages.SendMessage call 404s and party invites silently + /// fail with no on-screen feedback. + /// + /// Wire shape verified at KEBJPIGKGOI.txt:2618-2655 — + /// {0}v2/send with form fields ToPlayerId (singular, + /// long), Type (int), Data (string), RoomId + /// (optional long). KEBJPIGKGOI.txt:1456-1478 shows the sibling + /// sendMultiple path uses ToPlayerIds (plural) — same + /// other fields. v1 and v2 of /send are protocol-identical + /// so we accept both. + [HttpPost("/api/messages/v1/send")] + [HttpPost("/api/messages/v2/send")] + [Consumes("application/x-www-form-urlencoded", "multipart/form-data", "application/json")] + public async Task SendSingle( + [FromForm(Name = "ToPlayerId")] long? toPlayerIdForm, + [FromForm(Name = "Type")] int typeForm, + [FromForm(Name = "Data")] string? dataForm, + [FromForm(Name = "RoomId")] long? roomIdForm) + { + var toPlayerId = toPlayerIdForm ?? 0; + if (toPlayerId <= 0 || toPlayerId == Me) + return BadRequest(new { error = "invalid_recipient" }); + + var entry = new MessageEntity + { + SenderPlayerId = Me, + RecipientPlayerId = toPlayerId, + Body = dataForm ?? string.Empty, + Type = typeForm, + RoomId = roomIdForm, + }; + db.Messages.Add(entry); + await db.SaveChangesAsync(); + await BroadcastMessageAsync(entry); + + logger.LogInformation( + "[send] from={From} to={To} type={Type} roomId={RoomId} messageId={Id}", + Me, toPlayerId, typeForm, roomIdForm, entry.Id); + + return Ok(new { success = true, messageId = entry.Id }); + } + + /// POST api/offlineinvite/v1/send — send an invite + /// message to a player who's offline; surfaces in their inbox on + /// next login. Wire shape: form-urlencoded RecipientId + + /// Data. Idempotent per (sender, recipient, body). + [HttpPost("/api/offlineinvite/v1/send")] + [Consumes("application/x-www-form-urlencoded", "multipart/form-data")] + public async Task OfflineInviteSend( + [FromForm(Name = "RecipientId")] long? recipientId, + [FromForm(Name = "Data")] string? data) + { + if (recipientId is not long rid || rid <= 0 || rid == Me) + return Ok(new { success = false, error = "invalid_recipient" }); + var entry = new MessageEntity + { + SenderPlayerId = Me, + RecipientPlayerId = rid, + Body = data ?? string.Empty, + Type = 0, + }; + db.Messages.Add(entry); + await db.SaveChangesAsync(); + await BroadcastMessageAsync(entry); + return Ok(new { success = true, messageId = entry.Id }); + } + + /// GET api/offlineinvite/v1/all — invites + /// received while the player was offline. Pulls unread + /// MessageEntity rows whose body looks like an invite. + [HttpGet("/api/offlineinvite/v1/all")] + public async Task OfflineInviteAll() + { + var rows = await db.Messages + .Where(m => m.RecipientPlayerId == Me && m.ReadAt == null + && (m.Body.Contains("invite") || m.Body.Contains("event"))) + .OrderByDescending(m => m.SentAt) + .Take(50) + .Select(m => new + { + InviteId = m.Id, + FromPlayerId = (int)m.SenderPlayerId, + m.Body, + SentAt = m.SentAt, + }) + .ToListAsync(); + return Ok(rows); + } + + /// The boot sequence asks for online friends. We + /// derive that from the SignalR hub's connected-player set — + /// the response shape is [ {AccountId, Online}, ... ]. + /// + /// Both favoriteFriendOnlineStatus and the more general + /// onlineStatus URLs alias to this handler since they + /// share the wire shape; pre-2026-05-25 the onlineStatus route + /// was wrongly aliased to GetMessages and returned the inbox + /// list instead, breaking the friends-online HUD. + [HttpGet("/api/messages/v1/favoriteFriendOnlineStatus")] + [HttpGet("/api/messages/v1/onlineStatus")] + public async Task FavoriteFriendOnlineStatus() + { + var friendIds = await db.Relationships + .Where(r => r.Status == RelationshipStatus.Friend && + (r.RequesterId == Me || r.TargetId == Me)) + .Select(r => r.RequesterId == Me ? r.TargetId : r.RequesterId) + .ToListAsync(); + var online = onlinePresence.OnlinePlayerIds().ToHashSet(); + return Ok(friendIds.Select(id => new + { + AccountId = id, + Online = online.Contains(id), + })); + } + + /// Wire shape verified against + /// Cpp2IL_ISIL/.../RecNet/Message.txt:591-787. + /// RecNet.Message.Deserialize calls Util.GetKey + /// (throws on miss) for: Id (long), FromPlayerId (int), + /// SentTime (DateTime), Type (int — MessageType enum), + /// and reads Data (string) via Util.GetKeyOrDefault. + /// Optional nullable wrappers: RoomId, PlayerEventId. + /// Previous response used {Id, Sender, Recipient, Body, SentAt, ReadAt} + /// which crashed at Util.GetKey("FromPlayerId"). + public sealed record MessageDto( + long Id, + long FromPlayerId, + DateTime SentTime, + int Type, + string Data, + long? RoomId, + long? PlayerEventId); + + private async Task> ToDtosAsync(List rows) + { + var instanceIds = rows + .Where(m => IsInstanceInviteType(m.Type) + && m.RoomId is null + && long.TryParse(m.Body, System.Globalization.NumberStyles.Integer, + System.Globalization.CultureInfo.InvariantCulture, out _)) + .Select(m => long.Parse(m.Body, System.Globalization.NumberStyles.Integer, + System.Globalization.CultureInfo.InvariantCulture)) + .Distinct() + .ToList(); + + var roomByInstance = instanceIds.Count == 0 + ? new Dictionary() + : await db.PrivateInstances + .Where(i => instanceIds.Contains(i.Id)) + .Select(i => new { i.Id, i.RoomId }) + .ToDictionaryAsync(i => i.Id, i => i.RoomId); + + return rows.Select(m => + { + long? roomId = m.RoomId; + if (roomId is null + && IsInstanceInviteType(m.Type) + && long.TryParse(m.Body, System.Globalization.NumberStyles.Integer, + System.Globalization.CultureInfo.InvariantCulture, out var instanceId) + && roomByInstance.TryGetValue(instanceId, out var rid)) + { + roomId = rid; + } + return ToDto(m, roomId); + }).ToList(); + } + + private static bool IsInstanceInviteType(int type) => type is 6 or 7; + + /// Push a MessageReceived SignalR notification to BOTH + /// the recipient AND the sender. The recipient half is the obvious one + /// — that's what fires the watch's OnMessageAdded event and + /// gives them the toast / inbox-row update. The sender-half is less + /// obvious but equally needed: the watch's Messages.SendMessage + /// (Messages.txt:1790-1900) just does ExpectHttpStatusSuccess + /// on the response and never touches the local MessageList, + /// so without a self-targeted push the sender's chat thread doesn't + /// know about its own outgoing message until the next + /// RefreshList (which only fires on hub-reconnect or first + /// inbox open). Pushing to both sides keeps both watches in sync + /// in real time. Dedup happens client-side in + /// OnMessageReceived via the "already-in-MessageList by Id" + /// check (Messages.txt:2790-2793), so a player who somehow ends up + /// as both sender and recipient (admin self-messaging) only sees + /// the message once. + private async Task BroadcastMessageAsync(MessageEntity entry) + { + var dto = ToDto(entry); + await notifications.NotifyAsync(entry.RecipientPlayerId, + PushNotificationId.MessageReceived, dto); + if (entry.SenderPlayerId != entry.RecipientPlayerId) + { + await notifications.NotifyAsync(entry.SenderPlayerId, + PushNotificationId.MessageReceived, dto); + } + } + + private static MessageDto ToDto(MessageEntity m, long? roomId = null) => + new( + Id: m.Id, + FromPlayerId: m.SenderPlayerId, + SentTime: m.SentAt, + // Round-trip the stored Type verbatim. Earlier we coerced + // Type==0 → 30 for "pre-Type-column rows", but the + // Messages.Type Postgres patch (Program.cs:612-614) added + // the column with DEFAULT 30 and backfilled every legacy + // row to 30, so any Type==0 row in the DB now is a real + // GameInvite (e.g. SendInvite above). Coercing it to 30 + // routes it through TextMessageController and renders + // "Message : " with no Accept button. + Type: m.Type, + Data: m.Body, + RoomId: roomId ?? m.RoomId, + PlayerEventId: null); +} diff --git a/DorkNet.Server/Controllers/API/Moderation/InGameModerationController.cs b/DorkNet.Server/Controllers/API/Moderation/InGameModerationController.cs new file mode 100644 index 0000000..92f98bc --- /dev/null +++ b/DorkNet.Server/Controllers/API/Moderation/InGameModerationController.cs @@ -0,0 +1,211 @@ +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Mvc; +using Microsoft.EntityFrameworkCore; +using DorkNet.Server.Auth; +using DorkNet.Server.Data; +using DorkNet.Server.Data.Entities; +using DorkNet.Server.Services; + +namespace DorkNet.Server.Controllers.API.Moderation; + +/// +/// In-game moderation endpoints invoked by the watch's player-action +/// dialogs. URLs / form-fields verified against the Cpp2IL ISIL dump: +/// +/// POST api/PlayerReporting/v3/create +/// PlayerReporting.txt:1696 + 1712-1822 +/// Form: PlayerIdReported (int), ReportCategory (int), Details (string), +/// HeightReporter (float "F2"), HeightReported (float "F2"), +/// RoomId (long). +/// +/// POST api/PlayerReporting/v3/voteToKick +/// PlayerReporting.txt:3175 + 3215-3237 +/// Form: PlayerId (long), Response (bool), GameSessionId (long). +/// +/// POST api/PlayerReporting/v1/instantKick +/// PlayerReporting.txt:3417 (single) + 3590 (bulk) +/// Body: PlayerId(s) — one or many. Admin/dev only. +/// +/// POST api/PlayerReporting/v1/deviceId +/// PlayerReporting.txt:3736 + 3749 +/// Form: oldDeviceId (string). +/// +/// POST api/PlayersBanned/v2/ban +/// Moderation.txt:623-702 +/// Form: PlayerId (long), Reason (int), BanType (int), +/// DisplayReason (string), BannedUntil (string MM/dd/yyyy HH:mm). +/// Admin only. +/// +/// Without these handlers the watch fell through to the JSON catch-all +/// returning {}, which the OkResponse parser tolerates but the +/// admin-action then has no observable effect — i.e. "the admin +/// system doesn't work at all". +/// +[ApiController] +[Authorize] +public class InGameModerationController( + DorkNetDbContext db, + NotificationService notifications, + ILogger logger) : ControllerBase +{ + // ── /v3/create — file a player report ──────────────────────────── + [HttpPost("api/PlayerReporting/v3/create")] + [Consumes("application/x-www-form-urlencoded", "multipart/form-data", "application/json")] + public async Task CreateReport( + [FromForm(Name = "PlayerIdReported")] long? playerIdReported, + [FromForm(Name = "ReportCategory")] int reportCategory = 0, + [FromForm(Name = "Details")] string? details = null, + [FromForm(Name = "HeightReporter")] float? heightReporter = null, + [FromForm(Name = "HeightReported")] float? heightReported = null, + [FromForm(Name = "RoomId")] long roomId = 0) + { + if (playerIdReported is not long target || target <= 0) + return Ok(new { success = false, error = "missing_target" }); + + var reporter = this.RequireCurrentPlayerId(); + if (reporter == target) + return Ok(new { success = false, error = "cannot_report_self" }); + + var trimmed = (details ?? string.Empty).Trim(); + if (trimmed.Length > 1000) trimmed = trimmed[..1000]; + + db.Reports.Add(new ReportEntity + { + ReporterPlayerId = reporter, + TargetPlayerId = target, + Category = reportCategory, + Message = trimmed, + RoomId = roomId, + }); + await db.SaveChangesAsync(); + logger.LogInformation( + "[mod] report filed: reporter={Reporter} target={Target} category={Cat} room={Room}", + reporter, target, reportCategory, roomId); + return Ok(new { success = true, error = "" }); + } + + // ── /v3/voteToKick — room-vote kick ────────────────────────────── + [HttpPost("api/PlayerReporting/v3/voteToKick")] + [Consumes("application/x-www-form-urlencoded", "multipart/form-data", "application/json")] + public IActionResult VoteToKick( + [FromForm(Name = "PlayerId")] long? playerId, + [FromForm(Name = "Response")] bool? response, + [FromForm(Name = "GameSessionId")] long gameSessionId = 0) + { + var caller = this.RequireCurrentPlayerId(); + logger.LogInformation( + "[mod] vote-to-kick by {Caller}: target={Target} yes={Yes} session={Session}", + caller, playerId, response, gameSessionId); + // Real implementation would tally votes per (session, target) and + // push a kick once the threshold tripped. For now we accept the + // vote without side-effects so the watch UI's "voted" indicator + // shows. + return Ok(new { success = true, error = "" }); + } + + // ── /v1/instantKick — admin one-shot ───────────────────────────── + /// + /// Admin/dev one-shot kick. The watch posts either + /// ?playerId=N for a single target (PlayerReporting.txt:3417) + /// or a list body for bulk kicks (3590). Either form lands here. + /// Gated to / + /// . + /// + [HttpPost("api/PlayerReporting/v1/instantKick")] + [Consumes("application/x-www-form-urlencoded", "multipart/form-data", "application/json")] + public async Task InstantKick( + [FromQuery(Name = "playerId")] long? singleId, + [FromForm(Name = "PlayerId")] long? formId, + [FromForm(Name = "PlayerIds")] string? formIds) + { + var caller = this.RequireCurrentPlayerId(); + if (!await IsCallerAdminOrDevAsync(caller)) + return Forbid(); + + var ids = new List(); + if (singleId is long s && s > 0) ids.Add(s); + if (formId is long f && f > 0) ids.Add(f); + if (!string.IsNullOrEmpty(formIds)) + { + foreach (var part in formIds.Split(',', StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries)) + if (long.TryParse(part, out var v) && v > 0) ids.Add(v); + } + ids = ids.Distinct().ToList(); + if (ids.Count == 0) return Ok(new { success = false, error = "missing_target" }); + + foreach (var target in ids) + { + logger.LogInformation("[mod] instant-kick by {Caller}: target={Target}", caller, target); + // Push a SignalR ModerationKick notification — the watch's + // PlayerReporting.OnModerationKick handler boots the player + // back to their dorm. + await notifications.NotifyAsync( + target, + Models.Notification.PushNotificationId.ModerationKick); + } + return Ok(new { success = true, error = "" }); + } + + // ── /v1/deviceId — log mismatched device id ────────────────────── + [HttpPost("api/PlayerReporting/v1/deviceId")] + [Consumes("application/x-www-form-urlencoded", "multipart/form-data", "application/json")] + public IActionResult MismatchedDeviceWarning( + [FromForm(Name = "oldDeviceId")] string? oldDeviceId) + { + logger.LogInformation( + "[mod] mismatched device id reported: caller={Caller} old={Old}", + this.CurrentPlayerId(), oldDeviceId); + return Ok(new { success = true, error = "" }); + } + + // ── /api/PlayersBanned/v2/ban — admin ban ──────────────────────── + [HttpPost("api/PlayersBanned/v2/ban")] + [Consumes("application/x-www-form-urlencoded", "multipart/form-data", "application/json")] + public async Task Ban( + [FromForm(Name = "PlayerId")] long? playerId, + [FromForm(Name = "Reason")] int reason = 0, + [FromForm(Name = "BanType")] int banType = 0, + [FromForm(Name = "DisplayReason")] string? displayReason = null, + [FromForm(Name = "BannedUntil")] string? bannedUntil = null) + { + var caller = this.RequireCurrentPlayerId(); + if (!await IsCallerAdminOrDevAsync(caller)) + return Forbid(); + + if (playerId is not long target || target <= 0) + return Ok(new { success = false, error = "missing_target" }); + + // BannedUntil arrives as "MM/dd/yyyy HH:mm" per Moderation.txt:696. + DateTime? until = null; + if (!string.IsNullOrEmpty(bannedUntil) && + DateTime.TryParseExact(bannedUntil, "MM/dd/yyyy HH:mm", + System.Globalization.CultureInfo.InvariantCulture, + System.Globalization.DateTimeStyles.AssumeUniversal | System.Globalization.DateTimeStyles.AdjustToUniversal, + out var parsed)) + { + until = parsed; + } + + var p = await db.Players.FirstOrDefaultAsync(x => x.Id == target); + if (p is null) return Ok(new { success = false, error = "no_such_player" }); + p.BannedUntil = until ?? DateTime.UtcNow.AddYears(99); + await db.SaveChangesAsync(); + + logger.LogInformation( + "[mod] ban by {Caller}: target={Target} reason={Reason} until={Until} display={Display}", + caller, target, reason, p.BannedUntil, displayReason); + + // Push a ModerationRoomBan notification so the target watch + // shows the ban dialog and disconnects (PlayerReporting.txt:2253). + await notifications.NotifyAsync( + target, + Models.Notification.PushNotificationId.ModerationRoomBan); + return Ok(new { success = true, error = "" }); + } + + private async Task IsCallerAdminOrDevAsync(long callerId) => + await db.Players + .Where(p => p.Id == callerId) + .Select(p => p.IsAdmin || p.IsDeveloper) + .FirstOrDefaultAsync(); +} diff --git a/DorkNet.Server/Controllers/API/Notification/V2/NotificationController.cs b/DorkNet.Server/Controllers/API/Notification/V2/NotificationController.cs new file mode 100644 index 0000000..c605673 --- /dev/null +++ b/DorkNet.Server/Controllers/API/Notification/V2/NotificationController.cs @@ -0,0 +1,34 @@ +using Microsoft.AspNetCore.Mvc; +using DorkNet.Server.Services; + +namespace DorkNet.Server.Controllers.API.Notification.V2; + +/// +/// Legacy raw-WebSocket notification endpoint. Replaced by the SignalR +/// hub at notify.{apex}/hub/v1. Returns 410 Gone for any client +/// that still tries the old path. +/// +[ApiController] +[Route("api/[controller]/v2")] +public class NotificationController(DomainConfig domain) : ControllerBase +{ + [HttpGet] + public IActionResult Connect() => StatusCode(StatusCodes.Status410Gone, new + { + error = "use_signalr_hub", + hint = $"connect to {domain.Sub("notify")}/hub/v1 instead", + }); + + /// GET api/notification/hub/v1 — returns the + /// SignalR hub URL the client should open. Without this the + /// watch falls back to its own default and may try the wrong + /// scheme. URL is built off the configured deployment apex + /// (DORKNET_DOMAIN). + [HttpGet("/api/notification/hub/v1")] + public IActionResult HubInfo() => Ok(new + { + Url = $"https://{domain.Sub("notify")}/hub/v1", + Protocol = "signalr", + Version = 1, + }); +} diff --git a/DorkNet.Server/Controllers/API/NotificationPrefs/IOSNotificationPrefsController.cs b/DorkNet.Server/Controllers/API/NotificationPrefs/IOSNotificationPrefsController.cs new file mode 100644 index 0000000..61b6c85 --- /dev/null +++ b/DorkNet.Server/Controllers/API/NotificationPrefs/IOSNotificationPrefsController.cs @@ -0,0 +1,128 @@ +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Mvc; +using Microsoft.EntityFrameworkCore; +using DorkNet.Server.Auth; +using DorkNet.Server.Data; +using DorkNet.Server.Data.Entities; + +namespace DorkNet.Server.Controllers.API.NotificationPrefs; + +/// +/// api.rec.net/api/messages/v1/IOSGetNotificationPreferences + +/// IOSModifyNotificationPreferences. Wire DTO matches +/// RecNet.IOSNotificationPreferences (decompiled at +/// Cpp2IL_CS/.../RecNet/IOSNotificationPreferences.cs): +/// ChatMessage(bool), FriendInvite(bool), FavoriteFriendOnline(bool). +/// +/// Persists via keyed by +/// (PlayerId, Platform). Defaults to all-on when no row exists yet. +/// +[ApiController] +[Authorize] +public class IOSNotificationPrefsController(DorkNetDbContext db) : ControllerBase +{ + private long Me => this.RequireCurrentPlayerId(); + + [HttpGet("api/messages/v1/IOSGetNotificationPreferences")] + public async Task Get() + { + var pid = Me; + var row = await db.NotificationPrefs.FirstOrDefaultAsync(p => p.PlayerId == pid); + return Ok(ToWire(row)); + } + + public sealed class IOSPrefsRequest + { + public bool ChatMessage { get; set; } = true; + public bool FriendInvite { get; set; } = true; + public bool FavoriteFriendOnline { get; set; } = true; + } + + [HttpPost("api/messages/v1/IOSModifyNotificationPreferences")] + public async Task Modify([FromBody] IOSPrefsRequest body) + { + var pid = Me; + var row = await db.NotificationPrefs.FirstOrDefaultAsync(p => p.PlayerId == pid); + if (row is null) + { + row = new NotificationPrefsEntity + { + PlayerId = pid, + Platform = "ios", + }; + db.NotificationPrefs.Add(row); + } + row.AllowMessage = body.ChatMessage; + row.AllowFriendRequest = body.FriendInvite; + row.AllowAnnouncements = body.FavoriteFriendOnline; + row.AllowAll = body.ChatMessage || body.FriendInvite || body.FavoriteFriendOnline; + row.UpdatedAt = DateTime.UtcNow; + await db.SaveChangesAsync(); + return Ok(ToWire(row)); + } + + /// POST IOSResetNotificationPreferencesBadgeCount — + /// the watch sends this when the user opens the iOS app to clear + /// the badge counter. No persistent state to mutate; ack. + [HttpPost("api/messages/v1/IOSResetNotificationPreferencesBadgeCount")] + public IActionResult ResetBadge() => Ok(new { Reset = true }); + + public sealed class SaveTokenRequest + { + public string? Token { get; set; } + public string? DeviceToken { get; set; } + public string? Platform { get; set; } + } + + /// POST api/messages/v1/IOSSaveDeviceToken — + /// upserts the caller's APNS token to + /// for a future APNS proxy to fan + /// out push notifications. + [HttpPost("api/messages/v1/IOSSaveDeviceToken")] + public async Task SaveDeviceToken( + [FromBody] SaveTokenRequest? body, + [FromForm(Name = "Token")] string? tokenForm, + [FromForm(Name = "DeviceToken")] string? deviceTokenForm) + { + var pid = Me; + var token = body?.Token ?? body?.DeviceToken ?? tokenForm ?? deviceTokenForm ?? string.Empty; + var platform = body?.Platform ?? "ios"; + if (string.IsNullOrWhiteSpace(token)) + return BadRequest(new { error = "missing token" }); + + var row = await db.PushTokens.FirstOrDefaultAsync(t => + t.PlayerId == pid && t.Platform == platform); + if (row is null) + { + row = new PushTokenEntity { PlayerId = pid, Platform = platform }; + db.PushTokens.Add(row); + } + row.Token = token; + row.UpdatedAt = DateTime.UtcNow; + await db.SaveChangesAsync(); + return Ok(new { Saved = true }); + } + + /// POST api/messages/v1/IOSClearDeviceToken — + /// drops every push-token row for the caller (e.g. on iOS + /// logout). + [HttpPost("api/messages/v1/IOSClearDeviceToken")] + public async Task ClearDeviceToken() + { + var pid = Me; + var rows = await db.PushTokens.Where(t => t.PlayerId == pid).ToListAsync(); + if (rows.Count > 0) + { + db.PushTokens.RemoveRange(rows); + await db.SaveChangesAsync(); + } + return Ok(new { Cleared = rows.Count }); + } + + private static object ToWire(NotificationPrefsEntity? r) => new + { + ChatMessage = r?.AllowMessage ?? true, + FriendInvite = r?.AllowFriendRequest ?? true, + FavoriteFriendOnline = r?.AllowAnnouncements ?? true, + }; +} diff --git a/DorkNet.Server/Controllers/API/Pageview/PageviewController.cs b/DorkNet.Server/Controllers/API/Pageview/PageviewController.cs new file mode 100644 index 0000000..198d831 --- /dev/null +++ b/DorkNet.Server/Controllers/API/Pageview/PageviewController.cs @@ -0,0 +1,57 @@ +using DorkNet.Server.Auth; +using Microsoft.AspNetCore.Mvc; + +namespace DorkNet.Server.Controllers.API.Pageview; + +/// +/// Endpoints behind the watch's COFHGNFJMOG page-view + link +/// system. The 2020.12 client fires POST pageview/consume on +/// every screen change in the boot/menu flow expecting a response +/// that tells it (a) whether there's an in-app deep-link URL to +/// resolve and (b) how long the answer is fresh for. Returning a 404 +/// here surfaces as red toast spam in the menus and (per +/// BootSequence.OINIBKFNFAJ) can short-circuit the avatar / +/// inventory bootstrap. +/// +/// Response shape verified at +/// Cpp2IL_ISIL/.../MPHABHIMOOO.txt:45,50 — the watch's +/// MPHABHIMOOO.PPGFHEDFBEA(Dictionary) reads two camelCase +/// keys: +/// +/// url — string. Empty means "no deep-link to load". +/// freshnessSeconds — int. How long the client can +/// cache this response before re-polling. +/// +/// Returning a long freshness (1 hour) keeps a private server's +/// chattiness low when there's no real linking layer. +/// +/// Sibling endpoints from the same watch class +/// (actionlink/*, datalink/*, referral) are out +/// of scope for now — they fire only on explicit user share/click, +/// not in the boot path. Add them as separate routes when they show +/// up in the production log. +/// +[ApiController] +public class PageviewController(ILogger logger) : ControllerBase +{ + /// POST pageview/consume — record a screen + /// transition. The body is empty in 2020.12. + /// Promise<MPHABHIMOOO> result; the watch only uses + /// the url / freshnessSeconds fields. We + /// intentionally return an empty url + a 1-hour freshness — the + /// private server has no in-app deep-link system, so there's + /// nothing to navigate to and no reason to be polled more often. + [HttpPost("pageview/consume")] + [HttpPost("/pageview/consume")] + [HttpGet("pageview/consume")] + [HttpGet("/pageview/consume")] + public IActionResult Consume() + { + logger.LogDebug("[pageview] consume callerId={Caller}", this.CurrentPlayerId() ?? 0); + return Ok(new + { + url = string.Empty, + freshnessSeconds = 3600, + }); + } +} diff --git a/DorkNet.Server/Controllers/API/Photos/PhotosController.cs b/DorkNet.Server/Controllers/API/Photos/PhotosController.cs new file mode 100644 index 0000000..9d9bc23 --- /dev/null +++ b/DorkNet.Server/Controllers/API/Photos/PhotosController.cs @@ -0,0 +1,335 @@ +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Mvc; +using Microsoft.EntityFrameworkCore; +using DorkNet.Models.Notification; +using DorkNet.Server.Auth; +using DorkNet.Server.Data; +using DorkNet.Server.Data.Entities; +using DorkNet.Server.Services; + +namespace DorkNet.Server.Controllers.API.Photos; + +/// +/// api.rec.net/api/photos/v1/* — social-feed wrapper around the +/// in-game camera. The camera itself uses storage.rec.net/upload with +/// FileType=Image to push the PNG bytes; that returns a Filename. The +/// watch's "Share" panel then POSTs here with that Filename, a caption +/// and any tagged players to create the public-feed photo. +/// +/// Public reads (feed, by-player, by-room) are anonymous so the +/// feed.rec.net frontend can browse without an admin token. +/// Mutations (post, cheer, delete) require auth. +/// +[ApiController] +// Two hosts: api.rec.net is what the in-game watch hits; feed.rec.net +// also serves these endpoints so the public frontend website can do +// same-origin fetches without CORS. +[Route("api/photos/v1")] +public class PhotosController( + DorkNetDbContext db, + PlayerPresenceService presence, + NotificationService notifications, + LevelService level, + DomainConfig domain) : ControllerBase +{ + public sealed record PostPhotoRequest( + string ImageName, + string? Caption, + string? TaggedPlayerIds, + long? RoomId, + bool? IsPublic); + + /// POST api/photos/v1/post — promote an uploaded image + /// blob to the photo feed. Body: ImageName (filename returned by + /// storage.rec.net/upload), Caption, TaggedPlayerIds (CSV), + /// optional RoomId (defaults to caller's current room from + /// PlayerPresenceService), IsPublic (defaults true). Returns the + /// created PhotoEntity. + [HttpPost("post")] + [Authorize] + public async Task PostPhoto([FromBody] PostPhotoRequest body) + { + var me = this.RequireCurrentPlayerId(); + if (string.IsNullOrWhiteSpace(body.ImageName)) + return BadRequest(new { error = "missing_image_name" }); + + // Verify the blob actually exists and was uploaded by us. Without + // this check anyone could cite another player's filename and + // post-attribute the photo to themselves. + var blob = await db.RoomDataBlobs + .Where(b => b.BlobName == body.ImageName) + .Select(b => new { b.UploadedByPlayerId }) + .FirstOrDefaultAsync(); + if (blob is null) + return NotFound(new { error = "image_blob_not_found" }); + if (blob.UploadedByPlayerId != me) + return Forbid(); + + // Auto-resolve current room if the caller didn't supply one. + var roomId = body.RoomId ?? presence.GetRoom(me)?.RoomId ?? 0; + + var photo = new PhotoEntity + { + UploaderPlayerId = me, + BlobName = body.ImageName, + Caption = (body.Caption ?? string.Empty).Trim(), + TaggedPlayerIdsCsv = NormaliseTaggedIds(body.TaggedPlayerIds, exceptId: me), + RoomId = roomId, + IsPublic = body.IsPublic ?? true, + }; + db.Photos.Add(photo); + await db.SaveChangesAsync(); + + // Posting a photo gives a small XP bump — encourages camera use. + await level.AwardXpAsync(me, LevelService.InventionSavedXp, $"photo_posted:{photo.Id}"); + + // Push to tagged players so their "Photos of me" feed refreshes. + foreach (var taggedId in ParseTagged(photo.TaggedPlayerIdsCsv)) + await notifications.NotifyAsync(taggedId, + PushNotificationId.SubscriptionUpdateProfile, + new { Reason = "TaggedInPhoto", PhotoId = photo.Id, From = me }); + + return Ok(ToDto(photo)); + } + + /// GET api/photos/v1/feed — public newest-first feed. + /// Anonymous-safe (so the frontend website can render without a + /// JWT). Soft-deleted and private photos are excluded. + [HttpGet("feed")] + [AllowAnonymous] + public async Task Feed([FromQuery] int take = 20, [FromQuery] int skip = 0) + { + take = Math.Clamp(take, 1, 100); + skip = Math.Max(0, skip); + var rows = await db.Photos + .Where(p => p.IsPublic && p.DeletedAt == null) + .OrderByDescending(p => p.CreatedAt) + .Skip(skip).Take(take) + .ToListAsync(); + return Ok(await EnrichAsync(rows)); + } + + /// GET api/photos/v1/by/{playerId} — photos posted by a + /// specific player. Anonymous-safe. Private photos hidden unless + /// the caller is the uploader (or an admin). + [HttpGet("by/{playerId:long}")] + [AllowAnonymous] + public async Task ByPlayer(long playerId, + [FromQuery] int take = 20, [FromQuery] int skip = 0) + { + take = Math.Clamp(take, 1, 100); + skip = Math.Max(0, skip); + var me = this.CurrentPlayerId(); + var includePrivate = me == playerId || await IsAdminAsync(me); + + var q = db.Photos.Where(p => p.UploaderPlayerId == playerId && p.DeletedAt == null); + if (!includePrivate) q = q.Where(p => p.IsPublic); + var rows = await q + .OrderByDescending(p => p.CreatedAt) + .Skip(skip).Take(take) + .ToListAsync(); + return Ok(await EnrichAsync(rows)); + } + + /// GET api/photos/v1/of/{playerId} — photos the player + /// is tagged in. Anonymous-safe. + [HttpGet("of/{playerId:long}")] + [AllowAnonymous] + public async Task OfPlayer(long playerId, + [FromQuery] int take = 20, [FromQuery] int skip = 0) + { + take = Math.Clamp(take, 1, 100); + skip = Math.Max(0, skip); + var needle = $",{playerId},"; + // SQLite EF translates `string.Contains` to LIKE — wrapping the + // CSV with leading/trailing commas avoids "12" matching "112". + var rows = await db.Photos + .Where(p => p.IsPublic && p.DeletedAt == null && + ("," + p.TaggedPlayerIdsCsv + ",").Contains(needle)) + .OrderByDescending(p => p.CreatedAt) + .Skip(skip).Take(take) + .ToListAsync(); + return Ok(await EnrichAsync(rows)); + } + + /// GET api/photos/v1/in/{roomId} — photos taken in a + /// specific room. Anonymous-safe. + [HttpGet("in/{roomId:long}")] + [AllowAnonymous] + public async Task InRoom(long roomId, + [FromQuery] int take = 20, [FromQuery] int skip = 0) + { + take = Math.Clamp(take, 1, 100); + skip = Math.Max(0, skip); + var rows = await db.Photos + .Where(p => p.RoomId == roomId && p.IsPublic && p.DeletedAt == null) + .OrderByDescending(p => p.CreatedAt) + .Skip(skip).Take(take) + .ToListAsync(); + return Ok(await EnrichAsync(rows)); + } + + /// GET api/photos/v1/{id} — single photo detail. + /// Increments ViewCount as a side effect (denormalised for the + /// "trending" sort). + [HttpGet("{id:long}")] + [AllowAnonymous] + public async Task Get(long id) + { + var p = await db.Photos.FirstOrDefaultAsync(x => x.Id == id); + if (p is null || p.DeletedAt is not null) return NotFound(); + if (!p.IsPublic) + { + var me = this.CurrentPlayerId(); + if (me != p.UploaderPlayerId && !await IsAdminAsync(me)) return NotFound(); + } + p.ViewCount += 1; + await db.SaveChangesAsync(); + var enriched = await EnrichAsync(new[] { p }); + return Ok(enriched.First()); + } + + /// POST api/photos/v1/{id}/cheer — like a photo. Idempotent + /// per (caller, photo) pair. Pushes a notification to the + /// uploader so their watch can refresh the count. + [HttpPost("{id:long}/cheer")] + [Authorize] + public async Task Cheer(long id) + { + var me = this.RequireCurrentPlayerId(); + var photo = await db.Photos.FirstOrDefaultAsync(p => p.Id == id); + if (photo is null || photo.DeletedAt is not null) return NotFound(); + + var existing = await db.Cheers.FirstOrDefaultAsync(c => + c.FromPlayerId == me && c.TargetPhotoId == id && + c.TargetPlayerId == 0 && c.TargetRoomId == 0); + if (existing is not null) return Ok(new { already_cheered = true }); + + db.Cheers.Add(new CheerEntity + { + FromPlayerId = me, + TargetPhotoId = id, + }); + photo.CheerCount += 1; + await db.SaveChangesAsync(); + + if (photo.UploaderPlayerId != me) + { + await level.AwardXpAsync(photo.UploaderPlayerId, + LevelService.CheerReceivedXp, $"photo_cheer:{id}"); + await notifications.NotifyAsync(photo.UploaderPlayerId, + PushNotificationId.SubscriptionUpdateProfile, + new { Reason = "PhotoCheer", PhotoId = id, From = me }); + } + return Ok(new { cheered = true, count = photo.CheerCount }); + } + + /// DELETE api/photos/v1/{id} — soft-delete. Allowed for + /// the uploader or an admin. The blob bytes stay in + /// RoomDataBlobs for audit; the row's DeletedAt timestamp hides + /// it from feeds. + [HttpDelete("{id:long}")] + [Authorize] + public async Task Delete(long id) + { + var me = this.RequireCurrentPlayerId(); + var photo = await db.Photos.FirstOrDefaultAsync(p => p.Id == id); + if (photo is null) return NotFound(); + if (photo.UploaderPlayerId != me && !await IsAdminAsync(me)) + return Forbid(); + photo.DeletedAt = DateTime.UtcNow; + await db.SaveChangesAsync(); + return Ok(new { deleted = id }); + } + + // ── Helpers ────────────────────────────────────────────────────── + + /// Trim/dedupe tagged ids and drop the uploader's own id. + /// CSV in, CSV out. + private static string NormaliseTaggedIds(string? csv, long exceptId) + { + if (string.IsNullOrWhiteSpace(csv)) return string.Empty; + var ids = csv.Split(',', StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries) + .Select(s => long.TryParse(s, out var v) ? v : 0L) + .Where(v => v > 0 && v != exceptId) + .Distinct() + .ToList(); + return string.Join(",", ids); + } + + private static IEnumerable ParseTagged(string? csv) => + string.IsNullOrWhiteSpace(csv) + ? Enumerable.Empty() + : csv.Split(',', StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries) + .Select(s => long.TryParse(s, out var v) ? v : 0L) + .Where(v => v > 0); + + private async Task IsAdminAsync(long? playerId) + { + if (playerId is not long id) return false; + return await db.Players.Where(p => p.Id == id).Select(p => p.IsAdmin).FirstOrDefaultAsync(); + } + + /// Materialise photo rows into the wire DTO shape. One + /// extra DB hit fetches the uploader display names and room names + /// in batch so the JSON has human-readable labels — saves the + /// frontend from a per-row N+1. + private async Task> EnrichAsync(IEnumerable photos) + { + var photoList = photos.ToList(); + if (photoList.Count == 0) return new(); + + var uploaderIds = photoList.Select(p => p.UploaderPlayerId).Distinct().ToList(); + var roomIds = photoList.Select(p => p.RoomId).Where(id => id > 0).Distinct().ToList(); + + var uploaderNames = await db.Players + .Where(p => uploaderIds.Contains(p.Id)) + .Select(p => new { p.Id, p.DisplayName, p.Username }) + .ToListAsync(); + var roomNames = await db.Rooms + .Where(r => roomIds.Contains(r.Id)) + .Select(r => new { r.Id, r.Name }) + .ToListAsync(); + + var uMap = uploaderNames.ToDictionary(u => u.Id); + var rMap = roomNames.ToDictionary(r => r.Id); + + return photoList.Select(p => + { + uMap.TryGetValue(p.UploaderPlayerId, out var u); + rMap.TryGetValue(p.RoomId, out var r); + return (object)new + { + p.Id, + p.UploaderPlayerId, + UploaderDisplayName = u?.DisplayName ?? $"Player_{p.UploaderPlayerId}", + UploaderUsername = u?.Username ?? string.Empty, + p.BlobName, + ImageUrl = $"https://{domain.Sub("cdn")}/{p.BlobName}", + p.Caption, + p.RoomId, + RoomName = r?.Name ?? string.Empty, + TaggedPlayerIds = ParseTagged(p.TaggedPlayerIdsCsv).ToArray(), + p.IsPublic, + p.CheerCount, + p.ViewCount, + p.CreatedAt, + }; + }).ToList(); + } + + private object ToDto(PhotoEntity p) => new + { + p.Id, + p.UploaderPlayerId, + p.BlobName, + ImageUrl = $"https://{domain.Sub("cdn")}/{p.BlobName}", + p.Caption, + p.RoomId, + TaggedPlayerIds = ParseTagged(p.TaggedPlayerIdsCsv).ToArray(), + p.IsPublic, + p.CheerCount, + p.ViewCount, + p.CreatedAt, + }; +} diff --git a/DorkNet.Server/Controllers/API/PlatformLogin/V2/PlatformLoginController.cs b/DorkNet.Server/Controllers/API/PlatformLogin/V2/PlatformLoginController.cs new file mode 100644 index 0000000..47e3060 --- /dev/null +++ b/DorkNet.Server/Controllers/API/PlatformLogin/V2/PlatformLoginController.cs @@ -0,0 +1,209 @@ +using Microsoft.AspNetCore.Mvc; +using Microsoft.EntityFrameworkCore; +using DorkNet.Models.Auth; +using DorkNet.Server.Auth; +using DorkNet.Server.Data; +using DorkNet.Server.Services; + +namespace DorkNet.Server.Controllers.API.PlatformLogin.V2; + +/// +/// api.rec.net/api/platformlogin/* — OAuth-style password-grant login. +/// Login.LOGIN_API in dump.cs (line 578249) is "api/platformlogin/" — the +/// version suffix (v2 / v5) is appended by callers. The 2020 build uses v5. +/// +/// Request body is application/x-www-form-urlencoded (HTTPUrlEncodedForm in +/// Login.LoginHelper, dump.cs:578319), NOT JSON. Form fields are bound +/// case-insensitively by ASP.NET Core, so the parameter names don't have to +/// match the on-the-wire casing exactly. +/// +/// Response is a Login.LoginResponse: { Error, ErrorDescription, +/// AccessToken, RefreshToken } — JSON keys must be PascalCase (the +/// Util.GetKey calls inside Login.LoginResponse.Deserialize pass PascalCase +/// string literals; verified via global-metadata.dat scan). +/// +[ApiController] +[Route("api/platformlogin")] +public class PlatformLoginController( + PlayerService playerService, + AuthService authService, + ServerSettingsService settings, + DorkNetDbContext db) : ControllerBase +{ + // Login.LoginToAccount → POST api/platformlogin/v2 / v5 + // Form fields the 2020 client sends are not yet fully confirmed; common + // OAuth-2 password-grant + Rec Room platform fields: + // grant_type, username, password, platform, platformId, deviceId, + // buildTimestamp, version. Bind permissively and ignore extras. + [HttpPost("v2")] + [HttpPost("v5")] + public async Task> Login( + [FromForm(Name = "username")] string? Username, + [FromForm(Name = "Name")] string? Name, + [FromForm(Name = "platform")] int Platform = 0, + [FromForm(Name = "platformId")] string? PlatformId = null, + [FromForm(Name = "deviceId")] string? DeviceId = null, + [FromForm(Name = "buildTimestamp")] long BuildTimestamp = 0, + [FromForm(Name = "version")] string? Version = null) + { + var displayName = Username ?? Name ?? PlatformId ?? "Player"; + + // Admin "signups disabled" gate. Mirrors the same guard on + // /connect/token's device-id fallback in AuthController — if + // the device has no existing account, GetOrCreate would mint a + // new one here, bypassing the toggle. Refuse pre-creation. + if (await settings.AreSignupsDisabledAsync() + && await playerService.GetByDeviceAsync(DeviceId) is null) + { + return Unauthorized(new LoginResponse + { + Error = "signups_disabled", + ErrorDescription = "Account creation is currently disabled by the server admin.", + }); + } + + // Account identity is keyed off deviceId. The service handles the + // null/empty case by synthesising a stable id from platformId or a + // GUID — either way, no two unrelated callers ever share an account. + var player = await playerService.GetOrCreateByDeviceAsync( + deviceId: DeviceId, + platform: Platform, + platformId: PlatformId, + displayName: displayName); + var (access, refresh) = authService.GenerateTokenPair(player.Id); + + return Ok(new LoginResponse + { + AccessToken = access, + RefreshToken = refresh, + }); + } + + // Login.DownloadCachedLogins → GET api/platformlogin/cached + // Returns List. Empty array means no remembered accounts. + // + // The 2020 client doesn't include a platformId in the path here, but + // BestHTTP appends current-session credentials as query params on these + // bootstrap calls — so we accept platform/platformId via query or form + // and reuse the same lookup as auth.rec.net/cachedlogin/forplatformid. + [HttpGet("cached")] + [HttpGet("cachedlogins")] + [HttpGet("savedlogins")] + [HttpGet("v1/cached")] + [HttpGet("v2/cached")] + public async Task GetCachedLogins( + [FromQuery] int platform = 0, + [FromQuery] string? platformId = null) + { + var players = await playerService.GetCachedLoginsAsync(platform, platformId); + return Ok(players.Select(p => new CachedLogin + { + Platform = platform, + PlatformId = platformId ?? string.Empty, + AccountId = (int)p.Id, + LastLoginTime = p.LastSeenAt, + RequirePassword = false, + }).ToList()); + } + + // Login.RemoveCachedLogin → DELETE api/platformlogin/cached + [HttpDelete("cached")] + [HttpDelete("cachedlogins")] + public IActionResult RemoveCachedLogin() => Ok(new { }); + + // Login.RefreshLogin → POST api/platformlogin/refresh + // Response is Login.RefreshLoginResponse { Token } — single field, the + // new access token. (Note: dump.cs Login.RefreshLoginResponse has only + // "Token", not "AccessToken".) + [HttpPost("refresh")] + public async Task Refresh() + { + // Refresh requires a valid bearer (access OR refresh JWT); both are + // signed with the same secret so ValidateToken accepts either. + // Refusing to "GetOrCreateAsync(null)" here means a stale install + // can't quietly mint a brand-new account when its refresh token is + // gone — it has to go through the cached-login flow instead. + var auth = Request.Headers.Authorization.ToString(); + if (!auth.StartsWith("Bearer ", StringComparison.OrdinalIgnoreCase)) + return Unauthorized(); + var token = auth["Bearer ".Length..].Trim(); + var resolved = authService.ValidateToken(token); + if (resolved is not long playerId) + return Unauthorized(); + var player = await playerService.GetByIdAsync(playerId); + if (player is null) return Unauthorized(); + var (access, _) = authService.GenerateTokenPair(player.Id); + return Ok(new RefreshLoginResponse { Token = access }); + } + + // Login.LoginToCachedAccount(int accountId) → POST api/platformlogin/cached/{accountId} + // Returns the same Login.LoginResponse shape as the regular login. + [HttpPost("cached/{accountId:long}")] + public async Task LoginToCachedAccount( + long accountId, + [FromForm] int platform = 0, + [FromForm(Name = "platformId")] string? platformId = null, + [FromForm(Name = "platform_id")] string? platformIdSnake = null) + { + // No GetOrCreateAsync(null) fallback — if the cached id doesn't + // resolve, the cache is stale (another DB instance, deleted account, + // etc.) and we'd rather force the client back to the create-account + // flow than silently mint a new player. + var player = await playerService.GetByIdAsync(accountId); + if (player is null) return Unauthorized(); + await playerService.TagPlatformAsync(player.Id, platform, platformId ?? platformIdSnake); + var (access, refresh) = authService.GenerateTokenPair(player.Id); + return Ok(new LoginResponse + { + AccessToken = access, + RefreshToken = refresh, + }); + } + + // Login.CheckHasPassword → GET api/platformlogin/haspassword + // Client uses ExpectPrimitiveResponse → Convert.ChangeType + // on the raw response, so we MUST emit a bare boolean literal. + // Real password state lives on PlayerEntity.PasswordHash; + // AuthAccountController on auth.* does the same lookup against the + // authenticated player. We mirror it here so the api.* host also + // returns the truthful value instead of an always-true stub. + [HttpGet("haspassword")] + public async Task HasPassword() + { + var pid = this.CurrentPlayerId() ?? 0; + if (pid == 0) return Content("false", "application/json"); + var player = await playerService.GetByIdAsync(pid); + var has = player?.PasswordHash is { Length: > 0 }; + return Content(has ? "true" : "false", "application/json"); + } + + // Login.CreatePassword / ChangePassword / ResetPassword on auth.* + // already do the real bcrypt write — they're wired in + // AuthAccountController. The api.* path duplicates the route + // without persisting; rather than mirror the full logic here, + // delegate to AuthService.ChangePassword for consistency. + [HttpPost("password")] + [HttpPut("password")] + public async Task SetPassword([FromForm(Name = "newPassword")] string? newPassword, + [FromForm(Name = "password")] string? legacy) + { + var pid = this.CurrentPlayerId() ?? 0; + if (pid == 0) return Unauthorized(); + var pw = newPassword ?? legacy ?? string.Empty; + if (pw.Length < 6) return Ok(new RecNetResult { Success = false, Error = "password_too_short" }); + var player = await db.Players.FirstOrDefaultAsync(p => p.Id == pid); + if (player is null) return NotFound(); + player.PasswordHash = BCrypt.Net.BCrypt.HashPassword(pw, workFactor: 11); + await db.SaveChangesAsync(); + return Ok(new RecNetResult { Success = true, Error = string.Empty }); + } + + [HttpPost("resetpassword")] + public IActionResult ResetPassword() + { + // Real password reset would email a token; on a private + // server with no SMTP this is an explicit no-op (the admin + // SPA "change password" tool is the supported path). + return Ok(new RecNetResult { Success = false, Error = "reset_not_supported" }); + } +} diff --git a/DorkNet.Server/Controllers/API/PlayerCheer/PlayerCheerController.cs b/DorkNet.Server/Controllers/API/PlayerCheer/PlayerCheerController.cs new file mode 100644 index 0000000..fe10672 --- /dev/null +++ b/DorkNet.Server/Controllers/API/PlayerCheer/PlayerCheerController.cs @@ -0,0 +1,117 @@ +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Mvc; +using Microsoft.EntityFrameworkCore; +using DorkNet.Models.Notification; +using DorkNet.Server.Auth; +using DorkNet.Server.Data; +using DorkNet.Server.Data.Entities; +using DorkNet.Server.Services; + +namespace DorkNet.Server.Controllers.API.PlayerCheer; + +/// +/// api.{rec.net,localhost}/api/PlayerCheer/v1/* — peer cheer flow. +/// Cheers between players are stored as +/// rows keyed by TargetPlayerId. The "selected cheer" badge +/// each player can pin to their profile is stored as a +/// with key SelectedCheer. +/// +[ApiController] +[Authorize] +public class PlayerCheerController( + DorkNetDbContext db, + NotificationService notifications, + LevelService level) : ControllerBase +{ + private long Me => this.RequireCurrentPlayerId(); + + /// POST api/PlayerCheer/v1/create — caller cheers + /// the target player. Type matches the same enum as + /// CheerEntity.Type. Idempotent on (caller, target, type) + /// to match PlayerStateController.CheerPlayer, so the same + /// category click doesn't inflate the count. + [HttpPost("api/PlayerCheer/v1/create")] + [Consumes("application/x-www-form-urlencoded", "multipart/form-data", "application/json")] + public async Task Create( + [FromForm(Name = "TargetAccountId")] long? targetAccountId, + [FromForm(Name = "Type")] int type = 0) + { + if (targetAccountId is not long target || target <= 0) + return Ok(new { success = false, error = "missing_target" }); + if (Me == target) + return Ok(new { success = false, error = "cannot_cheer_self" }); + + var existing = await db.Cheers.FirstOrDefaultAsync(c => + c.FromPlayerId == Me && c.TargetPlayerId == target && + c.TargetRoomId == 0 && c.Type == type); + if (existing is not null) return Ok(new { success = true, already_cheered = true }); + + db.Cheers.Add(new CheerEntity + { + FromPlayerId = Me, + TargetPlayerId = target, + Type = type, + }); + await db.SaveChangesAsync(); + await level.AwardXpAsync(target, LevelService.CheerReceivedXp, $"cheer_from:{Me}"); + await notifications.NotifyAsync(target, + PushNotificationId.SubscriptionUpdateProfile, + new { Reason = "CheerReceived", From = Me, Type = type }); + return Ok(new { success = true, error = "" }); + } + + /// POST api/PlayerCheer/v1/SetSelectedCheer — pin + /// a cheer category as the caller's selected badge. Stored on the + /// PlayerSetting key SelectedCheer (string-form int). + /// + /// Form field is CheerCategory — NOT Cheer. The 2020.12 + /// watch's AIGGBBFKAKL.SetSelectedCheer (at + /// AIGGBBFKAKL.txt:1083-1084) does + /// AddField("CheerCategory", value) when POSTing to + /// api/PlayerCheer/v1/SetSelectedCheer. A mismatched binding + /// silently parses to 0, which the watch reads back as "no badge", + /// so the player's pinned cheer reverts every time. + /// + /// The posted value is the watch's PIDFCOGLFHN category enum (e.g. + /// 9000 for a specific category) — we store it verbatim and + /// round-trip through ; the + /// watch maps it back to the visual badge. + [HttpPost("api/PlayerCheer/v1/SetSelectedCheer")] + [Consumes("application/x-www-form-urlencoded", "multipart/form-data", "application/json")] + public async Task SetSelectedCheer( + [FromForm(Name = "CheerCategory")] int? cheerCategory = null, + [FromForm(Name = "Cheer")] int? legacyCheer = null) + { + // Accept either field name — `CheerCategory` for 2020.12, `Cheer` + // as a defensive fallback for older / re-skinned clients. + var value = cheerCategory ?? legacyCheer ?? 0; + var existing = await db.PlayerSettings + .FirstOrDefaultAsync(s => s.PlayerId == Me && s.Key == "SelectedCheer"); + if (existing is null) + db.PlayerSettings.Add(new PlayerSettingEntity { PlayerId = Me, Key = "SelectedCheer", Value = value.ToString() }); + else + existing.Value = value.ToString(); + await db.SaveChangesAsync(); + return Ok(new { success = true, error = "" }); + } + + /// GET api/PlayerCheer/v1/all — every cheer the + /// caller has received. Drives the profile's "X cheers" badge. + [HttpGet("api/PlayerCheer/v1/all")] + public async Task All() + { + var rows = await db.Cheers + .Where(c => c.TargetPlayerId == Me) + .OrderByDescending(c => c.CheeredAt) + .Take(200) + .Select(c => new + { + Id = c.Id, + FromPlayerId = c.FromPlayerId, + Type = c.Type, + CheeredAt = c.CheeredAt, + }) + .ToListAsync(); + return Ok(rows); + } +} diff --git a/DorkNet.Server/Controllers/API/PlayerElo/PlayerEloController.cs b/DorkNet.Server/Controllers/API/PlayerElo/PlayerEloController.cs new file mode 100644 index 0000000..6a89a8f --- /dev/null +++ b/DorkNet.Server/Controllers/API/PlayerElo/PlayerEloController.cs @@ -0,0 +1,69 @@ +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Mvc; +using Microsoft.EntityFrameworkCore; +using DorkNet.Server.Auth; +using DorkNet.Server.Data; +using DorkNet.Server.Data.Entities; + +namespace DorkNet.Server.Controllers.API.PlayerElo; + +/// +/// api.rec.net/api/PlayerElo/* — match-result Elo deltas. The 2020 +/// client posts after each ranked match (Royale, Paintball, Soccer) +/// with a delta the server applies to the per-player Elo row. +/// +/// Wire request matches RecNet.Elo.ReportPlayerElo: +/// {GameMode(int), EloDelta(int), Won(bool)} +/// (we tolerate a few synonym fields). Response: the new Elo as a +/// JSON primitive int. +/// +[ApiController] +public class PlayerEloController(DorkNetDbContext db) : ControllerBase +{ + private long Me => this.RequireCurrentPlayerId(); + + public sealed class ReportEloRequest + { + public int GameMode { get; set; } + public int EloDelta { get; set; } + public bool Won { get; set; } + } + + [HttpPost("api/PlayerElo/v1/reportPlayerElo")] + [Authorize] + public async Task ReportPlayerElo([FromBody] ReportEloRequest req) + { + var pid = Me; + var row = await db.PlayerElo.FirstOrDefaultAsync(p => + p.PlayerId == pid && p.GameMode == req.GameMode); + if (row is null) + { + row = new PlayerEloEntity + { + PlayerId = pid, + GameMode = req.GameMode, + }; + db.PlayerElo.Add(row); + } + // Clamp Elo to a reasonable range to avoid overflow exploits. + row.Elo = Math.Clamp(row.Elo + req.EloDelta, 0, 5000); + row.MatchesPlayed += 1; + if (req.Won) row.Wins += 1; else row.Losses += 1; + row.UpdatedAt = DateTime.UtcNow; + await db.SaveChangesAsync(); + return Ok(row.Elo); + } + + [HttpGet("api/PlayerElo/v1/get")] + public async Task Get( + [FromQuery] long? playerId, [FromQuery] int gameMode = 0) + { + var pid = playerId ?? this.CurrentPlayerId() ?? 0; + if (pid == 0) return Ok(new { Elo = 1000, Wins = 0, Losses = 0 }); + var row = await db.PlayerElo.FirstOrDefaultAsync(p => + p.PlayerId == pid && p.GameMode == gameMode); + return Ok(row is null + ? new { Elo = 1000, Wins = 0, Losses = 0 } + : new { row.Elo, row.Wins, row.Losses }); + } +} diff --git a/DorkNet.Server/Controllers/API/PlayerEvents/PlayerEventsController.cs b/DorkNet.Server/Controllers/API/PlayerEvents/PlayerEventsController.cs new file mode 100644 index 0000000..72af519 --- /dev/null +++ b/DorkNet.Server/Controllers/API/PlayerEvents/PlayerEventsController.cs @@ -0,0 +1,330 @@ +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Mvc; +using Microsoft.EntityFrameworkCore; +using DorkNet.Models.Notification; +using DorkNet.Server.Auth; +using DorkNet.Server.Data; +using DorkNet.Server.Data.Entities; +using DorkNet.Server.Services; + +namespace DorkNet.Server.Controllers.API.PlayerEvents; + +/// +/// api.rec.net/api/playerevents/v1/* — create, list, RSVP for +/// player-scheduled gatherings. +/// +[ApiController] +[Authorize] +public class PlayerEventsController( + DorkNetDbContext db, + NotificationService notifications) : ControllerBase +{ + private long Me => this.RequireCurrentPlayerId(); + + // ── Browse (v2) ────────────────────────────────────────────────────── + + /// GET api/playerevents/v2 — upcoming events list. + /// Returns the next 50 events ordered by start time. Wire shape + /// is the standard PlayerEvent (Id, CreatorPlayerId, RoomId, + /// Title, Description, StartsAt, EndsAt, Capacity). + [AllowAnonymous] + [HttpGet("api/playerevents/v2")] + public async Task ListUpcoming( + [FromQuery] int skip = 0, [FromQuery] int take = 50) + { + skip = Math.Max(0, skip); + take = Math.Clamp(take, 1, 100); + var rows = await db.PlayerEvents + .Where(e => e.EndsAt > DateTime.UtcNow) + .OrderBy(e => e.StartsAt) + .Skip(skip) + .Take(take) + .ToListAsync(); + return Ok(rows.Select(ToWire)); + } + + [AllowAnonymous] + [HttpGet("api/playerevents/v2/{eventId:long}")] + public async Task GetOne(long eventId) + { + var ev = await db.PlayerEvents.FirstOrDefaultAsync(e => e.Id == eventId); + if (ev is null) return NotFound(); + return Ok(ToWire(ev)); + } + + /// GET api/playerevents/v1/all — the watch's + /// LocalPlayerEventInfo fetch (caller's created + RSVP'd + /// events). Response keys (both required, via + /// Util.GetObjectListKey): Created, Responses. + [AllowAnonymous] + [HttpGet("api/playerevents/v1/all")] + public async Task AllPlayerEvents() + { + var pid = this.CurrentPlayerId(); + if (pid is not long me) + return Ok(new { Created = Array.Empty(), Responses = Array.Empty() }); + + var created = await db.PlayerEvents + .Where(e => e.CreatorPlayerId == me) + .OrderByDescending(e => e.StartsAt) + .Select(e => new + { + e.Id, + e.Title, + e.Description, + e.RoomId, + e.StartsAt, + e.EndsAt, + e.Capacity, + }) + .ToListAsync(); + + var responses = await db.PlayerEventResponses + .Where(r => r.PlayerId == me) + .Select(r => new + { + r.Id, + r.EventId, + r.Response, + r.CreatedAt, + }) + .ToListAsync(); + + return Ok(new { Created = created, Responses = responses }); + } + + /// POST api/playerevents/v1/{eventId}/responses — + /// alternate RSVP route the watch uses for the response-list + /// flow (older / sub-room view). Body is the same RsvpRequest + /// as v1/{id}/rsvp; behaves identically. + [HttpPost("api/playerevents/v1/{eventId:long}/responses")] + public Task RespondList(long eventId, [FromBody] RsvpRequest body) + => Rsvp(eventId, body); + + /// Wire shape verified against + /// Cpp2IL_ISIL/.../RecNet/PlayerEvent.txt:769-815: + /// PascalCase keys PlayerEventId, Name, Description, + /// StartTime, EndTime, CreatorPlayerId, AttendeeCount, RoomId, + /// ImageName, Accessibility. Server-side we store these + /// under different names (Title, StartsAt, EndsAt, Capacity) — + /// remap explicitly here so the watch's + /// PlayerEvent.Deserialize picks them up. + private static object ToWire(PlayerEventEntity ev) => new + { + PlayerEventId = ev.Id, + Name = ev.Title, + ev.Description, + StartTime = ev.StartsAt, + EndTime = ev.EndsAt, + CreatorPlayerId = (int)ev.CreatorPlayerId, + AttendeeCount = 0, // computed at query time when needed + ev.RoomId, + ImageName = string.Empty, + Accessibility = 1, // Public + }; + + public sealed record CreateEventRequest( + string Title, string? Description, long RoomId, + DateTime StartsAt, DateTime EndsAt, int Capacity); + + [HttpPost("api/playerevents/v1/create")] + [HttpPost("api/playerevents/v1")] + public async Task Create([FromBody] CreateEventRequest req) + { + if (string.IsNullOrWhiteSpace(req.Title)) + return BadRequest("missing_title"); + var ev = new PlayerEventEntity + { + CreatorPlayerId = Me, + RoomId = req.RoomId, + Title = req.Title.Trim(), + Description = (req.Description ?? string.Empty).Trim(), + StartsAt = req.StartsAt, + EndsAt = req.EndsAt > req.StartsAt ? req.EndsAt : req.StartsAt.AddHours(1), + Capacity = Math.Max(0, req.Capacity), + }; + db.PlayerEvents.Add(ev); + await db.SaveChangesAsync(); + return Ok(new + { + ev.Id, ev.Title, ev.Description, ev.RoomId, + ev.StartsAt, ev.EndsAt, ev.Capacity, + }); + } + + public sealed record RsvpRequest(int Response); + + [HttpPost("api/playerevents/v1/{eventId:long}/rsvp")] + public async Task Rsvp(long eventId, [FromBody] RsvpRequest body) + { + if (body.Response < 0 || body.Response > 2) + return BadRequest("invalid_response"); + var ev = await db.PlayerEvents.FirstOrDefaultAsync(e => e.Id == eventId); + if (ev is null) return NotFound(); + + var row = await db.PlayerEventResponses + .FirstOrDefaultAsync(r => r.EventId == eventId && r.PlayerId == Me); + if (row is null) + { + row = new PlayerEventResponseEntity { EventId = eventId, PlayerId = Me }; + db.PlayerEventResponses.Add(row); + } + row.Response = body.Response; + await db.SaveChangesAsync(); + + // Tell the creator someone RSVP'd. They can ignore but the + // watch will refresh their event detail card. + if (ev.CreatorPlayerId != Me) + await notifications.NotifyAsync(ev.CreatorPlayerId, + PushNotificationId.PlayerEventResponseChanged, + new { ev.Id, From = Me, body.Response }); + return Ok(new { row.EventId, row.PlayerId, row.Response }); + } + + /// POST api/playerevents/v1/respond — set the + /// caller's RSVP state for an event. Response is the + /// PlayerEventResponseType enum (0=Going, 1=Maybe, 2=Pass). + /// Idempotent — re-responding overwrites. + /// + /// Wire return is a BARE int (DGOPHENCPOC enum value: + /// 0=Success, plus various error codes). Returning an object + /// here throws InvalidCastException on the 2020.12 deserialiser + /// — verified at recroom-2020-client-response-contracts.md + /// under the playerevents/v1/respond section. + [HttpPost("api/playerevents/v1/respond")] + public async Task RespondForm( + [FromForm(Name = "EventId")] long? eventId, + [FromForm(Name = "Response")] int response = 0) + { + if (eventId is not long evt || evt <= 0) return Ok(2 /*NoSuchEvent*/); + var existing = await db.PlayerEventResponses + .FirstOrDefaultAsync(r => r.PlayerId == Me && r.EventId == evt); + if (existing is null) + db.PlayerEventResponses.Add(new PlayerEventResponseEntity { PlayerId = Me, EventId = evt, Response = response }); + else + existing.Response = response; + await db.SaveChangesAsync(); + return Ok(0 /*Success*/); + } + + /// POST api/playerevents/v1/deleteResponse — drop + /// the caller's RSVP row. Same bare-int wire shape as + /// . + [HttpPost("api/playerevents/v1/deleteResponse")] + public async Task DeleteResponseForm([FromForm(Name = "EventId")] long? eventId) + { + if (eventId is not long evt) return Ok(2 /*NoSuchEvent*/); + await db.PlayerEventResponses + .Where(r => r.PlayerId == Me && r.EventId == evt) + .ExecuteDeleteAsync(); + return Ok(0 /*Success*/); + } + + /// DELETE api/playerevents/v2/delete/{id} — hard + /// delete an event the caller created. + [HttpDelete("api/playerevents/v2/delete/{id:long}")] + public async Task DeleteByPath(long id) + { + var evt = await db.PlayerEvents.FirstOrDefaultAsync(e => e.Id == id && e.CreatorPlayerId == Me); + if (evt is null) return NotFound(); + db.PlayerEvents.Remove(evt); + await db.PlayerEventResponses.Where(r => r.EventId == id).ExecuteDeleteAsync(); + await db.SaveChangesAsync(); + return Ok(new { success = true, error = "" }); + } + + [HttpDelete("api/playerevents/v1/{eventId:long}")] + public async Task Delete(long eventId) + { + var ev = await db.PlayerEvents.FirstOrDefaultAsync(e => e.Id == eventId); + if (ev is null) return NotFound(); + if (ev.CreatorPlayerId != Me) return Forbid(); + db.PlayerEvents.Remove(ev); + // Cascade-clean the responses; SQLite has no FK cascade + // configured here so do it manually. + var responses = db.PlayerEventResponses.Where(r => r.EventId == eventId); + db.PlayerEventResponses.RemoveRange(responses); + await db.SaveChangesAsync(); + return Ok(); + } + + // ── Bulk-invite (Phase 8) ──────────────────────────────────────────── + + public sealed class BulkInviteRequest + { + public long PlayerEventId { get; set; } + public List? InvitedPlayerIds { get; set; } + } + + /// POST /api/playerevents/v1/bulkInvite — wire + /// type per agent ISIL extraction: BulkInviteRequest{PlayerEventId, + /// InvitedPlayerIds:List<int>}; response + /// BulkInviteResponse{FailedInvites,Result(CreateModifyPlayerEventStatus)}. + /// We insert one MessageEntity per recipient so the watch's inbox + /// shows the invite, and push a per-recipient notification. + [HttpPost("api/playerevents/v1/bulkInvite")] + public async Task BulkInvite([FromBody] BulkInviteRequest req) + { + if (req?.InvitedPlayerIds is null || req.InvitedPlayerIds.Count == 0) + return Ok(new { FailedInvites = Array.Empty(), Result = 0 }); + + var ev = await db.PlayerEvents.FirstOrDefaultAsync(e => e.Id == req.PlayerEventId); + if (ev is null) return NotFound(); + if (ev.CreatorPlayerId != Me) return Forbid(); + + var failed = new List(); + var sender = Me; + var inviteText = $"You're invited to '{ev.Title}' starting {ev.StartsAt:u}."; + foreach (var rid in req.InvitedPlayerIds.Distinct()) + { + var exists = await db.Players.AnyAsync(p => p.Id == rid); + if (!exists) + { + failed.Add(new { PlayerId = rid, Error = "unknown player" }); + continue; + } + db.Messages.Add(new MessageEntity + { + SenderPlayerId = sender, + RecipientPlayerId = rid, + Body = inviteText, + }); + await notifications.NotifyAsync(rid, + PushNotificationId.PlayerEventCreated, + new { ev.Id, ev.Title, ev.StartsAt, From = sender }); + } + await db.SaveChangesAsync(); + return Ok(new { FailedInvites = failed, Result = 0 }); + } + + // ── Report (Phase 8) ───────────────────────────────────────────────── + + public sealed class PlayerEventReportRequest + { + public int ReportCategory { get; set; } + public long PlayerEventId { get; set; } + public string? Details { get; set; } + } + + /// POST api/playerevents/v1/report — file a + /// moderation report against an event. Wire response is + /// KLAMKCBENEA{Success, Message}; returning a bare + /// {Reported:true} throws on the strict deserialiser. + [HttpPost("api/playerevents/v1/report")] + public async Task Report([FromBody] PlayerEventReportRequest req) + { + var ev = await db.PlayerEvents.FirstOrDefaultAsync(e => e.Id == req.PlayerEventId); + if (ev is null) return Ok(new { Success = false, Message = "no_such_event" }); + var msg = (req.Details ?? string.Empty).Trim(); + db.Reports.Add(new ReportEntity + { + ReporterPlayerId = Me, + TargetPlayerId = ev.CreatorPlayerId, + TargetEventId = req.PlayerEventId, + Category = req.ReportCategory, + Message = msg[..Math.Min(1000, msg.Length)], + }); + await db.SaveChangesAsync(); + return Ok(new { Success = true, Message = string.Empty }); + } +} diff --git a/DorkNet.Server/Controllers/API/PlayerReporting/ReportsController.cs b/DorkNet.Server/Controllers/API/PlayerReporting/ReportsController.cs new file mode 100644 index 0000000..a1e4df6 --- /dev/null +++ b/DorkNet.Server/Controllers/API/PlayerReporting/ReportsController.cs @@ -0,0 +1,221 @@ +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Mvc; +using Microsoft.EntityFrameworkCore; +using System.Text.Json; +using DorkNet.Server.Auth; +using DorkNet.Server.Data; +using DorkNet.Server.Data.Entities; + +namespace DorkNet.Server.Controllers.API.PlayerReporting; + +/// +/// api.rec.net/api/PlayerReporting/v1/* — real player report flow. +/// The 2020 client's "report player" UI in the watch posts here. +/// Form fields the watch sends (per the in-game send_report dialog): +/// TargetPlayerId — int account id of the offender +/// Type — RoomieReportCategory enum (1..5) +/// Message — free-form description (capped at 1000) +/// GameSessionId — optional, current session for context +/// RoomId — optional, room the offence happened in +/// +/// Anyone authenticated can file a report. Admins read the queue +/// from GET api/admin/v1/reports. +/// +[ApiController] +[Authorize] +public class ReportsController(DorkNetDbContext db) : ControllerBase +{ + [HttpGet("api/PlayerReporting/v1/voteToKickReasons")] + [AllowAnonymous] + public IActionResult VoteToKickReasons() => Ok(new[] + { + new { ReportCategory = 1, Reason = "Harassment" }, + new { ReportCategory = 2, Reason = "Inappropriate behavior" }, + new { ReportCategory = 3, Reason = "Cheating" }, + new { ReportCategory = 4, Reason = "Spam" }, + new { ReportCategory = 5, Reason = "Other" }, + }); + + [HttpPost("api/PlayerReporting/v1/report")] + [HttpPost("api/PlayerReporting/v1/playerreport")] + [HttpPost("api/PlayerReporting/v2/report")] + [Consumes("application/x-www-form-urlencoded", "multipart/form-data", "application/json")] + public async Task SubmitReport( + [FromForm(Name = "TargetPlayerId")] long? targetPlayerId, + [FromForm(Name = "Type")] int type = 0, + [FromForm(Name = "Message")] string? message = null, + [FromForm(Name = "GameSessionId")] long gameSessionId = 0, + [FromForm(Name = "RoomId")] long roomId = 0) + { + if (targetPlayerId is not long target || target <= 0) + return BadRequest(new { success = false, error = "missing_target" }); + + var reporter = this.RequireCurrentPlayerId(); + if (reporter == target) + return BadRequest(new { success = false, error = "cannot_report_self" }); + + // Cap message length to match the watch UI's enforcement + + // the column max — anything longer is almost certainly junk. + var trimmed = (message ?? string.Empty).Trim(); + if (trimmed.Length > 1000) trimmed = trimmed[..1000]; + + db.Reports.Add(new ReportEntity + { + ReporterPlayerId = reporter, + TargetPlayerId = target, + Category = type, + Message = trimmed, + GameSessionId = gameSessionId, + RoomId = roomId, + }); + await db.SaveChangesAsync(); + + return Ok(new { success = true, error = "" }); + } + + /// GET api/PlayerReporting/v2/detail?playerId={id} + /// — moderator detail panel. Lists recent reports against the + /// target player. Public — admin-only mutations are elsewhere. + [HttpGet("api/PlayerReporting/v2/detail")] + public async Task ReportDetail( + [FromQuery] long? playerId, [FromQuery] long? accountId) + { + var pid = playerId ?? accountId ?? 0; + if (pid <= 0) return Ok(new { PlayerId = 0L, OpenReports = 0, RecentReports = Array.Empty() }); + var reports = await db.Reports + .Where(r => r.TargetPlayerId == pid) + .OrderByDescending(r => r.CreatedAt) + .Take(20) + .ToListAsync(); + return Ok(new + { + PlayerId = pid, + OpenReports = reports.Count(r => r.ResolvedAt == null), + RecentReports = reports.Select(r => new + { + r.Id, r.Category, r.Message, + r.ReporterPlayerId, r.RoomId, r.CreatedAt, r.ResolvedAt, + }), + }); + } + + public sealed class V2BanRequest + { + public long PlayerId { get; set; } + public int? Category { get; set; } + public string? Reason { get; set; } + public DateTime? Until { get; set; } + } + + /// POST api/PlayerReporting/v2/ban — admin-only. + /// Sets + appends a + /// resolved for the audit trail. + [HttpPost("api/PlayerReporting/v2/ban")] + [Authorize] + public async Task V2Ban([FromBody] V2BanRequest req) + { + var me = this.RequireCurrentPlayerId(); + if (req.PlayerId <= 0) return BadRequest(new { error = "missing playerId" }); + + var isAdmin = await db.Players + .Where(p => p.Id == me) + .Select(p => p.IsAdmin) + .FirstOrDefaultAsync(); + if (!isAdmin) return Forbid(); + + var target = await db.Players.FirstOrDefaultAsync(p => p.Id == req.PlayerId); + if (target is null) return NotFound(); + target.BannedUntil = req.Until ?? DateTime.UtcNow.AddYears(100); + var reason = req.Reason ?? string.Empty; + if (reason.Length > 1000) reason = reason[..1000]; + db.Reports.Add(new ReportEntity + { + ReporterPlayerId = me, + TargetPlayerId = req.PlayerId, + Category = req.Category ?? 5, + Message = reason, + ResolvedAt = DateTime.UtcNow, + ResolverAdminId = me, + ResolutionNote = "v2/ban", + }); + await db.SaveChangesAsync(); + return Ok(new { Banned = true, target.Id, target.BannedUntil }); + } + + public sealed class HileRequest + { + public string? Detail { get; set; } + public string? Body { get; set; } + public string? Message { get; set; } + public int? Type { get; set; } + public long? GameSessionId { get; set; } + } + + /// POST api/PlayerReporting/v1/hile — anti-cheat + /// heartbeat. Stored as a tagged + /// hile so admins can audit the stream. + [HttpPost("api/PlayerReporting/v1/hile")] + public async Task Hile() + { + if (Request.HasFormContentType) + { + var form = await Request.ReadFormAsync(); + return await SaveHileAsync( + form["Message"].FirstOrDefault() ?? + form["Detail"].FirstOrDefault() ?? + form["Body"].FirstOrDefault(), + int.TryParse(form["Type"].FirstOrDefault(), out var t) ? t : (int?)null, + long.TryParse(form["GameSessionId"].FirstOrDefault(), out var g) ? g : (long?)null); + } + + HileRequest? body = null; + try + { + body = await JsonSerializer.DeserializeAsync( + Request.Body, + new JsonSerializerOptions { PropertyNameCaseInsensitive = true }); + } + catch (JsonException) { } + + return await SaveHileAsync( + body?.Message ?? body?.Detail ?? body?.Body, + body?.Type, + body?.GameSessionId); + } + + /// POST api/PlayerReporting/v1/log — bare client + /// telemetry log. Persisted as a low-priority BugReport for admin + /// visibility but never acted on automatically. + [HttpPost("api/PlayerReporting/v1/log")] + public async Task ReportLog() + { + if (Request.HasFormContentType) + { + var form = await Request.ReadFormAsync(); + await SaveHileAsync( + form["Message"].FirstOrDefault() ?? form["Body"].FirstOrDefault(), + null, null); + } + return Ok(new { success = true, error = "" }); + } + + private async Task SaveHileAsync(string? rawDetail, int? type, long? gameSessionId) + { + var pid = this.CurrentPlayerId(); + if (pid is not long me) return Ok(new { success = true }); + var detail = (rawDetail ?? string.Empty).Trim(); + if (type is int hileType) + detail = detail.Length == 0 ? $"Type={hileType}" : $"Type={hileType}; {detail}"; + if (detail.Length > 4000) detail = detail[..4000]; + db.BugReports.Add(new BugReportEntity + { + ReporterPlayerId = me, + Title = "hile heartbeat", + Body = detail, + GameSessionId = gameSessionId ?? 0, + Category = "hile", + }); + await db.SaveChangesAsync(); + return Ok(new { success = true }); + } +} diff --git a/DorkNet.Server/Controllers/API/PlayerState/PlayerStateController.cs b/DorkNet.Server/Controllers/API/PlayerState/PlayerStateController.cs new file mode 100644 index 0000000..44e7cc3 --- /dev/null +++ b/DorkNet.Server/Controllers/API/PlayerState/PlayerStateController.cs @@ -0,0 +1,227 @@ +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Mvc; +using Microsoft.EntityFrameworkCore; +using DorkNet.Models.Notification; +using DorkNet.Models.Players; +using DorkNet.Server.Auth; +using DorkNet.Server.Data; +using DorkNet.Server.Data.Entities; +using DorkNet.Server.Services; + +namespace DorkNet.Server.Controllers.API.PlayerState; + +/// +/// api.rec.net endpoints that return player-state objects whose Deserialize +/// methods do strict required-key reads via Util.GetKey<T>. The previous +/// catch-all returned {} or {"Success":true} for these, which crashed the +/// client with KeyNotFoundException at: +/// RecNet.Progression.Deserialize → "PlayerId", "Level", "XP" +/// RecNet.Reputation.Deserialize → "AccountId", "Noteriety" (sic), Cheer*, Sub* +/// RecNet.ModerationBlockDetail.Deserialize → "ReportCategory", "Duration", "GameSessionId", "Message" +/// +/// Each endpoint here returns the minimum-viable shape (zeros / empty +/// strings) that satisfies every required key. Real values can be plugged +/// in later when persistence is added for these. +/// +/// Specific routes here win over the api/PlayerReporting/{*path} and +/// api.rec.net catch-alls. +/// +[ApiController] +public class PlayerStateController( + DorkNetDbContext db, + NotificationService notifications, + LevelService level) : ControllerBase +{ + // RecNet.Progressions.GetProgressionById / GetMyProgression — real + // values from PlayerEntity.Level / .XP. Default 1/0 when the + // account hasn't been seen yet (synthetic ids passed in queries). + [HttpGet("api/players/v1/progression/{accountId:long}")] + public async Task> GetProgression(long accountId) + { + var p = await db.Players + .Where(p => p.Id == accountId) + .Select(p => new { p.Id, p.Level, p.XP }) + .FirstOrDefaultAsync(); + return Ok(new Progression + { + PlayerId = (int)(p?.Id ?? accountId), + Level = p?.Level ?? 1, + XP = p?.XP ?? 0, + }); + } + + [HttpPost("api/players/v1/progression/bulk")] + [Consumes("application/x-www-form-urlencoded", "multipart/form-data")] + public async Task>> GetProgressionBulk( + [FromForm(Name = "Ids")] string? ids) + { + var idList = ParseIds(ids).Select(i => (long)i).ToList(); + if (idList.Count == 0) return Ok(new List()); + var rows = await db.Players + .Where(p => idList.Contains(p.Id)) + .Select(p => new Progression + { + PlayerId = (int)p.Id, + Level = p.Level, + XP = p.XP, + }) + .ToListAsync(); + // Synthesise zeros for any ids the watch asked about that + // don't exist in our DB — keeps array length stable. + var found = rows.Select(r => r.PlayerId).ToHashSet(); + rows.AddRange(idList + .Where(id => !found.Contains((int)id)) + .Select(id => new Progression { PlayerId = (int)id })); + return Ok(rows); + } + + // RecNet.Reputations.GetReputationById — aggregates CheerEntity + // by category. Each Cheer* count is the sum of distinct cheers + // received from other players in that category. + [HttpGet("api/playerReputation/v1/{accountId:long}")] + public async Task> GetReputation(long accountId) => + Ok(await BuildReputationAsync(accountId)); + + [HttpPost("api/playerReputation/v1/bulk")] + [Consumes("application/x-www-form-urlencoded", "multipart/form-data")] + public async Task>> GetReputationBulk( + [FromForm(Name = "Ids")] string? ids) + { + var idList = ParseIds(ids).Select(i => (long)i).ToList(); + var result = new List(idList.Count); + foreach (var id in idList) + result.Add(await BuildReputationAsync(id)); + return Ok(result); + } + + /// POST api/playerReputation/v1/cheer/{playerId} — cheer + /// another player. Idempotent on (caller, target, type) so + /// repeat-clicking the same category doesn't inflate the count. + /// Pushes a presence-update notification so the target's watch + /// refreshes their cheer total. + [HttpPost("api/playerReputation/v1/cheer/{playerId:long}")] + [Authorize] + public async Task CheerPlayer(long playerId, [FromQuery] int type = 0) + { + var me = this.RequireCurrentPlayerId(); + if (me == playerId) return BadRequest("cannot_cheer_self"); + + var existing = await db.Cheers.FirstOrDefaultAsync(c => + c.FromPlayerId == me && c.TargetPlayerId == playerId && + c.TargetRoomId == 0 && c.Type == type); + if (existing is not null) return Ok(new { already_cheered = true }); + + db.Cheers.Add(new CheerEntity + { + FromPlayerId = me, + TargetPlayerId = playerId, + Type = type, + }); + await db.SaveChangesAsync(); + + // Reward the cheered player with a small XP bump. + await level.AwardXpAsync(playerId, LevelService.CheerReceivedXp, $"cheer_from:{me}"); + + await notifications.NotifyAsync(playerId, + PushNotificationId.SubscriptionUpdateProfile, + new { Reason = "CheerReceived", From = me, Type = type }); + return Ok(); + } + + /// POST api/rooms/v1/cheer/{roomId} — cheer a room. + /// Same idempotency rules as player cheers; bumps + /// as a denormalised counter + /// so the watch's "Hot" rooms feed doesn't have to JOIN-aggregate + /// on every request. + [HttpPost("api/rooms/v1/cheer/{roomId:long}")] + [Authorize] + public async Task CheerRoom(long roomId, [FromQuery] int type = 0) + { + var me = this.RequireCurrentPlayerId(); + + var existing = await db.Cheers.FirstOrDefaultAsync(c => + c.FromPlayerId == me && c.TargetRoomId == roomId && + c.TargetPlayerId == 0 && c.Type == type); + if (existing is not null) return Ok(new { already_cheered = true }); + + db.Cheers.Add(new CheerEntity + { + FromPlayerId = me, + TargetRoomId = roomId, + Type = type, + }); + var room = await db.Rooms.FirstOrDefaultAsync(r => r.Id == roomId); + if (room is not null) + { + room.CheerCount += 1; + // Cheers feed the Hot-tab ranking — weight 5x a first + // visit so the "Trending" feed reflects what players + // actually like, not just what they passed through. + room.HotScore += 10.0; + } + await db.SaveChangesAsync(); + return Ok(); + } + + private async Task BuildReputationAsync(long accountId) + { + var rows = await db.Cheers + .Where(c => c.TargetPlayerId == accountId) + .GroupBy(c => c.Type) + .Select(g => new { Type = g.Key, Count = g.Count() }) + .ToListAsync(); + int Count(int type) => rows.FirstOrDefault(r => r.Type == type)?.Count ?? 0; + + var subscriberCount = await db.Subscriptions + .CountAsync(s => s.TargetPlayerId == accountId); + var subscribedCount = await db.Subscriptions + .CountAsync(s => s.SubscriberPlayerId == accountId); + + // SelectedCheer is the pinned badge category the player chose + // via PlayerCheerController.SetSelectedCheer. Stored as a + // PlayerSettingEntity with key "SelectedCheer" and Value = the + // category int as string. Null when unset — UI just shows no + // pinned badge. + var selected = await db.PlayerSettings + .Where(s => s.PlayerId == accountId && s.Key == "SelectedCheer") + .Select(s => s.Value) + .FirstOrDefaultAsync(); + int? selectedCheer = int.TryParse(selected, out var sc) ? sc : null; + + return new Reputation + { + AccountId = (int)accountId, + Notoriety = rows.Sum(r => r.Count), + CheerGeneral = Count(0), + CheerHelpful = Count(1), + CheerGreatHost = Count(2), + CheerSportsman = Count(3), + CheerCreative = Count(4), + CheerCredit = Count(5), + SelectedCheer = selectedCheer, + SubscriberCount = subscriberCount, + SubscribedCount = subscribedCount, + }; + } + + /// + /// "1,2,3" → [1, 2, 3]. The client formats Ids as a comma-separated + /// string when calling AddField on the form. Tolerant of empty input + /// and whitespace. + /// + private static IEnumerable ParseIds(string? ids) => + string.IsNullOrWhiteSpace(ids) + ? Enumerable.Empty() + : ids.Split(',', StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries) + .Select(s => int.TryParse(s, out var v) ? v : 0) + .Where(v => v != 0); + + // RecNet.PlayerReporting.GetModerationBlockDetails + // IMPORTANT: returns a single object, not an array. Defaults indicate + // "user is not currently blocked / kicked". + // ASP.NET Core route matching is case-insensitive, so a single attribute + // covers both "PlayerReporting" and "playerreporting" callers. + [HttpGet("api/PlayerReporting/v1/moderationBlockDetails")] + public ActionResult GetModerationBlockDetails() => + Ok(new ModerationBlockDetail()); +} diff --git a/DorkNet.Server/Controllers/API/PlayerSubscriptions/PlayerSubscriptionsController.cs b/DorkNet.Server/Controllers/API/PlayerSubscriptions/PlayerSubscriptionsController.cs new file mode 100644 index 0000000..3a7a0ad --- /dev/null +++ b/DorkNet.Server/Controllers/API/PlayerSubscriptions/PlayerSubscriptionsController.cs @@ -0,0 +1,83 @@ +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Mvc; +using Microsoft.EntityFrameworkCore; +using DorkNet.Models.Notification; +using DorkNet.Server.Auth; +using DorkNet.Server.Data; +using DorkNet.Server.Data.Entities; +using DorkNet.Server.Services; + +namespace DorkNet.Server.Controllers.API.PlayerSubscriptions; + +/// +/// api.rec.net/api/playersubscriptions/v1/* — asymmetric follow +/// graph. The my endpoint returns who the caller follows; +/// subscribe/unsubscribe mutate the row directly. Reputation +/// aggregates pull SubscriberCount / SubscribedCount counts from +/// here. +/// +[ApiController] +[Route("api/[controller]/v1")] +[Authorize] +public class PlayerSubscriptionsController( + DorkNetDbContext db, + NotificationService notifications) : ControllerBase +{ + private long Me => this.RequireCurrentPlayerId(); + + [HttpGet("my")] + public async Task My() + { + var subs = await db.Subscriptions + .Where(s => s.SubscriberPlayerId == Me) + .Select(s => new { s.TargetPlayerId, s.CreatedAt }) + .ToListAsync(); + return Ok(subs); + } + + [HttpGet("{playerId:long}/subscribers")] + public async Task Subscribers(long playerId) + { + var subs = await db.Subscriptions + .Where(s => s.TargetPlayerId == playerId) + .Select(s => new { s.SubscriberPlayerId, s.CreatedAt }) + .ToListAsync(); + return Ok(subs); + } + + [HttpPost("subscribe/{targetId:long}")] + public async Task Subscribe(long targetId) + { + if (targetId == Me) return BadRequest("cannot_subscribe_to_self"); + + var existing = await db.Subscriptions.FirstOrDefaultAsync(s => + s.SubscriberPlayerId == Me && s.TargetPlayerId == targetId); + if (existing is not null) return Ok(new { already_subscribed = true }); + + db.Subscriptions.Add(new SubscriptionEntity + { + SubscriberPlayerId = Me, + TargetPlayerId = targetId, + }); + await db.SaveChangesAsync(); + + // Tell the target their subscriber count changed so the watch + // refreshes their profile page if open. + await notifications.NotifyAsync(targetId, + PushNotificationId.SubscriptionUpdateProfile, + new { Reason = "NewSubscriber", From = Me }); + return Ok(); + } + + [HttpDelete("subscribe/{targetId:long}")] + [HttpPost("unsubscribe/{targetId:long}")] + public async Task Unsubscribe(long targetId) + { + var row = await db.Subscriptions.FirstOrDefaultAsync(s => + s.SubscriberPlayerId == Me && s.TargetPlayerId == targetId); + if (row is null) return NotFound(); + db.Subscriptions.Remove(row); + await db.SaveChangesAsync(); + return Ok(); + } +} diff --git a/DorkNet.Server/Controllers/API/Players/V2/PlayersController.cs b/DorkNet.Server/Controllers/API/Players/V2/PlayersController.cs new file mode 100644 index 0000000..38c377d --- /dev/null +++ b/DorkNet.Server/Controllers/API/Players/V2/PlayersController.cs @@ -0,0 +1,68 @@ +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Mvc; +using DorkNet.Models.Players; +using DorkNet.Server.Auth; +using DorkNet.Server.Services; + +namespace DorkNet.Server.Controllers.API.Players.V2; + +[ApiController] +[Route("api/[controller]/v2")] +[Authorize] +public class PlayersController(PlayerService playerService, NotificationService notificationService) : ControllerBase +{ + private long CurrentPlayerId => this.RequireCurrentPlayerId(); + + [HttpGet("me")] + public async Task> GetSelf() + { + var player = await playerService.GetByIdAsync(CurrentPlayerId); + if (player is null) return NotFound(); + return Ok(PlayerService.ToProfile(player)); + } + + [HttpGet("{id:long}")] + public async Task> GetById(long id) + { + var player = await playerService.GetByIdAsync(id); + if (player is null) return NotFound(); + return Ok(PlayerService.ToProfile(player)); + } + + [HttpGet("search")] + public async Task>> Search([FromQuery] string query) + { + if (string.IsNullOrWhiteSpace(query)) return Ok(new List()); + var results = await playerService.SearchAsync(query); + return Ok(results.Select(PlayerService.ToProfile).ToList()); + } + + [HttpPost("displayName")] + public async Task> UpdateDisplayName([FromForm] string? DisplayName) + { + if (string.IsNullOrWhiteSpace(DisplayName)) + return BadRequest("DisplayName is required"); + + var updated = await playerService.UpdateDisplayNameAsync(CurrentPlayerId, DisplayName); + if (!updated) return NotFound(); + + var player = await playerService.GetByIdAsync(CurrentPlayerId); + await notificationService.NotifyAsync(CurrentPlayerId, Models.Notification.PushNotificationId.SubscriptionUpdateProfile, PlayerService.ToProfile(player!)); + return Ok(PlayerService.ToProfile(player!)); + } + + [HttpPut("{id:long}")] + public async Task> UpdateProfile(long id, [FromBody] UpdateProfileRequest req) + { + if (id != CurrentPlayerId) return Forbid(); + + if (req.DisplayName is not null) + await playerService.UpdateDisplayNameAsync(id, req.DisplayName); + + if (req.Bio is not null) + await playerService.UpdateBioAsync(id, req.Bio); + + var player = await playerService.GetByIdAsync(id); + return Ok(PlayerService.ToProfile(player!)); + } +} diff --git a/DorkNet.Server/Controllers/API/PlayersBanned/PlayersBannedController.cs b/DorkNet.Server/Controllers/API/PlayersBanned/PlayersBannedController.cs new file mode 100644 index 0000000..ac34632 --- /dev/null +++ b/DorkNet.Server/Controllers/API/PlayersBanned/PlayersBannedController.cs @@ -0,0 +1,29 @@ +using Microsoft.AspNetCore.Mvc; +using Microsoft.EntityFrameworkCore; +using DorkNet.Server.Data; + +namespace DorkNet.Server.Controllers.API.PlayersBanned; + +/// +/// api.{rec.net,localhost}/api/PlayersBanned/v1/all — current global +/// ban list. The watch's mod panel queries this on open. +/// +[ApiController] +public class PlayersBannedController(DorkNetDbContext db) : ControllerBase +{ + [HttpGet("api/PlayersBanned/v1/all")] + public async Task All() + { + var now = DateTime.UtcNow; + var rows = await db.Players + .Where(p => p.BannedUntil != null && p.BannedUntil > now) + .Select(p => new + { + PlayerId = (int)p.Id, + p.Username, + BannedUntil = p.BannedUntil, + }) + .ToListAsync(); + return Ok(rows); + } +} diff --git a/DorkNet.Server/Controllers/API/Playlists/PlaylistsController.cs b/DorkNet.Server/Controllers/API/Playlists/PlaylistsController.cs new file mode 100644 index 0000000..9822030 --- /dev/null +++ b/DorkNet.Server/Controllers/API/Playlists/PlaylistsController.cs @@ -0,0 +1,304 @@ +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Mvc; +using Microsoft.EntityFrameworkCore; +using DorkNet.Server.Auth; +using DorkNet.Server.Controllers.API.Rooms.V2; +using DorkNet.Server.Data; +using DorkNet.Server.Data.Entities; +using DorkNet.Server.Services; + +namespace DorkNet.Server.Controllers.API.Playlists; + +/// +/// 2020.12 playlist CRUD surface — every playlists/* route the +/// watch hits beyond the basic listing/detail reads in +/// . URL templates are bare (no +/// /roomserver/ prefix) because the 2020.12 client emits them +/// that way on the Rooms host; the same routes are NOT mirrored under +/// /roomserver/ here to avoid duplicating definitions that already +/// exist on RoomsController. +/// +/// Wire shape conventions: +/// • Single mutation responses use BMFAGMFKODA — the union +/// KMKPEOGJDFK entry + Rooms + Tags arrays +/// (same shape that PlaylistDetails in +/// returns). See . +/// • List responses (cheeredby/me, createdby/me, etc.) use bare +/// List<KMKPEOGJDFK> — the union entry alone. +/// • Bookmark / cheer toggles return {success: true}; the +/// watch's reads come back through interactionby/me. +/// +[ApiController] +public class PlaylistsController( + PlaylistService playlists, + DorkNetDbContext db) : ControllerBase +{ + private long Me => this.RequireCurrentPlayerId(); + + // ── Create / delete ────────────────────────────────────────────── + + public sealed class CreatePlaylistRequest + { + public string? Name { get; set; } + public string? Description { get; set; } + public string? ImageName { get; set; } + public string? Tags { get; set; } + } + + [HttpPost("/playlists")] + [Authorize] + public async Task Create([FromBody] CreatePlaylistRequest req) + { + if (string.IsNullOrWhiteSpace(req.Name)) return BadRequest(new { error = "missing_name" }); + var created = await playlists.CreateAsync(Me, req.Name, req.Description, req.ImageName, req.Tags); + return await BuildDetailsResponseAsync(created.Id); + } + + [HttpDelete("/playlists/{playlistId:long}")] + [Authorize] + public async Task Delete(long playlistId) + { + try + { + var ok = await playlists.DeleteAsync(playlistId, Me); + return ok ? Ok() : NotFound(); + } + catch (UnauthorizedAccessException) { return Forbid(); } + } + + // ── Per-field mutations (each returns BMFAGMFKODA) ─────────────── + + public sealed class StringFieldRequest { public string? Value { get; set; } } + public sealed class IntFieldRequest { public int? Value { get; set; } } + public sealed class BoolFieldRequest { public bool? Value { get; set; } } + public sealed class TagsRequest { public string? Tags { get; set; } public List? TagsList { get; set; } } + + [HttpPost("/playlists/{playlistId:long}/name")] + [HttpPut("/playlists/{playlistId:long}/name")] + [Authorize] + public Task PlaylistName(long playlistId, [FromBody] StringFieldRequest req, + [FromForm(Name = "Name")] string? formValue) => + ApplyMutation(playlistId, p => p.Name = (req?.Value ?? formValue ?? p.Name).Trim()); + + [HttpPost("/playlists/{playlistId:long}/description")] + [HttpPut("/playlists/{playlistId:long}/description")] + [Authorize] + public Task PlaylistDescription(long playlistId, [FromBody] StringFieldRequest req, + [FromForm(Name = "Description")] string? formValue) => + ApplyMutation(playlistId, p => p.Description = req?.Value ?? formValue ?? p.Description); + + [HttpPost("/playlists/{playlistId:long}/image")] + [HttpPut("/playlists/{playlistId:long}/image")] + [Authorize] + public Task PlaylistImage(long playlistId, [FromBody] StringFieldRequest req, + [FromForm(Name = "ImageName")] string? formValue) => + ApplyMutation(playlistId, p => p.ImageName = req?.Value ?? formValue ?? p.ImageName); + + [HttpPost("/playlists/{playlistId:long}/tags")] + [HttpPut("/playlists/{playlistId:long}/tags")] + [Authorize] + public Task PlaylistTags(long playlistId, [FromBody] TagsRequest? req, + [FromForm(Name = "Tags")] string? formValue) => + ApplyMutation(playlistId, p => + { + var csv = req?.Tags + ?? (req?.TagsList is { Count: > 0 } list ? string.Join(',', list) : null) + ?? formValue + ?? p.TagsCsv; + p.TagsCsv = csv; + }); + + /// Accessibility / visibility / level-voting / restrictions / + /// warning are persisted on the playlist row even though the 2020 + /// PlaylistEntity doesn't have dedicated columns for each — the wire + /// shape always reports the union-entry defaults, so for now the + /// mutation is a no-op acknowledgement that returns the unchanged + /// playlist. Real per-field columns can be added when we have a + /// playlist-settings UI to drive them. + [HttpPost("/playlists/{playlistId:long}/accessibility")] + [HttpPost("/playlists/{playlistId:long}/visibility")] + [HttpPost("/playlists/{playlistId:long}/levelvoting")] + [HttpPost("/playlists/{playlistId:long}/restrictions")] + [HttpPost("/playlists/{playlistId:long}/warning")] + [Authorize] + public Task PlaylistAck(long playlistId) => + ApplyMutation(playlistId, _ => { /* no-op: field not persisted yet */ }); + + private async Task ApplyMutation(long playlistId, Action mutator) + { + try + { + var updated = await playlists.ModifyAsync(playlistId, Me, mutator); + if (updated is null) return NotFound(); + return await BuildDetailsResponseAsync(playlistId); + } + catch (UnauthorizedAccessException) { return Forbid(); } + } + + // ── Member-room mutations ──────────────────────────────────────── + + [HttpPut("/playlists/{playlistId:long}/rooms/{roomId:long}")] + [HttpPost("/playlists/{playlistId:long}/rooms/{roomId:long}")] + [Authorize] + public async Task AddRoom(long playlistId, long roomId) + { + try + { + if (!await playlists.AddRoomAsync(playlistId, roomId, Me)) return NotFound(); + return await BuildDetailsResponseAsync(playlistId); + } + catch (UnauthorizedAccessException) { return Forbid(); } + } + + [HttpDelete("/playlists/{playlistId:long}/rooms/{roomId:long}")] + [Authorize] + public async Task RemoveRoom(long playlistId, long roomId) + { + try + { + if (!await playlists.RemoveRoomAsync(playlistId, roomId, Me)) return NotFound(); + return await BuildDetailsResponseAsync(playlistId); + } + catch (UnauthorizedAccessException) { return Forbid(); } + } + + // ── Cheer / favorite toggles ───────────────────────────────────── + + [HttpPost("/playlists/{playlistId:long}/interactionby/me/cheer")] + [HttpPut("/playlists/{playlistId:long}/interactionby/me/cheer")] + [Authorize] + public async Task Cheer(long playlistId) + { + await playlists.SetInteractionAsync(playlistId, Me, cheered: true); + return Ok(new { success = true }); + } + + [HttpDelete("/playlists/{playlistId:long}/interactionby/me/cheer")] + [Authorize] + public async Task Uncheer(long playlistId) + { + await playlists.SetInteractionAsync(playlistId, Me, cheered: false); + return Ok(new { success = true }); + } + + [HttpPost("/playlists/{playlistId:long}/interactionby/me/favorite")] + [HttpPut("/playlists/{playlistId:long}/interactionby/me/favorite")] + [Authorize] + public async Task Favorite(long playlistId) + { + await playlists.SetInteractionAsync(playlistId, Me, favorited: true); + return Ok(new { success = true }); + } + + [HttpDelete("/playlists/{playlistId:long}/interactionby/me/favorite")] + [Authorize] + public async Task Unfavorite(long playlistId) + { + await playlists.SetInteractionAsync(playlistId, Me, favorited: false); + return Ok(new { success = true }); + } + + // ── My-playlists tabs (bare-list KMKPEOGJDFK) ─────────────────── + + [HttpGet("/playlists/createdby/me")] + [Authorize] + public async Task CreatedByMe() + { + var rows = await playlists.CreatedByAsync(Me); + return Ok(rows.Select(RoomsController.BuildPlaylistUnionEntry).ToList()); + } + + [HttpGet("/playlists/cheeredby/me")] + [Authorize] + public async Task CheeredByMe() + { + var rows = await playlists.CheeredByAsync(Me); + return Ok(rows.Select(RoomsController.BuildPlaylistUnionEntry).ToList()); + } + + [HttpGet("/playlists/favoritedby/me")] + [Authorize] + public async Task FavoritedByMe() + { + var rows = await playlists.FavoritedByAsync(Me); + return Ok(rows.Select(RoomsController.BuildPlaylistUnionEntry).ToList()); + } + + /// GET /playlists/visitedby/me — we don't track + /// per-player playlist visits yet (visits are tracked at the Room + /// level via RoomVisitEntity). Return empty so the watch's + /// playlists tab renders cleanly; populated once we add a + /// PlaylistVisitEntity or derive from member-room visits. + [HttpGet("/playlists/visitedby/me")] + [Authorize] + public IActionResult VisitedByMe() => Ok(Array.Empty()); + + // ── Bulk lookup ────────────────────────────────────────────────── + + /// GET /playlists/bulk?id=A&id=B — bulk-by-id + /// lookup for the watch's playlist cache warmup. Bare list of + /// KMKPEOGJDFK entries, one per resolved id (missing ids + /// silently dropped). + [HttpGet("/playlists/bulk")] + public async Task Bulk() + { + var ids = Request.Query["id"].Concat(Request.Query["ids"]) + .SelectMany(v => (v ?? string.Empty).Split(',', + StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries)) + .Select(s => long.TryParse(s, out var n) ? n : 0) + .Where(n => n != 0) + .ToList(); + if (ids.Count == 0) return Ok(Array.Empty()); + var rows = await playlists.BulkAsync(ids); + return Ok(rows.Select(RoomsController.BuildPlaylistUnionEntry).ToList()); + } + + // ── Detail-response builder (BMFAGMFKODA shape) ───────────────── + + /// + /// BMFAGMFKODA wire shape: KMKPEOGJDFK union entry + Rooms list + /// (List<KLCOGEIGEBJ>) + Tags list (List<DPHPFLGAICI>). + /// Mirrors the inline logic in + /// so the BARE-PATH mutation routes here return the same shape the + /// watch's PlaylistDetails.Deserialize reads. + /// + private async Task BuildDetailsResponseAsync(long playlistId) + { + var p = await playlists.GetByIdAsync(playlistId); + if (p is null) return NotFound(); + + var roomIds = await playlists.RoomIdsAsync(playlistId); + List memberRooms = new(); + Dictionary> scenesByRoom = new(); + if (roomIds.Count > 0) + { + var roomRows = await db.Rooms.Where(r => roomIds.Contains(r.Id)).ToListAsync(); + var byId = roomRows.ToDictionary(r => r.Id); + memberRooms = roomIds + .Select(rid => byId.TryGetValue(rid, out var r) ? r : null) + .Where(r => r is not null).Select(r => r!).ToList(); + + var sceneRows = await db.RoomScenes + .Where(s => roomIds.Contains(s.RoomId)) + .OrderBy(s => s.OrderIndex).ToListAsync(); + scenesByRoom = sceneRows.GroupBy(s => s.RoomId) + .ToDictionary(g => g.Key, g => (IReadOnlyList)g.ToList()); + } + + var roomsWire = memberRooms + .Select(r => RoomsController.BuildRoomServerDetails(r, scenesByRoom.GetValueOrDefault(r.Id))) + .ToList(); + + var tagsWire = string.IsNullOrWhiteSpace(p.TagsCsv) + ? new List() + : p.TagsCsv + .Split(',', StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries) + .Select(t => (object)new { Type = 0, Tag = t }) + .ToList(); + + var result = RoomsController.BuildPlaylistUnionEntry(p); + result["Rooms"] = roomsWire; + result["Tags"] = tagsWire; + return Ok(result); + } +} diff --git a/DorkNet.Server/Controllers/API/Progression/ProgressionController.cs b/DorkNet.Server/Controllers/API/Progression/ProgressionController.cs new file mode 100644 index 0000000..b6c52c2 --- /dev/null +++ b/DorkNet.Server/Controllers/API/Progression/ProgressionController.cs @@ -0,0 +1,592 @@ +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Mvc; +using Microsoft.EntityFrameworkCore; +using DorkNet.Server.Auth; +using DorkNet.Server.Data; +using DorkNet.Server.Data.Entities; +using DorkNet.Server.Services; + +namespace DorkNet.Server.Controllers.API.ProgressionApi; + +/// +/// api.rec.net/api/{challenge,checklist,objectives}/* — daily +/// challenges, onboarding checklist, and the full objectives surface +/// (myprogress, cleargroup, updateobjective, completegroup). All +/// concepts share the same persistence model: +/// rows keyed by a prefix: +/// challenge:{id}, checklist:{id}, group:{id}, +/// obj:{group}:{index}. +/// +[ApiController] +public class ProgressionController( + DorkNetDbContext db, + LevelService level, + ServerSettingsService serverSettings) : ControllerBase +{ + private long Me => this.RequireCurrentPlayerId(); + private long? MeOrNull => this.CurrentPlayerId(); + private const int RecCenterTokens = 2; + + // ── Challenges ─────────────────────────────────────────────────────── + + /// GET api/challenge/v{1,2}/getCurrent — returns + /// the active daily-challenge map (NOT a flat challenge object). + /// Verified against + /// Cpp2IL_ISIL/.../RecNet/Challenges.txt:105-184 + /// (GetCurrentChallenges hits this URL and parses as + /// ChallengeMap). Wire shape per + /// RecNet/ChallengeMap.txt:540-616: + /// + /// ChallengeMapId (int, required), CompletedRequired + /// (bool, required), StartAt/EndAt/ServerTime + /// (DateTime, required), Challenges (List<RecNetChallenge>, + /// required), Gift (ChallengeGift, required), + /// ChallengeThemeString (string, optional). + /// + /// Each RecNetChallenge requires only ChallengeId; + /// Name/Config/Description/Tooltip/Complete + /// are optional. Each ChallengeGift requires GiftDropId, + /// Xp, Level, GiftContext, GiftRarity. + /// + /// Previous flat-challenge response crashed + /// ChallengeMap.Deserialize at Util.GetKey("ChallengeMapId"). + [HttpGet("api/challenge/v1/getCurrent")] + [HttpGet("api/challenge/v2/getCurrent")] + public async Task CurrentChallenge() + { + var pid = MeOrNull; + var now = DateTime.UtcNow; + var start = now.Date.AddDays(-(int)now.DayOfWeek); + var mapId = CurrentChallengeMapId(); + + var weekly = await serverSettings.GetWeeklyChallengesAsync(); + var challengeDefs = weekly.Challenges; + var keys = challengeDefs.Select(c => ChallengeProgressKey(mapId, c.Index)).ToArray(); + var rows = pid is long me + ? await db.ObjectiveProgress + .Where(o => o.PlayerId == me && keys.Contains(o.Key)) + .ToDictionaryAsync(o => o.Key, o => o.IsCompleted) + : new Dictionary(); + + bool Done(int idx) => rows.TryGetValue(keys[idx], out var v) && v; + + return Ok(new + { + ChallengeMapId = mapId, + CompletedRequired = weekly.CompletedRequired, + StartAt = start, + EndAt = start.AddDays(7), + ServerTime = now, + // 2020.12 ChallengeMap.Deserialize (FNEEJMCEPOL.PPGFHEDFBEA) reads + // FallbackGiftName as a required key. Missing → KeyNotFoundException + // → "Received malformed RecNet response" → blocks the weekly-challenge + // promise chain at boot. + FallbackGiftName = string.Empty, + ChallengeThemeString = "Weekly", + Challenges = challengeDefs.Select(c => new + { + ChallengeId = ChallengeId(mapId, c.Index), + c.Name, + c.Config, + c.Description, + c.Tooltip, + Complete = Done(c.Index), + }), + Gift = new + { + GiftDropId = weekly.Reward.GiftDropId != 0 ? weekly.Reward.GiftDropId : mapId, + weekly.Reward.AvatarItemDesc, + weekly.Reward.ConsumableItemDesc, + weekly.Reward.EquipmentPrefabName, + weekly.Reward.EquipmentModificationGuid, + weekly.Reward.Xp, + weekly.Reward.Level, + weekly.Reward.GiftContext, + weekly.Reward.GiftRarity, + }, + }); + } + + public sealed class UpdateChallengeRequest + { + public int Id { get; set; } + public int ChallengeMapId { get; set; } + public int ChallengeId { get; set; } + public int Progress { get; set; } + public string? Config { get; set; } + public bool? Complete { get; set; } + } + + [HttpPost("api/challenge/v1/updateProgress")] + [HttpPost("api/challenge/v2/updateProgress")] + [Authorize] + public async Task UpdateChallengeProgress([FromForm] UpdateChallengeRequest formReq) + { + var pid = Me; + var req = await ReadChallengeRequestAsync(formReq); + var mapId = req.ChallengeMapId != 0 ? req.ChallengeMapId : CurrentChallengeMapId(); + var challengeId = req.ChallengeId != 0 ? req.ChallengeId : req.Id; + var weekly = await serverSettings.GetWeeklyChallengesAsync(); + var challengeCount = Math.Max(1, weekly.Challenges.Count); + var index = ChallengeIndex(mapId, challengeId, challengeCount); + + var key = ChallengeProgressKey(mapId, index); + var row = await db.ObjectiveProgress.FirstOrDefaultAsync(o => o.PlayerId == pid && o.Key == key); + if (row is null) + { + row = new ObjectiveProgressEntity { PlayerId = pid, Key = key }; + db.ObjectiveProgress.Add(row); + } + var shouldComplete = req.Complete ?? true; + if (shouldComplete && !row.IsCompleted) + { + row.IsCompleted = true; + row.ClearedAt = DateTime.UtcNow; + } + await db.SaveChangesAsync(); + + var weeklyRewarded = false; + var allKeys = weekly.Challenges + .Select(c => c.Index) + .Select(i => ChallengeProgressKey(mapId, i)) + .ToArray(); + var completeCount = await db.ObjectiveProgress + .CountAsync(o => o.PlayerId == pid && allKeys.Contains(o.Key) && o.IsCompleted); + if (completeCount >= allKeys.Length) + { + weeklyRewarded = await GrantRewardOnceAsync( + pid, + $"weekly-challenge:{mapId}", + xp: weekly.Reward.Xp, + tokens: weekly.Reward.Tokens, + reason: $"weekly_challenge:{mapId}"); + } + + return Ok(new + { + ChallengeMapId = mapId, + ChallengeId = challengeId, + Config = req.Config ?? string.Empty, + Complete = row.IsCompleted, + Progress = Math.Max(req.Progress, row.IsCompleted ? 10 : 0), + IsCompleted = row.IsCompleted, + ClearedAt = row.ClearedAt, + Rewarded = weeklyRewarded, + }); + } + + // ── Checklist ──────────────────────────────────────────────────────── + + /// GET api/checklist/v{1,2}/current — returns + /// a flat List<ChecklistObjective>, NOT a wrapper + /// object. Wire shape per + /// Cpp2IL_CS/.../RecNet/Checklist.cs: + /// Order(int), Objective(ObjectiveType int), Count(int), + /// CreditAmount(int). All four keys are required + /// (Util.GetKey, not OrDefault). + /// + /// Completion state is tracked separately by the client via + /// rows it cross-references — we + /// don't put a per-item "done" flag on the wire; the client + /// matches by Order. Returning an empty list ([]) is also valid + /// per the deserialiser (List wrapper accepts empty). + [HttpGet("api/checklist/v1/current")] + [HttpGet("api/checklist/v2/current")] + public IActionResult CurrentChecklist() + { + // Default NUX checklist for a brand-new account. Each row + // maps an in-game step to an ObjectiveType enum value + // (decompiled from ProgressionManager.cs). + return Ok(new[] + { + new { Order = 0, Objective = 38 /* SaveOutfitSlot */, Count = 1, CreditAmount = 25 }, + new { Order = 1, Objective = 32 /* VisitACustomRoom */, Count = 1, CreditAmount = 25 }, + new { Order = 2, Objective = 2 /* AddAFriend */, Count = 1, CreditAmount = 25 }, + new { Order = 3, Objective = 30 /* GoToRecCenter */, Count = 1, CreditAmount = 25 }, + new { Order = 4, Objective = 6 /* CheerAPlayer */, Count = 1, CreditAmount = 25 }, + }); + } + + public sealed class CompleteChecklistRequest + { + public int Id { get; set; } + public int ItemIndex { get; set; } + } + + [HttpPost("api/checklist/v1/complete")] + [HttpPost("api/checklist/v2/complete")] + [Authorize] + public async Task CompleteChecklist([FromBody] CompleteChecklistRequest req) + { + var pid = Me; + var id = req.ItemIndex != 0 ? req.ItemIndex : req.Id; + var key = $"checklist:{id}"; + var row = await db.ObjectiveProgress.FirstOrDefaultAsync(o => o.PlayerId == pid && o.Key == key); + if (row is null) + { + row = new ObjectiveProgressEntity { PlayerId = pid, Key = key }; + db.ObjectiveProgress.Add(row); + } + row.IsCompleted = true; + row.ClearedAt = DateTime.UtcNow; + await db.SaveChangesAsync(); + var rewarded = await GrantRewardOnceAsync( + pid, + $"checklist:{id}", + xp: 25, + tokens: 25, + reason: $"checklist:{id}"); + var balance = await level.GetBalanceAsync(pid, RecCenterTokens); + return Ok(BalanceUpdateResponse(balance, RecCenterTokens, rewarded ? 25 : 0, 303)); + } + + // ── Objectives bulk-complete (real persistence) ────────────────────── + + /// POST api/objectives/v1/completegroup — replaces + /// the previous shape-stub in MissingEndpointsController. Now + /// upserts a real row keyed + /// by group:{id}. + public sealed class UpdateObjectiveRequest + { + public int Group { get; set; } + public int Index { get; set; } + public float Progress { get; set; } + public bool IsCompleted { get; set; } + } + + /// POST api/objectives/v1/updateobjective — per- + /// objective progress update. Was previously caught by the + /// namespace wildcard returning []; now persists to + /// keyed + /// obj:{group}:{index} so progress survives sessions. + /// Wire shape from RecNet.Objectives.UpdateObjectiveProgress. + [HttpPost("api/objectives/v1/updateobjective")] + [Authorize] + public async Task UpdateObjective([FromBody] UpdateObjectiveRequest req) + { + var pid = Me; + var key = $"obj:{req.Group}:{req.Index}"; + var row = await db.ObjectiveProgress.FirstOrDefaultAsync(o => o.PlayerId == pid && o.Key == key); + if (row is null) + { + row = new ObjectiveProgressEntity { PlayerId = pid, Key = key }; + db.ObjectiveProgress.Add(row); + } + if (req.IsCompleted || req.Progress >= 1f) + { + row.IsCompleted = true; + row.ClearedAt ??= DateTime.UtcNow; + } + await db.SaveChangesAsync(); + var rewardKey = $"obj:{req.Group}:{req.Index}"; + var isRewarded = await IsRewardedAsync(pid, rewardKey); + return Ok(new + { + // 2020.12 NMEKJOABIFC.PPGFHEDFBEA: HasClaimedReward (not IsRewarded). + req.Index, + req.Group, + Progress = row.IsCompleted ? 1f : req.Progress, + VisualProgress = row.IsCompleted ? 1f : req.Progress, + IsCompleted = row.IsCompleted, + HasClaimedReward = isRewarded, + }); + } + + [HttpPost("api/objectives/v1/completegroup")] + [Authorize] + public async Task CompleteGroup([FromBody] CompleteGroupBody body) + { + var pid = Me; + var key = $"group:{body.Group}"; + var row = await db.ObjectiveProgress.FirstOrDefaultAsync(o => o.PlayerId == pid && o.Key == key); + if (row is null) + { + row = new ObjectiveProgressEntity { PlayerId = pid, Key = key }; + db.ObjectiveProgress.Add(row); + } + row.IsCompleted = true; + row.ClearedAt = DateTime.UtcNow; + await db.SaveChangesAsync(); + var rewarded = await GrantObjectiveGroupRewardAsync(pid, body.Group); + return Ok(new + { + Group = body.Group, + IsCompleted = true, + ClearedAt = row.ClearedAt!.Value.ToString("o"), + RequiresCompleteOnServer = false, + IsRewarded = true, + Rewarded = rewarded, + }); + } + + public sealed class CompleteGroupBody { public int Group { get; set; } } + + /// GET api/objectives/v1/myprogress + + /// api/players/v2/objectives — the watch's daily/weekly + /// objectives checklist. Returns + /// {Objectives: [...], ObjectiveGroups: [...]} per + /// Objectives.MyProgress.Deserialize (RVA 0x14510A0). + /// Empty lists are acceptable; both keys must be present. + [HttpGet("api/objectives/v1/myprogress")] + [HttpGet("api/players/v2/objectives")] + public async Task MyObjectiveProgress() + { + var pid = this.CurrentPlayerId(); + if (pid is not long me) + return Ok(new { Objectives = Array.Empty(), ObjectiveGroups = Array.Empty() }); + + var rows = await db.ObjectiveProgress + .Where(o => o.PlayerId == me) + .ToListAsync(); + + var rewardKeys = rows + .Where(o => o.Key.StartsWith("reward:")) + .Select(o => o.Key["reward:".Length..]) + .ToHashSet(StringComparer.Ordinal); + + var objectives = rows + .Where(o => o.Key.StartsWith("obj:")) + .Select(o => + { + var (grp, idx) = ParseObjectiveKey(o.Key); + // 2020.12 NMEKJOABIFC.PPGFHEDFBEA reads Index, Group, Progress, + // VisualProgress, IsCompleted, HasClaimedReward (NOT IsRewarded). + // Missing HasClaimedReward → KeyNotFoundException → "Couldn't + // download progress data" → BootSequence kick to LogoutScene. + return new + { + Index = idx, + Group = grp, + Progress = o.IsCompleted ? 1f : 0f, + VisualProgress = o.IsCompleted ? 1f : 0f, + IsCompleted = o.IsCompleted, + HasClaimedReward = rewardKeys.Contains(o.Key), + }; + }); + + var groups = rows + .Where(o => o.Key.StartsWith("group:")) + .Select(o => new + { + Group = int.TryParse(o.Key["group:".Length..], out var g) ? g : 0, + IsCompleted = o.IsCompleted, + ClearedAt = o.ClearedAt ?? new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc), + RequiresCompleteOnServer = false, + IsRewarded = rewardKeys.Contains(o.Key), + }); + + return Ok(new { Objectives = objectives, ObjectiveGroups = groups }); + } + + /// POST api/objectives/v1/cleargroup — flips a + /// group's IsCompleted flag, used by the checklist's + /// "claim group reward" button. Response is a single + /// ObjectiveGroupProgress with Group/IsCompleted/ClearedAt + /// required keys; RequiresCompleteOnServer is optional. + [HttpPost("api/objectives/v1/cleargroup")] + public async Task ClearGroup([FromForm(Name = "group")] int? group) + { + var pid = this.CurrentPlayerId(); + var key = $"group:{group ?? 0}"; + var now = DateTime.UtcNow; + + if (pid is long me) + { + var row = await db.ObjectiveProgress + .FirstOrDefaultAsync(o => o.PlayerId == me && o.Key == key); + if (row is null) + { + row = new ObjectiveProgressEntity { PlayerId = me, Key = key }; + db.ObjectiveProgress.Add(row); + } + row.IsCompleted = true; + row.ClearedAt = now; + await db.SaveChangesAsync(); + await GrantObjectiveGroupRewardAsync(me, group ?? 0); + } + + return Ok(new + { + Group = group ?? 0, + IsCompleted = true, + ClearedAt = now.ToString("o"), + RequiresCompleteOnServer = false, + IsRewarded = true, + }); + } + + /// POST api/players/v1/objectives + + /// api/players/v2/objectives — batched ProgressionManager + /// completion records (the watch's + /// ACJLCBNBJDK.EIFPPIPOIHB(List<PIHHFPDKFAG>); body is a + /// JSON array of {objectiveType: int, additionalXp: int, + /// inParty: bool}). Grants XP + RecCenter tokens via + /// and dedupes per UTC day + /// through , so a re-submission of + /// the same objective on the same day is a no-op. Returns the + /// granted XP / currency + post-grant balance so the watch UI can + /// refresh without a follow-up GET. + [HttpPost("api/players/v1/objectives")] + [HttpPost("api/players/v2/objectives")] + [Authorize] + public async Task CompletePlayerObjectives([FromBody] List? records) + { + var pid = Me; + var grantedXp = 0; + var grantedTokens = 0; + foreach (var record in records ?? new()) + { + var (xp, tokens, reason) = RewardForObjectiveType(record.ObjectiveType, record.AdditionalXp); + if (xp <= 0 && tokens <= 0) continue; + if (await GrantRewardOnceAsync(pid, $"{reason}:{DateTime.UtcNow:yyyyMMdd}", xp, tokens, reason)) + { + grantedXp += xp; + grantedTokens += tokens; + } + } + + var balance = await level.GetBalanceAsync(pid, RecCenterTokens); + return Ok(new + { + GrantedXp = grantedXp, + GrantedCurrency = grantedTokens, + CurrencyType = RecCenterTokens, + Balance = balance, + BalanceType = 0, + }); + } + + public sealed class PlayerObjectiveCompletionRecord + { + public int ObjectiveType { get; set; } + public bool InParty { get; set; } + public int AdditionalXp { get; set; } + } + + private static (int Group, int Index) ParseObjectiveKey(string key) + { + const string prefix = "obj:"; + if (!key.StartsWith(prefix)) return (-1, -1); + var rest = key[prefix.Length..]; + var colon = rest.IndexOf(':'); + if (colon <= 0) return (-1, -1); + var grp = int.TryParse(rest[..colon], out var g) ? g : -1; + var idx = int.TryParse(rest[(colon + 1)..], out var i) ? i : -1; + return (grp, idx); + } + + private static int CurrentChallengeMapId() => + (int)((DateTime.UtcNow - new DateTime(2020, 1, 1)).Days / 7 % 52 + 1); + + private static string ChallengeProgressKey(int mapId, int index) => $"challenge:{mapId}:{index}"; + + private static int ChallengeId(int mapId, int index) => mapId * 10 + index; + + private static int ChallengeIndex(int mapId, int challengeId, int challengeCount) + { + if (challengeId >= mapId * 10 && challengeId < mapId * 10 + 10) + return Math.Clamp(challengeId - mapId * 10, 0, challengeCount - 1); + + return Math.Clamp(Math.Abs(challengeId % 10), 0, challengeCount - 1); + } + + private async Task ReadChallengeRequestAsync(UpdateChallengeRequest formReq) + { + if (Request.HasFormContentType || formReq.Id != 0 || formReq.ChallengeId != 0 || formReq.ChallengeMapId != 0) + return formReq; + + try + { + var body = await System.Text.Json.JsonSerializer.DeserializeAsync( + Request.Body, + new System.Text.Json.JsonSerializerOptions { PropertyNameCaseInsensitive = true }); + return body ?? formReq; + } + catch + { + return formReq; + } + } + + private async Task GrantObjectiveGroupRewardAsync(long playerId, int group) + { + var (xp, tokens, name) = group switch + { + 0 => (150, 150, "daily-objectives"), + 1 => (100, 100, "new-player-punch-card"), + 2 => (75, 75, "orientation-checklist"), + _ => (50, 50, $"objective-group-{group}"), + }; + return await GrantRewardOnceAsync(playerId, $"group:{group}", xp, tokens, name); + } + + private static (int Xp, int Tokens, string Reason) RewardForObjectiveType(int objectiveType, int additionalXp) + { + var xp = Math.Max(additionalXp, 0); + return objectiveType switch + { + 10 or 11 or 12 => (Math.Max(xp, 25), 25, $"daily-objective:{objectiveType}"), + 13 => (Math.Max(xp, 150), 150, "all-daily-objectives"), + 14 => (Math.Max(xp, 50), 50, "complete-any-daily"), + 15 => (Math.Max(xp, 75), 75, "complete-any-weekly"), + 25 => (Math.Max(xp, 25), 25, "nux-punchcard-objective"), + 26 => (Math.Max(xp, 100), 100, "nux-all-punchcard-objectives"), + _ => (xp, 0, $"objective:{objectiveType}"), + }; + } + + private async Task IsRewardedAsync(long playerId, string rewardKey) => + await db.ObjectiveProgress.AnyAsync(o => + o.PlayerId == playerId && o.Key == $"reward:{rewardKey}" && o.IsCompleted); + + private async Task GrantRewardOnceAsync(long playerId, string rewardKey, int xp, int tokens, string reason) + { + var sentinelKey = $"reward:{rewardKey}"; + var exists = await db.ObjectiveProgress.AnyAsync(o => + o.PlayerId == playerId && o.Key == sentinelKey && o.IsCompleted); + if (exists) return false; + + db.ObjectiveProgress.Add(new ObjectiveProgressEntity + { + PlayerId = playerId, + Key = sentinelKey, + IsCompleted = true, + ClearedAt = DateTime.UtcNow, + }); + await db.SaveChangesAsync(); + + if (xp > 0) await level.AwardXpAsync(playerId, xp, reason); + if (tokens > 0) await level.GrantCurrencyAsync(playerId, RecCenterTokens, tokens, reason); + return true; + } + + private static object BalanceUpdateResponse(long balance, int currencyType, int delta, int balanceAddType) => new + { + Balance = balance, + CurrencyType = currencyType, + BalanceType = 0, + BalanceUpdates = new[] + { + new + { + UpdateResponse = 0, + Data = delta <= 0 + ? Array.Empty() + : new object[] + { + new + { + BalanceAddType = balanceAddType, + BaseAward = delta, + BonusAward = 0, + RateLimit = 0, + CurrentCount = 1, + Total = delta, + BalanceType = 0, + BalanceInGiftBox = false, + }, + }, + }, + }, + }; +} diff --git a/DorkNet.Server/Controllers/API/QuickPlay/QuickPlayController.cs b/DorkNet.Server/Controllers/API/QuickPlay/QuickPlayController.cs new file mode 100644 index 0000000..c8fff6d --- /dev/null +++ b/DorkNet.Server/Controllers/API/QuickPlay/QuickPlayController.cs @@ -0,0 +1,51 @@ +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Mvc; +using DorkNet.Server.Data; + +namespace DorkNet.Server.Controllers.API.QuickPlay; + +/// +/// api.rec.net/api/quickPlay/* — the watch's "Quick Play" tile picks +/// a popular open AG room when the player wants to jump in +/// somewhere social without browsing. +/// +/// • GET v{1,2}/getandclear — returns the next match the +/// user was queued for, then clears it. We just synthesise a +/// "join hot AG room" target on each call (no real queue). +/// • POST v1/set — stores a desired-match tag. We log + ack; +/// no persistence needed for a single-region private server. +/// +[ApiController] +public class QuickPlayController(DorkNetDbContext db) : ControllerBase +{ + /// GET v{1,2}/getandclear — the watch polls this + /// on every connect / navigation to check whether the user has a + /// pending quickplay queue. Returning a real {RoomId, RoomName} + /// makes the client treat it as "you queued, go there now" and + /// immediately teleports — that's WHY users were randomly + /// spawning in Paddleball etc. + /// + /// Correct behaviour: return empty {} unless we actually have a + /// queued match for the caller. The /set endpoint is what + /// queues; getandclear is the consume. + /// + /// We don't persist queue state on a single-region private + /// server (no real matchmaking pool), so always return empty. + [HttpGet("api/quickPlay/v1/getandclear")] + [HttpGet("api/quickPlay/v2/getandclear")] + public IActionResult GetAndClear() => Ok(new { }); + + public sealed class SetQuickPlayRequest + { + public string? Tag { get; set; } + } + + [HttpPost("api/quickPlay/v1/set")] + [Authorize] + public IActionResult Set([FromBody] SetQuickPlayRequest? req) + { + // Preference is per-session; we don't persist (no benefit in + // a small private deployment). Just ack. + return Ok(new { Tag = req?.Tag ?? string.Empty, Set = true }); + } +} diff --git a/DorkNet.Server/Controllers/API/Relationships/V2/RelationshipsController.cs b/DorkNet.Server/Controllers/API/Relationships/V2/RelationshipsController.cs new file mode 100644 index 0000000..a424e3c --- /dev/null +++ b/DorkNet.Server/Controllers/API/Relationships/V2/RelationshipsController.cs @@ -0,0 +1,367 @@ +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Mvc; +using Microsoft.EntityFrameworkCore; +using DorkNet.Server.Auth; +using DorkNet.Server.Data; +using DorkNet.Server.Data.Entities; +using DorkNet.Server.Services; + +// Storage-side enum aliased so the wire mapping stays explicit at +// every callsite. Wire enum values (RelationshipType / +// ReciprocalStatus) live in this file as constants — they're tied +// to the disassembly and not used elsewhere. +using EntityStatus = DorkNet.Server.Data.Entities.RelationshipStatus; + +namespace DorkNet.Server.Controllers.API.Relationships.V2; + +/// +/// api/relationships/v2 — friendship + reciprocal-state endpoints. +/// +/// All routes / wire shapes verified against the patched 2020 client: +/// Cpp2IL_ISIL/IsilDump/Assembly-CSharp/RecNet/Relationships.txt +/// Cpp2IL_ISIL/IsilDump/Assembly-CSharp/RecNet/Relationship.txt +/// +/// Wire response = a Relationship object (or List of them for /get). +/// Relationship.Deserialize strict-reads via Util.GetKey<T>: +/// "PlayerID" (int) — REQUIRED — the OTHER account's id +/// "RelationshipType" (int) — REQUIRED — 0..3 +/// "Muted" (int) — REQUIRED — ReciprocalStatus 0..3 +/// "Ignored" (int) — REQUIRED — ReciprocalStatus 0..3 +/// "Favorited" (int) — optional (Util.GetKeyOrDefault, defaults 0) +/// +/// RelationshipType (from caller's perspective): +/// 0 None, 1 FriendRequestSent, 2 FriendRequestReceived, 3 Friend +/// ReciprocalStatus (symmetric flag — who set it): +/// 0 None, 1 Local (just me), 2 Remote (just them), 3 Mutual +/// +/// URLs called from the watch (Relationships.txt, all under +/// "api/relationships/"): +/// POST v2/addfriend?id={N} AddFriend +/// POST v2/removefriend?id={N} RemoveFriend / cancel-pending +/// POST v2/sendfriendrequest?id={N} SendFriendRequest +/// POST v2/acceptfriendrequest?id={N} AcceptFriendRequest +/// POST v1/favorite?id={N} FavoritePlayer +/// POST v1/unfavorite?id={N} UnfavoritePlayer +/// POST v1/mute / unmute / ignore / unignore (PostPreferenceChange) +/// GET v2/get GetMyRelationships +/// All POST URLs return one Relationship; GET returns List<Relationship>. +/// +[ApiController] +[Authorize] +public class RelationshipsController(DorkNetDbContext db, NotificationService notifications) : ControllerBase +{ + // RelationshipType — int values match Relationship.RelationshipType + // in the patched dump.cs. + private const int RT_None = 0; + private const int RT_FriendRequestSent = 1; + private const int RT_FriendRequestReceived = 2; + private const int RT_Friend = 3; + + // ReciprocalStatus values — also match the dump.cs enum. + private const int RS_None = 0; + private const int RS_Local = 1; + private const int RS_Remote = 2; + private const int RS_Mutual = 3; + + private long CurrentPlayerId => this.RequireCurrentPlayerId(); + + // ── GET /api/relationships/v2/get ──────────────────────────────── + // Watch URL: "api/relationships/v2/get". Returns the caller's full + // relationship list as List. + [HttpGet("api/relationships/v2/get")] + public async Task GetMyRelationships() + { + var me = CurrentPlayerId; + var rels = await db.Relationships + .Where(r => r.RequesterId == me || r.TargetId == me) + .ToListAsync(); + var wire = rels.Select(r => BuildRelationship(r, me)).ToList(); + return Ok(wire); + } + + /// GET api/relationships/v2/personaldetails/{playerId} + /// — single relationship row from the caller's perspective. Used + /// by the watch's profile-card view to determine whether to show + /// the "Add Friend" or "Remove Friend" affordance. Returns the + /// same Relationship wire shape as v2/get's list elements. + [HttpGet("api/relationships/v2/personaldetails/{playerId:long}")] + public async Task PersonalDetails(long playerId) + { + var me = CurrentPlayerId; + if (playerId <= 0 || playerId == me) return Ok(EmptyRelationship(playerId)); + var row = await FindAsync(me, playerId); + return Ok(row is null ? EmptyRelationship(playerId) : BuildRelationship(row, me)); + } + + // ── /api/relationships/v2/sendfriendrequest?id={N} ─────────────── + // Watch URL from Relationships.txt: "{0}v2/sendfriendrequest?id={1}". + // The ISIL labels the call site Core.Post but the live trace shows + // the watch sending GET — accepting both is harmless and the + // safest cross-build choice. + [HttpGet("api/relationships/v2/sendfriendrequest")] + [HttpPost("api/relationships/v2/sendfriendrequest")] + public async Task SendFriendRequest([FromQuery] long id) + { + var me = CurrentPlayerId; + if (id <= 0 || id == me) + return Ok(EmptyRelationship(id)); + + var existing = await FindAsync(me, id); + if (existing is not null) + return Ok(BuildRelationship(existing, me)); + + var row = new RelationshipEntity + { + RequesterId = me, + TargetId = id, + Status = EntityStatus.PendingSent, + }; + db.Relationships.Add(row); + await db.SaveChangesAsync(); + + await notifications.FriendRequestReceived(id, me); + return Ok(BuildRelationship(row, me)); + } + + // ── /api/relationships/v2/acceptfriendrequest?id={N} ──────────── + [HttpGet("api/relationships/v2/acceptfriendrequest")] + [HttpPost("api/relationships/v2/acceptfriendrequest")] + public async Task AcceptFriendRequest([FromQuery] long id) + { + var me = CurrentPlayerId; + if (id <= 0) + return Ok(EmptyRelationship(id)); + + // Only an incoming pending row can be accepted. The other side + // must have requested us — RequesterId=them, TargetId=me. + var rel = await db.Relationships.FirstOrDefaultAsync(r => + r.RequesterId == id && r.TargetId == me && + r.Status == EntityStatus.PendingSent); + if (rel is null) + return Ok(EmptyRelationship(id)); + + rel.Status = EntityStatus.Friend; + rel.UpdatedAt = DateTime.UtcNow; + await db.SaveChangesAsync(); + + await notifications.FriendRequestAccepted(id, me); + return Ok(BuildRelationship(rel, me)); + } + + // ── /api/relationships/v2/removefriend?id={N} ──────────────────── + // Symmetrical: undoes a friendship OR cancels a pending request OR + // declines an incoming one. Block rows are left alone — the watch + // has a separate explicit unblock path. Live trace shows the watch + // sending GET (BestHTTP queueing oddity vs the ISIL Core.Post call); + // accept both verbs. + [HttpGet("api/relationships/v2/removefriend")] + [HttpPost("api/relationships/v2/removefriend")] + public async Task RemoveFriend([FromQuery] long id) + { + var me = CurrentPlayerId; + if (id <= 0) + return Ok(EmptyRelationship(id)); + + var rel = await FindAsync(me, id); + if (rel is null) + return Ok(EmptyRelationship(id)); + if (rel.Status == EntityStatus.Blocked) + return Ok(BuildRelationship(rel, me)); + + db.Relationships.Remove(rel); + await db.SaveChangesAsync(); + await notifications.FriendRemoved(id, me); + return Ok(EmptyRelationship(id)); + } + + // ── /api/relationships/v2/addfriend?id={N} ─────────────────────── + // Per the disassembly, AddFriend is its own endpoint distinct from + // SendFriendRequest. Live trace shows the watch sending GET; ISIL + // says Core.Post. Accept both verbs. + [HttpGet("api/relationships/v2/addfriend")] + [HttpPost("api/relationships/v2/addfriend")] + public async Task AddFriend([FromQuery] long id) + { + var me = CurrentPlayerId; + if (id <= 0 || id == me) + return Ok(EmptyRelationship(id)); + + var existing = await FindAsync(me, id); + if (existing is null) + { + existing = new RelationshipEntity + { + RequesterId = me, + TargetId = id, + Status = EntityStatus.Friend, + }; + db.Relationships.Add(existing); + } + else if (existing.Status != EntityStatus.Blocked) + { + existing.Status = EntityStatus.Friend; + existing.UpdatedAt = DateTime.UtcNow; + } + await db.SaveChangesAsync(); + return Ok(BuildRelationship(existing, me)); + } + + // ── v1 favorite / unfavorite ───────────────────────────────────── + // Watch URLs: "api/relationships/v1/favorite?id={N}" and + // "api/relationships/v1/unfavorite?id={N}". These return a + // boolean-ish RecNetResult per the FavoritePlayer/UnfavoritePlayer + // disassembly (Core.Post … ExpectHttpStatusSuccess), not a + // Relationship — they don't surface any state change in the + // 2020 storage model so we just acknowledge. + [HttpPost("api/relationships/v1/favorite")] + public Task Favorite([FromQuery] long id) => SetPreference(id, favorited: true); + + [HttpPost("api/relationships/v1/unfavorite")] + public Task Unfavorite([FromQuery] long id) => SetPreference(id, favorited: false); + + [HttpPost("api/relationships/v1/mute")] + public Task Mute([FromQuery] long id, [FromForm(Name = "PlayerId")] long? formId) + => SetPreference(id != 0 ? id : formId ?? 0, muted: true); + + [HttpPost("api/relationships/v1/unmute")] + public Task Unmute([FromQuery] long id, [FromForm(Name = "PlayerId")] long? formId) + => SetPreference(id != 0 ? id : formId ?? 0, muted: false); + + [HttpPost("api/relationships/v1/ignore")] + public Task Ignore([FromQuery] long id, [FromForm(Name = "PlayerId")] long? formId) + => SetPreference(id != 0 ? id : formId ?? 0, ignored: true); + + [HttpPost("api/relationships/v1/unignore")] + public Task Unignore([FromQuery] long id, [FromForm(Name = "PlayerId")] long? formId) + => SetPreference(id != 0 ? id : formId ?? 0, ignored: false); + + /// Update favorited/muted/ignored on the caller's row. + /// Creates a placeholder row when none exists (so an "ignore" + /// before any friend request is still recorded). + private async Task SetPreference(long otherId, + bool? favorited = null, bool? muted = null, bool? ignored = null) + { + var me = this.RequireCurrentPlayerId(); + if (otherId <= 0 || otherId == me) + return Ok(new Models.Auth.RecNetResult { Success = false, Error = "invalid_id" }); + + var row = await db.Relationships.FirstOrDefaultAsync(r => + r.RequesterId == me && r.TargetId == otherId); + if (row is null) + { + row = new RelationshipEntity + { + RequesterId = me, + TargetId = otherId, + Status = EntityStatus.Friend, // placeholder; favorite/mute/ignore don't require friend status + }; + db.Relationships.Add(row); + } + if (favorited is bool f) row.Favorited = f; + if (muted is bool m) row.Muted = m; + if (ignored is bool i) row.Ignored = i; + row.UpdatedAt = DateTime.UtcNow; + await db.SaveChangesAsync(); + return Ok(new Models.Auth.RecNetResult { Success = true, Error = string.Empty }); + } + + public sealed class BulkIgnoreRequest + { + public int Platform { get; set; } + public List? PlatformUserIds { get; set; } + } + + /// POST api/relationships/v1/bulkignoreplatformusers + /// — bulk-import Steam/Oculus blocks. Persists to + /// so when those users sign + /// up later we can auto-set their relationship to Ignored. + [HttpPost("api/relationships/v1/bulkignoreplatformusers")] + [Authorize] + public async Task BulkIgnorePlatformUsers([FromBody] BulkIgnoreRequest req) + { + var me = this.RequireCurrentPlayerId(); + if (req?.PlatformUserIds is null || req.PlatformUserIds.Count == 0) + return Ok(new { Imported = 0 }); + + var added = 0; + foreach (var puid in req.PlatformUserIds.Distinct()) + { + if (string.IsNullOrWhiteSpace(puid)) continue; + var exists = await db.PlatformIgnores.AnyAsync(i => + i.PlayerId == me && i.Platform == req.Platform && i.PlatformUserId == puid); + if (exists) continue; + db.PlatformIgnores.Add(new PlatformIgnoreEntity + { + PlayerId = me, + Platform = req.Platform, + PlatformUserId = puid, + }); + added++; + } + await db.SaveChangesAsync(); + return Ok(new { Imported = added }); + } + + // ── helpers ────────────────────────────────────────────────────── + + private Task FindAsync(long me, long other) => + db.Relationships.FirstOrDefaultAsync(r => + (r.RequesterId == me && r.TargetId == other) || + (r.RequesterId == other && r.TargetId == me)); + + /// Map a storage row to the watch's wire RelationshipType + /// from 's perspective. PendingSent flips + /// direction depending on which side originated. + private static int MapType(RelationshipEntity r, long me) => r.Status switch + { + EntityStatus.Friend => RT_Friend, + EntityStatus.Blocked => RT_None, // Blocked is not a friendship; the watch reads block state via a separate flag we don't track yet. + EntityStatus.PendingSent => r.RequesterId == me ? RT_FriendRequestSent : RT_FriendRequestReceived, + EntityStatus.PendingReceived => r.RequesterId == me ? RT_FriendRequestReceived : RT_FriendRequestSent, + _ => RT_None, + }; + + /// + /// Build the wire object the patched Relationship.Deserialize + /// reads. Keys are case-sensitive (Util.GetKey is a Dictionary + /// TryGetValue) — we ship exactly what's in the disassembly. + /// Implemented as a Dictionary because System.Text.Json's + /// case-insensitive collision check rejects anonymous types with + /// PascalCase property names like "PlayerID" + "Muted" if any + /// other property could collide on lowercase — using a Dictionary + /// avoids that whole class of trouble. + /// + private static Dictionary BuildRelationship(RelationshipEntity r, long me) + { + var other = r.RequesterId == me ? r.TargetId : r.RequesterId; + // ReciprocalStatus enum: 0=None, 1=ByMe, 2=ByThem, 3=Both. + // Our schema only tracks "by me" (the requester's perspective) + // so we can answer ByMe / None; ByThem / Both would require + // looking up the OTHER direction's row too. Cheap extra query + // omitted — the watch's UI only consumes the ByMe bit. + var mineDir = r.RequesterId == me; + var muted = mineDir && r.Muted ? 1 : 0; + var ignored = mineDir && r.Ignored ? 1 : 0; + var favored = mineDir && r.Favorited ? 1 : 0; + return new() + { + ["PlayerID"] = (int)other, + ["RelationshipType"] = MapType(r, me), + ["Muted"] = muted, + ["Ignored"] = ignored, + ["Favorited"] = favored, + }; + } + + /// "None" relationship for a target the caller has nothing + /// on file with — used as a no-op success response so the watch's + /// Relationship.Deserialize stays satisfied. + private static Dictionary EmptyRelationship(long other) => new() + { + ["PlayerID"] = (int)other, + ["RelationshipType"] = RT_None, + ["Muted"] = RS_None, + ["Ignored"] = RS_None, + ["Favorited"] = RS_None, + }; +} diff --git a/DorkNet.Server/Controllers/API/Role/RoleController.cs b/DorkNet.Server/Controllers/API/Role/RoleController.cs new file mode 100644 index 0000000..836651d --- /dev/null +++ b/DorkNet.Server/Controllers/API/Role/RoleController.cs @@ -0,0 +1,90 @@ +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Mvc; +using Microsoft.EntityFrameworkCore; +using DorkNet.Server.Data; + +namespace DorkNet.Server.Controllers.API.Role; + +/// +/// api.rec.net/api/role/* — role / privilege probes the watch consults +/// when deciding whether to surface dev-only UI (the in-watch debug +/// console button, hidden admin-tool tab, etc). +/// +/// URL verified against +/// Cpp2IL_ISIL/IsilDump/Assembly-CSharp/RecNet/Accounts.txt:5168 +/// ("role/developer/{0}") called from +/// RecNet.Accounts.GetAccountIsDeveloper. Response shape is a +/// bare JSON boolean — the watch wraps the call with +/// PromiseExtensions.ExpectPrimitiveResponse<bool>, so an +/// array or object response throws InvalidCastException and silently +/// disables the dev UI. +/// +/// To make a player a developer (and unlock the watch's +/// debug-console button), set Players.IsDeveloper = 1 in the +/// SQLite DB or via the admin UI's player editor. +/// +[ApiController] +public class RoleController(DorkNetDbContext db) : ControllerBase +{ + // Service-routed call from Accounts.GetAccountIsDeveloper. The + // 2020 dump's SendRequest carries `Service = 0` (Auth) — verified + // at Cpp2IL_ISIL/.../RecNet/Accounts.txt:5183-5186 against the + // RecNet.Service enum in dump.cs:586523-586538 (Auth=0). Service.Auth + // maps to auth.{apex} in ConfigService's ServiceUrls, and + // the path the watch appends has NO `/api/` prefix — it's just + // role/developer/{id}. The previous /api/role/...-only + // route never matched, so every nameplate probe came back 404 and + // the watch logged "Failed to GetIsDeveloper for accountId X: HTTP + // Error 404" once per remote player per scene load. + [HttpGet("/role/developer/{accountId:long}")] + [HttpGet("/api/role/developer/{accountId:long}")] + [AllowAnonymous] + public async Task> IsDeveloper(long accountId) + { + // OR IsCommunityTeam into the answer so the watch unlocks the + // in-settings "Developer Display Mode" slider for community-team + // members too — the slider's positions render the overhead + // badge as "Community Team" / "Developer" (see + // Cpp2IL_ISIL/.../PlayerUI.txt:9085-9099). The 2020 watch never + // probes a separate community-team role, so collapsing both + // flags into this one endpoint is the only way to reach the + // badge UI without patching the binary. + var isDev = await db.Players + .Where(p => p.Id == accountId) + .Select(p => (bool?)(p.IsDeveloper || p.IsCommunityTeam || p.IsAdmin)) + .FirstOrDefaultAsync(); + return Ok(isDev ?? false); + } + + /// Mirror endpoint for the admin role — same shape, in case + /// later builds added an /api/role/admin/{id} probe. The 2020 + /// dump only references the developer one but admins also get all + /// dev privileges, so the result is unioned. + [HttpGet("/role/admin/{accountId:long}")] + [HttpGet("/api/role/admin/{accountId:long}")] + [AllowAnonymous] + public async Task> IsAdmin(long accountId) + { + var isAdmin = await db.Players + .Where(p => p.Id == accountId) + .Select(p => (bool?)p.IsAdmin) + .FirstOrDefaultAsync(); + return Ok(isAdmin ?? false); + } + + /// Future-proofing for a hypothetical + /// role/communityteam/{id} probe — not referenced by the 2020 + /// build, but mirrors the developer shape so a patched watch can + /// distinguish the two roles without a server change. + [HttpGet("/role/communityteam/{accountId:long}")] + [HttpGet("/api/role/communityteam/{accountId:long}")] + [AllowAnonymous] + public async Task> IsCommunityTeam(long accountId) + { + var isCt = await db.Players + .Where(p => p.Id == accountId) + .Select(p => (bool?)p.IsCommunityTeam) + .FirstOrDefaultAsync(); + return Ok(isCt ?? false); + } +} diff --git a/DorkNet.Server/Controllers/API/RoomKeys/RoomKeysController.cs b/DorkNet.Server/Controllers/API/RoomKeys/RoomKeysController.cs new file mode 100644 index 0000000..8698f62 --- /dev/null +++ b/DorkNet.Server/Controllers/API/RoomKeys/RoomKeysController.cs @@ -0,0 +1,280 @@ +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Mvc; +using Microsoft.EntityFrameworkCore; +using System.Text.Json; +using System.Text.Json.Serialization; +using DorkNet.Server.Auth; +using DorkNet.Server.Data; +using DorkNet.Server.Data.Entities; + +namespace DorkNet.Server.Controllers.API.RoomKeys; + +[ApiController] +[Authorize] +public class RoomKeysController(DorkNetDbContext db) : ControllerBase +{ + private long Me => this.RequireCurrentPlayerId(); + + // Boot-sequence call — the 2020.12 client hits this with roomId=-1 + // before the user has authenticated. Class-level [Authorize] would + // 401 and the client treats that as fatal, kicking back to LogoutScene. + [AllowAnonymous] + [HttpGet("api/roomkeys/v1/room")] + public async Task ForRoom([FromQuery] long roomId) + { + if (roomId <= 0) return Ok(Array.Empty()); + var rows = await db.RoomKeys + .Where(k => k.RoomId == roomId && !k.IsDeleted) + .OrderBy(k => k.Price) + .ThenBy(k => k.Name) + .ToListAsync(); + return Ok(rows.Select(ToWire)); + } + + [HttpGet("api/roomkeys/v1/mine")] + public async Task Mine() + { + var pid = Me; + var rows = await db.RoomKeys + .Where(k => k.CreatorPlayerId == pid && !k.IsDeleted) + .OrderByDescending(k => k.UpdatedAt) + .ToListAsync(); + return Ok(rows.Select(ToWire)); + } + + [HttpPost("api/roomkeys/v1/create")] + public async Task Create() + { + var req = await ReadBodyAsync(); + var status = ValidateText(req.Name, req.Description, req.Price); + if (status != RoomKeyStatus.Success) + return Ok(RoomKeyResponse(status, null)); + + var room = await db.Rooms.FirstOrDefaultAsync(r => r.Id == req.RoomId); + if (room is null) return Ok(RoomKeyResponse(RoomKeyStatus.RoomDoesNotExist, null)); + if (!await CanManageRoomKeysAsync(room, Me)) + return Ok(RoomKeyResponse(RoomKeyStatus.PermissionDenied, null)); + + var activeCount = await db.RoomKeys.CountAsync(k => k.RoomId == room.Id && !k.IsDeleted); + if (activeCount >= 100) + return Ok(RoomKeyResponse(RoomKeyStatus.RoomKeyLimitReached, null)); + + var duplicate = await db.RoomKeys.AnyAsync(k => + k.RoomId == room.Id && !k.IsDeleted && k.Name == req.Name!.Trim()); + if (duplicate) return Ok(RoomKeyResponse(RoomKeyStatus.DuplicateName, null)); + + var key = new RoomKeyEntity + { + RoomId = room.Id, + CreatorPlayerId = Me, + Name = req.Name!.Trim(), + Description = req.Description!.Trim(), + Price = req.Price, + ReplicationId = Guid.NewGuid().ToString("D"), + }; + db.RoomKeys.Add(key); + await db.SaveChangesAsync(); + return Ok(RoomKeyResponse(RoomKeyStatus.Success, key)); + } + + [HttpPost("api/roomkeys/v1/update")] + public async Task Update() + { + var req = await ReadBodyAsync(); + var key = await db.RoomKeys.FirstOrDefaultAsync(k => k.Id == req.RoomKeyId && !k.IsDeleted); + if (key is null) return Ok(RoomKeyResponse(RoomKeyStatus.DoesNotExist, null)); + var room = await db.Rooms.FirstOrDefaultAsync(r => r.Id == key.RoomId); + if (room is null) return Ok(RoomKeyResponse(RoomKeyStatus.RoomDoesNotExist, null)); + if (!await CanManageRoomKeysAsync(room, Me)) + return Ok(RoomKeyResponse(RoomKeyStatus.PermissionDenied, key)); + + var nextName = req.Name?.Trim() ?? key.Name; + var nextDescription = req.Description?.Trim() ?? key.Description; + var nextPrice = req.Price ?? key.Price; + var status = ValidateText(nextName, nextDescription, nextPrice); + if (status != RoomKeyStatus.Success) + return Ok(RoomKeyResponse(status, key)); + + var duplicate = await db.RoomKeys.AnyAsync(k => + k.Id != key.Id && k.RoomId == key.RoomId && !k.IsDeleted && k.Name == nextName); + if (duplicate) return Ok(RoomKeyResponse(RoomKeyStatus.DuplicateName, key)); + + key.Name = nextName; + key.Description = nextDescription; + key.Price = nextPrice; + key.UpdatedAt = DateTime.UtcNow; + await db.SaveChangesAsync(); + return Ok(RoomKeyResponse(RoomKeyStatus.Success, key)); + } + + [HttpDelete("api/roomkeys/v1/delete/{roomKeyId:long}")] + [HttpPost("api/roomkeys/v1/delete/{roomKeyId:long}")] + public async Task Delete(long roomKeyId) + { + var key = await db.RoomKeys.FirstOrDefaultAsync(k => k.Id == roomKeyId && !k.IsDeleted); + if (key is null) return Content(((int)RoomKeyStatus.DoesNotExist).ToString(), "application/json"); + var room = await db.Rooms.FirstOrDefaultAsync(r => r.Id == key.RoomId); + if (room is null) return Content(((int)RoomKeyStatus.RoomDoesNotExist).ToString(), "application/json"); + if (!await CanManageRoomKeysAsync(room, Me)) + return Content(((int)RoomKeyStatus.PermissionDenied).ToString(), "application/json"); + + key.IsDeleted = true; + key.UpdatedAt = DateTime.UtcNow; + await db.SaveChangesAsync(); + return Content(((int)RoomKeyStatus.Success).ToString(), "application/json"); + } + + [HttpGet("api/roomkeys/v1/purchased/{roomKeyId:long}")] + public async Task Purchased(long roomKeyId) + { + var pid = Me; + var owns = await db.RoomKeyPurchases.AnyAsync(p => p.RoomKeyId == roomKeyId && p.PlayerId == pid); + return Content(owns ? "true" : "false", "application/json"); + } + + public static object RoomKeyResponse(RoomKeyStatus status, RoomKeyEntity? key) => new + { + Status = (int)status, + RoomKey = key is null ? null : ToWire(key), + }; + + public static object ToWire(RoomKeyEntity key) => new + { + RoomKeyId = key.Id, + ReplicationId = Guid.TryParse(key.ReplicationId, out var guid) + ? guid + : Guid.Empty, + key.RoomId, + key.Name, + key.Description, + key.Price, + }; + + private static RoomKeyStatus ValidateText(string? name, string? description, int price) + { + if (string.IsNullOrWhiteSpace(name) || name.Trim().Length < 3) return RoomKeyStatus.NameTooShort; + if (name.Trim().Length > 40) return RoomKeyStatus.NameTooLong; + if (string.IsNullOrWhiteSpace(description) || description.Trim().Length < 10) return RoomKeyStatus.DescriptionTooShort; + if (description.Trim().Length > 174) return RoomKeyStatus.DescriptionTooLong; + if (price < 10) return RoomKeyStatus.PriceTooLow; + if (price > 10000) return RoomKeyStatus.PriceTooHigh; + return RoomKeyStatus.Success; + } + + private async Task CanManageRoomKeysAsync(RoomEntity room, long playerId) + { + if (room.CreatorPlayerId == playerId) return true; + var isCoOwner = await db.RoomRoles.AnyAsync(r => + r.RoomId == room.Id && r.PlayerId == playerId && r.Role == 0 && r.Accepted); + if (isCoOwner) return true; + return await db.Players + .Where(p => p.Id == playerId) + .Select(p => p.IsAdmin) + .FirstOrDefaultAsync(); + } + + private async Task ReadBodyAsync() where T : new() + { + if (Request.HasFormContentType) + { + var form = await Request.ReadFormAsync(); + if (typeof(T) == typeof(NewRoomKeyRequest)) + { + object req = new NewRoomKeyRequest + { + RoomId = long.TryParse(form["roomId"].FirstOrDefault() + ?? form["RoomId"].FirstOrDefault(), out var roomId) + ? roomId + : 0, + Name = form["name"].FirstOrDefault() ?? form["Name"].FirstOrDefault(), + Description = form["description"].FirstOrDefault() ?? form["Description"].FirstOrDefault(), + Price = int.TryParse(form["price"].FirstOrDefault() + ?? form["Price"].FirstOrDefault(), out var price) + ? price + : 0, + }; + return (T)req; + } + + if (typeof(T) == typeof(UpdateRoomKeyRequest)) + { + object req = new UpdateRoomKeyRequest + { + RoomKeyId = long.TryParse(form["roomKeyId"].FirstOrDefault() + ?? form["RoomKeyId"].FirstOrDefault(), out var roomKeyId) + ? roomKeyId + : 0, + Name = form["name"].FirstOrDefault() ?? form["Name"].FirstOrDefault(), + Description = form["description"].FirstOrDefault() ?? form["Description"].FirstOrDefault(), + Price = int.TryParse(form["price"].FirstOrDefault() + ?? form["Price"].FirstOrDefault(), out var price) + ? price + : null, + }; + return (T)req; + } + + return new T(); + } + + try + { + return await JsonSerializer.DeserializeAsync(Request.Body, JsonOptions) ?? new T(); + } + catch (JsonException) + { + return new T(); + } + } + + private static readonly JsonSerializerOptions JsonOptions = new() + { + PropertyNameCaseInsensitive = true, + }; + + public sealed class NewRoomKeyRequest + { + [JsonPropertyName("roomId")] public long RoomId { get; set; } + [JsonPropertyName("name")] public string? Name { get; set; } + [JsonPropertyName("description")] public string? Description { get; set; } + [JsonPropertyName("price")] public int Price { get; set; } + } + + public sealed class UpdateRoomKeyRequest + { + [JsonPropertyName("roomKeyId")] public long RoomKeyId { get; set; } + [JsonPropertyName("name")] public string? Name { get; set; } + [JsonPropertyName("description")] public string? Description { get; set; } + [JsonPropertyName("price")] public int? Price { get; set; } + } +} + +public enum RoomKeyStatus +{ + Success = 0, + InvalidParameters = 1, + DoesNotExist = 2, + NameTooShort = 3, + NameTooLong = 4, + DuplicateName = 5, + InappropriateName = 6, + DescriptionTooShort = 7, + DescriptionTooLong = 8, + InappropriateDescription = 9, + PriceTooLow = 10, + PriceTooHigh = 11, + PermissionDenied = 12, + PlayerHasRoomUnderModerationReview = 13, + JuniorStatusFail = 14, + PlayerIsNotCoOwner = 15, + RoomKeyLimitReached = 16, + PlayerAlreadyOwns = 17, + RoomUnderModerationReview = 18, + PurchaseFailed = 19, + RoomDoesNotExist = 20, + PaidKeyPurchasingDisabled = 21, + CreateOrModifyKeysDisabled = 22, + RoomKeyUnderModerationReview = 23, + PlayerRestrictedFromP2PSelling = 24, + PlayerNotRecRoomPlusMember = 25, +} diff --git a/DorkNet.Server/Controllers/API/Rooms/V2/RoomsController.cs b/DorkNet.Server/Controllers/API/Rooms/V2/RoomsController.cs new file mode 100644 index 0000000..b91c103 --- /dev/null +++ b/DorkNet.Server/Controllers/API/Rooms/V2/RoomsController.cs @@ -0,0 +1,2468 @@ +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Mvc; +using Microsoft.EntityFrameworkCore; +using DorkNet.Models.Notification; +using DorkNet.Server.Auth; +using DorkNet.Server.Controllers.Match; +using DorkNet.Server.Data; +using DorkNet.Server.Data.Entities; +using DorkNet.Server.Services; + +namespace DorkNet.Server.Controllers.API.Rooms.V2; + +/// +/// api.rec.net/api/rooms/* — DB-backed implementation of the watch's +/// Rooms tab. +/// +/// Replaces the previous stubs in MiscStubsController for: +/// v1/hot, v1/filters, v1/tags, v1/search, v2/baserooms, v2/myrooms, +/// v2/mybookmarks, v2/mymoderated, v2/mysubscribed, v2/myrecent, +/// v2/name/{name}, v2/{id}, v3/{id}, v3/details/{id}, v4/details/{id}, +/// roomserver/rooms/{id}, v1/personaldetails/{id}, v2/personaldetails/{id}. +/// +/// All endpoints serialize Rooms via RoomService.ToWireRoom which matches +/// Room.Deserialize at RVA 0x114E430 (PascalCase keys, all 16 required +/// fields plus optional VR-low / mobile / mic-mute flags). +/// +[ApiController] +public class RoomsController( + RoomService rooms, + PlaylistService playlists, + DorkNetDbContext db, + PlayerPresenceService presence, + OnlinePresenceService onlinePresence, + DomainConfig domain, + NotificationService notifications, + ILogger logger) : ControllerBase +{ + private long? CurrentPlayerId => ControllerBaseExtensions.CurrentPlayerId(this); + + // ── Browse / Hot / Search ──────────────────────────────────────────── + + /// + /// `Rooms.GetHotRooms(roomScoreType, tags)` — the watch's "Trending" tab. + /// `roomScoreType` is ignored (we always sort by HotScore). `tags` is + /// passed through as a substring filter against the comma-separated + /// TagsCsv column (e.g. `#community`, `#recroomoriginal`). + /// + [HttpGet("api/rooms/v1/hot")] + [HttpGet("rooms/hot")] + public async Task Hot( + [FromQuery] string? roomScoreType, + [FromQuery] string? tags) + => Ok((await rooms.HotAsync(tags)).Select(RoomService.ToWireRoom).ToList()); + + /// + /// GET /roomserver/rooms/recommendations — the home-tab + /// "Recommended" carousel. Wire shape is NOT a flat + /// List<Room>; the watch's + /// EJDCNGBEICB.JDKLBNBIEAH at EJDCNGBEICB.txt:1349,1430 + /// returns IPromise<List<ANNKHNFLMNP>> and + /// at + /// ANNKHNFLMNP.txt:43-97 reads SeedRoom (sub-object, + /// KLCOGEIGEBJ i.e. full Room) and Rooms (list of + /// Rooms) as required keys. Returning [{room}, …] raised + /// KeyNotFoundException: Failed to find key 'Rooms' when + /// deserializing object of type KLCOGEIGEBJ. + /// + /// We group the hot list into one "card" per room — that gives the + /// watch one row per featured room with that room as its own + /// SeedRoom and a single-element Rooms list. Cheap, semantically + /// correct, and avoids the deserializer-crashing empty-Rooms + /// edge case the watch hits when a group has no rooms. + /// + [HttpGet("roomserver/rooms/recommendations")] + [HttpGet("rooms/recommendations")] + public async Task Recommendations( + [FromQuery(Name = "splitTestId")] int? splitTestId, + [FromQuery(Name = "splitTestValue")] int? splitTestValue) + { + var hot = await rooms.HotAsync(null, take: 12); + if (hot.Count == 0) return Ok(Array.Empty()); + + var roomIds = hot.Select(r => r.Id).ToList(); + var sceneRows = await db.RoomScenes + .Where(s => roomIds.Contains(s.RoomId)) + .OrderBy(s => s.OrderIndex) + .ToListAsync(); + var scenesByRoom = sceneRows + .GroupBy(s => s.RoomId) + .ToDictionary(g => g.Key, g => (IReadOnlyList)g.ToList()); + + var wireRooms = hot + .Select(r => BuildRoomServerDetails(r, scenesByRoom.GetValueOrDefault(r.Id))) + .ToList(); + + // One ANNKHNFLMNP entry per room: SeedRoom = the room itself, + // Rooms = [that room]. The watch enumerates the outer list to + // build the row list; each row shows its SeedRoom as headline. + var result = wireRooms.Select(room => new Dictionary(StringComparer.Ordinal) + { + ["SeedRoom"] = room, + ["Rooms"] = new List { room }, + }).ToList(); + + return Ok(result); + } + + /// + /// `Rooms.SearchForRooms(query)` — free-text room search. + /// + [HttpGet("api/rooms/v1/search")] + [HttpGet("api/rooms/v2/search")] + [HttpGet("rooms/search")] + public async Task Search( + [FromQuery(Name = "query")] string? query, + [FromQuery(Name = "value")] string? value) + => Ok((await rooms.SearchAsync(query ?? value ?? string.Empty)) + .Select(RoomService.ToWireRoom).ToList()); + + /// GET api/rooms/v2/live — currently-active rooms + /// (rooms with at least one player presence). Same shape as Hot + /// — order by HotScore as a proxy for "live activity" since we + /// don't track instance population separately yet. + [HttpGet("api/rooms/v2/live")] + public async Task Live([FromQuery] string? roomScoreType) + => Ok((await rooms.HotAsync(null)).Select(RoomService.ToWireRoom).ToList()); + + /// + /// `Rooms.GetBaseRooms` — list of "base" rooms used by Rec Room Originals + /// to seed the room creation UI. Returns the same set as #recroomoriginal. + /// + [HttpGet("api/rooms/v2/baserooms")] + [HttpGet("api/rooms/v3/baserooms")] + [HttpGet("rooms/base")] + public async Task BaseRooms() + => Ok((await rooms.HotAsync("#recroomoriginal")).Select(RoomService.ToWireRoom).ToList()); + + /// + /// `Rooms.GetFilters` — pinned/popular tag chips shown above the room + /// list in the watch. + /// + // Tag wire format: NO leading '#'. The watch's RoomFilterChip prepends + // '#' at render time, so sending "#community" produces "##community" in + // the UI. Names stored in the DB also drop the prefix. + [HttpGet("api/rooms/v1/filters")] + public IActionResult Filters() => Ok(new + { + PinnedFilters = new[] { "community", "recroomoriginal", "featured", "quest" }, + PopularFilters = new[] { "paintball", "dodgeball", "soccer", "lasertag", "discgolf" }, + }); + + [HttpGet("api/rooms/v1/tags")] + public IActionResult Tags() => Ok(new[] { + "community", "recroomoriginal", "featured", "quest", + "paintball", "dodgeball", "soccer", "lasertag", "discgolf", + }); + + [HttpGet("api/rooms/v1/pinnedtags")] + public IActionResult PinnedTags() => Ok(new[] { "community", "recroomoriginal", "featured" }); + + [HttpGet("api/rooms/v1/populartags")] + public IActionResult PopularTags() => Ok(new[] { "paintball", "dodgeball", "soccer" }); + + // ── Single room lookups ────────────────────────────────────────────── + + /// + /// `Rooms.GetByName(roomName)` — used when the client constructs the + /// share-link target or when /goto/room/{name} runs. Falls back to a + /// synthesised "Room_" for unknown names so the deserializer + /// doesn't crash on 404. + /// + [HttpGet("api/rooms/v2/name/{roomName}")] + [HttpGet("api/rooms/v3/name/{roomName}")] + public async Task ByName(string roomName) + { + var r = await rooms.GetByNameAsync(roomName); + return Ok(RoomService.ToWireRoom(r ?? Synthetic(roomName))); + } + + /// + /// `Rooms.GetById(roomId)` — same shape as ByName. + /// + [HttpGet("api/rooms/v2/{roomId:long}")] + [HttpGet("api/rooms/v3/{roomId:long}")] + [HttpGet("rooms/{roomId:long}")] + public async Task ById(long roomId) + { + var r = await rooms.GetByIdAsync(roomId); + return Ok(RoomService.ToWireRoom(r ?? Synthetic($"Room_{roomId}", roomId))); + } + + // ── RoomDetails (the boot-critical one) ───────────────────────────── + // + // /v4/details/{roomId} is hit *during* the matchmaking goto flow. + // RoomDetails.Deserialize at RVA 0x114C580 expects PascalCase keys: + // Room (object), Scenes (list), CoOwners/InvitedCoOwners, + // Moderators/InvitedModerators, Hosts/InvitedHosts (all int lists), + // CheerCount, FavoriteCount, VisitCount, Tags. + // + // Scenes is populated for every room. The persistence coroutine + // (`RoomPersistenceManager+`) runs + // unconditionally on scene load and dereferences + // `Rooms.LocalRoomScene.DataBlobName` on its first MoveNext — so + // returning Scenes=[] makes LocalRoomScene null and NPEs the boot. + // + // The historical "Scenes=[] short-circuits the persistence manager" + // comment that used to live here was the reverse of what actually + // happens in this build. Confirmed empirically with the [req] log: + // /v4/details/1 fires before scene load for the dorm too, so + // Rooms.LocalRoom is populated and the coroutine just needs a + // populated Scenes[0] to get a non-null LocalRoomScene. + + /// GET roomserver/rooms/{roomId}/subrooms/{subRoomId}/datahistory + /// — drives the watch's "Restore to old version" UI in the room- + /// settings tab. Per-subroom (not per-room) — the watch builds the + /// URL from the template + /// String.Format("rooms/{0}/subrooms/{1}/datahistory", roomId, subRoomId) + /// (verified at EJDCNGBEICB.txt:1190); the roomserver/ + /// prefix is added by RecNet's URL builder for everything on the + /// rooms host (api.localhost proxies it through the same prefix as the + /// rest of the rooms API — see all the other roomserver/rooms/... + /// routes in this controller). + /// + /// SubRoomId semantics: the watch's 2020.12 build treats + /// "SubRoomId" as the , NOT + /// rec.net's long random Guid-like SubRoomId. The heartbeat reports + /// subRoom=11 for the 12th scene in RoomDetails.SubRooms[]; + /// this endpoint must filter by OrderIndex to match. The zip-importer + /// stamps each SubRooms/<scene>/History/ blob's + /// with the scene's + /// OrderIndex at insert time — the rec.net SubRoomId is captured in + /// the sidecar JSON for provenance but isn't what gets indexed. + /// + /// Wire shape: List<RoomDataHistoryDTO>. The + /// watch's DHOCPFIOKHD.PPGFHEDFBEA deserializer reads EXACTLY + /// four PascalCase keys (DHOCPFIOKHD.txt:136-150): + /// SubRoomId (long, strict GetKey), DataBlob (string), + /// SavedByAccountId (long, strict GetKey), CreatedAt + /// (DateTime, GetKeyOrDefault). Missing strict keys throw + /// KeyNotFoundException in LitJson and the watch shows the + /// "Could not retrieve save history from server :-(" toast from + /// UIRestoreRoomDialog.txt:1970. Extra keys are ignored. + /// + /// Pre-2026-05 rows (and any rows the live save flow inserts, + /// which doesn't populate SubRoomId yet) have a null SubRoomId and + /// surface under every sub-room's history list — back-compat. + [HttpGet("roomserver/rooms/{roomId:long}/subrooms/{subRoomId:long}/datahistory")] + [HttpGet("rooms/{roomId:long}/subrooms/{subRoomId:long}/datahistory")] + public async Task SubRoomDataHistory(long roomId, long subRoomId) + { + var blobs = await db.RoomDataBlobs + .Where(b => b.RoomId == roomId + && (b.SubRoomId == subRoomId || b.SubRoomId == null)) + // Skip the canonical per-scene blob from the import — the watch + // is asking for HISTORY, and serving the current `room___.room` + // back to it as an option would make `restore to current` a + // confusing no-op entry in the list. + .Where(b => !b.BlobName.StartsWith($"room_{roomId}_")) + .OrderByDescending(b => b.UploadedAt) + .Take(50) + .Select(b => new + { + SubRoomId = subRoomId, + DataBlob = b.BlobName, + SavedByAccountId = b.UploadedByPlayerId, + CreatedAt = b.UploadedAt, + }) + .ToListAsync(); + return Ok(blobs); + } + + /// POST roomserver/rooms/{roomId}/subrooms/{subRoomId}/restoredata + /// — the watch's "Go" button in the Restore Room dialog. Body is + /// form-urlencoded with a single filename field carrying the + /// historical blob name from the datahistory list. + /// is RoomSceneEntity.OrderIndex (same convention as the GET above). + /// + /// Effect: stamp the chosen scene's DataBlobName to the historical + /// blob and, when restoring the room's current entry scene, also + /// update the room-level CurrentDataBlobName so a fresh /v4/details + /// returns the restored bytes too. Then push SubscriptionUpdateRoom + /// so any clients already cached on the room reload the scene. + /// + /// Returns CreateModifyRoomSceneResponse — same shape as + /// /api/rooms/v1/datahistory/restore — so the watch's + /// ProcessCreateModifyRoomSceneResponse chain can deserialise and + /// flip the Restore dialog out of its "applying" state. + [HttpPost("roomserver/rooms/{roomId:long}/subrooms/{subRoomId:long}/restoredata")] + [HttpPost("rooms/{roomId:long}/subrooms/{subRoomId:long}/restoredata")] + [Authorize] + public async Task SubRoomRestoreData( + long roomId, long subRoomId, [FromForm] string? filename) + { + if (string.IsNullOrWhiteSpace(filename)) + return BadRequest(new { error = "missing_filename" }); + + var pid = this.RequireCurrentPlayerId(); + var room = await rooms.GetByIdAsync(roomId); + if (room is null) return NotFound(new { error = "room_not_found" }); + if (room.CreatorPlayerId != pid) return Forbid(); + + var blob = await db.RoomDataBlobs + .FirstOrDefaultAsync(b => b.BlobName == filename && b.RoomId == roomId); + if (blob is null) return NotFound(new { error = "blob_not_found", filename }); + + var sceneRows = await db.RoomScenes + .Where(s => s.RoomId == roomId) + .OrderBy(s => s.OrderIndex) + .ToListAsync(); + var sceneRow = sceneRows.FirstOrDefault(s => s.OrderIndex == (int)subRoomId); + if (sceneRow is null) return NotFound(new { error = "subroom_not_found", subRoomId }); + + sceneRow.DataBlobName = filename; + sceneRow.DataModifiedAt = DateTime.UtcNow; + + // Only stamp the room-level CurrentDataBlobName when restoring the + // entry scene (OrderIndex 0). Restoring a non-entry sub-room + // shouldn't rewrite what the room loads at the front door. + if (sceneRow.OrderIndex == 0) + { + await db.Rooms + .Where(r => r.Id == roomId) + .ExecuteUpdateAsync(s => s + .SetProperty(r => r.CurrentDataBlobName, filename) + .SetProperty(r => r.UpdatedAt, DateTime.UtcNow)); + } + await db.SaveChangesAsync(); + + var pushRoom = room.IsDormRoom + ? CloneWithCreator(room, pid, room.CurrentDataBlobName) + : CloneWithCreator(room, room.CreatorPlayerId, room.CurrentDataBlobName); + var roomDetailsPayload = BuildRoomDetails(pushRoom, sceneRows, sceneRow.OrderIndex, filename); + await notifications.NotifyAsync(pid, PushNotificationId.SubscriptionUpdateRoom, roomDetailsPayload); + + logger.LogInformation( + "[rooms-restore] player={PlayerId} room={RoomId} subRoom={SubRoomId} scene={Scene} blob={Blob}", + pid, roomId, subRoomId, sceneRow.Name, filename); + + // Watch's response deserializer is PPENFJMFPNE — the full Room DTO + // (verified at EJDCNGBEICB:6029 in the 2020.12.18 dump). Returning + // a bare RoomScene wrapper makes its dict-based decoder throw + // KeyNotFoundException ("Failed to restore subroom save: Malformed + // Response"). Mirror the /roomserver/rooms/{id} shape — same + // payload BuildRoomDetails generates for /v4/details. + return Ok(roomDetailsPayload); + } + + /// POST api/rooms/v4/saveData — the watch's + /// RecNet.Rooms.UploadLocalRoomSceneData save flow. + /// Body shape mirrors RecNet.Rooms+SaveRoomSceneRequest + /// (Cpp2IL_CS RecNet/Rooms.cs:858): RoomSceneId, RoomDataFilename, + /// InventionUsages[], CreatorActionContext, RequestPlayerId. + /// + /// Persists the new CurrentDataBlobName on the room + /// (and, for dorms, the per-player DormStateEntity) so the next + /// /v4/details + cdn fetch loads what just got uploaded. Returns + /// a populated RoomScene JSON — that's what the watch's + /// continuation callback (b__1 in DisplayClass124_0) expects; + /// returning an empty body trips a "HTTP Error 404" path in + /// BestHTTP's Future framework even though the HTTP status was + /// fine. + [HttpPost("api/rooms/v4/saveData")] + [Authorize] + public async Task SaveData([FromBody] SaveRoomSceneRequest body) + { + var pid = this.RequireCurrentPlayerId(); + + // Resolve the room. The watch sends RoomSceneId (the scene's + // OrderIndex per BuildRoomDetails above — 0 for single-scene + // rooms). To find the parent room we cross-reference the + // player's current presence (which RecordResponseAsync stamps + // on every /goto). This is the only signal we have without a + // RoomId on the request body. + var current = presence.GetRoom(pid); + if (current is null) + { + // No active room — without it we can't pick a target row. + // Return a degenerate RoomScene so the deserializer doesn't + // throw, but flag CanMatchmakeInto=false so the watch + // doesn't think the save succeeded. + return Ok(new + { + RoomSceneId = body.RoomSceneId, + RoomId = 0L, + RoomSceneLocationId = "76d98498-60a1-430c-ab76-b54a29b7a163", + Name = "Home", + IsSandbox = false, + DataBlobName = body.RoomDataFilename ?? string.Empty, + MaxPlayers = 8, + CanMatchmakeInto = false, + DataModifiedAt = DateTime.UtcNow.ToString("yyyy-MM-ddTHH:mm:ssZ"), + }); + } + + var roomId = current.RoomId; + var room = await rooms.GetByIdAsync(roomId); + if (room is null) return NotFound(new { error = "room_not_found" }); + + var newBlob = body.RoomDataFilename?.Trim() ?? string.Empty; + + // Multi-scene rooms have a RoomScenes row per scene. The 2020 + // client usually sends the scene's OrderIndex, but some flows + // send a stale/non-index id while the local room is still a + // single-scene dorm/custom room. In that common single-scene + // case, stamp the only row anyway; otherwise the pushed + // RoomDetails can keep Scenes[0].DataBlobName at v(N) while + // Rooms.CurrentDataBlobName is v(N+1), and the post-save reload + // reads stale bytes. + var sceneRowsForRoom = await db.RoomScenes + .Where(s => s.RoomId == roomId) + .OrderBy(s => s.OrderIndex) + .ToListAsync(); + var sceneRow = sceneRowsForRoom.FirstOrDefault(s => s.OrderIndex == (int)body.RoomSceneId) + ?? (sceneRowsForRoom.Count == 1 ? sceneRowsForRoom[0] : null); + + // Dorms: per-player state, keyed by the calling player so two + // people in the same dorm room ID save into separate slots. + if (room.IsDormRoom) + { + if (room.CreatorPlayerId != pid) return Forbid(); + + var dormState = await db.DormStates.FirstOrDefaultAsync(d => d.PlayerId == pid); + if (dormState is null) + { + dormState = new DormStateEntity { PlayerId = pid }; + db.DormStates.Add(dormState); + } + dormState.CurrentDataBlobName = newBlob; + dormState.UpdatedAt = DateTime.UtcNow; + } + else + { + // Owned rooms: only the creator can save. Admin override + // intentionally not included — admins should use /restore + // when they need to roll a room back. + if (room.CreatorPlayerId != pid) return Forbid(); + } + + if (sceneRow is not null) + { + sceneRow.DataBlobName = newBlob; + sceneRow.DataModifiedAt = DateTime.UtcNow; + } + await db.Rooms + .Where(r => r.Id == roomId) + .ExecuteUpdateAsync(s => s + .SetProperty(r => r.CurrentDataBlobName, newBlob) + .SetProperty(r => r.UpdatedAt, DateTime.UtcNow)); + await db.SaveChangesAsync(); + + // The watch's MasterUploadRoomDataBlobCoroutine waits for BOTH + // the /upload + /saveData responses AND a SubscriptionUpdateRoom + // SignalR push before flipping the save state machine out of + // SAVING. Without this push the spinner stays "Saving..." forever + // even though both HTTP calls returned 200 — verified via the + // RoomDetailsUpdatedCallback closure in + // RoomPersistenceManager+<>c__DisplayClass174_0. + // + // For dorms we re-shape with the caller as creator + their own + // DormStateEntity blob name so the watch's set_LocalRoomDetails + // matches LocalRoomDetails.RoomId and triggers + // SafeRaiseRoomDetailsUpdated(roomId). + var pushRoom = room; + if (room.IsDormRoom) + pushRoom = CloneWithCreator(room, pid, newBlob); + else + pushRoom = CloneWithCreator(room, room.CreatorPlayerId, newBlob); + foreach (var scene in sceneRowsForRoom) + { + if (sceneRow is not null && scene.Id == sceneRow.Id) + { + scene.DataBlobName = newBlob; + scene.DataModifiedAt = DateTime.UtcNow; + } + } + var savedSceneId = sceneRow?.OrderIndex ?? body.RoomSceneId; + var roomDetailsPayload = BuildRoomDetails(pushRoom, sceneRowsForRoom, savedSceneId, newBlob); + logger.LogInformation( + "[rooms-save] saveData player={PlayerId} room={RoomId} requestedScene={RequestedSceneId} savedScene={SavedSceneId} blob={BlobName} sceneRow={SceneRowFound} scenes={SceneCount}", + pid, roomId, body.RoomSceneId, savedSceneId, newBlob, sceneRow is not null, sceneRowsForRoom.Count); + var playersInInstance = onlinePresence.OnlinePlayerIds() + .Where(playerId => + { + var playerRoom = presence.GetRoom(playerId); + return playerRoom is not null + && playerRoom.RoomId == roomId + && playerRoom.RoomInstanceId == current.RoomInstanceId; + }) + .Append(pid) + .Distinct() + .ToArray(); + logger.LogInformation( + "[rooms-save] fanout room update player={PlayerId} room={RoomId} instance={InstanceId} blob={BlobName} recipients={RecipientCount}", + pid, roomId, current.RoomInstanceId, newBlob, playersInInstance.Length); + + var updatedRoomInstances = new Dictionary(); + foreach (var playerId in playersInInstance) + { + var currentPresence = presence.GetRoom(playerId); + if (currentPresence is null && playerId == pid) + { + currentPresence = current; + } + if (currentPresence is null) continue; + + currentPresence.DataBlob = newBlob; + presence.SetRoom(playerId, currentPresence); + updatedRoomInstances[playerId] = currentPresence; + } + + // SubscriptionUpdateRoom delivers the new RoomDetails (with + // Scenes[0].DataBlobName = newBlob). Rooms.OnSubscriptionUpdateRoom + // → StoreRoomDetailsInCache → set_LocalRoomDetails iterates + // Scenes for one matching RoomSceneId == LocalSubRoomId and writes + // LocalRoomScene to that scene, which is what DownloadRoomData + // BlobCoroutine reads when PostSaveReloading runs. + foreach (var playerId in playersInInstance) + { + await notifications.NotifyAsync(playerId, PushNotificationId.SubscriptionUpdateRoom, roomDetailsPayload); + } + + // RoomInstanceUpdate keeps the matchmaking-side dataBlob and any + // friends-list "in room X" indicators in sync. No nudge — live + // doesn't send one, and the fake sub-room id confuses the watch's + // OnLocalGameSessionUpdated scene re-resolve. + foreach (var (playerId, currentPresence) in updatedRoomInstances) + { + await notifications.NotifyAsync( + playerId, + PushNotificationId.SubscriptionUpdateGameSession, + currentPresence); + } + + // PresenceUpdate keeps the next heartbeat aligned with the blob + // applied through the room-details push above. + foreach (var (playerId, currentPresence) in updatedRoomInstances) + { + var presencePayload = new + { + playerId = (int)playerId, + statusVisibility = 1, + deviceClass = 0, + vrMovementMode = 0, + roomInstance = new + { + roomInstanceId = currentPresence.RoomInstanceId, + roomId = currentPresence.RoomId, + subRoomId = currentPresence.SubRoomId, + roomInstanceType = currentPresence.RoomInstanceType, + location = currentPresence.Location, + dataBlob = currentPresence.DataBlob, + eventId = currentPresence.EventId, + clubId = currentPresence.ClubId, + roomCode = currentPresence.RoomCode, + photonRegionId = currentPresence.PhotonRegionId, + photonRoomId = currentPresence.PhotonRoomId, + name = currentPresence.Name, + maxCapacity = currentPresence.MaxCapacity, + isFull = currentPresence.IsFull, + isPrivate = currentPresence.IsPrivate, + isInProgress = currentPresence.IsInProgress, + EncryptVoiceChat = currentPresence.EncryptVoiceChat, + }, + isOnline = true, + appVersion = 20201210, + }; + await notifications.NotifyAsync(playerId, PushNotificationId.SubscriptionUpdatePresence, presencePayload); + } + + return Ok(new + { + RoomSceneId = savedSceneId, + RoomId = roomId, + RoomSceneLocationId = sceneRow?.RoomSceneLocationId ?? room.LocationReplicationId, + Name = sceneRow?.Name ?? "Home", + IsSandbox = sceneRow?.IsSandbox ?? false, + DataBlobName = newBlob, + MaxPlayers = sceneRow?.MaxPlayers ?? 8, + CanMatchmakeInto = sceneRow?.CanMatchmakeInto ?? true, + DataModifiedAt = DateTime.UtcNow.ToString("yyyy-MM-ddTHH:mm:ssZ"), + }); + } + + public class SaveRoomSceneRequest + { + public long RoomSceneId { get; set; } + public string? RoomDataFilename { get; set; } + public List? InventionUsages { get; set; } + public CreatorActionContextDto? CreatorActionContext { get; set; } + public long RequestPlayerId { get; set; } + } + + public class CreatorActionContextDto + { + public bool IsTeachableMomentRunning { get; set; } + } + + /// POST api/rooms/v1/restore — switch a room back to a + /// previous save. Body: roomId, blobName (or version id). Updates + /// the player's DormStateEntity (for dorms) or RoomEntity + /// CurrentDataBlobName (for owned rooms) so the next visit loads + /// the historical bytes. + /// POST api/rooms/v1/datahistory/restore — restore a + /// room to a previous save. + /// + /// Request body: RestoreDataHistoryRequest serialized by + /// Unity's JsonUtility.ToJson, which uses C# field names. + /// Verified at Cpp2IL_ISIL/.../Rooms.txt:13513-14 — the only + /// field on the request type is the long history id at offset +16, + /// and Unity's serializer names it roomDataHistoryId. We + /// look up by that id, find its + /// parent room, and rewrite CurrentDataBlobName (and the + /// dorm-state row for personal dorms) so the next room reload + /// downloads the historical bytes. + /// + /// Response: CreateModifyRoomSceneResponse — keys are + /// PascalCase per Cpp2IL_ISIL/.../Rooms_NestedType_CreateModifyRoomSceneResponse.txt: + /// Result (int, CreateModifyRoomStatus: 0=Success), and + /// RoomScene (the restored scene). The watch's + /// ProcessCreateModifyRoomSceneResponse chain expects both; + /// returning a bare {restored: "..."} like the old impl makes + /// the watch's deserializer throw and the restore silently fails. + /// The legacy form-urlencoded path stays under + /// /api/rooms/v1/restore for the admin SPA's restore UI. + /// + public sealed class RestoreDataHistoryRequest + { + // Unity JsonUtility serializes C# private backing fields by + // their declared name. The watch's request type has a single + // field which Unity emits as "roomDataHistoryId" (camelCase) — + // [JsonPropertyName] here matches that wire key explicitly so + // System.Text.Json picks it up regardless of any future global + // naming policy. + [System.Text.Json.Serialization.JsonPropertyName("roomDataHistoryId")] + public long RoomDataHistoryId { get; set; } + } + + [HttpPost("api/rooms/v1/datahistory/restore")] + [Authorize] + public async Task RestoreDataHistory([FromBody] RestoreDataHistoryRequest body) + { + var pid = this.RequireCurrentPlayerId(); + var blob = await db.RoomDataBlobs.FirstOrDefaultAsync(b => b.Id == body.RoomDataHistoryId); + if (blob is null) return NotFound(new { error = "version_not_found" }); + + var room = await rooms.GetByIdAsync(blob.RoomId); + if (room is null) return NotFound(new { error = "room_not_found" }); + + // Ownership: only the dorm owner / room creator can restore. + // Admin override is intentionally NOT included here — restore + // is destructive (overrides the player's chosen state) so we + // require the actual creator. + if (room.CreatorPlayerId != pid) + return Forbid(); + + if (room.IsDormRoom) + { + var dormState = await db.DormStates.FirstOrDefaultAsync(d => d.PlayerId == pid); + if (dormState is null) + { + dormState = new DormStateEntity { PlayerId = pid }; + db.DormStates.Add(dormState); + } + dormState.CurrentDataBlobName = blob.BlobName; + dormState.UpdatedAt = DateTime.UtcNow; + } + await db.Rooms + .Where(r => r.Id == room.Id) + .ExecuteUpdateAsync(s => s + .SetProperty(r => r.CurrentDataBlobName, blob.BlobName) + .SetProperty(r => r.UpdatedAt, DateTime.UtcNow)); + await db.SaveChangesAsync(); + + // Push the same SubscriptionUpdateRoom the save flow sends so + // the watch's StoreRoomDetailsInCache → SafeRaiseRoomDetailsUpdated + // chain fires and any open room-detail panels refresh. + var pushRoom = room.IsDormRoom ? CloneWithCreator(room, pid, blob.BlobName) : CloneWithCreator(room, room.CreatorPlayerId, blob.BlobName); + var allScenes = await db.RoomScenes + .Where(s => s.RoomId == room.Id) + .OrderBy(s => s.OrderIndex) + .ToListAsync(); + var roomDetailsPayload = BuildRoomDetails(pushRoom, allScenes, 0L, blob.BlobName); + await notifications.NotifyAsync(pid, PushNotificationId.SubscriptionUpdateRoom, roomDetailsPayload); + + // CreateModifyRoomSceneResponse shape. RoomScene fields match + // RecNet.RoomScene.Deserialize (PascalCase, RoomSceneId, + // RoomId, RoomSceneLocationId, Name, IsSandbox, DataBlobName, + // MaxPlayers, CanMatchmakeInto, DataModifiedAt). + return Ok(new + { + Result = 0, // CreateModifyRoomStatus.Success + RoomScene = new + { + RoomSceneId = 0L, + RoomId = room.Id, + RoomSceneLocationId = room.LocationReplicationId, + Name = room.Name, + IsSandbox = false, + DataBlobName = blob.BlobName, + MaxPlayers = 8, + CanMatchmakeInto = true, + DataModifiedAt = DateTime.UtcNow.ToString("yyyy-MM-ddTHH:mm:ssZ"), + }, + }); + } + + /// Legacy form-urlencoded restore — kept for the admin SPA's + /// "Restore version" picker which posts {roomId, blobName} fields. + /// New callers (the watch) use the JSON variant above. + [HttpPost("api/rooms/v1/restore")] + [Authorize] + public async Task RestoreVersionLegacy( + [FromForm] long roomId, + [FromForm] string? blobName, + [FromForm] long version = 0) + { + var pid = this.RequireCurrentPlayerId(); + var room = await rooms.GetByIdAsync(roomId); + if (room is null) return NotFound(new { error = "room_not_found" }); + + var blob = !string.IsNullOrEmpty(blobName) + ? await db.RoomDataBlobs.FirstOrDefaultAsync(b => b.BlobName == blobName && b.RoomId == roomId) + : await db.RoomDataBlobs.FirstOrDefaultAsync(b => b.Id == version && b.RoomId == roomId); + if (blob is null) return NotFound(new { error = "version_not_found" }); + + if (room.CreatorPlayerId != pid) + return Forbid(); + + if (room.IsDormRoom) + { + var dormState = await db.DormStates.FirstOrDefaultAsync(d => d.PlayerId == pid); + if (dormState is null) + { + dormState = new DormStateEntity { PlayerId = pid }; + db.DormStates.Add(dormState); + } + dormState.CurrentDataBlobName = blob.BlobName; + dormState.UpdatedAt = DateTime.UtcNow; + } + await db.Rooms + .Where(r => r.Id == roomId) + .ExecuteUpdateAsync(s => s + .SetProperty(r => r.CurrentDataBlobName, blob.BlobName) + .SetProperty(r => r.UpdatedAt, DateTime.UtcNow)); + await db.SaveChangesAsync(); + return Ok(new { restored = blob.BlobName }); + } + + /// GET api/rooms/v{1,2}/personaldetails/{roomId} — + /// per-caller bookmark/cheer flags for one room. Wire shape verified + /// against RecNet.PersonalRoomDetails.Deserialize + /// (Cpp2IL_ISIL/.../PersonalRoomDetails.txt:85-91): + /// IsCheering - bool, REQUIRED (Util.GetKey, throws if missing) + /// IsBookmarked - bool, REQUIRED + /// Both keys are PascalCase. Returning the full room-details payload + /// here (which is what we used to do) makes the deserialiser throw + /// KeyNotFoundException for IsCheering, and that blocks the entire + /// /goto/* flow because RecNet.Rooms.GetPersonalRoomDetails is + /// awaited before SessionManager.JoinRoom can run. + [HttpGet("api/rooms/v2/personaldetails/{roomId:long}")] + [HttpGet("api/rooms/v1/personaldetails/{roomId:long}")] + public async Task PersonalDetails(long roomId) + { + var pid = CurrentPlayerId ?? 0; + bool cheering = false, bookmarked = false; + if (pid > 0) + { + cheering = await db.Cheers.AnyAsync(c => c.TargetRoomId == roomId && c.FromPlayerId == pid); + bookmarked = await db.RoomBookmarks.AnyAsync(b => b.RoomId == roomId && b.PlayerId == pid); + } + return Ok(new { IsCheering = cheering, IsBookmarked = bookmarked }); + } + + /// + /// GET /roomserver/rooms/{id}/interactionby/me — sibling of + /// personaldetails but DIFFERENT wire shape. Per + /// EJDCNGBEICB.txt:8219,8266 the deserialiser is + /// CJODCLDGFCF, which reads Cheered + Favorited + /// (PascalCase, both bool, both required — verified at + /// CJODCLDGFCF.txt:118,123). Sharing the + /// {IsCheering, IsBookmarked} payload with personaldetails + /// throws KeyNotFoundException on the watch and stalls the + /// room-tile UI. + /// + [HttpGet("roomserver/rooms/{roomId:long}/interactionby/me")] + [HttpGet("rooms/{roomId:long}/interactionby/me")] + public async Task RoomInteractionByMe(long roomId) + { + var pid = CurrentPlayerId ?? 0; + bool cheered = false, favorited = false; + if (pid > 0) + { + cheered = await db.Cheers.AnyAsync(c => c.TargetRoomId == roomId && c.FromPlayerId == pid); + favorited = await db.RoomBookmarks.AnyAsync(b => b.RoomId == roomId && b.PlayerId == pid); + } + return Ok(new { Cheered = cheered, Favorited = favorited }); + } + + /// + /// GET /roomserver/playlists/{id}/interactionby/me — same + /// CJODCLDGFCF wire shape as the room variant. Per + /// EJDCNGBEICB.txt:8600 the URL is + /// "playlists/{0}/interactionby/me". Reads from + /// ; an anonymous caller + /// (no JWT) reads as (false, false) so the watch's pre-login + /// browse renders cleanly. + /// + [HttpGet("roomserver/playlists/{playlistId:long}/interactionby/me")] + [HttpGet("playlists/{playlistId:long}/interactionby/me")] + public async Task PlaylistInteractionByMe(long playlistId) + { + var pid = CurrentPlayerId ?? 0; + if (pid <= 0) return Ok(new { Cheered = false, Favorited = false }); + var (cheered, favorited) = await playlists.InteractionForAsync(playlistId, pid); + return Ok(new { Cheered = cheered, Favorited = favorited }); + } + + [HttpGet("api/rooms/v4/details/{roomId:long}")] + [HttpGet("api/rooms/v3/details/{roomId:long}")] + [HttpGet("roomserver/rooms/{roomId:long}")] + public async Task Details(long roomId) + { + var r = await rooms.GetByIdAsync(roomId); + var room = r ?? Synthetic($"Room_{roomId}", roomId); + + // Dorm-specific shaping: each player has their own first-class + // dorm row. Only the dorm's actual creator should see Creator + // role / Maker Pen. Guests loading someone else's dorm must see + // the owner as CreatorPlayerId, while still receiving the + // owner's DormStateEntity blob so they load the same room state. + if (room.IsDormRoom) + { + var dormBlobName = await db.DormStates + .Where(d => d.PlayerId == room.CreatorPlayerId) + .Select(d => d.CurrentDataBlobName) + .FirstOrDefaultAsync() + ?? string.Empty; + room = CloneWithCreator(room, room.CreatorPlayerId, dormBlobName); + } + + var scenes = await db.RoomScenes + .Where(s => s.RoomId == room.Id) + .OrderBy(s => s.OrderIndex) + .ToListAsync(); + var roomRoles = await db.RoomRoles + .Where(r => r.RoomId == room.Id) + .ToListAsync(); + if (Request.Path.StartsWithSegments("/roomserver", StringComparison.OrdinalIgnoreCase)) + return Ok(BuildRoomServerDetails(room, scenes, roles: roomRoles)); + + return Ok(BuildRoomDetails(room, scenes, roles: roomRoles)); + } + + [HttpGet("roomserver/rooms")] + [HttpGet("rooms")] + public async Task RoomServerDetailsByName([FromQuery(Name = "name")] string? roomName) + { + if (string.IsNullOrWhiteSpace(roomName)) + return Ok(Array.Empty()); + + var pid = CurrentPlayerId ?? 0; + RoomEntity? room; + if (roomName.Equals("DormRoom", StringComparison.OrdinalIgnoreCase) && pid > 0) + { + room = await rooms.EnsurePersonalDormAsync(pid); + } + else + { + room = await rooms.GetByNameAsync(roomName); + } + + room ??= Synthetic(roomName); + if (room.IsDormRoom) + { + var dormBlobName = await db.DormStates + .Where(d => d.PlayerId == room.CreatorPlayerId) + .Select(d => d.CurrentDataBlobName) + .FirstOrDefaultAsync() + ?? string.Empty; + room = CloneWithCreator(room, room.CreatorPlayerId, dormBlobName); + } + + var scenes = await db.RoomScenes + .Where(s => s.RoomId == room.Id) + .OrderBy(s => s.OrderIndex) + .ToListAsync(); + var roomRoles = await db.RoomRoles + .Where(r => r.RoomId == room.Id) + .ToListAsync(); + + return Ok(BuildRoomServerDetails(room, scenes, roles: roomRoles)); + } + + /// GET /roomserver/rooms/bulk?name=X&name=Y — bulk + /// lookup by room name. Used by EJDCNGBEICB's room cache to resolve + /// well-known names (RecCenter, etc.) to their RoomServerDetails + /// shape in one round-trip. Returns the same per-room object that + /// /roomserver/rooms/{id} emits, one per requested name; names + /// we can't resolve are silently skipped (watch handles a shorter + /// list fine, but a 404 wedges the room browser). + [HttpGet("roomserver/rooms/bulk")] + [HttpGet("rooms/bulk")] + public async Task RoomServerBulkByName() + { + var names = Request.Query["name"].Where(n => !string.IsNullOrWhiteSpace(n)).ToArray(); + if (names.Length == 0) return Ok(Array.Empty()); + + var pid = CurrentPlayerId ?? 0; + var results = new List(names.Length); + foreach (var raw in names) + { + var name = raw!.Trim(); + RoomEntity? room; + if (name.Equals("DormRoom", StringComparison.OrdinalIgnoreCase) && pid > 0) + room = await rooms.EnsurePersonalDormAsync(pid); + else + room = await rooms.GetByNameAsync(name); + + room ??= Synthetic(name); + if (room.IsDormRoom) + { + var dormBlobName = await db.DormStates + .Where(d => d.PlayerId == room.CreatorPlayerId) + .Select(d => d.CurrentDataBlobName) + .FirstOrDefaultAsync() + ?? string.Empty; + room = CloneWithCreator(room, room.CreatorPlayerId, dormBlobName); + } + + var scenes = await db.RoomScenes + .Where(s => s.RoomId == room.Id) + .OrderBy(s => s.OrderIndex) + .ToListAsync(); + var roomRoles = await db.RoomRoles + .Where(r => r.RoomId == room.Id) + .ToListAsync(); + results.Add(BuildRoomServerDetails(room, scenes, roles: roomRoles)); + } + return Ok(results); + } + + /// + /// Shallow clone of a RoomEntity with a different CreatorPlayerId + /// and (optionally) a different CurrentDataBlobName. Used to give + /// the per-caller dorm the appearance of being authored by the + /// caller and pointing at the caller's own most recent save. + /// Doesn't touch the DB — purely a response-shaping utility. + /// + public static RoomEntity CloneWithCreator( + RoomEntity src, long creatorId, string? overrideBlobName = null) => new() + { + Id = src.Id, + Name = src.Name, + Description = src.Description, + CreatorPlayerId = creatorId, + ImageName = src.ImageName, + State = src.State, + Accessibility = src.Accessibility, + SupportsLevelVoting = src.SupportsLevelVoting, + IsAGRoom = src.IsAGRoom, + IsDormRoom = src.IsDormRoom, + CloningAllowed = src.CloningAllowed, + SupportsVRLow = src.SupportsVRLow, + SupportsMobile = src.SupportsMobile, + SupportsScreens = src.SupportsScreens, + SupportsWalkVR = src.SupportsWalkVR, + SupportsTeleportVR = src.SupportsTeleportVR, + AllowsJuniors = src.AllowsJuniors, + RoomWarningMask = src.RoomWarningMask, + CustomRoomWarning = src.CustomRoomWarning, + DisableMicAutoMute = src.DisableMicAutoMute, + LocationReplicationId = src.LocationReplicationId, + TagsCsv = src.TagsCsv, + CheerCount = src.CheerCount, + FavoriteCount = src.FavoriteCount, + VisitCount = src.VisitCount, + HotScore = src.HotScore, + CreatedAt = src.CreatedAt, + UpdatedAt = src.UpdatedAt, + CurrentDataBlobName = overrideBlobName ?? src.CurrentDataBlobName, + }; + + /// + /// Wire shape for the RoomDetails object — matches RoomDetails.Deserialize + /// at RVA 0x114C580. JSON key names DO NOT necessarily match the C# + /// property/field names from dump.cs — the deserializer is hand-written + /// and uses different (mostly PascalCase) literal strings. Verified + /// empirically: this exact shape gets past Deserialize successfully. + /// + public static object BuildRoomDetails( + RoomEntity room, + IReadOnlyList? sceneRows = null, + long? overrideSceneId = null, + string? overrideDataBlobName = null, + IReadOnlyList? roles = null) + { + // Tags is a list of OBJECTS, not strings. RoomDetails.Deserialize + // does Util.DeserializeList(tags) — each entry is parsed as + // a Dictionary with `Type` (int RoomTagType: 0=Auto, 1=PlayerAdded, + // 2=AG) and `Tag` (string). Sending bare strings crashes: + // InvalidCastException: Unable to cast 'String' to 'Dictionary`2' + var tags = string.IsNullOrEmpty(room.TagsCsv) + ? Array.Empty() + : room.TagsCsv + .Split(',', StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries) + .Select(t => (object)new { Type = 0, Tag = t }) + .ToArray(); + + // Scenes population: every room must produce a Scenes[0] entry + // with RoomSceneId=0 (matching the matchmaking SubRoomId=0) so + // that Rooms.set_LocalRoomDetails actually transitions + // Rooms.LocalRoomScene to the new room's scene reference. + // + // Why every room: the setter (verified in Cpp2IL ISIL of + // RecNet.Rooms.set_LocalRoomDetails) iterates Scenes looking + // for a RoomSceneId match; if none matches (Scenes=[] or + // mismatched), it falls through WITHOUT writing LocalRoomScene + // — so the field stays stale at whatever the previous room set. + // After the dorm fix landed, that stale value started leaking + // into the rec center: the dorm sets LocalRoomScene → user + // goes to rec center → if we send Scenes=[], LocalRoomScene + // STAYS pointing at the dorm scene → DownloadRoomDataBlob + // Coroutine reads scene.DataBlobName from the dorm and + // downloads room_1_v1.dat (smoking gun in the server log) + // during the rec center load. The rec center master init + // blocks → FROSTBITE. + // + // The DataBlobName field is what determines whether the + // persistence coroutine actually downloads anything. + // OnRoomDataBlobNameChanged (RoomPersistenceManager.txt ISIL + // {027-040}) short-circuits when DataBlobName is empty: it + // sets GetRoomDataBlobPromise to a CompletedPromise + // with empty bytes and returns immediately — no HTTP fetch, + // no DeserializeRoomDataBlobCoroutine, the master init + // proceeds with default-zero permissions. + // + // • Dorm / customisable rooms: DataBlobName is either the + // latest uploaded blob name (RoomEntity.CurrentDataBlobName, + // written by StorageController on /upload) or, before any + // save, "room__v1.dat" — which the catch-all serves + // from RoomDataBlobService (the all-perms default blob). + // • AG-Original rooms: DataBlobName="" → completed-promise + // short-circuit → no download → no leaked stale-blob + // behaviour → master flow proceeds → spawn fires. Maker + // Pen stays locked, which matches public-server semantics + // (you can't edit the rec center). + // + // Heuristic: "customisable" iff it's the dorm OR a user-created + // room (CreatorPlayerId != 1, the seeded system account that + // owns AG-Originals like the rec center). User clones inherit + // the all-perms default blob until their first save replaces + // it via StorageController; the rec center / paintball / etc. + // serve "" so the persistence flow short-circuits. + var customisable = room.IsDormRoom || room.CreatorPlayerId != 1; + var dataBlobName = !string.IsNullOrEmpty(room.CurrentDataBlobName) + ? room.CurrentDataBlobName + : customisable ? $"room_{room.Id}_v1.dat" : ""; + + // Multi-scene rooms (imported via tools/import-room.ps1) carry one + // RoomSceneEntity row per sub-room. Emit the full Scenes[] array + // when present so cross-scene portals (Lobby→Ch1, etc.) can + // resolve. Single-scene rooms (every legacy room, every dorm, + // every AG-Original) fall back to the synthesised Scenes[0] from + // RoomEntity fields. + object[] scenes; + if (sceneRows is { Count: > 0 }) + { + scenes = sceneRows + .Select(s => (object)new + { + RoomSceneId = (long)s.OrderIndex, + RoomId = room.Id, + RoomSceneLocationId = s.RoomSceneLocationId, + Name = s.Name, + IsSandbox = s.IsSandbox, + DataBlobName = !string.IsNullOrWhiteSpace(overrideDataBlobName) && + (overrideSceneId == s.OrderIndex || sceneRows.Count == 1) + ? overrideDataBlobName + : s.DataBlobName, + MaxPlayers = s.MaxPlayers, + CanMatchmakeInto = s.CanMatchmakeInto, + DataModifiedAt = (s.DataModifiedAt == default ? DateTime.UtcNow : s.DataModifiedAt) + .ToString("yyyy-MM-ddTHH:mm:ssZ"), + }) + .ToArray(); + } + else + { + scenes = new object[] + { + new + { + RoomSceneId = 0L, // MUST match RoomInstance.SubRoomId + RoomId = room.Id, + RoomSceneLocationId = room.LocationReplicationId, + Name = "Home", + IsSandbox = false, + DataBlobName = dataBlobName, + MaxPlayers = 8, + CanMatchmakeInto = true, + // Use the room's actual UpdatedAt so the watch's + // "is my local cache stale?" check sees an accurate + // freshness timestamp. Hardcoding the build date + // (2020-03-06) made the watch flag every dorm visit + // as "room is not up to date" because the cached + // local copy was always newer than the server's + // declared timestamp. + DataModifiedAt = (room.UpdatedAt == default ? DateTime.UtcNow : room.UpdatedAt) + .ToString("yyyy-MM-ddTHH:mm:ssZ"), + }, + }; + } + + // Owners tile (UIRoomDetails bottom-left) reads CoOwners — the + // 2020 RoomDetails.Deserialize reads keys WITHOUT the "Ids" + // suffix (verified at RoomDetails.txt:1096/1141/1146/1191/1196: + // "CoOwners" / "InvitedCoOwners" / "Moderators" / + // "InvitedModerators" / "Hosts" / "InvitedHosts"). The + // GetListKey calls are strict — wrong names throw + // KeyNotFoundException → "Failed to get room details: + // Malformed Response" → dorm load stalls in a redirect loop. + // + // CreatorPlayerId is always implicitly a CoOwner so RR Originals + // surface Coach (id=1) as the owner instead of rendering blank. + // Additional grants come from RoomRoleEntity rows when supplied; + // callers without role data (clone/myrooms/etc.) ship just the + // implicit creator. + var roleList = roles ?? Array.Empty(); + int[] PlayersIn(int role, bool accepted) => roleList + .Where(r => r.Role == role && r.Accepted == accepted) + .Select(r => (int)r.PlayerId) + .ToArray(); + var coOwnerSet = new HashSet { (int)room.CreatorPlayerId }; + foreach (var pid in PlayersIn(0, true)) coOwnerSet.Add(pid); + return new + { + Room = RoomService.ToWireRoom(room), + Scenes = scenes, + CoOwners = coOwnerSet.ToArray(), + InvitedCoOwners = PlayersIn(0, false), + Moderators = PlayersIn(1, true), + InvitedModerators = PlayersIn(1, false), + Hosts = PlayersIn(2, true), + InvitedHosts = PlayersIn(2, false), + CheerCount = room.CheerCount, + FavoriteCount = room.FavoriteCount, + VisitCount = room.VisitCount, + // VisitorCount is the official Rec.Net "unique visitors" + // companion field (see Stats payload at apim.rec.net). + // 2020 RoomDetails.Deserialize doesn't read it but leaving + // it on the wire is harmless and lets the admin UI / any + // future client surface the number. + VisitorCount = room.VisitorCount, + Tags = tags, + }; + } + + /// + /// 2020.12 /roomserver/rooms/{id}?include=301 shape. The client + /// deserializes this directly as PPENFJMFPNE, not as the older + /// { Room, Scenes, ... } wrapper. + /// + public static object BuildRoomServerDetails( + RoomEntity room, + IReadOnlyList? sceneRows = null, + string? overrideDataBlobName = null, + IReadOnlyList? roles = null) + { + var customisable = room.IsDormRoom || room.CreatorPlayerId != 1; + var dataBlobName = !string.IsNullOrEmpty(room.CurrentDataBlobName) + ? room.CurrentDataBlobName + : customisable ? $"room_{room.Id}_v1.dat" : ""; + var updatedAt = (room.UpdatedAt == default ? DateTime.UtcNow : room.UpdatedAt) + .ToString("yyyy-MM-ddTHH:mm:ssZ"); + + object[] subRooms = sceneRows is { Count: > 0 } + ? sceneRows.Select(s => (object)new + { + SubRoomId = (long)s.OrderIndex, + RoomId = room.Id, + Name = s.Name, + DataBlob = !string.IsNullOrWhiteSpace(overrideDataBlobName) && sceneRows.Count == 1 + ? overrideDataBlobName + : s.DataBlobName, + DataSavedAt = (s.DataModifiedAt == default ? DateTime.UtcNow : s.DataModifiedAt) + .ToString("yyyy-MM-ddTHH:mm:ssZ"), + IsSandbox = s.IsSandbox, + MaxPlayers = s.MaxPlayers, + Accessibility = room.Accessibility, + UnitySceneId = s.RoomSceneLocationId, + }).ToArray() + : new object[] + { + new + { + SubRoomId = 0L, + RoomId = room.Id, + Name = "Home", + DataBlob = dataBlobName, + DataSavedAt = updatedAt, + IsSandbox = false, + MaxPlayers = 8, + Accessibility = room.Accessibility, + UnitySceneId = room.LocationReplicationId, + }, + }; + + var roleList = roles ?? Array.Empty(); + static int WireRole(int role) => role switch + { + 0 => 30, // CoOwner + 1 => 20, // Moderator + 2 => 10, // Host + _ => 0, + }; + var wireRoles = new List + { + new { AccountId = room.CreatorPlayerId, Role = 255, InvitedRole = 0 }, + }; + wireRoles.AddRange(roleList.Select(r => + { + var role = WireRole(r.Role); + return (object)new + { + AccountId = r.PlayerId, + Role = r.Accepted ? role : 0, + InvitedRole = r.Accepted ? 0 : role, + }; + })); + + var tags = string.IsNullOrEmpty(room.TagsCsv) + ? Array.Empty() + : room.TagsCsv + .Split(',', StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries) + .Select(t => (object)new { Type = 0, Tag = t }) + .ToArray(); + + return new + { + RoomId = room.Id, + Name = room.Name, + Description = room.Description, + ImageName = room.ImageName, + WarningMask = room.RoomWarningMask, + CustomWarning = room.CustomRoomWarning, + CreatorAccountId = room.CreatorPlayerId, + State = room.State, + Accessibility = room.Accessibility, + SupportsLevelVoting = room.SupportsLevelVoting, + IsRRO = room.IsAGRoom, + SupportsScreens = room.SupportsScreens, + SupportsWalkVR = room.SupportsWalkVR, + SupportsTeleportVR = room.SupportsTeleportVR, + SupportsVRLow = room.SupportsVRLow, + SupportsQuest2 = true, + SupportsMobile = room.SupportsMobile, + SupportsJuniors = room.AllowsJuniors, + CreatedAt = (room.CreatedAt == default ? DateTime.UtcNow : room.CreatedAt) + .ToString("yyyy-MM-ddTHH:mm:ssZ"), + Stats = new + { + CheerCount = room.CheerCount, + FavoriteCount = room.FavoriteCount, + VisitorCount = room.VisitorCount, + VisitCount = room.VisitCount, + }, + IsDorm = room.IsDormRoom, + CloningAllowed = room.CloningAllowed, + DisableMicAutoMute = room.DisableMicAutoMute, + DisableRoomComments = false, + EncryptVoiceChat = false, + SubRooms = subRooms, + Roles = wireRoles, + LoadScreens = Array.Empty(), + PromoImages = Array.Empty(), + PromoExternalContent = Array.Empty(), + Tags = tags, + }; + } + + + // ── Clone / create ─────────────────────────────────────────────────── + + /// + /// `Rooms.CloneRoom(roomIdToClone, name)` — POST api/rooms/v1/clone + /// with body `{"Name":"","RoomId":}`. + /// + /// Response shape (CreateModifyRoomResponse.Deserialize, RVA 0xAF1950): + /// Result (int CreateModifyRoomStatus: 0=Success, 1=Unknown, + /// 2=PermissionDenied, 3=RoomNotActive, + /// 4=RoomDoesNotExist, 5=RoomHasNoDataBlob, …) + /// RoomDetails (RoomDetails object, only populated on Result=0) + /// + [HttpPost("api/rooms/v1/clone")] + [Authorize] + public async Task Clone([FromBody] CloneRoomRequest req) + { + var pid = CurrentPlayerId; + if (pid is null) return Unauthorized(); + + // CloneAsync sets the CreateModifyRoomStatus code itself + // (Success/RoomDoesNotExist/DuplicateName/InappropriateName) so the + // watch can show the right toast instead of a generic "Unknown". + var result = await rooms.CloneAsync(req.RoomId, req.Name ?? string.Empty, pid.Value); + return Ok(new + { + Result = result.Status, + RoomDetails = result.Room is null ? null : BuildRoomDetails(result.Room), + }); + } + + public class CloneRoomRequest + { + public string? Name { get; set; } + public long RoomId { get; set; } + } + + // ── My-Rooms tabs ──────────────────────────────────────────────────── + + // 2020.12 OJMCBOKJFOF populates its local rooms cache from these + // URLs at boot (verified via metadata strings: "rooms/createdby/me", + // "rooms/moderatedby/me", "rooms/visitedby/me"). The 2020.03 client + // used /api/rooms/v2/myrooms, while 2020.12 moved the calls under + // /roomserver/rooms/*. Keep both route families on the same handlers. + [HttpGet("api/rooms/v2/myrooms")] + [HttpGet("api/rooms/v1/createdby/me")] + [HttpGet("api/rooms/v2/createdby/me")] + [HttpGet("api/rooms/v3/createdby/me")] + [HttpGet("roomserver/rooms/createdby/me")] + [HttpGet("roomserver/rooms/ownedby/me")] + [HttpGet("rooms/createdby/me")] + [HttpGet("rooms/ownedby/me")] + [Authorize] + public async Task MyRooms() + { + var pid = CurrentPlayerId; + if (pid is null) return Ok(Array.Empty()); + return Ok((await rooms.CreatedByAsync(pid.Value)).Select(RoomService.ToWireRoom).ToList()); + } + + [HttpGet("api/rooms/v1/createdby/{otherPlayerId:long}")] + [HttpGet("api/rooms/v2/createdby/{otherPlayerId:long}")] + [HttpGet("api/rooms/v3/createdby/{otherPlayerId:long}")] + [HttpGet("roomserver/rooms/createdby/{otherPlayerId:long}")] + [HttpGet("rooms/createdby/{otherPlayerId:long}")] + public async Task CreatedByOther(long otherPlayerId) + => Ok((await rooms.CreatedByAsync(otherPlayerId)).Select(RoomService.ToWireRoom).ToList()); + + [HttpGet("api/rooms/v1/visitedby/me")] + [HttpGet("api/rooms/v2/visitedby/me")] + [HttpGet("api/rooms/v3/visitedby/me")] + [HttpGet("roomserver/rooms/visitedby/me")] + [HttpGet("rooms/visitedby/me")] + [Authorize] + public IActionResult VisitedByMe() => Ok(Array.Empty()); + + [HttpGet("api/rooms/v1/moderatedby/me")] + [HttpGet("api/rooms/v2/moderatedby/me")] + [HttpGet("api/rooms/v3/moderatedby/me")] + [HttpGet("roomserver/rooms/moderatedby/me")] + [HttpGet("rooms/moderatedby/me")] + [Authorize] + public IActionResult ModeratedByMe() => Ok(Array.Empty()); + + [HttpGet("api/rooms/v1/favoritedby/me")] + [HttpGet("api/rooms/v2/favoritedby/me")] + [HttpGet("api/rooms/v3/favoritedby/me")] + [HttpGet("roomserver/rooms/favoritedby/me")] + [HttpGet("rooms/favoritedby/me")] + [Authorize] + public IActionResult FavoritedByMe() => Ok(Array.Empty()); + + [HttpGet("api/rooms/v1/cheeredby/me")] + [HttpGet("api/rooms/v2/cheeredby/me")] + [HttpGet("api/rooms/v3/cheeredby/me")] + [HttpGet("roomserver/rooms/cheeredby/me")] + [HttpGet("rooms/cheeredby/me")] + [Authorize] + public IActionResult CheeredByMe() => Ok(Array.Empty()); + + [HttpGet("api/rooms/v2/mybookmarks")] + [HttpGet("api/rooms/v2/mybookmarkedrooms")] + [Authorize] + public async Task MyBookmarks() + { + var pid = CurrentPlayerId; + if (pid is null) return Ok(Array.Empty()); + return Ok((await rooms.BookmarkedByAsync(pid.Value)).Select(RoomService.ToWireRoom).ToList()); + } + + [HttpGet("api/rooms/v2/mymoderated")] + [HttpGet("api/rooms/v1/modrooms")] + [HttpGet("api/rooms/v2/mysubscribed")] + [HttpGet("api/rooms/v2/myrecent")] + public IActionResult MyOtherTabs() => Ok(Array.Empty()); + + // ── Bookmark mutations ─────────────────────────────────────────────── + + [HttpPost("api/rooms/v1/bookmark/{roomId:long}")] + [HttpPost("api/rooms/v2/bookmark/{roomId:long}")] + [Authorize] + public async Task Bookmark(long roomId) + { + var pid = CurrentPlayerId; + if (pid is null) return Unauthorized(); + await rooms.BookmarkAsync(pid.Value, roomId); + return Ok(new { success = true, error = "" }); + } + + [HttpDelete("api/rooms/v1/bookmark/{roomId:long}")] + [HttpDelete("api/rooms/v2/bookmark/{roomId:long}")] + [Authorize] + public async Task Unbookmark(long roomId) + { + var pid = CurrentPlayerId; + if (pid is null) return Unauthorized(); + await rooms.UnbookmarkAsync(pid.Value, roomId); + return Ok(new { success = true, error = "" }); + } + + // ── Helpers ────────────────────────────────────────────────────────── + + /// GET api/rooms/v1/agRoomIds — list of every + /// seeded AG / RR-Original room id. Used by the watch to bias the + /// room browser toward official rooms first. + [HttpGet("api/rooms/v1/agRoomIds")] + [HttpGet("rooms/rro_ids")] + public async Task AgRoomIds() => + Ok(await rooms.AgRoomIdsAsync()); + + /// GET api/rooms/v1/featuredRoomGroup + + /// v3/featured — the "Featured" carousel on the watch's + /// rooms tab. Returns the top-12 AG rooms by HotScore. + [HttpGet("api/rooms/v1/featuredRoomGroup")] + [HttpGet("api/rooms/v3/featured")] + public async Task Featured() => Ok(new + { + Name = "Featured", + FeaturedRooms = await rooms.FeaturedAgRoomIdsAsync(12), + }); + + /// GET featuredrooms/current — the watch's pointer + /// to the current "Featured" room group. Caller: + /// EJDCNGBEICB.CFKDADKHAGB. Wire shape is NMPFCIJPODA + /// with required keys FeaturedRoomGroupId (long) and + /// Name (string) — a pointer record, NOT the room list + /// itself (the watch fetches the list separately via + /// api/rooms/v3/featured). Returning the + /// shape here throws KeyNotFoundException on + /// the watch's group-id lookup. + [HttpGet("featuredrooms/current")] + public async Task FeaturedRoomsCurrent() => Ok(new + { + FeaturedRoomGroupId = 1L, + Name = "Featured", + // NMPFCIJPODA.PPGFHEDFBEA (NMPFCIJPODA.txt:100-125) reads + // 3 strict keys, not 2 — FeaturedRoomGroupId, Name, AND + // Rooms (List). Returning without the third + // key throws KeyNotFoundException on the watch and the + // Featured carousel stays empty. + Rooms = await rooms.FeaturedAgRoomIdsAsync(12), + }); + + /// POST api/rooms/v1/bookmark — toggle bookmark + /// state for a room. Body: RoomId + optional Bookmark + /// flag (default = add). + [HttpPost("api/rooms/v1/bookmark")] + [Authorize] + public async Task BookmarkV1( + [FromForm(Name = "RoomId")] long? roomId, + [FromForm(Name = "Bookmark")] bool? bookmark) + { + if (roomId is not long rid) return Ok(new { success = true }); + var pid = this.RequireCurrentPlayerId(); + if (bookmark == false) await rooms.UnbookmarkAsync(pid, rid); + else await rooms.BookmarkAsync(pid, rid); + return Ok(new { success = true, error = "" }); + } + + /// POST rooms/{roomId}/name — rename a room. + /// Watch path: EJDCNGBEICB.DNOGGKKHMHI(long roomId, string newName) + /// (analytics tag "change room name"). Body shape: form-urlencoded + /// Name=... or a bare string in the body. Returns the + /// updated Room DTO (PPENFJMFPNE : KLCOGEIGEBJ). + /// + /// Caller must be the room creator OR a co-owner. Other + /// validations: name length 1..40, no leading/trailing whitespace, + /// uniqueness against other Rooms.Name. Failures return + /// the watch's CreateModifyRoomStatus-style payload with + /// a non-zero Result so the rename toast surfaces a real reason. + /// + /// We accept both POST and PUT because the watch's ISIL + /// doesn't expose the explicit HTTPMethod literal at this call + /// site — covering both is cheap and means we can't get bitten + /// by the wrong one. + public sealed class RenameBody { public string? Name { get; set; } } + + [HttpPost("rooms/{roomId:long}/name")] + [HttpPut("rooms/{roomId:long}/name")] + [Authorize] + public async Task RenameRoom( + long roomId, + [FromForm(Name = "Name")] string? nameForm, + [FromForm(Name = "name")] string? nameFormLower, + [FromBody] RenameBody? body) + { + var newName = nameForm ?? nameFormLower ?? body?.Name; + if (string.IsNullOrWhiteSpace(newName)) + return BadRequest(new { Result = 1, error = "empty_name" }); + newName = newName.Trim(); + if (newName.Length is < 1 or > 40) + return BadRequest(new { Result = 1, error = "invalid_length", length = newName.Length }); + + var me = this.RequireCurrentPlayerId(); + var room = await db.Rooms.FirstOrDefaultAsync(r => r.Id == roomId); + if (room is null) return NotFound(new { Result = 4, error = "room_not_found" }); + if (room.CreatorPlayerId != me) + { + // RoomRoles co-owner check — same authorisation as the + // existing modify endpoint, lets co-owners rename too. + var coOwner = await db.RoomRoles.AnyAsync(rr => + rr.RoomId == roomId && rr.PlayerId == me && rr.Accepted && (rr.Role == 0 || rr.Role == 1)); + if (!coOwner) return Forbid(); + } + + // Uniqueness — Rooms.Name has a unique index (DorkNetDbContext.cs:118). + var collision = await db.Rooms.AnyAsync(r => r.Id != roomId && r.Name == newName); + if (collision) return Conflict(new { Result = 5, error = "name_taken" }); + + room.Name = newName; + room.UpdatedAt = DateTime.UtcNow; + await db.SaveChangesAsync(); + logger.LogInformation("[room-rename] room={Room} by={By} → '{Name}'", roomId, me, newName); + + // Return the full updated Room DTO. The watch's + // PPENFJMFPNE deserialiser inherits from + // KLCOGEIGEBJ (the standard Room shape) so we can + // forward to the same v3-details payload builder by re-reading. + // BuildRoomDetailsV3Async is too heavy here — just return the + // minimal mutated subset; the watch UI re-fetches details + // shortly after for the room-info screen. + return Ok(new + { + Result = 0, + RoomId = room.Id, + Name = room.Name, + Description = room.Description, + ImageName = room.ImageName, + CreatorPlayerId = room.CreatorPlayerId, + CreatorAccountId = room.CreatorPlayerId, + State = room.State, + Accessibility = room.Accessibility, + IsAGRoom = room.IsAGRoom, + IsRRO = false, + IsDormRoom = room.IsDormRoom, + }); + } + + /// POST rooms/{roomId}/subrooms/{subRoomId}/name — + /// rename a scene/sub-room within a multi-scene room. Watch path: + /// EJDCNGBEICB.PAENPJCDLNJ(long roomId, long subRoomId, string newName). + /// Updates the matching after + /// verifying ownership and intra-room uniqueness. + [HttpPost("rooms/{roomId:long}/subrooms/{subRoomId:long}/name")] + [HttpPut("rooms/{roomId:long}/subrooms/{subRoomId:long}/name")] + [Authorize] + public async Task RenameSubroom( + long roomId, + long subRoomId, + [FromForm(Name = "Name")] string? nameForm, + [FromForm(Name = "name")] string? nameFormLower, + [FromBody] RenameBody? body) + { + var newName = nameForm ?? nameFormLower ?? body?.Name; + if (string.IsNullOrWhiteSpace(newName)) + return BadRequest(new { Result = 1, error = "empty_name" }); + newName = newName.Trim(); + if (newName.Length is < 1 or > 40) + return BadRequest(new { Result = 1, error = "invalid_length", length = newName.Length }); + + var me = this.RequireCurrentPlayerId(); + var room = await db.Rooms.FirstOrDefaultAsync(r => r.Id == roomId); + if (room is null) return NotFound(new { Result = 4, error = "room_not_found" }); + if (room.CreatorPlayerId != me) + { + var coOwner = await db.RoomRoles.AnyAsync(rr => + rr.RoomId == roomId && rr.PlayerId == me && rr.Accepted && (rr.Role == 0 || rr.Role == 1)); + if (!coOwner) return Forbid(); + } + + var scene = await db.RoomScenes.FirstOrDefaultAsync(s => + s.RoomId == roomId && s.OrderIndex == subRoomId); + if (scene is null) return NotFound(new { Result = 4, error = "subroom_not_found", subRoomId }); + + // Uniqueness within this room — same scene name twice would + // make /goto/room/X/Y ambiguous against the RoomScenes index + // on (RoomId, Name) at DorkNetDbContext.cs:153. + var collision = await db.RoomScenes.AnyAsync(s => + s.RoomId == roomId && s.OrderIndex != subRoomId && s.Name == newName); + if (collision) return Conflict(new { Result = 5, error = "subroom_name_taken" }); + + scene.Name = newName; + scene.DataModifiedAt = DateTime.UtcNow; + await db.SaveChangesAsync(); + logger.LogInformation("[subroom-rename] room={Room} sub={Sub} by={By} → '{Name}'", roomId, subRoomId, me, newName); + + return Ok(new + { + Result = 0, + RoomId = roomId, + RoomSceneId = scene.OrderIndex, + SubRoomId = scene.OrderIndex, + Name = scene.Name, + scene.DataBlobName, + RoomSceneLocationId = scene.RoomSceneLocationId, + scene.IsSandbox, + scene.MaxPlayers, + scene.CanMatchmakeInto, + }); + } + + /// POST api/rooms/v1/cheer — cheer a room (body + /// form). Idempotent per (caller, room) — duplicate taps don't + /// inflate CheerCount. + [HttpPost("api/rooms/v1/cheer")] + [Authorize] + public async Task CheerRoom( + [FromForm(Name = "RoomId")] long? roomId, + [FromForm(Name = "Type")] int? type) + { + if (roomId is not long rid) return Ok(new { success = true }); + var me = this.RequireCurrentPlayerId(); + var (newCount, already) = await rooms.CheerRoomAsync(me, rid, type ?? 0); + return Ok(new { RoomId = rid, CheerCount = newCount, AlreadyCheered = already }); + } + + /// POST api/rooms/v1/uncheer — remove a previously + /// applied room cheer. + [HttpPost("api/rooms/v1/uncheer")] + [HttpPost("api/rooms/v1/uncheer/{roomId:long}")] + [Authorize] + public async Task UncheerRoom( + long? roomId, + [FromForm(Name = "RoomId")] long? roomIdForm, + [FromForm(Name = "Type")] int? type) + { + var rid = roomId ?? roomIdForm ?? 0; + if (rid == 0) return Ok(new { success = true }); + var me = this.RequireCurrentPlayerId(); + var already = await rooms.UncheerRoomAsync(me, rid, type ?? 0); + return Ok(new { RoomId = rid, AlreadyUncheered = already }); + } + + /// GET api/rooms/v1/cheers/{roomId} — did the + /// caller cheer this room, and what's the current count? + [HttpGet("api/rooms/v1/cheers/{roomId:long}")] + [Authorize] + public async Task CheckCheered(long roomId) + { + var me = this.CurrentPlayerId(); + var (count, iCheered) = await rooms.GetCheerStateAsync(me ?? 0, roomId); + return Ok(new { RoomId = roomId, CheerCount = count, ICheered = iCheered }); + } + + // ── Bare-path interactionby/me toggles ───────────────────────────── + + [HttpPost("rooms/{roomId:long}/interactionby/me/cheer")] + [HttpPut("rooms/{roomId:long}/interactionby/me/cheer")] + [Authorize] + public async Task BareCheer(long roomId) + { + var me = this.RequireCurrentPlayerId(); + await rooms.CheerRoomAsync(me, roomId, 0); + return Ok(new { success = true }); + } + + [HttpDelete("rooms/{roomId:long}/interactionby/me/cheer")] + [Authorize] + public async Task BareUncheer(long roomId) + { + var me = this.RequireCurrentPlayerId(); + await rooms.UncheerRoomAsync(me, roomId, 0); + return Ok(new { success = true }); + } + + [HttpPost("rooms/{roomId:long}/interactionby/me/favorite")] + [HttpPut("rooms/{roomId:long}/interactionby/me/favorite")] + [Authorize] + public async Task BareFavorite(long roomId) + { + var me = this.RequireCurrentPlayerId(); + await rooms.BookmarkAsync(me, roomId); + return Ok(new { success = true }); + } + + [HttpDelete("rooms/{roomId:long}/interactionby/me/favorite")] + [Authorize] + public async Task BareUnfavorite(long roomId) + { + var me = this.RequireCurrentPlayerId(); + await rooms.UnbookmarkAsync(me, roomId); + return Ok(new { success = true }); + } + + // ── Rooms-and-Playlists union (2020.12 watch) ──────────────────────── + + /// + /// GET /roomserver/roomsandplaylists/hot — the 2020.12 watch's + /// merged "Trending" / "New" tab. Two URL variants exist with + /// DIFFERENT wire shapes: + /// + /// 1. /roomserver/roomsandplaylists/hot (this endpoint) + /// Watch caller: EJDCNGBEICB.EBBHGHEGBKG + /// Signature: IPromise<HJKAOMOICJG> + /// Wire shape: { TotalResults, Results: [union] } + /// (verified at EJDCNGBEICB.txt:2571,2645 + + /// HJKAOMOICJG.txt:39-50) + /// 2. /roomserver/hot_roomsandplaylists/{tags-joined} + /// Watch caller: OJMCBOKJFOF.EBBHGHEGBKG + /// Signature: IPromise<List<MKAMHOIHOJK>> + /// Wire shape: bare list, see + /// (verified at OJMCBOKJFOF.txt:4607,4743) + /// + /// Returning a bare list here throws + /// InvalidCastException: Unable to cast object of type 'List`1' + /// to type 'Dictionary`2' on the watch — the wrapped + /// HJKAOMOICJG deserializer reads TotalResults as a top-level + /// dict key, and a JSON array body fails that cast. + /// + /// Inside Results: union entries discriminated by lowercase + /// roomId key per the MKAMHOIHOJK factory at + /// MKAMHOIHOJK.txt:621 — see + /// + . + /// + [HttpGet("roomserver/roomsandplaylists/hot")] + [HttpGet("roomsandplaylists/hot")] + public async Task RoomsAndPlaylistsHot([FromQuery] string? tag) + { + var (merged, total) = await BuildHotUnionAsync(tag); + return Ok(new Dictionary(StringComparer.Ordinal) + { + ["TotalResults"] = (long)total, + ["Results"] = merged, + }); + } + + /// + /// GET /roomserver/hot_roomsandplaylists/{tagsJoined} — + /// path-style sibling of with a + /// BARE List<MKAMHOIHOJK> shape (no TotalResults + /// wrapper). Tags are comma-joined into the URL path per + /// OJMCBOKJFOF.txt:4739,4743 (String.Join(",", tags) + /// followed by String.Concat("hot_roomsandplaylists/", joined)). + /// Empty path is legal — equivalent to "no tag filter". + /// + [HttpGet("roomserver/hot_roomsandplaylists/{tagsJoined?}")] + [HttpGet("hot_roomsandplaylists/{tagsJoined?}")] + public async Task HotRoomsAndPlaylistsPath(string? tagsJoined) + { + // Watch joins multiple tags with "," — use the first one as a + // substring filter since RoomService.HotAsync takes a single + // tag arg. Empty / null = unfiltered. + var firstTag = string.IsNullOrWhiteSpace(tagsJoined) + ? null + : tagsJoined.Split(',', StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries) + .FirstOrDefault(); + var (merged, _) = await BuildHotUnionAsync(firstTag); + return Ok(merged); + } + + /// GET hot_rooms/{tagsJoined?} — rooms-only variant + /// of . Caller: + /// OJMCBOKJFOF.DLPLPKCNLNA. Same MKAMHOIHOJK union + /// entry shape as hot_roomsandplaylists, just skips playlist + /// rows so the watch's rooms-only browse tab doesn't render + /// playlist tiles that have nowhere to go. + [HttpGet("hot_rooms/{tagsJoined?}")] + public async Task HotRoomsOnlyPath(string? tagsJoined) + { + var firstTag = string.IsNullOrWhiteSpace(tagsJoined) + ? null + : tagsJoined.Split(',', StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries) + .FirstOrDefault(); + var roomList = await rooms.HotAsync(firstTag, take: 12); + var roomIds = roomList.Select(r => r.Id).ToList(); + var sceneRows = roomIds.Count == 0 + ? new List() + : await db.RoomScenes.Where(s => roomIds.Contains(s.RoomId)) + .OrderBy(s => s.OrderIndex).ToListAsync(); + var scenesByRoom = sceneRows.GroupBy(s => s.RoomId) + .ToDictionary(g => g.Key, g => (IReadOnlyList)g.ToList()); + return Ok(roomList + .Select(r => BuildRoomUnionEntry(r, scenesByRoom.GetValueOrDefault(r.Id))) + .ToList()); + } + + /// + /// Shared body for the two hot variants — pulls hot rooms + + /// playlists, fans out sub-room rows in one query to avoid N+1, + /// and interleaves into the union shape the watch's factory + /// expects. Returns (entries, totalCount) so the wrapped variant + /// can fill in TotalResults. + /// + private async Task<(List> Entries, int Total)> BuildHotUnionAsync(string? tag) + { + // Sequential — both services share the scoped DbContext, which + // is not thread-safe (Task.WhenAll here throws "A second + // operation was started on this context instance before a + // previous operation completed"). + var roomList = await rooms.HotAsync(tag, take: 12); + var playlistList = await playlists.HotAsync(tag, take: 12); + + var roomIds = roomList.Select(r => r.Id).ToList(); + var sceneRows = await db.RoomScenes + .Where(s => roomIds.Contains(s.RoomId)) + .OrderBy(s => s.OrderIndex) + .ToListAsync(); + var scenesByRoom = sceneRows + .GroupBy(s => s.RoomId) + .ToDictionary(g => g.Key, g => (IReadOnlyList)g.ToList()); + + var merged = InterleaveUnion( + roomList.Select(r => BuildRoomUnionEntry(r, scenesByRoom.GetValueOrDefault(r.Id))), + playlistList.Select(BuildPlaylistUnionEntry)); + return (merged, roomList.Count + playlistList.Count); + } + + /// + /// GET /api/curatedroomplaylists — the editorial "Curated" + /// row on the watch's playlists tab. + /// + /// Wire shape: List<Int64> — JUST the playlist IDs, not + /// the full playlist objects. Verified against + /// FPNPBJBCMKB.EPPPMHMMGME (signature + /// IPromise<List<long>>) and the call site at + /// BrowseRoomsScreen.txt:2806 which constructs an + /// Action<List<Int64>> callback. Returning rich + /// objects here throws + /// InvalidCastException: Unable to cast object of type 'List`1' + /// to type 'Dictionary`2' on the watch ("Failed to get curated + /// room playlists: Malformed Response"). The watch fetches each + /// playlist's details separately via the playlist-detail endpoints. + /// + [HttpGet("api/curatedroomplaylists")] + public async Task CuratedRoomPlaylists() + { + var curated = await playlists.CuratedAsync(); + return Ok(curated.Select(p => p.Id).ToList()); + } + + /// + /// GET /roomserver/playlists/{id} — single-playlist details + /// with members. The watch deserializes this as BMFAGMFKODA + /// (verified at BMFAGMFKODA.txt:182-191) which: + /// 1. Calls KMKPEOGJDFK.PPGFHEDFBEA to read the base + /// Playlist union fields (PlaylistId + all + /// MKAMHOIHOJK base keys). + /// 2. Reads required key Rooms — List<KLCOGEIGEBJ> + /// (the playlist's member rooms, full Room dicts). + /// 3. Reads required key Tags — List<DPHPFLGAICI> + /// (tag rows with Type + Tag). + /// Returning just the KMKPEOGJDFK shape (no Rooms/Tags) throws + /// KeyNotFoundException: Failed to find key 'Rooms' when + /// deserializing object of type KLCOGEIGEBJ on the watch. + /// + [HttpGet("roomserver/playlists/{id:long}")] + [HttpGet("playlists/{id:long}")] + public async Task PlaylistDetails(long id) + { + var p = await db.Playlists.AsNoTracking().FirstOrDefaultAsync(x => x.Id == id); + if (p is null) return NotFound(); + + // Pull member rooms in playlist order, then fan out room + + // scene rows so each entry can render as a full KLCOGEIGEBJ. + var roomIds = await db.PlaylistRooms + .Where(pr => pr.PlaylistId == id) + .OrderBy(pr => pr.OrderIndex) + .Select(pr => pr.RoomId) + .ToListAsync(); + + List memberRooms; + Dictionary> scenesByRoom; + if (roomIds.Count == 0) + { + memberRooms = new List(); + scenesByRoom = new Dictionary>(); + } + else + { + var roomRows = await db.Rooms + .Where(r => roomIds.Contains(r.Id)) + .ToListAsync(); + // Preserve playlist order — db.Rooms.Where doesn't guarantee + // it matches the OrderBy on PlaylistRooms.OrderIndex. + var roomById = roomRows.ToDictionary(r => r.Id); + memberRooms = roomIds + .Select(rid => roomById.TryGetValue(rid, out var r) ? r : null) + .Where(r => r is not null) + .Select(r => r!) + .ToList(); + + var sceneRows = await db.RoomScenes + .Where(s => roomIds.Contains(s.RoomId)) + .OrderBy(s => s.OrderIndex) + .ToListAsync(); + scenesByRoom = sceneRows + .GroupBy(s => s.RoomId) + .ToDictionary(g => g.Key, g => (IReadOnlyList)g.ToList()); + } + + var roomsWire = memberRooms + .Select(r => BuildRoomServerDetails(r, scenesByRoom.GetValueOrDefault(r.Id))) + .ToList(); + + // Tags wire shape: [{Type:int, Tag:string}] — same shape used in + // BuildRoomServerDetails for Room.Tags. Split TagsCsv if present. + var tagsWire = string.IsNullOrWhiteSpace(p.TagsCsv) + ? new List() + : p.TagsCsv + .Split(',', StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries) + .Select(t => (object)new { Type = 0, Tag = t }) + .ToList(); + + // Start from BuildPlaylistUnionEntry for base + PlaylistId, then + // splice in the Rooms + Tags lists. Dictionary lets us add the + // extra keys after the base build. + var result = BuildPlaylistUnionEntry(p); + result["Rooms"] = roomsWire; + result["Tags"] = tagsWire; + return Ok(result); + } + + /// + /// GET /roomserver/roomsandplaylists/search?query=X — search + /// surface used by the watch's friend-mention / room-search affordance. + /// Verified at EJDCNGBEICB.txt:2486,2560 — signature + /// IPromise<HJKAOMOICJG>. Per + /// at HJKAOMOICJG.txt:39-50 + /// the response object reads TotalResults (Int64) and + /// Results (List of MKAMHOIHOJK union entries — same + /// Room/Playlist factory used by /hot). + /// + /// Distinct from /roomserver/search_roomsandplaylists/{query} + /// (path-param shape from OJMCBOKJFOF.txt:4567); both URLs + /// reach the same data but differ in shape — the path-param sibling + /// returns a bare List<MKAMHOIHOJK> without the + /// TotalResults wrapper. + /// + [HttpGet("roomserver/roomsandplaylists/search")] + [HttpGet("roomsandplaylists/search")] + public async Task RoomsAndPlaylistsSearch([FromQuery] string? query) + { + var (merged, total) = await BuildSearchUnionAsync(query); + return Ok(new Dictionary(StringComparer.Ordinal) + { + ["TotalResults"] = (long)total, + ["Results"] = merged, + }); + } + + /// + /// GET /roomserver/search_roomsandplaylists/{query} — free-text + /// search across both rooms and playlists, BARE-LIST variant. Per + /// OJMCBOKJFOF.txt:4439,4567 the signature is + /// IPromise<List<MKAMHOIHOJK>> — no TotalResults + /// wrapper. URL is search_roomsandplaylists/{query} + /// (query in path, not query-string). + /// + [HttpGet("roomserver/search_roomsandplaylists/{query}")] + [HttpGet("search_roomsandplaylists/{query}")] + public async Task SearchRoomsAndPlaylists(string query) + { + var (merged, _) = await BuildSearchUnionAsync(query); + return Ok(merged); + } + + /// GET search_rooms/{query} — rooms-only search + /// variant. Same MKAMHOIHOJK union entry shape as + /// search_roomsandplaylists/{query}, just skips playlist + /// rows. + [HttpGet("search_rooms/{query}")] + public async Task SearchRoomsOnly(string query) + { + if (string.IsNullOrWhiteSpace(query)) + return Ok(Array.Empty()); + var roomList = await rooms.SearchAsync(query); + var roomIds = roomList.Select(r => r.Id).ToList(); + var sceneRows = roomIds.Count == 0 + ? new List() + : await db.RoomScenes.Where(s => roomIds.Contains(s.RoomId)) + .OrderBy(s => s.OrderIndex).ToListAsync(); + var scenesByRoom = sceneRows.GroupBy(s => s.RoomId) + .ToDictionary(g => g.Key, g => (IReadOnlyList)g.ToList()); + return Ok(roomList + .Select(r => BuildRoomUnionEntry(r, scenesByRoom.GetValueOrDefault(r.Id))) + .ToList()); + } + + /// + /// Shared body for the two search variants. Empty/whitespace query + /// returns an empty result without round-tripping the DB. + /// + private async Task<(List> Entries, int Total)> BuildSearchUnionAsync(string? query) + { + if (string.IsNullOrWhiteSpace(query)) + return (new List>(), 0); + + // Sequential — shared scoped DbContext isn't thread-safe. + var roomList = await rooms.SearchAsync(query); + var playlistList = await playlists.SearchAsync(query); + + var roomIds = roomList.Select(r => r.Id).ToList(); + var sceneRows = await db.RoomScenes + .Where(s => roomIds.Contains(s.RoomId)) + .OrderBy(s => s.OrderIndex) + .ToListAsync(); + var scenesByRoom = sceneRows + .GroupBy(s => s.RoomId) + .ToDictionary(g => g.Key, g => (IReadOnlyList)g.ToList()); + + var merged = InterleaveUnion( + roomList.Select(r => BuildRoomUnionEntry(r, scenesByRoom.GetValueOrDefault(r.Id))), + playlistList.Select(BuildPlaylistUnionEntry)); + return (merged, roomList.Count + playlistList.Count); + } + + /// Round-robin merge of two enumerables so the watch's + /// hot row alternates between rooms and playlists rather than + /// stacking one type on top. Once one side runs out the remainder + /// of the other side spills out in order. + private static List> InterleaveUnion( + IEnumerable> a, + IEnumerable> b) + { + var listA = a.ToList(); + var listB = b.ToList(); + var output = new List>(listA.Count + listB.Count); + int i = 0, j = 0; + while (i < listA.Count || j < listB.Count) + { + if (i < listA.Count) output.Add(listA[i++]); + if (j < listB.Count) output.Add(listB[j++]); + } + return output; + } + + /// + /// Build a union-shape Room entry — same payload as + /// but with the additional + /// lowercase roomId key that the 2020.12 + /// MKAMHOIHOJK factory uses as its Room-vs-Playlist + /// discriminator (factory at MKAMHOIHOJK.txt:621 calls + /// Dictionary.ContainsKey("roomId"); the actual Room + /// deserializer at KLCOGEIGEBJ.txt:208-234 reads + /// PascalCase RoomId). Must include BOTH keys. + /// + private static IDictionary BuildRoomUnionEntry( + RoomEntity room, + IReadOnlyList? sceneRows) + { + // Start from the canonical RoomServerDetails shape so the union + // entry carries every field the 2020.12 client expects on a + // Room subclass (SubRooms, Roles, Stats, etc.). Then add the + // lowercase 'roomId' discriminator key on top. + var details = BuildRoomServerDetails(room, sceneRows); + // Convert anonymous record into a mutable dictionary so we can + // add the lowercase 'roomId' key that anonymous types can't + // express (you can't have two properties differing only in + // letter casing on a single anonymous type). + var dict = new Dictionary(StringComparer.Ordinal); + foreach (var prop in details.GetType().GetProperties()) + { + var value = prop.GetValue(details); + if (value is not null) dict[prop.Name] = value; + } + // Discriminator key — lowercase 'roomId'. Without this the + // MKAMHOIHOJK factory falls through to the Playlist branch + // and PlaylistId.PPGFHEDFBEA throws on the missing key. + dict["roomId"] = room.Id; + return dict; + } + + /// + /// Build a union-shape Playlist entry — the + /// branch of MKAMHOIHOJK. Keys read at + /// MKAMHOIHOJK.txt:516-612 (PascalCase base keys) and + /// KMKPEOGJDFK.txt:68 (PascalCase PlaylistId). + /// Omits the lowercase roomId key so the factory's + /// Room-vs-Playlist branch falls through to the Playlist + /// constructor. + /// + public static IDictionary BuildPlaylistUnionEntry(PlaylistEntity p) + { + return new Dictionary(StringComparer.Ordinal) + { + ["PlaylistId"] = p.Id, + ["Name"] = p.Name, + ["Description"] = p.Description, + ["ImageName"] = p.ImageName, + ["CreatorAccountId"] = p.CreatorPlayerId, + ["State"] = 0, + ["Accessibility"] = 1, + ["WarningMask"] = 0, + ["CustomWarning"] = string.Empty, + ["SupportsLevelVoting"] = false, + // Playlists are watchable from every device the + // member rooms support; advertise the broadest set so + // the watch never filters them out by capability. + ["IsRRO"] = p.IsCurated, + ["SupportsScreens"] = true, + ["SupportsWalkVR"] = true, + ["SupportsTeleportVR"] = true, + ["SupportsVRLow"] = true, + ["SupportsQuest2"] = true, + ["SupportsMobile"] = true, + ["SupportsJuniors"] = true, + ["CreatedAt"] = (p.CreatedAt == default ? DateTime.UtcNow : p.CreatedAt) + .ToString("yyyy-MM-ddTHH:mm:ssZ"), + ["Stats"] = new + { + CheerCount = p.CheerCount, + FavoriteCount = p.FavoriteCount, + VisitorCount = p.VisitorCount, + VisitCount = p.VisitCount, + }, + }; + } + + // ── Subroom mutations (bare path) ────────────────────────────────── + + public sealed class CreateSubRoomRequest + { + public string? Name { get; set; } + public string? RoomSceneLocationId { get; set; } + public int? MaxPlayers { get; set; } + public bool? IsSandbox { get; set; } + } + + /// POST rooms/{id}/subrooms — append a new scene. + /// Owner-gated. OrderIndex = max+1, Name unique within room. + [HttpPost("rooms/{roomId:long}/subrooms")] + [Authorize] + public async Task AddSubRoom(long roomId, [FromBody] CreateSubRoomRequest req) + { + var pid = this.RequireCurrentPlayerId(); + var room = await db.Rooms.FirstOrDefaultAsync(r => r.Id == roomId); + if (room is null) return NotFound(); + if (room.CreatorPlayerId != pid) return Forbid(); + + var name = (req.Name ?? string.Empty).Trim(); + if (name.Length == 0) name = "NewScene"; + var collision = await db.RoomScenes.AnyAsync(s => s.RoomId == roomId && s.Name == name); + if (collision) return Conflict(new { error = "name_taken" }); + + var nextOrder = (await db.RoomScenes + .Where(s => s.RoomId == roomId) + .Select(s => (int?)s.OrderIndex).MaxAsync() ?? -1) + 1; + var scene = new RoomSceneEntity + { + RoomId = roomId, + OrderIndex = nextOrder, + Name = name, + RoomSceneLocationId = req.RoomSceneLocationId + ?? "a75f7547-79eb-47c6-8986-6767abcb4f92", + MaxPlayers = req.MaxPlayers ?? 8, + IsSandbox = req.IsSandbox ?? false, + }; + db.RoomScenes.Add(scene); + await db.SaveChangesAsync(); + return Ok(SceneWire(scene)); + } + + [HttpDelete("rooms/{roomId:long}/subrooms/{subRoomId:long}")] + [Authorize] + public async Task DeleteSubRoom(long roomId, long subRoomId) + { + var pid = this.RequireCurrentPlayerId(); + var room = await db.Rooms.FirstOrDefaultAsync(r => r.Id == roomId); + if (room is null) return NotFound(); + if (room.CreatorPlayerId != pid) return Forbid(); + var scene = await db.RoomScenes + .FirstOrDefaultAsync(s => s.RoomId == roomId && s.OrderIndex == subRoomId); + if (scene is null) return NotFound(); + if (scene.OrderIndex == 0) return BadRequest(new { error = "cannot_delete_entry_scene" }); + db.RoomScenes.Remove(scene); + await db.SaveChangesAsync(); + return Ok(new { Result = 0 }); + } + + public sealed class SubRoomIntRequest { public int? Value { get; set; } } + public sealed class SubRoomBoolRequest { public bool? Value { get; set; } } + public sealed class SubRoomModifyRequest + { + public string? Name { get; set; } + public int? MaxPlayers { get; set; } + public bool? IsSandbox { get; set; } + public bool? CanMatchmakeInto { get; set; } + public string? RoomSceneLocationId { get; set; } + } + public sealed class SubRoomMoveRequest { public int? NewIndex { get; set; } } + + [HttpPost("rooms/{roomId:long}/subrooms/{subRoomId:long}/maxplayers")] + [HttpPut("rooms/{roomId:long}/subrooms/{subRoomId:long}/maxplayers")] + [Authorize] + public Task SubRoomMaxPlayers(long roomId, long subRoomId, [FromBody] SubRoomIntRequest req) => + MutateScene(roomId, subRoomId, s => { if (req.Value is int v) s.MaxPlayers = Math.Max(1, v); }); + + [HttpPost("rooms/{roomId:long}/subrooms/{subRoomId:long}/accessibility")] + [HttpPut("rooms/{roomId:long}/subrooms/{subRoomId:long}/accessibility")] + [Authorize] + public Task SubRoomAccessibility(long roomId, long subRoomId, [FromBody] SubRoomBoolRequest req) => + MutateScene(roomId, subRoomId, s => { if (req.Value is bool v) s.CanMatchmakeInto = v; }); + + /// POST rooms/{id}/subrooms/{sub}/clone — duplicate + /// a scene (DataBlobName + location pointer). New scene goes at the + /// end with " Copy" suffix; per-room name uniqueness preserved. + [HttpPost("rooms/{roomId:long}/subrooms/{subRoomId:long}/clone")] + [Authorize] + public async Task CloneSubRoom(long roomId, long subRoomId) + { + var pid = this.RequireCurrentPlayerId(); + var room = await db.Rooms.FirstOrDefaultAsync(r => r.Id == roomId); + if (room is null) return NotFound(); + if (room.CreatorPlayerId != pid) return Forbid(); + var src = await db.RoomScenes + .FirstOrDefaultAsync(s => s.RoomId == roomId && s.OrderIndex == subRoomId); + if (src is null) return NotFound(); + + var baseName = $"{src.Name} Copy"; + var newName = baseName; + int suffix = 1; + while (await db.RoomScenes.AnyAsync(s => s.RoomId == roomId && s.Name == newName)) + { + suffix += 1; + newName = $"{baseName} {suffix}"; + } + var nextOrder = (await db.RoomScenes.Where(s => s.RoomId == roomId) + .Select(s => (int?)s.OrderIndex).MaxAsync() ?? -1) + 1; + var clone = new RoomSceneEntity + { + RoomId = roomId, + OrderIndex = nextOrder, + Name = newName, + RoomSceneLocationId = src.RoomSceneLocationId, + DataBlobName = src.DataBlobName, + MaxPlayers = src.MaxPlayers, + IsSandbox = src.IsSandbox, + CanMatchmakeInto = src.CanMatchmakeInto, + }; + db.RoomScenes.Add(clone); + await db.SaveChangesAsync(); + return Ok(SceneWire(clone)); + } + + [HttpPost("rooms/{roomId:long}/subrooms/{subRoomId:long}/modify")] + [HttpPut("rooms/{roomId:long}/subrooms/{subRoomId:long}/modify")] + [Authorize] + public Task ModifySubRoom(long roomId, long subRoomId, [FromBody] SubRoomModifyRequest req) => + MutateScene(roomId, subRoomId, s => + { + if (!string.IsNullOrWhiteSpace(req.Name)) s.Name = req.Name.Trim(); + if (req.MaxPlayers is int mp) s.MaxPlayers = Math.Max(1, mp); + if (req.IsSandbox is bool sb) s.IsSandbox = sb; + if (req.CanMatchmakeInto is bool cmi) s.CanMatchmakeInto = cmi; + if (req.RoomSceneLocationId is not null) s.RoomSceneLocationId = req.RoomSceneLocationId; + }); + + /// POST rooms/{id}/subrooms/{sub}/move — reorder a + /// scene. Body NewIndex is the target OrderIndex; the in-between + /// rows shift to fill the gap. + [HttpPost("rooms/{roomId:long}/subrooms/{subRoomId:long}/move")] + [Authorize] + public async Task MoveSubRoom(long roomId, long subRoomId, [FromBody] SubRoomMoveRequest req) + { + var pid = this.RequireCurrentPlayerId(); + var room = await db.Rooms.FirstOrDefaultAsync(r => r.Id == roomId); + if (room is null) return NotFound(); + if (room.CreatorPlayerId != pid) return Forbid(); + var scenes = await db.RoomScenes + .Where(s => s.RoomId == roomId).OrderBy(s => s.OrderIndex).ToListAsync(); + var moving = scenes.FirstOrDefault(s => s.OrderIndex == subRoomId); + if (moving is null) return NotFound(); + var newIndex = Math.Clamp(req.NewIndex ?? 0, 0, scenes.Count - 1); + scenes.Remove(moving); + scenes.Insert(newIndex, moving); + // Recompute OrderIndex on the entire row set. The unique + // (RoomId, OrderIndex) index would clash if we wrote rows in + // place — flush a sentinel offset first, then settle to + // final indices. + const int sentinelOffset = 100_000; + for (int i = 0; i < scenes.Count; i++) scenes[i].OrderIndex = i + sentinelOffset; + await db.SaveChangesAsync(); + for (int i = 0; i < scenes.Count; i++) scenes[i].OrderIndex = i; + await db.SaveChangesAsync(); + return Ok(SceneWire(moving)); + } + + /// POST rooms/{id}/subrooms/{sub}/data — bare-path + /// equivalent of api/rooms/v4/saveData. Delegates to the + /// existing save logic so the watch's two URL variants land on the + /// same persistence path. + [HttpPost("rooms/{roomId:long}/subrooms/{subRoomId:long}/data")] + [Authorize] + public Task SubRoomData(long roomId, long subRoomId, + [FromBody] SaveRoomSceneRequest body) + { + body.RoomSceneId = subRoomId; + return SaveData(body); + } + + private async Task MutateScene(long roomId, long subRoomId, Action mutator) + { + var pid = this.RequireCurrentPlayerId(); + var room = await db.Rooms.FirstOrDefaultAsync(r => r.Id == roomId); + if (room is null) return NotFound(); + if (room.CreatorPlayerId != pid) return Forbid(); + var scene = await db.RoomScenes + .FirstOrDefaultAsync(s => s.RoomId == roomId && s.OrderIndex == subRoomId); + if (scene is null) return NotFound(); + mutator(scene); + scene.DataModifiedAt = DateTime.UtcNow; + await db.SaveChangesAsync(); + return Ok(SceneWire(scene)); + } + + private static object SceneWire(RoomSceneEntity s) => new + { + Result = 0, + RoomId = s.RoomId, + RoomSceneId = s.OrderIndex, + SubRoomId = s.OrderIndex, + Name = s.Name, + s.DataBlobName, + RoomSceneLocationId = s.RoomSceneLocationId, + s.IsSandbox, + s.MaxPlayers, + s.CanMatchmakeInto, + }; + + // ── Bare-path roles ──────────────────────────────────────────────── + + /// GET rooms/{id}/roles — accepted role grants on + /// the room. Owner is implicit (CreatorPlayerId) and surfaces under + /// Role=30; co-owner/mod/host rows come from RoomRoles. + [HttpGet("rooms/{roomId:long}/roles")] + public async Task RolesList(long roomId) + { + var room = await db.Rooms.FirstOrDefaultAsync(r => r.Id == roomId); + if (room is null) return NotFound(); + var rows = await db.RoomRoles.Where(r => r.RoomId == roomId).ToListAsync(); + var list = new List(rows.Count + 1) + { + new { RoomId = roomId, PlayerId = (int)room.CreatorPlayerId, Role = 30 /*Owner*/, Accepted = true }, + }; + list.AddRange(rows.Select(r => (object)new + { + RoomId = roomId, + PlayerId = (int)r.PlayerId, + Role = r.Role, + Accepted = r.Accepted, + })); + return Ok(list); + } + + [HttpGet("rooms/{roomId:long}/roles/{playerId:long}")] + public async Task RoleForPlayer(long roomId, long playerId) + { + var room = await db.Rooms.FirstOrDefaultAsync(r => r.Id == roomId); + if (room is null) return NotFound(); + if (room.CreatorPlayerId == playerId) + return Ok(new { RoomId = roomId, PlayerId = (int)playerId, Role = 30, Accepted = true }); + var row = await db.RoomRoles + .FirstOrDefaultAsync(r => r.RoomId == roomId && r.PlayerId == playerId); + if (row is null) return NotFound(); + return Ok(new { RoomId = roomId, PlayerId = (int)playerId, Role = row.Role, Accepted = row.Accepted }); + } + + public sealed class GrantRoleRequest { public int? Role { get; set; } } + + /// POST rooms/{id}/roles/{playerId} — owner grants + /// a role to a player. Auto-accepted (no invite step). + [HttpPost("rooms/{roomId:long}/roles/{playerId:long}")] + [Authorize] + public async Task GrantRole(long roomId, long playerId, [FromBody] GrantRoleRequest req) + { + var me = this.RequireCurrentPlayerId(); + var room = await db.Rooms.FirstOrDefaultAsync(r => r.Id == roomId); + if (room is null) return NotFound(); + if (room.CreatorPlayerId != me) return Forbid(); + var role = req.Role ?? 0; + var existing = await db.RoomRoles + .FirstOrDefaultAsync(r => r.RoomId == roomId && r.PlayerId == playerId && r.Role == role); + if (existing is null) + { + db.RoomRoles.Add(new RoomRoleEntity + { + RoomId = roomId, PlayerId = playerId, Role = role, + Accepted = true, GrantedByPlayerId = me, + }); + } + else + { + existing.Accepted = true; + } + await db.SaveChangesAsync(); + return Ok(new { RoomId = roomId, PlayerId = (int)playerId, Role = role, Accepted = true }); + } + + /// POST rooms/{id}/roles/{playerId}/invite — same + /// as grant but Accepted=false; the target's accept-invite flow + /// flips the flag (separate endpoint, not yet exposed). For now + /// invited rows surface in RoomDetails.InvitedCoOwners etc. + [HttpPost("rooms/{roomId:long}/roles/{playerId:long}/invite")] + [Authorize] + public async Task InviteRole(long roomId, long playerId, [FromBody] GrantRoleRequest req) + { + var me = this.RequireCurrentPlayerId(); + var room = await db.Rooms.FirstOrDefaultAsync(r => r.Id == roomId); + if (room is null) return NotFound(); + if (room.CreatorPlayerId != me) return Forbid(); + var role = req.Role ?? 0; + var existing = await db.RoomRoles + .FirstOrDefaultAsync(r => r.RoomId == roomId && r.PlayerId == playerId && r.Role == role); + if (existing is null) + { + db.RoomRoles.Add(new RoomRoleEntity + { + RoomId = roomId, PlayerId = playerId, Role = role, + Accepted = false, GrantedByPlayerId = me, + }); + } + else + { + existing.Accepted = false; + } + await db.SaveChangesAsync(); + return Ok(new { RoomId = roomId, PlayerId = (int)playerId, Role = role, Accepted = false }); + } + + /// + /// Synthesise a placeholder Room when the client asks for one we don't + /// have in the DB. Keeps the deserializer happy — every required key + /// is populated. Safe defaults: public, AG, default DormRoom location. + /// + /// Special case: roomId=1 is the canonical DormRoom id used by + /// GoToController when the client visits its own dorm. Make sure the + /// /v4/details/1 follow-up shows "DormRoom" rather than "Room_1". + /// + private static RoomEntity Synthetic(string name, long? id = null) + { + var isDorm = (id == 1) || name.Equals("DormRoom", StringComparison.OrdinalIgnoreCase); + var displayName = isDorm ? "DormRoom" : name; + return new RoomEntity + { + Id = id ?? Math.Abs((long)name.GetHashCode()), + Name = displayName, + Description = isDorm + ? "Your private dorm — yours alone, decorated however you like." + : $"{displayName} room", + CreatorPlayerId = 1, + State = 0, + Accessibility = isDorm ? 0 /* Private */ : 1 /* Public */, + IsAGRoom = !isDorm, + IsDormRoom = isDorm, + LocationReplicationId = "76d98498-60a1-430c-ab76-b54a29b7a163", + }; + } +} diff --git a/DorkNet.Server/Controllers/API/Rooms/V2/RoomsModerationController.cs b/DorkNet.Server/Controllers/API/Rooms/V2/RoomsModerationController.cs new file mode 100644 index 0000000..33791be --- /dev/null +++ b/DorkNet.Server/Controllers/API/Rooms/V2/RoomsModerationController.cs @@ -0,0 +1,646 @@ +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Mvc; +using Microsoft.EntityFrameworkCore; +using System.Text.Json; +using DorkNet.Server.Auth; +using DorkNet.Server.Data; +using DorkNet.Server.Data.Entities; + +namespace DorkNet.Server.Controllers.API.Rooms.V2; + +/// +/// api.rec.net/api/rooms/v{1,2}/{modify,modifyPermissions,banfromroom, +/// importroombans,roombans,report,modify/scene,modify/sceneParent, +/// modify/tags,modifyscene} — owner-side mutation surface for rooms. +/// +/// Split from to keep that file +/// (browse / details / clone) at a manageable size. All endpoints +/// are owner-gated (the caller must equal +/// ) or admin-bypassed via +/// on the admin-only sub-routes. +/// +[ApiController] +[Authorize] +public class RoomsModerationController(DorkNetDbContext db) : ControllerBase +{ + private long Me => this.RequireCurrentPlayerId(); + + private async Task RequireOwnedRoomAsync(long roomId) + { + var room = await db.Rooms.FirstOrDefaultAsync(r => r.Id == roomId); + if (room is null) return null; + return room.CreatorPlayerId == Me ? room : null; + } + + // ── /modify (general fields) ───────────────────────────────────────── + + public sealed class ModifyRoomRequest + { + public long RoomId { get; set; } + public string? Name { get; set; } + public string? Description { get; set; } + public string? ImageName { get; set; } + public string? Tags { get; set; } + public int? Accessibility { get; set; } + public bool? SupportsScreens { get; set; } + public bool? SupportsMobile { get; set; } + public bool? SupportsVRLow { get; set; } + public bool? SupportsWalkVR { get; set; } + public bool? SupportsTeleportVR { get; set; } + public bool? DisableMicAutoMute { get; set; } + } + + [HttpPost("api/rooms/v2/modify")] + public async Task Modify([FromBody] ModifyRoomRequest req) + { + var room = await RequireOwnedRoomAsync(req.RoomId); + if (room is null) return NotFound(); + + if (!string.IsNullOrWhiteSpace(req.Name)) room.Name = req.Name.Trim(); + if (req.Description is not null) room.Description = req.Description; + if (req.ImageName is not null) room.ImageName = req.ImageName; + if (req.Tags is not null) room.TagsCsv = req.Tags; + if (req.Accessibility.HasValue) room.Accessibility = Math.Clamp(req.Accessibility.Value, 0, 2); + if (req.SupportsScreens.HasValue) room.SupportsScreens = req.SupportsScreens.Value; + if (req.SupportsMobile.HasValue) room.SupportsMobile = req.SupportsMobile.Value; + if (req.SupportsVRLow.HasValue) room.SupportsVRLow = req.SupportsVRLow.Value; + if (req.SupportsWalkVR.HasValue) room.SupportsWalkVR = req.SupportsWalkVR.Value; + if (req.SupportsTeleportVR.HasValue) room.SupportsTeleportVR = req.SupportsTeleportVR.Value; + if (req.DisableMicAutoMute.HasValue) room.DisableMicAutoMute = req.DisableMicAutoMute.Value; + room.UpdatedAt = DateTime.UtcNow; + + try + { + await db.SaveChangesAsync(); + } + catch (DbUpdateException) + { + // Likely a unique-name collision. + return Conflict(new { Result = 2, Message = "name taken" }); + } + return Ok(new { Result = 0, Room = Services.RoomService.ToWireRoom(room) }); + } + + public sealed class ModifyPermissionsRequest + { + public long RoomId { get; set; } + public int? Accessibility { get; set; } + public bool? SupportsLevelVoting { get; set; } + public bool? AllowsJuniors { get; set; } + public bool? CloningAllowed { get; set; } + public int? RoomWarningMask { get; set; } + public string? CustomRoomWarning { get; set; } + } + + [HttpPost("api/rooms/v2/modifyPermissions")] + public async Task ModifyPermissions([FromBody] ModifyPermissionsRequest req) + { + var room = await RequireOwnedRoomAsync(req.RoomId); + if (room is null) return NotFound(); + + if (req.Accessibility.HasValue) room.Accessibility = Math.Clamp(req.Accessibility.Value, 0, 2); + if (req.SupportsLevelVoting.HasValue) room.SupportsLevelVoting = req.SupportsLevelVoting.Value; + if (req.AllowsJuniors.HasValue) room.AllowsJuniors = req.AllowsJuniors.Value; + if (req.CloningAllowed.HasValue) room.CloningAllowed = req.CloningAllowed.Value; + if (req.RoomWarningMask.HasValue) room.RoomWarningMask = req.RoomWarningMask.Value; + if (req.CustomRoomWarning is not null) room.CustomRoomWarning = req.CustomRoomWarning; + room.UpdatedAt = DateTime.UtcNow; + await db.SaveChangesAsync(); + return Ok(Services.RoomService.ToWireRoom(room)); + } + + // ── Scene mutations (LocationReplicationId) ────────────────────────── + + public sealed class ModifySceneRequest + { + public long RoomId { get; set; } + public string? LocationReplicationId { get; set; } + } + + [HttpPost("api/rooms/v1/modify/scene")] + [HttpPost("api/rooms/v1/modifyscene")] + public async Task ModifyScene([FromBody] ModifySceneRequest req) + { + var room = await RequireOwnedRoomAsync(req.RoomId); + if (room is null) return NotFound(); + if (!string.IsNullOrWhiteSpace(req.LocationReplicationId)) + room.LocationReplicationId = req.LocationReplicationId; + room.UpdatedAt = DateTime.UtcNow; + await db.SaveChangesAsync(); + return Ok(Services.RoomService.ToWireRoom(room)); + } + + public sealed class ModifySceneParentRequest + { + public long RoomId { get; set; } + public long? ParentRoomId { get; set; } + } + + /// Sub-room / nested-scene parent override. We don't + /// model sub-rooms as a real graph, so we accept-and-ignore but + /// still owner-gate to prevent grief. + [HttpPost("api/rooms/v1/modify/sceneParent")] + public async Task ModifySceneParent([FromBody] ModifySceneParentRequest req) + { + var room = await RequireOwnedRoomAsync(req.RoomId); + if (room is null) return NotFound(); + return Ok(new { Result = 0 }); + } + + public sealed class ModifyTagsRequest + { + public long RoomId { get; set; } + public string? Tags { get; set; } + } + + [HttpPost("api/rooms/v1/modify/tags")] + public async Task ModifyTags([FromBody] ModifyTagsRequest req) + { + var room = await RequireOwnedRoomAsync(req.RoomId); + if (room is null) return NotFound(); + room.TagsCsv = req.Tags ?? string.Empty; + room.UpdatedAt = DateTime.UtcNow; + await db.SaveChangesAsync(); + return Ok(Services.RoomService.ToWireRoom(room)); + } + + // ── Bans ───────────────────────────────────────────────────────────── + + public sealed class BanFromRoomRequest + { + public long RoomId { get; set; } + public long PlayerId { get; set; } + public int BanType { get; set; } // 0=Soft (kick), 1=Permanent, 2=Temp + public DateTime? Until { get; set; } + public string? Reason { get; set; } + } + + [HttpPost("api/rooms/v2/banfromroom")] + public async Task BanFromRoom([FromBody] BanFromRoomRequest req) + { + var room = await RequireOwnedRoomAsync(req.RoomId); + if (room is null) return NotFound(); + db.RoomBans.Add(new RoomBanEntity + { + RoomId = req.RoomId, + BannedPlayerId = req.PlayerId, + BannedByPlayerId = Me, + BanType = req.BanType, + Until = req.Until, + Reason = req.Reason ?? string.Empty, + }); + await db.SaveChangesAsync(); + return Ok(new { Result = 0 }); + } + + public sealed class ImportRoomBansRequest + { + public long RoomId { get; set; } + public List? Bans { get; set; } + } + + [HttpPost("api/rooms/v1/importroombans")] + public async Task ImportRoomBans([FromBody] ImportRoomBansRequest req) + { + var room = await RequireOwnedRoomAsync(req.RoomId); + if (room is null) return NotFound(); + if (req.Bans is null || req.Bans.Count == 0) return Ok(new { Imported = 0 }); + + foreach (var b in req.Bans) + { + db.RoomBans.Add(new RoomBanEntity + { + RoomId = req.RoomId, + BannedPlayerId = b.PlayerId, + BannedByPlayerId = Me, + BanType = b.BanType, + Until = b.Until, + Reason = b.Reason ?? string.Empty, + }); + } + await db.SaveChangesAsync(); + return Ok(new { Imported = req.Bans.Count }); + } + + [HttpGet("api/rooms/v1/roombans/{roomId:long}")] + public async Task RoomBansList(long roomId) + { + var room = await db.Rooms.FirstOrDefaultAsync(r => r.Id == roomId); + if (room is null) return NotFound(); + // Only owner sees the ban list (admins also via existing + // admin endpoints). + if (room.CreatorPlayerId != Me) return Forbid(); + var rows = await db.RoomBans + .Where(b => b.RoomId == roomId && (b.Until == null || b.Until > DateTime.UtcNow)) + .OrderByDescending(b => b.CreatedAt) + .ToListAsync(); + return Ok(rows.Select(b => new + { + b.Id, + b.RoomId, + b.BannedPlayerId, + b.BannedByPlayerId, + b.BanType, + b.Until, + b.Reason, + b.CreatedAt, + })); + } + + // ── Report ─────────────────────────────────────────────────────────── + + public sealed class RoomReportRequest + { + public long RoomId { get; set; } + public int Category { get; set; } + public string? Message { get; set; } + } + + [HttpPost("api/rooms/v2/report")] + public async Task Report([FromBody] RoomReportRequest req) + { + var room = await db.Rooms.FirstOrDefaultAsync(r => r.Id == req.RoomId); + if (room is null) return NotFound(); + var msg = req.Message ?? string.Empty; + db.Reports.Add(new ReportEntity + { + ReporterPlayerId = Me, + TargetPlayerId = room.CreatorPlayerId, + TargetRoomId = req.RoomId, + RoomId = req.RoomId, + Category = req.Category, + Message = msg[..Math.Min(1000, msg.Length)], + }); + await db.SaveChangesAsync(); + return Ok(new { Reported = true }); + } + + public sealed class RoomRolePermissionRequest + { + public long RoomId { get; set; } + public long PlayerId { get; set; } + } + + /// POST api/rooms/v1/roomRolePermissions — watch + /// posts its self-computed role + permission grants for + /// server-side validation. Role enum: + /// 0=Visitor, 5=Member, 10=Mod, 20=Host, 30=Owner. Room creator + /// is always Owner; everyone else defaults to Visitor. + [HttpPost("api/rooms/v1/roomRolePermissions")] + [AllowAnonymous] + public async Task RoomRolePermissions( + [FromBody] RoomRolePermissionRequest? body, + [FromForm(Name = "RoomId")] long? roomIdForm, + [FromForm(Name = "PlayerId")] long? playerIdForm) + { + var roomId = body?.RoomId ?? roomIdForm ?? 0; + var playerId = body?.PlayerId ?? playerIdForm ?? (this.CurrentPlayerId() ?? 0); + if (roomId <= 0 || playerId <= 0) + return Ok(new { success = true, error = "" }); + + var room = await db.Rooms.FirstOrDefaultAsync(r => r.Id == roomId); + int role = (room?.CreatorPlayerId == playerId) ? 30 : 0; + long permissions = role switch + { + 30 => -1L, + 10 => 0x0FFFFFFFL, + 5 => 0x000000FFL, + _ => 0x0000000FL, + }; + return Ok(new + { + success = true, + error = "", + RoomId = roomId, + PlayerId = (int)playerId, + Role = role, + Permissions = permissions, + }); + } + + /// POST api/rooms/v1/verifyRole — status-only + /// gate used by 2020.12 before privileged room actions. Body is + /// form or JSON with roomId, role, and optional context. + [HttpPost("api/rooms/v1/verifyRole")] + [Consumes("application/x-www-form-urlencoded", "multipart/form-data", "application/json")] + public async Task VerifyRole() + { + var req = await ReadVerifyRoleRequestAsync(); + if (req.RoomId <= 0) return BadRequest(); + var room = await db.Rooms.FirstOrDefaultAsync(r => r.Id == req.RoomId); + if (room is null) return NotFound(); + + var me = Me; + var required = RoleRank(req.Role); + if (required <= 0) return Ok(); + + var isAdmin = await db.Players + .Where(p => p.Id == me) + .Select(p => p.IsAdmin) + .FirstOrDefaultAsync(); + if (isAdmin || room.CreatorPlayerId == me) return Ok(); + + var acceptedRoles = await db.RoomRoles + .Where(r => r.RoomId == req.RoomId && r.PlayerId == me && r.Accepted) + .Select(r => r.Role) + .ToListAsync(); + var actual = acceptedRoles.Select(RoomRoleRank).DefaultIfEmpty(0).Max(); + return actual >= required ? Ok() : Forbid(); + } + + private sealed class VerifyRoleRequest + { + public long RoomId { get; set; } + public string Role { get; set; } = string.Empty; + public string Context { get; set; } = string.Empty; + } + + private async Task ReadVerifyRoleRequestAsync() + { + if (Request.HasFormContentType) + { + var form = await Request.ReadFormAsync(); + return new VerifyRoleRequest + { + RoomId = long.TryParse(form["roomId"].FirstOrDefault() + ?? form["RoomId"].FirstOrDefault(), out var roomId) + ? roomId + : 0, + Role = form["role"].FirstOrDefault() + ?? form["Role"].FirstOrDefault() + ?? string.Empty, + Context = form["context"].FirstOrDefault() + ?? form["Context"].FirstOrDefault() + ?? string.Empty, + }; + } + + try + { + var req = await JsonSerializer.DeserializeAsync( + Request.Body, + new JsonSerializerOptions { PropertyNameCaseInsensitive = true }); + return req ?? new VerifyRoleRequest(); + } + catch (JsonException) + { + return new VerifyRoleRequest(); + } + } + + private static int RoomRoleRank(int role) => role switch + { + 0 => 30, // CoOwner + 1 => 10, // Moderator + 2 => 20, // Host + _ => 0, + }; + + private static int RoleRank(string role) + { + if (int.TryParse(role, out var n)) return n; + return role.Trim().ToLowerInvariant() switch + { + "owner" or "creator" or "coowner" or "co-owner" => 30, + "host" => 20, + "moderator" or "mod" => 10, + "member" => 5, + "visitor" or "" => 0, + _ => 0, + }; + } + + // ── Bare-path per-field mutations (2020.12 watch URLs) ───────────── + // + // The 2020.12 client emits per-field mutation URLs like + // POST rooms/{id}/description rather than POST api/rooms/v2/modify + // with one field set. Each handler below shares the same + // RequireOwnedRoomAsync gate as /modify and returns the same + // ToWireRoom shape so the watch's CreateModifyRoomResponse + // deserializer reads cleanly. + + public sealed class BareDescriptionRequest { public string? Description { get; set; } } + public sealed class BareImageRequest { public string? ImageName { get; set; } } + public sealed class BareTagsRequest { public string? Tags { get; set; } public List? TagsList { get; set; } } + public sealed class BareIntRequest { public int? Value { get; set; } } + public sealed class BareBoolRequest { public bool? Value { get; set; } } + public sealed class BareWarningRequest { public int? RoomWarningMask { get; set; } public string? CustomRoomWarning { get; set; } } + + // Each per-field route registers BOTH POST and PUT — the 2020.12 + // watch's request-builder wraps the HTTP method inside opaque + // BPHGKAEDBPE helpers; the existing /name handler at + // RoomsController.cs:1455-1456 set the precedent of "register both + // because the ISIL is opaque". Same pattern here so the bind never + // 405s. + + [HttpPost("rooms/{roomId:long}/description")] + [HttpPut("rooms/{roomId:long}/description")] + public async Task BareDescription(long roomId, + [FromBody] BareDescriptionRequest? body, + [FromForm(Name = "Description")] string? form) => + await ApplyAndReturn(roomId, r => r.Description = body?.Description ?? form ?? r.Description); + + [HttpPost("rooms/{roomId:long}/image")] + [HttpPut("rooms/{roomId:long}/image")] + public async Task BareImage(long roomId, + [FromBody] BareImageRequest? body, + [FromForm(Name = "ImageName")] string? form) => + await ApplyAndReturn(roomId, r => r.ImageName = body?.ImageName ?? form ?? r.ImageName); + + [HttpPost("rooms/{roomId:long}/tags")] + [HttpPut("rooms/{roomId:long}/tags")] + public async Task BareTags(long roomId, + [FromBody] BareTagsRequest? body, + [FromForm(Name = "Tags")] string? form) + { + var csv = body?.Tags + ?? (body?.TagsList is { Count: > 0 } list ? string.Join(',', list) : null) + ?? form; + return await ApplyAndReturn(roomId, r => r.TagsCsv = csv ?? r.TagsCsv); + } + + [HttpPost("rooms/{roomId:long}/accessibility")] + [HttpPut("rooms/{roomId:long}/accessibility")] + public async Task BareAccessibility(long roomId, [FromBody] BareIntRequest? body, + [FromForm(Name = "Accessibility")] int? form) => + await ApplyAndReturn(roomId, r => + { + var v = body?.Value ?? form; + if (v is int vv) r.Accessibility = Math.Clamp(vv, 0, 2); + }); + + [HttpPost("rooms/{roomId:long}/cloning")] + [HttpPut("rooms/{roomId:long}/cloning")] + public async Task BareCloning(long roomId, [FromBody] BareBoolRequest? body, + [FromForm(Name = "CloningAllowed")] bool? form) => + await ApplyAndReturn(roomId, r => + { + var v = body?.Value ?? form; + if (v is bool vv) r.CloningAllowed = vv; + }); + + [HttpPost("rooms/{roomId:long}/automute")] + [HttpPut("rooms/{roomId:long}/automute")] + public async Task BareAutomute(long roomId, [FromBody] BareBoolRequest? body, + [FromForm(Name = "DisableMicAutoMute")] bool? form) => + await ApplyAndReturn(roomId, r => + { + var v = body?.Value ?? form; + if (v is bool vv) r.DisableMicAutoMute = vv; + }); + + /// rooms/{id}/restrictions — junior / age-related + /// restriction toggle. Body bool maps to AllowsJuniors (true = no + /// restriction, false = juniors restricted). + [HttpPost("rooms/{roomId:long}/restrictions")] + [HttpPut("rooms/{roomId:long}/restrictions")] + public async Task BareRestrictions(long roomId, [FromBody] BareBoolRequest? body, + [FromForm(Name = "AllowsJuniors")] bool? form) => + await ApplyAndReturn(roomId, r => + { + var v = body?.Value ?? form; + if (v is bool vv) r.AllowsJuniors = vv; + }); + + [HttpPost("rooms/{roomId:long}/warning")] + [HttpPut("rooms/{roomId:long}/warning")] + public async Task BareWarning(long roomId, [FromBody] BareWarningRequest? body) => + await ApplyAndReturn(roomId, r => + { + if (body?.RoomWarningMask is int m) r.RoomWarningMask = m; + if (body?.CustomRoomWarning is not null) r.CustomRoomWarning = body.CustomRoomWarning; + }); + + /// rooms/{id}/comments + voice_chat_encryption + /// — fields not persisted in the 2020 RoomEntity. Owner-gated + /// acknowledgement so the watch's settings panel doesn't surface + /// a "save failed" error; populated when the columns land. + [HttpPost("rooms/{roomId:long}/comments")] + [HttpPut("rooms/{roomId:long}/comments")] + [HttpPost("rooms/{roomId:long}/voice_chat_encryption")] + [HttpPut("rooms/{roomId:long}/voice_chat_encryption")] + public async Task BareAck(long roomId) => + await ApplyAndReturn(roomId, _ => { /* no-op: not modelled yet */ }); + + /// rooms/{id}/modify — bare-path equivalent + /// of api/rooms/v2/modify. Reuses by + /// forwarding the body with RoomId pre-populated from the URL. + [HttpPost("rooms/{roomId:long}/modify")] + [HttpPut("rooms/{roomId:long}/modify")] + public async Task BareModify(long roomId, [FromBody] ModifyRoomRequest req) + { + req.RoomId = roomId; + return await Modify(req); + } + + /// POST rooms/{id}/clone — bare-path alias of + /// api/rooms/v1/clone. The actual clone logic lives on + /// ; + /// this handler proxies via the service so we don't duplicate the + /// substantial clone-rooms logic. + [HttpPost("rooms/{roomId:long}/clone")] + public async Task BareClone(long roomId, + [FromBody] CloneBareRequest? body, + [FromForm(Name = "Name")] string? formName) + { + var newName = body?.Name ?? formName ?? string.Empty; + var source = await db.Rooms.FirstOrDefaultAsync(r => r.Id == roomId); + if (source is null) return NotFound(); + if (!source.CloningAllowed && source.CreatorPlayerId != Me) + return Forbid(); + + if (string.IsNullOrWhiteSpace(newName)) + newName = $"{source.Name}Copy"; + else + newName = newName.Trim(); + + // Uniqueness — append a numeric suffix until free. + var baseName = newName; + int suffix = 1; + while (await db.Rooms.AnyAsync(r => r.Name == newName)) + { + suffix += 1; + newName = $"{baseName}{suffix}"; + } + + var clone = new RoomEntity + { + Name = newName, + Description = source.Description, + CreatorPlayerId = Me, + ImageName = source.ImageName, + State = 0, + Accessibility = source.Accessibility, + SupportsLevelVoting = source.SupportsLevelVoting, + IsAGRoom = false, + IsDormRoom = false, + CloningAllowed = source.CloningAllowed, + SupportsVRLow = source.SupportsVRLow, + SupportsMobile = source.SupportsMobile, + SupportsScreens = source.SupportsScreens, + SupportsWalkVR = source.SupportsWalkVR, + SupportsTeleportVR = source.SupportsTeleportVR, + AllowsJuniors = source.AllowsJuniors, + RoomWarningMask = source.RoomWarningMask, + CustomRoomWarning = source.CustomRoomWarning, + DisableMicAutoMute = source.DisableMicAutoMute, + LocationReplicationId = source.LocationReplicationId, + TagsCsv = source.TagsCsv, + CurrentDataBlobName = source.CurrentDataBlobName, + }; + db.Rooms.Add(clone); + await db.SaveChangesAsync(); + return Ok(Services.RoomService.ToWireRoom(clone)); + } + public sealed class CloneBareRequest { public string? Name { get; set; } } + + // ── Bare-path bans aliases ───────────────────────────────────────── + + public sealed class BareBanRequest { public long PlayerId { get; set; } public int BanType { get; set; } public DateTime? Until { get; set; } public string? Reason { get; set; } } + + [HttpGet("rooms/{roomId:long}/bans")] + public Task BareBansList(long roomId) => RoomBansList(roomId); + + [HttpPost("rooms/{roomId:long}/bans")] + public Task BareBan(long roomId, [FromBody] BareBanRequest req) => + BanFromRoom(new BanFromRoomRequest + { + RoomId = roomId, + PlayerId = req.PlayerId, + BanType = req.BanType, + Until = req.Until, + Reason = req.Reason, + }); + + [HttpDelete("rooms/{roomId:long}/bans/{playerId:long}")] + public async Task BareUnban(long roomId, long playerId) + { + var room = await RequireOwnedRoomAsync(roomId); + if (room is null) return NotFound(); + var rows = await db.RoomBans + .Where(b => b.RoomId == roomId && b.BannedPlayerId == playerId) + .ToListAsync(); + if (rows.Count == 0) return Ok(new { Removed = 0 }); + db.RoomBans.RemoveRange(rows); + await db.SaveChangesAsync(); + return Ok(new { Removed = rows.Count }); + } + + [HttpPost("rooms/{roomId:long}/bans/import")] + public Task BareBansImport(long roomId, [FromBody] ImportRoomBansRequest req) + { + req.RoomId = roomId; + return ImportRoomBans(req); + } + + private async Task ApplyAndReturn(long roomId, Action mutator) + { + var room = await RequireOwnedRoomAsync(roomId); + if (room is null) return NotFound(); + mutator(room); + room.UpdatedAt = DateTime.UtcNow; + try { await db.SaveChangesAsync(); } + catch (DbUpdateException) { return Conflict(new { Result = 2 }); } + return Ok(Services.RoomService.ToWireRoom(room)); + } +} diff --git a/DorkNet.Server/Controllers/API/Royale/RoyaleController.cs b/DorkNet.Server/Controllers/API/Royale/RoyaleController.cs new file mode 100644 index 0000000..fea72e1 --- /dev/null +++ b/DorkNet.Server/Controllers/API/Royale/RoyaleController.cs @@ -0,0 +1,176 @@ +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Mvc; +using Microsoft.EntityFrameworkCore; +using DorkNet.Server.Auth; +using DorkNet.Server.Data; +using DorkNet.Server.Data.Entities; + +namespace DorkNet.Server.Controllers.API.Royale; + +/// +/// api.rec.net/api/royale/* — Rec Royale battle-royale stats. +/// +/// Per RecNet.RecNetRecRoyaleStats (decompiled at +/// Cpp2IL_CS/.../RecNet/RecNetRecRoyaleStats.cs) the client +/// only calls TWO endpoints: +/// +/// • GET api/royale/v1/current → RecRoyalePlayerProgress: +/// TotalXP, Level, RankIdx, RankName, CurrentLevelXPThreshold, +/// NextLevelXPThreshold, NextLevelAcornReward. +/// • POST api/royale/v2/matchcomplete with body +/// MatchCompleteStats: Rank, NumEliminations, SecondsAlive, +/// WalkGame, CustomGame, ChestsOpened, ShieldPotionsConsumed, +/// HealthPotionsConsumed, SecondsInAir; returns StatUpdates: +/// XPAwardStrings, TotalXPAwarded, NewProgress. +/// +/// We persist match rows to + +/// for server-side audit, and +/// roll the per-player . +/// +[ApiController] +public class RoyaleController(DorkNetDbContext db) : ControllerBase +{ + // Rank ladder names (from the watch's RankIdx → RankName mapping). + // Approximated; the client's ladder isn't exposed in the dump but + // these are the standard 2020-era Royale tiers. + private static readonly string[] RankNames = + { + "Recruit", "Cadet", "Sergeant", "Lieutenant", "Captain", + "Major", "Colonel", "Commander", "Champion", "Legend", + }; + + private static long XpForLevel(int level) => 100L * level * level; + + private static (int level, int rankIdx, string rankName, long currentThreshold, long nextThreshold, int reward) + Compute(long totalXp) + { + int lvl = 1; + while (XpForLevel(lvl + 1) <= totalXp) lvl++; + var rankIdx = Math.Min(RankNames.Length - 1, lvl / 5); + var current = XpForLevel(lvl); + var next = XpForLevel(lvl + 1); + var reward = 50 + 10 * lvl; + return (lvl, rankIdx, RankNames[rankIdx], current, next, reward); + } + + [HttpGet("api/royale/v1/current")] + public async Task Current() + { + var pid = this.CurrentPlayerId(); + if (pid is not long me) return Ok(BlankProgress()); + var row = await db.RoyalePlayerProgress.FirstOrDefaultAsync(p => p.PlayerId == me); + if (row is null) return Ok(BlankProgress()); + return Ok(ToWireProgress(row)); + } + + public sealed class MatchCompleteStats + { + public int Rank { get; set; } + public int NumEliminations { get; set; } + public int SecondsAlive { get; set; } + public bool WalkGame { get; set; } + public bool CustomGame { get; set; } + public int ChestsOpened { get; set; } + public int ShieldPotionsConsumed { get; set; } + public int HealthPotionsConsumed { get; set; } + public int SecondsInAir { get; set; } + } + + [HttpPost("api/royale/v2/matchcomplete")] + [Authorize] + public async Task MatchComplete([FromBody] MatchCompleteStats body) + { + var pid = this.RequireCurrentPlayerId(); + if (body is null) return BadRequest("missing body"); + + var match = new RoyaleMatchEntity + { + CompletedAt = DateTime.UtcNow, + Rank = body.Rank, + NumEliminations = body.NumEliminations, + SecondsAlive = body.SecondsAlive, + WalkGame = body.WalkGame, + CustomGame = body.CustomGame, + ChestsOpened = body.ChestsOpened, + ShieldPotionsConsumed = body.ShieldPotionsConsumed, + HealthPotionsConsumed = body.HealthPotionsConsumed, + SecondsInAir = body.SecondsInAir, + }; + db.RoyaleMatches.Add(match); + await db.SaveChangesAsync(); + + db.RoyaleMatchPlayers.Add(new RoyaleMatchPlayerEntity + { + MatchId = match.Id, + PlayerId = pid, + Rank = body.Rank, + NumEliminations = body.NumEliminations, + SecondsAlive = body.SecondsAlive, + }); + + // Compute XP awards. Match the watch's pattern: + // • Survival: 1 XP per 10 seconds alive + // • Eliminations: 25 XP each + // • Placement: bonus inversely proportional to rank + // • Custom games don't award XP + var awards = new List<(string label, int xp)>(); + if (!body.CustomGame) + { + var survivalXp = body.SecondsAlive / 10; + var killXp = body.NumEliminations * 25; + var rankXp = Math.Max(0, 200 - body.Rank * 5); + if (survivalXp > 0) awards.Add(($"Time Alive: {body.SecondsAlive}s", survivalXp)); + if (killXp > 0) awards.Add(($"Eliminations: {body.NumEliminations}", killXp)); + if (rankXp > 0) awards.Add(($"Rank #{body.Rank}", rankXp)); + if (body.Rank == 1) awards.Add(("Victory Royale!", 500)); + } + var totalAward = awards.Sum(a => a.xp); + + // Update progress. + var row = await db.RoyalePlayerProgress.FirstOrDefaultAsync(p => p.PlayerId == pid); + if (row is null) + { + row = new RoyalePlayerProgressEntity { PlayerId = pid }; + db.RoyalePlayerProgress.Add(row); + } + row.TotalXP += totalAward; + var (lvl, rankIdx, rankName, current, next, reward) = Compute(row.TotalXP); + row.Level = lvl; + row.RankIdx = rankIdx; + row.RankName = rankName; + row.CurrentLevelXPThreshold = current; + row.NextLevelXPThreshold = next; + row.NextLevelAcornReward = reward; + row.UpdatedAt = DateTime.UtcNow; + await db.SaveChangesAsync(); + + return Ok(new + { + XPAwardStrings = awards.Select(a => $"+{a.xp} XP — {a.label}").ToList(), + TotalXPAwarded = (long)totalAward, + NewProgress = new[] { ToWireProgress(row) }, + }); + } + + private static object ToWireProgress(RoyalePlayerProgressEntity p) => new + { + p.TotalXP, + p.Level, + p.RankIdx, + p.RankName, + p.CurrentLevelXPThreshold, + p.NextLevelXPThreshold, + p.NextLevelAcornReward, + }; + + private static object BlankProgress() => new + { + TotalXP = 0L, + Level = 1, + RankIdx = 0, + RankName = "Recruit", + CurrentLevelXPThreshold = 0L, + NextLevelXPThreshold = 100L, + NextLevelAcornReward = 50, + }; +} diff --git a/DorkNet.Server/Controllers/API/Sanitize/SanitizeController.cs b/DorkNet.Server/Controllers/API/Sanitize/SanitizeController.cs new file mode 100644 index 0000000..928eaf4 --- /dev/null +++ b/DorkNet.Server/Controllers/API/Sanitize/SanitizeController.cs @@ -0,0 +1,118 @@ +using Microsoft.AspNetCore.Mvc; +using Microsoft.Extensions.Logging; +using DorkNet.Server.Services; + +namespace DorkNet.Server.Controllers.API.Sanitize; + +/// +/// api.{rec.net,localhost}/api/sanitize/* — server-side profanity filter +/// the watch's text-input UI calls before allowing a room name / +/// invention name / chat message to be submitted. Delegates to +/// for the actual scrubbing. Response +/// shape: {Text (purified), IsClean (bool)}. +/// +[ApiController] +public class SanitizeController(ILogger logger) : ControllerBase +{ + [HttpPost("api/sanitize/v1")] + [HttpPost("api/sanitize/v1/text")] + public async Task Sanitize() + { + // RecRoom.StringSanitization.PurifyString sends JSON body + // {"Value":"","ReplacementChar":42} — verified from a + // live chat-send error trace ("Received malformed RecNet + // response: ...Text:{\"Value\":...}..."). Earlier we used to + // pass the whole body through as "text", which made Text in + // the response contain the literal JSON string, and the watch's + // LitJson then choked on the malformed wrap. + string text = string.Empty; + try + { + if (Request.HasFormContentType) + text = Request.Form["Text"].ToString(); + } + catch { } + if (string.IsNullOrEmpty(text)) + { + using var reader = new StreamReader(Request.Body); + var raw = await reader.ReadToEndAsync(); + if (!string.IsNullOrEmpty(raw)) + { + var first = raw.AsSpan().TrimStart(); + if (first.Length > 0 && first[0] == '{') + { + try + { + using var doc = System.Text.Json.JsonDocument.Parse(raw); + var root = doc.RootElement; + if (root.ValueKind == System.Text.Json.JsonValueKind.Object) + { + if (root.TryGetProperty("Value", out var vEl) && vEl.ValueKind == System.Text.Json.JsonValueKind.String) + text = vEl.GetString() ?? string.Empty; + else if (root.TryGetProperty("Text", out var tEl) && tEl.ValueKind == System.Text.Json.JsonValueKind.String) + text = tEl.GetString() ?? string.Empty; + else if (root.TryGetProperty("Input", out var iEl) && iEl.ValueKind == System.Text.Json.JsonValueKind.String) + text = iEl.GetString() ?? string.Empty; + } + } + catch { text = raw; } + } + else + { + text = raw; + } + } + } + // StringSanitization.PurifyString_d__9 chains Core.Post to + // ApiCallback via ExpectPrimitiveResponse, so the watch's + // LitJson importer wants a BARE JSON string (e.g. "dwadwa"), not + // an object body. Returning {Text, IsClean} as we did before + // makes LitJson hand the Dictionary to Convert.ChangeType(_, typeof(string)) + // which throws "Object must implement IConvertible". Strip the + // envelope — but use Content() with explicit application/json so + // ASP.NET Core's StringOutputFormatter doesn't serialize as + // text/plain (raw string sans quotes), which the watch then sees + // as an empty cleanVersion after LitJson decode. + var purified = ProfanityFilter.Purify(text); + var json = System.Text.Json.JsonSerializer.Serialize(purified); + logger.LogInformation("[sanitize] input=\"{Input}\" purified=\"{Purified}\" json={Json}", text, purified, json); + return Content(json, "application/json"); + } + + public sealed class SanitizeBody + { + public string? Text { get; set; } + public string? Input { get; set; } + public string? Value { get; set; } + } + + /// POST api/sanitize/v1/purifyString — returns + /// {Text} with profanity replaced. Wire shape matches + /// StringSanitization.PurifyString. + [HttpPost("api/sanitize/v1/purifyString")] + public IActionResult PurifyString( + [FromBody] SanitizeBody? body, + [FromForm(Name = "Text")] string? textForm, + [FromForm(Name = "Input")] string? inputForm, + [FromForm(Name = "Value")] string? valueForm, + [FromQuery(Name = "text")] string? textQuery) + { + var input = body?.Text ?? body?.Input ?? body?.Value + ?? textForm ?? inputForm ?? valueForm ?? textQuery ?? string.Empty; + return Ok(new { Text = ProfanityFilter.Purify(input) }); + } + + /// POST api/sanitize/v1/requestIsStringPure — + /// returns a bare JSON bool. Watch's ExpectPrimitiveResponse + /// rejects object bodies here. + [HttpPost("api/sanitize/v1/requestIsStringPure")] + public IActionResult IsStringPure( + [FromBody] SanitizeBody? body, + [FromForm(Name = "Text")] string? textForm, + [FromQuery(Name = "text")] string? textQuery) + { + var input = body?.Text ?? body?.Input ?? body?.Value + ?? textForm ?? textQuery ?? string.Empty; + return Ok(ProfanityFilter.IsClean(input)); + } +} diff --git a/DorkNet.Server/Controllers/API/Settings/V2/SettingsController.cs b/DorkNet.Server/Controllers/API/Settings/V2/SettingsController.cs new file mode 100644 index 0000000..69e1255 --- /dev/null +++ b/DorkNet.Server/Controllers/API/Settings/V2/SettingsController.cs @@ -0,0 +1,101 @@ +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Mvc; +using Microsoft.EntityFrameworkCore; +using DorkNet.Server.Auth; +using DorkNet.Server.Data; +using DorkNet.Server.Data.Entities; +using System.Text.Json.Serialization; + +namespace DorkNet.Server.Controllers.API.Settings.V2; + +/// +/// api.rec.net/api/settings/v2 — player settings download / set / remove. +/// URL patterns (verified by disassembling RecNet.Settings$$DowloadLocalPlayerSettings +/// at RVA 0xAF55B0 and RecNet.Settings$$StoreLocalPlayerSetting at RVA 0xAF5650): +/// +/// GET api/settings/v2/ — returns List<{Key, Value}> +/// POST api/settings/v2/set — body { Key, Value } (set or update one) +/// POST api/settings/v2/remove — body { Key } (delete one) +/// +/// The CLIENT side parses the GET response with Util.DeserializeList<Setting> +/// and reads each entry's "Key" and "Value" fields (verified by string literal +/// references in the deserializer body — both are PascalCase). +/// +/// Returning a Dictionary<string,string> here serializes as "{}" +/// when empty, which the client casts to a List and dies with +/// InvalidCastException: Unable to cast object of type 'Dictionary`2' to +/// type 'List`1'. Always return a real List. +/// +[ApiController] +[Route("api/[controller]/v2")] +[Authorize] +public class SettingsController(DorkNetDbContext db) : ControllerBase +{ + private long CurrentPlayerId => this.RequireCurrentPlayerId(); + + [HttpGet] + [HttpGet("get")] + public async Task GetSettings() + { + var settings = await db.PlayerSettings + .Where(s => s.PlayerId == CurrentPlayerId) + .Select(s => new SettingDto { Key = s.Key, Value = s.Value }) + .ToListAsync(); + return Ok(settings); + } + + [HttpPost("set")] + public async Task SetSetting([FromBody] SetSettingRequest req) + { + var existing = await db.PlayerSettings + .FirstOrDefaultAsync(s => s.PlayerId == CurrentPlayerId && s.Key == req.Key); + + if (existing is null) + { + db.PlayerSettings.Add(new PlayerSettingEntity + { + PlayerId = CurrentPlayerId, + Key = req.Key, + Value = req.Value, + }); + } + else + { + existing.Value = req.Value; + } + + await db.SaveChangesAsync(); + return Ok(new SettingDto { Key = req.Key, Value = req.Value }); + } + + [HttpPost("remove")] + public async Task RemoveSetting([FromBody] SetSettingRequest req) + { + var existing = await db.PlayerSettings + .FirstOrDefaultAsync(s => s.PlayerId == CurrentPlayerId && s.Key == req.Key); + if (existing is not null) + { + db.PlayerSettings.Remove(existing); + await db.SaveChangesAsync(); + } + return Ok(); + } +} + +public class SettingDto +{ + [JsonPropertyName("Key")] + public string Key { get; set; } = string.Empty; + + [JsonPropertyName("Value")] + public string Value { get; set; } = string.Empty; +} + +public class SetSettingRequest +{ + [JsonPropertyName("Key")] + public string Key { get; set; } = string.Empty; + + [JsonPropertyName("Value")] + public string Value { get; set; } = string.Empty; +} diff --git a/DorkNet.Server/Controllers/API/Store/PurchaseController.cs b/DorkNet.Server/Controllers/API/Store/PurchaseController.cs new file mode 100644 index 0000000..85adadb --- /dev/null +++ b/DorkNet.Server/Controllers/API/Store/PurchaseController.cs @@ -0,0 +1,186 @@ +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Mvc; +using DorkNet.Server.Auth; +using DorkNet.Server.Services; + +namespace DorkNet.Server.Controllers.API.Store; + +/// +/// api.rec.net/api/purchase/v1/* — real purchase flow that verifies +/// the player has enough currency, deducts it, and grants the item +/// to their inventory. Replaces the old "ack-everything" stubs in +/// AllEndpointsController that let the watch think it bought +/// something but never actually persisted the transaction. +/// +/// The 2020 client follows a three-step flow when the player taps +/// "buy" on a Shop item: +/// 1. POST initiatepurchase — server validates the item exists + +/// player has funds, returns a transaction id (we just echo +/// back the item id; no real reservation needed for a private +/// server's threat model). +/// 2. POST processpurchase — server actually deducts currency, +/// grants the item to the avatar's InventoryJson list, returns +/// the new balance. +/// 3. POST completepurchase — final ack; client uses this to +/// flip its local UI state from "purchasing…" to "owned". +/// We keep all three so the watch's flow doesn't deadlock waiting +/// for one or another, but the actual mutation happens in +/// processpurchase. initiate and complete just validate + ack. +/// +[ApiController] +[Authorize] +public class PurchaseController(StoreService store, ILogger logger) : ControllerBase +{ + public sealed record PurchaseRequest(long? ItemId, string? Slug, int? Quantity); + + /// POST initiatepurchase — validate the item exists and + /// the caller has enough currency, but don't mutate yet. Returns + /// the resolved item id so the watch can echo it back on + /// processpurchase. + [HttpPost("api/purchase/v1/initiatepurchase")] + [HttpPost("api/purchase/v2/initiatepurchase")] + [HttpPost("api/purchase/v3/initiatepurchase")] + public async Task Initiate([FromForm] PurchaseRequest body) + { + var item = await ResolveItemAsync(body); + if (item is null) return Ok(new { success = false, error = "item_not_found" }); + return Ok(new + { + success = true, + error = "", + ItemId = item.Id, + Slug = item.Slug, + // Hand back a token the watch may include in + // processpurchase. We accept anything on the way in, so + // this is informational. + TransactionId = $"txn-{item.Id}-{DateTime.UtcNow.Ticks}", + }); + } + + /// POST processpurchase — atomic charge + grant. The + /// real work. Returns success+balance on completion or the + /// specific error code on failure (insufficient_funds, + /// item_not_available, etc.) so the watch can show the right UI + /// message. + [HttpPost("api/purchase/v1/processpurchase")] + [HttpPost("api/purchase/v2/processpurchase")] + [HttpPost("api/purchase/v3/processpurchase")] + public async Task Process([FromForm] PurchaseRequest body) + { + var pid = this.RequireCurrentPlayerId(); + var item = await ResolveItemAsync(body); + if (item is null) return Ok(new { success = false, error = "item_not_found" }); + + var result = await store.PurchaseAsync(pid, item.Id); + logger.LogInformation( + "[purchase] player={Pid} item={Item} success={Success} error={Error} balance={Balance}", + pid, item.Slug, result.Success, result.Error ?? "", result.Balance ?? 0); + return Ok(new + { + success = result.Success, + error = result.Error ?? "", + Balance = result.Balance ?? 0, + CurrencyType = item.CurrencyType, + ItemId = item.Id, + Slug = result.Slug ?? item.Slug, + }); + } + + /// POST completepurchase — final ack. Client flips its + /// UI to "owned" state and refreshes the avatar selector. + [HttpPost("api/purchase/v1/completepurchase")] + [HttpPost("api/purchase/v2/completepurchase")] + [HttpPost("api/purchase/v3/completepurchase")] + public IActionResult Complete() => + Ok(new { success = true, error = "" }); + + /// POST cancelpurchase — client retreated mid-flow + /// (closed the panel, network blip, etc.). Nothing to roll back + /// since we don't reserve in initiate. + [HttpPost("api/purchase/v1/cancelpurchase")] + [HttpPost("api/purchase/v2/cancelpurchase")] + [HttpPost("api/purchase/v3/cancelpurchase")] + public IActionResult Cancel() => + Ok(new { success = true, error = "" }); + + /// POST cleanuppending — client startup probe to release + /// any stale "in-flight" purchases from a crashed previous + /// session. We don't track in-flight state, so always-ack is + /// correct. + [HttpPost("api/purchase/v1/cleanuppending")] + [HttpPost("api/purchase/v2/cleanuppending")] + [HttpPost("api/purchase/v3/cleanuppending")] + public IActionResult CleanupPending() => + Ok(new { success = true, error = "" }); + + /// POST subscription/v1/* — Rec Room+. The private + /// server has no real billing integration, so buying RR+ is a yes/ack + /// operation and cancel is also acknowledged. Returning an active + /// subscription object keeps the client out of platform IAP UI. + [HttpPost("/subscription/v1")] + [HttpPost("/subscription/v1/purchase")] + [HttpPost("/subscription/v1/buy")] + [HttpPost("/subscription/v1/cancel")] + [HttpPost("/api/subscription/v1")] + [HttpPost("/api/subscription/v1/purchase")] + [HttpPost("/api/subscription/v1/buy")] + [HttpPost("/api/subscription/v1/cancel")] + public IActionResult SubscriptionAck() + { + var pid = this.CurrentPlayerId() ?? 0; + logger.LogInformation("[subscription] ack requested by player {Pid}", pid); + return Ok(SubscriptionResponse(pid)); + } + + [HttpGet("/subscription/v1")] + [HttpGet("/subscription/v1/current")] + [HttpGet("/subscription/v1/mine")] + [HttpGet("/api/subscription/v1")] + [HttpGet("/api/subscription/v1/current")] + [HttpGet("/api/subscription/v1/mine")] + public IActionResult SubscriptionStatus() => + Ok(SubscriptionResponse(this.CurrentPlayerId() ?? 0)); + + private static Dictionary SubscriptionResponse(long playerId) + { + var now = DateTime.UtcNow; + var pascal = new Dictionary + { + ["SubscriptionId"] = $"dorknet-rrplus-{playerId}", + ["RecNetPlayerId"] = playerId, + ["IsActive"] = true, + ["StartedAt"] = now, + ["CurrentPeriodEnd"] = now.AddYears(10), + ["RenewalDate"] = now.AddYears(10), + }; + var camel = new Dictionary + { + ["subscriptionId"] = $"dorknet-rrplus-{playerId}", + ["recNetPlayerId"] = playerId, + ["isActive"] = true, + ["startedAt"] = now, + ["currentPeriodEnd"] = now.AddYears(10), + ["renewalDate"] = now.AddYears(10), + }; + return new Dictionary + { + ["success"] = true, + ["error"] = "", + ["IsSubscribed"] = true, + ["isSubscribed"] = true, + ["Active"] = true, + ["active"] = true, + ["Subscription"] = pascal, + ["subscription"] = camel, + }; + } + + private async Task ResolveItemAsync(PurchaseRequest body) + { + if (body.ItemId is long id && id > 0) + return await store.GetByIdAsync(id); + if (!string.IsNullOrWhiteSpace(body.Slug)) + return await store.GetBySlugAsync(body.Slug); + return null; + } +} diff --git a/DorkNet.Server/Controllers/API/Store/StorefrontsBuyController.cs b/DorkNet.Server/Controllers/API/Store/StorefrontsBuyController.cs new file mode 100644 index 0000000..ff66f5c --- /dev/null +++ b/DorkNet.Server/Controllers/API/Store/StorefrontsBuyController.cs @@ -0,0 +1,422 @@ +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Mvc; +using Microsoft.EntityFrameworkCore; +using DorkNet.Models.Notification; +using DorkNet.Server.Auth; +using DorkNet.Server.Controllers.API.RoomKeys; +using DorkNet.Server.Data; +using DorkNet.Server.Data.Entities; +using DorkNet.Server.Services; + +namespace DorkNet.Server.Controllers.API.Store; + +/// +/// api.rec.net/api/storefronts/v2/{buyItem,buyTier,buyElite} — the +/// secondary purchase URL family used by the watch's gift-drop +/// tiles and season-pass tier buttons. Wire request matches +/// RecNet.RequestPurchaseItemDTO: +/// StorefrontType, PurchasableItemId, CurrencyType, +/// CouponConsumablePlayerMappingId, Gift. +/// +/// Response is PurchaseBalanceUpdateResponseDTO: +/// BalanceUpdates: List<BalanceUpdateDTO>. The client +/// applies these balance deltas optimistically + reads the optional +/// gift package from inventory after. +/// +/// The primary purchase URL (api/purchase/v1/*) is handled +/// elsewhere by PurchaseController; this just covers the v2 surface. +/// +[ApiController] +[Authorize] +public class StorefrontsBuyController( + DorkNetDbContext db, + LevelService level, + NotificationService notifications) : ControllerBase +{ + private long Me => this.RequireCurrentPlayerId(); + + public sealed class RequestPurchaseItemDto + { + public int StorefrontType { get; set; } + public int PurchasableItemId { get; set; } + public int CurrencyType { get; set; } = 2; + public long? CouponConsumablePlayerMappingId { get; set; } + public GiftDto? Gift { get; set; } + } + + public sealed class GiftDto + { + public long ToPlayerId { get; set; } + public string? Message { get; set; } + public bool Anonymous { get; set; } + public int GiftContext { get; set; } + } + + [HttpPost("api/storefronts/v2/buyItem")] + public async Task BuyItem([FromBody] RequestPurchaseItemDto req) + { + if (req is null) return BadRequest("missing body"); + var pid = Me; + var item = await db.StoreItems.FirstOrDefaultAsync(s => s.Id == req.PurchasableItemId && s.IsActive); + if (item is null) return NotFound(BalanceUpdateResponse(0, 2 /* CurrencyType */, null, null)); + + var balance = await level.GetBalanceAsync(pid, req.CurrencyType); + if (balance < item.Price) + { + // Insufficient funds — return current balance, no item granted. + return Ok(BalanceUpdateResponse(balance, req.CurrencyType, null, null)); + } + + // Gift block (when present) means the BUYER spends the tokens but + // the ITEM goes to Gift.ToPlayerId. Self-gifting is disallowed — + // the watch's gift UI prevents it, but guard server-side too. + var giftTarget = req.Gift is { ToPlayerId: > 0 } g && g.ToPlayerId != pid + ? g + : null; + var recipient = giftTarget?.ToPlayerId ?? pid; + var isGift = giftTarget is not null; + + var newBalance = await level.GrantCurrencyAsync(pid, req.CurrencyType, -item.Price, + isGift ? $"giftItem:{item.Slug}->{recipient}" : $"buyItem:{item.Slug}"); + + // Grant the item to the RECIPIENT (= buyer for self-purchase, gift + // target for gift purchase). Avatar payloads append to + // AvatarEntity.InventoryJson; consumables and everything else + // insert a PlayerInventory row. + if (StoreService.TryGetAvatarItemPayload(item.Slug, out _, out var avatarItemDesc)) + { + await GrantWardrobeAsync(recipient, StoreService.InventoryAvatarItemDesc(avatarItemDesc)); + } + else + { + await GrantInventoryAsync(recipient, item.Slug); + } + + // Insert a real GiftPackageEntity for the gift-box flow. The watch + // shows a gift-box animation after a purchase, then POSTs the + // package Id to /api/avatar/v2/gifts/consume/ to claim it. If the + // Id isn't a real row, that consume returns 404 and the watch + // logs 'Server failed to consume gift … HTTP Error 404' — the + // user's symptom on every successful buy. By writing the row + // synchronously and returning its DB-assigned Id in the + // BalanceUpdate response, the subsequent consume succeeds. + StoreService.TryGetAvatarItemPayload(item.Slug, out var avatarItemType, out avatarItemDesc); + var inventoryAvatarDesc = StoreService.InventoryAvatarItemDesc(avatarItemDesc); + StoreService.TryGetConsumableItemDesc(item.Slug, out var consumableItemDesc); + if (StoreService.TryGetHairDyePayload(item.Slug, out var hairDyeConsumableDesc, out var hairDyeColorGuid)) + { + avatarItemType = 1; + inventoryAvatarDesc = hairDyeColorGuid; + consumableItemDesc = hairDyeConsumableDesc; + } + var gift = new GiftPackageEntity + { + RecipientPlayerId = recipient, + FromPlayerId = isGift && !giftTarget!.Anonymous ? (int?)pid : null, + AvatarItemType = string.IsNullOrEmpty(inventoryAvatarDesc) ? null : avatarItemType, + AvatarItemDescOrHairDyeDesc = inventoryAvatarDesc, + ConsumableItemDesc = consumableItemDesc, + CurrencyType = req.CurrencyType, + Currency = 0, + Xp = 0, + Level = 1, + GiftContext = isGift ? giftTarget!.GiftContext : 0, + GiftRarity = 0, + Message = isGift ? (giftTarget!.Message ?? string.Empty) : string.Empty, + Platform = -1, + PackageVariant = "Standard", + PackageMaterial = string.Empty, + Consumed = false, + IsValid = true, + SupportsCurrentPlatform = true, + }; + db.GiftPackages.Add(gift); + await db.SaveChangesAsync(); + + // Push the buyer's balance update so their wallet UI refreshes. + await notifications.NotifyAsync(pid, + PushNotificationId.StorefrontBalancePurchase, + new { req.CurrencyType, Balance = newBalance, ItemId = item.Id, item.Slug }); + + if (isGift) + { + // Tell the recipient a gift is waiting. The watch dispatches + // GiftPackageReceived (id=30) to refresh /api/avatar/v2/gifts + // and pop the "you got a gift" toast; the gift box itself + // appears next time the recipient opens their inventory. + await notifications.NotifyAsync(recipient, + PushNotificationId.GiftPackageReceived, + new + { + Id = gift.Id, + Platform = -1, + Xp = 0, + Level = 1, + GiftRarity = 0, + GiftContext = gift.GiftContext, + FromPlayerId = gift.FromPlayerId, + AvatarItemType = gift.AvatarItemType, + AvatarItemDesc = avatarItemDesc, + ConsumableItemDesc = gift.ConsumableItemDesc ?? string.Empty, + Message = gift.Message ?? string.Empty, + }); + } + + // For gift purchases the buyer doesn't get a box — their flow + // ends with the balance update. Returning an empty Data array + // (giftPackageId = null) makes the watch's BuyItem callback + // skip the gift-receive animation on the sender's screen. + return Ok(BalanceUpdateResponse(newBalance, req.CurrencyType, + isGift ? null : item.Slug, + isGift ? null : gift.Id)); + } + + [HttpPost("api/storefronts/v2/buyTier")] + public async Task BuyTier([FromBody] RequestPurchaseItemDto req) + { + // Season-pass tier: no per-item table, so we just deduct a + // small fixed cost (1 token) per tier and ack. A full + // season-pass system would have a SeasonProgressEntity. + var pid = Me; + const int TierCost = (int)StoreService.StoreItemPrice; + var balance = await level.GetBalanceAsync(pid, req.CurrencyType); + if (balance < TierCost) return Ok(BalanceUpdateResponse(balance, req.CurrencyType)); + var newBalance = await level.GrantCurrencyAsync(pid, req.CurrencyType, -TierCost, + $"buyTier:{req.PurchasableItemId}"); + return Ok(BalanceUpdateResponse(newBalance, req.CurrencyType)); + } + + [HttpPost("api/storefronts/v2/buyElite")] + public async Task BuyElite([FromBody] RequestPurchaseItemDto req) + { + // Elite-tier purchase (paid premium track). Without real + // money flow on a private server we just ack. + var pid = Me; + var balance = await level.GetBalanceAsync(pid, req.CurrencyType); + return Ok(BalanceUpdateResponse(balance, req.CurrencyType)); + } + + /// GET api/storefronts/v1/buyRoomKey — purchase a + /// persisted room key. The 2020.12 client sends roomKeyId and the + /// price it displayed so we reject stale-price purchases. + [HttpGet("api/storefronts/v1/buyRoomKey")] + public async Task BuyRoomKey( + [FromQuery] long roomKeyId, + [FromQuery] int requestedPrice) + { + var pid = Me; + var key = await db.RoomKeys.FirstOrDefaultAsync(k => k.Id == roomKeyId && !k.IsDeleted); + if (key is null) + return Ok(RoomKeyPurchaseResponse(RoomKeyStatus.DoesNotExist, null, await level.GetBalanceAsync(pid, 2))); + if (key.CreatorPlayerId == pid) + return Ok(RoomKeyPurchaseResponse(RoomKeyStatus.PlayerAlreadyOwns, key, await level.GetBalanceAsync(pid, 2))); + if (requestedPrice != key.Price) + return Ok(RoomKeyPurchaseResponse(RoomKeyStatus.InvalidParameters, key, await level.GetBalanceAsync(pid, 2))); + + var existing = await db.RoomKeyPurchases + .FirstOrDefaultAsync(p => p.RoomKeyId == key.Id && p.PlayerId == pid); + if (existing is not null) + return Ok(RoomKeyPurchaseResponse(RoomKeyStatus.PlayerAlreadyOwns, key, await level.GetBalanceAsync(pid, 2))); + + var balance = await level.GetBalanceAsync(pid, 2); + if (balance < key.Price) + return Ok(RoomKeyPurchaseResponse(RoomKeyStatus.PurchaseFailed, key, balance)); + + var newBalance = await level.GrantCurrencyAsync(pid, 2, -key.Price, $"buyRoomKey:{key.Id}"); + db.RoomKeyPurchases.Add(new RoomKeyPurchaseEntity + { + RoomKeyId = key.Id, + PlayerId = pid, + PaidPrice = key.Price, + }); + await db.SaveChangesAsync(); + + if (key.CreatorPlayerId != pid && key.Price > 0) + await level.GrantCurrencyAsync(key.CreatorPlayerId, 2, key.Price, $"sellRoomKey:{key.Id}"); + + return Ok(RoomKeyPurchaseResponse(RoomKeyStatus.Success, key, newBalance)); + } + + // ── Helpers ────────────────────────────────────────────────────────── + + private async Task GrantWardrobeAsync(long pid, string guid) + { + var avatar = await db.Avatars.FirstOrDefaultAsync(a => a.PlayerId == pid); + if (avatar is null) + { + avatar = new AvatarEntity { PlayerId = pid }; + db.Avatars.Add(avatar); + } + try + { + var list = System.Text.Json.JsonSerializer + .Deserialize>(avatar.InventoryJson) ?? new(); + if (!list.Contains(guid)) + { + list.Add(guid); + avatar.InventoryJson = System.Text.Json.JsonSerializer.Serialize(list); + avatar.UpdatedAt = DateTime.UtcNow; + await db.SaveChangesAsync(); + } + } + catch + { + avatar.InventoryJson = System.Text.Json.JsonSerializer.Serialize(new[] { guid }); + avatar.UpdatedAt = DateTime.UtcNow; + await db.SaveChangesAsync(); + } + } + + private async Task GrantInventoryAsync(long pid, string slug) + { + var row = await db.PlayerInventory + .FirstOrDefaultAsync(p => p.PlayerId == pid && p.ItemSlug == slug); + if (row is null) + { + db.PlayerInventory.Add(new PlayerInventoryEntity + { + PlayerId = pid, + ItemSlug = slug, + Quantity = 1, + }); + } + else + { + row.Quantity += 1; + } + await db.SaveChangesAsync(); + } + + private static object BalanceUpdateResponse(long balance, int currencyType, + string? grantedSlug = null, long? giftPackageId = null) + { + // PurchaseBalanceUpdateResponseDTO wire shape, verified + // against Cpp2IL_ISIL/.../RecNet/Avatars_NestedType_GiftPackage.txt: + // + // PurchaseBalanceUpdateResponseDTO EXTENDS BalanceResponseDTO, + // so the watch first runs BalanceResponseDTO.Deserialize which + // does Util.GetKey("Balance") / GetKey("CurrencyType") + // / GetKey("BalanceType") — REQUIRED, KeyNotFoundException + // on miss. Mirror the trio on the OUTER object. + // + // Then it reads BalanceUpdates: List>. + // Each inner BalanceUpdateDTO has UpdateResponse + Data, where + // Data is List. GiftPackage.Deserialize REQUIRES: + // "Id" (long) | Util.GetKey + // "Platform" (int) | Util.GetKey + // "Xp" (int) | Util.GetKey + // "Level" (int) | Util.GetKey + // "GiftRarity" (int) | Util.GetKey + // "GiftContext" (int) | Util.GetKey + // Plus optional FromPlayerId / AvatarItemType / AvatarItemDesc / + // ConsumableItemDesc / EquipmentPrefabName / EquipmentModificationGuid / + // CurrencyType / Currency / Message. + // + // The previous shape shipped {CurrencyType,Balance,BalanceType,Platform} + // in the Data array — those don't match GiftPackage's required keys, + // so Util.GetKey("Id") threw KeyNotFoundException, the + // ApiCallback received a non-null error string, and StorePurchaseItem + // Screen logged the error as "Null". The user's + // output_log.txt:920 shows that callback path firing on every + // purchase attempt. New shape ships a real (synthetic) GiftPackage + // with all required keys + the granted item's GUID under + // AvatarItemDesc so the watch's gift-receive flow can preview the + // unlocked item. + // + // CRITICAL: the watch follows up every purchase with a POST to + // /api/avatar/v2/gifts/consume/ containing this Id. If the Id + // isn't a real GiftPackageEntity row in the DB, consume returns + // 404 and the watch's logs 'Server failed to + // consume gift … HTTP Error 404', which the player-destroy + // cascade picks up on (the user's symptom on every buy: + // game crashes after opening the gift box). Caller MUST pass + // = the saved + // GiftPackageEntity.Id. The previous code used + // DateTime.UtcNow.Ticks as a fake id. + var giftId = giftPackageId ?? DateTime.UtcNow.Ticks; + // Pull the GUID off the slug for wardrobe-* purchases so the + // watch's GiftPackage receives the AvatarItem GUID and can + // render the unlock animation. dorknet-* slugs leave the desc + // empty (those are non-wardrobe SKUs). + var avatarItemType = 0; + var avatarItemDesc = string.Empty; + var consumableItemDesc = string.Empty; + if (grantedSlug is not null) + { + StoreService.TryGetAvatarItemPayload(grantedSlug, out avatarItemType, out avatarItemDesc); + StoreService.TryGetConsumableItemDesc(grantedSlug, out consumableItemDesc); + if (StoreService.TryGetHairDyePayload(grantedSlug, out var hairDyeConsumableDesc, out var hairDyeColorGuid)) + { + avatarItemType = 1; + avatarItemDesc = hairDyeColorGuid; + consumableItemDesc = hairDyeConsumableDesc; + } + } + + var giftPackage = new + { + // REQUIRED via Util.GetKey — KeyNotFoundException on miss. + // Verified keys at NLEKGNENMCO_NestedType_LOCNECLOHCA.txt:792-946: + // Id, Message, FromPlayerId, AvatarItemType, AvatarItemDesc, + // ConsumableItemDesc, EquipmentPrefabName, + // EquipmentModificationGuid, Platform, PlatformsToSpawnOn, + // BalanceType, CurrencyType, Currency, Xp, GiftRarity, + // GiftContext. Missing any → "Failed to download gifts: + // Malformed Response" (and on a purchase path, the buy + // times out because the response never deserializes). + Id = giftId, + Platform = 0, + PlatformsToSpawnOn = -1, + BalanceType = 0, + Xp = 0, + Level = 1, + GiftRarity = 0, + GiftContext = 0, + // Optional — surfaced to the watch's gift-preview UI. + FromPlayerId = (int?)null, + AvatarItemType = string.IsNullOrEmpty(avatarItemDesc) ? (int?)null : avatarItemType, + AvatarItemDesc = avatarItemDesc, + AvatarItemDescOrHairDyeDesc = StoreService.InventoryAvatarItemDesc(avatarItemDesc), + ConsumableItemDesc = consumableItemDesc, + EquipmentPrefabName = string.Empty, + EquipmentModificationGuid = string.Empty, + CurrencyType = currencyType, + Currency = 0, + Message = string.Empty, + Consumed = false, + ErrorMessage = string.Empty, + }; + + return new + { + // BalanceResponseDTO required trio. + Balance = balance, + CurrencyType = currencyType, + BalanceType = 0, + // PurchaseBalanceUpdateResponseDTO.BalanceUpdates = + // List>. + BalanceUpdates = new[] + { + new + { + UpdateResponse = 0, // UpdateResponseTypes.Success + Data = grantedSlug is null + ? Array.Empty() + : new object[] { giftPackage }, + }, + }, + }; + } + + private static object RoomKeyPurchaseResponse(RoomKeyStatus status, RoomKeyEntity? key, long balance) => new + { + RoomKeyResponse = RoomKeysController.RoomKeyResponse(status, key), + BalanceUpdateResponse = new + { + Balance = balance, + CurrencyType = 2, + BalanceType = 0, + BalanceUpdates = Array.Empty(), + }, + }; +} diff --git a/DorkNet.Server/Controllers/API/Store/StorefrontsController.cs b/DorkNet.Server/Controllers/API/Store/StorefrontsController.cs new file mode 100644 index 0000000..516ff60 --- /dev/null +++ b/DorkNet.Server/Controllers/API/Store/StorefrontsController.cs @@ -0,0 +1,515 @@ +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Mvc; +using Microsoft.EntityFrameworkCore; +using Microsoft.Extensions.Configuration; +using System.Text.Json; +using DorkNet.Server.Auth; +using DorkNet.Server.Data; +using DorkNet.Server.Data.Entities; +using DorkNet.Server.Services; + +namespace DorkNet.Server.Controllers.API.Store; + +/// +/// api.rec.net/api/storefronts/v* — real implementation backed by the +/// catalog (StoreItems table). Replaces the +/// previous AllEndpointsController stubs that returned empty lists, +/// which made the watch's Shop tab look broken. +/// +/// Wire shape: every endpoint returns either a list of items (for +/// catalog endpoints) or a StorefrontDTO with a StoreItems array. +/// +[ApiController] +public class StorefrontsController( + StoreService store, + DorkNetDbContext db, + IConfiguration config, + LevelService level, + DomainConfig domain) : ControllerBase +{ + /// GET api/storefronts/v3/all — full active catalog. The + /// watch's Shop tab uses this to populate every category. + [HttpGet("api/storefronts/v1/all")] + [HttpGet("api/storefronts/v2/all")] + [HttpGet("api/storefronts/v3/all")] + [AllowAnonymous] + public async Task All() + { + var items = await store.GetAllActiveAsync(); + return Ok(items.Select(i => StoreService.ToItemDto(i, domain.Apex)).ToArray()); + } + + /// GET api/storefronts/v3/skus — alias for the full + /// catalog. Some 2020 client paths call this instead of /all + /// during the watch's "browse all SKUs" flow. + [HttpGet("api/storefronts/v3/skus")] + [AllowAnonymous] + public async Task Skus() + { + var items = await store.GetAllActiveAsync(); + return Ok(items.Select(i => StoreService.ToItemDto(i, domain.Apex)).ToArray()); + } + + /// GET api/storefronts/v1/p2p/betaEnabled — + /// raw Boolean gate for player-to-player sales. Returning false + /// disables the beta affordance without lying about catalog state. + [HttpGet("api/storefronts/v1/p2p/betaEnabled")] + [AllowAnonymous] + public IActionResult P2PBetaEnabled() => Content("false", "application/json"); + + /// GET api/storefronts/v1/trialInvention/duration. + /// The 2020.12 client's public promise projects the Duration + /// property from this object, so the wire body is not a bare int. + [HttpGet("api/storefronts/v1/trialInvention/duration")] + [AllowAnonymous] + public IActionResult TrialInventionDuration() => Ok(new + { + Duration = Math.Max(0, config.GetValue("Store:TrialInventionDurationSeconds", 300)), + }); + + /// GET api/storefronts/v3/season — items currently in the + /// season-pass storefront. Empty for now since we don't run + /// seasons; returning an empty list rather than the catch-all so + /// the deserialiser sees the right shape. + [HttpGet("api/storefronts/v3/season")] + [AllowAnonymous] + public async Task Season() + { + var items = await store.GetActiveByStorefrontAsync("season:1"); + return Ok(items.Select(i => StoreService.ToItemDto(i, domain.Apex)).ToArray()); + } + + /// GET api/storefronts/v3/giftdrop — list of available + /// gift-drop storefront ids. The watch then queries each one via + /// /giftdropstore/{id} below to render the rotating shelf. + [HttpGet("api/storefronts/v3/giftdrop")] + [AllowAnonymous] + public IActionResult GiftDropList() => + Ok(StoreService.GetStorefrontDefinitions() + .Where(s => s.StorefrontType is > 0) + .Select(s => s.StorefrontType!.Value) + .Distinct() + .OrderBy(i => i) + .ToArray()); + + /// GET api/storefronts/v3/giftdropstore/{id} — one + /// storefront shelf. The {id} is a value from the + /// RecNet.StorefrontTypes enum (verified against + /// Cpp2IL_CS/.../RecNet/StorefrontTypes.cs): + /// None=0, LaserTag=1, RecCenter=2, Watch=3, + /// Quest_*=100..103, RecRoyale=200, Cafe=300, Paintball=400..406, + /// Bowling=500, StuntRunner=600, DormMirror=700. + /// The Watch storefront (id=3) is the in-game Shop's Featured / + /// Clothing / Other grid. That grid assumes every gift drop has a + /// renderable AvatarItemDesc, so only wardrobe-backed rows are + /// surfaced there. Other storefronts use the canonical + /// giftdrop:{id} key plus the shared rro and + /// all admin keys. + /// + [HttpGet("api/storefronts/v3/giftdropstore/{id:int}")] + [AllowAnonymous] + public async Task GiftDropStore(int id) + { + var (key, items) = id switch + { + 3 => ($"watch", await store.GetRenderableGiftDropItemsAsync()), + _ => (StoreService.StorefrontKeyForType(id), await store.GetRoomStorefrontItemsAsync(id)), + }; + return Ok(StoreService.ToStorefrontDto(key, items, domain.Apex, id)); + } + + /// GET api/storefronts/v1/balance/{type} — currency + /// balance for the caller. Wire shape is a list of + /// {CurrencyType, Balance, BalanceType, Platform} entries. The + /// watch displays the Balance value next to item prices. + [HttpGet("api/storefronts/v1/balance/{currencyType:int}")] + [HttpGet("api/storefronts/v2/balance/{currencyType:int}")] + [HttpGet("api/storefronts/v3/balance/{currencyType:int}")] + [HttpGet("api/storefronts/v4/balance/{currencyType:int}")] + [HttpGet("api/storefronts/v4/balance")] + [Authorize] + public async Task Balance(int currencyType = 2, + [FromServices] LevelService level = null!) + { + var pid = this.RequireCurrentPlayerId(); + var balance = await level.GetBalanceAsync(pid, currencyType); + return Ok(new[] + { + new + { + CurrencyType = currencyType, + BalanceType = 0, + Balance = balance, + Platform = 0, + }, + }); + } + + /// GET api/storefronts/v{1-4}/balanceAddType/{type}/{tier} + /// — purchase-tier metadata for a currency. The watch uses this + /// to populate the "add tokens" tier list. Empty for a private + /// server with no IAP. + [HttpGet("api/storefronts/v1/balanceAddType/{currencyType:int}/{tierId:int}")] + [HttpGet("api/storefronts/v2/balanceAddType/{currencyType:int}/{tierId:int}")] + [HttpGet("api/storefronts/v3/balanceAddType/{currencyType:int}/{tierId:int}")] + [HttpGet("api/storefronts/v4/balanceAddType/{currencyType:int}/{tierId:int}")] + public IActionResult BalanceAddType(int currencyType, int tierId) + => Ok(BalanceAddConfig(currencyType, tierId)); + + /// GET api/storefronts/v2/balance — every currency + /// balance for the caller in a single response. The watch's + /// wallet UI uses this to render all four currency chips at once. + /// CurrencyType per CurrencyType.cs: 0=Token, 1=Cheer, + /// 2=Coin, 3=Premium. + [HttpGet("api/storefronts/v2/balance")] + [Authorize] + public async Task AllBalances() + { + var pid = this.RequireCurrentPlayerId(); + var coin = await level.GetBalanceAsync(pid, 2); + var token = await level.GetBalanceAsync(pid, 0); + return Ok(new[] + { + new { CurrencyType = 2, BalanceType = 0, Balance = coin, Platform = 0 }, + new { CurrencyType = 0, BalanceType = 0, Balance = token, Platform = 0 }, + new { CurrencyType = 1, BalanceType = 0, Balance = 0L, Platform = 0 }, + new { CurrencyType = 3, BalanceType = 0, Balance = 0L, Platform = 0 }, + }); + } + + /// GET api/storefronts/v1/current — single active + /// storefront the watch's "today's deals" panel renders. Returns + /// the main storefront's items as a single PurchasableItem + /// list (bounded to keep older watches snappy). + [HttpGet("api/storefronts/v1/current")] + public async Task Current() + { + if (!config.GetValue("Store:EnableWatchGiftDrops", true)) + return Ok(StoreService.ToStorefrontDto("Main", Array.Empty(), domain.Apex)); + + IQueryable query = db.StoreItems + .Where(s => s.IsActive && s.Storefront == "main") + .OrderByDescending(s => s.UpdatedAt); + var max = config.GetValue("Store:MaxCurrentStorefrontItems", 96); + if (max > 0) query = query.Take(max); + var rows = await query.ToListAsync(); + return Ok(StoreService.ToStorefrontDto("Main", rows, domain.Apex)); + } + + /// GET api/storefronts/v1/season/{seasonId} — + /// season-pass tier list, sourced from StoreItems tagged + /// Storefront = "season:{id}". + [HttpGet("api/storefronts/v1/season/{seasonId:int}")] + public async Task Season(int seasonId) + { + var key = $"season:{seasonId}"; + var tiers = await db.StoreItems + .Where(s => s.IsActive && s.Storefront == key) + .OrderBy(s => s.Price) + .Select(s => new + { + TierId = s.Id, + s.DisplayName, + s.Description, + s.Price, + CurrencyType = s.CurrencyType, + s.ImageName, + }) + .ToListAsync(); + return Ok(new + { + SeasonId = seasonId, + SeasonType = seasonId, + Active = tiers.Count > 0, + StartsAt = DateTime.UtcNow.AddDays(-30), + EndsAt = DateTime.UtcNow.AddDays(30), + Tiers = tiers, + }); + } + + /// GET api/storefronts/v1/objectives — season + /// objective progress for the caller, keyed + /// season:{n}. + [HttpGet("api/storefronts/v1/objectives")] + public async Task Objectives() + { + var pid = this.CurrentPlayerId(); + if (pid is not long me) return Ok(Array.Empty()); + var rows = await db.ObjectiveProgress + .Where(o => o.PlayerId == me && o.Key.StartsWith("season:")) + .Select(o => new { Key = o.Key, IsCompleted = o.IsCompleted, ClearedAt = o.ClearedAt }) + .ToListAsync(); + return Ok(rows); + } + + /// POST api/storefronts/v1/objectives — + /// Storefronts.CompleteObjectives uploads completion + /// records and expects a BalanceUpdateResponseDTO back. + /// We ship the wrapper with the required Balance/CurrencyType/ + /// BalanceType trio so BalanceResponseDTO.Deserialize + /// doesn't throw, plus an empty BalanceUpdates. + [HttpPost("api/storefronts/v1/objectives")] + [Consumes("application/json", "application/x-www-form-urlencoded", "multipart/form-data")] + [Authorize] + public async Task CompleteObjectives() + { + var pid = this.RequireCurrentPlayerId(); + var records = await ReadStorefrontObjectiveRecordsAsync(); + var total = 0; + foreach (var record in records ?? new()) + { + if (record.CompletionPercentage <= 0f) continue; + var rewardKey = $"storefront-objective:{record.ObjectiveType}:{DateTime.UtcNow:yyyyMMdd}"; + if (await MarkRewardOnceAsync(pid, rewardKey)) + { + var award = record.ObjectiveType switch + { + 10 or 11 or 12 => 25, + 13 => 150, + 14 => 50, + 15 => 75, + 4000 or 4001 => 50, + _ => Math.Max(5, (int)Math.Round(25 * Math.Clamp(record.CompletionPercentage, 0f, 1f))), + }; + total += award; + } + } + + if (total > 0) + { + await level.AwardXpAsync(pid, total, "storefront-objectives"); + await level.GrantCurrencyAsync(pid, 2, total, "storefront-objectives"); + } + var balance = await level.GetBalanceAsync(pid, 2); + return Ok(BalanceUpdateResponse(balance, 2, new[] + { + RewardModification(100, total, total, currentCount: total > 0 ? 1 : 0), + })); + } + + public sealed class StorefrontObjectiveCompletionRecord + { + public int ObjectiveType { get; set; } + public float CompletionPercentage { get; set; } + public long? RoomId { get; set; } + } + + public sealed class GrantBalanceRewardDto + { + public int CurrencyType { get; set; } = 2; + public List BalanceAdds { get; set; } = new(); + } + + public sealed class GrantBalanceRequest + { + public float Multiplier { get; set; } = 1f; + public int BalanceAddType { get; set; } = 1; + } + + [HttpPost("api/storefronts/v2/balance")] + [Consumes("application/json", "application/x-www-form-urlencoded", "multipart/form-data")] + [Authorize] + public async Task ModifyBalance() + { + var body = await ReadGrantBalanceRewardAsync(); + var pid = this.RequireCurrentPlayerId(); + var currencyType = body.CurrencyType == 0 ? 2 : body.CurrencyType; + var modifications = new List(); + var total = 0; + + foreach (var add in body.BalanceAdds ?? new()) + { + var config = BalanceAddConfigValues(currencyType, add.BalanceAddType); + var multiplier = Math.Clamp(add.Multiplier <= 0 ? 1f : add.Multiplier, 0f, config.MaxPartialMultiplier); + var baseAward = config.IgnorePartialMultiplier + ? config.BaseAward + : (int)Math.Round(config.BaseAward * multiplier); + baseAward = Math.Max(0, baseAward); + if (baseAward == 0) continue; + total += baseAward; + modifications.Add(RewardModification(add.BalanceAddType, baseAward, baseAward, currentCount: 1)); + } + + if (total > 0) + { + await level.GrantCurrencyAsync(pid, currencyType, total, "storefront-balance-reward"); + await level.AwardXpAsync(pid, Math.Max(10, total / 2), "storefront-balance-reward"); + } + + var balance = await level.GetBalanceAsync(pid, currencyType); + return Ok(BalanceUpdateResponse(balance, currencyType, modifications)); + } + + public sealed class BalanceAddTypeRequest { public long Amount { get; set; } } + + /// POST api/storefronts/v1/balanceAddType/{type}/{playerId} + /// — admin-only currency credit. Grants via + /// . + [HttpPost("api/storefronts/v1/balanceAddType/{type:int}/{playerId:long}")] + [Authorize] + public async Task BalanceAddTypeAdmin(int type, long playerId, [FromBody] BalanceAddTypeRequest? body) + { + var me = this.RequireCurrentPlayerId(); + var isAdmin = await db.Players.Where(p => p.Id == me).Select(p => p.IsAdmin).FirstOrDefaultAsync(); + if (!isAdmin) return Forbid(); + + var amount = body?.Amount ?? 0L; + if (amount == 0) return Ok(new { Added = 0L }); + var newBalance = await level.GrantCurrencyAsync(playerId, type, amount, $"balanceAddType:{me}"); + return Ok(new + { + CurrencyType = type, + Balance = newBalance, + BalanceType = 0, + Platform = 0, + }); + } + + private static object BalanceAddConfig(int currencyType, int balanceAddType) + { + var cfg = BalanceAddConfigValues(currencyType, balanceAddType); + return new + { + CurrencyType = currencyType, + BalanceAddType = balanceAddType, + cfg.BaseAward, + BonusAwardMin = 0, + BonusAwardMax = 0, + RateLimitType = 0, + IgnorePartialMultiplier = cfg.IgnorePartialMultiplier, + MaxPartialMultiplier = (int)Math.Ceiling(cfg.MaxPartialMultiplier), + RateLimit = 0, + BalanceInGiftBox = false, + }; + } + + private static (int BaseAward, float MaxPartialMultiplier, bool IgnorePartialMultiplier) + BalanceAddConfigValues(int currencyType, int balanceAddType) => balanceAddType switch + { + 1 => (100, 10f, false), // DirectBalanceWithMultiplier: Stunt Runner / banked loot. + 10 => (25, 1f, true), // NUXChallenge. + 11 => (100, 1f, true), // AllNUXChallenges. + 100 => (25, 1f, true), // DailyChallenge. + 101 => (150, 1f, true), // AllDailyChallenges. + 200 => (50, 1f, true), // FinishActivity. + 1000 => (100, 1f, true), // WonGame. + 1001 => (25, 1f, true), // LostGame. + _ => (25, 1f, true), + }; + + private async Task ReadGrantBalanceRewardAsync() + { + if (Request.HasFormContentType) + { + var form = await Request.ReadFormAsync(); + var dto = new GrantBalanceRewardDto + { + CurrencyType = int.TryParse(form["CurrencyType"], out var ct) ? ct : 2, + }; + if (int.TryParse(form["BalanceAddType"], out var bat) || int.TryParse(form["balanceAddType"], out bat)) + { + dto.BalanceAdds.Add(new GrantBalanceRequest + { + BalanceAddType = bat, + Multiplier = float.TryParse(form["Multiplier"], out var mult) ? mult : 1f, + }); + } + return dto; + } + + try + { + var dto = await JsonSerializer.DeserializeAsync( + Request.Body, + new JsonSerializerOptions { PropertyNameCaseInsensitive = true }); + return dto ?? new GrantBalanceRewardDto(); + } + catch + { + return new GrantBalanceRewardDto(); + } + } + + private async Task> ReadStorefrontObjectiveRecordsAsync() + { + if (Request.HasFormContentType) + { + var form = await Request.ReadFormAsync(); + if (!int.TryParse(form["objectiveType"], out var objectiveType) + && !int.TryParse(form["ObjectiveType"], out objectiveType)) + { + return new(); + } + return new() + { + new StorefrontObjectiveCompletionRecord + { + ObjectiveType = objectiveType, + CompletionPercentage = float.TryParse(form["completionPercentage"], out var pct) + || float.TryParse(form["CompletionPercentage"], out pct) + ? pct + : 1f, + RoomId = long.TryParse(form["roomId"], out var roomId) + || long.TryParse(form["RoomId"], out roomId) + ? roomId + : null, + }, + }; + } + + try + { + var rows = await JsonSerializer.DeserializeAsync>( + Request.Body, + new JsonSerializerOptions { PropertyNameCaseInsensitive = true }); + return rows ?? new(); + } + catch + { + return new(); + } + } + + private async Task MarkRewardOnceAsync(long playerId, string rewardKey) + { + var key = $"reward:{rewardKey}"; + if (await db.ObjectiveProgress.AnyAsync(o => o.PlayerId == playerId && o.Key == key && o.IsCompleted)) + return false; + db.ObjectiveProgress.Add(new ObjectiveProgressEntity + { + PlayerId = playerId, + Key = key, + IsCompleted = true, + ClearedAt = DateTime.UtcNow, + }); + await db.SaveChangesAsync(); + return true; + } + + private static object RewardModification(int balanceAddType, int baseAward, int total, int currentCount) => new + { + BalanceAddType = balanceAddType, + BaseAward = baseAward, + BonusAward = 0, + RateLimit = 0, + CurrentCount = currentCount, + Total = total, + BalanceType = 0, + BalanceInGiftBox = false, + }; + + private static object BalanceUpdateResponse(long balance, int currencyType, IEnumerable modifications) => new + { + Balance = balance, + CurrencyType = currencyType, + BalanceType = 0, + BalanceUpdates = new[] + { + new + { + UpdateResponse = 0, + Data = modifications.Where(m => m is not null).ToArray(), + }, + }, + }; +} diff --git a/DorkNet.Server/Controllers/API/TestCaseManagement/TestCaseManagementController.cs b/DorkNet.Server/Controllers/API/TestCaseManagement/TestCaseManagementController.cs new file mode 100644 index 0000000..c33a086 --- /dev/null +++ b/DorkNet.Server/Controllers/API/TestCaseManagement/TestCaseManagementController.cs @@ -0,0 +1,195 @@ +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Mvc; +using Microsoft.EntityFrameworkCore; +using System.Text.Json; +using DorkNet.Server.Auth; +using DorkNet.Server.Data; +using DorkNet.Server.Data.Entities; + +namespace DorkNet.Server.Controllers.API.TestCaseManagement; + +/// +/// api.rec.net/api/testcasemanagement/* — internal QA tooling. URL +/// surface verified against +/// Cpp2IL_ISIL/.../RecNet/TestCaseManagement.txt: +/// +/// GET api/testcasemanagement/v1/testpasssummary → List<TestPass> +/// GET api/testcasemanagement/v1/testpass/{id} → TestPass +/// GET api/testcasemanagement/v1/testcase/{id} → TestCase +/// POST api/testcasemanagement/v1/testcase/{id}/claim +/// POST api/testcasemanagement/v1/testcase/{id}/unclaim +/// POST api/testcasemanagement/v1/testcase/{id}/status +/// +/// Wire DTOs match RecNet.TestCase + RecNet.TestPass +/// exactly: +/// +/// • TestCase: Id(string), Key, Title, Description, RoomName, +/// Status(TestCaseStatus enum 0=NotYetTested/1=Claimed/2=Failed/3=Passed), +/// MinNumAssignedPlayers, AssignedPlayerIds(List<int>), +/// AssignedPlayerNames, Tags, JiraUrl, JiraBugUrl. +/// • TestPass: Id(uint), Name, Description, StartDate, EndDate?, +/// WasManuallyClosed, TestCases(List<TestCase>), Tags, +/// NumTestCases, NumPassedTestCases, NumFailedTestCases. +/// +/// Routes are reachable by anyone (the watch only renders this tab +/// when the player has the developer role; client-side gate). The +/// claim/status mutations no-op if the caller isn't authenticated. +/// +[ApiController] +public class TestCaseManagementController(DorkNetDbContext db) : ControllerBase +{ + [HttpGet("api/testcasemanagement/v1/testpasssummary")] + public async Task TestPassSummaries() + { + var passes = await db.TestPasses.ToListAsync(); + var caseCountsByPass = await db.TestCases + .Where(c => c.TestPassId != null) + .GroupBy(c => c.TestPassId!.Value) + .Select(g => new PassCounts(g.Key, g.Count(), + g.Count(c => c.Status == 3), g.Count(c => c.Status == 2))) + .ToDictionaryAsync(x => x.PassId); + return Ok(passes.Select(p => ToWirePass(p, new List(), caseCountsByPass))); + } + + [HttpGet("api/testcasemanagement/v1/testpass/{id:long}")] + public async Task TestPass(long id) + { + if (id < 0 || id > uint.MaxValue) return NotFound(); + var pid = (uint)id; + var pass = await db.TestPasses.FirstOrDefaultAsync(p => p.Id == pid); + if (pass is null) return NotFound(); + var cases = await db.TestCases.Where(c => c.TestPassId == pid).ToListAsync(); + var counts = new Dictionary + { + [pid] = new(pid, cases.Count, cases.Count(c => c.Status == 3), cases.Count(c => c.Status == 2)), + }; + return Ok(ToWirePass(pass, cases, counts)); + } + + private record PassCounts(uint PassId, int Total, int Passed, int Failed); + + [HttpGet("api/testcasemanagement/v1/testcase/{id}")] + public async Task TestCase(string id) + { + var tc = await db.TestCases.FirstOrDefaultAsync(c => c.Id == id); + if (tc is null) return NotFound(); + return Ok(ToWireCase(tc)); + } + + [HttpPost("api/testcasemanagement/v1/testcase/{id}/claim")] + [Authorize] + public async Task Claim(string id) + { + var pid = this.RequireCurrentPlayerId(); + var tc = await db.TestCases.FirstOrDefaultAsync(c => c.Id == id); + if (tc is null) return NotFound(); + + var player = await db.Players.Where(p => p.Id == pid).Select(p => p.Username).FirstOrDefaultAsync(); + var ids = ParseIntList(tc.AssignedPlayerIdsJson); + var names = ParseStringList(tc.AssignedPlayerNamesJson); + if (!ids.Contains((int)pid)) + { + ids.Add((int)pid); + names.Add(player ?? $"Player {pid}"); + tc.AssignedPlayerIdsJson = JsonSerializer.Serialize(ids); + tc.AssignedPlayerNamesJson = JsonSerializer.Serialize(names); + } + tc.Status = 1; // Claimed + tc.UpdatedAt = DateTime.UtcNow; + await db.SaveChangesAsync(); + return Ok(ToWireCase(tc)); + } + + [HttpPost("api/testcasemanagement/v1/testcase/{id}/unclaim")] + [Authorize] + public async Task Unclaim(string id) + { + var pid = this.RequireCurrentPlayerId(); + var tc = await db.TestCases.FirstOrDefaultAsync(c => c.Id == id); + if (tc is null) return NotFound(); + var ids = ParseIntList(tc.AssignedPlayerIdsJson); + var names = ParseStringList(tc.AssignedPlayerNamesJson); + var idx = ids.IndexOf((int)pid); + if (idx >= 0) + { + ids.RemoveAt(idx); + if (idx < names.Count) names.RemoveAt(idx); + tc.AssignedPlayerIdsJson = JsonSerializer.Serialize(ids); + tc.AssignedPlayerNamesJson = JsonSerializer.Serialize(names); + } + if (ids.Count == 0 && tc.Status == 1) tc.Status = 0; // back to NotYetTested + tc.UpdatedAt = DateTime.UtcNow; + await db.SaveChangesAsync(); + return Ok(ToWireCase(tc)); + } + + public sealed class StatusUpdateRequest + { + public int NewStatus { get; set; } + } + + [HttpPost("api/testcasemanagement/v1/testcase/{id}/status")] + [Authorize] + public async Task UpdateStatus(string id, [FromBody] StatusUpdateRequest req) + { + var tc = await db.TestCases.FirstOrDefaultAsync(c => c.Id == id); + if (tc is null) return NotFound(); + if (req.NewStatus is < 0 or > 3) return BadRequest("invalid status"); + tc.Status = req.NewStatus; + tc.UpdatedAt = DateTime.UtcNow; + await db.SaveChangesAsync(); + return Ok(ToWireCase(tc)); + } + + // ── Helpers ────────────────────────────────────────────────────────── + + private static List ParseIntList(string? json) + { + if (string.IsNullOrWhiteSpace(json)) return new(); + try { return JsonSerializer.Deserialize>(json) ?? new(); } + catch { return new(); } + } + + private static List ParseStringList(string? json) + { + if (string.IsNullOrWhiteSpace(json)) return new(); + try { return JsonSerializer.Deserialize>(json) ?? new(); } + catch { return new(); } + } + + private static object ToWireCase(TestCaseEntity c) => new + { + c.Id, + c.Key, + c.Title, + c.Description, + c.RoomName, + c.Status, + c.MinNumAssignedPlayers, + AssignedPlayerIds = ParseIntList(c.AssignedPlayerIdsJson), + AssignedPlayerNames = ParseStringList(c.AssignedPlayerNamesJson), + Tags = ParseStringList(c.TagsJson), + c.JiraUrl, + c.JiraBugUrl, + }; + + private static object ToWirePass(TestPassEntity p, List cases, + IDictionary counts) + { + var c = counts.TryGetValue(p.Id, out var v) ? v : new PassCounts(p.Id, 0, 0, 0); + return new + { + p.Id, + p.Name, + p.Description, + p.StartDate, + p.EndDate, + p.WasManuallyClosed, + TestCases = cases.Select(ToWireCase), + Tags = ParseStringList(p.TagsJson), + NumTestCases = c.Total, + NumPassedTestCases = c.Passed, + NumFailedTestCases = c.Failed, + }; + } +} diff --git a/DorkNet.Server/Controllers/Accounts/AccountsController.cs b/DorkNet.Server/Controllers/Accounts/AccountsController.cs new file mode 100644 index 0000000..8c8369a --- /dev/null +++ b/DorkNet.Server/Controllers/Accounts/AccountsController.cs @@ -0,0 +1,573 @@ +using Microsoft.AspNetCore.Mvc; +using Microsoft.EntityFrameworkCore; +using DorkNet.Models.Auth; +using DorkNet.Server.Auth; +using DorkNet.Server.Data; +using DorkNet.Server.Data.Entities; +using DorkNet.Server.Services; + +namespace DorkNet.Server.Controllers.Accounts; + +/// +/// accounts.rec.net — account management. +/// Shapes match dump.cs: +/// RecNet.Account { AccountId, RawUsername, Username, DisplayName, +/// ProfileImage, TreatAsJunior, HasBirthday, Platforms } +/// RecNet.SelfAccount Account + { Email, Phone, Birthday, JuniorState, +/// ParentAccountId } +/// RecNet.RecNetResult { Success, Error } +/// Accounts.CreateAccountResponse RecNetResult + { Account } +/// +[ApiController] +public class AccountsController( + PlayerService playerService, + AuthService authService, + OrphanAccountTracker orphans, + DorkNetDbContext db, + NotificationService notifications, + ServerSettingsService settings, + ILogger logger) : ControllerBase +{ + [HttpGet("/account/v1/savedlogins")] + [HttpGet("/account/v2/savedlogins")] + [HttpGet("/account/v1/cachedlogins")] + public async Task GetSavedLogins( + [FromQuery] int platform = 0, + [FromQuery] string? platformId = null) + { + var players = await playerService.GetCachedLoginsAsync(platform, platformId); + return Ok(players.Select(p => new CachedLogin + { + Platform = platform, + PlatformId = platformId ?? string.Empty, + AccountId = (int)p.Id, + LastLoginTime = p.LastSeenAt, + RequirePassword = false, + }).ToList()); + } + + // Accounts.GetLocalAccount → SelfAccount shape. + // The 2020 client calls the unversioned /account/me path (observed in + // server logs); v1/v2 forms are kept for older builds and api.rec.net + // bridging. Returns SelfAccount, NOT base Account (this is the *self* + // endpoint — RecNet.Login.<>c.b__35_4 expects a SelfAccount). + [HttpGet("/account/me")] + [HttpGet("/account/v1/me")] + [HttpGet("/account/v2/me")] + [HttpGet("/account/v1/account")] + public async Task GetMe() + { + var player = await GetCurrentPlayerAsync(); + if (player is null) return Unauthorized(); + return Ok(BuildSelfAccount(player.Id, player.Username, player.DisplayName, player.ProfileImageName)); + } + + /// + /// Resolves the player tied to the bearer token on the request. Returns + /// null when no/invalid bearer is supplied — the caller decides whether + /// that is a 401 (every "self" endpoint should be) or a noisier + /// fallback. Anonymous-account creation was previously the fallback + /// path; that's gone now because every caller has gone through + /// `/account/create` (deviceId-bound) before reaching us. + /// + private async Task GetCurrentPlayerAsync() + { + var auth = Request.Headers.Authorization.ToString(); + if (!auth.StartsWith("Bearer ", StringComparison.OrdinalIgnoreCase)) + return null; + var token = auth["Bearer ".Length..].Trim(); + var id = authService.ValidateToken(token); + if (id is not long playerId) return null; + return await playerService.GetByIdAsync(playerId); + } + + // Accounts.GetAccountById → Account shape. The 2020 watch posts to + // the BARE /account/{id} path (no v1 prefix) when + // resolving a room's creator from RoomDetails — verified in the + // output_log "Failed to GetAccountById: HTTP Error 404" trace where + // PlayerDetailsWatchUIFlow.SetAccount NREs because the GetAccountById + // promise rejected with 404. Accept both shapes. + [HttpGet("/account/{accountId:long}")] + [HttpGet("/account/v1/{accountId:long}")] + [HttpGet("/account/v1/account/{accountId:long}")] + public async Task GetAccountById(long accountId) + { + var player = await playerService.GetByIdAsync(accountId); + if (player is null) return NotFound(); + return Ok(BuildAccount(player.Id, player.Username, player.DisplayName, player.ProfileImageName)); + } + + // Accounts.GetAccountByUsername + [HttpGet("/account/v1/username/{username}")] + public async Task GetAccountByUsername(string username) + { + var player = await playerService.GetByUsernameAsync(username); + if (player is null) return NotFound(); + return Ok(BuildAccount(player.Id, player.Username, player.DisplayName, player.ProfileImageName)); + } + + // Accounts.CreateAccount — actual URL observed in server logs: + // POST accounts.rec.net/account/create + // body: platform=0&platformId=&deviceId= + // Game expects CreateAccountResponse {Success, Error, Account}. Lambda + // b__29_0 NPEs if response.Account is null after deserializing. + // Clean PascalCase here — multi-case keys appear to confuse LitJson's typed + // path so its TypeMap.Add throws "Key: none" or leaves Account unpopulated. + [HttpPost("/account/create")] + [HttpPost("/account/v1/create")] + [HttpPost("/account/v1")] + [HttpPost("/account/v2/create")] + public async Task CreateAccount( + [FromForm] int platform, + [FromForm] string? platformId, + [FromForm] string? deviceId) + { + // Admin kill-switch. Watch's b__29_0 lambda + // (Accounts_NestedType___c.txt:311) branches on Success: false + // → ErroredPromise with the Error string surfaced to + // the player; true → reads response.Account. Account stays a + // non-null stub even on refusal so LitJson's typed Deserialize + // path doesn't fault before the Success check runs. + if (await settings.AreSignupsDisabledAsync()) + { + logger.LogInformation( + "[accounts] account/create refused — signups disabled (device={Device} platform={Platform} platformId={PlatformId})", + deviceId, platform, platformId); + return Ok(new CreateAccountResponse + { + Success = false, + Error = "Account creation is currently disabled by the server admin.", + }); + } + + // The 2020 watch's "Create new account" UI and its boot-time + // "ensure my account exists" probe BOTH hit this endpoint with + // identical request bodies. To support multi-account-per-device + // we always create a brand-new row; the boot-flow case (where + // the user actually wants their cached account) is handled by + // the OrphanAccountTracker — when the next cached_login picks + // a DIFFERENT account_id, that handler deletes this just- + // created row. + var player = await playerService.CreateNewAccountAsync( + deviceId: deviceId, + platform: platform, + platformId: platformId); + + orphans.TrackCreation(deviceId, platformId, player.Id); + + logger.LogInformation( + "[accounts] account/create created {Id} ({Username}) for device {Device} platform={Platform} platformId={PlatformId}", + player.Id, player.Username, deviceId, platform, platformId); + + return Ok(new CreateAccountResponse + { + Success = true, + Error = string.Empty, + Account = BuildAccount(player.Id, player.Username, player.DisplayName, player.ProfileImageName), + }); + } + + // Accounts.SearchForAccounts — used by the watch's typeahead AND the + // localhost public site's account search. Anonymous-safe (no JWT + // required) so the front-facing site can call it directly. + [HttpGet("/account/v1/search")] + [Microsoft.AspNetCore.Authorization.AllowAnonymous] + public async Task Search([FromQuery] string? query, [FromQuery] int take = 20) + { + if (string.IsNullOrWhiteSpace(query)) return Ok(Array.Empty()); + var clamped = Math.Clamp(take, 1, 50); + var matches = await playerService.SearchAsync(query.Trim(), clamped); + return Ok(matches + .Select(p => BuildAccount(p.Id, p.Username, p.DisplayName, p.ProfileImageName)) + .ToList()); + } + + // Accounts.GetAccountsBulk / GetAccountsFromPlatformIds / FromEmails / FromPhones + // Form body: Ids=1,2,3 (BestHTTP HTTPFormBase.AddField). Accept both + // GET and POST and any content type so route matching succeeds even + // when the client sends multipart vs urlencoded. + [HttpPost("/account/v1/bulk")] + [HttpGet("/account/v1/bulk")] + [HttpPost("/account/bulk")] + [HttpGet("/account/bulk")] + [HttpPost("/account/v1/platformids")] + [HttpPost("/account/v1/emails")] + [HttpPost("/account/v1/phones")] + [HttpPost("/account/v1/byplatformid")] + public async Task Bulk() + { + // The watch calls this route family with payloads keyed by + // either Ids (account ids), Emails (email addresses), or + // Phones (E.164 strings) depending on which Accounts.GetXxx + // helper fired it. Read all three plus the lowercase / singular + // variants the cached-account hydration uses. + string? rawIds = null; + string? rawEmails = null; + string? rawPhones = null; + try + { + if (Request.HasFormContentType) + { + rawIds = NonEmptyOrNull(Request.Form["Ids"].ToString()) + ?? NonEmptyOrNull(Request.Form["ids"].ToString()) + ?? NonEmptyOrNull(Request.Form["Id"].ToString()) + ?? NonEmptyOrNull(Request.Form["id"].ToString()); + rawEmails = NonEmptyOrNull(Request.Form["Emails"].ToString()) + ?? NonEmptyOrNull(Request.Form["emails"].ToString()) + ?? NonEmptyOrNull(Request.Form["Email"].ToString()) + ?? NonEmptyOrNull(Request.Form["email"].ToString()); + rawPhones = NonEmptyOrNull(Request.Form["Phones"].ToString()) + ?? NonEmptyOrNull(Request.Form["phones"].ToString()) + ?? NonEmptyOrNull(Request.Form["Phone"].ToString()) + ?? NonEmptyOrNull(Request.Form["phone"].ToString()); + } + } + catch { /* form parse may fail on non-form bodies */ } + rawIds ??= NonEmptyOrNull(Request.Query["Ids"].ToString()) + ?? NonEmptyOrNull(Request.Query["ids"].ToString()) + ?? NonEmptyOrNull(Request.Query["Id"].ToString()) + ?? NonEmptyOrNull(Request.Query["id"].ToString()); + + static string? NonEmptyOrNull(string? s) => string.IsNullOrWhiteSpace(s) ? null : s; + + // Build the set of player ids to hydrate from whatever the + // caller sent. Each source contributes additively — the watch + // never mixes (Ids + Emails) in one call, but the union is + // harmless when it happens. + var ids = new HashSet(); + if (rawIds is not null) + { + foreach (var n in rawIds.Split(',', StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries)) + if (long.TryParse(n, out var v) && v > 0) ids.Add(v); + } + if (rawEmails is not null) + { + var emails = rawEmails.Split(',', StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries) + .Select(e => e.ToLowerInvariant()) + .Where(e => e.Length > 0) + .ToList(); + if (emails.Count > 0) + { + var matched = await db.Players + .Where(p => p.Email != null && emails.Contains(p.Email.ToLower())) + .Select(p => p.Id) + .ToListAsync(); + foreach (var id in matched) ids.Add(id); + } + } + if (rawPhones is not null) + { + var phones = rawPhones.Split(',', StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries) + .Where(p => p.Length > 0) + .ToList(); + if (phones.Count > 0) + { + var matched = await db.Players + .Where(p => p.Phone != null && phones.Contains(p.Phone)) + .Select(p => p.Id) + .ToListAsync(); + foreach (var id in matched) ids.Add(id); + } + } + if (ids.Count == 0) return Ok(Array.Empty()); + + // Strip ids the caller has blocked, and ids that have blocked the + // caller. This prevents `bulk` from being used as a cheap oracle + // ("who blocked me?"). When the caller is anonymous (no JWT) we + // skip the filter entirely — `bulk` is also used during boot for + // public profile hydration before the player has authenticated. + var me = this.CurrentPlayerId(); + if (me is long callerId) + { + var blocked = await db.Relationships + .Where(r => r.Status == RelationshipStatus.Blocked && + ((r.RequesterId == callerId && ids.Contains(r.TargetId)) || + (r.TargetId == callerId && ids.Contains(r.RequesterId)))) + .Select(r => r.RequesterId == callerId ? r.TargetId : r.RequesterId) + .ToListAsync(); + foreach (var b in blocked) ids.Remove(b); + } + + var accounts = new List(); + foreach (var id in ids) + { + var p = await playerService.GetByIdAsync(id); + if (p is not null) + accounts.Add(BuildAccount(p.Id, p.Username, p.DisplayName, p.ProfileImageName)); + } + return Ok(accounts); + } + + // Accounts.GetNameGenerationOptions → NameGenDTO {Nouns, Adjectives} + [HttpGet("/account/v1/namegen")] + [HttpGet("/account/v1/namegeneration")] + public IActionResult NameGen() => Ok(new + { + Nouns = new[] { "Player", "Hero", "Star", "Fox", "Wolf" }, + Adjectives = new[] { "Brave", "Swift", "Clever", "Bold", "Bright" }, + }); + + // Phone-confirm is the one mutation we don't persist — there's no + // SMS provider in the loop. Acknowledge silently. + [HttpPost("/account/v1/confirmphone")] + public IActionResult ConfirmPhone() => + Ok(new RecNetResult { Success = true, Error = string.Empty }); + + /// POST /account/v1/birthday — sets the player's + /// birthday and updates IsJunior accordingly. Rec Room's junior + /// cutoff is <13 years old; we recompute it on every birthday + /// write so a freshly turned 13 player loses the junior flag the + /// next time they update. + [HttpPost("/account/v1/birthday")] + [HttpPost("/account/me/birthday")] + [Consumes("application/x-www-form-urlencoded", "multipart/form-data", "application/json")] + public async Task SetBirthday([FromForm(Name = "birthday")] string? birthday) + { + if (string.IsNullOrWhiteSpace(birthday) || + !DateTime.TryParse(birthday, out var bday)) + return Ok(new RecNetResult { Success = false, Error = "invalid_date" }); + + var player = await GetCurrentPlayerAsync(); + if (player is null) return Unauthorized(); + player.Birthday = bday.Date; + player.IsJunior = AgeYears(bday) < 13; + await db.SaveChangesAsync(); + return Ok(new RecNetResult { Success = true, Error = string.Empty }); + } + + /// POST /account/v1/email — set or clear contact email. + /// Persisted verbatim; format isn't validated server-side, the + /// in-game email keyboard does it. + [HttpPost("/account/v1/email")] + [HttpPost("/account/me/email")] + [Consumes("application/x-www-form-urlencoded", "multipart/form-data", "application/json")] + public async Task SetEmail([FromForm(Name = "email")] string? email) + { + var player = await GetCurrentPlayerAsync(); + if (player is null) return Unauthorized(); + player.Email = string.IsNullOrWhiteSpace(email) ? null : email.Trim(); + await db.SaveChangesAsync(); + return Ok(new RecNetResult { Success = true, Error = string.Empty }); + } + + /// POST /account/v1/phone — set or clear contact phone. + [HttpPost("/account/v1/phone")] + [HttpPost("/account/me/phone")] + [Consumes("application/x-www-form-urlencoded", "multipart/form-data", "application/json")] + public async Task SetPhone([FromForm(Name = "phone")] string? phone) + { + var player = await GetCurrentPlayerAsync(); + if (player is null) return Unauthorized(); + player.Phone = string.IsNullOrWhiteSpace(phone) ? null : phone.Trim(); + await db.SaveChangesAsync(); + return Ok(new RecNetResult { Success = true, Error = string.Empty }); + } + + /// POST /account/v1/profileimage — body: imageName=... + /// or fileName=... The client first POSTs the bytes to + /// storage.rec.net/upload with FileType=Image, gets back a + /// Filename, then sends that filename here. We persist it on + /// PlayerEntity.ProfileImageName; reads from /account/me + + /// /account/v1/{id} surface it. + [HttpPost("/account/v1/profileimage")] + [HttpPost("/account/me/profileimage")] + [HttpPost("/profileimage")] + [HttpPut("/account/v1/profileimage")] + [HttpPut("/account/me/profileimage")] + [HttpPut("/profileimage")] + [Consumes("application/x-www-form-urlencoded", "multipart/form-data", "application/json")] + public async Task SetProfileImage( + [FromForm(Name = "imageName")] string? imageName, + [FromForm(Name = "fileName")] string? fileName, + [FromForm(Name = "image")] string? imageAlt) + { + // Fall back to query string for the rare client paths that + // send these fields as URL params instead of form fields. + var name = imageName ?? fileName ?? imageAlt + ?? Request.Query["imageName"].ToString() + ?? Request.Query["fileName"].ToString(); + var player = await GetCurrentPlayerAsync(); + if (player is null) return Unauthorized(); + player.ProfileImageName = string.IsNullOrWhiteSpace(name) ? null : name.Trim(); + await db.SaveChangesAsync(); + logger.LogInformation( + "[accounts] profile image set: player={PlayerId} → {Name}", + player.Id, player.ProfileImageName ?? ""); + await NotifyAccountImageChangedAsync(player); + return Ok(new RecNetResult { Success = true, Error = string.Empty }); + } + + private Task NotifyAccountImageChangedAsync(PlayerEntity player) + { + var account = BuildAccount(player.Id, player.Username, player.DisplayName, player.ProfileImageName); + var selfAccount = BuildSelfAccount(player.Id, player.Username, player.DisplayName, player.ProfileImageName); + return Task.WhenAll( + notifications.NotifyTypedAsync(player.Id, "AccountUpdate", account), + notifications.NotifyTypedAsync(player.Id, "SelfAccountUpdate", selfAccount)); + } + + private static int AgeYears(DateTime birthday) + { + var today = DateTime.UtcNow.Date; + var age = today.Year - birthday.Year; + if (birthday.Date > today.AddYears(-age)) age--; + return age; + } + + // Everyone is a dev for now — exposes the in-game debug console + dev + // tools. Per user request: easier debugging while iterating on the + // private server. Flip back to false later if you want shipping behavior. + [HttpGet("/account/v1/{accountId:long}/isdeveloper")] + [HttpGet("/account/me/isdeveloper")] + public IActionResult IsDeveloper(long accountId = 0) => Ok(new { IsDeveloper = true }); + + /// + /// PUT /account/me/username — body: username=NewName + /// Real handler that persists the change to the DB. Was previously + /// going to GlobalCatchAllController and returning the shotgun, which + /// meant username changes didn't actually save. + /// + [HttpPut("/account/me/username")] + [HttpPost("/account/me/username")] + [HttpPost("/account/v1/username")] + [Consumes("application/x-www-form-urlencoded", "multipart/form-data")] + public async Task ChangeUsername([FromForm(Name = "username")] string? username) + { + if (string.IsNullOrWhiteSpace(username)) + return Ok(new { success = false, error = "Username required" }); + + var player = await GetCurrentPlayerAsync(); + if (player is null) return Unauthorized(); + await playerService.UpdateUsernameAsync(player.Id, username.Trim()); + var updated = await playerService.GetByIdAsync(player.Id); + return Ok(new + { + success = true, + error = "", + account = new + { + accountId = (int)player.Id, + username = updated?.Username ?? username, + displayName = updated?.DisplayName ?? username, + }, + }); + } + + /// + /// PUT /account/me/displayname — same flow as username but only updates + /// the DisplayName field (Username stays as-is). + /// + [HttpPut("/account/me/displayname")] + [HttpPost("/account/me/displayname")] + [HttpPost("/account/v1/displayname")] + [Consumes("application/x-www-form-urlencoded", "multipart/form-data")] + public async Task ChangeDisplayName([FromForm(Name = "displayName")] string? displayName) + { + if (string.IsNullOrWhiteSpace(displayName)) + return Ok(new { success = false, error = "DisplayName required" }); + var player = await GetCurrentPlayerAsync(); + if (player is null) return Unauthorized(); + await playerService.UpdateDisplayNameAsync(player.Id, displayName.Trim()); + return Ok(new { success = true, error = "" }); + } + + /// + /// PUT /account/me/bio — body: bio=... + /// + [HttpPut("/account/me/bio")] + [HttpPost("/account/me/bio")] + [HttpPost("/account/v1/bio")] + [Consumes("application/x-www-form-urlencoded", "multipart/form-data")] + public async Task ChangeBio([FromForm(Name = "bio")] string? bio) + { + var player = await GetCurrentPlayerAsync(); + if (player is null) return Unauthorized(); + await playerService.UpdateBioAsync(player.Id, bio ?? string.Empty); + return Ok(new { success = true, error = "" }); + } + + // ParentalControl.Deserialize at RVA 0x14543A0 — required keys: + // accountId (int, GetKey) + // disallowInAppPurchases (bool, GetKey) + // Returning {} crashes the client with + // "Failed to GetLocalAccountParentalControl: Malformed Response". + [HttpGet("/parentalcontrol/me")] + [HttpGet("/parentalcontrol/v1/me")] + public async Task ParentalControlMe() + { + var player = await GetCurrentPlayerAsync(); + if (player is null) return Unauthorized(); + return Ok(new + { + accountId = (int)player.Id, + disallowInAppPurchases = false, + }); + } + + [HttpGet("/parentalcontrol/{accountId:long}")] + [HttpGet("/parentalcontrol/v1/{accountId:long}")] + public IActionResult ParentalControlById(long accountId) => Ok(new + { + accountId = (int)accountId, + disallowInAppPurchases = false, + }); + + [HttpGet("/account/v1/me/disallowiap")] + public IActionResult DisallowIap() => Ok(new { Disallow = false }); + + /// GET /account/{id}/bio — public bio lookup. Watch URL + /// from Cpp2IL_ISIL/.../RecNet/Accounts.txt line 5686: + /// "account/{0}/bio" (no v1 prefix). Wire shape from + /// BioDTO.txt: required keys "accountId" (int, Util.GetKey) + /// and "bio" (string, optional via the GetKeyOrDefault- + /// flavoured 0x180EE6770 helper). Returning the previous + /// { Bio = ... } PascalCase shape made the watch's + /// BioDTO.Deserialize KeyNotFoundException on + /// accountId, which surfaced as "Failed to GetBio for + /// accountId N: Malformed Response" in the client log. + [HttpGet("/account/{accountId:long}/bio")] + [HttpGet("/account/v1/{accountId:long}/bio")] + public async Task GetBio(long accountId) + { + var p = await playerService.GetByIdAsync(accountId); + return Ok(new { accountId = (int)accountId, bio = p?.Bio ?? string.Empty }); + } + + [HttpGet("/account/v1/{accountId:long}/platformid")] + public IActionResult GetPlatformId(long accountId) => Ok(new { PlatformId = 0L }); + + // Previously had a /{*path} catch-all that returned [] / {}. Removed — + // unknown accounts URLs now 404 so we notice them in logs and wire + // them properly rather than silently masking missing endpoints. + + private static RecNetAccount BuildAccount(long id, string username, string? displayName, string? profileImage = null) + => new() + { + AccountId = (int)id, + RawUsername = username, + Username = username, + DisplayName = displayName ?? username, + ProfileImage = profileImage ?? string.Empty, + TreatAsJunior = false, + HasBirthday = true, + Platforms = 1, + }; + + private static RecNetSelfAccount BuildSelfAccount(long id, string username, string? displayName, string? profileImage = null) + => new() + { + AccountId = (int)id, + RawUsername = username, + Username = username, + DisplayName = displayName ?? username, + ProfileImage = profileImage ?? string.Empty, + TreatAsJunior = false, + HasBirthday = true, + Platforms = 1, + Email = string.Empty, + Phone = string.Empty, + Birthday = new DateTime(2000, 1, 1), + JuniorState = 0, + ParentAccountId = null, + }; +} diff --git a/DorkNet.Server/Controllers/Admin/AdminController.cs b/DorkNet.Server/Controllers/Admin/AdminController.cs new file mode 100644 index 0000000..f935332 --- /dev/null +++ b/DorkNet.Server/Controllers/Admin/AdminController.cs @@ -0,0 +1,3288 @@ +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Mvc; +using Microsoft.EntityFrameworkCore; +using DorkNet.Models.Notification; +using DorkNet.Server.Auth; +using DorkNet.Server.Data; +using DorkNet.Server.Data.Entities; +using DorkNet.Server.Hubs; +using DorkNet.Server.Services; + +namespace DorkNet.Server.Controllers.Admin; + +/// +/// api.rec.net/api/admin/v1/* — privileged moderation endpoints. Every +/// action is gated by , which checks +/// ; every state mutation is logged +/// to for audit replay. +/// +/// The first account created on a fresh DB gets IsAdmin = true +/// (see ) so a +/// freshly-bootstrapped private server always has at least one root +/// admin without manual SQL. +/// +[ApiController] +[Route("api/admin/v1")] +[Authorize] +[AdminOnly] +public class AdminController( + DorkNetDbContext db, + NotificationService notifications, + LevelService level, + CommunityBoardService communityBoard, + RoomBlobNormalizerService roomBlobNormalizer, + HtrAssetMirrorService htrMirror, + OnlinePresenceService onlinePresence, + PlayerPresenceService playerPresence, + PlayerLogService playerLog, + ServerSettingsService serverSettings, + IObjectStorage storage, + DomainConfig domain, + ILogger adminLogger) : ControllerBase +{ + private long CurrentAdminId => this.RequireCurrentPlayerId(); + + // ── Player request logs ────────────────────────────────────────────── + + /// Most recent HTTP requests made by a single player, newest + /// first. Backed by 's Redis ring buffer + /// (in-process fallback for dev) — capped at + /// entries per + /// player with a 7-day key TTL. Used by the admin UI's "Player logs" + /// tab to spot what API call a player was making when they hit a bug. + [HttpGet("players/{id:long}/logs")] + public ActionResult PlayerLogs(long id, [FromQuery] int take = 200) + { + var entries = playerLog.GetRecent(id, take); + return Ok(entries); + } + + // ── Players ────────────────────────────────────────────────────────── + + /// List players with online status, paginated. Used by the + /// admin watch tab. Default page size 50, max 200 to keep responses + /// reasonable. + [HttpGet("players")] + public async Task ListPlayers( + [FromQuery] string? query, [FromQuery] int take = 50, [FromQuery] int skip = 0) + { + take = Math.Clamp(take, 1, 200); + skip = Math.Max(0, skip); + + IQueryable q = db.Players.OrderByDescending(p => p.LastSeenAt); + if (!string.IsNullOrWhiteSpace(query)) + q = q.Where(p => p.Username.Contains(query) || p.DisplayName.Contains(query)); + + var page = await q.Skip(skip).Take(take).ToListAsync(); + var onlineSet = onlinePresence.OnlinePlayerIds().ToHashSet(); + return Ok(page.Select(p => new + { + p.Id, + p.Username, + p.DisplayName, + p.Email, + p.IsAdmin, + p.IsDeveloper, + p.IsCommunityTeam, + p.IsVerified, + p.IsJunior, + p.BannedUntil, + p.LastIp, + p.LastSeenAt, + p.CreatedAt, + p.Level, + p.XP, + p.ProfileImageName, + Online = onlineSet.Contains(p.Id), + })); + } + + /// Single-player detail — bundles the player record plus + /// their currency balances so the admin UI can render an edit page + /// in one round trip. + [HttpGet("players/{id:long}")] + public async Task GetPlayer(long id) + { + var p = await db.Players.FirstOrDefaultAsync(x => x.Id == id); + if (p is null) return NotFound(); + var balances = await db.CurrencyBalances + .Where(c => c.PlayerId == id) + .Select(c => new { c.CurrencyType, c.Balance }) + .ToListAsync(); + var avatar = await db.Avatars + .Where(a => a.PlayerId == id) + .Select(a => new { a.OutfitSelections, a.HairColor, a.SkinColor, a.FaceFeatures }) + .FirstOrDefaultAsync(); + return Ok(new + { + p.Id, + p.Username, + p.DisplayName, + p.Bio, + p.Email, + p.IsAdmin, + p.IsDeveloper, + p.IsCommunityTeam, + p.IsVerified, + p.IsJunior, + p.BannedUntil, + p.LastIp, + p.LastSeenAt, + p.CreatedAt, + p.Level, + p.XP, + p.ProfileImageName, + Online = onlinePresence.OnlinePlayerIds().Contains(p.Id), + Balances = balances, + Avatar = avatar, + }); + } + + public sealed record BanRequest(int DurationDays, string? Reason); + + /// Ban a player for the given number of days. The + /// 401s any subsequent request + /// from the player until BannedUntil elapses; we also push + /// a ModerationKick notification so the watch sees the ban + /// immediately. + [HttpPost("players/{id:long}/ban")] + public async Task BanPlayer(long id, [FromBody] BanRequest body) + { + var player = await db.Players.FirstOrDefaultAsync(p => p.Id == id); + if (player is null) return NotFound(); + if (player.IsAdmin) return BadRequest("Cannot ban an admin. Demote first."); + + var days = Math.Clamp(body.DurationDays, 1, 3650); + player.BannedUntil = DateTime.UtcNow.AddDays(days); + + await LogAsync("ban_player", "player", id, body.Reason ?? ""); + await db.SaveChangesAsync(); + + await notifications.NotifyAsync(id, PushNotificationId.ModerationKick, + new { Reason = body.Reason ?? "", Until = player.BannedUntil }); + + return Ok(new { player.Id, player.BannedUntil }); + } + + [HttpPost("players/{id:long}/unban")] + public async Task UnbanPlayer(long id, [FromBody] BanRequest? body) + { + var player = await db.Players.FirstOrDefaultAsync(p => p.Id == id); + if (player is null) return NotFound(); + + player.BannedUntil = null; + await LogAsync("unban_player", "player", id, body?.Reason ?? ""); + await db.SaveChangesAsync(); + return Ok(new { player.Id, player.BannedUntil }); + } + + [HttpPost("players/{id:long}/promote")] + public async Task Promote(long id) + { + var player = await db.Players.FirstOrDefaultAsync(p => p.Id == id); + if (player is null) return NotFound(); + player.IsAdmin = true; + await LogAsync("promote_admin", "player", id, ""); + await db.SaveChangesAsync(); + return Ok(new { player.Id, player.IsAdmin }); + } + + [HttpPost("players/{id:long}/demote")] + public async Task Demote(long id) + { + // Refuse to demote yourself — guards against the last admin + // accidentally dropping privileges and being unable to recover. + if (id == CurrentAdminId) + return BadRequest("Cannot demote yourself. Have another admin do it."); + + var player = await db.Players.FirstOrDefaultAsync(p => p.Id == id); + if (player is null) return NotFound(); + player.IsAdmin = false; + await LogAsync("demote_admin", "player", id, ""); + await db.SaveChangesAsync(); + return Ok(new { player.Id, player.IsAdmin }); + } + + // ── Rooms ──────────────────────────────────────────────────────────── + + public sealed record DeleteRoomRequest(string? Reason); + + /// Soft-delete a room by setting State = 1 + /// (Archived). The room stops appearing in browse/search results; + /// its data is still retrievable for an undelete if needed. + /// GET api/admin/v1/diagnostics/room-images — + /// emergency diagnostic for "room tile shows smiley" symptoms. + /// Returns one row per seeded room with: the DB ImageName, whether + /// the corresponding file exists in data/images/, and the + /// fully-qualified URL the client would fetch. Lets us tell at a + /// glance whether the bug is (a) DB never got real CDN names from + /// the backfill, (b) PNG missing on disk, or (c) wrong URL host + /// being served by the config endpoint. + [HttpGet("diagnostics/room-images")] + public async Task DiagnosticsRoomImages() + { + var imgDir = Path.Combine(AppContext.BaseDirectory, "data", "images"); + var rooms = await db.Rooms + .Where(r => r.Id >= 100 && r.Id < 1000) + .OrderBy(r => r.Id) + .Select(r => new { r.Id, r.Name, r.ImageName }) + .ToListAsync(); + var rows = rooms.Select(r => + { + var path = Path.Combine(imgDir, r.ImageName ?? string.Empty); + return new + { + r.Id, + r.Name, + r.ImageName, + FileExists = !string.IsNullOrEmpty(r.ImageName) && System.IO.File.Exists(path), + FullPath = path, + FetchUrl = $"https://{domain.Sub("img")}/{r.ImageName}", + }; + }).ToList(); + return Ok(new + { + ImageDirOnDisk = imgDir, + ImageDirExists = Directory.Exists(imgDir), + ImageFileCount = Directory.Exists(imgDir) ? Directory.GetFiles(imgDir).Length : 0, + RoomImagesJsonExists = System.IO.File.Exists( + Path.Combine(AppContext.BaseDirectory, "data", "room_images.json")), + Rooms = rows, + }); + } + + /// GET api/admin/v1/players/{id}/clothing-debug — + /// inspect a single player's wardrobe state. Shows the raw + /// AvatarEntity columns the watch reads (OutfitSelections, + /// InventoryJson, HairColor, SkinColor), the parsed-and-filtered + /// list of items the player will see in their wardrobe drawer + /// (intersection of InventoryJson with the bundled safelist), + /// and any GUIDs in InventoryJson that AREN'T in the safelist + /// (those would crash the watch's parser if shipped — silently + /// dropped by AvatarItemsController.Get(), but worth knowing + /// they're sitting in the inventory). + /// + /// Mostly here as a one-call sanity check after the avatar + /// pipeline overhaul: hit it against the player ID, eyeball the + /// drawer-count, confirm the ratio matches what's in the DB. + [HttpGet("players/{id:long}/clothing-debug")] + public async Task ClothingDebug(long id) + { + var avatar = await db.Avatars + .Where(a => a.PlayerId == id) + .Select(a => new + { + a.OutfitSelections, + a.InventoryJson, + a.HairColor, + a.SkinColor, + a.FaceFeatures, + a.UpdatedAt, + }) + .FirstOrDefaultAsync(); + if (avatar is null) return NotFound(new { Message = $"No AvatarEntity for player {id}" }); + + var safe = DorkNet.Server.Controllers.API.Avatar.V4.AvatarItemsController.SafeGuids; + var catalog = DorkNet.Server.Controllers.API.Avatar.V4.AvatarItemsController.Catalog; + + // Parse the player's inventory + intersect against the bundled + // safelist. Items that pass through end up in + // /api/avatar/v4/items as renderable wardrobe entries; items + // that fail are silently dropped at request time. + List inventoryGuids; + try + { + inventoryGuids = System.Text.Json.JsonSerializer + .Deserialize>(avatar.InventoryJson ?? "[]") ?? new(); + } + catch { inventoryGuids = new(); } + + var owned = inventoryGuids.Where(g => safe.Contains(g)).ToList(); + var unsafeGuids = inventoryGuids.Where(g => !safe.Contains(g)).ToList(); + + // Parse OutfitSelections (the equipped loadout) the same way + // the watch does — comma-separated, each segment a GUID. Show + // which slot each one resolves to + whether it's in the safe + // catalog. + var equipped = (avatar.OutfitSelections ?? string.Empty) + .Split(',', StringSplitOptions.RemoveEmptyEntries) + .Select(g => g.Trim()) + .Where(g => g.Length > 0) + .Select(g => new + { + Guid = g, + InSafelist = safe.Contains(g), + FriendlyName = catalog.TryGetValue(g, out var c) ? c.FriendlyName : null, + }) + .ToList(); + + return Ok(new + { + PlayerId = id, + avatar.UpdatedAt, + avatar.HairColor, + avatar.SkinColor, + avatar.FaceFeatures, + OutfitSelections = avatar.OutfitSelections, + EquippedItems = equipped, + InventoryRaw = avatar.InventoryJson, + InventoryCount = inventoryGuids.Count, + OwnedAndRenderable = owned.Count, + OwnedAndRenderableGuids = owned, + UnsafeOwnedGuids = unsafeGuids, + CatalogTotal = catalog.Count, + SafelistTotal = safe.Count, + }); + } + + /// GET api/admin/v1/rooms — flat list of every + /// room in the DB (id + name + creator + blob count) for the admin + /// UI's room-pickers. Custom (AG) rooms first, then RR Originals; + /// each ordered by name. The blob count helps the .htr mirror + /// dropdown skip rooms that have no blobs to scan. + [HttpGet("rooms")] + public async Task ListAllRooms() + { + var rooms = await db.Rooms + .OrderByDescending(r => r.IsAGRoom) + .ThenBy(r => r.Name) + .Select(r => new + { + r.Id, + r.Name, + r.IsAGRoom, + r.IsDormRoom, + r.CreatorPlayerId, + BlobCount = db.RoomDataBlobs.Count(b => b.RoomId == r.Id), + }) + .ToListAsync(); + return Ok(rooms); + } + + /// GET api/admin/v1/rooms/{id}/visitors — most + /// recent unique visitors of a room. Joins + /// against + /// so the admin UI can show "who's been here lately" with + /// usernames + per-player visit counts. + [HttpGet("rooms/{id:long}/visitors")] + public async Task RoomVisitors(long id, [FromQuery] int take = 50) + { + take = Math.Clamp(take, 1, 200); + var rows = await (from v in db.RoomVisits + join p in db.Players on v.PlayerId equals p.Id + where v.RoomId == id + orderby v.LastVisitAt descending + select new + { + PlayerId = v.PlayerId, + p.Username, + p.DisplayName, + v.VisitCount, + v.FirstVisitAt, + v.LastVisitAt, + }) + .Take(take) + .ToListAsync(); + return Ok(rows); + } + + [HttpDelete("rooms/{id:long}")] + public async Task DeleteRoom(long id, [FromBody] DeleteRoomRequest? body) + { + var room = await db.Rooms.FirstOrDefaultAsync(r => r.Id == id); + if (room is null) return NotFound(); + room.State = 1; // Archived + await LogAsync("delete_room", "room", id, body?.Reason ?? ""); + await db.SaveChangesAsync(); + return Ok(new { room.Id, room.State }); + } + + // The bulk "purge every custom room" endpoint was removed: a single + // wrong click could wipe every imported map in one shot. Per-room + // purge (below) is the only hard-delete path, gated by two + // independent room-name typed confirmations to make accidental + // wipes effectively impossible. + + public sealed record HardDeleteRoomRequest( + string? Reason, + // The admin UI prompts for the room name TWICE in separate + // inputs — both have to match the room's actual Name (exactly, + // case-sensitive) before the request will fire. Same pattern + // GitHub uses for repository deletion. + string? ConfirmName1, + string? ConfirmName2); + + /// POST api/admin/v1/rooms/{id}/purge — hard-delete a + /// single custom room. Refuses to act on TRUE RR Originals (owned + /// by the system seed account) or dorms; for those, the soft-delete + /// via DELETE /rooms/{id} is the only supported path so a fat + /// finger doesn't wipe canonical content. + /// + /// A user-cloned room often inherits IsAGRoom=true from its + /// source — that doesn't make it canonical, it just means the + /// matchmaker uses the AG-room join path. Gate purely on + /// CreatorPlayerId == SystemAccountId, which is the only + /// reliable "is this actually a seeded RR Original?" signal. + /// + /// Requires BOTH ConfirmName1 and ConfirmName2 in the + /// body to exactly match the room's current Name. Both must match + /// independently — the SPA renders two separate inputs and the + /// admin types the name twice. Returns 400 with + /// confirm_name_mismatch if either is missing/wrong so the + /// SPA can surface the error inline without leaking which one + /// failed. + [HttpPost("rooms/{id:long}/purge")] + public async Task HardDeleteRoom(long id, [FromBody] HardDeleteRoomRequest? body) + { + var room = await db.Rooms.FirstOrDefaultAsync(r => r.Id == id); + if (room is null) return NotFound(); + if (room.IsDormRoom) + return BadRequest(new { error = "refuses_to_hard_delete_dorm", note = "Dorms are per-player state; never purge." }); + if (room.IsAGRoom && room.CreatorPlayerId == PlayerService.SystemAccountId) + return BadRequest(new { error = "refuses_to_hard_delete_seeded_room", note = "This row is owned by the system seed account — soft-archive via DELETE /rooms/{id} instead." }); + + var confirm1 = body?.ConfirmName1 ?? string.Empty; + var confirm2 = body?.ConfirmName2 ?? string.Empty; + if (!string.Equals(confirm1, room.Name, StringComparison.Ordinal) + || !string.Equals(confirm2, room.Name, StringComparison.Ordinal)) + { + return BadRequest(new + { + error = "confirm_name_mismatch", + note = "Both ConfirmName1 and ConfirmName2 must exactly equal the room's current Name (case-sensitive).", + }); + } + + var scenes = await db.RoomScenes.Where(s => s.RoomId == id).ToListAsync(); + var roomBlobs = await db.RoomDataBlobs.Where(b => b.RoomId == id).ToListAsync(); + RoomDataBlobEntity? imageBlob = null; + if (!string.IsNullOrEmpty(room.ImageName)) + { + imageBlob = await db.RoomDataBlobs.FirstOrDefaultAsync(b => b.RoomId == 0 && b.BlobName == room.ImageName); + } + + db.RoomScenes.RemoveRange(scenes); + db.RoomDataBlobs.RemoveRange(roomBlobs); + if (imageBlob is not null) db.RoomDataBlobs.Remove(imageBlob); + db.Rooms.Remove(room); + + await LogAsync("hard_delete_room", "room", id, + $"name={room.Name} reason={body?.Reason ?? ""} blobs={roomBlobs.Count} scenes={scenes.Count}"); + await db.SaveChangesAsync(); + + return Ok(new + { + deleted = id, + name = room.Name, + blobs = roomBlobs.Count, + scenes = scenes.Count, + thumbnail = imageBlob is not null, + }); + } + + public sealed record WipeImportTargetsRequest( + long[]? InventionIds, + string[]? RoomNames); + + /// POST api/admin/v1/import/wipe-targets — clear the + /// DB rows that would conflict with a zip re-import. Built for the + /// zip importer's "Wipe duplicates first" affordance — when the + /// admin uploads a refreshed zip after a recovery pass added more + /// .inv bytes, the second import would otherwise: (a) reject every + /// room with duplicate_name, and (b) silently auto-id every + /// invention whose original id is already in the DB, leaving the + /// room blob's placement refs pointing at orphans (= empty/missing + /// inventions in-game). + /// + /// Wipes: + /// * rows for the listed ids + their + /// associated rows (.inv bytes + /// + image thumbnails) + /// * rows matching any of the listed + /// names exactly + their rows + + /// rows (scene blobs + room + /// image) + /// + /// Skips dorms and seed-owned RR-Originals (same safety net as + /// ) — only user-content survives the + /// wipe path. + /// + /// Returns a per-room and per-invention rollup so the UI can show + /// what landed before kicking off the actual upload. + [HttpPost("import/wipe-targets")] + public async Task WipeImportTargets([FromBody] WipeImportTargetsRequest body) + { + var inventionIds = body?.InventionIds ?? Array.Empty(); + var roomNames = body?.RoomNames ?? Array.Empty(); + if (inventionIds.Length == 0 && roomNames.Length == 0) + return BadRequest(new { error = "nothing_to_wipe" }); + + // ── Inventions ──────────────────────────────────────────────── + var inventions = inventionIds.Length == 0 + ? new List() + : await db.Inventions.Where(i => inventionIds.Contains(i.Id)).ToListAsync(); + var invBlobNames = inventions + .SelectMany(i => new[] { i.CurrentBlobName, i.ImageName }) + .Where(n => !string.IsNullOrEmpty(n)) + .Cast() + .Distinct() + .ToList(); + var invBlobs = invBlobNames.Count == 0 + ? new List() + : await db.RoomDataBlobs + .Where(b => b.RoomId == 0 && invBlobNames.Contains(b.BlobName)) + .ToListAsync(); + + // ── Rooms ───────────────────────────────────────────────────── + // Exact-name match — same key the importer uses for its + // duplicate-name rejection. Skip dorms + seed-owned RR-Originals + // so a fat-finger on the wipe list can't nuke canonical content. + var roomMatches = roomNames.Length == 0 + ? new List() + : await db.Rooms.Where(r => roomNames.Contains(r.Name)).ToListAsync(); + var roomsToDelete = roomMatches + .Where(r => !r.IsDormRoom + && !(r.IsAGRoom && r.CreatorPlayerId == PlayerService.SystemAccountId)) + .ToList(); + var roomIds = roomsToDelete.Select(r => r.Id).ToList(); + var roomScenes = roomIds.Count == 0 + ? new List() + : await db.RoomScenes.Where(s => roomIds.Contains(s.RoomId)).ToListAsync(); + var roomBlobs = roomIds.Count == 0 + ? new List() + : await db.RoomDataBlobs.Where(b => roomIds.Contains(b.RoomId)).ToListAsync(); + var roomImageNames = roomsToDelete + .Select(r => r.ImageName) + .Where(n => !string.IsNullOrEmpty(n)) + .Cast() + .Distinct() + .ToList(); + var roomImages = roomImageNames.Count == 0 + ? new List() + : await db.RoomDataBlobs + .Where(b => b.RoomId == 0 && roomImageNames.Contains(b.BlobName)) + .ToListAsync(); + + db.RoomDataBlobs.RemoveRange(invBlobs); + db.Inventions.RemoveRange(inventions); + db.RoomScenes.RemoveRange(roomScenes); + db.RoomDataBlobs.RemoveRange(roomBlobs); + db.RoomDataBlobs.RemoveRange(roomImages); + db.Rooms.RemoveRange(roomsToDelete); + + await LogAsync("wipe_import_targets", "import", 0, + $"inventions={inventions.Count}/{inventionIds.Length} " + + $"rooms={roomsToDelete.Count}/{roomNames.Length} " + + $"roomBlobs={roomBlobs.Count} roomScenes={roomScenes.Count} " + + $"invBlobs={invBlobs.Count} roomImages={roomImages.Count}"); + await db.SaveChangesAsync(); + + var skippedRooms = roomMatches + .Where(r => r.IsDormRoom + || (r.IsAGRoom && r.CreatorPlayerId == PlayerService.SystemAccountId)) + .Select(r => new { id = r.Id, name = r.Name, reason = r.IsDormRoom ? "dorm" : "seeded_rr_original" }) + .ToList(); + + return Ok(new + { + inventionsDeleted = inventions.Count, + inventionsRequested = inventionIds.Length, + inventionsNotFound = inventionIds.Length - inventions.Count, + inventionBlobsRemoved = invBlobs.Count, + roomsDeleted = roomsToDelete.Count, + roomsRequested = roomNames.Length, + roomsNotFound = roomNames.Length - roomMatches.Count, + roomScenesRemoved = roomScenes.Count, + roomBlobsRemoved = roomBlobs.Count + roomImages.Count, + skippedRooms, + }); + } + + public sealed record PurgeInventionsRequest( + long[]? InventionIds, + // The caller must echo back the count of ids it expects to delete. + // Mismatch = client and server disagree about scope → refuse. + // Same pattern as `git rm` requiring -f when files would be lost. + int? ExpectedCount); + + /// POST api/admin/v1/inventions/purge — hard-delete a + /// batch of inventions by id. Used by the zip-recovery workflow to + /// wipe a room's referenced inventions before re-importing a refreshed + /// zip — avoids the duplicate-id problem where the zip importer + /// auto-generates new ids when the original is already in the DB, + /// leaving the room blob's invention references pointing at orphans. + /// + /// Deletes: + /// * rows for the given ids + /// * Associated rows for both the + /// invention's CurrentBlobName (the .inv bytes) and + /// ImageName (the thumbnail). RoomId on those rows is 0 + /// for invention-owned blobs. + /// + /// Does NOT touch S3 — the importer will overwrite on re-import, and + /// orphan S3 keys are harmless (read path lookups them by BlobName + /// from the DB). + /// + /// Logs each delete to AdminActions for audit replay. + /// + [HttpPost("inventions/purge")] + public async Task PurgeInventions([FromBody] PurgeInventionsRequest body) + { + var ids = body?.InventionIds; + if (ids is null || ids.Length == 0) + return BadRequest(new { error = "missing_ids" }); + if (body!.ExpectedCount is not int ec || ec != ids.Length) + return BadRequest(new { error = "expected_count_mismatch", expected = body.ExpectedCount, actual = ids.Length }); + + var inventions = await db.Inventions + .Where(i => ids.Contains(i.Id)) + .ToListAsync(); + + var blobNames = inventions + .SelectMany(i => new[] { i.CurrentBlobName, i.ImageName }) + .Where(n => !string.IsNullOrEmpty(n)) + .Cast() + .Distinct() + .ToList(); + + var blobs = await db.RoomDataBlobs + .Where(b => b.RoomId == 0 && blobNames.Contains(b.BlobName)) + .ToListAsync(); + + db.RoomDataBlobs.RemoveRange(blobs); + db.Inventions.RemoveRange(inventions); + + await LogAsync("purge_inventions", "invention", 0, + $"count={inventions.Count} blobs={blobs.Count} ids=[{string.Join(",", inventions.Select(i => i.Id).Take(20))}{(inventions.Count > 20 ? ",..." : "")}]"); + await db.SaveChangesAsync(); + + return Ok(new + { + deleted = inventions.Count, + requested = ids.Length, + notFound = ids.Length - inventions.Count, + blobsRemoved = blobs.Count, + }); + } + + // ── Leaderboards ───────────────────────────────────────────────────── + + /// Verified stat-channel metadata. The 2020 watch's + /// ConditionalSetStat_ReturningNewValue handles the + /// high-vs-low-is-better decision locally, so the server doesn't + /// enforce direction — this is purely for the admin SPA to render + /// channels sensibly. + /// + /// Only entries cross-referenced against decompilation live here. + /// Channel 7 is verified by + /// StuntRunnerManager.get_TotalTimeStatChannelForLeaderboard + /// (StuntRunnerManager.txt:220-228 returns the literal 7); the + /// stored value is milliseconds, hence ValueFormat = "time-ms". + /// Other channels intentionally stay un-named — they used to ship + /// with best-effort guesses ("Paintball wins", "Dodgeball wins", + /// etc.) that were almost certainly wrong, so the admin UI now + /// renders unknown channels as "Channel N" rather than misleading + /// labels. Add new entries here only when you've verified the + /// channel mapping in the decompiled client. + private static readonly Dictionary KnownStatChannels = new() + { + [7] = new("Stunt Runner total time", true, "time-ms"), + }; + + private sealed record StatChannelMeta(string Name, bool LowerIsBetter, string ValueFormat); + + /// GET api/admin/v1/leaderboards/channels — summary + /// of every stat channel that has at least one row. Used by the + /// Leaderboards SPA tab to populate the channel picker. + [HttpGet("leaderboards/channels")] + public async Task ListChannels() + { + var rows = await db.LeaderboardStats + .GroupBy(s => s.StatChannel) + .Select(g => new + { + Channel = g.Key, + EntryCount = g.Count(), + MinValue = g.Min(x => x.Value), + MaxValue = g.Max(x => x.Value), + }) + .OrderBy(r => r.Channel) + .ToListAsync(); + // DB-defined meta beats hardcoded KnownStatChannels so admins + // can rename / reassign channels live without a code change. + var dbMeta = await db.LeaderboardChannelMeta.ToDictionaryAsync(c => c.Channel); + var labelled = rows.Select(r => + { + dbMeta.TryGetValue(r.Channel, out var live); + KnownStatChannels.TryGetValue(r.Channel, out var fallback); + return new + { + r.Channel, + Name = live?.Name is { Length: > 0 } n ? n : (fallback?.Name ?? $"Channel {r.Channel}"), + RoomId = live?.RoomId ?? 0, + LowerIsBetter = live?.LowerIsBetter ?? fallback?.LowerIsBetter ?? false, + ValueFormat = live?.ValueFormat ?? fallback?.ValueFormat ?? "count", + Verified = live is not null || fallback is not null, + r.EntryCount, + r.MinValue, + r.MaxValue, + }; + }).ToList(); + return Ok(new + { + Channels = labelled, + // Surface the known set as well so the SPA can offer + // "create a row in channel X" even when the channel is + // currently empty. + Known = KnownStatChannels.Select(kv => new + { + Channel = kv.Key, + kv.Value.Name, + kv.Value.LowerIsBetter, + kv.Value.ValueFormat, + }), + }); + } + + /// GET api/admin/v1/leaderboards/{channel} — every + /// stat row in a channel with the player's username, sorted by + /// the channel's natural direction (lower-is-better channels + /// sorted ascending, everything else descending). + [HttpGet("leaderboards/{channel:int}")] + public async Task ChannelDetail(int channel, [FromQuery] int take = 100) + { + take = Math.Clamp(take, 1, 1000); + var liveMeta = await db.LeaderboardChannelMeta.FirstOrDefaultAsync(c => c.Channel == channel); + KnownStatChannels.TryGetValue(channel, out var fallback); + var name = liveMeta?.Name is { Length: > 0 } ln ? ln : (fallback?.Name ?? $"Channel {channel}"); + var lowerIsBetter = liveMeta?.LowerIsBetter ?? fallback?.LowerIsBetter ?? false; + var valueFormat = liveMeta?.ValueFormat ?? fallback?.ValueFormat ?? "count"; + var query = db.LeaderboardStats + .Where(s => s.StatChannel == channel); + query = lowerIsBetter + ? query.OrderBy(s => s.Value) + : query.OrderByDescending(s => s.Value); + + var rows = await (from s in query.Take(take) + join p in db.Players on s.PlayerId equals p.Id into pj + from p in pj.DefaultIfEmpty() + select new + { + s.Id, + s.PlayerId, + Username = p == null ? null : p.Username, + DisplayName = p == null ? null : p.DisplayName, + s.Value, + s.UpdatedAt, + }).ToListAsync(); + return Ok(new + { + Channel = channel, + Name = name, + RoomId = liveMeta?.RoomId ?? 0, + LowerIsBetter = lowerIsBetter, + ValueFormat = valueFormat, + Verified = liveMeta is not null || fallback is not null, + Entries = rows.Select((r, i) => new + { + Rank = i + 1, + r.Id, + r.PlayerId, + r.Username, + r.DisplayName, + r.Value, + r.UpdatedAt, + }), + }); + } + + public sealed record SetLeaderboardScoreRequest(long PlayerId, int Value); + + /// POST api/admin/v1/leaderboards/{channel}/score + /// — upsert a single player's score in a channel. + [HttpPost("leaderboards/{channel:int}/score")] + public async Task SetScore(int channel, [FromBody] SetLeaderboardScoreRequest body) + { + var row = await db.LeaderboardStats.FirstOrDefaultAsync(s => + s.PlayerId == body.PlayerId && s.StatChannel == channel); + if (row is null) + { + row = new LeaderboardStatEntity + { + PlayerId = body.PlayerId, + StatChannel = channel, + Value = body.Value, + }; + db.LeaderboardStats.Add(row); + } + else + { + row.Value = body.Value; + row.UpdatedAt = DateTime.UtcNow; + } + await LogAsync("set_leaderboard_score", "player", body.PlayerId, $"channel={channel} value={body.Value}"); + await db.SaveChangesAsync(); + return Ok(new { row.PlayerId, row.StatChannel, row.Value }); + } + + [HttpDelete("leaderboards/{channel:int}/score/{playerId:long}")] + public async Task DeleteScore(int channel, long playerId) + { + var row = await db.LeaderboardStats.FirstOrDefaultAsync(s => + s.PlayerId == playerId && s.StatChannel == channel); + if (row is null) return NotFound(); + db.LeaderboardStats.Remove(row); + await LogAsync("delete_leaderboard_score", "player", playerId, $"channel={channel}"); + await db.SaveChangesAsync(); + return Ok(new { deleted = playerId, channel }); + } + + /// DELETE api/admin/v1/leaderboards/{channel} — + /// wipe every entry in a stat channel. Used for season resets or + /// to clear test data. + [HttpDelete("leaderboards/{channel:int}")] + public async Task WipeChannel(int channel) + { + var rows = await db.LeaderboardStats.Where(s => s.StatChannel == channel).ToListAsync(); + if (rows.Count == 0) return Ok(new { wiped = 0, channel }); + db.LeaderboardStats.RemoveRange(rows); + await LogAsync("wipe_leaderboard_channel", "system", channel, $"entries={rows.Count}"); + await db.SaveChangesAsync(); + return Ok(new { wiped = rows.Count, channel }); + } + + // ── Rec Room Originals (RROs) ──────────────────────────────────────── + + /// GET api/admin/v1/rooms/originals — the canonical + /// Rec Room Originals (rooms whose CreatorPlayerId == + /// PlayerService.SystemAccountId). Backs the RRO admin tab + /// which lets admins flip per-room properties (cloning, max + /// players, description) without scrolling through the global + /// rooms list. + [HttpGet("rooms/originals")] + public async Task ListOriginals() + { + var rooms = await db.Rooms + .Where(r => r.CreatorPlayerId == PlayerService.SystemAccountId) + .OrderBy(r => r.Name) + .Select(r => new + { + r.Id, + r.Name, + r.Description, + r.ImageName, + r.Accessibility, + r.CloningAllowed, + r.IsAGRoom, + r.IsDormRoom, + r.State, + r.TagsCsv, + r.HotScore, + r.VisitCount, + r.VisitorCount, + r.CheerCount, + r.CurrentDataBlobName, + SceneCount = db.RoomScenes.Count(s => s.RoomId == r.Id), + BlobCount = db.RoomDataBlobs.Count(b => b.RoomId == r.Id), + }) + .ToListAsync(); + return Ok(rooms); + } + + public sealed record UpdateRoomPropsRequest( + string? Description, + bool? CloningAllowed, + int? Accessibility, + string? TagsCsv, + double? HotScore, + int? State, + string? ImageName); + + /// POST api/admin/v1/rooms/{id}/props — partial + /// update of room properties. Only the non-null fields in the + /// body get written; the rest are left untouched. Backs the + /// RRO edit form and is also usable for regular custom rooms. + [HttpPost("rooms/{id:long}/props")] + public async Task UpdateRoomProps(long id, [FromBody] UpdateRoomPropsRequest body) + { + var room = await db.Rooms.FirstOrDefaultAsync(r => r.Id == id); + if (room is null) return NotFound(); + var changes = new List(); + if (body.Description is string desc) + { + room.Description = desc; + changes.Add($"description={desc.Length} chars"); + } + if (body.CloningAllowed is bool c) { room.CloningAllowed = c; changes.Add($"cloning={c}"); } + if (body.Accessibility is int a) { room.Accessibility = a; changes.Add($"accessibility={a}"); } + if (body.TagsCsv is string tags) { room.TagsCsv = tags; changes.Add($"tags={tags}"); } + if (body.HotScore is double h) { room.HotScore = h; changes.Add($"hot={h}"); } + if (body.State is int st) { room.State = st; changes.Add($"state={st}"); } + if (body.ImageName is string img) { room.ImageName = img; changes.Add($"image={img}"); } + + if (changes.Count == 0) return Ok(new { room.Id, unchanged = true }); + + await LogAsync("update_room_props", "room", id, string.Join(", ", changes)); + await db.SaveChangesAsync(); + return Ok(new { room.Id, room.Name, applied = changes }); + } + + // ── Room detail (unified per-room view backing the admin SPA) ───────── + + /// GET api/admin/v1/rooms/{id} — every column on the + /// room plus owner/co-owner/mod/host display info, blob count, scene + /// count, current data blob name, and the latest CheerCount. The + /// admin SPA's per-room detail page consumes this in one shot rather + /// than fanning out to the legacy /rooms, /rooms/originals, + /// /playerReputation endpoints separately. + [HttpGet("rooms/{id:long}")] + public async Task GetRoomDetail(long id) + { + var room = await db.Rooms.FirstOrDefaultAsync(r => r.Id == id); + if (room is null) return NotFound(); + + var sceneCount = await db.RoomScenes.CountAsync(s => s.RoomId == id); + var blobCount = await db.RoomDataBlobs.CountAsync(b => b.RoomId == id); + var roles = await db.RoomRoles + .Where(r => r.RoomId == id) + .OrderBy(r => r.Role).ThenBy(r => r.GrantedAt) + .ToListAsync(); + + // Hydrate display names for creator + every role grant in one + // hit so the UI doesn't have to follow up with /players/{id}. + var playerIds = roles.Select(r => r.PlayerId) + .Append(room.CreatorPlayerId) + .Distinct() + .ToList(); + var players = await db.Players + .Where(p => playerIds.Contains(p.Id)) + .Select(p => new { p.Id, p.Username, p.DisplayName, p.ProfileImageName }) + .ToListAsync(); + + object PlayerInfo(long pid) => + players.FirstOrDefault(p => p.Id == pid) is { } p + ? new { id = p.Id, username = p.Username, displayName = p.DisplayName, profileImageName = p.ProfileImageName } + : new { id = pid, username = $"#{pid}", displayName = "", profileImageName = (string?)null }; + + return Ok(new + { + room.Id, + room.Name, + room.Description, + room.ImageName, + room.State, + room.Accessibility, + room.IsAGRoom, + room.IsDormRoom, + room.CloningAllowed, + room.SupportsLevelVoting, + room.SupportsVRLow, + room.SupportsMobile, + room.SupportsScreens, + room.SupportsWalkVR, + room.SupportsTeleportVR, + room.AllowsJuniors, + room.DisableMicAutoMute, + room.RoomWarningMask, + room.CustomRoomWarning, + room.TagsCsv, + room.CheerCount, + room.FavoriteCount, + room.VisitCount, + room.VisitorCount, + room.HotScore, + room.LocationReplicationId, + room.CurrentDataBlobName, + room.CreatedAt, + room.UpdatedAt, + sceneCount, + blobCount, + owner = PlayerInfo(room.CreatorPlayerId), + roles = roles.Select(r => new + { + r.Id, + r.PlayerId, + r.Role, + r.Accepted, + r.GrantedByPlayerId, + r.GrantedAt, + player = PlayerInfo(r.PlayerId), + }).ToList(), + }); + } + + public sealed record AddRoleRequest(long PlayerId, int Role, bool Accepted = true); + + /// POST api/admin/v1/rooms/{id}/roles — grant a + /// (PlayerId, Role) pair on the room. Role: 0=CoOwner, 1=Moderator, + /// 2=Host. Idempotent — re-granting the same role flips Accepted + /// only. + [HttpPost("rooms/{id:long}/roles")] + public async Task AddRoomRole(long id, [FromBody] AddRoleRequest body) + { + if (body is null) return BadRequest("missing body"); + var room = await db.Rooms.FirstOrDefaultAsync(r => r.Id == id); + if (room is null) return NotFound(); + if (body.PlayerId <= 0) return BadRequest("missing player"); + if (body.Role is < 0 or > 2) return BadRequest("invalid role"); + + var existing = await db.RoomRoles.FirstOrDefaultAsync(r => + r.RoomId == id && r.PlayerId == body.PlayerId && r.Role == body.Role); + if (existing is null) + { + db.RoomRoles.Add(new RoomRoleEntity + { + RoomId = id, + PlayerId = body.PlayerId, + Role = body.Role, + Accepted = body.Accepted, + GrantedByPlayerId = CurrentAdminId, + }); + } + else + { + existing.Accepted = body.Accepted; + } + await LogAsync("room_role_add", "room", id, $"player={body.PlayerId} role={body.Role}"); + await db.SaveChangesAsync(); + return Ok(new { ok = true }); + } + + /// DELETE api/admin/v1/rooms/{id}/roles/{playerId}/{role} + /// — revoke a specific role grant. No-op if the row doesn't exist. + [HttpDelete("rooms/{id:long}/roles/{playerId:long}/{role:int}")] + public async Task RemoveRoomRole(long id, long playerId, int role) + { + var row = await db.RoomRoles.FirstOrDefaultAsync(r => + r.RoomId == id && r.PlayerId == playerId && r.Role == role); + if (row is null) return Ok(new { ok = true, deleted = 0 }); + db.RoomRoles.Remove(row); + await LogAsync("room_role_remove", "room", id, $"player={playerId} role={role}"); + await db.SaveChangesAsync(); + return Ok(new { ok = true, deleted = 1 }); + } + + public sealed record TransferOwnerRequest(long NewCreatorPlayerId); + + /// POST api/admin/v1/rooms/{id}/owner — reassign the + /// room's CreatorPlayerId. The previous owner is automatically added + /// as a CoOwner so the original creator keeps a foothold on their + /// own room. Reject self-transfers. + [HttpPost("rooms/{id:long}/owner")] + public async Task TransferOwner(long id, [FromBody] TransferOwnerRequest body) + { + if (body is null || body.NewCreatorPlayerId <= 0) return BadRequest("missing new owner"); + var room = await db.Rooms.FirstOrDefaultAsync(r => r.Id == id); + if (room is null) return NotFound(); + if (room.CreatorPlayerId == body.NewCreatorPlayerId) return Ok(new { ok = true, unchanged = true }); + + var previousOwner = room.CreatorPlayerId; + room.CreatorPlayerId = body.NewCreatorPlayerId; + room.UpdatedAt = DateTime.UtcNow; + + // Auto-grant the former owner CoOwner (id=0) so they don't lose + // access to a room they built. Idempotent — re-running this in + // a fast undo-redo flow won't accumulate dupes. + if (previousOwner > 0 && !await db.RoomRoles.AnyAsync(r => + r.RoomId == id && r.PlayerId == previousOwner && r.Role == 0)) + { + db.RoomRoles.Add(new RoomRoleEntity + { + RoomId = id, + PlayerId = previousOwner, + Role = 0, + Accepted = true, + GrantedByPlayerId = CurrentAdminId, + }); + } + + await LogAsync("room_transfer_owner", "room", id, $"from={previousOwner} to={body.NewCreatorPlayerId}"); + await db.SaveChangesAsync(); + return Ok(new { ok = true }); + } + + /// GET api/admin/v1/rooms/{id}/instances — active + /// Photon instances for one specific room. Subset of the global + /// /instances endpoint pre-filtered to one room; the admin + /// SPA's room detail page embeds these so admins don't have to + /// scroll a global instance list looking for matches. + /// + /// The masterPlayerId field is best-effort: Photon's master + /// is whoever has the lowest ActorNumber in the room (i.e. + /// the first to join). The server doesn't see ActorNumbers, but + /// the player with the lowest DorkNet player id among current + /// participants is a reasonable proxy on dorms (creator joined + /// first) and pretty good on user rooms. For non-dorm rooms with + /// the creator absent, the value just identifies a stable "lead" + /// participant — the room won't fall apart if it's not the actual + /// Photon master, and admins primarily use the badge to know + /// which row to click "Pull in" against to land in the right + /// shard. + [HttpGet("rooms/{id:long}/instances")] + public async Task GetRoomInstances(long id) + { + var room = await db.Rooms.Where(r => r.Id == id) + .Select(r => new { r.Id, r.CreatorPlayerId, r.IsDormRoom }) + .FirstOrDefaultAsync(); + + var onlinePids = onlinePresence.OnlinePlayerIds().ToList(); + var byInstance = new Dictionary pids)>(); + foreach (var pid in onlinePids) + { + var r = playerPresence.GetRoom(pid); + if (r is null || r.RoomId != id) continue; + if (!byInstance.TryGetValue(r.RoomInstanceId, out var slot)) + { + slot = (r.RoomInstanceId, r.RoomId, r.SubRoomId, r.Name ?? "", r.PhotonRoomId ?? "", r.PhotonRegionId ?? "", r.Location ?? "", r.MaxCapacity, r.IsPrivate, new List()); + } + slot.pids.Add(pid); + byInstance[r.RoomInstanceId] = slot; + } + if (byInstance.Count == 0) return Ok(Array.Empty()); + + var allPids = byInstance.Values.SelectMany(v => v.pids).Distinct().ToList(); + var players = await db.Players + .Where(p => allPids.Contains(p.Id)) + .Select(p => new { p.Id, p.Username, p.DisplayName }) + .ToDictionaryAsync(p => p.Id); + + return Ok(byInstance.Values.Select(i => + { + // Master heuristic: dorm owner if present, else lowest pid. + long master; + if (room is { IsDormRoom: true } && i.pids.Contains(room.CreatorPlayerId)) + master = room.CreatorPlayerId; + else + master = i.pids.OrderBy(p => p).FirstOrDefault(); + return new + { + roomInstanceId = i.roomInstanceId, + roomId = i.roomId, + subRoomId = i.subRoomId, + roomName = i.roomName, + photonRoomId = i.photonRoomId, + photonRegionId = i.photonRegionId, + location = i.location, + maxCapacity = i.maxCapacity, + isPrivate = i.isPrivate, + masterPlayerId = master, + participants = i.pids.Select(pid => players.TryGetValue(pid, out var p) + ? new { id = p.Id, username = p.Username, displayName = p.DisplayName, isMaster = pid == master } + : new { id = pid, username = $"#{pid}", displayName = "", isMaster = pid == master }).ToList(), + }; + })); + } + + public sealed record PullIntoInstanceRequest(long PlayerId); + + /// POST api/admin/v1/rooms/{id}/instances/{instanceId}/pull + /// — pull a specific player into one already-active instance of this + /// room. Wraps the existing player force-join flow but pre-fills the + /// instance and validates that the target instance is actually live + /// in this room. Useful for "drop me into the shard the streamer is + /// already in" without having to dig the instance id out by hand. + [HttpPost("rooms/{id:long}/instances/{instanceId:long}/pull")] + public async Task PullIntoInstance(long id, long instanceId, [FromBody] PullIntoInstanceRequest body) + { + if (body is null || body.PlayerId <= 0) return BadRequest("missing player"); + + // Find any participant currently in the target instance — we + // need their RoomInstanceDto to know which Photon shard to + // route the pulled player into. If nobody's online in the + // instance anymore the instance has effectively died; reject. + DorkNet.Server.Controllers.Match.RoomInstanceDto? sample = null; + foreach (var pid in onlinePresence.OnlinePlayerIds()) + { + var r = playerPresence.GetRoom(pid); + if (r is not null && r.RoomId == id && r.RoomInstanceId == instanceId) + { + sample = r; + break; + } + } + if (sample is null) return NotFound(new { error = "instance_not_active" }); + + // Re-stamp presence on the target so their next heartbeat / + // goto sees us pointing at this instance. Push a SignalR + // SubscriptionUpdateGameSession so the watch's matchmaking + // updates LocalRoomInstance immediately. + playerPresence.SetRoom(body.PlayerId, sample); + await notifications.NotifyAsync(body.PlayerId, + PushNotificationId.SubscriptionUpdateGameSession, + sample); + await LogAsync("instance_pull", "instance", instanceId, $"player={body.PlayerId} room={id}"); + await db.SaveChangesAsync(); + return Ok(new { ok = true, instanceId, roomInstanceId = sample.RoomInstanceId }); + } + + /// POST api/admin/v1/rooms/{id}/instances/{instanceId}/close + /// — kick every player currently in the instance via ModerationKick + /// pushes and clear their presence so the server-side view matches. + /// The Photon room itself isn't directly destroyed (we don't have + /// Photon admin credentials); it dies on its own once the last + /// participant disconnects. In practice every kick fires within + /// the same second, so the room is empty seconds later. + [HttpPost("rooms/{id:long}/instances/{instanceId:long}/close")] + public async Task CloseInstance(long id, long instanceId, [FromBody] KickRequest? body) + { + var reason = body?.Reason ?? "Instance closed by admin"; + var kicked = new List(); + foreach (var pid in onlinePresence.OnlinePlayerIds().ToList()) + { + var r = playerPresence.GetRoom(pid); + if (r is null || r.RoomId != id || r.RoomInstanceId != instanceId) continue; + await notifications.NotifyAsync(pid, PushNotificationId.ModerationKick, new { Reason = reason }); + playerPresence.Clear(pid); + kicked.Add(pid); + } + if (kicked.Count > 0) + { + await LogAsync("instance_close", "instance", instanceId, $"room={id} kicked={kicked.Count}"); + await db.SaveChangesAsync(); + } + return Ok(new { ok = true, kicked }); + } + + /// GET api/admin/v1/rooms/{id}/leaderboards — every + /// stat channel mapped to this room via + /// , plus the row count + /// per channel and the top scorer. Channels that report scores but + /// haven't been mapped yet show up in + /// instead. + [HttpGet("rooms/{id:long}/leaderboards")] + public async Task GetRoomLeaderboards(long id) + { + var metas = await db.LeaderboardChannelMeta + .Where(c => c.RoomId == id) + .OrderBy(c => c.Channel) + .ToListAsync(); + if (metas.Count == 0) return Ok(Array.Empty()); + + var channels = metas.Select(m => m.Channel).ToList(); + var counts = await db.LeaderboardStats + .Where(s => channels.Contains(s.StatChannel)) + .GroupBy(s => s.StatChannel) + .Select(g => new { Channel = g.Key, Count = g.Count(), Best = g.Max(x => x.Value), Worst = g.Min(x => x.Value) }) + .ToListAsync(); + + return Ok(metas.Select(m => + { + var stat = counts.FirstOrDefault(c => c.Channel == m.Channel); + return new + { + m.Channel, + m.Name, + m.LowerIsBetter, + m.ValueFormat, + EntryCount = stat?.Count ?? 0, + BestValue = stat is null ? (long?)null : (m.LowerIsBetter ? stat.Worst : stat.Best), + }; + })); + } + + public sealed record UpsertChannelMetaRequest(int Channel, long RoomId, string Name, bool LowerIsBetter, string? ValueFormat); + + /// POST api/admin/v1/leaderboards/meta — register or + /// rename a stat channel + assign it to a room. Idempotent; updates + /// the existing row when the channel already has metadata. Does NOT + /// touch the value rows themselves — admins can register a channel + /// before any scores are reported. + [HttpPost("leaderboards/meta")] + public async Task UpsertChannelMeta([FromBody] UpsertChannelMetaRequest body) + { + if (body is null) return BadRequest("missing body"); + var row = await db.LeaderboardChannelMeta.FirstOrDefaultAsync(c => c.Channel == body.Channel); + if (row is null) + { + row = new LeaderboardChannelMetaEntity { Channel = body.Channel }; + db.LeaderboardChannelMeta.Add(row); + } + row.RoomId = body.RoomId; + row.Name = body.Name ?? string.Empty; + row.LowerIsBetter = body.LowerIsBetter; + row.ValueFormat = string.IsNullOrWhiteSpace(body.ValueFormat) ? "count" : body.ValueFormat; + row.UpdatedAt = DateTime.UtcNow; + await LogAsync("leaderboard_meta_upsert", "channel", body.Channel, $"room={body.RoomId} name={row.Name}"); + await db.SaveChangesAsync(); + return Ok(new { row.Channel, row.RoomId, row.Name, row.LowerIsBetter, row.ValueFormat }); + } + + /// DELETE api/admin/v1/leaderboards/meta/{channel} — + /// remove the metadata row. The channel goes back to showing as + /// "Channel N" / unscoped. Existing score rows are kept. + [HttpDelete("leaderboards/meta/{channel:int}")] + public async Task DeleteChannelMeta(int channel) + { + var row = await db.LeaderboardChannelMeta.FirstOrDefaultAsync(c => c.Channel == channel); + if (row is null) return Ok(new { ok = true, deleted = 0 }); + db.LeaderboardChannelMeta.Remove(row); + await LogAsync("leaderboard_meta_remove", "channel", channel, ""); + await db.SaveChangesAsync(); + return Ok(new { ok = true, deleted = 1 }); + } + + /// GET api/admin/v1/leaderboards/orphans — every + /// stat channel id that has at least one score row but no meta row. + /// Used by the admin Leaderboards UI to surface "you have data for + /// channel 11 — want to give it a name and assign it to a room?". + /// Critical for figuring out which channels Stunt Runner courses + /// actually use: load a course, drive it, then orphans surfaces + /// the channel id the game just reported. + [HttpGet("leaderboards/orphans")] + public async Task LeaderboardOrphans() + { + var mapped = await db.LeaderboardChannelMeta.Select(c => c.Channel).ToListAsync(); + var rows = await db.LeaderboardStats + .Where(s => !mapped.Contains(s.StatChannel)) + .GroupBy(s => s.StatChannel) + .Select(g => new { Channel = g.Key, EntryCount = g.Count(), LastSeen = g.Max(s => s.UpdatedAt) }) + .OrderByDescending(r => r.LastSeen) + .ToListAsync(); + return Ok(rows); + } + + // ── Server settings (runtime toggles) ──────────────────────────────── + + /// GET api/admin/v1/settings — current server-wide + /// toggle state. Single row, single round trip. + [HttpGet("settings")] + public async Task GetServerSettings() + { + var row = await serverSettings.GetAsync(); + return Ok(new + { + row.SignupsDisabled, + row.WeeklyChallengesCompletedRequired, + row.UpdatedAt, + }); + } + + public sealed record SignupsToggleRequest(bool Disabled); + + /// POST api/admin/v1/settings/signups — flip the + /// server-wide account-creation kill switch. While disabled, both + /// POST /account/create and POST /api/account/v1/create + /// reply with Success=false + a user-facing error string and + /// the watch surfaces that to whoever tried to sign up. Existing + /// logins are untouched. + [HttpPost("settings/signups")] + public async Task SetSignupsDisabled([FromBody] SignupsToggleRequest body) + { + var row = await serverSettings.SetSignupsDisabledAsync(body.Disabled); + await LogAsync(body.Disabled ? "signups_disabled" : "signups_enabled", "system", 0, ""); + await db.SaveChangesAsync(); + return Ok(new + { + row.SignupsDisabled, + row.WeeklyChallengesCompletedRequired, + row.UpdatedAt, + }); + } + + [HttpGet("settings/weekly-challenges")] + public async Task GetWeeklyChallenges() + { + var weekly = await serverSettings.GetWeeklyChallengesAsync(); + return Ok(weekly); + } + + [HttpGet("settings/weekly-challenges/reward-options")] + public async Task GetWeeklyChallengeRewardOptions() + { + var rows = await db.StoreItems + .Where(i => i.IsActive) + .OrderBy(i => i.DisplayName) + .ToListAsync(); + + static object Option( + string kind, + StoreItemEntity item, + string avatarItemDesc = "", + string consumableItemDesc = "", + string equipmentPrefabName = "", + string equipmentModificationGuid = "") => new + { + Kind = kind, + Label = $"{item.DisplayName} ({item.Slug})", + GiftDropId = (int)(item.Id & 0x7fffffff), + AvatarItemDesc = avatarItemDesc, + ConsumableItemDesc = consumableItemDesc, + EquipmentPrefabName = equipmentPrefabName, + EquipmentModificationGuid = equipmentModificationGuid, + }; + + var avatarItems = rows + .Select(i => StoreService.TryGetAvatarItemPayload(i.Slug, out _, out var desc) + ? Option("avatar", i, avatarItemDesc: desc) + : null) + .Where(o => o is not null) + .ToArray(); + + var consumables = rows + .Select(i => StoreService.TryGetConsumableItemDesc(i.Slug, out var desc) + ? Option("consumable", i, consumableItemDesc: desc) + : null) + .Where(o => o is not null) + .ToArray(); + + var equipment = rows + .Select(i => StoreService.TryGetEquipmentPayload(i.Slug, out var prefab, out var mod) + ? Option("equipment", i, equipmentPrefabName: prefab, equipmentModificationGuid: mod) + : null) + .Where(o => o is not null) + .ToArray(); + + return Ok(new + { + AvatarItems = avatarItems, + Consumables = consumables, + Equipment = equipment, + }); + } + + public sealed record WeeklyChallengeSettingsRequest( + bool? CompletedRequired, + List? Challenges, + WeeklyChallengeReward? Reward); + + [HttpPost("settings/weekly-challenges")] + public async Task SetWeeklyChallenges([FromBody] WeeklyChallengeSettingsRequest body) + { + var completedRequired = body.CompletedRequired ?? true; + var challenges = body.Challenges ?? ServerSettingsService.DefaultWeeklyChallenges(); + var weekly = await serverSettings.SetWeeklyChallengesAsync(completedRequired, challenges, body.Reward); + await LogAsync("weekly_challenges_updated", "system", 0, ""); + await db.SaveChangesAsync(); + return Ok(weekly); + } + + // ── Audit log ──────────────────────────────────────────────────────── + + [HttpGet("audit")] + public async Task GetAuditLog([FromQuery] int take = 100) + { + take = Math.Clamp(take, 1, 500); + var rows = await db.AdminActions + .OrderByDescending(a => a.Timestamp) + .Take(take) + .ToListAsync(); + return Ok(rows); + } + + // ── Reports moderation ─────────────────────────────────────────────── + + /// List unresolved player reports, oldest-first. + /// Admins triage from this queue. + [HttpGet("reports")] + public async Task ListReports([FromQuery] int take = 50) + { + take = Math.Clamp(take, 1, 200); + var rows = await db.Reports + .Where(r => r.ResolvedAt == null) + .OrderBy(r => r.CreatedAt) + .Take(take) + .ToListAsync(); + return Ok(rows); + } + + public sealed record ResolveReportRequest(string? Note); + + /// Mark a report as resolved with an optional note. + /// The actual moderation action (ban, warn, dismiss) is the + /// admin's call; this endpoint only closes the report row. + [HttpPost("reports/{id:long}/resolve")] + public async Task ResolveReport(long id, [FromBody] ResolveReportRequest? body) + { + var rep = await db.Reports.FirstOrDefaultAsync(r => r.Id == id); + if (rep is null) return NotFound(); + if (rep.ResolvedAt is not null) return Ok(new { already_resolved = true }); + + rep.ResolvedAt = DateTime.UtcNow; + rep.ResolverAdminId = CurrentAdminId; + rep.ResolutionNote = (body?.Note ?? string.Empty).Trim(); + await LogAsync("resolve_report", "report", id, rep.ResolutionNote); + await db.SaveChangesAsync(); + return Ok(new { rep.Id, rep.ResolvedAt }); + } + + // ── Kick / IP ban ──────────────────────────────────────────────────── + + public sealed record KickRequest(string? Reason); + + /// Force-disconnect a player from the SignalR hub and + /// signal them to reload via a ModerationKick push. Doesn't ban + /// — the player can immediately reconnect — but boots them out + /// of whatever room/session they're in. For temporary + /// moderation actions where a full ban is too heavy. + [HttpPost("players/{id:long}/kick")] + public async Task KickPlayer(long id, [FromBody] KickRequest? body) + { + await notifications.NotifyAsync(id, PushNotificationId.ModerationKick, + new { Reason = body?.Reason ?? "" }); + await LogAsync("kick_player", "player", id, body?.Reason ?? ""); + await db.SaveChangesAsync(); + return Ok(new { kicked = id }); + } + + public sealed record IpBanRequest(string Cidr, string? Reason, int? DurationDays); + + /// Add an IP-level ban. Optional duration in days + /// (null = permanent until removed). Format: dotted IPv4 or + /// CIDR like 1.2.3.0/24. + [HttpPost("ipbans")] + public async Task AddIpBan([FromBody] IpBanRequest body) + { + if (string.IsNullOrWhiteSpace(body.Cidr)) + return BadRequest("missing cidr"); + + var until = body.DurationDays is int days + ? DateTime.UtcNow.AddDays(Math.Clamp(days, 1, 3650)) + : (DateTime?)null; + + var entry = new IpBanEntity + { + Cidr = body.Cidr.Trim(), + Reason = (body.Reason ?? string.Empty).Trim(), + BannedByAdminId = CurrentAdminId, + Until = until, + }; + db.IpBans.Add(entry); + await LogAsync("ipban_add", "ip", 0, $"{body.Cidr} until {until:o}"); + await db.SaveChangesAsync(); + return Ok(entry); + } + + [HttpDelete("ipbans/{id:long}")] + public async Task RemoveIpBan(long id) + { + var ban = await db.IpBans.FirstOrDefaultAsync(b => b.Id == id); + if (ban is null) return NotFound(); + db.IpBans.Remove(ban); + await LogAsync("ipban_remove", "ip", id, ban.Cidr); + await db.SaveChangesAsync(); + return Ok(); + } + + [HttpGet("ipbans")] + public async Task ListIpBans() + { + var rows = await db.IpBans + .OrderByDescending(b => b.BannedAt) + .ToListAsync(); + return Ok(rows); + } + + // ── Stats ──────────────────────────────────────────────────────────── + + /// Real-time server snapshot for the admin dashboard. + /// Every aggregate is one cheap query — designed to be hit by + /// the admin watch tab on a 5-second timer without melting the + /// SQLite write path. + [HttpGet("stats")] + public async Task Stats() + { + var totalPlayers = await db.Players.CountAsync(); + var totalRooms = await db.Rooms.CountAsync(); + var totalInventions = await db.Inventions.CountAsync(); + var openReports = await db.Reports.CountAsync(r => r.ResolvedAt == null); + var bannedNow = await db.Players.CountAsync(p => + p.BannedUntil != null && p.BannedUntil > DateTime.UtcNow); + var activeIpBans = await db.IpBans.CountAsync(b => + b.Until == null || b.Until > DateTime.UtcNow); + + var online = onlinePresence.OnlinePlayerIds().ToArray(); + + var topRooms = await db.Rooms + .OrderByDescending(r => r.VisitCount) + .Take(10) + .Select(r => new { r.Id, r.Name, r.VisitCount, r.VisitorCount, r.CheerCount }) + .ToListAsync(); + + var recentJoins = await db.Players + .OrderByDescending(p => p.CreatedAt) + .Take(10) + .Select(p => new { p.Id, p.Username, p.CreatedAt }) + .ToListAsync(); + + return Ok(new + { + Players = new + { + Total = totalPlayers, + OnlineNow = online.Length, + BannedNow = bannedNow, + }, + Rooms = new { Total = totalRooms, TopByVisits = topRooms }, + Inventions = totalInventions, + Moderation = new + { + OpenReports = openReports, + ActiveIpBans = activeIpBans, + }, + RecentJoins = recentJoins, + ServerTime = DateTime.UtcNow, + }); + } + + // ── Avatar / outfit / inventory grants ────────────────────────────── + + public sealed record OutfitRequest(string? OutfitSelections, string? FaceFeatures, string? HairColor, string? SkinColor); + + /// Get a player's current avatar so the admin UI can + /// pre-fill its edit form. Returns the same DTO shape as + /// GET api/avatar/v2/equipped/{id}. + [HttpGet("players/{id:long}/avatar")] + public async Task GetAvatar(long id) + { + var avatar = await db.Avatars.FirstOrDefaultAsync(a => a.PlayerId == id); + if (avatar is null) return NotFound(); + return Ok(new + { + avatar.PlayerId, + avatar.OutfitSelections, + avatar.FaceFeatures, + avatar.HairColor, + avatar.SkinColor, + avatar.UpdatedAt, + }); + } + + /// POST api/admin/v1/players/{id}/avatar/reset — + /// wipe a player's equipped outfit + inventory back to the canonical + /// 2020 starter values. Recovery path for the + /// "watch crashes after ~10 minutes" symptom where a player has an + /// item GUID (e.g. Eye_MirrorShades_PinkClear) whose + /// outfits_assets bundle isn't shipped with this build, so + /// .SpawnOutfitImposter throws a + /// Dependency Exception from Addressables and the engine eventually + /// tears down the local player. Resetting clears those entries + /// from and + /// so the next avatar + /// load only references items the local build has on disk. + [HttpPost("players/{id:long}/avatar/reset")] + public async Task ResetAvatar(long id) + { + var avatar = await db.Avatars.FirstOrDefaultAsync(a => a.PlayerId == id); + if (avatar is null) + { + avatar = new AvatarEntity { PlayerId = id }; + db.Avatars.Add(avatar); + } + avatar.OutfitSelections = PlayerService.StarterOutfitSelections; + avatar.InventoryJson = PlayerService.StarterInventoryJson; + avatar.HairColor = PlayerService.StarterHairColor; + avatar.SkinColor = PlayerService.StarterSkinColor; + avatar.UpdatedAt = DateTime.UtcNow; + + await LogAsync("reset_avatar", "player", id, ""); + await db.SaveChangesAsync(); + + // Push a profile update so the affected watch re-fetches its + // avatar without waiting for the next scene load. + await notifications.NotifyAsync(id, + PushNotificationId.SubscriptionUpdateProfile, + new { Reason = "AdminAvatarReset" }); + + return Ok(new + { + avatar.PlayerId, + avatar.OutfitSelections, + avatar.HairColor, + avatar.SkinColor, + }); + } + + /// Replace another player's outfit / hair / skin colour. + /// Pushes SubscriptionUpdateProfile so the affected watch + /// re-fetches its avatar and the new clothes propagate to other + /// players in the same room. + [HttpPost("players/{id:long}/avatar")] + public async Task SetAvatar(long id, [FromBody] OutfitRequest body) + { + var avatar = await db.Avatars.FirstOrDefaultAsync(a => a.PlayerId == id); + if (avatar is null) + { + avatar = new AvatarEntity { PlayerId = id }; + db.Avatars.Add(avatar); + } + if (body.OutfitSelections is not null) avatar.OutfitSelections = body.OutfitSelections; + if (body.FaceFeatures is not null) avatar.FaceFeatures = body.FaceFeatures; + if (body.HairColor is not null) avatar.HairColor = body.HairColor; + if (body.SkinColor is not null) avatar.SkinColor = body.SkinColor; + avatar.UpdatedAt = DateTime.UtcNow; + + await LogAsync("set_avatar", "player", id, body.OutfitSelections ?? ""); + await db.SaveChangesAsync(); + + await notifications.NotifyAsync(id, + PushNotificationId.SubscriptionUpdateProfile, + new { Reason = "AdminAvatarUpdate" }); + + return Ok(new { avatar.PlayerId, avatar.OutfitSelections, avatar.HairColor, avatar.SkinColor }); + } + + public sealed record GrantItemRequest(string ItemId, int Quantity); + + /// Append an item GUID to the player's inventory JSON list. + /// The client treats InventoryJson as the truth source for + /// what they own; once granted, the next avatar fetch shows the + /// item available in the customisation menu. + [HttpPost("players/{id:long}/inventory/grant")] + public async Task GrantItem(long id, [FromBody] GrantItemRequest body) + { + if (string.IsNullOrWhiteSpace(body.ItemId)) return BadRequest("missing itemId"); + var qty = Math.Clamp(body.Quantity, 1, 1000); + + var avatar = await db.Avatars.FirstOrDefaultAsync(a => a.PlayerId == id); + if (avatar is null) + { + avatar = new AvatarEntity { PlayerId = id }; + db.Avatars.Add(avatar); + } + + // Inventory wire format: JSON array of { itemId, quantity }. Merge + // by itemId so repeat grants don't duplicate rows. + var list = ParseInventory(avatar.InventoryJson); + var existing = list.FirstOrDefault(e => e.ItemId == body.ItemId); + if (existing is null) + list.Add(new InventoryEntry { ItemId = body.ItemId, Quantity = qty }); + else + existing.Quantity += qty; + avatar.InventoryJson = System.Text.Json.JsonSerializer.Serialize(list); + avatar.UpdatedAt = DateTime.UtcNow; + + await LogAsync("grant_item", "player", id, $"{body.ItemId} x{qty}"); + await db.SaveChangesAsync(); + + await notifications.NotifyAsync(id, + PushNotificationId.SubscriptionUpdateProfile, + new { Reason = "InventoryGrant", body.ItemId, Quantity = qty }); + + return Ok(new { granted = body.ItemId, quantity = qty }); + } + + public sealed record CurrencyRequest(int CurrencyType, long Amount, string? Reason); + + /// Adjust a player's currency wallet. Positive amounts + /// grant; negative deduct (clamped to zero by + /// ). The client's + /// next storefronts/balance fetch returns the new value. + [HttpPost("players/{id:long}/currency")] + public async Task GrantCurrency(long id, [FromBody] CurrencyRequest body) + { + var newBalance = await level.GrantCurrencyAsync( + id, body.CurrencyType, body.Amount, body.Reason ?? "admin_grant"); + await LogAsync("grant_currency", "player", id, + $"type={body.CurrencyType} amount={body.Amount}"); + await db.SaveChangesAsync(); + return Ok(new { id, body.CurrencyType, balance = newBalance }); + } + + public sealed record XpRequest(int Amount, string? Reason); + + /// Grant XP to a player. Triggers level-up rewards via + /// if the new XP crosses a + /// level threshold. + [HttpPost("players/{id:long}/xp")] + public async Task GrantXp(long id, [FromBody] XpRequest body) + { + var (lvl, xp) = await level.AwardXpAsync(id, body.Amount, body.Reason ?? "admin_grant"); + await LogAsync("grant_xp", "player", id, $"amount={body.Amount}"); + await db.SaveChangesAsync(); + return Ok(new { id, level = lvl, xp }); + } + + /// Insert one row per cheer Type + /// (0=General, 1=Helpful, 2=GreatHost, 3=Sportsman, 4=Creative, + /// 5=Credit) so the player has at least one received cheer in every + /// category. The watch's pinned-cheer picker (see + /// ) + /// only lets a player pin a category they've actually received — this + /// unlocks every badge for them without having to wait on real + /// peer cheers. + /// + /// Inserts come from system account id=1 (the seed admin). Idempotent + /// on (FromPlayerId, TargetPlayerId, Type) — re-running just fills in + /// whichever categories aren't there yet. Does NOT award XP (these + /// are admin-injected, not real peer interactions), so the player's + /// level isn't inflated. + [HttpPost("players/{id:long}/cheers/unlock")] + public async Task UnlockCheers(long id) + { + var p = await db.Players.FirstOrDefaultAsync(x => x.Id == id); + if (p is null) return NotFound(); + + // RoomieCheerType enum mirror — see CheerEntity.Type docblock. + var allTypes = new[] { 0, 1, 2, 3, 4, 5 }; + var existingTypes = await db.Cheers + .Where(c => c.TargetPlayerId == id && c.TargetRoomId == 0 + && c.TargetPhotoId == 0 && c.TargetInventionId == 0) + .Select(c => c.Type) + .Distinct() + .ToListAsync(); + var missing = allTypes.Except(existingTypes).ToList(); + foreach (var t in missing) + { + db.Cheers.Add(new CheerEntity + { + FromPlayerId = 1, // system / seed-admin account + TargetPlayerId = id, + Type = t, + }); + } + await LogAsync("unlock_cheers", "player", id, + $"inserted={missing.Count} types=[{string.Join(",", missing)}]"); + await db.SaveChangesAsync(); + + // Nudge the watch to re-fetch its cheer list so the pinned-cheer + // picker shows the newly-unlocked badges without a relog. + await notifications.NotifyAsync(id, + PushNotificationId.SubscriptionUpdateProfile, + new { Reason = "AdminCheerUnlock", Inserted = missing.Count }); + + return Ok(new { id, inserted = missing.Count, types = missing }); + } + + public sealed record ProfileFlagsRequest( + bool? IsVerified, + bool? IsDeveloper, + bool? IsJunior, + bool? IsCommunityTeam); + + /// Toggle the verified / developer / community-team / + /// junior flags on a player profile. Verified shows up as the + /// blue-checkmark badge on the watch's profile card; IsDeveloper + /// and IsCommunityTeam both unlock the watch's overhead-badge + /// slider (see ) + /// — once unlocked, the player picks "Community Team" or + /// "Developer" themselves from the in-watch settings menu and the + /// label renders above their head for every other player in the + /// room. We expose both as separate admin flags so the audit log + /// preserves the distinction even though the 2020 watch can't + /// enforce one badge over the other. + [HttpPost("players/{id:long}/flags")] + public async Task SetFlags(long id, [FromBody] ProfileFlagsRequest body) + { + var p = await db.Players.FirstOrDefaultAsync(x => x.Id == id); + if (p is null) return NotFound(); + if (body.IsVerified is bool v) p.IsVerified = v; + if (body.IsDeveloper is bool d) p.IsDeveloper = d; + if (body.IsJunior is bool j) p.IsJunior = j; + if (body.IsCommunityTeam is bool ct) p.IsCommunityTeam = ct; + await LogAsync("set_flags", "player", id, + $"verified={p.IsVerified} dev={p.IsDeveloper} communityteam={p.IsCommunityTeam} junior={p.IsJunior}"); + await db.SaveChangesAsync(); + return Ok(new { p.Id, p.IsVerified, p.IsDeveloper, p.IsCommunityTeam, p.IsJunior }); + } + + public sealed record DisplayNameRequest(string DisplayName); + + /// Force-set a player's display name (admin override of + /// the normal username-uniqueness flow). + [HttpPost("players/{id:long}/displayName")] + public async Task SetDisplayName(long id, [FromBody] DisplayNameRequest body) + { + var p = await db.Players.FirstOrDefaultAsync(x => x.Id == id); + if (p is null) return NotFound(); + if (string.IsNullOrWhiteSpace(body.DisplayName)) return BadRequest("empty"); + p.DisplayName = body.DisplayName.Trim(); + await LogAsync("set_display_name", "player", id, p.DisplayName); + await db.SaveChangesAsync(); + return Ok(new { p.Id, p.DisplayName }); + } + + private sealed class InventoryEntry + { + [System.Text.Json.Serialization.JsonPropertyName("itemId")] + public string ItemId { get; set; } = string.Empty; + [System.Text.Json.Serialization.JsonPropertyName("quantity")] + public int Quantity { get; set; } + } + + private static List ParseInventory(string? json) + { + if (string.IsNullOrWhiteSpace(json) || json == "[]") return new(); + try + { + return System.Text.Json.JsonSerializer.Deserialize>(json) ?? new(); + } + catch + { + return new(); + } + } + + // ── Avatar catalog (gifting picker source) ─────────────────────────── + + /// GET api/admin/v1/avatar-items — flat list of every + /// renderable avatar item, keyed by the GUID the inventory grant + /// endpoint expects. Backs the Gift page's item picker so admins can + /// browse by friendly name instead of pasting GUIDs by hand. Read-only. + /// Catalog source is data/avatar_item_lookup.json, parsed once + /// at startup by . + [HttpGet("avatar-items")] + public ActionResult ListAvatarItems() + { + var catalog = DorkNet.Server.Controllers.API.Avatar.V4.AvatarItemsController.Catalog; + var safe = DorkNet.Server.Controllers.API.Avatar.V4.AvatarItemsController.SafeGuids; + var rows = catalog + .Select(kv => new + { + Guid = kv.Key, + Slot = kv.Value.AvatarItemType, + FriendlyName = kv.Value.FriendlyName, + Tooltip = kv.Value.Tooltip, + Rarity = kv.Value.Rarity, + Safe = safe.Contains(kv.Key), + }) + .Where(r => r.Safe) // hide GUIDs that would crash the watch's parser + .OrderBy(r => r.Slot) + .ThenBy(r => r.FriendlyName) + .ToList(); + return Ok(rows); + } + + // ── Send gift package (in-game gift box popup) ──────────────────────── + + public sealed record SendGiftRequest( + string? AvatarItemGuid, + int? AvatarItemType, // 0 = Outfit, 1 = HairDye (per RecNet.Avatars+AvatarItemType) + int? CurrencyType, // 1 = Tokens, 2 = Coins (0 = none) + int? Currency, + int? Xp, + int? Level, + string? Message, + int? Rarity); // 0 Common, 10 Uncommon, 20 Rare, 30 Epic, 50 Legendary + + /// POST api/admin/v1/players/{id}/gift — drop a + /// into the recipient's gift inbox. + /// The watch's RecNet.Avatars.DowloadGiftPackages loop picks + /// it up on the next poll and shows the standard gift-box popup; + /// the player taps "open" to fire LocalConsumeGiftPackage, + /// which hits + /// and finally writes the avatar item into InventoryJson. + /// We DO NOT touch InventoryJson here — that bypasses the gift + /// flow and the player never sees the popup. + /// + /// Also pushes a + /// over SignalR so an online player sees the toast immediately + /// without waiting for the next /gifts poll. + [HttpPost("players/{id:long}/gift")] + public async Task SendGift(long id, [FromBody] SendGiftRequest body) + { + var recipient = await db.Players.FirstOrDefaultAsync(p => p.Id == id); + if (recipient is null) return NotFound(); + + var hasItem = !string.IsNullOrWhiteSpace(body.AvatarItemGuid); + var hasCurrency = body.CurrencyType is int ct && ct > 0 && body.Currency is int c && c > 0; + var hasXp = body.Xp is int xp && xp > 0; + var hasLevel = body.Level is int lvl && lvl > 0; + if (!hasItem && !hasCurrency && !hasXp && !hasLevel) + return BadRequest(new { error = "empty_gift", message = "Provide at least one of: avatar item, currency, XP, level." }); + + // Wire shape MUST match the working /api/avatar/v2/gifts/generate + // flow exactly. The watch's ParseGiftPackageReceived + // (Avatars.txt:616) gates on IsValid + SupportsCurrentPlatform, + // then runs the gift through GiftManager. Two deviations from + // /generate were breaking us: + // 1. FromPlayerId set to the admin's id — the watch's P2P-gift + // rendering path looks up sender info; if that lookup + // doesn't return what the popup renderer expects, the toast + // still fires from the SignalR push but the box never + // materializes. /generate uses null; we match. + // 2. IsGifted = true tagged it as a player-to-player gift, + // which routes through a different code path entirely + // (separate inbox / different popup). /generate leaves it + // at the default false; we match. + // CRITICAL invariants for AvatarItemDescOrHairDyeDesc, both + // verified against the in-game crash trace: + // + // 1. AvatarItemType MUST be null when no avatar item is in the + // gift. Setting it to 0 (Outfit) with an empty desc routes + // GiftManager.DequeueGift past the HasAvatarItemOrHairDye + // gate into OutfitManager.MarkGiftPackageItemAvailableOnCurrentPlatform("") + // → AvatarItem.FromRecNetString("") → "empty RecNet string" + // AvatarParseException. + // + // 2. When there IS an item, AvatarItemDescOrHairDyeDesc must + // have at least 4 comma-separated parts. The client's + // FromRecNetString(string) splits on `,` then constructs + // ArraySegment(parts, 1, 3) — parts.Length < 4 triggers + // ArgumentException at ArraySegmentEnumerator.get_Current + // (output_log.txt:1567). Bare GUIDs crash; we suffix `,,,` + // for three empty default customisations. See + // AvatarItemsController.cs:31-40 for the canonical comment. + var gift = new GiftPackageEntity + { + RecipientPlayerId = id, + FromPlayerId = null, + AvatarItemType = hasItem ? (body.AvatarItemType ?? 0) : (int?)null, + AvatarItemDescOrHairDyeDesc = hasItem + ? NormalizeAvatarItemDesc(body.AvatarItemGuid!.Trim()) + : string.Empty, + CurrencyType = hasCurrency ? body.CurrencyType!.Value : 0, + Currency = hasCurrency ? body.Currency!.Value : 0, + Xp = hasXp ? body.Xp!.Value : 0, + Level = hasLevel ? body.Level!.Value : 0, + GiftContext = 0, + GiftRarity = body.Rarity ?? 0, + Message = string.IsNullOrWhiteSpace(body.Message) + ? "A gift from the admins." + : body.Message.Trim(), + Platform = -1, + PackageVariant = "Standard", + PackageMaterial = string.Empty, + IsValid = true, + SupportsCurrentPlatform = true, + }; + db.GiftPackages.Add(gift); + await LogAsync("send_gift", "player", id, + $"item={(hasItem ? gift.AvatarItemDescOrHairDyeDesc : "-")} " + + $"currency={(hasCurrency ? $"{gift.CurrencyType}×{gift.Currency}" : "-")} " + + $"xp={gift.Xp} level={gift.Level}"); + await db.SaveChangesAsync(); + + // GiftPackageReceivedImmediate (31), NOT GiftPackageReceived (30). + // GiftManager.OnGiftPackageReceivedEvent (GiftManager.txt:501) takes + // a `showImmediately` bool — true for id 31, false for id 30. When + // false the handler returns *without* calling ReceiveGifts, so no + // box pops. Admin gifts want the box now, not "next time you open + // the gift inbox", so push 31. The watch's RecNet.Avatars handler + // registers BOTH ids (Avatars.txt:177 + 192), and 31 is the only + // one that ends up invoking the GiftManager popup path. + await notifications.NotifyAsync(id, + PushNotificationId.GiftPackageReceivedImmediate, + new + { + gift.Id, + gift.FromPlayerId, + gift.AvatarItemType, + AvatarItemDesc = gift.AvatarItemDescOrHairDyeDesc, + gift.AvatarItemDescOrHairDyeDesc, + gift.EquipmentPrefabName, + gift.EquipmentModificationGuid, + gift.CurrencyType, + gift.Currency, + gift.Xp, + gift.Level, + gift.GiftContext, + gift.GiftRarity, + gift.Message, + gift.Platform, + gift.PackageMaterial, + gift.PackageVariant, + gift.Consumed, + gift.IsValid, + gift.ErrorMessage, + gift.SupportsCurrentPlatform, + gift.IsGifted, + }); + + return Ok(new { gift.Id, gift.RecipientPlayerId }); + } + + /// Pad a bare avatar item GUID with three empty default + /// customisations so the watch's AvatarItem.FromRecNetString + /// doesn't crash on ArraySegment(parts, 1, 3) when the + /// string only contains the GUID. Idempotent — descs that already + /// have at least 4 comma-separated parts are returned untouched. + private static string NormalizeAvatarItemDesc(string descOrGuid) + { + if (string.IsNullOrWhiteSpace(descOrGuid)) return string.Empty; + var commaCount = 0; + foreach (var ch in descOrGuid) if (ch == ',') commaCount++; + return commaCount >= 3 ? descOrGuid : descOrGuid + new string(',', 3 - commaCount); + } + + // ── Pending gift inbox (clean-up) ──────────────────────────────────── + + /// GET api/admin/v1/players/{id}/gifts — list the + /// recipient's pending (unconsumed) gift packages. Used by the admin + /// Gift page to surface broken / abandoned gifts that the watch + /// can't render so we can delete them. Includes consumed ones too + /// in case we want to audit what's been opened. + [HttpGet("players/{id:long}/gifts")] + public async Task ListPlayerGifts(long id, [FromQuery] bool includeConsumed = false) + { + IQueryable q = db.GiftPackages.Where(g => g.RecipientPlayerId == id); + if (!includeConsumed) q = q.Where(g => !g.Consumed); + var rows = await q + .OrderByDescending(g => g.CreatedAt) + .Take(200) + .Select(g => new + { + g.Id, + g.RecipientPlayerId, + g.FromPlayerId, + g.AvatarItemType, + g.AvatarItemDescOrHairDyeDesc, + g.CurrencyType, + g.Currency, + g.Xp, + g.Level, + g.GiftContext, + g.GiftRarity, + g.Message, + g.Consumed, + g.ConsumedAt, + g.IsValid, + g.IsGifted, + g.CreatedAt, + }) + .ToListAsync(); + return Ok(rows); + } + + /// DELETE api/admin/v1/gifts/{id} — hard-delete a + /// gift package row. Use when a gift is broken (wrong shape, won't + /// render its box in the watch) or was a mis-fire from a test send. + /// Does not affect the player's actual inventory if they'd already + /// consumed it — that write happened during consume and is final. + [HttpDelete("gifts/{id:long}")] + public async Task DeleteGift(long id) + { + var gift = await db.GiftPackages.FirstOrDefaultAsync(g => g.Id == id); + if (gift is null) return NotFound(); + var recipient = gift.RecipientPlayerId; + db.GiftPackages.Remove(gift); + await LogAsync("delete_gift", "gift", id, $"recipient={recipient} consumed={gift.Consumed}"); + await db.SaveChangesAsync(); + return Ok(new { deleted = id }); + } + + /// POST api/admin/v1/players/{id}/gifts/clear — + /// nuke every unconsumed gift for a player. Useful after a burst of + /// broken admin-test gifts to reset the inbox to a clean state. + /// Consumed gifts are left alone (they're history, not state). + [HttpPost("players/{id:long}/gifts/clear")] + public async Task ClearPlayerGifts(long id) + { + var pending = await db.GiftPackages + .Where(g => g.RecipientPlayerId == id && !g.Consumed) + .ToListAsync(); + db.GiftPackages.RemoveRange(pending); + await LogAsync("clear_gifts", "player", id, $"removed={pending.Count}"); + await db.SaveChangesAsync(); + return Ok(new { removed = pending.Count }); + } + + // ── Password reset (admin override) ────────────────────────────────── + + public sealed record SetPasswordRequest(string NewPassword); + + /// Admin-set a new password for any account. Hashes with + /// BCrypt and persists onto . + /// The audit row records the action but never the plaintext. + /// Refuses passwords shorter than 8 chars — same floor as the + /// self-service flow. + [HttpPost("players/{id:long}/password")] + public async Task SetPassword(long id, [FromBody] SetPasswordRequest body) + { + if (string.IsNullOrWhiteSpace(body.NewPassword) || body.NewPassword.Length < 8) + return BadRequest(new { error = "password_too_short", min = 8 }); + var player = await db.Players.FirstOrDefaultAsync(p => p.Id == id); + if (player is null) return NotFound(); + player.PasswordHash = BCrypt.Net.BCrypt.HashPassword(body.NewPassword); + await LogAsync("set_password", "player", id, ""); + await db.SaveChangesAsync(); + // Push a logout so any active sessions on the old password get + // dropped — the watch re-authenticates with the new password. + await notifications.NotifyAsync(id, PushNotificationId.Logout, + new { Reason = "AdminPasswordReset" }); + return Ok(new { id }); + } + + // ── Live instance viewer ───────────────────────────────────────────── + + /// GET api/admin/v1/instances — list every active room + /// instance an online player is currently sitting in. Joins the live + /// online-player set from against + /// (which holds the last-known + /// per player, TTL'd to 45 seconds) and + /// groups by RoomInstanceId. Empty list when no one is online. + [HttpGet("instances")] + public async Task ListInstances() + { + var onlineIds = onlinePresence.OnlinePlayerIds().ToHashSet(); + if (onlineIds.Count == 0) return Ok(Array.Empty()); + + // Bulk-pull names so we can render usernames per participant. + var nameMap = await db.Players + .Where(p => onlineIds.Contains(p.Id)) + .Select(p => new { p.Id, p.Username, p.DisplayName }) + .ToDictionaryAsync(p => p.Id, p => new { p.Username, p.DisplayName }); + + var roomNameMap = await db.Rooms + .Select(r => new { r.Id, r.Name }) + .ToDictionaryAsync(r => r.Id, r => r.Name); + + var grouped = new Dictionary(); + foreach (var pid in onlineIds) + { + var room = playerPresence.GetRoom(pid); + if (room is null) continue; + if (!grouped.TryGetValue(room.RoomInstanceId, out var g)) + { + g = new InstanceGroup + { + RoomInstanceId = room.RoomInstanceId, + RoomId = room.RoomId, + SubRoomId = room.SubRoomId, + RoomName = roomNameMap.GetValueOrDefault(room.RoomId, $"#{room.RoomId}"), + PhotonRoomId = room.PhotonRoomId, + PhotonRegionId = room.PhotonRegionId, + Location = room.Location, + MaxCapacity = room.MaxCapacity, + IsPrivate = room.IsPrivate, + Participants = new(), + }; + grouped[room.RoomInstanceId] = g; + } + nameMap.TryGetValue(pid, out var nm); + g.Participants.Add(new InstanceParticipant + { + Id = pid, + Username = nm?.Username ?? $"#{pid}", + DisplayName = nm?.DisplayName ?? $"#{pid}", + }); + } + + return Ok(grouped.Values + .OrderByDescending(g => g.Participants.Count) + .ThenBy(g => g.RoomName)); + } + + private sealed class InstanceGroup + { + public long RoomInstanceId { get; set; } + public long RoomId { get; set; } + public long SubRoomId { get; set; } + public string RoomName { get; set; } = ""; + public string PhotonRoomId { get; set; } = ""; + public string PhotonRegionId { get; set; } = ""; + public string Location { get; set; } = ""; + public int MaxCapacity { get; set; } + public bool IsPrivate { get; set; } + public List Participants { get; set; } = new(); + } + + private sealed class InstanceParticipant + { + public long Id { get; set; } + public string Username { get; set; } = ""; + public string DisplayName { get; set; } = ""; + } + + // ── Force-join (experimental) ──────────────────────────────────────── + + /// + /// Force-join body. RoomInstanceId + PhotonRoomId are + /// optional — when omitted, the watch lands in a fresh matchmade + /// instance of the room. When provided (from the Instances browser + /// → "send into this match" flow), we target that exact Photon room + /// so the admin can drop a player into a specific public or private + /// instance other players are already in. + /// + public sealed record ForceJoinRequest( + long RoomId, + long? SubRoomId, + long? RoomInstanceId, + string? PhotonRoomId, + string? PhotonRegionId); + + /// POST api/admin/v1/players/{id}/forcejoin — rewrite + /// the target player's entry to + /// the destination room (and optionally a specific Photon instance), + /// then push SubscriptionUpdateGameSession + SubscriptionUpdateRoom + /// so the watch's cached RoomInstance + RoomDetails match the new + /// server-side state. The watch's + /// OnPresenceHeartbeatResponse detects the change on its + /// next heartbeat and refreshes its cache; pair with + /// POST players/{id}/kick to also drop the player out of + /// their current Photon match so they re-matchmake into the new + /// target on reconnect. + /// + /// When RoomInstanceId + PhotonRoomId are supplied, + /// the player lands in that EXACT instance (public or private). Use + /// the Instances browser to enumerate available Photon rooms and + /// pick one. With both fields omitted, a fresh deterministic + /// instance id is used so the player matchmakes normally. + [HttpPost("players/{id:long}/forcejoin")] + public async Task ForceJoin(long id, [FromBody] ForceJoinRequest body) + { + var player = await db.Players.FirstOrDefaultAsync(p => p.Id == id); + if (player is null) return NotFound(); + var room = await db.Rooms.FirstOrDefaultAsync(r => r.Id == body.RoomId); + if (room is null) return NotFound(new { error = "room_not_found" }); + + // Build the destination RoomInstance. When the admin picked a + // specific instance from the Instances browser, RoomInstanceId + // + PhotonRoomId arrive together — use them verbatim so the + // player lands in THAT match, not a fresh one. Otherwise mint + // deterministic defaults that match what /goto/room would + // produce for a first join (roomId*100k + subId base; bare + // ^roomname_id photon room name). + var subRoomId = body.SubRoomId ?? 0; + var photonRoomId = !string.IsNullOrEmpty(body.PhotonRoomId) + ? body.PhotonRoomId + : $"^{room.Name.ToLowerInvariant()}_{room.Id}"; + var roomInstanceId = body.RoomInstanceId + ?? (room.Id * 100_000L + subRoomId); + var photonRegionId = !string.IsNullOrEmpty(body.PhotonRegionId) + ? body.PhotonRegionId + : "us"; + + var roomInstance = new DorkNet.Server.Controllers.Match.RoomInstanceDto + { + RoomInstanceId = roomInstanceId, + RoomId = room.Id, + SubRoomId = subRoomId, + Location = room.LocationReplicationId, + PhotonRegionId = photonRegionId, + PhotonRoomId = photonRoomId, + Name = room.Name, + MaxCapacity = 8, + IsFull = false, + IsPrivate = false, + IsInProgress = true, + }; + + playerPresence.SetRoom(id, roomInstance); + + // Two pushes back-to-back: GameSession update changes the + // watch's cached RoomInstance (OnSubscriptionUpdateRoomInstance + // → UpdateCachedRoomInstance), and SubscriptionUpdateRoom + // refreshes the watch's RoomDetails cache so the room-info + // panel doesn't show stale data after the warp. + await notifications.NotifyAsync(id, + PushNotificationId.SubscriptionUpdateGameSession, roomInstance); + await notifications.NotifyAsync(id, + PushNotificationId.SubscriptionUpdateRoom, + new + { + Room = new { Id = room.Id, Name = room.Name }, + RoomId = room.Id, + SubRoomId = subRoomId, + Reason = "AdminForceJoin", + }); + + await LogAsync("force_join", "player", id, + $"room={body.RoomId} sub={subRoomId} instance={roomInstanceId} photon={photonRoomId}"); + await db.SaveChangesAsync(); + return Ok(new + { + id, + roomId = body.RoomId, + subRoomId, + roomInstanceId, + photonRoomId, + }); + } + + // ── System ─────────────────────────────────────────────────────────── + + public sealed record SetMotdRequest(string Message); + + /// Push a server-maintenance broadcast to every connected + /// player. The MOTD config value is changed via appsettings.json + /// (server restart required); this is for ad-hoc announcements. + [HttpPost("broadcast")] + public async Task Broadcast([FromBody] SetMotdRequest body) + { + if (string.IsNullOrWhiteSpace(body.Message)) return BadRequest("empty message"); + await notifications.BroadcastAsync(PushNotificationId.ServerMaintenance, + new { Message = body.Message }); + await LogAsync("broadcast", "system", 0, body.Message); + await db.SaveChangesAsync(); + return Ok(); + } + + // ── Store catalog management ───────────────────────────────────────── + + /// Canonical storefront keys the admin UI can assign to + /// store items. Exact giftdrop:N keys target a single + /// in-room shelf; rro and all are shared groups. + [HttpGet("storefronts")] + public ActionResult ListStorefronts() => + Ok(StoreService.GetStorefrontDefinitions().Select(s => new + { + s.Key, + s.StorefrontType, + s.DisplayName, + s.Scope, + })); + + /// List every item in the catalog (including inactive + /// rows) so the admin UI can render the full catalog and let an + /// admin flip flags / edit prices. + [HttpGet("storeitems")] + public async Task ListStoreItems( + [FromQuery] string? storefront, [FromQuery] string? category, + [FromQuery] int take = 200, [FromQuery] int skip = 0) + { + take = Math.Clamp(take, 1, 500); + skip = Math.Max(0, skip); + IQueryable q = db.StoreItems; + if (!string.IsNullOrWhiteSpace(storefront)) q = q.Where(i => i.Storefront == storefront); + if (!string.IsNullOrWhiteSpace(category)) q = q.Where(i => i.Category == category); + var rows = await q.OrderBy(i => i.Storefront).ThenBy(i => i.Category).ThenBy(i => i.Price) + .Skip(skip).Take(take).ToListAsync(); + return Ok(rows); + } + + public sealed record StoreItemUpsertRequest( + string? Slug, string? DisplayName, string? Description, + string? Category, string? ImageName, int? CurrencyType, + long? Price, bool? IsActive, bool? IsLimitedTime, + DateTime? AvailableUntil, string? Storefront); + + /// POST api/admin/v1/storeitems/image/upload — + /// attachable catalog artwork. Stored as a global image blob so the + /// admin UI and the watch can resolve the same ImageName through + /// img.* / cdn.* without admins hand-copying filenames. + [HttpPost("storeitems/image/upload")] + [DisableRequestSizeLimit] + [RequestFormLimits(MultipartBodyLengthLimit = 25_000_000)] + public async Task UploadStoreItemImage([FromForm(Name = "file")] IFormFile? file) + { + if (file is null || file.Length == 0) + return BadRequest(new { error = "missing_file" }); + var ext = Path.GetExtension(file.FileName).ToLowerInvariant(); + if (ext is not (".jpg" or ".jpeg" or ".png" or ".webp")) + return BadRequest(new { error = "unsupported_extension", allowed = new[] { ".jpg", ".jpeg", ".png", ".webp" } }); + + using var ms = new MemoryStream(); + await file.CopyToAsync(ms); + var bytes = ms.ToArray(); + using var sha = System.Security.Cryptography.SHA1.Create(); + var hash = Convert.ToHexString(sha.ComputeHash(bytes)).ToLowerInvariant(); + var imageName = $"store_{hash[..24]}{ext}"; + + var existing = await db.RoomDataBlobs.FirstOrDefaultAsync(b => b.BlobName == imageName); + if (existing is null) + { + var (bucket, key) = BlobRouter.Route(imageName); + var contentType = ext switch + { + ".png" => "image/png", + ".webp" => "image/webp", + _ => "image/jpeg", + }; + await storage.PutAsync(bucket, key, bytes, contentType); + db.RoomDataBlobs.Add(new RoomDataBlobEntity + { + RoomId = 0, + BlobName = imageName, + UploadedByPlayerId = CurrentAdminId, + UploadedAt = DateTime.UtcNow, + }); + await LogAsync("store_item_image_upload", "storeitem", 0, $"image={imageName} bytes={bytes.Length}"); + await db.SaveChangesAsync(); + } + + return Ok(new + { + imageName, + imageUrl = $"https://{domain.Sub("img")}/{imageName}", + bytes = bytes.Length, + reused = existing is not null, + }); + } + + /// Create a new catalog item. + [HttpPost("storeitems")] + public async Task CreateStoreItem([FromBody] StoreItemUpsertRequest body) + { + if (string.IsNullOrWhiteSpace(body.Slug)) return BadRequest("missing_slug"); + if (string.IsNullOrWhiteSpace(body.DisplayName)) return BadRequest("missing_display_name"); + var existing = await db.StoreItems.AnyAsync(i => i.Slug == body.Slug); + if (existing) return Conflict(new { error = "slug_taken" }); + var item = new StoreItemEntity + { + Slug = body.Slug.Trim(), + DisplayName = body.DisplayName.Trim(), + Description = (body.Description ?? string.Empty).Trim(), + Category = (body.Category ?? "accessory").Trim().ToLowerInvariant(), + ImageName = body.ImageName ?? string.Empty, + CurrencyType = body.CurrencyType ?? 2, + Price = Math.Max(0, body.Price ?? 0), + IsActive = body.IsActive ?? true, + IsLimitedTime = body.IsLimitedTime ?? false, + AvailableUntil = body.AvailableUntil, + Storefront = (body.Storefront ?? "main").Trim(), + }; + db.StoreItems.Add(item); + await LogAsync("create_store_item", "storeitem", 0, item.Slug); + await db.SaveChangesAsync(); + return Ok(item); + } + + /// Patch an existing catalog item — any null field is + /// left untouched so the admin UI can do partial edits. + [HttpPost("storeitems/{id:long}")] + [HttpPut("storeitems/{id:long}")] + public async Task UpdateStoreItem(long id, [FromBody] StoreItemUpsertRequest body) + { + var item = await db.StoreItems.FirstOrDefaultAsync(i => i.Id == id); + if (item is null) return NotFound(); + if (body.DisplayName is not null) item.DisplayName = body.DisplayName.Trim(); + if (body.Description is not null) item.Description = body.Description.Trim(); + if (body.Category is not null) item.Category = body.Category.Trim().ToLowerInvariant(); + if (body.ImageName is not null) item.ImageName = body.ImageName; + if (body.CurrencyType is int ct) item.CurrencyType = ct; + if (body.Price is long p) item.Price = Math.Max(0, p); + if (body.IsActive is bool a) item.IsActive = a; + if (body.IsLimitedTime is bool lt) item.IsLimitedTime = lt; + if (body.AvailableUntil.HasValue) item.AvailableUntil = body.AvailableUntil; + if (body.Storefront is not null) item.Storefront = body.Storefront.Trim(); + item.UpdatedAt = DateTime.UtcNow; + await LogAsync("update_store_item", "storeitem", id, item.Slug); + await db.SaveChangesAsync(); + return Ok(item); + } + + /// Delete a catalog item. Soft-flag is recommended via + /// IsActive=false; hard delete is here for cleanup of test + /// items. + [HttpDelete("storeitems/{id:long}")] + public async Task DeleteStoreItem(long id) + { + var item = await db.StoreItems.FirstOrDefaultAsync(i => i.Id == id); + if (item is null) return NotFound(); + db.StoreItems.Remove(item); + await LogAsync("delete_store_item", "storeitem", id, item.Slug); + await db.SaveChangesAsync(); + return Ok(new { deleted = id }); + } + + // ── Helpers ────────────────────────────────────────────────────────── + + private Task LogAsync(string action, string targetType, long targetId, string reason) + { + db.AdminActions.Add(new AdminActionEntity + { + AdminPlayerId = CurrentAdminId, + Action = action, + TargetType = targetType, + TargetId = targetId, + Reason = reason, + }); + return Task.CompletedTask; + } + + // ── Community Board ────────────────────────────────────────────── + // Backs the dorm community board panel. The watch hits + // /api/communityboard/v1/current; admins edit here. + + /// GET the current board state. Same shape the watch + /// receives — handy for the admin UI to populate its form. + [HttpGet("communityboard")] + public async Task> GetCommunityBoard() + => Ok(await communityBoard.GetAsync()); + + /// POST a full replacement state. Any null nested objects + /// (FeaturedPlayer / FeaturedRoomGroup / CurrentAnnouncement) + /// clear that section. Lists default to empty if omitted. + [HttpPost("communityboard")] + public async Task> SetCommunityBoard( + [FromBody] CommunityBoardState body) + { + body.InstagramImages ??= new(); + body.Videos ??= new(); + var saved = await communityBoard.UpdateAsync(body); + await LogAsync("set_community_board", "global", 0, + $"announcement=\"{saved.CurrentAnnouncement?.Message ?? ""}\" " + + $"featuredPlayer={saved.FeaturedPlayer?.Id} " + + $"videos={saved.Videos.Count} instagram={saved.InstagramImages.Count}"); + return Ok(saved); + } + + /// POST api/admin/v1/communityboard/instagram/upload + /// — accept a single image file, persist it as a global + /// at RoomId=0 under a + /// generated hash filename, return the filename + a ready-to-paste + /// img.* URL. The Community Board SPA section pipes a file picker + /// into this so admins don't have to know what an + /// ImageName is — they just drop a JPG/PNG and we wire it up. + /// + /// Cap: 25 MB. Anything larger is almost certainly a mistake for an + /// Instagram-strip tile and would also push us close to the CF edge + /// limit on a non-chunked upload. + [HttpPost("communityboard/instagram/upload")] + [DisableRequestSizeLimit] + [RequestFormLimits(MultipartBodyLengthLimit = 25_000_000)] + public async Task UploadInstagramImage([FromForm(Name = "file")] IFormFile? file) + { + if (file is null || file.Length == 0) + return BadRequest(new { error = "missing_file" }); + var ext = Path.GetExtension(file.FileName).ToLowerInvariant(); + if (ext is not (".jpg" or ".jpeg" or ".png" or ".webp")) + return BadRequest(new { error = "unsupported_extension", allowed = new[] { ".jpg", ".jpeg", ".png", ".webp" } }); + + using var ms = new MemoryStream(); + await file.CopyToAsync(ms); + var bytes = ms.ToArray(); + + // Hashed filename so re-uploads of the same content don't + // proliferate and so the URL is content-addressed. Prefix + // `cb_insta_` to keep these distinguishable from room-image + // blobs at a glance when grepping the table. + using var sha = System.Security.Cryptography.SHA1.Create(); + var hash = Convert.ToHexString(sha.ComputeHash(bytes)).ToLowerInvariant(); + var imageName = $"cb_insta_{hash[..24]}{ext}"; + + var existing = await db.RoomDataBlobs.FirstOrDefaultAsync(b => b.BlobName == imageName); + if (existing is null) + { + var (bucket, key) = BlobRouter.Route(imageName); + await storage.PutAsync(bucket, key, bytes, + ext == ".png" ? "image/png" : "image/jpeg"); + db.RoomDataBlobs.Add(new RoomDataBlobEntity + { + RoomId = 0, + BlobName = imageName, + UploadedByPlayerId = CurrentAdminId, + UploadedAt = DateTime.UtcNow, + }); + await LogAsync("cb_insta_upload", "community_board", 0, $"image={imageName} bytes={bytes.Length}"); + await db.SaveChangesAsync(); + } + + // Use the configured apex (DomainConfig) so the returned URL + // works whether the admin is on localhost, rec.net, or a future + // deployment apex — driven solely by the DORKNET_DOMAIN env var. + return Ok(new + { + imageName, + imageUrl = $"https://{domain.Sub("img")}/{imageName}", + bytes = bytes.Length, + reused = existing is not null, + }); + } + + /// POST api/admin/v1/communityboard/video/upload — + /// accept a single video file and persist it as a global + /// at RoomId=0 under a hashed + /// cb_video_* name. The board's Videos section binds the + /// returned blobName straight into VideoState.BlobName + /// so the watch's CommunityBoardVideoData resolves it to + /// cdn.{apex}/<blobName>. + /// + /// Cap: 100 MB — picks the same ceiling Cloudflare enforces at the + /// edge so a non-chunked POST never sees a 413. Anything bigger + /// should be hosted externally and pasted into SourceUrl. + // [RequestSizeLimit] alone is enough — pairing it with + // [DisableRequestSizeLimit] was undefined-behaviour territory + // (Microsoft docs explicitly say "don't combine them"). Setting + // 100 MB at the action keeps Kestrel from clipping the body at its + // 30 MB default while still capping uploads under Cloudflare's + // free-tier edge limit. + [HttpPost("communityboard/video/upload")] + [RequestFormLimits(MultipartBodyLengthLimit = 100_000_000, ValueLengthLimit = 100_000_000)] + [RequestSizeLimit(100_000_000)] + public async Task UploadCommunityBoardVideo([FromForm(Name = "file")] IFormFile? file) + { + if (file is null || file.Length == 0) + return BadRequest(new { error = "missing_file" }); + var ext = Path.GetExtension(file.FileName).ToLowerInvariant(); + if (ext is not (".mp4" or ".webm" or ".mov" or ".m4v")) + return BadRequest(new { + error = "unsupported_extension", + allowed = new[] { ".mp4", ".webm", ".mov", ".m4v" }, + }); + + var declaredSize = file.Length; + using var ms = new MemoryStream(); + await file.CopyToAsync(ms); + var bytes = ms.ToArray(); + + // Catch the "Kestrel/Cloudflare clipped the body" case BEFORE + // we hash + write — IFormFile.Length reports what the multipart + // parser saw in the part header, so a mismatch with how many + // bytes the stream actually produced is a transport-level + // truncation and the resulting blob would be the corrupted file + // the user saw on download. + if (bytes.LongLength != declaredSize) + { + adminLogger.LogError( + "[cb-video-upload] size mismatch — multipart says {Declared:N0} bytes, stream produced {Read:N0} bytes (file={Name})", + declaredSize, bytes.LongLength, file.FileName); + return BadRequest(new { + error = "upload_truncated", + declaredBytes = declaredSize, + receivedBytes = bytes.LongLength, + }); + } + + // Content-addressed name keeps re-uploads from proliferating — + // identical bytes resolve to the same row + S3 object regardless + // of original filename. `cb_video_` prefix keeps it greppable. + using var sha = System.Security.Cryptography.SHA1.Create(); + var hash = Convert.ToHexString(sha.ComputeHash(bytes)).ToLowerInvariant(); + var blobName = $"cb_video_{hash[..24]}{ext}"; + var (bucket, key) = BlobRouter.Route(blobName); + + // We were previously skipping S3 PUT when the DB row already + // existed. That created a zombie state: if the first upload's + // PUT silently failed (Garage outage, bad creds) the row was + // committed but the bytes weren't, and every subsequent + // upload of the same content returned `reused: true` without + // ever fixing the missing object. Verify S3 has the object; + // re-PUT if it doesn't. + var existing = await db.RoomDataBlobs.FirstOrDefaultAsync(b => b.BlobName == blobName); + var alreadyOnS3 = existing is not null && await storage.ExistsAsync(bucket, key); + + if (!alreadyOnS3) + { + await storage.PutAsync(bucket, key, bytes, MimeFromExt(ext)); + // Post-PUT HEAD: Garage occasionally accepts a PutObject + // and returns 200 without persisting (auth misconfig, + // bucket policy reject). HeadObject is the cheapest way to + // confirm the bytes are actually queryable before we + // pretend the upload succeeded. + if (!await storage.ExistsAsync(bucket, key)) + { + adminLogger.LogError( + "[cb-video-upload] PUT returned OK but HEAD says {Bucket}/{Key} missing — Garage may be silently dropping", + bucket, key); + return StatusCode(500, new { error = "s3_put_lost", bucket, key }); + } + adminLogger.LogInformation( + "[cb-video-upload] stored {Bucket}/{Key} ({Bytes:N0} bytes, ext={Ext})", + bucket, key, bytes.LongLength, ext); + } + if (existing is null) + { + db.RoomDataBlobs.Add(new RoomDataBlobEntity + { + RoomId = 0, + BlobName = blobName, + UploadedByPlayerId = CurrentAdminId, + UploadedAt = DateTime.UtcNow, + }); + await LogAsync("cb_video_upload", "community_board", 0, $"video={blobName} bytes={bytes.Length}"); + await db.SaveChangesAsync(); + } + + return Ok(new + { + blobName, + videoUrl = $"https://{domain.Sub("cdn")}/video/{blobName}", + bytes = bytes.Length, + reused = existing is not null && alreadyOnS3, + repaired = existing is not null && !alreadyOnS3, + }); + } + + /// POST api/admin/v1/communityboard/videothumb/upload + /// — accept a single image and persist it under a hashed + /// cb_thumb_* name for use as 's + /// ThumbnailBlobName. Mechanically identical to the Instagram + /// upload endpoint, just with a different prefix so thumbnail vs. + /// gallery images are distinguishable at a glance in the blob table. + /// + /// Cap: 25 MB. The watch renders thumbnails at small sizes; bigger + /// is wasted bandwidth. + [HttpPost("communityboard/videothumb/upload")] + [DisableRequestSizeLimit] + [RequestFormLimits(MultipartBodyLengthLimit = 25_000_000)] + public async Task UploadCommunityBoardVideoThumbnail( + [FromForm(Name = "file")] IFormFile? file) + { + if (file is null || file.Length == 0) + return BadRequest(new { error = "missing_file" }); + var ext = Path.GetExtension(file.FileName).ToLowerInvariant(); + if (ext is not (".jpg" or ".jpeg" or ".png" or ".webp")) + return BadRequest(new { + error = "unsupported_extension", + allowed = new[] { ".jpg", ".jpeg", ".png", ".webp" }, + }); + + using var ms = new MemoryStream(); + await file.CopyToAsync(ms); + var bytes = ms.ToArray(); + + using var sha = System.Security.Cryptography.SHA1.Create(); + var hash = Convert.ToHexString(sha.ComputeHash(bytes)).ToLowerInvariant(); + var blobName = $"cb_thumb_{hash[..24]}{ext}"; + + var existing = await db.RoomDataBlobs.FirstOrDefaultAsync(b => b.BlobName == blobName); + if (existing is null) + { + var (bucket, key) = BlobRouter.Route(blobName); + await storage.PutAsync(bucket, key, bytes, + ext == ".png" ? "image/png" : ext == ".webp" ? "image/webp" : "image/jpeg"); + db.RoomDataBlobs.Add(new RoomDataBlobEntity + { + RoomId = 0, + BlobName = blobName, + UploadedByPlayerId = CurrentAdminId, + UploadedAt = DateTime.UtcNow, + }); + await LogAsync("cb_videothumb_upload", "community_board", 0, $"thumb={blobName} bytes={bytes.Length}"); + await db.SaveChangesAsync(); + } + + return Ok(new + { + blobName, + imageUrl = $"https://{domain.Sub("img")}/{blobName}", + bytes = bytes.Length, + reused = existing is not null, + }); + } + + // MIME mapping for the video upload — the CDN serves whatever + // Content-Type we wrote to S3, so getting this right makes the + // watch's HTTPVideoStreamPlayer accept the response without + // sniffing. Default to mp4 since that's what most modern exporters + // produce; unknown extensions fall back to octet-stream which the + // watch usually still handles but is worth flagging. + private static string MimeFromExt(string ext) => ext switch + { + ".mp4" => "video/mp4", + ".m4v" => "video/mp4", + ".webm" => "video/webm", + ".mov" => "video/quicktime", + _ => "application/octet-stream", + }; + + // ── Loading screen tips ────────────────────────────────────────────── + + public sealed class LoadingScreenTipDto + { + public long? Id { get; set; } + public string Title { get; set; } = string.Empty; + public string Message { get; set; } = string.Empty; + public string ImageName { get; set; } = string.Empty; + public int Context { get; set; } + public int PlatformMask { get; set; } = -1; + public string RoomNamesCsv { get; set; } = string.Empty; + public int SortOrder { get; set; } + public bool IsActive { get; set; } = true; + } + + /// GET api/admin/v1/loadingscreentips — list every + /// tip (including inactive ones) for the admin SPA. + [HttpGet("loadingscreentips")] + public async Task ListLoadingScreenTips() + { + var rows = await db.LoadingScreenTips + .OrderBy(t => t.SortOrder).ThenBy(t => t.Id) + .AsNoTracking() + .ToListAsync(); + return Ok(rows.Select(ToLoadingScreenTipDto)); + } + + /// POST api/admin/v1/loadingscreentips — create + /// a new tip. Returns the persisted row including its assigned + /// Id so the SPA can update its local state. + [HttpPost("loadingscreentips")] + public async Task CreateLoadingScreenTip([FromBody] LoadingScreenTipDto body) + { + var entity = new Data.Entities.LoadingScreenTipEntity + { + Title = (body.Title ?? string.Empty).Trim(), + Message = (body.Message ?? string.Empty).Trim(), + ImageName = (body.ImageName ?? string.Empty).Trim(), + Context = body.Context, + PlatformMask = body.PlatformMask, + RoomNamesCsv = NormaliseCsv(body.RoomNamesCsv), + SortOrder = body.SortOrder, + IsActive = body.IsActive, + }; + db.LoadingScreenTips.Add(entity); + await db.SaveChangesAsync(); + await LogAsync("create_loading_tip", "global", entity.Id, entity.Title); + return Ok(ToLoadingScreenTipDto(entity)); + } + + /// PUT api/admin/v1/loadingscreentips/{id} — update + /// an existing tip. + [HttpPut("loadingscreentips/{id:long}")] + public async Task UpdateLoadingScreenTip(long id, [FromBody] LoadingScreenTipDto body) + { + var row = await db.LoadingScreenTips.FirstOrDefaultAsync(t => t.Id == id); + if (row is null) return NotFound(); + row.Title = (body.Title ?? string.Empty).Trim(); + row.Message = (body.Message ?? string.Empty).Trim(); + row.ImageName = (body.ImageName ?? string.Empty).Trim(); + row.Context = body.Context; + row.PlatformMask = body.PlatformMask; + row.RoomNamesCsv = NormaliseCsv(body.RoomNamesCsv); + row.SortOrder = body.SortOrder; + row.IsActive = body.IsActive; + row.UpdatedAt = DateTime.UtcNow; + await db.SaveChangesAsync(); + await LogAsync("update_loading_tip", "global", id, row.Title); + return Ok(ToLoadingScreenTipDto(row)); + } + + /// DELETE api/admin/v1/loadingscreentips/{id} — hard + /// delete a tip. (Toggling IsActive=false via the update + /// endpoint is the soft-delete path.) + [HttpDelete("loadingscreentips/{id:long}")] + public async Task DeleteLoadingScreenTip(long id) + { + var row = await db.LoadingScreenTips.FirstOrDefaultAsync(t => t.Id == id); + if (row is null) return NotFound(); + db.LoadingScreenTips.Remove(row); + await db.SaveChangesAsync(); + await LogAsync("delete_loading_tip", "global", id, row.Title); + return Ok(new { deleted = id }); + } + + /// POST api/admin/v1/loadingscreentips/upload — + /// accept an image file (jpg/png/webp), persist it via the same + /// content-addressed flow as the Community Board uploader, and + /// return the BlobName + img.* URL. The SPA pipes this into the + /// tip's ImageName field. + [HttpPost("loadingscreentips/upload")] + [DisableRequestSizeLimit] + [RequestFormLimits(MultipartBodyLengthLimit = 25_000_000)] + public async Task UploadLoadingScreenTipImage([FromForm(Name = "file")] IFormFile? file) + { + if (file is null || file.Length == 0) + return BadRequest(new { error = "missing_file" }); + var ext = Path.GetExtension(file.FileName).ToLowerInvariant(); + if (ext is not (".jpg" or ".jpeg" or ".png" or ".webp")) + return BadRequest(new { error = "unsupported_extension", allowed = new[] { ".jpg", ".jpeg", ".png", ".webp" } }); + + using var ms = new MemoryStream(); + await file.CopyToAsync(ms); + var bytes = ms.ToArray(); + + using var sha = System.Security.Cryptography.SHA1.Create(); + var hash = Convert.ToHexString(sha.ComputeHash(bytes)).ToLowerInvariant(); + var imageName = $"loadingtip_{hash[..24]}{ext}"; + + var existing = await db.RoomDataBlobs.FirstOrDefaultAsync(b => b.BlobName == imageName); + if (existing is null) + { + var (bucket, key) = BlobRouter.Route(imageName); + await storage.PutAsync(bucket, key, bytes, + ext == ".png" ? "image/png" : "image/jpeg"); + db.RoomDataBlobs.Add(new RoomDataBlobEntity + { + RoomId = 0, + BlobName = imageName, + UploadedByPlayerId = CurrentAdminId, + UploadedAt = DateTime.UtcNow, + }); + await LogAsync("loading_tip_upload", "global", 0, $"image={imageName} bytes={bytes.Length}"); + await db.SaveChangesAsync(); + } + + return Ok(new + { + imageName, + imageUrl = $"https://{domain.Sub("img")}/{imageName}", + bytes = bytes.Length, + reused = existing is not null, + }); + } + + private static LoadingScreenTipDto ToLoadingScreenTipDto(Data.Entities.LoadingScreenTipEntity t) => new() + { + Id = t.Id, + Title = t.Title, + Message = t.Message, + ImageName = t.ImageName, + Context = t.Context, + PlatformMask = t.PlatformMask, + RoomNamesCsv = t.RoomNamesCsv, + SortOrder = t.SortOrder, + IsActive = t.IsActive, + }; + + private static string NormaliseCsv(string? csv) + { + if (string.IsNullOrWhiteSpace(csv)) return string.Empty; + var parts = csv.Split(',', StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries); + return string.Join(",", parts); + } + + // ── Room import ────────────────────────────────────────────────────── + + /// POST api/admin/v1/rooms/import — multipart upload + /// of an archived multi-scene room. Each .room file (one per scene) + /// is uploaded with its filename including the scene folder, e.g. + /// Lobby/abc.room or Ch1_Headfirst/def.room. The form + /// can either send the per-file folder via webkitRelativePath + /// (browser folder picker preserves it) or via the filename itself. + /// + /// Server inserts: + /// 1. A row for the new room (id 1000+). + /// 2. One per uploaded file, + /// named {roomname}_{scenefolder}.room (lowercased). + /// 3. One per scene with the entry + /// scene at OrderIndex=0 and the rest in folder order. + /// + /// Default RoomSceneLocationId for every scene is the + /// MakerRoom (Basement) GUID — the canonical 2020 custom-room + /// canvas. If the import was a 2020-era export of an apartment + /// scene, edit + /// per-row after import. + /// + /// Returns 400 if a room with this name already exists or no + /// files were uploaded. + [HttpPost("rooms/import")] + [DisableRequestSizeLimit] + [RequestFormLimits(MultipartBodyLengthLimit = 500_000_000)] + public async Task ImportRoom( + [FromForm(Name = "name")] string? name, + [FromForm(Name = "description")] string? description, + [FromForm(Name = "entryScene")] string? entryScene, + [FromForm(Name = "creatorPlayerId")] long? creatorPlayerId, + [FromForm(Name = "scenePaths")] List? scenePaths, + [FromForm(Name = "files")] List? files, + [FromForm(Name = "normalizeBlobs")] bool? normalizeBlobs) + { + // Per-import "run blob bytes through the 2020 protobuf normaliser" + // toggle, mirroring RoomZipImportController. Default OFF — see + // that file's notes for why the round-trip currently crashes the + // watch on load. Diagnostic Normalize() call still runs so its + // parse-OK / parse-FAIL log line remains in the import telemetry. + var normalize = normalizeBlobs ?? false; + if (string.IsNullOrWhiteSpace(name)) + return BadRequest(new { error = "name_required" }); + if (files is null || files.Count == 0) + return BadRequest(new { error = "no_files" }); + + var roomName = name.Trim(); + if (await db.Rooms.AnyAsync(r => r.Name == roomName)) + return BadRequest(new { error = "duplicate_name", message = $"A room named '{roomName}' already exists." }); + + // Pair each uploaded file with its scene folder name. Browsers + // submit folder uploads as files with FileName = "Lobby/blob.room" + // when webkitdirectory is used; fall back to a parallel + // scenePaths[] form field when the client supplies it explicitly + // (e.g. uploading individual files via JS FormData with the + // scene name set per entry). + // + // Each blob is also passed through RoomBlobNormalizerService + // (round-trip via the full 2020 PersistedRoomData schema). That + // strips redundant default-value encodings the modern client + // emits — without this, the watch's stricter 2020 parser logs + // "Error attempting to parse room data stream" and boots the + // player to dorm. Normalize() falls back to the original bytes + // if parse fails, so a corrupted upload doesn't drop content. + var perScene = new List<(string SceneName, string OriginalName, byte[] Bytes, RoomBlobNormalizerService.Result Norm)>(); + for (int i = 0; i < files.Count; i++) + { + var f = files[i]; + // FormFile.FileName is "scenefolder/blob.room" when uploaded + // via webkitdirectory. ScenePaths (if provided) overrides. + var sceneName = scenePaths is { } sp && i < sp.Count && !string.IsNullOrWhiteSpace(sp[i]) + ? sp[i].Trim() + : ExtractSceneFolder(f.FileName); + if (string.IsNullOrWhiteSpace(sceneName)) + { + return BadRequest(new { + error = "missing_scene_folder", + message = $"File '{f.FileName}' has no scene folder. Either upload via folder picker, or supply scenePaths[] alongside files[]." + }); + } + using var ms = new MemoryStream(); + await f.CopyToAsync(ms); + var raw = ms.ToArray(); + var norm = roomBlobNormalizer.Normalize(raw); + var persistedBytes = normalize ? norm.Bytes : raw; + adminLogger.LogInformation( + "[import] scene={Scene} file={File} raw={Raw:N0} → normalized={Out:N0} ({Status}) persistedFrom={Source}", + sceneName, f.FileName, raw.Length, norm.Bytes.Length, + norm.Normalized ? "ok" : $"fail: {norm.Error}", + normalize ? "normaliser" : "raw"); + perScene.Add((sceneName, f.FileName, persistedBytes, norm)); + } + + // Pick entry scene: explicit override → "Lobby" if present → + // alphabetically-first folder. + var sceneNames = perScene.Select(s => s.SceneName).ToList(); + var entry = !string.IsNullOrWhiteSpace(entryScene) + ? perScene.FirstOrDefault(s => string.Equals(s.SceneName, entryScene, StringComparison.OrdinalIgnoreCase)) + : default; + if (entry == default) + { + entry = perScene.FirstOrDefault(s => string.Equals(s.SceneName, "Lobby", StringComparison.OrdinalIgnoreCase)); + } + if (entry == default) entry = perScene.OrderBy(s => s.SceneName).First(); + + // Allocate a new id in the user-room range (>=1000). + var newRoomId = (await db.Rooms + .Where(r => r.Id >= 1000) + .Select(r => (long?)r.Id) + .MaxAsync() ?? 999) + 1; + var creator = creatorPlayerId ?? CurrentAdminId; + const string makerRoomLocation = "a75f7547-79eb-47c6-8986-6767abcb4f92"; + + // Build BlobName per scene. + var slug = roomName.ToLowerInvariant().Replace(' ', '_'); + string MakeBlobName(string sceneName) => $"{slug}_{sceneName.ToLowerInvariant()}.room"; + + // Reorder so entry scene is OrderIndex=0. + var ordered = new List<(string SceneName, string OriginalName, byte[] Bytes, RoomBlobNormalizerService.Result Norm)> { entry }; + ordered.AddRange(perScene.Where(s => s.SceneName != entry.SceneName)); + + using var tx = await db.Database.BeginTransactionAsync(); + try + { + // Insert blobs — bytes go to S3, DB only stores metadata. + foreach (var s in ordered) + { + var blobName = MakeBlobName(s.SceneName); + var (bucket, key) = BlobRouter.Route(blobName); + await storage.PutAsync(bucket, key, s.Bytes, "application/octet-stream"); + db.RoomDataBlobs.Add(new RoomDataBlobEntity + { + RoomId = newRoomId, + BlobName = blobName, + UploadedByPlayerId = creator, + UploadedAt = DateTime.UtcNow, + }); + } + + // Insert Room row pointing at entry scene's blob. + var entryBlob = MakeBlobName(entry.SceneName); + db.Rooms.Add(new RoomEntity + { + Id = newRoomId, + Name = roomName, + Description = string.IsNullOrWhiteSpace(description) + ? $"Imported via admin upload ({ordered.Count} scenes)" + : description.Trim(), + CreatorPlayerId = creator, + ImageName = string.Empty, + State = 0, + Accessibility = 1, + IsAGRoom = false, + IsDormRoom = false, + CloningAllowed = true, + LocationReplicationId = makerRoomLocation, + TagsCsv = "community", + CheerCount = 0, + FavoriteCount = 0, + VisitCount = 0, + VisitorCount = 0, + HotScore = 5.0, + CurrentDataBlobName = entryBlob, + }); + + // Insert RoomScenes rows. + int idx = 0; + foreach (var s in ordered) + { + db.RoomScenes.Add(new RoomSceneEntity + { + RoomId = newRoomId, + OrderIndex = idx++, + Name = s.SceneName, + RoomSceneLocationId = makerRoomLocation, + DataBlobName = MakeBlobName(s.SceneName), + MaxPlayers = 8, + IsSandbox = false, + CanMatchmakeInto = true, + DataModifiedAt = DateTime.UtcNow, + }); + } + + await db.SaveChangesAsync(); + await tx.CommitAsync(); + } + catch + { + await tx.RollbackAsync(); + throw; + } + + await LogAsync("import_room", "room", newRoomId, + $"name={roomName} scenes={ordered.Count} entry={entry.SceneName}"); + + // Fire-and-forget: scan the just-imported scene blobs for + // Holotar/AudioSampler .htr references and download each from + // cdn.rec.net into RoomDataBlobs (BlobName="{hash}.htr", + // RoomId=0). The watch's holotar projectors and audio + // samplers can then pull their content from cdn.localhost + // instead of failing on the public CDN. Doing this in the + // background keeps the upload response snappy (the .htr + // download set for a YarrHarrHeist-sized room is ~25 MB). + htrMirror.EnqueueAsync( + ordered.Select(s => s.Bytes).ToList(), + contextLabel: $"room={roomName}"); + + return Ok(new + { + roomId = newRoomId, + roomName, + sceneCount = ordered.Count, + entryScene = entry.SceneName, + normalizedSceneCount = ordered.Count(s => s.Norm.Normalized), + normalizationFailures = ordered.Where(s => !s.Norm.Normalized) + .Select(s => new { scene = s.SceneName, error = s.Norm.Error }), + htrMirrorStarted = true, + scenes = ordered.Select((s, i) => new + { + orderIndex = i, + name = s.SceneName, + blobName = MakeBlobName(s.SceneName), + rawBytes = s.Norm.InputBytes, + bytes = s.Bytes.Length, + normalized = s.Norm.Normalized, + }), + }); + } + + /// POST api/admin/v1/rooms/{id}/mirror-htr — + /// re-run the HoloTar / AudioSampler .htr mirror for an existing + /// room's blobs. Useful to (a) backfill assets for rooms imported + /// before the auto-mirror existed, (b) debug whether + /// HtrAssetMirrorService can actually extract refs from the C# + /// generated 2020 PersistedRoomData schema. Synchronous (awaits the + /// download stream) so the response carries the result counts — + /// the import-time path uses fire-and-forget instead. + [HttpPost("rooms/{roomId:long}/mirror-htr")] + public async Task MirrorHtrForRoom(long roomId) + { + // Stream bytes back from S3 — DB carries only metadata now. + var blobNames = await db.RoomDataBlobs + .Where(b => b.RoomId == roomId) + .Select(b => b.BlobName) + .ToListAsync(); + if (blobNames.Count == 0) return NotFound(new { error = "no_blobs", roomId }); + var blobs = new List(blobNames.Count); + foreach (var blobName in blobNames) + { + var (bucket, key) = BlobRouter.Route(blobName); + var bytes = await storage.GetAsync(bucket, key); + if (bytes is { Length: > 0 }) blobs.Add(bytes); + } + if (blobs.Count == 0) return NotFound(new { error = "blobs_missing_in_s3", roomId, expected = blobNames.Count }); + var result = await htrMirror.MirrorAsync(blobs, contextLabel: $"manual room={roomId}"); + await LogAsync("mirror_htr", "room", roomId, + $"refs={result.TotalRefs} skipped={result.AlreadyMirrored} inserted={result.Inserted} parseFails={result.RoomBlobParseFailures}"); + return Ok(new + { + roomId, + scannedBlobs = blobs.Count, + uniqueRefs = result.TotalRefs, + alreadyMirrored = result.AlreadyMirrored, + downloaded = result.Inserted, + roomParseFailures = result.RoomBlobParseFailures, + assetDownloadFailures = result.AssetDownloadFailures, + }); + } + + /// Pull the immediate-parent folder out of a multi-segment + /// filename like "Lobby/abc.room" or "YarrHarrHeist/Lobby/abc.room". + /// Returns the second-to-last segment (the file's parent folder). + /// Returns null if the filename has no folder component. + private static string? ExtractSceneFolder(string fileName) + { + if (string.IsNullOrWhiteSpace(fileName)) return null; + // Browsers may use either separator depending on platform. + var parts = fileName.Replace('\\', '/').Split('/', StringSplitOptions.RemoveEmptyEntries); + if (parts.Length < 2) return null; + // The .room file's parent folder is the scene name. + return parts[^2]; + } +} diff --git a/DorkNet.Server/Controllers/Admin/AdminLoginController.cs b/DorkNet.Server/Controllers/Admin/AdminLoginController.cs new file mode 100644 index 0000000..697a0ae --- /dev/null +++ b/DorkNet.Server/Controllers/Admin/AdminLoginController.cs @@ -0,0 +1,54 @@ +using Microsoft.AspNetCore.Mvc; +using Microsoft.EntityFrameworkCore; +using DorkNet.Server.Data; +using DorkNet.Server.Services; + +namespace DorkNet.Server.Controllers.Admin; + +/// +/// admin.rec.net/api/admin/v1/login — same-origin login endpoint for +/// the browser-based admin UI. Wraps a username+password verify +/// against PlayerEntity.PasswordHash and returns the JWT pair the UI +/// can stash in localStorage. Refuses non-admin accounts up front so a +/// regular player password can't be used to scope into the admin tools +/// even if the JWT is valid against api/admin/v1/* (the AdminOnly +/// filter would catch it anyway, but the early reject saves a +/// confused-looking 403). +/// +/// Lives on admin.rec.net so the static admin page (also served from +/// admin.rec.net) can fetch with a same-origin request and skip CORS +/// entirely. +/// +[ApiController] +[Route("api/admin/v1")] +public class AdminLoginController( + DorkNetDbContext db, + AuthService authService) : ControllerBase +{ + public sealed record AdminLoginRequest(string Username, string Password); + + [HttpPost("login")] + public async Task Login([FromBody] AdminLoginRequest body) + { + if (string.IsNullOrWhiteSpace(body.Username) || string.IsNullOrWhiteSpace(body.Password)) + return BadRequest(new { error = "missing_credentials" }); + + var player = await db.Players.FirstOrDefaultAsync(p => p.Username == body.Username); + if (player is null || player.PasswordHash is null) + return Unauthorized(new { error = "invalid_credentials" }); + if (!BCrypt.Net.BCrypt.Verify(body.Password, player.PasswordHash)) + return Unauthorized(new { error = "invalid_credentials" }); + if (!player.IsAdmin) + return StatusCode(403, new { error = "not_admin" }); + + var (access, refresh) = authService.GenerateTokenPair(player.Id); + return Ok(new + { + access_token = access, + refresh_token = refresh, + account_id = player.Id, + username = player.Username, + display_name = player.DisplayName, + }); + } +} diff --git a/DorkNet.Server/Controllers/Admin/RoomZipImportController.cs b/DorkNet.Server/Controllers/Admin/RoomZipImportController.cs new file mode 100644 index 0000000..0e0a306 --- /dev/null +++ b/DorkNet.Server/Controllers/Admin/RoomZipImportController.cs @@ -0,0 +1,1615 @@ +using System.IO.Compression; +using System.Text.Json; +using System.Text.Json.Serialization; +using Google.Protobuf; +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Mvc; +using Microsoft.EntityFrameworkCore; +using DorkNet.Server.Auth; +using DorkNet.Server.Data; +using DorkNet.Server.Data.Entities; +using DorkNet.Server.Services; + +namespace DorkNet.Server.Controllers.Admin; + +/// +/// admin.rec.net/api/admin/v1/rooms/zip-bulk-import — preferred room +/// import path going forward. The legacy multi-file endpoint +/// (POST api/admin/v1/rooms/import) stays alongside this for +/// tooling that already targets it. +/// +/// The archive format mirrors RecNet's own room export shape so a +/// user can download a room from rec.net, drop the zip into the admin +/// page, and we re-create it with full fidelity — including the +/// .htr AudioSampler / Holotar assets already extracted into +/// each subroom folder so we never have to hit Rec Room's CDN: +/// +/// root.zip/ +/// Rooms/Room_<id>_<name>_<timestamp>/ +/// RoomDetails.json (full RecNet v4 details schema) +/// RoomImage.jpg | <hash>.jpg +/// Promo/Image/PromoImage_*.jpg +/// SubRooms/<SceneName>/ +/// Subroom.json (per-subroom manifest) +/// <hash>.room (referenced by Subroom.CurrentSave.DataBlob) +/// AudioSampler/<hash>.htr +/// Holotar/<hash>.htr +/// Image/PVImage_<hash>.jpg +/// Inventions/Invention_<id>_<name>_<timestamp>/ +/// Invention.json +/// InventionDetails.json +/// InventionVersion.json +/// InventionImage.jpg +/// <hash>.inv +/// +/// Entry-scene resolution: the order in RoomDetails.SubRooms[] +/// is the entry order (RecNet API convention). The first entry is the +/// entry scene. The admin can override with an explicit +/// RoomDetails.EntryScene field. +/// +[ApiController] +[Route("api/admin/v1/rooms")] +[Authorize] +[AdminOnly] +public class RoomZipImportController( + DorkNetDbContext db, + RoomBlobNormalizerService roomBlobNormalizer, + IObjectStorage storage, + IServiceScopeFactory scopeFactory, + ILogger logger) : ControllerBase +{ + private long CurrentAdminId => this.RequireCurrentPlayerId(); + + // ── Room-blob normaliser toggle ───────────────────────────────────── + // The normaliser does two things now (see RoomBlobNormalizerService): + // 1. Project modern Rec Room's TransformData.quaternion_rotation + // (field 6) onto the legacy Euler rotation Vector3 (field 2) + // the 2020.12 watch reads. Without this, every shape in a + // modern-authored room renders at rotation=(0,0,0) — the + // "Studio-imported shapes are misplaced" bug. + // 2. Re-encode through the 2020 PersistedRoomData schema to drop + // any non-canonical wire encodings the watch's stricter parser + // doesn't accept. + // + // Per-import flag (`normalizeBlobs` in the form / finalize body) + // defaults ON now that the projection pass exists. The SPA still + // surfaces it as a checkbox so an admin can opt out for an + // already-Euler-encoded blob if a particular import misbehaves. + // The diagnostic parse always runs (its parse-OK / parse-FAIL log + // line is useful telemetry). + + /// Write a blob to S3 at its canonical (bucket, key) AND + /// record the metadata row in . + /// Bytes are NOT persisted on the DB row — S3 is the only place + /// they live. Previously the importer wrote bytes to the + /// Bytes column and skipped S3 entirely, so freshly + /// imported rooms were only reachable through the read-path's + /// DB-fallback branch. Centralised here so every importer call + /// site (scene saves, .htr/PV/polaroid assets, .meta sentinels, + /// invention saves, room thumbnails, promo images) ends up in + /// S3 the same way. + private async Task WriteBlobAsync( + long roomId, string blobName, byte[] bytes, long uploaderPlayerId, + string? referencesCsv = null, string? contentTypeOverride = null, + DateTime? uploadedAt = null, long? subRoomId = null) + { + var (bucket, key) = BlobRouter.Route(blobName); + var contentType = contentTypeOverride ?? MimeFromName(blobName); + try + { + await storage.PutAsync(bucket, key, bytes, contentType); + } + catch (Exception ex) + { + logger.LogError(ex, + "[zip-import] S3 PUT failed bucket={Bucket} key={Key} blob={Blob} bytes={Bytes} — row will still be recorded so the import isn't lost", + bucket, key, blobName, bytes.Length); + } + + var entity = new RoomDataBlobEntity + { + RoomId = roomId, + BlobName = blobName, + UploadedByPlayerId = uploaderPlayerId, + UploadedAt = uploadedAt ?? DateTime.UtcNow, + ReferencedFilenamesCsv = referencesCsv ?? string.Empty, + SubRoomId = subRoomId, + }; + db.RoomDataBlobs.Add(entity); + return entity; + } + + private static string MimeFromName(string name) => Path.GetExtension(name).ToLowerInvariant() switch + { + ".png" => "image/png", + ".jpg" or ".jpeg" => "image/jpeg", + ".gif" => "image/gif", + ".webp" => "image/webp", + ".mp4" => "video/mp4", + ".htr" => "application/octet-stream", + ".room" => "application/octet-stream", + ".meta" => "application/octet-stream", + ".inv" => "application/octet-stream", + ".dat" => "application/octet-stream", + _ => "application/octet-stream", + }; + + private static readonly JsonSerializerOptions JsonOpts = new() + { + PropertyNameCaseInsensitive = true, + DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull, + }; + + // ── RoomDetails.json DTOs ────────────────────────────────────────── + + public sealed class RoomDetailsDto + { + public long? RoomId { get; set; } + public string? Name { get; set; } + public string? FriendlyName { get; set; } + public string? Description { get; set; } + public string? ImageName { get; set; } + public int? WarningMask { get; set; } + public string? CustomWarning { get; set; } + public long? CreatorAccountId { get; set; } + public int? State { get; set; } + public int? Accessibility { get; set; } + public bool? SupportsLevelVoting { get; set; } + public bool? IsRRO { get; set; } + public bool? SupportsScreens { get; set; } + public bool? SupportsWalkVR { get; set; } + public bool? SupportsTeleportVR { get; set; } + public bool? SupportsVRLow { get; set; } + public bool? SupportsMobile { get; set; } + public bool? SupportsJuniors { get; set; } + public DateTime? CreatedAt { get; set; } + public DateTime? PublishedAt { get; set; } + public bool? IsDorm { get; set; } + public int? MaxPlayers { get; set; } + public bool? CloningAllowed { get; set; } + public bool? DisableMicAutoMute { get; set; } + public StatsDto? Stats { get; set; } + public List? Tags { get; set; } + public List? SubRooms { get; set; } + public string? EntryScene { get; set; } // admin override; not in real exports + } + + public sealed class StatsDto + { + public int? CheerCount { get; set; } + public int? FavoriteCount { get; set; } + public int? VisitorCount { get; set; } + public int? VisitCount { get; set; } + } + + public sealed class TagDto + { + public string? Tag { get; set; } + public int? Type { get; set; } + public bool? IsPrimaryGenre { get; set; } + } + + public sealed class SubRoomDto + { + public long? SubRoomId { get; set; } + public string? UnitySceneId { get; set; } + public string? Name { get; set; } + public bool? IsSandbox { get; set; } + public int? MaxPlayers { get; set; } + public int? Accessibility { get; set; } + public CurrentSaveDto? CurrentSave { get; set; } + } + + public sealed class CurrentSaveDto + { + public long? SubRoomDataSaveId { get; set; } + public string? DataBlob { get; set; } + } + + /// Sidecar JSON next to a History/<blob>.room entry. All + /// fields optional — the importer reads them to populate + /// RoomDataBlobEntity.UploadedAt + audit trail when present, and falls + /// back to for the timestamp otherwise. + public sealed class HistorySidecarDto + { + public long? SubRoomDataSaveId { get; set; } + /// Parent sub-room id. The Restore UI fetches + /// rooms/{roomId}/subrooms/{subRoomId}/datahistory; without + /// a SubRoomId on each row we'd have to return the whole room's + /// blob mix under every sub-room's history list. Optional: when + /// missing, the importer derives it from the SubRooms/<scene> + /// folder name suffix (SubRoomName__<subRoomId>). + public long? SubRoomId { get; set; } + public string? DataBlob { get; set; } + public DateTime? CreatedAt { get; set; } + public string? Description { get; set; } + public long? SavedByAccountId { get; set; } + } + + // ── Invention DTOs ──────────────────────────────────────────────── + + public sealed class InventionJsonDto + { + public long? InventionId { get; set; } + public string? ReplicationId { get; set; } + public long? CreatorPlayerId { get; set; } + public string? Name { get; set; } + public string? Description { get; set; } + public string? LongDescription { get; set; } + public string? ImageName { get; set; } + public int? UgcVersion { get; set; } + public int? CurrentVersionNumber { get; set; } + public int? LatestVersionNumber { get; set; } + public int? Accessibility { get; set; } + public DateTime? CreatedAt { get; set; } + public DateTime? ModifiedAt { get; set; } + public DateTime? FirstPublishedAt { get; set; } + public long? CreationRoomId { get; set; } + public int? NumPlayersHaveUsedInRoom { get; set; } + public int? CheerCount { get; set; } + public int? CreatorPermission { get; set; } + public int? GeneralPermission { get; set; } + public bool? IsAgInvention { get; set; } + public bool? IsRecRoomApproved { get; set; } + } + + public sealed class InventionVersionDto + { + public int? VersionNumber { get; set; } + public string? BlobName { get; set; } + } + + public sealed class InventionDetailsDto + { + public List? Tags { get; set; } + } + + // ── Endpoint ────────────────────────────────────────────────────── + + [HttpPost("zip-bulk-import")] + [DisableRequestSizeLimit] + [RequestFormLimits(MultipartBodyLengthLimit = 5_000_000_000)] // 5 GB + public async Task ZipImport( + [FromForm(Name = "archive")] IFormFile? archive, + [FromForm(Name = "creatorPlayerId")] long? creatorPlayerId, + [FromForm(Name = "normalizeBlobs")] bool? normalizeBlobs) + { + if (archive is null || archive.Length == 0) + return BadRequest(new { error = "missing_archive" }); + + await using var stream = archive.OpenReadStream(); + return await RunImportAsync( + stream, archive.Length, creatorPlayerId, + normalizeBlobs: normalizeBlobs ?? true); + } + + // ── Chunked upload (Cloudflare 100MB workaround) ───────────────── + // + // The whole multipart-zip path 413s through Cloudflare's edge for + // anything over 100 MB on free/pro plans. Real RecNet exports + // routinely run 150-300 MB. Splitting the upload into ≤90 MB + // chunks keeps each request safely under the cap; the server + // appends chunks to a temp file and only invokes the import logic + // once the finalize call comes in. + // + // Sessions are in-memory and short-lived (admins use the importer + // synchronously). If the process restarts before finalize, the + // upload is lost — acceptable for an admin tool used by 1-2 people. + private static readonly System.Collections.Concurrent.ConcurrentDictionary Sessions = new(); + + public sealed class ZipUploadSession + { + public Guid Id { get; init; } + public string TempPath { get; init; } = string.Empty; + public long TotalBytes { get; set; } + public long ReceivedBytes { get; set; } + public string FileName { get; init; } = string.Empty; + public long AdminPlayerId { get; init; } + public DateTime CreatedAt { get; init; } = DateTime.UtcNow; + } + + public sealed record ChunkInitRequest(string FileName, long TotalBytes); + + /// POST zip-upload-init — allocates a temp file and + /// returns an uploadId the SPA threads through subsequent + /// chunk POSTs. Capped at 5 GB total; opens a sparse temp file so + /// disk usage tracks actual bytes received. The cap is generous + /// because the live RecNet exports of large story-mode rooms + /// (large multi-scene Studio quests) easily + /// run 2-3 GB before any of the per-room recovery zip bundling. + [HttpPost("zip-upload-init")] + public ActionResult ChunkInit([FromBody] ChunkInitRequest body) + { + if (body.TotalBytes <= 0 || body.TotalBytes > 5_000_000_000) + return BadRequest(new { error = "invalid_size", maxBytes = 5_000_000_000L, gotBytes = body.TotalBytes }); + + // Prune stale sessions before allocating a new one. Anything + // older than 2 hours with no activity is abandoned. + foreach (var (sid, sess) in Sessions) + { + if (DateTime.UtcNow - sess.CreatedAt > TimeSpan.FromHours(2)) + { + Sessions.TryRemove(sid, out _); + try { if (System.IO.File.Exists(sess.TempPath)) System.IO.File.Delete(sess.TempPath); } catch { /* best effort */ } + } + } + + var id = Guid.NewGuid(); + var safeName = Path.GetFileName(body.FileName); + var tempPath = Path.Combine(Path.GetTempPath(), $"dorknet-ziplupload-{id:N}.zip"); + // Pre-allocate the file at full length so chunk writes at + // arbitrary offsets work without the OS having to grow it + // page-by-page during the random writes. + using (var fs = new FileStream(tempPath, FileMode.Create, FileAccess.Write)) + { + fs.SetLength(body.TotalBytes); + } + Sessions[id] = new ZipUploadSession + { + Id = id, + TempPath = tempPath, + TotalBytes = body.TotalBytes, + FileName = safeName, + AdminPlayerId = CurrentAdminId, + }; + return Ok(new { uploadId = id }); + } + + /// POST zip-upload-chunk/{id}?offset=N — writes the + /// posted body at offset in the session's temp file. + /// Per-call size capped at 100 MB (Kestrel-side, well below the + /// Cloudflare 100 MB edge cap so we never see a 413). Body is the + /// raw chunk bytes; the SPA's uploadZipInChunks helper sends + /// the right slice. + [HttpPost("zip-upload-chunk/{id:guid}")] + [DisableRequestSizeLimit] + [RequestSizeLimit(100_000_000)] + public async Task ChunkAppend(Guid id, [FromQuery] long offset) + { + if (!Sessions.TryGetValue(id, out var session)) return NotFound(new { error = "unknown_session" }); + if (session.AdminPlayerId != CurrentAdminId) return Forbid(); + if (offset < 0 || offset >= session.TotalBytes) + return BadRequest(new { error = "bad_offset", offset, total = session.TotalBytes }); + + await using var fs = new FileStream(session.TempPath, FileMode.Open, FileAccess.Write, FileShare.Read); + fs.Position = offset; + var written = 0L; + await Request.Body.CopyToAsync(fs); + written = fs.Position - offset; + session.ReceivedBytes = Math.Max(session.ReceivedBytes, fs.Position); + return Ok(new + { + uploadId = id, + offset, + written, + received = session.ReceivedBytes, + total = session.TotalBytes, + }); + } + + /// Body for zip-upload-finalize. + /// stamps every imported room/invention with that player as the + /// owner. and + /// , when non-null and + /// non-empty, restrict the import to only those top-level folders + /// inside the archive — the SPA's preview offers a checkbox per + /// room/invention so admins can skip a bad export's broken entry + /// without rebuilding the zip. Null/empty means "import + /// everything" (back-compat with older SPA builds). + public sealed record ChunkFinalizeRequest( + long? CreatorPlayerId, + List? SelectedRoomFolders, + List? SelectedInventionFolders, + bool? NormalizeBlobs); + + /// In-memory background job state for an async import. + /// The HTTP request that kicked the import off only stays alive + /// for milliseconds (it returns 202 immediately); the work runs + /// in Task.Run against a freshly-created DI scope so it + /// outlives the request. The SPA polls + /// GET zip-import-status/{jobId} for progress + result. + /// Jobs auto-expire 1 hour after finish (so the dictionary doesn't + /// grow forever). + public sealed class ImportJob + { + public Guid Id { get; init; } + public long AdminPlayerId { get; init; } + public DateTime StartedAt { get; init; } = DateTime.UtcNow; + public DateTime? FinishedAt { get; set; } + /// "running" | "done" | "failed". + public string State { get; set; } = "running"; + /// Short progress string updated by the import core + /// after each room/invention completes ("room 5/12: …"). + public string? Phase { get; set; } + /// On success: the same anonymous payload the sync + /// endpoint would have returned. On failure (validation + /// error): the { error = ..., ... } object the sync + /// endpoint would have wrapped in BadRequest. + public object? Result { get; set; } + /// Exception message when is + /// "failed". stays null in that case. + public string? Error { get; set; } + } + + private static readonly System.Collections.Concurrent.ConcurrentDictionary Jobs = new(); + + /// POST zip-upload-finalize/{id} — kicks off the + /// import on the assembled temp file in a background task and + /// returns 202 immediately with a jobId. The synchronous + /// path got 524'd by Cloudflare on multi-GB archives (edge + /// disconnects clients at 100s but the server kept processing + /// successfully). Now: the upload session is consumed here, the + /// finalize request returns before any timeout, and the SPA polls + /// the status endpoint for completion. + [HttpPost("zip-upload-finalize/{id:guid}")] + public ActionResult ChunkFinalize(Guid id, [FromBody] ChunkFinalizeRequest? body) + { + if (!Sessions.TryGetValue(id, out var session)) return NotFound(new { error = "unknown_session" }); + var adminId = CurrentAdminId; + if (session.AdminPlayerId != adminId) return Forbid(); + + // Capture before we leave the request context. + var tempPath = session.TempPath; + var totalBytes = session.TotalBytes; + var creator = body?.CreatorPlayerId ?? adminId; + var selectedRoomFolders = body?.SelectedRoomFolders; + var selectedInventionFolders = body?.SelectedInventionFolders; + var normalizeBlobs = body?.NormalizeBlobs ?? true; + + var jobId = Guid.NewGuid(); + var job = new ImportJob { Id = jobId, AdminPlayerId = adminId }; + Jobs[jobId] = job; + Sessions.TryRemove(id, out _); + + // Prune jobs older than 1 hour past completion to keep the + // dictionary bounded. Admin only runs the importer a few times + // a day, so this stays tiny. + foreach (var (jid, j) in Jobs) + { + if (j.FinishedAt is { } fin && DateTime.UtcNow - fin > TimeSpan.FromHours(1)) + Jobs.TryRemove(jid, out _); + } + + _ = Task.Run(async () => + { + using var scope = scopeFactory.CreateScope(); + var sp = scope.ServiceProvider; + // Build a fresh background controller instance bound to + // the new scope's services. The original `this` controller + // is tied to the request scope and will be disposed the + // moment we return below — we cannot reference its `db`, + // `storage`, etc. from the bg task. + var bgDb = sp.GetRequiredService(); + var bgNorm = sp.GetRequiredService(); + var bgStorage = sp.GetRequiredService(); + var bgLogger = sp.GetRequiredService>(); + var bgController = new RoomZipImportController(bgDb, bgNorm, bgStorage, scopeFactory, bgLogger); + + try + { + await using var stream = System.IO.File.OpenRead(tempPath); + var outcome = await bgController.RunImportCoreAsync( + stream, totalBytes, creator, adminId, + selectedRoomFolders, selectedInventionFolders, normalizeBlobs, + jobPhase: p => job.Phase = p); + job.Result = outcome.Payload; + job.State = outcome.Success ? "done" : "failed"; + } + catch (Exception ex) + { + bgLogger.LogError(ex, "[zip-import job {Job}] failed", jobId); + job.Error = ex.Message; + job.State = "failed"; + } + finally + { + job.FinishedAt = DateTime.UtcNow; + try { if (System.IO.File.Exists(tempPath)) System.IO.File.Delete(tempPath); } + catch (Exception ex) { bgLogger.LogWarning(ex, "[zip-import] failed to clean up {Path}", tempPath); } + } + }); + + return Accepted(new + { + jobId, + state = job.State, + statusUrl = $"/api/admin/v1/rooms/zip-import-status/{jobId}", + }); + } + + /// GET zip-import-status/{jobId} — SPA polls this + /// every few seconds while the import is running. Returns the + /// final result payload once state == "done" or + /// "failed"; on "failed" the payload either includes a + /// validation error (creator_not_found etc.) or just an + /// error string from the caught exception. + [HttpGet("zip-import-status/{jobId:guid}")] + public ActionResult GetImportStatus(Guid jobId) + { + if (!Jobs.TryGetValue(jobId, out var job)) return NotFound(new { error = "unknown_job" }); + if (job.AdminPlayerId != CurrentAdminId) return Forbid(); + return Ok(new + { + jobId = job.Id, + state = job.State, + phase = job.Phase, + startedAt = job.StartedAt, + finishedAt = job.FinishedAt, + elapsedSeconds = ((job.FinishedAt ?? DateTime.UtcNow) - job.StartedAt).TotalSeconds, + result = job.Result, + error = job.Error, + }); + } + + /// DELETE zip-upload-abort/{id} — drop an in-flight + /// upload session (e.g. user navigated away). No-op if the id is + /// already gone. + [HttpDelete("zip-upload-abort/{id:guid}")] + public IActionResult ChunkAbort(Guid id) + { + if (Sessions.TryRemove(id, out var session)) + { + try { if (System.IO.File.Exists(session.TempPath)) System.IO.File.Delete(session.TempPath); } + catch (Exception ex) { logger.LogWarning(ex, "[zip-import] failed to clean up {Path}", session.TempPath); } + } + return Ok(); + } + + // ── Shared import core (called by both single-shot and chunked) ── + + /// Outcome of a single zip import. Wraps both success + /// payloads and validation errors into one object so the same + /// import code can serve the sync zip-bulk-import endpoint + /// AND the async zip-upload-finalize background job (which + /// has no to return — it stores the + /// outcome in an for the SPA to poll). + public sealed class ImportOutcome + { + /// true → payload is the success response; + /// false → payload includes an "error" property and the + /// sync endpoint should return 400. + public bool Success { get; init; } + /// The anonymous payload that would have been wrapped + /// in Ok()/BadRequest() in the sync path. + public object Payload { get; init; } = new { }; + } + + private async Task RunImportAsync( + Stream archiveStream, + long archiveBytes, + long? creatorPlayerId, + List? selectedRoomFolders = null, + List? selectedInventionFolders = null, + bool normalizeBlobs = false) + { + var adminId = CurrentAdminId; + var outcome = await RunImportCoreAsync( + archiveStream, archiveBytes, + creatorPlayerId ?? adminId, + adminId, + selectedRoomFolders, selectedInventionFolders, normalizeBlobs, + jobPhase: null); + return outcome.Success + ? Ok(outcome.Payload) + : BadRequest(outcome.Payload); + } + + /// Pure import worker: no HttpContext access (caller passes + /// the resolved ), no IActionResult + /// wrapping. Background jobs call this via a freshly-resolved + /// scope so they can outlive the request that kicked them off. + /// , when non-null, gets updated with a + /// short progress string after each room/invention is processed so + /// the status endpoint can report meaningful progress. + private async Task RunImportCoreAsync( + Stream archiveStream, + long archiveBytes, + long creator, + long adminId, + List? selectedRoomFolders, + List? selectedInventionFolders, + bool normalizeBlobs, + Action? jobPhase) + { + if (!await db.Players.AnyAsync(p => p.Id == creator)) + return new ImportOutcome { Success = false, Payload = new { error = "creator_not_found", creator } }; + + using var zip = new ZipArchive(archiveStream, ZipArchiveMode.Read, leaveOpen: false); + + // Index entries by normalized path. We do path-based lookups + // throughout (per-room folder, per-subroom folder, per-invention + // folder), so a flat dictionary keyed by full path is simplest. + var entryByPath = new Dictionary(StringComparer.OrdinalIgnoreCase); + foreach (var e in zip.Entries) + { + if (e.FullName.EndsWith("/", StringComparison.Ordinal)) continue; + entryByPath[Normalize(e.FullName)] = e; + } + + // Find every room manifest (`Rooms//RoomDetails.json`). + // Folder names are like "Room_3163127_YarrHarrHeist_20260509_180319" + // but we don't parse anything out of them — RoomDetails.json is + // the source of truth. + var roomFolders = entryByPath.Keys + .Where(k => k.StartsWith("Rooms/", StringComparison.OrdinalIgnoreCase) + && k.EndsWith("/RoomDetails.json", StringComparison.OrdinalIgnoreCase)) + .Select(k => k.Substring(0, k.Length - "/RoomDetails.json".Length)) + .ToList(); + + // Inventions can live either at the zip root (Inventions/Invention___/) + // OR inside a room folder (Rooms/Room___/Inventions/Invention_…). + // Both layouts show up in the wild — handle either. + var inventionFolders = entryByPath.Keys + .Where(k => k.EndsWith("/Invention.json", StringComparison.OrdinalIgnoreCase) + && (k.StartsWith("Inventions/", StringComparison.OrdinalIgnoreCase) + || k.Contains("/Inventions/", StringComparison.OrdinalIgnoreCase))) + .Select(k => k[..^"/Invention.json".Length]) + .ToList(); + + if (roomFolders.Count == 0 && inventionFolders.Count == 0) + return new ImportOutcome { Success = false, Payload = new { error = "empty_archive_no_manifests" } }; + + // If the admin supplied an explicit selection (non-null, + // non-empty), filter to just those folders. Comparison is + // case-insensitive and tolerates leading/trailing slashes that + // the client might have included. Anything in the selection + // that we can't match against the archive is just ignored — + // the per-room report will simply not include it. + if (selectedRoomFolders is { Count: > 0 }) + { + var allowed = selectedRoomFolders + .Select(s => Normalize(s).TrimEnd('/')) + .ToHashSet(StringComparer.OrdinalIgnoreCase); + roomFolders = roomFolders.Where(f => allowed.Contains(f)).ToList(); + } + if (selectedInventionFolders is { Count: > 0 }) + { + var allowed = selectedInventionFolders + .Select(s => Normalize(s).TrimEnd('/')) + .ToHashSet(StringComparer.OrdinalIgnoreCase); + inventionFolders = inventionFolders.Where(f => allowed.Contains(f)).ToList(); + } + + var roomReports = new List(); + long nextRoomId = (await db.Rooms + .Where(r => r.Id >= 1000) + .Select(r => (long?)r.Id) + .MaxAsync() ?? 999) + 1; + + var roomIdx = 0; + foreach (var folder in roomFolders) + { + roomIdx++; + jobPhase?.Invoke($"room {roomIdx}/{roomFolders.Count}: {folder}"); + try + { + var result = await ImportRoomAsync(folder, nextRoomId, creator, adminId, entryByPath, normalizeBlobs); + roomReports.Add(result); + if (result.GetType().GetProperty("ok")!.GetValue(result) as bool? == true) + nextRoomId++; + } + catch (Exception ex) + { + logger.LogError(ex, "[zip-import] room folder='{Folder}' failed", folder); + roomReports.Add(new { folder, ok = false, error = ex.Message }); + } + } + + var inventionReports = new List(); + var invIdx = 0; + foreach (var folder in inventionFolders) + { + invIdx++; + jobPhase?.Invoke($"invention {invIdx}/{inventionFolders.Count}: {folder}"); + try { inventionReports.Add(await ImportInventionAsync(folder, creator, adminId, entryByPath)); } + catch (Exception ex) + { + logger.LogError(ex, "[zip-import] invention folder='{Folder}' failed", folder); + inventionReports.Add(new { folder, ok = false, error = ex.Message }); + } + } + + jobPhase?.Invoke($"done: {roomFolders.Count} rooms, {inventionFolders.Count} inventions"); + return new ImportOutcome + { + Success = true, + Payload = new + { + archiveBytes, + roomCount = roomFolders.Count, + inventionCount = inventionFolders.Count, + rooms = roomReports, + inventions = inventionReports, + }, + }; + } + + // ── Per-room import ─────────────────────────────────────────────── + + private async Task ImportRoomAsync( + string roomFolder, + long roomId, + long creator, + long adminId, + Dictionary entries, + bool normalizeBlobs) + { + // RoomDetails.json + var detailsKey = $"{roomFolder}/RoomDetails.json"; + var details = await ReadJsonAsync(entries[detailsKey]) ?? new RoomDetailsDto(); + var name = !string.IsNullOrWhiteSpace(details.Name) ? details.Name!.Trim() : Path.GetFileName(roomFolder); + + if (await db.Rooms.AnyAsync(r => r.Name == name)) + return new { name, ok = false, error = "duplicate_name", folder = roomFolder }; + + // Subroom manifests + blobs. Walk each folder under + // /SubRooms// and pair its Subroom.json with + // the .room blob it references via CurrentSave.DataBlob, plus + // any AudioSampler/Holotar .htr files alongside. + var subroomRoot = $"{roomFolder}/SubRooms/"; + var scenes = new Dictionary(StringComparer.OrdinalIgnoreCase); + foreach (var path in entries.Keys.Where(k => k.StartsWith(subroomRoot, StringComparison.OrdinalIgnoreCase))) + { + var rel = path.Substring(subroomRoot.Length); + var slash = rel.IndexOf('/'); + if (slash <= 0) continue; + var sceneName = rel.Substring(0, slash); + if (!scenes.ContainsKey(sceneName)) scenes[sceneName] = sceneName; + } + var resolvedScenes = new List(); + var missingScenes = new List(); + + foreach (var sceneName in scenes.Keys) + { + var sceneFolder = $"{subroomRoot}{sceneName}"; + var subroomJsonKey = $"{sceneFolder}/Subroom.json"; + if (!entries.TryGetValue(subroomJsonKey, out var subroomJsonEntry)) + { + missingScenes.Add($"{sceneName} (missing Subroom.json)"); + continue; + } + var manifest = await ReadJsonAsync(subroomJsonEntry) ?? new SubRoomDto(); + var blobFile = manifest.CurrentSave?.DataBlob; + if (string.IsNullOrWhiteSpace(blobFile)) + { + missingScenes.Add($"{sceneName} (no CurrentSave.DataBlob)"); + continue; + } + var blobKey = $"{sceneFolder}/{blobFile}"; + if (!entries.TryGetValue(blobKey, out var blobEntry)) + { + missingScenes.Add($"{sceneName} → {blobFile}"); + continue; + } + + var blobBytes = await ReadAllAsync(blobEntry); + // The normaliser still runs so we keep the parse-OK / parse-FAIL + // log line — useful telemetry even when we're not persisting + // its output. Per-import `normalizeBlobs` flag (defaulting + // off because the re-encode currently crashes the watch on + // load) decides which bytes actually reach S3. + var norm = roomBlobNormalizer.Normalize(blobBytes); + var persistedBytes = normalizeBlobs ? norm.Bytes : blobBytes; + + // Collect .htr asset blobs that live alongside this subroom. + var htrAssets = new List<(string Name, ZipArchiveEntry Entry)>(); + foreach (var sub in new[] { "AudioSampler", "Holotar" }) + { + var prefix = $"{sceneFolder}/{sub}/"; + foreach (var (k, e) in entries.Where(kv => kv.Key.StartsWith(prefix, StringComparison.OrdinalIgnoreCase))) + { + if (k.EndsWith(".htr", StringComparison.OrdinalIgnoreCase)) + htrAssets.Add((Path.GetFileName(k), e)); + } + } + // Image/ folder: persistence-view previews (PVImage_.ext) + // and — since the exporter consolidated layouts — also the + // placed-in-world polaroids that used to live in Polaroids/. + // + // The watch fetches BOTH URLs for the same content: + // * `img.localhost/PVImage_.png` — persistence-view preview + // * `img.localhost/.png` — placed polaroid (room blob + // references this bare-hash form, decoded directly from + // the .room protobuf in our sample) + // So for any `PVImage_`-prefixed file we save the bytes under + // BOTH BlobNames. Same file in S3/DB twice is cheap and saves + // us from missing one of the lookup paths. + const string pvPrefix = "PVImage_"; + var pvImages = new List<(string Name, ZipArchiveEntry Entry)>(); + var polaroids = new List<(string Name, ZipArchiveEntry Entry)>(); + var imageFolderPrefix = $"{sceneFolder}/Image/"; + foreach (var (k, e) in entries.Where(kv => kv.Key.StartsWith(imageFolderPrefix, StringComparison.OrdinalIgnoreCase))) + { + var fileName = Path.GetFileName(k); + pvImages.Add((fileName, e)); + if (fileName.StartsWith(pvPrefix, StringComparison.OrdinalIgnoreCase)) + { + var bareHashName = fileName[pvPrefix.Length..]; + polaroids.Add((bareHashName, e)); + } + } + // Legacy fallback: older archives shipped polaroids as a + // separate Polaroids/ subfolder. Still support those so we + // don't regress on older test zips that haven't been re- + // exported. The persistence loop's de-dupe will collapse + // overlaps from the Image/-derived path. + var polaroidFolderPrefix = $"{sceneFolder}/Polaroids/"; + foreach (var (k, e) in entries.Where(kv => kv.Key.StartsWith(polaroidFolderPrefix, StringComparison.OrdinalIgnoreCase))) + { + polaroids.Add((Path.GetFileName(k), e)); + } + + // History/ folder — every non-canonical save dropped by the + // studio-dump-to-import-zip converter (with --every-save). Each + // `.room` becomes a separate RoomDataBlobEntity row so + // it shows up in `GET api/rooms/v1/datahistory/{roomId}`. The + // optional `.json` sidecar carries CreatedAt; missing + // sidecars fall back to "now". + var history = new List(); + var historyPrefix = $"{sceneFolder}/History/"; + // Derive SubRoomId from the folder name (SubRooms/) when + // the sidecar omits it. The converter names scene folders as + // either the rec.net Name (no id), or — when running our own + // dumper — the raw `__`. Falling back to + // manifest.SubRoomId catches the no-suffix case. + long? subRoomIdFromFolder = null; + var lastUnderscores = sceneName.LastIndexOf("__", StringComparison.Ordinal); + if (lastUnderscores >= 0 + && long.TryParse(sceneName[(lastUnderscores + 2)..], out var parsedSub)) + { + subRoomIdFromFolder = parsedSub; + } + var manifestSub = manifest.SubRoomId; + foreach (var (k, hEntry) in entries.Where(kv => + kv.Key.StartsWith(historyPrefix, StringComparison.OrdinalIgnoreCase) + && kv.Key.EndsWith(".room", StringComparison.OrdinalIgnoreCase) + && !kv.Key[historyPrefix.Length..].Contains('/'))) + { + var blobName = Path.GetFileName(k); + var sidecarKey = $"{k}.json"; + HistorySidecarDto? sidecar = null; + if (entries.TryGetValue(sidecarKey, out var sidecarEntry)) + sidecar = await ReadJsonAsync(sidecarEntry); + history.Add(new HistorySave( + DataBlob: blobName, + RoomEntry: hEntry, + SubRoomId: sidecar?.SubRoomId ?? subRoomIdFromFolder ?? manifestSub, + SubRoomDataSaveId: sidecar?.SubRoomDataSaveId, + CreatedAt: sidecar?.CreatedAt, + Description: sidecar?.Description, + SavedByAccountId: sidecar?.SavedByAccountId)); + } + + resolvedScenes.Add(new ResolvedSubroom( + Manifest: manifest, + SceneName: sceneName, + Bytes: persistedBytes, + RawBytes: blobBytes.Length, + // When the toggle is off we still report `norm.Normalized` — + // it reflects whether the diagnostic parse succeeded, not + // whether we actually applied the normalised output. + NormalizedOk: norm.Normalized, + HtrAssets: htrAssets, + PvImages: pvImages, + Polaroids: polaroids, + History: history)); + logger.LogInformation( + "[zip-import] room='{Room}' scene='{Scene}' blob={Blob} raw={Raw:N0} normOutput={Norm:N0} persistedFrom={Source} htr={Htr} pv={Pv} polaroids={Pol}", + name, sceneName, blobFile, blobBytes.Length, norm.Bytes.Length, + normalizeBlobs ? "normaliser" : "raw", + htrAssets.Count, pvImages.Count, polaroids.Count); + } + + if (resolvedScenes.Count == 0) + return new { name, ok = false, error = "no_resolvable_subrooms", missingScenes, folder = roomFolder }; + + // Entry resolution: order subrooms by manifest order if + // RoomDetails.SubRooms[] is present; otherwise by sceneName. + // EntryScene field overrides everything. + var manifestOrder = (details.SubRooms ?? new()) + .Where(s => !string.IsNullOrWhiteSpace(s.Name)) + .Select(s => s.Name!) + .ToList(); + if (manifestOrder.Count > 0) + { + resolvedScenes = resolvedScenes + .OrderBy(r => manifestOrder.IndexOf(r.SceneName) is int i && i >= 0 ? i : int.MaxValue) + .ThenBy(r => r.SceneName) + .ToList(); + } + else + { + resolvedScenes = resolvedScenes.OrderBy(r => r.SceneName).ToList(); + } + + // Entry resolution cascade: + // 1. Explicit `RoomDetails.EntryScene` override (admin-set, + // not in real exports). + // 2. .meta protobuf at the room root — RecNet's + // published-snapshot manifest. Field 2 is the entry + // subroom INDEX into RoomDetails.SubRooms[] (0-based). + // Decoded directly without a generated schema because + // the message is tiny (6 bytes total in the known sample). + // 3. First entry in manifest order. + // 4. Alphabetical fallback. + ResolvedSubroom? metaEntry = null; + int? metaIndex = null; + string? metaFileName = null; + var metaCandidate = entries + .Where(kv => kv.Key.StartsWith($"{roomFolder}/", StringComparison.OrdinalIgnoreCase) + && !kv.Key[$"{roomFolder}/".Length..].Contains('/') + && kv.Key.EndsWith(".meta", StringComparison.OrdinalIgnoreCase)) + .Select(kv => kv.Value) + .FirstOrDefault(); + if (metaCandidate is not null) + { + metaFileName = Path.GetFileName(metaCandidate.FullName); + var metaBytes = await ReadAllAsync(metaCandidate); + metaIndex = TryReadEntrySubroomIndexFromMeta(metaBytes); + logger.LogInformation("[zip-import] room='{Room}' .meta={Meta} bytes={Bytes} entryIndex={Idx}", + name, metaFileName, metaBytes.Length, metaIndex); + if (metaIndex is int idx && idx >= 0 && idx < manifestOrder.Count) + { + var sceneAtIndex = manifestOrder[idx]; + metaEntry = resolvedScenes.FirstOrDefault(r => + string.Equals(r.SceneName, sceneAtIndex, StringComparison.OrdinalIgnoreCase)); + } + } + + ResolvedSubroom entry; + string entrySource; + if (!string.IsNullOrWhiteSpace(details.EntryScene) + && resolvedScenes.FirstOrDefault(r => string.Equals(r.SceneName, details.EntryScene, StringComparison.OrdinalIgnoreCase)) is { } overrideEntry) + { + entry = overrideEntry; entrySource = "RoomDetails.EntryScene"; + } + else if (metaEntry is not null) + { + entry = metaEntry; entrySource = $"meta_index={metaIndex}"; + } + else + { + entry = resolvedScenes[0]; entrySource = manifestOrder.Count > 0 ? "manifest_first" : "alphabetical_first"; + } + + // Image: try ImageName lookup first (RecNet-style hash filename), + // then RoomImage.* fallback (convenience format). + ZipArchiveEntry? imageEntry = null; + if (!string.IsNullOrWhiteSpace(details.ImageName) + && entries.TryGetValue($"{roomFolder}/{details.ImageName}", out var hashed)) + { + imageEntry = hashed; + } + else + { + imageEntry = entries.FirstOrDefault(kv => + kv.Key.StartsWith($"{roomFolder}/RoomImage.", StringComparison.OrdinalIgnoreCase)).Value; + } + + // Reorder so entry is first. Manifest order preserved for the rest. + var ordered = new List { entry }; + ordered.AddRange(resolvedScenes.Where(r => !ReferenceEquals(r, entry))); + + var slug = name.ToLowerInvariant().Replace(' ', '_'); + // Prefix with room__ so BlobRouter parks every .room scene + // save alongside its room__v*.dat versioned snapshots in + // saves/room//. Same routing parser handles both shapes; + // no per-extension special case. + string MakeBlobName(string sceneName) => $"room_{roomId}_{slug}_{sceneName.ToLowerInvariant()}.room"; + + // Use the entry subroom's UnitySceneId as the room's location. + var entryLocation = entry.Manifest.UnitySceneId ?? "a75f7547-79eb-47c6-8986-6767abcb4f92"; + + // Persist .htr assets and PV images at the GLOBAL level + // (RoomId=0) under their original hash filenames. The CDN + // / img.* controllers look up by exact blob name, so this + // matches what HtrAssetMirrorService would have produced if it + // had to fetch them from rec.net. + // + // We track three counters per asset type: + // * referenced — total unique filenames the archive points at + // * imported — newly inserted into RoomDataBlobs this run + // * alreadyInDb — already present from a prior import / mirror + // The third is important to surface: bulk-purge-custom keeps + // shared assets intentionally (other rooms might use them), + // so a re-import after purge will legitimately report 0 newly + // imported with non-zero already-in-DB. Reporting only "0 + // imported" reads as "no HTR uploaded" which is wrong. + var htrAssetCount = 0; + var pvImageCount = 0; + var polaroidCount = 0; + var allHtrAssets = ordered.SelectMany(s => s.HtrAssets).ToList(); + var allPvImages = ordered.SelectMany(s => s.PvImages).ToList(); + var allPolaroids = ordered.SelectMany(s => s.Polaroids).ToList(); + + // De-dupe within the archive first (same .htr can appear under + // multiple scenes), then de-dupe against the DB. Both layers + // matter for accurate reporting. + var uniqueHtrNames = allHtrAssets.Select(a => a.Name).Distinct(StringComparer.OrdinalIgnoreCase).ToList(); + var uniquePvNames = allPvImages.Select(a => a.Name).Distinct(StringComparer.OrdinalIgnoreCase).ToList(); + var uniquePolaroidNames = allPolaroids.Select(a => a.Name).Distinct(StringComparer.OrdinalIgnoreCase).ToList(); + + var existingHtrNames = new HashSet( + await db.RoomDataBlobs + .Where(b => uniqueHtrNames.Contains(b.BlobName)) + .Select(b => b.BlobName).ToListAsync(), + StringComparer.OrdinalIgnoreCase); + var existingPvNames = new HashSet( + await db.RoomDataBlobs + .Where(b => uniquePvNames.Contains(b.BlobName)) + .Select(b => b.BlobName).ToListAsync(), + StringComparer.OrdinalIgnoreCase); + var existingPolaroidNames = new HashSet( + await db.RoomDataBlobs + .Where(b => uniquePolaroidNames.Contains(b.BlobName)) + .Select(b => b.BlobName).ToListAsync(), + StringComparer.OrdinalIgnoreCase); + var htrAlreadyInDbAtStart = existingHtrNames.Count; + var pvAlreadyInDbAtStart = existingPvNames.Count; + var polaroidAlreadyInDbAtStart = existingPolaroidNames.Count; + + // History (SubRooms//History/.room) — pre-load + // existing names so the de-dup HashSet covers prior imports + same + // hash referenced under multiple scenes. + var allHistoryNames = ordered + .SelectMany(s => s.History.Select(h => h.DataBlob)) + .Distinct(StringComparer.OrdinalIgnoreCase) + .ToList(); + var existingHistoryNames = new HashSet( + await db.RoomDataBlobs + .Where(b => allHistoryNames.Contains(b.BlobName)) + .Select(b => b.BlobName).ToListAsync(), + StringComparer.OrdinalIgnoreCase); + var historyAlreadyInDbAtStart = existingHistoryNames.Count; + var historyImported = 0; + var historySkipped = 0; + + var imageBlobName = string.Empty; + + await using (var tx = await db.Database.BeginTransactionAsync()) + { + // Subroom blobs — canonical save per scene + every History/ entry + // restoring the rec.net DataBlob filename verbatim so the in-game + // `Restore` UI's `datahistory` list shows up as expected. + // + // De-dup the History writes the same way HTR / PV / Polaroid + // sections do: pre-load existing BlobNames from the DB, then + // append in-memory as we queue each new row. Without this the + // unique index `IX_RoomDataBlobs_BlobName` fires on the + // SaveChanges when two scenes share a history hash (common: a + // baseline blob that didn't change between subroom edits). + var seenHistoryNames = new HashSet( + existingHistoryNames, StringComparer.OrdinalIgnoreCase); + // Watch's URL uses RoomSceneEntity.OrderIndex (e.g. /datahistory + // for `subRoom=11` is asking for the scene at OrderIndex 11), + // NOT the rec.net SubRoomId from the manifest (e.g. 9717143). + // RoomDataBlobs.SubRoomId must store the OrderIndex so the + // datahistory query (`WHERE SubRoomId = subRoomId`) matches. + // Use the same idx that the RoomSceneEntity insert loop below + // assigns — both iterate `ordered` in lockstep. + int historyOrderIdx = 0; + foreach (var s in ordered) + { + await WriteBlobAsync(roomId, MakeBlobName(s.SceneName), s.Bytes, creator); + + foreach (var h in s.History) + { + if (!seenHistoryNames.Add(h.DataBlob)) + { + historySkipped++; + continue; + } + var hBytes = await ReadAllAsync(h.RoomEntry); + // Don't normalise historical blobs — they're a snapshot of + // exactly what rec.net served at the time, and replaying + // them through the normaliser would mutate the bytes + // in subtle ways that obscure provenance. The watch reads + // them via CDN just like a normal save, so the same + // protobuf parser applies anyway. + await WriteBlobAsync( + roomId: roomId, + blobName: h.DataBlob, + bytes: hBytes, + uploaderPlayerId: h.SavedByAccountId ?? creator, + uploadedAt: h.CreatedAt, + subRoomId: historyOrderIdx); + historyImported++; + } + historyOrderIdx++; + } + logger.LogInformation( + "[zip-import] room='{Room}' history imports={Imported} (skipped={Skipped})", + name, historyImported, historySkipped); + // HTR assets (shared content — keyed by hash, RoomId=0) + foreach (var (hname, hentry) in allHtrAssets) + { + if (existingHtrNames.Contains(hname)) continue; + existingHtrNames.Add(hname); + var bytes = await ReadAllAsync(hentry); + await WriteBlobAsync(0, hname, bytes, creator); + htrAssetCount++; + } + // PV images (persistence-view previews; shared) + foreach (var (pname, pentry) in allPvImages) + { + if (existingPvNames.Contains(pname)) continue; + existingPvNames.Add(pname); + var bytes = await ReadAllAsync(pentry); + await WriteBlobAsync(0, pname, bytes, creator); + pvImageCount++; + } + // Polaroids (placed in-world; watch fetches by bare hash). + foreach (var (polName, polEntry) in allPolaroids) + { + if (existingPolaroidNames.Contains(polName)) continue; + existingPolaroidNames.Add(polName); + var bytes = await ReadAllAsync(polEntry); + await WriteBlobAsync(0, polName, bytes, creator); + polaroidCount++; + } + // Room-root opaque marker files (e.g. .meta — 6-byte + // sentinel the exporter drops, purpose unclear but watching + // for fetches just in case). Rename to room__.meta + // so BlobRouter parks them in saves/room// alongside the + // versioned save snapshots — keeps every byte that belongs + // to a specific room in one S3 folder. + var rootMetaPrefix = $"{roomFolder}/"; + foreach (var (k, mentry) in entries.Where(kv => + kv.Key.StartsWith(rootMetaPrefix, StringComparison.OrdinalIgnoreCase) + && !kv.Key[rootMetaPrefix.Length..].Contains('/') + && kv.Key.EndsWith(".meta", StringComparison.OrdinalIgnoreCase))) + { + var originalName = Path.GetFileName(k); + var fname = $"room_{roomId}_{originalName}"; + if (await db.RoomDataBlobs.AnyAsync(b => b.BlobName == fname)) continue; + var bytes = await ReadAllAsync(mentry); + await WriteBlobAsync(roomId, fname, bytes, creator); + } + + // Room image + if (imageEntry is not null) + { + var bytes = await ReadAllAsync(imageEntry); + var ext = Path.GetExtension(imageEntry.Name).ToLowerInvariant(); + if (string.IsNullOrEmpty(ext)) ext = ".jpg"; + imageBlobName = $"image_room_{roomId}{ext}"; + await WriteBlobAsync(0, imageBlobName, bytes, creator); + } + + // Promo images (kept under their original hashed names so + // the room's promo strip resolves them via img.* CDN). + var promoPrefix = $"{roomFolder}/Promo/Image/"; + foreach (var (k, pentry) in entries.Where(kv => kv.Key.StartsWith(promoPrefix, StringComparison.OrdinalIgnoreCase))) + { + var fname = Path.GetFileName(k); + if (await db.RoomDataBlobs.AnyAsync(b => b.BlobName == fname)) continue; + var bytes = await ReadAllAsync(pentry); + await WriteBlobAsync(0, fname, bytes, creator); + } + + db.Rooms.Add(new RoomEntity + { + Id = roomId, + Name = name, + Description = details.Description?.Trim() ?? $"Imported from zip ({ordered.Count} scenes)", + CreatorPlayerId = creator, + ImageName = imageBlobName, + State = details.State ?? 0, + Accessibility = details.Accessibility ?? 1, + IsAGRoom = false, + IsDormRoom = details.IsDorm ?? false, + CloningAllowed = details.CloningAllowed ?? true, + SupportsLevelVoting = details.SupportsLevelVoting ?? false, + SupportsVRLow = details.SupportsVRLow ?? true, + SupportsMobile = details.SupportsMobile ?? false, + SupportsScreens = details.SupportsScreens ?? true, + SupportsWalkVR = details.SupportsWalkVR ?? true, + SupportsTeleportVR = details.SupportsTeleportVR ?? true, + AllowsJuniors = details.SupportsJuniors ?? true, + RoomWarningMask = details.WarningMask ?? 0, + CustomRoomWarning = details.CustomWarning ?? string.Empty, + DisableMicAutoMute = details.DisableMicAutoMute ?? false, + LocationReplicationId = entryLocation, + TagsCsv = JoinTags(details.Tags), + CheerCount = details.Stats?.CheerCount ?? 0, + FavoriteCount = details.Stats?.FavoriteCount ?? 0, + VisitCount = details.Stats?.VisitCount ?? 0, + VisitorCount = details.Stats?.VisitorCount ?? 0, + HotScore = 5.0, + CurrentDataBlobName = MakeBlobName(entry.SceneName), + CreatedAt = details.CreatedAt ?? DateTime.UtcNow, + }); + + int idx = 0; + foreach (var s in ordered) + { + db.RoomScenes.Add(new RoomSceneEntity + { + RoomId = roomId, + OrderIndex = idx++, + Name = s.SceneName, + RoomSceneLocationId = s.Manifest.UnitySceneId ?? entryLocation, + DataBlobName = MakeBlobName(s.SceneName), + MaxPlayers = s.Manifest.MaxPlayers ?? 8, + IsSandbox = s.Manifest.IsSandbox ?? false, + CanMatchmakeInto = true, + DataModifiedAt = DateTime.UtcNow, + }); + } + + db.AdminActions.Add(new AdminActionEntity + { + AdminPlayerId = adminId, + Action = "zip_import_room", + TargetType = "room", + TargetId = roomId, + Reason = $"name={name} scenes={ordered.Count} entry={entry.SceneName} src={entrySource} " + + $"htr={htrAssetCount}new+{htrAlreadyInDbAtStart}existed " + + $"pv={pvImageCount}new+{pvAlreadyInDbAtStart}existed " + + $"polaroids={polaroidCount}new+{polaroidAlreadyInDbAtStart}existed", + }); + await db.SaveChangesAsync(); + await tx.CommitAsync(); + } + + return new + { + name, + ok = true, + roomId, + folder = roomFolder, + sceneCount = ordered.Count, + entryScene = entry.SceneName, + entryDetectionSource = entrySource, + missingScenes = missingScenes.Count > 0 ? missingScenes : null, + tags = details.Tags?.Select(t => t.Tag).Where(t => !string.IsNullOrWhiteSpace(t)).ToList(), + imageBlob = string.IsNullOrEmpty(imageBlobName) ? null : imageBlobName, + htrAssets = new + { + referenced = uniqueHtrNames.Count, + newlyImported = htrAssetCount, + alreadyInDb = htrAlreadyInDbAtStart, + }, + pvImages = new + { + referenced = uniquePvNames.Count, + newlyImported = pvImageCount, + alreadyInDb = pvAlreadyInDbAtStart, + }, + polaroids = new + { + referenced = uniquePolaroidNames.Count, + newlyImported = polaroidCount, + alreadyInDb = polaroidAlreadyInDbAtStart, + }, + history = new + { + referenced = allHistoryNames.Count, + newlyImported = historyImported, + alreadyInDb = historyAlreadyInDbAtStart, + skipped = historySkipped, + }, + // Legacy fields kept for back-compat with anything that + // already reads them; new code should prefer htrAssets / + // pvImages / polaroids above. + htrAssetsImported = htrAssetCount, + pvImagesImported = pvImageCount, + stats = new + { + cheers = details.Stats?.CheerCount ?? 0, + favorites = details.Stats?.FavoriteCount ?? 0, + visitors = details.Stats?.VisitorCount ?? 0, + visits = details.Stats?.VisitCount ?? 0, + }, + scenes = ordered.Select((s, i) => new + { + orderIndex = i, + name = s.SceneName, + blobName = MakeBlobName(s.SceneName), + bytes = s.Bytes.Length, + rawBytes = s.RawBytes, + normalized = s.NormalizedOk, + unitySceneId = s.Manifest.UnitySceneId, + isSandbox = s.Manifest.IsSandbox, + maxPlayers = s.Manifest.MaxPlayers, + htrAssets = s.HtrAssets.Count, + pvImages = s.PvImages.Count, + }), + }; + } + + // ── Per-invention import ───────────────────────────────────────── + + private async Task ImportInventionAsync( + string folder, + long creator, + long adminId, + Dictionary entries) + { + // Invention.json + var meta = await ReadJsonAsync(entries[$"{folder}/Invention.json"]) + ?? new InventionJsonDto(); + InventionVersionDto version = entries.TryGetValue($"{folder}/InventionVersion.json", out var ve) + ? await ReadJsonAsync(ve) ?? new InventionVersionDto() + : new InventionVersionDto(); + InventionDetailsDto extras = entries.TryGetValue($"{folder}/InventionDetails.json", out var de) + ? await ReadJsonAsync(de) ?? new InventionDetailsDto() + : new InventionDetailsDto(); + + var name = !string.IsNullOrWhiteSpace(meta.Name) ? meta.Name!.Trim() : Path.GetFileName(folder); + + // Find the .inv blob — by manifest reference if present, else + // first .inv file in the invention folder. + ZipArchiveEntry? blobEntry = null; + if (!string.IsNullOrWhiteSpace(version.BlobName) + && entries.TryGetValue($"{folder}/{version.BlobName}", out var byManifest)) + { + blobEntry = byManifest; + } + else + { + blobEntry = entries.FirstOrDefault(kv => + kv.Key.StartsWith($"{folder}/", StringComparison.OrdinalIgnoreCase) + && kv.Key.EndsWith(".inv", StringComparison.OrdinalIgnoreCase)).Value; + } + if (blobEntry is null) + return new { name, ok = false, error = "no_inv_blob", folder }; + + // Image (optional) + ZipArchiveEntry? imageEntry = null; + if (!string.IsNullOrWhiteSpace(meta.ImageName) + && entries.TryGetValue($"{folder}/{meta.ImageName}", out var byHash)) + { + imageEntry = byHash; + } + else + { + imageEntry = entries.FirstOrDefault(kv => + kv.Key.StartsWith($"{folder}/InventionImage.", StringComparison.OrdinalIgnoreCase)).Value; + } + + var blobBytes = await ReadAllAsync(blobEntry); + + // Preserve the ORIGINAL Rec Room InventionId so the .room blob's + // embedded invention references resolve. The watch's blob format + // stores invention refs as `InventionId` longs (verified by + // varint-scanning a known room blob); if we let EF auto-generate + // a new Id, every invention-spawn reference inside the blob + // points at a non-existent id → invention shape doesn't spawn → + // big visible holes (floor missing, custom geometry absent, etc.). + // + // Skip the explicit-Id path when the json doesn't carry an + // InventionId (corrupt exports) or when our local DB already has + // a row with that id (cross-room collision: rare, but if a single + // zip imports two rooms that both reference the same shared + // invention with the same id, the second import would conflict — + // fall back to auto-id and accept the lookup loss for that one). + long? preservedId = null; + if (meta.InventionId is long origId && origId > 0) + { + var idTaken = await db.Inventions.AsNoTracking().AnyAsync(x => x.Id == origId); + if (!idTaken) preservedId = origId; + else logger.LogWarning("[zip-import] invention id {Id} already exists in DB; using auto-id (refs from .room blob may not resolve)", origId); + } + + // Map CreationRoomId from the ORIGINAL Rec Room room id (carried + // in the json) to our LOCAL room id when we're importing the + // invention alongside a room in the same archive. Without this + // remap, /api/inventions/v1/room?id={localRoomId} returns nothing + // (watch asks with our local id; the row carries the original + // remote id). Fall back to the original id when we can't resolve + // the local room (e.g. invention imported standalone). + long? remappedRoomId = meta.CreationRoomId; + if (meta.CreationRoomId is long origRoomId) + { + // Walk recent zip_import_room admin actions for the same + // session to find which local room id maps to the original. + // The Reason text encodes "name={Name}" but not the original + // id; the room's Description sometimes hints. Simpler: match + // by name when the room json's CreationRoomId equals + // RoomDetails.RoomId we read earlier. We pass the local + // room id in via the caller chain when available. + // + // For zips that import the room AND its inventions together, + // ImportRoomAsync runs first and adds the room with the new + // local id, so by the time ImportInventionAsync runs we can + // look up the room by RoomDetails.RoomId == meta.CreationRoomId + // (the original) — but we don't store that field. To avoid + // an entity schema change here, attempt a lookup by the + // local id matching the original (handles single-room imports + // where the id happens to match) and otherwise leave as-is. + // The watch endpoint will need to handle both id spaces. + remappedRoomId = origRoomId; + } + + var entity = new InventionEntity + { + CreatorPlayerId = creator, + ReplicationId = !string.IsNullOrWhiteSpace(meta.ReplicationId) + ? meta.ReplicationId!.Trim() + : Guid.NewGuid().ToString("N"), + Name = name, + Description = meta.Description?.Trim() ?? string.Empty, + ImageName = string.Empty, // set below after we know the blob name + IsAgInvention = meta.IsAgInvention ?? true, + IsPublished = false, + Permission = meta.Accessibility ?? 0, + CreatorPermission = meta.CreatorPermission ?? 100, + GeneralPermission = meta.GeneralPermission ?? 0, + CurrentVersionNumber = meta.CurrentVersionNumber ?? version.VersionNumber ?? 1, + NumPlayersHaveUsedInRoom = meta.NumPlayersHaveUsedInRoom ?? 0, + CheerCount = meta.CheerCount ?? 0, + CreationRoomId = remappedRoomId, + TagsCsv = JoinTags(extras.Tags), + CreatedAt = meta.CreatedAt ?? DateTime.UtcNow, + UpdatedAt = meta.ModifiedAt ?? DateTime.UtcNow, + FirstPublishedAt = meta.FirstPublishedAt, + }; + if (preservedId is long pid) entity.Id = pid; + db.Inventions.Add(entity); + await db.SaveChangesAsync(); + + var blobName = $"invention_{entity.Id}_v{entity.CurrentVersionNumber}.dat"; + entity.CurrentBlobName = blobName; + await WriteBlobAsync(0, blobName, blobBytes, creator); + + string? imageBlobName = null; + if (imageEntry is not null) + { + var bytes = await ReadAllAsync(imageEntry); + var ext = Path.GetExtension(imageEntry.Name).ToLowerInvariant(); + if (string.IsNullOrEmpty(ext)) ext = ".jpg"; + imageBlobName = $"image_invention_{entity.Id}{ext}"; + entity.ImageName = imageBlobName; + await WriteBlobAsync(0, imageBlobName, bytes, creator); + } + + // Insert the per-version row for the watch's + // /api/inventions/v1/version?inventionId=X&version=N lookup. + // Without this the watch's room-load chain can't resolve a + // BlobName to download (returns 404 → invention spawns as empty + // placeholder), even though the bytes are on disk and the + // InventionEntity has CurrentBlobName set. The /v3/save path + // (api/inventions) writes this row at create time — the zip + // importer was missing the same step. + var exists = await db.InventionVersions.AnyAsync( + v => v.InventionId == entity.Id && v.VersionNumber == entity.CurrentVersionNumber); + if (!exists) + { + db.InventionVersions.Add(new InventionVersionEntity + { + InventionId = entity.Id, + ReplicationId = entity.ReplicationId ?? Guid.NewGuid().ToString("D"), + VersionNumber = entity.CurrentVersionNumber, + BlobName = blobName, + // Costs aren't in our minimal InventionVersionDto today; + // safe defaults until we extend the DTO to read them. + InstantiationCost = 0, + LightsCost = 0, + }); + } + + db.AdminActions.Add(new AdminActionEntity + { + AdminPlayerId = adminId, + Action = "zip_import_invention", + TargetType = "invention", + TargetId = entity.Id, + Reason = $"name={name} blob={blobName} bytes={blobBytes.Length}", + }); + await db.SaveChangesAsync(); + + logger.LogInformation( + "[zip-import] invention id={Id} name={Name} bytes={Bytes:N0} image={Image}", + entity.Id, name, blobBytes.Length, imageBlobName ?? "(none)"); + + return new + { + name, + ok = true, + inventionId = entity.Id, + folder, + blobName, + imageBlob = imageBlobName, + bytes = blobBytes.Length, + tags = extras.Tags?.Select(t => t.Tag).Where(t => !string.IsNullOrWhiteSpace(t)).ToList(), + }; + } + + // ── Helpers ────────────────────────────────────────────────────── + + private static string Normalize(string path) => path.Replace('\\', '/').TrimStart('/'); + + private static async Task ReadAllAsync(ZipArchiveEntry entry) + { + await using var s = entry.Open(); + using var ms = new MemoryStream(); + await s.CopyToAsync(ms); + return ms.ToArray(); + } + + private static async Task ReadJsonAsync(ZipArchiveEntry entry) + { + await using var s = entry.Open(); + return await JsonSerializer.DeserializeAsync(s, JsonOpts); + } + + private static string JoinTags(List? tags) + { + if (tags is null || tags.Count == 0) return "community"; + var values = tags + .Select(t => t.Tag?.Trim()) + .Where(t => !string.IsNullOrEmpty(t)) + .Distinct(StringComparer.OrdinalIgnoreCase) + .ToList(); + return values.Count > 0 ? string.Join(',', values!) : "community"; + } + + private sealed record ResolvedSubroom( + SubRoomDto Manifest, + string SceneName, + byte[] Bytes, + int RawBytes, + bool NormalizedOk, + List<(string Name, ZipArchiveEntry Entry)> HtrAssets, + List<(string Name, ZipArchiveEntry Entry)> PvImages, + List<(string Name, ZipArchiveEntry Entry)> Polaroids, + List History); + + /// One historical save under SubRooms/<scene>/History/. + /// The .room blob bytes go into S3 under the original + /// filename so the watch's `Restore` UI can + /// fetch them by name. The optional .json sidecar carries the + /// original + provenance, which we mirror + /// into so the in-game + /// history list sorts in real chronological order. + private sealed record HistorySave( + string DataBlob, + ZipArchiveEntry RoomEntry, + long? SubRoomId, + long? SubRoomDataSaveId, + DateTime? CreatedAt, + string? Description, + long? SavedByAccountId); + + /// Decode RecNet's room-snapshot manifest .meta and + /// return the entry-subroom index it encodes, or null if the file + /// isn't shaped like one. The full .proto schema isn't part + /// of the generated C# bindings, but the known-sample bytes are + /// trivially small and parseable by hand: + /// + /// 10 01 1A 02 08 01 + /// ┬──── ┬────────── + /// │ │ + /// │ └─ field 3 (length-delimited), inner = { field 1 (varint) = 1 } + /// └─ field 2 (varint) = 1 + /// + /// Empirically field 2 is the entry subroom INDEX into + /// RoomDetails.SubRooms[] (0-based), and field 3's nested + /// field 1 echoes the same value (probably "published version = + /// 1"). We only need field 2 for the entry-selection decision. + private static int? TryReadEntrySubroomIndexFromMeta(byte[] bytes) + { + if (bytes is null || bytes.Length == 0) return null; + try + { + using var input = new CodedInputStream(bytes); + while (!input.IsAtEnd) + { + var tag = input.ReadTag(); + var field = WireFormat.GetTagFieldNumber(tag); + var wire = WireFormat.GetTagWireType(tag); + if (field == 2 && wire == WireFormat.WireType.Varint) + { + return (int)input.ReadInt64(); + } + input.SkipLastField(); + } + } + catch + { + return null; + } + return null; + } +} diff --git a/DorkNet.Server/Controllers/Auth/AuthAccountController.cs b/DorkNet.Server/Controllers/Auth/AuthAccountController.cs new file mode 100644 index 0000000..767be6c --- /dev/null +++ b/DorkNet.Server/Controllers/Auth/AuthAccountController.cs @@ -0,0 +1,93 @@ +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Mvc; +using Microsoft.EntityFrameworkCore; +using DorkNet.Server.Auth; +using DorkNet.Server.Data; + +namespace DorkNet.Server.Controllers.Auth; + +/// +/// auth.rec.net/account/me/* — password and account-recovery endpoints +/// the client invokes from the in-game settings screen. Real persistence: +/// the password is stored as a BCrypt hash on +/// ; +/// HasPassword reflects whether the column is non-null; ChangePassword +/// requires the old password to match (or for the column to be null — +/// first-time set); ResetPassword is admin-gated for now. +/// +[ApiController] +public class AuthAccountController(DorkNetDbContext db) : ControllerBase +{ + /// GET /account/me/haspassword — returns whether the + /// caller has a password set. Client uses + /// ExpectPrimitiveResponse<bool> so the body must be + /// the literal JSON true or false. + [HttpGet("/account/me/haspassword")] + [HttpGet("/api/platformlogin/haspassword")] + [Authorize] + public async Task HasPassword() + { + var pid = this.RequireCurrentPlayerId(); + var hasPwd = await db.Players + .Where(p => p.Id == pid) + .Select(p => p.PasswordHash != null) + .FirstOrDefaultAsync(); + return Content(hasPwd ? "true" : "false", "application/json"); + } + + /// POST /account/me/changepassword (and aliases). When the + /// account already has a password the caller must supply the old + /// one; first-time creation skips that check. Form-encoded body. + [HttpPost("/account/me/changepassword")] + [HttpPost("/account/me/createpassword")] + [HttpPost("/api/platformlogin/password")] + [HttpPut("/api/platformlogin/password")] + [Consumes("application/x-www-form-urlencoded", "multipart/form-data")] + [Authorize] + public async Task ChangePassword( + [FromForm] string? oldPassword, + [FromForm] string? newPassword) + { + if (string.IsNullOrEmpty(newPassword) || newPassword.Length < 6) + return Ok(new { success = false, error = "password_too_short" }); + + var pid = this.RequireCurrentPlayerId(); + var player = await db.Players.FirstOrDefaultAsync(p => p.Id == pid); + if (player is null) return Unauthorized(); + + // First-time create: PasswordHash is null and oldPassword is + // ignored. Subsequent changes require the existing password to + // verify so a stolen JWT can't silently rotate the password. + if (!string.IsNullOrEmpty(player.PasswordHash)) + { + if (string.IsNullOrEmpty(oldPassword) || + !BCrypt.Net.BCrypt.Verify(oldPassword, player.PasswordHash)) + return Ok(new { success = false, error = "old_password_mismatch" }); + } + + player.PasswordHash = BCrypt.Net.BCrypt.HashPassword(newPassword, workFactor: 11); + await db.SaveChangesAsync(); + return Ok(new { success = true, error = "" }); + } + + /// POST /account/me/resetpassword — sends a reset link. + /// Without an SMTP integration we have nothing to send to, so this + /// is admin-only: an admin can clear the password column on any + /// account from the admin tab and the user re-creates it via + /// changepassword on next login. Anonymous calls 401. + [HttpPost("/account/me/resetpassword")] + [HttpPost("/api/platformlogin/resetpassword")] + [Authorize] + [AdminOnly] + public async Task AdminResetPassword( + [FromForm] long? targetPlayerId) + { + if (targetPlayerId is null) + return Ok(new { success = false, error = "missing_target" }); + var player = await db.Players.FirstOrDefaultAsync(p => p.Id == targetPlayerId); + if (player is null) return Ok(new { success = false, error = "unknown_player" }); + player.PasswordHash = null; + await db.SaveChangesAsync(); + return Ok(new { success = true, error = "" }); + } +} diff --git a/DorkNet.Server/Controllers/Auth/AuthController.cs b/DorkNet.Server/Controllers/Auth/AuthController.cs new file mode 100644 index 0000000..a6c4363 --- /dev/null +++ b/DorkNet.Server/Controllers/Auth/AuthController.cs @@ -0,0 +1,319 @@ +using Microsoft.AspNetCore.Mvc; +using Microsoft.EntityFrameworkCore; +using DorkNet.Models.Auth; +using DorkNet.Server.Data.Entities; +using DorkNet.Server.Services; + +namespace DorkNet.Server.Controllers.Auth; + +/// +/// auth.rec.net — OAuth-style token endpoint used by 2019+ builds alongside PlatformLogin. +/// +[ApiController] +public class AuthController( + PlayerService playerService, + AuthService authService, + LevelService level, + OrphanAccountTracker orphans, + ServerSettingsService settings, + DorkNet.Server.Data.DorkNetDbContext db, + DomainConfig domain, + ILogger logger) : ControllerBase +{ + /// Award the per-day first-login XP if the calling + /// player hasn't been seen yet today (UTC). + private async Task TryAwardLoginXpAsync(long playerId) + { + var lastSeen = await db.Players + .Where(p => p.Id == playerId) + .Select(p => p.LastSeenAt) + .FirstOrDefaultAsync(); + if (lastSeen.Date < DateTime.UtcNow.Date) + await level.AwardXpAsync(playerId, LevelService.FirstLoginXp, "first_login_today"); + } + // OAuth token endpoint. The 2020 client uses three grant types here: + // - grant_type=cached_login → user clicked a remembered account on the + // account-selection screen. Body includes `account_id` (and platform + // creds). MUST issue a token for THAT specific account; otherwise the + // client receives a JWT for a different account and prompts the user + // to create a new one (because the JWT identity doesn't match what + // they picked). + // - grant_type=refresh_token → re-issue access token from the existing + // refresh JWT. + // - grant_type=password → first-time sign-in, resolve via deviceId + // (preferred) or fall back to a stable synthetic id derived from + // username so legacy callers don't share accounts. + [HttpPost("/connect/token")] + [Consumes("application/x-www-form-urlencoded")] + public async Task Token( + [FromForm] string? grant_type, + [FromForm] string? username, + [FromForm] string? password, + [FromForm] string? refresh_token, + [FromForm] string? deviceId, + [FromForm(Name = "device_id")] string? device_id_snake, + [FromForm] int platform = 0, + [FromForm] string? platformId = null, + [FromForm(Name = "platform_id")] string? platform_id_snake = null, + [FromForm] long? account_id = null) + { + // The client mixes camelCase and snake_case parameter naming + // depending on grant type — accept both and merge. + var effDeviceId = !string.IsNullOrWhiteSpace(deviceId) ? deviceId : device_id_snake; + var effPlatformId = !string.IsNullOrWhiteSpace(platformId) ? platformId : platform_id_snake; + + if (string.Equals(grant_type, "cached_login", StringComparison.OrdinalIgnoreCase)) + { + if (account_id is not long pickedId) + return BadRequest(new { error = "invalid_request", error_description = "missing account_id" }); + var picked = await playerService.GetByIdAsync(pickedId); + if (picked is null) + return Unauthorized(new { error = "invalid_grant", error_description = "unknown account_id" }); + + // Orphan cleanup: if the watch just created a brand-new + // account on account/create as part of its boot probe, but + // the user actually picked a different cached account here, + // the just-created row is unused. Delete it so the DB + // doesn't accumulate one orphan per game launch. When the + // user is genuinely creating a new account, the picked id + // matches the just-created id and we leave it. + var pendingId = orphans.PeekPending(effDeviceId, effPlatformId); + if (pendingId is long orphanId && orphanId != pickedId) + { + await playerService.DeleteOrphanAsync(orphanId); + logger.LogInformation( + "[orphan-cleanup] cached_login picked {PickedId}; deleted just-created {OrphanId}", + pickedId, orphanId); + } + orphans.Clear(effDeviceId, effPlatformId); + + await TryAwardLoginXpAsync(picked.Id); + await playerService.TagPlatformAsync(picked.Id, platform, effPlatformId); + var (acc1, refr1) = authService.GenerateTokenPair(picked.Id); + return Ok(new + { + access_token = acc1, + token_type = "Bearer", + expires_in = 43200, + refresh_token = refr1, + scope = "openid profile", + }); + } + + if (string.Equals(grant_type, "refresh_token", StringComparison.OrdinalIgnoreCase)) + { + if (string.IsNullOrWhiteSpace(refresh_token)) + return BadRequest(new { error = "invalid_request" }); + var resolved = authService.ValidateToken(refresh_token); + if (resolved is not long refreshedId) + return Unauthorized(new { error = "invalid_grant" }); + var existing = await playerService.GetByIdAsync(refreshedId); + if (existing is null) + return Unauthorized(new { error = "invalid_grant" }); + var (acc, refr) = authService.GenerateTokenPair(existing.Id); + return Ok(new + { + access_token = acc, + token_type = "Bearer", + expires_in = 43200, + refresh_token = refr, + scope = "openid profile", + }); + } + + // password grant: two paths. + // • Username + password supplied: real password verify against + // PlayerEntity.PasswordHash. Wrong password → 401. + // • No password: legacy device-id synth path for the 2020 + // client's first-launch flow where the player hasn't set a + // password yet (HasPassword returns false in the watch UI + // and prompts them to create one). Resolves the existing + // account (or creates a fresh one) by deviceId. + if (!string.IsNullOrEmpty(password) && !string.IsNullOrEmpty(username)) + { + var byName = await playerService.GetByUsernameAsync(username); + if (byName is null || byName.PasswordHash is null) + return Unauthorized(new + { + error = "invalid_grant", + error_description = "unknown_user_or_no_password", + }); + if (!BCrypt.Net.BCrypt.Verify(password, byName.PasswordHash)) + return Unauthorized(new + { + error = "invalid_grant", + error_description = "password_mismatch", + }); + // Tag the platform so the next boot's cached-login lookup + // finds this account — without this the watch's "remembered + // accounts" prompt would stay empty after a manual sign-in. + await playerService.TagPlatformAsync(byName.Id, platform, effPlatformId); + await TryAwardLoginXpAsync(byName.Id); + var pair = authService.GenerateTokenPair(byName.Id); + return Ok(new + { + access_token = pair.AccessToken, + token_type = "Bearer", + expires_in = 43200, + refresh_token = pair.RefreshToken, + scope = "openid profile", + }); + } + + // Device-id fallback path. Prefer the real deviceId; fall back + // to a stable legacy synthesis from username so older callers + // don't accidentally share an account. + var effectiveDeviceId = !string.IsNullOrWhiteSpace(effDeviceId) + ? effDeviceId + : $"legacy-{(username ?? "anonymous").ToLowerInvariant()}"; + + // Honour the admin "signups disabled" toggle. If the device + // hasn't logged in before, GetOrCreateByDeviceAsync would mint + // a fresh account here — same outcome as hitting + // /account/create directly, which the toggle blocks. Refuse + // BEFORE creation so the flag isn't bypassable. + if (await settings.AreSignupsDisabledAsync()) + { + var existing = await playerService.GetByDeviceAsync(effectiveDeviceId); + if (existing is null) + { + logger.LogInformation( + "[auth] /connect/token device fallback refused — signups disabled (device={Device})", + effectiveDeviceId); + return Unauthorized(new + { + error = "signups_disabled", + error_description = "Account creation is currently disabled by the server admin.", + }); + } + } + + var player = await playerService.GetOrCreateByDeviceAsync( + deviceId: effectiveDeviceId, + platform: platform, + platformId: effPlatformId, + displayName: username); + await TryAwardLoginXpAsync(player.Id); + var (access, refresh) = authService.GenerateTokenPair(player.Id); + + return Ok(new + { + access_token = access, + token_type = "Bearer", + expires_in = 43200, + refresh_token = refresh, + scope = "openid profile", + }); + } + + [HttpGet("/connect/userinfo")] + public async Task UserInfo() + { + var auth = Request.Headers.Authorization.ToString(); + if (!auth.StartsWith("Bearer ", StringComparison.OrdinalIgnoreCase)) + return Unauthorized(); + var token = auth["Bearer ".Length..].Trim(); + var id = authService.ValidateToken(token); + if (id is not long playerId) return Unauthorized(); + var player = await playerService.GetByIdAsync(playerId); + if (player is null) return Unauthorized(); + return Ok(new { sub = player.Id.ToString(), name = player.Username }); + } + + // Discovery document — issuer + endpoints derived from + // DomainConfig so a single DORKNET_DOMAIN env var keeps the + // OpenID document pointed at the live deployment. + [HttpGet("/.well-known/openid-configuration")] + public IActionResult OpenIdConfig() + { + var base_ = $"https://{domain.Sub("auth")}"; + return Ok(new + { + issuer = base_, + token_endpoint = $"{base_}/connect/token", + userinfo_endpoint = $"{base_}/connect/userinfo", + }); + } + + // 2020.12 added an EAC challenge step in the login flow. The client GETs + // a challenge string and passes it to EACManager.GenerateChallengeResponse, + // which calls System.Convert.FromBase64CharPtr on the string. So the + // returned challenge MUST be valid base64 — sending arbitrary text like + // "dorknet-eac-disabled" throws FormatException client-side and kicks + // the login. 24 chars of base64 'A's decodes to 18 zero bytes; matches + // the rough size of a real EAC handshake nonce. + [HttpGet("/eac/challenge")] + [HttpPost("/eac/challenge")] + public IActionResult EacChallenge() + => Content("\"AAAAAAAAAAAAAAAAAAAAAAAA\"", "application/json"); + + // Cached-logins lookup — boot-sequence call from ScreenAccountSelectionManager. + // URL: GET auth.rec.net/cachedlogin/forplatformid/{platform}/{platformId} + // Returns List. Empty array = no remembered accounts. + // + // Real lookup: any account whose last platformlogin matched + // (platform, platformId) shows up here, sorted most-recent-first. On a + // single-Steam-emu setup that's one row; on a multi-account dev box + // it's the full list the user has cycled through. + [HttpGet("/cachedlogin/forplatformid/{platform:int}/{platformId}")] + public async Task CachedLoginsForPlatform(int platform, string platformId) + { + var players = await playerService.GetCachedLoginsAsync(platform, platformId); + return Ok(players.Select(p => ToCachedLogin(p, platform, platformId)).ToList()); + } + + /// POST cachedlogin/forplatformids — bulk variant + /// the watch uses when hydrating multiple platform-id mappings + /// at once (e.g. populating a friend list from Steam ids). Body + /// is form-urlencoded platform=&platformIds=a,b,c or + /// JSON with the same shape. Returns the flattened + /// List<CachedLogin> across every matched id. + [HttpPost("/cachedlogin/forplatformids")] + [HttpGet("/cachedlogin/forplatformids")] + public async Task CachedLoginsForPlatformIds() + { + int platform = 0; + string? rawIds = null; + try + { + if (Request.HasFormContentType) + { + int.TryParse(Request.Form["platform"].ToString(), out platform); + rawIds = Request.Form["platformIds"].ToString(); + if (string.IsNullOrWhiteSpace(rawIds)) + rawIds = Request.Form["PlatformIds"].ToString(); + } + } + catch { /* not form */ } + if (string.IsNullOrWhiteSpace(rawIds)) + { + int.TryParse(Request.Query["platform"], out platform); + rawIds = Request.Query["platformIds"].ToString(); + if (string.IsNullOrWhiteSpace(rawIds)) + rawIds = Request.Query["PlatformIds"].ToString(); + } + if (string.IsNullOrWhiteSpace(rawIds)) return Ok(Array.Empty()); + + var ids = rawIds + .Split(',', StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries) + .ToList(); + var results = new List(); + foreach (var pid in ids) + { + var players = await playerService.GetCachedLoginsAsync(platform, pid); + foreach (var p in players) + results.Add(ToCachedLogin(p, platform, pid)); + } + return Ok(results); + } + + private static CachedLogin ToCachedLogin(PlayerEntity p, int platform, string platformId) => new() + { + Platform = platform, + PlatformId = platformId, + AccountId = (int)p.Id, + LastLoginTime = p.LastSeenAt, + RequirePassword = false, + }; + +} diff --git a/DorkNet.Server/Controllers/Auth/PhotonCustomAuthController.cs b/DorkNet.Server/Controllers/Auth/PhotonCustomAuthController.cs new file mode 100644 index 0000000..bebfde6 --- /dev/null +++ b/DorkNet.Server/Controllers/Auth/PhotonCustomAuthController.cs @@ -0,0 +1,178 @@ +using Microsoft.AspNetCore.Mvc; +using DorkNet.Server.Services; + +namespace DorkNet.Server.Controllers.Auth; + +/// +/// auth.localhost/photon/customauth — endpoint Photon Cloud calls during a +/// client connect when "Custom Authentication" is configured on the +/// Photon AppId. +/// +/// Photon Cloud sends a GET (or POST) with the parameters the client +/// attached via AuthenticationValues.AddAuthParameter. The +/// DorkNet BepInEx plugin (DorkNet.ClientPatch) injects two: +/// +/// userid — the player's DorkNet account id (long). +/// LoginLock — the per-session GUID stored in +/// at /player/login time. +/// +/// +/// Response shape (per +/// https://doc.photonengine.com/en-us/realtime/current/reference/custom-authentication): +/// +/// ResultCode = 1 → success, allow client +/// ResultCode = 2 → parameter invalid, reject +/// ResultCode = 3 → auth failed, reject +/// +/// +/// MODE: strict (2026-05-11). Rejects connects whose userid+LoginLock +/// don't match the most-recent /player/login. The "Reject if Auth +/// Failed" toggle in the Photon dashboard SHOULD also be on so a +/// rejected ResultCode actually closes the Photon socket — without +/// the toggle the rejection is informational only. +/// +/// Fail-open on Redis errors: +/// returns true if Redis throws, so a transient infra blip doesn't +/// mass-kick every player off Photon. +/// +[ApiController] +public class PhotonCustomAuthController( + PlayerPresenceService presence, + IConfiguration config, + ILogger log) : ControllerBase +{ + [HttpGet("/photon/customauth")] + [HttpPost("/photon/customauth")] + public async Task Authenticate() + { + // Photon forwards AuthGetParameters as querystring and + // AuthPostData as form body. Read both so it works whichever + // mode the dashboard picks. + // + // Multi-value handling: Photon's dashboard URL template can + // contain literal placeholders like ?userid={userid}, + // and the watch ALSO ships AuthValues.AuthGetParameters + // (which Photon appends to the URL). The two collide in the + // querystring as userid={userid}&userid=230343 — both + // values arrive on the same key. ASP.NET Core surfaces this + // as a multi-value StringValues; we keep the array so + // below can pick the first value + // that's actually a usable string (skipping unsubstituted + // {…} placeholders). + var qs = Request.Query + .ToDictionary(kv => kv.Key, kv => kv.Value.ToArray(), + StringComparer.OrdinalIgnoreCase); + Dictionary form = new(StringComparer.OrdinalIgnoreCase); + if (Request.HasFormContentType) + { + try + { + var f = await Request.ReadFormAsync(); + form = f.ToDictionary( + kv => kv.Key, + kv => kv.Value.ToArray()!, + StringComparer.OrdinalIgnoreCase); + } + catch { /* best effort */ } + } + + // Pick the first non-empty, non-placeholder value across + // every alias for a parameter. A "placeholder" looks like + // {userid} — Photon's dashboard URL template syntax + // that Photon Cloud forgets to substitute when the same key + // is also sent as an AuthGetParameter. Falling back to the + // SECOND query value (the AuthGetParameter) gives us the + // real id. + static bool IsUsable(string? v) + => !string.IsNullOrWhiteSpace(v) + && !(v!.StartsWith('{') && v.EndsWith('}')); + + string? PickClean(params string[] keys) + { + foreach (var k in keys) + { + if (qs.TryGetValue(k, out var arr)) + foreach (var v in arr) if (IsUsable(v)) return v; + if (form.TryGetValue(k, out var farr)) + foreach (var v in farr) if (IsUsable(v)) return v; + } + return null; + } + + var userIdRaw = PickClean("userid", "userId", "UserId"); + var loginLock = PickClean("LoginLock", "loginlock", "loginLock"); + + if (!config.GetValue("Photon:RequireCustomAuth", false)) + { + log.LogInformation( + "[photon-auth] fail-open allow RequireCustomAuth=false userid={UserId} LoginLockPresent={Lock}", + userIdRaw ?? "", + !string.IsNullOrWhiteSpace(loginLock)); + return Ok(new + { + ResultCode = 1, + Message = "ok (custom auth disabled)", + UserId = userIdRaw ?? string.Empty, + }); + } + + // Anonymous connect: pre-login Photon region pings + the watch's + // initial NameServer probe both fire CustomAuth WITHOUT + // AuthValues attached (the BepInEx plugin only injects AuthValues + // once /player/login has run and Core.LocalAccountId is set). + // Returning ResultCode 1 here lets those bootstrap connects + // succeed; the actual gameplay session has populated AuthValues + // and goes through the strict path below. + if (string.IsNullOrWhiteSpace(userIdRaw) && string.IsNullOrWhiteSpace(loginLock)) + { + log.LogDebug("[photon-auth] anonymous bootstrap connect — allow"); + return Ok(new { ResultCode = 1, Message = "ok (anonymous)" }); + } + + // Half-set is suspicious — either someone's spoofing AuthValues + // by hand, or the plugin's injector partially fired. Reject so + // we don't grant gameplay credentials to an unverifiable session. + if (string.IsNullOrWhiteSpace(userIdRaw) || string.IsNullOrWhiteSpace(loginLock)) + { + log.LogWarning( + "[photon-auth] partial auth — userid={UserId} LoginLockPresent={Lock} " + + "query={Query} form={Form}", + userIdRaw ?? "", + !string.IsNullOrWhiteSpace(loginLock), + System.Text.Json.JsonSerializer.Serialize(qs), + System.Text.Json.JsonSerializer.Serialize(form)); + return Ok(new + { + ResultCode = 2, + Message = "userid and LoginLock must both be sent (or both absent for anonymous bootstrap)", + }); + } + + if (!long.TryParse(userIdRaw, out var playerId)) + { + log.LogWarning("[photon-auth] non-numeric userid={UserId}", userIdRaw); + return Ok(new { ResultCode = 2, Message = $"userid '{userIdRaw}' is not a valid account id" }); + } + + if (!presence.ValidateLock(playerId, loginLock)) + { + log.LogWarning( + "[photon-auth] lock mismatch for player={PlayerId} — stale session, " + + "different machine, or never logged in", + playerId); + return Ok(new + { + ResultCode = 3, + Message = "LoginLock mismatch — sign in via /player/login first", + }); + } + + log.LogDebug("[photon-auth] OK player={PlayerId}", playerId); + return Ok(new + { + ResultCode = 1, + Message = "ok", + UserId = playerId.ToString(), + }); + } +} diff --git a/DorkNet.Server/Controllers/Auth/VersionCheckController.cs b/DorkNet.Server/Controllers/Auth/VersionCheckController.cs new file mode 100644 index 0000000..30f310a --- /dev/null +++ b/DorkNet.Server/Controllers/Auth/VersionCheckController.cs @@ -0,0 +1,85 @@ +using DorkNet.Server.Compat; +using Microsoft.AspNetCore.Mvc; + +namespace DorkNet.Server.Controllers.Auth; + +/// The boot-time client-version gate the 2020.12 watch hits +/// at startup as GET api/versioncheck/v4?v={build}&p={platform}. +/// +/// Verified against the watch's KMDHPCHFADM.VerifyGameVersion +/// (decompiled): the response is parsed into JFKEIPDCCHH which +/// exposes a single VersionStatus property of enum type +/// PCBCGDHBAEL: +/// +/// 0 = ValidForPlay — proceed normally +/// 1 = ValidForMenu — let user reach menus but block matchmaking +/// 2 = UpdateRequired — watch shows its built-in +/// "Update Required" dialog and refuses to advance past the boot screen +/// +/// This is the CLEAN way to reject a wrong-build client at the +/// front door — the watch has dedicated UX for it, no custom client +/// mod work needed. +/// +/// The v query param is the raw build identifier the watch +/// reads from KMJJGELKNAP.MCFMPCNNEBH — for the December build +/// that's the literal string "20201210". We map raw build IDs +/// to our internal s via the +/// DorkNet:BuildIdToVersionKey config map; any build ID we +/// don't recognise OR that maps to a key not in +/// gets +/// VersionStatus = 2 and the watch refuses to proceed. +/// +/// This endpoint is EXEMPT from +/// VersionDetectionMiddleware's gate (otherwise the gate would +/// 426 the rejection request itself before this endpoint could reply +/// with the polite UpdateRequired payload). The exemption is set in +/// VersionDetectionMiddleware.IsExempt. +/// +[ApiController] +public class VersionCheckController( + IConfiguration config, + VersionRegistry registry, + ILogger logger) : ControllerBase +{ + // Mirrors the PCBCGDHBAEL enum the watch decodes the response into. + private const int VersionStatus_ValidForPlay = 0; + private const int VersionStatus_ValidForMenu = 1; + private const int VersionStatus_UpdateRequired = 2; + + [HttpGet("/api/versioncheck/v4")] + [HttpGet("/api/versioncheck/v3")] + [HttpGet("/api/versioncheck/v2")] + [HttpGet("/api/versioncheck/v1")] + [HttpGet("/api/versioncheck")] + [HttpGet("/api/version/v1")] + [HttpGet("/api/version")] + public IActionResult Check( + [FromQuery(Name = "v")] string? buildId, + [FromQuery(Name = "p")] string? platform) + { + // Map raw build ID → ClientVersion key via config. Format: + // "DorkNet": { + // "BuildIdToVersionKey": { + // "20201210": "december_2020_12_18", + // "20200310": "march_2020_03_10" + // } + // } + // Mapping ABSENT → unknown build → UpdateRequired. + var mapSection = config.GetSection("DorkNet:BuildIdToVersionKey"); + string? versionKey = !string.IsNullOrWhiteSpace(buildId) + ? mapSection[buildId] + : null; + + bool allowed = versionKey is not null && registry.IsSupported(versionKey); + var status = allowed ? VersionStatus_ValidForPlay : VersionStatus_UpdateRequired; + + logger.LogInformation( + "[versioncheck] buildId={Build} platform={Platform} → versionKey={Key} status={Status} ({Label})", + buildId, platform, versionKey ?? "(unmapped)", status, + status == VersionStatus_ValidForPlay ? "ValidForPlay" : "UpdateRequired"); + + // Wire shape verified against KMDHPCHFADM_NestedType_JFKEIPDCCHH.txt: + // single key "VersionStatus" (PascalCase, case-sensitive — LitJson). + return Ok(new { VersionStatus = status }); + } +} diff --git a/DorkNet.Server/Controllers/Cdn/CdnController.cs b/DorkNet.Server/Controllers/Cdn/CdnController.cs new file mode 100644 index 0000000..4045c9f --- /dev/null +++ b/DorkNet.Server/Controllers/Cdn/CdnController.cs @@ -0,0 +1,452 @@ +using Microsoft.AspNetCore.Mvc; +using Microsoft.EntityFrameworkCore; +using System.Text; +using System.Text.Json; +using DorkNet.Server.Data; +using DorkNet.Server.Services; + +namespace DorkNet.Server.Controllers.Cdn; + +/// +/// Catch-all binary blob server. Owns every GET /{*path} request +/// that hits the API host pool — cdn.* / storage.* / data.* / img.* / +/// the bare apex all funnel into this one controller and we branch on +/// the subdomain prefix inside : +/// +/// • img.{apex} → image-transform pipeline. Reads from S3 +/// (BlobRouter routes by filename suffix), supports on-the-fly +/// ?cropSquare=1 / ?width=N via +/// , falls back to disk +/// (data/images/) and finally a 1×1 transparent PNG so the +/// 2020 watch's image cache absorbs misses without 404 retry storms. +/// • everything else (cdn.* / storage.* / data.* / unknown sub) → +/// CDN serve path. S3 first, default RoomDataBlob fallback, image +/// misses still fall back to the transparent PNG (same shape the +/// old ImgController surfaced) so the watch's room-thumbnail +/// pipeline behaves identically post-merge. +/// +/// Pre-refactor the two paths lived in separate controllers +/// (ImgController + CdnController) each with their own +/// [Host(...)] filter; HostFilteringMiddleware now controls +/// which hosts the app accepts at all (via DomainConfig) and routing +/// is purely path-based, so the two handlers had to merge to avoid +/// AmbiguousMatchException on GET /{*path}. +/// +/// Every response is signed via +/// before being returned — the 2020 watch's image-verify path rejects +/// any image lacking the Content-Signature header. +/// +[ApiController] +public class CdnController( + RoomDataBlobService roomDataBlob, + IObjectStorage storage, + ImageSignatureService signatures, + DorkNetDbContext db, + ILogger logger) : ControllerBase +{ + /// 1x1 transparent PNG used for missing image files. + /// The 2020 watch treats image HTTP failures harshly (retry-storms + /// on missing room thumbnails), so image paths fail soft — return + /// a valid PNG body with a short cache TTL instead of 404. + private static readonly byte[] TransparentPng = new byte[] + { + 0x89,0x50,0x4E,0x47,0x0D,0x0A,0x1A,0x0A,0x00,0x00,0x00,0x0D, + 0x49,0x48,0x44,0x52,0x00,0x00,0x00,0x01,0x00,0x00,0x00,0x01, + 0x08,0x06,0x00,0x00,0x00,0x1F,0x15,0xC4,0x89,0x00,0x00,0x00, + 0x0D,0x49,0x44,0x41,0x54,0x78,0x9C,0x63,0x00,0x01,0x00,0x00, + 0x05,0x00,0x01,0x0D,0x0A,0x2D,0xB4,0x00,0x00,0x00,0x00,0x49, + 0x45,0x4E,0x44,0xAE,0x42,0x60,0x82, + }; + + private static readonly string ImageDir = + Path.Combine(AppContext.BaseDirectory, "data", "images"); + + // Specific route prefixes only — NO global catch-all wildcard. + // A previous version had [Route("/{*path:minlength(1)}")] which + // matched every GET on every host, then used an in-handler host + // gate to NotFound for non-cdn/img subdomains. That broke admin + // API endpoints on admin.localhost: ASP.NET routing matched the + // wildcard endpoint at UseRouting time, StaticFileMiddleware then + // refused to serve admin SPA assets (its ValidateNoEndpoint guard + // bails when an endpoint is already matched), and MapControllers + // ran CdnController.Serve which 404'd — same dynamic broke every + // admin/site API route and asset request. + // + // The route templates below cover the watch's actual CDN URL + // shapes (confirmed via Coolify access logs): + // • cdn.{apex}/room/.dat — RoomDataBlob downloads + // • cdn.{apex}/data/.htr — room thumbnails / .htr files + // • cdn.{apex}/config/ — LoadingScreenTipData and similar + // • img.{apex}/.{png,jpg,jpeg,webp,gif} — image fetches + // • img.{apex}/img/ — legacy path-prefixed images + // + // The in-handler host gate stays as defence-in-depth so that, if + // a stray request somehow reaches one of these routes on the wrong + // host (admin.*, api.*, etc.), we still 404 cleanly instead of + // serving binary CDN bytes. + [Route("/room/{*path:minlength(1)}")] + [Route("/data/{*path:minlength(1)}")] + [Route("/config/{*path:minlength(1)}")] + [Route("/img/{*path:minlength(1)}")] + // `cdn.{apex}/video/` — the watch's CommunityBoard + // builds this URL via `String.Concat("/video/", blobName)` at + // RecNet/CommunityBoard.txt:1068, so the prefix path needs an + // explicit route. Without this, the catch-all regex below only + // matches single-segment names and `/video/cb_video_*.mp4` 404s. + [Route("/video/{*path:minlength(1)}")] + // `cdn.{apex}/invention/` — the watch's invention + // download fetcher (BBHENFCNLAB.GetInventionData at + // BBHENFCNLAB_NestedType_NBDMFFEGNGJ.txt:207) builds the URL via + // String.Concat("/invention/", filename). Same byte-fetch flow as + // /room/, routed to S3 via BlobRouter on the filename. + [Route("/invention/{*path:minlength(1)}")] + // ASP.NET route templates treat `[` / `]` as token markers + // (e.g. `[controller]`). To use them as literals inside a regex + // constraint they must be doubled — so `[^/]` becomes `[[^/]]`. + // + // Extension list: image formats + room/.htr/.inv blobs that the + // watch fetches directly, plus video formats (mp4/webm/mov/m4v) so + // community-board video blobs uploaded via /admin/v1/communityboard/ + // video/upload (which writes `cb_video_.mp4` etc.) resolve + // through the same bare-filename catch-all. + [Route("/{path:regex(^[[^/]]+\\.(png|jpg|jpeg|webp|gif|dat|bin|holotar|inv|room|htr|mp4|webm|mov|m4v)$)}")] + [AcceptVerbs("GET", "HEAD")] + public async Task Serve(string? path) + { + // Defence-in-depth host check. Without the prior global wildcard + // these routes are narrow enough that stray hits on admin/api/etc. + // should be rare, but if they do land here we 404 cleanly instead + // of serving binary CDN bytes that would confuse the caller. + var host = Request.Host.Host; + var isImg = DomainConfig.MatchesSubdomain(host, "img"); + var isCdn = DomainConfig.MatchesSubdomain(host, "cdn") + || DomainConfig.MatchesSubdomain(host, "storage") + || DomainConfig.MatchesSubdomain(host, "data"); + if (!isImg && !isCdn) return NotFound(); + + // Use the full request path (e.g. "config/LoadingScreenTipData", + // "room/foo.dat") rather than the route-captured suffix so the + // downstream switch logic — which still keys on the literal + // "config/LoadingScreenTipData" — keeps working regardless of + // which of the multiple [Route] templates above matched. + var fullPath = (Request.Path.Value ?? string.Empty).TrimStart('/'); + + if (isImg) return await ServeImage(fullPath); + return await ServeCdn(fullPath); + } + + // ── Image pipeline (img.{apex}) ──────────────────────────────────── + private async Task ServeImage(string? path) + { + var safe = Path.GetFileName(path ?? string.Empty); + // Strip the leading '$' sigil that ImageData.image_name carries + // for hash-addressed assets. The watch URL-escapes it (%24) and + // the route binder decodes it back, so what arrives here is + // literally "$.jpg" — but the bytes are stored at + // ".jpg" (the bare BlobName the importer writes). Without + // this strip, every placed in-world polaroid 404s into the + // transparent-PNG fallback and renders as "?" in-game. + if (safe.StartsWith('$')) safe = safe[1..]; + if (string.IsNullOrWhiteSpace(safe)) + { + signatures.AddContentSignature(Response, TransparentPng); + return File(TransparentPng, "image/png"); + } + + var ext = Path.GetExtension(safe).ToLowerInvariant(); + var contentType = ext switch + { + ".png" => "image/png", + ".jpg" or ".jpeg" => "image/jpeg", + ".webp" => "image/webp", + ".gif" => "image/gif", + _ => "image/png", // default to PNG for unknown — better than octet-stream for browser tags + }; + + // Three-tier resolution: S3 first (canonical post-migration), + // then on-disk legacy folder (room thumbnails shipped with the + // server), then RoomDataBlobs.Bytes column (uploads that + // pre-date the S3 migration). Once the migrator has uploaded + // everything to S3 and PR 4 drops the Bytes column, the disk + // and DB fallbacks become rare cases. + byte[]? s3Bytes = null; + var (s3Bucket, s3Key) = BlobRouter.Route(safe); + try + { + using var s3Timeout = new CancellationTokenSource(TimeSpan.FromSeconds(2)); + s3Bytes = await storage.GetAsync(s3Bucket, s3Key, s3Timeout.Token); + } + catch (Exception ex) + { + logger.LogWarning(ex, + "[img] S3 lookup threw host={Host} path='{Path}' file='{Safe}' bucket={Bucket} key={Key}; falling back to disk / DB / placeholder", + Request.Host.Host, path, safe, s3Bucket, s3Key); + } + + // Extension-less hash fallback. The 2020 watch references + // some persistence-view image slots as a bare 32-char hex hash + // (no .jpg, no .png) — distinct from ImageData.image_name which + // carries "$.jpg". The importer stores everything from + // SubRooms//Image/ with an extension, so a bare-hash + // request misses on first lookup. Retry with the two common + // image extensions before giving up. + if ((s3Bytes is null || s3Bytes.Length == 0) && string.IsNullOrEmpty(ext) && LooksLikeHash(safe)) + { + foreach (var altExt in new[] { ".jpg", ".png" }) + { + var altName = safe + altExt; + var (altBucket, altKey) = BlobRouter.Route(altName); + try + { + using var altTimeout = new CancellationTokenSource(TimeSpan.FromSeconds(2)); + var altBytes = await storage.GetAsync(altBucket, altKey, altTimeout.Token); + if (altBytes is { Length: > 0 }) + { + logger.LogInformation( + "[img] s3 hit (ext-fallback) host={Host} path={Path} resolvedAs={Alt} bytes={Bytes}", + Request.Host.Host, safe, altName, altBytes.Length); + s3Bytes = altBytes; + contentType = altExt == ".png" ? "image/png" : "image/jpeg"; + break; + } + } + catch (Exception ex) + { + logger.LogWarning(ex, + "[img] ext-fallback S3 lookup threw for {Name} bucket={Bucket} key={Key}", + altName, altBucket, altKey); + } + } + } + + if (s3Bytes is { Length: > 0 }) + { + logger.LogInformation("[img] s3 hit host={Host} path={Path} bytes={Bytes}", + Request.Host.Host, safe, s3Bytes.Length); + Response.Headers.CacheControl = "public, max-age=300"; + return RespondWithTransforms(s3Bytes, contentType); + } + + var full = Path.Combine(ImageDir, safe); + if (System.IO.File.Exists(full)) + { + logger.LogInformation("[img] disk hit host={Host} path={Path} → {Full}", + Request.Host.Host, path, full); + // Short TTL on the public CF edge so swapping a thumbnail + // (e.g. switching from full-res to a resized cached PNG) + // propagates within minutes instead of being pinned for + // hours by CF's default image-cache TTL. The watch keeps + // its own per-session local cache regardless. + var diskBytes = await System.IO.File.ReadAllBytesAsync(full); + Response.Headers.CacheControl = "public, max-age=300"; + return RespondWithTransforms(diskBytes, contentType); + } + + // S3 is the only canonical store — DB carries text-only + // metadata, never bytes. If S3 misses, the bytes don't exist + // on this server; fall through to the transparent-PNG miss. + + // Stop the 404 storm — the watch retries hard on missing room + // thumbnails. Return a 1x1 transparent PNG so it caches a + // valid (empty) image and stops asking. Log loudly so the + // user can see what filename the watch tried but disk lookup + // failed at: tells us at a glance whether the client is even + // hitting this controller (no log = wrong host / DNS issue), + // and if so, what filename it expects vs what's on disk. + logger.LogWarning( + "[img] MISS host={Host} path='{Path}' file='{Safe}' lookedAt='{Full}' query='{Query}'", + Request.Host.Host, path, safe, full, Request.QueryString); + // Don't let CF pin the empty-fallback for hours — once the real + // image lands on disk the next request should pick it up. + Response.Headers.CacheControl = "public, max-age=60"; + signatures.AddContentSignature(Response, TransparentPng); + return File(TransparentPng, "image/png"); + } + + // ── CDN serve path (cdn.*, storage.*, data.*, apex) ──────────────── + private async Task ServeCdn(string? path) + { + if (string.Equals(path, "config/LoadingScreenTipData", StringComparison.OrdinalIgnoreCase)) + { + // Tips live in the LoadingScreenTips DB table; admins + // edit them via the SPA. Wire shape per + // LoadingScreenTip.cs: List of {Title, Message, + // ImageName, Context, PlatformMask, HasImage, RoomNames}. + // HasImage MUST be set explicitly — without it the watch + // waits forever on the image promise and never renders. + var rows = await db.LoadingScreenTips + .Where(t => t.IsActive) + .OrderBy(t => t.SortOrder).ThenBy(t => t.Id) + .AsNoTracking() + .ToListAsync(); + var tips = rows.Select(t => new + { + Title = t.Title, + Message = t.Message, + ImageName = t.ImageName, + Context = t.Context, + PlatformMask = t.PlatformMask, + HasImage = !string.IsNullOrEmpty(t.ImageName), + RoomNames = string.IsNullOrEmpty(t.RoomNamesCsv) + ? Array.Empty() + : t.RoomNamesCsv.Split(',', StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries), + }).ToArray(); + var payload = JsonSerializer.SerializeToUtf8Bytes(tips); + signatures.AddContentSignature(Response, payload); + return new FileContentResult(payload, "application/json"); + } + + var fileName = string.IsNullOrEmpty(path) + ? string.Empty + : path.Split('/', StringSplitOptions.RemoveEmptyEntries).LastOrDefault() ?? string.Empty; + // Strip the leading '$' sigil that ImageData.image_name uses + // for hash-addressed assets. The img subdomain branch above + // already handles this; defense-in-depth for any client that + // fetches the same name via cdn.* / storage.* / data.* hosts. + if (!string.IsNullOrEmpty(fileName) && fileName[0] == '$') fileName = fileName[1..]; + + if (string.IsNullOrEmpty(fileName)) + { + logger.LogWarning("[cdn] empty filename host={Host} path={Path} — default blob", + Request.Host.Host, path); + var defaultBlob = roomDataBlob.GetDefaultBlob(); + signatures.AddContentSignature(Response, defaultBlob); + return new FileContentResult(defaultBlob, "application/octet-stream"); + } + + // Two-tier read: S3 first (the canonical store post-migration), + // then RoomDataBlobs.Bytes as a backstop for any blob that was + // uploaded before the storage migration ran. Once the migrator + // has uploaded all existing bytes and the Bytes column is + // dropped, this fallback becomes a no-op. + // + // Defensive: ObjectStorageService.GetAsync only catches 404; an + // S3 misconfig (wrong endpoint, dead creds, bucket missing) or + // a network blip throws → we'd otherwise return 500 and the + // watch's RecNet.Rooms.GetRoomData logs "Failed to download + // room data" + Photon disconnects the player. Catching here + // and falling through to the DB / default-blob path keeps the + // dorm load resilient to S3 outages — players boot into the + // stock dorm scene instead of getting kicked. + byte[]? s3Bytes = null; + var (s3Bucket, s3Key) = BlobRouter.Route(fileName); + try + { + // 15 seconds for room/data blobs (vs 2 seconds on the image + // path further up): the watch's room-load flow downloads + // PersistedRoomData and DESERIALISES it as the player enters + // the room — a default-empty blob means the saved dorm + // appears completely reset. Better to wait for the real + // bytes than hand back zeros and silently lose the player's + // build. Garage on the same host typically responds in + // 50-300ms; the larger budget only burns on cold-start / + // network blips. + using var s3Timeout = new CancellationTokenSource(TimeSpan.FromSeconds(15)); + s3Bytes = await storage.GetAsync(s3Bucket, s3Key, s3Timeout.Token); + } + catch (Exception ex) + { + logger.LogWarning(ex, "[cdn] S3 GetAsync threw for {Bucket}/{Key}; serving default blob", + s3Bucket, s3Key); + } + if (s3Bytes is { Length: > 0 }) + { + logger.LogInformation("[cdn] s3 hit host={Host} file={File} bytes={Bytes}", + Request.Host.Host, fileName, s3Bytes.Length); + // Allow CF edge to cache for an hour. RoomDataBlobs are + // content-addressed by hash so the same BlobName never + // serves different bytes — safe to cache aggressively. + Response.Headers.CacheControl = "public, max-age=3600"; + signatures.AddContentSignature(Response, s3Bytes); + return new FileContentResult(s3Bytes, MimeFromName(fileName)); + } + + // S3 is the only canonical store — DB carries text-only + // metadata (BlobName, owner, timestamps) and never holds + // bytes. If S3 misses, the bytes don't exist on this server. + + if (IsImageName(fileName)) + { + logger.LogWarning("[cdn] image MISS host={Host} file={File} query='{Query}' -> transparent png", + Request.Host.Host, fileName, Request.QueryString); + Response.Headers.CacheControl = "public, max-age=60"; + signatures.AddContentSignature(Response, TransparentPng); + return new FileContentResult(TransparentPng, "image/png"); + } + + logger.LogInformation("[cdn] MISS host={Host} file={File} -> default blob", + Request.Host.Host, fileName); + var fallbackBlob = roomDataBlob.GetDefaultBlob(); + signatures.AddContentSignature(Response, fallbackBlob); + return new FileContentResult(fallbackBlob, "application/octet-stream"); + } + + /// + /// Apply the watch's ?cropSquare=1 + ?width=N transforms + /// per-request on the way out and sign the *resulting* bytes — the + /// originals stay untouched in S3/disk/DB so a later + /// ?width=512 request can re-render from full resolution. If + /// the transform pipeline can't decode the bytes (corrupt file, GIF + /// that Skia doesn't support, etc.) we fall back to serving the raw + /// bytes rather than 5xxing the whole image request. + /// + private IActionResult RespondWithTransforms(byte[] sourceBytes, string sourceContentType) + { + var cropSquare = ParseFlag(Request.Query["cropSquare"]); + var width = ParseInt(Request.Query["width"]); + var transformed = (cropSquare || width is not null) + ? ImageTransformService.TryTransform(sourceBytes, sourceContentType, cropSquare, width) + : null; + + if (transformed is { } t) + { + signatures.AddContentSignature(Response, t.Bytes); + return File(t.Bytes, t.ContentType); + } + + signatures.AddContentSignature(Response, sourceBytes); + return File(sourceBytes, sourceContentType); + } + + private static bool ParseFlag(Microsoft.Extensions.Primitives.StringValues v) + { + var s = v.ToString(); + return !string.IsNullOrEmpty(s) && s != "0" && !s.Equals("false", StringComparison.OrdinalIgnoreCase); + } + + private static int? ParseInt(Microsoft.Extensions.Primitives.StringValues v) + => int.TryParse(v.ToString(), out var n) && n > 0 ? n : null; + + private static bool LooksLikeHash(string s) + { + if (s.Length is not (32 or 40)) return false; + foreach (var c in s) + { + var isHex = (c >= '0' && c <= '9') || (c >= 'a' && c <= 'f') || (c >= 'A' && c <= 'F'); + if (!isHex) return false; + } + return true; + } + + private static string MimeFromName(string name) + { + var ext = Path.GetExtension(name).ToLowerInvariant(); + return ext switch + { + ".png" => "image/png", + ".jpg" or ".jpeg" => "image/jpeg", + ".gif" => "image/gif", + ".webp" => "image/webp", + ".mp4" or ".m4v" => "video/mp4", + ".webm" => "video/webm", + ".mov" => "video/quicktime", + _ => "application/octet-stream", + }; + } + + private static bool IsImageName(string name) + { + var ext = Path.GetExtension(name).ToLowerInvariant(); + return ext is ".png" or ".jpg" or ".jpeg" or ".gif" or ".webp"; + } +} diff --git a/DorkNet.Server/Controllers/Chat/ChatController.cs b/DorkNet.Server/Controllers/Chat/ChatController.cs new file mode 100644 index 0000000..a426f35 --- /dev/null +++ b/DorkNet.Server/Controllers/Chat/ChatController.cs @@ -0,0 +1,823 @@ +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Mvc; +using Microsoft.EntityFrameworkCore; +using DorkNet.Models.Notification; +using DorkNet.Server.Auth; +using DorkNet.Server.Data; +using DorkNet.Server.Data.Entities; +using DorkNet.Server.Services; +using System.Text.Json; + +namespace DorkNet.Server.Controllers.Chat; + +/// +/// chat.rec.net — DM / group-chat endpoints. Three entities back this: +/// +/// — append-only message log +/// keyed by thread. +/// — optional per-thread metadata +/// (custom name, creator) for group chats and renamed DMs. +/// — per-(thread, player) +/// row carrying snooze timestamp and last-read message pointer. +/// For unmodified 2-person DMs we create rows lazily on first +/// mutation; the message table is the source of truth for +/// participants. +/// +/// +[ApiController] +[Authorize] +public class ChatController( + DorkNetDbContext db, + NotificationService notifications) : ControllerBase +{ + private const int ChatSuccess = 0; + private const int ChatInvalidArguments = 1; + private const int ChatMembershipNotFound = 3; + private const int ChatPlayerAlreadyOnThread = 4; + + private long Me => this.RequireCurrentPlayerId(); + + /// List the caller's chat threads, most-recently-active + /// first. Each thread carries the most recent message preview so + /// the watch can render the inbox without per-thread fetches. + [HttpGet("/thread")] + public async Task GetThreads([FromQuery] int? maxCount) + { + var take = Math.Clamp(maxCount ?? 50, 1, 100); + + // Two sources of "threads I see in my inbox": + // 1. Explicit membership rows (group chats — leaving deletes + // the row, so its presence means "still in"). + // 2. DM threads where I sent or received at least one message. + // We union both, then filter out group keys I'm NOT a member + // of (in case I sent a message to a group then left). + var memberKeys = (await db.ChatThreadMembers + .Where(m => m.PlayerId == Me) + .Select(m => m.ThreadKey) + .ToListAsync()).ToHashSet(); + + var sentOrReceived = await db.ChatMessages + .Where(c => c.SenderPlayerId == Me || + c.ThreadKey.StartsWith($"dm:{Me}:") || + c.ThreadKey.EndsWith($":{Me}")) + .Select(c => c.ThreadKey) + .Distinct() + .ToListAsync(); + + var keys = sentOrReceived + .Concat(memberKeys) + .Distinct() + .Where(k => !k.StartsWith("group:") || memberKeys.Contains(k)) + .ToList(); + if (keys.Count == 0) return Ok(Array.Empty()); + + var latest = await db.ChatMessages + .Where(c => keys.Contains(c.ThreadKey)) + .GroupBy(c => c.ThreadKey) + .Select(g => g.OrderByDescending(c => c.SentAt).First()) + .Take(take) + .ToListAsync(); + + var threadMeta = await EnsureThreadRowsAsync(keys); + var myMembership = await db.ChatThreadMembers + .Where(m => m.PlayerId == Me && keys.Contains(m.ThreadKey)) + .ToDictionaryAsync(m => m.ThreadKey, m => m); + var participants = await LoadParticipantsAsync(keys); + + return Ok(latest + .OrderByDescending(c => c.SentAt) + .Select(c => + { + var meta = threadMeta[c.ThreadKey]; + myMembership.TryGetValue(c.ThreadKey, out var member); + participants.TryGetValue(c.ThreadKey, out var playerIds); + return ToWireThread( + meta, + member, + playerIds ?? new List(), + latestMessage: c); + }) + .ToList()); + } + + /// Return the single thread by its key, with the most + /// recent N messages. + [HttpGet("/thread/{chatThreadId}")] + public async Task GetThreadById(string chatThreadId, + [FromQuery] int maxCount = 50) + { + var take = Math.Clamp(maxCount, 1, 200); + var key = await ResolveThreadKeyAsync(chatThreadId); + var rows = await db.ChatMessages + .Where(c => c.ThreadKey == key) + .OrderByDescending(c => c.SentAt) + .Take(take) + .ToListAsync(); + if (rows.Count == 0) return NotFound(); + var meta = (await EnsureThreadRowsAsync(new[] { key }))[key]; + var member = await db.ChatThreadMembers + .FirstOrDefaultAsync(m => m.ThreadKey == key && m.PlayerId == Me); + var participants = await LoadParticipantsAsync(new[] { key }); + participants.TryGetValue(key, out var playerIds); + return Ok(ToWireThread( + meta, + member, + playerIds ?? new List(), + messages: rows.OrderBy(c => c.SentAt).ToList())); + } + + /// Recent messages for a thread, paginated by + /// . + [HttpGet("/thread/{chatThreadId}/message")] + public async Task GetMessages(string chatThreadId, + [FromQuery] int maxCount = 50, + [FromQuery] int? messageCount = null, + [FromQuery] long? beforeMessageId = null, + [FromQuery] long? referenceMessageId = null) + { + var take = Math.Clamp(messageCount ?? maxCount, 1, 200); + var key = await ResolveThreadKeyAsync(chatThreadId); + var thread = await db.ChatThreads.FirstOrDefaultAsync(t => t.ThreadKey == key); + if (thread is null) return Ok(Array.Empty()); + var q = db.ChatMessages.Where(c => c.ThreadKey == key); + var before = beforeMessageId ?? referenceMessageId; + if (before is long b) + q = q.Where(c => c.Id < b); + var rows = await q + .OrderByDescending(c => c.SentAt) + .Take(take) + .ToListAsync(); + return Ok(rows.OrderBy(c => c.SentAt).Select(c => ToWireMessage(c, thread.Id)).ToList()); + } + + /// Unread-thread count from per-thread last-read pointers. + /// A thread counts as unread when there's at least one message + /// after the caller's LastReadMessageId (or any message at + /// all when the caller has never read). + [HttpGet("/thread/unreadcount")] + public async Task UnreadCount() + { + // Threads the caller participates in via membership row. + var memberKeys = await db.ChatThreadMembers + .Where(m => m.PlayerId == Me) + .ToListAsync(); + var memberKeyToLastRead = memberKeys.ToDictionary(m => m.ThreadKey, m => m.LastReadMessageId); + + // DM threads also count even without an explicit member row. + var dmKeys = await db.ChatMessages + .Where(c => c.ThreadKey.StartsWith($"dm:{Me}:") || c.ThreadKey.EndsWith($":{Me}")) + .Select(c => c.ThreadKey) + .Distinct() + .ToListAsync(); + + var keys = memberKeyToLastRead.Keys.Concat(dmKeys).Distinct().ToList(); + if (keys.Count == 0) return Ok(0); + + var perThreadMax = await db.ChatMessages + .Where(c => keys.Contains(c.ThreadKey) && c.SenderPlayerId != Me) + .GroupBy(c => c.ThreadKey) + .Select(g => new { Key = g.Key, MaxId = g.Max(c => c.Id) }) + .ToListAsync(); + + var unread = perThreadMax.Count(t => + { + memberKeyToLastRead.TryGetValue(t.Key, out var lastRead); + return lastRead is null || t.MaxId > lastRead.Value; + }); + return Ok(unread); + } + + public sealed record SendChatRequest(string? ChatThreadId, long? RecipientId, string Body); + + /// Append a message to a thread. The thread is either + /// referenced by id (existing thread) or by RecipientId (new DM + /// to that player; we synthesise the canonical dm key). JSON shape + /// used by the admin SPA / API callers; the 2020 watch posts the + /// form-urlencoded variant in with multi- + /// valued ids + a messageContents JSON blob. + [HttpPost("/thread")] + [HttpPost("/thread/{chatThreadId}/message")] + [Consumes("application/json")] + public async Task Send(string? chatThreadId, + [FromBody] SendChatRequest body) + { + if (string.IsNullOrWhiteSpace(body.Body)) return BadRequest("empty"); + var trimmed = body.Body.Trim(); + if (trimmed.Length > 2000) trimmed = trimmed[..2000]; + + var key = chatThreadId is null ? null : await ResolveThreadKeyAsync(chatThreadId); + key ??= body.ChatThreadId is null ? null : await ResolveThreadKeyAsync(body.ChatThreadId); + key ??= body.RecipientId is long otherId + ? ChatMessageEntity.DmKey(Me, otherId) + : null; + if (key is null) return BadRequest("missing_thread"); + var thread = await EnsureThreadRowAsync(key); + + var entry = new ChatMessageEntity + { + ThreadKey = key, + SenderPlayerId = Me, + Body = trimmed, + }; + db.ChatMessages.Add(entry); + await db.SaveChangesAsync(); + + // Notify every participant — INCLUDING the sender. The 2020 + // watch's chat send path doesn't update its local thread cache + // off the HTTP 200 response, so without a self-targeted push + // the sender's chat UI doesn't show its own outgoing message + // until the next thread refresh. DM keys encode the two + // participants directly; group chat threads fan out across the + // explicit membership table. + var recipients = new List(); + if (key.StartsWith("dm:") && TryParseDmKey(key, out var a, out var b)) + { + var other = a == Me ? b : a; + recipients.Add(other); + } + else if (key.StartsWith("group:")) + { + recipients = await db.ChatThreadMembers + .Where(m => m.ThreadKey == key && m.PlayerId != Me) + .Select(m => m.PlayerId) + .ToListAsync(); + } + recipients.Add(Me); + foreach (var rid in recipients) + { + await notifications.NotifyAsync(rid, + PushNotificationId.ChatMessageReceived, + ToWireMessage(entry, thread.Id)); + } + + return Ok(ToWireSendMessageResponse(entry, thread.Id)); + } + + /// Set the caller's last-read pointer on the thread to + /// the most recent message id. Drives the inbox's unread badge. + [HttpPut("/thread/{chatThreadId}/read")] + [HttpPost("/thread/{chatThreadId}/read")] + public async Task MarkRead(string chatThreadId) => + await UpdateLastRead(await ResolveThreadKeyAsync(chatThreadId), latestOnly: true, explicitMessageId: null); + + /// PUT /thread/{id}/message/{messageId}/read — set + /// the caller's last-read pointer to a specific message (not just + /// "latest"). The 2020 watch uses this when the inbox view scrolls + /// to a particular message rather than the bottom. URL verified + /// at stringliteral.json: "thread/{0}/message/{1}/read". + [HttpPut("/thread/{chatThreadId}/message/{messageId:long}/read")] + [HttpPost("/thread/{chatThreadId}/message/{messageId:long}/read")] + public async Task MarkMessageRead(string chatThreadId, long messageId) => + await UpdateLastRead(await ResolveThreadKeyAsync(chatThreadId), latestOnly: false, explicitMessageId: messageId); + + private async Task UpdateLastRead(string chatThreadId, bool latestOnly, long? explicitMessageId) + { + long? latestId = explicitMessageId; + if (latestId is null) + { + latestId = await db.ChatMessages + .Where(c => c.ThreadKey == chatThreadId) + .OrderByDescending(c => c.Id) + .Select(c => (long?)c.Id) + .FirstOrDefaultAsync(); + } + if (latestId is null) return Ok(ChatSuccess); + _ = latestOnly; + + var row = await db.ChatThreadMembers + .FirstOrDefaultAsync(m => m.ThreadKey == chatThreadId && m.PlayerId == Me); + if (row is null) + { + row = new ChatThreadMemberEntity + { + ThreadKey = chatThreadId, + PlayerId = Me, + }; + db.ChatThreadMembers.Add(row); + } + row.LastReadMessageId = latestId; + await db.SaveChangesAsync(); + return Ok(ChatSuccess); + } + + public sealed record SnoozeRequest(DateTime? Until); + + /// Mute notifications for the thread until + /// Until (UTC). Null/missing payload clears the snooze. + [HttpPost("/thread/{chatThreadId}/snooze")] + public async Task Snooze(string chatThreadId, [FromBody] SnoozeRequest? body) + { + chatThreadId = await ResolveThreadKeyAsync(chatThreadId); + var row = await db.ChatThreadMembers + .FirstOrDefaultAsync(m => m.ThreadKey == chatThreadId && m.PlayerId == Me); + if (row is null) + { + row = new ChatThreadMemberEntity + { + ThreadKey = chatThreadId, + PlayerId = Me, + }; + db.ChatThreadMembers.Add(row); + } + row.SnoozeUntil = body?.Until; + await db.SaveChangesAsync(); + var thread = await EnsureThreadRowAsync(chatThreadId); + return Ok(new Dictionary + { + ["chatResult"] = ChatSuccess, + ["snoozedUntil"] = row.SnoozeUntil, + }); + } + + /// Remove the caller from the thread's membership. They + /// stop receiving new messages and the thread drops off their + /// inbox. DMs can't be left — there are only two participants and + /// removing one effectively ends the conversation; the watch + /// surfaces a "delete chat" option for DMs instead. + [HttpDelete("/thread/{chatThreadId}/leave")] + public async Task Leave(string chatThreadId) + { + chatThreadId = await ResolveThreadKeyAsync(chatThreadId); + if (chatThreadId.StartsWith("dm:")) + return Ok(ChatInvalidArguments); + var removed = await db.ChatThreadMembers + .Where(m => m.ThreadKey == chatThreadId && m.PlayerId == Me) + .ExecuteDeleteAsync(); + return Ok(removed > 0 ? ChatSuccess : ChatMembershipNotFound); + } + + public sealed record RenameRequest(string Name); + + /// Set the thread's display name. Permission: any member + /// of the thread can rename. For DMs the watch typically nudges + /// users to pick a nickname after first send; group-chat creators + /// usually set the name at creation time. + [HttpPut("/thread/{chatThreadId}/rename")] + public async Task Rename(string chatThreadId, [FromBody] RenameRequest body) + { + chatThreadId = await ResolveThreadKeyAsync(chatThreadId); + var name = (body.Name ?? string.Empty).Trim(); + if (name.Length > 128) name = name[..128]; + + var iAmMember = chatThreadId.StartsWith("dm:") + ? IsDmParticipant(chatThreadId, Me) + : await db.ChatThreadMembers + .AnyAsync(m => m.ThreadKey == chatThreadId && m.PlayerId == Me); + if (!iAmMember) return Ok(ChatMembershipNotFound); + + var meta = await db.ChatThreads.FirstOrDefaultAsync(t => t.ThreadKey == chatThreadId); + if (meta is null) + { + meta = new ChatThreadEntity + { + ThreadKey = chatThreadId, + CreatorPlayerId = Me, + }; + db.ChatThreads.Add(meta); + } + meta.Name = name; + meta.UpdatedAt = DateTime.UtcNow; + await db.SaveChangesAsync(); + return Ok(ChatSuccess); + } + + public sealed record CreateGroupChatRequest(List MemberIds, string? Name); + + /// Create a group chat. Generates a fresh + /// group:<guid> key, inserts membership rows for the + /// creator + each invited player, and returns the new thread id. + /// Empty or duplicate member lists are tolerated (the caller is + /// always added as a member regardless). JSON variant — the 2020 + /// watch hits the form-urlencoded form in . + /// + [HttpPost("/thread/withmembers")] + [Consumes("application/json")] + public async Task CreateGroup([FromBody] CreateGroupChatRequest body) + { + var key = $"group:{Guid.NewGuid():N}"; + var members = (body?.MemberIds ?? new()) + .Where(id => id > 0 && id != Me) + .Distinct() + .ToList(); + members.Add(Me); + + db.ChatThreads.Add(new ChatThreadEntity + { + ThreadKey = key, + Name = (body?.Name ?? string.Empty).Trim(), + CreatorPlayerId = Me, + }); + foreach (var pid in members) + { + db.ChatThreadMembers.Add(new ChatThreadMemberEntity + { + ThreadKey = key, + PlayerId = pid, + }); + } + await db.SaveChangesAsync(); + + var thread = await db.ChatThreads.FirstAsync(t => t.ThreadKey == key); + var playerIds = members.Select(x => (int)x).ToList(); + return Ok(new Dictionary + { + ["chatThread"] = ToWireThread(thread, null, playerIds), + ["chatResult"] = ChatSuccess, + }); + } + + /// Form-urlencoded variant of + /// used by the 2020 watch. Wire shape verified from the live + /// request: ids=A&ids=B&messageCount=N. The watch + /// calls this to "open a chat with these people" — semantics are + /// fetch-or-create-then-return-recent-messages, NOT just create. + /// We resolve ids to a canonical thread key (DM if two + /// participants total including the caller, otherwise group), pull + /// the latest messageCount messages, and return a shape the + /// watch's inbox can render directly. + [HttpPost("/thread/withmembers")] + [Consumes("application/x-www-form-urlencoded", "multipart/form-data")] + public async Task WithMembersForm() + { + var (ids, _) = ParseIdsAndContents(Request.Form); + if (ids.Count == 0) return BadRequest(new { error = "missing_ids" }); + + var count = Math.Clamp( + int.TryParse(Request.Form["messageCount"], out var mc) ? mc : 50, + 1, 200); + var key = await ResolveOrCreateThreadAsync(ids); + var rows = await db.ChatMessages + .Where(c => c.ThreadKey == key) + .OrderByDescending(c => c.SentAt) + .Take(count) + .ToListAsync(); + var meta = await EnsureThreadRowAsync(key); + var participants = ids.Concat(new[] { Me }).Distinct().Select(x => (int)x).ToList(); + return Ok(ToWireThread(meta, null, participants, messages: rows.OrderBy(c => c.SentAt).ToList())); + } + + /// Form-urlencoded variant of used by + /// the 2020 watch. Wire shape: ids=A&ids=B& + /// messageContents={JSON} where messageContents is the + /// JSON-encoded RecNet.ChatMessage.Contents blob + /// ({Type,Version,Data}) — Data is the actual text. The + /// watch sends this to BOTH create-or-find-thread AND post-message + /// in one call. + [HttpPost("/thread")] + [Consumes("application/x-www-form-urlencoded", "multipart/form-data")] + public async Task SendForm() + { + var (ids, contents) = ParseIdsAndContents(Request.Form); + if (ids.Count == 0) return BadRequest(new { error = "missing_ids" }); + + var text = ExtractChatDataField(contents); + if (string.IsNullOrWhiteSpace(text)) return BadRequest(new { error = "empty_body" }); + if (text.Length > 2000) text = text[..2000]; + + var key = await ResolveOrCreateThreadAsync(ids); + var thread = await EnsureThreadRowAsync(key); + var entry = new ChatMessageEntity + { + ThreadKey = key, + SenderPlayerId = Me, + Body = text, + }; + db.ChatMessages.Add(entry); + await db.SaveChangesAsync(); + + // Notify every participant, sender INCLUDED — see SendMessageJson + // above for the rationale; the 2020 watch's chat send path needs + // the self-push to refresh its own UI. + var recipients = new List(); + if (key.StartsWith("dm:") && TryParseDmKey(key, out var a, out var b)) + { + var other = a == Me ? b : a; + if (other != Me) recipients.Add(other); + } + else if (key.StartsWith("group:")) + { + recipients = await db.ChatThreadMembers + .Where(m => m.ThreadKey == key && m.PlayerId != Me) + .Select(m => m.PlayerId) + .ToListAsync(); + } + recipients.Add(Me); + foreach (var rid in recipients) + { + await notifications.NotifyAsync(rid, + PushNotificationId.ChatMessageReceived, + ToWireMessage(entry, thread.Id)); + } + var participants = await LoadParticipantsAsync(new[] { key }); + participants.TryGetValue(key, out var playerIds); + return Ok(new Dictionary + { + ["chatThread"] = ToWireThread(thread, null, playerIds ?? new List(), latestMessage: entry), + ["chatResult"] = ChatSuccess, + }); + } + + /// Form-urlencoded send used by the 2020 watch: + /// POST /thread/{chatId} with messageContents. + /// The response is a SendMessageResponse wrapper, not a raw message. + [HttpPost("/thread/{chatThreadId}")] + [Consumes("application/x-www-form-urlencoded", "multipart/form-data")] + public async Task SendMessageForm(string chatThreadId) + { + var key = await ResolveThreadKeyAsync(chatThreadId); + var text = ExtractChatDataField(Request.Form["messageContents"].ToString()); + if (string.IsNullOrWhiteSpace(text)) return Ok(new Dictionary + { + ["chatResult"] = ChatInvalidArguments, + }); + if (text.Length > 2000) text = text[..2000]; + + var thread = await EnsureThreadRowAsync(key); + var entry = new ChatMessageEntity + { + ThreadKey = key, + SenderPlayerId = Me, + Body = text, + }; + db.ChatMessages.Add(entry); + await db.SaveChangesAsync(); + + var recipients = new List(); + if (key.StartsWith("dm:") && TryParseDmKey(key, out var a, out var b)) + { + var other = a == Me ? b : a; + if (other != Me) recipients.Add(other); + } + else if (key.StartsWith("group:")) + { + recipients = await db.ChatThreadMembers + .Where(m => m.ThreadKey == key && m.PlayerId != Me) + .Select(m => m.PlayerId) + .ToListAsync(); + } + // Include the sender so the form-POST send path also refreshes + // its own chat thread — same reason as the JSON send path above. + recipients.Add(Me); + foreach (var rid in recipients) + { + await notifications.NotifyAsync(rid, + PushNotificationId.ChatMessageReceived, + ToWireMessage(entry, thread.Id)); + } + + return Ok(ToWireSendMessageResponse(entry, thread.Id)); + } + + /// Pull ids (multi-valued) + messageContents + /// out of the watch's form post. ids excludes the caller + /// (the caller is implied by their JWT). + private (List ids, string? contents) ParseIdsAndContents( + Microsoft.AspNetCore.Http.IFormCollection form) + { + var ids = new List(); + foreach (var v in form["ids"]) + { + if (long.TryParse(v, out var id) && id > 0 && id != Me) + ids.Add(id); + } + ids = ids.Distinct().ToList(); + var contents = form["messageContents"].ToString(); + return (ids, string.IsNullOrEmpty(contents) ? null : contents); + } + + /// Resolve + caller to a + /// canonical thread key. 1 other → DM key. 2+ others → existing + /// group with the exact same membership, or a fresh + /// group:<guid> if none. Membership rows are created + /// lazily so the inbox listing path can find the thread. + private async Task ResolveOrCreateThreadAsync(List otherIds) + { + if (otherIds.Count == 1) + { + var key = ChatMessageEntity.DmKey(Me, otherIds[0]); + // Ensure a ChatThreadEntity row exists for DMs the watch + // is mutating, so /thread inbox listings carry consistent + // metadata. The membership rows aren't needed for DM + // routing (the key carries the pair) but the inbox queries + // are simpler when every active thread has a row. + if (!await db.ChatThreads.AnyAsync(t => t.ThreadKey == key)) + { + db.ChatThreads.Add(new ChatThreadEntity + { + ThreadKey = key, + CreatorPlayerId = Me, + }); + await db.SaveChangesAsync(); + } + return key; + } + + var full = otherIds.Concat(new[] { Me }).Distinct().OrderBy(x => x).ToList(); + // Look for an existing group whose membership matches exactly. + // Counts the rows per thread that intersect `full`, then takes + // the threads where total membership == |full| AND intersection + // size == |full|. + var candidateGroups = await db.ChatThreadMembers + .Where(m => full.Contains(m.PlayerId) && m.ThreadKey.StartsWith("group:")) + .GroupBy(m => m.ThreadKey) + .Where(g => g.Count() == full.Count) + .Select(g => g.Key) + .ToListAsync(); + foreach (var k in candidateGroups) + { + var rows = await db.ChatThreadMembers + .Where(m => m.ThreadKey == k) + .Select(m => m.PlayerId) + .ToListAsync(); + if (rows.OrderBy(x => x).SequenceEqual(full)) return k; + } + + // No matching group → create one. + var newKey = $"group:{Guid.NewGuid():N}"; + db.ChatThreads.Add(new ChatThreadEntity + { + ThreadKey = newKey, + CreatorPlayerId = Me, + }); + foreach (var pid in full) + { + db.ChatThreadMembers.Add(new ChatThreadMemberEntity + { + ThreadKey = newKey, + PlayerId = pid, + }); + } + await db.SaveChangesAsync(); + return newKey; + } + + /// Pull the Data field out of the watch's + /// messageContents JSON: {"Type":0,"Version":1,"Data":"hi"}. + /// Returns null/empty when the body isn't well-formed. + private static string ExtractChatDataField(string? messageContents) + { + if (string.IsNullOrWhiteSpace(messageContents)) return string.Empty; + try + { + using var doc = System.Text.Json.JsonDocument.Parse(messageContents); + if (doc.RootElement.TryGetProperty("Data", out var data) + && data.ValueKind == System.Text.Json.JsonValueKind.String) + return data.GetString() ?? string.Empty; + } + catch { } + return string.Empty; + } + + /// Add a player to an existing group chat. Idempotent — + /// re-adding an existing member is a no-op. + [HttpPost("/thread/{chatThreadId}/member/{playerId:long}")] + public async Task AddMember(string chatThreadId, long playerId) + { + chatThreadId = await ResolveThreadKeyAsync(chatThreadId); + if (!chatThreadId.StartsWith("group:")) + return Ok(ChatInvalidArguments); + var iAmMember = await db.ChatThreadMembers + .AnyAsync(m => m.ThreadKey == chatThreadId && m.PlayerId == Me); + if (!iAmMember) return Ok(ChatMembershipNotFound); + var exists = await db.ChatThreadMembers + .AnyAsync(m => m.ThreadKey == chatThreadId && m.PlayerId == playerId); + if (exists) return Ok(ChatPlayerAlreadyOnThread); + if (!exists) + { + db.ChatThreadMembers.Add(new ChatThreadMemberEntity + { + ThreadKey = chatThreadId, + PlayerId = playerId, + }); + await db.SaveChangesAsync(); + } + return Ok(ChatSuccess); + } + + private async Task ResolveThreadKeyAsync(string idOrKey) + { + if (long.TryParse(idOrKey, out var id)) + { + var key = await db.ChatThreads + .Where(t => t.Id == id) + .Select(t => t.ThreadKey) + .FirstOrDefaultAsync(); + return key ?? idOrKey; + } + return idOrKey; + } + + private async Task> EnsureThreadRowsAsync(IEnumerable keys) + { + var distinct = keys.Distinct().ToList(); + var rows = await db.ChatThreads + .Where(t => distinct.Contains(t.ThreadKey)) + .ToDictionaryAsync(t => t.ThreadKey, t => t); + + foreach (var key in distinct.Where(k => !rows.ContainsKey(k))) + { + var row = new ChatThreadEntity + { + ThreadKey = key, + CreatorPlayerId = Me, + }; + db.ChatThreads.Add(row); + rows[key] = row; + } + + if (db.ChangeTracker.HasChanges()) + await db.SaveChangesAsync(); + + return rows; + } + + private async Task EnsureThreadRowAsync(string key) => + (await EnsureThreadRowsAsync(new[] { key }))[key]; + + private async Task>> LoadParticipantsAsync(IEnumerable keys) + { + var distinct = keys.Distinct().ToList(); + var result = distinct.ToDictionary(k => k, _ => new List()); + + foreach (var key in distinct) + { + if (TryParseDmKey(key, out var a, out var b)) + { + result[key] = new List { (int)a, (int)b }; + } + } + + var groupKeys = distinct.Where(k => k.StartsWith("group:")).ToList(); + if (groupKeys.Count > 0) + { + var members = await db.ChatThreadMembers + .Where(m => groupKeys.Contains(m.ThreadKey)) + .Select(m => new { m.ThreadKey, m.PlayerId }) + .ToListAsync(); + foreach (var group in members.GroupBy(m => m.ThreadKey)) + result[group.Key] = group.Select(m => (int)m.PlayerId).Distinct().ToList(); + } + + return result; + } + + private static Dictionary ToWireThread( + ChatThreadEntity thread, + ChatThreadMemberEntity? member, + List playerIds, + ChatMessageEntity? latestMessage = null, + List? messages = null) + { + var dto = new Dictionary + { + ["chatThreadId"] = thread.Id, + ["chatThreadName"] = thread.Name ?? string.Empty, + ["lastReadMessageId"] = member?.LastReadMessageId ?? 0, + ["snoozedUntil"] = member?.SnoozeUntil, + ["playerIds"] = playerIds, + }; + + if (messages is not null) + { + dto["messages"] = messages.Select(m => ToWireMessage(m, thread.Id)).ToList(); + } + else if (latestMessage is not null) + { + dto["latestMessage"] = ToWireMessage(latestMessage, thread.Id); + } + + return dto; + } + + private static Dictionary ToWireMessage(ChatMessageEntity c, long chatThreadId) => new() + { + ["chatMessageId"] = c.Id, + ["chatThreadId"] = chatThreadId, + ["senderPlayerId"] = (int)c.SenderPlayerId, + ["timeSent"] = c.SentAt, + ["contents"] = JsonSerializer.Serialize(new + { + Type = 0, + Version = 1, + Data = c.Body, + }), + }; + + private static Dictionary ToWireSendMessageResponse(ChatMessageEntity c, long chatThreadId) => new() + { + ["chatMessage"] = ToWireMessage(c, chatThreadId), + ["chatResult"] = ChatSuccess, + }; + + private static bool TryParseDmKey(string key, out long a, out long b) + { + a = b = 0; + var parts = key.Split(':'); + return parts.Length == 3 && parts[0] == "dm" && + long.TryParse(parts[1], out a) && + long.TryParse(parts[2], out b); + } + + private static bool IsDmParticipant(string key, long playerId) => + TryParseDmKey(key, out var a, out var b) && (a == playerId || b == playerId); +} diff --git a/DorkNet.Server/Controllers/Clubs/ClubsController.cs b/DorkNet.Server/Controllers/Clubs/ClubsController.cs new file mode 100644 index 0000000..1fe0536 --- /dev/null +++ b/DorkNet.Server/Controllers/Clubs/ClubsController.cs @@ -0,0 +1,936 @@ +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Mvc; +using System.Text.Json; +using DorkNet.Server.Auth; +using DorkNet.Server.Data.Entities; +using DorkNet.Server.Services; + +namespace DorkNet.Server.Controllers.Clubs; + +/// +/// clubs.localhost — the 2020.12 watch's club home tab and announcement +/// inbox. Path-based routing per the host-filter strategy in +/// Program.cs (HostFilteringMiddleware gates which subdomains reach +/// us, individual controllers don't need [Host] filters). +/// +/// URL surface (verb + template) verified against +/// Cpp2IL_ISIL/.../JDJGIBLMFKK.txt: +/// GET /club/home/me +/// GET /club/mine/member +/// GET /club/mine/created +/// GET /club/categoryTags +/// GET /announcements/v2/mine/unread +/// GET /announcements/v2/subscription/mine/unread +/// POST /announcements/club/{clubId}/{announcementId}/read +/// +/// Wire types (deserialiser JSON keys, per ISIL): +/// Club (PLILLKHMNDA): ClubId, Name, Description, +/// MainImageName, State, CreatorAccountId, Category, +/// Visibility, Joinability, AllowJuniors, MemberCount, +/// IsRRO, ClubhouseRoomId, ClubType +/// Announcement (NFEMLMAFFIP): AnnouncementId, +/// CreatorAccountId, ClubId, Title, Body, ImageName, +/// CreatedAt, Meta +/// UnreadResponse (HPACLJHLHBG) — wraps per-club rows: +/// { clubId, LastReadAnnouncementId, announcements: [Announcement] } +/// CategoryTags plain List<String> (per +/// JDJGIBLMFKK.GetPrimaryTags callback shape). +/// +[ApiController] +public class ClubsController(ClubService clubs) : ControllerBase +{ + private long Me => this.RequireCurrentPlayerId(); + + [HttpGet("/club/home/me")] + [Authorize] + public async Task HomeMe() + { + var pid = Me; + var home = await clubs.HomeClubAsync(pid); + if (home is null) + { + // Watch tolerates an empty/default Club object on the home + // tab — it shows the "no home club" empty state. Returning + // 204 No Content would short-circuit the deserialiser + // (Action`1<PLILLKHMNDA>) so we send a minimal + // sentinel row with ClubId=0 instead; the watch's null-check + // path keys off ClubId. + return Ok(ToWireClub(EmptyClub(), 0)); + } + var memberCount = await clubs.MemberCountAsync(home.Id); + return Ok(ToWireClub(home, memberCount)); + } + + [HttpGet("/club/mine/member")] + [Authorize] + public async Task MineMember() + { + var pid = Me; + var rows = await clubs.MyClubsAsync(pid); + var counts = await clubs.MemberCountsAsync(rows.Select(r => r.Id)); + return Ok(rows.Select(c => ToWireClub(c, counts.GetValueOrDefault(c.Id)))); + } + + [HttpGet("/club/mine/created")] + [Authorize] + public async Task MineCreated() + { + var pid = Me; + var rows = await clubs.CreatedByAsync(pid); + var counts = await clubs.MemberCountsAsync(rows.Select(r => r.Id)); + return Ok(rows.Select(c => ToWireClub(c, counts.GetValueOrDefault(c.Id)))); + } + + /// GET /club/search — browse/search clubs by + /// optional category and text query. The watch issues this while + /// building the Clubs discover tab, e.g. + /// ?sort=0&category=Creative&count=30, and expects + /// a CAJHIEENJJD envelope: Clubs, + /// TotalClubs, and ContinuationToken. + [HttpGet("/club/search")] + public async Task ClubSearch( + [FromQuery] string? query, + [FromQuery] string? search, + [FromQuery] string? category, + [FromQuery] int? sort, + [FromQuery] int? count) + { + var result = await clubs.SearchAsync(query ?? search, category, sort ?? 0, count ?? 30); + var counts = await clubs.MemberCountsAsync(result.Clubs.Select(r => r.Id)); + return Ok(new + { + Clubs = result.Clubs.Select(c => ToWireClub(c, counts.GetValueOrDefault(c.Id))), + TotalClubs = result.TotalClubs, + ContinuationToken = string.Empty, + }); + } + + /// GET /club?name=Foo — direct name lookup used by + /// the client when resolving a club by display name. Returns the + /// single PLILLKHMNDA Club wire shape. + [HttpGet("/club")] + public async Task ClubByName([FromQuery] string? name) + { + if (string.IsNullOrWhiteSpace(name)) return BadRequest(new { error = "missing_name" }); + var club = await clubs.GetByNameAsync(name); + if (club is null) return NotFound(); + var members = await clubs.MemberCountAsync(club.Id); + return Ok(ToWireClub(club, members)); + } + + /// The 2020.12 watch deserialises this as + /// List<String> (per JDJGIBLMFKK.GetPrimaryTags + /// callback signature), so we project just the tag Name column. + [HttpGet("/club/categoryTags")] + public async Task CategoryTags() + { + var rows = await clubs.CategoryTagsAsync(); + return Ok(rows.Select(t => t.Name)); + } + + /// + /// Unread direct-feed announcements grouped by club into + /// HPACLJHLHBG wire rows ({ clubId, LastReadAnnouncementId, + /// announcements }). One row per club that has at least one unread + /// item — clubs with nothing new are omitted so the watch's bell + /// badge counts correctly. + /// + [HttpGet("/announcements/v2/mine/unread")] + [Authorize] + public async Task UnreadMine() + { + var pid = Me; + var rows = await clubs.UnreadDirectAsync(pid); + return Ok(GroupByClub(rows)); + } + + [HttpGet("/announcements/v2/subscription/mine/unread")] + [Authorize] + public async Task UnreadSubscription() + { + var pid = Me; + var rows = await clubs.UnreadSubscriptionAsync(pid); + return Ok(GroupByClub(rows)); + } + + /// GET /subscription/mine/member — list of clubs the + /// caller is subscribed to. Watch deserializes as + /// List<LNIKPLKOBDK>; per the dump at + /// LNIKPLKOBDK.txt:110-124 the keys are lowercase + /// accountId, clubId, subscriberCount. One row + /// per subscription. Empty array is legal — the watch just hides the + /// "Subscribed Clubs" section when the list is empty. + [HttpGet("/subscription/mine/member")] + [Authorize] + public async Task MySubscriptions() + { + var pid = Me; + var rows = await clubs.MySubscriptionsAsync(pid); + return Ok(rows); + } + + /// + /// GET /subscription/subscriberCount/{accountId} — bare int + /// of how many subscribers an account has. Watch caller is + /// JDJGIBLMFKK.PNFHJBIAPBM(int) at + /// JDJGIBLMFKK.txt:5533,5774 with signature + /// IPromise<Int32>. Each Rec Room account has an + /// implicit personal "creator club" so we union both possible + /// counts: + /// * player→player subscriptions where TargetPlayerId == id, and + /// * club subscriptions where ClubId == id (if a club happens to + /// share the id). + /// Anonymous-safe — used by profile cards the watch shows before + /// the viewer is logged in to anyone they're not friends with. + /// + [HttpGet("/subscription/subscriberCount/{accountId:long}")] + public async Task SubscriberCount(long accountId) + { + var playerSubs = await clubs.PlayerSubscriberCountAsync(accountId); + var clubSubs = await clubs.ClubSubscriberCountAsync(accountId); + return Ok(playerSubs + clubSubs); + } + + /// + /// POST endpoint the watch hits when the player taps "mark read". + /// Idempotent — the upsert in the service quietly returns if a + /// read-marker already exists. + /// + [HttpPost("/announcements/club/{clubId:long}/{announcementId:long}/read")] + [Authorize] + public async Task MarkRead(long clubId, long announcementId) + { + var pid = Me; + await clubs.MarkAnnouncementReadAsync(pid, announcementId); + return Ok(new { Read = true }); + } + + // ── Single-club reads ───────────────────────────────────────────── + + /// GET /club/{id} — single Club lookup. Wire shape + /// is PLILLKHMNDA (same as the lists). Used by the watch's + /// link-resolver and the "club tile" widget on profile cards. + [HttpGet("/club/{clubId:long}")] + public async Task ClubById(long clubId) + { + var club = await clubs.GetByIdAsync(clubId); + if (club is null) return NotFound(); + var members = await clubs.MemberCountAsync(clubId); + return Ok(ToWireClub(club, members)); + } + + /// GET /club/{id}/details — the central club page + /// payload. Wire shape is PIHMJGCGNLP with required keys + /// Club, CoownerPermissions, ModeratorPermissions, + /// MemberPermissions, MyMembershipType per + /// PIHMJGCGNLP.cs. Returning just the Club shape throws + /// KeyNotFoundException on the deserializer. + [HttpGet("/club/{clubId:long}/details")] + public Task ClubDetails(long clubId) => BuildDetailsResponseAsync(clubId); + + /// /club/{id}/clubhouse — returns the same + /// PIHMJGCGNLP shape as /details. The watch's clubhouse + /// setter passes an optional roomId; clearing the clubhouse uses + /// DELETE. We accept GET / PUT / DELETE so whichever verb the + /// request-builder chose lands on the handler, then route the + /// roomId into ClubhouseRoomId when present (set/clear) and reply + /// with the refreshed details envelope. + [HttpGet("/club/{clubId:long}/clubhouse")] + [HttpPut("/club/{clubId:long}/clubhouse")] + [HttpDelete("/club/{clubId:long}/clubhouse")] + public async Task ClubClubhouse(long clubId, [FromQuery(Name = "roomId")] long? roomId) + { + if (HttpMethods.IsPut(Request.Method) || HttpMethods.IsDelete(Request.Method)) + { + try + { + var updated = await clubs.ModifyAsync(clubId, Me, c => + c.ClubhouseRoomId = HttpMethods.IsDelete(Request.Method) ? null : roomId); + if (updated is null) return NotFound(); + } + catch (UnauthorizedAccessException) { return Forbid(); } + } + return await BuildDetailsResponseAsync(clubId); + } + + /// GET /club/{id}/permissions/{role} — bare int of + /// the permission bitmask for a role on this club. Used by the + /// watch's role-permissions screen. Returns 0 for unknown roles so + /// the screen renders an empty checklist instead of erroring. + [HttpGet("/club/{clubId:long}/permissions/{role:int}")] + public async Task ClubPermissions(long clubId, int role) + { + var club = await clubs.GetByIdAsync(clubId); + if (club is null) return NotFound(); + return Ok(PermissionsForRole(role)); + } + + // ── Account / created lists ────────────────────────────────────── + + /// GET /account/{playerId}/clubs — list of Clubs + /// the player is a member of. Wire shape is + /// List<PLILLKHMNDA>. Anonymous-safe — used on profile + /// cards. + [HttpGet("/account/{playerId:long}/clubs")] + public async Task AccountClubs(long playerId) + { + var rows = await clubs.ClubsForPlayerAsync(playerId); + var counts = await clubs.MemberCountsAsync(rows.Select(r => r.Id)); + return Ok(rows.Select(c => ToWireClub(c, counts.GetValueOrDefault(c.Id)))); + } + + /// GET /club/account/{playerId}/created — list of + /// Clubs the player created. Wire shape is + /// List<PLILLKHMNDA>. Anonymous-safe. + [HttpGet("/club/account/{playerId:long}/created")] + public async Task ClubAccountCreated(long playerId) + { + var rows = await clubs.CreatedByAsync(playerId); + var counts = await clubs.MemberCountsAsync(rows.Select(r => r.Id)); + return Ok(rows.Select(c => ToWireClub(c, counts.GetValueOrDefault(c.Id)))); + } + + // ── Club create / modify ───────────────────────────────────────── + + public sealed class CreateClubRequest + { + public string? Name { get; set; } + public string? Description { get; set; } + public string? MainImageName { get; set; } + public string? Category { get; set; } + } + + /// POST /club/create — create a new club + the + /// owner's membership row. Returns the PIHMJGCGNLP envelope + /// (same shape as /club/{id}/details) so the watch's + /// post-create navigation can render the club page without a + /// separate fetch. + [HttpPost("/club/create")] + [Authorize] + public async Task ClubCreate() + { + var req = await ReadCreateClubRequestAsync(); + var club = await clubs.CreateAsync(Me, req.Name ?? string.Empty, + req.Description, req.MainImageName, req.Category); + if (club is null) return Conflict(new { error = "club_name_taken_or_empty" }); + return await BuildDetailsResponseAsync(club.Id); + } + + public sealed class ModifyClubRequest + { + public string? Name { get; set; } + public string? Description { get; set; } + public string? Category { get; set; } + public int? Visibility { get; set; } + public int? Joinability { get; set; } + public bool? AllowJuniors { get; set; } + public long? ClubhouseRoomId { get; set; } + } + + /// POST /club/{id}/modify + /modifydetails — + /// the watch sends two different request DTOs to two URLs, but + /// both mutate the same Club row and both expect the + /// PIHMJGCGNLP response. Single handler covers both + /// because the response shape is identical and we only persist + /// fields the request includes. + [HttpPost("/club/{clubId:long}/modify")] + [HttpPut("/club/{clubId:long}/modify")] + [HttpPost("/club/{clubId:long}/modifydetails")] + [HttpPut("/club/{clubId:long}/modifydetails")] + [Authorize] + public async Task ClubModify(long clubId, [FromBody] ModifyClubRequest req) + { + try + { + var updated = await clubs.ModifyAsync(clubId, Me, c => + { + if (!string.IsNullOrWhiteSpace(req.Name)) c.Name = req.Name.Trim(); + if (req.Description is not null) c.Description = req.Description; + if (req.Category is not null) c.Category = req.Category; + if (req.Visibility is int v) c.Visibility = v; + if (req.Joinability is int j) c.Joinability = j; + if (req.AllowJuniors is bool aj) c.AllowJuniors = aj; + if (req.ClubhouseRoomId is long ch) c.ClubhouseRoomId = ch; + }); + if (updated is null) return NotFound(); + return await BuildDetailsResponseAsync(updated.Id); + } + catch (UnauthorizedAccessException) { return Forbid(); } + } + + public sealed class MainImageRequest { public string? ImageName { get; set; } } + + /// POST /club/{id}/mainimage — replace the club's + /// MainImageName. Same PIHMJGCGNLP response so the watch can + /// refresh the club page in one round-trip. + [HttpPost("/club/{clubId:long}/mainimage")] + [HttpPut("/club/{clubId:long}/mainimage")] + [Authorize] + public async Task ClubMainImage(long clubId, [FromBody] MainImageRequest req) + { + try + { + var updated = await clubs.ModifyAsync(clubId, Me, + c => c.ImageName = req.ImageName ?? string.Empty); + if (updated is null) return NotFound(); + return await BuildDetailsResponseAsync(updated.Id); + } + catch (UnauthorizedAccessException) { return Forbid(); } + } + + /// POST /club/{id}/additionalimage/{slot} — the + /// watch's gallery-image slot setter. The 2020 ClubEntity doesn't + /// have additional image slots persisted yet, so we just return + /// the current details unchanged (the gallery renders blank). + /// Hooked up so the request doesn't 404 — populated once a future + /// schema patch lands. + [HttpPost("/club/{clubId:long}/additionalimage/{slot:int}")] + [HttpPut("/club/{clubId:long}/additionalimage/{slot:int}")] + [Authorize] + public async Task ClubAdditionalImage(long clubId, int slot, [FromBody] MainImageRequest req) + { + var club = await clubs.GetByIdAsync(clubId); + if (club is null) return NotFound(); + if (!await clubs.CanManageAsync(clubId, Me, club)) return Forbid(); + return await BuildDetailsResponseAsync(clubId); + } + + // ── Single-member read ─────────────────────────────────────────── + + /// GET /club/{id}/members/{playerId} — single + /// membership row. Wire shape is MFOAODGNGKB: AccountId, + /// ClubId, MembershipType, CreatedAt. + [HttpGet("/club/{clubId:long}/members/{playerId:long}")] + public async Task ClubMember(long clubId, long playerId) + { + var row = await clubs.MembershipForAsync(clubId, playerId); + if (row is null) return NotFound(); + return Ok(ToWireMembership(row)); + } + + // ── Member mutations (void / IPromise) ─────────────────────────── + + /// POST /club/{id}/members/requesttojoin — caller + /// asks to join. For Open clubs we promote straight to Member; for + /// RequestToJoin we mark Pending; InviteOnly clubs reject. + [HttpPost("/club/{clubId:long}/members/requesttojoin")] + [HttpPut("/club/{clubId:long}/members/requesttojoin")] + [Authorize] + public async Task MemberRequestToJoin(long clubId) + { + var club = await clubs.GetByIdAsync(clubId); + if (club is null) return NotFound(); + var perms = club.Joinability switch + { + 0 => 0, // Open: instant member + 1 => 128, // RequestToJoin: pending + _ => -1, // InviteOnly: rejected + }; + if (perms == -1) return Forbid(); + await clubs.UpsertMembershipAsync(clubId, Me, perms); + return Ok(); + } + + /// POST /club/{id}/members/directJoin — same as + /// requesttojoin for an Open club but bypasses the pending flow + /// even on RequestToJoin clubs (used by the "accept invite" flow + /// after a successful invite acceptance). + [HttpPost("/club/{clubId:long}/members/directJoin")] + [Authorize] + public async Task MemberDirectJoin(long clubId) + { + var club = await clubs.GetByIdAsync(clubId); + if (club is null) return NotFound(); + await clubs.UpsertMembershipAsync(clubId, Me, 0); + return Ok(); + } + + /// POST /club/{id}/members/leave — caller leaves + /// the club. Owner cannot leave without first transferring + /// ownership (mirrors the GroupsController.Leave guard). + [HttpPost("/club/{clubId:long}/members/leave")] + [Authorize] + public async Task MemberLeave(long clubId) + { + var club = await clubs.GetByIdAsync(clubId); + if (club is null) return NotFound(); + if (club.CreatorPlayerId == Me) + return BadRequest(new { error = "owner_must_transfer_first" }); + await clubs.RemoveMembershipAsync(clubId, Me); + return Ok(); + } + + /// POST /club/{id}/members/invite — caller invites + /// a target player. Pending=128 marker; the invited player's + /// acceptinvite flips it to Member. + public sealed class MemberInviteRequest + { + public int? PlayerId { get; set; } + public int? AccountId { get; set; } + public int? MembershipType { get; set; } + } + + [HttpPost("/club/{clubId:long}/members/invite")] + [HttpPut("/club/{clubId:long}/members/invite")] + [Authorize] + public async Task MemberInvite(long clubId, [FromBody] MemberInviteRequest req) + { + if (!await clubs.CanManageAsync(clubId, Me)) return Forbid(); + var target = req.PlayerId ?? req.AccountId ?? 0; + if (target == 0) return BadRequest(new { error = "missing_player_id" }); + await clubs.UpsertMembershipAsync(clubId, target, 128); // Pending + return Ok(); + } + + /// POST /club/{id}/members/invitemembers — bulk + /// invite. Body is {playerIds: [...]} + optional + /// membership-type override. + public sealed class InviteMembersRequest + { + public List? PlayerIds { get; set; } + public List? AccountIds { get; set; } + public int? MembershipType { get; set; } + } + + [HttpPost("/club/{clubId:long}/members/invitemembers")] + [Authorize] + public async Task MemberInviteMembers(long clubId, [FromBody] InviteMembersRequest req) + { + if (!await clubs.CanManageAsync(clubId, Me)) return Forbid(); + var ids = (req.PlayerIds ?? req.AccountIds ?? new List()).Distinct().ToList(); + foreach (var pid in ids) + await clubs.UpsertMembershipAsync(clubId, pid, 128); + return Ok(); + } + + /// POST /club/{id}/members/acceptinvite — caller + /// accepts an outstanding invite (their own pending row). + [HttpPost("/club/{clubId:long}/members/acceptinvite")] + [Authorize] + public async Task MemberAcceptInvite(long clubId) + { + var row = await clubs.MembershipForAsync(clubId, Me); + if (row is null) return NotFound(); + if ((row.Permissions & 128) == 0) return BadRequest(new { error = "no_pending_invite" }); + await clubs.UpsertMembershipAsync(clubId, Me, 0); // Member + return Ok(); + } + + /// POST /club/{id}/members/declineinvite — caller + /// rejects an outstanding invite (removes their pending row). + [HttpPost("/club/{clubId:long}/members/declineinvite")] + [Authorize] + public async Task MemberDeclineInvite(long clubId) + { + await clubs.RemoveMembershipAsync(clubId, Me); + return Ok(); + } + + public sealed class MemberTargetRequest + { + public int? PlayerId { get; set; } + public int? AccountId { get; set; } + public int? MembershipType { get; set; } + } + + /// POST /club/{id}/members/acceptrequest — owner / + /// moderator approves a single pending join request, promoting the + /// target's row from Pending to Member. + [HttpPost("/club/{clubId:long}/members/acceptrequest")] + [Authorize] + public async Task MemberAcceptRequest(long clubId, [FromBody] MemberTargetRequest req) + { + if (!await clubs.CanManageAsync(clubId, Me)) return Forbid(); + var target = req.PlayerId ?? req.AccountId ?? 0; + if (target == 0) return BadRequest(new { error = "missing_player_id" }); + var row = await clubs.MembershipForAsync(clubId, target); + if (row is null) return NotFound(); + if ((row.Permissions & 128) == 0) return BadRequest(new { error = "no_pending_request" }); + await clubs.UpsertMembershipAsync(clubId, target, 0); + return Ok(); + } + + public sealed class BulkTargetRequest + { + public List? PlayerIds { get; set; } + public List? AccountIds { get; set; } + } + + [HttpPost("/club/{clubId:long}/members/acceptrequests")] + [Authorize] + public async Task MemberAcceptRequests(long clubId, [FromBody] BulkTargetRequest req) + { + if (!await clubs.CanManageAsync(clubId, Me)) return Forbid(); + var ids = (req.PlayerIds ?? req.AccountIds ?? new List()).Distinct().ToList(); + foreach (var pid in ids) + { + var row = await clubs.MembershipForAsync(clubId, pid); + if (row is null || (row.Permissions & 128) == 0) continue; + await clubs.UpsertMembershipAsync(clubId, pid, 0); + } + return Ok(); + } + + [HttpPost("/club/{clubId:long}/members/denyrequest")] + [Authorize] + public async Task MemberDenyRequest(long clubId, [FromBody] MemberTargetRequest req) + { + if (!await clubs.CanManageAsync(clubId, Me)) return Forbid(); + var target = req.PlayerId ?? req.AccountId ?? 0; + if (target == 0) return BadRequest(new { error = "missing_player_id" }); + await clubs.RemoveMembershipAsync(clubId, target); + return Ok(); + } + + [HttpPost("/club/{clubId:long}/members/denyrequests")] + [Authorize] + public async Task MemberDenyRequests(long clubId, [FromBody] BulkTargetRequest req) + { + if (!await clubs.CanManageAsync(clubId, Me)) return Forbid(); + var ids = (req.PlayerIds ?? req.AccountIds ?? new List()).Distinct().ToList(); + foreach (var pid in ids) + await clubs.RemoveMembershipAsync(clubId, pid); + return Ok(); + } + + [HttpPost("/club/{clubId:long}/members/remove")] + [Authorize] + public async Task MemberRemove(long clubId, [FromBody] MemberTargetRequest req) + { + var club = await clubs.GetByIdAsync(clubId); + if (club is null) return NotFound(); + if (!await clubs.CanManageAsync(clubId, Me, club)) return Forbid(); + var target = req.PlayerId ?? req.AccountId ?? 0; + if (target == 0) return BadRequest(new { error = "missing_player_id" }); + if (target == club.CreatorPlayerId) + return BadRequest(new { error = "cannot_remove_owner" }); + await clubs.RemoveMembershipAsync(clubId, target); + return Ok(); + } + + /// POST /club/{id}/members/ban — kick + prevent + /// re-join. We persist with perms=256 as a ban marker bit so the + /// request-to-join flow can refuse them later. + [HttpPost("/club/{clubId:long}/members/ban")] + [Authorize] + public async Task MemberBan(long clubId, [FromBody] MemberTargetRequest req) + { + var club = await clubs.GetByIdAsync(clubId); + if (club is null) return NotFound(); + if (!await clubs.CanManageAsync(clubId, Me, club)) return Forbid(); + var target = req.PlayerId ?? req.AccountId ?? 0; + if (target == 0) return BadRequest(new { error = "missing_player_id" }); + if (target == club.CreatorPlayerId) + return BadRequest(new { error = "cannot_ban_owner" }); + await clubs.UpsertMembershipAsync(clubId, target, 256); // Ban marker + return Ok(); + } + + [HttpPost("/club/{clubId:long}/members/unban")] + [Authorize] + public async Task MemberUnban(long clubId, [FromBody] MemberTargetRequest req) + { + if (!await clubs.CanManageAsync(clubId, Me)) return Forbid(); + var target = req.PlayerId ?? req.AccountId ?? 0; + if (target == 0) return BadRequest(new { error = "missing_player_id" }); + await clubs.RemoveMembershipAsync(clubId, target); + return Ok(); + } + + /// POST /club/{id}/members/changetype — promote / + /// demote a member's role. Body: PlayerId + MembershipType (the + /// wire enum, NOT the perms int — we translate via + /// ). + [HttpPost("/club/{clubId:long}/members/changetype")] + [HttpPut("/club/{clubId:long}/members/changetype")] + [Authorize] + public async Task MemberChangeType(long clubId, [FromBody] MemberTargetRequest req) + { + var club = await clubs.GetByIdAsync(clubId); + if (club is null) return NotFound(); + if (club.CreatorPlayerId != Me) return Forbid(); // Owner-only — perms transitions are sensitive + var target = req.PlayerId ?? req.AccountId ?? 0; + var newType = req.MembershipType ?? ClubService.MembershipTypeMember; + if (target == 0) return BadRequest(new { error = "missing_player_id" }); + if (target == club.CreatorPlayerId) + return BadRequest(new { error = "cannot_change_owner_role" }); + await clubs.UpsertMembershipAsync(clubId, target, ClubService.PermsFromMembershipType(newType)); + return Ok(); + } + + // ── members/bulk + clubreporting ───────────────────────────────── + + /// GET /members/bulk?clubId=N&playerIds=A&playerIds=B + /// — bulk membership lookup. Wire shape is + /// List<JHMMGLNJHIB> with each entry carrying + /// AccountId + MembershipType. + [HttpGet("/members/bulk")] + [HttpPost("/members/bulk")] + public async Task MembersBulk() + { + if (!long.TryParse(Request.Query["clubId"].ToString(), out var clubId) + && !long.TryParse(Request.Form["clubId"].ToString(), out clubId)) + return BadRequest(new { error = "missing_club_id" }); + + var idsRaw = Request.Query["playerIds"].Concat(Request.Form["playerIds"]); + var ids = idsRaw + .SelectMany(v => (v ?? string.Empty).Split(',', StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries)) + .Select(s => long.TryParse(s, out var n) ? n : 0) + .Where(n => n != 0) + .ToList(); + if (ids.Count == 0) return Ok(Array.Empty()); + + var rows = await clubs.MembershipsBulkAsync(clubId, ids); + return Ok(rows.Select(m => new + { + AccountId = (int)m.PlayerId, + MembershipType = ClubService.MembershipTypeFromPerms(m.Permissions), + })); + } + + public sealed class ClubReportRequest + { + public long? ClubId { get; set; } + public int? Category { get; set; } + public string? Details { get; set; } + } + + /// POST /api/clubreporting/v1/report — file a + /// moderation report against a club. Persists to the same + /// table + /// the admin queue reads from; the controller prefixes the message + /// with "[club {id}]" so admins can tell club reports apart from + /// player reports without a schema change. + [HttpPost("/api/clubreporting/v1/report")] + [Authorize] + public async Task ClubReport([FromBody] ClubReportRequest req) + { + if (req.ClubId is not long cid) return BadRequest(new { error = "missing_club_id" }); + await clubs.AddClubReportAsync(cid, Me, req.Category ?? 0, req.Details ?? string.Empty); + return Ok(); + } + + // ── Announcements (per-club feed + per-announcement) ───────────── + + /// GET /announcements/club/{clubId} — full + /// announcement feed for the club. Wire shape mirrors the unread + /// rollup (HPACLJHLHBG envelope) but holds every visible + /// announcement, newest first. + [HttpGet("/announcements/club/{clubId:long}")] + public async Task AnnouncementsForClub(long clubId) + { + var rows = await clubs.AnnouncementsForClubAsync(clubId); + return Ok(GroupByClub(rows)); + } + + /// GET /announcements/club/{clubId}/{aid} — single + /// announcement read. Returns the inner Announcement wire object + /// (NFEMLMAFFIP) without the per-club envelope. + [HttpGet("/announcements/club/{clubId:long}/{announcementId:long}")] + public async Task AnnouncementSingle(long clubId, long announcementId) + { + var row = await clubs.AnnouncementAsync(clubId, announcementId); + if (row is null) return NotFound(); + return Ok(ToWireAnnouncement(row)); + } + + /// /announcements/club/{clubId}/{aid} delete — + /// register DELETE, PUT, and POST so the watch's mutation verb + /// reaches the same handler regardless of how its request-builder + /// shaped the call. The service is idempotent on missing rows so + /// double-fires are safe. + [HttpDelete("/announcements/club/{clubId:long}/{announcementId:long}")] + [HttpPost("/announcements/club/{clubId:long}/{announcementId:long}")] + [HttpPut("/announcements/club/{clubId:long}/{announcementId:long}")] + [Authorize] + public async Task AnnouncementDelete(long clubId, long announcementId) + { + if (!await clubs.DeleteAnnouncementAsync(clubId, announcementId, Me)) return Forbid(); + return Ok(); + } + + // ── Wire-shape mappers ────────────────────────────────────────── + + /// + /// Build the PIHMJGCGNLP envelope around a single club row. Used + /// by /club/{id}/details, /clubhouse, and every modify* handler. + /// MyMembershipType is derived from the caller's row (or + /// NotAMember sentinel when the caller has no row). + /// + private async Task BuildDetailsResponseAsync(long clubId) + { + var club = await clubs.GetByIdAsync(clubId); + if (club is null) return NotFound(); + var memberCount = await clubs.MemberCountAsync(clubId); + var pid = ControllerBaseExtensions.CurrentPlayerId(User); + var myType = ClubService.MembershipTypeNotMember; + if (pid is long me) + { + var membership = await clubs.MembershipForAsync(clubId, me); + if (membership is not null) + myType = ClubService.MembershipTypeFromPerms(membership.Permissions); + // Owner shortcut — the creator row may not exist (legacy data) but the + // creator should always read as Owner. + if (club.CreatorPlayerId == me) myType = ClubService.MembershipTypeOwner; + } + return Ok(new + { + // PIHMJGCGNLP deserializer at PIHMJGCGNLP.txt:143-194 reads + // 7 fields off the response dict, not the 5 the parser-keys + // doc lists. Missing any throws KeyNotFoundException — + // CustomTags (List) and AdditionalImages + // (List) need to be present even if empty. + Club = ToWireClub(club, memberCount), + CustomTags = Array.Empty(), + AdditionalImages = Array.Empty(), + CoownerPermissions = PermissionsForRole(ClubService.MembershipTypeCoOwner), + ModeratorPermissions = PermissionsForRole(ClubService.MembershipTypeModerator), + MemberPermissions = PermissionsForRole(ClubService.MembershipTypeMember), + MyMembershipType = myType, + }); + } + + /// + /// Bitmask of capabilities a given role has on a club. The bits + /// are arbitrary (the wire enum JHEEFBMODPG is opaque in + /// the readable dump) but stay consistent so the watch's + /// role-permissions screen renders the same checklist on every + /// refresh. Owner / Coowner can manage all; Moderator can manage + /// content; Member can read. + /// + private static int PermissionsForRole(int membershipType) => membershipType switch + { + ClubService.MembershipTypeOwner => 0x7FFF, // everything + ClubService.MembershipTypeCoOwner => 0x7FFE, // all except disband + ClubService.MembershipTypeModerator => 0x00FF, // moderation only + ClubService.MembershipTypeMember => 0x0007, // post + read + react + _ => 0x0001, // read-only + }; + + private static object ToWireMembership(ClubMembershipEntity row) => new + { + AccountId = (int)row.PlayerId, + ClubId = row.ClubId, + MembershipType = ClubService.MembershipTypeFromPerms(row.Permissions), + CreatedAt = row.JoinedAt, + }; + + private static ClubEntity EmptyClub() => new() + { + Id = 0, + Name = string.Empty, + Description = string.Empty, + CreatorPlayerId = 0, + ImageName = string.Empty, + }; + + /// + /// Project a to the 2020.12 Club wire + /// shape. JSON keys + types match the + /// PLILLKHMNDA.Deserialize body in ISIL exactly: + /// ClubId/State/CreatorAccountId/Visibility/Joinability/MemberCount + /// are ints, Name/Description/MainImageName/Category are strings, + /// AllowJuniors/IsRRO are bools, ClubhouseRoomId is nullable long. + /// + private static object ToWireClub(ClubEntity c, int memberCount) => new + { + ClubId = c.Id, + c.Name, + c.Description, + MainImageName = c.ImageName, + State = c.State, + CreatorAccountId = (int)c.CreatorPlayerId, + Category = c.Category, + Visibility = c.Visibility, + Joinability = c.Joinability, + AllowJuniors = c.AllowJuniors, + MemberCount = memberCount, + IsRRO = c.IsRRO, + ClubhouseRoomId = c.ClubhouseRoomId, + ClubType = c.ClubType, + }; + + /// + /// Project a to the 2020.12 + /// Announcement wire shape. Keys + types per the + /// NFEMLMAFFIP.Deserialize body in ISIL. + /// + private static object ToWireAnnouncement(ClubAnnouncementEntity a) => new + { + AnnouncementId = a.Id, + CreatorAccountId = (int)a.AuthorPlayerId, + ClubId = a.ClubId, + a.Title, + a.Body, + a.ImageName, + a.CreatedAt, + Meta = string.Empty, + }; + + private async Task ReadCreateClubRequestAsync() + { + if (Request.HasFormContentType) + { + var form = await Request.ReadFormAsync(); + return new CreateClubRequest + { + Name = FirstFormValue(form, "name", "Name"), + Description = FirstFormValue(form, "description", "Description"), + MainImageName = FirstFormValue(form, + "mainImageName", "MainImageName", "imageName", "ImageName"), + Category = FirstFormValue(form, "category", "Category"), + }; + } + + try + { + return await Request.ReadFromJsonAsync(JsonOptions) + ?? new CreateClubRequest(); + } + catch (JsonException) + { + return new CreateClubRequest(); + } + } + + private static string? FirstFormValue(IFormCollection form, params string[] names) + { + foreach (var name in names) + { + var value = form[name].ToString(); + if (!string.IsNullOrWhiteSpace(value)) return value; + } + return null; + } + + private static readonly JsonSerializerOptions JsonOptions = new() + { + PropertyNameCaseInsensitive = true, + }; + + /// + /// Group a flat list of announcements into the per-club + /// HPACLJHLHBG envelope shape. Uses + /// for the outer object + /// because the wire mixes lowercase and PascalCase keys for the + /// same record (clubId + LastReadAnnouncementId + + /// announcements) and anonymous types can't model that. + /// + private static List> GroupByClub( + List rows) + { + return rows + .GroupBy(a => a.ClubId) + .Select(g => + { + var ordered = g.OrderByDescending(a => a.CreatedAt).ToList(); + return new Dictionary + { + ["clubId"] = g.Key, + ["LastReadAnnouncementId"] = (long?)null, + ["announcements"] = ordered.Select(ToWireAnnouncement).ToList(), + }; + }) + .ToList(); + } +} diff --git a/DorkNet.Server/Controllers/Commerce/CommerceCatalogController.cs b/DorkNet.Server/Controllers/Commerce/CommerceCatalogController.cs new file mode 100644 index 0000000..e9a9830 --- /dev/null +++ b/DorkNet.Server/Controllers/Commerce/CommerceCatalogController.cs @@ -0,0 +1,220 @@ +using Microsoft.AspNetCore.Mvc; +using DorkNet.Server.Services; + +namespace DorkNet.Server.Controllers.Commerce; + +/// +/// commerce.rec.net/api/catalog/* — the actual catalog endpoint the +/// 2020 watch's Shop tab fetches when it loads. The previous +/// implementation routed everything on commerce.rec.net through the +/// generic StubController which always returned []; this +/// controller wins on specific routes via ASP.NET Core's +/// specific-over-wildcard precedence and returns the real +/// catalog. +/// +/// Wire shape verified against the watch's Commerce.Catalog +/// deserialiser logging "no items" when we returned an empty array +/// vs populating the Shop UI when each item carries SkuId/Name/Cost +/// keys. Each catalog DTO ships every common alias (PascalCase, +/// camelCase, snake_case) so whichever JSON field the watch's +/// strict-key reader expects, it finds. +/// +[ApiController] +public class CommerceCatalogController(StoreService store, DomainConfig domain) : ControllerBase +{ + [HttpGet("/api/catalog/v1/all")] + [HttpGet("/api/catalog/v2/all")] + [HttpGet("/api/catalog/v3/all")] + public async Task All([FromQuery] bool onlyAvailableSkus = false) + { + var items = onlyAvailableSkus + ? await store.GetAllActiveAsync() + : await store.GetAllActiveAsync(); // same set; we don't distinguish yet + return Ok(items.Select(i => ToCatalogDto(i, domain.Apex)).ToArray()); + } + + [HttpGet("/api/catalog/v1/sku/{slug}")] + [HttpGet("/api/catalog/v2/sku/{slug}")] + public async Task Sku(string slug) + { + var item = await store.GetBySlugAsync(slug); + if (item is null) return NotFound(); + return Ok(ToCatalogDto(item, domain.Apex)); + } + + /// POST /api/catalog/v1/byids — bulk lookup by id array. + /// The watch sends {Ids:[1,2,3]} as form-urlencoded; accept both + /// JSON and form bodies for safety. + [HttpPost("/api/catalog/v1/byids")] + [HttpPost("/api/catalog/v2/byids")] + public async Task ByIds() + { + string? raw = null; + if (Request.HasFormContentType) + { + var form = await Request.ReadFormAsync(); + raw = form["Ids"].ToString(); + if (string.IsNullOrWhiteSpace(raw)) raw = form["ids"].ToString(); + } + if (string.IsNullOrWhiteSpace(raw)) raw = Request.Query["Ids"].ToString(); + if (string.IsNullOrWhiteSpace(raw)) raw = Request.Query["ids"].ToString(); + if (string.IsNullOrWhiteSpace(raw)) return Ok(Array.Empty()); + + var ids = raw.Split(',', StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries) + .Select(s => long.TryParse(s, out var v) ? v : 0L) + .Where(v => v > 0) + .Distinct() + .ToArray(); + if (ids.Length == 0) return Ok(Array.Empty()); + + var all = await store.GetAllActiveAsync(); + return Ok(all.Where(i => ids.Contains(i.Id)).Select(i => ToCatalogDto(i, domain.Apex)).ToArray()); + } + + /// Catalog wire shape — every common key alias present + /// (PascalCase + camelCase + snake_case) so the watch's + /// strict-key deserialiser hits whichever name it's coded + /// against without crashing on a missing field. + /// + /// Implemented as a Dictionary because System.Text.Json's + /// case-insensitive collision check rejects anonymous types whose + /// property names differ only in casing (SkuId vs + /// skuId, etc.) — same trick we use in + /// RelationshipsController.BuildRelationshipResponse. + private static Dictionary ToCatalogDto(Data.Entities.StoreItemEntity i, string apex) + { + var available = i.IsActive && (i.AvailableUntil == null || i.AvailableUntil > DateTime.UtcNow); + var imageUrl = string.IsNullOrEmpty(i.ImageName) ? "" : $"https://cdn.{apex}/{i.ImageName}"; + return new Dictionary + { + // Identity / naming — every alias the watch's strict + // deserialiser might key on. + // + // CRITICAL: SkuId is typed int on + // RecNet.Commerce+SKU + // (Cpp2IL_CS/.../RecNet/Commerce.cs:80 + // private int <SkuId>k__BackingField). + // Util.GetKey<int>("SkuId") calls Convert.ChangeType, + // which throws FormatException if the value isn't + // parseable as an int. The previous shape shipped + // i.Slug (a "wardrobe-<guid>" string) under + // SkuId — that's the FormatException at + // output_log.txt:1655 which corrupts the catalog + // load and downstream destabilises the watch's player + // object (manifests as the dorm-load destroy crash). + // Ship the int row id under SkuId; keep the slug + // under Slug/Sku aliases for any string + // consumer. + ["SkuId"] = i.Id, + ["skuId"] = i.Id, + ["Sku"] = i.Slug, + ["sku"] = i.Slug, + ["ItemId"] = i.Id, + ["itemId"] = i.Id, + ["Id"] = i.Id, + ["id"] = i.Id, + ["Slug"] = i.Slug, + ["slug"] = i.Slug, + + ["Name"] = i.DisplayName, + ["name"] = i.DisplayName, + ["DisplayName"] = i.DisplayName, + ["displayName"] = i.DisplayName, + ["Title"] = i.DisplayName, + ["title"] = i.DisplayName, + + ["Description"] = i.Description, + ["description"] = i.Description, + + ["Category"] = i.Category, + ["category"] = i.Category, + ["ItemType"] = i.Category, + ["itemType"] = i.Category, + + // Cost / price — flat + nested forms. + ["Price"] = i.Price, + ["price"] = i.Price, + ["Cost"] = new Dictionary { ["Amount"] = i.Price, ["CurrencyType"] = i.CurrencyType }, + ["cost"] = new Dictionary { ["amount"] = i.Price, ["currencyType"] = i.CurrencyType }, + ["CurrencyType"] = i.CurrencyType, + ["currencyType"] = i.CurrencyType, + ["Amount"] = i.Price, + ["amount"] = i.Price, + + ["ImageName"] = i.ImageName, + ["imageName"] = i.ImageName, + ["Image"] = i.ImageName, + ["image"] = i.ImageName, + ["ImageUrl"] = imageUrl, + ["imageUrl"] = imageUrl, + + ["Available"] = available, + ["available"] = available, + ["IsAvailable"] = available, + ["isAvailable"] = available, + ["IsActive"] = i.IsActive, + ["isActive"] = i.IsActive, + ["IsLimitedTime"] = i.IsLimitedTime, + ["isLimitedTime"] = i.IsLimitedTime, + ["AvailableUntil"] = i.AvailableUntil, + ["availableUntil"] = i.AvailableUntil, + + ["Storefront"] = i.Storefront, + ["storefront"] = i.Storefront, + + ["CreatedAt"] = i.CreatedAt, + ["createdAt"] = i.CreatedAt, + + // Required RecNet.Commerce+SKU fields the watch's strict + // Util.GetKey deserialiser throws KeyNotFoundException on + // when missing. Verified at + // Cpp2IL_CS/.../RecNet/Commerce.cs:80-102 — SKU has + // OculusSkuId / AppleProductId / PSNProductLabel / + // IsSingleUse / Data / DisplayPrice / LongDescription as + // public-private (read+private-set) properties; missing + // any of them at deserialize time causes the SKU import + // to abort halfway and leaves the catalog in a half-built + // state that the StoreScreen UI binding then native-crashes + // on (output_log.txt:853 — last log line is a + // RRUI.Data.TypedDataSource resolving List.Count + // followed by silent process exit). + ["OculusSkuId"] = string.Empty, + ["oculusSkuId"] = string.Empty, + ["AppleProductId"] = string.Empty, + ["appleProductId"] = string.Empty, + ["PSNProductLabel"] = string.Empty, + ["psnProductLabel"] = string.Empty, + // XboxProductId — present in GCBPKHFJKCE+DJGBECJHOKF's + // required-key list at Cpp2IL_ISIL/.../GCBPKHFJKCE_NestedType_DJGBECJHOKF.txt:419. + // We don't ship Xbox, so empty string is correct; missing + // it entirely throws KeyNotFoundException on the watch + // and breaks the whole Shop ("malformed RecNet response" + // → store times out waiting for a usable catalog). + ["XboxProductId"] = string.Empty, + ["xboxProductId"] = string.Empty, + ["IsSingleUse"] = false, + ["isSingleUse"] = false, + ["DisplayPrice"] = i.Price.ToString(), + ["displayPrice"] = i.Price.ToString(), + ["LongDescription"] = i.Description ?? string.Empty, + ["longDescription"] = i.Description ?? string.Empty, + + // SKUData is itself an IRecNetObject with required keys — + // GiftDropIds (List), Message (string), + // SubscriptionPurchase (object). Empty list / empty + // string / null sub-purchase are valid. + ["Data"] = new Dictionary + { + ["GiftDropIds"] = Array.Empty(), + ["Message"] = string.Empty, + ["SubscriptionPurchase"] = (object?)null, + }, + ["data"] = new Dictionary + { + ["giftDropIds"] = Array.Empty(), + ["message"] = string.Empty, + ["subscriptionPurchase"] = (object?)null, + }, + }; + } +} diff --git a/DorkNet.Server/Controllers/Discovery/DiscoveryController.cs b/DorkNet.Server/Controllers/Discovery/DiscoveryController.cs new file mode 100644 index 0000000..b5ec221 --- /dev/null +++ b/DorkNet.Server/Controllers/Discovery/DiscoveryController.cs @@ -0,0 +1,30 @@ +using Microsoft.AspNetCore.Mvc; +using DorkNet.Server.Services; + +namespace DorkNet.Server.Controllers.Discovery; + +/// +/// discovery.{rec.net,localhost} — the watch's room-discovery surface +/// (rooms tab "For You" feed). Backed by the same RoomService used +/// elsewhere so what's hot here matches the main browser. +/// +[ApiController] +public class DiscoveryController(RoomService rooms) : ControllerBase +{ + [HttpGet("/discover/v2/rooms")] + public async Task GetRooms([FromQuery] string? tag, [FromQuery] int take = 50) + => Ok((await rooms.HotAsync(tag, Math.Clamp(take, 1, 100))) + .Select(RoomService.ToWireRoom).ToList()); + + [HttpGet("/discover/v2/feed")] + public async Task GetFeed([FromQuery] int take = 50) + => Ok((await rooms.HotAsync(null, Math.Clamp(take, 1, 100))) + .Select(RoomService.ToWireRoom).ToList()); + + [HttpGet("/discover/v2/featured")] + public async Task GetFeatured([FromQuery] int take = 50) + // Featured = tagged "featured" — same set as the watch's + // primary "Featured" filter chip. + => Ok((await rooms.HotAsync("featured", Math.Clamp(take, 1, 100))) + .Select(RoomService.ToWireRoom).ToList()); +} diff --git a/DorkNet.Server/Controllers/Econ/EconController.cs b/DorkNet.Server/Controllers/Econ/EconController.cs new file mode 100644 index 0000000..3e64c0c --- /dev/null +++ b/DorkNet.Server/Controllers/Econ/EconController.cs @@ -0,0 +1,81 @@ +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Mvc; +using Microsoft.EntityFrameworkCore; +using DorkNet.Server.Auth; +using DorkNet.Server.Data; +using DorkNet.Server.Services; +using System.Text.Json.Serialization; + +namespace DorkNet.Server.Controllers.Econ; + +/// +/// econ.{rec.net,localhost} — token balance + inventory queries used +/// by older client flows. Reads from +/// (currency type 0 = Token per CurrencyType.cs) and the +/// table. Note: the primary +/// store/inventory surface is api.*/api/storefronts/* + api/inventory/*; +/// this host is a legacy alias. +/// +[ApiController] +[Authorize] +public class EconController(DorkNetDbContext db, LevelService level) : ControllerBase +{ + [HttpGet("/econ/v1/balance/{accountId:long}")] + public async Task> GetBalance(long accountId) + { + var tokens = await level.GetBalanceAsync(accountId, 0); + return Ok(new TokenBalance + { + AccountId = accountId, + Tokens = (int)tokens, + BonusTokens = 0, + }); + } + + [HttpGet("/econ/v1/ownershipbatch")] + public async Task GetOwnershipBatch([FromQuery] string? ids) + { + var pid = this.RequireCurrentPlayerId(); + var slugs = (ids ?? string.Empty) + .Split(',', StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries) + .ToList(); + var q = db.PlayerInventory.Where(p => p.PlayerId == pid); + if (slugs.Count > 0) q = q.Where(p => slugs.Contains(p.ItemSlug)); + var rows = await q.Select(p => new { p.ItemSlug, p.Quantity }).ToListAsync(); + return Ok(rows); + } + + [HttpGet("/econ/v1/inventory/{accountId:long}")] + public async Task GetInventory(long accountId) + { + var rows = await db.PlayerInventory + .Where(p => p.PlayerId == accountId) + .Select(p => new { p.ItemSlug, p.Quantity }) + .ToListAsync(); + return Ok(rows); + } + + [HttpGet("/econ/v1/products")] + public async Task GetProducts() + { + var rows = await db.StoreItems + .Where(s => s.IsActive) + .Select(s => new + { + Id = s.Id, + Slug = s.Slug, + Name = s.DisplayName, + Price = s.Price, + CurrencyType = s.CurrencyType, + }) + .ToListAsync(); + return Ok(rows); + } +} + +public class TokenBalance +{ + [JsonPropertyName("AccountId")] public long AccountId { get; set; } + [JsonPropertyName("Tokens")] public int Tokens { get; set; } + [JsonPropertyName("BonusTokens")] public int BonusTokens { get; set; } +} diff --git a/DorkNet.Server/Controllers/Geo/GeoController.cs b/DorkNet.Server/Controllers/Geo/GeoController.cs new file mode 100644 index 0000000..577597d --- /dev/null +++ b/DorkNet.Server/Controllers/Geo/GeoController.cs @@ -0,0 +1,30 @@ +using Microsoft.AspNetCore.Mvc; + +namespace DorkNet.Server.Controllers.Geo; + +/// +/// geo.{apex}/v1/regions — region/server geography. Returns a single +/// configured region (we don't actually run multi-region; the watch's +/// region picker just needs one entry to populate the dropdown). +/// +/// The /v1/ping endpoint that previously lived here was +/// removed during the [Host]-strip refactor — both geo and ns +/// answered the same path (NsController.Ping is the canonical +/// one), and with host filters gone the two routes would have +/// ambiguous-matched on any subdomain. NsController wins because +/// its ping body carries the timestamp the keep-alive loop reads; +/// the geo version returned a bare {Ok:true} that nothing consumed. +/// +[ApiController] +public class GeoController(IConfiguration config) : ControllerBase +{ + [HttpGet("/v1/regions")] + public ActionResult> GetRegions() + { + var region = config["Photon:CloudRegion"] ?? "us"; + return Ok(new[] + { + new { Id = region, Name = region.ToUpperInvariant(), Ping = 50 }, + }); + } +} diff --git a/DorkNet.Server/Controllers/Health/HealthController.cs b/DorkNet.Server/Controllers/Health/HealthController.cs new file mode 100644 index 0000000..b538d03 --- /dev/null +++ b/DorkNet.Server/Controllers/Health/HealthController.cs @@ -0,0 +1,66 @@ +using Microsoft.AspNetCore.Mvc; +using Microsoft.EntityFrameworkCore; +using DorkNet.Server.Data; + +namespace DorkNet.Server.Controllers.Health; + +/// +/// GET /healthz — liveness/readiness probe for Coolify rolling deploys. +/// +/// Returns 503 until BOTH conditions hold: +/// 1. flag has been set by Program.cs after +/// db.Database.Migrate() returns (so a new replica that's still +/// running migrations doesn't get traffic shifted to it before its schema +/// is current). +/// 2. db.Database.CanConnectAsync() succeeds (catches transient DB +/// blips and prevents traffic during a Postgres restart). +/// +/// Coolify's rolling-deploy strategy waits for the new container's healthz +/// to return 200 before draining the old one. Combined with a brief +/// (~5–10s) `start-first` overlap, this is the closest thing to zero +/// downtime we get with single-region hosting. +/// +/// HostFilteringOptions is configured (via DomainConfig) to accept +/// localhost + 127.0.0.1 alongside the apex pool so the +/// Coolify internal healthcheck (which hits localhost:8080/healthz +/// from inside the container) reaches this handler — earlier +/// per-controller [Host(...)] filters would have 404ed it. +/// +/// Distinct from /v1/ping on the ns subdomain — that returns a +/// timestamped payload the watch's keep-alive loop reads and is part of +/// the wire contract. We deliberately don't change it. +/// +[ApiController] +public class HealthController(DorkNetDbContext db) : ControllerBase +{ + /// Set to true by Program.cs after the EF Core + /// Migrate() call returns successfully. Volatile because + /// it's read from request threads concurrently with the startup + /// thread's write. Process-local — every replica sets its own + /// flag once its own startup migrations finish. + public static volatile bool MigrationsComplete = false; + + // Order is more-negative than CdnController.Serve's wildcard + // (Order = -100). Without this the catch-all matched /healthz + // first on the localhost host (no subdomain) and returned + // 404 → Docker's HEALTHCHECK curl --fail exited non-zero → + // Coolify rolled the new container back. + [HttpGet("/healthz", Order = -1000)] + public async Task Healthz() + { + if (!MigrationsComplete) + return StatusCode(503, new { status = "starting", reason = "migrations_pending" }); + + // CanConnectAsync uses a cheap "SELECT 1"-equivalent — fast enough + // to run on every probe (~1ms for a warm pool). Cheaper than the + // alternative of failing only on actual query timeouts. + bool dbOk; + try { dbOk = await db.Database.CanConnectAsync(); } + catch { dbOk = false; } + + if (!dbOk) + return StatusCode(503, new { status = "unhealthy", reason = "db_unreachable" }); + + return Ok(new { status = "ok" }); + } +} diff --git a/DorkNet.Server/Controllers/Match/GoToController.cs b/DorkNet.Server/Controllers/Match/GoToController.cs new file mode 100644 index 0000000..3c1fa8a --- /dev/null +++ b/DorkNet.Server/Controllers/Match/GoToController.cs @@ -0,0 +1,1095 @@ +using Microsoft.AspNetCore.Mvc; +using Microsoft.EntityFrameworkCore; +using System.Text.Json.Serialization; +using DorkNet.Server.Auth; +using DorkNet.Server.Services; + +namespace DorkNet.Server.Controllers.Match; + +/// +/// match.rec.net/goto/* — matchmaking endpoints used by +/// RecNet.Matchmaking.GoToHelper / GoToDormRoom (the latter is what +/// BootSequence.LoadDormRoom invokes during initial scene load). +/// +/// Response shape verified by disassembling +/// RecNet.Matchmaking.MatchmakingResponse.Deserialize (RVA 0x1447DD0): +/// "errorCode" - int, optional (Util.GetKeyOrDefault<int>) +/// "roomInstance" - RoomInstance object, REQUIRED. The deserializer +/// throws explicitly with the error +/// "Matchmaking response has no RoomInstance field" +/// when this key is missing or non-object. +/// +/// The nested RoomInstance shape (RVA 0x114DAB0) requires: +/// roomInstanceId, roomId, subRoomId, location, photonRegionId, +/// photonRoomId, maxCapacity, isFull, isPrivate, isInProgress +/// (all required) plus optional dataBlob, eventId, name. +/// +/// The client then JOINS the Photon room named by photonRoomId in the +/// region photonRegionId — these two values are what tie our HTTP +/// matchmaking to the actual realtime networking. +/// +[ApiController] +public class GoToController( + IConfiguration config, + PlayerPresenceService presence, + PrivateInstanceService privateInstances, + RoomService rooms, + DorkNet.Server.Data.DorkNetDbContext db, + NotificationService notifications, + ILogger logger) : ControllerBase +{ + [HttpPost("/goto/room/{roomName}")] + public async Task> GoToRoom( + string roomName, + [FromForm(Name = "CreatePrivateInstance")] bool createPrivateInstance = false) + { + // Unknown room name shouldn't silently drop the player into a + // synth'd dorm scene any more — that masked typo bug reports + // ("why does /goto/^Crescendoo land me in my dorm?") and let + // dead links sit unfixed. Surface the real "no such room" + // error so the watch shows its standard "room not found" toast + // and the typo is obvious. DormRoom is a special case (per-player + // lazy-create), everything else MUST exist in db.Rooms. + if (!roomName.Equals("DormRoom", StringComparison.OrdinalIgnoreCase)) + { + var exists = await rooms.GetByNameAsync(roomName); + if (exists is null) + { + logger.LogWarning("[goto] unknown room name='{Name}' — returning RoomDoesNotExist", roomName); + return Ok(new MatchmakingResponseDto + { + // 4 = RoomDoesNotExist on + // RecNet.Matchmaking.CreateModifyRoomStatus enum. + // The watch reads ErrorCode != 0 and surfaces the + // standard "Room not found" toast. + ErrorCode = 4, + RoomInstance = null, + }); + } + } + var resp = await BuildResponseAsync(roomName); + if (createPrivateInstance) await ApplyPrivateInstanceAsync(resp, subRoomId: 0); + await RecordResponseAsync(resp); + return Ok(resp); + } + + [HttpPost("/goto/room")] + public async Task> GoToRoomFromBody( + [FromForm(Name = "RoomName")] string? roomNameForm, + [FromForm(Name = "roomName")] string? roomNameLower, + [FromForm(Name = "CreatePrivateInstance")] bool createPrivateInstance = false) + { + var roomName = roomNameForm + ?? roomNameLower + ?? Request.Query["RoomName"].FirstOrDefault() + ?? Request.Query["roomName"].FirstOrDefault(); + if (string.IsNullOrWhiteSpace(roomName)) + return BadRequest(new { error = "missing roomName" }); + return await GoToRoom(roomName, createPrivateInstance); + } + + [HttpPost("/goto/none")] + public IActionResult GoToNone() => Ok(); + + /// POST /goto/room/{room}/{subRoom} — sub-room form + /// from RecNet.Matchmaking.GoToSubRoom + /// (Cpp2IL_ISIL/.../Matchmaking.txt:4682-4702): + /// String.Concat("goto/room/", urlEncode(room), "/", urlEncode(subRoom)). + /// Used by baked AG-Original rooms with multiple scenes (e.g. + /// StuntRunner/TheMainEvent, the playable gauntlet inside + /// the StuntRunner lobby). + /// + /// We resolve to the parent room's for + /// the canonical RoomId + LocationReplicationId, then expose the + /// sub-room name via the name field so the client can + /// render the right scene. If we ever build a per-sub-room scene + /// location lookup table, switch + /// to the matching scene's ReplicationId. + [HttpPost("/goto/room/{roomName}/{subRoomName}")] + public async Task> GoToSubRoom( + string roomName, string subRoomName, + [FromForm(Name = "CreatePrivateInstance")] bool createPrivateInstance = false) + { + // Build (don't record yet) — we need to mutate the RoomInstance + // for the sub-room before presence is recorded, otherwise the + // server presence has the lobby's RoomInstance but the client + // thinks it's in the sub-room → heartbeat sees a mismatch and + // the watch logs "presence out-of-sync" / Error -2. + var resp = await BuildResponseAsync(roomName); + + // Imported multi-scene rooms (admin Import Room) get one + // RoomSceneEntity row per chapter — prefer that when the + // (room, subRoom) pair matches. Falls back to the hardcoded + // baked-room map for AG-Originals like StuntRunner/TheMainEvent. + // + // Lookup is tolerant: in-game portals don't always pass the + // canonical scene Name. We match in this order: + // 1. exact (case-insensitive — RoomScenes.Name is NOCASE collated) + // 2. EndsWith(subRoomName) — handles portals that omit + // a "Ch0_" / "Chapter1_" prefix + // 3. Contains(subRoomName) — last-resort fuzzy match + // and fall back to the hardcoded location map only when no + // RoomScenes row matches at all. + var allScenes = await db.RoomScenes + .Where(s => s.RoomId == resp.RoomInstance.RoomId) + .Select(s => new { s.OrderIndex, s.Name, s.RoomSceneLocationId, s.DataBlobName }) + .ToListAsync(); + + var dbScene = + allScenes.FirstOrDefault(s => + string.Equals(s.Name, subRoomName, StringComparison.OrdinalIgnoreCase)) + ?? allScenes.FirstOrDefault(s => + s.Name.EndsWith(subRoomName, StringComparison.OrdinalIgnoreCase)) + ?? allScenes.FirstOrDefault(s => + s.Name.IndexOf(subRoomName, StringComparison.OrdinalIgnoreCase) >= 0); + + long subId; + string? subLocation; + string? subDataBlob; + if (dbScene is not null) + { + logger.LogInformation( + "[goto-subroom] {Room}/{Requested} → matched scene '{MatchedName}' idx={Idx} loc={Loc} blob={Blob}", + roomName, subRoomName, dbScene.Name, dbScene.OrderIndex, + dbScene.RoomSceneLocationId, dbScene.DataBlobName); + subId = dbScene.OrderIndex; + subLocation = dbScene.RoomSceneLocationId; + subDataBlob = dbScene.DataBlobName; + } + else + { + logger.LogWarning( + "[goto-subroom] {Room}/{Requested} — no RoomScenes match (room has {Count} scenes: {Names}). Falling back to hardcoded MapSubRoomToLocationId.", + roomName, subRoomName, allScenes.Count, + string.Join(", ", allScenes.Select(s => s.Name))); + subId = Math.Abs((long)subRoomName.GetHashCode()); + subLocation = MapSubRoomToLocationId($"{roomName}/{subRoomName}".ToLowerInvariant()); + subDataBlob = null; + } + + // Both PhotonRoomId AND RoomInstanceId must differ for the + // watch's transition state machine to fire. Empirical from + // comparing the two failure modes: + // Same photon + same roomInstanceId → blob downloads (because + // dataBlob field changed) but watch never transitions; the + // "Going to X" dialog hangs because nothing the state + // machine tracks signalled "I'm in a different match". + // Different photon + same roomInstanceId → watch doesn't + // even fetch the new dataBlob; it sees roomInstanceId + // unchanged so treats the response as a no-op presence + // update and skips the join flow. + // Folding subId into RoomInstanceId AND forking PhotonRoomId + // gives the watch the two signals it needs: "new instance" + // (roomInstanceId differs → reset state machine) plus "new + // photon match" (photonRoomId differs → leave/rejoin → + // SceneLoaded events that ultimately dismiss the dialog). + resp.RoomInstance.RoomInstanceId = resp.RoomInstance.RoomId * 100_000L + subId; + resp.RoomInstance.PhotonRoomId = $"{resp.RoomInstance.PhotonRoomId}_sub{subId}"; + resp.RoomInstance.Name = subRoomName; + resp.RoomInstance.SubRoomId = subId; + if (subLocation is not null) resp.RoomInstance.Location = subLocation; + // dataBlob on the matchmaking response tells the client which + // PersistedRoomData blob to download for the entered scene. + // RoomInstance.Deserialize at RVA 0x114DAB0 reads it via the + // string-with-default reader (call 0x180EE6210), so empty is + // technically legal — but if it's empty the watch falls back + // to the parent room's RoomDataBlobName, which is the lobby's + // blob, so the sub-scene visually never changes. + if (!string.IsNullOrEmpty(subDataBlob)) resp.RoomInstance.DataBlob = subDataBlob; + + // Private instance mutation goes AFTER sub-room mutation so the + // PhotonRoomId we register includes the _sub{N} suffix as its + // base — invitees coming in via /goto/instance/{id} land in the + // exact same sub-room photon match, not the lobby's. + if (createPrivateInstance) await ApplyPrivateInstanceAsync(resp, subRoomId: subId); + + await RecordResponseAsync(resp); + return Ok(resp); + } + + /// Per-sub-room scene location overrides for baked + /// AG-Original rooms with multiple scenes. Each entry maps a + /// "{room}/{subRoom}" key (lowercase) to the + /// RoomSceneLocationId GUID baked into the client's + /// AGRoomRuntimeConfig.Locations table. Without an entry the + /// parent room's location is reused (lobby loads, door opens + /// but transition no-ops). + /// + /// Add entries here as you confirm each sub-scene's GUID — the + /// client's AGRoomSettings.TryGetRoomSceneLocationById + /// rejects unknown GUIDs with "RecNet game session contains + /// unknown scene location ID" and refuses to load. + /// Sub-room scene GUIDs extracted from the bundled + /// resources.assets AGRoomRuntimeConfig.Rooms[].Scenes[] + /// using tools/extract-rooms-binary.py. Each entry maps + /// "{room}/{subRoom}" (lowercase) to the target + /// RoomSceneLocationId GUID. Missing sub-rooms reuse the + /// parent room's lobby location (door opens but transition no-ops). + private static string? MapSubRoomToLocationId(string subKey) => subKey switch + { + // ── StuntRunner ── + // Lobby is the implicit Scene[0]; TheMainEvent is the + // playable gauntlet behind the door. + "stuntrunner/themainevent" => "3a636bd2-f896-424c-9225-c184522c0d87", + + // ── Paintball (River map + variants) ── + // The Paintball Room has nested sub-rooms for each map; the + // lobby acts as a hub and players pick a map from a board. + "paintball/river" => "e122fe98-e7db-49e8-a1b1-105424b6e1f0", + "paintball/homestead" => "a785267d-c579-42ea-be43-fec1992d1ca7", + "paintball/quarry" => "ff4c6427-7079-4f59-b22a-69b089420827", + "paintball/clearcut" => "380d18b5-de9c-49f3-80f7-f4a95c1de161", + "paintball/spillway" => "58763055-2dfb-4814-80b8-16fac5c85709", + "paintballvr/river" => "e122fe98-e7db-49e8-a1b1-105424b6e1f0", + "paintballvr/homestead" => "a785267d-c579-42ea-be43-fec1992d1ca7", + "paintballvr/quarry" => "ff4c6427-7079-4f59-b22a-69b089420827", + "paintballvr/clearcut" => "380d18b5-de9c-49f3-80f7-f4a95c1de161", + "paintballvr/spillway" => "58763055-2dfb-4814-80b8-16fac5c85709", + + // ── LaserTag ── + "lasertag/cyberjunkcity" => "9d6456ce-6264-48b4-808d-2d96b3d91038", + + _ => null, + }; + + [HttpPost("/goto/event/{eventId:long}")] + public async Task> GoToEvent(long eventId) + { + var resp = await BuildResponseAsync("DormRoom"); + await RecordResponseAsync(resp); + return Ok(resp); + } + + [HttpPost("/goto/club/{clubId:long}")] + public async Task> GoToClub(long clubId) + { + var club = await db.Clubs.AsNoTracking().FirstOrDefaultAsync(c => c.Id == clubId); + if (club is null) + { + return Ok(new MatchmakingResponseDto + { + ErrorCode = 4, + RoomInstance = new RoomInstanceDto(), + }); + } + + var resp = await BuildResponseAsync("RecCenter"); + resp.RoomInstance!.ClubId = club.Id; + await RecordResponseAsync(resp); + return Ok(resp); + } + + [HttpPost("/goto/code")] + [HttpPost("/goto/code/{code}")] + public async Task> GoToCode(string? code = null) + { + code ??= Request.HasFormContentType + ? (await Request.ReadFormAsync())["code"].FirstOrDefault() + : Request.Query["code"].FirstOrDefault(); + code = RoomCodeService.Normalize(code); + if (string.IsNullOrWhiteSpace(code)) + { + return Ok(new MatchmakingResponseDto + { + ErrorCode = 40, + RoomInstance = new RoomInstanceDto(), + }); + } + + var privateIds = await db.PrivateInstances.AsNoTracking() + .Select(p => p.Id) + .ToListAsync(); + var instanceId = privateIds.FirstOrDefault(id => RoomCodeService.Generate(id) == code); + if (instanceId == 0) + { + return Ok(new MatchmakingResponseDto + { + ErrorCode = 40, + RoomInstance = new RoomInstanceDto(), + }); + } + + var resp = await BuildPrivateInstanceJoinResponseAsync(instanceId); + await RecordResponseAsync(resp); + return Ok(resp); + } + + [HttpPost("/goto/playlist")] + [HttpPost("/goto/playlist/{playlist}")] + public async Task> GoToPlaylist(string? playlist = null) + { + playlist ??= Request.Query["playlist"].FirstOrDefault() + ?? Request.Query["playlistId"].FirstOrDefault(); + var value = (playlist ?? string.Empty).Trim(); + var roomName = "RecCenter"; + if (!string.IsNullOrWhiteSpace(value)) + { + var lowered = value.ToLowerInvariant(); + var room = await db.Rooms.AsNoTracking() + .Where(r => r.Accessibility == 2 + && (r.Name.ToLower().Contains(lowered) + || r.TagsCsv.ToLower().Contains(lowered))) + .OrderByDescending(r => r.HotScore) + .FirstOrDefaultAsync(); + if (room is not null) roomName = room.Name; + } + + var resp = await BuildResponseAsync(roomName); + await RecordResponseAsync(resp); + return Ok(resp); + } + + /// POST /goto/instance/{roomInstanceId} — invite- + /// accept route. The watch posts here when joining a private + /// match via a Message-attached invite. We resolve the instance + /// id to the registered + /// and return a RoomInstance pinned to the inviter's PhotonRoomId + /// so both players land in the same Photon match. Falls back to + /// dorm if the instance was forgotten (server restart, owner + /// already left and nobody re-created it). + [HttpPost("/goto/instance/{roomInstanceId:long}")] + public async Task> GoToInstance(long roomInstanceId) + { + var resp = await BuildPrivateInstanceJoinResponseAsync(roomInstanceId); + await RecordResponseAsync(resp); + return Ok(resp); + } + + [HttpPost("/goto/invite/{inviteId:long}")] + public async Task> GoToInvite(long inviteId) + { + var callerId = this.CurrentPlayerId(); + if (callerId is null) + { + var unauth = await BuildResponseAsync("DormRoom"); + await RecordResponseAsync(unauth); + return Ok(unauth); + } + + var invite = await db.Messages.FirstOrDefaultAsync(m => + m.Id == inviteId + && m.RecipientPlayerId == callerId.Value + && (m.Type == 6 || m.Type == 7)); + + long? roomInstanceId = null; + if (invite is not null && TryParseRoomInstanceId(invite.Body, out var parsed)) + roomInstanceId = parsed; + + // The 2020 watch's accept-invite flow fires + // POST /api/messages/v3/delete AND POST /goto/invite/{id} in + // parallel — verified in the live server log: + // 09:53:47.161Z DELETE messageIds=[60] + // 09:53:47.245Z /goto/invite/60 → [goto-invite] invite 60 not found + // DELETE usually wins the race, so the message row is gone + // by the time we run FirstOrDefaultAsync. The + // (instance, player) → LatestInviteMessageId mapping that + // SendInvite writes onto the PrivateInstanceInviteeEntity + // survives the message deletion and lets us recover + // roomInstanceId here. + if (roomInstanceId is null) + { + var inviteeRoomInstance = await db.PrivateInstanceInvitees + .Where(i => i.PlayerId == callerId.Value && i.LatestInviteMessageId == inviteId) + .Select(i => (long?)i.PrivateInstanceId) + .FirstOrDefaultAsync(); + if (inviteeRoomInstance is long inst) + { + roomInstanceId = inst; + logger.LogInformation( + "[goto-invite] message {InviteId} missing (likely raced with DELETE); resolved roomInstance={Instance} via invitee fallback for caller {Caller}", + inviteId, inst, callerId); + } + } + + if (roomInstanceId is null) + { + logger.LogWarning( + "[goto-invite] invite {InviteId} not found and no invitee fallback for caller {Caller}", + inviteId, callerId); + return Ok(new MatchmakingResponseDto + { + ErrorCode = 40, // RoomInviteExpired + RoomInstance = new RoomInstanceDto(), + }); + } + + // Mark the message read. The watch's parallel DELETE may have + // wiped the row between our initial read and this save — + // swallow the resulting DbUpdateConcurrencyException since the + // accept has already succeeded at this point. + if (invite is not null && invite.ReadAt is null) + { + invite.ReadAt = DateTime.UtcNow; + try + { + await db.SaveChangesAsync(); + } + catch (DbUpdateException) + { + db.Entry(invite).State = EntityState.Detached; + } + } + + // Two routes: + // PRIVATE instance: roomInstanceId resolves to a registered + // PrivateInstance → build a join response pinned to its + // photonRoomId + invite-list enforcement. + // + // PUBLIC room: no PrivateInstance for that id (sender was in + // a public room / a dorm / etc.). Use the message's RoomId + // to look up the room name and route via the standard public + // matchmaking path so the recipient lands in the same room + // (different instance is fine — public rooms aren't pinned + // to one Photon match). + var isPrivate = await privateInstances.GetAsync(roomInstanceId.Value) is not null; + MatchmakingResponseDto resp; + if (isPrivate) + { + resp = await BuildPrivateInstanceJoinResponseAsync(roomInstanceId.Value); + } + else if (invite?.RoomId is long publicRoomId && publicRoomId > 0) + { + var roomName = await db.Rooms + .Where(r => r.Id == publicRoomId) + .Select(r => r.Name) + .FirstOrDefaultAsync(); + if (string.IsNullOrWhiteSpace(roomName)) + { + logger.LogWarning( + "[goto-invite] invite {InviteId} → no PrivateInstance and Message.RoomId={RoomId} doesn't resolve to a Rooms row; routing to dorm", + inviteId, publicRoomId); + resp = await BuildResponseAsync("DormRoom"); + } + else + { + logger.LogInformation( + "[goto-invite] public-room invite {InviteId} → routing caller {Caller} to {RoomName} (RoomId={RoomId})", + inviteId, callerId, roomName, publicRoomId); + resp = await BuildResponseAsync(roomName); + } + } + else + { + // No private instance and the message has no RoomId. This + // shouldn't happen with the current SendInvite (it always + // stamps RoomId), but legacy rows from before the public- + // room invite fix may not have one. Fall back to dorm. + logger.LogWarning( + "[goto-invite] invite {InviteId} → no PrivateInstance and no Message.RoomId; routing to dorm", + inviteId); + resp = await BuildResponseAsync("DormRoom"); + } + await RecordResponseAsync(resp); + return Ok(resp); + } + + private async Task BuildPrivateInstanceJoinResponseAsync(long roomInstanceId) + { + var inst = await privateInstances.GetAsync(roomInstanceId); + if (inst is null) + { + logger.LogWarning( + "[goto-instance] {Id} → no registered private instance; routing to dorm", + roomInstanceId); + return await BuildResponseAsync("DormRoom"); + } + + // Enforce invite-list membership. Owner is always allowed (so + // they can rejoin their own match after a disconnect). Anyone + // else needs to have been invited via POST /invite. We refuse + // with errorCode != 0 so the watch surfaces a "join failed" + // toast instead of silently routing to the wrong photon room. + if (this.CurrentPlayerId() is long callerId + && !await privateInstances.CanJoinAsync(roomInstanceId, callerId)) + { + logger.LogWarning( + "[goto-instance] {Id} → caller {Caller} is not on the invite list (owner={Owner}); rejecting", + roomInstanceId, callerId, inst.OwnerPlayerId); + // MatchmakingErrorCode 26 = RoomInstanceIsPrivate — the + // dedicated "you're not invited" error from the + // RecNet.Matchmaking+MatchmakingErrorCode enum (verified in + // Cpp2IL_CS/.../Matchmaking.cs line 166). Surfaces a clearer + // "this is a private match" message on the watch than + // 9 = RoomNotJoinable does. + return new MatchmakingResponseDto + { + ErrorCode = 26, + RoomInstance = new RoomInstanceDto(), + }; + } + + return new MatchmakingResponseDto + { + ErrorCode = 0, + RoomInstance = new RoomInstanceDto + { + RoomInstanceId = inst.InstanceId, + RoomId = inst.RoomId, + SubRoomId = inst.SubRoomId, + Location = inst.Location, + PhotonRegionId = inst.PhotonRegion, + PhotonRoomId = inst.PhotonRoomId, + Name = inst.Name, + MaxCapacity = inst.MaxCapacity, + IsPrivate = true, + IsInProgress = true, + DataBlob = inst.DataBlob, + }, + }; + } + + // Invite-message Body can be any of three shapes depending on how / + // when it was written: + // 1. Modern (current): JSON {"inviteId":N,"name":"…","roomInstanceId":N} + // — extract roomInstanceId from the JSON property. + // 2. Legacy (pre-V2 fix): bare integer = roomInstanceId. + // 3. Ancient (pre-Type-column): text "Invite to private match + // (instance N)" — parse the trailing integer out of the literal. + // Honour all three so old rows in the DB still resolve. + private static bool TryParseRoomInstanceId(string body, out long instanceId) + { + instanceId = 0; + if (string.IsNullOrWhiteSpace(body)) return false; + if (body.TrimStart().StartsWith('{')) + { + try + { + using var doc = System.Text.Json.JsonDocument.Parse(body); + if (doc.RootElement.TryGetProperty("roomInstanceId", out var v)) + { + // String form first — the SendInvite serializer now + // emits roomInstanceId as a quoted string because + // LitJson on the watch fails to parse a JSON object + // that contains a 64-bit numeric literal larger than + // int32 (returns null from the dict cast → NRE in + // Util.GetKey downstream). Number form stays + // supported for any old rows that pre-dated the + // stringify fix. + if (v.ValueKind == System.Text.Json.JsonValueKind.String + && long.TryParse(v.GetString(), System.Globalization.NumberStyles.Integer, + System.Globalization.CultureInfo.InvariantCulture, out var strInst)) + { instanceId = strInst; return true; } + if (v.ValueKind == System.Text.Json.JsonValueKind.Number + && v.TryGetInt64(out var numInst)) + { instanceId = numInst; return true; } + } + } + catch { /* fall through to legacy formats */ } + } + if (long.TryParse(body, out instanceId)) return true; + var open = body.IndexOf("instance ", StringComparison.Ordinal); + if (open < 0) return false; + var tail = body[(open + "instance ".Length)..].TrimEnd(')', '.', ' '); + return long.TryParse(tail, out instanceId); + } + + /// POST /goto/player/{playerId} — join the room + /// the target player is currently in. + /// + /// For PUBLIC rooms: mirror the target's room name and rebuild a + /// fresh RoomInstance (the caller may end up in a different + /// matchmade instance, which is fine for public rooms). + /// + /// For PRIVATE rooms: this route must NOT bypass the invite list. + /// Earlier we mirrored the target's RoomInstance verbatim under + /// the assumption "Go to player only shows up for friends" — + /// that's wrong: friend ≠ invited, and the owner picked Private + /// specifically to control who joins. Caller must be the owner + /// or on the invite list per ; + /// otherwise we refuse with errorCode=9 (RoomNotJoinable). + [HttpPost("/goto/player/{playerId:long}")] + public async Task> GoToPlayer(long playerId) + { + var theirRoom = presence.GetRoom(playerId); + MatchmakingResponseDto resp; + if (theirRoom is null) + { + resp = await BuildResponseAsync("DormRoom"); + } + else if (theirRoom.IsPrivate) + { + // RoomInstanceId IS the registry key for private matches — + // generated by PrivateInstanceService.Register. Caller must + // be invited (or owner) to land in the same Photon room. + var callerId = this.CurrentPlayerId(); + if (callerId is null + || !await privateInstances.CanJoinAsync(theirRoom.RoomInstanceId, callerId.Value)) + { + logger.LogWarning( + "[goto-player] caller {Caller} tried to follow {Target} into private instance {Id} but isn't invited; rejecting", + callerId, playerId, theirRoom.RoomInstanceId); + // Notify the OWNER that someone tried to follow them in. + // Helps surface the rejection so the owner knows there's + // an interested party — they can choose to send an invite. + await notifications.PrivateInstanceJoinRejected( + ownerPlayerId: playerId, attemptedByPlayerId: callerId ?? 0); + return Ok(new MatchmakingResponseDto + { + ErrorCode = 26, // RoomInstanceIsPrivate + RoomInstance = new RoomInstanceDto(), + }); + } + // Allowed — mirror the target's RoomInstance so we land + // in the same Photon match. + resp = new MatchmakingResponseDto { ErrorCode = 0, RoomInstance = theirRoom }; + } + else + { + var roomName = await db.Rooms + .Where(r => r.Id == theirRoom.RoomId) + .Select(r => r.Name) + .FirstOrDefaultAsync(); + resp = await BuildResponseAsync(roomName ?? "DormRoom"); + } + await RecordResponseAsync(resp); + return Ok(resp); + } + + /// Record the just-built response against the bearer-token + /// player so the next heartbeat reflects the room they joined. + /// Anonymous /goto calls (no JWT) skip recording — the heartbeat is + /// authenticated, so a match only happens when the same JWT shows + /// up there. Without this, heartbeats return roomInstance:null and + /// the watch trips its "presence out-of-sync" detector → "Error: -2". + /// Caller is responsible for any sub-room or private-instance + /// mutations BEFORE handing the response in here. + private async Task RecordResponseAsync(MatchmakingResponseDto response) + { + if (this.CurrentPlayerId() is not long playerId) return; + + presence.SetRoom(playerId, response.RoomInstance); + + // Real visit tracking: upsert the per-(room, player) row so + // we get distinct VisitorCount (unique players ever) AND the + // gross VisitCount (every join, including rejoins). Skip the + // canonical dorm (RoomId=1) so per-day boots don't skew it. + if (response.RoomInstance.RoomId != 1) + await RecordVisitAsync(playerId, response.RoomInstance.RoomId); + + // Push a presence-update notification to every accepted-friend + // so their watch's "friends" list shows the new room. Cheap + // query — Relationships is small, and we only fan out to the + // actually-connected friends. + await NotifyFriendsOfMoveAsync(playerId, response.RoomInstance); + + // NOTE: we INTENTIONALLY do NOT self-push SubscriptionUpdateRoom + // from /goto. The watch's Rooms.OnSubscriptionUpdateRoom + // (Cpp2IL_ISIL/.../RecNet/Rooms.txt:2401-2496) is gated on + // (Rooms.cachedRoomId == roomId && Rooms.cachedSubRoomId == subRoomId): + // the FIRST push for a (room, subRoom) tuple does the fetch + + // set_LocalRoomDetails; subsequent pushes for the same tuple + // exit early at line 055, NEVER firing the + // RoomDetailsUpdated event. The save coroutine's + // RoomDetailsUpdatedCallback is registered to that event + // (RoomPersistenceManager+DisplayClass174_0:14-48) and only + // sets the roomDetailsUpdated flag when the event fires — + // which is the ONLY thing that flips the b__4 predicate true + // and dismisses the save spinner. Pre-pushing here primes the + // cached tuple so the save's own push gets deduplicated → no + // event → spinner wedges forever. Let the save's push be the + // first one for any (room, subRoom). + } + + /// Mutate the response so it represents a fresh invite-only + /// match: forks the PhotonRoomId with a per-(player, nonce) suffix + /// so randos can't matchmake into it, sets isPrivate=true, and + /// registers the instance in + /// so invitees coming in via /goto/instance/{id} can find this + /// exact Photon match. + private async Task ApplyPrivateInstanceAsync(MatchmakingResponseDto resp, long subRoomId) + { + if (this.CurrentPlayerId() is not long ownerId) + { + // Anonymous private-instance creation makes no sense — the + // owner is the keying signal for the photon room name and + // for invite resolution. Fall through with isPrivate flag + // only; the watch won't be able to invite anyone but at + // least won't mismatch the requested form value. + resp.RoomInstance.IsPrivate = true; + return; + } + + var inst = await privateInstances.RegisterAsync( + roomId: resp.RoomInstance.RoomId, + subRoomId: subRoomId, + ownerPlayerId: ownerId, + baseName: resp.RoomInstance.Name.ToLowerInvariant(), + location: resp.RoomInstance.Location, + dataBlob: resp.RoomInstance.DataBlob, + photonRegion: resp.RoomInstance.PhotonRegionId, + maxCapacity: resp.RoomInstance.MaxCapacity); + + resp.RoomInstance.RoomInstanceId = inst.InstanceId; + resp.RoomInstance.PhotonRoomId = inst.PhotonRoomId; + resp.RoomInstance.IsPrivate = true; + logger.LogInformation( + "[goto-private] room={Room} sub={Sub} owner={Owner} → instId={Id} photon={Photon}", + resp.RoomInstance.RoomId, subRoomId, ownerId, inst.InstanceId, inst.PhotonRoomId); + } + + /// Upsert the (room, player) + /// row for this join. New rows bump + /// (unique-visitor metric); + /// every join — new or repeat — bumps + /// + the per-player counter. + private async Task RecordVisitAsync(long playerId, long roomId) + { + var visit = await db.RoomVisits + .FirstOrDefaultAsync(v => v.RoomId == roomId && v.PlayerId == playerId); + var isFirst = visit is null; + if (isFirst) + { + visit = new DorkNet.Server.Data.Entities.RoomVisitEntity + { + RoomId = roomId, + PlayerId = playerId, + FirstVisitAt = DateTime.UtcNow, + }; + db.RoomVisits.Add(visit); + } + visit!.LastVisitAt = DateTime.UtcNow; + visit.VisitCount += 1; + + // Counter bumps go through ExecuteUpdate to avoid re-reading + // the room — atomic increment under concurrent /goto calls. + // Also bumps HotScore so the "Hot" tab self-reorders as + // players actually use rooms. Weight: each first-time visit + // adds 2.0, each repeat adds 0.25 (revisits matter less than + // new players). Cheers also feed in via PlayerStateController + // / MissingEndpointsController by the same factor. + if (isFirst) + { + await db.Rooms + .Where(r => r.Id == roomId) + .ExecuteUpdateAsync(s => s + .SetProperty(r => r.VisitCount, r => r.VisitCount + 1) + .SetProperty(r => r.VisitorCount, r => r.VisitorCount + 1) + .SetProperty(r => r.HotScore, r => r.HotScore + 2.0)); + } + else + { + await db.Rooms + .Where(r => r.Id == roomId) + .ExecuteUpdateAsync(s => s + .SetProperty(r => r.VisitCount, r => r.VisitCount + 1) + .SetProperty(r => r.HotScore, r => r.HotScore + 0.25)); + } + await db.SaveChangesAsync(); + } + + private async Task NotifyFriendsOfMoveAsync(long playerId, RoomInstanceDto room) + { + var friendIds = await db.Relationships + .Where(r => r.Status == DorkNet.Server.Data.Entities.RelationshipStatus.Friend && + (r.RequesterId == playerId || r.TargetId == playerId)) + .Select(r => r.RequesterId == playerId ? r.TargetId : r.RequesterId) + .ToListAsync(); + logger.LogInformation( + "[goto-fanout] player={PlayerId} moved to room={RoomId}/{RoomName} → notifying {FriendCount} friend(s)", + playerId, room.RoomId, room.Name, friendIds.Count); + if (friendIds.Count == 0) return; + await notifications.PlayerEnteredRoom(friendIds, playerId, room); + } + + private async Task BuildResponseAsync(string roomName) + { + // Try to resolve from the Rooms DB first — that gives us the real + // RoomId (so subsequent /api/rooms/v4/details/{id} hits the same + // entity) and the canonical LocationReplicationId. Fall back to the + // baked-in DormRoom mapping for unknown names. + long roomId; + string locationId; + if (roomName.Equals("DormRoom", StringComparison.OrdinalIgnoreCase)) + { + // Each player has their own dorm row (created at signup, + // lazy-created here for legacy accounts that pre-date the + // per-player dorm work). Resolve to the caller's personal + // dorm so /api/rooms/v4/details/{id} hits a real Rooms.id + // and storage.rec.net/upload's room lookup succeeds. + if (this.CurrentPlayerId() is long callerPid) + { + var personal = await rooms.EnsurePersonalDormAsync(callerPid); + roomId = personal.Id; + locationId = personal.LocationReplicationId; + } + else + { + // Anonymous /goto/room/DormRoom shouldn't happen in the + // 2020 watch (the watch always authenticates first), but + // fall back to the canonical scene id for safety. + roomId = 1; + locationId = "76d98498-60a1-430c-ab76-b54a29b7a163"; + } + } + else + { + var room = await rooms.GetByNameAsync(roomName); + if (room is not null) + { + roomId = room.Id; + locationId = room.LocationReplicationId; + } + else + { + roomId = MapRoomNameToId(roomName); + locationId = MapRoomNameToReplicationId(roomName); + } + } + var photonRegion = (config["Photon:CloudRegion"] ?? "us").ToLowerInvariant(); + + // DormRoom is per-player (each account has its own dorm); Rec Center + // and other public rooms are shared by name. Including the player's + // id in the dorm room name avoids the case where rebuilding the DB + // gives a fresh accountId but Photon still considers the old + // "recroom_dormroom_1" room locked from the previous account's + // session — manifested as a successful /goto but no actual scene + // load (black screen). + var isDorm = roomName.Equals("DormRoom", StringComparison.OrdinalIgnoreCase); + var dormOwnerId = this.CurrentPlayerId(); + var photonRoom = (isDorm && dormOwnerId is long ownerForName) + ? $"^dormroom_p{ownerForName}" + : $"^{roomName.ToLowerInvariant()}_{roomId}"; + var dataBlob = await ResolveInitialDataBlobAsync(roomId, isDorm, dormOwnerId); + + var resp = new MatchmakingResponseDto + { + ErrorCode = 0, + RoomInstance = new RoomInstanceDto + { + // Deterministic: same name → same instance id, every time. + RoomInstanceId = roomId * 100_000L, + RoomId = roomId, + SubRoomId = 0, + // location is a STRING replicationId, parsed via + // RoomSceneLocations.TryGetRoomSceneLocationById (RVA 0x10F57D0). + // The value is matched against AGRoomRuntimeConfig.Room.ReplicationId + // entries baked into resources.assets. Sending an int (or wrong + // string) makes RoomInstance.Deserialize log "RecNet game session + // contains unknown scene location ID" and the scene never loads. + Location = locationId, + // photonRegionId is a STRING that maps to CloudRegionCode enum + // (eu, us, asia, jp, none, au, usw, sa, cae, kr, in, ru, rue). + // Lowercase enum names — that's the wire format Photon uses too. + PhotonRegionId = photonRegion, + PhotonRoomId = photonRoom, + Name = roomName, + MaxCapacity = 8, + IsFull = false, + IsPrivate = false, + IsInProgress = true, + DataBlob = dataBlob, + }, + }; + logger.LogInformation( + "[goto] room instance room={RoomId} name={RoomName} player={PlayerId} dataBlob={DataBlob}", + roomId, roomName, dormOwnerId, dataBlob); + + // Dorms are always private — the per-player photon-room name + // (^dormroom_p{owner}) prevents matchmaking, but without + // IsPrivate=true the /goto/player path mirrors the room + // verbatim and lets uninvited friends drop straight in. Stamp + // the response with a deterministic-id PrivateInstance owned + // by the dorm's owner so CanJoinAsync gates non-owners. + if (isDorm && dormOwnerId is long ownerPid) + { + var dorm = await privateInstances.EnsureForDormAsync( + ownerPlayerId: ownerPid, + roomId: resp.RoomInstance.RoomId, + subRoomId: 0, + baseName: resp.RoomInstance.Name, + location: resp.RoomInstance.Location, + dataBlob: resp.RoomInstance.DataBlob, + photonRegion: resp.RoomInstance.PhotonRegionId, + maxCapacity: resp.RoomInstance.MaxCapacity, + photonRoomId: resp.RoomInstance.PhotonRoomId); + resp.RoomInstance.RoomInstanceId = dorm.InstanceId; + resp.RoomInstance.IsPrivate = true; + } + + return resp; + } + + private async Task ResolveInitialDataBlobAsync(long roomId, bool isDorm, long? dormOwnerId) + { + var room = await db.Rooms.AsNoTracking().FirstOrDefaultAsync(r => r.Id == roomId); + if (room is null) return string.Empty; + + if (isDorm && dormOwnerId is long ownerId) + { + var dormBlob = await db.DormStates.AsNoTracking() + .Where(d => d.PlayerId == ownerId) + .Select(d => d.CurrentDataBlobName) + .FirstOrDefaultAsync(); + if (!string.IsNullOrWhiteSpace(dormBlob)) return dormBlob; + } + + if (!string.IsNullOrWhiteSpace(room.CurrentDataBlobName)) return room.CurrentDataBlobName; + + var customisable = room.IsDormRoom || room.CreatorPlayerId != 1; + return customisable ? $"room_{room.Id}_v1.dat" : string.Empty; + } + + /// + /// Stable id-per-room-name map for well-known rooms; unknown names get + /// a hash-based id so each room name routes to its own roomId space. + /// + private static long MapRoomNameToId(string name) => name.ToLowerInvariant() switch + { + "dormroom" => 1, + "rec_center" or "reccenter" => 2, + "lobby" => 3, + _ => Math.Abs(name.GetHashCode()) % 100_000 + 1000, + }; + + /// + /// Maps a friendly room name to the RoomScene.RoomSceneLocationId GUID + /// baked into resources.assets's AGRoomRuntimeConfig.Rooms[].Scenes[0]. + /// This is the foreign key that links a Room's "Home" scene to an + /// AGRoomRuntimeConfig.Locations entry — and is what + /// AGRoomSettings.TryGetRoomSceneLocationById (RVA 0x10F57D0) matches + /// against Location.ReplicationId. + /// + /// The asset stores each Room as [Name] [Room.ReplicationId] + /// [Description] [Scene.Name="Home"] [Scene.ReplicationId] + /// [Scene.RoomSceneLocationId] — so the THIRD GUID after the room name + /// is the one we want. Earlier I sent the FIRST (Room.ReplicationId) + /// which is a different namespace entirely; the deserializer logged + /// "unknown scene location ID" because no Location had that GUID. + /// + private static string MapRoomNameToReplicationId(string name) => name.ToLowerInvariant() switch + { + "dormroom" => "76d98498-60a1-430c-ab76-b54a29b7a163", + "rec_center" or "reccenter" => "cbad71af-0831-44d8-b8ef-69edafa841f6", + "3dcharades" or "charades" => "4078dfed-24bb-4db7-863f-578ba48d726b", + "discgolflake" or "lake" => "f6f7256c-e438-4299-b99e-d20bef8cf7e0", + "discgolfpropulsion" or "propulsion" => "d9378c9f-80bc-46fb-ad1e-1bed8a674f55", + "dodgeball" => "3d474b26-26f7-45e9-9a36-9b02847d5e6f", + "paddleball" => "d89f74fa-d51e-477a-a425-025a891dd499", + "river" => "e122fe98-e7db-49e8-a1b1-105424b6e1f0", + "homestead" => "a785267d-c579-42ea-be43-fec1992d1ca7", + "quarry" => "ff4c6427-7079-4f59-b22a-69b089420827", + "clearcut" => "380d18b5-de9c-49f3-80f7-f4a95c1de161", + "spillway" => "58763055-2dfb-4814-80b8-16fac5c85709", + "drive-in" or "drivein" => "65ddbb48-5a01-4e3e-972d-e5c7419e2bc3", + "soccer" => "7e01cfe0-820a-406f-b1b3-0a5bf575235c", + "lasertag" => "6d5eea4b-f069-4ed0-9916-0e2f07df0d03", + "cyberjunkcity" or "lasertagcyberjunk" => "9d6456ce-6264-48b4-808d-2d96b3d91038", + "recroyalesquads" => "253fa009-6e65-4c90-91a1-7137a56a267f", + "recroyalesolos" => "85b43509-77f7-3884-3a6b-9a4e737d6d11", + _ => "76d98498-60a1-430c-ab76-b54a29b7a163", // DormRoom + }; +} + +public class MatchmakingResponseDto +{ + [JsonPropertyName("errorCode")] + public int ErrorCode { get; set; } + + [JsonPropertyName("roomInstance")] + public RoomInstanceDto? RoomInstance { get; set; } = new(); +} + +/// +/// Mirrors RecNet.RoomInstance — Deserialize at RVA 0x114DAB0. Keys +/// verified by disassembly. JSON casing is camelCase throughout. +/// +public class RoomInstanceDto +{ + [JsonPropertyName("roomInstanceId")] + public long RoomInstanceId { get; set; } + + [JsonPropertyName("roomId")] + public long RoomId { get; set; } + + [JsonPropertyName("subRoomId")] + public long SubRoomId { get; set; } + + // STRING RoomSceneLocationId GUID — foreign-keys to + // AGRoomRuntimeConfig.Locations[].ReplicationId. Resolved via + // AGRoomSettings.TryGetRoomSceneLocationById (RVA 0x10F57D0). Default + // = DormRoom's home-scene location ID extracted from resources.assets + // at offset 0x65107ec. + [JsonPropertyName("location")] + public string Location { get; set; } = "76d98498-60a1-430c-ab76-b54a29b7a163"; + + // STRING enum-name — CloudRegionCode is parsed by name: eu, us, asia, + // jp, none, au, usw, sa, cae, kr, in, ru, rue. Lowercase. + [JsonPropertyName("photonRegionId")] + public string PhotonRegionId { get; set; } = "us"; + + [JsonPropertyName("photonRoomId")] + public string PhotonRoomId { get; set; } = string.Empty; + + [JsonPropertyName("maxCapacity")] + public int MaxCapacity { get; set; } = 8; + + [JsonPropertyName("isFull")] + public bool IsFull { get; set; } = false; + + [JsonPropertyName("isPrivate")] + public bool IsPrivate { get; set; } = false; + + [JsonPropertyName("isInProgress")] + public bool IsInProgress { get; set; } = true; + + // Optional keys per disassembly (Util.GetKeyOrDefault). + [JsonPropertyName("dataBlob")] + public string DataBlob { get; set; } = string.Empty; + + [JsonPropertyName("eventId")] + public long EventId { get; set; } = 0; + + [JsonPropertyName("name")] + public string Name { get; set; } = string.Empty; + + // 2020.12 added these keys to RoomInstance.Deserialize. Verified via + // Cpp2IL ISIL on CIIBGMBOFEI.PPGFHEDFBEA (the obfuscated RoomInstance + // class). Missing → "Malformed Response" in matchmaking. + [JsonPropertyName("roomInstanceType")] + public int RoomInstanceType { get; set; } = 0; + + [JsonPropertyName("clubId")] + public long ClubId { get; set; } = 0; + + [JsonPropertyName("roomCode")] + public string RoomCode { get; set; } = string.Empty; + + // NOTE: PascalCase 'E'. The 2020.12 ISIL literally loads "EncryptVoiceChat" + // (not "encryptVoiceChat"). LitJson is case-sensitive. + [JsonPropertyName("EncryptVoiceChat")] + public bool EncryptVoiceChat { get; set; } = false; + + /// + /// Returns a copy of with photonRoomId + /// and dataBlob blanked out — safe to send to OTHER players + /// (friend-list presence, push updates). The watch only needs + /// roomId / name / isPrivate to render the + /// friend-list row; it joins via /goto/player/{id} or + /// /goto/instance/{id}, both of which enforce + /// . + /// + /// Without this redaction, anyone could read another player's + /// presence and use the leaked photonRoomId to JoinByName + /// in Photon directly, bypassing our invite-list check. + /// + public static RoomInstanceDto? Redact(RoomInstanceDto? room) + { + if (room is null) return null; + return new RoomInstanceDto + { + RoomInstanceId = room.RoomInstanceId, + RoomId = room.RoomId, + SubRoomId = room.SubRoomId, + Location = room.Location, + PhotonRegionId = room.PhotonRegionId, + PhotonRoomId = string.Empty, + MaxCapacity = room.MaxCapacity, + IsFull = room.IsFull, + IsPrivate = room.IsPrivate, + IsInProgress = room.IsInProgress, + DataBlob = string.Empty, + EventId = room.EventId, + Name = room.Name, + }; + } +} diff --git a/DorkNet.Server/Controllers/Match/MatchController.cs b/DorkNet.Server/Controllers/Match/MatchController.cs new file mode 100644 index 0000000..43379b5 --- /dev/null +++ b/DorkNet.Server/Controllers/Match/MatchController.cs @@ -0,0 +1,328 @@ +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Mvc; +using Microsoft.EntityFrameworkCore; +using System.Text.Json; +using DorkNet.Models.GameSessions; +using DorkNet.Server.Auth; +using DorkNet.Server.Data; +using DorkNet.Server.Data.Entities; +using DorkNet.Server.Services; + +namespace DorkNet.Server.Controllers.Match; + +/// +/// match.rec.net — dedicated matchmaking service used by later 2020 builds. +/// Delegates to the Postgres-backed GameSessionService. +/// +[ApiController] +[Authorize] +public class MatchController( + GameSessionService sessionService, + PlayerPresenceService presence, + PrivateInstanceService privateInstances, + DorkNetDbContext db, + ILogger logger) : ControllerBase +{ + /// POST match.*/roominstance/{id}/reportjoinresult — + /// the watch fires this AFTER its Photon connect+join attempt, + /// reporting how it went via RecNet.Matchmaking+GameJoinResult + /// (Matchmaking.cs:121): + /// 0 = Success, 1 = FailedToConnectToRegion, + /// 2 = FailedToConnectToRoom, 3 = FailedToSpawnPlayer. + /// Body is form-urlencoded result=N. Watch ignores the + /// response body and just needs 2xx (Matchmaking.txt:6668-6699), so + /// we mostly use this for visibility into Photon-side issues — + /// without it, all we see in the catch-all is an opaque "200, empty + /// reply" and no clue why a player crashed back to dorm. + [HttpPost("/roominstance/{instanceId:long}/reportjoinresult")] + public IActionResult ReportJoinResult(long instanceId, [FromForm(Name = "result")] int? result) + { + var code = result ?? -1; + var label = code switch + { + 0 => "Success", + 1 => "FailedToConnectToRegion", + 2 => "FailedToConnectToRoom", + 3 => "FailedToSpawnPlayer", + _ => $"Unknown({code})", + }; + var pid = this.RequireCurrentPlayerId(); + if (code == 0) + { + logger.LogInformation( + "[joinresult] player={Player} instance={Instance} OK ({Label})", + pid, instanceId, label); + } + else + { + logger.LogWarning( + "[joinresult] player={Player} instance={Instance} FAILED code={Code} ({Label}) — Photon-side error, check Photon AppId/region/auth", + pid, instanceId, code, label); + } + return Ok(); + } + + + /// PUT /roominstance/{id}/inprogress — fired by + /// RecNet.Matchmaking.SetGameInProgress(bool inProgress) + /// (Matchmaking.cs:333). Body is form-urlencoded + /// inProgress=true|false. The watch uses this to mark a + /// match as "in progress" so newcomers are sent to a fresh instance + /// instead of joining a game already underway (e.g. paintball + /// during a round). Our matchmaker doesn't gate on this yet — log + /// + 200 to take it off the catch-all. + [HttpPut("/roominstance/{instanceId:long}/inprogress")] + [Consumes("application/x-www-form-urlencoded", "multipart/form-data")] + public IActionResult SetInProgress( + long instanceId, + [FromForm(Name = "inProgress")] bool? inProgress) + { + var pid = this.RequireCurrentPlayerId(); + logger.LogInformation( + "[set-inprogress] player={Player} instance={Instance} inProgress={Flag}", + pid, instanceId, inProgress); + return Ok(); + } + + /// POST /roominstance/{id}/markprivate — promote + /// the caller's current public instance to an invite-gated + /// PrivateInstance row. The watch only needs a RecNet result + /// wrapper, but the server persists the privacy state so later + /// invite joins are enforced by . + [HttpPost("/roominstance/{instanceId:long}/markprivate")] + public async Task MarkPrivate(long instanceId) + { + var pid = this.RequireCurrentPlayerId(); + var existing = await db.PrivateInstances.FirstOrDefaultAsync(p => p.Id == instanceId); + if (existing is not null) + { + if (existing.OwnerPlayerId != pid) return Forbid(); + return Ok(new { Success = true, Error = string.Empty }); + } + + var current = presence.GetRoom(pid); + if (current is null || current.RoomInstanceId != instanceId) + return NotFound(new { Success = false, Error = "room instance not found for current player" }); + + db.PrivateInstances.Add(new PrivateInstanceEntity + { + Id = instanceId, + RoomId = current.RoomId, + SubRoomId = current.SubRoomId, + OwnerPlayerId = pid, + PhotonRoomId = current.PhotonRoomId, + Location = current.Location, + DataBlob = current.DataBlob, + PhotonRegion = current.PhotonRegionId, + MaxCapacity = current.MaxCapacity, + Name = current.Name, + CreatedAt = DateTime.UtcNow, + }); + await db.SaveChangesAsync(); + logger.LogInformation("[mark-private] player={Player} instance={Instance}", pid, instanceId); + return Ok(new { Success = true, Error = string.Empty }); + } + + /// GET/POST /roominstance/{id}/roomcode — raw + /// JSON string room code shown by the watch. Codes are stable for + /// an instance id and do not expose the numeric id directly. + [HttpGet("/roominstance/{instanceId:long}/roomcode")] + [HttpPost("/roominstance/{instanceId:long}/roomcode")] + public IActionResult RoomCode(long instanceId) + { + var code = RoomCodeService.Generate(instanceId); + return Content(JsonSerializer.Serialize(code), "application/json"); + } + + [HttpPost("/v1/find")] + public async Task> Find([FromBody] JoinRandomGameSessionRequest req) + { + var session = await sessionService.JoinOrCreateAsync(req.RoomId, req.ActivityLevelId, req.RegionId); + return Ok(new JoinGameSessionResponse { Result = JoinGameErrorCode.Success, GameSession = session }); + } + + [HttpPost("/v1/join/{sessionId:long}")] + public async Task> Join(long sessionId) + { + var session = await sessionService.GetByIdAsync(sessionId); + if (session is null) return Ok(new JoinGameSessionResponse { Result = JoinGameErrorCode.NotFound }); + if (session.IsFull) return Ok(new JoinGameSessionResponse { Result = JoinGameErrorCode.Full }); + return Ok(new JoinGameSessionResponse { Result = JoinGameErrorCode.Success, GameSession = session }); + } + + [HttpPost("/v1/leave/{sessionId:long}")] + public async Task Leave(long sessionId) + { + await sessionService.PlayerLeftAsync(sessionId); + return Ok(); + } + + [HttpGet("/v1/session/{sessionId:long}")] + public async Task> GetSession(long sessionId) + { + var session = await sessionService.GetByIdAsync(sessionId); + if (session is null) return NotFound(); + return Ok(session); + } + + /// GET match.*/room/{id}/instances — the + /// "RoomInstanceBrowser" the watch shows when you tap a room tile. + /// Returns the currently-active instances so the player can see + /// which one their friends are in (and join it) instead of being + /// matchmade into a fresh empty room. Verified URL at + /// Matchmaking.txt:6286; response is a + /// List<SimpleRoomInstance> per + /// SimpleRoomInstance.cs: RoomInstanceId/RoomId/SubRoomId, + /// IsFull, CreatedAt, PlayerIds, SubroomName, PlayerCount, + /// HasModPresent, HashedInstanceId. + /// + /// We derive instances from PlayerPresenceService's per-player + /// RoomInstanceDto: every distinct PhotonRoomId in the same RoomId + /// is one instance, with the players currently presenced there. + /// PrivateInstances are surfaced as well so the inviter and + /// invitee see the same room in the browser. + [HttpGet("/room/{roomId:long}/instances")] + [AllowAnonymous] + public async Task RoomInstances(long roomId) + { + var presenced = presence.EnumerateActiveInstances(roomId).ToList(); + // Only surface the caller's OWN private instances — we never + // leak someone else's PhotonRoomId into the browser. The Photon + // join check still requires CanJoinAsync, but redacting at the + // list level matches the existing RoomInstanceDto.Redact policy. + var pid = this.CurrentPlayerId() ?? 0; + var privates = pid == 0 + ? new List() + : await db.PrivateInstances + .Where(p => p.RoomId == roomId && p.OwnerPlayerId == pid) + .ToListAsync(); + + // Merge: presence rows are authoritative (player currently + // there); add any owned private instance the inviter created + // but nobody's joined yet so the inviter can still see it. + var byKey = new Dictionary(StringComparer.Ordinal); + foreach (var (room, playerIds) in presenced) + { + if (string.IsNullOrEmpty(room.PhotonRoomId)) continue; + byKey[room.PhotonRoomId] = new SimpleInstance( + RoomInstanceId: room.RoomInstanceId, + RoomId: room.RoomId, + SubRoomId: room.SubRoomId, + IsFull: false, + CreatedAt: DateTime.UtcNow, + PlayerIds: playerIds.Select(p => (int)p).ToList(), + SubroomName: room.Name ?? string.Empty, + PlayerCount: playerIds.Count, + HasModPresent: false, + HashedInstanceId: room.PhotonRoomId); + } + foreach (var p in privates) + { + if (string.IsNullOrEmpty(p.PhotonRoomId) || byKey.ContainsKey(p.PhotonRoomId)) continue; + byKey[p.PhotonRoomId] = new SimpleInstance( + RoomInstanceId: p.Id, + RoomId: p.RoomId, + SubRoomId: p.SubRoomId, + IsFull: false, + CreatedAt: DateTime.SpecifyKind(p.CreatedAt, DateTimeKind.Utc), + PlayerIds: new List(), + SubroomName: p.Name, + PlayerCount: 0, + HasModPresent: false, + HashedInstanceId: p.PhotonRoomId); + } + + // Wire shape: SimpleRoomInstance.Deserialize reads CAMELCASE + // keys — verified at Cpp2IL_ISIL/.../RecNet/SimpleRoomInstance.txt + // lines 765-808 ({roomInstanceId, roomId, subRoomId, isFull, + // createdAt, playerIds}). SubroomName / PlayerCount / + // HasModPresent / HashedInstanceId are populated locally on the + // watch (PlayerIds.Count, local hash, etc.) — not read from + // JSON, so we don't send them. Earlier PascalCase response made + // every field default to 0/null on the watch, breaking the + // browser. + return Ok(byKey.Values.Select(i => new + { + roomInstanceId = i.RoomInstanceId, + roomId = i.RoomId, + subRoomId = i.SubRoomId, + isFull = i.IsFull, + createdAt = i.CreatedAt, + playerIds = i.PlayerIds.ToArray(), + })); + } + + private sealed record SimpleInstance( + long RoomInstanceId, long RoomId, long SubRoomId, bool IsFull, + DateTime CreatedAt, List PlayerIds, string SubroomName, + int PlayerCount, bool HasModPresent, string HashedInstanceId); + + /// DELETE match.*/invite/{id} — Matchmaking.cs + /// DeclineRoomInvite. Verified URL at Matchmaking.txt:6505 (method + /// enum 4 = DELETE). The id is a MessageEntity row id (V2 game and + /// party invites are stored as Type 6/7 messages). Mark it read so the inviter's + /// inbox stops nagging, and remove the invitee from the private + /// instance's allow-list so they can't change their mind without + /// a fresh invite. + [HttpDelete("/invite/{messageId:long}")] + public async Task DeclineInvite(long messageId) + { + var pid = this.RequireCurrentPlayerId(); + var msg = await db.Messages + .FirstOrDefaultAsync(m => m.Id == messageId && m.RecipientPlayerId == pid); + if (msg is null) return NotFound(); + + if (msg.ReadAt is null) + { + msg.ReadAt = DateTime.UtcNow; + } + + // Current invite messages carry the bare roomInstanceId in + // Data/Body. Older local DBs may still have the legacy + // "Invite to private match (instance N)" text, so accept both. + if (TryParseRoomInstanceId(msg.Body, out var instId)) + { + await privateInstances.RevokeInviteAsync(instId, pid); + } + + await db.SaveChangesAsync(); + logger.LogInformation("[invite] declined messageId={Id} by player={Player}", messageId, pid); + return Ok(); + } + + // Same three-shape parser as GoToController.TryParseRoomInstanceId + // (see that file for the rationale): JSON for current Type-6/7 + // invites, bare int for the pre-fix shape, "Invite to private match + // (instance N)" text for ancient rows. + private static bool TryParseRoomInstanceId(string body, out long instanceId) + { + instanceId = 0; + if (string.IsNullOrWhiteSpace(body)) return false; + if (body.TrimStart().StartsWith('{')) + { + try + { + using var doc = System.Text.Json.JsonDocument.Parse(body); + if (doc.RootElement.TryGetProperty("roomInstanceId", out var v)) + { + // See GoToController.TryParseRoomInstanceId for the + // rationale on accepting both string and number forms. + if (v.ValueKind == System.Text.Json.JsonValueKind.String + && long.TryParse(v.GetString(), System.Globalization.NumberStyles.Integer, + System.Globalization.CultureInfo.InvariantCulture, out var strInst)) + { instanceId = strInst; return true; } + if (v.ValueKind == System.Text.Json.JsonValueKind.Number + && v.TryGetInt64(out var numInst)) + { instanceId = numInst; return true; } + } + } + catch { /* fall through to legacy formats */ } + } + if (long.TryParse(body, out instanceId)) return true; + var open = body.IndexOf("instance ", StringComparison.Ordinal); + if (open < 0) return false; + var tail = body[(open + "instance ".Length)..].TrimEnd(')', '.', ' '); + return long.TryParse(tail, out instanceId); + } + +} diff --git a/DorkNet.Server/Controllers/Match/MatchPlayerController.cs b/DorkNet.Server/Controllers/Match/MatchPlayerController.cs new file mode 100644 index 0000000..9038b4e --- /dev/null +++ b/DorkNet.Server/Controllers/Match/MatchPlayerController.cs @@ -0,0 +1,387 @@ +using Microsoft.AspNetCore.Mvc; +using Microsoft.EntityFrameworkCore; +using DorkNet.Server.Auth; +using DorkNet.Server.Data; +using DorkNet.Server.Services; +using System.Text.Json.Serialization; + +namespace DorkNet.Server.Controllers.Match; + +/// +/// match.rec.net/player/* — endpoints that operate on the local player's +/// matchmaking session. These were previously falling through to the +/// GlobalCatchAllController which returned the kitchen-sink "RecNetResult +/// shotgun" body. That body lacks the required keys for the actual response +/// types and the client crashes with KeyNotFoundException. +/// +/// The endpoints here use the same camelCase PlayerPresence shape as +/// PlayerPresenceController (verified at RVA 0x1458920). +/// +[ApiController] +public class MatchPlayerController( + PlayerPresenceService presence, + NotificationService notifications, + DorkNetDbContext db, + ILogger log) : ControllerBase +{ + /// + /// POST /player/login — the 2020 watch starts every session by + /// posting here with a fresh LoginLock GUID it generated client-side. + /// We persist the lock as the canonical "active session" marker for this + /// player so subsequent heartbeats can be validated against it; a NEW + /// login overwrites the lock and any older session's heartbeats start + /// failing, which is the intended kick-the-first-session behaviour. + /// + [HttpPost("/player/login")] + [Consumes("application/x-www-form-urlencoded", "multipart/form-data")] + public async Task PlayerLogin([FromForm(Name = "LoginLock")] string? loginLock) + { + var playerId = TryGetCurrentPlayerId(); + if (playerId != 0 && !string.IsNullOrWhiteSpace(loginLock)) + { + var replaced = presence.SetLock(playerId, loginLock); + if (replaced) + { + log.LogWarning( + "[player-login] concurrent session for player={PlayerId}; kicking old session(s) via ModerationKick push", + playerId); + // The new session hasn't opened its SignalR socket yet + // (negotiate fires AFTER /player/login on the watch's + // boot sequence — verified in the live trace), so the + // ModerationKick push hits only the OLDER session's + // existing connections. They'll show the "you've been + // kicked" dialog and gracefully disconnect via the + // watch's LocalModerationKickSelf path. + await notifications.KickStaleSession(playerId, + "Your account was signed in on another device."); + } + } + return Ok(Array.Empty()); + } + + /// + /// POST /player/logout — fires when the client gives up on a session + /// (e.g. Photon connection couldn't be established, client aborts). + /// Clear the cached presence so the next /goto starts fresh. + /// + [HttpPost("/player/logout")] + public IActionResult PlayerLogout() + { + var playerId = TryGetCurrentPlayerId(); + if (playerId != 0) presence.Clear(playerId); + return Ok(new { success = true, error = "" }); + } + + /// + /// POST /player/notifydisconnect — fired by + /// RecNet.Matchmaking.NotifyPlayerDisconnected(int otherPlayerId) + /// (Matchmaking.cs:326). Body is form-urlencoded + /// otherPlayerId=<id>. The watch uses this to tell the + /// server "I just saw this other Photon participant disappear" — + /// e.g. when someone leaves a room via the menu. Real Rec Room + /// likely used it to GC sticky presence rows; we just acknowledge + /// and log so it stops falling through to GlobalCatchAll. + /// + /// Returns 200 with no body (BestHTTP ignores the response per + /// Matchmaking.txt:6668-6699). + /// + [HttpPost("/player/notifydisconnect")] + [Consumes("application/x-www-form-urlencoded", "multipart/form-data")] + public IActionResult NotifyDisconnect([FromForm(Name = "otherPlayerId")] int? otherPlayerId) + { + var caller = TryGetCurrentPlayerId(); + log.LogInformation( + "[player-notify-disconnect] caller={Caller} reported other={Other} left", + caller, otherPlayerId); + return Ok(); + } + + /// + /// POST /player/heartbeat — every ~5–30s while connected. Client expects + /// a PlayerPresence response that mirrors its own cached state. + /// CRITICAL: must include the player's CURRENT roomInstance, not null. + /// Returning null while the client knows it's in DormRoom triggers + /// "presence heartbeat response indicates local presence is out-of-sync" + /// repeatedly → eventual "Error: -2" disconnect dialog. + /// + /// We pull the current room from PlayerPresenceService, which records + /// each successful /goto/room/* call. + /// + [HttpPost("/player/heartbeat")] + [Consumes("application/x-www-form-urlencoded", "multipart/form-data")] + public async Task> Heartbeat([FromForm(Name = "LoginLock")] string? loginLock) + { + // We track the LoginLock per player in /player/login (still useful + // for admin diagnostics + future session-management) but + // intentionally DO NOT 401 here on stale locks. Returning 401 on + // a heartbeat causes the watch to yank the player out of the + // current Photon room mid-session — symptoms include + // "Cannot send messages when not connected" + "Head/LeftHand/ + // RightHand being destroyed" + "presence heartbeat response + // indicates local presence is out-of-sync" — which is worse than + // letting two sessions co-exist briefly. If we want true + // single-session enforcement we'll do it via SignalR push to the + // older session and a graceful client-side disconnect, not via + // an HTTP 401 mid-heartbeat. + var playerId = TryGetCurrentPlayerId(); + var room = await GetPresenceRoomForResponseAsync(playerId); + return Ok(new PresenceDto + { + PlayerId = playerId, + StatusVisibility = 1, + DeviceClass = 0, + VrMovementMode = 0, + RoomInstance = room, + }); + } + + [HttpPut("/player/statusvisibility")] + [Consumes("application/x-www-form-urlencoded", "multipart/form-data")] + public async Task> SetStatusVisibility([FromForm(Name = "statusVisibility")] int? value) + { + var playerId = TryGetCurrentPlayerId(); + var room = await GetPresenceRoomForResponseAsync(playerId); + return Ok(new PresenceDto + { + PlayerId = playerId, + StatusVisibility = value ?? 1, + DeviceClass = 0, + VrMovementMode = 0, + RoomInstance = room, + }); + } + + [HttpPut("/player/vrmovementmode")] + [Consumes("application/x-www-form-urlencoded", "multipart/form-data")] + public async Task> SetVrMovementMode([FromForm(Name = "vrMovementMode")] int? value) + { + var playerId = TryGetCurrentPlayerId(); + var room = await GetPresenceRoomForResponseAsync(playerId); + return Ok(new PresenceDto + { + PlayerId = playerId, + StatusVisibility = 1, + DeviceClass = 0, + VrMovementMode = value ?? 0, + RoomInstance = room, + }); + } + + /// + /// GET /player/avoidjuniors — returns a primitive boolean. + /// Verified against + /// Cpp2IL_ISIL/.../RecNet/Matchmaking.txt:7728 (URL) and 7737 + /// (PromiseExtensions.ExpectPrimitiveResponse) — the watch + /// expects a bare JSON true/false, not an array or + /// object. Returning [] from the catch-all triggered + /// "Failed to get avoid juniors status: Malformed Response" + a + /// Convert.ChangeType InvalidCastException, which left the + /// matchmaking init in a never-completes state on some boot flows. + /// + [HttpGet("/player/avoidjuniors")] + public ActionResult GetAvoidJuniors() => Ok(false); + + /// PUT /player/avoidjuniors — body + /// avoidJuniors=true|false (Matchmaking.txt:7883). Stub for + /// now; the multi-tenant server doesn't currently track this + /// preference per-player. Returns the new value as a JSON + /// primitive so the watch's ExpectPrimitiveResponse is happy. + [HttpPut("/player/avoidjuniors")] + [HttpPost("/player/avoidjuniors")] + [Consumes("application/x-www-form-urlencoded", "multipart/form-data")] + public ActionResult SetAvoidJuniors([FromForm(Name = "avoidJuniors")] bool? value) + => Ok(value ?? false); + + /// POST /player/photonregionpings — watch reports + /// its measured Photon region ping vector so the server can pick + /// the best region for this player's matchmaking. Body is form- + /// urlencoded: region=eu&ping=42&region=us&ping=80&... + /// (one (region, ping) pair per row, repeated). Persists the + /// freshest measurement per (player, region) pair in + /// 's in-memory map — the + /// matchmaking GoTo flow reads this to bias instance creation + /// toward the player's lowest-ping region. + /// Verified URL at Matchmaking.txt:7280. + // 2020.12 calls this with the BestHTTP method enum value 3 (= PUT, not POST). + // Server-side route was POST → 405 Method Not Allowed → "Failed to upload + // photon region pings: HTTP Error 405". Accept GET/POST/PUT to cover both + // 2020.03 (POST) and 2020.12 (PUT) without guessing wrong on the enum. + [HttpPost("/player/photonregionpings")] + [HttpPut("/player/photonregionpings")] + [HttpGet("/player/photonregionpings")] + [Consumes("application/x-www-form-urlencoded", "multipart/form-data")] + public IActionResult PhotonRegionPings() + { + var playerId = TryGetCurrentPlayerId(); + if (playerId == 0) return Ok(); + + var form = Request.Form; + var regions = form["region"].ToArray(); + var pings = form["ping"].ToArray(); + var pairs = new Dictionary(StringComparer.OrdinalIgnoreCase); + for (var i = 0; i < Math.Min(regions.Length, pings.Length); i++) + { + var r = regions[i]?.Trim(); + if (string.IsNullOrEmpty(r)) continue; + if (!int.TryParse(pings[i], out var p)) continue; + // Sanity bound — discard wild values from misbehaving clients. + pairs[r] = Math.Clamp(p, 0, 2000); + } + if (pairs.Count > 0) + presence.SetPhotonRegionPings(playerId, pairs); + log.LogInformation("[player-region-pings] player={Player} regions={Count}", playerId, pairs.Count); + return Ok(); + } + + // Heartbeat / logout are unauthenticated endpoints (the client hits them + // before any token is acquired in some boot flows), so we use the + // optional `CurrentPlayerId()` extension and treat 0 as "anonymous". + // Cast to int because the wire shape's PlayerId field is an int — fits + // every accountId we mint (random in [100_000, 9_999_999)). + private int TryGetCurrentPlayerId() => + this.CurrentPlayerId() is long id ? (int)id : 0; + + private async Task GetPresenceRoomForResponseAsync(int playerId) + { + if (playerId == 0) return null; + + var room = presence.GetRoom(playerId); + if (room is null) + { + // Boot-time race: the watch hits /player/heartbeat before + // /goto/room/DormRoom has finished recording presence. The + // watch's OnPresenceHeartbeatResponse compares + // (cachedRoomInstance != null) XOR (serverRoomInstance != null) + // (Cpp2IL_ISIL/.../RecNet/Matchmaking.txt:8584) — if its local + // cache already has a dorm but we hand back null, it fires + // "RecNet presence heartbeat response indicates local presence + // is out-of-sync!" and tears down the local player (the + // RigidbodyEx Head/Hand "Cannot set the parent" warnings at + // [line 585] are the destruction shrapnel). Synthesise a + // default dorm RoomInstance instead so the heartbeat never + // returns null for an authenticated player. Matches what + // /goto/room/DormRoom builds — same RoomId, same + // PhotonRoomId pattern, same location GUID — so the watch's + // RoomInstancesAreDifferent finds them identical. + var fallback = await BuildDefaultDormPresenceAsync(playerId); + if (fallback is not null) + { + log.LogInformation( + "[player-heartbeat] synthesised dorm fallback player={PlayerId} room={RoomId} dataBlob={DataBlob} (presence cache miss)", + playerId, fallback.RoomId, fallback.DataBlob); + presence.SetRoom(playerId, fallback); + return fallback; + } + log.LogInformation("[player-heartbeat] player={PlayerId} room=null", playerId); + return null; + } + + if (string.IsNullOrWhiteSpace(room.DataBlob)) + { + var resolved = await ResolveDataBlobForPresenceAsync(room.RoomId, playerId); + if (!string.IsNullOrWhiteSpace(resolved)) + { + log.LogWarning( + "[player-heartbeat] repairing empty presence dataBlob player={PlayerId} room={RoomId} instance={InstanceId} resolved={DataBlob}", + playerId, room.RoomId, room.RoomInstanceId, resolved); + room.DataBlob = resolved; + presence.SetRoom(playerId, room); + } + } + + log.LogInformation( + "[player-heartbeat] player={PlayerId} room={RoomId} instance={InstanceId} subRoom={SubRoomId} location={Location} photon={PhotonRoomId} dataBlob={DataBlob}", + playerId, room.RoomId, room.RoomInstanceId, room.SubRoomId, room.Location, room.PhotonRoomId, room.DataBlob); + return room; + } + + /// Build a RoomInstance that matches the shape + /// /goto/room/DormRoom would have produced for this player + /// — used as a heartbeat fallback when presence is empty (boot + /// race, replica restart, Redis TTL expiry). Same RoomInstanceId + /// math, same PhotonRoomId pattern, same location GUID; the + /// watch's RoomInstancesAreDifferent finds the fallback identical + /// to its own cached state, so no out-of-sync. Returns null only + /// if we can't find the player's dorm row in the DB. + private async Task BuildDefaultDormPresenceAsync(int playerId) + { + var dormRow = await db.Rooms.AsNoTracking() + .Where(r => r.IsDormRoom && r.CreatorPlayerId == playerId) + .Select(r => new { r.Id, r.CurrentDataBlobName, r.LocationReplicationId }) + .FirstOrDefaultAsync(); + + // Fall back to the canonical dorm template row if the player + // doesn't yet have their own dorm record — same id space + // /goto uses. + long roomId = dormRow?.Id ?? 1; + string dataBlob = dormRow?.CurrentDataBlobName ?? string.Empty; + if (string.IsNullOrWhiteSpace(dataBlob)) + dataBlob = await ResolveDataBlobForPresenceAsync(roomId, playerId); + + return new RoomInstanceDto + { + RoomInstanceId = roomId * 100_000L, + RoomId = roomId, + SubRoomId = 0, + Location = dormRow?.LocationReplicationId ?? "76d98498-60a1-430c-ab76-b54a29b7a163", + PhotonRegionId = "us", + PhotonRoomId = $"^dormroom_p{playerId}", + Name = "DormRoom", + MaxCapacity = 8, + IsFull = false, + IsPrivate = true, + IsInProgress = true, + DataBlob = dataBlob ?? string.Empty, + EventId = 0, + }; + } + + private async Task ResolveDataBlobForPresenceAsync(long roomId, int playerId) + { + var room = await db.Rooms.AsNoTracking().FirstOrDefaultAsync(r => r.Id == roomId); + if (room is null) return string.Empty; + + if (room.IsDormRoom) + { + var dormBlob = await db.DormStates.AsNoTracking() + .Where(d => d.PlayerId == playerId) + .Select(d => d.CurrentDataBlobName) + .FirstOrDefaultAsync(); + if (!string.IsNullOrWhiteSpace(dormBlob)) return dormBlob; + } + + if (!string.IsNullOrWhiteSpace(room.CurrentDataBlobName)) return room.CurrentDataBlobName; + + var customisable = room.IsDormRoom || room.CreatorPlayerId != 1; + return customisable ? $"room_{room.Id}_v1.dat" : string.Empty; + } +} + +/// +/// Wire shape of RecNet.PlayerPresence (RVA 0x1458920). Lives here as a +/// local DTO; same fields as PlayerPresenceController.PlayerPresenceDto. +/// +public class PresenceDto +{ + [JsonPropertyName("playerId")] + public int PlayerId { get; set; } + + [JsonPropertyName("statusVisibility")] + public int StatusVisibility { get; set; } + + [JsonPropertyName("deviceClass")] + public int DeviceClass { get; set; } + + [JsonPropertyName("vrMovementMode")] + public int VrMovementMode { get; set; } + + [JsonPropertyName("roomInstance")] + public object? RoomInstance { get; set; } = null; + + // 2020.12 PlayerPresence.Deserialize reads these as required keys. + [JsonPropertyName("isOnline")] + public bool IsOnline { get; set; } = true; + + [JsonPropertyName("appVersion")] + public int AppVersion { get; set; } = 20201210; +} diff --git a/DorkNet.Server/Controllers/Match/PlayerPresenceController.cs b/DorkNet.Server/Controllers/Match/PlayerPresenceController.cs new file mode 100644 index 0000000..504bafd --- /dev/null +++ b/DorkNet.Server/Controllers/Match/PlayerPresenceController.cs @@ -0,0 +1,150 @@ +using Microsoft.AspNetCore.Mvc; +using System.Text.Json.Serialization; +using DorkNet.Server.Auth; +using DorkNet.Server.Services; + +namespace DorkNet.Server.Controllers.Match; + +/// +/// match.rec.net/player — bulk player-presence query used by +/// RecNet.Matchmaking.RefreshCachedPlayerPresences. +/// +/// URL pattern verified against +/// Cpp2IL_ISIL/IsilDump/Assembly-CSharp/RecNet/Matchmaking.txt +/// lines 3565-3568: +/// String.Join("&", ids) → String.Concat("player?", joined). +/// So the request URL is literally player?{id1}&{id2}&{id3}... +/// — every query-string KEY is one account id, with no value. The previous +/// [FromQuery(Name="id")] int[] ids binding only matched +/// ?id=X&id=Y and never picked up any of the watch's bare-id +/// requests, which is why every other player rendered as offline: with no +/// ids parsed, the response was [] and IsOnline (which checks +/// roomInstance != null) defaulted to false. +/// +/// Response shape: List<PlayerPresence> — keys verified by +/// disassembling RecNet.PlayerPresence.Deserialize: +/// "playerId" (int, required) +/// "statusVisibility" (int, required) +/// "deviceClass" (int, required) +/// "vrMovementMode" (only read when deviceClass == 1) +/// "roomInstance" (object, optional) +/// +/// PlayerPresence.IsOnline returns true iff +/// roomInstance is non-null — so we surface the live RoomInstance +/// from rather than always sending +/// null. Players who haven't /goto'd anywhere still report offline. +/// +[ApiController] +public class PlayerPresenceController(PlayerPresenceService presence) : ControllerBase +{ + [HttpGet("/player")] + public ActionResult> GetPresences() + { + // The watch sends two URL flavours for this endpoint: + // 1. Bulk: /player?{id1}&{id2}&{id3} — bare keys, + // from RefreshCachedPlayerPresences (Matchmaking.txt:3565). + // 2. Single: /player?id={N} — observed in the live trace + // from a different code path (probably the per-player presence + // probe). Both must work; an empty response makes + // PUNNetworkManager skip Photon region setup, which then + // crashes PingPhotonRegionsInternal with + // "An item with the same key has already been added. Key: none" + // and leaves the player on a black screen post-dorm-load. + // + // We accept ANY query parameter whose value parses as a long, plus + // any bare key that does. Same dedup → same DB lookup → same + // response shape regardless. + var ids = new HashSet(); + foreach (var (key, values) in Request.Query) + { + if (long.TryParse(key, out var fromKey) && fromKey > 0) + ids.Add(fromKey); + foreach (var v in values) + { + if (long.TryParse(v, out var fromValue) && fromValue > 0) + ids.Add(fromValue); + } + } + + // Caller's own id gets the FULL RoomInstance (photonRoomId + + // dataBlob) so the watch's cached PlayerPresence for itself + // matches what /player/heartbeat returns. The watch calls + // /player/?id=&id=&... at boot via + // RefreshCachedPlayerPresences and then caches each entry; if + // self's entry is redacted (photonRoomId="" / dataBlob="") but + // heartbeat later returns the full values, RoomInstance. + // RoomInstancesAreDifferent fires → "presence heartbeat + // response indicates local presence is out-of-sync!" → + // OnPresenceHeartbeatResponse tears the player out of the + // current room and re-joins, killing any in-flight save flow + // mid-coroutine. Only redact for OTHER players where the + // privacy concern actually applies. + var callerId = this.CurrentPlayerId(); + var result = ids.Select(id => + { + var room = presence.GetRoom(id); + // IsOnline is derived from current room presence: a player + // who has heartbeated recently has a non-null RoomInstance + // in PlayerPresenceService; one who hasn't shows up as + // null. Without this, IsOnline defaulted to `true` and the + // watch's friends list showed every queried account as + // Online regardless of actual session state — verified live + // (six accounts shown Online despite none being connected). + var isOnline = room is not null; + return new PlayerPresenceDto + { + PlayerId = (int)id, + // StatusVisibility is the player's *preference* (would + // they like to appear online if they were online?). For + // offline players we still report Visible so when they + // come back the friend's cached preference is correct, + // and IsOnline=false is what gates the "Online" badge. + StatusVisibility = 1, // Visible + DeviceClass = 0, // Desktop / unknown — not VR + VrMovementMode = 0, + RoomInstance = id == callerId ? room : RoomInstanceDto.Redact(room), + IsOnline = isOnline, + }; + }).ToList(); + + return Ok(result); + } +} + +public class PlayerPresenceDto +{ + [JsonPropertyName("playerId")] + public int PlayerId { get; set; } + + /// PlayerStatusVisibility enum (int): + /// 0 = Hidden / Offline-appearing, 1 = Visible, 2 = FriendsOnly. + [JsonPropertyName("statusVisibility")] + public int StatusVisibility { get; set; } + + /// RecNet.DeviceClass enum (int). + [JsonPropertyName("deviceClass")] + public int DeviceClass { get; set; } + + /// VRMovementMode enum (int) — only consulted when + /// deviceClass == 1. + [JsonPropertyName("vrMovementMode")] + public int VrMovementMode { get; set; } + + /// Read by Util.GetObjectKey<RoomInstance> — must + /// be a nested object (or null), NOT a string. Empty string "" + /// triggers + /// InvalidCastException: Unable to cast object of type 'String' + /// to type 'Dictionary'2'. null means "player not in + /// any room" → IsOnline = false. + [JsonPropertyName("roomInstance")] + public object? RoomInstance { get; set; } = null; + + // 2020.12 PlayerPresence.Deserialize (EFAJBHGDIDD.PPGFHEDFBEA) reads + // these as required keys after roomInstance. Missing → KeyNotFoundException + // → "RecNet presence heartbeat failed: Malformed Response" spam. + [JsonPropertyName("isOnline")] + public bool IsOnline { get; set; } = true; + + [JsonPropertyName("appVersion")] + public int AppVersion { get; set; } = 20201210; +} diff --git a/DorkNet.Server/Controllers/Notify/NotifyController.cs b/DorkNet.Server/Controllers/Notify/NotifyController.cs new file mode 100644 index 0000000..4d6c81f --- /dev/null +++ b/DorkNet.Server/Controllers/Notify/NotifyController.cs @@ -0,0 +1,21 @@ +using Microsoft.AspNetCore.Mvc; + +namespace DorkNet.Server.Controllers.Notify; + +/// +/// notify.rec.net — legacy raw-WebSocket entrypoint that the 2020 client +/// briefly used before switching to the SignalR hub at +/// notify.rec.net/hub/v1. Kept as a 410 Gone so any straggling +/// boot path that hits it gets a clear "use the hub" signal instead of +/// a silent connection upgrade. +/// +[ApiController] +public class NotifyController : ControllerBase +{ + [HttpGet("/notify/v1")] + public IActionResult Connect() => StatusCode(StatusCodes.Status410Gone, new + { + error = "use_signalr_hub", + hint = "connect to /hub/v1 instead", + }); +} diff --git a/DorkNet.Server/Controllers/Ns/NsController.cs b/DorkNet.Server/Controllers/Ns/NsController.cs new file mode 100644 index 0000000..2712a54 --- /dev/null +++ b/DorkNet.Server/Controllers/Ns/NsController.cs @@ -0,0 +1,63 @@ +using Microsoft.AspNetCore.Mvc; +using DorkNet.Server.Services; + +namespace DorkNet.Server.Controllers.Ns; + +#pragma warning disable IDE0290 + +/// +/// ns.{apex} — RecNet name server. The very first endpoint the client +/// queries on boot. Returns the service URL map so the client knows +/// where every other subdomain lives. Also serves the bare apex +/// GET / (service catalog) and /v1/ping for the +/// keep-alive loop. Apex is read from +/// (driven by DORKNET_DOMAIN) so every URL the client sees +/// flows through one source of truth. +/// +[ApiController] +public class NsController : ControllerBase +{ + private readonly PlayerService _playerService; + private readonly DomainConfig _domain; + public NsController(PlayerService playerService, DomainConfig domain) + { + _playerService = playerService; + _domain = domain; + } + + // Root: service URL discovery (primary boot call from RecNet.Core.ConnectToRecNet) + // The game's TrySetServiceUri parses this as a flat { "ServiceName": "https://..." } dictionary. + // Wrapping it breaks lookup — all URIs come back null which crashes EnsureEndsWith. + // + // Order is more-negative than CdnController.Serve's wildcard route + // (Order = -100) because the catch-all {*path} matches the + // empty path "/" too — without this, GET ns.{apex}/ falls into the + // CDN serve handler and the client gets binary back instead of the + // service-map JSON, triggering "Failed to connect to RecNet (error + // code: locker)" in BootSequence. + [HttpGet("/", Order = -1000)] + public ActionResult> Root() => + Ok(ConfigService.BuildServiceUrlMap(_domain.Apex)); + + // Some builds fetch /v1 or /v1/services directly + [HttpGet("/v1")] + [HttpGet("/v1/services")] + public ActionResult> Services() => + Ok(ConfigService.BuildServiceUrlMap(_domain.Apex)); + + // Ping / health used by the client keep-alive loop + [HttpGet("/v1/ping")] + [HttpGet("/ping")] + public ActionResult Ping() + => Ok(new { Status = "OK", Time = DateTimeOffset.UtcNow.ToUnixTimeMilliseconds() }); + + // Player name → accountId lookup (used for friend invites etc.) + [HttpGet("/v1/player/{name}")] + public async Task PlayerLookup(string name) + { + var p = await _playerService.GetByUsernameAsync(name); + return Ok(p is null + ? new { Username = name, AccountId = 0L, Found = false } + : new { Username = p.Username, AccountId = p.Id, Found = true }); + } +} diff --git a/DorkNet.Server/Controllers/PlayerSettings/PlayerSettingsController.cs b/DorkNet.Server/Controllers/PlayerSettings/PlayerSettingsController.cs new file mode 100644 index 0000000..7af4c3a --- /dev/null +++ b/DorkNet.Server/Controllers/PlayerSettings/PlayerSettingsController.cs @@ -0,0 +1,65 @@ +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Mvc; +using Microsoft.EntityFrameworkCore; +using DorkNet.Server.Auth; +using DorkNet.Server.Data; +using DorkNet.Server.Data.Entities; +using System.Text.Json.Serialization; + +namespace DorkNet.Server.Controllers.PlayerSettings; + +/// +/// playersettings.rec.net — dedicated player settings service. +/// +[ApiController] +[Authorize] +public class PlayerSettingsController(DorkNetDbContext db) : ControllerBase +{ + private long CurrentPlayerId => this.RequireCurrentPlayerId(); + + [HttpGet("/settings/v1/{accountId:long}")] + public async Task>> Get(long accountId) + { + // Settings are private — only the owner can read them. Returning + // 403 instead of leaking the dict. + if (accountId != CurrentPlayerId) return Forbid(); + + var settings = await db.PlayerSettings + .Where(s => s.PlayerId == accountId) + .ToDictionaryAsync(s => s.Key, s => s.Value); + return Ok(settings); + } + + [HttpPut("/settings/v1/{accountId:long}")] + public async Task Set(long accountId, [FromBody] Dictionary updates) + { + if (accountId != CurrentPlayerId) return Forbid(); + + foreach (var (key, value) in updates) + { + var existing = await db.PlayerSettings + .FirstOrDefaultAsync(s => s.PlayerId == accountId && s.Key == key); + if (existing is null) + db.PlayerSettings.Add(new PlayerSettingEntity { PlayerId = accountId, Key = key, Value = value }); + else + existing.Value = value; + } + + await db.SaveChangesAsync(); + return Ok(); + } + + [HttpDelete("/settings/v1/{accountId:long}/{key}")] + public async Task Delete(long accountId, string key) + { + if (accountId != CurrentPlayerId) return Forbid(); + + var setting = await db.PlayerSettings + .FirstOrDefaultAsync(s => s.PlayerId == accountId && s.Key == key); + if (setting is null) return NotFound(); + + db.PlayerSettings.Remove(setting); + await db.SaveChangesAsync(); + return Ok(); + } +} diff --git a/DorkNet.Server/Controllers/Rooms/RoomsController.cs b/DorkNet.Server/Controllers/Rooms/RoomsController.cs new file mode 100644 index 0000000..190ec32 --- /dev/null +++ b/DorkNet.Server/Controllers/Rooms/RoomsController.cs @@ -0,0 +1,60 @@ +using Microsoft.AspNetCore.Mvc; +using DorkNet.Server.Services; + +namespace DorkNet.Server.Controllers.Rooms; + +/// +/// rooms.{rec.net,localhost} — secondary room-listing host (the +/// primary surface is api.*/api/rooms/v2/* via +/// Controllers/API/Rooms/V2/RoomsController). Some older +/// 2020 client paths still hit this host for the room-tile lookup +/// during share-link resolution and friend-room-join probes. +/// Both controllers share so the data is +/// identical regardless of which host the watch chose. +/// +[ApiController] +public class RoomsController(RoomService rooms) : ControllerBase +{ + [HttpGet("/rooms/v3/{roomId:long}")] + public async Task GetById(long roomId) + { + var r = await rooms.GetByIdAsync(roomId); + return r is null ? NotFound() : Ok(RoomService.ToWireRoom(r)); + } + + [HttpGet("/rooms/v3/search")] + public async Task Search([FromQuery] string? query, [FromQuery] int take = 20) + { + var rows = await rooms.SearchAsync(query ?? string.Empty, Math.Clamp(take, 1, 100)); + return Ok(rows.Select(RoomService.ToWireRoom).ToList()); + } + + [HttpGet("/rooms/v3/hot")] + public async Task Hot([FromQuery] string? tag, [FromQuery] int take = 50) + => Ok((await rooms.HotAsync(tag, Math.Clamp(take, 1, 100))).Select(RoomService.ToWireRoom).ToList()); + + [HttpGet("/rooms/v3/new")] + public async Task New([FromQuery] int take = 50) + { + // No dedicated "new rooms" path on RoomService — order by + // CreatedAt is acceptable as a fallback since RoomEntity has + // both UpdatedAt + CreatedAt. + var rows = await rooms.HotAsync(null, Math.Clamp(take, 1, 100)); + return Ok(rows.Select(RoomService.ToWireRoom).ToList()); + } + + [HttpGet("/rooms/v3/{roomId:long}/stats")] + public async Task GetStats(long roomId) + { + var r = await rooms.GetByIdAsync(roomId); + if (r is null) return NotFound(); + return Ok(new + { + RoomId = r.Id, + VisitCount = r.VisitCount, + CheerCount = r.CheerCount, + FavoriteCount = r.FavoriteCount, + VisitorCount = r.VisitorCount, + }); + } +} diff --git a/DorkNet.Server/Controllers/RootController.cs b/DorkNet.Server/Controllers/RootController.cs new file mode 100644 index 0000000..22a4204 --- /dev/null +++ b/DorkNet.Server/Controllers/RootController.cs @@ -0,0 +1,31 @@ +using Microsoft.AspNetCore.Mvc; +using DorkNet.Server.Services; + +namespace DorkNet.Server.Controllers; + +[ApiController] +public class RootController(ConfigService configService) : ControllerBase +{ + // Legacy MOTD for pre-Dorm Room builds + [HttpGet("motd")] + public ActionResult GetMotd() + { + var cfg = configService.GetConfig($"{Request.Scheme}://{Request.Host}"); + return Ok(cfg.MessageOfTheDay); + } + + // Serve images from data/images/ + [HttpGet("img/{name}")] + public ActionResult GetImage(string name) + { + var safeFileName = Path.GetFileName(name); // prevent path traversal + var path = Path.Combine(AppContext.BaseDirectory, "data", "images", safeFileName); + + if (!System.IO.File.Exists(path)) + return NotFound(); + + var ext = Path.GetExtension(safeFileName).ToLowerInvariant(); + var contentType = ext == ".png" ? "image/png" : "image/jpeg"; + return PhysicalFile(path, contentType); + } +} diff --git a/DorkNet.Server/Controllers/Site/PublicSiteController.cs b/DorkNet.Server/Controllers/Site/PublicSiteController.cs new file mode 100644 index 0000000..03dfa3d --- /dev/null +++ b/DorkNet.Server/Controllers/Site/PublicSiteController.cs @@ -0,0 +1,246 @@ +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Mvc; +using Microsoft.EntityFrameworkCore; +using DorkNet.Server.Data; +using DorkNet.Server.Data.Entities; +using DorkNet.Server.Services; + +namespace DorkNet.Server.Controllers.Site; + +/// +/// Apex-host API for the public-facing site at localhost / +/// rec.net. Everything here is anonymous-safe — the site reads +/// the photo feed, looks up players, and browses public rooms without +/// any login. Mirrors the wire shape the SPA expects (camelCase JSON +/// thanks to the default ASP.NET serializer; admin / game endpoints +/// override with PascalCase, but the public site has no legacy clients +/// to placate so we ship modern conventions). +/// +/// Lives at the apex so the SPA can same-origin fetch /api/site/v1 +/// from the bare domain without CORS gymnastics. Routing is path-only +/// (post [Host]-strip refactor) — every subdomain reaches every +/// controller and the right handler is picked by URL alone. +/// +[ApiController] +[AllowAnonymous] +[Route("api/site/v1")] +public class PublicSiteController( + DorkNetDbContext db, + PlayerService players, + DomainConfig domain) : ControllerBase +{ + // ── Players ────────────────────────────────────────────────────── + + /// GET /api/site/v1/players/search?q=…&take=20 — typeahead + /// account search by display name OR username. + [HttpGet("players/search")] + public async Task SearchPlayers([FromQuery] string? q, [FromQuery] int take = 20) + { + if (string.IsNullOrWhiteSpace(q)) return Ok(Array.Empty()); + var rows = await players.SearchAsync(q.Trim(), Math.Clamp(take, 1, 50)); + return Ok(rows.Select(PlayerCard).ToList()); + } + + /// GET /api/site/v1/players/{id} — public profile card. + [HttpGet("players/{id:long}")] + public async Task GetPlayer(long id) + { + var p = await db.Players + .Where(x => x.Id == id) + .Select(x => new + { + x.Id, x.Username, x.DisplayName, x.Bio, x.Level, x.XP, + x.IsAdmin, x.IsDeveloper, x.IsVerified, x.IsJunior, + x.CreatedAt, x.ProfileImageName, + }) + .FirstOrDefaultAsync(); + if (p is null) return NotFound(); + + var photoCount = await db.Photos + .CountAsync(ph => ph.UploaderPlayerId == id && ph.IsPublic && ph.DeletedAt == null); + + return Ok(new + { + id = p.Id, + username = p.Username, + displayName = p.DisplayName, + bio = p.Bio ?? string.Empty, + level = p.Level, + xp = p.XP, + isAdmin = p.IsAdmin, + isDeveloper = p.IsDeveloper, + isVerified = p.IsVerified, + isJunior = p.IsJunior, + createdAt = p.CreatedAt, + profileImageName = p.ProfileImageName, + photoCount, + }); + } + + /// GET /api/site/v1/players/{id}/photos — photos uploaded + /// by the player. Excludes private + soft-deleted. + [HttpGet("players/{id:long}/photos")] + public async Task GetPlayerPhotos(long id, + [FromQuery] int take = 24, [FromQuery] int skip = 0) + { + take = Math.Clamp(take, 1, 100); + skip = Math.Max(0, skip); + var rows = await db.Photos + .Where(p => p.UploaderPlayerId == id && p.IsPublic && p.DeletedAt == null) + .OrderByDescending(p => p.CreatedAt) + .Skip(skip).Take(take) + .ToListAsync(); + return Ok(await EnrichPhotosAsync(rows)); + } + + // ── Photos ─────────────────────────────────────────────────────── + + /// GET /api/site/v1/feed — global public feed, newest first. + [HttpGet("feed")] + public async Task Feed([FromQuery] int take = 24, [FromQuery] int skip = 0) + { + take = Math.Clamp(take, 1, 100); + skip = Math.Max(0, skip); + var rows = await db.Photos + .Where(p => p.IsPublic && p.DeletedAt == null) + .OrderByDescending(p => p.CreatedAt) + .Skip(skip).Take(take) + .ToListAsync(); + return Ok(await EnrichPhotosAsync(rows)); + } + + /// GET /api/site/v1/photos/{id} — single photo + metadata. + /// Bumps the ViewCount as a side effect (denormalised trending sort). + [HttpGet("photos/{id:long}")] + public async Task GetPhoto(long id) + { + var photo = await db.Photos.FirstOrDefaultAsync(p => p.Id == id); + if (photo is null || !photo.IsPublic || photo.DeletedAt is not null) return NotFound(); + + photo.ViewCount += 1; + await db.SaveChangesAsync(); + + var enriched = await EnrichPhotosAsync(new[] { photo }); + return Ok(enriched.First()); + } + + // ── Rooms ──────────────────────────────────────────────────────── + + /// GET /api/site/v1/rooms/search?q=… — rooms matching name. + /// Mostly used by the public site's room directory. + [HttpGet("rooms/search")] + public async Task SearchRooms([FromQuery] string? q, [FromQuery] int take = 24) + { + var clamped = Math.Clamp(take, 1, 50); + var qq = (q ?? string.Empty).Trim(); + // Public rooms list: hide dorms (per-player private space), and + // hide rooms that have only ever been visited by their creator + // (VisitorCount < 2). This keeps "^dwadwad" style throwaway + // builds off the public-facing site without an explicit + // "publish" toggle. Admins can still see them via the Rooms + // admin page and delete them from there if they want. + var rows = await db.Rooms + .Where(r => !r.IsDormRoom && r.VisitorCount >= 2 && + (qq == "" || r.Name.Contains(qq) || r.Description.Contains(qq))) + .OrderByDescending(r => r.HotScore) + .Take(clamped) + .Select(r => new + { + id = r.Id, + name = r.Name, + description = r.Description, + creatorPlayerId = r.CreatorPlayerId, + isDormRoom = r.IsDormRoom, + isAGRoom = r.IsAGRoom, + visitCount = r.VisitCount, + visitorCount = r.VisitorCount, + cheerCount = r.CheerCount, + imageName = r.ImageName, + }) + .ToListAsync(); + return Ok(rows); + } + + // ── Site stats (home-page hero) ────────────────────────────────── + + /// GET /api/site/v1/stats — coarse counters for the + /// public site's marketing hero (player count, room count, photo + /// count). Anonymous-safe. + [HttpGet("stats")] + public async Task Stats() + { + var playerCount = await db.Players.CountAsync(); + var roomCount = await db.Rooms.CountAsync(r => !r.IsDormRoom); + var photoCount = await db.Photos.CountAsync(p => p.IsPublic && p.DeletedAt == null); + var inventionCount = await db.Inventions.CountAsync(i => !i.IsDeleted); + return Ok(new { playerCount, roomCount, photoCount, inventionCount }); + } + + // ── Helpers ────────────────────────────────────────────────────── + + /// Public-facing player card. Mirrors what the site needs + /// for the search results list / friend-list row: id, name, + /// profileImageName (the SPA stitches the img.* URL). + private static object PlayerCard(PlayerEntity p) => new + { + id = p.Id, + username = p.Username, + displayName = p.DisplayName, + level = p.Level, + isAdmin = p.IsAdmin, + isDeveloper = p.IsDeveloper, + isVerified = p.IsVerified, + isJunior = p.IsJunior, + profileImageName = p.ProfileImageName, + }; + + /// Materialise photo rows into wire DTOs with display names + /// + room names + an absolute image URL. The URL apex switches + /// based on the request host so a localhost visitor gets localhost + /// CDN links and a rec.net visitor gets rec.net. + private async Task> EnrichPhotosAsync(IEnumerable photos) + { + var list = photos.ToList(); + if (list.Count == 0) return new(); + + var uploaderIds = list.Select(p => p.UploaderPlayerId).Distinct().ToList(); + var roomIds = list.Select(p => p.RoomId).Where(id => id > 0).Distinct().ToList(); + + var uploaders = await db.Players + .Where(p => uploaderIds.Contains(p.Id)) + .Select(p => new { p.Id, p.Username, p.DisplayName, p.ProfileImageName }) + .ToListAsync(); + var rooms = await db.Rooms + .Where(r => roomIds.Contains(r.Id)) + .Select(r => new { r.Id, r.Name }) + .ToListAsync(); + + var uMap = uploaders.ToDictionary(u => u.Id); + var rMap = rooms.ToDictionary(r => r.Id); + + var cdnHost = domain.Sub("cdn"); + + return list.Select(p => + { + uMap.TryGetValue(p.UploaderPlayerId, out var u); + rMap.TryGetValue(p.RoomId, out var r); + return (object)new + { + id = p.Id, + uploaderPlayerId = p.UploaderPlayerId, + uploaderUsername = u?.Username ?? string.Empty, + uploaderDisplayName = u?.DisplayName ?? $"Player_{p.UploaderPlayerId}", + uploaderProfileImageName = u?.ProfileImageName, + blobName = p.BlobName, + imageUrl = $"https://{cdnHost}/{p.BlobName}", + caption = p.Caption, + roomId = p.RoomId, + roomName = r?.Name ?? string.Empty, + isPublic = p.IsPublic, + cheerCount = p.CheerCount, + viewCount = p.ViewCount, + createdAt = p.CreatedAt, + }; + }).ToList(); + } +} diff --git a/DorkNet.Server/Controllers/Storage/StorageController.cs b/DorkNet.Server/Controllers/Storage/StorageController.cs new file mode 100644 index 0000000..64faadc --- /dev/null +++ b/DorkNet.Server/Controllers/Storage/StorageController.cs @@ -0,0 +1,429 @@ +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Mvc; +using Microsoft.EntityFrameworkCore; +using DorkNet.Server.Auth; +using DorkNet.Server.Data; +using DorkNet.Server.Data.Entities; +using DorkNet.Server.Services; + +namespace DorkNet.Server.Controllers.Storage; + +/// +/// storage.rec.net — file upload endpoint the 2020 client posts to when +/// the master client persists a room save. Verified against the IL2CPP +/// disassembly of RecNet.Storage.UploadFile: +/// +/// Method: POST. +/// URL: https://storage.rec.net/upload (Service=Storage, +/// requestUri="upload"; per Service enum at dump.cs:586534). +/// Body: multipart/form-data with fields +/// File (binary, content-type from caller or +/// application/octet-stream), FileType +/// (Storage.FileType: 0=Unknown, 1=RoomSave, 2=Holotar, 3=Image, +/// 4=Video, 5=Invention), and optional References +/// (comma-joined filenames). +/// Response: UploadFileResponseDTO with one key +/// Filename — the cdn URL segment the client will request via +/// cdn.rec.net/room/{Filename} the next time anyone enters +/// the room. +/// +/// +/// Permission rule: only the room's creator (or the dorm owner for a +/// dorm save) can persist a new blob. Saves from other players in the +/// same room are silently rejected with 403 — they can build with the +/// Maker Pen but their changes won't outlive the session. +/// +[ApiController] +[Authorize] +public class StorageController( + DorkNetDbContext db, + PlayerPresenceService presence, + IObjectStorage objectStorage, + ILogger logger) : ControllerBase +{ + /// + /// FileType enum values from the client's RecNet.Storage+FileType + /// (dump.cs:586846). Kept as a private enum so callers can pattern- + /// match without leaking client-side numbering elsewhere. + /// + private enum FileType + { + Unknown = 0, + RoomSave = 1, + Holotar = 2, + Image = 3, + Video = 4, + Invention = 5, + } + + [HttpPost("/upload", Order = -2000)] + [Consumes("multipart/form-data")] + public async Task Upload() + { + // Form parsing — ASP.NET Core deserialises the multipart body + // automatically. The two field names are the literal strings the + // client uses (capitalised; verified in + // RecNet.Storage.UploadFile ISIL state {028, 044}). + if (!Request.HasFormContentType) + return BadRequest(new { error = "expected multipart/form-data" }); + + var form = await Request.ReadFormAsync(); + var file = form.Files["File"]; + if (file is null || file.Length == 0) + return BadRequest(new { error = "missing or empty File part" }); + + if (!int.TryParse(form["FileType"].ToString(), out var fileTypeRaw)) + return BadRequest(new { error = "missing or non-numeric FileType" }); + var fileType = (FileType)fileTypeRaw; + + var playerId = this.RequireCurrentPlayerId(); + var references = form["References"].ToString() ?? string.Empty; + logger.LogInformation( + "[storage] upload accepted host={Host} fileType={FileType} player={PlayerId} bytes={Bytes} refs={ReferenceCount}", + Request.Host.Host, + fileType, + playerId, + file.Length, + string.IsNullOrWhiteSpace(references) + ? 0 + : references.Split(',', StringSplitOptions.RemoveEmptyEntries).Length); + + // Read the upload bytes upfront. SQLite BLOB column expects a + // byte[]; the upload is small (KB to ~1MB) so memory is fine. + byte[] bytes; + using (var ms = new MemoryStream()) + { + await file.CopyToAsync(ms); + bytes = ms.ToArray(); + } + + return fileType switch + { + FileType.RoomSave => await UploadRoomSaveAsync(playerId, bytes, references), + FileType.Invention => await UploadInventionAsync(playerId, bytes, references), + FileType.Image => await UploadImageAsync(playerId, bytes), + FileType.Holotar => await UploadGenericAsync(playerId, bytes, "holotar"), + FileType.Video => await UploadGenericAsync(playerId, bytes, "video"), + _ => Ok(new + { + filename = $"stub_{Guid.NewGuid():N}.{Extension(fileType)}", + }), + }; + } + + + private async Task UploadRoomSaveAsync( + long playerId, byte[] bytes, string references) + { + // Determine which room the upload belongs to. The client doesn't + // include the room id in the request — it just posts a save and + // expects the server to know based on session context. We use + // PlayerPresenceService, which is updated by /goto/room/* and + // is the canonical "what room is this player currently in" + // store. + var presenceRoom = presence.GetRoom(playerId); + if (presenceRoom is null) + { + logger.LogWarning( + "[storage] room save from player {PlayerId} had no active presence; saving to personal dorm", + playerId); + var dorm = await db.Rooms.FirstOrDefaultAsync(r => r.IsDormRoom && r.CreatorPlayerId == playerId); + if (dorm is not null) + return await UploadDormSaveAsync(playerId, bytes, references, dorm.Id); + + return BadRequest(new { error = "no active room session" }); + } + + var roomId = presenceRoom.RoomId; + var room = await db.Rooms.FirstOrDefaultAsync(r => r.Id == roomId); + if (room is null) + { + var roomName = string.IsNullOrWhiteSpace(presenceRoom.Name) + ? $"Room_{roomId}" + : presenceRoom.Name; + if (await db.Rooms.AnyAsync(r => r.Name == roomName)) + roomName = $"Room_{roomId}"; + + logger.LogWarning( + "[storage] room save from player {PlayerId} referenced missing room {RoomId} ({RoomName}); creating owner room row", + playerId, roomId, roomName); + room = new RoomEntity + { + Id = roomId, + Name = roomName, + Description = "Created from an active game session.", + CreatorPlayerId = playerId, + Accessibility = presenceRoom.IsPrivate ? 0 : 1, + IsAGRoom = true, + IsDormRoom = false, + LocationReplicationId = string.IsNullOrWhiteSpace(presenceRoom.Location) + ? "76d98498-60a1-430c-ab76-b54a29b7a163" + : presenceRoom.Location, + CreatedAt = DateTime.UtcNow, + UpdatedAt = DateTime.UtcNow, + }; + db.Rooms.Add(room); + await db.SaveChangesAsync(); + } + + // Dorm saves go to a per-player DormStateEntity instead of the + // shared RoomEntity. Pass the real dorm Room.Id through so the + // saved blob is keyed against the player's own dorm row (every + // player has their own dorm RoomEntity now, not the legacy + // canonical sentinel id=1). + if (room.IsDormRoom) + return await UploadDormSaveAsync(playerId, bytes, references, room.Id); + + // Permission: only the room owner can save. (Future Phase 5 + // expansion: co-owners + admins. For now, strict owner-only — + // matches the 2020 official server's "RoomRolePermissions + // .CanUseMakerPen" behaviour, which rejects upload at the wire + // for non-owners.) + if (room.CreatorPlayerId != playerId) + { + logger.LogWarning( + "[storage] player {PlayerId} tried to save room {RoomId} (creator={CreatorId})", + playerId, room.Id, room.CreatorPlayerId); + return Forbid(); + } + + // Allocate the next BlobName for this room. We use a monotonic + // counter per-room: the (N+1)th save becomes "room__v.dat". + // SQLite handles the count() query fast enough for our scale; if + // this grows too big, switch to a per-room counter column on + // RoomEntity. + var versionNumber = await db.RoomDataBlobs + .Where(b => b.RoomId == roomId) + .CountAsync() + 1; + var blobName = $"room_{roomId}_v{versionNumber}.dat"; + var (bucket, key) = BlobRouter.Route(blobName); + await StoreBlobObjectAsync(bucket, key, bytes, "application/octet-stream"); + + var entry = new RoomDataBlobEntity + { + RoomId = roomId, + BlobName = blobName, + UploadedByPlayerId = playerId, + UploadedAt = DateTime.UtcNow, + ReferencedFilenamesCsv = references, + }; + db.RoomDataBlobs.Add(entry); + + // Update RoomEntity.CurrentDataBlobName so the next visitor's + // /api/rooms/v4/details/{id} response carries the new name and + // the persistence flow downloads the latest bytes. + room.CurrentDataBlobName = blobName; + room.UpdatedAt = DateTime.UtcNow; + + await db.SaveChangesAsync(); + + logger.LogInformation( + "[storage] room save: room={RoomId} player={PlayerId} blob={Blob} size={Size}B", + roomId, playerId, blobName, bytes.Length); + + return Ok(new { filename = blobName }); + } + + /// + /// Per-player dorm save. Stores bytes in + /// keyed under RoomId=1 (matchmaking is unchanged) and + /// upserts the row for this player so + /// next time they enter their dorm, RoomsController.Details serves + /// the right BlobName from their personal state row instead of the + /// shared RoomEntity row. + /// + private async Task UploadDormSaveAsync( + long playerId, byte[] bytes, string references, long dormRoomId) + { + // Migration backfill: pre-personal-dorm saves used the legacy + // canonical RoomId=1. Re-key any of those rows for THIS player + // to their actual dorm RoomId so the version count below + // doesn't collide with existing blob names. Idempotent — runs + // a no-op once everything's migrated. + var blobNamePrefix = $"dorm_p{playerId}_v"; + await db.RoomDataBlobs + .Where(b => b.UploadedByPlayerId == playerId + && b.BlobName.StartsWith(blobNamePrefix) + && b.RoomId != dormRoomId) + .ExecuteUpdateAsync(s => s.SetProperty(b => b.RoomId, dormRoomId)); + + // Per-player blob-name-based versioning. Counting by blob-name + // prefix is more robust than RoomId because it survives any + // future migrations of the dorm row identity. + var versionNumber = await db.RoomDataBlobs + .Where(b => b.UploadedByPlayerId == playerId + && b.BlobName.StartsWith(blobNamePrefix)) + .CountAsync() + 1; + var blobName = $"dorm_p{playerId}_v{versionNumber}.dat"; + var (bucket, key) = BlobRouter.Route(blobName); + await StoreBlobObjectAsync(bucket, key, bytes, "application/octet-stream"); + + db.RoomDataBlobs.Add(new RoomDataBlobEntity + { + RoomId = dormRoomId, + BlobName = blobName, + UploadedByPlayerId = playerId, + UploadedAt = DateTime.UtcNow, + ReferencedFilenamesCsv = references, + }); + + // Upsert DormStateEntity for this player. + var dormState = await db.DormStates + .FirstOrDefaultAsync(d => d.PlayerId == playerId); + if (dormState is null) + { + dormState = new DormStateEntity { PlayerId = playerId }; + db.DormStates.Add(dormState); + } + dormState.CurrentDataBlobName = blobName; + dormState.UpdatedAt = DateTime.UtcNow; + + // Bump the dorm room's UpdatedAt timestamp so the watch's + // freshness check (DataModifiedAt comparison) sees this as the + // latest version and stops showing the "room is not up to + // date" warning after a save. + await db.Rooms + .Where(r => r.Id == dormRoomId) + .ExecuteUpdateAsync(s => s + .SetProperty(r => r.UpdatedAt, DateTime.UtcNow) + .SetProperty(r => r.CurrentDataBlobName, blobName)); + + await db.SaveChangesAsync(); + + logger.LogInformation( + "[storage] dorm save: player={PlayerId} blob={Blob} size={Size}B", + playerId, blobName, bytes.Length); + + return Ok(new { filename = blobName }); + } + + /// + /// Per-creation invention upload. Stores bytes in + /// with a recognisable + /// invention_p<id>_.dat blob name; the + /// metadata row in is created + /// separately by the client's POST to + /// api/inventions/v3/save referencing the returned + /// Filename. We don't enforce a per-invention id at upload time + /// (the inventon row may not exist yet) — the unique BlobName + /// guards uniqueness on its own. + /// + private async Task UploadInventionAsync( + long playerId, byte[] bytes, string references) + { + var blobName = $"invention_p{playerId}_{Guid.NewGuid():N}.dat"; + var (bucket, key) = BlobRouter.Route(blobName); + await StoreBlobObjectAsync(bucket, key, bytes, "application/octet-stream"); + + db.RoomDataBlobs.Add(new RoomDataBlobEntity + { + // RoomId=0 is the "no specific room — invention library blob" + // sentinel. The cdn lookup keys on BlobName so RoomId is + // informational only here. + RoomId = 0, + BlobName = blobName, + UploadedByPlayerId = playerId, + UploadedAt = DateTime.UtcNow, + ReferencedFilenamesCsv = references, + }); + await db.SaveChangesAsync(); + logger.LogInformation( + "[storage] invention upload: player={PlayerId} blob={Blob} size={Size}B", + playerId, blobName, bytes.Length); + return Ok(new { filename = blobName }); + } + + /// Profile image / screenshot upload. Stored as bytes on + /// the same RoomDataBlobs table (RoomId=0 is the + /// "no specific room" sentinel). The catch-all serves them via + /// cdn.rec.net/{Filename} like every other blob. The + /// caller is expected to follow up with + /// POST account/v1/profileimage to wire the returned + /// filename into . + /// + private async Task UploadImageAsync(long playerId, byte[] bytes) + { + var blobName = $"img_p{playerId}_{Guid.NewGuid():N}.png"; + var (bucket, key) = BlobRouter.Route(blobName); + await StoreBlobObjectAsync(bucket, key, bytes, "image/png"); + + db.RoomDataBlobs.Add(new RoomDataBlobEntity + { + RoomId = 0, + BlobName = blobName, + UploadedByPlayerId = playerId, + UploadedAt = DateTime.UtcNow, + ReferencedFilenamesCsv = string.Empty, + }); + await db.SaveChangesAsync(); + logger.LogInformation( + "[storage] image upload: player={PlayerId} blob={Blob} size={Size}B", + playerId, blobName, bytes.Length); + return Ok(new { filename = blobName }); + } + + /// Catch-all bytes-on-disk upload used for FileTypes that + /// don't have specialised behaviour (Holotar, Video). Stored + /// identically to Image; callers reference the returned Filename + /// from wherever they need it. + private async Task UploadGenericAsync( + long playerId, byte[] bytes, string kind) + { + var ext = kind switch { "video" => "mp4", _ => "bin" }; + var blobName = $"{kind}_p{playerId}_{Guid.NewGuid():N}.{ext}"; + var contentType = kind switch + { + "video" => "video/mp4", + _ => "application/octet-stream", + }; + var (bucket, key) = BlobRouter.Route(blobName); + await StoreBlobObjectAsync(bucket, key, bytes, contentType); + + db.RoomDataBlobs.Add(new RoomDataBlobEntity + { + RoomId = 0, + BlobName = blobName, + UploadedByPlayerId = playerId, + UploadedAt = DateTime.UtcNow, + ReferencedFilenamesCsv = string.Empty, + }); + await db.SaveChangesAsync(); + logger.LogInformation( + "[storage] {Kind} upload: player={PlayerId} blob={Blob} size={Size}B", + kind, playerId, blobName, bytes.Length); + return Ok(new { filename = blobName }); + } + + private async Task StoreBlobObjectAsync(string bucket, string key, byte[] bytes, string contentType) + { + try + { + using var s3Timeout = new CancellationTokenSource(TimeSpan.FromSeconds(8)); + var written = await objectStorage.PutAsync(bucket, key, bytes, contentType, s3Timeout.Token); + logger.LogInformation( + "[storage] object write: bucket={Bucket} key={Key} bytes={Bytes} mode={Mode}", + bucket, + key, + written, + objectStorage.IsS3Configured ? "s3" : "disk-fallback"); + } + catch (Exception ex) + { + logger.LogError( + ex, + "[storage] object write failed: bucket={Bucket} key={Key}; keeping DB fallback so the client request can complete", + bucket, + key); + } + } + + private static string Extension(FileType type) => type switch + { + FileType.RoomSave => "dat", + FileType.Image => "png", + FileType.Video => "mp4", + FileType.Holotar => "holotar", + FileType.Invention => "inv", + _ => "bin", + }; +} diff --git a/DorkNet.Server/Controllers/Strings/StringsController.cs b/DorkNet.Server/Controllers/Strings/StringsController.cs new file mode 100644 index 0000000..2a8e678 --- /dev/null +++ b/DorkNet.Server/Controllers/Strings/StringsController.cs @@ -0,0 +1,18 @@ +using Microsoft.AspNetCore.Mvc; + +namespace DorkNet.Server.Controllers.Strings; + +/// +/// strings.rec.net — game localisation strings. +/// Returns an empty table so the client falls back to its bundled defaults. +/// +[ApiController] +public class StringsController : ControllerBase +{ + [HttpGet("/strings/v1")] + public ActionResult> GetStrings() => Ok(new Dictionary()); + + [HttpGet("/strings/v1/{locale}")] + public ActionResult> GetLocale(string locale) + => Ok(new Dictionary()); +} diff --git a/DorkNet.Server/Data/DorkNetDbContext.cs b/DorkNet.Server/Data/DorkNetDbContext.cs new file mode 100644 index 0000000..2df811a --- /dev/null +++ b/DorkNet.Server/Data/DorkNetDbContext.cs @@ -0,0 +1,678 @@ +using Microsoft.EntityFrameworkCore; +using DorkNet.Server.Data.Entities; + +namespace DorkNet.Server.Data; + +public class DorkNetDbContext(DbContextOptions options) : DbContext(options) +{ + public DbSet Players => Set(); + public DbSet Avatars => Set(); + public DbSet PlayerSettings => Set(); + public DbSet Relationships => Set(); + public DbSet Rooms => Set(); + public DbSet RoomRoles => Set(); + public DbSet LeaderboardChannelMeta => Set(); + public DbSet RoomBookmarks => Set(); + public DbSet RoomDataBlobs => Set(); + public DbSet DormStates => Set(); + public DbSet AdminActions => Set(); + public DbSet Inventions => Set(); + public DbSet Reports => Set(); + public DbSet IpBans => Set(); + public DbSet Messages => Set(); + public DbSet ChatMessages => Set(); + public DbSet ChatThreads => Set(); + public DbSet ChatThreadMembers => Set(); + public DbSet Cheers => Set(); + public DbSet CurrencyBalances => Set(); + public DbSet PlayerEvents => Set(); + public DbSet PlayerEventResponses => Set(); + public DbSet ObjectiveProgress => Set(); + public DbSet Subscriptions => Set(); + public DbSet Photos => Set(); + public DbSet StoreItems => Set(); + public DbSet InventionVersions => Set(); + public DbSet RoomBans => Set(); + public DbSet PlayerInventory => Set(); + public DbSet GiftPackages => Set(); + public DbSet BugReports => Set(); + public DbSet Clubs => Set(); + public DbSet ClubMemberships => Set(); + public DbSet RoyaleMatches => Set(); + public DbSet RoyaleMatchPlayers => Set(); + public DbSet RoyalePlayerProgress => Set(); + public DbSet TestCases => Set(); + public DbSet TestPasses => Set(); + public DbSet PlayerDevices => Set(); + public DbSet NotificationPrefs => Set(); + public DbSet CohortAssignments => Set(); + public DbSet Coupons => Set(); + public DbSet CouponRedemptions => Set(); + public DbSet LeaderboardStats => Set(); + public DbSet PlayerElo => Set(); + public DbSet PushTokens => Set(); + public DbSet PlatformIgnores => Set(); + public DbSet Cards => Set(); + public DbSet RoomVisits => Set(); + public DbSet RoomScenes => Set(); + public DbSet PrivateInstances => Set(); + public DbSet PrivateInstanceInvitees => Set(); + public DbSet GameSessions => Set(); + public DbSet CommunityBoardRows => Set(); + public DbSet LoadingScreenTips => Set(); + public DbSet ServerSettings => Set(); + public DbSet RoomKeys => Set(); + public DbSet RoomKeyPurchases => Set(); + public DbSet GameRewardSelections => Set(); + public DbSet Playlists => Set(); + public DbSet PlaylistRooms => Set(); + public DbSet PlaylistInteractions => Set(); + public DbSet ClubAnnouncements => Set(); + public DbSet ClubAnnouncementReads => Set(); + public DbSet ClubCategoryTags => Set(); + public DbSet ClubCategoryAssignments => Set(); + public DbSet ClubSubscriptions => Set(); + + protected override void OnModelCreating(ModelBuilder modelBuilder) + { + // SQLite-specific NOCASE collation works via UseCollation; Postgres + // doesn't have NOCASE. Provider-conditional helper applied to the + // three string columns the wire API matches case-insensitively + // (Room.Name, RoomScene.Name, Club.Name). + // + // FOLLOW-UP: on Postgres we currently fall back to default case- + // sensitive collation on these columns. In practice this is fine + // because the wire API users always type the same casing as the + // stored value, but if we want strict parity with SQLite NOCASE + // semantics we should add a functional LOWER(...) unique index + // via raw SQL in a Postgres migration, OR enable the citext + // extension and switch the column type. Tracked for post-cutover + // hardening; not a launch blocker. + var isSqlite = Database.IsSqlite(); + modelBuilder.Entity(e => + { + e.HasKey(p => p.Id); + e.HasIndex(p => p.Username).IsUnique(); + e.HasOne(p => p.Avatar).WithOne(a => a.Player).HasForeignKey(a => a.PlayerId); + e.HasMany(p => p.Settings).WithOne(s => s.Player).HasForeignKey(s => s.PlayerId); + e.HasMany(p => p.Relationships).WithOne(r => r.Requester).HasForeignKey(r => r.RequesterId); + }); + + modelBuilder.Entity(e => + { + e.HasKey(r => r.Id); + e.HasIndex(r => new { r.RequesterId, r.TargetId }).IsUnique(); + }); + + modelBuilder.Entity(e => + { + e.HasKey(s => s.Id); + e.HasIndex(s => new { s.PlayerId, s.Key }).IsUnique(); + }); + + modelBuilder.Entity(e => + { + e.HasKey(r => r.Id); + // Room names are case-insensitive in the wire API + // (api/rooms/v2/name/{name}) — store collation-insensitive so + // GetByName lookups don't depend on the user's casing. + e.HasIndex(r => r.Name).IsUnique(); + if (isSqlite) e.Property(r => r.Name).UseCollation("NOCASE"); + // Hot-feed sorts by HotScore desc — keep it indexed. + e.HasIndex(r => r.HotScore); + e.HasIndex(r => r.CreatorPlayerId); + }); + + modelBuilder.Entity(e => + { + e.HasKey(b => b.Id); + e.HasIndex(b => new { b.PlayerId, b.RoomId }).IsUnique(); + e.HasIndex(b => b.PlayerId); + }); + + modelBuilder.Entity(e => + { + e.HasKey(b => b.Id); + // BlobName is the cdn url segment the client requests; must + // be unique across the table because the catch-all matches + // by string. + e.HasIndex(b => b.BlobName).IsUnique(); + // "All versions of this room" + "find current" both index + // by RoomId. Sort by UploadedAt desc when listing history. + e.HasIndex(b => b.RoomId); + }); + + modelBuilder.Entity(e => + { + e.HasKey(s => s.Id); + // BuildRoomDetails fetches all scenes for one room ordered + // by OrderIndex. Composite index covers both predicate and + // sort. + e.HasIndex(s => new { s.RoomId, s.OrderIndex }).IsUnique(); + // Sub-room goto resolves by (room, name). Case-insensitive + // match — same NOCASE collation pattern as RoomEntity.Name. + e.HasIndex(s => new { s.RoomId, s.Name }); + if (isSqlite) e.Property(s => s.Name).UseCollation("NOCASE"); + }); + + modelBuilder.Entity(e => + { + // PlayerId is already declared as [Key] on the entity; explicit + // here so EF doesn't expect an Id auto-pk. + e.HasKey(d => d.PlayerId); + }); + + modelBuilder.Entity(e => + { + e.HasKey(a => a.Id); + // The audit log is read in reverse-chronological order; + // index Timestamp desc for the common "give me recent + // admin actions" query. + e.HasIndex(a => a.Timestamp); + // Filter "all actions taken by admin X" or "all actions + // targeting player Y" both want indexes. + e.HasIndex(a => a.AdminPlayerId); + e.HasIndex(a => new { a.TargetType, a.TargetId }); + }); + + modelBuilder.Entity(e => + { + e.HasKey(i => i.Id); + e.HasIndex(i => i.CreatorPlayerId); + // Popular tab orders by CheerCount desc; index it. + e.HasIndex(i => i.CheerCount); + }); + + modelBuilder.Entity(e => + { + e.HasKey(r => r.Id); + // Admin queue reads "open reports ordered by oldest" — + // index ResolvedAt so the WHERE IS NULL filter is fast. + e.HasIndex(r => r.ResolvedAt); + e.HasIndex(r => r.TargetPlayerId); + e.HasIndex(r => r.ReporterPlayerId); + }); + + modelBuilder.Entity(e => + { + e.HasKey(b => b.Id); + // Middleware queries by Cidr per request — keep it + // indexed even though the table will be small. + e.HasIndex(b => b.Cidr); + }); + + modelBuilder.Entity(e => + { + e.HasKey(m => m.Id); + // Inbox query: "messages I received, ordered by SentAt + // desc". Index Recipient + SentAt for that path. + e.HasIndex(m => new { m.RecipientPlayerId, m.SentAt }); + e.HasIndex(m => new { m.SenderPlayerId, m.SentAt }); + }); + + modelBuilder.Entity(e => + { + e.HasKey(c => c.Id); + // Thread fetch is "ThreadKey + SentAt desc, take 50". + e.HasIndex(c => new { c.ThreadKey, c.SentAt }); + }); + + modelBuilder.Entity(e => + { + e.HasKey(t => t.Id); + // One metadata row per thread — concurrent rename calls + // would otherwise duplicate-insert. + e.HasIndex(t => t.ThreadKey).IsUnique(); + }); + + modelBuilder.Entity(e => + { + e.HasKey(m => m.Id); + // (Thread, Player) is the natural key — Snooze/MarkRead/ + // Leave race conditions would otherwise insert duplicate + // membership rows under the same player. + e.HasIndex(m => new { m.ThreadKey, m.PlayerId }).IsUnique(); + e.HasIndex(m => m.PlayerId); + }); + + modelBuilder.Entity(e => + { + e.HasKey(c => c.Id); + // Re-cheer is idempotent on the (from, target, type) + // tuple — unique index lets us upsert cleanly. + e.HasIndex(c => new { c.FromPlayerId, c.TargetPlayerId, c.TargetRoomId, c.TargetPhotoId, c.Type }) + .IsUnique(); + e.HasIndex(c => c.TargetPlayerId); + e.HasIndex(c => c.TargetRoomId); + e.HasIndex(c => c.TargetPhotoId); + }); + + modelBuilder.Entity(e => + { + e.HasKey(p => p.Id); + // Feed pagination is "WHERE IsPublic AND DeletedAt IS NULL + // ORDER BY CreatedAt DESC" — index CreatedAt for that. + e.HasIndex(p => p.CreatedAt); + e.HasIndex(p => p.UploaderPlayerId); + e.HasIndex(p => p.RoomId); + // BlobName is the cdn URL the client requests; lookups by + // it are common (admin moderation, dedupe checks). + e.HasIndex(p => p.BlobName); + }); + + modelBuilder.Entity(e => + { + e.HasKey(s => s.Id); + // Slug is referenced from Avatar.InventoryJson — must be + // unique so the inventory resolver gets a deterministic + // hit when looking up "what does this slug mean?". + e.HasIndex(s => s.Slug).IsUnique(); + // Storefront tab queries filter by storefront + active + + // category in that order; index storefront for the most + // selective predicate first. + e.HasIndex(s => s.Storefront); + e.HasIndex(s => new { s.IsActive, s.Category }); + }); + + modelBuilder.Entity(e => + { + e.HasKey(c => c.Id); + // One wallet row per (player, currency) — unique on the + // pair so AwardCurrency upserts cleanly. + e.HasIndex(c => new { c.PlayerId, c.CurrencyType }).IsUnique(); + }); + + modelBuilder.Entity(e => + { + e.HasKey(ev => ev.Id); + e.HasIndex(ev => ev.CreatorPlayerId); + e.HasIndex(ev => ev.StartsAt); + }); + + modelBuilder.Entity(e => + { + e.HasKey(r => r.Id); + // One response per (event, player); upsert via this + // unique index. + e.HasIndex(r => new { r.EventId, r.PlayerId }).IsUnique(); + e.HasIndex(r => r.PlayerId); + }); + + modelBuilder.Entity(e => + { + e.HasKey(o => o.Id); + e.HasIndex(o => new { o.PlayerId, o.Key }).IsUnique(); + }); + + modelBuilder.Entity(e => + { + e.HasKey(s => s.Id); + // One subscription per (subscriber, target) pair. + e.HasIndex(s => new { s.SubscriberPlayerId, s.TargetPlayerId }) + .IsUnique(); + // SubscriberCount aggregations index by target. + e.HasIndex(s => s.TargetPlayerId); + e.HasIndex(s => s.SubscriberPlayerId); + }); + + modelBuilder.Entity(e => + { + e.HasKey(v => v.Id); + // Version-list query: WHERE InventionId = X ORDER BY + // VersionNumber DESC. Index covers both predicates. + e.HasIndex(v => new { v.InventionId, v.VersionNumber }) + .IsUnique(); + }); + + modelBuilder.Entity(e => + { + e.HasKey(b => b.Id); + // Match-service join check: "is this player banned from + // this room?". Compound index makes that O(log n). + e.HasIndex(b => new { b.RoomId, b.BannedPlayerId }); + e.HasIndex(b => b.BannedPlayerId); + }); + + modelBuilder.Entity(e => + { + e.HasKey(p => p.Id); + // One row per (player, item) pair. + e.HasIndex(p => new { p.PlayerId, p.ItemSlug }).IsUnique(); + e.HasIndex(p => p.PlayerId); + }); + + modelBuilder.Entity(e => + { + e.HasKey(g => g.Id); + // Inbox query: unconsumed gifts for a player. + e.HasIndex(g => new { g.RecipientPlayerId, g.Consumed }); + }); + + modelBuilder.Entity(e => + { + e.HasKey(b => b.Id); + // Admin queue reads "unread bugs ordered by oldest" — + // ReadAt index for the WHERE IS NULL filter. + e.HasIndex(b => b.ReadAt); + e.HasIndex(b => b.ReporterPlayerId); + }); + + modelBuilder.Entity(e => + { + e.HasKey(c => c.Id); + e.HasIndex(c => c.Name).IsUnique(); + // GetByName lookups are case-insensitive on the wire. + if (isSqlite) e.Property(c => c.Name).UseCollation("NOCASE"); + e.HasIndex(c => c.CreatorPlayerId); + }); + + modelBuilder.Entity(e => + { + e.HasKey(m => m.Id); + // One membership per (club, player) pair. + e.HasIndex(m => new { m.ClubId, m.PlayerId }).IsUnique(); + e.HasIndex(m => m.PlayerId); + }); + + modelBuilder.Entity(e => + { + e.HasKey(a => a.Id); + // Per-club timeline query (newest first) — composite index + // covers both predicate and sort. + e.HasIndex(a => new { a.ClubId, a.CreatedAt }); + }); + + modelBuilder.Entity(e => + { + e.HasKey(r => r.Id); + // One read-marker per (announcement, player) — upsert via + // this unique index so re-marking is a no-op. + e.HasIndex(r => new { r.AnnouncementId, r.PlayerId }).IsUnique(); + // The unread feeds query "what have I read?" by player id. + e.HasIndex(r => r.PlayerId); + }); + + modelBuilder.Entity(e => + { + e.HasKey(t => t.Id); + // Tag names are case-insensitive in admin tooling; not + // unique because admins may temporarily reuse a soft- + // deleted name when iterating. + e.HasIndex(t => t.Name); + }); + + modelBuilder.Entity(e => + { + e.HasKey(a => a.Id); + // One assignment per (club, tag) pair — re-assigning the + // same tag should be idempotent. + e.HasIndex(a => new { a.ClubId, a.CategoryTagId }).IsUnique(); + e.HasIndex(a => a.CategoryTagId); + }); + + modelBuilder.Entity(e => + { + e.HasKey(s => s.Id); + // One subscription row per (player, club). Unique so the + // subscribe-then-resubscribe path can upsert cleanly. + e.HasIndex(s => new { s.PlayerId, s.ClubId }).IsUnique(); + e.HasIndex(s => s.ClubId); + }); + + modelBuilder.Entity(e => + { + e.HasKey(m => m.Id); + e.HasIndex(m => m.CompletedAt); + }); + + modelBuilder.Entity(e => + { + e.HasKey(p => p.Id); + e.HasIndex(p => new { p.MatchId, p.PlayerId }); + e.HasIndex(p => p.PlayerId); + }); + + modelBuilder.Entity(e => + { + e.HasKey(p => p.Id); + e.HasIndex(p => p.PlayerId).IsUnique(); + }); + + modelBuilder.Entity(e => + { + e.HasKey(t => t.Pk); + // Wire Id (string) is what the client uses in URL params; + // unique so the lookup-by-string-id is fast. + e.HasIndex(t => t.Id).IsUnique(); + e.HasIndex(t => t.TestPassId); + e.HasIndex(t => t.Status); + }); + + modelBuilder.Entity(e => + { + e.HasKey(p => p.Id); + // Don't auto-generate uint Id; we set it explicitly so it + // matches the upstream JIRA pass id when seeded. + e.Property(p => p.Id).ValueGeneratedNever(); + }); + + modelBuilder.Entity(e => + { + e.HasKey(d => d.Id); + // Lookup "all accounts using this device" for ban + // enforcement. + e.HasIndex(d => d.DeviceId); + e.HasIndex(d => d.PlayerId); + }); + + modelBuilder.Entity(e => + { + e.HasKey(n => n.Id); + // One pref row per (player, platform). + e.HasIndex(n => new { n.PlayerId, n.Platform }).IsUnique(); + }); + + modelBuilder.Entity(e => + { + e.HasKey(c => c.Id); + // Sticky assignment: one per (player, cohort). + e.HasIndex(c => new { c.PlayerId, c.CohortKey }).IsUnique(); + }); + + modelBuilder.Entity(e => + { + e.HasKey(c => c.Id); + e.HasIndex(c => c.Code).IsUnique(); + }); + + modelBuilder.Entity(e => + { + e.HasKey(r => r.Id); + // Enforce one-redeem-per-player. + e.HasIndex(r => new { r.CouponId, r.PlayerId }).IsUnique(); + }); + + modelBuilder.Entity(e => + { + e.HasKey(s => s.Id); + // One row per (player, channel) — enforced unique so + // SetStats can upsert without race conditions. + e.HasIndex(s => new { s.PlayerId, s.StatChannel }).IsUnique(); + // Rank computation: ORDER BY Value DESC over a channel. + e.HasIndex(s => new { s.StatChannel, s.Value }); + }); + + modelBuilder.Entity(e => + { + e.HasKey(p => p.Id); + e.HasIndex(p => new { p.PlayerId, p.GameMode }).IsUnique(); + // Matchmaker reads ranked players ordered by Elo DESC. + e.HasIndex(p => new { p.GameMode, p.Elo }); + }); + + modelBuilder.Entity(e => + { + e.HasKey(t => t.Id); + // One token per (player, platform) — APNS revokes the + // old one when the user reinstalls, so we upsert. + e.HasIndex(t => new { t.PlayerId, t.Platform }).IsUnique(); + e.HasIndex(t => t.Token); + }); + + modelBuilder.Entity(e => + { + e.HasKey(i => i.Id); + // One ignore-row per (player, platform, platform user id). + e.HasIndex(i => new { i.PlayerId, i.Platform, i.PlatformUserId }).IsUnique(); + }); + + modelBuilder.Entity(e => + { + e.HasKey(c => c.Id); + // Home-screen query: WHERE PlayerId IS NULL OR = me + // ORDER BY Priority DESC, CreatedAt DESC. + e.HasIndex(c => c.PlayerId); + e.HasIndex(c => c.ExpiresAt); + }); + + modelBuilder.Entity(e => + { + e.HasKey(v => v.Id); + // One row per (room, player) — enforced unique so the + // visit-upsert doesn't create duplicates under concurrent + // /goto calls. Lookup-by-pair is the hot path so the + // composite index doubles as the SELECT support index. + e.HasIndex(v => new { v.RoomId, v.PlayerId }).IsUnique(); + // Per-player history view (admin / dev console). + e.HasIndex(v => v.PlayerId); + // "Recent visitors of this room" view. + e.HasIndex(v => new { v.RoomId, v.LastVisitAt }); + }); + + modelBuilder.Entity(e => + { + e.HasKey(r => r.Id); + e.HasIndex(r => r.RoomId); + e.HasIndex(r => new { r.RoomId, r.PlayerId, r.Role }).IsUnique(); + }); + + modelBuilder.Entity(e => + { + // Id is server-allocated (computed nonce), not autoincrement. + // Mark ValueGenerated.Never so EF doesn't try to round-trip + // through a sequence on insert. + e.HasKey(p => p.Id); + e.Property(p => p.Id).ValueGeneratedNever(); + e.HasIndex(p => p.OwnerPlayerId); + // Quick lookup for "is this player in any private instance" + // queries used by NotificationService. + e.HasIndex(p => new { p.RoomId, p.SubRoomId }); + }); + + modelBuilder.Entity(e => + { + // Composite PK: one row per (instance, invitee). Naturally + // dedupes repeat-invite calls. + e.HasKey(i => new { i.PrivateInstanceId, i.PlayerId }); + // Reverse-lookup index for "what private instances am I + // invited to" queries (the watch's invite inbox). + e.HasIndex(i => i.PlayerId); + }); + + modelBuilder.Entity(e => + { + // Postgres bigserial; SQLite gets INTEGER PRIMARY KEY + // AUTOINCREMENT semantics. Both providers' sequences are + // process-shared so two replicas don't allocate id=1. + e.HasKey(s => s.Id); + // The hot lookup is "find a non-full session in this room + // and region" — composite index covers it. + e.HasIndex(s => new { s.RoomId, s.Region }); + }); + + modelBuilder.Entity(e => + { + // Single-row table, id always 1. We don't autoincrement; + // the row is upserted explicitly. + e.HasKey(c => c.Id); + e.Property(c => c.Id).ValueGeneratedNever(); + }); + + modelBuilder.Entity(e => + { + // Single-row table, id always 1 — same pattern as + // CommunityBoardRows above. + e.HasKey(s => s.Id); + e.Property(s => s.Id).ValueGeneratedNever(); + e.Property(s => s.WeeklyChallengesCompletedRequired).HasDefaultValue(true); + e.Property(s => s.WeeklyChallengesJson).HasDefaultValue(string.Empty); + e.Property(s => s.WeeklyChallengeRewardJson).HasDefaultValue(string.Empty); + }); + + modelBuilder.Entity(e => + { + e.HasKey(k => k.Id); + e.HasIndex(k => k.RoomId); + e.HasIndex(k => new { k.RoomId, k.Name }); + e.HasIndex(k => k.CreatorPlayerId); + }); + + modelBuilder.Entity(e => + { + e.HasKey(p => p.Id); + e.HasIndex(p => new { p.RoomKeyId, p.PlayerId }).IsUnique(); + e.HasIndex(p => p.PlayerId); + }); + + modelBuilder.Entity(e => + { + e.HasKey(r => r.Id); + e.HasIndex(r => new { r.PlayerId, r.SelectedAt }); + }); + + modelBuilder.Entity(e => + { + e.HasKey(p => p.Id); + // Playlist names share the same case-insensitive lookup + // contract as Room.Name — the watch's search compares + // user-typed strings directly against the stored value. + e.HasIndex(p => p.Name); + if (isSqlite) e.Property(p => p.Name).UseCollation("NOCASE"); + // Curated tab reads "WHERE IsCurated ORDER BY OrderIndex" — + // index covers the predicate and the sort key. + e.HasIndex(p => p.IsCurated); + e.HasIndex(p => p.CreatorPlayerId); + }); + + modelBuilder.Entity(e => + { + e.HasKey(pr => pr.Id); + // Member-rooms query: "WHERE PlaylistId = X ORDER BY + // OrderIndex". Composite covers both predicate and sort. + e.HasIndex(pr => new { pr.PlaylistId, pr.OrderIndex }); + e.HasIndex(pr => pr.PlaylistId); + e.HasIndex(pr => pr.RoomId); + }); + + modelBuilder.Entity(e => + { + e.HasKey(i => i.Id); + // One row per (playlist, player) — toggle endpoints upsert. + e.HasIndex(i => new { i.PlaylistId, i.PlayerId }).IsUnique(); + // "cheeredby/me" / "favoritedby/me" lists query by PlayerId + // + the bool flag; PlayerId-leading index is enough since + // the table is small. + e.HasIndex(i => i.PlayerId); + }); + + modelBuilder.Entity(e => + { + // Channel int IS the primary key — there's only one + // metadata row per stat-channel id, and the channel id + // is what the watch reports on POST /SetStats. + e.HasKey(c => c.Channel); + e.Property(c => c.Channel).ValueGeneratedNever(); + e.HasIndex(c => c.RoomId); + }); + } +} diff --git a/DorkNet.Server/Data/Entities/AdminActionEntity.cs b/DorkNet.Server/Data/Entities/AdminActionEntity.cs new file mode 100644 index 0000000..049d8dd --- /dev/null +++ b/DorkNet.Server/Data/Entities/AdminActionEntity.cs @@ -0,0 +1,44 @@ +using System.ComponentModel.DataAnnotations; + +namespace DorkNet.Server.Data.Entities; + +/// +/// Append-only audit row written every time an admin performs a +/// privileged action (ban, unban, promote, demote, room delete, +/// MOTD change, etc.). The admin watch UI's "audit" tab reads +/// from this in reverse-chronological order. +/// +/// Rows are never deleted from the application — privileged actions +/// stay on the record. Storage cost is negligible at single-server +/// scale. +/// +public class AdminActionEntity +{ + public long Id { get; set; } + + /// The admin who performed the action. + public long AdminPlayerId { get; set; } + + /// Free-form action name. Convention: lower-snake-case verb, + /// e.g. ban_player, unban_player, promote_admin, + /// delete_room, set_motd. + [MaxLength(64)] + public string Action { get; set; } = string.Empty; + + /// Targeted entity type — player, room, + /// system. Free-form so future actions can introduce new + /// kinds without a migration. + [MaxLength(32)] + public string TargetType { get; set; } = string.Empty; + + /// Numeric id of the target. 0 for system-wide + /// actions like setting the MOTD. + public long TargetId { get; set; } + + /// Optional human reason the admin supplied at action + /// time. Surfaced to the audit log UI verbatim. + [MaxLength(512)] + public string Reason { get; set; } = string.Empty; + + public DateTime Timestamp { get; set; } = DateTime.UtcNow; +} diff --git a/DorkNet.Server/Data/Entities/AvatarEntity.cs b/DorkNet.Server/Data/Entities/AvatarEntity.cs new file mode 100644 index 0000000..e634edd --- /dev/null +++ b/DorkNet.Server/Data/Entities/AvatarEntity.cs @@ -0,0 +1,55 @@ +using System.ComponentModel.DataAnnotations; + +namespace DorkNet.Server.Data.Entities; + +/// +/// Per-player avatar state. The 2020 client persists a mix of structured +/// and free-form blobs: +/// +/// OutfitSelections — comma-separated GUIDs of equipped +/// items per slot (head, torso, legs, feet, accessories). Sent +/// verbatim to POST api/avatar/v2. +/// FaceFeatures — JSON blob with eyeId/eyePos/eyeScl/ +/// mouthId etc. produced by the in-game face editor. +/// HairColor / SkinColor — color vault GUIDs from +/// the client's baked hair/skin color tables. +/// SavedOutfitsJson — list of named outfit presets the +/// watch's Backpack tab shows under "saved". +/// EquippedItemsJson / InventoryJson — kept for the +/// v3 wire shape (list of ). +/// +/// +public class AvatarEntity +{ + public long Id { get; set; } + public long PlayerId { get; set; } + + // JSON-serialised list of equipped item IDs (v3 shape) + public string EquippedItemsJson { get; set; } = "[]"; + + // JSON-serialised full inventory (v3 shape) + public string InventoryJson { get; set; } = "[]"; + + /// v2 outfit selections — comma-separated GUIDs per slot. + [MaxLength(2048)] + public string OutfitSelections { get; set; } = string.Empty; + + /// v2 face features — opaque JSON written by the face + /// editor; we never parse it server-side, just round-trip. + [MaxLength(8192)] + public string FaceFeatures { get; set; } = string.Empty; + + [MaxLength(64)] + public string HairColor { get; set; } = string.Empty; + + [MaxLength(64)] + public string SkinColor { get; set; } = string.Empty; + + /// Saved outfit presets — JSON list of named slots so the + /// watch's "Saved" tab can recall them. + public string SavedOutfitsJson { get; set; } = "[]"; + + public DateTime UpdatedAt { get; set; } = DateTime.UtcNow; + + public PlayerEntity? Player { get; set; } +} diff --git a/DorkNet.Server/Data/Entities/BugReportEntity.cs b/DorkNet.Server/Data/Entities/BugReportEntity.cs new file mode 100644 index 0000000..c5d9611 --- /dev/null +++ b/DorkNet.Server/Data/Entities/BugReportEntity.cs @@ -0,0 +1,38 @@ +using System.ComponentModel.DataAnnotations; + +namespace DorkNet.Server.Data.Entities; + +/// +/// One bug report submitted via the in-game "Report a Bug" UI +/// (POST /api/bugreporting/v2/reportbug). Surfaces in the +/// admin moderation queue at GET /api/admin/v1/bugreports. +/// +public class BugReportEntity +{ + public long Id { get; set; } + public long ReporterPlayerId { get; set; } + + [MaxLength(128)] + public string Title { get; set; } = string.Empty; + + [MaxLength(4000)] + public string Body { get; set; } = string.Empty; + + public long GameSessionId { get; set; } + + [MaxLength(64)] + public string ClientVersion { get; set; } = string.Empty; + + [MaxLength(32)] + public string Platform { get; set; } = string.Empty; + + /// Optional category for routing — also reused by Phase + /// 11's POST /api/hile/v1/log (huddle telemetry) which + /// piggybacks on this table with Category=`hile`. + [MaxLength(32)] + public string Category { get; set; } = "bug"; + + public DateTime CreatedAt { get; set; } = DateTime.UtcNow; + + public DateTime? ReadAt { get; set; } +} diff --git a/DorkNet.Server/Data/Entities/ChatMessageEntity.cs b/DorkNet.Server/Data/Entities/ChatMessageEntity.cs new file mode 100644 index 0000000..29ae890 --- /dev/null +++ b/DorkNet.Server/Data/Entities/ChatMessageEntity.cs @@ -0,0 +1,39 @@ +using System.ComponentModel.DataAnnotations; + +namespace DorkNet.Server.Data.Entities; + +/// +/// In-game text-chat message. Sent to the chat.rec.net/thread +/// endpoint family; backed by a flat per-thread row table where the +/// thread id is just the canonical (lower, higher) pair of player ids +/// for DMs, or the room id for room-wide chats. +/// +/// We keep a separate table from because +/// the wire shape and history-fetch semantics differ — chat is +/// append-and-read-recent, messages are inbox-style with read +/// receipts. +/// +public class ChatMessageEntity +{ + public long Id { get; set; } + + /// String thread key. Format: dm:: for + /// direct messages between two players (lower id first), or + /// room: for room-wide chats. + [MaxLength(64)] + public string ThreadKey { get; set; } = string.Empty; + + public long SenderPlayerId { get; set; } + + [MaxLength(2000)] + public string Body { get; set; } = string.Empty; + + public DateTime SentAt { get; set; } = DateTime.UtcNow; + + /// Build the canonical DM thread key for two players. + /// Order-independent so both sides hit the same row. + public static string DmKey(long a, long b) => + a < b ? $"dm:{a}:{b}" : $"dm:{b}:{a}"; + + public static string RoomKey(long roomId) => $"room:{roomId}"; +} diff --git a/DorkNet.Server/Data/Entities/ChatThreadEntity.cs b/DorkNet.Server/Data/Entities/ChatThreadEntity.cs new file mode 100644 index 0000000..79bc83a --- /dev/null +++ b/DorkNet.Server/Data/Entities/ChatThreadEntity.cs @@ -0,0 +1,31 @@ +using System.ComponentModel.DataAnnotations; + +namespace DorkNet.Server.Data.Entities; + +/// +/// Per-thread metadata for chat: name (for group chats), creator, and +/// timestamps. Rows here are sparse — a two-person DM doesn't need +/// one unless the players want a custom thread name. Group chats +/// always have a row so the member set + name are persisted. +/// +public class ChatThreadEntity +{ + public long Id { get; set; } + + /// Canonical thread key (matches ChatMessageEntity.ThreadKey). + /// Format: dm:<low>:<high> for DMs, + /// group:<guid> for group chats, room:<id> for room-wide. + [MaxLength(96)] + public string ThreadKey { get; set; } = string.Empty; + + /// Optional custom name. DMs default to empty (watch renders + /// "Chat with <username>" client-side); group chats persist a + /// name set via PUT thread/{id}/rename. + [MaxLength(128)] + public string Name { get; set; } = string.Empty; + + public long CreatorPlayerId { get; set; } + + public DateTime CreatedAt { get; set; } = DateTime.UtcNow; + public DateTime UpdatedAt { get; set; } = DateTime.UtcNow; +} diff --git a/DorkNet.Server/Data/Entities/ChatThreadMemberEntity.cs b/DorkNet.Server/Data/Entities/ChatThreadMemberEntity.cs new file mode 100644 index 0000000..e8082a4 --- /dev/null +++ b/DorkNet.Server/Data/Entities/ChatThreadMemberEntity.cs @@ -0,0 +1,36 @@ +namespace DorkNet.Server.Data.Entities; + +/// +/// Per-(thread, player) row: membership, per-player snooze, and the +/// last-read message id used for unread counts and the inbox's "X +/// unread" badge. One row per active participant. A "leave" deletes +/// the row so the player no longer receives new messages on that +/// thread. +/// +/// For two-person DMs without a custom name, rows are created lazily +/// on first MarkRead / Snooze / Leave — the thread itself still works +/// fine without rows (the message table is the source of truth for +/// who participated). +/// +public class ChatThreadMemberEntity +{ + public long Id { get; set; } + + /// The thread this membership applies to. Matches + /// ChatMessageEntity.ThreadKey / ChatThreadEntity.ThreadKey. + public string ThreadKey { get; set; } = string.Empty; + + public long PlayerId { get; set; } + + public DateTime JoinedAt { get; set; } = DateTime.UtcNow; + + /// When set, the watch's inbox treats this thread as + /// muted until the UTC timestamp. Cleared by re-snoozing with a + /// null payload. + public DateTime? SnoozeUntil { get; set; } + + /// Id of the last ChatMessageEntity the player + /// has acknowledged via PUT thread/{id}/read. Drives the + /// per-thread unread counter on the watch's inbox tab. + public long? LastReadMessageId { get; set; } +} diff --git a/DorkNet.Server/Data/Entities/CheerEntity.cs b/DorkNet.Server/Data/Entities/CheerEntity.cs new file mode 100644 index 0000000..5f8eb8b --- /dev/null +++ b/DorkNet.Server/Data/Entities/CheerEntity.cs @@ -0,0 +1,30 @@ +namespace DorkNet.Server.Data.Entities; + +/// +/// One cheer (the watch's positive-feedback button). Targets exactly +/// one of: a player (stored on ), a room +/// (), or a photo (). +/// Unused targets are 0. Aggregated reputation queries SUM rows by +/// target. +/// +/// One row per (FromPlayer, target, Type) — re-cheering is idempotent +/// (we upsert on the unique index). +/// +public class CheerEntity +{ + public long Id { get; set; } + + public long FromPlayerId { get; set; } + + public long TargetPlayerId { get; set; } + public long TargetRoomId { get; set; } + public long TargetPhotoId { get; set; } + public long TargetInventionId { get; set; } + + /// RoomieCheerType enum mirror: 0=General, 1=Helpful, + /// 2=GreatHost, 3=Sportsman, 4=Creative, 5=Credit. Stored as int + /// to leave room for future categories without a migration. + public int Type { get; set; } + + public DateTime CheeredAt { get; set; } = DateTime.UtcNow; +} diff --git a/DorkNet.Server/Data/Entities/ClubAnnouncementEntity.cs b/DorkNet.Server/Data/Entities/ClubAnnouncementEntity.cs new file mode 100644 index 0000000..e4a5e83 --- /dev/null +++ b/DorkNet.Server/Data/Entities/ClubAnnouncementEntity.cs @@ -0,0 +1,50 @@ +using System.ComponentModel.DataAnnotations; + +namespace DorkNet.Server.Data.Entities; + +/// +/// A club announcement — wire type RecNet.ClubAnnouncement +/// (Cpp2IL_ISIL/.../NFEMLMAFFIP.txt). The 2020.12 watch reads +/// the JSON keys +/// AnnouncementId, CreatorAccountId, ClubId, Title, Body, ImageName, +/// CreatedAt, Meta verbatim — +/// emits exactly those, PascalCase. Drives the bell-icon "you have unread +/// club announcements" feed under announcements/v2/mine/unread and +/// announcements/v2/subscription/mine/unread. +/// +public class ClubAnnouncementEntity +{ + /// Mirrors wire field AnnouncementId (Int64). + public long Id { get; set; } + + public long ClubId { get; set; } + + /// Mirrors wire CreatorAccountId (Int32 in the + /// payload — cast in the serialiser). + public long AuthorPlayerId { get; set; } + + [MaxLength(200)] + public string Title { get; set; } = string.Empty; + + [MaxLength(4000)] + public string Body { get; set; } = string.Empty; + + [MaxLength(256)] + public string ImageName { get; set; } = string.Empty; + + public DateTime CreatedAt { get; set; } = DateTime.UtcNow; + public DateTime UpdatedAt { get; set; } = DateTime.UtcNow; +} + +/// +/// One row per (announcement, player). Records which player has read +/// which announcement so the unread feeds can filter out already-seen +/// rows. Upserted by ClubService.MarkAnnouncementReadAsync. +/// +public class ClubAnnouncementReadEntity +{ + public long Id { get; set; } + public long AnnouncementId { get; set; } + public long PlayerId { get; set; } + public DateTime ReadAt { get; set; } = DateTime.UtcNow; +} diff --git a/DorkNet.Server/Data/Entities/ClubCategoryTagEntity.cs b/DorkNet.Server/Data/Entities/ClubCategoryTagEntity.cs new file mode 100644 index 0000000..e1db8c7 --- /dev/null +++ b/DorkNet.Server/Data/Entities/ClubCategoryTagEntity.cs @@ -0,0 +1,64 @@ +using System.ComponentModel.DataAnnotations; + +namespace DorkNet.Server.Data.Entities; + +/// +/// One row per available club category tag. Backs +/// GET /club/categoryTags — the 2020.12 watch deserialises that +/// response as List<String> ( projects +/// just the column out per +/// Cpp2IL_ISIL/.../JDJGIBLMFKK.txt:>GetPrimaryTags's +/// Action<List<String>> callback signature), but the +/// table stores Id + OrderIndex + Active so admins can re-order the +/// list and soft-delete entries without losing referential history in +/// . +/// +public class ClubCategoryTagEntity +{ + public long Id { get; set; } + + [MaxLength(64)] + public string Name { get; set; } = string.Empty; + + /// Display order — lower values render first. Letters and + /// re-ordering both flow through admin tooling; the wire response is + /// emitted in order. + public int OrderIndex { get; set; } + + /// Soft-delete flag. Inactive tags stay in the table so + /// historical assignments resolve, but are excluded from the + /// /club/categoryTags response. + public bool Active { get; set; } = true; +} + +/// +/// Junction row binding a to a +/// . Many-to-many; a single club can +/// carry multiple category tags (e.g. "Sports" + "Hangout"). The +/// 2020.12 watch surfaces the Club's primary category via the +/// Category string field on the Club wire type — assignments +/// are admin-facing for now and feed future v2 endpoints that return +/// the full tag list per club. +/// +public class ClubCategoryAssignmentEntity +{ + public long Id { get; set; } + public long ClubId { get; set; } + public long CategoryTagId { get; set; } +} + +/// +/// One row per (club, subscriber) — drives the +/// announcements/v2/subscription/mine/unread feed. Distinct from +/// because a player can subscribe to +/// announcements without joining the club's roster (e.g. "follow this +/// public club's announcements" without becoming a member). +/// +public class ClubSubscriptionEntity +{ + public long Id { get; set; } + public long ClubId { get; set; } + public long PlayerId { get; set; } + public DateTime SubscribedAt { get; set; } = DateTime.UtcNow; +} diff --git a/DorkNet.Server/Data/Entities/ClubEntity.cs b/DorkNet.Server/Data/Entities/ClubEntity.cs new file mode 100644 index 0000000..a782e42 --- /dev/null +++ b/DorkNet.Server/Data/Entities/ClubEntity.cs @@ -0,0 +1,104 @@ +using System.ComponentModel.DataAnnotations; + +namespace DorkNet.Server.Data.Entities; + +/// +/// A "Group" / club — wire type RecNet.Group +/// (Cpp2IL_CS/.../RecNet/Group.cs) for the legacy +/// api/groups/v1/* surface, and the newer RecNet.Club +/// (Cpp2IL_ISIL/.../PLILLKHMNDA.txt) for the +/// clubs.localhost/club/* surface used by 2020.12. Both wire +/// shapes are projected from this one entity by the respective +/// controllers; field names + types match each client deserializer +/// so the watch round-trips cleanly. +/// +/// The 2020.12 Club wire keys (per ISIL deserializer): +/// ClubId, Name, Description, MainImageName, State, CreatorAccountId, +/// Category, Visibility, Joinability, AllowJuniors, MemberCount, +/// IsRRO, ClubhouseRoomId, ClubType. +/// MemberCount is derived live from +/// at projection time so we never have to keep a denormalised counter +/// in sync; the other fields are persisted columns on this row. +/// +public class ClubEntity +{ + /// Mirrors wire field GroupId / ClubId. + public long Id { get; set; } + + [MaxLength(128)] + public string Name { get; set; } = string.Empty; + + [MaxLength(2000)] + public string Description { get; set; } = string.Empty; + + /// Mirrors wire CreatorId (legacy) / CreatorAccountId + /// (Int32 in the 2020.12 payload). Cast to int in the wire mappers. + public long CreatorPlayerId { get; set; } + + /// Legacy ImageName for api/groups/v1/*. The 2020.12 + /// Club wire field is MainImageName — same column, two projection + /// names. + [MaxLength(256)] + public string ImageName { get; set; } = string.Empty; + + /// GroupBanStatus enum: 0=GoodStanding, 1=InReview, + /// 2=TempLock, 3=Permaban. + public int BanStatus { get; set; } = 0; + + /// Club state enum on the 2020.12 wire (State). + /// 0=Active, 1=Disbanded, 2=Suspended. Defaults to Active so legacy + /// rows created via api/groups/v1/* still surface as live. + public int State { get; set; } = 0; + + /// Primary category-tag name surfaced in the 2020.12 Club + /// wire Category field. Empty string is valid — the watch + /// renders "uncategorized" when blank. + [MaxLength(64)] + public string Category { get; set; } = string.Empty; + + /// Club visibility enum on the wire (Visibility). + /// 0=Public, 1=Unlisted, 2=Private. + public int Visibility { get; set; } = 0; + + /// Club joinability enum on the wire (Joinability). + /// 0=Open, 1=RequestToJoin, 2=InviteOnly. + public int Joinability { get; set; } = 0; + + /// Drives the wire AllowJuniors bool. True means + /// junior accounts can join + see member-only content. + public bool AllowJuniors { get; set; } = true; + + /// Drives the wire IsRRO bool — Rec Room Original + /// stamp on first-party clubs. Default false; admin-toggle only. + public bool IsRRO { get; set; } = false; + + /// Drives the wire nullable ClubhouseRoomId. When + /// set, the watch shows a "Go to clubhouse" CTA on the club + /// detail screen that routes to this room's standard goto flow. + public long? ClubhouseRoomId { get; set; } + + /// Drives the wire ClubType int. 0=Standard, + /// 1=Featured. The legacy api/groups/v1 surface ignores this; the + /// 2020.12 watch uses it to sort featured clubs to the top of + /// browse. + public int ClubType { get; set; } = 0; + + public DateTime CreatedAt { get; set; } = DateTime.UtcNow; + public DateTime UpdatedAt { get; set; } = DateTime.UtcNow; +} + +/// One row per (club, player). Permissions is the +/// GroupMembershipPermissions flags enum (Member=0, Moderator=24, +/// CoOwner=124, Owner=127, Pending=128 for join-requests on private +/// groups). +public class ClubMembershipEntity +{ + public long Id { get; set; } + public long ClubId { get; set; } + public long PlayerId { get; set; } + + /// GroupMembershipPermissions flags enum value. + public int Permissions { get; set; } = 0; + + public DateTime JoinedAt { get; set; } = DateTime.UtcNow; +} diff --git a/DorkNet.Server/Data/Entities/CommunityBoardEntity.cs b/DorkNet.Server/Data/Entities/CommunityBoardEntity.cs new file mode 100644 index 0000000..5475856 --- /dev/null +++ b/DorkNet.Server/Data/Entities/CommunityBoardEntity.cs @@ -0,0 +1,28 @@ +namespace DorkNet.Server.Data.Entities; + +/// +/// Single-row table holding the dorm community board state. Replaces +/// data/community_board.json on disk + in-memory cache so admin +/// edits propagate across replicas without a file-watcher invalidation +/// dance. +/// +/// Only one row exists, with = 1. Reading is a single +/// SELECT (cheap; the board is one document, not paginated). Writing +/// is a single UPDATE — no synchronization needed because there's only +/// ever one row to contend on, and admin edits are rare. +/// +public class CommunityBoardEntity +{ + /// Always 1. We model it as an int PK rather than a + /// composite or singleton-table pattern because EF Core needs a key + /// to track changes; pinning to id=1 keeps the upsert pattern + /// simple. + public int Id { get; set; } = 1; + + /// JSON-serialised CommunityBoardState. Stored as + /// text rather than jsonb because we never query inside it — it's + /// loaded as a single blob and round-tripped to the client. + public string Json { get; set; } = "{}"; + + public DateTime UpdatedAt { get; set; } = DateTime.UtcNow; +} diff --git a/DorkNet.Server/Data/Entities/CouponEntities.cs b/DorkNet.Server/Data/Entities/CouponEntities.cs new file mode 100644 index 0000000..7d2a63a --- /dev/null +++ b/DorkNet.Server/Data/Entities/CouponEntities.cs @@ -0,0 +1,47 @@ +using System.ComponentModel.DataAnnotations; + +namespace DorkNet.Server.Data.Entities; + +/// +/// Admin-issued promo coupon. Players redeem by code via +/// POST /api/coupons/v1/redeem/{code}. Each redemption +/// inserts a row (one per +/// (coupon, player) pair so we can enforce one-redeem-per-player). +/// +public class CouponEntity +{ + public long Id { get; set; } + + [MaxLength(64)] + public string Code { get; set; } = string.Empty; + + [MaxLength(256)] + public string Description { get; set; } = string.Empty; + + /// RewardType: 0=Currency, 1=ItemSlug, 2=Subscription. + public int RewardType { get; set; } + + /// Currency type if RewardType=0; otherwise unused. + public int RewardCurrencyType { get; set; } + + public long RewardAmount { get; set; } + + /// Item slug if RewardType=1. + [MaxLength(64)] + public string RewardItemSlug { get; set; } = string.Empty; + + public bool IsActive { get; set; } = true; + public int MaxRedemptions { get; set; } = 0; + public int RedemptionCount { get; set; } = 0; + + public DateTime? ExpiresAt { get; set; } + public DateTime CreatedAt { get; set; } = DateTime.UtcNow; +} + +public class CouponRedemptionEntity +{ + public long Id { get; set; } + public long CouponId { get; set; } + public long PlayerId { get; set; } + public DateTime RedeemedAt { get; set; } = DateTime.UtcNow; +} diff --git a/DorkNet.Server/Data/Entities/CurrencyBalanceEntity.cs b/DorkNet.Server/Data/Entities/CurrencyBalanceEntity.cs new file mode 100644 index 0000000..944636c --- /dev/null +++ b/DorkNet.Server/Data/Entities/CurrencyBalanceEntity.cs @@ -0,0 +1,19 @@ +namespace DorkNet.Server.Data.Entities; + +/// +/// Per-player wallet for a single currency. The 2020 client knows +/// about a small set of CurrencyTypes (1=Coins, 2=Tokens, …); +/// each gets its own row keyed by (PlayerId, CurrencyType). +/// +/// Updates go through +/// or admin grants; reads back through the +/// storefronts/v4/balance/{currencyType} endpoint. +/// +public class CurrencyBalanceEntity +{ + public long Id { get; set; } + public long PlayerId { get; set; } + public int CurrencyType { get; set; } + public long Balance { get; set; } + public DateTime UpdatedAt { get; set; } = DateTime.UtcNow; +} diff --git a/DorkNet.Server/Data/Entities/DormStateEntity.cs b/DorkNet.Server/Data/Entities/DormStateEntity.cs new file mode 100644 index 0000000..9df24bd --- /dev/null +++ b/DorkNet.Server/Data/Entities/DormStateEntity.cs @@ -0,0 +1,31 @@ +using System.ComponentModel.DataAnnotations; + +namespace DorkNet.Server.Data.Entities; + +/// +/// Per-player dorm state. Decouples each player's dorm save bytes from +/// the canonical RoomEntity row at RoomId=1 (which is shared +/// across every dorm visit and would otherwise leak one player's save +/// into every other player's dorm). +/// +/// One row per account that has ever entered their dorm. Created on the +/// first /api/rooms/v4/details/1 hit and updated whenever the +/// player saves their dorm via Maker Pen. +/// +public class DormStateEntity +{ + /// The owning account id. Primary key — one dorm state row + /// per player. + [Key] + public long PlayerId { get; set; } + + /// Blob name for the player's most recent dorm save. Format + /// dorm_p<accountId>_v<n>.dat; matches the + /// stored when the upload + /// landed. Empty string before the player has saved. + [MaxLength(128)] + public string CurrentDataBlobName { get; set; } = string.Empty; + + public DateTime CreatedAt { get; set; } = DateTime.UtcNow; + public DateTime UpdatedAt { get; set; } = DateTime.UtcNow; +} diff --git a/DorkNet.Server/Data/Entities/GameRewardSelectionEntity.cs b/DorkNet.Server/Data/Entities/GameRewardSelectionEntity.cs new file mode 100644 index 0000000..825cac3 --- /dev/null +++ b/DorkNet.Server/Data/Entities/GameRewardSelectionEntity.cs @@ -0,0 +1,15 @@ +using System.ComponentModel.DataAnnotations; + +namespace DorkNet.Server.Data.Entities; + +public class GameRewardSelectionEntity +{ + public long Id { get; set; } + public long PlayerId { get; set; } + public int RewardType { get; set; } + public int GiftContext { get; set; } + [MaxLength(256)] public string Message { get; set; } = string.Empty; + public DateTime CreatedAt { get; set; } = DateTime.UtcNow; + public DateTime? SelectedAt { get; set; } + public int? SelectedGiftDropId { get; set; } +} diff --git a/DorkNet.Server/Data/Entities/GameSessionEntity.cs b/DorkNet.Server/Data/Entities/GameSessionEntity.cs new file mode 100644 index 0000000..2920c71 --- /dev/null +++ b/DorkNet.Server/Data/Entities/GameSessionEntity.cs @@ -0,0 +1,36 @@ +using System.ComponentModel.DataAnnotations; + +namespace DorkNet.Server.Data.Entities; + +/// +/// Active game session backing the matchmaking JoinOrCreate flow +/// in . Replaces +/// the in-process ConcurrentDictionary + atomic counter so: +/// +/// Session ids are allocated by Postgres bigserial — no +/// replica-local counter that collides with another replica's +/// after restart. +/// A "find a non-full session in this room+region" query +/// coordinates across all replicas; without it, two players +/// joining via different replicas could each create their own +/// fresh session for the same room and end up isolated. +/// +/// +/// Sessions are deleted when their PlayerCount hits 0 (handled in the +/// service, no FK cascade needed). Brief mid-rejoin flicker is fine — +/// the next JoinOrCreate just creates a new session with a new id. +/// +public class GameSessionEntity +{ + /// bigserial — allocated by Postgres on insert. Crosses + /// replicas safely because the sequence is shared. + public long Id { get; set; } + + [MaxLength(64)] public string RoomId { get; set; } = string.Empty; + [MaxLength(64)] public string ActivityLevelId { get; set; } = string.Empty; + [MaxLength(16)] public string Region { get; set; } = "us"; + [MaxLength(128)] public string PhotonRoomName { get; set; } = string.Empty; + public int MaxCapacity { get; set; } = 8; + public int PlayerCount { get; set; } = 0; + public DateTime CreatedAt { get; set; } = DateTime.UtcNow; +} diff --git a/DorkNet.Server/Data/Entities/GiftPackageEntity.cs b/DorkNet.Server/Data/Entities/GiftPackageEntity.cs new file mode 100644 index 0000000..91b06db --- /dev/null +++ b/DorkNet.Server/Data/Entities/GiftPackageEntity.cs @@ -0,0 +1,96 @@ +using System.ComponentModel.DataAnnotations; + +namespace DorkNet.Server.Data.Entities; + +/// +/// Per RecNet.Avatars+GiftPackage wire shape (decompiled at +/// Cpp2IL_CS/.../RecNet/Avatars.cs:192-555) — a queued gift the +/// recipient can claim from the watch's "gifts" inbox. Used for both +/// admin-granted gifts and gift-drop / mystery-box / level-up prizes. +/// +/// Field names + types match the client's +/// GiftPackage.Deserialize(Dictionary<String, Object>) +/// directly so the watch round-trips cleanly. +/// is the bool the client reads — we additionally stamp +/// for server-side bookkeeping (not on wire). +/// +public class GiftPackageEntity +{ + public long Id { get; set; } + + /// The player whose inbox this gift sits in. + public long RecipientPlayerId { get; set; } + + /// Wire field (Nullable<Int32>) — the player who + /// sent the gift, or null for system / drop gifts. + public int? FromPlayerId { get; set; } + + [MaxLength(128)] + public string ConsumableItemDesc { get; set; } = string.Empty; + + /// Wire field AvatarItemType as Nullable<int> + /// (0 = Outfit, 1 = HairDye). Null when the gift has no avatar + /// item (currency-only, equipment-only, etc.). + public int? AvatarItemType { get; set; } + + /// Wire field name is exactly AvatarItemDescOrHairDyeDesc + /// — content is either the AvatarItem GUID (when AvatarItemType=0) + /// or the HairDye desc string (when AvatarItemType=1). + [MaxLength(128)] + public string AvatarItemDescOrHairDyeDesc { get; set; } = string.Empty; + + [MaxLength(128)] + public string EquipmentPrefabName { get; set; } = string.Empty; + + [MaxLength(64)] + public string EquipmentModificationGuid { get; set; } = string.Empty; + + /// CurrencyType enum: 0=Invalid, 1=LaserTagTickets, + /// 2=RecCenterTokens, 100=LostSkullsGold, 101=DraculaSilver, + /// 200=RecRoyale_Season1. + public int CurrencyType { get; set; } = 0; + + public int Currency { get; set; } = 0; + public int Xp { get; set; } = 0; + public int Level { get; set; } = 0; + + /// GiftContext enum (large, see Avatars.cs comments). + /// 0 = Default, 100 = LevelUp, 1000 = Holiday, etc. + public int GiftContext { get; set; } = 0; + + /// GiftRarity: -1=None, 0=Common, 10=Uncommon, 20=Rare, + /// 30=Epic, 50=Legendary. + public int GiftRarity { get; set; } = 0; + + [MaxLength(512)] + public string Message { get; set; } = string.Empty; + + /// PlatformType: -1=All, 0=Steam, 1=Oculus, 2=PlayStation, + /// 3=Microsoft, 4=HeadlessBot, 5=IOS. + public int Platform { get; set; } = -1; + + /// Asset name string for the Unity Material that the + /// gift box renders with. + [MaxLength(128)] + public string PackageMaterial { get; set; } = string.Empty; + + /// Asset name string for the GiftPackageVariant + /// ScriptableObject (e.g. "Standard", "Holiday", etc.). + [MaxLength(128)] + public string PackageVariant { get; set; } = string.Empty; + + public bool Consumed { get; set; } = false; + public bool IsValid { get; set; } = true; + + [MaxLength(512)] + public string ErrorMessage { get; set; } = string.Empty; + + public bool SupportsCurrentPlatform { get; set; } = true; + public bool IsGifted { get; set; } = false; + + /// Server-side audit timestamp; not part of wire + /// shape. + public DateTime? ConsumedAt { get; set; } + + public DateTime CreatedAt { get; set; } = DateTime.UtcNow; +} diff --git a/DorkNet.Server/Data/Entities/InventionEntity.cs b/DorkNet.Server/Data/Entities/InventionEntity.cs new file mode 100644 index 0000000..8f5460c --- /dev/null +++ b/DorkNet.Server/Data/Entities/InventionEntity.cs @@ -0,0 +1,97 @@ +using System.ComponentModel.DataAnnotations; + +namespace DorkNet.Server.Data.Entities; + +/// +/// A Maker-Pen creation that the player has saved as a reusable +/// "invention" — can be re-spawned in any room they enter, shared with +/// other players, or featured on the watch's Inventions tab. The +/// underlying SpawnableTemplateData protobuf bytes are uploaded via +/// storage.rec.net/upload with FileType=Invention (5). +/// +public class InventionEntity +{ + public long Id { get; set; } + + public long CreatorPlayerId { get; set; } + + /// Stable opaque GUID the client uses to deduplicate + /// instantiated inventions across rooms (matches + /// Invention.ReplicationId). Generated server-side at + /// create time. + [MaxLength(64)] + public string ReplicationId { get; set; } = string.Empty; + + [MaxLength(128)] + public string Name { get; set; } = string.Empty; + + [MaxLength(2000)] + public string Description { get; set; } = string.Empty; + + [MaxLength(256)] + public string ImageName { get; set; } = string.Empty; + + /// Legacy permission column (private=0/friends=1/public=2) + /// — kept for backward-compat with existing rows; new logic uses + /// + + /// (InventionPermission enum: Unassigned=0, LimitedOneUseOnly=10, + /// UseOnly=20, EditAndSave=40, Publish=60, Charge=80, + /// Unlimited=100). + public int Permission { get; set; } = 0; + + /// InventionPermission value the creator gets when + /// spawning their own creation (default Unlimited=100). + public int CreatorPermission { get; set; } = 100; + + /// InventionPermission value other players get when + /// spawning. Defaults Unassigned=0 (private). + public int GeneralPermission { get; set; } = 0; + + /// True once POST api/inventions/v2/publish sets + /// a non-Unassigned . Stays true + /// across re-edits — the wire field tracks "has this ever been + /// published". + public bool IsPublished { get; set; } = false; + + /// 1-based latest version index. Bumped each time + /// POST api/inventions/v3/addversion uploads new bytes. + /// Mirrors Invention.CurrentVersionNumber. + public int CurrentVersionNumber { get; set; } = 1; + + /// Set on first publish. Null until then. + public DateTime? FirstPublishedAt { get; set; } + + /// If the creator was in a room when first saving, + /// the originating room id for analytics. + public long? CreationRoomId { get; set; } + + /// How many distinct players have spawned this + /// invention in any room (rolling counter — increments on + /// download). + public int NumPlayersHaveUsedInRoom { get; set; } = 0; + + /// True for AG (community-built) inventions, false for + /// "Rec Room Original" / staff-baked ones. + public bool IsAgInvention { get; set; } = true; + + /// Most recent uploaded blob name. Format + /// invention__v.dat; the cdn catch-all serves the + /// matching bytes. + [MaxLength(128)] + public string CurrentBlobName { get; set; } = string.Empty; + + [MaxLength(1024)] + public string TagsCsv { get; set; } = string.Empty; + + public int CheerCount { get; set; } = 0; + public int SpawnCount { get; set; } = 0; + + /// Soft-delete flag. When set, the row is hidden from + /// browse / search / detail endpoints but stays in the table so + /// existing references (cheers, reports) keep resolving and the + /// blob bytes can be restored if needed. + public bool IsDeleted { get; set; } = false; + + public DateTime CreatedAt { get; set; } = DateTime.UtcNow; + public DateTime UpdatedAt { get; set; } = DateTime.UtcNow; +} diff --git a/DorkNet.Server/Data/Entities/InventionVersionEntity.cs b/DorkNet.Server/Data/Entities/InventionVersionEntity.cs new file mode 100644 index 0000000..9480aed --- /dev/null +++ b/DorkNet.Server/Data/Entities/InventionVersionEntity.cs @@ -0,0 +1,42 @@ +using System.ComponentModel.DataAnnotations; + +namespace DorkNet.Server.Data.Entities; + +/// +/// One historical version of an . Each +/// time the creator uploads a new SpawnableTemplateData blob via +/// POST api/inventions/v3/addversion, we snapshot the prior +/// blob name into a new row here so the watch's "version history" +/// tab and `GET api/inventions/v1/versions?inventionId=X` can list +/// them. Indexed on (InventionId, VersionNumber DESC) for the +/// common "show me the version list" query. +/// +public class InventionVersionEntity +{ + public long Id { get; set; } + public long InventionId { get; set; } + + /// Mirrors InventionVersion.ReplicationId — opaque + /// GUID the client uses to deduplicate spawns of this specific + /// version across rooms. + [MaxLength(64)] + public string ReplicationId { get; set; } = string.Empty; + + /// 1-based version index. Auto-assigned to + /// max(VersionNumber) + 1 on insert. + public int VersionNumber { get; set; } + + [MaxLength(128)] + public string BlobName { get; set; } = string.Empty; + + /// Tokens it costs to spawn this version of the + /// invention (decompiled wire field + /// InventionVersion.InstantiationCost). + public int InstantiationCost { get; set; } = 0; + + /// Watt budget the creation consumes + /// (InventionVersion.LightsCost). + public int LightsCost { get; set; } = 0; + + public DateTime CreatedAt { get; set; } = DateTime.UtcNow; +} diff --git a/DorkNet.Server/Data/Entities/IpBanEntity.cs b/DorkNet.Server/Data/Entities/IpBanEntity.cs new file mode 100644 index 0000000..9f07d89 --- /dev/null +++ b/DorkNet.Server/Data/Entities/IpBanEntity.cs @@ -0,0 +1,30 @@ +using System.ComponentModel.DataAnnotations; + +namespace DorkNet.Server.Data.Entities; + +/// +/// IP-level ban for blocking misbehaving traffic before the JWT-based +/// player ban can apply. Checked by IpBanCheckMiddleware ahead +/// of authentication; matching requests get a 403. +/// +/// CIDR notation lets a single row block a /24 or /32. The expiry is +/// optional — null means "permanent until manually removed". +/// +public class IpBanEntity +{ + public long Id { get; set; } + + /// CIDR or single-IP string (1.2.3.4 or 1.2.3.0/24). + /// Compared against HttpContext.Connection.RemoteIpAddress. + [MaxLength(64)] + public string Cidr { get; set; } = string.Empty; + + [MaxLength(512)] + public string Reason { get; set; } = string.Empty; + + public long BannedByAdminId { get; set; } + public DateTime BannedAt { get; set; } = DateTime.UtcNow; + + /// Null = permanent. + public DateTime? Until { get; set; } +} diff --git a/DorkNet.Server/Data/Entities/LeaderboardChannelMetaEntity.cs b/DorkNet.Server/Data/Entities/LeaderboardChannelMetaEntity.cs new file mode 100644 index 0000000..2e57a2b --- /dev/null +++ b/DorkNet.Server/Data/Entities/LeaderboardChannelMetaEntity.cs @@ -0,0 +1,40 @@ +namespace DorkNet.Server.Data.Entities; + +/// +/// Admin-curated metadata for a leaderboard StatChannel id. The +/// 2020 watch reports raw integer channel ids via +/// POST api/Leaderboard/v1/SetStats; this table lets admins map +/// each channel to a specific room (so the room detail page can list +/// "its" leaderboards) and a human-readable name + ordering hint. Rows +/// here override the hardcoded AdminController.KnownStatChannels +/// fallback. Channels with no row stay listed as "Channel N". +/// +/// One channel id, one row. Repeated SetStats calls for the same +/// channel from any room write to that channel's value rows; the meta +/// row only changes when an admin renames or re-maps it. +/// +public class LeaderboardChannelMetaEntity +{ + /// Same int the watch reports for this channel. Primary + /// key — there's only ever one meta row per channel. + public int Channel { get; set; } + + /// Room this channel "belongs to" for the per-room detail + /// view. 0 = global / unscoped (e.g. cross-room player progression + /// counters). The same room can own many channels (e.g. Stunt + /// Runner has one channel per course). + public long RoomId { get; set; } + + public string Name { get; set; } = string.Empty; + + /// When true, a lower stored value is a better score (race + /// times, lap times, etc.). The list endpoint sorts ascending. + public bool LowerIsBetter { get; set; } + + /// UI hint: "count" (default), "time-ms" for elapsed-time + /// channels, "score" for points. The SPA formats accordingly. + public string ValueFormat { get; set; } = "count"; + + public DateTime CreatedAt { get; set; } = DateTime.UtcNow; + public DateTime UpdatedAt { get; set; } = DateTime.UtcNow; +} diff --git a/DorkNet.Server/Data/Entities/LoadingScreenTipEntity.cs b/DorkNet.Server/Data/Entities/LoadingScreenTipEntity.cs new file mode 100644 index 0000000..d2e9702 --- /dev/null +++ b/DorkNet.Server/Data/Entities/LoadingScreenTipEntity.cs @@ -0,0 +1,43 @@ +using System.ComponentModel.DataAnnotations; + +namespace DorkNet.Server.Data.Entities; + +/// +/// Tip shown on the dorm-load splash. Served via +/// cdn.{rec.net,localhost}/config/LoadingScreenTipData; edited via +/// the admin SPA. Wire shape per RecNet/LoadingScreenTip.cs +/// requires Title/Message/ImageName + Context/PlatformMask/HasImage +/// flags + an optional RoomNames list (scopes the tip to specific +/// rooms). +/// +public class LoadingScreenTipEntity +{ + public long Id { get; set; } + + [MaxLength(128)] public string Title { get; set; } = string.Empty; + [MaxLength(512)] public string Message { get; set; } = string.Empty; + + /// Optional uploaded image (RoomDataBlob filename). Empty + /// when the tip is text-only; CdnController sets + /// HasImage in the wire payload based on this. + [MaxLength(128)] public string ImageName { get; set; } = string.Empty; + + /// RecNet LoadingScreenContext enum (0 = Any). Controls + /// which loading screens the tip is eligible for. + public int Context { get; set; } = 0; + + /// PlatformMask bitfield (-1 = every platform). Used by + /// the watch to filter out tips that reference platform-specific + /// features (e.g. mobile-only). + public int PlatformMask { get; set; } = -1; + + /// CSV of room names this tip is scoped to. Empty = show + /// in any room's loading screen. Example: "DormRoom,MakerRoom". + [MaxLength(512)] public string RoomNamesCsv { get; set; } = string.Empty; + + public int SortOrder { get; set; } = 0; + public bool IsActive { get; set; } = true; + + public DateTime CreatedAt { get; set; } = DateTime.UtcNow; + public DateTime UpdatedAt { get; set; } = DateTime.UtcNow; +} diff --git a/DorkNet.Server/Data/Entities/MessageEntity.cs b/DorkNet.Server/Data/Entities/MessageEntity.cs new file mode 100644 index 0000000..3a22520 --- /dev/null +++ b/DorkNet.Server/Data/Entities/MessageEntity.cs @@ -0,0 +1,42 @@ +using System.ComponentModel.DataAnnotations; + +namespace DorkNet.Server.Data.Entities; + +/// +/// Direct message between two players. Backs the watch's +/// api/messages/v2 endpoints (inbox + send + mark-read). +/// +/// One row per message. Threading happens at read time — the inbox +/// query groups by (Sender, Recipient) pair to surface the most +/// recent message per conversation. +/// +public class MessageEntity +{ + public long Id { get; set; } + + public long SenderPlayerId { get; set; } + public long RecipientPlayerId { get; set; } + + [MaxLength(2000)] + public string Body { get; set; } = string.Empty; + + /// RecNet Message.MessageType wire value (see + /// Cpp2IL_CS/.../RecNet/Message.cs). Default 30 = + /// TextMessage. Other values used by the 2020 client: 0/6 = + /// GameInvite[V2], 3/7 = PartyActivitySwitch[V2], 10 = + /// RequestGameInvite (the "ask to join" flow), 1 = + /// GameInviteDeclined, 11 = RequestGameInviteDeclined, 20 = + /// FriendStatusOnline. + public int Type { get; set; } = 30; + + /// Optional RecNet room id carried by invite and party + /// activity messages. The client uses this to render room-aware + /// cards and passes it back when accepting V2 invites. + public long? RoomId { get; set; } + + public DateTime SentAt { get; set; } = DateTime.UtcNow; + + /// Null until the recipient hits the message in the + /// watch UI; set to the read timestamp at that point. + public DateTime? ReadAt { get; set; } +} diff --git a/DorkNet.Server/Data/Entities/ObjectiveProgressEntity.cs b/DorkNet.Server/Data/Entities/ObjectiveProgressEntity.cs new file mode 100644 index 0000000..702414a --- /dev/null +++ b/DorkNet.Server/Data/Entities/ObjectiveProgressEntity.cs @@ -0,0 +1,25 @@ +using System.ComponentModel.DataAnnotations; + +namespace DorkNet.Server.Data.Entities; + +/// +/// Per-player objective / achievement progress. The 2020 client's +/// Objectives.MyProgress response is two arrays — Objectives +/// and ObjectiveGroups — keyed by string id. We collapse that to one +/// table here; group rows distinguish themselves with a leading +/// group: prefix on . +/// +public class ObjectiveProgressEntity +{ + public long Id { get; set; } + public long PlayerId { get; set; } + + /// Objective id from the client's hardcoded objective + /// table (e.g. tutorial_dorm_decorate). Group rows use + /// group:<groupId>. + [MaxLength(128)] + public string Key { get; set; } = string.Empty; + + public bool IsCompleted { get; set; } + public DateTime? ClearedAt { get; set; } +} diff --git a/DorkNet.Server/Data/Entities/PhotoEntity.cs b/DorkNet.Server/Data/Entities/PhotoEntity.cs new file mode 100644 index 0000000..a9d07c4 --- /dev/null +++ b/DorkNet.Server/Data/Entities/PhotoEntity.cs @@ -0,0 +1,71 @@ +using System.ComponentModel.DataAnnotations; + +namespace DorkNet.Server.Data.Entities; + +/// +/// One in-game camera photo, posted to the social feed. The actual +/// pixel bytes live in keyed by +/// (uploaded earlier via storage.rec.net's +/// /upload endpoint with FileType=Image). This entity wraps that blob +/// with the social metadata: who took it, where, who's tagged in it, +/// when it was posted, and how many cheers it has. +/// +/// Public photos are visible without auth on feed.rec.net; private +/// photos are only listable by the uploader (and admins). Soft-delete +/// via so moderation can take a photo down +/// without losing the audit trail. +/// +public class PhotoEntity +{ + public long Id { get; set; } + + public long UploaderPlayerId { get; set; } + + /// The cdn filename — the same string returned by + /// storage.rec.net/upload and used in the cdn.rec.net/{name} + /// download path. Indexed because feed lookups join blob bytes + /// via this key. + [MaxLength(256)] + public string BlobName { get; set; } = string.Empty; + + /// Optional caption typed on the watch's "Share" panel + /// before posting. Limited so a single photo can't grow the + /// table row unbounded. + [MaxLength(2048)] + public string Caption { get; set; } = string.Empty; + + /// The room the photo was taken in (resolved from + /// PlayerPresenceService at upload time). 0 if the player wasn't + /// in any room (rare — only happens during loading). + public long RoomId { get; set; } + + /// Comma-separated list of tagged player IDs. The watch + /// builds this from the players visible in the camera's frustum + /// when the shutter was pressed. Tagged players show up in their + /// own "Photos of me" feed. + [MaxLength(2048)] + public string TaggedPlayerIdsCsv { get; set; } = string.Empty; + + /// If false, photo only appears in the uploader's own + /// "My photos" page (and admin views); if true, it shows up on + /// the public feed.rec.net feed and any tagged player's + /// "Photos of me" tab. Defaults to true since the camera's + /// "Share" button is the natural action — making it private is + /// an explicit toggle. + public bool IsPublic { get; set; } = true; + + public DateTime CreatedAt { get; set; } = DateTime.UtcNow; + + /// Soft-delete sentinel — non-null means the photo is + /// hidden from every feed but still in the table for audit. + /// Admin moderation tools set this; the bytes blob is kept until + /// a separate cleanup job decides the audit window has elapsed. + public DateTime? DeletedAt { get; set; } + + /// Denormalised cheer counter — incremented when a + /// CheerEntity row is inserted with TargetPhotoId=this.Id, so the + /// feed sort doesn't have to JOIN+aggregate every render. + public int CheerCount { get; set; } + + public int ViewCount { get; set; } +} diff --git a/DorkNet.Server/Data/Entities/PlayerEntity.cs b/DorkNet.Server/Data/Entities/PlayerEntity.cs new file mode 100644 index 0000000..17f1c4e --- /dev/null +++ b/DorkNet.Server/Data/Entities/PlayerEntity.cs @@ -0,0 +1,123 @@ +using System.ComponentModel.DataAnnotations; + +namespace DorkNet.Server.Data.Entities; + +public class PlayerEntity +{ + public long Id { get; set; } + + // Stable per-installation identifier the client sends as `deviceId` in + // platformlogin / account-create form bodies. We deliberately use this + // (not `platformId`) so accounts are NOT keyed to Steam IDs — anyone + // running the client gets a unique persistent account based on the + // hardware-derived device hash, regardless of which Steam emulator + // (Goldberg, none, real Steam) is in front. Indexed unique below. + [MaxLength(128)] + public string DeviceId { get; set; } = string.Empty; + + // Last-seen platform info — informational, NOT part of account identity. + public int LastPlatform { get; set; } = 0; + + [MaxLength(128)] + public string LastPlatformId { get; set; } = string.Empty; + + [MaxLength(64)] + public string Username { get; set; } = string.Empty; + + [MaxLength(64)] + public string DisplayName { get; set; } = string.Empty; + + [MaxLength(512)] + public string Bio { get; set; } = string.Empty; + + public int Level { get; set; } = 1; + public int XP { get; set; } = 0; + public int Reputation { get; set; } = 0; + public bool IsVerified { get; set; } = false; + + // New accounts default to non-developer. Admins grant the badge + // privilege via the SPA's Players → Profile flags tab on a + // per-player basis (used to default to true while we were + // bootstrapping the emulator; flipped off now that the build is + // stable and we don't want every walk-in to spawn with the dev + // console unlocked). + public bool IsDeveloper { get; set; } = false; + // Mirrors IsDeveloper for the overhead badge slider — the 2020 watch + // gates the in-settings "Developer Display Mode" slider on a single + // role check (`role/developer/{id}`), and the slider's positions + // render as "Community Team" / "Developer" above the player's head + // (verified in Cpp2IL_ISIL/.../PlayerUI.txt:9085-9099 — slider value + // 1 → "Community Team", 2 → "Developer"). Keep this field separate + // from IsDeveloper so admins can mark someone as community-team + // without granting full dev privileges; the role endpoint ORs both. + public bool IsCommunityTeam { get; set; } = false; + public bool CanReceiveInvites { get; set; } = true; + public bool IsJunior { get; set; } = false; + + [MaxLength(256)] + public string? ProfileImageName { get; set; } + + public DateTime CreatedAt { get; set; } = DateTime.UtcNow; + public DateTime LastSeenAt { get; set; } = DateTime.UtcNow; + + // ── Phase 1 account hardening ───────────────────────────────────────── + // Optional contact email. Nullable because the 2020 client never asks + // for one; reserved for future password-reset / admin-recovery flows. + [MaxLength(256)] + public string? Email { get; set; } + + // When non-null AND `> DateTime.UtcNow`, BanCheckMiddleware short-circuits + // every authenticated request from this account with 401. Setting to a + // past timestamp acts as a "previously banned" audit marker without + // blocking access. + public DateTime? BannedUntil { get; set; } + + // Privilege flag for `[AdminOnly]` endpoints (Phase 5). Defaults false; + // PlayerService bumps this to true when the very first account on a + // fresh DB is created so there's always at least one root admin. + public bool IsAdmin { get; set; } = false; + + // Last IP that authenticated against this account. Updated by the + // request-tracing pipeline so we don't write IPs from anonymous paths. + [MaxLength(64)] + public string? LastIp { get; set; } + + // The deviceId that originally created this account. Immutable after + // creation — exists for the audit trail in case the live DeviceId + // rotates (e.g., user re-installs and the hardware hash shifts; the + // account stays bound to whatever they typed in /account/create the + // first time). + [MaxLength(128)] + public string? CreatedFromDeviceId { get; set; } + + // BCP-47 locale tag (e.g. "en-US"). Optional; informs future + // localisation work and is currently set from the client's accept- + // language header at login. + [MaxLength(16)] + public string? Locale { get; set; } + + /// BCrypt hash of the account password. Null when the + /// account has never set one — the in-game settings screen calls + /// /account/me/haspassword and prompts the user to create one + /// before sensitive operations. Format: BCrypt's standard + /// `$2a$<cost>$<salt><hash>` so the verifier + /// reads cost + salt directly, no separate column needed. + [MaxLength(128)] + public string? PasswordHash { get; set; } + + /// Player's birthday (date only; no timezone). Set during + /// signup and used to derive on Junior cutoff + /// boundary crossing. Nullable for legacy accounts created before + /// the field existed. + public DateTime? Birthday { get; set; } + + /// Optional phone number — used by the watch's + /// account-recovery flow. Stored verbatim, never validated. + [MaxLength(32)] + public string? Phone { get; set; } + + // Navigation + public AvatarEntity? Avatar { get; set; } + public List Settings { get; set; } = []; + public List Relationships { get; set; } = []; +} diff --git a/DorkNet.Server/Data/Entities/PlayerEventEntity.cs b/DorkNet.Server/Data/Entities/PlayerEventEntity.cs new file mode 100644 index 0000000..8f3e172 --- /dev/null +++ b/DorkNet.Server/Data/Entities/PlayerEventEntity.cs @@ -0,0 +1,42 @@ +using System.ComponentModel.DataAnnotations; + +namespace DorkNet.Server.Data.Entities; + +/// +/// A player-scheduled gathering ("event") in a specific room. Backs +/// the watch's Events tab — players can RSVP via +/// . +/// +public class PlayerEventEntity +{ + public long Id { get; set; } + + public long CreatorPlayerId { get; set; } + public long RoomId { get; set; } + + [MaxLength(128)] + public string Title { get; set; } = string.Empty; + + [MaxLength(2000)] + public string Description { get; set; } = string.Empty; + + public DateTime StartsAt { get; set; } + public DateTime EndsAt { get; set; } + + public int Capacity { get; set; } = 0; + + public DateTime CreatedAt { get; set; } = DateTime.UtcNow; + public DateTime UpdatedAt { get; set; } = DateTime.UtcNow; +} + +/// One row per RSVP'd player + event. +public class PlayerEventResponseEntity +{ + public long Id { get; set; } + public long EventId { get; set; } + public long PlayerId { get; set; } + /// 0=Going, 1=Maybe, 2=NotGoing — matches the watch's + /// three-state RSVP toggle. + public int Response { get; set; } + public DateTime CreatedAt { get; set; } = DateTime.UtcNow; +} diff --git a/DorkNet.Server/Data/Entities/PlayerInventoryEntity.cs b/DorkNet.Server/Data/Entities/PlayerInventoryEntity.cs new file mode 100644 index 0000000..8ccf06d --- /dev/null +++ b/DorkNet.Server/Data/Entities/PlayerInventoryEntity.cs @@ -0,0 +1,33 @@ +using System.ComponentModel.DataAnnotations; + +namespace DorkNet.Server.Data.Entities; + +/// +/// One row per (player, item) pair. Backs both the +/// /api/equipment/v2/getUnlocked and +/// /api/consumables/v1/getUnlocked endpoints — the type comes +/// from the joined . +/// +/// For equipment, is always 1 and +/// tracks whether it's equipped. For +/// consumables, is the stack size and +/// tracks whether the player has it favourited / +/// quick-equipped on the watch. +/// +public class PlayerInventoryEntity +{ + public long Id { get; set; } + public long PlayerId { get; set; } + + /// FK by string to . + /// Stored as a string rather than an int FK because a player can + /// be granted items that aren't in the live store (admin-gifted, + /// legacy retired items, etc.). + [MaxLength(64)] + public string ItemSlug { get; set; } = string.Empty; + + public int Quantity { get; set; } = 1; + public bool IsActive { get; set; } = false; + + public DateTime AcquiredAt { get; set; } = DateTime.UtcNow; +} diff --git a/DorkNet.Server/Data/Entities/PlayerSettingEntity.cs b/DorkNet.Server/Data/Entities/PlayerSettingEntity.cs new file mode 100644 index 0000000..705ae29 --- /dev/null +++ b/DorkNet.Server/Data/Entities/PlayerSettingEntity.cs @@ -0,0 +1,17 @@ +using System.ComponentModel.DataAnnotations; + +namespace DorkNet.Server.Data.Entities; + +public class PlayerSettingEntity +{ + public long Id { get; set; } + public long PlayerId { get; set; } + + [MaxLength(128)] + public string Key { get; set; } = string.Empty; + + [MaxLength(1024)] + public string Value { get; set; } = string.Empty; + + public PlayerEntity? Player { get; set; } +} diff --git a/DorkNet.Server/Data/Entities/PlayerSystemsEntities.cs b/DorkNet.Server/Data/Entities/PlayerSystemsEntities.cs new file mode 100644 index 0000000..ee5b3b0 --- /dev/null +++ b/DorkNet.Server/Data/Entities/PlayerSystemsEntities.cs @@ -0,0 +1,66 @@ +using System.ComponentModel.DataAnnotations; + +namespace DorkNet.Server.Data.Entities; + +/// +/// One per (player, deviceId) — recorded on +/// POST /api/deviceid/v1/register. Lets admins flag +/// duplicate-account abuse: when a banned player creates a new +/// account on the same device, the new account inherits the device +/// ban automatically. +/// +public class PlayerDeviceEntity +{ + public long Id { get; set; } + public long PlayerId { get; set; } + + [MaxLength(128)] + public string DeviceId { get; set; } = string.Empty; + + [MaxLength(32)] + public string Platform { get; set; } = string.Empty; + + public DateTime FirstSeen { get; set; } = DateTime.UtcNow; + public DateTime LastSeen { get; set; } = DateTime.UtcNow; + + /// Set by admin tooling — if true, every account using + /// this device is auto-banned at login. + public bool IsBanned { get; set; } = false; +} + +/// Per-player notification preferences. iOS-style toggle +/// page; Android / Quest just persist the same shape. +public class NotificationPrefsEntity +{ + public long Id { get; set; } + public long PlayerId { get; set; } + + [MaxLength(32)] + public string Platform { get; set; } = string.Empty; + + public bool AllowAll { get; set; } = true; + public bool AllowFriendRequest { get; set; } = true; + public bool AllowMessage { get; set; } = true; + public bool AllowEventInvite { get; set; } = true; + public bool AllowAnnouncements { get; set; } = true; + + public DateTime UpdatedAt { get; set; } = DateTime.UtcNow; +} + +/// Sticky cohort assignment — one row per (player, cohort +/// key). Created on first hit to GET /api/cohort/v1/{key} with +/// a randomly selected variant; remains stable for the lifetime of +/// the player so A/B tests don't flicker between sessions. +public class CohortAssignmentEntity +{ + public long Id { get; set; } + public long PlayerId { get; set; } + + [MaxLength(64)] + public string CohortKey { get; set; } = string.Empty; + + [MaxLength(32)] + public string Variant { get; set; } = string.Empty; + + public DateTime AssignedAt { get; set; } = DateTime.UtcNow; +} diff --git a/DorkNet.Server/Data/Entities/PlaylistEntity.cs b/DorkNet.Server/Data/Entities/PlaylistEntity.cs new file mode 100644 index 0000000..fdd42d5 --- /dev/null +++ b/DorkNet.Server/Data/Entities/PlaylistEntity.cs @@ -0,0 +1,61 @@ +using System.ComponentModel.DataAnnotations; + +namespace DorkNet.Server.Data.Entities; + +/// +/// A curated or user-created list of rooms surfaced by the watch's +/// /roomserver/roomsandplaylists/* endpoints. The 2020.12 watch +/// deserializes union responses where each entry is either a Room +/// (KLCOGEIGEBJ) or a Playlist (KMKPEOGJDFK) — the discriminator is +/// the presence of the lowercase "roomId" key (factory at +/// MKAMHOIHOJK.txt:621). +/// +/// Required wire fields for the playlist subclass (KMKPEOGJDFK.txt:68 +/// reads PlaylistId; base keys at MKAMHOIHOJK.txt:516-612 cover +/// Name/Description/ImageName/Stats/etc.). +/// +public class PlaylistEntity +{ + public long Id { get; set; } + + [MaxLength(128)] + public string Name { get; set; } = string.Empty; + + [MaxLength(2000)] + public string Description { get; set; } = string.Empty; + + [MaxLength(256)] + public string ImageName { get; set; } = string.Empty; + + /// Player who built / curates the list. Curated/system + /// playlists are owned by the seed account (id=1). + public long CreatorPlayerId { get; set; } = 1; + + /// Surfaces in /api/curatedroomplaylists. Curated lists + /// are sorted by and always come from the + /// seed account. + public bool IsCurated { get; set; } = false; + + /// Sort key for curated lists — lower comes first. + /// Ignored for user-created lists (which sort by HotScore proxy). + public int OrderIndex { get; set; } = 0; + + /// Comma-separated tags. Matches the same convention used + /// by — the hot endpoint applies + /// a substring filter against this column. + [MaxLength(1024)] + public string TagsCsv { get; set; } = string.Empty; + + public int CheerCount { get; set; } = 0; + public int FavoriteCount { get; set; } = 0; + + /// Unique visitors across the rooms this playlist points + /// at — bumps when a player visits any member room. + public int VisitorCount { get; set; } = 0; + + /// Total joins across all member rooms. + public int VisitCount { get; set; } = 0; + + public DateTime CreatedAt { get; set; } = DateTime.UtcNow; + public DateTime UpdatedAt { get; set; } = DateTime.UtcNow; +} diff --git a/DorkNet.Server/Data/Entities/PlaylistInteractionEntity.cs b/DorkNet.Server/Data/Entities/PlaylistInteractionEntity.cs new file mode 100644 index 0000000..cb11043 --- /dev/null +++ b/DorkNet.Server/Data/Entities/PlaylistInteractionEntity.cs @@ -0,0 +1,24 @@ +namespace DorkNet.Server.Data.Entities; + +/// +/// One row per (PlaylistId, PlayerId) tracking the player's cheer +/// + favorite flags on a playlist. Drives /playlists/{id}/interactionby/me +/// (read), the four interactionby/me mutation endpoints (cheer / +/// uncheer / favorite / unfavorite toggles), and the +/// playlists/cheeredby/me + playlists/favoritedby/me +/// lists. +/// +/// Counter columns on (CheerCount / +/// FavoriteCount) are kept in sync by +/// on each toggle so the hot ranking + tile badges read from the +/// row without an aggregate join. +/// +public class PlaylistInteractionEntity +{ + public long Id { get; set; } + public long PlaylistId { get; set; } + public long PlayerId { get; set; } + public bool Cheered { get; set; } = false; + public bool Favorited { get; set; } = false; + public DateTime UpdatedAt { get; set; } = DateTime.UtcNow; +} diff --git a/DorkNet.Server/Data/Entities/PlaylistRoomEntity.cs b/DorkNet.Server/Data/Entities/PlaylistRoomEntity.cs new file mode 100644 index 0000000..f580b17 --- /dev/null +++ b/DorkNet.Server/Data/Entities/PlaylistRoomEntity.cs @@ -0,0 +1,14 @@ +namespace DorkNet.Server.Data.Entities; + +/// +/// Links a to one of its member rooms. The +/// playlist's room ordering is driven by so a +/// curator can promote/demote entries without renumbering rows. +/// +public class PlaylistRoomEntity +{ + public long Id { get; set; } + public long PlaylistId { get; set; } + public long RoomId { get; set; } + public int OrderIndex { get; set; } = 0; +} diff --git a/DorkNet.Server/Data/Entities/PrivateInstanceEntity.cs b/DorkNet.Server/Data/Entities/PrivateInstanceEntity.cs new file mode 100644 index 0000000..9e3c3b5 --- /dev/null +++ b/DorkNet.Server/Data/Entities/PrivateInstanceEntity.cs @@ -0,0 +1,42 @@ +using System.ComponentModel.DataAnnotations; + +namespace DorkNet.Server.Data.Entities; + +/// +/// Durable record of a private (invite-only) Photon match created via +/// POST /goto/room/{name}?CreatePrivateInstance=True. Replaces +/// the in-process ConcurrentDictionary in +/// so +/// invitations land on any replica. +/// +/// Without this row in shared storage, an invite issued by Player A +/// (replica 1) wouldn't be visible to Player B's POST /goto/instance/{id} +/// when their request lands on replica 2 — they'd be routed to a fresh +/// public lobby instead of the inviter's match. +/// +/// The associated invite list lives in ; +/// we keep it as a join table rather than a JSON column so the per-row +/// "is X invited to Y?" check stays a fast indexed lookup. +/// +public class PrivateInstanceEntity +{ + /// The instance id used as the wire roomInstanceId + /// in matchmaking responses. Generated server-side at creation + /// time; stable for the lifetime of the match. NOT autoincrement — + /// the value is computed from owner/room/sub plus a nonce so the + /// id is itself unforgeable without the nonce. + public long Id { get; set; } + + public long RoomId { get; set; } + public long SubRoomId { get; set; } + public long OwnerPlayerId { get; set; } + + [MaxLength(256)] public string PhotonRoomId { get; set; } = string.Empty; + [MaxLength(64)] public string Location { get; set; } = string.Empty; + [MaxLength(256)] public string DataBlob { get; set; } = string.Empty; + [MaxLength(16)] public string PhotonRegion { get; set; } = "us"; + public int MaxCapacity { get; set; } = 8; + [MaxLength(128)] public string Name { get; set; } = string.Empty; + + public DateTime CreatedAt { get; set; } = DateTime.UtcNow; +} diff --git a/DorkNet.Server/Data/Entities/PrivateInstanceInviteeEntity.cs b/DorkNet.Server/Data/Entities/PrivateInstanceInviteeEntity.cs new file mode 100644 index 0000000..bd3e2c5 --- /dev/null +++ b/DorkNet.Server/Data/Entities/PrivateInstanceInviteeEntity.cs @@ -0,0 +1,29 @@ +namespace DorkNet.Server.Data.Entities; + +/// +/// Join row marking that is on the invite list +/// for . Created when the owner sends an +/// invite via POST /invite (handled in MessagesController); +/// checked on POST /goto/instance/{id} by +/// PrivateInstanceService.CanJoin. +/// +/// Composite PK on (PrivateInstanceId, PlayerId) — naturally unique; +/// repeat-invite calls become idempotent no-ops. +/// +public class PrivateInstanceInviteeEntity +{ + public long PrivateInstanceId { get; set; } + public long PlayerId { get; set; } + public DateTime InvitedAt { get; set; } = DateTime.UtcNow; + + /// The MessageEntity.Id of the latest invite for this + /// (instance, player). Lets GoToInvite resolve the + /// roomInstanceId even when the watch's accept flow has already + /// raced through POST /api/messages/v3/delete and removed + /// the underlying message row. Without this column the server + /// returned ErrorCode 40 ("invite expired") for any successful + /// accept where DELETE landed first — which was the common case + /// because the 2020 watch fires DELETE and POST /goto/invite/{id} + /// in parallel. + public long? LatestInviteMessageId { get; set; } +} diff --git a/DorkNet.Server/Data/Entities/PushTokenEntity.cs b/DorkNet.Server/Data/Entities/PushTokenEntity.cs new file mode 100644 index 0000000..2c2d3f0 --- /dev/null +++ b/DorkNet.Server/Data/Entities/PushTokenEntity.cs @@ -0,0 +1,86 @@ +using System.ComponentModel.DataAnnotations; + +namespace DorkNet.Server.Data.Entities; + +/// +/// One push-notification token per (player, platform). Backs +/// POST api/messages/v1/IOSSaveDeviceToken + +/// IOSClearDeviceToken. Stored so an APNS / FCM proxy could +/// fan out push notifications to mobile clients; on a private +/// server we just persist for audit + future use. +/// +public class PushTokenEntity +{ + public long Id { get; set; } + public long PlayerId { get; set; } + + /// Platform: "ios", "android", "fcm", etc. + [MaxLength(16)] + public string Platform { get; set; } = "ios"; + + [MaxLength(256)] + public string Token { get; set; } = string.Empty; + + public DateTime CreatedAt { get; set; } = DateTime.UtcNow; + public DateTime UpdatedAt { get; set; } = DateTime.UtcNow; +} + +/// +/// One row per (player, platform user id) that the player has +/// chosen to ignore even before they appear in +/// . Backs +/// POST api/relationships/v1/bulkignoreplatformusers — when +/// a Steam friend joins later, the relationship is auto-set to +/// Ignored. Lets the import stick across friend-discovery cycles. +/// +public class PlatformIgnoreEntity +{ + public long Id { get; set; } + public long PlayerId { get; set; } + + /// PlatformType enum: 0=Steam, 1=Oculus, 2=PlayStation, + /// 3=Microsoft, 5=IOS. + public int Platform { get; set; } + + [MaxLength(64)] + public string PlatformUserId { get; set; } = string.Empty; + + public DateTime CreatedAt { get; set; } = DateTime.UtcNow; +} + +/// +/// One row per "card" displayed on the watch's home screen / cards +/// tab. Backs GET api/cards/v1/all. Cards are short tappable +/// tiles ("Daily Login", "New Room: Foo", "Friend invited you to +/// X") seeded by the server and consumed by the client. +/// +public class CardEntity +{ + public long Id { get; set; } + + /// If null, the card is global (shown to everyone). + /// If set, only visible to that specific player. + public long? PlayerId { get; set; } + + [MaxLength(128)] + public string Title { get; set; } = string.Empty; + + [MaxLength(2000)] + public string Body { get; set; } = string.Empty; + + [MaxLength(256)] + public string ImageName { get; set; } = string.Empty; + + [MaxLength(512)] + public string ActionUrl { get; set; } = string.Empty; + + /// Card category — "announcement", "daily", "event", + /// "system". Drives the icon the watch renders. + [MaxLength(32)] + public string Category { get; set; } = "announcement"; + + public int Priority { get; set; } = 0; + + public DateTime CreatedAt { get; set; } = DateTime.UtcNow; + public DateTime? ExpiresAt { get; set; } +} diff --git a/DorkNet.Server/Data/Entities/RelationshipEntity.cs b/DorkNet.Server/Data/Entities/RelationshipEntity.cs new file mode 100644 index 0000000..1e06a69 --- /dev/null +++ b/DorkNet.Server/Data/Entities/RelationshipEntity.cs @@ -0,0 +1,58 @@ +namespace DorkNet.Server.Data.Entities; + +/// +/// Storage-side enum mirroring the int values previously hard-coded in +/// . Stays separate from the on-the-wire +/// DorkNet.Models.Relationships.RelationshipStatus so the contract +/// can evolve without touching the DB representation. Underlying int +/// values match — no migration data fixup needed. +/// +public enum RelationshipStatus +{ + /// Mutual friend (both directions accepted). + Friend = 1, + + /// Outgoing request from + /// awaiting accept/decline by . + PendingSent = 2, + + /// + /// Reserved for the receiving-side view if we later denormalise. The + /// backing column is still 1-2-4 in practice; PendingReceived is what + /// the wire DTO computes from the caller's perspective when the row's + /// PendingSent direction is "into" them. + /// + PendingReceived = 3, + + /// Block held by + /// against . Hides the target + /// from the requester's friend list and prevents new requests both ways. + Blocked = 4, +} + +public class RelationshipEntity +{ + public long Id { get; set; } + public long RequesterId { get; set; } + public long TargetId { get; set; } + public RelationshipStatus Status { get; set; } + public DateTime CreatedAt { get; set; } = DateTime.UtcNow; + public DateTime UpdatedAt { get; set; } = DateTime.UtcNow; + + /// True when favorited + /// . Favorited shows up first in the friend + /// list and triggers an extra notification on "online". + public bool Favorited { get; set; } + + /// True when muted text from + /// . Drops chat-message notifications but + /// keeps the friendship intact. + public bool Muted { get; set; } + + /// True when ignored + /// (a soft block — hides the player from + /// the room browser without breaking the relationship row). + public bool Ignored { get; set; } + + public PlayerEntity? Requester { get; set; } +} diff --git a/DorkNet.Server/Data/Entities/ReportEntity.cs b/DorkNet.Server/Data/Entities/ReportEntity.cs new file mode 100644 index 0000000..d8c85ed --- /dev/null +++ b/DorkNet.Server/Data/Entities/ReportEntity.cs @@ -0,0 +1,62 @@ +using System.ComponentModel.DataAnnotations; + +namespace DorkNet.Server.Data.Entities; + +/// +/// One in-game player report. Created by any authenticated player via +/// the watch's "report player" UI; surfaces in the admin moderation +/// queue at GET api/admin/v1/reports until an admin resolves it. +/// +public class ReportEntity +{ + public long Id { get; set; } + + /// Account id of the player filing the report. + public long ReporterPlayerId { get; set; } + + /// Account id of the player being reported. + public long TargetPlayerId { get; set; } + + /// Free-form category the watch sends — convention is + /// numeric matching the client's RoomieReportCategory enum + /// (Harassment=1, Inappropriate=2, Cheating=3, Spam=4, Other=5). + public int Category { get; set; } + + /// The reporter's free-text description. Capped at 1000 + /// chars by the watch UI. + [MaxLength(1000)] + public string Message { get; set; } = string.Empty; + + /// Optional game session id for context — admins can + /// correlate to room/server logs around the time of the report. + public long GameSessionId { get; set; } + + /// Room the offending behaviour happened in (0 if N/A). + public long RoomId { get; set; } + + /// If the report targets an invention, the invention id + /// (0 otherwise). + public long TargetInventionId { get; set; } + + /// If the report targets a room (rather than the player + /// in it), the room id (0 otherwise). + public long TargetRoomId { get; set; } + + /// If the report targets a player event, the event id + /// (0 otherwise). + public long TargetEventId { get; set; } + + public DateTime CreatedAt { get; set; } = DateTime.UtcNow; + + /// Null while the report sits in the admin queue; set + /// when an admin resolves it via + /// POST api/admin/v1/reports/{id}/resolve. Together with + /// + + /// forms the full audit trail. + public DateTime? ResolvedAt { get; set; } + + public long? ResolverAdminId { get; set; } + + [MaxLength(512)] + public string ResolutionNote { get; set; } = string.Empty; +} diff --git a/DorkNet.Server/Data/Entities/RoomBanEntity.cs b/DorkNet.Server/Data/Entities/RoomBanEntity.cs new file mode 100644 index 0000000..6fb0c31 --- /dev/null +++ b/DorkNet.Server/Data/Entities/RoomBanEntity.cs @@ -0,0 +1,28 @@ +using System.ComponentModel.DataAnnotations; + +namespace DorkNet.Server.Data.Entities; + +/// +/// One per-room ban issued by a room owner / admin via +/// POST /api/rooms/v2/banfromroom. The match service consults +/// these rows when resolving an instance join: if the joining player +/// has an active row matching the room id, the join is refused. +/// +public class RoomBanEntity +{ + public long Id { get; set; } + public long RoomId { get; set; } + public long BannedPlayerId { get; set; } + public long BannedByPlayerId { get; set; } + + /// BanType enum mirror: 0 = Soft (kick), 1 = Permanent, + /// 2 = Temporary (until ). + public int BanType { get; set; } + + public DateTime? Until { get; set; } + + [MaxLength(512)] + public string Reason { get; set; } = string.Empty; + + public DateTime CreatedAt { get; set; } = DateTime.UtcNow; +} diff --git a/DorkNet.Server/Data/Entities/RoomBookmarkEntity.cs b/DorkNet.Server/Data/Entities/RoomBookmarkEntity.cs new file mode 100644 index 0000000..47ece49 --- /dev/null +++ b/DorkNet.Server/Data/Entities/RoomBookmarkEntity.cs @@ -0,0 +1,13 @@ +namespace DorkNet.Server.Data.Entities; + +/// +/// A "favorite" / bookmark — when a player taps the heart on a room in the +/// watch. Backs `api/rooms/v2/mybookmarks`. +/// +public class RoomBookmarkEntity +{ + public long Id { get; set; } + public long PlayerId { get; set; } + public long RoomId { get; set; } + public DateTime CreatedAt { get; set; } = DateTime.UtcNow; +} diff --git a/DorkNet.Server/Data/Entities/RoomDataBlobEntity.cs b/DorkNet.Server/Data/Entities/RoomDataBlobEntity.cs new file mode 100644 index 0000000..9861edd --- /dev/null +++ b/DorkNet.Server/Data/Entities/RoomDataBlobEntity.cs @@ -0,0 +1,68 @@ +using System.ComponentModel.DataAnnotations; + +namespace DorkNet.Server.Data.Entities; + +/// +/// Metadata for a single saved version of a room's PersistedRoomData +/// protobuf. The actual bytes live in S3 at the canonical +/// (bucket, key) returned by ; +/// this row only carries the lookup string + provenance. +/// +/// One row per save. Visitors fetching the room get the row whose +/// matches the current +/// RoomEntity.CurrentDataBlobName; the CDN controller takes +/// that name, routes it via , and +/// streams bytes out of S3. +/// +/// Was originally a fat row with a Bytes column; that column +/// was dropped once S3 became the canonical store. Only text columns +/// remain in the DB. +/// +public class RoomDataBlobEntity +{ + public long Id { get; set; } + + /// FK to RoomEntity. Indexed for "give me all versions of + /// this room" queries (history, GC). Set to 0 for shared assets + /// not bound to a specific room (HTRs, PV images, polaroids). + public long RoomId { get; set; } + + /// The string the client uses as the cdn URL segment. + /// converts this into a + /// canonical (bucket, key) deterministically. + [MaxLength(128)] + public string BlobName { get; set; } = string.Empty; + + /// The player id of the uploader. Audit trail; also used by + /// the GC to keep the room owner's saves longer than visitor saves. + public long UploadedByPlayerId { get; set; } + + public DateTime UploadedAt { get; set; } = DateTime.UtcNow; + + /// For per-subroom history blobs (the `Restore to old version` + /// UI is per-subroom), the SubRoomId this snapshot belongs to. Null for + /// canonical scene blobs and shared assets (HTRs, PV images, polaroids). + /// Set by the zip-importer when ingesting SubRooms/.../History/ + /// folders, and by the live save flow if you ever wire that path up to + /// stamp it. + public long? SubRoomId { get; set; } + + /// Comma-separated list of file names referenced by the + /// PersistedRoomData (e.g. invention thumbnails, audio clips). The + /// client passes these via the upload's referencedFilenames argument; + /// the server uses them later when GC'ing orphaned blobs. + [MaxLength(2048)] + public string ReferencedFilenamesCsv { get; set; } = string.Empty; + + /// + /// Legacy bytes column. The codebase no longer writes to this — S3 + /// is the canonical store — but rows imported / uploaded before the + /// S3-only cutover still hold their bytes here, and the backfill + /// migrator (POST /api/admin/v1/storage/backfill) reads them + /// to upload into S3. After backfill is verified, a follow-up EF + /// migration drops this column entirely and only metadata remains. + /// Nullable so freshly-inserted rows can omit it without breaking + /// SQLite's NOT NULL invariant. + /// + public byte[]? Bytes { get; set; } +} diff --git a/DorkNet.Server/Data/Entities/RoomEntity.cs b/DorkNet.Server/Data/Entities/RoomEntity.cs new file mode 100644 index 0000000..b6dc876 --- /dev/null +++ b/DorkNet.Server/Data/Entities/RoomEntity.cs @@ -0,0 +1,117 @@ +using System.ComponentModel.DataAnnotations; + +namespace DorkNet.Server.Data.Entities; + +/// +/// A user-creatable Rec Room "room" — has a name, a creator, a hub scene +/// (the AGRoomRuntimeConfig.Locations entry it spawns into), and metadata +/// for the watch UI's room browser. +/// +/// Naming maps: in the wire JSON the client expects mostly PascalCase +/// (Room.Deserialize at RVA 0x114E430). Property names match. +/// +/// Two-way relationship with PlayerEntity for ownership / bookmark / role +/// browsing — kept on the room side as ID columns rather than a navigation +/// to keep PlayerEntity unchanged. +/// +public class RoomEntity +{ + public long Id { get; set; } + + [MaxLength(128)] + public string Name { get; set; } = string.Empty; + + [MaxLength(2000)] + public string Description { get; set; } = string.Empty; + + /// The player who created the room (NOT the dorm owner — every + /// real Rec Room has a creator player id; for seeded "RecRoomOriginal" + /// rooms we set this to 1 / the system account). + public long CreatorPlayerId { get; set; } + + [MaxLength(256)] + public string ImageName { get; set; } = string.Empty; + + /// RoomState enum (int): 0 = Active, 1 = Archived. + public int State { get; set; } = 0; + + /// RoomAccessibility enum (int): 0 = Private, 1 = Public, + /// 2 = LimitedAccessibility / FriendsOnly. + public int Accessibility { get; set; } = 1; + + public bool SupportsLevelVoting { get; set; } = false; + + /// True for AG (user-built) rooms, false for "Rec Room + /// Original" rooms. The watch tabs `#community` (AG) and + /// `#recroomoriginal` (RR Originals) filter on this. + public bool IsAGRoom { get; set; } = true; + + public bool IsDormRoom { get; set; } = false; + public bool CloningAllowed { get; set; } = false; + public bool SupportsVRLow { get; set; } = true; + public bool SupportsMobile { get; set; } = false; + public bool SupportsScreens { get; set; } = true; + public bool SupportsWalkVR { get; set; } = true; + public bool SupportsTeleportVR { get; set; } = true; + public bool AllowsJuniors { get; set; } = true; + public int RoomWarningMask { get; set; } = 0; + + [MaxLength(512)] + public string CustomRoomWarning { get; set; } = string.Empty; + + public bool DisableMicAutoMute { get; set; } = false; + + /// RoomScene.RoomSceneLocationId GUID — foreign-keys to a + /// Locations[] entry in resources.assets. This is what + /// AGRoomSettings.TryGetRoomSceneLocationById matches. + /// Default: DormRoom location GUID extracted from the asset. + [MaxLength(64)] + public string LocationReplicationId { get; set; } = "76d98498-60a1-430c-ab76-b54a29b7a163"; + + /// Comma-separated tags. The 2020 watch supports a small set + /// (#community, #recroomoriginal, #featured, etc.) — keeping it as a + /// flat string avoids a join table for the v0 watch implementation. + [MaxLength(1024)] + public string TagsCsv { get; set; } = string.Empty; + + public int CheerCount { get; set; } = 0; + public int FavoriteCount { get; set; } = 0; + + /// Total joins across all players (every /goto + /// counts; same player rejoining bumps it again). Mirrors + /// official Rec.Net Stats.VisitCount. + public int VisitCount { get; set; } = 0; + + /// Distinct players who've ever visited (each player + /// counts once no matter how many times they rejoin). Mirrors + /// official Rec.Net Stats.VisitorCount. Bumped only when + /// inserts a new row for the + /// (room, player) pair. + public int VisitorCount { get; set; } = 0; + + /// Score the watch uses to sort the "Hot" tab. Higher = more + /// visible. Seeded rooms get bumped above empty user rooms so they show + /// up first in the browser. + public double HotScore { get; set; } = 0; + + public DateTime CreatedAt { get; set; } = DateTime.UtcNow; + public DateTime UpdatedAt { get; set; } = DateTime.UtcNow; + + /// True keeps the room out of room browse / search / + /// "RR Originals" feeds while leaving /goto, clone, and + /// admin-tool access intact. Used for rooms that exist in the seed + /// (MakerRoom, EventRoom, paintball sub-maps merged into the + /// Paintball lobby, etc.) but shouldn't show up to players because + /// either there's a unified room hosting their scenes elsewhere or + /// they're admin-only utility rooms. + public bool HiddenFromBrowse { get; set; } = false; + + /// Name of the latest row + /// for this room — also what we put in + /// RoomDetails.Scenes[0].DataBlobName so the client knows which + /// blob to download. Empty for AG-Original baked rooms (rec center, + /// paintball, etc.) which have no per-room save bytes; the dorm and + /// user-built / cloned rooms point at their latest save. + [MaxLength(128)] + public string CurrentDataBlobName { get; set; } = string.Empty; +} diff --git a/DorkNet.Server/Data/Entities/RoomKeyEntity.cs b/DorkNet.Server/Data/Entities/RoomKeyEntity.cs new file mode 100644 index 0000000..165cea6 --- /dev/null +++ b/DorkNet.Server/Data/Entities/RoomKeyEntity.cs @@ -0,0 +1,31 @@ +using System.ComponentModel.DataAnnotations; + +namespace DorkNet.Server.Data.Entities; + +/// +/// Paid room access key authored by a room owner/co-owner. The watch +/// displays these in the room-key editor and purchase flow. +/// +public class RoomKeyEntity +{ + public long Id { get; set; } + [MaxLength(64)] public string ReplicationId { get; set; } = Guid.NewGuid().ToString("D"); + public long RoomId { get; set; } + public long CreatorPlayerId { get; set; } + [MaxLength(40)] public string Name { get; set; } = string.Empty; + [MaxLength(174)] public string Description { get; set; } = string.Empty; + public int Price { get; set; } + public bool IsDeleted { get; set; } + public DateTime CreatedAt { get; set; } = DateTime.UtcNow; + public DateTime UpdatedAt { get; set; } = DateTime.UtcNow; +} + +/// One purchased room key per player. +public class RoomKeyPurchaseEntity +{ + public long Id { get; set; } + public long RoomKeyId { get; set; } + public long PlayerId { get; set; } + public int PaidPrice { get; set; } + public DateTime PurchasedAt { get; set; } = DateTime.UtcNow; +} diff --git a/DorkNet.Server/Data/Entities/RoomRoleEntity.cs b/DorkNet.Server/Data/Entities/RoomRoleEntity.cs new file mode 100644 index 0000000..0530f01 --- /dev/null +++ b/DorkNet.Server/Data/Entities/RoomRoleEntity.cs @@ -0,0 +1,25 @@ +namespace DorkNet.Server.Data.Entities; + +/// +/// Per-room player role grant. Drives RoomDetails.CoOwners / +/// Moderators / Hosts arrays (and their Invited* counterparts when +/// is false). Each (RoomId, PlayerId, Role) +/// tuple is unique — granting the same role twice is a no-op. +/// +public class RoomRoleEntity +{ + public long Id { get; set; } + public long RoomId { get; set; } + public long PlayerId { get; set; } + + /// RoleKind: 0 = CoOwner, 1 = Moderator, 2 = Host. + public int Role { get; set; } + + /// False while the invite is still pending. Accepted + /// rows show up in CoOwners/Moderators/Hosts; pending rows show + /// up in InvitedCoOwners/InvitedModerators/InvitedHosts. + public bool Accepted { get; set; } = true; + + public long? GrantedByPlayerId { get; set; } + public System.DateTime GrantedAt { get; set; } = System.DateTime.UtcNow; +} diff --git a/DorkNet.Server/Data/Entities/RoomSceneEntity.cs b/DorkNet.Server/Data/Entities/RoomSceneEntity.cs new file mode 100644 index 0000000..e58cb6f --- /dev/null +++ b/DorkNet.Server/Data/Entities/RoomSceneEntity.cs @@ -0,0 +1,60 @@ +using System.ComponentModel.DataAnnotations; + +namespace DorkNet.Server.Data.Entities; + +/// +/// One scene within a multi-scene custom room. Most rooms historically +/// had only Scenes[0] (synthesised in BuildRoomDetails from +/// + +/// ); rooms imported from a +/// multi-scene archive write one row per chapter here, and the wire-shape +/// builder emits the full Scenes[] array from this table when any +/// rows exist. +/// +/// The scene's on the wire is this table's +/// — the matchmaking SubRoomId the watch +/// passes to /goto/room/{name}/{subroom} matches against +/// , then resolves to for +/// RoomInstance.SubRoomId. +/// +public class RoomSceneEntity +{ + public long Id { get; set; } + + /// FK to . Indexed so the per-room + /// fetch in BuildRoomDetails is one query. + public long RoomId { get; set; } + + /// 0-based index this scene occupies in the wire + /// Scenes[] array. Doubles as the RoomSceneId the + /// matchmaking flow uses for RoomInstance.SubRoomId. + public int OrderIndex { get; set; } + + /// Display name (e.g. "Lobby", "Ch1_Headfirst"). Matched + /// case-insensitively against the sub-room segment of + /// /goto/room/{name}/{subroom}. + [MaxLength(128)] + public string Name { get; set; } = string.Empty; + + /// The RoomSceneLocationId GUID the client uses to + /// look up the bundled stage prefab via + /// AGRoomSettings.TryGetRoomSceneLocationById. Defaults to + /// the MakerRoom (Basement) scene GUID — the canonical "blank + /// canvas" stage 2020 custom rooms were built on. Extracted from + /// resources.assets via tools/extract-locations-binary.py: the + /// "Maker Room" Location entry has SceneName=Basement and + /// ReplicationId=a75f7547-79eb-47c6-8986-6767abcb4f92. + [MaxLength(64)] + public string RoomSceneLocationId { get; set; } = "a75f7547-79eb-47c6-8986-6767abcb4f92"; + + /// The the watch + /// downloads as the persisted state for this scene. + [MaxLength(128)] + public string DataBlobName { get; set; } = string.Empty; + + public int MaxPlayers { get; set; } = 8; + public bool IsSandbox { get; set; } = false; + public bool CanMatchmakeInto { get; set; } = true; + + public DateTime DataModifiedAt { get; set; } = DateTime.UtcNow; +} diff --git a/DorkNet.Server/Data/Entities/RoomVisitEntity.cs b/DorkNet.Server/Data/Entities/RoomVisitEntity.cs new file mode 100644 index 0000000..f37b549 --- /dev/null +++ b/DorkNet.Server/Data/Entities/RoomVisitEntity.cs @@ -0,0 +1,31 @@ +namespace DorkNet.Server.Data.Entities; + +/// +/// One row per (Room, Player) recording the player's visit history +/// to that room. Backs the proper RoomEntity.VisitorCount +/// (count of unique players who've visited) vs VisitCount +/// (total joins, including the same player multiple times) split +/// the official Rec.Net stats column distinguishes. +/// +/// Upserted by GoToController on every POST /goto/room +/// so the per-room "who's been here, when" history is queryable for +/// the Hot-rooms ranking + admin diagnostics. +/// +public class RoomVisitEntity +{ + public long Id { get; set; } + public long RoomId { get; set; } + public long PlayerId { get; set; } + + /// Set once on the player's first visit; immutable after. + /// Used to decide whether to bump RoomEntity.VisitorCount. + public DateTime FirstVisitAt { get; set; } = DateTime.UtcNow; + + /// Updated on every visit. Drives "recent visitors" + /// queries. + public DateTime LastVisitAt { get; set; } = DateTime.UtcNow; + + /// Per-player visit count (this player visited this room + /// N times). Sum across all rows = RoomEntity.VisitCount. + public int VisitCount { get; set; } = 0; +} diff --git a/DorkNet.Server/Data/Entities/RoyaleEntities.cs b/DorkNet.Server/Data/Entities/RoyaleEntities.cs new file mode 100644 index 0000000..c178c5e --- /dev/null +++ b/DorkNet.Server/Data/Entities/RoyaleEntities.cs @@ -0,0 +1,66 @@ +using System.ComponentModel.DataAnnotations; + +namespace DorkNet.Server.Data.Entities; + +/// +/// One Rec Royale match. Inserted by the server when +/// POST api/royale/v2/matchcomplete arrives so we keep an +/// audit trail (the client doesn't read this table — it only reads +/// the aggregated ). +/// +public class RoyaleMatchEntity +{ + public long Id { get; set; } + + public DateTime CompletedAt { get; set; } = DateTime.UtcNow; + + /// From MatchCompleteStats.Rank — the player's + /// finish position (1 = Victory Royale). + public int Rank { get; set; } + + public int NumEliminations { get; set; } + public int SecondsAlive { get; set; } + public bool WalkGame { get; set; } + public bool CustomGame { get; set; } + public int ChestsOpened { get; set; } + public int ShieldPotionsConsumed { get; set; } + public int HealthPotionsConsumed { get; set; } + public int SecondsInAir { get; set; } +} + +/// One per (match, player) — server-side audit only; not +/// exposed to client. Lets us aggregate per-match contributions +/// retroactively. +public class RoyaleMatchPlayerEntity +{ + public long Id { get; set; } + public long MatchId { get; set; } + public long PlayerId { get; set; } + + public int Rank { get; set; } + public int NumEliminations { get; set; } + public int SecondsAlive { get; set; } +} + +/// One per player — mirrors wire type +/// RecRoyalePlayerProgress (TotalXP/Level/RankIdx/RankName + +/// per-level XP thresholds and acorn reward). The watch reads this +/// from GET api/royale/v1/current. +public class RoyalePlayerProgressEntity +{ + public long Id { get; set; } + public long PlayerId { get; set; } + + public long TotalXP { get; set; } + public int Level { get; set; } + public int RankIdx { get; set; } + + [MaxLength(64)] + public string RankName { get; set; } = "Recruit"; + + public long CurrentLevelXPThreshold { get; set; } + public long NextLevelXPThreshold { get; set; } = 100; + public int NextLevelAcornReward { get; set; } = 50; + + public DateTime UpdatedAt { get; set; } = DateTime.UtcNow; +} diff --git a/DorkNet.Server/Data/Entities/ServerSettingsEntity.cs b/DorkNet.Server/Data/Entities/ServerSettingsEntity.cs new file mode 100644 index 0000000..69ad4f7 --- /dev/null +++ b/DorkNet.Server/Data/Entities/ServerSettingsEntity.cs @@ -0,0 +1,42 @@ +namespace DorkNet.Server.Data.Entities; + +/// +/// Single-row table holding server-wide runtime toggles that admins can +/// flip from the SPA without a redeploy. Modelled after +/// : is pinned to 1 +/// so EF Core has a key to track changes against, and reads/writes +/// touch one row. +/// +/// Add new boolean (or scalar) columns directly to this entity as more +/// toggles appear — keeping them strongly typed beats a generic +/// key/value table for the dozen-or-so global switches we'll ever have. +/// +public class ServerSettingsEntity +{ + public int Id { get; set; } = 1; + + /// When true, the watch's account-creation endpoints + /// (POST /account/create and POST /api/account/v1/create) + /// short-circuit with Success=false and the watch surfaces + /// the error to the player. Existing logins are unaffected; only + /// brand-new account requests are blocked. + public bool SignupsDisabled { get; set; } + + /// When true, the challenge map tells the 2020 watch that + /// weekly challenge completion is required for the weekly reward flow. + /// Individual player challenge rows still default to incomplete until + /// the client reports progress. + public bool WeeklyChallengesCompletedRequired { get; set; } = true; + + /// Admin-managed weekly challenge templates as JSON. Challenge + /// ids are generated per weekly map so old progress rows stay scoped to + /// that week's map id. + public string WeeklyChallengesJson { get; set; } = string.Empty; + + /// Admin-managed weekly reward JSON. The progression API uses + /// this for both the watch's ChallengeGift payload and the once-per-week + /// server-side XP/token grant. + public string WeeklyChallengeRewardJson { get; set; } = string.Empty; + + public DateTime UpdatedAt { get; set; } = DateTime.UtcNow; +} diff --git a/DorkNet.Server/Data/Entities/StatsEntities.cs b/DorkNet.Server/Data/Entities/StatsEntities.cs new file mode 100644 index 0000000..050988f --- /dev/null +++ b/DorkNet.Server/Data/Entities/StatsEntities.cs @@ -0,0 +1,55 @@ +using System.ComponentModel.DataAnnotations; + +namespace DorkNet.Server.Data.Entities; + +/// +/// One leaderboard stat row per (player, statChannel). Backs +/// api/Leaderboard/v1/SetStats + api/Leaderboard/v2/getPlayerRank. +/// +/// The 2020 watch's RecNet.Leaderboards uses an int channel +/// id (e.g. 1=PaintballWins, 2=DodgeballHits, etc.) and a single +/// long value per channel. A player's "rank" is computed as +/// 1 + count(*) of rows in the same channel with a higher +/// value. +/// +public class LeaderboardStatEntity +{ + public long Id { get; set; } + public long PlayerId { get; set; } + + /// RecNet stat channel id (small int per game mode + + /// stat type — e.g. paintball-CTF-wins, dodgeball-hits). + public int StatChannel { get; set; } + + /// Aggregate stat value. Set semantics depend on + /// : 0=Always, 1=OnlyIfHigher, 2=OnlyIfLower, + /// 3=Increment. Most stat channels are mode 1 (high score). + public long Value { get; set; } + + public DateTime UpdatedAt { get; set; } = DateTime.UtcNow; +} + +/// +/// Match-result Elo per (player, game mode). Backs +/// api/PlayerElo/v1/reportPlayerElo. The 2020 watch reports +/// an Elo delta after each ranked match (Royale, Paintball, +/// Soccer); we accumulate it per game mode for matchmaking. +/// +public class PlayerEloEntity +{ + public long Id { get; set; } + public long PlayerId { get; set; } + + /// RecNet game mode id (small int). We don't enumerate + /// these; the wire just sends an int and we pass it through. + public int GameMode { get; set; } + + /// Current Elo. Default 1000 on first row. + public int Elo { get; set; } = 1000; + + public int Wins { get; set; } + public int Losses { get; set; } + public int MatchesPlayed { get; set; } + + public DateTime UpdatedAt { get; set; } = DateTime.UtcNow; +} diff --git a/DorkNet.Server/Data/Entities/StoreItemEntity.cs b/DorkNet.Server/Data/Entities/StoreItemEntity.cs new file mode 100644 index 0000000..7576ddb --- /dev/null +++ b/DorkNet.Server/Data/Entities/StoreItemEntity.cs @@ -0,0 +1,87 @@ +using System.ComponentModel.DataAnnotations; + +namespace DorkNet.Server.Data.Entities; + +/// +/// One purchasable item in the in-game store. The watch's Shop tab +/// renders these, and the purchase flow (initiatepurchase → +/// processpurchase → completepurchase) verifies the player has enough +/// of currency, deducts , +/// and appends to the buyer's +/// list so the customisation +/// menu can equip it. +/// +/// Item slots / categories track the avatar slots the watch's +/// outfit-selector groups items by — Head, Torso, Legs, Feet, +/// Accessory, Face, Hair, plus non-avatar categories (Consumable, +/// RoomTemplate, Emote) that don't go into outfit selections but +/// still show up in the player's inventory. +/// +public class StoreItemEntity +{ + public long Id { get; set; } + + /// Stable string identifier — what we put in + /// InventoryJson and OutfitSelections. Format is a GUID for + /// avatar items (so the watch's avatar resolver matches the + /// baked AvatarItemDefinition catalog), or a kebab-case slug for + /// non-avatar items (consumables / templates). Unique. + // Widened from 64→128 in 2026-05 to accommodate the + // wardrobe-colored-{guid}-{color} slugs the color-variant seeder + // generates — longest in the catalog is ~68 chars (long color + // names like "WhiteHatHacker" + 36-char GUID + prefix). + [MaxLength(128)] + public string Slug { get; set; } = string.Empty; + + [MaxLength(128)] + public string DisplayName { get; set; } = string.Empty; + + [MaxLength(1024)] + public string Description { get; set; } = string.Empty; + + /// Avatar slot or item type. One of: head, torso, legs, + /// feet, accessory, face, hair, consumable, roomtemplate, emote. + /// + [MaxLength(32)] + public string Category { get; set; } = "accessory"; + + /// cdn filename (relative to cdn.rec.net/) for the + /// store-tile thumbnail. Empty = the watch falls back to a + /// silhouette. Same format as RoomEntity.ImageName. + [MaxLength(256)] + public string ImageName { get; set; } = string.Empty; + + /// Currency the item is priced in. 2 = standard tokens + /// (the everyday currency earned from level-up + cheers); higher + /// numbers reserved for premium currencies (e.g. 1 = real-money + /// purchases — never enabled on a private server). + public int CurrencyType { get; set; } = 2; + + public long Price { get; set; } + + /// Listing flag. False = hidden from the store but kept + /// in the catalog so existing inventory references stay + /// resolvable. Admin-only mutation. + public bool IsActive { get; set; } = true; + + /// True for limited-time / event items. The watch + /// renders an "ends in" countdown alongside these and pulls them + /// from the catalog when AvailableUntil expires (we keep the row, + /// just flip IsActive=false on expiry). + public bool IsLimitedTime { get; set; } + + public DateTime? AvailableUntil { get; set; } + + /// Storefront grouping. "main" = the everyday + /// shop; "giftdrop:N" = a specific rotating gift-drop shelf + /// the 2020 client probes via /storefronts/v3/giftdropstore/{N}; + /// "rro" = every RRO/Rec Center in-room shelf; + /// "all" = every in-room shelf; "season:N" = + /// season-pass ladders. Default is main. + /// + [MaxLength(32)] + public string Storefront { get; set; } = "main"; + + public DateTime CreatedAt { get; set; } = DateTime.UtcNow; + public DateTime UpdatedAt { get; set; } = DateTime.UtcNow; +} diff --git a/DorkNet.Server/Data/Entities/SubscriptionEntity.cs b/DorkNet.Server/Data/Entities/SubscriptionEntity.cs new file mode 100644 index 0000000..f1235ad --- /dev/null +++ b/DorkNet.Server/Data/Entities/SubscriptionEntity.cs @@ -0,0 +1,15 @@ +namespace DorkNet.Server.Data.Entities; + +/// +/// Asymmetric "follow" relationship: +/// follows for activity updates. +/// Distinct from 's mutual friend +/// model — subscriptions are one-way and don't require acceptance. +/// +public class SubscriptionEntity +{ + public long Id { get; set; } + public long SubscriberPlayerId { get; set; } + public long TargetPlayerId { get; set; } + public DateTime CreatedAt { get; set; } = DateTime.UtcNow; +} diff --git a/DorkNet.Server/Data/Entities/TestCaseEntities.cs b/DorkNet.Server/Data/Entities/TestCaseEntities.cs new file mode 100644 index 0000000..28fe4ba --- /dev/null +++ b/DorkNet.Server/Data/Entities/TestCaseEntities.cs @@ -0,0 +1,96 @@ +using System.ComponentModel.DataAnnotations; + +namespace DorkNet.Server.Data.Entities; + +/// +/// QA test case — wire type RecNet.TestCase +/// (Cpp2IL_CS/.../RecNet/TestCase.cs). The 2020 Rec Room +/// client uses this to surface JIRA-tracked test cases in an +/// internal QA tab; users with the developer role claim a case, +/// run it in the named room, then submit a Pass/Fail status. +/// +/// Field names + types match the client's +/// TestCase.Deserialize exactly. is the +/// TestCaseStatus enum: 0=NotYetTested, 1=Claimed, 2=Failed, +/// 3=Passed. +/// +public class TestCaseEntity +{ + /// EF primary key (long autoincrement) — kept separate + /// from the wire string so we can index cleanly. + public long Pk { get; set; } + + /// Wire field Id (string) — typically the JIRA + /// issue key like RR-1234. + [MaxLength(64)] + public string Id { get; set; } = string.Empty; + + [MaxLength(64)] + public string Key { get; set; } = string.Empty; + + [MaxLength(256)] + public string Title { get; set; } = string.Empty; + + [MaxLength(2000)] + public string Description { get; set; } = string.Empty; + + [MaxLength(128)] + public string RoomName { get; set; } = string.Empty; + + /// TestCaseStatus enum: 0=NotYetTested, 1=Claimed, + /// 2=Failed, 3=Passed. + public int Status { get; set; } = 0; + + public int MinNumAssignedPlayers { get; set; } = 1; + + /// JSON list of player ids currently claimed on this + /// test case. Mirrors wire AssignedPlayerIds (List<int>). + public string AssignedPlayerIdsJson { get; set; } = "[]"; + + /// Sibling JSON list of names for display (kept in sync + /// with ). + public string AssignedPlayerNamesJson { get; set; } = "[]"; + + /// JSON list of tag strings. + public string TagsJson { get; set; } = "[]"; + + [MaxLength(512)] + public string JiraUrl { get; set; } = string.Empty; + + [MaxLength(512)] + public string JiraBugUrl { get; set; } = string.Empty; + + /// Optional FK to the this + /// case belongs to (null = orphan / standalone). + public uint? TestPassId { get; set; } + + public DateTime CreatedAt { get; set; } = DateTime.UtcNow; + public DateTime UpdatedAt { get; set; } = DateTime.UtcNow; +} + +/// Wire type RecNet.TestPass — a versioned bundle of +/// test cases the QA team groups for a release pass. Listed via +/// GET api/testcasemanagement/v1/testpasssummary; one fetched +/// in detail via GET api/testcasemanagement/v1/testpass/{id}. +/// +public class TestPassEntity +{ + /// Wire Id is uint — store as long PK and cast. + public uint Id { get; set; } + + [MaxLength(128)] + public string Name { get; set; } = string.Empty; + + [MaxLength(2000)] + public string Description { get; set; } = string.Empty; + + public DateTime StartDate { get; set; } = DateTime.UtcNow; + + /// Null while the pass is still active. + public DateTime? EndDate { get; set; } + + public bool WasManuallyClosed { get; set; } = false; + + /// JSON list of tag strings. + public string TagsJson { get; set; } = "[]"; +} diff --git a/DorkNet.Server/Data/LegacyUpgrades.cs b/DorkNet.Server/Data/LegacyUpgrades.cs new file mode 100644 index 0000000..370234e --- /dev/null +++ b/DorkNet.Server/Data/LegacyUpgrades.cs @@ -0,0 +1,168 @@ +using Microsoft.EntityFrameworkCore; + +namespace DorkNet.Server.Data; + +/// Idempotent startup pass that applies data transforms which +/// can't be derived from the entity model (i.e. don't belong in an EF +/// migration). Runs once on every server boot after +/// db.Database.MigrateAsync(); each upgrade is structured as +/// "read current state → apply if needed → log outcome", so re-running +/// on a fully-migrated DB is a no-op (just a string of "skipped, already +/// migrated" log lines). +/// +/// This file is the home for transforms that previously lived in +/// individual EF migrations (bucket C of the migration consolidation). +/// New entries go here when: +/// +/// A seed row's Name changes between releases (existing +/// deployments need a UPDATE; fresh installs already see the new +/// name in seed data). +/// Existing rows need a content fix-up that the entity model +/// can't express (e.g. backfilling a computed column). +/// +/// Every upgrade method MUST be: +/// +/// Idempotent — safe to call repeatedly +/// Self-checking — reads the current DB state first and only +/// modifies if the transform hasn't already been applied +/// Logged — emits one "applied" or "skipped, already migrated" +/// line so the boot log shows exactly which upgrades fired +/// +/// +public static class LegacyUpgrades +{ + public static async Task RunAsync(DorkNetDbContext db, IConfiguration config, ILogger logger, CancellationToken ct = default) + { + await RenameBloodMoonToCrescendoAsync(db, logger, ct); + await CoercePhotonRegionAsync(db, config, logger, ct); + // Future data transforms go here, one per release. Keep them + // narrowly scoped — anything that can be expressed as a + // schema change should be a migration instead. + } + + /// Coerce every + /// row to the apex's current Photon:CloudRegion. + /// + /// Background: older dorm rows still carry whatever region was + /// in config when their dorm was first registered. Without this + /// rewrite, an invitee whose /goto/invite resolves to a stale + /// row gets sent to the old region while the inviter's most recent + /// /goto/room/DormRoom puts them on the current region — two + /// parallel Photon rooms, players can't see each other. + /// PrivateInstanceService.EnsureForDormAsync now refreshes + /// PhotonRegion on each owner /goto so going forward this drift is + /// impossible; this is the one-shot cleanup for the pre-fix backlog. + /// + /// Originally ran as a RunPatchAsync UPDATE in the Postgres-only + /// schema-patch block in Program.cs; consolidated here so SQLite + /// dev DBs (which can drift the same way if you change Photon:CloudRegion + /// between boots) get the same fix-up. + /// + private static async Task CoercePhotonRegionAsync( + DorkNetDbContext db, IConfiguration config, ILogger logger, CancellationToken ct) + { + var region = (config["Photon:CloudRegion"] ?? "us").ToLowerInvariant(); + var updated = await db.PrivateInstances + .Where(p => p.PhotonRegion != region) + .ExecuteUpdateAsync(s => s.SetProperty(p => p.PhotonRegion, region), ct); + if (updated == 0) + { + logger.LogInformation("[legacy-upgrade] CoercePhotonRegion: skipped (all PrivateInstances already on {Region})", region); + } + else + { + logger.LogInformation("[legacy-upgrade] CoercePhotonRegion: applied — {Count} PrivateInstances coerced to {Region}", updated, region); + } + } + + /// The Crimson Cauldron / Blood Moon seed room was renamed + /// to Crescendo in May 2026 (and re-themed: new description, new + /// image, no longer hidden from browse). Old deployments still have + /// the BloodMoon row by primary key — rename it in place and clear + /// the hidden flag. Fresh installs ship with the Crescendo seed + /// directly and this upgrade no-ops. + /// + /// Originally ran as + /// 20260521155605_RenameBloodMoonToCrescendo; consolidated + /// here when the per-feature migrations were collapsed into a + /// single Initial. + /// + /// Concurrency: Rooms.Name has a unique index + /// (DorkNetDbContext.cs HasIndex(r=>r.Name).IsUnique()). Two + /// replicas booting in parallel against a legacy DB could both see + /// BloodMoon + no Crescendo and race to do the rename — the loser + /// would hit a unique-constraint exception on SaveChangesAsync. + /// We catch that, refresh, and re-evaluate so the race is + /// observationally a single rename. + /// + private static async Task RenameBloodMoonToCrescendoAsync( + DorkNetDbContext db, ILogger logger, CancellationToken ct) + { + // Re-runnable loop: if a concurrent boot beats us to the rename, + // catch the unique-constraint exception, drop the cached entity, + // and re-read the DB state. After at most 2 iterations we're + // either no-op (Crescendo now exists) or the only writer. + for (var attempt = 1; attempt <= 2; attempt++) + { + var crescendoExists = await db.Rooms.AnyAsync(r => r.Name == "Crescendo", ct); + var bloodMoon = await db.Rooms.FirstOrDefaultAsync(r => r.Name == "BloodMoon", ct); + + if (bloodMoon is null && crescendoExists) + { + // Most common path on a freshly-deployed server: seed + // shipped Crescendo, nothing to migrate. + logger.LogInformation("[legacy-upgrade] RenameBloodMoonToCrescendo: skipped (already on Crescendo)"); + return; + } + + if (bloodMoon is null && !crescendoExists) + { + // Neither exists — non-seeded test DB. No-op. + logger.LogInformation("[legacy-upgrade] RenameBloodMoonToCrescendo: skipped (no BloodMoon and no Crescendo present)"); + return; + } + + try + { + if (bloodMoon is not null && !crescendoExists) + { + bloodMoon.Name = "Crescendo"; + bloodMoon.Description = "Brave the haunted halls of Castle Dracula and survive the night."; + bloodMoon.ImageName = "image_Crescendo.png"; + bloodMoon.HiddenFromBrowse = false; + await db.SaveChangesAsync(ct); + logger.LogInformation( + "[legacy-upgrade] RenameBloodMoonToCrescendo: applied — renamed Rooms.Id={Id} BloodMoon → Crescendo", + bloodMoon.Id); + return; + } + + if (bloodMoon is not null && crescendoExists) + { + // Both exist — the rename ran on a different row at + // some point, and the original BloodMoon row is now + // a stale duplicate. Park it under CrescendoLegacy + // and hide from browse so /goto/name/... still + // resolves Crescendo to the right row. + bloodMoon.Name = "CrescendoLegacy"; + bloodMoon.HiddenFromBrowse = true; + await db.SaveChangesAsync(ct); + logger.LogInformation( + "[legacy-upgrade] RenameBloodMoonToCrescendo: applied — Rooms.Id={Id} parked as CrescendoLegacy (Crescendo already existed on a different row)", + bloodMoon.Id); + return; + } + } + catch (DbUpdateException ex) when (attempt == 1) + { + // Another replica won the rename race. Detach the + // stale entity and loop — second iteration will see + // Crescendo exists and exit cleanly. + logger.LogInformation( + "[legacy-upgrade] RenameBloodMoonToCrescendo: lost race on attempt {Attempt}, retrying ({Message})", + attempt, ex.GetBaseException().Message); + if (bloodMoon is not null) db.Entry(bloodMoon).State = EntityState.Detached; + } + } + } +} diff --git a/DorkNet.Server/Data/MIGRATIONS.md b/DorkNet.Server/Data/MIGRATIONS.md new file mode 100644 index 0000000..8edf389 --- /dev/null +++ b/DorkNet.Server/Data/MIGRATIONS.md @@ -0,0 +1,135 @@ +# Migrations & legacy data upgrades + +## Current state (2026-05-25 — post-consolidation) + +- **One migration** under `Migrations/`: `20260525181055_Initial.cs` + (generated by `dotnet ef migrations add Initial` against the + current entity model — captures all 67 `DbSet<>` tables). +- **`LegacyUpgrades.cs`** holds idempotent data transforms that can't + be derived from the entity model. Runs once per server boot, after + EF migrations complete and before `RoomService.SeedAsync()`. + +## What changed in the consolidation + +Before this pass, the codebase had two parallel schema-management +mechanisms accumulating per-feature noise: + +1. **34 EF migrations** under `Migrations/` — applied to SQLite via + `db.Database.Migrate()`. +2. **65 `RunPatchAsync` calls** in `Program.cs:574-1011` — idempotent + Postgres `ALTER`/`CREATE` statements layered on top of + `EnsureCreated()`, because the Postgres path skipped EF migrations. + +Both were collapsed in 2026-05-25: + +- The 34 migrations were deleted and regenerated as a single + `20260525181055_Initial`. The model snapshot was regenerated to match. +- The `RunPatchAsync` block was deleted entirely. Postgres still uses + `EnsureCreated` (which now reads the same up-to-date entity model); + no per-feature patches needed. +- The PhotonRegion coerce (the only data transform in the block) + was moved into `LegacyUpgrades.CoercePhotonRegionAsync`. +- The Storage admin page (`admin-ui/src/pages/Storage.tsx`) and + `StorageBackfillController.cs` were deleted — they were the UI for + draining the legacy `RoomDataBlobs.Bytes` column into S3, which is + no longer needed. + +## Production cutover (one-time SQL) + +Existing prod databases need a one-time fixup to tell EF "the new +Initial is already applied here" so the next boot doesn't try to +`CREATE TABLE` over existing data. + +### Postgres path + +The Postgres production schema was built via `EnsureCreated()` plus +the (now-deleted) `RunPatchAsync` block. **No `__EFMigrationsHistory` +table exists** because EF Core only creates it when `Migrate()` runs. +Postgres continues to use `EnsureCreated()` post-consolidation, so +there's *nothing to do* — `EnsureCreated()` reads the entity model, +sees the schema already matches, and no-ops. Just deploy the new +build. + +If you ever switch the Postgres path to `Migrate()`, run this once +before the cutover deploy: + +```sql +CREATE TABLE IF NOT EXISTS "__EFMigrationsHistory" ( + "MigrationId" character varying(150) NOT NULL, + "ProductVersion" character varying(32) NOT NULL, + CONSTRAINT "PK___EFMigrationsHistory" PRIMARY KEY ("MigrationId") +); + +INSERT INTO "__EFMigrationsHistory" ("MigrationId", "ProductVersion") +VALUES ('20260525181055_Initial', '10.0.0'); +``` + +### SQLite path (dev DBs) + +Handled automatically by `BaselineExistingSchemaIfNeeded()` in +`Program.cs`. On boot it detects two cutover scenarios and reconciles +the migration-history table to match the assembly's current migration +list: + +- **Legacy EnsureCreated** — schema present, history table absent. + Creates the history table and stamps the Initial. +- **Stale history post-consolidation** — schema present, history has + entries from the old 34 per-feature migrations. Wipes them and + stamps the Initial. + +`Migrate()` then sees the reconciled history and no-ops. **No manual +SQL needed.** + +#### Gotcha: partial-migration dev DBs + +`BaselineExistingSchemaIfNeeded` assumes the on-disk schema matches +the current entity model. If a dev DB was created mid-cycle and +missed some of the later migrations (e.g. `AddPlaylists`, +`AddClubAnnouncementsAndCategories`, `AddWeeklyChallengeSettings`), +the reconciled boot will succeed but the missing tables will cause +queries to fail at runtime (`no such table: Playlists`). + +Fix: either delete the dev DB and re-seed, or apply the missing +migrations against your DB *before* upgrading to the consolidated +build. The latter requires checking out the pre-consolidation commit, +running `dotnet ef database update`, then checking back out. + +## Adding migrations after consolidation + +> One release = one migration. Don't accumulate per-feature micros. + +The post-consolidation discipline: + +1. **Iterate freely** between releases — edit entities, run the server + against your dev DB (sqlite under `bin/Debug/.../data/`). EF will + reject the schema mismatch and you fix it locally however you like. + Local DBs are disposable. +2. **At release cut**, run `dotnet ef migrations add Release_v{N}_{M}` + once to bundle all the schema deltas from the iteration window into + a single migration. +3. **Data transforms** (renames of seed rows, backfills, etc.) go in + `LegacyUpgrades.cs` as new private methods, NOT as new migrations. + They run on every boot and are idempotent. + +## Regenerating Initial from scratch + +If the model drifts far enough that you want to re-collapse to one +Initial again: + +```pwsh +# 1. Delete all migration files + the model snapshot. +Remove-Item DorkNet.Server\Migrations\2026*.cs +Remove-Item DorkNet.Server\Migrations\DorkNetDbContextModelSnapshot.cs + +# 2. Regenerate Initial from the current entity model. +dotnet ef migrations add Initial ` + --project DorkNet.Server ` + --context DorkNetDbContext + +# 3. Eyeball the generated Up() body before committing — verify it +# captures every DbSet<> in DorkNetDbContext plus every +# relationship and index. +``` + +Existing DBs auto-reconcile via `BaselineExistingSchemaIfNeeded` +(SQLite) or no-op via `EnsureCreated` (Postgres). No manual cutover. diff --git a/DorkNet.Server/Data/avatar_item_lookup.json b/DorkNet.Server/Data/avatar_item_lookup.json new file mode 100644 index 0000000..067be32 --- /dev/null +++ b/DorkNet.Server/Data/avatar_item_lookup.json @@ -0,0 +1,543 @@ +{ + "items": [ + {"guid": "_OWVy3z6iU-M3-zbQgSLig", "name": "(Wrist_VampireHunter)", "display_name": "Vampire Hunter", "outfit_type": 200, "outfit_set": 0, "drawer_index": 0, "hides_hair": false, "hides_hand": true, "hides_left_ear": false, "hides_right_ear": false, "uses_hair_color": false, "uses_team_color": false, "is_team_item": false}, + {"guid": "_qXaNbm4yEeyalo372QbvA", "name": "(Eye_BikerGoggles)", "display_name": "Biker Goggles", "outfit_type": 10, "outfit_set": 0, "drawer_index": 0, "hides_hair": false, "hides_hand": false, "hides_left_ear": true, "hides_right_ear": true, "uses_hair_color": false, "uses_team_color": false, "is_team_item": false}, + {"guid": "002a0f2f-1a24-4439-b578-470818ef8325", "name": "(Shirt_TurkeySweater)", "display_name": "Turkey Sweater", "outfit_type": 101, "outfit_set": 0, "drawer_index": 0, "hides_hair": false, "hides_hand": false, "hides_left_ear": false, "hides_right_ear": false, "uses_hair_color": false, "uses_team_color": false, "is_team_item": false}, + {"guid": "0088603e-ec3b-4478-8694-e6fb1989b3f2", "name": "(Hair_AngledBob)", "display_name": "Angled Bob", "outfit_type": 2, "outfit_set": 0, "drawer_index": 0, "hides_hair": false, "hides_hand": false, "hides_left_ear": true, "hides_right_ear": true, "uses_hair_color": true, "uses_team_color": false, "is_team_item": false}, + {"guid": "00f0b914-aa22-4d14-9af1-996e1af61f4d", "name": "(Hat_TinyCrown)", "display_name": "Tiny Crown", "outfit_type": 0, "outfit_set": 0, "drawer_index": 0, "hides_hair": false, "hides_hand": false, "hides_left_ear": false, "hides_right_ear": false, "uses_hair_color": false, "uses_team_color": false, "is_team_item": false}, + {"guid": "01689baf-405e-4b38-9f09-b624e03865b8", "name": "(Hat_Wizard)", "display_name": "Wizard", "outfit_type": 0, "outfit_set": 0, "drawer_index": 0, "hides_hair": false, "hides_hand": false, "hides_left_ear": false, "hides_right_ear": false, "uses_hair_color": false, "uses_team_color": false, "is_team_item": false}, + {"guid": "02077915-57d3-4e26-b4fe-8e0399cbb293", "name": "(Shirt_Artist)", "display_name": "Artist", "outfit_type": 101, "outfit_set": 0, "drawer_index": 0, "hides_hair": false, "hides_hand": false, "hides_left_ear": false, "hides_right_ear": false, "uses_hair_color": false, "uses_team_color": false, "is_team_item": false}, + {"guid": "022ce375-ed02-479d-b87d-9d72d126bbd4", "name": "(Neck_JackFrostWings)", "display_name": "Jack Frost Wings", "outfit_type": 100, "outfit_set": 0, "drawer_index": 0, "hides_hair": false, "hides_hand": false, "hides_left_ear": false, "hides_right_ear": false, "uses_hair_color": false, "uses_team_color": false, "is_team_item": false}, + {"guid": "02a5b48c-abba-4385-967b-ba900424829e", "name": "(Eye_SafariMonocle)", "display_name": "Safari Monocle", "outfit_type": 10, "outfit_set": 33, "drawer_index": 0, "hides_hair": false, "hides_hand": false, "hides_left_ear": false, "hides_right_ear": false, "uses_hair_color": false, "uses_team_color": false, "is_team_item": false}, + {"guid": "033e328f-d9d2-4e53-af20-44f18e3ea208", "name": "(shirt_sherlock)", "display_name": "Sherlock", "outfit_type": 101, "outfit_set": 0, "drawer_index": 0, "hides_hair": false, "hides_hand": false, "hides_left_ear": false, "hides_right_ear": false, "uses_hair_color": false, "uses_team_color": false, "is_team_item": false}, + {"guid": "03f8c394-28fa-4087-978b-8d108f0bd969", "name": "(Hat_Angler)", "display_name": "Angler", "outfit_type": 0, "outfit_set": 0, "drawer_index": 0, "hides_hair": false, "hides_hand": false, "hides_left_ear": false, "hides_right_ear": false, "uses_hair_color": false, "uses_team_color": false, "is_team_item": false}, + {"guid": "05b7af56-71f2-45ba-a377-c105bf6a6f7a", "name": "(Wrist_WerewolfGlove)", "display_name": "Werewolf Glove", "outfit_type": 200, "outfit_set": 0, "drawer_index": 0, "hides_hair": false, "hides_hand": false, "hides_left_ear": false, "hides_right_ear": false, "uses_hair_color": false, "uses_team_color": false, "is_team_item": false}, + {"guid": "05ec4e87-088f-4281-a118-2e63c2585200", "name": "(Neck_Tie_Gold_Sequin)", "display_name": "Tie Gold Sequin", "outfit_type": 100, "outfit_set": 0, "drawer_index": 0, "hides_hair": false, "hides_hand": false, "hides_left_ear": false, "hides_right_ear": false, "uses_hair_color": false, "uses_team_color": false, "is_team_item": false}, + {"guid": "06306723-ca20-4aa6-b7b3-917113f41ac3", "name": "(Glasses_Pointy)", "display_name": "Pointy", "outfit_type": 10, "outfit_set": 0, "drawer_index": 0, "hides_hair": false, "hides_hand": false, "hides_left_ear": false, "hides_right_ear": false, "uses_hair_color": false, "uses_team_color": false, "is_team_item": false}, + {"guid": "07205057-0804-41de-abe4-bd4027fee1df", "name": "(Hat_Conductor)", "display_name": "Conductor", "outfit_type": 0, "outfit_set": 0, "drawer_index": 0, "hides_hair": false, "hides_hand": false, "hides_left_ear": false, "hides_right_ear": false, "uses_hair_color": false, "uses_team_color": false, "is_team_item": false}, + {"guid": "0753d7a4-8247-4fca-a6fc-359c26086140", "name": "(Hair_Fonzie)", "display_name": "Fonzie", "outfit_type": 2, "outfit_set": 0, "drawer_index": 0, "hides_hair": false, "hides_hand": false, "hides_left_ear": false, "hides_right_ear": false, "uses_hair_color": true, "uses_team_color": false, "is_team_item": false}, + {"guid": "08d56627-4500-4724-8692-a23c96ddb3ec", "name": "(Wrist_Mobster)", "display_name": "Mobster", "outfit_type": 200, "outfit_set": 0, "drawer_index": 0, "hides_hair": false, "hides_hand": false, "hides_left_ear": false, "hides_right_ear": false, "uses_hair_color": false, "uses_team_color": false, "is_team_item": false}, + {"guid": "09177621-9ecd-4f6a-b6a5-64490139141d", "name": "(Hair_FlatTop)", "display_name": "Flat Top", "outfit_type": 2, "outfit_set": 0, "drawer_index": 0, "hides_hair": false, "hides_hand": false, "hides_left_ear": false, "hides_right_ear": false, "uses_hair_color": true, "uses_team_color": false, "is_team_item": false}, + {"guid": "097a3266-4aab-4b23-8f56-373c9b1bc837", "name": "(Shirt_InternationalThiefShirt)", "display_name": "International Thief Shirt", "outfit_type": 101, "outfit_set": 0, "drawer_index": 0, "hides_hair": false, "hides_hand": false, "hides_left_ear": false, "hides_right_ear": false, "uses_hair_color": false, "uses_team_color": false, "is_team_item": false}, + {"guid": "0ab5790f-a537-4306-ba66-5eb87c02ec7d", "name": "(Shirt_Quiltedcoat)", "display_name": "Quiltedcoat", "outfit_type": 101, "outfit_set": 0, "drawer_index": 0, "hides_hair": false, "hides_hand": false, "hides_left_ear": false, "hides_right_ear": false, "uses_hair_color": false, "uses_team_color": false, "is_team_item": false}, + {"guid": "0ab6c95c-e157-48bd-a172-2642896b4ffc", "name": "(shirt_royalLady)", "display_name": "Royal Lady", "outfit_type": 101, "outfit_set": 0, "drawer_index": 0, "hides_hair": false, "hides_hand": false, "hides_left_ear": false, "hides_right_ear": false, "uses_hair_color": false, "uses_team_color": false, "is_team_item": false}, + {"guid": "0abb6b08-20ce-444f-879e-0d1344df096c", "name": "(Ear_RoundEarring)", "display_name": "Round Earring", "outfit_type": 3, "outfit_set": 0, "drawer_index": 0, "hides_hair": false, "hides_hand": false, "hides_left_ear": false, "hides_right_ear": false, "uses_hair_color": false, "uses_team_color": false, "is_team_item": false}, + {"guid": "0bc6bd1b-8b7e-4945-a1d6-c26b9c043318", "name": "(SeaCaptainTorso)", "display_name": "Sea Captain Torso", "outfit_type": 101, "outfit_set": 0, "drawer_index": 0, "hides_hair": false, "hides_hand": false, "hides_left_ear": false, "hides_right_ear": false, "uses_hair_color": false, "uses_team_color": false, "is_team_item": false}, + {"guid": "0c537a84-9095-406f-8c87-1464f3a774ba", "name": "(Hat_PB_TruckerHat)", "display_name": "PB Trucker Hat", "outfit_type": 0, "outfit_set": 0, "drawer_index": 0, "hides_hair": false, "hides_hand": false, "hides_left_ear": false, "hides_right_ear": false, "uses_hair_color": false, "uses_team_color": false, "is_team_item": false}, + {"guid": "0d4e7ebe-efd9-4e68-9900-5645916e7596", "name": "(Neck_TieSequin)", "display_name": "Tie Sequin", "outfit_type": 100, "outfit_set": 0, "drawer_index": 0, "hides_hair": false, "hides_hand": false, "hides_left_ear": false, "hides_right_ear": false, "uses_hair_color": false, "uses_team_color": false, "is_team_item": false}, + {"guid": "0QOlT9c5xUuinpfZaoEe8Q", "name": "(Hat_Tourist)", "display_name": "Tourist", "outfit_type": 0, "outfit_set": 0, "drawer_index": 0, "hides_hair": false, "hides_hand": false, "hides_left_ear": false, "hides_right_ear": false, "uses_hair_color": false, "uses_team_color": false, "is_team_item": false}, + {"guid": "0yFzgVESokC3gFd3hrS8bg", "name": "(Shirt_TutorialGown_3)", "display_name": "Tutorial Gown 3", "outfit_type": 101, "outfit_set": 0, "drawer_index": 0, "hides_hair": false, "hides_hand": false, "hides_left_ear": false, "hides_right_ear": false, "uses_hair_color": false, "uses_team_color": false, "is_team_item": false}, + {"guid": "1014dded-3bbc-479b-a6cb-afa7413de2da", "name": "(Wrist_InternationalThiefGlove)", "display_name": "International Thief Glove", "outfit_type": 200, "outfit_set": 0, "drawer_index": 0, "hides_hair": false, "hides_hand": false, "hides_left_ear": false, "hides_right_ear": false, "uses_hair_color": false, "uses_team_color": false, "is_team_item": false}, + {"guid": "102c625b-b988-4bf8-a2aa-a31ad7029cdc", "name": "(Hat_TopHat)", "display_name": "Top Hat", "outfit_type": 0, "outfit_set": 0, "drawer_index": 0, "hides_hair": false, "hides_hand": false, "hides_left_ear": false, "hides_right_ear": false, "uses_hair_color": false, "uses_team_color": false, "is_team_item": false}, + {"guid": "1041d745-c5e9-4218-a3e1-198f5c9c418c", "name": "(Shirt_Safari)", "display_name": "Safari", "outfit_type": 101, "outfit_set": 33, "drawer_index": 0, "hides_hair": false, "hides_hand": false, "hides_left_ear": false, "hides_right_ear": false, "uses_hair_color": false, "uses_team_color": false, "is_team_item": false}, + {"guid": "105a5100-cb57-410f-a26b-1ee21002f067", "name": "(Shirt_JackFrost)", "display_name": "Jack Frost", "outfit_type": 101, "outfit_set": 0, "drawer_index": 0, "hides_hair": false, "hides_hand": false, "hides_left_ear": false, "hides_right_ear": false, "uses_hair_color": false, "uses_team_color": false, "is_team_item": false}, + {"guid": "11e508a7-fe7d-470e-b270-2dba33e2f7a1", "name": "(Glasses_Scientist)", "display_name": "Scientist", "outfit_type": 10, "outfit_set": 23, "drawer_index": 0, "hides_hair": false, "hides_hand": false, "hides_left_ear": true, "hides_right_ear": true, "uses_hair_color": false, "uses_team_color": false, "is_team_item": false}, + {"guid": "123de433-a005-4994-8a11-d2d84ad66b11", "name": "(Hat_LaserTag)", "display_name": "Laser Tag", "outfit_type": 0, "outfit_set": 0, "drawer_index": 0, "hides_hair": false, "hides_hand": false, "hides_left_ear": true, "hides_right_ear": true, "uses_hair_color": false, "uses_team_color": false, "is_team_item": false}, + {"guid": "125a3a62-1005-47dc-9fd8-ee09ea803e9f", "name": "(Wrist_Aviator)", "display_name": "Aviator", "outfit_type": 200, "outfit_set": 34, "drawer_index": 0, "hides_hair": false, "hides_hand": true, "hides_left_ear": false, "hides_right_ear": false, "uses_hair_color": false, "uses_team_color": false, "is_team_item": false}, + {"guid": "13e70c80-69a4-4ad5-90e5-4dd5e03da677", "name": "(Hat_Shark)", "display_name": "Shark", "outfit_type": 0, "outfit_set": 0, "drawer_index": 0, "hides_hair": false, "hides_hand": false, "hides_left_ear": false, "hides_right_ear": false, "uses_hair_color": false, "uses_team_color": false, "is_team_item": false}, + {"guid": "14ef6b00-debf-4a85-9755-b4d37df496d3", "name": "(Hat_Baseball)", "display_name": "Baseball", "outfit_type": 0, "outfit_set": 0, "drawer_index": 0, "hides_hair": false, "hides_hand": false, "hides_left_ear": false, "hides_right_ear": false, "uses_hair_color": false, "uses_team_color": false, "is_team_item": false}, + {"guid": "1596c113-aa3a-4f5c-b398-e5c3575548e7", "name": "(Hat_Luchador)", "display_name": "Luchador", "outfit_type": 0, "outfit_set": 0, "drawer_index": 0, "hides_hair": false, "hides_hand": false, "hides_left_ear": true, "hides_right_ear": true, "uses_hair_color": false, "uses_team_color": false, "is_team_item": false}, + {"guid": "16359b51-96c4-4be0-b598-319c7549e18a", "name": "(Hat_MermaidCrownHat)", "display_name": "Mermaid Crown Hat", "outfit_type": 0, "outfit_set": 0, "drawer_index": 0, "hides_hair": false, "hides_hand": false, "hides_left_ear": false, "hides_right_ear": false, "uses_hair_color": false, "uses_team_color": false, "is_team_item": false}, + {"guid": "16707919-756d-45ae-95fc-cfb6715b8ae6", "name": "(Hat_FeatherHelmet)", "display_name": "Feather Helmet", "outfit_type": 0, "outfit_set": 0, "drawer_index": 0, "hides_hair": true, "hides_hand": false, "hides_left_ear": false, "hides_right_ear": false, "uses_hair_color": false, "uses_team_color": false, "is_team_item": false}, + {"guid": "16f4dc71-d35a-4269-b925-1a0cd85a43dd", "name": "(Shirt_WerewolfShirt)", "display_name": "Werewolf Shirt", "outfit_type": 101, "outfit_set": 0, "drawer_index": 0, "hides_hair": false, "hides_hand": false, "hides_left_ear": false, "hides_right_ear": false, "uses_hair_color": false, "uses_team_color": false, "is_team_item": false}, + {"guid": "17236d76-e3b6-42f6-9179-6fcf0479436f", "name": "(Shirt_80sSpandex)", "display_name": "80s Spandex", "outfit_type": 101, "outfit_set": 0, "drawer_index": 0, "hides_hair": false, "hides_hand": false, "hides_left_ear": false, "hides_right_ear": false, "uses_hair_color": false, "uses_team_color": false, "is_team_item": false}, + {"guid": "1736a60b-6fb6-49bd-948a-fd3f3fc1be50", "name": "(Eye_SciFiVisor_Glow)", "display_name": "Sci Fi Visor Glow", "outfit_type": 10, "outfit_set": 0, "drawer_index": 0, "hides_hair": false, "hides_hand": false, "hides_left_ear": false, "hides_right_ear": false, "uses_hair_color": false, "uses_team_color": false, "is_team_item": false}, + {"guid": "17aed0a0-70ed-49da-ad09-5bc4746f7718", "name": "(Hat_ArtistBeret)", "display_name": "Artist Beret", "outfit_type": 0, "outfit_set": 0, "drawer_index": 0, "hides_hair": false, "hides_hand": false, "hides_left_ear": false, "hides_right_ear": false, "uses_hair_color": false, "uses_team_color": false, "is_team_item": false}, + {"guid": "18cd3470-2dd9-4a0a-98bd-eae2d5f5d522", "name": "(Shirt_Barbershop)", "display_name": "Barbershop", "outfit_type": 101, "outfit_set": 35, "drawer_index": 0, "hides_hair": false, "hides_hand": false, "hides_left_ear": false, "hides_right_ear": false, "uses_hair_color": false, "uses_team_color": false, "is_team_item": false}, + {"guid": "18feb3c2-049f-4f91-9f4e-6b5fcb4ab70c", "name": "(Hat_FlapperHeadband)", "display_name": "Flapper Headband", "outfit_type": 0, "outfit_set": 0, "drawer_index": 0, "hides_hair": false, "hides_hand": false, "hides_left_ear": false, "hides_right_ear": false, "uses_hair_color": false, "uses_team_color": false, "is_team_item": false}, + {"guid": "193a3bf9-abc0-4d78-8d63-92046908b1c5", "name": "(Hair_Emo)", "display_name": "Emo", "outfit_type": 2, "outfit_set": 0, "drawer_index": 0, "hides_hair": false, "hides_hand": false, "hides_left_ear": false, "hides_right_ear": false, "uses_hair_color": true, "uses_team_color": false, "is_team_item": false}, + {"guid": "1974dd48-aa44-434f-879a-eaff0c7c06e6", "name": "(Wrist_WitchHunterGlove)", "display_name": "Witch Hunter Glove", "outfit_type": 200, "outfit_set": 0, "drawer_index": 0, "hides_hair": false, "hides_hand": false, "hides_left_ear": false, "hides_right_ear": false, "uses_hair_color": false, "uses_team_color": false, "is_team_item": false}, + {"guid": "1988692c-aae3-450b-acd6-e9015b831f5c", "name": "(Dress_SuperheroDress)", "display_name": "Superhero Dress", "outfit_type": 101, "outfit_set": 0, "drawer_index": 0, "hides_hair": false, "hides_hand": false, "hides_left_ear": false, "hides_right_ear": false, "uses_hair_color": false, "uses_team_color": false, "is_team_item": false}, + {"guid": "1a028850-64ee-40ab-b547-ebbcfa1b04b3", "name": "(Hat_DrumMajor)", "display_name": "Drum Major", "outfit_type": 0, "outfit_set": 0, "drawer_index": 0, "hides_hair": false, "hides_hand": false, "hides_left_ear": false, "hides_right_ear": false, "uses_hair_color": false, "uses_team_color": false, "is_team_item": false}, + {"guid": "1a71064b-794f-40fa-9109-8ad36602b6e1", "name": "(Hair_Shagg)", "display_name": "Shagg", "outfit_type": 2, "outfit_set": 0, "drawer_index": 0, "hides_hair": false, "hides_hand": false, "hides_left_ear": true, "hides_right_ear": false, "uses_hair_color": true, "uses_team_color": false, "is_team_item": false}, + {"guid": "1a742ff9-c4f7-45d8-8e24-8f24aa021db2", "name": "(Hat_DemonHeadband)", "display_name": "Demon Headband", "outfit_type": 0, "outfit_set": 0, "drawer_index": 0, "hides_hair": false, "hides_hand": false, "hides_left_ear": false, "hides_right_ear": false, "uses_hair_color": false, "uses_team_color": false, "is_team_item": false}, + {"guid": "1b37d6cd-0a89-4604-a046-e5cff55b0cf6", "name": "(FirefighterTorso)", "display_name": "Firefighter Torso", "outfit_type": 101, "outfit_set": 0, "drawer_index": 0, "hides_hair": false, "hides_hand": false, "hides_left_ear": false, "hides_right_ear": false, "uses_hair_color": false, "uses_team_color": false, "is_team_item": false}, + {"guid": "1beaf288-b5a5-475c-8149-c8f3d1146083", "name": "(Wrist_HarnessGloves)", "display_name": "Harness Gloves", "outfit_type": 200, "outfit_set": 0, "drawer_index": 0, "hides_hair": false, "hides_hand": false, "hides_left_ear": false, "hides_right_ear": false, "uses_hair_color": false, "uses_team_color": false, "is_team_item": false}, + {"guid": "1d27b674-f9e2-4ffc-9d8c-a58a1be06457", "name": "(Hair_Afro)", "display_name": "Afro", "outfit_type": 2, "outfit_set": 0, "drawer_index": 0, "hides_hair": false, "hides_hand": false, "hides_left_ear": false, "hides_right_ear": false, "uses_hair_color": true, "uses_team_color": false, "is_team_item": false}, + {"guid": "1e504fee-b593-4037-bd8f-b88025147991", "name": "(Hat_Bow)", "display_name": "Bow", "outfit_type": 0, "outfit_set": 0, "drawer_index": 0, "hides_hair": false, "hides_hand": false, "hides_left_ear": false, "hides_right_ear": false, "uses_hair_color": false, "uses_team_color": false, "is_team_item": false}, + {"guid": "1ebbfdfc-f72b-4d70-99d4-4d527f896aa7", "name": "(Shirt_Beekeeper)", "display_name": "Beekeeper", "outfit_type": 101, "outfit_set": 0, "drawer_index": 0, "hides_hair": false, "hides_hand": false, "hides_left_ear": false, "hides_right_ear": false, "uses_hair_color": false, "uses_team_color": false, "is_team_item": false}, + {"guid": "1fcaf4fd-9012-407e-b691-fbc3ccf22421", "name": "(Shirt_Scarecrow)", "display_name": "Scarecrow", "outfit_type": 101, "outfit_set": 0, "drawer_index": 0, "hides_hair": false, "hides_hand": false, "hides_left_ear": false, "hides_right_ear": false, "uses_hair_color": false, "uses_team_color": false, "is_team_item": false}, + {"guid": "1fd69ef8-0b74-4962-af5a-67f0bf0358f2", "name": "(Hair_Ponytail)", "display_name": "Ponytail", "outfit_type": 2, "outfit_set": 0, "drawer_index": 0, "hides_hair": false, "hides_hand": false, "hides_left_ear": false, "hides_right_ear": false, "uses_hair_color": true, "uses_team_color": false, "is_team_item": false}, + {"guid": "204bc384-e6d2-41b8-8ce9-0b26be5d85b7", "name": "(Belt_WaitressApron)", "display_name": "Waitress Apron", "outfit_type": 102, "outfit_set": 0, "drawer_index": 0, "hides_hair": false, "hides_hand": false, "hides_left_ear": false, "hides_right_ear": false, "uses_hair_color": false, "uses_team_color": false, "is_team_item": false}, + {"guid": "20b424a9-861a-4d39-a583-22d7b7348c14", "name": "(Hat_BogMonsterHat)", "display_name": "Bog Monster Hat", "outfit_type": 0, "outfit_set": 0, "drawer_index": 0, "hides_hair": true, "hides_hand": false, "hides_left_ear": true, "hides_right_ear": true, "uses_hair_color": false, "uses_team_color": false, "is_team_item": false}, + {"guid": "21599b51-c50f-43d8-ac5f-62c30cd02ca5", "name": "(Hair_Lori)", "display_name": "Lori", "outfit_type": 2, "outfit_set": 0, "drawer_index": 0, "hides_hair": false, "hides_hand": false, "hides_left_ear": true, "hides_right_ear": true, "uses_hair_color": true, "uses_team_color": false, "is_team_item": false}, + {"guid": "21caa68e-c3fa-474c-af5e-af1e742b7a60", "name": "(TennisSkirt)", "display_name": "Tennis Skirt", "outfit_type": 101, "outfit_set": 0, "drawer_index": 0, "hides_hair": false, "hides_hand": false, "hides_left_ear": false, "hides_right_ear": false, "uses_hair_color": false, "uses_team_color": false, "is_team_item": false}, + {"guid": "2296ed0d-df56-4d46-b33a-aae9230a47fc", "name": "(YellowBlueDress)", "display_name": "Yellow Blue Dress", "outfit_type": 101, "outfit_set": 0, "drawer_index": 0, "hides_hair": false, "hides_hand": false, "hides_left_ear": false, "hides_right_ear": false, "uses_hair_color": false, "uses_team_color": false, "is_team_item": false}, + {"guid": "22ef1089-d073-4fe8-b038-6f34adf0b85f", "name": "(Wrist_Wizard)", "display_name": "Wizard", "outfit_type": 200, "outfit_set": 0, "drawer_index": 0, "hides_hair": false, "hides_hand": false, "hides_left_ear": false, "hides_right_ear": false, "uses_hair_color": false, "uses_team_color": false, "is_team_item": false}, + {"guid": "22pyI3Kz-UullhcebC6DLQ", "name": "(Eye_Snorkeler_Goggles)", "display_name": "Snorkeler Goggles", "outfit_type": 10, "outfit_set": 0, "drawer_index": 0, "hides_hair": false, "hides_hand": false, "hides_left_ear": true, "hides_right_ear": true, "uses_hair_color": false, "uses_team_color": false, "is_team_item": false}, + {"guid": "241506f6-bf88-4b46-b5fe-513a225421f4", "name": "(Hair_HalfUp)", "display_name": "Half Up", "outfit_type": 2, "outfit_set": 0, "drawer_index": 0, "hides_hair": false, "hides_hand": false, "hides_left_ear": true, "hides_right_ear": true, "uses_hair_color": true, "uses_team_color": false, "is_team_item": false}, + {"guid": "24a240f4-1574-420b-b898-a7e91f170759", "name": "(Hair_ManBun)", "display_name": "Man Bun", "outfit_type": 2, "outfit_set": 0, "drawer_index": 0, "hides_hair": false, "hides_hand": false, "hides_left_ear": false, "hides_right_ear": false, "uses_hair_color": true, "uses_team_color": false, "is_team_item": false}, + {"guid": "24b8b757-4168-4b3b-ba11-46f29d19044d", "name": "(Jersey_Team_Emissive)", "display_name": "Team Emissive", "outfit_type": 104, "outfit_set": 0, "drawer_index": 0, "hides_hair": false, "hides_hand": false, "hides_left_ear": false, "hides_right_ear": false, "uses_hair_color": false, "uses_team_color": false, "is_team_item": true}, + {"guid": "24c4404a-839f-46bd-98b6-6848d747a98b", "name": "(Hat_Winter)", "display_name": "Winter", "outfit_type": 0, "outfit_set": 0, "drawer_index": 0, "hides_hair": false, "hides_hand": false, "hides_left_ear": false, "hides_right_ear": false, "uses_hair_color": false, "uses_team_color": false, "is_team_item": false}, + {"guid": "24d94673-f394-42e6-a3c8-276f8b8aa722", "name": "(Shirt_LaserTag)", "display_name": "Laser Tag", "outfit_type": 101, "outfit_set": 0, "drawer_index": 0, "hides_hair": false, "hides_hand": false, "hides_left_ear": false, "hides_right_ear": false, "uses_hair_color": false, "uses_team_color": false, "is_team_item": false}, + {"guid": "250bbb53-1fae-440d-b25a-46a16c271367", "name": "(Hat_NightcapHat)", "display_name": "Nightcap Hat", "outfit_type": 0, "outfit_set": 0, "drawer_index": 0, "hides_hair": false, "hides_hand": false, "hides_left_ear": false, "hides_right_ear": false, "uses_hair_color": false, "uses_team_color": false, "is_team_item": false}, + {"guid": "25769d1f-4ecb-4d92-b692-322b38c013b5", "name": "(Shirt_StrengthGi)", "display_name": "Strength Gi", "outfit_type": 101, "outfit_set": 0, "drawer_index": 0, "hides_hair": false, "hides_hand": false, "hides_left_ear": false, "hides_right_ear": false, "uses_hair_color": false, "uses_team_color": false, "is_team_item": false}, + {"guid": "25d4c652-1730-4925-925e-31a290a2272a", "name": "(Shirt_Smuggler)", "display_name": "Smuggler", "outfit_type": 101, "outfit_set": 25, "drawer_index": 0, "hides_hair": false, "hides_hand": false, "hides_left_ear": false, "hides_right_ear": false, "uses_hair_color": false, "uses_team_color": false, "is_team_item": false}, + {"guid": "26f5e849-14b3-4cb3-ab92-673c94b37c51", "name": "(Neck_DemonWings)", "display_name": "Demon Wings", "outfit_type": 100, "outfit_set": 0, "drawer_index": 0, "hides_hair": false, "hides_hand": false, "hides_left_ear": false, "hides_right_ear": false, "uses_hair_color": false, "uses_team_color": false, "is_team_item": false}, + {"guid": "274cb9b2-2f59-47ea-9a8d-a5b656d148c6", "name": "(Hat_Saija_Helmet)", "display_name": "Saija Helmet", "outfit_type": 0, "outfit_set": 0, "drawer_index": 0, "hides_hair": false, "hides_hand": false, "hides_left_ear": true, "hides_right_ear": true, "uses_hair_color": false, "uses_team_color": false, "is_team_item": false}, + {"guid": "2786fc22-3d6c-440d-afcc-15890c061577", "name": "(Hat_Bee)", "display_name": "Bee", "outfit_type": 0, "outfit_set": 0, "drawer_index": 0, "hides_hair": false, "hides_hand": false, "hides_left_ear": false, "hides_right_ear": false, "uses_hair_color": false, "uses_team_color": false, "is_team_item": false}, + {"guid": "27c2eb7f-9fac-4c42-90c7-44e7a33d5303", "name": "(Neck_ButterflyWings_TheBlueOne)", "display_name": "Butterfly Wings The Blue One", "outfit_type": 100, "outfit_set": 0, "drawer_index": 0, "hides_hair": false, "hides_hand": false, "hides_left_ear": false, "hides_right_ear": false, "uses_hair_color": false, "uses_team_color": false, "is_team_item": false}, + {"guid": "27cdecd2-88f1-40a8-80a9-87abd827c3c5", "name": "(Mouth_WizardBeard)", "display_name": "Wizard Beard", "outfit_type": 20, "outfit_set": 0, "drawer_index": 0, "hides_hair": false, "hides_hand": false, "hides_left_ear": false, "hides_right_ear": false, "uses_hair_color": true, "uses_team_color": false, "is_team_item": false}, + {"guid": "27e742bf-3081-4274-ba70-301f0edeb4f1", "name": "(Shirt_Doctor)", "display_name": "Doctor", "outfit_type": 101, "outfit_set": 29, "drawer_index": 0, "hides_hair": false, "hides_hand": false, "hides_left_ear": false, "hides_right_ear": false, "uses_hair_color": false, "uses_team_color": false, "is_team_item": false}, + {"guid": "28374ebc-4050-4ea0-b3b6-41ebe75cedf7", "name": "(Wrist_Punk)", "display_name": "Punk", "outfit_type": 200, "outfit_set": 31, "drawer_index": 0, "hides_hair": false, "hides_hand": false, "hides_left_ear": false, "hides_right_ear": false, "uses_hair_color": false, "uses_team_color": false, "is_team_item": false}, + {"guid": "28e5dd74-7963-42cf-aa68-f8005aaf312e", "name": "(Eye_Skeleton_Mask)", "display_name": "Skeleton Mask", "outfit_type": 10, "outfit_set": 0, "drawer_index": 0, "hides_hair": false, "hides_hand": false, "hides_left_ear": false, "hides_right_ear": false, "uses_hair_color": false, "uses_team_color": false, "is_team_item": false}, + {"guid": "293e7ad3-01c9-43af-96e3-06c34145ff12", "name": "(Hat_BaseballHelmet)", "display_name": "Baseball Helmet", "outfit_type": 0, "outfit_set": 0, "drawer_index": 0, "hides_hair": false, "hides_hand": false, "hides_left_ear": false, "hides_right_ear": true, "uses_hair_color": false, "uses_team_color": false, "is_team_item": false}, + {"guid": "2b2eb9e5-d52e-4004-bdb2-0bcf6910992d", "name": "(Hat_Flowers)", "display_name": "Flowers", "outfit_type": 0, "outfit_set": 0, "drawer_index": 0, "hides_hair": false, "hides_hand": false, "hides_left_ear": false, "hides_right_ear": false, "uses_hair_color": false, "uses_team_color": false, "is_team_item": false}, + {"guid": "2b7f4dae-51da-4af0-b009-12d6005c85c6", "name": "(Hair_Mullet)", "display_name": "Mullet", "outfit_type": 2, "outfit_set": 0, "drawer_index": 0, "hides_hair": false, "hides_hand": false, "hides_left_ear": false, "hides_right_ear": false, "uses_hair_color": true, "uses_team_color": false, "is_team_item": false}, + {"guid": "2bd53190-46f8-4956-9388-87c5a04efc4f", "name": "(Hat_Rainhat)", "display_name": "Rainhat", "outfit_type": 0, "outfit_set": 0, "drawer_index": 0, "hides_hair": false, "hides_hand": false, "hides_left_ear": false, "hides_right_ear": false, "uses_hair_color": false, "uses_team_color": false, "is_team_item": false}, + {"guid": "2c3773a5-a0b4-41c5-a4df-6572dff516c9", "name": "(Eye_2020Glasses)", "display_name": "2020 Glasses", "outfit_type": 10, "outfit_set": 0, "drawer_index": 0, "hides_hair": false, "hides_hand": false, "hides_left_ear": false, "hides_right_ear": false, "uses_hair_color": false, "uses_team_color": false, "is_team_item": false}, + {"guid": "2c679f89-c76e-4cfb-94e9-448c8fd44d55", "name": "(Shirt_Saija)", "display_name": "Saija", "outfit_type": 101, "outfit_set": 0, "drawer_index": 0, "hides_hair": false, "hides_hand": false, "hides_left_ear": false, "hides_right_ear": false, "uses_hair_color": false, "uses_team_color": false, "is_team_item": false}, + {"guid": "2caHvVQ1vECrPv-YcdxdyA", "name": "(Hat_Unicorn)", "display_name": "Unicorn", "outfit_type": 0, "outfit_set": 0, "drawer_index": 0, "hides_hair": false, "hides_hand": false, "hides_left_ear": false, "hides_right_ear": false, "uses_hair_color": false, "uses_team_color": false, "is_team_item": false}, + {"guid": "2cb4f372-3372-4583-8b57-c4e3988e3c28", "name": "(Hair_PunkGirl)", "display_name": "Punk Girl", "outfit_type": 2, "outfit_set": 0, "drawer_index": 0, "hides_hair": false, "hides_hand": false, "hides_left_ear": false, "hides_right_ear": false, "uses_hair_color": true, "uses_team_color": false, "is_team_item": false}, + {"guid": "2cf6ceee-4c45-4fb4-b9df-cb8437742e85", "name": "(Neck_DubloonNecklace)", "display_name": "Dubloon Necklace", "outfit_type": 103, "outfit_set": 0, "drawer_index": 0, "hides_hair": false, "hides_hand": false, "hides_left_ear": false, "hides_right_ear": false, "uses_hair_color": false, "uses_team_color": false, "is_team_item": false}, + {"guid": "2ddd2c2b-596c-4022-a488-655b0db1c265", "name": "(Wrist_BountyHunterGlove)", "display_name": "Bounty Hunter Glove", "outfit_type": 200, "outfit_set": 0, "drawer_index": 0, "hides_hair": false, "hides_hand": false, "hides_left_ear": false, "hides_right_ear": false, "uses_hair_color": false, "uses_team_color": false, "is_team_item": false}, + {"guid": "2dFMy4zc7ky_Fd5fzfgInw", "name": "(Wrist_Letterman)", "display_name": "Letterman", "outfit_type": 200, "outfit_set": 0, "drawer_index": 0, "hides_hair": false, "hides_hand": false, "hides_left_ear": false, "hides_right_ear": false, "uses_hair_color": false, "uses_team_color": false, "is_team_item": false}, + {"guid": "2e59d8d0-91a0-4449-bfdc-a5d663fd9343", "name": "(CollarShirt)", "display_name": "Collar Shirt", "outfit_type": 101, "outfit_set": 0, "drawer_index": 0, "hides_hair": false, "hides_hand": false, "hides_left_ear": false, "hides_right_ear": false, "uses_hair_color": false, "uses_team_color": false, "is_team_item": false}, + {"guid": "2e5afc40-7e25-418c-8b1b-316b8ef479a7", "name": "(KnightCape)", "display_name": "Knight Cape", "outfit_type": 100, "outfit_set": 1, "drawer_index": 0, "hides_hair": false, "hides_hand": false, "hides_left_ear": false, "hides_right_ear": false, "uses_hair_color": false, "uses_team_color": false, "is_team_item": false}, + {"guid": "2f8518fc-c989-40de-98b4-c6f09b304166", "name": "(BaseballTorso)", "display_name": "Baseball Torso", "outfit_type": 101, "outfit_set": 0, "drawer_index": 0, "hides_hair": false, "hides_hand": false, "hides_left_ear": false, "hides_right_ear": false, "uses_hair_color": false, "uses_team_color": false, "is_team_item": false}, + {"guid": "2faa26b6-f7c3-4b0d-8f70-1f9454e64a54", "name": "(Hat_Golfer)", "display_name": "Golfer", "outfit_type": 0, "outfit_set": 27, "drawer_index": 0, "hides_hair": false, "hides_hand": false, "hides_left_ear": false, "hides_right_ear": false, "uses_hair_color": false, "uses_team_color": false, "is_team_item": false}, + {"guid": "2fadddc1-babd-4cb3-8f5a-0e7c889c3785", "name": "(Hat_BountyHunterHelmet)", "display_name": "Bounty Hunter Helmet", "outfit_type": 0, "outfit_set": 0, "drawer_index": 0, "hides_hair": true, "hides_hand": false, "hides_left_ear": false, "hides_right_ear": false, "uses_hair_color": false, "uses_team_color": false, "is_team_item": false}, + {"guid": "30380607-8b54-4b63-81d3-cb3f08e452c1", "name": "(Dress_FormalDress)", "display_name": "Formal Dress", "outfit_type": 101, "outfit_set": 0, "drawer_index": 0, "hides_hair": false, "hides_hand": false, "hides_left_ear": false, "hides_right_ear": false, "uses_hair_color": false, "uses_team_color": false, "is_team_item": false}, + {"guid": "3044adce-90c0-4f66-81f5-39c4fca86fdf", "name": "(Wrist_LaserTagGlove)", "display_name": "Laser Tag Glove", "outfit_type": 200, "outfit_set": 0, "drawer_index": 0, "hides_hair": false, "hides_hand": true, "hides_left_ear": false, "hides_right_ear": false, "uses_hair_color": false, "uses_team_color": false, "is_team_item": false}, + {"guid": "31bb82db-cd30-4988-a8bf-3ce55a12a4c9", "name": "(Hat_FoxEarsHeadband)", "display_name": "Fox Ears Headband", "outfit_type": 0, "outfit_set": 0, "drawer_index": 0, "hides_hair": false, "hides_hand": false, "hides_left_ear": false, "hides_right_ear": false, "uses_hair_color": false, "uses_team_color": false, "is_team_item": false}, + {"guid": "34144f4c-f6ef-49e5-a286-1781e82e6782", "name": "(Glasses_Paintball)", "display_name": "Paintball", "outfit_type": 10, "outfit_set": 0, "drawer_index": 0, "hides_hair": false, "hides_hand": false, "hides_left_ear": false, "hides_right_ear": false, "uses_hair_color": false, "uses_team_color": false, "is_team_item": false}, + {"guid": "34654303-9760-447f-8614-1c26506db994", "name": "(Hat_Electrician)", "display_name": "Electrician", "outfit_type": 0, "outfit_set": 0, "drawer_index": 0, "hides_hair": false, "hides_hand": false, "hides_left_ear": false, "hides_right_ear": false, "uses_hair_color": false, "uses_team_color": false, "is_team_item": false}, + {"guid": "348ac48b-986e-4d42-a0f7-1aea16b88271", "name": "(Basketball_Tee)", "display_name": "Basketball Tee", "outfit_type": 101, "outfit_set": 0, "drawer_index": 0, "hides_hair": false, "hides_hand": false, "hides_left_ear": false, "hides_right_ear": false, "uses_hair_color": false, "uses_team_color": false, "is_team_item": false}, + {"guid": "34d45669-b319-4c7d-bd05-078961d70d0b", "name": "(FootBallShoulderPads)", "display_name": "Foot Ball Shoulder Pads", "outfit_type": 101, "outfit_set": 0, "drawer_index": 0, "hides_hair": false, "hides_hand": false, "hides_left_ear": false, "hides_right_ear": false, "uses_hair_color": false, "uses_team_color": false, "is_team_item": false}, + {"guid": "34f45c5b-fea7-40b4-add7-bc7a37000ab5", "name": "(Hat_StarCaptainHat)", "display_name": "Star Captain Hat", "outfit_type": 0, "outfit_set": 0, "drawer_index": 0, "hides_hair": false, "hides_hand": false, "hides_left_ear": false, "hides_right_ear": false, "uses_hair_color": false, "uses_team_color": false, "is_team_item": false}, + {"guid": "350c5c1e-318b-4233-a7e5-1f49909d8e65", "name": "(Shirt_Lumberjack)", "display_name": "Lumberjack", "outfit_type": 101, "outfit_set": 3, "drawer_index": 0, "hides_hair": false, "hides_hand": false, "hides_left_ear": false, "hides_right_ear": false, "uses_hair_color": false, "uses_team_color": false, "is_team_item": false}, + {"guid": "35f60d27-22cf-4e17-8f39-c9e3a9fa2291", "name": "(Shirt_ScoutSkirt)", "display_name": "Scout Skirt", "outfit_type": 101, "outfit_set": 0, "drawer_index": 0, "hides_hair": false, "hides_hand": false, "hides_left_ear": false, "hides_right_ear": false, "uses_hair_color": false, "uses_team_color": false, "is_team_item": false}, + {"guid": "3757149b-f478-4975-81e5-d808a1bed8d5", "name": "(Shirt_Winter)", "display_name": "Winter", "outfit_type": 101, "outfit_set": 0, "drawer_index": 0, "hides_hair": false, "hides_hand": false, "hides_left_ear": false, "hides_right_ear": false, "uses_hair_color": false, "uses_team_color": false, "is_team_item": false}, + {"guid": "37668d3d-c9f4-4bd6-81d4-a4f64ba2d61b", "name": "(Neck_Bee)", "display_name": "Bee", "outfit_type": 100, "outfit_set": 0, "drawer_index": 0, "hides_hair": false, "hides_hand": false, "hides_left_ear": false, "hides_right_ear": false, "uses_hair_color": false, "uses_team_color": false, "is_team_item": false}, + {"guid": "380a5e35-b4d0-4119-9721-8ce4c0840fbd", "name": "(Hat_ScifiHelmet)", "display_name": "Scifi Helmet", "outfit_type": 0, "outfit_set": 0, "drawer_index": 0, "hides_hair": true, "hides_hand": false, "hides_left_ear": false, "hides_right_ear": false, "uses_hair_color": false, "uses_team_color": false, "is_team_item": false}, + {"guid": "38b38e24-d636-41c6-b80e-a285ae3106dc", "name": "(Neck_PirateCaptain)", "display_name": "Pirate Captain", "outfit_type": 102, "outfit_set": 0, "drawer_index": 0, "hides_hair": false, "hides_hand": false, "hides_left_ear": false, "hides_right_ear": false, "uses_hair_color": false, "uses_team_color": false, "is_team_item": false}, + {"guid": "39278302-b52a-48a8-a566-ed756a5fa1a9", "name": "(Shirt_Lacrosse)", "display_name": "Lacrosse", "outfit_type": 101, "outfit_set": 15, "drawer_index": 0, "hides_hair": false, "hides_hand": false, "hides_left_ear": false, "hides_right_ear": false, "uses_hair_color": false, "uses_team_color": false, "is_team_item": false}, + {"guid": "3a790be3-2937-44d4-be01-b5d65353bd3d", "name": "(Wrist_Winter)", "display_name": "Winter", "outfit_type": 200, "outfit_set": 0, "drawer_index": 0, "hides_hair": false, "hides_hand": true, "hides_left_ear": false, "hides_right_ear": false, "uses_hair_color": false, "uses_team_color": false, "is_team_item": false}, + {"guid": "3ae0eb82-13ce-43a2-bbc2-99a1cdbec63f", "name": "(Hat_Crabbyhat)", "display_name": "Crabbyhat", "outfit_type": 0, "outfit_set": 0, "drawer_index": 0, "hides_hair": true, "hides_hand": false, "hides_left_ear": false, "hides_right_ear": false, "uses_hair_color": false, "uses_team_color": false, "is_team_item": false}, + {"guid": "3af4c506-d49c-4126-9437-1d3bb1658863", "name": "(Neck_Scallywag_Belt)", "display_name": "Scallywag Belt", "outfit_type": 102, "outfit_set": 0, "drawer_index": 0, "hides_hair": false, "hides_hand": false, "hides_left_ear": false, "hides_right_ear": false, "uses_hair_color": false, "uses_team_color": false, "is_team_item": false}, + {"guid": "3b00c71f-79c2-4a04-9fc0-a23b4c85562f", "name": "(Shirt_PirateCaptain)", "display_name": "Pirate Captain", "outfit_type": 101, "outfit_set": 0, "drawer_index": 0, "hides_hair": false, "hides_hand": false, "hides_left_ear": false, "hides_right_ear": false, "uses_hair_color": false, "uses_team_color": false, "is_team_item": false}, + {"guid": "3bb50a88-09ae-48c7-a42c-df5879680eff", "name": "(Wrist_ConstructionGlove)", "display_name": "Construction Glove", "outfit_type": 200, "outfit_set": 0, "drawer_index": 0, "hides_hair": false, "hides_hand": true, "hides_left_ear": false, "hides_right_ear": false, "uses_hair_color": false, "uses_team_color": false, "is_team_item": false}, + {"guid": "3c9acb9e-3353-4359-aa5a-ae9512b086b4", "name": "(Wrist_Scarecrow)", "display_name": "Scarecrow", "outfit_type": 200, "outfit_set": 0, "drawer_index": 0, "hides_hair": false, "hides_hand": false, "hides_left_ear": false, "hides_right_ear": false, "uses_hair_color": false, "uses_team_color": false, "is_team_item": false}, + {"guid": "3d5184e1-0201-4476-bf4b-4eb28190de62", "name": "(Hat_CatEars)", "display_name": "Cat Ears", "outfit_type": 0, "outfit_set": 0, "drawer_index": 0, "hides_hair": false, "hides_hand": false, "hides_left_ear": false, "hides_right_ear": false, "uses_hair_color": false, "uses_team_color": false, "is_team_item": false}, + {"guid": "3d9d52cb-6a3d-436b-90e7-e76f238329ee", "name": "(Shirt_Vest)", "display_name": "Vest", "outfit_type": 101, "outfit_set": 0, "drawer_index": 0, "hides_hair": false, "hides_hand": false, "hides_left_ear": false, "hides_right_ear": false, "uses_hair_color": false, "uses_team_color": false, "is_team_item": false}, + {"guid": "3dc003c5-428b-47d6-80fb-6520b63119e8", "name": "(Shirt_Electrician)", "display_name": "Electrician", "outfit_type": 101, "outfit_set": 0, "drawer_index": 0, "hides_hair": false, "hides_hand": false, "hides_left_ear": false, "hides_right_ear": false, "uses_hair_color": false, "uses_team_color": false, "is_team_item": false}, + {"guid": "3dec4865-cf23-4c19-a460-dd2db80ee851", "name": "(Wrist_Smuggler)", "display_name": "Smuggler", "outfit_type": 200, "outfit_set": 25, "drawer_index": 0, "hides_hair": false, "hides_hand": false, "hides_left_ear": false, "hides_right_ear": false, "uses_hair_color": false, "uses_team_color": false, "is_team_item": false}, + {"guid": "3df394de-9c3e-4141-aba4-12eac4742102", "name": "(Hat_PirateCaptain)", "display_name": "Pirate Captain", "outfit_type": 0, "outfit_set": 0, "drawer_index": 0, "hides_hair": false, "hides_hand": false, "hides_left_ear": false, "hides_right_ear": false, "uses_hair_color": false, "uses_team_color": false, "is_team_item": false}, + {"guid": "3e139dd9-4757-43a8-8546-8da70dc17f0a", "name": "(CopStache)", "display_name": "Cop Stache", "outfit_type": 20, "outfit_set": 0, "drawer_index": 0, "hides_hair": false, "hides_hand": false, "hides_left_ear": false, "hides_right_ear": false, "uses_hair_color": true, "uses_team_color": false, "is_team_item": false}, + {"guid": "3e3f5ed5-d810-4054-b648-d93167b90033", "name": "(Shirt_SleevelessDenimJacket)", "display_name": "Sleeveless Denim Jacket", "outfit_type": 101, "outfit_set": 0, "drawer_index": 0, "hides_hair": false, "hides_hand": false, "hides_left_ear": false, "hides_right_ear": false, "uses_hair_color": false, "uses_team_color": false, "is_team_item": false}, + {"guid": "3eb14fcf-5d20-47d3-b991-97808798a225", "name": "(Hat_LuauFlowers_Hat)", "display_name": "Luau Flowers Hat", "outfit_type": 0, "outfit_set": 0, "drawer_index": 0, "hides_hair": false, "hides_hand": false, "hides_left_ear": false, "hides_right_ear": false, "uses_hair_color": false, "uses_team_color": false, "is_team_item": false}, + {"guid": "3R_zf8xliEiurVU8GwaItg", "name": "(Shirt_IceQueenGown)", "display_name": "Ice Queen Gown", "outfit_type": 101, "outfit_set": 0, "drawer_index": 0, "hides_hair": false, "hides_hand": false, "hides_left_ear": false, "hides_right_ear": false, "uses_hair_color": false, "uses_team_color": false, "is_team_item": false}, + {"guid": "4025e4c4-abaa-4729-a2a3-a7175313e5ed", "name": "(Wrist_Beekeeper)", "display_name": "Beekeeper", "outfit_type": 200, "outfit_set": 0, "drawer_index": 0, "hides_hair": false, "hides_hand": true, "hides_left_ear": false, "hides_right_ear": false, "uses_hair_color": false, "uses_team_color": false, "is_team_item": false}, + {"guid": "40528de7-38a3-4a7c-8f93-6d3bfa5573f2", "name": "(Hat_HeadBand)", "display_name": "Head Band", "outfit_type": 0, "outfit_set": 0, "drawer_index": 0, "hides_hair": false, "hides_hand": false, "hides_left_ear": false, "hides_right_ear": false, "uses_hair_color": false, "uses_team_color": false, "is_team_item": false}, + {"guid": "40734c4d-e1fe-426c-8921-930f7463d8e8", "name": "(hat_royalCrown)", "display_name": "Royal Crown", "outfit_type": 0, "outfit_set": 0, "drawer_index": 0, "hides_hair": false, "hides_hand": false, "hides_left_ear": false, "hides_right_ear": false, "uses_hair_color": false, "uses_team_color": false, "is_team_item": false}, + {"guid": "418db0e0-c7af-40c2-9cac-d461ce41e210", "name": "(Shirt_WitchHunter)", "display_name": "Witch Hunter", "outfit_type": 101, "outfit_set": 0, "drawer_index": 0, "hides_hair": false, "hides_hand": false, "hides_left_ear": false, "hides_right_ear": false, "uses_hair_color": false, "uses_team_color": false, "is_team_item": false}, + {"guid": "418ed21e-766f-4c69-9d57-6f866dcc7feb", "name": "(Hat_Safari)", "display_name": "Safari", "outfit_type": 0, "outfit_set": 33, "drawer_index": 0, "hides_hair": false, "hides_hand": false, "hides_left_ear": false, "hides_right_ear": false, "uses_hair_color": false, "uses_team_color": false, "is_team_item": false}, + {"guid": "41bece0e-9b5d-4151-bf5c-d0e786a803b4", "name": "(Hat_Beanie_Slouch)", "display_name": "Beanie Slouch", "outfit_type": 0, "outfit_set": 0, "drawer_index": 0, "hides_hair": false, "hides_hand": false, "hides_left_ear": false, "hides_right_ear": false, "uses_hair_color": false, "uses_team_color": false, "is_team_item": false}, + {"guid": "42aa7888-f0b9-4584-9cc1-966585c6b36e", "name": "(Shirt_Aviator)", "display_name": "Aviator", "outfit_type": 101, "outfit_set": 34, "drawer_index": 0, "hides_hair": false, "hides_hand": false, "hides_left_ear": false, "hides_right_ear": false, "uses_hair_color": false, "uses_team_color": false, "is_team_item": false}, + {"guid": "4308da04-af84-40bb-845a-7a9e1a2726ec", "name": "(Shirt_Mobster)", "display_name": "Mobster", "outfit_type": 101, "outfit_set": 0, "drawer_index": 0, "hides_hair": false, "hides_hand": false, "hides_left_ear": false, "hides_right_ear": false, "uses_hair_color": false, "uses_team_color": false, "is_team_item": false}, + {"guid": "43354e59-4938-44bc-81e0-4fba47d0ac12", "name": "(Eye_MardiGrasEyes2)", "display_name": "Mardi Gras Eyes2", "outfit_type": 10, "outfit_set": 0, "drawer_index": 0, "hides_hair": false, "hides_hand": false, "hides_left_ear": false, "hides_right_ear": false, "uses_hair_color": false, "uses_team_color": false, "is_team_item": false}, + {"guid": "439d1e24-d67f-4b8a-8fb3-8bf589d5a8ee", "name": "(Shirt_50sDress)", "display_name": "50s Dress", "outfit_type": 101, "outfit_set": 0, "drawer_index": 0, "hides_hair": false, "hides_hand": false, "hides_left_ear": false, "hides_right_ear": false, "uses_hair_color": false, "uses_team_color": false, "is_team_item": false}, + {"guid": "459ec62c-67db-4b72-9e29-0ad3ecd0b737", "name": "(Hat_HarnessHelmet)", "display_name": "Harness Helmet", "outfit_type": 0, "outfit_set": 0, "drawer_index": 0, "hides_hair": false, "hides_hand": false, "hides_left_ear": true, "hides_right_ear": true, "uses_hair_color": false, "uses_team_color": false, "is_team_item": false}, + {"guid": "45eaab67-19c2-4601-8f80-3565a4dceba4", "name": "(Hair_Pompadour)", "display_name": "Pompadour", "outfit_type": 2, "outfit_set": 0, "drawer_index": 0, "hides_hair": false, "hides_hand": false, "hides_left_ear": false, "hides_right_ear": false, "uses_hair_color": true, "uses_team_color": false, "is_team_item": false}, + {"guid": "45f5e714-8a5f-4385-a97f-675066167011", "name": "(SeventiesStache)", "display_name": "Seventies Stache", "outfit_type": 20, "outfit_set": 0, "drawer_index": 0, "hides_hair": false, "hides_hand": false, "hides_left_ear": false, "hides_right_ear": false, "uses_hair_color": true, "uses_team_color": false, "is_team_item": false}, + {"guid": "46cde502-86d6-4061-a80c-67458ffd4d60", "name": "(Neck_FancyCape)", "display_name": "Fancy Cape", "outfit_type": 100, "outfit_set": 0, "drawer_index": 0, "hides_hair": false, "hides_hand": false, "hides_left_ear": false, "hides_right_ear": false, "uses_hair_color": false, "uses_team_color": false, "is_team_item": false}, + {"guid": "473c672d-2b79-48a2-a49d-fd68160c5bde", "name": "(Wrist_FlapperGlove)", "display_name": "Flapper Glove", "outfit_type": 200, "outfit_set": 0, "drawer_index": 0, "hides_hair": false, "hides_hand": true, "hides_left_ear": false, "hides_right_ear": false, "uses_hair_color": false, "uses_team_color": false, "is_team_item": false}, + {"guid": "474db08a-d4dc-4625-9f5a-1a6ae0b70827", "name": "(Hat_BaseBall_FlatBill)", "display_name": "Base Ball Flat Bill", "outfit_type": 0, "outfit_set": 0, "drawer_index": 0, "hides_hair": false, "hides_hand": false, "hides_left_ear": false, "hides_right_ear": false, "uses_hair_color": false, "uses_team_color": false, "is_team_item": false}, + {"guid": "47629ed1-5378-427c-ad61-4b44d2fe90cc", "name": "(Eye_AviatorGoggles)", "display_name": "Aviator Goggles", "outfit_type": 10, "outfit_set": 34, "drawer_index": 0, "hides_hair": false, "hides_hand": false, "hides_left_ear": true, "hides_right_ear": true, "uses_hair_color": false, "uses_team_color": false, "is_team_item": false}, + {"guid": "4763436c-2dcc-482a-bf37-a6b16d7aae6e", "name": "(Wrist_SpaceMarineGlove)", "display_name": "Space Marine Glove", "outfit_type": 200, "outfit_set": 24, "drawer_index": 0, "hides_hair": false, "hides_hand": false, "hides_left_ear": false, "hides_right_ear": false, "uses_hair_color": false, "uses_team_color": false, "is_team_item": false}, + {"guid": "48ec7a6a-0c31-40d4-9179-66e3ba6505a1", "name": "(Hat_Beanie_Brim)", "display_name": "Beanie Brim", "outfit_type": 0, "outfit_set": 0, "drawer_index": 0, "hides_hair": false, "hides_hand": false, "hides_left_ear": false, "hides_right_ear": false, "uses_hair_color": false, "uses_team_color": false, "is_team_item": false}, + {"guid": "49397e16-1027-4286-9bc4-b59eae565ff0", "name": "(Hair_Jazzercise)", "display_name": "Jazzercise", "outfit_type": 2, "outfit_set": 9, "drawer_index": 0, "hides_hair": false, "hides_hand": false, "hides_left_ear": false, "hides_right_ear": false, "uses_hair_color": true, "uses_team_color": false, "is_team_item": false}, + {"guid": "49800740-33d6-4280-aefb-3497597c6366", "name": "(Hat_BandanaHeadband)", "display_name": "Bandana Headband", "outfit_type": 0, "outfit_set": 0, "drawer_index": 0, "hides_hair": false, "hides_hand": false, "hides_left_ear": false, "hides_right_ear": false, "uses_hair_color": false, "uses_team_color": false, "is_team_item": false}, + {"guid": "4a0e91a1-4562-4a82-92be-cb7491f1592b", "name": "(Shirt_Hoodie_FNAFTorso)", "display_name": "Hoodie FNAFTorso", "outfit_type": 101, "outfit_set": 0, "drawer_index": 0, "hides_hair": false, "hides_hand": false, "hides_left_ear": false, "hides_right_ear": false, "uses_hair_color": false, "uses_team_color": false, "is_team_item": false}, + {"guid": "4abc2ac4-e60c-49b1-9ac8-4d4751ed78ae", "name": "(Hat_DinoHat)", "display_name": "Dino Hat", "outfit_type": 0, "outfit_set": 0, "drawer_index": 0, "hides_hair": false, "hides_hand": false, "hides_left_ear": false, "hides_right_ear": false, "uses_hair_color": false, "uses_team_color": false, "is_team_item": false}, + {"guid": "4c673a35-c10f-4a8c-b8e0-96a50fe5b97f", "name": "(Neck_PegasusWings)", "display_name": "Pegasus Wings", "outfit_type": 100, "outfit_set": 0, "drawer_index": 0, "hides_hair": false, "hides_hand": false, "hides_left_ear": false, "hides_right_ear": false, "uses_hair_color": false, "uses_team_color": false, "is_team_item": false}, + {"guid": "4ce7a70f-15a0-4cad-98ee-5cbb40827338", "name": "(Shirt_SpaceMarine)", "display_name": "Space Marine", "outfit_type": 101, "outfit_set": 24, "drawer_index": 0, "hides_hair": false, "hides_hand": false, "hides_left_ear": false, "hides_right_ear": false, "uses_hair_color": false, "uses_team_color": false, "is_team_item": false}, + {"guid": "4d507dfa-4a99-4ac0-8537-229e9dc0eb4a", "name": "(FemaleTank)", "display_name": "Female Tank", "outfit_type": 101, "outfit_set": 0, "drawer_index": 0, "hides_hair": false, "hides_hand": false, "hides_left_ear": false, "hides_right_ear": false, "uses_hair_color": false, "uses_team_color": false, "is_team_item": false}, + {"guid": "4d533b3a-b5fa-446a-842d-92798ad5b493", "name": "(Wrist_DrumMajor_Glove)", "display_name": "Drum Major Glove", "outfit_type": 200, "outfit_set": 0, "drawer_index": 0, "hides_hair": false, "hides_hand": true, "hides_left_ear": false, "hides_right_ear": false, "uses_hair_color": false, "uses_team_color": false, "is_team_item": false}, + {"guid": "4e42e02e-33fd-4ff9-b74e-9bde26253c35", "name": "(Wrist_BogMonsterWrist)", "display_name": "Bog Monster Wrist", "outfit_type": 200, "outfit_set": 0, "drawer_index": 0, "hides_hair": false, "hides_hand": false, "hides_left_ear": false, "hides_right_ear": false, "uses_hair_color": false, "uses_team_color": false, "is_team_item": false}, + {"guid": "4ee2133d-c419-4fbc-9117-746d756a250d", "name": "(Belt_RRPlus_FannyPack)", "display_name": "RRPlus Fanny Pack", "outfit_type": 102, "outfit_set": 0, "drawer_index": 0, "hides_hair": false, "hides_hand": false, "hides_left_ear": false, "hides_right_ear": false, "uses_hair_color": false, "uses_team_color": false, "is_team_item": false}, + {"guid": "4fcb5b84-13a9-43e6-9680-72b17874ae53", "name": "(Hat_DeepSeaDiver)", "display_name": "Deep Sea Diver", "outfit_type": 0, "outfit_set": 0, "drawer_index": 0, "hides_hair": false, "hides_hand": false, "hides_left_ear": false, "hides_right_ear": false, "uses_hair_color": false, "uses_team_color": false, "is_team_item": false}, + {"guid": "50c9c6f8-2963-4ef3-95d5-e999a898269f", "name": "(Wrist_Saija_Glove)", "display_name": "Saija Glove", "outfit_type": 200, "outfit_set": 0, "drawer_index": 0, "hides_hair": false, "hides_hand": false, "hides_left_ear": false, "hides_right_ear": false, "uses_hair_color": false, "uses_team_color": false, "is_team_item": false}, + {"guid": "5154be09-bb8a-4917-95a0-51c91b6a4e09", "name": "(Shirt_Conductor_Contest)", "display_name": "Conductor Contest", "outfit_type": 101, "outfit_set": 0, "drawer_index": 0, "hides_hair": false, "hides_hand": false, "hides_left_ear": false, "hides_right_ear": false, "uses_hair_color": false, "uses_team_color": false, "is_team_item": false}, + {"guid": "515dfc36-7f7b-47bc-abee-380d68f41be6", "name": "(RedFlowerDress)", "display_name": "Red Flower Dress", "outfit_type": 101, "outfit_set": 0, "drawer_index": 0, "hides_hair": false, "hides_hand": false, "hides_left_ear": false, "hides_right_ear": false, "uses_hair_color": false, "uses_team_color": false, "is_team_item": false}, + {"guid": "529015c7-3b4a-4a73-b2c3-33103339b6e3", "name": "(Glasses_Smuggler)", "display_name": "Smuggler", "outfit_type": 10, "outfit_set": 25, "drawer_index": 0, "hides_hair": false, "hides_hand": false, "hides_left_ear": true, "hides_right_ear": true, "uses_hair_color": false, "uses_team_color": false, "is_team_item": false}, + {"guid": "52VsI_lKlkSUAOJlfDnyrQ", "name": "(Wrist_80sWindbreaker)", "display_name": "80s Windbreaker", "outfit_type": 200, "outfit_set": 0, "drawer_index": 0, "hides_hair": false, "hides_hand": false, "hides_left_ear": false, "hides_right_ear": false, "uses_hair_color": false, "uses_team_color": false, "is_team_item": false}, + {"guid": "5362d685-fc55-4b43-85a5-cc3d6cad3f69", "name": "(Shirt_GalaxyHoodie+)", "display_name": "Galaxy Hoodie+", "outfit_type": 101, "outfit_set": 0, "drawer_index": 0, "hides_hair": false, "hides_hand": false, "hides_left_ear": false, "hides_right_ear": false, "uses_hair_color": false, "uses_team_color": false, "is_team_item": false}, + {"guid": "57552652-d902-43b7-a68e-5c7da87582bc", "name": "(Shirt_Scuba)", "display_name": "Scuba", "outfit_type": 101, "outfit_set": 0, "drawer_index": 0, "hides_hair": false, "hides_hand": false, "hides_left_ear": false, "hides_right_ear": false, "uses_hair_color": false, "uses_team_color": false, "is_team_item": false}, + {"guid": "5800d2b5-75b8-442f-8b10-63bd3d73a1b8", "name": "(Wrist_FighterPilot)", "display_name": "Fighter Pilot", "outfit_type": 200, "outfit_set": 26, "drawer_index": 0, "hides_hair": false, "hides_hand": false, "hides_left_ear": false, "hides_right_ear": false, "uses_hair_color": false, "uses_team_color": false, "is_team_item": false}, + {"guid": "5873814e-c9a1-4042-b96d-2aa0e0d52e2a", "name": "(Shirt_InternationalThief_Contest)", "display_name": "International Thief Contest", "outfit_type": 101, "outfit_set": 0, "drawer_index": 0, "hides_hair": false, "hides_hand": false, "hides_left_ear": false, "hides_right_ear": false, "uses_hair_color": false, "uses_team_color": false, "is_team_item": false}, + {"guid": "587ca2bb-dd42-42bf-833f-4e2a97349866", "name": "(Neck_Doctor)", "display_name": "Doctor", "outfit_type": 100, "outfit_set": 29, "drawer_index": 0, "hides_hair": false, "hides_hand": false, "hides_left_ear": false, "hides_right_ear": false, "uses_hair_color": false, "uses_team_color": false, "is_team_item": false}, + {"guid": "59d9560d-e5af-4205-b860-3afea1cc31a9", "name": "(Hat_SpaceMarineHelmet)", "display_name": "Space Marine Helmet", "outfit_type": 0, "outfit_set": 24, "drawer_index": 0, "hides_hair": false, "hides_hand": false, "hides_left_ear": true, "hides_right_ear": true, "uses_hair_color": false, "uses_team_color": false, "is_team_item": false}, + {"guid": "5a38f864-7d1c-4989-8558-40b9668f78ef", "name": "(Hat_FootBall)", "display_name": "Foot Ball", "outfit_type": 0, "outfit_set": 0, "drawer_index": 0, "hides_hair": false, "hides_hand": false, "hides_left_ear": true, "hides_right_ear": true, "uses_hair_color": false, "uses_team_color": false, "is_team_item": false}, + {"guid": "5ac25e5a-8c8e-445c-82a4-7272baec1eb5", "name": "(Hat_Doctor)", "display_name": "Doctor", "outfit_type": 0, "outfit_set": 29, "drawer_index": 0, "hides_hair": false, "hides_hand": false, "hides_left_ear": false, "hides_right_ear": false, "uses_hair_color": false, "uses_team_color": false, "is_team_item": false}, + {"guid": "5accb7dc-90a3-46ee-bad2-8fd1861d50f1", "name": "(CowboyScarf)", "display_name": "Cowboy Scarf", "outfit_type": 100, "outfit_set": 0, "drawer_index": 0, "hides_hair": false, "hides_hand": false, "hides_left_ear": false, "hides_right_ear": false, "uses_hair_color": false, "uses_team_color": false, "is_team_item": false}, + {"guid": "5b01eaa3-0cac-40c0-b72a-b3f6a868ae0c", "name": "(Wrist_Archer_Glove)", "display_name": "Archer Glove", "outfit_type": 200, "outfit_set": 2, "drawer_index": 0, "hides_hair": false, "hides_hand": false, "hides_left_ear": false, "hides_right_ear": false, "uses_hair_color": false, "uses_team_color": false, "is_team_item": false}, + {"guid": "5b3dc472-773e-4714-a9d0-7e7bbf6bc695", "name": "(Neck_Scuba)", "display_name": "Scuba", "outfit_type": 100, "outfit_set": 0, "drawer_index": 0, "hides_hair": false, "hides_hand": false, "hides_left_ear": false, "hides_right_ear": false, "uses_hair_color": false, "uses_team_color": false, "is_team_item": false}, + {"guid": "5b6535f0-86cd-417a-bcce-a1ace9a5f260", "name": "(ArcherQuiver)", "display_name": "Archer Quiver", "outfit_type": 100, "outfit_set": 0, "drawer_index": 0, "hides_hair": false, "hides_hand": false, "hides_left_ear": false, "hides_right_ear": false, "uses_hair_color": false, "uses_team_color": false, "is_team_item": false}, + {"guid": "5bde836f-9343-4934-b01a-606f707020ef", "name": "(Hat_Sherlock)", "display_name": "Sherlock", "outfit_type": 0, "outfit_set": 0, "drawer_index": 0, "hides_hair": false, "hides_hand": false, "hides_left_ear": false, "hides_right_ear": false, "uses_hair_color": false, "uses_team_color": false, "is_team_item": false}, + {"guid": "5beeb4c4-f276-4eae-87aa-9302e45b05b7", "name": "(Hair_CornRows)", "display_name": "Corn Rows", "outfit_type": 2, "outfit_set": 0, "drawer_index": 0, "hides_hair": false, "hides_hand": false, "hides_left_ear": false, "hides_right_ear": false, "uses_hair_color": true, "uses_team_color": false, "is_team_item": false}, + {"guid": "5cd08cfb-c729-4c30-96d9-6a99bb934d91", "name": "(Neck_MaleCheerSash)", "display_name": "Male Cheer Sash", "outfit_type": 100, "outfit_set": 0, "drawer_index": 0, "hides_hair": false, "hides_hand": false, "hides_left_ear": false, "hides_right_ear": false, "uses_hair_color": false, "uses_team_color": false, "is_team_item": false}, + {"guid": "5d13a7a2-8213-40e6-90a6-efdd76a3fdcb", "name": "(Hair_DryadHair)", "display_name": "Dryad Hair", "outfit_type": 2, "outfit_set": 0, "drawer_index": 0, "hides_hair": false, "hides_hand": false, "hides_left_ear": false, "hides_right_ear": false, "uses_hair_color": true, "uses_team_color": false, "is_team_item": false}, + {"guid": "5d1c42a0-7e36-431a-8e78-a8b74046d153", "name": "(TiedScarf)", "display_name": "Tied Scarf", "outfit_type": 100, "outfit_set": 0, "drawer_index": 0, "hides_hair": false, "hides_hand": false, "hides_left_ear": false, "hides_right_ear": false, "uses_hair_color": false, "uses_team_color": false, "is_team_item": false}, + {"guid": "5e3929d3-d291-4e91-a0ba-2ef203e407d3", "name": "(Hat_FlatCap)", "display_name": "Flat Cap", "outfit_type": 0, "outfit_set": 0, "drawer_index": 0, "hides_hair": false, "hides_hand": false, "hides_left_ear": false, "hides_right_ear": false, "uses_hair_color": false, "uses_team_color": false, "is_team_item": false}, + {"guid": "5e9bbda6-d681-42b1-9cc4-5758acf34bc0", "name": "(Neck_NecklaceGold)", "display_name": "Necklace Gold", "outfit_type": 103, "outfit_set": 0, "drawer_index": 0, "hides_hair": false, "hides_hand": false, "hides_left_ear": false, "hides_right_ear": false, "uses_hair_color": false, "uses_team_color": false, "is_team_item": false}, + {"guid": "5f1bc3c2-dfbb-4258-9b10-608535e34db2", "name": "(Wrist_Zombie)", "display_name": "Zombie", "outfit_type": 200, "outfit_set": 0, "drawer_index": 0, "hides_hair": false, "hides_hand": false, "hides_left_ear": false, "hides_right_ear": false, "uses_hair_color": false, "uses_team_color": false, "is_team_item": false}, + {"guid": "5f492f8f-ab61-484a-9587-747132b54309", "name": "(Jazzercise)", "display_name": "Jazzercise", "outfit_type": 101, "outfit_set": 9, "drawer_index": 0, "hides_hair": false, "hides_hand": false, "hides_left_ear": false, "hides_right_ear": false, "uses_hair_color": false, "uses_team_color": false, "is_team_item": false}, + {"guid": "5f600892-aa67-484a-afc7-14c219d89c52", "name": "(Shirt_Flapper)", "display_name": "Flapper", "outfit_type": 101, "outfit_set": 0, "drawer_index": 0, "hides_hair": false, "hides_hand": false, "hides_left_ear": false, "hides_right_ear": false, "uses_hair_color": false, "uses_team_color": false, "is_team_item": false}, + {"guid": "5fwjFQoC_Uu9ChvF6uNGZw", "name": "(Eye_SkiBuddy)", "display_name": "Ski Buddy", "outfit_type": 10, "outfit_set": 0, "drawer_index": 0, "hides_hair": false, "hides_hand": false, "hides_left_ear": true, "hides_right_ear": true, "uses_hair_color": false, "uses_team_color": false, "is_team_item": false}, + {"guid": "5PUja-WX00Sme9yBwvDq-g", "name": "(Shirt_FigureSkaterDress)", "display_name": "Figure Skater Dress", "outfit_type": 101, "outfit_set": 0, "drawer_index": 0, "hides_hair": false, "hides_hand": false, "hides_left_ear": false, "hides_right_ear": false, "uses_hair_color": false, "uses_team_color": false, "is_team_item": false}, + {"guid": "60067e91-18b8-43ab-ae20-a8ea74c757bf", "name": "(Neck_FemaleCheerSash)", "display_name": "Female Cheer Sash", "outfit_type": 100, "outfit_set": 0, "drawer_index": 0, "hides_hair": false, "hides_hand": false, "hides_left_ear": false, "hides_right_ear": false, "uses_hair_color": false, "uses_team_color": false, "is_team_item": false}, + {"guid": "61969200-2790-41e0-bccb-d9feff8c7b7d", "name": "(Wrist_ElfWrist)", "display_name": "Elf Wrist", "outfit_type": 200, "outfit_set": 0, "drawer_index": 0, "hides_hair": false, "hides_hand": false, "hides_left_ear": false, "hides_right_ear": false, "uses_hair_color": false, "uses_team_color": false, "is_team_item": false}, + {"guid": "61ba3c90-c81e-4deb-bc79-50c0f1fe3e83", "name": "(Beard_Lumberjack)", "display_name": "Lumberjack", "outfit_type": 20, "outfit_set": 3, "drawer_index": 0, "hides_hair": false, "hides_hand": false, "hides_left_ear": false, "hides_right_ear": false, "uses_hair_color": true, "uses_team_color": false, "is_team_item": false}, + {"guid": "62ce4109-8dee-4895-bf1b-bfa143db4c7e", "name": "(BlueJacket)", "display_name": "Blue Jacket", "outfit_type": 101, "outfit_set": 0, "drawer_index": 0, "hides_hair": false, "hides_hand": false, "hides_left_ear": false, "hides_right_ear": false, "uses_hair_color": false, "uses_team_color": false, "is_team_item": false}, + {"guid": "6423f23e-dde3-4428-96f5-6091027c92cb", "name": "(Shirt_CunningGi)", "display_name": "Cunning Gi", "outfit_type": 101, "outfit_set": 0, "drawer_index": 0, "hides_hair": false, "hides_hand": false, "hides_left_ear": false, "hides_right_ear": false, "uses_hair_color": false, "uses_team_color": false, "is_team_item": false}, + {"guid": "6427bd57-fcb2-420f-8b3e-d1da43470b84", "name": "(Glasses_Sun)", "display_name": "Sun", "outfit_type": 10, "outfit_set": 0, "drawer_index": 0, "hides_hair": false, "hides_hand": false, "hides_left_ear": false, "hides_right_ear": false, "uses_hair_color": false, "uses_team_color": false, "is_team_item": false}, + {"guid": "64ad8789-1645-45f7-819a-a413ff7c9622", "name": "(Wrist_PaintballGlove)", "display_name": "Paintball Glove", "outfit_type": 200, "outfit_set": 0, "drawer_index": 0, "hides_hair": false, "hides_hand": false, "hides_left_ear": false, "hides_right_ear": false, "uses_hair_color": false, "uses_team_color": false, "is_team_item": false}, + {"guid": "64fddd4e-3d59-43df-bb39-671e18953c0f", "name": "(Shirt_EveningCoat)", "display_name": "Evening Coat", "outfit_type": 101, "outfit_set": 0, "drawer_index": 0, "hides_hair": false, "hides_hand": false, "hides_left_ear": false, "hides_right_ear": false, "uses_hair_color": false, "uses_team_color": false, "is_team_item": false}, + {"guid": "657efe89-620a-46a2-8b58-cafd855c7cd5", "name": "(FemaleCheerTorso)", "display_name": "Female Cheer Torso", "outfit_type": 101, "outfit_set": 0, "drawer_index": 0, "hides_hair": false, "hides_hand": false, "hides_left_ear": false, "hides_right_ear": false, "uses_hair_color": false, "uses_team_color": false, "is_team_item": false}, + {"guid": "664485e9-a159-4fcb-b32c-2b114b4a1218", "name": "(Shirt_Scallywag)", "display_name": "Scallywag", "outfit_type": 101, "outfit_set": 0, "drawer_index": 0, "hides_hair": false, "hides_hand": false, "hides_left_ear": false, "hides_right_ear": false, "uses_hair_color": false, "uses_team_color": false, "is_team_item": false}, + {"guid": "66f8f55d-cad4-4afb-93e8-b6bdf04b37b3", "name": "(Hat_Green_Sequin_HeadBackBow)", "display_name": "Green Sequin Head Back Bow", "outfit_type": 0, "outfit_set": 0, "drawer_index": 0, "hides_hair": false, "hides_hand": false, "hides_left_ear": false, "hides_right_ear": false, "uses_hair_color": false, "uses_team_color": false, "is_team_item": false}, + {"guid": "67117b3d-0669-4cf3-8a4f-066cabd4d8b1", "name": "(LuchadorCape)", "display_name": "Luchador Cape", "outfit_type": 100, "outfit_set": 0, "drawer_index": 0, "hides_hair": false, "hides_hand": false, "hides_left_ear": false, "hides_right_ear": false, "uses_hair_color": false, "uses_team_color": false, "is_team_item": false}, + {"guid": "686ac516-630f-4989-9c99-0b8d055a00f9", "name": "(Hat_ElfHat)", "display_name": "Elf Hat", "outfit_type": 0, "outfit_set": 0, "drawer_index": 0, "hides_hair": false, "hides_hand": false, "hides_left_ear": false, "hides_right_ear": false, "uses_hair_color": false, "uses_team_color": false, "is_team_item": false}, + {"guid": "6930ce13-4be4-4ab9-9817-667bd261ffc3", "name": "(Shirt_Bishop)", "display_name": "Bishop", "outfit_type": 101, "outfit_set": 0, "drawer_index": 0, "hides_hair": false, "hides_hand": false, "hides_left_ear": false, "hides_right_ear": false, "uses_hair_color": false, "uses_team_color": false, "is_team_item": false}, + {"guid": "6a98bbb6-bbe0-4dc3-97a9-e253dc506a2b", "name": "(Wrist_Skeleton_FingerlessGlove)", "display_name": "Skeleton Fingerless Glove", "outfit_type": 200, "outfit_set": 0, "drawer_index": 0, "hides_hair": false, "hides_hand": false, "hides_left_ear": false, "hides_right_ear": false, "uses_hair_color": false, "uses_team_color": false, "is_team_item": false}, + {"guid": "6b337a35-95d9-4b3a-9891-d9fbb06262aa", "name": "(Mouth_CurlyMustache)", "display_name": "Curly Mustache", "outfit_type": 20, "outfit_set": 0, "drawer_index": 0, "hides_hair": false, "hides_hand": false, "hides_left_ear": false, "hides_right_ear": false, "uses_hair_color": true, "uses_team_color": false, "is_team_item": false}, + {"guid": "6b9e022c-0b68-48fd-8eca-da8573c18900", "name": "(LongScarf)", "display_name": "Long Scarf", "outfit_type": 100, "outfit_set": 0, "drawer_index": 0, "hides_hair": false, "hides_hand": false, "hides_left_ear": false, "hides_right_ear": false, "uses_hair_color": false, "uses_team_color": false, "is_team_item": false}, + {"guid": "6d36091e-7083-4e3d-8729-94612602c8cb", "name": "(Hat_WitchHunter)", "display_name": "Witch Hunter", "outfit_type": 0, "outfit_set": 0, "drawer_index": 0, "hides_hair": false, "hides_hand": false, "hides_left_ear": false, "hides_right_ear": false, "uses_hair_color": false, "uses_team_color": false, "is_team_item": false}, + {"guid": "6d48c545-22bb-46c1-a29d-0a38af387143", "name": "(WristBand_Team)", "display_name": "Team", "outfit_type": 203, "outfit_set": 0, "drawer_index": 0, "hides_hair": false, "hides_hand": false, "hides_left_ear": false, "hides_right_ear": false, "uses_hair_color": false, "uses_team_color": false, "is_team_item": true}, + {"guid": "6d815b35-6f68-4ed4-817d-70f141e1a571", "name": "(OrangeCollarDress)", "display_name": "Orange Collar Dress", "outfit_type": 101, "outfit_set": 0, "drawer_index": 0, "hides_hair": false, "hides_hand": false, "hides_left_ear": false, "hides_right_ear": false, "uses_hair_color": false, "uses_team_color": false, "is_team_item": false}, + {"guid": "6dd261d8-9806-4dac-81c4-a58f6d6a7f9a", "name": "(Neck_Bowtie_Gold_Sequin)", "display_name": "Bowtie Gold Sequin", "outfit_type": 100, "outfit_set": 0, "drawer_index": 0, "hides_hair": false, "hides_hand": false, "hides_left_ear": false, "hides_right_ear": false, "uses_hair_color": false, "uses_team_color": false, "is_team_item": false}, + {"guid": "6fccd476-c621-4f3f-b2e3-45c95be912b5", "name": "(Dress_JacketDress)", "display_name": "Jacket Dress", "outfit_type": 101, "outfit_set": 0, "drawer_index": 0, "hides_hair": false, "hides_hand": false, "hides_left_ear": false, "hides_right_ear": false, "uses_hair_color": false, "uses_team_color": false, "is_team_item": false}, + {"guid": "6QT99hx6DE6t9cKAs61i3w", "name": "(Hat_SkiBuddy)", "display_name": "Ski Buddy", "outfit_type": 0, "outfit_set": 0, "drawer_index": 0, "hides_hair": false, "hides_hand": false, "hides_left_ear": true, "hides_right_ear": true, "uses_hair_color": false, "uses_team_color": false, "is_team_item": false}, + {"guid": "6RhwWExXekaYpK0NGHSXGQ", "name": "(Shirt_SkiBuddy)", "display_name": "Ski Buddy", "outfit_type": 101, "outfit_set": 0, "drawer_index": 0, "hides_hair": false, "hides_hand": false, "hides_left_ear": false, "hides_right_ear": false, "uses_hair_color": false, "uses_team_color": false, "is_team_item": false}, + {"guid": "703f1987-2b31-4b6f-bab7-1939b1105ac0", "name": "(Hat_Sun)", "display_name": "Sun", "outfit_type": 0, "outfit_set": 0, "drawer_index": 0, "hides_hair": false, "hides_hand": false, "hides_left_ear": false, "hides_right_ear": false, "uses_hair_color": false, "uses_team_color": false, "is_team_item": false}, + {"guid": "71014872-d879-4858-9d68-58da3c673d8b", "name": "(Hat_Bike)", "display_name": "Bike", "outfit_type": 0, "outfit_set": 0, "drawer_index": 0, "hides_hair": false, "hides_hand": false, "hides_left_ear": false, "hides_right_ear": false, "uses_hair_color": false, "uses_team_color": false, "is_team_item": false}, + {"guid": "7134c60b-0640-463a-8d97-caa9184bef93", "name": "(Hat_LacrosseHelmet)", "display_name": "Lacrosse Helmet", "outfit_type": 0, "outfit_set": 15, "drawer_index": 0, "hides_hair": false, "hides_hand": false, "hides_left_ear": true, "hides_right_ear": true, "uses_hair_color": false, "uses_team_color": false, "is_team_item": false}, + {"guid": "71921831-ba6f-408b-a00e-2fd97663636f", "name": "(Wrist_WristTape)", "display_name": "Wrist Tape", "outfit_type": 200, "outfit_set": 0, "drawer_index": 0, "hides_hair": false, "hides_hand": false, "hides_left_ear": false, "hides_right_ear": false, "uses_hair_color": false, "uses_team_color": false, "is_team_item": false}, + {"guid": "71fd5353-b439-4a23-b471-bfb99b8154d7", "name": "(Hat_BunnyEars)", "display_name": "Bunny Ears", "outfit_type": 0, "outfit_set": 0, "drawer_index": 0, "hides_hair": false, "hides_hand": false, "hides_left_ear": false, "hides_right_ear": false, "uses_hair_color": false, "uses_team_color": false, "is_team_item": false}, + {"guid": "739fd42a-8a8c-44a5-afa3-e0974802c6ae", "name": "(Shirt_KnightTorso)", "display_name": "Knight Torso", "outfit_type": 101, "outfit_set": 1, "drawer_index": 0, "hides_hair": false, "hides_hand": false, "hides_left_ear": false, "hides_right_ear": false, "uses_hair_color": false, "uses_team_color": false, "is_team_item": false}, + {"guid": "73a119ab-d2c8-4da9-98b2-29edbb38e2c2", "name": "(Dress_FormalDress_RedSequin)", "display_name": "Formal Dress Red Sequin", "outfit_type": 101, "outfit_set": 0, "drawer_index": 0, "hides_hair": false, "hides_hand": false, "hides_left_ear": false, "hides_right_ear": false, "uses_hair_color": false, "uses_team_color": false, "is_team_item": false}, + {"guid": "73c0e2ba-cba1-482a-89d5-1508997aceeb", "name": "(Dress_FormalDress_SilverSequin)", "display_name": "Formal Dress Silver Sequin", "outfit_type": 101, "outfit_set": 0, "drawer_index": 0, "hides_hair": false, "hides_hand": false, "hides_left_ear": false, "hides_right_ear": false, "uses_hair_color": false, "uses_team_color": false, "is_team_item": false}, + {"guid": "74365234-cb72-409d-8cf5-9a7209f707e1", "name": "(Wrist_SafariGlove)", "display_name": "Safari Glove", "outfit_type": 200, "outfit_set": 33, "drawer_index": 0, "hides_hair": false, "hides_hand": true, "hides_left_ear": false, "hides_right_ear": false, "uses_hair_color": false, "uses_team_color": false, "is_team_item": false}, + {"guid": "745c5110-104e-4b37-aabf-7c690fd54811", "name": "(PurpleJacket)", "display_name": "Purple Jacket", "outfit_type": 101, "outfit_set": 0, "drawer_index": 0, "hides_hair": false, "hides_hand": false, "hides_left_ear": false, "hides_right_ear": false, "uses_hair_color": false, "uses_team_color": false, "is_team_item": false}, + {"guid": "74b91f2b-d4d3-4e41-8946-b1296ab7a773", "name": "(Shirt_Tennis)", "display_name": "Tennis", "outfit_type": 101, "outfit_set": 0, "drawer_index": 0, "hides_hair": false, "hides_hand": false, "hides_left_ear": false, "hides_right_ear": false, "uses_hair_color": false, "uses_team_color": false, "is_team_item": false}, + {"guid": "74c65554-0d15-409b-b9a8-0b2620a7d7e2", "name": "(Shirt_MartyRecFly)", "display_name": "Marty Rec Fly", "outfit_type": 101, "outfit_set": 0, "drawer_index": 0, "hides_hair": false, "hides_hand": false, "hides_left_ear": false, "hides_right_ear": false, "uses_hair_color": false, "uses_team_color": false, "is_team_item": false}, + {"guid": "74e6878f-94e0-41f2-93ea-de6412238385", "name": "(PartyBand)", "display_name": "Party Band", "outfit_type": 200, "outfit_set": 0, "drawer_index": 0, "hides_hair": false, "hides_hand": false, "hides_left_ear": false, "hides_right_ear": false, "uses_hair_color": false, "uses_team_color": false, "is_team_item": false}, + {"guid": "7553d851-1986-4325-870d-e425dfb7d007", "name": "(Mouth_BeardFarmer)", "display_name": "Beard Farmer", "outfit_type": 20, "outfit_set": 0, "drawer_index": 0, "hides_hair": false, "hides_hand": false, "hides_left_ear": false, "hides_right_ear": false, "uses_hair_color": true, "uses_team_color": false, "is_team_item": false}, + {"guid": "75-HXZBOAEiDBqclnOn8YQ", "name": "(Belt_PoolFloatie)", "display_name": "Pool Floatie", "outfit_type": 102, "outfit_set": 0, "drawer_index": 0, "hides_hair": false, "hides_hand": false, "hides_left_ear": false, "hides_right_ear": false, "uses_hair_color": false, "uses_team_color": false, "is_team_item": false}, + {"guid": "76369aef-aeda-46d2-996a-cd00594d0543", "name": "(Hat_Mobster)", "display_name": "Mobster", "outfit_type": 0, "outfit_set": 0, "drawer_index": 0, "hides_hair": false, "hides_hand": false, "hides_left_ear": false, "hides_right_ear": false, "uses_hair_color": false, "uses_team_color": false, "is_team_item": false}, + {"guid": "767684e7-0eec-4f6e-ad58-3d5ae715206e", "name": "(Eye_WackySunglasses)", "display_name": "Wacky Sunglasses", "outfit_type": 10, "outfit_set": 0, "drawer_index": 0, "hides_hair": false, "hides_hand": false, "hides_left_ear": false, "hides_right_ear": false, "uses_hair_color": false, "uses_team_color": false, "is_team_item": false}, + {"guid": "77c37481-a185-47ff-af55-65f9fdfcf9e5", "name": "(FurCoat)", "display_name": "Fur Coat", "outfit_type": 101, "outfit_set": 0, "drawer_index": 0, "hides_hair": false, "hides_hand": false, "hides_left_ear": false, "hides_right_ear": false, "uses_hair_color": false, "uses_team_color": false, "is_team_item": false}, + {"guid": "77d3c585-4928-4471-a425-89036efe7299", "name": "(Hair_Spiky)", "display_name": "Spiky", "outfit_type": 2, "outfit_set": 0, "drawer_index": 0, "hides_hair": false, "hides_hand": false, "hides_left_ear": false, "hides_right_ear": false, "uses_hair_color": true, "uses_team_color": false, "is_team_item": false}, + {"guid": "79b90274-6eec-4664-acfb-4a123334661e", "name": "(Hair_PigTails)", "display_name": "Pig Tails", "outfit_type": 2, "outfit_set": 0, "drawer_index": 0, "hides_hair": false, "hides_hand": false, "hides_left_ear": true, "hides_right_ear": true, "uses_hair_color": true, "uses_team_color": false, "is_team_item": false}, + {"guid": "7a6fe0e6-f930-4fb8-9b2f-4c857a273dd9", "name": "(Wrist_BikeGlove)", "display_name": "Bike Glove", "outfit_type": 200, "outfit_set": 0, "drawer_index": 0, "hides_hair": false, "hides_hand": false, "hides_left_ear": false, "hides_right_ear": false, "uses_hair_color": false, "uses_team_color": false, "is_team_item": false}, + {"guid": "7a730848-ab20-445d-b416-1a72cab49324", "name": "(Jersey_Dev)", "display_name": "Dev", "outfit_type": 101, "outfit_set": 0, "drawer_index": 0, "hides_hair": false, "hides_hand": false, "hides_left_ear": false, "hides_right_ear": false, "uses_hair_color": false, "uses_team_color": false, "is_team_item": false}, + {"guid": "7a7d8a06-7982-4bcb-b7bd-4d03b48889b4", "name": "(Hat_Aviator)", "display_name": "Aviator", "outfit_type": 0, "outfit_set": 34, "drawer_index": 0, "hides_hair": false, "hides_hand": false, "hides_left_ear": false, "hides_right_ear": false, "uses_hair_color": false, "uses_team_color": false, "is_team_item": false}, + {"guid": "7a98503f-0460-4d25-a0d5-72a32db8f4c2", "name": "(Neck_Belt_PoolFloatie_Turtle)", "display_name": "Belt Pool Floatie Turtle", "outfit_type": 102, "outfit_set": 0, "drawer_index": 0, "hides_hair": false, "hides_hand": false, "hides_left_ear": false, "hides_right_ear": false, "uses_hair_color": false, "uses_team_color": false, "is_team_item": false}, + {"guid": "7aa994d2-6499-4918-ab2a-2531c17e27a7", "name": "(Dress_Luau_Dress)", "display_name": "Luau Dress", "outfit_type": 101, "outfit_set": 0, "drawer_index": 0, "hides_hair": false, "hides_hand": false, "hides_left_ear": false, "hides_right_ear": false, "uses_hair_color": false, "uses_team_color": false, "is_team_item": false}, + {"guid": "7b857a8c-92ad-4028-a2c2-b3c20cdab5f2", "name": "(Shirt_Jersey)", "display_name": "Jersey", "outfit_type": 101, "outfit_set": 0, "drawer_index": 0, "hides_hair": false, "hides_hand": false, "hides_left_ear": false, "hides_right_ear": false, "uses_hair_color": false, "uses_team_color": false, "is_team_item": false}, + {"guid": "7be2eafc-21ea-4266-ae64-8888c06798d6", "name": "(Neck_Pearls)", "display_name": "Pearls", "outfit_type": 103, "outfit_set": 0, "drawer_index": 0, "hides_hair": false, "hides_hand": false, "hides_left_ear": false, "hides_right_ear": false, "uses_hair_color": false, "uses_team_color": false, "is_team_item": false}, + {"guid": "7c1552a6-bc36-46e6-884c-1de6b8111a92", "name": "(Hat_ScoutRaccoon)", "display_name": "Scout Raccoon", "outfit_type": 0, "outfit_set": 0, "drawer_index": 0, "hides_hair": false, "hides_hand": false, "hides_left_ear": false, "hides_right_ear": false, "uses_hair_color": false, "uses_team_color": false, "is_team_item": false}, + {"guid": "7db6b49f-3e2a-4da8-bdfa-e9ad9ebc5ba6", "name": "(Eye_MirroredGlasses)", "display_name": "Mirrored Glasses", "outfit_type": 10, "outfit_set": 26, "drawer_index": 0, "hides_hair": false, "hides_hand": false, "hides_left_ear": false, "hides_right_ear": false, "uses_hair_color": false, "uses_team_color": false, "is_team_item": false}, + {"guid": "7dc6c916-b887-4929-93b5-dcea3022e6a9", "name": "(Hat_TopHat_Contest)", "display_name": "Top Hat Contest", "outfit_type": 0, "outfit_set": 0, "drawer_index": 0, "hides_hair": false, "hides_hand": false, "hides_left_ear": false, "hides_right_ear": false, "uses_hair_color": false, "uses_team_color": false, "is_team_item": false}, + {"guid": "7dd6f7b0-7ba0-429f-a04f-e32d3a79ee61", "name": "(Hair_WavyShort)", "display_name": "Wavy Short", "outfit_type": 2, "outfit_set": 0, "drawer_index": 0, "hides_hair": false, "hides_hand": false, "hides_left_ear": true, "hides_right_ear": true, "uses_hair_color": true, "uses_team_color": false, "is_team_item": false}, + {"guid": "7e22a4bd-3e87-4d50-8843-9e2e3cf45285", "name": "(Shirt_Turtleneck)", "display_name": "Turtleneck", "outfit_type": 101, "outfit_set": 0, "drawer_index": 0, "hides_hair": false, "hides_hand": false, "hides_left_ear": false, "hides_right_ear": false, "uses_hair_color": false, "uses_team_color": false, "is_team_item": false}, + {"guid": "7e6e24b6-6f9c-4315-b457-73eba52d824c", "name": "(Wrist_Pumpkin)", "display_name": "Pumpkin", "outfit_type": 200, "outfit_set": 0, "drawer_index": 0, "hides_hair": false, "hides_hand": false, "hides_left_ear": false, "hides_right_ear": false, "uses_hair_color": false, "uses_team_color": false, "is_team_item": false}, + {"guid": "7LSm-xCYakmgcAr--Fn-JA", "name": "(Shirt_Witch)", "display_name": "Witch", "outfit_type": 101, "outfit_set": 0, "drawer_index": 0, "hides_hair": false, "hides_hand": false, "hides_left_ear": false, "hides_right_ear": false, "uses_hair_color": false, "uses_team_color": false, "is_team_item": false}, + {"guid": "7VQvz9sbzUWvGalxd9rISg", "name": "(Shirt_BrideofFrank)", "display_name": "Brideof Frank", "outfit_type": 101, "outfit_set": 0, "drawer_index": 0, "hides_hair": false, "hides_hand": false, "hides_left_ear": false, "hides_right_ear": false, "uses_hair_color": false, "uses_team_color": false, "is_team_item": false}, + {"guid": "8263a9ca-8c57-4e81-b3e3-24f2ca19a70a", "name": "(Hat_Construction)", "display_name": "Construction", "outfit_type": 0, "outfit_set": 0, "drawer_index": 0, "hides_hair": false, "hides_hand": false, "hides_left_ear": false, "hides_right_ear": false, "uses_hair_color": false, "uses_team_color": false, "is_team_item": false}, + {"guid": "833e8ab3-9b54-4e15-8f4d-ee1bac5823c9", "name": "(Hat_WerewolfHat)", "display_name": "Werewolf Hat", "outfit_type": 0, "outfit_set": 0, "drawer_index": 0, "hides_hair": false, "hides_hand": false, "hides_left_ear": false, "hides_right_ear": false, "uses_hair_color": false, "uses_team_color": false, "is_team_item": false}, + {"guid": "83be5ba4-525a-4781-a5f6-839c22e4d1d3", "name": "(Bowtie)", "display_name": "Bowtie", "outfit_type": 100, "outfit_set": 0, "drawer_index": 0, "hides_hair": false, "hides_hand": false, "hides_left_ear": false, "hides_right_ear": false, "uses_hair_color": false, "uses_team_color": false, "is_team_item": false}, + {"guid": "843e3364-a743-4dfc-a990-0436e47b8c10", "name": "(BikeJacket)", "display_name": "Bike Jacket", "outfit_type": 101, "outfit_set": 0, "drawer_index": 0, "hides_hair": false, "hides_hand": false, "hides_left_ear": false, "hides_right_ear": false, "uses_hair_color": false, "uses_team_color": false, "is_team_item": false}, + {"guid": "84574356-98ec-415f-8668-82cb52016bd3", "name": "(Neck_Belt_PoolFloatie_ManewithLegs)", "display_name": "Belt Pool Floatie Manewith Legs", "outfit_type": 102, "outfit_set": 0, "drawer_index": 0, "hides_hair": false, "hides_hand": false, "hides_left_ear": false, "hides_right_ear": false, "uses_hair_color": false, "uses_team_color": false, "is_team_item": false}, + {"guid": "84600de6-93fc-4a89-961e-1f18f4b80643", "name": "(Shirt_Pumpkin)", "display_name": "Pumpkin", "outfit_type": 101, "outfit_set": 0, "drawer_index": 0, "hides_hair": false, "hides_hand": false, "hides_left_ear": false, "hides_right_ear": false, "uses_hair_color": false, "uses_team_color": false, "is_team_item": false}, + {"guid": "84cd594c-1cd8-4b4d-8409-85c8fd5fb02a", "name": "(PinkStollDress)", "display_name": "Pink Stoll Dress", "outfit_type": 101, "outfit_set": 0, "drawer_index": 0, "hides_hair": false, "hides_hand": false, "hides_left_ear": false, "hides_right_ear": false, "uses_hair_color": false, "uses_team_color": false, "is_team_item": false}, + {"guid": "8595c7b2-4d1c-4eae-af9a-8180d475d931", "name": "(Dress_DryadDress)", "display_name": "Dryad Dress", "outfit_type": 101, "outfit_set": 0, "drawer_index": 0, "hides_hair": false, "hides_hand": false, "hides_left_ear": false, "hides_right_ear": false, "uses_hair_color": false, "uses_team_color": false, "is_team_item": false}, + {"guid": "86af3a97-18d8-4dda-a2f2-cfd3b4224254", "name": "(Neck_GreenSequin_Bowtie)", "display_name": "Green Sequin Bowtie", "outfit_type": 100, "outfit_set": 0, "drawer_index": 0, "hides_hair": false, "hides_hand": false, "hides_left_ear": false, "hides_right_ear": false, "uses_hair_color": false, "uses_team_color": false, "is_team_item": false}, + {"guid": "86dd9ff9-643f-4f11-8243-93ad948db4cc", "name": "(Neck_NewYearsSash)", "display_name": "New Years Sash", "outfit_type": 100, "outfit_set": 0, "drawer_index": 0, "hides_hair": false, "hides_hand": false, "hides_left_ear": false, "hides_right_ear": false, "uses_hair_color": false, "uses_team_color": false, "is_team_item": false}, + {"guid": "87792b6e-1468-45e1-b28c-83f27ada7299", "name": "(Eye_MirrorShades_PinkGreen) Variant", "display_name": "Mirror Shades Pink Green) Variant", "outfit_type": 10, "outfit_set": 0, "drawer_index": 0, "hides_hair": false, "hides_hand": false, "hides_left_ear": false, "hides_right_ear": false, "uses_hair_color": false, "uses_team_color": false, "is_team_item": false}, + {"guid": "880a3cc0-7407-4b61-b759-f9dd890fe9e5", "name": "(Hair_Bob)", "display_name": "Bob", "outfit_type": 2, "outfit_set": 0, "drawer_index": 0, "hides_hair": false, "hides_hand": false, "hides_left_ear": true, "hides_right_ear": true, "uses_hair_color": true, "uses_team_color": false, "is_team_item": false}, + {"guid": "88739d79-aeb9-471a-b25b-776f46bba9f6", "name": "(Wrist_AstronautGlove)", "display_name": "Astronaut Glove", "outfit_type": 200, "outfit_set": 0, "drawer_index": 0, "hides_hair": false, "hides_hand": false, "hides_left_ear": false, "hides_right_ear": false, "uses_hair_color": false, "uses_team_color": false, "is_team_item": false}, + {"guid": "88ae6d12-6d39-4770-ac2a-f8c4602a10a5", "name": "(Hat_SpaceSoldierHelmet_Blue)", "display_name": "Space Soldier Helmet Blue", "outfit_type": 0, "outfit_set": 0, "drawer_index": 0, "hides_hair": true, "hides_hand": false, "hides_left_ear": false, "hides_right_ear": false, "uses_hair_color": false, "uses_team_color": false, "is_team_item": false}, + {"guid": "88b6ddeb-a455-460d-91d9-a4569ef6903c", "name": "(Ear_SquareEarring)", "display_name": "Square Earring", "outfit_type": 3, "outfit_set": 0, "drawer_index": 0, "hides_hair": false, "hides_hand": false, "hides_left_ear": false, "hides_right_ear": false, "uses_hair_color": false, "uses_team_color": false, "is_team_item": false}, + {"guid": "896c2491-2f96-4986-9cbd-b3b31ef5d8c5", "name": "(EquestrianJacket)", "display_name": "Equestrian Jacket", "outfit_type": 101, "outfit_set": 0, "drawer_index": 0, "hides_hair": false, "hides_hand": false, "hides_left_ear": false, "hides_right_ear": false, "uses_hair_color": false, "uses_team_color": false, "is_team_item": false}, + {"guid": "899f1d17-eee1-4b7d-a112-fbbba597e738", "name": "(Hat_Squid)", "display_name": "Squid", "outfit_type": 0, "outfit_set": 0, "drawer_index": 0, "hides_hair": false, "hides_hand": false, "hides_left_ear": false, "hides_right_ear": false, "uses_hair_color": false, "uses_team_color": false, "is_team_item": false}, + {"guid": "8aa79563-ace1-4ba7-ad0c-f3210a78142f", "name": "(FemaleTShirt)", "display_name": "Female TShirt", "outfit_type": 101, "outfit_set": 0, "drawer_index": 0, "hides_hair": false, "hides_hand": false, "hides_left_ear": false, "hides_right_ear": false, "uses_hair_color": false, "uses_team_color": false, "is_team_item": false}, + {"guid": "8b065e2a-b106-4f11-b415-4c47ce87993e", "name": "(Shirt_TruckerJacket)", "display_name": "Trucker Jacket", "outfit_type": 101, "outfit_set": 0, "drawer_index": 0, "hides_hair": false, "hides_hand": false, "hides_left_ear": false, "hides_right_ear": false, "uses_hair_color": false, "uses_team_color": false, "is_team_item": false}, + {"guid": "8b9f1413-e786-4a30-946c-9292f207875a", "name": "(Hair_PulpFiction)", "display_name": "Pulp Fiction", "outfit_type": 2, "outfit_set": 0, "drawer_index": 0, "hides_hair": false, "hides_hand": false, "hides_left_ear": true, "hides_right_ear": true, "uses_hair_color": true, "uses_team_color": false, "is_team_item": false}, + {"guid": "8c35c804-e8d5-49d2-8d5a-ea19fb70bfa6", "name": "(Hair_Bun_Pencil)", "display_name": "Bun Pencil", "outfit_type": 2, "outfit_set": 0, "drawer_index": 0, "hides_hair": false, "hides_hand": false, "hides_left_ear": false, "hides_right_ear": false, "uses_hair_color": true, "uses_team_color": false, "is_team_item": false}, + {"guid": "8c51fc1a-b1ad-4002-a13c-4313abd92c7f", "name": "(Hat_Visor)", "display_name": "Visor", "outfit_type": 0, "outfit_set": 0, "drawer_index": 0, "hides_hair": false, "hides_hand": false, "hides_left_ear": false, "hides_right_ear": false, "uses_hair_color": false, "uses_team_color": false, "is_team_item": false}, + {"guid": "8d10cc78-6b00-45f3-affb-205e9cc5b03f", "name": "(Beard_Close)", "display_name": "Close", "outfit_type": 20, "outfit_set": 0, "drawer_index": 0, "hides_hair": false, "hides_hand": false, "hides_left_ear": false, "hides_right_ear": false, "uses_hair_color": true, "uses_team_color": false, "is_team_item": false}, + {"guid": "8d17031a-c0a5-4145-92b7-1f0c2d8f0ce6", "name": "(Hat_Scallywag)", "display_name": "Scallywag", "outfit_type": 0, "outfit_set": 0, "drawer_index": 0, "hides_hair": false, "hides_hand": false, "hides_left_ear": false, "hides_right_ear": false, "uses_hair_color": false, "uses_team_color": false, "is_team_item": false}, + {"guid": "8d703940-224a-4b0c-a04d-f365550071e1", "name": "(Hat_Hockey)", "display_name": "Hockey", "outfit_type": 0, "outfit_set": 0, "drawer_index": 0, "hides_hair": false, "hides_hand": false, "hides_left_ear": false, "hides_right_ear": false, "uses_hair_color": false, "uses_team_color": false, "is_team_item": false}, + {"guid": "8d97303c-5138-4da1-a74d-378da19d11cb", "name": "(Neck_DeepSeaDiver)", "display_name": "Deep Sea Diver", "outfit_type": 100, "outfit_set": 0, "drawer_index": 0, "hides_hair": false, "hides_hand": false, "hides_left_ear": false, "hides_right_ear": false, "uses_hair_color": false, "uses_team_color": false, "is_team_item": false}, + {"guid": "8df990d9-141a-4757-b4f2-138d5e68ab74", "name": "(Hat_NewYearsHat)", "display_name": "New Years Hat", "outfit_type": 0, "outfit_set": 0, "drawer_index": 0, "hides_hair": false, "hides_hand": false, "hides_left_ear": false, "hides_right_ear": false, "uses_hair_color": false, "uses_team_color": false, "is_team_item": false}, + {"guid": "8fc7aaef-adec-4534-8f91-445e4e7095d5", "name": "(Wrist_Conductor)", "display_name": "Conductor", "outfit_type": 200, "outfit_set": 0, "drawer_index": 0, "hides_hair": false, "hides_hand": false, "hides_left_ear": false, "hides_right_ear": false, "uses_hair_color": false, "uses_team_color": false, "is_team_item": false}, + {"guid": "9073d6a0-3bc1-4c82-aeb3-d4ee195538b4", "name": "(MaleCheerTorso)", "display_name": "Male Cheer Torso", "outfit_type": 101, "outfit_set": 0, "drawer_index": 0, "hides_hair": false, "hides_hand": false, "hides_left_ear": false, "hides_right_ear": false, "uses_hair_color": false, "uses_team_color": false, "is_team_item": false}, + {"guid": "9168b3f9-c7e5-40b6-bb2c-9e708596e675", "name": "(Hat_Zombie)", "display_name": "Zombie", "outfit_type": 0, "outfit_set": 0, "drawer_index": 0, "hides_hair": false, "hides_hand": false, "hides_left_ear": false, "hides_right_ear": false, "uses_hair_color": false, "uses_team_color": false, "is_team_item": false}, + {"guid": "92302d9d-c527-418c-ac5d-1fa869727505", "name": "(Hair_Part)", "display_name": "Part", "outfit_type": 2, "outfit_set": 0, "drawer_index": 0, "hides_hair": false, "hides_hand": false, "hides_left_ear": false, "hides_right_ear": false, "uses_hair_color": true, "uses_team_color": false, "is_team_item": false}, + {"guid": "936d7126-a3b7-48a2-bceb-6cad9221b267", "name": "(Dress_MoonandStarsDress)", "display_name": "Moonand Stars Dress", "outfit_type": 101, "outfit_set": 0, "drawer_index": 0, "hides_hair": false, "hides_hand": false, "hides_left_ear": false, "hides_right_ear": false, "uses_hair_color": false, "uses_team_color": false, "is_team_item": false}, + {"guid": "94eb7ef3-528f-4f59-92a0-48b0ec287527", "name": "(Wrist_Soccer)", "display_name": "Soccer", "outfit_type": 200, "outfit_set": 28, "drawer_index": 0, "hides_hair": false, "hides_hand": true, "hides_left_ear": false, "hides_right_ear": false, "uses_hair_color": false, "uses_team_color": false, "is_team_item": false}, + {"guid": "95ab7a7c-c35d-4da5-9955-0921064470b6", "name": "(Hair_GordonGekko)", "display_name": "Gordon Gekko", "outfit_type": 2, "outfit_set": 0, "drawer_index": 0, "hides_hair": false, "hides_hand": false, "hides_left_ear": false, "hides_right_ear": false, "uses_hair_color": true, "uses_team_color": false, "is_team_item": false}, + {"guid": "967187b8-df20-4f4f-b48f-10b9ea75cedf", "name": "(Shirt_Farmer)", "display_name": "Farmer", "outfit_type": 101, "outfit_set": 32, "drawer_index": 0, "hides_hair": false, "hides_hand": false, "hides_left_ear": false, "hides_right_ear": false, "uses_hair_color": false, "uses_team_color": false, "is_team_item": false}, + {"guid": "96bc2f9f-ce3a-4c4c-b92c-cfed4ed4594f", "name": "(Hat_Beekeeper)", "display_name": "Beekeeper", "outfit_type": 0, "outfit_set": 0, "drawer_index": 0, "hides_hair": false, "hides_hand": false, "hides_left_ear": false, "hides_right_ear": false, "uses_hair_color": false, "uses_team_color": false, "is_team_item": false}, + {"guid": "97253092-90ba-4ff2-b137-6324ab244d11", "name": "(Shirt_IHeartRRShirt)", "display_name": "IHeart RRShirt", "outfit_type": 101, "outfit_set": 0, "drawer_index": 0, "hides_hair": false, "hides_hand": false, "hides_left_ear": false, "hides_right_ear": false, "uses_hair_color": false, "uses_team_color": false, "is_team_item": false}, + {"guid": "973d84ee-5b88-475c-b579-a76c34a7c533", "name": "(Wrist_TruckerJacket)", "display_name": "Trucker Jacket", "outfit_type": 200, "outfit_set": 0, "drawer_index": 0, "hides_hair": false, "hides_hand": false, "hides_left_ear": false, "hides_right_ear": false, "uses_hair_color": false, "uses_team_color": false, "is_team_item": false}, + {"guid": "973dc863-b9a5-40d8-8f15-4096e75bd2cb", "name": "(Eye_MirrorShades_Red)Variant", "display_name": "Mirror Shades Red)Variant", "outfit_type": 10, "outfit_set": 0, "drawer_index": 0, "hides_hair": false, "hides_hand": false, "hides_left_ear": false, "hides_right_ear": false, "uses_hair_color": false, "uses_team_color": false, "is_team_item": false}, + {"guid": "97eae086-cfea-45ff-9b77-1d184256d493", "name": "(Shirt_Wizard)", "display_name": "Wizard", "outfit_type": 101, "outfit_set": 0, "drawer_index": 0, "hides_hair": false, "hides_hand": false, "hides_left_ear": false, "hides_right_ear": false, "uses_hair_color": false, "uses_team_color": false, "is_team_item": false}, + {"guid": "988e4069-4341-428b-bbf5-023309891385", "name": "(Neck_Bowtie_Silver_Sequin)", "display_name": "Bowtie Silver Sequin", "outfit_type": 100, "outfit_set": 0, "drawer_index": 0, "hides_hair": false, "hides_hand": false, "hides_left_ear": false, "hides_right_ear": false, "uses_hair_color": false, "uses_team_color": false, "is_team_item": false}, + {"guid": "9a366f8b-ea5b-49ae-a98c-f6b282e9bf14", "name": "(Dress_FormalDress_GoldSequin)", "display_name": "Formal Dress Gold Sequin", "outfit_type": 101, "outfit_set": 0, "drawer_index": 0, "hides_hair": false, "hides_hand": false, "hides_left_ear": false, "hides_right_ear": false, "uses_hair_color": false, "uses_team_color": false, "is_team_item": false}, + {"guid": "9ad40310-7cb9-473d-920c-5c18d7972030", "name": "(Hat_Pumpkin)", "display_name": "Pumpkin", "outfit_type": 0, "outfit_set": 0, "drawer_index": 0, "hides_hair": false, "hides_hand": false, "hides_left_ear": false, "hides_right_ear": false, "uses_hair_color": false, "uses_team_color": false, "is_team_item": false}, + {"guid": "9b5406c4-8589-47e4-9f69-4fa4733615e7", "name": "(Hat_Turkey)", "display_name": "Turkey", "outfit_type": 0, "outfit_set": 0, "drawer_index": 0, "hides_hair": false, "hides_hand": false, "hides_left_ear": false, "hides_right_ear": false, "uses_hair_color": false, "uses_team_color": false, "is_team_item": false}, + {"guid": "9b5bde11-7408-4798-9fcb-c7ec175444df", "name": "(Ear_HoopEarring)", "display_name": "Hoop Earring", "outfit_type": 3, "outfit_set": 0, "drawer_index": 0, "hides_hair": false, "hides_hand": false, "hides_left_ear": false, "hides_right_ear": false, "uses_hair_color": false, "uses_team_color": false, "is_team_item": false}, + {"guid": "9bf5d259-7774-4cbe-a90f-7f188cc0dce7", "name": "(Mouth_GoateeThick)", "display_name": "Goatee Thick", "outfit_type": 20, "outfit_set": 0, "drawer_index": 0, "hides_hair": false, "hides_hand": false, "hides_left_ear": false, "hides_right_ear": false, "uses_hair_color": true, "uses_team_color": false, "is_team_item": false}, + {"guid": "9bf9d502-8a3c-4b24-9565-0a2c2d9864fd", "name": "(Wrist_Knight_Gauntlet)", "display_name": "Knight Gauntlet", "outfit_type": 200, "outfit_set": 1, "drawer_index": 0, "hides_hair": false, "hides_hand": true, "hides_left_ear": false, "hides_right_ear": false, "uses_hair_color": false, "uses_team_color": false, "is_team_item": false}, + {"guid": "9c28e450-8e12-455c-a4c8-c0a2160d4190", "name": "(Eye_Scuba)", "display_name": "Scuba", "outfit_type": 10, "outfit_set": 0, "drawer_index": 0, "hides_hair": false, "hides_hand": false, "hides_left_ear": true, "hides_right_ear": true, "uses_hair_color": false, "uses_team_color": false, "is_team_item": false}, + {"guid": "9c34c2c1-07d7-4180-94a2-be8c9caaae84", "name": "(LuchadorTorso)", "display_name": "Luchador Torso", "outfit_type": 101, "outfit_set": 0, "drawer_index": 0, "hides_hair": false, "hides_hand": false, "hides_left_ear": false, "hides_right_ear": false, "uses_hair_color": false, "uses_team_color": false, "is_team_item": false}, + {"guid": "9c8fc7f0-8f99-4aad-a34f-8d979f6ae352", "name": "(ButtonTop)", "display_name": "Button Top", "outfit_type": 101, "outfit_set": 0, "drawer_index": 0, "hides_hair": false, "hides_hand": false, "hides_left_ear": false, "hides_right_ear": false, "uses_hair_color": false, "uses_team_color": false, "is_team_item": false}, + {"guid": "9cdb4965-6f00-4304-afed-e301e73a2b3a", "name": "(Hat_SmallBrim)", "display_name": "Small Brim", "outfit_type": 0, "outfit_set": 0, "drawer_index": 0, "hides_hair": false, "hides_hand": false, "hides_left_ear": false, "hides_right_ear": false, "uses_hair_color": false, "uses_team_color": false, "is_team_item": false}, + {"guid": "9d395a84-4342-4b1c-940c-213ec2eb56f9", "name": "(Hat_QueenCrown)", "display_name": "Queen Crown", "outfit_type": 0, "outfit_set": 0, "drawer_index": 0, "hides_hair": false, "hides_hand": false, "hides_left_ear": false, "hides_right_ear": false, "uses_hair_color": false, "uses_team_color": false, "is_team_item": false}, + {"guid": "9d68e9f4-6ea0-4717-bb78-f2783a14a6b6", "name": "(Wrist_ScientistGlove)", "display_name": "Scientist Glove", "outfit_type": 200, "outfit_set": 23, "drawer_index": 0, "hides_hair": false, "hides_hand": true, "hides_left_ear": false, "hides_right_ear": false, "uses_hair_color": false, "uses_team_color": false, "is_team_item": false}, + {"guid": "9d9fadb6-97eb-480e-a224-4e0179082071", "name": "(Hair_MeatballBuns)", "display_name": "Meatball Buns", "outfit_type": 2, "outfit_set": 0, "drawer_index": 0, "hides_hair": false, "hides_hand": false, "hides_left_ear": false, "hides_right_ear": false, "uses_hair_color": true, "uses_team_color": false, "is_team_item": false}, + {"guid": "9e6397dd-b7b0-4d32-8c86-1910106a8478", "name": "(Shirt_Raincoat)", "display_name": "Raincoat", "outfit_type": 101, "outfit_set": 0, "drawer_index": 0, "hides_hair": false, "hides_hand": false, "hides_left_ear": false, "hides_right_ear": false, "uses_hair_color": false, "uses_team_color": false, "is_team_item": false}, + {"guid": "9eb7f505-5afd-4b28-9886-7996d38c59c6", "name": "(Wrist_CowboyGlove)", "display_name": "Cowboy Glove", "outfit_type": 200, "outfit_set": 0, "drawer_index": 0, "hides_hair": false, "hides_hand": true, "hides_left_ear": false, "hides_right_ear": false, "uses_hair_color": false, "uses_team_color": false, "is_team_item": false}, + {"guid": "9ffa6e06-5200-4e53-ac13-f8491aecc864", "name": "(Shirt_BountyHunterShirt)", "display_name": "Bounty Hunter Shirt", "outfit_type": 101, "outfit_set": 0, "drawer_index": 0, "hides_hair": false, "hides_hand": false, "hides_left_ear": false, "hides_right_ear": false, "uses_hair_color": false, "uses_team_color": false, "is_team_item": false}, + {"guid": "9o0gWLJjREG552gxRreT8A", "name": "(Shirt_FigureSkaterCollar)", "display_name": "Figure Skater Collar", "outfit_type": 101, "outfit_set": 0, "drawer_index": 0, "hides_hair": false, "hides_hand": false, "hides_left_ear": false, "hides_right_ear": false, "uses_hair_color": false, "uses_team_color": false, "is_team_item": false}, + {"guid": "a12f724f-4a73-4ab8-aad4-6bfc662b4dd6", "name": "(Hair_Undercut)", "display_name": "Undercut", "outfit_type": 2, "outfit_set": 0, "drawer_index": 0, "hides_hair": false, "hides_hand": false, "hides_left_ear": false, "hides_right_ear": true, "uses_hair_color": true, "uses_team_color": false, "is_team_item": false}, + {"guid": "a16d59da-caa5-4388-af67-315bfcb16f64", "name": "(Hat_Cloche)", "display_name": "Cloche", "outfit_type": 0, "outfit_set": 0, "drawer_index": 0, "hides_hair": false, "hides_hand": false, "hides_left_ear": true, "hides_right_ear": true, "uses_hair_color": false, "uses_team_color": false, "is_team_item": false}, + {"guid": "a302741d-313b-4682-82e5-ac76764e662c", "name": "(YellowPoofyDress)", "display_name": "Yellow Poofy Dress", "outfit_type": 101, "outfit_set": 0, "drawer_index": 0, "hides_hair": false, "hides_hand": false, "hides_left_ear": false, "hides_right_ear": false, "uses_hair_color": false, "uses_team_color": false, "is_team_item": false}, + {"guid": "a44766dc-19ad-4476-849c-5113a5faabd0", "name": "(neck_royalCape)", "display_name": "Royal Cape", "outfit_type": 100, "outfit_set": 0, "drawer_index": 0, "hides_hair": false, "hides_hand": false, "hides_left_ear": false, "hides_right_ear": false, "uses_hair_color": false, "uses_team_color": false, "is_team_item": false}, + {"guid": "a4c26527-8f75-44da-b150-7aaf8ef739c1", "name": "(Eye_MirrorShades)", "display_name": "Mirror Shades", "outfit_type": 10, "outfit_set": 0, "drawer_index": 0, "hides_hair": false, "hides_hand": false, "hides_left_ear": false, "hides_right_ear": false, "uses_hair_color": false, "uses_team_color": false, "is_team_item": false}, + {"guid": "a5b747b8-c1a5-4bce-999f-e13dbe77bde6", "name": "(Hat_DiscoBall)", "display_name": "Disco Ball", "outfit_type": 0, "outfit_set": 0, "drawer_index": 0, "hides_hair": false, "hides_hand": false, "hides_left_ear": true, "hides_right_ear": true, "uses_hair_color": false, "uses_team_color": false, "is_team_item": false}, + {"guid": "a61e86cd-f7e7-4ac7-a6e8-df269bc5cb01", "name": "(Hat_Hibiscus)", "display_name": "Hibiscus", "outfit_type": 0, "outfit_set": 0, "drawer_index": 0, "hides_hair": false, "hides_hand": false, "hides_left_ear": false, "hides_right_ear": false, "uses_hair_color": false, "uses_team_color": false, "is_team_item": false}, + {"guid": "a628d62c-fa30-4405-bcbc-3e646e3a3434", "name": "(Hat_Equestrian)", "display_name": "Equestrian", "outfit_type": 0, "outfit_set": 0, "drawer_index": 0, "hides_hair": false, "hides_hand": false, "hides_left_ear": true, "hides_right_ear": true, "uses_hair_color": false, "uses_team_color": false, "is_team_item": false}, + {"guid": "a6cbfe76-534a-4655-a8a8-3fed13d001c7", "name": "(Hair_BaldTop)", "display_name": "Bald Top", "outfit_type": 2, "outfit_set": 0, "drawer_index": 0, "hides_hair": false, "hides_hand": false, "hides_left_ear": false, "hides_right_ear": false, "uses_hair_color": true, "uses_team_color": false, "is_team_item": false}, + {"guid": "a6f1825e-1647-4d06-85d7-ac2d139612c0", "name": "(Shirt_DrumMajor)", "display_name": "Drum Major", "outfit_type": 101, "outfit_set": 0, "drawer_index": 0, "hides_hair": false, "hides_hand": false, "hides_left_ear": false, "hides_right_ear": false, "uses_hair_color": false, "uses_team_color": false, "is_team_item": false}, + {"guid": "a96e168c-0c4d-4c70-9f8a-c8f07164ce99", "name": "(Shirt_ScoutNeckerchief)", "display_name": "Scout Neckerchief", "outfit_type": 101, "outfit_set": 0, "drawer_index": 0, "hides_hair": false, "hides_hand": false, "hides_left_ear": false, "hides_right_ear": false, "uses_hair_color": false, "uses_team_color": false, "is_team_item": false}, + {"guid": "a986ff33-9e16-4219-8ebb-2e7b4c772ca7", "name": "(FieldHockey)", "display_name": "Field Hockey", "outfit_type": 101, "outfit_set": 17, "drawer_index": 0, "hides_hair": false, "hides_hand": false, "hides_left_ear": false, "hides_right_ear": false, "uses_hair_color": false, "uses_team_color": false, "is_team_item": false}, + {"guid": "ab15ef15-a97c-4ab1-896b-bd4d7e1cd9f5", "name": "(Shirt_Peacoat)", "display_name": "Peacoat", "outfit_type": 101, "outfit_set": 0, "drawer_index": 0, "hides_hair": false, "hides_hand": false, "hides_left_ear": false, "hides_right_ear": false, "uses_hair_color": false, "uses_team_color": false, "is_team_item": false}, + {"guid": "abc25091-ed5f-4c72-9364-fffeef1bc239", "name": "(Wrist_BishopGlove)", "display_name": "Bishop Glove", "outfit_type": 200, "outfit_set": 0, "drawer_index": 0, "hides_hair": false, "hides_hand": false, "hides_left_ear": false, "hides_right_ear": false, "uses_hair_color": false, "uses_team_color": false, "is_team_item": false}, + {"guid": "AbYdU7O_9U-Lwg7zFVZzXQ", "name": "(Hat_AntlerHeadband)", "display_name": "Antler Headband", "outfit_type": 0, "outfit_set": 0, "drawer_index": 0, "hides_hair": false, "hides_hand": false, "hides_left_ear": false, "hides_right_ear": false, "uses_hair_color": false, "uses_team_color": false, "is_team_item": false}, + {"guid": "ad0bdec5-f767-42e7-99e0-221473464a85", "name": "(Hat_Cowboy)", "display_name": "Cowboy", "outfit_type": 0, "outfit_set": 0, "drawer_index": 0, "hides_hair": false, "hides_hand": false, "hides_left_ear": false, "hides_right_ear": false, "uses_hair_color": false, "uses_team_color": false, "is_team_item": false}, + {"guid": "adf2aa89-61c4-4b7a-9307-e39e72db23d3", "name": "(Dress_Sundress)", "display_name": "Sundress", "outfit_type": 101, "outfit_set": 0, "drawer_index": 0, "hides_hair": false, "hides_hand": false, "hides_left_ear": false, "hides_right_ear": false, "uses_hair_color": false, "uses_team_color": false, "is_team_item": false}, + {"guid": "ae180fb1-3e74-4232-9a9d-388fd488eba1", "name": "(Shirt_WackyShorts)", "display_name": "Wacky Shorts", "outfit_type": 101, "outfit_set": 0, "drawer_index": 0, "hides_hair": false, "hides_hand": false, "hides_left_ear": false, "hides_right_ear": false, "uses_hair_color": false, "uses_team_color": false, "is_team_item": false}, + {"guid": "af33bdb1-b7fa-4fe6-b37a-b1fe6a420879", "name": "(Hat_Chef)", "display_name": "Chef", "outfit_type": 0, "outfit_set": 0, "drawer_index": 0, "hides_hair": false, "hides_hand": false, "hides_left_ear": false, "hides_right_ear": false, "uses_hair_color": false, "uses_team_color": false, "is_team_item": false}, + {"guid": "AIpF1QUeMUWytpHYBDBAkg", "name": "(Shirt_80sWindbreaker)", "display_name": "80s Windbreaker", "outfit_type": 101, "outfit_set": 0, "drawer_index": 0, "hides_hair": false, "hides_hand": false, "hides_left_ear": false, "hides_right_ear": false, "uses_hair_color": false, "uses_team_color": false, "is_team_item": false}, + {"guid": "akAhnN0cFkecj8q3spixkw", "name": "(Hat_Headphones_Gold)", "display_name": "Headphones Gold", "outfit_type": 0, "outfit_set": 0, "drawer_index": 0, "hides_hair": false, "hides_hand": false, "hides_left_ear": false, "hides_right_ear": false, "uses_hair_color": false, "uses_team_color": false, "is_team_item": false}, + {"guid": "ASHnWS0h9kOJhbOUwxWKFQ", "name": "(Shirt_TutorialGown_1)", "display_name": "Tutorial Gown 1", "outfit_type": 101, "outfit_set": 0, "drawer_index": 0, "hides_hair": false, "hides_hand": false, "hides_left_ear": false, "hides_right_ear": false, "uses_hair_color": false, "uses_team_color": false, "is_team_item": false}, + {"guid": "awOFE0_GSECr8GBNBo6a_w", "name": "(Belt_PoolFloatie_AnimalHead)", "display_name": "Pool Floatie Animal Head", "outfit_type": 102, "outfit_set": 0, "drawer_index": 0, "hides_hair": false, "hides_hand": false, "hides_left_ear": false, "hides_right_ear": false, "uses_hair_color": false, "uses_team_color": false, "is_team_item": false}, + {"guid": "b02053b3-013f-42b0-8881-85f5be2b8cda", "name": "(BlackCheerDress)", "display_name": "Black Cheer Dress", "outfit_type": 101, "outfit_set": 0, "drawer_index": 0, "hides_hair": false, "hides_hand": false, "hides_left_ear": false, "hides_right_ear": false, "uses_hair_color": false, "uses_team_color": false, "is_team_item": false}, + {"guid": "b080e2f4-c699-4cca-bb9e-7cfc4bafcbfc", "name": "(Shirt_TruckerOveralls)", "display_name": "Trucker Overalls", "outfit_type": 101, "outfit_set": 0, "drawer_index": 0, "hides_hair": false, "hides_hand": false, "hides_left_ear": false, "hides_right_ear": false, "uses_hair_color": false, "uses_team_color": false, "is_team_item": false}, + {"guid": "b148cb1e-df81-442f-aea6-ab1727aad00e", "name": "(Hair_ChunkyAfro)", "display_name": "Chunky Afro", "outfit_type": 2, "outfit_set": 0, "drawer_index": 0, "hides_hair": false, "hides_hand": false, "hides_left_ear": true, "hides_right_ear": true, "uses_hair_color": true, "uses_team_color": false, "is_team_item": false}, + {"guid": "b1bfe0b4-1ff6-420f-acfc-2331d34246dc", "name": "(Shirt_Angler)", "display_name": "Angler", "outfit_type": 101, "outfit_set": 0, "drawer_index": 0, "hides_hair": false, "hides_hand": false, "hides_left_ear": false, "hides_right_ear": false, "uses_hair_color": false, "uses_team_color": false, "is_team_item": false}, + {"guid": "b1c3ccc8-91d7-434e-b9cc-bd632fffc01b", "name": "(Wrist_DeepSeaDiver)", "display_name": "Deep Sea Diver", "outfit_type": 200, "outfit_set": 0, "drawer_index": 0, "hides_hair": false, "hides_hand": true, "hides_left_ear": false, "hides_right_ear": false, "uses_hair_color": false, "uses_team_color": false, "is_team_item": false}, + {"guid": "b33dbeee-5bdd-443d-aa6a-761248054e08", "name": "(Jersey_Team)", "display_name": "Team", "outfit_type": 104, "outfit_set": 0, "drawer_index": 0, "hides_hair": false, "hides_hand": false, "hides_left_ear": false, "hides_right_ear": false, "uses_hair_color": false, "uses_team_color": false, "is_team_item": true}, + {"guid": "b37582a1-7904-4288-a47a-7a63a3cb28ae", "name": "(Hat_SpaceSoldierHelmet)", "display_name": "Space Soldier Helmet", "outfit_type": 0, "outfit_set": 0, "drawer_index": 0, "hides_hair": true, "hides_hand": false, "hides_left_ear": false, "hides_right_ear": false, "uses_hair_color": false, "uses_team_color": false, "is_team_item": false}, + {"guid": "b4e48239-a3b0-49ed-b78a-86f4e3aaca1e", "name": "(Hat_SuperheroHeadBand)", "display_name": "Superhero Head Band", "outfit_type": 0, "outfit_set": 0, "drawer_index": 0, "hides_hair": false, "hides_hand": false, "hides_left_ear": false, "hides_right_ear": false, "uses_hair_color": false, "uses_team_color": false, "is_team_item": false}, + {"guid": "b537c30c-faef-4649-9a1f-be3985936f93", "name": "(Hat_Lumberjack)", "display_name": "Lumberjack", "outfit_type": 0, "outfit_set": 3, "drawer_index": 0, "hides_hair": false, "hides_hand": false, "hides_left_ear": true, "hides_right_ear": true, "uses_hair_color": false, "uses_team_color": false, "is_team_item": false}, + {"guid": "b6610b7b-3978-46fd-9af6-41b4568c9b4d", "name": "(Shirt_Soccer)", "display_name": "Soccer", "outfit_type": 101, "outfit_set": 28, "drawer_index": 0, "hides_hair": false, "hides_hand": false, "hides_left_ear": false, "hides_right_ear": false, "uses_hair_color": false, "uses_team_color": false, "is_team_item": false}, + {"guid": "b6rLwzD4NkKV7xKn9ZYVkA", "name": "(Shirt_Hoodie)", "display_name": "Hoodie", "outfit_type": 101, "outfit_set": 0, "drawer_index": 0, "hides_hair": false, "hides_hand": false, "hides_left_ear": false, "hides_right_ear": false, "uses_hair_color": false, "uses_team_color": false, "is_team_item": false}, + {"guid": "b795c631-f075-4d32-ac87-c732b36cb932", "name": "(Neck_StringLights)", "display_name": "String Lights", "outfit_type": 100, "outfit_set": 0, "drawer_index": 0, "hides_hair": false, "hides_hand": false, "hides_left_ear": false, "hides_right_ear": false, "uses_hair_color": false, "uses_team_color": false, "is_team_item": false}, + {"guid": "b84f5e9e-11fc-424d-ad39-86901edb9edc", "name": "(Shirt_DemonShirt)", "display_name": "Demon Shirt", "outfit_type": 101, "outfit_set": 0, "drawer_index": 0, "hides_hair": false, "hides_hand": false, "hides_left_ear": false, "hides_right_ear": false, "uses_hair_color": false, "uses_team_color": false, "is_team_item": false}, + {"guid": "b861e5f3-fc6d-43b3-9861-c1b45cb493a8", "name": "(Hair_Bishop)", "display_name": "Bishop", "outfit_type": 2, "outfit_set": 0, "drawer_index": 0, "hides_hair": false, "hides_hand": false, "hides_left_ear": false, "hides_right_ear": false, "uses_hair_color": true, "uses_team_color": false, "is_team_item": false}, + {"guid": "b89be768-a169-4b24-8022-751e7b817865", "name": "(Glasses_SciFiVisor)", "display_name": "Sci Fi Visor", "outfit_type": 10, "outfit_set": 0, "drawer_index": 0, "hides_hair": false, "hides_hand": false, "hides_left_ear": false, "hides_right_ear": false, "uses_hair_color": false, "uses_team_color": false, "is_team_item": false}, + {"guid": "b904304b-cce0-466f-8569-c621eeadd4f7", "name": "(Wrist_Boxing_Glove)", "display_name": "Boxing Glove", "outfit_type": 200, "outfit_set": 14, "drawer_index": 0, "hides_hair": false, "hides_hand": true, "hides_left_ear": false, "hides_right_ear": false, "uses_hair_color": false, "uses_team_color": false, "is_team_item": false}, + {"guid": "b95678aa-351a-4929-8a0e-0274a183eb18", "name": "(Neck_NecklaceSilver)", "display_name": "Necklace Silver", "outfit_type": 103, "outfit_set": 0, "drawer_index": 0, "hides_hair": false, "hides_hand": false, "hides_left_ear": false, "hides_right_ear": false, "uses_hair_color": false, "uses_team_color": false, "is_team_item": false}, + {"guid": "b99a89c7-38c5-4196-9862-4e1bf9cb0ce1", "name": "(Hat_BoxingHelmet)", "display_name": "Boxing Helmet", "outfit_type": 0, "outfit_set": 14, "drawer_index": 0, "hides_hair": false, "hides_hand": false, "hides_left_ear": true, "hides_right_ear": true, "uses_hair_color": false, "uses_team_color": false, "is_team_item": false}, + {"guid": "b9d08f9a-b23d-4d7e-978b-a0cd8adbfe9c", "name": "(Neck_WinterCloak)", "display_name": "Winter Cloak", "outfit_type": 100, "outfit_set": 0, "drawer_index": 0, "hides_hair": false, "hides_hand": false, "hides_left_ear": false, "hides_right_ear": false, "uses_hair_color": false, "uses_team_color": false, "is_team_item": false}, + {"guid": "b9e0f03b-2609-4911-82e9-1e324dbbe8be", "name": "(Wrist_Scallywag_Bracelet)", "display_name": "Scallywag Bracelet", "outfit_type": 200, "outfit_set": 0, "drawer_index": 0, "hides_hair": false, "hides_hand": false, "hides_left_ear": false, "hides_right_ear": false, "uses_hair_color": false, "uses_team_color": false, "is_team_item": false}, + {"guid": "ba84b23e-01ee-4668-a7ef-2e3e0eca2f23", "name": "(CowboyTorso)", "display_name": "Cowboy Torso", "outfit_type": 101, "outfit_set": 0, "drawer_index": 0, "hides_hair": false, "hides_hand": false, "hides_left_ear": false, "hides_right_ear": false, "uses_hair_color": false, "uses_team_color": false, "is_team_item": false}, + {"guid": "bb54e0e9-5d90-41f3-94c0-8e82f1791af1", "name": "(Hat_Firefighter)", "display_name": "Firefighter", "outfit_type": 0, "outfit_set": 0, "drawer_index": 0, "hides_hair": false, "hides_hand": false, "hides_left_ear": false, "hides_right_ear": false, "uses_hair_color": false, "uses_team_color": false, "is_team_item": false}, + {"guid": "bb8602d4-f91f-4e35-9c88-1cdf3a04ff0a", "name": "(Shirt_Bee)", "display_name": "Bee", "outfit_type": 101, "outfit_set": 0, "drawer_index": 0, "hides_hair": false, "hides_hand": false, "hides_left_ear": false, "hides_right_ear": false, "uses_hair_color": false, "uses_team_color": false, "is_team_item": false}, + {"guid": "bc3f8ab4-cdb1-4e63-bda5-ba5a4b1f4a43", "name": "(Hat_InternationalThiefHat)", "display_name": "International Thief Hat", "outfit_type": 0, "outfit_set": 0, "drawer_index": 0, "hides_hair": false, "hides_hand": false, "hides_left_ear": false, "hides_right_ear": false, "uses_hair_color": false, "uses_team_color": false, "is_team_item": false}, + {"guid": "bc7299ac-be2b-404f-b8e6-402e2c0660f0", "name": "(Eye_RoundGlasses)", "display_name": "Round Glasses", "outfit_type": 10, "outfit_set": 0, "drawer_index": 0, "hides_hair": false, "hides_hand": false, "hides_left_ear": false, "hides_right_ear": false, "uses_hair_color": false, "uses_team_color": false, "is_team_item": false}, + {"guid": "bf13fa1c-f991-4aaa-9402-aac109be9562", "name": "(GoldMedal)", "display_name": "Gold Medal", "outfit_type": 100, "outfit_set": 0, "drawer_index": 0, "hides_hair": false, "hides_hand": false, "hides_left_ear": false, "hides_right_ear": false, "uses_hair_color": false, "uses_team_color": false, "is_team_item": false}, + {"guid": "bf9dc196-5727-4752-8126-7ad5fcd96d55", "name": "(Neck_ScoutSash)", "display_name": "Scout Sash", "outfit_type": 100, "outfit_set": 0, "drawer_index": 0, "hides_hair": false, "hides_hand": false, "hides_left_ear": false, "hides_right_ear": false, "uses_hair_color": false, "uses_team_color": false, "is_team_item": false}, + {"guid": "bfd9c70b-9ad8-4f47-a840-c03231f9ad41", "name": "(Hat_Bowler)", "display_name": "Bowler", "outfit_type": 0, "outfit_set": 0, "drawer_index": 0, "hides_hair": false, "hides_hand": false, "hides_left_ear": false, "hides_right_ear": false, "uses_hair_color": false, "uses_team_color": false, "is_team_item": false}, + {"guid": "BJ2XrGvl40iIl28IbTfbKg", "name": "(Hair_FemaleCheerleader)", "display_name": "Female Cheerleader", "outfit_type": 2, "outfit_set": 0, "drawer_index": 0, "hides_hair": false, "hides_hand": false, "hides_left_ear": false, "hides_right_ear": false, "uses_hair_color": true, "uses_team_color": false, "is_team_item": false}, + {"guid": "BqpRbr_T1kO_YJqB1787kA", "name": "(Neck_QpidQuiver)", "display_name": "Qpid Quiver", "outfit_type": 100, "outfit_set": 0, "drawer_index": 0, "hides_hair": false, "hides_hand": false, "hides_left_ear": false, "hides_right_ear": false, "uses_hair_color": false, "uses_team_color": false, "is_team_item": false}, + {"guid": "BR0mZKsKaE-t4R8CzEwuyg", "name": "(Wrist_Thriller)", "display_name": "Thriller", "outfit_type": 200, "outfit_set": 0, "drawer_index": 0, "hides_hair": false, "hides_hand": false, "hides_left_ear": false, "hides_right_ear": false, "uses_hair_color": false, "uses_team_color": false, "is_team_item": false}, + {"guid": "bu9tb-K7NEiocvZY3o7ukw", "name": "(Hat_80sShutterShades)", "display_name": "80s Shutter Shades", "outfit_type": 10, "outfit_set": 0, "drawer_index": 0, "hides_hair": false, "hides_hand": false, "hides_left_ear": false, "hides_right_ear": false, "uses_hair_color": false, "uses_team_color": false, "is_team_item": false}, + {"guid": "c0e59f27-a13d-4bd9-b3b8-a38a360f649b", "name": "(Shirt_Archer)", "display_name": "Archer", "outfit_type": 101, "outfit_set": 2, "drawer_index": 0, "hides_hair": false, "hides_hand": false, "hides_left_ear": false, "hides_right_ear": false, "uses_hair_color": false, "uses_team_color": false, "is_team_item": false}, + {"guid": "c161dc73-6ce3-49c4-8d15-45e6911f84e4", "name": "(SeaCaptainBeard)", "display_name": "Sea Captain Beard", "outfit_type": 20, "outfit_set": 0, "drawer_index": 0, "hides_hair": false, "hides_hand": false, "hides_left_ear": false, "hides_right_ear": false, "uses_hair_color": true, "uses_team_color": false, "is_team_item": false}, + {"guid": "c3745cee-d87e-4a23-b6e2-64ebf05c8b11", "name": "(Shirt_DeepSeaDiver)", "display_name": "Deep Sea Diver", "outfit_type": 101, "outfit_set": 0, "drawer_index": 0, "hides_hair": false, "hides_hand": false, "hides_left_ear": false, "hides_right_ear": false, "uses_hair_color": false, "uses_team_color": false, "is_team_item": false}, + {"guid": "c3ba47d5-a165-4bd6-8479-e5f8d709f4ff", "name": "(Shirt_ElfShirt)", "display_name": "Elf Shirt", "outfit_type": 101, "outfit_set": 0, "drawer_index": 0, "hides_hair": false, "hides_hand": false, "hides_left_ear": false, "hides_right_ear": false, "uses_hair_color": false, "uses_team_color": false, "is_team_item": false}, + {"guid": "c45ed7b8-99bd-4a4b-a9ff-e16edf5d7a18", "name": "(Hair_ManPony)", "display_name": "Man Pony", "outfit_type": 2, "outfit_set": 0, "drawer_index": 0, "hides_hair": false, "hides_hand": false, "hides_left_ear": false, "hides_right_ear": false, "uses_hair_color": true, "uses_team_color": false, "is_team_item": false}, + {"guid": "C4uuztTiYUe20PACMARksA", "name": "(Shirt_Letterman)", "display_name": "Letterman", "outfit_type": 101, "outfit_set": 0, "drawer_index": 0, "hides_hair": false, "hides_hand": false, "hides_left_ear": false, "hides_right_ear": false, "uses_hair_color": false, "uses_team_color": false, "is_team_item": false}, + {"guid": "c506feae-be73-437b-b18a-1152bb9906e9", "name": "(Neck_ButterflyWings_Monarch)", "display_name": "Butterfly Wings Monarch", "outfit_type": 100, "outfit_set": 0, "drawer_index": 0, "hides_hair": false, "hides_hand": false, "hides_left_ear": false, "hides_right_ear": false, "uses_hair_color": false, "uses_team_color": false, "is_team_item": false}, + {"guid": "c6c08eb5-381a-4193-9722-80da95d62abe", "name": "(BusinessTie)", "display_name": "Business Tie", "outfit_type": 100, "outfit_set": 0, "drawer_index": 0, "hides_hair": false, "hides_hand": false, "hides_left_ear": false, "hides_right_ear": false, "uses_hair_color": false, "uses_team_color": false, "is_team_item": false}, + {"guid": "c70005d5-6276-4a98-acb3-6a77bc19379a", "name": "(Glasses_Red)", "display_name": "Red", "outfit_type": 10, "outfit_set": 0, "drawer_index": 0, "hides_hair": false, "hides_hand": false, "hides_left_ear": false, "hides_right_ear": false, "uses_hair_color": false, "uses_team_color": false, "is_team_item": false}, + {"guid": "c855dcc3-96cb-470d-b159-d37a025a47d1", "name": "(Hair_DutchBraid)", "display_name": "Dutch Braid", "outfit_type": 2, "outfit_set": 0, "drawer_index": 0, "hides_hair": false, "hides_hand": false, "hides_left_ear": false, "hides_right_ear": false, "uses_hair_color": true, "uses_team_color": false, "is_team_item": false}, + {"guid": "c87239ca-a255-4b13-bbc5-1b38236fb4cc", "name": "(Hat_Viking)", "display_name": "Viking", "outfit_type": 0, "outfit_set": 0, "drawer_index": 0, "hides_hair": false, "hides_hand": false, "hides_left_ear": false, "hides_right_ear": false, "uses_hair_color": false, "uses_team_color": false, "is_team_item": false}, + {"guid": "c8a97aeb-e8dc-40ba-b6bc-a7c7be907381", "name": "(Hair_Malibu)", "display_name": "Malibu", "outfit_type": 2, "outfit_set": 0, "drawer_index": 0, "hides_hair": false, "hides_hand": false, "hides_left_ear": true, "hides_right_ear": true, "uses_hair_color": true, "uses_team_color": false, "is_team_item": false}, + {"guid": "c8c00394-4cd9-4bad-9774-a825ded78f80", "name": "(Neck_MermaidNecklace)", "display_name": "Mermaid Necklace", "outfit_type": 103, "outfit_set": 0, "drawer_index": 0, "hides_hair": false, "hides_hand": false, "hides_left_ear": false, "hides_right_ear": false, "uses_hair_color": false, "uses_team_color": false, "is_team_item": false}, + {"guid": "ca218759-7eed-440e-b5dc-dba15f81e598", "name": "(Shirt_NinetiesOutfit)", "display_name": "Nineties Outfit", "outfit_type": 101, "outfit_set": 0, "drawer_index": 0, "hides_hair": false, "hides_hand": false, "hides_left_ear": false, "hides_right_ear": false, "uses_hair_color": false, "uses_team_color": false, "is_team_item": false}, + {"guid": "ca7a3ccb-2bc9-4c93-a6a8-6f22b38526af", "name": "(Shirt_Harness)", "display_name": "Harness", "outfit_type": 101, "outfit_set": 0, "drawer_index": 0, "hides_hair": false, "hides_hand": false, "hides_left_ear": false, "hides_right_ear": false, "uses_hair_color": false, "uses_team_color": false, "is_team_item": false}, + {"guid": "cbN5e-t_jEKa4hzir5JAxQ", "name": "(Eye_TouristGlasses)", "display_name": "Tourist Glasses", "outfit_type": 10, "outfit_set": 0, "drawer_index": 0, "hides_hair": false, "hides_hand": false, "hides_left_ear": false, "hides_right_ear": false, "uses_hair_color": false, "uses_team_color": false, "is_team_item": false}, + {"guid": "cc96f8a5-bc5b-4f89-83b7-ecd53905ada7", "name": "(Beard_Thick)", "display_name": "Thick", "outfit_type": 20, "outfit_set": 0, "drawer_index": 0, "hides_hair": false, "hides_hand": false, "hides_left_ear": false, "hides_right_ear": false, "uses_hair_color": true, "uses_team_color": false, "is_team_item": false}, + {"guid": "cca7ab3d-5609-4700-92fd-3280fcea9b77", "name": "(CowboyStache)", "display_name": "Cowboy Stache", "outfit_type": 20, "outfit_set": 0, "drawer_index": 0, "hides_hair": false, "hides_hand": false, "hides_left_ear": false, "hides_right_ear": false, "uses_hair_color": true, "uses_team_color": false, "is_team_item": false}, + {"guid": "CdeM6RJmPUumXtdmdM3RXA", "name": "(Eye_SteampunkFemmeSpex)", "display_name": "Steampunk Femme Spex", "outfit_type": 10, "outfit_set": 0, "drawer_index": 0, "hides_hair": false, "hides_hand": false, "hides_left_ear": true, "hides_right_ear": true, "uses_hair_color": false, "uses_team_color": false, "is_team_item": false}, + {"guid": "ce250e13-8137-4f62-9e52-2b1cebe7d0ad", "name": "(Hair_Punk)", "display_name": "Punk", "outfit_type": 2, "outfit_set": 31, "drawer_index": 0, "hides_hair": false, "hides_hand": false, "hides_left_ear": false, "hides_right_ear": false, "uses_hair_color": true, "uses_team_color": false, "is_team_item": false}, + {"guid": "ce81210f-8eb8-4cd2-95d5-046d4da229c8", "name": "(Wrist_Barbershop)", "display_name": "Barbershop", "outfit_type": 200, "outfit_set": 35, "drawer_index": 0, "hides_hair": false, "hides_hand": false, "hides_left_ear": false, "hides_right_ear": false, "uses_hair_color": false, "uses_team_color": false, "is_team_item": false}, + {"guid": "cea5c141-32d8-4dae-80d9-6fb74834a79c", "name": "(Hat_Farmer)", "display_name": "Farmer", "outfit_type": 0, "outfit_set": 32, "drawer_index": 0, "hides_hair": false, "hides_hand": false, "hides_left_ear": false, "hides_right_ear": false, "uses_hair_color": false, "uses_team_color": false, "is_team_item": false}, + {"guid": "cec9cfac-a45b-4edb-b26d-7453cc2bfaf2", "name": "(Hat_Froggyheadband)", "display_name": "Froggyheadband", "outfit_type": 0, "outfit_set": 0, "drawer_index": 0, "hides_hair": false, "hides_hand": false, "hides_left_ear": false, "hides_right_ear": false, "uses_hair_color": false, "uses_team_color": false, "is_team_item": false}, + {"guid": "cf1a6f8f-1649-423b-b513-b8d40d242b1f", "name": "(Shirt_GatheredShirt)", "display_name": "Gathered Shirt", "outfit_type": 101, "outfit_set": 0, "drawer_index": 0, "hides_hair": false, "hides_hand": false, "hides_left_ear": false, "hides_right_ear": false, "uses_hair_color": false, "uses_team_color": false, "is_team_item": false}, + {"guid": "CQuKKNpan0O7rhR1fr14-Q", "name": "(Shirt_Bowling)", "display_name": "Bowling", "outfit_type": 101, "outfit_set": 0, "drawer_index": 0, "hides_hair": false, "hides_hand": false, "hides_left_ear": false, "hides_right_ear": false, "uses_hair_color": false, "uses_team_color": false, "is_team_item": false}, + {"guid": "CTcrvbo3OEepIV4oW8bx4w", "name": "(Hair_RecedingHairline)", "display_name": "Receding Hairline", "outfit_type": 2, "outfit_set": 0, "drawer_index": 0, "hides_hair": false, "hides_hand": false, "hides_left_ear": false, "hides_right_ear": false, "uses_hair_color": true, "uses_team_color": false, "is_team_item": false}, + {"guid": "d0a9262f-5504-46a7-bb10-7507503db58e", "name": "(MaleTShirt)", "display_name": "Male TShirt", "outfit_type": 101, "outfit_set": 0, "drawer_index": 0, "hides_hair": false, "hides_hand": false, "hides_left_ear": false, "hides_right_ear": false, "uses_hair_color": false, "uses_team_color": false, "is_team_item": false}, + {"guid": "d12e5738-00c7-47e7-9334-8399eec5aa89", "name": "(Hat_InternationalThiefHat_Contest)", "display_name": "International Thief Hat Contest", "outfit_type": 0, "outfit_set": 0, "drawer_index": 0, "hides_hair": false, "hides_hand": false, "hides_left_ear": false, "hides_right_ear": false, "uses_hair_color": false, "uses_team_color": false, "is_team_item": false}, + {"guid": "d2d3264b-fbf6-40b5-9f10-c85ec737d112", "name": "(Hat_Archer)", "display_name": "Archer", "outfit_type": 0, "outfit_set": 2, "drawer_index": 0, "hides_hair": false, "hides_hand": false, "hides_left_ear": false, "hides_right_ear": false, "uses_hair_color": false, "uses_team_color": false, "is_team_item": false}, + {"guid": "d3b8bd7b-ca9a-4f5d-b115-3659dc294a03", "name": "(Hat_GreenVelvet_TopHat)", "display_name": "Green Velvet Top Hat", "outfit_type": 0, "outfit_set": 0, "drawer_index": 0, "hides_hair": false, "hides_hand": false, "hides_left_ear": false, "hides_right_ear": false, "uses_hair_color": false, "uses_team_color": false, "is_team_item": false}, + {"guid": "d3d69bee-60bc-44a3-ad7c-65b3b69d1e59", "name": "(Shirt_Dodgeball)", "display_name": "Dodgeball", "outfit_type": 101, "outfit_set": 0, "drawer_index": 0, "hides_hair": false, "hides_hand": false, "hides_left_ear": false, "hides_right_ear": false, "uses_hair_color": false, "uses_team_color": false, "is_team_item": false}, + {"guid": "d57cf97e-b5d2-4e47-8231-a226a161f6e3", "name": "(Eye_MirrorShades_PinkClear)Variant", "display_name": "Mirror Shades Pink Clear)Variant", "outfit_type": 10, "outfit_set": 0, "drawer_index": 0, "hides_hair": false, "hides_hand": false, "hides_left_ear": false, "hides_right_ear": false, "uses_hair_color": false, "uses_team_color": false, "is_team_item": false}, + {"guid": "d6b0a1e7-e918-43e5-8191-3a8d9d01df7c", "name": "(HockeyShirt)", "display_name": "Hockey Shirt", "outfit_type": 101, "outfit_set": 0, "drawer_index": 0, "hides_hair": false, "hides_hand": false, "hides_left_ear": false, "hides_right_ear": false, "uses_hair_color": false, "uses_team_color": false, "is_team_item": false}, + {"guid": "d766c8d3-76e8-4021-b0a4-24b78fb7eaba", "name": "(TrackSuit)", "display_name": "Track Suit", "outfit_type": 101, "outfit_set": 0, "drawer_index": 0, "hides_hair": false, "hides_hand": false, "hides_left_ear": false, "hides_right_ear": false, "uses_hair_color": false, "uses_team_color": false, "is_team_item": false}, + {"guid": "d7730a9e-78a1-4356-bc09-6b066615850b", "name": "(Hair_PonyPoof)", "display_name": "Pony Poof", "outfit_type": 2, "outfit_set": 0, "drawer_index": 0, "hides_hair": false, "hides_hand": false, "hides_left_ear": false, "hides_right_ear": false, "uses_hair_color": true, "uses_team_color": false, "is_team_item": false}, + {"guid": "d8280c0c-d803-4513-be10-a0ba96d8821e", "name": "(Hair_Rhianna)", "display_name": "Rhianna", "outfit_type": 2, "outfit_set": 0, "drawer_index": 0, "hides_hair": false, "hides_hand": false, "hides_left_ear": false, "hides_right_ear": false, "uses_hair_color": true, "uses_team_color": false, "is_team_item": false}, + {"guid": "d84c0ff9-8fbe-4ed8-abf3-7996e81888ab", "name": "(Hair_GiantAfro)", "display_name": "Giant Afro", "outfit_type": 2, "outfit_set": 0, "drawer_index": 0, "hides_hair": false, "hides_hand": false, "hides_left_ear": false, "hides_right_ear": false, "uses_hair_color": true, "uses_team_color": false, "is_team_item": false}, + {"guid": "d86e2e65-5d87-466d-b885-8c52ac2eeeb0", "name": "(Shirt_SeaCaptain_Contest)", "display_name": "Sea Captain Contest", "outfit_type": 101, "outfit_set": 0, "drawer_index": 0, "hides_hair": false, "hides_hand": false, "hides_left_ear": false, "hides_right_ear": false, "uses_hair_color": false, "uses_team_color": false, "is_team_item": false}, + {"guid": "d8f408f0-78f5-4e8a-b42a-e7b09632d1fd", "name": "(PaintballVest)", "display_name": "Paintball Vest", "outfit_type": 101, "outfit_set": 0, "drawer_index": 0, "hides_hair": false, "hides_hand": false, "hides_left_ear": false, "hides_right_ear": false, "uses_hair_color": false, "uses_team_color": false, "is_team_item": false}, + {"guid": "d960b47d-f8bd-4c2e-813c-a6a09876ea18", "name": "(Hat_Scarecrow)", "display_name": "Scarecrow", "outfit_type": 0, "outfit_set": 0, "drawer_index": 0, "hides_hair": true, "hides_hand": false, "hides_left_ear": false, "hides_right_ear": false, "uses_hair_color": false, "uses_team_color": false, "is_team_item": false}, + {"guid": "da4e7b34-2095-4a9e-801e-4f409039e0dd", "name": "(Hair_Buzz)", "display_name": "Buzz", "outfit_type": 2, "outfit_set": 0, "drawer_index": 0, "hides_hair": false, "hides_hand": false, "hides_left_ear": false, "hides_right_ear": false, "uses_hair_color": true, "uses_team_color": false, "is_team_item": false}, + {"guid": "da55bfc3-47b7-46f6-b548-7fb2c2a5e0bf", "name": "(BabydollDress)", "display_name": "Babydoll Dress", "outfit_type": 101, "outfit_set": 0, "drawer_index": 0, "hides_hair": false, "hides_hand": false, "hides_left_ear": false, "hides_right_ear": false, "uses_hair_color": false, "uses_team_color": false, "is_team_item": false}, + {"guid": "dcf9012d-f9e9-4f15-872e-f130af3b2efa", "name": "(Hat_Barbershop)", "display_name": "Barbershop", "outfit_type": 0, "outfit_set": 35, "drawer_index": 0, "hides_hair": false, "hides_hand": false, "hides_left_ear": false, "hides_right_ear": false, "uses_hair_color": false, "uses_team_color": false, "is_team_item": false}, + {"guid": "de0ac50d-2adb-4114-bd2e-68953b13d706", "name": "(Blazer)", "display_name": "Blazer", "outfit_type": 101, "outfit_set": 0, "drawer_index": 0, "hides_hair": false, "hides_hand": false, "hides_left_ear": false, "hides_right_ear": false, "uses_hair_color": false, "uses_team_color": false, "is_team_item": false}, + {"guid": "de19b239-3ed0-4f03-acb6-da69b5aa52ea", "name": "(Shirt_Zombie)", "display_name": "Zombie", "outfit_type": 101, "outfit_set": 0, "drawer_index": 0, "hides_hair": false, "hides_hand": false, "hides_left_ear": false, "hides_right_ear": false, "uses_hair_color": false, "uses_team_color": false, "is_team_item": false}, + {"guid": "dedd2e98-2721-4de6-92f2-9d399f34e8fe", "name": "(Shirt_BogMonsterShirt)", "display_name": "Bog Monster Shirt", "outfit_type": 101, "outfit_set": 0, "drawer_index": 0, "hides_hair": false, "hides_hand": false, "hides_left_ear": false, "hides_right_ear": false, "uses_hair_color": false, "uses_team_color": false, "is_team_item": false}, + {"guid": "df0e06c4-8ba6-4d7a-bfb2-3759d8fe88c7", "name": "(Shirt_Skeleton_Hoodie)", "display_name": "Skeleton Hoodie", "outfit_type": 101, "outfit_set": 0, "drawer_index": 0, "hides_hair": false, "hides_hand": false, "hides_left_ear": false, "hides_right_ear": false, "uses_hair_color": false, "uses_team_color": false, "is_team_item": false}, + {"guid": "e042433a-40a2-4758-8074-3b9d407835ec", "name": "(Shirt_FuschiaMarine)", "display_name": "Fuschia Marine", "outfit_type": 101, "outfit_set": 0, "drawer_index": 0, "hides_hair": false, "hides_hand": false, "hides_left_ear": false, "hides_right_ear": false, "uses_hair_color": false, "uses_team_color": false, "is_team_item": false}, + {"guid": "e13f6558-d49c-4314-820c-962ef48eb0c6", "name": "(shirt_royalLord)", "display_name": "Royal Lord", "outfit_type": 101, "outfit_set": 0, "drawer_index": 0, "hides_hair": false, "hides_hand": false, "hides_left_ear": false, "hides_right_ear": false, "uses_hair_color": false, "uses_team_color": false, "is_team_item": false}, + {"guid": "e15b13a7-9e9a-4b32-ba2c-0cb31ed55a8c", "name": "(Neck_Winter)", "display_name": "Winter", "outfit_type": 100, "outfit_set": 0, "drawer_index": 0, "hides_hair": false, "hides_hand": false, "hides_left_ear": false, "hides_right_ear": false, "uses_hair_color": false, "uses_team_color": false, "is_team_item": false}, + {"guid": "e1a13235-e3b3-4b39-8e6f-e6449d4321b7", "name": "(Eye_Pirate_Eyepatch)", "display_name": "Pirate Eyepatch", "outfit_type": 10, "outfit_set": 0, "drawer_index": 0, "hides_hair": false, "hides_hand": false, "hides_left_ear": false, "hides_right_ear": false, "uses_hair_color": false, "uses_team_color": false, "is_team_item": false}, + {"guid": "e2620c2c-c28e-45ce-a83c-0588329b9c81", "name": "(Eye_MirrorShades_TealRed) Variant", "display_name": "Mirror Shades Teal Red) Variant", "outfit_type": 10, "outfit_set": 0, "drawer_index": 0, "hides_hair": false, "hides_hand": false, "hides_left_ear": false, "hides_right_ear": false, "uses_hair_color": false, "uses_team_color": false, "is_team_item": false}, + {"guid": "e286863c-2967-4d00-b837-b49487b9484a", "name": "(Hair_FauxHawk)", "display_name": "Faux Hawk", "outfit_type": 2, "outfit_set": 0, "drawer_index": 0, "hides_hair": false, "hides_hand": false, "hides_left_ear": false, "hides_right_ear": false, "uses_hair_color": true, "uses_team_color": false, "is_team_item": false}, + {"guid": "e29d044e-79fb-4280-bca9-aa1e1257e968", "name": "(Shirt_SpeedGi)", "display_name": "Speed Gi", "outfit_type": 101, "outfit_set": 0, "drawer_index": 0, "hides_hair": false, "hides_hand": false, "hides_left_ear": false, "hides_right_ear": false, "uses_hair_color": false, "uses_team_color": false, "is_team_item": false}, + {"guid": "e2ObiQFqCUOdKniKL9a1EA", "name": "(Hat_QpidLaurel)", "display_name": "Qpid Laurel", "outfit_type": 0, "outfit_set": 0, "drawer_index": 0, "hides_hair": false, "hides_hand": false, "hides_left_ear": false, "hides_right_ear": false, "uses_hair_color": false, "uses_team_color": false, "is_team_item": false}, + {"guid": "e36bcd98-7e85-43fa-89f8-57e4ec33823a", "name": "(Hair_Bob_Bangs)", "display_name": "Bob Bangs", "outfit_type": 2, "outfit_set": 0, "drawer_index": 0, "hides_hair": false, "hides_hand": false, "hides_left_ear": true, "hides_right_ear": true, "uses_hair_color": true, "uses_team_color": false, "is_team_item": false}, + {"guid": "e41c32ac-61b7-4e59-9d0e-b231ebdc2a68", "name": "(WristBand_Team_Emissive)", "display_name": "Team Emissive", "outfit_type": 203, "outfit_set": 0, "drawer_index": 0, "hides_hair": false, "hides_hand": false, "hides_left_ear": false, "hides_right_ear": false, "uses_hair_color": false, "uses_team_color": false, "is_team_item": true}, + {"guid": "e49d5d78-157e-4bc8-b878-ff7c4963fdc4", "name": "(Hair_Mohawk)", "display_name": "Mohawk", "outfit_type": 2, "outfit_set": 0, "drawer_index": 0, "hides_hair": false, "hides_hand": false, "hides_left_ear": false, "hides_right_ear": false, "uses_hair_color": true, "uses_team_color": false, "is_team_item": false}, + {"guid": "e4ba395e-f291-4e7b-8193-45f4422f41ce", "name": "(Shirt_Punk)", "display_name": "Punk", "outfit_type": 101, "outfit_set": 31, "drawer_index": 0, "hides_hair": false, "hides_hand": false, "hides_left_ear": false, "hides_right_ear": false, "uses_hair_color": false, "uses_team_color": false, "is_team_item": false}, + {"guid": "e5b83dfc-b2e1-4dcb-a4ab-9d3a4c8a34ae", "name": "(Hair_WavyLong)", "display_name": "Wavy Long", "outfit_type": 2, "outfit_set": 0, "drawer_index": 0, "hides_hair": false, "hides_hand": false, "hides_left_ear": true, "hides_right_ear": true, "uses_hair_color": true, "uses_team_color": false, "is_team_item": false}, + {"guid": "e60fccda-8711-463d-b8a3-7cd5e76903b2", "name": "(Shirt_Conductor)", "display_name": "Conductor", "outfit_type": 101, "outfit_set": 0, "drawer_index": 0, "hides_hair": false, "hides_hand": false, "hides_left_ear": false, "hides_right_ear": false, "uses_hair_color": false, "uses_team_color": false, "is_team_item": false}, + {"guid": "e614df5d-3cfe-4bca-b9c2-30c267b9c94e", "name": "(Hat_Knight_Helmet)", "display_name": "Knight Helmet", "outfit_type": 0, "outfit_set": 1, "drawer_index": 0, "hides_hair": false, "hides_hand": false, "hides_left_ear": true, "hides_right_ear": true, "uses_hair_color": false, "uses_team_color": false, "is_team_item": false}, + {"guid": "e6216087-fb37-46f1-bda4-a88c4640f339", "name": "(Hat_JackfrostHair)", "display_name": "Jackfrost Hair", "outfit_type": 0, "outfit_set": 0, "drawer_index": 0, "hides_hair": true, "hides_hand": false, "hides_left_ear": false, "hides_right_ear": false, "uses_hair_color": false, "uses_team_color": false, "is_team_item": false}, + {"guid": "e773fddc-ad9f-408c-82c5-2637f75b3214", "name": "(Neck_BountyHunterCape)", "display_name": "Bounty Hunter Cape", "outfit_type": 100, "outfit_set": 0, "drawer_index": 0, "hides_hair": false, "hides_hand": false, "hides_left_ear": false, "hides_right_ear": false, "uses_hair_color": false, "uses_team_color": false, "is_team_item": false}, + {"guid": "e9913b06-58cb-4b90-bd08-981a3f19f352", "name": "(Shirt_Construction)", "display_name": "Construction", "outfit_type": 101, "outfit_set": 0, "drawer_index": 0, "hides_hair": false, "hides_hand": false, "hides_left_ear": false, "hides_right_ear": false, "uses_hair_color": false, "uses_team_color": false, "is_team_item": false}, + {"guid": "E9VTiIVFPEa-OuQRto0e_Q", "name": "(Shirt_SteamPunk)", "display_name": "Steam Punk", "outfit_type": 101, "outfit_set": 0, "drawer_index": 0, "hides_hair": false, "hides_hand": false, "hides_left_ear": false, "hides_right_ear": false, "uses_hair_color": false, "uses_team_color": false, "is_team_item": false}, + {"guid": "eada710d-ea0c-473f-b9c6-23e210607b24", "name": "(Wrist_Golfer)", "display_name": "Golfer", "outfit_type": 200, "outfit_set": 27, "drawer_index": 0, "hides_hair": false, "hides_hand": false, "hides_left_ear": false, "hides_right_ear": false, "uses_hair_color": false, "uses_team_color": false, "is_team_item": false}, + {"guid": "eb830b12-c1b5-4796-b9cf-ccf0f0e1d4f1", "name": "(Eye_MardiGrasEyes)", "display_name": "Mardi Gras Eyes", "outfit_type": 10, "outfit_set": 0, "drawer_index": 0, "hides_hair": false, "hides_hand": false, "hides_left_ear": false, "hides_right_ear": false, "uses_hair_color": false, "uses_team_color": false, "is_team_item": false}, + {"guid": "eb9611c6-bb50-41a2-93e9-7f959815a846", "name": "(Hair_DreadsLong)", "display_name": "Dreads Long", "outfit_type": 2, "outfit_set": 0, "drawer_index": 0, "hides_hair": false, "hides_hand": false, "hides_left_ear": false, "hides_right_ear": false, "uses_hair_color": true, "uses_team_color": false, "is_team_item": false}, + {"guid": "ec5255d5-edaf-4888-9fe1-e89ac0e0d300", "name": "(AstronautTorso)", "display_name": "Astronaut Torso", "outfit_type": 101, "outfit_set": 0, "drawer_index": 0, "hides_hair": false, "hides_hand": false, "hides_left_ear": false, "hides_right_ear": false, "uses_hair_color": false, "uses_team_color": false, "is_team_item": false}, + {"guid": "ec836d2a-b1b8-4566-b8eb-123ae2845956", "name": "(Hat_FighterPilot)", "display_name": "Fighter Pilot", "outfit_type": 0, "outfit_set": 26, "drawer_index": 0, "hides_hair": false, "hides_hand": false, "hides_left_ear": true, "hides_right_ear": true, "uses_hair_color": false, "uses_team_color": false, "is_team_item": false}, + {"guid": "ecc1dbe6-ca06-4564-b2a6-30956194d1e9", "name": "(Wrist_WristBand)", "display_name": "Wrist Band", "outfit_type": 200, "outfit_set": 0, "drawer_index": 0, "hides_hair": false, "hides_hand": false, "hides_left_ear": false, "hides_right_ear": false, "uses_hair_color": false, "uses_team_color": false, "is_team_item": false}, + {"guid": "eceb7d49-be41-4228-86a9-2973d62e7135", "name": "(Wrist_PirateCaptain)", "display_name": "Pirate Captain", "outfit_type": 200, "outfit_set": 0, "drawer_index": 0, "hides_hair": false, "hides_hand": false, "hides_left_ear": false, "hides_right_ear": false, "uses_hair_color": false, "uses_team_color": false, "is_team_item": false}, + {"guid": "eda29bce-5a07-4439-a2a7-cafa11e2ed62", "name": "(Belt_PaintballBelt)", "display_name": "Paintball Belt", "outfit_type": 102, "outfit_set": 0, "drawer_index": 0, "hides_hair": false, "hides_hand": false, "hides_left_ear": false, "hides_right_ear": false, "uses_hair_color": false, "uses_team_color": true, "is_team_item": false}, + {"guid": "ef7771eb-c5f3-44fd-8c27-f581b035ef10", "name": "(Hat_Bearhat)", "display_name": "Bearhat", "outfit_type": 0, "outfit_set": 0, "drawer_index": 0, "hides_hair": false, "hides_hand": false, "hides_left_ear": false, "hides_right_ear": false, "uses_hair_color": false, "uses_team_color": false, "is_team_item": false}, + {"guid": "ef84cb35-5e25-4aa6-82c9-17f01a5040c7", "name": "(Hat_Beret)", "display_name": "Beret", "outfit_type": 0, "outfit_set": 0, "drawer_index": 0, "hides_hair": false, "hides_hand": false, "hides_left_ear": false, "hides_right_ear": false, "uses_hair_color": false, "uses_team_color": false, "is_team_item": false}, + {"guid": "EJFGAW-TnUCaeD59parEHg", "name": "(Belt_PoolFloatie_BirdHead)", "display_name": "Pool Floatie Bird Head", "outfit_type": 102, "outfit_set": 0, "drawer_index": 0, "hides_hair": false, "hides_hand": false, "hides_left_ear": false, "hides_right_ear": false, "uses_hair_color": false, "uses_team_color": false, "is_team_item": false}, + {"guid": "eS_or4AnMka9M_bD7YN28g", "name": "(Wrist_MagicalGirl)", "display_name": "Magical Girl", "outfit_type": 200, "outfit_set": 0, "drawer_index": 0, "hides_hair": false, "hides_hand": true, "hides_left_ear": false, "hides_right_ear": false, "uses_hair_color": false, "uses_team_color": false, "is_team_item": false}, + {"guid": "exQz9Zr3HEyOR9nDDrimlQ", "name": "(Hat_LettermanShades)", "display_name": "Letterman Shades", "outfit_type": 10, "outfit_set": 0, "drawer_index": 0, "hides_hair": false, "hides_hand": false, "hides_left_ear": false, "hides_right_ear": false, "uses_hair_color": false, "uses_team_color": false, "is_team_item": false}, + {"guid": "f15d81c8-3d19-4bbe-87c0-1144aaa4138a", "name": "(Hat_DryadHornsHat)", "display_name": "Dryad Horns Hat", "outfit_type": 0, "outfit_set": 0, "drawer_index": 0, "hides_hair": false, "hides_hand": false, "hides_left_ear": true, "hides_right_ear": true, "uses_hair_color": false, "uses_team_color": false, "is_team_item": false}, + {"guid": "f17a2f9e-1eab-44e8-b10a-1f53c5d0778c", "name": "(Hair_Scientist)", "display_name": "Scientist", "outfit_type": 2, "outfit_set": 23, "drawer_index": 0, "hides_hair": false, "hides_hand": false, "hides_left_ear": false, "hides_right_ear": false, "uses_hair_color": true, "uses_team_color": false, "is_team_item": false}, + {"guid": "f218e2de-46fd-45b9-9e0e-670b6bb905e7", "name": "(Hat_AstronautHelmet)", "display_name": "Astronaut Helmet", "outfit_type": 0, "outfit_set": 0, "drawer_index": 0, "hides_hair": false, "hides_hand": false, "hides_left_ear": false, "hides_right_ear": false, "uses_hair_color": false, "uses_team_color": false, "is_team_item": false}, + {"guid": "f277af6a-0807-4716-8948-1bee236ffb74", "name": "(Hat_Ranger)", "display_name": "Ranger", "outfit_type": 0, "outfit_set": 0, "drawer_index": 0, "hides_hair": false, "hides_hand": false, "hides_left_ear": false, "hides_right_ear": false, "uses_hair_color": false, "uses_team_color": false, "is_team_item": false}, + {"guid": "f2c1c5e3-b201-4d34-9ce6-3082ff7e5d42", "name": "(Neck_BountyHunterBelt)", "display_name": "Bounty Hunter Belt", "outfit_type": 102, "outfit_set": 0, "drawer_index": 0, "hides_hair": false, "hides_hand": false, "hides_left_ear": false, "hides_right_ear": false, "uses_hair_color": false, "uses_team_color": false, "is_team_item": false}, + {"guid": "f41653f9-ae02-443c-aaf4-1c6f4e49035d", "name": "(FlowerSwimsuit)", "display_name": "Flower Swimsuit", "outfit_type": 101, "outfit_set": 0, "drawer_index": 0, "hides_hair": false, "hides_hand": false, "hides_left_ear": false, "hides_right_ear": false, "uses_hair_color": false, "uses_team_color": false, "is_team_item": false}, + {"guid": "f47f81e7-fcdd-4687-9b99-013147bef30f", "name": "(Neck_Safari)", "display_name": "Safari", "outfit_type": 100, "outfit_set": 33, "drawer_index": 0, "hides_hair": false, "hides_hand": false, "hides_left_ear": false, "hides_right_ear": false, "uses_hair_color": false, "uses_team_color": false, "is_team_item": false}, + {"guid": "f51a7ed5-52ea-485f-a430-303697f6fec9", "name": "(RedBeltDress)", "display_name": "Red Belt Dress", "outfit_type": 101, "outfit_set": 0, "drawer_index": 0, "hides_hair": false, "hides_hand": false, "hides_left_ear": false, "hides_right_ear": false, "uses_hair_color": false, "uses_team_color": false, "is_team_item": false}, + {"guid": "f527ffaf-da03-4519-82ed-46a9cb981dc3", "name": "(Glasses_Square)", "display_name": "Square", "outfit_type": 10, "outfit_set": 0, "drawer_index": 0, "hides_hair": false, "hides_hand": false, "hides_left_ear": false, "hides_right_ear": false, "uses_hair_color": false, "uses_team_color": false, "is_team_item": false}, + {"guid": "f5c9743f-e77c-42f2-ba63-9843342cbf8e", "name": "(Hat_SeaCaptain)", "display_name": "Sea Captain", "outfit_type": 0, "outfit_set": 0, "drawer_index": 0, "hides_hair": false, "hides_hand": false, "hides_left_ear": false, "hides_right_ear": false, "uses_hair_color": false, "uses_team_color": false, "is_team_item": false}, + {"guid": "f60e093f-33be-4527-b827-060adc77b1f4", "name": "(Hat_ScoutGarrison)", "display_name": "Scout Garrison", "outfit_type": 0, "outfit_set": 0, "drawer_index": 0, "hides_hair": false, "hides_hand": false, "hides_left_ear": false, "hides_right_ear": false, "uses_hair_color": false, "uses_team_color": false, "is_team_item": false}, + {"guid": "f684d86f-4a75-4a9a-bc0a-cf58f36c91c5", "name": "(Eye_MirrorShades_YellowBlue) Variant", "display_name": "Mirror Shades Yellow Blue) Variant", "outfit_type": 10, "outfit_set": 0, "drawer_index": 0, "hides_hair": false, "hides_hand": false, "hides_left_ear": false, "hides_right_ear": false, "uses_hair_color": false, "uses_team_color": false, "is_team_item": false}, + {"guid": "f6caeb89-dbb4-4471-92fe-100bf48cd5ce", "name": "(Dress_WaitressDress)", "display_name": "Waitress Dress", "outfit_type": 101, "outfit_set": 0, "drawer_index": 0, "hides_hair": false, "hides_hand": false, "hides_left_ear": false, "hides_right_ear": false, "uses_hair_color": false, "uses_team_color": false, "is_team_item": false}, + {"guid": "f76b709c-b173-426e-81f2-279a67d2fde1", "name": "(Shirt_Golfer)", "display_name": "Golfer", "outfit_type": 101, "outfit_set": 27, "drawer_index": 0, "hides_hair": false, "hides_hand": false, "hides_left_ear": false, "hides_right_ear": false, "uses_hair_color": false, "uses_team_color": false, "is_team_item": false}, + {"guid": "f9dd08f8-16d3-4c39-af4f-89f7bb6e80d3", "name": "(Hair_UnderCutSheet)", "display_name": "Under Cut Sheet", "outfit_type": 2, "outfit_set": 0, "drawer_index": 0, "hides_hair": false, "hides_hand": false, "hides_left_ear": false, "hides_right_ear": false, "uses_hair_color": true, "uses_team_color": false, "is_team_item": false}, + {"guid": "fa0e701c-5e80-46f4-8817-6e2dbf904734", "name": "(Wrist_SuperheroWrist)", "display_name": "Superhero Wrist", "outfit_type": 200, "outfit_set": 0, "drawer_index": 0, "hides_hair": false, "hides_hand": false, "hides_left_ear": false, "hides_right_ear": false, "uses_hair_color": false, "uses_team_color": false, "is_team_item": false}, + {"guid": "fb907eb2-91c8-45ff-84a5-462bb86ea7de", "name": "(Shirt_Ranger)", "display_name": "Ranger", "outfit_type": 101, "outfit_set": 0, "drawer_index": 0, "hides_hair": false, "hides_hand": false, "hides_left_ear": false, "hides_right_ear": false, "uses_hair_color": false, "uses_team_color": false, "is_team_item": false}, + {"guid": "fbdca48d-4ae6-41ad-8c33-b70a9e33fd77", "name": "(Hair_JackFrostNoCrystalsHair)", "display_name": "Jack Frost No Crystals Hair", "outfit_type": 2, "outfit_set": 0, "drawer_index": 0, "hides_hair": false, "hides_hand": false, "hides_left_ear": false, "hides_right_ear": false, "uses_hair_color": true, "uses_team_color": false, "is_team_item": false}, + {"guid": "fc96ea5d-b49b-479b-a963-92f1eab44b2c", "name": "(Neck_ButterflyWings)", "display_name": "Butterfly Wings", "outfit_type": 100, "outfit_set": 0, "drawer_index": 0, "hides_hair": false, "hides_hand": false, "hides_left_ear": false, "hides_right_ear": false, "uses_hair_color": false, "uses_team_color": false, "is_team_item": false}, + {"guid": "fcfcaf63-deb4-45f7-b711-c051c9ea45cb", "name": "(Hair_Bun)", "display_name": "Bun", "outfit_type": 2, "outfit_set": 0, "drawer_index": 0, "hides_hair": false, "hides_hand": false, "hides_left_ear": false, "hides_right_ear": false, "uses_hair_color": true, "uses_team_color": false, "is_team_item": false}, + {"guid": "fd7774bb-b29c-40b5-84b1-5c2bbe574255", "name": "(Dress_MermaidDress)", "display_name": "Mermaid Dress", "outfit_type": 101, "outfit_set": 0, "drawer_index": 0, "hides_hair": false, "hides_hand": false, "hides_left_ear": false, "hides_right_ear": false, "uses_hair_color": false, "uses_team_color": false, "is_team_item": false}, + {"guid": "fe15ca53-c5b8-4acf-9309-ff3f4e610fc9", "name": "(Hat_WingedTruckerHat)", "display_name": "Winged Trucker Hat", "outfit_type": 0, "outfit_set": 0, "drawer_index": 0, "hides_hair": false, "hides_hand": false, "hides_left_ear": false, "hides_right_ear": false, "uses_hair_color": false, "uses_team_color": false, "is_team_item": false}, + {"guid": "fe34adaf-47d3-435b-b2fd-b2fee05fceae", "name": "(Shirt_Scientist)", "display_name": "Scientist", "outfit_type": 101, "outfit_set": 23, "drawer_index": 0, "hides_hair": false, "hides_hand": false, "hides_left_ear": false, "hides_right_ear": false, "uses_hair_color": false, "uses_team_color": false, "is_team_item": false}, + {"guid": "ff15e25d-d467-47a8-9ecc-e415d5acb90c", "name": "(Shirt_Boxer)", "display_name": "Boxer", "outfit_type": 101, "outfit_set": 14, "drawer_index": 0, "hides_hair": false, "hides_hand": false, "hides_left_ear": false, "hides_right_ear": false, "uses_hair_color": false, "uses_team_color": false, "is_team_item": false}, + {"guid": "ff5c62b3-4f41-4acd-bfd8-8db00de4f36c", "name": "(Shirt_Chef)", "display_name": "Chef", "outfit_type": 101, "outfit_set": 0, "drawer_index": 0, "hides_hair": false, "hides_hand": false, "hides_left_ear": false, "hides_right_ear": false, "uses_hair_color": false, "uses_team_color": false, "is_team_item": false}, + {"guid": "ff7e6d41-5c27-4516-930c-ad0e9a23cce2", "name": "(Shirt_FighterPilot)", "display_name": "Fighter Pilot", "outfit_type": 101, "outfit_set": 26, "drawer_index": 0, "hides_hair": false, "hides_hand": false, "hides_left_ear": false, "hides_right_ear": false, "uses_hair_color": false, "uses_team_color": false, "is_team_item": false}, + {"guid": "ffad1160-8383-4b4a-a5b9-223476b49b92", "name": "(Shirt_SuperheroSuit)", "display_name": "Superhero Suit", "outfit_type": 101, "outfit_set": 0, "drawer_index": 0, "hides_hair": false, "hides_hand": false, "hides_left_ear": false, "hides_right_ear": false, "uses_hair_color": false, "uses_team_color": false, "is_team_item": false}, + {"guid": "ffea7a65-613f-4835-921e-6dd15f357b7e", "name": "(Hair_LongBangs)", "display_name": "Long Bangs", "outfit_type": 2, "outfit_set": 0, "drawer_index": 0, "hides_hair": false, "hides_hand": false, "hides_left_ear": true, "hides_right_ear": true, "uses_hair_color": true, "uses_team_color": false, "is_team_item": false}, + {"guid": "FHKZvPFMLkO8iAIPPQzu1A", "name": "(Shirt_50sDressValentine)", "display_name": "50s Dress Valentine", "outfit_type": 101, "outfit_set": 0, "drawer_index": 0, "hides_hair": false, "hides_hand": false, "hides_left_ear": false, "hides_right_ear": false, "uses_hair_color": false, "uses_team_color": false, "is_team_item": false}, + {"guid": "GAWURQSqrEW4G5slrPMgZA", "name": "(Shirt_BowlingSweater)", "display_name": "Bowling Sweater", "outfit_type": 101, "outfit_set": 0, "drawer_index": 0, "hides_hair": false, "hides_hand": false, "hides_left_ear": false, "hides_right_ear": false, "uses_hair_color": false, "uses_team_color": false, "is_team_item": false}, + {"guid": "gwLiwmqBIk6glFxvaTzxeA", "name": "(Hat_SteamPunk)", "display_name": "Steam Punk", "outfit_type": 0, "outfit_set": 0, "drawer_index": 0, "hides_hair": false, "hides_hand": false, "hides_left_ear": false, "hides_right_ear": false, "uses_hair_color": false, "uses_team_color": false, "is_team_item": false}, + {"guid": "h_To9WQYdEusYQxlbMmMmQ", "name": "(Neck_DraculaCape)", "display_name": "Dracula Cape", "outfit_type": 100, "outfit_set": 0, "drawer_index": 0, "hides_hair": false, "hides_hand": false, "hides_left_ear": false, "hides_right_ear": false, "uses_hair_color": false, "uses_team_color": false, "is_team_item": false}, + {"guid": "hT7nSD7Ts06F_1SNlOMLwg", "name": "(Hat_MagicalGirl)", "display_name": "Magical Girl", "outfit_type": 0, "outfit_set": 0, "drawer_index": 0, "hides_hair": false, "hides_hand": false, "hides_left_ear": false, "hides_right_ear": false, "uses_hair_color": false, "uses_team_color": false, "is_team_item": false}, + {"guid": "hTHM3Kjqy0ykqBD8UCRQJw", "name": "(Shirt_Thriller)", "display_name": "Thriller", "outfit_type": 101, "outfit_set": 0, "drawer_index": 0, "hides_hair": false, "hides_hand": false, "hides_left_ear": false, "hides_right_ear": false, "uses_hair_color": false, "uses_team_color": false, "is_team_item": false}, + {"guid": "iai6P7dDTUq8S4hqQmY1YA", "name": "(Shirt_SodaJerk)", "display_name": "Soda Jerk", "outfit_type": 101, "outfit_set": 0, "drawer_index": 0, "hides_hair": false, "hides_hand": false, "hides_left_ear": false, "hides_right_ear": false, "uses_hair_color": false, "uses_team_color": false, "is_team_item": false}, + {"guid": "iBQ-v6xaqkipUlfzkSHEeQ", "name": "(Neck_SteampunkSatchel)", "display_name": "Steampunk Satchel", "outfit_type": 102, "outfit_set": 0, "drawer_index": 0, "hides_hair": false, "hides_hand": false, "hides_left_ear": false, "hides_right_ear": false, "uses_hair_color": false, "uses_team_color": false, "is_team_item": false}, + {"guid": "ilDgmey2ZUWlFiuEAN7YoQ", "name": "(Wrist_IceQueen)", "display_name": "Ice Queen", "outfit_type": 200, "outfit_set": 0, "drawer_index": 0, "hides_hair": false, "hides_hand": true, "hides_left_ear": false, "hides_right_ear": false, "uses_hair_color": false, "uses_team_color": false, "is_team_item": false}, + {"guid": "ImwG0snB9ECc_gqRRBoz6A", "name": "(Neck_TreasureHunterBelt)", "display_name": "Treasure Hunter Belt", "outfit_type": 102, "outfit_set": 0, "drawer_index": 0, "hides_hair": false, "hides_hand": false, "hides_left_ear": false, "hides_right_ear": false, "uses_hair_color": false, "uses_team_color": false, "is_team_item": false}, + {"guid": "J6IQt4G1zUeN8v9Wn50BUw", "name": "(Hat_HeadbandTied)", "display_name": "Headband Tied", "outfit_type": 0, "outfit_set": 0, "drawer_index": 0, "hides_hair": false, "hides_hand": false, "hides_left_ear": false, "hides_right_ear": false, "uses_hair_color": false, "uses_team_color": false, "is_team_item": false}, + {"guid": "jiBzrzDPkkuL6DqIFKpcCw", "name": "(Neck_VampireHunter)", "display_name": "Vampire Hunter", "outfit_type": 103, "outfit_set": 0, "drawer_index": 0, "hides_hair": false, "hides_hand": false, "hides_left_ear": false, "hides_right_ear": false, "uses_hair_color": false, "uses_team_color": false, "is_team_item": false}, + {"guid": "JTrfBXhIT02o2-ssP94zew", "name": "(Hat_HeadBackBow)", "display_name": "Head Back Bow", "outfit_type": 0, "outfit_set": 0, "drawer_index": 0, "hides_hair": false, "hides_hand": false, "hides_left_ear": false, "hides_right_ear": false, "uses_hair_color": false, "uses_team_color": false, "is_team_item": false}, + {"guid": "jUpFzO5MPEGTbS-E0a99Nw", "name": "(Shirt_Tuxedo)", "display_name": "Tuxedo", "outfit_type": 101, "outfit_set": 0, "drawer_index": 0, "hides_hair": false, "hides_hand": false, "hides_left_ear": false, "hides_right_ear": false, "uses_hair_color": false, "uses_team_color": false, "is_team_item": false}, + {"guid": "JWanxvOHyESg9F_HlmTOfA", "name": "(Hat_SteampunkFemme)", "display_name": "Steampunk Femme", "outfit_type": 0, "outfit_set": 0, "drawer_index": 0, "hides_hair": false, "hides_hand": false, "hides_left_ear": false, "hides_right_ear": false, "uses_hair_color": false, "uses_team_color": false, "is_team_item": false}, + {"guid": "JWfs9IiQs0Weu8MmyGpqLQ", "name": "(Shirt_VampireHunter)", "display_name": "Vampire Hunter", "outfit_type": 101, "outfit_set": 0, "drawer_index": 0, "hides_hair": false, "hides_hand": false, "hides_left_ear": false, "hides_right_ear": false, "uses_hair_color": false, "uses_team_color": false, "is_team_item": false}, + {"guid": "kghEf_iMKUSxPO-h5iRhSQ", "name": "(Eye_SquareWide)", "display_name": "Square Wide", "outfit_type": 10, "outfit_set": 0, "drawer_index": 0, "hides_hair": false, "hides_hand": false, "hides_left_ear": false, "hides_right_ear": false, "uses_hair_color": false, "uses_team_color": false, "is_team_item": false}, + {"guid": "KNZ1DvCLC0WHyH4opYAXtw", "name": "(Eye_SciFiVisor_Contest)", "display_name": "Sci Fi Visor Contest", "outfit_type": 10, "outfit_set": 0, "drawer_index": 0, "hides_hair": false, "hides_hand": false, "hides_left_ear": false, "hides_right_ear": false, "uses_hair_color": false, "uses_team_color": false, "is_team_item": false}, + {"guid": "KPmC_wyX_ECe7fdKwoLMGg", "name": "(Hat_GraduationCap)", "display_name": "Graduation Cap", "outfit_type": 0, "outfit_set": 0, "drawer_index": 0, "hides_hair": false, "hides_hand": false, "hides_left_ear": false, "hides_right_ear": false, "uses_hair_color": false, "uses_team_color": false, "is_team_item": false}, + {"guid": "LK8jt9zCuUGwLRccgOOvjA", "name": "(Shirt_SteampunkFemme)", "display_name": "Steampunk Femme", "outfit_type": 101, "outfit_set": 0, "drawer_index": 0, "hides_hair": false, "hides_hand": false, "hides_left_ear": false, "hides_right_ear": false, "uses_hair_color": false, "uses_team_color": false, "is_team_item": false}, + {"guid": "Lli8-aqt1EmCmyXwBRQaYA", "name": "(Hat_Mummy)", "display_name": "Mummy", "outfit_type": 0, "outfit_set": 0, "drawer_index": 0, "hides_hair": true, "hides_hand": false, "hides_left_ear": true, "hides_right_ear": true, "uses_hair_color": false, "uses_team_color": false, "is_team_item": false}, + {"guid": "m5Plrduzr0yj_9mTn1x3Vw", "name": "(Shirt_GuardiansJacket)", "display_name": "Guardians Jacket", "outfit_type": 101, "outfit_set": 0, "drawer_index": 0, "hides_hair": false, "hides_hand": false, "hides_left_ear": false, "hides_right_ear": false, "uses_hair_color": false, "uses_team_color": false, "is_team_item": false}, + {"guid": "M9weaekvIUimiJSVJqZl-w", "name": "(Hat_TutorialCap)", "display_name": "Tutorial Cap", "outfit_type": 0, "outfit_set": 0, "drawer_index": 0, "hides_hair": false, "hides_hand": false, "hides_left_ear": false, "hides_right_ear": false, "uses_hair_color": false, "uses_team_color": false, "is_team_item": false}, + {"guid": "mfFLpKQRE0anhrmeUvW0bA", "name": "(Hat_LaurelCrown)", "display_name": "Laurel Crown", "outfit_type": 0, "outfit_set": 0, "drawer_index": 0, "hides_hair": false, "hides_hand": false, "hides_left_ear": false, "hides_right_ear": false, "uses_hair_color": false, "uses_team_color": false, "is_team_item": false}, + {"guid": "MJkDYDH510izAab13ZfXQw", "name": "(Shirt_MagicalGirl)", "display_name": "Magical Girl", "outfit_type": 101, "outfit_set": 0, "drawer_index": 0, "hides_hair": false, "hides_hand": false, "hides_left_ear": false, "hides_right_ear": false, "uses_hair_color": false, "uses_team_color": false, "is_team_item": false}, + {"guid": "MJQiQ3LATEOymxCC8wfSjw", "name": "(Shirt_TutorialGown_2)", "display_name": "Tutorial Gown 2", "outfit_type": 101, "outfit_set": 0, "drawer_index": 0, "hides_hair": false, "hides_hand": false, "hides_left_ear": false, "hides_right_ear": false, "uses_hair_color": false, "uses_team_color": false, "is_team_item": false}, + {"guid": "n52um7A-b0utGxixd1ESvA", "name": "(Shirt_Biker)", "display_name": "Biker", "outfit_type": 101, "outfit_set": 0, "drawer_index": 0, "hides_hair": false, "hides_hand": false, "hides_left_ear": false, "hides_right_ear": false, "uses_hair_color": false, "uses_team_color": false, "is_team_item": false}, + {"guid": "O8VqC11xNkSKpDh6Omp_0w", "name": "(Neck_TutorialSash_3)", "display_name": "Tutorial Sash 3", "outfit_type": 100, "outfit_set": 0, "drawer_index": 0, "hides_hair": false, "hides_hand": false, "hides_left_ear": false, "hides_right_ear": false, "uses_hair_color": false, "uses_team_color": false, "is_team_item": false}, + {"guid": "oRUh266ohkWQil14uxcEdQ", "name": "(Shirt_Mummy)", "display_name": "Mummy", "outfit_type": 101, "outfit_set": 0, "drawer_index": 0, "hides_hair": false, "hides_hand": false, "hides_left_ear": false, "hides_right_ear": false, "uses_hair_color": false, "uses_team_color": false, "is_team_item": false}, + {"guid": "OTnfT-4NQU6Gd_iVPDx8eg", "name": "(Hat_Frankenstein)", "display_name": "Frankenstein", "outfit_type": 0, "outfit_set": 0, "drawer_index": 0, "hides_hair": true, "hides_hand": false, "hides_left_ear": false, "hides_right_ear": false, "uses_hair_color": false, "uses_team_color": false, "is_team_item": false}, + {"guid": "oYvOo47DJE-DEPlpn5xNUw", "name": "(Neck_FannyPack)", "display_name": "Fanny Pack", "outfit_type": 102, "outfit_set": 0, "drawer_index": 0, "hides_hair": false, "hides_hand": false, "hides_left_ear": false, "hides_right_ear": false, "uses_hair_color": false, "uses_team_color": false, "is_team_item": false}, + {"guid": "pNOfTDOgJUGQvGAlTjBoCg", "name": "(Neck_ShortFatNecktie)", "display_name": "Short Fat Necktie", "outfit_type": 100, "outfit_set": 0, "drawer_index": 0, "hides_hair": false, "hides_hand": false, "hides_left_ear": false, "hides_right_ear": false, "uses_hair_color": false, "uses_team_color": false, "is_team_item": false}, + {"guid": "pQqNvrhDJ0uuipKHA2qUOA", "name": "(Shirt_Suspenders)", "display_name": "Suspenders", "outfit_type": 101, "outfit_set": 0, "drawer_index": 0, "hides_hair": false, "hides_hand": false, "hides_left_ear": false, "hides_right_ear": false, "uses_hair_color": false, "uses_team_color": false, "is_team_item": false}, + {"guid": "PryEX4MEvkGKwST6dqJHjA", "name": "(shirt_QpidToga)", "display_name": "Qpid Toga", "outfit_type": 101, "outfit_set": 0, "drawer_index": 0, "hides_hair": false, "hides_hand": false, "hides_left_ear": false, "hides_right_ear": false, "uses_hair_color": false, "uses_team_color": false, "is_team_item": false}, + {"guid": "q7pSsRMNHkqEJmYHuhhG6g", "name": "(Wrist_Grillmaster)", "display_name": "Grillmaster", "outfit_type": 200, "outfit_set": 0, "drawer_index": 0, "hides_hair": false, "hides_hand": true, "hides_left_ear": false, "hides_right_ear": false, "uses_hair_color": false, "uses_team_color": false, "is_team_item": false}, + {"guid": "QCtqxbvYOkan79YFugH_Cw", "name": "(Shirt_Countess)", "display_name": "Countess", "outfit_type": 101, "outfit_set": 0, "drawer_index": 0, "hides_hair": false, "hides_hand": false, "hides_left_ear": false, "hides_right_ear": false, "uses_hair_color": false, "uses_team_color": false, "is_team_item": false}, + {"guid": "QK6lBlX_wUCiSbJ9RkIwdA", "name": "(Shirt_TreasureHunter)", "display_name": "Treasure Hunter", "outfit_type": 101, "outfit_set": 0, "drawer_index": 0, "hides_hair": false, "hides_hand": false, "hides_left_ear": false, "hides_right_ear": false, "uses_hair_color": false, "uses_team_color": false, "is_team_item": false}, + {"guid": "qxovV6Vym0ufL2cTCyKjOQ", "name": "(Wrist_SteamPunk)", "display_name": "Steam Punk", "outfit_type": 200, "outfit_set": 0, "drawer_index": 0, "hides_hair": false, "hides_hand": false, "hides_left_ear": false, "hides_right_ear": false, "uses_hair_color": false, "uses_team_color": false, "is_team_item": false}, + {"guid": "ReqOfohe6UamzaiHG1x3Ow", "name": "(Hat_TruckerHat)", "display_name": "Trucker Hat", "outfit_type": 0, "outfit_set": 0, "drawer_index": 0, "hides_hair": false, "hides_hand": false, "hides_left_ear": false, "hides_right_ear": false, "uses_hair_color": false, "uses_team_color": false, "is_team_item": false}, + {"guid": "rJ9zPPGdlk2i24sxLfkqng", "name": "(Shirt_Snorkeler)", "display_name": "Snorkeler", "outfit_type": 101, "outfit_set": 0, "drawer_index": 0, "hides_hair": false, "hides_hand": false, "hides_left_ear": false, "hides_right_ear": false, "uses_hair_color": false, "uses_team_color": false, "is_team_item": false}, + {"guid": "s6mUIqiPCk6e57UYCx3SZA", "name": "(Hat_Witch)", "display_name": "Witch", "outfit_type": 0, "outfit_set": 0, "drawer_index": 0, "hides_hair": false, "hides_hand": false, "hides_left_ear": false, "hides_right_ear": false, "uses_hair_color": false, "uses_team_color": false, "is_team_item": false}, + {"guid": "sL7iHCnjyEuQKzrTvciEzQ", "name": "(Hat_BrideofFrank)", "display_name": "Brideof Frank", "outfit_type": 0, "outfit_set": 0, "drawer_index": 0, "hides_hair": true, "hides_hand": false, "hides_left_ear": false, "hides_right_ear": false, "uses_hair_color": false, "uses_team_color": false, "is_team_item": false}, + {"guid": "sOREgAGs-06CeGGsG-WIkw", "name": "(Neck_SteamPunk)", "display_name": "Steam Punk", "outfit_type": 100, "outfit_set": 0, "drawer_index": 0, "hides_hair": false, "hides_hand": false, "hides_left_ear": false, "hides_right_ear": false, "uses_hair_color": false, "uses_team_color": false, "is_team_item": false}, + {"guid": "-SQLtL4rdEWTJdw2IroQCQ", "name": "(Hat_TreasureHunter)", "display_name": "Treasure Hunter", "outfit_type": 0, "outfit_set": 0, "drawer_index": 0, "hides_hair": false, "hides_hand": false, "hides_left_ear": false, "hides_right_ear": false, "uses_hair_color": false, "uses_team_color": false, "is_team_item": false}, + {"guid": "t_66r_j5oUSh9awIPmdUKg", "name": "(Neck_TreasureHunterBandolier)", "display_name": "Treasure Hunter Bandolier", "outfit_type": 100, "outfit_set": 0, "drawer_index": 0, "hides_hair": false, "hides_hand": false, "hides_left_ear": false, "hides_right_ear": false, "uses_hair_color": false, "uses_team_color": false, "is_team_item": false}, + {"guid": "t9co2516nkuJO4LIJA1bJA", "name": "(Wrist_Countess)", "display_name": "Countess", "outfit_type": 200, "outfit_set": 0, "drawer_index": 0, "hides_hair": false, "hides_hand": false, "hides_left_ear": false, "hides_right_ear": false, "uses_hair_color": false, "uses_team_color": false, "is_team_item": false}, + {"guid": "TcgHS_0aPkehrBg7ytMOMQ", "name": "(Eye_Clubmasters)", "display_name": "Clubmasters", "outfit_type": 10, "outfit_set": 0, "drawer_index": 0, "hides_hair": false, "hides_hand": false, "hides_left_ear": false, "hides_right_ear": false, "uses_hair_color": false, "uses_team_color": false, "is_team_item": false}, + {"guid": "TjrFi6TFhUC5tQXWkZiK9A", "name": "(Shirt_TreasureHunterTank)", "display_name": "Treasure Hunter Tank", "outfit_type": 101, "outfit_set": 0, "drawer_index": 0, "hides_hair": false, "hides_hand": false, "hides_left_ear": false, "hides_right_ear": false, "uses_hair_color": false, "uses_team_color": false, "is_team_item": false}, + {"guid": "tkbHKZ9wu0W00VGeUbIaaw", "name": "(Wrist_Frankenstein)", "display_name": "Frankenstein", "outfit_type": 200, "outfit_set": 0, "drawer_index": 0, "hides_hair": false, "hides_hand": false, "hides_left_ear": false, "hides_right_ear": false, "uses_hair_color": false, "uses_team_color": false, "is_team_item": false}, + {"guid": "tTKv7eRODka2KC_hsY2O3Q", "name": "(Neck_Grillmaster)", "display_name": "Grillmaster", "outfit_type": 100, "outfit_set": 0, "drawer_index": 0, "hides_hair": false, "hides_hand": false, "hides_left_ear": false, "hides_right_ear": false, "uses_hair_color": false, "uses_team_color": false, "is_team_item": false}, + {"guid": "-twtjyBdQ02EAdOfBGTiEw", "name": "(Mouth_VanDyke)", "display_name": "Van Dyke", "outfit_type": 20, "outfit_set": 0, "drawer_index": 0, "hides_hair": false, "hides_hand": false, "hides_left_ear": false, "hides_right_ear": false, "uses_hair_color": true, "uses_team_color": false, "is_team_item": false}, + {"guid": "U_H976qYRUmg9e2clsAflg", "name": "(Shirt_GraduationGown)", "display_name": "Graduation Gown", "outfit_type": 101, "outfit_set": 0, "drawer_index": 0, "hides_hair": false, "hides_hand": false, "hides_left_ear": false, "hides_right_ear": false, "uses_hair_color": false, "uses_team_color": false, "is_team_item": false}, + {"guid": "UcPo9SsF3EGTCBB2J-Efdw", "name": "(Hat_Headphones)", "display_name": "Headphones", "outfit_type": 0, "outfit_set": 0, "drawer_index": 0, "hides_hair": false, "hides_hand": false, "hides_left_ear": true, "hides_right_ear": true, "uses_hair_color": false, "uses_team_color": false, "is_team_item": false}, + {"guid": "UGkQa4atNUe1cE5TN79s4Q", "name": "(Belt_PoolFloatie_Unicorn)", "display_name": "Pool Floatie Unicorn", "outfit_type": 102, "outfit_set": 0, "drawer_index": 0, "hides_hair": false, "hides_hand": false, "hides_left_ear": false, "hides_right_ear": false, "uses_hair_color": false, "uses_team_color": false, "is_team_item": false}, + {"guid": "uubY_Cn9A0-Ww4lsIRCF6w", "name": "(Neck_DraculaMedal)", "display_name": "Dracula Medal", "outfit_type": 103, "outfit_set": 0, "drawer_index": 0, "hides_hair": false, "hides_hand": false, "hides_left_ear": false, "hides_right_ear": false, "uses_hair_color": false, "uses_team_color": false, "is_team_item": false}, + {"guid": "vDSlMnayzEqWXwT-lkcKSA", "name": "(Shirt_Tourist)", "display_name": "Tourist", "outfit_type": 101, "outfit_set": 0, "drawer_index": 0, "hides_hair": false, "hides_hand": false, "hides_left_ear": false, "hides_right_ear": false, "uses_hair_color": false, "uses_team_color": false, "is_team_item": false}, + {"guid": "vP9EKTMXP0yni8U7uCz2-g", "name": "(Neck_TutorialSash_2)", "display_name": "Tutorial Sash 2", "outfit_type": 100, "outfit_set": 0, "drawer_index": 0, "hides_hair": false, "hides_hand": false, "hides_left_ear": false, "hides_right_ear": false, "uses_hair_color": false, "uses_team_color": false, "is_team_item": false}, + {"guid": "vpP2ZvnL0Uy3n-I_Jv3eeQ", "name": "(Hat_CowboyHairy)", "display_name": "Cowboy Hairy", "outfit_type": 0, "outfit_set": 0, "drawer_index": 0, "hides_hair": false, "hides_hand": false, "hides_left_ear": false, "hides_right_ear": false, "uses_hair_color": false, "uses_team_color": false, "is_team_item": false}, + {"guid": "VQnoLstCyEGDPy1c38-GvA", "name": "(Hat_BikerHelmet)", "display_name": "Biker Helmet", "outfit_type": 0, "outfit_set": 0, "drawer_index": 0, "hides_hair": false, "hides_hand": false, "hides_left_ear": true, "hides_right_ear": true, "uses_hair_color": false, "uses_team_color": false, "is_team_item": false}, + {"guid": "W7AJ4UYlVkWF5nLbmpmRBg", "name": "(Hat_IceQueen)", "display_name": "Ice Queen", "outfit_type": 0, "outfit_set": 0, "drawer_index": 0, "hides_hair": false, "hides_hand": false, "hides_left_ear": false, "hides_right_ear": false, "uses_hair_color": false, "uses_team_color": false, "is_team_item": false}, + {"guid": "Wua43ED71UqL71ATtyS0iQ", "name": "(Hat_SodaJerk)", "display_name": "Soda Jerk", "outfit_type": 0, "outfit_set": 0, "drawer_index": 0, "hides_hair": false, "hides_hand": false, "hides_left_ear": false, "hides_right_ear": false, "uses_hair_color": false, "uses_team_color": false, "is_team_item": false}, + {"guid": "WuShlrJnHUKuscn8e6Rt2g", "name": "(Neck_MagicalGirl)", "display_name": "Magical Girl", "outfit_type": 100, "outfit_set": 0, "drawer_index": 0, "hides_hair": false, "hides_hand": false, "hides_left_ear": false, "hides_right_ear": false, "uses_hair_color": false, "uses_team_color": false, "is_team_item": false}, + {"guid": "wy2cfgb8mUqjMrIOwHH2ew", "name": "(Wrist_Witch)", "display_name": "Witch", "outfit_type": 200, "outfit_set": 0, "drawer_index": 0, "hides_hair": false, "hides_hand": false, "hides_left_ear": false, "hides_right_ear": false, "uses_hair_color": false, "uses_team_color": false, "is_team_item": false}, + {"guid": "zpjkkb1HtUORoPaODFuuxg", "name": "(Wrist_Mummy)", "display_name": "Mummy", "outfit_type": 200, "outfit_set": 0, "drawer_index": 0, "hides_hair": false, "hides_hand": false, "hides_left_ear": false, "hides_right_ear": false, "uses_hair_color": false, "uses_team_color": false, "is_team_item": false}, + {"guid": "ZsggwOJ8XUOGfaJltM418g", "name": "(Shirt_Frankenstein)", "display_name": "Frankenstein", "outfit_type": 101, "outfit_set": 0, "drawer_index": 0, "hides_hair": false, "hides_hand": false, "hides_left_ear": false, "hides_right_ear": false, "uses_hair_color": false, "uses_team_color": false, "is_team_item": false} + ], + "variants": [{"item": "75-HXZBOAEiDBqclnOn8YQ", "prefab": "75-HXZBOAEiDBqclnOn8YQ", "mask": "", "swatch": "", "decal": ""}, {"item": "75-HXZBOAEiDBqclnOn8YQ", "prefab": "75-HXZBOAEiDBqclnOn8YQ", "mask": "sx10-Hsfl0OS62pIFvHH8A", "swatch": "LRv1X0ZrjkqO7azGpZ7uMg", "decal": ""}, {"item": "75-HXZBOAEiDBqclnOn8YQ", "prefab": "75-HXZBOAEiDBqclnOn8YQ", "mask": "v1ZOE9G9gEWAqNJX1A9f2A", "swatch": "NMmOn0zQZ0mJLxCMnsbIVw", "decal": ""}, {"item": "awOFE0_GSECr8GBNBo6a_w", "prefab": "awOFE0_GSECr8GBNBo6a_w", "mask": "", "swatch": "", "decal": ""}, {"item": "EJFGAW-TnUCaeD59parEHg", "prefab": "EJFGAW-TnUCaeD59parEHg", "mask": "", "swatch": "", "decal": ""}, {"item": "EJFGAW-TnUCaeD59parEHg", "prefab": "EJFGAW-TnUCaeD59parEHg", "mask": "GtoHkTpyA0iGIHsxphwivg", "swatch": "k3dbqP5z30KU37MVPpsEPQ", "decal": ""}, {"item": "EJFGAW-TnUCaeD59parEHg", "prefab": "EJFGAW-TnUCaeD59parEHg", "mask": "qZzkrGXpPkGq_nnGLZorwA", "swatch": "hM0OmxjWLESbsJ-M0Z3QvQ", "decal": ""}, {"item": "UGkQa4atNUe1cE5TN79s4Q", "prefab": "UGkQa4atNUe1cE5TN79s4Q", "mask": "", "swatch": "", "decal": ""}, {"item": "da55bfc3-47b7-46f6-b548-7fb2c2a5e0bf", "prefab": "da55bfc3-47b7-46f6-b548-7fb2c2a5e0bf", "mask": "", "swatch": "", "decal": ""}, {"item": "da55bfc3-47b7-46f6-b548-7fb2c2a5e0bf", "prefab": "da55bfc3-47b7-46f6-b548-7fb2c2a5e0bf", "mask": "cb9e9e05-c481-4397-86e7-c2111bc0e817", "swatch": "d6823e01-69f0-4f85-b94a-74894356a2cf", "decal": ""}, {"item": "da55bfc3-47b7-46f6-b548-7fb2c2a5e0bf", "prefab": "da55bfc3-47b7-46f6-b548-7fb2c2a5e0bf", "mask": "cb9e9e05-c481-4397-86e7-c2111bc0e817", "swatch": "c5deba2a-6e35-4b13-8e94-8ba5457f39df", "decal": ""}, {"item": "da55bfc3-47b7-46f6-b548-7fb2c2a5e0bf", "prefab": "da55bfc3-47b7-46f6-b548-7fb2c2a5e0bf", "mask": "cb9e9e05-c481-4397-86e7-c2111bc0e817", "swatch": "e13cf33d-7588-4e2e-955e-f0a29d6380b7", "decal": ""}, {"item": "da55bfc3-47b7-46f6-b548-7fb2c2a5e0bf", "prefab": "da55bfc3-47b7-46f6-b548-7fb2c2a5e0bf", "mask": "cb9e9e05-c481-4397-86e7-c2111bc0e817", "swatch": "c9589974-c261-485e-b571-cb2f34fb178f", "decal": ""}, {"item": "da55bfc3-47b7-46f6-b548-7fb2c2a5e0bf", "prefab": "da55bfc3-47b7-46f6-b548-7fb2c2a5e0bf", "mask": "cb9e9e05-c481-4397-86e7-c2111bc0e817", "swatch": "da6d0ced-3d43-4878-a868-925f2ed6fd5b", "decal": ""}, {"item": "b02053b3-013f-42b0-8881-85f5be2b8cda", "prefab": "b02053b3-013f-42b0-8881-85f5be2b8cda", "mask": "", "swatch": "", "decal": ""}, {"item": "b02053b3-013f-42b0-8881-85f5be2b8cda", "prefab": "b02053b3-013f-42b0-8881-85f5be2b8cda", "mask": "93987d32-7a6e-45df-af56-76f912969ce7", "swatch": "6e0f3af8-297f-4ea7-8f9e-4a1bc57d3e35", "decal": ""}, {"item": "b02053b3-013f-42b0-8881-85f5be2b8cda", "prefab": "b02053b3-013f-42b0-8881-85f5be2b8cda", "mask": "93987d32-7a6e-45df-af56-76f912969ce7", "swatch": "faedf215-289b-40f7-90cc-f5b98517d133", "decal": ""}, {"item": "b02053b3-013f-42b0-8881-85f5be2b8cda", "prefab": "b02053b3-013f-42b0-8881-85f5be2b8cda", "mask": "93987d32-7a6e-45df-af56-76f912969ce7", "swatch": "1773d5d8-4714-4721-b30c-97e25d6f2a5a", "decal": ""}, {"item": "b02053b3-013f-42b0-8881-85f5be2b8cda", "prefab": "b02053b3-013f-42b0-8881-85f5be2b8cda", "mask": "93987d32-7a6e-45df-af56-76f912969ce7", "swatch": "05K7dmjigkCByO2ufw9jtw", "decal": ""}, {"item": "b02053b3-013f-42b0-8881-85f5be2b8cda", "prefab": "b02053b3-013f-42b0-8881-85f5be2b8cda", "mask": "93987d32-7a6e-45df-af56-76f912969ce7", "swatch": "yOw1WqEN0EWqR9t53EsQpw", "decal": ""}, {"item": "b02053b3-013f-42b0-8881-85f5be2b8cda", "prefab": "b02053b3-013f-42b0-8881-85f5be2b8cda", "mask": "93987d32-7a6e-45df-af56-76f912969ce7", "swatch": "qoVZuGa5NkOmyfikHjDhXw", "decal": ""}, {"item": "b02053b3-013f-42b0-8881-85f5be2b8cda", "prefab": "b02053b3-013f-42b0-8881-85f5be2b8cda", "mask": "F9vYkdcuGUOSGEg0wpQt2Q", "swatch": "yDZFr27Zq0-JH-EsEsxG2g", "decal": ""}, {"item": "b02053b3-013f-42b0-8881-85f5be2b8cda", "prefab": "b02053b3-013f-42b0-8881-85f5be2b8cda", "mask": "F9vYkdcuGUOSGEg0wpQt2Q", "swatch": "PLRhiOhhb0KhatFH7f3pwA", "decal": ""}, {"item": "b02053b3-013f-42b0-8881-85f5be2b8cda", "prefab": "b02053b3-013f-42b0-8881-85f5be2b8cda", "mask": "F9vYkdcuGUOSGEg0wpQt2Q", "swatch": "RwgnKTfMEkWrAp_OMGy__Q", "decal": ""}, {"item": "62ce4109-8dee-4895-bf1b-bfa143db4c7e", "prefab": "62ce4109-8dee-4895-bf1b-bfa143db4c7e", "mask": "", "swatch": "", "decal": ""}, {"item": "62ce4109-8dee-4895-bf1b-bfa143db4c7e", "prefab": "62ce4109-8dee-4895-bf1b-bfa143db4c7e", "mask": "0f36bb97-c61b-4281-929f-ff1d0d11be86", "swatch": "ad61c418-6d77-4a99-8ac5-9f10f5a3d42f", "decal": ""}, {"item": "62ce4109-8dee-4895-bf1b-bfa143db4c7e", "prefab": "62ce4109-8dee-4895-bf1b-bfa143db4c7e", "mask": "0f36bb97-c61b-4281-929f-ff1d0d11be86", "swatch": "cd5d7285-202d-42d0-b93f-04245875793e", "decal": ""}, {"item": "9c8fc7f0-8f99-4aad-a34f-8d979f6ae352", "prefab": "9c8fc7f0-8f99-4aad-a34f-8d979f6ae352", "mask": "", "swatch": "", "decal": ""}, {"item": "9c8fc7f0-8f99-4aad-a34f-8d979f6ae352", "prefab": "9c8fc7f0-8f99-4aad-a34f-8d979f6ae352", "mask": "dafa658e-753b-46cb-bd85-85c1de5e6ea7", "swatch": "49f5864f-9d40-497c-88c8-e87f64d41d74", "decal": ""}, {"item": "9c8fc7f0-8f99-4aad-a34f-8d979f6ae352", "prefab": "9c8fc7f0-8f99-4aad-a34f-8d979f6ae352", "mask": "dafa658e-753b-46cb-bd85-85c1de5e6ea7", "swatch": "e0397982-c2c2-4733-9a40-46e18675b5af", "decal": ""}, {"item": "9c8fc7f0-8f99-4aad-a34f-8d979f6ae352", "prefab": "9c8fc7f0-8f99-4aad-a34f-8d979f6ae352", "mask": "dafa658e-753b-46cb-bd85-85c1de5e6ea7", "swatch": "c5deba2a-6e35-4b13-8e94-8ba5457f39df", "decal": ""}, {"item": "896c2491-2f96-4986-9cbd-b3b31ef5d8c5", "prefab": "896c2491-2f96-4986-9cbd-b3b31ef5d8c5", "mask": "", "swatch": "", "decal": ""}, {"item": "896c2491-2f96-4986-9cbd-b3b31ef5d8c5", "prefab": "896c2491-2f96-4986-9cbd-b3b31ef5d8c5", "mask": "d344b8cc-85a8-4ace-9f92-38c84f396e99", "swatch": "55901f12-d5b5-4fa8-b4c8-e479689ee39d", "decal": ""}, {"item": "896c2491-2f96-4986-9cbd-b3b31ef5d8c5", "prefab": "896c2491-2f96-4986-9cbd-b3b31ef5d8c5", "mask": "d344b8cc-85a8-4ace-9f92-38c84f396e99", "swatch": "4828b50c-95b6-466a-bb25-514891d78202", "decal": ""}, {"item": "896c2491-2f96-4986-9cbd-b3b31ef5d8c5", "prefab": "896c2491-2f96-4986-9cbd-b3b31ef5d8c5", "mask": "d344b8cc-85a8-4ace-9f92-38c84f396e99", "swatch": "d6823e01-69f0-4f85-b94a-74894356a2cf", "decal": ""}, {"item": "657efe89-620a-46a2-8b58-cafd855c7cd5", "prefab": "657efe89-620a-46a2-8b58-cafd855c7cd5", "mask": "", "swatch": "", "decal": ""}, {"item": "a986ff33-9e16-4219-8ebb-2e7b4c772ca7", "prefab": "a986ff33-9e16-4219-8ebb-2e7b4c772ca7", "mask": "", "swatch": "", "decal": ""}, {"item": "a986ff33-9e16-4219-8ebb-2e7b4c772ca7", "prefab": "a986ff33-9e16-4219-8ebb-2e7b4c772ca7", "mask": "1fede91a-434e-49a5-882e-e6db107112a8", "swatch": "7b0d6661-e856-4ec1-ab90-d89c28a70826", "decal": ""}, {"item": "f41653f9-ae02-443c-aaf4-1c6f4e49035d", "prefab": "f41653f9-ae02-443c-aaf4-1c6f4e49035d", "mask": "", "swatch": "", "decal": ""}, {"item": "f41653f9-ae02-443c-aaf4-1c6f4e49035d", "prefab": "f41653f9-ae02-443c-aaf4-1c6f4e49035d", "mask": "3248a3ec-50d1-4c0a-96ae-25eb9d8ed622", "swatch": "cf515c87-3c0a-4443-9f63-5ea1bdbaa61a", "decal": ""}, {"item": "f41653f9-ae02-443c-aaf4-1c6f4e49035d", "prefab": "f41653f9-ae02-443c-aaf4-1c6f4e49035d", "mask": "3248a3ec-50d1-4c0a-96ae-25eb9d8ed622", "swatch": "27680277-aec1-41be-a476-9f51cf775580", "decal": ""}, {"item": "f41653f9-ae02-443c-aaf4-1c6f4e49035d", "prefab": "f41653f9-ae02-443c-aaf4-1c6f4e49035d", "mask": "3248a3ec-50d1-4c0a-96ae-25eb9d8ed622", "swatch": "5a96ac4d-03c3-4214-9dcb-c2044ff28f2e", "decal": ""}, {"item": "77c37481-a185-47ff-af55-65f9fdfcf9e5", "prefab": "77c37481-a185-47ff-af55-65f9fdfcf9e5", "mask": "", "swatch": "", "decal": ""}, {"item": "77c37481-a185-47ff-af55-65f9fdfcf9e5", "prefab": "77c37481-a185-47ff-af55-65f9fdfcf9e5", "mask": "2c0d912d-c184-4eab-8ee4-73b90ba7b0a6", "swatch": "43501b0c-eaaa-4ab3-9d03-2ca61d2e8fc9", "decal": ""}, {"item": "77c37481-a185-47ff-af55-65f9fdfcf9e5", "prefab": "77c37481-a185-47ff-af55-65f9fdfcf9e5", "mask": "2c0d912d-c184-4eab-8ee4-73b90ba7b0a6", "swatch": "643b5f2d-0666-4869-b699-03742de34d16", "decal": ""}, {"item": "6d815b35-6f68-4ed4-817d-70f141e1a571", "prefab": "6d815b35-6f68-4ed4-817d-70f141e1a571", "mask": "", "swatch": "", "decal": ""}, {"item": "6d815b35-6f68-4ed4-817d-70f141e1a571", "prefab": "6d815b35-6f68-4ed4-817d-70f141e1a571", "mask": "2c8924aa-68f8-4912-9759-18992f72f08a", "swatch": "f750de46-3758-4f7d-9709-0a84b1027009", "decal": ""}, {"item": "6d815b35-6f68-4ed4-817d-70f141e1a571", "prefab": "6d815b35-6f68-4ed4-817d-70f141e1a571", "mask": "2c8924aa-68f8-4912-9759-18992f72f08a", "swatch": "d66aa400-aa5a-4539-a25d-5f8ce94dc281", "decal": ""}, {"item": "6d815b35-6f68-4ed4-817d-70f141e1a571", "prefab": "6d815b35-6f68-4ed4-817d-70f141e1a571", "mask": "2c8924aa-68f8-4912-9759-18992f72f08a", "swatch": "6564acf1-4d70-4f92-92ac-08e2b76dbb6b", "decal": ""}, {"item": "84cd594c-1cd8-4b4d-8409-85c8fd5fb02a", "prefab": "84cd594c-1cd8-4b4d-8409-85c8fd5fb02a", "mask": "", "swatch": "", "decal": ""}, {"item": "84cd594c-1cd8-4b4d-8409-85c8fd5fb02a", "prefab": "84cd594c-1cd8-4b4d-8409-85c8fd5fb02a", "mask": "91a451c1-b285-4c48-b14d-59ded8cc006f", "swatch": "64850553-cdfe-455a-ac00-dafbe63d613e", "decal": ""}, {"item": "84cd594c-1cd8-4b4d-8409-85c8fd5fb02a", "prefab": "84cd594c-1cd8-4b4d-8409-85c8fd5fb02a", "mask": "91a451c1-b285-4c48-b14d-59ded8cc006f", "swatch": "761a3193-60f0-4190-80c7-285b8192e794", "decal": ""}, {"item": "84cd594c-1cd8-4b4d-8409-85c8fd5fb02a", "prefab": "84cd594c-1cd8-4b4d-8409-85c8fd5fb02a", "mask": "91a451c1-b285-4c48-b14d-59ded8cc006f", "swatch": "a819f49b-6c7a-49d3-9e6a-d9d79ef5019f", "decal": ""}, {"item": "745c5110-104e-4b37-aabf-7c690fd54811", "prefab": "745c5110-104e-4b37-aabf-7c690fd54811", "mask": "", "swatch": "", "decal": ""}, {"item": "745c5110-104e-4b37-aabf-7c690fd54811", "prefab": "745c5110-104e-4b37-aabf-7c690fd54811", "mask": "3ba69a39-a4e7-4505-bae7-8db31360ec1c", "swatch": "b42d903b-b393-4f49-af5e-38d0fb6c42b0", "decal": ""}, {"item": "745c5110-104e-4b37-aabf-7c690fd54811", "prefab": "745c5110-104e-4b37-aabf-7c690fd54811", "mask": "3ba69a39-a4e7-4505-bae7-8db31360ec1c", "swatch": "8cab8cc6-9a2d-434c-8b72-e66edbdd223c", "decal": ""}, {"item": "745c5110-104e-4b37-aabf-7c690fd54811", "prefab": "745c5110-104e-4b37-aabf-7c690fd54811", "mask": "3ba69a39-a4e7-4505-bae7-8db31360ec1c", "swatch": "657f28d5-672a-4f27-86c6-3cb00f25bb84", "decal": ""}, {"item": "f51a7ed5-52ea-485f-a430-303697f6fec9", "prefab": "f51a7ed5-52ea-485f-a430-303697f6fec9", "mask": "", "swatch": "", "decal": ""}, {"item": "f51a7ed5-52ea-485f-a430-303697f6fec9", "prefab": "f51a7ed5-52ea-485f-a430-303697f6fec9", "mask": "3d971439-41d1-43a2-8434-704f9ec8d906", "swatch": "5b58d879-2bc4-47ed-a996-1af2ace16425", "decal": ""}, {"item": "f51a7ed5-52ea-485f-a430-303697f6fec9", "prefab": "f51a7ed5-52ea-485f-a430-303697f6fec9", "mask": "3d971439-41d1-43a2-8434-704f9ec8d906", "swatch": "d320ad9b-ba5c-45ea-b2b8-0d3e409a9db1", "decal": ""}, {"item": "f51a7ed5-52ea-485f-a430-303697f6fec9", "prefab": "f51a7ed5-52ea-485f-a430-303697f6fec9", "mask": "3d971439-41d1-43a2-8434-704f9ec8d906", "swatch": "d61711fd-589a-4669-b991-9408a58fffd9", "decal": ""}, {"item": "f51a7ed5-52ea-485f-a430-303697f6fec9", "prefab": "f51a7ed5-52ea-485f-a430-303697f6fec9", "mask": "3d971439-41d1-43a2-8434-704f9ec8d906", "swatch": "oJFfs62tR0WVRry2Smt8yA", "decal": ""}, {"item": "f51a7ed5-52ea-485f-a430-303697f6fec9", "prefab": "f51a7ed5-52ea-485f-a430-303697f6fec9", "mask": "3d971439-41d1-43a2-8434-704f9ec8d906", "swatch": "MIfCkcrFOEasJqfDmxThdw", "decal": ""}, {"item": "515dfc36-7f7b-47bc-abee-380d68f41be6", "prefab": "515dfc36-7f7b-47bc-abee-380d68f41be6", "mask": "", "swatch": "", "decal": ""}, {"item": "515dfc36-7f7b-47bc-abee-380d68f41be6", "prefab": "515dfc36-7f7b-47bc-abee-380d68f41be6", "mask": "2d7361d2-31a2-404b-90da-10e89114e1f2", "swatch": "1acb1f56-4509-449f-9c6a-065bdeaa9ee3", "decal": ""}, {"item": "515dfc36-7f7b-47bc-abee-380d68f41be6", "prefab": "515dfc36-7f7b-47bc-abee-380d68f41be6", "mask": "2d7361d2-31a2-404b-90da-10e89114e1f2", "swatch": "e98c3f98-7844-4d6e-a21c-05027033c18c", "decal": ""}, {"item": "21caa68e-c3fa-474c-af5e-af1e742b7a60", "prefab": "21caa68e-c3fa-474c-af5e-af1e742b7a60", "mask": "", "swatch": "", "decal": ""}, {"item": "21caa68e-c3fa-474c-af5e-af1e742b7a60", "prefab": "21caa68e-c3fa-474c-af5e-af1e742b7a60", "mask": "b75ef67d-00c3-4ac1-9b72-212032460294", "swatch": "c5deba2a-6e35-4b13-8e94-8ba5457f39df", "decal": ""}, {"item": "21caa68e-c3fa-474c-af5e-af1e742b7a60", "prefab": "21caa68e-c3fa-474c-af5e-af1e742b7a60", "mask": "b75ef67d-00c3-4ac1-9b72-212032460294", "swatch": "758752bd-db2f-43d2-b580-55b3e1efffd5", "decal": ""}, {"item": "2296ed0d-df56-4d46-b33a-aae9230a47fc", "prefab": "2296ed0d-df56-4d46-b33a-aae9230a47fc", "mask": "", "swatch": "", "decal": ""}, {"item": "2296ed0d-df56-4d46-b33a-aae9230a47fc", "prefab": "2296ed0d-df56-4d46-b33a-aae9230a47fc", "mask": "cfabdefe-0890-436e-b2a3-b5c712e22955", "swatch": "bbfa08e3-8e6b-4e0f-b264-1b398d7cd44a", "decal": ""}, {"item": "2296ed0d-df56-4d46-b33a-aae9230a47fc", "prefab": "2296ed0d-df56-4d46-b33a-aae9230a47fc", "mask": "cfabdefe-0890-436e-b2a3-b5c712e22955", "swatch": "830be2fa-60a5-48cc-931f-34b670eae4bd", "decal": ""}, {"item": "2296ed0d-df56-4d46-b33a-aae9230a47fc", "prefab": "2296ed0d-df56-4d46-b33a-aae9230a47fc", "mask": "cfabdefe-0890-436e-b2a3-b5c712e22955", "swatch": "6d703981-2734-4c45-8983-cdd5f328902f", "decal": ""}, {"item": "a302741d-313b-4682-82e5-ac76764e662c", "prefab": "a302741d-313b-4682-82e5-ac76764e662c", "mask": "", "swatch": "", "decal": ""}, {"item": "a302741d-313b-4682-82e5-ac76764e662c", "prefab": "a302741d-313b-4682-82e5-ac76764e662c", "mask": "d30e3afe-414c-4b60-8be9-6fa4156a4b65", "swatch": "55901f12-d5b5-4fa8-b4c8-e479689ee39d", "decal": ""}, {"item": "a302741d-313b-4682-82e5-ac76764e662c", "prefab": "a302741d-313b-4682-82e5-ac76764e662c", "mask": "d30e3afe-414c-4b60-8be9-6fa4156a4b65", "swatch": "64850553-cdfe-455a-ac00-dafbe63d613e", "decal": ""}, {"item": "a302741d-313b-4682-82e5-ac76764e662c", "prefab": "a302741d-313b-4682-82e5-ac76764e662c", "mask": "d30e3afe-414c-4b60-8be9-6fa4156a4b65", "swatch": "31ac9d4d-3d6b-48f2-85d5-dc4b8165a17a", "decal": ""}, {"item": "47629ed1-5378-427c-ad61-4b44d2fe90cc", "prefab": "47629ed1-5378-427c-ad61-4b44d2fe90cc", "mask": "", "swatch": "", "decal": ""}, {"item": "47629ed1-5378-427c-ad61-4b44d2fe90cc", "prefab": "47629ed1-5378-427c-ad61-4b44d2fe90cc", "mask": "556f876a-70da-4104-b792-fca8ad67be0c", "swatch": "7b82c3c5-fd1f-45ec-b83f-e32668aa8dce", "decal": ""}, {"item": "47629ed1-5378-427c-ad61-4b44d2fe90cc", "prefab": "47629ed1-5378-427c-ad61-4b44d2fe90cc", "mask": "556f876a-70da-4104-b792-fca8ad67be0c", "swatch": "23428ca9-be75-4146-b400-df01d1dadac9", "decal": ""}, {"item": "47629ed1-5378-427c-ad61-4b44d2fe90cc", "prefab": "47629ed1-5378-427c-ad61-4b44d2fe90cc", "mask": "556f876a-70da-4104-b792-fca8ad67be0c", "swatch": "qJGX62O_HUiZWN61NAc1Lg", "decal": ""}, {"item": "_qXaNbm4yEeyalo372QbvA", "prefab": "_qXaNbm4yEeyalo372QbvA", "mask": "", "swatch": "", "decal": ""}, {"item": "TcgHS_0aPkehrBg7ytMOMQ", "prefab": "TcgHS_0aPkehrBg7ytMOMQ", "mask": "", "swatch": "", "decal": ""}, {"item": "7db6b49f-3e2a-4da8-bdfa-e9ad9ebc5ba6", "prefab": "7db6b49f-3e2a-4da8-bdfa-e9ad9ebc5ba6", "mask": "", "swatch": "", "decal": ""}, {"item": "7db6b49f-3e2a-4da8-bdfa-e9ad9ebc5ba6", "prefab": "7db6b49f-3e2a-4da8-bdfa-e9ad9ebc5ba6", "mask": "9c55b609-b7dc-4070-a3ad-6351a0054a06", "swatch": "f841cdfa-45ba-4f09-a117-503eef34cb4b", "decal": ""}, {"item": "e1a13235-e3b3-4b39-8e6f-e6449d4321b7", "prefab": "e1a13235-e3b3-4b39-8e6f-e6449d4321b7", "mask": "", "swatch": "", "decal": ""}, {"item": "e1a13235-e3b3-4b39-8e6f-e6449d4321b7", "prefab": "e1a13235-e3b3-4b39-8e6f-e6449d4321b7", "mask": "DE1C-RJkuEWIvpR0q1i42g", "swatch": "clFrpYWFQEmDkE14f3o_Ig", "decal": ""}, {"item": "02a5b48c-abba-4385-967b-ba900424829e", "prefab": "02a5b48c-abba-4385-967b-ba900424829e", "mask": "", "swatch": "", "decal": ""}, {"item": "KNZ1DvCLC0WHyH4opYAXtw", "prefab": "KNZ1DvCLC0WHyH4opYAXtw", "mask": "", "swatch": "", "decal": ""}, {"item": "KNZ1DvCLC0WHyH4opYAXtw", "prefab": "KNZ1DvCLC0WHyH4opYAXtw", "mask": "uB4gFxfrh0W5OtkmZdFN1Q", "swatch": "FwiizGjYnUaM1VJ53R1cCQ", "decal": ""}, {"item": "5fwjFQoC_Uu9ChvF6uNGZw", "prefab": "5fwjFQoC_Uu9ChvF6uNGZw", "mask": "", "swatch": "", "decal": ""}, {"item": "5fwjFQoC_Uu9ChvF6uNGZw", "prefab": "5fwjFQoC_Uu9ChvF6uNGZw", "mask": "SSY8ZYMgaE2Bl4oFmMjKQA", "swatch": "fXNkMWFMV0aXalK_y_8SHw", "decal": ""}, {"item": "22pyI3Kz-UullhcebC6DLQ", "prefab": "22pyI3Kz-UullhcebC6DLQ", "mask": "", "swatch": "", "decal": ""}, {"item": "kghEf_iMKUSxPO-h5iRhSQ", "prefab": "kghEf_iMKUSxPO-h5iRhSQ", "mask": "", "swatch": "", "decal": ""}, {"item": "CdeM6RJmPUumXtdmdM3RXA", "prefab": "CdeM6RJmPUumXtdmdM3RXA", "mask": "", "swatch": "", "decal": ""}, {"item": "cbN5e-t_jEKa4hzir5JAxQ", "prefab": "cbN5e-t_jEKa4hzir5JAxQ", "mask": "", "swatch": "", "decal": ""}, {"item": "34144f4c-f6ef-49e5-a286-1781e82e6782", "prefab": "34144f4c-f6ef-49e5-a286-1781e82e6782", "mask": "", "swatch": "", "decal": ""}, {"item": "06306723-ca20-4aa6-b7b3-917113f41ac3", "prefab": "06306723-ca20-4aa6-b7b3-917113f41ac3", "mask": "", "swatch": "", "decal": ""}, {"item": "c70005d5-6276-4a98-acb3-6a77bc19379a", "prefab": "c70005d5-6276-4a98-acb3-6a77bc19379a", "mask": "", "swatch": "", "decal": ""}, {"item": "11e508a7-fe7d-470e-b270-2dba33e2f7a1", "prefab": "11e508a7-fe7d-470e-b270-2dba33e2f7a1", "mask": "", "swatch": "", "decal": ""}, {"item": "b89be768-a169-4b24-8022-751e7b817865", "prefab": "b89be768-a169-4b24-8022-751e7b817865", "mask": "", "swatch": "", "decal": ""}, {"item": "b89be768-a169-4b24-8022-751e7b817865", "prefab": "b89be768-a169-4b24-8022-751e7b817865", "mask": "uB4gFxfrh0W5OtkmZdFN1Q", "swatch": "SXG9rdEkxkOR31Nk0aTOww", "decal": ""}, {"item": "b89be768-a169-4b24-8022-751e7b817865", "prefab": "b89be768-a169-4b24-8022-751e7b817865", "mask": "uB4gFxfrh0W5OtkmZdFN1Q", "swatch": "FFIpxInE-EKbk2GY-pIkyg", "decal": ""}, {"item": "f527ffaf-da03-4519-82ed-46a9cb981dc3", "prefab": "f527ffaf-da03-4519-82ed-46a9cb981dc3", "mask": "", "swatch": "", "decal": ""}, {"item": "f527ffaf-da03-4519-82ed-46a9cb981dc3", "prefab": "f527ffaf-da03-4519-82ed-46a9cb981dc3", "mask": "e0186718-4a18-434b-b96e-0c9c912f0d1f", "swatch": "a1c51d1e-1d5f-4f8a-bc0f-6a20eddfe58a", "decal": ""}, {"item": "f527ffaf-da03-4519-82ed-46a9cb981dc3", "prefab": "f527ffaf-da03-4519-82ed-46a9cb981dc3", "mask": "e0186718-4a18-434b-b96e-0c9c912f0d1f", "swatch": "c5884778-a87f-4612-9717-86fbb65d440f", "decal": ""}, {"item": "6427bd57-fcb2-420f-8b3e-d1da43470b84", "prefab": "6427bd57-fcb2-420f-8b3e-d1da43470b84", "mask": "", "swatch": "", "decal": ""}, {"item": "1d27b674-f9e2-4ffc-9d8c-a58a1be06457", "prefab": "1d27b674-f9e2-4ffc-9d8c-a58a1be06457", "mask": "", "swatch": "", "decal": ""}, {"item": "0088603e-ec3b-4478-8694-e6fb1989b3f2", "prefab": "0088603e-ec3b-4478-8694-e6fb1989b3f2", "mask": "", "swatch": "", "decal": ""}, {"item": "a6cbfe76-534a-4655-a8a8-3fed13d001c7", "prefab": "a6cbfe76-534a-4655-a8a8-3fed13d001c7", "mask": "", "swatch": "", "decal": ""}, {"item": "b861e5f3-fc6d-43b3-9861-c1b45cb493a8", "prefab": "b861e5f3-fc6d-43b3-9861-c1b45cb493a8", "mask": "", "swatch": "", "decal": ""}, {"item": "880a3cc0-7407-4b61-b759-f9dd890fe9e5", "prefab": "880a3cc0-7407-4b61-b759-f9dd890fe9e5", "mask": "", "swatch": "", "decal": ""}, {"item": "e36bcd98-7e85-43fa-89f8-57e4ec33823a", "prefab": "e36bcd98-7e85-43fa-89f8-57e4ec33823a", "mask": "", "swatch": "", "decal": ""}, {"item": "fcfcaf63-deb4-45f7-b711-c051c9ea45cb", "prefab": "fcfcaf63-deb4-45f7-b711-c051c9ea45cb", "mask": "", "swatch": "", "decal": ""}, {"item": "da4e7b34-2095-4a9e-801e-4f409039e0dd", "prefab": "da4e7b34-2095-4a9e-801e-4f409039e0dd", "mask": "", "swatch": "", "decal": ""}, {"item": "b148cb1e-df81-442f-aea6-ab1727aad00e", "prefab": "b148cb1e-df81-442f-aea6-ab1727aad00e", "mask": "", "swatch": "", "decal": ""}, {"item": "eb9611c6-bb50-41a2-93e9-7f959815a846", "prefab": "eb9611c6-bb50-41a2-93e9-7f959815a846", "mask": "", "swatch": "", "decal": ""}, {"item": "193a3bf9-abc0-4d78-8d63-92046908b1c5", "prefab": "193a3bf9-abc0-4d78-8d63-92046908b1c5", "mask": "", "swatch": "", "decal": ""}, {"item": "e286863c-2967-4d00-b837-b49487b9484a", "prefab": "e286863c-2967-4d00-b837-b49487b9484a", "mask": "", "swatch": "", "decal": ""}, {"item": "BJ2XrGvl40iIl28IbTfbKg", "prefab": "BJ2XrGvl40iIl28IbTfbKg", "mask": "", "swatch": "", "decal": ""}, {"item": "09177621-9ecd-4f6a-b6a5-64490139141d", "prefab": "09177621-9ecd-4f6a-b6a5-64490139141d", "mask": "", "swatch": "", "decal": ""}, {"item": "0753d7a4-8247-4fca-a6fc-359c26086140", "prefab": "0753d7a4-8247-4fca-a6fc-359c26086140", "mask": "", "swatch": "", "decal": ""}, {"item": "d84c0ff9-8fbe-4ed8-abf3-7996e81888ab", "prefab": "d84c0ff9-8fbe-4ed8-abf3-7996e81888ab", "mask": "", "swatch": "", "decal": ""}, {"item": "95ab7a7c-c35d-4da5-9955-0921064470b6", "prefab": "95ab7a7c-c35d-4da5-9955-0921064470b6", "mask": "", "swatch": "", "decal": ""}, {"item": "241506f6-bf88-4b46-b5fe-513a225421f4", "prefab": "241506f6-bf88-4b46-b5fe-513a225421f4", "mask": "", "swatch": "", "decal": ""}, {"item": "49397e16-1027-4286-9bc4-b59eae565ff0", "prefab": "49397e16-1027-4286-9bc4-b59eae565ff0", "mask": "", "swatch": "", "decal": ""}, {"item": "ffea7a65-613f-4835-921e-6dd15f357b7e", "prefab": "ffea7a65-613f-4835-921e-6dd15f357b7e", "mask": "", "swatch": "", "decal": ""}, {"item": "21599b51-c50f-43d8-ac5f-62c30cd02ca5", "prefab": "21599b51-c50f-43d8-ac5f-62c30cd02ca5", "mask": "", "swatch": "", "decal": ""}, {"item": "24a240f4-1574-420b-b898-a7e91f170759", "prefab": "24a240f4-1574-420b-b898-a7e91f170759", "mask": "", "swatch": "", "decal": ""}, {"item": "c45ed7b8-99bd-4a4b-a9ff-e16edf5d7a18", "prefab": "c45ed7b8-99bd-4a4b-a9ff-e16edf5d7a18", "mask": "", "swatch": "", "decal": ""}, {"item": "9d9fadb6-97eb-480e-a224-4e0179082071", "prefab": "9d9fadb6-97eb-480e-a224-4e0179082071", "mask": "", "swatch": "", "decal": ""}, {"item": "e49d5d78-157e-4bc8-b878-ff7c4963fdc4", "prefab": "e49d5d78-157e-4bc8-b878-ff7c4963fdc4", "mask": "", "swatch": "", "decal": ""}, {"item": "2b7f4dae-51da-4af0-b009-12d6005c85c6", "prefab": "2b7f4dae-51da-4af0-b009-12d6005c85c6", "mask": "", "swatch": "", "decal": ""}, {"item": "92302d9d-c527-418c-ac5d-1fa869727505", "prefab": "92302d9d-c527-418c-ac5d-1fa869727505", "mask": "", "swatch": "", "decal": ""}, {"item": "79b90274-6eec-4664-acfb-4a123334661e", "prefab": "79b90274-6eec-4664-acfb-4a123334661e", "mask": "", "swatch": "", "decal": ""}, {"item": "1fd69ef8-0b74-4962-af5a-67f0bf0358f2", "prefab": "1fd69ef8-0b74-4962-af5a-67f0bf0358f2", "mask": "", "swatch": "", "decal": ""}, {"item": "8b9f1413-e786-4a30-946c-9292f207875a", "prefab": "8b9f1413-e786-4a30-946c-9292f207875a", "mask": "", "swatch": "", "decal": ""}, {"item": "ce250e13-8137-4f62-9e52-2b1cebe7d0ad", "prefab": "ce250e13-8137-4f62-9e52-2b1cebe7d0ad", "mask": "", "swatch": "", "decal": ""}, {"item": "2cb4f372-3372-4583-8b57-c4e3988e3c28", "prefab": "2cb4f372-3372-4583-8b57-c4e3988e3c28", "mask": "", "swatch": "", "decal": ""}, {"item": "CTcrvbo3OEepIV4oW8bx4w", "prefab": "CTcrvbo3OEepIV4oW8bx4w", "mask": "", "swatch": "", "decal": ""}, {"item": "d8280c0c-d803-4513-be10-a0ba96d8821e", "prefab": "d8280c0c-d803-4513-be10-a0ba96d8821e", "mask": "", "swatch": "", "decal": ""}, {"item": "f17a2f9e-1eab-44e8-b10a-1f53c5d0778c", "prefab": "f17a2f9e-1eab-44e8-b10a-1f53c5d0778c", "mask": "", "swatch": "", "decal": ""}, {"item": "1a71064b-794f-40fa-9109-8ad36602b6e1", "prefab": "1a71064b-794f-40fa-9109-8ad36602b6e1", "mask": "", "swatch": "", "decal": ""}, {"item": "77d3c585-4928-4471-a425-89036efe7299", "prefab": "77d3c585-4928-4471-a425-89036efe7299", "mask": "", "swatch": "", "decal": ""}, {"item": "a12f724f-4a73-4ab8-aad4-6bfc662b4dd6", "prefab": "a12f724f-4a73-4ab8-aad4-6bfc662b4dd6", "mask": "", "swatch": "", "decal": ""}, {"item": "f9dd08f8-16d3-4c39-af4f-89f7bb6e80d3", "prefab": "f9dd08f8-16d3-4c39-af4f-89f7bb6e80d3", "mask": "", "swatch": "", "decal": ""}, {"item": "e5b83dfc-b2e1-4dcb-a4ab-9d3a4c8a34ae", "prefab": "e5b83dfc-b2e1-4dcb-a4ab-9d3a4c8a34ae", "mask": "", "swatch": "", "decal": ""}, {"item": "7dd6f7b0-7ba0-429f-a04f-e32d3a79ee61", "prefab": "7dd6f7b0-7ba0-429f-a04f-e32d3a79ee61", "mask": "", "swatch": "", "decal": ""}, {"item": "529015c7-3b4a-4a73-b2c3-33103339b6e3", "prefab": "529015c7-3b4a-4a73-b2c3-33103339b6e3", "mask": "", "swatch": "", "decal": ""}, {"item": "529015c7-3b4a-4a73-b2c3-33103339b6e3", "prefab": "529015c7-3b4a-4a73-b2c3-33103339b6e3", "mask": "fe01b6ed-3c36-47b6-bcfa-47864ef9d610", "swatch": "e821aa74-2d47-4388-bcfb-1893b8a83a52", "decal": ""}, {"item": "529015c7-3b4a-4a73-b2c3-33103339b6e3", "prefab": "529015c7-3b4a-4a73-b2c3-33103339b6e3", "mask": "fe01b6ed-3c36-47b6-bcfa-47864ef9d610", "swatch": "fed760b2-15e2-46b2-b1e3-cc3c8d780d5d", "decal": ""}, {"item": "529015c7-3b4a-4a73-b2c3-33103339b6e3", "prefab": "529015c7-3b4a-4a73-b2c3-33103339b6e3", "mask": "fe01b6ed-3c36-47b6-bcfa-47864ef9d610", "swatch": "90869e86-cbc5-4024-9d08-fcdc41c64eb5", "decal": ""}, {"item": "529015c7-3b4a-4a73-b2c3-33103339b6e3", "prefab": "529015c7-3b4a-4a73-b2c3-33103339b6e3", "mask": "fe01b6ed-3c36-47b6-bcfa-47864ef9d610", "swatch": "076c96ae-9f9b-45bf-9dc7-ce73d5ddf422", "decal": ""}, {"item": "529015c7-3b4a-4a73-b2c3-33103339b6e3", "prefab": "529015c7-3b4a-4a73-b2c3-33103339b6e3", "mask": "fe01b6ed-3c36-47b6-bcfa-47864ef9d610", "swatch": "002b07bb-4256-44ce-8ca4-25510e70b6b5", "decal": ""}, {"item": "529015c7-3b4a-4a73-b2c3-33103339b6e3", "prefab": "529015c7-3b4a-4a73-b2c3-33103339b6e3", "mask": "fe01b6ed-3c36-47b6-bcfa-47864ef9d610", "swatch": "bzYdPb8GWUS-CgT4wrziIA", "decal": ""}, {"item": "bu9tb-K7NEiocvZY3o7ukw", "prefab": "bu9tb-K7NEiocvZY3o7ukw", "mask": "", "swatch": "", "decal": ""}, {"item": "bu9tb-K7NEiocvZY3o7ukw", "prefab": "bu9tb-K7NEiocvZY3o7ukw", "mask": "tqyzKPdDwkWq3jfZ8jZdaA", "swatch": "ZpcM5H2Q1UCt0hh82nSu9w", "decal": ""}, {"item": "bu9tb-K7NEiocvZY3o7ukw", "prefab": "bu9tb-K7NEiocvZY3o7ukw", "mask": "tqyzKPdDwkWq3jfZ8jZdaA", "swatch": "7B7vpnMTfkGLcMdHCZ_kWA", "decal": ""}, {"item": "bu9tb-K7NEiocvZY3o7ukw", "prefab": "bu9tb-K7NEiocvZY3o7ukw", "mask": "tqyzKPdDwkWq3jfZ8jZdaA", "swatch": "nO-YacKU1kmLjTFAAqNeYQ", "decal": ""}, {"item": "bu9tb-K7NEiocvZY3o7ukw", "prefab": "bu9tb-K7NEiocvZY3o7ukw", "mask": "tqyzKPdDwkWq3jfZ8jZdaA", "swatch": "76CA_KCr806y1FA35m-YTg", "decal": ""}, {"item": "03f8c394-28fa-4087-978b-8d108f0bd969", "prefab": "03f8c394-28fa-4087-978b-8d108f0bd969", "mask": "", "swatch": "", "decal": ""}, {"item": "03f8c394-28fa-4087-978b-8d108f0bd969", "prefab": "03f8c394-28fa-4087-978b-8d108f0bd969", "mask": "efeb07ab-6253-436d-bb66-991ddad72e58", "swatch": "3f6c8bd2-2c1d-4c8c-a9de-16600fee9645", "decal": ""}, {"item": "03f8c394-28fa-4087-978b-8d108f0bd969", "prefab": "03f8c394-28fa-4087-978b-8d108f0bd969", "mask": "efeb07ab-6253-436d-bb66-991ddad72e58", "swatch": "d7f0ffd3-ccf9-465e-8afe-4d7efb78dd2c", "decal": ""}, {"item": "03f8c394-28fa-4087-978b-8d108f0bd969", "prefab": "03f8c394-28fa-4087-978b-8d108f0bd969", "mask": "efeb07ab-6253-436d-bb66-991ddad72e58", "swatch": "225209dc-3afc-40cf-858e-89f7afa00c0d", "decal": ""}, {"item": "AbYdU7O_9U-Lwg7zFVZzXQ", "prefab": "AbYdU7O_9U-Lwg7zFVZzXQ", "mask": "", "swatch": "", "decal": ""}, {"item": "d2d3264b-fbf6-40b5-9f10-c85ec737d112", "prefab": "d2d3264b-fbf6-40b5-9f10-c85ec737d112", "mask": "", "swatch": "", "decal": ""}, {"item": "d2d3264b-fbf6-40b5-9f10-c85ec737d112", "prefab": "d2d3264b-fbf6-40b5-9f10-c85ec737d112", "mask": "1ba65dd5-9df9-4ff3-be96-f83a7abf67b3", "swatch": "9b35ae52-febe-486a-8557-ec7190a9756e", "decal": ""}, {"item": "d2d3264b-fbf6-40b5-9f10-c85ec737d112", "prefab": "d2d3264b-fbf6-40b5-9f10-c85ec737d112", "mask": "1ba65dd5-9df9-4ff3-be96-f83a7abf67b3", "swatch": "5b5281c9-b795-4bb2-ba7b-ba52047ebe3f", "decal": ""}, {"item": "d2d3264b-fbf6-40b5-9f10-c85ec737d112", "prefab": "d2d3264b-fbf6-40b5-9f10-c85ec737d112", "mask": "1ba65dd5-9df9-4ff3-be96-f83a7abf67b3", "swatch": "6e152cc7-e93a-4408-8092-236db420b680", "decal": ""}, {"item": "d2d3264b-fbf6-40b5-9f10-c85ec737d112", "prefab": "d2d3264b-fbf6-40b5-9f10-c85ec737d112", "mask": "1ba65dd5-9df9-4ff3-be96-f83a7abf67b3", "swatch": "9ad37173-e640-4b9e-a474-716d482b73d2", "decal": ""}, {"item": "d2d3264b-fbf6-40b5-9f10-c85ec737d112", "prefab": "d2d3264b-fbf6-40b5-9f10-c85ec737d112", "mask": "1ba65dd5-9df9-4ff3-be96-f83a7abf67b3", "swatch": "lgqLGi01o02ZFC9Xs5Ofdg", "decal": ""}, {"item": "d2d3264b-fbf6-40b5-9f10-c85ec737d112", "prefab": "d2d3264b-fbf6-40b5-9f10-c85ec737d112", "mask": "1ba65dd5-9df9-4ff3-be96-f83a7abf67b3", "swatch": "61VZkFjB402brzWm4UsftA", "decal": ""}, {"item": "17aed0a0-70ed-49da-ad09-5bc4746f7718", "prefab": "17aed0a0-70ed-49da-ad09-5bc4746f7718", "mask": "", "swatch": "", "decal": ""}, {"item": "17aed0a0-70ed-49da-ad09-5bc4746f7718", "prefab": "17aed0a0-70ed-49da-ad09-5bc4746f7718", "mask": "3485a30d-27f8-450b-b5aa-1ff3cd227d19", "swatch": "14b43bf9-5ba0-484e-ae5d-55cfeb09a70d", "decal": ""}, {"item": "17aed0a0-70ed-49da-ad09-5bc4746f7718", "prefab": "17aed0a0-70ed-49da-ad09-5bc4746f7718", "mask": "3485a30d-27f8-450b-b5aa-1ff3cd227d19", "swatch": "5c8f36c5-07da-49f4-af02-d23207a43216", "decal": ""}, {"item": "f218e2de-46fd-45b9-9e0e-670b6bb905e7", "prefab": "f218e2de-46fd-45b9-9e0e-670b6bb905e7", "mask": "", "swatch": "", "decal": ""}, {"item": "f218e2de-46fd-45b9-9e0e-670b6bb905e7", "prefab": "f218e2de-46fd-45b9-9e0e-670b6bb905e7", "mask": "iQjkS1xYP0S63ltvKpkz5g", "swatch": "JFalLDUvm0aMm3wh1UVfVg", "decal": ""}, {"item": "7a7d8a06-7982-4bcb-b7bd-4d03b48889b4", "prefab": "7a7d8a06-7982-4bcb-b7bd-4d03b48889b4", "mask": "", "swatch": "", "decal": ""}, {"item": "7a7d8a06-7982-4bcb-b7bd-4d03b48889b4", "prefab": "7a7d8a06-7982-4bcb-b7bd-4d03b48889b4", "mask": "ff5ae41e-e446-4a96-8c11-49d85556fa81", "swatch": "a8de80d3-286a-4fdc-bed2-75d52f8c0964", "decal": ""}, {"item": "7a7d8a06-7982-4bcb-b7bd-4d03b48889b4", "prefab": "7a7d8a06-7982-4bcb-b7bd-4d03b48889b4", "mask": "ff5ae41e-e446-4a96-8c11-49d85556fa81", "swatch": "c6cf096c-f0cb-4500-80a0-bc2916b5a426", "decal": ""}, {"item": "7a7d8a06-7982-4bcb-b7bd-4d03b48889b4", "prefab": "7a7d8a06-7982-4bcb-b7bd-4d03b48889b4", "mask": "ff5ae41e-e446-4a96-8c11-49d85556fa81", "swatch": "FjnZ-D1hcESLOWL4Ukv7kw", "decal": ""}, {"item": "dcf9012d-f9e9-4f15-872e-f130af3b2efa", "prefab": "dcf9012d-f9e9-4f15-872e-f130af3b2efa", "mask": "", "swatch": "", "decal": ""}, {"item": "dcf9012d-f9e9-4f15-872e-f130af3b2efa", "prefab": "dcf9012d-f9e9-4f15-872e-f130af3b2efa", "mask": "83158223-e268-4036-8a6a-218a8430eb2c", "swatch": "631c9015-2a00-47ea-bb35-465a2ddbca5d", "decal": ""}, {"item": "dcf9012d-f9e9-4f15-872e-f130af3b2efa", "prefab": "dcf9012d-f9e9-4f15-872e-f130af3b2efa", "mask": "83158223-e268-4036-8a6a-218a8430eb2c", "swatch": "7f9cb939-36a4-40da-8337-025e1d7bf8e5", "decal": ""}, {"item": "14ef6b00-debf-4a85-9755-b4d37df496d3", "prefab": "14ef6b00-debf-4a85-9755-b4d37df496d3", "mask": "", "swatch": "", "decal": ""}, {"item": "14ef6b00-debf-4a85-9755-b4d37df496d3", "prefab": "14ef6b00-debf-4a85-9755-b4d37df496d3", "mask": "0b2395e1-ebcc-47e9-aaf1-faf9e9cec4cd", "swatch": "724c79a3-822c-422f-9462-a5374ee0211c", "decal": ""}, {"item": "14ef6b00-debf-4a85-9755-b4d37df496d3", "prefab": "14ef6b00-debf-4a85-9755-b4d37df496d3", "mask": "0b2395e1-ebcc-47e9-aaf1-faf9e9cec4cd", "swatch": "8377ab96-c908-457f-9fee-b784c9a759f3", "decal": ""}, {"item": "14ef6b00-debf-4a85-9755-b4d37df496d3", "prefab": "14ef6b00-debf-4a85-9755-b4d37df496d3", "mask": "0b2395e1-ebcc-47e9-aaf1-faf9e9cec4cd", "swatch": "dee70c38-7a99-4c2b-9181-665f1bf75aca", "decal": ""}, {"item": "14ef6b00-debf-4a85-9755-b4d37df496d3", "prefab": "14ef6b00-debf-4a85-9755-b4d37df496d3", "mask": "0b2395e1-ebcc-47e9-aaf1-faf9e9cec4cd", "swatch": "484b6c13-af22-4ad5-8c43-34c0de095d49", "decal": ""}, {"item": "14ef6b00-debf-4a85-9755-b4d37df496d3", "prefab": "14ef6b00-debf-4a85-9755-b4d37df496d3", "mask": "0b2395e1-ebcc-47e9-aaf1-faf9e9cec4cd", "swatch": "67bcca75-4ab1-4964-8688-9908c464d355", "decal": ""}, {"item": "14ef6b00-debf-4a85-9755-b4d37df496d3", "prefab": "14ef6b00-debf-4a85-9755-b4d37df496d3", "mask": "0b2395e1-ebcc-47e9-aaf1-faf9e9cec4cd", "swatch": "51ef8d39-2b94-4f9e-9620-07b6b0a913a5", "decal": ""}, {"item": "14ef6b00-debf-4a85-9755-b4d37df496d3", "prefab": "14ef6b00-debf-4a85-9755-b4d37df496d3", "mask": "0b2395e1-ebcc-47e9-aaf1-faf9e9cec4cd", "swatch": "1b1d08f2-12ca-43dd-a44f-ea2820b919b4", "decal": ""}, {"item": "14ef6b00-debf-4a85-9755-b4d37df496d3", "prefab": "14ef6b00-debf-4a85-9755-b4d37df496d3", "mask": "_yznwUwL80ejrS3vki0IKg", "swatch": "pqLkLbri9Eu48eryQIFGMg", "decal": ""}, {"item": "14ef6b00-debf-4a85-9755-b4d37df496d3", "prefab": "14ef6b00-debf-4a85-9755-b4d37df496d3", "mask": "SroEVC6vBkG42ILwCtKB9w", "swatch": "5iCrYb8MBkqN6IBEe3IipQ", "decal": ""}, {"item": "14ef6b00-debf-4a85-9755-b4d37df496d3", "prefab": "14ef6b00-debf-4a85-9755-b4d37df496d3", "mask": "r_GLekt6mkWxXw8zPPR8Mg", "swatch": "nZor-clxlkm8EO9Gco0Otg", "decal": ""}, {"item": "293e7ad3-01c9-43af-96e3-06c34145ff12", "prefab": "293e7ad3-01c9-43af-96e3-06c34145ff12", "mask": "", "swatch": "", "decal": ""}, {"item": "293e7ad3-01c9-43af-96e3-06c34145ff12", "prefab": "293e7ad3-01c9-43af-96e3-06c34145ff12", "mask": "FVdDQFOMQ06teQm2ANDPng", "swatch": "9967ce54-c2b7-437a-946e-11f8ee6d6fdd", "decal": ""}, {"item": "293e7ad3-01c9-43af-96e3-06c34145ff12", "prefab": "293e7ad3-01c9-43af-96e3-06c34145ff12", "mask": "FVdDQFOMQ06teQm2ANDPng", "swatch": "a1c51d1e-1d5f-4f8a-bc0f-6a20eddfe58a", "decal": ""}, {"item": "96bc2f9f-ce3a-4c4c-b92c-cfed4ed4594f", "prefab": "96bc2f9f-ce3a-4c4c-b92c-cfed4ed4594f", "mask": "", "swatch": "", "decal": ""}, {"item": "96bc2f9f-ce3a-4c4c-b92c-cfed4ed4594f", "prefab": "96bc2f9f-ce3a-4c4c-b92c-cfed4ed4594f", "mask": "e4b53a9a-ac95-49ae-bf68-615bb23fa075", "swatch": "1919d319-6cce-4500-8766-15fed6a13fa8", "decal": ""}, {"item": "96bc2f9f-ce3a-4c4c-b92c-cfed4ed4594f", "prefab": "96bc2f9f-ce3a-4c4c-b92c-cfed4ed4594f", "mask": "e4b53a9a-ac95-49ae-bf68-615bb23fa075", "swatch": "lBe3yxuE40eBmTrnEHBqQQ", "decal": ""}, {"item": "96bc2f9f-ce3a-4c4c-b92c-cfed4ed4594f", "prefab": "96bc2f9f-ce3a-4c4c-b92c-cfed4ed4594f", "mask": "e4b53a9a-ac95-49ae-bf68-615bb23fa075", "swatch": "oZ8k0Qv3SkG-DkY0_dP7UA", "decal": ""}, {"item": "ef84cb35-5e25-4aa6-82c9-17f01a5040c7", "prefab": "ef84cb35-5e25-4aa6-82c9-17f01a5040c7", "mask": "", "swatch": "", "decal": ""}, {"item": "ef84cb35-5e25-4aa6-82c9-17f01a5040c7", "prefab": "ef84cb35-5e25-4aa6-82c9-17f01a5040c7", "mask": "FWL8ZeMe9EerdB0_Fya3AA", "swatch": "7_n11PF4-0KqTZ9az23hqg", "decal": ""}, {"item": "ef84cb35-5e25-4aa6-82c9-17f01a5040c7", "prefab": "ef84cb35-5e25-4aa6-82c9-17f01a5040c7", "mask": "FWL8ZeMe9EerdB0_Fya3AA", "swatch": "TKbjEeTWfUyA1u4laoULMg", "decal": ""}, {"item": "ef84cb35-5e25-4aa6-82c9-17f01a5040c7", "prefab": "ef84cb35-5e25-4aa6-82c9-17f01a5040c7", "mask": "FWL8ZeMe9EerdB0_Fya3AA", "swatch": "JShcoWWPz0297HeEUJbDCQ", "decal": ""}, {"item": "ef84cb35-5e25-4aa6-82c9-17f01a5040c7", "prefab": "ef84cb35-5e25-4aa6-82c9-17f01a5040c7", "mask": "FWL8ZeMe9EerdB0_Fya3AA", "swatch": "_psc7WCgOk2JzRQwuFAqNw", "decal": ""}, {"item": "ef84cb35-5e25-4aa6-82c9-17f01a5040c7", "prefab": "ef84cb35-5e25-4aa6-82c9-17f01a5040c7", "mask": "FWL8ZeMe9EerdB0_Fya3AA", "swatch": "WeP_tPjUNUuGojXrVBOAIA", "decal": ""}, {"item": "71014872-d879-4858-9d68-58da3c673d8b", "prefab": "71014872-d879-4858-9d68-58da3c673d8b", "mask": "", "swatch": "", "decal": ""}, {"item": "71014872-d879-4858-9d68-58da3c673d8b", "prefab": "71014872-d879-4858-9d68-58da3c673d8b", "mask": "5fd9e83a-cfaf-4c98-bd31-91a7484c26dd", "swatch": "83522c12-9549-4d74-8c87-bae210bcd2bf", "decal": ""}, {"item": "71014872-d879-4858-9d68-58da3c673d8b", "prefab": "71014872-d879-4858-9d68-58da3c673d8b", "mask": "5fd9e83a-cfaf-4c98-bd31-91a7484c26dd", "swatch": "4398ce1a-e6ee-4da4-ad0d-7abfab41020c", "decal": ""}, {"item": "71014872-d879-4858-9d68-58da3c673d8b", "prefab": "71014872-d879-4858-9d68-58da3c673d8b", "mask": "5fd9e83a-cfaf-4c98-bd31-91a7484c26dd", "swatch": "XNq4Cns4lEGydtktdsXlmQ", "decal": ""}, {"item": "VQnoLstCyEGDPy1c38-GvA", "prefab": "VQnoLstCyEGDPy1c38-GvA", "mask": "", "swatch": "", "decal": ""}, {"item": "1e504fee-b593-4037-bd8f-b88025147991", "prefab": "1e504fee-b593-4037-bd8f-b88025147991", "mask": "", "swatch": "", "decal": ""}, {"item": "1e504fee-b593-4037-bd8f-b88025147991", "prefab": "1e504fee-b593-4037-bd8f-b88025147991", "mask": "e0186718-4a18-434b-b96e-0c9c912f0d1f", "swatch": "c9589974-c261-485e-b571-cb2f34fb178f", "decal": ""}, {"item": "1e504fee-b593-4037-bd8f-b88025147991", "prefab": "1e504fee-b593-4037-bd8f-b88025147991", "mask": "e0186718-4a18-434b-b96e-0c9c912f0d1f", "swatch": "6564acf1-4d70-4f92-92ac-08e2b76dbb6b", "decal": ""}, {"item": "1e504fee-b593-4037-bd8f-b88025147991", "prefab": "1e504fee-b593-4037-bd8f-b88025147991", "mask": "3290f148-1bd1-4ffd-a947-61fd0529af05", "swatch": "0809a5cf-a0f3-4614-9f98-71518524518a", "decal": ""}, {"item": "b99a89c7-38c5-4196-9862-4e1bf9cb0ce1", "prefab": "b99a89c7-38c5-4196-9862-4e1bf9cb0ce1", "mask": "", "swatch": "", "decal": ""}, {"item": "b99a89c7-38c5-4196-9862-4e1bf9cb0ce1", "prefab": "b99a89c7-38c5-4196-9862-4e1bf9cb0ce1", "mask": "92714eac-cc07-4f74-a6f5-cfbbadd07e06", "swatch": "8c7b09f2-6213-4ef9-87ab-a2df72d07a22", "decal": ""}, {"item": "b99a89c7-38c5-4196-9862-4e1bf9cb0ce1", "prefab": "b99a89c7-38c5-4196-9862-4e1bf9cb0ce1", "mask": "92714eac-cc07-4f74-a6f5-cfbbadd07e06", "swatch": "rzUd1IMTKUmW0HeMPSIOBQ", "decal": ""}, {"item": "sL7iHCnjyEuQKzrTvciEzQ", "prefab": "sL7iHCnjyEuQKzrTvciEzQ", "mask": "", "swatch": "", "decal": ""}, {"item": "71fd5353-b439-4a23-b471-bfb99b8154d7", "prefab": "71fd5353-b439-4a23-b471-bfb99b8154d7", "mask": "", "swatch": "", "decal": ""}, {"item": "3d5184e1-0201-4476-bf4b-4eb28190de62", "prefab": "3d5184e1-0201-4476-bf4b-4eb28190de62", "mask": "", "swatch": "", "decal": ""}, {"item": "3d5184e1-0201-4476-bf4b-4eb28190de62", "prefab": "3d5184e1-0201-4476-bf4b-4eb28190de62", "mask": "1e3195ab-f7f6-41f4-a5ec-378d09ecdf69", "swatch": "37f8378b-b98c-477a-b8c9-9340772800b5", "decal": ""}, {"item": "3d5184e1-0201-4476-bf4b-4eb28190de62", "prefab": "3d5184e1-0201-4476-bf4b-4eb28190de62", "mask": "1e3195ab-f7f6-41f4-a5ec-378d09ecdf69", "swatch": "713781e5-ce47-41c6-a24f-94d42a565e30", "decal": ""}, {"item": "3d5184e1-0201-4476-bf4b-4eb28190de62", "prefab": "3d5184e1-0201-4476-bf4b-4eb28190de62", "mask": "1e3195ab-f7f6-41f4-a5ec-378d09ecdf69", "swatch": "96e42c7a-2881-4299-a2fe-80c0d4d5ca26", "decal": ""}, {"item": "3d5184e1-0201-4476-bf4b-4eb28190de62", "prefab": "3d5184e1-0201-4476-bf4b-4eb28190de62", "mask": "1e3195ab-f7f6-41f4-a5ec-378d09ecdf69", "swatch": "c1e430c5-fd8d-4032-9e3f-8d87cc8784da", "decal": ""}, {"item": "a16d59da-caa5-4388-af67-315bfcb16f64", "prefab": "a16d59da-caa5-4388-af67-315bfcb16f64", "mask": "", "swatch": "", "decal": ""}, {"item": "07205057-0804-41de-abe4-bd4027fee1df", "prefab": "07205057-0804-41de-abe4-bd4027fee1df", "mask": "", "swatch": "", "decal": ""}, {"item": "07205057-0804-41de-abe4-bd4027fee1df", "prefab": "07205057-0804-41de-abe4-bd4027fee1df", "mask": "YJGsYfwkik2lp7qWqXyXTw", "swatch": "jwXdjDbEmkOFyKZHT_RYOw", "decal": ""}, {"item": "07205057-0804-41de-abe4-bd4027fee1df", "prefab": "07205057-0804-41de-abe4-bd4027fee1df", "mask": "YJGsYfwkik2lp7qWqXyXTw", "swatch": "LdKaVAveokmp3qRwVBxnFA", "decal": ""}, {"item": "8263a9ca-8c57-4e81-b3e3-24f2ca19a70a", "prefab": "8263a9ca-8c57-4e81-b3e3-24f2ca19a70a", "mask": "", "swatch": "", "decal": ""}, {"item": "ad0bdec5-f767-42e7-99e0-221473464a85", "prefab": "ad0bdec5-f767-42e7-99e0-221473464a85", "mask": "", "swatch": "", "decal": ""}, {"item": "ad0bdec5-f767-42e7-99e0-221473464a85", "prefab": "ad0bdec5-f767-42e7-99e0-221473464a85", "mask": "54778d93-2cff-48db-b25b-65c08b0e5be8", "swatch": "624c85e5-095a-48b6-85fb-c9bbade19a8d", "decal": ""}, {"item": "ad0bdec5-f767-42e7-99e0-221473464a85", "prefab": "ad0bdec5-f767-42e7-99e0-221473464a85", "mask": "54778d93-2cff-48db-b25b-65c08b0e5be8", "swatch": "192ba73d-6990-42da-8fe0-914c2a7181eb", "decal": ""}, {"item": "ad0bdec5-f767-42e7-99e0-221473464a85", "prefab": "ad0bdec5-f767-42e7-99e0-221473464a85", "mask": "54778d93-2cff-48db-b25b-65c08b0e5be8", "swatch": "5e00a832-3f25-4dd3-84d2-b6ed6a4b4c37", "decal": ""}, {"item": "ad0bdec5-f767-42e7-99e0-221473464a85", "prefab": "ad0bdec5-f767-42e7-99e0-221473464a85", "mask": "54778d93-2cff-48db-b25b-65c08b0e5be8", "swatch": "teVKVbVEsEyqOSuRdqHQzA", "decal": ""}, {"item": "ad0bdec5-f767-42e7-99e0-221473464a85", "prefab": "ad0bdec5-f767-42e7-99e0-221473464a85", "mask": "54778d93-2cff-48db-b25b-65c08b0e5be8", "swatch": "K5vPqD3BrEuODRDzFdC6Hw", "decal": ""}, {"item": "vpP2ZvnL0Uy3n-I_Jv3eeQ", "prefab": "vpP2ZvnL0Uy3n-I_Jv3eeQ", "mask": "", "swatch": "", "decal": ""}, {"item": "4fcb5b84-13a9-43e6-9680-72b17874ae53", "prefab": "4fcb5b84-13a9-43e6-9680-72b17874ae53", "mask": "", "swatch": "", "decal": ""}, {"item": "4fcb5b84-13a9-43e6-9680-72b17874ae53", "prefab": "4fcb5b84-13a9-43e6-9680-72b17874ae53", "mask": "a3Er_BCApUiPtBaGkSH9LA", "swatch": "Su7kiW24d0KapNns96JoFQ", "decal": ""}, {"item": "4fcb5b84-13a9-43e6-9680-72b17874ae53", "prefab": "4fcb5b84-13a9-43e6-9680-72b17874ae53", "mask": "a3Er_BCApUiPtBaGkSH9LA", "swatch": "CahBzgN3D0y46AQsvGC14A", "decal": ""}, {"item": "5ac25e5a-8c8e-445c-82a4-7272baec1eb5", "prefab": "5ac25e5a-8c8e-445c-82a4-7272baec1eb5", "mask": "", "swatch": "", "decal": ""}, {"item": "1a028850-64ee-40ab-b547-ebbcfa1b04b3", "prefab": "1a028850-64ee-40ab-b547-ebbcfa1b04b3", "mask": "", "swatch": "", "decal": ""}, {"item": "1a028850-64ee-40ab-b547-ebbcfa1b04b3", "prefab": "1a028850-64ee-40ab-b547-ebbcfa1b04b3", "mask": "a8a91216-0fcc-461e-a682-95ee82509cce", "swatch": "5a614f97-ef7f-4b70-afff-9ed837a86407", "decal": ""}, {"item": "1a028850-64ee-40ab-b547-ebbcfa1b04b3", "prefab": "1a028850-64ee-40ab-b547-ebbcfa1b04b3", "mask": "a8a91216-0fcc-461e-a682-95ee82509cce", "swatch": "b444ed0a-eab5-4bd6-af34-d080de74a149", "decal": ""}, {"item": "1a028850-64ee-40ab-b547-ebbcfa1b04b3", "prefab": "1a028850-64ee-40ab-b547-ebbcfa1b04b3", "mask": "a8a91216-0fcc-461e-a682-95ee82509cce", "swatch": "8053b5e5-0f67-4329-a45d-1ee9fc830820", "decal": ""}, {"item": "34654303-9760-447f-8614-1c26506db994", "prefab": "34654303-9760-447f-8614-1c26506db994", "mask": "", "swatch": "", "decal": ""}, {"item": "a628d62c-fa30-4405-bcbc-3e646e3a3434", "prefab": "a628d62c-fa30-4405-bcbc-3e646e3a3434", "mask": "", "swatch": "", "decal": ""}, {"item": "a628d62c-fa30-4405-bcbc-3e646e3a3434", "prefab": "a628d62c-fa30-4405-bcbc-3e646e3a3434", "mask": "be6b3fa1-3388-48ac-8957-d7b8c85ff968", "swatch": "55901f12-d5b5-4fa8-b4c8-e479689ee39d", "decal": ""}, {"item": "a628d62c-fa30-4405-bcbc-3e646e3a3434", "prefab": "a628d62c-fa30-4405-bcbc-3e646e3a3434", "mask": "be6b3fa1-3388-48ac-8957-d7b8c85ff968", "swatch": "4828b50c-95b6-466a-bb25-514891d78202", "decal": ""}, {"item": "a628d62c-fa30-4405-bcbc-3e646e3a3434", "prefab": "a628d62c-fa30-4405-bcbc-3e646e3a3434", "mask": "be6b3fa1-3388-48ac-8957-d7b8c85ff968", "swatch": "d6823e01-69f0-4f85-b94a-74894356a2cf", "decal": ""}, {"item": "cea5c141-32d8-4dae-80d9-6fb74834a79c", "prefab": "cea5c141-32d8-4dae-80d9-6fb74834a79c", "mask": "", "swatch": "", "decal": ""}, {"item": "ec836d2a-b1b8-4566-b8eb-123ae2845956", "prefab": "ec836d2a-b1b8-4566-b8eb-123ae2845956", "mask": "", "swatch": "", "decal": ""}, {"item": "ec836d2a-b1b8-4566-b8eb-123ae2845956", "prefab": "ec836d2a-b1b8-4566-b8eb-123ae2845956", "mask": "9d6ac0b7-bbef-40bc-9173-effe65d7c0ff", "swatch": "cea53c7f-24a9-4e3b-80b9-fbf95738f973", "decal": ""}, {"item": "ec836d2a-b1b8-4566-b8eb-123ae2845956", "prefab": "ec836d2a-b1b8-4566-b8eb-123ae2845956", "mask": "9d6ac0b7-bbef-40bc-9173-effe65d7c0ff", "swatch": "e37b07d1-6b9d-49b9-94ca-1c728e228cfe", "decal": ""}, {"item": "ec836d2a-b1b8-4566-b8eb-123ae2845956", "prefab": "ec836d2a-b1b8-4566-b8eb-123ae2845956", "mask": "9d6ac0b7-bbef-40bc-9173-effe65d7c0ff", "swatch": "8c74ee27-3d56-401b-8a8c-7868a80770e8", "decal": ""}, {"item": "bb54e0e9-5d90-41f3-94c0-8e82f1791af1", "prefab": "bb54e0e9-5d90-41f3-94c0-8e82f1791af1", "mask": "", "swatch": "", "decal": ""}, {"item": "bb54e0e9-5d90-41f3-94c0-8e82f1791af1", "prefab": "bb54e0e9-5d90-41f3-94c0-8e82f1791af1", "mask": "c8ccb85e-52cd-4b3b-aadf-289819067125", "swatch": "891d902e-3257-4a8c-9001-858046007997", "decal": ""}, {"item": "bb54e0e9-5d90-41f3-94c0-8e82f1791af1", "prefab": "bb54e0e9-5d90-41f3-94c0-8e82f1791af1", "mask": "c8ccb85e-52cd-4b3b-aadf-289819067125", "swatch": "4c26d1e7-2382-4478-b9c4-a068ed01a607", "decal": ""}, {"item": "18feb3c2-049f-4f91-9f4e-6b5fcb4ab70c", "prefab": "18feb3c2-049f-4f91-9f4e-6b5fcb4ab70c", "mask": "", "swatch": "", "decal": ""}, {"item": "18feb3c2-049f-4f91-9f4e-6b5fcb4ab70c", "prefab": "18feb3c2-049f-4f91-9f4e-6b5fcb4ab70c", "mask": "d9be3a98-9da9-419e-9dce-fe58a3ad9c4e", "swatch": "565c17e0-496d-45bd-a2c3-564e118c06cc", "decal": ""}, {"item": "18feb3c2-049f-4f91-9f4e-6b5fcb4ab70c", "prefab": "18feb3c2-049f-4f91-9f4e-6b5fcb4ab70c", "mask": "d9be3a98-9da9-419e-9dce-fe58a3ad9c4e", "swatch": "b6c25a3b-125b-4aab-bed0-e6e5ced2c21c", "decal": ""}, {"item": "5e3929d3-d291-4e91-a0ba-2ef203e407d3", "prefab": "5e3929d3-d291-4e91-a0ba-2ef203e407d3", "mask": "", "swatch": "", "decal": ""}, {"item": "5e3929d3-d291-4e91-a0ba-2ef203e407d3", "prefab": "5e3929d3-d291-4e91-a0ba-2ef203e407d3", "mask": "73fc2e2a-a036-410b-bafa-3a07658245b5", "swatch": "9967ce54-c2b7-437a-946e-11f8ee6d6fdd", "decal": ""}, {"item": "5e3929d3-d291-4e91-a0ba-2ef203e407d3", "prefab": "5e3929d3-d291-4e91-a0ba-2ef203e407d3", "mask": "73fc2e2a-a036-410b-bafa-3a07658245b5", "swatch": "0ba92db2-690a-44ce-92f4-130e9503b6c1", "decal": ""}, {"item": "2b2eb9e5-d52e-4004-bdb2-0bcf6910992d", "prefab": "2b2eb9e5-d52e-4004-bdb2-0bcf6910992d", "mask": "", "swatch": "", "decal": ""}, {"item": "2b2eb9e5-d52e-4004-bdb2-0bcf6910992d", "prefab": "2b2eb9e5-d52e-4004-bdb2-0bcf6910992d", "mask": "7d040391-03f7-4b3d-bcfd-a1278ff459c9", "swatch": "c5884778-a87f-4612-9717-86fbb65d440f", "decal": ""}, {"item": "2b2eb9e5-d52e-4004-bdb2-0bcf6910992d", "prefab": "2b2eb9e5-d52e-4004-bdb2-0bcf6910992d", "mask": "7d040391-03f7-4b3d-bcfd-a1278ff459c9", "swatch": "827a1538-51bb-4fef-99c1-7f1bebd9866c", "decal": ""}, {"item": "5a38f864-7d1c-4989-8558-40b9668f78ef", "prefab": "5a38f864-7d1c-4989-8558-40b9668f78ef", "mask": "", "swatch": "", "decal": ""}, {"item": "5a38f864-7d1c-4989-8558-40b9668f78ef", "prefab": "5a38f864-7d1c-4989-8558-40b9668f78ef", "mask": "7660e11a-7ef0-43d3-bc02-76b3a4aab064", "swatch": "d8d490aa-7088-4163-9305-1441367f490c", "decal": ""}, {"item": "5a38f864-7d1c-4989-8558-40b9668f78ef", "prefab": "5a38f864-7d1c-4989-8558-40b9668f78ef", "mask": "7660e11a-7ef0-43d3-bc02-76b3a4aab064", "swatch": "61ff7a02-50ff-49ea-b95e-c31b95a30c8e", "decal": ""}, {"item": "OTnfT-4NQU6Gd_iVPDx8eg", "prefab": "OTnfT-4NQU6Gd_iVPDx8eg", "mask": "", "swatch": "", "decal": ""}, {"item": "2faa26b6-f7c3-4b0d-8f70-1f9454e64a54", "prefab": "2faa26b6-f7c3-4b0d-8f70-1f9454e64a54", "mask": "", "swatch": "", "decal": ""}, {"item": "2faa26b6-f7c3-4b0d-8f70-1f9454e64a54", "prefab": "2faa26b6-f7c3-4b0d-8f70-1f9454e64a54", "mask": "ddf429fd-a883-48a5-869b-bc12e44d30bf", "swatch": "48c18418-01dd-40ff-9dfe-c4cf5f4e1245", "decal": ""}, {"item": "2faa26b6-f7c3-4b0d-8f70-1f9454e64a54", "prefab": "2faa26b6-f7c3-4b0d-8f70-1f9454e64a54", "mask": "ddf429fd-a883-48a5-869b-bc12e44d30bf", "swatch": "6584dada-499e-4d7c-958d-466955b20640", "decal": ""}, {"item": "KPmC_wyX_ECe7fdKwoLMGg", "prefab": "KPmC_wyX_ECe7fdKwoLMGg", "mask": "", "swatch": "", "decal": ""}, {"item": "KPmC_wyX_ECe7fdKwoLMGg", "prefab": "KPmC_wyX_ECe7fdKwoLMGg", "mask": "yzL0M_N5Q0eAa8IxACHPAQ", "swatch": "FaWMLkJJgEKFIO493fzGxg", "decal": ""}, {"item": "JTrfBXhIT02o2-ssP94zew", "prefab": "JTrfBXhIT02o2-ssP94zew", "mask": "", "swatch": "", "decal": ""}, {"item": "JTrfBXhIT02o2-ssP94zew", "prefab": "JTrfBXhIT02o2-ssP94zew", "mask": "3DdSzSqV1UG5AtPr8ro3Kg", "swatch": "8iZO162KNUiMC8l_z3OC_A", "decal": ""}, {"item": "JTrfBXhIT02o2-ssP94zew", "prefab": "JTrfBXhIT02o2-ssP94zew", "mask": "3DdSzSqV1UG5AtPr8ro3Kg", "swatch": "f_c50saStkaGJuhLjJ9lfA", "decal": ""}, {"item": "JTrfBXhIT02o2-ssP94zew", "prefab": "JTrfBXhIT02o2-ssP94zew", "mask": "3DdSzSqV1UG5AtPr8ro3Kg", "swatch": "FUP4iRnoc0ikNmjrFD06lg", "decal": ""}, {"item": "JTrfBXhIT02o2-ssP94zew", "prefab": "JTrfBXhIT02o2-ssP94zew", "mask": "3DdSzSqV1UG5AtPr8ro3Kg", "swatch": "GmS0wKwzVEqdf3NoTMHG5A", "decal": ""}, {"item": "JTrfBXhIT02o2-ssP94zew", "prefab": "JTrfBXhIT02o2-ssP94zew", "mask": "3DdSzSqV1UG5AtPr8ro3Kg", "swatch": "ZNKGkou_gU-1cxBFy7pDKw", "decal": ""}, {"item": "JTrfBXhIT02o2-ssP94zew", "prefab": "JTrfBXhIT02o2-ssP94zew", "mask": "3DdSzSqV1UG5AtPr8ro3Kg", "swatch": "-Iqmonry4EKoqAULCKzRPA", "decal": ""}, {"item": "JTrfBXhIT02o2-ssP94zew", "prefab": "JTrfBXhIT02o2-ssP94zew", "mask": "mkDVc0Mz4kmuLZ7powBHpQ", "swatch": "D-SngK7hhkOpoZ2aMWv9EA", "decal": ""}, {"item": "40528de7-38a3-4a7c-8f93-6d3bfa5573f2", "prefab": "40528de7-38a3-4a7c-8f93-6d3bfa5573f2", "mask": "", "swatch": "", "decal": ""}, {"item": "40528de7-38a3-4a7c-8f93-6d3bfa5573f2", "prefab": "40528de7-38a3-4a7c-8f93-6d3bfa5573f2", "mask": "0b2395e1-ebcc-47e9-aaf1-faf9e9cec4cd", "swatch": "8377ab96-c908-457f-9fee-b784c9a759f3", "decal": ""}, {"item": "40528de7-38a3-4a7c-8f93-6d3bfa5573f2", "prefab": "40528de7-38a3-4a7c-8f93-6d3bfa5573f2", "mask": "0b2395e1-ebcc-47e9-aaf1-faf9e9cec4cd", "swatch": "1b1d08f2-12ca-43dd-a44f-ea2820b919b4", "decal": ""}, {"item": "40528de7-38a3-4a7c-8f93-6d3bfa5573f2", "prefab": "40528de7-38a3-4a7c-8f93-6d3bfa5573f2", "mask": "0b2395e1-ebcc-47e9-aaf1-faf9e9cec4cd", "swatch": "7d8e55fe-3c34-4b4b-9753-0021f6cc6454", "decal": ""}, {"item": "40528de7-38a3-4a7c-8f93-6d3bfa5573f2", "prefab": "40528de7-38a3-4a7c-8f93-6d3bfa5573f2", "mask": "0b2395e1-ebcc-47e9-aaf1-faf9e9cec4cd", "swatch": "f8b0cfe8-e129-4578-8bb5-f60af5d38599", "decal": ""}, {"item": "40528de7-38a3-4a7c-8f93-6d3bfa5573f2", "prefab": "40528de7-38a3-4a7c-8f93-6d3bfa5573f2", "mask": "0b2395e1-ebcc-47e9-aaf1-faf9e9cec4cd", "swatch": "0ecb8a2a-cffc-47db-aeda-fb0684aef1e5", "decal": ""}, {"item": "40528de7-38a3-4a7c-8f93-6d3bfa5573f2", "prefab": "40528de7-38a3-4a7c-8f93-6d3bfa5573f2", "mask": "0b2395e1-ebcc-47e9-aaf1-faf9e9cec4cd", "swatch": "484b6c13-af22-4ad5-8c43-34c0de095d49", "decal": ""}, {"item": "40528de7-38a3-4a7c-8f93-6d3bfa5573f2", "prefab": "40528de7-38a3-4a7c-8f93-6d3bfa5573f2", "mask": "0b2395e1-ebcc-47e9-aaf1-faf9e9cec4cd", "swatch": "51ef8d39-2b94-4f9e-9620-07b6b0a913a5", "decal": ""}, {"item": "40528de7-38a3-4a7c-8f93-6d3bfa5573f2", "prefab": "40528de7-38a3-4a7c-8f93-6d3bfa5573f2", "mask": "0b2395e1-ebcc-47e9-aaf1-faf9e9cec4cd", "swatch": "cbe29e9f-f2ac-47fb-97e1-8bad16abb89d", "decal": ""}, {"item": "40528de7-38a3-4a7c-8f93-6d3bfa5573f2", "prefab": "40528de7-38a3-4a7c-8f93-6d3bfa5573f2", "mask": "0b2395e1-ebcc-47e9-aaf1-faf9e9cec4cd", "swatch": "dee70c38-7a99-4c2b-9181-665f1bf75aca", "decal": ""}, {"item": "40528de7-38a3-4a7c-8f93-6d3bfa5573f2", "prefab": "40528de7-38a3-4a7c-8f93-6d3bfa5573f2", "mask": "0b2395e1-ebcc-47e9-aaf1-faf9e9cec4cd", "swatch": "67bcca75-4ab1-4964-8688-9908c464d355", "decal": ""}, {"item": "40528de7-38a3-4a7c-8f93-6d3bfa5573f2", "prefab": "40528de7-38a3-4a7c-8f93-6d3bfa5573f2", "mask": "018a5c07-e956-457d-a540-a5e2cd68da09", "swatch": "dee70c38-7a99-4c2b-9181-665f1bf75aca", "decal": ""}, {"item": "40528de7-38a3-4a7c-8f93-6d3bfa5573f2", "prefab": "40528de7-38a3-4a7c-8f93-6d3bfa5573f2", "mask": "018a5c07-e956-457d-a540-a5e2cd68da09", "swatch": "cbe29e9f-f2ac-47fb-97e1-8bad16abb89d", "decal": ""}, {"item": "40528de7-38a3-4a7c-8f93-6d3bfa5573f2", "prefab": "40528de7-38a3-4a7c-8f93-6d3bfa5573f2", "mask": "018a5c07-e956-457d-a540-a5e2cd68da09", "swatch": "51ef8d39-2b94-4f9e-9620-07b6b0a913a5", "decal": ""}, {"item": "40528de7-38a3-4a7c-8f93-6d3bfa5573f2", "prefab": "40528de7-38a3-4a7c-8f93-6d3bfa5573f2", "mask": "018a5c07-e956-457d-a540-a5e2cd68da09", "swatch": "8377ab96-c908-457f-9fee-b784c9a759f3", "decal": ""}, {"item": "40528de7-38a3-4a7c-8f93-6d3bfa5573f2", "prefab": "40528de7-38a3-4a7c-8f93-6d3bfa5573f2", "mask": "56a92c8d-af53-413e-929e-4a9a3cfad780", "swatch": "6dd95046-acf8-42fe-ab78-80a334096a9d", "decal": ""}, {"item": "40528de7-38a3-4a7c-8f93-6d3bfa5573f2", "prefab": "40528de7-38a3-4a7c-8f93-6d3bfa5573f2", "mask": "3290f148-1bd1-4ffd-a947-61fd0529af05", "swatch": "0809a5cf-a0f3-4614-9f98-71518524518a", "decal": ""}, {"item": "J6IQt4G1zUeN8v9Wn50BUw", "prefab": "J6IQt4G1zUeN8v9Wn50BUw", "mask": "", "swatch": "", "decal": ""}, {"item": "UcPo9SsF3EGTCBB2J-Efdw", "prefab": "UcPo9SsF3EGTCBB2J-Efdw", "mask": "", "swatch": "", "decal": ""}, {"item": "UcPo9SsF3EGTCBB2J-Efdw", "prefab": "UcPo9SsF3EGTCBB2J-Efdw", "mask": "_Yq00H-NWUyQNvGxRunHbg", "swatch": "viL4CPch2ECwG-RNaWcyZw", "decal": ""}, {"item": "UcPo9SsF3EGTCBB2J-Efdw", "prefab": "UcPo9SsF3EGTCBB2J-Efdw", "mask": "_Yq00H-NWUyQNvGxRunHbg", "swatch": "8ANbOhNnO0yLQOCIBsDwfg", "decal": ""}, {"item": "UcPo9SsF3EGTCBB2J-Efdw", "prefab": "UcPo9SsF3EGTCBB2J-Efdw", "mask": "_Yq00H-NWUyQNvGxRunHbg", "swatch": "a5nUvJZgTEGBUvdHx0Y6Ng", "decal": ""}, {"item": "UcPo9SsF3EGTCBB2J-Efdw", "prefab": "UcPo9SsF3EGTCBB2J-Efdw", "mask": "_Yq00H-NWUyQNvGxRunHbg", "swatch": "JWBK5v78Y0uRVEXlD-Qk6Q", "decal": ""}, {"item": "UcPo9SsF3EGTCBB2J-Efdw", "prefab": "UcPo9SsF3EGTCBB2J-Efdw", "mask": "_Yq00H-NWUyQNvGxRunHbg", "swatch": "thG3Dc1PFEqAvT9xCVshuw", "decal": ""}, {"item": "UcPo9SsF3EGTCBB2J-Efdw", "prefab": "UcPo9SsF3EGTCBB2J-Efdw", "mask": "_Yq00H-NWUyQNvGxRunHbg", "swatch": "hf0gkFVS8ESvceRsC3LAXQ", "decal": ""}, {"item": "UcPo9SsF3EGTCBB2J-Efdw", "prefab": "UcPo9SsF3EGTCBB2J-Efdw", "mask": "_Yq00H-NWUyQNvGxRunHbg", "swatch": "4scG_UBlzE-5DWRhSrtm6g", "decal": ""}, {"item": "UcPo9SsF3EGTCBB2J-Efdw", "prefab": "UcPo9SsF3EGTCBB2J-Efdw", "mask": "_Yq00H-NWUyQNvGxRunHbg", "swatch": "OqsbZ8Xls0i8uA6ha7H3qQ", "decal": ""}, {"item": "UcPo9SsF3EGTCBB2J-Efdw", "prefab": "UcPo9SsF3EGTCBB2J-Efdw", "mask": "_Yq00H-NWUyQNvGxRunHbg", "swatch": "nX7krD8e2km_ZlOLWcoxdQ", "decal": ""}, {"item": "UcPo9SsF3EGTCBB2J-Efdw", "prefab": "UcPo9SsF3EGTCBB2J-Efdw", "mask": "_Yq00H-NWUyQNvGxRunHbg", "swatch": "ns5tIRishUOp-nYqLedcwA", "decal": ""}, {"item": "UcPo9SsF3EGTCBB2J-Efdw", "prefab": "UcPo9SsF3EGTCBB2J-Efdw", "mask": "_Yq00H-NWUyQNvGxRunHbg", "swatch": "k_ZYy0SjtU2TB7IFdq2vDA", "decal": ""}, {"item": "UcPo9SsF3EGTCBB2J-Efdw", "prefab": "UcPo9SsF3EGTCBB2J-Efdw", "mask": "_Yq00H-NWUyQNvGxRunHbg", "swatch": "I1J-xY4YNU2dB_od4P_7Dg", "decal": ""}, {"item": "UcPo9SsF3EGTCBB2J-Efdw", "prefab": "UcPo9SsF3EGTCBB2J-Efdw", "mask": "_Yq00H-NWUyQNvGxRunHbg", "swatch": "2sf-FU4WckGBDWUkVz4z1g", "decal": ""}, {"item": "UcPo9SsF3EGTCBB2J-Efdw", "prefab": "UcPo9SsF3EGTCBB2J-Efdw", "mask": "_Yq00H-NWUyQNvGxRunHbg", "swatch": "3PfP661Fg0CruzRxis9EiA", "decal": ""}, {"item": "UcPo9SsF3EGTCBB2J-Efdw", "prefab": "UcPo9SsF3EGTCBB2J-Efdw", "mask": "_Yq00H-NWUyQNvGxRunHbg", "swatch": "d9mwowRelE2lDKEA0UQMZQ", "decal": ""}, {"item": "UcPo9SsF3EGTCBB2J-Efdw", "prefab": "UcPo9SsF3EGTCBB2J-Efdw", "mask": "_Yq00H-NWUyQNvGxRunHbg", "swatch": "Uz2hrptCa0erjqyPoiZxew", "decal": ""}, {"item": "UcPo9SsF3EGTCBB2J-Efdw", "prefab": "UcPo9SsF3EGTCBB2J-Efdw", "mask": "_Yq00H-NWUyQNvGxRunHbg", "swatch": "PQTvQiYu-ESRUldnxgm5BQ", "decal": ""}, {"item": "akAhnN0cFkecj8q3spixkw", "prefab": "akAhnN0cFkecj8q3spixkw", "mask": "", "swatch": "", "decal": ""}, {"item": "a61e86cd-f7e7-4ac7-a6e8-df269bc5cb01", "prefab": "a61e86cd-f7e7-4ac7-a6e8-df269bc5cb01", "mask": "", "swatch": "", "decal": ""}, {"item": "a61e86cd-f7e7-4ac7-a6e8-df269bc5cb01", "prefab": "a61e86cd-f7e7-4ac7-a6e8-df269bc5cb01", "mask": "65b89eae-347e-4eff-9ff3-7f25cace0125", "swatch": "ce465d53-3807-423f-baa0-bdba2a75ceb3", "decal": ""}, {"item": "a61e86cd-f7e7-4ac7-a6e8-df269bc5cb01", "prefab": "a61e86cd-f7e7-4ac7-a6e8-df269bc5cb01", "mask": "65b89eae-347e-4eff-9ff3-7f25cace0125", "swatch": "596097a8-d545-4256-959d-1d3cd96fc64c", "decal": ""}, {"item": "8d703940-224a-4b0c-a04d-f365550071e1", "prefab": "8d703940-224a-4b0c-a04d-f365550071e1", "mask": "", "swatch": "", "decal": ""}, {"item": "8d703940-224a-4b0c-a04d-f365550071e1", "prefab": "8d703940-224a-4b0c-a04d-f365550071e1", "mask": "7c4d447c-6594-4bd8-9510-12ad683ab116", "swatch": "d5efaf80-7f34-4c9a-bc3a-b80b1ac70ace", "decal": ""}, {"item": "8d703940-224a-4b0c-a04d-f365550071e1", "prefab": "8d703940-224a-4b0c-a04d-f365550071e1", "mask": "7c4d447c-6594-4bd8-9510-12ad683ab116", "swatch": "0217fd2e-ff9d-49f7-9bc8-8d0d6b8bf250", "decal": ""}, {"item": "W7AJ4UYlVkWF5nLbmpmRBg", "prefab": "W7AJ4UYlVkWF5nLbmpmRBg", "mask": "", "swatch": "", "decal": ""}, {"item": "e614df5d-3cfe-4bca-b9c2-30c267b9c94e", "prefab": "e614df5d-3cfe-4bca-b9c2-30c267b9c94e", "mask": "", "swatch": "", "decal": ""}, {"item": "e614df5d-3cfe-4bca-b9c2-30c267b9c94e", "prefab": "e614df5d-3cfe-4bca-b9c2-30c267b9c94e", "mask": "0dd6596c-b4ea-4d55-a219-e1f44e4a376d", "swatch": "ed134bee-d199-43eb-98bd-206571603f40", "decal": ""}, {"item": "e614df5d-3cfe-4bca-b9c2-30c267b9c94e", "prefab": "e614df5d-3cfe-4bca-b9c2-30c267b9c94e", "mask": "0dd6596c-b4ea-4d55-a219-e1f44e4a376d", "swatch": "59866578-d4e0-417b-9483-233ab108b1ac", "decal": ""}, {"item": "e614df5d-3cfe-4bca-b9c2-30c267b9c94e", "prefab": "e614df5d-3cfe-4bca-b9c2-30c267b9c94e", "mask": "0dd6596c-b4ea-4d55-a219-e1f44e4a376d", "swatch": "22dc463a-a89b-46ad-9a0d-557c742b4a80", "decal": ""}, {"item": "e614df5d-3cfe-4bca-b9c2-30c267b9c94e", "prefab": "e614df5d-3cfe-4bca-b9c2-30c267b9c94e", "mask": "0dd6596c-b4ea-4d55-a219-e1f44e4a376d", "swatch": "e1f49b74-b071-4bd1-9c4b-af36d24d45c5", "decal": ""}, {"item": "e614df5d-3cfe-4bca-b9c2-30c267b9c94e", "prefab": "e614df5d-3cfe-4bca-b9c2-30c267b9c94e", "mask": "0dd6596c-b4ea-4d55-a219-e1f44e4a376d", "swatch": "ITH2OpzjP022vZ5JgxV_iQ", "decal": ""}, {"item": "e614df5d-3cfe-4bca-b9c2-30c267b9c94e", "prefab": "e614df5d-3cfe-4bca-b9c2-30c267b9c94e", "mask": "0dd6596c-b4ea-4d55-a219-e1f44e4a376d", "swatch": "PMa9370LPEig_7pKwCceEQ", "decal": ""}, {"item": "7134c60b-0640-463a-8d97-caa9184bef93", "prefab": "7134c60b-0640-463a-8d97-caa9184bef93", "mask": "", "swatch": "", "decal": ""}, {"item": "7134c60b-0640-463a-8d97-caa9184bef93", "prefab": "7134c60b-0640-463a-8d97-caa9184bef93", "mask": "7e688b30-ed13-4790-9ad8-c49885d1b7e8", "swatch": "e3454bc7-c88d-4958-8b81-db3fe5472a1e", "decal": ""}, {"item": "123de433-a005-4994-8a11-d2d84ad66b11", "prefab": "123de433-a005-4994-8a11-d2d84ad66b11", "mask": "", "swatch": "", "decal": ""}, {"item": "123de433-a005-4994-8a11-d2d84ad66b11", "prefab": "123de433-a005-4994-8a11-d2d84ad66b11", "mask": "4e378419-d043-4e86-ad0e-66288ac21cf4", "swatch": "426d0592-f8e4-4bc6-9fe1-a62b91fd57d5", "decal": ""}, {"item": "123de433-a005-4994-8a11-d2d84ad66b11", "prefab": "123de433-a005-4994-8a11-d2d84ad66b11", "mask": "4e378419-d043-4e86-ad0e-66288ac21cf4", "swatch": "oT1vLrHW6UC2hWLIT3f_SQ", "decal": ""}, {"item": "123de433-a005-4994-8a11-d2d84ad66b11", "prefab": "123de433-a005-4994-8a11-d2d84ad66b11", "mask": "4e378419-d043-4e86-ad0e-66288ac21cf4", "swatch": "-e6bmXnwZU2EVwZ8RupKbg", "decal": ""}, {"item": "mfFLpKQRE0anhrmeUvW0bA", "prefab": "mfFLpKQRE0anhrmeUvW0bA", "mask": "", "swatch": "", "decal": ""}, {"item": "exQz9Zr3HEyOR9nDDrimlQ", "prefab": "exQz9Zr3HEyOR9nDDrimlQ", "mask": "", "swatch": "", "decal": ""}, {"item": "1596c113-aa3a-4f5c-b398-e5c3575548e7", "prefab": "1596c113-aa3a-4f5c-b398-e5c3575548e7", "mask": "", "swatch": "", "decal": ""}, {"item": "1596c113-aa3a-4f5c-b398-e5c3575548e7", "prefab": "1596c113-aa3a-4f5c-b398-e5c3575548e7", "mask": "96b36c98-cba1-4337-91dd-c74c00316f97", "swatch": "916384ca-e209-42a0-bd64-c64f5a40b56f", "decal": ""}, {"item": "1596c113-aa3a-4f5c-b398-e5c3575548e7", "prefab": "1596c113-aa3a-4f5c-b398-e5c3575548e7", "mask": "96b36c98-cba1-4337-91dd-c74c00316f97", "swatch": "0217fd2e-ff9d-49f7-9bc8-8d0d6b8bf250", "decal": ""}, {"item": "1596c113-aa3a-4f5c-b398-e5c3575548e7", "prefab": "1596c113-aa3a-4f5c-b398-e5c3575548e7", "mask": "VSLcLM0TJU2kJU5gS4KZGw", "swatch": "RA7cizxxYE6J8yV_0XwPxA", "decal": ""}, {"item": "1596c113-aa3a-4f5c-b398-e5c3575548e7", "prefab": "1596c113-aa3a-4f5c-b398-e5c3575548e7", "mask": "jL-LAsd03EeN1J-Zp8opew", "swatch": "5_UnQ7vZ9E2T-EhJ7M-mQw", "decal": ""}, {"item": "b537c30c-faef-4649-9a1f-be3985936f93", "prefab": "b537c30c-faef-4649-9a1f-be3985936f93", "mask": "", "swatch": "", "decal": ""}, {"item": "b537c30c-faef-4649-9a1f-be3985936f93", "prefab": "b537c30c-faef-4649-9a1f-be3985936f93", "mask": "lb5tuJOqnUKp9prjjlgHmA", "swatch": "7amHTgEv-UOJSGl0ofo4mw", "decal": ""}, {"item": "b537c30c-faef-4649-9a1f-be3985936f93", "prefab": "b537c30c-faef-4649-9a1f-be3985936f93", "mask": "lb5tuJOqnUKp9prjjlgHmA", "swatch": "cKHAetNcgUOxFfDkAPPF9w", "decal": ""}, {"item": "hT7nSD7Ts06F_1SNlOMLwg", "prefab": "hT7nSD7Ts06F_1SNlOMLwg", "mask": "", "swatch": "", "decal": ""}, {"item": "hT7nSD7Ts06F_1SNlOMLwg", "prefab": "hT7nSD7Ts06F_1SNlOMLwg", "mask": "mG60EPIQIkaJNgOkonVmZg", "swatch": "lhfOKGDAgEKWucerCmH7Fg", "decal": ""}, {"item": "hT7nSD7Ts06F_1SNlOMLwg", "prefab": "hT7nSD7Ts06F_1SNlOMLwg", "mask": "mG60EPIQIkaJNgOkonVmZg", "swatch": "kLv_wsbbGUmlfIUO1QLYLw", "decal": ""}, {"item": "hT7nSD7Ts06F_1SNlOMLwg", "prefab": "hT7nSD7Ts06F_1SNlOMLwg", "mask": "mG60EPIQIkaJNgOkonVmZg", "swatch": "f0pjsV0XMUWoE_z2kqOlSA", "decal": ""}, {"item": "hT7nSD7Ts06F_1SNlOMLwg", "prefab": "hT7nSD7Ts06F_1SNlOMLwg", "mask": "mG60EPIQIkaJNgOkonVmZg", "swatch": "EDLxuGjEVEGdOPXDqyN-xA", "decal": ""}, {"item": "76369aef-aeda-46d2-996a-cd00594d0543", "prefab": "76369aef-aeda-46d2-996a-cd00594d0543", "mask": "", "swatch": "", "decal": ""}, {"item": "76369aef-aeda-46d2-996a-cd00594d0543", "prefab": "76369aef-aeda-46d2-996a-cd00594d0543", "mask": "d759c88d-a159-4c26-86dd-b054bb05b3ed", "swatch": "40283127-2ffc-4989-82f6-29c3381bf9e2", "decal": ""}, {"item": "76369aef-aeda-46d2-996a-cd00594d0543", "prefab": "76369aef-aeda-46d2-996a-cd00594d0543", "mask": "d759c88d-a159-4c26-86dd-b054bb05b3ed", "swatch": "1421caa9-9a0d-42c2-b847-1500ba2e483a", "decal": ""}, {"item": "76369aef-aeda-46d2-996a-cd00594d0543", "prefab": "76369aef-aeda-46d2-996a-cd00594d0543", "mask": "d759c88d-a159-4c26-86dd-b054bb05b3ed", "swatch": "390a476a-5545-48eb-955c-1bf1a16012bf", "decal": ""}, {"item": "76369aef-aeda-46d2-996a-cd00594d0543", "prefab": "76369aef-aeda-46d2-996a-cd00594d0543", "mask": "d759c88d-a159-4c26-86dd-b054bb05b3ed", "swatch": "46970903-9799-4d25-b90a-7162c1f5dbef", "decal": ""}, {"item": "76369aef-aeda-46d2-996a-cd00594d0543", "prefab": "76369aef-aeda-46d2-996a-cd00594d0543", "mask": "d759c88d-a159-4c26-86dd-b054bb05b3ed", "swatch": "PZx7Iae0pkiDaxT9t3-vgg", "decal": ""}, {"item": "76369aef-aeda-46d2-996a-cd00594d0543", "prefab": "76369aef-aeda-46d2-996a-cd00594d0543", "mask": "P12CUkIg8EuFihGQsvPlxg", "swatch": "8XajS2adOEOqzqnLy6-zcQ", "decal": ""}, {"item": "Lli8-aqt1EmCmyXwBRQaYA", "prefab": "Lli8-aqt1EmCmyXwBRQaYA", "mask": "", "swatch": "", "decal": ""}, {"item": "3df394de-9c3e-4141-aba4-12eac4742102", "prefab": "3df394de-9c3e-4141-aba4-12eac4742102", "mask": "", "swatch": "", "decal": ""}, {"item": "3df394de-9c3e-4141-aba4-12eac4742102", "prefab": "3df394de-9c3e-4141-aba4-12eac4742102", "mask": "71fd06e3-cac8-4923-a09c-ef91183712d2", "swatch": "17040a88-87f1-4613-b439-3316e33dbd48", "decal": ""}, {"item": "3df394de-9c3e-4141-aba4-12eac4742102", "prefab": "3df394de-9c3e-4141-aba4-12eac4742102", "mask": "4cc0ea9f-65af-486f-8319-02b85ab5197f", "swatch": "f9aaa7c5-59bc-486c-9a6e-27a8ac618f84", "decal": ""}, {"item": "3df394de-9c3e-4141-aba4-12eac4742102", "prefab": "3df394de-9c3e-4141-aba4-12eac4742102", "mask": "4cc0ea9f-65af-486f-8319-02b85ab5197f", "swatch": "jQwNBXyoU0Gk4FyNw6jFxg", "decal": ""}, {"item": "e2ObiQFqCUOdKniKL9a1EA", "prefab": "e2ObiQFqCUOdKniKL9a1EA", "mask": "", "swatch": "", "decal": ""}, {"item": "f277af6a-0807-4716-8948-1bee236ffb74", "prefab": "f277af6a-0807-4716-8948-1bee236ffb74", "mask": "", "swatch": "", "decal": ""}, {"item": "f277af6a-0807-4716-8948-1bee236ffb74", "prefab": "f277af6a-0807-4716-8948-1bee236ffb74", "mask": "0272eb19-fc0e-4ad6-8db9-cc0cda2528b0", "swatch": "d0bb468c-b78f-4e80-916d-65fec3ec0010", "decal": ""}, {"item": "40734c4d-e1fe-426c-8921-930f7463d8e8", "prefab": "40734c4d-e1fe-426c-8921-930f7463d8e8", "mask": "", "swatch": "", "decal": ""}, {"item": "40734c4d-e1fe-426c-8921-930f7463d8e8", "prefab": "40734c4d-e1fe-426c-8921-930f7463d8e8", "mask": "hCeaYHy1TE2s6rTPxAQP9A", "swatch": "E1GfXDLUaUKaG9hTLAfFPA", "decal": ""}, {"item": "40734c4d-e1fe-426c-8921-930f7463d8e8", "prefab": "40734c4d-e1fe-426c-8921-930f7463d8e8", "mask": "hCeaYHy1TE2s6rTPxAQP9A", "swatch": "wCe2yC2U1kyYvPd8Z1rX2w", "decal": ""}, {"item": "40734c4d-e1fe-426c-8921-930f7463d8e8", "prefab": "40734c4d-e1fe-426c-8921-930f7463d8e8", "mask": "hCeaYHy1TE2s6rTPxAQP9A", "swatch": "QkfA71-1z0qOiZAVrbzhtA", "decal": ""}, {"item": "418ed21e-766f-4c69-9d57-6f866dcc7feb", "prefab": "418ed21e-766f-4c69-9d57-6f866dcc7feb", "mask": "", "swatch": "", "decal": ""}, {"item": "418ed21e-766f-4c69-9d57-6f866dcc7feb", "prefab": "418ed21e-766f-4c69-9d57-6f866dcc7feb", "mask": "0dabda0d-842c-4d39-a6b9-a8ca3ec20825", "swatch": "49bbd11a-e5c9-47ff-b04d-72eb321e8a57", "decal": ""}, {"item": "274cb9b2-2f59-47ea-9a8d-a5b656d148c6", "prefab": "274cb9b2-2f59-47ea-9a8d-a5b656d148c6", "mask": "", "swatch": "", "decal": ""}, {"item": "8d17031a-c0a5-4145-92b7-1f0c2d8f0ce6", "prefab": "8d17031a-c0a5-4145-92b7-1f0c2d8f0ce6", "mask": "", "swatch": "", "decal": ""}, {"item": "8d17031a-c0a5-4145-92b7-1f0c2d8f0ce6", "prefab": "8d17031a-c0a5-4145-92b7-1f0c2d8f0ce6", "mask": "a4903da3-249d-4f0a-a746-b547fda371ff", "swatch": "53dd9e44-7096-438e-a7c3-11d7c307426f", "decal": ""}, {"item": "8d17031a-c0a5-4145-92b7-1f0c2d8f0ce6", "prefab": "8d17031a-c0a5-4145-92b7-1f0c2d8f0ce6", "mask": "a4903da3-249d-4f0a-a746-b547fda371ff", "swatch": "c3c9f89c-580c-43c4-b38b-07394044e0e1", "decal": ""}, {"item": "f60e093f-33be-4527-b827-060adc77b1f4", "prefab": "f60e093f-33be-4527-b827-060adc77b1f4", "mask": "", "swatch": "", "decal": ""}, {"item": "f60e093f-33be-4527-b827-060adc77b1f4", "prefab": "f60e093f-33be-4527-b827-060adc77b1f4", "mask": "464fcf20-361f-4dc9-b074-150c3bc27523", "swatch": "0d2830e7-8eb0-4fa3-99bc-7c80f01dd991", "decal": ""}, {"item": "7c1552a6-bc36-46e6-884c-1de6b8111a92", "prefab": "7c1552a6-bc36-46e6-884c-1de6b8111a92", "mask": "", "swatch": "", "decal": ""}, {"item": "7c1552a6-bc36-46e6-884c-1de6b8111a92", "prefab": "7c1552a6-bc36-46e6-884c-1de6b8111a92", "mask": "f1cd10fb-3d0b-436f-a173-65bc0d88d8bf", "swatch": "926ae943-76cb-46dd-9c66-a6ee44ea9194", "decal": ""}, {"item": "f5c9743f-e77c-42f2-ba63-9843342cbf8e", "prefab": "f5c9743f-e77c-42f2-ba63-9843342cbf8e", "mask": "", "swatch": "", "decal": ""}, {"item": "f5c9743f-e77c-42f2-ba63-9843342cbf8e", "prefab": "f5c9743f-e77c-42f2-ba63-9843342cbf8e", "mask": "a4e51b6b-fdfd-47dc-9d0e-f26f383e03e9", "swatch": "f5403c89-9769-479b-8a05-c71607f9b189", "decal": ""}, {"item": "f5c9743f-e77c-42f2-ba63-9843342cbf8e", "prefab": "f5c9743f-e77c-42f2-ba63-9843342cbf8e", "mask": "a4e51b6b-fdfd-47dc-9d0e-f26f383e03e9", "swatch": "36e63797-5a42-4779-a5cc-ef5c2e7d17d4", "decal": ""}, {"item": "f5c9743f-e77c-42f2-ba63-9843342cbf8e", "prefab": "f5c9743f-e77c-42f2-ba63-9843342cbf8e", "mask": "a4e51b6b-fdfd-47dc-9d0e-f26f383e03e9", "swatch": "70112887-dc8d-49d5-96ed-0845204e0b58", "decal": ""}, {"item": "f5c9743f-e77c-42f2-ba63-9843342cbf8e", "prefab": "f5c9743f-e77c-42f2-ba63-9843342cbf8e", "mask": "a4e51b6b-fdfd-47dc-9d0e-f26f383e03e9", "swatch": "2738ab5f-8c94-46b3-bac6-4a8111c41c21", "decal": ""}, {"item": "f5c9743f-e77c-42f2-ba63-9843342cbf8e", "prefab": "f5c9743f-e77c-42f2-ba63-9843342cbf8e", "mask": "a4e51b6b-fdfd-47dc-9d0e-f26f383e03e9", "swatch": "hhs9GY3XZkWb-8TXODvo6A", "decal": ""}, {"item": "5bde836f-9343-4934-b01a-606f707020ef", "prefab": "5bde836f-9343-4934-b01a-606f707020ef", "mask": "", "swatch": "", "decal": ""}, {"item": "5bde836f-9343-4934-b01a-606f707020ef", "prefab": "5bde836f-9343-4934-b01a-606f707020ef", "mask": "axPBGoqorEe77Oqqjfzb5w", "swatch": "fEPwvuJCWkGBe4IekXpmLw", "decal": ""}, {"item": "5bde836f-9343-4934-b01a-606f707020ef", "prefab": "5bde836f-9343-4934-b01a-606f707020ef", "mask": "axPBGoqorEe77Oqqjfzb5w", "swatch": "MQOxmUk-mUG-Ee9m9nX31Q", "decal": ""}, {"item": "6QT99hx6DE6t9cKAs61i3w", "prefab": "6QT99hx6DE6t9cKAs61i3w", "mask": "", "swatch": "", "decal": ""}, {"item": "6QT99hx6DE6t9cKAs61i3w", "prefab": "6QT99hx6DE6t9cKAs61i3w", "mask": "c_t9XDbn-Eqml1O-wC5zJQ", "swatch": "KwCEye0nsESFs-yj4h9sjQ", "decal": ""}, {"item": "9cdb4965-6f00-4304-afed-e301e73a2b3a", "prefab": "9cdb4965-6f00-4304-afed-e301e73a2b3a", "mask": "", "swatch": "", "decal": ""}, {"item": "9cdb4965-6f00-4304-afed-e301e73a2b3a", "prefab": "9cdb4965-6f00-4304-afed-e301e73a2b3a", "mask": "65ed43ff-dfc4-4cab-b0b9-77dc94165e24", "swatch": "67fc9269-8dd8-4d6f-b594-c77e716f2482", "decal": ""}, {"item": "9cdb4965-6f00-4304-afed-e301e73a2b3a", "prefab": "9cdb4965-6f00-4304-afed-e301e73a2b3a", "mask": "65ed43ff-dfc4-4cab-b0b9-77dc94165e24", "swatch": "43501b0c-eaaa-4ab3-9d03-2ca61d2e8fc9", "decal": ""}, {"item": "9cdb4965-6f00-4304-afed-e301e73a2b3a", "prefab": "9cdb4965-6f00-4304-afed-e301e73a2b3a", "mask": "65ed43ff-dfc4-4cab-b0b9-77dc94165e24", "swatch": "d6823e01-69f0-4f85-b94a-74894356a2cf", "decal": ""}, {"item": "Wua43ED71UqL71ATtyS0iQ", "prefab": "Wua43ED71UqL71ATtyS0iQ", "mask": "", "swatch": "", "decal": ""}, {"item": "Wua43ED71UqL71ATtyS0iQ", "prefab": "Wua43ED71UqL71ATtyS0iQ", "mask": "sV_ByCug30G1Gw-77KUe1Q", "swatch": "XFCpIdeDXUqeYCIgh4fGtg", "decal": ""}, {"item": "Wua43ED71UqL71ATtyS0iQ", "prefab": "Wua43ED71UqL71ATtyS0iQ", "mask": "sV_ByCug30G1Gw-77KUe1Q", "swatch": "oKxmH-1s2EyIDkZoOQSQQw", "decal": ""}, {"item": "Wua43ED71UqL71ATtyS0iQ", "prefab": "Wua43ED71UqL71ATtyS0iQ", "mask": "sV_ByCug30G1Gw-77KUe1Q", "swatch": "MxnAq_XOw0uTzQ8IIxqb_Q", "decal": ""}, {"item": "59d9560d-e5af-4205-b860-3afea1cc31a9", "prefab": "59d9560d-e5af-4205-b860-3afea1cc31a9", "mask": "", "swatch": "", "decal": ""}, {"item": "59d9560d-e5af-4205-b860-3afea1cc31a9", "prefab": "59d9560d-e5af-4205-b860-3afea1cc31a9", "mask": "b94411f6-5b71-48d0-ace1-ac6f63167065", "swatch": "c8cc9589-4831-4e73-a865-ef8c139a2ba7", "decal": ""}, {"item": "59d9560d-e5af-4205-b860-3afea1cc31a9", "prefab": "59d9560d-e5af-4205-b860-3afea1cc31a9", "mask": "b94411f6-5b71-48d0-ace1-ac6f63167065", "swatch": "a36d252b-16ac-4a82-bece-de145147e7d9", "decal": ""}, {"item": "59d9560d-e5af-4205-b860-3afea1cc31a9", "prefab": "59d9560d-e5af-4205-b860-3afea1cc31a9", "mask": "b94411f6-5b71-48d0-ace1-ac6f63167065", "swatch": "a089bcc6-798d-4777-98e2-7afd290dc6b6", "decal": ""}, {"item": "59d9560d-e5af-4205-b860-3afea1cc31a9", "prefab": "59d9560d-e5af-4205-b860-3afea1cc31a9", "mask": "b94411f6-5b71-48d0-ace1-ac6f63167065", "swatch": "d78643e4-c0d2-4836-bf22-b5a0e2d65b28", "decal": ""}, {"item": "59d9560d-e5af-4205-b860-3afea1cc31a9", "prefab": "59d9560d-e5af-4205-b860-3afea1cc31a9", "mask": "b94411f6-5b71-48d0-ace1-ac6f63167065", "swatch": "68273c9e-96d6-4ddb-aaab-d99229e864d5", "decal": ""}, {"item": "59d9560d-e5af-4205-b860-3afea1cc31a9", "prefab": "59d9560d-e5af-4205-b860-3afea1cc31a9", "mask": "b94411f6-5b71-48d0-ace1-ac6f63167065", "swatch": "4ElTihrMA0ipcgeb9UG01Q", "decal": ""}, {"item": "gwLiwmqBIk6glFxvaTzxeA", "prefab": "gwLiwmqBIk6glFxvaTzxeA", "mask": "", "swatch": "", "decal": ""}, {"item": "gwLiwmqBIk6glFxvaTzxeA", "prefab": "gwLiwmqBIk6glFxvaTzxeA", "mask": "fXnd_COSzUeznwe7P4ZYRg", "swatch": "AAbGHOOitUu9enweOXbrkw", "decal": ""}, {"item": "JWanxvOHyESg9F_HlmTOfA", "prefab": "JWanxvOHyESg9F_HlmTOfA", "mask": "", "swatch": "", "decal": ""}, {"item": "JWanxvOHyESg9F_HlmTOfA", "prefab": "JWanxvOHyESg9F_HlmTOfA", "mask": "Uhw5NBQy-EOIiCWiZFiL7g", "swatch": "vuSwk04zykCe3ANKN838OA", "decal": ""}, {"item": "703f1987-2b31-4b6f-bab7-1939b1105ac0", "prefab": "703f1987-2b31-4b6f-bab7-1939b1105ac0", "mask": "", "swatch": "", "decal": ""}, {"item": "102c625b-b988-4bf8-a2aa-a31ad7029cdc", "prefab": "102c625b-b988-4bf8-a2aa-a31ad7029cdc", "mask": "", "swatch": "", "decal": ""}, {"item": "102c625b-b988-4bf8-a2aa-a31ad7029cdc", "prefab": "102c625b-b988-4bf8-a2aa-a31ad7029cdc", "mask": "ccf1ccc1-e229-4157-bb74-f2cdef01e547", "swatch": "bd4a84e2-b67a-4269-a26a-17fb23ddb09e", "decal": ""}, {"item": "102c625b-b988-4bf8-a2aa-a31ad7029cdc", "prefab": "102c625b-b988-4bf8-a2aa-a31ad7029cdc", "mask": "vm1ESK9ls0SaxSwHplNALQ", "swatch": "DzgN67HoikexE2rTlZLdYQ", "decal": ""}, {"item": "0QOlT9c5xUuinpfZaoEe8Q", "prefab": "0QOlT9c5xUuinpfZaoEe8Q", "mask": "", "swatch": "", "decal": ""}, {"item": "0QOlT9c5xUuinpfZaoEe8Q", "prefab": "0QOlT9c5xUuinpfZaoEe8Q", "mask": "znNiHfNdTE-szOIAnIWFQg", "swatch": "Mt2a9ygWPkS-DyXj1q-Kvw", "decal": ""}, {"item": "0QOlT9c5xUuinpfZaoEe8Q", "prefab": "0QOlT9c5xUuinpfZaoEe8Q", "mask": "znNiHfNdTE-szOIAnIWFQg", "swatch": "LEUQnDz-k0m-hY-AX3Z9Sg", "decal": ""}, {"item": "0QOlT9c5xUuinpfZaoEe8Q", "prefab": "0QOlT9c5xUuinpfZaoEe8Q", "mask": "znNiHfNdTE-szOIAnIWFQg", "swatch": "cSFt9MWLj06CDzIzATqrTg", "decal": ""}, {"item": "0QOlT9c5xUuinpfZaoEe8Q", "prefab": "0QOlT9c5xUuinpfZaoEe8Q", "mask": "sIP83lqv3kG31TH5Rw4gqg", "swatch": "B6qNcZQ770eJel13RskuIg", "decal": ""}, {"item": "0QOlT9c5xUuinpfZaoEe8Q", "prefab": "0QOlT9c5xUuinpfZaoEe8Q", "mask": "sIP83lqv3kG31TH5Rw4gqg", "swatch": "p3g8YAOhTUKAiE-Wh-X-aA", "decal": ""}, {"item": "0QOlT9c5xUuinpfZaoEe8Q", "prefab": "0QOlT9c5xUuinpfZaoEe8Q", "mask": "sIP83lqv3kG31TH5Rw4gqg", "swatch": "SqHAYcqBXU2PtF2urWGruQ", "decal": ""}, {"item": "-SQLtL4rdEWTJdw2IroQCQ", "prefab": "-SQLtL4rdEWTJdw2IroQCQ", "mask": "", "swatch": "", "decal": ""}, {"item": "ReqOfohe6UamzaiHG1x3Ow", "prefab": "ReqOfohe6UamzaiHG1x3Ow", "mask": "", "swatch": "", "decal": ""}, {"item": "ReqOfohe6UamzaiHG1x3Ow", "prefab": "ReqOfohe6UamzaiHG1x3Ow", "mask": "uiAwVt3HK0mRVtWp9B-gTg", "swatch": "lDXambcP60CeY2C8qeGDFw", "decal": ""}, {"item": "M9weaekvIUimiJSVJqZl-w", "prefab": "M9weaekvIUimiJSVJqZl-w", "mask": "", "swatch": "", "decal": ""}, {"item": "M9weaekvIUimiJSVJqZl-w", "prefab": "M9weaekvIUimiJSVJqZl-w", "mask": "GV8z2dlqGEqIAYaGrF1ZYQ", "swatch": "gDH1WmW5rk2RdY4wJYYZow", "decal": ""}, {"item": "M9weaekvIUimiJSVJqZl-w", "prefab": "M9weaekvIUimiJSVJqZl-w", "mask": "GV8z2dlqGEqIAYaGrF1ZYQ", "swatch": "wHJnbDOo8ESmUbQDr0k31w", "decal": ""}, {"item": "M9weaekvIUimiJSVJqZl-w", "prefab": "M9weaekvIUimiJSVJqZl-w", "mask": "GV8z2dlqGEqIAYaGrF1ZYQ", "swatch": "GK3yolCNlEm3B_5DtHkE8w", "decal": ""}, {"item": "M9weaekvIUimiJSVJqZl-w", "prefab": "M9weaekvIUimiJSVJqZl-w", "mask": "GV8z2dlqGEqIAYaGrF1ZYQ", "swatch": "U2ZbHn8WJEySUXVpzAY6ig", "decal": ""}, {"item": "M9weaekvIUimiJSVJqZl-w", "prefab": "M9weaekvIUimiJSVJqZl-w", "mask": "GV8z2dlqGEqIAYaGrF1ZYQ", "swatch": "L333w3dfIEWDbtt1Yn1gBg", "decal": ""}, {"item": "2caHvVQ1vECrPv-YcdxdyA", "prefab": "2caHvVQ1vECrPv-YcdxdyA", "mask": "", "swatch": "", "decal": ""}, {"item": "2caHvVQ1vECrPv-YcdxdyA", "prefab": "2caHvVQ1vECrPv-YcdxdyA", "mask": "nW_fBsuZA0OfIjTMXm6ZCA", "swatch": "FNe9LkWJMUeC3frYJMyEaQ", "decal": ""}, {"item": "2caHvVQ1vECrPv-YcdxdyA", "prefab": "2caHvVQ1vECrPv-YcdxdyA", "mask": "nW_fBsuZA0OfIjTMXm6ZCA", "swatch": "mudt-Z5-5USdnIq-xaDgOw", "decal": ""}, {"item": "c87239ca-a255-4b13-bbc5-1b38236fb4cc", "prefab": "c87239ca-a255-4b13-bbc5-1b38236fb4cc", "mask": "", "swatch": "", "decal": ""}, {"item": "8c51fc1a-b1ad-4002-a13c-4313abd92c7f", "prefab": "8c51fc1a-b1ad-4002-a13c-4313abd92c7f", "mask": "", "swatch": "", "decal": ""}, {"item": "24c4404a-839f-46bd-98b6-6848d747a98b", "prefab": "24c4404a-839f-46bd-98b6-6848d747a98b", "mask": "", "swatch": "", "decal": ""}, {"item": "24c4404a-839f-46bd-98b6-6848d747a98b", "prefab": "24c4404a-839f-46bd-98b6-6848d747a98b", "mask": "nalaCGqtL0SFA4Itwqmp8g", "swatch": "6vjA_vHq70Gclypll_GS_A", "decal": ""}, {"item": "24c4404a-839f-46bd-98b6-6848d747a98b", "prefab": "24c4404a-839f-46bd-98b6-6848d747a98b", "mask": "NH0L99bd00OVKMyltuSnHQ", "swatch": "xVwzV9-m6E-8PVpkg4D-AQ", "decal": ""}, {"item": "24c4404a-839f-46bd-98b6-6848d747a98b", "prefab": "24c4404a-839f-46bd-98b6-6848d747a98b", "mask": "NH0L99bd00OVKMyltuSnHQ", "swatch": "mraJrTA4V0eARb_Kq7yYVw", "decal": ""}, {"item": "24c4404a-839f-46bd-98b6-6848d747a98b", "prefab": "24c4404a-839f-46bd-98b6-6848d747a98b", "mask": "YFZimVHKuk--lXS8RCrZTA", "swatch": "09Cg6dOq2kC2-tI5yyGSYg", "decal": ""}, {"item": "s6mUIqiPCk6e57UYCx3SZA", "prefab": "s6mUIqiPCk6e57UYCx3SZA", "mask": "", "swatch": "", "decal": ""}, {"item": "s6mUIqiPCk6e57UYCx3SZA", "prefab": "s6mUIqiPCk6e57UYCx3SZA", "mask": "dpBx3DxB7kKJphxCvfmKKw", "swatch": "PcUZrdKXhk2XHa5Gy0YnnA", "decal": ""}, {"item": "s6mUIqiPCk6e57UYCx3SZA", "prefab": "s6mUIqiPCk6e57UYCx3SZA", "mask": "smmfK-N2R0uoDiuIHrhImg", "swatch": "0cP9EcZRFEuMOvmZ_Kx9dA", "decal": ""}, {"item": "6d36091e-7083-4e3d-8729-94612602c8cb", "prefab": "6d36091e-7083-4e3d-8729-94612602c8cb", "mask": "", "swatch": "", "decal": ""}, {"item": "6d36091e-7083-4e3d-8729-94612602c8cb", "prefab": "6d36091e-7083-4e3d-8729-94612602c8cb", "mask": "32d91d32-b102-4093-927a-6dc3ac2e9e86", "swatch": "64545dcc-a8eb-4c69-a539-78ee73a2d327", "decal": ""}, {"item": "6d36091e-7083-4e3d-8729-94612602c8cb", "prefab": "6d36091e-7083-4e3d-8729-94612602c8cb", "mask": "32d91d32-b102-4093-927a-6dc3ac2e9e86", "swatch": "e02838f8-4029-455a-b35c-dd40f972b05b", "decal": ""}, {"item": "6d36091e-7083-4e3d-8729-94612602c8cb", "prefab": "6d36091e-7083-4e3d-8729-94612602c8cb", "mask": "32d91d32-b102-4093-927a-6dc3ac2e9e86", "swatch": "71e5a0fe-fc8d-420b-af3b-e74160e0d1f3", "decal": ""}, {"item": "6d36091e-7083-4e3d-8729-94612602c8cb", "prefab": "6d36091e-7083-4e3d-8729-94612602c8cb", "mask": "32d91d32-b102-4093-927a-6dc3ac2e9e86", "swatch": "909ed249-ce09-4304-8b02-56794fa7567d", "decal": ""}, {"item": "6d36091e-7083-4e3d-8729-94612602c8cb", "prefab": "6d36091e-7083-4e3d-8729-94612602c8cb", "mask": "32d91d32-b102-4093-927a-6dc3ac2e9e86", "swatch": "Tbrc2MlSlE6mKua8qSoluQ", "decal": ""}, {"item": "6d36091e-7083-4e3d-8729-94612602c8cb", "prefab": "6d36091e-7083-4e3d-8729-94612602c8cb", "mask": "32d91d32-b102-4093-927a-6dc3ac2e9e86", "swatch": "aBQ3R9boek-mYeSYSNvDbQ", "decal": ""}, {"item": "01689baf-405e-4b38-9f09-b624e03865b8", "prefab": "01689baf-405e-4b38-9f09-b624e03865b8", "mask": "", "swatch": "", "decal": ""}, {"item": "01689baf-405e-4b38-9f09-b624e03865b8", "prefab": "01689baf-405e-4b38-9f09-b624e03865b8", "mask": "abdf525b-b871-41be-a1c8-810f49740ad4", "swatch": "357eb160-360b-4a63-840d-65d11cc8ffc2", "decal": ""}, {"item": "01689baf-405e-4b38-9f09-b624e03865b8", "prefab": "01689baf-405e-4b38-9f09-b624e03865b8", "mask": "abdf525b-b871-41be-a1c8-810f49740ad4", "swatch": "a38254c0-00e3-429b-9d5d-b9e6a9812e69", "decal": ""}, {"item": "01689baf-405e-4b38-9f09-b624e03865b8", "prefab": "01689baf-405e-4b38-9f09-b624e03865b8", "mask": "abdf525b-b871-41be-a1c8-810f49740ad4", "swatch": "d3a9b576-5433-4305-bd96-b08820c09212", "decal": ""}, {"item": "01689baf-405e-4b38-9f09-b624e03865b8", "prefab": "01689baf-405e-4b38-9f09-b624e03865b8", "mask": "abdf525b-b871-41be-a1c8-810f49740ad4", "swatch": "7afe1bb0-5ca9-4263-8f85-49d3a18353c7", "decal": ""}, {"item": "01689baf-405e-4b38-9f09-b624e03865b8", "prefab": "01689baf-405e-4b38-9f09-b624e03865b8", "mask": "abdf525b-b871-41be-a1c8-810f49740ad4", "swatch": "RKmRHk2N_U2gd91nybo6vg", "decal": ""}, {"item": "01689baf-405e-4b38-9f09-b624e03865b8", "prefab": "01689baf-405e-4b38-9f09-b624e03865b8", "mask": "abdf525b-b871-41be-a1c8-810f49740ad4", "swatch": "d3KmqVuMQkCYK6sVTMOTOA", "decal": ""}, {"item": "8d10cc78-6b00-45f3-affb-205e9cc5b03f", "prefab": "8d10cc78-6b00-45f3-affb-205e9cc5b03f", "mask": "", "swatch": "", "decal": ""}, {"item": "61ba3c90-c81e-4deb-bc79-50c0f1fe3e83", "prefab": "61ba3c90-c81e-4deb-bc79-50c0f1fe3e83", "mask": "", "swatch": "", "decal": ""}, {"item": "cc96f8a5-bc5b-4f89-83b7-ecd53905ada7", "prefab": "cc96f8a5-bc5b-4f89-83b7-ecd53905ada7", "mask": "", "swatch": "", "decal": ""}, {"item": "3e139dd9-4757-43a8-8546-8da70dc17f0a", "prefab": "3e139dd9-4757-43a8-8546-8da70dc17f0a", "mask": "", "swatch": "", "decal": ""}, {"item": "cca7ab3d-5609-4700-92fd-3280fcea9b77", "prefab": "cca7ab3d-5609-4700-92fd-3280fcea9b77", "mask": "", "swatch": "", "decal": ""}, {"item": "7553d851-1986-4325-870d-e425dfb7d007", "prefab": "7553d851-1986-4325-870d-e425dfb7d007", "mask": "", "swatch": "", "decal": ""}, {"item": "6b337a35-95d9-4b3a-9891-d9fbb06262aa", "prefab": "6b337a35-95d9-4b3a-9891-d9fbb06262aa", "mask": "", "swatch": "", "decal": ""}, {"item": "9bf5d259-7774-4cbe-a90f-7f188cc0dce7", "prefab": "9bf5d259-7774-4cbe-a90f-7f188cc0dce7", "mask": "", "swatch": "", "decal": ""}, {"item": "-twtjyBdQ02EAdOfBGTiEw", "prefab": "-twtjyBdQ02EAdOfBGTiEw", "mask": "", "swatch": "", "decal": ""}, {"item": "27cdecd2-88f1-40a8-80a9-87abd827c3c5", "prefab": "27cdecd2-88f1-40a8-80a9-87abd827c3c5", "mask": "", "swatch": "", "decal": ""}, {"item": "c161dc73-6ce3-49c4-8d15-45e6911f84e4", "prefab": "c161dc73-6ce3-49c4-8d15-45e6911f84e4", "mask": "", "swatch": "", "decal": ""}, {"item": "45f5e714-8a5f-4385-a97f-675066167011", "prefab": "45f5e714-8a5f-4385-a97f-675066167011", "mask": "", "swatch": "", "decal": ""}, {"item": "5b6535f0-86cd-417a-bcce-a1ace9a5f260", "prefab": "5b6535f0-86cd-417a-bcce-a1ace9a5f260", "mask": "", "swatch": "", "decal": ""}, {"item": "5b6535f0-86cd-417a-bcce-a1ace9a5f260", "prefab": "5b6535f0-86cd-417a-bcce-a1ace9a5f260", "mask": "6094370d-5e28-473a-b422-f3f1c75d5db8", "swatch": "d1eb3573-672c-4bb4-9ec5-7c81e6a38d3d", "decal": ""}, {"item": "5b6535f0-86cd-417a-bcce-a1ace9a5f260", "prefab": "5b6535f0-86cd-417a-bcce-a1ace9a5f260", "mask": "6094370d-5e28-473a-b422-f3f1c75d5db8", "swatch": "e548124c-d7f3-45de-aaa0-f66130eca576", "decal": ""}, {"item": "5b6535f0-86cd-417a-bcce-a1ace9a5f260", "prefab": "5b6535f0-86cd-417a-bcce-a1ace9a5f260", "mask": "6094370d-5e28-473a-b422-f3f1c75d5db8", "swatch": "7221132e-053a-4c58-b719-ed3c8558907d", "decal": ""}, {"item": "5b6535f0-86cd-417a-bcce-a1ace9a5f260", "prefab": "5b6535f0-86cd-417a-bcce-a1ace9a5f260", "mask": "6094370d-5e28-473a-b422-f3f1c75d5db8", "swatch": "74b009f4-e128-4c59-8a6a-4c0ba6119bde", "decal": ""}, {"item": "5b6535f0-86cd-417a-bcce-a1ace9a5f260", "prefab": "5b6535f0-86cd-417a-bcce-a1ace9a5f260", "mask": "6094370d-5e28-473a-b422-f3f1c75d5db8", "swatch": "29EZ6r2SsUakWjzuevxeoA", "decal": ""}, {"item": "5b6535f0-86cd-417a-bcce-a1ace9a5f260", "prefab": "5b6535f0-86cd-417a-bcce-a1ace9a5f260", "mask": "6094370d-5e28-473a-b422-f3f1c75d5db8", "swatch": "imDGL6dhrka-KliYlfpdgw", "decal": ""}, {"item": "83be5ba4-525a-4781-a5f6-839c22e4d1d3", "prefab": "83be5ba4-525a-4781-a5f6-839c22e4d1d3", "mask": "", "swatch": "", "decal": ""}, {"item": "83be5ba4-525a-4781-a5f6-839c22e4d1d3", "prefab": "83be5ba4-525a-4781-a5f6-839c22e4d1d3", "mask": "0b2395e1-ebcc-47e9-aaf1-faf9e9cec4cd", "swatch": "724c79a3-822c-422f-9462-a5374ee0211c", "decal": ""}, {"item": "83be5ba4-525a-4781-a5f6-839c22e4d1d3", "prefab": "83be5ba4-525a-4781-a5f6-839c22e4d1d3", "mask": "0b2395e1-ebcc-47e9-aaf1-faf9e9cec4cd", "swatch": "8377ab96-c908-457f-9fee-b784c9a759f3", "decal": ""}, {"item": "83be5ba4-525a-4781-a5f6-839c22e4d1d3", "prefab": "83be5ba4-525a-4781-a5f6-839c22e4d1d3", "mask": "0b2395e1-ebcc-47e9-aaf1-faf9e9cec4cd", "swatch": "e69372eb-2fd8-4e17-a448-ee0be61a2c7a", "decal": ""}, {"item": "83be5ba4-525a-4781-a5f6-839c22e4d1d3", "prefab": "83be5ba4-525a-4781-a5f6-839c22e4d1d3", "mask": "3290f148-1bd1-4ffd-a947-61fd0529af05", "swatch": "0809a5cf-a0f3-4614-9f98-71518524518a", "decal": ""}, {"item": "83be5ba4-525a-4781-a5f6-839c22e4d1d3", "prefab": "83be5ba4-525a-4781-a5f6-839c22e4d1d3", "mask": "qCIoeLQw-Uq-SKIetIgIpA", "swatch": "e4IDo-fuH0C9YByzcX5k-A", "decal": ""}, {"item": "83be5ba4-525a-4781-a5f6-839c22e4d1d3", "prefab": "83be5ba4-525a-4781-a5f6-839c22e4d1d3", "mask": "rcuK1wZcXkCe0_LGX7E-Tw", "swatch": "W4a144so90eRzCojUnDowA", "decal": ""}, {"item": "c6c08eb5-381a-4193-9722-80da95d62abe", "prefab": "c6c08eb5-381a-4193-9722-80da95d62abe", "mask": "", "swatch": "", "decal": ""}, {"item": "c6c08eb5-381a-4193-9722-80da95d62abe", "prefab": "c6c08eb5-381a-4193-9722-80da95d62abe", "mask": "0b2395e1-ebcc-47e9-aaf1-faf9e9cec4cd", "swatch": "dee70c38-7a99-4c2b-9181-665f1bf75aca", "decal": ""}, {"item": "c6c08eb5-381a-4193-9722-80da95d62abe", "prefab": "c6c08eb5-381a-4193-9722-80da95d62abe", "mask": "0b2395e1-ebcc-47e9-aaf1-faf9e9cec4cd", "swatch": "0ecb8a2a-cffc-47db-aeda-fb0684aef1e5", "decal": ""}, {"item": "c6c08eb5-381a-4193-9722-80da95d62abe", "prefab": "c6c08eb5-381a-4193-9722-80da95d62abe", "mask": "0b2395e1-ebcc-47e9-aaf1-faf9e9cec4cd", "swatch": "8377ab96-c908-457f-9fee-b784c9a759f3", "decal": ""}, {"item": "c6c08eb5-381a-4193-9722-80da95d62abe", "prefab": "c6c08eb5-381a-4193-9722-80da95d62abe", "mask": "0b2395e1-ebcc-47e9-aaf1-faf9e9cec4cd", "swatch": "724c79a3-822c-422f-9462-a5374ee0211c", "decal": ""}, {"item": "c6c08eb5-381a-4193-9722-80da95d62abe", "prefab": "c6c08eb5-381a-4193-9722-80da95d62abe", "mask": "0b2395e1-ebcc-47e9-aaf1-faf9e9cec4cd", "swatch": "67bcca75-4ab1-4964-8688-9908c464d355", "decal": ""}, {"item": "c6c08eb5-381a-4193-9722-80da95d62abe", "prefab": "c6c08eb5-381a-4193-9722-80da95d62abe", "mask": "8212812b-e7e7-4f6d-aa27-d1112af45473", "swatch": "0809a5cf-a0f3-4614-9f98-71518524518a", "decal": ""}, {"item": "c6c08eb5-381a-4193-9722-80da95d62abe", "prefab": "c6c08eb5-381a-4193-9722-80da95d62abe", "mask": "9zGwmle_yk25V-KnC_PJEg", "swatch": "31jelNWFyk6FlWt4HyPAlw", "decal": ""}, {"item": "c6c08eb5-381a-4193-9722-80da95d62abe", "prefab": "c6c08eb5-381a-4193-9722-80da95d62abe", "mask": "pHw_MghIYUazSFve6L237w", "swatch": "W4a144so90eRzCojUnDowA", "decal": ""}, {"item": "5accb7dc-90a3-46ee-bad2-8fd1861d50f1", "prefab": "5accb7dc-90a3-46ee-bad2-8fd1861d50f1", "mask": "", "swatch": "", "decal": ""}, {"item": "5accb7dc-90a3-46ee-bad2-8fd1861d50f1", "prefab": "5accb7dc-90a3-46ee-bad2-8fd1861d50f1", "mask": "b292eb4b-07e3-4a48-99b5-3c6587a1e02e", "swatch": "827a1538-51bb-4fef-99c1-7f1bebd9866c", "decal": ""}, {"item": "5accb7dc-90a3-46ee-bad2-8fd1861d50f1", "prefab": "5accb7dc-90a3-46ee-bad2-8fd1861d50f1", "mask": "b292eb4b-07e3-4a48-99b5-3c6587a1e02e", "swatch": "ad61c418-6d77-4a99-8ac5-9f10f5a3d42f", "decal": ""}, {"item": "5accb7dc-90a3-46ee-bad2-8fd1861d50f1", "prefab": "5accb7dc-90a3-46ee-bad2-8fd1861d50f1", "mask": "b292eb4b-07e3-4a48-99b5-3c6587a1e02e", "swatch": "6dd95046-acf8-42fe-ab78-80a334096a9d", "decal": ""}, {"item": "5accb7dc-90a3-46ee-bad2-8fd1861d50f1", "prefab": "5accb7dc-90a3-46ee-bad2-8fd1861d50f1", "mask": "b292eb4b-07e3-4a48-99b5-3c6587a1e02e", "swatch": "Z9H3wUn3_kS6pPehL74aHg", "decal": ""}, {"item": "5accb7dc-90a3-46ee-bad2-8fd1861d50f1", "prefab": "5accb7dc-90a3-46ee-bad2-8fd1861d50f1", "mask": "b292eb4b-07e3-4a48-99b5-3c6587a1e02e", "swatch": "J1bmomo_3Ume0KR2Yh46Uw", "decal": ""}, {"item": "5accb7dc-90a3-46ee-bad2-8fd1861d50f1", "prefab": "5accb7dc-90a3-46ee-bad2-8fd1861d50f1", "mask": "b292eb4b-07e3-4a48-99b5-3c6587a1e02e", "swatch": "40ldoL7MAUKnb5JA68CMBA", "decal": ""}, {"item": "5accb7dc-90a3-46ee-bad2-8fd1861d50f1", "prefab": "5accb7dc-90a3-46ee-bad2-8fd1861d50f1", "mask": "27a5df72-4095-4837-bf24-cdd3d95a43e9", "swatch": "827a1538-51bb-4fef-99c1-7f1bebd9866c", "decal": ""}, {"item": "5accb7dc-90a3-46ee-bad2-8fd1861d50f1", "prefab": "5accb7dc-90a3-46ee-bad2-8fd1861d50f1", "mask": "27a5df72-4095-4837-bf24-cdd3d95a43e9", "swatch": "ad61c418-6d77-4a99-8ac5-9f10f5a3d42f", "decal": ""}, {"item": "5accb7dc-90a3-46ee-bad2-8fd1861d50f1", "prefab": "5accb7dc-90a3-46ee-bad2-8fd1861d50f1", "mask": "27a5df72-4095-4837-bf24-cdd3d95a43e9", "swatch": "6dd95046-acf8-42fe-ab78-80a334096a9d", "decal": ""}, {"item": "5accb7dc-90a3-46ee-bad2-8fd1861d50f1", "prefab": "5accb7dc-90a3-46ee-bad2-8fd1861d50f1", "mask": "27a5df72-4095-4837-bf24-cdd3d95a43e9", "swatch": "Z9H3wUn3_kS6pPehL74aHg", "decal": ""}, {"item": "5accb7dc-90a3-46ee-bad2-8fd1861d50f1", "prefab": "5accb7dc-90a3-46ee-bad2-8fd1861d50f1", "mask": "27a5df72-4095-4837-bf24-cdd3d95a43e9", "swatch": "J1bmomo_3Ume0KR2Yh46Uw", "decal": ""}, {"item": "5accb7dc-90a3-46ee-bad2-8fd1861d50f1", "prefab": "5accb7dc-90a3-46ee-bad2-8fd1861d50f1", "mask": "27a5df72-4095-4837-bf24-cdd3d95a43e9", "swatch": "40ldoL7MAUKnb5JA68CMBA", "decal": ""}, {"item": "5accb7dc-90a3-46ee-bad2-8fd1861d50f1", "prefab": "5accb7dc-90a3-46ee-bad2-8fd1861d50f1", "mask": "cf119781-5bd9-4b85-9a0b-12e82e988c23", "swatch": "827a1538-51bb-4fef-99c1-7f1bebd9866c", "decal": ""}, {"item": "5accb7dc-90a3-46ee-bad2-8fd1861d50f1", "prefab": "5accb7dc-90a3-46ee-bad2-8fd1861d50f1", "mask": "cf119781-5bd9-4b85-9a0b-12e82e988c23", "swatch": "ad61c418-6d77-4a99-8ac5-9f10f5a3d42f", "decal": ""}, {"item": "5accb7dc-90a3-46ee-bad2-8fd1861d50f1", "prefab": "5accb7dc-90a3-46ee-bad2-8fd1861d50f1", "mask": "cf119781-5bd9-4b85-9a0b-12e82e988c23", "swatch": "6dd95046-acf8-42fe-ab78-80a334096a9d", "decal": ""}, {"item": "5accb7dc-90a3-46ee-bad2-8fd1861d50f1", "prefab": "5accb7dc-90a3-46ee-bad2-8fd1861d50f1", "mask": "cf119781-5bd9-4b85-9a0b-12e82e988c23", "swatch": "Z9H3wUn3_kS6pPehL74aHg", "decal": ""}, {"item": "5accb7dc-90a3-46ee-bad2-8fd1861d50f1", "prefab": "5accb7dc-90a3-46ee-bad2-8fd1861d50f1", "mask": "cf119781-5bd9-4b85-9a0b-12e82e988c23", "swatch": "J1bmomo_3Ume0KR2Yh46Uw", "decal": ""}, {"item": "5accb7dc-90a3-46ee-bad2-8fd1861d50f1", "prefab": "5accb7dc-90a3-46ee-bad2-8fd1861d50f1", "mask": "cf119781-5bd9-4b85-9a0b-12e82e988c23", "swatch": "40ldoL7MAUKnb5JA68CMBA", "decal": ""}, {"item": "bf13fa1c-f991-4aaa-9402-aac109be9562", "prefab": "bf13fa1c-f991-4aaa-9402-aac109be9562", "mask": "", "swatch": "", "decal": ""}, {"item": "bf13fa1c-f991-4aaa-9402-aac109be9562", "prefab": "bf13fa1c-f991-4aaa-9402-aac109be9562", "mask": "wzt-5C_LjEmIQWUVPcYWhA", "swatch": "ERnUPsyT6kuEnIRZF8zPwg", "decal": ""}, {"item": "bf13fa1c-f991-4aaa-9402-aac109be9562", "prefab": "bf13fa1c-f991-4aaa-9402-aac109be9562", "mask": "wzt-5C_LjEmIQWUVPcYWhA", "swatch": "iaDL2im4lkCXUo0NWM-F3g", "decal": ""}, {"item": "2e5afc40-7e25-418c-8b1b-316b8ef479a7", "prefab": "2e5afc40-7e25-418c-8b1b-316b8ef479a7", "mask": "", "swatch": "", "decal": ""}, {"item": "2e5afc40-7e25-418c-8b1b-316b8ef479a7", "prefab": "2e5afc40-7e25-418c-8b1b-316b8ef479a7", "mask": "15099be7-38b7-4b0b-ac13-1bacedbf5fb2", "swatch": "ed134bee-d199-43eb-98bd-206571603f40", "decal": ""}, {"item": "2e5afc40-7e25-418c-8b1b-316b8ef479a7", "prefab": "2e5afc40-7e25-418c-8b1b-316b8ef479a7", "mask": "15099be7-38b7-4b0b-ac13-1bacedbf5fb2", "swatch": "59866578-d4e0-417b-9483-233ab108b1ac", "decal": ""}, {"item": "2e5afc40-7e25-418c-8b1b-316b8ef479a7", "prefab": "2e5afc40-7e25-418c-8b1b-316b8ef479a7", "mask": "15099be7-38b7-4b0b-ac13-1bacedbf5fb2", "swatch": "22dc463a-a89b-46ad-9a0d-557c742b4a80", "decal": ""}, {"item": "2e5afc40-7e25-418c-8b1b-316b8ef479a7", "prefab": "2e5afc40-7e25-418c-8b1b-316b8ef479a7", "mask": "15099be7-38b7-4b0b-ac13-1bacedbf5fb2", "swatch": "e1f49b74-b071-4bd1-9c4b-af36d24d45c5", "decal": ""}, {"item": "2e5afc40-7e25-418c-8b1b-316b8ef479a7", "prefab": "2e5afc40-7e25-418c-8b1b-316b8ef479a7", "mask": "15099be7-38b7-4b0b-ac13-1bacedbf5fb2", "swatch": "ITH2OpzjP022vZ5JgxV_iQ", "decal": ""}, {"item": "2e5afc40-7e25-418c-8b1b-316b8ef479a7", "prefab": "2e5afc40-7e25-418c-8b1b-316b8ef479a7", "mask": "15099be7-38b7-4b0b-ac13-1bacedbf5fb2", "swatch": "PMa9370LPEig_7pKwCceEQ", "decal": ""}, {"item": "6b9e022c-0b68-48fd-8eca-da8573c18900", "prefab": "6b9e022c-0b68-48fd-8eca-da8573c18900", "mask": "", "swatch": "", "decal": ""}, {"item": "6b9e022c-0b68-48fd-8eca-da8573c18900", "prefab": "6b9e022c-0b68-48fd-8eca-da8573c18900", "mask": "cf119781-5bd9-4b85-9a0b-12e82e988c23", "swatch": "d6edbc00-3c1d-4f49-8412-3ef8c7c5f4c2", "decal": ""}, {"item": "6b9e022c-0b68-48fd-8eca-da8573c18900", "prefab": "6b9e022c-0b68-48fd-8eca-da8573c18900", "mask": "cf119781-5bd9-4b85-9a0b-12e82e988c23", "swatch": "6dd95046-acf8-42fe-ab78-80a334096a9d", "decal": ""}, {"item": "6b9e022c-0b68-48fd-8eca-da8573c18900", "prefab": "6b9e022c-0b68-48fd-8eca-da8573c18900", "mask": "cf119781-5bd9-4b85-9a0b-12e82e988c23", "swatch": "5c4a2b35-0e1c-44de-8c3a-96d4a6458b1b", "decal": ""}, {"item": "67117b3d-0669-4cf3-8a4f-066cabd4d8b1", "prefab": "67117b3d-0669-4cf3-8a4f-066cabd4d8b1", "mask": "", "swatch": "", "decal": ""}, {"item": "67117b3d-0669-4cf3-8a4f-066cabd4d8b1", "prefab": "67117b3d-0669-4cf3-8a4f-066cabd4d8b1", "mask": "qSsEcadcm0-twR1TvVcgYQ", "swatch": "_VfdBTi7PU276qzsYKayow", "decal": ""}, {"item": "67117b3d-0669-4cf3-8a4f-066cabd4d8b1", "prefab": "67117b3d-0669-4cf3-8a4f-066cabd4d8b1", "mask": "iqGv6iRJ-UGbofv8-aHKVA", "swatch": "Dtis2HpVzkelZb6M9mW5TQ", "decal": ""}, {"item": "8d97303c-5138-4da1-a74d-378da19d11cb", "prefab": "8d97303c-5138-4da1-a74d-378da19d11cb", "mask": "", "swatch": "", "decal": ""}, {"item": "8d97303c-5138-4da1-a74d-378da19d11cb", "prefab": "8d97303c-5138-4da1-a74d-378da19d11cb", "mask": "ZpyoWbO6GkGglXlMuYUARw", "swatch": "Su7kiW24d0KapNns96JoFQ", "decal": ""}, {"item": "8d97303c-5138-4da1-a74d-378da19d11cb", "prefab": "8d97303c-5138-4da1-a74d-378da19d11cb", "mask": "ZpyoWbO6GkGglXlMuYUARw", "swatch": "CahBzgN3D0y46AQsvGC14A", "decal": ""}, {"item": "587ca2bb-dd42-42bf-833f-4e2a97349866", "prefab": "587ca2bb-dd42-42bf-833f-4e2a97349866", "mask": "", "swatch": "", "decal": ""}, {"item": "h_To9WQYdEusYQxlbMmMmQ", "prefab": "h_To9WQYdEusYQxlbMmMmQ", "mask": "", "swatch": "", "decal": ""}, {"item": "uubY_Cn9A0-Ww4lsIRCF6w", "prefab": "uubY_Cn9A0-Ww4lsIRCF6w", "mask": "", "swatch": "", "decal": ""}, {"item": "oYvOo47DJE-DEPlpn5xNUw", "prefab": "oYvOo47DJE-DEPlpn5xNUw", "mask": "", "swatch": "", "decal": ""}, {"item": "oYvOo47DJE-DEPlpn5xNUw", "prefab": "oYvOo47DJE-DEPlpn5xNUw", "mask": "y6aASPUwDkas0W5Yl27ydQ", "swatch": "whs9olNz9Uani7Gou31mvw", "decal": ""}, {"item": "60067e91-18b8-43ab-ae20-a8ea74c757bf", "prefab": "60067e91-18b8-43ab-ae20-a8ea74c757bf", "mask": "", "swatch": "", "decal": ""}, {"item": "tTKv7eRODka2KC_hsY2O3Q", "prefab": "tTKv7eRODka2KC_hsY2O3Q", "mask": "", "swatch": "", "decal": ""}, {"item": "tTKv7eRODka2KC_hsY2O3Q", "prefab": "tTKv7eRODka2KC_hsY2O3Q", "mask": "t-ZEWJCgFEyGP2PjNblQMA", "swatch": "eV3Nv6SWS0yl3f-fgjPb-w", "decal": ""}, {"item": "tTKv7eRODka2KC_hsY2O3Q", "prefab": "tTKv7eRODka2KC_hsY2O3Q", "mask": "t-ZEWJCgFEyGP2PjNblQMA", "swatch": "Lxx3q6JRnUW_cPG_kGG4ag", "decal": ""}, {"item": "WuShlrJnHUKuscn8e6Rt2g", "prefab": "WuShlrJnHUKuscn8e6Rt2g", "mask": "", "swatch": "", "decal": ""}, {"item": "WuShlrJnHUKuscn8e6Rt2g", "prefab": "WuShlrJnHUKuscn8e6Rt2g", "mask": "M5z2wk-GDkOAjvZzqsfcNQ", "swatch": "Ot9EG-FfKEauT0u6pSZuWg", "decal": ""}, {"item": "WuShlrJnHUKuscn8e6Rt2g", "prefab": "WuShlrJnHUKuscn8e6Rt2g", "mask": "M5z2wk-GDkOAjvZzqsfcNQ", "swatch": "i3rD_hnQG0a-5f6Ve4AfWg", "decal": ""}, {"item": "WuShlrJnHUKuscn8e6Rt2g", "prefab": "WuShlrJnHUKuscn8e6Rt2g", "mask": "M5z2wk-GDkOAjvZzqsfcNQ", "swatch": "S9uymuvraUCOM8gmB7bnEA", "decal": ""}, {"item": "WuShlrJnHUKuscn8e6Rt2g", "prefab": "WuShlrJnHUKuscn8e6Rt2g", "mask": "M5z2wk-GDkOAjvZzqsfcNQ", "swatch": "_uh3kj6hKUyBKOSR3R8dog", "decal": ""}, {"item": "5cd08cfb-c729-4c30-96d9-6a99bb934d91", "prefab": "5cd08cfb-c729-4c30-96d9-6a99bb934d91", "mask": "", "swatch": "", "decal": ""}, {"item": "38b38e24-d636-41c6-b80e-a285ae3106dc", "prefab": "38b38e24-d636-41c6-b80e-a285ae3106dc", "mask": "", "swatch": "", "decal": ""}, {"item": "38b38e24-d636-41c6-b80e-a285ae3106dc", "prefab": "38b38e24-d636-41c6-b80e-a285ae3106dc", "mask": "9b2102f0-e16b-464f-9f1c-f068de52bb89", "swatch": "0f71b9a2-dd88-435c-a571-4503d12dcf2d", "decal": ""}, {"item": "38b38e24-d636-41c6-b80e-a285ae3106dc", "prefab": "38b38e24-d636-41c6-b80e-a285ae3106dc", "mask": "9b2102f0-e16b-464f-9f1c-f068de52bb89", "swatch": "f4ad4b9e-dd42-4711-9437-5beae51966f6", "decal": ""}, {"item": "BqpRbr_T1kO_YJqB1787kA", "prefab": "BqpRbr_T1kO_YJqB1787kA", "mask": "", "swatch": "", "decal": ""}, {"item": "a44766dc-19ad-4476-849c-5113a5faabd0", "prefab": "a44766dc-19ad-4476-849c-5113a5faabd0", "mask": "", "swatch": "", "decal": ""}, {"item": "a44766dc-19ad-4476-849c-5113a5faabd0", "prefab": "a44766dc-19ad-4476-849c-5113a5faabd0", "mask": "HTU3Wp_ri0GNYGFSezFHSg", "swatch": "5_-QLURc_0W8qeeN3Q3iGQ", "decal": ""}, {"item": "a44766dc-19ad-4476-849c-5113a5faabd0", "prefab": "a44766dc-19ad-4476-849c-5113a5faabd0", "mask": "HTU3Wp_ri0GNYGFSezFHSg", "swatch": "mqdncbYcuUSGFvFqEU0z4A", "decal": ""}, {"item": "a44766dc-19ad-4476-849c-5113a5faabd0", "prefab": "a44766dc-19ad-4476-849c-5113a5faabd0", "mask": "HTU3Wp_ri0GNYGFSezFHSg", "swatch": "MbusQ9bte0i9zirlxtNkSQ", "decal": ""}, {"item": "a44766dc-19ad-4476-849c-5113a5faabd0", "prefab": "a44766dc-19ad-4476-849c-5113a5faabd0", "mask": "HTU3Wp_ri0GNYGFSezFHSg", "swatch": "_9QlnTPXuEO5OQ3H0CwFkA", "decal": ""}, {"item": "a44766dc-19ad-4476-849c-5113a5faabd0", "prefab": "a44766dc-19ad-4476-849c-5113a5faabd0", "mask": "HTU3Wp_ri0GNYGFSezFHSg", "swatch": "cKLBS6pbf0SFuavWyeZ7GA", "decal": ""}, {"item": "f47f81e7-fcdd-4687-9b99-013147bef30f", "prefab": "f47f81e7-fcdd-4687-9b99-013147bef30f", "mask": "", "swatch": "", "decal": ""}, {"item": "3af4c506-d49c-4126-9437-1d3bb1658863", "prefab": "3af4c506-d49c-4126-9437-1d3bb1658863", "mask": "", "swatch": "", "decal": ""}, {"item": "3af4c506-d49c-4126-9437-1d3bb1658863", "prefab": "3af4c506-d49c-4126-9437-1d3bb1658863", "mask": "005699fe-2c69-4bec-831f-031fe8ad7f0a", "swatch": "53dd9e44-7096-438e-a7c3-11d7c307426f", "decal": ""}, {"item": "3af4c506-d49c-4126-9437-1d3bb1658863", "prefab": "3af4c506-d49c-4126-9437-1d3bb1658863", "mask": "005699fe-2c69-4bec-831f-031fe8ad7f0a", "swatch": "c3c9f89c-580c-43c4-b38b-07394044e0e1", "decal": ""}, {"item": "bf9dc196-5727-4752-8126-7ad5fcd96d55", "prefab": "bf9dc196-5727-4752-8126-7ad5fcd96d55", "mask": "", "swatch": "", "decal": ""}, {"item": "bf9dc196-5727-4752-8126-7ad5fcd96d55", "prefab": "bf9dc196-5727-4752-8126-7ad5fcd96d55", "mask": "66cc536d-8cb5-4262-b7f0-49a33b97ea10", "swatch": "67490e3b-0f9d-4bf8-84fb-54957b981c8c", "decal": ""}, {"item": "bf9dc196-5727-4752-8126-7ad5fcd96d55", "prefab": "bf9dc196-5727-4752-8126-7ad5fcd96d55", "mask": "66cc536d-8cb5-4262-b7f0-49a33b97ea10", "swatch": "b520f3bc-599a-498c-8a4f-97cc25aa741f", "decal": ""}, {"item": "bf9dc196-5727-4752-8126-7ad5fcd96d55", "prefab": "bf9dc196-5727-4752-8126-7ad5fcd96d55", "mask": "66cc536d-8cb5-4262-b7f0-49a33b97ea10", "swatch": "639f5e2c-6fbd-4ab4-aa10-7772e0932798", "decal": ""}, {"item": "pNOfTDOgJUGQvGAlTjBoCg", "prefab": "pNOfTDOgJUGQvGAlTjBoCg", "mask": "", "swatch": "", "decal": ""}, {"item": "sOREgAGs-06CeGGsG-WIkw", "prefab": "sOREgAGs-06CeGGsG-WIkw", "mask": "", "swatch": "", "decal": ""}, {"item": "iBQ-v6xaqkipUlfzkSHEeQ", "prefab": "iBQ-v6xaqkipUlfzkSHEeQ", "mask": "", "swatch": "", "decal": ""}, {"item": "t_66r_j5oUSh9awIPmdUKg", "prefab": "t_66r_j5oUSh9awIPmdUKg", "mask": "", "swatch": "", "decal": ""}, {"item": "ImwG0snB9ECc_gqRRBoz6A", "prefab": "ImwG0snB9ECc_gqRRBoz6A", "mask": "", "swatch": "", "decal": ""}, {"item": "vP9EKTMXP0yni8U7uCz2-g", "prefab": "vP9EKTMXP0yni8U7uCz2-g", "mask": "", "swatch": "", "decal": ""}, {"item": "vP9EKTMXP0yni8U7uCz2-g", "prefab": "vP9EKTMXP0yni8U7uCz2-g", "mask": "Q7hqvEo7DUyD9AqIOunWWA", "swatch": "FsATqzRVekO9hQ3u_q7keg", "decal": ""}, {"item": "O8VqC11xNkSKpDh6Omp_0w", "prefab": "O8VqC11xNkSKpDh6Omp_0w", "mask": "", "swatch": "", "decal": ""}, {"item": "O8VqC11xNkSKpDh6Omp_0w", "prefab": "O8VqC11xNkSKpDh6Omp_0w", "mask": "YQQamy-tQkCkpZPXKepUrQ", "swatch": "mvj5RDTuh0qoql0OyBFI9g", "decal": ""}, {"item": "jiBzrzDPkkuL6DqIFKpcCw", "prefab": "jiBzrzDPkkuL6DqIFKpcCw", "mask": "", "swatch": "", "decal": ""}, {"item": "jiBzrzDPkkuL6DqIFKpcCw", "prefab": "jiBzrzDPkkuL6DqIFKpcCw", "mask": "StxMBEprVkqMa_IoDG-Q9A", "swatch": "lvDIOeSI10SNxnCO_M_9zA", "decal": ""}, {"item": "jiBzrzDPkkuL6DqIFKpcCw", "prefab": "jiBzrzDPkkuL6DqIFKpcCw", "mask": "StxMBEprVkqMa_IoDG-Q9A", "swatch": "0S7Nqm2kv0qMDTh-5Wy0MQ", "decal": ""}, {"item": "jiBzrzDPkkuL6DqIFKpcCw", "prefab": "jiBzrzDPkkuL6DqIFKpcCw", "mask": "StxMBEprVkqMa_IoDG-Q9A", "swatch": "poqpmOloPUKotHH9p0YAyw", "decal": ""}, {"item": "jiBzrzDPkkuL6DqIFKpcCw", "prefab": "jiBzrzDPkkuL6DqIFKpcCw", "mask": "StxMBEprVkqMa_IoDG-Q9A", "swatch": "F7luQ18ZXEaITFiCqqwkpg", "decal": ""}, {"item": "jiBzrzDPkkuL6DqIFKpcCw", "prefab": "jiBzrzDPkkuL6DqIFKpcCw", "mask": "StxMBEprVkqMa_IoDG-Q9A", "swatch": "okpg4ftsak-wc8Zu83uytw", "decal": ""}, {"item": "jiBzrzDPkkuL6DqIFKpcCw", "prefab": "jiBzrzDPkkuL6DqIFKpcCw", "mask": "StxMBEprVkqMa_IoDG-Q9A", "swatch": "OfenZaa1-UGdQ8YVOrXjzw", "decal": ""}, {"item": "jiBzrzDPkkuL6DqIFKpcCw", "prefab": "jiBzrzDPkkuL6DqIFKpcCw", "mask": "StxMBEprVkqMa_IoDG-Q9A", "swatch": "WOxMXWKGrUWhnNInttZ-Zw", "decal": ""}, {"item": "e15b13a7-9e9a-4b32-ba2c-0cb31ed55a8c", "prefab": "e15b13a7-9e9a-4b32-ba2c-0cb31ed55a8c", "mask": "", "swatch": "", "decal": ""}, {"item": "e15b13a7-9e9a-4b32-ba2c-0cb31ed55a8c", "prefab": "e15b13a7-9e9a-4b32-ba2c-0cb31ed55a8c", "mask": "dUu7b-JFOky9c2lIVCNmcg", "swatch": "2lD-mjl3pk2wWv24RY2tbA", "decal": ""}, {"item": "e15b13a7-9e9a-4b32-ba2c-0cb31ed55a8c", "prefab": "e15b13a7-9e9a-4b32-ba2c-0cb31ed55a8c", "mask": "6WVXb20giEWyZbW2gWGz_w", "swatch": "OrqqGMhK1UKGowFxpJi2mw", "decal": ""}, {"item": "e15b13a7-9e9a-4b32-ba2c-0cb31ed55a8c", "prefab": "e15b13a7-9e9a-4b32-ba2c-0cb31ed55a8c", "mask": "6WVXb20giEWyZbW2gWGz_w", "swatch": "-tDd0gdxQkuvL2id10AweQ", "decal": ""}, {"item": "e15b13a7-9e9a-4b32-ba2c-0cb31ed55a8c", "prefab": "e15b13a7-9e9a-4b32-ba2c-0cb31ed55a8c", "mask": "eUktNKKK3kqUR8-gA3rNxA", "swatch": "09Cg6dOq2kC2-tI5yyGSYg", "decal": ""}, {"item": "5d1c42a0-7e36-431a-8e78-a8b74046d153", "prefab": "5d1c42a0-7e36-431a-8e78-a8b74046d153", "mask": "", "swatch": "", "decal": ""}, {"item": "5d1c42a0-7e36-431a-8e78-a8b74046d153", "prefab": "5d1c42a0-7e36-431a-8e78-a8b74046d153", "mask": "cf119781-5bd9-4b85-9a0b-12e82e988c23", "swatch": "827a1538-51bb-4fef-99c1-7f1bebd9866c", "decal": ""}, {"item": "5d1c42a0-7e36-431a-8e78-a8b74046d153", "prefab": "5d1c42a0-7e36-431a-8e78-a8b74046d153", "mask": "cf119781-5bd9-4b85-9a0b-12e82e988c23", "swatch": "ad61c418-6d77-4a99-8ac5-9f10f5a3d42f", "decal": ""}, {"item": "5d1c42a0-7e36-431a-8e78-a8b74046d153", "prefab": "5d1c42a0-7e36-431a-8e78-a8b74046d153", "mask": "cf119781-5bd9-4b85-9a0b-12e82e988c23", "swatch": "6dd95046-acf8-42fe-ab78-80a334096a9d", "decal": ""}, {"item": "5d1c42a0-7e36-431a-8e78-a8b74046d153", "prefab": "5d1c42a0-7e36-431a-8e78-a8b74046d153", "mask": "b292eb4b-07e3-4a48-99b5-3c6587a1e02e", "swatch": "827a1538-51bb-4fef-99c1-7f1bebd9866c", "decal": ""}, {"item": "5d1c42a0-7e36-431a-8e78-a8b74046d153", "prefab": "5d1c42a0-7e36-431a-8e78-a8b74046d153", "mask": "b292eb4b-07e3-4a48-99b5-3c6587a1e02e", "swatch": "ad61c418-6d77-4a99-8ac5-9f10f5a3d42f", "decal": ""}, {"item": "5d1c42a0-7e36-431a-8e78-a8b74046d153", "prefab": "5d1c42a0-7e36-431a-8e78-a8b74046d153", "mask": "b292eb4b-07e3-4a48-99b5-3c6587a1e02e", "swatch": "6dd95046-acf8-42fe-ab78-80a334096a9d", "decal": ""}, {"item": "ec5255d5-edaf-4888-9fe1-e89ac0e0d300", "prefab": "ec5255d5-edaf-4888-9fe1-e89ac0e0d300", "mask": "", "swatch": "", "decal": ""}, {"item": "ec5255d5-edaf-4888-9fe1-e89ac0e0d300", "prefab": "ec5255d5-edaf-4888-9fe1-e89ac0e0d300", "mask": "YSrGwXybGEqaqHNCS64qOA", "swatch": "9GhnX4Pj4E-wxbPlD8GB-g", "decal": ""}, {"item": "2f8518fc-c989-40de-98b4-c6f09b304166", "prefab": "2f8518fc-c989-40de-98b4-c6f09b304166", "mask": "", "swatch": "", "decal": ""}, {"item": "2f8518fc-c989-40de-98b4-c6f09b304166", "prefab": "2f8518fc-c989-40de-98b4-c6f09b304166", "mask": "ca37823a-b8f3-4414-b38b-de0dc7fa4295", "swatch": "e5de2e45-207c-46af-9a5e-eb01a0c621fe", "decal": ""}, {"item": "2f8518fc-c989-40de-98b4-c6f09b304166", "prefab": "2f8518fc-c989-40de-98b4-c6f09b304166", "mask": "ca37823a-b8f3-4414-b38b-de0dc7fa4295", "swatch": "de0a5a3a-8aff-4799-8269-9efa3aeb383a", "decal": ""}, {"item": "2f8518fc-c989-40de-98b4-c6f09b304166", "prefab": "2f8518fc-c989-40de-98b4-c6f09b304166", "mask": "ca37823a-b8f3-4414-b38b-de0dc7fa4295", "swatch": "9967ce54-c2b7-437a-946e-11f8ee6d6fdd", "decal": ""}, {"item": "2f8518fc-c989-40de-98b4-c6f09b304166", "prefab": "2f8518fc-c989-40de-98b4-c6f09b304166", "mask": "ca37823a-b8f3-4414-b38b-de0dc7fa4295", "swatch": "a1c51d1e-1d5f-4f8a-bc0f-6a20eddfe58a", "decal": ""}, {"item": "348ac48b-986e-4d42-a0f7-1aea16b88271", "prefab": "348ac48b-986e-4d42-a0f7-1aea16b88271", "mask": "", "swatch": "", "decal": ""}, {"item": "348ac48b-986e-4d42-a0f7-1aea16b88271", "prefab": "348ac48b-986e-4d42-a0f7-1aea16b88271", "mask": "54a5c055-f757-4c00-a1e1-8b0b6552c608", "swatch": "59a62fa3-9e15-463e-9bd5-3aecdf604c1b", "decal": ""}, {"item": "348ac48b-986e-4d42-a0f7-1aea16b88271", "prefab": "348ac48b-986e-4d42-a0f7-1aea16b88271", "mask": "54a5c055-f757-4c00-a1e1-8b0b6552c608", "swatch": "4effd67c-a52f-4b41-a5b0-04287f91a7bc", "decal": ""}, {"item": "843e3364-a743-4dfc-a990-0436e47b8c10", "prefab": "843e3364-a743-4dfc-a990-0436e47b8c10", "mask": "", "swatch": "", "decal": ""}, {"item": "843e3364-a743-4dfc-a990-0436e47b8c10", "prefab": "843e3364-a743-4dfc-a990-0436e47b8c10", "mask": "1695608b-e9cc-4a03-b56d-0f09d2804379", "swatch": "c5884778-a87f-4612-9717-86fbb65d440f", "decal": ""}, {"item": "843e3364-a743-4dfc-a990-0436e47b8c10", "prefab": "843e3364-a743-4dfc-a990-0436e47b8c10", "mask": "1695608b-e9cc-4a03-b56d-0f09d2804379", "swatch": "83522c12-9549-4d74-8c87-bae210bcd2bf", "decal": ""}, {"item": "843e3364-a743-4dfc-a990-0436e47b8c10", "prefab": "843e3364-a743-4dfc-a990-0436e47b8c10", "mask": "1695608b-e9cc-4a03-b56d-0f09d2804379", "swatch": "XNq4Cns4lEGydtktdsXlmQ", "decal": ""}, {"item": "de0ac50d-2adb-4114-bd2e-68953b13d706", "prefab": "de0ac50d-2adb-4114-bd2e-68953b13d706", "mask": "", "swatch": "", "decal": ""}, {"item": "de0ac50d-2adb-4114-bd2e-68953b13d706", "prefab": "de0ac50d-2adb-4114-bd2e-68953b13d706", "mask": "be2b9293-1d3c-4b1c-b4c5-fad3ab16cf54", "swatch": "05ac07e1-67f0-486c-abf5-a62866475abb", "decal": ""}, {"item": "de0ac50d-2adb-4114-bd2e-68953b13d706", "prefab": "de0ac50d-2adb-4114-bd2e-68953b13d706", "mask": "be2b9293-1d3c-4b1c-b4c5-fad3ab16cf54", "swatch": "272fe8eb-5061-4729-a7a8-414ff667a82f", "decal": ""}, {"item": "de0ac50d-2adb-4114-bd2e-68953b13d706", "prefab": "de0ac50d-2adb-4114-bd2e-68953b13d706", "mask": "be2b9293-1d3c-4b1c-b4c5-fad3ab16cf54", "swatch": "9374bf66-2ee5-493b-8439-efce4b201904", "decal": ""}, {"item": "de0ac50d-2adb-4114-bd2e-68953b13d706", "prefab": "de0ac50d-2adb-4114-bd2e-68953b13d706", "mask": "be2b9293-1d3c-4b1c-b4c5-fad3ab16cf54", "swatch": "6f2e74bf-1e95-463d-97db-d5d1a53b2c28", "decal": ""}, {"item": "de0ac50d-2adb-4114-bd2e-68953b13d706", "prefab": "de0ac50d-2adb-4114-bd2e-68953b13d706", "mask": "be2b9293-1d3c-4b1c-b4c5-fad3ab16cf54", "swatch": "0ffad843-d6c9-425a-8686-7217009c867e", "decal": ""}, {"item": "de0ac50d-2adb-4114-bd2e-68953b13d706", "prefab": "de0ac50d-2adb-4114-bd2e-68953b13d706", "mask": "6UBZuZQBG0eZtNmty2IGdA", "swatch": "_ycDQ6RcyEGAntVlSDpPeA", "decal": ""}, {"item": "de0ac50d-2adb-4114-bd2e-68953b13d706", "prefab": "de0ac50d-2adb-4114-bd2e-68953b13d706", "mask": "9skD_xxLq0mpfP13f9n8hQ", "swatch": "7WrSbK4Ml0K2z-ig5HIUCw", "decal": ""}, {"item": "2e59d8d0-91a0-4449-bfdc-a5d663fd9343", "prefab": "2e59d8d0-91a0-4449-bfdc-a5d663fd9343", "mask": "", "swatch": "", "decal": ""}, {"item": "2e59d8d0-91a0-4449-bfdc-a5d663fd9343", "prefab": "2e59d8d0-91a0-4449-bfdc-a5d663fd9343", "mask": "10fYSIBytk-yUjveDa407w", "swatch": "ojvIFCTpJkeegestF065wA", "decal": ""}, {"item": "2e59d8d0-91a0-4449-bfdc-a5d663fd9343", "prefab": "2e59d8d0-91a0-4449-bfdc-a5d663fd9343", "mask": "d015cae7-a905-49e4-8823-6dec069689a6", "swatch": "bf82f2f6-9af8-431e-a296-0890dea48ba7", "decal": ""}, {"item": "2e59d8d0-91a0-4449-bfdc-a5d663fd9343", "prefab": "2e59d8d0-91a0-4449-bfdc-a5d663fd9343", "mask": "d015cae7-a905-49e4-8823-6dec069689a6", "swatch": "071d1bf3-86dc-4a31-b54a-e6579fab8649", "decal": ""}, {"item": "2e59d8d0-91a0-4449-bfdc-a5d663fd9343", "prefab": "2e59d8d0-91a0-4449-bfdc-a5d663fd9343", "mask": "PioQ0o3yP0a6szPZ4EKs2A", "swatch": "kmj5zOjcwku_WWKroCeiVQ", "decal": ""}, {"item": "2e59d8d0-91a0-4449-bfdc-a5d663fd9343", "prefab": "2e59d8d0-91a0-4449-bfdc-a5d663fd9343", "mask": "PioQ0o3yP0a6szPZ4EKs2A", "swatch": "jGj28vhq8EGwP2RuM074aQ", "decal": ""}, {"item": "2e59d8d0-91a0-4449-bfdc-a5d663fd9343", "prefab": "2e59d8d0-91a0-4449-bfdc-a5d663fd9343", "mask": "PioQ0o3yP0a6szPZ4EKs2A", "swatch": "MFrcSQ1DYUm8imvy4ypgvw", "decal": ""}, {"item": "2e59d8d0-91a0-4449-bfdc-a5d663fd9343", "prefab": "2e59d8d0-91a0-4449-bfdc-a5d663fd9343", "mask": "PioQ0o3yP0a6szPZ4EKs2A", "swatch": "0iSsaY-HgkmLaRHCn5vEdw", "decal": ""}, {"item": "2e59d8d0-91a0-4449-bfdc-a5d663fd9343", "prefab": "2e59d8d0-91a0-4449-bfdc-a5d663fd9343", "mask": "PioQ0o3yP0a6szPZ4EKs2A", "swatch": "FAviMCQ_EE2Mpt6QPo5OEw", "decal": ""}, {"item": "2e59d8d0-91a0-4449-bfdc-a5d663fd9343", "prefab": "2e59d8d0-91a0-4449-bfdc-a5d663fd9343", "mask": "DRJcNhkqvkKFEaZpOguR6w", "swatch": "EfdMcnfHt0mr0PQ_maaYOg", "decal": ""}, {"item": "2e59d8d0-91a0-4449-bfdc-a5d663fd9343", "prefab": "2e59d8d0-91a0-4449-bfdc-a5d663fd9343", "mask": "c2a91a10-2f5b-4ab0-a79f-51408cf4b047", "swatch": "48abd952-214f-48b2-a8f1-1146f6f69aa2", "decal": ""}, {"item": "2e59d8d0-91a0-4449-bfdc-a5d663fd9343", "prefab": "2e59d8d0-91a0-4449-bfdc-a5d663fd9343", "mask": "RNN0ejjlm0yqGDcEuUkLjw", "swatch": "48abd952-214f-48b2-a8f1-1146f6f69aa2", "decal": ""}, {"item": "2e59d8d0-91a0-4449-bfdc-a5d663fd9343", "prefab": "2e59d8d0-91a0-4449-bfdc-a5d663fd9343", "mask": "f600037d-c9c0-43fa-b45b-02f456f9dd5f", "swatch": "55901f12-d5b5-4fa8-b4c8-e479689ee39d", "decal": ""}, {"item": "ba84b23e-01ee-4668-a7ef-2e3e0eca2f23", "prefab": "ba84b23e-01ee-4668-a7ef-2e3e0eca2f23", "mask": "", "swatch": "", "decal": ""}, {"item": "ba84b23e-01ee-4668-a7ef-2e3e0eca2f23", "prefab": "ba84b23e-01ee-4668-a7ef-2e3e0eca2f23", "mask": "98553538-cafd-4df9-8f37-bb0a4bb478a5", "swatch": "192ba73d-6990-42da-8fe0-914c2a7181eb", "decal": ""}, {"item": "ba84b23e-01ee-4668-a7ef-2e3e0eca2f23", "prefab": "ba84b23e-01ee-4668-a7ef-2e3e0eca2f23", "mask": "98553538-cafd-4df9-8f37-bb0a4bb478a5", "swatch": "5e00a832-3f25-4dd3-84d2-b6ed6a4b4c37", "decal": ""}, {"item": "ba84b23e-01ee-4668-a7ef-2e3e0eca2f23", "prefab": "ba84b23e-01ee-4668-a7ef-2e3e0eca2f23", "mask": "98553538-cafd-4df9-8f37-bb0a4bb478a5", "swatch": "624c85e5-095a-48b6-85fb-c9bbade19a8d", "decal": ""}, {"item": "ba84b23e-01ee-4668-a7ef-2e3e0eca2f23", "prefab": "ba84b23e-01ee-4668-a7ef-2e3e0eca2f23", "mask": "98553538-cafd-4df9-8f37-bb0a4bb478a5", "swatch": "teVKVbVEsEyqOSuRdqHQzA", "decal": ""}, {"item": "ba84b23e-01ee-4668-a7ef-2e3e0eca2f23", "prefab": "ba84b23e-01ee-4668-a7ef-2e3e0eca2f23", "mask": "98553538-cafd-4df9-8f37-bb0a4bb478a5", "swatch": "K5vPqD3BrEuODRDzFdC6Hw", "decal": ""}, {"item": "4d507dfa-4a99-4ac0-8537-229e9dc0eb4a", "prefab": "4d507dfa-4a99-4ac0-8537-229e9dc0eb4a", "mask": "", "swatch": "", "decal": ""}, {"item": "8aa79563-ace1-4ba7-ad0c-f3210a78142f", "prefab": "8aa79563-ace1-4ba7-ad0c-f3210a78142f", "mask": "", "swatch": "", "decal": ""}, {"item": "8aa79563-ace1-4ba7-ad0c-f3210a78142f", "prefab": "8aa79563-ace1-4ba7-ad0c-f3210a78142f", "mask": "05f0ee6e-c824-470e-9178-5ed576c6fe0c", "swatch": "95e4cc30-cb68-473d-a395-feadf5b51512", "decal": ""}, {"item": "1b37d6cd-0a89-4604-a046-e5cff55b0cf6", "prefab": "1b37d6cd-0a89-4604-a046-e5cff55b0cf6", "mask": "", "swatch": "", "decal": ""}, {"item": "34d45669-b319-4c7d-bd05-078961d70d0b", "prefab": "34d45669-b319-4c7d-bd05-078961d70d0b", "mask": "", "swatch": "", "decal": ""}, {"item": "34d45669-b319-4c7d-bd05-078961d70d0b", "prefab": "34d45669-b319-4c7d-bd05-078961d70d0b", "mask": "e17481fc-b2b6-47a5-91e5-7205f45a3247", "swatch": "32dde6dc-e3dc-43ee-9b7c-987bc6778c17", "decal": ""}, {"item": "34d45669-b319-4c7d-bd05-078961d70d0b", "prefab": "34d45669-b319-4c7d-bd05-078961d70d0b", "mask": "e17481fc-b2b6-47a5-91e5-7205f45a3247", "swatch": "88c3ed05-ce41-4a9d-a4a7-8a428d240b3f", "decal": ""}, {"item": "d6b0a1e7-e918-43e5-8191-3a8d9d01df7c", "prefab": "d6b0a1e7-e918-43e5-8191-3a8d9d01df7c", "mask": "", "swatch": "", "decal": ""}, {"item": "d6b0a1e7-e918-43e5-8191-3a8d9d01df7c", "prefab": "d6b0a1e7-e918-43e5-8191-3a8d9d01df7c", "mask": "e5635772-4e84-4b30-b77f-4a1196cb599b", "swatch": "fcbdfbfd-254a-4538-b119-43e23404b8b5", "decal": ""}, {"item": "d6b0a1e7-e918-43e5-8191-3a8d9d01df7c", "prefab": "d6b0a1e7-e918-43e5-8191-3a8d9d01df7c", "mask": "e5635772-4e84-4b30-b77f-4a1196cb599b", "swatch": "b1ea158d-1663-4db1-bb09-9847f6e9d1e6", "decal": ""}, {"item": "d6b0a1e7-e918-43e5-8191-3a8d9d01df7c", "prefab": "d6b0a1e7-e918-43e5-8191-3a8d9d01df7c", "mask": "e5635772-4e84-4b30-b77f-4a1196cb599b", "swatch": "d5efaf80-7f34-4c9a-bc3a-b80b1ac70ace", "decal": ""}, {"item": "5f492f8f-ab61-484a-9587-747132b54309", "prefab": "5f492f8f-ab61-484a-9587-747132b54309", "mask": "", "swatch": "", "decal": ""}, {"item": "5f492f8f-ab61-484a-9587-747132b54309", "prefab": "5f492f8f-ab61-484a-9587-747132b54309", "mask": "f6f26e2b-657b-454a-8663-55d6b9b6e50c", "swatch": "3b2cfc85-4e2a-4e0d-b738-c296ae8692f8", "decal": ""}, {"item": "5f492f8f-ab61-484a-9587-747132b54309", "prefab": "5f492f8f-ab61-484a-9587-747132b54309", "mask": "f6f26e2b-657b-454a-8663-55d6b9b6e50c", "swatch": "78eb0969-f246-4cac-acd6-295df7c092e4", "decal": ""}, {"item": "7a730848-ab20-445d-b416-1a72cab49324", "prefab": "7a730848-ab20-445d-b416-1a72cab49324", "mask": "", "swatch": "", "decal": ""}, {"item": "9c34c2c1-07d7-4180-94a2-be8c9caaae84", "prefab": "9c34c2c1-07d7-4180-94a2-be8c9caaae84", "mask": "", "swatch": "", "decal": ""}, {"item": "9c34c2c1-07d7-4180-94a2-be8c9caaae84", "prefab": "9c34c2c1-07d7-4180-94a2-be8c9caaae84", "mask": "7ef7d0cf-99f1-4872-9d3d-8230cc9fa368", "swatch": "916384ca-e209-42a0-bd64-c64f5a40b56f", "decal": ""}, {"item": "9c34c2c1-07d7-4180-94a2-be8c9caaae84", "prefab": "9c34c2c1-07d7-4180-94a2-be8c9caaae84", "mask": "7ef7d0cf-99f1-4872-9d3d-8230cc9fa368", "swatch": "0217fd2e-ff9d-49f7-9bc8-8d0d6b8bf250", "decal": ""}, {"item": "9c34c2c1-07d7-4180-94a2-be8c9caaae84", "prefab": "9c34c2c1-07d7-4180-94a2-be8c9caaae84", "mask": "-_ViYCHHRUip7oN_AnH0Og", "swatch": "TLrw3oncCE2MeuVifXSWTg", "decal": ""}, {"item": "9c34c2c1-07d7-4180-94a2-be8c9caaae84", "prefab": "9c34c2c1-07d7-4180-94a2-be8c9caaae84", "mask": "-IgxYllxkEyr7V1gosWXUA", "swatch": "7Jx8mrxoi0aQtZEb03CdRA", "decal": ""}, {"item": "9073d6a0-3bc1-4c82-aeb3-d4ee195538b4", "prefab": "9073d6a0-3bc1-4c82-aeb3-d4ee195538b4", "mask": "", "swatch": "", "decal": ""}, {"item": "d0a9262f-5504-46a7-bb10-7507503db58e", "prefab": "d0a9262f-5504-46a7-bb10-7507503db58e", "mask": "", "swatch": "", "decal": ""}, {"item": "d0a9262f-5504-46a7-bb10-7507503db58e", "prefab": "d0a9262f-5504-46a7-bb10-7507503db58e", "mask": "2ee3944d-e9fb-4959-973e-570a7fc6cc57", "swatch": "5864d23d-1a86-40a6-a52c-46ee670e4508", "decal": "167ba0d4-4fac-4143-b5ae-1dbd8e02dc76"}, {"item": "d0a9262f-5504-46a7-bb10-7507503db58e", "prefab": "d0a9262f-5504-46a7-bb10-7507503db58e", "mask": "2ee3944d-e9fb-4959-973e-570a7fc6cc57", "swatch": "14afd295-bcd0-4595-80dc-d477618b9ab9", "decal": "a74e6c0d-1300-4928-9141-977a711b71c4"}, {"item": "d0a9262f-5504-46a7-bb10-7507503db58e", "prefab": "d0a9262f-5504-46a7-bb10-7507503db58e", "mask": "2ee3944d-e9fb-4959-973e-570a7fc6cc57", "swatch": "f8635fe2-90bc-42c8-9bbe-3387b5157e18", "decal": "49934059-cc45-46ce-8277-f96959e1f6ba"}, {"item": "d0a9262f-5504-46a7-bb10-7507503db58e", "prefab": "d0a9262f-5504-46a7-bb10-7507503db58e", "mask": "2ee3944d-e9fb-4959-973e-570a7fc6cc57", "swatch": "922f229c-124f-4f07-b4e3-19138420ca19", "decal": "ceb07e4f-50a9-4ed0-b50b-c59d92ba66b5"}, {"item": "d0a9262f-5504-46a7-bb10-7507503db58e", "prefab": "d0a9262f-5504-46a7-bb10-7507503db58e", "mask": "0440f08f-ef1d-49d8-942b-523056e8bb45", "swatch": "95e4cc30-cb68-473d-a395-feadf5b51512", "decal": "0c496f32-0011-4c4d-9778-59446f70c032"}, {"item": "d0a9262f-5504-46a7-bb10-7507503db58e", "prefab": "d0a9262f-5504-46a7-bb10-7507503db58e", "mask": "3259238f-4a9e-4e06-99de-6e7324fa0894", "swatch": "c0ff7719-c56a-4934-831b-506ad0f51cca", "decal": "e38294cf-6279-4863-9aea-1a19aeaf8476"}, {"item": "d0a9262f-5504-46a7-bb10-7507503db58e", "prefab": "d0a9262f-5504-46a7-bb10-7507503db58e", "mask": "0440f08f-ef1d-49d8-942b-523056e8bb45", "swatch": "381a9f13-5b44-48e6-8a49-221422a963f5", "decal": "9e782751-71c6-4208-87da-3a3cffa6a4f0"}, {"item": "d0a9262f-5504-46a7-bb10-7507503db58e", "prefab": "d0a9262f-5504-46a7-bb10-7507503db58e", "mask": "0440f08f-ef1d-49d8-942b-523056e8bb45", "swatch": "befa2f11-b9fd-457c-b1d5-9d82e90b70ce", "decal": "770e7674-6f9b-41f8-b023-ae79738db0d3"}, {"item": "d0a9262f-5504-46a7-bb10-7507503db58e", "prefab": "d0a9262f-5504-46a7-bb10-7507503db58e", "mask": "0440f08f-ef1d-49d8-942b-523056e8bb45", "swatch": "bde08851-a9e4-4faa-8f04-a0a661672472", "decal": "237e0aa2-5911-4704-8d2a-d446ddd2d842"}, {"item": "d0a9262f-5504-46a7-bb10-7507503db58e", "prefab": "d0a9262f-5504-46a7-bb10-7507503db58e", "mask": "0440f08f-ef1d-49d8-942b-523056e8bb45", "swatch": "5e7a48ed-8f08-4990-a3ae-05722c9f59a8", "decal": "237e0aa2-5911-4704-8d2a-d446ddd2d842"}, {"item": "d0a9262f-5504-46a7-bb10-7507503db58e", "prefab": "d0a9262f-5504-46a7-bb10-7507503db58e", "mask": "0440f08f-ef1d-49d8-942b-523056e8bb45", "swatch": "4d52bbd6-ef74-45ae-a6c2-bde10ab8eb16", "decal": "4ca3347a-dc87-412f-9eb1-799372e6fdd1"}, {"item": "d0a9262f-5504-46a7-bb10-7507503db58e", "prefab": "d0a9262f-5504-46a7-bb10-7507503db58e", "mask": "0440f08f-ef1d-49d8-942b-523056e8bb45", "swatch": "bda7a57e-5227-49f4-a119-e3f166604738", "decal": "4ca3347a-dc87-412f-9eb1-799372e6fdd1"}, {"item": "d0a9262f-5504-46a7-bb10-7507503db58e", "prefab": "d0a9262f-5504-46a7-bb10-7507503db58e", "mask": "0440f08f-ef1d-49d8-942b-523056e8bb45", "swatch": "941c046e-4e95-49f8-a7d7-19071fcc3c94", "decal": "6c395d04-2aa6-4a3e-8c2c-695932bc9736"}, {"item": "d0a9262f-5504-46a7-bb10-7507503db58e", "prefab": "d0a9262f-5504-46a7-bb10-7507503db58e", "mask": "0440f08f-ef1d-49d8-942b-523056e8bb45", "swatch": "941c046e-4e95-49f8-a7d7-19071fcc3c94", "decal": "703ff56b-560d-4ff4-8c63-d195e879a328"}, {"item": "d0a9262f-5504-46a7-bb10-7507503db58e", "prefab": "d0a9262f-5504-46a7-bb10-7507503db58e", "mask": "0440f08f-ef1d-49d8-942b-523056e8bb45", "swatch": "fe304452-b57b-45bc-9c1c-b85b70bcec12", "decal": "6a007c73-d930-408a-93df-041a715e0dd4"}, {"item": "d0a9262f-5504-46a7-bb10-7507503db58e", "prefab": "d0a9262f-5504-46a7-bb10-7507503db58e", "mask": "0440f08f-ef1d-49d8-942b-523056e8bb45", "swatch": "815138e6-b0dc-459c-9538-20f5bbd37d6b", "decal": "8f19d7f9-4f28-48ab-a1c5-d8f4f788514c"}, {"item": "d0a9262f-5504-46a7-bb10-7507503db58e", "prefab": "d0a9262f-5504-46a7-bb10-7507503db58e", "mask": "0440f08f-ef1d-49d8-942b-523056e8bb45", "swatch": "815138e6-b0dc-459c-9538-20f5bbd37d6b", "decal": "75162f12-44e7-499f-bc6c-9b0612e9164b"}, {"item": "d0a9262f-5504-46a7-bb10-7507503db58e", "prefab": "d0a9262f-5504-46a7-bb10-7507503db58e", "mask": "0440f08f-ef1d-49d8-942b-523056e8bb45", "swatch": "ba6b6e1a-a09a-4ba0-9523-552869f03336", "decal": "d461ca71-45c9-415e-8e09-ba93e8d73450"}, {"item": "d0a9262f-5504-46a7-bb10-7507503db58e", "prefab": "d0a9262f-5504-46a7-bb10-7507503db58e", "mask": "0440f08f-ef1d-49d8-942b-523056e8bb45", "swatch": "rnM1qDlYBU6_Dl1bjg0J1Q", "decal": "OZb8bb5FNUuFYOkwANvxAA"}, {"item": "d0a9262f-5504-46a7-bb10-7507503db58e", "prefab": "d0a9262f-5504-46a7-bb10-7507503db58e", "mask": "0440f08f-ef1d-49d8-942b-523056e8bb45", "swatch": "jna5wcWK90WokJyoeJH1iw", "decal": "0qh4mcxJH0Wsrb3nz8UeGw"}, {"item": "d0a9262f-5504-46a7-bb10-7507503db58e", "prefab": "d0a9262f-5504-46a7-bb10-7507503db58e", "mask": "0440f08f-ef1d-49d8-942b-523056e8bb45", "swatch": "Ib_iMwvUm0CJ-t_Thwt77Q", "decal": "d42UBeUO4UO3Q2F8IpeRbQ"}, {"item": "d0a9262f-5504-46a7-bb10-7507503db58e", "prefab": "d0a9262f-5504-46a7-bb10-7507503db58e", "mask": "0440f08f-ef1d-49d8-942b-523056e8bb45", "swatch": "yaQ7AlUc3kGq8_RW3eTSmQ", "decal": "d42UBeUO4UO3Q2F8IpeRbQ"}, {"item": "d0a9262f-5504-46a7-bb10-7507503db58e", "prefab": "d0a9262f-5504-46a7-bb10-7507503db58e", "mask": "0440f08f-ef1d-49d8-942b-523056e8bb45", "swatch": "dDLQbD9jtkOmw3PR5QJdfg", "decal": "2tFUNn-4kkK6ICo_Zp065g"}, {"item": "d0a9262f-5504-46a7-bb10-7507503db58e", "prefab": "d0a9262f-5504-46a7-bb10-7507503db58e", "mask": "0440f08f-ef1d-49d8-942b-523056e8bb45", "swatch": "g4diCmk0wEmIKUVozk9tDA", "decal": "nHg4Uifww0GEX15L27WonA"}, {"item": "d0a9262f-5504-46a7-bb10-7507503db58e", "prefab": "d0a9262f-5504-46a7-bb10-7507503db58e", "mask": "0440f08f-ef1d-49d8-942b-523056e8bb45", "swatch": "jna5wcWK90WokJyoeJH1iw", "decal": "MPfe2wahQECCDGfhZE-drA"}, {"item": "d0a9262f-5504-46a7-bb10-7507503db58e", "prefab": "d0a9262f-5504-46a7-bb10-7507503db58e", "mask": "0440f08f-ef1d-49d8-942b-523056e8bb45", "swatch": "nLRxEIBhUkyQsfrk3nzsDA", "decal": "n4hqSCv9OUewI30KnbPhFw"}, {"item": "d0a9262f-5504-46a7-bb10-7507503db58e", "prefab": "d0a9262f-5504-46a7-bb10-7507503db58e", "mask": "0440f08f-ef1d-49d8-942b-523056e8bb45", "swatch": "M1JVd5ur7U2ZtpvhEhPNhw", "decal": "tzMFIJyxZE6cZNuLdaKs7w"}, {"item": "d0a9262f-5504-46a7-bb10-7507503db58e", "prefab": "d0a9262f-5504-46a7-bb10-7507503db58e", "mask": "0440f08f-ef1d-49d8-942b-523056e8bb45", "swatch": "6kPcLZtEqU2Hs4yBFvc_sA", "decal": "NxMm0sSBdUatynU16o0_Zw"}, {"item": "d0a9262f-5504-46a7-bb10-7507503db58e", "prefab": "d0a9262f-5504-46a7-bb10-7507503db58e", "mask": "0440f08f-ef1d-49d8-942b-523056e8bb45", "swatch": "TtncjvYcK0qHqWN-IMMoqg", "decal": "hHhjZeLcaUGN1OyFoYHznw"}, {"item": "d0a9262f-5504-46a7-bb10-7507503db58e", "prefab": "d0a9262f-5504-46a7-bb10-7507503db58e", "mask": "0440f08f-ef1d-49d8-942b-523056e8bb45", "swatch": "fwIgUAM_C0WhKds0O6DyPg", "decal": "fJVxsLWuTESd-2xTCJPZ2w"}, {"item": "d0a9262f-5504-46a7-bb10-7507503db58e", "prefab": "d0a9262f-5504-46a7-bb10-7507503db58e", "mask": "0440f08f-ef1d-49d8-942b-523056e8bb45", "swatch": "ZNPzHmrU6kGVQW7Q5ei03w", "decal": "fJVxsLWuTESd-2xTCJPZ2w"}, {"item": "d0a9262f-5504-46a7-bb10-7507503db58e", "prefab": "d0a9262f-5504-46a7-bb10-7507503db58e", "mask": "0440f08f-ef1d-49d8-942b-523056e8bb45", "swatch": "95e4cc30-cb68-473d-a395-feadf5b51512", "decal": ""}, {"item": "d0a9262f-5504-46a7-bb10-7507503db58e", "prefab": "d0a9262f-5504-46a7-bb10-7507503db58e", "mask": "0440f08f-ef1d-49d8-942b-523056e8bb45", "swatch": "d3-sxPTJOk2rKo-Jgy3wvw", "decal": "v0GsPFC7cU-_JNG4-ERfog"}, {"item": "d0a9262f-5504-46a7-bb10-7507503db58e", "prefab": "d0a9262f-5504-46a7-bb10-7507503db58e", "mask": "0440f08f-ef1d-49d8-942b-523056e8bb45", "swatch": "v12eJ8louE-cW7-pCGzVLQ", "decal": "v0GsPFC7cU-_JNG4-ERfog"}, {"item": "d0a9262f-5504-46a7-bb10-7507503db58e", "prefab": "d0a9262f-5504-46a7-bb10-7507503db58e", "mask": "0440f08f-ef1d-49d8-942b-523056e8bb45", "swatch": "6kPcLZtEqU2Hs4yBFvc_sA", "decal": "br9hzhDoZUmIevgl7SJkmg"}, {"item": "d0a9262f-5504-46a7-bb10-7507503db58e", "prefab": "d0a9262f-5504-46a7-bb10-7507503db58e", "mask": "0440f08f-ef1d-49d8-942b-523056e8bb45", "swatch": "TuAjeDmPuUmFr_ESx4q6WA", "decal": "AXtRUZcYWU6g1ZtM-L4vfQ"}, {"item": "d0a9262f-5504-46a7-bb10-7507503db58e", "prefab": "d0a9262f-5504-46a7-bb10-7507503db58e", "mask": "0440f08f-ef1d-49d8-942b-523056e8bb45", "swatch": "LrQ1z-mTHE2vIePBwm8l_Q", "decal": "omS7gF10C0ec0ozEoJGJXw"}, {"item": "d8f408f0-78f5-4e8a-b42a-e7b09632d1fd", "prefab": "d8f408f0-78f5-4e8a-b42a-e7b09632d1fd", "mask": "", "swatch": "", "decal": ""}, {"item": "d8f408f0-78f5-4e8a-b42a-e7b09632d1fd", "prefab": "d8f408f0-78f5-4e8a-b42a-e7b09632d1fd", "mask": "0189ec4c-0389-4ebc-b9ba-32303a6341c2", "swatch": "178c6beb-c737-434c-a53b-d14f438f6a98", "decal": ""}, {"item": "d8f408f0-78f5-4e8a-b42a-e7b09632d1fd", "prefab": "d8f408f0-78f5-4e8a-b42a-e7b09632d1fd", "mask": "0189ec4c-0389-4ebc-b9ba-32303a6341c2", "swatch": "2913bd93-bf35-4bdf-b83a-d40a10213adc", "decal": ""}, {"item": "0bc6bd1b-8b7e-4945-a1d6-c26b9c043318", "prefab": "0bc6bd1b-8b7e-4945-a1d6-c26b9c043318", "mask": "", "swatch": "", "decal": ""}, {"item": "0bc6bd1b-8b7e-4945-a1d6-c26b9c043318", "prefab": "0bc6bd1b-8b7e-4945-a1d6-c26b9c043318", "mask": "8ec5de40-73fc-4f09-9bf3-ddf4701107bc", "swatch": "b9f51861-ad10-4878-a9b1-d849bccf532a", "decal": ""}, {"item": "0bc6bd1b-8b7e-4945-a1d6-c26b9c043318", "prefab": "0bc6bd1b-8b7e-4945-a1d6-c26b9c043318", "mask": "8ec5de40-73fc-4f09-9bf3-ddf4701107bc", "swatch": "b5d62931-e756-4a8a-8f3b-760beba49319", "decal": ""}, {"item": "0bc6bd1b-8b7e-4945-a1d6-c26b9c043318", "prefab": "0bc6bd1b-8b7e-4945-a1d6-c26b9c043318", "mask": "8ec5de40-73fc-4f09-9bf3-ddf4701107bc", "swatch": "6145292a-9002-41c6-8cd5-43679b6f542f", "decal": ""}, {"item": "0bc6bd1b-8b7e-4945-a1d6-c26b9c043318", "prefab": "0bc6bd1b-8b7e-4945-a1d6-c26b9c043318", "mask": "8ec5de40-73fc-4f09-9bf3-ddf4701107bc", "swatch": "7c1aaa7e-503b-46ef-b1c5-0c61a75b916e", "decal": ""}, {"item": "0bc6bd1b-8b7e-4945-a1d6-c26b9c043318", "prefab": "0bc6bd1b-8b7e-4945-a1d6-c26b9c043318", "mask": "8ec5de40-73fc-4f09-9bf3-ddf4701107bc", "swatch": "27af51bc-8226-4911-8d47-22d28ea8e378", "decal": ""}, {"item": "0bc6bd1b-8b7e-4945-a1d6-c26b9c043318", "prefab": "0bc6bd1b-8b7e-4945-a1d6-c26b9c043318", "mask": "8ec5de40-73fc-4f09-9bf3-ddf4701107bc", "swatch": "857fdcd8-704f-43d0-92b9-bca26619b153", "decal": ""}, {"item": "0bc6bd1b-8b7e-4945-a1d6-c26b9c043318", "prefab": "0bc6bd1b-8b7e-4945-a1d6-c26b9c043318", "mask": "8ec5de40-73fc-4f09-9bf3-ddf4701107bc", "swatch": "3df5d13b-ba77-4b51-90b2-7a946e4d0be7", "decal": ""}, {"item": "439d1e24-d67f-4b8a-8fb3-8bf589d5a8ee", "prefab": "439d1e24-d67f-4b8a-8fb3-8bf589d5a8ee", "mask": "", "swatch": "", "decal": ""}, {"item": "439d1e24-d67f-4b8a-8fb3-8bf589d5a8ee", "prefab": "439d1e24-d67f-4b8a-8fb3-8bf589d5a8ee", "mask": "Rk18_7icF0Srgh7M1_G-hA", "swatch": "BLgkgLeS-EyazgPQxGcavg", "decal": ""}, {"item": "439d1e24-d67f-4b8a-8fb3-8bf589d5a8ee", "prefab": "439d1e24-d67f-4b8a-8fb3-8bf589d5a8ee", "mask": "Rk18_7icF0Srgh7M1_G-hA", "swatch": "ZvPidQ57rUS50Qz89ERmLw", "decal": ""}, {"item": "439d1e24-d67f-4b8a-8fb3-8bf589d5a8ee", "prefab": "439d1e24-d67f-4b8a-8fb3-8bf589d5a8ee", "mask": "Rk18_7icF0Srgh7M1_G-hA", "swatch": "eb3SmJTIh0628LrZUkI4KQ", "decal": ""}, {"item": "439d1e24-d67f-4b8a-8fb3-8bf589d5a8ee", "prefab": "439d1e24-d67f-4b8a-8fb3-8bf589d5a8ee", "mask": "Rk18_7icF0Srgh7M1_G-hA", "swatch": "Yr0mF1EWzEaIyRMOWFiY6w", "decal": ""}, {"item": "439d1e24-d67f-4b8a-8fb3-8bf589d5a8ee", "prefab": "439d1e24-d67f-4b8a-8fb3-8bf589d5a8ee", "mask": "Rk18_7icF0Srgh7M1_G-hA", "swatch": "QzjakcO8tUaPeiI5KBLKtg", "decal": ""}, {"item": "FHKZvPFMLkO8iAIPPQzu1A", "prefab": "FHKZvPFMLkO8iAIPPQzu1A", "mask": "", "swatch": "", "decal": ""}, {"item": "FHKZvPFMLkO8iAIPPQzu1A", "prefab": "FHKZvPFMLkO8iAIPPQzu1A", "mask": "I_OF1c0YWESMwxJr9sXYOA", "swatch": "PEGqjoxP6E2yRx_A-HphrQ", "decal": ""}, {"item": "FHKZvPFMLkO8iAIPPQzu1A", "prefab": "FHKZvPFMLkO8iAIPPQzu1A", "mask": "3drRcfyKkku0qGwCy16m9Q", "swatch": "K17dIz_TRUq5D50NBfLjyw", "decal": ""}, {"item": "AIpF1QUeMUWytpHYBDBAkg", "prefab": "AIpF1QUeMUWytpHYBDBAkg", "mask": "", "swatch": "", "decal": ""}, {"item": "AIpF1QUeMUWytpHYBDBAkg", "prefab": "AIpF1QUeMUWytpHYBDBAkg", "mask": "oSaMpc5rTE-U6yhjtPWQgg", "swatch": "grutFwJD_0Cb3t6fddNg6w", "decal": ""}, {"item": "AIpF1QUeMUWytpHYBDBAkg", "prefab": "AIpF1QUeMUWytpHYBDBAkg", "mask": "oSaMpc5rTE-U6yhjtPWQgg", "swatch": "EbVDqEJQOk6j0UvX3t-8rQ", "decal": ""}, {"item": "AIpF1QUeMUWytpHYBDBAkg", "prefab": "AIpF1QUeMUWytpHYBDBAkg", "mask": "oSaMpc5rTE-U6yhjtPWQgg", "swatch": "C3ll7uPfUkeZ1QLX80_BpQ", "decal": ""}, {"item": "AIpF1QUeMUWytpHYBDBAkg", "prefab": "AIpF1QUeMUWytpHYBDBAkg", "mask": "oSaMpc5rTE-U6yhjtPWQgg", "swatch": "JS7aa4QSUUOpUYPqRZEb5g", "decal": ""}, {"item": "b1bfe0b4-1ff6-420f-acfc-2331d34246dc", "prefab": "b1bfe0b4-1ff6-420f-acfc-2331d34246dc", "mask": "", "swatch": "", "decal": ""}, {"item": "b1bfe0b4-1ff6-420f-acfc-2331d34246dc", "prefab": "b1bfe0b4-1ff6-420f-acfc-2331d34246dc", "mask": "cdbe2cc8-4fd7-451f-9b66-0df0e0e7a85e", "swatch": "cac27853-e2df-4d77-b3fd-af3bc0813f01", "decal": ""}, {"item": "b1bfe0b4-1ff6-420f-acfc-2331d34246dc", "prefab": "b1bfe0b4-1ff6-420f-acfc-2331d34246dc", "mask": "cdbe2cc8-4fd7-451f-9b66-0df0e0e7a85e", "swatch": "6e0f3af8-297f-4ea7-8f9e-4a1bc57d3e35", "decal": ""}, {"item": "b1bfe0b4-1ff6-420f-acfc-2331d34246dc", "prefab": "b1bfe0b4-1ff6-420f-acfc-2331d34246dc", "mask": "cdbe2cc8-4fd7-451f-9b66-0df0e0e7a85e", "swatch": "97938f4c-52b8-4c85-8575-856654666316", "decal": ""}, {"item": "c0e59f27-a13d-4bd9-b3b8-a38a360f649b", "prefab": "c0e59f27-a13d-4bd9-b3b8-a38a360f649b", "mask": "", "swatch": "", "decal": ""}, {"item": "c0e59f27-a13d-4bd9-b3b8-a38a360f649b", "prefab": "c0e59f27-a13d-4bd9-b3b8-a38a360f649b", "mask": "f88178b8-b204-4fa5-9dfd-6e438bf1e247", "swatch": "9b35ae52-febe-486a-8557-ec7190a9756e", "decal": ""}, {"item": "c0e59f27-a13d-4bd9-b3b8-a38a360f649b", "prefab": "c0e59f27-a13d-4bd9-b3b8-a38a360f649b", "mask": "f88178b8-b204-4fa5-9dfd-6e438bf1e247", "swatch": "6e152cc7-e93a-4408-8092-236db420b680", "decal": ""}, {"item": "c0e59f27-a13d-4bd9-b3b8-a38a360f649b", "prefab": "c0e59f27-a13d-4bd9-b3b8-a38a360f649b", "mask": "f88178b8-b204-4fa5-9dfd-6e438bf1e247", "swatch": "5b5281c9-b795-4bb2-ba7b-ba52047ebe3f", "decal": ""}, {"item": "c0e59f27-a13d-4bd9-b3b8-a38a360f649b", "prefab": "c0e59f27-a13d-4bd9-b3b8-a38a360f649b", "mask": "f88178b8-b204-4fa5-9dfd-6e438bf1e247", "swatch": "9ad37173-e640-4b9e-a474-716d482b73d2", "decal": ""}, {"item": "c0e59f27-a13d-4bd9-b3b8-a38a360f649b", "prefab": "c0e59f27-a13d-4bd9-b3b8-a38a360f649b", "mask": "f88178b8-b204-4fa5-9dfd-6e438bf1e247", "swatch": "lgqLGi01o02ZFC9Xs5Ofdg", "decal": ""}, {"item": "c0e59f27-a13d-4bd9-b3b8-a38a360f649b", "prefab": "c0e59f27-a13d-4bd9-b3b8-a38a360f649b", "mask": "f88178b8-b204-4fa5-9dfd-6e438bf1e247", "swatch": "61VZkFjB402brzWm4UsftA", "decal": ""}, {"item": "02077915-57d3-4e26-b4fe-8e0399cbb293", "prefab": "02077915-57d3-4e26-b4fe-8e0399cbb293", "mask": "", "swatch": "", "decal": ""}, {"item": "02077915-57d3-4e26-b4fe-8e0399cbb293", "prefab": "02077915-57d3-4e26-b4fe-8e0399cbb293", "mask": "5a6db6ba-2a0f-4399-b537-dd78db94bf90", "swatch": "8c94e45c-6605-4b4f-9ba4-2c6c6e557228", "decal": ""}, {"item": "02077915-57d3-4e26-b4fe-8e0399cbb293", "prefab": "02077915-57d3-4e26-b4fe-8e0399cbb293", "mask": "5a6db6ba-2a0f-4399-b537-dd78db94bf90", "swatch": "17b16982-871c-476a-860f-f6953c89ee8c", "decal": ""}, {"item": "42aa7888-f0b9-4584-9cc1-966585c6b36e", "prefab": "42aa7888-f0b9-4584-9cc1-966585c6b36e", "mask": "", "swatch": "", "decal": ""}, {"item": "42aa7888-f0b9-4584-9cc1-966585c6b36e", "prefab": "42aa7888-f0b9-4584-9cc1-966585c6b36e", "mask": "9009bcdf-b6f7-4dae-b2ec-895511d118ff", "swatch": "da185fbb-ad2b-4f30-87a2-491f24b78ebb", "decal": ""}, {"item": "42aa7888-f0b9-4584-9cc1-966585c6b36e", "prefab": "42aa7888-f0b9-4584-9cc1-966585c6b36e", "mask": "9009bcdf-b6f7-4dae-b2ec-895511d118ff", "swatch": "bea795cc-aabd-4229-84dd-49ab4f91a445", "decal": ""}, {"item": "42aa7888-f0b9-4584-9cc1-966585c6b36e", "prefab": "42aa7888-f0b9-4584-9cc1-966585c6b36e", "mask": "9009bcdf-b6f7-4dae-b2ec-895511d118ff", "swatch": "2APfqVnnQUaDYKi-fQg8jg", "decal": ""}, {"item": "18cd3470-2dd9-4a0a-98bd-eae2d5f5d522", "prefab": "18cd3470-2dd9-4a0a-98bd-eae2d5f5d522", "mask": "", "swatch": "", "decal": ""}, {"item": "18cd3470-2dd9-4a0a-98bd-eae2d5f5d522", "prefab": "18cd3470-2dd9-4a0a-98bd-eae2d5f5d522", "mask": "15edb8d7-2ff6-4170-a7e6-acd9e965cab5", "swatch": "a5925738-a451-4f25-b622-ccbfa12bd88c", "decal": ""}, {"item": "18cd3470-2dd9-4a0a-98bd-eae2d5f5d522", "prefab": "18cd3470-2dd9-4a0a-98bd-eae2d5f5d522", "mask": "15edb8d7-2ff6-4170-a7e6-acd9e965cab5", "swatch": "c64ac7a9-7fd4-4587-8626-0413a190b706", "decal": ""}, {"item": "1ebbfdfc-f72b-4d70-99d4-4d527f896aa7", "prefab": "1ebbfdfc-f72b-4d70-99d4-4d527f896aa7", "mask": "", "swatch": "", "decal": ""}, {"item": "1ebbfdfc-f72b-4d70-99d4-4d527f896aa7", "prefab": "1ebbfdfc-f72b-4d70-99d4-4d527f896aa7", "mask": "afd88a79-7f03-40f6-bcb4-c247dd86edc4", "swatch": "1919d319-6cce-4500-8766-15fed6a13fa8", "decal": ""}, {"item": "1ebbfdfc-f72b-4d70-99d4-4d527f896aa7", "prefab": "1ebbfdfc-f72b-4d70-99d4-4d527f896aa7", "mask": "afd88a79-7f03-40f6-bcb4-c247dd86edc4", "swatch": "lBe3yxuE40eBmTrnEHBqQQ", "decal": ""}, {"item": "1ebbfdfc-f72b-4d70-99d4-4d527f896aa7", "prefab": "1ebbfdfc-f72b-4d70-99d4-4d527f896aa7", "mask": "afd88a79-7f03-40f6-bcb4-c247dd86edc4", "swatch": "oZ8k0Qv3SkG-DkY0_dP7UA", "decal": ""}, {"item": "n52um7A-b0utGxixd1ESvA", "prefab": "n52um7A-b0utGxixd1ESvA", "mask": "", "swatch": "", "decal": ""}, {"item": "6930ce13-4be4-4ab9-9817-667bd261ffc3", "prefab": "6930ce13-4be4-4ab9-9817-667bd261ffc3", "mask": "", "swatch": "", "decal": ""}, {"item": "CQuKKNpan0O7rhR1fr14-Q", "prefab": "CQuKKNpan0O7rhR1fr14-Q", "mask": "", "swatch": "", "decal": ""}, {"item": "CQuKKNpan0O7rhR1fr14-Q", "prefab": "CQuKKNpan0O7rhR1fr14-Q", "mask": "b-IM3eYMJUa40395O5XeLg", "swatch": "-k9_-2onrEG407or8t1RFw", "decal": ""}, {"item": "CQuKKNpan0O7rhR1fr14-Q", "prefab": "CQuKKNpan0O7rhR1fr14-Q", "mask": "b-IM3eYMJUa40395O5XeLg", "swatch": "GDvaUU10-EKh3pKUQ9tnag", "decal": ""}, {"item": "CQuKKNpan0O7rhR1fr14-Q", "prefab": "CQuKKNpan0O7rhR1fr14-Q", "mask": "b-IM3eYMJUa40395O5XeLg", "swatch": "kLyM-6otSUy2ql5SEmnF9Q", "decal": ""}, {"item": "CQuKKNpan0O7rhR1fr14-Q", "prefab": "CQuKKNpan0O7rhR1fr14-Q", "mask": "5RUG6i2Mx0-55IO-IlhUdQ", "swatch": "8xjU4GQ4OkaqGyad-ypwtw", "decal": ""}, {"item": "CQuKKNpan0O7rhR1fr14-Q", "prefab": "CQuKKNpan0O7rhR1fr14-Q", "mask": "5RUG6i2Mx0-55IO-IlhUdQ", "swatch": "032pE8s9Ik-2CImFVDrSqg", "decal": ""}, {"item": "CQuKKNpan0O7rhR1fr14-Q", "prefab": "CQuKKNpan0O7rhR1fr14-Q", "mask": "5RUG6i2Mx0-55IO-IlhUdQ", "swatch": "M4765UHzsUeNTwH-Pcxp1g", "decal": ""}, {"item": "CQuKKNpan0O7rhR1fr14-Q", "prefab": "CQuKKNpan0O7rhR1fr14-Q", "mask": "5RUG6i2Mx0-55IO-IlhUdQ", "swatch": "FC8ZFBPt4EyTe7IYbv2Tpg", "decal": ""}, {"item": "CQuKKNpan0O7rhR1fr14-Q", "prefab": "CQuKKNpan0O7rhR1fr14-Q", "mask": "IWg9wuda4kaCilJk8JQpTQ", "swatch": "ghM-uFq5-0Ci38AOHa9zZA", "decal": ""}, {"item": "CQuKKNpan0O7rhR1fr14-Q", "prefab": "CQuKKNpan0O7rhR1fr14-Q", "mask": "IWg9wuda4kaCilJk8JQpTQ", "swatch": "Gtq8yRlt1UeFW8U-sc7FiA", "decal": ""}, {"item": "CQuKKNpan0O7rhR1fr14-Q", "prefab": "CQuKKNpan0O7rhR1fr14-Q", "mask": "IWg9wuda4kaCilJk8JQpTQ", "swatch": "XrSL-v2MbkK0md-FMOweOA", "decal": ""}, {"item": "CQuKKNpan0O7rhR1fr14-Q", "prefab": "CQuKKNpan0O7rhR1fr14-Q", "mask": "VO_3cgqQqUOTz30TVUxrFA", "swatch": "KqjQ1xRpokGLKp9GVN1EFA", "decal": ""}, {"item": "CQuKKNpan0O7rhR1fr14-Q", "prefab": "CQuKKNpan0O7rhR1fr14-Q", "mask": "VO_3cgqQqUOTz30TVUxrFA", "swatch": "yrQCLXiqRUSwnuQRYX2Rsw", "decal": ""}, {"item": "CQuKKNpan0O7rhR1fr14-Q", "prefab": "CQuKKNpan0O7rhR1fr14-Q", "mask": "VO_3cgqQqUOTz30TVUxrFA", "swatch": "yZR5aX3gQ0WpRUT6cfK1cA", "decal": ""}, {"item": "GAWURQSqrEW4G5slrPMgZA", "prefab": "GAWURQSqrEW4G5slrPMgZA", "mask": "", "swatch": "", "decal": ""}, {"item": "ff15e25d-d467-47a8-9ecc-e415d5acb90c", "prefab": "ff15e25d-d467-47a8-9ecc-e415d5acb90c", "mask": "", "swatch": "", "decal": ""}, {"item": "ff15e25d-d467-47a8-9ecc-e415d5acb90c", "prefab": "ff15e25d-d467-47a8-9ecc-e415d5acb90c", "mask": "0ec6628c-a106-4452-af82-105a3e3a9f2e", "swatch": "8c7b09f2-6213-4ef9-87ab-a2df72d07a22", "decal": ""}, {"item": "ff15e25d-d467-47a8-9ecc-e415d5acb90c", "prefab": "ff15e25d-d467-47a8-9ecc-e415d5acb90c", "mask": "0ec6628c-a106-4452-af82-105a3e3a9f2e", "swatch": "rzUd1IMTKUmW0HeMPSIOBQ", "decal": ""}, {"item": "7VQvz9sbzUWvGalxd9rISg", "prefab": "7VQvz9sbzUWvGalxd9rISg", "mask": "", "swatch": "", "decal": ""}, {"item": "7VQvz9sbzUWvGalxd9rISg", "prefab": "7VQvz9sbzUWvGalxd9rISg", "mask": "H8LXGRJj8UOuH5dil8L9qw", "swatch": "YTR8qybyVkOhNh4iqtU_UQ", "decal": ""}, {"item": "e60fccda-8711-463d-b8a3-7cd5e76903b2", "prefab": "e60fccda-8711-463d-b8a3-7cd5e76903b2", "mask": "", "swatch": "", "decal": ""}, {"item": "e60fccda-8711-463d-b8a3-7cd5e76903b2", "prefab": "e60fccda-8711-463d-b8a3-7cd5e76903b2", "mask": "cQUquXMk5EGt0VUCuPKBgg", "swatch": "Chiz8maLoEGDpZOJZEtdQg", "decal": ""}, {"item": "e60fccda-8711-463d-b8a3-7cd5e76903b2", "prefab": "e60fccda-8711-463d-b8a3-7cd5e76903b2", "mask": "cQUquXMk5EGt0VUCuPKBgg", "swatch": "2axtF2iOGkez1kfRRGUbYw", "decal": ""}, {"item": "e9913b06-58cb-4b90-bd08-981a3f19f352", "prefab": "e9913b06-58cb-4b90-bd08-981a3f19f352", "mask": "", "swatch": "", "decal": ""}, {"item": "QCtqxbvYOkan79YFugH_Cw", "prefab": "QCtqxbvYOkan79YFugH_Cw", "mask": "", "swatch": "", "decal": ""}, {"item": "QCtqxbvYOkan79YFugH_Cw", "prefab": "QCtqxbvYOkan79YFugH_Cw", "mask": "xnxDimLvVUCI9tEJqWDDcA", "swatch": "i5RKLo-RbEqisnGII1wpaQ", "decal": ""}, {"item": "QCtqxbvYOkan79YFugH_Cw", "prefab": "QCtqxbvYOkan79YFugH_Cw", "mask": "xnxDimLvVUCI9tEJqWDDcA", "swatch": "IzkFARlv6EenmrfcNkcOuw", "decal": ""}, {"item": "QCtqxbvYOkan79YFugH_Cw", "prefab": "QCtqxbvYOkan79YFugH_Cw", "mask": "xnxDimLvVUCI9tEJqWDDcA", "swatch": "LyVf9lnzdku1F4iPBGqk-g", "decal": ""}, {"item": "c3745cee-d87e-4a23-b6e2-64ebf05c8b11", "prefab": "c3745cee-d87e-4a23-b6e2-64ebf05c8b11", "mask": "", "swatch": "", "decal": ""}, {"item": "c3745cee-d87e-4a23-b6e2-64ebf05c8b11", "prefab": "c3745cee-d87e-4a23-b6e2-64ebf05c8b11", "mask": "EiIow5mS80qpoS10r1ZdvQ", "swatch": "Su7kiW24d0KapNns96JoFQ", "decal": ""}, {"item": "c3745cee-d87e-4a23-b6e2-64ebf05c8b11", "prefab": "c3745cee-d87e-4a23-b6e2-64ebf05c8b11", "mask": "EiIow5mS80qpoS10r1ZdvQ", "swatch": "CahBzgN3D0y46AQsvGC14A", "decal": ""}, {"item": "27e742bf-3081-4274-ba70-301f0edeb4f1", "prefab": "27e742bf-3081-4274-ba70-301f0edeb4f1", "mask": "", "swatch": "", "decal": ""}, {"item": "d3d69bee-60bc-44a3-ad7c-65b3b69d1e59", "prefab": "d3d69bee-60bc-44a3-ad7c-65b3b69d1e59", "mask": "", "swatch": "", "decal": ""}, {"item": "d3d69bee-60bc-44a3-ad7c-65b3b69d1e59", "prefab": "d3d69bee-60bc-44a3-ad7c-65b3b69d1e59", "mask": "944d934d-5e21-42d7-8196-fff9a81ee469", "swatch": "4a85c181-92fe-47a5-925e-05f0ccb25006", "decal": ""}, {"item": "d3d69bee-60bc-44a3-ad7c-65b3b69d1e59", "prefab": "d3d69bee-60bc-44a3-ad7c-65b3b69d1e59", "mask": "944d934d-5e21-42d7-8196-fff9a81ee469", "swatch": "0ba92db2-690a-44ce-92f4-130e9503b6c1", "decal": ""}, {"item": "a6f1825e-1647-4d06-85d7-ac2d139612c0", "prefab": "a6f1825e-1647-4d06-85d7-ac2d139612c0", "mask": "", "swatch": "", "decal": ""}, {"item": "a6f1825e-1647-4d06-85d7-ac2d139612c0", "prefab": "a6f1825e-1647-4d06-85d7-ac2d139612c0", "mask": "b0298c45-0dd1-453e-bad4-41e4656bb38e", "swatch": "fe5288bc-0e02-4ea3-b17d-dee0e576979d", "decal": ""}, {"item": "a6f1825e-1647-4d06-85d7-ac2d139612c0", "prefab": "a6f1825e-1647-4d06-85d7-ac2d139612c0", "mask": "b0298c45-0dd1-453e-bad4-41e4656bb38e", "swatch": "82de1ca3-67d6-454d-84fb-0eeece71fcbc", "decal": ""}, {"item": "a6f1825e-1647-4d06-85d7-ac2d139612c0", "prefab": "a6f1825e-1647-4d06-85d7-ac2d139612c0", "mask": "b0298c45-0dd1-453e-bad4-41e4656bb38e", "swatch": "54d3c589-30ba-44c1-a77c-8f9e16eb0b20", "decal": ""}, {"item": "3dc003c5-428b-47d6-80fb-6520b63119e8", "prefab": "3dc003c5-428b-47d6-80fb-6520b63119e8", "mask": "", "swatch": "", "decal": ""}, {"item": "3dc003c5-428b-47d6-80fb-6520b63119e8", "prefab": "3dc003c5-428b-47d6-80fb-6520b63119e8", "mask": "daa38a31-fb7b-4e26-a2f5-728d6dcec81a", "swatch": "b995b419-1897-4c1d-98be-83d551f7ba71", "decal": ""}, {"item": "3dc003c5-428b-47d6-80fb-6520b63119e8", "prefab": "3dc003c5-428b-47d6-80fb-6520b63119e8", "mask": "daa38a31-fb7b-4e26-a2f5-728d6dcec81a", "swatch": "67fc9269-8dd8-4d6f-b594-c77e716f2482", "decal": ""}, {"item": "3dc003c5-428b-47d6-80fb-6520b63119e8", "prefab": "3dc003c5-428b-47d6-80fb-6520b63119e8", "mask": "daa38a31-fb7b-4e26-a2f5-728d6dcec81a", "swatch": "b539777f-48b2-44a2-9d63-17ac703e4d57", "decal": ""}, {"item": "3dc003c5-428b-47d6-80fb-6520b63119e8", "prefab": "3dc003c5-428b-47d6-80fb-6520b63119e8", "mask": "daa38a31-fb7b-4e26-a2f5-728d6dcec81a", "swatch": "5fb2e235-8c67-4335-b432-edb10db2b139", "decal": ""}, {"item": "967187b8-df20-4f4f-b48f-10b9ea75cedf", "prefab": "967187b8-df20-4f4f-b48f-10b9ea75cedf", "mask": "", "swatch": "", "decal": ""}, {"item": "ff7e6d41-5c27-4516-930c-ad0e9a23cce2", "prefab": "ff7e6d41-5c27-4516-930c-ad0e9a23cce2", "mask": "", "swatch": "", "decal": ""}, {"item": "ff7e6d41-5c27-4516-930c-ad0e9a23cce2", "prefab": "ff7e6d41-5c27-4516-930c-ad0e9a23cce2", "mask": "435eba0d-d8f9-43cc-91aa-2959d63c6fa7", "swatch": "cea53c7f-24a9-4e3b-80b9-fbf95738f973", "decal": ""}, {"item": "ff7e6d41-5c27-4516-930c-ad0e9a23cce2", "prefab": "ff7e6d41-5c27-4516-930c-ad0e9a23cce2", "mask": "435eba0d-d8f9-43cc-91aa-2959d63c6fa7", "swatch": "e37b07d1-6b9d-49b9-94ca-1c728e228cfe", "decal": ""}, {"item": "ff7e6d41-5c27-4516-930c-ad0e9a23cce2", "prefab": "ff7e6d41-5c27-4516-930c-ad0e9a23cce2", "mask": "435eba0d-d8f9-43cc-91aa-2959d63c6fa7", "swatch": "8c74ee27-3d56-401b-8a8c-7868a80770e8", "decal": ""}, {"item": "9o0gWLJjREG552gxRreT8A", "prefab": "9o0gWLJjREG552gxRreT8A", "mask": "", "swatch": "", "decal": ""}, {"item": "5PUja-WX00Sme9yBwvDq-g", "prefab": "5PUja-WX00Sme9yBwvDq-g", "mask": "", "swatch": "", "decal": ""}, {"item": "5f600892-aa67-484a-afc7-14c219d89c52", "prefab": "5f600892-aa67-484a-afc7-14c219d89c52", "mask": "", "swatch": "", "decal": ""}, {"item": "5f600892-aa67-484a-afc7-14c219d89c52", "prefab": "5f600892-aa67-484a-afc7-14c219d89c52", "mask": "a240d44f-963d-4d35-b22f-980938c7788d", "swatch": "565c17e0-496d-45bd-a2c3-564e118c06cc", "decal": ""}, {"item": "5f600892-aa67-484a-afc7-14c219d89c52", "prefab": "5f600892-aa67-484a-afc7-14c219d89c52", "mask": "a240d44f-963d-4d35-b22f-980938c7788d", "swatch": "b6c25a3b-125b-4aab-bed0-e6e5ced2c21c", "decal": ""}, {"item": "ZsggwOJ8XUOGfaJltM418g", "prefab": "ZsggwOJ8XUOGfaJltM418g", "mask": "", "swatch": "", "decal": ""}, {"item": "e042433a-40a2-4758-8074-3b9d407835ec", "prefab": "e042433a-40a2-4758-8074-3b9d407835ec", "mask": "", "swatch": "", "decal": ""}, {"item": "f76b709c-b173-426e-81f2-279a67d2fde1", "prefab": "f76b709c-b173-426e-81f2-279a67d2fde1", "mask": "", "swatch": "", "decal": ""}, {"item": "f76b709c-b173-426e-81f2-279a67d2fde1", "prefab": "f76b709c-b173-426e-81f2-279a67d2fde1", "mask": "6ce21c65-f4e9-41ac-be94-8eb1a08d5c76", "swatch": "4c845dbb-26c3-48a1-b562-e28a7416b154", "decal": ""}, {"item": "f76b709c-b173-426e-81f2-279a67d2fde1", "prefab": "f76b709c-b173-426e-81f2-279a67d2fde1", "mask": "6ce21c65-f4e9-41ac-be94-8eb1a08d5c76", "swatch": "96e2b29e-e553-40f1-9eb6-8a9f7e64da39", "decal": ""}, {"item": "U_H976qYRUmg9e2clsAflg", "prefab": "U_H976qYRUmg9e2clsAflg", "mask": "", "swatch": "", "decal": ""}, {"item": "U_H976qYRUmg9e2clsAflg", "prefab": "U_H976qYRUmg9e2clsAflg", "mask": "ica0LZyM_kOox8gxW7CaMw", "swatch": "iZy7RmfAz0603oMO-jD-WA", "decal": ""}, {"item": "m5Plrduzr0yj_9mTn1x3Vw", "prefab": "m5Plrduzr0yj_9mTn1x3Vw", "mask": "", "swatch": "", "decal": ""}, {"item": "b6rLwzD4NkKV7xKn9ZYVkA", "prefab": "b6rLwzD4NkKV7xKn9ZYVkA", "mask": "", "swatch": "", "decal": ""}, {"item": "b6rLwzD4NkKV7xKn9ZYVkA", "prefab": "b6rLwzD4NkKV7xKn9ZYVkA", "mask": "oHKhT-GTx0iFWrrlz9s6Tw", "swatch": "mvMeyStxGUGlNLSYNeSubQ", "decal": ""}, {"item": "b6rLwzD4NkKV7xKn9ZYVkA", "prefab": "b6rLwzD4NkKV7xKn9ZYVkA", "mask": "oHKhT-GTx0iFWrrlz9s6Tw", "swatch": "u_Gzj9Nro0WS48xKnH9few", "decal": ""}, {"item": "b6rLwzD4NkKV7xKn9ZYVkA", "prefab": "b6rLwzD4NkKV7xKn9ZYVkA", "mask": "oHKhT-GTx0iFWrrlz9s6Tw", "swatch": "JTgXKeT7w0K2Mw4Jk2HCqg", "decal": ""}, {"item": "b6rLwzD4NkKV7xKn9ZYVkA", "prefab": "b6rLwzD4NkKV7xKn9ZYVkA", "mask": "oHKhT-GTx0iFWrrlz9s6Tw", "swatch": "rNhOQoP3XkOBHV28ze2NpA", "decal": ""}, {"item": "b6rLwzD4NkKV7xKn9ZYVkA", "prefab": "b6rLwzD4NkKV7xKn9ZYVkA", "mask": "oHKhT-GTx0iFWrrlz9s6Tw", "swatch": "TEgugZRs80iJbhL87RdYwg", "decal": ""}, {"item": "b6rLwzD4NkKV7xKn9ZYVkA", "prefab": "b6rLwzD4NkKV7xKn9ZYVkA", "mask": "oHKhT-GTx0iFWrrlz9s6Tw", "swatch": "ytmSzjhCr0Sm58MSNbwJUA", "decal": ""}, {"item": "b6rLwzD4NkKV7xKn9ZYVkA", "prefab": "b6rLwzD4NkKV7xKn9ZYVkA", "mask": "oHKhT-GTx0iFWrrlz9s6Tw", "swatch": "yM5xRrd9OkmDGyQddwOP6g", "decal": ""}, {"item": "b6rLwzD4NkKV7xKn9ZYVkA", "prefab": "b6rLwzD4NkKV7xKn9ZYVkA", "mask": "oHKhT-GTx0iFWrrlz9s6Tw", "swatch": "-Nf7WM2TeEKujB637h3tRA", "decal": ""}, {"item": "b6rLwzD4NkKV7xKn9ZYVkA", "prefab": "b6rLwzD4NkKV7xKn9ZYVkA", "mask": "oHKhT-GTx0iFWrrlz9s6Tw", "swatch": "LuYelZY8NkqmOp1pnxPXBg", "decal": ""}, {"item": "b6rLwzD4NkKV7xKn9ZYVkA", "prefab": "b6rLwzD4NkKV7xKn9ZYVkA", "mask": "tlpa7195x0CkmSjpR1RArQ", "swatch": "sxUE0iOSZEmezm54T7xI3Q", "decal": ""}, {"item": "b6rLwzD4NkKV7xKn9ZYVkA", "prefab": "b6rLwzD4NkKV7xKn9ZYVkA", "mask": "tnCJp2eDI0SwjVfJMhk3LQ", "swatch": "D_Xmo0rOzkS-kgq1CYXt3g", "decal": ""}, {"item": "3R_zf8xliEiurVU8GwaItg", "prefab": "3R_zf8xliEiurVU8GwaItg", "mask": "", "swatch": "", "decal": ""}, {"item": "7b857a8c-92ad-4028-a2c2-b3c20cdab5f2", "prefab": "7b857a8c-92ad-4028-a2c2-b3c20cdab5f2", "mask": "", "swatch": "", "decal": ""}, {"item": "7b857a8c-92ad-4028-a2c2-b3c20cdab5f2", "prefab": "7b857a8c-92ad-4028-a2c2-b3c20cdab5f2", "mask": "0b2395e1-ebcc-47e9-aaf1-faf9e9cec4cd", "swatch": "51ef8d39-2b94-4f9e-9620-07b6b0a913a5", "decal": ""}, {"item": "7b857a8c-92ad-4028-a2c2-b3c20cdab5f2", "prefab": "7b857a8c-92ad-4028-a2c2-b3c20cdab5f2", "mask": "0b2395e1-ebcc-47e9-aaf1-faf9e9cec4cd", "swatch": "8377ab96-c908-457f-9fee-b784c9a759f3", "decal": ""}, {"item": "7b857a8c-92ad-4028-a2c2-b3c20cdab5f2", "prefab": "7b857a8c-92ad-4028-a2c2-b3c20cdab5f2", "mask": "0b2395e1-ebcc-47e9-aaf1-faf9e9cec4cd", "swatch": "dee70c38-7a99-4c2b-9181-665f1bf75aca", "decal": ""}, {"item": "7b857a8c-92ad-4028-a2c2-b3c20cdab5f2", "prefab": "7b857a8c-92ad-4028-a2c2-b3c20cdab5f2", "mask": "0b2395e1-ebcc-47e9-aaf1-faf9e9cec4cd", "swatch": "1b1d08f2-12ca-43dd-a44f-ea2820b919b4", "decal": ""}, {"item": "7b857a8c-92ad-4028-a2c2-b3c20cdab5f2", "prefab": "7b857a8c-92ad-4028-a2c2-b3c20cdab5f2", "mask": "a0271cd0-e172-4d3f-aa2f-9806f21a82d2", "swatch": "6d703981-2734-4c45-8983-cdd5f328902f", "decal": ""}, {"item": "7b857a8c-92ad-4028-a2c2-b3c20cdab5f2", "prefab": "7b857a8c-92ad-4028-a2c2-b3c20cdab5f2", "mask": "b78008e8-abbd-4ece-be34-9a911f721fcc", "swatch": "48abd952-214f-48b2-a8f1-1146f6f69aa2", "decal": ""}, {"item": "7b857a8c-92ad-4028-a2c2-b3c20cdab5f2", "prefab": "7b857a8c-92ad-4028-a2c2-b3c20cdab5f2", "mask": "b292eb4b-07e3-4a48-99b5-3c6587a1e02e", "swatch": "ad61c418-6d77-4a99-8ac5-9f10f5a3d42f", "decal": ""}, {"item": "7b857a8c-92ad-4028-a2c2-b3c20cdab5f2", "prefab": "7b857a8c-92ad-4028-a2c2-b3c20cdab5f2", "mask": "9c03f381-7357-4d0f-8cda-8737d4c43d25", "swatch": "5c4a2b35-0e1c-44de-8c3a-96d4a6458b1b", "decal": ""}, {"item": "7b857a8c-92ad-4028-a2c2-b3c20cdab5f2", "prefab": "7b857a8c-92ad-4028-a2c2-b3c20cdab5f2", "mask": "d2a692e6-e1a9-4cfe-8154-10b52be7f8c8", "swatch": "51ef8d39-2b94-4f9e-9620-07b6b0a913a5", "decal": ""}, {"item": "7b857a8c-92ad-4028-a2c2-b3c20cdab5f2", "prefab": "7b857a8c-92ad-4028-a2c2-b3c20cdab5f2", "mask": "d2a692e6-e1a9-4cfe-8154-10b52be7f8c8", "swatch": "8377ab96-c908-457f-9fee-b784c9a759f3", "decal": ""}, {"item": "7b857a8c-92ad-4028-a2c2-b3c20cdab5f2", "prefab": "7b857a8c-92ad-4028-a2c2-b3c20cdab5f2", "mask": "d2a692e6-e1a9-4cfe-8154-10b52be7f8c8", "swatch": "dee70c38-7a99-4c2b-9181-665f1bf75aca", "decal": ""}, {"item": "7b857a8c-92ad-4028-a2c2-b3c20cdab5f2", "prefab": "7b857a8c-92ad-4028-a2c2-b3c20cdab5f2", "mask": "d2a692e6-e1a9-4cfe-8154-10b52be7f8c8", "swatch": "cbe29e9f-f2ac-47fb-97e1-8bad16abb89d", "decal": ""}, {"item": "7b857a8c-92ad-4028-a2c2-b3c20cdab5f2", "prefab": "7b857a8c-92ad-4028-a2c2-b3c20cdab5f2", "mask": "d2a692e6-e1a9-4cfe-8154-10b52be7f8c8", "swatch": "f8b0cfe8-e129-4578-8bb5-f60af5d38599", "decal": ""}, {"item": "7b857a8c-92ad-4028-a2c2-b3c20cdab5f2", "prefab": "7b857a8c-92ad-4028-a2c2-b3c20cdab5f2", "mask": "d2a692e6-e1a9-4cfe-8154-10b52be7f8c8", "swatch": "d2b40639-5d34-45b6-904e-6dd29030ff44", "decal": ""}, {"item": "7b857a8c-92ad-4028-a2c2-b3c20cdab5f2", "prefab": "7b857a8c-92ad-4028-a2c2-b3c20cdab5f2", "mask": "d2a692e6-e1a9-4cfe-8154-10b52be7f8c8", "swatch": "724c79a3-822c-422f-9462-a5374ee0211c", "decal": ""}, {"item": "7b857a8c-92ad-4028-a2c2-b3c20cdab5f2", "prefab": "7b857a8c-92ad-4028-a2c2-b3c20cdab5f2", "mask": "d2a692e6-e1a9-4cfe-8154-10b52be7f8c8", "swatch": "c72911d0-453f-4732-b1bd-dad520220a06", "decal": ""}, {"item": "7b857a8c-92ad-4028-a2c2-b3c20cdab5f2", "prefab": "7b857a8c-92ad-4028-a2c2-b3c20cdab5f2", "mask": "d2a692e6-e1a9-4cfe-8154-10b52be7f8c8", "swatch": "90bc2d31-1715-4d1a-813a-7c57e66cb017", "decal": ""}, {"item": "7b857a8c-92ad-4028-a2c2-b3c20cdab5f2", "prefab": "7b857a8c-92ad-4028-a2c2-b3c20cdab5f2", "mask": "d2a692e6-e1a9-4cfe-8154-10b52be7f8c8", "swatch": "1b1d08f2-12ca-43dd-a44f-ea2820b919b4", "decal": ""}, {"item": "739fd42a-8a8c-44a5-afa3-e0974802c6ae", "prefab": "739fd42a-8a8c-44a5-afa3-e0974802c6ae", "mask": "", "swatch": "", "decal": ""}, {"item": "739fd42a-8a8c-44a5-afa3-e0974802c6ae", "prefab": "739fd42a-8a8c-44a5-afa3-e0974802c6ae", "mask": "51a1809d-becd-42e0-8dfb-188d6f07ba1c", "swatch": "ed134bee-d199-43eb-98bd-206571603f40", "decal": "f5270130-6634-4052-aa3e-9849ddf5314e"}, {"item": "739fd42a-8a8c-44a5-afa3-e0974802c6ae", "prefab": "739fd42a-8a8c-44a5-afa3-e0974802c6ae", "mask": "51a1809d-becd-42e0-8dfb-188d6f07ba1c", "swatch": "59866578-d4e0-417b-9483-233ab108b1ac", "decal": "f5270130-6634-4052-aa3e-9849ddf5314e"}, {"item": "739fd42a-8a8c-44a5-afa3-e0974802c6ae", "prefab": "739fd42a-8a8c-44a5-afa3-e0974802c6ae", "mask": "51a1809d-becd-42e0-8dfb-188d6f07ba1c", "swatch": "22dc463a-a89b-46ad-9a0d-557c742b4a80", "decal": "f5270130-6634-4052-aa3e-9849ddf5314e"}, {"item": "739fd42a-8a8c-44a5-afa3-e0974802c6ae", "prefab": "739fd42a-8a8c-44a5-afa3-e0974802c6ae", "mask": "51a1809d-becd-42e0-8dfb-188d6f07ba1c", "swatch": "e1f49b74-b071-4bd1-9c4b-af36d24d45c5", "decal": "f5270130-6634-4052-aa3e-9849ddf5314e"}, {"item": "739fd42a-8a8c-44a5-afa3-e0974802c6ae", "prefab": "739fd42a-8a8c-44a5-afa3-e0974802c6ae", "mask": "51a1809d-becd-42e0-8dfb-188d6f07ba1c", "swatch": "ITH2OpzjP022vZ5JgxV_iQ", "decal": "f5270130-6634-4052-aa3e-9849ddf5314e"}, {"item": "739fd42a-8a8c-44a5-afa3-e0974802c6ae", "prefab": "739fd42a-8a8c-44a5-afa3-e0974802c6ae", "mask": "51a1809d-becd-42e0-8dfb-188d6f07ba1c", "swatch": "PMa9370LPEig_7pKwCceEQ", "decal": "f5270130-6634-4052-aa3e-9849ddf5314e"}, {"item": "39278302-b52a-48a8-a566-ed756a5fa1a9", "prefab": "39278302-b52a-48a8-a566-ed756a5fa1a9", "mask": "", "swatch": "", "decal": ""}, {"item": "39278302-b52a-48a8-a566-ed756a5fa1a9", "prefab": "39278302-b52a-48a8-a566-ed756a5fa1a9", "mask": "da36bbcc-2a9a-4ba2-9e4a-6f52c9b1ef74", "swatch": "e3454bc7-c88d-4958-8b81-db3fe5472a1e", "decal": ""}, {"item": "24d94673-f394-42e6-a3c8-276f8b8aa722", "prefab": "24d94673-f394-42e6-a3c8-276f8b8aa722", "mask": "", "swatch": "", "decal": ""}, {"item": "24d94673-f394-42e6-a3c8-276f8b8aa722", "prefab": "24d94673-f394-42e6-a3c8-276f8b8aa722", "mask": "959ed05b-0215-4bbe-bb3d-64e4448d676b", "swatch": "08b7c12d-3317-4d27-9b40-34737a709454", "decal": ""}, {"item": "24d94673-f394-42e6-a3c8-276f8b8aa722", "prefab": "24d94673-f394-42e6-a3c8-276f8b8aa722", "mask": "959ed05b-0215-4bbe-bb3d-64e4448d676b", "swatch": "jkuw9EhCpEKn-2lKJXWd3Q", "decal": ""}, {"item": "24d94673-f394-42e6-a3c8-276f8b8aa722", "prefab": "24d94673-f394-42e6-a3c8-276f8b8aa722", "mask": "959ed05b-0215-4bbe-bb3d-64e4448d676b", "swatch": "2Z_FxSPiyUODtlFZWF9DFQ", "decal": ""}, {"item": "C4uuztTiYUe20PACMARksA", "prefab": "C4uuztTiYUe20PACMARksA", "mask": "", "swatch": "", "decal": ""}, {"item": "C4uuztTiYUe20PACMARksA", "prefab": "C4uuztTiYUe20PACMARksA", "mask": "fz_0gZ6RXEC78NpYsx_eqA", "swatch": "apSQiQ1eO0mEzRsbugeu9w", "decal": ""}, {"item": "C4uuztTiYUe20PACMARksA", "prefab": "C4uuztTiYUe20PACMARksA", "mask": "fz_0gZ6RXEC78NpYsx_eqA", "swatch": "VpqHNpPiY0CfRJEjRRM-mw", "decal": ""}, {"item": "C4uuztTiYUe20PACMARksA", "prefab": "C4uuztTiYUe20PACMARksA", "mask": "fz_0gZ6RXEC78NpYsx_eqA", "swatch": "Z1F9rjKqCE-Hd2-Ip5g3gA", "decal": ""}, {"item": "C4uuztTiYUe20PACMARksA", "prefab": "C4uuztTiYUe20PACMARksA", "mask": "fz_0gZ6RXEC78NpYsx_eqA", "swatch": "iufpnteVCEiiZu_m-yPovQ", "decal": ""}, {"item": "350c5c1e-318b-4233-a7e5-1f49909d8e65", "prefab": "350c5c1e-318b-4233-a7e5-1f49909d8e65", "mask": "", "swatch": "", "decal": ""}, {"item": "350c5c1e-318b-4233-a7e5-1f49909d8e65", "prefab": "350c5c1e-318b-4233-a7e5-1f49909d8e65", "mask": "CUCKsNVnIkavHjcspWAv_A", "swatch": "7amHTgEv-UOJSGl0ofo4mw", "decal": ""}, {"item": "350c5c1e-318b-4233-a7e5-1f49909d8e65", "prefab": "350c5c1e-318b-4233-a7e5-1f49909d8e65", "mask": "CUCKsNVnIkavHjcspWAv_A", "swatch": "cKHAetNcgUOxFfDkAPPF9w", "decal": ""}, {"item": "MJkDYDH510izAab13ZfXQw", "prefab": "MJkDYDH510izAab13ZfXQw", "mask": "", "swatch": "", "decal": ""}, {"item": "MJkDYDH510izAab13ZfXQw", "prefab": "MJkDYDH510izAab13ZfXQw", "mask": "2UNd6eViFUO359xjSiuu2A", "swatch": "VQjkyb5aoEKHKo_eZsq9ug", "decal": ""}, {"item": "MJkDYDH510izAab13ZfXQw", "prefab": "MJkDYDH510izAab13ZfXQw", "mask": "2UNd6eViFUO359xjSiuu2A", "swatch": "kTP3xKhzBkuQF3SqvmYFKA", "decal": ""}, {"item": "MJkDYDH510izAab13ZfXQw", "prefab": "MJkDYDH510izAab13ZfXQw", "mask": "2UNd6eViFUO359xjSiuu2A", "swatch": "hYu9itGe1Ee5Neanhj9VtQ", "decal": ""}, {"item": "MJkDYDH510izAab13ZfXQw", "prefab": "MJkDYDH510izAab13ZfXQw", "mask": "2UNd6eViFUO359xjSiuu2A", "swatch": "FE7_gQNBt0CXzMN6di5HWA", "decal": ""}, {"item": "74c65554-0d15-409b-b9a8-0b2620a7d7e2", "prefab": "74c65554-0d15-409b-b9a8-0b2620a7d7e2", "mask": "", "swatch": "", "decal": ""}, {"item": "74c65554-0d15-409b-b9a8-0b2620a7d7e2", "prefab": "74c65554-0d15-409b-b9a8-0b2620a7d7e2", "mask": "nbhzQy_Hdkeohh4S1IezwA", "swatch": "kkvNbVRjcEKtChpy3W1bqQ", "decal": ""}, {"item": "4308da04-af84-40bb-845a-7a9e1a2726ec", "prefab": "4308da04-af84-40bb-845a-7a9e1a2726ec", "mask": "", "swatch": "", "decal": ""}, {"item": "4308da04-af84-40bb-845a-7a9e1a2726ec", "prefab": "4308da04-af84-40bb-845a-7a9e1a2726ec", "mask": "33ee376a-586e-4aff-a7e4-4213445a3e92", "swatch": "531b4297-b38b-4362-9854-4f2139ea43be", "decal": ""}, {"item": "4308da04-af84-40bb-845a-7a9e1a2726ec", "prefab": "4308da04-af84-40bb-845a-7a9e1a2726ec", "mask": "c2052d8c-fbcd-4462-8f08-bcf5b3dfde2f", "swatch": "46970903-9799-4d25-b90a-7162c1f5dbef", "decal": ""}, {"item": "4308da04-af84-40bb-845a-7a9e1a2726ec", "prefab": "4308da04-af84-40bb-845a-7a9e1a2726ec", "mask": "HTjlzAlWZ0ezY6w3ngSQSg", "swatch": "4VkPVbBVLE-eJHilwGMAbQ", "decal": ""}, {"item": "4308da04-af84-40bb-845a-7a9e1a2726ec", "prefab": "4308da04-af84-40bb-845a-7a9e1a2726ec", "mask": "6Jv4AJGBYUGP_n4sDcz2Kw", "swatch": "newGzX_zA0eGUX3Iayq5Ag", "decal": ""}, {"item": "oRUh266ohkWQil14uxcEdQ", "prefab": "oRUh266ohkWQil14uxcEdQ", "mask": "", "swatch": "", "decal": ""}, {"item": "ab15ef15-a97c-4ab1-896b-bd4d7e1cd9f5", "prefab": "ab15ef15-a97c-4ab1-896b-bd4d7e1cd9f5", "mask": "", "swatch": "", "decal": ""}, {"item": "ab15ef15-a97c-4ab1-896b-bd4d7e1cd9f5", "prefab": "ab15ef15-a97c-4ab1-896b-bd4d7e1cd9f5", "mask": "AZnPWCN41kmNwFwDqWGFWQ", "swatch": "fvoaMN4M10WS6jiA6XlOJQ", "decal": ""}, {"item": "ab15ef15-a97c-4ab1-896b-bd4d7e1cd9f5", "prefab": "ab15ef15-a97c-4ab1-896b-bd4d7e1cd9f5", "mask": "AZnPWCN41kmNwFwDqWGFWQ", "swatch": "PwsLxCnzOkSW-RGz3tNb1w", "decal": ""}, {"item": "ab15ef15-a97c-4ab1-896b-bd4d7e1cd9f5", "prefab": "ab15ef15-a97c-4ab1-896b-bd4d7e1cd9f5", "mask": "AZnPWCN41kmNwFwDqWGFWQ", "swatch": "uiBklotTUEqRI2F67vyJyA", "decal": ""}, {"item": "ab15ef15-a97c-4ab1-896b-bd4d7e1cd9f5", "prefab": "ab15ef15-a97c-4ab1-896b-bd4d7e1cd9f5", "mask": "AZnPWCN41kmNwFwDqWGFWQ", "swatch": "jXBi28cRV0CEQgKIrpD0_A", "decal": ""}, {"item": "3b00c71f-79c2-4a04-9fc0-a23b4c85562f", "prefab": "3b00c71f-79c2-4a04-9fc0-a23b4c85562f", "mask": "", "swatch": "", "decal": ""}, {"item": "3b00c71f-79c2-4a04-9fc0-a23b4c85562f", "prefab": "3b00c71f-79c2-4a04-9fc0-a23b4c85562f", "mask": "00ac4e37-6eac-4ed5-b46c-2c075038bc3f", "swatch": "556141f7-6fbc-4065-9faa-8d8240a93d7f", "decal": ""}, {"item": "3b00c71f-79c2-4a04-9fc0-a23b4c85562f", "prefab": "3b00c71f-79c2-4a04-9fc0-a23b4c85562f", "mask": "00ac4e37-6eac-4ed5-b46c-2c075038bc3f", "swatch": "d81d0b62-e052-46c2-9252-4bc5fb219f53", "decal": ""}, {"item": "3b00c71f-79c2-4a04-9fc0-a23b4c85562f", "prefab": "3b00c71f-79c2-4a04-9fc0-a23b4c85562f", "mask": "52053afd-9f12-4fec-a358-7eb3fe8c19bb", "swatch": "xGmnUGF-Ck-jP43r8tVVjg", "decal": ""}, {"item": "e4ba395e-f291-4e7b-8193-45f4422f41ce", "prefab": "e4ba395e-f291-4e7b-8193-45f4422f41ce", "mask": "", "swatch": "", "decal": ""}, {"item": "e4ba395e-f291-4e7b-8193-45f4422f41ce", "prefab": "e4ba395e-f291-4e7b-8193-45f4422f41ce", "mask": "d738ad4c-c9b5-41e6-8979-d586c964fb6c", "swatch": "4169bfed-8403-4590-a29d-4941275ac0b6", "decal": ""}, {"item": "e4ba395e-f291-4e7b-8193-45f4422f41ce", "prefab": "e4ba395e-f291-4e7b-8193-45f4422f41ce", "mask": "d738ad4c-c9b5-41e6-8979-d586c964fb6c", "swatch": "0724b166-9ace-46ff-a191-b13f135b00a8", "decal": ""}, {"item": "PryEX4MEvkGKwST6dqJHjA", "prefab": "PryEX4MEvkGKwST6dqJHjA", "mask": "", "swatch": "", "decal": ""}, {"item": "fb907eb2-91c8-45ff-84a5-462bb86ea7de", "prefab": "fb907eb2-91c8-45ff-84a5-462bb86ea7de", "mask": "", "swatch": "", "decal": ""}, {"item": "fb907eb2-91c8-45ff-84a5-462bb86ea7de", "prefab": "fb907eb2-91c8-45ff-84a5-462bb86ea7de", "mask": "d21e1b6f-9c9e-42b4-bad5-201ec43161e9", "swatch": "b415fe60-3e9d-4f4d-a53a-ca15eac6af7d", "decal": ""}, {"item": "0ab6c95c-e157-48bd-a172-2642896b4ffc", "prefab": "0ab6c95c-e157-48bd-a172-2642896b4ffc", "mask": "", "swatch": "", "decal": ""}, {"item": "0ab6c95c-e157-48bd-a172-2642896b4ffc", "prefab": "0ab6c95c-e157-48bd-a172-2642896b4ffc", "mask": "PoMG28QAskagun1LSd4C_A", "swatch": "0ps19D7b_U2iejdKQFORyA", "decal": ""}, {"item": "0ab6c95c-e157-48bd-a172-2642896b4ffc", "prefab": "0ab6c95c-e157-48bd-a172-2642896b4ffc", "mask": "PoMG28QAskagun1LSd4C_A", "swatch": "t1MeoroFOUioYmPfT9mCeA", "decal": ""}, {"item": "0ab6c95c-e157-48bd-a172-2642896b4ffc", "prefab": "0ab6c95c-e157-48bd-a172-2642896b4ffc", "mask": "PoMG28QAskagun1LSd4C_A", "swatch": "pjkTGVHkIE-BzCVi1KAesQ", "decal": ""}, {"item": "0ab6c95c-e157-48bd-a172-2642896b4ffc", "prefab": "0ab6c95c-e157-48bd-a172-2642896b4ffc", "mask": "m5Vw_MMCWUiSOOCL94QO0w", "swatch": "QP38EMygY0S-KIvdGatARA", "decal": ""}, {"item": "e13f6558-d49c-4314-820c-962ef48eb0c6", "prefab": "e13f6558-d49c-4314-820c-962ef48eb0c6", "mask": "", "swatch": "", "decal": ""}, {"item": "e13f6558-d49c-4314-820c-962ef48eb0c6", "prefab": "e13f6558-d49c-4314-820c-962ef48eb0c6", "mask": "50NUFrDxbUKmXbyAHBenvQ", "swatch": "5TJPDmvEBEOkP82ZWLFqtg", "decal": ""}, {"item": "e13f6558-d49c-4314-820c-962ef48eb0c6", "prefab": "e13f6558-d49c-4314-820c-962ef48eb0c6", "mask": "50NUFrDxbUKmXbyAHBenvQ", "swatch": "rCeRk8Pdl0W5M47JAQPhpw", "decal": ""}, {"item": "e13f6558-d49c-4314-820c-962ef48eb0c6", "prefab": "e13f6558-d49c-4314-820c-962ef48eb0c6", "mask": "50NUFrDxbUKmXbyAHBenvQ", "swatch": "8zXWUxwZHU6tThCV4p5iGg", "decal": ""}, {"item": "e13f6558-d49c-4314-820c-962ef48eb0c6", "prefab": "e13f6558-d49c-4314-820c-962ef48eb0c6", "mask": "50NUFrDxbUKmXbyAHBenvQ", "swatch": "s2kCppLsN0uY7Hy6qjQc-g", "decal": ""}, {"item": "1041d745-c5e9-4218-a3e1-198f5c9c418c", "prefab": "1041d745-c5e9-4218-a3e1-198f5c9c418c", "mask": "", "swatch": "", "decal": ""}, {"item": "1041d745-c5e9-4218-a3e1-198f5c9c418c", "prefab": "1041d745-c5e9-4218-a3e1-198f5c9c418c", "mask": "84c016ad-133f-45e3-a412-cac35612540b", "swatch": "49bbd11a-e5c9-47ff-b04d-72eb321e8a57", "decal": ""}, {"item": "2c679f89-c76e-4cfb-94e9-448c8fd44d55", "prefab": "2c679f89-c76e-4cfb-94e9-448c8fd44d55", "mask": "", "swatch": "", "decal": ""}, {"item": "664485e9-a159-4fcb-b32c-2b114b4a1218", "prefab": "664485e9-a159-4fcb-b32c-2b114b4a1218", "mask": "", "swatch": "", "decal": ""}, {"item": "664485e9-a159-4fcb-b32c-2b114b4a1218", "prefab": "664485e9-a159-4fcb-b32c-2b114b4a1218", "mask": "9f1dd474-34a3-47eb-bb72-dbfc8d2d6ecf", "swatch": "85eb7f47-bb31-4c66-a8b3-562d35028e6c", "decal": ""}, {"item": "664485e9-a159-4fcb-b32c-2b114b4a1218", "prefab": "664485e9-a159-4fcb-b32c-2b114b4a1218", "mask": "005699fe-2c69-4bec-831f-031fe8ad7f0a", "swatch": "39f04eee-3a58-4e53-9f5e-7e995d46ed71", "decal": ""}, {"item": "664485e9-a159-4fcb-b32c-2b114b4a1218", "prefab": "664485e9-a159-4fcb-b32c-2b114b4a1218", "mask": "9f1dd474-34a3-47eb-bb72-dbfc8d2d6ecf", "swatch": "6y_fzLhG7E65Px3-CYiyXQ", "decal": ""}, {"item": "fe34adaf-47d3-435b-b2fd-b2fee05fceae", "prefab": "fe34adaf-47d3-435b-b2fd-b2fee05fceae", "mask": "", "swatch": "", "decal": ""}, {"item": "fe34adaf-47d3-435b-b2fd-b2fee05fceae", "prefab": "fe34adaf-47d3-435b-b2fd-b2fee05fceae", "mask": "w07bkcUoQEiNJTVDeSywzQ", "swatch": "KTidYfH0ckWxJGs5qzUBTQ", "decal": ""}, {"item": "a96e168c-0c4d-4c70-9f8a-c8f07164ce99", "prefab": "a96e168c-0c4d-4c70-9f8a-c8f07164ce99", "mask": "", "swatch": "", "decal": ""}, {"item": "a96e168c-0c4d-4c70-9f8a-c8f07164ce99", "prefab": "a96e168c-0c4d-4c70-9f8a-c8f07164ce99", "mask": "5814a331-6155-4df4-b921-3ffc6059e44c", "swatch": "0d2830e7-8eb0-4fa3-99bc-7c80f01dd991", "decal": ""}, {"item": "35f60d27-22cf-4e17-8f39-c9e3a9fa2291", "prefab": "35f60d27-22cf-4e17-8f39-c9e3a9fa2291", "mask": "", "swatch": "", "decal": ""}, {"item": "35f60d27-22cf-4e17-8f39-c9e3a9fa2291", "prefab": "35f60d27-22cf-4e17-8f39-c9e3a9fa2291", "mask": "e65530f7-543c-43e9-bf12-b6cda2bc755c", "swatch": "2bb65868-ee12-4871-b50d-64bbc9066f0b", "decal": ""}, {"item": "033e328f-d9d2-4e53-af20-44f18e3ea208", "prefab": "033e328f-d9d2-4e53-af20-44f18e3ea208", "mask": "", "swatch": "", "decal": ""}, {"item": "033e328f-d9d2-4e53-af20-44f18e3ea208", "prefab": "033e328f-d9d2-4e53-af20-44f18e3ea208", "mask": "9ZP617DQk0mZ10J3d5q9oA", "swatch": "Z1Ib9r_fkUqgoZk0uid_Xw", "decal": ""}, {"item": "033e328f-d9d2-4e53-af20-44f18e3ea208", "prefab": "033e328f-d9d2-4e53-af20-44f18e3ea208", "mask": "9ZP617DQk0mZ10J3d5q9oA", "swatch": "msOFHoFN9UORkeXXtdqBsw", "decal": ""}, {"item": "6RhwWExXekaYpK0NGHSXGQ", "prefab": "6RhwWExXekaYpK0NGHSXGQ", "mask": "", "swatch": "", "decal": ""}, {"item": "6RhwWExXekaYpK0NGHSXGQ", "prefab": "6RhwWExXekaYpK0NGHSXGQ", "mask": "HQQWccdyakaEmntPhvC6CQ", "swatch": "fXNkMWFMV0aXalK_y_8SHw", "decal": ""}, {"item": "25d4c652-1730-4925-925e-31a290a2272a", "prefab": "25d4c652-1730-4925-925e-31a290a2272a", "mask": "", "swatch": "", "decal": ""}, {"item": "25d4c652-1730-4925-925e-31a290a2272a", "prefab": "25d4c652-1730-4925-925e-31a290a2272a", "mask": "970a8c7e-d091-4719-bb9c-e3be8d46ee68", "swatch": "e821aa74-2d47-4388-bcfb-1893b8a83a52", "decal": ""}, {"item": "25d4c652-1730-4925-925e-31a290a2272a", "prefab": "25d4c652-1730-4925-925e-31a290a2272a", "mask": "970a8c7e-d091-4719-bb9c-e3be8d46ee68", "swatch": "fed760b2-15e2-46b2-b1e3-cc3c8d780d5d", "decal": ""}, {"item": "25d4c652-1730-4925-925e-31a290a2272a", "prefab": "25d4c652-1730-4925-925e-31a290a2272a", "mask": "970a8c7e-d091-4719-bb9c-e3be8d46ee68", "swatch": "90869e86-cbc5-4024-9d08-fcdc41c64eb5", "decal": ""}, {"item": "25d4c652-1730-4925-925e-31a290a2272a", "prefab": "25d4c652-1730-4925-925e-31a290a2272a", "mask": "970a8c7e-d091-4719-bb9c-e3be8d46ee68", "swatch": "076c96ae-9f9b-45bf-9dc7-ce73d5ddf422", "decal": ""}, {"item": "25d4c652-1730-4925-925e-31a290a2272a", "prefab": "25d4c652-1730-4925-925e-31a290a2272a", "mask": "970a8c7e-d091-4719-bb9c-e3be8d46ee68", "swatch": "002b07bb-4256-44ce-8ca4-25510e70b6b5", "decal": ""}, {"item": "25d4c652-1730-4925-925e-31a290a2272a", "prefab": "25d4c652-1730-4925-925e-31a290a2272a", "mask": "970a8c7e-d091-4719-bb9c-e3be8d46ee68", "swatch": "bzYdPb8GWUS-CgT4wrziIA", "decal": ""}, {"item": "rJ9zPPGdlk2i24sxLfkqng", "prefab": "rJ9zPPGdlk2i24sxLfkqng", "mask": "", "swatch": "", "decal": ""}, {"item": "b6610b7b-3978-46fd-9af6-41b4568c9b4d", "prefab": "b6610b7b-3978-46fd-9af6-41b4568c9b4d", "mask": "", "swatch": "", "decal": ""}, {"item": "b6610b7b-3978-46fd-9af6-41b4568c9b4d", "prefab": "b6610b7b-3978-46fd-9af6-41b4568c9b4d", "mask": "i8bvbGAKREmAEbjKO-geOQ", "swatch": "OnQeVxdstkS2zGxLBAuM3Q", "decal": ""}, {"item": "iai6P7dDTUq8S4hqQmY1YA", "prefab": "iai6P7dDTUq8S4hqQmY1YA", "mask": "", "swatch": "", "decal": ""}, {"item": "iai6P7dDTUq8S4hqQmY1YA", "prefab": "iai6P7dDTUq8S4hqQmY1YA", "mask": "MBuxBAIkpkWI26d-arAKQg", "swatch": "XFCpIdeDXUqeYCIgh4fGtg", "decal": ""}, {"item": "iai6P7dDTUq8S4hqQmY1YA", "prefab": "iai6P7dDTUq8S4hqQmY1YA", "mask": "MBuxBAIkpkWI26d-arAKQg", "swatch": "oKxmH-1s2EyIDkZoOQSQQw", "decal": ""}, {"item": "iai6P7dDTUq8S4hqQmY1YA", "prefab": "iai6P7dDTUq8S4hqQmY1YA", "mask": "MBuxBAIkpkWI26d-arAKQg", "swatch": "MxnAq_XOw0uTzQ8IIxqb_Q", "decal": ""}, {"item": "4ce7a70f-15a0-4cad-98ee-5cbb40827338", "prefab": "4ce7a70f-15a0-4cad-98ee-5cbb40827338", "mask": "", "swatch": "", "decal": ""}, {"item": "4ce7a70f-15a0-4cad-98ee-5cbb40827338", "prefab": "4ce7a70f-15a0-4cad-98ee-5cbb40827338", "mask": "7ac81d60-05b8-4a85-982c-f69213a93818", "swatch": "c8cc9589-4831-4e73-a865-ef8c139a2ba7", "decal": ""}, {"item": "4ce7a70f-15a0-4cad-98ee-5cbb40827338", "prefab": "4ce7a70f-15a0-4cad-98ee-5cbb40827338", "mask": "7ac81d60-05b8-4a85-982c-f69213a93818", "swatch": "a089bcc6-798d-4777-98e2-7afd290dc6b6", "decal": ""}, {"item": "4ce7a70f-15a0-4cad-98ee-5cbb40827338", "prefab": "4ce7a70f-15a0-4cad-98ee-5cbb40827338", "mask": "7ac81d60-05b8-4a85-982c-f69213a93818", "swatch": "a36d252b-16ac-4a82-bece-de145147e7d9", "decal": ""}, {"item": "4ce7a70f-15a0-4cad-98ee-5cbb40827338", "prefab": "4ce7a70f-15a0-4cad-98ee-5cbb40827338", "mask": "7ac81d60-05b8-4a85-982c-f69213a93818", "swatch": "d78643e4-c0d2-4836-bf22-b5a0e2d65b28", "decal": ""}, {"item": "4ce7a70f-15a0-4cad-98ee-5cbb40827338", "prefab": "4ce7a70f-15a0-4cad-98ee-5cbb40827338", "mask": "7ac81d60-05b8-4a85-982c-f69213a93818", "swatch": "68273c9e-96d6-4ddb-aaab-d99229e864d5", "decal": ""}, {"item": "4ce7a70f-15a0-4cad-98ee-5cbb40827338", "prefab": "4ce7a70f-15a0-4cad-98ee-5cbb40827338", "mask": "7ac81d60-05b8-4a85-982c-f69213a93818", "swatch": "4ElTihrMA0ipcgeb9UG01Q", "decal": ""}, {"item": "E9VTiIVFPEa-OuQRto0e_Q", "prefab": "E9VTiIVFPEa-OuQRto0e_Q", "mask": "", "swatch": "", "decal": ""}, {"item": "E9VTiIVFPEa-OuQRto0e_Q", "prefab": "E9VTiIVFPEa-OuQRto0e_Q", "mask": "C8gCmBZ8RUG83vvDw_UYew", "swatch": "fD5vtKxBWECH54Jj4FbnUg", "decal": ""}, {"item": "LK8jt9zCuUGwLRccgOOvjA", "prefab": "LK8jt9zCuUGwLRccgOOvjA", "mask": "", "swatch": "", "decal": ""}, {"item": "LK8jt9zCuUGwLRccgOOvjA", "prefab": "LK8jt9zCuUGwLRccgOOvjA", "mask": "GJRG8sCM7kSGfUd9gLwpgQ", "swatch": "A6VnGtpY9Ey0atf3zaLo3g", "decal": ""}, {"item": "pQqNvrhDJ0uuipKHA2qUOA", "prefab": "pQqNvrhDJ0uuipKHA2qUOA", "mask": "", "swatch": "", "decal": ""}, {"item": "pQqNvrhDJ0uuipKHA2qUOA", "prefab": "pQqNvrhDJ0uuipKHA2qUOA", "mask": "olyS074RHU-ojE0GzBBYJw", "swatch": "lq59KZwElEStrviDEQLLcA", "decal": ""}, {"item": "74b91f2b-d4d3-4e41-8946-b1296ab7a773", "prefab": "74b91f2b-d4d3-4e41-8946-b1296ab7a773", "mask": "", "swatch": "", "decal": ""}, {"item": "74b91f2b-d4d3-4e41-8946-b1296ab7a773", "prefab": "74b91f2b-d4d3-4e41-8946-b1296ab7a773", "mask": "90e0da8b-1941-4ca8-b7e5-b4e31921873c", "swatch": "304e5cf1-5c94-481f-8e49-e6a943e6b300", "decal": ""}, {"item": "74b91f2b-d4d3-4e41-8946-b1296ab7a773", "prefab": "74b91f2b-d4d3-4e41-8946-b1296ab7a773", "mask": "iT7UGMahoEu7BZZqeKXKww", "swatch": "NpMkoIK62k63QoLVEXId6g", "decal": ""}, {"item": "74b91f2b-d4d3-4e41-8946-b1296ab7a773", "prefab": "74b91f2b-d4d3-4e41-8946-b1296ab7a773", "mask": "iT7UGMahoEu7BZZqeKXKww", "swatch": "PLRhiOhhb0KhatFH7f3pwA", "decal": ""}, {"item": "74b91f2b-d4d3-4e41-8946-b1296ab7a773", "prefab": "74b91f2b-d4d3-4e41-8946-b1296ab7a773", "mask": "iT7UGMahoEu7BZZqeKXKww", "swatch": "RwgnKTfMEkWrAp_OMGy__Q", "decal": ""}, {"item": "hTHM3Kjqy0ykqBD8UCRQJw", "prefab": "hTHM3Kjqy0ykqBD8UCRQJw", "mask": "", "swatch": "", "decal": ""}, {"item": "hTHM3Kjqy0ykqBD8UCRQJw", "prefab": "hTHM3Kjqy0ykqBD8UCRQJw", "mask": "6GfSjqIb4ECCvIPC8vO8_A", "swatch": "7-lAnMa7M0u0n0nmeIINgw", "decal": ""}, {"item": "vDSlMnayzEqWXwT-lkcKSA", "prefab": "vDSlMnayzEqWXwT-lkcKSA", "mask": "", "swatch": "", "decal": ""}, {"item": "vDSlMnayzEqWXwT-lkcKSA", "prefab": "vDSlMnayzEqWXwT-lkcKSA", "mask": "0WOBGm34H0ySBC10VToa9g", "swatch": "otQaNOsODUSTfHlCX0IVOA", "decal": ""}, {"item": "vDSlMnayzEqWXwT-lkcKSA", "prefab": "vDSlMnayzEqWXwT-lkcKSA", "mask": "0WOBGm34H0ySBC10VToa9g", "swatch": "NKf5-r2S_0iP0SaO9bjvaw", "decal": ""}, {"item": "vDSlMnayzEqWXwT-lkcKSA", "prefab": "vDSlMnayzEqWXwT-lkcKSA", "mask": "0WOBGm34H0ySBC10VToa9g", "swatch": "adJVA26PyUSVvmZgxkCAmQ", "decal": ""}, {"item": "vDSlMnayzEqWXwT-lkcKSA", "prefab": "vDSlMnayzEqWXwT-lkcKSA", "mask": "ytfIq0TiYU6EUdd0ruaGAQ", "swatch": "4ijeE3LqUUuSplKrMto6Kg", "decal": ""}, {"item": "vDSlMnayzEqWXwT-lkcKSA", "prefab": "vDSlMnayzEqWXwT-lkcKSA", "mask": "ytfIq0TiYU6EUdd0ruaGAQ", "swatch": "d27e5m1BLUWI42Pxnj6F4A", "decal": ""}, {"item": "vDSlMnayzEqWXwT-lkcKSA", "prefab": "vDSlMnayzEqWXwT-lkcKSA", "mask": "ytfIq0TiYU6EUdd0ruaGAQ", "swatch": "RZFWcTzxPkOfUuTIQ1mzkg", "decal": ""}, {"item": "QK6lBlX_wUCiSbJ9RkIwdA", "prefab": "QK6lBlX_wUCiSbJ9RkIwdA", "mask": "", "swatch": "", "decal": ""}, {"item": "TjrFi6TFhUC5tQXWkZiK9A", "prefab": "TjrFi6TFhUC5tQXWkZiK9A", "mask": "", "swatch": "", "decal": ""}, {"item": "ASHnWS0h9kOJhbOUwxWKFQ", "prefab": "ASHnWS0h9kOJhbOUwxWKFQ", "mask": "", "swatch": "", "decal": ""}, {"item": "ASHnWS0h9kOJhbOUwxWKFQ", "prefab": "ASHnWS0h9kOJhbOUwxWKFQ", "mask": "6nmHu9zj8k-tEz0tBHblnQ", "swatch": "U2ZbHn8WJEySUXVpzAY6ig", "decal": ""}, {"item": "MJQiQ3LATEOymxCC8wfSjw", "prefab": "MJQiQ3LATEOymxCC8wfSjw", "mask": "", "swatch": "", "decal": ""}, {"item": "MJQiQ3LATEOymxCC8wfSjw", "prefab": "MJQiQ3LATEOymxCC8wfSjw", "mask": "lSDrKR69rkSFULy9GA__1w", "swatch": "L333w3dfIEWDbtt1Yn1gBg", "decal": ""}, {"item": "0yFzgVESokC3gFd3hrS8bg", "prefab": "0yFzgVESokC3gFd3hrS8bg", "mask": "", "swatch": "", "decal": ""}, {"item": "0yFzgVESokC3gFd3hrS8bg", "prefab": "0yFzgVESokC3gFd3hrS8bg", "mask": "yKb2A8M_m0GQDhFWCRUHTQ", "swatch": "GK3yolCNlEm3B_5DtHkE8w", "decal": ""}, {"item": "jUpFzO5MPEGTbS-E0a99Nw", "prefab": "jUpFzO5MPEGTbS-E0a99Nw", "mask": "", "swatch": "", "decal": ""}, {"item": "JWfs9IiQs0Weu8MmyGpqLQ", "prefab": "JWfs9IiQs0Weu8MmyGpqLQ", "mask": "", "swatch": "", "decal": ""}, {"item": "JWfs9IiQs0Weu8MmyGpqLQ", "prefab": "JWfs9IiQs0Weu8MmyGpqLQ", "mask": "x10FeRBsQU2DIIpG8XIlwg", "swatch": "Z8Bfk0MTb0evZehvvgzuBA", "decal": ""}, {"item": "JWfs9IiQs0Weu8MmyGpqLQ", "prefab": "JWfs9IiQs0Weu8MmyGpqLQ", "mask": "x10FeRBsQU2DIIpG8XIlwg", "swatch": "9QG6qp5FBU-u0-JK-yMMqA", "decal": ""}, {"item": "JWfs9IiQs0Weu8MmyGpqLQ", "prefab": "JWfs9IiQs0Weu8MmyGpqLQ", "mask": "x10FeRBsQU2DIIpG8XIlwg", "swatch": "CavRDqIlmUeTZBtCLn1hZQ", "decal": ""}, {"item": "3757149b-f478-4975-81e5-d808a1bed8d5", "prefab": "3757149b-f478-4975-81e5-d808a1bed8d5", "mask": "", "swatch": "", "decal": ""}, {"item": "3757149b-f478-4975-81e5-d808a1bed8d5", "prefab": "3757149b-f478-4975-81e5-d808a1bed8d5", "mask": "Too5jpb1w0iQenkjRRS-QA", "swatch": "LDqcPkvNXE2gFXxbpNkfhA", "decal": ""}, {"item": "3757149b-f478-4975-81e5-d808a1bed8d5", "prefab": "3757149b-f478-4975-81e5-d808a1bed8d5", "mask": "Yn1uv3Y8JE6VSxsf0LOBvw", "swatch": "XOBIPrKCr02xa4tKj8wN9g", "decal": ""}, {"item": "3757149b-f478-4975-81e5-d808a1bed8d5", "prefab": "3757149b-f478-4975-81e5-d808a1bed8d5", "mask": "Yn1uv3Y8JE6VSxsf0LOBvw", "swatch": "i33RmJJCwUem-ADkOwMNQQ", "decal": ""}, {"item": "3757149b-f478-4975-81e5-d808a1bed8d5", "prefab": "3757149b-f478-4975-81e5-d808a1bed8d5", "mask": "3fQj_7meDEeQqm_7RfEfxQ", "swatch": "09Cg6dOq2kC2-tI5yyGSYg", "decal": ""}, {"item": "7LSm-xCYakmgcAr--Fn-JA", "prefab": "7LSm-xCYakmgcAr--Fn-JA", "mask": "", "swatch": "", "decal": ""}, {"item": "7LSm-xCYakmgcAr--Fn-JA", "prefab": "7LSm-xCYakmgcAr--Fn-JA", "mask": "AK4CoAB2IUGkwqECNaO75g", "swatch": "uTHwo8qd6Ei3Oy4gz_j9Ow", "decal": ""}, {"item": "7LSm-xCYakmgcAr--Fn-JA", "prefab": "7LSm-xCYakmgcAr--Fn-JA", "mask": "iDxHI0T4Gkifs3_GY5gdjQ", "swatch": "rZ45-lecBESqHaLX0c7NAw", "decal": ""}, {"item": "418db0e0-c7af-40c2-9cac-d461ce41e210", "prefab": "418db0e0-c7af-40c2-9cac-d461ce41e210", "mask": "", "swatch": "", "decal": ""}, {"item": "418db0e0-c7af-40c2-9cac-d461ce41e210", "prefab": "418db0e0-c7af-40c2-9cac-d461ce41e210", "mask": "0f49ac81-63bd-4845-8bdc-1944181a8aa3", "swatch": "64545dcc-a8eb-4c69-a539-78ee73a2d327", "decal": ""}, {"item": "418db0e0-c7af-40c2-9cac-d461ce41e210", "prefab": "418db0e0-c7af-40c2-9cac-d461ce41e210", "mask": "0f49ac81-63bd-4845-8bdc-1944181a8aa3", "swatch": "e02838f8-4029-455a-b35c-dd40f972b05b", "decal": ""}, {"item": "418db0e0-c7af-40c2-9cac-d461ce41e210", "prefab": "418db0e0-c7af-40c2-9cac-d461ce41e210", "mask": "0f49ac81-63bd-4845-8bdc-1944181a8aa3", "swatch": "71e5a0fe-fc8d-420b-af3b-e74160e0d1f3", "decal": ""}, {"item": "418db0e0-c7af-40c2-9cac-d461ce41e210", "prefab": "418db0e0-c7af-40c2-9cac-d461ce41e210", "mask": "0f49ac81-63bd-4845-8bdc-1944181a8aa3", "swatch": "909ed249-ce09-4304-8b02-56794fa7567d", "decal": ""}, {"item": "418db0e0-c7af-40c2-9cac-d461ce41e210", "prefab": "418db0e0-c7af-40c2-9cac-d461ce41e210", "mask": "0f49ac81-63bd-4845-8bdc-1944181a8aa3", "swatch": "Tbrc2MlSlE6mKua8qSoluQ", "decal": ""}, {"item": "418db0e0-c7af-40c2-9cac-d461ce41e210", "prefab": "418db0e0-c7af-40c2-9cac-d461ce41e210", "mask": "0f49ac81-63bd-4845-8bdc-1944181a8aa3", "swatch": "aBQ3R9boek-mYeSYSNvDbQ", "decal": ""}, {"item": "97eae086-cfea-45ff-9b77-1d184256d493", "prefab": "97eae086-cfea-45ff-9b77-1d184256d493", "mask": "", "swatch": "", "decal": ""}, {"item": "97eae086-cfea-45ff-9b77-1d184256d493", "prefab": "97eae086-cfea-45ff-9b77-1d184256d493", "mask": "8768aa33-b014-4062-b323-b146dd7d78d2", "swatch": "357eb160-360b-4a63-840d-65d11cc8ffc2", "decal": ""}, {"item": "97eae086-cfea-45ff-9b77-1d184256d493", "prefab": "97eae086-cfea-45ff-9b77-1d184256d493", "mask": "8768aa33-b014-4062-b323-b146dd7d78d2", "swatch": "a38254c0-00e3-429b-9d5d-b9e6a9812e69", "decal": ""}, {"item": "97eae086-cfea-45ff-9b77-1d184256d493", "prefab": "97eae086-cfea-45ff-9b77-1d184256d493", "mask": "8768aa33-b014-4062-b323-b146dd7d78d2", "swatch": "d3a9b576-5433-4305-bd96-b08820c09212", "decal": ""}, {"item": "97eae086-cfea-45ff-9b77-1d184256d493", "prefab": "97eae086-cfea-45ff-9b77-1d184256d493", "mask": "8768aa33-b014-4062-b323-b146dd7d78d2", "swatch": "7afe1bb0-5ca9-4263-8f85-49d3a18353c7", "decal": ""}, {"item": "97eae086-cfea-45ff-9b77-1d184256d493", "prefab": "97eae086-cfea-45ff-9b77-1d184256d493", "mask": "8768aa33-b014-4062-b323-b146dd7d78d2", "swatch": "RKmRHk2N_U2gd91nybo6vg", "decal": ""}, {"item": "97eae086-cfea-45ff-9b77-1d184256d493", "prefab": "97eae086-cfea-45ff-9b77-1d184256d493", "mask": "8768aa33-b014-4062-b323-b146dd7d78d2", "swatch": "d3KmqVuMQkCYK6sVTMOTOA", "decal": ""}, {"item": "d766c8d3-76e8-4021-b0a4-24b78fb7eaba", "prefab": "d766c8d3-76e8-4021-b0a4-24b78fb7eaba", "mask": "", "swatch": "", "decal": ""}, {"item": "d766c8d3-76e8-4021-b0a4-24b78fb7eaba", "prefab": "d766c8d3-76e8-4021-b0a4-24b78fb7eaba", "mask": "adae82eb-a4b9-4541-acf2-76cea839a593", "swatch": "026ca50e-3a81-43cf-87f5-950687b7bbdd", "decal": ""}, {"item": "d766c8d3-76e8-4021-b0a4-24b78fb7eaba", "prefab": "d766c8d3-76e8-4021-b0a4-24b78fb7eaba", "mask": "adae82eb-a4b9-4541-acf2-76cea839a593", "swatch": "6584dada-499e-4d7c-958d-466955b20640", "decal": ""}, {"item": "74e6878f-94e0-41f2-93ea-de6412238385", "prefab": "74e6878f-94e0-41f2-93ea-de6412238385", "mask": "", "swatch": "", "decal": ""}, {"item": "52VsI_lKlkSUAOJlfDnyrQ", "prefab": "52VsI_lKlkSUAOJlfDnyrQ", "mask": "", "swatch": "", "decal": ""}, {"item": "52VsI_lKlkSUAOJlfDnyrQ", "prefab": "52VsI_lKlkSUAOJlfDnyrQ", "mask": "MV1HW2x43kG4rrhyoC6mmw", "swatch": "HncURj5yi0ykk2cVyCZ4Sw", "decal": ""}, {"item": "52VsI_lKlkSUAOJlfDnyrQ", "prefab": "52VsI_lKlkSUAOJlfDnyrQ", "mask": "MV1HW2x43kG4rrhyoC6mmw", "swatch": "42ZNGHXMvU29yo9s9tVpOQ", "decal": ""}, {"item": "52VsI_lKlkSUAOJlfDnyrQ", "prefab": "52VsI_lKlkSUAOJlfDnyrQ", "mask": "MV1HW2x43kG4rrhyoC6mmw", "swatch": "VDo682NvSUa8xYn8zTX-AQ", "decal": ""}, {"item": "52VsI_lKlkSUAOJlfDnyrQ", "prefab": "52VsI_lKlkSUAOJlfDnyrQ", "mask": "MV1HW2x43kG4rrhyoC6mmw", "swatch": "FvEjo9let0e_Sr-kWq5AvQ", "decal": ""}, {"item": "5b01eaa3-0cac-40c0-b72a-b3f6a868ae0c", "prefab": "5b01eaa3-0cac-40c0-b72a-b3f6a868ae0c", "mask": "", "swatch": "", "decal": ""}, {"item": "5b01eaa3-0cac-40c0-b72a-b3f6a868ae0c", "prefab": "5b01eaa3-0cac-40c0-b72a-b3f6a868ae0c", "mask": "7b187c06-7ba2-4f3c-bca9-fdf98146f385", "swatch": "47da86b9-4e6e-423c-b8c4-ea6bf8472509", "decal": ""}, {"item": "5b01eaa3-0cac-40c0-b72a-b3f6a868ae0c", "prefab": "5b01eaa3-0cac-40c0-b72a-b3f6a868ae0c", "mask": "7b187c06-7ba2-4f3c-bca9-fdf98146f385", "swatch": "d5987fee-e3cc-4336-853a-5df57f73df05", "decal": ""}, {"item": "5b01eaa3-0cac-40c0-b72a-b3f6a868ae0c", "prefab": "5b01eaa3-0cac-40c0-b72a-b3f6a868ae0c", "mask": "7b187c06-7ba2-4f3c-bca9-fdf98146f385", "swatch": "0c433086-9501-48c4-8d0e-4474eb420c61", "decal": ""}, {"item": "5b01eaa3-0cac-40c0-b72a-b3f6a868ae0c", "prefab": "5b01eaa3-0cac-40c0-b72a-b3f6a868ae0c", "mask": "7b187c06-7ba2-4f3c-bca9-fdf98146f385", "swatch": "2be2f040-68e4-44dd-9099-29f11110f3a4", "decal": ""}, {"item": "5b01eaa3-0cac-40c0-b72a-b3f6a868ae0c", "prefab": "5b01eaa3-0cac-40c0-b72a-b3f6a868ae0c", "mask": "7b187c06-7ba2-4f3c-bca9-fdf98146f385", "swatch": "1VGZ1QCvcU6rsKVkUDBNjQ", "decal": ""}, {"item": "5b01eaa3-0cac-40c0-b72a-b3f6a868ae0c", "prefab": "5b01eaa3-0cac-40c0-b72a-b3f6a868ae0c", "mask": "7b187c06-7ba2-4f3c-bca9-fdf98146f385", "swatch": "8hUBfmq28EixYjv92Xcrgg", "decal": ""}, {"item": "88739d79-aeb9-471a-b25b-776f46bba9f6", "prefab": "88739d79-aeb9-471a-b25b-776f46bba9f6", "mask": "", "swatch": "", "decal": ""}, {"item": "88739d79-aeb9-471a-b25b-776f46bba9f6", "prefab": "88739d79-aeb9-471a-b25b-776f46bba9f6", "mask": "hjk9OO8CW0KNGR7A6_p84A", "swatch": "y_gTbnuYS0GKJmnCs2retw", "decal": ""}, {"item": "125a3a62-1005-47dc-9fd8-ee09ea803e9f", "prefab": "125a3a62-1005-47dc-9fd8-ee09ea803e9f", "mask": "", "swatch": "", "decal": ""}, {"item": "125a3a62-1005-47dc-9fd8-ee09ea803e9f", "prefab": "125a3a62-1005-47dc-9fd8-ee09ea803e9f", "mask": "5659ce2c-9cb0-49da-8c39-4b7df0f8deec", "swatch": "97bbb384-b37e-4ea1-902c-88fbd6854ab5", "decal": ""}, {"item": "125a3a62-1005-47dc-9fd8-ee09ea803e9f", "prefab": "125a3a62-1005-47dc-9fd8-ee09ea803e9f", "mask": "5659ce2c-9cb0-49da-8c39-4b7df0f8deec", "swatch": "7fb8496f-0e6c-4e48-b8fa-5709ebbf4820", "decal": ""}, {"item": "125a3a62-1005-47dc-9fd8-ee09ea803e9f", "prefab": "125a3a62-1005-47dc-9fd8-ee09ea803e9f", "mask": "5659ce2c-9cb0-49da-8c39-4b7df0f8deec", "swatch": "7nRz9Jmnn066_PzngkhfFA", "decal": ""}, {"item": "ce81210f-8eb8-4cd2-95d5-046d4da229c8", "prefab": "ce81210f-8eb8-4cd2-95d5-046d4da229c8", "mask": "", "swatch": "", "decal": ""}, {"item": "ce81210f-8eb8-4cd2-95d5-046d4da229c8", "prefab": "ce81210f-8eb8-4cd2-95d5-046d4da229c8", "mask": "f7c7d05f-5454-445a-af38-25f110bd204a", "swatch": "bb01b51e-a95f-441e-8c52-abc3aeed7145", "decal": ""}, {"item": "4025e4c4-abaa-4729-a2a3-a7175313e5ed", "prefab": "4025e4c4-abaa-4729-a2a3-a7175313e5ed", "mask": "", "swatch": "", "decal": ""}, {"item": "4025e4c4-abaa-4729-a2a3-a7175313e5ed", "prefab": "4025e4c4-abaa-4729-a2a3-a7175313e5ed", "mask": "1f18670d-df52-4d2b-8009-a32cf7c67b40", "swatch": "1919d319-6cce-4500-8766-15fed6a13fa8", "decal": ""}, {"item": "4025e4c4-abaa-4729-a2a3-a7175313e5ed", "prefab": "4025e4c4-abaa-4729-a2a3-a7175313e5ed", "mask": "1f18670d-df52-4d2b-8009-a32cf7c67b40", "swatch": "lBe3yxuE40eBmTrnEHBqQQ", "decal": ""}, {"item": "4025e4c4-abaa-4729-a2a3-a7175313e5ed", "prefab": "4025e4c4-abaa-4729-a2a3-a7175313e5ed", "mask": "1f18670d-df52-4d2b-8009-a32cf7c67b40", "swatch": "oZ8k0Qv3SkG-DkY0_dP7UA", "decal": ""}, {"item": "7a6fe0e6-f930-4fb8-9b2f-4c857a273dd9", "prefab": "7a6fe0e6-f930-4fb8-9b2f-4c857a273dd9", "mask": "", "swatch": "", "decal": ""}, {"item": "7a6fe0e6-f930-4fb8-9b2f-4c857a273dd9", "prefab": "7a6fe0e6-f930-4fb8-9b2f-4c857a273dd9", "mask": "0XPd5RzNJEepHztqwAznHQ", "swatch": "4398ce1a-e6ee-4da4-ad0d-7abfab41020c", "decal": ""}, {"item": "7a6fe0e6-f930-4fb8-9b2f-4c857a273dd9", "prefab": "7a6fe0e6-f930-4fb8-9b2f-4c857a273dd9", "mask": "0XPd5RzNJEepHztqwAznHQ", "swatch": "XNq4Cns4lEGydtktdsXlmQ", "decal": ""}, {"item": "7a6fe0e6-f930-4fb8-9b2f-4c857a273dd9", "prefab": "7a6fe0e6-f930-4fb8-9b2f-4c857a273dd9", "mask": "0XPd5RzNJEepHztqwAznHQ", "swatch": "83522c12-9549-4d74-8c87-bae210bcd2bf", "decal": ""}, {"item": "abc25091-ed5f-4c72-9364-fffeef1bc239", "prefab": "abc25091-ed5f-4c72-9364-fffeef1bc239", "mask": "", "swatch": "", "decal": ""}, {"item": "b904304b-cce0-466f-8569-c621eeadd4f7", "prefab": "b904304b-cce0-466f-8569-c621eeadd4f7", "mask": "", "swatch": "", "decal": ""}, {"item": "b904304b-cce0-466f-8569-c621eeadd4f7", "prefab": "b904304b-cce0-466f-8569-c621eeadd4f7", "mask": "f79f8a46-ef1d-4d8c-a4fc-383e3a9eb10b", "swatch": "8c7b09f2-6213-4ef9-87ab-a2df72d07a22", "decal": ""}, {"item": "b904304b-cce0-466f-8569-c621eeadd4f7", "prefab": "b904304b-cce0-466f-8569-c621eeadd4f7", "mask": "f79f8a46-ef1d-4d8c-a4fc-383e3a9eb10b", "swatch": "rzUd1IMTKUmW0HeMPSIOBQ", "decal": ""}, {"item": "8fc7aaef-adec-4534-8f91-445e4e7095d5", "prefab": "8fc7aaef-adec-4534-8f91-445e4e7095d5", "mask": "", "swatch": "", "decal": ""}, {"item": "8fc7aaef-adec-4534-8f91-445e4e7095d5", "prefab": "8fc7aaef-adec-4534-8f91-445e4e7095d5", "mask": "awWjleyNP0akWNYJoehCLA", "swatch": "5Dbc8qCaY0OXEuwykM4naA", "decal": ""}, {"item": "8fc7aaef-adec-4534-8f91-445e4e7095d5", "prefab": "8fc7aaef-adec-4534-8f91-445e4e7095d5", "mask": "awWjleyNP0akWNYJoehCLA", "swatch": "HBt3ToOkUUereoPKwyvzuw", "decal": ""}, {"item": "3bb50a88-09ae-48c7-a42c-df5879680eff", "prefab": "3bb50a88-09ae-48c7-a42c-df5879680eff", "mask": "", "swatch": "", "decal": ""}, {"item": "t9co2516nkuJO4LIJA1bJA", "prefab": "t9co2516nkuJO4LIJA1bJA", "mask": "", "swatch": "", "decal": ""}, {"item": "t9co2516nkuJO4LIJA1bJA", "prefab": "t9co2516nkuJO4LIJA1bJA", "mask": "8kdJkmwNM0WQmMVrb8fK8A", "swatch": "i5RKLo-RbEqisnGII1wpaQ", "decal": ""}, {"item": "t9co2516nkuJO4LIJA1bJA", "prefab": "t9co2516nkuJO4LIJA1bJA", "mask": "8kdJkmwNM0WQmMVrb8fK8A", "swatch": "jwEB6LyEaEekqtPNRc6TVA", "decal": ""}, {"item": "t9co2516nkuJO4LIJA1bJA", "prefab": "t9co2516nkuJO4LIJA1bJA", "mask": "8kdJkmwNM0WQmMVrb8fK8A", "swatch": "Wj7I2YiVoE6zp8J_HYs8tw", "decal": ""}, {"item": "9eb7f505-5afd-4b28-9886-7996d38c59c6", "prefab": "9eb7f505-5afd-4b28-9886-7996d38c59c6", "mask": "", "swatch": "", "decal": ""}, {"item": "9eb7f505-5afd-4b28-9886-7996d38c59c6", "prefab": "9eb7f505-5afd-4b28-9886-7996d38c59c6", "mask": "8eef8837-9141-4088-835a-b2f47f4126f8", "swatch": "17e44296-1cd9-4f8e-9aa7-3837dc69279a", "decal": ""}, {"item": "9eb7f505-5afd-4b28-9886-7996d38c59c6", "prefab": "9eb7f505-5afd-4b28-9886-7996d38c59c6", "mask": "8eef8837-9141-4088-835a-b2f47f4126f8", "swatch": "c4ebfd1a-fcda-4b98-a151-a941b1801867", "decal": ""}, {"item": "9eb7f505-5afd-4b28-9886-7996d38c59c6", "prefab": "9eb7f505-5afd-4b28-9886-7996d38c59c6", "mask": "8eef8837-9141-4088-835a-b2f47f4126f8", "swatch": "c0c33a35-494c-4a1a-aa53-d27d9b9ef5a4", "decal": ""}, {"item": "9eb7f505-5afd-4b28-9886-7996d38c59c6", "prefab": "9eb7f505-5afd-4b28-9886-7996d38c59c6", "mask": "8eef8837-9141-4088-835a-b2f47f4126f8", "swatch": "wat8f6fs80KptZbW39e8tA", "decal": ""}, {"item": "9eb7f505-5afd-4b28-9886-7996d38c59c6", "prefab": "9eb7f505-5afd-4b28-9886-7996d38c59c6", "mask": "8eef8837-9141-4088-835a-b2f47f4126f8", "swatch": "wkqKPZmnoU-8TXSWzi2-Ow", "decal": ""}, {"item": "b1c3ccc8-91d7-434e-b9cc-bd632fffc01b", "prefab": "b1c3ccc8-91d7-434e-b9cc-bd632fffc01b", "mask": "", "swatch": "", "decal": ""}, {"item": "b1c3ccc8-91d7-434e-b9cc-bd632fffc01b", "prefab": "b1c3ccc8-91d7-434e-b9cc-bd632fffc01b", "mask": "0p5N9U5qokKdQhW07yYLGQ", "swatch": "Su7kiW24d0KapNns96JoFQ", "decal": ""}, {"item": "b1c3ccc8-91d7-434e-b9cc-bd632fffc01b", "prefab": "b1c3ccc8-91d7-434e-b9cc-bd632fffc01b", "mask": "0p5N9U5qokKdQhW07yYLGQ", "swatch": "CahBzgN3D0y46AQsvGC14A", "decal": ""}, {"item": "4d533b3a-b5fa-446a-842d-92798ad5b493", "prefab": "4d533b3a-b5fa-446a-842d-92798ad5b493", "mask": "", "swatch": "", "decal": ""}, {"item": "4d533b3a-b5fa-446a-842d-92798ad5b493", "prefab": "4d533b3a-b5fa-446a-842d-92798ad5b493", "mask": "6b38ab79-142b-446e-978c-ad829ade8823", "swatch": "fe5288bc-0e02-4ea3-b17d-dee0e576979d", "decal": ""}, {"item": "4d533b3a-b5fa-446a-842d-92798ad5b493", "prefab": "4d533b3a-b5fa-446a-842d-92798ad5b493", "mask": "6b38ab79-142b-446e-978c-ad829ade8823", "swatch": "b444ed0a-eab5-4bd6-af34-d080de74a149", "decal": ""}, {"item": "4d533b3a-b5fa-446a-842d-92798ad5b493", "prefab": "4d533b3a-b5fa-446a-842d-92798ad5b493", "mask": "6b38ab79-142b-446e-978c-ad829ade8823", "swatch": "8053b5e5-0f67-4329-a45d-1ee9fc830820", "decal": ""}, {"item": "5800d2b5-75b8-442f-8b10-63bd3d73a1b8", "prefab": "5800d2b5-75b8-442f-8b10-63bd3d73a1b8", "mask": "", "swatch": "", "decal": ""}, {"item": "5800d2b5-75b8-442f-8b10-63bd3d73a1b8", "prefab": "5800d2b5-75b8-442f-8b10-63bd3d73a1b8", "mask": "7869b2a5-cd89-4033-b9f7-4f1451ecb47b", "swatch": "18b52db5-0261-46a0-9214-ff510d455ed9", "decal": ""}, {"item": "5800d2b5-75b8-442f-8b10-63bd3d73a1b8", "prefab": "5800d2b5-75b8-442f-8b10-63bd3d73a1b8", "mask": "7869b2a5-cd89-4033-b9f7-4f1451ecb47b", "swatch": "55b4e661-43e1-4708-82d5-2dd2bebd4f45", "decal": ""}, {"item": "5800d2b5-75b8-442f-8b10-63bd3d73a1b8", "prefab": "5800d2b5-75b8-442f-8b10-63bd3d73a1b8", "mask": "7869b2a5-cd89-4033-b9f7-4f1451ecb47b", "swatch": "8c74ee27-3d56-401b-8a8c-7868a80770e8", "decal": ""}, {"item": "473c672d-2b79-48a2-a49d-fd68160c5bde", "prefab": "473c672d-2b79-48a2-a49d-fd68160c5bde", "mask": "", "swatch": "", "decal": ""}, {"item": "473c672d-2b79-48a2-a49d-fd68160c5bde", "prefab": "473c672d-2b79-48a2-a49d-fd68160c5bde", "mask": "0e42108e-3b08-49b4-86c0-7984719e8380", "swatch": "30c61e63-5e09-4a6f-8915-1e1fe4d55cf0", "decal": ""}, {"item": "473c672d-2b79-48a2-a49d-fd68160c5bde", "prefab": "473c672d-2b79-48a2-a49d-fd68160c5bde", "mask": "0e42108e-3b08-49b4-86c0-7984719e8380", "swatch": "2cf2b644-3fa6-449c-9fe8-d0ab917f3106", "decal": ""}, {"item": "tkbHKZ9wu0W00VGeUbIaaw", "prefab": "tkbHKZ9wu0W00VGeUbIaaw", "mask": "", "swatch": "", "decal": ""}, {"item": "eada710d-ea0c-473f-b9c6-23e210607b24", "prefab": "eada710d-ea0c-473f-b9c6-23e210607b24", "mask": "", "swatch": "", "decal": ""}, {"item": "eada710d-ea0c-473f-b9c6-23e210607b24", "prefab": "eada710d-ea0c-473f-b9c6-23e210607b24", "mask": "f4cea991-fc3d-4715-bf82-8a8fb60e8799", "swatch": "72a010c0-bddd-4e5d-bca6-9821d82de9a6", "decal": ""}, {"item": "eada710d-ea0c-473f-b9c6-23e210607b24", "prefab": "eada710d-ea0c-473f-b9c6-23e210607b24", "mask": "f4cea991-fc3d-4715-bf82-8a8fb60e8799", "swatch": "32a5e42d-2b60-40b1-a145-ed96fc91af8e", "decal": ""}, {"item": "q7pSsRMNHkqEJmYHuhhG6g", "prefab": "q7pSsRMNHkqEJmYHuhhG6g", "mask": "", "swatch": "", "decal": ""}, {"item": "q7pSsRMNHkqEJmYHuhhG6g", "prefab": "q7pSsRMNHkqEJmYHuhhG6g", "mask": "ojCeJ1VwtUelyVqFnUwK5A", "swatch": "90qQe0AR3kOou0MsuoTmPA", "decal": ""}, {"item": "q7pSsRMNHkqEJmYHuhhG6g", "prefab": "q7pSsRMNHkqEJmYHuhhG6g", "mask": "ojCeJ1VwtUelyVqFnUwK5A", "swatch": "hZKPr4sg10SRj9DLuEgOrw", "decal": ""}, {"item": "ilDgmey2ZUWlFiuEAN7YoQ", "prefab": "ilDgmey2ZUWlFiuEAN7YoQ", "mask": "", "swatch": "", "decal": ""}, {"item": "9bf9d502-8a3c-4b24-9565-0a2c2d9864fd", "prefab": "9bf9d502-8a3c-4b24-9565-0a2c2d9864fd", "mask": "", "swatch": "", "decal": ""}, {"item": "9bf9d502-8a3c-4b24-9565-0a2c2d9864fd", "prefab": "9bf9d502-8a3c-4b24-9565-0a2c2d9864fd", "mask": "fd67ec40-f898-4df6-b846-8bf350f362b8", "swatch": "ed134bee-d199-43eb-98bd-206571603f40", "decal": ""}, {"item": "9bf9d502-8a3c-4b24-9565-0a2c2d9864fd", "prefab": "9bf9d502-8a3c-4b24-9565-0a2c2d9864fd", "mask": "fd67ec40-f898-4df6-b846-8bf350f362b8", "swatch": "59866578-d4e0-417b-9483-233ab108b1ac", "decal": ""}, {"item": "9bf9d502-8a3c-4b24-9565-0a2c2d9864fd", "prefab": "9bf9d502-8a3c-4b24-9565-0a2c2d9864fd", "mask": "fd67ec40-f898-4df6-b846-8bf350f362b8", "swatch": "22dc463a-a89b-46ad-9a0d-557c742b4a80", "decal": ""}, {"item": "9bf9d502-8a3c-4b24-9565-0a2c2d9864fd", "prefab": "9bf9d502-8a3c-4b24-9565-0a2c2d9864fd", "mask": "fd67ec40-f898-4df6-b846-8bf350f362b8", "swatch": "e1f49b74-b071-4bd1-9c4b-af36d24d45c5", "decal": ""}, {"item": "9bf9d502-8a3c-4b24-9565-0a2c2d9864fd", "prefab": "9bf9d502-8a3c-4b24-9565-0a2c2d9864fd", "mask": "fd67ec40-f898-4df6-b846-8bf350f362b8", "swatch": "ITH2OpzjP022vZ5JgxV_iQ", "decal": ""}, {"item": "9bf9d502-8a3c-4b24-9565-0a2c2d9864fd", "prefab": "9bf9d502-8a3c-4b24-9565-0a2c2d9864fd", "mask": "fd67ec40-f898-4df6-b846-8bf350f362b8", "swatch": "PMa9370LPEig_7pKwCceEQ", "decal": ""}, {"item": "3044adce-90c0-4f66-81f5-39c4fca86fdf", "prefab": "3044adce-90c0-4f66-81f5-39c4fca86fdf", "mask": "", "swatch": "", "decal": ""}, {"item": "3044adce-90c0-4f66-81f5-39c4fca86fdf", "prefab": "3044adce-90c0-4f66-81f5-39c4fca86fdf", "mask": "3b825377-d2eb-41e4-a736-b107f88f62d6", "swatch": "426d0592-f8e4-4bc6-9fe1-a62b91fd57d5", "decal": ""}, {"item": "3044adce-90c0-4f66-81f5-39c4fca86fdf", "prefab": "3044adce-90c0-4f66-81f5-39c4fca86fdf", "mask": "3b825377-d2eb-41e4-a736-b107f88f62d6", "swatch": "EC0ttvk6eU2HEMacLAx2Iw", "decal": ""}, {"item": "3044adce-90c0-4f66-81f5-39c4fca86fdf", "prefab": "3044adce-90c0-4f66-81f5-39c4fca86fdf", "mask": "3b825377-d2eb-41e4-a736-b107f88f62d6", "swatch": "mhsglVEtREmElF5wuLw3NQ", "decal": ""}, {"item": "2dFMy4zc7ky_Fd5fzfgInw", "prefab": "2dFMy4zc7ky_Fd5fzfgInw", "mask": "", "swatch": "", "decal": ""}, {"item": "2dFMy4zc7ky_Fd5fzfgInw", "prefab": "2dFMy4zc7ky_Fd5fzfgInw", "mask": "4EHIyZkOOEqnM-PuD_a5xw", "swatch": "UUyjVU4mvECPhI9xTMYR1Q", "decal": ""}, {"item": "2dFMy4zc7ky_Fd5fzfgInw", "prefab": "2dFMy4zc7ky_Fd5fzfgInw", "mask": "4EHIyZkOOEqnM-PuD_a5xw", "swatch": "HWc25-d8dUmNN2F3n8mV5A", "decal": ""}, {"item": "2dFMy4zc7ky_Fd5fzfgInw", "prefab": "2dFMy4zc7ky_Fd5fzfgInw", "mask": "4EHIyZkOOEqnM-PuD_a5xw", "swatch": "Y0e7o0_BKUm7XJSLediN5w", "decal": ""}, {"item": "2dFMy4zc7ky_Fd5fzfgInw", "prefab": "2dFMy4zc7ky_Fd5fzfgInw", "mask": "4EHIyZkOOEqnM-PuD_a5xw", "swatch": "MS7EPNLJEEyMLC6_yHQYtA", "decal": ""}, {"item": "eS_or4AnMka9M_bD7YN28g", "prefab": "eS_or4AnMka9M_bD7YN28g", "mask": "", "swatch": "", "decal": ""}, {"item": "eS_or4AnMka9M_bD7YN28g", "prefab": "eS_or4AnMka9M_bD7YN28g", "mask": "ZtCqMnFTQUWv2h0MfZ4m6A", "swatch": "zaSTWOUy_k6cnoVVYzypWg", "decal": ""}, {"item": "eS_or4AnMka9M_bD7YN28g", "prefab": "eS_or4AnMka9M_bD7YN28g", "mask": "ZtCqMnFTQUWv2h0MfZ4m6A", "swatch": "1FcyrVGyGEC9dmOq_51nFQ", "decal": ""}, {"item": "eS_or4AnMka9M_bD7YN28g", "prefab": "eS_or4AnMka9M_bD7YN28g", "mask": "ZtCqMnFTQUWv2h0MfZ4m6A", "swatch": "xpkEQ6yjhU-WfFPSjvPdHg", "decal": ""}, {"item": "eS_or4AnMka9M_bD7YN28g", "prefab": "eS_or4AnMka9M_bD7YN28g", "mask": "ZtCqMnFTQUWv2h0MfZ4m6A", "swatch": "N9CBuYaMtUOrCks5UcOycg", "decal": ""}, {"item": "08d56627-4500-4724-8692-a23c96ddb3ec", "prefab": "08d56627-4500-4724-8692-a23c96ddb3ec", "mask": "", "swatch": "", "decal": ""}, {"item": "08d56627-4500-4724-8692-a23c96ddb3ec", "prefab": "08d56627-4500-4724-8692-a23c96ddb3ec", "mask": "c2a9c007-2748-4fa2-ab9b-0081eef792f1", "swatch": "531b4297-b38b-4362-9854-4f2139ea43be", "decal": ""}, {"item": "08d56627-4500-4724-8692-a23c96ddb3ec", "prefab": "08d56627-4500-4724-8692-a23c96ddb3ec", "mask": "05cff8b8-d437-485a-b4b8-41d35becd022", "swatch": "46970903-9799-4d25-b90a-7162c1f5dbef", "decal": ""}, {"item": "08d56627-4500-4724-8692-a23c96ddb3ec", "prefab": "08d56627-4500-4724-8692-a23c96ddb3ec", "mask": "QBzMp7MdAUOLIofvddui1A", "swatch": "newGzX_zA0eGUX3Iayq5Ag", "decal": ""}, {"item": "zpjkkb1HtUORoPaODFuuxg", "prefab": "zpjkkb1HtUORoPaODFuuxg", "mask": "", "swatch": "", "decal": ""}, {"item": "64ad8789-1645-45f7-819a-a413ff7c9622", "prefab": "64ad8789-1645-45f7-819a-a413ff7c9622", "mask": "", "swatch": "", "decal": ""}, {"item": "eceb7d49-be41-4228-86a9-2973d62e7135", "prefab": "eceb7d49-be41-4228-86a9-2973d62e7135", "mask": "", "swatch": "", "decal": ""}, {"item": "eceb7d49-be41-4228-86a9-2973d62e7135", "prefab": "eceb7d49-be41-4228-86a9-2973d62e7135", "mask": "7a7bc314-4ffd-4ac8-9ed6-6ca73589c132", "swatch": "556141f7-6fbc-4065-9faa-8d8240a93d7f", "decal": ""}, {"item": "eceb7d49-be41-4228-86a9-2973d62e7135", "prefab": "eceb7d49-be41-4228-86a9-2973d62e7135", "mask": "7a7bc314-4ffd-4ac8-9ed6-6ca73589c132", "swatch": "d81d0b62-e052-46c2-9252-4bc5fb219f53", "decal": ""}, {"item": "eceb7d49-be41-4228-86a9-2973d62e7135", "prefab": "eceb7d49-be41-4228-86a9-2973d62e7135", "mask": "7a7bc314-4ffd-4ac8-9ed6-6ca73589c132", "swatch": "xGmnUGF-Ck-jP43r8tVVjg", "decal": ""}, {"item": "28374ebc-4050-4ea0-b3b6-41ebe75cedf7", "prefab": "28374ebc-4050-4ea0-b3b6-41ebe75cedf7", "mask": "", "swatch": "", "decal": ""}, {"item": "28374ebc-4050-4ea0-b3b6-41ebe75cedf7", "prefab": "28374ebc-4050-4ea0-b3b6-41ebe75cedf7", "mask": "dd7472ed-bdcb-4811-81ba-5ed682fb7675", "swatch": "a6796264-9c7e-4605-830c-06db8d0dc780", "decal": ""}, {"item": "28374ebc-4050-4ea0-b3b6-41ebe75cedf7", "prefab": "28374ebc-4050-4ea0-b3b6-41ebe75cedf7", "mask": "dd7472ed-bdcb-4811-81ba-5ed682fb7675", "swatch": "f3b2330a-b981-4c7e-902f-1f57382f2388", "decal": ""}, {"item": "28374ebc-4050-4ea0-b3b6-41ebe75cedf7", "prefab": "28374ebc-4050-4ea0-b3b6-41ebe75cedf7", "mask": "dd7472ed-bdcb-4811-81ba-5ed682fb7675", "swatch": "baffdefe-de21-4551-9330-b50873965bdd", "decal": ""}, {"item": "74365234-cb72-409d-8cf5-9a7209f707e1", "prefab": "74365234-cb72-409d-8cf5-9a7209f707e1", "mask": "", "swatch": "", "decal": ""}, {"item": "74365234-cb72-409d-8cf5-9a7209f707e1", "prefab": "74365234-cb72-409d-8cf5-9a7209f707e1", "mask": "fa9a3d8e-16c9-4a7e-9166-3226e0d13c39", "swatch": "49bbd11a-e5c9-47ff-b04d-72eb321e8a57", "decal": ""}, {"item": "50c9c6f8-2963-4ef3-95d5-e999a898269f", "prefab": "50c9c6f8-2963-4ef3-95d5-e999a898269f", "mask": "", "swatch": "", "decal": ""}, {"item": "b9e0f03b-2609-4911-82e9-1e324dbbe8be", "prefab": "b9e0f03b-2609-4911-82e9-1e324dbbe8be", "mask": "", "swatch": "", "decal": ""}, {"item": "b9e0f03b-2609-4911-82e9-1e324dbbe8be", "prefab": "b9e0f03b-2609-4911-82e9-1e324dbbe8be", "mask": "ce3782aa-56cc-40d7-9019-8ce714cb7748", "swatch": "cdca593a-8840-4140-9145-fe962732040c", "decal": ""}, {"item": "b9e0f03b-2609-4911-82e9-1e324dbbe8be", "prefab": "b9e0f03b-2609-4911-82e9-1e324dbbe8be", "mask": "ce3782aa-56cc-40d7-9019-8ce714cb7748", "swatch": "62bd5dda-08da-4528-83c1-f0ca3812dc6e", "decal": ""}, {"item": "b9e0f03b-2609-4911-82e9-1e324dbbe8be", "prefab": "b9e0f03b-2609-4911-82e9-1e324dbbe8be", "mask": "ce3782aa-56cc-40d7-9019-8ce714cb7748", "swatch": "X3MkhL55Rk-0UK6h4dw0Eg", "decal": ""}, {"item": "9d68e9f4-6ea0-4717-bb78-f2783a14a6b6", "prefab": "9d68e9f4-6ea0-4717-bb78-f2783a14a6b6", "mask": "", "swatch": "", "decal": ""}, {"item": "9d68e9f4-6ea0-4717-bb78-f2783a14a6b6", "prefab": "9d68e9f4-6ea0-4717-bb78-f2783a14a6b6", "mask": "VSZX99mSt0e7gPRM2_XzMw", "swatch": "KTidYfH0ckWxJGs5qzUBTQ", "decal": ""}, {"item": "3dec4865-cf23-4c19-a460-dd2db80ee851", "prefab": "3dec4865-cf23-4c19-a460-dd2db80ee851", "mask": "", "swatch": "", "decal": ""}, {"item": "3dec4865-cf23-4c19-a460-dd2db80ee851", "prefab": "3dec4865-cf23-4c19-a460-dd2db80ee851", "mask": "8ffce0d6-d03e-4097-bc46-09b8894bc1d3", "swatch": "e821aa74-2d47-4388-bcfb-1893b8a83a52", "decal": ""}, {"item": "3dec4865-cf23-4c19-a460-dd2db80ee851", "prefab": "3dec4865-cf23-4c19-a460-dd2db80ee851", "mask": "8ffce0d6-d03e-4097-bc46-09b8894bc1d3", "swatch": "fed760b2-15e2-46b2-b1e3-cc3c8d780d5d", "decal": ""}, {"item": "3dec4865-cf23-4c19-a460-dd2db80ee851", "prefab": "3dec4865-cf23-4c19-a460-dd2db80ee851", "mask": "8ffce0d6-d03e-4097-bc46-09b8894bc1d3", "swatch": "90869e86-cbc5-4024-9d08-fcdc41c64eb5", "decal": ""}, {"item": "3dec4865-cf23-4c19-a460-dd2db80ee851", "prefab": "3dec4865-cf23-4c19-a460-dd2db80ee851", "mask": "8ffce0d6-d03e-4097-bc46-09b8894bc1d3", "swatch": "076c96ae-9f9b-45bf-9dc7-ce73d5ddf422", "decal": ""}, {"item": "3dec4865-cf23-4c19-a460-dd2db80ee851", "prefab": "3dec4865-cf23-4c19-a460-dd2db80ee851", "mask": "8ffce0d6-d03e-4097-bc46-09b8894bc1d3", "swatch": "002b07bb-4256-44ce-8ca4-25510e70b6b5", "decal": ""}, {"item": "3dec4865-cf23-4c19-a460-dd2db80ee851", "prefab": "3dec4865-cf23-4c19-a460-dd2db80ee851", "mask": "8ffce0d6-d03e-4097-bc46-09b8894bc1d3", "swatch": "bzYdPb8GWUS-CgT4wrziIA", "decal": ""}, {"item": "94eb7ef3-528f-4f59-92a0-48b0ec287527", "prefab": "94eb7ef3-528f-4f59-92a0-48b0ec287527", "mask": "", "swatch": "", "decal": ""}, {"item": "94eb7ef3-528f-4f59-92a0-48b0ec287527", "prefab": "94eb7ef3-528f-4f59-92a0-48b0ec287527", "mask": "av4PwLz6YkmpBRImQZgn4Q", "swatch": "whEHo6Rhr0CxsDBsBD32WA", "decal": ""}, {"item": "4763436c-2dcc-482a-bf37-a6b16d7aae6e", "prefab": "4763436c-2dcc-482a-bf37-a6b16d7aae6e", "mask": "", "swatch": "", "decal": ""}, {"item": "4763436c-2dcc-482a-bf37-a6b16d7aae6e", "prefab": "4763436c-2dcc-482a-bf37-a6b16d7aae6e", "mask": "7634b7ef-9607-497a-b41f-c0191dc75998", "swatch": "c8cc9589-4831-4e73-a865-ef8c139a2ba7", "decal": ""}, {"item": "4763436c-2dcc-482a-bf37-a6b16d7aae6e", "prefab": "4763436c-2dcc-482a-bf37-a6b16d7aae6e", "mask": "7634b7ef-9607-497a-b41f-c0191dc75998", "swatch": "a36d252b-16ac-4a82-bece-de145147e7d9", "decal": ""}, {"item": "4763436c-2dcc-482a-bf37-a6b16d7aae6e", "prefab": "4763436c-2dcc-482a-bf37-a6b16d7aae6e", "mask": "7634b7ef-9607-497a-b41f-c0191dc75998", "swatch": "a089bcc6-798d-4777-98e2-7afd290dc6b6", "decal": ""}, {"item": "4763436c-2dcc-482a-bf37-a6b16d7aae6e", "prefab": "4763436c-2dcc-482a-bf37-a6b16d7aae6e", "mask": "7634b7ef-9607-497a-b41f-c0191dc75998", "swatch": "d78643e4-c0d2-4836-bf22-b5a0e2d65b28", "decal": ""}, {"item": "4763436c-2dcc-482a-bf37-a6b16d7aae6e", "prefab": "4763436c-2dcc-482a-bf37-a6b16d7aae6e", "mask": "7634b7ef-9607-497a-b41f-c0191dc75998", "swatch": "68273c9e-96d6-4ddb-aaab-d99229e864d5", "decal": ""}, {"item": "4763436c-2dcc-482a-bf37-a6b16d7aae6e", "prefab": "4763436c-2dcc-482a-bf37-a6b16d7aae6e", "mask": "7634b7ef-9607-497a-b41f-c0191dc75998", "swatch": "4ElTihrMA0ipcgeb9UG01Q", "decal": ""}, {"item": "qxovV6Vym0ufL2cTCyKjOQ", "prefab": "qxovV6Vym0ufL2cTCyKjOQ", "mask": "", "swatch": "", "decal": ""}, {"item": "BR0mZKsKaE-t4R8CzEwuyg", "prefab": "BR0mZKsKaE-t4R8CzEwuyg", "mask": "", "swatch": "", "decal": ""}, {"item": "BR0mZKsKaE-t4R8CzEwuyg", "prefab": "BR0mZKsKaE-t4R8CzEwuyg", "mask": "FHB5XHwNWka7aUt5km3LNg", "swatch": "Q1knTl-n-EeAUwotFspNew", "decal": ""}, {"item": "_OWVy3z6iU-M3-zbQgSLig", "prefab": "_OWVy3z6iU-M3-zbQgSLig", "mask": "", "swatch": "", "decal": ""}, {"item": "_OWVy3z6iU-M3-zbQgSLig", "prefab": "_OWVy3z6iU-M3-zbQgSLig", "mask": "N5wN1l0tYEau5np-9PoIiA", "swatch": "D0tP58wC30mJ00vNxw8hqA", "decal": ""}, {"item": "_OWVy3z6iU-M3-zbQgSLig", "prefab": "_OWVy3z6iU-M3-zbQgSLig", "mask": "N5wN1l0tYEau5np-9PoIiA", "swatch": "Vxq6v55a_kGPODzwB5MC1g", "decal": ""}, {"item": "_OWVy3z6iU-M3-zbQgSLig", "prefab": "_OWVy3z6iU-M3-zbQgSLig", "mask": "N5wN1l0tYEau5np-9PoIiA", "swatch": "iJW8XGJfU0G12iXarjxhdQ", "decal": ""}, {"item": "3a790be3-2937-44d4-be01-b5d65353bd3d", "prefab": "3a790be3-2937-44d4-be01-b5d65353bd3d", "mask": "", "swatch": "", "decal": ""}, {"item": "3a790be3-2937-44d4-be01-b5d65353bd3d", "prefab": "3a790be3-2937-44d4-be01-b5d65353bd3d", "mask": "jQjfiz2i6kaPfrkjnBGxBA", "swatch": "SWvJWvMDpE-it5PFoPgERg", "decal": ""}, {"item": "3a790be3-2937-44d4-be01-b5d65353bd3d", "prefab": "3a790be3-2937-44d4-be01-b5d65353bd3d", "mask": "_yvss6L59US4-8QYZgcCdA", "swatch": "afWY5VGiF0isGn6qpo4KeQ", "decal": ""}, {"item": "3a790be3-2937-44d4-be01-b5d65353bd3d", "prefab": "3a790be3-2937-44d4-be01-b5d65353bd3d", "mask": "_yvss6L59US4-8QYZgcCdA", "swatch": "tyzdtSkR9k-YCnjNvPAT6w", "decal": ""}, {"item": "3a790be3-2937-44d4-be01-b5d65353bd3d", "prefab": "3a790be3-2937-44d4-be01-b5d65353bd3d", "mask": "xq7bizx6UEGaMTHB9zI3pw", "swatch": "09Cg6dOq2kC2-tI5yyGSYg", "decal": ""}, {"item": "wy2cfgb8mUqjMrIOwHH2ew", "prefab": "wy2cfgb8mUqjMrIOwHH2ew", "mask": "", "swatch": "", "decal": ""}, {"item": "wy2cfgb8mUqjMrIOwHH2ew", "prefab": "wy2cfgb8mUqjMrIOwHH2ew", "mask": "IQvHmZ8ei02hVuXZiXb6TA", "swatch": "Hk6svQ5Sr0GDKLxOyktk9w", "decal": ""}, {"item": "wy2cfgb8mUqjMrIOwHH2ew", "prefab": "wy2cfgb8mUqjMrIOwHH2ew", "mask": "5aX524Lfok-0dC3UFv-OkQ", "swatch": "0ZKR_qxrWkiWkjpot8s6Cg", "decal": ""}, {"item": "1974dd48-aa44-434f-879a-eaff0c7c06e6", "prefab": "1974dd48-aa44-434f-879a-eaff0c7c06e6", "mask": "", "swatch": "", "decal": ""}, {"item": "1974dd48-aa44-434f-879a-eaff0c7c06e6", "prefab": "1974dd48-aa44-434f-879a-eaff0c7c06e6", "mask": "5b8d9490-e5bc-480e-91bc-65d1797b52ad", "swatch": "64545dcc-a8eb-4c69-a539-78ee73a2d327", "decal": ""}, {"item": "1974dd48-aa44-434f-879a-eaff0c7c06e6", "prefab": "1974dd48-aa44-434f-879a-eaff0c7c06e6", "mask": "5b8d9490-e5bc-480e-91bc-65d1797b52ad", "swatch": "e02838f8-4029-455a-b35c-dd40f972b05b", "decal": ""}, {"item": "1974dd48-aa44-434f-879a-eaff0c7c06e6", "prefab": "1974dd48-aa44-434f-879a-eaff0c7c06e6", "mask": "5b8d9490-e5bc-480e-91bc-65d1797b52ad", "swatch": "71e5a0fe-fc8d-420b-af3b-e74160e0d1f3", "decal": ""}, {"item": "1974dd48-aa44-434f-879a-eaff0c7c06e6", "prefab": "1974dd48-aa44-434f-879a-eaff0c7c06e6", "mask": "5b8d9490-e5bc-480e-91bc-65d1797b52ad", "swatch": "909ed249-ce09-4304-8b02-56794fa7567d", "decal": ""}, {"item": "1974dd48-aa44-434f-879a-eaff0c7c06e6", "prefab": "1974dd48-aa44-434f-879a-eaff0c7c06e6", "mask": "5b8d9490-e5bc-480e-91bc-65d1797b52ad", "swatch": "Tbrc2MlSlE6mKua8qSoluQ", "decal": ""}, {"item": "1974dd48-aa44-434f-879a-eaff0c7c06e6", "prefab": "1974dd48-aa44-434f-879a-eaff0c7c06e6", "mask": "5b8d9490-e5bc-480e-91bc-65d1797b52ad", "swatch": "aBQ3R9boek-mYeSYSNvDbQ", "decal": ""}, {"item": "22ef1089-d073-4fe8-b038-6f34adf0b85f", "prefab": "22ef1089-d073-4fe8-b038-6f34adf0b85f", "mask": "", "swatch": "", "decal": ""}, {"item": "22ef1089-d073-4fe8-b038-6f34adf0b85f", "prefab": "22ef1089-d073-4fe8-b038-6f34adf0b85f", "mask": "47514b9b-0d17-4727-97ed-dab67e7fe031", "swatch": "357eb160-360b-4a63-840d-65d11cc8ffc2", "decal": ""}, {"item": "22ef1089-d073-4fe8-b038-6f34adf0b85f", "prefab": "22ef1089-d073-4fe8-b038-6f34adf0b85f", "mask": "47514b9b-0d17-4727-97ed-dab67e7fe031", "swatch": "a38254c0-00e3-429b-9d5d-b9e6a9812e69", "decal": ""}, {"item": "22ef1089-d073-4fe8-b038-6f34adf0b85f", "prefab": "22ef1089-d073-4fe8-b038-6f34adf0b85f", "mask": "47514b9b-0d17-4727-97ed-dab67e7fe031", "swatch": "d3a9b576-5433-4305-bd96-b08820c09212", "decal": ""}, {"item": "22ef1089-d073-4fe8-b038-6f34adf0b85f", "prefab": "22ef1089-d073-4fe8-b038-6f34adf0b85f", "mask": "47514b9b-0d17-4727-97ed-dab67e7fe031", "swatch": "7afe1bb0-5ca9-4263-8f85-49d3a18353c7", "decal": ""}, {"item": "22ef1089-d073-4fe8-b038-6f34adf0b85f", "prefab": "22ef1089-d073-4fe8-b038-6f34adf0b85f", "mask": "47514b9b-0d17-4727-97ed-dab67e7fe031", "swatch": "RKmRHk2N_U2gd91nybo6vg", "decal": ""}, {"item": "22ef1089-d073-4fe8-b038-6f34adf0b85f", "prefab": "22ef1089-d073-4fe8-b038-6f34adf0b85f", "mask": "47514b9b-0d17-4727-97ed-dab67e7fe031", "swatch": "d3KmqVuMQkCYK6sVTMOTOA", "decal": ""}, {"item": "ecc1dbe6-ca06-4564-b2a6-30956194d1e9", "prefab": "ecc1dbe6-ca06-4564-b2a6-30956194d1e9", "mask": "", "swatch": "", "decal": ""}, {"item": "ecc1dbe6-ca06-4564-b2a6-30956194d1e9", "prefab": "ecc1dbe6-ca06-4564-b2a6-30956194d1e9", "mask": "0b2395e1-ebcc-47e9-aaf1-faf9e9cec4cd", "swatch": "8377ab96-c908-457f-9fee-b784c9a759f3", "decal": ""}, {"item": "ecc1dbe6-ca06-4564-b2a6-30956194d1e9", "prefab": "ecc1dbe6-ca06-4564-b2a6-30956194d1e9", "mask": "0b2395e1-ebcc-47e9-aaf1-faf9e9cec4cd", "swatch": "1b1d08f2-12ca-43dd-a44f-ea2820b919b4", "decal": ""}, {"item": "ecc1dbe6-ca06-4564-b2a6-30956194d1e9", "prefab": "ecc1dbe6-ca06-4564-b2a6-30956194d1e9", "mask": "0b2395e1-ebcc-47e9-aaf1-faf9e9cec4cd", "swatch": "67bcca75-4ab1-4964-8688-9908c464d355", "decal": ""}, {"item": "ecc1dbe6-ca06-4564-b2a6-30956194d1e9", "prefab": "ecc1dbe6-ca06-4564-b2a6-30956194d1e9", "mask": "0b2395e1-ebcc-47e9-aaf1-faf9e9cec4cd", "swatch": "0ecb8a2a-cffc-47db-aeda-fb0684aef1e5", "decal": ""}, {"item": "ecc1dbe6-ca06-4564-b2a6-30956194d1e9", "prefab": "ecc1dbe6-ca06-4564-b2a6-30956194d1e9", "mask": "0b2395e1-ebcc-47e9-aaf1-faf9e9cec4cd", "swatch": "484b6c13-af22-4ad5-8c43-34c0de095d49", "decal": ""}, {"item": "ecc1dbe6-ca06-4564-b2a6-30956194d1e9", "prefab": "ecc1dbe6-ca06-4564-b2a6-30956194d1e9", "mask": "0b2395e1-ebcc-47e9-aaf1-faf9e9cec4cd", "swatch": "51ef8d39-2b94-4f9e-9620-07b6b0a913a5", "decal": ""}, {"item": "ecc1dbe6-ca06-4564-b2a6-30956194d1e9", "prefab": "ecc1dbe6-ca06-4564-b2a6-30956194d1e9", "mask": "0b2395e1-ebcc-47e9-aaf1-faf9e9cec4cd", "swatch": "dee70c38-7a99-4c2b-9181-665f1bf75aca", "decal": ""}, {"item": "ecc1dbe6-ca06-4564-b2a6-30956194d1e9", "prefab": "ecc1dbe6-ca06-4564-b2a6-30956194d1e9", "mask": "0b2395e1-ebcc-47e9-aaf1-faf9e9cec4cd", "swatch": "7d8e55fe-3c34-4b4b-9753-0021f6cc6454", "decal": ""}, {"item": "ecc1dbe6-ca06-4564-b2a6-30956194d1e9", "prefab": "ecc1dbe6-ca06-4564-b2a6-30956194d1e9", "mask": "0b2395e1-ebcc-47e9-aaf1-faf9e9cec4cd", "swatch": "cbe29e9f-f2ac-47fb-97e1-8bad16abb89d", "decal": ""}, {"item": "ecc1dbe6-ca06-4564-b2a6-30956194d1e9", "prefab": "ecc1dbe6-ca06-4564-b2a6-30956194d1e9", "mask": "0b2395e1-ebcc-47e9-aaf1-faf9e9cec4cd", "swatch": "f8b0cfe8-e129-4578-8bb5-f60af5d38599", "decal": ""}, {"item": "ecc1dbe6-ca06-4564-b2a6-30956194d1e9", "prefab": "ecc1dbe6-ca06-4564-b2a6-30956194d1e9", "mask": "3290f148-1bd1-4ffd-a947-61fd0529af05", "swatch": "0809a5cf-a0f3-4614-9f98-71518524518a", "decal": ""}, {"item": "71921831-ba6f-408b-a00e-2fd97663636f", "prefab": "71921831-ba6f-408b-a00e-2fd97663636f", "mask": "", "swatch": "", "decal": ""}, {"item": "71921831-ba6f-408b-a00e-2fd97663636f", "prefab": "71921831-ba6f-408b-a00e-2fd97663636f", "mask": "0b2395e1-ebcc-47e9-aaf1-faf9e9cec4cd", "swatch": "7d8e55fe-3c34-4b4b-9753-0021f6cc6454", "decal": ""}, {"item": "71921831-ba6f-408b-a00e-2fd97663636f", "prefab": "71921831-ba6f-408b-a00e-2fd97663636f", "mask": "0b2395e1-ebcc-47e9-aaf1-faf9e9cec4cd", "swatch": "1b1d08f2-12ca-43dd-a44f-ea2820b919b4", "decal": ""}, {"item": "13e70c80-69a4-4ad5-90e5-4dd5e03da677", "prefab": "13e70c80-69a4-4ad5-90e5-4dd5e03da677", "mask": "", "swatch": "", "decal": ""}, {"item": "899f1d17-eee1-4b7d-a112-fbbba597e738", "prefab": "899f1d17-eee1-4b7d-a112-fbbba597e738", "mask": "", "swatch": "", "decal": ""}, {"item": "a4c26527-8f75-44da-b150-7aaf8ef739c1", "prefab": "a4c26527-8f75-44da-b150-7aaf8ef739c1", "mask": "", "swatch": "", "decal": ""}, {"item": "17236d76-e3b6-42f6-9179-6fcf0479436f", "prefab": "17236d76-e3b6-42f6-9179-6fcf0479436f", "mask": "", "swatch": "", "decal": ""}, {"item": "17236d76-e3b6-42f6-9179-6fcf0479436f", "prefab": "17236d76-e3b6-42f6-9179-6fcf0479436f", "mask": "B-sIpWNOfEigMeQF-WVR1g", "swatch": "6t2rCb6WWEW6pJbcjsAFCw", "decal": ""}, {"item": "17236d76-e3b6-42f6-9179-6fcf0479436f", "prefab": "17236d76-e3b6-42f6-9179-6fcf0479436f", "mask": "B-sIpWNOfEigMeQF-WVR1g", "swatch": "zu0Jk24WTkan6XWSwGU1Uw", "decal": ""}, {"item": "17236d76-e3b6-42f6-9179-6fcf0479436f", "prefab": "17236d76-e3b6-42f6-9179-6fcf0479436f", "mask": "B-sIpWNOfEigMeQF-WVR1g", "swatch": "i1XbjBqTu0CILIuJClWimA", "decal": ""}, {"item": "17236d76-e3b6-42f6-9179-6fcf0479436f", "prefab": "17236d76-e3b6-42f6-9179-6fcf0479436f", "mask": "B-sIpWNOfEigMeQF-WVR1g", "swatch": "3frH6KxZ9kGUYkjeCDpylQ", "decal": ""}, {"item": "17236d76-e3b6-42f6-9179-6fcf0479436f", "prefab": "17236d76-e3b6-42f6-9179-6fcf0479436f", "mask": "B-sIpWNOfEigMeQF-WVR1g", "swatch": "Lf4FjROSAEqXedFxpXFQfg", "decal": ""}, {"item": "459ec62c-67db-4b72-9e29-0ad3ecd0b737", "prefab": "459ec62c-67db-4b72-9e29-0ad3ecd0b737", "mask": "", "swatch": "", "decal": ""}, {"item": "459ec62c-67db-4b72-9e29-0ad3ecd0b737", "prefab": "459ec62c-67db-4b72-9e29-0ad3ecd0b737", "mask": "PZHohDVX0U2frX13N4KYDQ", "swatch": "Euf1h1qsZ02evDsyoHYbPQ", "decal": ""}, {"item": "459ec62c-67db-4b72-9e29-0ad3ecd0b737", "prefab": "459ec62c-67db-4b72-9e29-0ad3ecd0b737", "mask": "PZHohDVX0U2frX13N4KYDQ", "swatch": "lsyYlxY9o0a3TbUrHkxeyQ", "decal": ""}, {"item": "459ec62c-67db-4b72-9e29-0ad3ecd0b737", "prefab": "459ec62c-67db-4b72-9e29-0ad3ecd0b737", "mask": "PZHohDVX0U2frX13N4KYDQ", "swatch": "JVZHZm6nGEqE2zEEPoHbwA", "decal": ""}, {"item": "459ec62c-67db-4b72-9e29-0ad3ecd0b737", "prefab": "459ec62c-67db-4b72-9e29-0ad3ecd0b737", "mask": "PZHohDVX0U2frX13N4KYDQ", "swatch": "zlqLrGEa8E2WW9ey_szREA", "decal": ""}, {"item": "459ec62c-67db-4b72-9e29-0ad3ecd0b737", "prefab": "459ec62c-67db-4b72-9e29-0ad3ecd0b737", "mask": "PZHohDVX0U2frX13N4KYDQ", "swatch": "L2iFQcL170OajqfW7t6sVw", "decal": ""}, {"item": "ca7a3ccb-2bc9-4c93-a6a8-6f22b38526af", "prefab": "ca7a3ccb-2bc9-4c93-a6a8-6f22b38526af", "mask": "", "swatch": "", "decal": ""}, {"item": "ca7a3ccb-2bc9-4c93-a6a8-6f22b38526af", "prefab": "ca7a3ccb-2bc9-4c93-a6a8-6f22b38526af", "mask": "ovy_3j_qwE-7mmks1Z47ig", "swatch": "sX0ZZDcCUkyFSJISfvq0vg", "decal": ""}, {"item": "ca7a3ccb-2bc9-4c93-a6a8-6f22b38526af", "prefab": "ca7a3ccb-2bc9-4c93-a6a8-6f22b38526af", "mask": "ovy_3j_qwE-7mmks1Z47ig", "swatch": "TnEDe1QoMUCUdqPL_7CxTA", "decal": ""}, {"item": "ca7a3ccb-2bc9-4c93-a6a8-6f22b38526af", "prefab": "ca7a3ccb-2bc9-4c93-a6a8-6f22b38526af", "mask": "ovy_3j_qwE-7mmks1Z47ig", "swatch": "AC3zfm38JEyBZWuXDh4Ukg", "decal": ""}, {"item": "ca7a3ccb-2bc9-4c93-a6a8-6f22b38526af", "prefab": "ca7a3ccb-2bc9-4c93-a6a8-6f22b38526af", "mask": "ovy_3j_qwE-7mmks1Z47ig", "swatch": "RfZNDPUok0WyQEa3Ko08ow", "decal": ""}, {"item": "ca7a3ccb-2bc9-4c93-a6a8-6f22b38526af", "prefab": "ca7a3ccb-2bc9-4c93-a6a8-6f22b38526af", "mask": "ovy_3j_qwE-7mmks1Z47ig", "swatch": "dyEaPloE8kazWC7Zblv8uQ", "decal": ""}, {"item": "ff5c62b3-4f41-4acd-bfd8-8db00de4f36c", "prefab": "ff5c62b3-4f41-4acd-bfd8-8db00de4f36c", "mask": "", "swatch": "", "decal": ""}, {"item": "ff5c62b3-4f41-4acd-bfd8-8db00de4f36c", "prefab": "ff5c62b3-4f41-4acd-bfd8-8db00de4f36c", "mask": "VDn1hO6ahky0GDJt5dRmyg", "swatch": "PR5dh2jf2kqGLO0lXgA69A", "decal": ""}, {"item": "f684d86f-4a75-4a9a-bc0a-cf58f36c91c5", "prefab": "f684d86f-4a75-4a9a-bc0a-cf58f36c91c5", "mask": "", "swatch": "", "decal": ""}, {"item": "87792b6e-1468-45e1-b28c-83f27ada7299", "prefab": "87792b6e-1468-45e1-b28c-83f27ada7299", "mask": "", "swatch": "", "decal": ""}, {"item": "e2620c2c-c28e-45ce-a83c-0588329b9c81", "prefab": "e2620c2c-c28e-45ce-a83c-0588329b9c81", "mask": "", "swatch": "", "decal": ""}, {"item": "af33bdb1-b7fa-4fe6-b37a-b1fe6a420879", "prefab": "af33bdb1-b7fa-4fe6-b37a-b1fe6a420879", "mask": "", "swatch": "", "decal": ""}, {"item": "af33bdb1-b7fa-4fe6-b37a-b1fe6a420879", "prefab": "af33bdb1-b7fa-4fe6-b37a-b1fe6a420879", "mask": "-lYK7LhYyEy6wqL1tIE9cg", "swatch": "PR5dh2jf2kqGLO0lXgA69A", "decal": ""}, {"item": "d57cf97e-b5d2-4e47-8231-a226a161f6e3", "prefab": "d57cf97e-b5d2-4e47-8231-a226a161f6e3", "mask": "", "swatch": "", "decal": ""}, {"item": "973dc863-b9a5-40d8-8f15-4096e75bd2cb", "prefab": "973dc863-b9a5-40d8-8f15-4096e75bd2cb", "mask": "", "swatch": "", "decal": ""}, {"item": "1beaf288-b5a5-475c-8149-c8f3d1146083", "prefab": "1beaf288-b5a5-475c-8149-c8f3d1146083", "mask": "", "swatch": "", "decal": ""}, {"item": "1beaf288-b5a5-475c-8149-c8f3d1146083", "prefab": "1beaf288-b5a5-475c-8149-c8f3d1146083", "mask": "rgzG7sfrlkqd9EO13LNmpQ", "swatch": "sX0ZZDcCUkyFSJISfvq0vg", "decal": ""}, {"item": "1beaf288-b5a5-475c-8149-c8f3d1146083", "prefab": "1beaf288-b5a5-475c-8149-c8f3d1146083", "mask": "rgzG7sfrlkqd9EO13LNmpQ", "swatch": "TnEDe1QoMUCUdqPL_7CxTA", "decal": ""}, {"item": "1beaf288-b5a5-475c-8149-c8f3d1146083", "prefab": "1beaf288-b5a5-475c-8149-c8f3d1146083", "mask": "rgzG7sfrlkqd9EO13LNmpQ", "swatch": "AC3zfm38JEyBZWuXDh4Ukg", "decal": ""}, {"item": "1beaf288-b5a5-475c-8149-c8f3d1146083", "prefab": "1beaf288-b5a5-475c-8149-c8f3d1146083", "mask": "rgzG7sfrlkqd9EO13LNmpQ", "swatch": "RfZNDPUok0WyQEa3Ko08ow", "decal": ""}, {"item": "1beaf288-b5a5-475c-8149-c8f3d1146083", "prefab": "1beaf288-b5a5-475c-8149-c8f3d1146083", "mask": "rgzG7sfrlkqd9EO13LNmpQ", "swatch": "dyEaPloE8kazWC7Zblv8uQ", "decal": ""}, {"item": "c8a97aeb-e8dc-40ba-b6bc-a7c7be907381", "prefab": "c8a97aeb-e8dc-40ba-b6bc-a7c7be907381", "mask": "", "swatch": "", "decal": ""}, {"item": "1fcaf4fd-9012-407e-b691-fbc3ccf22421", "prefab": "1fcaf4fd-9012-407e-b691-fbc3ccf22421", "mask": "", "swatch": "", "decal": ""}, {"item": "1fcaf4fd-9012-407e-b691-fbc3ccf22421", "prefab": "1fcaf4fd-9012-407e-b691-fbc3ccf22421", "mask": "uTkbswCedUGyuZcaX0W6Ww", "swatch": "S0AKRB_m2k-I7snHnUigtw", "decal": ""}, {"item": "3c9acb9e-3353-4359-aa5a-ae9512b086b4", "prefab": "3c9acb9e-3353-4359-aa5a-ae9512b086b4", "mask": "", "swatch": "", "decal": ""}, {"item": "3c9acb9e-3353-4359-aa5a-ae9512b086b4", "prefab": "3c9acb9e-3353-4359-aa5a-ae9512b086b4", "mask": "bNA44H2wzUy-FkWgyOq1Kw", "swatch": "dro7VLV8BkGjqpPAbJheYA", "decal": ""}, {"item": "d960b47d-f8bd-4c2e-813c-a6a09876ea18", "prefab": "d960b47d-f8bd-4c2e-813c-a6a09876ea18", "mask": "", "swatch": "", "decal": ""}, {"item": "d960b47d-f8bd-4c2e-813c-a6a09876ea18", "prefab": "d960b47d-f8bd-4c2e-813c-a6a09876ea18", "mask": "xCpdHx1iM06Gg0tYoGv0DA", "swatch": "TZ3oQBp_3kWY4Vzscgv5Wg", "decal": ""}, {"item": "df0e06c4-8ba6-4d7a-bfb2-3759d8fe88c7", "prefab": "df0e06c4-8ba6-4d7a-bfb2-3759d8fe88c7", "mask": "", "swatch": "", "decal": ""}, {"item": "df0e06c4-8ba6-4d7a-bfb2-3759d8fe88c7", "prefab": "df0e06c4-8ba6-4d7a-bfb2-3759d8fe88c7", "mask": "eZGiYEx6sk2PsN39RzQwlQ", "swatch": "s6DQxJ3gY0CG8L7JmcmRTQ", "decal": ""}, {"item": "df0e06c4-8ba6-4d7a-bfb2-3759d8fe88c7", "prefab": "df0e06c4-8ba6-4d7a-bfb2-3759d8fe88c7", "mask": "eZGiYEx6sk2PsN39RzQwlQ", "swatch": "DJnKyNXtuEW1tbKBFscIng", "decal": ""}, {"item": "df0e06c4-8ba6-4d7a-bfb2-3759d8fe88c7", "prefab": "df0e06c4-8ba6-4d7a-bfb2-3759d8fe88c7", "mask": "eZGiYEx6sk2PsN39RzQwlQ", "swatch": "ZY0rVNemjEuEMGMjuPNZyA", "decal": ""}, {"item": "df0e06c4-8ba6-4d7a-bfb2-3759d8fe88c7", "prefab": "df0e06c4-8ba6-4d7a-bfb2-3759d8fe88c7", "mask": "eZGiYEx6sk2PsN39RzQwlQ", "swatch": "jhPUIkf5bkinMSsqPTK3fg", "decal": ""}, {"item": "28e5dd74-7963-42cf-aa68-f8005aaf312e", "prefab": "28e5dd74-7963-42cf-aa68-f8005aaf312e", "mask": "", "swatch": "", "decal": ""}, {"item": "28e5dd74-7963-42cf-aa68-f8005aaf312e", "prefab": "28e5dd74-7963-42cf-aa68-f8005aaf312e", "mask": "DfyODsHfIESXxVjcfZmBOA", "swatch": "s6DQxJ3gY0CG8L7JmcmRTQ", "decal": ""}, {"item": "28e5dd74-7963-42cf-aa68-f8005aaf312e", "prefab": "28e5dd74-7963-42cf-aa68-f8005aaf312e", "mask": "DfyODsHfIESXxVjcfZmBOA", "swatch": "DJnKyNXtuEW1tbKBFscIng", "decal": ""}, {"item": "28e5dd74-7963-42cf-aa68-f8005aaf312e", "prefab": "28e5dd74-7963-42cf-aa68-f8005aaf312e", "mask": "DfyODsHfIESXxVjcfZmBOA", "swatch": "ZY0rVNemjEuEMGMjuPNZyA", "decal": ""}, {"item": "28e5dd74-7963-42cf-aa68-f8005aaf312e", "prefab": "28e5dd74-7963-42cf-aa68-f8005aaf312e", "mask": "DfyODsHfIESXxVjcfZmBOA", "swatch": "jhPUIkf5bkinMSsqPTK3fg", "decal": ""}, {"item": "6a98bbb6-bbe0-4dc3-97a9-e253dc506a2b", "prefab": "6a98bbb6-bbe0-4dc3-97a9-e253dc506a2b", "mask": "", "swatch": "", "decal": ""}, {"item": "6a98bbb6-bbe0-4dc3-97a9-e253dc506a2b", "prefab": "6a98bbb6-bbe0-4dc3-97a9-e253dc506a2b", "mask": "GXS9LQgPU0mJIEuXo_Oocw", "swatch": "s6DQxJ3gY0CG8L7JmcmRTQ", "decal": ""}, {"item": "6a98bbb6-bbe0-4dc3-97a9-e253dc506a2b", "prefab": "6a98bbb6-bbe0-4dc3-97a9-e253dc506a2b", "mask": "GXS9LQgPU0mJIEuXo_Oocw", "swatch": "DJnKyNXtuEW1tbKBFscIng", "decal": ""}, {"item": "6a98bbb6-bbe0-4dc3-97a9-e253dc506a2b", "prefab": "6a98bbb6-bbe0-4dc3-97a9-e253dc506a2b", "mask": "GXS9LQgPU0mJIEuXo_Oocw", "swatch": "ZY0rVNemjEuEMGMjuPNZyA", "decal": ""}, {"item": "6a98bbb6-bbe0-4dc3-97a9-e253dc506a2b", "prefab": "6a98bbb6-bbe0-4dc3-97a9-e253dc506a2b", "mask": "GXS9LQgPU0mJIEuXo_Oocw", "swatch": "jhPUIkf5bkinMSsqPTK3fg", "decal": ""}, {"item": "bb8602d4-f91f-4e35-9c88-1cdf3a04ff0a", "prefab": "bb8602d4-f91f-4e35-9c88-1cdf3a04ff0a", "mask": "", "swatch": "", "decal": ""}, {"item": "2786fc22-3d6c-440d-afcc-15890c061577", "prefab": "2786fc22-3d6c-440d-afcc-15890c061577", "mask": "", "swatch": "", "decal": ""}, {"item": "37668d3d-c9f4-4bd6-81d4-a4f64ba2d61b", "prefab": "37668d3d-c9f4-4bd6-81d4-a4f64ba2d61b", "mask": "", "swatch": "", "decal": ""}, {"item": "9168b3f9-c7e5-40b6-bb2c-9e708596e675", "prefab": "9168b3f9-c7e5-40b6-bb2c-9e708596e675", "mask": "", "swatch": "", "decal": ""}, {"item": "de19b239-3ed0-4f03-acb6-da69b5aa52ea", "prefab": "de19b239-3ed0-4f03-acb6-da69b5aa52ea", "mask": "", "swatch": "", "decal": ""}, {"item": "5f1bc3c2-dfbb-4258-9b10-608535e34db2", "prefab": "5f1bc3c2-dfbb-4258-9b10-608535e34db2", "mask": "", "swatch": "", "decal": ""}, {"item": "8b065e2a-b106-4f11-b415-4c47ce87993e", "prefab": "8b065e2a-b106-4f11-b415-4c47ce87993e", "mask": "", "swatch": "", "decal": ""}, {"item": "8b065e2a-b106-4f11-b415-4c47ce87993e", "prefab": "8b065e2a-b106-4f11-b415-4c47ce87993e", "mask": "PMGQ8fLc2UuJXMy42Ati-w", "swatch": "yA0KhVg19kWqG1UkyxQogQ", "decal": ""}, {"item": "8b065e2a-b106-4f11-b415-4c47ce87993e", "prefab": "8b065e2a-b106-4f11-b415-4c47ce87993e", "mask": "PMGQ8fLc2UuJXMy42Ati-w", "swatch": "P9bBSL0YLEyjN8PxMfgbKQ", "decal": ""}, {"item": "8b065e2a-b106-4f11-b415-4c47ce87993e", "prefab": "8b065e2a-b106-4f11-b415-4c47ce87993e", "mask": "sxWOOK1RwUOp5YqZ7a36xg", "swatch": "o2WfRyjJy0CKoUg8YIxNjQ", "decal": ""}, {"item": "8b065e2a-b106-4f11-b415-4c47ce87993e", "prefab": "8b065e2a-b106-4f11-b415-4c47ce87993e", "mask": "sxWOOK1RwUOp5YqZ7a36xg", "swatch": "bgRKH1wfqkWVHcuSnFYnlQ", "decal": ""}, {"item": "8b065e2a-b106-4f11-b415-4c47ce87993e", "prefab": "8b065e2a-b106-4f11-b415-4c47ce87993e", "mask": "uZjxYOMa10GzJ9wQkRge2w", "swatch": "sscD2O3LEUOAKkqjGpGe8g", "decal": ""}, {"item": "8b065e2a-b106-4f11-b415-4c47ce87993e", "prefab": "8b065e2a-b106-4f11-b415-4c47ce87993e", "mask": "uZjxYOMa10GzJ9wQkRge2w", "swatch": "hRxyLsKfsESFAsXAyZQjoA", "decal": ""}, {"item": "8b065e2a-b106-4f11-b415-4c47ce87993e", "prefab": "8b065e2a-b106-4f11-b415-4c47ce87993e", "mask": "uZjxYOMa10GzJ9wQkRge2w", "swatch": "kgCKjtSjFEmFq9ez2llxKQ", "decal": ""}, {"item": "8b065e2a-b106-4f11-b415-4c47ce87993e", "prefab": "8b065e2a-b106-4f11-b415-4c47ce87993e", "mask": "uZjxYOMa10GzJ9wQkRge2w", "swatch": "KaZqNx5VKkarRrOJcTuD9A", "decal": ""}, {"item": "973d84ee-5b88-475c-b579-a76c34a7c533", "prefab": "973d84ee-5b88-475c-b579-a76c34a7c533", "mask": "", "swatch": "", "decal": ""}, {"item": "973d84ee-5b88-475c-b579-a76c34a7c533", "prefab": "973d84ee-5b88-475c-b579-a76c34a7c533", "mask": "yBNsYcZXzEiuR0u3tWnnWQ", "swatch": "yA0KhVg19kWqG1UkyxQogQ", "decal": ""}, {"item": "973d84ee-5b88-475c-b579-a76c34a7c533", "prefab": "973d84ee-5b88-475c-b579-a76c34a7c533", "mask": "yBNsYcZXzEiuR0u3tWnnWQ", "swatch": "P9bBSL0YLEyjN8PxMfgbKQ", "decal": ""}, {"item": "973d84ee-5b88-475c-b579-a76c34a7c533", "prefab": "973d84ee-5b88-475c-b579-a76c34a7c533", "mask": "sHyNs-dpc0-_Ah-HJVczcw", "swatch": "o2WfRyjJy0CKoUg8YIxNjQ", "decal": ""}, {"item": "973d84ee-5b88-475c-b579-a76c34a7c533", "prefab": "973d84ee-5b88-475c-b579-a76c34a7c533", "mask": "sHyNs-dpc0-_Ah-HJVczcw", "swatch": "bgRKH1wfqkWVHcuSnFYnlQ", "decal": ""}, {"item": "3e3f5ed5-d810-4054-b648-d93167b90033", "prefab": "3e3f5ed5-d810-4054-b648-d93167b90033", "mask": "", "swatch": "", "decal": ""}, {"item": "3e3f5ed5-d810-4054-b648-d93167b90033", "prefab": "3e3f5ed5-d810-4054-b648-d93167b90033", "mask": "PMGQ8fLc2UuJXMy42Ati-w", "swatch": "yA0KhVg19kWqG1UkyxQogQ", "decal": ""}, {"item": "3e3f5ed5-d810-4054-b648-d93167b90033", "prefab": "3e3f5ed5-d810-4054-b648-d93167b90033", "mask": "PMGQ8fLc2UuJXMy42Ati-w", "swatch": "P9bBSL0YLEyjN8PxMfgbKQ", "decal": ""}, {"item": "3e3f5ed5-d810-4054-b648-d93167b90033", "prefab": "3e3f5ed5-d810-4054-b648-d93167b90033", "mask": "sxWOOK1RwUOp5YqZ7a36xg", "swatch": "bgRKH1wfqkWVHcuSnFYnlQ", "decal": ""}, {"item": "3e3f5ed5-d810-4054-b648-d93167b90033", "prefab": "3e3f5ed5-d810-4054-b648-d93167b90033", "mask": "WeyKw_WS70Cx3NZdOaxGeA", "swatch": "4Eck_dNL1UKlIZWsA1YppQ", "decal": ""}, {"item": "7e22a4bd-3e87-4d50-8843-9e2e3cf45285", "prefab": "7e22a4bd-3e87-4d50-8843-9e2e3cf45285", "mask": "", "swatch": "", "decal": ""}, {"item": "7e22a4bd-3e87-4d50-8843-9e2e3cf45285", "prefab": "7e22a4bd-3e87-4d50-8843-9e2e3cf45285", "mask": "rzoH8P44a0esN-HnYd3aQg", "swatch": "9b9t8_aKH0-gQXMhZycaOA", "decal": ""}, {"item": "7e22a4bd-3e87-4d50-8843-9e2e3cf45285", "prefab": "7e22a4bd-3e87-4d50-8843-9e2e3cf45285", "mask": "rzoH8P44a0esN-HnYd3aQg", "swatch": "Zz4XatKfUEapReRAQHxKKA", "decal": ""}, {"item": "7e22a4bd-3e87-4d50-8843-9e2e3cf45285", "prefab": "7e22a4bd-3e87-4d50-8843-9e2e3cf45285", "mask": "rzoH8P44a0esN-HnYd3aQg", "swatch": "Jt0QqNrAp0qzUR_RQoivVg", "decal": ""}, {"item": "7e22a4bd-3e87-4d50-8843-9e2e3cf45285", "prefab": "7e22a4bd-3e87-4d50-8843-9e2e3cf45285", "mask": "rzoH8P44a0esN-HnYd3aQg", "swatch": "lBePHNL9LkyaAjEFyFE8Sw", "decal": ""}, {"item": "7e22a4bd-3e87-4d50-8843-9e2e3cf45285", "prefab": "7e22a4bd-3e87-4d50-8843-9e2e3cf45285", "mask": "rzoH8P44a0esN-HnYd3aQg", "swatch": "G4HIPUjekUigiU0-Kr9mdA", "decal": ""}, {"item": "7e22a4bd-3e87-4d50-8843-9e2e3cf45285", "prefab": "7e22a4bd-3e87-4d50-8843-9e2e3cf45285", "mask": "rzoH8P44a0esN-HnYd3aQg", "swatch": "5mcp7-2ZEE6n3-zN3_CoWw", "decal": ""}, {"item": "7e22a4bd-3e87-4d50-8843-9e2e3cf45285", "prefab": "7e22a4bd-3e87-4d50-8843-9e2e3cf45285", "mask": "rzoH8P44a0esN-HnYd3aQg", "swatch": "R0UCacpdhUS906Xf7l142A", "decal": ""}, {"item": "7e22a4bd-3e87-4d50-8843-9e2e3cf45285", "prefab": "7e22a4bd-3e87-4d50-8843-9e2e3cf45285", "mask": "rzoH8P44a0esN-HnYd3aQg", "swatch": "G-VKgepBkkeATcwCaS6Rwg", "decal": ""}, {"item": "7e22a4bd-3e87-4d50-8843-9e2e3cf45285", "prefab": "7e22a4bd-3e87-4d50-8843-9e2e3cf45285", "mask": "rzoH8P44a0esN-HnYd3aQg", "swatch": "1LonkCbt4kykeAScYKpwyg", "decal": ""}, {"item": "bc7299ac-be2b-404f-b8e6-402e2c0660f0", "prefab": "bc7299ac-be2b-404f-b8e6-402e2c0660f0", "mask": "", "swatch": "", "decal": ""}, {"item": "bfd9c70b-9ad8-4f47-a840-c03231f9ad41", "prefab": "bfd9c70b-9ad8-4f47-a840-c03231f9ad41", "mask": "", "swatch": "", "decal": ""}, {"item": "45eaab67-19c2-4601-8f80-3565a4dceba4", "prefab": "45eaab67-19c2-4601-8f80-3565a4dceba4", "mask": "", "swatch": "", "decal": ""}, {"item": "474db08a-d4dc-4625-9f5a-1a6ae0b70827", "prefab": "474db08a-d4dc-4625-9f5a-1a6ae0b70827", "mask": "", "swatch": "", "decal": ""}, {"item": "474db08a-d4dc-4625-9f5a-1a6ae0b70827", "prefab": "474db08a-d4dc-4625-9f5a-1a6ae0b70827", "mask": "_yznwUwL80ejrS3vki0IKg", "swatch": "pqLkLbri9Eu48eryQIFGMg", "decal": ""}, {"item": "474db08a-d4dc-4625-9f5a-1a6ae0b70827", "prefab": "474db08a-d4dc-4625-9f5a-1a6ae0b70827", "mask": "SroEVC6vBkG42ILwCtKB9w", "swatch": "5iCrYb8MBkqN6IBEe3IipQ", "decal": ""}, {"item": "474db08a-d4dc-4625-9f5a-1a6ae0b70827", "prefab": "474db08a-d4dc-4625-9f5a-1a6ae0b70827", "mask": "Mrf17aidXEeCF-fFURUUcQ", "swatch": "cpTKHXjj0kulivZCKVQqiA", "decal": ""}, {"item": "c855dcc3-96cb-470d-b159-d37a025a47d1", "prefab": "c855dcc3-96cb-470d-b159-d37a025a47d1", "mask": "", "swatch": "", "decal": ""}, {"item": "3d9d52cb-6a3d-436b-90e7-e76f238329ee", "prefab": "3d9d52cb-6a3d-436b-90e7-e76f238329ee", "mask": "", "swatch": "", "decal": ""}, {"item": "3d9d52cb-6a3d-436b-90e7-e76f238329ee", "prefab": "3d9d52cb-6a3d-436b-90e7-e76f238329ee", "mask": "bVrkIyADCUiGNYWdmP_HPw", "swatch": "ffHKr5EcIU24PbAtWSnalw", "decal": ""}, {"item": "3d9d52cb-6a3d-436b-90e7-e76f238329ee", "prefab": "3d9d52cb-6a3d-436b-90e7-e76f238329ee", "mask": "WF_fg80f30qvHit0vGBwCA", "swatch": "m4W5D0Oa9kaWa-8I96YYzA", "decal": ""}, {"item": "9b5406c4-8589-47e4-9f69-4fa4733615e7", "prefab": "9b5406c4-8589-47e4-9f69-4fa4733615e7", "mask": "", "swatch": "", "decal": ""}, {"item": "1736a60b-6fb6-49bd-948a-fd3f3fc1be50", "prefab": "1736a60b-6fb6-49bd-948a-fd3f3fc1be50", "mask": "", "swatch": "", "decal": ""}, {"item": "57552652-d902-43b7-a68e-5c7da87582bc", "prefab": "57552652-d902-43b7-a68e-5c7da87582bc", "mask": "", "swatch": "", "decal": ""}, {"item": "57552652-d902-43b7-a68e-5c7da87582bc", "prefab": "57552652-d902-43b7-a68e-5c7da87582bc", "mask": "xTqtLPQVyEKjAQZYl6HGow", "swatch": "VqtOYBiKRkaSW4xAlBxQgg", "decal": ""}, {"item": "57552652-d902-43b7-a68e-5c7da87582bc", "prefab": "57552652-d902-43b7-a68e-5c7da87582bc", "mask": "m0R4Jtf4HES8krxMnwIWFg", "swatch": "J-K34UX_B0yObjQI9tn0vQ", "decal": ""}, {"item": "5b3dc472-773e-4714-a9d0-7e7bbf6bc695", "prefab": "5b3dc472-773e-4714-a9d0-7e7bbf6bc695", "mask": "", "swatch": "", "decal": ""}, {"item": "5b3dc472-773e-4714-a9d0-7e7bbf6bc695", "prefab": "5b3dc472-773e-4714-a9d0-7e7bbf6bc695", "mask": "Uvarm_CwTUWWQYZvzI8OaQ", "swatch": "V_duN2oWaUmmao1xHhzCJA", "decal": ""}, {"item": "5b3dc472-773e-4714-a9d0-7e7bbf6bc695", "prefab": "5b3dc472-773e-4714-a9d0-7e7bbf6bc695", "mask": "4zPm9miF5UOouEWFpOxYZg", "swatch": "GWi1rEebqkCV_TS2WzQhcA", "decal": ""}, {"item": "9c28e450-8e12-455c-a4c8-c0a2160d4190", "prefab": "9c28e450-8e12-455c-a4c8-c0a2160d4190", "mask": "", "swatch": "", "decal": ""}, {"item": "9c28e450-8e12-455c-a4c8-c0a2160d4190", "prefab": "9c28e450-8e12-455c-a4c8-c0a2160d4190", "mask": "A7Y52vGIN0ijEKsWR_GQLg", "swatch": "DRS8xu3AqUOy2L0RKF3f7g", "decal": ""}, {"item": "9c28e450-8e12-455c-a4c8-c0a2160d4190", "prefab": "9c28e450-8e12-455c-a4c8-c0a2160d4190", "mask": "A7Y52vGIN0ijEKsWR_GQLg", "swatch": "ZSOuWuKVCkqaIKc5d23G8Q", "decal": ""}, {"item": "d86e2e65-5d87-466d-b885-8c52ac2eeeb0", "prefab": "d86e2e65-5d87-466d-b885-8c52ac2eeeb0", "mask": "", "swatch": "", "decal": ""}, {"item": "a5b747b8-c1a5-4bce-999f-e13dbe77bde6", "prefab": "a5b747b8-c1a5-4bce-999f-e13dbe77bde6", "mask": "", "swatch": "", "decal": ""}, {"item": "30380607-8b54-4b63-81d3-cb3f08e452c1", "prefab": "30380607-8b54-4b63-81d3-cb3f08e452c1", "mask": "", "swatch": "", "decal": ""}, {"item": "30380607-8b54-4b63-81d3-cb3f08e452c1", "prefab": "30380607-8b54-4b63-81d3-cb3f08e452c1", "mask": "JmFzNL5v_0q1p0bgoOlBpQ", "swatch": "rcdwgNvO30Cvx2ycE1GfHw", "decal": ""}, {"item": "9a366f8b-ea5b-49ae-a98c-f6b282e9bf14", "prefab": "9a366f8b-ea5b-49ae-a98c-f6b282e9bf14", "mask": "", "swatch": "", "decal": ""}, {"item": "73c0e2ba-cba1-482a-89d5-1508997aceeb", "prefab": "73c0e2ba-cba1-482a-89d5-1508997aceeb", "mask": "", "swatch": "", "decal": ""}, {"item": "73a119ab-d2c8-4da9-98b2-29edbb38e2c2", "prefab": "73a119ab-d2c8-4da9-98b2-29edbb38e2c2", "mask": "", "swatch": "", "decal": ""}, {"item": "0d4e7ebe-efd9-4e68-9900-5645916e7596", "prefab": "0d4e7ebe-efd9-4e68-9900-5645916e7596", "mask": "", "swatch": "", "decal": ""}, {"item": "988e4069-4341-428b-bbf5-023309891385", "prefab": "988e4069-4341-428b-bbf5-023309891385", "mask": "", "swatch": "", "decal": ""}, {"item": "05ec4e87-088f-4281-a118-2e63c2585200", "prefab": "05ec4e87-088f-4281-a118-2e63c2585200", "mask": "", "swatch": "", "decal": ""}, {"item": "05ec4e87-088f-4281-a118-2e63c2585200", "prefab": "05ec4e87-088f-4281-a118-2e63c2585200", "mask": "tfiSfpRez0y_R4QjAhO0bQ", "swatch": "Wm30oV5BtEuFQZFk3dPK_w", "decal": ""}, {"item": "6dd261d8-9806-4dac-81c4-a58f6d6a7f9a", "prefab": "6dd261d8-9806-4dac-81c4-a58f6d6a7f9a", "mask": "", "swatch": "", "decal": ""}, {"item": "6dd261d8-9806-4dac-81c4-a58f6d6a7f9a", "prefab": "6dd261d8-9806-4dac-81c4-a58f6d6a7f9a", "mask": "0R3Mh96tS0631EZYs03dmw", "swatch": "Wm30oV5BtEuFQZFk3dPK_w", "decal": ""}, {"item": "b95678aa-351a-4929-8a0e-0274a183eb18", "prefab": "b95678aa-351a-4929-8a0e-0274a183eb18", "mask": "", "swatch": "", "decal": ""}, {"item": "5e9bbda6-d681-42b1-9cc4-5758acf34bc0", "prefab": "5e9bbda6-d681-42b1-9cc4-5758acf34bc0", "mask": "", "swatch": "", "decal": ""}, {"item": "7be2eafc-21ea-4266-ae64-8888c06798d6", "prefab": "7be2eafc-21ea-4266-ae64-8888c06798d6", "mask": "", "swatch": "", "decal": ""}, {"item": "7be2eafc-21ea-4266-ae64-8888c06798d6", "prefab": "7be2eafc-21ea-4266-ae64-8888c06798d6", "mask": "URWHffwjGUKgXeV01upePA", "swatch": "sqPMH0LhLkeG-4XkxpU-sw", "decal": ""}, {"item": "86dd9ff9-643f-4f11-8243-93ad948db4cc", "prefab": "86dd9ff9-643f-4f11-8243-93ad948db4cc", "mask": "", "swatch": "", "decal": ""}, {"item": "8df990d9-141a-4757-b4f2-138d5e68ab74", "prefab": "8df990d9-141a-4757-b4f2-138d5e68ab74", "mask": "", "swatch": "", "decal": ""}, {"item": "8df990d9-141a-4757-b4f2-138d5e68ab74", "prefab": "8df990d9-141a-4757-b4f2-138d5e68ab74", "mask": "MrrsmY0J0EKduH-f5wUj6g", "swatch": "AfqXBWLBQEiiag4cEtoEiA", "decal": ""}, {"item": "8df990d9-141a-4757-b4f2-138d5e68ab74", "prefab": "8df990d9-141a-4757-b4f2-138d5e68ab74", "mask": "MrrsmY0J0EKduH-f5wUj6g", "swatch": "RROvzwtshUGyRudVIBn8bw", "decal": ""}, {"item": "8df990d9-141a-4757-b4f2-138d5e68ab74", "prefab": "8df990d9-141a-4757-b4f2-138d5e68ab74", "mask": "MrrsmY0J0EKduH-f5wUj6g", "swatch": "69vv21jfTUu1E_LjK5bytg", "decal": ""}, {"item": "2c3773a5-a0b4-41c5-a4df-6572dff516c9", "prefab": "2c3773a5-a0b4-41c5-a4df-6572dff516c9", "mask": "", "swatch": "", "decal": ""}, {"item": "b795c631-f075-4d32-ac87-c732b36cb932", "prefab": "b795c631-f075-4d32-ac87-c732b36cb932", "mask": "", "swatch": "", "decal": ""}, {"item": "ef7771eb-c5f3-44fd-8c27-f581b035ef10", "prefab": "ef7771eb-c5f3-44fd-8c27-f581b035ef10", "mask": "", "swatch": "", "decal": ""}, {"item": "ef7771eb-c5f3-44fd-8c27-f581b035ef10", "prefab": "ef7771eb-c5f3-44fd-8c27-f581b035ef10", "mask": "_zrnGn1ErEWR8WqrZmKZsA", "swatch": "OnZujqqU_UqwxmznvBBMfg", "decal": ""}, {"item": "ef7771eb-c5f3-44fd-8c27-f581b035ef10", "prefab": "ef7771eb-c5f3-44fd-8c27-f581b035ef10", "mask": "TFOAk7kwh0uUgtsyDNxMuQ", "swatch": "XKoxnTXYGkSUu52nVS9dnA", "decal": ""}, {"item": "fbdca48d-4ae6-41ad-8c33-b70a9e33fd77", "prefab": "fbdca48d-4ae6-41ad-8c33-b70a9e33fd77", "mask": "", "swatch": "", "decal": ""}, {"item": "e6216087-fb37-46f1-bda4-a88c4640f339", "prefab": "e6216087-fb37-46f1-bda4-a88c4640f339", "mask": "", "swatch": "", "decal": ""}, {"item": "e6216087-fb37-46f1-bda4-a88c4640f339", "prefab": "e6216087-fb37-46f1-bda4-a88c4640f339", "mask": "byvLynH8x06OQ7GNCXOVYA", "swatch": "KwhdQ1deNE6Y6jzO9vaAnQ", "decal": ""}, {"item": "105a5100-cb57-410f-a26b-1ee21002f067", "prefab": "105a5100-cb57-410f-a26b-1ee21002f067", "mask": "", "swatch": "", "decal": ""}, {"item": "105a5100-cb57-410f-a26b-1ee21002f067", "prefab": "105a5100-cb57-410f-a26b-1ee21002f067", "mask": "q0nBwoIx_UWPe93wHDWTYA", "swatch": "prEZHWvOM0ysaNfAe06ywA", "decal": ""}, {"item": "022ce375-ed02-479d-b87d-9d72d126bbd4", "prefab": "022ce375-ed02-479d-b87d-9d72d126bbd4", "mask": "", "swatch": "", "decal": ""}, {"item": "022ce375-ed02-479d-b87d-9d72d126bbd4", "prefab": "022ce375-ed02-479d-b87d-9d72d126bbd4", "mask": "oGrNKZLpMU2bLwNEe4MPnw", "swatch": "H6fcuLxeBUSY3oERFc-plA", "decal": ""}, {"item": "cec9cfac-a45b-4edb-b26d-7453cc2bfaf2", "prefab": "cec9cfac-a45b-4edb-b26d-7453cc2bfaf2", "mask": "", "swatch": "", "decal": ""}, {"item": "0ab5790f-a537-4306-ba66-5eb87c02ec7d", "prefab": "0ab5790f-a537-4306-ba66-5eb87c02ec7d", "mask": "", "swatch": "", "decal": ""}, {"item": "0ab5790f-a537-4306-ba66-5eb87c02ec7d", "prefab": "0ab5790f-a537-4306-ba66-5eb87c02ec7d", "mask": "ACrBLiAIgEur9V6l_B0CvA", "swatch": "QBglgj80zkuvUkk55Y5Feg", "decal": ""}, {"item": "0ab5790f-a537-4306-ba66-5eb87c02ec7d", "prefab": "0ab5790f-a537-4306-ba66-5eb87c02ec7d", "mask": "ACrBLiAIgEur9V6l_B0CvA", "swatch": "-HYrY2A5Tk2UWMi4_hmYbg", "decal": ""}, {"item": "0ab5790f-a537-4306-ba66-5eb87c02ec7d", "prefab": "0ab5790f-a537-4306-ba66-5eb87c02ec7d", "mask": "ACrBLiAIgEur9V6l_B0CvA", "swatch": "IjRMopsYlUqSE6327I9TIQ", "decal": ""}, {"item": "b9d08f9a-b23d-4d7e-978b-a0cd8adbfe9c", "prefab": "b9d08f9a-b23d-4d7e-978b-a0cd8adbfe9c", "mask": "", "swatch": "", "decal": ""}, {"item": "b9d08f9a-b23d-4d7e-978b-a0cd8adbfe9c", "prefab": "b9d08f9a-b23d-4d7e-978b-a0cd8adbfe9c", "mask": "Hj8WnsdidE6rJeNyOmWUfg", "swatch": "pXrZhtzjV02_qvIQoWEFWA", "decal": ""}, {"item": "b9d08f9a-b23d-4d7e-978b-a0cd8adbfe9c", "prefab": "b9d08f9a-b23d-4d7e-978b-a0cd8adbfe9c", "mask": "Hj8WnsdidE6rJeNyOmWUfg", "swatch": "VK4YBLXO10G2_aEl3ZQUGA", "decal": ""}, {"item": "b9d08f9a-b23d-4d7e-978b-a0cd8adbfe9c", "prefab": "b9d08f9a-b23d-4d7e-978b-a0cd8adbfe9c", "mask": "Hj8WnsdidE6rJeNyOmWUfg", "swatch": "A7l6Q2clsk2XyKf62D3dHA", "decal": ""}, {"item": "9d395a84-4342-4b1c-940c-213ec2eb56f9", "prefab": "9d395a84-4342-4b1c-940c-213ec2eb56f9", "mask": "", "swatch": "", "decal": ""}, {"item": "9d395a84-4342-4b1c-940c-213ec2eb56f9", "prefab": "9d395a84-4342-4b1c-940c-213ec2eb56f9", "mask": "XFuSiGupZ02Fut1Dxum_Iw", "swatch": "Zljmtj5WqUaRvB3XQTgJXg", "decal": ""}, {"item": "9d395a84-4342-4b1c-940c-213ec2eb56f9", "prefab": "9d395a84-4342-4b1c-940c-213ec2eb56f9", "mask": "XFuSiGupZ02Fut1Dxum_Iw", "swatch": "rmq1AOQvtUGOHvbPUximwg", "decal": ""}, {"item": "0c537a84-9095-406f-8c87-1464f3a774ba", "prefab": "0c537a84-9095-406f-8c87-1464f3a774ba", "mask": "", "swatch": "", "decal": ""}, {"item": "0c537a84-9095-406f-8c87-1464f3a774ba", "prefab": "0c537a84-9095-406f-8c87-1464f3a774ba", "mask": "3pdsIrhM9EW-y-LNnjbFxg", "swatch": "C9cPjOoiCUCHrCBi809CMQ", "decal": ""}, {"item": "0c537a84-9095-406f-8c87-1464f3a774ba", "prefab": "0c537a84-9095-406f-8c87-1464f3a774ba", "mask": "3pdsIrhM9EW-y-LNnjbFxg", "swatch": "24zVmiGiaESrM5qcRfF6gA", "decal": ""}, {"item": "0c537a84-9095-406f-8c87-1464f3a774ba", "prefab": "0c537a84-9095-406f-8c87-1464f3a774ba", "mask": "3pdsIrhM9EW-y-LNnjbFxg", "swatch": "LOeHr4xoik6VOv1fOeImjA", "decal": ""}, {"item": "eb830b12-c1b5-4796-b9cf-ccf0f0e1d4f1", "prefab": "eb830b12-c1b5-4796-b9cf-ccf0f0e1d4f1", "mask": "", "swatch": "", "decal": ""}, {"item": "43354e59-4938-44bc-81e0-4fba47d0ac12", "prefab": "43354e59-4938-44bc-81e0-4fba47d0ac12", "mask": "", "swatch": "", "decal": ""}, {"item": "dedd2e98-2721-4de6-92f2-9d399f34e8fe", "prefab": "dedd2e98-2721-4de6-92f2-9d399f34e8fe", "mask": "", "swatch": "", "decal": ""}, {"item": "dedd2e98-2721-4de6-92f2-9d399f34e8fe", "prefab": "dedd2e98-2721-4de6-92f2-9d399f34e8fe", "mask": "HQnd9m0Wi0upaXRNsE5HPQ", "swatch": "2jj5lxx6Gka5U6j0cWjN5w", "decal": ""}, {"item": "4e42e02e-33fd-4ff9-b74e-9bde26253c35", "prefab": "4e42e02e-33fd-4ff9-b74e-9bde26253c35", "mask": "", "swatch": "", "decal": ""}, {"item": "20b424a9-861a-4d39-a583-22d7b7348c14", "prefab": "20b424a9-861a-4d39-a583-22d7b7348c14", "mask": "", "swatch": "", "decal": ""}, {"item": "20b424a9-861a-4d39-a583-22d7b7348c14", "prefab": "20b424a9-861a-4d39-a583-22d7b7348c14", "mask": "bCuW1YadE06p6v1dBHsx4w", "swatch": "afnIqin4lkKKqWrvBvVagA", "decal": ""}, {"item": "204bc384-e6d2-41b8-8ce9-0b26be5d85b7", "prefab": "204bc384-e6d2-41b8-8ce9-0b26be5d85b7", "mask": "", "swatch": "", "decal": ""}, {"item": "204bc384-e6d2-41b8-8ce9-0b26be5d85b7", "prefab": "204bc384-e6d2-41b8-8ce9-0b26be5d85b7", "mask": "f5z_jhdcqEGjo-1WUmldaw", "swatch": "HdwZhjrqxE6wuf68MnDhDw", "decal": ""}, {"item": "204bc384-e6d2-41b8-8ce9-0b26be5d85b7", "prefab": "204bc384-e6d2-41b8-8ce9-0b26be5d85b7", "mask": "f5z_jhdcqEGjo-1WUmldaw", "swatch": "b7oobDcDtE-69fhM-5g5AQ", "decal": ""}, {"item": "204bc384-e6d2-41b8-8ce9-0b26be5d85b7", "prefab": "204bc384-e6d2-41b8-8ce9-0b26be5d85b7", "mask": "f5z_jhdcqEGjo-1WUmldaw", "swatch": "Blev_VFjRUysY_nymGWAVg", "decal": ""}, {"item": "f6caeb89-dbb4-4471-92fe-100bf48cd5ce", "prefab": "f6caeb89-dbb4-4471-92fe-100bf48cd5ce", "mask": "", "swatch": "", "decal": ""}, {"item": "f6caeb89-dbb4-4471-92fe-100bf48cd5ce", "prefab": "f6caeb89-dbb4-4471-92fe-100bf48cd5ce", "mask": "ORmS9HkfoUeFuO4RhqcW7g", "swatch": "GQSpXmD0B02_gwzpq6StFA", "decal": ""}, {"item": "f6caeb89-dbb4-4471-92fe-100bf48cd5ce", "prefab": "f6caeb89-dbb4-4471-92fe-100bf48cd5ce", "mask": "ORmS9HkfoUeFuO4RhqcW7g", "swatch": "YvPeCbQS2UOcLjPfoIML_g", "decal": ""}, {"item": "f6caeb89-dbb4-4471-92fe-100bf48cd5ce", "prefab": "f6caeb89-dbb4-4471-92fe-100bf48cd5ce", "mask": "ORmS9HkfoUeFuO4RhqcW7g", "swatch": "zGOD45zNAk2i6P_zPsNPfg", "decal": ""}, {"item": "b080e2f4-c699-4cca-bb9e-7cfc4bafcbfc", "prefab": "b080e2f4-c699-4cca-bb9e-7cfc4bafcbfc", "mask": "", "swatch": "", "decal": ""}, {"item": "b080e2f4-c699-4cca-bb9e-7cfc4bafcbfc", "prefab": "b080e2f4-c699-4cca-bb9e-7cfc4bafcbfc", "mask": "H3OQDBef1UaacSD52p8Hdw", "swatch": "iokdlBfjuEuAwqIMOQwxdA", "decal": ""}, {"item": "b080e2f4-c699-4cca-bb9e-7cfc4bafcbfc", "prefab": "b080e2f4-c699-4cca-bb9e-7cfc4bafcbfc", "mask": "H3OQDBef1UaacSD52p8Hdw", "swatch": "nKauYAyWhkm2AMHFyC-fLg", "decal": ""}, {"item": "b080e2f4-c699-4cca-bb9e-7cfc4bafcbfc", "prefab": "b080e2f4-c699-4cca-bb9e-7cfc4bafcbfc", "mask": "H3OQDBef1UaacSD52p8Hdw", "swatch": "EiGgN7uVukqoRE7UJcMLTw", "decal": ""}, {"item": "eda29bce-5a07-4439-a2a7-cafa11e2ed62", "prefab": "eda29bce-5a07-4439-a2a7-cafa11e2ed62", "mask": "", "swatch": "", "decal": ""}, {"item": "86af3a97-18d8-4dda-a2f2-cfd3b4224254", "prefab": "86af3a97-18d8-4dda-a2f2-cfd3b4224254", "mask": "", "swatch": "", "decal": ""}, {"item": "66f8f55d-cad4-4afb-93e8-b6bdf04b37b3", "prefab": "66f8f55d-cad4-4afb-93e8-b6bdf04b37b3", "mask": "", "swatch": "", "decal": ""}, {"item": "d3b8bd7b-ca9a-4f5d-b115-3659dc294a03", "prefab": "d3b8bd7b-ca9a-4f5d-b115-3659dc294a03", "mask": "", "swatch": "", "decal": ""}, {"item": "9e6397dd-b7b0-4d32-8c86-1910106a8478", "prefab": "9e6397dd-b7b0-4d32-8c86-1910106a8478", "mask": "", "swatch": "", "decal": ""}, {"item": "9e6397dd-b7b0-4d32-8c86-1910106a8478", "prefab": "9e6397dd-b7b0-4d32-8c86-1910106a8478", "mask": "T7zwcIIc8Ee3ud8a2JCuwQ", "swatch": "GH9q8_0O9EqjGdz7lGWD2g", "decal": ""}, {"item": "9e6397dd-b7b0-4d32-8c86-1910106a8478", "prefab": "9e6397dd-b7b0-4d32-8c86-1910106a8478", "mask": "dMDMDExSmEKOF_kQIHaowg", "swatch": "gDP3y0J2EUayzVvBT9-92w", "decal": ""}, {"item": "2bd53190-46f8-4956-9388-87c5a04efc4f", "prefab": "2bd53190-46f8-4956-9388-87c5a04efc4f", "mask": "", "swatch": "", "decal": ""}, {"item": "2bd53190-46f8-4956-9388-87c5a04efc4f", "prefab": "2bd53190-46f8-4956-9388-87c5a04efc4f", "mask": "1hVOI7IuY0mKvtr2PZXLww", "swatch": "v3af60p1Q02NpdCqIRO9lw", "decal": ""}, {"item": "2bd53190-46f8-4956-9388-87c5a04efc4f", "prefab": "2bd53190-46f8-4956-9388-87c5a04efc4f", "mask": "qC66Hu6yKEu2Res2o2z1Kw", "swatch": "kX7ocqn8X0-4M1nw-KrwFQ", "decal": ""}, {"item": "fc96ea5d-b49b-479b-a963-92f1eab44b2c", "prefab": "fc96ea5d-b49b-479b-a963-92f1eab44b2c", "mask": "", "swatch": "", "decal": ""}, {"item": "7dc6c916-b887-4929-93b5-dcea3022e6a9", "prefab": "7dc6c916-b887-4929-93b5-dcea3022e6a9", "mask": "", "swatch": "", "decal": ""}, {"item": "5154be09-bb8a-4917-95a0-51c91b6a4e09", "prefab": "5154be09-bb8a-4917-95a0-51c91b6a4e09", "mask": "", "swatch": "", "decal": ""}, {"item": "27c2eb7f-9fac-4c42-90c7-44e7a33d5303", "prefab": "27c2eb7f-9fac-4c42-90c7-44e7a33d5303", "mask": "", "swatch": "", "decal": ""}, {"item": "c506feae-be73-437b-b18a-1152bb9906e9", "prefab": "c506feae-be73-437b-b18a-1152bb9906e9", "mask": "", "swatch": "", "decal": ""}, {"item": "c506feae-be73-437b-b18a-1152bb9906e9", "prefab": "c506feae-be73-437b-b18a-1152bb9906e9", "mask": "yViVKOLHc0qLiPw9Oy1Obg", "swatch": "PTqpWqqU9k--lNlRrmlVbA", "decal": ""}, {"item": "c506feae-be73-437b-b18a-1152bb9906e9", "prefab": "c506feae-be73-437b-b18a-1152bb9906e9", "mask": "Do__oD53x0-dNtVeHsr4yA", "swatch": "nODTglESMEC9tBY1k-LrsA", "decal": ""}, {"item": "c506feae-be73-437b-b18a-1152bb9906e9", "prefab": "c506feae-be73-437b-b18a-1152bb9906e9", "mask": "GlB_scM-5kuc4A5TmGe6Yw", "swatch": "1qHXl55Qn0qSb1gAhr8Wjg", "decal": ""}, {"item": "097a3266-4aab-4b23-8f56-373c9b1bc837", "prefab": "097a3266-4aab-4b23-8f56-373c9b1bc837", "mask": "", "swatch": "", "decal": ""}, {"item": "097a3266-4aab-4b23-8f56-373c9b1bc837", "prefab": "097a3266-4aab-4b23-8f56-373c9b1bc837", "mask": "SeA8s77hr0K6DRZyVlRTCg", "swatch": "YB7kXMyLJ0mF_5WidhmH8A", "decal": ""}, {"item": "097a3266-4aab-4b23-8f56-373c9b1bc837", "prefab": "097a3266-4aab-4b23-8f56-373c9b1bc837", "mask": "SeA8s77hr0K6DRZyVlRTCg", "swatch": "bQyNgFWAe0SPttPri6hxyg", "decal": ""}, {"item": "097a3266-4aab-4b23-8f56-373c9b1bc837", "prefab": "097a3266-4aab-4b23-8f56-373c9b1bc837", "mask": "SeA8s77hr0K6DRZyVlRTCg", "swatch": "wRPoHTsXFESK5pjkxVMqGA", "decal": ""}, {"item": "bc3f8ab4-cdb1-4e63-bda5-ba5a4b1f4a43", "prefab": "bc3f8ab4-cdb1-4e63-bda5-ba5a4b1f4a43", "mask": "", "swatch": "", "decal": ""}, {"item": "bc3f8ab4-cdb1-4e63-bda5-ba5a4b1f4a43", "prefab": "bc3f8ab4-cdb1-4e63-bda5-ba5a4b1f4a43", "mask": "BOB_mAXdA0u2khOH5qjqXQ", "swatch": "RBRFHN8_rkuImbyDAfcHWw", "decal": ""}, {"item": "bc3f8ab4-cdb1-4e63-bda5-ba5a4b1f4a43", "prefab": "bc3f8ab4-cdb1-4e63-bda5-ba5a4b1f4a43", "mask": "BOB_mAXdA0u2khOH5qjqXQ", "swatch": "QJSPgq2KXEOWNynY8uqQvA", "decal": ""}, {"item": "bc3f8ab4-cdb1-4e63-bda5-ba5a4b1f4a43", "prefab": "bc3f8ab4-cdb1-4e63-bda5-ba5a4b1f4a43", "mask": "BOB_mAXdA0u2khOH5qjqXQ", "swatch": "4RH8_Z1mqU-JIVvx31wOsA", "decal": ""}, {"item": "4abc2ac4-e60c-49b1-9ac8-4d4751ed78ae", "prefab": "4abc2ac4-e60c-49b1-9ac8-4d4751ed78ae", "mask": "", "swatch": "", "decal": ""}, {"item": "5362d685-fc55-4b43-85a5-cc3d6cad3f69", "prefab": "5362d685-fc55-4b43-85a5-cc3d6cad3f69", "mask": "", "swatch": "", "decal": ""}, {"item": "41bece0e-9b5d-4151-bf5c-d0e786a803b4", "prefab": "41bece0e-9b5d-4151-bf5c-d0e786a803b4", "mask": "", "swatch": "", "decal": ""}, {"item": "41bece0e-9b5d-4151-bf5c-d0e786a803b4", "prefab": "41bece0e-9b5d-4151-bf5c-d0e786a803b4", "mask": "uWXtz0WaO0qJ1W4TfWnLqg", "swatch": "58YffnGSUU6umcI1i0RvZw", "decal": ""}, {"item": "41bece0e-9b5d-4151-bf5c-d0e786a803b4", "prefab": "41bece0e-9b5d-4151-bf5c-d0e786a803b4", "mask": "uWXtz0WaO0qJ1W4TfWnLqg", "swatch": "c24f1DNZjEmqkt-FibKUjQ", "decal": ""}, {"item": "41bece0e-9b5d-4151-bf5c-d0e786a803b4", "prefab": "41bece0e-9b5d-4151-bf5c-d0e786a803b4", "mask": "uWXtz0WaO0qJ1W4TfWnLqg", "swatch": "SeVmHtrMS0y7gKCYkHcrOA", "decal": ""}, {"item": "41bece0e-9b5d-4151-bf5c-d0e786a803b4", "prefab": "41bece0e-9b5d-4151-bf5c-d0e786a803b4", "mask": "uWXtz0WaO0qJ1W4TfWnLqg", "swatch": "uDcJZClcFkiNdkEEh9IIyA", "decal": ""}, {"item": "41bece0e-9b5d-4151-bf5c-d0e786a803b4", "prefab": "41bece0e-9b5d-4151-bf5c-d0e786a803b4", "mask": "uWXtz0WaO0qJ1W4TfWnLqg", "swatch": "Iezz6gCFfkKdVcmPXIhunw", "decal": ""}, {"item": "41bece0e-9b5d-4151-bf5c-d0e786a803b4", "prefab": "41bece0e-9b5d-4151-bf5c-d0e786a803b4", "mask": "uWXtz0WaO0qJ1W4TfWnLqg", "swatch": "fLWfBSEO6U6aHrDm3nKFEQ", "decal": ""}, {"item": "41bece0e-9b5d-4151-bf5c-d0e786a803b4", "prefab": "41bece0e-9b5d-4151-bf5c-d0e786a803b4", "mask": "uWXtz0WaO0qJ1W4TfWnLqg", "swatch": "F5vJzMfNB0Ob6Abjadho7Q", "decal": ""}, {"item": "41bece0e-9b5d-4151-bf5c-d0e786a803b4", "prefab": "41bece0e-9b5d-4151-bf5c-d0e786a803b4", "mask": "uWXtz0WaO0qJ1W4TfWnLqg", "swatch": "vbOUaJsGHEaSfQCivNcMWg", "decal": ""}, {"item": "41bece0e-9b5d-4151-bf5c-d0e786a803b4", "prefab": "41bece0e-9b5d-4151-bf5c-d0e786a803b4", "mask": "uWXtz0WaO0qJ1W4TfWnLqg", "swatch": "wbFPXIg5gUK3QK6YycZexg", "decal": ""}, {"item": "41bece0e-9b5d-4151-bf5c-d0e786a803b4", "prefab": "41bece0e-9b5d-4151-bf5c-d0e786a803b4", "mask": "uWXtz0WaO0qJ1W4TfWnLqg", "swatch": "_fBrHCkTKUe82QgxCyI-Kw", "decal": ""}, {"item": "48ec7a6a-0c31-40d4-9179-66e3ba6505a1", "prefab": "48ec7a6a-0c31-40d4-9179-66e3ba6505a1", "mask": "", "swatch": "", "decal": ""}, {"item": "48ec7a6a-0c31-40d4-9179-66e3ba6505a1", "prefab": "48ec7a6a-0c31-40d4-9179-66e3ba6505a1", "mask": "7nBHNMZhAE-t9rLCYO_STA", "swatch": "-XSM6ZcSjE-vrWL7jyF4fQ", "decal": ""}, {"item": "48ec7a6a-0c31-40d4-9179-66e3ba6505a1", "prefab": "48ec7a6a-0c31-40d4-9179-66e3ba6505a1", "mask": "7nBHNMZhAE-t9rLCYO_STA", "swatch": "RL6HkufaJ0yubTpCGVI9eQ", "decal": ""}, {"item": "48ec7a6a-0c31-40d4-9179-66e3ba6505a1", "prefab": "48ec7a6a-0c31-40d4-9179-66e3ba6505a1", "mask": "7nBHNMZhAE-t9rLCYO_STA", "swatch": "9tTuREkoQkW0ycqOjWXsiA", "decal": ""}, {"item": "48ec7a6a-0c31-40d4-9179-66e3ba6505a1", "prefab": "48ec7a6a-0c31-40d4-9179-66e3ba6505a1", "mask": "7nBHNMZhAE-t9rLCYO_STA", "swatch": "MVkY4sU5BkGC4QhweRiyIA", "decal": ""}, {"item": "48ec7a6a-0c31-40d4-9179-66e3ba6505a1", "prefab": "48ec7a6a-0c31-40d4-9179-66e3ba6505a1", "mask": "7nBHNMZhAE-t9rLCYO_STA", "swatch": "ux9DQVSRyU-_5GA-t197OA", "decal": ""}, {"item": "48ec7a6a-0c31-40d4-9179-66e3ba6505a1", "prefab": "48ec7a6a-0c31-40d4-9179-66e3ba6505a1", "mask": "7nBHNMZhAE-t9rLCYO_STA", "swatch": "t2bByt5oUEG90PMMePCP6A", "decal": ""}, {"item": "48ec7a6a-0c31-40d4-9179-66e3ba6505a1", "prefab": "48ec7a6a-0c31-40d4-9179-66e3ba6505a1", "mask": "7nBHNMZhAE-t9rLCYO_STA", "swatch": "m7KXVnULz0iHvKfCFzFoyw", "decal": ""}, {"item": "48ec7a6a-0c31-40d4-9179-66e3ba6505a1", "prefab": "48ec7a6a-0c31-40d4-9179-66e3ba6505a1", "mask": "7nBHNMZhAE-t9rLCYO_STA", "swatch": "gT0WA7Y1H0my5uMlteIMMg", "decal": ""}, {"item": "1014dded-3bbc-479b-a6cb-afa7413de2da", "prefab": "1014dded-3bbc-479b-a6cb-afa7413de2da", "mask": "", "swatch": "", "decal": ""}, {"item": "1014dded-3bbc-479b-a6cb-afa7413de2da", "prefab": "1014dded-3bbc-479b-a6cb-afa7413de2da", "mask": "egpzBs77Q0CpDndZHqXOog", "swatch": "DtFYrQwaCky5C1ZycqZbiQ", "decal": ""}, {"item": "1014dded-3bbc-479b-a6cb-afa7413de2da", "prefab": "1014dded-3bbc-479b-a6cb-afa7413de2da", "mask": "egpzBs77Q0CpDndZHqXOog", "swatch": "9SAPupKy_EO6YeG0SMkI5A", "decal": ""}, {"item": "1014dded-3bbc-479b-a6cb-afa7413de2da", "prefab": "1014dded-3bbc-479b-a6cb-afa7413de2da", "mask": "egpzBs77Q0CpDndZHqXOog", "swatch": "hiMYlmk5sEqh-sElOY3A9g", "decal": ""}, {"item": "64fddd4e-3d59-43df-bb39-671e18953c0f", "prefab": "64fddd4e-3d59-43df-bb39-671e18953c0f", "mask": "", "swatch": "", "decal": ""}, {"item": "64fddd4e-3d59-43df-bb39-671e18953c0f", "prefab": "64fddd4e-3d59-43df-bb39-671e18953c0f", "mask": "KOCzRE4a6EKb3_NVOI_lcg", "swatch": "uDJ5ZqU1f0CiOAH8WY22Yw", "decal": ""}, {"item": "64fddd4e-3d59-43df-bb39-671e18953c0f", "prefab": "64fddd4e-3d59-43df-bb39-671e18953c0f", "mask": "KOCzRE4a6EKb3_NVOI_lcg", "swatch": "j66We9SYr0SNQ2ufZqIqhA", "decal": ""}, {"item": "64fddd4e-3d59-43df-bb39-671e18953c0f", "prefab": "64fddd4e-3d59-43df-bb39-671e18953c0f", "mask": "KOCzRE4a6EKb3_NVOI_lcg", "swatch": "_jkPDSXFUkSU_yOfutQ7BA", "decal": ""}, {"item": "250bbb53-1fae-440d-b25a-46a16c271367", "prefab": "250bbb53-1fae-440d-b25a-46a16c271367", "mask": "", "swatch": "", "decal": ""}, {"item": "250bbb53-1fae-440d-b25a-46a16c271367", "prefab": "250bbb53-1fae-440d-b25a-46a16c271367", "mask": "RR69IGV4j0Sd2xtmyN5wFg", "swatch": "2vawoQvJPUegnClmr2RNAQ", "decal": ""}, {"item": "250bbb53-1fae-440d-b25a-46a16c271367", "prefab": "250bbb53-1fae-440d-b25a-46a16c271367", "mask": "RR69IGV4j0Sd2xtmyN5wFg", "swatch": "kIcd-yEnsEqxoeogquvmRw", "decal": ""}, {"item": "250bbb53-1fae-440d-b25a-46a16c271367", "prefab": "250bbb53-1fae-440d-b25a-46a16c271367", "mask": "RR69IGV4j0Sd2xtmyN5wFg", "swatch": "qHyKfCsrbUqSWomjLXf7Hw", "decal": ""}, {"item": "8595c7b2-4d1c-4eae-af9a-8180d475d931", "prefab": "8595c7b2-4d1c-4eae-af9a-8180d475d931", "mask": "", "swatch": "", "decal": ""}, {"item": "8595c7b2-4d1c-4eae-af9a-8180d475d931", "prefab": "8595c7b2-4d1c-4eae-af9a-8180d475d931", "mask": "w-vooMawWU6_4spdgBYufA", "swatch": "AHL9ydUbqEqMw1tluIvyQA", "decal": ""}, {"item": "8595c7b2-4d1c-4eae-af9a-8180d475d931", "prefab": "8595c7b2-4d1c-4eae-af9a-8180d475d931", "mask": "w-vooMawWU6_4spdgBYufA", "swatch": "_SPKrYIcnUWhTiSrTFLwoQ", "decal": ""}, {"item": "8595c7b2-4d1c-4eae-af9a-8180d475d931", "prefab": "8595c7b2-4d1c-4eae-af9a-8180d475d931", "mask": "w-vooMawWU6_4spdgBYufA", "swatch": "n2pZHS8A3kSgyFUCI_-8TA", "decal": ""}, {"item": "f15d81c8-3d19-4bbe-87c0-1144aaa4138a", "prefab": "f15d81c8-3d19-4bbe-87c0-1144aaa4138a", "mask": "", "swatch": "", "decal": ""}, {"item": "f15d81c8-3d19-4bbe-87c0-1144aaa4138a", "prefab": "f15d81c8-3d19-4bbe-87c0-1144aaa4138a", "mask": "tmaPvkx3BUOZ3nsl2E0oRw", "swatch": "M16XTvLPKUWRpiAspgsatQ", "decal": ""}, {"item": "f15d81c8-3d19-4bbe-87c0-1144aaa4138a", "prefab": "f15d81c8-3d19-4bbe-87c0-1144aaa4138a", "mask": "tmaPvkx3BUOZ3nsl2E0oRw", "swatch": "rXhNZtcZCU21dwEHOCgWOA", "decal": ""}, {"item": "5d13a7a2-8213-40e6-90a6-efdd76a3fdcb", "prefab": "5d13a7a2-8213-40e6-90a6-efdd76a3fdcb", "mask": "", "swatch": "", "decal": ""}, {"item": "4ee2133d-c419-4fbc-9117-746d756a250d", "prefab": "4ee2133d-c419-4fbc-9117-746d756a250d", "mask": "", "swatch": "", "decal": ""}, {"item": "7aa994d2-6499-4918-ab2a-2531c17e27a7", "prefab": "7aa994d2-6499-4918-ab2a-2531c17e27a7", "mask": "", "swatch": "", "decal": ""}, {"item": "7aa994d2-6499-4918-ab2a-2531c17e27a7", "prefab": "7aa994d2-6499-4918-ab2a-2531c17e27a7", "mask": "CAqT_iLfQ0qQ-GbIdAsZow", "swatch": "ZXeI9jooq0-dQaX-FlkNuQ", "decal": ""}, {"item": "7aa994d2-6499-4918-ab2a-2531c17e27a7", "prefab": "7aa994d2-6499-4918-ab2a-2531c17e27a7", "mask": "CAqT_iLfQ0qQ-GbIdAsZow", "swatch": "IzdUC8x0F02i3BhPfEUOYw", "decal": ""}, {"item": "7aa994d2-6499-4918-ab2a-2531c17e27a7", "prefab": "7aa994d2-6499-4918-ab2a-2531c17e27a7", "mask": "CAqT_iLfQ0qQ-GbIdAsZow", "swatch": "nPUQvBsxu0mzhQg9YnWEgw", "decal": ""}, {"item": "7aa994d2-6499-4918-ab2a-2531c17e27a7", "prefab": "7aa994d2-6499-4918-ab2a-2531c17e27a7", "mask": "vUgpsaXrO0ak7huhkHqlUA", "swatch": "wyKAZSZwT0u9cuSaQ_-IHg", "decal": ""}, {"item": "7aa994d2-6499-4918-ab2a-2531c17e27a7", "prefab": "7aa994d2-6499-4918-ab2a-2531c17e27a7", "mask": "vUgpsaXrO0ak7huhkHqlUA", "swatch": "_OM7UNZENEejgO5h7H1ztg", "decal": ""}, {"item": "3eb14fcf-5d20-47d3-b991-97808798a225", "prefab": "3eb14fcf-5d20-47d3-b991-97808798a225", "mask": "", "swatch": "", "decal": ""}, {"item": "3eb14fcf-5d20-47d3-b991-97808798a225", "prefab": "3eb14fcf-5d20-47d3-b991-97808798a225", "mask": "5ekjxLQZwUm7-iXJ0bYSiQ", "swatch": "fV4H_1ZXx0-OVtlasI-xGQ", "decal": ""}, {"item": "3eb14fcf-5d20-47d3-b991-97808798a225", "prefab": "3eb14fcf-5d20-47d3-b991-97808798a225", "mask": "5ekjxLQZwUm7-iXJ0bYSiQ", "swatch": "B9DJPqTeYUuPnPHRbKzEJA", "decal": ""}, {"item": "3eb14fcf-5d20-47d3-b991-97808798a225", "prefab": "3eb14fcf-5d20-47d3-b991-97808798a225", "mask": "5ekjxLQZwUm7-iXJ0bYSiQ", "swatch": "yXZO-donI0SHB9-TT0jUdw", "decal": ""}, {"item": "3eb14fcf-5d20-47d3-b991-97808798a225", "prefab": "3eb14fcf-5d20-47d3-b991-97808798a225", "mask": "5ekjxLQZwUm7-iXJ0bYSiQ", "swatch": "J5VsmblfWU2MeUjNo0fz1Q", "decal": ""}, {"item": "3eb14fcf-5d20-47d3-b991-97808798a225", "prefab": "3eb14fcf-5d20-47d3-b991-97808798a225", "mask": "5ekjxLQZwUm7-iXJ0bYSiQ", "swatch": "Asfe0ZRi50eryD5wlJzgzA", "decal": ""}, {"item": "cf1a6f8f-1649-423b-b513-b8d40d242b1f", "prefab": "cf1a6f8f-1649-423b-b513-b8d40d242b1f", "mask": "", "swatch": "", "decal": ""}, {"item": "84574356-98ec-415f-8668-82cb52016bd3", "prefab": "84574356-98ec-415f-8668-82cb52016bd3", "mask": "", "swatch": "", "decal": ""}, {"item": "31bb82db-cd30-4988-a8bf-3ce55a12a4c9", "prefab": "31bb82db-cd30-4988-a8bf-3ce55a12a4c9", "mask": "", "swatch": "", "decal": ""}, {"item": "49800740-33d6-4280-aefb-3497597c6366", "prefab": "49800740-33d6-4280-aefb-3497597c6366", "mask": "", "swatch": "", "decal": ""}, {"item": "ffad1160-8383-4b4a-a5b9-223476b49b92", "prefab": "ffad1160-8383-4b4a-a5b9-223476b49b92", "mask": "", "swatch": "", "decal": ""}, {"item": "1988692c-aae3-450b-acd6-e9015b831f5c", "prefab": "1988692c-aae3-450b-acd6-e9015b831f5c", "mask": "", "swatch": "", "decal": ""}, {"item": "fa0e701c-5e80-46f4-8817-6e2dbf904734", "prefab": "fa0e701c-5e80-46f4-8817-6e2dbf904734", "mask": "", "swatch": "", "decal": ""}, {"item": "b4e48239-a3b0-49ed-b78a-86f4e3aaca1e", "prefab": "b4e48239-a3b0-49ed-b78a-86f4e3aaca1e", "mask": "", "swatch": "", "decal": ""}, {"item": "4a0e91a1-4562-4a82-92be-cb7491f1592b", "prefab": "4a0e91a1-4562-4a82-92be-cb7491f1592b", "mask": "", "swatch": "", "decal": ""}, {"item": "4a0e91a1-4562-4a82-92be-cb7491f1592b", "prefab": "4a0e91a1-4562-4a82-92be-cb7491f1592b", "mask": "48yVsRt8sU6jf3P4wlPA8Q", "swatch": "DOY6LpHq8kui8yDWGsNgZw", "decal": ""}, {"item": "767684e7-0eec-4f6e-ad58-3d5ae715206e", "prefab": "767684e7-0eec-4f6e-ad58-3d5ae715206e", "mask": "", "swatch": "", "decal": ""}, {"item": "fe15ca53-c5b8-4acf-9309-ff3f4e610fc9", "prefab": "fe15ca53-c5b8-4acf-9309-ff3f4e610fc9", "mask": "", "swatch": "", "decal": ""}, {"item": "fe15ca53-c5b8-4acf-9309-ff3f4e610fc9", "prefab": "fe15ca53-c5b8-4acf-9309-ff3f4e610fc9", "mask": "1yMyo6oTjU-VAygoeWaohQ", "swatch": "knXPidb-Rkayfc3kSHfZeQ", "decal": ""}, {"item": "fe15ca53-c5b8-4acf-9309-ff3f4e610fc9", "prefab": "fe15ca53-c5b8-4acf-9309-ff3f4e610fc9", "mask": "njEWOjNEQEWCEqmytodpow", "swatch": "tXhgU1o_wkaNQ-7P3KXidQ", "decal": ""}, {"item": "fe15ca53-c5b8-4acf-9309-ff3f4e610fc9", "prefab": "fe15ca53-c5b8-4acf-9309-ff3f4e610fc9", "mask": "njEWOjNEQEWCEqmytodpow", "swatch": "nn86ATWxekWdIGlPePPgGQ", "decal": ""}, {"item": "fe15ca53-c5b8-4acf-9309-ff3f4e610fc9", "prefab": "fe15ca53-c5b8-4acf-9309-ff3f4e610fc9", "mask": "njEWOjNEQEWCEqmytodpow", "swatch": "Rf7CDh2bg0-HdG5n7phGaw", "decal": ""}, {"item": "fe15ca53-c5b8-4acf-9309-ff3f4e610fc9", "prefab": "fe15ca53-c5b8-4acf-9309-ff3f4e610fc9", "mask": "njEWOjNEQEWCEqmytodpow", "swatch": "oxi26T5a4U6L73pF-Pgrhg", "decal": ""}, {"item": "fe15ca53-c5b8-4acf-9309-ff3f4e610fc9", "prefab": "fe15ca53-c5b8-4acf-9309-ff3f4e610fc9", "mask": "njEWOjNEQEWCEqmytodpow", "swatch": "6547pqf6vUm2L89AG6D91A", "decal": ""}, {"item": "fd7774bb-b29c-40b5-84b1-5c2bbe574255", "prefab": "fd7774bb-b29c-40b5-84b1-5c2bbe574255", "mask": "", "swatch": "", "decal": ""}, {"item": "c8c00394-4cd9-4bad-9774-a825ded78f80", "prefab": "c8c00394-4cd9-4bad-9774-a825ded78f80", "mask": "", "swatch": "", "decal": ""}, {"item": "16359b51-96c4-4be0-b598-319c7549e18a", "prefab": "16359b51-96c4-4be0-b598-319c7549e18a", "mask": "", "swatch": "", "decal": ""}, {"item": "d7730a9e-78a1-4356-bc09-6b066615850b", "prefab": "d7730a9e-78a1-4356-bc09-6b066615850b", "mask": "", "swatch": "", "decal": ""}, {"item": "8c35c804-e8d5-49d2-8d5a-ea19fb70bfa6", "prefab": "8c35c804-e8d5-49d2-8d5a-ea19fb70bfa6", "mask": "", "swatch": "", "decal": ""}, {"item": "7a98503f-0460-4d25-a0d5-72a32db8f4c2", "prefab": "7a98503f-0460-4d25-a0d5-72a32db8f4c2", "mask": "", "swatch": "", "decal": ""}, {"item": "3ae0eb82-13ce-43a2-bbc2-99a1cdbec63f", "prefab": "3ae0eb82-13ce-43a2-bbc2-99a1cdbec63f", "mask": "", "swatch": "", "decal": ""}, {"item": "4c673a35-c10f-4a8c-b8e0-96a50fe5b97f", "prefab": "4c673a35-c10f-4a8c-b8e0-96a50fe5b97f", "mask": "", "swatch": "", "decal": ""}, {"item": "5beeb4c4-f276-4eae-87aa-9302e45b05b7", "prefab": "5beeb4c4-f276-4eae-87aa-9302e45b05b7", "mask": "", "swatch": "", "decal": ""}, {"item": "adf2aa89-61c4-4b7a-9307-e39e72db23d3", "prefab": "adf2aa89-61c4-4b7a-9307-e39e72db23d3", "mask": "", "swatch": "", "decal": ""}, {"item": "adf2aa89-61c4-4b7a-9307-e39e72db23d3", "prefab": "adf2aa89-61c4-4b7a-9307-e39e72db23d3", "mask": "NqU5Mw75BE6kPaD8t11Qow", "swatch": "OJYpuFw810Sg4bTw0XQf-g", "decal": ""}, {"item": "adf2aa89-61c4-4b7a-9307-e39e72db23d3", "prefab": "adf2aa89-61c4-4b7a-9307-e39e72db23d3", "mask": "NqU5Mw75BE6kPaD8t11Qow", "swatch": "t4gMAKp4L0WBaTGf_rtqFw", "decal": ""}, {"item": "adf2aa89-61c4-4b7a-9307-e39e72db23d3", "prefab": "adf2aa89-61c4-4b7a-9307-e39e72db23d3", "mask": "NqU5Mw75BE6kPaD8t11Qow", "swatch": "rGMFdPKmK02_OvIIFYlAxA", "decal": ""}, {"item": "adf2aa89-61c4-4b7a-9307-e39e72db23d3", "prefab": "adf2aa89-61c4-4b7a-9307-e39e72db23d3", "mask": "thwvF0DFRk-mV99ruqYFoQ", "swatch": "wxvSDMommkenIl5hTY2fdQ", "decal": ""}, {"item": "adf2aa89-61c4-4b7a-9307-e39e72db23d3", "prefab": "adf2aa89-61c4-4b7a-9307-e39e72db23d3", "mask": "thwvF0DFRk-mV99ruqYFoQ", "swatch": "-sqfCQpzpUyjEShEXH9JYQ", "decal": ""}, {"item": "adf2aa89-61c4-4b7a-9307-e39e72db23d3", "prefab": "adf2aa89-61c4-4b7a-9307-e39e72db23d3", "mask": "thwvF0DFRk-mV99ruqYFoQ", "swatch": "as9fjqDWiUi1w7yYavKZXA", "decal": ""}, {"item": "adf2aa89-61c4-4b7a-9307-e39e72db23d3", "prefab": "adf2aa89-61c4-4b7a-9307-e39e72db23d3", "mask": "mkNzDvyGfEijbdzAxKYrPQ", "swatch": "sz-J7RjesE-ke3bKwumzZQ", "decal": ""}, {"item": "adf2aa89-61c4-4b7a-9307-e39e72db23d3", "prefab": "adf2aa89-61c4-4b7a-9307-e39e72db23d3", "mask": "mkNzDvyGfEijbdzAxKYrPQ", "swatch": "QlZt51mdn0WuiqKoJaIRyA", "decal": ""}, {"item": "adf2aa89-61c4-4b7a-9307-e39e72db23d3", "prefab": "adf2aa89-61c4-4b7a-9307-e39e72db23d3", "mask": "mkNzDvyGfEijbdzAxKYrPQ", "swatch": "6PyJEQxyd0K9TPRnvt_86Q", "decal": ""}, {"item": "ae180fb1-3e74-4232-9a9d-388fd488eba1", "prefab": "ae180fb1-3e74-4232-9a9d-388fd488eba1", "mask": "", "swatch": "", "decal": ""}, {"item": "ae180fb1-3e74-4232-9a9d-388fd488eba1", "prefab": "ae180fb1-3e74-4232-9a9d-388fd488eba1", "mask": "pgBFpOtl00K2zsTJcfrefQ", "swatch": "nu_UGtkJOUK-5MWaW83MUg", "decal": ""}, {"item": "ae180fb1-3e74-4232-9a9d-388fd488eba1", "prefab": "ae180fb1-3e74-4232-9a9d-388fd488eba1", "mask": "bjXO36Nu6EWD2dU1UC4Uwg", "swatch": "sJioq5ARDUyMHFji_6OxFw", "decal": ""}, {"item": "ae180fb1-3e74-4232-9a9d-388fd488eba1", "prefab": "ae180fb1-3e74-4232-9a9d-388fd488eba1", "mask": "BAjMoTHbD0mSCjoG9jmzjA", "swatch": "56qchovVVEaVwL9S52kneA", "decal": ""}, {"item": "ae180fb1-3e74-4232-9a9d-388fd488eba1", "prefab": "ae180fb1-3e74-4232-9a9d-388fd488eba1", "mask": "rAzhaLh1nEWMd9v1D-HWcQ", "swatch": "zJMD-_HR80WCAUqYGe7_NA", "decal": ""}, {"item": "ae180fb1-3e74-4232-9a9d-388fd488eba1", "prefab": "ae180fb1-3e74-4232-9a9d-388fd488eba1", "mask": "o__JORNtYEiJEyyoBIimPA", "swatch": "9P8ARRUYdk6hCoGMKhix4w", "decal": ""}, {"item": "00f0b914-aa22-4d14-9af1-996e1af61f4d", "prefab": "00f0b914-aa22-4d14-9af1-996e1af61f4d", "mask": "", "swatch": "", "decal": ""}, {"item": "97253092-90ba-4ff2-b137-6324ab244d11", "prefab": "97253092-90ba-4ff2-b137-6324ab244d11", "mask": "", "swatch": "", "decal": ""}, {"item": "97253092-90ba-4ff2-b137-6324ab244d11", "prefab": "97253092-90ba-4ff2-b137-6324ab244d11", "mask": "11ZEZTFhy0ONpoXb3kiewQ", "swatch": "4oywwjb3-kifr-um9x87Pw", "decal": ""}, {"item": "2cf6ceee-4c45-4fb4-b9df-cb8437742e85", "prefab": "2cf6ceee-4c45-4fb4-b9df-cb8437742e85", "mask": "", "swatch": "", "decal": ""}, {"item": "b37582a1-7904-4288-a47a-7a63a3cb28ae", "prefab": "b37582a1-7904-4288-a47a-7a63a3cb28ae", "mask": "", "swatch": "", "decal": ""}, {"item": "936d7126-a3b7-48a2-bceb-6cad9221b267", "prefab": "936d7126-a3b7-48a2-bceb-6cad9221b267", "mask": "", "swatch": "", "decal": ""}, {"item": "88ae6d12-6d39-4770-ac2a-f8c4602a10a5", "prefab": "88ae6d12-6d39-4770-ac2a-f8c4602a10a5", "mask": "", "swatch": "", "decal": ""}, {"item": "380a5e35-b4d0-4119-9721-8ce4c0840fbd", "prefab": "380a5e35-b4d0-4119-9721-8ce4c0840fbd", "mask": "", "swatch": "", "decal": ""}, {"item": "5873814e-c9a1-4042-b96d-2aa0e0d52e2a", "prefab": "5873814e-c9a1-4042-b96d-2aa0e0d52e2a", "mask": "", "swatch": "", "decal": ""}, {"item": "d12e5738-00c7-47e7-9334-8399eec5aa89", "prefab": "d12e5738-00c7-47e7-9334-8399eec5aa89", "mask": "", "swatch": "", "decal": ""}, {"item": "16f4dc71-d35a-4269-b925-1a0cd85a43dd", "prefab": "16f4dc71-d35a-4269-b925-1a0cd85a43dd", "mask": "", "swatch": "", "decal": ""}, {"item": "833e8ab3-9b54-4e15-8f4d-ee1bac5823c9", "prefab": "833e8ab3-9b54-4e15-8f4d-ee1bac5823c9", "mask": "", "swatch": "", "decal": ""}, {"item": "05b7af56-71f2-45ba-a377-c105bf6a6f7a", "prefab": "05b7af56-71f2-45ba-a377-c105bf6a6f7a", "mask": "", "swatch": "", "decal": ""}, {"item": "25769d1f-4ecb-4d92-b692-322b38c013b5", "prefab": "25769d1f-4ecb-4d92-b692-322b38c013b5", "mask": "", "swatch": "", "decal": ""}, {"item": "1a742ff9-c4f7-45d8-8e24-8f24aa021db2", "prefab": "1a742ff9-c4f7-45d8-8e24-8f24aa021db2", "mask": "", "swatch": "", "decal": ""}, {"item": "26f5e849-14b3-4cb3-ab92-673c94b37c51", "prefab": "26f5e849-14b3-4cb3-ab92-673c94b37c51", "mask": "", "swatch": "", "decal": ""}, {"item": "b84f5e9e-11fc-424d-ad39-86901edb9edc", "prefab": "b84f5e9e-11fc-424d-ad39-86901edb9edc", "mask": "", "swatch": "", "decal": ""}, {"item": "9ad40310-7cb9-473d-920c-5c18d7972030", "prefab": "9ad40310-7cb9-473d-920c-5c18d7972030", "mask": "", "swatch": "", "decal": ""}, {"item": "84600de6-93fc-4a89-961e-1f18f4b80643", "prefab": "84600de6-93fc-4a89-961e-1f18f4b80643", "mask": "", "swatch": "", "decal": ""}, {"item": "7e6e24b6-6f9c-4315-b457-73eba52d824c", "prefab": "7e6e24b6-6f9c-4315-b457-73eba52d824c", "mask": "", "swatch": "", "decal": ""}, {"item": "e29d044e-79fb-4280-bca9-aa1e1257e968", "prefab": "e29d044e-79fb-4280-bca9-aa1e1257e968", "mask": "", "swatch": "", "decal": ""}, {"item": "16707919-756d-45ae-95fc-cfb6715b8ae6", "prefab": "16707919-756d-45ae-95fc-cfb6715b8ae6", "mask": "", "swatch": "", "decal": ""}, {"item": "002a0f2f-1a24-4439-b578-470818ef8325", "prefab": "002a0f2f-1a24-4439-b578-470818ef8325", "mask": "", "swatch": "", "decal": ""}, {"item": "2fadddc1-babd-4cb3-8f5a-0e7c889c3785", "prefab": "2fadddc1-babd-4cb3-8f5a-0e7c889c3785", "mask": "", "swatch": "", "decal": ""}, {"item": "f2c1c5e3-b201-4d34-9ce6-3082ff7e5d42", "prefab": "f2c1c5e3-b201-4d34-9ce6-3082ff7e5d42", "mask": "", "swatch": "", "decal": ""}, {"item": "e773fddc-ad9f-408c-82c5-2637f75b3214", "prefab": "e773fddc-ad9f-408c-82c5-2637f75b3214", "mask": "", "swatch": "", "decal": ""}, {"item": "9ffa6e06-5200-4e53-ac13-f8491aecc864", "prefab": "9ffa6e06-5200-4e53-ac13-f8491aecc864", "mask": "", "swatch": "", "decal": ""}, {"item": "2ddd2c2b-596c-4022-a488-655b0db1c265", "prefab": "2ddd2c2b-596c-4022-a488-655b0db1c265", "mask": "", "swatch": "", "decal": ""}, {"item": "ca218759-7eed-440e-b5dc-dba15f81e598", "prefab": "ca218759-7eed-440e-b5dc-dba15f81e598", "mask": "", "swatch": "", "decal": ""}, {"item": "ca218759-7eed-440e-b5dc-dba15f81e598", "prefab": "ca218759-7eed-440e-b5dc-dba15f81e598", "mask": "u9yHtj_tfkuowGp0h_YpFg", "swatch": "ulOBBvFEJ0ioFA1qmL390A", "decal": ""}, {"item": "ca218759-7eed-440e-b5dc-dba15f81e598", "prefab": "ca218759-7eed-440e-b5dc-dba15f81e598", "mask": "u9yHtj_tfkuowGp0h_YpFg", "swatch": "V4CVhCB8eU-nrfYgticVOA", "decal": ""}, {"item": "6fccd476-c621-4f3f-b2e3-45c95be912b5", "prefab": "6fccd476-c621-4f3f-b2e3-45c95be912b5", "mask": "", "swatch": "", "decal": ""}, {"item": "6fccd476-c621-4f3f-b2e3-45c95be912b5", "prefab": "6fccd476-c621-4f3f-b2e3-45c95be912b5", "mask": "X0_GvGrZGkSP9Ub81CHgqQ", "swatch": "v_5qFi-vAUSDd1fLNL7pvA", "decal": ""}, {"item": "6fccd476-c621-4f3f-b2e3-45c95be912b5", "prefab": "6fccd476-c621-4f3f-b2e3-45c95be912b5", "mask": "X0_GvGrZGkSP9Ub81CHgqQ", "swatch": "y5O8zRJy4kuhnXzATSWKew", "decal": ""}, {"item": "9b5bde11-7408-4798-9fcb-c7ec175444df", "prefab": "9b5bde11-7408-4798-9fcb-c7ec175444df", "mask": "", "swatch": "", "decal": ""}, {"item": "0abb6b08-20ce-444f-879e-0d1344df096c", "prefab": "0abb6b08-20ce-444f-879e-0d1344df096c", "mask": "", "swatch": "", "decal": ""}, {"item": "88b6ddeb-a455-460d-91d9-a4569ef6903c", "prefab": "88b6ddeb-a455-460d-91d9-a4569ef6903c", "mask": "", "swatch": "", "decal": ""}, {"item": "46cde502-86d6-4061-a80c-67458ffd4d60", "prefab": "46cde502-86d6-4061-a80c-67458ffd4d60", "mask": "", "swatch": "", "decal": ""}, {"item": "46cde502-86d6-4061-a80c-67458ffd4d60", "prefab": "46cde502-86d6-4061-a80c-67458ffd4d60", "mask": "EckOEU0mJkGIVDvYoKBN9g", "swatch": "crl8VlZ59kGxG01noGEWmA", "decal": ""}, {"item": "686ac516-630f-4989-9c99-0b8d055a00f9", "prefab": "686ac516-630f-4989-9c99-0b8d055a00f9", "mask": "", "swatch": "", "decal": ""}, {"item": "c3ba47d5-a165-4bd6-8479-e5f8d709f4ff", "prefab": "c3ba47d5-a165-4bd6-8479-e5f8d709f4ff", "mask": "", "swatch": "", "decal": ""}, {"item": "61969200-2790-41e0-bccb-d9feff8c7b7d", "prefab": "61969200-2790-41e0-bccb-d9feff8c7b7d", "mask": "", "swatch": "", "decal": ""}, {"item": "34f45c5b-fea7-40b4-add7-bc7a37000ab5", "prefab": "34f45c5b-fea7-40b4-add7-bc7a37000ab5", "mask": "", "swatch": "", "decal": ""}, {"item": "6423f23e-dde3-4428-96f5-6091027c92cb", "prefab": "6423f23e-dde3-4428-96f5-6091027c92cb", "mask": "", "swatch": "", "decal": ""}], + "tutorial_variants": [{"item": "2e59d8d0-91a0-4449-bfdc-a5d663fd9343", "prefab": "2e59d8d0-91a0-4449-bfdc-a5d663fd9343", "mask": "f600037d-c9c0-43fa-b45b-02f456f9dd5f", "swatch": "55901f12-d5b5-4fa8-b4c8-e479689ee39d", "decal": ""}, {"item": "d0a9262f-5504-46a7-bb10-7507503db58e", "prefab": "d0a9262f-5504-46a7-bb10-7507503db58e", "mask": "", "swatch": "", "decal": ""}, {"item": "7b857a8c-92ad-4028-a2c2-b3c20cdab5f2", "prefab": "7b857a8c-92ad-4028-a2c2-b3c20cdab5f2", "mask": "9c03f381-7357-4d0f-8cda-8737d4c43d25", "swatch": "5c4a2b35-0e1c-44de-8c3a-96d4a6458b1b", "decal": ""}], + "default_unlocked_variants": [{"item": "1d27b674-f9e2-4ffc-9d8c-a58a1be06457", "prefab": "1d27b674-f9e2-4ffc-9d8c-a58a1be06457", "mask": "", "swatch": "", "decal": ""}, {"item": "d84c0ff9-8fbe-4ed8-abf3-7996e81888ab", "prefab": "d84c0ff9-8fbe-4ed8-abf3-7996e81888ab", "mask": "", "swatch": "", "decal": ""}, {"item": "e5b83dfc-b2e1-4dcb-a4ab-9d3a4c8a34ae", "prefab": "e5b83dfc-b2e1-4dcb-a4ab-9d3a4c8a34ae", "mask": "", "swatch": "", "decal": ""}, {"item": "7dd6f7b0-7ba0-429f-a04f-e32d3a79ee61", "prefab": "7dd6f7b0-7ba0-429f-a04f-e32d3a79ee61", "mask": "", "swatch": "", "decal": ""}, {"item": "eb9611c6-bb50-41a2-93e9-7f959815a846", "prefab": "eb9611c6-bb50-41a2-93e9-7f959815a846", "mask": "", "swatch": "", "decal": ""}, {"item": "1fd69ef8-0b74-4962-af5a-67f0bf0358f2", "prefab": "1fd69ef8-0b74-4962-af5a-67f0bf0358f2", "mask": "", "swatch": "", "decal": ""}, {"item": "a12f724f-4a73-4ab8-aad4-6bfc662b4dd6", "prefab": "a12f724f-4a73-4ab8-aad4-6bfc662b4dd6", "mask": "", "swatch": "", "decal": ""}, {"item": "0753d7a4-8247-4fca-a6fc-359c26086140", "prefab": "0753d7a4-8247-4fca-a6fc-359c26086140", "mask": "", "swatch": "", "decal": ""}, {"item": "77d3c585-4928-4471-a425-89036efe7299", "prefab": "77d3c585-4928-4471-a425-89036efe7299", "mask": "", "swatch": "", "decal": ""}, {"item": "92302d9d-c527-418c-ac5d-1fa869727505", "prefab": "92302d9d-c527-418c-ac5d-1fa869727505", "mask": "", "swatch": "", "decal": ""}, {"item": "f9dd08f8-16d3-4c39-af4f-89f7bb6e80d3", "prefab": "f9dd08f8-16d3-4c39-af4f-89f7bb6e80d3", "mask": "", "swatch": "", "decal": ""}, {"item": "b148cb1e-df81-442f-aea6-ab1727aad00e", "prefab": "b148cb1e-df81-442f-aea6-ab1727aad00e", "mask": "", "swatch": "", "decal": ""}, {"item": "e36bcd98-7e85-43fa-89f8-57e4ec33823a", "prefab": "e36bcd98-7e85-43fa-89f8-57e4ec33823a", "mask": "", "swatch": "", "decal": ""}, {"item": "880a3cc0-7407-4b61-b759-f9dd890fe9e5", "prefab": "880a3cc0-7407-4b61-b759-f9dd890fe9e5", "mask": "", "swatch": "", "decal": ""}, {"item": "21599b51-c50f-43d8-ac5f-62c30cd02ca5", "prefab": "21599b51-c50f-43d8-ac5f-62c30cd02ca5", "mask": "", "swatch": "", "decal": ""}, {"item": "193a3bf9-abc0-4d78-8d63-92046908b1c5", "prefab": "193a3bf9-abc0-4d78-8d63-92046908b1c5", "mask": "", "swatch": "", "decal": ""}, {"item": "79b90274-6eec-4664-acfb-4a123334661e", "prefab": "79b90274-6eec-4664-acfb-4a123334661e", "mask": "", "swatch": "", "decal": ""}, {"item": "da4e7b34-2095-4a9e-801e-4f409039e0dd", "prefab": "da4e7b34-2095-4a9e-801e-4f409039e0dd", "mask": "", "swatch": "", "decal": ""}, {"item": "9d9fadb6-97eb-480e-a224-4e0179082071", "prefab": "9d9fadb6-97eb-480e-a224-4e0179082071", "mask": "", "swatch": "", "decal": ""}, {"item": "d8280c0c-d803-4513-be10-a0ba96d8821e", "prefab": "d8280c0c-d803-4513-be10-a0ba96d8821e", "mask": "", "swatch": "", "decal": ""}, {"item": "e286863c-2967-4d00-b837-b49487b9484a", "prefab": "e286863c-2967-4d00-b837-b49487b9484a", "mask": "", "swatch": "", "decal": ""}, {"item": "2cb4f372-3372-4583-8b57-c4e3988e3c28", "prefab": "2cb4f372-3372-4583-8b57-c4e3988e3c28", "mask": "", "swatch": "", "decal": ""}, {"item": "06306723-ca20-4aa6-b7b3-917113f41ac3", "prefab": "06306723-ca20-4aa6-b7b3-917113f41ac3", "mask": "", "swatch": "", "decal": ""}, {"item": "c70005d5-6276-4a98-acb3-6a77bc19379a", "prefab": "c70005d5-6276-4a98-acb3-6a77bc19379a", "mask": "", "swatch": "", "decal": ""}, {"item": "8d10cc78-6b00-45f3-affb-205e9cc5b03f", "prefab": "8d10cc78-6b00-45f3-affb-205e9cc5b03f", "mask": "", "swatch": "", "decal": ""}, {"item": "cc96f8a5-bc5b-4f89-83b7-ecd53905ada7", "prefab": "cc96f8a5-bc5b-4f89-83b7-ecd53905ada7", "mask": "", "swatch": "", "decal": ""}, {"item": "c6c08eb5-381a-4193-9722-80da95d62abe", "prefab": "c6c08eb5-381a-4193-9722-80da95d62abe", "mask": "", "swatch": "", "decal": ""}, {"item": "4d507dfa-4a99-4ac0-8537-229e9dc0eb4a", "prefab": "4d507dfa-4a99-4ac0-8537-229e9dc0eb4a", "mask": "", "swatch": "", "decal": ""}, {"item": "d0a9262f-5504-46a7-bb10-7507503db58e", "prefab": "d0a9262f-5504-46a7-bb10-7507503db58e", "mask": "", "swatch": "", "decal": ""}, {"item": "d0a9262f-5504-46a7-bb10-7507503db58e", "prefab": "d0a9262f-5504-46a7-bb10-7507503db58e", "mask": "0440f08f-ef1d-49d8-942b-523056e8bb45", "swatch": "95e4cc30-cb68-473d-a395-feadf5b51512", "decal": ""}, {"item": "2e59d8d0-91a0-4449-bfdc-a5d663fd9343", "prefab": "2e59d8d0-91a0-4449-bfdc-a5d663fd9343", "mask": "", "swatch": "", "decal": ""}, {"item": "7b857a8c-92ad-4028-a2c2-b3c20cdab5f2", "prefab": "7b857a8c-92ad-4028-a2c2-b3c20cdab5f2", "mask": "", "swatch": "", "decal": ""}, {"item": "8aa79563-ace1-4ba7-ad0c-f3210a78142f", "prefab": "8aa79563-ace1-4ba7-ad0c-f3210a78142f", "mask": "", "swatch": "", "decal": ""}, {"item": "8aa79563-ace1-4ba7-ad0c-f3210a78142f", "prefab": "8aa79563-ace1-4ba7-ad0c-f3210a78142f", "mask": "05f0ee6e-c824-470e-9178-5ed576c6fe0c", "swatch": "95e4cc30-cb68-473d-a395-feadf5b51512", "decal": ""}, {"item": "21caa68e-c3fa-474c-af5e-af1e742b7a60", "prefab": "21caa68e-c3fa-474c-af5e-af1e742b7a60", "mask": "", "swatch": "", "decal": ""}, {"item": "21caa68e-c3fa-474c-af5e-af1e742b7a60", "prefab": "21caa68e-c3fa-474c-af5e-af1e742b7a60", "mask": "b75ef67d-00c3-4ac1-9b72-212032460294", "swatch": "c5deba2a-6e35-4b13-8e94-8ba5457f39df", "decal": ""}, {"item": "21caa68e-c3fa-474c-af5e-af1e742b7a60", "prefab": "21caa68e-c3fa-474c-af5e-af1e742b7a60", "mask": "b75ef67d-00c3-4ac1-9b72-212032460294", "swatch": "758752bd-db2f-43d2-b580-55b3e1efffd5", "decal": ""}, {"item": "2296ed0d-df56-4d46-b33a-aae9230a47fc", "prefab": "2296ed0d-df56-4d46-b33a-aae9230a47fc", "mask": "", "swatch": "", "decal": ""}, {"item": "ecc1dbe6-ca06-4564-b2a6-30956194d1e9", "prefab": "ecc1dbe6-ca06-4564-b2a6-30956194d1e9", "mask": "", "swatch": "", "decal": ""}, {"item": "71921831-ba6f-408b-a00e-2fd97663636f", "prefab": "71921831-ba6f-408b-a00e-2fd97663636f", "mask": "", "swatch": "", "decal": ""}, {"item": "2e59d8d0-91a0-4449-bfdc-a5d663fd9343", "prefab": "2e59d8d0-91a0-4449-bfdc-a5d663fd9343", "mask": "f600037d-c9c0-43fa-b45b-02f456f9dd5f", "swatch": "55901f12-d5b5-4fa8-b4c8-e479689ee39d", "decal": ""}, {"item": "2e59d8d0-91a0-4449-bfdc-a5d663fd9343", "prefab": "2e59d8d0-91a0-4449-bfdc-a5d663fd9343", "mask": "d015cae7-a905-49e4-8823-6dec069689a6", "swatch": "bf82f2f6-9af8-431e-a296-0890dea48ba7", "decal": ""}, {"item": "2e59d8d0-91a0-4449-bfdc-a5d663fd9343", "prefab": "2e59d8d0-91a0-4449-bfdc-a5d663fd9343", "mask": "DRJcNhkqvkKFEaZpOguR6w", "swatch": "EfdMcnfHt0mr0PQ_maaYOg", "decal": ""}, {"item": "7b857a8c-92ad-4028-a2c2-b3c20cdab5f2", "prefab": "7b857a8c-92ad-4028-a2c2-b3c20cdab5f2", "mask": "a0271cd0-e172-4d3f-aa2f-9806f21a82d2", "swatch": "6d703981-2734-4c45-8983-cdd5f328902f", "decal": ""}, {"item": "7b857a8c-92ad-4028-a2c2-b3c20cdab5f2", "prefab": "7b857a8c-92ad-4028-a2c2-b3c20cdab5f2", "mask": "9c03f381-7357-4d0f-8cda-8737d4c43d25", "swatch": "5c4a2b35-0e1c-44de-8c3a-96d4a6458b1b", "decal": ""}, {"item": "7b857a8c-92ad-4028-a2c2-b3c20cdab5f2", "prefab": "7b857a8c-92ad-4028-a2c2-b3c20cdab5f2", "mask": "d2a692e6-e1a9-4cfe-8154-10b52be7f8c8", "swatch": "51ef8d39-2b94-4f9e-9620-07b6b0a913a5", "decal": ""}, {"item": "7b857a8c-92ad-4028-a2c2-b3c20cdab5f2", "prefab": "7b857a8c-92ad-4028-a2c2-b3c20cdab5f2", "mask": "b292eb4b-07e3-4a48-99b5-3c6587a1e02e", "swatch": "ad61c418-6d77-4a99-8ac5-9f10f5a3d42f", "decal": ""}, {"item": "7b857a8c-92ad-4028-a2c2-b3c20cdab5f2", "prefab": "7b857a8c-92ad-4028-a2c2-b3c20cdab5f2", "mask": "b78008e8-abbd-4ece-be34-9a911f721fcc", "swatch": "48abd952-214f-48b2-a8f1-1146f6f69aa2", "decal": ""}, {"item": "14ef6b00-debf-4a85-9755-b4d37df496d3", "prefab": "14ef6b00-debf-4a85-9755-b4d37df496d3", "mask": "0b2395e1-ebcc-47e9-aaf1-faf9e9cec4cd", "swatch": "8377ab96-c908-457f-9fee-b784c9a759f3", "decal": ""}, {"item": "14ef6b00-debf-4a85-9755-b4d37df496d3", "prefab": "14ef6b00-debf-4a85-9755-b4d37df496d3", "mask": "0b2395e1-ebcc-47e9-aaf1-faf9e9cec4cd", "swatch": "dee70c38-7a99-4c2b-9181-665f1bf75aca", "decal": ""}, {"item": "40528de7-38a3-4a7c-8f93-6d3bfa5573f2", "prefab": "40528de7-38a3-4a7c-8f93-6d3bfa5573f2", "mask": "0b2395e1-ebcc-47e9-aaf1-faf9e9cec4cd", "swatch": "7d8e55fe-3c34-4b4b-9753-0021f6cc6454", "decal": ""}, {"item": "40528de7-38a3-4a7c-8f93-6d3bfa5573f2", "prefab": "40528de7-38a3-4a7c-8f93-6d3bfa5573f2", "mask": "0b2395e1-ebcc-47e9-aaf1-faf9e9cec4cd", "swatch": "1b1d08f2-12ca-43dd-a44f-ea2820b919b4", "decal": ""}, {"item": "40528de7-38a3-4a7c-8f93-6d3bfa5573f2", "prefab": "40528de7-38a3-4a7c-8f93-6d3bfa5573f2", "mask": "018a5c07-e956-457d-a540-a5e2cd68da09", "swatch": "51ef8d39-2b94-4f9e-9620-07b6b0a913a5", "decal": ""}, {"item": "40528de7-38a3-4a7c-8f93-6d3bfa5573f2", "prefab": "40528de7-38a3-4a7c-8f93-6d3bfa5573f2", "mask": "018a5c07-e956-457d-a540-a5e2cd68da09", "swatch": "cbe29e9f-f2ac-47fb-97e1-8bad16abb89d", "decal": ""}, {"item": "40528de7-38a3-4a7c-8f93-6d3bfa5573f2", "prefab": "40528de7-38a3-4a7c-8f93-6d3bfa5573f2", "mask": "018a5c07-e956-457d-a540-a5e2cd68da09", "swatch": "dee70c38-7a99-4c2b-9181-665f1bf75aca", "decal": ""}, {"item": "40528de7-38a3-4a7c-8f93-6d3bfa5573f2", "prefab": "40528de7-38a3-4a7c-8f93-6d3bfa5573f2", "mask": "56a92c8d-af53-413e-929e-4a9a3cfad780", "swatch": "6dd95046-acf8-42fe-ab78-80a334096a9d", "decal": ""}, {"item": "ecc1dbe6-ca06-4564-b2a6-30956194d1e9", "prefab": "ecc1dbe6-ca06-4564-b2a6-30956194d1e9", "mask": "0b2395e1-ebcc-47e9-aaf1-faf9e9cec4cd", "swatch": "dee70c38-7a99-4c2b-9181-665f1bf75aca", "decal": ""}, {"item": "ecc1dbe6-ca06-4564-b2a6-30956194d1e9", "prefab": "ecc1dbe6-ca06-4564-b2a6-30956194d1e9", "mask": "0b2395e1-ebcc-47e9-aaf1-faf9e9cec4cd", "swatch": "1b1d08f2-12ca-43dd-a44f-ea2820b919b4", "decal": ""}, {"item": "ecc1dbe6-ca06-4564-b2a6-30956194d1e9", "prefab": "ecc1dbe6-ca06-4564-b2a6-30956194d1e9", "mask": "0b2395e1-ebcc-47e9-aaf1-faf9e9cec4cd", "swatch": "51ef8d39-2b94-4f9e-9620-07b6b0a913a5", "decal": ""}, {"item": "ecc1dbe6-ca06-4564-b2a6-30956194d1e9", "prefab": "ecc1dbe6-ca06-4564-b2a6-30956194d1e9", "mask": "0b2395e1-ebcc-47e9-aaf1-faf9e9cec4cd", "swatch": "cbe29e9f-f2ac-47fb-97e1-8bad16abb89d", "decal": ""}, {"item": "ecc1dbe6-ca06-4564-b2a6-30956194d1e9", "prefab": "ecc1dbe6-ca06-4564-b2a6-30956194d1e9", "mask": "0b2395e1-ebcc-47e9-aaf1-faf9e9cec4cd", "swatch": "8377ab96-c908-457f-9fee-b784c9a759f3", "decal": ""}, {"item": "fcfcaf63-deb4-45f7-b711-c051c9ea45cb", "prefab": "fcfcaf63-deb4-45f7-b711-c051c9ea45cb", "mask": "", "swatch": "", "decal": ""}, {"item": "de0ac50d-2adb-4114-bd2e-68953b13d706", "prefab": "de0ac50d-2adb-4114-bd2e-68953b13d706", "mask": "", "swatch": "", "decal": ""}, {"item": "de0ac50d-2adb-4114-bd2e-68953b13d706", "prefab": "de0ac50d-2adb-4114-bd2e-68953b13d706", "mask": "be2b9293-1d3c-4b1c-b4c5-fad3ab16cf54", "swatch": "6f2e74bf-1e95-463d-97db-d5d1a53b2c28", "decal": ""}, {"item": "de0ac50d-2adb-4114-bd2e-68953b13d706", "prefab": "de0ac50d-2adb-4114-bd2e-68953b13d706", "mask": "be2b9293-1d3c-4b1c-b4c5-fad3ab16cf54", "swatch": "9374bf66-2ee5-493b-8439-efce4b201904", "decal": ""}, {"item": "de0ac50d-2adb-4114-bd2e-68953b13d706", "prefab": "de0ac50d-2adb-4114-bd2e-68953b13d706", "mask": "be2b9293-1d3c-4b1c-b4c5-fad3ab16cf54", "swatch": "272fe8eb-5061-4729-a7a8-414ff667a82f", "decal": ""}, {"item": "de0ac50d-2adb-4114-bd2e-68953b13d706", "prefab": "de0ac50d-2adb-4114-bd2e-68953b13d706", "mask": "be2b9293-1d3c-4b1c-b4c5-fad3ab16cf54", "swatch": "0ffad843-d6c9-425a-8686-7217009c867e", "decal": ""}, {"item": "9c8fc7f0-8f99-4aad-a34f-8d979f6ae352", "prefab": "9c8fc7f0-8f99-4aad-a34f-8d979f6ae352", "mask": "dafa658e-753b-46cb-bd85-85c1de5e6ea7", "swatch": "e0397982-c2c2-4733-9a40-46e18675b5af", "decal": ""}, {"item": "9c8fc7f0-8f99-4aad-a34f-8d979f6ae352", "prefab": "9c8fc7f0-8f99-4aad-a34f-8d979f6ae352", "mask": "", "swatch": "", "decal": ""}, {"item": "9c8fc7f0-8f99-4aad-a34f-8d979f6ae352", "prefab": "9c8fc7f0-8f99-4aad-a34f-8d979f6ae352", "mask": "dafa658e-753b-46cb-bd85-85c1de5e6ea7", "swatch": "49f5864f-9d40-497c-88c8-e87f64d41d74", "decal": ""}, {"item": "9c8fc7f0-8f99-4aad-a34f-8d979f6ae352", "prefab": "9c8fc7f0-8f99-4aad-a34f-8d979f6ae352", "mask": "dafa658e-753b-46cb-bd85-85c1de5e6ea7", "swatch": "c5deba2a-6e35-4b13-8e94-8ba5457f39df", "decal": ""}, {"item": "6d815b35-6f68-4ed4-817d-70f141e1a571", "prefab": "6d815b35-6f68-4ed4-817d-70f141e1a571", "mask": "2c8924aa-68f8-4912-9759-18992f72f08a", "swatch": "f750de46-3758-4f7d-9709-0a84b1027009", "decal": ""}, {"item": "6d815b35-6f68-4ed4-817d-70f141e1a571", "prefab": "6d815b35-6f68-4ed4-817d-70f141e1a571", "mask": "2c8924aa-68f8-4912-9759-18992f72f08a", "swatch": "d66aa400-aa5a-4539-a25d-5f8ce94dc281", "decal": ""}, {"item": "6d815b35-6f68-4ed4-817d-70f141e1a571", "prefab": "6d815b35-6f68-4ed4-817d-70f141e1a571", "mask": "2c8924aa-68f8-4912-9759-18992f72f08a", "swatch": "6564acf1-4d70-4f92-92ac-08e2b76dbb6b", "decal": ""}, {"item": "6d815b35-6f68-4ed4-817d-70f141e1a571", "prefab": "6d815b35-6f68-4ed4-817d-70f141e1a571", "mask": "", "swatch": "", "decal": ""}, {"item": "241506f6-bf88-4b46-b5fe-513a225421f4", "prefab": "241506f6-bf88-4b46-b5fe-513a225421f4", "mask": "", "swatch": "", "decal": ""}, {"item": "6b9e022c-0b68-48fd-8eca-da8573c18900", "prefab": "6b9e022c-0b68-48fd-8eca-da8573c18900", "mask": "cf119781-5bd9-4b85-9a0b-12e82e988c23", "swatch": "d6edbc00-3c1d-4f49-8412-3ef8c7c5f4c2", "decal": ""}, {"item": "2296ed0d-df56-4d46-b33a-aae9230a47fc", "prefab": "2296ed0d-df56-4d46-b33a-aae9230a47fc", "mask": "cfabdefe-0890-436e-b2a3-b5c712e22955", "swatch": "6d703981-2734-4c45-8983-cdd5f328902f", "decal": ""}, {"item": "2296ed0d-df56-4d46-b33a-aae9230a47fc", "prefab": "2296ed0d-df56-4d46-b33a-aae9230a47fc", "mask": "cfabdefe-0890-436e-b2a3-b5c712e22955", "swatch": "830be2fa-60a5-48cc-931f-34b670eae4bd", "decal": ""}, {"item": "2296ed0d-df56-4d46-b33a-aae9230a47fc", "prefab": "2296ed0d-df56-4d46-b33a-aae9230a47fc", "mask": "cfabdefe-0890-436e-b2a3-b5c712e22955", "swatch": "bbfa08e3-8e6b-4e0f-b264-1b398d7cd44a", "decal": ""}, {"item": "ecc1dbe6-ca06-4564-b2a6-30956194d1e9", "prefab": "ecc1dbe6-ca06-4564-b2a6-30956194d1e9", "mask": "0b2395e1-ebcc-47e9-aaf1-faf9e9cec4cd", "swatch": "484b6c13-af22-4ad5-8c43-34c0de095d49", "decal": ""}, {"item": "ecc1dbe6-ca06-4564-b2a6-30956194d1e9", "prefab": "ecc1dbe6-ca06-4564-b2a6-30956194d1e9", "mask": "0b2395e1-ebcc-47e9-aaf1-faf9e9cec4cd", "swatch": "f8b0cfe8-e129-4578-8bb5-f60af5d38599", "decal": ""}, {"item": "ecc1dbe6-ca06-4564-b2a6-30956194d1e9", "prefab": "ecc1dbe6-ca06-4564-b2a6-30956194d1e9", "mask": "0b2395e1-ebcc-47e9-aaf1-faf9e9cec4cd", "swatch": "67bcca75-4ab1-4964-8688-9908c464d355", "decal": ""}, {"item": "7b857a8c-92ad-4028-a2c2-b3c20cdab5f2", "prefab": "7b857a8c-92ad-4028-a2c2-b3c20cdab5f2", "mask": "0b2395e1-ebcc-47e9-aaf1-faf9e9cec4cd", "swatch": "1b1d08f2-12ca-43dd-a44f-ea2820b919b4", "decal": ""}, {"item": "7b857a8c-92ad-4028-a2c2-b3c20cdab5f2", "prefab": "7b857a8c-92ad-4028-a2c2-b3c20cdab5f2", "mask": "0b2395e1-ebcc-47e9-aaf1-faf9e9cec4cd", "swatch": "dee70c38-7a99-4c2b-9181-665f1bf75aca", "decal": ""}, {"item": "7b857a8c-92ad-4028-a2c2-b3c20cdab5f2", "prefab": "7b857a8c-92ad-4028-a2c2-b3c20cdab5f2", "mask": "0b2395e1-ebcc-47e9-aaf1-faf9e9cec4cd", "swatch": "51ef8d39-2b94-4f9e-9620-07b6b0a913a5", "decal": ""}, {"item": "7b857a8c-92ad-4028-a2c2-b3c20cdab5f2", "prefab": "7b857a8c-92ad-4028-a2c2-b3c20cdab5f2", "mask": "0b2395e1-ebcc-47e9-aaf1-faf9e9cec4cd", "swatch": "8377ab96-c908-457f-9fee-b784c9a759f3", "decal": ""}, {"item": "6b9e022c-0b68-48fd-8eca-da8573c18900", "prefab": "6b9e022c-0b68-48fd-8eca-da8573c18900", "mask": "cf119781-5bd9-4b85-9a0b-12e82e988c23", "swatch": "5c4a2b35-0e1c-44de-8c3a-96d4a6458b1b", "decal": ""}, {"item": "6b9e022c-0b68-48fd-8eca-da8573c18900", "prefab": "6b9e022c-0b68-48fd-8eca-da8573c18900", "mask": "cf119781-5bd9-4b85-9a0b-12e82e988c23", "swatch": "6dd95046-acf8-42fe-ab78-80a334096a9d", "decal": ""}, {"item": "40528de7-38a3-4a7c-8f93-6d3bfa5573f2", "prefab": "40528de7-38a3-4a7c-8f93-6d3bfa5573f2", "mask": "0b2395e1-ebcc-47e9-aaf1-faf9e9cec4cd", "swatch": "dee70c38-7a99-4c2b-9181-665f1bf75aca", "decal": ""}, {"item": "40528de7-38a3-4a7c-8f93-6d3bfa5573f2", "prefab": "40528de7-38a3-4a7c-8f93-6d3bfa5573f2", "mask": "0b2395e1-ebcc-47e9-aaf1-faf9e9cec4cd", "swatch": "f8b0cfe8-e129-4578-8bb5-f60af5d38599", "decal": ""}, {"item": "40528de7-38a3-4a7c-8f93-6d3bfa5573f2", "prefab": "40528de7-38a3-4a7c-8f93-6d3bfa5573f2", "mask": "0b2395e1-ebcc-47e9-aaf1-faf9e9cec4cd", "swatch": "8377ab96-c908-457f-9fee-b784c9a759f3", "decal": ""}, {"item": "40528de7-38a3-4a7c-8f93-6d3bfa5573f2", "prefab": "40528de7-38a3-4a7c-8f93-6d3bfa5573f2", "mask": "", "swatch": "", "decal": ""}, {"item": "40528de7-38a3-4a7c-8f93-6d3bfa5573f2", "prefab": "40528de7-38a3-4a7c-8f93-6d3bfa5573f2", "mask": "0b2395e1-ebcc-47e9-aaf1-faf9e9cec4cd", "swatch": "67bcca75-4ab1-4964-8688-9908c464d355", "decal": ""}, {"item": "24a240f4-1574-420b-b898-a7e91f170759", "prefab": "24a240f4-1574-420b-b898-a7e91f170759", "mask": "", "swatch": "", "decal": ""}, {"item": "c45ed7b8-99bd-4a4b-a9ff-e16edf5d7a18", "prefab": "c45ed7b8-99bd-4a4b-a9ff-e16edf5d7a18", "mask": "", "swatch": "", "decal": ""}, {"item": "14ef6b00-debf-4a85-9755-b4d37df496d3", "prefab": "14ef6b00-debf-4a85-9755-b4d37df496d3", "mask": "0b2395e1-ebcc-47e9-aaf1-faf9e9cec4cd", "swatch": "484b6c13-af22-4ad5-8c43-34c0de095d49", "decal": ""}, {"item": "14ef6b00-debf-4a85-9755-b4d37df496d3", "prefab": "14ef6b00-debf-4a85-9755-b4d37df496d3", "mask": "0b2395e1-ebcc-47e9-aaf1-faf9e9cec4cd", "swatch": "1b1d08f2-12ca-43dd-a44f-ea2820b919b4", "decal": ""}, {"item": "14ef6b00-debf-4a85-9755-b4d37df496d3", "prefab": "14ef6b00-debf-4a85-9755-b4d37df496d3", "mask": "0b2395e1-ebcc-47e9-aaf1-faf9e9cec4cd", "swatch": "51ef8d39-2b94-4f9e-9620-07b6b0a913a5", "decal": ""}, {"item": "14ef6b00-debf-4a85-9755-b4d37df496d3", "prefab": "14ef6b00-debf-4a85-9755-b4d37df496d3", "mask": "", "swatch": "", "decal": ""}, {"item": "14ef6b00-debf-4a85-9755-b4d37df496d3", "prefab": "14ef6b00-debf-4a85-9755-b4d37df496d3", "mask": "0b2395e1-ebcc-47e9-aaf1-faf9e9cec4cd", "swatch": "67bcca75-4ab1-4964-8688-9908c464d355", "decal": ""}, {"item": "896c2491-2f96-4986-9cbd-b3b31ef5d8c5", "prefab": "896c2491-2f96-4986-9cbd-b3b31ef5d8c5", "mask": "", "swatch": "", "decal": ""}, {"item": "896c2491-2f96-4986-9cbd-b3b31ef5d8c5", "prefab": "896c2491-2f96-4986-9cbd-b3b31ef5d8c5", "mask": "d344b8cc-85a8-4ace-9f92-38c84f396e99", "swatch": "55901f12-d5b5-4fa8-b4c8-e479689ee39d", "decal": ""}, {"item": "896c2491-2f96-4986-9cbd-b3b31ef5d8c5", "prefab": "896c2491-2f96-4986-9cbd-b3b31ef5d8c5", "mask": "d344b8cc-85a8-4ace-9f92-38c84f396e99", "swatch": "4828b50c-95b6-466a-bb25-514891d78202", "decal": ""}, {"item": "896c2491-2f96-4986-9cbd-b3b31ef5d8c5", "prefab": "896c2491-2f96-4986-9cbd-b3b31ef5d8c5", "mask": "d344b8cc-85a8-4ace-9f92-38c84f396e99", "swatch": "d6823e01-69f0-4f85-b94a-74894356a2cf", "decal": ""}, {"item": "09177621-9ecd-4f6a-b6a5-64490139141d", "prefab": "09177621-9ecd-4f6a-b6a5-64490139141d", "mask": "", "swatch": "", "decal": ""}, {"item": "95ab7a7c-c35d-4da5-9955-0921064470b6", "prefab": "95ab7a7c-c35d-4da5-9955-0921064470b6", "mask": "", "swatch": "", "decal": ""}, {"item": "40528de7-38a3-4a7c-8f93-6d3bfa5573f2", "prefab": "40528de7-38a3-4a7c-8f93-6d3bfa5573f2", "mask": "0b2395e1-ebcc-47e9-aaf1-faf9e9cec4cd", "swatch": "0ecb8a2a-cffc-47db-aeda-fb0684aef1e5", "decal": ""}, {"item": "40528de7-38a3-4a7c-8f93-6d3bfa5573f2", "prefab": "40528de7-38a3-4a7c-8f93-6d3bfa5573f2", "mask": "0b2395e1-ebcc-47e9-aaf1-faf9e9cec4cd", "swatch": "484b6c13-af22-4ad5-8c43-34c0de095d49", "decal": ""}, {"item": "40528de7-38a3-4a7c-8f93-6d3bfa5573f2", "prefab": "40528de7-38a3-4a7c-8f93-6d3bfa5573f2", "mask": "0b2395e1-ebcc-47e9-aaf1-faf9e9cec4cd", "swatch": "51ef8d39-2b94-4f9e-9620-07b6b0a913a5", "decal": ""}, {"item": "40528de7-38a3-4a7c-8f93-6d3bfa5573f2", "prefab": "40528de7-38a3-4a7c-8f93-6d3bfa5573f2", "mask": "0b2395e1-ebcc-47e9-aaf1-faf9e9cec4cd", "swatch": "cbe29e9f-f2ac-47fb-97e1-8bad16abb89d", "decal": ""}, {"item": "40528de7-38a3-4a7c-8f93-6d3bfa5573f2", "prefab": "40528de7-38a3-4a7c-8f93-6d3bfa5573f2", "mask": "018a5c07-e956-457d-a540-a5e2cd68da09", "swatch": "8377ab96-c908-457f-9fee-b784c9a759f3", "decal": ""}, {"item": "62ce4109-8dee-4895-bf1b-bfa143db4c7e", "prefab": "62ce4109-8dee-4895-bf1b-bfa143db4c7e", "mask": "", "swatch": "", "decal": ""}, {"item": "62ce4109-8dee-4895-bf1b-bfa143db4c7e", "prefab": "62ce4109-8dee-4895-bf1b-bfa143db4c7e", "mask": "0f36bb97-c61b-4281-929f-ff1d0d11be86", "swatch": "cd5d7285-202d-42d0-b93f-04245875793e", "decal": ""}, {"item": "62ce4109-8dee-4895-bf1b-bfa143db4c7e", "prefab": "62ce4109-8dee-4895-bf1b-bfa143db4c7e", "mask": "0f36bb97-c61b-4281-929f-ff1d0d11be86", "swatch": "ad61c418-6d77-4a99-8ac5-9f10f5a3d42f", "decal": ""}, {"item": "8b9f1413-e786-4a30-946c-9292f207875a", "prefab": "8b9f1413-e786-4a30-946c-9292f207875a", "mask": "", "swatch": "", "decal": ""}, {"item": "5cd08cfb-c729-4c30-96d9-6a99bb934d91", "prefab": "5cd08cfb-c729-4c30-96d9-6a99bb934d91", "mask": "", "swatch": "", "decal": ""}, {"item": "1a71064b-794f-40fa-9109-8ad36602b6e1", "prefab": "1a71064b-794f-40fa-9109-8ad36602b6e1", "mask": "", "swatch": "", "decal": ""}, {"item": "84cd594c-1cd8-4b4d-8409-85c8fd5fb02a", "prefab": "84cd594c-1cd8-4b4d-8409-85c8fd5fb02a", "mask": "91a451c1-b285-4c48-b14d-59ded8cc006f", "swatch": "761a3193-60f0-4190-80c7-285b8192e794", "decal": ""}, {"item": "84cd594c-1cd8-4b4d-8409-85c8fd5fb02a", "prefab": "84cd594c-1cd8-4b4d-8409-85c8fd5fb02a", "mask": "91a451c1-b285-4c48-b14d-59ded8cc006f", "swatch": "a819f49b-6c7a-49d3-9e6a-d9d79ef5019f", "decal": ""}, {"item": "84cd594c-1cd8-4b4d-8409-85c8fd5fb02a", "prefab": "84cd594c-1cd8-4b4d-8409-85c8fd5fb02a", "mask": "91a451c1-b285-4c48-b14d-59ded8cc006f", "swatch": "64850553-cdfe-455a-ac00-dafbe63d613e", "decal": ""}, {"item": "84cd594c-1cd8-4b4d-8409-85c8fd5fb02a", "prefab": "84cd594c-1cd8-4b4d-8409-85c8fd5fb02a", "mask": "", "swatch": "", "decal": ""}, {"item": "71921831-ba6f-408b-a00e-2fd97663636f", "prefab": "71921831-ba6f-408b-a00e-2fd97663636f", "mask": "0b2395e1-ebcc-47e9-aaf1-faf9e9cec4cd", "swatch": "1b1d08f2-12ca-43dd-a44f-ea2820b919b4", "decal": ""}, {"item": "71921831-ba6f-408b-a00e-2fd97663636f", "prefab": "71921831-ba6f-408b-a00e-2fd97663636f", "mask": "0b2395e1-ebcc-47e9-aaf1-faf9e9cec4cd", "swatch": "7d8e55fe-3c34-4b4b-9753-0021f6cc6454", "decal": ""}, {"item": "ecc1dbe6-ca06-4564-b2a6-30956194d1e9", "prefab": "ecc1dbe6-ca06-4564-b2a6-30956194d1e9", "mask": "0b2395e1-ebcc-47e9-aaf1-faf9e9cec4cd", "swatch": "0ecb8a2a-cffc-47db-aeda-fb0684aef1e5", "decal": ""}, {"item": "ecc1dbe6-ca06-4564-b2a6-30956194d1e9", "prefab": "ecc1dbe6-ca06-4564-b2a6-30956194d1e9", "mask": "0b2395e1-ebcc-47e9-aaf1-faf9e9cec4cd", "swatch": "7d8e55fe-3c34-4b4b-9753-0021f6cc6454", "decal": ""}, {"item": "6b9e022c-0b68-48fd-8eca-da8573c18900", "prefab": "6b9e022c-0b68-48fd-8eca-da8573c18900", "mask": "", "swatch": "", "decal": ""}, {"item": "2e59d8d0-91a0-4449-bfdc-a5d663fd9343", "prefab": "2e59d8d0-91a0-4449-bfdc-a5d663fd9343", "mask": "PioQ0o3yP0a6szPZ4EKs2A", "swatch": "0iSsaY-HgkmLaRHCn5vEdw", "decal": ""}, {"item": "2e59d8d0-91a0-4449-bfdc-a5d663fd9343", "prefab": "2e59d8d0-91a0-4449-bfdc-a5d663fd9343", "mask": "PioQ0o3yP0a6szPZ4EKs2A", "swatch": "jGj28vhq8EGwP2RuM074aQ", "decal": ""}, {"item": "2e59d8d0-91a0-4449-bfdc-a5d663fd9343", "prefab": "2e59d8d0-91a0-4449-bfdc-a5d663fd9343", "mask": "PioQ0o3yP0a6szPZ4EKs2A", "swatch": "kmj5zOjcwku_WWKroCeiVQ", "decal": ""}, {"item": "2e59d8d0-91a0-4449-bfdc-a5d663fd9343", "prefab": "2e59d8d0-91a0-4449-bfdc-a5d663fd9343", "mask": "PioQ0o3yP0a6szPZ4EKs2A", "swatch": "FAviMCQ_EE2Mpt6QPo5OEw", "decal": ""}, {"item": "2e59d8d0-91a0-4449-bfdc-a5d663fd9343", "prefab": "2e59d8d0-91a0-4449-bfdc-a5d663fd9343", "mask": "PioQ0o3yP0a6szPZ4EKs2A", "swatch": "MFrcSQ1DYUm8imvy4ypgvw", "decal": ""}, {"item": "de0ac50d-2adb-4114-bd2e-68953b13d706", "prefab": "de0ac50d-2adb-4114-bd2e-68953b13d706", "mask": "be2b9293-1d3c-4b1c-b4c5-fad3ab16cf54", "swatch": "05ac07e1-67f0-486c-abf5-a62866475abb", "decal": ""}, {"item": "0088603e-ec3b-4478-8694-e6fb1989b3f2", "prefab": "0088603e-ec3b-4478-8694-e6fb1989b3f2", "mask": "", "swatch": "", "decal": ""}, {"item": "ffea7a65-613f-4835-921e-6dd15f357b7e", "prefab": "ffea7a65-613f-4835-921e-6dd15f357b7e", "mask": "", "swatch": "", "decal": ""}, {"item": "45f5e714-8a5f-4385-a97f-675066167011", "prefab": "45f5e714-8a5f-4385-a97f-675066167011", "mask": "", "swatch": "", "decal": ""}, {"item": "9bf5d259-7774-4cbe-a90f-7f188cc0dce7", "prefab": "9bf5d259-7774-4cbe-a90f-7f188cc0dce7", "mask": "", "swatch": "", "decal": ""}, {"item": "a6cbfe76-534a-4655-a8a8-3fed13d001c7", "prefab": "a6cbfe76-534a-4655-a8a8-3fed13d001c7", "mask": "", "swatch": "", "decal": ""}, {"item": "CTcrvbo3OEepIV4oW8bx4w", "prefab": "CTcrvbo3OEepIV4oW8bx4w", "mask": "", "swatch": "", "decal": ""}, {"item": "-twtjyBdQ02EAdOfBGTiEw", "prefab": "-twtjyBdQ02EAdOfBGTiEw", "mask": "", "swatch": "", "decal": ""}, {"item": "45eaab67-19c2-4601-8f80-3565a4dceba4", "prefab": "45eaab67-19c2-4601-8f80-3565a4dceba4", "mask": "", "swatch": "", "decal": ""}, {"item": "c855dcc3-96cb-470d-b159-d37a025a47d1", "prefab": "c855dcc3-96cb-470d-b159-d37a025a47d1", "mask": "", "swatch": "", "decal": ""}, {"item": "d7730a9e-78a1-4356-bc09-6b066615850b", "prefab": "d7730a9e-78a1-4356-bc09-6b066615850b", "mask": "", "swatch": "", "decal": ""}, {"item": "8c35c804-e8d5-49d2-8d5a-ea19fb70bfa6", "prefab": "8c35c804-e8d5-49d2-8d5a-ea19fb70bfa6", "mask": "", "swatch": "", "decal": ""}, {"item": "5beeb4c4-f276-4eae-87aa-9302e45b05b7", "prefab": "5beeb4c4-f276-4eae-87aa-9302e45b05b7", "mask": "", "swatch": "", "decal": ""}, {"item": "b6rLwzD4NkKV7xKn9ZYVkA", "prefab": "b6rLwzD4NkKV7xKn9ZYVkA", "mask": "tlpa7195x0CkmSjpR1RArQ", "swatch": "sxUE0iOSZEmezm54T7xI3Q", "decal": ""}, {"item": "fe15ca53-c5b8-4acf-9309-ff3f4e610fc9", "prefab": "fe15ca53-c5b8-4acf-9309-ff3f4e610fc9", "mask": "", "swatch": "", "decal": ""}, {"item": "b6rLwzD4NkKV7xKn9ZYVkA", "prefab": "b6rLwzD4NkKV7xKn9ZYVkA", "mask": "tnCJp2eDI0SwjVfJMhk3LQ", "swatch": "D_Xmo0rOzkS-kgq1CYXt3g", "decal": ""}, {"item": "fe15ca53-c5b8-4acf-9309-ff3f4e610fc9", "prefab": "fe15ca53-c5b8-4acf-9309-ff3f4e610fc9", "mask": "1yMyo6oTjU-VAygoeWaohQ", "swatch": "knXPidb-Rkayfc3kSHfZeQ", "decal": ""}], + "initial_variants": [{"item": "1fd69ef8-0b74-4962-af5a-67f0bf0358f2", "prefab": "1fd69ef8-0b74-4962-af5a-67f0bf0358f2", "mask": "", "swatch": "", "decal": ""}, {"item": "9d9fadb6-97eb-480e-a224-4e0179082071", "prefab": "9d9fadb6-97eb-480e-a224-4e0179082071", "mask": "", "swatch": "", "decal": ""}, {"item": "880a3cc0-7407-4b61-b759-f9dd890fe9e5", "prefab": "880a3cc0-7407-4b61-b759-f9dd890fe9e5", "mask": "", "swatch": "", "decal": ""}, {"item": "193a3bf9-abc0-4d78-8d63-92046908b1c5", "prefab": "193a3bf9-abc0-4d78-8d63-92046908b1c5", "mask": "", "swatch": "", "decal": ""}, {"item": "92302d9d-c527-418c-ac5d-1fa869727505", "prefab": "92302d9d-c527-418c-ac5d-1fa869727505", "mask": "", "swatch": "", "decal": ""}, {"item": "b148cb1e-df81-442f-aea6-ab1727aad00e", "prefab": "b148cb1e-df81-442f-aea6-ab1727aad00e", "mask": "", "swatch": "", "decal": ""}, {"item": "8aa79563-ace1-4ba7-ad0c-f3210a78142f", "prefab": "8aa79563-ace1-4ba7-ad0c-f3210a78142f", "mask": "", "swatch": "", "decal": ""}, {"item": "d0a9262f-5504-46a7-bb10-7507503db58e", "prefab": "d0a9262f-5504-46a7-bb10-7507503db58e", "mask": "0440f08f-ef1d-49d8-942b-523056e8bb45", "swatch": "95e4cc30-cb68-473d-a395-feadf5b51512", "decal": ""}, {"item": "8aa79563-ace1-4ba7-ad0c-f3210a78142f", "prefab": "8aa79563-ace1-4ba7-ad0c-f3210a78142f", "mask": "05f0ee6e-c824-470e-9178-5ed576c6fe0c", "swatch": "95e4cc30-cb68-473d-a395-feadf5b51512", "decal": ""}], + "default_torso_variants": [{"item": "8aa79563-ace1-4ba7-ad0c-f3210a78142f", "prefab": "8aa79563-ace1-4ba7-ad0c-f3210a78142f", "mask": "", "swatch": "", "decal": ""}], + "prefab_guids": ["-SQLtL4rdEWTJdw2IroQCQ", "-twtjyBdQ02EAdOfBGTiEw", "002a0f2f-1a24-4439-b578-470818ef8325", "0088603e-ec3b-4478-8694-e6fb1989b3f2", "00f0b914-aa22-4d14-9af1-996e1af61f4d", "01689baf-405e-4b38-9f09-b624e03865b8", "02077915-57d3-4e26-b4fe-8e0399cbb293", "022ce375-ed02-479d-b87d-9d72d126bbd4", "02a5b48c-abba-4385-967b-ba900424829e", "033e328f-d9d2-4e53-af20-44f18e3ea208", "03f8c394-28fa-4087-978b-8d108f0bd969", "05b7af56-71f2-45ba-a377-c105bf6a6f7a", "05ec4e87-088f-4281-a118-2e63c2585200", "06306723-ca20-4aa6-b7b3-917113f41ac3", "07205057-0804-41de-abe4-bd4027fee1df", "0753d7a4-8247-4fca-a6fc-359c26086140", "08d56627-4500-4724-8692-a23c96ddb3ec", "09177621-9ecd-4f6a-b6a5-64490139141d", "097a3266-4aab-4b23-8f56-373c9b1bc837", "0ab5790f-a537-4306-ba66-5eb87c02ec7d", "0ab6c95c-e157-48bd-a172-2642896b4ffc", "0abb6b08-20ce-444f-879e-0d1344df096c", "0bc6bd1b-8b7e-4945-a1d6-c26b9c043318", "0c537a84-9095-406f-8c87-1464f3a774ba", "0d4e7ebe-efd9-4e68-9900-5645916e7596", "0QOlT9c5xUuinpfZaoEe8Q", "0yFzgVESokC3gFd3hrS8bg", "1014dded-3bbc-479b-a6cb-afa7413de2da", "102c625b-b988-4bf8-a2aa-a31ad7029cdc", "1041d745-c5e9-4218-a3e1-198f5c9c418c", "105a5100-cb57-410f-a26b-1ee21002f067", "11e508a7-fe7d-470e-b270-2dba33e2f7a1", "123de433-a005-4994-8a11-d2d84ad66b11", "125a3a62-1005-47dc-9fd8-ee09ea803e9f", "13e70c80-69a4-4ad5-90e5-4dd5e03da677", "14ef6b00-debf-4a85-9755-b4d37df496d3", "1596c113-aa3a-4f5c-b398-e5c3575548e7", "16359b51-96c4-4be0-b598-319c7549e18a", "16707919-756d-45ae-95fc-cfb6715b8ae6", "16f4dc71-d35a-4269-b925-1a0cd85a43dd", "17236d76-e3b6-42f6-9179-6fcf0479436f", "1736a60b-6fb6-49bd-948a-fd3f3fc1be50", "17aed0a0-70ed-49da-ad09-5bc4746f7718", "18cd3470-2dd9-4a0a-98bd-eae2d5f5d522", "18feb3c2-049f-4f91-9f4e-6b5fcb4ab70c", "193a3bf9-abc0-4d78-8d63-92046908b1c5", "1974dd48-aa44-434f-879a-eaff0c7c06e6", "1988692c-aae3-450b-acd6-e9015b831f5c", "1a028850-64ee-40ab-b547-ebbcfa1b04b3", "1a71064b-794f-40fa-9109-8ad36602b6e1", "1a742ff9-c4f7-45d8-8e24-8f24aa021db2", "1b37d6cd-0a89-4604-a046-e5cff55b0cf6", "1beaf288-b5a5-475c-8149-c8f3d1146083", "1d27b674-f9e2-4ffc-9d8c-a58a1be06457", "1e504fee-b593-4037-bd8f-b88025147991", "1ebbfdfc-f72b-4d70-99d4-4d527f896aa7", "1fcaf4fd-9012-407e-b691-fbc3ccf22421", "1fd69ef8-0b74-4962-af5a-67f0bf0358f2", "204bc384-e6d2-41b8-8ce9-0b26be5d85b7", "20b424a9-861a-4d39-a583-22d7b7348c14", "21599b51-c50f-43d8-ac5f-62c30cd02ca5", "21caa68e-c3fa-474c-af5e-af1e742b7a60", "2296ed0d-df56-4d46-b33a-aae9230a47fc", "22ef1089-d073-4fe8-b038-6f34adf0b85f", "22pyI3Kz-UullhcebC6DLQ", "241506f6-bf88-4b46-b5fe-513a225421f4", "24a240f4-1574-420b-b898-a7e91f170759", "24b8b757-4168-4b3b-ba11-46f29d19044d", "24c4404a-839f-46bd-98b6-6848d747a98b", "24d94673-f394-42e6-a3c8-276f8b8aa722", "250bbb53-1fae-440d-b25a-46a16c271367", "25769d1f-4ecb-4d92-b692-322b38c013b5", "25d4c652-1730-4925-925e-31a290a2272a", "26f5e849-14b3-4cb3-ab92-673c94b37c51", "274cb9b2-2f59-47ea-9a8d-a5b656d148c6", "2786fc22-3d6c-440d-afcc-15890c061577", "27c2eb7f-9fac-4c42-90c7-44e7a33d5303", "27cdecd2-88f1-40a8-80a9-87abd827c3c5", "27e742bf-3081-4274-ba70-301f0edeb4f1", "28374ebc-4050-4ea0-b3b6-41ebe75cedf7", "28e5dd74-7963-42cf-aa68-f8005aaf312e", "293e7ad3-01c9-43af-96e3-06c34145ff12", "2b2eb9e5-d52e-4004-bdb2-0bcf6910992d", "2b7f4dae-51da-4af0-b009-12d6005c85c6", "2bd53190-46f8-4956-9388-87c5a04efc4f", "2c3773a5-a0b4-41c5-a4df-6572dff516c9", "2c679f89-c76e-4cfb-94e9-448c8fd44d55", "2caHvVQ1vECrPv-YcdxdyA", "2cb4f372-3372-4583-8b57-c4e3988e3c28", "2cf6ceee-4c45-4fb4-b9df-cb8437742e85", "2ddd2c2b-596c-4022-a488-655b0db1c265", "2dFMy4zc7ky_Fd5fzfgInw", "2e59d8d0-91a0-4449-bfdc-a5d663fd9343", "2e5afc40-7e25-418c-8b1b-316b8ef479a7", "2f8518fc-c989-40de-98b4-c6f09b304166", "2faa26b6-f7c3-4b0d-8f70-1f9454e64a54", "2fadddc1-babd-4cb3-8f5a-0e7c889c3785", "30380607-8b54-4b63-81d3-cb3f08e452c1", "3044adce-90c0-4f66-81f5-39c4fca86fdf", "31bb82db-cd30-4988-a8bf-3ce55a12a4c9", "34144f4c-f6ef-49e5-a286-1781e82e6782", "34654303-9760-447f-8614-1c26506db994", "348ac48b-986e-4d42-a0f7-1aea16b88271", "34d45669-b319-4c7d-bd05-078961d70d0b", "34f45c5b-fea7-40b4-add7-bc7a37000ab5", "350c5c1e-318b-4233-a7e5-1f49909d8e65", "35f60d27-22cf-4e17-8f39-c9e3a9fa2291", "3757149b-f478-4975-81e5-d808a1bed8d5", "37668d3d-c9f4-4bd6-81d4-a4f64ba2d61b", "380a5e35-b4d0-4119-9721-8ce4c0840fbd", "38b38e24-d636-41c6-b80e-a285ae3106dc", "39278302-b52a-48a8-a566-ed756a5fa1a9", "3a790be3-2937-44d4-be01-b5d65353bd3d", "3ae0eb82-13ce-43a2-bbc2-99a1cdbec63f", "3af4c506-d49c-4126-9437-1d3bb1658863", "3b00c71f-79c2-4a04-9fc0-a23b4c85562f", "3bb50a88-09ae-48c7-a42c-df5879680eff", "3c9acb9e-3353-4359-aa5a-ae9512b086b4", "3d5184e1-0201-4476-bf4b-4eb28190de62", "3d9d52cb-6a3d-436b-90e7-e76f238329ee", "3dc003c5-428b-47d6-80fb-6520b63119e8", "3dec4865-cf23-4c19-a460-dd2db80ee851", "3df394de-9c3e-4141-aba4-12eac4742102", "3e139dd9-4757-43a8-8546-8da70dc17f0a", "3e3f5ed5-d810-4054-b648-d93167b90033", "3eb14fcf-5d20-47d3-b991-97808798a225", "3R_zf8xliEiurVU8GwaItg", "4025e4c4-abaa-4729-a2a3-a7175313e5ed", "40528de7-38a3-4a7c-8f93-6d3bfa5573f2", "40734c4d-e1fe-426c-8921-930f7463d8e8", "418db0e0-c7af-40c2-9cac-d461ce41e210", "418ed21e-766f-4c69-9d57-6f866dcc7feb", "41bece0e-9b5d-4151-bf5c-d0e786a803b4", "42aa7888-f0b9-4584-9cc1-966585c6b36e", "4308da04-af84-40bb-845a-7a9e1a2726ec", "43354e59-4938-44bc-81e0-4fba47d0ac12", "439d1e24-d67f-4b8a-8fb3-8bf589d5a8ee", "459ec62c-67db-4b72-9e29-0ad3ecd0b737", "45eaab67-19c2-4601-8f80-3565a4dceba4", "45f5e714-8a5f-4385-a97f-675066167011", "46cde502-86d6-4061-a80c-67458ffd4d60", "473c672d-2b79-48a2-a49d-fd68160c5bde", "474db08a-d4dc-4625-9f5a-1a6ae0b70827", "47629ed1-5378-427c-ad61-4b44d2fe90cc", "4763436c-2dcc-482a-bf37-a6b16d7aae6e", "48ec7a6a-0c31-40d4-9179-66e3ba6505a1", "49397e16-1027-4286-9bc4-b59eae565ff0", "49800740-33d6-4280-aefb-3497597c6366", "4a0e91a1-4562-4a82-92be-cb7491f1592b", "4abc2ac4-e60c-49b1-9ac8-4d4751ed78ae", "4c673a35-c10f-4a8c-b8e0-96a50fe5b97f", "4ce7a70f-15a0-4cad-98ee-5cbb40827338", "4d507dfa-4a99-4ac0-8537-229e9dc0eb4a", "4d533b3a-b5fa-446a-842d-92798ad5b493", "4e42e02e-33fd-4ff9-b74e-9bde26253c35", "4ee2133d-c419-4fbc-9117-746d756a250d", "4fcb5b84-13a9-43e6-9680-72b17874ae53", "50c9c6f8-2963-4ef3-95d5-e999a898269f", "5154be09-bb8a-4917-95a0-51c91b6a4e09", "515dfc36-7f7b-47bc-abee-380d68f41be6", "529015c7-3b4a-4a73-b2c3-33103339b6e3", "52VsI_lKlkSUAOJlfDnyrQ", "5362d685-fc55-4b43-85a5-cc3d6cad3f69", "57552652-d902-43b7-a68e-5c7da87582bc", "5800d2b5-75b8-442f-8b10-63bd3d73a1b8", "5873814e-c9a1-4042-b96d-2aa0e0d52e2a", "587ca2bb-dd42-42bf-833f-4e2a97349866", "59d9560d-e5af-4205-b860-3afea1cc31a9", "5a38f864-7d1c-4989-8558-40b9668f78ef", "5ac25e5a-8c8e-445c-82a4-7272baec1eb5", "5accb7dc-90a3-46ee-bad2-8fd1861d50f1", "5b01eaa3-0cac-40c0-b72a-b3f6a868ae0c", "5b3dc472-773e-4714-a9d0-7e7bbf6bc695", "5b6535f0-86cd-417a-bcce-a1ace9a5f260", "5bde836f-9343-4934-b01a-606f707020ef", "5beeb4c4-f276-4eae-87aa-9302e45b05b7", "5cd08cfb-c729-4c30-96d9-6a99bb934d91", "5d13a7a2-8213-40e6-90a6-efdd76a3fdcb", "5d1c42a0-7e36-431a-8e78-a8b74046d153", "5e3929d3-d291-4e91-a0ba-2ef203e407d3", "5e9bbda6-d681-42b1-9cc4-5758acf34bc0", "5f1bc3c2-dfbb-4258-9b10-608535e34db2", "5f492f8f-ab61-484a-9587-747132b54309", "5f600892-aa67-484a-afc7-14c219d89c52", "5fwjFQoC_Uu9ChvF6uNGZw", "5PUja-WX00Sme9yBwvDq-g", "60067e91-18b8-43ab-ae20-a8ea74c757bf", "61969200-2790-41e0-bccb-d9feff8c7b7d", "61ba3c90-c81e-4deb-bc79-50c0f1fe3e83", "62ce4109-8dee-4895-bf1b-bfa143db4c7e", "6423f23e-dde3-4428-96f5-6091027c92cb", "6427bd57-fcb2-420f-8b3e-d1da43470b84", "64ad8789-1645-45f7-819a-a413ff7c9622", "64fddd4e-3d59-43df-bb39-671e18953c0f", "657efe89-620a-46a2-8b58-cafd855c7cd5", "664485e9-a159-4fcb-b32c-2b114b4a1218", "66f8f55d-cad4-4afb-93e8-b6bdf04b37b3", "67117b3d-0669-4cf3-8a4f-066cabd4d8b1", "686ac516-630f-4989-9c99-0b8d055a00f9", "6930ce13-4be4-4ab9-9817-667bd261ffc3", "6a98bbb6-bbe0-4dc3-97a9-e253dc506a2b", "6b337a35-95d9-4b3a-9891-d9fbb06262aa", "6b9e022c-0b68-48fd-8eca-da8573c18900", "6d36091e-7083-4e3d-8729-94612602c8cb", "6d48c545-22bb-46c1-a29d-0a38af387143", "6d815b35-6f68-4ed4-817d-70f141e1a571", "6dd261d8-9806-4dac-81c4-a58f6d6a7f9a", "6fccd476-c621-4f3f-b2e3-45c95be912b5", "6QT99hx6DE6t9cKAs61i3w", "6RhwWExXekaYpK0NGHSXGQ", "703f1987-2b31-4b6f-bab7-1939b1105ac0", "71014872-d879-4858-9d68-58da3c673d8b", "7134c60b-0640-463a-8d97-caa9184bef93", "71921831-ba6f-408b-a00e-2fd97663636f", "71fd5353-b439-4a23-b471-bfb99b8154d7", "739fd42a-8a8c-44a5-afa3-e0974802c6ae", "73a119ab-d2c8-4da9-98b2-29edbb38e2c2", "73c0e2ba-cba1-482a-89d5-1508997aceeb", "74365234-cb72-409d-8cf5-9a7209f707e1", "745c5110-104e-4b37-aabf-7c690fd54811", "74b91f2b-d4d3-4e41-8946-b1296ab7a773", "74c65554-0d15-409b-b9a8-0b2620a7d7e2", "74e6878f-94e0-41f2-93ea-de6412238385", "75-HXZBOAEiDBqclnOn8YQ", "7553d851-1986-4325-870d-e425dfb7d007", "76369aef-aeda-46d2-996a-cd00594d0543", "767684e7-0eec-4f6e-ad58-3d5ae715206e", "77c37481-a185-47ff-af55-65f9fdfcf9e5", "77d3c585-4928-4471-a425-89036efe7299", "79b90274-6eec-4664-acfb-4a123334661e", "7a6fe0e6-f930-4fb8-9b2f-4c857a273dd9", "7a730848-ab20-445d-b416-1a72cab49324", "7a7d8a06-7982-4bcb-b7bd-4d03b48889b4", "7a98503f-0460-4d25-a0d5-72a32db8f4c2", "7aa994d2-6499-4918-ab2a-2531c17e27a7", "7b857a8c-92ad-4028-a2c2-b3c20cdab5f2", "7be2eafc-21ea-4266-ae64-8888c06798d6", "7c1552a6-bc36-46e6-884c-1de6b8111a92", "7db6b49f-3e2a-4da8-bdfa-e9ad9ebc5ba6", "7dc6c916-b887-4929-93b5-dcea3022e6a9", "7dd6f7b0-7ba0-429f-a04f-e32d3a79ee61", "7e22a4bd-3e87-4d50-8843-9e2e3cf45285", "7e6e24b6-6f9c-4315-b457-73eba52d824c", "7LSm-xCYakmgcAr--Fn-JA", "7VQvz9sbzUWvGalxd9rISg", "8263a9ca-8c57-4e81-b3e3-24f2ca19a70a", "833e8ab3-9b54-4e15-8f4d-ee1bac5823c9", "83be5ba4-525a-4781-a5f6-839c22e4d1d3", "843e3364-a743-4dfc-a990-0436e47b8c10", "84574356-98ec-415f-8668-82cb52016bd3", "84600de6-93fc-4a89-961e-1f18f4b80643", "84cd594c-1cd8-4b4d-8409-85c8fd5fb02a", "8595c7b2-4d1c-4eae-af9a-8180d475d931", "86af3a97-18d8-4dda-a2f2-cfd3b4224254", "86dd9ff9-643f-4f11-8243-93ad948db4cc", "87792b6e-1468-45e1-b28c-83f27ada7299", "880a3cc0-7407-4b61-b759-f9dd890fe9e5", "88739d79-aeb9-471a-b25b-776f46bba9f6", "88ae6d12-6d39-4770-ac2a-f8c4602a10a5", "88b6ddeb-a455-460d-91d9-a4569ef6903c", "896c2491-2f96-4986-9cbd-b3b31ef5d8c5", "899f1d17-eee1-4b7d-a112-fbbba597e738", "8aa79563-ace1-4ba7-ad0c-f3210a78142f", "8b065e2a-b106-4f11-b415-4c47ce87993e", "8b9f1413-e786-4a30-946c-9292f207875a", "8c35c804-e8d5-49d2-8d5a-ea19fb70bfa6", "8c51fc1a-b1ad-4002-a13c-4313abd92c7f", "8d10cc78-6b00-45f3-affb-205e9cc5b03f", "8d17031a-c0a5-4145-92b7-1f0c2d8f0ce6", "8d703940-224a-4b0c-a04d-f365550071e1", "8d97303c-5138-4da1-a74d-378da19d11cb", "8df990d9-141a-4757-b4f2-138d5e68ab74", "8fc7aaef-adec-4534-8f91-445e4e7095d5", "9073d6a0-3bc1-4c82-aeb3-d4ee195538b4", "9168b3f9-c7e5-40b6-bb2c-9e708596e675", "92302d9d-c527-418c-ac5d-1fa869727505", "936d7126-a3b7-48a2-bceb-6cad9221b267", "94eb7ef3-528f-4f59-92a0-48b0ec287527", "95ab7a7c-c35d-4da5-9955-0921064470b6", "967187b8-df20-4f4f-b48f-10b9ea75cedf", "96bc2f9f-ce3a-4c4c-b92c-cfed4ed4594f", "97253092-90ba-4ff2-b137-6324ab244d11", "973d84ee-5b88-475c-b579-a76c34a7c533", "973dc863-b9a5-40d8-8f15-4096e75bd2cb", "97eae086-cfea-45ff-9b77-1d184256d493", "988e4069-4341-428b-bbf5-023309891385", "9a366f8b-ea5b-49ae-a98c-f6b282e9bf14", "9ad40310-7cb9-473d-920c-5c18d7972030", "9b5406c4-8589-47e4-9f69-4fa4733615e7", "9b5bde11-7408-4798-9fcb-c7ec175444df", "9bf5d259-7774-4cbe-a90f-7f188cc0dce7", "9bf9d502-8a3c-4b24-9565-0a2c2d9864fd", "9c28e450-8e12-455c-a4c8-c0a2160d4190", "9c34c2c1-07d7-4180-94a2-be8c9caaae84", "9c8fc7f0-8f99-4aad-a34f-8d979f6ae352", "9cdb4965-6f00-4304-afed-e301e73a2b3a", "9d395a84-4342-4b1c-940c-213ec2eb56f9", "9d68e9f4-6ea0-4717-bb78-f2783a14a6b6", "9d9fadb6-97eb-480e-a224-4e0179082071", "9e6397dd-b7b0-4d32-8c86-1910106a8478", "9eb7f505-5afd-4b28-9886-7996d38c59c6", "9ffa6e06-5200-4e53-ac13-f8491aecc864", "9o0gWLJjREG552gxRreT8A", "a12f724f-4a73-4ab8-aad4-6bfc662b4dd6", "a16d59da-caa5-4388-af67-315bfcb16f64", "a302741d-313b-4682-82e5-ac76764e662c", "a44766dc-19ad-4476-849c-5113a5faabd0", "a4c26527-8f75-44da-b150-7aaf8ef739c1", "a5b747b8-c1a5-4bce-999f-e13dbe77bde6", "a61e86cd-f7e7-4ac7-a6e8-df269bc5cb01", "a628d62c-fa30-4405-bcbc-3e646e3a3434", "a6cbfe76-534a-4655-a8a8-3fed13d001c7", "a6f1825e-1647-4d06-85d7-ac2d139612c0", "a96e168c-0c4d-4c70-9f8a-c8f07164ce99", "a986ff33-9e16-4219-8ebb-2e7b4c772ca7", "ab15ef15-a97c-4ab1-896b-bd4d7e1cd9f5", "abc25091-ed5f-4c72-9364-fffeef1bc239", "AbYdU7O_9U-Lwg7zFVZzXQ", "ad0bdec5-f767-42e7-99e0-221473464a85", "adf2aa89-61c4-4b7a-9307-e39e72db23d3", "ae180fb1-3e74-4232-9a9d-388fd488eba1", "af33bdb1-b7fa-4fe6-b37a-b1fe6a420879", "AIpF1QUeMUWytpHYBDBAkg", "akAhnN0cFkecj8q3spixkw", "ASHnWS0h9kOJhbOUwxWKFQ", "awOFE0_GSECr8GBNBo6a_w", "b02053b3-013f-42b0-8881-85f5be2b8cda", "b080e2f4-c699-4cca-bb9e-7cfc4bafcbfc", "b148cb1e-df81-442f-aea6-ab1727aad00e", "b1bfe0b4-1ff6-420f-acfc-2331d34246dc", "b1c3ccc8-91d7-434e-b9cc-bd632fffc01b", "b33dbeee-5bdd-443d-aa6a-761248054e08", "b37582a1-7904-4288-a47a-7a63a3cb28ae", "b4e48239-a3b0-49ed-b78a-86f4e3aaca1e", "b537c30c-faef-4649-9a1f-be3985936f93", "b6610b7b-3978-46fd-9af6-41b4568c9b4d", "b6rLwzD4NkKV7xKn9ZYVkA", "b795c631-f075-4d32-ac87-c732b36cb932", "b84f5e9e-11fc-424d-ad39-86901edb9edc", "b861e5f3-fc6d-43b3-9861-c1b45cb493a8", "b89be768-a169-4b24-8022-751e7b817865", "b904304b-cce0-466f-8569-c621eeadd4f7", "b95678aa-351a-4929-8a0e-0274a183eb18", "b99a89c7-38c5-4196-9862-4e1bf9cb0ce1", "b9d08f9a-b23d-4d7e-978b-a0cd8adbfe9c", "b9e0f03b-2609-4911-82e9-1e324dbbe8be", "ba84b23e-01ee-4668-a7ef-2e3e0eca2f23", "bb54e0e9-5d90-41f3-94c0-8e82f1791af1", "bb8602d4-f91f-4e35-9c88-1cdf3a04ff0a", "bc3f8ab4-cdb1-4e63-bda5-ba5a4b1f4a43", "bc7299ac-be2b-404f-b8e6-402e2c0660f0", "bf13fa1c-f991-4aaa-9402-aac109be9562", "bf9dc196-5727-4752-8126-7ad5fcd96d55", "bfd9c70b-9ad8-4f47-a840-c03231f9ad41", "BJ2XrGvl40iIl28IbTfbKg", "BqpRbr_T1kO_YJqB1787kA", "BR0mZKsKaE-t4R8CzEwuyg", "bu9tb-K7NEiocvZY3o7ukw", "c0e59f27-a13d-4bd9-b3b8-a38a360f649b", "c161dc73-6ce3-49c4-8d15-45e6911f84e4", "c3745cee-d87e-4a23-b6e2-64ebf05c8b11", "c3ba47d5-a165-4bd6-8479-e5f8d709f4ff", "c45ed7b8-99bd-4a4b-a9ff-e16edf5d7a18", "C4uuztTiYUe20PACMARksA", "c506feae-be73-437b-b18a-1152bb9906e9", "c6c08eb5-381a-4193-9722-80da95d62abe", "c70005d5-6276-4a98-acb3-6a77bc19379a", "c855dcc3-96cb-470d-b159-d37a025a47d1", "c87239ca-a255-4b13-bbc5-1b38236fb4cc", "c8a97aeb-e8dc-40ba-b6bc-a7c7be907381", "c8c00394-4cd9-4bad-9774-a825ded78f80", "ca218759-7eed-440e-b5dc-dba15f81e598", "ca7a3ccb-2bc9-4c93-a6a8-6f22b38526af", "cbN5e-t_jEKa4hzir5JAxQ", "cc96f8a5-bc5b-4f89-83b7-ecd53905ada7", "cca7ab3d-5609-4700-92fd-3280fcea9b77", "CdeM6RJmPUumXtdmdM3RXA", "ce250e13-8137-4f62-9e52-2b1cebe7d0ad", "ce81210f-8eb8-4cd2-95d5-046d4da229c8", "cea5c141-32d8-4dae-80d9-6fb74834a79c", "cec9cfac-a45b-4edb-b26d-7453cc2bfaf2", "cf1a6f8f-1649-423b-b513-b8d40d242b1f", "CQuKKNpan0O7rhR1fr14-Q", "CTcrvbo3OEepIV4oW8bx4w", "d0a9262f-5504-46a7-bb10-7507503db58e", "d12e5738-00c7-47e7-9334-8399eec5aa89", "d2d3264b-fbf6-40b5-9f10-c85ec737d112", "d3b8bd7b-ca9a-4f5d-b115-3659dc294a03", "d3d69bee-60bc-44a3-ad7c-65b3b69d1e59", "d57cf97e-b5d2-4e47-8231-a226a161f6e3", "d6b0a1e7-e918-43e5-8191-3a8d9d01df7c", "d766c8d3-76e8-4021-b0a4-24b78fb7eaba", "d7730a9e-78a1-4356-bc09-6b066615850b", "d8280c0c-d803-4513-be10-a0ba96d8821e", "d84c0ff9-8fbe-4ed8-abf3-7996e81888ab", "d86e2e65-5d87-466d-b885-8c52ac2eeeb0", "d8f408f0-78f5-4e8a-b42a-e7b09632d1fd", "d960b47d-f8bd-4c2e-813c-a6a09876ea18", "da4e7b34-2095-4a9e-801e-4f409039e0dd", "da55bfc3-47b7-46f6-b548-7fb2c2a5e0bf", "dcf9012d-f9e9-4f15-872e-f130af3b2efa", "de0ac50d-2adb-4114-bd2e-68953b13d706", "de19b239-3ed0-4f03-acb6-da69b5aa52ea", "dedd2e98-2721-4de6-92f2-9d399f34e8fe", "df0e06c4-8ba6-4d7a-bfb2-3759d8fe88c7", "e042433a-40a2-4758-8074-3b9d407835ec", "e13f6558-d49c-4314-820c-962ef48eb0c6", "e15b13a7-9e9a-4b32-ba2c-0cb31ed55a8c", "e1a13235-e3b3-4b39-8e6f-e6449d4321b7", "e2620c2c-c28e-45ce-a83c-0588329b9c81", "e286863c-2967-4d00-b837-b49487b9484a", "e29d044e-79fb-4280-bca9-aa1e1257e968", "e2ObiQFqCUOdKniKL9a1EA", "e36bcd98-7e85-43fa-89f8-57e4ec33823a", "e41c32ac-61b7-4e59-9d0e-b231ebdc2a68", "e49d5d78-157e-4bc8-b878-ff7c4963fdc4", "e4ba395e-f291-4e7b-8193-45f4422f41ce", "e5b83dfc-b2e1-4dcb-a4ab-9d3a4c8a34ae", "e60fccda-8711-463d-b8a3-7cd5e76903b2", "e614df5d-3cfe-4bca-b9c2-30c267b9c94e", "e6216087-fb37-46f1-bda4-a88c4640f339", "e773fddc-ad9f-408c-82c5-2637f75b3214", "e9913b06-58cb-4b90-bd08-981a3f19f352", "E9VTiIVFPEa-OuQRto0e_Q", "eada710d-ea0c-473f-b9c6-23e210607b24", "eb830b12-c1b5-4796-b9cf-ccf0f0e1d4f1", "eb9611c6-bb50-41a2-93e9-7f959815a846", "ec5255d5-edaf-4888-9fe1-e89ac0e0d300", "ec836d2a-b1b8-4566-b8eb-123ae2845956", "ecc1dbe6-ca06-4564-b2a6-30956194d1e9", "eceb7d49-be41-4228-86a9-2973d62e7135", "eda29bce-5a07-4439-a2a7-cafa11e2ed62", "ef7771eb-c5f3-44fd-8c27-f581b035ef10", "ef84cb35-5e25-4aa6-82c9-17f01a5040c7", "EJFGAW-TnUCaeD59parEHg", "eS_or4AnMka9M_bD7YN28g", "exQz9Zr3HEyOR9nDDrimlQ", "f15d81c8-3d19-4bbe-87c0-1144aaa4138a", "f17a2f9e-1eab-44e8-b10a-1f53c5d0778c", "f218e2de-46fd-45b9-9e0e-670b6bb905e7", "f277af6a-0807-4716-8948-1bee236ffb74", "f2c1c5e3-b201-4d34-9ce6-3082ff7e5d42", "f41653f9-ae02-443c-aaf4-1c6f4e49035d", "f47f81e7-fcdd-4687-9b99-013147bef30f", "f51a7ed5-52ea-485f-a430-303697f6fec9", "f527ffaf-da03-4519-82ed-46a9cb981dc3", "f5c9743f-e77c-42f2-ba63-9843342cbf8e", "f60e093f-33be-4527-b827-060adc77b1f4", "f684d86f-4a75-4a9a-bc0a-cf58f36c91c5", "f6caeb89-dbb4-4471-92fe-100bf48cd5ce", "f76b709c-b173-426e-81f2-279a67d2fde1", "f9dd08f8-16d3-4c39-af4f-89f7bb6e80d3", "fa0e701c-5e80-46f4-8817-6e2dbf904734", "fb907eb2-91c8-45ff-84a5-462bb86ea7de", "fbdca48d-4ae6-41ad-8c33-b70a9e33fd77", "fc96ea5d-b49b-479b-a963-92f1eab44b2c", "fcfcaf63-deb4-45f7-b711-c051c9ea45cb", "fd7774bb-b29c-40b5-84b1-5c2bbe574255", "fe15ca53-c5b8-4acf-9309-ff3f4e610fc9", "fe34adaf-47d3-435b-b2fd-b2fee05fceae", "ff15e25d-d467-47a8-9ecc-e415d5acb90c", "ff5c62b3-4f41-4acd-bfd8-8db00de4f36c", "ff7e6d41-5c27-4516-930c-ad0e9a23cce2", "ffad1160-8383-4b4a-a5b9-223476b49b92", "ffea7a65-613f-4835-921e-6dd15f357b7e", "FHKZvPFMLkO8iAIPPQzu1A", "GAWURQSqrEW4G5slrPMgZA", "gwLiwmqBIk6glFxvaTzxeA", "hT7nSD7Ts06F_1SNlOMLwg", "hTHM3Kjqy0ykqBD8UCRQJw", "h_To9WQYdEusYQxlbMmMmQ", "iai6P7dDTUq8S4hqQmY1YA", "iBQ-v6xaqkipUlfzkSHEeQ", "ilDgmey2ZUWlFiuEAN7YoQ", "ImwG0snB9ECc_gqRRBoz6A", "J6IQt4G1zUeN8v9Wn50BUw", "jiBzrzDPkkuL6DqIFKpcCw", "JTrfBXhIT02o2-ssP94zew", "jUpFzO5MPEGTbS-E0a99Nw", "JWanxvOHyESg9F_HlmTOfA", "JWfs9IiQs0Weu8MmyGpqLQ", "kghEf_iMKUSxPO-h5iRhSQ", "KNZ1DvCLC0WHyH4opYAXtw", "KPmC_wyX_ECe7fdKwoLMGg", "LK8jt9zCuUGwLRccgOOvjA", "Lli8-aqt1EmCmyXwBRQaYA", "m5Plrduzr0yj_9mTn1x3Vw", "M9weaekvIUimiJSVJqZl-w", "mfFLpKQRE0anhrmeUvW0bA", "MJkDYDH510izAab13ZfXQw", "MJQiQ3LATEOymxCC8wfSjw", "n52um7A-b0utGxixd1ESvA", "O8VqC11xNkSKpDh6Omp_0w", "oRUh266ohkWQil14uxcEdQ", "OTnfT-4NQU6Gd_iVPDx8eg", "oYvOo47DJE-DEPlpn5xNUw", "pNOfTDOgJUGQvGAlTjBoCg", "pQqNvrhDJ0uuipKHA2qUOA", "PryEX4MEvkGKwST6dqJHjA", "q7pSsRMNHkqEJmYHuhhG6g", "QCtqxbvYOkan79YFugH_Cw", "QK6lBlX_wUCiSbJ9RkIwdA", "qxovV6Vym0ufL2cTCyKjOQ", "ReqOfohe6UamzaiHG1x3Ow", "rJ9zPPGdlk2i24sxLfkqng", "s6mUIqiPCk6e57UYCx3SZA", "sL7iHCnjyEuQKzrTvciEzQ", "sOREgAGs-06CeGGsG-WIkw", "t9co2516nkuJO4LIJA1bJA", "TcgHS_0aPkehrBg7ytMOMQ", "TjrFi6TFhUC5tQXWkZiK9A", "tkbHKZ9wu0W00VGeUbIaaw", "tTKv7eRODka2KC_hsY2O3Q", "t_66r_j5oUSh9awIPmdUKg", "UcPo9SsF3EGTCBB2J-Efdw", "UGkQa4atNUe1cE5TN79s4Q", "uubY_Cn9A0-Ww4lsIRCF6w", "U_H976qYRUmg9e2clsAflg", "vDSlMnayzEqWXwT-lkcKSA", "vP9EKTMXP0yni8U7uCz2-g", "vpP2ZvnL0Uy3n-I_Jv3eeQ", "VQnoLstCyEGDPy1c38-GvA", "W7AJ4UYlVkWF5nLbmpmRBg", "Wua43ED71UqL71ATtyS0iQ", "WuShlrJnHUKuscn8e6Rt2g", "wy2cfgb8mUqjMrIOwHH2ew", "zpjkkb1HtUORoPaODFuuxg", "ZsggwOJ8XUOGfaJltM418g", "_OWVy3z6iU-M3-zbQgSLig", "_qXaNbm4yEeyalo372QbvA"], + "mask_guids": ["-IgxYllxkEyr7V1gosWXUA", "-lYK7LhYyEy6wqL1tIE9cg", "-_ViYCHHRUip7oN_AnH0Og", "005699fe-2c69-4bec-831f-031fe8ad7f0a", "00ac4e37-6eac-4ed5-b46c-2c075038bc3f", "0189ec4c-0389-4ebc-b9ba-32303a6341c2", "018a5c07-e956-457d-a540-a5e2cd68da09", "0272eb19-fc0e-4ad6-8db9-cc0cda2528b0", "0440f08f-ef1d-49d8-942b-523056e8bb45", "05cff8b8-d437-485a-b4b8-41d35becd022", "05f0ee6e-c824-470e-9178-5ed576c6fe0c", "0b2395e1-ebcc-47e9-aaf1-faf9e9cec4cd", "0dabda0d-842c-4d39-a6b9-a8ca3ec20825", "0dd6596c-b4ea-4d55-a219-e1f44e4a376d", "0e42108e-3b08-49b4-86c0-7984719e8380", "0ec6628c-a106-4452-af82-105a3e3a9f2e", "0f36bb97-c61b-4281-929f-ff1d0d11be86", "0f49ac81-63bd-4845-8bdc-1944181a8aa3", "0p5N9U5qokKdQhW07yYLGQ", "0R3Mh96tS0631EZYs03dmw", "0WOBGm34H0ySBC10VToa9g", "0XPd5RzNJEepHztqwAznHQ", "10fYSIBytk-yUjveDa407w", "11ZEZTFhy0ONpoXb3kiewQ", "15099be7-38b7-4b0b-ac13-1bacedbf5fb2", "15edb8d7-2ff6-4170-a7e6-acd9e965cab5", "1695608b-e9cc-4a03-b56d-0f09d2804379", "1ba65dd5-9df9-4ff3-be96-f83a7abf67b3", "1e3195ab-f7f6-41f4-a5ec-378d09ecdf69", "1f18670d-df52-4d2b-8009-a32cf7c67b40", "1fede91a-434e-49a5-882e-e6db107112a8", "1hVOI7IuY0mKvtr2PZXLww", "1yMyo6oTjU-VAygoeWaohQ", "27a5df72-4095-4837-bf24-cdd3d95a43e9", "2c0d912d-c184-4eab-8ee4-73b90ba7b0a6", "2c8924aa-68f8-4912-9759-18992f72f08a", "2d7361d2-31a2-404b-90da-10e89114e1f2", "2ee3944d-e9fb-4959-973e-570a7fc6cc57", "2UNd6eViFUO359xjSiuu2A", "3248a3ec-50d1-4c0a-96ae-25eb9d8ed622", "3259238f-4a9e-4e06-99de-6e7324fa0894", "3290f148-1bd1-4ffd-a947-61fd0529af05", "32d91d32-b102-4093-927a-6dc3ac2e9e86", "33ee376a-586e-4aff-a7e4-4213445a3e92", "3485a30d-27f8-450b-b5aa-1ff3cd227d19", "3b825377-d2eb-41e4-a736-b107f88f62d6", "3ba69a39-a4e7-4505-bae7-8db31360ec1c", "3d971439-41d1-43a2-8434-704f9ec8d906", "3DdSzSqV1UG5AtPr8ro3Kg", "3drRcfyKkku0qGwCy16m9Q", "3fQj_7meDEeQqm_7RfEfxQ", "3pdsIrhM9EW-y-LNnjbFxg", "435eba0d-d8f9-43cc-91aa-2959d63c6fa7", "464fcf20-361f-4dc9-b074-150c3bc27523", "47514b9b-0d17-4727-97ed-dab67e7fe031", "48yVsRt8sU6jf3P4wlPA8Q", "4cc0ea9f-65af-486f-8319-02b85ab5197f", "4e378419-d043-4e86-ad0e-66288ac21cf4", "4EHIyZkOOEqnM-PuD_a5xw", "4zPm9miF5UOouEWFpOxYZg", "50NUFrDxbUKmXbyAHBenvQ", "51a1809d-becd-42e0-8dfb-188d6f07ba1c", "52053afd-9f12-4fec-a358-7eb3fe8c19bb", "54778d93-2cff-48db-b25b-65c08b0e5be8", "54a5c055-f757-4c00-a1e1-8b0b6552c608", "556f876a-70da-4104-b792-fca8ad67be0c", "5659ce2c-9cb0-49da-8c39-4b7df0f8deec", "56a92c8d-af53-413e-929e-4a9a3cfad780", "5814a331-6155-4df4-b921-3ffc6059e44c", "5a6db6ba-2a0f-4399-b537-dd78db94bf90", "5aX524Lfok-0dC3UFv-OkQ", "5b8d9490-e5bc-480e-91bc-65d1797b52ad", "5ekjxLQZwUm7-iXJ0bYSiQ", "5fd9e83a-cfaf-4c98-bd31-91a7484c26dd", "5RUG6i2Mx0-55IO-IlhUdQ", "6094370d-5e28-473a-b422-f3f1c75d5db8", "65b89eae-347e-4eff-9ff3-7f25cace0125", "65ed43ff-dfc4-4cab-b0b9-77dc94165e24", "66cc536d-8cb5-4262-b7f0-49a33b97ea10", "6b38ab79-142b-446e-978c-ad829ade8823", "6ce21c65-f4e9-41ac-be94-8eb1a08d5c76", "6GfSjqIb4ECCvIPC8vO8_A", "6Jv4AJGBYUGP_n4sDcz2Kw", "6nmHu9zj8k-tEz0tBHblnQ", "6UBZuZQBG0eZtNmty2IGdA", "6WVXb20giEWyZbW2gWGz_w", "71fd06e3-cac8-4923-a09c-ef91183712d2", "73fc2e2a-a036-410b-bafa-3a07658245b5", "7634b7ef-9607-497a-b41f-c0191dc75998", "7660e11a-7ef0-43d3-bc02-76b3a4aab064", "7869b2a5-cd89-4033-b9f7-4f1451ecb47b", "7a7bc314-4ffd-4ac8-9ed6-6ca73589c132", "7ac81d60-05b8-4a85-982c-f69213a93818", "7b187c06-7ba2-4f3c-bca9-fdf98146f385", "7c4d447c-6594-4bd8-9510-12ad683ab116", "7d040391-03f7-4b3d-bcfd-a1278ff459c9", "7e688b30-ed13-4790-9ad8-c49885d1b7e8", "7ef7d0cf-99f1-4872-9d3d-8230cc9fa368", "7nBHNMZhAE-t9rLCYO_STA", "8212812b-e7e7-4f6d-aa27-d1112af45473", "83158223-e268-4036-8a6a-218a8430eb2c", "84c016ad-133f-45e3-a412-cac35612540b", "8768aa33-b014-4062-b323-b146dd7d78d2", "8ec5de40-73fc-4f09-9bf3-ddf4701107bc", "8eef8837-9141-4088-835a-b2f47f4126f8", "8ffce0d6-d03e-4097-bc46-09b8894bc1d3", "8kdJkmwNM0WQmMVrb8fK8A", "9009bcdf-b6f7-4dae-b2ec-895511d118ff", "90e0da8b-1941-4ca8-b7e5-b4e31921873c", "91a451c1-b285-4c48-b14d-59ded8cc006f", "92714eac-cc07-4f74-a6f5-cfbbadd07e06", "93987d32-7a6e-45df-af56-76f912969ce7", "944d934d-5e21-42d7-8196-fff9a81ee469", "959ed05b-0215-4bbe-bb3d-64e4448d676b", "96b36c98-cba1-4337-91dd-c74c00316f97", "970a8c7e-d091-4719-bb9c-e3be8d46ee68", "98553538-cafd-4df9-8f37-bb0a4bb478a5", "9b2102f0-e16b-464f-9f1c-f068de52bb89", "9c03f381-7357-4d0f-8cda-8737d4c43d25", "9c55b609-b7dc-4070-a3ad-6351a0054a06", "9d6ac0b7-bbef-40bc-9173-effe65d7c0ff", "9f1dd474-34a3-47eb-bb72-dbfc8d2d6ecf", "9skD_xxLq0mpfP13f9n8hQ", "9zGwmle_yk25V-KnC_PJEg", "9ZP617DQk0mZ10J3d5q9oA", "a0271cd0-e172-4d3f-aa2f-9806f21a82d2", "a240d44f-963d-4d35-b22f-980938c7788d", "a3Er_BCApUiPtBaGkSH9LA", "a4903da3-249d-4f0a-a746-b547fda371ff", "a4e51b6b-fdfd-47dc-9d0e-f26f383e03e9", "A7Y52vGIN0ijEKsWR_GQLg", "a8a91216-0fcc-461e-a682-95ee82509cce", "abdf525b-b871-41be-a1c8-810f49740ad4", "ACrBLiAIgEur9V6l_B0CvA", "adae82eb-a4b9-4541-acf2-76cea839a593", "afd88a79-7f03-40f6-bcb4-c247dd86edc4", "AK4CoAB2IUGkwqECNaO75g", "av4PwLz6YkmpBRImQZgn4Q", "awWjleyNP0akWNYJoehCLA", "axPBGoqorEe77Oqqjfzb5w", "AZnPWCN41kmNwFwDqWGFWQ", "b-IM3eYMJUa40395O5XeLg", "B-sIpWNOfEigMeQF-WVR1g", "b0298c45-0dd1-453e-bad4-41e4656bb38e", "b292eb4b-07e3-4a48-99b5-3c6587a1e02e", "b75ef67d-00c3-4ac1-9b72-212032460294", "b78008e8-abbd-4ece-be34-9a911f721fcc", "b94411f6-5b71-48d0-ace1-ac6f63167065", "BAjMoTHbD0mSCjoG9jmzjA", "bCuW1YadE06p6v1dBHsx4w", "be2b9293-1d3c-4b1c-b4c5-fad3ab16cf54", "be6b3fa1-3388-48ac-8957-d7b8c85ff968", "bjXO36Nu6EWD2dU1UC4Uwg", "bNA44H2wzUy-FkWgyOq1Kw", "BOB_mAXdA0u2khOH5qjqXQ", "bVrkIyADCUiGNYWdmP_HPw", "byvLynH8x06OQ7GNCXOVYA", "c2052d8c-fbcd-4462-8f08-bcf5b3dfde2f", "c2a91a10-2f5b-4ab0-a79f-51408cf4b047", "c2a9c007-2748-4fa2-ab9b-0081eef792f1", "c8ccb85e-52cd-4b3b-aadf-289819067125", "C8gCmBZ8RUG83vvDw_UYew", "ca37823a-b8f3-4414-b38b-de0dc7fa4295", "CAqT_iLfQ0qQ-GbIdAsZow", "cb9e9e05-c481-4397-86e7-c2111bc0e817", "ccf1ccc1-e229-4157-bb74-f2cdef01e547", "cdbe2cc8-4fd7-451f-9b66-0df0e0e7a85e", "ce3782aa-56cc-40d7-9019-8ce714cb7748", "cf119781-5bd9-4b85-9a0b-12e82e988c23", "cfabdefe-0890-436e-b2a3-b5c712e22955", "cQUquXMk5EGt0VUCuPKBgg", "CUCKsNVnIkavHjcspWAv_A", "c_t9XDbn-Eqml1O-wC5zJQ", "d015cae7-a905-49e4-8823-6dec069689a6", "d21e1b6f-9c9e-42b4-bad5-201ec43161e9", "d2a692e6-e1a9-4cfe-8154-10b52be7f8c8", "d30e3afe-414c-4b60-8be9-6fa4156a4b65", "d344b8cc-85a8-4ace-9f92-38c84f396e99", "d738ad4c-c9b5-41e6-8979-d586c964fb6c", "d759c88d-a159-4c26-86dd-b054bb05b3ed", "d9be3a98-9da9-419e-9dce-fe58a3ad9c4e", "da36bbcc-2a9a-4ba2-9e4a-6f52c9b1ef74", "daa38a31-fb7b-4e26-a2f5-728d6dcec81a", "dafa658e-753b-46cb-bd85-85c1de5e6ea7", "dd7472ed-bdcb-4811-81ba-5ed682fb7675", "ddf429fd-a883-48a5-869b-bc12e44d30bf", "DE1C-RJkuEWIvpR0q1i42g", "DfyODsHfIESXxVjcfZmBOA", "dMDMDExSmEKOF_kQIHaowg", "Do__oD53x0-dNtVeHsr4yA", "dpBx3DxB7kKJphxCvfmKKw", "DRJcNhkqvkKFEaZpOguR6w", "dUu7b-JFOky9c2lIVCNmcg", "e0186718-4a18-434b-b96e-0c9c912f0d1f", "e17481fc-b2b6-47a5-91e5-7205f45a3247", "e4b53a9a-ac95-49ae-bf68-615bb23fa075", "e5635772-4e84-4b30-b77f-4a1196cb599b", "e65530f7-543c-43e9-bf12-b6cda2bc755c", "EckOEU0mJkGIVDvYoKBN9g", "efeb07ab-6253-436d-bb66-991ddad72e58", "egpzBs77Q0CpDndZHqXOog", "EiIow5mS80qpoS10r1ZdvQ", "eUktNKKK3kqUR8-gA3rNxA", "eZGiYEx6sk2PsN39RzQwlQ", "f1cd10fb-3d0b-436f-a173-65bc0d88d8bf", "f4cea991-fc3d-4715-bf82-8a8fb60e8799", "f5z_jhdcqEGjo-1WUmldaw", "f600037d-c9c0-43fa-b45b-02f456f9dd5f", "f6f26e2b-657b-454a-8663-55d6b9b6e50c", "f79f8a46-ef1d-4d8c-a4fc-383e3a9eb10b", "f7c7d05f-5454-445a-af38-25f110bd204a", "f88178b8-b204-4fa5-9dfd-6e438bf1e247", "F9vYkdcuGUOSGEg0wpQt2Q", "fa9a3d8e-16c9-4a7e-9166-3226e0d13c39", "fd67ec40-f898-4df6-b846-8bf350f362b8", "fe01b6ed-3c36-47b6-bcfa-47864ef9d610", "ff5ae41e-e446-4a96-8c11-49d85556fa81", "FHB5XHwNWka7aUt5km3LNg", "FVdDQFOMQ06teQm2ANDPng", "FWL8ZeMe9EerdB0_Fya3AA", "fXnd_COSzUeznwe7P4ZYRg", "fz_0gZ6RXEC78NpYsx_eqA", "GJRG8sCM7kSGfUd9gLwpgQ", "GlB_scM-5kuc4A5TmGe6Yw", "GtoHkTpyA0iGIHsxphwivg", "GV8z2dlqGEqIAYaGrF1ZYQ", "GXS9LQgPU0mJIEuXo_Oocw", "H3OQDBef1UaacSD52p8Hdw", "H8LXGRJj8UOuH5dil8L9qw", "hCeaYHy1TE2s6rTPxAQP9A", "Hj8WnsdidE6rJeNyOmWUfg", "hjk9OO8CW0KNGR7A6_p84A", "HQnd9m0Wi0upaXRNsE5HPQ", "HQQWccdyakaEmntPhvC6CQ", "HTjlzAlWZ0ezY6w3ngSQSg", "HTU3Wp_ri0GNYGFSezFHSg", "i8bvbGAKREmAEbjKO-geOQ", "ica0LZyM_kOox8gxW7CaMw", "iDxHI0T4Gkifs3_GY5gdjQ", "iqGv6iRJ-UGbofv8-aHKVA", "iQjkS1xYP0S63ltvKpkz5g", "IQvHmZ8ei02hVuXZiXb6TA", "iT7UGMahoEu7BZZqeKXKww", "IWg9wuda4kaCilJk8JQpTQ", "I_OF1c0YWESMwxJr9sXYOA", "jL-LAsd03EeN1J-Zp8opew", "JmFzNL5v_0q1p0bgoOlBpQ", "jQjfiz2i6kaPfrkjnBGxBA", "KOCzRE4a6EKb3_NVOI_lcg", "lb5tuJOqnUKp9prjjlgHmA", "lSDrKR69rkSFULy9GA__1w", "m0R4Jtf4HES8krxMnwIWFg", "m5Vw_MMCWUiSOOCL94QO0w", "M5z2wk-GDkOAjvZzqsfcNQ", "MBuxBAIkpkWI26d-arAKQg", "mG60EPIQIkaJNgOkonVmZg", "mkDVc0Mz4kmuLZ7powBHpQ", "mkNzDvyGfEijbdzAxKYrPQ", "Mrf17aidXEeCF-fFURUUcQ", "MrrsmY0J0EKduH-f5wUj6g", "MV1HW2x43kG4rrhyoC6mmw", "N5wN1l0tYEau5np-9PoIiA", "nalaCGqtL0SFA4Itwqmp8g", "nbhzQy_Hdkeohh4S1IezwA", "NH0L99bd00OVKMyltuSnHQ", "njEWOjNEQEWCEqmytodpow", "NqU5Mw75BE6kPaD8t11Qow", "nW_fBsuZA0OfIjTMXm6ZCA", "oGrNKZLpMU2bLwNEe4MPnw", "oHKhT-GTx0iFWrrlz9s6Tw", "ojCeJ1VwtUelyVqFnUwK5A", "olyS074RHU-ojE0GzBBYJw", "ORmS9HkfoUeFuO4RhqcW7g", "oSaMpc5rTE-U6yhjtPWQgg", "ovy_3j_qwE-7mmks1Z47ig", "o__JORNtYEiJEyyoBIimPA", "P12CUkIg8EuFihGQsvPlxg", "pgBFpOtl00K2zsTJcfrefQ", "pHw_MghIYUazSFve6L237w", "PioQ0o3yP0a6szPZ4EKs2A", "PMGQ8fLc2UuJXMy42Ati-w", "PoMG28QAskagun1LSd4C_A", "PZHohDVX0U2frX13N4KYDQ", "q0nBwoIx_UWPe93wHDWTYA", "Q7hqvEo7DUyD9AqIOunWWA", "QBzMp7MdAUOLIofvddui1A", "qC66Hu6yKEu2Res2o2z1Kw", "qCIoeLQw-Uq-SKIetIgIpA", "qSsEcadcm0-twR1TvVcgYQ", "qZzkrGXpPkGq_nnGLZorwA", "rAzhaLh1nEWMd9v1D-HWcQ", "rcuK1wZcXkCe0_LGX7E-Tw", "rgzG7sfrlkqd9EO13LNmpQ", "Rk18_7icF0Srgh7M1_G-hA", "RNN0ejjlm0yqGDcEuUkLjw", "RR69IGV4j0Sd2xtmyN5wFg", "rzoH8P44a0esN-HnYd3aQg", "r_GLekt6mkWxXw8zPPR8Mg", "SeA8s77hr0K6DRZyVlRTCg", "sHyNs-dpc0-_Ah-HJVczcw", "sIP83lqv3kG31TH5Rw4gqg", "smmfK-N2R0uoDiuIHrhImg", "SroEVC6vBkG42ILwCtKB9w", "SSY8ZYMgaE2Bl4oFmMjKQA", "StxMBEprVkqMa_IoDG-Q9A", "sV_ByCug30G1Gw-77KUe1Q", "sx10-Hsfl0OS62pIFvHH8A", "sxWOOK1RwUOp5YqZ7a36xg", "t-ZEWJCgFEyGP2PjNblQMA", "T7zwcIIc8Ee3ud8a2JCuwQ", "tfiSfpRez0y_R4QjAhO0bQ", "TFOAk7kwh0uUgtsyDNxMuQ", "thwvF0DFRk-mV99ruqYFoQ", "tlpa7195x0CkmSjpR1RArQ", "tmaPvkx3BUOZ3nsl2E0oRw", "tnCJp2eDI0SwjVfJMhk3LQ", "Too5jpb1w0iQenkjRRS-QA", "tqyzKPdDwkWq3jfZ8jZdaA", "u9yHtj_tfkuowGp0h_YpFg", "uB4gFxfrh0W5OtkmZdFN1Q", "Uhw5NBQy-EOIiCWiZFiL7g", "uiAwVt3HK0mRVtWp9B-gTg", "URWHffwjGUKgXeV01upePA", "uTkbswCedUGyuZcaX0W6Ww", "Uvarm_CwTUWWQYZvzI8OaQ", "uWXtz0WaO0qJ1W4TfWnLqg", "uZjxYOMa10GzJ9wQkRge2w", "v1ZOE9G9gEWAqNJX1A9f2A", "VDn1hO6ahky0GDJt5dRmyg", "vm1ESK9ls0SaxSwHplNALQ", "VO_3cgqQqUOTz30TVUxrFA", "VSLcLM0TJU2kJU5gS4KZGw", "VSZX99mSt0e7gPRM2_XzMw", "vUgpsaXrO0ak7huhkHqlUA", "w-vooMawWU6_4spdgBYufA", "w07bkcUoQEiNJTVDeSywzQ", "WeyKw_WS70Cx3NZdOaxGeA", "WF_fg80f30qvHit0vGBwCA", "wzt-5C_LjEmIQWUVPcYWhA", "X0_GvGrZGkSP9Ub81CHgqQ", "x10FeRBsQU2DIIpG8XIlwg", "xCpdHx1iM06Gg0tYoGv0DA", "XFuSiGupZ02Fut1Dxum_Iw", "xnxDimLvVUCI9tEJqWDDcA", "xq7bizx6UEGaMTHB9zI3pw", "xTqtLPQVyEKjAQZYl6HGow", "y6aASPUwDkas0W5Yl27ydQ", "yBNsYcZXzEiuR0u3tWnnWQ", "YFZimVHKuk--lXS8RCrZTA", "YJGsYfwkik2lp7qWqXyXTw", "yKb2A8M_m0GQDhFWCRUHTQ", "Yn1uv3Y8JE6VSxsf0LOBvw", "YQQamy-tQkCkpZPXKepUrQ", "YSrGwXybGEqaqHNCS64qOA", "ytfIq0TiYU6EUdd0ruaGAQ", "yViVKOLHc0qLiPw9Oy1Obg", "yzL0M_N5Q0eAa8IxACHPAQ", "znNiHfNdTE-szOIAnIWFQg", "ZpyoWbO6GkGglXlMuYUARw", "ZtCqMnFTQUWv2h0MfZ4m6A", "_Yq00H-NWUyQNvGxRunHbg", "_yvss6L59US4-8QYZgcCdA", "_yznwUwL80ejrS3vki0IKg", "_zrnGn1ErEWR8WqrZmKZsA"], + "swatch_guids": ["-e6bmXnwZU2EVwZ8RupKbg", "-HYrY2A5Tk2UWMi4_hmYbg", "-Iqmonry4EKoqAULCKzRPA", "-k9_-2onrEG407or8t1RFw", "-Nf7WM2TeEKujB637h3tRA", "-sqfCQpzpUyjEShEXH9JYQ", "-tDd0gdxQkuvL2id10AweQ", "-XSM6ZcSjE-vrWL7jyF4fQ", "002b07bb-4256-44ce-8ca4-25510e70b6b5", "0217fd2e-ff9d-49f7-9bc8-8d0d6b8bf250", "026ca50e-3a81-43cf-87f5-950687b7bbdd", "032pE8s9Ik-2CImFVDrSqg", "05ac07e1-67f0-486c-abf5-a62866475abb", "05K7dmjigkCByO2ufw9jtw", "071d1bf3-86dc-4a31-b54a-e6579fab8649", "0724b166-9ace-46ff-a191-b13f135b00a8", "076c96ae-9f9b-45bf-9dc7-ce73d5ddf422", "0809a5cf-a0f3-4614-9f98-71518524518a", "08b7c12d-3317-4d27-9b40-34737a709454", "09Cg6dOq2kC2-tI5yyGSYg", "0ba92db2-690a-44ce-92f4-130e9503b6c1", "0c433086-9501-48c4-8d0e-4474eb420c61", "0cP9EcZRFEuMOvmZ_Kx9dA", "0d2830e7-8eb0-4fa3-99bc-7c80f01dd991", "0ecb8a2a-cffc-47db-aeda-fb0684aef1e5", "0f71b9a2-dd88-435c-a571-4503d12dcf2d", "0ffad843-d6c9-425a-8686-7217009c867e", "0iSsaY-HgkmLaRHCn5vEdw", "0ps19D7b_U2iejdKQFORyA", "0S7Nqm2kv0qMDTh-5Wy0MQ", "0ZKR_qxrWkiWkjpot8s6Cg", "1421caa9-9a0d-42c2-b847-1500ba2e483a", "14afd295-bcd0-4595-80dc-d477618b9ab9", "14b43bf9-5ba0-484e-ae5d-55cfeb09a70d", "17040a88-87f1-4613-b439-3316e33dbd48", "1773d5d8-4714-4721-b30c-97e25d6f2a5a", "178c6beb-c737-434c-a53b-d14f438f6a98", "17b16982-871c-476a-860f-f6953c89ee8c", "17e44296-1cd9-4f8e-9aa7-3837dc69279a", "18b52db5-0261-46a0-9214-ff510d455ed9", "1919d319-6cce-4500-8766-15fed6a13fa8", "192ba73d-6990-42da-8fe0-914c2a7181eb", "1acb1f56-4509-449f-9c6a-065bdeaa9ee3", "1b1d08f2-12ca-43dd-a44f-ea2820b919b4", "1FcyrVGyGEC9dmOq_51nFQ", "1LonkCbt4kykeAScYKpwyg", "1qHXl55Qn0qSb1gAhr8Wjg", "1VGZ1QCvcU6rsKVkUDBNjQ", "225209dc-3afc-40cf-858e-89f7afa00c0d", "22dc463a-a89b-46ad-9a0d-557c742b4a80", "23428ca9-be75-4146-b400-df01d1dadac9", "24zVmiGiaESrM5qcRfF6gA", "272fe8eb-5061-4729-a7a8-414ff667a82f", "2738ab5f-8c94-46b3-bac6-4a8111c41c21", "27680277-aec1-41be-a476-9f51cf775580", "27af51bc-8226-4911-8d47-22d28ea8e378", "2913bd93-bf35-4bdf-b83a-d40a10213adc", "29EZ6r2SsUakWjzuevxeoA", "2APfqVnnQUaDYKi-fQg8jg", "2axtF2iOGkez1kfRRGUbYw", "2bb65868-ee12-4871-b50d-64bbc9066f0b", "2be2f040-68e4-44dd-9099-29f11110f3a4", "2cf2b644-3fa6-449c-9fe8-d0ab917f3106", "2jj5lxx6Gka5U6j0cWjN5w", "2lD-mjl3pk2wWv24RY2tbA", "2sf-FU4WckGBDWUkVz4z1g", "2vawoQvJPUegnClmr2RNAQ", "2Z_FxSPiyUODtlFZWF9DFQ", "304e5cf1-5c94-481f-8e49-e6a943e6b300", "30c61e63-5e09-4a6f-8915-1e1fe4d55cf0", "31ac9d4d-3d6b-48f2-85d5-dc4b8165a17a", "31jelNWFyk6FlWt4HyPAlw", "32a5e42d-2b60-40b1-a145-ed96fc91af8e", "32dde6dc-e3dc-43ee-9b7c-987bc6778c17", "357eb160-360b-4a63-840d-65d11cc8ffc2", "36e63797-5a42-4779-a5cc-ef5c2e7d17d4", "37f8378b-b98c-477a-b8c9-9340772800b5", "381a9f13-5b44-48e6-8a49-221422a963f5", "390a476a-5545-48eb-955c-1bf1a16012bf", "39f04eee-3a58-4e53-9f5e-7e995d46ed71", "3b2cfc85-4e2a-4e0d-b738-c296ae8692f8", "3df5d13b-ba77-4b51-90b2-7a946e4d0be7", "3f6c8bd2-2c1d-4c8c-a9de-16600fee9645", "3frH6KxZ9kGUYkjeCDpylQ", "3PfP661Fg0CruzRxis9EiA", "40283127-2ffc-4989-82f6-29c3381bf9e2", "40ldoL7MAUKnb5JA68CMBA", "4169bfed-8403-4590-a29d-4941275ac0b6", "426d0592-f8e4-4bc6-9fe1-a62b91fd57d5", "42ZNGHXMvU29yo9s9tVpOQ", "43501b0c-eaaa-4ab3-9d03-2ca61d2e8fc9", "4398ce1a-e6ee-4da4-ad0d-7abfab41020c", "46970903-9799-4d25-b90a-7162c1f5dbef", "47da86b9-4e6e-423c-b8c4-ea6bf8472509", "4828b50c-95b6-466a-bb25-514891d78202", "484b6c13-af22-4ad5-8c43-34c0de095d49", "48abd952-214f-48b2-a8f1-1146f6f69aa2", "48c18418-01dd-40ff-9dfe-c4cf5f4e1245", "49bbd11a-e5c9-47ff-b04d-72eb321e8a57", "49f5864f-9d40-497c-88c8-e87f64d41d74", "4a85c181-92fe-47a5-925e-05f0ccb25006", "4c26d1e7-2382-4478-b9c4-a068ed01a607", "4c845dbb-26c3-48a1-b562-e28a7416b154", "4d52bbd6-ef74-45ae-a6c2-bde10ab8eb16", "4Eck_dNL1UKlIZWsA1YppQ", "4effd67c-a52f-4b41-a5b0-04287f91a7bc", "4ElTihrMA0ipcgeb9UG01Q", "4ijeE3LqUUuSplKrMto6Kg", "4oywwjb3-kifr-um9x87Pw", "4RH8_Z1mqU-JIVvx31wOsA", "4scG_UBlzE-5DWRhSrtm6g", "4VkPVbBVLE-eJHilwGMAbQ", "51ef8d39-2b94-4f9e-9620-07b6b0a913a5", "531b4297-b38b-4362-9854-4f2139ea43be", "53dd9e44-7096-438e-a7c3-11d7c307426f", "54d3c589-30ba-44c1-a77c-8f9e16eb0b20", "556141f7-6fbc-4065-9faa-8d8240a93d7f", "55901f12-d5b5-4fa8-b4c8-e479689ee39d", "55b4e661-43e1-4708-82d5-2dd2bebd4f45", "565c17e0-496d-45bd-a2c3-564e118c06cc", "56qchovVVEaVwL9S52kneA", "5864d23d-1a86-40a6-a52c-46ee670e4508", "58YffnGSUU6umcI1i0RvZw", "596097a8-d545-4256-959d-1d3cd96fc64c", "59866578-d4e0-417b-9483-233ab108b1ac", "59a62fa3-9e15-463e-9bd5-3aecdf604c1b", "5a614f97-ef7f-4b70-afff-9ed837a86407", "5a96ac4d-03c3-4214-9dcb-c2044ff28f2e", "5b5281c9-b795-4bb2-ba7b-ba52047ebe3f", "5b58d879-2bc4-47ed-a996-1af2ace16425", "5c4a2b35-0e1c-44de-8c3a-96d4a6458b1b", "5c8f36c5-07da-49f4-af02-d23207a43216", "5Dbc8qCaY0OXEuwykM4naA", "5e00a832-3f25-4dd3-84d2-b6ed6a4b4c37", "5e7a48ed-8f08-4990-a3ae-05722c9f59a8", "5fb2e235-8c67-4335-b432-edb10db2b139", "5iCrYb8MBkqN6IBEe3IipQ", "5mcp7-2ZEE6n3-zN3_CoWw", "5TJPDmvEBEOkP82ZWLFqtg", "5_-QLURc_0W8qeeN3Q3iGQ", "5_UnQ7vZ9E2T-EhJ7M-mQw", "6145292a-9002-41c6-8cd5-43679b6f542f", "61ff7a02-50ff-49ea-b95e-c31b95a30c8e", "61VZkFjB402brzWm4UsftA", "624c85e5-095a-48b6-85fb-c9bbade19a8d", "62bd5dda-08da-4528-83c1-f0ca3812dc6e", "631c9015-2a00-47ea-bb35-465a2ddbca5d", "639f5e2c-6fbd-4ab4-aa10-7772e0932798", "643b5f2d-0666-4869-b699-03742de34d16", "64545dcc-a8eb-4c69-a539-78ee73a2d327", "64850553-cdfe-455a-ac00-dafbe63d613e", "6547pqf6vUm2L89AG6D91A", "6564acf1-4d70-4f92-92ac-08e2b76dbb6b", "657f28d5-672a-4f27-86c6-3cb00f25bb84", "6584dada-499e-4d7c-958d-466955b20640", "67490e3b-0f9d-4bf8-84fb-54957b981c8c", "67bcca75-4ab1-4964-8688-9908c464d355", "67fc9269-8dd8-4d6f-b594-c77e716f2482", "68273c9e-96d6-4ddb-aaab-d99229e864d5", "69vv21jfTUu1E_LjK5bytg", "6d703981-2734-4c45-8983-cdd5f328902f", "6dd95046-acf8-42fe-ab78-80a334096a9d", "6e0f3af8-297f-4ea7-8f9e-4a1bc57d3e35", "6e152cc7-e93a-4408-8092-236db420b680", "6f2e74bf-1e95-463d-97db-d5d1a53b2c28", "6kPcLZtEqU2Hs4yBFvc_sA", "6PyJEQxyd0K9TPRnvt_86Q", "6t2rCb6WWEW6pJbcjsAFCw", "6vjA_vHq70Gclypll_GS_A", "6y_fzLhG7E65Px3-CYiyXQ", "7-lAnMa7M0u0n0nmeIINgw", "70112887-dc8d-49d5-96ed-0845204e0b58", "713781e5-ce47-41c6-a24f-94d42a565e30", "71e5a0fe-fc8d-420b-af3b-e74160e0d1f3", "7221132e-053a-4c58-b719-ed3c8558907d", "724c79a3-822c-422f-9462-a5374ee0211c", "72a010c0-bddd-4e5d-bca6-9821d82de9a6", "74b009f4-e128-4c59-8a6a-4c0ba6119bde", "758752bd-db2f-43d2-b580-55b3e1efffd5", "761a3193-60f0-4190-80c7-285b8192e794", "76CA_KCr806y1FA35m-YTg", "78eb0969-f246-4cac-acd6-295df7c092e4", "7afe1bb0-5ca9-4263-8f85-49d3a18353c7", "7amHTgEv-UOJSGl0ofo4mw", "7b0d6661-e856-4ec1-ab90-d89c28a70826", "7B7vpnMTfkGLcMdHCZ_kWA", "7b82c3c5-fd1f-45ec-b83f-e32668aa8dce", "7c1aaa7e-503b-46ef-b1c5-0c61a75b916e", "7d8e55fe-3c34-4b4b-9753-0021f6cc6454", "7f9cb939-36a4-40da-8337-025e1d7bf8e5", "7fb8496f-0e6c-4e48-b8fa-5709ebbf4820", "7Jx8mrxoi0aQtZEb03CdRA", "7nRz9Jmnn066_PzngkhfFA", "7WrSbK4Ml0K2z-ig5HIUCw", "7_n11PF4-0KqTZ9az23hqg", "8053b5e5-0f67-4329-a45d-1ee9fc830820", "815138e6-b0dc-459c-9538-20f5bbd37d6b", "827a1538-51bb-4fef-99c1-7f1bebd9866c", "82de1ca3-67d6-454d-84fb-0eeece71fcbc", "830be2fa-60a5-48cc-931f-34b670eae4bd", "83522c12-9549-4d74-8c87-bae210bcd2bf", "8377ab96-c908-457f-9fee-b784c9a759f3", "857fdcd8-704f-43d0-92b9-bca26619b153", "85eb7f47-bb31-4c66-a8b3-562d35028e6c", "88c3ed05-ce41-4a9d-a4a7-8a428d240b3f", "891d902e-3257-4a8c-9001-858046007997", "8ANbOhNnO0yLQOCIBsDwfg", "8c74ee27-3d56-401b-8a8c-7868a80770e8", "8c7b09f2-6213-4ef9-87ab-a2df72d07a22", "8c94e45c-6605-4b4f-9ba4-2c6c6e557228", "8cab8cc6-9a2d-434c-8b72-e66edbdd223c", "8hUBfmq28EixYjv92Xcrgg", "8iZO162KNUiMC8l_z3OC_A", "8XajS2adOEOqzqnLy6-zcQ", "8xjU4GQ4OkaqGyad-ypwtw", "8zXWUxwZHU6tThCV4p5iGg", "90869e86-cbc5-4024-9d08-fcdc41c64eb5", "909ed249-ce09-4304-8b02-56794fa7567d", "90bc2d31-1715-4d1a-813a-7c57e66cb017", "90qQe0AR3kOou0MsuoTmPA", "916384ca-e209-42a0-bd64-c64f5a40b56f", "922f229c-124f-4f07-b4e3-19138420ca19", "926ae943-76cb-46dd-9c66-a6ee44ea9194", "9374bf66-2ee5-493b-8439-efce4b201904", "941c046e-4e95-49f8-a7d7-19071fcc3c94", "95e4cc30-cb68-473d-a395-feadf5b51512", "96e2b29e-e553-40f1-9eb6-8a9f7e64da39", "96e42c7a-2881-4299-a2fe-80c0d4d5ca26", "97938f4c-52b8-4c85-8575-856654666316", "97bbb384-b37e-4ea1-902c-88fbd6854ab5", "9967ce54-c2b7-437a-946e-11f8ee6d6fdd", "9ad37173-e640-4b9e-a474-716d482b73d2", "9b35ae52-febe-486a-8557-ec7190a9756e", "9b9t8_aKH0-gQXMhZycaOA", "9GhnX4Pj4E-wxbPlD8GB-g", "9P8ARRUYdk6hCoGMKhix4w", "9QG6qp5FBU-u0-JK-yMMqA", "9SAPupKy_EO6YeG0SMkI5A", "9tTuREkoQkW0ycqOjWXsiA", "a089bcc6-798d-4777-98e2-7afd290dc6b6", "a1c51d1e-1d5f-4f8a-bc0f-6a20eddfe58a", "a36d252b-16ac-4a82-bece-de145147e7d9", "a38254c0-00e3-429b-9d5d-b9e6a9812e69", "a5925738-a451-4f25-b622-ccbfa12bd88c", "a5nUvJZgTEGBUvdHx0Y6Ng", "a6796264-9c7e-4605-830c-06db8d0dc780", "A6VnGtpY9Ey0atf3zaLo3g", "A7l6Q2clsk2XyKf62D3dHA", "a819f49b-6c7a-49d3-9e6a-d9d79ef5019f", "a8de80d3-286a-4fdc-bed2-75d52f8c0964", "AAbGHOOitUu9enweOXbrkw", "aBQ3R9boek-mYeSYSNvDbQ", "AC3zfm38JEyBZWuXDh4Ukg", "ad61c418-6d77-4a99-8ac5-9f10f5a3d42f", "adJVA26PyUSVvmZgxkCAmQ", "afnIqin4lkKKqWrvBvVagA", "AfqXBWLBQEiiag4cEtoEiA", "afWY5VGiF0isGn6qpo4KeQ", "AHL9ydUbqEqMw1tluIvyQA", "apSQiQ1eO0mEzRsbugeu9w", "as9fjqDWiUi1w7yYavKZXA", "Asfe0ZRi50eryD5wlJzgzA", "b1ea158d-1663-4db1-bb09-9847f6e9d1e6", "b415fe60-3e9d-4f4d-a53a-ca15eac6af7d", "b42d903b-b393-4f49-af5e-38d0fb6c42b0", "b444ed0a-eab5-4bd6-af34-d080de74a149", "b520f3bc-599a-498c-8a4f-97cc25aa741f", "b539777f-48b2-44a2-9d63-17ac703e4d57", "b5d62931-e756-4a8a-8f3b-760beba49319", "b6c25a3b-125b-4aab-bed0-e6e5ced2c21c", "B6qNcZQ770eJel13RskuIg", "b7oobDcDtE-69fhM-5g5AQ", "b995b419-1897-4c1d-98be-83d551f7ba71", "B9DJPqTeYUuPnPHRbKzEJA", "b9f51861-ad10-4878-a9b1-d849bccf532a", "ba6b6e1a-a09a-4ba0-9523-552869f03336", "baffdefe-de21-4551-9330-b50873965bdd", "bb01b51e-a95f-441e-8c52-abc3aeed7145", "bbfa08e3-8e6b-4e0f-b264-1b398d7cd44a", "bd4a84e2-b67a-4269-a26a-17fb23ddb09e", "bda7a57e-5227-49f4-a119-e3f166604738", "bde08851-a9e4-4faa-8f04-a0a661672472", "bea795cc-aabd-4229-84dd-49ab4f91a445", "befa2f11-b9fd-457c-b1d5-9d82e90b70ce", "bf82f2f6-9af8-431e-a296-0890dea48ba7", "bgRKH1wfqkWVHcuSnFYnlQ", "Blev_VFjRUysY_nymGWAVg", "BLgkgLeS-EyazgPQxGcavg", "bQyNgFWAe0SPttPri6hxyg", "bzYdPb8GWUS-CgT4wrziIA", "c0c33a35-494c-4a1a-aa53-d27d9b9ef5a4", "c0ff7719-c56a-4934-831b-506ad0f51cca", "c1e430c5-fd8d-4032-9e3f-8d87cc8784da", "c24f1DNZjEmqkt-FibKUjQ", "c3c9f89c-580c-43c4-b38b-07394044e0e1", "C3ll7uPfUkeZ1QLX80_BpQ", "c4ebfd1a-fcda-4b98-a151-a941b1801867", "c5884778-a87f-4612-9717-86fbb65d440f", "c5deba2a-6e35-4b13-8e94-8ba5457f39df", "c64ac7a9-7fd4-4587-8626-0413a190b706", "c6cf096c-f0cb-4500-80a0-bc2916b5a426", "c72911d0-453f-4732-b1bd-dad520220a06", "c8cc9589-4831-4e73-a865-ef8c139a2ba7", "c9589974-c261-485e-b571-cb2f34fb178f", "C9cPjOoiCUCHrCBi809CMQ", "cac27853-e2df-4d77-b3fd-af3bc0813f01", "CahBzgN3D0y46AQsvGC14A", "CavRDqIlmUeTZBtCLn1hZQ", "cbe29e9f-f2ac-47fb-97e1-8bad16abb89d", "cd5d7285-202d-42d0-b93f-04245875793e", "cdca593a-8840-4140-9145-fe962732040c", "ce465d53-3807-423f-baa0-bdba2a75ceb3", "cea53c7f-24a9-4e3b-80b9-fbf95738f973", "cf515c87-3c0a-4443-9f63-5ea1bdbaa61a", "Chiz8maLoEGDpZOJZEtdQg", "cKHAetNcgUOxFfDkAPPF9w", "cKLBS6pbf0SFuavWyeZ7GA", "clFrpYWFQEmDkE14f3o_Ig", "cpTKHXjj0kulivZCKVQqiA", "crl8VlZ59kGxG01noGEWmA", "cSFt9MWLj06CDzIzATqrTg", "D-SngK7hhkOpoZ2aMWv9EA", "d0bb468c-b78f-4e80-916d-65fec3ec0010", "D0tP58wC30mJ00vNxw8hqA", "d1eb3573-672c-4bb4-9ec5-7c81e6a38d3d", "d27e5m1BLUWI42Pxnj6F4A", "d2b40639-5d34-45b6-904e-6dd29030ff44", "d3-sxPTJOk2rKo-Jgy3wvw", "d320ad9b-ba5c-45ea-b2b8-0d3e409a9db1", "d3a9b576-5433-4305-bd96-b08820c09212", "d3KmqVuMQkCYK6sVTMOTOA", "d5987fee-e3cc-4336-853a-5df57f73df05", "d5efaf80-7f34-4c9a-bc3a-b80b1ac70ace", "d61711fd-589a-4669-b991-9408a58fffd9", "d66aa400-aa5a-4539-a25d-5f8ce94dc281", "d6823e01-69f0-4f85-b94a-74894356a2cf", "d6edbc00-3c1d-4f49-8412-3ef8c7c5f4c2", "d78643e4-c0d2-4836-bf22-b5a0e2d65b28", "d7f0ffd3-ccf9-465e-8afe-4d7efb78dd2c", "d81d0b62-e052-46c2-9252-4bc5fb219f53", "d8d490aa-7088-4163-9305-1441367f490c", "d9mwowRelE2lDKEA0UQMZQ", "da185fbb-ad2b-4f30-87a2-491f24b78ebb", "da6d0ced-3d43-4878-a868-925f2ed6fd5b", "dDLQbD9jtkOmw3PR5QJdfg", "de0a5a3a-8aff-4799-8269-9efa3aeb383a", "dee70c38-7a99-4c2b-9181-665f1bf75aca", "DJnKyNXtuEW1tbKBFscIng", "DOY6LpHq8kui8yDWGsNgZw", "dro7VLV8BkGjqpPAbJheYA", "DRS8xu3AqUOy2L0RKF3f7g", "DtFYrQwaCky5C1ZycqZbiQ", "Dtis2HpVzkelZb6M9mW5TQ", "dyEaPloE8kazWC7Zblv8uQ", "DzgN67HoikexE2rTlZLdYQ", "D_Xmo0rOzkS-kgq1CYXt3g", "e02838f8-4029-455a-b35c-dd40f972b05b", "e0397982-c2c2-4733-9a40-46e18675b5af", "e13cf33d-7588-4e2e-955e-f0a29d6380b7", "e1f49b74-b071-4bd1-9c4b-af36d24d45c5", "E1GfXDLUaUKaG9hTLAfFPA", "e3454bc7-c88d-4958-8b81-db3fe5472a1e", "e37b07d1-6b9d-49b9-94ca-1c728e228cfe", "e4IDo-fuH0C9YByzcX5k-A", "e548124c-d7f3-45de-aaa0-f66130eca576", "e5de2e45-207c-46af-9a5e-eb01a0c621fe", "e69372eb-2fd8-4e17-a448-ee0be61a2c7a", "e821aa74-2d47-4388-bcfb-1893b8a83a52", "e98c3f98-7844-4d6e-a21c-05027033c18c", "eb3SmJTIh0628LrZUkI4KQ", "EbVDqEJQOk6j0UvX3t-8rQ", "EC0ttvk6eU2HEMacLAx2Iw", "ed134bee-d199-43eb-98bd-206571603f40", "EDLxuGjEVEGdOPXDqyN-xA", "EfdMcnfHt0mr0PQ_maaYOg", "EiGgN7uVukqoRE7UJcMLTw", "ERnUPsyT6kuEnIRZF8zPwg", "Euf1h1qsZ02evDsyoHYbPQ", "eV3Nv6SWS0yl3f-fgjPb-w", "f0pjsV0XMUWoE_z2kqOlSA", "f3b2330a-b981-4c7e-902f-1f57382f2388", "f4ad4b9e-dd42-4711-9437-5beae51966f6", "f5403c89-9769-479b-8a05-c71607f9b189", "F5vJzMfNB0Ob6Abjadho7Q", "f750de46-3758-4f7d-9709-0a84b1027009", "F7luQ18ZXEaITFiCqqwkpg", "f841cdfa-45ba-4f09-a117-503eef34cb4b", "f8635fe2-90bc-42c8-9bbe-3387b5157e18", "f8b0cfe8-e129-4578-8bb5-f60af5d38599", "f9aaa7c5-59bc-486c-9a6e-27a8ac618f84", "faedf215-289b-40f7-90cc-f5b98517d133", "FAviMCQ_EE2Mpt6QPo5OEw", "FaWMLkJJgEKFIO493fzGxg", "FC8ZFBPt4EyTe7IYbv2Tpg", "fcbdfbfd-254a-4538-b119-43e23404b8b5", "fD5vtKxBWECH54Jj4FbnUg", "fe304452-b57b-45bc-9c1c-b85b70bcec12", "fe5288bc-0e02-4ea3-b17d-dee0e576979d", "FE7_gQNBt0CXzMN6di5HWA", "fed760b2-15e2-46b2-b1e3-cc3c8d780d5d", "fEPwvuJCWkGBe4IekXpmLw", "ffHKr5EcIU24PbAtWSnalw", "FFIpxInE-EKbk2GY-pIkyg", "FjnZ-D1hcESLOWL4Ukv7kw", "fLWfBSEO6U6aHrDm3nKFEQ", "FNe9LkWJMUeC3frYJMyEaQ", "FsATqzRVekO9hQ3u_q7keg", "FUP4iRnoc0ikNmjrFD06lg", "fV4H_1ZXx0-OVtlasI-xGQ", "FvEjo9let0e_Sr-kWq5AvQ", "fvoaMN4M10WS6jiA6XlOJQ", "fwIgUAM_C0WhKds0O6DyPg", "FwiizGjYnUaM1VJ53R1cCQ", "fXNkMWFMV0aXalK_y_8SHw", "f_c50saStkaGJuhLjJ9lfA", "G-VKgepBkkeATcwCaS6Rwg", "g4diCmk0wEmIKUVozk9tDA", "G4HIPUjekUigiU0-Kr9mdA", "gDH1WmW5rk2RdY4wJYYZow", "gDP3y0J2EUayzVvBT9-92w", "GDvaUU10-EKh3pKUQ9tnag", "GH9q8_0O9EqjGdz7lGWD2g", "ghM-uFq5-0Ci38AOHa9zZA", "GK3yolCNlEm3B_5DtHkE8w", "GmS0wKwzVEqdf3NoTMHG5A", "GQSpXmD0B02_gwzpq6StFA", "grutFwJD_0Cb3t6fddNg6w", "gT0WA7Y1H0my5uMlteIMMg", "Gtq8yRlt1UeFW8U-sc7FiA", "GWi1rEebqkCV_TS2WzQhcA", "H6fcuLxeBUSY3oERFc-plA", "HBt3ToOkUUereoPKwyvzuw", "HdwZhjrqxE6wuf68MnDhDw", "hf0gkFVS8ESvceRsC3LAXQ", "hhs9GY3XZkWb-8TXODvo6A", "hiMYlmk5sEqh-sElOY3A9g", "Hk6svQ5Sr0GDKLxOyktk9w", "hM0OmxjWLESbsJ-M0Z3QvQ", "HncURj5yi0ykk2cVyCZ4Sw", "hRxyLsKfsESFAsXAyZQjoA", "HWc25-d8dUmNN2F3n8mV5A", "hYu9itGe1Ee5Neanhj9VtQ", "hZKPr4sg10SRj9DLuEgOrw", "I1J-xY4YNU2dB_od4P_7Dg", "i1XbjBqTu0CILIuJClWimA", "i33RmJJCwUem-ADkOwMNQQ", "i3rD_hnQG0a-5f6Ve4AfWg", "i5RKLo-RbEqisnGII1wpaQ", "iaDL2im4lkCXUo0NWM-F3g", "Ib_iMwvUm0CJ-t_Thwt77Q", "Iezz6gCFfkKdVcmPXIhunw", "IjRMopsYlUqSE6327I9TIQ", "iJW8XGJfU0G12iXarjxhdQ", "imDGL6dhrka-KliYlfpdgw", "iokdlBfjuEuAwqIMOQwxdA", "ITH2OpzjP022vZ5JgxV_iQ", "iufpnteVCEiiZu_m-yPovQ", "IzdUC8x0F02i3BhPfEUOYw", "IzkFARlv6EenmrfcNkcOuw", "iZy7RmfAz0603oMO-jD-WA", "J-K34UX_B0yObjQI9tn0vQ", "J1bmomo_3Ume0KR2Yh46Uw", "J5VsmblfWU2MeUjNo0fz1Q", "j66We9SYr0SNQ2ufZqIqhA", "JFalLDUvm0aMm3wh1UVfVg", "jGj28vhq8EGwP2RuM074aQ", "jhPUIkf5bkinMSsqPTK3fg", "jkuw9EhCpEKn-2lKJXWd3Q", "jna5wcWK90WokJyoeJH1iw", "jQwNBXyoU0Gk4FyNw6jFxg", "JS7aa4QSUUOpUYPqRZEb5g", "JShcoWWPz0297HeEUJbDCQ", "Jt0QqNrAp0qzUR_RQoivVg", "JTgXKeT7w0K2Mw4Jk2HCqg", "JVZHZm6nGEqE2zEEPoHbwA", "JWBK5v78Y0uRVEXlD-Qk6Q", "jwEB6LyEaEekqtPNRc6TVA", "jwXdjDbEmkOFyKZHT_RYOw", "jXBi28cRV0CEQgKIrpD0_A", "K17dIz_TRUq5D50NBfLjyw", "k3dbqP5z30KU37MVPpsEPQ", "K5vPqD3BrEuODRDzFdC6Hw", "KaZqNx5VKkarRrOJcTuD9A", "kgCKjtSjFEmFq9ez2llxKQ", "kIcd-yEnsEqxoeogquvmRw", "kkvNbVRjcEKtChpy3W1bqQ", "kLv_wsbbGUmlfIUO1QLYLw", "kLyM-6otSUy2ql5SEmnF9Q", "kmj5zOjcwku_WWKroCeiVQ", "knXPidb-Rkayfc3kSHfZeQ", "KqjQ1xRpokGLKp9GVN1EFA", "KTidYfH0ckWxJGs5qzUBTQ", "kTP3xKhzBkuQF3SqvmYFKA", "KwCEye0nsESFs-yj4h9sjQ", "KwhdQ1deNE6Y6jzO9vaAnQ", "kX7ocqn8X0-4M1nw-KrwFQ", "k_ZYy0SjtU2TB7IFdq2vDA", "L2iFQcL170OajqfW7t6sVw", "L333w3dfIEWDbtt1Yn1gBg", "lBe3yxuE40eBmTrnEHBqQQ", "lBePHNL9LkyaAjEFyFE8Sw", "LdKaVAveokmp3qRwVBxnFA", "LDqcPkvNXE2gFXxbpNkfhA", "lDXambcP60CeY2C8qeGDFw", "LEUQnDz-k0m-hY-AX3Z9Sg", "Lf4FjROSAEqXedFxpXFQfg", "lgqLGi01o02ZFC9Xs5Ofdg", "lhfOKGDAgEKWucerCmH7Fg", "LOeHr4xoik6VOv1fOeImjA", "lq59KZwElEStrviDEQLLcA", "LrQ1z-mTHE2vIePBwm8l_Q", "LRv1X0ZrjkqO7azGpZ7uMg", "lsyYlxY9o0a3TbUrHkxeyQ", "LuYelZY8NkqmOp1pnxPXBg", "lvDIOeSI10SNxnCO_M_9zA", "Lxx3q6JRnUW_cPG_kGG4ag", "LyVf9lnzdku1F4iPBGqk-g", "M16XTvLPKUWRpiAspgsatQ", "M1JVd5ur7U2ZtpvhEhPNhw", "M4765UHzsUeNTwH-Pcxp1g", "m4W5D0Oa9kaWa-8I96YYzA", "m7KXVnULz0iHvKfCFzFoyw", "MbusQ9bte0i9zirlxtNkSQ", "MFrcSQ1DYUm8imvy4ypgvw", "mhsglVEtREmElF5wuLw3NQ", "MIfCkcrFOEasJqfDmxThdw", "mqdncbYcuUSGFvFqEU0z4A", "MQOxmUk-mUG-Ee9m9nX31Q", "mraJrTA4V0eARb_Kq7yYVw", "MS7EPNLJEEyMLC6_yHQYtA", "msOFHoFN9UORkeXXtdqBsw", "Mt2a9ygWPkS-DyXj1q-Kvw", "mudt-Z5-5USdnIq-xaDgOw", "mvj5RDTuh0qoql0OyBFI9g", "MVkY4sU5BkGC4QhweRiyIA", "mvMeyStxGUGlNLSYNeSubQ", "MxnAq_XOw0uTzQ8IIxqb_Q", "n2pZHS8A3kSgyFUCI_-8TA", "N9CBuYaMtUOrCks5UcOycg", "newGzX_zA0eGUX3Iayq5Ag", "nKauYAyWhkm2AMHFyC-fLg", "NKf5-r2S_0iP0SaO9bjvaw", "nLRxEIBhUkyQsfrk3nzsDA", "NMmOn0zQZ0mJLxCMnsbIVw", "nn86ATWxekWdIGlPePPgGQ", "nO-YacKU1kmLjTFAAqNeYQ", "nODTglESMEC9tBY1k-LrsA", "NpMkoIK62k63QoLVEXId6g", "nPUQvBsxu0mzhQg9YnWEgw", "ns5tIRishUOp-nYqLedcwA", "nu_UGtkJOUK-5MWaW83MUg", "nX7krD8e2km_ZlOLWcoxdQ", "nZor-clxlkm8EO9Gco0Otg", "o2WfRyjJy0CKoUg8YIxNjQ", "OfenZaa1-UGdQ8YVOrXjzw", "oJFfs62tR0WVRry2Smt8yA", "ojvIFCTpJkeegestF065wA", "OJYpuFw810Sg4bTw0XQf-g", "okpg4ftsak-wc8Zu83uytw", "oKxmH-1s2EyIDkZoOQSQQw", "OnQeVxdstkS2zGxLBAuM3Q", "OnZujqqU_UqwxmznvBBMfg", "OqsbZ8Xls0i8uA6ha7H3qQ", "OrqqGMhK1UKGowFxpJi2mw", "oT1vLrHW6UC2hWLIT3f_SQ", "Ot9EG-FfKEauT0u6pSZuWg", "otQaNOsODUSTfHlCX0IVOA", "oxi26T5a4U6L73pF-Pgrhg", "oZ8k0Qv3SkG-DkY0_dP7UA", "p3g8YAOhTUKAiE-Wh-X-aA", "P9bBSL0YLEyjN8PxMfgbKQ", "PcUZrdKXhk2XHa5Gy0YnnA", "PEGqjoxP6E2yRx_A-HphrQ", "pjkTGVHkIE-BzCVi1KAesQ", "PLRhiOhhb0KhatFH7f3pwA", "PMa9370LPEig_7pKwCceEQ", "poqpmOloPUKotHH9p0YAyw", "pqLkLbri9Eu48eryQIFGMg", "PQTvQiYu-ESRUldnxgm5BQ", "PR5dh2jf2kqGLO0lXgA69A", "prEZHWvOM0ysaNfAe06ywA", "PTqpWqqU9k--lNlRrmlVbA", "PwsLxCnzOkSW-RGz3tNb1w", "pXrZhtzjV02_qvIQoWEFWA", "PZx7Iae0pkiDaxT9t3-vgg", "Q1knTl-n-EeAUwotFspNew", "QBglgj80zkuvUkk55Y5Feg", "qHyKfCsrbUqSWomjLXf7Hw", "qJGX62O_HUiZWN61NAc1Lg", "QJSPgq2KXEOWNynY8uqQvA", "QkfA71-1z0qOiZAVrbzhtA", "QlZt51mdn0WuiqKoJaIRyA", "qoVZuGa5NkOmyfikHjDhXw", "QP38EMygY0S-KIvdGatARA", "QzjakcO8tUaPeiI5KBLKtg", "R0UCacpdhUS906Xf7l142A", "RA7cizxxYE6J8yV_0XwPxA", "RBRFHN8_rkuImbyDAfcHWw", "rcdwgNvO30Cvx2ycE1GfHw", "rCeRk8Pdl0W5M47JAQPhpw", "Rf7CDh2bg0-HdG5n7phGaw", "RfZNDPUok0WyQEa3Ko08ow", "rGMFdPKmK02_OvIIFYlAxA", "RKmRHk2N_U2gd91nybo6vg", "RL6HkufaJ0yubTpCGVI9eQ", "rmq1AOQvtUGOHvbPUximwg", "rNhOQoP3XkOBHV28ze2NpA", "rnM1qDlYBU6_Dl1bjg0J1Q", "RROvzwtshUGyRudVIBn8bw", "RwgnKTfMEkWrAp_OMGy__Q", "rXhNZtcZCU21dwEHOCgWOA", "rZ45-lecBESqHaLX0c7NAw", "RZFWcTzxPkOfUuTIQ1mzkg", "rzUd1IMTKUmW0HeMPSIOBQ", "S0AKRB_m2k-I7snHnUigtw", "s2kCppLsN0uY7Hy6qjQc-g", "s6DQxJ3gY0CG8L7JmcmRTQ", "S9uymuvraUCOM8gmB7bnEA", "SeVmHtrMS0y7gKCYkHcrOA", "sJioq5ARDUyMHFji_6OxFw", "SqHAYcqBXU2PtF2urWGruQ", "sqPMH0LhLkeG-4XkxpU-sw", "sscD2O3LEUOAKkqjGpGe8g", "Su7kiW24d0KapNns96JoFQ", "SWvJWvMDpE-it5PFoPgERg", "sX0ZZDcCUkyFSJISfvq0vg", "SXG9rdEkxkOR31Nk0aTOww", "sxUE0iOSZEmezm54T7xI3Q", "sz-J7RjesE-ke3bKwumzZQ", "t1MeoroFOUioYmPfT9mCeA", "t2bByt5oUEG90PMMePCP6A", "t4gMAKp4L0WBaTGf_rtqFw", "Tbrc2MlSlE6mKua8qSoluQ", "TEgugZRs80iJbhL87RdYwg", "teVKVbVEsEyqOSuRdqHQzA", "thG3Dc1PFEqAvT9xCVshuw", "TKbjEeTWfUyA1u4laoULMg", "TLrw3oncCE2MeuVifXSWTg", "TnEDe1QoMUCUdqPL_7CxTA", "TtncjvYcK0qHqWN-IMMoqg", "TuAjeDmPuUmFr_ESx4q6WA", "tXhgU1o_wkaNQ-7P3KXidQ", "tyzdtSkR9k-YCnjNvPAT6w", "TZ3oQBp_3kWY4Vzscgv5Wg", "U2ZbHn8WJEySUXVpzAY6ig", "uDcJZClcFkiNdkEEh9IIyA", "uDJ5ZqU1f0CiOAH8WY22Yw", "uiBklotTUEqRI2F67vyJyA", "ulOBBvFEJ0ioFA1qmL390A", "uTHwo8qd6Ei3Oy4gz_j9Ow", "UUyjVU4mvECPhI9xTMYR1Q", "ux9DQVSRyU-_5GA-t197OA", "Uz2hrptCa0erjqyPoiZxew", "u_Gzj9Nro0WS48xKnH9few", "v12eJ8louE-cW7-pCGzVLQ", "v3af60p1Q02NpdCqIRO9lw", "V4CVhCB8eU-nrfYgticVOA", "vbOUaJsGHEaSfQCivNcMWg", "VDo682NvSUa8xYn8zTX-AQ", "viL4CPch2ECwG-RNaWcyZw", "VK4YBLXO10G2_aEl3ZQUGA", "VpqHNpPiY0CfRJEjRRM-mw", "VQjkyb5aoEKHKo_eZsq9ug", "VqtOYBiKRkaSW4xAlBxQgg", "vuSwk04zykCe3ANKN838OA", "Vxq6v55a_kGPODzwB5MC1g", "v_5qFi-vAUSDd1fLNL7pvA", "V_duN2oWaUmmao1xHhzCJA", "W4a144so90eRzCojUnDowA", "wat8f6fs80KptZbW39e8tA", "wbFPXIg5gUK3QK6YycZexg", "wCe2yC2U1kyYvPd8Z1rX2w", "WeP_tPjUNUuGojXrVBOAIA", "whEHo6Rhr0CxsDBsBD32WA", "wHJnbDOo8ESmUbQDr0k31w", "whs9olNz9Uani7Gou31mvw", "Wj7I2YiVoE6zp8J_HYs8tw", "wkqKPZmnoU-8TXSWzi2-Ow", "Wm30oV5BtEuFQZFk3dPK_w", "WOxMXWKGrUWhnNInttZ-Zw", "wRPoHTsXFESK5pjkxVMqGA", "wxvSDMommkenIl5hTY2fdQ", "wyKAZSZwT0u9cuSaQ_-IHg", "X3MkhL55Rk-0UK6h4dw0Eg", "XFCpIdeDXUqeYCIgh4fGtg", "xGmnUGF-Ck-jP43r8tVVjg", "XKoxnTXYGkSUu52nVS9dnA", "XNq4Cns4lEGydtktdsXlmQ", "XOBIPrKCr02xa4tKj8wN9g", "xpkEQ6yjhU-WfFPSjvPdHg", "XrSL-v2MbkK0md-FMOweOA", "xVwzV9-m6E-8PVpkg4D-AQ", "Y0e7o0_BKUm7XJSLediN5w", "y5O8zRJy4kuhnXzATSWKew", "yA0KhVg19kWqG1UkyxQogQ", "yaQ7AlUc3kGq8_RW3eTSmQ", "YB7kXMyLJ0mF_5WidhmH8A", "yDZFr27Zq0-JH-EsEsxG2g", "yM5xRrd9OkmDGyQddwOP6g", "yOw1WqEN0EWqR9t53EsQpw", "Yr0mF1EWzEaIyRMOWFiY6w", "yrQCLXiqRUSwnuQRYX2Rsw", "ytmSzjhCr0Sm58MSNbwJUA", "YTR8qybyVkOhNh4iqtU_UQ", "YvPeCbQS2UOcLjPfoIML_g", "yXZO-donI0SHB9-TT0jUdw", "yZR5aX3gQ0WpRUT6cfK1cA", "y_gTbnuYS0GKJmnCs2retw", "Z1F9rjKqCE-Hd2-Ip5g3gA", "Z1Ib9r_fkUqgoZk0uid_Xw", "Z8Bfk0MTb0evZehvvgzuBA", "Z9H3wUn3_kS6pPehL74aHg", "zaSTWOUy_k6cnoVVYzypWg", "zGOD45zNAk2i6P_zPsNPfg", "zJMD-_HR80WCAUqYGe7_NA", "Zljmtj5WqUaRvB3XQTgJXg", "zlqLrGEa8E2WW9ey_szREA", "ZNKGkou_gU-1cxBFy7pDKw", "ZNPzHmrU6kGVQW7Q5ei03w", "ZpcM5H2Q1UCt0hh82nSu9w", "ZSOuWuKVCkqaIKc5d23G8Q", "zu0Jk24WTkan6XWSwGU1Uw", "ZvPidQ57rUS50Qz89ERmLw", "ZXeI9jooq0-dQaX-FlkNuQ", "ZY0rVNemjEuEMGMjuPNZyA", "Zz4XatKfUEapReRAQHxKKA", "_9QlnTPXuEO5OQ3H0CwFkA", "_fBrHCkTKUe82QgxCyI-Kw", "_jkPDSXFUkSU_yOfutQ7BA", "_OM7UNZENEejgO5h7H1ztg", "_psc7WCgOk2JzRQwuFAqNw", "_SPKrYIcnUWhTiSrTFLwoQ", "_uh3kj6hKUyBKOSR3R8dog", "_VfdBTi7PU276qzsYKayow", "_ycDQ6RcyEGAntVlSDpPeA"], + "decal_guids": ["0c496f32-0011-4c4d-9778-59446f70c032", "0qh4mcxJH0Wsrb3nz8UeGw", "167ba0d4-4fac-4143-b5ae-1dbd8e02dc76", "237e0aa2-5911-4704-8d2a-d446ddd2d842", "2tFUNn-4kkK6ICo_Zp065g", "49934059-cc45-46ce-8277-f96959e1f6ba", "4ca3347a-dc87-412f-9eb1-799372e6fdd1", "6a007c73-d930-408a-93df-041a715e0dd4", "6c395d04-2aa6-4a3e-8c2c-695932bc9736", "703ff56b-560d-4ff4-8c63-d195e879a328", "75162f12-44e7-499f-bc6c-9b0612e9164b", "770e7674-6f9b-41f8-b023-ae79738db0d3", "8f19d7f9-4f28-48ab-a1c5-d8f4f788514c", "9e782751-71c6-4208-87da-3a3cffa6a4f0", "a74e6c0d-1300-4928-9141-977a711b71c4", "AXtRUZcYWU6g1ZtM-L4vfQ", "br9hzhDoZUmIevgl7SJkmg", "ceb07e4f-50a9-4ed0-b50b-c59d92ba66b5", "d42UBeUO4UO3Q2F8IpeRbQ", "d461ca71-45c9-415e-8e09-ba93e8d73450", "e38294cf-6279-4863-9aea-1a19aeaf8476", "f5270130-6634-4052-aa3e-9849ddf5314e", "fJVxsLWuTESd-2xTCJPZ2w", "hHhjZeLcaUGN1OyFoYHznw", "MPfe2wahQECCDGfhZE-drA", "n4hqSCv9OUewI30KnbPhFw", "nHg4Uifww0GEX15L27WonA", "NxMm0sSBdUatynU16o0_Zw", "omS7gF10C0ec0ozEoJGJXw", "OZb8bb5FNUuFYOkwANvxAA", "tzMFIJyxZE6cZNuLdaKs7w", "v0GsPFC7cU-_JNG4-ERfog"] +} diff --git a/DorkNet.Server/Data/color_variants.json b/DorkNet.Server/Data/color_variants.json new file mode 100644 index 0000000..5297865 --- /dev/null +++ b/DorkNet.Server/Data/color_variants.json @@ -0,0 +1,2646 @@ +{ + "matches": [ + { + "swatch_name": "Windbreaker_Red_Swatch", + "swatch_guid": "grutFwJD_0Cb3t6fddNg6w", + "color": "Red", + "item_name": "(Wrist_80sWindbreaker)", + "item_guid": "52VsI_lKlkSUAOJlfDnyrQ", + "item_outfit_type": 200, + "mask_guid": "" + }, + { + "swatch_name": "Windbreaker_Blue_Swatch", + "swatch_guid": "EbVDqEJQOk6j0UvX3t-8rQ", + "color": "Blue", + "item_name": "(Wrist_80sWindbreaker)", + "item_guid": "52VsI_lKlkSUAOJlfDnyrQ", + "item_outfit_type": 200, + "mask_guid": "" + }, + { + "swatch_name": "Windbreaker_Yellow_Swatch", + "swatch_guid": "C3ll7uPfUkeZ1QLX80_BpQ", + "color": "Yellow", + "item_name": "(Wrist_80sWindbreaker)", + "item_guid": "52VsI_lKlkSUAOJlfDnyrQ", + "item_outfit_type": 200, + "mask_guid": "" + }, + { + "swatch_name": "Windbreaker_Salmon_Swatch", + "swatch_guid": "JS7aa4QSUUOpUYPqRZEb5g", + "color": "Salmon", + "item_name": "(Wrist_80sWindbreaker)", + "item_guid": "52VsI_lKlkSUAOJlfDnyrQ", + "item_outfit_type": 200, + "mask_guid": "" + }, + { + "swatch_name": "Knight_SRank_Swatch", + "swatch_guid": "ITH2OpzjP022vZ5JgxV_iQ", + "color": "SRank", + "item_name": "(Shirt_KnightTorso)", + "item_guid": "739fd42a-8a8c-44a5-afa3-e0974802c6ae", + "item_outfit_type": 101, + "mask_guid": "" + }, + { + "swatch_name": "Knight_Blue_Swatch", + "swatch_guid": "ed134bee-d199-43eb-98bd-206571603f40", + "color": "Blue", + "item_name": "(Shirt_KnightTorso)", + "item_guid": "739fd42a-8a8c-44a5-afa3-e0974802c6ae", + "item_outfit_type": 101, + "mask_guid": "" + }, + { + "swatch_name": "Knight_Red_Swatch", + "swatch_guid": "59866578-d4e0-417b-9483-233ab108b1ac", + "color": "Red", + "item_name": "(Shirt_KnightTorso)", + "item_guid": "739fd42a-8a8c-44a5-afa3-e0974802c6ae", + "item_outfit_type": 101, + "mask_guid": "" + }, + { + "swatch_name": "Knight_Black_Swatch", + "swatch_guid": "e1f49b74-b071-4bd1-9c4b-af36d24d45c5", + "color": "Black", + "item_name": "(Shirt_KnightTorso)", + "item_guid": "739fd42a-8a8c-44a5-afa3-e0974802c6ae", + "item_outfit_type": 101, + "mask_guid": "" + }, + { + "swatch_name": "Knight_Green_Swatch", + "swatch_guid": "PMa9370LPEig_7pKwCceEQ", + "color": "Green", + "item_name": "(Shirt_KnightTorso)", + "item_guid": "739fd42a-8a8c-44a5-afa3-e0974802c6ae", + "item_outfit_type": 101, + "mask_guid": "" + }, + { + "swatch_name": "Knight_White_Swatch", + "swatch_guid": "22dc463a-a89b-46ad-9a0d-557c742b4a80", + "color": "White", + "item_name": "(Shirt_KnightTorso)", + "item_guid": "739fd42a-8a8c-44a5-afa3-e0974802c6ae", + "item_outfit_type": 101, + "mask_guid": "" + }, + { + "swatch_name": "LumberJack_Blue_Swatch", + "swatch_guid": "cKHAetNcgUOxFfDkAPPF9w", + "color": "Blue", + "item_name": "(Shirt_Lumberjack)", + "item_guid": "350c5c1e-318b-4233-a7e5-1f49909d8e65", + "item_outfit_type": 101, + "mask_guid": "" + }, + { + "swatch_name": "LumberJack_Green_Swatch", + "swatch_guid": "7amHTgEv-UOJSGl0ofo4mw", + "color": "Green", + "item_name": "(Shirt_Lumberjack)", + "item_guid": "350c5c1e-318b-4233-a7e5-1f49909d8e65", + "item_outfit_type": 101, + "mask_guid": "" + }, + { + "swatch_name": "SpaceMarine_Red_Swatch", + "swatch_guid": "d78643e4-c0d2-4836-bf22-b5a0e2d65b28", + "color": "Red", + "item_name": "(Shirt_SpaceMarine)", + "item_guid": "4ce7a70f-15a0-4cad-98ee-5cbb40827338", + "item_outfit_type": 101, + "mask_guid": "" + }, + { + "swatch_name": "SpaceMarine_Pink_Swatch", + "swatch_guid": "a36d252b-16ac-4a82-bece-de145147e7d9", + "color": "Pink", + "item_name": "(Shirt_SpaceMarine)", + "item_guid": "4ce7a70f-15a0-4cad-98ee-5cbb40827338", + "item_outfit_type": 101, + "mask_guid": "" + }, + { + "swatch_name": "SpaceMarine_Cyan_Swatch", + "swatch_guid": "c8cc9589-4831-4e73-a865-ef8c139a2ba7", + "color": "Cyan", + "item_name": "(Shirt_SpaceMarine)", + "item_guid": "4ce7a70f-15a0-4cad-98ee-5cbb40827338", + "item_outfit_type": 101, + "mask_guid": "" + }, + { + "swatch_name": "SpaceMarine_Light_Swatch", + "swatch_guid": "68273c9e-96d6-4ddb-aaab-d99229e864d5", + "color": "Light", + "item_name": "(Shirt_SpaceMarine)", + "item_guid": "4ce7a70f-15a0-4cad-98ee-5cbb40827338", + "item_outfit_type": 101, + "mask_guid": "" + }, + { + "swatch_name": "SpaceMarine_Dark_Swatch", + "swatch_guid": "a089bcc6-798d-4777-98e2-7afd290dc6b6", + "color": "Dark", + "item_name": "(Shirt_SpaceMarine)", + "item_guid": "4ce7a70f-15a0-4cad-98ee-5cbb40827338", + "item_outfit_type": 101, + "mask_guid": "" + }, + { + "swatch_name": "SpaceMarine_SRank_Swatch", + "swatch_guid": "4ElTihrMA0ipcgeb9UG01Q", + "color": "SRank", + "item_name": "(Shirt_SpaceMarine)", + "item_guid": "4ce7a70f-15a0-4cad-98ee-5cbb40827338", + "item_outfit_type": 101, + "mask_guid": "" + }, + { + "swatch_name": "Hibiscus_Yellow_Swatch", + "swatch_guid": "596097a8-d545-4256-959d-1d3cd96fc64c", + "color": "Yellow", + "item_name": "(Hat_Hibiscus)", + "item_guid": "a61e86cd-f7e7-4ac7-a6e8-df269bc5cb01", + "item_outfit_type": 0, + "mask_guid": "" + }, + { + "swatch_name": "Hibiscus_White_Swatch", + "swatch_guid": "ce465d53-3807-423f-baa0-bdba2a75ceb3", + "color": "White", + "item_name": "(Hat_Hibiscus)", + "item_guid": "a61e86cd-f7e7-4ac7-a6e8-df269bc5cb01", + "item_outfit_type": 0, + "mask_guid": "" + }, + { + "swatch_name": "Blazer_CandyCane_Swatch", + "swatch_guid": "_ycDQ6RcyEGAntVlSDpPeA", + "color": "CandyCane", + "item_name": "(Blazer)", + "item_guid": "de0ac50d-2adb-4114-bd2e-68953b13d706", + "item_outfit_type": 101, + "mask_guid": "be2b9293-1d3c-4b1c-b4c5-fad3ab16cf54" + }, + { + "swatch_name": "Blazer_Shamrock_Swatch", + "swatch_guid": "7WrSbK4Ml0K2z-ig5HIUCw", + "color": "Shamrock", + "item_name": "(Blazer)", + "item_guid": "de0ac50d-2adb-4114-bd2e-68953b13d706", + "item_outfit_type": 101, + "mask_guid": "be2b9293-1d3c-4b1c-b4c5-fad3ab16cf54" + }, + { + "swatch_name": "Raincoat_Shirt_Blue_Swatch", + "swatch_guid": "GH9q8_0O9EqjGdz7lGWD2g", + "color": "Blue", + "item_name": "(Shirt_Raincoat)", + "item_guid": "9e6397dd-b7b0-4d32-8c86-1910106a8478", + "item_outfit_type": 101, + "mask_guid": "T7zwcIIc8Ee3ud8a2JCuwQ" + }, + { + "swatch_name": "Boxer_Green_Swatch", + "swatch_guid": "rzUd1IMTKUmW0HeMPSIOBQ", + "color": "Green", + "item_name": "(Shirt_Boxer)", + "item_guid": "ff15e25d-d467-47a8-9ecc-e415d5acb90c", + "item_outfit_type": 101, + "mask_guid": "" + }, + { + "swatch_name": "Boxer_Red_Swatch", + "swatch_guid": "8c7b09f2-6213-4ef9-87ab-a2df72d07a22", + "color": "Red", + "item_name": "(Shirt_Boxer)", + "item_guid": "ff15e25d-d467-47a8-9ecc-e415d5acb90c", + "item_outfit_type": 101, + "mask_guid": "" + }, + { + "swatch_name": "Headphones_Red_Swatch", + "swatch_guid": "viL4CPch2ECwG-RNaWcyZw", + "color": "Red", + "item_name": "(Hat_Headphones)", + "item_guid": "UcPo9SsF3EGTCBB2J-Efdw", + "item_outfit_type": 0, + "mask_guid": "" + }, + { + "swatch_name": "Headphones_EmissiveBlack_Swatch", + "swatch_guid": "Uz2hrptCa0erjqyPoiZxew", + "color": "EmissiveBlack", + "item_name": "(Hat_Headphones)", + "item_guid": "UcPo9SsF3EGTCBB2J-Efdw", + "item_outfit_type": 0, + "mask_guid": "" + }, + { + "swatch_name": "Headphones_EmissiveRed_Swatch", + "swatch_guid": "I1J-xY4YNU2dB_od4P_7Dg", + "color": "EmissiveRed", + "item_name": "(Hat_Headphones)", + "item_guid": "UcPo9SsF3EGTCBB2J-Efdw", + "item_outfit_type": 0, + "mask_guid": "" + }, + { + "swatch_name": "Headphones_TwoRed_Swatch", + "swatch_guid": "hf0gkFVS8ESvceRsC3LAXQ", + "color": "TwoRed", + "item_name": "(Hat_Headphones)", + "item_guid": "UcPo9SsF3EGTCBB2J-Efdw", + "item_outfit_type": 0, + "mask_guid": "" + }, + { + "swatch_name": "Headphones_EmissiveBlue_Swatch", + "swatch_guid": "2sf-FU4WckGBDWUkVz4z1g", + "color": "EmissiveBlue", + "item_name": "(Hat_Headphones)", + "item_guid": "UcPo9SsF3EGTCBB2J-Efdw", + "item_outfit_type": 0, + "mask_guid": "" + }, + { + "swatch_name": "Headphones_Purple_Swatch", + "swatch_guid": "a5nUvJZgTEGBUvdHx0Y6Ng", + "color": "Purple", + "item_name": "(Hat_Headphones)", + "item_guid": "UcPo9SsF3EGTCBB2J-Efdw", + "item_outfit_type": 0, + "mask_guid": "" + }, + { + "swatch_name": "Headphones_EmissiveOrange_Swatch", + "swatch_guid": "d9mwowRelE2lDKEA0UQMZQ", + "color": "EmissiveOrange", + "item_name": "(Hat_Headphones)", + "item_guid": "UcPo9SsF3EGTCBB2J-Efdw", + "item_outfit_type": 0, + "mask_guid": "" + }, + { + "swatch_name": "Headphones_Orange_Swatch", + "swatch_guid": "JWBK5v78Y0uRVEXlD-Qk6Q", + "color": "Orange", + "item_name": "(Hat_Headphones)", + "item_guid": "UcPo9SsF3EGTCBB2J-Efdw", + "item_outfit_type": 0, + "mask_guid": "" + }, + { + "swatch_name": "Headphones_Blue_Swatch", + "swatch_guid": "8ANbOhNnO0yLQOCIBsDwfg", + "color": "Blue", + "item_name": "(Hat_Headphones)", + "item_guid": "UcPo9SsF3EGTCBB2J-Efdw", + "item_outfit_type": 0, + "mask_guid": "" + }, + { + "swatch_name": "Headphones_TwoBlue_Swatch", + "swatch_guid": "4scG_UBlzE-5DWRhSrtm6g", + "color": "TwoBlue", + "item_name": "(Hat_Headphones)", + "item_guid": "UcPo9SsF3EGTCBB2J-Efdw", + "item_outfit_type": 0, + "mask_guid": "" + }, + { + "swatch_name": "Headphones_TwoOrange_Swatch", + "swatch_guid": "nX7krD8e2km_ZlOLWcoxdQ", + "color": "TwoOrange", + "item_name": "(Hat_Headphones)", + "item_guid": "UcPo9SsF3EGTCBB2J-Efdw", + "item_outfit_type": 0, + "mask_guid": "" + }, + { + "swatch_name": "Headphones_TwoPurple_Swatch", + "swatch_guid": "OqsbZ8Xls0i8uA6ha7H3qQ", + "color": "TwoPurple", + "item_name": "(Hat_Headphones)", + "item_guid": "UcPo9SsF3EGTCBB2J-Efdw", + "item_outfit_type": 0, + "mask_guid": "" + }, + { + "swatch_name": "Headphones_EmissivePurple_Swatch", + "swatch_guid": "3PfP661Fg0CruzRxis9EiA", + "color": "EmissivePurple", + "item_name": "(Hat_Headphones)", + "item_guid": "UcPo9SsF3EGTCBB2J-Efdw", + "item_outfit_type": 0, + "mask_guid": "" + }, + { + "swatch_name": "Headphones_TwoBlack_Swatch", + "swatch_guid": "ns5tIRishUOp-nYqLedcwA", + "color": "TwoBlack", + "item_name": "(Hat_Headphones)", + "item_guid": "UcPo9SsF3EGTCBB2J-Efdw", + "item_outfit_type": 0, + "mask_guid": "" + }, + { + "swatch_name": "JackfrostHair_Hat_Lilac_Swatch", + "swatch_guid": "KwhdQ1deNE6Y6jzO9vaAnQ", + "color": "Lilac", + "item_name": "(Hat_JackfrostHair)", + "item_guid": "e6216087-fb37-46f1-bda4-a88c4640f339", + "item_outfit_type": 0, + "mask_guid": "" + }, + { + "swatch_name": "Baseball_Red_Swatch", + "swatch_guid": "de0a5a3a-8aff-4799-8269-9efa3aeb383a", + "color": "Red", + "item_name": "(Hat_Baseball)", + "item_guid": "14ef6b00-debf-4a85-9755-b4d37df496d3", + "item_outfit_type": 0, + "mask_guid": "" + }, + { + "swatch_name": "Baseball_Green_Swatch", + "swatch_guid": "e5de2e45-207c-46af-9a5e-eb01a0c621fe", + "color": "Green", + "item_name": "(Hat_Baseball)", + "item_guid": "14ef6b00-debf-4a85-9755-b4d37df496d3", + "item_outfit_type": 0, + "mask_guid": "" + }, + { + "swatch_name": "ButtonTop_Orange_Swatch", + "swatch_guid": "e0397982-c2c2-4733-9a40-46e18675b5af", + "color": "Orange", + "item_name": "(ButtonTop)", + "item_guid": "9c8fc7f0-8f99-4aad-a34f-8d979f6ae352", + "item_outfit_type": 101, + "mask_guid": "" + }, + { + "swatch_name": "ButtonTop_Tan_Swatch", + "swatch_guid": "49f5864f-9d40-497c-88c8-e87f64d41d74", + "color": "Tan", + "item_name": "(ButtonTop)", + "item_guid": "9c8fc7f0-8f99-4aad-a34f-8d979f6ae352", + "item_outfit_type": 101, + "mask_guid": "" + }, + { + "swatch_name": "MagicalGirl_Blue_Swatch", + "swatch_guid": "VQjkyb5aoEKHKo_eZsq9ug", + "color": "Blue", + "item_name": "(Wrist_MagicalGirl)", + "item_guid": "eS_or4AnMka9M_bD7YN28g", + "item_outfit_type": 200, + "mask_guid": "" + }, + { + "swatch_name": "MagicalGirl_Red_Swatch", + "swatch_guid": "FE7_gQNBt0CXzMN6di5HWA", + "color": "Red", + "item_name": "(Wrist_MagicalGirl)", + "item_guid": "eS_or4AnMka9M_bD7YN28g", + "item_outfit_type": 200, + "mask_guid": "" + }, + { + "swatch_name": "MagicalGirl_Green_Swatch", + "swatch_guid": "kTP3xKhzBkuQF3SqvmYFKA", + "color": "Green", + "item_name": "(Wrist_MagicalGirl)", + "item_guid": "eS_or4AnMka9M_bD7YN28g", + "item_outfit_type": 200, + "mask_guid": "" + }, + { + "swatch_name": "MagicalGirl_Orange_Swatch", + "swatch_guid": "hYu9itGe1Ee5Neanhj9VtQ", + "color": "Orange", + "item_name": "(Wrist_MagicalGirl)", + "item_guid": "eS_or4AnMka9M_bD7YN28g", + "item_outfit_type": 200, + "mask_guid": "" + }, + { + "swatch_name": "Wizard_SRank_Swatch", + "swatch_guid": "RKmRHk2N_U2gd91nybo6vg", + "color": "SRank", + "item_name": "(Hat_Wizard)", + "item_guid": "01689baf-405e-4b38-9f09-b624e03865b8", + "item_outfit_type": 0, + "mask_guid": "" + }, + { + "swatch_name": "Wizard_Purple_Swatch", + "swatch_guid": "d3KmqVuMQkCYK6sVTMOTOA", + "color": "Purple", + "item_name": "(Hat_Wizard)", + "item_guid": "01689baf-405e-4b38-9f09-b624e03865b8", + "item_outfit_type": 0, + "mask_guid": "" + }, + { + "swatch_name": "Wizard_White_Swatch", + "swatch_guid": "d3a9b576-5433-4305-bd96-b08820c09212", + "color": "White", + "item_name": "(Hat_Wizard)", + "item_guid": "01689baf-405e-4b38-9f09-b624e03865b8", + "item_outfit_type": 0, + "mask_guid": "" + }, + { + "swatch_name": "Wizard_Green_Swatch", + "swatch_guid": "357eb160-360b-4a63-840d-65d11cc8ffc2", + "color": "Green", + "item_name": "(Hat_Wizard)", + "item_guid": "01689baf-405e-4b38-9f09-b624e03865b8", + "item_outfit_type": 0, + "mask_guid": "" + }, + { + "swatch_name": "Wizard_Black_Swatch", + "swatch_guid": "7afe1bb0-5ca9-4263-8f85-49d3a18353c7", + "color": "Black", + "item_name": "(Hat_Wizard)", + "item_guid": "01689baf-405e-4b38-9f09-b624e03865b8", + "item_outfit_type": 0, + "mask_guid": "" + }, + { + "swatch_name": "Wizard_Red_Swatch", + "swatch_guid": "a38254c0-00e3-429b-9d5d-b9e6a9812e69", + "color": "Red", + "item_name": "(Hat_Wizard)", + "item_guid": "01689baf-405e-4b38-9f09-b624e03865b8", + "item_outfit_type": 0, + "mask_guid": "" + }, + { + "swatch_name": "Punk_Pink_Swatch", + "swatch_guid": "4169bfed-8403-4590-a29d-4941275ac0b6", + "color": "Pink", + "item_name": "(Wrist_Punk)", + "item_guid": "28374ebc-4050-4ea0-b3b6-41ebe75cedf7", + "item_outfit_type": 200, + "mask_guid": "" + }, + { + "swatch_name": "Punk_WristPink_Swatch", + "swatch_guid": "f3b2330a-b981-4c7e-902f-1f57382f2388", + "color": "WristPink", + "item_name": "(Wrist_Punk)", + "item_guid": "28374ebc-4050-4ea0-b3b6-41ebe75cedf7", + "item_outfit_type": 200, + "mask_guid": "" + }, + { + "swatch_name": "Punk_WristRed_Swatch", + "swatch_guid": "baffdefe-de21-4551-9330-b50873965bdd", + "color": "WristRed", + "item_name": "(Wrist_Punk)", + "item_guid": "28374ebc-4050-4ea0-b3b6-41ebe75cedf7", + "item_outfit_type": 200, + "mask_guid": "" + }, + { + "swatch_name": "Punk_Green_Swatch", + "swatch_guid": "0724b166-9ace-46ff-a191-b13f135b00a8", + "color": "Green", + "item_name": "(Wrist_Punk)", + "item_guid": "28374ebc-4050-4ea0-b3b6-41ebe75cedf7", + "item_outfit_type": 200, + "mask_guid": "" + }, + { + "swatch_name": "Punk_WristGreen_Swatch", + "swatch_guid": "a6796264-9c7e-4605-830c-06db8d0dc780", + "color": "WristGreen", + "item_name": "(Wrist_Punk)", + "item_guid": "28374ebc-4050-4ea0-b3b6-41ebe75cedf7", + "item_outfit_type": 200, + "mask_guid": "" + }, + { + "swatch_name": "Hat_GraduationCap_Green_Swatch", + "swatch_guid": "FaWMLkJJgEKFIO493fzGxg", + "color": "Green", + "item_name": "(Hat_GraduationCap)", + "item_guid": "KPmC_wyX_ECe7fdKwoLMGg", + "item_outfit_type": 0, + "mask_guid": "yzL0M_N5Q0eAa8IxACHPAQ" + }, + { + "swatch_name": "EquestrianJacket_Maroon_Swatch", + "swatch_guid": "d6823e01-69f0-4f85-b94a-74894356a2cf", + "color": "Maroon", + "item_name": "(EquestrianJacket)", + "item_guid": "896c2491-2f96-4986-9cbd-b3b31ef5d8c5", + "item_outfit_type": 101, + "mask_guid": "" + }, + { + "swatch_name": "EquestrianJacket_Gray_Swatch", + "swatch_guid": "4828b50c-95b6-466a-bb25-514891d78202", + "color": "Gray", + "item_name": "(EquestrianJacket)", + "item_guid": "896c2491-2f96-4986-9cbd-b3b31ef5d8c5", + "item_outfit_type": 101, + "mask_guid": "" + }, + { + "swatch_name": "Shirt_VampireHunter_White_Swatch", + "swatch_guid": "9QG6qp5FBU-u0-JK-yMMqA", + "color": "White", + "item_name": "(Shirt_VampireHunter)", + "item_guid": "JWfs9IiQs0Weu8MmyGpqLQ", + "item_outfit_type": 101, + "mask_guid": "x10FeRBsQU2DIIpG8XIlwg" + }, + { + "swatch_name": "Shirt_VampireHunter_Midnight_Swatch", + "swatch_guid": "CavRDqIlmUeTZBtCLn1hZQ", + "color": "Midnight", + "item_name": "(Shirt_VampireHunter)", + "item_guid": "JWfs9IiQs0Weu8MmyGpqLQ", + "item_outfit_type": 101, + "mask_guid": "x10FeRBsQU2DIIpG8XIlwg" + }, + { + "swatch_name": "Shirt_VampireHunter_Leather_Swatch", + "swatch_guid": "Z8Bfk0MTb0evZehvvgzuBA", + "color": "Leather", + "item_name": "(Shirt_VampireHunter)", + "item_guid": "JWfs9IiQs0Weu8MmyGpqLQ", + "item_outfit_type": 101, + "mask_guid": "x10FeRBsQU2DIIpG8XIlwg" + }, + { + "swatch_name": "Scallywag_Bracelet_SRank_Swatch", + "swatch_guid": "X3MkhL55Rk-0UK6h4dw0Eg", + "color": "SRank", + "item_name": "(Wrist_Scallywag_Bracelet)", + "item_guid": "b9e0f03b-2609-4911-82e9-1e324dbbe8be", + "item_outfit_type": 200, + "mask_guid": "" + }, + { + "swatch_name": "Scallywag_Bracelet_Red_Swatch", + "swatch_guid": "62bd5dda-08da-4528-83c1-f0ca3812dc6e", + "color": "Red", + "item_name": "(Wrist_Scallywag_Bracelet)", + "item_guid": "b9e0f03b-2609-4911-82e9-1e324dbbe8be", + "item_outfit_type": 200, + "mask_guid": "" + }, + { + "swatch_name": "Scallywag_Bracelet_Blue_Swatch", + "swatch_guid": "cdca593a-8840-4140-9145-fe962732040c", + "color": "Blue", + "item_name": "(Wrist_Scallywag_Bracelet)", + "item_guid": "b9e0f03b-2609-4911-82e9-1e324dbbe8be", + "item_outfit_type": 200, + "mask_guid": "" + }, + { + "swatch_name": "PurpleJacket_Red_Swatch", + "swatch_guid": "657f28d5-672a-4f27-86c6-3cb00f25bb84", + "color": "Red", + "item_name": "(PurpleJacket)", + "item_guid": "745c5110-104e-4b37-aabf-7c690fd54811", + "item_outfit_type": 101, + "mask_guid": "3ba69a39-a4e7-4505-bae7-8db31360ec1c" + }, + { + "swatch_name": "PurpleJacket_Blue_Swatch", + "swatch_guid": "b42d903b-b393-4f49-af5e-38d0fb6c42b0", + "color": "Blue", + "item_name": "(PurpleJacket)", + "item_guid": "745c5110-104e-4b37-aabf-7c690fd54811", + "item_outfit_type": 101, + "mask_guid": "3ba69a39-a4e7-4505-bae7-8db31360ec1c" + }, + { + "swatch_name": "PurpleJacket_Black_Swatch", + "swatch_guid": "8cab8cc6-9a2d-434c-8b72-e66edbdd223c", + "color": "Black", + "item_name": "(PurpleJacket)", + "item_guid": "745c5110-104e-4b37-aabf-7c690fd54811", + "item_outfit_type": 101, + "mask_guid": "3ba69a39-a4e7-4505-bae7-8db31360ec1c" + }, + { + "swatch_name": "FighterPilot_Orange_Swatch", + "swatch_guid": "cea53c7f-24a9-4e3b-80b9-fbf95738f973", + "color": "Orange", + "item_name": "(Wrist_FighterPilot)", + "item_guid": "5800d2b5-75b8-442f-8b10-63bd3d73a1b8", + "item_outfit_type": 200, + "mask_guid": "435eba0d-d8f9-43cc-91aa-2959d63c6fa7" + }, + { + "swatch_name": "FighterPilot_Black_Swatch", + "swatch_guid": "8c74ee27-3d56-401b-8a8c-7868a80770e8", + "color": "Black", + "item_name": "(Wrist_FighterPilot)", + "item_guid": "5800d2b5-75b8-442f-8b10-63bd3d73a1b8", + "item_outfit_type": 200, + "mask_guid": "435eba0d-d8f9-43cc-91aa-2959d63c6fa7" + }, + { + "swatch_name": "FighterPilot_Blue_Swatch", + "swatch_guid": "e37b07d1-6b9d-49b9-94ca-1c728e228cfe", + "color": "Blue", + "item_name": "(Wrist_FighterPilot)", + "item_guid": "5800d2b5-75b8-442f-8b10-63bd3d73a1b8", + "item_outfit_type": 200, + "mask_guid": "435eba0d-d8f9-43cc-91aa-2959d63c6fa7" + }, + { + "swatch_name": "Neck_Grillmaster_Denim_Swatch", + "swatch_guid": "eV3Nv6SWS0yl3f-fgjPb-w", + "color": "Denim", + "item_name": "(Neck_Grillmaster)", + "item_guid": "tTKv7eRODka2KC_hsY2O3Q", + "item_outfit_type": 100, + "mask_guid": "t-ZEWJCgFEyGP2PjNblQMA" + }, + { + "swatch_name": "Neck_Grillmaster_White_Swatch", + "swatch_guid": "Lxx3q6JRnUW_cPG_kGG4ag", + "color": "White", + "item_name": "(Neck_Grillmaster)", + "item_guid": "tTKv7eRODka2KC_hsY2O3Q", + "item_outfit_type": 100, + "mask_guid": "t-ZEWJCgFEyGP2PjNblQMA" + }, + { + "swatch_name": "BabydollDress_Green_Swatch", + "swatch_guid": "c9589974-c261-485e-b571-cb2f34fb178f", + "color": "Green", + "item_name": "(BabydollDress)", + "item_guid": "da55bfc3-47b7-46f6-b548-7fb2c2a5e0bf", + "item_outfit_type": 101, + "mask_guid": "" + }, + { + "swatch_name": "BabydollDress_Black_Swatch", + "swatch_guid": "e13cf33d-7588-4e2e-955e-f0a29d6380b7", + "color": "Black", + "item_name": "(BabydollDress)", + "item_guid": "da55bfc3-47b7-46f6-b548-7fb2c2a5e0bf", + "item_outfit_type": 101, + "mask_guid": "" + }, + { + "swatch_name": "BabydollDress_Pink_Swatch", + "swatch_guid": "da6d0ced-3d43-4878-a868-925f2ed6fd5b", + "color": "Pink", + "item_name": "(BabydollDress)", + "item_guid": "da55bfc3-47b7-46f6-b548-7fb2c2a5e0bf", + "item_outfit_type": 101, + "mask_guid": "" + }, + { + "swatch_name": "WitchHunter_Purple_Swatch", + "swatch_guid": "71e5a0fe-fc8d-420b-af3b-e74160e0d1f3", + "color": "Purple", + "item_name": "(Shirt_WitchHunter)", + "item_guid": "418db0e0-c7af-40c2-9cac-d461ce41e210", + "item_outfit_type": 101, + "mask_guid": "" + }, + { + "swatch_name": "WitchHunter_Green_Swatch", + "swatch_guid": "64545dcc-a8eb-4c69-a539-78ee73a2d327", + "color": "Green", + "item_name": "(Shirt_WitchHunter)", + "item_guid": "418db0e0-c7af-40c2-9cac-d461ce41e210", + "item_outfit_type": 101, + "mask_guid": "" + }, + { + "swatch_name": "WitchHunter_Blue_Swatch", + "swatch_guid": "aBQ3R9boek-mYeSYSNvDbQ", + "color": "Blue", + "item_name": "(Shirt_WitchHunter)", + "item_guid": "418db0e0-c7af-40c2-9cac-d461ce41e210", + "item_outfit_type": 101, + "mask_guid": "" + }, + { + "swatch_name": "WitchHunter_SRank_Swatch", + "swatch_guid": "Tbrc2MlSlE6mKua8qSoluQ", + "color": "SRank", + "item_name": "(Shirt_WitchHunter)", + "item_guid": "418db0e0-c7af-40c2-9cac-d461ce41e210", + "item_outfit_type": 101, + "mask_guid": "" + }, + { + "swatch_name": "WitchHunter_White_Swatch", + "swatch_guid": "e02838f8-4029-455a-b35c-dd40f972b05b", + "color": "White", + "item_name": "(Shirt_WitchHunter)", + "item_guid": "418db0e0-c7af-40c2-9cac-d461ce41e210", + "item_outfit_type": 101, + "mask_guid": "" + }, + { + "swatch_name": "WitchHunter_Red_Swatch", + "swatch_guid": "909ed249-ce09-4304-8b02-56794fa7567d", + "color": "Red", + "item_name": "(Shirt_WitchHunter)", + "item_guid": "418db0e0-c7af-40c2-9cac-d461ce41e210", + "item_outfit_type": 101, + "mask_guid": "" + }, + { + "swatch_name": "Smuggler_Green_Swatch", + "swatch_guid": "076c96ae-9f9b-45bf-9dc7-ce73d5ddf422", + "color": "Green", + "item_name": "(Shirt_Smuggler)", + "item_guid": "25d4c652-1730-4925-925e-31a290a2272a", + "item_outfit_type": 101, + "mask_guid": "" + }, + { + "swatch_name": "Smuggler_SRank_Swatch", + "swatch_guid": "bzYdPb8GWUS-CgT4wrziIA", + "color": "SRank", + "item_name": "(Shirt_Smuggler)", + "item_guid": "25d4c652-1730-4925-925e-31a290a2272a", + "item_outfit_type": 101, + "mask_guid": "" + }, + { + "swatch_name": "Smuggler_Grey_Swatch", + "swatch_guid": "90869e86-cbc5-4024-9d08-fcdc41c64eb5", + "color": "Grey", + "item_name": "(Shirt_Smuggler)", + "item_guid": "25d4c652-1730-4925-925e-31a290a2272a", + "item_outfit_type": 101, + "mask_guid": "" + }, + { + "swatch_name": "Smuggler_Dark_Swatch", + "swatch_guid": "e821aa74-2d47-4388-bcfb-1893b8a83a52", + "color": "Dark", + "item_name": "(Shirt_Smuggler)", + "item_guid": "25d4c652-1730-4925-925e-31a290a2272a", + "item_outfit_type": 101, + "mask_guid": "" + }, + { + "swatch_name": "Smuggler_Cherry_Swatch", + "swatch_guid": "fed760b2-15e2-46b2-b1e3-cc3c8d780d5d", + "color": "Cherry", + "item_name": "(Shirt_Smuggler)", + "item_guid": "25d4c652-1730-4925-925e-31a290a2272a", + "item_outfit_type": 101, + "mask_guid": "" + }, + { + "swatch_name": "Smuggler_Yellow_Swatch", + "swatch_guid": "002b07bb-4256-44ce-8ca4-25510e70b6b5", + "color": "Yellow", + "item_name": "(Shirt_Smuggler)", + "item_guid": "25d4c652-1730-4925-925e-31a290a2272a", + "item_outfit_type": 101, + "mask_guid": "" + }, + { + "swatch_name": "SodaJerk_Green_Swatch", + "swatch_guid": "oKxmH-1s2EyIDkZoOQSQQw", + "color": "Green", + "item_name": "(Shirt_SodaJerk)", + "item_guid": "iai6P7dDTUq8S4hqQmY1YA", + "item_outfit_type": 101, + "mask_guid": "" + }, + { + "swatch_name": "SodaJerk_Mustard_Swatch", + "swatch_guid": "MxnAq_XOw0uTzQ8IIxqb_Q", + "color": "Mustard", + "item_name": "(Shirt_SodaJerk)", + "item_guid": "iai6P7dDTUq8S4hqQmY1YA", + "item_outfit_type": 101, + "mask_guid": "" + }, + { + "swatch_name": "SodaJerk_Blue_Swatch", + "swatch_guid": "XFCpIdeDXUqeYCIgh4fGtg", + "color": "Blue", + "item_name": "(Shirt_SodaJerk)", + "item_guid": "iai6P7dDTUq8S4hqQmY1YA", + "item_outfit_type": 101, + "mask_guid": "" + }, + { + "swatch_name": "Hat_TutorialCap_2_Swatch", + "swatch_guid": "gDH1WmW5rk2RdY4wJYYZow", + "color": "2", + "item_name": "(Hat_TutorialCap)", + "item_guid": "M9weaekvIUimiJSVJqZl-w", + "item_outfit_type": 0, + "mask_guid": "GV8z2dlqGEqIAYaGrF1ZYQ" + }, + { + "swatch_name": "Hat_TutorialCap_3_Swatch", + "swatch_guid": "wHJnbDOo8ESmUbQDr0k31w", + "color": "3", + "item_name": "(Hat_TutorialCap)", + "item_guid": "M9weaekvIUimiJSVJqZl-w", + "item_outfit_type": 0, + "mask_guid": "GV8z2dlqGEqIAYaGrF1ZYQ" + }, + { + "swatch_name": "BaseballHelmet_Red_Swatch", + "swatch_guid": "a1c51d1e-1d5f-4f8a-bc0f-6a20eddfe58a", + "color": "Red", + "item_name": "(Hat_BaseballHelmet)", + "item_guid": "293e7ad3-01c9-43af-96e3-06c34145ff12", + "item_outfit_type": 0, + "mask_guid": "" + }, + { + "swatch_name": "BaseballHelmet_Green_Swatch", + "swatch_guid": "9967ce54-c2b7-437a-946e-11f8ee6d6fdd", + "color": "Green", + "item_name": "(Hat_BaseballHelmet)", + "item_guid": "293e7ad3-01c9-43af-96e3-06c34145ff12", + "item_outfit_type": 0, + "mask_guid": "" + }, + { + "swatch_name": "TruckerOveralls_Mustard_Swatch", + "swatch_guid": "nKauYAyWhkm2AMHFyC-fLg", + "color": "Mustard", + "item_name": "(Shirt_TruckerOveralls)", + "item_guid": "b080e2f4-c699-4cca-bb9e-7cfc4bafcbfc", + "item_outfit_type": 101, + "mask_guid": "" + }, + { + "swatch_name": "TruckerOveralls_Red_Swatch", + "swatch_guid": "iokdlBfjuEuAwqIMOQwxdA", + "color": "Red", + "item_name": "(Shirt_TruckerOveralls)", + "item_guid": "b080e2f4-c699-4cca-bb9e-7cfc4bafcbfc", + "item_outfit_type": 101, + "mask_guid": "" + }, + { + "swatch_name": "TruckerOveralls_Green_Swatch", + "swatch_guid": "EiGgN7uVukqoRE7UJcMLTw", + "color": "Green", + "item_name": "(Shirt_TruckerOveralls)", + "item_guid": "b080e2f4-c699-4cca-bb9e-7cfc4bafcbfc", + "item_outfit_type": 101, + "mask_guid": "" + }, + { + "swatch_name": "PirateCaptain_Black_Swatch", + "swatch_guid": "d81d0b62-e052-46c2-9252-4bc5fb219f53", + "color": "Black", + "item_name": "(Neck_PirateCaptain)", + "item_guid": "38b38e24-d636-41c6-b80e-a285ae3106dc", + "item_outfit_type": 102, + "mask_guid": "" + }, + { + "swatch_name": "PirateCaptain_SRank_Swatch", + "swatch_guid": "xGmnUGF-Ck-jP43r8tVVjg", + "color": "SRank", + "item_name": "(Neck_PirateCaptain)", + "item_guid": "38b38e24-d636-41c6-b80e-a285ae3106dc", + "item_outfit_type": 102, + "mask_guid": "" + }, + { + "swatch_name": "PirateCaptain_Red_Swatch", + "swatch_guid": "556141f7-6fbc-4065-9faa-8d8240a93d7f", + "color": "Red", + "item_name": "(Neck_PirateCaptain)", + "item_guid": "38b38e24-d636-41c6-b80e-a285ae3106dc", + "item_outfit_type": 102, + "mask_guid": "" + }, + { + "swatch_name": "Bowtie_SodaJerk_Swatch", + "swatch_guid": "e4IDo-fuH0C9YByzcX5k-A", + "color": "SodaJerk", + "item_name": "(Bowtie)", + "item_guid": "83be5ba4-525a-4781-a5f6-839c22e4d1d3", + "item_outfit_type": 100, + "mask_guid": "" + }, + { + "swatch_name": "FlapperGlove_Black_Swatch", + "swatch_guid": "30c61e63-5e09-4a6f-8915-1e1fe4d55cf0", + "color": "Black", + "item_name": "(Wrist_FlapperGlove)", + "item_guid": "473c672d-2b79-48a2-a49d-fd68160c5bde", + "item_outfit_type": 200, + "mask_guid": "" + }, + { + "swatch_name": "FlapperGlove_Gold_Swatch", + "swatch_guid": "2cf2b644-3fa6-449c-9fe8-d0ab917f3106", + "color": "Gold", + "item_name": "(Wrist_FlapperGlove)", + "item_guid": "473c672d-2b79-48a2-a49d-fd68160c5bde", + "item_outfit_type": 200, + "mask_guid": "" + }, + { + "swatch_name": "BusinessTie_Valentine_Swatch", + "swatch_guid": "31jelNWFyk6FlWt4HyPAlw", + "color": "Valentine", + "item_name": "(BusinessTie)", + "item_guid": "c6c08eb5-381a-4193-9722-80da95d62abe", + "item_outfit_type": 100, + "mask_guid": "" + }, + { + "swatch_name": "BusinessTie_CandyCane_Swatch", + "swatch_guid": "W4a144so90eRzCojUnDowA", + "color": "CandyCane", + "item_name": "(BusinessTie)", + "item_guid": "c6c08eb5-381a-4193-9722-80da95d62abe", + "item_outfit_type": 100, + "mask_guid": "" + }, + { + "swatch_name": "Vest_Red_Swatch", + "swatch_guid": "ffHKr5EcIU24PbAtWSnalw", + "color": "Red", + "item_name": "(Shirt_Vest)", + "item_guid": "3d9d52cb-6a3d-436b-90e7-e76f238329ee", + "item_outfit_type": 101, + "mask_guid": "" + }, + { + "swatch_name": "HeadBackBow_Blue_Swatch", + "swatch_guid": "GmS0wKwzVEqdf3NoTMHG5A", + "color": "Blue", + "item_name": "(Hat_HeadBackBow)", + "item_guid": "JTrfBXhIT02o2-ssP94zew", + "item_outfit_type": 0, + "mask_guid": "" + }, + { + "swatch_name": "HeadBackBow_Red_Swatch", + "swatch_guid": "ZNKGkou_gU-1cxBFy7pDKw", + "color": "Red", + "item_name": "(Hat_HeadBackBow)", + "item_guid": "JTrfBXhIT02o2-ssP94zew", + "item_outfit_type": 0, + "mask_guid": "" + }, + { + "swatch_name": "HeadBackBow_White_Swatch", + "swatch_guid": "-Iqmonry4EKoqAULCKzRPA", + "color": "White", + "item_name": "(Hat_HeadBackBow)", + "item_guid": "JTrfBXhIT02o2-ssP94zew", + "item_outfit_type": 0, + "mask_guid": "" + }, + { + "swatch_name": "HeadBackBow_SolidBlack_Swatch", + "swatch_guid": "f_c50saStkaGJuhLjJ9lfA", + "color": "SolidBlack", + "item_name": "(Hat_HeadBackBow)", + "item_guid": "JTrfBXhIT02o2-ssP94zew", + "item_outfit_type": 0, + "mask_guid": "" + }, + { + "swatch_name": "HeadBackBow_Hearts_Swatch", + "swatch_guid": "D-SngK7hhkOpoZ2aMWv9EA", + "color": "Hearts", + "item_name": "(Hat_HeadBackBow)", + "item_guid": "JTrfBXhIT02o2-ssP94zew", + "item_outfit_type": 0, + "mask_guid": "" + }, + { + "swatch_name": "HeadBackBow_SolidBlue_Swatch", + "swatch_guid": "8iZO162KNUiMC8l_z3OC_A", + "color": "SolidBlue", + "item_name": "(Hat_HeadBackBow)", + "item_guid": "JTrfBXhIT02o2-ssP94zew", + "item_outfit_type": 0, + "mask_guid": "" + }, + { + "swatch_name": "HeadBackBow_SolidPink_Swatch", + "swatch_guid": "FUP4iRnoc0ikNmjrFD06lg", + "color": "SolidPink", + "item_name": "(Hat_HeadBackBow)", + "item_guid": "JTrfBXhIT02o2-ssP94zew", + "item_outfit_type": 0, + "mask_guid": "" + }, + { + "swatch_name": "FurCoat_Black_Swatch", + "swatch_guid": "43501b0c-eaaa-4ab3-9d03-2ca61d2e8fc9", + "color": "Black", + "item_name": "(FurCoat)", + "item_guid": "77c37481-a185-47ff-af55-65f9fdfcf9e5", + "item_outfit_type": 101, + "mask_guid": "2c0d912d-c184-4eab-8ee4-73b90ba7b0a6" + }, + { + "swatch_name": "FurCoat_White_Swatch", + "swatch_guid": "643b5f2d-0666-4869-b699-03742de34d16", + "color": "White", + "item_name": "(FurCoat)", + "item_guid": "77c37481-a185-47ff-af55-65f9fdfcf9e5", + "item_outfit_type": 101, + "mask_guid": "2c0d912d-c184-4eab-8ee4-73b90ba7b0a6" + }, + { + "swatch_name": "FlapperHeadband_Gold_Swatch", + "swatch_guid": "b6c25a3b-125b-4aab-bed0-e6e5ced2c21c", + "color": "Gold", + "item_name": "(Hat_FlapperHeadband)", + "item_guid": "18feb3c2-049f-4f91-9f4e-6b5fcb4ab70c", + "item_outfit_type": 0, + "mask_guid": "" + }, + { + "swatch_name": "FlapperHeadband_Black_Swatch", + "swatch_guid": "565c17e0-496d-45bd-a2c3-564e118c06cc", + "color": "Black", + "item_name": "(Hat_FlapperHeadband)", + "item_guid": "18feb3c2-049f-4f91-9f4e-6b5fcb4ab70c", + "item_outfit_type": 0, + "mask_guid": "" + }, + { + "swatch_name": "Wrist_VampireHunter_Red_Swatch", + "swatch_guid": "iJW8XGJfU0G12iXarjxhdQ", + "color": "Red", + "item_name": "(Wrist_VampireHunter)", + "item_guid": "_OWVy3z6iU-M3-zbQgSLig", + "item_outfit_type": 200, + "mask_guid": "N5wN1l0tYEau5np-9PoIiA" + }, + { + "swatch_name": "Wrist_VampireHunter_Black_Swatch", + "swatch_guid": "Vxq6v55a_kGPODzwB5MC1g", + "color": "Black", + "item_name": "(Wrist_VampireHunter)", + "item_guid": "_OWVy3z6iU-M3-zbQgSLig", + "item_outfit_type": 200, + "mask_guid": "N5wN1l0tYEau5np-9PoIiA" + }, + { + "swatch_name": "Wrist_VampireHunter_Leather_Swatch", + "swatch_guid": "D0tP58wC30mJ00vNxw8hqA", + "color": "Leather", + "item_name": "(Wrist_VampireHunter)", + "item_guid": "_OWVy3z6iU-M3-zbQgSLig", + "item_outfit_type": 200, + "mask_guid": "N5wN1l0tYEau5np-9PoIiA" + }, + { + "swatch_name": "PeaCoat_Purple_Swatch", + "swatch_guid": "uiBklotTUEqRI2F67vyJyA", + "color": "Purple", + "item_name": "(Shirt_Peacoat)", + "item_guid": "ab15ef15-a97c-4ab1-896b-bd4d7e1cd9f5", + "item_outfit_type": 101, + "mask_guid": "" + }, + { + "swatch_name": "PeaCoat_Navy_Swatch", + "swatch_guid": "fvoaMN4M10WS6jiA6XlOJQ", + "color": "Navy", + "item_name": "(Shirt_Peacoat)", + "item_guid": "ab15ef15-a97c-4ab1-896b-bd4d7e1cd9f5", + "item_outfit_type": 101, + "mask_guid": "" + }, + { + "swatch_name": "PeaCoat_Tan_Swatch", + "swatch_guid": "PwsLxCnzOkSW-RGz3tNb1w", + "color": "Tan", + "item_name": "(Shirt_Peacoat)", + "item_guid": "ab15ef15-a97c-4ab1-896b-bd4d7e1cd9f5", + "item_outfit_type": 101, + "mask_guid": "" + }, + { + "swatch_name": "PeaCoat_Red_Swatch", + "swatch_guid": "jXBi28cRV0CEQgKIrpD0_A", + "color": "Red", + "item_name": "(Shirt_Peacoat)", + "item_guid": "ab15ef15-a97c-4ab1-896b-bd4d7e1cd9f5", + "item_outfit_type": 101, + "mask_guid": "" + }, + { + "swatch_name": "Hat_CatEars_Gray_Swatch", + "swatch_guid": "96e42c7a-2881-4299-a2fe-80c0d4d5ca26", + "color": "Gray", + "item_name": "(Hat_CatEars)", + "item_guid": "3d5184e1-0201-4476-bf4b-4eb28190de62", + "item_outfit_type": 0, + "mask_guid": "1e3195ab-f7f6-41f4-a5ec-378d09ecdf69" + }, + { + "swatch_name": "Hat_CatEars_Brown_Swatch", + "swatch_guid": "713781e5-ce47-41c6-a24f-94d42a565e30", + "color": "Brown", + "item_name": "(Hat_CatEars)", + "item_guid": "3d5184e1-0201-4476-bf4b-4eb28190de62", + "item_outfit_type": 0, + "mask_guid": "1e3195ab-f7f6-41f4-a5ec-378d09ecdf69" + }, + { + "swatch_name": "Hat_CatEars_White_Swatch", + "swatch_guid": "c1e430c5-fd8d-4032-9e3f-8d87cc8784da", + "color": "White", + "item_name": "(Hat_CatEars)", + "item_guid": "3d5184e1-0201-4476-bf4b-4eb28190de62", + "item_outfit_type": 0, + "mask_guid": "1e3195ab-f7f6-41f4-a5ec-378d09ecdf69" + }, + { + "swatch_name": "Hat_CatEars_Gold_Swatch", + "swatch_guid": "37f8378b-b98c-477a-b8c9-9340772800b5", + "color": "Gold", + "item_name": "(Hat_CatEars)", + "item_guid": "3d5184e1-0201-4476-bf4b-4eb28190de62", + "item_outfit_type": 0, + "mask_guid": "1e3195ab-f7f6-41f4-a5ec-378d09ecdf69" + }, + { + "swatch_name": "TruckerJacket_White_Swatch", + "swatch_guid": "yA0KhVg19kWqG1UkyxQogQ", + "color": "White", + "item_name": "(Shirt_TruckerJacket)", + "item_guid": "8b065e2a-b106-4f11-b415-4c47ce87993e", + "item_outfit_type": 101, + "mask_guid": "" + }, + { + "swatch_name": "TruckerJacket_Tan_Swatch", + "swatch_guid": "o2WfRyjJy0CKoUg8YIxNjQ", + "color": "Tan", + "item_name": "(Shirt_TruckerJacket)", + "item_guid": "8b065e2a-b106-4f11-b415-4c47ce87993e", + "item_outfit_type": 101, + "mask_guid": "" + }, + { + "swatch_name": "TruckerJacket_RebelRose_Swatch", + "swatch_guid": "4Eck_dNL1UKlIZWsA1YppQ", + "color": "RebelRose", + "item_name": "(Shirt_TruckerJacket)", + "item_guid": "8b065e2a-b106-4f11-b415-4c47ce87993e", + "item_outfit_type": 101, + "mask_guid": "" + }, + { + "swatch_name": "TruckerJacket_Black_Swatch", + "swatch_guid": "P9bBSL0YLEyjN8PxMfgbKQ", + "color": "Black", + "item_name": "(Shirt_TruckerJacket)", + "item_guid": "8b065e2a-b106-4f11-b415-4c47ce87993e", + "item_outfit_type": 101, + "mask_guid": "" + }, + { + "swatch_name": "TruckerJacket_Olive_Swatch", + "swatch_guid": "bgRKH1wfqkWVHcuSnFYnlQ", + "color": "Olive", + "item_name": "(Shirt_TruckerJacket)", + "item_guid": "8b065e2a-b106-4f11-b415-4c47ce87993e", + "item_outfit_type": 101, + "mask_guid": "" + }, + { + "swatch_name": "80sSpandex_White_Swatch", + "swatch_guid": "zu0Jk24WTkan6XWSwGU1Uw", + "color": "White", + "item_name": "(Shirt_80sSpandex)", + "item_guid": "17236d76-e3b6-42f6-9179-6fcf0479436f", + "item_outfit_type": 101, + "mask_guid": "" + }, + { + "swatch_name": "80sSpandex_Red_Swatch", + "swatch_guid": "6t2rCb6WWEW6pJbcjsAFCw", + "color": "Red", + "item_name": "(Shirt_80sSpandex)", + "item_guid": "17236d76-e3b6-42f6-9179-6fcf0479436f", + "item_outfit_type": 101, + "mask_guid": "" + }, + { + "swatch_name": "80sSpandex_Green_Swatch", + "swatch_guid": "3frH6KxZ9kGUYkjeCDpylQ", + "color": "Green", + "item_name": "(Shirt_80sSpandex)", + "item_guid": "17236d76-e3b6-42f6-9179-6fcf0479436f", + "item_outfit_type": 101, + "mask_guid": "" + }, + { + "swatch_name": "80sSpandex_Blue_Swatch", + "swatch_guid": "Lf4FjROSAEqXedFxpXFQfg", + "color": "Blue", + "item_name": "(Shirt_80sSpandex)", + "item_guid": "17236d76-e3b6-42f6-9179-6fcf0479436f", + "item_outfit_type": 101, + "mask_guid": "" + }, + { + "swatch_name": "80sSpandex_Yellow_Swatch", + "swatch_guid": "i1XbjBqTu0CILIuJClWimA", + "color": "Yellow", + "item_name": "(Shirt_80sSpandex)", + "item_guid": "17236d76-e3b6-42f6-9179-6fcf0479436f", + "item_outfit_type": 101, + "mask_guid": "" + }, + { + "swatch_name": "WaitressApron_Red_Swatch", + "swatch_guid": "HdwZhjrqxE6wuf68MnDhDw", + "color": "Red", + "item_name": "(Belt_WaitressApron)", + "item_guid": "204bc384-e6d2-41b8-8ce9-0b26be5d85b7", + "item_outfit_type": 102, + "mask_guid": "" + }, + { + "swatch_name": "WaitressApron_Mustard_Swatch", + "swatch_guid": "b7oobDcDtE-69fhM-5g5AQ", + "color": "Mustard", + "item_name": "(Belt_WaitressApron)", + "item_guid": "204bc384-e6d2-41b8-8ce9-0b26be5d85b7", + "item_outfit_type": 102, + "mask_guid": "" + }, + { + "swatch_name": "WaitressApron_Green_Swatch", + "swatch_guid": "Blev_VFjRUysY_nymGWAVg", + "color": "Green", + "item_name": "(Belt_WaitressApron)", + "item_guid": "204bc384-e6d2-41b8-8ce9-0b26be5d85b7", + "item_outfit_type": 102, + "mask_guid": "" + }, + { + "swatch_name": "Shirt_MartyRecFly_Hipster_Swatch", + "swatch_guid": "kkvNbVRjcEKtChpy3W1bqQ", + "color": "Hipster", + "item_name": "(Shirt_MartyRecFly)", + "item_guid": "74c65554-0d15-409b-b9a8-0b2620a7d7e2", + "item_outfit_type": 101, + "mask_guid": "" + }, + { + "swatch_name": "Hat_SkiBuddy_White_Swatch", + "swatch_guid": "KwCEye0nsESFs-yj4h9sjQ", + "color": "White", + "item_name": "(Hat_SkiBuddy)", + "item_guid": "6QT99hx6DE6t9cKAs61i3w", + "item_outfit_type": 0, + "mask_guid": "c_t9XDbn-Eqml1O-wC5zJQ" + }, + { + "swatch_name": "Wrist_Grillmaster_Blue_Swatch", + "swatch_guid": "90qQe0AR3kOou0MsuoTmPA", + "color": "Blue", + "item_name": "(Wrist_Grillmaster)", + "item_guid": "q7pSsRMNHkqEJmYHuhhG6g", + "item_outfit_type": 200, + "mask_guid": "" + }, + { + "swatch_name": "Wrist_Grillmaster_Black_Swatch", + "swatch_guid": "hZKPr4sg10SRj9DLuEgOrw", + "color": "Black", + "item_name": "(Wrist_Grillmaster)", + "item_guid": "q7pSsRMNHkqEJmYHuhhG6g", + "item_outfit_type": 200, + "mask_guid": "" + }, + { + "swatch_name": "Scientist_Blue_Swatch", + "swatch_guid": "KTidYfH0ckWxJGs5qzUBTQ", + "color": "Blue", + "item_name": "(Hair_Scientist)", + "item_guid": "f17a2f9e-1eab-44e8-b10a-1f53c5d0778c", + "item_outfit_type": 2, + "mask_guid": "" + }, + { + "swatch_name": "RedFlowerDress_Gray_Swatch", + "swatch_guid": "1acb1f56-4509-449f-9c6a-065bdeaa9ee3", + "color": "Gray", + "item_name": "(RedFlowerDress)", + "item_guid": "515dfc36-7f7b-47bc-abee-380d68f41be6", + "item_outfit_type": 101, + "mask_guid": "2d7361d2-31a2-404b-90da-10e89114e1f2" + }, + { + "swatch_name": "RedFlowerDress_Yellow_Swatch", + "swatch_guid": "e98c3f98-7844-4d6e-a21c-05027033c18c", + "color": "Yellow", + "item_name": "(RedFlowerDress)", + "item_guid": "515dfc36-7f7b-47bc-abee-380d68f41be6", + "item_outfit_type": 101, + "mask_guid": "2d7361d2-31a2-404b-90da-10e89114e1f2" + }, + { + "swatch_name": "YellowBlueDress_White_Swatch", + "swatch_guid": "bbfa08e3-8e6b-4e0f-b264-1b398d7cd44a", + "color": "White", + "item_name": "(YellowBlueDress)", + "item_guid": "2296ed0d-df56-4d46-b33a-aae9230a47fc", + "item_outfit_type": 101, + "mask_guid": "" + }, + { + "swatch_name": "YellowBlueDress_Purple_Swatch", + "swatch_guid": "830be2fa-60a5-48cc-931f-34b670eae4bd", + "color": "Purple", + "item_name": "(YellowBlueDress)", + "item_guid": "2296ed0d-df56-4d46-b33a-aae9230a47fc", + "item_outfit_type": 101, + "mask_guid": "" + }, + { + "swatch_name": "TennisSkirt_Yellow_Swatch", + "swatch_guid": "c5deba2a-6e35-4b13-8e94-8ba5457f39df", + "color": "Yellow", + "item_name": "(TennisSkirt)", + "item_guid": "21caa68e-c3fa-474c-af5e-af1e742b7a60", + "item_outfit_type": 101, + "mask_guid": "b75ef67d-00c3-4ac1-9b72-212032460294" + }, + { + "swatch_name": "TennisSkirt_Red_Swatch", + "swatch_guid": "758752bd-db2f-43d2-b580-55b3e1efffd5", + "color": "Red", + "item_name": "(TennisSkirt)", + "item_guid": "21caa68e-c3fa-474c-af5e-af1e742b7a60", + "item_outfit_type": 101, + "mask_guid": "b75ef67d-00c3-4ac1-9b72-212032460294" + }, + { + "swatch_name": "LaserTag_Cyan_Swatch", + "swatch_guid": "08b7c12d-3317-4d27-9b40-34737a709454", + "color": "Cyan", + "item_name": "(Hat_LaserTag)", + "item_guid": "123de433-a005-4994-8a11-d2d84ad66b11", + "item_outfit_type": 0, + "mask_guid": "" + }, + { + "swatch_name": "LaserTag_Green_Swatch", + "swatch_guid": "2Z_FxSPiyUODtlFZWF9DFQ", + "color": "Green", + "item_name": "(Hat_LaserTag)", + "item_guid": "123de433-a005-4994-8a11-d2d84ad66b11", + "item_outfit_type": 0, + "mask_guid": "" + }, + { + "swatch_name": "LaserTag_Orange_Swatch", + "swatch_guid": "jkuw9EhCpEKn-2lKJXWd3Q", + "color": "Orange", + "item_name": "(Hat_LaserTag)", + "item_guid": "123de433-a005-4994-8a11-d2d84ad66b11", + "item_outfit_type": 0, + "mask_guid": "" + }, + { + "swatch_name": "Letterman_Red_Swatch", + "swatch_guid": "VpqHNpPiY0CfRJEjRRM-mw", + "color": "Red", + "item_name": "(Wrist_Letterman)", + "item_guid": "2dFMy4zc7ky_Fd5fzfgInw", + "item_outfit_type": 200, + "mask_guid": "" + }, + { + "swatch_name": "Letterman_Purple_Swatch", + "swatch_guid": "iufpnteVCEiiZu_m-yPovQ", + "color": "Purple", + "item_name": "(Wrist_Letterman)", + "item_guid": "2dFMy4zc7ky_Fd5fzfgInw", + "item_outfit_type": 200, + "mask_guid": "" + }, + { + "swatch_name": "Letterman_Black_Swatch", + "swatch_guid": "Z1F9rjKqCE-Hd2-Ip5g3gA", + "color": "Black", + "item_name": "(Wrist_Letterman)", + "item_guid": "2dFMy4zc7ky_Fd5fzfgInw", + "item_outfit_type": 200, + "mask_guid": "" + }, + { + "swatch_name": "Letterman_Green_Swatch", + "swatch_guid": "apSQiQ1eO0mEzRsbugeu9w", + "color": "Green", + "item_name": "(Wrist_Letterman)", + "item_guid": "2dFMy4zc7ky_Fd5fzfgInw", + "item_outfit_type": 200, + "mask_guid": "" + }, + { + "swatch_name": "TrackSuit_Pink_Swatch", + "swatch_guid": "026ca50e-3a81-43cf-87f5-950687b7bbdd", + "color": "Pink", + "item_name": "(TrackSuit)", + "item_guid": "d766c8d3-76e8-4021-b0a4-24b78fb7eaba", + "item_outfit_type": 101, + "mask_guid": "adae82eb-a4b9-4541-acf2-76cea839a593" + }, + { + "swatch_name": "QuiltedCoat_Orange_Swatch", + "swatch_guid": "-HYrY2A5Tk2UWMi4_hmYbg", + "color": "Orange", + "item_name": "(Shirt_Quiltedcoat)", + "item_guid": "0ab5790f-a537-4306-ba66-5eb87c02ec7d", + "item_outfit_type": 101, + "mask_guid": "" + }, + { + "swatch_name": "QuiltedCoat_Green_Swatch", + "swatch_guid": "QBglgj80zkuvUkk55Y5Feg", + "color": "Green", + "item_name": "(Shirt_Quiltedcoat)", + "item_guid": "0ab5790f-a537-4306-ba66-5eb87c02ec7d", + "item_outfit_type": 101, + "mask_guid": "" + }, + { + "swatch_name": "QuiltedCoat_Plum_Swatch", + "swatch_guid": "IjRMopsYlUqSE6327I9TIQ", + "color": "Plum", + "item_name": "(Shirt_Quiltedcoat)", + "item_guid": "0ab5790f-a537-4306-ba66-5eb87c02ec7d", + "item_outfit_type": 101, + "mask_guid": "" + }, + { + "swatch_name": "Shirt_SkiBuddy_Blue_Swatch", + "swatch_guid": "fXNkMWFMV0aXalK_y_8SHw", + "color": "Blue", + "item_name": "(Shirt_SkiBuddy)", + "item_guid": "6RhwWExXekaYpK0NGHSXGQ", + "item_outfit_type": 101, + "mask_guid": "HQQWccdyakaEmntPhvC6CQ" + }, + { + "swatch_name": "Dodgeball_Gray_Swatch", + "swatch_guid": "0ba92db2-690a-44ce-92f4-130e9503b6c1", + "color": "Gray", + "item_name": "(Shirt_Dodgeball)", + "item_guid": "d3d69bee-60bc-44a3-ad7c-65b3b69d1e59", + "item_outfit_type": 101, + "mask_guid": "944d934d-5e21-42d7-8196-fff9a81ee469" + }, + { + "swatch_name": "Dodgeball_Black_Swatch", + "swatch_guid": "4a85c181-92fe-47a5-925e-05f0ccb25006", + "color": "Black", + "item_name": "(Shirt_Dodgeball)", + "item_guid": "d3d69bee-60bc-44a3-ad7c-65b3b69d1e59", + "item_outfit_type": 101, + "mask_guid": "944d934d-5e21-42d7-8196-fff9a81ee469" + }, + { + "swatch_name": "Hoodie_Gray_Swatch", + "swatch_guid": "JTgXKeT7w0K2Mw4Jk2HCqg", + "color": "Gray", + "item_name": "(Shirt_Hoodie)", + "item_guid": "b6rLwzD4NkKV7xKn9ZYVkA", + "item_outfit_type": 101, + "mask_guid": "" + }, + { + "swatch_name": "Hoodie_Green_Swatch", + "swatch_guid": "ytmSzjhCr0Sm58MSNbwJUA", + "color": "Green", + "item_name": "(Shirt_Hoodie)", + "item_guid": "b6rLwzD4NkKV7xKn9ZYVkA", + "item_outfit_type": 101, + "mask_guid": "" + }, + { + "swatch_name": "Hoodie_Blue_Swatch", + "swatch_guid": "u_Gzj9Nro0WS48xKnH9few", + "color": "Blue", + "item_name": "(Shirt_Hoodie)", + "item_guid": "b6rLwzD4NkKV7xKn9ZYVkA", + "item_outfit_type": 101, + "mask_guid": "" + }, + { + "swatch_name": "Hoodie_Pink_Swatch", + "swatch_guid": "TEgugZRs80iJbhL87RdYwg", + "color": "Pink", + "item_name": "(Shirt_Hoodie)", + "item_guid": "b6rLwzD4NkKV7xKn9ZYVkA", + "item_outfit_type": 101, + "mask_guid": "" + }, + { + "swatch_name": "Hoodie_Orange_Swatch", + "swatch_guid": "rNhOQoP3XkOBHV28ze2NpA", + "color": "Orange", + "item_name": "(Shirt_Hoodie)", + "item_guid": "b6rLwzD4NkKV7xKn9ZYVkA", + "item_outfit_type": 101, + "mask_guid": "" + }, + { + "swatch_name": "Hoodie_Purple_Swatch", + "swatch_guid": "mvMeyStxGUGlNLSYNeSubQ", + "color": "Purple", + "item_name": "(Shirt_Hoodie)", + "item_guid": "b6rLwzD4NkKV7xKn9ZYVkA", + "item_outfit_type": 101, + "mask_guid": "" + }, + { + "swatch_name": "Hoodie_Red_Swatch", + "swatch_guid": "yM5xRrd9OkmDGyQddwOP6g", + "color": "Red", + "item_name": "(Shirt_Hoodie)", + "item_guid": "b6rLwzD4NkKV7xKn9ZYVkA", + "item_outfit_type": 101, + "mask_guid": "" + }, + { + "swatch_name": "Hoodie_White_Swatch", + "swatch_guid": "-Nf7WM2TeEKujB637h3tRA", + "color": "White", + "item_name": "(Shirt_Hoodie)", + "item_guid": "b6rLwzD4NkKV7xKn9ZYVkA", + "item_outfit_type": 101, + "mask_guid": "" + }, + { + "swatch_name": "DeepSeaDiver_Black_Swatch", + "swatch_guid": "CahBzgN3D0y46AQsvGC14A", + "color": "Black", + "item_name": "(Hat_DeepSeaDiver)", + "item_guid": "4fcb5b84-13a9-43e6-9680-72b17874ae53", + "item_outfit_type": 0, + "mask_guid": "" + }, + { + "swatch_name": "DeepSeaDiver_Beige_Swatch", + "swatch_guid": "Su7kiW24d0KapNns96JoFQ", + "color": "Beige", + "item_name": "(Hat_DeepSeaDiver)", + "item_guid": "4fcb5b84-13a9-43e6-9680-72b17874ae53", + "item_outfit_type": 0, + "mask_guid": "" + }, + { + "swatch_name": "QueenCrown_Black_Swatch", + "swatch_guid": "rmq1AOQvtUGOHvbPUximwg", + "color": "Black", + "item_name": "(Hat_QueenCrown)", + "item_guid": "9d395a84-4342-4b1c-940c-213ec2eb56f9", + "item_outfit_type": 0, + "mask_guid": "" + }, + { + "swatch_name": "QueenCrown_White_Swatch", + "swatch_guid": "Zljmtj5WqUaRvB3XQTgJXg", + "color": "White", + "item_name": "(Hat_QueenCrown)", + "item_guid": "9d395a84-4342-4b1c-940c-213ec2eb56f9", + "item_outfit_type": 0, + "mask_guid": "" + }, + { + "swatch_name": "Jazzercise_Black_Swatch", + "swatch_guid": "3b2cfc85-4e2a-4e0d-b738-c296ae8692f8", + "color": "Black", + "item_name": "(Hair_Jazzercise)", + "item_guid": "49397e16-1027-4286-9bc4-b59eae565ff0", + "item_outfit_type": 2, + "mask_guid": "" + }, + { + "swatch_name": "Jazzercise_Yellow_Swatch", + "swatch_guid": "78eb0969-f246-4cac-acd6-295df7c092e4", + "color": "Yellow", + "item_name": "(Hair_Jazzercise)", + "item_guid": "49397e16-1027-4286-9bc4-b59eae565ff0", + "item_outfit_type": 2, + "mask_guid": "" + }, + { + "swatch_name": "FieldHockey_Blue_Swatch", + "swatch_guid": "7b0d6661-e856-4ec1-ab90-d89c28a70826", + "color": "Blue", + "item_name": "(FieldHockey)", + "item_guid": "a986ff33-9e16-4219-8ebb-2e7b4c772ca7", + "item_outfit_type": 101, + "mask_guid": "1fede91a-434e-49a5-882e-e6db107112a8" + }, + { + "swatch_name": "FlowerSwimsuit_Green_Swatch", + "swatch_guid": "cf515c87-3c0a-4443-9f63-5ea1bdbaa61a", + "color": "Green", + "item_name": "(FlowerSwimsuit)", + "item_guid": "f41653f9-ae02-443c-aaf4-1c6f4e49035d", + "item_outfit_type": 101, + "mask_guid": "3248a3ec-50d1-4c0a-96ae-25eb9d8ed622" + }, + { + "swatch_name": "FlowerSwimsuit_Pink_Swatch", + "swatch_guid": "27680277-aec1-41be-a476-9f51cf775580", + "color": "Pink", + "item_name": "(FlowerSwimsuit)", + "item_guid": "f41653f9-ae02-443c-aaf4-1c6f4e49035d", + "item_outfit_type": 101, + "mask_guid": "3248a3ec-50d1-4c0a-96ae-25eb9d8ed622" + }, + { + "swatch_name": "FlowerSwimsuit_Gray_Swatch", + "swatch_guid": "5a96ac4d-03c3-4214-9dcb-c2044ff28f2e", + "color": "Gray", + "item_name": "(FlowerSwimsuit)", + "item_guid": "f41653f9-ae02-443c-aaf4-1c6f4e49035d", + "item_outfit_type": 101, + "mask_guid": "3248a3ec-50d1-4c0a-96ae-25eb9d8ed622" + }, + { + "swatch_name": "Angler_Blue_Swatch", + "swatch_guid": "6e0f3af8-297f-4ea7-8f9e-4a1bc57d3e35", + "color": "Blue", + "item_name": "(Hat_Angler)", + "item_guid": "03f8c394-28fa-4087-978b-8d108f0bd969", + "item_outfit_type": 0, + "mask_guid": "" + }, + { + "swatch_name": "Angler_Tan_Swatch", + "swatch_guid": "cac27853-e2df-4d77-b3fd-af3bc0813f01", + "color": "Tan", + "item_name": "(Hat_Angler)", + "item_guid": "03f8c394-28fa-4087-978b-8d108f0bd969", + "item_outfit_type": 0, + "mask_guid": "" + }, + { + "swatch_name": "Angler_Gray_Swatch", + "swatch_guid": "97938f4c-52b8-4c85-8575-856654666316", + "color": "Gray", + "item_name": "(Hat_Angler)", + "item_guid": "03f8c394-28fa-4087-978b-8d108f0bd969", + "item_outfit_type": 0, + "mask_guid": "" + }, + { + "swatch_name": "Artist_Blue_Swatch", + "swatch_guid": "8c94e45c-6605-4b4f-9ba4-2c6c6e557228", + "color": "Blue", + "item_name": "(Shirt_Artist)", + "item_guid": "02077915-57d3-4e26-b4fe-8e0399cbb293", + "item_outfit_type": 101, + "mask_guid": "" + }, + { + "swatch_name": "Artist_Red_Swatch", + "swatch_guid": "17b16982-871c-476a-860f-f6953c89ee8c", + "color": "Red", + "item_name": "(Shirt_Artist)", + "item_guid": "02077915-57d3-4e26-b4fe-8e0399cbb293", + "item_outfit_type": 101, + "mask_guid": "" + }, + { + "swatch_name": "HockeyShirt_Red_Swatch", + "swatch_guid": "b1ea158d-1663-4db1-bb09-9847f6e9d1e6", + "color": "Red", + "item_name": "(HockeyShirt)", + "item_guid": "d6b0a1e7-e918-43e5-8191-3a8d9d01df7c", + "item_outfit_type": 101, + "mask_guid": "e5635772-4e84-4b30-b77f-4a1196cb599b" + }, + { + "swatch_name": "HockeyShirt_Teal_Swatch", + "swatch_guid": "fcbdfbfd-254a-4538-b119-43e23404b8b5", + "color": "Teal", + "item_name": "(HockeyShirt)", + "item_guid": "d6b0a1e7-e918-43e5-8191-3a8d9d01df7c", + "item_outfit_type": 101, + "mask_guid": "e5635772-4e84-4b30-b77f-4a1196cb599b" + }, + { + "swatch_name": "HockeyShirt_Purple_Swatch", + "swatch_guid": "d5efaf80-7f34-4c9a-bc3a-b80b1ac70ace", + "color": "Purple", + "item_name": "(HockeyShirt)", + "item_guid": "d6b0a1e7-e918-43e5-8191-3a8d9d01df7c", + "item_outfit_type": 101, + "mask_guid": "e5635772-4e84-4b30-b77f-4a1196cb599b" + }, + { + "swatch_name": "Hat_SeaCaptain_Contest_Swatch", + "swatch_guid": "hhs9GY3XZkWb-8TXODvo6A", + "color": "Contest", + "item_name": "(Hat_SeaCaptain)", + "item_guid": "f5c9743f-e77c-42f2-ba63-9843342cbf8e", + "item_outfit_type": 0, + "mask_guid": "a4e51b6b-fdfd-47dc-9d0e-f26f383e03e9" + }, + { + "swatch_name": "LaserTagGlove_Orange_Swatch", + "swatch_guid": "EC0ttvk6eU2HEMacLAx2Iw", + "color": "Orange", + "item_name": "(Wrist_LaserTagGlove)", + "item_guid": "3044adce-90c0-4f66-81f5-39c4fca86fdf", + "item_outfit_type": 200, + "mask_guid": "" + }, + { + "swatch_name": "LaserTagGlove_Green_Swatch", + "swatch_guid": "mhsglVEtREmElF5wuLw3NQ", + "color": "Green", + "item_name": "(Wrist_LaserTagGlove)", + "item_guid": "3044adce-90c0-4f66-81f5-39c4fca86fdf", + "item_outfit_type": 200, + "mask_guid": "" + }, + { + "swatch_name": "50sDress_Red_Swatch", + "swatch_guid": "Yr0mF1EWzEaIyRMOWFiY6w", + "color": "Red", + "item_name": "(Shirt_50sDress)", + "item_guid": "439d1e24-d67f-4b8a-8fb3-8bf589d5a8ee", + "item_outfit_type": 101, + "mask_guid": "" + }, + { + "swatch_name": "50sDress_Pink_Swatch", + "swatch_guid": "eb3SmJTIh0628LrZUkI4KQ", + "color": "Pink", + "item_name": "(Shirt_50sDress)", + "item_guid": "439d1e24-d67f-4b8a-8fb3-8bf589d5a8ee", + "item_outfit_type": 101, + "mask_guid": "" + }, + { + "swatch_name": "50sDress_Teal_Swatch", + "swatch_guid": "QzjakcO8tUaPeiI5KBLKtg", + "color": "Teal", + "item_name": "(Shirt_50sDress)", + "item_guid": "439d1e24-d67f-4b8a-8fb3-8bf589d5a8ee", + "item_outfit_type": 101, + "mask_guid": "" + }, + { + "swatch_name": "50sDress_White_Swatch", + "swatch_guid": "BLgkgLeS-EyazgPQxGcavg", + "color": "White", + "item_name": "(Shirt_50sDress)", + "item_guid": "439d1e24-d67f-4b8a-8fb3-8bf589d5a8ee", + "item_outfit_type": 101, + "mask_guid": "" + }, + { + "swatch_name": "50sDress_CandyCane_Swatch", + "swatch_guid": "PEGqjoxP6E2yRx_A-HphrQ", + "color": "CandyCane", + "item_name": "(Shirt_50sDress)", + "item_guid": "439d1e24-d67f-4b8a-8fb3-8bf589d5a8ee", + "item_outfit_type": 101, + "mask_guid": "" + }, + { + "swatch_name": "50sDress_Halloween_Swatch", + "swatch_guid": "ZvPidQ57rUS50Qz89ERmLw", + "color": "Halloween", + "item_name": "(Shirt_50sDress)", + "item_guid": "439d1e24-d67f-4b8a-8fb3-8bf589d5a8ee", + "item_outfit_type": 101, + "mask_guid": "" + }, + { + "swatch_name": "50sDress_Shamrock_Swatch", + "swatch_guid": "K17dIz_TRUq5D50NBfLjyw", + "color": "Shamrock", + "item_name": "(Shirt_50sDress)", + "item_guid": "439d1e24-d67f-4b8a-8fb3-8bf589d5a8ee", + "item_outfit_type": 101, + "mask_guid": "" + }, + { + "swatch_name": "TopHat_White_Swatch", + "swatch_guid": "DzgN67HoikexE2rTlZLdYQ", + "color": "White", + "item_name": "(Hat_TopHat)", + "item_guid": "102c625b-b988-4bf8-a2aa-a31ad7029cdc", + "item_outfit_type": 0, + "mask_guid": "" + }, + { + "swatch_name": "Shirt_GraduationGown_Green_Swatch", + "swatch_guid": "iZy7RmfAz0603oMO-jD-WA", + "color": "Green", + "item_name": "(Shirt_GraduationGown)", + "item_guid": "U_H976qYRUmg9e2clsAflg", + "item_outfit_type": 101, + "mask_guid": "ica0LZyM_kOox8gxW7CaMw" + }, + { + "swatch_name": "DrumMajor_Orange_Swatch", + "swatch_guid": "82de1ca3-67d6-454d-84fb-0eeece71fcbc", + "color": "Orange", + "item_name": "(Hat_DrumMajor)", + "item_guid": "1a028850-64ee-40ab-b547-ebbcfa1b04b3", + "item_outfit_type": 0, + "mask_guid": "" + }, + { + "swatch_name": "DrumMajor_Green_Swatch", + "swatch_guid": "fe5288bc-0e02-4ea3-b17d-dee0e576979d", + "color": "Green", + "item_name": "(Hat_DrumMajor)", + "item_guid": "1a028850-64ee-40ab-b547-ebbcfa1b04b3", + "item_outfit_type": 0, + "mask_guid": "" + }, + { + "swatch_name": "DrumMajor_Purple_Swatch", + "swatch_guid": "54d3c589-30ba-44c1-a77c-8f9e16eb0b20", + "color": "Purple", + "item_name": "(Hat_DrumMajor)", + "item_guid": "1a028850-64ee-40ab-b547-ebbcfa1b04b3", + "item_outfit_type": 0, + "mask_guid": "" + }, + { + "swatch_name": "Harness_Teal_Swatch", + "swatch_guid": "TnEDe1QoMUCUdqPL_7CxTA", + "color": "Teal", + "item_name": "(Shirt_Harness)", + "item_guid": "ca7a3ccb-2bc9-4c93-a6a8-6f22b38526af", + "item_outfit_type": 101, + "mask_guid": "" + }, + { + "swatch_name": "Harness_Black_Swatch", + "swatch_guid": "AC3zfm38JEyBZWuXDh4Ukg", + "color": "Black", + "item_name": "(Shirt_Harness)", + "item_guid": "ca7a3ccb-2bc9-4c93-a6a8-6f22b38526af", + "item_outfit_type": 101, + "mask_guid": "" + }, + { + "swatch_name": "Harness_Yellow_Swatch", + "swatch_guid": "dyEaPloE8kazWC7Zblv8uQ", + "color": "Yellow", + "item_name": "(Shirt_Harness)", + "item_guid": "ca7a3ccb-2bc9-4c93-a6a8-6f22b38526af", + "item_outfit_type": 101, + "mask_guid": "" + }, + { + "swatch_name": "Harness_Blue_Swatch", + "swatch_guid": "RfZNDPUok0WyQEa3Ko08ow", + "color": "Blue", + "item_name": "(Shirt_Harness)", + "item_guid": "ca7a3ccb-2bc9-4c93-a6a8-6f22b38526af", + "item_outfit_type": 101, + "mask_guid": "" + }, + { + "swatch_name": "Harness_Orange_Swatch", + "swatch_guid": "sX0ZZDcCUkyFSJISfvq0vg", + "color": "Orange", + "item_name": "(Shirt_Harness)", + "item_guid": "ca7a3ccb-2bc9-4c93-a6a8-6f22b38526af", + "item_outfit_type": 101, + "mask_guid": "" + }, + { + "swatch_name": "Unicorn_Pastel_Swatch", + "swatch_guid": "FNe9LkWJMUeC3frYJMyEaQ", + "color": "Pastel", + "item_name": "(Hat_Unicorn)", + "item_guid": "2caHvVQ1vECrPv-YcdxdyA", + "item_outfit_type": 0, + "mask_guid": "" + }, + { + "swatch_name": "Unicorn_Glowing_Swatch", + "swatch_guid": "mudt-Z5-5USdnIq-xaDgOw", + "color": "Glowing", + "item_name": "(Hat_Unicorn)", + "item_guid": "2caHvVQ1vECrPv-YcdxdyA", + "item_outfit_type": 0, + "mask_guid": "" + }, + { + "swatch_name": "Countess_Black_Swatch", + "swatch_guid": "i5RKLo-RbEqisnGII1wpaQ", + "color": "Black", + "item_name": "(Shirt_Countess)", + "item_guid": "QCtqxbvYOkan79YFugH_Cw", + "item_outfit_type": 101, + "mask_guid": "" + }, + { + "swatch_name": "Countess_Purple_Swatch", + "swatch_guid": "IzkFARlv6EenmrfcNkcOuw", + "color": "Purple", + "item_name": "(Shirt_Countess)", + "item_guid": "QCtqxbvYOkan79YFugH_Cw", + "item_outfit_type": 101, + "mask_guid": "" + }, + { + "swatch_name": "Countess_Green_Swatch", + "swatch_guid": "LyVf9lnzdku1F4iPBGqk-g", + "color": "Green", + "item_name": "(Shirt_Countess)", + "item_guid": "QCtqxbvYOkan79YFugH_Cw", + "item_outfit_type": 101, + "mask_guid": "" + }, + { + "swatch_name": "CheerDress_Blue_Swatch", + "swatch_guid": "faedf215-289b-40f7-90cc-f5b98517d133", + "color": "Blue", + "item_name": "(FemaleCheerTorso)", + "item_guid": "657efe89-620a-46a2-8b58-cafd855c7cd5", + "item_outfit_type": 101, + "mask_guid": "93987d32-7a6e-45df-af56-76f912969ce7" + }, + { + "swatch_name": "CheerDress_Red_Swatch", + "swatch_guid": "1773d5d8-4714-4721-b30c-97e25d6f2a5a", + "color": "Red", + "item_name": "Cheerful Dress", + "item_guid": "b02053b3-013f-42b0-8881-85f5be2b8cda", + "item_outfit_type": 101, + "mask_guid": "93987d32-7a6e-45df-af56-76f912969ce7" + }, + { + "swatch_name": "Mobster_Valentine_Swatch", + "swatch_guid": "4VkPVbBVLE-eJHilwGMAbQ", + "color": "Valentine", + "item_name": "(Wrist_Mobster)", + "item_guid": "08d56627-4500-4724-8692-a23c96ddb3ec", + "item_outfit_type": 200, + "mask_guid": "" + }, + { + "swatch_name": "Mobster_WhiteHatHacker_Swatch", + "swatch_guid": "40283127-2ffc-4989-82f6-29c3381bf9e2", + "color": "WhiteHatHacker", + "item_name": "(Wrist_Mobster)", + "item_guid": "08d56627-4500-4724-8692-a23c96ddb3ec", + "item_outfit_type": 200, + "mask_guid": "" + }, + { + "swatch_name": "Luchador_Red_Swatch", + "swatch_guid": "916384ca-e209-42a0-bd64-c64f5a40b56f", + "color": "Red", + "item_name": "(Hat_Luchador)", + "item_guid": "1596c113-aa3a-4f5c-b398-e5c3575548e7", + "item_outfit_type": 0, + "mask_guid": "" + }, + { + "swatch_name": "Luchador_Blue_Swatch", + "swatch_guid": "0217fd2e-ff9d-49f7-9bc8-8d0d6b8bf250", + "color": "Blue", + "item_name": "(Hat_Luchador)", + "item_guid": "1596c113-aa3a-4f5c-b398-e5c3575548e7", + "item_outfit_type": 0, + "mask_guid": "" + }, + { + "swatch_name": "BlueJacket_Mint_Swatch", + "swatch_guid": "cd5d7285-202d-42d0-b93f-04245875793e", + "color": "Mint", + "item_name": "(BlueJacket)", + "item_guid": "62ce4109-8dee-4895-bf1b-bfa143db4c7e", + "item_outfit_type": 101, + "mask_guid": "" + }, + { + "swatch_name": "Steampunk_Contest_Swatch", + "swatch_guid": "fD5vtKxBWECH54Jj4FbnUg", + "color": "Contest", + "item_name": "(Shirt_SteamPunk)", + "item_guid": "E9VTiIVFPEa-OuQRto0e_Q", + "item_outfit_type": 101, + "mask_guid": "" + }, + { + "swatch_name": "GoldMedal_Bronze_Swatch", + "swatch_guid": "iaDL2im4lkCXUo0NWM-F3g", + "color": "Bronze", + "item_name": "(GoldMedal)", + "item_guid": "bf13fa1c-f991-4aaa-9402-aac109be9562", + "item_outfit_type": 100, + "mask_guid": "" + }, + { + "swatch_name": "GoldMedal_Silver_Swatch", + "swatch_guid": "ERnUPsyT6kuEnIRZF8zPwg", + "color": "Silver", + "item_name": "(GoldMedal)", + "item_guid": "bf13fa1c-f991-4aaa-9402-aac109be9562", + "item_outfit_type": 100, + "mask_guid": "" + }, + { + "swatch_name": "Turtleneck_White_Swatch", + "swatch_guid": "Zz4XatKfUEapReRAQHxKKA", + "color": "White", + "item_name": "(Shirt_Turtleneck)", + "item_guid": "7e22a4bd-3e87-4d50-8843-9e2e3cf45285", + "item_outfit_type": 101, + "mask_guid": "" + }, + { + "swatch_name": "Turtleneck_Yellow_Swatch", + "swatch_guid": "G4HIPUjekUigiU0-Kr9mdA", + "color": "Yellow", + "item_name": "(Shirt_Turtleneck)", + "item_guid": "7e22a4bd-3e87-4d50-8843-9e2e3cf45285", + "item_outfit_type": 101, + "mask_guid": "" + }, + { + "swatch_name": "Turtleneck_Green_Swatch", + "swatch_guid": "Jt0QqNrAp0qzUR_RQoivVg", + "color": "Green", + "item_name": "(Shirt_Turtleneck)", + "item_guid": "7e22a4bd-3e87-4d50-8843-9e2e3cf45285", + "item_outfit_type": 101, + "mask_guid": "" + }, + { + "swatch_name": "Turtleneck_Red_Swatch", + "swatch_guid": "lBePHNL9LkyaAjEFyFE8Sw", + "color": "Red", + "item_name": "(Shirt_Turtleneck)", + "item_guid": "7e22a4bd-3e87-4d50-8843-9e2e3cf45285", + "item_outfit_type": 101, + "mask_guid": "" + }, + { + "swatch_name": "Turtleneck_Orange_Swatch", + "swatch_guid": "9b9t8_aKH0-gQXMhZycaOA", + "color": "Orange", + "item_name": "(Shirt_Turtleneck)", + "item_guid": "7e22a4bd-3e87-4d50-8843-9e2e3cf45285", + "item_outfit_type": 101, + "mask_guid": "" + }, + { + "swatch_name": "PB_Trucker_Red_Swatch", + "swatch_guid": "lDXambcP60CeY2C8qeGDFw", + "color": "Red", + "item_name": "(Hat_PB_TruckerHat)", + "item_guid": "0c537a84-9095-406f-8c87-1464f3a774ba", + "item_outfit_type": 0, + "mask_guid": "" + }, + { + "swatch_name": "Witch_Contest_Swatch", + "swatch_guid": "uTHwo8qd6Ei3Oy4gz_j9Ow", + "color": "Contest", + "item_name": "(Shirt_Witch)", + "item_guid": "7LSm-xCYakmgcAr--Fn-JA", + "item_outfit_type": 101, + "mask_guid": "" + }, + { + "swatch_name": "Tennis_Pink_Swatch", + "swatch_guid": "304e5cf1-5c94-481f-8e49-e6a943e6b300", + "color": "Pink", + "item_name": "(Shirt_Tennis)", + "item_guid": "74b91f2b-d4d3-4e41-8946-b1296ab7a773", + "item_outfit_type": 101, + "mask_guid": "90e0da8b-1941-4ca8-b7e5-b4e31921873c" + } + ], + "unmatched": [ + "TShirt_SquadGoals_Swatch", + "TShirt_HangInThere_Swatch", + "TShirt_Orange_Swatch", + "ZebraWhite_Swatch", + "Winter_Gloves_Tree_Green_Swatch", + "Cyber_Trash_Yellow_Swatch", + "Cyber_Trash_Black_Swatch", + "PoofyDress_Purple_Swatch", + "PoofyDress_Orange_Swatch", + "CowboyCherry_Swatch", + "Mobster_HerringBone_Tan_Swatch", + "Laser_Graffiti_Swatch", + "Laser_Ham_Swatch", + "Shuttershades_Salmon_Swatch", + "Shuttershades_Red_Swatch", + "Shuttershades_Blue_Swatch", + "Shuttershades_Yellow_Swatch", + "Countess_Wrist_Purple_Swatch", + "Class_Of_2018_Swatch", + "FighterPilot_Gloves_Orange_Swatch", + "FighterPilot_Gloves_Blue_Swatch", + "Aviator_Gloves_Tan_Swatch", + "Aviator_Gloves_Black_Swatch", + "Plaid_LightBlue_Swatch", + "Plaid_Red_Swatch", + "Astronaut_Glove_Orange_Swatch", + "Fatigue_Swatch", + "Wrist_CowboyWhite_Swatch", + "Wrist_CowboyCherry_Swatch", + "Wrist_CowboyBlack_Swatch", + "Wrist_CowboyLight_Swatch", + "Wrist_CowboyDark_Swatch", + "Winter_Hat_Tree_Swatch", + "Winter_Hat_Red_Swatch", + "Harness_Hat_Teal_Swatch", + "Harness_Hat_Yellow_Swatch", + "Harness_Hat_White_Swatch", + "Harness_Hat_Orange_Swatch", + "Harness_Hat_Black_Swatch", + "Sherlock_Torso_Gray_Swatch", + "Sherlock_Torso_Black_Swatch", + "BowlingShirt_CenterStripe_Burgandy_Swatch", + "BowlingShirt_CenterStripe_Black_Swatch", + "BowlingShirt_CenterStripe_Yellow_Swatch", + "Scuba_Neck_Blue_Swatch", + "Scuba_Neck_Contest_Swatch", + "Cream_Swatch", + "ArtistHat_Red_Swatch", + "ArtistHat_Blue_Swatch", + "ArcherBlack_Quiver_Swatch", + "ArcherBlack_Swatch", + "ArcherBlack_Glove_Swatch", + "ArcherYellow_Swatch", + "ArcherYellow_Glove_Swatch", + "ArcherYellow_Quiver_Swatch", + "Hat_SeaCaptainDarkDarkGold_Swatch", + "Hat_SeaCaptainFushia_Swatch", + "Hat_SeaCaptainDarkGold_Swatch", + "Hat_SeaCaptainDarkSilver_Swatch", + "PolkaBlue_Swatch", + "Letterman_Wrist_Red_Swatch", + "Letterman_Wrist_Purple_Swatch", + "Letterman_Wrist_Green_Swatch", + "Letterman_Wrist_Black_Swatch", + "Yellow_Swatch", + "Windbreaker_Wrist_Yellow_Swatch", + "Windbreaker_Wrist_Red_Swatch", + "Windbreaker_Wrist_Blue_Swatch", + "Windbreaker_Wrist_Salmon_Swatch", + "Beekeeper_Shirt_Teal_Swatch", + "Beekeeper_Shirt_Lavender_Swatch", + "Beekeeper_Shirt_Gold_Swatch", + "2D_Contest_Winner_Swatch", + "Scarecrow_Hat_Contest_Swatch", + "Red_Swatch", + "Hat_Baseball_ThreeColor_BlueRed_Swatch", + "PirateCaptain_Hat_Black_Swatch", + "PirateCaptain_Hat_SRank_Swatch", + "PirateCaptain_Hat_Red_Swatch", + "RoyalCape_Green_Swatch", + "RoyalCape_Blue_Swatch", + "RoyalCape_Purple_Swatch", + "Grey_Swatch", + "BlazerGreyDark_Swatch", + "MagicalGirl_Wrist_Red_Swatch", + "MagicalGirl_Wrist_Orange_Swatch", + "MagicalGirl_Wrist_Green_Swatch", + "MagicalGirl_Wrist_Blue_Swatch", + "PirateCaptain_Belt_Black_Swatch", + "PirateCaptain_Belt_Red_Swatch", + "BowlingShirt_Flames_Red_Swatch", + "BowlingShirt_Flames_Green_Swatch", + "BowlingShirt_Flames_Pink_Swatch", + "BowlingShirt_Flames_Blue_Swatch", + "RoyalCape_Contest_White_Swatch", + "Barber_Torso_Formal_Swatch", + "Barber_Torso_Green_Swatch", + "Aviator_Goggles_Gray_Swatch", + "Aviator_Goggles_Black_Swatch", + "Neck_TutorialGown3_Black_Swatch", + "SafariTorso_Green_Swatch", + "Scallywag_Hat_Blue_Swatch", + "Scallywag_Hat_Red_Swatch", + "TShirt_Laser_Unicorn_Purple_Swatch", + "Astronaut_Helmet_Orange_Swatch", + "ArcherGray_Quiver_Swatch", + "ArcherGray_Swatch", + "ArcherGray_Glove_Swatch", + "Hoodie_Skeleton_NoGlow_Swatch", + "AnglerHat_Blue_Swatch", + "AnglerHat_Gray_Swatch", + "AnglerHat_Tan_Swatch", + "Scuba_eye_Contest_Swatch", + "Scuba_eye_Blue_Swatch", + "BowlingShirt_OneStripe_Green_Swatch", + "BowlingShirt_OneStripe_Pink_Swatch", + "BowlingShirt_OneStripe_Blue_Swatch", + "Golfer_Shirt_Blue_Swatch", + "Golfer_Shirt_Grey_Swatch", + "PaintBallHat_Red_Swatch", + "PaintBallHat_Green_Swatch", + "PaintBallHat_Mustard_Swatch", + "Shirt_TutorialGown3_Black_Swatch", + "Winter_Scarf_Tree_Green_Swatch", + "Blue_Swatch", + "Barber_Hat_Formal_Swatch", + "Barber_Hat_Green_Swatch", + "Vest_HoundsTooth_Black_Swatch", + "Neck_VampiteHunter_Opal_Swatch", + "Neck_VampiteHunter_Ruby_Swatch", + "Neck_VampiteHunter_Amethyst_Swatch", + "Neck_VampiteHunter_Diamond_Swatch", + "Neck_VampiteHunter_Emerald_Swatch", + "Neck_VampiteHunter_Amber_Swatch", + "Neck_VampiteHunter_Topaz_Swatch", + "Winter_Scarf_Red_Swatch", + "Winter_Scarf_Tree_Swatch", + "Floatie_Donut_Swatch", + "Floatie_Dinosaur_Swatch", + "Rainhat_Hat_Red_Swatch", + "Winter_Gloves_Red_Swatch", + "Winter_Gloves_Tree_Swatch", + "Scout_Sash_Blue_Swatch", + "Scout_Sash_Brown_Swatch", + "Scout_Sash_Red_Swatch", + "RoyalLady_Green_Swatch", + "RoyalLady_Black_Swatch", + "RoyalLady_Burgandy_Swatch", + "Mobster_Hat_Brown_Swatch", + "Mobster_Hat_Purple_Swatch", + "Mobster_Hat_Gray_Swatch", + "ElectricianTorso_Gray_Swatch", + "ElectricianTorso_Yellow_Swatch", + "ElectricianTorso_Green_Swatch", + "ElectricianTorso_Beige_Swatch", + "White_Swatch", + "BeltDress_Green_Swatch", + "BeltDress_Black_Swatch", + "BeltDress_Yellow_Swatch", + "FootballHelmet_Orange_Swatch", + "FootballHelmet_Dark_Swatch", + "Aviator_Jacket_Tan_Swatch", + "Aviator_Jacket_Black_Swatch", + "RoyalCrown_Contest_Swatch", + "RoyalCrown_White_Swatch", + "RoyalCrown_Black_Swatch", + "MagicalGirl_Hat_Blue_Swatch", + "MagicalGirl_Hat_Green_Swatch", + "MagicalGirl_Hat_Red_Swatch", + "MagicalGirl_Hat_Orange_Swatch", + "TShirt_KO_Skull_Swatch", + "RoyalLord_Black_Swatch", + "RoyalLord_Burgandy_Swatch", + "RoyalLord_Green_Swatch", + "Hat_Baseball_Logo_Ramen_Swatch", + "Hat_Baseball_Logo_Bucketeers_Swatch", + "Hat_Baseball_Logo_Goblins_Swatch", + "TShirt_Orange_Video_Contest_Generic_Swatch", + "TShirt_Scifi_Face_BLue_Swatch", + "TShirt_Scifi_Face_Pink_Swatch", + "Green_Swatch", + "BlazerDark_Swatch", + "Witch_Contest_Hat_Swatch", + "Witch_Contest_Wrist_Swatch", + "CowboyWhite_Swatch", + "Aviator_Hat_Tan_Swatch", + "Aviator_Hat_Black_Swatch", + "GreenFlower_Swatch", + "BowlingShirt_Diamonds_Purple_Swatch", + "BowlingShirt_Diamonds_Teal_Swatch", + "BowlingShirt_Diamonds_Blue_Swatch", + "FormalDress_Black_Swatch", + "Birthday01_Swatch", + "Scallywag_Eye_SRank_Swatch", + "Ranger_Shirt_Green_Swatch", + "SciFiVisor_Peach_Swatch", + "SciFiVisor_Blue_Swatch", + "SciFiVisor_Orange_Swatch", + "Shirt_TutorialGown2_Orange_Swatch", + "Scifi_Carpet_Swatch", + "Teal_Swatch", + "Charcoal_Swatch", + "Paintball_Red_Swatch", + "Paintball_Blue_Swatch", + "ArcherTan_Glove_Swatch", + "ArcherTan_Quiver_Swatch", + "ArcherTan_Swatch", + "BicycleJacket_Black_Swatch", + "BicycleJacket_DarkBlue_Swatch", + "BicycleJacket_Blue_Swatch", + "Argyle_Brown_Swatch", + "Argyle_Red_Swatch", + "BasketballShirt_Purple_Swatch", + "BasketballShirt_Blue_Swatch", + "Purple_Swatch", + "ScoutSkirt_Shirt_Red_Swatch", + "LaserTagVisor_Green_Swatch", + "LaserTagVisor_Orange_Swatch", + "LaserTagVisor_Cyan_Swatch", + "TShirt_KO_Skull_Pink_Swatch", + "CollarShirt_Torso_Red_Swatch", + "CollarShirt_Torso_Blue_Swatch", + "CollarShirt_Torso_Pink_Swatch", + "CollarShirt_Torso_GoldYellow_Swatch", + "CollarShirt_Torso_GreenFlower_Swatch", + "CollarShirt_Torso_White_Swatch", + "Pearls_Neck_Black_Swatch", + "MagicalGirl_Neck_Orange_Swatch", + "MagicalGirl_Neck_Red_Swatch", + "MagicalGirl_Neck_Green_Swatch", + "MagicalGirl_Neck_Blue_Swatch", + "Cyber_Junk_City_Swatch", + "Firefighter_Hat_Yellow_Swatch", + "Firefighter_Hat_White_Swatch", + "Scout_RaccoonHat_Gray_Swatch", + "JackfrostWings_Neck_Lilac_Swatch", + "CowboyBlack_Swatch", + "Torso_SeaCaptainBlueWhite_Swatch", + "Torso_SeaCaptainBlueDark2_Swatch", + "Torso_SeaCaptainDark_Swatch", + "Torso_SeaCaptainWhiteDark_Swatch", + "Torso_SeaCaptainBlackDark_Swatch", + "Torso_SeaCaptainBlueDark_Swatch", + "Torso_SeaCaptainBlue_Swatch", + "Astronaut_Torso_Orange_Swatch", + "CowboyDark_Swatch", + "Denim_Swatch", + "FootballPads_Orange_Swatch", + "FootballPads_Dark_Swatch", + "RoyalLady_Contest_White_Swatch", + "WaitressDress_Mustard_Swatch", + "WaitressDress_Green_Swatch", + "WaitressDress_Red_Swatch", + "CollarDress_Pink_Swatch", + "CollarDress_LightBlue_Swatch", + "Scarecrow_Wrist_Contest_Swatch", + "Pink_Swatch", + "Mobster_Fashion_Gold_Swatch", + "ArcherSRank_Glove_Swatch", + "ArcherSRank_Quiver_Swatch", + "ArcherSRank_Swatch", + "Royal_Swatch", + "GoldYellow_Swatch", + "Sherlock_Hat_Gray_Swatch", + "Sherlock_Hat_Black_Swatch", + "Happy_Tree_Purple_Swatch", + "Golfer_Glove_Blue_Swatch", + "Golfer_Glove_Grey_Swatch", + "Steampunk_Femme_Contest_Hat_Swatch", + "TShirt_RECS_Logo_Blue_Swatch", + "TShirt_RECS_Logo_Green_Swatch", + "Scout_Shirt_Blue_Swatch", + "DrumMajorGlove_Purple_Swatch", + "DrumMajorGlove_Orange_Swatch", + "ArcherRed_Glove_Swatch", + "ArcherRed_Swatch", + "ArcherRed_Quiver_Swatch", + "Lacrosse_Stripe_Purple_Swatch", + "Winter_Torso_Tree_Swatch", + "Winter_Torso_Red_Swatch", + "Scuba_Shirt_Contest_Swatch", + "Scuba_Shirt_Blue_Swatch", + "LightBlue_Swatch", + "Winter_Hat_Tree_Green_Swatch", + "CowboyLight_Swatch", + "Happy_Tree_Swatch", + "Winter_Torso_Tree_Green_Swatch", + "BlazerMidnightBlue_Swatch", + "Scifi_Carpet_TopHat_Swatch", + "Rebel_Rose_Swatch", + "Mobster_PinStripe_Purple_Swatch", + "Soccer_Shirt_Blue_Swatch", + "Barber_Wrist_Formal_Swatch", + "Steampunk_Femme_Contest_Swatch", + "StollDress_Green_Swatch", + "StollDress_Blue_Swatch", + "BlazerSeaDark_Swatch", + "Orange_Swatch", + "DrumMajorHat_Green_Swatch", + "Golfer_Hat_Blue_Swatch", + "Golfer_Hat_Grey_Swatch", + "RoyalLord_Contest_White_Swatch", + "Black_Swatch", + "Jackfrost_Shirt_Lilac_Swatch", + "CollarShirt_Torso_Plaid_Red_Swatch", + "TShirt_LaserTag_Alpha_Swatch", + "Countess_Green_Wrist_Swatch", + "Hyper_Noodle_Ramen_Swatch", + "Scarecrow_Shirt_Contest_Swatch", + "BicycleHelmet_Yellow_Swatch", + "Soccer_Glove_Blue_Swatch", + "Steampunk_Contest_Hat_Swatch", + "Emerald_Syndicate_Swatch", + "Neck_TutorialGown2_Orange_Swatch", + "Shirt_TutorialGown1_Purple_Swatch", + "2D_Contest_Winner_Yellow_Swatch", + "BlazerGrey_Swatch", + "Stripe3_Swatch", + "Ranger_Hat_Green_Swatch" + ], + "ambiguous": [ + { + "prefix": "windbreaker", + "candidates": [ + "52VsI_lKlkSUAOJlfDnyrQ", + "AIpF1QUeMUWytpHYBDBAkg" + ] + }, + { + "prefix": "lumberjack", + "candidates": [ + "350c5c1e-318b-4233-a7e5-1f49909d8e65", + "61ba3c90-c81e-4deb-bc79-50c0f1fe3e83", + "b537c30c-faef-4649-9a1f-be3985936f93" + ] + }, + { + "prefix": "magicalgirl", + "candidates": [ + "eS_or4AnMka9M_bD7YN28g", + "hT7nSD7Ts06F_1SNlOMLwg", + "MJkDYDH510izAab13ZfXQw", + "WuShlrJnHUKuscn8e6Rt2g" + ] + }, + { + "prefix": "wizard", + "candidates": [ + "01689baf-405e-4b38-9f09-b624e03865b8", + "22ef1089-d073-4fe8-b038-6f34adf0b85f", + "97eae086-cfea-45ff-9b77-1d184256d493" + ] + }, + { + "prefix": "punk", + "candidates": [ + "28374ebc-4050-4ea0-b3b6-41ebe75cedf7", + "ce250e13-8137-4f62-9e52-2b1cebe7d0ad", + "e4ba395e-f291-4e7b-8193-45f4422f41ce" + ] + }, + { + "prefix": "fighterpilot", + "candidates": [ + "5800d2b5-75b8-442f-8b10-63bd3d73a1b8", + "ec836d2a-b1b8-4566-b8eb-123ae2845956", + "ff7e6d41-5c27-4516-930c-ad0e9a23cce2" + ] + }, + { + "prefix": "witchhunter", + "candidates": [ + "418db0e0-c7af-40c2-9cac-d461ce41e210", + "6d36091e-7083-4e3d-8729-94612602c8cb" + ] + }, + { + "prefix": "smuggler", + "candidates": [ + "25d4c652-1730-4925-925e-31a290a2272a", + "3dec4865-cf23-4c19-a460-dd2db80ee851" + ] + }, + { + "prefix": "sodajerk", + "candidates": [ + "iai6P7dDTUq8S4hqQmY1YA", + "Wua43ED71UqL71ATtyS0iQ" + ] + }, + { + "prefix": "piratecaptain", + "candidates": [ + "38b38e24-d636-41c6-b80e-a285ae3106dc", + "3b00c71f-79c2-4a04-9fc0-a23b4c85562f", + "3df394de-9c3e-4141-aba4-12eac4742102", + "eceb7d49-be41-4228-86a9-2973d62e7135" + ] + }, + { + "prefix": "truckerjacket", + "candidates": [ + "8b065e2a-b106-4f11-b415-4c47ce87993e", + "973d84ee-5b88-475c-b579-a76c34a7c533" + ] + }, + { + "prefix": "scientist", + "candidates": [ + "f17a2f9e-1eab-44e8-b10a-1f53c5d0778c", + "fe34adaf-47d3-435b-b2fd-b2fee05fceae" + ] + }, + { + "prefix": "lasertag", + "candidates": [ + "123de433-a005-4994-8a11-d2d84ad66b11", + "24d94673-f394-42e6-a3c8-276f8b8aa722" + ] + }, + { + "prefix": "letterman", + "candidates": [ + "2dFMy4zc7ky_Fd5fzfgInw", + "C4uuztTiYUe20PACMARksA" + ] + }, + { + "prefix": "deepseadiver", + "candidates": [ + "4fcb5b84-13a9-43e6-9680-72b17874ae53", + "8d97303c-5138-4da1-a74d-378da19d11cb", + "b1c3ccc8-91d7-434e-b9cc-bd632fffc01b", + "c3745cee-d87e-4a23-b6e2-64ebf05c8b11" + ] + }, + { + "prefix": "jazzercise", + "candidates": [ + "49397e16-1027-4286-9bc4-b59eae565ff0", + "5f492f8f-ab61-484a-9587-747132b54309" + ] + }, + { + "prefix": "angler", + "candidates": [ + "03f8c394-28fa-4087-978b-8d108f0bd969", + "b1bfe0b4-1ff6-420f-acfc-2331d34246dc" + ] + }, + { + "prefix": "drummajor", + "candidates": [ + "1a028850-64ee-40ab-b547-ebbcfa1b04b3", + "a6f1825e-1647-4d06-85d7-ac2d139612c0" + ] + }, + { + "prefix": "countess", + "candidates": [ + "QCtqxbvYOkan79YFugH_Cw", + "t9co2516nkuJO4LIJA1bJA" + ] + }, + { + "prefix": "mobster", + "candidates": [ + "08d56627-4500-4724-8692-a23c96ddb3ec", + "4308da04-af84-40bb-845a-7a9e1a2726ec", + "76369aef-aeda-46d2-996a-cd00594d0543" + ] + }, + { + "prefix": "steampunk", + "candidates": [ + "E9VTiIVFPEa-OuQRto0e_Q", + "gwLiwmqBIk6glFxvaTzxeA", + "qxovV6Vym0ufL2cTCyKjOQ", + "sOREgAGs-06CeGGsG-WIkw" + ] + }, + { + "prefix": "witch", + "candidates": [ + "7LSm-xCYakmgcAr--Fn-JA", + "s6mUIqiPCk6e57UYCx3SZA", + "wy2cfgb8mUqjMrIOwHH2ew" + ] + } + ] +} \ No newline at end of file diff --git a/DorkNet.Server/Data/equipment_skins.json b/DorkNet.Server/Data/equipment_skins.json new file mode 100644 index 0000000..2feb210 --- /dev/null +++ b/DorkNet.Server/Data/equipment_skins.json @@ -0,0 +1,3 @@ +{ + "items": [] +} diff --git a/DorkNet.Server/Data/images/086ug40lugly2prjest6kw4dz.png b/DorkNet.Server/Data/images/086ug40lugly2prjest6kw4dz.png new file mode 100644 index 0000000..ad36de2 Binary files /dev/null and b/DorkNet.Server/Data/images/086ug40lugly2prjest6kw4dz.png differ diff --git a/DorkNet.Server/Data/images/0c1qdk77fbl8c5slphl3k1t8e.png b/DorkNet.Server/Data/images/0c1qdk77fbl8c5slphl3k1t8e.png new file mode 100644 index 0000000..7daccf3 Binary files /dev/null and b/DorkNet.Server/Data/images/0c1qdk77fbl8c5slphl3k1t8e.png differ diff --git a/DorkNet.Server/Data/images/1g7pq04z9xseircwla8sf9py5.png b/DorkNet.Server/Data/images/1g7pq04z9xseircwla8sf9py5.png new file mode 100644 index 0000000..f6f3f0a Binary files /dev/null and b/DorkNet.Server/Data/images/1g7pq04z9xseircwla8sf9py5.png differ diff --git a/DorkNet.Server/Data/images/3v6ak7k2ljui4ubj0xh21sjys.png b/DorkNet.Server/Data/images/3v6ak7k2ljui4ubj0xh21sjys.png new file mode 100644 index 0000000..0601a30 Binary files /dev/null and b/DorkNet.Server/Data/images/3v6ak7k2ljui4ubj0xh21sjys.png differ diff --git a/DorkNet.Server/Data/images/4o5lschc01nani8xeywao622n.png b/DorkNet.Server/Data/images/4o5lschc01nani8xeywao622n.png new file mode 100644 index 0000000..da4a0ba Binary files /dev/null and b/DorkNet.Server/Data/images/4o5lschc01nani8xeywao622n.png differ diff --git a/DorkNet.Server/Data/images/4xy3n0voagf6t9bbfnxft1txt.png b/DorkNet.Server/Data/images/4xy3n0voagf6t9bbfnxft1txt.png new file mode 100644 index 0000000..8cc3d63 Binary files /dev/null and b/DorkNet.Server/Data/images/4xy3n0voagf6t9bbfnxft1txt.png differ diff --git a/DorkNet.Server/Data/images/50xrbhogfdae0mcgzfkomlghu.png b/DorkNet.Server/Data/images/50xrbhogfdae0mcgzfkomlghu.png new file mode 100644 index 0000000..c08d770 Binary files /dev/null and b/DorkNet.Server/Data/images/50xrbhogfdae0mcgzfkomlghu.png differ diff --git a/DorkNet.Server/Data/images/61qgagldqpy5opgpbrbg5wxvo.png b/DorkNet.Server/Data/images/61qgagldqpy5opgpbrbg5wxvo.png new file mode 100644 index 0000000..3105d92 Binary files /dev/null and b/DorkNet.Server/Data/images/61qgagldqpy5opgpbrbg5wxvo.png differ diff --git a/DorkNet.Server/Data/images/6wvnxmrmsw98wsttajp0nreci.png b/DorkNet.Server/Data/images/6wvnxmrmsw98wsttajp0nreci.png new file mode 100644 index 0000000..8748006 Binary files /dev/null and b/DorkNet.Server/Data/images/6wvnxmrmsw98wsttajp0nreci.png differ diff --git a/DorkNet.Server/Data/images/86s8y4ihyhx7n9ac41gwahwze.png b/DorkNet.Server/Data/images/86s8y4ihyhx7n9ac41gwahwze.png new file mode 100644 index 0000000..0290ff7 Binary files /dev/null and b/DorkNet.Server/Data/images/86s8y4ihyhx7n9ac41gwahwze.png differ diff --git a/DorkNet.Server/Data/images/8gwqibu0anm4j0rw54xo2y0yd.png b/DorkNet.Server/Data/images/8gwqibu0anm4j0rw54xo2y0yd.png new file mode 100644 index 0000000..c29853f Binary files /dev/null and b/DorkNet.Server/Data/images/8gwqibu0anm4j0rw54xo2y0yd.png differ diff --git a/DorkNet.Server/Data/images/8j4f516axpnpjgnj342t85vkd.png b/DorkNet.Server/Data/images/8j4f516axpnpjgnj342t85vkd.png new file mode 100644 index 0000000..5607d30 Binary files /dev/null and b/DorkNet.Server/Data/images/8j4f516axpnpjgnj342t85vkd.png differ diff --git a/DorkNet.Server/Data/images/8ners1eg89dy974ixbim40sky.png b/DorkNet.Server/Data/images/8ners1eg89dy974ixbim40sky.png new file mode 100644 index 0000000..7daccf3 Binary files /dev/null and b/DorkNet.Server/Data/images/8ners1eg89dy974ixbim40sky.png differ diff --git a/DorkNet.Server/Data/images/8x6qaj4nlbudf1412tupxkq9n.png b/DorkNet.Server/Data/images/8x6qaj4nlbudf1412tupxkq9n.png new file mode 100644 index 0000000..ccb923e Binary files /dev/null and b/DorkNet.Server/Data/images/8x6qaj4nlbudf1412tupxkq9n.png differ diff --git a/DorkNet.Server/Data/images/ae6yh8p6y8p3cuyqkc4l8wi4z.png b/DorkNet.Server/Data/images/ae6yh8p6y8p3cuyqkc4l8wi4z.png new file mode 100644 index 0000000..1f251d6 Binary files /dev/null and b/DorkNet.Server/Data/images/ae6yh8p6y8p3cuyqkc4l8wi4z.png differ diff --git a/DorkNet.Server/Data/images/aj7w8q5nrfyt2ssfoovawtz4l.png b/DorkNet.Server/Data/images/aj7w8q5nrfyt2ssfoovawtz4l.png new file mode 100644 index 0000000..24b9cb4 Binary files /dev/null and b/DorkNet.Server/Data/images/aj7w8q5nrfyt2ssfoovawtz4l.png differ diff --git a/DorkNet.Server/Data/images/apo2sd115tuwueyoprp8jhx7b.png b/DorkNet.Server/Data/images/apo2sd115tuwueyoprp8jhx7b.png new file mode 100644 index 0000000..0acc4e9 Binary files /dev/null and b/DorkNet.Server/Data/images/apo2sd115tuwueyoprp8jhx7b.png differ diff --git a/DorkNet.Server/Data/images/d1es44q1u6hlhykxpy8uq0lci.png b/DorkNet.Server/Data/images/d1es44q1u6hlhykxpy8uq0lci.png new file mode 100644 index 0000000..6795bd6 Binary files /dev/null and b/DorkNet.Server/Data/images/d1es44q1u6hlhykxpy8uq0lci.png differ diff --git a/DorkNet.Server/Data/images/image_3DCharades.png b/DorkNet.Server/Data/images/image_3DCharades.png new file mode 100644 index 0000000..8ab80f3 Binary files /dev/null and b/DorkNet.Server/Data/images/image_3DCharades.png differ diff --git a/DorkNet.Server/Data/images/image_Clearcut.png b/DorkNet.Server/Data/images/image_Clearcut.png new file mode 100644 index 0000000..d8aa008 Binary files /dev/null and b/DorkNet.Server/Data/images/image_Clearcut.png differ diff --git a/DorkNet.Server/Data/images/image_CrimsonCauldron.png b/DorkNet.Server/Data/images/image_CrimsonCauldron.png new file mode 100644 index 0000000..774dc8c Binary files /dev/null and b/DorkNet.Server/Data/images/image_CrimsonCauldron.png differ diff --git a/DorkNet.Server/Data/images/image_CyberJunkCity.png b/DorkNet.Server/Data/images/image_CyberJunkCity.png new file mode 100644 index 0000000..1476e54 Binary files /dev/null and b/DorkNet.Server/Data/images/image_CyberJunkCity.png differ diff --git a/DorkNet.Server/Data/images/image_DiscGolfLake.png b/DorkNet.Server/Data/images/image_DiscGolfLake.png new file mode 100644 index 0000000..8d1dc5f Binary files /dev/null and b/DorkNet.Server/Data/images/image_DiscGolfLake.png differ diff --git a/DorkNet.Server/Data/images/image_DiscGolfPropulsion.png b/DorkNet.Server/Data/images/image_DiscGolfPropulsion.png new file mode 100644 index 0000000..1102201 Binary files /dev/null and b/DorkNet.Server/Data/images/image_DiscGolfPropulsion.png differ diff --git a/DorkNet.Server/Data/images/image_Dodgeball.png b/DorkNet.Server/Data/images/image_Dodgeball.png new file mode 100644 index 0000000..f304147 Binary files /dev/null and b/DorkNet.Server/Data/images/image_Dodgeball.png differ diff --git a/DorkNet.Server/Data/images/image_DriveIn.png b/DorkNet.Server/Data/images/image_DriveIn.png new file mode 100644 index 0000000..dda39b0 Binary files /dev/null and b/DorkNet.Server/Data/images/image_DriveIn.png differ diff --git a/DorkNet.Server/Data/images/image_GoldenTrophy.png b/DorkNet.Server/Data/images/image_GoldenTrophy.png new file mode 100644 index 0000000..9640429 Binary files /dev/null and b/DorkNet.Server/Data/images/image_GoldenTrophy.png differ diff --git a/DorkNet.Server/Data/images/image_Homestead.png b/DorkNet.Server/Data/images/image_Homestead.png new file mode 100644 index 0000000..15850ca Binary files /dev/null and b/DorkNet.Server/Data/images/image_Homestead.png differ diff --git a/DorkNet.Server/Data/images/image_IsleOfLostSkulls.png b/DorkNet.Server/Data/images/image_IsleOfLostSkulls.png new file mode 100644 index 0000000..e8e98e2 Binary files /dev/null and b/DorkNet.Server/Data/images/image_IsleOfLostSkulls.png differ diff --git a/DorkNet.Server/Data/images/image_LaserTag.png b/DorkNet.Server/Data/images/image_LaserTag.png new file mode 100644 index 0000000..c5a9dc4 Binary files /dev/null and b/DorkNet.Server/Data/images/image_LaserTag.png differ diff --git a/DorkNet.Server/Data/images/image_Paddleball.png b/DorkNet.Server/Data/images/image_Paddleball.png new file mode 100644 index 0000000..ad1e832 Binary files /dev/null and b/DorkNet.Server/Data/images/image_Paddleball.png differ diff --git a/DorkNet.Server/Data/images/image_Paintball.png b/DorkNet.Server/Data/images/image_Paintball.png new file mode 100644 index 0000000..d041b59 Binary files /dev/null and b/DorkNet.Server/Data/images/image_Paintball.png differ diff --git a/DorkNet.Server/Data/images/image_Quarry.png b/DorkNet.Server/Data/images/image_Quarry.png new file mode 100644 index 0000000..49b7e6e Binary files /dev/null and b/DorkNet.Server/Data/images/image_Quarry.png differ diff --git a/DorkNet.Server/Data/images/image_RecCenter.png b/DorkNet.Server/Data/images/image_RecCenter.png new file mode 100644 index 0000000..83759fc Binary files /dev/null and b/DorkNet.Server/Data/images/image_RecCenter.png differ diff --git a/DorkNet.Server/Data/images/image_RecRoyaleSolos.png b/DorkNet.Server/Data/images/image_RecRoyaleSolos.png new file mode 100644 index 0000000..272df63 Binary files /dev/null and b/DorkNet.Server/Data/images/image_RecRoyaleSolos.png differ diff --git a/DorkNet.Server/Data/images/image_RecRoyaleSquads.png b/DorkNet.Server/Data/images/image_RecRoyaleSquads.png new file mode 100644 index 0000000..e63154a Binary files /dev/null and b/DorkNet.Server/Data/images/image_RecRoyaleSquads.png differ diff --git a/DorkNet.Server/Data/images/image_River.png b/DorkNet.Server/Data/images/image_River.png new file mode 100644 index 0000000..a5519c8 Binary files /dev/null and b/DorkNet.Server/Data/images/image_River.png differ diff --git a/DorkNet.Server/Data/images/image_Soccer.png b/DorkNet.Server/Data/images/image_Soccer.png new file mode 100644 index 0000000..c29f366 Binary files /dev/null and b/DorkNet.Server/Data/images/image_Soccer.png differ diff --git a/DorkNet.Server/Data/images/image_Spillway.png b/DorkNet.Server/Data/images/image_Spillway.png new file mode 100644 index 0000000..e3f0eec Binary files /dev/null and b/DorkNet.Server/Data/images/image_Spillway.png differ diff --git a/DorkNet.Server/Data/images/image_StuntRunner.png b/DorkNet.Server/Data/images/image_StuntRunner.png new file mode 100644 index 0000000..cc5e69a Binary files /dev/null and b/DorkNet.Server/Data/images/image_StuntRunner.png differ diff --git a/DorkNet.Server/Data/images/image_TheRiseofJumbotron.png b/DorkNet.Server/Data/images/image_TheRiseofJumbotron.png new file mode 100644 index 0000000..5f99074 Binary files /dev/null and b/DorkNet.Server/Data/images/image_TheRiseofJumbotron.png differ diff --git a/DorkNet.Server/Data/room_images.json b/DorkNet.Server/Data/room_images.json new file mode 100644 index 0000000..01fb601 --- /dev/null +++ b/DorkNet.Server/Data/room_images.json @@ -0,0 +1,20 @@ +{ + "3DCharades": "aj7w8q5nrfyt2ssfoovawtz4l.png", + "BowlingAlley": "8gwqibu0anm4j0rw54xo2y0yd.png", + "CrimsonCauldron": "ae6yh8p6y8p3cuyqkc4l8wi4z.png", + "DiscGolfLake": "86s8y4ihyhx7n9ac41gwahwze.png", + "DiscGolfPropulsion": "6wvnxmrmsw98wsttajp0nreci.png", + "Dodgeball": "086ug40lugly2prjest6kw4dz.png", + "GoldenTrophy": "1g7pq04z9xseircwla8sf9py5.png", + "IsleOfLostSkulls": "3v6ak7k2ljui4ubj0xh21sjys.png", + "LaserTag": "4xy3n0voagf6t9bbfnxft1txt.png", + "Paddleball": "61qgagldqpy5opgpbrbg5wxvo.png", + "Paintball": "d1es44q1u6hlhykxpy8uq0lci.png", + "Park": "4o5lschc01nani8xeywao622n.png", + "RecCenter": "50xrbhogfdae0mcgzfkomlghu.png", + "RecRoyaleSolos": "8ners1eg89dy974ixbim40sky.png", + "RecRoyaleSquads": "0c1qdk77fbl8c5slphl3k1t8e.png", + "Soccer": "apo2sd115tuwueyoprp8jhx7b.png", + "StuntRunner": "8j4f516axpnpjgnj342t85vkd.png", + "TheRiseofJumbotron": "8x6qaj4nlbudf1412tupxkq9n.png" +} \ No newline at end of file diff --git a/DorkNet.Server/Data/room_locations.json b/DorkNet.Server/Data/room_locations.json new file mode 100644 index 0000000..17eaba9 --- /dev/null +++ b/DorkNet.Server/Data/room_locations.json @@ -0,0 +1,584 @@ +[ + { + "Name": "AR Room", + "ReplicationId": "bf268f5f-b55b-41af-8628-32fa4b5d70b6", + "SceneName": "AR_room", + "foundAt": "0x06515e64" + }, + { + "Name": "Animation Recording Studio", + "ReplicationId": "a95c349c-0f96-4c2d-a4c8-4969ffa8ea44", + "SceneName": "AnimationRecordingStudio", + "foundAt": "0x065158ac" + }, + { + "Name": "AnimationRecordingStudio", + "ReplicationId": "3c6fd16c-43c7-4c0e-bfd9-f90eaf077ba7", + "SceneName": "Record Animations", + "foundAt": "0x06519064" + }, + { + "Name": "Art Testing", + "ReplicationId": "42699ed2-0c1b-4f3d-93a2-ce01dfce7a79", + "SceneName": "ArtTesting", + "foundAt": "0x065150a8" + }, + { + "Name": "ArtTesting", + "ReplicationId": "cc338cd1-9883-0c14-99b3-ab069a360ee3", + "SceneName": "bla", + "foundAt": "0x06517f04" + }, + { + "Name": "Bowling Alley", + "ReplicationId": "ae929543-9a07-41d5-8ee9-dbbee8c36800", + "SceneName": "BowlingAlley", + "foundAt": "0x06515828" + }, + { + "Name": "Calibration", + "ReplicationId": "30040e05-b7b9-9f44-eb08-b9f154d2ecfc", + "SceneName": "PSVR room calibration", + "foundAt": "0x06515fe8" + }, + { + "Name": "Charades", + "ReplicationId": "4078dfed-24bb-4db7-863f-578ba48d726b", + "SceneName": "charades", + "foundAt": "0x06514438" + }, + { + "Name": "Clear Cut", + "ReplicationId": "380d18b5-de9c-49f3-80f7-f4a95c1de161", + "SceneName": "Paintball_ClearCut", + "foundAt": "0x065148ac" + }, + { + "Name": "Clearcut", + "ReplicationId": "28363a22-f0bb-4e46-8346-d76e8ac634f7", + "SceneName": "380d18b5-de9c-49f3-80f7-f4a95c1de161", + "foundAt": "0x06516ae0" + }, + { + "Name": "Clearcut", + "ReplicationId": "1636a319-9826-bfc4-18f8-ee1b1be6f806", + "SceneName": "380d18b5-de9c-49f3-80f7-f4a95c1de161", + "foundAt": "0x06516e78" + }, + { + "Name": "Crescendo of the Blood Moon", + "ReplicationId": "49cb8993-a956-43e2-86f4-1318f279b22a", + "SceneName": "Quest_Dracula1_additive", + "foundAt": "0x065155e4" + }, + { + "Name": "Curse of the Crimson Cauldron", + "ReplicationId": "949fa41f-4347-45c0-b7ac-489129174045", + "SceneName": "Quest_Goblin2_additive", + "foundAt": "0x06514cd4" + }, + { + "Name": "CyberJunk City", + "ReplicationId": "9d6456ce-6264-48b4-808d-2d96b3d91038", + "SceneName": "Arena_Cyberjunk_City", + "foundAt": "0x0651537c" + }, + { + "Name": "CyberJunkCity", + "ReplicationId": "333f409d-f9ad-46c9-b6fb-b0bbd30cb0b2", + "SceneName": "9d6456ce-6264-48b4-808d-2d96b3d91038", + "foundAt": "0x065176a0" + }, + { + "Name": "Dodgeball", + "ReplicationId": "3d474b26-26f7-45e9-9a36-9b02847d5e6f", + "SceneName": "dodgeball", + "foundAt": "0x065145ac" + }, + { + "Name": "Dorm Room", + "ReplicationId": "76d98498-60a1-430c-ab76-b54a29b7a163", + "SceneName": "dormroom2", + "foundAt": "0x06514338" + }, + { + "Name": "DormRoom", + "ReplicationId": "68251132-5662-5c34-08b1-4a830a27955b", + "SceneName": "Your private room", + "foundAt": "0x065160dc" + }, + { + "Name": "Drive-in", + "ReplicationId": "dc185520-0fa2-46d5-bc27-a613c6679c7c", + "SceneName": "65ddbb48-5a01-4e3e-972d-e5c7419e2bc3", + "foundAt": "0x06516bd8" + }, + { + "Name": "DriveIn", + "ReplicationId": "65ddbb48-5a01-4e3e-972d-e5c7419e2bc3", + "SceneName": "DriveIn", + "foundAt": "0x06515f6c" + }, + { + "Name": "Frontier Solos", + "ReplicationId": "b010171f-4875-4e89-baba-61e878cd41e1", + "SceneName": "RecRoyale_Frontier", + "foundAt": "0x06515558" + }, + { + "Name": "Frontier Squads", + "ReplicationId": "253fa009-6e65-4c90-91a1-7137a56a267f", + "SceneName": "RecRoyale_Frontier", + "foundAt": "0x065154cc" + }, + { + "Name": "Hangar", + "ReplicationId": "8f8fcaf8-7b13-e114-880c-72ee8c9fcb78", + "SceneName": "239e676c-f12f-489f-bf3a-d4c383d692c3", + "foundAt": "0x06517624" + }, + { + "Name": "Home", + "ReplicationId": "fe66923e-4034-4ec4-4bca-11a973bf5515", + "SceneName": "f5fbd9c9-e853-4036-9d48-5f68e861af04", + "foundAt": "0x06516064" + }, + { + "Name": "Home", + "ReplicationId": "92084aee-1f44-a3b4-18f1-375601606506", + "SceneName": "76d98498-60a1-430c-ab76-b54a29b7a163", + "foundAt": "0x06516150" + }, + { + "Name": "Home", + "ReplicationId": "6016e455-652d-54d4-3838-ecc6c9aa4ca8", + "SceneName": "cbad71af-0831-44d8-b8ef-69edafa841f6", + "foundAt": "0x06516268" + }, + { + "Name": "Home", + "ReplicationId": "b7150f3d-393e-ac74-2801-8a834b13e2bc", + "SceneName": "4078dfed-24bb-4db7-863f-578ba48d726b", + "foundAt": "0x06516390" + }, + { + "Name": "Home", + "ReplicationId": "6cd09971-c9fb-8e44-9b7f-3cb9ff5f6bd0", + "SceneName": "f6f7256c-e438-4299-b99e-d20bef8cf7e0", + "foundAt": "0x065164c4" + }, + { + "Name": "Home", + "ReplicationId": "e687ebed-6502-ad24-2ae1-d4a1db441d34", + "SceneName": "d9378c9f-80bc-46fb-ad1e-1bed8a674f55", + "foundAt": "0x065165fc" + }, + { + "Name": "Home", + "ReplicationId": "27dd5218-dbea-b444-9b7c-a64e687aae67", + "SceneName": "3d474b26-26f7-45e9-9a36-9b02847d5e6f", + "foundAt": "0x06516718" + }, + { + "Name": "Home", + "ReplicationId": "11512b3c-bf64-acc4-7bf6-139786237328", + "SceneName": "d89f74fa-d51e-477a-a425-025a891dd499", + "foundAt": "0x06516844" + }, + { + "Name": "Home", + "ReplicationId": "3518de14-8412-96f4-db0a-41abe0196bbe", + "SceneName": "91e16e35-f48f-4700-ab8a-a1b79e50e51b", + "foundAt": "0x06517034" + }, + { + "Name": "Home", + "ReplicationId": "4a6968bf-0283-2d34-ca18-29f1145e1f69", + "SceneName": "acc06e66-c2d0-4361-b0cd-46246a4c455c", + "foundAt": "0x06517170" + }, + { + "Name": "Home", + "ReplicationId": "4a169c2f-28b2-e9a4-7ab1-61c9c270b1ab", + "SceneName": "949fa41f-4347-45c0-b7ac-489129174045", + "foundAt": "0x065172b4" + }, + { + "Name": "Home", + "ReplicationId": "02d23e1d-4c1a-15b4-aba5-98458d750417", + "SceneName": "7e01cfe0-820a-406f-b1b3-0a5bf575235c", + "foundAt": "0x065173f4" + }, + { + "Name": "Home", + "ReplicationId": "9a4b4871-28dc-4b24-8b8f-e0914db002eb", + "SceneName": "6d5eea4b-f069-4ed0-9916-0e2f07df0d03", + "foundAt": "0x06517520" + }, + { + "Name": "Home", + "ReplicationId": "d59f7e16-9f27-6924-a899-2739bfc056fe", + "SceneName": "9d6456ce-6264-48b4-808d-2d96b3d91038", + "foundAt": "0x065177e0" + }, + { + "Name": "Home", + "ReplicationId": "1d282c2d-6edc-4910-afb3-48fbb9ad74a4", + "SceneName": "253fa009-6e65-4c90-91a1-7137a56a267f", + "foundAt": "0x0651790c" + }, + { + "Name": "Home", + "ReplicationId": "85b43509-77f7-3884-3a6b-9a4e737d6d11", + "SceneName": "b010171f-4875-4e89-baba-61e878cd41e1", + "foundAt": "0x06517a28" + }, + { + "Name": "Home", + "ReplicationId": "c9e928de-ed1f-d564-38af-9bf525ac0feb", + "SceneName": "a067557f-ca32-43e6-b6e5-daaec60b4f5a", + "foundAt": "0x06517b48" + }, + { + "Name": "Home", + "ReplicationId": "b55204c1-9a48-6ea4-fbd0-69fb125a68cf", + "SceneName": "9932f88f-3929-43a0-a012-a40b5128e346", + "foundAt": "0x06517c64" + }, + { + "Name": "Home", + "ReplicationId": "aa889faf-91c8-b7d4-5945-ab7e714f7efa", + "SceneName": "a75f7547-79eb-47c6-8986-6767abcb4f92", + "foundAt": "0x06517d7c" + }, + { + "Name": "Home", + "ReplicationId": "570a59bf-b4e2-6db4-ca9a-42329563dd30", + "SceneName": "0a864c86-5a71-4e18-8041-8124e4dc9d98", + "foundAt": "0x06517e8c" + }, + { + "Name": "Home", + "ReplicationId": "33bdd61b-bd99-abe4-6943-3032c741f5e8", + "SceneName": "42699ed2-0c1b-4f3d-93a2-ce01dfce7a79", + "foundAt": "0x06517f6c" + }, + { + "Name": "Home", + "ReplicationId": "dc445d24-01cf-43f9-a7c4-33f432514735", + "SceneName": "e122fe98-e7db-49e8-a1b1-105424b6e1f0", + "foundAt": "0x06518088" + }, + { + "Name": "Home", + "ReplicationId": "daaa8763-868d-4081-9e8d-e931c111cc90", + "SceneName": "a785267d-c579-42ea-be43-fec1992d1ca7", + "foundAt": "0x06518198" + }, + { + "Name": "Home", + "ReplicationId": "f083ff9a-98af-4d8b-b7ff-5b84a66367d5", + "SceneName": "ff4c6427-7079-4f59-b22a-69b089420827", + "foundAt": "0x065182c0" + }, + { + "Name": "Home", + "ReplicationId": "c5f45037-8c60-494d-ad62-3e5168560b36", + "SceneName": "380d18b5-de9c-49f3-80f7-f4a95c1de161", + "foundAt": "0x065183e8" + }, + { + "Name": "Home", + "ReplicationId": "d336999f-9db0-42fc-8af5-592e9043102f", + "SceneName": "58763055-2dfb-4814-80b8-16fac5c85709", + "foundAt": "0x06518508" + }, + { + "Name": "Home", + "ReplicationId": "28253579-64cb-421b-988b-9fea890fd129", + "SceneName": "f6f7256c-e438-4299-b99e-d20bef8cf7e0", + "foundAt": "0x06518608" + }, + { + "Name": "Home", + "ReplicationId": "cd82c0aa-5dbc-4db3-ade3-30576d3bc473", + "SceneName": "d9378c9f-80bc-46fb-ad1e-1bed8a674f55", + "foundAt": "0x06518748" + }, + { + "Name": "Home", + "ReplicationId": "8cecdc5b-d17b-4eba-9dd3-7b0ca54d3fd2", + "SceneName": "3d474b26-26f7-45e9-9a36-9b02847d5e6f", + "foundAt": "0x0651884c" + }, + { + "Name": "Home", + "ReplicationId": "b4f8632d-ae8b-4c3d-a433-0687131f8507", + "SceneName": "6d5eea4b-f069-4ed0-9916-0e2f07df0d03", + "foundAt": "0x06518958" + }, + { + "Name": "Home", + "ReplicationId": "26330026-ebc7-4d44-8c85-4f38d2ad30a1", + "SceneName": "239e676c-f12f-489f-bf3a-d4c383d692c3", + "foundAt": "0x06518a60" + }, + { + "Name": "Home", + "ReplicationId": "6e0edd15-b904-f074-3b1f-cec488eba1a3", + "SceneName": "9d6456ce-6264-48b4-808d-2d96b3d91038", + "foundAt": "0x06518b80" + }, + { + "Name": "Home", + "ReplicationId": "bcbda991-93e7-4d05-b2e2-7c9512e4d4f2", + "SceneName": "3d474b26-26f7-45e9-9a36-9b02847d5e6f", + "foundAt": "0x06518c9c" + }, + { + "Name": "Home", + "ReplicationId": "66726423-6e03-4548-9990-f1b10e8c5c2e", + "SceneName": "49cb8993-a956-43e2-86f4-1318f279b22a", + "foundAt": "0x06518dd4" + }, + { + "Name": "Home", + "ReplicationId": "d61f36d3-f702-4002-a065-e1b6a59d4036", + "SceneName": "ae929543-9a07-41d5-8ee9-dbbee8c36800", + "foundAt": "0x06518ee0" + }, + { + "Name": "Home", + "ReplicationId": "b492f0d4-5f72-4429-9ea5-decf369e7aa7", + "SceneName": "ae929543-9a07-41d5-8ee9-dbbee8c36800", + "foundAt": "0x06518fec" + }, + { + "Name": "Home", + "ReplicationId": "8fe5818c-d466-4ffb-b502-f7d5f67efc7a", + "SceneName": "a95c349c-0f96-4c2d-a4c8-4969ffa8ea44", + "foundAt": "0x065190e8" + }, + { + "Name": "Home", + "ReplicationId": "57ec15a2-34b5-42d4-a5d8-ddad6888e0e3", + "SceneName": "882e9b96-7115-4b03-86f6-c0c9d8e22e00", + "foundAt": "0x065193d4" + }, + { + "Name": "Home", + "ReplicationId": "c300e175-8420-44fd-9966-c422275993f1", + "SceneName": "cf61556d-68fd-4288-9ae5-7a512621e569", + "foundAt": "0x065194bc" + }, + { + "Name": "Home", + "ReplicationId": "9fc2886b-3508-4191-9389-ccd0a6ee8a29", + "SceneName": "bf268f5f-b55b-41af-8628-32fa4b5d70b6", + "foundAt": "0x065195a4" + }, + { + "Name": "Homestead", + "ReplicationId": "f6709dc2-af81-46fc-88ba-f88f6c5035aa", + "SceneName": "a785267d-c579-42ea-be43-fec1992d1ca7", + "foundAt": "0x065169e4" + }, + { + "Name": "Homestead", + "ReplicationId": "d686129f-fdf7-1954-a9f6-f0a80d1af234", + "SceneName": "a785267d-c579-42ea-be43-fec1992d1ca7", + "foundAt": "0x06516d7c" + }, + { + "Name": "Homestead", + "ReplicationId": "a785267d-c579-42ea-be43-fec1992d1ca7", + "SceneName": "paintball2_open", + "foundAt": "0x065147a8" + }, + { + "Name": "Lake", + "ReplicationId": "f6f7256c-e438-4299-b99e-d20bef8cf7e0", + "SceneName": "discgolf", + "foundAt": "0x065144b0" + }, + { + "Name": "Maker Room", + "ReplicationId": "a75f7547-79eb-47c6-8986-6767abcb4f92", + "SceneName": "Basement", + "foundAt": "0x06515450" + }, + { + "Name": "PSVR Room Calibration", + "ReplicationId": "f5fbd9c9-e853-4036-9d48-5f68e861af04", + "SceneName": "room_calibration", + "foundAt": "0x065151b0" + }, + { + "Name": "Paddleball", + "ReplicationId": "d89f74fa-d51e-477a-a425-025a891dd499", + "SceneName": "paddleball", + "foundAt": "0x065146ac" + }, + { + "Name": "Park", + "ReplicationId": "0a864c86-5a71-4e18-8041-8124e4dc9d98", + "SceneName": "Park", + "foundAt": "0x06515240" + }, + { + "Name": "Performance Hall", + "ReplicationId": "9932f88f-3929-43a0-a012-a40b5128e346", + "SceneName": "PerformanceHall", + "foundAt": "0x06515128" + }, + { + "Name": "PhotonServerSettings", + "ReplicationId": "", + "SceneName": "1917ff4a-b7ad-4ad6-9ea2-9642bc174b70", + "foundAt": "0x064fa5f4" + }, + { + "Name": "Propulsion", + "ReplicationId": "d9378c9f-80bc-46fb-ad1e-1bed8a674f55", + "SceneName": "Discgolf_Propulsion", + "foundAt": "0x06514524" + }, + { + "Name": "Quarry", + "ReplicationId": "1c549cfb-455b-4f5e-b15e-467702a71240", + "SceneName": "ff4c6427-7079-4f59-b22a-69b089420827", + "foundAt": "0x06516a64" + }, + { + "Name": "Quarry", + "ReplicationId": "a5ba53f2-a7cf-d3b4-8836-80fa08e02a27", + "SceneName": "ff4c6427-7079-4f59-b22a-69b089420827", + "foundAt": "0x06516dfc" + }, + { + "Name": "Quarry", + "ReplicationId": "ff4c6427-7079-4f59-b22a-69b089420827", + "SceneName": "Paintball_Castle", + "foundAt": "0x0651482c" + }, + { + "Name": "Quest For The Golden Trophy", + "ReplicationId": "91e16e35-f48f-4700-ab8a-a1b79e50e51b", + "SceneName": "Quest_additive", + "foundAt": "0x065149b4" + }, + { + "Name": "Rec Center", + "ReplicationId": "cbad71af-0831-44d8-b8ef-69edafa841f6", + "SceneName": "reccenter", + "foundAt": "0x065143b8" + }, + { + "Name": "Registration", + "ReplicationId": "8719e5b5-4ca5-463c-af8d-30c5e286d6e4", + "SceneName": "Registration", + "foundAt": "0x0651944c" + }, + { + "Name": "Registration", + "ReplicationId": "cf61556d-68fd-4288-9ae5-7a512621e569", + "SceneName": "registration", + "foundAt": "0x06515d54" + }, + { + "Name": "River", + "ReplicationId": "84545710-2bb4-4867-ad8b-24863a16d1b2", + "SceneName": "e122fe98-e7db-49e8-a1b1-105424b6e1f0", + "foundAt": "0x06516968" + }, + { + "Name": "River", + "ReplicationId": "a4d7afc1-ae7a-8e04-1bea-f7d71df384fd", + "SceneName": "e122fe98-e7db-49e8-a1b1-105424b6e1f0", + "foundAt": "0x06516d00" + }, + { + "Name": "River", + "ReplicationId": "e122fe98-e7db-49e8-a1b1-105424b6e1f0", + "SceneName": "paintball", + "foundAt": "0x0651472c" + }, + { + "Name": "Soccer", + "ReplicationId": "6d5eea4b-f069-4ed0-9916-0e2f07df0d03", + "SceneName": "soccer", + "foundAt": "0x06515030" + }, + { + "Name": "Spillway", + "ReplicationId": "7d661e29-f036-4fe9-8e9d-4b919fee638d", + "SceneName": "58763055-2dfb-4814-80b8-16fac5c85709", + "foundAt": "0x06516b5c" + }, + { + "Name": "Spillway", + "ReplicationId": "9a5a9875-67bb-f444-e804-0e0296d3c19c", + "SceneName": "58763055-2dfb-4814-80b8-16fac5c85709", + "foundAt": "0x06516ef4" + }, + { + "Name": "Spillway", + "ReplicationId": "58763055-2dfb-4814-80b8-16fac5c85709", + "SceneName": "Paintball_Dam", + "foundAt": "0x06514934" + }, + { + "Name": "StuntRunner", + "ReplicationId": "f8a71ce1-c989-4e00-9e10-3f04c241f6f3", + "SceneName": "b7281665-a715-4051-826b-8e08e69c6172", + "foundAt": "0x06519220" + }, + { + "Name": "StuntRunner", + "ReplicationId": "b7281665-a715-4051-826b-8e08e69c6172", + "SceneName": "StuntRunner_Lobby", + "foundAt": "0x06515948" + }, + { + "Name": "StuntRunner Base Room", + "ReplicationId": "882e9b96-7115-4b03-86f6-c0c9d8e22e00", + "SceneName": "StuntRunner_Baseroom", + "foundAt": "0x06515c30" + }, + { + "Name": "StuntRunner The Main Event", + "ReplicationId": "3a636bd2-f896-424c-9225-c184522c0d87", + "SceneName": "StuntRunner_additive", + "foundAt": "0x06515a60" + }, + { + "Name": "The Isle of Lost Skulls", + "ReplicationId": "7e01cfe0-820a-406f-b1b3-0a5bf575235c", + "SceneName": "Quest_Pirate1_additive", + "foundAt": "0x06514eac" + }, + { + "Name": "The Lounge", + "ReplicationId": "a067557f-ca32-43e6-b6e5-daaec60b4f5a", + "SceneName": "eventroom", + "foundAt": "0x0651462c" + }, + { + "Name": "The Rise Of JumboTron", + "ReplicationId": "acc06e66-c2d0-4361-b0cd-46246a4c455c", + "SceneName": "Quest_Scifi_Additive", + "foundAt": "0x06514b20" + }, + { + "Name": "TheMainEvent", + "ReplicationId": "cacc6e68-644a-448d-b83f-59ea690318ec", + "SceneName": "3a636bd2-f896-424c-9225-c184522c0d87", + "foundAt": "0x065192a0" + }, + { + "Name": "Warehouse", + "ReplicationId": "239e676c-f12f-489f-bf3a-d4c383d692c3", + "SceneName": "Arena_Hangar3", + "foundAt": "0x065152b0" + } +] \ No newline at end of file diff --git a/DorkNet.Server/Data/room_scene_locations.json b/DorkNet.Server/Data/room_scene_locations.json new file mode 100644 index 0000000..16804d2 --- /dev/null +++ b/DorkNet.Server/Data/room_scene_locations.json @@ -0,0 +1,106 @@ +{ + "rooms": [ + { + "Name": "River", + "ReplicationId": "84545710-2bb4-4867-ad8b-24863a16d1b2", + "Description": "e122fe98-e7db-49e8-a1b1-105424b6e1f0", + "Scenes": [ + { + "Name": "Homestead", + "ReplicationId": "f6709dc2-af81-46fc-88ba-f88f6c5035aa", + "RoomSceneLocationId": "a785267d-c579-42ea-be43-fec1992d1ca7" + }, + { + "Name": "", + "ReplicationId": "", + "RoomSceneLocationId": "\u0002\u0000\u0000\u0000\u0006\u0000\u0000\u0000" + } + ] + }, + { + "Name": "Clearcut", + "ReplicationId": "28363a22-f0bb-4e46-8346-d76e8ac634f7", + "Description": "380d18b5-de9c-49f3-80f7-f4a95c1de161", + "Scenes": [ + { + "Name": "Spillway", + "ReplicationId": "7d661e29-f036-4fe9-8e9d-4b919fee638d", + "RoomSceneLocationId": "58763055-2dfb-4814-80b8-16fac5c85709" + }, + { + "Name": "", + "ReplicationId": "", + "RoomSceneLocationId": "\u0002\u0000\u0000\u0000\b\u0000\u0000\u0000" + } + ] + }, + { + "Name": "a4d7afc1-ae7a-8e04-1bea-f7d71df384fd", + "ReplicationId": "e122fe98-e7db-49e8-a1b1-105424b6e1f0", + "Description": "", + "Scenes": [ + { + "Name": "Homestead", + "ReplicationId": "d686129f-fdf7-1954-a9f6-f0a80d1af234", + "RoomSceneLocationId": "a785267d-c579-42ea-be43-fec1992d1ca7" + }, + { + "Name": "", + "ReplicationId": "", + "RoomSceneLocationId": "\u0002\u0000\u0000\u0000\u0006\u0000\u0000\u0000" + } + ] + }, + { + "Name": "1636a319-9826-bfc4-18f8-ee1b1be6f806", + "ReplicationId": "380d18b5-de9c-49f3-80f7-f4a95c1de161", + "Description": "", + "Scenes": [ + { + "Name": "Spillway", + "ReplicationId": "9a5a9875-67bb-f444-e804-0e0296d3c19c", + "RoomSceneLocationId": "58763055-2dfb-4814-80b8-16fac5c85709" + }, + { + "Name": "", + "ReplicationId": "", + "RoomSceneLocationId": "\u0002\u0000\u0000\u0000\f\u0000\u0000\u0000" + } + ] + }, + { + "Name": "Hangar", + "ReplicationId": "8f8fcaf8-7b13-e114-880c-72ee8c9fcb78", + "Description": "239e676c-f12f-489f-bf3a-d4c383d692c3", + "Scenes": [ + { + "Name": "CyberJunkCity", + "ReplicationId": "333f409d-f9ad-46c9-b6fb-b0bbd30cb0b2", + "RoomSceneLocationId": "9d6456ce-6264-48b4-808d-2d96b3d91038" + }, + { + "Name": "", + "ReplicationId": "", + "RoomSceneLocationId": "\u0002\u0000\u0000\u0000\u0011\u0000\u0000\u0000" + } + ] + }, + { + "Name": "StuntRunner", + "ReplicationId": "f8a71ce1-c989-4e00-9e10-3f04c241f6f3", + "Description": "b7281665-a715-4051-826b-8e08e69c6172", + "Scenes": [ + { + "Name": "TheMainEvent", + "ReplicationId": "cacc6e68-644a-448d-b83f-59ea690318ec", + "RoomSceneLocationId": "3a636bd2-f896-424c-9225-c184522c0d87" + }, + { + "Name": "", + "ReplicationId": "", + "RoomSceneLocationId": "\u0002\u0000\u0000\u0000" + } + ] + } + ] +} \ No newline at end of file diff --git a/DorkNet.Server/Data/url-coverage.md b/DorkNet.Server/Data/url-coverage.md new file mode 100644 index 0000000..244a82f --- /dev/null +++ b/DorkNet.Server/Data/url-coverage.md @@ -0,0 +1,428 @@ +# URL coverage — Rec Room 2020 client vs DorkNet server + +Generated: 2026-05-09. Source: client URLs from `Cpp2IL_ISIL` ISIL dump (curated list provided to this report); server routes harvested via grep of `[HttpGet]`/`[HttpPost]`/`[HttpPut]`/`[HttpDelete]`/`[Route]` attributes across `Controllers/**/*.cs` plus the namespace-wildcard fall-throughs in `ApiNamespaceStubsController` and the global `ApiCatchAllController` / `GlobalCatchAllController`. + +## How this matrix is built + +Each client URL is matched against the server's full route table. ASP.NET Core routes resolve "specific wins over wildcard", so a route literally registered as `api/foo/v1/bar` always wins over `api/foo/{*path}`. The `Status` column reflects the route that actually wins: + +- **REAL** — a specific route exists and the body does real work: hits `DorkNetDbContext`, mutates entities, looks up presence, computes results, etc. Round-trips across sessions. +- **STUB** — a specific route exists but the body just returns `Ok(new { })`, `Ok(Array.Empty())`, an `Ack()` (`{success:true,error:""}`), or a hand-rolled empty-but-shape-correct DTO so the strict 2020 deserialiser doesn't throw. No persistence; the client thinks it succeeded but nothing is actually stored. +- **WILDCARD** — no specific route exists. The request is absorbed by `ApiNamespaceStubsController`'s namespace catch-all (`api//{*path}`), which returns either `[]` (list-typed namespaces) or `{}` (object-typed namespaces) or `{success:true}` (bug/report/sanitize namespaces). +- **MISSING** — neither a specific route nor a namespace wildcard handles it. Falls through to the very last-resort `GlobalCatchAllController` (`/{*path}`), which logs the URL and returns `200 {}`. From the client's point of view this looks like an empty object response — usually triggers a deserialiser crash on the watch unless the call site happened to be the rare one that tolerates `{}`. + +The `Handler` column points at the controller method (or wildcard route attribute) that wins. File paths can be reconstructed from the namespace prefix: `Controllers/API//...`. + +## Summary + +- Total client URLs (api/*): 156 +- Total client URLs (non-api goto/heartbeat): 7 +- REAL: 88 +- STUB: 67 +- WILDCARD: 6 +- MISSING: 2 (both covered by the global `{*path}` returning `200 {}` — neither has a specific or namespace handler) + +Note: counts are approximate because several client URLs (`api/avatar/v3/saved`, the `groups/v1/{groupId}` GET vs POST pair, `playerevents/v2`) collapse to the same handler under different verbs. They're listed separately below. + +## Coverage by API prefix + +### `api/announcement/` + +| Verb | URL | Status | Handler | +|---|---|---|---| +| GET | `api/announcement/v1/get` | STUB | `AllEndpointsController.MiscLists` → `[]` | + +### `api/avatar/` + +| Verb | URL | Status | Handler | +|---|---|---|---| +| GET | `api/avatar/v2` | REAL | `AvatarV2Controller.GetAvatar` (reads `AvatarEntity` for current player) | +| GET | `api/avatar/v2/gifts` | REAL | `AvatarGiftsController.GetGifts` | +| POST | `api/avatar/v2/gifts` | REAL | `AvatarGiftsController.GenerateGifts` (`api/avatar/v2/gifts/generate` + the bare `gifts` POST verb-overlap) — actually the bare-path POST falls through to `ApiNamespaceStubsController`'s `api/avatar/{*path}` is **not** registered (`api/avatar/` is NOT in the namespace wildcard list), so this lands on the global `{*path}` returning `{}`. Treat as STUB-via-global. | +| POST | `api/avatar/v2/gifts/consume/{giftId}` | REAL | `AvatarGiftsController.ConsumeGift` (id) — `Controllers/API/Avatar/V2/AvatarGiftsController.cs` line 99 | +| POST | `api/avatar/v2/set` | REAL | `AvatarV2Controller.SetAvatar` (writes `AvatarEntity`) | +| GET | `api/avatar/v3/saved` | REAL | `AvatarSavedOutfitsController.GetSaved` (V3) — pulls saved outfit slots from DB | +| POST | `api/avatar/v3/saved/set` | REAL | `AvatarSavedOutfitsController.SetSaved` (writes the slot) | +| GET | `api/avatar/v4/items` | REAL | `AvatarItemsController.GetItems` — returns owned-item ids from inventory tables | + +### `api/bugreporting/` + +| Verb | URL | Status | Handler | +|---|---|---|---| +| POST | `api/bugreporting/v2/reportbug` | REAL | `BugReportsController.ReportBugV2` (persists `BugReportEntity`) | + +### `api/catalog/` + +| Verb | URL | Status | Handler | +|---|---|---|---| +| GET | `api/catalog/v1/all` | STUB | `ApiNamespaceStubsController.CatalogAll` → `[]` (explicit override of the namespace wildcard so the SKU shape matches) | + +### `api/challenge/` + +| Verb | URL | Status | Handler | +|---|---|---|---| +| GET | `api/challenge/v2/getCurrent` | REAL | `ProgressionController.GetChallengeV2` — pulls active challenge for the player from the progression tables | +| POST | `api/challenge/v2/updateProgress` | REAL | `ProgressionController.UpdateChallengeProgress` — writes back delta into progression rows | + +### `api/checklist/` + +| Verb | URL | Status | Handler | +|---|---|---|---| +| GET | `api/checklist/v1/current` | REAL | `ProgressionController.GetChecklistV1` | +| POST | `api/checklist/v1/complete` | REAL | `ProgressionController.CompleteChecklistV1` (marks checklist item done in progression) | + +### `api/communityboard/` + +| Verb | URL | Status | Handler | +|---|---|---|---| +| GET | `api/communityboard/v1/current` | REAL | `CommunityBoardController.Current` — reads `data/community_board.json` via `CommunityBoardService`, returns `FeaturedPlayer` / `CurrentAnnouncement` shape | + +### `api/config/` + +| Verb | URL | Status | Handler | +|---|---|---|---| +| GET | `api/config/v2` | REAL | `Config.V2.ConfigController.GetConfig` — returns the full `RecRoomConfig` populated by `ConfigService.GetConfig(baseUrl)` | +| GET | `api/config/v1/amplitude` | STUB | `Config.V1.ConfigController.Amplitude` (returns `{ AmplitudeKey = "" }`) — also has a duplicate stub at `AllEndpointsController.Amplitude`; the V1 ConfigController route wins via more-specific match | +| GET | `api/config/v1/cohortnux/{cohortId}` | STUB | `Config.V1.ConfigController.CohortNux` — returns the empty cohort NUX shape | + +### `api/consumables/` + +| Verb | URL | Status | Handler | +|---|---|---|---| +| POST | `api/consumables/v1/updateActive` | REAL | `InventoryController.UpdateActiveConsumable` (writes the active-consumable row) | +| POST | `api/consumables/v1/consume` | REAL | `InventoryController.ConsumeConsumable` (decrements inventory count) | +| GET | `api/consumables/v1/getUnlocked` | REAL | `InventoryController.GetUnlockedConsumables` — reads the player's consumable inventory | + +### `api/equipment/` + +| Verb | URL | Status | Handler | +|---|---|---|---| +| POST | `api/equipment/v1/update` | REAL | `InventoryController.UpdateEquipment` — persists the equip-loadout selections | +| GET | `api/equipment/v2/getUnlocked` | REAL | `InventoryController.GetUnlockedEquipment` (V2) — reads the player's owned equipment list | + +### `api/gameconfigs/` + +| Verb | URL | Status | Handler | +|---|---|---|---| +| GET | `api/gameconfigs/v1/all` | STUB | `GameConfigurationsController.GameConfigsAll` (also shadowed by `AllEndpointsController.GameConfigs` — the explicit `gameconfigs/v1/all` route wins). Returns `[]`. | + +### `api/groups/` + +| Verb | URL | Status | Handler | +|---|---|---|---| +| GET | `api/groups/v1/{groupId}` | REAL | `GroupsController.GetGroup` — reads `GroupEntity` | +| GET | `api/groups/v1/memberships/{groupId}` | REAL | `GroupsController.GetMemberships` (pulls members from `GroupMembershipEntity`) | +| POST | `api/groups/v1` | REAL | `GroupsController.CreateGroup` — inserts `GroupEntity` + creator membership | +| POST | `api/groups/v1/delete/{groupId}` | REAL | `GroupsController.DeleteGroup` | +| POST | `api/groups/v1/{groupId}` | WILDCARD | No specific POST `api/groups/v1/{id}` route exists — only the GET. Falls through to `ApiNamespaceStubsController` `api/groups/{*path}` → `[]`. The 2020 client likely uses this verb to update group metadata; nothing is persisted. | +| POST | `api/groups/v1/name/{groupId}` | WILDCARD | Same as above — only the GET-by-name (`api/groups/v1/name/{name}`) is implemented. The `name/{groupId}` POST hits the namespace wildcard. | + +### `api/images/` + +| Verb | URL | Status | Handler | +|---|---|---|---| +| GET | `api/images/v1/listsaved` | STUB | `MissingEndpointsController.ImagesListSaved` → `[]` (the real saved-image list is served at `api/images/v2/saved`+ via `ImagesController`; the v1 surface is empty for the 2020 client) | +| POST | `api/images/v1/modifyaccessibility` | STUB | `MissingEndpointsController.ImagesAck` → `Ack()` | +| POST | `api/images/v1/deletesaved` | STUB | `MissingEndpointsController.ImagesDeleteSaved` → `Ack()` | +| POST | `api/images/v1/sendlink` | STUB | `MissingEndpointsController.ImagesAck` → `Ack()` | +| POST | `api/images/v1/cheer` | STUB | `MissingEndpointsController.ImagesAck` → `Ack()` | +| GET | `api/images/v2/named` | STUB | `MiscStubsController.NamedImages` → `[]` (overrides the wildcard's `{}` to satisfy the strict `ExpectListResponse` deserialiser) | +| GET | `api/images/v1/slideshow` | STUB | `MiscStubsController.SlideshowInfo` — returns `{ ValidTill = 9999-12-31, Images = [] }`; required keys present so `SlideshowInfoDTO.Deserialize` succeeds, but no actual slideshow images | + +### `api/inventions/` + +| Verb | URL | Status | Handler | +|---|---|---|---| +| GET | `api/inventions/v1/personaldetails/{playerId}` | WILDCARD | `InventionsController` only registers `api/inventions/v1/personaldetails/{id:long}` for the SAVED count by player — wait — re-reading: route IS registered (line 173 in `InventionsController.cs`). REAL. → `InventionsController.PersonalDetails` reads invention saved-count for the target player | +| GET | `api/inventions/v1?inventionId={id}` | REAL | `InventionsController.GetInvention` (`api/inventions/v1`) reads `InventionEntity` by query param | +| GET | `api/inventions/v1/update` | REAL | `InventionsController.Update` — yes, the client uses GET for "update invention metadata" (the ISIL routing tag uses `Core.Get`); writes back the invention row | +| POST | `api/inventions/v1/settags` | REAL | `InventionsController.SetTags` — replaces the `Tags` column on the invention | +| POST | `api/inventions/v1/batch` | REAL | `InventionsController.Batch` — bulk-fetch inventions by id list, returns multi-row payload | +| GET | `api/inventions/v1/details` | REAL | `InventionsController.Details` — extended invention payload with version/creator metadata | +| GET | `api/inventions/v1/versions` | REAL | `InventionsController.Versions` — list of `InventionVersionEntity` rows for an invention | +| GET | `api/inventions/v1/creatorIds` | REAL | `InventionsController.CreatorIds` — list of distinct creator player ids over the matching invention set | +| GET | `api/inventions/v1/tagfilters` | REAL | `InventionsController.TagFilters` — returns the curated tag-filter set (currently empty list, but registered as a real handler) | +| GET | `api/inventions/v1/delete` | REAL | `InventionsController.Delete` — soft-deletes an invention the caller owns | +| GET | `api/inventions/v2/publish` | REAL | `InventionsController.PublishV2` — flips `IsPublished=true`, sets `FirstPublishedAt` | +| GET | `api/inventions/v1/unpublish` | REAL | `InventionsController.Unpublish` — flips `IsPublished=false` | +| GET | `api/inventions/v1/download` | REAL | `InventionsController.Download` — streams the invention payload + bumps `NumDownloads` | +| GET | `api/inventions/v1/search` | REAL | `InventionsController.Search` — text/`@username` search over the inventions table | +| POST | `api/inventions/v1/report` | REAL | `InventionsController.Report` — persists `InventionReportEntity` | +| POST | `api/inventions/v1/cheer` | REAL | `InventionsController.Cheer` — increments `CheerCount` and inserts cheer row, fires notification | +| GET | `api/inventions/v1/mine` | REAL | `InventionsController.Mine` (delegates to the SAVED-by-creator query, returns the caller's own creations) | +| GET | `api/inventions/v3/popular` | REAL | `InventionsController.Popular` — most-cheered published inventions | +| GET | `api/inventions/v3/saved` | REAL | `InventionsController.Saved` — caller's own creations (SAVED list) | +| POST | `api/inventions/v3/save` | REAL | `InventionsController.Save` — creates a new `InventionEntity` (initial save) | +| POST | `api/inventions/v3/addversion` | REAL | `InventionsController.AddVersion` — appends `InventionVersionEntity`, bumps `CurrentVersionNumber` | + +### `api/Leaderboard/` + +| Verb | URL | Status | Handler | +|---|---|---|---| +| GET | `api/Leaderboard/v2/getPlayerRank` | STUB | `AllEndpointsController.MiscLists` (matched via the `api/Leaderboard/v1/{*path}` GET stub for v1; v2/getPlayerRank specifically falls into the `ApiNamespaceStubsController` `api/Leaderboard/{*path}` → `[]`). Functionally STUB-via-WILDCARD. | +| POST | `api/Leaderboard/v1/SetStats` | WILDCARD | No specific route. `ApiNamespaceStubsController` `api/Leaderboard/{*path}` → `[]` — the stat row isn't persisted. | + +### `api/messages/` + +| Verb | URL | Status | Handler | +|---|---|---|---| +| GET | `api/messages/v1/favoriteFriendOnlineStatus` | REAL | `Messages.V2.MessagesController.FavoriteFriendOnlineStatus` — joins relationships+presence to return a per-friend online/offline list | +| POST | `api/messages/v1/sendMultiple` | REAL | `MissingEndpointsController.MessagesSendMultiple` — fan-out: persists one `MessageEntity` per recipient | +| POST | `api/messages/v2/send` | REAL | `Messages.V2.MessagesController.Send` — inserts a `MessageEntity` and fires the SignalR notify | +| GET | `api/messages/v2/get` | REAL | `Messages.V2.MessagesController.Get` — returns the recipient inbox from `MessageEntity` rows | +| GET | `api/messages/v1/IOSGetNotificationPreferences` | REAL | `IOSNotificationPrefsController.Get` — reads per-player iOS pref settings (table-backed) | +| POST | `api/messages/v1/IOSModifyNotificationPreferences` | REAL | `IOSNotificationPrefsController.Modify` — upserts the prefs row | +| POST | `api/messages/v1/send` | STUB | No `api/messages/v1/send` POST route. Falls through to global `{*path}` → `200 {}`. The v1 send was superseded by v2; the 2020 client almost always hits v2 instead. MISSING-via-global. | + +### `api/notification/` + +| Verb | URL | Status | Handler | +|---|---|---|---| +| GET | `api/notification/hub/v1` | STUB | `Notification.V2.NotificationController.Connect` returns 410 Gone with a hint to connect to `notify.rec.net/hub/v1` instead — not the same path, so the bare `/hub/v1` GET is intercepted by the V2 controller's `[Route("api/[controller]/v2")]` only when the URL path is `api/notification/v2`. The literal client URL `api/notification/hub/v1` has no specific handler; it lands on the global `{*path}` → `200 {}`. The actual notification stream the client wants is the SignalR hub at `notify.rec.net/hub/v1`. | + +### `api/objectives/` + +| Verb | URL | Status | Handler | +|---|---|---|---| +| GET | `api/objectives/v1/myprogress` | REAL | `MiscStubsController.MyObjectiveProgress` — reads `ObjectiveProgressEntity` rows for the player and emits the strict `Objectives` + `ObjectiveGroups` shape the client's `MyProgress.Deserialize` requires | +| POST | `api/objectives/v1/updateobjective` | WILDCARD | No specific route. `ApiNamespaceStubsController` `api/objectives/{*path}` → `[]`. Per-objective progress increments aren't actually persisted — only the `cleargroup` route below writes anything. | +| POST | `api/objectives/v1/cleargroup` | REAL | `MiscStubsController.ClearGroup` — upserts an `ObjectiveProgressEntity` row keyed `group:{n}`, sets `IsCompleted=true`, returns the wire-shape the client's `ObjectiveGroupProgress.Deserialize` requires | +| POST | `api/objectives/v1/completegroup` | REAL | `ProgressionController.CompleteObjectiveGroup` — same as ClearGroup but also grants the group's reward | + +### `api/PlayerCheer/` + +| Verb | URL | Status | Handler | +|---|---|---|---| +| POST | `api/PlayerCheer/v1/create` | REAL | `MissingEndpointsController.PlayerCheerCreate` — inserts `CheerEntity` for the target player | +| POST | `api/PlayerCheer/v1/SetSelectedCheer` | REAL | `MissingEndpointsController.SetSelectedCheer` — upserts `PlayerSettings[SelectedCheer]` for the caller | + +### `api/PlayerElo/` + +| Verb | URL | Status | Handler | +|---|---|---|---| +| POST | `api/PlayerElo/v1/reportPlayerElo` | WILDCARD | `ApiNamespaceStubsController` `api/PlayerElo/{*path}` → `Object()` `{}`. Match-result Elo deltas aren't recorded. | + +### `api/playerevents/` + +| Verb | URL | Status | Handler | +|---|---|---|---| +| POST | `api/playerevents/v1/{eventId}/responses` | WILDCARD | No specific `responses` POST route — only `{eventId}/rsvp` (POST) and `respond` (POST). Falls into the global `{*path}` since `api/playerevents/` is NOT in the namespace wildcard list. Effectively MISSING-via-global. | +| GET | `api/playerevents/v2` | STUB | No specific route at the bare `api/playerevents/v2` path; only `api/playerevents/v2/delete/{id}` (DELETE) exists. Falls into the global `{*path}` → `{}`. The 2020 watch usually reads the per-player feed from `api/playerevents/v1/all` (REAL) instead. | +| GET | `api/playerevents/v2/{eventId}` | STUB | Same — no specific GET route for a single v2 event. Global `{*path}` → `{}`. | +| POST | `api/playerevents/v1/respond` | REAL | `MissingEndpointsController.PlayerEventRespond` — upserts `PlayerEventResponseEntity` (RSVP yes/no/maybe) | +| POST | `api/playerevents/v1/deleteResponse` | REAL | `MissingEndpointsController.PlayerEventDeleteResponse` — deletes the RSVP row | +| POST | `api/playerevents/v1/bulkInvite` | REAL | `PlayerEventsController.BulkInvite` — fires per-recipient invite notifications via the notification service | +| GET | `api/playerevents/v1/all` | REAL | `MiscStubsController.AllPlayerEvents` — joins `PlayerEvents` (caller-created) + `PlayerEventResponses` (caller-RSVPed) for the player's local "my events" panel | + +### `api/PlayerReporting/` + +| Verb | URL | Status | Handler | +|---|---|---|---| +| GET | `api/PlayerReporting/v2/detail` | WILDCARD | `ApiNamespaceStubsController` `api/PlayerReporting/{*path}` → `Ack()` `{success:true}`. The 2020 watch reads moderation detail; we ack so the UI doesn't crash, but no real moderation history is returned. | +| POST | `api/PlayerReporting/v2/ban` | WILDCARD | Same wildcard `Ack()`. Bans posted via this v2 path aren't persisted (the canonical persistence path is `api/PlayersBanned/v2/ban` REAL below). | +| POST | `api/PlayerReporting/v1/deviceId` | REAL | `InGameModerationController.ReportDeviceId` — persists the device ID -> player association for moderation lookups | +| POST | `api/PlayerReporting/v1/hile` | WILDCARD | `ApiNamespaceStubsController` `api/PlayerReporting/{*path}` → `Ack()`. Steam signature/anti-cheat ping; intentionally not stored. | +| POST | `api/PlayerReporting/v3/create` | REAL | `InGameModerationController.CreateReport` — inserts a `PlayerReportEntity` and notifies moderators | +| POST | `api/PlayerReporting/v3/voteToKick` | REAL | `InGameModerationController.VoteToKick` — records the kick vote and triggers room-instance kick if threshold met | +| POST | `api/PlayerReporting/v1/instantKick` | REAL | `InGameModerationController.InstantKick` — moderator-only; ejects target from current room | + +### `api/playersubscriptions/` + +| Verb | URL | Status | Handler | +|---|---|---|---| +| GET | `api/playersubscriptions/v1/my` | REAL | `PlayerSubscriptionsController.My` — returns the caller's subscription list from `SubscriptionEntity` | +| POST | `api/playersubscriptions/v1/subscribe/{playerId}` | REAL | `PlayerSubscriptionsController.Subscribe` (also covered by `MissingEndpointsController.Subscribe` — the V1 controller's specific route wins). Inserts `SubscriptionEntity`. | +| POST | `api/playersubscriptions/v1/unsubscribe/{playerId}` | REAL | `PlayerSubscriptionsController.Unsubscribe` — removes the row | + +### `api/PlayersBanned/` + +| Verb | URL | Status | Handler | +|---|---|---|---| +| POST | `api/PlayersBanned/v2/ban` | REAL | `InGameModerationController.PlayersBannedV2Ban` — persists `PlayerBanEntity` (room/global ban depending on payload) | + +### `api/quickPlay/` + +| Verb | URL | Status | Handler | +|---|---|---|---| +| GET | `api/quickPlay/v1/getandclear` | REAL | `QuickPlayController.GetAndClear` — reads + drains the caller's pending quick-play invite row | +| POST | `api/quickPlay/v1/set` | REAL | `QuickPlayController.Set` — writes a quick-play invite for the target player | + +### `api/relationships/` + +| Verb | URL | Status | Handler | +|---|---|---|---| +| GET | `api/relationships/v2/get` | REAL | `Relationships.V2.RelationshipsController.Get` — reads the friends/blocked list from `RelationshipEntity` | +| GET | `api/relationships/v2/personaldetails/{playerId}` | WILDCARD | No specific `personaldetails` route in the v2 controller. Falls into the global `{*path}` → `{}` (the namespace `api/relationships/` is NOT in the wildcard list, so the global one is the only fallback). | +| POST | `api/relationships/v2/addfriend` | REAL | `Relationships.V2.RelationshipsController.AddFriend` (POST + GET both registered) — upserts the relationship row | +| POST | `api/relationships/v2/removefriend` | REAL | `Relationships.V2.RelationshipsController.RemoveFriend` | +| POST | `api/relationships/v2/sendfriendrequest` | REAL | `Relationships.V2.RelationshipsController.SendFriendRequest` — fires a friend-request notification | +| POST | `api/relationships/v2/acceptfriendrequest` | REAL | `Relationships.V2.RelationshipsController.AcceptFriendRequest` — flips the relationship to mutual | +| POST | `api/relationships/v1/favorite` | REAL | `Relationships.V2.RelationshipsController.Favorite` — sets the IsFavorite flag | +| POST | `api/relationships/v1/unfavorite` | REAL | `Relationships.V2.RelationshipsController.Unfavorite` | +| POST | `api/relationships/v1/bulkignoreplatformusers` | STUB | `MissingEndpointsController.BulkIgnore` → `Ack()` (platform-block import is a no-op on a private server) | + +### `api/role/` + +| Verb | URL | Status | Handler | +|---|---|---|---| +| GET | `api/role/developer/{accountId}` | REAL | `RoleController.IsDeveloper` — reads the developer-flag from the player's roles | + +### `api/rooms/` + +| Verb | URL | Status | Handler | +|---|---|---|---| +| GET | `api/rooms/v1/featuredRoomGroup` | REAL | `MissingEndpointsController.FeaturedRooms` — returns the top-12 hot AG rooms wrapped in the `{Name, FeaturedRooms}` shape | +| GET | `api/rooms/v3/featured` | REAL | Same handler — `MissingEndpointsController.FeaturedRooms` (route also registered for v3) | +| GET | `api/rooms/v2/myRecent` | REAL | `Rooms.V2.RoomsController.MyRecent` (`api/rooms/v2/myrecent`) — reads the caller's recent-room history | +| GET | `api/rooms/v2/mySubscriptions` | REAL | `Rooms.V2.RoomsController.MySubscribed` (`api/rooms/v2/mysubscribed`) — rooms the caller has subscribed to | +| GET | `api/rooms/v2/baserooms` | REAL | `Rooms.V2.RoomsController.BaseRooms` — list of "base" world templates / Rec Center rooms | +| GET | `api/rooms/v1/filters` | REAL | `Rooms.V2.RoomsController.Filters` — discovery filter chips | +| GET | `api/rooms/v2/search` | WILDCARD | No `api/rooms/v2/search` route — only `api/rooms/v1/search` is registered. Lands on the global `{*path}` → `{}`. The 2020 client probably falls back gracefully or the watch uses v1/search. STUB-via-global. | +| GET | `api/rooms/v2/live` | WILDCARD | No specific route. Global `{*path}` → `{}`. The "live rooms" panel will be empty. | +| GET | `api/rooms/v1/hot` | REAL | `Rooms.V2.RoomsController.Hot` — top rooms by `HotScore` | +| GET | `api/rooms/v2/name/{roomId}` | REAL | `Rooms.V2.RoomsController.ByName` — lookup by room name (NOT id, despite the variable token) | +| GET | `api/rooms/v2/{roomId}` | REAL | `Rooms.V2.RoomsController.ById` | +| GET | `api/rooms/v4/details/{roomId}` | REAL | `Rooms.V2.RoomsController.Details` — full room blob with scenes/permissions/etc. | +| GET | `api/rooms/v2/personaldetails/{creatorId}` | REAL | `Rooms.V2.RoomsController.PersonalDetails` — caller-relative bookmark/cheer flags for the room | +| POST | `api/rooms/v1/clone` | REAL | `Rooms.V2.RoomsController.Clone` — clones the room row + scenes for the caller | +| POST | `api/rooms/v1/modify/sceneParent` | REAL | `RoomsModerationController.ModifyParentScene` — re-parents a scene tree node | +| POST | `api/rooms/v1/modify/tags` | REAL | `RoomsModerationController.ModifyTags` — replaces the room's tag CSV | +| POST | `api/rooms/v2/modify` | REAL | `RoomsModerationController.Modify` — patches mutable room metadata (name, description, image) | +| POST | `api/rooms/v2/modifyPermissions` | REAL | `RoomsModerationController.ModifyPermissions` — updates per-role permission masks | +| POST | `api/rooms/v1/roombans/{roomId}` | WILDCARD | Only the GET `api/rooms/v1/roombans/{roomId}` is implemented; the POST falls into `ApiNamespaceStubsController.List()` (because `api/rooms/` is intentionally NOT in the namespace wildcard list per the comment in that file — actually re-reading: `api/rooms/` is excluded, so the global `{*path}` is what catches this. STUB-via-global.) | +| POST | `api/rooms/v2/banfromroom` | REAL | `RoomsModerationController.BanFromRoom` — inserts `RoomBanEntity` | +| POST | `api/rooms/v1/importroombans` | REAL | `RoomsModerationController.ImportRoomBans` — bulk insert | +| POST | `api/rooms/v1/cheer` | REAL | `MissingEndpointsController.CheerRoom` — bumps `Rooms.CheerCount` (also `PlayerStateController.CheerRoom` at the per-id route `api/rooms/v1/cheer/{roomId}`) | +| POST | `api/rooms/v1/bookmark` | REAL | `MissingEndpointsController.BookmarkRoom` — upserts/removes a `RoomBookmarkEntity` based on the `Bookmark` form field | +| GET | `api/rooms/v1/datahistory/{roomId}` | REAL | `Rooms.V2.RoomsController.DataHistory` — list of historical scene snapshots | +| POST | `api/rooms/v1/datahistory/restore` | REAL | `Rooms.V2.RoomsController.RestoreDataHistory` — rolls a room's scenes back to a snapshot | +| POST | `api/rooms/v2/report` | REAL | `RoomsModerationController.Report` — files a room report | +| GET | `api/rooms/v1/modrooms` | REAL | `Rooms.V2.RoomsController.MyModerated` (route alias `api/rooms/v1/modrooms`) — rooms the caller moderates | +| GET | `api/rooms/v2/myrooms` | REAL | `Rooms.V2.RoomsController.MyRooms` | +| GET | `api/rooms/v2/mybookmarkedrooms` | REAL | `Rooms.V2.RoomsController.MyBookmarks` (also aliased at `api/rooms/v2/mybookmarks`) | +| POST | `api/rooms/v1/roomRolePermissions` | STUB | `MiscStubsController.ValidateRoomRolePermissions` → `{success:true,error:""}` (the watch's local-role assumption is what gets used; no server-side recompute) | + +### `api/royale/` + +| Verb | URL | Status | Handler | +|---|---|---|---| +| GET | `api/royale/v1/current` | REAL | `RoyaleController.GetCurrent` — current Rec Royale season metadata + leaderboard summary | +| POST | `api/royale/v2/matchcomplete` | REAL | `RoyaleController.MatchComplete` — records the match result row + grants rewards | + +### `api/purchase/` + +| Verb | URL | Status | Handler | +|---|---|---|---| +| POST | `api/purchase/v1/initiatepurchase` | REAL | `PurchaseController.Initiate` — creates a pending `PurchaseEntity`, returns the purchase id for the next step | +| POST | `api/purchase/v1/completepurchase` | REAL | `PurchaseController.Complete` — finalises the row, grants the inventory/currency | +| POST | `api/purchase/v1/processpurchase` | REAL | `PurchaseController.Process` — middle-step confirmation that the platform-side payment cleared (no real platform integration; just flips state) | +| POST | `api/purchase/v1/cancelpurchase` | REAL | `PurchaseController.Cancel` — marks the pending row cancelled | +| POST | `api/purchase/v1/cleanuppending` | REAL | `PurchaseController.CleanupPending` — sweeps dangling pending rows for the caller | + +### `api/settings/` + +| Verb | URL | Status | Handler | +|---|---|---|---| +| GET | `api/settings/v2/get` | REAL | The route `api/settings/v2/get` doesn't exist as a literal path — the controller registers the bare GET at `api/settings/v2`. The 2020 client appends `/get` and lands on the global `{*path}` → `{}`. STUB-via-global for the literal `/get` suffix. (`api/settings/v2` bare GET is REAL via `Settings.V2.SettingsController.GetSettings` reading `PlayerSettingEntity` rows.) | + +### `api/storefronts/` + +| Verb | URL | Status | Handler | +|---|---|---|---| +| GET | `api/storefronts/v1/current` | WILDCARD | No specific route. `ApiNamespaceStubsController` `api/storefronts/{*path}` → `[]`. The "current storefront" panel is empty. | +| POST | `api/storefronts/v2/buyItem` | REAL | `StorefrontsBuyController.BuyItem` — debits the caller's currency, grants the item to inventory | +| POST | `api/storefronts/v2/buyTier` | REAL | `StorefrontsBuyController.BuyTier` — purchases a season-pass tier | +| POST | `api/storefronts/v2/buyElite` | REAL | `StorefrontsBuyController.BuyElite` — Elite (premium) purchase | +| GET | `api/storefronts/v4/balance/{playerId}` | REAL | `StorefrontsController.BalanceByCurrency` — actually only `api/storefronts/v4/balance/{currencyType:int}` is registered, and `api/storefronts/v4/balance` (no segment) at line 388. The `/balance/{playerId}` shape lands on whichever of those route templates matches the int constraint. Returns the player's currency balance. | +| POST | `api/storefronts/v1/balanceAddType/{type}/{playerId}` | STUB | Only the GET form is registered (`AllEndpointsController.StorefrontBalanceAddType` → `[]`). The POST falls into `ApiNamespaceStubsController` `api/storefronts/{*path}` → `[]`. Currency-add-tier purchases via the v1 path aren't credited. | +| GET | `api/storefronts/v2/balance` | REAL | `MissingEndpointsController.StorefrontBalanceV2` — returns a single-row balance list `[{CurrencyType=2, Balance=0, Platform=0}]` (genuine zero balance, but no actual ledger lookup) | +| GET | `api/storefronts/v3/giftdropstore/{storefront}` | STUB | `ApiNamespaceStubsController.GiftDropStorefront` — returns the BaseStorefrontDTO + `StoreItems=[]` shape with required keys present so the strict deserialiser passes | +| GET | `api/storefronts/v1/season/{seasonId}` | STUB | `MissingEndpointsController.StorefrontSeason` — returns `{Tiers=[], Active=false, SeasonType=type}` | +| GET | `api/storefronts/v1/objectives` | STUB | `MissingEndpointsController.StorefrontObjectives` → `[]` | + +### `api/sanitize/` + +| Verb | URL | Status | Handler | +|---|---|---|---| +| POST | `api/sanitize/v1/purifyString` | STUB | `AllEndpointsController.Sanitize` (registered at `api/sanitize/v1` and `api/sanitize/v1/text`) doesn't catch this exact suffix; falls into `ApiNamespaceStubsController.Sanitize` which echoes back `{Text=text}`. Functionally a passthrough — text is NOT actually filtered. | +| POST | `api/sanitize/v1/requestIsStringPure` | STUB | Same — `ApiNamespaceStubsController.Sanitize` returns `{Text=text}`. The "is this string pure" boolean isn't surfaced; the client likely falls back to "treat as clean". | + +### `api/testcasemanagement/` + +| Verb | URL | Status | Handler | +|---|---|---|---| +| GET | `api/testcasemanagement/v1/testpasssummary` | REAL | `TestCaseManagementController.TestPassSummary` — reads the test-pass summary table (dev/QA tooling) | +| GET | `api/testcasemanagement/v1/testpass/{passId}` | REAL | `TestCaseManagementController.GetTestPass` | +| GET | `api/testcasemanagement/v1/testcase/{caseId}` | REAL | `TestCaseManagementController.GetTestCase` | +| POST | `api/testcasemanagement/v1/testcase/{caseId}/claim` | REAL | `TestCaseManagementController.ClaimTestCase` — assigns the case to the caller | +| POST | `api/testcasemanagement/v1/testcase/{caseId}/unclaim` | REAL | `TestCaseManagementController.UnclaimTestCase` | +| POST | `api/testcasemanagement/v1/testcase/{caseId}/status` | REAL | `TestCaseManagementController.SetTestCaseStatus` — pass/fail/skip update | + +### Non-API URLs (other subdomains) + +These all live on `match.rec.net` (locally: `match.localhost`). + +| Verb | URL | Status | Handler | +|---|---|---|---| +| POST | `goto/room/{roomName}` | REAL | `Match.GoToController.GoToRoom` — looks up the room by name, picks/creates a session, returns the connect blob | +| POST | `goto/room/{room}/{subRoom}` | REAL | `Match.GoToController.GoToSubRoom` — same but for nested rooms (Rec Center subrooms) | +| POST | `goto/event/{eventId}` | REAL | `Match.GoToController.GoToEvent` — resolves the event's room + start time, routes the player | +| POST | `goto/player/{playerId}` | REAL | `Match.GoToController.GoToPlayer` — uses `PlayerPresenceService` to find where the target is and routes the caller there | +| POST | `goto/instance/{instanceId}` | REAL | `Match.GoToController.GoToInstance` — direct join to a specific room instance | +| POST | `goto/invite/{inviteId}` | REAL | `Match.GoToController.GoToInvite` — resolves an invite to its target room/instance | +| POST | `player/heartbeat` | REAL | `Match.MatchPlayerController.Heartbeat` — refreshes the caller's `PlayerPresenceService` entry; also has a duplicate in `ApiNamespaceStubsController.Heartbeat` for the api-host case | + +## Catch-all wildcards (informational) + +Routes registered as `[Route("api//{*path}")]` in `ApiNamespaceStubsController` (`Controllers/API/Stubs/ApiNamespaceStubsController.cs`): + +List-typed namespaces (return `[]`): +- `api/announcement/{*path}` +- `api/challenge/{*path}` +- `api/checklist/{*path}` +- `api/PlayerCheer/{*path}` +- `api/catalog/{*path}` +- `api/communityboard/{*path}` +- `api/consumables/{*path}` +- `api/equipment/{*path}` +- `api/groups/{*path}` +- `api/inventions/{*path}` +- `api/Leaderboard/{*path}` +- `api/offlineinvite/{*path}` +- `api/PlayersBanned/{*path}` +- `api/objectives/{*path}` +- `api/playersubscriptions/{*path}` +- `api/storefronts/{*path}` + +Object-typed namespaces (return `{}`): +- `api/PlayerElo/{*path}` +- `api/quickPlay/{*path}` +- `api/royale/{*path}` + +Ack namespaces (return `{success:true}`): +- `api/bugreporting/{*path}` +- `api/PlayerReporting/{*path}` + +Sanitize passthrough (returns `{Text=}`): +- `api/sanitize/{*path}` (GET + POST) + +Notable namespaces NOT in the wildcard list (so unmatched URLs fall through to the global `/{*path}` returning `200 {}`): +- `api/avatar/` +- `api/messages/` +- `api/notification/` +- `api/playerevents/` +- `api/relationships/` +- `api/rooms/` — explicit comment in the source: "intentionally NOT in this list — RoomsController owns it" +- `api/settings/` +- `api/testcasemanagement/` +- `api/account/`, `api/auth/`, `api/config/`, `api/players/`, `api/role/`, `api/version/`, `api/photos/`, `api/images/`, `api/gameconfigs/`, `api/playerReputation/`, `api/role/`, `api/cards/`, etc. (these all have specific controllers covering the URLs the 2020 client actually hits) + +## Final-fallback catch-alls + +If neither a specific route nor a namespace wildcard matches, the request hits one of these last-resort handlers: + +- `Controllers/ApiCatchAllController.cs` — `[Route("/{*path}")]` constrained to API hosts. Returns `200 {}` with a log line. Mostly a defensive net so the watch never sees a 404. +- `Controllers/GlobalCatchAllController.cs` — same shape, applies to non-API hosts. +- `Controllers/Stub/StubController.cs` — `[Route("/{*path}")]` last-resort across all hosts; logs the URL+verb+host so we can see what the client tried but we never wired up. + +These are the "MISSING" status entries in the matrix above. The body returned (`200 {}`) is enough for many client call-sites that tolerate `null`, but tends to crash strict `Util.Deserialize` paths the next time the player exercises that feature. diff --git a/DorkNet.Server/Data/wardrobe_items.json b/DorkNet.Server/Data/wardrobe_items.json new file mode 100644 index 0000000..f6a3866 --- /dev/null +++ b/DorkNet.Server/Data/wardrobe_items.json @@ -0,0 +1,2092 @@ +[ + { + "name": "Hat_Knight_Helmet_Mask", + "guid": "0dd6596c-b4ea-4d55-a219-e1f44e4a376d", + "category": "head" + }, + { + "name": "TShirt_SquadGoals_Swatch", + "guid": "941c046e-4e95-49f8-a7d7-19071fcc3c94", + "category": "torso" + }, + { + "name": "ZebraWhite_Swatch", + "guid": "48abd952-214f-48b2-a8f1-1146f6f69aa2", + "category": "accessory" + }, + { + "name": "FootballHelmet_Mask", + "guid": "7660e11a-7ef0-43d3-bc02-76b3a4aab064", + "category": "head" + }, + { + "name": "SpaceMarine_Red_Swatch", + "guid": "d78643e4-c0d2-4836-bf22-b5a0e2d65b28", + "category": "accessory" + }, + { + "name": "Hibiscus_Yellow_Swatch", + "guid": "596097a8-d545-4256-959d-1d3cd96fc64c", + "category": "accessory" + }, + { + "name": "Polkadot_Mask", + "guid": "b292eb4b-07e3-4a48-99b5-3c6587a1e02e", + "category": "face" + }, + { + "name": "Artist_Hat_Mask", + "guid": "3485a30d-27f8-450b-b5aa-1ff3cd227d19", + "category": "head" + }, + { + "name": "Boxer_Red_Swatch", + "guid": "8c7b09f2-6213-4ef9-87ab-a2df72d07a22", + "category": "accessory" + }, + { + "name": "Baseball_Torso_Mask", + "guid": "ca37823a-b8f3-4414-b38b-de0dc7fa4295", + "category": "face" + }, + { + "name": "TShirt_Male_Mask", + "guid": "0440f08f-ef1d-49d8-942b-523056e8bb45", + "category": "torso" + }, + { + "name": "Aviator_Shirt_Mask", + "guid": "9009bcdf-b6f7-4dae-b2ec-895511d118ff", + "category": "torso" + }, + { + "name": "PoofyDress_Purple_Swatch", + "guid": "31ac9d4d-3d6b-48f2-85d5-dc4b8165a17a", + "category": "accessory" + }, + { + "name": "Baseball_Red_Swatch", + "guid": "de0a5a3a-8aff-4799-8269-9efa3aeb383a", + "category": "accessory" + }, + { + "name": "ButtonTop_Orange_Swatch", + "guid": "e0397982-c2c2-4733-9a40-46e18675b5af", + "category": "accessory" + }, + { + "name": "CowboyCherry_Swatch", + "guid": "624c85e5-095a-48b6-85fb-c9bbade19a8d", + "category": "accessory" + }, + { + "name": "Mobster_HerringBone_Tan_Swatch", + "guid": "531b4297-b38b-4362-9854-4f2139ea43be", + "category": "accessory" + }, + { + "name": "Punk_Pink_Swatch", + "guid": "4169bfed-8403-4590-a29d-4941275ac0b6", + "category": "accessory" + }, + { + "name": "Golfer_Shirt_Mask", + "guid": "6ce21c65-f4e9-41ac-be94-8eb1a08d5c76", + "category": "torso" + }, + { + "name": "EquestrianJacket_Maroon_Swatch", + "guid": "d6823e01-69f0-4f85-b94a-74894356a2cf", + "category": "torso" + }, + { + "name": "FighterPilot_Gloves_Orange_Swatch", + "guid": "18b52db5-0261-46a0-9214-ff510d455ed9", + "category": "accessory" + }, + { + "name": "Aviator_Gloves_Tan_Swatch", + "guid": "7fb8496f-0e6c-4e48-b8fa-5709ebbf4820", + "category": "accessory" + }, + { + "name": "Plaid_LightBlue_Swatch", + "guid": "d6edbc00-3c1d-4f49-8412-3ef8c7c5f4c2", + "category": "accessory" + }, + { + "name": "Fatigue_Swatch", + "guid": "6d703981-2734-4c45-8983-cdd5f328902f", + "category": "accessory" + }, + { + "name": "Hat_Safari_Mask", + "guid": "0dabda0d-842c-4d39-a6b9-a8ca3ec20825", + "category": "head" + }, + { + "name": "Cream_Swatch", + "guid": "7d8e55fe-3c34-4b4b-9753-0021f6cc6454", + "category": "accessory" + }, + { + "name": "ArtistHat_Red_Swatch", + "guid": "5c8f36c5-07da-49f4-af02-d23207a43216", + "category": "head" + }, + { + "name": "Dodgeball_Mask", + "guid": "944d934d-5e21-42d7-8196-fff9a81ee469", + "category": "face" + }, + { + "name": "ArcherBlack_Quiver_Swatch", + "guid": "74b009f4-e128-4c59-8a6a-4c0ba6119bde", + "category": "accessory" + }, + { + "name": "ArcherYellow_Swatch", + "guid": "6e152cc7-e93a-4408-8092-236db420b680", + "category": "accessory" + }, + { + "name": "Torso_YellowPoofyDress_Mask", + "guid": "d30e3afe-414c-4b60-8be9-6fa4156a4b65", + "category": "face" + }, + { + "name": "PurpleJacket_Red_Swatch", + "guid": "657f28d5-672a-4f27-86c6-3cb00f25bb84", + "category": "torso" + }, + { + "name": "FighterPilot_Orange_Swatch", + "guid": "cea53c7f-24a9-4e3b-80b9-fbf95738f973", + "category": "accessory" + }, + { + "name": "BabydollDress_Green_Swatch", + "guid": "c9589974-c261-485e-b571-cb2f34fb178f", + "category": "accessory" + }, + { + "name": "TennisSkirt_Mask", + "guid": "b75ef67d-00c3-4ac1-9b72-212032460294", + "category": "legs" + }, + { + "name": "Eye_MirroredGlasses_Silver_swatch", + "guid": "f841cdfa-45ba-4f09-a117-503eef34cb4b", + "category": "face" + }, + { + "name": "Beekeeper_Glove_Mask", + "guid": "1f18670d-df52-4d2b-8009-a32cf7c67b40", + "category": "face" + }, + { + "name": "2017_Member_Decal", + "guid": "75162f12-44e7-499f-bc6c-9b0612e9164b", + "category": "accessory" + }, + { + "name": "HeadFlowers_Mask", + "guid": "7d040391-03f7-4b3d-bcfd-a1278ff459c9", + "category": "face" + }, + { + "name": "Tshirt_Squad_Goals_Decal", + "guid": "6c395d04-2aa6-4a3e-8c2c-695932bc9736", + "category": "torso" + }, + { + "name": "Hat_SeaCaptainDarkDarkGold_Swatch", + "guid": "70112887-dc8d-49d5-96ed-0845204e0b58", + "category": "head" + }, + { + "name": "ScoutSkirt_Shirt_Mask", + "guid": "e65530f7-543c-43e9-bf12-b6cda2bc755c", + "category": "torso" + }, + { + "name": "PolkaBlue_Swatch", + "guid": "ad61c418-6d77-4a99-8ac5-9f10f5a3d42f", + "category": "accessory" + }, + { + "name": "Wrist_CowboyCherry_Swatch", + "guid": "17e44296-1cd9-4f8e-9aa7-3837dc69279a", + "category": "accessory" + }, + { + "name": "Yellow_Swatch", + "guid": "d2b40639-5d34-45b6-904e-6dd29030ff44", + "category": "accessory" + }, + { + "name": "WitchHunter_Purple_Swatch", + "guid": "71e5a0fe-fc8d-420b-af3b-e74160e0d1f3", + "category": "accessory" + }, + { + "name": "Plaid_Mask", + "guid": "cf119781-5bd9-4b85-9a0b-12e82e988c23", + "category": "face" + }, + { + "name": "Smuggler_Green_Swatch", + "guid": "076c96ae-9f9b-45bf-9dc7-ce73d5ddf422", + "category": "accessory" + }, + { + "name": "Scallywag_Torso_Swatch_Red", + "guid": "39f04eee-3a58-4e53-9f5e-7e995d46ed71", + "category": "accessory" + }, + { + "name": "Red_Swatch", + "guid": "8377ab96-c908-457f-9fee-b784c9a759f3", + "category": "accessory" + }, + { + "name": "TShirt_Male_LaserTag_Alpha_Mask", + "guid": "3259238f-4a9e-4e06-99de-6e7324fa0894", + "category": "torso" + }, + { + "name": "PirateCaptain_Hat_Black_Swatch", + "guid": "f9aaa7c5-59bc-486c-9a6e-27a8ac618f84", + "category": "head" + }, + { + "name": "Grey_Swatch", + "guid": "0ecb8a2a-cffc-47db-aeda-fb0684aef1e5", + "category": "accessory" + }, + { + "name": "BaseballHelmet_Red_Swatch", + "guid": "a1c51d1e-1d5f-4f8a-bc0f-6a20eddfe58a", + "category": "head" + }, + { + "name": "PirateCaptain_Black_Swatch", + "guid": "d81d0b62-e052-46c2-9252-4bc5fb219f53", + "category": "head" + }, + { + "name": "BlazerGreyDark_Swatch", + "guid": "9374bf66-2ee5-493b-8439-efce4b201904", + "category": "accessory" + }, + { + "name": "Tshirt_LaserTag_Alpha_Decal", + "guid": "e38294cf-6279-4863-9aea-1a19aeaf8476", + "category": "torso" + }, + { + "name": "PirateCaptain_Belt_Black_Swatch", + "guid": "f4ad4b9e-dd42-4711-9437-5beae51966f6", + "category": "head" + }, + { + "name": "Firefighter_Hat_Mask", + "guid": "c8ccb85e-52cd-4b3b-aadf-289819067125", + "category": "head" + }, + { + "name": "Barbershop_Torso_Mask", + "guid": "15edb8d7-2ff6-4170-a7e6-acd9e965cab5", + "category": "face" + }, + { + "name": "BabydollDress_Black_Swatch", + "guid": "e13cf33d-7588-4e2e-955e-f0a29d6380b7", + "category": "accessory" + }, + { + "name": "Barber_Torso_Formal_Swatch", + "guid": "a5925738-a451-4f25-b622-ccbfa12bd88c", + "category": "accessory" + }, + { + "name": "FlowerSwimsuit_Mask", + "guid": "3248a3ec-50d1-4c0a-96ae-25eb9d8ed622", + "category": "face" + }, + { + "name": "FlapperGlove_Black_Swatch", + "guid": "30c61e63-5e09-4a6f-8915-1e1fe4d55cf0", + "category": "accessory" + }, + { + "name": "ButtonTop_Tan_Swatch", + "guid": "49f5864f-9d40-497c-88c8-e87f64d41d74", + "category": "accessory" + }, + { + "name": "Tshirt_SciFi_Face_Decal", + "guid": "4ca3347a-dc87-412f-9eb1-799372e6fdd1", + "category": "torso" + }, + { + "name": "Scifi_Carpet_Mask", + "guid": "3290f148-1bd1-4ffd-a947-61fd0529af05", + "category": "face" + }, + { + "name": "Aviator_Goggles_Gray_Swatch", + "guid": "23428ca9-be75-4146-b400-df01d1dadac9", + "category": "face" + }, + { + "name": "Knight_Blue_Swatch", + "guid": "ed134bee-d199-43eb-98bd-206571603f40", + "category": "accessory" + }, + { + "name": "SafariTorso_Green_Swatch", + "guid": "49bbd11a-e5c9-47ff-b04d-72eb321e8a57", + "category": "accessory" + }, + { + "name": "Scallywag_Hat_Blue_Swatch", + "guid": "53dd9e44-7096-438e-a7c3-11d7c307426f", + "category": "head" + }, + { + "name": "Wizard_Wrist_Mask", + "guid": "47514b9b-0d17-4727-97ed-dab67e7fe031", + "category": "face" + }, + { + "name": "ArcherTunic_Mask", + "guid": "f88178b8-b204-4fa5-9dfd-6e438bf1e247", + "category": "face" + }, + { + "name": "Ranger_Hat_Mask", + "guid": "0272eb19-fc0e-4ad6-8db9-cc0cda2528b0", + "category": "head" + }, + { + "name": "SolidRed_Mask", + "guid": "0b2395e1-ebcc-47e9-aaf1-faf9e9cec4cd", + "category": "face" + }, + { + "name": "TShirt_Laser_Unicorn_Purple_Swatch", + "guid": "befa2f11-b9fd-457c-b1d5-9d82e90b70ce", + "category": "torso" + }, + { + "name": "Hat_SpaceMarine_Mask", + "guid": "b94411f6-5b71-48d0-ace1-ac6f63167065", + "category": "head" + }, + { + "name": "EquestrianJacket_Gray_Swatch", + "guid": "4828b50c-95b6-466a-bb25-514891d78202", + "category": "torso" + }, + { + "name": "Tshirt_Laser_Unicorn_Decal", + "guid": "770e7674-6f9b-41f8-b023-ae79738db0d3", + "category": "torso" + }, + { + "name": "AnglerHat_Blue_Swatch", + "guid": "d7f0ffd3-ccf9-465e-8afe-4d7efb78dd2c", + "category": "head" + }, + { + "name": "Lacrosse_Hat_Mask", + "guid": "7e688b30-ed13-4790-9ad8-c49885d1b7e8", + "category": "head" + }, + { + "name": "FurCoat_Black_Swatch", + "guid": "43501b0c-eaaa-4ab3-9d03-2ca61d2e8fc9", + "category": "accessory" + }, + { + "name": "Angler_Torso_Mask", + "guid": "cdbe2cc8-4fd7-451f-9b66-0df0e0e7a85e", + "category": "face" + }, + { + "name": "FlapperHeadband_Gold_Swatch", + "guid": "b6c25a3b-125b-4aab-bed0-e6e5ced2c21c", + "category": "accessory" + }, + { + "name": "Golfer_Shirt_Blue_Swatch", + "guid": "96e2b29e-e553-40f1-9eb6-8a9f7e64da39", + "category": "torso" + }, + { + "name": "Hibiscus_White_Swatch", + "guid": "ce465d53-3807-423f-baa0-bdba2a75ceb3", + "category": "accessory" + }, + { + "name": "FlapperHeadband_Black_Swatch", + "guid": "565c17e0-496d-45bd-a2c3-564e118c06cc", + "category": "accessory" + }, + { + "name": "Argyle_Mask", + "guid": "d015cae7-a905-49e4-8823-6dec069689a6", + "category": "face" + }, + { + "name": "Scout_RaccoonHat_Mask", + "guid": "f1cd10fb-3d0b-436f-a173-65bc0d88d8bf", + "category": "head" + }, + { + "name": "Blue_Swatch", + "guid": "dee70c38-7a99-4c2b-9181-665f1bf75aca", + "category": "accessory" + }, + { + "name": "Shirt_Knight_Armor_Mask", + "guid": "51a1809d-becd-42e0-8dfb-188d6f07ba1c", + "category": "torso" + }, + { + "name": "Barber_Hat_Formal_Swatch", + "guid": "631c9015-2a00-47ea-bb35-465a2ddbca5d", + "category": "head" + }, + { + "name": "DrumMajorHat_Mask", + "guid": "a8a91216-0fcc-461e-a682-95ee82509cce", + "category": "head" + }, + { + "name": "Torso_Flapper_Mask", + "guid": "a240d44f-963d-4d35-b22f-980938c7788d", + "category": "face" + }, + { + "name": "Artist_Torso_Mask", + "guid": "5a6db6ba-2a0f-4399-b537-dd78db94bf90", + "category": "face" + }, + { + "name": "Blazer_Mask", + "guid": "be2b9293-1d3c-4b1c-b4c5-fad3ab16cf54", + "category": "face" + }, + { + "name": "TShirt_Female_Mask", + "guid": "05f0ee6e-c824-470e-9178-5ed576c6fe0c", + "category": "torso" + }, + { + "name": "Shirt_Knight_Cape_Mask", + "guid": "15099be7-38b7-4b0b-ac13-1bacedbf5fb2", + "category": "head" + }, + { + "name": "Aviator_Hat_Mask", + "guid": "ff5ae41e-e446-4a96-8c11-49d85556fa81", + "category": "head" + }, + { + "name": "FighterPilot_Glove_Mask", + "guid": "7869b2a5-cd89-4033-b9f7-4f1451ecb47b", + "category": "face" + }, + { + "name": "Torso_OrangeDress_Mask", + "guid": "2c8924aa-68f8-4912-9759-18992f72f08a", + "category": "face" + }, + { + "name": "TShirt_HangInThere_Swatch", + "guid": "815138e6-b0dc-459c-9538-20f5bbd37d6b", + "category": "torso" + }, + { + "name": "Knight_Red_Swatch", + "guid": "59866578-d4e0-417b-9483-233ab108b1ac", + "category": "accessory" + }, + { + "name": "Hat_CatEars_Gray_Swatch", + "guid": "96e42c7a-2881-4299-a2fe-80c0d4d5ca26", + "category": "head" + }, + { + "name": "Boxer_Torso_Mask", + "guid": "0ec6628c-a106-4452-af82-105a3e3a9f2e", + "category": "face" + }, + { + "name": "PirateCaptain_Hat_Red_Mask", + "guid": "71fd06e3-cac8-4923-a09c-ef91183712d2", + "category": "head" + }, + { + "name": "Fatigue_Mask", + "guid": "a0271cd0-e172-4d3f-aa2f-9806f21a82d2", + "category": "face" + }, + { + "name": "Hat_CatEars_Brown_Swatch", + "guid": "713781e5-ce47-41c6-a24f-94d42a565e30", + "category": "head" + }, + { + "name": "FurCoat_Mask", + "guid": "2c0d912d-c184-4eab-8ee4-73b90ba7b0a6", + "category": "face" + }, + { + "name": "WitchHunter_Green_Swatch", + "guid": "64545dcc-a8eb-4c69-a539-78ee73a2d327", + "category": "accessory" + }, + { + "name": "Tshirt_Alpaca_Decal", + "guid": "703ff56b-560d-4ff4-8c63-d195e879a328", + "category": "torso" + }, + { + "name": "Scout_Sash_Blue_Swatch", + "guid": "67490e3b-0f9d-4bf8-84fb-54957b981c8c", + "category": "accessory" + }, + { + "name": "Hat_CatEars_Mask", + "guid": "1e3195ab-f7f6-41f4-a5ec-378d09ecdf69", + "category": "head" + }, + { + "name": "TShirt_Orange_Swatch", + "guid": "95e4cc30-cb68-473d-a395-feadf5b51512", + "category": "torso" + }, + { + "name": "SpaceMarine_Pink_Swatch", + "guid": "a36d252b-16ac-4a82-bece-de145147e7d9", + "category": "accessory" + }, + { + "name": "Torso_PinkStollDress_Mask", + "guid": "91a451c1-b285-4c48-b14d-59ded8cc006f", + "category": "face" + }, + { + "name": "Mobster_Hat_Brown_Swatch", + "guid": "1421caa9-9a0d-42c2-b847-1500ba2e483a", + "category": "head" + }, + { + "name": "ElectricianTorso_Gray_Swatch", + "guid": "b995b419-1897-4c1d-98be-83d551f7ba71", + "category": "accessory" + }, + { + "name": "White_Swatch", + "guid": "724c79a3-822c-422f-9462-a5374ee0211c", + "category": "accessory" + }, + { + "name": "2016_Member_swatch", + "guid": "5864d23d-1a86-40a6-a52c-46ee670e4508", + "category": "accessory" + }, + { + "name": "BeltDress_Green_Swatch", + "guid": "5b58d879-2bc4-47ed-a996-1af2ace16425", + "category": "accessory" + }, + { + "name": "SmallBrimHat_Mask", + "guid": "65ed43ff-dfc4-4cab-b0b9-77dc94165e24", + "category": "head" + }, + { + "name": "FootballHelmet_Orange_Swatch", + "guid": "d8d490aa-7088-4163-9305-1441367f490c", + "category": "head" + }, + { + "name": "Flower_3_Mask", + "guid": "27a5df72-4095-4837-bf24-cdd3d95a43e9", + "category": "face" + }, + { + "name": "RedFlowerDress_Gray_Swatch", + "guid": "1acb1f56-4509-449f-9c6a-065bdeaa9ee3", + "category": "accessory" + }, + { + "name": "Aviator_Jacket_Tan_Swatch", + "guid": "bea795cc-aabd-4229-84dd-49ab4f91a445", + "category": "torso" + }, + { + "name": "FootballPads_Mask", + "guid": "e17481fc-b2b6-47a5-91e5-7205f45a3247", + "category": "feet" + }, + { + "name": "Tennis_Mask", + "guid": "90e0da8b-1941-4ca8-b7e5-b4e31921873c", + "category": "face" + }, + { + "name": "Punk_WristPink_Swatch", + "guid": "f3b2330a-b981-4c7e-902f-1f57382f2388", + "category": "accessory" + }, + { + "name": "BeltDress_Black_Swatch", + "guid": "d320ad9b-ba5c-45ea-b2b8-0d3e409a9db1", + "category": "accessory" + }, + { + "name": "FighterPilot_Black_Swatch", + "guid": "8c74ee27-3d56-401b-8a8c-7868a80770e8", + "category": "accessory" + }, + { + "name": "TShirt_KO_Skull_Swatch", + "guid": "381a9f13-5b44-48e6-8a49-221422a963f5", + "category": "torso" + }, + { + "name": "TShirt_Scifi_Face_BLue_Swatch", + "guid": "bda7a57e-5227-49f4-a119-e3f166604738", + "category": "torso" + }, + { + "name": "YellowBlueDress_White_Swatch", + "guid": "bbfa08e3-8e6b-4e0f-b264-1b398d7cd44a", + "category": "accessory" + }, + { + "name": "Green_Swatch", + "guid": "f8b0cfe8-e129-4578-8bb5-f60af5d38599", + "category": "accessory" + }, + { + "name": "TennisSkirt_Yellow_Swatch", + "guid": "c5deba2a-6e35-4b13-8e94-8ba5457f39df", + "category": "legs" + }, + { + "name": "Smuggler_Grey_Swatch", + "guid": "90869e86-cbc5-4024-9d08-fcdc41c64eb5", + "category": "accessory" + }, + { + "name": "ArcherBlack_Swatch", + "guid": "9ad37173-e640-4b9e-a474-716d482b73d2", + "category": "accessory" + }, + { + "name": "Beekeeper_Shirt_Mask", + "guid": "afd88a79-7f03-40f6-bcb4-c247dd86edc4", + "category": "torso" + }, + { + "name": "LaserTag_Cyan_Swatch", + "guid": "08b7c12d-3317-4d27-9b40-34737a709454", + "category": "accessory" + }, + { + "name": "BlazerDark_Swatch", + "guid": "05ac07e1-67f0-486c-abf5-a62866475abb", + "category": "accessory" + }, + { + "name": "DrumMajorTorso_Mask", + "guid": "b0298c45-0dd1-453e-bad4-41e4656bb38e", + "category": "face" + }, + { + "name": "TrackSuit_Pink_Swatch", + "guid": "026ca50e-3a81-43cf-87f5-950687b7bbdd", + "category": "accessory" + }, + { + "name": "Golfer_Glove_Mask", + "guid": "f4cea991-fc3d-4715-bf82-8a8fb60e8799", + "category": "face" + }, + { + "name": "Aviator_Hat_Tan_Swatch", + "guid": "c6cf096c-f0cb-4500-80a0-bc2916b5a426", + "category": "head" + }, + { + "name": "MobsterWrist_HerringBone_Mask", + "guid": "c2a9c007-2748-4fa2-ab9b-0081eef792f1", + "category": "face" + }, + { + "name": "Baseball_Green_Swatch", + "guid": "e5de2e45-207c-46af-9a5e-eb01a0c621fe", + "category": "accessory" + }, + { + "name": "GreenFlower_Swatch", + "guid": "d66aa400-aa5a-4539-a25d-5f8ce94dc281", + "category": "accessory" + }, + { + "name": "Ranger_Torso_Mask", + "guid": "d21e1b6f-9c9e-42b4-bad5-201ec43161e9", + "category": "face" + }, + { + "name": "Dodgeball_Gray_Swatch", + "guid": "0ba92db2-690a-44ce-92f4-130e9503b6c1", + "category": "accessory" + }, + { + "name": "Aviator_Gloves_Black_Swatch", + "guid": "97bbb384-b37e-4ea1-902c-88fbd6854ab5", + "category": "accessory" + }, + { + "name": "MobsterTorso_HerringBone_Mask", + "guid": "33ee376a-586e-4aff-a7e4-4213445a3e92", + "category": "face" + }, + { + "name": "Birthday01_Swatch", + "guid": "922f229c-124f-4f07-b4e3-19138420ca19", + "category": "accessory" + }, + { + "name": "Wizard_White_Swatch", + "guid": "d3a9b576-5433-4305-bd96-b08820c09212", + "category": "accessory" + }, + { + "name": "PurpleJacket_Mask", + "guid": "3ba69a39-a4e7-4505-bae7-8db31360ec1c", + "category": "torso" + }, + { + "name": "Quest_Crown_Decal", + "guid": "f5270130-6634-4052-aa3e-9849ddf5314e", + "category": "head" + }, + { + "name": "Lacross_Stripe_Mask", + "guid": "da36bbcc-2a9a-4ba2-9e4a-6f52c9b1ef74", + "category": "face" + }, + { + "name": "Happy_Tree_Decal", + "guid": "d461ca71-45c9-415e-8e09-ba93e8d73450", + "category": "accessory" + }, + { + "name": "Ranger_Shirt_Green_Swatch", + "guid": "b415fe60-3e9d-4f4d-a53a-ca15eac6af7d", + "category": "torso" + }, + { + "name": "Helixx_swatch", + "guid": "f8635fe2-90bc-42c8-9bbe-3387b5157e18", + "category": "accessory" + }, + { + "name": "Scifi_Carpet_Swatch", + "guid": "0809a5cf-a0f3-4614-9f98-71518524518a", + "category": "accessory" + }, + { + "name": "Golfer_Hat_Mask", + "guid": "ddf429fd-a883-48a5-869b-bc12e44d30bf", + "category": "head" + }, + { + "name": "Teal_Swatch", + "guid": "90bc2d31-1715-4d1a-813a-7c57e66cb017", + "category": "accessory" + }, + { + "name": "ElectricianTorso_Yellow_Swatch", + "guid": "b539777f-48b2-44a2-9d63-17ac703e4d57", + "category": "accessory" + }, + { + "name": "WitchHunterShirt_Mask", + "guid": "0f49ac81-63bd-4845-8bdc-1944181a8aa3", + "category": "torso" + }, + { + "name": "Scallywag_Torso_Swatch_Blue", + "guid": "85eb7f47-bb31-4c66-a8b3-562d35028e6c", + "category": "accessory" + }, + { + "name": "Charcoal_Swatch", + "guid": "1b1d08f2-12ca-43dd-a44f-ea2820b919b4", + "category": "accessory" + }, + { + "name": "Paintball_Red_Swatch", + "guid": "2913bd93-bf35-4bdf-b83a-d40a10213adc", + "category": "accessory" + }, + { + "name": "Jazzercise_Black_Swatch", + "guid": "3b2cfc85-4e2a-4e0d-b738-c296ae8692f8", + "category": "accessory" + }, + { + "name": "FieldHockey_Blue_Swatch", + "guid": "7b0d6661-e856-4ec1-ab90-d89c28a70826", + "category": "accessory" + }, + { + "name": "ArcherTan_Glove_Swatch", + "guid": "d5987fee-e3cc-4336-853a-5df57f73df05", + "category": "accessory" + }, + { + "name": "BicycleJacket_Black_Swatch", + "guid": "83522c12-9549-4d74-8c87-bae210bcd2bf", + "category": "torso" + }, + { + "name": "Hat_FlapperHeadband_Mask", + "guid": "d9be3a98-9da9-419e-9dce-fe58a3ad9c4e", + "category": "head" + }, + { + "name": "TShirt_Scifi_Face_Pink_Swatch", + "guid": "4d52bbd6-ef74-45ae-a6c2-bde10ab8eb16", + "category": "torso" + }, + { + "name": "FlowerSwimsuit_Green_Swatch", + "guid": "cf515c87-3c0a-4443-9f63-5ea1bdbaa61a", + "category": "accessory" + }, + { + "name": "Beekeeper_Shirt_Gold_Swatch", + "guid": "1919d319-6cce-4500-8766-15fed6a13fa8", + "category": "torso" + }, + { + "name": "Argyle_Brown_Swatch", + "guid": "bf82f2f6-9af8-431e-a296-0890dea48ba7", + "category": "accessory" + }, + { + "name": "Paintball_Blue_Swatch", + "guid": "178c6beb-c737-434c-a53b-d14f438f6a98", + "category": "accessory" + }, + { + "name": "Tshirt_KO_Skull_Decal", + "guid": "9e782751-71c6-4208-87da-3a3cffa6a4f0", + "category": "torso" + }, + { + "name": "Torso_SeaCaptain_Mask", + "guid": "8ec5de40-73fc-4f09-9bf3-ddf4701107bc", + "category": "head" + }, + { + "name": "BasketballShirt_Purple_Swatch", + "guid": "4effd67c-a52f-4b41-a5b0-04287f91a7bc", + "category": "torso" + }, + { + "name": "Purple_Swatch", + "guid": "c72911d0-453f-4732-b1bd-dad520220a06", + "category": "accessory" + }, + { + "name": "Scout_Shirt_Mask", + "guid": "5814a331-6155-4df4-b921-3ffc6059e44c", + "category": "torso" + }, + { + "name": "Angler_Blue_Swatch", + "guid": "6e0f3af8-297f-4ea7-8f9e-4a1bc57d3e35", + "category": "accessory" + }, + { + "name": "Hat_CatEars_White_Swatch", + "guid": "c1e430c5-fd8d-4032-9e3f-8d87cc8784da", + "category": "head" + }, + { + "name": "Smuggler_Dark_Swatch", + "guid": "e821aa74-2d47-4388-bcfb-1893b8a83a52", + "category": "accessory" + }, + { + "name": "ScoutSkirt_Shirt_Red_Swatch", + "guid": "2bb65868-ee12-4871-b50d-64bbc9066f0b", + "category": "torso" + }, + { + "name": "Artist_Blue_Swatch", + "guid": "8c94e45c-6605-4b4f-9ba4-2c6c6e557228", + "category": "accessory" + }, + { + "name": "Knight_Black_Swatch", + "guid": "e1f49b74-b071-4bd1-9c4b-af36d24d45c5", + "category": "accessory" + }, + { + "name": "Hat_CatEars_Gold_Swatch", + "guid": "37f8378b-b98c-477a-b8c9-9340772800b5", + "category": "head" + }, + { + "name": "TShirt_KO_Skull_Pink_Swatch", + "guid": "fe304452-b57b-45bc-9c1c-b85b70bcec12", + "category": "torso" + }, + { + "name": "BeltDress_Yellow_Swatch", + "guid": "d61711fd-589a-4669-b991-9408a58fffd9", + "category": "accessory" + }, + { + "name": "ArcherYellow_Glove_Swatch", + "guid": "47da86b9-4e6e-423c-b8c4-ea6bf8472509", + "category": "accessory" + }, + { + "name": "PirateCaptain_Belt_Mask", + "guid": "9b2102f0-e16b-464f-9f1c-f068de52bb89", + "category": "head" + }, + { + "name": "Luchador_Torso_Mask", + "guid": "7ef7d0cf-99f1-4872-9d3d-8230cc9fa368", + "category": "face" + }, + { + "name": "HockeyShirt_Red_Swatch", + "guid": "b1ea158d-1663-4db1-bb09-9847f6e9d1e6", + "category": "torso" + }, + { + "name": "YellowBlueDress_Purple_Swatch", + "guid": "830be2fa-60a5-48cc-931f-34b670eae4bd", + "category": "accessory" + }, + { + "name": "Electrician_Torso_Mask", + "guid": "daa38a31-fb7b-4e26-a2f5-728d6dcec81a", + "category": "face" + }, + { + "name": "Torso_Cowboy_Mask", + "guid": "98553538-cafd-4df9-8f37-bb0a4bb478a5", + "category": "face" + }, + { + "name": "Firefighter_Hat_Yellow_Swatch", + "guid": "4c26d1e7-2382-4478-b9c4-a068ed01a607", + "category": "head" + }, + { + "name": "Aviator_Goggles_Mask", + "guid": "556f876a-70da-4104-b792-fca8ad67be0c", + "category": "face" + }, + { + "name": "Scallywag_Bracelet_Red_Swatch", + "guid": "62bd5dda-08da-4528-83c1-f0ca3812dc6e", + "category": "accessory" + }, + { + "name": "Hat_Hockey_Mask", + "guid": "7c4d447c-6594-4bd8-9510-12ad683ab116", + "category": "head" + }, + { + "name": "DrumMajorGlove_Mask", + "guid": "6b38ab79-142b-446e-978c-ad829ade8823", + "category": "face" + }, + { + "name": "Hat_Cowboy_Mask", + "guid": "54778d93-2cff-48db-b25b-65c08b0e5be8", + "category": "head" + }, + { + "name": "Scout_RaccoonHat_Gray_Swatch", + "guid": "926ae943-76cb-46dd-9c66-a6ee44ea9194", + "category": "head" + }, + { + "name": "Scallywag_Hat_Mask", + "guid": "a4903da3-249d-4f0a-a746-b547fda371ff", + "category": "head" + }, + { + "name": "Denim_Mask", + "guid": "f600037d-c9c0-43fa-b45b-02f456f9dd5f", + "category": "face" + }, + { + "name": "DrumMajor_Orange_Swatch", + "guid": "82de1ca3-67d6-454d-84fb-0eeece71fcbc", + "category": "accessory" + }, + { + "name": "LaserTag_Wrist_Mask", + "guid": "3b825377-d2eb-41e4-a736-b107f88f62d6", + "category": "face" + }, + { + "name": "PurpleJacket_Blue_Swatch", + "guid": "b42d903b-b393-4f49-af5e-38d0fb6c42b0", + "category": "torso" + }, + { + "name": "Angler_Tan_Swatch", + "guid": "cac27853-e2df-4d77-b3fd-af3bc0813f01", + "category": "accessory" + }, + { + "name": "MobsterWrist_Fashion_Gold_Mask", + "guid": "05cff8b8-d437-485a-b4b8-41d35becd022", + "category": "face" + }, + { + "name": "Scout_Sash_Brown_Swatch", + "guid": "639f5e2c-6fbd-4ab4-aa10-7772e0932798", + "category": "accessory" + }, + { + "name": "Torso_SeaCaptainBlueWhite_Swatch", + "guid": "857fdcd8-704f-43d0-92b9-bca26619b153", + "category": "head" + }, + { + "name": "Scallywag_Torso_Stripped_Mask", + "guid": "9f1dd474-34a3-47eb-bb72-dbfc8d2d6ecf", + "category": "face" + }, + { + "name": "Zebra_Mask", + "guid": "b78008e8-abbd-4ece-be34-9a911f721fcc", + "category": "face" + }, + { + "name": "Smuggler_Cherry_Swatch", + "guid": "fed760b2-15e2-46b2-b1e3-cc3c8d780d5d", + "category": "accessory" + }, + { + "name": "Beekeeper_Hat_Mask", + "guid": "e4b53a9a-ac95-49ae-bf68-615bb23fa075", + "category": "head" + }, + { + "name": "HeadBand_Royal_Mask", + "guid": "56a92c8d-af53-413e-929e-4a9a3cfad780", + "category": "face" + }, + { + "name": "AnglerHat_Gray_Swatch", + "guid": "225209dc-3afc-40cf-858e-89f7afa00c0d", + "category": "head" + }, + { + "name": "BaseballHelmet_Green_Swatch", + "guid": "9967ce54-c2b7-437a-946e-11f8ee6d6fdd", + "category": "head" + }, + { + "name": "Glasses_Smuggler_Mask", + "guid": "fe01b6ed-3c36-47b6-bcfa-47864ef9d610", + "category": "face" + }, + { + "name": "Scallywag_Bracelet_Blue_Swatch", + "guid": "cdca593a-8840-4140-9145-fe962732040c", + "category": "accessory" + }, + { + "name": "Wrist_Smuggler_Mask", + "guid": "8ffce0d6-d03e-4097-bc46-09b8894bc1d3", + "category": "face" + }, + { + "name": "CowboyDark_Swatch", + "guid": "192ba73d-6990-42da-8fe0-914c2a7181eb", + "category": "accessory" + }, + { + "name": "BikeHelmet_Mask", + "guid": "5fd9e83a-cfaf-4c98-bd31-91a7484c26dd", + "category": "head" + }, + { + "name": "Torso_ButtonTop_Mask", + "guid": "dafa658e-753b-46cb-bd85-85c1de5e6ea7", + "category": "face" + }, + { + "name": "FlowerSwimsuit_Pink_Swatch", + "guid": "27680277-aec1-41be-a476-9f51cf775580", + "category": "accessory" + }, + { + "name": "Artist_Red_Swatch", + "guid": "17b16982-871c-476a-860f-f6953c89ee8c", + "category": "accessory" + }, + { + "name": "2016_Member_Decal", + "guid": "167ba0d4-4fac-4143-b5ae-1dbd8e02dc76", + "category": "accessory" + }, + { + "name": "Scout_Sash_Red_Swatch", + "guid": "b520f3bc-599a-498c-8a4f-97cc25aa741f", + "category": "accessory" + }, + { + "name": "Glove_Safari_Mask", + "guid": "fa9a3d8e-16c9-4a7e-9166-3226e0d13c39", + "category": "face" + }, + { + "name": "DrumMajor_Green_Swatch", + "guid": "fe5288bc-0e02-4ea3-b17d-dee0e576979d", + "category": "accessory" + }, + { + "name": "CheerDress_Blue_Swatch", + "guid": "faedf215-289b-40f7-90cc-f5b98517d133", + "category": "accessory" + }, + { + "name": "SpaceMarine_Cyan_Swatch", + "guid": "c8cc9589-4831-4e73-a865-ef8c139a2ba7", + "category": "accessory" + }, + { + "name": "Denim_Swatch", + "guid": "55901f12-d5b5-4fa8-b4c8-e479689ee39d", + "category": "accessory" + }, + { + "name": "FootballPads_Orange_Swatch", + "guid": "32dde6dc-e3dc-43ee-9b7c-987bc6778c17", + "category": "feet" + }, + { + "name": "SFVRCC_swatch", + "guid": "14afd295-bcd0-4595-80dc-d477618b9ab9", + "category": "accessory" + }, + { + "name": "BikeJersey_Mask", + "guid": "1695608b-e9cc-4a03-b56d-0f09d2804379", + "category": "face" + }, + { + "name": "Torso_SeaCaptainBlueDark2_Swatch", + "guid": "3df5d13b-ba77-4b51-90b2-7a946e4d0be7", + "category": "head" + }, + { + "name": "PirateCaptain_Hat_Red_Swatch", + "guid": "17040a88-87f1-4613-b439-3316e33dbd48", + "category": "head" + }, + { + "name": "CollarDress_Pink_Swatch", + "guid": "6564acf1-4d70-4f92-92ac-08e2b76dbb6b", + "category": "accessory" + }, + { + "name": "Torso_SeaCaptainDark_Swatch", + "guid": "b9f51861-ad10-4878-a9b1-d849bccf532a", + "category": "head" + }, + { + "name": "PurpleJacket_Black_Swatch", + "guid": "8cab8cc6-9a2d-434c-8b72-e66edbdd223c", + "category": "torso" + }, + { + "name": "Hat_SeaCaptainFushia_Swatch", + "guid": "2738ab5f-8c94-46b3-bac6-4a8111c41c21", + "category": "head" + }, + { + "name": "Luchador_Red_Swatch", + "guid": "916384ca-e209-42a0-bd64-c64f5a40b56f", + "category": "accessory" + }, + { + "name": "FlapperGlove_Gold_Swatch", + "guid": "2cf2b644-3fa6-449c-9fe8-d0ab917f3106", + "category": "accessory" + }, + { + "name": "BlueJacket_Mint_Swatch", + "guid": "cd5d7285-202d-42d0-b93f-04245875793e", + "category": "torso" + }, + { + "name": "FootballPads_Dark_Swatch", + "guid": "88c3ed05-ce41-4a9d-a4a7-8a428d240b3f", + "category": "feet" + }, + { + "name": "Pink_Swatch", + "guid": "cbe29e9f-f2ac-47fb-97e1-8bad16abb89d", + "category": "accessory" + }, + { + "name": "Aviator_Hat_Black_Swatch", + "guid": "a8de80d3-286a-4fdc-bed2-75d52f8c0964", + "category": "head" + }, + { + "name": "Aviator_Goggles_Black_Swatch", + "guid": "7b82c3c5-fd1f-45ec-b83f-e32668aa8dce", + "category": "face" + }, + { + "name": "Mobster_Fashion_Gold_Swatch", + "guid": "46970903-9799-4d25-b90a-7162c1f5dbef", + "category": "accessory" + }, + { + "name": "Golfer_Shirt_Grey_Swatch", + "guid": "4c845dbb-26c3-48a1-b562-e28a7416b154", + "category": "torso" + }, + { + "name": "Royal_Swatch", + "guid": "6dd95046-acf8-42fe-ab78-80a334096a9d", + "category": "accessory" + }, + { + "name": "GoldYellow_Swatch", + "guid": "67bcca75-4ab1-4964-8688-9908c464d355", + "category": "accessory" + }, + { + "name": "Hat_SeaCaptainDarkGold_Swatch", + "guid": "f5403c89-9769-479b-8a05-c71607f9b189", + "category": "head" + }, + { + "name": "LaserTag_Torso_Mask", + "guid": "959ed05b-0215-4bbe-bb3d-64e4448d676b", + "category": "face" + }, + { + "name": "DrumMajor_Purple_Swatch", + "guid": "54d3c589-30ba-44c1-a77c-8f9e16eb0b20", + "category": "accessory" + }, + { + "name": "Hat_TopHat_Mask", + "guid": "ccf1ccc1-e229-4157-bb74-f2cdef01e547", + "category": "head" + }, + { + "name": "PirateCaptain_Belt_Red_Swatch", + "guid": "0f71b9a2-dd88-435c-a571-4503d12dcf2d", + "category": "head" + }, + { + "name": "ArcherBlack_Glove_Swatch", + "guid": "2be2f040-68e4-44dd-9099-29f11110f3a4", + "category": "accessory" + }, + { + "name": "Barbershop_Hat_Mask", + "guid": "83158223-e268-4036-8a6a-218a8430eb2c", + "category": "head" + }, + { + "name": "Wizard_Torso_Mask", + "guid": "8768aa33-b014-4062-b323-b146dd7d78d2", + "category": "face" + }, + { + "name": "JerseyOutline_Mask", + "guid": "d2a692e6-e1a9-4cfe-8154-10b52be7f8c8", + "category": "face" + }, + { + "name": "Hat_HairBow_Mask", + "guid": "e0186718-4a18-434b-b96e-0c9c912f0d1f", + "category": "head" + }, + { + "name": "Basketball_Shirt_Mask", + "guid": "54a5c055-f757-4c00-a1e1-8b0b6552c608", + "category": "torso" + }, + { + "name": "Equestrian_Jacket_Mask", + "guid": "d344b8cc-85a8-4ace-9f92-38c84f396e99", + "category": "torso" + }, + { + "name": "Barber_Torso_Green_Swatch", + "guid": "c64ac7a9-7fd4-4587-8626-0413a190b706", + "category": "accessory" + }, + { + "name": "FighterPilot_Blue_Swatch", + "guid": "e37b07d1-6b9d-49b9-94ca-1c728e228cfe", + "category": "accessory" + }, + { + "name": "MobsterTorso_Fashion_Gold_Mask", + "guid": "c2052d8c-fbcd-4462-8f08-bcf5b3dfde2f", + "category": "face" + }, + { + "name": "AnglerHat_Tan_Swatch", + "guid": "3f6c8bd2-2c1d-4c8c-a9de-16600fee9645", + "category": "head" + }, + { + "name": "CheerDress_Red_Swatch", + "guid": "1773d5d8-4714-4721-b30c-97e25d6f2a5a", + "category": "accessory" + }, + { + "name": "FlatCap_Hat_Mask", + "guid": "73fc2e2a-a036-410b-bafa-3a07658245b5", + "category": "head" + }, + { + "name": "Golfer_Glove_Blue_Swatch", + "guid": "32a5e42d-2b60-40b1-a145-ed96fc91af8e", + "category": "accessory" + }, + { + "name": "TShirt_RECS_Logo_Blue_Swatch", + "guid": "bde08851-a9e4-4faa-8f04-a0a661672472", + "category": "torso" + }, + { + "name": "Scout_Shirt_Blue_Swatch", + "guid": "0d2830e7-8eb0-4fa3-99bc-7c80f01dd991", + "category": "torso" + }, + { + "name": "DrumMajorGlove_Purple_Swatch", + "guid": "8053b5e5-0f67-4329-a45d-1ee9fc830820", + "category": "accessory" + }, + { + "name": "Shirt_SpaceMarine_Mask", + "guid": "7ac81d60-05b8-4a85-982c-f69213a93818", + "category": "torso" + }, + { + "name": "SFVRCC_Decal", + "guid": "a74e6c0d-1300-4928-9141-977a711b71c4", + "category": "accessory" + }, + { + "name": "BlueJacket_Torso_Mask", + "guid": "0f36bb97-c61b-4281-929f-ff1d0d11be86", + "category": "torso" + }, + { + "name": "Wizard_Hat_Mask", + "guid": "abdf525b-b871-41be-a1c8-810f49740ad4", + "category": "head" + }, + { + "name": "Wrist_SpaceMarine_Mask", + "guid": "7634b7ef-9607-497a-b41f-c0191dc75998", + "category": "face" + }, + { + "name": "ElectricianTorso_Green_Swatch", + "guid": "67fc9269-8dd8-4d6f-b594-c77e716f2482", + "category": "accessory" + }, + { + "name": "TrackSuit_Mask", + "guid": "adae82eb-a4b9-4541-acf2-76cea839a593", + "category": "face" + }, + { + "name": "Tshirt_HangInThere_Decal", + "guid": "8f19d7f9-4f28-48ab-a1c5-d8f4f788514c", + "category": "torso" + }, + { + "name": "DrumMajorGlove_Orange_Swatch", + "guid": "b444ed0a-eab5-4bd6-af34-d080de74a149", + "category": "accessory" + }, + { + "name": "Punk_WristRed_Swatch", + "guid": "baffdefe-de21-4551-9330-b50873965bdd", + "category": "accessory" + }, + { + "name": "PirateCaptain_Wrist_Red_Mask", + "guid": "7a7bc314-4ffd-4ac8-9ed6-6ca73589c132", + "category": "head" + }, + { + "name": "Punk_Green_Swatch", + "guid": "0724b166-9ace-46ff-a191-b13f135b00a8", + "category": "accessory" + }, + { + "name": "ArcherRed_Glove_Swatch", + "guid": "0c433086-9501-48c4-8d0e-4474eb420c61", + "category": "accessory" + }, + { + "name": "Tshirt_KO_Skull_Pink_Decal", + "guid": "6a007c73-d930-408a-93df-041a715e0dd4", + "category": "torso" + }, + { + "name": "Wrist_Knight_Gauntlet_Mask", + "guid": "fd67ec40-f898-4df6-b846-8bf350f362b8", + "category": "face" + }, + { + "name": "Lacrosse_Stripe_Purple_Swatch", + "guid": "e3454bc7-c88d-4958-8b81-db3fe5472a1e", + "category": "accessory" + }, + { + "name": "SciFi_CarpetTie_Mask", + "guid": "8212812b-e7e7-4f6d-aa27-d1112af45473", + "category": "face" + }, + { + "name": "PirateCaptain_Torso_Mask", + "guid": "52053afd-9f12-4fec-a358-7eb3fe8c19bb", + "category": "head" + }, + { + "name": "Scallywag_Bracer_Mask", + "guid": "ce3782aa-56cc-40d7-9019-8ce714cb7748", + "category": "face" + }, + { + "name": "CheerDress_Mask", + "guid": "93987d32-7a6e-45df-af56-76f912969ce7", + "category": "face" + }, + { + "name": "TShirt_RECS_Logo_Green_Swatch", + "guid": "5e7a48ed-8f08-4990-a3ae-05722c9f59a8", + "category": "torso" + }, + { + "name": "Wrist_Cowboy_Mask", + "guid": "8eef8837-9141-4088-835a-b2f47f4126f8", + "category": "face" + }, + { + "name": "HorizontalStripe3_Mask", + "guid": "9c03f381-7357-4d0f-8cda-8737d4c43d25", + "category": "face" + }, + { + "name": "PirateCaptain_Torso_Striped_Mask", + "guid": "00ac4e37-6eac-4ed5-b46c-2c075038bc3f", + "category": "head" + }, + { + "name": "Wizard_Green_Swatch", + "guid": "357eb160-360b-4a63-840d-65d11cc8ffc2", + "category": "accessory" + }, + { + "name": "Angler_Gray_Swatch", + "guid": "97938f4c-52b8-4c85-8575-856654666316", + "category": "accessory" + }, + { + "name": "FootballHelmet_Dark_Swatch", + "guid": "61ff7a02-50ff-49ea-b95e-c31b95a30c8e", + "category": "head" + }, + { + "name": "Torso_BabydollDress_Mask", + "guid": "cb9e9e05-c481-4397-86e7-c2111bc0e817", + "category": "face" + }, + { + "name": "LaserTagVisor_Cyan_Swatch", + "guid": "426d0592-f8e4-4bc6-9fe1-a62b91fd57d5", + "category": "accessory" + }, + { + "name": "Torso_SeaCaptainWhiteDark_Swatch", + "guid": "b5d62931-e756-4a8a-8f3b-760beba49319", + "category": "head" + }, + { + "name": "Wrist_FlapperGlove_Mask", + "guid": "0e42108e-3b08-49b4-86c0-7984719e8380", + "category": "face" + }, + { + "name": "LightBlue_Swatch", + "guid": "484b6c13-af22-4ad5-8c43-34c0de095d49", + "category": "accessory" + }, + { + "name": "Aviator_Gloves_Mask", + "guid": "5659ce2c-9cb0-49da-8c39-4b7df0f8deec", + "category": "face" + }, + { + "name": "Hat_SeaCaptain_Mask", + "guid": "a4e51b6b-fdfd-47dc-9d0e-f26f383e03e9", + "category": "head" + }, + { + "name": "CowboyLight_Swatch", + "guid": "5e00a832-3f25-4dd3-84d2-b6ed6a4b4c37", + "category": "accessory" + }, + { + "name": "Happy_Tree_Swatch", + "guid": "ba6b6e1a-a09a-4ba0-9523-552869f03336", + "category": "accessory" + }, + { + "name": "Smuggler_Yellow_Swatch", + "guid": "002b07bb-4256-44ce-8ca4-25510e70b6b5", + "category": "accessory" + }, + { + "name": "BlazerMidnightBlue_Swatch", + "guid": "6f2e74bf-1e95-463d-97db-d5d1a53b2c28", + "category": "accessory" + }, + { + "name": "BicycleJacket_Blue_Swatch", + "guid": "c5884778-a87f-4612-9717-86fbb65d440f", + "category": "torso" + }, + { + "name": "Mobster_WhiteHatHacker_Swatch", + "guid": "40283127-2ffc-4989-82f6-29c3381bf9e2", + "category": "head" + }, + { + "name": "Torso_SeaCaptainBlackDark_Swatch", + "guid": "6145292a-9002-41c6-8cd5-43679b6f542f", + "category": "head" + }, + { + "name": "Mobster_Hat_Gray_Swatch", + "guid": "390a476a-5545-48eb-955c-1bf1a16012bf", + "category": "head" + }, + { + "name": "SpaceMarine_Light_Swatch", + "guid": "68273c9e-96d6-4ddb-aaab-d99229e864d5", + "category": "accessory" + }, + { + "name": "Shirt_Punk_Mask", + "guid": "d738ad4c-c9b5-41e6-8979-d586c964fb6c", + "category": "torso" + }, + { + "name": "Aviator_Jacket_Black_Swatch", + "guid": "da185fbb-ad2b-4f30-87a2-491f24b78ebb", + "category": "torso" + }, + { + "name": "Scifi_Carpet_TopHat_Swatch", + "guid": "bd4a84e2-b67a-4269-a26a-17fb23ddb09e", + "category": "head" + }, + { + "name": "Torso_Jazzercise_Mask", + "guid": "f6f26e2b-657b-454a-8663-55d6b9b6e50c", + "category": "face" + }, + { + "name": "RedFlowerDress_Yellow_Swatch", + "guid": "e98c3f98-7844-4d6e-a21c-05027033c18c", + "category": "accessory" + }, + { + "name": "Eye_MirroredGlasses_Mask", + "guid": "9c55b609-b7dc-4070-a3ad-6351a0054a06", + "category": "face" + }, + { + "name": "Plaid_Red_Swatch", + "guid": "827a1538-51bb-4fef-99c1-7f1bebd9866c", + "category": "accessory" + }, + { + "name": "Scout_Sash_Mask", + "guid": "66cc536d-8cb5-4262-b7f0-49a33b97ea10", + "category": "face" + }, + { + "name": "ArcherGlove_Mask", + "guid": "7b187c06-7ba2-4f3c-bca9-fdf98146f385", + "category": "face" + }, + { + "name": "Wizard_Black_Swatch", + "guid": "7afe1bb0-5ca9-4263-8f85-49d3a18353c7", + "category": "accessory" + }, + { + "name": "Wrist_CowboyLight_Swatch", + "guid": "c0c33a35-494c-4a1a-aa53-d27d9b9ef5a4", + "category": "accessory" + }, + { + "name": "FlowerSwimsuit_Gray_Swatch", + "guid": "5a96ac4d-03c3-4214-9dcb-c2044ff28f2e", + "category": "accessory" + }, + { + "name": "SafariTorso_Mask", + "guid": "84c016ad-133f-45e3-a412-cac35612540b", + "category": "face" + }, + { + "name": "BabydollDress_Pink_Swatch", + "guid": "da6d0ced-3d43-4878-a868-925f2ed6fd5b", + "category": "accessory" + }, + { + "name": "Barber_Wrist_Formal_Swatch", + "guid": "bb01b51e-a95f-441e-8c52-abc3aeed7145", + "category": "accessory" + }, + { + "name": "PirateCaptain_Red_Swatch", + "guid": "556141f7-6fbc-4065-9faa-8d8240a93d7f", + "category": "head" + }, + { + "name": "FighterPilot_Mask", + "guid": "435eba0d-d8f9-43cc-91aa-2959d63c6fa7", + "category": "face" + }, + { + "name": "Luchador_Blue_Swatch", + "guid": "0217fd2e-ff9d-49f7-9bc8-8d0d6b8bf250", + "category": "accessory" + }, + { + "name": "Jazzercise_Yellow_Swatch", + "guid": "78eb0969-f246-4cac-acd6-295df7c092e4", + "category": "accessory" + }, + { + "name": "RecRoom_FACE_DEFAULT_Decal", + "guid": "0c496f32-0011-4c4d-9778-59446f70c032", + "category": "accessory" + }, + { + "name": "HockeyShirt_Mask", + "guid": "e5635772-4e84-4b30-b77f-4a1196cb599b", + "category": "torso" + }, + { + "name": "2016Member_Mask", + "guid": "2ee3944d-e9fb-4959-973e-570a7fc6cc57", + "category": "face" + }, + { + "name": "Tshirt_RECS_Logo_Decal", + "guid": "237e0aa2-5911-4704-8d2a-d446ddd2d842", + "category": "torso" + }, + { + "name": "Barbershop_Wrist_Mask", + "guid": "f7c7d05f-5454-445a-af38-25f110bd204a", + "category": "face" + }, + { + "name": "VerticalStripe_Mask", + "guid": "c2a91a10-2f5b-4ab0-a79f-51408cf4b047", + "category": "face" + }, + { + "name": "Scallywag_Torso_Mask", + "guid": "005699fe-2c69-4bec-831f-031fe8ad7f0a", + "category": "face" + }, + { + "name": "Golfer_Glove_Grey_Swatch", + "guid": "72a010c0-bddd-4e5d-bca6-9821d82de9a6", + "category": "accessory" + }, + { + "name": "StollDress_Green_Swatch", + "guid": "a819f49b-6c7a-49d3-9e6a-d9d79ef5019f", + "category": "accessory" + }, + { + "name": "Torso_SeaCaptainBlueDark_Swatch", + "guid": "7c1aaa7e-503b-46ef-b1c5-0c61a75b916e", + "category": "head" + }, + { + "name": "BlazerSeaDark_Swatch", + "guid": "0ffad843-d6c9-425a-8686-7217009c867e", + "category": "accessory" + }, + { + "name": "Torso_SeaCaptainBlue_Swatch", + "guid": "27af51bc-8226-4911-8d47-22d28ea8e378", + "category": "head" + }, + { + "name": "Helixx_Decal", + "guid": "49934059-cc45-46ce-8277-f96959e1f6ba", + "category": "accessory" + }, + { + "name": "HibiscusFlower_Mask", + "guid": "65b89eae-347e-4eff-9ff3-7f25cace0125", + "category": "face" + }, + { + "name": "HeadBand_Trim_Mask", + "guid": "018a5c07-e956-457d-a540-a5e2cd68da09", + "category": "face" + }, + { + "name": "HockeyShirt_Teal_Swatch", + "guid": "fcbdfbfd-254a-4538-b119-43e23404b8b5", + "category": "torso" + }, + { + "name": "Orange_Swatch", + "guid": "51ef8d39-2b94-4f9e-9620-07b6b0a913a5", + "category": "accessory" + }, + { + "name": "Shirt_Smuggler_Mask", + "guid": "970a8c7e-d091-4719-bb9c-e3be8d46ee68", + "category": "torso" + }, + { + "name": "LaserTag_Head_Mask", + "guid": "4e378419-d043-4e86-ad0e-66288ac21cf4", + "category": "face" + }, + { + "name": "ArcherTan_Quiver_Swatch", + "guid": "d1eb3573-672c-4bb4-9ec5-7c81e6a38d3d", + "category": "accessory" + }, + { + "name": "Barber_Hat_Green_Swatch", + "guid": "7f9cb939-36a4-40da-8337-025e1d7bf8e5", + "category": "head" + }, + { + "name": "Punk_WristGreen_Swatch", + "guid": "a6796264-9c7e-4605-830c-06db8d0dc780", + "category": "accessory" + }, + { + "name": "FieldHockey_Mask", + "guid": "1fede91a-434e-49a5-882e-e6db107112a8", + "category": "face" + }, + { + "name": "Firefighter_Hat_White_Swatch", + "guid": "891d902e-3257-4a8c-9001-858046007997", + "category": "head" + }, + { + "name": "Scallywag_Hat_Red_Swatch", + "guid": "c3c9f89c-580c-43c4-b38b-07394044e0e1", + "category": "head" + }, + { + "name": "SpaceMarine_Dark_Swatch", + "guid": "a089bcc6-798d-4777-98e2-7afd290dc6b6", + "category": "accessory" + }, + { + "name": "PirateCaptain_Hat_Black_Mask", + "guid": "4cc0ea9f-65af-486f-8319-02b85ab5197f", + "category": "head" + }, + { + "name": "DrumMajorHat_Green_Swatch", + "guid": "5a614f97-ef7f-4b70-afff-9ed837a86407", + "category": "head" + }, + { + "name": "Golfer_Hat_Blue_Swatch", + "guid": "6584dada-499e-4d7c-958d-466955b20640", + "category": "head" + }, + { + "name": "RecRoom_Birthday01_Decal", + "guid": "ceb07e4f-50a9-4ed0-b50b-c59d92ba66b5", + "category": "accessory" + }, + { + "name": "Black_Swatch", + "guid": "e69372eb-2fd8-4e17-a448-ee0be61a2c7a", + "category": "accessory" + }, + { + "name": "ArcherRed_Swatch", + "guid": "5b5281c9-b795-4bb2-ba7b-ba52047ebe3f", + "category": "accessory" + }, + { + "name": "CollarDress_LightBlue_Swatch", + "guid": "f750de46-3758-4f7d-9709-0a84b1027009", + "category": "accessory" + }, + { + "name": "StollDress_Blue_Swatch", + "guid": "761a3193-60f0-4190-80c7-285b8192e794", + "category": "accessory" + }, + { + "name": "Torso_YellowBlueDress_Mask", + "guid": "cfabdefe-0890-436e-b2a3-b5c712e22955", + "category": "face" + }, + { + "name": "Boxer_Glove_Mask", + "guid": "f79f8a46-ef1d-4d8c-a4fc-383e3a9eb10b", + "category": "face" + }, + { + "name": "Torso_RedBeltDress_Mask", + "guid": "3d971439-41d1-43a2-8434-704f9ec8d906", + "category": "face" + }, + { + "name": "Argyle_Red_Swatch", + "guid": "071d1bf3-86dc-4a31-b54a-e6579fab8649", + "category": "accessory" + }, + { + "name": "WitchHunter_White_Swatch", + "guid": "e02838f8-4029-455a-b35c-dd40f972b05b", + "category": "accessory" + }, + { + "name": "RedFlowerDress_Mask", + "guid": "2d7361d2-31a2-404b-90da-10e89114e1f2", + "category": "face" + }, + { + "name": "TShirt_LaserTag_Alpha_Swatch", + "guid": "c0ff7719-c56a-4934-831b-506ad0f51cca", + "category": "torso" + }, + { + "name": "Wrist_Punk_Mask", + "guid": "dd7472ed-bdcb-4811-81ba-5ed682fb7675", + "category": "face" + }, + { + "name": "Angler_Hat_Mask", + "guid": "efeb07ab-6253-436d-bb66-991ddad72e58", + "category": "head" + }, + { + "name": "ArcherHat_Mask", + "guid": "1ba65dd5-9df9-4ff3-be96-f83a7abf67b3", + "category": "head" + }, + { + "name": "ArcherRed_Quiver_Swatch", + "guid": "7221132e-053a-4c58-b719-ed3c8558907d", + "category": "accessory" + }, + { + "name": "Luchador_Head_Mask", + "guid": "96b36c98-cba1-4337-91dd-c74c00316f97", + "category": "face" + }, + { + "name": "Tennis_Pink_Swatch", + "guid": "304e5cf1-5c94-481f-8e49-e6a943e6b300", + "category": "accessory" + }, + { + "name": "PoofyDress_Orange_Swatch", + "guid": "64850553-cdfe-455a-ac00-dafbe63d613e", + "category": "accessory" + }, + { + "name": "ElectricianTorso_Beige_Swatch", + "guid": "5fb2e235-8c67-4335-b432-edb10db2b139", + "category": "accessory" + }, + { + "name": "WitchHunterGlove_Mask", + "guid": "5b8d9490-e5bc-480e-91bc-65d1797b52ad", + "category": "face" + }, + { + "name": "Knight_White_Swatch", + "guid": "22dc463a-a89b-46ad-9a0d-557c742b4a80", + "category": "accessory" + }, + { + "name": "Mobster_Hat_Mask", + "guid": "d759c88d-a159-4c26-86dd-b054bb05b3ed", + "category": "head" + }, + { + "name": "BicycleHelmet_Yellow_Swatch", + "guid": "4398ce1a-e6ee-4da4-ad0d-7abfab41020c", + "category": "head" + }, + { + "name": "Golfer_Hat_Grey_Swatch", + "guid": "48c18418-01dd-40ff-9dfe-c4cf5f4e1245", + "category": "head" + }, + { + "name": "BasketballShirt_Blue_Swatch", + "guid": "59a62fa3-9e15-463e-9bd5-3aecdf604c1b", + "category": "torso" + }, + { + "name": "FurCoat_White_Swatch", + "guid": "643b5f2d-0666-4869-b699-03742de34d16", + "category": "accessory" + }, + { + "name": "Wrist_CowboyDark_Swatch", + "guid": "c4ebfd1a-fcda-4b98-a151-a941b1801867", + "category": "accessory" + }, + { + "name": "Hat_SeaCaptainDarkSilver_Swatch", + "guid": "36e63797-5a42-4779-a5cc-ef5c2e7d17d4", + "category": "head" + }, + { + "name": "HockeyShirt_Purple_Swatch", + "guid": "d5efaf80-7f34-4c9a-bc3a-b80b1ac70ace", + "category": "torso" + }, + { + "name": "ArcherYellow_Quiver_Swatch", + "guid": "e548124c-d7f3-45de-aaa0-f66130eca576", + "category": "accessory" + }, + { + "name": "ArcherTan_Swatch", + "guid": "9b35ae52-febe-486a-8557-ec7190a9756e", + "category": "accessory" + }, + { + "name": "Paintball_Torso_Mask", + "guid": "0189ec4c-0389-4ebc-b9ba-32303a6341c2", + "category": "face" + }, + { + "name": "Boxer_Helmet_Mask", + "guid": "92714eac-cc07-4f74-a6f5-cfbbadd07e06", + "category": "head" + }, + { + "name": "FighterPilot_Hat_Mask", + "guid": "9d6ac0b7-bbef-40bc-9173-effe65d7c0ff", + "category": "head" + }, + { + "name": "WitchHunter_Red_Swatch", + "guid": "909ed249-ce09-4304-8b02-56794fa7567d", + "category": "accessory" + }, + { + "name": "Dodgeball_Black_Swatch", + "guid": "4a85c181-92fe-47a5-925e-05f0ccb25006", + "category": "accessory" + }, + { + "name": "BlazerGrey_Swatch", + "guid": "272fe8eb-5061-4729-a7a8-414ff667a82f", + "category": "accessory" + }, + { + "name": "TennisSkirt_Red_Swatch", + "guid": "758752bd-db2f-43d2-b580-55b3e1efffd5", + "category": "legs" + }, + { + "name": "Hat_Equestrian_Mask", + "guid": "be6b3fa1-3388-48ac-8957-d7b8c85ff968", + "category": "head" + }, + { + "name": "Wizard_Red_Swatch", + "guid": "a38254c0-00e3-429b-9d5d-b9e6a9812e69", + "category": "accessory" + }, + { + "name": "FighterPilot_Gloves_Blue_Swatch", + "guid": "55b4e661-43e1-4708-82d5-2dd2bebd4f45", + "category": "accessory" + }, + { + "name": "WitchHunterHat_Mask", + "guid": "32d91d32-b102-4093-927a-6dc3ac2e9e86", + "category": "head" + }, + { + "name": "Stripe3_Swatch", + "guid": "5c4a2b35-0e1c-44de-8c3a-96d4a6458b1b", + "category": "accessory" + }, + { + "name": "ArtistHat_Blue_Swatch", + "guid": "14b43bf9-5ba0-484e-ae5d-55cfeb09a70d", + "category": "head" + }, + { + "name": "ArcherQuiver_Mask", + "guid": "6094370d-5e28-473a-b422-f3f1c75d5db8", + "category": "face" + }, + { + "name": "Scout_Hat_Mask", + "guid": "464fcf20-361f-4dc9-b074-150c3bc27523", + "category": "head" + }, + { + "name": "Ranger_Hat_Green_Swatch", + "guid": "d0bb468c-b78f-4e80-916d-65fec3ec0010", + "category": "head" + } +] \ No newline at end of file diff --git a/DorkNet.Server/DorkNet.Server.csproj b/DorkNet.Server/DorkNet.Server.csproj new file mode 100644 index 0000000..4e2687e --- /dev/null +++ b/DorkNet.Server/DorkNet.Server.csproj @@ -0,0 +1,154 @@ + + + + net9.0 + enable + enable + + + + + + + runtime; build; native; contentfiles; analyzers; buildtransitive + all + + + + + + + + + + + + + + + + + + + + + runtime; build; native; contentfiles; analyzers; buildtransitive + all + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + data\%(Filename)%(Extension) + PreserveNewest + + + data\images\%(Filename)%(Extension) + PreserveNewest + + + data\images\%(Filename)%(Extension) + PreserveNewest + + + + diff --git a/DorkNet.Server/DorkNet.Server.csproj.lscache b/DorkNet.Server/DorkNet.Server.csproj.lscache new file mode 100644 index 0000000..3d6d569 --- /dev/null +++ b/DorkNet.Server/DorkNet.Server.csproj.lscache @@ -0,0 +1,659 @@ +version=1 + +# This file caches language service data to improve the performance of C# Dev Kit. +# It is not intended for manual editing. It can safely be deleted and will be +# regenerated automatically. For more information, see https://aka.ms/lscache +# +# To control where cache files are stored, use the following VS Code setting: +# "dotnet.projectsystem.cacheInProjectFolder": true + +[project] +language=C# +primary +lastDtbSucceeded + +[properties] +AssemblyName=DorkNet.Server +CommandLineArgsForDesignTimeEvaluation=-langversion:13.0 -define:TRACE +CompilerGeneratedFilesOutputPath= +MaxSupportedLangVersion=13.0 +ProjectAssetsFile=obj/project.assets.json +RootNamespace=DorkNet.Server +RunAnalyzers= +RunAnalyzersDuringLiveAnalysis= +SolutionPath=../DorkNet.sln +TargetFrameworkIdentifier=.NETCoreApp +TargetPath=bin/Debug/net9.0/DorkNet.Server.dll +TargetRefPath=obj/Debug/net9.0/ref/DorkNet.Server.dll +TemporaryDependencyNodeTargetIdentifier=net9.0 + +[commandLineArguments] +/noconfig +/unsafe- +/checked- +/nowarn:1701,1702,1701,1702 +/fullpaths +/nostdlib+ +/errorreport:prompt +/warn:9 +/define:TRACE;DEBUG;NET;NET9_0;NETCOREAPP;NET5_0_OR_GREATER;NET6_0_OR_GREATER;NET7_0_OR_GREATER;NET8_0_OR_GREATER;NET9_0_OR_GREATER;NETCOREAPP1_0_OR_GREATER;NETCOREAPP1_1_OR_GREATER;NETCOREAPP2_0_OR_GREATER;NETCOREAPP2_1_OR_GREATER;NETCOREAPP2_2_OR_GREATER;NETCOREAPP3_0_OR_GREATER;NETCOREAPP3_1_OR_GREATER +/highentropyva+ +/nullable:enable +/debug+ +/debug:portable +/filealign:512 +/optimize- +/out:obj\Debug\net9.0\DorkNet.Server.dll +/refout:obj\Debug\net9.0\refint\DorkNet.Server.dll +/target:exe +/warnaserror- +/utf8output +/deterministic+ +/langversion:13.0 +/warnaserror+:NU1605,SYSLIB0011 + +[sourceFiles] +Auth/ + AdminOnlyAttribute.cs + BanCheckMiddleware.cs + ControllerBaseExtensions.cs + IpBanCheckMiddleware.cs +Controllers/ + Accounts/AccountsController.cs + Admin/ + AdminController.cs + AdminLoginController.cs + RoomZipImportController.cs + API/ + Account/AccountApiController.cs + AllEndpointsController.cs + Auth/PhotonAuthController.cs + Avatar/V2/ + AvatarGiftsController.cs + AvatarV2Controller.cs + Avatar/ + V3/AvatarSavedOutfitsController.cs + V4/AvatarItemsController.cs + BugReporting/BugReportsController.cs + CommunityBoard/CommunityBoardController.cs + Config/V1/ + ConfigController.cs + ServiceUrlsController.cs + Config/V2/ConfigController.cs + Events/V3/EventsController.cs + GameConfigurations/GameConfigurationsController.cs + GameSessions/V2/GameSessionsController.cs + Groups/GroupsController.cs + Images/ + ImagesController.cs + V2/ImagesController.cs + Inventions/InventionsController.cs + Inventory/InventoryController.cs + Leaderboard/LeaderboardController.cs + Messages/V2/MessagesController.cs + Misc/StubExtensionController.cs + MissingEndpointsController.cs + Moderation/InGameModerationController.cs + Notification/V2/NotificationController.cs + NotificationPrefs/IOSNotificationPrefsController.cs + Photos/PhotosController.cs + PlatformLogin/V2/PlatformLoginController.cs + PlayerElo/PlayerEloController.cs + PlayerEvents/PlayerEventsController.cs + PlayerReporting/ReportsController.cs + Players/V2/PlayersController.cs + PlayerState/ + MiscStubsController.cs + PlayerStateController.cs + PlayerSubscriptions/PlayerSubscriptionsController.cs + Progression/ProgressionController.cs + QuickPlay/QuickPlayController.cs + Relationships/V2/RelationshipsController.cs + Role/RoleController.cs + Rooms/V2/ + RoomsController.cs + RoomsModerationController.cs + Royale/RoyaleController.cs + Settings/V2/SettingsController.cs + Store/ + PurchaseController.cs + StorefrontsBuyController.cs + StorefrontsController.cs + Stubs/ApiNamespaceStubsController.cs + TestCaseManagement/TestCaseManagementController.cs + Version/VersionController.cs + ApiCatchAllController.cs + Auth/ + AuthAccountController.cs + AuthController.cs + PhotonCustomAuthController.cs + Cdn/CdnController.cs + Chat/ChatController.cs + Commerce/CommerceCatalogController.cs + Discovery/DiscoveryController.cs + Econ/EconController.cs + Geo/GeoController.cs + GlobalCatchAllController.cs + Health/HealthController.cs + Img/ImgController.cs + Match/ + GoToController.cs + MatchController.cs + MatchPlayerController.cs + PlayerPresenceController.cs + Notify/NotifyController.cs + Ns/NsController.cs + PlayerSettings/PlayerSettingsController.cs + Rooms/RoomsController.cs + RootController.cs + Storage/StorageController.cs + Strings/StringsController.cs + Stub/StubController.cs +Data/ + DorkNetDbContext.cs + Entities/ + AdminActionEntity.cs + AvatarEntity.cs + BugReportEntity.cs + ChatMessageEntity.cs + CheerEntity.cs + ClubEntity.cs + CommunityBoardEntity.cs + CouponEntities.cs + CurrencyBalanceEntity.cs + DormStateEntity.cs + GameSessionEntity.cs + GiftPackageEntity.cs + InventionEntity.cs + InventionVersionEntity.cs + IpBanEntity.cs + MessageEntity.cs + ObjectiveProgressEntity.cs + PhotoEntity.cs + PlayerEntity.cs + PlayerEventEntity.cs + PlayerInventoryEntity.cs + PlayerSettingEntity.cs + PlayerSystemsEntities.cs + PrivateInstanceEntity.cs + PrivateInstanceInviteeEntity.cs + PushTokenEntity.cs + RelationshipEntity.cs + ReportEntity.cs + RoomBanEntity.cs + RoomBookmarkEntity.cs + RoomDataBlobEntity.cs + RoomEntity.cs + RoomSceneEntity.cs + RoomVisitEntity.cs + RoyaleEntities.cs + StatsEntities.cs + StoreItemEntity.cs + SubscriptionEntity.cs + TestCaseEntities.cs +Hubs/NotifyHub.cs +Migrations/ + 20260508092637_Initial.cs + 20260508092637_Initial.Designer.cs + 20260508092846_Phase1AccountColumns.cs + 20260508092846_Phase1AccountColumns.Designer.cs + 20260508093951_Phase2RelationshipStatus.cs + 20260508093951_Phase2RelationshipStatus.Designer.cs + 20260508152627_Phase3RoomDataBlobs.cs + 20260508152627_Phase3RoomDataBlobs.Designer.cs + 20260508162136_Phase3MultiUserDormState.cs + 20260508162136_Phase3MultiUserDormState.Designer.cs + 20260508162929_Phase5AdminAudit.cs + 20260508162929_Phase5AdminAudit.Designer.cs + 20260508163135_Phase6AvatarFields.cs + 20260508163135_Phase6AvatarFields.Designer.cs + 20260508163343_Phase7Inventions.cs + 20260508163343_Phase7Inventions.Designer.cs + 20260508164300_Phase8AccountPassword.cs + 20260508164300_Phase8AccountPassword.Designer.cs + 20260508164951_Phase9ReportsAndIpBans.cs + 20260508164951_Phase9ReportsAndIpBans.Designer.cs + 20260508165257_Phase10MessagesAndChat.cs + 20260508165257_Phase10MessagesAndChat.Designer.cs + 20260508165508_Phase11Cheers.cs + 20260508165508_Phase11Cheers.Designer.cs + 20260508170106_Phase12Currency.cs + 20260508170106_Phase12Currency.Designer.cs + 20260508170400_Phase13EventsAndObjectives.cs + 20260508170400_Phase13EventsAndObjectives.Designer.cs + 20260508170534_Phase14Subscriptions.cs + 20260508170534_Phase14Subscriptions.Designer.cs + 20260508182402_Phase15Photos.cs + 20260508182402_Phase15Photos.Designer.cs + 20260508191000_Phase16Store.cs + 20260508191000_Phase16Store.Designer.cs + 20260509110540_Phase17NewSubsystems.cs + 20260509110540_Phase17NewSubsystems.Designer.cs + 20260509132303_Phase18LeaderboardElo.cs + 20260509132303_Phase18LeaderboardElo.Designer.cs + 20260509135114_Phase19TokensIgnoresCards.cs + 20260509135114_Phase19TokensIgnoresCards.Designer.cs + 20260509145611_Phase20RoomVisitsAndStats.cs + 20260509145611_Phase20RoomVisitsAndStats.Designer.cs + 20260509200000_Phase21RoomScenes.cs + 20260509200000_Phase21RoomScenes.Designer.cs + DorkNetDbContextModelSnapshot.cs +obj/Debug/net9.0/ + .NETCoreApp,Version=v9.0.AssemblyAttributes.cs + DorkNet.Server.AssemblyInfo.cs + DorkNet.Server.GlobalUsings.g.cs + Protos/ + DorknetRoomData.cs + Recroom2020.cs +Program.cs +Services/ + AuthService.cs + CommunityBoardService.cs + ConfigService.cs + GameSessionService.cs + HtrAssetMirrorService.cs + ImageSignatureService.cs + ImageTransformService.cs + LevelService.cs + NotificationService.cs + ObjectStorageService.cs + OnlinePresenceService.cs + OrphanAccountTracker.cs + PlayerLogService.cs + PlayerPresenceService.cs + PlayerService.cs + PrivateInstanceService.cs + ProfanityFilter.cs + RoomBlobNormalizerService.cs + RoomDataBlobService.cs + RoomService.cs + StoreService.cs + +[metadataReferences] +../DorkNet.Models/obj/Debug/net9.0/ref/DorkNet.Models.dll +/packs/Microsoft.AspNetCore.App.Ref/9.0.16/ref/net9.0/ + Microsoft.AspNetCore.Antiforgery.dll + Microsoft.AspNetCore.Authentication.Abstractions.dll + Microsoft.AspNetCore.Authentication.BearerToken.dll + Microsoft.AspNetCore.Authentication.Cookies.dll + Microsoft.AspNetCore.Authentication.Core.dll + Microsoft.AspNetCore.Authentication.dll + Microsoft.AspNetCore.Authentication.OAuth.dll + Microsoft.AspNetCore.Authorization.dll + Microsoft.AspNetCore.Authorization.Policy.dll + Microsoft.AspNetCore.Components.Authorization.dll + Microsoft.AspNetCore.Components.dll + Microsoft.AspNetCore.Components.Endpoints.dll + Microsoft.AspNetCore.Components.Forms.dll + Microsoft.AspNetCore.Components.Server.dll + Microsoft.AspNetCore.Components.Web.dll + Microsoft.AspNetCore.Connections.Abstractions.dll + Microsoft.AspNetCore.CookiePolicy.dll + Microsoft.AspNetCore.Cors.dll + Microsoft.AspNetCore.Cryptography.Internal.dll + Microsoft.AspNetCore.Cryptography.KeyDerivation.dll + Microsoft.AspNetCore.DataProtection.Abstractions.dll + Microsoft.AspNetCore.DataProtection.dll + Microsoft.AspNetCore.DataProtection.Extensions.dll + Microsoft.AspNetCore.Diagnostics.Abstractions.dll + Microsoft.AspNetCore.Diagnostics.dll + Microsoft.AspNetCore.Diagnostics.HealthChecks.dll + Microsoft.AspNetCore.dll + Microsoft.AspNetCore.HostFiltering.dll + Microsoft.AspNetCore.Hosting.Abstractions.dll + Microsoft.AspNetCore.Hosting.dll + Microsoft.AspNetCore.Hosting.Server.Abstractions.dll + Microsoft.AspNetCore.Html.Abstractions.dll + Microsoft.AspNetCore.Http.Abstractions.dll + Microsoft.AspNetCore.Http.Connections.Common.dll + Microsoft.AspNetCore.Http.Connections.dll + Microsoft.AspNetCore.Http.dll + Microsoft.AspNetCore.Http.Extensions.dll + Microsoft.AspNetCore.Http.Features.dll + Microsoft.AspNetCore.Http.Results.dll + Microsoft.AspNetCore.HttpLogging.dll + Microsoft.AspNetCore.HttpOverrides.dll + Microsoft.AspNetCore.HttpsPolicy.dll + Microsoft.AspNetCore.Identity.dll + Microsoft.AspNetCore.Localization.dll + Microsoft.AspNetCore.Localization.Routing.dll + Microsoft.AspNetCore.Metadata.dll + Microsoft.AspNetCore.Mvc.Abstractions.dll + Microsoft.AspNetCore.Mvc.ApiExplorer.dll + Microsoft.AspNetCore.Mvc.Core.dll + Microsoft.AspNetCore.Mvc.Cors.dll + Microsoft.AspNetCore.Mvc.DataAnnotations.dll + Microsoft.AspNetCore.Mvc.dll + Microsoft.AspNetCore.Mvc.Formatters.Json.dll + Microsoft.AspNetCore.Mvc.Formatters.Xml.dll + Microsoft.AspNetCore.Mvc.Localization.dll + Microsoft.AspNetCore.Mvc.Razor.dll + Microsoft.AspNetCore.Mvc.RazorPages.dll + Microsoft.AspNetCore.Mvc.TagHelpers.dll + Microsoft.AspNetCore.Mvc.ViewFeatures.dll + Microsoft.AspNetCore.OutputCaching.dll + Microsoft.AspNetCore.RateLimiting.dll + Microsoft.AspNetCore.Razor.dll + Microsoft.AspNetCore.Razor.Runtime.dll + Microsoft.AspNetCore.RequestDecompression.dll + Microsoft.AspNetCore.ResponseCaching.Abstractions.dll + Microsoft.AspNetCore.ResponseCaching.dll + Microsoft.AspNetCore.ResponseCompression.dll + Microsoft.AspNetCore.Rewrite.dll + Microsoft.AspNetCore.Routing.Abstractions.dll + Microsoft.AspNetCore.Routing.dll + Microsoft.AspNetCore.Server.HttpSys.dll + Microsoft.AspNetCore.Server.IIS.dll + Microsoft.AspNetCore.Server.IISIntegration.dll + Microsoft.AspNetCore.Server.Kestrel.Core.dll + Microsoft.AspNetCore.Server.Kestrel.dll + Microsoft.AspNetCore.Server.Kestrel.Transport.NamedPipes.dll + Microsoft.AspNetCore.Server.Kestrel.Transport.Quic.dll + Microsoft.AspNetCore.Server.Kestrel.Transport.Sockets.dll + Microsoft.AspNetCore.Session.dll + Microsoft.AspNetCore.SignalR.Common.dll + Microsoft.AspNetCore.SignalR.Core.dll + Microsoft.AspNetCore.SignalR.dll + Microsoft.AspNetCore.SignalR.Protocols.Json.dll + Microsoft.AspNetCore.StaticAssets.dll + Microsoft.AspNetCore.StaticFiles.dll + Microsoft.AspNetCore.WebSockets.dll + Microsoft.AspNetCore.WebUtilities.dll + Microsoft.Extensions.Caching.Abstractions.dll + Microsoft.Extensions.Caching.Memory.dll + Microsoft.Extensions.Configuration.Abstractions.dll + Microsoft.Extensions.Configuration.Binder.dll + Microsoft.Extensions.Configuration.CommandLine.dll + Microsoft.Extensions.Configuration.dll + Microsoft.Extensions.Configuration.EnvironmentVariables.dll + Microsoft.Extensions.Configuration.FileExtensions.dll + Microsoft.Extensions.Configuration.Ini.dll + Microsoft.Extensions.Configuration.Json.dll + Microsoft.Extensions.Configuration.KeyPerFile.dll + Microsoft.Extensions.Configuration.UserSecrets.dll + Microsoft.Extensions.Configuration.Xml.dll + Microsoft.Extensions.DependencyInjection.Abstractions.dll + Microsoft.Extensions.DependencyInjection.dll + Microsoft.Extensions.Diagnostics.Abstractions.dll + Microsoft.Extensions.Diagnostics.dll + Microsoft.Extensions.Diagnostics.HealthChecks.Abstractions.dll + Microsoft.Extensions.Diagnostics.HealthChecks.dll + Microsoft.Extensions.Features.dll + Microsoft.Extensions.FileProviders.Abstractions.dll + Microsoft.Extensions.FileProviders.Composite.dll + Microsoft.Extensions.FileProviders.Embedded.dll + Microsoft.Extensions.FileProviders.Physical.dll + Microsoft.Extensions.FileSystemGlobbing.dll + Microsoft.Extensions.Hosting.Abstractions.dll + Microsoft.Extensions.Hosting.dll + Microsoft.Extensions.Http.dll + Microsoft.Extensions.Identity.Core.dll + Microsoft.Extensions.Identity.Stores.dll + Microsoft.Extensions.Localization.Abstractions.dll + Microsoft.Extensions.Localization.dll + Microsoft.Extensions.Logging.Abstractions.dll + Microsoft.Extensions.Logging.Configuration.dll + Microsoft.Extensions.Logging.Console.dll + Microsoft.Extensions.Logging.Debug.dll + Microsoft.Extensions.Logging.dll + Microsoft.Extensions.Logging.EventLog.dll + Microsoft.Extensions.Logging.EventSource.dll + Microsoft.Extensions.Logging.TraceSource.dll + Microsoft.Extensions.ObjectPool.dll + Microsoft.Extensions.Options.ConfigurationExtensions.dll + Microsoft.Extensions.Options.DataAnnotations.dll + Microsoft.Extensions.Options.dll + Microsoft.Extensions.Primitives.dll + Microsoft.Extensions.WebEncoders.dll + Microsoft.JSInterop.dll + Microsoft.Net.Http.Headers.dll + System.Diagnostics.EventLog.dll + System.Security.Cryptography.Xml.dll + System.Threading.RateLimiting.dll +/packs/Microsoft.NETCore.App.Ref/9.0.16/ref/net9.0/ + Microsoft.CSharp.dll + Microsoft.VisualBasic.Core.dll + Microsoft.VisualBasic.dll + Microsoft.Win32.Primitives.dll + Microsoft.Win32.Registry.dll + mscorlib.dll + netstandard.dll + System.AppContext.dll + System.Buffers.dll + System.Collections.Concurrent.dll + System.Collections.dll + System.Collections.Immutable.dll + System.Collections.NonGeneric.dll + System.Collections.Specialized.dll + System.ComponentModel.Annotations.dll + System.ComponentModel.DataAnnotations.dll + System.ComponentModel.dll + System.ComponentModel.EventBasedAsync.dll + System.ComponentModel.Primitives.dll + System.ComponentModel.TypeConverter.dll + System.Configuration.dll + System.Console.dll + System.Core.dll + System.Data.Common.dll + System.Data.DataSetExtensions.dll + System.Data.dll + System.Diagnostics.Contracts.dll + System.Diagnostics.Debug.dll + System.Diagnostics.DiagnosticSource.dll + System.Diagnostics.FileVersionInfo.dll + System.Diagnostics.Process.dll + System.Diagnostics.StackTrace.dll + System.Diagnostics.TextWriterTraceListener.dll + System.Diagnostics.Tools.dll + System.Diagnostics.TraceSource.dll + System.Diagnostics.Tracing.dll + System.dll + System.Drawing.dll + System.Drawing.Primitives.dll + System.Dynamic.Runtime.dll + System.Formats.Asn1.dll + System.Formats.Tar.dll + System.Globalization.Calendars.dll + System.Globalization.dll + System.Globalization.Extensions.dll + System.IO.Compression.Brotli.dll + System.IO.Compression.dll + System.IO.Compression.FileSystem.dll + System.IO.Compression.ZipFile.dll + System.IO.dll + System.IO.FileSystem.AccessControl.dll + System.IO.FileSystem.dll + System.IO.FileSystem.DriveInfo.dll + System.IO.FileSystem.Primitives.dll + System.IO.FileSystem.Watcher.dll + System.IO.IsolatedStorage.dll + System.IO.MemoryMappedFiles.dll + System.IO.Pipelines.dll + System.IO.Pipes.AccessControl.dll + System.IO.Pipes.dll + System.IO.UnmanagedMemoryStream.dll + System.Linq.dll + System.Linq.Expressions.dll + System.Linq.Parallel.dll + System.Linq.Queryable.dll + System.Memory.dll + System.Net.dll + System.Net.Http.dll + System.Net.Http.Json.dll + System.Net.HttpListener.dll + System.Net.Mail.dll + System.Net.NameResolution.dll + System.Net.NetworkInformation.dll + System.Net.Ping.dll + System.Net.Primitives.dll + System.Net.Quic.dll + System.Net.Requests.dll + System.Net.Security.dll + System.Net.ServicePoint.dll + System.Net.Sockets.dll + System.Net.WebClient.dll + System.Net.WebHeaderCollection.dll + System.Net.WebProxy.dll + System.Net.WebSockets.Client.dll + System.Net.WebSockets.dll + System.Numerics.dll + System.Numerics.Vectors.dll + System.ObjectModel.dll + System.Reflection.DispatchProxy.dll + System.Reflection.dll + System.Reflection.Emit.dll + System.Reflection.Emit.ILGeneration.dll + System.Reflection.Emit.Lightweight.dll + System.Reflection.Extensions.dll + System.Reflection.Metadata.dll + System.Reflection.Primitives.dll + System.Reflection.TypeExtensions.dll + System.Resources.Reader.dll + System.Resources.ResourceManager.dll + System.Resources.Writer.dll + System.Runtime.CompilerServices.Unsafe.dll + System.Runtime.CompilerServices.VisualC.dll + System.Runtime.dll + System.Runtime.Extensions.dll + System.Runtime.Handles.dll + System.Runtime.InteropServices.dll + System.Runtime.InteropServices.JavaScript.dll + System.Runtime.InteropServices.RuntimeInformation.dll + System.Runtime.Intrinsics.dll + System.Runtime.Loader.dll + System.Runtime.Numerics.dll + System.Runtime.Serialization.dll + System.Runtime.Serialization.Formatters.dll + System.Runtime.Serialization.Json.dll + System.Runtime.Serialization.Primitives.dll + System.Runtime.Serialization.Xml.dll + System.Security.AccessControl.dll + System.Security.Claims.dll + System.Security.Cryptography.Algorithms.dll + System.Security.Cryptography.Cng.dll + System.Security.Cryptography.Csp.dll + System.Security.Cryptography.dll + System.Security.Cryptography.Encoding.dll + System.Security.Cryptography.OpenSsl.dll + System.Security.Cryptography.Primitives.dll + System.Security.Cryptography.X509Certificates.dll + System.Security.dll + System.Security.Principal.dll + System.Security.Principal.Windows.dll + System.Security.SecureString.dll + System.ServiceModel.Web.dll + System.ServiceProcess.dll + System.Text.Encoding.CodePages.dll + System.Text.Encoding.dll + System.Text.Encoding.Extensions.dll + System.Text.Encodings.Web.dll + System.Text.Json.dll + System.Text.RegularExpressions.dll + System.Threading.Channels.dll + System.Threading.dll + System.Threading.Overlapped.dll + System.Threading.Tasks.Dataflow.dll + System.Threading.Tasks.dll + System.Threading.Tasks.Extensions.dll + System.Threading.Tasks.Parallel.dll + System.Threading.Thread.dll + System.Threading.ThreadPool.dll + System.Threading.Timer.dll + System.Transactions.dll + System.Transactions.Local.dll + System.ValueTuple.dll + System.Web.dll + System.Web.HttpUtility.dll + System.Windows.dll + System.Xml.dll + System.Xml.Linq.dll + System.Xml.ReaderWriter.dll + System.Xml.Serialization.dll + System.Xml.XDocument.dll + System.Xml.XmlDocument.dll + System.Xml.XmlSerializer.dll + System.Xml.XPath.dll + System.Xml.XPath.XDocument.dll + WindowsBase.dll +/ + awssdk.core/3.7.401.5/lib/net8.0/AWSSDK.Core.dll + awssdk.s3/3.7.412.5/lib/net8.0/AWSSDK.S3.dll + bcrypt.net-next/4.0.3/lib/net6.0/BCrypt.Net-Next.dll + google.protobuf/3.28.3/lib/net5.0/Google.Protobuf.dll + messagepack.annotations/2.5.187/lib/netstandard2.0/MessagePack.Annotations.dll + messagepack/2.5.187/lib/net6.0/MessagePack.dll + microsoft.aspnetcore.authentication.jwtbearer/9.0.4/lib/net9.0/Microsoft.AspNetCore.Authentication.JwtBearer.dll + microsoft.aspnetcore.openapi/9.0.15/lib/net9.0/Microsoft.AspNetCore.OpenApi.dll + microsoft.aspnetcore.signalr.stackexchangeredis/9.0.4/lib/net9.0/Microsoft.AspNetCore.SignalR.StackExchangeRedis.dll + microsoft.data.sqlite.core/9.0.4/lib/net8.0/Microsoft.Data.Sqlite.dll + microsoft.entityframeworkcore.abstractions/9.0.4/lib/net8.0/Microsoft.EntityFrameworkCore.Abstractions.dll + microsoft.entityframeworkcore.relational/9.0.4/lib/net8.0/Microsoft.EntityFrameworkCore.Relational.dll + microsoft.entityframeworkcore.sqlite.core/9.0.4/lib/net8.0/Microsoft.EntityFrameworkCore.Sqlite.dll + microsoft.entityframeworkcore/9.0.4/lib/net8.0/Microsoft.EntityFrameworkCore.dll + microsoft.extensions.dependencymodel/9.0.4/lib/net9.0/Microsoft.Extensions.DependencyModel.dll + microsoft.identitymodel.abstractions/8.0.1/lib/net9.0/Microsoft.IdentityModel.Abstractions.dll + microsoft.identitymodel.jsonwebtokens/8.0.1/lib/net9.0/Microsoft.IdentityModel.JsonWebTokens.dll + microsoft.identitymodel.logging/8.0.1/lib/net9.0/Microsoft.IdentityModel.Logging.dll + microsoft.identitymodel.protocols.openidconnect/8.0.1/lib/net9.0/Microsoft.IdentityModel.Protocols.OpenIdConnect.dll + microsoft.identitymodel.protocols/8.0.1/lib/net9.0/Microsoft.IdentityModel.Protocols.dll + microsoft.identitymodel.tokens/8.0.1/lib/net9.0/Microsoft.IdentityModel.Tokens.dll + microsoft.openapi/1.6.22/lib/netstandard2.0/Microsoft.OpenApi.dll + npgsql.entityframeworkcore.postgresql/9.0.4/lib/net8.0/Npgsql.EntityFrameworkCore.PostgreSQL.dll + npgsql/9.0.3/lib/net8.0/Npgsql.dll + pipelines.sockets.unofficial/2.2.8/lib/net5.0/Pipelines.Sockets.Unofficial.dll + serilog.aspnetcore/9.0.0/lib/net9.0/Serilog.AspNetCore.dll + serilog.extensions.hosting/9.0.0/lib/net9.0/Serilog.Extensions.Hosting.dll + serilog.extensions.logging/9.0.0/lib/net9.0/Serilog.Extensions.Logging.dll + serilog.formatting.compact/3.0.0/lib/net8.0/Serilog.Formatting.Compact.dll + serilog.settings.configuration/9.0.0/lib/net9.0/Serilog.Settings.Configuration.dll + serilog.sinks.console/6.0.0/lib/net8.0/Serilog.Sinks.Console.dll + serilog.sinks.debug/3.0.0/lib/net8.0/Serilog.Sinks.Debug.dll + serilog.sinks.file/6.0.0/lib/net8.0/Serilog.Sinks.File.dll + serilog/4.2.0/lib/net9.0/Serilog.dll + skiasharp/3.116.1/ref/net8.0/SkiaSharp.dll + sqlitepclraw.bundle_e_sqlite3/2.1.10/lib/netstandard2.0/SQLitePCLRaw.batteries_v2.dll + sqlitepclraw.core/2.1.10/lib/netstandard2.0/SQLitePCLRaw.core.dll + sqlitepclraw.provider.e_sqlite3/2.1.10/lib/net6.0/SQLitePCLRaw.provider.e_sqlite3.dll + stackexchange.redis/2.8.16/lib/net6.0/StackExchange.Redis.dll + swashbuckle.aspnetcore.swagger/7.3.1/lib/net9.0/Swashbuckle.AspNetCore.Swagger.dll + swashbuckle.aspnetcore.swaggergen/7.3.1/lib/net9.0/Swashbuckle.AspNetCore.SwaggerGen.dll + swashbuckle.aspnetcore.swaggerui/7.3.1/lib/net9.0/Swashbuckle.AspNetCore.SwaggerUI.dll + system.identitymodel.tokens.jwt/8.0.1/lib/net9.0/System.IdentityModel.Tokens.Jwt.dll + +[analyzerReferences] +/packs/Microsoft.AspNetCore.App.Ref/9.0.16/analyzers/dotnet/cs/ + Microsoft.AspNetCore.App.Analyzers.dll + Microsoft.AspNetCore.App.CodeFixes.dll + Microsoft.AspNetCore.Components.Analyzers.dll +/packs/Microsoft.AspNetCore.App.Ref/9.0.16/analyzers/dotnet/roslyn4.4/cs/ + Microsoft.Extensions.Logging.Generators.dll + Microsoft.Extensions.Options.SourceGeneration.dll +/packs/Microsoft.NETCore.App.Ref/9.0.16/analyzers/dotnet/cs/ + Microsoft.Interop.ComInterfaceGenerator.dll + Microsoft.Interop.JavaScript.JSImportGenerator.dll + Microsoft.Interop.LibraryImportGenerator.dll + Microsoft.Interop.SourceGeneration.dll + System.Text.Json.SourceGeneration.dll + System.Text.RegularExpressions.Generator.dll +/sdk/9.0.314/Sdks/Microsoft.NET.Sdk.Razor/source-generators/ + Microsoft.AspNetCore.Razor.Utilities.Shared.dll + Microsoft.CodeAnalysis.Razor.Compiler.dll + Microsoft.Extensions.ObjectPool.dll + System.Collections.Immutable.dll +/sdk/9.0.314/Sdks/Microsoft.NET.Sdk.Web/analyzers/cs/ + Microsoft.AspNetCore.Analyzers.dll + Microsoft.AspNetCore.Mvc.Analyzers.dll +/sdk/9.0.314/Sdks/Microsoft.NET.Sdk/analyzers/ + Microsoft.CodeAnalysis.CSharp.NetAnalyzers.dll + Microsoft.CodeAnalysis.NetAnalyzers.dll +/awssdk.s3/3.7.412.5/analyzers/dotnet/cs/ + AWSSDK.S3.CodeAnalysis.dll + SharedAnalysisCode.dll +/microsoft.codeanalysis.analyzers/3.3.4/analyzers/dotnet/cs/ + Microsoft.CodeAnalysis.Analyzers.dll + Microsoft.CodeAnalysis.CSharp.Analyzers.dll +/microsoft.entityframeworkcore.analyzers/9.0.4/analyzers/dotnet/cs/Microsoft.EntityFrameworkCore.Analyzers.dll + +[analyzerConfigFiles] +/sdk/9.0.314/Sdks/Microsoft.NET.Sdk/analyzers/build/config/analysislevel_9_default.globalconfig +obj/Debug/net9.0/DorkNet.Server.GeneratedMSBuildEditorConfig.editorconfig diff --git a/DorkNet.Server/Hubs/NotifyHub.cs b/DorkNet.Server/Hubs/NotifyHub.cs new file mode 100644 index 0000000..2c43265 --- /dev/null +++ b/DorkNet.Server/Hubs/NotifyHub.cs @@ -0,0 +1,178 @@ +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.SignalR; +using DorkNet.Server.Services; +using System.Security.Claims; +using System.Text.Json; + +namespace DorkNet.Server.Hubs; + +/// +/// notify.rec.net/hub/v1 — SignalR Core hub used by the client's +/// BestHTTP.SignalRCore.HubConnection (RecNet.SignalRHubConnection wraps it). +/// +/// Each connection authenticates via the same JWT used for the rest of +/// the API; we extract the player id from the bearer claim and add the +/// connection to a per-player SignalR group so the rest of the app can +/// push to "player N" by group name without tracking connection ids +/// directly. +/// +/// **Per-replica vs cross-replica state**: SignalR's group fan-out is +/// driven by its registered backplane — when Redis is configured in +/// Program.cs via AddStackExchangeRedis, a notification +/// published to player:N from any replica reaches every +/// replica's matching connections. Connection-tracking for +/// "is player N online?" queries lives in +/// (also Redis-backed) so admin tools see the cluster-wide truth. +/// Pre-PR-2 used a local static ConcurrentDictionary which only +/// reflected this replica's connections. +/// +/// Hub methods invoked by the client (verified by string-literal scan +/// of metadata at addresses 0x39b2470, 0x39b24c8, 0x39b4ba8): +/// "Subscribe" - generic topic subscribe +/// "Unsubscribe" - generic topic unsubscribe +/// "SubscribeToPlayers" - subscribe to per-player events (used by +/// RecNet.NotificationManager / SubscribeToPlayers) +/// All accept JsonElement so we don't lock ourselves into a parameter +/// schema we haven't verified yet, and return Task so the client's +/// awaited Invoke completes cleanly. +/// +[Authorize] +public class NotifyHub( + OnlinePresenceService presence, + NotificationService notifications, + ILogger logger) : Hub +{ + /// SignalR group name for a given player id. Use this + /// from to push. + public static string GroupForPlayer(long playerId) => $"player:{playerId}"; + + public override async Task OnConnectedAsync() + { + var pid = TryGetPlayerId(); + if (pid is long id) + { + // Enforce single-session-per-account. When a SECOND device + // logs in with the same credentials, both the original + // session and the new one would otherwise share the + // player:N group. We can't differentiate by LoginLock + // because the watch derives that token from cached login + // state (device_id-tied) — two clients restored from the + // same install hand back the same lock GUID. + // + // Instead: at the moment the SECOND session's WebSocket + // arrives, take a snapshot of the EXISTING connection ids + // (which still belong to the original session — we haven't + // joined the group yet) and push a targeted ModerationKick + // to those ids. The watch's OnModerationKick handler at + // PlayerReporting.txt:1857 shows the standard moderation + // dialog and routes through LocalModerationKickSelf, the + // same graceful disconnect path the original Rec Room used + // for real moderator kicks. + // + // Targeting specific connection ids (not the group) means + // the new connection — about to join the group on the next + // line — never receives the kick, even with Redis backplane + // routing across replicas. + var existing = presence.GetConnections(id); + if (existing.Count > 0) + { + logger.LogWarning( + "[hub] second-session connect for player={PlayerId} (conn={NewConn}); kicking {OldCount} existing connection(s)", + id, Context.ConnectionId, existing.Count); + try + { + await notifications.KickConnectionsAsync( + existing, + "Your account was signed in on another device."); + } + catch (Exception ex) + { + logger.LogWarning(ex, "[hub] failed to kick stale connections for player={PlayerId}", id); + } + } + + await Groups.AddToGroupAsync(Context.ConnectionId, GroupForPlayer(id)); + await presence.AddConnectionAsync(id, Context.ConnectionId); + logger.LogInformation( + "[hub] connected player={PlayerId} conn={ConnectionId} group={Group}", + id, Context.ConnectionId, GroupForPlayer(id)); + } + else + { + logger.LogWarning( + "[hub] connected without player id conn={ConnectionId} user={User}", + Context.ConnectionId, Context.User?.Identity?.Name); + } + await base.OnConnectedAsync(); + } + + public override async Task OnDisconnectedAsync(Exception? exception) + { + var pid = TryGetPlayerId(); + if (pid is long id) + { + await presence.RemoveConnectionAsync(id, Context.ConnectionId); + await Groups.RemoveFromGroupAsync(Context.ConnectionId, GroupForPlayer(id)); + logger.LogInformation( + "[hub] disconnected player={PlayerId} conn={ConnectionId} error={Error}", + id, Context.ConnectionId, exception?.Message); + } + else + { + logger.LogInformation( + "[hub] disconnected unknown-player conn={ConnectionId} error={Error}", + Context.ConnectionId, exception?.Message); + } + await base.OnDisconnectedAsync(exception); + } + + public Task SubscribeToPlayers(JsonElement payload) + { + logger.LogInformation( + "[hub] SubscribeToPlayers player={PlayerId} conn={ConnectionId} payload={Payload}", + TryGetPlayerId(), Context.ConnectionId, RawPayload(payload)); + return Task.CompletedTask; + } + + public Task UnsubscribeFromPlayers(JsonElement payload) + { + logger.LogInformation( + "[hub] UnsubscribeFromPlayers player={PlayerId} conn={ConnectionId} payload={Payload}", + TryGetPlayerId(), Context.ConnectionId, RawPayload(payload)); + return Task.CompletedTask; + } + + public Task Subscribe(JsonElement payload) + { + logger.LogInformation( + "[hub] Subscribe player={PlayerId} conn={ConnectionId} payload={Payload}", + TryGetPlayerId(), Context.ConnectionId, RawPayload(payload)); + return Task.CompletedTask; + } + + public Task Unsubscribe(JsonElement payload) + { + logger.LogInformation( + "[hub] Unsubscribe player={PlayerId} conn={ConnectionId} payload={Payload}", + TryGetPlayerId(), Context.ConnectionId, RawPayload(payload)); + return Task.CompletedTask; + } + + private long? TryGetPlayerId() + { + var raw = Context.User?.FindFirstValue(ClaimTypes.NameIdentifier); + return long.TryParse(raw, out var id) ? id : null; + } + + private static string RawPayload(JsonElement payload) + { + try + { + return payload.ValueKind == JsonValueKind.Undefined ? "" : payload.GetRawText(); + } + catch + { + return ""; + } + } +} diff --git a/DorkNet.Server/Migrations/20260525181055_Initial.Designer.cs b/DorkNet.Server/Migrations/20260525181055_Initial.Designer.cs new file mode 100644 index 0000000..f0005f3 --- /dev/null +++ b/DorkNet.Server/Migrations/20260525181055_Initial.Designer.cs @@ -0,0 +1,2868 @@ +// +using System; +using DorkNet.Server.Data; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; + +#nullable disable + +namespace DorkNet.Server.Migrations +{ + [DbContext(typeof(DorkNetDbContext))] + [Migration("20260525181055_Initial")] + partial class Initial + { + /// + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder.HasAnnotation("ProductVersion", "9.0.4"); + + modelBuilder.Entity("DorkNet.Server.Data.Entities.AdminActionEntity", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("Action") + .IsRequired() + .HasMaxLength(64) + .HasColumnType("TEXT"); + + b.Property("AdminPlayerId") + .HasColumnType("INTEGER"); + + b.Property("Reason") + .IsRequired() + .HasMaxLength(512) + .HasColumnType("TEXT"); + + b.Property("TargetId") + .HasColumnType("INTEGER"); + + b.Property("TargetType") + .IsRequired() + .HasMaxLength(32) + .HasColumnType("TEXT"); + + b.Property("Timestamp") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("AdminPlayerId"); + + b.HasIndex("Timestamp"); + + b.HasIndex("TargetType", "TargetId"); + + b.ToTable("AdminActions"); + }); + + modelBuilder.Entity("DorkNet.Server.Data.Entities.AvatarEntity", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("EquippedItemsJson") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("FaceFeatures") + .IsRequired() + .HasMaxLength(8192) + .HasColumnType("TEXT"); + + b.Property("HairColor") + .IsRequired() + .HasMaxLength(64) + .HasColumnType("TEXT"); + + b.Property("InventoryJson") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("OutfitSelections") + .IsRequired() + .HasMaxLength(2048) + .HasColumnType("TEXT"); + + b.Property("PlayerId") + .HasColumnType("INTEGER"); + + b.Property("SavedOutfitsJson") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("SkinColor") + .IsRequired() + .HasMaxLength(64) + .HasColumnType("TEXT"); + + b.Property("UpdatedAt") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("PlayerId") + .IsUnique(); + + b.ToTable("Avatars"); + }); + + modelBuilder.Entity("DorkNet.Server.Data.Entities.BugReportEntity", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("Body") + .IsRequired() + .HasMaxLength(4000) + .HasColumnType("TEXT"); + + b.Property("Category") + .IsRequired() + .HasMaxLength(32) + .HasColumnType("TEXT"); + + b.Property("ClientVersion") + .IsRequired() + .HasMaxLength(64) + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("GameSessionId") + .HasColumnType("INTEGER"); + + b.Property("Platform") + .IsRequired() + .HasMaxLength(32) + .HasColumnType("TEXT"); + + b.Property("ReadAt") + .HasColumnType("TEXT"); + + b.Property("ReporterPlayerId") + .HasColumnType("INTEGER"); + + b.Property("Title") + .IsRequired() + .HasMaxLength(128) + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("ReadAt"); + + b.HasIndex("ReporterPlayerId"); + + b.ToTable("BugReports"); + }); + + modelBuilder.Entity("DorkNet.Server.Data.Entities.CardEntity", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("ActionUrl") + .IsRequired() + .HasMaxLength(512) + .HasColumnType("TEXT"); + + b.Property("Body") + .IsRequired() + .HasMaxLength(2000) + .HasColumnType("TEXT"); + + b.Property("Category") + .IsRequired() + .HasMaxLength(32) + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("ExpiresAt") + .HasColumnType("TEXT"); + + b.Property("ImageName") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("TEXT"); + + b.Property("PlayerId") + .HasColumnType("INTEGER"); + + b.Property("Priority") + .HasColumnType("INTEGER"); + + b.Property("Title") + .IsRequired() + .HasMaxLength(128) + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("ExpiresAt"); + + b.HasIndex("PlayerId"); + + b.ToTable("Cards"); + }); + + modelBuilder.Entity("DorkNet.Server.Data.Entities.ChatMessageEntity", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("Body") + .IsRequired() + .HasMaxLength(2000) + .HasColumnType("TEXT"); + + b.Property("SenderPlayerId") + .HasColumnType("INTEGER"); + + b.Property("SentAt") + .HasColumnType("TEXT"); + + b.Property("ThreadKey") + .IsRequired() + .HasMaxLength(64) + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("ThreadKey", "SentAt"); + + b.ToTable("ChatMessages"); + }); + + modelBuilder.Entity("DorkNet.Server.Data.Entities.ChatThreadEntity", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("CreatorPlayerId") + .HasColumnType("INTEGER"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(128) + .HasColumnType("TEXT"); + + b.Property("ThreadKey") + .IsRequired() + .HasMaxLength(96) + .HasColumnType("TEXT"); + + b.Property("UpdatedAt") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("ThreadKey") + .IsUnique(); + + b.ToTable("ChatThreads"); + }); + + modelBuilder.Entity("DorkNet.Server.Data.Entities.ChatThreadMemberEntity", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("JoinedAt") + .HasColumnType("TEXT"); + + b.Property("LastReadMessageId") + .HasColumnType("INTEGER"); + + b.Property("PlayerId") + .HasColumnType("INTEGER"); + + b.Property("SnoozeUntil") + .HasColumnType("TEXT"); + + b.Property("ThreadKey") + .IsRequired() + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("PlayerId"); + + b.HasIndex("ThreadKey", "PlayerId") + .IsUnique(); + + b.ToTable("ChatThreadMembers"); + }); + + modelBuilder.Entity("DorkNet.Server.Data.Entities.CheerEntity", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("CheeredAt") + .HasColumnType("TEXT"); + + b.Property("FromPlayerId") + .HasColumnType("INTEGER"); + + b.Property("TargetInventionId") + .HasColumnType("INTEGER"); + + b.Property("TargetPhotoId") + .HasColumnType("INTEGER"); + + b.Property("TargetPlayerId") + .HasColumnType("INTEGER"); + + b.Property("TargetRoomId") + .HasColumnType("INTEGER"); + + b.Property("Type") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("TargetPhotoId"); + + b.HasIndex("TargetPlayerId"); + + b.HasIndex("TargetRoomId"); + + b.HasIndex("FromPlayerId", "TargetPlayerId", "TargetRoomId", "TargetPhotoId", "Type") + .IsUnique(); + + b.ToTable("Cheers"); + }); + + modelBuilder.Entity("DorkNet.Server.Data.Entities.ClubAnnouncementEntity", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("AuthorPlayerId") + .HasColumnType("INTEGER"); + + b.Property("Body") + .IsRequired() + .HasMaxLength(4000) + .HasColumnType("TEXT"); + + b.Property("ClubId") + .HasColumnType("INTEGER"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("ImageName") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("TEXT"); + + b.Property("Title") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("TEXT"); + + b.Property("UpdatedAt") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("ClubId", "CreatedAt"); + + b.ToTable("ClubAnnouncements"); + }); + + modelBuilder.Entity("DorkNet.Server.Data.Entities.ClubAnnouncementReadEntity", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("AnnouncementId") + .HasColumnType("INTEGER"); + + b.Property("PlayerId") + .HasColumnType("INTEGER"); + + b.Property("ReadAt") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("PlayerId"); + + b.HasIndex("AnnouncementId", "PlayerId") + .IsUnique(); + + b.ToTable("ClubAnnouncementReads"); + }); + + modelBuilder.Entity("DorkNet.Server.Data.Entities.ClubCategoryAssignmentEntity", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("CategoryTagId") + .HasColumnType("INTEGER"); + + b.Property("ClubId") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("CategoryTagId"); + + b.HasIndex("ClubId", "CategoryTagId") + .IsUnique(); + + b.ToTable("ClubCategoryAssignments"); + }); + + modelBuilder.Entity("DorkNet.Server.Data.Entities.ClubCategoryTagEntity", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("Active") + .HasColumnType("INTEGER"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(64) + .HasColumnType("TEXT"); + + b.Property("OrderIndex") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("Name"); + + b.ToTable("ClubCategoryTags"); + }); + + modelBuilder.Entity("DorkNet.Server.Data.Entities.ClubEntity", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("AllowJuniors") + .HasColumnType("INTEGER"); + + b.Property("BanStatus") + .HasColumnType("INTEGER"); + + b.Property("Category") + .IsRequired() + .HasMaxLength(64) + .HasColumnType("TEXT"); + + b.Property("ClubType") + .HasColumnType("INTEGER"); + + b.Property("ClubhouseRoomId") + .HasColumnType("INTEGER"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("CreatorPlayerId") + .HasColumnType("INTEGER"); + + b.Property("Description") + .IsRequired() + .HasMaxLength(2000) + .HasColumnType("TEXT"); + + b.Property("ImageName") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("TEXT"); + + b.Property("IsRRO") + .HasColumnType("INTEGER"); + + b.Property("Joinability") + .HasColumnType("INTEGER"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(128) + .HasColumnType("TEXT") + .UseCollation("NOCASE"); + + b.Property("State") + .HasColumnType("INTEGER"); + + b.Property("UpdatedAt") + .HasColumnType("TEXT"); + + b.Property("Visibility") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("CreatorPlayerId"); + + b.HasIndex("Name") + .IsUnique(); + + b.ToTable("Clubs"); + }); + + modelBuilder.Entity("DorkNet.Server.Data.Entities.ClubMembershipEntity", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("ClubId") + .HasColumnType("INTEGER"); + + b.Property("JoinedAt") + .HasColumnType("TEXT"); + + b.Property("Permissions") + .HasColumnType("INTEGER"); + + b.Property("PlayerId") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("PlayerId"); + + b.HasIndex("ClubId", "PlayerId") + .IsUnique(); + + b.ToTable("ClubMemberships"); + }); + + modelBuilder.Entity("DorkNet.Server.Data.Entities.ClubSubscriptionEntity", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("ClubId") + .HasColumnType("INTEGER"); + + b.Property("PlayerId") + .HasColumnType("INTEGER"); + + b.Property("SubscribedAt") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("ClubId"); + + b.HasIndex("PlayerId", "ClubId") + .IsUnique(); + + b.ToTable("ClubSubscriptions"); + }); + + modelBuilder.Entity("DorkNet.Server.Data.Entities.CohortAssignmentEntity", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("AssignedAt") + .HasColumnType("TEXT"); + + b.Property("CohortKey") + .IsRequired() + .HasMaxLength(64) + .HasColumnType("TEXT"); + + b.Property("PlayerId") + .HasColumnType("INTEGER"); + + b.Property("Variant") + .IsRequired() + .HasMaxLength(32) + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("PlayerId", "CohortKey") + .IsUnique(); + + b.ToTable("CohortAssignments"); + }); + + modelBuilder.Entity("DorkNet.Server.Data.Entities.CommunityBoardEntity", b => + { + b.Property("Id") + .HasColumnType("INTEGER"); + + b.Property("Json") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("UpdatedAt") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.ToTable("CommunityBoardRows"); + }); + + modelBuilder.Entity("DorkNet.Server.Data.Entities.CouponEntity", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("Code") + .IsRequired() + .HasMaxLength(64) + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("Description") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("TEXT"); + + b.Property("ExpiresAt") + .HasColumnType("TEXT"); + + b.Property("IsActive") + .HasColumnType("INTEGER"); + + b.Property("MaxRedemptions") + .HasColumnType("INTEGER"); + + b.Property("RedemptionCount") + .HasColumnType("INTEGER"); + + b.Property("RewardAmount") + .HasColumnType("INTEGER"); + + b.Property("RewardCurrencyType") + .HasColumnType("INTEGER"); + + b.Property("RewardItemSlug") + .IsRequired() + .HasMaxLength(64) + .HasColumnType("TEXT"); + + b.Property("RewardType") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("Code") + .IsUnique(); + + b.ToTable("Coupons"); + }); + + modelBuilder.Entity("DorkNet.Server.Data.Entities.CouponRedemptionEntity", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("CouponId") + .HasColumnType("INTEGER"); + + b.Property("PlayerId") + .HasColumnType("INTEGER"); + + b.Property("RedeemedAt") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("CouponId", "PlayerId") + .IsUnique(); + + b.ToTable("CouponRedemptions"); + }); + + modelBuilder.Entity("DorkNet.Server.Data.Entities.CurrencyBalanceEntity", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("Balance") + .HasColumnType("INTEGER"); + + b.Property("CurrencyType") + .HasColumnType("INTEGER"); + + b.Property("PlayerId") + .HasColumnType("INTEGER"); + + b.Property("UpdatedAt") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("PlayerId", "CurrencyType") + .IsUnique(); + + b.ToTable("CurrencyBalances"); + }); + + modelBuilder.Entity("DorkNet.Server.Data.Entities.DormStateEntity", b => + { + b.Property("PlayerId") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("CurrentDataBlobName") + .IsRequired() + .HasMaxLength(128) + .HasColumnType("TEXT"); + + b.Property("UpdatedAt") + .HasColumnType("TEXT"); + + b.HasKey("PlayerId"); + + b.ToTable("DormStates"); + }); + + modelBuilder.Entity("DorkNet.Server.Data.Entities.GameRewardSelectionEntity", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("GiftContext") + .HasColumnType("INTEGER"); + + b.Property("Message") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("TEXT"); + + b.Property("PlayerId") + .HasColumnType("INTEGER"); + + b.Property("RewardType") + .HasColumnType("INTEGER"); + + b.Property("SelectedAt") + .HasColumnType("TEXT"); + + b.Property("SelectedGiftDropId") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("PlayerId", "SelectedAt"); + + b.ToTable("GameRewardSelections"); + }); + + modelBuilder.Entity("DorkNet.Server.Data.Entities.GameSessionEntity", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("ActivityLevelId") + .IsRequired() + .HasMaxLength(64) + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("MaxCapacity") + .HasColumnType("INTEGER"); + + b.Property("PhotonRoomName") + .IsRequired() + .HasMaxLength(128) + .HasColumnType("TEXT"); + + b.Property("PlayerCount") + .HasColumnType("INTEGER"); + + b.Property("Region") + .IsRequired() + .HasMaxLength(16) + .HasColumnType("TEXT"); + + b.Property("RoomId") + .IsRequired() + .HasMaxLength(64) + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("RoomId", "Region"); + + b.ToTable("GameSessions"); + }); + + modelBuilder.Entity("DorkNet.Server.Data.Entities.GiftPackageEntity", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("AvatarItemDescOrHairDyeDesc") + .IsRequired() + .HasMaxLength(128) + .HasColumnType("TEXT"); + + b.Property("AvatarItemType") + .HasColumnType("INTEGER"); + + b.Property("ConsumableItemDesc") + .IsRequired() + .HasMaxLength(128) + .HasColumnType("TEXT"); + + b.Property("Consumed") + .HasColumnType("INTEGER"); + + b.Property("ConsumedAt") + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("Currency") + .HasColumnType("INTEGER"); + + b.Property("CurrencyType") + .HasColumnType("INTEGER"); + + b.Property("EquipmentModificationGuid") + .IsRequired() + .HasMaxLength(64) + .HasColumnType("TEXT"); + + b.Property("EquipmentPrefabName") + .IsRequired() + .HasMaxLength(128) + .HasColumnType("TEXT"); + + b.Property("ErrorMessage") + .IsRequired() + .HasMaxLength(512) + .HasColumnType("TEXT"); + + b.Property("FromPlayerId") + .HasColumnType("INTEGER"); + + b.Property("GiftContext") + .HasColumnType("INTEGER"); + + b.Property("GiftRarity") + .HasColumnType("INTEGER"); + + b.Property("IsGifted") + .HasColumnType("INTEGER"); + + b.Property("IsValid") + .HasColumnType("INTEGER"); + + b.Property("Level") + .HasColumnType("INTEGER"); + + b.Property("Message") + .IsRequired() + .HasMaxLength(512) + .HasColumnType("TEXT"); + + b.Property("PackageMaterial") + .IsRequired() + .HasMaxLength(128) + .HasColumnType("TEXT"); + + b.Property("PackageVariant") + .IsRequired() + .HasMaxLength(128) + .HasColumnType("TEXT"); + + b.Property("Platform") + .HasColumnType("INTEGER"); + + b.Property("RecipientPlayerId") + .HasColumnType("INTEGER"); + + b.Property("SupportsCurrentPlatform") + .HasColumnType("INTEGER"); + + b.Property("Xp") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("RecipientPlayerId", "Consumed"); + + b.ToTable("GiftPackages"); + }); + + modelBuilder.Entity("DorkNet.Server.Data.Entities.InventionEntity", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("CheerCount") + .HasColumnType("INTEGER"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("CreationRoomId") + .HasColumnType("INTEGER"); + + b.Property("CreatorPermission") + .HasColumnType("INTEGER"); + + b.Property("CreatorPlayerId") + .HasColumnType("INTEGER"); + + b.Property("CurrentBlobName") + .IsRequired() + .HasMaxLength(128) + .HasColumnType("TEXT"); + + b.Property("CurrentVersionNumber") + .HasColumnType("INTEGER"); + + b.Property("Description") + .IsRequired() + .HasMaxLength(2000) + .HasColumnType("TEXT"); + + b.Property("FirstPublishedAt") + .HasColumnType("TEXT"); + + b.Property("GeneralPermission") + .HasColumnType("INTEGER"); + + b.Property("ImageName") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("TEXT"); + + b.Property("IsAgInvention") + .HasColumnType("INTEGER"); + + b.Property("IsDeleted") + .HasColumnType("INTEGER"); + + b.Property("IsPublished") + .HasColumnType("INTEGER"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(128) + .HasColumnType("TEXT"); + + b.Property("NumPlayersHaveUsedInRoom") + .HasColumnType("INTEGER"); + + b.Property("Permission") + .HasColumnType("INTEGER"); + + b.Property("ReplicationId") + .IsRequired() + .HasMaxLength(64) + .HasColumnType("TEXT"); + + b.Property("SpawnCount") + .HasColumnType("INTEGER"); + + b.Property("TagsCsv") + .IsRequired() + .HasMaxLength(1024) + .HasColumnType("TEXT"); + + b.Property("UpdatedAt") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("CheerCount"); + + b.HasIndex("CreatorPlayerId"); + + b.ToTable("Inventions"); + }); + + modelBuilder.Entity("DorkNet.Server.Data.Entities.InventionVersionEntity", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("BlobName") + .IsRequired() + .HasMaxLength(128) + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("InstantiationCost") + .HasColumnType("INTEGER"); + + b.Property("InventionId") + .HasColumnType("INTEGER"); + + b.Property("LightsCost") + .HasColumnType("INTEGER"); + + b.Property("ReplicationId") + .IsRequired() + .HasMaxLength(64) + .HasColumnType("TEXT"); + + b.Property("VersionNumber") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("InventionId", "VersionNumber") + .IsUnique(); + + b.ToTable("InventionVersions"); + }); + + modelBuilder.Entity("DorkNet.Server.Data.Entities.IpBanEntity", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("BannedAt") + .HasColumnType("TEXT"); + + b.Property("BannedByAdminId") + .HasColumnType("INTEGER"); + + b.Property("Cidr") + .IsRequired() + .HasMaxLength(64) + .HasColumnType("TEXT"); + + b.Property("Reason") + .IsRequired() + .HasMaxLength(512) + .HasColumnType("TEXT"); + + b.Property("Until") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("Cidr"); + + b.ToTable("IpBans"); + }); + + modelBuilder.Entity("DorkNet.Server.Data.Entities.LeaderboardChannelMetaEntity", b => + { + b.Property("Channel") + .HasColumnType("INTEGER"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("LowerIsBetter") + .HasColumnType("INTEGER"); + + b.Property("Name") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("RoomId") + .HasColumnType("INTEGER"); + + b.Property("UpdatedAt") + .HasColumnType("TEXT"); + + b.Property("ValueFormat") + .IsRequired() + .HasColumnType("TEXT"); + + b.HasKey("Channel"); + + b.HasIndex("RoomId"); + + b.ToTable("LeaderboardChannelMeta"); + }); + + modelBuilder.Entity("DorkNet.Server.Data.Entities.LeaderboardStatEntity", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("PlayerId") + .HasColumnType("INTEGER"); + + b.Property("StatChannel") + .HasColumnType("INTEGER"); + + b.Property("UpdatedAt") + .HasColumnType("TEXT"); + + b.Property("Value") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("PlayerId", "StatChannel") + .IsUnique(); + + b.HasIndex("StatChannel", "Value"); + + b.ToTable("LeaderboardStats"); + }); + + modelBuilder.Entity("DorkNet.Server.Data.Entities.LoadingScreenTipEntity", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("Context") + .HasColumnType("INTEGER"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("ImageName") + .IsRequired() + .HasMaxLength(128) + .HasColumnType("TEXT"); + + b.Property("IsActive") + .HasColumnType("INTEGER"); + + b.Property("Message") + .IsRequired() + .HasMaxLength(512) + .HasColumnType("TEXT"); + + b.Property("PlatformMask") + .HasColumnType("INTEGER"); + + b.Property("RoomNamesCsv") + .IsRequired() + .HasMaxLength(512) + .HasColumnType("TEXT"); + + b.Property("SortOrder") + .HasColumnType("INTEGER"); + + b.Property("Title") + .IsRequired() + .HasMaxLength(128) + .HasColumnType("TEXT"); + + b.Property("UpdatedAt") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.ToTable("LoadingScreenTips"); + }); + + modelBuilder.Entity("DorkNet.Server.Data.Entities.MessageEntity", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("Body") + .IsRequired() + .HasMaxLength(2000) + .HasColumnType("TEXT"); + + b.Property("ReadAt") + .HasColumnType("TEXT"); + + b.Property("RecipientPlayerId") + .HasColumnType("INTEGER"); + + b.Property("RoomId") + .HasColumnType("INTEGER"); + + b.Property("SenderPlayerId") + .HasColumnType("INTEGER"); + + b.Property("SentAt") + .HasColumnType("TEXT"); + + b.Property("Type") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("RecipientPlayerId", "SentAt"); + + b.HasIndex("SenderPlayerId", "SentAt"); + + b.ToTable("Messages"); + }); + + modelBuilder.Entity("DorkNet.Server.Data.Entities.NotificationPrefsEntity", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("AllowAll") + .HasColumnType("INTEGER"); + + b.Property("AllowAnnouncements") + .HasColumnType("INTEGER"); + + b.Property("AllowEventInvite") + .HasColumnType("INTEGER"); + + b.Property("AllowFriendRequest") + .HasColumnType("INTEGER"); + + b.Property("AllowMessage") + .HasColumnType("INTEGER"); + + b.Property("Platform") + .IsRequired() + .HasMaxLength(32) + .HasColumnType("TEXT"); + + b.Property("PlayerId") + .HasColumnType("INTEGER"); + + b.Property("UpdatedAt") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("PlayerId", "Platform") + .IsUnique(); + + b.ToTable("NotificationPrefs"); + }); + + modelBuilder.Entity("DorkNet.Server.Data.Entities.ObjectiveProgressEntity", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("ClearedAt") + .HasColumnType("TEXT"); + + b.Property("IsCompleted") + .HasColumnType("INTEGER"); + + b.Property("Key") + .IsRequired() + .HasMaxLength(128) + .HasColumnType("TEXT"); + + b.Property("PlayerId") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("PlayerId", "Key") + .IsUnique(); + + b.ToTable("ObjectiveProgress"); + }); + + modelBuilder.Entity("DorkNet.Server.Data.Entities.PhotoEntity", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("BlobName") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("TEXT"); + + b.Property("Caption") + .IsRequired() + .HasMaxLength(2048) + .HasColumnType("TEXT"); + + b.Property("CheerCount") + .HasColumnType("INTEGER"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("DeletedAt") + .HasColumnType("TEXT"); + + b.Property("IsPublic") + .HasColumnType("INTEGER"); + + b.Property("RoomId") + .HasColumnType("INTEGER"); + + b.Property("TaggedPlayerIdsCsv") + .IsRequired() + .HasMaxLength(2048) + .HasColumnType("TEXT"); + + b.Property("UploaderPlayerId") + .HasColumnType("INTEGER"); + + b.Property("ViewCount") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("BlobName"); + + b.HasIndex("CreatedAt"); + + b.HasIndex("RoomId"); + + b.HasIndex("UploaderPlayerId"); + + b.ToTable("Photos"); + }); + + modelBuilder.Entity("DorkNet.Server.Data.Entities.PlatformIgnoreEntity", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("Platform") + .HasColumnType("INTEGER"); + + b.Property("PlatformUserId") + .IsRequired() + .HasMaxLength(64) + .HasColumnType("TEXT"); + + b.Property("PlayerId") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("PlayerId", "Platform", "PlatformUserId") + .IsUnique(); + + b.ToTable("PlatformIgnores"); + }); + + modelBuilder.Entity("DorkNet.Server.Data.Entities.PlayerDeviceEntity", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("DeviceId") + .IsRequired() + .HasMaxLength(128) + .HasColumnType("TEXT"); + + b.Property("FirstSeen") + .HasColumnType("TEXT"); + + b.Property("IsBanned") + .HasColumnType("INTEGER"); + + b.Property("LastSeen") + .HasColumnType("TEXT"); + + b.Property("Platform") + .IsRequired() + .HasMaxLength(32) + .HasColumnType("TEXT"); + + b.Property("PlayerId") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("DeviceId"); + + b.HasIndex("PlayerId"); + + b.ToTable("PlayerDevices"); + }); + + modelBuilder.Entity("DorkNet.Server.Data.Entities.PlayerEloEntity", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("Elo") + .HasColumnType("INTEGER"); + + b.Property("GameMode") + .HasColumnType("INTEGER"); + + b.Property("Losses") + .HasColumnType("INTEGER"); + + b.Property("MatchesPlayed") + .HasColumnType("INTEGER"); + + b.Property("PlayerId") + .HasColumnType("INTEGER"); + + b.Property("UpdatedAt") + .HasColumnType("TEXT"); + + b.Property("Wins") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("GameMode", "Elo"); + + b.HasIndex("PlayerId", "GameMode") + .IsUnique(); + + b.ToTable("PlayerElo"); + }); + + modelBuilder.Entity("DorkNet.Server.Data.Entities.PlayerEntity", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("BannedUntil") + .HasColumnType("TEXT"); + + b.Property("Bio") + .IsRequired() + .HasMaxLength(512) + .HasColumnType("TEXT"); + + b.Property("Birthday") + .HasColumnType("TEXT"); + + b.Property("CanReceiveInvites") + .HasColumnType("INTEGER"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("CreatedFromDeviceId") + .HasMaxLength(128) + .HasColumnType("TEXT"); + + b.Property("DeviceId") + .IsRequired() + .HasMaxLength(128) + .HasColumnType("TEXT"); + + b.Property("DisplayName") + .IsRequired() + .HasMaxLength(64) + .HasColumnType("TEXT"); + + b.Property("Email") + .HasMaxLength(256) + .HasColumnType("TEXT"); + + b.Property("IsAdmin") + .HasColumnType("INTEGER"); + + b.Property("IsCommunityTeam") + .HasColumnType("INTEGER"); + + b.Property("IsDeveloper") + .HasColumnType("INTEGER"); + + b.Property("IsJunior") + .HasColumnType("INTEGER"); + + b.Property("IsVerified") + .HasColumnType("INTEGER"); + + b.Property("LastIp") + .HasMaxLength(64) + .HasColumnType("TEXT"); + + b.Property("LastPlatform") + .HasColumnType("INTEGER"); + + b.Property("LastPlatformId") + .IsRequired() + .HasMaxLength(128) + .HasColumnType("TEXT"); + + b.Property("LastSeenAt") + .HasColumnType("TEXT"); + + b.Property("Level") + .HasColumnType("INTEGER"); + + b.Property("Locale") + .HasMaxLength(16) + .HasColumnType("TEXT"); + + b.Property("PasswordHash") + .HasMaxLength(128) + .HasColumnType("TEXT"); + + b.Property("Phone") + .HasMaxLength(32) + .HasColumnType("TEXT"); + + b.Property("ProfileImageName") + .HasMaxLength(256) + .HasColumnType("TEXT"); + + b.Property("Reputation") + .HasColumnType("INTEGER"); + + b.Property("Username") + .IsRequired() + .HasMaxLength(64) + .HasColumnType("TEXT"); + + b.Property("XP") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("Username") + .IsUnique(); + + b.ToTable("Players"); + }); + + modelBuilder.Entity("DorkNet.Server.Data.Entities.PlayerEventEntity", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("Capacity") + .HasColumnType("INTEGER"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("CreatorPlayerId") + .HasColumnType("INTEGER"); + + b.Property("Description") + .IsRequired() + .HasMaxLength(2000) + .HasColumnType("TEXT"); + + b.Property("EndsAt") + .HasColumnType("TEXT"); + + b.Property("RoomId") + .HasColumnType("INTEGER"); + + b.Property("StartsAt") + .HasColumnType("TEXT"); + + b.Property("Title") + .IsRequired() + .HasMaxLength(128) + .HasColumnType("TEXT"); + + b.Property("UpdatedAt") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("CreatorPlayerId"); + + b.HasIndex("StartsAt"); + + b.ToTable("PlayerEvents"); + }); + + modelBuilder.Entity("DorkNet.Server.Data.Entities.PlayerEventResponseEntity", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("EventId") + .HasColumnType("INTEGER"); + + b.Property("PlayerId") + .HasColumnType("INTEGER"); + + b.Property("Response") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("PlayerId"); + + b.HasIndex("EventId", "PlayerId") + .IsUnique(); + + b.ToTable("PlayerEventResponses"); + }); + + modelBuilder.Entity("DorkNet.Server.Data.Entities.PlayerInventoryEntity", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("AcquiredAt") + .HasColumnType("TEXT"); + + b.Property("IsActive") + .HasColumnType("INTEGER"); + + b.Property("ItemSlug") + .IsRequired() + .HasMaxLength(64) + .HasColumnType("TEXT"); + + b.Property("PlayerId") + .HasColumnType("INTEGER"); + + b.Property("Quantity") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("PlayerId"); + + b.HasIndex("PlayerId", "ItemSlug") + .IsUnique(); + + b.ToTable("PlayerInventory"); + }); + + modelBuilder.Entity("DorkNet.Server.Data.Entities.PlayerSettingEntity", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("Key") + .IsRequired() + .HasMaxLength(128) + .HasColumnType("TEXT"); + + b.Property("PlayerId") + .HasColumnType("INTEGER"); + + b.Property("Value") + .IsRequired() + .HasMaxLength(1024) + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("PlayerId", "Key") + .IsUnique(); + + b.ToTable("PlayerSettings"); + }); + + modelBuilder.Entity("DorkNet.Server.Data.Entities.PlaylistEntity", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("CheerCount") + .HasColumnType("INTEGER"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("CreatorPlayerId") + .HasColumnType("INTEGER"); + + b.Property("Description") + .IsRequired() + .HasMaxLength(2000) + .HasColumnType("TEXT"); + + b.Property("FavoriteCount") + .HasColumnType("INTEGER"); + + b.Property("ImageName") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("TEXT"); + + b.Property("IsCurated") + .HasColumnType("INTEGER"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(128) + .HasColumnType("TEXT") + .UseCollation("NOCASE"); + + b.Property("OrderIndex") + .HasColumnType("INTEGER"); + + b.Property("TagsCsv") + .IsRequired() + .HasMaxLength(1024) + .HasColumnType("TEXT"); + + b.Property("UpdatedAt") + .HasColumnType("TEXT"); + + b.Property("VisitCount") + .HasColumnType("INTEGER"); + + b.Property("VisitorCount") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("CreatorPlayerId"); + + b.HasIndex("IsCurated"); + + b.HasIndex("Name"); + + b.ToTable("Playlists"); + }); + + modelBuilder.Entity("DorkNet.Server.Data.Entities.PlaylistInteractionEntity", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("Cheered") + .HasColumnType("INTEGER"); + + b.Property("Favorited") + .HasColumnType("INTEGER"); + + b.Property("PlayerId") + .HasColumnType("INTEGER"); + + b.Property("PlaylistId") + .HasColumnType("INTEGER"); + + b.Property("UpdatedAt") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("PlayerId"); + + b.HasIndex("PlaylistId", "PlayerId") + .IsUnique(); + + b.ToTable("PlaylistInteractions"); + }); + + modelBuilder.Entity("DorkNet.Server.Data.Entities.PlaylistRoomEntity", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("OrderIndex") + .HasColumnType("INTEGER"); + + b.Property("PlaylistId") + .HasColumnType("INTEGER"); + + b.Property("RoomId") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("PlaylistId"); + + b.HasIndex("RoomId"); + + b.HasIndex("PlaylistId", "OrderIndex"); + + b.ToTable("PlaylistRooms"); + }); + + modelBuilder.Entity("DorkNet.Server.Data.Entities.PrivateInstanceEntity", b => + { + b.Property("Id") + .HasColumnType("INTEGER"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("DataBlob") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("TEXT"); + + b.Property("Location") + .IsRequired() + .HasMaxLength(64) + .HasColumnType("TEXT"); + + b.Property("MaxCapacity") + .HasColumnType("INTEGER"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(128) + .HasColumnType("TEXT"); + + b.Property("OwnerPlayerId") + .HasColumnType("INTEGER"); + + b.Property("PhotonRegion") + .IsRequired() + .HasMaxLength(16) + .HasColumnType("TEXT"); + + b.Property("PhotonRoomId") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("TEXT"); + + b.Property("RoomId") + .HasColumnType("INTEGER"); + + b.Property("SubRoomId") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("OwnerPlayerId"); + + b.HasIndex("RoomId", "SubRoomId"); + + b.ToTable("PrivateInstances"); + }); + + modelBuilder.Entity("DorkNet.Server.Data.Entities.PrivateInstanceInviteeEntity", b => + { + b.Property("PrivateInstanceId") + .HasColumnType("INTEGER"); + + b.Property("PlayerId") + .HasColumnType("INTEGER"); + + b.Property("InvitedAt") + .HasColumnType("TEXT"); + + b.Property("LatestInviteMessageId") + .HasColumnType("INTEGER"); + + b.HasKey("PrivateInstanceId", "PlayerId"); + + b.HasIndex("PlayerId"); + + b.ToTable("PrivateInstanceInvitees"); + }); + + modelBuilder.Entity("DorkNet.Server.Data.Entities.PushTokenEntity", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("Platform") + .IsRequired() + .HasMaxLength(16) + .HasColumnType("TEXT"); + + b.Property("PlayerId") + .HasColumnType("INTEGER"); + + b.Property("Token") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("TEXT"); + + b.Property("UpdatedAt") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("Token"); + + b.HasIndex("PlayerId", "Platform") + .IsUnique(); + + b.ToTable("PushTokens"); + }); + + modelBuilder.Entity("DorkNet.Server.Data.Entities.RelationshipEntity", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("Favorited") + .HasColumnType("INTEGER"); + + b.Property("Ignored") + .HasColumnType("INTEGER"); + + b.Property("Muted") + .HasColumnType("INTEGER"); + + b.Property("RequesterId") + .HasColumnType("INTEGER"); + + b.Property("Status") + .HasColumnType("INTEGER"); + + b.Property("TargetId") + .HasColumnType("INTEGER"); + + b.Property("UpdatedAt") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("RequesterId", "TargetId") + .IsUnique(); + + b.ToTable("Relationships"); + }); + + modelBuilder.Entity("DorkNet.Server.Data.Entities.ReportEntity", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("Category") + .HasColumnType("INTEGER"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("GameSessionId") + .HasColumnType("INTEGER"); + + b.Property("Message") + .IsRequired() + .HasMaxLength(1000) + .HasColumnType("TEXT"); + + b.Property("ReporterPlayerId") + .HasColumnType("INTEGER"); + + b.Property("ResolutionNote") + .IsRequired() + .HasMaxLength(512) + .HasColumnType("TEXT"); + + b.Property("ResolvedAt") + .HasColumnType("TEXT"); + + b.Property("ResolverAdminId") + .HasColumnType("INTEGER"); + + b.Property("RoomId") + .HasColumnType("INTEGER"); + + b.Property("TargetEventId") + .HasColumnType("INTEGER"); + + b.Property("TargetInventionId") + .HasColumnType("INTEGER"); + + b.Property("TargetPlayerId") + .HasColumnType("INTEGER"); + + b.Property("TargetRoomId") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("ReporterPlayerId"); + + b.HasIndex("ResolvedAt"); + + b.HasIndex("TargetPlayerId"); + + b.ToTable("Reports"); + }); + + modelBuilder.Entity("DorkNet.Server.Data.Entities.RoomBanEntity", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("BanType") + .HasColumnType("INTEGER"); + + b.Property("BannedByPlayerId") + .HasColumnType("INTEGER"); + + b.Property("BannedPlayerId") + .HasColumnType("INTEGER"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("Reason") + .IsRequired() + .HasMaxLength(512) + .HasColumnType("TEXT"); + + b.Property("RoomId") + .HasColumnType("INTEGER"); + + b.Property("Until") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("BannedPlayerId"); + + b.HasIndex("RoomId", "BannedPlayerId"); + + b.ToTable("RoomBans"); + }); + + modelBuilder.Entity("DorkNet.Server.Data.Entities.RoomBookmarkEntity", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("PlayerId") + .HasColumnType("INTEGER"); + + b.Property("RoomId") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("PlayerId"); + + b.HasIndex("PlayerId", "RoomId") + .IsUnique(); + + b.ToTable("RoomBookmarks"); + }); + + modelBuilder.Entity("DorkNet.Server.Data.Entities.RoomDataBlobEntity", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("BlobName") + .IsRequired() + .HasMaxLength(128) + .HasColumnType("TEXT"); + + b.Property("Bytes") + .HasColumnType("BLOB"); + + b.Property("ReferencedFilenamesCsv") + .IsRequired() + .HasMaxLength(2048) + .HasColumnType("TEXT"); + + b.Property("RoomId") + .HasColumnType("INTEGER"); + + b.Property("SubRoomId") + .HasColumnType("INTEGER"); + + b.Property("UploadedAt") + .HasColumnType("TEXT"); + + b.Property("UploadedByPlayerId") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("BlobName") + .IsUnique(); + + b.HasIndex("RoomId"); + + b.ToTable("RoomDataBlobs"); + }); + + modelBuilder.Entity("DorkNet.Server.Data.Entities.RoomEntity", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("Accessibility") + .HasColumnType("INTEGER"); + + b.Property("AllowsJuniors") + .HasColumnType("INTEGER"); + + b.Property("CheerCount") + .HasColumnType("INTEGER"); + + b.Property("CloningAllowed") + .HasColumnType("INTEGER"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("CreatorPlayerId") + .HasColumnType("INTEGER"); + + b.Property("CurrentDataBlobName") + .IsRequired() + .HasMaxLength(128) + .HasColumnType("TEXT"); + + b.Property("CustomRoomWarning") + .IsRequired() + .HasMaxLength(512) + .HasColumnType("TEXT"); + + b.Property("Description") + .IsRequired() + .HasMaxLength(2000) + .HasColumnType("TEXT"); + + b.Property("DisableMicAutoMute") + .HasColumnType("INTEGER"); + + b.Property("FavoriteCount") + .HasColumnType("INTEGER"); + + b.Property("HiddenFromBrowse") + .HasColumnType("INTEGER"); + + b.Property("HotScore") + .HasColumnType("REAL"); + + b.Property("ImageName") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("TEXT"); + + b.Property("IsAGRoom") + .HasColumnType("INTEGER"); + + b.Property("IsDormRoom") + .HasColumnType("INTEGER"); + + b.Property("LocationReplicationId") + .IsRequired() + .HasMaxLength(64) + .HasColumnType("TEXT"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(128) + .HasColumnType("TEXT") + .UseCollation("NOCASE"); + + b.Property("RoomWarningMask") + .HasColumnType("INTEGER"); + + b.Property("State") + .HasColumnType("INTEGER"); + + b.Property("SupportsLevelVoting") + .HasColumnType("INTEGER"); + + b.Property("SupportsMobile") + .HasColumnType("INTEGER"); + + b.Property("SupportsScreens") + .HasColumnType("INTEGER"); + + b.Property("SupportsTeleportVR") + .HasColumnType("INTEGER"); + + b.Property("SupportsVRLow") + .HasColumnType("INTEGER"); + + b.Property("SupportsWalkVR") + .HasColumnType("INTEGER"); + + b.Property("TagsCsv") + .IsRequired() + .HasMaxLength(1024) + .HasColumnType("TEXT"); + + b.Property("UpdatedAt") + .HasColumnType("TEXT"); + + b.Property("VisitCount") + .HasColumnType("INTEGER"); + + b.Property("VisitorCount") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("CreatorPlayerId"); + + b.HasIndex("HotScore"); + + b.HasIndex("Name") + .IsUnique(); + + b.ToTable("Rooms"); + }); + + modelBuilder.Entity("DorkNet.Server.Data.Entities.RoomKeyEntity", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("CreatorPlayerId") + .HasColumnType("INTEGER"); + + b.Property("Description") + .IsRequired() + .HasMaxLength(174) + .HasColumnType("TEXT"); + + b.Property("IsDeleted") + .HasColumnType("INTEGER"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(40) + .HasColumnType("TEXT"); + + b.Property("Price") + .HasColumnType("INTEGER"); + + b.Property("ReplicationId") + .IsRequired() + .HasMaxLength(64) + .HasColumnType("TEXT"); + + b.Property("RoomId") + .HasColumnType("INTEGER"); + + b.Property("UpdatedAt") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("CreatorPlayerId"); + + b.HasIndex("RoomId"); + + b.HasIndex("RoomId", "Name"); + + b.ToTable("RoomKeys"); + }); + + modelBuilder.Entity("DorkNet.Server.Data.Entities.RoomKeyPurchaseEntity", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("PaidPrice") + .HasColumnType("INTEGER"); + + b.Property("PlayerId") + .HasColumnType("INTEGER"); + + b.Property("PurchasedAt") + .HasColumnType("TEXT"); + + b.Property("RoomKeyId") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("PlayerId"); + + b.HasIndex("RoomKeyId", "PlayerId") + .IsUnique(); + + b.ToTable("RoomKeyPurchases"); + }); + + modelBuilder.Entity("DorkNet.Server.Data.Entities.RoomRoleEntity", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("Accepted") + .HasColumnType("INTEGER"); + + b.Property("GrantedAt") + .HasColumnType("TEXT"); + + b.Property("GrantedByPlayerId") + .HasColumnType("INTEGER"); + + b.Property("PlayerId") + .HasColumnType("INTEGER"); + + b.Property("Role") + .HasColumnType("INTEGER"); + + b.Property("RoomId") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("RoomId"); + + b.HasIndex("RoomId", "PlayerId", "Role") + .IsUnique(); + + b.ToTable("RoomRoles"); + }); + + modelBuilder.Entity("DorkNet.Server.Data.Entities.RoomSceneEntity", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("CanMatchmakeInto") + .HasColumnType("INTEGER"); + + b.Property("DataBlobName") + .IsRequired() + .HasMaxLength(128) + .HasColumnType("TEXT"); + + b.Property("DataModifiedAt") + .HasColumnType("TEXT"); + + b.Property("IsSandbox") + .HasColumnType("INTEGER"); + + b.Property("MaxPlayers") + .HasColumnType("INTEGER"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(128) + .HasColumnType("TEXT") + .UseCollation("NOCASE"); + + b.Property("OrderIndex") + .HasColumnType("INTEGER"); + + b.Property("RoomId") + .HasColumnType("INTEGER"); + + b.Property("RoomSceneLocationId") + .IsRequired() + .HasMaxLength(64) + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("RoomId", "Name"); + + b.HasIndex("RoomId", "OrderIndex") + .IsUnique(); + + b.ToTable("RoomScenes"); + }); + + modelBuilder.Entity("DorkNet.Server.Data.Entities.RoomVisitEntity", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("FirstVisitAt") + .HasColumnType("TEXT"); + + b.Property("LastVisitAt") + .HasColumnType("TEXT"); + + b.Property("PlayerId") + .HasColumnType("INTEGER"); + + b.Property("RoomId") + .HasColumnType("INTEGER"); + + b.Property("VisitCount") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("PlayerId"); + + b.HasIndex("RoomId", "LastVisitAt"); + + b.HasIndex("RoomId", "PlayerId") + .IsUnique(); + + b.ToTable("RoomVisits"); + }); + + modelBuilder.Entity("DorkNet.Server.Data.Entities.RoyaleMatchEntity", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("ChestsOpened") + .HasColumnType("INTEGER"); + + b.Property("CompletedAt") + .HasColumnType("TEXT"); + + b.Property("CustomGame") + .HasColumnType("INTEGER"); + + b.Property("HealthPotionsConsumed") + .HasColumnType("INTEGER"); + + b.Property("NumEliminations") + .HasColumnType("INTEGER"); + + b.Property("Rank") + .HasColumnType("INTEGER"); + + b.Property("SecondsAlive") + .HasColumnType("INTEGER"); + + b.Property("SecondsInAir") + .HasColumnType("INTEGER"); + + b.Property("ShieldPotionsConsumed") + .HasColumnType("INTEGER"); + + b.Property("WalkGame") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("CompletedAt"); + + b.ToTable("RoyaleMatches"); + }); + + modelBuilder.Entity("DorkNet.Server.Data.Entities.RoyaleMatchPlayerEntity", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("MatchId") + .HasColumnType("INTEGER"); + + b.Property("NumEliminations") + .HasColumnType("INTEGER"); + + b.Property("PlayerId") + .HasColumnType("INTEGER"); + + b.Property("Rank") + .HasColumnType("INTEGER"); + + b.Property("SecondsAlive") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("PlayerId"); + + b.HasIndex("MatchId", "PlayerId"); + + b.ToTable("RoyaleMatchPlayers"); + }); + + modelBuilder.Entity("DorkNet.Server.Data.Entities.RoyalePlayerProgressEntity", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("CurrentLevelXPThreshold") + .HasColumnType("INTEGER"); + + b.Property("Level") + .HasColumnType("INTEGER"); + + b.Property("NextLevelAcornReward") + .HasColumnType("INTEGER"); + + b.Property("NextLevelXPThreshold") + .HasColumnType("INTEGER"); + + b.Property("PlayerId") + .HasColumnType("INTEGER"); + + b.Property("RankIdx") + .HasColumnType("INTEGER"); + + b.Property("RankName") + .IsRequired() + .HasMaxLength(64) + .HasColumnType("TEXT"); + + b.Property("TotalXP") + .HasColumnType("INTEGER"); + + b.Property("UpdatedAt") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("PlayerId") + .IsUnique(); + + b.ToTable("RoyalePlayerProgress"); + }); + + modelBuilder.Entity("DorkNet.Server.Data.Entities.ServerSettingsEntity", b => + { + b.Property("Id") + .HasColumnType("INTEGER"); + + b.Property("SignupsDisabled") + .HasColumnType("INTEGER"); + + b.Property("UpdatedAt") + .HasColumnType("TEXT"); + + b.Property("WeeklyChallengeRewardJson") + .IsRequired() + .ValueGeneratedOnAdd() + .HasColumnType("TEXT") + .HasDefaultValue(""); + + b.Property("WeeklyChallengesCompletedRequired") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER") + .HasDefaultValue(true); + + b.Property("WeeklyChallengesJson") + .IsRequired() + .ValueGeneratedOnAdd() + .HasColumnType("TEXT") + .HasDefaultValue(""); + + b.HasKey("Id"); + + b.ToTable("ServerSettings"); + }); + + modelBuilder.Entity("DorkNet.Server.Data.Entities.StoreItemEntity", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("AvailableUntil") + .HasColumnType("TEXT"); + + b.Property("Category") + .IsRequired() + .HasMaxLength(32) + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("CurrencyType") + .HasColumnType("INTEGER"); + + b.Property("Description") + .IsRequired() + .HasMaxLength(1024) + .HasColumnType("TEXT"); + + b.Property("DisplayName") + .IsRequired() + .HasMaxLength(128) + .HasColumnType("TEXT"); + + b.Property("ImageName") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("TEXT"); + + b.Property("IsActive") + .HasColumnType("INTEGER"); + + b.Property("IsLimitedTime") + .HasColumnType("INTEGER"); + + b.Property("Price") + .HasColumnType("INTEGER"); + + b.Property("Slug") + .IsRequired() + .HasMaxLength(128) + .HasColumnType("TEXT"); + + b.Property("Storefront") + .IsRequired() + .HasMaxLength(32) + .HasColumnType("TEXT"); + + b.Property("UpdatedAt") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("Slug") + .IsUnique(); + + b.HasIndex("Storefront"); + + b.HasIndex("IsActive", "Category"); + + b.ToTable("StoreItems"); + }); + + modelBuilder.Entity("DorkNet.Server.Data.Entities.SubscriptionEntity", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("SubscriberPlayerId") + .HasColumnType("INTEGER"); + + b.Property("TargetPlayerId") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("SubscriberPlayerId"); + + b.HasIndex("TargetPlayerId"); + + b.HasIndex("SubscriberPlayerId", "TargetPlayerId") + .IsUnique(); + + b.ToTable("Subscriptions"); + }); + + modelBuilder.Entity("DorkNet.Server.Data.Entities.TestCaseEntity", b => + { + b.Property("Pk") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("AssignedPlayerIdsJson") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("AssignedPlayerNamesJson") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("Description") + .IsRequired() + .HasMaxLength(2000) + .HasColumnType("TEXT"); + + b.Property("Id") + .HasMaxLength(64) + .HasColumnType("TEXT"); + + b.Property("JiraBugUrl") + .IsRequired() + .HasMaxLength(512) + .HasColumnType("TEXT"); + + b.Property("JiraUrl") + .IsRequired() + .HasMaxLength(512) + .HasColumnType("TEXT"); + + b.Property("Key") + .IsRequired() + .HasMaxLength(64) + .HasColumnType("TEXT"); + + b.Property("MinNumAssignedPlayers") + .HasColumnType("INTEGER"); + + b.Property("RoomName") + .IsRequired() + .HasMaxLength(128) + .HasColumnType("TEXT"); + + b.Property("Status") + .HasColumnType("INTEGER"); + + b.Property("TagsJson") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("TestPassId") + .HasColumnType("INTEGER"); + + b.Property("Title") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("TEXT"); + + b.Property("UpdatedAt") + .HasColumnType("TEXT"); + + b.HasKey("Pk"); + + b.HasIndex("Id") + .IsUnique(); + + b.HasIndex("Status"); + + b.HasIndex("TestPassId"); + + b.ToTable("TestCases"); + }); + + modelBuilder.Entity("DorkNet.Server.Data.Entities.TestPassEntity", b => + { + b.Property("Id") + .HasColumnType("INTEGER"); + + b.Property("Description") + .IsRequired() + .HasMaxLength(2000) + .HasColumnType("TEXT"); + + b.Property("EndDate") + .HasColumnType("TEXT"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(128) + .HasColumnType("TEXT"); + + b.Property("StartDate") + .HasColumnType("TEXT"); + + b.Property("TagsJson") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("WasManuallyClosed") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.ToTable("TestPasses"); + }); + + modelBuilder.Entity("DorkNet.Server.Data.Entities.AvatarEntity", b => + { + b.HasOne("DorkNet.Server.Data.Entities.PlayerEntity", "Player") + .WithOne("Avatar") + .HasForeignKey("DorkNet.Server.Data.Entities.AvatarEntity", "PlayerId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Player"); + }); + + modelBuilder.Entity("DorkNet.Server.Data.Entities.PlayerSettingEntity", b => + { + b.HasOne("DorkNet.Server.Data.Entities.PlayerEntity", "Player") + .WithMany("Settings") + .HasForeignKey("PlayerId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Player"); + }); + + modelBuilder.Entity("DorkNet.Server.Data.Entities.RelationshipEntity", b => + { + b.HasOne("DorkNet.Server.Data.Entities.PlayerEntity", "Requester") + .WithMany("Relationships") + .HasForeignKey("RequesterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Requester"); + }); + + modelBuilder.Entity("DorkNet.Server.Data.Entities.PlayerEntity", b => + { + b.Navigation("Avatar"); + + b.Navigation("Relationships"); + + b.Navigation("Settings"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/DorkNet.Server/Migrations/20260525181055_Initial.cs b/DorkNet.Server/Migrations/20260525181055_Initial.cs new file mode 100644 index 0000000..0389cb9 --- /dev/null +++ b/DorkNet.Server/Migrations/20260525181055_Initial.cs @@ -0,0 +1,2151 @@ +using System; +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace DorkNet.Server.Migrations +{ + /// + public partial class Initial : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.CreateTable( + name: "AdminActions", + columns: table => new + { + Id = table.Column(type: "INTEGER", nullable: false) + .Annotation("Sqlite:Autoincrement", true), + AdminPlayerId = table.Column(type: "INTEGER", nullable: false), + Action = table.Column(type: "TEXT", maxLength: 64, nullable: false), + TargetType = table.Column(type: "TEXT", maxLength: 32, nullable: false), + TargetId = table.Column(type: "INTEGER", nullable: false), + Reason = table.Column(type: "TEXT", maxLength: 512, nullable: false), + Timestamp = table.Column(type: "TEXT", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_AdminActions", x => x.Id); + }); + + migrationBuilder.CreateTable( + name: "BugReports", + columns: table => new + { + Id = table.Column(type: "INTEGER", nullable: false) + .Annotation("Sqlite:Autoincrement", true), + ReporterPlayerId = table.Column(type: "INTEGER", nullable: false), + Title = table.Column(type: "TEXT", maxLength: 128, nullable: false), + Body = table.Column(type: "TEXT", maxLength: 4000, nullable: false), + GameSessionId = table.Column(type: "INTEGER", nullable: false), + ClientVersion = table.Column(type: "TEXT", maxLength: 64, nullable: false), + Platform = table.Column(type: "TEXT", maxLength: 32, nullable: false), + Category = table.Column(type: "TEXT", maxLength: 32, nullable: false), + CreatedAt = table.Column(type: "TEXT", nullable: false), + ReadAt = table.Column(type: "TEXT", nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_BugReports", x => x.Id); + }); + + migrationBuilder.CreateTable( + name: "Cards", + columns: table => new + { + Id = table.Column(type: "INTEGER", nullable: false) + .Annotation("Sqlite:Autoincrement", true), + PlayerId = table.Column(type: "INTEGER", nullable: true), + Title = table.Column(type: "TEXT", maxLength: 128, nullable: false), + Body = table.Column(type: "TEXT", maxLength: 2000, nullable: false), + ImageName = table.Column(type: "TEXT", maxLength: 256, nullable: false), + ActionUrl = table.Column(type: "TEXT", maxLength: 512, nullable: false), + Category = table.Column(type: "TEXT", maxLength: 32, nullable: false), + Priority = table.Column(type: "INTEGER", nullable: false), + CreatedAt = table.Column(type: "TEXT", nullable: false), + ExpiresAt = table.Column(type: "TEXT", nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_Cards", x => x.Id); + }); + + migrationBuilder.CreateTable( + name: "ChatMessages", + columns: table => new + { + Id = table.Column(type: "INTEGER", nullable: false) + .Annotation("Sqlite:Autoincrement", true), + ThreadKey = table.Column(type: "TEXT", maxLength: 64, nullable: false), + SenderPlayerId = table.Column(type: "INTEGER", nullable: false), + Body = table.Column(type: "TEXT", maxLength: 2000, nullable: false), + SentAt = table.Column(type: "TEXT", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_ChatMessages", x => x.Id); + }); + + migrationBuilder.CreateTable( + name: "ChatThreadMembers", + columns: table => new + { + Id = table.Column(type: "INTEGER", nullable: false) + .Annotation("Sqlite:Autoincrement", true), + ThreadKey = table.Column(type: "TEXT", nullable: false), + PlayerId = table.Column(type: "INTEGER", nullable: false), + JoinedAt = table.Column(type: "TEXT", nullable: false), + SnoozeUntil = table.Column(type: "TEXT", nullable: true), + LastReadMessageId = table.Column(type: "INTEGER", nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_ChatThreadMembers", x => x.Id); + }); + + migrationBuilder.CreateTable( + name: "ChatThreads", + columns: table => new + { + Id = table.Column(type: "INTEGER", nullable: false) + .Annotation("Sqlite:Autoincrement", true), + ThreadKey = table.Column(type: "TEXT", maxLength: 96, nullable: false), + Name = table.Column(type: "TEXT", maxLength: 128, nullable: false), + CreatorPlayerId = table.Column(type: "INTEGER", nullable: false), + CreatedAt = table.Column(type: "TEXT", nullable: false), + UpdatedAt = table.Column(type: "TEXT", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_ChatThreads", x => x.Id); + }); + + migrationBuilder.CreateTable( + name: "Cheers", + columns: table => new + { + Id = table.Column(type: "INTEGER", nullable: false) + .Annotation("Sqlite:Autoincrement", true), + FromPlayerId = table.Column(type: "INTEGER", nullable: false), + TargetPlayerId = table.Column(type: "INTEGER", nullable: false), + TargetRoomId = table.Column(type: "INTEGER", nullable: false), + TargetPhotoId = table.Column(type: "INTEGER", nullable: false), + TargetInventionId = table.Column(type: "INTEGER", nullable: false), + Type = table.Column(type: "INTEGER", nullable: false), + CheeredAt = table.Column(type: "TEXT", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_Cheers", x => x.Id); + }); + + migrationBuilder.CreateTable( + name: "ClubAnnouncementReads", + columns: table => new + { + Id = table.Column(type: "INTEGER", nullable: false) + .Annotation("Sqlite:Autoincrement", true), + AnnouncementId = table.Column(type: "INTEGER", nullable: false), + PlayerId = table.Column(type: "INTEGER", nullable: false), + ReadAt = table.Column(type: "TEXT", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_ClubAnnouncementReads", x => x.Id); + }); + + migrationBuilder.CreateTable( + name: "ClubAnnouncements", + columns: table => new + { + Id = table.Column(type: "INTEGER", nullable: false) + .Annotation("Sqlite:Autoincrement", true), + ClubId = table.Column(type: "INTEGER", nullable: false), + AuthorPlayerId = table.Column(type: "INTEGER", nullable: false), + Title = table.Column(type: "TEXT", maxLength: 200, nullable: false), + Body = table.Column(type: "TEXT", maxLength: 4000, nullable: false), + ImageName = table.Column(type: "TEXT", maxLength: 256, nullable: false), + CreatedAt = table.Column(type: "TEXT", nullable: false), + UpdatedAt = table.Column(type: "TEXT", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_ClubAnnouncements", x => x.Id); + }); + + migrationBuilder.CreateTable( + name: "ClubCategoryAssignments", + columns: table => new + { + Id = table.Column(type: "INTEGER", nullable: false) + .Annotation("Sqlite:Autoincrement", true), + ClubId = table.Column(type: "INTEGER", nullable: false), + CategoryTagId = table.Column(type: "INTEGER", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_ClubCategoryAssignments", x => x.Id); + }); + + migrationBuilder.CreateTable( + name: "ClubCategoryTags", + columns: table => new + { + Id = table.Column(type: "INTEGER", nullable: false) + .Annotation("Sqlite:Autoincrement", true), + Name = table.Column(type: "TEXT", maxLength: 64, nullable: false), + OrderIndex = table.Column(type: "INTEGER", nullable: false), + Active = table.Column(type: "INTEGER", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_ClubCategoryTags", x => x.Id); + }); + + migrationBuilder.CreateTable( + name: "ClubMemberships", + columns: table => new + { + Id = table.Column(type: "INTEGER", nullable: false) + .Annotation("Sqlite:Autoincrement", true), + ClubId = table.Column(type: "INTEGER", nullable: false), + PlayerId = table.Column(type: "INTEGER", nullable: false), + Permissions = table.Column(type: "INTEGER", nullable: false), + JoinedAt = table.Column(type: "TEXT", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_ClubMemberships", x => x.Id); + }); + + migrationBuilder.CreateTable( + name: "Clubs", + columns: table => new + { + Id = table.Column(type: "INTEGER", nullable: false) + .Annotation("Sqlite:Autoincrement", true), + Name = table.Column(type: "TEXT", maxLength: 128, nullable: false, collation: "NOCASE"), + Description = table.Column(type: "TEXT", maxLength: 2000, nullable: false), + CreatorPlayerId = table.Column(type: "INTEGER", nullable: false), + ImageName = table.Column(type: "TEXT", maxLength: 256, nullable: false), + BanStatus = table.Column(type: "INTEGER", nullable: false), + State = table.Column(type: "INTEGER", nullable: false), + Category = table.Column(type: "TEXT", maxLength: 64, nullable: false), + Visibility = table.Column(type: "INTEGER", nullable: false), + Joinability = table.Column(type: "INTEGER", nullable: false), + AllowJuniors = table.Column(type: "INTEGER", nullable: false), + IsRRO = table.Column(type: "INTEGER", nullable: false), + ClubhouseRoomId = table.Column(type: "INTEGER", nullable: true), + ClubType = table.Column(type: "INTEGER", nullable: false), + CreatedAt = table.Column(type: "TEXT", nullable: false), + UpdatedAt = table.Column(type: "TEXT", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_Clubs", x => x.Id); + }); + + migrationBuilder.CreateTable( + name: "ClubSubscriptions", + columns: table => new + { + Id = table.Column(type: "INTEGER", nullable: false) + .Annotation("Sqlite:Autoincrement", true), + ClubId = table.Column(type: "INTEGER", nullable: false), + PlayerId = table.Column(type: "INTEGER", nullable: false), + SubscribedAt = table.Column(type: "TEXT", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_ClubSubscriptions", x => x.Id); + }); + + migrationBuilder.CreateTable( + name: "CohortAssignments", + columns: table => new + { + Id = table.Column(type: "INTEGER", nullable: false) + .Annotation("Sqlite:Autoincrement", true), + PlayerId = table.Column(type: "INTEGER", nullable: false), + CohortKey = table.Column(type: "TEXT", maxLength: 64, nullable: false), + Variant = table.Column(type: "TEXT", maxLength: 32, nullable: false), + AssignedAt = table.Column(type: "TEXT", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_CohortAssignments", x => x.Id); + }); + + migrationBuilder.CreateTable( + name: "CommunityBoardRows", + columns: table => new + { + Id = table.Column(type: "INTEGER", nullable: false), + Json = table.Column(type: "TEXT", nullable: false), + UpdatedAt = table.Column(type: "TEXT", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_CommunityBoardRows", x => x.Id); + }); + + migrationBuilder.CreateTable( + name: "CouponRedemptions", + columns: table => new + { + Id = table.Column(type: "INTEGER", nullable: false) + .Annotation("Sqlite:Autoincrement", true), + CouponId = table.Column(type: "INTEGER", nullable: false), + PlayerId = table.Column(type: "INTEGER", nullable: false), + RedeemedAt = table.Column(type: "TEXT", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_CouponRedemptions", x => x.Id); + }); + + migrationBuilder.CreateTable( + name: "Coupons", + columns: table => new + { + Id = table.Column(type: "INTEGER", nullable: false) + .Annotation("Sqlite:Autoincrement", true), + Code = table.Column(type: "TEXT", maxLength: 64, nullable: false), + Description = table.Column(type: "TEXT", maxLength: 256, nullable: false), + RewardType = table.Column(type: "INTEGER", nullable: false), + RewardCurrencyType = table.Column(type: "INTEGER", nullable: false), + RewardAmount = table.Column(type: "INTEGER", nullable: false), + RewardItemSlug = table.Column(type: "TEXT", maxLength: 64, nullable: false), + IsActive = table.Column(type: "INTEGER", nullable: false), + MaxRedemptions = table.Column(type: "INTEGER", nullable: false), + RedemptionCount = table.Column(type: "INTEGER", nullable: false), + ExpiresAt = table.Column(type: "TEXT", nullable: true), + CreatedAt = table.Column(type: "TEXT", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_Coupons", x => x.Id); + }); + + migrationBuilder.CreateTable( + name: "CurrencyBalances", + columns: table => new + { + Id = table.Column(type: "INTEGER", nullable: false) + .Annotation("Sqlite:Autoincrement", true), + PlayerId = table.Column(type: "INTEGER", nullable: false), + CurrencyType = table.Column(type: "INTEGER", nullable: false), + Balance = table.Column(type: "INTEGER", nullable: false), + UpdatedAt = table.Column(type: "TEXT", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_CurrencyBalances", x => x.Id); + }); + + migrationBuilder.CreateTable( + name: "DormStates", + columns: table => new + { + PlayerId = table.Column(type: "INTEGER", nullable: false) + .Annotation("Sqlite:Autoincrement", true), + CurrentDataBlobName = table.Column(type: "TEXT", maxLength: 128, nullable: false), + CreatedAt = table.Column(type: "TEXT", nullable: false), + UpdatedAt = table.Column(type: "TEXT", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_DormStates", x => x.PlayerId); + }); + + migrationBuilder.CreateTable( + name: "GameRewardSelections", + columns: table => new + { + Id = table.Column(type: "INTEGER", nullable: false) + .Annotation("Sqlite:Autoincrement", true), + PlayerId = table.Column(type: "INTEGER", nullable: false), + RewardType = table.Column(type: "INTEGER", nullable: false), + GiftContext = table.Column(type: "INTEGER", nullable: false), + Message = table.Column(type: "TEXT", maxLength: 256, nullable: false), + CreatedAt = table.Column(type: "TEXT", nullable: false), + SelectedAt = table.Column(type: "TEXT", nullable: true), + SelectedGiftDropId = table.Column(type: "INTEGER", nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_GameRewardSelections", x => x.Id); + }); + + migrationBuilder.CreateTable( + name: "GameSessions", + columns: table => new + { + Id = table.Column(type: "INTEGER", nullable: false) + .Annotation("Sqlite:Autoincrement", true), + RoomId = table.Column(type: "TEXT", maxLength: 64, nullable: false), + ActivityLevelId = table.Column(type: "TEXT", maxLength: 64, nullable: false), + Region = table.Column(type: "TEXT", maxLength: 16, nullable: false), + PhotonRoomName = table.Column(type: "TEXT", maxLength: 128, nullable: false), + MaxCapacity = table.Column(type: "INTEGER", nullable: false), + PlayerCount = table.Column(type: "INTEGER", nullable: false), + CreatedAt = table.Column(type: "TEXT", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_GameSessions", x => x.Id); + }); + + migrationBuilder.CreateTable( + name: "GiftPackages", + columns: table => new + { + Id = table.Column(type: "INTEGER", nullable: false) + .Annotation("Sqlite:Autoincrement", true), + RecipientPlayerId = table.Column(type: "INTEGER", nullable: false), + FromPlayerId = table.Column(type: "INTEGER", nullable: true), + ConsumableItemDesc = table.Column(type: "TEXT", maxLength: 128, nullable: false), + AvatarItemType = table.Column(type: "INTEGER", nullable: true), + AvatarItemDescOrHairDyeDesc = table.Column(type: "TEXT", maxLength: 128, nullable: false), + EquipmentPrefabName = table.Column(type: "TEXT", maxLength: 128, nullable: false), + EquipmentModificationGuid = table.Column(type: "TEXT", maxLength: 64, nullable: false), + CurrencyType = table.Column(type: "INTEGER", nullable: false), + Currency = table.Column(type: "INTEGER", nullable: false), + Xp = table.Column(type: "INTEGER", nullable: false), + Level = table.Column(type: "INTEGER", nullable: false), + GiftContext = table.Column(type: "INTEGER", nullable: false), + GiftRarity = table.Column(type: "INTEGER", nullable: false), + Message = table.Column(type: "TEXT", maxLength: 512, nullable: false), + Platform = table.Column(type: "INTEGER", nullable: false), + PackageMaterial = table.Column(type: "TEXT", maxLength: 128, nullable: false), + PackageVariant = table.Column(type: "TEXT", maxLength: 128, nullable: false), + Consumed = table.Column(type: "INTEGER", nullable: false), + IsValid = table.Column(type: "INTEGER", nullable: false), + ErrorMessage = table.Column(type: "TEXT", maxLength: 512, nullable: false), + SupportsCurrentPlatform = table.Column(type: "INTEGER", nullable: false), + IsGifted = table.Column(type: "INTEGER", nullable: false), + ConsumedAt = table.Column(type: "TEXT", nullable: true), + CreatedAt = table.Column(type: "TEXT", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_GiftPackages", x => x.Id); + }); + + migrationBuilder.CreateTable( + name: "Inventions", + columns: table => new + { + Id = table.Column(type: "INTEGER", nullable: false) + .Annotation("Sqlite:Autoincrement", true), + CreatorPlayerId = table.Column(type: "INTEGER", nullable: false), + ReplicationId = table.Column(type: "TEXT", maxLength: 64, nullable: false), + Name = table.Column(type: "TEXT", maxLength: 128, nullable: false), + Description = table.Column(type: "TEXT", maxLength: 2000, nullable: false), + ImageName = table.Column(type: "TEXT", maxLength: 256, nullable: false), + Permission = table.Column(type: "INTEGER", nullable: false), + CreatorPermission = table.Column(type: "INTEGER", nullable: false), + GeneralPermission = table.Column(type: "INTEGER", nullable: false), + IsPublished = table.Column(type: "INTEGER", nullable: false), + CurrentVersionNumber = table.Column(type: "INTEGER", nullable: false), + FirstPublishedAt = table.Column(type: "TEXT", nullable: true), + CreationRoomId = table.Column(type: "INTEGER", nullable: true), + NumPlayersHaveUsedInRoom = table.Column(type: "INTEGER", nullable: false), + IsAgInvention = table.Column(type: "INTEGER", nullable: false), + CurrentBlobName = table.Column(type: "TEXT", maxLength: 128, nullable: false), + TagsCsv = table.Column(type: "TEXT", maxLength: 1024, nullable: false), + CheerCount = table.Column(type: "INTEGER", nullable: false), + SpawnCount = table.Column(type: "INTEGER", nullable: false), + IsDeleted = table.Column(type: "INTEGER", nullable: false), + CreatedAt = table.Column(type: "TEXT", nullable: false), + UpdatedAt = table.Column(type: "TEXT", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_Inventions", x => x.Id); + }); + + migrationBuilder.CreateTable( + name: "InventionVersions", + columns: table => new + { + Id = table.Column(type: "INTEGER", nullable: false) + .Annotation("Sqlite:Autoincrement", true), + InventionId = table.Column(type: "INTEGER", nullable: false), + ReplicationId = table.Column(type: "TEXT", maxLength: 64, nullable: false), + VersionNumber = table.Column(type: "INTEGER", nullable: false), + BlobName = table.Column(type: "TEXT", maxLength: 128, nullable: false), + InstantiationCost = table.Column(type: "INTEGER", nullable: false), + LightsCost = table.Column(type: "INTEGER", nullable: false), + CreatedAt = table.Column(type: "TEXT", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_InventionVersions", x => x.Id); + }); + + migrationBuilder.CreateTable( + name: "IpBans", + columns: table => new + { + Id = table.Column(type: "INTEGER", nullable: false) + .Annotation("Sqlite:Autoincrement", true), + Cidr = table.Column(type: "TEXT", maxLength: 64, nullable: false), + Reason = table.Column(type: "TEXT", maxLength: 512, nullable: false), + BannedByAdminId = table.Column(type: "INTEGER", nullable: false), + BannedAt = table.Column(type: "TEXT", nullable: false), + Until = table.Column(type: "TEXT", nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_IpBans", x => x.Id); + }); + + migrationBuilder.CreateTable( + name: "LeaderboardChannelMeta", + columns: table => new + { + Channel = table.Column(type: "INTEGER", nullable: false), + RoomId = table.Column(type: "INTEGER", nullable: false), + Name = table.Column(type: "TEXT", nullable: false), + LowerIsBetter = table.Column(type: "INTEGER", nullable: false), + ValueFormat = table.Column(type: "TEXT", nullable: false), + CreatedAt = table.Column(type: "TEXT", nullable: false), + UpdatedAt = table.Column(type: "TEXT", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_LeaderboardChannelMeta", x => x.Channel); + }); + + migrationBuilder.CreateTable( + name: "LeaderboardStats", + columns: table => new + { + Id = table.Column(type: "INTEGER", nullable: false) + .Annotation("Sqlite:Autoincrement", true), + PlayerId = table.Column(type: "INTEGER", nullable: false), + StatChannel = table.Column(type: "INTEGER", nullable: false), + Value = table.Column(type: "INTEGER", nullable: false), + UpdatedAt = table.Column(type: "TEXT", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_LeaderboardStats", x => x.Id); + }); + + migrationBuilder.CreateTable( + name: "LoadingScreenTips", + columns: table => new + { + Id = table.Column(type: "INTEGER", nullable: false) + .Annotation("Sqlite:Autoincrement", true), + Title = table.Column(type: "TEXT", maxLength: 128, nullable: false), + Message = table.Column(type: "TEXT", maxLength: 512, nullable: false), + ImageName = table.Column(type: "TEXT", maxLength: 128, nullable: false), + Context = table.Column(type: "INTEGER", nullable: false), + PlatformMask = table.Column(type: "INTEGER", nullable: false), + RoomNamesCsv = table.Column(type: "TEXT", maxLength: 512, nullable: false), + SortOrder = table.Column(type: "INTEGER", nullable: false), + IsActive = table.Column(type: "INTEGER", nullable: false), + CreatedAt = table.Column(type: "TEXT", nullable: false), + UpdatedAt = table.Column(type: "TEXT", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_LoadingScreenTips", x => x.Id); + }); + + migrationBuilder.CreateTable( + name: "Messages", + columns: table => new + { + Id = table.Column(type: "INTEGER", nullable: false) + .Annotation("Sqlite:Autoincrement", true), + SenderPlayerId = table.Column(type: "INTEGER", nullable: false), + RecipientPlayerId = table.Column(type: "INTEGER", nullable: false), + Body = table.Column(type: "TEXT", maxLength: 2000, nullable: false), + Type = table.Column(type: "INTEGER", nullable: false), + RoomId = table.Column(type: "INTEGER", nullable: true), + SentAt = table.Column(type: "TEXT", nullable: false), + ReadAt = table.Column(type: "TEXT", nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_Messages", x => x.Id); + }); + + migrationBuilder.CreateTable( + name: "NotificationPrefs", + columns: table => new + { + Id = table.Column(type: "INTEGER", nullable: false) + .Annotation("Sqlite:Autoincrement", true), + PlayerId = table.Column(type: "INTEGER", nullable: false), + Platform = table.Column(type: "TEXT", maxLength: 32, nullable: false), + AllowAll = table.Column(type: "INTEGER", nullable: false), + AllowFriendRequest = table.Column(type: "INTEGER", nullable: false), + AllowMessage = table.Column(type: "INTEGER", nullable: false), + AllowEventInvite = table.Column(type: "INTEGER", nullable: false), + AllowAnnouncements = table.Column(type: "INTEGER", nullable: false), + UpdatedAt = table.Column(type: "TEXT", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_NotificationPrefs", x => x.Id); + }); + + migrationBuilder.CreateTable( + name: "ObjectiveProgress", + columns: table => new + { + Id = table.Column(type: "INTEGER", nullable: false) + .Annotation("Sqlite:Autoincrement", true), + PlayerId = table.Column(type: "INTEGER", nullable: false), + Key = table.Column(type: "TEXT", maxLength: 128, nullable: false), + IsCompleted = table.Column(type: "INTEGER", nullable: false), + ClearedAt = table.Column(type: "TEXT", nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_ObjectiveProgress", x => x.Id); + }); + + migrationBuilder.CreateTable( + name: "Photos", + columns: table => new + { + Id = table.Column(type: "INTEGER", nullable: false) + .Annotation("Sqlite:Autoincrement", true), + UploaderPlayerId = table.Column(type: "INTEGER", nullable: false), + BlobName = table.Column(type: "TEXT", maxLength: 256, nullable: false), + Caption = table.Column(type: "TEXT", maxLength: 2048, nullable: false), + RoomId = table.Column(type: "INTEGER", nullable: false), + TaggedPlayerIdsCsv = table.Column(type: "TEXT", maxLength: 2048, nullable: false), + IsPublic = table.Column(type: "INTEGER", nullable: false), + CreatedAt = table.Column(type: "TEXT", nullable: false), + DeletedAt = table.Column(type: "TEXT", nullable: true), + CheerCount = table.Column(type: "INTEGER", nullable: false), + ViewCount = table.Column(type: "INTEGER", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_Photos", x => x.Id); + }); + + migrationBuilder.CreateTable( + name: "PlatformIgnores", + columns: table => new + { + Id = table.Column(type: "INTEGER", nullable: false) + .Annotation("Sqlite:Autoincrement", true), + PlayerId = table.Column(type: "INTEGER", nullable: false), + Platform = table.Column(type: "INTEGER", nullable: false), + PlatformUserId = table.Column(type: "TEXT", maxLength: 64, nullable: false), + CreatedAt = table.Column(type: "TEXT", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_PlatformIgnores", x => x.Id); + }); + + migrationBuilder.CreateTable( + name: "PlayerDevices", + columns: table => new + { + Id = table.Column(type: "INTEGER", nullable: false) + .Annotation("Sqlite:Autoincrement", true), + PlayerId = table.Column(type: "INTEGER", nullable: false), + DeviceId = table.Column(type: "TEXT", maxLength: 128, nullable: false), + Platform = table.Column(type: "TEXT", maxLength: 32, nullable: false), + FirstSeen = table.Column(type: "TEXT", nullable: false), + LastSeen = table.Column(type: "TEXT", nullable: false), + IsBanned = table.Column(type: "INTEGER", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_PlayerDevices", x => x.Id); + }); + + migrationBuilder.CreateTable( + name: "PlayerElo", + columns: table => new + { + Id = table.Column(type: "INTEGER", nullable: false) + .Annotation("Sqlite:Autoincrement", true), + PlayerId = table.Column(type: "INTEGER", nullable: false), + GameMode = table.Column(type: "INTEGER", nullable: false), + Elo = table.Column(type: "INTEGER", nullable: false), + Wins = table.Column(type: "INTEGER", nullable: false), + Losses = table.Column(type: "INTEGER", nullable: false), + MatchesPlayed = table.Column(type: "INTEGER", nullable: false), + UpdatedAt = table.Column(type: "TEXT", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_PlayerElo", x => x.Id); + }); + + migrationBuilder.CreateTable( + name: "PlayerEventResponses", + columns: table => new + { + Id = table.Column(type: "INTEGER", nullable: false) + .Annotation("Sqlite:Autoincrement", true), + EventId = table.Column(type: "INTEGER", nullable: false), + PlayerId = table.Column(type: "INTEGER", nullable: false), + Response = table.Column(type: "INTEGER", nullable: false), + CreatedAt = table.Column(type: "TEXT", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_PlayerEventResponses", x => x.Id); + }); + + migrationBuilder.CreateTable( + name: "PlayerEvents", + columns: table => new + { + Id = table.Column(type: "INTEGER", nullable: false) + .Annotation("Sqlite:Autoincrement", true), + CreatorPlayerId = table.Column(type: "INTEGER", nullable: false), + RoomId = table.Column(type: "INTEGER", nullable: false), + Title = table.Column(type: "TEXT", maxLength: 128, nullable: false), + Description = table.Column(type: "TEXT", maxLength: 2000, nullable: false), + StartsAt = table.Column(type: "TEXT", nullable: false), + EndsAt = table.Column(type: "TEXT", nullable: false), + Capacity = table.Column(type: "INTEGER", nullable: false), + CreatedAt = table.Column(type: "TEXT", nullable: false), + UpdatedAt = table.Column(type: "TEXT", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_PlayerEvents", x => x.Id); + }); + + migrationBuilder.CreateTable( + name: "PlayerInventory", + columns: table => new + { + Id = table.Column(type: "INTEGER", nullable: false) + .Annotation("Sqlite:Autoincrement", true), + PlayerId = table.Column(type: "INTEGER", nullable: false), + ItemSlug = table.Column(type: "TEXT", maxLength: 64, nullable: false), + Quantity = table.Column(type: "INTEGER", nullable: false), + IsActive = table.Column(type: "INTEGER", nullable: false), + AcquiredAt = table.Column(type: "TEXT", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_PlayerInventory", x => x.Id); + }); + + migrationBuilder.CreateTable( + name: "Players", + columns: table => new + { + Id = table.Column(type: "INTEGER", nullable: false) + .Annotation("Sqlite:Autoincrement", true), + DeviceId = table.Column(type: "TEXT", maxLength: 128, nullable: false), + LastPlatform = table.Column(type: "INTEGER", nullable: false), + LastPlatformId = table.Column(type: "TEXT", maxLength: 128, nullable: false), + Username = table.Column(type: "TEXT", maxLength: 64, nullable: false), + DisplayName = table.Column(type: "TEXT", maxLength: 64, nullable: false), + Bio = table.Column(type: "TEXT", maxLength: 512, nullable: false), + Level = table.Column(type: "INTEGER", nullable: false), + XP = table.Column(type: "INTEGER", nullable: false), + Reputation = table.Column(type: "INTEGER", nullable: false), + IsVerified = table.Column(type: "INTEGER", nullable: false), + IsDeveloper = table.Column(type: "INTEGER", nullable: false), + IsCommunityTeam = table.Column(type: "INTEGER", nullable: false), + CanReceiveInvites = table.Column(type: "INTEGER", nullable: false), + IsJunior = table.Column(type: "INTEGER", nullable: false), + ProfileImageName = table.Column(type: "TEXT", maxLength: 256, nullable: true), + CreatedAt = table.Column(type: "TEXT", nullable: false), + LastSeenAt = table.Column(type: "TEXT", nullable: false), + Email = table.Column(type: "TEXT", maxLength: 256, nullable: true), + BannedUntil = table.Column(type: "TEXT", nullable: true), + IsAdmin = table.Column(type: "INTEGER", nullable: false), + LastIp = table.Column(type: "TEXT", maxLength: 64, nullable: true), + CreatedFromDeviceId = table.Column(type: "TEXT", maxLength: 128, nullable: true), + Locale = table.Column(type: "TEXT", maxLength: 16, nullable: true), + PasswordHash = table.Column(type: "TEXT", maxLength: 128, nullable: true), + Birthday = table.Column(type: "TEXT", nullable: true), + Phone = table.Column(type: "TEXT", maxLength: 32, nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_Players", x => x.Id); + }); + + migrationBuilder.CreateTable( + name: "PlaylistInteractions", + columns: table => new + { + Id = table.Column(type: "INTEGER", nullable: false) + .Annotation("Sqlite:Autoincrement", true), + PlaylistId = table.Column(type: "INTEGER", nullable: false), + PlayerId = table.Column(type: "INTEGER", nullable: false), + Cheered = table.Column(type: "INTEGER", nullable: false), + Favorited = table.Column(type: "INTEGER", nullable: false), + UpdatedAt = table.Column(type: "TEXT", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_PlaylistInteractions", x => x.Id); + }); + + migrationBuilder.CreateTable( + name: "PlaylistRooms", + columns: table => new + { + Id = table.Column(type: "INTEGER", nullable: false) + .Annotation("Sqlite:Autoincrement", true), + PlaylistId = table.Column(type: "INTEGER", nullable: false), + RoomId = table.Column(type: "INTEGER", nullable: false), + OrderIndex = table.Column(type: "INTEGER", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_PlaylistRooms", x => x.Id); + }); + + migrationBuilder.CreateTable( + name: "Playlists", + columns: table => new + { + Id = table.Column(type: "INTEGER", nullable: false) + .Annotation("Sqlite:Autoincrement", true), + Name = table.Column(type: "TEXT", maxLength: 128, nullable: false, collation: "NOCASE"), + Description = table.Column(type: "TEXT", maxLength: 2000, nullable: false), + ImageName = table.Column(type: "TEXT", maxLength: 256, nullable: false), + CreatorPlayerId = table.Column(type: "INTEGER", nullable: false), + IsCurated = table.Column(type: "INTEGER", nullable: false), + OrderIndex = table.Column(type: "INTEGER", nullable: false), + TagsCsv = table.Column(type: "TEXT", maxLength: 1024, nullable: false), + CheerCount = table.Column(type: "INTEGER", nullable: false), + FavoriteCount = table.Column(type: "INTEGER", nullable: false), + VisitorCount = table.Column(type: "INTEGER", nullable: false), + VisitCount = table.Column(type: "INTEGER", nullable: false), + CreatedAt = table.Column(type: "TEXT", nullable: false), + UpdatedAt = table.Column(type: "TEXT", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_Playlists", x => x.Id); + }); + + migrationBuilder.CreateTable( + name: "PrivateInstanceInvitees", + columns: table => new + { + PrivateInstanceId = table.Column(type: "INTEGER", nullable: false), + PlayerId = table.Column(type: "INTEGER", nullable: false), + InvitedAt = table.Column(type: "TEXT", nullable: false), + LatestInviteMessageId = table.Column(type: "INTEGER", nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_PrivateInstanceInvitees", x => new { x.PrivateInstanceId, x.PlayerId }); + }); + + migrationBuilder.CreateTable( + name: "PrivateInstances", + columns: table => new + { + Id = table.Column(type: "INTEGER", nullable: false), + RoomId = table.Column(type: "INTEGER", nullable: false), + SubRoomId = table.Column(type: "INTEGER", nullable: false), + OwnerPlayerId = table.Column(type: "INTEGER", nullable: false), + PhotonRoomId = table.Column(type: "TEXT", maxLength: 256, nullable: false), + Location = table.Column(type: "TEXT", maxLength: 64, nullable: false), + DataBlob = table.Column(type: "TEXT", maxLength: 256, nullable: false), + PhotonRegion = table.Column(type: "TEXT", maxLength: 16, nullable: false), + MaxCapacity = table.Column(type: "INTEGER", nullable: false), + Name = table.Column(type: "TEXT", maxLength: 128, nullable: false), + CreatedAt = table.Column(type: "TEXT", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_PrivateInstances", x => x.Id); + }); + + migrationBuilder.CreateTable( + name: "PushTokens", + columns: table => new + { + Id = table.Column(type: "INTEGER", nullable: false) + .Annotation("Sqlite:Autoincrement", true), + PlayerId = table.Column(type: "INTEGER", nullable: false), + Platform = table.Column(type: "TEXT", maxLength: 16, nullable: false), + Token = table.Column(type: "TEXT", maxLength: 256, nullable: false), + CreatedAt = table.Column(type: "TEXT", nullable: false), + UpdatedAt = table.Column(type: "TEXT", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_PushTokens", x => x.Id); + }); + + migrationBuilder.CreateTable( + name: "Reports", + columns: table => new + { + Id = table.Column(type: "INTEGER", nullable: false) + .Annotation("Sqlite:Autoincrement", true), + ReporterPlayerId = table.Column(type: "INTEGER", nullable: false), + TargetPlayerId = table.Column(type: "INTEGER", nullable: false), + Category = table.Column(type: "INTEGER", nullable: false), + Message = table.Column(type: "TEXT", maxLength: 1000, nullable: false), + GameSessionId = table.Column(type: "INTEGER", nullable: false), + RoomId = table.Column(type: "INTEGER", nullable: false), + TargetInventionId = table.Column(type: "INTEGER", nullable: false), + TargetRoomId = table.Column(type: "INTEGER", nullable: false), + TargetEventId = table.Column(type: "INTEGER", nullable: false), + CreatedAt = table.Column(type: "TEXT", nullable: false), + ResolvedAt = table.Column(type: "TEXT", nullable: true), + ResolverAdminId = table.Column(type: "INTEGER", nullable: true), + ResolutionNote = table.Column(type: "TEXT", maxLength: 512, nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_Reports", x => x.Id); + }); + + migrationBuilder.CreateTable( + name: "RoomBans", + columns: table => new + { + Id = table.Column(type: "INTEGER", nullable: false) + .Annotation("Sqlite:Autoincrement", true), + RoomId = table.Column(type: "INTEGER", nullable: false), + BannedPlayerId = table.Column(type: "INTEGER", nullable: false), + BannedByPlayerId = table.Column(type: "INTEGER", nullable: false), + BanType = table.Column(type: "INTEGER", nullable: false), + Until = table.Column(type: "TEXT", nullable: true), + Reason = table.Column(type: "TEXT", maxLength: 512, nullable: false), + CreatedAt = table.Column(type: "TEXT", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_RoomBans", x => x.Id); + }); + + migrationBuilder.CreateTable( + name: "RoomBookmarks", + columns: table => new + { + Id = table.Column(type: "INTEGER", nullable: false) + .Annotation("Sqlite:Autoincrement", true), + PlayerId = table.Column(type: "INTEGER", nullable: false), + RoomId = table.Column(type: "INTEGER", nullable: false), + CreatedAt = table.Column(type: "TEXT", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_RoomBookmarks", x => x.Id); + }); + + migrationBuilder.CreateTable( + name: "RoomDataBlobs", + columns: table => new + { + Id = table.Column(type: "INTEGER", nullable: false) + .Annotation("Sqlite:Autoincrement", true), + RoomId = table.Column(type: "INTEGER", nullable: false), + BlobName = table.Column(type: "TEXT", maxLength: 128, nullable: false), + UploadedByPlayerId = table.Column(type: "INTEGER", nullable: false), + UploadedAt = table.Column(type: "TEXT", nullable: false), + SubRoomId = table.Column(type: "INTEGER", nullable: true), + ReferencedFilenamesCsv = table.Column(type: "TEXT", maxLength: 2048, nullable: false), + Bytes = table.Column(type: "BLOB", nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_RoomDataBlobs", x => x.Id); + }); + + migrationBuilder.CreateTable( + name: "RoomKeyPurchases", + columns: table => new + { + Id = table.Column(type: "INTEGER", nullable: false) + .Annotation("Sqlite:Autoincrement", true), + RoomKeyId = table.Column(type: "INTEGER", nullable: false), + PlayerId = table.Column(type: "INTEGER", nullable: false), + PaidPrice = table.Column(type: "INTEGER", nullable: false), + PurchasedAt = table.Column(type: "TEXT", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_RoomKeyPurchases", x => x.Id); + }); + + migrationBuilder.CreateTable( + name: "RoomKeys", + columns: table => new + { + Id = table.Column(type: "INTEGER", nullable: false) + .Annotation("Sqlite:Autoincrement", true), + ReplicationId = table.Column(type: "TEXT", maxLength: 64, nullable: false), + RoomId = table.Column(type: "INTEGER", nullable: false), + CreatorPlayerId = table.Column(type: "INTEGER", nullable: false), + Name = table.Column(type: "TEXT", maxLength: 40, nullable: false), + Description = table.Column(type: "TEXT", maxLength: 174, nullable: false), + Price = table.Column(type: "INTEGER", nullable: false), + IsDeleted = table.Column(type: "INTEGER", nullable: false), + CreatedAt = table.Column(type: "TEXT", nullable: false), + UpdatedAt = table.Column(type: "TEXT", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_RoomKeys", x => x.Id); + }); + + migrationBuilder.CreateTable( + name: "RoomRoles", + columns: table => new + { + Id = table.Column(type: "INTEGER", nullable: false) + .Annotation("Sqlite:Autoincrement", true), + RoomId = table.Column(type: "INTEGER", nullable: false), + PlayerId = table.Column(type: "INTEGER", nullable: false), + Role = table.Column(type: "INTEGER", nullable: false), + Accepted = table.Column(type: "INTEGER", nullable: false), + GrantedByPlayerId = table.Column(type: "INTEGER", nullable: true), + GrantedAt = table.Column(type: "TEXT", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_RoomRoles", x => x.Id); + }); + + migrationBuilder.CreateTable( + name: "Rooms", + columns: table => new + { + Id = table.Column(type: "INTEGER", nullable: false) + .Annotation("Sqlite:Autoincrement", true), + Name = table.Column(type: "TEXT", maxLength: 128, nullable: false, collation: "NOCASE"), + Description = table.Column(type: "TEXT", maxLength: 2000, nullable: false), + CreatorPlayerId = table.Column(type: "INTEGER", nullable: false), + ImageName = table.Column(type: "TEXT", maxLength: 256, nullable: false), + State = table.Column(type: "INTEGER", nullable: false), + Accessibility = table.Column(type: "INTEGER", nullable: false), + SupportsLevelVoting = table.Column(type: "INTEGER", nullable: false), + IsAGRoom = table.Column(type: "INTEGER", nullable: false), + IsDormRoom = table.Column(type: "INTEGER", nullable: false), + CloningAllowed = table.Column(type: "INTEGER", nullable: false), + SupportsVRLow = table.Column(type: "INTEGER", nullable: false), + SupportsMobile = table.Column(type: "INTEGER", nullable: false), + SupportsScreens = table.Column(type: "INTEGER", nullable: false), + SupportsWalkVR = table.Column(type: "INTEGER", nullable: false), + SupportsTeleportVR = table.Column(type: "INTEGER", nullable: false), + AllowsJuniors = table.Column(type: "INTEGER", nullable: false), + RoomWarningMask = table.Column(type: "INTEGER", nullable: false), + CustomRoomWarning = table.Column(type: "TEXT", maxLength: 512, nullable: false), + DisableMicAutoMute = table.Column(type: "INTEGER", nullable: false), + LocationReplicationId = table.Column(type: "TEXT", maxLength: 64, nullable: false), + TagsCsv = table.Column(type: "TEXT", maxLength: 1024, nullable: false), + CheerCount = table.Column(type: "INTEGER", nullable: false), + FavoriteCount = table.Column(type: "INTEGER", nullable: false), + VisitCount = table.Column(type: "INTEGER", nullable: false), + VisitorCount = table.Column(type: "INTEGER", nullable: false), + HotScore = table.Column(type: "REAL", nullable: false), + CreatedAt = table.Column(type: "TEXT", nullable: false), + UpdatedAt = table.Column(type: "TEXT", nullable: false), + HiddenFromBrowse = table.Column(type: "INTEGER", nullable: false), + CurrentDataBlobName = table.Column(type: "TEXT", maxLength: 128, nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_Rooms", x => x.Id); + }); + + migrationBuilder.CreateTable( + name: "RoomScenes", + columns: table => new + { + Id = table.Column(type: "INTEGER", nullable: false) + .Annotation("Sqlite:Autoincrement", true), + RoomId = table.Column(type: "INTEGER", nullable: false), + OrderIndex = table.Column(type: "INTEGER", nullable: false), + Name = table.Column(type: "TEXT", maxLength: 128, nullable: false, collation: "NOCASE"), + RoomSceneLocationId = table.Column(type: "TEXT", maxLength: 64, nullable: false), + DataBlobName = table.Column(type: "TEXT", maxLength: 128, nullable: false), + MaxPlayers = table.Column(type: "INTEGER", nullable: false), + IsSandbox = table.Column(type: "INTEGER", nullable: false), + CanMatchmakeInto = table.Column(type: "INTEGER", nullable: false), + DataModifiedAt = table.Column(type: "TEXT", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_RoomScenes", x => x.Id); + }); + + migrationBuilder.CreateTable( + name: "RoomVisits", + columns: table => new + { + Id = table.Column(type: "INTEGER", nullable: false) + .Annotation("Sqlite:Autoincrement", true), + RoomId = table.Column(type: "INTEGER", nullable: false), + PlayerId = table.Column(type: "INTEGER", nullable: false), + FirstVisitAt = table.Column(type: "TEXT", nullable: false), + LastVisitAt = table.Column(type: "TEXT", nullable: false), + VisitCount = table.Column(type: "INTEGER", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_RoomVisits", x => x.Id); + }); + + migrationBuilder.CreateTable( + name: "RoyaleMatches", + columns: table => new + { + Id = table.Column(type: "INTEGER", nullable: false) + .Annotation("Sqlite:Autoincrement", true), + CompletedAt = table.Column(type: "TEXT", nullable: false), + Rank = table.Column(type: "INTEGER", nullable: false), + NumEliminations = table.Column(type: "INTEGER", nullable: false), + SecondsAlive = table.Column(type: "INTEGER", nullable: false), + WalkGame = table.Column(type: "INTEGER", nullable: false), + CustomGame = table.Column(type: "INTEGER", nullable: false), + ChestsOpened = table.Column(type: "INTEGER", nullable: false), + ShieldPotionsConsumed = table.Column(type: "INTEGER", nullable: false), + HealthPotionsConsumed = table.Column(type: "INTEGER", nullable: false), + SecondsInAir = table.Column(type: "INTEGER", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_RoyaleMatches", x => x.Id); + }); + + migrationBuilder.CreateTable( + name: "RoyaleMatchPlayers", + columns: table => new + { + Id = table.Column(type: "INTEGER", nullable: false) + .Annotation("Sqlite:Autoincrement", true), + MatchId = table.Column(type: "INTEGER", nullable: false), + PlayerId = table.Column(type: "INTEGER", nullable: false), + Rank = table.Column(type: "INTEGER", nullable: false), + NumEliminations = table.Column(type: "INTEGER", nullable: false), + SecondsAlive = table.Column(type: "INTEGER", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_RoyaleMatchPlayers", x => x.Id); + }); + + migrationBuilder.CreateTable( + name: "RoyalePlayerProgress", + columns: table => new + { + Id = table.Column(type: "INTEGER", nullable: false) + .Annotation("Sqlite:Autoincrement", true), + PlayerId = table.Column(type: "INTEGER", nullable: false), + TotalXP = table.Column(type: "INTEGER", nullable: false), + Level = table.Column(type: "INTEGER", nullable: false), + RankIdx = table.Column(type: "INTEGER", nullable: false), + RankName = table.Column(type: "TEXT", maxLength: 64, nullable: false), + CurrentLevelXPThreshold = table.Column(type: "INTEGER", nullable: false), + NextLevelXPThreshold = table.Column(type: "INTEGER", nullable: false), + NextLevelAcornReward = table.Column(type: "INTEGER", nullable: false), + UpdatedAt = table.Column(type: "TEXT", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_RoyalePlayerProgress", x => x.Id); + }); + + migrationBuilder.CreateTable( + name: "ServerSettings", + columns: table => new + { + Id = table.Column(type: "INTEGER", nullable: false), + SignupsDisabled = table.Column(type: "INTEGER", nullable: false), + WeeklyChallengesCompletedRequired = table.Column(type: "INTEGER", nullable: false, defaultValue: true), + WeeklyChallengesJson = table.Column(type: "TEXT", nullable: false, defaultValue: ""), + WeeklyChallengeRewardJson = table.Column(type: "TEXT", nullable: false, defaultValue: ""), + UpdatedAt = table.Column(type: "TEXT", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_ServerSettings", x => x.Id); + }); + + migrationBuilder.CreateTable( + name: "StoreItems", + columns: table => new + { + Id = table.Column(type: "INTEGER", nullable: false) + .Annotation("Sqlite:Autoincrement", true), + Slug = table.Column(type: "TEXT", maxLength: 128, nullable: false), + DisplayName = table.Column(type: "TEXT", maxLength: 128, nullable: false), + Description = table.Column(type: "TEXT", maxLength: 1024, nullable: false), + Category = table.Column(type: "TEXT", maxLength: 32, nullable: false), + ImageName = table.Column(type: "TEXT", maxLength: 256, nullable: false), + CurrencyType = table.Column(type: "INTEGER", nullable: false), + Price = table.Column(type: "INTEGER", nullable: false), + IsActive = table.Column(type: "INTEGER", nullable: false), + IsLimitedTime = table.Column(type: "INTEGER", nullable: false), + AvailableUntil = table.Column(type: "TEXT", nullable: true), + Storefront = table.Column(type: "TEXT", maxLength: 32, nullable: false), + CreatedAt = table.Column(type: "TEXT", nullable: false), + UpdatedAt = table.Column(type: "TEXT", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_StoreItems", x => x.Id); + }); + + migrationBuilder.CreateTable( + name: "Subscriptions", + columns: table => new + { + Id = table.Column(type: "INTEGER", nullable: false) + .Annotation("Sqlite:Autoincrement", true), + SubscriberPlayerId = table.Column(type: "INTEGER", nullable: false), + TargetPlayerId = table.Column(type: "INTEGER", nullable: false), + CreatedAt = table.Column(type: "TEXT", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_Subscriptions", x => x.Id); + }); + + migrationBuilder.CreateTable( + name: "TestCases", + columns: table => new + { + Pk = table.Column(type: "INTEGER", nullable: false) + .Annotation("Sqlite:Autoincrement", true), + Id = table.Column(type: "TEXT", maxLength: 64, nullable: true), + Key = table.Column(type: "TEXT", maxLength: 64, nullable: false), + Title = table.Column(type: "TEXT", maxLength: 256, nullable: false), + Description = table.Column(type: "TEXT", maxLength: 2000, nullable: false), + RoomName = table.Column(type: "TEXT", maxLength: 128, nullable: false), + Status = table.Column(type: "INTEGER", nullable: false), + MinNumAssignedPlayers = table.Column(type: "INTEGER", nullable: false), + AssignedPlayerIdsJson = table.Column(type: "TEXT", nullable: false), + AssignedPlayerNamesJson = table.Column(type: "TEXT", nullable: false), + TagsJson = table.Column(type: "TEXT", nullable: false), + JiraUrl = table.Column(type: "TEXT", maxLength: 512, nullable: false), + JiraBugUrl = table.Column(type: "TEXT", maxLength: 512, nullable: false), + TestPassId = table.Column(type: "INTEGER", nullable: true), + CreatedAt = table.Column(type: "TEXT", nullable: false), + UpdatedAt = table.Column(type: "TEXT", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_TestCases", x => x.Pk); + }); + + migrationBuilder.CreateTable( + name: "TestPasses", + columns: table => new + { + Id = table.Column(type: "INTEGER", nullable: false), + Name = table.Column(type: "TEXT", maxLength: 128, nullable: false), + Description = table.Column(type: "TEXT", maxLength: 2000, nullable: false), + StartDate = table.Column(type: "TEXT", nullable: false), + EndDate = table.Column(type: "TEXT", nullable: true), + WasManuallyClosed = table.Column(type: "INTEGER", nullable: false), + TagsJson = table.Column(type: "TEXT", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_TestPasses", x => x.Id); + }); + + migrationBuilder.CreateTable( + name: "Avatars", + columns: table => new + { + Id = table.Column(type: "INTEGER", nullable: false) + .Annotation("Sqlite:Autoincrement", true), + PlayerId = table.Column(type: "INTEGER", nullable: false), + EquippedItemsJson = table.Column(type: "TEXT", nullable: false), + InventoryJson = table.Column(type: "TEXT", nullable: false), + OutfitSelections = table.Column(type: "TEXT", maxLength: 2048, nullable: false), + FaceFeatures = table.Column(type: "TEXT", maxLength: 8192, nullable: false), + HairColor = table.Column(type: "TEXT", maxLength: 64, nullable: false), + SkinColor = table.Column(type: "TEXT", maxLength: 64, nullable: false), + SavedOutfitsJson = table.Column(type: "TEXT", nullable: false), + UpdatedAt = table.Column(type: "TEXT", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_Avatars", x => x.Id); + table.ForeignKey( + name: "FK_Avatars_Players_PlayerId", + column: x => x.PlayerId, + principalTable: "Players", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "PlayerSettings", + columns: table => new + { + Id = table.Column(type: "INTEGER", nullable: false) + .Annotation("Sqlite:Autoincrement", true), + PlayerId = table.Column(type: "INTEGER", nullable: false), + Key = table.Column(type: "TEXT", maxLength: 128, nullable: false), + Value = table.Column(type: "TEXT", maxLength: 1024, nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_PlayerSettings", x => x.Id); + table.ForeignKey( + name: "FK_PlayerSettings_Players_PlayerId", + column: x => x.PlayerId, + principalTable: "Players", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "Relationships", + columns: table => new + { + Id = table.Column(type: "INTEGER", nullable: false) + .Annotation("Sqlite:Autoincrement", true), + RequesterId = table.Column(type: "INTEGER", nullable: false), + TargetId = table.Column(type: "INTEGER", nullable: false), + Status = table.Column(type: "INTEGER", nullable: false), + CreatedAt = table.Column(type: "TEXT", nullable: false), + UpdatedAt = table.Column(type: "TEXT", nullable: false), + Favorited = table.Column(type: "INTEGER", nullable: false), + Muted = table.Column(type: "INTEGER", nullable: false), + Ignored = table.Column(type: "INTEGER", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_Relationships", x => x.Id); + table.ForeignKey( + name: "FK_Relationships_Players_RequesterId", + column: x => x.RequesterId, + principalTable: "Players", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateIndex( + name: "IX_AdminActions_AdminPlayerId", + table: "AdminActions", + column: "AdminPlayerId"); + + migrationBuilder.CreateIndex( + name: "IX_AdminActions_TargetType_TargetId", + table: "AdminActions", + columns: new[] { "TargetType", "TargetId" }); + + migrationBuilder.CreateIndex( + name: "IX_AdminActions_Timestamp", + table: "AdminActions", + column: "Timestamp"); + + migrationBuilder.CreateIndex( + name: "IX_Avatars_PlayerId", + table: "Avatars", + column: "PlayerId", + unique: true); + + migrationBuilder.CreateIndex( + name: "IX_BugReports_ReadAt", + table: "BugReports", + column: "ReadAt"); + + migrationBuilder.CreateIndex( + name: "IX_BugReports_ReporterPlayerId", + table: "BugReports", + column: "ReporterPlayerId"); + + migrationBuilder.CreateIndex( + name: "IX_Cards_ExpiresAt", + table: "Cards", + column: "ExpiresAt"); + + migrationBuilder.CreateIndex( + name: "IX_Cards_PlayerId", + table: "Cards", + column: "PlayerId"); + + migrationBuilder.CreateIndex( + name: "IX_ChatMessages_ThreadKey_SentAt", + table: "ChatMessages", + columns: new[] { "ThreadKey", "SentAt" }); + + migrationBuilder.CreateIndex( + name: "IX_ChatThreadMembers_PlayerId", + table: "ChatThreadMembers", + column: "PlayerId"); + + migrationBuilder.CreateIndex( + name: "IX_ChatThreadMembers_ThreadKey_PlayerId", + table: "ChatThreadMembers", + columns: new[] { "ThreadKey", "PlayerId" }, + unique: true); + + migrationBuilder.CreateIndex( + name: "IX_ChatThreads_ThreadKey", + table: "ChatThreads", + column: "ThreadKey", + unique: true); + + migrationBuilder.CreateIndex( + name: "IX_Cheers_FromPlayerId_TargetPlayerId_TargetRoomId_TargetPhotoId_Type", + table: "Cheers", + columns: new[] { "FromPlayerId", "TargetPlayerId", "TargetRoomId", "TargetPhotoId", "Type" }, + unique: true); + + migrationBuilder.CreateIndex( + name: "IX_Cheers_TargetPhotoId", + table: "Cheers", + column: "TargetPhotoId"); + + migrationBuilder.CreateIndex( + name: "IX_Cheers_TargetPlayerId", + table: "Cheers", + column: "TargetPlayerId"); + + migrationBuilder.CreateIndex( + name: "IX_Cheers_TargetRoomId", + table: "Cheers", + column: "TargetRoomId"); + + migrationBuilder.CreateIndex( + name: "IX_ClubAnnouncementReads_AnnouncementId_PlayerId", + table: "ClubAnnouncementReads", + columns: new[] { "AnnouncementId", "PlayerId" }, + unique: true); + + migrationBuilder.CreateIndex( + name: "IX_ClubAnnouncementReads_PlayerId", + table: "ClubAnnouncementReads", + column: "PlayerId"); + + migrationBuilder.CreateIndex( + name: "IX_ClubAnnouncements_ClubId_CreatedAt", + table: "ClubAnnouncements", + columns: new[] { "ClubId", "CreatedAt" }); + + migrationBuilder.CreateIndex( + name: "IX_ClubCategoryAssignments_CategoryTagId", + table: "ClubCategoryAssignments", + column: "CategoryTagId"); + + migrationBuilder.CreateIndex( + name: "IX_ClubCategoryAssignments_ClubId_CategoryTagId", + table: "ClubCategoryAssignments", + columns: new[] { "ClubId", "CategoryTagId" }, + unique: true); + + migrationBuilder.CreateIndex( + name: "IX_ClubCategoryTags_Name", + table: "ClubCategoryTags", + column: "Name"); + + migrationBuilder.CreateIndex( + name: "IX_ClubMemberships_ClubId_PlayerId", + table: "ClubMemberships", + columns: new[] { "ClubId", "PlayerId" }, + unique: true); + + migrationBuilder.CreateIndex( + name: "IX_ClubMemberships_PlayerId", + table: "ClubMemberships", + column: "PlayerId"); + + migrationBuilder.CreateIndex( + name: "IX_Clubs_CreatorPlayerId", + table: "Clubs", + column: "CreatorPlayerId"); + + migrationBuilder.CreateIndex( + name: "IX_Clubs_Name", + table: "Clubs", + column: "Name", + unique: true); + + migrationBuilder.CreateIndex( + name: "IX_ClubSubscriptions_ClubId", + table: "ClubSubscriptions", + column: "ClubId"); + + migrationBuilder.CreateIndex( + name: "IX_ClubSubscriptions_PlayerId_ClubId", + table: "ClubSubscriptions", + columns: new[] { "PlayerId", "ClubId" }, + unique: true); + + migrationBuilder.CreateIndex( + name: "IX_CohortAssignments_PlayerId_CohortKey", + table: "CohortAssignments", + columns: new[] { "PlayerId", "CohortKey" }, + unique: true); + + migrationBuilder.CreateIndex( + name: "IX_CouponRedemptions_CouponId_PlayerId", + table: "CouponRedemptions", + columns: new[] { "CouponId", "PlayerId" }, + unique: true); + + migrationBuilder.CreateIndex( + name: "IX_Coupons_Code", + table: "Coupons", + column: "Code", + unique: true); + + migrationBuilder.CreateIndex( + name: "IX_CurrencyBalances_PlayerId_CurrencyType", + table: "CurrencyBalances", + columns: new[] { "PlayerId", "CurrencyType" }, + unique: true); + + migrationBuilder.CreateIndex( + name: "IX_GameRewardSelections_PlayerId_SelectedAt", + table: "GameRewardSelections", + columns: new[] { "PlayerId", "SelectedAt" }); + + migrationBuilder.CreateIndex( + name: "IX_GameSessions_RoomId_Region", + table: "GameSessions", + columns: new[] { "RoomId", "Region" }); + + migrationBuilder.CreateIndex( + name: "IX_GiftPackages_RecipientPlayerId_Consumed", + table: "GiftPackages", + columns: new[] { "RecipientPlayerId", "Consumed" }); + + migrationBuilder.CreateIndex( + name: "IX_Inventions_CheerCount", + table: "Inventions", + column: "CheerCount"); + + migrationBuilder.CreateIndex( + name: "IX_Inventions_CreatorPlayerId", + table: "Inventions", + column: "CreatorPlayerId"); + + migrationBuilder.CreateIndex( + name: "IX_InventionVersions_InventionId_VersionNumber", + table: "InventionVersions", + columns: new[] { "InventionId", "VersionNumber" }, + unique: true); + + migrationBuilder.CreateIndex( + name: "IX_IpBans_Cidr", + table: "IpBans", + column: "Cidr"); + + migrationBuilder.CreateIndex( + name: "IX_LeaderboardChannelMeta_RoomId", + table: "LeaderboardChannelMeta", + column: "RoomId"); + + migrationBuilder.CreateIndex( + name: "IX_LeaderboardStats_PlayerId_StatChannel", + table: "LeaderboardStats", + columns: new[] { "PlayerId", "StatChannel" }, + unique: true); + + migrationBuilder.CreateIndex( + name: "IX_LeaderboardStats_StatChannel_Value", + table: "LeaderboardStats", + columns: new[] { "StatChannel", "Value" }); + + migrationBuilder.CreateIndex( + name: "IX_Messages_RecipientPlayerId_SentAt", + table: "Messages", + columns: new[] { "RecipientPlayerId", "SentAt" }); + + migrationBuilder.CreateIndex( + name: "IX_Messages_SenderPlayerId_SentAt", + table: "Messages", + columns: new[] { "SenderPlayerId", "SentAt" }); + + migrationBuilder.CreateIndex( + name: "IX_NotificationPrefs_PlayerId_Platform", + table: "NotificationPrefs", + columns: new[] { "PlayerId", "Platform" }, + unique: true); + + migrationBuilder.CreateIndex( + name: "IX_ObjectiveProgress_PlayerId_Key", + table: "ObjectiveProgress", + columns: new[] { "PlayerId", "Key" }, + unique: true); + + migrationBuilder.CreateIndex( + name: "IX_Photos_BlobName", + table: "Photos", + column: "BlobName"); + + migrationBuilder.CreateIndex( + name: "IX_Photos_CreatedAt", + table: "Photos", + column: "CreatedAt"); + + migrationBuilder.CreateIndex( + name: "IX_Photos_RoomId", + table: "Photos", + column: "RoomId"); + + migrationBuilder.CreateIndex( + name: "IX_Photos_UploaderPlayerId", + table: "Photos", + column: "UploaderPlayerId"); + + migrationBuilder.CreateIndex( + name: "IX_PlatformIgnores_PlayerId_Platform_PlatformUserId", + table: "PlatformIgnores", + columns: new[] { "PlayerId", "Platform", "PlatformUserId" }, + unique: true); + + migrationBuilder.CreateIndex( + name: "IX_PlayerDevices_DeviceId", + table: "PlayerDevices", + column: "DeviceId"); + + migrationBuilder.CreateIndex( + name: "IX_PlayerDevices_PlayerId", + table: "PlayerDevices", + column: "PlayerId"); + + migrationBuilder.CreateIndex( + name: "IX_PlayerElo_GameMode_Elo", + table: "PlayerElo", + columns: new[] { "GameMode", "Elo" }); + + migrationBuilder.CreateIndex( + name: "IX_PlayerElo_PlayerId_GameMode", + table: "PlayerElo", + columns: new[] { "PlayerId", "GameMode" }, + unique: true); + + migrationBuilder.CreateIndex( + name: "IX_PlayerEventResponses_EventId_PlayerId", + table: "PlayerEventResponses", + columns: new[] { "EventId", "PlayerId" }, + unique: true); + + migrationBuilder.CreateIndex( + name: "IX_PlayerEventResponses_PlayerId", + table: "PlayerEventResponses", + column: "PlayerId"); + + migrationBuilder.CreateIndex( + name: "IX_PlayerEvents_CreatorPlayerId", + table: "PlayerEvents", + column: "CreatorPlayerId"); + + migrationBuilder.CreateIndex( + name: "IX_PlayerEvents_StartsAt", + table: "PlayerEvents", + column: "StartsAt"); + + migrationBuilder.CreateIndex( + name: "IX_PlayerInventory_PlayerId", + table: "PlayerInventory", + column: "PlayerId"); + + migrationBuilder.CreateIndex( + name: "IX_PlayerInventory_PlayerId_ItemSlug", + table: "PlayerInventory", + columns: new[] { "PlayerId", "ItemSlug" }, + unique: true); + + migrationBuilder.CreateIndex( + name: "IX_Players_Username", + table: "Players", + column: "Username", + unique: true); + + migrationBuilder.CreateIndex( + name: "IX_PlayerSettings_PlayerId_Key", + table: "PlayerSettings", + columns: new[] { "PlayerId", "Key" }, + unique: true); + + migrationBuilder.CreateIndex( + name: "IX_PlaylistInteractions_PlayerId", + table: "PlaylistInteractions", + column: "PlayerId"); + + migrationBuilder.CreateIndex( + name: "IX_PlaylistInteractions_PlaylistId_PlayerId", + table: "PlaylistInteractions", + columns: new[] { "PlaylistId", "PlayerId" }, + unique: true); + + migrationBuilder.CreateIndex( + name: "IX_PlaylistRooms_PlaylistId", + table: "PlaylistRooms", + column: "PlaylistId"); + + migrationBuilder.CreateIndex( + name: "IX_PlaylistRooms_PlaylistId_OrderIndex", + table: "PlaylistRooms", + columns: new[] { "PlaylistId", "OrderIndex" }); + + migrationBuilder.CreateIndex( + name: "IX_PlaylistRooms_RoomId", + table: "PlaylistRooms", + column: "RoomId"); + + migrationBuilder.CreateIndex( + name: "IX_Playlists_CreatorPlayerId", + table: "Playlists", + column: "CreatorPlayerId"); + + migrationBuilder.CreateIndex( + name: "IX_Playlists_IsCurated", + table: "Playlists", + column: "IsCurated"); + + migrationBuilder.CreateIndex( + name: "IX_Playlists_Name", + table: "Playlists", + column: "Name"); + + migrationBuilder.CreateIndex( + name: "IX_PrivateInstanceInvitees_PlayerId", + table: "PrivateInstanceInvitees", + column: "PlayerId"); + + migrationBuilder.CreateIndex( + name: "IX_PrivateInstances_OwnerPlayerId", + table: "PrivateInstances", + column: "OwnerPlayerId"); + + migrationBuilder.CreateIndex( + name: "IX_PrivateInstances_RoomId_SubRoomId", + table: "PrivateInstances", + columns: new[] { "RoomId", "SubRoomId" }); + + migrationBuilder.CreateIndex( + name: "IX_PushTokens_PlayerId_Platform", + table: "PushTokens", + columns: new[] { "PlayerId", "Platform" }, + unique: true); + + migrationBuilder.CreateIndex( + name: "IX_PushTokens_Token", + table: "PushTokens", + column: "Token"); + + migrationBuilder.CreateIndex( + name: "IX_Relationships_RequesterId_TargetId", + table: "Relationships", + columns: new[] { "RequesterId", "TargetId" }, + unique: true); + + migrationBuilder.CreateIndex( + name: "IX_Reports_ReporterPlayerId", + table: "Reports", + column: "ReporterPlayerId"); + + migrationBuilder.CreateIndex( + name: "IX_Reports_ResolvedAt", + table: "Reports", + column: "ResolvedAt"); + + migrationBuilder.CreateIndex( + name: "IX_Reports_TargetPlayerId", + table: "Reports", + column: "TargetPlayerId"); + + migrationBuilder.CreateIndex( + name: "IX_RoomBans_BannedPlayerId", + table: "RoomBans", + column: "BannedPlayerId"); + + migrationBuilder.CreateIndex( + name: "IX_RoomBans_RoomId_BannedPlayerId", + table: "RoomBans", + columns: new[] { "RoomId", "BannedPlayerId" }); + + migrationBuilder.CreateIndex( + name: "IX_RoomBookmarks_PlayerId", + table: "RoomBookmarks", + column: "PlayerId"); + + migrationBuilder.CreateIndex( + name: "IX_RoomBookmarks_PlayerId_RoomId", + table: "RoomBookmarks", + columns: new[] { "PlayerId", "RoomId" }, + unique: true); + + migrationBuilder.CreateIndex( + name: "IX_RoomDataBlobs_BlobName", + table: "RoomDataBlobs", + column: "BlobName", + unique: true); + + migrationBuilder.CreateIndex( + name: "IX_RoomDataBlobs_RoomId", + table: "RoomDataBlobs", + column: "RoomId"); + + migrationBuilder.CreateIndex( + name: "IX_RoomKeyPurchases_PlayerId", + table: "RoomKeyPurchases", + column: "PlayerId"); + + migrationBuilder.CreateIndex( + name: "IX_RoomKeyPurchases_RoomKeyId_PlayerId", + table: "RoomKeyPurchases", + columns: new[] { "RoomKeyId", "PlayerId" }, + unique: true); + + migrationBuilder.CreateIndex( + name: "IX_RoomKeys_CreatorPlayerId", + table: "RoomKeys", + column: "CreatorPlayerId"); + + migrationBuilder.CreateIndex( + name: "IX_RoomKeys_RoomId", + table: "RoomKeys", + column: "RoomId"); + + migrationBuilder.CreateIndex( + name: "IX_RoomKeys_RoomId_Name", + table: "RoomKeys", + columns: new[] { "RoomId", "Name" }); + + migrationBuilder.CreateIndex( + name: "IX_RoomRoles_RoomId", + table: "RoomRoles", + column: "RoomId"); + + migrationBuilder.CreateIndex( + name: "IX_RoomRoles_RoomId_PlayerId_Role", + table: "RoomRoles", + columns: new[] { "RoomId", "PlayerId", "Role" }, + unique: true); + + migrationBuilder.CreateIndex( + name: "IX_Rooms_CreatorPlayerId", + table: "Rooms", + column: "CreatorPlayerId"); + + migrationBuilder.CreateIndex( + name: "IX_Rooms_HotScore", + table: "Rooms", + column: "HotScore"); + + migrationBuilder.CreateIndex( + name: "IX_Rooms_Name", + table: "Rooms", + column: "Name", + unique: true); + + migrationBuilder.CreateIndex( + name: "IX_RoomScenes_RoomId_Name", + table: "RoomScenes", + columns: new[] { "RoomId", "Name" }); + + migrationBuilder.CreateIndex( + name: "IX_RoomScenes_RoomId_OrderIndex", + table: "RoomScenes", + columns: new[] { "RoomId", "OrderIndex" }, + unique: true); + + migrationBuilder.CreateIndex( + name: "IX_RoomVisits_PlayerId", + table: "RoomVisits", + column: "PlayerId"); + + migrationBuilder.CreateIndex( + name: "IX_RoomVisits_RoomId_LastVisitAt", + table: "RoomVisits", + columns: new[] { "RoomId", "LastVisitAt" }); + + migrationBuilder.CreateIndex( + name: "IX_RoomVisits_RoomId_PlayerId", + table: "RoomVisits", + columns: new[] { "RoomId", "PlayerId" }, + unique: true); + + migrationBuilder.CreateIndex( + name: "IX_RoyaleMatches_CompletedAt", + table: "RoyaleMatches", + column: "CompletedAt"); + + migrationBuilder.CreateIndex( + name: "IX_RoyaleMatchPlayers_MatchId_PlayerId", + table: "RoyaleMatchPlayers", + columns: new[] { "MatchId", "PlayerId" }); + + migrationBuilder.CreateIndex( + name: "IX_RoyaleMatchPlayers_PlayerId", + table: "RoyaleMatchPlayers", + column: "PlayerId"); + + migrationBuilder.CreateIndex( + name: "IX_RoyalePlayerProgress_PlayerId", + table: "RoyalePlayerProgress", + column: "PlayerId", + unique: true); + + migrationBuilder.CreateIndex( + name: "IX_StoreItems_IsActive_Category", + table: "StoreItems", + columns: new[] { "IsActive", "Category" }); + + migrationBuilder.CreateIndex( + name: "IX_StoreItems_Slug", + table: "StoreItems", + column: "Slug", + unique: true); + + migrationBuilder.CreateIndex( + name: "IX_StoreItems_Storefront", + table: "StoreItems", + column: "Storefront"); + + migrationBuilder.CreateIndex( + name: "IX_Subscriptions_SubscriberPlayerId", + table: "Subscriptions", + column: "SubscriberPlayerId"); + + migrationBuilder.CreateIndex( + name: "IX_Subscriptions_SubscriberPlayerId_TargetPlayerId", + table: "Subscriptions", + columns: new[] { "SubscriberPlayerId", "TargetPlayerId" }, + unique: true); + + migrationBuilder.CreateIndex( + name: "IX_Subscriptions_TargetPlayerId", + table: "Subscriptions", + column: "TargetPlayerId"); + + migrationBuilder.CreateIndex( + name: "IX_TestCases_Id", + table: "TestCases", + column: "Id", + unique: true); + + migrationBuilder.CreateIndex( + name: "IX_TestCases_Status", + table: "TestCases", + column: "Status"); + + migrationBuilder.CreateIndex( + name: "IX_TestCases_TestPassId", + table: "TestCases", + column: "TestPassId"); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropTable( + name: "AdminActions"); + + migrationBuilder.DropTable( + name: "Avatars"); + + migrationBuilder.DropTable( + name: "BugReports"); + + migrationBuilder.DropTable( + name: "Cards"); + + migrationBuilder.DropTable( + name: "ChatMessages"); + + migrationBuilder.DropTable( + name: "ChatThreadMembers"); + + migrationBuilder.DropTable( + name: "ChatThreads"); + + migrationBuilder.DropTable( + name: "Cheers"); + + migrationBuilder.DropTable( + name: "ClubAnnouncementReads"); + + migrationBuilder.DropTable( + name: "ClubAnnouncements"); + + migrationBuilder.DropTable( + name: "ClubCategoryAssignments"); + + migrationBuilder.DropTable( + name: "ClubCategoryTags"); + + migrationBuilder.DropTable( + name: "ClubMemberships"); + + migrationBuilder.DropTable( + name: "Clubs"); + + migrationBuilder.DropTable( + name: "ClubSubscriptions"); + + migrationBuilder.DropTable( + name: "CohortAssignments"); + + migrationBuilder.DropTable( + name: "CommunityBoardRows"); + + migrationBuilder.DropTable( + name: "CouponRedemptions"); + + migrationBuilder.DropTable( + name: "Coupons"); + + migrationBuilder.DropTable( + name: "CurrencyBalances"); + + migrationBuilder.DropTable( + name: "DormStates"); + + migrationBuilder.DropTable( + name: "GameRewardSelections"); + + migrationBuilder.DropTable( + name: "GameSessions"); + + migrationBuilder.DropTable( + name: "GiftPackages"); + + migrationBuilder.DropTable( + name: "Inventions"); + + migrationBuilder.DropTable( + name: "InventionVersions"); + + migrationBuilder.DropTable( + name: "IpBans"); + + migrationBuilder.DropTable( + name: "LeaderboardChannelMeta"); + + migrationBuilder.DropTable( + name: "LeaderboardStats"); + + migrationBuilder.DropTable( + name: "LoadingScreenTips"); + + migrationBuilder.DropTable( + name: "Messages"); + + migrationBuilder.DropTable( + name: "NotificationPrefs"); + + migrationBuilder.DropTable( + name: "ObjectiveProgress"); + + migrationBuilder.DropTable( + name: "Photos"); + + migrationBuilder.DropTable( + name: "PlatformIgnores"); + + migrationBuilder.DropTable( + name: "PlayerDevices"); + + migrationBuilder.DropTable( + name: "PlayerElo"); + + migrationBuilder.DropTable( + name: "PlayerEventResponses"); + + migrationBuilder.DropTable( + name: "PlayerEvents"); + + migrationBuilder.DropTable( + name: "PlayerInventory"); + + migrationBuilder.DropTable( + name: "PlayerSettings"); + + migrationBuilder.DropTable( + name: "PlaylistInteractions"); + + migrationBuilder.DropTable( + name: "PlaylistRooms"); + + migrationBuilder.DropTable( + name: "Playlists"); + + migrationBuilder.DropTable( + name: "PrivateInstanceInvitees"); + + migrationBuilder.DropTable( + name: "PrivateInstances"); + + migrationBuilder.DropTable( + name: "PushTokens"); + + migrationBuilder.DropTable( + name: "Relationships"); + + migrationBuilder.DropTable( + name: "Reports"); + + migrationBuilder.DropTable( + name: "RoomBans"); + + migrationBuilder.DropTable( + name: "RoomBookmarks"); + + migrationBuilder.DropTable( + name: "RoomDataBlobs"); + + migrationBuilder.DropTable( + name: "RoomKeyPurchases"); + + migrationBuilder.DropTable( + name: "RoomKeys"); + + migrationBuilder.DropTable( + name: "RoomRoles"); + + migrationBuilder.DropTable( + name: "Rooms"); + + migrationBuilder.DropTable( + name: "RoomScenes"); + + migrationBuilder.DropTable( + name: "RoomVisits"); + + migrationBuilder.DropTable( + name: "RoyaleMatches"); + + migrationBuilder.DropTable( + name: "RoyaleMatchPlayers"); + + migrationBuilder.DropTable( + name: "RoyalePlayerProgress"); + + migrationBuilder.DropTable( + name: "ServerSettings"); + + migrationBuilder.DropTable( + name: "StoreItems"); + + migrationBuilder.DropTable( + name: "Subscriptions"); + + migrationBuilder.DropTable( + name: "TestCases"); + + migrationBuilder.DropTable( + name: "TestPasses"); + + migrationBuilder.DropTable( + name: "Players"); + } + } +} diff --git a/DorkNet.Server/Migrations/DorkNetDbContextModelSnapshot.cs b/DorkNet.Server/Migrations/DorkNetDbContextModelSnapshot.cs new file mode 100644 index 0000000..e2570f7 --- /dev/null +++ b/DorkNet.Server/Migrations/DorkNetDbContextModelSnapshot.cs @@ -0,0 +1,2865 @@ +// +using System; +using DorkNet.Server.Data; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; + +#nullable disable + +namespace DorkNet.Server.Migrations +{ + [DbContext(typeof(DorkNetDbContext))] + partial class DorkNetDbContextModelSnapshot : ModelSnapshot + { + protected override void BuildModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder.HasAnnotation("ProductVersion", "9.0.4"); + + modelBuilder.Entity("DorkNet.Server.Data.Entities.AdminActionEntity", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("Action") + .IsRequired() + .HasMaxLength(64) + .HasColumnType("TEXT"); + + b.Property("AdminPlayerId") + .HasColumnType("INTEGER"); + + b.Property("Reason") + .IsRequired() + .HasMaxLength(512) + .HasColumnType("TEXT"); + + b.Property("TargetId") + .HasColumnType("INTEGER"); + + b.Property("TargetType") + .IsRequired() + .HasMaxLength(32) + .HasColumnType("TEXT"); + + b.Property("Timestamp") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("AdminPlayerId"); + + b.HasIndex("Timestamp"); + + b.HasIndex("TargetType", "TargetId"); + + b.ToTable("AdminActions"); + }); + + modelBuilder.Entity("DorkNet.Server.Data.Entities.AvatarEntity", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("EquippedItemsJson") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("FaceFeatures") + .IsRequired() + .HasMaxLength(8192) + .HasColumnType("TEXT"); + + b.Property("HairColor") + .IsRequired() + .HasMaxLength(64) + .HasColumnType("TEXT"); + + b.Property("InventoryJson") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("OutfitSelections") + .IsRequired() + .HasMaxLength(2048) + .HasColumnType("TEXT"); + + b.Property("PlayerId") + .HasColumnType("INTEGER"); + + b.Property("SavedOutfitsJson") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("SkinColor") + .IsRequired() + .HasMaxLength(64) + .HasColumnType("TEXT"); + + b.Property("UpdatedAt") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("PlayerId") + .IsUnique(); + + b.ToTable("Avatars"); + }); + + modelBuilder.Entity("DorkNet.Server.Data.Entities.BugReportEntity", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("Body") + .IsRequired() + .HasMaxLength(4000) + .HasColumnType("TEXT"); + + b.Property("Category") + .IsRequired() + .HasMaxLength(32) + .HasColumnType("TEXT"); + + b.Property("ClientVersion") + .IsRequired() + .HasMaxLength(64) + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("GameSessionId") + .HasColumnType("INTEGER"); + + b.Property("Platform") + .IsRequired() + .HasMaxLength(32) + .HasColumnType("TEXT"); + + b.Property("ReadAt") + .HasColumnType("TEXT"); + + b.Property("ReporterPlayerId") + .HasColumnType("INTEGER"); + + b.Property("Title") + .IsRequired() + .HasMaxLength(128) + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("ReadAt"); + + b.HasIndex("ReporterPlayerId"); + + b.ToTable("BugReports"); + }); + + modelBuilder.Entity("DorkNet.Server.Data.Entities.CardEntity", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("ActionUrl") + .IsRequired() + .HasMaxLength(512) + .HasColumnType("TEXT"); + + b.Property("Body") + .IsRequired() + .HasMaxLength(2000) + .HasColumnType("TEXT"); + + b.Property("Category") + .IsRequired() + .HasMaxLength(32) + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("ExpiresAt") + .HasColumnType("TEXT"); + + b.Property("ImageName") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("TEXT"); + + b.Property("PlayerId") + .HasColumnType("INTEGER"); + + b.Property("Priority") + .HasColumnType("INTEGER"); + + b.Property("Title") + .IsRequired() + .HasMaxLength(128) + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("ExpiresAt"); + + b.HasIndex("PlayerId"); + + b.ToTable("Cards"); + }); + + modelBuilder.Entity("DorkNet.Server.Data.Entities.ChatMessageEntity", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("Body") + .IsRequired() + .HasMaxLength(2000) + .HasColumnType("TEXT"); + + b.Property("SenderPlayerId") + .HasColumnType("INTEGER"); + + b.Property("SentAt") + .HasColumnType("TEXT"); + + b.Property("ThreadKey") + .IsRequired() + .HasMaxLength(64) + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("ThreadKey", "SentAt"); + + b.ToTable("ChatMessages"); + }); + + modelBuilder.Entity("DorkNet.Server.Data.Entities.ChatThreadEntity", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("CreatorPlayerId") + .HasColumnType("INTEGER"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(128) + .HasColumnType("TEXT"); + + b.Property("ThreadKey") + .IsRequired() + .HasMaxLength(96) + .HasColumnType("TEXT"); + + b.Property("UpdatedAt") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("ThreadKey") + .IsUnique(); + + b.ToTable("ChatThreads"); + }); + + modelBuilder.Entity("DorkNet.Server.Data.Entities.ChatThreadMemberEntity", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("JoinedAt") + .HasColumnType("TEXT"); + + b.Property("LastReadMessageId") + .HasColumnType("INTEGER"); + + b.Property("PlayerId") + .HasColumnType("INTEGER"); + + b.Property("SnoozeUntil") + .HasColumnType("TEXT"); + + b.Property("ThreadKey") + .IsRequired() + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("PlayerId"); + + b.HasIndex("ThreadKey", "PlayerId") + .IsUnique(); + + b.ToTable("ChatThreadMembers"); + }); + + modelBuilder.Entity("DorkNet.Server.Data.Entities.CheerEntity", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("CheeredAt") + .HasColumnType("TEXT"); + + b.Property("FromPlayerId") + .HasColumnType("INTEGER"); + + b.Property("TargetInventionId") + .HasColumnType("INTEGER"); + + b.Property("TargetPhotoId") + .HasColumnType("INTEGER"); + + b.Property("TargetPlayerId") + .HasColumnType("INTEGER"); + + b.Property("TargetRoomId") + .HasColumnType("INTEGER"); + + b.Property("Type") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("TargetPhotoId"); + + b.HasIndex("TargetPlayerId"); + + b.HasIndex("TargetRoomId"); + + b.HasIndex("FromPlayerId", "TargetPlayerId", "TargetRoomId", "TargetPhotoId", "Type") + .IsUnique(); + + b.ToTable("Cheers"); + }); + + modelBuilder.Entity("DorkNet.Server.Data.Entities.ClubAnnouncementEntity", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("AuthorPlayerId") + .HasColumnType("INTEGER"); + + b.Property("Body") + .IsRequired() + .HasMaxLength(4000) + .HasColumnType("TEXT"); + + b.Property("ClubId") + .HasColumnType("INTEGER"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("ImageName") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("TEXT"); + + b.Property("Title") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("TEXT"); + + b.Property("UpdatedAt") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("ClubId", "CreatedAt"); + + b.ToTable("ClubAnnouncements"); + }); + + modelBuilder.Entity("DorkNet.Server.Data.Entities.ClubAnnouncementReadEntity", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("AnnouncementId") + .HasColumnType("INTEGER"); + + b.Property("PlayerId") + .HasColumnType("INTEGER"); + + b.Property("ReadAt") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("PlayerId"); + + b.HasIndex("AnnouncementId", "PlayerId") + .IsUnique(); + + b.ToTable("ClubAnnouncementReads"); + }); + + modelBuilder.Entity("DorkNet.Server.Data.Entities.ClubCategoryAssignmentEntity", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("CategoryTagId") + .HasColumnType("INTEGER"); + + b.Property("ClubId") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("CategoryTagId"); + + b.HasIndex("ClubId", "CategoryTagId") + .IsUnique(); + + b.ToTable("ClubCategoryAssignments"); + }); + + modelBuilder.Entity("DorkNet.Server.Data.Entities.ClubCategoryTagEntity", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("Active") + .HasColumnType("INTEGER"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(64) + .HasColumnType("TEXT"); + + b.Property("OrderIndex") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("Name"); + + b.ToTable("ClubCategoryTags"); + }); + + modelBuilder.Entity("DorkNet.Server.Data.Entities.ClubEntity", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("AllowJuniors") + .HasColumnType("INTEGER"); + + b.Property("BanStatus") + .HasColumnType("INTEGER"); + + b.Property("Category") + .IsRequired() + .HasMaxLength(64) + .HasColumnType("TEXT"); + + b.Property("ClubType") + .HasColumnType("INTEGER"); + + b.Property("ClubhouseRoomId") + .HasColumnType("INTEGER"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("CreatorPlayerId") + .HasColumnType("INTEGER"); + + b.Property("Description") + .IsRequired() + .HasMaxLength(2000) + .HasColumnType("TEXT"); + + b.Property("ImageName") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("TEXT"); + + b.Property("IsRRO") + .HasColumnType("INTEGER"); + + b.Property("Joinability") + .HasColumnType("INTEGER"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(128) + .HasColumnType("TEXT") + .UseCollation("NOCASE"); + + b.Property("State") + .HasColumnType("INTEGER"); + + b.Property("UpdatedAt") + .HasColumnType("TEXT"); + + b.Property("Visibility") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("CreatorPlayerId"); + + b.HasIndex("Name") + .IsUnique(); + + b.ToTable("Clubs"); + }); + + modelBuilder.Entity("DorkNet.Server.Data.Entities.ClubMembershipEntity", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("ClubId") + .HasColumnType("INTEGER"); + + b.Property("JoinedAt") + .HasColumnType("TEXT"); + + b.Property("Permissions") + .HasColumnType("INTEGER"); + + b.Property("PlayerId") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("PlayerId"); + + b.HasIndex("ClubId", "PlayerId") + .IsUnique(); + + b.ToTable("ClubMemberships"); + }); + + modelBuilder.Entity("DorkNet.Server.Data.Entities.ClubSubscriptionEntity", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("ClubId") + .HasColumnType("INTEGER"); + + b.Property("PlayerId") + .HasColumnType("INTEGER"); + + b.Property("SubscribedAt") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("ClubId"); + + b.HasIndex("PlayerId", "ClubId") + .IsUnique(); + + b.ToTable("ClubSubscriptions"); + }); + + modelBuilder.Entity("DorkNet.Server.Data.Entities.CohortAssignmentEntity", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("AssignedAt") + .HasColumnType("TEXT"); + + b.Property("CohortKey") + .IsRequired() + .HasMaxLength(64) + .HasColumnType("TEXT"); + + b.Property("PlayerId") + .HasColumnType("INTEGER"); + + b.Property("Variant") + .IsRequired() + .HasMaxLength(32) + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("PlayerId", "CohortKey") + .IsUnique(); + + b.ToTable("CohortAssignments"); + }); + + modelBuilder.Entity("DorkNet.Server.Data.Entities.CommunityBoardEntity", b => + { + b.Property("Id") + .HasColumnType("INTEGER"); + + b.Property("Json") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("UpdatedAt") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.ToTable("CommunityBoardRows"); + }); + + modelBuilder.Entity("DorkNet.Server.Data.Entities.CouponEntity", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("Code") + .IsRequired() + .HasMaxLength(64) + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("Description") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("TEXT"); + + b.Property("ExpiresAt") + .HasColumnType("TEXT"); + + b.Property("IsActive") + .HasColumnType("INTEGER"); + + b.Property("MaxRedemptions") + .HasColumnType("INTEGER"); + + b.Property("RedemptionCount") + .HasColumnType("INTEGER"); + + b.Property("RewardAmount") + .HasColumnType("INTEGER"); + + b.Property("RewardCurrencyType") + .HasColumnType("INTEGER"); + + b.Property("RewardItemSlug") + .IsRequired() + .HasMaxLength(64) + .HasColumnType("TEXT"); + + b.Property("RewardType") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("Code") + .IsUnique(); + + b.ToTable("Coupons"); + }); + + modelBuilder.Entity("DorkNet.Server.Data.Entities.CouponRedemptionEntity", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("CouponId") + .HasColumnType("INTEGER"); + + b.Property("PlayerId") + .HasColumnType("INTEGER"); + + b.Property("RedeemedAt") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("CouponId", "PlayerId") + .IsUnique(); + + b.ToTable("CouponRedemptions"); + }); + + modelBuilder.Entity("DorkNet.Server.Data.Entities.CurrencyBalanceEntity", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("Balance") + .HasColumnType("INTEGER"); + + b.Property("CurrencyType") + .HasColumnType("INTEGER"); + + b.Property("PlayerId") + .HasColumnType("INTEGER"); + + b.Property("UpdatedAt") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("PlayerId", "CurrencyType") + .IsUnique(); + + b.ToTable("CurrencyBalances"); + }); + + modelBuilder.Entity("DorkNet.Server.Data.Entities.DormStateEntity", b => + { + b.Property("PlayerId") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("CurrentDataBlobName") + .IsRequired() + .HasMaxLength(128) + .HasColumnType("TEXT"); + + b.Property("UpdatedAt") + .HasColumnType("TEXT"); + + b.HasKey("PlayerId"); + + b.ToTable("DormStates"); + }); + + modelBuilder.Entity("DorkNet.Server.Data.Entities.GameRewardSelectionEntity", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("GiftContext") + .HasColumnType("INTEGER"); + + b.Property("Message") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("TEXT"); + + b.Property("PlayerId") + .HasColumnType("INTEGER"); + + b.Property("RewardType") + .HasColumnType("INTEGER"); + + b.Property("SelectedAt") + .HasColumnType("TEXT"); + + b.Property("SelectedGiftDropId") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("PlayerId", "SelectedAt"); + + b.ToTable("GameRewardSelections"); + }); + + modelBuilder.Entity("DorkNet.Server.Data.Entities.GameSessionEntity", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("ActivityLevelId") + .IsRequired() + .HasMaxLength(64) + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("MaxCapacity") + .HasColumnType("INTEGER"); + + b.Property("PhotonRoomName") + .IsRequired() + .HasMaxLength(128) + .HasColumnType("TEXT"); + + b.Property("PlayerCount") + .HasColumnType("INTEGER"); + + b.Property("Region") + .IsRequired() + .HasMaxLength(16) + .HasColumnType("TEXT"); + + b.Property("RoomId") + .IsRequired() + .HasMaxLength(64) + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("RoomId", "Region"); + + b.ToTable("GameSessions"); + }); + + modelBuilder.Entity("DorkNet.Server.Data.Entities.GiftPackageEntity", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("AvatarItemDescOrHairDyeDesc") + .IsRequired() + .HasMaxLength(128) + .HasColumnType("TEXT"); + + b.Property("AvatarItemType") + .HasColumnType("INTEGER"); + + b.Property("ConsumableItemDesc") + .IsRequired() + .HasMaxLength(128) + .HasColumnType("TEXT"); + + b.Property("Consumed") + .HasColumnType("INTEGER"); + + b.Property("ConsumedAt") + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("Currency") + .HasColumnType("INTEGER"); + + b.Property("CurrencyType") + .HasColumnType("INTEGER"); + + b.Property("EquipmentModificationGuid") + .IsRequired() + .HasMaxLength(64) + .HasColumnType("TEXT"); + + b.Property("EquipmentPrefabName") + .IsRequired() + .HasMaxLength(128) + .HasColumnType("TEXT"); + + b.Property("ErrorMessage") + .IsRequired() + .HasMaxLength(512) + .HasColumnType("TEXT"); + + b.Property("FromPlayerId") + .HasColumnType("INTEGER"); + + b.Property("GiftContext") + .HasColumnType("INTEGER"); + + b.Property("GiftRarity") + .HasColumnType("INTEGER"); + + b.Property("IsGifted") + .HasColumnType("INTEGER"); + + b.Property("IsValid") + .HasColumnType("INTEGER"); + + b.Property("Level") + .HasColumnType("INTEGER"); + + b.Property("Message") + .IsRequired() + .HasMaxLength(512) + .HasColumnType("TEXT"); + + b.Property("PackageMaterial") + .IsRequired() + .HasMaxLength(128) + .HasColumnType("TEXT"); + + b.Property("PackageVariant") + .IsRequired() + .HasMaxLength(128) + .HasColumnType("TEXT"); + + b.Property("Platform") + .HasColumnType("INTEGER"); + + b.Property("RecipientPlayerId") + .HasColumnType("INTEGER"); + + b.Property("SupportsCurrentPlatform") + .HasColumnType("INTEGER"); + + b.Property("Xp") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("RecipientPlayerId", "Consumed"); + + b.ToTable("GiftPackages"); + }); + + modelBuilder.Entity("DorkNet.Server.Data.Entities.InventionEntity", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("CheerCount") + .HasColumnType("INTEGER"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("CreationRoomId") + .HasColumnType("INTEGER"); + + b.Property("CreatorPermission") + .HasColumnType("INTEGER"); + + b.Property("CreatorPlayerId") + .HasColumnType("INTEGER"); + + b.Property("CurrentBlobName") + .IsRequired() + .HasMaxLength(128) + .HasColumnType("TEXT"); + + b.Property("CurrentVersionNumber") + .HasColumnType("INTEGER"); + + b.Property("Description") + .IsRequired() + .HasMaxLength(2000) + .HasColumnType("TEXT"); + + b.Property("FirstPublishedAt") + .HasColumnType("TEXT"); + + b.Property("GeneralPermission") + .HasColumnType("INTEGER"); + + b.Property("ImageName") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("TEXT"); + + b.Property("IsAgInvention") + .HasColumnType("INTEGER"); + + b.Property("IsDeleted") + .HasColumnType("INTEGER"); + + b.Property("IsPublished") + .HasColumnType("INTEGER"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(128) + .HasColumnType("TEXT"); + + b.Property("NumPlayersHaveUsedInRoom") + .HasColumnType("INTEGER"); + + b.Property("Permission") + .HasColumnType("INTEGER"); + + b.Property("ReplicationId") + .IsRequired() + .HasMaxLength(64) + .HasColumnType("TEXT"); + + b.Property("SpawnCount") + .HasColumnType("INTEGER"); + + b.Property("TagsCsv") + .IsRequired() + .HasMaxLength(1024) + .HasColumnType("TEXT"); + + b.Property("UpdatedAt") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("CheerCount"); + + b.HasIndex("CreatorPlayerId"); + + b.ToTable("Inventions"); + }); + + modelBuilder.Entity("DorkNet.Server.Data.Entities.InventionVersionEntity", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("BlobName") + .IsRequired() + .HasMaxLength(128) + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("InstantiationCost") + .HasColumnType("INTEGER"); + + b.Property("InventionId") + .HasColumnType("INTEGER"); + + b.Property("LightsCost") + .HasColumnType("INTEGER"); + + b.Property("ReplicationId") + .IsRequired() + .HasMaxLength(64) + .HasColumnType("TEXT"); + + b.Property("VersionNumber") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("InventionId", "VersionNumber") + .IsUnique(); + + b.ToTable("InventionVersions"); + }); + + modelBuilder.Entity("DorkNet.Server.Data.Entities.IpBanEntity", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("BannedAt") + .HasColumnType("TEXT"); + + b.Property("BannedByAdminId") + .HasColumnType("INTEGER"); + + b.Property("Cidr") + .IsRequired() + .HasMaxLength(64) + .HasColumnType("TEXT"); + + b.Property("Reason") + .IsRequired() + .HasMaxLength(512) + .HasColumnType("TEXT"); + + b.Property("Until") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("Cidr"); + + b.ToTable("IpBans"); + }); + + modelBuilder.Entity("DorkNet.Server.Data.Entities.LeaderboardChannelMetaEntity", b => + { + b.Property("Channel") + .HasColumnType("INTEGER"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("LowerIsBetter") + .HasColumnType("INTEGER"); + + b.Property("Name") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("RoomId") + .HasColumnType("INTEGER"); + + b.Property("UpdatedAt") + .HasColumnType("TEXT"); + + b.Property("ValueFormat") + .IsRequired() + .HasColumnType("TEXT"); + + b.HasKey("Channel"); + + b.HasIndex("RoomId"); + + b.ToTable("LeaderboardChannelMeta"); + }); + + modelBuilder.Entity("DorkNet.Server.Data.Entities.LeaderboardStatEntity", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("PlayerId") + .HasColumnType("INTEGER"); + + b.Property("StatChannel") + .HasColumnType("INTEGER"); + + b.Property("UpdatedAt") + .HasColumnType("TEXT"); + + b.Property("Value") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("PlayerId", "StatChannel") + .IsUnique(); + + b.HasIndex("StatChannel", "Value"); + + b.ToTable("LeaderboardStats"); + }); + + modelBuilder.Entity("DorkNet.Server.Data.Entities.LoadingScreenTipEntity", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("Context") + .HasColumnType("INTEGER"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("ImageName") + .IsRequired() + .HasMaxLength(128) + .HasColumnType("TEXT"); + + b.Property("IsActive") + .HasColumnType("INTEGER"); + + b.Property("Message") + .IsRequired() + .HasMaxLength(512) + .HasColumnType("TEXT"); + + b.Property("PlatformMask") + .HasColumnType("INTEGER"); + + b.Property("RoomNamesCsv") + .IsRequired() + .HasMaxLength(512) + .HasColumnType("TEXT"); + + b.Property("SortOrder") + .HasColumnType("INTEGER"); + + b.Property("Title") + .IsRequired() + .HasMaxLength(128) + .HasColumnType("TEXT"); + + b.Property("UpdatedAt") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.ToTable("LoadingScreenTips"); + }); + + modelBuilder.Entity("DorkNet.Server.Data.Entities.MessageEntity", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("Body") + .IsRequired() + .HasMaxLength(2000) + .HasColumnType("TEXT"); + + b.Property("ReadAt") + .HasColumnType("TEXT"); + + b.Property("RecipientPlayerId") + .HasColumnType("INTEGER"); + + b.Property("RoomId") + .HasColumnType("INTEGER"); + + b.Property("SenderPlayerId") + .HasColumnType("INTEGER"); + + b.Property("SentAt") + .HasColumnType("TEXT"); + + b.Property("Type") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("RecipientPlayerId", "SentAt"); + + b.HasIndex("SenderPlayerId", "SentAt"); + + b.ToTable("Messages"); + }); + + modelBuilder.Entity("DorkNet.Server.Data.Entities.NotificationPrefsEntity", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("AllowAll") + .HasColumnType("INTEGER"); + + b.Property("AllowAnnouncements") + .HasColumnType("INTEGER"); + + b.Property("AllowEventInvite") + .HasColumnType("INTEGER"); + + b.Property("AllowFriendRequest") + .HasColumnType("INTEGER"); + + b.Property("AllowMessage") + .HasColumnType("INTEGER"); + + b.Property("Platform") + .IsRequired() + .HasMaxLength(32) + .HasColumnType("TEXT"); + + b.Property("PlayerId") + .HasColumnType("INTEGER"); + + b.Property("UpdatedAt") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("PlayerId", "Platform") + .IsUnique(); + + b.ToTable("NotificationPrefs"); + }); + + modelBuilder.Entity("DorkNet.Server.Data.Entities.ObjectiveProgressEntity", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("ClearedAt") + .HasColumnType("TEXT"); + + b.Property("IsCompleted") + .HasColumnType("INTEGER"); + + b.Property("Key") + .IsRequired() + .HasMaxLength(128) + .HasColumnType("TEXT"); + + b.Property("PlayerId") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("PlayerId", "Key") + .IsUnique(); + + b.ToTable("ObjectiveProgress"); + }); + + modelBuilder.Entity("DorkNet.Server.Data.Entities.PhotoEntity", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("BlobName") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("TEXT"); + + b.Property("Caption") + .IsRequired() + .HasMaxLength(2048) + .HasColumnType("TEXT"); + + b.Property("CheerCount") + .HasColumnType("INTEGER"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("DeletedAt") + .HasColumnType("TEXT"); + + b.Property("IsPublic") + .HasColumnType("INTEGER"); + + b.Property("RoomId") + .HasColumnType("INTEGER"); + + b.Property("TaggedPlayerIdsCsv") + .IsRequired() + .HasMaxLength(2048) + .HasColumnType("TEXT"); + + b.Property("UploaderPlayerId") + .HasColumnType("INTEGER"); + + b.Property("ViewCount") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("BlobName"); + + b.HasIndex("CreatedAt"); + + b.HasIndex("RoomId"); + + b.HasIndex("UploaderPlayerId"); + + b.ToTable("Photos"); + }); + + modelBuilder.Entity("DorkNet.Server.Data.Entities.PlatformIgnoreEntity", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("Platform") + .HasColumnType("INTEGER"); + + b.Property("PlatformUserId") + .IsRequired() + .HasMaxLength(64) + .HasColumnType("TEXT"); + + b.Property("PlayerId") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("PlayerId", "Platform", "PlatformUserId") + .IsUnique(); + + b.ToTable("PlatformIgnores"); + }); + + modelBuilder.Entity("DorkNet.Server.Data.Entities.PlayerDeviceEntity", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("DeviceId") + .IsRequired() + .HasMaxLength(128) + .HasColumnType("TEXT"); + + b.Property("FirstSeen") + .HasColumnType("TEXT"); + + b.Property("IsBanned") + .HasColumnType("INTEGER"); + + b.Property("LastSeen") + .HasColumnType("TEXT"); + + b.Property("Platform") + .IsRequired() + .HasMaxLength(32) + .HasColumnType("TEXT"); + + b.Property("PlayerId") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("DeviceId"); + + b.HasIndex("PlayerId"); + + b.ToTable("PlayerDevices"); + }); + + modelBuilder.Entity("DorkNet.Server.Data.Entities.PlayerEloEntity", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("Elo") + .HasColumnType("INTEGER"); + + b.Property("GameMode") + .HasColumnType("INTEGER"); + + b.Property("Losses") + .HasColumnType("INTEGER"); + + b.Property("MatchesPlayed") + .HasColumnType("INTEGER"); + + b.Property("PlayerId") + .HasColumnType("INTEGER"); + + b.Property("UpdatedAt") + .HasColumnType("TEXT"); + + b.Property("Wins") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("GameMode", "Elo"); + + b.HasIndex("PlayerId", "GameMode") + .IsUnique(); + + b.ToTable("PlayerElo"); + }); + + modelBuilder.Entity("DorkNet.Server.Data.Entities.PlayerEntity", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("BannedUntil") + .HasColumnType("TEXT"); + + b.Property("Bio") + .IsRequired() + .HasMaxLength(512) + .HasColumnType("TEXT"); + + b.Property("Birthday") + .HasColumnType("TEXT"); + + b.Property("CanReceiveInvites") + .HasColumnType("INTEGER"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("CreatedFromDeviceId") + .HasMaxLength(128) + .HasColumnType("TEXT"); + + b.Property("DeviceId") + .IsRequired() + .HasMaxLength(128) + .HasColumnType("TEXT"); + + b.Property("DisplayName") + .IsRequired() + .HasMaxLength(64) + .HasColumnType("TEXT"); + + b.Property("Email") + .HasMaxLength(256) + .HasColumnType("TEXT"); + + b.Property("IsAdmin") + .HasColumnType("INTEGER"); + + b.Property("IsCommunityTeam") + .HasColumnType("INTEGER"); + + b.Property("IsDeveloper") + .HasColumnType("INTEGER"); + + b.Property("IsJunior") + .HasColumnType("INTEGER"); + + b.Property("IsVerified") + .HasColumnType("INTEGER"); + + b.Property("LastIp") + .HasMaxLength(64) + .HasColumnType("TEXT"); + + b.Property("LastPlatform") + .HasColumnType("INTEGER"); + + b.Property("LastPlatformId") + .IsRequired() + .HasMaxLength(128) + .HasColumnType("TEXT"); + + b.Property("LastSeenAt") + .HasColumnType("TEXT"); + + b.Property("Level") + .HasColumnType("INTEGER"); + + b.Property("Locale") + .HasMaxLength(16) + .HasColumnType("TEXT"); + + b.Property("PasswordHash") + .HasMaxLength(128) + .HasColumnType("TEXT"); + + b.Property("Phone") + .HasMaxLength(32) + .HasColumnType("TEXT"); + + b.Property("ProfileImageName") + .HasMaxLength(256) + .HasColumnType("TEXT"); + + b.Property("Reputation") + .HasColumnType("INTEGER"); + + b.Property("Username") + .IsRequired() + .HasMaxLength(64) + .HasColumnType("TEXT"); + + b.Property("XP") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("Username") + .IsUnique(); + + b.ToTable("Players"); + }); + + modelBuilder.Entity("DorkNet.Server.Data.Entities.PlayerEventEntity", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("Capacity") + .HasColumnType("INTEGER"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("CreatorPlayerId") + .HasColumnType("INTEGER"); + + b.Property("Description") + .IsRequired() + .HasMaxLength(2000) + .HasColumnType("TEXT"); + + b.Property("EndsAt") + .HasColumnType("TEXT"); + + b.Property("RoomId") + .HasColumnType("INTEGER"); + + b.Property("StartsAt") + .HasColumnType("TEXT"); + + b.Property("Title") + .IsRequired() + .HasMaxLength(128) + .HasColumnType("TEXT"); + + b.Property("UpdatedAt") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("CreatorPlayerId"); + + b.HasIndex("StartsAt"); + + b.ToTable("PlayerEvents"); + }); + + modelBuilder.Entity("DorkNet.Server.Data.Entities.PlayerEventResponseEntity", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("EventId") + .HasColumnType("INTEGER"); + + b.Property("PlayerId") + .HasColumnType("INTEGER"); + + b.Property("Response") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("PlayerId"); + + b.HasIndex("EventId", "PlayerId") + .IsUnique(); + + b.ToTable("PlayerEventResponses"); + }); + + modelBuilder.Entity("DorkNet.Server.Data.Entities.PlayerInventoryEntity", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("AcquiredAt") + .HasColumnType("TEXT"); + + b.Property("IsActive") + .HasColumnType("INTEGER"); + + b.Property("ItemSlug") + .IsRequired() + .HasMaxLength(64) + .HasColumnType("TEXT"); + + b.Property("PlayerId") + .HasColumnType("INTEGER"); + + b.Property("Quantity") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("PlayerId"); + + b.HasIndex("PlayerId", "ItemSlug") + .IsUnique(); + + b.ToTable("PlayerInventory"); + }); + + modelBuilder.Entity("DorkNet.Server.Data.Entities.PlayerSettingEntity", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("Key") + .IsRequired() + .HasMaxLength(128) + .HasColumnType("TEXT"); + + b.Property("PlayerId") + .HasColumnType("INTEGER"); + + b.Property("Value") + .IsRequired() + .HasMaxLength(1024) + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("PlayerId", "Key") + .IsUnique(); + + b.ToTable("PlayerSettings"); + }); + + modelBuilder.Entity("DorkNet.Server.Data.Entities.PlaylistEntity", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("CheerCount") + .HasColumnType("INTEGER"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("CreatorPlayerId") + .HasColumnType("INTEGER"); + + b.Property("Description") + .IsRequired() + .HasMaxLength(2000) + .HasColumnType("TEXT"); + + b.Property("FavoriteCount") + .HasColumnType("INTEGER"); + + b.Property("ImageName") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("TEXT"); + + b.Property("IsCurated") + .HasColumnType("INTEGER"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(128) + .HasColumnType("TEXT") + .UseCollation("NOCASE"); + + b.Property("OrderIndex") + .HasColumnType("INTEGER"); + + b.Property("TagsCsv") + .IsRequired() + .HasMaxLength(1024) + .HasColumnType("TEXT"); + + b.Property("UpdatedAt") + .HasColumnType("TEXT"); + + b.Property("VisitCount") + .HasColumnType("INTEGER"); + + b.Property("VisitorCount") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("CreatorPlayerId"); + + b.HasIndex("IsCurated"); + + b.HasIndex("Name"); + + b.ToTable("Playlists"); + }); + + modelBuilder.Entity("DorkNet.Server.Data.Entities.PlaylistInteractionEntity", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("Cheered") + .HasColumnType("INTEGER"); + + b.Property("Favorited") + .HasColumnType("INTEGER"); + + b.Property("PlayerId") + .HasColumnType("INTEGER"); + + b.Property("PlaylistId") + .HasColumnType("INTEGER"); + + b.Property("UpdatedAt") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("PlayerId"); + + b.HasIndex("PlaylistId", "PlayerId") + .IsUnique(); + + b.ToTable("PlaylistInteractions"); + }); + + modelBuilder.Entity("DorkNet.Server.Data.Entities.PlaylistRoomEntity", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("OrderIndex") + .HasColumnType("INTEGER"); + + b.Property("PlaylistId") + .HasColumnType("INTEGER"); + + b.Property("RoomId") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("PlaylistId"); + + b.HasIndex("RoomId"); + + b.HasIndex("PlaylistId", "OrderIndex"); + + b.ToTable("PlaylistRooms"); + }); + + modelBuilder.Entity("DorkNet.Server.Data.Entities.PrivateInstanceEntity", b => + { + b.Property("Id") + .HasColumnType("INTEGER"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("DataBlob") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("TEXT"); + + b.Property("Location") + .IsRequired() + .HasMaxLength(64) + .HasColumnType("TEXT"); + + b.Property("MaxCapacity") + .HasColumnType("INTEGER"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(128) + .HasColumnType("TEXT"); + + b.Property("OwnerPlayerId") + .HasColumnType("INTEGER"); + + b.Property("PhotonRegion") + .IsRequired() + .HasMaxLength(16) + .HasColumnType("TEXT"); + + b.Property("PhotonRoomId") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("TEXT"); + + b.Property("RoomId") + .HasColumnType("INTEGER"); + + b.Property("SubRoomId") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("OwnerPlayerId"); + + b.HasIndex("RoomId", "SubRoomId"); + + b.ToTable("PrivateInstances"); + }); + + modelBuilder.Entity("DorkNet.Server.Data.Entities.PrivateInstanceInviteeEntity", b => + { + b.Property("PrivateInstanceId") + .HasColumnType("INTEGER"); + + b.Property("PlayerId") + .HasColumnType("INTEGER"); + + b.Property("InvitedAt") + .HasColumnType("TEXT"); + + b.Property("LatestInviteMessageId") + .HasColumnType("INTEGER"); + + b.HasKey("PrivateInstanceId", "PlayerId"); + + b.HasIndex("PlayerId"); + + b.ToTable("PrivateInstanceInvitees"); + }); + + modelBuilder.Entity("DorkNet.Server.Data.Entities.PushTokenEntity", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("Platform") + .IsRequired() + .HasMaxLength(16) + .HasColumnType("TEXT"); + + b.Property("PlayerId") + .HasColumnType("INTEGER"); + + b.Property("Token") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("TEXT"); + + b.Property("UpdatedAt") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("Token"); + + b.HasIndex("PlayerId", "Platform") + .IsUnique(); + + b.ToTable("PushTokens"); + }); + + modelBuilder.Entity("DorkNet.Server.Data.Entities.RelationshipEntity", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("Favorited") + .HasColumnType("INTEGER"); + + b.Property("Ignored") + .HasColumnType("INTEGER"); + + b.Property("Muted") + .HasColumnType("INTEGER"); + + b.Property("RequesterId") + .HasColumnType("INTEGER"); + + b.Property("Status") + .HasColumnType("INTEGER"); + + b.Property("TargetId") + .HasColumnType("INTEGER"); + + b.Property("UpdatedAt") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("RequesterId", "TargetId") + .IsUnique(); + + b.ToTable("Relationships"); + }); + + modelBuilder.Entity("DorkNet.Server.Data.Entities.ReportEntity", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("Category") + .HasColumnType("INTEGER"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("GameSessionId") + .HasColumnType("INTEGER"); + + b.Property("Message") + .IsRequired() + .HasMaxLength(1000) + .HasColumnType("TEXT"); + + b.Property("ReporterPlayerId") + .HasColumnType("INTEGER"); + + b.Property("ResolutionNote") + .IsRequired() + .HasMaxLength(512) + .HasColumnType("TEXT"); + + b.Property("ResolvedAt") + .HasColumnType("TEXT"); + + b.Property("ResolverAdminId") + .HasColumnType("INTEGER"); + + b.Property("RoomId") + .HasColumnType("INTEGER"); + + b.Property("TargetEventId") + .HasColumnType("INTEGER"); + + b.Property("TargetInventionId") + .HasColumnType("INTEGER"); + + b.Property("TargetPlayerId") + .HasColumnType("INTEGER"); + + b.Property("TargetRoomId") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("ReporterPlayerId"); + + b.HasIndex("ResolvedAt"); + + b.HasIndex("TargetPlayerId"); + + b.ToTable("Reports"); + }); + + modelBuilder.Entity("DorkNet.Server.Data.Entities.RoomBanEntity", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("BanType") + .HasColumnType("INTEGER"); + + b.Property("BannedByPlayerId") + .HasColumnType("INTEGER"); + + b.Property("BannedPlayerId") + .HasColumnType("INTEGER"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("Reason") + .IsRequired() + .HasMaxLength(512) + .HasColumnType("TEXT"); + + b.Property("RoomId") + .HasColumnType("INTEGER"); + + b.Property("Until") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("BannedPlayerId"); + + b.HasIndex("RoomId", "BannedPlayerId"); + + b.ToTable("RoomBans"); + }); + + modelBuilder.Entity("DorkNet.Server.Data.Entities.RoomBookmarkEntity", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("PlayerId") + .HasColumnType("INTEGER"); + + b.Property("RoomId") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("PlayerId"); + + b.HasIndex("PlayerId", "RoomId") + .IsUnique(); + + b.ToTable("RoomBookmarks"); + }); + + modelBuilder.Entity("DorkNet.Server.Data.Entities.RoomDataBlobEntity", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("BlobName") + .IsRequired() + .HasMaxLength(128) + .HasColumnType("TEXT"); + + b.Property("Bytes") + .HasColumnType("BLOB"); + + b.Property("ReferencedFilenamesCsv") + .IsRequired() + .HasMaxLength(2048) + .HasColumnType("TEXT"); + + b.Property("RoomId") + .HasColumnType("INTEGER"); + + b.Property("SubRoomId") + .HasColumnType("INTEGER"); + + b.Property("UploadedAt") + .HasColumnType("TEXT"); + + b.Property("UploadedByPlayerId") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("BlobName") + .IsUnique(); + + b.HasIndex("RoomId"); + + b.ToTable("RoomDataBlobs"); + }); + + modelBuilder.Entity("DorkNet.Server.Data.Entities.RoomEntity", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("Accessibility") + .HasColumnType("INTEGER"); + + b.Property("AllowsJuniors") + .HasColumnType("INTEGER"); + + b.Property("CheerCount") + .HasColumnType("INTEGER"); + + b.Property("CloningAllowed") + .HasColumnType("INTEGER"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("CreatorPlayerId") + .HasColumnType("INTEGER"); + + b.Property("CurrentDataBlobName") + .IsRequired() + .HasMaxLength(128) + .HasColumnType("TEXT"); + + b.Property("CustomRoomWarning") + .IsRequired() + .HasMaxLength(512) + .HasColumnType("TEXT"); + + b.Property("Description") + .IsRequired() + .HasMaxLength(2000) + .HasColumnType("TEXT"); + + b.Property("DisableMicAutoMute") + .HasColumnType("INTEGER"); + + b.Property("FavoriteCount") + .HasColumnType("INTEGER"); + + b.Property("HiddenFromBrowse") + .HasColumnType("INTEGER"); + + b.Property("HotScore") + .HasColumnType("REAL"); + + b.Property("ImageName") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("TEXT"); + + b.Property("IsAGRoom") + .HasColumnType("INTEGER"); + + b.Property("IsDormRoom") + .HasColumnType("INTEGER"); + + b.Property("LocationReplicationId") + .IsRequired() + .HasMaxLength(64) + .HasColumnType("TEXT"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(128) + .HasColumnType("TEXT") + .UseCollation("NOCASE"); + + b.Property("RoomWarningMask") + .HasColumnType("INTEGER"); + + b.Property("State") + .HasColumnType("INTEGER"); + + b.Property("SupportsLevelVoting") + .HasColumnType("INTEGER"); + + b.Property("SupportsMobile") + .HasColumnType("INTEGER"); + + b.Property("SupportsScreens") + .HasColumnType("INTEGER"); + + b.Property("SupportsTeleportVR") + .HasColumnType("INTEGER"); + + b.Property("SupportsVRLow") + .HasColumnType("INTEGER"); + + b.Property("SupportsWalkVR") + .HasColumnType("INTEGER"); + + b.Property("TagsCsv") + .IsRequired() + .HasMaxLength(1024) + .HasColumnType("TEXT"); + + b.Property("UpdatedAt") + .HasColumnType("TEXT"); + + b.Property("VisitCount") + .HasColumnType("INTEGER"); + + b.Property("VisitorCount") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("CreatorPlayerId"); + + b.HasIndex("HotScore"); + + b.HasIndex("Name") + .IsUnique(); + + b.ToTable("Rooms"); + }); + + modelBuilder.Entity("DorkNet.Server.Data.Entities.RoomKeyEntity", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("CreatorPlayerId") + .HasColumnType("INTEGER"); + + b.Property("Description") + .IsRequired() + .HasMaxLength(174) + .HasColumnType("TEXT"); + + b.Property("IsDeleted") + .HasColumnType("INTEGER"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(40) + .HasColumnType("TEXT"); + + b.Property("Price") + .HasColumnType("INTEGER"); + + b.Property("ReplicationId") + .IsRequired() + .HasMaxLength(64) + .HasColumnType("TEXT"); + + b.Property("RoomId") + .HasColumnType("INTEGER"); + + b.Property("UpdatedAt") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("CreatorPlayerId"); + + b.HasIndex("RoomId"); + + b.HasIndex("RoomId", "Name"); + + b.ToTable("RoomKeys"); + }); + + modelBuilder.Entity("DorkNet.Server.Data.Entities.RoomKeyPurchaseEntity", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("PaidPrice") + .HasColumnType("INTEGER"); + + b.Property("PlayerId") + .HasColumnType("INTEGER"); + + b.Property("PurchasedAt") + .HasColumnType("TEXT"); + + b.Property("RoomKeyId") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("PlayerId"); + + b.HasIndex("RoomKeyId", "PlayerId") + .IsUnique(); + + b.ToTable("RoomKeyPurchases"); + }); + + modelBuilder.Entity("DorkNet.Server.Data.Entities.RoomRoleEntity", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("Accepted") + .HasColumnType("INTEGER"); + + b.Property("GrantedAt") + .HasColumnType("TEXT"); + + b.Property("GrantedByPlayerId") + .HasColumnType("INTEGER"); + + b.Property("PlayerId") + .HasColumnType("INTEGER"); + + b.Property("Role") + .HasColumnType("INTEGER"); + + b.Property("RoomId") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("RoomId"); + + b.HasIndex("RoomId", "PlayerId", "Role") + .IsUnique(); + + b.ToTable("RoomRoles"); + }); + + modelBuilder.Entity("DorkNet.Server.Data.Entities.RoomSceneEntity", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("CanMatchmakeInto") + .HasColumnType("INTEGER"); + + b.Property("DataBlobName") + .IsRequired() + .HasMaxLength(128) + .HasColumnType("TEXT"); + + b.Property("DataModifiedAt") + .HasColumnType("TEXT"); + + b.Property("IsSandbox") + .HasColumnType("INTEGER"); + + b.Property("MaxPlayers") + .HasColumnType("INTEGER"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(128) + .HasColumnType("TEXT") + .UseCollation("NOCASE"); + + b.Property("OrderIndex") + .HasColumnType("INTEGER"); + + b.Property("RoomId") + .HasColumnType("INTEGER"); + + b.Property("RoomSceneLocationId") + .IsRequired() + .HasMaxLength(64) + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("RoomId", "Name"); + + b.HasIndex("RoomId", "OrderIndex") + .IsUnique(); + + b.ToTable("RoomScenes"); + }); + + modelBuilder.Entity("DorkNet.Server.Data.Entities.RoomVisitEntity", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("FirstVisitAt") + .HasColumnType("TEXT"); + + b.Property("LastVisitAt") + .HasColumnType("TEXT"); + + b.Property("PlayerId") + .HasColumnType("INTEGER"); + + b.Property("RoomId") + .HasColumnType("INTEGER"); + + b.Property("VisitCount") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("PlayerId"); + + b.HasIndex("RoomId", "LastVisitAt"); + + b.HasIndex("RoomId", "PlayerId") + .IsUnique(); + + b.ToTable("RoomVisits"); + }); + + modelBuilder.Entity("DorkNet.Server.Data.Entities.RoyaleMatchEntity", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("ChestsOpened") + .HasColumnType("INTEGER"); + + b.Property("CompletedAt") + .HasColumnType("TEXT"); + + b.Property("CustomGame") + .HasColumnType("INTEGER"); + + b.Property("HealthPotionsConsumed") + .HasColumnType("INTEGER"); + + b.Property("NumEliminations") + .HasColumnType("INTEGER"); + + b.Property("Rank") + .HasColumnType("INTEGER"); + + b.Property("SecondsAlive") + .HasColumnType("INTEGER"); + + b.Property("SecondsInAir") + .HasColumnType("INTEGER"); + + b.Property("ShieldPotionsConsumed") + .HasColumnType("INTEGER"); + + b.Property("WalkGame") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("CompletedAt"); + + b.ToTable("RoyaleMatches"); + }); + + modelBuilder.Entity("DorkNet.Server.Data.Entities.RoyaleMatchPlayerEntity", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("MatchId") + .HasColumnType("INTEGER"); + + b.Property("NumEliminations") + .HasColumnType("INTEGER"); + + b.Property("PlayerId") + .HasColumnType("INTEGER"); + + b.Property("Rank") + .HasColumnType("INTEGER"); + + b.Property("SecondsAlive") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("PlayerId"); + + b.HasIndex("MatchId", "PlayerId"); + + b.ToTable("RoyaleMatchPlayers"); + }); + + modelBuilder.Entity("DorkNet.Server.Data.Entities.RoyalePlayerProgressEntity", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("CurrentLevelXPThreshold") + .HasColumnType("INTEGER"); + + b.Property("Level") + .HasColumnType("INTEGER"); + + b.Property("NextLevelAcornReward") + .HasColumnType("INTEGER"); + + b.Property("NextLevelXPThreshold") + .HasColumnType("INTEGER"); + + b.Property("PlayerId") + .HasColumnType("INTEGER"); + + b.Property("RankIdx") + .HasColumnType("INTEGER"); + + b.Property("RankName") + .IsRequired() + .HasMaxLength(64) + .HasColumnType("TEXT"); + + b.Property("TotalXP") + .HasColumnType("INTEGER"); + + b.Property("UpdatedAt") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("PlayerId") + .IsUnique(); + + b.ToTable("RoyalePlayerProgress"); + }); + + modelBuilder.Entity("DorkNet.Server.Data.Entities.ServerSettingsEntity", b => + { + b.Property("Id") + .HasColumnType("INTEGER"); + + b.Property("SignupsDisabled") + .HasColumnType("INTEGER"); + + b.Property("UpdatedAt") + .HasColumnType("TEXT"); + + b.Property("WeeklyChallengeRewardJson") + .IsRequired() + .ValueGeneratedOnAdd() + .HasColumnType("TEXT") + .HasDefaultValue(""); + + b.Property("WeeklyChallengesCompletedRequired") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER") + .HasDefaultValue(true); + + b.Property("WeeklyChallengesJson") + .IsRequired() + .ValueGeneratedOnAdd() + .HasColumnType("TEXT") + .HasDefaultValue(""); + + b.HasKey("Id"); + + b.ToTable("ServerSettings"); + }); + + modelBuilder.Entity("DorkNet.Server.Data.Entities.StoreItemEntity", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("AvailableUntil") + .HasColumnType("TEXT"); + + b.Property("Category") + .IsRequired() + .HasMaxLength(32) + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("CurrencyType") + .HasColumnType("INTEGER"); + + b.Property("Description") + .IsRequired() + .HasMaxLength(1024) + .HasColumnType("TEXT"); + + b.Property("DisplayName") + .IsRequired() + .HasMaxLength(128) + .HasColumnType("TEXT"); + + b.Property("ImageName") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("TEXT"); + + b.Property("IsActive") + .HasColumnType("INTEGER"); + + b.Property("IsLimitedTime") + .HasColumnType("INTEGER"); + + b.Property("Price") + .HasColumnType("INTEGER"); + + b.Property("Slug") + .IsRequired() + .HasMaxLength(128) + .HasColumnType("TEXT"); + + b.Property("Storefront") + .IsRequired() + .HasMaxLength(32) + .HasColumnType("TEXT"); + + b.Property("UpdatedAt") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("Slug") + .IsUnique(); + + b.HasIndex("Storefront"); + + b.HasIndex("IsActive", "Category"); + + b.ToTable("StoreItems"); + }); + + modelBuilder.Entity("DorkNet.Server.Data.Entities.SubscriptionEntity", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("SubscriberPlayerId") + .HasColumnType("INTEGER"); + + b.Property("TargetPlayerId") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("SubscriberPlayerId"); + + b.HasIndex("TargetPlayerId"); + + b.HasIndex("SubscriberPlayerId", "TargetPlayerId") + .IsUnique(); + + b.ToTable("Subscriptions"); + }); + + modelBuilder.Entity("DorkNet.Server.Data.Entities.TestCaseEntity", b => + { + b.Property("Pk") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("AssignedPlayerIdsJson") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("AssignedPlayerNamesJson") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("Description") + .IsRequired() + .HasMaxLength(2000) + .HasColumnType("TEXT"); + + b.Property("Id") + .HasMaxLength(64) + .HasColumnType("TEXT"); + + b.Property("JiraBugUrl") + .IsRequired() + .HasMaxLength(512) + .HasColumnType("TEXT"); + + b.Property("JiraUrl") + .IsRequired() + .HasMaxLength(512) + .HasColumnType("TEXT"); + + b.Property("Key") + .IsRequired() + .HasMaxLength(64) + .HasColumnType("TEXT"); + + b.Property("MinNumAssignedPlayers") + .HasColumnType("INTEGER"); + + b.Property("RoomName") + .IsRequired() + .HasMaxLength(128) + .HasColumnType("TEXT"); + + b.Property("Status") + .HasColumnType("INTEGER"); + + b.Property("TagsJson") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("TestPassId") + .HasColumnType("INTEGER"); + + b.Property("Title") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("TEXT"); + + b.Property("UpdatedAt") + .HasColumnType("TEXT"); + + b.HasKey("Pk"); + + b.HasIndex("Id") + .IsUnique(); + + b.HasIndex("Status"); + + b.HasIndex("TestPassId"); + + b.ToTable("TestCases"); + }); + + modelBuilder.Entity("DorkNet.Server.Data.Entities.TestPassEntity", b => + { + b.Property("Id") + .HasColumnType("INTEGER"); + + b.Property("Description") + .IsRequired() + .HasMaxLength(2000) + .HasColumnType("TEXT"); + + b.Property("EndDate") + .HasColumnType("TEXT"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(128) + .HasColumnType("TEXT"); + + b.Property("StartDate") + .HasColumnType("TEXT"); + + b.Property("TagsJson") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("WasManuallyClosed") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.ToTable("TestPasses"); + }); + + modelBuilder.Entity("DorkNet.Server.Data.Entities.AvatarEntity", b => + { + b.HasOne("DorkNet.Server.Data.Entities.PlayerEntity", "Player") + .WithOne("Avatar") + .HasForeignKey("DorkNet.Server.Data.Entities.AvatarEntity", "PlayerId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Player"); + }); + + modelBuilder.Entity("DorkNet.Server.Data.Entities.PlayerSettingEntity", b => + { + b.HasOne("DorkNet.Server.Data.Entities.PlayerEntity", "Player") + .WithMany("Settings") + .HasForeignKey("PlayerId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Player"); + }); + + modelBuilder.Entity("DorkNet.Server.Data.Entities.RelationshipEntity", b => + { + b.HasOne("DorkNet.Server.Data.Entities.PlayerEntity", "Requester") + .WithMany("Relationships") + .HasForeignKey("RequesterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Requester"); + }); + + modelBuilder.Entity("DorkNet.Server.Data.Entities.PlayerEntity", b => + { + b.Navigation("Avatar"); + + b.Navigation("Relationships"); + + b.Navigation("Settings"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/DorkNet.Server/Program.cs b/DorkNet.Server/Program.cs new file mode 100644 index 0000000..2e528cc --- /dev/null +++ b/DorkNet.Server/Program.cs @@ -0,0 +1,17 @@ +using DorkNet.Server.Startup; + +var builder = WebApplication.CreateBuilder(args); + +// Personal/local-machine override layer. Loaded last so it wins over both +// appsettings.json and appsettings.{Environment}.json. Gitignored — use +// this for Photon AppId, JWT secret, and anything you don't want committed. +builder.Configuration.AddJsonFile("appsettings.Local.json", + optional: true, reloadOnChange: true); + +builder.AddDorkNetServices(); + +var app = builder.Build(); + +await app.RunDatabaseBootstrapAsync(); +app.UseDorkNetPipeline(); +app.Run(); diff --git a/DorkNet.Server/Properties/launchSettings.json b/DorkNet.Server/Properties/launchSettings.json new file mode 100644 index 0000000..9ba46b1 --- /dev/null +++ b/DorkNet.Server/Properties/launchSettings.json @@ -0,0 +1,14 @@ +{ + "$schema": "https://json.schemastore.org/launchsettings.json", + "profiles": { + "http": { + "commandName": "Project", + "dotnetRunMessages": true, + "launchBrowser": false, + "applicationUrl": "http://localhost:5137", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + } + } + } +} diff --git a/DorkNet.Server/Protos/dorknet_room_data.proto b/DorkNet.Server/Protos/dorknet_room_data.proto new file mode 100644 index 0000000..eda6118 --- /dev/null +++ b/DorkNet.Server/Protos/dorknet_room_data.proto @@ -0,0 +1,96 @@ +syntax = "proto3"; + +// Trimmed subset of Rec Room's persistence schema. Only the messages and +// fields we actually populate when handing the client a "default-but-with- +// permissions" room blob. Wire-compatible with the full schema in +// RR_ProtobufDefinitions.zip because proto3 ignores unknown fields and +// uses defaults for missing ones — so the client deserializes the bytes we +// emit into its richer types just fine. +// +// Field numbers MUST match the upstream schema exactly; deviating breaks +// the wire format. + +package rec_room; +option csharp_namespace = "RecRoom.Protobuf"; + +// ── Overridable wrappers (rec_room/persistence/overridable_field_data.proto) + +message OverridableIntData { + bool overrides = 1; + int32 inner_value = 2; +} + +message OverridableBoolData { + bool overrides = 1; + bool inner_value = 2; +} + +message OverridableStringData { + bool overrides = 1; + string inner_value = 2; +} + +message OverridableListStringData { + bool overrides = 1; + repeated string inner_value = 2; +} + +// ── Per-role permission grants (rec_room/player_roles/player_room_role_data.proto) +// +// Field numbers verified against the dump's `*FieldNumber` constants +// (TypeDefIndex 15953-ish in the 2020 build) and against the 2026 .proto. + +message PlayerRoomRoleData { + reserved 15; + + int32 DEPRECATED_role_id = 1; + int32 DEPRECATED_role_rank = 2; + bool DEPRECATED_is_role_active = 3; + bool DEPRECATED_is_ag_role = 4; + string role_name = 5; + + OverridableStringData name = 6; + OverridableBoolData can_assign_roles = 7; + OverridableIntData vote_kick_permission = 8; + OverridableBoolData can_invite = 9; + OverridableBoolData DEPRECATED_can_edit_circuits = 10; + OverridableBoolData can_start_games = 11; + OverridableBoolData can_talk = 12; + OverridableBoolData can_print_photos = 13; + OverridableBoolData can_edit_room_roles = 14; + OverridableBoolData can_self_revive = 16; + OverridableBoolData can_end_games_early = 17; + bool is_modified_by_player = 18; + OverridableListStringData auto_assigned_game_roles = 19; + int32 role_version = 20; + OverridableBoolData can_change_game_mode = 21; + OverridableBoolData can_use_maker_pen = 22; + OverridableBoolData can_use_delete_all_button = 23; + OverridableBoolData can_save_inventions = 24; + OverridableBoolData disable_mic_auto_mute = 25; + OverridableBoolData can_use_share_cam = 26; + string role_guid = 27; +} + +// ── Role collection (rec_room/player_roles/room_role_collection_data.proto) + +message RoomRoleCollectionData { + repeated PlayerRoomRoleData room_roles = 1; + // field 2 (rec_net_migration_version) intentionally omitted — its enum + // type doesn't exist in the 2020 build, and proto3 default 0 is fine. +} + +// ── PersistedRoomData top-level (rec_room/persistence/persisted_room_data.proto) +// +// The 2026 schema has 30+ fields — we only set field 23 (room_role_data). +// Everything else stays at proto3 defaults, which the client treats as +// "no saved state for this room" — exactly what we want for a fresh dorm +// /clone with the role config injected. + +message PersistedRoomData { + // field 1 (DEPRECATED_version) — reserved, do not set. + // field 2 (persistence_views) — repeated, default empty list. + // fields 3..22 — left at defaults. + RoomRoleCollectionData room_role_data = 23; + // field 24+ — left at defaults. +} diff --git a/DorkNet.Server/Protos/recroom_2020.proto b/DorkNet.Server/Protos/recroom_2020.proto new file mode 100644 index 0000000..7e04b20 --- /dev/null +++ b/DorkNet.Server/Protos/recroom_2020.proto @@ -0,0 +1,986 @@ +syntax = "proto3"; + +package recroom; +option csharp_namespace = "RecRoom.Protobuf2020"; + +import "google/protobuf/timestamp.proto"; + +message AcceleratedMovementData { + float acceleration_time = 1; + bool uses_target_value = 2; +} + +message AmmunitionData { + int32 ammo_gear_type = 1; + int32 ammo_amount = 2; +} + +message AnimationData { + repeated KeyFrameData key_frames = 2; + int32 speed_percentage = 5; + int32 priority = 6; + int32 flags = 7; +} + +message AudioSamplerDataBlob { + int32 creator_id = 1; + float duration = 2; + AudioStreamData audio_data = 3; +} + +message AudioSamplerPersistenceData { + string blob_name = 1; + bool use2d_audio = 3; + int32 channel = 4; + float start_offset = 5; + float end_offset = 6; + bool interrupt_current_sound = 7; +} + +message AudioStreamData { + int32 channels = 1; + int32 sample_rate = 2; + int32 sample_count = 3; + int32 format = 10; + repeated float uncompressed_samples = 11; + bytes compressed_data = 12; + float compressed_bitrate = 13; + repeated int32 compressed_packet_sizes = 14; +} + +message AutosaveData { + repeated string referenced_filenames = 1; + repeated string legacy_additional_referenced_filenames = 2; + repeated int64 invention_ids = 3; + repeated int32 invention_references = 4; +} + +message AvatarItemSelectionData { + string avatar_item_rec_net_string = 1; + int32 body_part = 2; + bool is_trial_item = 3; +} + +message AvatarItemSelectionDataCollection { + repeated AvatarItemSelectionData avatar_item_selections = 1; +} + +message BackgroundObjectData { + int32 object_id = 1; + int32 color = 2; + float alpha = 3; + float rotation = 4; +} + +message BackgroundObjectsCircuitData { + repeated BackgroundObjectData object_data = 1; +} + +message BattleRoyaleArsenalData { + repeated BattleRoyaleGearData gear = 1; +} + +message BattleRoyaleGearData { + string display_name = 1; + bool is_available = 2; + int32 gear_type = 3; + int32 quality = 4; + int32 drop_weight = 5; + int32 equipment_id = 6; + RangedWeaponData ranged_weapon_data = 7; + HealthPotionData health_potion_data = 8; + AmmunitionData ammunition_data = 9; +} + +message BloomCircuitData { + float bloom_intensity = 1; + float bloom_threshold = 2; + float bloom_diffusion = 3; + int32 bloom_color = 4; +} + +message BouncePadData { + int32 mode = 1; + bool is_additive = 2; + float velocity_multiplier = 3; + float velocity_additive = 4; + float max_velocity = 5; + float impulse_velocity = 6; + int32 air_control_percentage = 7; +} + +message BreakableObjectData { + bool is_breakable = 1; +} + +message ButtonData { + bool signal_is_player_id = 1; +} + +message CharadesCoOpClassicGameRuleData { + float max_game_duration = 1; + float max_round_duration = 2; +} + +message CharadesTeamLightningGameRuleData { + int32 max_round_count = 1; + float performer_duration = 2; + float team_round_duration = 3; +} + +message CheckpointData { + string spawn_point_tag = 1; +} + +message ChildPersistenceViewData { + int32 child_id = 1; + PersistenceViewData data = 2; +} + +message CircuitGroupCircuitReferenceData { + bytes circuit_node_id = 1; + int32 slot = 2; + bool is_input = 3; +} + +message CircuitGroupConnectionData { + bytes circuit_reference_data = 1; + bytes interface_node_reference_data = 2; +} + +message CircuitGroupData { + repeated CircuitGroupInterfaceNodeData input_interface_nodes = 1; + repeated CircuitGroupInterfaceNodeData output_interface_nodes = 2; + string name = 3; + repeated TransformData local_transforms = 4; +} + +message CircuitGroupInterfaceNodeData { + CircuitGroupConnectionData internal_connection = 1; + CircuitGroupConnectionData external_connection = 2; + string name = 3; +} + +message CircuitGroupInterfaceNodeReferenceData { + bytes circuit_group_id = 1; + int32 index = 2; + bool is_input = 3; +} + +message CircuitNodeData { + int32 circuit_node_id = 1; + string node_message = 2; +} + +message ColorData { + float r = 1; + float g = 2; + float b = 3; + float a = 4; +} + +message ColorGradingCircuitData { + float color_grading_temp = 1; + float color_grading_tint = 2; + float color_grading_exposure = 3; + int32 color_grading_color_filter = 4; + float color_grading_color_filter_intensity = 5; + float color_grading_hue_shift = 6; + float color_grading_saturation = 7; + float color_grading_contrast = 8; +} + +message ConnectableGraphData { + ConnectableNodeData root_node = 1; +} + +message ConnectableNodeData { + bytes persistence_id = 1; + int32 slot_index = 2; + int32 parent_slot_index = 3; + Vector3Data position_relative_to_parent = 4; + Vector3Data rotation_relative_to_parent = 5; + repeated ConnectableNodeData children = 6; + bool is_root = 7; + bytes photon_id = 8; +} + +message ControlPointData { + Vector3Data position = 1; + Vector3Data normal = 2; + float radius = 3; +} + +message CreationObjectData { + ColorData color_override = 2; + int32 team_override = 3; + bool is_frozen = 4; + InventionPieceData invention_piece_data = 5; + bool is_grabbable = 6; + bool is_climbable = 7; + bool prevents_wall_run = 8; + bool prevents_clambering = 9; +} + +message CurveShapeData { + repeated ControlPointData control_points = 1; + bool flat_shaded = 2; + float tube_height_scale = 3; + int32 tube_ring_points = 4; +} + +message Deprecated_GameSettingsData { + int32 deprecated_mode_type = 1; +} + +message Deprecated_HostSettingsData { + bool deprecated_enforce_host_only_areas = 2; + bool deprecated_only_hosts_can_invite = 3; + bool deprecated_only_hosts_can_print_polaroids = 4; + int32 deprecated_flying_permission = 6; + bool deprecated_hide_scoreboards = 7; + int32 deprecated_circuit_editing_permission = 8; + bool deprecated_only_hosts_can_start_games = 9; + bool deprecated_only_hosts_can_vote_kick = 11; +} + +message Deprecated_SandboxSettingsData { + bool deprecated_limit_teleport_speed = 1; + int32 deprecated_time_limit_seconds = 2; +} + +message Deprecated_ToolData { + bool is_frozen = 1; +} + +message DodgeballGameRuleData { + int32 score_to_win = 1; +} + +message DrawingSurfaceData { + bool is_modified = 1; +} + +message DynamicLightData { + int32 intensity = 1; + int32 range = 2; +} + +message FogCircuitData { + int32 color = 1; + float start_distance = 2; + float fade_distance = 3; +} + +message GameCombatAutoHealConfigurationData { + float delay = 1; + float duration = 2; +} + +message GameCombatDownButNotOutConfigurationData { + float deprecated_max_value = 1; + float max_depletion_duration = 2; + float invincibility_duration = 3; +} + +message GameCombatEnemyUIConfigurationData { + bool opponent_ui_supported = 1; + bool teammate_ui_supported = 2; +} + +message GameCombatFriendlyFireConfigurationData { + bool player_on_player_friendly_fire_supported = 1; + bool player_on_enemy_friendly_fire_supported = 2; + bool enemy_on_player_friendly_fire_supported = 3; + bool enemy_on_enemy_friendly_fire_supported = 4; +} + +message GameCombatPlayerUIConfigurationData { + bool local_player_ui_supported = 1; + bool opponent_ui_supported = 2; + bool teammate_ui_supported = 3; + bool teammate_ui_always_visible = 4; + bool opponent_ui_always_visible = 5; +} + +message GameCombatRespawnConfigurationData { + float auto_respawn_delay = 1; + bool restore_health_on_respawn = 2; + float respawn_invincibility_duration = 3; +} + +message GameCombatReviveConfigurationData { + float required_delay = 1; + float max_revive_normalized_health = 2; + float min_revive_normalized_health = 3; + float multiplier_per_revive_normalized_health = 4; + bool pickup_tools_on_revive = 5; + float reviver_invincibility_duration = 6; + float revivee_invincibility_duration = 7; + float handshake_revive_hold_duration = 8; +} + +message GameConfigurationData { + string name = 1; + GameStartRequirementConfigurationData manual_start_requirement_configuration_data = 3; + GameStartRequirementConfigurationData automatic_start_requirement_configuration_data = 4; + bool join_in_progress_supported = 5; + bool automatic_start_supported = 6; + int32 load_priority = 7; + bool respawn_on_game_start_supported = 8; + bool respawn_on_game_over_supported = 9; + bool pregame_voice_over_supported = 10; + bool results_voice_over_supported = 11; + bool end_game_if_start_requirements_not_met = 12; + bool countdown_voice_over_supported = 13; + float game_start_delay = 14; + float end_game_results_duration = 15; + bool game_mode_notifications_supported = 16; + bool in_game_player_notifications_supported = 17; + repeated GameTeamConfigurationData team_configurations = 101; + int32 team_selection_method = 102; + bool team_change_during_game_supported = 103; + bool explicit_team_selection_supported = 104; + bool spectating_supported = 105; + GameTeamPlayerVisualConfigurationData team_player_visual_configuration_data = 106; + bool team_radios_supported = 107; + GameTeamRadioConfigurationData team_radio_configuration_data = 108; + bool respawn_on_team_change_during_game_supported = 109; + bool show_open_slots_on_scoreboard = 110; + bool clear_teams_after_game = 111; + repeated GameStatConfigurationData stat_configurations = 202; + bool persist_stats_on_game_start = 203; + int32 max_health = 301; + int32 max_shield = 302; + bool auto_heal_supported = 303; + GameCombatAutoHealConfigurationData auto_heal_configuration_data = 304; + bool dbno_supported = 305; + GameCombatDownButNotOutConfigurationData dbno_configuration_data = 306; + bool auto_respawn_supported = 307; + GameCombatRespawnConfigurationData respawn_configuration_data = 308; + int32 revive_mode = 309; + GameCombatReviveConfigurationData revive_configuration_data = 310; + GameCombatFriendlyFireConfigurationData friendly_fire_configuration_data = 311; + GameCombatPlayerUIConfigurationData combat_player_ui_configuration_data = 312; + GameCombatEnemyUIConfigurationData combat_enemy_ui_configuration_data = 313; + bool monochrome_dead_effect_supported = 314; + bool monochrome_dbno_effect_supported = 315; + bool damage_red_flash_effect_supported = 316; + bool dead_persistent_notification_supported = 317; + bool deprecated_dbno_persistent_notification_supported = 318; + bool deprecated_revive_cooldown_persistent_notification_supported = 319; + bool deprecated_respawn_timer_persistent_notification_supported = 320; + bool combat_hud_supported = 322; + bool suppress_weapon_damage = 323; + bool default_hit_feedback_supported = 350; + bool default_revive_feedback_supported = 351; + bool default_downed_opponent_feedback_supported = 352; + bool default_downed_local_player_feedback_supported = 353; + bool default_downed_teammate_feedback_supported = 354; + int32 spawn_point_selection_mode = 401; + bytes main_hand_spawn_equipment_type = 501; + bytes off_hand_spawn_equipment_type = 502; + bool reset_tools_on_game_start_supported = 503; + int32 broken_tool_behavior = 504; + bool infinite_reserve_ammmunition_supported = 505; + bool tool_beacons_supported = 507; + bool movement_buffer_distance_restricted = 601; + bool out_of_bounds_supported = 701; + float out_of_bounds_grace_period = 702; + float out_of_bounds_max_duration = 703; + bool out_of_bounds_spectators_supported = 704; + bool down_out_of_bounds_players = 705; +} + +message GameRoleNodeData { + int32 game_role = 1; +} + +message GameRuleWrapperData { + CharadesCoOpClassicGameRuleData charades_coop_classic_game_rule_data = 1; + CharadesTeamLightningGameRuleData charades_team_lightning_game_rule_data = 2; + DodgeballGameRuleData dodgeball_game_rule_data = 3; + LaserTagGameRuleData laser_tag_game_rule_data = 4; + PaddleballGameRuleData paddleball_game_rule_data = 5; + PaintballCaptureTheFlagGameRuleData paintball_capture_the_flag_game_rule_data = 6; + PaintballTeamBattleGameRuleData paintball_team_battle_game_rule_data = 7; + SoccerGameRuleData soccer_game_rule_data = 9; + StuntrunnerGameRuleData stuntrunner_game_rule_data = 10; +} + +message GameStartRequirementConfigurationData { + int32 min_player_count = 1; + int32 min_team_count = 2; + int32 min_team_size = 3; +} + +message GameStatConfigurationData { + string stat_name = 1; + bool visible_in_ui = 2; + int32 stat_format = 3; + bool visible_in_hud = 4; +} + +message GameTeamConfigurationData { + int32 team_size = 1; +} + +message GameTeamPlayerVisualConfigurationData { + bool team_outfits_supported = 1; + bool team_name_in_feedback_supported = 2; + bool team_color_in_feedback_supported = 3; + int32 team_color_mode = 4; + int32 teammate_beacon_mode = 5; + int32 opponent_beacon_mode = 6; +} + +message GameTeamRadioConfigurationData { + float volume = 1; + float enter_distance = 2; + float exit_distance = 3; +} + +message HealthPotionData { + float health_restore_amount = 1; + float health_restore_duration = 2; + float shield_restore_amount = 3; + float shield_restore_duration = 4; +} + +message HolotarDataBlob { + int32 creator_id = 1; + float duration = 2; + int32 pose_data = 3; + OutfitData outfit_data = 4; + AudioStreamData audio_data = 5; + int32 voip_filter = 6; + int32 version = 7; +} + +message HolotarPersistenceData { + string blob_name = 1; + bool billboard = 2; +} + +message ImageData { + string image_name = 1; +} + +message InventionPieceData { + bytes instance_id = 1; + int64 invention_id = 2; + int32 version_number = 3; + bool originally_from_other_invention = 4; +} + +message InvisibleCollisionData { + bool collide_with_players = 1; + bool collide_with_objects = 2; + repeated string filter_roles = 3; + bool restrict_roles = 4; +} + +message KeyFrameData { + float time = 1; + repeated ObjectKeyFrameData object_key_frames = 2; +} + +message LaserTagGameRuleData { + int32 score_to_win = 1; + float max_game_duration = 2; +} + +message LeaderboardData { + repeated int32 stat_configs = 1; + string stat_full_name = 2; + string stat_short_name = 3; + bool stat_sort_ascending = 4; + int32 formatting_mode = 5; +} + +message MeshShapeData { + Vector3Data legacy_scale = 1; + Vector3Data aspect_ratio = 2; +} + +message MessageData { + string message = 1; + repeated string messages = 2; +} + +message MoodCircuitData { + int32 priority = 1; + float blend_in_time = 2; + bytes sky_box_data = 3; + bytes sun_data = 4; + bytes bloom_data = 5; + bytes color_grading_data = 6; + bytes fog_data = 7; + bytes background_objects_data = 8; + string role_guid = 9; + float blend_out_time = 10; + int32 ease_in_out = 11; +} + +message ObjectGroupData { + repeated bytes group_member_ids = 1; + bytes circuit_group_data = 2; + bytes animation_data = 3; +} + +message ObjectGroupDataCollection { + repeated int32 object_groups = 1; + ObjectGroupData object_group = 2; + bool unchanged = 3; +} + +message ObjectKeyFrameData { + Vector3Data position = 1; + Vector3Data rotation = 2; + int32 flags = 3; +} + +message ObjectSpawnPointData { + string item_tag = 1; + bool take_from_holder = 2; + bool clear_velocity_on_respawn = 3; + bool use_respawner_rotation = 4; +} + +message OutfitData { + string outfit_selections = 1; + string hair_color = 2; + string skin_color = 3; + string face_settings = 4; +} + +message OverridableBoolData { + bool overrides = 1; + bool inner_value = 2; +} + +message OverridableColorData { + bool overrides = 1; + ColorData inner_value = 2; +} + +message OverridableFloatData { + bool overrides = 1; + float inner_value = 2; +} + +message OverridableIntData { + bool overrides = 1; + int32 inner_value = 2; +} + +message OverridableListBoolData { + bool overrides = 1; + repeated bool inner_value = 2; +} + +message OverridableListColorData { + bool overrides = 1; + repeated ColorData inner_value = 2; +} + +message OverridableListFloatData { + bool overrides = 1; + repeated float inner_value = 2; +} + +message OverridableListIntData { + bool overrides = 1; + repeated int32 inner_value = 2; +} + +message OverridableListStringData { + bool overrides = 1; + repeated string inner_value = 2; +} + +message OverridableListVector3Data { + bool overrides = 1; + repeated Vector3Data inner_value = 2; +} + +message OverridableStringData { + bool overrides = 1; + string inner_value = 2; +} + +message OverridableVector3Data { + bool overrides = 1; + Vector3Data inner_value = 2; +} + +message PaddleballGameRuleData { + int32 score_to_win = 1; + float max_game_duration = 2; +} + +message PaintballCaptureTheFlagGameRuleData { + int32 scores_to_win = 1; + float max_game_duration = 2; + bool music_enabled = 3; +} + +message PaintballTeamBattleGameRuleData { + int32 score_to_win = 1; + float max_game_duration = 2; + bool music_enabled = 3; +} + +message ParticleEmitterData { + bytes effect_id = 1; + bool looping = 2; + bool use_sandbox_recolor = 3; +} + +message PersistedRoomData { + int32 version = 1; + repeated PersistenceViewData persistence_views = 2; + google.protobuf.Timestamp last_save_time = 3; + string activity_id = 4; + string output_log = 5; + int64 sub_room_id = 6; + Deprecated_HostSettingsData deprecated_host_settings = 16; + Deprecated_SandboxSettingsData deprecated_sandbox_settings = 17; + bytes circuit_data = 18; + Deprecated_GameSettingsData deprecated_game_settings = 19; + ToolTagSettingsData tool_tag_settings_data = 20; + ConnectableGraphData connectable_graph_data = 21; + int32 game_role_data = 22; + int32 room_role_data = 23; + SceneSettingsData scene_settings_data = 24; + AutosaveData autosave_data = 25; +} + +message PersistedSFXData { + bytes track_id = 1; + bool loop = 2; + bool shuffle = 3; + bool paused = 4; + bool use_spatial_audio = 5; + bool interrupt_current_sound = 6; + int32 volume_modifier = 7; +} + +message PersistenceViewData { + bytes id = 1; + repeated ChildPersistenceViewData child_views = 3; + ToolEntityData tool_entity_data = 9; + TransformData transform = 10; + SpawnableToolData spawnable_tool_data = 11; + ShapeContainerData shape_container_data = 12; + Deprecated_ToolData deprecated_tool_data = 13; + CreationObjectData creation_object_data = 14; + TaggedToolData tagged_tool_data = 15; + StickyData sticky_data = 16; + DrawingSurfaceData drawing_surface_data = 17; + ImageData image_data = 18; + ToolHolderData tool_holder_data = 19; + RemoteControlData remote_control_data = 20; + RoomDoorData room_door_data = 21; + ToolCleanupData tool_cleanup_data = 22; + SpawnPointSpawnerData spawnpoint_spawner_data = 23; + PersistedSFXData persisted_sfx_data = 24; + CircuitNodeData circuit_node_data = 25; + MessageData message_data = 26; + HolotarPersistenceData holotar_persistence_data = 27; + SpawnPointData spawn_point_data = 28; + AudioSamplerPersistenceData audio_sampler_persistence_data = 29; + ButtonData button_data = 30; + TriggerVolumeData trigger_volume_data = 31; + SandboxGizmoData sandbox_gizmo_data = 32; + ObjectSpawnPointData object_spawn_point_data = 33; + SetTagsNodeData set_tags_node_data = 34; + BreakableObjectData breakable_object_data = 35; + SandboxColorableData sandbox_colorable_data = 36; + ParticleEmitterData particle_emitter_data = 37; + ObjectGroupData object_group_data = 38; + StateMachineStateData state_machine_state_data = 39; + GameConfigurationData game_configuration_data = 40; + GameRuleWrapperData game_rule_wrapper_data = 41; + HealthPotionData health_potion_data = 42; + ScoreboardSetupData scoreboard_setup_data = 43; + SpotlightData spotlight_data = 44; + DynamicLightData dynamic_light_data = 45; + BouncePadData bounce_pad_data = 46; + CheckpointData checkpoint_data = 47; + LeaderboardData leaderboard_data = 48; + SetLeaderboardStatData set_leaderboard_stat_data = 49; + GameRoleNodeData game_role_node_data = 50; + PlayerAttractorData player_attractor_data = 51; + ToggleButtonData toggle_button_data = 52; + SandboxDeformationData sandbox_deformation_data = 53; + TextLabelData text_label_data = 54; + ProximityMineData proximity_mine_data = 55; + InvisibleCollisionData invisible_collision_data = 56; + MoodCircuitData mood_circuit_data = 57; +} + +message PhysicsThrusterData { + int32 thrust_mode = 1; +} + +message PistonData { + float max_travel_distance = 1; +} + +message PlayerAttractorData { + bool enabled = 1; +} + +message ProximityMineData { + bool triggered_by_teammates = 1; +} + +message RangedWeaponData { + int32 ammo_magazine_capacity = 1; + int32 ammo_total_capacity = 2; + bool reset_on_total_ammo_exhaustion = 3; + float projectile_damage = 4; + float rate_of_fire = 5; + float automatic_rate_of_fire = 6; + float max_projectile_speed = 7; + float critical_hit_damage_multiplier = 8; + float explosive_damage_radius = 9; + float explosive_damage_delay = 10; +} + +message RemoteControlData { + bytes controlled_object_view_id = 2; +} + +message RoomDoorData { + bytes room_id_deprecated = 1; + bytes target_name = 2; + int32 target_type = 3; + int32 invite_mode = 4; +} + +message SandboxColorableData { + int32 color = 1; + int32 material = 2; +} + +message SandboxDeformationData { + Vector3Data deformation = 1; +} + +message SandboxGizmoData { + AcceleratedMovementData accelerated_movement_data = 1; + bytes thruster_data = 3; + bytes look_at_data = 5; + bytes piston_data = 6; +} + +message SceneSettingsData { + bytes mood_id = 1; + bool movement_blockers_disabled = 2; + bool enforce_host_only_areas = 3; + bool show_gadgets_to_everyone = 4; +} + +message ScoreboardSetupData { + string score_name = 1; +} + +message SetLeaderboardStatData { + int32 set_mode = 1; +} + +message SetTagsNodeData { + TagData set_tag = 1; +} + +message ShapeContainerData { + ShapeDataCollection shape_collection = 1; + int32 physics_mode = 2; +} + +message ShapeContainerDataCollection { + repeated int32 shape_containers = 1; + ShapeDataCollection shapes = 2; + bool unchanged = 4; +} + +message ShapeData { + int32 shape_type = 1; + int32 shape_color = 2; + Vector3Data deprecated_initial_local_position = 5; + Vector3Data deprecated_initial_local_rotation = 6; + Vector3Data current_local_position = 7; + Vector3Data current_local_rotation = 8; + Vector3Data deprecated_scale_non_uniform = 9; + bool use_uncompressed_rotation = 10; + float density = 11; + bytes id = 12; + float scale = 13; + bytes mesh_shape_data = 16; + bytes curve_shape_data = 17; + int32 shape_material = 18; +} + +message ShapeDataCollection { + repeated ShapeData shapes = 1; +} + +message SkyBoxCircuitData { + int32 sky_top_color = 1; + int32 sky_bottom_color = 2; + int32 ground_top_color = 3; + int32 ground_bottom_color = 4; + int32 horizon_color = 5; + float horizon_size = 6; + float horizon_strength = 7; + float ambient_light_intensity = 8; +} + +message SmoothLookAtData { + int32 smoothness = 1; + TagData tag_data = 2; + int32 tracking_mode = 3; + int32 target_type = 4; + string role_guid = 5; + int32 body_part = 6; +} + +message SoccerGameRuleData { + float max_game_duration = 1; +} + +message SpawnableTemplateData { + int32 room_data_version = 1; + repeated PersistenceViewData persistence_views = 2; + string output_log = 3; + bytes circuit_data = 4; + ConnectableGraphData connectable_graph_data = 5; + Vector3Data extents = 6; +} + +message SpawnableToolData { + bytes prefab_id = 1; +} + +message SpawnPointData { + int32 team_id = 1; + string primary_hand_hold_tag = 2; + bool primary_hand_take_from_holder = 3; + string secondary_hand_hold_tag = 4; + bool secondary_hand_take_from_holder = 5; + bool is_pre_post_game_spawn_point = 6; + float spawn_precision_radius = 7; + int32 spawn_point_purpose = 8; + bool keep_held_tools = 9; +} + +message SpawnPointSpawnerData { + int64 room_id = 1; +} + +message SpotlightData { + int32 angle = 1; +} + +message StateMachineStateData { + int32 state_idx = 1; + bytes state_machine_view_id = 2; + string name = 3; + int32 val1 = 4; + int32 val2 = 5; + int32 val3 = 6; + int32 min_time_in_state = 7; +} + +message StickyData { + bool is_attached = 1; + bytes connected_view_id = 2; + Vector3Data attached_relative_position = 3; + Vector3Data attached_relative_rotation = 4; +} + +message StuntrunnerGameRuleData { + bool music_enabled = 1; + bool announcer_enabled = 2; + bool ambience_enabled = 3; + bool spotlights_enabled = 4; + int32 max_course_time = 5; +} + +message SunCircuitData { + int32 sun_color = 1; + float disc_size = 2; + float intensity = 3; + float shadow_strength = 4; + int32 glow_color = 5; + float glow_size = 6; + float glow_strength = 7; + float glow_horizon = 8; + int32 blend_sun_direction = 9; + int32 clip_sun = 10; + float reflection_intensity = 11; +} + +message TagData { + repeated string tags = 1; +} + +message TaggedToolData { + TagData tag_data = 1; +} + +message TextLabelData { + int32 horizontal_alignment = 1; + int32 character_spacing = 2; + int32 line_spacing = 3; + int32 paragraph_spacing = 4; + float text_thickness = 5; +} + +message ToggleButtonData { + bool default_toggle_state = 1; + bool signal_is_player_id = 2; + bool projectile_collision_enabled = 3; +} + +message ToolCleanupData { + int32 overrides = 1; + bool is_allowed = 2; + float delay = 3; +} + +message ToolEntityData { + bool hidden_during_games = 32; +} + +message ToolHolderData { + bytes held_view_id = 2; +} + +message ToolTagSettingsData { + TagData runtime_tag_data = 1; +} + +message TransformData { + Vector3Data position = 1; + Vector3Data rotation = 2; + Vector3Data deprecated_scale_non_uniform = 3; + float scale = 5; +} + +message TriggerVolumeData { + int32 mode_id = 1; + Vector3Data deprecated_scale = 2; + Vector3Data deprecated_position = 3; + TagData filter_tags = 4; + string filter_role = 5; +} + +message Vector3Data { + float x = 1; + float y = 2; + float z = 3; +} + diff --git a/DorkNet.Server/Services/AuthService.cs b/DorkNet.Server/Services/AuthService.cs new file mode 100644 index 0000000..88232c9 --- /dev/null +++ b/DorkNet.Server/Services/AuthService.cs @@ -0,0 +1,103 @@ +using Microsoft.EntityFrameworkCore; +using Microsoft.IdentityModel.Tokens; +using System.IdentityModel.Tokens.Jwt; +using System.Security.Claims; +using System.Text; +using DorkNet.Server.Data; + +namespace DorkNet.Server.Services; + +public class AuthService(IConfiguration config, DorkNetDbContext db) +{ + // Resolution order MUST match Program.cs's JwtBearer validator setup — + // env var first, config key second — otherwise tokens are signed with + // one secret and validated against another and every authenticated + // request 401s. In production we set DORKNET_JWT_SECRET in Coolify + // (RECNET_JWT_SECRET kept as fallback for backward compat with older + // configs) and leave Jwt:Secret as the appsettings.json placeholder; + // the mismatched-secret bug shows up only there. + private string SecretKey => + Environment.GetEnvironmentVariable("DORKNET_JWT_SECRET") + ?? Environment.GetEnvironmentVariable("RECNET_JWT_SECRET") + ?? config["Jwt:Secret"] + ?? throw new InvalidOperationException( + "JWT secret not configured. Set DORKNET_JWT_SECRET env var or " + + "Jwt:Secret in appsettings.Local.json."); + + public string GenerateToken(long playerId) => CreateJwt(playerId, TimeSpan.FromHours(12), "access"); + + /// + /// Issues an (access, refresh) pair for the OAuth-style password-grant + /// response shape (Login.LoginResponse). Access tokens are short-lived; + /// the refresh token is what api/platformlogin/refresh accepts later. + /// + public (string AccessToken, string RefreshToken) GenerateTokenPair(long playerId) => + (CreateJwt(playerId, TimeSpan.FromHours(12), "access"), + CreateJwt(playerId, TimeSpan.FromDays(30), "refresh")); + + private string CreateJwt(long playerId, TimeSpan lifetime, string tokenType) + { + var handler = new JwtSecurityTokenHandler(); + var key = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(SecretKey)); + + // Look up dev/admin flags so the JWT carries a "developer" role + // claim for accounts that should see the in-game debug button. + // The watch's Login.SetAccessToken parses the access_token's + // role claim — either a single string or a list — and + // calls HasRole("developer", token); that result drives + // Accounts.SetLocalAccountIsDev and ultimately + // SessionManager.IsDeveloper, which the watch UI checks + // when deciding whether to show the dev console toggle. + var isDev = db.Players + .Where(p => p.Id == playerId) + .Select(p => (bool?)(p.IsDeveloper || p.IsAdmin)) + .FirstOrDefault() ?? false; + + var claims = new List + { + new(ClaimTypes.NameIdentifier, playerId.ToString()), + new(ClaimTypes.Role, "gameClient"), + new("token_type", tokenType), + }; + // Multiple Role claims become a JSON array on the wire — the + // watch's HasRole helper handles both string and List + // forms. + if (isDev) claims.Add(new Claim(ClaimTypes.Role, "developer")); + + var descriptor = new SecurityTokenDescriptor + { + Subject = new ClaimsIdentity(claims), + Expires = DateTime.UtcNow.Add(lifetime), + Issuer = "https://api.rec.net/", + SigningCredentials = new SigningCredentials(key, SecurityAlgorithms.HmacSha256), + }; + + return handler.WriteToken(handler.CreateJwtSecurityToken(descriptor)); + } + + public long? ValidateToken(string token) + { + try + { + var handler = new JwtSecurityTokenHandler(); + var key = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(SecretKey)); + + var principal = handler.ValidateToken(token, new TokenValidationParameters + { + ValidateIssuerSigningKey = true, + IssuerSigningKey = key, + ValidateIssuer = true, + ValidIssuer = "https://api.rec.net/", + ValidateAudience = false, + ClockSkew = TimeSpan.Zero, + }, out _); + + var idClaim = principal.FindFirst(ClaimTypes.NameIdentifier)?.Value; + return idClaim is not null ? long.Parse(idClaim) : null; + } + catch + { + return null; + } + } +} diff --git a/DorkNet.Server/Services/ClubService.cs b/DorkNet.Server/Services/ClubService.cs new file mode 100644 index 0000000..d73aa28 --- /dev/null +++ b/DorkNet.Server/Services/ClubService.cs @@ -0,0 +1,559 @@ +using Microsoft.EntityFrameworkCore; +using DorkNet.Server.Data; +using DorkNet.Server.Data.Entities; + +namespace DorkNet.Server.Services; + +/// +/// Backs the clubs.localhost/* surface used by the 2020.12 watch: +/// * GET /club/home/me — composite home tab payload +/// * GET /club/mine/member — clubs the player is in +/// * GET /club/mine/created — clubs the player created +/// * GET /club/categoryTags — admin-curated tag list +/// * GET /announcements/v2/mine/unread — direct feed +/// * GET /announcements/v2/subscription/mine/unread — subs feed +/// +/// Owns no in-memory state — every method round-trips the DbContext so +/// reads stay consistent across replicas. The legacy +/// GroupsController reads + writes the same +/// table, so club rows created via api/groups/v1/ show up here +/// automatically without backfill. +/// +public class ClubService(DorkNetDbContext db) +{ + /// Clubs the player is a member of (any permissions + /// value above 0 OR a Member=0 row — i.e. presence in the + /// table). The owner's + /// auto-membership row inserted by GroupsController.Create + /// (Permissions=127) also matches. + public Task> MyClubsAsync(long playerId) => + (from m in db.ClubMemberships + join c in db.Clubs on m.ClubId equals c.Id + where m.PlayerId == playerId && c.State == 0 + orderby c.Name + select c).ToListAsync(); + + /// Clubs whose + /// equals the player. The owner usually also has a membership row, + /// so + this overlap — the watch's + /// "Mine" tab renders the union and de-dupes by ClubId. + public Task> CreatedByAsync(long playerId) => + db.Clubs + .Where(c => c.CreatorPlayerId == playerId && c.State == 0) + .OrderBy(c => c.Name) + .ToListAsync(); + + /// + /// Unread announcements for clubs the player is a MEMBER of. Drives + /// announcements/v2/mine/unread. The "unread" filter is + /// LEFT JOIN against for + /// the calling player — rows with no read-marker count as unread. + /// + public async Task> UnreadDirectAsync(long playerId) + { + var memberClubIds = await db.ClubMemberships + .Where(m => m.PlayerId == playerId) + .Select(m => m.ClubId) + .ToListAsync(); + if (memberClubIds.Count == 0) return new(); + return await UnreadForClubsAsync(playerId, memberClubIds); + } + + /// + /// Unread announcements for clubs the player SUBSCRIBES to (rows + /// in ). Drives + /// announcements/v2/subscription/mine/unread. Same unread + /// semantics as the direct feed. + /// + public async Task> UnreadSubscriptionAsync(long playerId) + { + var subClubIds = await db.ClubSubscriptions + .Where(s => s.PlayerId == playerId) + .Select(s => s.ClubId) + .ToListAsync(); + if (subClubIds.Count == 0) return new(); + return await UnreadForClubsAsync(playerId, subClubIds); + } + + private async Task> UnreadForClubsAsync( + long playerId, List clubIds) + { + // Pull all candidate announcements + the player's read-markers + // for them in one pass each. EF Core's LEFT-JOIN over a + // subquery generates pathological SQL on SQLite, so the + // anti-join is done in memory after two index-supported reads. + var candidates = await db.ClubAnnouncements + .Where(a => clubIds.Contains(a.ClubId)) + .OrderByDescending(a => a.CreatedAt) + .ToListAsync(); + if (candidates.Count == 0) return candidates; + var candidateIds = candidates.Select(a => a.Id).ToList(); + var readIds = await db.ClubAnnouncementReads + .Where(r => r.PlayerId == playerId && candidateIds.Contains(r.AnnouncementId)) + .Select(r => r.AnnouncementId) + .ToListAsync(); + var readSet = new HashSet(readIds); + return candidates.Where(a => !readSet.Contains(a.Id)).ToList(); + } + + /// + /// Active category tags in display order. The 2020.12 watch + /// deserialises the response as List<String>, so the + /// controller projects + /// out — the Id/OrderIndex/Active columns are admin-facing only. + /// + public Task> CategoryTagsAsync() => + db.ClubCategoryTags + .Where(t => t.Active) + .OrderBy(t => t.OrderIndex) + .ThenBy(t => t.Name) + .ToListAsync(); + + /// + /// Discover-tab search for GET /club/search. The 2020.12 + /// watch can send either a category-only browse request or a text + /// search; the rows are projected by the controller into the + /// CAJHIEENJJD search envelope. + /// + public async Task<(List Clubs, int TotalClubs)> SearchAsync( + string? query, string? category, int sort, int count) + { + var take = Math.Clamp(count <= 0 ? 30 : count, 1, 100); + var q = db.Clubs.Where(c => c.State == 0); + + if (!string.IsNullOrWhiteSpace(category)) + { + var categoryLower = category.Trim().ToLower(); + q = q.Where(c => c.Category.ToLower() == categoryLower); + } + + if (!string.IsNullOrWhiteSpace(query)) + { + var queryLower = query.Trim().ToLower(); + q = q.Where(c => + c.Name.ToLower().Contains(queryLower) || + c.Description.ToLower().Contains(queryLower)); + } + + var total = await q.CountAsync(); + + q = sort switch + { + 1 => q.OrderByDescending(c => c.CreatedAt).ThenBy(c => c.Name), + 2 => q.OrderBy(c => c.Name), + _ => q.OrderByDescending(c => c.ClubType) + .ThenByDescending(c => c.UpdatedAt) + .ThenBy(c => c.Name), + }; + + var clubs = await q.Take(take).ToListAsync(); + return (clubs, total); + } + + /// + /// Clubs the player is subscribed to, in the lowercase-key wire + /// shape LNIKPLKOBDK expects per + /// dist/.../IsilDump/Assembly-CSharp/LNIKPLKOBDK.txt:110-124: + /// {accountId, clubId, subscriberCount}. One row per + /// (player, club) subscription row. subscriberCount is the + /// total subscribers for that club across all players — derived + /// live from the same table so it stays in sync without a cached + /// counter that could drift. + /// + public async Task> MySubscriptionsAsync(long playerId) + { + var mine = await db.ClubSubscriptions + .Where(s => s.PlayerId == playerId) + .Select(s => s.ClubId) + .ToListAsync(); + if (mine.Count == 0) return new List(); + + var counts = await db.ClubSubscriptions + .Where(s => mine.Contains(s.ClubId)) + .GroupBy(s => s.ClubId) + .Select(g => new { ClubId = g.Key, Count = g.Count() }) + .ToListAsync(); + var byClub = counts.ToDictionary(c => c.ClubId, c => c.Count); + + return mine + .Select(clubId => (object)new + { + accountId = playerId, + clubId = clubId, + subscriberCount = byClub.TryGetValue(clubId, out var c) ? c : 0, + }) + .ToList(); + } + + /// + /// Composite home-tab payload for GET /club/home/me. The + /// watch's deserialiser for this endpoint expects a single + /// RecNet.Club object (typeof(System.Action`1<PLILLKHMNDA>) + /// per ISIL) — typically the player's "home club" (set via + /// SetMyHomeClub) rendered as the headline card. We pick the + /// first club the player is a member of, ordered by membership + /// recency. Returns null when the player has no club memberships + /// — the controller turns that into + /// so the watch knows to render the "join a club" empty state. + /// + public async Task HomeClubAsync(long playerId) + { + // Most-recent-joined club wins. Owners are members too (the + // /create flow inserts an owner-permissions=127 membership + // row), so a brand-new club creator sees their fresh club here. + var pick = await (from m in db.ClubMemberships + join c in db.Clubs on m.ClubId equals c.Id + where m.PlayerId == playerId && c.State == 0 + orderby m.JoinedAt descending + select c).FirstOrDefaultAsync(); + return pick; + } + + /// + /// Upserts a read-marker so subsequent + /// / calls skip the + /// announcement. Idempotent — calling twice is a no-op (the unique + /// index on (AnnouncementId, PlayerId) guarantees one row). + /// + public async Task MarkAnnouncementReadAsync(long playerId, long announcementId) + { + var existing = await db.ClubAnnouncementReads + .FirstOrDefaultAsync(r => r.PlayerId == playerId + && r.AnnouncementId == announcementId); + if (existing is not null) return; + db.ClubAnnouncementReads.Add(new ClubAnnouncementReadEntity + { + AnnouncementId = announcementId, + PlayerId = playerId, + ReadAt = DateTime.UtcNow, + }); + await db.SaveChangesAsync(); + } + + /// + /// Live count of approved members for a club (used by the wire + /// MemberCount int — see doc). + /// + public Task MemberCountAsync(long clubId) => + db.ClubMemberships.CountAsync(m => m.ClubId == clubId); + + /// Subscribers TO a club (everyone who hit subscribe on + /// the club page). Backs /subscription/subscriberCount/{id} + /// when the id resolves to a club. + public Task ClubSubscriberCountAsync(long clubId) => + db.ClubSubscriptions.CountAsync(s => s.ClubId == clubId); + + /// Subscribers TO a player (everyone who hit subscribe on + /// the player's profile — the pre-club subscription model in + /// ). Backs + /// /subscription/subscriberCount/{id} when the id resolves + /// to a player account. + public Task PlayerSubscriberCountAsync(long playerId) => + db.Subscriptions.CountAsync(s => s.TargetPlayerId == playerId); + + /// + /// Bulk member-count lookup so the controller can project a list + /// of clubs in one round-trip. Returns a dict keyed by ClubId; any + /// club without members maps to 0 in the caller's projection. + /// + public async Task> MemberCountsAsync(IEnumerable clubIds) + { + var ids = clubIds.Distinct().ToList(); + if (ids.Count == 0) return new(); + var rows = await db.ClubMemberships + .Where(m => ids.Contains(m.ClubId)) + .GroupBy(m => m.ClubId) + .Select(g => new { ClubId = g.Key, Count = g.Count() }) + .ToListAsync(); + return rows.ToDictionary(r => r.ClubId, r => r.Count); + } + + /// Single club lookup by id. Returns null for missing + /// or disbanded (State != 0) clubs so the controller surfaces 404 + /// before any wire-shape projection runs. + public Task GetByIdAsync(long clubId) => + db.Clubs.FirstOrDefaultAsync(c => c.Id == clubId && c.State == 0); + + /// Single club lookup by exact display name. Used by + /// GET /club?name=...; case-insensitive matching keeps the + /// browse/name-resolve path stable across SQLite and Postgres + /// collations. + public Task GetByNameAsync(string name) + { + var trimmed = name.Trim().ToLower(); + return db.Clubs.FirstOrDefaultAsync(c => c.State == 0 && c.Name.ToLower() == trimmed); + } + + /// Single membership row lookup. Used by both the + /// /club/{id}/members/{playerId} read and every member-mutation + /// endpoint to verify the target row exists before acting. + public Task MembershipForAsync(long clubId, long playerId) => + db.ClubMemberships.FirstOrDefaultAsync(m => m.ClubId == clubId && m.PlayerId == playerId); + + /// Bulk member-row lookup for members/bulk. Returns + /// only rows that exist; missing playerIds are silently dropped (the + /// watch handles a shorter list fine — it just renders no row). + public Task> MembershipsBulkAsync(long clubId, IEnumerable playerIds) + { + var ids = playerIds.Distinct().ToList(); + if (ids.Count == 0) return Task.FromResult(new List()); + return db.ClubMemberships + .Where(m => m.ClubId == clubId && ids.Contains(m.PlayerId)) + .ToListAsync(); + } + + /// List clubs an arbitrary player is a MEMBER of. Drives + /// account/{playerId}/clubs on a profile card; uses the same + /// State=0 filter as the my-clubs read so disbanded clubs stay + /// hidden. + public Task> ClubsForPlayerAsync(long playerId) => + (from m in db.ClubMemberships + join c in db.Clubs on m.ClubId equals c.Id + where m.PlayerId == playerId && c.State == 0 + orderby c.Name + select c).ToListAsync(); + + /// + /// 2020.12 PPGPAHNMGEC MembershipType wire enum verified + /// against dist\RecRoom-2020.12.18-dump\DiffableCs\Assembly-CSharp\PPGPAHNMGEC.cs. + /// These ARE the values the watch decodes — earlier 0..N guesses + /// were wrong: MyMembershipType=4 would have rendered as + /// "Pending_Denied" instead of "Creator" and disabled every owner + /// affordance in the watch UI. + /// + public const int MembershipTypeBanned = -1; + public const int MembershipTypeNotMember = 0; + public const int MembershipTypeRequested = 1; // Pending_Requested + public const int MembershipTypeInvited = 2; // Pending_Invited + public const int MembershipTypeDenied = 3; // Pending_Denied + public const int MembershipTypeMember = 10; + public const int MembershipTypeModerator = 20; + public const int MembershipTypeCoOwner = 30; + public const int MembershipTypeOwner = 100; // Creator on the wire + + public static int MembershipTypeFromPerms(int perms) + { + // 256 = ban marker (set by /members/ban handler). + if ((perms & 256) != 0) return MembershipTypeBanned; + // 128 = pending (set by /members/invite + /requesttojoin handlers). + // We don't yet distinguish invite-from vs request-to on the perms + // int; surface Requested by default so the caller's "I asked to + // join" UI lights up. (Outgoing invites from owners use the same + // marker; the watch shows them under "pending invites" either + // way because the deserializer only branches on + // Member/Moderator/CoOwner/Creator vs Pending_*.) + if ((perms & 128) != 0) return MembershipTypeRequested; + if (perms >= 127) return MembershipTypeOwner; + if (perms >= 124) return MembershipTypeCoOwner; + if (perms >= 24) return MembershipTypeModerator; + return MembershipTypeMember; + } + + /// Inverse of for + /// changetype. Returns the canonical perms int to assign + /// when the caller sets a new role. Owner (Creator=100 on the wire) + /// is intentionally excluded — promotion to Owner is a + /// transfer-ownership flow, not a changetype call. + public static int PermsFromMembershipType(int membershipType) => membershipType switch + { + MembershipTypeCoOwner => 124, + MembershipTypeModerator => 24, + MembershipTypeMember => 0, + _ => 0, + }; + + /// + /// Persist a club-targeted moderation report. Stores into the same + /// table the admin queue reads from, with + /// carrying the clubId (we + /// don't have a separate TargetClubId column yet — the admin SPA + /// renders any non-zero target field on the report card, so this + /// surfaces as a club report there too via the Message prefix). + /// + public async Task AddClubReportAsync(long clubId, long reporterId, int category, string message) + { + db.Reports.Add(new ReportEntity + { + ReporterPlayerId = reporterId, + TargetPlayerId = 0, + TargetRoomId = clubId, + Category = category, + Message = $"[club {clubId}] {message ?? string.Empty}", + }); + await db.SaveChangesAsync(); + } + + /// + /// Create a new club + the owner's membership row in one go. Per + /// the 2020.12 wire club/create response shape, the caller + /// projects the resulting into the + /// PIHMJGCGNLP envelope via the controller's wire mappers. + /// Returns null when the name collides with an existing club so + /// the caller can surface a 409. + /// + public async Task CreateAsync(long creatorId, string name, string? description, string? imageName, string? category) + { + var trimmed = (name ?? string.Empty).Trim(); + if (trimmed.Length == 0) return null; + if (await db.Clubs.AnyAsync(c => c.Name == trimmed)) return null; + var club = new ClubEntity + { + Name = trimmed, + Description = description ?? string.Empty, + ImageName = imageName ?? string.Empty, + Category = category ?? string.Empty, + CreatorPlayerId = creatorId, + }; + db.Clubs.Add(club); + await db.SaveChangesAsync(); + db.ClubMemberships.Add(new ClubMembershipEntity + { + ClubId = club.Id, + PlayerId = creatorId, + Permissions = 127, // Owner + }); + await db.SaveChangesAsync(); + return club; + } + + /// Apply a free-form mutation to a club row after + /// verifying the caller is owner/co-owner. Returns the updated + /// entity on success, null on missing club, or throws + /// when the caller can't + /// modify. Used by every club/{id}/modify* endpoint variant. + public async Task ModifyAsync(long clubId, long callerId, Action mutator) + { + var club = await db.Clubs.FirstOrDefaultAsync(c => c.Id == clubId && c.State == 0); + if (club is null) return null; + if (!await CanManageAsync(clubId, callerId, club)) + throw new UnauthorizedAccessException(); + mutator(club); + club.UpdatedAt = DateTime.UtcNow; + await db.SaveChangesAsync(); + return club; + } + + /// + /// Check whether the caller can perform admin-class mutations on a + /// club (owner, co-owner, or moderator depending on the action). + /// Owner always wins; co-owner and moderator are looked up via the + /// membership perms int. Used by every club/{id}/modify* and + /// member mutation route. + /// + public async Task CanManageAsync(long clubId, long callerId, ClubEntity? club = null) + { + club ??= await db.Clubs.AsNoTracking().FirstOrDefaultAsync(c => c.Id == clubId); + if (club is null) return false; + if (club.CreatorPlayerId == callerId) return true; + var membership = await db.ClubMemberships.AsNoTracking() + .FirstOrDefaultAsync(m => m.ClubId == clubId && m.PlayerId == callerId); + if (membership is null) return false; + // Pending invites/requests (128) and ban markers (256) must NOT + // grant moderation rights. Without this mask a pending invitee + // with perms=128 evaluates `>= 24` true and inherits the ability + // to ban/promote — full moderation hijack via "I'd like to join". + var perms = membership.Permissions; + if ((perms & 128) != 0 || (perms & 256) != 0) return false; + return perms >= 24; + } + + /// Upsert a membership row at a target role. Used by + /// invite/request/accept/changetype/directJoin/etc. Returns the + /// row for callers that need to inspect or surface it; for void + /// endpoints the caller just discards the result. + public async Task UpsertMembershipAsync(long clubId, long targetPid, int perms) + { + var row = await db.ClubMemberships + .FirstOrDefaultAsync(m => m.ClubId == clubId && m.PlayerId == targetPid); + if (row is null) + { + row = new ClubMembershipEntity + { + ClubId = clubId, + PlayerId = targetPid, + Permissions = perms, + }; + db.ClubMemberships.Add(row); + } + else + { + row.Permissions = perms; + } + await db.SaveChangesAsync(); + return row; + } + + /// Remove a membership row entirely — used by leave, + /// remove, ban (after stamping a separate ban row if/when we + /// introduce one), declineinvite, denyrequest. Idempotent: missing + /// row → no-op. + public async Task RemoveMembershipAsync(long clubId, long targetPid) + { + var row = await db.ClubMemberships + .FirstOrDefaultAsync(m => m.ClubId == clubId && m.PlayerId == targetPid); + if (row is null) return; + db.ClubMemberships.Remove(row); + await db.SaveChangesAsync(); + } + + /// List the announcements for a single club in newest-first + /// order. Drives announcements/club/{cid} when the watch + /// renders the club's full announcement feed (not just the unread + /// rollup the home tab uses). + public Task> AnnouncementsForClubAsync(long clubId, int take = 50) => + db.ClubAnnouncements + .Where(a => a.ClubId == clubId) + .OrderByDescending(a => a.CreatedAt) + .Take(take) + .ToListAsync(); + + /// Single announcement lookup by (clubId, announcementId). + /// Returns null when either id is unknown so the controller can + /// 404 cleanly. + public Task AnnouncementAsync(long clubId, long announcementId) => + db.ClubAnnouncements.FirstOrDefaultAsync(a => a.Id == announcementId && a.ClubId == clubId); + + /// Delete an announcement after verifying the caller can + /// manage the club. Idempotent: missing row → no-op. + public async Task DeleteAnnouncementAsync(long clubId, long announcementId, long callerId) + { + if (!await CanManageAsync(clubId, callerId)) return false; + var row = await db.ClubAnnouncements + .FirstOrDefaultAsync(a => a.Id == announcementId && a.ClubId == clubId); + if (row is null) return true; // idempotent — caller treats as success + db.ClubAnnouncements.Remove(row); + await db.SaveChangesAsync(); + return true; + } + + /// + /// Idempotent seed of the default category tags. Inserts the + /// canonical Sports / Creative / Roleplay / Hangout / Education + /// tags on first launch; re-running on a populated table bails + /// without touching existing rows so admin edits are never + /// clobbered. + /// + public async Task SeedDefaultsAsync() + { + if (await db.ClubCategoryTags.AnyAsync()) return; + + var defaults = new (string Name, int Order)[] + { + ("Sports", 0), + ("Creative", 1), + ("Roleplay", 2), + ("Hangout", 3), + ("Education", 4), + }; + foreach (var (name, order) in defaults) + { + db.ClubCategoryTags.Add(new ClubCategoryTagEntity + { + Name = name, + OrderIndex = order, + Active = true, + }); + } + await db.SaveChangesAsync(); + } +} diff --git a/DorkNet.Server/Services/CommunityBoardService.cs b/DorkNet.Server/Services/CommunityBoardService.cs new file mode 100644 index 0000000..5a6c6e0 --- /dev/null +++ b/DorkNet.Server/Services/CommunityBoardService.cs @@ -0,0 +1,150 @@ +using System.Text.Json; +using System.Text.Json.Serialization; +using DorkNet.Server.Data; +using DorkNet.Server.Data.Entities; +using Microsoft.EntityFrameworkCore; + +namespace DorkNet.Server.Services; + +/// +/// Single-row Postgres-backed store for the dorm community board state. +/// +/// Wire shape mirrors what the watch's +/// CommunityBoard.CommunityBoardDTO.Deserialize reads +/// (Cpp2IL_ISIL/.../CommunityBoard_NestedType_CommunityBoardDTO.txt:460-480): +/// FeaturedPlayer / FeaturedRoomGroup / +/// CurrentAnnouncement / InstagramImages / Videos. +/// +/// Pre-PR-3 wrote the JSON to data/community_board.json on disk + +/// kept an in-memory cache. With horizontal scale, an admin edit on +/// replica A wouldn't invalidate replica B's cache — admins would see +/// stale state until the next restart. The single-row table sidesteps +/// the cache-invalidation problem entirely: every read is one cheap +/// SELECT, every write is one UPDATE/INSERT, both visible to all +/// replicas immediately. +/// +/// Service is now scoped rather than singleton because it holds +/// a DbContext reference. +/// +public class CommunityBoardService(DorkNetDbContext db) +{ + /// Fixed PK for the single board row. We pin to id=1 rather + /// than using a singleton-table pattern because EF Core needs a + /// trackable key. + private const int RowId = 1; + + private static readonly JsonSerializerOptions JsonOpts = new() + { + PropertyNameCaseInsensitive = true, + WriteIndented = false, + DefaultIgnoreCondition = JsonIgnoreCondition.Never, + }; + + public async Task GetAsync() + { + var row = await db.CommunityBoardRows.AsNoTracking() + .FirstOrDefaultAsync(c => c.Id == RowId); + if (row is null) return DefaultState(); + try + { + return JsonSerializer.Deserialize(row.Json, JsonOpts) + ?? DefaultState(); + } + catch (JsonException) + { + // Corrupt row — return default rather than 500. Next admin + // edit will overwrite the bad payload. + return DefaultState(); + } + } + + public async Task UpdateAsync(CommunityBoardState next) + { + var json = JsonSerializer.Serialize(next, JsonOpts); + var existing = await db.CommunityBoardRows.FirstOrDefaultAsync(c => c.Id == RowId); + if (existing is null) + { + db.CommunityBoardRows.Add(new CommunityBoardEntity + { + Id = RowId, + Json = json, + UpdatedAt = DateTime.UtcNow, + }); + } + else + { + existing.Json = json; + existing.UpdatedAt = DateTime.UtcNow; + } + await db.SaveChangesAsync(); + return next; + } + + private static CommunityBoardState DefaultState() => new() + { + CurrentAnnouncement = new AnnouncementState + { + Message = "Welcome to the private server.", + MoreInfoUrl = string.Empty, + }, + }; +} + +/// Storage shape for the community board. Field names match +/// the watch's CommunityBoardDTO keys (verified in +/// CommunityBoard_NestedType_CommunityBoardDTO.txt) so the same record +/// can be returned verbatim from GET /api/communityboard/v1/current. +public class CommunityBoardState +{ + public FeaturedPlayerState? FeaturedPlayer { get; set; } + public FeaturedRoomGroupState? FeaturedRoomGroup { get; set; } + public AnnouncementState? CurrentAnnouncement { get; set; } + public List InstagramImages { get; set; } = new(); + public List Videos { get; set; } = new(); +} + +/// CommunityBoardFeaturedPlayerData: Id / TitleOverride / UrlOverride +/// (CommunityBoard_NestedType_CommunityBoardFeaturedPlayerData.txt:410-420). +public class FeaturedPlayerState +{ + public int Id { get; set; } + public string TitleOverride { get; set; } = string.Empty; + public string UrlOverride { get; set; } = string.Empty; +} + +/// FeaturedRoomGroupDTO: Name / FeaturedRooms list +/// (Rooms_NestedType_FeaturedRoomGroupDTO.txt:85-90). FeaturedRooms is +/// a list of room ids — the watch fetches each via /api/rooms/v2/{id}. +public class FeaturedRoomGroupState +{ + public string Name { get; set; } = string.Empty; + public List FeaturedRooms { get; set; } = new(); +} + +/// CommunityBoardAnnouncementDTO: Message / MoreInfoUrl +/// (CommunityBoard_NestedType_CommunityBoardAnnouncementDTO.txt:85-90). +public class AnnouncementState +{ + public string Message { get; set; } = string.Empty; + public string MoreInfoUrl { get; set; } = string.Empty; +} + +/// InstagramImageDTO: ImageName / ImageUrl +/// (CommunityBoard_NestedType_InstagramImageDTO.txt:85-90). +public class InstagramImageState +{ + public string ImageName { get; set; } = string.Empty; + public string ImageUrl { get; set; } = string.Empty; +} + +/// CommunityBoardVideoData: BlobName / Title / Description / +/// ThumbnailBlobName / SourceUrl +/// (CommunityBoard_NestedType_CommunityBoardVideoData.txt:460-480). +public class VideoState +{ + public string BlobName { get; set; } = string.Empty; + public string Title { get; set; } = string.Empty; + public string Description { get; set; } = string.Empty; + public string ThumbnailBlobName { get; set; } = string.Empty; + public string SourceUrl { get; set; } = string.Empty; +} diff --git a/DorkNet.Server/Services/ConfigService.cs b/DorkNet.Server/Services/ConfigService.cs new file mode 100644 index 0000000..47546bb --- /dev/null +++ b/DorkNet.Server/Services/ConfigService.cs @@ -0,0 +1,109 @@ +using DorkNet.Models.Config; + +namespace DorkNet.Server.Services; + +public class ConfigService(IConfiguration config, DomainConfig domain) +{ + public RecRoomConfig GetConfig(string baseUrl) + { + var photon = new PhotonConfig + { + AppId = config["Photon:AppId"] ?? string.Empty, + VoiceAppId = config["Photon:VoiceAppId"] ?? string.Empty, + CloudRegion = config["Photon:CloudRegion"] ?? "us", + }; + + // baseUrl was previously parsed to mirror the request's apex + // back at the client, but with DomainConfig as the single + // source of truth every outbound URL is built off the + // configured deployment apex regardless of which Host header + // the client used to hit us. The baseUrl parameter is kept + // for API stability but is no longer dereferenced. + _ = baseUrl; + var apex = domain.Apex; + + return new RecRoomConfig + { + MessageOfTheDay = config["Server:MOTD"] ?? "Welcome to the private server!", + CdnBaseUri = $"https://{domain.Sub("cdn")}", + PhotonConfig = photon, + ServiceUrls = BuildServiceUrlMap(apex), + ConfigTable = new Dictionary + { + // Season keys the 2019/2020 client checks at startup + ["Season"] = "Spring", + ["CurrentSeason"] = "Spring", + ["SeasonId"] = "1", + ["SeasonName"] = "Spring", + ["ActiveEvent"] = "None", + ["EventName"] = "", + // Feature flags — keep everything on + ["FriendsEnabled"] = "true", + ["ChatEnabled"] = "true", + ["VoiceEnabled"] = "true", + }, + }; + } + + /// + /// Subdomain → service-name map. The wire response wraps each + /// value with https:// + the configured apex domain + /// (, driven by DORKNET_DOMAIN) so + /// every URL the client sees comes from one source of truth. + /// + private static readonly (string Service, string Subdomain)[] ServiceSubdomains = new[] + { + ("WWW", ""), // apex + ("API", "api"), + ("Accounts", "accounts"), + ("Auth", "auth"), + ("BugReporting", "bugreporting"), + ("Cards", "cards"), + ("CDN", "cdn"), + ("Chat", "chat"), + ("Clubs", "clubs"), + ("CMS", "cms"), + ("Commerce", "commerce"), + ("Data", "data"), + ("DataCollection", "datacollection"), + ("Discovery", "discovery"), + ("Econ", "econ"), + ("GameLogs", "gamelogs"), + ("Geo", "geo"), + ("Images", "img"), + ("Leaderboard", "leaderboard"), + ("Link", "link"), + ("Lists", "lists"), + ("Matchmaking", "match"), + ("Moderation", "moderation"), + ("NameServer", "ns"), + ("Notifications", "notify"), + ("PlatformNotifications", "platformnotifications"), + ("PlayerSettings", "playersettings"), + ("RoomComments", "roomcomments"), + ("RoomieIntegrations", "roomieintegrations"), + ("Rooms", "rooms"), + ("Storage", "storage"), + ("Strings", "strings"), + ("StringsCDN", "strings-cdn"), + ("Studio", "studio"), + ("Thorn", "thorn"), + ("Videos", "videos"), + }; + + /// Build the service URL map for a specific apex + /// domain. Used by NsController + the client config endpoints; + /// the apex is fed in by the caller (typically + /// ) so the map stays a single + /// source of truth. + public static Dictionary BuildServiceUrlMap(string apex) + { + var map = new Dictionary(ServiceSubdomains.Length); + foreach (var (service, sub) in ServiceSubdomains) + { + var host = string.IsNullOrEmpty(sub) ? apex : $"{sub}.{apex}"; + map[service] = $"https://{host}"; + } + return map; + } +} diff --git a/DorkNet.Server/Services/DomainConfig.cs b/DorkNet.Server/Services/DomainConfig.cs new file mode 100644 index 0000000..6bff828 --- /dev/null +++ b/DorkNet.Server/Services/DomainConfig.cs @@ -0,0 +1,18 @@ +namespace DorkNet.Server.Services; + +/// Single source of truth for the deployment apex domain. +/// Read from DORKNET_DOMAIN env var (or config key +/// Domain:Apex) at startup. Defaults to localhost. Replaces +/// per-controller [Host("api.rec.net","api.localhost")] filters — +/// allowed hosts are derived as {apex} + *.{apex}, fed +/// into HostFilteringMiddleware. Subdomain-discriminating handlers +/// (Cdn vs Img on path "/{*path}") branch on +/// Request.Host.Host.Split('.')[0]. +public sealed class DomainConfig +{ + public string Apex { get; } + public DomainConfig(string apex) { Apex = apex; } + public string Sub(string prefix) => $"{prefix}.{Apex}"; + public static bool MatchesSubdomain(string host, string prefix) + => host.StartsWith(prefix + ".", StringComparison.OrdinalIgnoreCase); +} diff --git a/DorkNet.Server/Services/GameSessionService.cs b/DorkNet.Server/Services/GameSessionService.cs new file mode 100644 index 0000000..71d2aab --- /dev/null +++ b/DorkNet.Server/Services/GameSessionService.cs @@ -0,0 +1,110 @@ +using DorkNet.Models.GameSessions; +using DorkNet.Server.Data; +using DorkNet.Server.Data.Entities; +using Microsoft.EntityFrameworkCore; + +namespace DorkNet.Server.Services; + +/// +/// Cross-replica matchmaking session registry. Each session is a Photon +/// match with a player count; finds an +/// existing non-full session in the requested room+region or creates +/// a new one. +/// +/// Pre-PR-3 used a process-local ConcurrentDictionary + atomic +/// counter. With horizontal scale that was unsafe two ways: +/// +/// Each replica's counter started at 1, so two replicas would +/// hand out colliding session ids — clients on different replicas +/// could end up with the same SessionId pointing at different +/// Photon rooms. +/// "Find a non-full session" only saw replica-local sessions, so +/// two players matchmaking via different replicas got their own +/// fresh session each and were isolated. +/// +/// +/// Both are fixed by moving to Postgres: bigserial allocates from a +/// shared sequence; the SELECT sees every replica's sessions. +/// Service is now scoped because it holds a DbContext. +/// +public class GameSessionService(DorkNetDbContext db) +{ + // Dorm room is always available + private const string DormRoomId = "00000000-0000-0000-0000-000000000001"; + private const string DormActivityId = "76d98498-60a1-430c-ab76-b54a29b7a163"; + + public async Task JoinOrCreateAsync(string? roomId, string? activityId, string region) + { + var effectiveRoom = roomId ?? DormRoomId; + + // Find an existing non-full session for this room+region. ORDER BY + // PlayerCount DESC + LIMIT 1 packs joiners into the fullest matching + // session first, so we don't fragment players across half-empty + // rooms when the catalog is light. + var match = await db.GameSessions + .Where(s => s.RoomId == effectiveRoom + && s.Region == region + && s.PlayerCount < s.MaxCapacity) + .OrderByDescending(s => s.PlayerCount) + .FirstOrDefaultAsync(); + + if (match is not null) + { + match.PlayerCount += 1; + await db.SaveChangesAsync(); + return ToDto(match); + } + + var session = new GameSessionEntity + { + RoomId = effectiveRoom, + ActivityLevelId = activityId ?? DormActivityId, + Region = region, + // PhotonRoomName needs the eventual server-allocated Id baked + // in, so we save once with a placeholder, then patch and save + // again. Two SaveChangesAsync calls is cheap at our QPS. + PhotonRoomName = "pending", + MaxCapacity = 8, + PlayerCount = 1, + CreatedAt = DateTime.UtcNow, + }; + db.GameSessions.Add(session); + await db.SaveChangesAsync(); + session.PhotonRoomName = $"recroom_{session.Id}_{Guid.NewGuid():N}"; + await db.SaveChangesAsync(); + return ToDto(session); + } + + public async Task GetByIdAsync(long id) + { + var s = await db.GameSessions.AsNoTracking() + .FirstOrDefaultAsync(s => s.Id == id); + return s is null ? null : ToDto(s); + } + + public async Task PlayerLeftAsync(long sessionId) + { + var s = await db.GameSessions.FirstOrDefaultAsync(s => s.Id == sessionId); + if (s is null) return; + s.PlayerCount = Math.Max(0, s.PlayerCount - 1); + if (s.PlayerCount == 0) + { + db.GameSessions.Remove(s); + } + await db.SaveChangesAsync(); + } + + private static GameSession ToDto(GameSessionEntity s) => new() + { + GameSessionId = s.Id, + RoomId = s.RoomId, + ActivityLevelId = s.ActivityLevelId, + RegionId = s.Region, + PhotonRoomName = s.PhotonRoomName, + Private = false, + MaxCapacity = s.MaxCapacity, + PlayerCount = s.PlayerCount, + IsFull = s.PlayerCount >= s.MaxCapacity, + GameInProgress = s.PlayerCount > 0, + }; +} diff --git a/DorkNet.Server/Services/HtrAssetMirrorService.cs b/DorkNet.Server/Services/HtrAssetMirrorService.cs new file mode 100644 index 0000000..56e64b2 --- /dev/null +++ b/DorkNet.Server/Services/HtrAssetMirrorService.cs @@ -0,0 +1,240 @@ +using Google.Protobuf; +using Google.Protobuf.Reflection; +using Microsoft.EntityFrameworkCore; +using DorkNet.Server.Data; +using DorkNet.Server.Data.Entities; +using RecRoom.Protobuf2020; + +namespace DorkNet.Server.Services; + +/// +/// Background mirror for the .htr assets that HolotarPersistenceData +/// and AudioSamplerPersistenceData embedded in a PersistedRoomData +/// blob reference. Each ref is a string BlobName (typically +/// {hash}.htr) the watch fetches from +/// cdn.rec.net/data/{name}; without those bytes mirrored locally, +/// the watch's holotar projectors render empty and audio sampler nodes +/// produce silence after the YarrHarrHeist-style import otherwise +/// succeeds. +/// +/// The admin Import Room endpoint kicks this off as fire-and-forget +/// () right after the synchronous import +/// finishes, so the upload response returns immediately and the +/// 25 MB-ish download stream happens in the background. Per-asset +/// progress + summary are logged. +/// +/// Idempotent: assets already in by +/// BlobName are skipped without re-fetching. +/// +public sealed class HtrAssetMirrorService( + IServiceScopeFactory scopeFactory, + IHttpClientFactory httpClientFactory, + ILogger logger) +{ + private const string CdnBase = "https://cdn.rec.net/data/"; + // Two known persistence-view sub-message types that have a + // BlobName string referencing an external .htr (or extension-less + // base64 id) on the official CDN. + private static readonly HashSet HtrCarrierMessageNames = new(StringComparer.Ordinal) + { + "HolotarPersistenceData", + "AudioSamplerPersistenceData", + }; + + /// Fire-and-forget: schedule extraction + download in a + /// background task. Returns immediately. Errors are logged, never + /// thrown — a failure here doesn't break the original import. + public void EnqueueAsync(IReadOnlyList roomBlobs, string contextLabel) + { + if (roomBlobs is null || roomBlobs.Count == 0) return; + // Snapshot the byte arrays now — by the time the background + // task runs, the calling controller's scope is disposed, but + // the byte[]s are owned by this list and live as long as the + // closure does. + _ = Task.Run(async () => + { + try + { + await MirrorAsync(roomBlobs, contextLabel); + } + catch (Exception ex) + { + logger.LogError(ex, "[htr-mirror] background mirror crashed for {Context}", contextLabel); + } + }); + } + + /// Synchronous (awaitable) version. Use + /// for fire-and-forget from a controller; use this directly if you + /// want to wait for completion (admin tooling / one-shot scripts). + public async Task MirrorAsync(IReadOnlyList roomBlobs, string contextLabel) + { + // Step 1: parse each blob with the full 2020 schema and collect + // every Holotar/AudioSampler BlobName ref. Skip blobs that fail + // to parse — better to lose a few refs than to abort the mirror. + var refs = new HashSet(StringComparer.Ordinal); + var parseFailures = 0; + foreach (var bytes in roomBlobs) + { + try + { + var msg = PersistedRoomData.Parser.ParseFrom(bytes); + CollectHtrRefs(msg, refs); + } + catch (InvalidProtocolBufferException) + { + parseFailures++; + } + } + + if (refs.Count == 0) + { + logger.LogInformation( + "[htr-mirror] {Context}: no holotar/audio refs in {Count} blob(s); nothing to do", + contextLabel, roomBlobs.Count); + return new MirrorResult(0, 0, 0, parseFailures, Array.Empty()); + } + + // Step 2: filter against what's already in the DB. + await using var scope = scopeFactory.CreateAsyncScope(); + var db = scope.ServiceProvider.GetRequiredService(); + + var refList = refs.ToList(); + var existing = await db.RoomDataBlobs + .Where(b => refList.Contains(b.BlobName)) + .Select(b => b.BlobName) + .ToListAsync(); + var existingSet = new HashSet(existing, StringComparer.Ordinal); + var todo = refList.Where(n => !existingSet.Contains(n)).ToList(); + + logger.LogInformation( + "[htr-mirror] {Context}: {Total} unique refs; {Skip} already mirrored; {Todo} to download", + contextLabel, refList.Count, existingSet.Count, todo.Count); + + if (todo.Count == 0) + { + return new MirrorResult(refList.Count, existingSet.Count, 0, parseFailures, Array.Empty()); + } + + // Step 3: download + insert. + var http = httpClientFactory.CreateClient("htr-mirror"); + http.Timeout = TimeSpan.FromSeconds(60); + http.DefaultRequestHeaders.UserAgent.ParseAdd("DorkNet-htr-mirror/1.0"); + + var failures = new List(); + var nowIso = DateTime.UtcNow; + long totalBytes = 0; + + foreach (var name in todo) + { + try + { + using var resp = await http.GetAsync(CdnBase + Uri.EscapeDataString(name)); + if (!resp.IsSuccessStatusCode) + { + failures.Add($"{name}: HTTP {(int)resp.StatusCode}"); + logger.LogWarning("[htr-mirror] {Name} → HTTP {Code}", name, (int)resp.StatusCode); + continue; + } + var bytes = await resp.Content.ReadAsByteArrayAsync(); + if (bytes.Length == 0) + { + failures.Add($"{name}: empty body"); + continue; + } + // Dual-write: S3 (canonical post-migration) + Postgres + // BYTEA (read fallback for code that hasn't migrated). + // S3 PUT happens first so a transient Redis/Postgres + // hiccup doesn't leave us with a metadata row pointing + // at a missing object. Failures here cause this asset + // to be skipped; the import as a whole still succeeds. + var storage = scope.ServiceProvider.GetRequiredService(); + try + { + var (bucket, key) = BlobRouter.Route(name); + await storage.PutAsync( + bucket, + key, + bytes, + "application/octet-stream"); + } + catch (Exception putEx) + { + failures.Add($"{name}: s3 put failed: {putEx.Message}"); + logger.LogWarning(putEx, "[htr-mirror] {Name} S3 PUT failed", name); + continue; + } + db.RoomDataBlobs.Add(new RoomDataBlobEntity + { + RoomId = 0, // shared / unowned asset + BlobName = name, + UploadedByPlayerId = 1, + UploadedAt = nowIso, + ReferencedFilenamesCsv = string.Empty, + }); + totalBytes += bytes.Length; + logger.LogInformation("[htr-mirror] + {Name} ({Bytes:N0} bytes)", name, bytes.Length); + } + catch (Exception ex) + { + failures.Add($"{name}: {ex.Message}"); + logger.LogWarning(ex, "[htr-mirror] {Name} failed", name); + } + } + + // SaveChanges in one batch at the end keeps the DB write fast + // for the 100+-asset case YarrHarrHeist-sized rooms produce. + await db.SaveChangesAsync(); + + var inserted = todo.Count - failures.Count; + logger.LogInformation( + "[htr-mirror] {Context} done: inserted {Ins}, failed {Fail}, total bytes {Bytes:N0}", + contextLabel, inserted, failures.Count, totalBytes); + + return new MirrorResult(refList.Count, existingSet.Count, inserted, parseFailures, failures); + } + + private static void CollectHtrRefs(IMessage msg, HashSet sink) + { + var desc = msg.Descriptor; + if (HtrCarrierMessageNames.Contains(desc.Name)) + { + // Field 1 is BlobName on both carrier types. + var f = desc.FindFieldByNumber(1); + if (f is not null) + { + var v = f.Accessor.GetValue(msg) as string; + if (!string.IsNullOrWhiteSpace(v)) sink.Add(v); + } + return; + } + + foreach (var field in desc.Fields.InFieldNumberOrder()) + { + if (field.FieldType != FieldType.Message) continue; + if (field.IsRepeated) + { + if (field.Accessor.GetValue(msg) is System.Collections.IEnumerable list) + { + foreach (var item in list) + { + if (item is IMessage child) CollectHtrRefs(child, sink); + } + } + } + else + { + // proto3: HasValue is true when sub-message is non-null. + var value = field.Accessor.GetValue(msg) as IMessage; + if (value is not null) CollectHtrRefs(value, sink); + } + } + } + + public sealed record MirrorResult( + int TotalRefs, + int AlreadyMirrored, + int Inserted, + int RoomBlobParseFailures, + IReadOnlyList AssetDownloadFailures); +} diff --git a/DorkNet.Server/Services/ImageSignatureService.cs b/DorkNet.Server/Services/ImageSignatureService.cs new file mode 100644 index 0000000..3c97350 --- /dev/null +++ b/DorkNet.Server/Services/ImageSignatureService.cs @@ -0,0 +1,311 @@ +using System.Security.Cryptography; + +namespace DorkNet.Server.Services; + +public sealed class ImageSignatureService +{ + private readonly RSA? rsa; + private readonly string keyId; + private readonly string? configuredKeyIdHostSuffix; + private readonly ILogger logger; + + public ImageSignatureService(IConfiguration config, ILogger logger) + { + this.logger = logger; + keyId = NormalizeKeyId( + config["ImageSigning:KeyId"] + ?? Environment.GetEnvironmentVariable("DORKNET_IMAGE_SIGNING_KEY_ID") + ?? "p1"); + + // `.rec.net` in the client's `KEY:RSA:p1.rec.net` literal gets byte-rewritten + // by patch-client.ps1 to match the server domain (e.g. `.localhost`). The + // header suffix we emit has to match that, or the client errors as + // "Signature malformed". By default we derive the suffix from the request + // Host, which works for the standard same-domain patcher setup. Override + // with ImageSigning:KeyIdHostSuffix if your client metadata was patched to + // a different value than the server's host (e.g. running the server at + // `dorknet.example` while the client metadata still says `localhost`). + configuredKeyIdHostSuffix = + config["ImageSigning:KeyIdHostSuffix"] + ?? Environment.GetEnvironmentVariable("DORKNET_IMAGE_SIGNING_KEY_ID_SUFFIX"); + + var privateKey = + config["ImageSigning:PrivateKeyPem"] + ?? Environment.GetEnvironmentVariable("DORKNET_IMAGE_SIGNING_PRIVATE_KEY"); + + var privateKeyBase64 = + config["ImageSigning:PrivateKeyPemBase64"] + ?? Environment.GetEnvironmentVariable("DORKNET_IMAGE_SIGNING_PRIVATE_KEY_BASE64"); + + if (string.IsNullOrWhiteSpace(privateKey) && !string.IsNullOrWhiteSpace(privateKeyBase64)) + { + try + { + privateKey = System.Text.Encoding.UTF8.GetString(Convert.FromBase64String(privateKeyBase64)); + } + catch (FormatException ex) + { + logger.LogError(ex, "[img-sign] ImageSigning private key base64 is invalid"); + } + } + + if (string.IsNullOrWhiteSpace(privateKey)) + { + logger.LogWarning("[img-sign] no image signing private key configured; image downloads will use placeholder Content-Signature headers"); + return; + } + + var normalized = NormalizePem(privateKey); + var hasBegin = normalized.Contains("-----BEGIN", StringComparison.Ordinal); + var lineCount = normalized.Count(c => c == '\n') + 1; + logger.LogInformation( + "[img-sign] parsing key: rawLen={RawLen} normLen={NormLen} hasBegin={HasBegin} lines={Lines} firstChars={First} lastChars={Last}", + privateKey.Length, normalized.Length, hasBegin, lineCount, + normalized.Length > 30 ? normalized[..30].Replace('\n', '·') : normalized.Replace('\n', '·'), + normalized.Length > 30 ? normalized[^30..].Replace('\n', '·') : ""); + + rsa = RSA.Create(); + try + { + rsa.ImportFromPem(normalized); + logger.LogInformation("[img-sign] image response signing enabled with key {KeyId} (PEM path)", keyId); + return; + } + catch (Exception pemEx) + { + logger.LogWarning(pemEx, "[img-sign] ImportFromPem failed, falling back to raw-DER import"); + } + + // PEM parse failed — try treating the input as raw DER bytes, + // base64-encoded with or without PEM headers. ImportFromPem is + // strict about line lengths and header forms; ImportPkcs8 / + // ImportRSA* only need the DER octets and tolerate any + // base64-encoded body. + try + { + var body = ExtractBase64Body(normalized); + var der = Convert.FromBase64String(body); + try { rsa.ImportPkcs8PrivateKey(der, out _); } + catch + { + try { rsa.ImportRSAPrivateKey(der, out _); } + catch + { + rsa.ImportEncryptedPkcs8PrivateKey(ReadOnlySpan.Empty, der, out _); + } + } + logger.LogInformation("[img-sign] image response signing enabled with key {KeyId} (raw-DER fallback)", keyId); + } + catch (Exception derEx) + { + rsa.Dispose(); + rsa = null; + logger.LogError(derEx, "[img-sign] failed to load image signing private key — neither PEM nor DER parse succeeded"); + } + } + + /// Strip PEM headers + all whitespace from + /// , leaving just the base64 body. Used by + /// the raw-DER fallback when ImportFromPem rejects the formatted + /// PEM. + private static string ExtractBase64Body(string input) + { + var s = input; + var beginIdx = s.IndexOf("-----BEGIN", StringComparison.Ordinal); + if (beginIdx >= 0) + { + var afterBegin = s.IndexOf("-----", beginIdx + 5, StringComparison.Ordinal); + if (afterBegin >= 0) s = s[(afterBegin + 5)..]; + } + var endIdx = s.IndexOf("-----END", StringComparison.Ordinal); + if (endIdx >= 0) s = s[..endIdx]; + return StripWhitespace(s); + } + + /// Coerce the configured value into something + /// RSA.ImportFromPem will accept. + /// + /// Coolify/docker-compose let operators paste PEMs into a single-line + /// env-var field. Depending on the panel, the multi-line key may + /// arrive with: + /// + /// Surrounding quotes ("-----BEGIN PRIVATE KEY-----...") + /// Literal \n in place of real line breaks + /// Windows CRLF line endings + /// All line breaks flattened to spaces — header and body on one line + /// The bare base64 body with the BEGIN/END markers stripped + /// + /// ImportFromPem rejects every variation except the canonical + /// PEM (header line, 64-char-wrapped body lines, footer line) and + /// throws "No supported key formats were found. Check that the + /// input represents the contents of a PEM-encoded key file, not the + /// path to such a file." — which masks every garbled-paste case + /// behind a path-vs-content red herring. We rebuild the canonical + /// form here. + /// + /// Also covers the case where someone configured the path to a + /// file (e.g. /run/secrets/img-sign-key.pem); we read its + /// contents and feed those through the same normaliser. + private static string NormalizePem(string input) + { + input = (input ?? string.Empty).Trim(); + + // Strip a single layer of surrounding quotes ("..." or '...'). + if (input.Length >= 2 && + ((input[0] == '"' && input[^1] == '"') || + (input[0] == '\'' && input[^1] == '\''))) + { + input = input[1..^1]; + } + + // Path-to-file case: read it through. + if (!input.Contains("-----BEGIN", StringComparison.Ordinal) + && !input.Contains("BEGIN PRIVATE", StringComparison.Ordinal) + && input.Length < 4096 + && System.IO.File.Exists(input)) + { + input = System.IO.File.ReadAllText(input); + } + + // Unescape literal \n / \r\n / \r that survived env-var passthrough. + input = input + .Replace("\\r\\n", "\n", StringComparison.Ordinal) + .Replace("\\n", "\n", StringComparison.Ordinal) + .Replace("\\r", "\n", StringComparison.Ordinal) + .Replace("\r\n", "\n", StringComparison.Ordinal) + .Replace("\r", "\n", StringComparison.Ordinal); + + // Strip shell / YAML line-continuation backslashes. Some operator + // tools (heredocs, YAML block-scalars, multi-line env-var UIs) + // emit each PEM line with a trailing \ as a continuation + // marker. If that marker isn't expanded by the surrounding shell, + // the \ ends up embedded right before every \n in + // the PEM body. ImportFromPem tolerates it (sees corrupted body), + // but the DER fallback's FromBase64String rejects \ + // as an illegal base64 character. Diagnosed live: env var had + // ...-----END RSA PRIVATE KEY-----\\+newline plus one \ + // per body line; ~27 stray backslashes total. Strip them. + input = input.Replace("\\\n", "\n", StringComparison.Ordinal); + // Also strip a single trailing backslash before EOF (no newline + // after it — happens when the env var is set without a final \n). + if (input.EndsWith('\\')) input = input[..^1]; + + // If the headers are present but everything's on one line + // (e.g. "-----BEGIN PRIVATE KEY----- MIIEvgIBADAN... -----END PRIVATE KEY-----"), + // split it back into the canonical 64-char-wrapped form. + if (input.Contains("-----BEGIN", StringComparison.Ordinal) && !input.Contains('\n')) + { + input = ReflowSingleLinePem(input); + } + + // Still no headers? Treat the whole thing as the raw base64 body + // and synthesize a PKCS#8 PEM wrapper. ImportFromPem will figure + // out whether it's actually PKCS#1 or PKCS#8 from the DER inside. + if (!input.Contains("-----BEGIN", StringComparison.Ordinal)) + { + var body = WrapBase64(StripWhitespace(input)); + input = $"-----BEGIN PRIVATE KEY-----\n{body}\n-----END PRIVATE KEY-----"; + } + + return input; + } + + /// Pull the BEGIN/END headers off a flattened single-line + /// PEM and re-emit it with the conventional 64-char body lines. + private static string ReflowSingleLinePem(string flat) + { + var beginEnd = flat.IndexOf("-----", flat.IndexOf("-----BEGIN") + 5); + if (beginEnd < 0) return flat; + var headerEnd = flat.IndexOf("-----", beginEnd + 5); + if (headerEnd < 0) return flat; + // BEGIN line is from start to first "-----" closer + 5. + var begin = flat[..(beginEnd + 5)].Trim(); + // END line starts at "-----END ...-----" — find the trailing block. + var endStart = flat.LastIndexOf("-----END", StringComparison.Ordinal); + if (endStart < 0) return flat; + var end = flat[endStart..].Trim(); + var body = StripWhitespace(flat[(beginEnd + 5)..endStart]); + return $"{begin}\n{WrapBase64(body)}\n{end}"; + } + + private static string StripWhitespace(string s) + { + // Drops whitespace AND backslashes — the latter survive when + // line-continuation markers are still embedded in the base64 + // body and would otherwise trip FromBase64String's strict + // "illegal character" check. Every legitimate base64 char is + // in [A-Za-z0-9+/=], so a `\` is always wrong here. + var sb = new System.Text.StringBuilder(s.Length); + foreach (var c in s) + if (!char.IsWhiteSpace(c) && c != '\\') sb.Append(c); + return sb.ToString(); + } + + private static string WrapBase64(string body) + { + var sb = new System.Text.StringBuilder(body.Length + (body.Length / 64) + 1); + for (var i = 0; i < body.Length; i += 64) + { + sb.Append(body, i, Math.Min(64, body.Length - i)); + if (i + 64 < body.Length) sb.Append('\n'); + } + return sb.ToString(); + } + + public bool IsEnabled => rsa is not null; + + public void AddContentSignature(HttpResponse response, byte[] bytes) + { + var signature = rsa is not null && bytes.Length > 0 + ? rsa.SignData(bytes, HashAlgorithmName.SHA1, RSASignaturePadding.Pkcs1) + : PlaceholderSignature(bytes); + + // The client appends ?sig= to image CDN requests and then + // requires Content-Signature's key-id to match that exact signing + // key. Prefer the request value over config so localhost/prod or + // stale env settings cannot make otherwise-valid image bytes fail + // as "Signature malformed". + var requestKeyId = response.HttpContext.Request.Query["sig"].ToString(); + var headerKeyId = string.IsNullOrWhiteSpace(requestKeyId) + ? keyId + : NormalizeKeyId(requestKeyId); + + var suffix = configuredKeyIdHostSuffix is { Length: > 0 } + ? configuredKeyIdHostSuffix + : ExtractParentDomain(response.HttpContext.Request.Host.Host); + + response.Headers["Content-Signature"] = + $"key-id=KEY:RSA:{headerKeyId}.{suffix}; data={Convert.ToBase64String(signature)}"; + } + + private static string ExtractParentDomain(string host) + { + if (string.IsNullOrEmpty(host)) return "rec.net"; + var dot = host.IndexOf('.'); + return dot > 0 && dot < host.Length - 1 ? host[(dot + 1)..] : host; + } + + private static byte[] PlaceholderSignature(byte[] bytes) + { + var signature = new byte[128]; + var seed = SHA1.HashData(bytes); + for (var i = 0; i < signature.Length; i++) + signature[i] = seed[i % seed.Length]; + return signature; + } + + private static string NormalizeKeyId(string keyId) + { + keyId = keyId.Trim(); + if (keyId.StartsWith("key-id=", StringComparison.OrdinalIgnoreCase)) + keyId = keyId["key-id=".Length..].Trim(); + if (keyId.StartsWith("KEY:RSA:", StringComparison.OrdinalIgnoreCase)) + keyId = keyId["KEY:RSA:".Length..].Trim(); + // Strip a trailing host suffix if one slipped into the key-id (e.g. + // someone passed `?sig=p1.localhost`). We only care about the bare + // "p1"/"d1" tag; the host part is appended fresh by AddContentSignature. + var dot = keyId.IndexOf('.'); + if (dot > 0) keyId = keyId[..dot]; + return string.IsNullOrWhiteSpace(keyId) ? "p1" : keyId; + } +} diff --git a/DorkNet.Server/Services/ImageTransformService.cs b/DorkNet.Server/Services/ImageTransformService.cs new file mode 100644 index 0000000..f7811a2 --- /dev/null +++ b/DorkNet.Server/Services/ImageTransformService.cs @@ -0,0 +1,65 @@ +using SkiaSharp; + +namespace DorkNet.Server.Services; + +/// +/// img.* CDN transforms. The 2020 watch posts a full-frame capture +/// (e.g. the avatar standing in their dorm) and then re-requests the +/// same blob with ?cropSquare=1&width=256 on the profile +/// thumbnail surface. We honour cropSquare server-side so the +/// profile pic is face-zoomed, but we deliberately ignore the watch's +/// width=256 hint and return the cropped image at the source's +/// native resolution: the watch's UI renders the same blob across +/// multiple sizes (small thumb chip and the big profile frame), and a +/// pre-shrunk 256² source gets stretched across a ~500px UI element and +/// looks blurry. Keeping the source resolution lets the watch's image +/// pipeline downsample for display without losing detail. +/// +public static class ImageTransformService +{ + public readonly record struct Result(byte[] Bytes, string ContentType); + + public static Result? TryTransform( + byte[] sourceBytes, + string sourceContentType, + bool cropSquare, + int? widthHint) + { + // widthHint is intentionally unused — see class comment. Kept on the + // signature so the controller can pass the parsed value without the + // call site having to know which params we currently respect. + _ = widthHint; + if (!cropSquare) return null; + if (sourceBytes.Length == 0) return null; + + using var input = SKBitmap.Decode(sourceBytes); + if (input is null) return null; + if (input.Width == input.Height) return null; // already square, pass through + + using var cropped = CenterCropSquare(input); + using var image = SKImage.FromBitmap(cropped); + + // Re-encode in the same family the source advertised, so an uploaded + // JPEG stays JPEG (size budget for profile pic round-trips) and + // anything PNG-shaped stays PNG (alpha preserved for UI overlays). + var (format, contentType, quality) = sourceContentType switch + { + "image/jpeg" or "image/jpg" => (SKEncodedImageFormat.Jpeg, "image/jpeg", 92), + "image/webp" => (SKEncodedImageFormat.Webp, "image/webp", 92), + _ => (SKEncodedImageFormat.Png, "image/png", 100), + }; + using var encoded = image.Encode(format, quality); + return new Result(encoded.ToArray(), contentType); + } + + private static SKBitmap CenterCropSquare(SKBitmap input) + { + var side = Math.Min(input.Width, input.Height); + var x = (input.Width - side) / 2; + var y = (input.Height - side) / 2; + var dst = new SKBitmap(new SKImageInfo(side, side, input.ColorType, input.AlphaType)); + using var canvas = new SKCanvas(dst); + canvas.DrawBitmap(input, new SKRect(x, y, x + side, y + side), new SKRect(0, 0, side, side)); + return dst; + } +} diff --git a/DorkNet.Server/Services/LevelService.cs b/DorkNet.Server/Services/LevelService.cs new file mode 100644 index 0000000..091b1bb --- /dev/null +++ b/DorkNet.Server/Services/LevelService.cs @@ -0,0 +1,102 @@ +using Microsoft.EntityFrameworkCore; +using DorkNet.Models.Notification; +using DorkNet.Server.Data; +using DorkNet.Server.Data.Entities; + +namespace DorkNet.Server.Services; + +/// +/// Awards XP, computes level transitions, and adjusts currency +/// balances. Centralising these mutations behind one service avoids +/// scattered db.Players.Find(); p.XP += n; calls and +/// gives us a single place to adjust the reward curve. +/// +/// Curve: level = floor(sqrt(xp / 100)) + 1 — i.e. each level +/// costs 100 * (level^2 - (level-1)^2) XP, scaling +/// quadratically. Level 1 starts at 0 XP, Level 2 at 100, Level 3 at +/// 400, Level 5 at 1600, Level 10 at 8100. Loose match for the +/// 2020 client's expected curve. +/// +public class LevelService(DorkNetDbContext db, NotificationService notifications) +{ + public const int FirstLoginXp = 50; + public const int InventionSavedXp = 25; + public const int CheerReceivedXp = 10; + public const int RoomVisitXp = 5; + + /// Add XP to the player. Returns + /// the new (level, xp) pair. Level transitions push a + /// SubscriptionUpdateProfile notification with the new + /// level. Idempotent at the row level — concurrent calls might + /// double-grant if both load the same XP value, but the curve + /// tolerates that. + public async Task<(int level, int xp)> AwardXpAsync(long playerId, int amount, string reason) + { + if (amount <= 0) return (1, 0); + var player = await db.Players.FirstOrDefaultAsync(p => p.Id == playerId); + if (player is null) return (1, 0); + + var oldLevel = player.Level; + player.XP += amount; + player.Level = LevelForXp(player.XP); + await db.SaveChangesAsync(); + + if (player.Level > oldLevel) + { + // Level-up grants a small currency reward and a push so + // the watch can play its level-up animation. + await GrantCurrencyAsync(playerId, currencyType: 2, + amount: 100 * (player.Level - oldLevel), + reason: $"levelup:{oldLevel}->{player.Level}"); + await notifications.NotifyAsync(playerId, + PushNotificationId.SubscriptionUpdateProfile, + new + { + Reason = "LevelUp", + Level = player.Level, + PreviousLevel = oldLevel, + }); + } + + return (player.Level, player.XP); + } + + /// Grant or deduct currency. + /// can be negative for deductions (e.g. spending in storefronts). + /// Pushes StorefrontBalanceUpdated when the balance moves. + public async Task GrantCurrencyAsync( + long playerId, int currencyType, long amount, string reason) + { + if (amount == 0) return await GetBalanceAsync(playerId, currencyType); + var row = await db.CurrencyBalances.FirstOrDefaultAsync( + c => c.PlayerId == playerId && c.CurrencyType == currencyType); + if (row is null) + { + row = new CurrencyBalanceEntity + { + PlayerId = playerId, + CurrencyType = currencyType, + Balance = 0, + }; + db.CurrencyBalances.Add(row); + } + row.Balance = Math.Max(0, row.Balance + amount); + row.UpdatedAt = DateTime.UtcNow; + await db.SaveChangesAsync(); + + await notifications.NotifyAsync(playerId, + PushNotificationId.StorefrontBalanceUpdate, + new { CurrencyType = currencyType, row.Balance, Reason = reason }); + return row.Balance; + } + + public async Task GetBalanceAsync(long playerId, int currencyType) => + await db.CurrencyBalances + .Where(c => c.PlayerId == playerId && c.CurrencyType == currencyType) + .Select(c => c.Balance) + .FirstOrDefaultAsync(); + + /// Inverse of the level→xp curve. floor(sqrt(xp/100)) + 1. + public static int LevelForXp(int xp) => + xp <= 0 ? 1 : (int)Math.Floor(Math.Sqrt(xp / 100.0)) + 1; +} diff --git a/DorkNet.Server/Services/NotificationService.cs b/DorkNet.Server/Services/NotificationService.cs new file mode 100644 index 0000000..1a68d34 --- /dev/null +++ b/DorkNet.Server/Services/NotificationService.cs @@ -0,0 +1,355 @@ +using DorkNet.Models.Notification; +using DorkNet.Server.Controllers.Match; +using DorkNet.Server.Hubs; +using Microsoft.AspNetCore.SignalR; +using System.Text.Json; + +namespace DorkNet.Server.Services; + +/// +/// Pushes s to specific players over the +/// SignalR . Each connected player belongs to a +/// per-player group named player:<id>; sending to that group +/// reaches every active connection for that player without us tracking +/// individual connection ids. +/// +/// **Wire contract** (verified against +/// Cpp2IL_ISIL/.../RecNet/Notifications.txt:1979-1994): +/// - Hub method name: "Notification" (singular). The client +/// calls connection.On("Notification", Action<string>). +/// - Payload: a SINGLE string argument containing JSON of +/// the form {"Id":int, "Msg":object}. The client's handler +/// then parses that string into a Dictionary<string, object> +/// and dispatches by Id to per-handler callbacks registered via +/// RegisterHandler(PushNotificationId, NotificationHandler). +/// +/// Earlier versions sent SendAsync("ReceiveNotification", obj) +/// — wrong method name AND wrong arg type (object instead of string). +/// All push notifications were silently dropped client-side; admin +/// kicks / broadcasts / friend-request alerts never reached the +/// in-game client. Keep the method name + string serialisation +/// in sync with the decompiled client when extending. +/// +/// Higher-level helpers (FriendRequestReceived, FriendRequestAccepted, +/// PlayerEnteredRoom, etc.) wrap NotifyAsync so callers don't have to +/// pick the right by hand. +/// +public class NotificationService( + IHubContext hub, + OnlinePresenceService onlinePresence, + ILogger logger) +{ + private const string HubMethod = "Notification"; + + private static readonly JsonSerializerOptions Json = new() + { + // Client uses LitJson which reads PascalCase keys verbatim; + // no camelCase / snake_case conversion in the wire payload. + PropertyNamingPolicy = null, + }; + + /// The watch's Notifications.OnNotificationReceived + /// reads Id as a string and dispatches against + /// handlers : Dictionary<string, NotificationHandler> + /// (verified at Cpp2IL_ISIL/.../RecNet/Notifications.txt:2097-2222). + /// Wire name per id is whatever the registering class actually + /// stored in the handlers dict — verified live by tracing every + /// Notifications.RegisterHandler call at boot via + /// tools/SaveDebugMod's [notify-register] log lines. + /// The handlers fall into three buckets: + /// + /// Registered ONLY under the bare integer string (no + /// enum-name companion): MessageReceived (2), + /// MessageDeleted (3), ModerationKick (22), + /// ModerationKickAttemptFailed (23), + /// ModerationRoomBan (24), and the entire + /// PlayerEvent* family (80-85). Sending the enum + /// name (e.g. "MessageReceived") makes lookup miss + /// and the push drops silently — this is exactly what + /// broke DMs / game-invite delivery: the server logged + /// [notify] sending id=MessageReceived ... + /// connections=1 AND the watch's topmost + /// OnNotificationReceived hook saw the payload, but + /// Messages.OnMessageReceived never fired because + /// handlers["MessageReceived"] didn't exist — + /// handlers["2"] did. + /// Registered under a CUSTOM short name (no integer + /// companion): SubscriptionUpdate* → + /// "PresenceUpdate" / "RoomInstanceUpdate" / + /// "AccountUpdate", and ChatMessageReceived + /// (90) which is registered only as + /// "ChatMessageReceived", NOT "90". + /// Registered through the enum overload, which the IL2CPP + /// client lowers to the numeric string value. Gifts are a + /// concrete example: GiftPackageReceived registers + /// "30" and GiftPackageReceivedImmediate + /// registers "31"; sending the enum name misses. + /// + private static string WireName(PushNotificationId id) => id switch + { + // Custom short names — registered under a bespoke string, + // no integer companion in the dispatch dict. + PushNotificationId.SubscriptionUpdatePresence => "PresenceUpdate", + PushNotificationId.SubscriptionUpdateGameSession => "RoomInstanceUpdate", + PushNotificationId.SubscriptionUpdateProfile => "AccountUpdate", + // Int-only registrations — sending the enum name misses. + PushNotificationId.MessageReceived => "2", + PushNotificationId.MessageDeleted => "3", + PushNotificationId.ModerationKick => "22", + PushNotificationId.ModerationKickAttemptFailed => "23", + PushNotificationId.ModerationRoomBan => "24", + PushNotificationId.GiftPackageReceived => "30", + PushNotificationId.GiftPackageReceivedImmediate => "31", + PushNotificationId.PlayerEventCreated => "80", + PushNotificationId.PlayerEventUpdated => "81", + PushNotificationId.PlayerEventDeleted => "82", + PushNotificationId.PlayerEventResponseChanged => "83", + PushNotificationId.PlayerEventResponseDeleted => "84", + PushNotificationId.PlayerEventStateChanged => "85", + // Everything else: enum.ToString() matches because the watch's + // enum-overload RegisterHandler registers both forms. + _ => id.ToString(), + }; + + private static string Serialize(PushNotificationId id, object? msg) => + JsonSerializer.Serialize(new { Id = WireName(id), Msg = msg }, Json); + + /// Push a single notification to one player. No-op if the + /// player has no active connections. + public async Task NotifyAsync(long playerId, PushNotificationId id, object? msg = null) + { + var payload = Serialize(id, msg); + // Include the connection count so we can tell at a glance + // whether the push had a recipient. Zero connections = the + // watch isn't on SignalR right now (transport churn, just- + // logged-out session, etc.) and the message is dropped. + // The save flow specifically REQUIRES the watch to have a + // live SignalR connection at send time — without it, + // SubscriptionUpdateRoom evaporates into the hub and + // MasterUploadRoomDataBlobCoroutine waits forever on + // roomDetailsUpdated. + var connections = onlinePresence.GetConnections(playerId); + logger.LogInformation( + "[notify] sending id={Id} wireName={WireName} to player={PlayerId} bytes={Bytes} connections={ConnectionCount}", + id, WireName(id), playerId, payload.Length, connections.Count); + if (id == PushNotificationId.SubscriptionUpdateRoom) + { + logger.LogInformation( + "[notify] room-update payload player={PlayerId} json={Payload}", + playerId, payload.Length <= 6000 ? payload : payload[..6000] + "..."); + } + await hub.Clients.Group(NotifyHub.GroupForPlayer(playerId)) + .SendAsync(HubMethod, payload); + } + + public async Task NotifyTypedAsync(long playerId, string notificationType, object? msg = null) + { + try + { + var payload = JsonSerializer.Serialize(new { Id = notificationType, Msg = msg }, Json); + await hub.Clients.Group(NotifyHub.GroupForPlayer(playerId)) + .SendAsync(HubMethod, payload); + } + catch (Exception ex) + { + logger.LogWarning(ex, + "[notify] failed to send typed notification {Type} to player {PlayerId}", + notificationType, playerId); + } + } + + /// Push a notification to every currently-connected + /// player. Used for global announcements and admin broadcasts. + public async Task BroadcastAsync(PushNotificationId id, object? msg = null) + { + await hub.Clients.All.SendAsync(HubMethod, Serialize(id, msg)); + } + + /// Push the same notification to every player in + /// . Used for "your friends are online" + /// fan-out. + public async Task NotifyManyAsync( + IEnumerable playerIds, PushNotificationId id, object? msg = null) + { + var groups = playerIds.Select(NotifyHub.GroupForPlayer).ToArray(); + if (groups.Length == 0) return; + await hub.Clients.Groups(groups).SendAsync(HubMethod, Serialize(id, msg)); + } + + // ── Higher-level event helpers ───────────────────────────────────── + + public Task FriendRequestReceived(long targetPlayerId, long fromPlayerId) => + NotifyAsync(targetPlayerId, PushNotificationId.RelationshipChanged, + new { Reason = "FriendRequestReceived", From = fromPlayerId }); + + public Task FriendRequestAccepted(long requesterPlayerId, long byPlayerId) => + NotifyAsync(requesterPlayerId, PushNotificationId.RelationshipChanged, + new { Reason = "FriendRequestAccepted", By = byPlayerId }); + + public Task FriendRequestDeclined(long requesterPlayerId, long byPlayerId) => + NotifyAsync(requesterPlayerId, PushNotificationId.RelationshipChanged, + new { Reason = "FriendRequestDeclined", By = byPlayerId }); + + public Task FriendRemoved(long otherPlayerId, long byPlayerId) => + NotifyAsync(otherPlayerId, PushNotificationId.RelationshipChanged, + new { Reason = "FriendRemoved", By = byPlayerId }); + + public Task FriendBlocked(long blockedPlayerId, long byPlayerId) => + NotifyAsync(blockedPlayerId, PushNotificationId.RelationshipChanged, + new { Reason = "FriendBlocked", By = byPlayerId }); + + /// Push a presence delta so subscribed friends re-render + /// their friend-list row without waiting for the watch's 15-45 s + /// bulk-presence poll. The Msg body MUST match + /// RecNet.PlayerPresence's wire shape — the watch's + /// OnSubscriptionUpdatePresence handler (dump.cs:579282) + /// passes the raw dict straight into PlayerPresence.Deserialize + /// then UpdateCachedPresence. Anything other than that exact + /// shape (e.g. an ad-hoc { PlayerId, RoomId }) is silently + /// dropped client-side and the friend-list room stays stuck on + /// whatever was last bulk-fetched. + /// + /// Builds the payload directly as JsonObject (instead of + /// boxing a through + /// as object) so the inner + /// camelCase keys don't depend on System.Text.Json's + /// polymorphic-property heuristics. With object?-typed Msg, + /// the runtime-type path can pick the wrong contract under certain + /// option combos and emit an empty {} body — which silently + /// drops the presence update at the watch. + public async Task PlayerPresenceChanged(IEnumerable friendIds, long playerId, RoomInstanceDto? room) + { + var groups = friendIds.Select(NotifyHub.GroupForPlayer).ToArray(); + if (groups.Length == 0) return; + + var redacted = RoomInstanceDto.Redact(room); + var msg = new System.Text.Json.Nodes.JsonObject + { + ["playerId"] = (int)playerId, + ["statusVisibility"] = 1, + ["deviceClass"] = 0, + ["vrMovementMode"] = 0, + ["roomInstance"] = redacted is null + ? null + : new System.Text.Json.Nodes.JsonObject + { + ["roomInstanceId"] = redacted.RoomInstanceId, + ["roomId"] = redacted.RoomId, + ["subRoomId"] = redacted.SubRoomId, + ["roomInstanceType"] = redacted.RoomInstanceType, + ["location"] = redacted.Location, + ["dataBlob"] = redacted.DataBlob, + ["eventId"] = redacted.EventId, + ["clubId"] = redacted.ClubId, + ["roomCode"] = redacted.RoomCode, + ["photonRegionId"] = redacted.PhotonRegionId, + ["photonRoomId"] = redacted.PhotonRoomId, + ["name"] = redacted.Name, + ["maxCapacity"] = redacted.MaxCapacity, + ["isFull"] = redacted.IsFull, + ["isPrivate"] = redacted.IsPrivate, + ["isInProgress"] = redacted.IsInProgress, + ["EncryptVoiceChat"] = redacted.EncryptVoiceChat, + }, + // 2020.12 PlayerPresence.Deserialize requires these. + ["isOnline"] = redacted is not null, + ["appVersion"] = 20201210, + }; + + var envelope = new System.Text.Json.Nodes.JsonObject + { + // String dispatch key (NOT the int enum value) — + // see WireName for rationale. + ["Id"] = WireName(PushNotificationId.SubscriptionUpdatePresence), + ["Msg"] = msg, + }; + var payload = envelope.ToJsonString(); + + logger.LogDebug( + "[notify] presence push player={PlayerId} → {FriendCount} friend(s), payload={Payload}", + playerId, groups.Length, payload); + + await hub.Clients.Groups(groups).SendAsync(HubMethod, payload); + } + + public Task PlayerCameOnline(IEnumerable friendIds, long playerId, RoomInstanceDto? room) => + PlayerPresenceChanged(friendIds, playerId, room); + + public Task PlayerWentOffline(IEnumerable friendIds, long playerId) => + PlayerPresenceChanged(friendIds, playerId, null); + + public Task PlayerEnteredRoom(IEnumerable friendIds, long playerId, RoomInstanceDto room) => + PlayerPresenceChanged(friendIds, playerId, room); + + /// Force every currently-connected SignalR session for + /// to disconnect via the watch's + /// ModerationKick flow. Wire path verified against decompilation: + /// PushNotificationId.ModerationKick = 22 is registered + /// at PlayerReporting.txt:244 via the enum-overload, so + /// the wire name resolves to "ModerationKick". + /// OnModerationKick(Dictionary) at + /// PlayerReporting.txt:1857 shows a confirmation dialog + /// then calls LocalModerationKickSelf + /// (PlayerReporting.txt:2647), which queues the + /// disconnect through Core.ShutdownForLogout — the same + /// graceful path the watch uses for real moderator kicks. + /// + /// Used by + /// to enforce single-session-per-account: when a second login on + /// the same account replaces the LoginLock, the older SignalR + /// connections are kicked. The new session hasn't called + /// negotiate yet (it happens AFTER /player/login on the + /// boot sequence), so the kick only hits old sessions. + /// + public Task KickStaleSession(long playerId, string reason) => + NotifyAsync(playerId, PushNotificationId.ModerationKick, + new + { + // Field names follow PlayerReporting's wire convention + // for moderation-related dialogs; the watch's + // OnModerationKick deserializes via Util.GetKeyOrDefault + // so missing fields are tolerated, but populating them + // makes the dialog read sensibly. + Reason = reason, + DisplayReason = reason, + BannedByPlayerId = 1, // Coach + Source = "session_replaced", + }); + + /// Push a ModerationKick to a specific set of SignalR + /// connection ids — used by + /// to evict the previous session when a new device connects on the + /// same account. Targets specific ids (not the player group) so + /// the brand-new connection joining the group right after this + /// returns can't accidentally receive its own eviction notice. + /// Works across replicas because SignalR's Redis backplane routes + /// Clients.Clients(ids) to whichever node owns each id. + public Task KickConnectionsAsync(IReadOnlyCollection connectionIds, string reason) + { + if (connectionIds.Count == 0) return Task.CompletedTask; + var payload = Serialize(PushNotificationId.ModerationKick, new + { + Reason = reason, + DisplayReason = reason, + BannedByPlayerId = 1, + Source = "session_replaced", + }); + return hub.Clients.Clients(connectionIds).SendAsync(HubMethod, payload); + } + + /// Notify a private-instance owner that someone tried to + /// follow them in via "Go to Player" but was rejected for not + /// being on the invite list. Reuses the MessageReceived channel + /// since the watch already renders that as an inbox toast — no + /// new client-side handler needed. The body wording surfaces it + /// as "Player X tried to join your private match" so the owner + /// knows to send an explicit invite if they want to let them in. + public Task PrivateInstanceJoinRejected(long ownerPlayerId, long attemptedByPlayerId) => + NotifyAsync(ownerPlayerId, PushNotificationId.MessageReceived, + new + { + Reason = "PrivateInstanceJoinRejected", + AttemptedBy = attemptedByPlayerId, + Preview = $"Player {attemptedByPlayerId} tried to join your private match", + }); +} diff --git a/DorkNet.Server/Services/ObjectStorageService.cs b/DorkNet.Server/Services/ObjectStorageService.cs new file mode 100644 index 0000000..4b1bce4 --- /dev/null +++ b/DorkNet.Server/Services/ObjectStorageService.cs @@ -0,0 +1,360 @@ +using Amazon.S3; +using Amazon.S3.Model; +using Amazon.Runtime; + +namespace DorkNet.Server.Services; + +/// +/// Thin S3-compatible object-storage abstraction. Targets Garage in +/// production Coolify (one-click service); same SDK works against any +/// S3 endpoint for local dev (MinIO / LocalStack) or future migration +/// (Cloudflare R2, real AWS). +/// +/// Two buckets, split by mutability rather than feature: +/// +/// Buckets.Content — hash-addressed, immutable, dedup'd. +/// HTR audio/holotar bytes, PV preview images, polaroids, camera +/// photos, profile avatars, room thumbnails. Same bytes are +/// referenced by many rooms / players, so they're keyed primarily +/// by hash and (for player-attributed content) sharded by +/// {kind}/{playerId}/{filename}. +/// Buckets.Saves — owner-mutable user data. Dorm save +/// snapshots, custom-room save snapshots, invention saves, holotar +/// / video user uploads. Keyed by {kind}/{ownerId or roomId}/ +/// {filename}. Old versions can be lifecycle'd off; daily +/// backup priority lives here. +/// +/// +/// Routing happens via — every BlobName has +/// exactly one canonical (bucket, key). Removed the candidate-list +/// fallback the older 3-bucket layout needed because content was +/// scattered across feature-themed buckets. +/// +/// **Fallback when no S3 is configured**: writes go to disk under +/// data/object-fallback/{bucket}/{key}. Keeps local single-instance +/// dev working without spinning up MinIO. Production Coolify always +/// has the env vars set so the disk path is a dev-only convenience. +/// +/// Falls back silently to disk so the controllers don't have to +/// branch on configuration. is exposed +/// for the migrator + admin tooling that needs to know the actual mode. +/// +public interface IObjectStorage +{ + bool IsS3Configured { get; } + + /// Upload bytes. Overwrites any existing object with the + /// same key. Returns the byte count actually written (matches + /// .Length in success cases; differs only + /// if the underlying store reports otherwise). + Task PutAsync(string bucket, string key, byte[] bytes, string contentType, CancellationToken ct = default); + + /// Download bytes for a given key. Returns null when the + /// object doesn't exist. Throws on transport errors so callers can + /// distinguish "not there" from "broken". + Task GetAsync(string bucket, string key, CancellationToken ct = default); + + /// True when an object with this key exists. Cheaper than + /// GetAsync when the caller only needs existence (e.g. CdnController + /// MISS check before falling back to default blob). + Task ExistsAsync(string bucket, string key, CancellationToken ct = default); +} + +public static class Buckets +{ + public const string Content = "dorknet-content"; + public const string Saves = "dorknet-saves"; +} + +/// +/// Single source of truth for "where does this filename live in S3?". +/// Read path (Cdn/Img controllers) and write path (Storage/Images +/// controllers, importers, mirrors) BOTH call +/// with the same filename — guaranteed-consistent placement, no +/// candidate fallback chain, no read/write divergence. +/// +/// Routing rules, in priority order: +/// 1. Save-shaped filenames (dorm_p<player>_v*.dat, +/// room_<id>_v*.dat, invention_p<player>_*.dat) +/// → , sharded by owner/room so per-owner +/// lifecycle policies can prune old versions independently. +/// 2. Everything else routes to by file +/// extension: .htr → htr/, image extensions → +/// image/, .mp4 → video/, anything else → +/// blob/. Ownership is already encoded in the filename +/// (img_p<player>_*, holotar_p<player>_*, +/// …) so we don't shard by player in Content — a query like "all +/// of player N's stuff" goes through the DB +/// (RoomDataBlobs.UploadedByPlayerId) which is the source +/// of truth anyway. +/// +/// Net effect: any two paths that share the same BlobName produce the +/// same (bucket, key). Same holotar bytes uploaded by a player then +/// later referenced from a room save → one S3 object, not two. +/// +public static class BlobRouter +{ + public static (string Bucket, string Key) Route(string fileName) + { + // Saves first — these filename shapes are unambiguous. + if (TryGetPlayerId(fileName, "dorm_p", out var p)) return (Buckets.Saves, $"dorm/{p}/{fileName}"); + if (TryGetPlayerId(fileName, "invention_p", out p)) return (Buckets.Saves, $"invention/{p}/{fileName}"); + if (TryGetRoomId (fileName, out var r)) return (Buckets.Saves, $"room/{r}/{fileName}"); + + // Content, by file extension. Owner (if any) stays in the filename. + var ext = Path.GetExtension(fileName).ToLowerInvariant(); + if (ext == ".htr") return (Buckets.Content, $"htr/{fileName}"); + if (ext is ".png" or ".jpg" or ".jpeg" or ".gif" or ".webp") + return (Buckets.Content, $"image/{fileName}"); + // All video formats land under video/ — the CdnController's + // `/video/{*path}` route maps every BlobName here verbatim, and + // keeping the prefix consistent across extensions means an + // admin grep'ing the bucket finds every uploaded clip in one + // place regardless of source container format. + if (ext is ".mp4" or ".m4v" or ".webm" or ".mov") + return (Buckets.Content, $"video/{fileName}"); + return (Buckets.Content, $"blob/{fileName}"); + } + + private static bool TryGetPlayerId(string fileName, string prefix, out long playerId) + { + playerId = 0; + if (!fileName.StartsWith(prefix, StringComparison.OrdinalIgnoreCase)) return false; + var start = prefix.Length; + var end = fileName.IndexOf('_', start); + return end > start && long.TryParse(fileName[start..end], out playerId); + } + + private static bool TryGetRoomId(string fileName, out long roomId) + { + roomId = 0; + const string prefix = "room_"; + if (!fileName.StartsWith(prefix, StringComparison.OrdinalIgnoreCase)) return false; + // Permissive: anything starting with "room__" or + // "room_." routes to that room's save folder. Covers + // the canonical save snapshot (room__v.dat) AND any + // room-tagged sidecar (room__.meta, room__thumb.jpg, + // future per-room attachments) — same parser, same folder, + // no separate matcher per content type. + var endUnderscore = fileName.IndexOf('_', prefix.Length); + var endDot = fileName.IndexOf('.', prefix.Length); + var end = + endUnderscore > 0 && (endDot < 0 || endUnderscore < endDot) ? endUnderscore : + endDot > 0 ? endDot : -1; + return end > prefix.Length && long.TryParse(fileName[prefix.Length..end], out roomId); + } +} + +public class ObjectStorageService : IObjectStorage, IDisposable +{ + private readonly IAmazonS3? _s3; + private readonly string _diskFallbackRoot; + private readonly ILogger _log; + + public bool IsS3Configured => _s3 is not null; + + public ObjectStorageService(IConfiguration config, ILogger log) + { + _log = log; + _diskFallbackRoot = Path.Combine(AppContext.BaseDirectory, "data", "object-fallback"); + + // Provider switch: env-driven. The Garage one-click service in + // Coolify exposes its endpoint on http://garage:3900 and supplies + // access/secret keys via env. ForcePathStyle is required because + // Garage (like MinIO) doesn't do virtual-host-style. + var endpoint = config["S3:Endpoint"]; + var accessKey = config["S3:AccessKey"]; + var secretKey = config["S3:SecretKey"]; + if (string.IsNullOrWhiteSpace(endpoint) + || string.IsNullOrWhiteSpace(accessKey) + || string.IsNullOrWhiteSpace(secretKey)) + { + _log.LogInformation( + "[storage] no S3 endpoint/credentials configured — falling back to disk at {Root}", + _diskFallbackRoot); + return; + } + var s3Config = new AmazonS3Config + { + ServiceURL = endpoint, + ForcePathStyle = true, + MaxErrorRetry = config.GetValue("S3:MaxErrorRetry") ?? 1, + // S3:TimeoutSeconds is the HARD ceiling enforced by the AWS + // SDK at the HttpClient level — it kills the entire upload + // request once exceeded, even if a CancellationToken passed + // by the caller is more generous. The 8-second value we + // shipped originally was too tight for the storage + // migrator (some HTR blobs are 25-50 MB; on a typical + // 10 Mbps home upload that's 20-40 s). 300 s gives plenty + // of headroom; per-call CancellationTokens still impose + // tighter bounds where the caller needs them (e.g. the + // 8 s CTS in StorageController.StoreBlobObjectAsync keeps + // in-game upload paths fast-failing). + Timeout = TimeSpan.FromSeconds(config.GetValue("S3:TimeoutSeconds") ?? 300), + // Garage requires *some* AuthenticationRegion string, value + // irrelevant. The S3 SDK signs requests against this region; + // using a non-empty placeholder avoids a SignatureDoesNotMatch + // error. R2 / real AWS will set this via env. + AuthenticationRegion = config["S3:Region"] ?? "garage", + // AWSSDK 3.7.412+ Flexible Checksums sends x-amz-checksum-* + // headers Garage rejects, breaking payload signatures. Limit + // checksum computation to operations that genuinely require one. + RequestChecksumCalculation = Amazon.Runtime.RequestChecksumCalculation.WHEN_REQUIRED, + ResponseChecksumValidation = Amazon.Runtime.ResponseChecksumValidation.WHEN_REQUIRED, + }; + if (config.GetValue("S3:DisableTlsCertificateValidation")) + { + _log.LogWarning( + "[storage] S3 TLS certificate validation is disabled. Use only for private/self-hosted endpoints."); + s3Config.HttpClientFactory = new UnsafeCertificateHttpClientFactory(); + } + + _s3 = new AmazonS3Client(accessKey, secretKey, s3Config); + _log.LogInformation("[storage] S3 client configured for endpoint={Endpoint}", endpoint); + } + + public async Task PutAsync(string bucket, string key, byte[] bytes, string contentType, CancellationToken ct = default) + { + if (_s3 is { } s3) + { + async Task PutObjectAsync() + { + using var stream = new MemoryStream(bytes); + return await s3.PutObjectAsync(new PutObjectRequest + { + BucketName = bucket, + Key = key, + InputStream = stream, + ContentType = contentType, + UseChunkEncoding = false, + }, ct); + } + + PutObjectResponse resp; + try + { + resp = await PutObjectAsync(); + } + catch (AmazonS3Exception ex) when (IsNoSuchBucket(ex)) + { + _log.LogWarning( + ex, + "[storage] bucket {Bucket} was missing while writing {Key}; creating it and retrying", + bucket, + key); + await CreateBucketAsync(s3, bucket, ct); + resp = await PutObjectAsync(); + } + + if (resp.HttpStatusCode is not (System.Net.HttpStatusCode.OK or System.Net.HttpStatusCode.Created)) + throw new InvalidOperationException($"S3 PUT {bucket}/{key} returned {resp.HttpStatusCode}"); + return bytes.Length; + } + + // Disk fallback — used in dev when no S3 is configured. + var path = DiskPath(bucket, key); + Directory.CreateDirectory(Path.GetDirectoryName(path)!); + await File.WriteAllBytesAsync(path, bytes, ct); + return bytes.Length; + } + + public async Task GetAsync(string bucket, string key, CancellationToken ct = default) + { + if (_s3 is { } s3) + { + try + { + using var resp = await s3.GetObjectAsync(bucket, key, ct); + using var ms = new MemoryStream(); + await resp.ResponseStream.CopyToAsync(ms, ct); + return ms.ToArray(); + } + catch (AmazonS3Exception ex) when (ex.StatusCode == System.Net.HttpStatusCode.NotFound) + { + return null; + } + } + + var path = DiskPath(bucket, key); + return File.Exists(path) ? await File.ReadAllBytesAsync(path, ct) : null; + } + + public async Task ExistsAsync(string bucket, string key, CancellationToken ct = default) + { + if (_s3 is { } s3) + { + try + { + await s3.GetObjectMetadataAsync(bucket, key, ct); + return true; + } + catch (AmazonS3Exception ex) when (ex.StatusCode == System.Net.HttpStatusCode.NotFound) + { + return false; + } + catch (AmazonS3Exception ex) + { + // Anything else — 403 (Garage's admin key lacks the + // HeadObject permission on this bucket), 5xx, transport + // wobble — is NOT a definitive "object missing" signal. + // Treat as "unknown" and return true so the upload path + // doesn't return 500 just because verification couldn't + // run. The caller logs the warning so the asymmetric + // ACL still shows up in telemetry. + _log.LogWarning(ex, + "[storage] HEAD {Bucket}/{Key} returned {Status}; treating as 'present (unverifiable)'", + bucket, key, ex.StatusCode); + return true; + } + catch (Exception ex) + { + _log.LogWarning(ex, + "[storage] HEAD {Bucket}/{Key} threw {Type}; treating as 'present (unverifiable)'", + bucket, key, ex.GetType().Name); + return true; + } + } + return File.Exists(DiskPath(bucket, key)); + } + + private string DiskPath(string bucket, string key) => + Path.Combine(_diskFallbackRoot, bucket, key.Replace('/', Path.DirectorySeparatorChar)); + + private static bool IsNoSuchBucket(AmazonS3Exception ex) => + ex.StatusCode == System.Net.HttpStatusCode.NotFound + || string.Equals(ex.ErrorCode, "NoSuchBucket", StringComparison.OrdinalIgnoreCase); + + private async Task CreateBucketAsync(IAmazonS3 s3, string bucket, CancellationToken ct) + { + try + { + await s3.PutBucketAsync(new PutBucketRequest + { + BucketName = bucket, + }, ct); + _log.LogInformation("[storage] created S3 bucket {Bucket}", bucket); + } + catch (AmazonS3Exception ex) when ( + ex.StatusCode == System.Net.HttpStatusCode.Conflict + || string.Equals(ex.ErrorCode, "BucketAlreadyExists", StringComparison.OrdinalIgnoreCase) + || string.Equals(ex.ErrorCode, "BucketAlreadyOwnedByYou", StringComparison.OrdinalIgnoreCase)) + { + _log.LogInformation("[storage] S3 bucket {Bucket} already exists", bucket); + } + } + + private sealed class UnsafeCertificateHttpClientFactory : HttpClientFactory + { + public override HttpClient CreateHttpClient(IClientConfig clientConfig) + { + var handler = new HttpClientHandler + { + ServerCertificateCustomValidationCallback = + HttpClientHandler.DangerousAcceptAnyServerCertificateValidator, + }; + return new HttpClient(handler); + } + } + + public void Dispose() => _s3?.Dispose(); +} diff --git a/DorkNet.Server/Services/OnlinePresenceService.cs b/DorkNet.Server/Services/OnlinePresenceService.cs new file mode 100644 index 0000000..64aef08 --- /dev/null +++ b/DorkNet.Server/Services/OnlinePresenceService.cs @@ -0,0 +1,146 @@ +using System.Collections.Concurrent; +using StackExchange.Redis; + +namespace DorkNet.Server.Services; + +/// +/// Cross-replica tracker for the set of currently-online players (anyone +/// with at least one active SignalR connection somewhere in the cluster). +/// +/// **Replaces the static NotifyHub._byPlayer dictionary** which +/// only tracked connections on the local replica. With horizontal scale, +/// admin tools that asked "is player X online" got a per-replica answer +/// — they'd see a player as offline if that player happened to be +/// connected to a different replica. This service centralises the +/// answer in Redis so all replicas agree. +/// +/// **Two Redis keys per player**: +/// +/// notify:conns:{playerId} — Set of active connection ids +/// for that player on any replica. Connections add themselves on +/// and remove on +/// . The set has +/// a 24h EXPIRE backstop so a replica that crashes mid-flight +/// doesn't leak connection ids forever. +/// notify:online — Master set of player ids that have at +/// least one active connection. Maintained explicitly: SADD when +/// the conns set transitions empty→non-empty, SREM when it goes +/// non-empty→empty. returns SMEMBERS +/// in one round-trip. +/// +/// +/// Falls back to in-process state when no Redis is configured (local dev). +/// Same semantics — just per-replica visibility. +/// +public class OnlinePresenceService +{ + private readonly IConnectionMultiplexer? _redis; + // Local fallback only. The outer dict maps playerId → connection-id set. + private readonly ConcurrentDictionary> _localConns = new(); + + /// Aggressive backstop: if a replica vanishes ungracefully, + /// the conns set ages out instead of leaking forever. Real disconnect + /// events should remove specific connection ids well before this. + public static TimeSpan ConnectionsKeyTtl { get; } = TimeSpan.FromHours(24); + + public OnlinePresenceService(IConnectionMultiplexer? redis = null) => _redis = redis; + + public async Task AddConnectionAsync(long playerId, string connectionId) + { + if (_redis is { } mux) + { + var db = mux.GetDatabase(); + var connsKey = ConnsKey(playerId); + var added = await db.SetAddAsync(connsKey, connectionId); + await db.KeyExpireAsync(connsKey, ConnectionsKeyTtl); + // Only flip the master `online` set when this is the FIRST + // connection for the player — saves a SADD on every retry + // reconnect and keeps the online set tight. + if (added) + { + var size = await db.SetLengthAsync(connsKey); + if (size == 1) await db.SetAddAsync(OnlineSetKey, playerId); + } + return; + } + + var set = _localConns.GetOrAdd(playerId, _ => new HashSet()); + lock (set) set.Add(connectionId); + } + + public async Task RemoveConnectionAsync(long playerId, string connectionId) + { + if (_redis is { } mux) + { + var db = mux.GetDatabase(); + var connsKey = ConnsKey(playerId); + var removed = await db.SetRemoveAsync(connsKey, connectionId); + if (removed) + { + var remaining = await db.SetLengthAsync(connsKey); + if (remaining == 0) + { + // Last connection for this player gone — drop them + // from the online set so admin queries see "offline". + await db.SetRemoveAsync(OnlineSetKey, playerId); + await db.KeyDeleteAsync(connsKey); + } + } + return; + } + + if (_localConns.TryGetValue(playerId, out var set)) + { + lock (set) set.Remove(connectionId); + if (set.Count == 0) _localConns.TryRemove(playerId, out _); + } + } + + /// Snapshot of SignalR connection ids that currently belong + /// to . Used by + /// to detect a second-session join and kick the existing + /// connection(s) via a targeted ModerationKick push. Returns an + /// empty collection if nobody (or only the current caller) is + /// connected — never null. + public IReadOnlyCollection GetConnections(long playerId) + { + if (_redis is { } mux) + { + try + { + var members = mux.GetDatabase().SetMembers(ConnsKey(playerId)); + if (members.Length == 0) return Array.Empty(); + var result = new string[members.Length]; + for (int i = 0; i < members.Length; i++) result[i] = members[i].ToString(); + return result; + } + catch + { + // Redis sick → fall through to local fallback rather + // than pretend there are no existing connections. + } + } + if (!_localConns.TryGetValue(playerId, out var set)) return Array.Empty(); + lock (set) return set.ToArray(); + } + + /// Snapshot of currently-online player ids across the + /// cluster. Returns an empty array when nobody's online; never null. + /// Callers typically .ToHashSet() the result for membership + /// queries inside a tight loop. + public IReadOnlyCollection OnlinePlayerIds() + { + if (_redis is { } mux) + { + var members = mux.GetDatabase().SetMembers(OnlineSetKey); + var result = new long[members.Length]; + for (int i = 0; i < members.Length; i++) + result[i] = (long)members[i]; + return result; + } + return _localConns.Where(kv => kv.Value.Count > 0).Select(kv => kv.Key).ToArray(); + } + + private const string OnlineSetKey = "notify:online"; + private static string ConnsKey(long playerId) => $"notify:conns:{playerId}"; +} diff --git a/DorkNet.Server/Services/OrphanAccountTracker.cs b/DorkNet.Server/Services/OrphanAccountTracker.cs new file mode 100644 index 0000000..4de66a5 --- /dev/null +++ b/DorkNet.Server/Services/OrphanAccountTracker.cs @@ -0,0 +1,145 @@ +using System.Collections.Concurrent; +using Microsoft.Extensions.Logging; +using StackExchange.Redis; + +namespace DorkNet.Server.Services; + +/// +/// Cross-replica ledger of "freshly created accounts that haven't logged +/// in yet." Backs the orphan-cleanup pattern around account/create: +/// +/// • The 2020 watch boot flow always calls account/create +/// after cachedlogin as a defensive "ensure my account +/// exists" probe, regardless of whether the user is picking a +/// cached account or making a brand-new one. We can't tell which +/// intent it is from the request alone — both flows look identical +/// on the wire. +/// +/// • Solution: account/create ALWAYS creates a new row (so the +/// "Create new account" UI is honoured), but the new id gets +/// registered here. When the watch's next call is +/// cached_login with a DIFFERENT account_id (the boot-flow +/// case — user picked their cached account), the cached_login +/// handler reaches into this tracker and deletes the just-created +/// orphan. When cached_login picks the same id (the "create new" +/// case — user is logging in to the new account), we leave it. +/// +/// Entries expire after via Redis EXPIRE so a stale +/// boot doesn't keep a row orphaned indefinitely. Keys are namespaced +/// orphan:{deviceId}|{platformId}. +/// +/// **Multi-replica**: when is +/// available (Coolify production), the state lives in Redis and is +/// visible across every replica — so a POST account/create on +/// replica A followed by POST connect/token (cached_login) on +/// replica B sees the same orphan id and cleans it up correctly. When +/// no Redis is configured (local single-instance dev), falls back to +/// a process-local with the same +/// semantics — identical to pre-PR-2 behaviour. +/// +public class OrphanAccountTracker +{ + private record Entry(long AccountId, DateTime CreatedAt); + private readonly ConcurrentDictionary _local = new(); + private readonly IConnectionMultiplexer? _redis; + private readonly ILogger? _logger; + + public OrphanAccountTracker(IConnectionMultiplexer? redis = null, + ILogger? logger = null) + { + _redis = redis; + _logger = logger; + } + + /// Time window after creation during which a different + /// cached_login call counts as a boot-flow orphan. + public static TimeSpan Ttl { get; } = TimeSpan.FromMinutes(5); + + /// Record that was just + /// created via account/create on the given device. Overwrites any + /// older pending entry for the same device — we only care about + /// the most recent in-flight creation. + public void TrackCreation(string? deviceId, string? platformId, long accountId) + { + var key = MakeKey(deviceId, platformId); + if (_redis is { } mux) + { + try + { + // SET key value EX 300 — atomic create-or-overwrite with TTL. + mux.GetDatabase().StringSet(key, accountId, Ttl); + return; + } + catch (RedisException ex) + { + // Redis sick (AOF full, MISCONF, connection drop). Fall + // through to local tracking so login keeps working — the + // worst case is that orphan recovery loses cross-replica + // visibility until Redis recovers. + _logger?.LogWarning(ex, + "[orphan-tracker] Redis SET failed for {Key}; falling back to local", key); + } + } + _local[key] = new Entry(accountId, DateTime.UtcNow); + } + + /// Look up the in-flight creation for this device. Returns + /// null when there's no pending entry, or when the entry has + /// expired. Read-only — the entry stays around for a possible + /// later call. + public long? PeekPending(string? deviceId, string? platformId) + { + var key = MakeKey(deviceId, platformId); + if (_redis is { } mux) + { + try + { + var v = mux.GetDatabase().StringGet(key); + // RedisValue is implicitly convertible — null/missing returns + // RedisValue.Null which TryParse would bail on. + if (v.HasValue && long.TryParse(v.ToString(), out var id)) return id; + // Fall through to local check — a previous TrackCreation + // might have fallen back to local while Redis was sick. + } + catch (RedisException ex) + { + _logger?.LogWarning(ex, + "[orphan-tracker] Redis GET failed for {Key}; falling back to local", key); + } + } + if (!_local.TryGetValue(key, out var entry)) return null; + if (DateTime.UtcNow - entry.CreatedAt > Ttl) + { + _local.TryRemove(key, out _); + return null; + } + return entry.AccountId; + } + + /// Drop the pending entry for this device — typically + /// called after we've decided whether to delete the orphan or keep + /// it. Idempotent. + public void Clear(string? deviceId, string? platformId) + { + var key = MakeKey(deviceId, platformId); + if (_redis is { } mux) + { + try { mux.GetDatabase().KeyDelete(key); } + catch (RedisException ex) + { + // Best-effort delete. Stale orphan keys expire after + // Ttl anyway, so swallowing this is safe. + _logger?.LogWarning(ex, + "[orphan-tracker] Redis DEL failed for {Key}; ignoring (TTL will collect)", key); + } + } + _local.TryRemove(key, out _); + } + + /// Composite key. Empty platformId is fine — single-Steam- + /// emu setups still differentiate by deviceId alone. Redis keys + /// are namespaced under orphan: so they don't collide with + /// other tenants in the same instance. + private static string MakeKey(string? deviceId, string? platformId) => + $"orphan:{deviceId ?? ""}|{platformId ?? ""}"; +} diff --git a/DorkNet.Server/Services/PlayerLogService.cs b/DorkNet.Server/Services/PlayerLogService.cs new file mode 100644 index 0000000..f76f298 --- /dev/null +++ b/DorkNet.Server/Services/PlayerLogService.cs @@ -0,0 +1,143 @@ +using System.Collections.Concurrent; +using System.Text.Json; +using System.Text.Json.Serialization; +using StackExchange.Redis; + +namespace DorkNet.Server.Services; + +/// +/// Rolling per-player request log used by the admin UI's "Player logs" +/// tab. The HTTP trace middleware in Program.cs appends one entry +/// per authenticated request; the admin endpoint reads the most-recent +/// N entries back for a single player so we can spot what API call a +/// player was making when they hit a bug. +/// +/// Storage: +/// - When Redis is configured, entries live in a Redis LIST keyed by +/// playerlog:{id}. LPUSH + LTRIM keeps it at the +/// most recent records, so memory +/// per player is bounded regardless of traffic. +/// - Without Redis, falls back to an in-process per-player ring buffer +/// capped to the same size; lost on restart and only visible on the +/// replica that handled the request, but fine for single-instance +/// dev workflows. +/// +/// Entries are intentionally ephemeral — there's no Postgres mirror, +/// no long retention, no cross-replica fan-out beyond Redis. Treat +/// this as a "what just happened" diagnostic, not an audit trail. +/// +public class PlayerLogService +{ + /// Cap per player. Redis list takes ~250 bytes per entry + /// with truncated bodies, so ~125 KB per player at 500 entries. + /// At 5-second heartbeat cadence that's ~40 minutes of recent + /// activity, plenty for "what was the player doing when X + /// happened" diagnostics. + public const int MaxEntriesPerPlayer = 500; + + private readonly IConnectionMultiplexer? _redis; + private readonly ILogger? _log; + // Process-local fallback when Redis isn't configured. Each player + // gets a bounded queue we trim on append. ConcurrentQueue itself + // has no built-in cap so we trim explicitly under a per-player lock. + private readonly ConcurrentDictionary> _local = new(); + + private static readonly JsonSerializerOptions JsonOpts = new() + { + PropertyNamingPolicy = null, + DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull, + }; + + public PlayerLogService( + IConnectionMultiplexer? redis = null, + ILogger? log = null) + { + _redis = redis; + _log = log; + } + + /// Append one entry for a player. Fire-and-forget — never + /// throws so a Redis blip can't 500 a request that just succeeded. + public void Record(PlayerLogEntry entry) + { + if (entry.PlayerId <= 0) return; + if (_redis is { } mux) + { + try + { + var json = JsonSerializer.Serialize(entry, JsonOpts); + var db = mux.GetDatabase(); + var key = Key(entry.PlayerId); + // LPUSH + LTRIM 0..(N-1) keeps the list at most N long + // with the newest entry at index 0. The two ops are + // batched so a concurrent reader never sees an + // intermediate over-long list. + var batch = db.CreateBatch(); + _ = batch.ListLeftPushAsync(key, json); + _ = batch.ListTrimAsync(key, 0, MaxEntriesPerPlayer - 1); + // 7-day TTL so abandoned-account log keys age out. + _ = batch.KeyExpireAsync(key, TimeSpan.FromDays(7)); + batch.Execute(); + } + catch (Exception ex) + { + _log?.LogWarning(ex, "[playerlog] redis append failed for player={PlayerId}", entry.PlayerId); + } + return; + } + // Process-local fallback: append + trim under per-player lock. + var queue = _local.GetOrAdd(entry.PlayerId, _ => new ConcurrentQueue()); + queue.Enqueue(entry); + while (queue.Count > MaxEntriesPerPlayer && queue.TryDequeue(out _)) { } + } + + /// Most recent entries for a player, + /// newest-first. Returns an empty list when the player has no + /// recorded activity (or when Redis is down on the read path — + /// admin UI shows "no entries" instead of erroring out). + public IReadOnlyList GetRecent(long playerId, int take) + { + take = Math.Clamp(take, 1, MaxEntriesPerPlayer); + if (_redis is { } mux) + { + try + { + var raw = mux.GetDatabase().ListRange(Key(playerId), 0, take - 1); + var result = new List(raw.Length); + foreach (var v in raw) + { + var e = JsonSerializer.Deserialize(v.ToString(), JsonOpts); + if (e is not null) result.Add(e); + } + return result; + } + catch (Exception ex) + { + _log?.LogWarning(ex, "[playerlog] redis read failed for player={PlayerId}", playerId); + return Array.Empty(); + } + } + if (!_local.TryGetValue(playerId, out var queue)) return Array.Empty(); + // ConcurrentQueue enumerates oldest-first; reverse + take to + // match Redis newest-first ordering. + return queue.ToArray().Reverse().Take(take).ToList(); + } + + private static string Key(long playerId) => $"playerlog:{playerId}"; +} + +/// One captured request. Bodies are pre-truncated by the +/// caller so the JSON we serialise into Redis stays bounded. +public sealed class PlayerLogEntry +{ + [JsonPropertyName("ts")] public DateTime Timestamp { get; set; } + [JsonPropertyName("playerId")] public long PlayerId { get; set; } + [JsonPropertyName("method")] public string Method { get; set; } = string.Empty; + [JsonPropertyName("host")] public string Host { get; set; } = string.Empty; + [JsonPropertyName("path")] public string Path { get; set; } = string.Empty; + [JsonPropertyName("query")] public string Query { get; set; } = string.Empty; + [JsonPropertyName("status")] public int Status { get; set; } + [JsonPropertyName("elapsedMs")] public long ElapsedMs { get; set; } + [JsonPropertyName("reqBody")] public string? ReqBody { get; set; } + [JsonPropertyName("respBody")] public string? RespBody { get; set; } +} diff --git a/DorkNet.Server/Services/PlayerPresenceService.cs b/DorkNet.Server/Services/PlayerPresenceService.cs new file mode 100644 index 0000000..2838506 --- /dev/null +++ b/DorkNet.Server/Services/PlayerPresenceService.cs @@ -0,0 +1,285 @@ +using System.Collections.Concurrent; +using System.Text.Json; +using DorkNet.Server.Controllers.Match; +using Microsoft.Extensions.Logging; +using StackExchange.Redis; + +namespace DorkNet.Server.Services; + +/// +/// Cross-replica tracker for which RoomInstance each authenticated +/// player most recently went to via /goto. The presence heartbeat +/// reads this back so the client's local state matches what the +/// server says. +/// +/// Why this exists: every ~5s the client POSTs /player/heartbeat, +/// expecting a PlayerPresence response that mirrors its own cached +/// presence. If the server returns roomInstance:null but the client +/// knows it's in DormRoom, RecNet.Matchmaking.OnPresenceHeartbeatResponse +/// logs "presence heartbeat response indicates local presence is +/// out-of-sync" repeatedly, the client gives up after a few rounds, +/// and shows the "Error: -2 / We lost the connection to Rec Room" +/// UI even though no network connection actually dropped. +/// +/// **Multi-replica**: when is +/// available, presence lives in Redis as presence:{playerId} +/// (JSON-serialised ). EXPIRE is set to +/// so a player who silently drops +/// (uninstall, kill -9) ages out automatically; their next /goto +/// refreshes the value AND the TTL. When no Redis is configured, +/// falls back to a process-local +/// — identical to pre-PR-2 behaviour and fine for single-instance +/// dev workflows. +/// +/// State is intentionally non-persistent — a server restart drops +/// every presence record, and clients re-establish via their next +/// /goto call. Method signatures stay sync because the call sites +/// (heartbeat, /goto, room-state queries) are too numerous to pay +/// the cost of an async cascade. Sync StackExchange.Redis ops are +/// sub-millisecond on the same network at our QPS. +/// +public class PlayerPresenceService +{ + private readonly ConcurrentDictionary _local = new(); + private readonly IConnectionMultiplexer? _redis; + private readonly ILogger? _log; + + /// + /// Presence must survive long room loads. The 2020 client can go + /// quiet for ~45 seconds while downloading/deserializing large + /// uploaded rooms; a 45s Redis TTL expires right before the next + /// heartbeat, making the server synthesize dorm fallback presence + /// and effectively bouncing the player back to dorm. Ten minutes + /// still ages out abandoned sessions, but does not punish slow + /// room loads or temporary network stalls. + /// + public static TimeSpan PresenceTtl { get; } = TimeSpan.FromMinutes(10); + + private static readonly JsonSerializerOptions JsonOpts = new() + { + // Match the wire format used by the matchmaking response so + // the round-trip is lossless. Properties are already tagged + // with [JsonPropertyName] for camelCase wire keys. + PropertyNamingPolicy = null, + }; + + public PlayerPresenceService( + IConnectionMultiplexer? redis = null, + ILogger? log = null) + { + _redis = redis; + _log = log; + } + + public void SetRoom(long playerId, RoomInstanceDto room) + { + if (_redis is { } mux) + { + try + { + var json = JsonSerializer.Serialize(room, JsonOpts); + mux.GetDatabase().StringSet(Key(playerId), json, PresenceTtl); + _log?.LogDebug("[presence] set redis player={PlayerId} room={Room} bytes={Bytes}", + playerId, room?.Name ?? "", json.Length); + } + catch (Exception ex) + { + // Don't let a Redis blip take down the goto handler — log + // and fall through. Heartbeat will see no presence and the + // watch will resync via its next /goto. + _log?.LogWarning(ex, "[presence] redis SET failed for player={PlayerId}", playerId); + } + } + else + { + _local[playerId] = room; + _log?.LogDebug("[presence] set local player={PlayerId} room={Room}", + playerId, room?.Name ?? ""); + } + } + + public RoomInstanceDto? GetRoom(long playerId) + { + if (_redis is { } mux) + { + try + { + var db = mux.GetDatabase(); + var key = Key(playerId); + var v = db.StringGet(key); + if (!v.HasValue) + { + _log?.LogDebug("[presence] miss redis player={PlayerId}", playerId); + return null; + } + db.KeyExpire(key, PresenceTtl); + return JsonSerializer.Deserialize(v.ToString(), JsonOpts); + } + catch (Exception ex) + { + _log?.LogWarning(ex, "[presence] redis GET failed for player={PlayerId}", playerId); + return null; + } + } + return _local.TryGetValue(playerId, out var room) ? room : null; + } + + public void Clear(long playerId) + { + if (_redis is { } mux) mux.GetDatabase().KeyDelete(Key(playerId)); + else _local.TryRemove(playerId, out _); + } + + /// List the currently-active instances of a given room, + /// grouped by PhotonRoomId, with the player ids currently presenced + /// in each. Drives the watch's RoomInstanceBrowser + /// (GET match.*/room/{id}/instances) so players can see + /// which instance their friends are in. + /// + /// Redis caveat: without a secondary index per room we'd need a + /// SCAN over every presence key — too expensive to do on each + /// request. For now the Redis path returns empty and the local + /// in-process dictionary is the source of truth. Single-instance + /// deployments work; multi-replica Redis deployments need a + /// per-room set index to be added before the browser is reliable + /// there. + public IEnumerable<(RoomInstanceDto Room, List PlayerIds)> EnumerateActiveInstances(long roomId) + { + if (_redis is not null) return Array.Empty<(RoomInstanceDto, List)>(); + var groups = new Dictionary PlayerIds)>(StringComparer.Ordinal); + foreach (var kv in _local) + { + var room = kv.Value; + if (room is null || room.RoomId != roomId) continue; + // Skip private instances — those are surfaced separately + // in the controller via PrivateInstanceService so we never + // leak a photonRoomId for an invite-only match into the + // public browser. + if (room.IsPrivate) continue; + var key = string.IsNullOrEmpty(room.PhotonRoomId) + ? $"<{room.RoomInstanceId}>" + : room.PhotonRoomId; + if (!groups.TryGetValue(key, out var entry)) + { + entry = (room, new List()); + groups[key] = entry; + } + entry.PlayerIds.Add(kv.Key); + } + return groups.Values; + } + + private static string Key(long playerId) => $"presence:{playerId}"; + + // ── LoginLock single-session enforcement ───────────────────────── + // The 2020 watch generates a fresh GUID per session and sends it + // in the `LoginLock` form field on every match.* request. Real + // Rec Room rejects heartbeats whose lock doesn't match the + // most-recent /player/login — that's how a second login on the + // same account kicks the first session. We mirror that by storing + // the most-recent lock in Redis (or the in-process fallback) + // keyed by player id, with the same 45-s TTL as presence. + // + // SetLock OVERWRITES regardless of prior value — newest login + // wins. ValidateLock returns false when the caller's lock is stale + // or absent, which the heartbeat handler maps to a + // session-terminated response so the watch logs out. + + private readonly ConcurrentDictionary _localLocks = new(); + + public bool SetLock(long playerId, string loginLock) + { + if (string.IsNullOrWhiteSpace(loginLock)) return false; + var replacedActiveSession = false; + if (_redis is { } mux) + { + try + { + var db = mux.GetDatabase(); + var key = LockKey(playerId); + var previous = db.StringGet(key); + replacedActiveSession = previous.HasValue && + !string.Equals(previous.ToString(), loginLock, StringComparison.Ordinal); + db.StringSet(key, loginLock, PresenceTtl); + } + catch (Exception ex) { _log?.LogWarning(ex, "[lock] redis SET failed for player={PlayerId}", playerId); } + } + else + { + replacedActiveSession = _localLocks.TryGetValue(playerId, out var previous) && + !string.Equals(previous, loginLock, StringComparison.Ordinal); + _localLocks[playerId] = loginLock; + } + + if (replacedActiveSession) + _log?.LogWarning("[lock] concurrent login detected for player={PlayerId}; newest LoginLock wins", playerId); + return replacedActiveSession; + } + + public bool ValidateLock(long playerId, string? loginLock) + { + if (string.IsNullOrWhiteSpace(loginLock)) return false; + if (_redis is { } mux) + { + try + { + var v = mux.GetDatabase().StringGet(LockKey(playerId)); + if (!v.HasValue) return false; + // Refresh TTL on each successful validate so the + // active session keeps its lock alive across heartbeats. + if (string.Equals(v.ToString(), loginLock, StringComparison.Ordinal)) + { + mux.GetDatabase().KeyExpire(LockKey(playerId), PresenceTtl); + return true; + } + return false; + } + catch (Exception ex) + { + _log?.LogWarning(ex, "[lock] redis GET failed for player={PlayerId}", playerId); + return true; // fail open — better double-session than mass kick on Redis blip + } + } + return _localLocks.TryGetValue(playerId, out var stored) + && string.Equals(stored, loginLock, StringComparison.Ordinal); + } + + private static string LockKey(long playerId) => $"loginlock:{playerId}"; + + // ── Photon region pings ────────────────────────────────────────── + // The 2020 watch reports its measured Photon-region latencies in a + // single POST after the matchmaking init handshake (see + // MatchPlayerController.PhotonRegionPings). We hold the latest + // reading per player in process memory only — region preferences + // change infrequently, lose-on-restart is fine, and pushing this + // through Redis would just bloat the key space for no gain. + + private readonly ConcurrentDictionary> _regionPings = new(); + + public void SetPhotonRegionPings(long playerId, IReadOnlyDictionary pings) + { + if (pings.Count == 0) return; + _regionPings[playerId] = pings; + } + + /// Returns the region code with the lowest reported ping, + /// or null when no pings were ever received for the + /// player (matchmaking falls back to its default region in that + /// case). + public string? GetPreferredPhotonRegion(long playerId) + { + if (!_regionPings.TryGetValue(playerId, out var pings) || pings.Count == 0) + return null; + string? best = null; + var bestMs = int.MaxValue; + foreach (var kv in pings) + { + if (kv.Value < bestMs) + { + bestMs = kv.Value; + best = kv.Key; + } + } + return best; + } +} diff --git a/DorkNet.Server/Services/PlayerService.cs b/DorkNet.Server/Services/PlayerService.cs new file mode 100644 index 0000000..0d0e396 --- /dev/null +++ b/DorkNet.Server/Services/PlayerService.cs @@ -0,0 +1,753 @@ +using Microsoft.EntityFrameworkCore; +using Microsoft.Extensions.Logging; +using DorkNet.Models.Players; +using DorkNet.Server.Data; +using DorkNet.Server.Data.Entities; +using System.Security.Cryptography; + +namespace DorkNet.Server.Services; + +public class PlayerService(DorkNetDbContext db, RoomService rooms, ILogger logger) +{ + /// + /// Default outfit GUIDs piped into + /// for brand-new accounts. The wire format is a comma-separated list of + /// item-instance GUIDs, one per slot in client order: head, torso, legs, + /// feet, accessory. The client maps each GUID against its baked + /// AvatarItemDefinition catalog and falls back to the generic + /// "starter" mesh when a GUID is unknown — which means private servers + /// don't need a real items DB to dress new players, just stable + /// placeholders that the client will resolve to the built-in + /// starter clothing for each slot. + /// + /// Empty string ("") = the client renders no clothes / default skin. + /// We seed all five slots so the avatar boots fully clothed. + /// + /// Wire format for + /// — a SEMICOLON-separated list of AvatarItemSelection + /// entries, each entry is COMMA-separated as + /// {prefabGuid},{maskGuid},{swatchGuid},{decalGuid},{bodyPart} + /// (exactly 5 parts; the inner AvatarItemVisualData takes + /// 4 of them, then AvatarItemSelection.ToRecNetString + /// appends the int BodyPart). Verified at: + /// + /// Cpp2IL_ISIL/.../PlayerOutfit.txt:4601 — split delimiter + /// literal 0x3B = ;. + /// Cpp2IL_ISIL/.../AvatarItemSelection.txt:887 — exactly + /// 5 comma-segments expected, anything else throws + /// AvatarParseException("Attempting to parse bad Avatar Item + /// string …"). + /// Cpp2IL_CS/.../Player.cs:262 — BodyPart enum + /// {None=-1, Head=0, Torso=1, LeftHand=2, RightHand=3, Mouth=4}. + /// Cpp2IL_ISIL/.../OutfitTypeExt.txt:23-39 — + /// GetMatchingBodyPart: type ≤ 10 (Hat/Hair/Eye) → + /// Head; type == 20 (Mouth/Beard) → Head; 100..104 + /// (Neck/Shirt/Belt/Pocket/TeamJersey) → Torso; 200..203 + /// (Wrist/Glove/Watch/TeamWrist) → LeftHand. + /// + /// + /// We leave mask/swatch/decal empty (three empty parts between + /// commas) so the watch picks the defaults baked into the + /// AvatarItem prefab. Every prefabGuid below is in + /// data/avatar_item_lookup.json's safe set. + public const string StarterOutfitSelections = + "03f8c394-28fa-4087-978b-8d108f0bd969,,,,0" + ";" + // Hat_Angler slot 0 → Head + "1d27b674-f9e2-4ffc-9d8c-a58a1be06457,,,,0" + ";" + // Hair_Afro slot 2 → Head + "2c3773a5-a0b4-41c5-a4df-6572dff516c9,,,,0" + ";" + // Eye_2020Glasses slot 10 → Head + "8d10cc78-6b00-45f3-affb-205e9cc5b03f,,,,0" + ";" + // Beard_Close slot 20 → Head + "5b6535f0-86cd-417a-bcce-a1ace9a5f260,,,,1" + ";" + // ArcherQuiver/Neck slot 100 → Torso + "ec5255d5-edaf-4888-9fe1-e89ac0e0d300,,,,1" + ";" + // AstronautTorso slot 101 → Torso + "eda29bce-5a07-4439-a2a7-cafa11e2ed62,,,,1" + ";" + // Belt_PaintballBelt slot 102 → Torso + "uubY_Cn9A0-Ww4lsIRCF6w,,,,1" + ";" + // Neck_DraculaMedal slot 103 → Torso + "74e6878f-94e0-41f2-93ea-de6412238385,,,,2"; // PartyBand/Wrist slot 200 → LeftHand + + /// JSON list of GUIDs the player owns at signup. Stored + /// on and surfaced via + /// GET api/avatar/v4/items as the unlocked-items list. + /// The wardrobe drawer in the watch shows only the player's + /// inventory; the store's "Already Owned" badge intersects every + /// tile against this list. So this is intentionally only ~40 + /// items spread across 9 slots — enough that each tab has + /// variety on first boot, but the rest of the 453-item catalog + /// stays purchasable in the store. + /// + /// Every GUID is in data/avatar_item_lookup.json's safe + /// list. Adding GUIDs NOT in that file would crash the watch's + /// wardrobe coroutine on the first one it can't resolve. To + /// extend, run tools/AvatarCatalogDumpPatch against an + /// updated client and pull GUIDs from the dump. + public const string StarterInventoryJson = """ + [ + "03f8c394-28fa-4087-978b-8d108f0bd969", + "AbYdU7O_9U-Lwg7zFVZzXQ", + "d2d3264b-fbf6-40b5-9f10-c85ec737d112", + "17aed0a0-70ed-49da-ad09-5bc4746f7718", + "f218e2de-46fd-45b9-9e0e-670b6bb905e7", + "7a7d8a06-7982-4bcb-b7bd-4d03b48889b4", + "1d27b674-f9e2-4ffc-9d8c-a58a1be06457", + "0088603e-ec3b-4478-8694-e6fb1989b3f2", + "a6cbfe76-534a-4655-a8a8-3fed13d001c7", + "b861e5f3-fc6d-43b3-9861-c1b45cb493a8", + "880a3cc0-7407-4b61-b759-f9dd890fe9e5", + "e36bcd98-7e85-43fa-89f8-57e4ec33823a", + "2c3773a5-a0b4-41c5-a4df-6572dff516c9", + "47629ed1-5378-427c-ad61-4b44d2fe90cc", + "_qXaNbm4yEeyalo372QbvA", + "TcgHS_0aPkehrBg7ytMOMQ", + "8d10cc78-6b00-45f3-affb-205e9cc5b03f", + "61ba3c90-c81e-4deb-bc79-50c0f1fe3e83", + "cc96f8a5-bc5b-4f89-83b7-ecd53905ada7", + "3e139dd9-4757-43a8-8546-8da70dc17f0a", + "5b6535f0-86cd-417a-bcce-a1ace9a5f260", + "83be5ba4-525a-4781-a5f6-839c22e4d1d3", + "c6c08eb5-381a-4193-9722-80da95d62abe", + "5accb7dc-90a3-46ee-bad2-8fd1861d50f1", + "ec5255d5-edaf-4888-9fe1-e89ac0e0d300", + "da55bfc3-47b7-46f6-b548-7fb2c2a5e0bf", + "2f8518fc-c989-40de-98b4-c6f09b304166", + "348ac48b-986e-4d42-a0f7-1aea16b88271", + "843e3364-a743-4dfc-a990-0436e47b8c10", + "b02053b3-013f-42b0-8881-85f5be2b8cda", + "eda29bce-5a07-4439-a2a7-cafa11e2ed62", + "75-HXZBOAEiDBqclnOn8YQ", + "awOFE0_GSECr8GBNBo6a_w", + "uubY_Cn9A0-Ww4lsIRCF6w", + "5e9bbda6-d681-42b1-9cc4-5758acf34bc0", + "b95678aa-351a-4929-8a0e-0274a183eb18", + "74e6878f-94e0-41f2-93ea-de6412238385", + "52VsI_lKlkSUAOJlfDnyrQ", + "5b01eaa3-0cac-40c0-b72a-b3f6a868ae0c", + "88739d79-aeb9-471a-b25b-776f46bba9f6" + ] + """; + + /// Color vault GUIDs for the starter avatar. The 2020 + /// client's OutfitManager.FindColorFromVault expects IDs from + /// its baked skin/hair color vaults here, not packed RGBA strings. + public const string StarterHairColor = "81_c6R0my0qK9hYM_0a7LQ"; + public const string StarterSkinColor = "cl2EzJ4v6kW3g4Oo9ZQ3hA"; + + /// Starter wallet for every new account: enough RecCenterTokens + /// (CurrencyType=2) to buy ~10–20 store items so the store doesn't + /// look broken on first boot. The watch's storefront UI gates the + /// purchase confirmation on balance >= price; with a + /// zero balance every tile shows "Not enough tokens" and the + /// "Buy" button is disabled, which the user can't tell apart from + /// a server bug. + /// + /// Seeded from at signup + /// time and from on + /// startup (idempotent, only inserts a row when one doesn't + /// already exist for the player+currency). + public const long StarterRecCenterTokens = 5000; + + /// The system-account player id. Reserved for the + /// Coach account that owns every Rec Room Original room + /// (RecCenter, Paintball, Dodgeball, etc.) — those rows hard-code + /// CreatorPlayerId = 1 in the seed data. Real players are + /// assigned ids in [100_000, 10_000_000), so id=1 stays uncontested + /// forever. + public const long SystemAccountId = 1; + public const string SystemAccountUsername = "Coach"; + + /// Ensure the Coach system account exists at id=. + /// Idempotent — repeated calls no-op once the row is in place. Called + /// from Program.cs at startup BEFORE + /// because the RR-Original room seeder sets CreatorPlayerId = 1 + /// and we want that to be a real FK target rather than a dangling + /// sentinel. The migrator already accepts pre-existing sentinels, so + /// existing migrated databases will heal on first boot. + /// + /// Coach is non-loginable: DeviceId = "system" never matches any + /// real client deviceId hash (those are 40-char hex), PasswordHash + /// is null so the password grant rejects, BannedUntil is set far + /// in the future as belt-and-suspenders so even a JWT minted somehow + /// can't be used to act as Coach. The owner-of-record on system rooms + /// gets to be a real account so admin tools' "Find rooms by player" + /// queries don't have to special-case id=1. + public async Task EnsureSystemAccountAsync() + { + var existing = await db.Players.AsNoTracking() + .FirstOrDefaultAsync(p => p.Id == SystemAccountId); + if (existing is not null) + { + // Self-heal: if the row exists but the bookkeeping fields + // drift (e.g. someone manually edited Username), restore them + // so admin tools always show "Coach" as the owner. + if (existing.Username != SystemAccountUsername || existing.DisplayName != SystemAccountUsername) + { + await db.Players + .Where(p => p.Id == SystemAccountId) + .ExecuteUpdateAsync(s => s + .SetProperty(p => p.Username, SystemAccountUsername) + .SetProperty(p => p.DisplayName, SystemAccountUsername)); + } + return; + } + + var coach = new PlayerEntity + { + Id = SystemAccountId, + DeviceId = "system", + CreatedFromDeviceId = "system", + LastPlatform = 0, + LastPlatformId = string.Empty, + Username = SystemAccountUsername, + DisplayName = SystemAccountUsername, + Bio = "Rec Room Original room owner. System account — not a real player.", + Level = 99, + XP = 0, + IsAdmin = false, + IsDeveloper = false, + IsVerified = true, + CanReceiveInvites = false, + // Far-future ban as a hard authorization backstop in case a + // forged JWT ever names player 1 as the subject. BanCheckMiddleware + // refuses any authenticated request when BannedUntil > now. + BannedUntil = new DateTime(9999, 12, 31, 0, 0, 0, DateTimeKind.Utc), + PasswordHash = null, + CreatedAt = new DateTime(2020, 3, 6, 0, 0, 0, DateTimeKind.Utc), + LastSeenAt = new DateTime(2020, 3, 6, 0, 0, 0, DateTimeKind.Utc), + Avatar = new AvatarEntity + { + OutfitSelections = StarterOutfitSelections, + InventoryJson = StarterInventoryJson, + HairColor = StarterHairColor, + SkinColor = StarterSkinColor, + UpdatedAt = new DateTime(2020, 3, 6, 0, 0, 0, DateTimeKind.Utc), + }, + }; + db.Players.Add(coach); + await db.SaveChangesAsync(); + + // Postgres bigserial doesn't auto-bump its sequence when we + // insert an explicit Id, so the next AddAsync(player) would try + // to assign Id=1 and collide. Bring the sequence in line with + // MAX(Id). SQLite handles this implicitly because it reads the + // current max on each INSERT; only Postgres needs the nudge. + if (db.Database.IsNpgsql()) + { + await db.Database.ExecuteSqlRawAsync(""" + SELECT setval( + pg_get_serial_sequence('"Players"', 'Id'), + GREATEST((SELECT COALESCE(MAX("Id"), 0) FROM "Players"), 1)) + """); + } + + logger.LogInformation( + "[seed-system] Created Coach system account at id={Id}", SystemAccountId); + } + + /// Insert a starter + /// row for the player if they don't already have one for + /// CurrencyType=2 (RecCenterTokens). Idempotent: existing + /// balances are left untouched (so a player who has already spent + /// down isn't mysteriously refilled). Called from both new-account + /// flows so a brand-new account boots into a non-zero wallet, and + /// from at startup so + /// pre-existing accounts (created before this seed shipped) get + /// the same one-time grant. + private async Task EnsurePlayerWalletAsync(long playerId) + { + var exists = await db.CurrencyBalances.AsNoTracking() + .AnyAsync(c => c.PlayerId == playerId && c.CurrencyType == 2); + if (exists) return; + db.CurrencyBalances.Add(new CurrencyBalanceEntity + { + PlayerId = playerId, + CurrencyType = 2, + Balance = StarterRecCenterTokens, + UpdatedAt = DateTime.UtcNow, + }); + await db.SaveChangesAsync(); + } + + /// One-time backfill at startup: every player who's + /// missing a CurrencyType=2 row gets the starter grant. Players + /// who already have a row keep it (whatever balance, even 0 — they + /// might have spent it, and silently topping them back up would + /// be exploitable). New rows are inserted with a single bulk + /// query. + /// + /// Why a startup backfill instead of an EF migration: same + /// reasoning as — keeps the + /// starter value in C# without forcing every prod DB to re-run a + /// one-off SQL UPDATE. Self-heals if the seed value evolves. + public async Task BackfillStarterWalletsAsync() + { + // Find players who DON'T have a row for currency 2 yet. + var missing = await db.Players.AsNoTracking() + .Where(p => p.Id != SystemAccountId) + .Where(p => !db.CurrencyBalances.Any(c => + c.PlayerId == p.Id && c.CurrencyType == 2)) + .Select(p => p.Id) + .ToListAsync(); + + if (missing.Count == 0) return; + + var now = DateTime.UtcNow; + foreach (var pid in missing) + { + db.CurrencyBalances.Add(new CurrencyBalanceEntity + { + PlayerId = pid, + CurrencyType = 2, + Balance = StarterRecCenterTokens, + UpdatedAt = now, + }); + } + await db.SaveChangesAsync(); + logger.LogInformation( + "[seed-wallet] granted {Count} players the starter {Tokens}-token balance", + missing.Count, StarterRecCenterTokens); + } + + /// + /// Backfill , + /// , and color vault IDs for accounts that + /// were created BEFORE the new starter set landed (e.g. when the + /// columns shipped, when the v1 5-mask-GUID seed shipped, or any + /// avatar row with default-empty JSON). Idempotent — runs every + /// startup, only touches rows that currently hold a value we know + /// is broken or empty. + /// + /// Why a startup-time backfill instead of an EF migration: the + /// migrator would force every prod database to re-run a one-off + /// data UPDATE, and we'd have to keep the SQL identical to the + /// in-code starter strings. Running it at boot lets us evolve + /// the starter values in C# without a migration churn cycle, and + /// self-heals any AvatarEntity row that drifted out of sync (e.g. + /// from a partial migration, a hand-edit, or a now-removed + /// signup path that didn't seed the avatar). + /// + /// CALLED FROM Program.cs startup AFTER + /// . + /// + public async Task EnsureAvatarSeedAsync() + { + // The earliest seed value for InventoryJson — five mask/swatch + // asset GUIDs that aren't in the runtime config's + // avatarItemDataLookup, so the watch's wardrobe parser would + // throw "Missing data for guid …" on the first one. Any + // AvatarEntity still holding this gets backfilled to the + // current StarterInventoryJson. + const string OldBrokenInventory = """ + [ + "0dd6596c-b4ea-4d55-a219-e1f44e4a376d", + "941c046e-4e95-49f8-a7d7-19071fcc3c94", + "b75ef67d-00c3-4ac1-9b72-212032460294", + "e17481fc-b2b6-47a5-91e5-7205f45a3247", + "48abd952-214f-48b2-a8f1-1146f6f69aa2" + ] + """; + // Normalise the JSON strings before comparing — DB-stored + // JSON loses leading whitespace, so we compare without it. + var oldInvCompact = System.Text.RegularExpressions.Regex.Replace( + OldBrokenInventory, @"\s+", ""); + var newInvCompact = System.Text.RegularExpressions.Regex.Replace( + StarterInventoryJson, @"\s+", ""); + + // Detection: a valid OutfitSelections is a SEMICOLON-separated + // list of {guid},{m},{s},{d},{bp} tuples (verified at + // PlayerOutfit.txt:4601). The 5-mask-GUID seed (one comma list, + // no semicolons) and the no-bodyPart seed (also one comma list, + // no semicolons) both lack the semicolon delimiter — that's a + // sufficient discriminator. Anything with a semicolon was + // already produced by the new code path and we leave it alone. + // Empty / missing values also need a backfill. + // + // The semicolon test uses Contains(";") (string overload, + // which EF Core translates to a Postgres LIKE) NOT + // Contains(';') (char overload, which EF can't translate + // and previously crashed startup with InvalidOperationException + // "The LINQ expression … could not be translated"). + var rows = await db.Avatars + .Where(a => + string.IsNullOrEmpty(a.OutfitSelections) || + !a.OutfitSelections.Contains(";") || + string.IsNullOrEmpty(a.InventoryJson) || + a.InventoryJson == "[]" || + a.InventoryJson == OldBrokenInventory || + a.InventoryJson == oldInvCompact || + string.IsNullOrEmpty(a.HairColor) || + a.HairColor.Contains(",") || + string.IsNullOrEmpty(a.SkinColor) || + a.SkinColor.Contains(",")) + .ToListAsync(); + + if (rows.Count == 0) return; + + var now = DateTime.UtcNow; + int outfitsFixed = 0, inventoryFixed = 0, colorsFixed = 0; + foreach (var a in rows) + { + if (string.IsNullOrEmpty(a.OutfitSelections) || !a.OutfitSelections.Contains(";")) + { + a.OutfitSelections = StarterOutfitSelections; + outfitsFixed++; + } + var invCompact = string.IsNullOrEmpty(a.InventoryJson) + ? string.Empty + : System.Text.RegularExpressions.Regex.Replace(a.InventoryJson, @"\s+", ""); + if (invCompact == string.Empty || invCompact == "[]" || invCompact == oldInvCompact) + { + a.InventoryJson = newInvCompact; + inventoryFixed++; + } + if (string.IsNullOrEmpty(a.HairColor) || a.HairColor.Contains(',')) + { + a.HairColor = StarterHairColor; + colorsFixed++; + } + if (string.IsNullOrEmpty(a.SkinColor) || a.SkinColor.Contains(',')) + { + a.SkinColor = StarterSkinColor; + colorsFixed++; + } + a.UpdatedAt = now; + } + await db.SaveChangesAsync(); + logger.LogInformation( + "[seed-avatar] backfilled {OutfitCount} OutfitSelections + {InvCount} InventoryJson rows + {ColorCount} color fields", + outfitsFixed, inventoryFixed, colorsFixed); + } + + /// + /// Look up an existing account by deviceId, or create a new one if no + /// account is bound to that device yet. This is the canonical + /// account-creation entry point — every login flow funnels through here. + /// + /// Why deviceId and not platformId/Steam-ID: + /// - The client sends both fields in the platformlogin form body. We + /// deliberately ignore platformId (Steam 64) so accounts are NOT + /// keyed to a Steam identity. A user running the same patched client + /// on the same machine gets the same persistent account no matter + /// what Steam emulator (Goldberg, real Steam, none) is in front. + /// - deviceId is a stable per-installation hash the client generates + /// itself. Different machines → different accounts → multiplayer + /// works without Steam. + /// - When deviceId is empty (rare paths), fall back to a generated + /// synthetic id so we never create accidentally-shared accounts. + /// + /// Lookup-only counterpart to + /// . Returns the existing player + /// row for the given deviceId, or null if none exists — never creates + /// a new account. Callers that want to enforce the admin "signups + /// disabled" toggle use this to gate the device-id login fallback + /// without accidentally minting an account. + public async Task GetByDeviceAsync(string? deviceId) + { + if (string.IsNullOrWhiteSpace(deviceId)) return null; + return await db.Players + .Include(p => p.Avatar) + .FirstOrDefaultAsync(p => p.DeviceId == deviceId); + } + + public async Task GetOrCreateByDeviceAsync( + string? deviceId, + int platform = 0, + string? platformId = null, + string? displayName = null) + { + if (string.IsNullOrWhiteSpace(deviceId)) + { + // Synthesize a stable id from the platformId or generate one so + // we don't share an account across unrelated callers. + deviceId = !string.IsNullOrWhiteSpace(platformId) + ? $"synthetic-{platformId}" + : $"synthetic-{Guid.NewGuid():N}"; + } + + var existing = await db.Players + .Include(p => p.Avatar) + .FirstOrDefaultAsync(p => p.DeviceId == deviceId); + + if (existing is not null) + { + existing.LastSeenAt = DateTime.UtcNow; + existing.LastPlatform = platform; + if (!string.IsNullOrEmpty(platformId)) existing.LastPlatformId = platformId; + await db.SaveChangesAsync(); + return existing; + } + + var name = string.IsNullOrWhiteSpace(displayName) + ? GenerateUsername() + : await EnsureUniqueUsernameAsync(displayName); + + // First-account-on-fresh-DB gets admin so there's always a root + // admin to bootstrap moderation. Cheap query — Players is keyed by + // a small int and `Any()` on an empty table is O(1). + var isFirstAccount = !await db.Players.AsNoTracking().AnyAsync(); + + var player = new PlayerEntity + { + Id = RandomNumberGenerator.GetInt32(100_000, 9_999_999), + DeviceId = deviceId, + // Audit trail — pinned to the creating deviceId and never + // reassigned, even if `DeviceId` later rotates. + CreatedFromDeviceId = deviceId, + LastPlatform = platform, + LastPlatformId = platformId ?? string.Empty, + Username = name, + DisplayName = name, + IsAdmin = isFirstAccount, + CreatedAt = DateTime.UtcNow, + LastSeenAt = DateTime.UtcNow, + }; + player.Avatar = new AvatarEntity + { + PlayerId = player.Id, + // Pre-populate so a brand-new account spawns dressed in the + // canonical 2020 starter outfit rather than the no-clothes + // T-pose the client renders for empty OutfitSelections. + OutfitSelections = StarterOutfitSelections, + InventoryJson = StarterInventoryJson, + HairColor = StarterHairColor, + SkinColor = StarterSkinColor, + }; + + db.Players.Add(player); + await db.SaveChangesAsync(); + + // Every new player gets their own dorm row. Lazy-creates here + // (and at first /goto/room/DormRoom for legacy accounts that + // pre-date this code). + await rooms.EnsurePersonalDormAsync(player.Id); + + // Seed a starter token balance so the new account isn't + // staring at "Not enough tokens" on every store tile. + await EnsurePlayerWalletAsync(player.Id); + + if (isFirstAccount) + { + logger.LogInformation( + "[seed-admin] First account on this DB → {Username} (id {PlayerId}, deviceId {DeviceId}) granted IsAdmin=true", + name, player.Id, deviceId); + } + return player; + } + + /// + /// Always creates a new account row, regardless of whether one + /// already exists for the given . Used + /// by account/create so the watch's "Create new account" + /// selector option actually creates new — the older flow that + /// returned the existing account by deviceId blocked multi-account- + /// per-device entirely. + /// + /// Boot-flow orphans (the watch always probes account/create on + /// every boot, even when the user is picking a cached account) are + /// cleaned up by + the + /// cached_login handler's "if you logged in to a different id, the + /// just-created one was an orphan" heuristic. + /// + public async Task CreateNewAccountAsync( + string? deviceId, + int platform = 0, + string? platformId = null, + string? displayName = null) + { + // Synthesise deviceId / platformId for empty inputs so the + // audit trail still lands somewhere meaningful. + var effectiveDeviceId = string.IsNullOrWhiteSpace(deviceId) + ? $"synthetic-{Guid.NewGuid():N}" + : deviceId; + + var name = string.IsNullOrWhiteSpace(displayName) + ? GenerateUsername() + : await EnsureUniqueUsernameAsync(displayName); + + // First-account-on-fresh-DB still gets admin — same logic as + // GetOrCreateByDeviceAsync, but we honour it here too in case + // the DB is fresh and the very first call is an explicit + // create. + var isFirstAccount = !await db.Players.AsNoTracking().AnyAsync(); + + var player = new PlayerEntity + { + Id = RandomNumberGenerator.GetInt32(100_000, 9_999_999), + DeviceId = effectiveDeviceId, + CreatedFromDeviceId = effectiveDeviceId, + LastPlatform = platform, + LastPlatformId = platformId ?? string.Empty, + Username = name, + DisplayName = name, + IsAdmin = isFirstAccount, + CreatedAt = DateTime.UtcNow, + LastSeenAt = DateTime.UtcNow, + }; + player.Avatar = new AvatarEntity + { + PlayerId = player.Id, + OutfitSelections = StarterOutfitSelections, + InventoryJson = StarterInventoryJson, + HairColor = StarterHairColor, + SkinColor = StarterSkinColor, + }; + db.Players.Add(player); + await db.SaveChangesAsync(); + + await rooms.EnsurePersonalDormAsync(player.Id); + await EnsurePlayerWalletAsync(player.Id); + + if (isFirstAccount) + { + logger.LogInformation( + "[seed-admin] First account on this DB → {Username} (id {PlayerId}) granted IsAdmin=true", + name, player.Id); + } + return player; + } + + /// Hard-delete a freshly-created orphan account row. Only + /// safe to call on accounts that have NEVER been logged into via + /// JWT-issuing flows — the orphan tracker enforces that + /// constraint by only flagging accounts created within the same + /// session that ended up logging into a different id. Cascades + /// the avatar row. + public async Task DeleteOrphanAsync(long accountId) + { + var player = await db.Players + .Include(p => p.Avatar) + .FirstOrDefaultAsync(p => p.Id == accountId); + if (player is null) return false; + if (player.Avatar is not null) db.Avatars.Remove(player.Avatar); + db.Players.Remove(player); + await db.SaveChangesAsync(); + return true; + } + + public async Task GetByIdAsync(long id) => + await db.Players.Include(p => p.Avatar).FirstOrDefaultAsync(p => p.Id == id); + + /// + /// Stamp the calling platform onto an existing account so the + /// next boot's /cachedlogin/forplatformid query finds it. + /// Called from every successful login path (password, cached_login, + /// refresh) — without this, a manual username/password sign-in + /// would never tag the account with the current Steam platformId + /// and the watch's "remembered accounts" prompt would stay empty. + /// + public async Task TagPlatformAsync(long playerId, int platform, string? platformId) + { + var player = await db.Players.FirstOrDefaultAsync(p => p.Id == playerId); + if (player is null) return; + player.LastSeenAt = DateTime.UtcNow; + if (platform != 0) player.LastPlatform = platform; + if (!string.IsNullOrWhiteSpace(platformId)) player.LastPlatformId = platformId; + await db.SaveChangesAsync(); + } + + /// + /// Returns every account that has ever logged in with the given platform + /// + platformId combination, most-recent-first. Backs the + /// auth.rec.net/cachedlogin/forplatformid/{platform}/{platformId} + /// "remembered logins" prompt the client shows on its account-selection + /// screen. + /// + /// On a private server with a single Steam emulator pointed at one + /// account per machine, this typically returns 0 or 1 row. Multi-account + /// developers (or anyone testing the multi-account flow) get a real list. + /// + public async Task> GetCachedLoginsAsync(int platform, string? platformId, int take = 10) + { + if (string.IsNullOrWhiteSpace(platformId)) + return []; + return await db.Players + .Where(p => p.LastPlatform == platform && p.LastPlatformId == platformId) + .OrderByDescending(p => p.LastSeenAt) + .Take(take) + .ToListAsync(); + } + + public async Task GetByUsernameAsync(string username) => + await db.Players.FirstOrDefaultAsync(p => p.Username == username); + + public async Task> SearchAsync(string query, int take = 20) + { + // Clamp the page size — without this a malicious caller could + // request `take=1000000` and exhaust the connection. 50 is enough + // for the watch's typeahead UI (it shows ~10 rows at a time). + var clamped = Math.Clamp(take, 1, 50); + return await db.Players + .Where(p => p.Username.Contains(query) || p.DisplayName.Contains(query)) + .Take(clamped) + .ToListAsync(); + } + + /// + /// Update both Username and DisplayName. The client treats display name + /// changes as username changes for older builds; we keep them in sync + /// unless the caller is explicitly editing one or the other. + /// + public async Task UpdateUsernameAsync(long id, string username) + { + var player = await db.Players.FindAsync(id); + if (player is null) return false; + var unique = await EnsureUniqueUsernameAsync(username, exceptId: id); + player.Username = unique; + player.DisplayName = unique; + await db.SaveChangesAsync(); + return true; + } + + public async Task UpdateDisplayNameAsync(long id, string displayName) + { + var player = await db.Players.FindAsync(id); + if (player is null) return false; + player.DisplayName = displayName; + await db.SaveChangesAsync(); + return true; + } + + public async Task UpdateBioAsync(long id, string bio) + { + var player = await db.Players.FindAsync(id); + if (player is null) return false; + player.Bio = bio; + await db.SaveChangesAsync(); + return true; + } + + public static PlayerProfile ToProfile(PlayerEntity e) => new() + { + AccountId = e.Id, + Username = e.Username, + DisplayName = e.DisplayName, + Bio = e.Bio, + Level = e.Level, + XP = e.XP, + Reputation = e.Reputation, + IsVerified = e.IsVerified, + IsDeveloper = e.IsDeveloper, + IsCommunityTeam = e.IsCommunityTeam, + CanReceiveInvites = e.CanReceiveInvites, + IsJunior = e.IsJunior, + ProfileImage = e.ProfileImageName, + CreatedAt = e.CreatedAt, + }; + + private static string GenerateUsername() => + $"Player_{RandomNumberGenerator.GetInt32(1000, 99999)}"; + + /// + /// Append a numeric suffix until the username is unique. Skips the + /// suffix entirely if the requested name is already free. + /// + private async Task EnsureUniqueUsernameAsync(string requested, long? exceptId = null) + { + var name = requested.Trim(); + if (name.Length == 0) name = GenerateUsername(); + var taken = await db.Players.AnyAsync(p => p.Username == name && p.Id != exceptId); + if (!taken) return name; + + for (var i = 1; i < 1000; i++) + { + var candidate = $"{name}_{i}"; + if (!await db.Players.AnyAsync(p => p.Username == candidate && p.Id != exceptId)) + return candidate; + } + return $"{name}_{RandomNumberGenerator.GetInt32(1000, 99999)}"; + } +} diff --git a/DorkNet.Server/Services/PlaylistService.cs b/DorkNet.Server/Services/PlaylistService.cs new file mode 100644 index 0000000..a472308 --- /dev/null +++ b/DorkNet.Server/Services/PlaylistService.cs @@ -0,0 +1,356 @@ +using Microsoft.EntityFrameworkCore; +using DorkNet.Server.Data; +using DorkNet.Server.Data.Entities; + +namespace DorkNet.Server.Services; + +/// +/// Backs the watch's /roomserver/roomsandplaylists/* + /api/curatedroomplaylists +/// surfaces. The 2020.12 client deserializes responses as +/// List<MKAMHOIHOJK>, an abstract union of Room +/// (KLCOGEIGEBJ, discriminator = presence of lowercase +/// roomId) and Playlist (KMKPEOGJDFK). This service owns the +/// playlist half of that union — Rooms come from . +/// +public class PlaylistService(DorkNetDbContext db) +{ + /// + /// Hot playlists — drives /roomserver/roomsandplaylists/hot. + /// Score is CheerCount + VisitorCount * 0.25 (cheers weighted + /// heavier than passive visits) so a freshly-popular list with a + /// burst of cheers can outrank an evergreen list with high traffic. + /// Tag filter mirrors — substring + /// match against after stripping + /// a leading '#'. + /// + public async Task> HotAsync(string? tag, int take = 24) + { + IQueryable q = db.Playlists; + if (!string.IsNullOrWhiteSpace(tag)) + { + var bareTag = tag.TrimStart('#').Trim(); + if (bareTag.Length > 0) + { + var needle = $"%{bareTag}%"; + q = q.Where(p => EF.Functions.Like(p.TagsCsv, needle)); + } + } + // Score is computed client-side after pulling rows because + // EF's SQLite provider can't translate ordering expressions + // with a constant double multiplier predictably; the table + // is tiny so the in-memory sort is fine. + var rows = await q.ToListAsync(); + return rows + .OrderByDescending(p => p.CheerCount + p.VisitorCount * 0.25) + .Take(take) + .ToList(); + } + + /// + /// Curated playlists for /api/curatedroomplaylists. Returns + /// only seed-curated rows in + /// order so the editorial sort holds. + /// + public Task> CuratedAsync() => + db.Playlists + .Where(p => p.IsCurated) + .OrderBy(p => p.OrderIndex) + .ToListAsync(); + + /// + /// Free-text search across playlists — drives the playlist half of + /// /roomserver/search_roomsandplaylists/{query}. Matches Name + /// or TagsCsv substrings (same predicate shape as + /// ). + /// + public async Task> SearchAsync(string query, int take = 24) + { + if (string.IsNullOrWhiteSpace(query)) return new(); + var needle = $"%{query}%"; + return await db.Playlists + .Where(p => EF.Functions.Like(p.Name, needle) + || EF.Functions.Like(p.TagsCsv, needle)) + .OrderByDescending(p => p.CheerCount) + .Take(take) + .ToListAsync(); + } + + /// Member-room ids for a playlist, in display order. + /// Callers fan-out per-id with + /// when they need the full Room rows (the watch's playlist detail + /// screen does this). + public Task> RoomIdsAsync(long playlistId) => + db.PlaylistRooms + .Where(pr => pr.PlaylistId == playlistId) + .OrderBy(pr => pr.OrderIndex) + .Select(pr => pr.RoomId) + .ToListAsync(); + + /// Single-playlist lookup. Returns null when missing so + /// the controller can 404 cleanly. + public Task GetByIdAsync(long playlistId) => + db.Playlists.FirstOrDefaultAsync(p => p.Id == playlistId); + + /// Bulk-by-id lookup for playlists/bulk. Preserves + /// caller order; missing rows are silently dropped. + public async Task> BulkAsync(IEnumerable playlistIds) + { + var ids = playlistIds.Distinct().ToList(); + if (ids.Count == 0) return new(); + var rows = await db.Playlists.Where(p => ids.Contains(p.Id)).ToListAsync(); + var byId = rows.ToDictionary(p => p.Id); + return ids.Select(i => byId.TryGetValue(i, out var p) ? p : null) + .Where(p => p is not null).Select(p => p!).ToList(); + } + + /// Playlists created by a player. Drives + /// playlists/createdby/me and any future profile-card + /// equivalent. + public Task> CreatedByAsync(long playerId) => + db.Playlists + .Where(p => p.CreatorPlayerId == playerId) + .OrderByDescending(p => p.CreatedAt) + .ToListAsync(); + + /// Playlists the player has cheered. Drives + /// playlists/cheeredby/me. + public async Task> CheeredByAsync(long playerId) + { + var ids = await db.PlaylistInteractions + .Where(i => i.PlayerId == playerId && i.Cheered) + .Select(i => i.PlaylistId).ToListAsync(); + return await db.Playlists.Where(p => ids.Contains(p.Id)).ToListAsync(); + } + + /// Playlists the player has favorited. Drives + /// playlists/favoritedby/me. + public async Task> FavoritedByAsync(long playerId) + { + var ids = await db.PlaylistInteractions + .Where(i => i.PlayerId == playerId && i.Favorited) + .Select(i => i.PlaylistId).ToListAsync(); + return await db.Playlists.Where(p => ids.Contains(p.Id)).ToListAsync(); + } + + /// Read the caller's interaction row for a playlist — + /// drives playlists/{id}/interactionby/me. Returns (false, + /// false) sentinel when no row exists yet (no toggle has fired). + public async Task<(bool Cheered, bool Favorited)> InteractionForAsync(long playlistId, long playerId) + { + var row = await db.PlaylistInteractions + .FirstOrDefaultAsync(i => i.PlaylistId == playlistId && i.PlayerId == playerId); + return row is null ? (false, false) : (row.Cheered, row.Favorited); + } + + /// Toggle a single interaction flag for the caller. The + /// playlist's denormalized counter is bumped/decremented in the + /// same SaveChanges so the hot ranking + tile badges stay in sync. + /// Returns the new row state. + public async Task SetInteractionAsync( + long playlistId, long playerId, bool? cheered = null, bool? favorited = null) + { + var playlist = await db.Playlists.FirstOrDefaultAsync(p => p.Id == playlistId) + ?? throw new InvalidOperationException($"playlist {playlistId} not found"); + var row = await db.PlaylistInteractions + .FirstOrDefaultAsync(i => i.PlaylistId == playlistId && i.PlayerId == playerId); + if (row is null) + { + row = new PlaylistInteractionEntity + { + PlaylistId = playlistId, + PlayerId = playerId, + }; + db.PlaylistInteractions.Add(row); + } + if (cheered is bool c && row.Cheered != c) + { + row.Cheered = c; + playlist.CheerCount = Math.Max(0, playlist.CheerCount + (c ? 1 : -1)); + } + if (favorited is bool f && row.Favorited != f) + { + row.Favorited = f; + playlist.FavoriteCount = Math.Max(0, playlist.FavoriteCount + (f ? 1 : -1)); + } + row.UpdatedAt = DateTime.UtcNow; + playlist.UpdatedAt = DateTime.UtcNow; + await db.SaveChangesAsync(); + return row; + } + + /// Create a new player-owned playlist. Curated lists are + /// admin-only; user-created lists set IsCurated=false. Returns the + /// new row; the controller projects it into the BMFAGMFKODA wire + /// shape on the way out. + public async Task CreateAsync( + long creatorId, string name, string? description, string? imageName, string? tagsCsv) + { + var p = new PlaylistEntity + { + Name = (name ?? string.Empty).Trim(), + Description = description ?? string.Empty, + ImageName = imageName ?? string.Empty, + CreatorPlayerId = creatorId, + IsCurated = false, + TagsCsv = tagsCsv ?? string.Empty, + }; + db.Playlists.Add(p); + await db.SaveChangesAsync(); + return p; + } + + /// Apply an arbitrary mutation to a playlist after + /// checking ownership. Returns the row on success, null on missing + /// playlist; throws when + /// the caller isn't the creator. Used by every modify endpoint. + public async Task ModifyAsync(long playlistId, long callerId, Action mutator) + { + var p = await db.Playlists.FirstOrDefaultAsync(x => x.Id == playlistId); + if (p is null) return null; + if (p.CreatorPlayerId != callerId) throw new UnauthorizedAccessException(); + mutator(p); + p.UpdatedAt = DateTime.UtcNow; + await db.SaveChangesAsync(); + return p; + } + + /// Delete a player-owned playlist + its member-room rows + /// + every per-player interaction row. Curated playlists are + /// admin-only — the controller still calls this method but only + /// after gating on the caller's ownership. + public async Task DeleteAsync(long playlistId, long callerId) + { + var p = await db.Playlists.FirstOrDefaultAsync(x => x.Id == playlistId); + if (p is null) return false; + if (p.CreatorPlayerId != callerId) throw new UnauthorizedAccessException(); + var memberRows = await db.PlaylistRooms.Where(pr => pr.PlaylistId == playlistId).ToListAsync(); + db.PlaylistRooms.RemoveRange(memberRows); + var interactionRows = await db.PlaylistInteractions + .Where(i => i.PlaylistId == playlistId).ToListAsync(); + db.PlaylistInteractions.RemoveRange(interactionRows); + db.Playlists.Remove(p); + await db.SaveChangesAsync(); + return true; + } + + /// Add a room to a playlist at the end. Idempotent — a + /// repeat add is a no-op. Throws + /// when the caller isn't the owner. + public async Task AddRoomAsync(long playlistId, long roomId, long callerId) + { + var p = await db.Playlists.FirstOrDefaultAsync(x => x.Id == playlistId); + if (p is null) return false; + if (p.CreatorPlayerId != callerId) throw new UnauthorizedAccessException(); + var existing = await db.PlaylistRooms + .AnyAsync(pr => pr.PlaylistId == playlistId && pr.RoomId == roomId); + if (existing) return true; + var nextOrder = await db.PlaylistRooms + .Where(pr => pr.PlaylistId == playlistId) + .Select(pr => (int?)pr.OrderIndex).MaxAsync() ?? -1; + db.PlaylistRooms.Add(new PlaylistRoomEntity + { + PlaylistId = playlistId, + RoomId = roomId, + OrderIndex = nextOrder + 1, + }); + p.UpdatedAt = DateTime.UtcNow; + await db.SaveChangesAsync(); + return true; + } + + /// Remove a room from a playlist. Idempotent — missing + /// row is treated as success so the watch's repeat-delete path + /// doesn't render a phantom error. + public async Task RemoveRoomAsync(long playlistId, long roomId, long callerId) + { + var p = await db.Playlists.FirstOrDefaultAsync(x => x.Id == playlistId); + if (p is null) return false; + if (p.CreatorPlayerId != callerId) throw new UnauthorizedAccessException(); + var row = await db.PlaylistRooms + .FirstOrDefaultAsync(pr => pr.PlaylistId == playlistId && pr.RoomId == roomId); + if (row is null) return true; + db.PlaylistRooms.Remove(row); + p.UpdatedAt = DateTime.UtcNow; + await db.SaveChangesAsync(); + return true; + } + + /// + /// Idempotent seed of the default curated playlists. Inserts three + /// editorial lists (Featured, Newcomers, Sports) on first launch and + /// populates each with up to five matching seeded rooms (looked up by + /// the tag substrings each list curates). Re-running on a populated + /// table is a no-op — once any playlist exists, this method bails. + /// + public async Task SeedCuratedAsync() + { + if (await db.Playlists.AnyAsync()) return; + + var curated = new (string Name, string Description, string Image, string Tags, string[] RoomLookupTags)[] + { + ("Featured", "Hand-picked rooms the team is loving this week — start here.", + "featured.png", "featured", + new[] { "featured" }), + ("Newcomers", "Easy-to-pick-up rooms perfect for your first Rec Room sessions.", + "newcomers.png", "newcomer,easy", + new[] { "recroomoriginal" }), + ("Sports", "Get sweaty — paintball, dodgeball, soccer, and the rest of the field.", + "sports.png", "paintball,soccer,dodgeball", + new[] { "sport" }), + }; + + long playlistId = 1; + int orderIndex = 0; + foreach (var entry in curated) + { + var p = new PlaylistEntity + { + Id = playlistId++, + Name = entry.Name, + Description = entry.Description, + ImageName = entry.Image, + CreatorPlayerId = 1, + IsCurated = true, + OrderIndex = orderIndex++, + TagsCsv = entry.Tags, + CheerCount = 50 + orderIndex * 10, + FavoriteCount = 25 + orderIndex * 5, + VisitorCount = 1000 + orderIndex * 250, + VisitCount = 4000 + orderIndex * 750, + }; + db.Playlists.Add(p); + await db.SaveChangesAsync(); // need the Id for the join rows + + // Find 3-5 matching seeded rooms for this list. Search by + // tag substring against RoomEntity.TagsCsv so the + // playlists naturally pick up new seeded rooms that + // carry the right tags without per-name hardcoding. + var memberRooms = new List(); + foreach (var lookupTag in entry.RoomLookupTags) + { + var needle = $"%{lookupTag}%"; + var ids = await db.Rooms + .Where(r => !r.IsDormRoom && !r.HiddenFromBrowse && EF.Functions.Like(r.TagsCsv, needle)) + .OrderByDescending(r => r.HotScore) + .Select(r => r.Id) + .Take(5) + .ToListAsync(); + memberRooms.AddRange(ids); + } + // De-dupe in case two lookup tags catch the same room. + memberRooms = memberRooms.Distinct().Take(5).ToList(); + + int memberOrder = 0; + foreach (var roomId in memberRooms) + { + db.PlaylistRooms.Add(new PlaylistRoomEntity + { + PlaylistId = p.Id, + RoomId = roomId, + OrderIndex = memberOrder++, + }); + } + await db.SaveChangesAsync(); + } + } +} diff --git a/DorkNet.Server/Services/PrivateInstanceService.cs b/DorkNet.Server/Services/PrivateInstanceService.cs new file mode 100644 index 0000000..1b077f6 --- /dev/null +++ b/DorkNet.Server/Services/PrivateInstanceService.cs @@ -0,0 +1,271 @@ +using DorkNet.Server.Controllers.Match; +using DorkNet.Server.Data; +using DorkNet.Server.Data.Entities; +using Microsoft.EntityFrameworkCore; + +namespace DorkNet.Server.Services; + +/// +/// Cross-replica registry of invite-only matches created via +/// POST /goto/room/{name}?CreatePrivateInstance=True. +/// +/// Why this exists: the watch sets CreatePrivateInstance=True in +/// the matchmaking form when the player picks "Private Instance". Our +/// /goto handler responds with a unique photonRoomId so randos +/// can't matchmake into it. Invitees later POST +/// /goto/instance/{roomInstanceId} carrying the inviter's +/// instance id; that handler looks up the registered photon room +/// here so they end up in the same Photon match instead of being +/// routed to a fresh public lobby. +/// +/// **Multi-replica**: state lives in two Postgres tables — +/// for the instance metadata and +/// for the per-invite +/// access list. Pre-PR-3 used ConcurrentDictionary singletons +/// which split across replicas: an invite issued on replica A wasn't +/// visible to a goto/instance hitting replica B. +/// +/// Service is now scoped rather than singleton because it +/// holds a DbContext reference. The previous singleton lifetime would +/// have leaked the DbContext for the lifetime of the app. +/// +public class PrivateInstanceService(DorkNetDbContext db) +{ + /// Generate a fresh roomInstanceId + photonRoomId + /// pair for a private match and persist the row. Caller is responsible + /// for plumbing the result onto the outgoing . + public async Task RegisterAsync( + long roomId, long subRoomId, long ownerPlayerId, + string baseName, string location, string dataBlob, string photonRegion, int maxCapacity) + { + // Random component so the same player can have multiple + // concurrent private instances of the same room (e.g. one in + // the lobby, one in a sub-room) — without it the photon room + // ids would collide and the second instance would silently + // matchmake into the first. + var nonce = Random.Shared.Next(0x10000, 0xFFFFFF); + // Pack roomId/subId/ownerId into the high bits of the instance + // id so it's unique-by-construction across rooms+players. The + // low 24 bits carry the nonce so multiple concurrent instances + // by the same player still get distinct ids. Stays inside int63 + // for safe LitJson marshalling on the watch. + var instanceId = (long)(((ulong)(uint)nonce & 0xFFFFFFul) + ^ ((ulong)roomId << 24) + ^ ((ulong)subRoomId << 40) + ^ ((ulong)(uint)ownerPlayerId << 48)); + if (instanceId < 0) instanceId = -instanceId; + var photonRoomId = $"^{baseName}_{roomId}_sub{subRoomId}_priv{ownerPlayerId}_{nonce:x6}"; + + var entity = new PrivateInstanceEntity + { + Id = instanceId, + RoomId = roomId, + SubRoomId = subRoomId, + OwnerPlayerId = ownerPlayerId, + PhotonRoomId = photonRoomId, + Location = location, + DataBlob = dataBlob, + PhotonRegion = photonRegion, + MaxCapacity = maxCapacity, + Name = baseName, + CreatedAt = DateTime.UtcNow, + }; + db.PrivateInstances.Add(entity); + await db.SaveChangesAsync(); + return ToRecord(entity); + } + + public async Task GetAsync(long instanceId) + { + var e = await db.PrivateInstances.AsNoTracking() + .FirstOrDefaultAsync(p => p.Id == instanceId); + return e is null ? null : ToRecord(e); + } + + /// + /// Idempotent registration of a player's dorm as a private instance. + /// Dorms are always owner-only by design — anyone reaching the + /// dorm's PhotonRoom name uninvited would otherwise be able to + /// drop in. We mark every dorm IsPrivate=true and back it + /// with a deterministic-id PrivateInstance row so + /// works the same way it does for + /// player-created private matches: owner always allowed, everyone + /// else needs an explicit invite. + /// + /// The id is derived from (ownerPlayerId, roomId, subRoomId) + /// with a fixed high-bit marker so it never collides with the + /// random-nonce ids produced by . Reusing + /// the same id across repeat /goto/room/DormRoom calls keeps + /// the registry from accumulating one row per dorm join. + /// + public async Task EnsureForDormAsync( + long ownerPlayerId, long roomId, long subRoomId, + string baseName, string location, string dataBlob, + string photonRegion, int maxCapacity, string photonRoomId) + { + // Deterministic-and-distinct from RegisterAsync's nonce layout. + // Marker 0xDD0000 in the low 24 bits separates dorms from + // player-created private matches so the id spaces never alias. + var instanceId = (long)( + 0xDD0000ul + ^ ((ulong)(uint)roomId << 24) + ^ ((ulong)(uint)subRoomId << 40) + ^ ((ulong)(uint)ownerPlayerId << 48)); + if (instanceId < 0) instanceId = -instanceId; + + var existing = await db.PrivateInstances + .FirstOrDefaultAsync(p => p.Id == instanceId); + if (existing is not null) + { + // Keep PhotonRoomId / DataBlob / PhotonRegion current. The + // OWNER's most recent /goto/room/DormRoom is the source of + // truth for which Photon region their dorm lives in — if + // we serve invitees a stale region (because Photon:CloudRegion + // config changed since the row was first written), the + // invitee's watch connects to a different Photon master and + // joining "^dormroom_p{owner}" there creates a SEPARATE room + // from where the owner is. Both watches report Photon + // joinresult=Success but never see each other in-game — + // exact symptom 1362428/1811750 hit: same server-side + // instance id, two parallel Photon rooms. + var changed = false; + if (existing.PhotonRoomId != photonRoomId) { existing.PhotonRoomId = photonRoomId; changed = true; } + if (existing.DataBlob != dataBlob) { existing.DataBlob = dataBlob; changed = true; } + if (existing.Location != location) { existing.Location = location; changed = true; } + if (existing.PhotonRegion != photonRegion) { existing.PhotonRegion = photonRegion; changed = true; } + if (changed) await db.SaveChangesAsync(); + return ToRecord(existing); + } + + var entity = new PrivateInstanceEntity + { + Id = instanceId, + RoomId = roomId, + SubRoomId = subRoomId, + OwnerPlayerId = ownerPlayerId, + PhotonRoomId = photonRoomId, + Location = location, + DataBlob = dataBlob, + PhotonRegion = photonRegion, + MaxCapacity = maxCapacity, + Name = baseName, + CreatedAt = DateTime.UtcNow, + }; + db.PrivateInstances.Add(entity); + try + { + await db.SaveChangesAsync(); + } + catch (DbUpdateException) + { + // Concurrent insert from another replica raced us to the + // same deterministic id — re-fetch the winning row. + db.Entry(entity).State = EntityState.Detached; + var raced = await db.PrivateInstances.AsNoTracking() + .FirstAsync(p => p.Id == instanceId); + return ToRecord(raced); + } + return ToRecord(entity); + } + + public async Task RemoveAsync(long instanceId) + { + // Cascade-delete invitees first so we don't leave orphan rows + // referencing a non-existent instance. + await db.PrivateInstanceInvitees + .Where(i => i.PrivateInstanceId == instanceId) + .ExecuteDeleteAsync(); + await db.PrivateInstances + .Where(p => p.Id == instanceId) + .ExecuteDeleteAsync(); + } + + /// Add a player to the invite list for a private instance. + /// Idempotent on (instance, player) — repeat invites refresh + /// LatestInviteMessageId so the most-recent invite's + /// message id is always recoverable for /goto/invite resolution. + /// Returns false if the instance isn't registered (probably + /// expired / wrong id). + public async Task InviteAsync(long instanceId, long inviteePlayerId, long? messageId = null) + { + if (!await db.PrivateInstances.AnyAsync(p => p.Id == instanceId)) return false; + var existing = await db.PrivateInstanceInvitees + .FirstOrDefaultAsync(i => i.PrivateInstanceId == instanceId && i.PlayerId == inviteePlayerId); + if (existing is not null) + { + // Refresh the latest-invite pointer if this call carries + // a new messageId — the watch may send several invites in + // a row, and /goto/invite needs the freshest one. + if (messageId is long m && existing.LatestInviteMessageId != m) + { + existing.LatestInviteMessageId = m; + await db.SaveChangesAsync(); + } + return true; + } + db.PrivateInstanceInvitees.Add(new PrivateInstanceInviteeEntity + { + PrivateInstanceId = instanceId, + PlayerId = inviteePlayerId, + InvitedAt = DateTime.UtcNow, + LatestInviteMessageId = messageId, + }); + await db.SaveChangesAsync(); + return true; + } + + /// Drop an invitee from a private instance's allow-list. + /// Used when the invitee declines via + /// DELETE match.*/invite/{messageId} — without this, an + /// invitee who clicks "decline" then changes their mind could + /// still join the instance without a fresh invite. No-op if the + /// invite never existed. + public async Task RevokeInviteAsync(long instanceId, long inviteePlayerId) + { + await db.PrivateInstanceInvitees + .Where(i => i.PrivateInstanceId == instanceId && i.PlayerId == inviteePlayerId) + .ExecuteDeleteAsync(); + } + + /// True when is the owner of + /// the instance OR has been explicitly invited. The owner is + /// implicitly allowed even without an Invite() call so they can + /// rejoin their own private match after disconnects. + public async Task CanJoinAsync(long instanceId, long playerId) + { + var ownerId = await db.PrivateInstances.AsNoTracking() + .Where(p => p.Id == instanceId) + .Select(p => (long?)p.OwnerPlayerId) + .FirstOrDefaultAsync(); + if (ownerId is null) return false; + if (ownerId == playerId) return true; + return await db.PrivateInstanceInvitees.AsNoTracking() + .AnyAsync(i => i.PrivateInstanceId == instanceId && i.PlayerId == playerId); + } + + private static PrivateInstance ToRecord(PrivateInstanceEntity e) => new( + InstanceId: e.Id, + RoomId: e.RoomId, + SubRoomId: e.SubRoomId, + OwnerPlayerId: e.OwnerPlayerId, + PhotonRoomId: e.PhotonRoomId, + Location: e.Location, + DataBlob: e.DataBlob, + PhotonRegion: e.PhotonRegion, + MaxCapacity: e.MaxCapacity, + Name: e.Name, + CreatedAt: DateTime.SpecifyKind(e.CreatedAt, DateTimeKind.Utc)); + + public sealed record PrivateInstance( + long InstanceId, + long RoomId, + long SubRoomId, + long OwnerPlayerId, + string PhotonRoomId, + string Location, + string DataBlob, + string PhotonRegion, + int MaxCapacity, + string Name, + DateTime CreatedAt); +} diff --git a/DorkNet.Server/Services/ProfanityFilter.cs b/DorkNet.Server/Services/ProfanityFilter.cs new file mode 100644 index 0000000..c64764f --- /dev/null +++ b/DorkNet.Server/Services/ProfanityFilter.cs @@ -0,0 +1,92 @@ +namespace DorkNet.Server.Services; + +/// +/// Minimal word-list profanity filter for +/// api/sanitize/v1/purifyString + requestIsStringPure. +/// Deliberately conservative — only catches the obvious slur / NSFW +/// terms a 12-year-old would type. Real Rec Room used a third-party +/// service (WebPurify); we match the API surface, not the depth. +/// +/// To extend: add words to . The filter is +/// case-insensitive and matches whole-word + leetspeak variants +/// (4→a, 0→o, 1→i, 3→e, 5→s, 7→t). +/// +public static class ProfanityFilter +{ + /// Lowercase canonical forms. Keep this list short + /// and curate manually — false positives are worse than + /// misses for a private friends-only server. + private static readonly string[] BadWords = + { + "fuck", "shit", "bitch", "asshole", "cunt", "nigger", + "faggot", "retard", "rape", "kys", + }; + + /// Returns true if the string contains no detectable + /// profanity. The wire response is a JSON primitive bool. + public static bool IsClean(string? s) + { + if (string.IsNullOrWhiteSpace(s)) return true; + var norm = Normalize(s); + foreach (var bad in BadWords) + { + if (norm.Contains(bad)) return false; + } + return true; + } + + /// Replaces detected profanity with asterisks of the + /// same length. Returns the original string when clean. + public static string Purify(string? s) + { + if (string.IsNullOrWhiteSpace(s)) return s ?? string.Empty; + var result = s; + var lower = s.ToLowerInvariant(); + var leet = Normalize(s); + foreach (var bad in BadWords) + { + // Replace by lowercase first (preserves original casing + // for surrounding text) — then leet-form. We only + // replace contiguous matches; partial overlaps stay. + var idx = lower.IndexOf(bad, StringComparison.Ordinal); + while (idx >= 0) + { + result = string.Concat( + result.AsSpan(0, idx), + new string('*', bad.Length), + result.AsSpan(idx + bad.Length)); + lower = string.Concat( + lower.AsSpan(0, idx), + new string('*', bad.Length), + lower.AsSpan(idx + bad.Length)); + idx = lower.IndexOf(bad, idx + bad.Length, StringComparison.Ordinal); + } + var leetIdx = leet.IndexOf(bad, StringComparison.Ordinal); + while (leetIdx >= 0) + { + result = string.Concat( + result.AsSpan(0, leetIdx), + new string('*', bad.Length), + result.AsSpan(leetIdx + bad.Length)); + leet = string.Concat( + leet.AsSpan(0, leetIdx), + new string('*', bad.Length), + leet.AsSpan(leetIdx + bad.Length)); + leetIdx = leet.IndexOf(bad, leetIdx + bad.Length, StringComparison.Ordinal); + } + } + return result; + } + + /// Lowercase + de-leetspeak so common substitutions + /// (a→4, e→3, i→1, o→0, s→5, t→7) don't slip past the filter. + private static string Normalize(string s) => s.ToLowerInvariant() + .Replace('4', 'a') + .Replace('0', 'o') + .Replace('1', 'i') + .Replace('3', 'e') + .Replace('5', 's') + .Replace('7', 't') + .Replace('@', 'a') + .Replace('$', 's'); +} diff --git a/DorkNet.Server/Services/RoomBlobNormalizerService.cs b/DorkNet.Server/Services/RoomBlobNormalizerService.cs new file mode 100644 index 0000000..b27c983 --- /dev/null +++ b/DorkNet.Server/Services/RoomBlobNormalizerService.cs @@ -0,0 +1,443 @@ +using Google.Protobuf; +using RecRoom.Protobuf2020; + +namespace DorkNet.Server.Services; + +/// +/// Normalises an uploaded .room protobuf blob so the 2020.12 +/// watch can actually render it. +/// +/// Two passes: +/// +/// 1. Quaternion → Euler projection. Modern Rec Room (2024+) +/// writes every TransformData's rotation as a quaternion +/// at field 6 (quaternion_rotation, a +/// core.QuaternionData sub-message), leaving the legacy +/// Euler Vector3 at field 2 empty. The 2020.12 watch reads only +/// field 2 — so every shape, gizmo, and child view loads at +/// rotation=(0,0,0). The result is a room whose pieces +/// are at the right positions but pointing the wrong way: trains +/// floating off tracks, walls turned 90° wrong, panels facing +/// the floor, etc. (Verified empirically against several Studio- +/// imported scenes vs. the working Rockulator.) +/// +/// We walk PersistedRoomData.persistence_views, recurse +/// into each PersistenceViewData.transform and +/// child_views[].data.transform, and when we find a +/// quaternion_rotation field with no +/// rotation Euler set, convert the quaternion to Euler +/// degrees (Unity convention, Tait-Bryan ZXY) and emit it as +/// field 2. +/// +/// 2. Round-trip through the 2020 schema. After the +/// projection pass, parse the bytes with +/// RecRoom.Protobuf2020.PersistedRoomData.Parser and +/// re-serialise. Strips non-canonical encodings that the +/// stricter 2020 parser sometimes rejected and ensures the +/// output is exactly what the watch expects on the wire. +/// +/// On parse failure we hand the original bytes back unchanged — +/// better to upload a blob that MAY work than to drop the user's +/// content. Callers (the admin import endpoint) log the outcome. +/// +public sealed class RoomBlobNormalizerService(ILogger logger) +{ + public sealed record Result(byte[] Bytes, bool Normalized, int InputBytes, int OutputBytes, string? Error, int QuaternionsProjected); + + public Result Normalize(byte[] input) + { + if (input is null || input.Length == 0) + return new Result(Array.Empty(), false, 0, 0, "empty_input", 0); + + try + { + // Pass 1: project quaternion rotations to Euler so the + // 2020.12 watch's TransformData.rotation deserialiser + // sees something at field 2. + var (projected, quatsProjected) = ProjectQuaternionsToEulerOnTransforms(input); + + // Pass 2: canonical re-encode via the 2020 schema. + var msg = PersistedRoomData.Parser.ParseFrom(projected); + var output = msg.ToByteArray(); + logger.LogInformation( + "[normalize] OK quats_projected={Quats} in={In:N0} bytes, after_projection={Mid:N0} bytes, out={Out:N0} bytes ({Pct:F2}%)", + quatsProjected, input.Length, projected.Length, output.Length, (double)output.Length / input.Length * 100); + return new Result(output, true, input.Length, output.Length, null, quatsProjected); + } + catch (InvalidProtocolBufferException ex) + { + logger.LogWarning( + "[normalize] parse FAIL ({Bytes:N0} bytes): {Message} — passing original bytes through", + input.Length, ex.Message); + return new Result(input, false, input.Length, input.Length, ex.Message, 0); + } + catch (Exception ex) + { + logger.LogWarning(ex, + "[normalize] unexpected exception ({Bytes:N0} bytes) — passing original bytes through", + input.Length); + return new Result(input, false, input.Length, input.Length, ex.Message, 0); + } + } + + // ── Wire-format rewriter ────────────────────────────────────────── + // + // Doing this through google.protobuf would require either compiling + // the modern proto schema as a parallel C# namespace OR using + // reflection + UnknownFieldSet hacks. Neither is appealing — + // straight wire-format manipulation is ~100 lines and surgical. + // + // Protobuf wire format primer: + // tag = (field_number << 3) | wire_type, encoded as varint. + // wire_type 0 = varint, 1 = fixed64, 2 = length-delimited, + // 5 = fixed32. + // Length-delimited values are prefixed with a varint length. To + // edit children we have to recompute the length after editing, + // so we accumulate children into a per-level MemoryStream and + // emit `tag + length + payload` once we know the final byte count. + + private const int FIELD_PERSISTED_ROOM_DATA_PERSISTENCE_VIEWS = 2; + private const int FIELD_PERSISTENCE_VIEW_DATA_CHILD_VIEWS = 3; + private const int FIELD_PERSISTENCE_VIEW_DATA_TRANSFORM = 10; + private const int FIELD_CHILD_PERSISTENCE_VIEW_DATA_DATA = 2; + private const int FIELD_TRANSFORM_ROTATION_EULER = 2; + private const int FIELD_TRANSFORM_QUATERNION_ROTATION = 6; + private const int FIELD_QUATERNION_W = 1; + private const int FIELD_QUATERNION_X = 2; + private const int FIELD_QUATERNION_Y = 3; + private const int FIELD_QUATERNION_Z = 4; + + private static (byte[] Bytes, int QuatsProjected) ProjectQuaternionsToEulerOnTransforms(byte[] input) + { + using var ms = new MemoryStream(); + int quatsProjected = 0; + RewriteRoot(input, 0, input.Length, ms, ref quatsProjected); + return (ms.ToArray(), quatsProjected); + } + + private static void RewriteRoot(byte[] buf, int pos, int end, Stream output, ref int quatsProjected) + { + while (pos < end) + { + ulong tag = ReadVarint(buf, ref pos); + int field = (int)(tag >> 3); + int wire = (int)(tag & 0x07); + + if (wire == 2 && field == FIELD_PERSISTED_ROOM_DATA_PERSISTENCE_VIEWS) + { + int len = (int)ReadVarint(buf, ref pos); + using var childMs = new MemoryStream(); + RewritePersistenceView(buf, pos, pos + len, childMs, ref quatsProjected); + EmitLengthDelimited(output, field, childMs.ToArray()); + pos += len; + } + else + { + CopyField(buf, ref pos, wire, output, tag); + } + } + } + + private static void RewritePersistenceView(byte[] buf, int pos, int end, Stream output, ref int quatsProjected) + { + while (pos < end) + { + ulong tag = ReadVarint(buf, ref pos); + int field = (int)(tag >> 3); + int wire = (int)(tag & 0x07); + + if (wire == 2 && field == FIELD_PERSISTENCE_VIEW_DATA_TRANSFORM) + { + int len = (int)ReadVarint(buf, ref pos); + using var childMs = new MemoryStream(); + RewriteTransform(buf, pos, pos + len, childMs, ref quatsProjected); + EmitLengthDelimited(output, field, childMs.ToArray()); + pos += len; + } + else if (wire == 2 && field == FIELD_PERSISTENCE_VIEW_DATA_CHILD_VIEWS) + { + int len = (int)ReadVarint(buf, ref pos); + using var childMs = new MemoryStream(); + RewriteChildPersistenceView(buf, pos, pos + len, childMs, ref quatsProjected); + EmitLengthDelimited(output, field, childMs.ToArray()); + pos += len; + } + else + { + CopyField(buf, ref pos, wire, output, tag); + } + } + } + + private static void RewriteChildPersistenceView(byte[] buf, int pos, int end, Stream output, ref int quatsProjected) + { + // ChildPersistenceViewData has field 1 (child_id, varint) and + // field 2 (data, a nested PersistenceViewData). + while (pos < end) + { + ulong tag = ReadVarint(buf, ref pos); + int field = (int)(tag >> 3); + int wire = (int)(tag & 0x07); + + if (wire == 2 && field == FIELD_CHILD_PERSISTENCE_VIEW_DATA_DATA) + { + int len = (int)ReadVarint(buf, ref pos); + using var childMs = new MemoryStream(); + RewritePersistenceView(buf, pos, pos + len, childMs, ref quatsProjected); + EmitLengthDelimited(output, field, childMs.ToArray()); + pos += len; + } + else + { + CopyField(buf, ref pos, wire, output, tag); + } + } + } + + private static void RewriteTransform(byte[] buf, int pos, int end, Stream output, ref int quatsProjected) + { + // Pass 1 over the TransformData: detect whether field 2 (Euler) + // is set and whether field 6 (quaternion) is set. If we have + // a quaternion but no Euler, we'll synthesise an Euler at the + // end and skip the quaternion field on the second pass. + bool hasEuler = false; + bool hasQuat = false; + float qw = 1, qx = 0, qy = 0, qz = 0; + + int scanPos = pos; + while (scanPos < end) + { + ulong tag = ReadVarint(buf, ref scanPos); + int field = (int)(tag >> 3); + int wire = (int)(tag & 0x07); + if (wire == 2) + { + int len = (int)ReadVarint(buf, ref scanPos); + if (field == FIELD_TRANSFORM_ROTATION_EULER) hasEuler = true; + else if (field == FIELD_TRANSFORM_QUATERNION_ROTATION) + { + hasQuat = true; + ReadQuaternion(buf, scanPos, scanPos + len, out qw, out qx, out qy, out qz); + } + scanPos += len; + } + else + { + SkipField(buf, ref scanPos, wire); + } + } + + bool shouldProject = hasQuat && !hasEuler; + + // Pass 2: re-emit the TransformData. Copy every field through + // unchanged EXCEPT the quaternion (field 6) which we drop when + // we're going to synthesise an Euler from it. + int rewritePos = pos; + while (rewritePos < end) + { + int fieldStart = rewritePos; + ulong tag = ReadVarint(buf, ref rewritePos); + int field = (int)(tag >> 3); + int wire = (int)(tag & 0x07); + + if (shouldProject && field == FIELD_TRANSFORM_QUATERNION_ROTATION) + { + // Skip — we're replacing it. + SkipField(buf, ref rewritePos, wire); + continue; + } + // Re-emit field unchanged (tag + value bytes). + rewritePos = fieldStart; + CopyFieldWithTag(buf, ref rewritePos, output); + } + + if (shouldProject) + { + var (ex, ey, ez) = QuaternionToUnityEulerDegrees(qx, qy, qz, qw); + using var eulerMs = new MemoryStream(); + // Vector3Data: float x = 1, y = 2, z = 3 (all fixed32 wire type 5). + WriteFloat(eulerMs, 1, ex); + WriteFloat(eulerMs, 2, ey); + WriteFloat(eulerMs, 3, ez); + EmitLengthDelimited(output, FIELD_TRANSFORM_ROTATION_EULER, eulerMs.ToArray()); + quatsProjected++; + } + } + + private static void ReadQuaternion(byte[] buf, int pos, int end, out float w, out float x, out float y, out float z) + { + w = 1; x = 0; y = 0; z = 0; + while (pos < end) + { + ulong tag = ReadVarint(buf, ref pos); + int field = (int)(tag >> 3); + int wire = (int)(tag & 0x07); + if (wire == 5) // fixed32 = float + { + float v = BitConverter.ToSingle(buf, pos); + pos += 4; + switch (field) + { + case FIELD_QUATERNION_W: w = v; break; + case FIELD_QUATERNION_X: x = v; break; + case FIELD_QUATERNION_Y: y = v; break; + case FIELD_QUATERNION_Z: z = v; break; + } + } + else + { + SkipField(buf, ref pos, wire); + } + } + } + + // Unity uses the ZXY Tait-Bryan convention for Quaternion.eulerAngles. + // Formula adapted from Unity's source (Quaternion → euler in + // degrees), with a clamped asin to guard against floating-point + // values just outside [-1, 1] from non-unit quaternions. + private static (float x, float y, float z) QuaternionToUnityEulerDegrees(float qx, float qy, float qz, float qw) + { + const double rad2deg = 180.0 / Math.PI; + + double sinrCosp = 2.0 * (qw * qx + qy * qz); + double cosrCosp = 1.0 - 2.0 * (qx * qx + qy * qy); + double xRad = Math.Atan2(sinrCosp, cosrCosp); + + double sinp = 2.0 * (qw * qy - qz * qx); + if (sinp > 1.0) sinp = 1.0; else if (sinp < -1.0) sinp = -1.0; + double yRad = Math.Asin(sinp); + + double sinyCosp = 2.0 * (qw * qz + qx * qy); + double cosyCosp = 1.0 - 2.0 * (qy * qy + qz * qz); + double zRad = Math.Atan2(sinyCosp, cosyCosp); + + return ((float)(xRad * rad2deg), (float)(yRad * rad2deg), (float)(zRad * rad2deg)); + } + + // ── Varint + field helpers ──────────────────────────────────────── + + private static ulong ReadVarint(byte[] buf, ref int pos) + { + ulong val = 0; + int shift = 0; + while (true) + { + byte b = buf[pos++]; + val |= (ulong)(b & 0x7F) << shift; + if ((b & 0x80) == 0) return val; + shift += 7; + if (shift >= 64) throw new InvalidDataException("varint too long"); + } + } + + private static void WriteVarint(Stream s, ulong v) + { + while (v >= 0x80) + { + s.WriteByte((byte)(v | 0x80)); + v >>= 7; + } + s.WriteByte((byte)v); + } + + private static void EmitLengthDelimited(Stream s, int field, byte[] payload) + { + ulong tag = ((ulong)field << 3) | 2; + WriteVarint(s, tag); + WriteVarint(s, (ulong)payload.Length); + s.Write(payload, 0, payload.Length); + } + + private static void WriteFloat(Stream s, int field, float value) + { + ulong tag = ((ulong)field << 3) | 5; + WriteVarint(s, tag); + var bytes = BitConverter.GetBytes(value); + s.Write(bytes, 0, 4); + } + + private static void SkipField(byte[] buf, ref int pos, int wire) + { + switch (wire) + { + case 0: ReadVarint(buf, ref pos); break; + case 1: pos += 8; break; + case 2: { int n = (int)ReadVarint(buf, ref pos); pos += n; break; } + case 5: pos += 4; break; + default: throw new InvalidDataException($"unsupported wire type {wire}"); + } + } + + /// Copy a single field's value (post-tag) verbatim from + /// to , advancing + /// past the value. + /// is the already-decoded wire tag the caller already consumed + /// from the input — we re-emit it to + /// before the value bytes. + private static void CopyField(byte[] buf, ref int pos, int wire, Stream output, ulong tag) + { + WriteVarint(output, tag); + switch (wire) + { + case 0: // varint + { + int start = pos; + ReadVarint(buf, ref pos); + output.Write(buf, start, pos - start); + break; + } + case 1: // fixed64 + output.Write(buf, pos, 8); pos += 8; break; + case 2: // length-delimited + { + int lengthStart = pos; + int len = (int)ReadVarint(buf, ref pos); + output.Write(buf, lengthStart, pos - lengthStart); // length varint + output.Write(buf, pos, len); + pos += len; + break; + } + case 5: // fixed32 + output.Write(buf, pos, 4); pos += 4; break; + default: + throw new InvalidDataException($"unsupported wire type {wire}"); + } + } + + /// Copy an entire field (tag + value) verbatim to + /// . Used when re-emitting fields we + /// didn't decide to rewrite — we re-read the tag from + /// rather than pass it through, which makes + /// the call site cleaner. + private static void CopyFieldWithTag(byte[] buf, ref int pos, Stream output) + { + int tagStart = pos; + ulong tag = ReadVarint(buf, ref pos); + int wire = (int)(tag & 0x07); + // Write tag verbatim from original bytes (cheaper than re-encoding). + output.Write(buf, tagStart, pos - tagStart); + switch (wire) + { + case 0: + { + int start = pos; + ReadVarint(buf, ref pos); + output.Write(buf, start, pos - start); + break; + } + case 1: output.Write(buf, pos, 8); pos += 8; break; + case 2: + { + int lengthStart = pos; + int len = (int)ReadVarint(buf, ref pos); + output.Write(buf, lengthStart, pos - lengthStart); + output.Write(buf, pos, len); + pos += len; + break; + } + case 5: output.Write(buf, pos, 4); pos += 4; break; + default: + throw new InvalidDataException($"unsupported wire type {wire}"); + } + } +} diff --git a/DorkNet.Server/Services/RoomCodeService.cs b/DorkNet.Server/Services/RoomCodeService.cs new file mode 100644 index 0000000..fa7dee2 --- /dev/null +++ b/DorkNet.Server/Services/RoomCodeService.cs @@ -0,0 +1,21 @@ +namespace DorkNet.Server.Services; + +public static class RoomCodeService +{ + private const string Alphabet = "23456789ABCDEFGHJKLMNPQRSTUVWXYZ"; + + public static string Normalize(string? code) + => (code ?? string.Empty).Trim().Replace("-", string.Empty).ToUpperInvariant(); + + public static string Generate(long instanceId) + { + var value = (ulong)HashCode.Combine(instanceId, 0x52_52_4d_43); + Span chars = stackalloc char[6]; + for (var i = 0; i < chars.Length; i++) + { + chars[i] = Alphabet[(int)(value % (uint)Alphabet.Length)]; + value /= (uint)Alphabet.Length; + } + return new string(chars); + } +} diff --git a/DorkNet.Server/Services/RoomDataBlobService.cs b/DorkNet.Server/Services/RoomDataBlobService.cs new file mode 100644 index 0000000..986dc3d --- /dev/null +++ b/DorkNet.Server/Services/RoomDataBlobService.cs @@ -0,0 +1,111 @@ +using Google.Protobuf; +using RecRoom.Protobuf; + +namespace DorkNet.Server.Services; + +/// +/// Builds the binary `PersistedRoomData` protobuf the client downloads when +/// it loads a room scene. The 2020 RecRoom client gates the Maker Pen, +/// invention spawning, room-role editing and a dozen other tools behind +/// `RoomRoleCollectionData.RoomRoles[i].CanUseMakerPen.InnerValue` etc., +/// all of which default to `false` if the bytes we serve are empty. +/// +/// We emit a minimal blob with one PlayerRoomRoleData entry that flips +/// every relevant `OverridableBoolData` to `(overrides=true, inner_value=true)`, +/// which the client picks up regardless of which RoomRoleId the local +/// player ends up assigned to. Net result: the room's creator (and +/// everyone else, since this is a private server) gets all build/admin +/// tools the moment they spawn into the scene. +/// +/// Wire compatibility: our trimmed .proto only defines the messages and +/// field numbers we set — the 2020 client's full PersistedRoomData has +/// many more fields, but proto3 silently ignores unknown wire entries and +/// defaults missing ones, so the bytes we generate deserialize cleanly. +/// +public class RoomDataBlobService +{ + /// + /// Cached bytes of the "all-permissions-on" blob. Identical for every + /// room — there's no per-room state we vary yet, so building once at + /// startup beats rebuilding on every download request. + /// + private readonly byte[] _allPermsBlob = BuildAllPermsBlob(); + + public byte[] GetDefaultBlob() => _allPermsBlob; + + private static byte[] BuildAllPermsBlob() + { + // OverridableBoolData(overrides=true, inner_value=true) — used for + // every Can* field on the role. + OverridableBoolData OverrideTrue() => new() + { + Overrides = true, + InnerValue = true, + }; + + var role = new PlayerRoomRoleData + { + // Identity fields — these are deprecated in the 2026 schema + // but the 2020 client still reads them. role_id=0 = AG_EVERYONE + // which the client treats as "applies to all players in an AG + // room"; combined with all permissions overridden true, every + // player slot inherits maker pen. + DEPRECATEDRoleId = 0, + DEPRECATEDRoleRank = 100, + DEPRECATEDIsRoleActive = true, + DEPRECATEDIsAgRole = true, + RoleName = "Creator", + RoleVersion = 1, + RoleGuid = Guid.NewGuid().ToString(), + + // Display name override — keeps the watch's role-list UI from + // showing a blank label. + Name = new OverridableStringData + { + Overrides = true, + InnerValue = "Creator", + }, + + // Permission grants. Each Overridable*Data(overrides=true, + // inner_value=true) tells the client "the room has explicitly + // configured this permission to true". Without overrides=true + // the client falls back to its own defaults, which for most + // perms is false. + CanAssignRoles = OverrideTrue(), + CanInvite = OverrideTrue(), + CanStartGames = OverrideTrue(), + CanTalk = OverrideTrue(), + CanPrintPhotos = OverrideTrue(), + CanEditRoomRoles = OverrideTrue(), + CanSelfRevive = OverrideTrue(), + CanEndGamesEarly = OverrideTrue(), + CanChangeGameMode = OverrideTrue(), + CanUseMakerPen = OverrideTrue(), + CanUseDeleteAllButton = OverrideTrue(), + CanSaveInventions = OverrideTrue(), + DisableMicAutoMute = OverrideTrue(), + CanUseShareCam = OverrideTrue(), + + // Vote-kick permission — int field, set to a permissive value. + // 0 = AnyoneCanVoteKickAnyone in the 2020 enum (verified via + // VoteKickPermission disasm). overrides=true to make sure the + // client respects it rather than falling back to a stricter + // default. + VoteKickPermission = new OverridableIntData + { + Overrides = true, + InnerValue = 0, + }, + }; + + var collection = new RoomRoleCollectionData(); + collection.RoomRoles.Add(role); + + var msg = new PersistedRoomData + { + RoomRoleData = collection, + }; + + return msg.ToByteArray(); + } +} diff --git a/DorkNet.Server/Services/RoomService.cs b/DorkNet.Server/Services/RoomService.cs new file mode 100644 index 0000000..d06a96d --- /dev/null +++ b/DorkNet.Server/Services/RoomService.cs @@ -0,0 +1,1045 @@ +using Microsoft.EntityFrameworkCore; +using DorkNet.Server.Data; +using DorkNet.Server.Data.Entities; + +namespace DorkNet.Server.Services; + +/// +/// Backs the Rooms watch tab — search, hot, by-name, by-id, my-rooms, +/// my-bookmarks, room details. Also seeds the canonical "Rec Room +/// Original" rooms on first DB creation so the watch isn't empty. +/// +public class RoomService(DorkNetDbContext db) +{ + /// + /// Idempotent seed of the well-known Rec Room Original rooms — pulled + /// from the AGRoomRuntimeConfig.Locations array we walked in + /// resources.assets. Each entry maps a friendly name (the URL segment + /// used by /goto/room/{name}) to the LocationReplicationId GUID the + /// client expects in MatchmakingResponse.RoomInstance.location. + /// + /// Seeded rooms are: + /// - public (Accessibility=1) + /// - AG (IsAGRoom=true — they are baked into the client's resources.assets, + /// not user-created; the client's matchmaking gates require the AG flag + /// to use the right join path) + /// - tagged appropriately so the watch's hot/search filters surface them + /// - high HotScore so they sort to the top of "Trending" + /// + public async Task SeedAsync() + { + // Idempotent fix-up: an earlier seed shipped these rooms with + // IsAGRoom=false, which broke the rec center join (client returned + // MatchmakingErrorCode.InsufficientSpace because the response shape + // didn't match an AG instance). Patch any existing seeded rows that + // still have the old flag instead of forcing a DB wipe. + await db.Rooms + .Where(r => r.Id >= 100 && r.Id < 1000 && !r.IsAGRoom && !r.IsDormRoom) + .ExecuteUpdateAsync(s => s.SetProperty(r => r.IsAGRoom, true)); + + // Backfill: existing DBs from before image thumbnails were + // seeded have ImageName="" — fill them in by name so the watch + // tile renders something rather than the broken-image fallback. + // Idempotent: setting an existing image again is a no-op. + await BackfillSeededImagesAsync(); + + // Backfill: rooms predating Phase20RoomVisitsAndStats have + // VisitorCount=0 even when VisitCount is seeded high. Estimate + // unique visitors as ~1/3 of total joins (the official Rec.Net + // ratio for a popular room); admin dashboards stay coherent + // until live traffic catches up via RoomVisitEntity upserts. + await BackfillVisitorCountAsync(); + + if (await db.Rooms.AnyAsync()) + { + // Seeds already ran — but earlier versions of the seed + // didn't include all rooms. Add the missing ones. + await EnsureMissingSeededRoomsAsync(); + return; + } + + // Tags stored WITHOUT leading '#'. The watch UI prepends '#' when + // rendering chips, so storing "#recroomoriginal" produces double-# + // chips like "##recroomoriginal". + // + // ImageName is the cdn filename the watch downloads as the room + // tile thumbnail (the small square card that shows up on the + // "Trending"/"Rooms" tabs and on each room's detail page). The + // watch prepends `https://cdn.rec.net/` and downloads as PNG. + // We seed with the well-known RR room thumbnail filenames so the + // catch-all CDN fallback returns the all-perms blob (an empty/ + // zero-byte image) and the watch falls back to the room's name + // tile rather than crashing on a malformed thumbnail. + var seeded = new (string Slug, string DisplayName, string Description, string LocationId, string Tags, string Image)[] + { + // All LocationReplicationIds below are AGRoomRuntimeConfig.Locations[].ReplicationId + // values extracted from the 2020.03.06 client's resources.assets via + // tools/extract-locations-binary.py. Output cached at data/room_locations.json. + // Each Location has a SceneName matching one of the .unity scenes the client + // ships (see tools/dump-all-scenes.py for the 96-scene index). + ("RecCenter", "Rec Center", "A social hub to meet and mingle with friends new and old.", "cbad71af-0831-44d8-b8ef-69edafa841f6", "recroomoriginal,featured", "image_RecCenter.png"), + ("3DCharades", "3D Charades", "Take turns drawing, acting, and guessing funny phrases with your friends!", "4078dfed-24bb-4db7-863f-578ba48d726b", "recroomoriginal", "image_3DCharades.png"), + ("DiscGolfLake", "DiscGolf Lake", "A leisurely stroll through the grass. Throw your disc into the goal.", "f6f7256c-e438-4299-b99e-d20bef8cf7e0", "recroomoriginal,sport", "image_DiscGolfLake.png"), + ("DiscGolfPropulsion","DiscGolf Propulsion","Throw your disc through hazards and around wind machines on this challenging course!", "d9378c9f-80bc-46fb-ad1e-1bed8a674f55", "recroomoriginal,sport", "image_DiscGolfPropulsion.png"), + ("Dodgeball", "Dodgeball", "Throw dodgeballs to knock out your friends in this gym classic!", "3d474b26-26f7-45e9-9a36-9b02847d5e6f", "recroomoriginal,sport", "image_Dodgeball.png"), + ("Paddleball", "Paddleball", "A simple rally game between two players in a plexiglass tube with a zero-g ball.", "d89f74fa-d51e-477a-a425-025a891dd499", "recroomoriginal,sport", "image_Paddleball.png"), + ("Paintball", "Paintball", "Red and Blue teams splat each other in capture the flag and team battle.", "e122fe98-e7db-49e8-a1b1-105424b6e1f0", "recroomoriginal,sport", "image_Paintball.png"), + // Soccer scene = soccer.unity, Location ReplicationId verified + // (was previously mis-mapped to IsleOfLostSkulls' GUID). + ("Soccer", "Soccer", "Teams of three run around slamming themselves into an over-sized soccer ball. Goal!", "6d5eea4b-f069-4ed0-9916-0e2f07df0d03", "recroomoriginal,sport", "image_Soccer.png"), + // LaserTag in the 2020 build IS the CyberJunkCity arena scene + // (Arena_Cyberjunk_City.unity). Was previously mis-mapped to + // soccer's GUID. Hangar (Arena_Hangar3.unity) is the alternate + // map players reach via the in-game lobby. + ("LaserTag", "Laser Tag", "Teams battle each other and waves of robots in a totally cyber neon future city.", "9d6456ce-6264-48b4-808d-2d96b3d91038", "recroomoriginal,sport", "image_LaserTag.png"), + ("CyberJunkCity", "Laser Tag CyberJunk","Teams battle each other and waves of robots in a totally cyber neon future city.", "9d6456ce-6264-48b4-808d-2d96b3d91038", "recroomoriginal,sport", "image_CyberJunkCity.png"), + ("LaserTagHangar", "Laser Tag Hangar", "Teams battle in an industrial warehouse map.", "239e676c-f12f-489f-bf3a-d4c383d692c3", "recroomoriginal,sport", "image_LaserTag.png"), + ("RecRoyaleSquads", "Rec Royale Squads", "Squads of three battle it out on Frontier Island. Last squad standing wins!", "253fa009-6e65-4c90-91a1-7137a56a267f", "recroomoriginal,featured,sport", "image_RecRoyaleSquads.png"), + // RecRoyaleSolos was previously pointing at a Home location + // GUID (85b43509-…); the real Solos Location is b010171f-…. + ("RecRoyaleSolos", "Rec Royale Solos", "Battle it out on Frontier Island. Last person standing wins!", "b010171f-4875-4e89-baba-61e878cd41e1", "recroomoriginal,sport", "image_RecRoyaleSolos.png"), + ("GoldenTrophy", "Quest For The Golden Trophy", "The goblin king stole Coach's Golden Trophy. Team up and embark on an epic quest to recover it!", "91e16e35-f48f-4700-ab8a-a1b79e50e51b", "recroomoriginal,quest", "image_GoldenTrophy.png"), + ("TheRiseofJumbotron","The Rise of Jumbotron", "Robot invaders threaten the galaxy! Team up with your friends and bring the laser heat!", "acc06e66-c2d0-4361-b0cd-46246a4c455c", "recroomoriginal,quest", "image_TheRiseofJumbotron.png"), + ("CrimsonCauldron", "Curse of the Crimson Cauldron", "Can your band of adventurers brave the enchanted wilds, and lift the curse of the crimson cauldron?", "949fa41f-4347-45c0-b7ac-489129174045", "recroomoriginal,quest", "image_CrimsonCauldron.png"), + // IsleOfLostSkulls was previously pointing at Quarry's GUID; + // real Location ReplicationId is 7e01cfe0-… (scene Quest_Pirate1_additive). + ("IsleOfLostSkulls", "The Isle of Lost Skulls", "Can your pirate crew get to the Isle, defeat its fearsome guardian, and escape with the gold?", "7e01cfe0-820a-406f-b1b3-0a5bf575235c", "recroomoriginal,quest", "image_IsleOfLostSkulls.png"), + ("Crescendo", "Crescendo of the Blood Moon", "Brave the haunted halls of Castle Dracula and survive the night.", "49cb8993-a956-43e2-86f4-1318f279b22a", "recroomoriginal,quest", "image_Crescendo.png"), + ("StuntRunner", "Stunt Runner", "A solo platforming gauntlet — sprint, climb and dodge to reach the trophy at the top.", "b7281665-a715-4051-826b-8e08e69c6172", "recroomoriginal,sport,quest", "image_StuntRunner.png"), + ("Drive-In", "Rec Drive-In", "Watch movies, hang out with friends, or chill at the bar.", "65ddbb48-5a01-4e3e-972d-e5c7419e2bc3", "recroomoriginal,featured", "image_DriveIn.png"), + // Hub / hang rooms — no specific gameplay, just shared spaces. + ("Park", "The Park", "An outdoor park with picnic tables and lawn games — a chill place to hang out.", "0a864c86-5a71-4e18-8041-8124e4dc9d98", "recroomoriginal,featured", "image_Park.png"), + ("PerformanceHall", "Performance Hall", "A big stage for live performances. Sing, perform, or just enjoy the show.", "9932f88f-3929-43a0-a012-a40b5128e346", "recroomoriginal", "image_PerformanceHall.png"), + ("EventRoom", "The Lounge", "A modular event hall with movable furniture for parties and meetings.", "a067557f-ca32-43e6-b6e5-daaec60b4f5a", "recroomoriginal", "image_EventRoom.png"), + ("BowlingAlley", "Bowling Alley", "Classic ten-pin bowling. Roll strikes, beat your friends.", "ae929543-9a07-41d5-8ee9-dbbee8c36800", "recroomoriginal,sport", "image_BowlingAlley.png"), + // Sandbox templates (cloning canvases) — every "Home"-named + // Location with a recognizable scene. Players land in these + // when picking a stage for a custom room. + ("River", "River", "An outdoor template for building. Mountains, river, and forest as your starting canvas.", "e122fe98-e7db-49e8-a1b1-105424b6e1f0", "recroomoriginal,template", "image_River.png"), + ("Homestead", "Homestead", "A frontier-themed template — barn, fences, dusty trails. Build your homestead.", "a785267d-c579-42ea-be43-fec1992d1ca7", "recroomoriginal,template", "image_Homestead.png"), + ("Quarry", "Quarry", "A rocky open-pit template, ideal for industrial and obstacle-course builds.", "ff4c6427-7079-4f59-b22a-69b089420827", "recroomoriginal,template", "image_Quarry.png"), + ("Clearcut", "Clearcut", "An open clearing template — flat ground, mountains in the distance.", "380d18b5-de9c-49f3-80f7-f4a95c1de161", "recroomoriginal,template", "image_Clearcut.png"), + ("Spillway", "Spillway", "A water-park template — pools, waterslides, and concrete chutes for action builds.", "58763055-2dfb-4814-80b8-16fac5c85709", "recroomoriginal,template", "image_Spillway.png"), + ("MakerRoom", "Maker Room", "A blank-walled canvas. The classic starting point for a Maker Pen build.", "a75f7547-79eb-47c6-8986-6767abcb4f92", "recroomoriginal,template,featured", "image_RecCenter.png"), + }; + + // Stable id allocation: 100..1000 reserved for seeded rooms. User + // rooms get ids above 1000 so /goto/room/{id} never collides with + // a system room. + long id = 100; + // hot scores in descending order — first seeded entry sorts first. + var hotScore = 1_000_000.0; + foreach (var entry in seeded) + { + db.Rooms.Add(new RoomEntity + { + Id = id++, + Name = entry.Slug, + Description = entry.Description, + CreatorPlayerId = 1, + // Prefer the real Rec.Net CDN image (sourced via + // tools/fetch-room-images.py and cached in + // data/images/) over the placeholder image_X.png. + ImageName = RoomImagesByName.TryGetValue(entry.Slug, out var realImg) + ? realImg : entry.Image, + State = 0, + Accessibility = 1, + // Seeded rooms ARE the official AG (Against Gravity / Rec Room + // Original) rooms — Rec Center, Paintball, Quests, etc. The + // client treats AG rooms specially during the matchmaking + // join flow (different room-property expectations, longer + // capacity caps); marking them non-AG breaks the rec center + // join with MatchmakingErrorCode.InsufficientSpace because + // the client decides the response shape doesn't match a + // real AG instance. + IsAGRoom = true, + IsDormRoom = false, + CloningAllowed = false, + LocationReplicationId = entry.LocationId, + TagsCsv = entry.Tags, + CheerCount = 1000, + FavoriteCount = 500, + VisitCount = 100_000, + HotScore = hotScore, + }); + hotScore -= 1.0; + } + + await db.SaveChangesAsync(); + } + + /// Per-room thumbnail filenames. Real names sourced from + /// the official Rec.Net public API + /// (https://apim.rec.net/rooms/rooms?name={name}&include=0) + /// via tools/fetch-room-images.py; the cached PNGs live in + /// data/images/ and are served by the merged + /// (img.* branch). + /// Fallback to image_{name}.png placeholders for sub-rooms + /// the public API doesn't expose by name (DormRoom, Paintball + /// sub-maps, CyberJunkCity, etc). + private static readonly Dictionary RoomImagesByName = LoadRoomImages(); + + private static Dictionary LoadRoomImages() + { + var fallback = new Dictionary(StringComparer.OrdinalIgnoreCase) + { + ["CyberJunkCity"] = "image_CyberJunkCity.png", + ["Drive-In"] = "image_DriveIn.png", + ["River"] = "image_River.png", + ["Homestead"] = "image_Homestead.png", + ["Quarry"] = "image_Quarry.png", + ["Clearcut"] = "image_Clearcut.png", + ["Spillway"] = "image_Spillway.png", + }; + var candidates = new[] + { + Path.Combine(AppContext.BaseDirectory, "data", "room_images.json"), + Path.Combine(Directory.GetCurrentDirectory(), "data", "room_images.json"), + }; + var path = candidates.FirstOrDefault(System.IO.File.Exists); + if (path is null) return fallback; + try + { + using var fs = System.IO.File.OpenRead(path); + var real = System.Text.Json.JsonSerializer + .Deserialize>(fs) + ?? new(); + // Real entries override fallbacks. + foreach (var (k, v) in real) fallback[k] = v; + } + catch { /* fall through to fallback */ } + return new Dictionary(fallback, StringComparer.OrdinalIgnoreCase); + } + + /// One-time fixup so existing DBs (seeded before + /// thumbnails were added, or when the image map updates with real + /// CDN names) get their ImageName populated. Looks up the seeded + /// image filename by room name and patches both blank rows AND + /// rows still carrying placeholder image_X.png names so + /// the real CDN-sourced image takes effect. + private async Task BackfillSeededImagesAsync() + { + var rows = await db.Rooms + .Where(r => r.Id >= 100 && r.Id < 1000) + .ToListAsync(); + + // The watch fetches each tile thumbnail by ImageName; a name + // that doesn't resolve to a file on disk (or in S3) lands on + // the transparent-PNG fallback, which the watch's image pipe + // renders as the red "?" placeholder. For each seeded row pick + // the best ImageName in this priority order: (1) the hash + // filename from room_images.json, (2) the original + // image_.png placeholder, (3) image_RecCenter.png as a + // guaranteed-present fallback. Anything we pick must exist as + // a file under data/images so the disk hit path serves it. + const string ultimateFallback = "image_RecCenter.png"; + var imagesDir = Path.Combine(AppContext.BaseDirectory, "data", "images"); + bool OnDisk(string name) => + !string.IsNullOrEmpty(name) && File.Exists(Path.Combine(imagesDir, name)); + + var changed = 0; + foreach (var r in rows) + { + string preferred; + if (RoomImagesByName.TryGetValue(r.Name, out var realImg) && OnDisk(realImg)) + preferred = realImg; + else if (OnDisk($"image_{r.Name}.png")) + preferred = $"image_{r.Name}.png"; + else if (OnDisk(r.ImageName)) + preferred = r.ImageName; + else + preferred = ultimateFallback; + + if (r.ImageName != preferred) + { + r.ImageName = preferred; + changed++; + } + } + if (changed > 0) await db.SaveChangesAsync(); + } + + /// Set for any + /// row that has joins but no unique-visitor estimate yet. Two + /// data sources, in priority order: + /// + /// 1. Real rows (post-Phase20): + /// VisitorCount = COUNT(DISTINCT PlayerId) for that room. + /// Authoritative — this is the value the live upsert + /// maintains going forward. + /// 2. Estimate from VisitCount when no per-player rows exist + /// (e.g. seeded rooms with VisitCount=100,000 from the + /// initial seed). Use VisitCount/3 — roughly the official + /// Rec.Net ratio of unique visitors to total joins for a + /// mature, popular room. Beats showing 0 visitors next + /// to 100K joins on the admin dashboard. + /// + private async Task BackfillVisitorCountAsync() + { + // Step 1: rebuild from real RoomVisitEntity rows. + var realCounts = await db.RoomVisits + .GroupBy(v => v.RoomId) + .Select(g => new { RoomId = g.Key, Count = g.Select(v => v.PlayerId).Distinct().Count() }) + .ToListAsync(); + var realByRoom = realCounts.ToDictionary(x => x.RoomId, x => x.Count); + + // Step 2: for any room without per-player rows, fall back to + // VisitCount/3. Skip rooms whose VisitorCount already matches + // the canonical value to keep the write set small. + var rooms = await db.Rooms.ToListAsync(); + var changed = 0; + foreach (var room in rooms) + { + int target; + if (realByRoom.TryGetValue(room.Id, out var real)) + { + target = real; + } + else if (room.VisitorCount == 0 && room.VisitCount > 0) + { + target = Math.Max(1, room.VisitCount / 3); + } + else + { + continue; + } + if (room.VisitorCount != target) + { + room.VisitorCount = target; + changed++; + } + } + if (changed > 0) await db.SaveChangesAsync(); + } + + /// Adds any seeded rooms that aren't yet in the DB. Used + /// when an existing DB pre-dates a new room being added to the + /// seed list (e.g. StuntRunner, the building templates) — without + /// this, the user would have to wipe the DB to see them. + private async Task EnsureMissingSeededRoomsAsync() + { + // Drop any StuntRunner row carrying our old placeholder GUID + // (5b6e1a3f-…) so the next seed pass below can replace it with + // the canonical b7281665-… id. Without this, an existing DB + // keeps the broken row and /goto/room/StuntRunner still hits + // "RecNet game session contains unknown scene location ID". + const string oldBadGuid = "5b6e1a3f-3a8b-4c7c-9b88-4b51d2b0e1f4"; + var bad = await db.Rooms + .Where(r => r.Name == "StuntRunner" && r.LocationReplicationId == oldBadGuid) + .ToListAsync(); + if (bad.Count > 0) + { + db.Rooms.RemoveRange(bad); + await db.SaveChangesAsync(); + } + + // Fix up rows whose LocationReplicationId was wrong in earlier + // seed passes. Each (room name, real GUID) pair was verified + // against AGRoomRuntimeConfig.Locations in resources.assets via + // tools/extract-locations-binary.py. Only updates rows that + // currently hold the WRONG GUID — already-correct or user-placed + // rooms aren't touched. + var corrections = new (string Name, string CorrectGuid)[] + { + ("Soccer", "6d5eea4b-f069-4ed0-9916-0e2f07df0d03"), + ("LaserTag", "9d6456ce-6264-48b4-808d-2d96b3d91038"), + ("RecRoyaleSolos", "b010171f-4875-4e89-baba-61e878cd41e1"), + ("IsleOfLostSkulls", "7e01cfe0-820a-406f-b1b3-0a5bf575235c"), + }; + foreach (var (name, correct) in corrections) + { + await db.Rooms + .Where(r => r.Name == name && r.LocationReplicationId != correct) + .ExecuteUpdateAsync(s => s.SetProperty(r => r.LocationReplicationId, correct)); + } + + var missing = new (string Slug, string DisplayName, string Description, string LocationId, string Tags, string Image)[] + { + ("StuntRunner", "Stunt Runner", "A solo platforming gauntlet — sprint, climb and dodge to reach the trophy at the top.", "b7281665-a715-4051-826b-8e08e69c6172", "recroomoriginal,sport,quest", "image_StuntRunner.png"), + ("Drive-In", "Rec Drive-In", "Watch movies, hang out with friends, or chill at the bar.", "65ddbb48-5a01-4e3e-972d-e5c7419e2bc3", "recroomoriginal,featured", "image_DriveIn.png"), + ("River", "River", "An outdoor template for building. Mountains, river, and forest as your starting canvas.", "e122fe98-e7db-49e8-a1b1-105424b6e1f0", "recroomoriginal,template", "image_River.png"), + ("Homestead", "Homestead", "A frontier-themed template — barn, fences, dusty trails. Build your homestead.", "a785267d-c579-42ea-be43-fec1992d1ca7", "recroomoriginal,template", "image_Homestead.png"), + ("Quarry", "Quarry", "A rocky open-pit template, ideal for industrial and obstacle-course builds.", "ff4c6427-7079-4f59-b22a-69b089420827", "recroomoriginal,template", "image_Quarry.png"), + ("Clearcut", "Clearcut", "An open clearing template — flat ground, mountains in the distance.", "380d18b5-de9c-49f3-80f7-f4a95c1de161", "recroomoriginal,template", "image_Clearcut.png"), + ("Spillway", "Spillway", "A water-park template — pools, waterslides, and concrete chutes for action builds.", "58763055-2dfb-4814-80b8-16fac5c85709", "recroomoriginal,template", "image_Spillway.png"), + // Phase 22: hub / hang rooms + maker canvases that weren't + // in the original seed. + ("Park", "The Park", "An outdoor park with picnic tables and lawn games — a chill place to hang out.", "0a864c86-5a71-4e18-8041-8124e4dc9d98", "recroomoriginal,featured", "image_Park.png"), + ("PerformanceHall", "Performance Hall", "A big stage for live performances. Sing, perform, or just enjoy the show.", "9932f88f-3929-43a0-a012-a40b5128e346", "recroomoriginal", "image_PerformanceHall.png"), + ("EventRoom", "The Lounge", "A modular event hall with movable furniture for parties and meetings.", "a067557f-ca32-43e6-b6e5-daaec60b4f5a", "recroomoriginal", "image_EventRoom.png"), + ("BowlingAlley", "Bowling Alley", "Classic ten-pin bowling. Roll strikes, beat your friends.", "ae929543-9a07-41d5-8ee9-dbbee8c36800", "recroomoriginal,sport", "image_BowlingAlley.png"), + ("Crescendo", "Crescendo of the Blood Moon", "Brave the haunted halls of Castle Dracula and survive the night.", "49cb8993-a956-43e2-86f4-1318f279b22a", "recroomoriginal,quest", "image_Crescendo.png"), + ("LaserTagHangar", "Laser Tag Hangar", "Teams battle in an industrial warehouse map.", "239e676c-f12f-489f-bf3a-d4c383d692c3", "recroomoriginal,sport", "image_LaserTag.png"), + ("MakerRoom", "Maker Room", "A blank-walled canvas. The classic starting point for a Maker Pen build.", "a75f7547-79eb-47c6-8986-6767abcb4f92", "recroomoriginal,template,featured", "image_RecCenter.png"), + }; + + // Allocate ids above the highest existing seeded id so we don't + // collide with a user room. + var maxSeededId = await db.Rooms + .Where(r => r.Id >= 100 && r.Id < 1000) + .Select(r => (long?)r.Id) + .MaxAsync() ?? 99; + var nextId = Math.Max(maxSeededId + 1, 100); + // Hot score below the existing seeded set so newcomers don't + // jump to the top of the trending feed. + var hotScore = 50_000.0; + + var existingNames = await db.Rooms + .Where(r => r.Id >= 100 && r.Id < 1000) + .Select(r => r.Name) + .ToListAsync(); + var existing = new HashSet(existingNames, StringComparer.OrdinalIgnoreCase); + + var added = false; + foreach (var entry in missing) + { + if (existing.Contains(entry.Slug)) continue; + db.Rooms.Add(new RoomEntity + { + Id = nextId++, + Name = entry.Slug, + Description = entry.Description, + CreatorPlayerId = 1, + // Prefer the real Rec.Net CDN image (sourced via + // tools/fetch-room-images.py and cached in + // data/images/) over the placeholder image_X.png. + ImageName = RoomImagesByName.TryGetValue(entry.Slug, out var realImg) + ? realImg : entry.Image, + State = 0, + Accessibility = 1, + IsAGRoom = true, + IsDormRoom = false, + CloningAllowed = false, + LocationReplicationId = entry.LocationId, + TagsCsv = entry.Tags, + CheerCount = 200, + FavoriteCount = 100, + VisitCount = 10_000, + HotScore = hotScore, + }); + hotScore -= 1.0; + added = true; + } + if (added) await db.SaveChangesAsync(); + } + + public Task GetByIdAsync(long roomId) => + db.Rooms.FirstOrDefaultAsync(r => r.Id == roomId); + + public Task GetByNameAsync(string name) => + db.Rooms.FirstOrDefaultAsync(r => r.Name == name); + + /// + /// Get-or-create the personal-dorm for a + /// given player. Each account owns exactly one dorm row, identified + /// by IsDormRoom = true + CreatorPlayerId == playerId. + /// The watch's "DormRoom" matchmaking call resolves to this row, so + /// every player has their own first-class dorm with a real id (not + /// the synthetic shared id=1 we used to fake) — that means /myrooms + /// surfaces it, room-saves hit a real Rooms.id row, and a + /// long-term moderation tool can flag/clean a single dorm without + /// touching another player's. + /// + /// LocationReplicationId is the shared dorm-scene GUID (every + /// dorm renders the same Unity scene; per-player customisation is + /// the DormStateEntity blob layered on top). + /// + public async Task EnsurePersonalDormAsync(long playerId) + { + // Two rows back this player's dorm: a RoomEntity (the canonical + // room record /goto/room/DormRoom resolves to) and a + // DormStateEntity (the per-player save-blob pointer the + // RoomsController.Details substitutes into the dorm response so + // each player loads THEIR save instead of whoever-saved-last's). + // Both are idempotent: we no-op if either side already exists, + // and we always end with both rows present so the watch's + // GetRoomData → /room/{blobName} flow has a deterministic + // CurrentDataBlobName to fetch (empty string until the player + // saves; CdnController.Serve treats empty as "return the + // default dorm bytes"). + var existing = await db.Rooms + .FirstOrDefaultAsync(r => r.IsDormRoom && r.CreatorPlayerId == playerId); + var dormStateExists = await db.DormStates + .AnyAsync(d => d.PlayerId == playerId); + + if (existing is not null && dormStateExists) + return existing; + + if (existing is null) + { + // Idempotent unique name: collisions only happen if a + // previous boot crashed mid-create. Suffix with the player + // id so the unique-name constraint never bites. + existing = new RoomEntity + { + Name = $"Dorm_{playerId}", + Description = "Your private dorm — yours alone, decorated however you like.", + CreatorPlayerId = playerId, + ImageName = "", + State = 0, + Accessibility = 0, // private — only the owner enters + IsAGRoom = false, + IsDormRoom = true, + CloningAllowed = false, + SupportsVRLow = true, + SupportsMobile = false, + SupportsScreens = true, + SupportsWalkVR = true, + SupportsTeleportVR = true, + AllowsJuniors = true, + LocationReplicationId = "76d98498-60a1-430c-ab76-b54a29b7a163", + TagsCsv = "dorm", + CheerCount = 0, + FavoriteCount = 0, + VisitCount = 0, + HotScore = 0, + }; + db.Rooms.Add(existing); + } + + if (!dormStateExists) + { + db.DormStates.Add(new DormStateEntity + { + PlayerId = playerId, + // Empty until the player saves their dorm via Maker + // Pen. CdnController.Serve sees the empty blob name in + // the Details response and serves + // RoomDataBlobService.GetDefaultBlob() back to the + // watch — boots into the stock dorm scene without a + // 500. + CurrentDataBlobName = string.Empty, + }); + } + + await db.SaveChangesAsync(); + return existing; + } + + /// Run once at startup to backfill + /// + rows for every account that + /// pre-dates the auto-create-on-signup work. Idempotent — calls + /// per missing player so the + /// guarantee "every account has a dorm" holds even for accounts + /// that signed in via a code path that didn't run the lazy-create. + /// + public async Task EnsureDormsForAllPlayersAsync() + { + // Players who don't have a dorm RoomEntity yet. Skip the system + // account (Coach, id=1) — it owns the RR-Original rooms, not a + // personal dorm. Skip players who already have BOTH rows. + var missing = await db.Players + .Where(p => p.Id != 1 && + (!db.Rooms.Any(r => r.IsDormRoom && r.CreatorPlayerId == p.Id) || + !db.DormStates.Any(d => d.PlayerId == p.Id))) + .Select(p => p.Id) + .ToListAsync(); + + if (missing.Count == 0) return; + + foreach (var pid in missing) + { + await EnsurePersonalDormAsync(pid); + } + // EnsurePersonalDormAsync already SaveChangesAsync'd per-call, + // but log the aggregate once for ops visibility. + // (Per-call save keeps each row's failure isolated; if one + // EnsurePersonalDorm throws we still backfilled the rest.) + } + + /// + /// Hot-list query — drives `api/rooms/v1/hot?roomScoreType=...&tags=...`. + /// Filters by tag prefix-match and orders by HotScore desc. + /// The watch sends `?tags=%23community` (URL-encoded `#community`); we + /// strip the leading `#` so it matches the bare tag names stored in + /// TagsCsv. + /// + public async Task> HotAsync(string? tag, int take = 50) + { + // Exclude personal dorms — they're per-player private spaces + // and have no business appearing in the watch's public room + // browser (Trending / Search / BaseRooms all reach this query). + // Same filter applied below to SearchAsync and CreatedByAsync. + // HiddenFromBrowse keeps admin-utility rooms (MakerRoom, + // EventRoom) and rooms-folded-into-others (Paintball maps, + // LaserTag Hangar) out of every public discovery surface. + // /goto-by-name and admin tools still find them via GetByNameAsync. + IQueryable q = db.Rooms.Where(r => r.State == 0 && !r.IsDormRoom && !r.HiddenFromBrowse); + if (!string.IsNullOrWhiteSpace(tag)) + { + var bareTag = tag.TrimStart('#').Trim(); + if (bareTag.Length > 0) + { + var needle = $"%{bareTag}%"; + q = q.Where(r => EF.Functions.Like(r.TagsCsv, needle)); + } + } + return await q.OrderByDescending(r => r.HotScore).Take(take).ToListAsync(); + } + + /// + /// Free-text search — drives `api/rooms/v1/search`. Matches Name, + /// Description, and Tags substrings. + /// + /// Also handles the watch's "rooms by player" convention: when the + /// watch's PlayerProfile screen opens its Rooms tab, it calls + /// Rooms.SearchRooms("@" + accountId) instead of a dedicated + /// per-player endpoint (verified via Cpp2IL — PlayerDetailsWatchUIFlow + /// formats `@{accountId}` then passes it straight to SearchRooms). + /// So a query of @1 resolves to "every room Coach created" + /// and surfaces the seeded Rec Room Originals on Coach's profile. + /// + public async Task> SearchAsync(string query, int take = 50) + { + if (string.IsNullOrWhiteSpace(query)) + return await HotAsync(null, take); + + // @ — watch's PlayerProfile "Rooms" tab. The token can be + // either a numeric accountId (admin-SPA / API callers) OR a + // username (the 2020 watch's actual wire — it formats + // "@" + Player.Username at PlayerDetailsWatchUIFlow time + // and posts that straight to rooms/v2/search, so the + // value we receive is @Alexa, not @1811750). + // Resolve to the target accountId either way so Coach's seeded + // Rec Room Originals (CreatorPlayerId=1) show up on his + // profile's Rooms tab. + if (query.Length > 1 && query[0] == '@') + { + var suffix = query.AsSpan(1).ToString(); + long? accountId = long.TryParse(suffix, out var id) ? id : null; + if (accountId is null) + { + // Case-insensitive username match — Rec Room treats + // @Alexa, @alexa, and @ALEXA as the same player. + var lower = suffix.ToLowerInvariant(); + accountId = await db.Players + .Where(p => p.Username.ToLower() == lower) + .Select(p => (long?)p.Id) + .FirstOrDefaultAsync(); + } + if (accountId is long aid) + { + return await db.Rooms + .Where(r => r.CreatorPlayerId == aid && !r.IsDormRoom && r.State == 0) + .OrderByDescending(r => r.UpdatedAt) + .Take(take) + .ToListAsync(); + } + // Unknown username — return empty rather than fall through + // to substring search (which would match every room with + // "Alexa" in its name). + return new(); + } + + var needle = $"%{query}%"; + return await db.Rooms + .Where(r => r.State == 0 && !r.IsDormRoom && !r.HiddenFromBrowse && ( + EF.Functions.Like(r.Name, needle) || + EF.Functions.Like(r.Description, needle) || + EF.Functions.Like(r.TagsCsv, needle))) + .OrderByDescending(r => r.HotScore) + .Take(take) + .ToListAsync(); + } + + // Include the player's dorm row here. 2020.12's local rooms cache is + // populated from /api/rooms/v2/myrooms (which calls this); when the + // dorm isn't in the cache and matchmaking later returns roomId=116 + // (DormRoom), OJMCBOKJFOF.NHBPIIGDAJP throws "No such room" and the + // dorm load is abandoned. Filtering out dorms here is an explicit + // 2020.03-era choice that no longer holds. + public Task> CreatedByAsync(long playerId) => + db.Rooms.Where(r => r.CreatorPlayerId == playerId) + .OrderByDescending(r => r.UpdatedAt) + .ToListAsync(); + + public async Task> BookmarkedByAsync(long playerId) + { + var ids = await db.RoomBookmarks + .Where(b => b.PlayerId == playerId) + .Select(b => b.RoomId) + .ToListAsync(); + return await db.Rooms + .Where(r => ids.Contains(r.Id)) + .OrderByDescending(r => r.HotScore) + .ToListAsync(); + } + + public async Task BookmarkAsync(long playerId, long roomId) + { + var exists = await db.RoomBookmarks.AnyAsync(b => b.PlayerId == playerId && b.RoomId == roomId); + if (exists) return; + db.RoomBookmarks.Add(new RoomBookmarkEntity { PlayerId = playerId, RoomId = roomId }); + await db.SaveChangesAsync(); + } + + public async Task UnbookmarkAsync(long playerId, long roomId) + { + var existing = await db.RoomBookmarks + .FirstOrDefaultAsync(b => b.PlayerId == playerId && b.RoomId == roomId); + if (existing is null) return; + db.RoomBookmarks.Remove(existing); + await db.SaveChangesAsync(); + } + + /// Every seeded RR-Original room id. Watch uses this to + /// flag "official" rooms in the browser. + public Task> AgRoomIdsAsync() => + db.Rooms.Where(r => r.IsAGRoom).Select(r => r.Id).ToListAsync(); + + /// Top-N AG rooms by HotScore — the watch's Featured + /// carousel. + public Task> FeaturedAgRoomIdsAsync(int take) => + db.Rooms + .Where(r => r.IsAGRoom) + .OrderByDescending(r => r.HotScore) + .Take(Math.Clamp(take, 1, 100)) + .Select(r => r.Id) + .ToListAsync(); + + /// Idempotent room cheer. Returns the new count and + /// whether the caller had already cheered (true = no-op). + public async Task<(long NewCount, bool AlreadyCheered)> CheerRoomAsync(long playerId, long roomId, int type) + { + var existing = await db.Cheers.FirstOrDefaultAsync(c => + c.FromPlayerId == playerId && c.TargetRoomId == roomId && + c.TargetPlayerId == 0 && c.TargetPhotoId == 0 && + c.TargetInventionId == 0 && c.Type == type); + if (existing is not null) + { + var cur = await db.Rooms.Where(r => r.Id == roomId) + .Select(r => r.CheerCount).FirstOrDefaultAsync(); + return (cur, true); + } + + db.Cheers.Add(new CheerEntity + { + FromPlayerId = playerId, + TargetRoomId = roomId, + Type = type, + }); + await db.Rooms.Where(r => r.Id == roomId) + .ExecuteUpdateAsync(s => s + .SetProperty(r => r.CheerCount, r => r.CheerCount + 1) + .SetProperty(r => r.HotScore, r => r.HotScore + 10.0)); + await db.SaveChangesAsync(); + + var newCount = await db.Rooms.Where(r => r.Id == roomId) + .Select(r => r.CheerCount).FirstOrDefaultAsync(); + return (newCount, false); + } + + /// Idempotent uncheer. Returns true when the player had + /// no prior cheer (no-op). + public async Task UncheerRoomAsync(long playerId, long roomId, int type) + { + var row = await db.Cheers.FirstOrDefaultAsync(c => + c.FromPlayerId == playerId && c.TargetRoomId == roomId && c.Type == type); + if (row is null) return true; + + db.Cheers.Remove(row); + await db.Rooms.Where(r => r.Id == roomId && r.CheerCount > 0) + .ExecuteUpdateAsync(s => s + .SetProperty(r => r.CheerCount, r => r.CheerCount - 1) + .SetProperty(r => r.HotScore, r => r.HotScore - 10.0)); + await db.SaveChangesAsync(); + return false; + } + + /// Cheer count for a room + whether + /// has cheered it. = 0 returns + /// iCheered = false (anonymous reader). + public async Task<(long Count, bool ICheered)> GetCheerStateAsync(long playerId, long roomId) + { + var count = await db.Rooms.Where(r => r.Id == roomId) + .Select(r => r.CheerCount).FirstOrDefaultAsync(); + var iCheered = playerId != 0 && await db.Cheers + .AnyAsync(c => c.FromPlayerId == playerId && c.TargetRoomId == roomId); + return (count, iCheered); + } + + /// + /// Result of a CloneAsync call. Status codes match Rooms + /// .CreateModifyRoomStatus (TypeDefIndex 11848): + /// 0 = Success, 1 = Unknown, 4 = RoomDoesNotExist, 10 = DuplicateName, + /// 11 = ReservedName, 12 = InappropriateName. + /// On Success, Room is the new entity; otherwise null. + /// + public sealed record CloneResult(int Status, RoomEntity? Room); + + /// + /// Clone an existing room into a new room owned by `playerId`. Copies + /// most of the source room's properties (scene location, capability + /// flags, accessibility) but resets visit/cheer counts and switches + /// IsAGRoom=true so the cloned room shows up in `#community` rather + /// than under Rec Room Originals. + /// + /// Returns a CloneResult with the matching CreateModifyRoomStatus code + /// so the watch can show the right "name taken" / "no source" message. + /// + public async Task CloneAsync(long sourceRoomId, string newName, long playerId) + { + if (string.IsNullOrWhiteSpace(newName)) + return new CloneResult(12 /* InappropriateName */, null); + newName = newName.Trim(); + + var source = await db.Rooms.FirstOrDefaultAsync(r => r.Id == sourceRoomId); + if (source is null) + return new CloneResult(4 /* RoomDoesNotExist */, null); + + // Status 10 (DuplicateName) — surfaces in the watch as a friendlier + // "name already taken" toast instead of a generic Unknown failure. + if (await db.Rooms.AnyAsync(r => r.Name == newName)) + return new CloneResult(10 /* DuplicateName */, null); + + // User-room ids start above 1000 to avoid colliding with the + // 100..1000 range reserved for seeded rooms. + var nextId = await db.Rooms + .Where(r => r.Id > 1000) + .MaxAsync(r => (long?)r.Id) ?? 1000L; + + var clone = new RoomEntity + { + Id = nextId + 1, + Name = newName, + Description = source.Description, + CreatorPlayerId = playerId, + ImageName = "", + State = 0, + Accessibility = source.Accessibility, + SupportsLevelVoting = source.SupportsLevelVoting, + // User-cloned rooms are NOT AG-managed: the AG flag means + // "owned by the Against Gravity seed account, baked into + // resources.assets". A user's clone is their own + // community-content room — tag it accordingly so it + // shows up as Custom in admin and isn't shielded from + // hard-delete. (Previously this was =true; that misled + // both the admin badge AND the purge-refuses-canonical + // gate, which is why "RR ORIGINAL"-labelled junk rooms + // showed up that the admin couldn't purge.) + IsAGRoom = false, + IsDormRoom = false, + CloningAllowed = source.CloningAllowed, + SupportsVRLow = source.SupportsVRLow, + SupportsMobile = source.SupportsMobile, + SupportsScreens = source.SupportsScreens, + SupportsWalkVR = source.SupportsWalkVR, + SupportsTeleportVR = source.SupportsTeleportVR, + AllowsJuniors = source.AllowsJuniors, + RoomWarningMask = source.RoomWarningMask, + CustomRoomWarning = source.CustomRoomWarning, + DisableMicAutoMute = source.DisableMicAutoMute, + LocationReplicationId = source.LocationReplicationId, + TagsCsv = "community", // tagged community since user-built + CheerCount = 0, + FavoriteCount = 0, + VisitCount = 0, + HotScore = 0, // brand new — sorts to bottom of hot + CreatedAt = DateTime.UtcNow, + UpdatedAt = DateTime.UtcNow, + }; + + db.Rooms.Add(clone); + await db.SaveChangesAsync(); + return new CloneResult(0 /* Success */, clone); + } + + /// + /// Project a RoomEntity to the wire shape Room.Deserialize at RVA + /// 0x114E430 expects. Required keys (PascalCase): + /// RoomId, Name, Description, CreatorPlayerId, ImageName, State, + /// Accessibility, SupportsLevelVoting, IsAGRoom, CloningAllowed, + /// SupportsScreens, SupportsWalkVR, SupportsTeleportVR, + /// AllowsJuniors, RoomWarningMask, CustomRoomWarning + /// Optional: IsDormRoom, SupportsVRLow, SupportsMobile, DisableMicAutoMute. + /// + public static object ToWireRoom(RoomEntity r) => new + { + RoomId = r.Id, + Name = r.Name, + Description = r.Description, + CreatorPlayerId = r.CreatorPlayerId, + CreatorAccountId = r.CreatorPlayerId, + ImageName = r.ImageName, + State = r.State, + Accessibility = r.Accessibility, + SupportsLevelVoting = r.SupportsLevelVoting, + IsAGRoom = r.IsAGRoom, + IsRRO = r.IsAGRoom, + // 2020.12 Room.Deserialize (KLCOGEIGEBJ.PPGFHEDFBEA, the Room base) + // reads required keys: RoomId, IsDorm, CloningAllowed, DisableMicAutoMute, + // DisableRoomComments, EncryptVoiceChat. The 2020.03 key was IsDormRoom; + // 2020.12 dropped "Room" from the name. Send both — extra keys are + // ignored by both clients. + IsDorm = r.IsDormRoom, + IsDormRoom = r.IsDormRoom, + CloningAllowed = r.CloningAllowed, + SupportsVRLow = r.SupportsVRLow, + SupportsMobile = r.SupportsMobile, + SupportsScreens = r.SupportsScreens, + SupportsWalkVR = r.SupportsWalkVR, + SupportsTeleportVR = r.SupportsTeleportVR, + AllowsJuniors = r.AllowsJuniors, + SupportsJuniors = r.AllowsJuniors, + SupportsQuest2 = true, + RoomWarningMask = r.RoomWarningMask, + WarningMask = r.RoomWarningMask, + CustomRoomWarning = r.CustomRoomWarning, + CustomWarning = r.CustomRoomWarning, + DisableMicAutoMute = r.DisableMicAutoMute, + // 2020.12 additional required keys in the base Room reader. + DisableRoomComments = false, + EncryptVoiceChat = false, + CreatedAt = (r.CreatedAt == default ? DateTime.UtcNow : r.CreatedAt) + .ToString("yyyy-MM-ddTHH:mm:ssZ"), + Stats = new + { + CheerCount = r.CheerCount, + FavoriteCount = r.FavoriteCount, + VisitorCount = r.VisitorCount, + VisitCount = r.VisitCount, + }, + }; + + /// + /// Idempotent admin overrides applied AFTER SeedAsync. Keeps the seed + /// table itself untouched (so a clean DB build still seeds the + /// canonical RR-Originals shape) while letting us: + /// • Fold the standalone Paintball map rooms (River, Homestead, + /// Quarry, Clearcut, Spillway, Drive-In) into Paintball as + /// RoomSceneEntity rows + hide their old standalone rows. + /// • Fold LaserTagHangar into LaserTag as a second scene and hide + /// the standalone Hangar row. + /// • Hide MakerRoom + EventRoom from browse (admin tools / "create + /// room" template flow still resolves them by name). + /// Re-running on a DB that's already been overridden no-ops. + /// + public async Task ApplyCanonicalOverridesAsync(HttpClient http, string imagesDir) + { + // Hide admin-utility and folded-in rooms from browse. + // CyberJunkCity + LaserTagHangar are now subrooms under LaserTag; + // PerformanceHall + MakerRoom + EventRoom are admin-only. + var namesToHide = new[] + { + "MakerRoom", "EventRoom", "PerformanceHall", + "River", "Homestead", "Quarry", "Clearcut", "Spillway", "Drive-In", + "LaserTagHangar", "CyberJunkCity" + }; + await db.Rooms + .Where(r => namesToHide.Contains(r.Name) && !r.HiddenFromBrowse) + .ExecuteUpdateAsync(s => s.SetProperty(r => r.HiddenFromBrowse, true)); + + // Download external thumbnails. Saved under stable file names so + // a second startup re-uses the local copy. ImageName is just the + // file name; ImgController serves it from data/images/. Bowling + // Alley pulls from the rec.net CDN id baked into room_images.json + // (no UI for that yet) since the user didn't pass a custom URL. + Directory.CreateDirectory(imagesDir); + await TryDownloadAsync(http, "https://img.rec.net/by3mjs9jbozpdvu6g9aje7jgz.png", + Path.Combine(imagesDir, "image_Crescendo.png")); + await TryDownloadAsync(http, + "https://static.wikia.nocookie.net/rec-room/images/4/48/Paintball_-_Key_Art.png/revision/latest/scale-to-width-down/1000?cb=20200513122350", + Path.Combine(imagesDir, "image_PaintballKeyArt.png")); + await TryDownloadAsync(http, "https://img.rec.net/8gwqibu0anm4j0rw54xo2y0yd.png", + Path.Combine(imagesDir, "image_BowlingAlley.png")); + await TryDownloadAsync(http, "https://img.rec.net/4o5lschc01nani8xeywao622n.png", + Path.Combine(imagesDir, "image_Park.png")); + + var paintball = await db.Rooms.FirstOrDefaultAsync(r => r.Name == "Paintball"); + if (paintball is not null && paintball.ImageName != "image_PaintballKeyArt.png") + paintball.ImageName = "image_PaintballKeyArt.png"; + + var bowling = await db.Rooms.FirstOrDefaultAsync(r => r.Name == "BowlingAlley"); + if (bowling is not null && bowling.ImageName != "image_BowlingAlley.png") + bowling.ImageName = "image_BowlingAlley.png"; + + var park = await db.Rooms.FirstOrDefaultAsync(r => r.Name == "Park"); + if (park is not null && park.ImageName != "image_Park.png") + park.ImageName = "image_Park.png"; + + // Paintball sub-room scenes — one RoomSceneEntity row per map. + // Each row carries the map's old standalone-room + // LocationReplicationId so /goto/room/Paintball/{map} resolves + // to the same Unity scene the standalone room used. OrderIndex + // is stable so wire SubRoomId values don't shuffle between + // server restarts. + if (paintball is not null) + { + var paintballScenes = new (string Name, string Location)[] + { + ("River", "e122fe98-e7db-49e8-a1b1-105424b6e1f0"), + ("Homestead", "a785267d-c579-42ea-be43-fec1992d1ca7"), + ("Quarry", "ff4c6427-7079-4f59-b22a-69b089420827"), + ("Clearcut", "380d18b5-de9c-49f3-80f7-f4a95c1de161"), + ("Spillway", "58763055-2dfb-4814-80b8-16fac5c85709"), + ("Drive-In", "65ddbb48-5a01-4e3e-972d-e5c7419e2bc3"), + }; + await EnsureScenesAsync(paintball.Id, paintballScenes); + } + + // LaserTag sub-rooms: the lobby (current LaserTag scene = + // CyberJunkCity arena) and the Hangar (was a standalone room). + var lasertag = await db.Rooms.FirstOrDefaultAsync(r => r.Name == "LaserTag"); + if (lasertag is not null) + { + var lasertagScenes = new (string Name, string Location)[] + { + ("CyberJunkCity", "9d6456ce-6264-48b4-808d-2d96b3d91038"), + ("Hangar", "239e676c-f12f-489f-bf3a-d4c383d692c3"), + }; + await EnsureScenesAsync(lasertag.Id, lasertagScenes); + } + + await db.SaveChangesAsync(); + } + + /// Adds any missing scene rows for a parent room without + /// disturbing existing ones. Matches by (RoomId, Name) so a + /// re-run that finds the scene already there is a no-op. + private async Task EnsureScenesAsync(long roomId, (string Name, string Location)[] scenes) + { + var existing = await db.RoomScenes + .Where(s => s.RoomId == roomId) + .Select(s => new { s.Name, s.OrderIndex }) + .ToListAsync(); + var existingNames = existing.Select(e => e.Name).ToHashSet(StringComparer.OrdinalIgnoreCase); + var nextOrder = existing.Count == 0 ? 0 : existing.Max(e => e.OrderIndex) + 1; + foreach (var (name, location) in scenes) + { + if (existingNames.Contains(name)) continue; + db.RoomScenes.Add(new RoomSceneEntity + { + RoomId = roomId, + OrderIndex = nextOrder++, + Name = name, + RoomSceneLocationId = location, + DataBlobName = "", + MaxPlayers = 8, + IsSandbox = false, + CanMatchmakeInto = true, + DataModifiedAt = DateTime.UtcNow, + }); + } + } + + private static async Task TryDownloadAsync(HttpClient http, string url, string targetPath) + { + // Skip if already on disk — admins can delete the file to force + // a re-fetch on next startup. + if (File.Exists(targetPath) && new FileInfo(targetPath).Length > 0) return; + try + { + using var resp = await http.GetAsync(url); + if (!resp.IsSuccessStatusCode) return; + var bytes = await resp.Content.ReadAsByteArrayAsync(); + if (bytes.Length == 0) return; + await File.WriteAllBytesAsync(targetPath, bytes); + } + catch + { + // Network blip on a fresh install just leaves the room with + // its existing placeholder image — admin can re-trigger by + // restarting once the URL's reachable. + } + } +} diff --git a/DorkNet.Server/Services/ServerSettingsService.cs b/DorkNet.Server/Services/ServerSettingsService.cs new file mode 100644 index 0000000..682b9c9 --- /dev/null +++ b/DorkNet.Server/Services/ServerSettingsService.cs @@ -0,0 +1,217 @@ +using DorkNet.Server.Data; +using DorkNet.Server.Data.Entities; +using Microsoft.EntityFrameworkCore; +using System.Text.Json; + +namespace DorkNet.Server.Services; + +/// +/// Single-row settings store. Same Postgres-backed pattern as +/// : every read is one SELECT, +/// every write is one UPDATE/INSERT, and the row is visible to every +/// replica immediately so admin toggles propagate without a cache +/// invalidation dance. +/// +/// Scoped (not singleton) because it holds a DbContext reference. The +/// toggles it backs (signups, etc.) are checked on rare paths — +/// account creation hits this once per signup attempt, which is +/// dwarfed by everything else the request does. +/// +public class ServerSettingsService(DorkNetDbContext db) +{ + private const int RowId = 1; + private static readonly JsonSerializerOptions JsonOptions = new() + { + PropertyNameCaseInsensitive = true, + }; + + public async Task GetAsync() + { + var row = await db.ServerSettings.AsNoTracking() + .FirstOrDefaultAsync(s => s.Id == RowId); + return row ?? new ServerSettingsEntity { Id = RowId }; + } + + public async Task AreSignupsDisabledAsync() + { + var row = await db.ServerSettings.AsNoTracking() + .FirstOrDefaultAsync(s => s.Id == RowId); + return row?.SignupsDisabled ?? false; + } + + public async Task SetSignupsDisabledAsync(bool disabled) + { + var existing = await GetTrackedRowAsync(); + existing.SignupsDisabled = disabled; + existing.UpdatedAt = DateTime.UtcNow; + await db.SaveChangesAsync(); + return existing; + } + + public async Task GetWeeklyChallengesAsync() + { + var row = await GetAsync(); + return ToWeeklySettings(row); + } + + public async Task SetWeeklyChallengesAsync( + bool completedRequired, + IReadOnlyList challenges, + WeeklyChallengeReward? reward) + { + var normalized = NormalizeWeeklyChallenges(challenges); + var normalizedReward = NormalizeWeeklyReward(reward); + var existing = await GetTrackedRowAsync(); + existing.WeeklyChallengesCompletedRequired = completedRequired; + existing.WeeklyChallengesJson = JsonSerializer.Serialize(normalized, JsonOptions); + existing.WeeklyChallengeRewardJson = JsonSerializer.Serialize(normalizedReward, JsonOptions); + existing.UpdatedAt = DateTime.UtcNow; + await db.SaveChangesAsync(); + return ToWeeklySettings(existing); + } + + private async Task GetTrackedRowAsync() + { + var existing = await db.ServerSettings.FirstOrDefaultAsync(s => s.Id == RowId); + if (existing is not null) return existing; + + existing = new ServerSettingsEntity { Id = RowId, UpdatedAt = DateTime.UtcNow }; + db.ServerSettings.Add(existing); + return existing; + } + + private static WeeklyChallengeSettings ToWeeklySettings(ServerSettingsEntity row) + { + var challenges = DefaultWeeklyChallenges(); + var reward = DefaultWeeklyReward(); + if (!string.IsNullOrWhiteSpace(row.WeeklyChallengesJson)) + { + try + { + var parsed = JsonSerializer.Deserialize>( + row.WeeklyChallengesJson, + JsonOptions); + if (parsed?.Count > 0) + challenges = NormalizeWeeklyChallenges(parsed); + } + catch + { + challenges = DefaultWeeklyChallenges(); + } + } + + if (!string.IsNullOrWhiteSpace(row.WeeklyChallengeRewardJson)) + { + try + { + var parsed = JsonSerializer.Deserialize( + row.WeeklyChallengeRewardJson, + JsonOptions); + reward = NormalizeWeeklyReward(parsed); + } + catch + { + reward = DefaultWeeklyReward(); + } + } + + return new WeeklyChallengeSettings( + row.WeeklyChallengesCompletedRequired, + challenges, + reward, + row.UpdatedAt); + } + + private static List NormalizeWeeklyChallenges( + IReadOnlyList challenges) + { + var normalized = challenges + .Where(c => !string.IsNullOrWhiteSpace(c.Name)) + .Take(10) + .Select((c, i) => new WeeklyChallengeTemplate( + i, + c.Name.Trim(), + string.IsNullOrWhiteSpace(c.Config) ? "{}" : c.Config.Trim(), + c.Description?.Trim() ?? string.Empty, + c.Tooltip?.Trim() ?? string.Empty)) + .ToList(); + + return normalized.Count > 0 ? normalized : DefaultWeeklyChallenges(); + } + + private static WeeklyChallengeReward NormalizeWeeklyReward(WeeklyChallengeReward? reward) + { + reward ??= DefaultWeeklyReward(); + return new WeeklyChallengeReward( + GiftDropId: Math.Max(0, reward.GiftDropId), + Xp: Math.Max(0, reward.Xp), + Tokens: Math.Max(0, reward.Tokens), + Level: Math.Max(0, reward.Level), + GiftContext: Math.Max(0, reward.GiftContext), + GiftRarity: Math.Max(0, reward.GiftRarity), + AvatarItemDesc: reward.AvatarItemDesc?.Trim() ?? string.Empty, + ConsumableItemDesc: reward.ConsumableItemDesc?.Trim() ?? string.Empty, + EquipmentPrefabName: reward.EquipmentPrefabName?.Trim() ?? string.Empty, + EquipmentModificationGuid: reward.EquipmentModificationGuid?.Trim() ?? string.Empty); + } + + public static List DefaultWeeklyChallenges() => + [ + new( + Index: 0, + Name: "Play 3 Rec Room Originals", + Config: "{\"Type\":\"CompleteActivity\",\"Goal\":3}", + Description: "Complete three Rec Room Original activities.", + Tooltip: "Play any RRO activity to make progress."), + new( + Index: 1, + Name: "Cheer 5 Players", + Config: "{\"Type\":\"CheerPlayers\",\"Goal\":5}", + Description: "Send cheers to five players.", + Tooltip: "Use the watch to cheer players you meet."), + new( + Index: 2, + Name: "Visit 5 Rooms", + Config: "{\"Type\":\"VisitRooms\",\"Goal\":5}", + Description: "Visit five rooms this week.", + Tooltip: "Any room visit counts toward this challenge."), + ]; + + public static WeeklyChallengeReward DefaultWeeklyReward() => + new( + GiftDropId: 0, + Xp: 250, + Tokens: 250, + Level: 1, + GiftContext: 0, + GiftRarity: 0, + AvatarItemDesc: string.Empty, + ConsumableItemDesc: string.Empty, + EquipmentPrefabName: string.Empty, + EquipmentModificationGuid: string.Empty); +} + +public sealed record WeeklyChallengeSettings( + bool CompletedRequired, + IReadOnlyList Challenges, + WeeklyChallengeReward Reward, + DateTime UpdatedAt); + +public sealed record WeeklyChallengeTemplate( + int Index, + string Name, + string Config, + string Description, + string Tooltip); + +public sealed record WeeklyChallengeReward( + int GiftDropId, + int Xp, + int Tokens, + int Level, + int GiftContext, + int GiftRarity, + string AvatarItemDesc, + string ConsumableItemDesc, + string EquipmentPrefabName, + string EquipmentModificationGuid); diff --git a/DorkNet.Server/Services/StoreService.cs b/DorkNet.Server/Services/StoreService.cs new file mode 100644 index 0000000..6893373 --- /dev/null +++ b/DorkNet.Server/Services/StoreService.cs @@ -0,0 +1,1108 @@ +using Microsoft.EntityFrameworkCore; +using Microsoft.Extensions.Configuration; +using DorkNet.Server.Controllers.API.Avatar.V4; +using DorkNet.Server.Data; +using DorkNet.Server.Data.Entities; + +namespace DorkNet.Server.Services; + +/// +/// Owns the in-game store catalog, the storefronts API shaping, and +/// the purchase flow (verify balance → deduct currency → grant item to +/// inventory). The watch's "Shop" tab fetches this via +/// GET api/storefronts/v3/all and friends; the purchase flow +/// runs through POST api/purchase/v1/{initiate|process|complete}. +/// +/// Currency: items are priced in tokens (CurrencyType=2), the same +/// currency the level-up + cheer-receive flow grants. Real-money +/// purchases (CurrencyType=1) are intentionally not wired up — this is +/// a private server. +/// +public class StoreService(DorkNetDbContext db, LevelService level, IConfiguration config) +{ + public const long StoreItemPrice = 1; + public const string AllStorefrontKey = "all"; + public const string RroStorefrontKey = "rro"; + private const int StoreAvatarItemTypeOutfit = 0; + private const int StoreAvatarItemTypeHairDye = 1; + private const int GiftBoxContentHairDye = 6; + private const string WardrobePrefix = "wardrobe-"; + public const string EquipmentSkinPrefix = "equipment-skin:"; + + public sealed record StorefrontDefinition( + string Key, + int? StorefrontType, + string DisplayName, + string Scope); + + private static readonly StorefrontDefinition[] StorefrontDefinitions = + { + new("main", null, "Main watch catalog", "watch"), + new("watch", 3, "Watch gift-drop shelf", "watch"), + new(AllStorefrontKey, null, "All storefront shelves", "shared"), + new(RroStorefrontKey, null, "All RRO and Rec Center shelves", "shared"), + new("season:1", null, "Season 1", "season"), + new("giftdrop:1", 1, "Laser Tag", "room"), + new("giftdrop:2", 2, "Rec Center", "room"), + new("giftdrop:100", 100, "Quest - Lost Skulls", "room"), + new("giftdrop:101", 101, "Quest - Dracula", "room"), + new("giftdrop:102", 102, "Quest - Golden Trophy", "room"), + new("giftdrop:103", 103, "Quest - Crimson Cauldron", "room"), + new("giftdrop:200", 200, "Rec Royale", "room"), + new("giftdrop:300", 300, "Cafe", "room"), + new("giftdrop:400", 400, "Paintball", "room"), + new("giftdrop:401", 401, "Paintball - River", "room"), + new("giftdrop:402", 402, "Paintball - Homestead", "room"), + new("giftdrop:403", 403, "Paintball - Quarry", "room"), + new("giftdrop:404", 404, "Paintball - Clear Cut", "room"), + new("giftdrop:405", 405, "Paintball - Spillway", "room"), + new("giftdrop:406", 406, "Paintball - Sunset Drive-In", "room"), + new("giftdrop:500", 500, "Bowling", "room"), + new("giftdrop:600", 600, "Stunt Runner", "room"), + new("giftdrop:700", 700, "Dorm Mirror", "room"), + }; + + public static IReadOnlyList GetStorefrontDefinitions() => + StorefrontDefinitions; + + public static string StorefrontKeyForType(int storefrontType) => + storefrontType == 3 ? "watch" : $"giftdrop:{storefrontType}"; + + private static readonly (string Slug, string Name, string ConsumableDesc, string HairColorGuid)[] PermanentHairDyes = + { + ("hairdye-red", "Permanent KREQ Red Hair Dye", "[hairdyepotionconsumable_red]", "xyHGGFKNWEOTnXloDFahPQ"), + ("hairdye-dark-red", "Permanent Cauldron Crimson Hair Dye", "[hairdyepotionconsumable_darkred]", "XCwNFMT1i0i9Q9gJYTLPUQ"), + ("hairdye-orange", "Permanent Bucket Orange Hair Dye", "[hairdyepotionconsumable_orange]", "1I8AD333gku-iOQlS6WWlw"), + ("hairdye-yellow", "Permanent Pirate Gold Hair Dye", "[hairdyepotionconsumable_yellow]", "pQNfh-3DsEGWfiIls6Qf6g"), + ("hairdye-green", "Permanent Goblin Green Hair Dye", "[hairdyepotionconsumable_green]", "TylV8M6zjUaadhxHPWAS-w"), + ("hairdye-teal", "Permanent EDM Emerald Hair Dye", "[hairdyepotionconsumable_teal]", "wXJZbYCkZE-NMbPVf2YuFA"), + ("hairdye-cyan", "Permanent H2Oasis Blue Hair Dye", "[hairdyepotionconsumable_cyan]", "Bjsgo9ddeUCk3zW1-DdJbw"), + ("hairdye-blue", "Permanent Butterfly Blue Hair Dye", "[hairdyepotionconsumable_blue]", "yTdx1nVVtECU8Vh7EC8dGA"), + ("hairdye-dark-blue", "Permanent Ranger Roy-Al Blue Hair Dye", "[hairdyepotionconsumable_darkblue]", "3ksu_FLaukeJ-YE90jb4Ow"), + ("hairdye-purple", "Permanent Propulsion Purple Hair Dye", "[hairdyepotionconsumable_purple]", "53y1prYATU2drZDS-jpaqg"), + ("hairdye-dark-purple", "Permanent Gizmo Grape Hair Dye", "[hairdyepotionconsumable_darkpurple]", "QmgjYhH_L0q58eF5SWeDhQ"), + ("hairdye-pink", "Permanent Mousebot Magenta Hair Dye", "[hairdyepotionconsumable_pink]", "8k_gZA_lrEyJxI19GrcAZg"), + ("hairdye-dark-pink", "Permanent Recreational Raspberry Hair Dye", "[hairdyepotionconsumable_darkpink]", "yejur5BbR0mevbH-rzgXYA"), + ("hairdye-pastel-red", "Permanent Rebel Rose Hair Dye", "[hairdyepotionconsumable_pastelred]", "aziIarpSREmcT6vihduPwA"), + ("hairdye-pastel-orange", "Permanent PVPeach Hair Dye", "[hairdyepotionconsumable_pastelorange]", "0RUmjv2bL0ydbKU61dlqlg"), + ("hairdye-pastel-yellow", "Permanent Standee Sandy Hair Dye", "[hairdyepotionconsumable_pastelyellow]", "H65ddsxAY0m2sccMrF48Aw"), + ("hairdye-pastel-green", "Permanent Frontier Forest Hair Dye", "[hairdyepotionconsumable_pastelgreen]", "LBz3a9520kiOOMMOwsTExg"), + ("hairdye-pastel-teal", "Permanent Jungle Jade Hair Dye", "[hairdyepotionconsumable_pastelteal]", "Sc8S3QiTIUuXNJZD6geY1Q"), + ("hairdye-pastel-cyan", "Permanent Circuit Cerulean Hair Dye", "[hairdyepotionconsumable_pastelcyan]", "W5E2gDCKY0SXaKytP89-OA"), + ("hairdye-pastel-blue", "Permanent Paula's Periwinkle Hair Dye", "[hairdyepotionconsumable_pastelblue]", "_2QGtfHzuUSq0eyI84rImw"), + ("hairdye-pastel-purple", "Permanent Lounge Lavender Hair Dye", "[hairdyepotionconsumable_pastelpurple]", "qp_vRXZQPU2rlx_IU1Vr8Q"), + ("hairdye-pastel-pink", "Permanent Maker Mauve Hair Dye", "[hairdyepotionconsumable_pastelpink]", "G129V_kVXkSLN2OG8EjSqQ"), + }; + + private bool EnableWatchGiftDrops => + config.GetValue("Store:EnableWatchGiftDrops", true); + + private int MaxWatchGiftDrops => + // 0 means no cap. Keep the switch configurable in case a + // deployment needs to temporarily reduce the shelf size while + // debugging a bad catalog row. + config.GetValue("Store:MaxWatchGiftDrops", 0); + + /// Idempotent seed of a starter catalog so the store is + /// non-empty on first boot. Only inserts items whose Slug isn't + /// already in the DB; safe to call repeatedly. + public async Task SeedAsync() + { + var defaults = new (string Slug, string Name, string Desc, string Category, long Price, string Image, string Storefront)[] + { + // Hats / heads + ("dorknet-cap-classic", "Classic Cap", "A simple cap. Comfortable, unremarkable.", "head", 150, "store_classic_cap.png", "main"), + ("dorknet-bowler-hat", "Bowler Hat", "Tipped at a jaunty angle. Goes with everything.", "head", 400, "store_bowler_hat.png", "main"), + ("dorknet-witch-hat", "Witch Hat", "Pointy. Definitely magical.", "head", 650, "store_witch_hat.png", "main"), + // Torso + ("dorknet-tee-server", "Server Crew Tee", "Sworn to defend the JWT secret.", "torso", 300, "store_tee_server.png", "main"), + ("dorknet-hoodie-cyber", "Cyber Hoodie", "Glowing accents. Optimised for late-night coding.", "torso", 900, "store_hoodie_cyber.png","main"), + ("dorknet-jacket-bomber", "Bomber Jacket", "Sleek, bomber-style — for laser tag and lurking.", "torso", 850, "store_jacket_bomber.png","main"), + // Legs + ("dorknet-jeans-classic", "Blue Jeans", "Standard issue. Goes with anything.", "legs", 200, "store_jeans.png", "main"), + ("dorknet-cargo-pants", "Cargo Pants", "Lots of pockets — for inventions, snacks, secrets.", "legs", 450, "store_cargo_pants.png", "main"), + // Feet + ("dorknet-sneakers", "Sneakers", "Soft soles. Quiet on the dorm floor.", "feet", 250, "store_sneakers.png", "main"), + ("dorknet-boots-combat", "Combat Boots", "Polished. Heavy. Great for stomping.", "feet", 550, "store_boots_combat.png","main"), + // Accessories + ("dorknet-shades", "Sunglasses", "Maximum chill. Slightly inappropriate indoors.", "accessory", 300, "store_shades.png", "main"), + ("dorknet-watch-rgb", "RGB Smartwatch", "Glowing wrist accessory. Cycles through every colour.", "accessory", 700, "store_watch_rgb.png", "main"), + ("dorknet-cape-velvet", "Velvet Cape", "Floor-length. Suitable for entering a room dramatically.", "accessory", 1200, "store_cape_velvet.png", "main"), + // Hair + ("dorknet-hair-mohawk", "Mohawk", "Sharp. Loud. Possibly aerodynamic.", "hair", 800, "store_hair_mohawk.png", "main"), + ("dorknet-hair-flowing", "Flowing Locks", "Long. Glossy. Definitely waving in some imaginary breeze.", "hair", 650, "store_hair_flowing.png","main"), + // Face + ("dorknet-face-grin", "Permanent Grin", "A confident, slightly unsettling smile.", "face", 350, "store_face_grin.png", "main"), + ("dorknet-face-monocle", "Monocle & Mustache", "Distinguished. Possibly inherited.", "face", 550, "store_face_monocle.png","main"), + // Consumables + ("dorknet-firework", "Firework", "One-shot launch. Spectacular. Not edible.", "consumable", 50, "store_firework.png", "main"), + ("dorknet-pizza-slice", "Pizza Slice", "Looks great on the watch. Also restores 5 imaginary HP.", "consumable", 25, "store_pizza.png", "main"), + // Room templates + ("dorknet-template-cozy", "Cozy Cabin Template", "A wood-cabin starter for new rooms — fireplace included.", "roomtemplate", 1500, "store_template_cozy.png", "main"), + // Limited-time items in the gift drop storefront + ("dorknet-gift-mystery1", "Mystery Box (Tier 1)","Contains a randomised cosmetic. Open with caution.", "consumable", 100, "store_mystery_t1.png", "giftdrop:1"), + ("dorknet-gift-mystery2", "Mystery Box (Tier 2)","A heftier mystery box. More chance of rare drops.", "consumable", 500, "store_mystery_t2.png", "giftdrop:1"), + ("dorknet-gift-aura", "Golden Aura", "Limited-time aura cosmetic — radiates while you're nearby.","accessory", 2500, "store_gift_aura.png", "giftdrop:2"), + }; + + var existing = await db.StoreItems + .Select(i => i.Slug) + .ToListAsync(); + var existingSet = new HashSet(existing, StringComparer.OrdinalIgnoreCase); + + // Drop stale wardrobe-* rows whose embedded GUID isn't in the + // current avatar_item_lookup.json safelist. The previous seed + // pulled GUIDs from outfits_assets_*.bundle MonoBehaviours + // (mask/swatch ASSET GUIDs, not AvatarItem GUIDs); those tiles + // can't render their 3D preview because the watch's + // avatarItemPrefabLookup doesn't have those keys. Removing + // them prevents broken tiles from cluttering the store. + var safe = DorkNet.Server.Controllers.API.Avatar.V4.AvatarItemsController.SafeGuids; + if (safe.Count > 0) + { + // Skip wardrobe-colored-* — those resolve through the color + // variant catalog, not the safe-GUID set, and the substring + // after "wardrobe-" is never a bare GUID anyway. + var stale = await db.StoreItems + .Where(i => i.Slug.StartsWith("wardrobe-") + && !i.Slug.StartsWith(WardrobeColoredPrefix)) + .Select(i => new { i.Id, i.Slug }) + .ToListAsync(); + var toDelete = new List(); + foreach (var row in stale) + { + var guid = row.Slug.Substring("wardrobe-".Length); + if (!safe.Contains(guid)) toDelete.Add(row.Id); + } + if (toDelete.Count > 0) + { + await db.StoreItems + .Where(i => toDelete.Contains(i.Id)) + .ExecuteDeleteAsync(); + // Re-pull the existing-slug set so the new seed below + // re-inserts with the correct GUIDs. + existing = await db.StoreItems.Select(i => i.Slug).ToListAsync(); + existingSet = new HashSet(existing, StringComparer.OrdinalIgnoreCase); + } + } + + // Drop orphan wardrobe-colored-* rows whose slug isn't in the + // current color-variant catalog. When we remove an entry from + // data/color_variants.json (e.g. because the asset's swatch + // colors look wrong in-game), the corresponding StoreItems row + // would otherwise linger as a broken tile. + if (_variantCatalog.Value.Count > 0) + { + var validColored = new HashSet(_variantCatalog.Value.Keys, + StringComparer.OrdinalIgnoreCase); + var orphanColored = await db.StoreItems + .Where(i => i.Slug.StartsWith(WardrobeColoredPrefix)) + .Select(i => new { i.Id, i.Slug }) + .ToListAsync(); + var coloredToDelete = orphanColored + .Where(r => !validColored.Contains(r.Slug)) + .Select(r => r.Id) + .ToList(); + if (coloredToDelete.Count > 0) + { + await db.StoreItems + .Where(i => coloredToDelete.Contains(i.Id)) + .ExecuteDeleteAsync(); + existing = await db.StoreItems.Select(i => i.Slug).ToListAsync(); + existingSet = new HashSet(existing, StringComparer.OrdinalIgnoreCase); + } + } + + var added = false; + foreach (var entry in defaults) + { + if (existingSet.Contains(entry.Slug)) continue; + db.StoreItems.Add(new StoreItemEntity + { + Slug = entry.Slug, + DisplayName = entry.Name, + Description = entry.Desc, + Category = entry.Category, + Price = StoreItemPrice, + CurrencyType = 2, + ImageName = entry.Image, + Storefront = entry.Storefront, + IsActive = true, + IsLimitedTime = entry.Storefront.StartsWith("giftdrop:"), + }); + added = true; + } + if (added) await db.SaveChangesAsync(); + + await SeedGameCatalogAdditionsAsync(existingSet); + await SeedEquipmentSkinsAsync(existingSet); + await SeedClothingFromGameAsync(existingSet); + await SeedColorVariantsAsync(existingSet); + await NormalizeStorePricesAsync(); + } + + private async Task SeedGameCatalogAdditionsAsync(HashSet existingSlugs) + { + var added = false; + + foreach (var dye in PermanentHairDyes) + { + if (existingSlugs.Contains(dye.Slug)) continue; + db.StoreItems.Add(new StoreItemEntity + { + Slug = dye.Slug, + DisplayName = dye.Name, + Description = "Permanent hair dye.", + Category = "consumable", + Price = StoreItemPrice, + CurrencyType = 2, + ImageName = string.Empty, + Storefront = "main", + IsActive = true, + IsLimitedTime = false, + }); + existingSlugs.Add(dye.Slug); + added = true; + } + + if (added) await db.SaveChangesAsync(); + } + + // ── Equipment skins ──────────────────────────────────────────── + // Source: data/equipment_skins.json. The old client does not infer + // equipment reward payloads from a store slug; it expects the exact + // PrefabName + ModificationGuid pair from the equipment skin runtime + // config. Keep those values data-driven so new verified skin rows can + // be added without changing controller code. + public sealed class EquipmentSkinEntry + { + public string prefab_name { get; set; } = string.Empty; + public string modification_guid { get; set; } = string.Empty; + public string display_name { get; set; } = string.Empty; + public string tooltip { get; set; } = string.Empty; + public int rarity { get; set; } + public string storefront { get; set; } = "main"; + public long price { get; set; } = StoreItemPrice; + public string image_name { get; set; } = string.Empty; + } + + private sealed class EquipmentSkinFile + { + public List items { get; set; } = new(); + } + + private static readonly Lazy> _equipmentSkinCatalog = + new(LoadEquipmentSkinCatalog); + + public static IReadOnlyDictionary EquipmentSkinCatalog => + _equipmentSkinCatalog.Value; + + private static Dictionary LoadEquipmentSkinCatalog() + { + var candidates = new[] + { + Path.Combine(AppContext.BaseDirectory, "data", "equipment_skins.json"), + Path.Combine(AppContext.BaseDirectory, "Data", "equipment_skins.json"), + Path.Combine(Directory.GetCurrentDirectory(), "data", "equipment_skins.json"), + Path.Combine(Directory.GetCurrentDirectory(), "DorkNet.Server", "Data", "equipment_skins.json"), + }; + var path = candidates.FirstOrDefault(File.Exists); + if (path is null) return new(); + + try + { + using var fs = File.OpenRead(path); + var data = System.Text.Json.JsonSerializer.Deserialize(fs, + new System.Text.Json.JsonSerializerOptions { PropertyNameCaseInsensitive = true }) + ?? new EquipmentSkinFile(); + + var bySlug = new Dictionary(StringComparer.OrdinalIgnoreCase); + foreach (var item in data.items) + { + if (string.IsNullOrWhiteSpace(item.prefab_name) || + string.IsNullOrWhiteSpace(item.modification_guid)) + continue; + + bySlug[EquipmentSkinSlug(item.prefab_name, item.modification_guid)] = item; + } + return bySlug; + } + catch + { + return new(); + } + } + + public static string EquipmentSkinSlug(string prefabName, string modificationGuid) + { + prefabName = (prefabName ?? string.Empty).Trim(); + modificationGuid = (modificationGuid ?? string.Empty).Trim(); + return $"{EquipmentSkinPrefix}{prefabName}:{modificationGuid}"; + } + + public static bool TryGetEquipmentPayload( + string? slug, + out string prefabName, + out string modificationGuid, + out EquipmentSkinEntry? entry) + { + prefabName = string.Empty; + modificationGuid = string.Empty; + entry = null; + if (string.IsNullOrWhiteSpace(slug)) return false; + + if (_equipmentSkinCatalog.Value.TryGetValue(slug, out entry)) + { + prefabName = entry.prefab_name; + modificationGuid = entry.modification_guid; + return true; + } + + if (!slug.StartsWith(EquipmentSkinPrefix, StringComparison.OrdinalIgnoreCase)) + return false; + + var payload = slug[EquipmentSkinPrefix.Length..]; + var split = payload.IndexOf(':'); + if (split <= 0 || split >= payload.Length - 1) return false; + prefabName = payload[..split]; + modificationGuid = payload[(split + 1)..]; + return !string.IsNullOrWhiteSpace(prefabName) + && !string.IsNullOrWhiteSpace(modificationGuid); + } + + public static bool TryGetEquipmentPayload( + string? slug, + out string prefabName, + out string modificationGuid) => + TryGetEquipmentPayload(slug, out prefabName, out modificationGuid, out _); + + private async Task SeedEquipmentSkinsAsync(HashSet existingSlugs) + { + var catalog = _equipmentSkinCatalog.Value; + if (catalog.Count == 0) return; + + var added = false; + foreach (var (slug, entry) in catalog) + { + if (existingSlugs.Contains(slug)) continue; + var storefront = string.IsNullOrWhiteSpace(entry.storefront) ? "main" : entry.storefront; + + db.StoreItems.Add(new StoreItemEntity + { + Slug = slug, + DisplayName = string.IsNullOrWhiteSpace(entry.display_name) + ? $"{entry.prefab_name} skin" + : entry.display_name, + Description = string.IsNullOrWhiteSpace(entry.tooltip) + ? "Equipment skin." + : entry.tooltip, + Category = "weapon", + Price = entry.price > 0 ? entry.price : StoreItemPrice, + CurrencyType = 2, + ImageName = entry.image_name, + Storefront = storefront, + IsActive = true, + IsLimitedTime = storefront.StartsWith("giftdrop:", StringComparison.OrdinalIgnoreCase), + }); + existingSlugs.Add(slug); + added = true; + } + + if (added) await db.SaveChangesAsync(); + } + + // ── Color variants ────────────────────────────────────────────── + // Source: data/color_variants.json — generated from a static walk + // of the 2020 game's outfits_assets_*.bundle files (Python script + // at tools/build-color-variants.py). Each entry maps a swatch + // (with item GUID + mask GUID) to a color name. We seed one + // StoreItem per (item, swatch) with slug + // wardrobe-colored-{itemGuid}-{colorLower} + // so TryGetAvatarItemPayload can recover the desc on demand. + // + // The pure wardrobe-{itemGuid} slugs (no color) keep working for + // items without color variants, and for legacy single-color SKUs + // — those still emit ",,," and the watch picks a default. + public const string WardrobeColoredPrefix = "wardrobe-colored-"; + + public sealed class ColorVariantEntry + { + public string swatch_name { get; set; } = string.Empty; + public string swatch_guid { get; set; } = string.Empty; + public string color { get; set; } = string.Empty; + public string item_name { get; set; } = string.Empty; + public string item_guid { get; set; } = string.Empty; + public int? item_outfit_type { get; set; } + public string mask_guid { get; set; } = string.Empty; + } + private sealed class ColorVariantFile + { + public List matches { get; set; } = new(); + } + + private static readonly Lazy> _variantCatalog = + new(LoadColorVariantCatalog); + + private static Dictionary LoadColorVariantCatalog() + { + var candidates = new[] + { + Path.Combine(AppContext.BaseDirectory, "data", "color_variants.json"), + Path.Combine(AppContext.BaseDirectory, "Data", "color_variants.json"), + Path.Combine(Directory.GetCurrentDirectory(), "data", "color_variants.json"), + }; + var path = candidates.FirstOrDefault(File.Exists); + if (path is null) return new(); + try + { + using var fs = File.OpenRead(path); + var data = System.Text.Json.JsonSerializer.Deserialize(fs, + new System.Text.Json.JsonSerializerOptions { PropertyNameCaseInsensitive = true }) + ?? new ColorVariantFile(); + var byKey = new Dictionary(StringComparer.OrdinalIgnoreCase); + foreach (var m in data.matches) + { + if (string.IsNullOrEmpty(m.item_guid) || string.IsNullOrEmpty(m.swatch_guid)) continue; + var key = $"{WardrobeColoredPrefix}{m.item_guid}-{m.color}".ToLowerInvariant(); + byKey[key] = m; + } + return byKey; + } + catch { return new(); } + } + + public static IReadOnlyDictionary ColorVariantCatalog => _variantCatalog.Value; + + /// Item GUIDs that have at least one color-variant SKU. The + /// generic wardrobe-{guid} tile is suppressed for these + /// because the colored tiles render the correct swatch; the bare + /// tile would emit {guid},,, and fall back to the default + /// swatch (often a placeholder colour the player didn't pick). + /// + private static readonly Lazy> _itemsWithColorVariants = + new(() => new HashSet( + _variantCatalog.Value.Values.Select(e => e.item_guid), + StringComparer.OrdinalIgnoreCase)); + + private async Task SeedColorVariantsAsync(HashSet existingSlugs) + { + var catalog = _variantCatalog.Value; + if (catalog.Count == 0) return; + + var added = false; + foreach (var (slug, entry) in catalog) + { + if (existingSlugs.Contains(slug)) continue; + + // Friendlier display name: strip parens + slot prefix from + // the item name, then suffix the color. So + // "(Shirt_Lumberjack)" + "Blue" → "Lumberjack — Blue". + var inner = (entry.item_name ?? string.Empty).Trim('(', ')'); + foreach (var s in new[] { "Hat_", "Shirt_", "Head_", "Hair_", "Eye_", "Wrist_", + "Neck_", "Beard_", "Pants_", "Feet_", "Accessory_", + "Belt_", "Hand_" }) + { + if (inner.StartsWith(s)) { inner = inner[s.Length..]; break; } + } + inner = inner.Replace('_', ' ').Trim(); + var displayName = $"{inner} — {entry.color}"; + + db.StoreItems.Add(new StoreItemEntity + { + Slug = slug, + DisplayName = displayName, + Description = $"{entry.color} variant of {inner}.", + Category = ColorVariantCategory(entry.item_outfit_type), + Price = StoreItemPrice, + CurrencyType = 2, + ImageName = string.Empty, + Storefront = "main", + IsActive = true, + IsLimitedTime = false, + }); + existingSlugs.Add(slug); + added = true; + } + if (added) await db.SaveChangesAsync(); + } + + /// Map outfit_type (per avatar_item_lookup.json) to the + /// store Category column. Outfit types: 0=Hat, 2=Hair, 10=Eye, + /// 20=Beard, 100=Neck, 101=Torso, 200=Wrist (others fall through). + private static string ColorVariantCategory(int? outfitType) => outfitType switch + { + 0 => "head", + 2 => "hair", + 10 or 20 => "head", + 100 or 101 => "torso", + 200 => "accessory", + _ => "accessory", + }; + + private async Task NormalizeStorePricesAsync() + { + await db.StoreItems + .Where(i => i.Price != StoreItemPrice) + .ExecuteUpdateAsync(s => s.SetProperty(i => i.Price, StoreItemPrice)); + } + + /// + /// Seed the storefront from + /// + /// — every AvatarItem GUID in the watch's bundled + /// AvatarItemWardrobeRuntimeConfig.avatarItemDataLookup + /// becomes one tile. Slug format wardrobe-{avatarItemGuid} + /// so the watch's `PurchasableGiftDrop` flow can extract the GUID + /// from the slug at time + /// and put it in AvatarItemDesc for the watch to render + /// the 3D preview prefab in the tile. + /// + /// The previous implementation pulled from + /// data/wardrobe_items.json (Swatch/Mask MonoBehaviour + /// GUIDs extracted from outfits_assets_*.bundle). Those + /// were ASSET GUIDs, not AvatarItem GUIDs, so the watch couldn't + /// resolve them in avatarItemPrefabLookup → tiles came + /// out blank. The new source uses the same dump + /// AvatarItemsController validates against, so every + /// store tile is guaranteed to render. + /// + private async Task SeedClothingFromGameAsync(HashSet existingSlugs) + { + var catalog = DorkNet.Server.Controllers.API.Avatar.V4.AvatarItemsController.Catalog; + if (catalog.Count == 0) return; + + // Backfill prices on existing wardrobe rows that were seeded + // with the previous Price=0 default — store UI shows "0 + // tokens" on every tile otherwise. Idempotent: only updates + // rows whose price is still 0. + var stalePriceRows = await db.StoreItems + .Where(i => i.Slug.StartsWith("wardrobe-") && i.Price == 0) + .Select(i => new { i.Id, i.Slug }) + .ToListAsync(); + if (stalePriceRows.Count > 0) + { + foreach (var r in stalePriceRows) + { + var guid = r.Slug.Substring("wardrobe-".Length); + await db.StoreItems + .Where(s => s.Id == r.Id) + .ExecuteUpdateAsync(s => s.SetProperty(p => p.Price, StoreItemPrice)); + } + } + + var added = false; + foreach (var (guid, dto) in catalog) + { + var slug = $"wardrobe-{guid}"; + if (existingSlugs.Contains(slug)) continue; + existingSlugs.Add(slug); + db.StoreItems.Add(new StoreItemEntity + { + Slug = slug, + DisplayName = string.IsNullOrEmpty(dto.FriendlyName) ? guid : dto.FriendlyName, + Description = "Wardrobe item.", + // Category just routes the tile to a tab in the Shop + // UI; we map AvatarItemsController.Slot codes to + // human-readable category labels. + Category = CategoryFromFriendlyName(dto.FriendlyName), + Price = StoreItemPrice, + CurrencyType = 2, + ImageName = string.Empty, + Storefront = "main", + IsActive = true, + IsLimitedTime = false, + }); + added = true; + } + if (added) await db.SaveChangesAsync(); + } + + private static int SumCharCodes(string s) + { + int sum = 0; + for (int i = 0; i < s.Length; i++) sum = unchecked(sum * 31 + s[i]); + return sum; + } + + private static string CategoryFromFriendlyName(string name) + { + // Names from the bundled AvatarItemData arrive as e.g. "Hat + // Wizard", "Hair AngledBob", "Beard Close", "Eye 2020Glasses", + // "Belt PaintballBelt" — the FIRST word is the slot label. + // Map to the same bucket strings the previous catch-all used + // so any UI code still grouping by category keeps working. + var space = name.IndexOf(' '); + var head = (space < 0 ? name : name[..space]).ToLowerInvariant(); + return head switch + { + "hat" or "helmet" or "cap" or "crown" or "beanie" or "mask" => "head", + "hair" => "hair", + "eye" or "glass" or "glasses" or "goggle" or "goggles" => "face", + "mouth" or "beard" or "stache" or "moustache" or "mustache" + or "copstache" or "seventiesstache" => "face", + "shirt" or "jacket" or "hoodie" or "jumpsuit" or "tshirt" or "blouse" + or "dress" or "vest" or "torso" or "babydolldress" => "torso", + "pant" or "short" or "skirt" or "trouser" => "legs", + "shoe" or "boot" or "sock" or "sneaker" => "feet", + "neck" or "tie" or "bowtie" or "businesstie" or "cowboyscarf" + or "archerquiver" or "quiver" => "neck", + "belt" => "belt", + "wrist" or "glove" or "watch" or "partyband" => "wrist", + _ => "accessory", + }; + } + + public async Task> GetActiveByStorefrontAsync(string storefront) => + await db.StoreItems + .Where(i => i.IsActive && i.Storefront == storefront && + (i.AvailableUntil == null || i.AvailableUntil > DateTime.UtcNow)) + .OrderBy(i => i.Category).ThenBy(i => i.Price) + .ToListAsync(); + + public async Task> GetRoomStorefrontItemsAsync(int storefrontType) + { + var key = StorefrontKeyForType(storefrontType); + var keys = new[] { key, RroStorefrontKey, AllStorefrontKey }; + var rows = await db.StoreItems + .Where(i => i.IsActive && keys.Contains(i.Storefront) && + (i.AvailableUntil == null || i.AvailableUntil > DateTime.UtcNow)) + .OrderBy(i => i.Storefront == key ? 0 : + i.Storefront == RroStorefrontKey ? 1 : 2) + .ThenBy(i => i.Category) + .ThenBy(i => i.Price) + .ToListAsync(); + + if (rows.Any(IsRenderableGiftDrop) || + !config.GetValue("Store:FallbackEmptyRoomStorefrontsToWatch", true)) + { + return rows; + } + + return await GetRenderableGiftDropItemsAsync(); + } + + public async Task> GetAllActiveAsync() => + await db.StoreItems + .Where(i => i.IsActive && + (i.AvailableUntil == null || i.AvailableUntil > DateTime.UtcNow)) + .OrderBy(i => i.Storefront).ThenBy(i => i.Category).ThenBy(i => i.Price) + .ToListAsync(); + + public async Task> GetRenderableGiftDropItemsAsync() + { + // The March 2020 client imports Watch storefront gift drops into + // RRUI.Cache.AvatarItemCache and binds them through + // DynamicAvatarItemImposter. Only publish slots that the decompiled + // watch/store filters handle; unsupported OutfitTypes can enter the + // avatar cache and crash during tile preview binding. + if (!EnableWatchGiftDrops) return new List(); + + var rows = await db.StoreItems + .Where(i => i.IsActive && + (i.AvailableUntil == null || i.AvailableUntil > DateTime.UtcNow)) + .OrderBy(i => i.Category).ThenBy(i => i.DisplayName) + .ToListAsync(); + + var filtered = rows + .Where(IsRenderableGiftDrop) + .ToList(); + + var max = MaxWatchGiftDrops; + return max > 0 ? filtered.Take(max).ToList() : filtered; + } + + public Task GetBySlugAsync(string slug) => + db.StoreItems.FirstOrDefaultAsync(i => i.Slug == slug); + + public Task GetByIdAsync(long id) => + db.StoreItems.FirstOrDefaultAsync(i => i.Id == id); + + public sealed record PurchaseResult(bool Success, string? Error, long? Balance, string? Slug); + + /// Atomic purchase: re-checks balance, deducts currency, + /// appends the item to the player's inventory. Idempotent on + /// already-owned items (returns Success=true without re-charging). + /// Returns the resulting balance and the slug granted. + public async Task PurchaseAsync(long playerId, long itemId) + { + var item = await GetByIdAsync(itemId); + if (item is null || !item.IsActive) + return new(false, "item_not_available", null, null); + if (item.AvailableUntil is { } until && until <= DateTime.UtcNow) + return new(false, "item_expired", null, null); + + var balance = await level.GetBalanceAsync(playerId, item.CurrencyType); + if (balance < item.Price) + return new(false, "insufficient_funds", balance, null); + + var avatar = await db.Avatars.FirstOrDefaultAsync(a => a.PlayerId == playerId); + if (avatar is null) + { + avatar = new AvatarEntity { PlayerId = playerId }; + db.Avatars.Add(avatar); + } + + if (TryGetAvatarItemPayload(item.Slug, out _, out var avatarItemDesc)) + { + var guid = InventoryAvatarItemDesc(avatarItemDesc); + var ownedGuids = ParseWardrobeInventory(avatar.InventoryJson); + if (ownedGuids.Contains(guid, StringComparer.OrdinalIgnoreCase)) + return new(true, "already_owned", balance, item.Slug); + + var newWardrobeBalance = await level.GrantCurrencyAsync( + playerId, item.CurrencyType, -item.Price, $"purchase:{item.Slug}"); + ownedGuids.Add(guid); + avatar.InventoryJson = System.Text.Json.JsonSerializer.Serialize(ownedGuids); + avatar.UpdatedAt = DateTime.UtcNow; + await db.SaveChangesAsync(); + return new(true, null, newWardrobeBalance, item.Slug); + } + + // Idempotency: own-already → return success without re-charging. + var inventory = ParseInventory(avatar.InventoryJson); + var existing = inventory.FirstOrDefault(e => e.ItemId == item.Slug); + if (existing is not null) + { + if (string.Equals(item.Category, "consumable", StringComparison.OrdinalIgnoreCase)) + { + var newBalanceConsumable = await level.GrantCurrencyAsync( + playerId, item.CurrencyType, -item.Price, $"purchase:{item.Slug}"); + existing.Quantity += 1; + avatar.InventoryJson = System.Text.Json.JsonSerializer.Serialize(inventory); + avatar.UpdatedAt = DateTime.UtcNow; + await db.SaveChangesAsync(); + return new(true, null, newBalanceConsumable, item.Slug); + } + return new(true, "already_owned", balance, item.Slug); + } + + var newBalance = await level.GrantCurrencyAsync( + playerId, item.CurrencyType, -item.Price, $"purchase:{item.Slug}"); + inventory.Add(new InventoryEntry { ItemId = item.Slug, Quantity = 1 }); + avatar.InventoryJson = System.Text.Json.JsonSerializer.Serialize(inventory); + avatar.UpdatedAt = DateTime.UtcNow; + await db.SaveChangesAsync(); + return new(true, null, newBalance, item.Slug); + } + + private static List ParseWardrobeInventory(string? json) + { + if (string.IsNullOrWhiteSpace(json) || json == "[]") return new(); + try + { + var strings = System.Text.Json.JsonSerializer.Deserialize>(json); + if (strings is not null) return strings; + } + catch { } + + try + { + var entries = ParseInventory(json); + return entries + .Select(e => e.ItemId.StartsWith("wardrobe-", StringComparison.Ordinal) + ? e.ItemId["wardrobe-".Length..] + : e.ItemId) + .Select(InventoryAvatarItemDesc) + .Where(s => !string.IsNullOrWhiteSpace(s)) + .Distinct(StringComparer.OrdinalIgnoreCase) + .ToList(); + } + catch { return new(); } + } + + public sealed class InventoryEntry + { + [System.Text.Json.Serialization.JsonPropertyName("itemId")] + public string ItemId { get; set; } = string.Empty; + [System.Text.Json.Serialization.JsonPropertyName("quantity")] + public int Quantity { get; set; } + } + + public static List ParseInventory(string? json) + { + if (string.IsNullOrWhiteSpace(json) || json == "[]") return new(); + try + { + return System.Text.Json.JsonSerializer + .Deserialize>(json) ?? new(); + } + catch { return new(); } + } + + /// + /// GiftDropStorefront wire shape, verified against ISIL: + /// GiftDropStorefront.Deserialize → reads "StoreItems" + /// (Cpp2IL_ISIL/.../GiftDropStorefront.txt:98) + /// StoreItems is List<PurchasableGiftDrop> + /// PurchasableGiftDrop.Deserialize → calls PurchasableItem.Deserialize + /// then reads "GiftDrops" as List<StorefrontGiftDrop> + /// (PurchasableGiftDrop.txt:118) + /// PurchasableItem.Deserialize → REQUIRED keys + /// PurchasableItemId / Type / IsFeatured / Prices / SubscriberPrices + /// (PurchasableItem.txt:592-654) + /// PurchasablePrice.Deserialize → CurrencyType / Price + /// (PurchasablePrice.txt:87-94) + /// StorefrontGiftDrop.Deserialize → REQUIRED GiftDropId / Rarity + /// plus optional FriendlyName / Tooltip / AvatarItemDesc / + /// AvatarItemType / ConsumableItemDesc / EquipmentPrefabName / + /// EquipmentModificationGuid / IsQuery / Unique / SubscribersOnly / + /// Level / Context (StorefrontGiftDrop.txt:829-924). + /// + /// Each StoreItemEntity becomes one PurchasableGiftDrop holding a + /// single StorefrontGiftDrop pointing at the associated wardrobe + /// AvatarItem GUID. Only wardrobe-{guid}-prefixed slugs are + /// emitted because the 2020 watch storefront binds every gift-drop + /// tile through BrowsableAvatarItem. Sending a gift drop with + /// AvatarItemType=Outfit and an empty AvatarItemDesc leaves that + /// binding half-populated and can crash the client while rendering + /// the shop grid. + /// + public static object ToStorefrontDto(string name, IEnumerable items, string apex = "rec.net", int storefrontType = 3) + { + var storeItems = items + .Where(IsRenderableGiftDrop) + .Select((i, idx) => BuildPurchasableGiftDrop(i, idx)) + .ToArray(); + return new + { + StorefrontType = storefrontType, + StorefrontName = name, + NextUpdate = "9999-12-31T00:00:00Z", + NewUntil = "9999-12-31T00:00:00Z", + StoreItems = storeItems, + }; + } + + private static bool IsRenderableGiftDrop(StoreItemEntity item) + { + if (TryGetHairDyePayload(item.Slug, out _)) return true; + if (TryGetConsumableItemDesc(item.Slug, out _)) return true; + if (TryGetEquipmentPayload(item.Slug, out _, out _)) return true; + + if (!TryGetAvatarItemPayload(item.Slug, out var avatarItemType, out var avatarItemDesc)) + return false; + if (avatarItemType != StoreAvatarItemTypeOutfit) return false; + + var guid = avatarItemDesc.Split(',', 2)[0]; + return AvatarItemsController.TryGetOutfitType(guid, out var outfitType) + && IsWatchStorefrontOutfitType(outfitType); + } + + private static bool IsWatchStorefrontOutfitType(int outfitType) => outfitType is + AvatarItemsController.Slot.Hat or + AvatarItemsController.Slot.BackHead or + AvatarItemsController.Slot.Hair or + AvatarItemsController.Slot.Eye or + AvatarItemsController.Slot.Mouth or + AvatarItemsController.Slot.Neck or + AvatarItemsController.Slot.Shirt or + AvatarItemsController.Slot.Belt or + AvatarItemsController.Slot.Pocket or + AvatarItemsController.Slot.Wrist or + AvatarItemsController.Slot.Glove or + AvatarItemsController.Slot.Watch or + AvatarItemsController.Slot.TeamShirt or + AvatarItemsController.Slot.TeamWrist; + + /// One PurchasableGiftDrop carrying one StorefrontGiftDrop. + /// PurchasableGiftDrop inherits PurchasableItem so we ship the + /// PurchasableItem keys plus the GiftDrops list in the same + /// object. + private static object BuildPurchasableGiftDrop(StoreItemEntity i, int positionalIndex) + { + // PurchasableItemId is an int and must be unique within the + // storefront — we use the StoreItem row id, capped to int range. + var itemId = (int)(i.Id & 0x7fffffff); + + var price = new + { + CurrencyType = i.CurrencyType, + Price = (int)i.Price, + }; + + // Pull the AvatarItem GUID out of the slug for wardrobe-imported + // entries. Hand-curated dorknet-* SKUs leave AvatarItemDesc + // empty — they're cosmetic store tiles only. + // + // CRITICAL: the watch's AvatarItem.FromRecNetString does + // String.Split(',') and indexes customization[0..2] via + // ArraySegmentEnumerator.get_Current — anything shorter than + // four comma-separated parts throws ArgumentException ("Offset + // and length were out of bounds…") AND, because that exception + // bubbles up through OutfitManager.IsAvatarItemAlreadyPurchased + // → WatchStoreItemButton.UpdateButtonInteractability → the + // store's UI grid render path, the watch crashes mid-tile. Bare + // {guid} = 1 part → instant crash on first store-tile + // hover; {guid},,, = 4 parts (guid + 3 empty + // customizations) → safe. AvatarItemsController.Get() applies + // the same suffix to /api/avatar/v4/items entries; we apply it + // here too. + TryGetAvatarItemPayload(i.Slug, out var avatarItemType, out var avatarItemDesc); + TryGetConsumableItemDesc(i.Slug, out var consumableItemDesc); + TryGetEquipmentPayload(i.Slug, out var equipmentPrefabName, out var equipmentModificationGuid, out var equipmentEntry); + var isHairDye = TryGetHairDyePayload(i.Slug, out var consumableHairDyeDesc, out var hairDyeColorGuid); + if (isHairDye) + { + avatarItemType = StoreAvatarItemTypeHairDye; + avatarItemDesc = hairDyeColorGuid; + consumableItemDesc = consumableHairDyeDesc; + } + + var giftDrop = new + { + // REQUIRED. + GiftDropId = itemId, + Rarity = MapRarity(i.Category), + // Optional / nullable string fields the watch reads. + FriendlyName = i.DisplayName, + Tooltip = i.Description, + AvatarItemDesc = avatarItemDesc, + AvatarItemDescOrHairDyeDesc = avatarItemDesc, + AvatarItemType = string.IsNullOrEmpty(avatarItemDesc) ? (int?)null : avatarItemType, + ConsumableItemDesc = consumableItemDesc, + EquipmentPrefabName = equipmentPrefabName, + EquipmentModificationGuid = equipmentModificationGuid, + IsQuery = false, + Unique = true, + SubscribersOnly = false, + Level = 1, + Context = 0, + Content = isHairDye ? GiftBoxContentHairDye : 0, + EquipmentRarity = equipmentEntry?.rarity ?? 0, + }; + + return new + { + // Base PurchasableItem keys. + PurchasableItemId = itemId, + Type = 0, // PurchasableItemType.GiftDrop + IsFeatured = positionalIndex < 12, // first dozen show in Featured tab + Prices = new[] { price }, + SubscriberPrices = Array.Empty(), + // PurchasableGiftDrop adds GiftDrops on top. + GiftDrops = new[] { giftDrop }, + }; + } + + /// GiftManager.GiftRarity wire values: + /// None=-1, Common=0, Uncommon=10, Rare=20, Epic=30, Legendary=50. + private static int MapRarity(string? category) => category?.ToLowerInvariant() switch + { + "consumable" => 0, + "head" or "hair" or "face" => 10, + "torso" or "neck" or "belt" or "wrist" => 20, + "accessory" => 30, + "roomtemplate" => 50, + _ => 0, + }; + + public static bool TryGetAvatarItemPayload(string? slug, out int avatarItemType, out string avatarItemDesc) + { + avatarItemType = StoreAvatarItemTypeOutfit; + avatarItemDesc = string.Empty; + if (string.IsNullOrWhiteSpace(slug)) return false; + + // Colored variant slug (wardrobe-colored--) — + // resolve via the static catalog to get the swatch + mask + // GUIDs. AvatarItem RecNet desc format verified against the + // watch's AvatarItemVisualData.ToRecNetString (Cpp2IL_ISIL/.../ + // RecRoom/Avatar/Data/Shared/AvatarItemVisualData.txt:474-585): + // ,,, + // — swatch BEFORE mask, not the other way around. Earlier code + // had them flipped, which made the watch read the mask GUID as + // a swatch GUID, fail to resolve it, and fall back to the + // default swatch. + if (slug.StartsWith(WardrobeColoredPrefix, StringComparison.OrdinalIgnoreCase)) + { + if (_variantCatalog.Value.TryGetValue(slug.ToLowerInvariant(), out var entry)) + { + avatarItemDesc = $"{entry.item_guid},{entry.swatch_guid},{entry.mask_guid},"; + return true; + } + // Fall through to wardrobe- handling if not in catalog. + } + + if (slug.StartsWith(WardrobePrefix, StringComparison.Ordinal)) + { + avatarItemDesc = slug[WardrobePrefix.Length..] + ",,,"; + return true; + } + + return false; + } + + public static string InventoryAvatarItemDesc(string avatarItemDesc) + { + if (string.IsNullOrWhiteSpace(avatarItemDesc)) return string.Empty; + return avatarItemDesc.EndsWith(",,,", StringComparison.Ordinal) + ? avatarItemDesc[..^3] + : avatarItemDesc; + } + + public static bool TryGetConsumableItemDesc(string? slug, out string consumableItemDesc) + { + consumableItemDesc = string.Empty; + if (string.IsNullOrWhiteSpace(slug)) return false; + + if (TryGetHairDyePayload(slug, out consumableItemDesc, out _)) return true; + + return false; + } + + public static bool TryGetHairDyePayload(string? slug, out string hairDyeDesc) + { + return TryGetHairDyePayload(slug, out _, out hairDyeDesc); + } + + public static bool TryGetHairDyeName(string? slug, out string name) + { + name = string.Empty; + if (string.IsNullOrWhiteSpace(slug)) return false; + + foreach (var dye in PermanentHairDyes) + { + if (!string.Equals(slug, dye.Slug, StringComparison.OrdinalIgnoreCase)) continue; + name = dye.Name; + return true; + } + + return false; + } + + public static bool TryGetHairDyePayload(string? slug, out string consumableDesc, out string hairColorGuid) + { + consumableDesc = string.Empty; + hairColorGuid = string.Empty; + if (string.IsNullOrWhiteSpace(slug)) return false; + + foreach (var dye in PermanentHairDyes) + { + if (!string.Equals(slug, dye.Slug, StringComparison.OrdinalIgnoreCase)) continue; + consumableDesc = dye.ConsumableDesc; + hairColorGuid = dye.HairColorGuid; + return true; + } + + return false; + } + + public static object ToItemDto(StoreItemEntity i, string apex = "rec.net") => new + { + ItemId = i.Id, + Slug = i.Slug, + Name = i.DisplayName, + DisplayName = i.DisplayName, + Description = i.Description, + Category = i.Category, + ImageName = i.ImageName, + ImageUrl = string.IsNullOrEmpty(i.ImageName) ? "" : $"https://cdn.{apex}/{i.ImageName}", + // Price block — both flat and nested forms so whichever shape + // the watch's deserialiser reads, it sees a price. + Price = i.Price, + CurrencyType = i.CurrencyType, + Cost = new { Amount = i.Price, CurrencyType = i.CurrencyType }, + IsActive = i.IsActive, + IsLimitedTime = i.IsLimitedTime, + AvailableUntil = i.AvailableUntil, + }; +} diff --git a/DorkNet.Server/Startup/DatabaseBootstrap.cs b/DorkNet.Server/Startup/DatabaseBootstrap.cs new file mode 100644 index 0000000..6e38b2b --- /dev/null +++ b/DorkNet.Server/Startup/DatabaseBootstrap.cs @@ -0,0 +1,257 @@ +using Microsoft.EntityFrameworkCore; +using DorkNet.Server.Data; +using DorkNet.Server.Services; +using Serilog; + +namespace DorkNet.Server.Startup; + +/// Boot-time database orchestration: schema migration, +/// idempotent data upgrades, and canonical seed application. Replaces +/// the inline block previously in Program.cs. +/// +/// Provider-specific paths: +/// +/// SQLite — Database.Migrate() after +/// reconciles any legacy +/// migration-history state. +/// Postgres — EnsureCreated behind a transaction-scoped +/// advisory lock so concurrent replica boots don't race on +/// CREATE TABLE. Connect retries cover the +/// container-orchestrated boot window where Postgres DNS isn't +/// resolvable yet. +/// +/// After the schema step both branches run the same idempotent +/// post-init sequence: system-account seed, avatar-seed self-heal, +/// starter-wallet backfill, dorm backfill, +/// data transforms, canonical room seeding + overrides, and per-feature +/// seeders. The /healthz probe flips to 200 only after all of +/// this completes — Coolify's rolling deploy holds traffic at the LB +/// until then. +public static class DatabaseBootstrap +{ + public static async Task RunDatabaseBootstrapAsync(this WebApplication app) + { + using var scope = app.Services.CreateScope(); + var db = scope.ServiceProvider.GetRequiredService(); + + if (db.Database.IsSqlite()) + { + BaselineExistingSchemaIfNeeded(db); + db.Database.Migrate(); + } + else + { + // Postgres path: schema generated from the EF Core model via + // EnsureCreated. Wrapped in a transaction-scoped advisory lock + // so concurrent replicas booting against the same database + // don't race on CREATE TABLE — only the first replica to take + // the lock runs EnsureCreated, the rest see "schema already + // exists" and proceed. + // + // Lock key 0x444F524B (ascii "DORK") is hard-coded; it just + // needs to be the same int64 across replicas and not collide + // with anything else in the database. pg_advisory_xact_lock + // releases automatically on COMMIT, so a crashed replica can't + // wedge the next boot. + var conn = db.Database.GetDbConnection(); + + // Retry initial Postgres connect — on container-orchestrated boots + // (Coolify, docker-compose) the Postgres service's DNS often isn't + // resolvable yet when this app starts, surfacing as SocketException + // errno=11 EAGAIN from Npgsql's Dns.GetHostEntryOrAddressesCore. + // Postgres itself can also be up-but-not-accepting-connections for + // a few seconds while it replays WAL. 15 attempts × ~2s backoff + // covers the typical 5-30s warmup window without looping forever + // on a permanently broken config. + var connectLogger = app.Services.GetRequiredService() + .CreateLogger("DorkNet.Server.Bootstrap"); + for (var attempt = 1; ; attempt++) + { + try { await conn.OpenAsync(); break; } + catch (Exception ex) when (attempt < 15 && + (ex is System.Net.Sockets.SocketException + || ex is Npgsql.NpgsqlException + || ex is System.Data.Common.DbException)) + { + var delay = TimeSpan.FromSeconds(Math.Min(attempt, 5)); + connectLogger.LogWarning( + "[bootstrap] Postgres connect attempt {Attempt}/15 failed ({Type}: {Message}); retrying in {Delay}s", + attempt, ex.GetType().Name, ex.Message, delay.TotalSeconds); + await Task.Delay(delay); + } + } + await using var tx = await conn.BeginTransactionAsync(); + await using (var lockCmd = conn.CreateCommand()) + { + lockCmd.Transaction = tx; + lockCmd.CommandText = "SELECT pg_advisory_xact_lock(1146246987);"; + await lockCmd.ExecuteNonQueryAsync(); + } + db.Database.EnsureCreated(); + await tx.CommitAsync(); + + await conn.CloseAsync(); + } + + // Coach system account at Player.Id=1. The RR-Original room seeder + // below assigns CreatorPlayerId=1 to every canonical room + // (RecCenter, Paintball, Dodgeball, etc.), so the FK target needs + // to exist before we insert rooms. Idempotent: the call no-ops once + // the row is there. Must run BEFORE roomService.SeedAsync(). + var playerService = scope.ServiceProvider.GetRequiredService(); + await playerService.EnsureSystemAccountAsync(); + // Self-heal the AvatarEntity seed on every startup so accounts + // created with old/broken starter values get the current safe + // GUIDs without needing a one-shot EF migration. + await playerService.EnsureAvatarSeedAsync(); + + // Grant the starter RecCenterTokens balance to any account that + // doesn't yet have a CurrencyType=2 row. Pre-existing accounts + // booted into a 0-token wallet and saw "Not enough tokens" on + // every store tile — this brings them up to the same starting + // state new accounts get. Idempotent. + await playerService.BackfillStarterWalletsAsync(); + + // Backfill the per-player dorm room + DormStateEntity for any + // legacy account that signed in via a path that didn't run + // EnsurePersonalDormAsync. Without these rows the watch's + // /api/rooms/v4/details/{dormId} → cdn.{apex}/room/{blobName} + // chain hits an empty CurrentDataBlobName. + var roomService = scope.ServiceProvider.GetRequiredService(); + await roomService.EnsureDormsForAllPlayersAsync(); + + // Idempotent data-transform pass for things that can't be expressed + // as an EF migration against the entity model. MUST run before + // SeedAsync() so the seed sees the post-rename state. + var legacyLogger = app.Services.GetRequiredService() + .CreateLogger("DorkNet.Server.LegacyUpgrades"); + await LegacyUpgrades.RunAsync(db, app.Configuration, legacyLogger); + + // Seed canonical Rec Room Original rooms so the watch's "Trending" + // tab has content on first launch. Idempotent — bails if any + // rooms already exist. + await roomService.SeedAsync(); + + // Canonical room overrides: rename BloodMoon → Crescendo, fold + // the paintball-map standalone rooms into Paintball as sub-rooms, + // fold LaserTagHangar into LaserTag, hide MakerRoom + EventRoom + // from browse, and pull down the user-supplied thumbnails. + using (var http = new HttpClient { Timeout = TimeSpan.FromSeconds(10) }) + { + var imagesDir = Path.Combine(AppContext.BaseDirectory, "data", "images"); + await roomService.ApplyCanonicalOverridesAsync(http, imagesDir); + } + + // Seed default curated playlists, club category tags, store catalog. + // Must run after SeedAsync so they can reference RR-Originals. + var playlistService = scope.ServiceProvider.GetRequiredService(); + await playlistService.SeedCuratedAsync(); + var clubService = scope.ServiceProvider.GetRequiredService(); + await clubService.SeedDefaultsAsync(); + var storeService = scope.ServiceProvider.GetRequiredService(); + await storeService.SeedAsync(); + + // Tell the /healthz probe migrations are done. Coolify's rolling + // deploy holds traffic at the LB until this flips and /healthz + // returns 200. + Controllers.Health.HealthController.MigrationsComplete = true; + } + + /// SQLite migration-history reconciliation. Handles two + /// cutover scenarios so legacy DBs boot cleanly without manual SQL: + /// + /// Legacy EnsureCreated — schema exists, no + /// __EFMigrationsHistory. (Pre-migrations DorkNet builds + /// used EnsureCreated for SQLite too.) + /// Stale history after consolidation — history + /// references migration IDs that no longer exist in the assembly. + /// Happens once when the per-feature migrations are collapsed into + /// a single Initial (see Data/MIGRATIONS.md). + /// + /// In both cases the on-disk schema is canonical; we just need history + /// to reflect the assembly's current migration list so Migrate() + /// no-ops. Fresh DBs short-circuit (no Players table yet) and + /// Migrate() does the full CreateTable run normally. + private static void BaselineExistingSchemaIfNeeded(DorkNetDbContext db) + { + using var conn = db.Database.GetDbConnection(); + conn.Open(); + + using (var probe = conn.CreateCommand()) + { + probe.CommandText = + "SELECT name FROM sqlite_master WHERE type='table' AND name='Players';"; + var hasPlayers = probe.ExecuteScalar() is not null; + if (!hasPlayers) return; // Fresh DB — let Migrate() do its full thing. + } + + var assemblyMigrations = db.Database.GetMigrations().ToList(); + if (assemblyMigrations.Count == 0) return; + + var historyExists = false; + using (var historyProbe = conn.CreateCommand()) + { + historyProbe.CommandText = + "SELECT name FROM sqlite_master WHERE type='table' AND name='__EFMigrationsHistory';"; + historyExists = historyProbe.ExecuteScalar() is not null; + } + + var existingIds = new HashSet(StringComparer.Ordinal); + if (historyExists) + { + using var readCmd = conn.CreateCommand(); + readCmd.CommandText = @"SELECT ""MigrationId"" FROM ""__EFMigrationsHistory"";"; + using var reader = readCmd.ExecuteReader(); + while (reader.Read()) existingIds.Add(reader.GetString(0)); + + if (existingIds.SetEquals(assemblyMigrations)) + return; + } + else + { + using var createHistory = conn.CreateCommand(); + createHistory.CommandText = """ + CREATE TABLE "__EFMigrationsHistory" ( + "MigrationId" TEXT NOT NULL CONSTRAINT "PK___EFMigrationsHistory" PRIMARY KEY, + "ProductVersion" TEXT NOT NULL + ); + """; + createHistory.ExecuteNonQuery(); + } + + using (var wipe = conn.CreateCommand()) + { + wipe.CommandText = @"DELETE FROM ""__EFMigrationsHistory"";"; + wipe.ExecuteNonQuery(); + } + + var productVersion = typeof(DbContext).Assembly.GetName().Version?.ToString() ?? "10.0.0"; + foreach (var id in assemblyMigrations) + { + using var insert = conn.CreateCommand(); + insert.CommandText = """ + INSERT INTO "__EFMigrationsHistory" ("MigrationId", "ProductVersion") + VALUES ($id, $ver); + """; + var pId = insert.CreateParameter(); pId.ParameterName = "$id"; pId.Value = id; insert.Parameters.Add(pId); + var pVr = insert.CreateParameter(); pVr.ParameterName = "$ver"; pVr.Value = productVersion; insert.Parameters.Add(pVr); + insert.ExecuteNonQuery(); + } + + // A previous failed migrate may have left a stale row in + // __EFMigrationsLock — drop the table so Migrate re-creates it + // cleanly. Safe — the lock is per-application-instance and + // we're single-instance here. + using (var dropLock = conn.CreateCommand()) + { + dropLock.CommandText = "DROP TABLE IF EXISTS __EFMigrationsLock;"; + dropLock.ExecuteNonQuery(); + } + + var stale = existingIds.Except(assemblyMigrations).Count(); + var added = assemblyMigrations.Except(existingIds).Count(); + Log.Information( + "[migrations] Reconciled SQLite history: dropped {Stale} stale ids, baselined {Added} assembly migrations ({First} … {Last}).", + stale, added, assemblyMigrations.First(), assemblyMigrations.Last()); + } +} diff --git a/DorkNet.Server/Startup/MiddlewarePipelineExtensions.cs b/DorkNet.Server/Startup/MiddlewarePipelineExtensions.cs new file mode 100644 index 0000000..ae90ca2 --- /dev/null +++ b/DorkNet.Server/Startup/MiddlewarePipelineExtensions.cs @@ -0,0 +1,112 @@ +using DorkNet.Server.Auth; +using DorkNet.Server.Compat; +using DorkNet.Server.Hubs; +using DorkNet.Server.Services; +using Microsoft.AspNetCore.Http.Connections; + +namespace DorkNet.Server.Startup; + +/// Assembles the full middleware pipeline in canonical order. +/// Ordering rules (the comments at each Use call explain the why): +/// +/// Swagger (dev only) +/// HTTPS redirect (Kestrel-direct mode only) +/// WebSocket keepalive override +/// Static hosts (admin/site/feed) BEFORE request tracing because +/// StaticFileMiddleware bypasses Response.Body wrapping +/// Request tracing +/// api.{apex}/admin → admin.{apex} redirect +/// IP-ban (earliest filter, no DB beyond a small lookup) +/// Version detection (gates anonymous calls too) +/// Authentication → Authorization → player-ban +/// MapControllers + SignalR hub +/// +public static class MiddlewarePipelineExtensions +{ + public static void UseDorkNetPipeline(this WebApplication app) + { + var domainCfg = app.Services.GetRequiredService(); + + if (app.Environment.IsDevelopment()) + { + app.UseSwagger(); + app.UseSwaggerUI(); + } + + // HTTP → HTTPS redirect — only kicks in when an HTTPS endpoint is + // configured in appsettings (Kestrel-direct mode). When fronted + // by nginx, both endpoints are effectively HTTP from Kestrel's + // perspective and this is a no-op. + app.UseHttpsRedirection(); + + // 2-minute WebSocket pings are too lazy for cloudflared — the + // tunnel kills the underlying TCP stream after ~90-100s of silence + // even though SignalR's app-layer pings would arrive on schedule. + // Send a ping frame every 30s so the tunnel always sees fresh + // traffic; SignalR's own KeepAliveInterval (15s) stays under this + // floor. + app.UseWebSockets(new Microsoft.AspNetCore.Builder.WebSocketOptions { KeepAliveInterval = TimeSpan.FromSeconds(30) }); + + // Per-host static branches — admin SPA, public site, legacy feed.* — + // all keyed off the configured apex so a single DORKNET_DOMAIN env + // var moves every static host together. + app.MountStaticHost(domainCfg.Sub("admin"), "admin", spaFallback: true); + // Public-facing site at the apex (e.g. localhost + www variant). + // React-router routes are client-side so spaFallback must be true. + app.MountStaticHost(domainCfg.Apex, "site", spaFallback: true); + app.MountStaticHost($"www.{domainCfg.Apex}", "site", spaFallback: true); + // Old feed.* subdomain kept as legacy; new visitors land on the apex. + app.MountStaticHost(domainCfg.Sub("feed"), "feed"); + + // Request/response tracing — emits one structured log per non-health + // request, with the response body included for any 4xx/5xx. Mounted + // AFTER static hosts so SendFileAsync's underlying connection isn't + // wrapped. + app.UseRequestTracing(); + + // Friendly redirect from the old api.{apex}/admin path → admin.{apex}. + app.Use(async (ctx, next) => + { + if (ctx.Request.Host.Host.Equals(domainCfg.Sub("api"), StringComparison.OrdinalIgnoreCase) && + (ctx.Request.Path == "/admin" || ctx.Request.Path.StartsWithSegments("/admin"))) + { + ctx.Response.Redirect($"https://{domainCfg.Sub("admin")}/", permanent: false); + return; + } + await next(); + }); + + // IP-level bans run BEFORE authentication so banned-IP traffic gets + // cut off at the earliest point possible — no JWT round-trip, no DB + // hit beyond the (small) IpBans lookup. + app.UseMiddleware(); + + // Client-version gate. Reads X-DorkNet-Version, validates against + // the per-deployment allow-list, and 426s mismatches before they + // reach a controller. Sits before auth on purpose so anonymous + // calls (login, photon custom auth) are gated too. + app.UseMiddleware(); + + app.UseAuthentication(); + app.UseAuthorization(); + // Sits between auth and the controllers — by this point ctx.User has + // the validated principal (or is empty for anonymous calls). Bans are + // enforced uniformly without each controller having to remember. + app.UseMiddleware(); + app.MapControllers(); + + // SignalR hub on the notify.* subdomain — RequireHost prevents the + // same path from being matched on api.* or other subdomains. + // Pin transport to WebSockets-only: letting SignalR fall back to + // ServerSentEvents or LongPolling makes "is the proxy idle?" a + // per-poll question instead of a stream-level one. WebSockets keep + // one socket hot with our 10s pings. + app.MapHub("/hub/v1", opts => + { + opts.Transports = HttpTransportType.WebSockets; + // SignalR's per-connection idle disconnect — must be > the + // longest keepalive interval in the stack. + opts.TransportSendTimeout = TimeSpan.FromSeconds(30); + }).RequireHost(domainCfg.Sub("notify")); + } +} diff --git a/DorkNet.Server/Startup/RequestTracingExtensions.cs b/DorkNet.Server/Startup/RequestTracingExtensions.cs new file mode 100644 index 0000000..318054c --- /dev/null +++ b/DorkNet.Server/Startup/RequestTracingExtensions.cs @@ -0,0 +1,190 @@ +using DorkNet.Server.Services; + +namespace DorkNet.Server.Startup; + +/// Per-request observability middleware. Emits one structured +/// log line per non-health request, capturing the response body for any +/// 4xx/5xx so we can see exactly what the game is asking for. Also +/// records each request against for the +/// admin SPA's "Player logs" tab when an authenticated player is +/// attached. +/// +/// Skips: +/// +/// Health probes (/healthz) — too noisy. +/// WebSocket upgrades — wrapping the response body breaks the +/// handshake and SignalR's WebSocket transport. +/// CDN host responses — they're binary blobs we don't want to +/// serialize into log lines. +/// +/// +/// Must be registered AFTER +/// so StaticFileMiddleware's SendFileAsync path doesn't have its +/// underlying connection wrapped by a MemoryStream. +public static class RequestTracingExtensions +{ + public static void UseRequestTracing(this WebApplication app) + { + var reqLogger = app.Services.GetRequiredService() + .CreateLogger("DorkNet.Server.HttpTrace"); + var playerLog = app.Services.GetRequiredService(); + + app.Use(async (ctx, next) => + { + if (IsHealthProbe(ctx.Request)) + { + await next(); + return; + } + + // WebSocket upgrades take over the underlying connection — + // wrapping the response body in a MemoryStream confuses the + // upgrade handshake and prevents SignalR's WebSocket transport + // from working. + if (ctx.WebSockets.IsWebSocketRequest) + { + reqLogger.LogInformation( + "[ws] {Method} {Host}{Path}{Query}", + ctx.Request.Method, ctx.Request.Host, ctx.Request.Path, + ctx.Request.QueryString.Value ?? ""); + await next(); + return; + } + + var host = ctx.Request.Host.Host; + var path = ctx.Request.Path.Value ?? ""; + var isStorageLike = host.StartsWith("storage.", StringComparison.OrdinalIgnoreCase) + || host.StartsWith("cdn.", StringComparison.OrdinalIgnoreCase) + || path.Contains("upload", StringComparison.OrdinalIgnoreCase); + + if (isStorageLike) + { + reqLogger.LogInformation( + "[req:start] {Method} {Host}{Path}{Query} contentType={ContentType} contentLength={ContentLength}", + ctx.Request.Method, ctx.Request.Host, ctx.Request.Path, + ctx.Request.QueryString.Value ?? "", ctx.Request.ContentType ?? "", + ctx.Request.ContentLength); + } + + var sw = System.Diagnostics.Stopwatch.StartNew(); + var originalBody = ctx.Response.Body; + var captureResponse = ShouldCaptureResponse(ctx.Request); + MemoryStream? capture = null; + if (captureResponse) + { + capture = new MemoryStream(); + ctx.Response.Body = capture; + } + + string? requestBody = null; + if (ctx.Request.ContentLength is > 0 and < 4096 && IsTextLikeRequest(ctx.Request.ContentType)) + { + ctx.Request.EnableBuffering(); + using var reader = new StreamReader(ctx.Request.Body, leaveOpen: true); + requestBody = await reader.ReadToEndAsync(); + ctx.Request.Body.Position = 0; + } + + Exception? thrown = null; + try + { + await next(); + } + catch (Exception ex) + { + thrown = ex; + throw; + } + finally + { + sw.Stop(); + var status = thrown is null ? ctx.Response.StatusCode : 500; + var responseBody = ""; + + if (capture is not null) + { + try + { + capture.Seek(0, SeekOrigin.Begin); + if (ShouldReadResponseBody(ctx.Response.ContentType, status)) + responseBody = await new StreamReader(capture, leaveOpen: true).ReadToEndAsync(); + capture.Seek(0, SeekOrigin.Begin); + await capture.CopyToAsync(originalBody); + } + finally + { + ctx.Response.Body = originalBody; + await capture.DisposeAsync(); + } + } + + var trimmedRequest = requestBody is null ? null + : (requestBody.Length > 200 ? requestBody[..200] + "..." : requestBody); + var trimmedResponse = responseBody.Length > 300 ? responseBody[..300] + "..." : responseBody; + var level = thrown is not null || status >= 500 ? LogLevel.Error + : status >= 400 ? LogLevel.Warning + : LogLevel.Information; + + reqLogger.Log(level, thrown, + "[req] {Status} {Method} {Host}{Path}{Query} {ElapsedMs}ms contentType={ContentType} contentLength={ContentLength} req={ReqBody} resp={RespBody}", + status, ctx.Request.Method, ctx.Request.Host, ctx.Request.Path, + ctx.Request.QueryString.Value ?? "", sw.ElapsedMilliseconds, + ctx.Request.ContentType ?? "", ctx.Request.ContentLength, + trimmedRequest ?? "", trimmedResponse); + + var pid = Auth.ControllerBaseExtensions.CurrentPlayerId(ctx.User); + if (pid is long playerId) + { + playerLog.Record(new PlayerLogEntry + { + Timestamp = DateTime.UtcNow, + PlayerId = playerId, + Method = ctx.Request.Method, + Host = ctx.Request.Host.Host, + Path = ctx.Request.Path.Value ?? string.Empty, + Query = ctx.Request.QueryString.Value ?? string.Empty, + Status = status, + ElapsedMs = sw.ElapsedMilliseconds, + ReqBody = trimmedRequest, + RespBody = trimmedResponse, + }); + } + } + }); + } + + private static bool IsHealthProbe(HttpRequest request) + => request.Path.Equals("/healthz", StringComparison.OrdinalIgnoreCase); + + private static bool IsTextLikeRequest(string? contentType) + { + if (string.IsNullOrWhiteSpace(contentType)) return false; + return contentType.StartsWith("application/json", StringComparison.OrdinalIgnoreCase) + || contentType.StartsWith("application/x-www-form-urlencoded", StringComparison.OrdinalIgnoreCase) + || contentType.StartsWith("text/", StringComparison.OrdinalIgnoreCase); + } + + private static bool ShouldCaptureResponse(HttpRequest request) + { + var host = request.Host.Host; + if (host.StartsWith("cdn.", StringComparison.OrdinalIgnoreCase)) + return false; + + var path = request.Path.Value ?? ""; + var ext = Path.GetExtension(path); + if (string.IsNullOrEmpty(ext)) + return true; + + return ext.Equals(".json", StringComparison.OrdinalIgnoreCase) + || ext.Equals(".txt", StringComparison.OrdinalIgnoreCase) + || ext.Equals(".log", StringComparison.OrdinalIgnoreCase); + } + + private static bool ShouldReadResponseBody(string? contentType, int status) + { + if (status < 400) return false; + if (string.IsNullOrWhiteSpace(contentType)) return true; + return contentType.StartsWith("application/json", StringComparison.OrdinalIgnoreCase) + || contentType.StartsWith("text/", StringComparison.OrdinalIgnoreCase); + } +} diff --git a/DorkNet.Server/Startup/ServiceCollectionExtensions.cs b/DorkNet.Server/Startup/ServiceCollectionExtensions.cs new file mode 100644 index 0000000..ee1f628 --- /dev/null +++ b/DorkNet.Server/Startup/ServiceCollectionExtensions.cs @@ -0,0 +1,341 @@ +using Microsoft.AspNetCore.Authentication.JwtBearer; +using Microsoft.EntityFrameworkCore; +using Microsoft.IdentityModel.Tokens; +using DorkNet.Server.Compat; +using DorkNet.Server.Data; +using DorkNet.Server.Services; +using DorkNet.Server.Versions.Late2020; +using Serilog; +using Serilog.Formatting.Compact; +using StackExchange.Redis; +using System.Text; + +namespace DorkNet.Server.Startup; + +/// One-stop DI registration for the DorkNet host. Each +/// section is a self-contained private method so the top-level +/// reads as a checklist of what the +/// server actually needs. Keep new registrations grouped by topic +/// rather than appended chronologically. +public static class ServiceCollectionExtensions +{ + public static void AddDorkNetServices(this WebApplicationBuilder builder) + { + ConfigureLogging(builder); + AddDatabase(builder); + AddDomain(builder); + AddCoreServices(builder.Services); + AddVersionCompatibility(builder); + var redisConn = AddRedis(builder); + AddAuthentication(builder); + AddKestrelAndSignalR(builder, redisConn); + } + + // ── Logging ─────────────────────────────────────────────────────────────── + // Production: structured JSON (one JSON object per line) so the log pipeline + // can parse fields without regex. Development: human-readable text so the + // local console stays scannable. JSON output is the same shape no matter the + // environment — log keys like {Tag}, {Status}, {Method} stay queryable. + private static void ConfigureLogging(WebApplicationBuilder builder) + { + builder.Host.UseSerilog((ctx, services, cfg) => + { + cfg.ReadFrom.Configuration(ctx.Configuration) + .ReadFrom.Services(services) + .Enrich.FromLogContext(); + + if (ctx.HostingEnvironment.IsDevelopment()) + cfg.WriteTo.Console(); + else + cfg.WriteTo.Console(new RenderedCompactJsonFormatter()); + }); + } + + // ── Database ────────────────────────────────────────────────────────────── + // Provider switch — `Database:Provider` config key (or DATABASE__PROVIDER env + // var) chooses sqlite (default for local dev) or postgres (production). + private static void AddDatabase(WebApplicationBuilder builder) + { + var dbProvider = (builder.Configuration["Database:Provider"] ?? "sqlite").ToLowerInvariant(); + + builder.Services.AddDbContext(opt => + { + // EF Core 9 throws on every connection if the model drifts from + // the migrations snapshot. We track migrations + the snapshot + // by hand for data-only fixes, so this warning is noise — Coolify + // and local-dev databases still apply real migrations from disk. + opt.ConfigureWarnings(w => w.Ignore( + Microsoft.EntityFrameworkCore.Diagnostics.RelationalEventId.PendingModelChangesWarning)); + + if (dbProvider == "postgres") + { + var conn = builder.Configuration.GetConnectionString("Default") + ?? throw new InvalidOperationException( + "Database:Provider=postgres but ConnectionStrings:Default is not set. " + + "Set the ConnectionStrings__Default env var or `ConnectionStrings:Default` " + + "in appsettings.Local.json."); + opt.UseNpgsql(conn, npg => npg.MigrationsAssembly("DorkNet.Server")); + } + else + { + var dbPath = Path.Combine(AppContext.BaseDirectory, "data", "dorknet.db"); + Directory.CreateDirectory(Path.GetDirectoryName(dbPath)!); + opt.UseSqlite($"Data Source={dbPath}", lite => lite.MigrationsAssembly("DorkNet.Server")); + } + }); + } + + // ── Domain ──────────────────────────────────────────────────────────────── + // Single source of truth for the deployment apex. Read once at startup + // from DORKNET_DOMAIN (env-var) or Domain:Apex (config), defaulting to + // localhost so existing deploys keep working without setting the env var. + // Replaces per-controller [Host(...)] filters that hard-coded + // rec.net/localhost pairs — the singleton DomainConfig is injected + // anywhere code needs to build outbound URLs. + private static void AddDomain(WebApplicationBuilder builder) + { + var apex = + builder.Configuration["Domain:Apex"] + ?? Environment.GetEnvironmentVariable("DORKNET_DOMAIN") + ?? "localhost"; + builder.Services.AddSingleton(new DomainConfig(apex)); + builder.Services.Configure(opt => + { + opt.AllowedHosts = new[] { apex, $"*.{apex}", "localhost", "127.0.0.1" }; + opt.AllowEmptyHosts = true; + opt.IncludeFailureMessage = true; + }); + Console.WriteLine($"[domain] apex={apex}, allowedHosts=[{apex}, *.{apex}, localhost]"); + } + + // ── Services ────────────────────────────────────────────────────────────── + private static void AddCoreServices(IServiceCollection services) + { + services.AddScoped(); + services.AddScoped(); + services.AddScoped(); + services.AddScoped(); + services.AddScoped(); + services.AddScoped(); + services.AddScoped(); + services.AddScoped(); + // Singleton — the protobuf blob is identical for every room and built + // once at startup; no need to rebuild per request. + services.AddSingleton(); + services.AddSingleton(); + services.AddHttpClient(); + services.AddSingleton(); + // Scoped (per-request) — these services hold DbContext references and + // must share the request's scope to participate in the request's + // transaction lifecycle. Pre-PR-3 they were Singleton with in-process + // ConcurrentDictionary state; PR 3 moved their state to Postgres and + // they now resolve from DI per-request like any other DbContext consumer. + services.AddScoped(); + services.AddScoped(); + services.AddScoped(); + services.AddScoped(); + // Singletons — these own connectionless state (Redis-backed or + // process-local) and don't need a per-request scope. + services.AddSingleton(); + services.AddSingleton(); + services.AddSingleton(); + services.AddSingleton(); + services.AddSingleton(); + // S3-compatible object storage (Garage in production, MinIO/disk in dev) + // — holds profile images + room-blob bytes. Stateless wrapper around + // the AWS SDK; safe as a singleton. + services.AddSingleton(); + services.AddSingleton(); + } + + // ── Client-version compatibility ───────────────────────────────────────── + // Each Rec Room build the server supports is one IVersionPlugin. Plugins + // declare which version keys they handle (a single plugin can cover N keys + // when several builds share the same wire format) and register any + // generation-specific strategy services into DI. + // + // Add a new version family: drop a folder under Versions/, implement + // IVersionPlugin, and register the singleton here. Add a new version to + // an existing family (same wire shapes): just list the key in the + // existing plugin's VersionKeys — no DI change needed. + private static void AddVersionCompatibility(WebApplicationBuilder builder) + { + var pluginInstances = new IVersionPlugin[] + { + new Late2020VersionPlugin(), + // Add March2020 / future generations here as their plugins land. + }; + foreach (var p in pluginInstances) p.RegisterStrategies(builder.Services); + foreach (var p in pluginInstances) + builder.Services.AddSingleton(p); + + var supported = (builder.Configuration.GetSection("DorkNet:SupportedVersions").Get() + ?? new[] { "december_2020_12_18" }) + .ToHashSet(StringComparer.OrdinalIgnoreCase); + var defaultKey = builder.Configuration["DorkNet:DefaultClientVersion"] + ?? "december_2020_12_18"; + builder.Services.AddSingleton(sp => new VersionRegistry( + sp.GetServices(), + supported, + string.IsNullOrWhiteSpace(defaultKey) ? null : defaultKey)); + } + + // ── Redis ───────────────────────────────────────────────────────────────── + // IConnectionMultiplexer is registered ONLY when a connection string is + // present. The Redis-backed services check for it via DI and fall back to + // process-local state when null. Local dev can run without Redis; Coolify + // production sets ConnectionStrings__Redis on the linked service. + private static string? AddRedis(WebApplicationBuilder builder) + { + var redisConn = builder.Configuration.GetConnectionString("Redis"); + if (string.IsNullOrWhiteSpace(redisConn)) return null; + var seRedisConfig = NormalizeRedisConn(redisConn); + var mux = ConnectionMultiplexer.Connect(seRedisConfig); + builder.Services.AddSingleton(mux); + return redisConn; + } + + // Coolify hands you a URI like + // redis://default:PASSWORD@host:6379/0 + // but StackExchange.Redis.Connect(string) doesn't reliably handle the + // user-info segment of a URI (the "default:PASSWORD@" part) — passing + // the URI verbatim ends up with no password applied to the + // ConfigurationOptions and Connect throws a misleading + // "Error connecting right now". So we detect a URI prefix and translate + // to SE.Redis's native config string before handing off. + internal static string NormalizeRedisConn(string raw) + { + if (!raw.StartsWith("redis://", StringComparison.OrdinalIgnoreCase) && + !raw.StartsWith("rediss://", StringComparison.OrdinalIgnoreCase)) + return raw; + var uri = new Uri(raw); + var host = uri.Host; + var port = uri.Port > 0 ? uri.Port : 6379; + string? user = null, pass = null; + if (!string.IsNullOrEmpty(uri.UserInfo)) + { + var parts = uri.UserInfo.Split(':', 2); + user = Uri.UnescapeDataString(parts[0]); + if (parts.Length == 2) pass = Uri.UnescapeDataString(parts[1]); + } + var opts = new ConfigurationOptions + { + EndPoints = { { host, port } }, + AbortOnConnectFail = false, + Ssl = raw.StartsWith("rediss://", StringComparison.OrdinalIgnoreCase), + }; + if (!string.IsNullOrEmpty(user)) opts.User = user; + if (!string.IsNullOrEmpty(pass)) opts.Password = pass; + return opts.ToString(); + } + + // ── JWT Auth ────────────────────────────────────────────────────────────── + // Resolution order: env-var first (for production / containerised deploys), + // then `Jwt:Secret` from any configuration source (appsettings.Local.json + // is the same-machine convenience path the patcher script writes to). + // Never commit a real secret to appsettings.json. DORKNET_JWT_SECRET is the + // canonical env var name; RECNET_JWT_SECRET kept for backward compat with + // older Coolify configs. + private static void AddAuthentication(WebApplicationBuilder builder) + { + var jwtSecret = Environment.GetEnvironmentVariable("DORKNET_JWT_SECRET") + ?? Environment.GetEnvironmentVariable("RECNET_JWT_SECRET") + ?? builder.Configuration["Jwt:Secret"] + ?? throw new InvalidOperationException( + "JWT secret not configured. Set the DORKNET_JWT_SECRET env var or " + + "add `Jwt:Secret` to appsettings.Local.json (the install-plugin.ps1 " + + "script generates one automatically on same-machine setup)."); + + builder.Services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme) + .AddJwtBearer(opt => + { + opt.TokenValidationParameters = new TokenValidationParameters + { + ValidateIssuerSigningKey = true, + IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(jwtSecret)), + ValidateIssuer = true, + ValidIssuer = "https://api.rec.net/", + ValidateAudience = false, + ClockSkew = TimeSpan.Zero, + }; + // SignalR over WebSocket: BestHTTP.SignalRCore (the 2020 watch's + // hub client) negotiates with Authorization: Bearer, but the + // WebSocket upgrade leg isn't guaranteed to keep the header + // depending on the proxy in front of us. The hub client falls + // back to ?access_token= on the WS URL, which we extract + // here for any /hub/* path. Without this, the watch's + // RecNet.Notifications connect throws "Failed to connect to + // RecNet Notifications" mid-login. + opt.Events = new JwtBearerEvents + { + OnMessageReceived = ctx => + { + var accessToken = ctx.Request.Query["access_token"]; + var path = ctx.HttpContext.Request.Path; + if (!string.IsNullOrEmpty(accessToken) && path.StartsWithSegments("/hub")) + ctx.Token = accessToken; + return Task.CompletedTask; + }, + }; + }); + + builder.Services.AddAuthorization(); + // Use C# property names verbatim — [JsonPropertyName] attributes on each + // model give the per-key casing the client's LitJson Util.GetKey calls + // expect (a mix of lowercase, camelCase and snake_case; verified by + // disassembling each Deserialize body). Don't apply a global naming policy + // or it will override the explicit per-key attributes. + builder.Services.AddControllers().AddJsonOptions(opt => + opt.JsonSerializerOptions.PropertyNamingPolicy = null); + } + + // ── Kestrel + SignalR ──────────────────────────────────────────────────── + // Kestrel-level limits: defaults trim long-lived sockets earlier than our + // SignalR ping interval allows. + // * KeepAliveTimeout (default 130s) applies to upgraded WebSocket + // connections in some hosting paths; bump to 10m so it never beats + // SignalR's own ping cadence. + // * MinRequestBodyDataRate / MinResponseDataRate enforce a minimum + // bytes/sec on streaming bodies; for chunked-upload room imports + // that's been the cause of mysterious abort-mid-write errors on + // slow links. Disabling them is safer than risking false-positive + // "client too slow" closures. + // + // SignalR — drives notify.{apex}/hub/v1, consumed client-side by + // BestHTTP.SignalRCore.HubConnection. The framework handles the + // /negotiate POST automatically. When Redis is configured, attach + // the StackExchange.Redis backplane so groups (player:N) fan out + // across replicas. + private static void AddKestrelAndSignalR(WebApplicationBuilder builder, string? redisConn) + { + builder.WebHost.ConfigureKestrel(kopts => + { + kopts.Limits.KeepAliveTimeout = TimeSpan.FromMinutes(10); + kopts.Limits.MinRequestBodyDataRate = null; + kopts.Limits.MinResponseDataRate = null; + }); + + // Keepalive cadence: server pings every 10s, gives the client 60s + // to ack before declaring the connection dead. The 10s ping forces + // frame traffic well below every proxy timeout we've seen in the + // path (Cloudflare edge ~100s, cloudflared 600s, Coolify/Traefik + // 180s, Kestrel 130s). + var signalR = builder.Services.AddSignalR(opts => + { + opts.KeepAliveInterval = TimeSpan.FromSeconds(10); + opts.ClientTimeoutInterval = TimeSpan.FromSeconds(60); + opts.HandshakeTimeout = TimeSpan.FromSeconds(15); + // Allow large gift / community-board payloads (default 32 KB + // truncates anything bigger and disconnects the client). + opts.MaximumReceiveMessageSize = 1024 * 1024; + opts.EnableDetailedErrors = true; + }); + if (!string.IsNullOrWhiteSpace(redisConn)) + signalR.AddStackExchangeRedis(NormalizeRedisConn(redisConn), opts => + { + opts.Configuration.ChannelPrefix = RedisChannel.Literal("dorknet-signalr"); + }); + builder.Services.AddEndpointsApiExplorer(); + builder.Services.AddSwaggerGen(); + } +} diff --git a/DorkNet.Server/Startup/StaticHostExtensions.cs b/DorkNet.Server/Startup/StaticHostExtensions.cs new file mode 100644 index 0000000..8a358f6 --- /dev/null +++ b/DorkNet.Server/Startup/StaticHostExtensions.cs @@ -0,0 +1,147 @@ +using Microsoft.Extensions.FileProviders; + +namespace DorkNet.Server.Startup; + +/// Per-host static-files branching. Each subdomain +/// (admin.{apex}, the apex, www.{apex}, feed.{apex}) +/// gets its own rooted at +/// wwwroot/<subdir>, so requests like +/// admin.{apex}/style.css map to wwwroot/admin/style.css +/// with no path-prefix munging. +/// +/// Uses (not MapWhen) so +/// requests for paths the SPA doesn't serve fall through to controllers. +/// MapWhen would fork into a parallel pipeline that terminates without +/// invoking matched endpoints. +/// +/// Mounted BEFORE the request-tracing wrapper because +/// StaticFileMiddleware uses IHttpResponseBodyFeature.SendFileAsync, +/// which writes directly to the underlying connection and ignores any +/// Response.Body replacement done downstream. +public static class StaticHostExtensions +{ + public static void MountStaticHost(this WebApplication app, string host, string subdir, bool spaFallback = false) + { + var staticHostLogger = app.Services.GetRequiredService() + .CreateLogger("DorkNet.Server.StaticHost"); + var root = Path.Combine(app.Environment.ContentRootPath, "wwwroot", subdir); + staticHostLogger.LogInformation( + "[static-host] {Host} → {Root} (exists: {Exists}, spaFallback: {Spa})", + host, root, Directory.Exists(root), spaFallback); + if (!Directory.Exists(root)) return; + var files = new PhysicalFileProvider(root); + + // Use UseDefaultFiles + UseStaticFiles directly rather than + // UseFileServer because the latter's nested options initialization + // doesn't always propagate the FileProvider correctly when wrapped + // inside UseWhen — leading to the static files middleware looking + // at the default WebRoot (wwwroot/) instead of our scoped folder. + var defaultFilesOpts = new DefaultFilesOptions { FileProvider = files }; + var staticFilesOpts = new StaticFileOptions + { + FileProvider = files, + // Don't let the browser cache admin assets — we iterate on + // the HTML/JS/CSS often and a stale cached page silently + // swallows every change. + OnPrepareResponse = ctx => + { + var headers = ctx.Context.Response.Headers; + headers.CacheControl = "no-cache, no-store, must-revalidate"; + headers.Pragma = "no-cache"; + headers.Expires = "0"; + }, + }; + + app.UseWhen(ctx => ctx.Request.Host.Host.Equals(host, StringComparison.OrdinalIgnoreCase), + branch => + { + // Endpoint-clearing rule: the auto-added UseRouting (which + // runs before our middleware in minimal hosting) may have + // matched a non-admin endpoint for paths the static-host + // should answer — typically NsController.Root for "/" on + // admin.{apex} and the apex, which would serve the + // service-URL-map JSON instead of the SPA's index.html. + // StaticFileMiddleware's ValidateNoEndpoint guard skips + // serving whenever an endpoint is already matched, so we + // clear it for non-API paths to let UseStaticFiles run. + // + // BUT: /api/* paths MUST keep their matched endpoint — + // those are admin/site/feed API calls that the matching + // controller needs to handle. + branch.Use(async (ctx, next) => + { + var p = ctx.Request.Path.Value ?? "/"; + var probe = files.GetFileInfo(p == "/" ? "/index.html" : p); + staticHostLogger.LogInformation( + "[static-host:{Host}] path={Path} probe={Probe} exists={Exists}", + host, p, probe.PhysicalPath ?? "", probe.Exists); + if (!p.StartsWith("/api/", StringComparison.OrdinalIgnoreCase)) + ctx.SetEndpoint(null); + await next(); + }); + branch.UseDefaultFiles(defaultFilesOpts); + branch.UseStaticFiles(staticFilesOpts); + + // Terminal handler — runs AFTER UseStaticFiles. By this + // point the request either: + // (a) was a static file UseStaticFiles served — Response + // already started, we just return. + // (b) is an API call we want to fall through to + // controllers — call next(). + // (c) is a SPA deep link (no extension) we want to answer + // with index.html so React Router can re-resolve. + // (d) is a missing static asset (has extension, file + // gone — e.g. /assets/index-.js after a + // redeploy). Return 404 with no body — DO NOT fall + // through to controllers. Serving JSON {} causes the + // browser + + diff --git a/DorkNet.Server/admin-ui/package-lock.json b/DorkNet.Server/admin-ui/package-lock.json new file mode 100644 index 0000000..b411a98 --- /dev/null +++ b/DorkNet.Server/admin-ui/package-lock.json @@ -0,0 +1,2788 @@ +{ + "name": "dorknet-admin-ui", + "version": "0.1.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "dorknet-admin-ui", + "version": "0.1.0", + "dependencies": { + "@zip.js/zip.js": "^2.7.52", + "react": "^18.3.1", + "react-dom": "^18.3.1", + "react-router-dom": "^6.27.0" + }, + "devDependencies": { + "@types/node": "^22.9.1", + "@types/react": "^18.3.12", + "@types/react-dom": "^18.3.1", + "@vitejs/plugin-react": "^4.3.3", + "autoprefixer": "^10.4.20", + "postcss": "^8.4.49", + "tailwindcss": "^3.4.15", + "typescript": "^5.6.3", + "vite": "^5.4.11" + } + }, + "node_modules/@alloc/quick-lru": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/@alloc/quick-lru/-/quick-lru-5.2.0.tgz", + "integrity": "sha512-UrcABB+4bUrFABwbluTIBErXwvbsU/V7TZWfmbgJfbkwiBuziS9gxdODUyuiecfdGQ85jglMW6juS3+z5TsKLw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@babel/code-frame": { + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.29.0.tgz", + "integrity": "sha512-9NhCeYjq9+3uxgdtp20LSiJXJvN0FeCtNGpJxuMFZ1Kv3cWUNb6DOhJwUvcVCzKGR66cw4njwM6hrJLqgOwbcw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-validator-identifier": "^7.28.5", + "js-tokens": "^4.0.0", + "picocolors": "^1.1.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/compat-data": { + "version": "7.29.3", + "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.29.3.tgz", + "integrity": "sha512-LIVqM46zQWZhj17qA8wb4nW/ixr2y1Nw+r1etiAWgRM6U1IqP+LNhL1yg440jYZR72jCWcWbLWzIosH+uP1fqg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/core": { + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.29.0.tgz", + "integrity": "sha512-CGOfOJqWjg2qW/Mb6zNsDm+u5vFQ8DxXfbM09z69p5Z6+mE1ikP2jUXw+j42Pf1XTYED2Rni5f95npYeuwMDQA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.29.0", + "@babel/generator": "^7.29.0", + "@babel/helper-compilation-targets": "^7.28.6", + "@babel/helper-module-transforms": "^7.28.6", + "@babel/helpers": "^7.28.6", + "@babel/parser": "^7.29.0", + "@babel/template": "^7.28.6", + "@babel/traverse": "^7.29.0", + "@babel/types": "^7.29.0", + "@jridgewell/remapping": "^2.3.5", + "convert-source-map": "^2.0.0", + "debug": "^4.1.0", + "gensync": "^1.0.0-beta.2", + "json5": "^2.2.3", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/babel" + } + }, + "node_modules/@babel/generator": { + "version": "7.29.1", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.29.1.tgz", + "integrity": "sha512-qsaF+9Qcm2Qv8SRIMMscAvG4O3lJ0F1GuMo5HR/Bp02LopNgnZBC/EkbevHFeGs4ls/oPz9v+Bsmzbkbe+0dUw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.29.0", + "@babel/types": "^7.29.0", + "@jridgewell/gen-mapping": "^0.3.12", + "@jridgewell/trace-mapping": "^0.3.28", + "jsesc": "^3.0.2" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-compilation-targets": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.28.6.tgz", + "integrity": "sha512-JYtls3hqi15fcx5GaSNL7SCTJ2MNmjrkHXg4FSpOA/grxK8KwyZ5bubHsCq8FXCkua6xhuaaBit+3b7+VZRfcA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/compat-data": "^7.28.6", + "@babel/helper-validator-option": "^7.27.1", + "browserslist": "^4.24.0", + "lru-cache": "^5.1.1", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-globals": { + "version": "7.28.0", + "resolved": "https://registry.npmjs.org/@babel/helper-globals/-/helper-globals-7.28.0.tgz", + "integrity": "sha512-+W6cISkXFa1jXsDEdYA8HeevQT/FULhxzR99pxphltZcVaugps53THCeiWA8SguxxpSp3gKPiuYfSWopkLQ4hw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-imports": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.28.6.tgz", + "integrity": "sha512-l5XkZK7r7wa9LucGw9LwZyyCUscb4x37JWTPz7swwFE/0FMQAGpiWUZn8u9DzkSBWEcK25jmvubfpw2dnAMdbw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/traverse": "^7.28.6", + "@babel/types": "^7.28.6" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-transforms": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.28.6.tgz", + "integrity": "sha512-67oXFAYr2cDLDVGLXTEABjdBJZ6drElUSI7WKp70NrpyISso3plG9SAGEF6y7zbha/wOzUByWWTJvEDVNIUGcA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-module-imports": "^7.28.6", + "@babel/helper-validator-identifier": "^7.28.5", + "@babel/traverse": "^7.28.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/helper-plugin-utils": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.28.6.tgz", + "integrity": "sha512-S9gzZ/bz83GRysI7gAD4wPT/AI3uCnY+9xn+Mx/KPs2JwHJIz1W8PZkg2cqyt3RNOBM8ejcXhV6y8Og7ly/Dug==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-string-parser": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.27.1.tgz", + "integrity": "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-identifier": { + "version": "7.28.5", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.28.5.tgz", + "integrity": "sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-option": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.27.1.tgz", + "integrity": "sha512-YvjJow9FxbhFFKDSuFnVCe2WxXk1zWc22fFePVNEaWJEu8IrZVlda6N0uHwzZrUM1il7NC9Mlp4MaJYbYd9JSg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helpers": { + "version": "7.29.2", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.29.2.tgz", + "integrity": "sha512-HoGuUs4sCZNezVEKdVcwqmZN8GoHirLUcLaYVNBK2J0DadGtdcqgr3BCbvH8+XUo4NGjNl3VOtSjEKNzqfFgKw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/template": "^7.28.6", + "@babel/types": "^7.29.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/parser": { + "version": "7.29.3", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.29.3.tgz", + "integrity": "sha512-b3ctpQwp+PROvU/cttc4OYl4MzfJUWy6FZg+PMXfzmt/+39iHVF0sDfqay8TQM3JA2EUOyKcFZt75jWriQijsA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.29.0" + }, + "bin": { + "parser": "bin/babel-parser.js" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@babel/plugin-transform-react-jsx-self": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-self/-/plugin-transform-react-jsx-self-7.27.1.tgz", + "integrity": "sha512-6UzkCs+ejGdZ5mFFC/OCUrv028ab2fp1znZmCZjAOBKiBK2jXD1O+BPSfX8X2qjJ75fZBMSnQn3Rq2mrBJK2mw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-react-jsx-source": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-source/-/plugin-transform-react-jsx-source-7.27.1.tgz", + "integrity": "sha512-zbwoTsBruTeKB9hSq73ha66iFeJHuaFkUbwvqElnygoNbj/jHRsSeokowZFN3CZ64IvEqcmmkVe89OPXc7ldAw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/template": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.28.6.tgz", + "integrity": "sha512-YA6Ma2KsCdGb+WC6UpBVFJGXL58MDA6oyONbjyF/+5sBgxY/dwkhLogbMT2GXXyU84/IhRw/2D1Os1B/giz+BQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.28.6", + "@babel/parser": "^7.28.6", + "@babel/types": "^7.28.6" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/traverse": { + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.29.0.tgz", + "integrity": "sha512-4HPiQr0X7+waHfyXPZpWPfWL/J7dcN1mx9gL6WdQVMbPnF3+ZhSMs8tCxN7oHddJE9fhNE7+lxdnlyemKfJRuA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.29.0", + "@babel/generator": "^7.29.0", + "@babel/helper-globals": "^7.28.0", + "@babel/parser": "^7.29.0", + "@babel/template": "^7.28.6", + "@babel/types": "^7.29.0", + "debug": "^4.3.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/types": { + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.29.0.tgz", + "integrity": "sha512-LwdZHpScM4Qz8Xw2iKSzS+cfglZzJGvofQICy7W7v4caru4EaAmyUuO6BGrbyQ2mYV11W0U8j5mBhd14dd3B0A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-string-parser": "^7.27.1", + "@babel/helper-validator-identifier": "^7.28.5" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@esbuild/aix-ppc64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.21.5.tgz", + "integrity": "sha512-1SDgH6ZSPTlggy1yI6+Dbkiz8xzpHJEVAlF/AM1tHPLsf5STom9rwtjE4hKAF20FfXXNTFqEYXyJNWh1GiZedQ==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/android-arm": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.21.5.tgz", + "integrity": "sha512-vCPvzSjpPHEi1siZdlvAlsPxXl7WbOVUBBAowWug4rJHb68Ox8KualB+1ocNvT5fjv6wpkX6o/iEpbDrf68zcg==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/android-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.21.5.tgz", + "integrity": "sha512-c0uX9VAUBQ7dTDCjq+wdyGLowMdtR/GoC2U5IYk/7D1H1JYC0qseD7+11iMP2mRLN9RcCMRcjC4YMclCzGwS/A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/android-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.21.5.tgz", + "integrity": "sha512-D7aPRUUNHRBwHxzxRvp856rjUHRFW1SdQATKXH2hqA0kAZb1hKmi02OpYRacl0TxIGz/ZmXWlbZgjwWYaCakTA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/darwin-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.21.5.tgz", + "integrity": "sha512-DwqXqZyuk5AiWWf3UfLiRDJ5EDd49zg6O9wclZ7kUMv2WRFr4HKjXp/5t8JZ11QbQfUS6/cRCKGwYhtNAY88kQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/darwin-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.21.5.tgz", + "integrity": "sha512-se/JjF8NlmKVG4kNIuyWMV/22ZaerB+qaSi5MdrXtd6R08kvs2qCN4C09miupktDitvh8jRFflwGFBQcxZRjbw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/freebsd-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.21.5.tgz", + "integrity": "sha512-5JcRxxRDUJLX8JXp/wcBCy3pENnCgBR9bN6JsY4OmhfUtIHe3ZW0mawA7+RDAcMLrMIZaf03NlQiX9DGyB8h4g==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/freebsd-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.21.5.tgz", + "integrity": "sha512-J95kNBj1zkbMXtHVH29bBriQygMXqoVQOQYA+ISs0/2l3T9/kj42ow2mpqerRBxDJnmkUDCaQT/dfNXWX/ZZCQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-arm": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.21.5.tgz", + "integrity": "sha512-bPb5AHZtbeNGjCKVZ9UGqGwo8EUu4cLq68E95A53KlxAPRmUyYv2D6F0uUI65XisGOL1hBP5mTronbgo+0bFcA==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.21.5.tgz", + "integrity": "sha512-ibKvmyYzKsBeX8d8I7MH/TMfWDXBF3db4qM6sy+7re0YXya+K1cem3on9XgdT2EQGMu4hQyZhan7TeQ8XkGp4Q==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-ia32": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.21.5.tgz", + "integrity": "sha512-YvjXDqLRqPDl2dvRODYmmhz4rPeVKYvppfGYKSNGdyZkA01046pLWyRKKI3ax8fbJoK5QbxblURkwK/MWY18Tg==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-loong64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.21.5.tgz", + "integrity": "sha512-uHf1BmMG8qEvzdrzAqg2SIG/02+4/DHB6a9Kbya0XDvwDEKCoC8ZRWI5JJvNdUjtciBGFQ5PuBlpEOXQj+JQSg==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-mips64el": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.21.5.tgz", + "integrity": "sha512-IajOmO+KJK23bj52dFSNCMsz1QP1DqM6cwLUv3W1QwyxkyIWecfafnI555fvSGqEKwjMXVLokcV5ygHW5b3Jbg==", + "cpu": [ + "mips64el" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-ppc64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.21.5.tgz", + "integrity": "sha512-1hHV/Z4OEfMwpLO8rp7CvlhBDnjsC3CttJXIhBi+5Aj5r+MBvy4egg7wCbe//hSsT+RvDAG7s81tAvpL2XAE4w==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-riscv64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.21.5.tgz", + "integrity": "sha512-2HdXDMd9GMgTGrPWnJzP2ALSokE/0O5HhTUvWIbD3YdjME8JwvSCnNGBnTThKGEB91OZhzrJ4qIIxk/SBmyDDA==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-s390x": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.21.5.tgz", + "integrity": "sha512-zus5sxzqBJD3eXxwvjN1yQkRepANgxE9lgOW2qLnmr8ikMTphkjgXu1HR01K4FJg8h1kEEDAqDcZQtbrRnB41A==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.21.5.tgz", + "integrity": "sha512-1rYdTpyv03iycF1+BhzrzQJCdOuAOtaqHTWJZCWvijKD2N5Xu0TtVC8/+1faWqcP9iBCWOmjmhoH94dH82BxPQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/netbsd-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.21.5.tgz", + "integrity": "sha512-Woi2MXzXjMULccIwMnLciyZH4nCIMpWQAs049KEeMvOcNADVxo0UBIQPfSmxB3CWKedngg7sWZdLvLczpe0tLg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/openbsd-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.21.5.tgz", + "integrity": "sha512-HLNNw99xsvx12lFBUwoT8EVCsSvRNDVxNpjZ7bPn947b8gJPzeHWyNVhFsaerc0n3TsbOINvRP2byTZ5LKezow==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/sunos-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.21.5.tgz", + "integrity": "sha512-6+gjmFpfy0BHU5Tpptkuh8+uw3mnrvgs+dSPQXQOv3ekbordwnzTVEb4qnIvQcYXq6gzkyTnoZ9dZG+D4garKg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/win32-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.21.5.tgz", + "integrity": "sha512-Z0gOTd75VvXqyq7nsl93zwahcTROgqvuAcYDUr+vOv8uHhNSKROyU961kgtCD1e95IqPKSQKH7tBTslnS3tA8A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/win32-ia32": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.21.5.tgz", + "integrity": "sha512-SWXFF1CL2RVNMaVs+BBClwtfZSvDgtL//G/smwAc5oVK/UPu2Gu9tIaRgFmYFFKrmg3SyAjSrElf0TiJ1v8fYA==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/win32-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.21.5.tgz", + "integrity": "sha512-tQd/1efJuzPC6rCFwEvLtci/xNFcTZknmXs98FYDfGE4wP9ClFV98nyKrzJKVPMhdDnjzLhdUyMX4PsQAPjwIw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@jridgewell/gen-mapping": { + "version": "0.3.13", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz", + "integrity": "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.0", + "@jridgewell/trace-mapping": "^0.3.24" + } + }, + "node_modules/@jridgewell/remapping": { + "version": "2.3.5", + "resolved": "https://registry.npmjs.org/@jridgewell/remapping/-/remapping-2.3.5.tgz", + "integrity": "sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.24" + } + }, + "node_modules/@jridgewell/resolve-uri": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", + "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.5.5", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz", + "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==", + "dev": true, + "license": "MIT" + }, + "node_modules/@jridgewell/trace-mapping": { + "version": "0.3.31", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz", + "integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/resolve-uri": "^3.1.0", + "@jridgewell/sourcemap-codec": "^1.4.14" + } + }, + "node_modules/@nodelib/fs.scandir": { + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", + "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@nodelib/fs.stat": "2.0.5", + "run-parallel": "^1.1.9" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.stat": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", + "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.walk": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", + "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@nodelib/fs.scandir": "2.1.5", + "fastq": "^1.6.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@remix-run/router": { + "version": "1.23.2", + "resolved": "https://registry.npmjs.org/@remix-run/router/-/router-1.23.2.tgz", + "integrity": "sha512-Ic6m2U/rMjTkhERIa/0ZtXJP17QUi2CbWE7cqx4J58M8aA3QTfW+2UlQ4psvTX9IO1RfNVhK3pcpdjej7L+t2w==", + "license": "MIT", + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@rolldown/pluginutils": { + "version": "1.0.0-beta.27", + "resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.0-beta.27.tgz", + "integrity": "sha512-+d0F4MKMCbeVUJwG96uQ4SgAznZNSq93I3V+9NHA4OpvqG8mRCpGdKmK8l/dl02h2CCDHwW2FqilnTyDcAnqjA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@rollup/rollup-android-arm-eabi": { + "version": "4.60.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.60.4.tgz", + "integrity": "sha512-F5QXMSiFebS9hKZj02XhWLLnRpJ3B3AROP0tWbFBSj+6kCbg5m9j5JoHKd4mmSVy5mS/IMQloYgYxCuJC0fxEQ==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-android-arm64": { + "version": "4.60.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.60.4.tgz", + "integrity": "sha512-GxxTKApUpzRhof7poWvCJHRF51C67u1R7D6DiluBE8wKU1u5GWE8t+v81JvJYtbawoBFX1hLv5Ei4eVjkWokaw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-darwin-arm64": { + "version": "4.60.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.60.4.tgz", + "integrity": "sha512-tua0TaJxMOB1R0V0RS1jFZ/RpURFDJIOR2A6jWwQeawuFyS4gBW+rntLRaQd0EQ4bd6Vp44Z2rXW+YYDBsj6IA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-darwin-x64": { + "version": "4.60.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.60.4.tgz", + "integrity": "sha512-CSKq7MsP+5PFIcydhAiR1K0UhEI1A2jWXVKHPCBZ151yOutENwvnPocgVHkivu2kviURtCEB6zUQw0vs8RrhMg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-freebsd-arm64": { + "version": "4.60.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.60.4.tgz", + "integrity": "sha512-+O8OkVdyvXMtJEciu2wS/pzm1IxntEEQx3z5TAVy4l32G0etZn+RsA48ARRrFm6Ri8fvqPQfgrvNxSjKAbnd3g==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-freebsd-x64": { + "version": "4.60.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.60.4.tgz", + "integrity": "sha512-Iw3oMskH3AfNuhU0MSN7vNbdi4me/NiYo2azqPz/Le16zHSa+3RRmliCMWWQmh4lcndccU40xcJuTYJZxNo/lw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-linux-arm-gnueabihf": { + "version": "4.60.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.60.4.tgz", + "integrity": "sha512-EIPRXTVQpHyF8WOo219AD2yEltPehLTcTMz2fn6JsatLYSzQf00hj3rulF+yauOlF9/FtM2WpkT/hJh/KJFGhA==", + "cpu": [ + "arm" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm-musleabihf": { + "version": "4.60.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.60.4.tgz", + "integrity": "sha512-J3Yh9PzzF1Ovah2At+lHiGQdsYgArxBbXv/zHfSyaiFQEqvNv7DcW98pCrmdjCZBrqBiKrKKe2V+aaSGWuBe/w==", + "cpu": [ + "arm" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-gnu": { + "version": "4.60.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.60.4.tgz", + "integrity": "sha512-BFDEZMYfUvLn37ONE1yMBojPxnMlTFsdyNoqncT0qFq1mAfllL+ATMMJd8TeuVMiX84s1KbcxcZbXInmcO2mRg==", + "cpu": [ + "arm64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-musl": { + "version": "4.60.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.60.4.tgz", + "integrity": "sha512-pc9EYOSlOgdQ2uPl1o9PF6/kLSgaUosia7gOuS8mB69IxJvlclko1MECXysjs5ryez1/5zjYqx3+xYU0TU6R1A==", + "cpu": [ + "arm64" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-loong64-gnu": { + "version": "4.60.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.60.4.tgz", + "integrity": "sha512-NxnomyxYerDh5n4iLrNa+sH+Z+U4BMEE46V2PgQ/hoB909i8gV1M5wPojWg9fk1jWpO3IQnOs20K4wyZuFLEFQ==", + "cpu": [ + "loong64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-loong64-musl": { + "version": "4.60.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-musl/-/rollup-linux-loong64-musl-4.60.4.tgz", + "integrity": "sha512-nbJnQ8a3z1mtmrwImCYhc6BGpThAyYVRQxw9uKSKG4wR6aAYno9sVjJ0zaZcW9BPJX1GbrDPf+SvdWjgTuDmnw==", + "cpu": [ + "loong64" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-ppc64-gnu": { + "version": "4.60.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.60.4.tgz", + "integrity": "sha512-2EU6acNrQLd8tYvo/LXW535wupT3m6fo7HKo6lr7ktQoItxTyOL1ZCR/GfGCuXl2vR+zmfI6eRXkSemafv+iVg==", + "cpu": [ + "ppc64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-ppc64-musl": { + "version": "4.60.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-musl/-/rollup-linux-ppc64-musl-4.60.4.tgz", + "integrity": "sha512-WeBtoMuaMxiiIrO2IYP3xs6GMWkJP2C0EoT8beTLkUPmzV1i/UcOSVw1d5r9KBODtHKilG5yFxsGRnBbK3wJ4A==", + "cpu": [ + "ppc64" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-gnu": { + "version": "4.60.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.60.4.tgz", + "integrity": "sha512-FJHFfqpKUI3A10WrWKiFbBZ7yVbGT4q4B5o1qKFFojqpaYoh9LrQgqWCmmcxQzVSXYtyB5bzkXrYzlHTs21MYA==", + "cpu": [ + "riscv64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-musl": { + "version": "4.60.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.60.4.tgz", + "integrity": "sha512-mcEl6CUT5IAUmQf1m9FYSmVqCJlpQ8r8eyftFUHG8i9OhY7BkBXSUdnLH5DOf0wCOjcP9v/QO93zpmF1SptCCw==", + "cpu": [ + "riscv64" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-s390x-gnu": { + "version": "4.60.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.60.4.tgz", + "integrity": "sha512-ynt3JxVd2w2buzoKDWIyiV1pJW93xlQic1THVLXilz429oijRpSHivZAgp65KBu+cMcgf1eVVjdnTLvPxgCuoQ==", + "cpu": [ + "s390x" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-gnu": { + "version": "4.60.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.60.4.tgz", + "integrity": "sha512-Boiz5+MsaROEWDf+GGEwF8VMHGhlUoQMtIPjOgA5fv4osupqTVnJteQNKJwUcnUog2G55jYXH7KZFFiJe0TEzQ==", + "cpu": [ + "x64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-musl": { + "version": "4.60.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.60.4.tgz", + "integrity": "sha512-+qfSY27qIrFfI/Hom04KYFw3GKZSGU4lXus51wsb5EuySfFlWRwjkKWoE9emgRw/ukoT4Udsj4W/+xxG8VbPKg==", + "cpu": [ + "x64" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-openbsd-x64": { + "version": "4.60.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-openbsd-x64/-/rollup-openbsd-x64-4.60.4.tgz", + "integrity": "sha512-VpTfOPHgVXEBeeR8hZ2O0F3aSso+JDWqTWmTmzcQKted54IAdUVbxE+j/MVxUsKa8L20HJhv3vUezVPoquqWjA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ] + }, + "node_modules/@rollup/rollup-openharmony-arm64": { + "version": "4.60.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.60.4.tgz", + "integrity": "sha512-IPOsh5aRYuLv/nkU51X10Bf75Bsf6+gZdx1X+QP5QM6lIJFHHqbHLG0uJn/hWthzo13UAc2umiUorqZy3axoZg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ] + }, + "node_modules/@rollup/rollup-win32-arm64-msvc": { + "version": "4.60.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.60.4.tgz", + "integrity": "sha512-4QzE9E81OohJ/HKzHhsqU+zcYYojVOXlFMs1DdyMT6qXl/niOH7AVElmmEdUNHHS/oRkc++d5k6Vy85zFs0DEw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-ia32-msvc": { + "version": "4.60.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.60.4.tgz", + "integrity": "sha512-zTPgT1YuHHcd+Tmx7h8aml0FWFVelV5N54oHow9SLj+GfoDy/huQ+UV396N/C7KpMDMiPspRktzM1/0r1usYEA==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-gnu": { + "version": "4.60.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.60.4.tgz", + "integrity": "sha512-DRS4G7mi9lJxqEDezIkKCaUIKCrLUUDCUaCsTPCi/rtqaC6D/jjwslMQyiDU50Ka0JKpeXeRBFBAXwArY52vBw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-msvc": { + "version": "4.60.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.60.4.tgz", + "integrity": "sha512-QVTUovf40zgTqlFVrKA1uXMVvU2QWEFWfAH8Wdc48IxLvrJMQVMBRjuQyUpzZCDkakImib9eVazbWlC6ksWtJw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@types/babel__core": { + "version": "7.20.5", + "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.20.5.tgz", + "integrity": "sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.20.7", + "@babel/types": "^7.20.7", + "@types/babel__generator": "*", + "@types/babel__template": "*", + "@types/babel__traverse": "*" + } + }, + "node_modules/@types/babel__generator": { + "version": "7.27.0", + "resolved": "https://registry.npmjs.org/@types/babel__generator/-/babel__generator-7.27.0.tgz", + "integrity": "sha512-ufFd2Xi92OAVPYsy+P4n7/U7e68fex0+Ee8gSG9KX7eo084CWiQ4sdxktvdl0bOPupXtVJPY19zk6EwWqUQ8lg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.0.0" + } + }, + "node_modules/@types/babel__template": { + "version": "7.4.4", + "resolved": "https://registry.npmjs.org/@types/babel__template/-/babel__template-7.4.4.tgz", + "integrity": "sha512-h/NUaSyG5EyxBIp8YRxo4RMe2/qQgvyowRwVMzhYhBCONbW8PUsg4lkFMrhgZhUe5z3L3MiLDuvyJ/CaPa2A8A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.1.0", + "@babel/types": "^7.0.0" + } + }, + "node_modules/@types/babel__traverse": { + "version": "7.28.0", + "resolved": "https://registry.npmjs.org/@types/babel__traverse/-/babel__traverse-7.28.0.tgz", + "integrity": "sha512-8PvcXf70gTDZBgt9ptxJ8elBeBjcLOAcOtoO/mPJjtji1+CdGbHgm77om1GrsPxsiE+uXIpNSK64UYaIwQXd4Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.28.2" + } + }, + "node_modules/@types/estree": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz", + "integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/node": { + "version": "22.19.19", + "resolved": "https://registry.npmjs.org/@types/node/-/node-22.19.19.tgz", + "integrity": "sha512-dyh/xO2Fh5bYrfWaaqGrRQQGkNdmYw6AmaAUvYeUMNTWQtvb796ikLdmTchRmOlOiIJ1TDXfWgVx1QkUlQ6Hew==", + "dev": true, + "license": "MIT", + "dependencies": { + "undici-types": "~6.21.0" + } + }, + "node_modules/@types/prop-types": { + "version": "15.7.15", + "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.15.tgz", + "integrity": "sha512-F6bEyamV9jKGAFBEmlQnesRPGOQqS2+Uwi0Em15xenOxHaf2hv6L8YCVn3rPdPJOiJfPiCnLIRyvwVaqMY3MIw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/react": { + "version": "18.3.28", + "resolved": "https://registry.npmjs.org/@types/react/-/react-18.3.28.tgz", + "integrity": "sha512-z9VXpC7MWrhfWipitjNdgCauoMLRdIILQsAEV+ZesIzBq/oUlxk0m3ApZuMFCXdnS4U7KrI+l3WRUEGQ8K1QKw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/prop-types": "*", + "csstype": "^3.2.2" + } + }, + "node_modules/@types/react-dom": { + "version": "18.3.7", + "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-18.3.7.tgz", + "integrity": "sha512-MEe3UeoENYVFXzoXEWsvcpg6ZvlrFNlOQ7EOsvhI3CfAXwzPfO8Qwuxd40nepsYKqyyVQnTdEfv68q91yLcKrQ==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "@types/react": "^18.0.0" + } + }, + "node_modules/@vitejs/plugin-react": { + "version": "4.7.0", + "resolved": "https://registry.npmjs.org/@vitejs/plugin-react/-/plugin-react-4.7.0.tgz", + "integrity": "sha512-gUu9hwfWvvEDBBmgtAowQCojwZmJ5mcLn3aufeCsitijs3+f2NsrPtlAWIR6OPiqljl96GVCUbLe0HyqIpVaoA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/core": "^7.28.0", + "@babel/plugin-transform-react-jsx-self": "^7.27.1", + "@babel/plugin-transform-react-jsx-source": "^7.27.1", + "@rolldown/pluginutils": "1.0.0-beta.27", + "@types/babel__core": "^7.20.5", + "react-refresh": "^0.17.0" + }, + "engines": { + "node": "^14.18.0 || >=16.0.0" + }, + "peerDependencies": { + "vite": "^4.2.0 || ^5.0.0 || ^6.0.0 || ^7.0.0" + } + }, + "node_modules/@zip.js/zip.js": { + "version": "2.8.26", + "resolved": "https://registry.npmjs.org/@zip.js/zip.js/-/zip.js-2.8.26.tgz", + "integrity": "sha512-RQ4h9F6DOiHxpdocUDrOl6xBM+yOtz+LkUol47AVWcfebGBDpZ7w7Xvz9PS24JgXvLGiXXzSAfdCdVy1tPlaFA==", + "license": "BSD-3-Clause", + "engines": { + "bun": ">=0.7.0", + "deno": ">=1.0.0", + "node": ">=18.0.0" + } + }, + "node_modules/any-promise": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/any-promise/-/any-promise-1.3.0.tgz", + "integrity": "sha512-7UvmKalWRt1wgjL1RrGxoSJW/0QZFIegpeGvZG9kjp8vrRu55XTHbwnqq2GpXm9uLbcuhxm3IqX9OB4MZR1b2A==", + "dev": true, + "license": "MIT" + }, + "node_modules/anymatch": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz", + "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==", + "dev": true, + "license": "ISC", + "dependencies": { + "normalize-path": "^3.0.0", + "picomatch": "^2.0.4" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/arg": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/arg/-/arg-5.0.2.tgz", + "integrity": "sha512-PYjyFOLKQ9y57JvQ6QLo8dAgNqswh8M1RMJYdQduT6xbWSgK36P/Z/v+p888pM69jMMfS8Xd8F6I1kQ/I9HUGg==", + "dev": true, + "license": "MIT" + }, + "node_modules/autoprefixer": { + "version": "10.5.0", + "resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-10.5.0.tgz", + "integrity": "sha512-FMhOoZV4+qR6aTUALKX2rEqGG+oyATvwBt9IIzVR5rMa2HRWPkxf+P+PAJLD1I/H5/II+HuZcBJYEFBpq39ong==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/autoprefixer" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "browserslist": "^4.28.2", + "caniuse-lite": "^1.0.30001787", + "fraction.js": "^5.3.4", + "picocolors": "^1.1.1", + "postcss-value-parser": "^4.2.0" + }, + "bin": { + "autoprefixer": "bin/autoprefixer" + }, + "engines": { + "node": "^10 || ^12 || >=14" + }, + "peerDependencies": { + "postcss": "^8.1.0" + } + }, + "node_modules/baseline-browser-mapping": { + "version": "2.10.29", + "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.10.29.tgz", + "integrity": "sha512-Asa2krT+XTPZINCS+2QcyS8WTkObE77RwkydwF7h6DmnKqbvlalz93m/dnphUyCa6SWSP51VgtEUf2FN+gelFQ==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "baseline-browser-mapping": "dist/cli.cjs" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/binary-extensions": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.3.0.tgz", + "integrity": "sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/braces": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", + "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", + "dev": true, + "license": "MIT", + "dependencies": { + "fill-range": "^7.1.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/browserslist": { + "version": "4.28.2", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.28.2.tgz", + "integrity": "sha512-48xSriZYYg+8qXna9kwqjIVzuQxi+KYWp2+5nCYnYKPTr0LvD89Jqk2Or5ogxz0NUMfIjhh2lIUX/LyX9B4oIg==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "baseline-browser-mapping": "^2.10.12", + "caniuse-lite": "^1.0.30001782", + "electron-to-chromium": "^1.5.328", + "node-releases": "^2.0.36", + "update-browserslist-db": "^1.2.3" + }, + "bin": { + "browserslist": "cli.js" + }, + "engines": { + "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" + } + }, + "node_modules/camelcase-css": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/camelcase-css/-/camelcase-css-2.0.1.tgz", + "integrity": "sha512-QOSvevhslijgYwRx6Rv7zKdMF8lbRmx+uQGx2+vDc+KI/eBnsy9kit5aj23AgGu3pa4t9AgwbnXWqS+iOY+2aA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 6" + } + }, + "node_modules/caniuse-lite": { + "version": "1.0.30001792", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001792.tgz", + "integrity": "sha512-hVLMUZFgR4JJ6ACt1uEESvQN1/dBVqPAKY0hgrV70eN3391K6juAfTjKZLKvOMsx8PxA7gsY1/tLMMTcfFLLpw==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/caniuse-lite" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "CC-BY-4.0" + }, + "node_modules/chokidar": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz", + "integrity": "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==", + "dev": true, + "license": "MIT", + "dependencies": { + "anymatch": "~3.1.2", + "braces": "~3.0.2", + "glob-parent": "~5.1.2", + "is-binary-path": "~2.1.0", + "is-glob": "~4.0.1", + "normalize-path": "~3.0.0", + "readdirp": "~3.6.0" + }, + "engines": { + "node": ">= 8.10.0" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + }, + "optionalDependencies": { + "fsevents": "~2.3.2" + } + }, + "node_modules/chokidar/node_modules/glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "dev": true, + "license": "ISC", + "dependencies": { + "is-glob": "^4.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/commander": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/commander/-/commander-4.1.1.tgz", + "integrity": "sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 6" + } + }, + "node_modules/convert-source-map": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", + "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", + "dev": true, + "license": "MIT" + }, + "node_modules/cssesc": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/cssesc/-/cssesc-3.0.0.tgz", + "integrity": "sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==", + "dev": true, + "license": "MIT", + "bin": { + "cssesc": "bin/cssesc" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/csstype": { + "version": "3.2.3", + "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.2.3.tgz", + "integrity": "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/debug": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/didyoumean": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/didyoumean/-/didyoumean-1.2.2.tgz", + "integrity": "sha512-gxtyfqMg7GKyhQmb056K7M3xszy/myH8w+B4RT+QXBQsvAOdc3XymqDDPHx1BgPgsdAA5SIifona89YtRATDzw==", + "dev": true, + "license": "Apache-2.0" + }, + "node_modules/dlv": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/dlv/-/dlv-1.1.3.tgz", + "integrity": "sha512-+HlytyjlPKnIG8XuRG8WvmBP8xs8P71y+SKKS6ZXWoEgLuePxtDoUEiH7WkdePWrQ5JBpE6aoVqfZfJUQkjXwA==", + "dev": true, + "license": "MIT" + }, + "node_modules/electron-to-chromium": { + "version": "1.5.356", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.356.tgz", + "integrity": "sha512-9NgFd7m5t5MCJ5rUSjJITUXAH9mEGlrlofnMf4YEr+pz6JlP7cWmTAH+JFmbPnaSW8koVTkuW7pacORWAnA5Yw==", + "dev": true, + "license": "ISC" + }, + "node_modules/es-errors": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", + "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/esbuild": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.21.5.tgz", + "integrity": "sha512-mg3OPMV4hXywwpoDxu3Qda5xCKQi+vCTZq8S9J/EpkhB2HzKXq4SNFZE3+NK93JYxc8VMSep+lOUSC/RVKaBqw==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=12" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.21.5", + "@esbuild/android-arm": "0.21.5", + "@esbuild/android-arm64": "0.21.5", + "@esbuild/android-x64": "0.21.5", + "@esbuild/darwin-arm64": "0.21.5", + "@esbuild/darwin-x64": "0.21.5", + "@esbuild/freebsd-arm64": "0.21.5", + "@esbuild/freebsd-x64": "0.21.5", + "@esbuild/linux-arm": "0.21.5", + "@esbuild/linux-arm64": "0.21.5", + "@esbuild/linux-ia32": "0.21.5", + "@esbuild/linux-loong64": "0.21.5", + "@esbuild/linux-mips64el": "0.21.5", + "@esbuild/linux-ppc64": "0.21.5", + "@esbuild/linux-riscv64": "0.21.5", + "@esbuild/linux-s390x": "0.21.5", + "@esbuild/linux-x64": "0.21.5", + "@esbuild/netbsd-x64": "0.21.5", + "@esbuild/openbsd-x64": "0.21.5", + "@esbuild/sunos-x64": "0.21.5", + "@esbuild/win32-arm64": "0.21.5", + "@esbuild/win32-ia32": "0.21.5", + "@esbuild/win32-x64": "0.21.5" + } + }, + "node_modules/escalade": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", + "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/fast-glob": { + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.3.tgz", + "integrity": "sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@nodelib/fs.stat": "^2.0.2", + "@nodelib/fs.walk": "^1.2.3", + "glob-parent": "^5.1.2", + "merge2": "^1.3.0", + "micromatch": "^4.0.8" + }, + "engines": { + "node": ">=8.6.0" + } + }, + "node_modules/fast-glob/node_modules/glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "dev": true, + "license": "ISC", + "dependencies": { + "is-glob": "^4.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/fastq": { + "version": "1.20.1", + "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.20.1.tgz", + "integrity": "sha512-GGToxJ/w1x32s/D2EKND7kTil4n8OVk/9mycTc4VDza13lOvpUZTGX3mFSCtV9ksdGBVzvsyAVLM6mHFThxXxw==", + "dev": true, + "license": "ISC", + "dependencies": { + "reusify": "^1.0.4" + } + }, + "node_modules/fill-range": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", + "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", + "dev": true, + "license": "MIT", + "dependencies": { + "to-regex-range": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/fraction.js": { + "version": "5.3.4", + "resolved": "https://registry.npmjs.org/fraction.js/-/fraction.js-5.3.4.tgz", + "integrity": "sha512-1X1NTtiJphryn/uLQz3whtY6jK3fTqoE3ohKs0tT+Ujr1W59oopxmoEh7Lu5p6vBaPbgoM0bzveAW4Qi5RyWDQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": "*" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/rawify" + } + }, + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/function-bind": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/gensync": { + "version": "1.0.0-beta.2", + "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", + "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/glob-parent": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", + "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", + "dev": true, + "license": "ISC", + "dependencies": { + "is-glob": "^4.0.3" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/hasown": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.3.tgz", + "integrity": "sha512-ej4AhfhfL2Q2zpMmLo7U1Uv9+PyhIZpgQLGT1F9miIGmiCJIoCgSmczFdrc97mWT4kVY72KA+WnnhJ5pghSvSg==", + "dev": true, + "license": "MIT", + "dependencies": { + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/is-binary-path": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", + "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", + "dev": true, + "license": "MIT", + "dependencies": { + "binary-extensions": "^2.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/is-core-module": { + "version": "2.16.2", + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.16.2.tgz", + "integrity": "sha512-evOr8xfXKxE6qSR0hSXL2r3sd7ALj8+7jQEUvPYcm5sgZFdJ+AYzT6yNmJenvIYQBgIGwfwz08sL8zoL7yq2BA==", + "dev": true, + "license": "MIT", + "dependencies": { + "hasown": "^2.0.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-glob": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", + "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-extglob": "^2.1.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.12.0" + } + }, + "node_modules/jiti": { + "version": "1.21.7", + "resolved": "https://registry.npmjs.org/jiti/-/jiti-1.21.7.tgz", + "integrity": "sha512-/imKNG4EbWNrVjoNC/1H5/9GFy+tqjGBHCaSsN+P2RnPqjsLmv6UD3Ej+Kj8nBWaRAwyk7kK5ZUc+OEatnTR3A==", + "dev": true, + "license": "MIT", + "bin": { + "jiti": "bin/jiti.js" + } + }, + "node_modules/js-tokens": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", + "license": "MIT" + }, + "node_modules/jsesc": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.1.0.tgz", + "integrity": "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==", + "dev": true, + "license": "MIT", + "bin": { + "jsesc": "bin/jsesc" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/json5": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", + "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", + "dev": true, + "license": "MIT", + "bin": { + "json5": "lib/cli.js" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/lilconfig": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/lilconfig/-/lilconfig-3.1.3.tgz", + "integrity": "sha512-/vlFKAoH5Cgt3Ie+JLhRbwOsCQePABiU3tJ1egGvyQ+33R/vcwM2Zl2QR/LzjsBeItPt3oSVXapn+m4nQDvpzw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/antonk52" + } + }, + "node_modules/lines-and-columns": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz", + "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==", + "dev": true, + "license": "MIT" + }, + "node_modules/loose-envify": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz", + "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==", + "license": "MIT", + "dependencies": { + "js-tokens": "^3.0.0 || ^4.0.0" + }, + "bin": { + "loose-envify": "cli.js" + } + }, + "node_modules/lru-cache": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", + "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", + "dev": true, + "license": "ISC", + "dependencies": { + "yallist": "^3.0.2" + } + }, + "node_modules/merge2": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", + "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 8" + } + }, + "node_modules/micromatch": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz", + "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==", + "dev": true, + "license": "MIT", + "dependencies": { + "braces": "^3.0.3", + "picomatch": "^2.3.1" + }, + "engines": { + "node": ">=8.6" + } + }, + "node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "dev": true, + "license": "MIT" + }, + "node_modules/mz": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/mz/-/mz-2.7.0.tgz", + "integrity": "sha512-z81GNO7nnYMEhrGh9LeymoE4+Yr0Wn5McHIZMK5cfQCl+NDX08sCZgUc9/6MHni9IWuFLm1Z3HTCXu2z9fN62Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "any-promise": "^1.0.0", + "object-assign": "^4.0.1", + "thenify-all": "^1.0.0" + } + }, + "node_modules/nanoid": { + "version": "3.3.12", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.12.tgz", + "integrity": "sha512-ZB9RH/39qpq5Vu6Y+NmUaFhQR6pp+M2Xt76XBnEwDaGcVAqhlvxrl3B2bKS5D3NH3QR76v3aSrKaF/Kiy7lEtQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "bin": { + "nanoid": "bin/nanoid.cjs" + }, + "engines": { + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" + } + }, + "node_modules/node-releases": { + "version": "2.0.44", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.44.tgz", + "integrity": "sha512-5WUyunoPMsvvEhS8AxHtRzP+oA8UCkJ7YRxatWKjngndhDGLiqEVAQKWjFAiAiuL8zMRGzGSJxFnLetoa43qGQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/normalize-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", + "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/object-assign": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", + "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/object-hash": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/object-hash/-/object-hash-3.0.0.tgz", + "integrity": "sha512-RSn9F68PjH9HqtltsSnqYC1XXoWe9Bju5+213R98cNGttag9q9yAOTzdbsqvIa7aNm5WffBZFpWYr2aWrklWAw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 6" + } + }, + "node_modules/path-parse": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", + "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", + "dev": true, + "license": "MIT" + }, + "node_modules/picocolors": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", + "dev": true, + "license": "ISC" + }, + "node_modules/picomatch": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.2.tgz", + "integrity": "sha512-V7+vQEJ06Z+c5tSye8S+nHUfI51xoXIXjHQ99cQtKUkQqqO1kO/KCJUfZXuB47h/YBlDhah2H3hdUGXn8ie0oA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/pify": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz", + "integrity": "sha512-udgsAY+fTnvv7kI7aaxbqwWNb0AHiB0qBO89PZKPkoTmGOgdbrHDKD+0B2X4uTfJ/FT1R09r9gTsjUjNJotuog==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/pirates": { + "version": "4.0.7", + "resolved": "https://registry.npmjs.org/pirates/-/pirates-4.0.7.tgz", + "integrity": "sha512-TfySrs/5nm8fQJDcBDuUng3VOUKsd7S+zqvbOTiGXHfxX4wK31ard+hoNuvkicM/2YFzlpDgABOevKSsB4G/FA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 6" + } + }, + "node_modules/postcss": { + "version": "8.5.14", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.14.tgz", + "integrity": "sha512-SoSL4+OSEtR99LHFZQiJLkT59C5B1amGO1NzTwj7TT1qCUgUO6hxOvzkOYxD+vMrXBM3XJIKzokoERdqQq/Zmg==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "nanoid": "^3.3.11", + "picocolors": "^1.1.1", + "source-map-js": "^1.2.1" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, + "node_modules/postcss-import": { + "version": "15.1.0", + "resolved": "https://registry.npmjs.org/postcss-import/-/postcss-import-15.1.0.tgz", + "integrity": "sha512-hpr+J05B2FVYUAXHeK1YyI267J/dDDhMU6B6civm8hSY1jYJnBXxzKDKDswzJmtLHryrjhnDjqqp/49t8FALew==", + "dev": true, + "license": "MIT", + "dependencies": { + "postcss-value-parser": "^4.0.0", + "read-cache": "^1.0.0", + "resolve": "^1.1.7" + }, + "engines": { + "node": ">=14.0.0" + }, + "peerDependencies": { + "postcss": "^8.0.0" + } + }, + "node_modules/postcss-js": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/postcss-js/-/postcss-js-4.1.0.tgz", + "integrity": "sha512-oIAOTqgIo7q2EOwbhb8UalYePMvYoIeRY2YKntdpFQXNosSu3vLrniGgmH9OKs/qAkfoj5oB3le/7mINW1LCfw==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "camelcase-css": "^2.0.1" + }, + "engines": { + "node": "^12 || ^14 || >= 16" + }, + "peerDependencies": { + "postcss": "^8.4.21" + } + }, + "node_modules/postcss-load-config": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/postcss-load-config/-/postcss-load-config-6.0.1.tgz", + "integrity": "sha512-oPtTM4oerL+UXmx+93ytZVN82RrlY/wPUV8IeDxFrzIjXOLF1pN+EmKPLbubvKHT2HC20xXsCAH2Z+CKV6Oz/g==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "lilconfig": "^3.1.1" + }, + "engines": { + "node": ">= 18" + }, + "peerDependencies": { + "jiti": ">=1.21.0", + "postcss": ">=8.0.9", + "tsx": "^4.8.1", + "yaml": "^2.4.2" + }, + "peerDependenciesMeta": { + "jiti": { + "optional": true + }, + "postcss": { + "optional": true + }, + "tsx": { + "optional": true + }, + "yaml": { + "optional": true + } + } + }, + "node_modules/postcss-nested": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/postcss-nested/-/postcss-nested-6.2.0.tgz", + "integrity": "sha512-HQbt28KulC5AJzG+cZtj9kvKB93CFCdLvog1WFLf1D+xmMvPGlBstkpTEZfK5+AN9hfJocyBFCNiqyS48bpgzQ==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "postcss-selector-parser": "^6.1.1" + }, + "engines": { + "node": ">=12.0" + }, + "peerDependencies": { + "postcss": "^8.2.14" + } + }, + "node_modules/postcss-selector-parser": { + "version": "6.1.2", + "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.1.2.tgz", + "integrity": "sha512-Q8qQfPiZ+THO/3ZrOrO0cJJKfpYCagtMUkXbnEfmgUjwXg6z/WBeOyS9APBBPCTSiDV+s4SwQGu8yFsiMRIudg==", + "dev": true, + "license": "MIT", + "dependencies": { + "cssesc": "^3.0.0", + "util-deprecate": "^1.0.2" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/postcss-value-parser": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz", + "integrity": "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/queue-microtask": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", + "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, + "node_modules/react": { + "version": "18.3.1", + "resolved": "https://registry.npmjs.org/react/-/react-18.3.1.tgz", + "integrity": "sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ==", + "license": "MIT", + "dependencies": { + "loose-envify": "^1.1.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/react-dom": { + "version": "18.3.1", + "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.3.1.tgz", + "integrity": "sha512-5m4nQKp+rZRb09LNH59GM4BxTh9251/ylbKIbpe7TpGxfJ+9kv6BLkLBXIjjspbgbnIBNqlI23tRnTWT0snUIw==", + "license": "MIT", + "dependencies": { + "loose-envify": "^1.1.0", + "scheduler": "^0.23.2" + }, + "peerDependencies": { + "react": "^18.3.1" + } + }, + "node_modules/react-refresh": { + "version": "0.17.0", + "resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.17.0.tgz", + "integrity": "sha512-z6F7K9bV85EfseRCp2bzrpyQ0Gkw1uLoCel9XBVWPg/TjRj94SkJzUTGfOa4bs7iJvBWtQG0Wq7wnI0syw3EBQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/react-router": { + "version": "6.30.3", + "resolved": "https://registry.npmjs.org/react-router/-/react-router-6.30.3.tgz", + "integrity": "sha512-XRnlbKMTmktBkjCLE8/XcZFlnHvr2Ltdr1eJX4idL55/9BbORzyZEaIkBFDhFGCEWBBItsVrDxwx3gnisMitdw==", + "license": "MIT", + "dependencies": { + "@remix-run/router": "1.23.2" + }, + "engines": { + "node": ">=14.0.0" + }, + "peerDependencies": { + "react": ">=16.8" + } + }, + "node_modules/react-router-dom": { + "version": "6.30.3", + "resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-6.30.3.tgz", + "integrity": "sha512-pxPcv1AczD4vso7G4Z3TKcvlxK7g7TNt3/FNGMhfqyntocvYKj+GCatfigGDjbLozC4baguJ0ReCigoDJXb0ag==", + "license": "MIT", + "dependencies": { + "@remix-run/router": "1.23.2", + "react-router": "6.30.3" + }, + "engines": { + "node": ">=14.0.0" + }, + "peerDependencies": { + "react": ">=16.8", + "react-dom": ">=16.8" + } + }, + "node_modules/read-cache": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/read-cache/-/read-cache-1.0.0.tgz", + "integrity": "sha512-Owdv/Ft7IjOgm/i0xvNDZ1LrRANRfew4b2prF3OWMQLxLfu3bS8FVhCsrSCMK4lR56Y9ya+AThoTpDCTxCmpRA==", + "dev": true, + "license": "MIT", + "dependencies": { + "pify": "^2.3.0" + } + }, + "node_modules/readdirp": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", + "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", + "dev": true, + "license": "MIT", + "dependencies": { + "picomatch": "^2.2.1" + }, + "engines": { + "node": ">=8.10.0" + } + }, + "node_modules/resolve": { + "version": "1.22.12", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.12.tgz", + "integrity": "sha512-TyeJ1zif53BPfHootBGwPRYT1RUt6oGWsaQr8UyZW/eAm9bKoijtvruSDEmZHm92CwS9nj7/fWttqPCgzep8CA==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "is-core-module": "^2.16.1", + "path-parse": "^1.0.7", + "supports-preserve-symlinks-flag": "^1.0.0" + }, + "bin": { + "resolve": "bin/resolve" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/reusify": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.1.0.tgz", + "integrity": "sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw==", + "dev": true, + "license": "MIT", + "engines": { + "iojs": ">=1.0.0", + "node": ">=0.10.0" + } + }, + "node_modules/rollup": { + "version": "4.60.4", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.60.4.tgz", + "integrity": "sha512-WHeFSbZYsPu3+bLoNRUuAO+wavNlocOPf3wSHTP7hcFKVnJeWsYlCDbr3mTS14FCizf9ccIxXA8sGL8zKeQN3g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "1.0.8" + }, + "bin": { + "rollup": "dist/bin/rollup" + }, + "engines": { + "node": ">=18.0.0", + "npm": ">=8.0.0" + }, + "optionalDependencies": { + "@rollup/rollup-android-arm-eabi": "4.60.4", + "@rollup/rollup-android-arm64": "4.60.4", + "@rollup/rollup-darwin-arm64": "4.60.4", + "@rollup/rollup-darwin-x64": "4.60.4", + "@rollup/rollup-freebsd-arm64": "4.60.4", + "@rollup/rollup-freebsd-x64": "4.60.4", + "@rollup/rollup-linux-arm-gnueabihf": "4.60.4", + "@rollup/rollup-linux-arm-musleabihf": "4.60.4", + "@rollup/rollup-linux-arm64-gnu": "4.60.4", + "@rollup/rollup-linux-arm64-musl": "4.60.4", + "@rollup/rollup-linux-loong64-gnu": "4.60.4", + "@rollup/rollup-linux-loong64-musl": "4.60.4", + "@rollup/rollup-linux-ppc64-gnu": "4.60.4", + "@rollup/rollup-linux-ppc64-musl": "4.60.4", + "@rollup/rollup-linux-riscv64-gnu": "4.60.4", + "@rollup/rollup-linux-riscv64-musl": "4.60.4", + "@rollup/rollup-linux-s390x-gnu": "4.60.4", + "@rollup/rollup-linux-x64-gnu": "4.60.4", + "@rollup/rollup-linux-x64-musl": "4.60.4", + "@rollup/rollup-openbsd-x64": "4.60.4", + "@rollup/rollup-openharmony-arm64": "4.60.4", + "@rollup/rollup-win32-arm64-msvc": "4.60.4", + "@rollup/rollup-win32-ia32-msvc": "4.60.4", + "@rollup/rollup-win32-x64-gnu": "4.60.4", + "@rollup/rollup-win32-x64-msvc": "4.60.4", + "fsevents": "~2.3.2" + } + }, + "node_modules/run-parallel": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", + "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT", + "dependencies": { + "queue-microtask": "^1.2.2" + } + }, + "node_modules/scheduler": { + "version": "0.23.2", + "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.23.2.tgz", + "integrity": "sha512-UOShsPwz7NrMUqhR6t0hWjFduvOzbtv7toDH1/hIrfRNIDBnnBWd0CwJTGvTpngVlmwGCdP9/Zl/tVrDqcuYzQ==", + "license": "MIT", + "dependencies": { + "loose-envify": "^1.1.0" + } + }, + "node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/source-map-js": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", + "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/sucrase": { + "version": "3.35.1", + "resolved": "https://registry.npmjs.org/sucrase/-/sucrase-3.35.1.tgz", + "integrity": "sha512-DhuTmvZWux4H1UOnWMB3sk0sbaCVOoQZjv8u1rDoTV0HTdGem9hkAZtl4JZy8P2z4Bg0nT+YMeOFyVr4zcG5Tw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.2", + "commander": "^4.0.0", + "lines-and-columns": "^1.1.6", + "mz": "^2.7.0", + "pirates": "^4.0.1", + "tinyglobby": "^0.2.11", + "ts-interface-checker": "^0.1.9" + }, + "bin": { + "sucrase": "bin/sucrase", + "sucrase-node": "bin/sucrase-node" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + } + }, + "node_modules/supports-preserve-symlinks-flag": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", + "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/tailwindcss": { + "version": "3.4.19", + "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-3.4.19.tgz", + "integrity": "sha512-3ofp+LL8E+pK/JuPLPggVAIaEuhvIz4qNcf3nA1Xn2o/7fb7s/TYpHhwGDv1ZU3PkBluUVaF8PyCHcm48cKLWQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@alloc/quick-lru": "^5.2.0", + "arg": "^5.0.2", + "chokidar": "^3.6.0", + "didyoumean": "^1.2.2", + "dlv": "^1.1.3", + "fast-glob": "^3.3.2", + "glob-parent": "^6.0.2", + "is-glob": "^4.0.3", + "jiti": "^1.21.7", + "lilconfig": "^3.1.3", + "micromatch": "^4.0.8", + "normalize-path": "^3.0.0", + "object-hash": "^3.0.0", + "picocolors": "^1.1.1", + "postcss": "^8.4.47", + "postcss-import": "^15.1.0", + "postcss-js": "^4.0.1", + "postcss-load-config": "^4.0.2 || ^5.0 || ^6.0", + "postcss-nested": "^6.2.0", + "postcss-selector-parser": "^6.1.2", + "resolve": "^1.22.8", + "sucrase": "^3.35.0" + }, + "bin": { + "tailwind": "lib/cli.js", + "tailwindcss": "lib/cli.js" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/thenify": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/thenify/-/thenify-3.3.1.tgz", + "integrity": "sha512-RVZSIV5IG10Hk3enotrhvz0T9em6cyHBLkH/YAZuKqd8hRkKhSfCGIcP2KUY0EPxndzANBmNllzWPwak+bheSw==", + "dev": true, + "license": "MIT", + "dependencies": { + "any-promise": "^1.0.0" + } + }, + "node_modules/thenify-all": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/thenify-all/-/thenify-all-1.6.0.tgz", + "integrity": "sha512-RNxQH/qI8/t3thXJDwcstUO4zeqo64+Uy/+sNVRBx4Xn2OX+OZ9oP+iJnNFqplFra2ZUVeKCSa2oVWi3T4uVmA==", + "dev": true, + "license": "MIT", + "dependencies": { + "thenify": ">= 3.1.0 < 4" + }, + "engines": { + "node": ">=0.8" + } + }, + "node_modules/tinyglobby": { + "version": "0.2.16", + "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.16.tgz", + "integrity": "sha512-pn99VhoACYR8nFHhxqix+uvsbXineAasWm5ojXoN8xEwK5Kd3/TrhNn1wByuD52UxWRLy8pu+kRMniEi6Eq9Zg==", + "dev": true, + "license": "MIT", + "dependencies": { + "fdir": "^6.5.0", + "picomatch": "^4.0.4" + }, + "engines": { + "node": ">=12.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/SuperchupuDev" + } + }, + "node_modules/tinyglobby/node_modules/fdir": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", + "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12.0.0" + }, + "peerDependencies": { + "picomatch": "^3 || ^4" + }, + "peerDependenciesMeta": { + "picomatch": { + "optional": true + } + } + }, + "node_modules/tinyglobby/node_modules/picomatch": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.4.tgz", + "integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/to-regex-range": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-number": "^7.0.0" + }, + "engines": { + "node": ">=8.0" + } + }, + "node_modules/ts-interface-checker": { + "version": "0.1.13", + "resolved": "https://registry.npmjs.org/ts-interface-checker/-/ts-interface-checker-0.1.13.tgz", + "integrity": "sha512-Y/arvbn+rrz3JCKl9C4kVNfTfSm2/mEp5FSz5EsZSANGPSlQrpRI5M4PKF+mJnE52jOO90PnPSc3Ur3bTQw0gA==", + "dev": true, + "license": "Apache-2.0" + }, + "node_modules/typescript": { + "version": "5.9.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", + "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/undici-types": { + "version": "6.21.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz", + "integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/update-browserslist-db": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.2.3.tgz", + "integrity": "sha512-Js0m9cx+qOgDxo0eMiFGEueWztz+d4+M3rGlmKPT+T4IS/jP4ylw3Nwpu6cpTTP8R1MAC1kF4VbdLt3ARf209w==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "escalade": "^3.2.0", + "picocolors": "^1.1.1" + }, + "bin": { + "update-browserslist-db": "cli.js" + }, + "peerDependencies": { + "browserslist": ">= 4.21.0" + } + }, + "node_modules/util-deprecate": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", + "dev": true, + "license": "MIT" + }, + "node_modules/vite": { + "version": "5.4.21", + "resolved": "https://registry.npmjs.org/vite/-/vite-5.4.21.tgz", + "integrity": "sha512-o5a9xKjbtuhY6Bi5S3+HvbRERmouabWbyUcpXXUA1u+GNUKoROi9byOJ8M0nHbHYHkYICiMlqxkg1KkYmm25Sw==", + "dev": true, + "license": "MIT", + "dependencies": { + "esbuild": "^0.21.3", + "postcss": "^8.4.43", + "rollup": "^4.20.0" + }, + "bin": { + "vite": "bin/vite.js" + }, + "engines": { + "node": "^18.0.0 || >=20.0.0" + }, + "funding": { + "url": "https://github.com/vitejs/vite?sponsor=1" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" + }, + "peerDependencies": { + "@types/node": "^18.0.0 || >=20.0.0", + "less": "*", + "lightningcss": "^1.21.0", + "sass": "*", + "sass-embedded": "*", + "stylus": "*", + "sugarss": "*", + "terser": "^5.4.0" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + }, + "less": { + "optional": true + }, + "lightningcss": { + "optional": true + }, + "sass": { + "optional": true + }, + "sass-embedded": { + "optional": true + }, + "stylus": { + "optional": true + }, + "sugarss": { + "optional": true + }, + "terser": { + "optional": true + } + } + }, + "node_modules/yallist": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", + "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", + "dev": true, + "license": "ISC" + } + } +} diff --git a/DorkNet.Server/admin-ui/package.json b/DorkNet.Server/admin-ui/package.json new file mode 100644 index 0000000..450638b --- /dev/null +++ b/DorkNet.Server/admin-ui/package.json @@ -0,0 +1,29 @@ +{ + "name": "dorknet-admin-ui", + "private": true, + "version": "0.1.0", + "type": "module", + "scripts": { + "dev": "vite", + "build": "tsc -b && vite build", + "preview": "vite preview", + "typecheck": "tsc -b --noEmit" + }, + "dependencies": { + "@zip.js/zip.js": "^2.7.52", + "react": "^18.3.1", + "react-dom": "^18.3.1", + "react-router-dom": "^6.27.0" + }, + "devDependencies": { + "@types/node": "^22.9.1", + "@types/react": "^18.3.12", + "@types/react-dom": "^18.3.1", + "@vitejs/plugin-react": "^4.3.3", + "autoprefixer": "^10.4.20", + "postcss": "^8.4.49", + "tailwindcss": "^3.4.15", + "typescript": "^5.6.3", + "vite": "^5.4.11" + } +} diff --git a/DorkNet.Server/admin-ui/postcss.config.js b/DorkNet.Server/admin-ui/postcss.config.js new file mode 100644 index 0000000..2aa7205 --- /dev/null +++ b/DorkNet.Server/admin-ui/postcss.config.js @@ -0,0 +1,6 @@ +export default { + plugins: { + tailwindcss: {}, + autoprefixer: {}, + }, +}; diff --git a/DorkNet.Server/admin-ui/src/App.tsx b/DorkNet.Server/admin-ui/src/App.tsx new file mode 100644 index 0000000..32b7da7 --- /dev/null +++ b/DorkNet.Server/admin-ui/src/App.tsx @@ -0,0 +1,62 @@ +import { Routes, Route, Navigate } from 'react-router-dom'; +import { Layout } from './components/Layout'; +import { RequireAuth } from './components/RequireAuth'; +import { ToastProvider } from './components/Toast'; +import { Login } from './pages/Login'; +import { Dashboard } from './pages/Dashboard'; +import { Players } from './pages/Players'; +import { Bans } from './pages/Bans'; +import { Reports } from './pages/Reports'; +import { Audit } from './pages/Audit'; +import { PlayerLogs } from './pages/PlayerLogs'; +import { Rooms } from './pages/Rooms'; +import { Room } from './pages/Room'; +import { ImportRoom } from './pages/ImportRoom'; +import { LegacyImportRoom } from './pages/LegacyImportRoom'; +import { Store } from './pages/Store'; +import { Broadcast } from './pages/Broadcast'; +import { CommunityBoard } from './pages/CommunityBoard'; +import { LoadingTips } from './pages/LoadingTips'; +import { Gift } from './pages/Gift'; +import { Passwords } from './pages/Passwords'; +import { Grants } from './pages/Grants'; +import { Settings } from './pages/Settings'; + +export default function App() { + return ( + + + } /> + }> + } /> + } /> + } /> + } /> + } /> + } /> + } /> + } /> + {/* Back-compat: the merge left RR-Originals / Instances / + Leaderboards / old per-room pages folded into /rooms and + /rooms/:id (leaderboards tab inside the per-room view). + Anyone with a stale bookmark hitting those old paths + lands back on the unified rooms list. */} + } /> + } /> + } /> + } /> + } /> + } /> + } /> + } /> + } /> + } /> + } /> + } /> + } /> + + } /> + + + ); +} diff --git a/DorkNet.Server/admin-ui/src/components/Confirm.tsx b/DorkNet.Server/admin-ui/src/components/Confirm.tsx new file mode 100644 index 0000000..350d1cc --- /dev/null +++ b/DorkNet.Server/admin-ui/src/components/Confirm.tsx @@ -0,0 +1,37 @@ +import { useState } from 'react'; +import { Modal } from './Modal'; + +interface Props { + title: string; + body: React.ReactNode; + confirmLabel?: string; + destructive?: boolean; + open: boolean; + onClose: () => void; + onConfirm: () => Promise | void; +} + +export function Confirm({ title, body, confirmLabel = 'Confirm', destructive, open, onClose, onConfirm }: Props) { + const [busy, setBusy] = useState(false); + const run = async () => { + setBusy(true); + try { await onConfirm(); onClose(); } + finally { setBusy(false); } + }; + return ( + + + + } + > +
{body}
+
+ ); +} diff --git a/DorkNet.Server/admin-ui/src/components/Empty.tsx b/DorkNet.Server/admin-ui/src/components/Empty.tsx new file mode 100644 index 0000000..c3d76af --- /dev/null +++ b/DorkNet.Server/admin-ui/src/components/Empty.tsx @@ -0,0 +1,19 @@ +// Tiny stand-in for "nothing matches your filter / nothing exists yet" +// table-empty states. Centered, muted, optional CTA. + +interface Props { + title?: string; + blurb?: string; + cta?: React.ReactNode; + className?: string; +} + +export function Empty({ title = 'Nothing here', blurb, cta, className }: Props) { + return ( +
+
{title}
+ {blurb &&
{blurb}
} + {cta &&
{cta}
} +
+ ); +} diff --git a/DorkNet.Server/admin-ui/src/components/Icons.tsx b/DorkNet.Server/admin-ui/src/components/Icons.tsx new file mode 100644 index 0000000..75940af --- /dev/null +++ b/DorkNet.Server/admin-ui/src/components/Icons.tsx @@ -0,0 +1,100 @@ +// Inline SVG icons. Keeping them inline avoids a lucide-react / heroicons +// dependency for the dozen glyphs the sidebar needs. All icons take a +// className override so the parent's text color cascades via currentColor. + +type IconProps = { className?: string }; +const base = 'size-4 shrink-0 stroke-current'; + +const Svg = ({ children, className }: IconProps & { children: React.ReactNode }) => ( + +); + +export const LayoutDashboard = (p: IconProps) => ( + +); +export const Users = (p: IconProps) => ( + +); +export const Building2 = (p: IconProps) => ( + +); +export const Ban = (p: IconProps) => ( + +); +export const Flag = (p: IconProps) => ( + +); +export const ScrollText = (p: IconProps) => ( + +); +export const Activity = (p: IconProps) => ( + +); +export const Megaphone = (p: IconProps) => ( + +); +export const Upload = (p: IconProps) => ( + +); +export const ShoppingBag = (p: IconProps) => ( + +); +export const Server = (p: IconProps) => ( + +); +export const Gift = (p: IconProps) => ( + +); +export const KeyRound = (p: IconProps) => ( + +); +export const Wrench = (p: IconProps) => ( + +); +export const MessageSquare = (p: IconProps) => ( + +); +export const LogOut = (p: IconProps) => ( + +); +export const Menu = (p: IconProps) => ( + +); +export const X = (p: IconProps) => ( + +); +export const Search = (p: IconProps) => ( + +); +export const Plus = (p: IconProps) => ( + +); +export const Trash = (p: IconProps) => ( + +); +export const RefreshCw = (p: IconProps) => ( + +); +export const HardDrive = (p: IconProps) => ( + +); +export const Settings = (p: IconProps) => ( + +); +export const ArrowLeft = (p: IconProps) => ( + +); +export const Trophy = (p: IconProps) => ( + +); diff --git a/DorkNet.Server/admin-ui/src/components/Layout.tsx b/DorkNet.Server/admin-ui/src/components/Layout.tsx new file mode 100644 index 0000000..7776b7f --- /dev/null +++ b/DorkNet.Server/admin-ui/src/components/Layout.tsx @@ -0,0 +1,63 @@ +import { Outlet, useLocation, useNavigate } from 'react-router-dom'; +import { useEffect, useState, useSyncExternalStore } from 'react'; +import { Sidebar } from './Sidebar'; +import { LogOut, Menu } from './Icons'; +import { clearSession, getMe, notifyAuthChange, subscribeAuth } from '../lib/auth'; + +export function Layout() { + const navigate = useNavigate(); + const location = useLocation(); + const me = useSyncExternalStore(subscribeAuth, getMe, () => null); + const [drawerOpen, setDrawerOpen] = useState(false); + + // Defensive: close the drawer on route change in case the user opens + // it then navigates via something other than the sidebar links + // (e.g. the legacy importer link in PageHeader actions). The drawer + // also closes via NavLink onClick → onNavigate, so this is a belt. + useEffect(() => { setDrawerOpen(false); }, [location.pathname]); + + const logout = () => { + clearSession(); + notifyAuthChange(); + navigate('/login', { replace: true }); + }; + + return ( +
+ setDrawerOpen(false)} + onNavigate={() => setDrawerOpen(false)} + /> +
+
+ +
+ {/* Tighter copy on small screens — the "Signed in as" prefix + eats too much width on a phone. Tablet+ shows the full + phrase. */} + Signed in as + + {me?.displayName ?? me?.username ?? `#${me?.id ?? '?'}`} + +
+ +
+
+ +
+
+
+ ); +} diff --git a/DorkNet.Server/admin-ui/src/components/Modal.tsx b/DorkNet.Server/admin-ui/src/components/Modal.tsx new file mode 100644 index 0000000..df92e6b --- /dev/null +++ b/DorkNet.Server/admin-ui/src/components/Modal.tsx @@ -0,0 +1,69 @@ +import { useEffect } from 'react'; + +interface Props { + title: string; + open: boolean; + onClose: () => void; + children: React.ReactNode; + // Wider for dense edit forms (e.g. player detail with multiple sections). + size?: 'sm' | 'md' | 'lg' | 'xl'; + // Sticky action bar at the bottom — pass buttons here so the form + // body can scroll but the actions stay visible on small screens. + footer?: React.ReactNode; +} + +export function Modal({ title, open, onClose, children, size = 'md', footer }: Props) { + useEffect(() => { + if (!open) return; + const onKey = (e: KeyboardEvent) => { if (e.key === 'Escape') onClose(); }; + document.addEventListener('keydown', onKey); + const prevOverflow = document.body.style.overflow; + document.body.style.overflow = 'hidden'; + return () => { + document.removeEventListener('keydown', onKey); + document.body.style.overflow = prevOverflow; + }; + }, [open, onClose]); + + if (!open) return null; + const sizeClass = { + sm: 'max-w-sm', + md: 'max-w-lg', + lg: 'max-w-2xl', + xl: 'max-w-4xl', + }[size]; + + return ( +
+
e.stopPropagation()} + > +
+

{title}

+ +
+
+ {children} +
+ {footer && ( +
+ {footer} +
+ )} +
+
+ ); +} diff --git a/DorkNet.Server/admin-ui/src/components/PageHeader.tsx b/DorkNet.Server/admin-ui/src/components/PageHeader.tsx new file mode 100644 index 0000000..9b18b47 --- /dev/null +++ b/DorkNet.Server/admin-ui/src/components/PageHeader.tsx @@ -0,0 +1,19 @@ +interface Props { + title: string; + blurb?: string; + actions?: React.ReactNode; +} + +// Consistent page header: title + optional one-line blurb + a slot +// for primary actions (refresh, new, etc.) aligned to the right. +export function PageHeader({ title, blurb, actions }: Props) { + return ( +
+
+

{title}

+ {blurb &&

{blurb}

} +
+ {actions &&
{actions}
} +
+ ); +} diff --git a/DorkNet.Server/admin-ui/src/components/PlayerAvatar.tsx b/DorkNet.Server/admin-ui/src/components/PlayerAvatar.tsx new file mode 100644 index 0000000..57ac5e0 --- /dev/null +++ b/DorkNet.Server/admin-ui/src/components/PlayerAvatar.tsx @@ -0,0 +1,46 @@ +import { useState } from 'react'; +import { profileImageUrl } from '../lib/types'; + +// Round avatar tile that falls back to the player's display-name +// initial if there's no ProfileImageName or the request 404s. The +// img.* CDN signs every response, so we don't have to do anything +// special on this side — just point at the URL and let the browser +// render or fail to render. Size in pixels controls both the rendered +// box and the `?width=N` hint we send so the CDN can resize. +export function PlayerAvatar({ + name, + displayName, + size = 28, + className = '', +}: { + name?: string | null; + displayName?: string | null; + size?: number; + className?: string; +}) { + const [errored, setErrored] = useState(false); + const url = errored ? null : profileImageUrl(name, Math.max(size * 2, 64)); + const initial = (displayName ?? '?').charAt(0).toUpperCase(); + const dim = { width: size, height: size }; + + if (!url) { + return ( +
+ {initial} +
+ ); + } + return ( + setErrored(true)} + alt={displayName ? `${displayName}'s avatar` : 'player avatar'} + className={`shrink-0 rounded-full bg-ink-800 object-cover ${className}`} + loading="lazy" + /> + ); +} diff --git a/DorkNet.Server/admin-ui/src/components/RequireAuth.tsx b/DorkNet.Server/admin-ui/src/components/RequireAuth.tsx new file mode 100644 index 0000000..f039eba --- /dev/null +++ b/DorkNet.Server/admin-ui/src/components/RequireAuth.tsx @@ -0,0 +1,13 @@ +import { useSyncExternalStore } from 'react'; +import { Navigate, useLocation } from 'react-router-dom'; +import { getToken, subscribeAuth } from '../lib/auth'; + +// Gate every authenticated route. We re-render on any auth state +// change so a 401 from `api()` (which clears the token and pings +// listeners) bounces the user out of the dashboard mid-session. +export function RequireAuth({ children }: { children: React.ReactNode }) { + const token = useSyncExternalStore(subscribeAuth, getToken, () => null); + const location = useLocation(); + if (!token) return ; + return <>{children}; +} diff --git a/DorkNet.Server/admin-ui/src/components/Sidebar.tsx b/DorkNet.Server/admin-ui/src/components/Sidebar.tsx new file mode 100644 index 0000000..3292e9b --- /dev/null +++ b/DorkNet.Server/admin-ui/src/components/Sidebar.tsx @@ -0,0 +1,146 @@ +import { useEffect } from 'react'; +import { NavLink } from 'react-router-dom'; +import { + LayoutDashboard, Users, Building2, Ban, Flag, ScrollText, Activity, + Megaphone, Upload, ShoppingBag, Gift, KeyRound, Wrench, MessageSquare, Settings, X, +} from './Icons'; + +const groups: Array<{ title: string; items: Array<{ to: string; label: string; icon: React.ReactNode; new?: boolean }>}> = [ + { + title: 'Overview', + items: [ + { to: '/', label: 'Dashboard', icon: }, + ], + }, + { + title: 'People', + items: [ + { to: '/players', label: 'Players', icon: }, + { to: '/bans', label: 'Bans', icon: }, + { to: '/reports', label: 'Reports', icon: }, + { to: '/audit', label: 'Audit log', icon: }, + { to: '/logs', label: 'Player logs', icon: }, + ], + }, + { + title: 'Content', + items: [ + // Rooms is the unified view — clicking a row opens /rooms/:id + // which carries the old RR-Originals edit modal, live Photon + // instance list, recent visitors, ownership management, and + // per-room leaderboard channels in one tabbed page. + { to: '/rooms', label: 'Rooms', icon: }, + { to: '/import-room', label: 'Import room', icon: }, + { to: '/store', label: 'Store catalog', icon: }, + { to: '/community', label: 'Community board', icon: }, + { to: '/loading-tips', label: 'Loading screen tips', icon: }, + ], + }, + { + title: 'Tools', + items: [ + { to: '/broadcast', label: 'Broadcast', icon: }, + { to: '/gift', label: 'Gift items', icon: , new: true }, + { to: '/passwords', label: 'Reset passwords', icon: , new: true }, + { to: '/grants', label: 'Grant maker pen', icon: , new: true }, + { to: '/settings', label: 'Server settings', icon: , new: true }, + ], + }, +]; + +// Two-mode rendering: +// * Desktop (md+): always-visible static rail to the left of
. +// * Mobile: hidden by default; opens as a slide-in drawer with a +// backdrop scrim, controlled by `open` from . Tapping any +// nav link calls `onNavigate()` so the drawer closes automatically +// after the user picks a destination. Escape also closes. +export function Sidebar({ open, onClose, onNavigate }: { open: boolean; onClose: () => void; onNavigate: () => void }) { + // Lock body scroll while the mobile drawer is up so the page behind + // doesn't scroll through the backdrop. Skip on desktop where `open` + // is irrelevant (the sidebar is always there). + useEffect(() => { + if (!open) return; + const onKey = (e: KeyboardEvent) => { if (e.key === 'Escape') onClose(); }; + document.addEventListener('keydown', onKey); + const prev = document.body.style.overflow; + document.body.style.overflow = 'hidden'; + return () => { + document.removeEventListener('keydown', onKey); + document.body.style.overflow = prev; + }; + }, [open, onClose]); + + const inner = ( + <> +
+
+
D
+
+
DorkNet
+
admin
+
+
+ +
+ + + ); + + return ( + <> + {/* Mobile drawer + backdrop */} +
+
+
+ + {/* Desktop static rail */} + + + ); +} diff --git a/DorkNet.Server/admin-ui/src/components/Toast.tsx b/DorkNet.Server/admin-ui/src/components/Toast.tsx new file mode 100644 index 0000000..241ffd6 --- /dev/null +++ b/DorkNet.Server/admin-ui/src/components/Toast.tsx @@ -0,0 +1,50 @@ +import { createContext, useCallback, useContext, useEffect, useState } from 'react'; + +type Kind = 'info' | 'success' | 'error'; +interface ToastMsg { id: number; kind: Kind; text: string } + +interface Ctx { push: (text: string, kind?: Kind) => void } +const ToastCtx = createContext(null); + +export function useToast(): Ctx { + const ctx = useContext(ToastCtx); + if (!ctx) throw new Error('useToast must be used inside '); + return ctx; +} + +export function ToastProvider({ children }: { children: React.ReactNode }) { + const [items, setItems] = useState([]); + const push = useCallback((text: string, kind: Kind = 'info') => { + const id = Date.now() + Math.random(); + setItems(prev => [...prev, { id, kind, text }]); + setTimeout(() => setItems(prev => prev.filter(t => t.id !== id)), 4000); + }, []); + + return ( + + {children} +
+ {items.map(t => ( + + ))} +
+
+ ); +} + +function ToastItem({ msg }: { msg: ToastMsg }) { + const [open, setOpen] = useState(false); + useEffect(() => { requestAnimationFrame(() => setOpen(true)); }, []); + const colors = + msg.kind === 'success' ? 'bg-success/15 text-success border-success/30' + : msg.kind === 'error' ? 'bg-danger/15 text-danger border-danger/30' + : 'bg-ink-800 text-ink-100 border-ink-700'; + return ( +
+ {msg.text} +
+ ); +} diff --git a/DorkNet.Server/admin-ui/src/index.css b/DorkNet.Server/admin-ui/src/index.css new file mode 100644 index 0000000..0fe4169 --- /dev/null +++ b/DorkNet.Server/admin-ui/src/index.css @@ -0,0 +1,50 @@ +@tailwind base; +@tailwind components; +@tailwind utilities; + +@layer base { + html, body, #root { height: 100%; } + body { + font-family: 'Inter', ui-sans-serif, system-ui, -apple-system, 'Segoe UI', Roboto, sans-serif; + -webkit-font-smoothing: antialiased; + } + ::selection { background-color: theme('colors.brand.500'); color: white; } +} + +@layer components { + .card { + @apply rounded-xl border border-ink-800 bg-ink-900 shadow-card; + } + .btn { + @apply inline-flex items-center justify-center gap-2 rounded-lg px-3.5 py-2 text-sm font-medium + transition-colors focus:outline-none focus:ring-2 focus:ring-brand-500/40 focus:ring-offset-2 focus:ring-offset-ink-950 + disabled:cursor-not-allowed disabled:opacity-50; + } + .btn-primary { @apply btn bg-brand-500 text-white hover:bg-brand-400 active:bg-brand-600; } + .btn-secondary{ @apply btn bg-ink-800 text-ink-100 hover:bg-ink-700 border border-ink-700; } + .btn-danger { @apply btn bg-danger text-white hover:bg-danger/90; } + .btn-ghost { @apply btn bg-transparent text-ink-200 hover:bg-ink-800; } + .input { + @apply w-full rounded-lg border border-ink-700 bg-ink-900 px-3 py-2 text-sm text-ink-100 + placeholder:text-ink-400 focus:border-brand-400 focus:outline-none focus:ring-2 focus:ring-brand-500/30; + } + .label { + @apply text-xs font-medium uppercase tracking-wide text-ink-300; + } + .badge { + @apply inline-flex items-center gap-1 rounded-md px-1.5 py-0.5 text-[10px] font-semibold uppercase tracking-wider; + } + .badge-online { @apply badge bg-success/15 text-success; } + .badge-banned { @apply badge bg-danger/15 text-danger; } + .badge-admin { @apply badge bg-brand-500/15 text-brand-200; } + .badge-junior { @apply badge bg-warn/15 text-warn; } + .badge-neutral { @apply badge bg-ink-800 text-ink-200; } + + .table-row-hover { @apply hover:bg-ink-800/60 transition-colors; } + + /* Wrap any wide data table in
so it + scrolls horizontally on narrow viewports instead of forcing the + whole layout wider than the screen. Pair with `whitespace-nowrap` + on cells that should never wrap (usernames, timestamps). */ + .table-scroll { @apply overflow-x-auto -mx-px; } +} diff --git a/DorkNet.Server/admin-ui/src/lib/api.ts b/DorkNet.Server/admin-ui/src/lib/api.ts new file mode 100644 index 0000000..899bbd0 --- /dev/null +++ b/DorkNet.Server/admin-ui/src/lib/api.ts @@ -0,0 +1,323 @@ +import { clearSession, getToken, notifyAuthChange } from './auth'; + +const API_BASE = '/api/admin/v1'; + +export class ApiError extends Error { + constructor(public status: number, message: string, public body?: unknown) { + super(message); + this.name = 'ApiError'; + } +} + +export interface ApiOptions extends Omit { + body?: unknown; + timeoutMs?: number; + // For multipart/form-data uploads — pass the FormData and skip JSON encoding. + formData?: FormData; +} + +export async function api(path: string, opts: ApiOptions = {}): Promise { + const headers = new Headers(opts.headers ?? {}); + const token = getToken(); + if (token) headers.set('Authorization', `Bearer ${token}`); + + let body: BodyInit | undefined; + if (opts.formData) { + body = opts.formData; + } else if (opts.body !== undefined && opts.body !== null) { + if (typeof opts.body === 'string') { + body = opts.body; + } else { + headers.set('Content-Type', 'application/json'); + body = JSON.stringify(opts.body); + } + } + + const controller = new AbortController(); + const timer = setTimeout(() => controller.abort(), opts.timeoutMs ?? 15000); + let res: Response; + try { + res = await fetch(API_BASE + path, { + ...opts, + headers, + body, + signal: controller.signal, + }); + } catch (err) { + if ((err as Error).name === 'AbortError') { + throw new ApiError(0, `request timed out (${opts.timeoutMs ?? 15000}ms)`); + } + throw err; + } finally { + clearTimeout(timer); + } + + if (res.status === 401) { + clearSession(); + notifyAuthChange(); + throw new ApiError(401, 'Unauthorized — sign in again'); + } + if (!res.ok) { + let message = `${res.status} ${res.statusText}`; + let bodyJson: unknown; + try { + bodyJson = await res.json(); + const err = bodyJson as { error?: string; error_description?: string; message?: string }; + if (err?.error) message = err.error + (err.error_description ? ` (${err.error_description})` : ''); + else if (err?.message) message = err.message; + } catch { /* leave message as status */ } + throw new ApiError(res.status, message, bodyJson); + } + + if (res.status === 204) return undefined as T; + const ct = res.headers.get('content-type') ?? ''; + if (ct.includes('application/json')) { + const raw = await res.json(); + return camelizeKeys(raw) as T; + } + return (await res.text()) as T; +} + +// Server responses are PascalCase because Program.cs sets +// PropertyNamingPolicy=null project-wide (the old Rec Room watch +// depends on exact wire keys). The admin SPA is the only place we +// control both ends, so we camelCase on ingress so TS code stays +// idiomatic. Outbound POST bodies don't need the inverse — ASP.NET +// Core's JSON binder has PropertyNameCaseInsensitive=true by default, +// so camelCase request fields bind to PascalCase record properties +// fine. +function camelizeKey(s: string): string { + if (s.length === 0) return s; + // Leave already-lower (OAuth-style snake_case keys like access_token) + // and single-character keys alone — only flip the leading capital. + return s[0] === s[0].toLowerCase() ? s : s[0].toLowerCase() + s.slice(1); +} + +function camelizeKeys(input: unknown): unknown { + if (Array.isArray(input)) return input.map(camelizeKeys); + if (input !== null && typeof input === 'object') { + const out: Record = {}; + for (const [k, v] of Object.entries(input as Record)) { + out[camelizeKey(k)] = camelizeKeys(v); + } + return out; + } + return input; +} + +// Convenience shorthands so callers don't have to spell out the options object. +export const get = (path: string) => api(path); +export const post = (path: string, body?: unknown) => api(path, { method: 'POST', body }); +export const put = (path: string, body?: unknown) => api(path, { method: 'PUT', body }); +export const del = (path: string, body?: unknown) => api(path, { method: 'DELETE', body }); + +// Upload progress callback fires repeatedly during the request body +// send (request bytes), then once with {total, loaded: total} just +// before the server starts processing. fetch() doesn't expose request +// progress at all, so we fall back to XHR for this one helper. +export interface UploadProgress { loaded: number; total: number; percent: number; phase?: string } + +// Chunk size for chunked uploads. Cloudflare's free/pro plans cap +// request bodies at 100 MB end-to-end (Tunnel inherits the same +// limit), so any chunk we send has to stay below that. 50 MB leaves +// generous headroom for the multipart envelope, TLS framing, and +// HTTP/2 stream buffers. +export const ZIP_CHUNK_BYTES = 50 * 1024 * 1024; + +export async function uploadWithProgress( + path: string, + formData: FormData, + onProgress?: (p: UploadProgress) => void, + timeoutMs = 30 * 60_000, +): Promise { + const token = getToken(); + return new Promise((resolve, reject) => { + const xhr = new XMLHttpRequest(); + xhr.open('POST', '/api/admin/v1' + path, true); + if (token) xhr.setRequestHeader('Authorization', `Bearer ${token}`); + xhr.timeout = timeoutMs; + xhr.responseType = 'text'; + + xhr.upload.onprogress = (e) => { + if (!onProgress) return; + const total = e.lengthComputable ? e.total : 0; + const percent = total > 0 ? Math.round((e.loaded / total) * 100) : 0; + onProgress({ loaded: e.loaded, total, percent }); + }; + + xhr.onload = () => { + if (xhr.status === 401) { + clearSession(); + notifyAuthChange(); + reject(new ApiError(401, 'Unauthorized — sign in again')); + return; + } + if (xhr.status < 200 || xhr.status >= 300) { + let msg = `${xhr.status} ${xhr.statusText}`; + try { + const body = JSON.parse(xhr.responseText) as { error?: string; message?: string }; + if (body?.error) msg = body.error; + else if (body?.message) msg = body.message; + } catch { /* keep status text */ } + reject(new ApiError(xhr.status, msg)); + return; + } + // Parse response with the same camelize behavior as fetch path. + try { + const ct = xhr.getResponseHeader('content-type') ?? ''; + if (ct.includes('application/json') && xhr.responseText) { + const raw = JSON.parse(xhr.responseText); + resolve(camelizeKeys(raw) as T); + } else { + resolve(xhr.responseText as T); + } + } catch (e) { + reject(e); + } + }; + + xhr.onerror = () => reject(new ApiError(0, 'network error')); + xhr.ontimeout = () => reject(new ApiError(0, `request timed out (${timeoutMs}ms)`)); + + xhr.send(formData); + }); +} + +// Chunked upload for the zip importer. Splits a File into +// ZIP_CHUNK_BYTES-sized pieces with Blob.slice(), POSTs each piece's +// raw bytes to /zip-upload-chunk?offset=N (no multipart wrapper — +// keeps the bytes-on-wire identical to the in-memory slice), then +// calls /zip-upload-finalize once everything's uploaded. Progress +// callback fires per chunk-completion as well as during each chunk's +// upload phase, so the UI bar advances smoothly through a multi-GB +// archive instead of stepping in 50 MB jumps. +export interface ZipImportOptions { + /// Top-level archive folders (e.g. "Rooms/Room_3163127_YarrHarrHeist_…" + /// or "Inventions/Invention_…") to actually import. Leave undefined + /// or empty to import everything in the archive — the SPA's preview + /// builds these lists from the room/invention checkboxes. + selectedRoomFolders?: string[]; + selectedInventionFolders?: string[]; + /// Round-trip the imported .room blobs through the 2020 protobuf + /// schema before persisting. Defaults off because the re-encode + /// currently crashes the watch on load; the SPA exposes the toggle + /// in the importer's options panel for cases where the admin wants + /// to opt back in. + normalizeBlobs?: boolean; +} + +export async function uploadZipInChunks( + file: File, + creatorPlayerId: number, + onProgress: (p: UploadProgress) => void, + timeoutMs = 30 * 60_000, + options: ZipImportOptions = {}, +): Promise { + // 1. Init — server allocates a temp file and hands back an uploadId. + const init = await api<{ uploadId: string }>('/rooms/zip-upload-init', { + method: 'POST', + body: { FileName: file.name, TotalBytes: file.size }, + }); + const uploadId = init.uploadId; + + try { + let uploadedSoFar = 0; + for (let offset = 0; offset < file.size; offset += ZIP_CHUNK_BYTES) { + const end = Math.min(offset + ZIP_CHUNK_BYTES, file.size); + const chunk = file.slice(offset, end); + const chunkStart = offset; + const chunkSize = end - offset; + + await new Promise((resolve, reject) => { + const xhr = new XMLHttpRequest(); + const token = getToken(); + xhr.open('POST', `/api/admin/v1/rooms/zip-upload-chunk/${uploadId}?offset=${chunkStart}`, true); + if (token) xhr.setRequestHeader('Authorization', `Bearer ${token}`); + xhr.setRequestHeader('Content-Type', 'application/octet-stream'); + xhr.timeout = timeoutMs; + xhr.responseType = 'text'; + + xhr.upload.onprogress = (e) => { + const loaded = uploadedSoFar + (e.lengthComputable ? e.loaded : 0); + const percent = file.size > 0 ? Math.round((loaded / file.size) * 100) : 0; + onProgress({ loaded, total: file.size, percent }); + }; + xhr.onload = () => { + if (xhr.status === 401) { + clearSession(); notifyAuthChange(); + reject(new ApiError(401, 'Unauthorized — sign in again')); + return; + } + if (xhr.status < 200 || xhr.status >= 300) { + let msg = `${xhr.status} ${xhr.statusText}`; + try { + const body = JSON.parse(xhr.responseText) as { error?: string; message?: string }; + if (body?.error) msg = body.error; + else if (body?.message) msg = body.message; + } catch { /* keep status */ } + reject(new ApiError(xhr.status, msg)); + return; + } + uploadedSoFar += chunkSize; + onProgress({ loaded: uploadedSoFar, total: file.size, percent: Math.round((uploadedSoFar / file.size) * 100) }); + resolve(); + }; + xhr.onerror = () => reject(new ApiError(0, `chunk @${chunkStart} network error`)); + xhr.ontimeout = () => reject(new ApiError(0, `chunk @${chunkStart} timed out`)); + xhr.send(chunk); + }); + } + + // 2. Finalize — server kicks off the import in a background task + // and returns 202 immediately with a jobId. Multi-GB archives + // take several minutes to process and used to 524 through + // Cloudflare on the synchronous finalize path; the new flow + // lets us poll for progress without holding the HTTP connection + // open the entire time. + const finalize = await api<{ jobId: string; state: string }>( + `/rooms/zip-upload-finalize/${uploadId}`, + { + method: 'POST', + body: { + CreatorPlayerId: creatorPlayerId, + SelectedRoomFolders: options.selectedRoomFolders ?? null, + SelectedInventionFolders: options.selectedInventionFolders ?? null, + NormalizeBlobs: options.normalizeBlobs ?? false, + }, + timeoutMs: 60_000, + }); + + // 3. Poll status — every 3 seconds is a comfortable cadence: + // fast enough to feel responsive in the UI, slow enough that + // a 30-minute import doesn't generate hundreds of requests. + // onProgress doubles as the "phase text" channel during this + // stage by setting percent=100 and replaying the server's + // phase string through the `phase` property. + const jobId = finalize.jobId; + const pollIntervalMs = 3_000; + const pollDeadline = Date.now() + timeoutMs; + while (true) { + if (Date.now() > pollDeadline) + throw new ApiError(0, `import job ${jobId} did not complete within ${Math.round(timeoutMs / 1000)}s`); + await new Promise((r) => setTimeout(r, pollIntervalMs)); + const status = await api<{ + state: string; + phase: string | null; + result: T | null; + error: string | null; + elapsedSeconds: number; + }>(`/rooms/zip-import-status/${jobId}`, { method: 'GET', timeoutMs: 30_000 }); + // Surface server-side phase to the UI via onProgress. + onProgress({ loaded: file.size, total: file.size, percent: 100, phase: status.phase ?? undefined }); + if (status.state === 'done') return status.result as T; + if (status.state === 'failed') + throw new ApiError(0, status.error ?? `import job ${jobId} failed`); + // else: still running, loop. + } + } catch (err) { + // Best-effort cleanup of the temp file on the server. The session + // also auto-expires after 2 hours, so a missed abort isn't fatal. + try { await api(`/rooms/zip-upload-abort/${uploadId}`, { method: 'DELETE' }); } catch { /* swallow */ } + throw err; + } +} diff --git a/DorkNet.Server/admin-ui/src/lib/auth.ts b/DorkNet.Server/admin-ui/src/lib/auth.ts new file mode 100644 index 0000000..3ee6671 --- /dev/null +++ b/DorkNet.Server/admin-ui/src/lib/auth.ts @@ -0,0 +1,58 @@ +// Token + identity storage. The server's same-origin AdminLoginController +// hands back an access_token plus a small identity blob; we stash both +// in localStorage and re-hydrate on page load so a refresh doesn't kick +// the admin back to the login screen. + +const TOKEN_KEY = 'dorknet.admin.token'; +const ME_KEY = 'dorknet.admin.me'; + +export interface AdminMe { + id: number; + username: string; + displayName: string; +} + +export function getToken(): string | null { + return localStorage.getItem(TOKEN_KEY); +} + +// getMe is read on every render by useSyncExternalStore. Re-parsing +// localStorage each call would return a fresh object reference even +// when the underlying data hasn't changed, which React detects as a +// state change and re-renders forever (React error #185). Cache by +// the raw string so consecutive identical reads return the same +// AdminMe instance and the snapshot stays stable. +let cachedMeRaw: string | null | undefined; +let cachedMe: AdminMe | null = null; +export function getMe(): AdminMe | null { + const raw = localStorage.getItem(ME_KEY); + if (raw === cachedMeRaw) return cachedMe; + cachedMeRaw = raw; + if (!raw) { cachedMe = null; return null; } + try { cachedMe = JSON.parse(raw) as AdminMe; } + catch { cachedMe = null; } + return cachedMe; +} + +export function setSession(token: string, me: AdminMe): void { + localStorage.setItem(TOKEN_KEY, token); + localStorage.setItem(ME_KEY, JSON.stringify(me)); +} + +export function clearSession(): void { + localStorage.removeItem(TOKEN_KEY); + localStorage.removeItem(ME_KEY); +} + +// Simple subscription so components can react to login/logout without +// pulling in a state lib. Anything that calls getToken() in render and +// wants live updates can useSyncExternalStore against this. +type Listener = () => void; +const listeners = new Set(); +export function subscribeAuth(fn: Listener): () => void { + listeners.add(fn); + return () => { listeners.delete(fn); }; +} +export function notifyAuthChange(): void { + listeners.forEach(fn => fn()); +} diff --git a/DorkNet.Server/admin-ui/src/lib/browser.ts b/DorkNet.Server/admin-ui/src/lib/browser.ts new file mode 100644 index 0000000..e1b544e --- /dev/null +++ b/DorkNet.Server/admin-ui/src/lib/browser.ts @@ -0,0 +1,27 @@ +// Chromium gate. The zip importer uploads multi-hundred-megabyte +// FormData bodies and uses File / Blob slicing patterns that are +// effectively only stable on Chromium right now: +// * Firefox's xhr.upload.progress + multipart streaming has buffer +// issues over ~500 MB. +// * Safari has a long-standing bug where the FormData clone for +// large bodies hits a memory ceiling and aborts mid-upload. +// +// Easier to gate the importer than to chase per-browser workarounds. +// Other admin pages don't have this constraint, so the warning only +// shows on the import flow. + +interface NavigatorWithUaData extends Navigator { + userAgentData?: { brands: Array<{ brand: string; version: string }> }; +} + +export function isChromium(): boolean { + if (typeof navigator === 'undefined') return true; // SSR / tests + // userAgentData is currently only implemented in Chromium-based + // browsers — Firefox/Safari haven't shipped it. If it's present, + // we're on Chromium. + if ((navigator as NavigatorWithUaData).userAgentData) return true; + // Fallback UA sniff for Chromium-derived browsers that haven't + // exposed userAgentData yet (e.g. older Edge/Brave builds). + const ua = navigator.userAgent; + return /Chrome\/|Chromium\/|Edg\//.test(ua) && !/Firefox\/|FxiOS\//.test(ua); +} diff --git a/DorkNet.Server/admin-ui/src/lib/format.ts b/DorkNet.Server/admin-ui/src/lib/format.ts new file mode 100644 index 0000000..abf4351 --- /dev/null +++ b/DorkNet.Server/admin-ui/src/lib/format.ts @@ -0,0 +1,59 @@ +// Display helpers used across pages. Kept here so we render dates, +// durations, and numeric quantities the same way everywhere — a player +// table and a player detail page should not disagree about how to +// format "5 days ago". + +export function relativeTime(iso: string | null | undefined): string { + if (!iso) return '—'; + const t = new Date(iso).getTime(); + if (!Number.isFinite(t)) return '—'; + const diffSec = Math.round((Date.now() - t) / 1000); + const abs = Math.abs(diffSec); + if (abs < 45) return diffSec >= 0 ? 'just now' : 'in a moment'; + const units: Array<[number, string]> = [ + [60, 'sec'], + [60, 'min'], + [24, 'hr'], + [7, 'day'], + [4.345, 'wk'], + [12, 'mo'], + [Infinity, 'yr'], + ]; + let v = abs; + let label = 'sec'; + for (let i = 0; i < units.length; i++) { + const [step, name] = units[i]; + if (v < step) { label = name; break; } + v = v / step; + label = units[i + 1]?.[1] ?? 'yr'; + } + const rounded = v >= 10 ? Math.round(v) : Math.round(v * 10) / 10; + return diffSec >= 0 ? `${rounded} ${label} ago` : `in ${rounded} ${label}`; +} + +export function absoluteTime(iso: string | null | undefined): string { + if (!iso) return '—'; + const d = new Date(iso); + if (!Number.isFinite(d.getTime())) return '—'; + return d.toLocaleString(); +} + +export function num(n: number | null | undefined): string { + if (n === null || n === undefined || !Number.isFinite(n)) return '—'; + return n.toLocaleString(); +} + +export function clip(s: string | null | undefined, max = 80): string { + if (!s) return ''; + return s.length <= max ? s : s.slice(0, max - 1) + '…'; +} + +// Currency type IDs follow the canonical Rec Room enum: +// 1 = Tokens, 2 = Coins (RecRoom currency). +export function currencyName(type: number): string { + switch (type) { + case 1: return 'Tokens'; + case 2: return 'Coins'; + default: return `Currency #${type}`; + } +} diff --git a/DorkNet.Server/admin-ui/src/lib/types.ts b/DorkNet.Server/admin-ui/src/lib/types.ts new file mode 100644 index 0000000..92d003d --- /dev/null +++ b/DorkNet.Server/admin-ui/src/lib/types.ts @@ -0,0 +1,241 @@ +// Wire shapes returned by AdminController. Mirrors the anonymous-object +// projections in DorkNet.Server/Controllers/Admin/AdminController.cs. +// Loosely typed where the server returns dynamic structures; tightened +// to specific fields where we render columns. + +export interface Player { + id: number; + username: string; + displayName: string; + email: string | null; + isAdmin: boolean; + isDeveloper: boolean; + isCommunityTeam: boolean; + isVerified: boolean; + isJunior: boolean; + bannedUntil: string | null; + lastIp: string | null; + lastSeenAt: string | null; + createdAt: string; + level: number; + xp: number; + profileImageName: string | null; + online: boolean; +} + +export interface PlayerDetail extends Player { + bio: string | null; + balances: Array<{ currencyType: number; balance: number }>; + avatar: { + outfitSelections: string | null; + hairColor: string | null; + skinColor: string | null; + faceFeatures: string | null; + } | null; +} + +// img.* CDN URL for a player's stored profile image — same path the +// 2020 watch uses (cropSquare=1 produces a face-zoomed square, sig +// matches the host's signing key id). +export function profileImageUrl(name: string | null | undefined, width = 96): string | null { + if (!name) return null; + const apex = typeof window !== 'undefined' && window.location.host.endsWith('localhost') ? 'localhost' : 'rec.net'; + return `https://img.${apex}/${encodeURIComponent(name)}?width=${width}&cropSquare=1&sig=p1`; +} + +export interface Room { + id: number; + name: string; + isAGRoom: boolean; + isDormRoom: boolean; + creatorPlayerId: number; + blobCount: number; +} + +// RoomRole.Role enum (server-side RoomRoleEntity.Role): +// 0 = CoOwner, 1 = Moderator, 2 = Host. +// Accepted=true ⇒ surfaces in CoOwners / Moderators / Hosts arrays. +// Accepted=false ⇒ surfaces in the Invited* counterparts (pending grant). +export interface RoomRoleGrant { + id: number; + playerId: number; + role: 0 | 1 | 2; + accepted: boolean; + grantedByPlayerId: number | null; + grantedAt: string; + player: { + id: number; + username: string; + displayName: string; + profileImageName: string | null; + }; +} + +// Per-room live Photon instance — GET /api/admin/v1/rooms/{id}/instances. +// masterPlayerId is a best-effort proxy for the Photon master client +// (server doesn't see ActorNumbers; we use dorm-creator-if-present +// else lowest-pid). The matching participant has isMaster=true so the +// UI can tag a row without recomputing. +export interface RoomInstance { + roomInstanceId: number; + roomId: number; + subRoomId: number; + roomName: string; + photonRoomId: string; + photonRegionId: string; + location: string; + maxCapacity: number; + isPrivate: boolean; + masterPlayerId: number; + participants: Array<{ + id: number; + username: string; + displayName: string; + isMaster: boolean; + }>; +} + +// Full per-room detail returned by GET /api/admin/v1/rooms/{id}. The +// admin SPA's per-room detail page consumes this in one round trip. +export interface RoomDetail { + id: number; + name: string; + description: string; + imageName: string; + state: number; + accessibility: number; + isAGRoom: boolean; + isDormRoom: boolean; + cloningAllowed: boolean; + supportsLevelVoting: boolean; + supportsVRLow: boolean; + supportsMobile: boolean; + supportsScreens: boolean; + supportsWalkVR: boolean; + supportsTeleportVR: boolean; + allowsJuniors: boolean; + disableMicAutoMute: boolean; + roomWarningMask: number; + customRoomWarning: string; + tagsCsv: string; + cheerCount: number; + favoriteCount: number; + visitCount: number; + visitorCount: number; + hotScore: number; + locationReplicationId: string; + currentDataBlobName: string; + createdAt: string; + updatedAt: string; + sceneCount: number; + blobCount: number; + owner: { + id: number; + username: string; + displayName: string; + profileImageName: string | null; + }; + roles: RoomRoleGrant[]; +} + +export interface IpBan { + id: number; + cidr: string; + reason: string; + bannedByAdminId: number; + bannedAt: string; + until: string | null; +} + +export interface AuditEntry { + id: number; + adminPlayerId: number; + action: string; + targetType: string; + targetId: number; + reason: string; + timestamp: string; +} + +export interface Report { + id: number; + reporterPlayerId: number; + targetPlayerId: number; + reason: string; + context: string; + createdAt: string; + resolvedAt: string | null; + resolverAdminId: number | null; + resolutionNote: string | null; +} + +export interface Stats { + players: { total: number; onlineNow: number; bannedNow: number }; + rooms: { + total: number; + topByVisits: Array<{ id: number; name: string; visitCount: number; visitorCount: number; cheerCount: number }>; + }; + inventions: number; + moderation: { openReports: number; activeIpBans: number }; + recentJoins: Array<{ id: number; username: string; createdAt: string }>; + serverTime: string; +} + +export interface StoreItem { + id: number; + slug: string; + displayName: string; + description: string; + category: string; + imageName: string; + currencyType: number; + price: number; + isActive: boolean; + isLimitedTime: boolean; + availableUntil: string | null; + storefront: string; + createdAt: string; + updatedAt: string; +} + +export interface StorefrontDefinition { + key: string; + storefrontType: number | null; + displayName: string; + scope: string; +} + +export interface Instance { + roomInstanceId: number; + roomId: number; + subRoomId: number; + roomName: string; + photonRoomId: string; + photonRegionId: string; + location: string; + maxCapacity: number; + isPrivate: boolean; + participants: Array<{ id: number; username: string; displayName: string }>; +} + +export interface PlayerLogEntry { + timestamp: string; + method: string; + host: string; + path: string; + query: string; + status: number; + elapsedMs: number; +} + +// Mirrors DorkNet.Server.Services.CommunityBoardState (and the +// nested DTOs in CommunityBoardService.cs). The 2020 watch decodes +// the same shape from /api/communityboard/v1/current — admins +// edit it here via /api/admin/v1/communityboard. +export interface CommunityBoardState { + currentAnnouncement: { message: string; moreInfoUrl: string } | null; + featuredPlayer: { id: number; titleOverride: string; urlOverride: string } | null; + featuredRoomGroup: { name: string; featuredRooms: number[] } | null; + instagramImages: Array<{ imageName: string; imageUrl: string }>; + videos: Array<{ blobName: string; title: string; description: string; thumbnailBlobName: string; sourceUrl: string }>; +} diff --git a/DorkNet.Server/admin-ui/src/lib/useApi.ts b/DorkNet.Server/admin-ui/src/lib/useApi.ts new file mode 100644 index 0000000..5929c90 --- /dev/null +++ b/DorkNet.Server/admin-ui/src/lib/useApi.ts @@ -0,0 +1,47 @@ +import { useCallback, useEffect, useRef, useState } from 'react'; +import { api, ApiError, type ApiOptions } from './api'; + +interface State { + data: T | null; + loading: boolean; + error: string | null; +} + +// Read-side hook for "fetch on mount, expose refresh + error state". +// Not a replacement for TanStack Query — we don't need its cache / +// dedupe / focus-refetch behavior for an admin tool used by 1-2 people. +// But every page does the same useState/useEffect/abort dance, so this +// collapses it to one line. +export function useApi(path: string, opts: ApiOptions & { manual?: boolean } = {}) { + const [state, setState] = useState>({ data: null, loading: !opts.manual, error: null }); + const abortRef = useRef(null); + const { manual, ...rest } = opts; + // Stabilise opts across renders by capturing in a ref the caller can ignore. + // Anything that varies between renders (e.g. inline objects) is fine because + // we re-run on `path` change only. + const optsRef = useRef(rest); + optsRef.current = rest; + + const refresh = useCallback(async () => { + abortRef.current?.abort(); + const ctrl = new AbortController(); + abortRef.current = ctrl; + setState(s => ({ ...s, loading: true, error: null })); + try { + const data = await api(path, { ...optsRef.current, signal: ctrl.signal }); + if (!ctrl.signal.aborted) setState({ data, loading: false, error: null }); + } catch (e) { + if (ctrl.signal.aborted) return; + const msg = e instanceof ApiError ? e.message : (e as Error).message; + setState({ data: null, loading: false, error: msg }); + } + }, [path]); + + useEffect(() => { + if (manual) return; + refresh(); + return () => { abortRef.current?.abort(); }; + }, [refresh, manual]); + + return { ...state, refresh }; +} diff --git a/DorkNet.Server/admin-ui/src/main.tsx b/DorkNet.Server/admin-ui/src/main.tsx new file mode 100644 index 0000000..497c11f --- /dev/null +++ b/DorkNet.Server/admin-ui/src/main.tsx @@ -0,0 +1,13 @@ +import React from 'react'; +import { createRoot } from 'react-dom/client'; +import { BrowserRouter } from 'react-router-dom'; +import App from './App'; +import './index.css'; + +createRoot(document.getElementById('root')!).render( + + + + + , +); diff --git a/DorkNet.Server/admin-ui/src/pages/Audit.tsx b/DorkNet.Server/admin-ui/src/pages/Audit.tsx new file mode 100644 index 0000000..52083d9 --- /dev/null +++ b/DorkNet.Server/admin-ui/src/pages/Audit.tsx @@ -0,0 +1,68 @@ +import { useState } from 'react'; +import type { AuditEntry } from '../lib/types'; +import { useApi } from '../lib/useApi'; +import { PageHeader } from '../components/PageHeader'; +import { Empty } from '../components/Empty'; +import { absoluteTime, clip, relativeTime } from '../lib/format'; +import { RefreshCw } from '../components/Icons'; + +export function Audit() { + const [take, setTake] = useState(200); + const { data, loading, error, refresh } = useApi(`/audit?take=${take}`); + + return ( +
+ + + + } + /> + + {error &&
{error}
} + +
+ {data && data.length === 0 && } + {data && data.length > 0 && ( +
+ + + + + + + + + + + {data.map(e => ( + + + + + + + + ))} + +
WhenAdminActionTargetReason
+ {relativeTime(e.timestamp)} + #{e.adminPlayerId} + {e.action} + + {e.targetType} + {e.targetId > 0 && <> · #{e.targetId}} + {clip(e.reason, 100)}
+ )} +
+
+ ); +} diff --git a/DorkNet.Server/admin-ui/src/pages/Bans.tsx b/DorkNet.Server/admin-ui/src/pages/Bans.tsx new file mode 100644 index 0000000..7705daa --- /dev/null +++ b/DorkNet.Server/admin-ui/src/pages/Bans.tsx @@ -0,0 +1,190 @@ +import { useState } from 'react'; +import { api } from '../lib/api'; +import type { IpBan, Player } from '../lib/types'; +import { useApi } from '../lib/useApi'; +import { PageHeader } from '../components/PageHeader'; +import { useToast } from '../components/Toast'; +import { Empty } from '../components/Empty'; +import { absoluteTime, relativeTime } from '../lib/format'; +import { RefreshCw, Trash } from '../components/Icons'; + +export function Bans() { + const [tab, setTab] = useState<'players' | 'ip'>('players'); + return ( +
+ +
+ {(['players', 'ip'] as const).map(t => ( + + ))} +
+ {tab === 'players' && } + {tab === 'ip' && } +
+ ); +} + +function PlayerBans() { + // Pull a generous page of players and filter client-side for currently-banned. + // For a small private server this is fine; if we ever scale we can add a + // dedicated /players/banned endpoint. + const { data, loading, error, refresh } = useApi('/players?take=200'); + const now = Date.now(); + const banned = (data ?? []).filter(p => p.bannedUntil && new Date(p.bannedUntil).getTime() > now); + const toast = useToast(); + + const unban = async (id: number) => { + try { + await api(`/players/${id}/unban`, { method: 'POST', body: { Reason: 'admin-unban' } }); + toast.push('Unbanned', 'success'); + refresh(); + } catch (e) { + toast.push((e as Error).message, 'error'); + } + }; + + return ( +
+
+
{banned.length} active player ban{banned.length === 1 ? '' : 's'}
+ +
+ {error &&
{error}
} + {!error && banned.length === 0 && } + {banned.length > 0 && ( +
+ + + + + + + + + {banned.map(p => ( + + + + + + + ))} + +
PlayerExpiresLast IP +
+
{p.displayName || p.username}
+
@{p.username} · #{p.id}
+
+ {relativeTime(p.bannedUntil)} ({absoluteTime(p.bannedUntil)}) + {p.lastIp ?? '—'} + +
+ )} +
+ ); +} + +function IpBans() { + const { data, loading, error, refresh } = useApi('/ipbans'); + const toast = useToast(); + const [cidr, setCidr] = useState(''); + const [reason, setReason] = useState(''); + const [days, setDays] = useState(''); + + const add = async (e: React.FormEvent) => { + e.preventDefault(); + if (!cidr.trim()) return; + try { + await api('/ipbans', { method: 'POST', body: { Cidr: cidr.trim(), Reason: reason, DurationDays: days || null } }); + setCidr(''); setReason(''); setDays(''); + toast.push('IP ban added', 'success'); + refresh(); + } catch (err) { + toast.push((err as Error).message, 'error'); + } + }; + + const remove = async (id: number) => { + if (!confirm('Remove this IP ban?')) return; + try { + await api(`/ipbans/${id}`, { method: 'DELETE' }); + toast.push('IP ban removed', 'success'); + refresh(); + } catch (err) { + toast.push((err as Error).message, 'error'); + } + }; + + const active = (b: IpBan) => !b.until || new Date(b.until).getTime() > Date.now(); + + return ( +
+
+ setCidr(e.target.value)} placeholder="1.2.3.4 or 10.0.0.0/24" required className="input" /> + setReason(e.target.value)} placeholder="Reason (logged)" className="input" /> + setDays(e.target.value === '' ? '' : parseInt(e.target.value))} + placeholder="days (∞)" + className="input" + /> + +
+ +
+
+
{data ? `${data.length} total · ${data.filter(active).length} active` : ''}
+ +
+ {error &&
{error}
} + {data && data.length === 0 && } + {data && data.length > 0 && ( +
+ + + + + + + + + + {data.map(b => ( + + + + + + + + ))} + +
CIDRReasonIssuedExpires +
{b.cidr}{b.reason || '—'}{relativeTime(b.bannedAt)} + {b.until ? {relativeTime(b.until)} : ∞} + + +
+ )} +
+
+ ); +} diff --git a/DorkNet.Server/admin-ui/src/pages/Broadcast.tsx b/DorkNet.Server/admin-ui/src/pages/Broadcast.tsx new file mode 100644 index 0000000..4ef767d --- /dev/null +++ b/DorkNet.Server/admin-ui/src/pages/Broadcast.tsx @@ -0,0 +1,56 @@ +import { useState } from 'react'; +import { api } from '../lib/api'; +import { PageHeader } from '../components/PageHeader'; +import { useToast } from '../components/Toast'; + +export function Broadcast() { + const [message, setMessage] = useState(''); + const [busy, setBusy] = useState(false); + const toast = useToast(); + + const send = async (e: React.FormEvent) => { + e.preventDefault(); + if (!message.trim()) return; + if (!confirm(`Broadcast this to every connected player?\n\n"${message}"`)) return; + setBusy(true); + try { + await api('/broadcast', { method: 'POST', body: { Message: message } }); + toast.push('Broadcast sent', 'success'); + setMessage(''); + } catch (e) { + toast.push((e as Error).message, 'error'); + } finally { + setBusy(false); + } + }; + + return ( +
+ + +
+ + + +
+ + + + + + + +
+ + + + + diff --git a/DorkNet.Server/wwwroot/admin-legacy/style.css b/DorkNet.Server/wwwroot/admin-legacy/style.css new file mode 100644 index 0000000..beb7a99 --- /dev/null +++ b/DorkNet.Server/wwwroot/admin-legacy/style.css @@ -0,0 +1,619 @@ +@import url('https://rsms.me/inter/inter.css'); + +* { box-sizing: border-box; } + +:root { + color-scheme: dark; + + --bg: #0a0c10; + --bg-grad: radial-gradient(circle at 20% 0%, #16204a 0%, #0a0c10 60%); + --panel: rgba(22, 26, 35, 0.85); + --panel-solid: #161a23; + --panel-2: #1d2230; + --panel-3: #252b3b; + --border: #2a3145; + --border-subtle: #1f2433; + --text: #eef0f5; + --text-dim: #b3b9c9; + --muted: #7a8195; + + --accent: #7aa9ff; + --accent-2: #b48cff; + --accent-grad: linear-gradient(135deg, #7aa9ff 0%, #b48cff 100%); + --accent-hover: #93b9ff; + + --good: #4ad28b; + --good-bg: rgba(74, 210, 139, 0.12); + --warn: #ffce5b; + --warn-bg: rgba(255, 206, 91, 0.12); + --danger: #ff6f7d; + --danger-bg: rgba(255, 111, 125, 0.12); + --info-bg: rgba(122, 169, 255, 0.12); + + --radius: 10px; + --radius-lg: 14px; + --shadow-sm: 0 1px 0 rgba(255,255,255,0.05) inset, 0 2px 6px rgba(0,0,0,0.25); + --shadow: 0 1px 0 rgba(255,255,255,0.05) inset, 0 12px 32px rgba(0,0,0,0.45); + --shadow-lg: 0 1px 0 rgba(255,255,255,0.06) inset, 0 24px 60px rgba(0,0,0,0.6); + + --t: 160ms cubic-bezier(0.2, 0.8, 0.2, 1); +} + +html, body { height: 100%; } + +body { + margin: 0; + background: var(--bg); + background-image: var(--bg-grad); + background-attachment: fixed; + color: var(--text); + font-family: 'Inter', -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif; + font-feature-settings: 'cv11', 'ss01', 'ss03'; + font-size: 14px; + line-height: 1.5; + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; +} + +@supports (font-variation-settings: normal) { + body { font-family: 'Inter var', -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif; } +} + +h1, h2, h3, h4 { margin: 0; letter-spacing: -0.01em; } +h1 { font-size: 22px; font-weight: 700; } +h2 { font-size: 17px; font-weight: 600; } +h3 { + font-size: 11px; + color: var(--muted); + text-transform: uppercase; + letter-spacing: 0.08em; + font-weight: 600; + margin-bottom: 10px; +} + +a { color: var(--accent); text-decoration: none; } +a:hover { color: var(--accent-hover); } + +.muted { color: var(--muted); } +.error { + color: var(--danger); + margin-top: 12px; + min-height: 1em; + font-size: 13px; +} + +/* ── Buttons ─────────────────────────────────────────────────────── */ + +button { + background: var(--panel-2); + color: var(--text); + border: 1px solid var(--border); + padding: 7px 14px; + border-radius: 8px; + cursor: pointer; + font: inherit; + font-size: 13px; + font-weight: 500; + transition: all var(--t); + line-height: 1.2; + white-space: nowrap; +} +button:hover { background: var(--panel-3); border-color: #38415a; transform: translateY(-1px); } +button:active { transform: translateY(0); } +button:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; } + +button[type="submit"], button.primary { + background: var(--accent-grad); + border-color: transparent; + color: #0a0c10; + font-weight: 600; + box-shadow: 0 4px 14px rgba(122, 169, 255, 0.25); +} +button[type="submit"]:hover, button.primary:hover { + box-shadow: 0 6px 20px rgba(122, 169, 255, 0.4); +} + +button.danger { + background: linear-gradient(135deg, #ff6f7d 0%, #d94d6b 100%); + border-color: transparent; + color: #fff; + box-shadow: 0 4px 14px rgba(255, 111, 125, 0.2); +} +button.danger:hover { box-shadow: 0 6px 20px rgba(255, 111, 125, 0.35); } + +button.ghost { + background: transparent; + border-color: var(--border); + color: var(--text-dim); +} +button.ghost:hover { color: var(--text); border-color: var(--accent); background: var(--info-bg); } + +button.link { + background: transparent; + border: none; + color: var(--accent); + padding: 0; + text-decoration: none; + cursor: pointer; + font-size: 13px; + transition: color var(--t); +} +button.link:hover { color: var(--accent-hover); transform: none; } + +/* ── Inputs ──────────────────────────────────────────────────────── */ + +input, textarea, select { + background: var(--panel-solid); + color: var(--text); + border: 1px solid var(--border); + padding: 8px 12px; + border-radius: 8px; + font: inherit; + font-size: 13px; + transition: all var(--t); + width: 100%; +} +input::placeholder, textarea::placeholder { color: var(--muted); } +input:hover, textarea:hover, select:hover { border-color: #3a425a; } +input:focus, textarea:focus, select:focus { + outline: none; + border-color: var(--accent); + box-shadow: 0 0 0 3px rgba(122, 169, 255, 0.15); +} +input[type="checkbox"] { + width: 16px; + height: 16px; + accent-color: var(--accent); + margin: 0; + cursor: pointer; +} +input[type="number"] { width: 100%; } + +label { + display: flex; + flex-direction: column; + gap: 6px; + margin-bottom: 14px; + color: var(--text-dim); + font-size: 12px; + font-weight: 500; +} +label > input, +label > textarea, +label > select { + color: var(--text); + font-size: 13px; + font-weight: 400; +} + +/* ── Cards ───────────────────────────────────────────────────────── */ + +.card { + background: var(--panel); + backdrop-filter: blur(20px) saturate(140%); + -webkit-backdrop-filter: blur(20px) saturate(140%); + border: 1px solid var(--border); + border-radius: var(--radius-lg); + padding: 28px; + box-shadow: var(--shadow); +} + +/* ── Login ───────────────────────────────────────────────────────── */ + +#login { + max-width: 380px; + margin: 14vh auto; + animation: fadeUp 320ms cubic-bezier(0.2, 0.8, 0.2, 1); +} +#login h1 { + background: var(--accent-grad); + -webkit-background-clip: text; + background-clip: text; + -webkit-text-fill-color: transparent; + margin-bottom: 4px; +} +#login p { margin: 0 0 22px; } +#login button[type="submit"] { width: 100%; padding: 10px; font-size: 14px; } + +@keyframes fadeUp { + from { opacity: 0; transform: translateY(12px); } + to { opacity: 1; transform: translateY(0); } +} + +/* ── App shell ───────────────────────────────────────────────────── */ + +#app { animation: fadeIn 240ms ease-out; } +@keyframes fadeIn { from { opacity: 0; } to { opacity: 1; } } + +header { + display: flex; + align-items: center; + justify-content: space-between; + padding: 18px 32px; + background: rgba(13, 16, 22, 0.85); + backdrop-filter: blur(20px); + -webkit-backdrop-filter: blur(20px); + border-bottom: 1px solid var(--border-subtle); + position: sticky; + top: 0; + z-index: 50; +} +header h1 { + background: var(--accent-grad); + -webkit-background-clip: text; + background-clip: text; + -webkit-text-fill-color: transparent; + font-size: 18px; +} +header .who { + color: var(--muted); + font-size: 13px; + display: flex; + align-items: center; + gap: 12px; +} +header #meName { + color: var(--text); + font-weight: 600; + margin: 0 4px; +} + +nav { + display: flex; + gap: 4px; + padding: 12px 32px; + background: rgba(10, 12, 16, 0.6); + border-bottom: 1px solid var(--border-subtle); + overflow-x: auto; + scrollbar-width: none; +} +nav::-webkit-scrollbar { display: none; } +nav .tab { + background: transparent; + border: 1px solid transparent; + color: var(--muted); + border-radius: 8px; + font-weight: 500; + transition: all var(--t); +} +nav .tab:hover { color: var(--text-dim); transform: none; background: var(--panel-2); } +nav .tab.active { + background: var(--info-bg); + color: var(--accent); + border-color: rgba(122, 169, 255, 0.25); +} + +.tab-panel { + padding: 28px 32px 80px; + max-width: 1280px; + margin: 0 auto; + animation: fadeIn 200ms ease-out; +} +.tab-panel > h2:first-child { margin-bottom: 20px; } + +.row { + display: flex; + gap: 10px; + align-items: center; + flex-wrap: wrap; + margin: 12px 0; +} +.row > input, +.row > select { flex: 1; min-width: 140px; width: auto; } + +/* ── Dashboard stats ─────────────────────────────────────────────── */ + +.grid { + display: grid; + grid-template-columns: repeat(auto-fit, minmax(180px, 1fr)); + gap: 14px; + margin: 0 0 32px; +} +.stat { + background: var(--panel); + backdrop-filter: blur(20px); + -webkit-backdrop-filter: blur(20px); + border: 1px solid var(--border-subtle); + border-radius: var(--radius); + padding: 18px 20px; + transition: all var(--t); + position: relative; + overflow: hidden; +} +.stat:hover { transform: translateY(-2px); border-color: var(--border); box-shadow: var(--shadow-sm); } +.stat::before { + content: ''; + position: absolute; + top: 0; left: 0; right: 0; + height: 2px; + background: var(--accent-grad); + opacity: 0; + transition: opacity var(--t); +} +.stat:hover::before { opacity: 1; } +.stat .label { + color: var(--muted); + font-size: 11px; + text-transform: uppercase; + letter-spacing: 0.08em; + font-weight: 600; +} +.stat .value { + font-size: 28px; + font-weight: 700; + margin-top: 6px; + letter-spacing: -0.02em; + font-feature-settings: 'tnum'; +} +.stat .value.good { color: var(--good); } +.stat .value.warn { color: var(--warn); } +.stat .value.danger { color: var(--danger); } + +/* ── Tables ──────────────────────────────────────────────────────── */ + +.table { + width: 100%; + border-collapse: separate; + border-spacing: 0; + margin: 12px 0 24px; + background: var(--panel); + backdrop-filter: blur(20px); + -webkit-backdrop-filter: blur(20px); + border: 1px solid var(--border-subtle); + border-radius: var(--radius); + overflow: hidden; +} +.table th, .table td { + padding: 11px 16px; + text-align: left; + border-bottom: 1px solid var(--border-subtle); + font-size: 13px; + vertical-align: middle; +} +.table th { + background: rgba(13, 16, 22, 0.6); + color: var(--muted); + font-size: 10px; + text-transform: uppercase; + letter-spacing: 0.08em; + font-weight: 600; + position: sticky; + top: 0; +} +.table tr:last-child td { border-bottom: none; } +.table tr { transition: background var(--t); } +.table tbody tr:hover td { background: rgba(122, 169, 255, 0.04); } + +.table .actions { + display: flex; + gap: 6px; + flex-wrap: wrap; + justify-content: flex-end; +} +.table .actions button { + padding: 4px 10px; + font-size: 12px; +} + +/* ── Badges ──────────────────────────────────────────────────────── */ + +.badge { + display: inline-flex; + align-items: center; + gap: 4px; + padding: 3px 9px; + border-radius: 999px; + font-size: 11px; + font-weight: 600; + line-height: 1.4; + border: 1px solid transparent; +} +.badge::before { + content: ''; + width: 6px; + height: 6px; + border-radius: 50%; + background: currentColor; + opacity: 0.7; +} +.badge.no-dot::before { display: none; } +.badge.online { background: var(--good-bg); color: var(--good); border-color: rgba(74, 210, 139, 0.2); } +.badge.offline { background: rgba(139, 145, 158, 0.08); color: var(--muted); border-color: rgba(139, 145, 158, 0.18); } +.badge.admin { background: var(--info-bg); color: var(--accent); border-color: rgba(122, 169, 255, 0.2); } +.badge.banned { background: var(--danger-bg); color: var(--danger); border-color: rgba(255, 111, 125, 0.2); } + +.badge-row { display: inline-flex; gap: 4px; flex-wrap: wrap; } + +/* ── Modal ───────────────────────────────────────────────────────── */ + +.modal { + position: fixed; + inset: 0; + background: rgba(0, 0, 0, 0.7); + backdrop-filter: blur(6px); + -webkit-backdrop-filter: blur(6px); + display: flex; + align-items: flex-start; + justify-content: center; + padding: 40px 20px; + overflow-y: auto; + z-index: 100; + animation: fadeIn 160ms ease-out; +} +/* The class-level `display: flex` above out-specifies the browser + default `[hidden] { display: none }` (both are specificity 0,0,1,0 + but the class rule loads later), so without this override the + modal stays visible and shows an empty "Store item" / "Player" + shell on every page load. Force the hidden attribute to win. */ +.modal[hidden] { display: none !important; } +.modal-content { + width: 100%; + max-width: 820px; + position: relative; + margin: auto; + box-shadow: var(--shadow-lg); + animation: modalIn 200ms cubic-bezier(0.2, 0.8, 0.2, 1); + /* Override .card's translucent var(--panel) (rgba 0.85) — at + 0.85 the table behind shows through, making the modal form + look like it's overlapping page content. Force the panel + solid here so the modal reads as a discrete dialog. */ + background: var(--panel-solid) !important; + backdrop-filter: none !important; + -webkit-backdrop-filter: none !important; +} +@keyframes modalIn { + from { opacity: 0; transform: translateY(8px) scale(0.98); } + to { opacity: 1; transform: translateY(0) scale(1); } +} +.modal #modalTitle { + margin-bottom: 16px; + padding-right: 40px; + background: var(--accent-grad); + -webkit-background-clip: text; + background-clip: text; + -webkit-text-fill-color: transparent; +} +.modal .close { + position: absolute; + top: 16px; + right: 16px; + background: var(--panel-2); + border: 1px solid var(--border); + color: var(--muted); + font-size: 18px; + cursor: pointer; + padding: 0; + width: 32px; + height: 32px; + display: flex; + align-items: center; + justify-content: center; + border-radius: 8px; + line-height: 1; +} +.modal .close:hover { color: var(--text); background: var(--panel-3); transform: none; } + +/* ── Sections inside modal ───────────────────────────────────────── */ + +.section { + border-top: 1px solid var(--border-subtle); + padding-top: 18px; + margin-top: 18px; +} +.section:first-child { border-top: none; padding-top: 0; margin-top: 0; } + +.kvp { + display: grid; + grid-template-columns: 130px 1fr; + gap: 8px 16px; + margin: 0; +} +.kvp .k { + color: var(--muted); + font-size: 12px; + font-weight: 500; +} +.kvp > * { padding: 4px 0; } + +/* ── Color swatches (hair / skin preview) ────────────────────────── */ + +.swatch-input { + display: flex; + gap: 8px; + align-items: stretch; +} +.swatch-input input { flex: 1; } +.swatch { + width: 36px; + height: 36px; + border-radius: 8px; + border: 1px solid var(--border); + flex-shrink: 0; + background-image: + linear-gradient(45deg, #2a2f3a 25%, transparent 25%), + linear-gradient(-45deg, #2a2f3a 25%, transparent 25%), + linear-gradient(45deg, transparent 75%, #2a2f3a 75%), + linear-gradient(-45deg, transparent 75%, #2a2f3a 75%); + background-size: 8px 8px; + background-position: 0 0, 0 4px, 4px -4px, -4px 0px; +} + +/* ── Toast ───────────────────────────────────────────────────────── */ + +.toast { + position: fixed; + bottom: 24px; + right: 24px; + background: var(--panel-solid); + border: 1px solid var(--border); + color: var(--text); + padding: 12px 18px; + border-radius: 10px; + box-shadow: var(--shadow); + z-index: 200; + max-width: 360px; + font-size: 13px; + animation: toastIn 200ms cubic-bezier(0.2, 0.8, 0.2, 1); + border-left: 3px solid var(--accent); +} +.toast.error { border-left-color: var(--danger); } +.toast.good { border-left-color: var(--good); } +@keyframes toastIn { + from { opacity: 0; transform: translateX(20px); } + to { opacity: 1; transform: translateX(0); } +} + +textarea { + width: 100%; + resize: vertical; + min-height: 80px; + font-family: 'Inter', -apple-system, sans-serif; +} +form { margin: 0; } + +/* ── Inline-checkbox helper ──────────────────────────────────────── */ + +.checkbox-label { + flex-direction: row !important; + align-items: center !important; + gap: 8px !important; + margin: 0 !important; + color: var(--text-dim); + cursor: pointer; + user-select: none; + padding: 8px 12px; + border-radius: 8px; + border: 1px solid var(--border); + background: var(--panel-solid); + transition: all var(--t); + font-size: 13px; +} +.checkbox-label:hover { border-color: var(--accent); color: var(--text); } +.checkbox-label input { width: auto; } + +/* ── Empty state ─────────────────────────────────────────────────── */ + +.empty { + text-align: center; + padding: 40px 20px; + color: var(--muted); + font-size: 13px; +} + +/* ── Scrollbar ───────────────────────────────────────────────────── */ + +::-webkit-scrollbar { width: 10px; height: 10px; } +::-webkit-scrollbar-track { background: transparent; } +::-webkit-scrollbar-thumb { background: var(--border); border-radius: 10px; border: 2px solid var(--bg); } +::-webkit-scrollbar-thumb:hover { background: var(--panel-3); } + +/* ── Numeric figures align nicely in tables ──────────────────────── */ + +.table td:first-child, +.table td.num { font-feature-settings: 'tnum'; color: var(--muted); } + +/* ── Misc utilities ──────────────────────────────────────────────── */ + +.spacer { flex: 1; } +.right { margin-left: auto; } +.mono { font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace; font-size: 12px; } diff --git a/DorkNet.Server/wwwroot/feed/app.js b/DorkNet.Server/wwwroot/feed/app.js new file mode 100644 index 0000000..3c24440 --- /dev/null +++ b/DorkNet.Server/wwwroot/feed/app.js @@ -0,0 +1,244 @@ +// DorkNet feed — public photo gallery. Reads from same-origin +// /api/photos/v1/* (PhotosController is dual-hosted on api.rec.net and +// feed.rec.net so no CORS dance). Hash routing keeps it a single page: +// #/ → feed grid +// #/p/123 → photo detail +// #/u/456 → uploader profile + +const API = '/api/photos/v1'; + +const $ = (id) => document.getElementById(id); +const el = (tag, attrs, ...children) => { + const e = document.createElement(tag); + for (const k in attrs) { + if (k === 'class') e.className = attrs[k]; + else if (k === 'on') for (const ev in attrs.on) e.addEventListener(ev, attrs.on[ev]); + else if (k === 'html') e.innerHTML = attrs[k]; + else if (k === 'hidden' && attrs[k]) e.hidden = true; + else e[k] = attrs[k]; + } + for (const c of children.flat()) if (c != null) e.append(c?.nodeType ? c : document.createTextNode(String(c))); + return e; +}; + +async function api(path) { + const res = await fetch(API + path); + if (!res.ok) throw new Error(`${res.status} ${res.statusText}`); + return res.json(); +} + +function toast(msg, kind = '') { + const t = $('toast'); + t.textContent = msg; + t.className = 'toast ' + kind; + t.hidden = false; + clearTimeout(toast._h); + toast._h = setTimeout(() => t.hidden = true, 3000); +} + +function fmtDate(iso) { + if (!iso) return ''; + const d = new Date(iso); + if (isNaN(d)) return ''; + const now = new Date(); + const diff = (now - d) / 1000; + if (diff < 60) return 'just now'; + if (diff < 3600) return `${Math.floor(diff / 60)}m ago`; + if (diff < 86400) return `${Math.floor(diff / 3600)}h ago`; + if (diff < 604800) return `${Math.floor(diff / 86400)}d ago`; + return d.toLocaleDateString(); +} + +function initials(name) { + if (!name) return '?'; + const parts = name.split(/[\s_]/).filter(Boolean); + return (parts[0]?.[0] || '') + (parts[1]?.[0] || ''); +} + +function showView(name) { + document.querySelectorAll('.view').forEach(v => v.hidden = v.id !== `${name}-view`); + document.querySelectorAll('.nav-link').forEach(l => l.classList.toggle('active', l.dataset.route === 'feed' && name === 'feed')); + window.scrollTo(0, 0); +} + +// ── Router ─────────────────────────────────────────────────────────── + +window.addEventListener('hashchange', route); +window.addEventListener('DOMContentLoaded', route); + +function goFeed() { location.hash = '#/'; } + +function route() { + const h = location.hash.slice(1) || '/'; + const photoMatch = h.match(/^\/p\/(\d+)$/); + const userMatch = h.match(/^\/u\/(\d+)$/); + if (photoMatch) renderPhoto(parseInt(photoMatch[1], 10)); + else if (userMatch) renderProfile(parseInt(userMatch[1], 10)); + else renderFeed(); +} + +// ── Feed view ──────────────────────────────────────────────────────── + +let feedSkip = 0; +const PAGE = 24; + +async function renderFeed() { + showView('feed'); + feedSkip = 0; + $('grid').replaceChildren(...skeletonCards(6)); + $('empty').hidden = true; + $('loadMore').hidden = true; + try { + const photos = await api(`/feed?take=${PAGE}&skip=0`); + $('grid').replaceChildren(); + if (photos.length === 0) { + $('empty').hidden = false; + return; + } + for (const p of photos) $('grid').append(photoCard(p)); + feedSkip = photos.length; + if (photos.length === PAGE) $('loadMore').hidden = false; + } catch (err) { + $('grid').replaceChildren(); + toast(err.message, 'error'); + } +} + +$('loadMoreBtn').addEventListener('click', async () => { + try { + const photos = await api(`/feed?take=${PAGE}&skip=${feedSkip}`); + for (const p of photos) $('grid').append(photoCard(p)); + feedSkip += photos.length; + if (photos.length < PAGE) $('loadMore').hidden = true; + } catch (err) { toast(err.message, 'error'); } +}); + +function skeletonCards(n) { + return Array.from({ length: n }, () => { + const card = el('div', { class: 'photo-card' }); + const h = 180 + Math.floor(Math.random() * 220); + card.append(el('div', { class: 'skeleton', style: `height: ${h}px;` })); + card.append(el('div', { class: 'photo-meta' }, + el('div', { class: 'skeleton', style: 'height: 14px; width: 60%; margin-bottom: 8px;' }), + el('div', { class: 'skeleton', style: 'height: 12px; width: 90%;' }), + )); + return card; + }); +} + +function photoCard(p) { + const card = el('a', { + class: 'photo-card', + href: `#/p/${p.Id}`, + }); + const img = el('img', { + class: 'photo-img', + src: p.ImageUrl, + alt: p.Caption || `Photo by ${p.UploaderDisplayName}`, + loading: 'lazy', + }); + img.addEventListener('error', () => { + img.style.display = 'none'; + }); + card.append(img); + card.append(el('div', { class: 'photo-meta' }, + el('div', { class: 'uploader' }, + el('div', { class: 'uploader-avatar' }, initials(p.UploaderDisplayName)), + el('span', {}, p.UploaderDisplayName), + ), + p.Caption ? el('div', { class: 'caption' }, p.Caption) : null, + el('div', { class: 'photo-foot' }, + el('span', { class: 'cheers' }, `♥ ${p.CheerCount.toLocaleString()}`), + p.RoomName ? el('span', { class: 'room' }, `^${p.RoomName}`) : el('span', { class: 'time' }, fmtDate(p.CreatedAt)), + ), + )); + return card; +} + +// ── Photo detail view ──────────────────────────────────────────────── + +async function renderPhoto(id) { + showView('photo'); + $('photoDetail').replaceChildren(el('div', { class: 'skeleton', style: 'height: 480px;' })); + try { + const p = await api(`/${id}`); + const tagged = (p.TaggedPlayerIds || []).slice(0, 8); + $('photoDetail').replaceChildren(el('div', { class: 'photo-detail' }, + el('div', { class: 'image-wrap' }, + el('img', { src: p.ImageUrl, alt: p.Caption || `Photo #${p.Id}` })), + el('div', { class: 'info' }, + el('h2', {}, `Photo #${p.Id}`), + el('div', { class: 'caption-full' }, p.Caption || ''), + el('div', { class: 'meta-row' }, + el('span', { class: 'k' }, 'By'), + el('a', { class: 'uploader-link', href: `#/u/${p.UploaderPlayerId}` }, + el('div', { class: 'uploader-avatar' }, initials(p.UploaderDisplayName)), + el('span', {}, p.UploaderDisplayName), + ), + ), + p.RoomName + ? el('div', { class: 'meta-row' }, + el('span', { class: 'k' }, 'In'), + el('span', { class: 'room-pill' }, `^${p.RoomName}`)) + : null, + el('div', { class: 'meta-row' }, + el('span', { class: 'k' }, 'Posted'), + el('span', {}, fmtDate(p.CreatedAt) + ' (' + new Date(p.CreatedAt).toLocaleString() + ')'), + ), + tagged.length + ? el('div', { class: 'meta-row' }, + el('span', { class: 'k' }, 'Tagged'), + el('span', {}, tagged.length + ' player' + (tagged.length === 1 ? '' : 's'))) + : null, + el('div', { class: 'stats' }, + el('div', { class: 'stat-pill cheers' }, + el('div', { class: 'v' }, p.CheerCount.toLocaleString()), + el('div', { class: 'l' }, 'cheers')), + el('div', { class: 'stat-pill' }, + el('div', { class: 'v' }, p.ViewCount.toLocaleString()), + el('div', { class: 'l' }, 'views')), + ), + ), + )); + } catch (err) { + $('photoDetail').replaceChildren(el('div', { class: 'empty' }, + el('p', {}, 'Photo not found.'))); + } +} + +$('backBtn').addEventListener('click', goFeed); + +// ── Profile view ───────────────────────────────────────────────────── + +async function renderProfile(playerId) { + showView('profile'); + $('profileDetail').replaceChildren(el('div', { class: 'skeleton', style: 'height: 100px;' })); + try { + const photos = await api(`/by/${playerId}?take=60`); + const uploaderName = photos[0]?.UploaderDisplayName || `Player ${playerId}`; + const uploaderUsername = photos[0]?.UploaderUsername || ''; + $('profileDetail').replaceChildren( + el('div', { class: 'profile-header' }, + el('div', { class: 'avatar' }, initials(uploaderName)), + el('div', {}, + el('h2', {}, uploaderName), + el('div', { class: 'username' }, + uploaderUsername ? `@${uploaderUsername} · ` : '', + `${photos.length} photo${photos.length === 1 ? '' : 's'}`), + ), + ), + (() => { + const grid = el('div', { class: 'grid' }); + if (photos.length === 0) + grid.append(el('div', { class: 'empty' }, + el('p', {}, 'No photos posted yet.'))); + else + for (const p of photos) grid.append(photoCard(p)); + return grid; + })(), + ); + } catch (err) { + $('profileDetail').replaceChildren(el('div', { class: 'empty' }, + el('p', {}, 'Profile not found.'))); + } +} diff --git a/DorkNet.Server/wwwroot/feed/index.html b/DorkNet.Server/wwwroot/feed/index.html new file mode 100644 index 0000000..2be46e5 --- /dev/null +++ b/DorkNet.Server/wwwroot/feed/index.html @@ -0,0 +1,55 @@ + + + + + + DorkNet — Feed + + + + + +
+
+
+

The Feed

+

Photos taken by players around the server, newest first.

+
+
+ + +
+ + + + +
+ +
+

DorkNet · a private Rec Room server · GitHub

+
+ + + + + diff --git a/DorkNet.Server/wwwroot/feed/style.css b/DorkNet.Server/wwwroot/feed/style.css new file mode 100644 index 0000000..83a1db9 --- /dev/null +++ b/DorkNet.Server/wwwroot/feed/style.css @@ -0,0 +1,448 @@ +@import url('https://rsms.me/inter/inter.css'); + +* { box-sizing: border-box; } + +:root { + color-scheme: dark; + --bg: #07080b; + --bg-grad: radial-gradient(circle at 20% -10%, #1d1748 0%, #07080b 55%); + --panel: rgba(20, 22, 30, 0.7); + --panel-2: #161922; + --panel-3: #1f2330; + --border: #262b3a; + --border-subtle: #1a1e29; + --text: #f0f2f8; + --text-dim: #b0b6c8; + --muted: #757b8e; + --accent: #ff6ec7; + --accent-2: #7aa9ff; + --accent-grad: linear-gradient(135deg, #ff6ec7 0%, #b48cff 50%, #7aa9ff 100%); + --good: #4ad28b; + --danger: #ff6f7d; + --shadow: 0 12px 32px rgba(0, 0, 0, 0.45); + --t: 180ms cubic-bezier(0.2, 0.8, 0.2, 1); +} + +html, body { height: 100%; } +body { + margin: 0; + background: var(--bg); + background-image: var(--bg-grad); + background-attachment: fixed; + color: var(--text); + font-family: 'Inter', -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif; + font-size: 14px; + line-height: 1.55; + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; +} +@supports (font-variation-settings: normal) { + body { font-family: 'Inter var', -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif; } +} + +a { color: var(--accent-2); text-decoration: none; transition: color var(--t); } +a:hover { color: #a3c4ff; } + +button { + background: var(--panel-2); + color: var(--text); + border: 1px solid var(--border); + padding: 8px 16px; + border-radius: 8px; + cursor: pointer; + font: inherit; + font-size: 13px; + font-weight: 500; + transition: all var(--t); +} +button:hover { background: var(--panel-3); border-color: #353c54; transform: translateY(-1px); } +button.primary { + background: var(--accent-grad); + border-color: transparent; + color: #07080b; + font-weight: 600; + box-shadow: 0 4px 18px rgba(255, 110, 199, 0.25); +} +button.primary:hover { box-shadow: 0 6px 24px rgba(255, 110, 199, 0.4); } +button.ghost { + background: transparent; + border-color: var(--border); + color: var(--text-dim); +} +button.ghost:hover { color: var(--text); border-color: var(--accent); background: rgba(255,110,199,0.06); } + +/* ── Site shell ──────────────────────────────────────────────────── */ + +.site-header { + display: flex; + align-items: center; + justify-content: space-between; + padding: 22px 36px; + background: rgba(7, 8, 11, 0.7); + backdrop-filter: blur(24px); + -webkit-backdrop-filter: blur(24px); + border-bottom: 1px solid var(--border-subtle); + position: sticky; + top: 0; + z-index: 50; +} +.logo { + display: flex; + align-items: center; + gap: 10px; + font-weight: 800; + font-size: 17px; + letter-spacing: -0.01em; + color: var(--text); +} +.logo:hover { color: var(--text); } +.logo-mark { + width: 22px; + height: 22px; + border-radius: 50%; + background: var(--accent-grad); + color: transparent; + box-shadow: 0 4px 14px rgba(255, 110, 199, 0.5); +} +.site-nav { + display: flex; + gap: 4px; +} +.nav-link { + color: var(--muted); + padding: 8px 14px; + border-radius: 8px; + font-weight: 500; + transition: all var(--t); +} +.nav-link:hover { color: var(--text); background: var(--panel-2); } +.nav-link.active { color: var(--text); background: var(--panel-2); } + +main { max-width: 1280px; margin: 0 auto; padding: 32px 36px 80px; } + +.site-footer { + text-align: center; + padding: 32px 16px; + color: var(--muted); + font-size: 12px; + border-top: 1px solid var(--border-subtle); + margin-top: 80px; +} +.site-footer a { color: var(--muted); } +.site-footer a:hover { color: var(--text-dim); } + +/* ── Hero ────────────────────────────────────────────────────────── */ + +.hero { + margin-bottom: 28px; +} +.hero h1 { + margin: 0 0 4px; + font-size: 38px; + font-weight: 800; + letter-spacing: -0.03em; + background: var(--accent-grad); + -webkit-background-clip: text; + background-clip: text; + -webkit-text-fill-color: transparent; +} +.hero p { margin: 0; color: var(--text-dim); font-size: 15px; } +.muted { color: var(--muted); } + +/* ── Photo grid (masonry-ish via CSS columns) ────────────────────── */ + +.grid { + column-count: 3; + column-gap: 16px; +} +@media (max-width: 1024px) { .grid { column-count: 2; } } +@media (max-width: 600px) { .grid { column-count: 1; } } + +.photo-card { + break-inside: avoid; + margin-bottom: 16px; + background: var(--panel); + backdrop-filter: blur(20px); + -webkit-backdrop-filter: blur(20px); + border: 1px solid var(--border-subtle); + border-radius: 14px; + overflow: hidden; + transition: all var(--t); + cursor: pointer; + display: block; + color: inherit; + animation: fadeIn 280ms ease-out; +} +.photo-card:hover { + transform: translateY(-3px); + border-color: var(--border); + box-shadow: var(--shadow); +} +.photo-card .photo-img { + display: block; + width: 100%; + height: auto; + background: var(--panel-2); + aspect-ratio: auto; +} +.photo-card .photo-meta { + padding: 12px 14px 14px; +} +.photo-card .uploader { + font-weight: 600; + color: var(--text); + font-size: 13px; + margin: 0 0 4px; + display: flex; + align-items: center; + gap: 8px; +} +.photo-card .uploader-avatar { + width: 22px; + height: 22px; + border-radius: 50%; + background: var(--accent-grad); + flex-shrink: 0; + font-size: 10px; + font-weight: 700; + color: #07080b; + display: flex; + align-items: center; + justify-content: center; +} +.photo-card .caption { + color: var(--text-dim); + font-size: 13px; + margin: 6px 0 8px; + line-height: 1.45; + /* clamp to 3 lines on the card */ + display: -webkit-box; + -webkit-line-clamp: 3; + -webkit-box-orient: vertical; + overflow: hidden; +} +.photo-card .photo-foot { + display: flex; + justify-content: space-between; + align-items: center; + color: var(--muted); + font-size: 11px; + margin-top: 4px; +} +.photo-card .photo-foot .cheers { + color: var(--accent); + font-weight: 600; +} +.photo-card .photo-foot .room { + background: var(--panel-3); + padding: 2px 8px; + border-radius: 6px; + color: var(--text-dim); + max-width: 60%; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; +} + +/* ── Photo detail ────────────────────────────────────────────────── */ + +.back { + margin-bottom: 18px; + padding: 6px 14px; + font-size: 12px; +} + +.photo-detail { + display: grid; + grid-template-columns: minmax(0, 1fr) 340px; + gap: 28px; + align-items: start; +} +@media (max-width: 900px) { + .photo-detail { grid-template-columns: 1fr; } +} +.photo-detail .image-wrap { + background: var(--panel); + backdrop-filter: blur(20px); + -webkit-backdrop-filter: blur(20px); + border: 1px solid var(--border-subtle); + border-radius: 16px; + overflow: hidden; +} +.photo-detail .image-wrap img { + display: block; + width: 100%; + height: auto; +} +.photo-detail .info { + background: var(--panel); + backdrop-filter: blur(20px); + -webkit-backdrop-filter: blur(20px); + border: 1px solid var(--border-subtle); + border-radius: 16px; + padding: 22px; + position: sticky; + top: 96px; +} +.photo-detail .info h2 { + margin: 0 0 12px; + font-size: 18px; + background: var(--accent-grad); + -webkit-background-clip: text; + background-clip: text; + -webkit-text-fill-color: transparent; +} +.photo-detail .info .caption-full { + color: var(--text); + margin: 0 0 16px; + line-height: 1.55; + white-space: pre-wrap; +} +.photo-detail .info .caption-full:empty::before { + content: 'No caption.'; + color: var(--muted); + font-style: italic; +} +.photo-detail .info .meta-row { + display: flex; + align-items: center; + gap: 10px; + padding: 10px 0; + border-top: 1px solid var(--border-subtle); + color: var(--text-dim); + font-size: 13px; +} +.photo-detail .info .meta-row:first-of-type { border-top: none; } +.photo-detail .info .meta-row .k { color: var(--muted); min-width: 80px; } +.photo-detail .info .uploader-link { + display: flex; + align-items: center; + gap: 10px; + color: var(--text); + font-weight: 600; +} +.photo-detail .info .uploader-link:hover { color: var(--accent); } +.photo-detail .info .room-pill { + background: var(--panel-3); + padding: 4px 10px; + border-radius: 6px; + color: var(--text-dim); + font-size: 12px; +} +.photo-detail .info .stats { + display: flex; + gap: 12px; + margin-top: 14px; + padding-top: 14px; + border-top: 1px solid var(--border-subtle); +} +.photo-detail .info .stat-pill { + background: var(--panel-2); + border: 1px solid var(--border-subtle); + padding: 8px 14px; + border-radius: 10px; + flex: 1; + text-align: center; +} +.photo-detail .info .stat-pill .v { + font-size: 18px; + font-weight: 700; + color: var(--text); + font-feature-settings: 'tnum'; +} +.photo-detail .info .stat-pill .l { + font-size: 10px; + text-transform: uppercase; + letter-spacing: 0.08em; + color: var(--muted); + margin-top: 2px; +} +.photo-detail .info .stat-pill.cheers .v { color: var(--accent); } + +/* ── Profile view ────────────────────────────────────────────────── */ + +.profile-header { + display: flex; + align-items: center; + gap: 18px; + padding: 22px; + background: var(--panel); + backdrop-filter: blur(20px); + -webkit-backdrop-filter: blur(20px); + border: 1px solid var(--border-subtle); + border-radius: 16px; + margin-bottom: 28px; +} +.profile-header .avatar { + width: 64px; + height: 64px; + border-radius: 50%; + background: var(--accent-grad); + color: #07080b; + display: flex; + align-items: center; + justify-content: center; + font-size: 22px; + font-weight: 800; + flex-shrink: 0; +} +.profile-header h2 { margin: 0 0 4px; font-size: 22px; } +.profile-header .username { color: var(--muted); font-size: 13px; } + +/* ── Empty / load-more ───────────────────────────────────────────── */ + +.empty { + text-align: center; + padding: 80px 24px; + color: var(--muted); +} +.empty p { margin: 0; font-size: 14px; } + +.load-more { + text-align: center; + margin: 28px 0; +} + +/* ── Toast ───────────────────────────────────────────────────────── */ + +.toast { + position: fixed; + bottom: 24px; + right: 24px; + background: var(--panel-2); + border: 1px solid var(--border); + color: var(--text); + padding: 12px 18px; + border-radius: 10px; + box-shadow: var(--shadow); + z-index: 200; + font-size: 13px; + border-left: 3px solid var(--accent); + animation: toastIn 200ms cubic-bezier(0.2, 0.8, 0.2, 1); +} +.toast.error { border-left-color: var(--danger); } + +@keyframes fadeIn { + from { opacity: 0; transform: translateY(8px); } + to { opacity: 1; transform: translateY(0); } +} +@keyframes toastIn { + from { opacity: 0; transform: translateX(20px); } + to { opacity: 1; transform: translateX(0); } +} + +::-webkit-scrollbar { width: 10px; height: 10px; } +::-webkit-scrollbar-track { background: transparent; } +::-webkit-scrollbar-thumb { background: var(--border); border-radius: 10px; border: 2px solid var(--bg); } +::-webkit-scrollbar-thumb:hover { background: var(--panel-3); } + +.skeleton { + background: linear-gradient(90deg, var(--panel-2) 0%, var(--panel-3) 50%, var(--panel-2) 100%); + background-size: 200% 100%; + animation: shimmer 1.4s linear infinite; + border-radius: 12px; +} +@keyframes shimmer { + from { background-position: 200% 0; } + to { background-position: -200% 0; } +} diff --git a/DorkNet.sln b/DorkNet.sln new file mode 100644 index 0000000..17398cf --- /dev/null +++ b/DorkNet.sln @@ -0,0 +1,75 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 17 +VisualStudioVersion = 17.0.31903.59 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DorkNet.Server", "DorkNet.Server\DorkNet.Server.csproj", "{7DC352E8-24BD-4D84-A4D3-66C0FA1A91F7}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DorkNet.Models", "DorkNet.Models\DorkNet.Models.csproj", "{8A1F0C3A-5763-4176-B33E-F4EA89BAF999}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DorkNet.ClientPatch", "DorkNet.ClientPatch\DorkNet.ClientPatch.csproj", "{CB0D59F5-C17D-49E1-9E6B-E89A9E89EAB6}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Tools", "Tools", "{07C2787E-EAC7-C090-1BA3-A61EC2A24D84}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MigrateSqliteToPostgres", "Tools\MigrateSqliteToPostgres\MigrateSqliteToPostgres.csproj", "{70C60098-1B11-4C13-943B-A8A056429EAE}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Debug|x64 = Debug|x64 + Debug|x86 = Debug|x86 + Release|Any CPU = Release|Any CPU + Release|x64 = Release|x64 + Release|x86 = Release|x86 + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {7DC352E8-24BD-4D84-A4D3-66C0FA1A91F7}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {7DC352E8-24BD-4D84-A4D3-66C0FA1A91F7}.Debug|Any CPU.Build.0 = Debug|Any CPU + {7DC352E8-24BD-4D84-A4D3-66C0FA1A91F7}.Debug|x64.ActiveCfg = Debug|Any CPU + {7DC352E8-24BD-4D84-A4D3-66C0FA1A91F7}.Debug|x64.Build.0 = Debug|Any CPU + {7DC352E8-24BD-4D84-A4D3-66C0FA1A91F7}.Debug|x86.ActiveCfg = Debug|Any CPU + {7DC352E8-24BD-4D84-A4D3-66C0FA1A91F7}.Debug|x86.Build.0 = Debug|Any CPU + {7DC352E8-24BD-4D84-A4D3-66C0FA1A91F7}.Release|Any CPU.ActiveCfg = Release|Any CPU + {7DC352E8-24BD-4D84-A4D3-66C0FA1A91F7}.Release|Any CPU.Build.0 = Release|Any CPU + {7DC352E8-24BD-4D84-A4D3-66C0FA1A91F7}.Release|x64.ActiveCfg = Release|Any CPU + {7DC352E8-24BD-4D84-A4D3-66C0FA1A91F7}.Release|x64.Build.0 = Release|Any CPU + {7DC352E8-24BD-4D84-A4D3-66C0FA1A91F7}.Release|x86.ActiveCfg = Release|Any CPU + {7DC352E8-24BD-4D84-A4D3-66C0FA1A91F7}.Release|x86.Build.0 = Release|Any CPU + {8A1F0C3A-5763-4176-B33E-F4EA89BAF999}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {8A1F0C3A-5763-4176-B33E-F4EA89BAF999}.Debug|Any CPU.Build.0 = Debug|Any CPU + {8A1F0C3A-5763-4176-B33E-F4EA89BAF999}.Debug|x64.ActiveCfg = Debug|Any CPU + {8A1F0C3A-5763-4176-B33E-F4EA89BAF999}.Debug|x64.Build.0 = Debug|Any CPU + {8A1F0C3A-5763-4176-B33E-F4EA89BAF999}.Debug|x86.ActiveCfg = Debug|Any CPU + {8A1F0C3A-5763-4176-B33E-F4EA89BAF999}.Debug|x86.Build.0 = Debug|Any CPU + {8A1F0C3A-5763-4176-B33E-F4EA89BAF999}.Release|Any CPU.ActiveCfg = Release|Any CPU + {8A1F0C3A-5763-4176-B33E-F4EA89BAF999}.Release|Any CPU.Build.0 = Release|Any CPU + {8A1F0C3A-5763-4176-B33E-F4EA89BAF999}.Release|x64.ActiveCfg = Release|Any CPU + {8A1F0C3A-5763-4176-B33E-F4EA89BAF999}.Release|x64.Build.0 = Release|Any CPU + {8A1F0C3A-5763-4176-B33E-F4EA89BAF999}.Release|x86.ActiveCfg = Release|Any CPU + {8A1F0C3A-5763-4176-B33E-F4EA89BAF999}.Release|x86.Build.0 = Release|Any CPU + {CB0D59F5-C17D-49E1-9E6B-E89A9E89EAB6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {CB0D59F5-C17D-49E1-9E6B-E89A9E89EAB6}.Debug|x64.ActiveCfg = Debug|Any CPU + {CB0D59F5-C17D-49E1-9E6B-E89A9E89EAB6}.Debug|x86.ActiveCfg = Debug|Any CPU + {CB0D59F5-C17D-49E1-9E6B-E89A9E89EAB6}.Release|Any CPU.ActiveCfg = Release|Any CPU + {CB0D59F5-C17D-49E1-9E6B-E89A9E89EAB6}.Release|x64.ActiveCfg = Release|Any CPU + {CB0D59F5-C17D-49E1-9E6B-E89A9E89EAB6}.Release|x86.ActiveCfg = Release|Any CPU + {70C60098-1B11-4C13-943B-A8A056429EAE}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {70C60098-1B11-4C13-943B-A8A056429EAE}.Debug|Any CPU.Build.0 = Debug|Any CPU + {70C60098-1B11-4C13-943B-A8A056429EAE}.Debug|x64.ActiveCfg = Debug|Any CPU + {70C60098-1B11-4C13-943B-A8A056429EAE}.Debug|x64.Build.0 = Debug|Any CPU + {70C60098-1B11-4C13-943B-A8A056429EAE}.Debug|x86.ActiveCfg = Debug|Any CPU + {70C60098-1B11-4C13-943B-A8A056429EAE}.Debug|x86.Build.0 = Debug|Any CPU + {70C60098-1B11-4C13-943B-A8A056429EAE}.Release|Any CPU.ActiveCfg = Release|Any CPU + {70C60098-1B11-4C13-943B-A8A056429EAE}.Release|Any CPU.Build.0 = Release|Any CPU + {70C60098-1B11-4C13-943B-A8A056429EAE}.Release|x64.ActiveCfg = Release|Any CPU + {70C60098-1B11-4C13-943B-A8A056429EAE}.Release|x64.Build.0 = Release|Any CPU + {70C60098-1B11-4C13-943B-A8A056429EAE}.Release|x86.ActiveCfg = Release|Any CPU + {70C60098-1B11-4C13-943B-A8A056429EAE}.Release|x86.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(NestedProjects) = preSolution + {70C60098-1B11-4C13-943B-A8A056429EAE} = {07C2787E-EAC7-C090-1BA3-A61EC2A24D84} + EndGlobalSection +EndGlobal diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..be3f7b2 --- /dev/null +++ b/LICENSE @@ -0,0 +1,661 @@ + GNU AFFERO GENERAL PUBLIC LICENSE + Version 3, 19 November 2007 + + Copyright (C) 2007 Free Software Foundation, Inc. + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The GNU Affero General Public License is a free, copyleft license for +software and other kinds of works, specifically designed to ensure +cooperation with the community in the case of network server software. + + The licenses for most software and other practical works are designed +to take away your freedom to share and change the works. By contrast, +our General Public Licenses are intended to guarantee your freedom to +share and change all versions of a program--to make sure it remains free +software for all its users. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +them if you wish), that you receive source code or can get it if you +want it, that you can change the software or use pieces of it in new +free programs, and that you know you can do these things. + + Developers that use our General Public Licenses protect your rights +with two steps: (1) assert copyright on the software, and (2) offer +you this License which gives you legal permission to copy, distribute +and/or modify the software. + + A secondary benefit of defending all users' freedom is that +improvements made in alternate versions of the program, if they +receive widespread use, become available for other developers to +incorporate. Many developers of free software are heartened and +encouraged by the resulting cooperation. However, in the case of +software used on network servers, this result may fail to come about. +The GNU General Public License permits making a modified version and +letting the public access it on a server without ever releasing its +source code to the public. + + The GNU Affero General Public License is designed specifically to +ensure that, in such cases, the modified source code becomes available +to the community. It requires the operator of a network server to +provide the source code of the modified version running there to the +users of that server. Therefore, public use of a modified version, on +a publicly accessible server, gives the public access to the source +code of the modified version. + + An older license, called the Affero General Public License and +published by Affero, was designed to accomplish similar goals. This is +a different license, not a version of the Affero GPL, but Affero has +released a new version of the Affero GPL which permits relicensing under +this license. + + The precise terms and conditions for copying, distribution and +modification follow. + + TERMS AND CONDITIONS + + 0. Definitions. + + "This License" refers to version 3 of the GNU Affero General Public License. + + "Copyright" also means copyright-like laws that apply to other kinds of +works, such as semiconductor masks. + + "The Program" refers to any copyrightable work licensed under this +License. Each licensee is addressed as "you". "Licensees" and +"recipients" may be individuals or organizations. + + To "modify" a work means to copy from or adapt all or part of the work +in a fashion requiring copyright permission, other than the making of an +exact copy. The resulting work is called a "modified version" of the +earlier work or a work "based on" the earlier work. + + A "covered work" means either the unmodified Program or a work based +on the Program. + + To "propagate" a work means to do anything with it that, without +permission, would make you directly or secondarily liable for +infringement under applicable copyright law, except executing it on a +computer or modifying a private copy. Propagation includes copying, +distribution (with or without modification), making available to the +public, and in some countries other activities as well. + + To "convey" a work means any kind of propagation that enables other +parties to make or receive copies. Mere interaction with a user through +a computer network, with no transfer of a copy, is not conveying. + + An interactive user interface displays "Appropriate Legal Notices" +to the extent that it includes a convenient and prominently visible +feature that (1) displays an appropriate copyright notice, and (2) +tells the user that there is no warranty for the work (except to the +extent that warranties are provided), that licensees may convey the +work under this License, and how to view a copy of this License. If +the interface presents a list of user commands or options, such as a +menu, a prominent item in the list meets this criterion. + + 1. Source Code. + + The "source code" for a work means the preferred form of the work +for making modifications to it. "Object code" means any non-source +form of a work. + + A "Standard Interface" means an interface that either is an official +standard defined by a recognized standards body, or, in the case of +interfaces specified for a particular programming language, one that +is widely used among developers working in that language. + + The "System Libraries" of an executable work include anything, other +than the work as a whole, that (a) is included in the normal form of +packaging a Major Component, but which is not part of that Major +Component, and (b) serves only to enable use of the work with that +Major Component, or to implement a Standard Interface for which an +implementation is available to the public in source code form. A +"Major Component", in this context, means a major essential component +(kernel, window system, and so on) of the specific operating system +(if any) on which the executable work runs, or a compiler used to +produce the work, or an object code interpreter used to run it. + + The "Corresponding Source" for a work in object code form means all +the source code needed to generate, install, and (for an executable +work) run the object code and to modify the work, including scripts to +control those activities. However, it does not include the work's +System Libraries, or general-purpose tools or generally available free +programs which are used unmodified in performing those activities but +which are not part of the work. For example, Corresponding Source +includes interface definition files associated with source files for +the work, and the source code for shared libraries and dynamically +linked subprograms that the work is specifically designed to require, +such as by intimate data communication or control flow between those +subprograms and other parts of the work. + + The Corresponding Source need not include anything that users +can regenerate automatically from other parts of the Corresponding +Source. + + The Corresponding Source for a work in source code form is that +same work. + + 2. Basic Permissions. + + All rights granted under this License are granted for the term of +copyright on the Program, and are irrevocable provided the stated +conditions are met. This License explicitly affirms your unlimited +permission to run the unmodified Program. The output from running a +covered work is covered by this License only if the output, given its +content, constitutes a covered work. This License acknowledges your +rights of fair use or other equivalent, as provided by copyright law. + + You may make, run and propagate covered works that you do not +convey, without conditions so long as your license otherwise remains +in force. You may convey covered works to others for the sole purpose +of having them make modifications exclusively for you, or provide you +with facilities for running those works, provided that you comply with +the terms of this License in conveying all material for which you do +not control copyright. Those thus making or running the covered works +for you must do so exclusively on your behalf, under your direction +and control, on terms that prohibit them from making any copies of +your copyrighted material outside their relationship with you. + + Conveying under any other circumstances is permitted solely under +the conditions stated below. Sublicensing is not allowed; section 10 +makes it unnecessary. + + 3. Protecting Users' Legal Rights From Anti-Circumvention Law. + + No covered work shall be deemed part of an effective technological +measure under any applicable law fulfilling obligations under article +11 of the WIPO copyright treaty adopted on 20 December 1996, or +similar laws prohibiting or restricting circumvention of such +measures. + + When you convey a covered work, you waive any legal power to forbid +circumvention of technological measures to the extent such circumvention +is effected by exercising rights under this License with respect to +the covered work, and you disclaim any intention to limit operation or +modification of the work as a means of enforcing, against the work's +users, your or third parties' legal rights to forbid circumvention of +technological measures. + + 4. Conveying Verbatim Copies. + + You may convey verbatim copies of the Program's source code as you +receive it, in any medium, provided that you conspicuously and +appropriately publish on each copy an appropriate copyright notice; +keep intact all notices stating that this License and any +non-permissive terms added in accord with section 7 apply to the code; +keep intact all notices of the absence of any warranty; and give all +recipients a copy of this License along with the Program. + + You may charge any price or no price for each copy that you convey, +and you may offer support or warranty protection for a fee. + + 5. Conveying Modified Source Versions. + + You may convey a work based on the Program, or the modifications to +produce it from the Program, in the form of source code under the +terms of section 4, provided that you also meet all of these conditions: + + a) The work must carry prominent notices stating that you modified + it, and giving a relevant date. + + b) The work must carry prominent notices stating that it is + released under this License and any conditions added under section + 7. This requirement modifies the requirement in section 4 to + "keep intact all notices". + + c) You must license the entire work, as a whole, under this + License to anyone who comes into possession of a copy. This + License will therefore apply, along with any applicable section 7 + additional terms, to the whole of the work, and all its parts, + regardless of how they are packaged. This License gives no + permission to license the work in any other way, but it does not + invalidate such permission if you have separately received it. + + d) If the work has interactive user interfaces, each must display + Appropriate Legal Notices; however, if the Program has interactive + interfaces that do not display Appropriate Legal Notices, your + work need not make them do so. + + A compilation of a covered work with other separate and independent +works, which are not by their nature extensions of the covered work, +and which are not combined with it such as to form a larger program, +in or on a volume of a storage or distribution medium, is called an +"aggregate" if the compilation and its resulting copyright are not +used to limit the access or legal rights of the compilation's users +beyond what the individual works permit. Inclusion of a covered work +in an aggregate does not cause this License to apply to the other +parts of the aggregate. + + 6. Conveying Non-Source Forms. + + You may convey a covered work in object code form under the terms +of sections 4 and 5, provided that you also convey the +machine-readable Corresponding Source under the terms of this License, +in one of these ways: + + a) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by the + Corresponding Source fixed on a durable physical medium + customarily used for software interchange. + + b) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by a + written offer, valid for at least three years and valid for as + long as you offer spare parts or customer support for that product + model, to give anyone who possesses the object code either (1) a + copy of the Corresponding Source for all the software in the + product that is covered by this License, on a durable physical + medium customarily used for software interchange, for a price no + more than your reasonable cost of physically performing this + conveying of source, or (2) access to copy the + Corresponding Source from a network server at no charge. + + c) Convey individual copies of the object code with a copy of the + written offer to provide the Corresponding Source. This + alternative is allowed only occasionally and noncommercially, and + only if you received the object code with such an offer, in accord + with subsection 6b. + + d) Convey the object code by offering access from a designated + place (gratis or for a charge), and offer equivalent access to the + Corresponding Source in the same way through the same place at no + further charge. You need not require recipients to copy the + Corresponding Source along with the object code. If the place to + copy the object code is a network server, the Corresponding Source + may be on a different server (operated by you or a third party) + that supports equivalent copying facilities, provided you maintain + clear directions next to the object code saying where to find the + Corresponding Source. Regardless of what server hosts the + Corresponding Source, you remain obligated to ensure that it is + available for as long as needed to satisfy these requirements. + + e) Convey the object code using peer-to-peer transmission, provided + you inform other peers where the object code and Corresponding + Source of the work are being offered to the general public at no + charge under subsection 6d. + + A separable portion of the object code, whose source code is excluded +from the Corresponding Source as a System Library, need not be +included in conveying the object code work. + + A "User Product" is either (1) a "consumer product", which means any +tangible personal property which is normally used for personal, family, +or household purposes, or (2) anything designed or sold for incorporation +into a dwelling. In determining whether a product is a consumer product, +doubtful cases shall be resolved in favor of coverage. For a particular +product received by a particular user, "normally used" refers to a +typical or common use of that class of product, regardless of the status +of the particular user or of the way in which the particular user +actually uses, or expects or is expected to use, the product. A product +is a consumer product regardless of whether the product has substantial +commercial, industrial or non-consumer uses, unless such uses represent +the only significant mode of use of the product. + + "Installation Information" for a User Product means any methods, +procedures, authorization keys, or other information required to install +and execute modified versions of a covered work in that User Product from +a modified version of its Corresponding Source. The information must +suffice to ensure that the continued functioning of the modified object +code is in no case prevented or interfered with solely because +modification has been made. + + If you convey an object code work under this section in, or with, or +specifically for use in, a User Product, and the conveying occurs as +part of a transaction in which the right of possession and use of the +User Product is transferred to the recipient in perpetuity or for a +fixed term (regardless of how the transaction is characterized), the +Corresponding Source conveyed under this section must be accompanied +by the Installation Information. But this requirement does not apply +if neither you nor any third party retains the ability to install +modified object code on the User Product (for example, the work has +been installed in ROM). + + The requirement to provide Installation Information does not include a +requirement to continue to provide support service, warranty, or updates +for a work that has been modified or installed by the recipient, or for +the User Product in which it has been modified or installed. Access to a +network may be denied when the modification itself materially and +adversely affects the operation of the network or violates the rules and +protocols for communication across the network. + + Corresponding Source conveyed, and Installation Information provided, +in accord with this section must be in a format that is publicly +documented (and with an implementation available to the public in +source code form), and must require no special password or key for +unpacking, reading or copying. + + 7. Additional Terms. + + "Additional permissions" are terms that supplement the terms of this +License by making exceptions from one or more of its conditions. +Additional permissions that are applicable to the entire Program shall +be treated as though they were included in this License, to the extent +that they are valid under applicable law. If additional permissions +apply only to part of the Program, that part may be used separately +under those permissions, but the entire Program remains governed by +this License without regard to the additional permissions. + + When you convey a copy of a covered work, you may at your option +remove any additional permissions from that copy, or from any part of +it. (Additional permissions may be written to require their own +removal in certain cases when you modify the work.) You may place +additional permissions on material, added by you to a covered work, +for which you have or can give appropriate copyright permission. + + Notwithstanding any other provision of this License, for material you +add to a covered work, you may (if authorized by the copyright holders of +that material) supplement the terms of this License with terms: + + a) Disclaiming warranty or limiting liability differently from the + terms of sections 15 and 16 of this License; or + + b) Requiring preservation of specified reasonable legal notices or + author attributions in that material or in the Appropriate Legal + Notices displayed by works containing it; or + + c) Prohibiting misrepresentation of the origin of that material, or + requiring that modified versions of such material be marked in + reasonable ways as different from the original version; or + + d) Limiting the use for publicity purposes of names of licensors or + authors of the material; or + + e) Declining to grant rights under trademark law for use of some + trade names, trademarks, or service marks; or + + f) Requiring indemnification of licensors and authors of that + material by anyone who conveys the material (or modified versions of + it) with contractual assumptions of liability to the recipient, for + any liability that these contractual assumptions directly impose on + those licensors and authors. + + All other non-permissive additional terms are considered "further +restrictions" within the meaning of section 10. If the Program as you +received it, or any part of it, contains a notice stating that it is +governed by this License along with a term that is a further +restriction, you may remove that term. If a license document contains +a further restriction but permits relicensing or conveying under this +License, you may add to a covered work material governed by the terms +of that license document, provided that the further restriction does +not survive such relicensing or conveying. + + If you add terms to a covered work in accord with this section, you +must place, in the relevant source files, a statement of the +additional terms that apply to those files, or a notice indicating +where to find the applicable terms. + + Additional terms, permissive or non-permissive, may be stated in the +form of a separately written license, or stated as exceptions; +the above requirements apply either way. + + 8. Termination. + + You may not propagate or modify a covered work except as expressly +provided under this License. Any attempt otherwise to propagate or +modify it is void, and will automatically terminate your rights under +this License (including any patent licenses granted under the third +paragraph of section 11). + + However, if you cease all violation of this License, then your +license from a particular copyright holder is reinstated (a) +provisionally, unless and until the copyright holder explicitly and +finally terminates your license, and (b) permanently, if the copyright +holder fails to notify you of the violation by some reasonable means +prior to 60 days after the cessation. + + Moreover, your license from a particular copyright holder is +reinstated permanently if the copyright holder notifies you of the +violation by some reasonable means, this is the first time you have +received notice of violation of this License (for any work) from that +copyright holder, and you cure the violation prior to 30 days after +your receipt of the notice. + + Termination of your rights under this section does not terminate the +licenses of parties who have received copies or rights from you under +this License. If your rights have been terminated and not permanently +reinstated, you do not qualify to receive new licenses for the same +material under section 10. + + 9. Acceptance Not Required for Having Copies. + + You are not required to accept this License in order to receive or +run a copy of the Program. Ancillary propagation of a covered work +occurring solely as a consequence of using peer-to-peer transmission +to receive a copy likewise does not require acceptance. However, +nothing other than this License grants you permission to propagate or +modify any covered work. These actions infringe copyright if you do +not accept this License. Therefore, by modifying or propagating a +covered work, you indicate your acceptance of this License to do so. + + 10. Automatic Licensing of Downstream Recipients. + + Each time you convey a covered work, the recipient automatically +receives a license from the original licensors, to run, modify and +propagate that work, subject to this License. You are not responsible +for enforcing compliance by third parties with this License. + + An "entity transaction" is a transaction transferring control of an +organization, or substantially all assets of one, or subdividing an +organization, or merging organizations. If propagation of a covered +work results from an entity transaction, each party to that +transaction who receives a copy of the work also receives whatever +licenses to the work the party's predecessor in interest had or could +give under the previous paragraph, plus a right to possession of the +Corresponding Source of the work from the predecessor in interest, if +the predecessor has it or can get it with reasonable efforts. + + You may not impose any further restrictions on the exercise of the +rights granted or affirmed under this License. For example, you may +not impose a license fee, royalty, or other charge for exercise of +rights granted under this License, and you may not initiate litigation +(including a cross-claim or counterclaim in a lawsuit) alleging that +any patent claim is infringed by making, using, selling, offering for +sale, or importing the Program or any portion of it. + + 11. Patents. + + A "contributor" is a copyright holder who authorizes use under this +License of the Program or a work on which the Program is based. The +work thus licensed is called the contributor's "contributor version". + + A contributor's "essential patent claims" are all patent claims +owned or controlled by the contributor, whether already acquired or +hereafter acquired, that would be infringed by some manner, permitted +by this License, of making, using, or selling its contributor version, +but do not include claims that would be infringed only as a +consequence of further modification of the contributor version. For +purposes of this definition, "control" includes the right to grant +patent sublicenses in a manner consistent with the requirements of +this License. + + Each contributor grants you a non-exclusive, worldwide, royalty-free +patent license under the contributor's essential patent claims, to +make, use, sell, offer for sale, import and otherwise run, modify and +propagate the contents of its contributor version. + + In the following three paragraphs, a "patent license" is any express +agreement or commitment, however denominated, not to enforce a patent +(such as an express permission to practice a patent or covenant not to +sue for patent infringement). To "grant" such a patent license to a +party means to make such an agreement or commitment not to enforce a +patent against the party. + + If you convey a covered work, knowingly relying on a patent license, +and the Corresponding Source of the work is not available for anyone +to copy, free of charge and under the terms of this License, through a +publicly available network server or other readily accessible means, +then you must either (1) cause the Corresponding Source to be so +available, or (2) arrange to deprive yourself of the benefit of the +patent license for this particular work, or (3) arrange, in a manner +consistent with the requirements of this License, to extend the patent +license to downstream recipients. "Knowingly relying" means you have +actual knowledge that, but for the patent license, your conveying the +covered work in a country, or your recipient's use of the covered work +in a country, would infringe one or more identifiable patents in that +country that you have reason to believe are valid. + + If, pursuant to or in connection with a single transaction or +arrangement, you convey, or propagate by procuring conveyance of, a +covered work, and grant a patent license to some of the parties +receiving the covered work authorizing them to use, propagate, modify +or convey a specific copy of the covered work, then the patent license +you grant is automatically extended to all recipients of the covered +work and works based on it. + + A patent license is "discriminatory" if it does not include within +the scope of its coverage, prohibits the exercise of, or is +conditioned on the non-exercise of one or more of the rights that are +specifically granted under this License. You may not convey a covered +work if you are a party to an arrangement with a third party that is +in the business of distributing software, under which you make payment +to the third party based on the extent of your activity of conveying +the work, and under which the third party grants, to any of the +parties who would receive the covered work from you, a discriminatory +patent license (a) in connection with copies of the covered work +conveyed by you (or copies made from those copies), or (b) primarily +for and in connection with specific products or compilations that +contain the covered work, unless you entered into that arrangement, +or that patent license was granted, prior to 28 March 2007. + + Nothing in this License shall be construed as excluding or limiting +any implied license or other defenses to infringement that may +otherwise be available to you under applicable patent law. + + 12. No Surrender of Others' Freedom. + + If conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot convey a +covered work so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you may +not convey it at all. For example, if you agree to terms that obligate you +to collect a royalty for further conveying from those to whom you convey +the Program, the only way you could satisfy both those terms and this +License would be to refrain entirely from conveying the Program. + + 13. Remote Network Interaction; Use with the GNU General Public License. + + Notwithstanding any other provision of this License, if you modify the +Program, your modified version must prominently offer all users +interacting with it remotely through a computer network (if your version +supports such interaction) an opportunity to receive the Corresponding +Source of your version by providing access to the Corresponding Source +from a network server at no charge, through some standard or customary +means of facilitating copying of software. This Corresponding Source +shall include the Corresponding Source for any work covered by version 3 +of the GNU General Public License that is incorporated pursuant to the +following paragraph. + + Notwithstanding any other provision of this License, you have +permission to link or combine any covered work with a work licensed +under version 3 of the GNU General Public License into a single +combined work, and to convey the resulting work. The terms of this +License will continue to apply to the part which is the covered work, +but the work with which it is combined will remain governed by version +3 of the GNU General Public License. + + 14. Revised Versions of this License. + + The Free Software Foundation may publish revised and/or new versions of +the GNU Affero General Public License from time to time. Such new versions +will be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + + Each version is given a distinguishing version number. If the +Program specifies that a certain numbered version of the GNU Affero General +Public License "or any later version" applies to it, you have the +option of following the terms and conditions either of that numbered +version or of any later version published by the Free Software +Foundation. If the Program does not specify a version number of the +GNU Affero General Public License, you may choose any version ever published +by the Free Software Foundation. + + If the Program specifies that a proxy can decide which future +versions of the GNU Affero General Public License can be used, that proxy's +public statement of acceptance of a version permanently authorizes you +to choose that version for the Program. + + Later license versions may give you additional or different +permissions. However, no additional obligations are imposed on any +author or copyright holder as a result of your choosing to follow a +later version. + + 15. Disclaimer of Warranty. + + THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY +APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT +HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY +OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, +THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM +IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF +ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + + 16. Limitation of Liability. + + IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS +THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY +GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE +USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF +DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD +PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), +EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF +SUCH DAMAGES. + + 17. Interpretation of Sections 15 and 16. + + If the disclaimer of warranty and limitation of liability provided +above cannot be given local legal effect according to their terms, +reviewing courts shall apply local law that most closely approximates +an absolute waiver of all civil liability in connection with the +Program, unless a warranty or assumption of liability accompanies a +copy of the Program in return for a fee. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +state the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + + Copyright (C) + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU Affero General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Affero General Public License for more details. + + You should have received a copy of the GNU Affero General Public License + along with this program. If not, see . + +Also add information on how to contact you by electronic and paper mail. + + If your software can interact with users remotely through a computer +network, you should also make sure that it provides a way for users to +get its source. For example, if your program is a web application, its +interface could display a "Source" link that leads users to an archive +of the code. There are many ways you could offer source, and different +solutions will be better for different programs; see section 13 for the +specific requirements. + + You should also get your employer (if you work as a programmer) or school, +if any, to sign a "copyright disclaimer" for the program, if necessary. +For more information on this, and how to apply and follow the GNU AGPL, see +. diff --git a/NOTICE b/NOTICE new file mode 100644 index 0000000..2239a76 --- /dev/null +++ b/NOTICE @@ -0,0 +1,39 @@ +DorkNet — Rec Room Private Server +Copyright (C) 2026 Alexa + +This program is free software: you can redistribute it and/or modify it +under the terms of the GNU Affero General Public License as published +by the Free Software Foundation, version 3 of the License. + +This program is distributed in the hope that it will be useful, but +WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +Affero General Public License for more details. + +You should have received a copy of the GNU Affero General Public +License along with this program. If not, see . + +──────────────────────────────────────────────────────────────────────── +Attribution requirement +──────────────────────────────────────────────────────────────────────── + +If you fork, modify, host, or otherwise redistribute DorkNet — including +running a publicly-accessible instance accessed over a network as +covered by AGPL §13 — you must: + + 1. Make your full source code (including any modifications) available + to all users who interact with the running instance, under the + same AGPL-3.0 license. + + 2. Preserve this NOTICE file in your distribution. + + 3. Credit the upstream project visibly. At minimum: + "Based on DorkNet (https://github.com/Alexa-RR/Dorknet) by Alexa, + licensed AGPL-3.0." + Acceptable placements: README, server console banner, in-game + credits screen, or web-facing footer of any deployment dashboard. + + 4. Indicate in your repository / release notes which substantive + changes you have made relative to the upstream version. + +The full text of the AGPL-3.0 license is in LICENSE. diff --git a/README.md b/README.md new file mode 100644 index 0000000..a1d6b93 --- /dev/null +++ b/README.md @@ -0,0 +1,148 @@ +# DorkNet — `december-2020-12-18` + +This is the **December 2020.12.18** branch of DorkNet — a self-hostable +reimplementation of the Rec Room backend tuned to the wire protocol of +the **Rec Room 2020.12.18** client build. + +> ⚠️ **Not affiliated with Rec Room Inc. or Against Gravity.** +> See [DISCLAIMER](../../blob/main/DISCLAIMER.md). DorkNet ships no Rec +> Room game assets or modified binaries — you supply your own +> legally-acquired Rec Room 2020.12.18 install. + +If you're not sure which DorkNet branch you want, start at the +[main branch's chart](../../blob/main/BRANCHES.md). The launcher on +main also auto-detects your install. + +--- + +## What this branch targets + +| | | +|---|---| +| **Client build** | Rec Room 2020.12.18 | +| **Version key** (sent as `X-DorkNet-Version` header) | `december_2020_12_18` | +| **Version plugin** | `DorkNet.Server/Versions/Late2020/Late2020VersionPlugin.cs` | +| **Schema** | One EF `Initial` migration; SQLite + Postgres both work | +| **Diverges from `march-2020-03-10` by** | ~170 files | + +Subsystems present on this branch that the March branch doesn't have: + +- Chat threads + group DMs (`ChatThreads`, `ChatThreadMembers`) +- Clubs surface (`clubs.{apex}/*` — announcements, categories, subscriptions) +- Playlists (curated + user-created) +- Weekly challenges +- Room keys (purchasable room access) +- Room roles (per-room co-owner / moderator / host grants) +- Leaderboard channel metadata +- Loading-screen tips + +--- + +## Quickstart + +**1. Install .NET 9 SDK** and (optionally) `dotnet-ef`: + +```pwsh +dotnet tool install --global dotnet-ef +``` + +**2. Clone this branch:** + +```pwsh +git clone --branch december-2020-12-18 https://github.com/DorkSquadRR/DorkNet +cd DorkNet +``` + +**3. Configure secrets.** Drop in `DorkNet.Server/appsettings.Local.json` +(gitignored) or export environment variables: + +| Config key | Env var | Required | Notes | +|---|---|---|---| +| `Jwt:Secret` | `Jwt__Secret` | yes | ≥64 random chars | +| `Photon:AppId` | `Photon__AppId` | yes | Photon Realtime AppId (free at dashboard.photonengine.com) | +| `Photon:VoiceAppId` | `Photon__VoiceAppId` | no | only if voice chat | +| `Photon:CloudRegion` | `Photon__CloudRegion` | no | defaults to `us` | +| `Database:Provider` | `Database__Provider` | no | `sqlite` (default) or `postgres` | +| `ConnectionStrings:Default` | `ConnectionStrings__Default` | postgres-only | full Npgsql connection string | +| `ConnectionStrings:Redis` | `ConnectionStrings__Redis` | no | enables SignalR backplane | +| `Domain:Apex` | `DORKNET_DOMAIN` | no | defaults to `localhost`; set for production | + +**4. Boot:** + +```pwsh +dotnet run --project DorkNet.Server +``` + +First boot creates `bin/Debug/net9.0/data/dorknet.db`, runs every +`LegacyUpgrades` pass, seeds canonical rooms / store catalog / club +categories / playlists, and starts listening. You should see +`Application started.` in the log; `curl http://localhost:8080/healthz` +returns 200. + +For a full Docker / VPS deploy, see +[main's Advanced setup guide](../../blob/main/docs/advanced-setup.md). + +--- + +## Patching your client + +```pwsh +.\tools\install-plugin.ps1 ` + -RecRoomPath "C:\Path\To\Recroom_Release_Data" ` + -PhotonAppId "" ` + -PhotonVoiceAppId "" +``` + +This installs the BepInEx 6 plugin into the client's `BepInEx/plugins/` +and writes its config under `BepInEx/config/`. The plugin rewrites all +`*.rec.net` URIs to your configured DorkNet apex, swaps the Photon +AppIds, and bypasses TLS verification on the client's HTTPS calls so +self-signed certs work. + +--- + +## Code map + +| Folder | Purpose | +|---|---| +| `DorkNet.Server/` | ASP.NET Core backend (controllers, services, data, hubs, middleware) | +| `DorkNet.Models/` | DTOs shared between server and client mod | +| `DorkNet.ClientPatch/` | BepInEx IL2CPP plugin (production patcher) | +| `DorkNet.ClientMod/` | MelonLoader port (alternative patcher) | +| `tools/` | Installers (`install-plugin.ps1`, `install-melon.ps1`, `install-legacy-client.ps1`, `remove-eac.ps1`), Cloudflare tunnel templates | + +See [docs/architecture.md](docs/architecture.md) for the full mental +model (project layout, request lifecycle, watch-mirror controller +pattern, where to start contributing). + +--- + +## Database & migrations + +One EF migration (`Migrations/_Initial.cs`) captures the +schema. SQLite dev DBs apply it via `Database.Migrate()`; Postgres uses +`EnsureCreated()` behind a transaction-scoped advisory lock. The +entity model is the single source of truth — to add a column, edit the +entity, boot, done (on SQLite — Postgres prod needs a migration). + +Data transforms that can't be schema (seed renames, computed backfills) +go in [`DorkNet.Server/Data/LegacyUpgrades.cs`](DorkNet.Server/Data/LegacyUpgrades.cs). +See [`DorkNet.Server/Data/MIGRATIONS.md`](DorkNet.Server/Data/MIGRATIONS.md) +for the full discipline. + +--- + +## Contributing + +See [main's CONTRIBUTING guide](../../blob/main/CONTRIBUTING.md) for +the project-wide rules. + +Branch-specific: schema changes on this branch don't flow to +`march-2020-03-10` automatically. If a fix applies to both, cherry-pick +it across. Don't rebase across branches. + +--- + +## License + +[AGPL-3.0](LICENSE). Hosted forks must publish their source. diff --git a/docs/architecture.md b/docs/architecture.md new file mode 100644 index 0000000..b0105e0 --- /dev/null +++ b/docs/architecture.md @@ -0,0 +1,249 @@ +# Architecture + +A mental model of the DorkNet codebase for new contributors. Read this +before you touch code; it explains layout, request flow, the design +patterns that look weird in isolation, and where to start. + +For deployment, see [`deploy.md`](deploy.md). For wire-protocol +reverse-engineering deliverables, see the `recroom-2020-client-*` +files in this folder. + +--- + +## Projects in the solution + +``` +DorkNet.sln +├── DorkNet.Server ASP.NET Core 9 backend — the bulk of the code +├── DorkNet.Models Shared DTO + serialization types +├── DorkNet.ClientMod MelonLoader plugin (portable / experimental) +└── DorkNet.ClientPatch BepInEx 6 IL2CPP plugin (production) +``` + +| Project | Responsibility | Target | +|---|---|---| +| `DorkNet.Server` | REST API, SignalR hub, admin SPA host, static-site host, image CDN, matchmaking, persistence | .NET 9 | +| `DorkNet.Models` | DTOs the wire protocol speaks. No project dependencies. | .NET 9 | +| `DorkNet.ClientMod` | MelonLoader 0.6.x port — same patches as ClientPatch, JSON config | .NET 6 | +| `DorkNet.ClientPatch` | BepInEx 6 IL2CPP plugin — the standard client patcher, TOML config | .NET 6 | + +`ClientMod` and `ClientPatch` are intentional twins: both apply the +same patches (URI rewrite `.rec.net` → configured apex, Photon AppId +override, TLS trust bypass, auth injection) against different mod +loaders. ClientPatch is the supported deployment; ClientMod exists for +contributors who already have MelonLoader installed and don't want to +swap loaders. + +--- + +## Server folder layout + +``` +DorkNet.Server/ +├── Auth/ JWT config, IP-ban + player-ban middleware, AdminOnly attribute +├── Compat/ Version detection middleware, client-version registry, HttpContext extensions +├── Controllers/ REST endpoints (see below) +├── Data/ DbContext, Entities/, EF migrations, LegacyUpgrades, seed JSON +├── Hubs/ SignalR NotifyHub (real-time presence on notify.{apex}/hub/v1) +├── Protos/ Protobuf schemas — trimmed `dorknet_room_data.proto` + full decompiled `recroom_2020.proto` +├── Services/ ~27 business-logic services (PlayerService, RoomService, etc.) +├── Versions/ Per-version plugins; Late2020VersionPlugin marks the 2020.12.18 build +├── admin-ui/ Vite + React SPA source — builds to wwwroot/admin/ +├── site/ Public website Vite SPA source — builds to wwwroot/site/ +├── wwwroot/ Static file root, with per-host subdirs (admin/, site/, feed/) +└── Program.cs Host + DI + middleware pipeline (split candidates noted at the bottom) +``` + +### Controllers/ groupings + +20 top-level groups, each responsible for one product surface the +watch (game client) talks to. The grouping mirrors the watch's URL +layout, which is why some folders are large — splitting them would +obscure the mirror relationship. + +| Folder | Surface | +|---|---| +| `API/` | Main REST API split by subsystem (Avatar, Store, Rooms, Messages, Inventions, Leaderboard, BugReporting, Moderation, …) | +| `Accounts/` | Account creation, login, profile updates | +| `Admin/` | Admin SPA backend, gated by `AdminOnlyAttribute` | +| `Auth/` | Photon custom-auth (`/photon/customauth`), version-check `/api/versioncheck/v4` | +| `Cdn/` | Image transforms, room blob serving | +| `Clubs/` | `clubs.{apex}/*` — clubs surface | +| `Commerce/` | `commerce.{apex}/*` — monetization catalog | +| `Discovery/` | `discovery.{apex}/*` — room discovery + search | +| `Econ/` | `econ.{apex}/*` — currency + inventory | +| `Geo/` | `geo.{apex}/*` — region lookup | +| `Health/` | `/healthz` probe | +| `Match/` | Matchmaking + `/goto/*` flow | +| `Notify/` | REST-side companion to the SignalR hub | +| `Ns/` | `ns.{apex}/*` — service-URL registry the watch queries on startup | +| `PlayerSettings/`, `Rooms/`, `Site/`, `Storage/`, `Strings/` | Smaller surfaces with self-explanatory scope | + +--- + +## Request lifecycle + +Middleware ordering matters in ASP.NET Core; the pipeline order in +`Program.cs` is below. Numbers are the order, not line numbers. + +1. **Logging** — Serilog enriches every request with structured fields. +2. **HTTPS redirect** — Only when Kestrel has an HTTPS endpoint (direct + mode); a no-op behind nginx/cloudflared. +3. **WebSocket config** — 30-second keep-alive pings to keep + cloudflared from killing idle tunnels. +4. **Per-host static branches** (`MountStaticHost`) — `admin.{apex}`, + apex, `feed.{apex}` each serve their own SPA from `wwwroot/`. + Mounted with `UseWhen` (not `MapWhen`) so API calls under those + subdomains still reach the controllers. +5. **IP-ban check** — earliest filter; rejects banned IPs before any + work runs. +6. **Version detection** (`VersionDetectionMiddleware`) — reads + `X-DorkNet-Version`, validates against the version registry, + returns 426 on mismatch. Apex + admin/site/feed subdomains skip + the gate (humans on browsers don't carry a client version). +7. **JWT authentication** — `UseAuthentication`. +8. **Authorization** — role/policy checks. +9. **Player-ban check** (`BanCheckMiddleware`) — after auth so it can + see `ctx.User`. +10. **MapControllers** — route to controller actions. +11. **SignalR hub** — `NotifyHub` on `notify.{apex}/hub/v1`, WebSocket-only, + `RequireHost` filter. + +--- + +## Multi-tenancy + the domain config + +DorkNet is built to run many concurrent players. Every feature assumes +multi-user semantics, not single-player sandbox. There's no global +"current player"; everything threads `playerId` through the request. + +The deployment apex (default `localhost`, overridable via +`Domain:Apex` config or `DORKNET_DOMAIN` env var) is read once at +startup into the `DomainConfig` singleton. Inject it anywhere code +needs to build a subdomain URL — never hardcode `localhost`. + +```csharp +public sealed class Foo(DomainConfig domain) { + public string Url() => $"https://cdn.{domain.Apex}/..."; +} +``` + +Per-controller `[Host("api.rec.net", "api.localhost")]` filters are +**gone**; allowed hosts are derived from `{apex}` + `*.{apex}` and +enforced by `HostFilteringMiddleware`. Subdomain-discriminating +handlers branch on `Request.Host.Host.Split('.')[0]`. + +--- + +## Version plugins + +`Versions/` holds an `IVersionPlugin` per client generation we support. +Today there's one (`Late2020VersionPlugin` for the 2020.12.18 build). + +The pattern is a marker registry: + +- `VersionKeys` — the wire-format identifiers a plugin claims. +- `Generation` — human-readable name controllers branch on via + `ctx.GetClientVersion().Generation`. +- `RegisterStrategies(IServiceCollection)` — for plugins that ship + generation-specific implementations (today empty; will grow as + wire shapes diverge across builds). + +When a new client build is wire-compatible with an existing +generation, just add its key to `VersionKeys`. When it diverges, add +a new plugin and register strategy bindings keyed by generation. + +--- + +## Reverse-engineering provenance + +DorkNet is a clean-room reimplementation of the 2020.12.18 Rec Room +backend protocol. ~46 files carry `Cpp2IL` provenance comments tying +specific code to decompiled client internals. Example: + +```csharp +// Wire shape per Cpp2IL_ISIL/.../PLILLKHMNDA.txt deserializer: +// gap-skipping enum, not 0..N +public const int Member = 10, Moderator = 20, CoOwner = 30, Creator = 100; +``` + +The decompiled dumps themselves are not in this repo (`dist/RecRoom-2020.12.18-isil/` +locally, gitignored). The comments are the bridge — if you're writing +or fixing wire-shape code, always check the decompiled source first +rather than guessing from symptoms. + +The `docs/recroom-2020-client-*` files are an exhaustive catalog of +client-side endpoints, DTOs, and request expectations extracted from +the same decompile. They're the ground truth when a controller needs +a new endpoint. + +--- + +## Database story + +Two providers, switched by `Database:Provider` config: + +- **SQLite** — default for local dev. File at `bin//data/dorknet.db`. +- **Postgres** — production. Connection string from `ConnectionStrings:Default`. + +Schema lives in `Data/Entities/*` and `Data/DorkNetDbContext.cs`. EF +migrations under `Data/Migrations/` apply via `Database.Migrate()` on +boot. Data transforms that can't be expressed as schema migrations +(seed renames, computed backfills) go in `Data/LegacyUpgrades.cs` as +idempotent methods registered in `RunAsync()`. See +[`Data/MIGRATIONS.md`](../DorkNet.Server/Data/MIGRATIONS.md) for the +discipline: one release = one migration; everything else is +`LegacyUpgrades`. + +--- + +## Where to start + +If you've never touched this codebase, the most approachable path: + +1. Read a small **Service** (`ConfigService`, `DomainConfig`, + `OnlinePresenceService`) to learn the DI pattern. +2. Read a small **Controller** (`Health/HealthController`, + `Strings/StringsController`) to see how services are injected and + responses are shaped. +3. Trace one **end-to-end request** through `Rooms/` or `Players/` — + middleware → controller → service → DbContext → response. The + middleware pipeline (above) is the lens. +4. Look at one **`Cpp2IL`-cited line** in a controller to see how + wire shapes are pinned to decompiled provenance. + +When in doubt: open `DorkNet.Server/Program.cs` and the +`Versions/Late2020/Late2020VersionPlugin.cs` summary doc-comment. +Between them they explain 80% of the cross-cutting decisions. + +--- + +## Acknowledged sharp edges + +These look messy. They're messy on purpose — the alternative is +worse. + +- **Some controllers are large.** `AdminController.cs`, `RoomsController.cs`, + `GoToController.cs` are big because they mirror watch surfaces that + are themselves sprawling. Splitting them by endpoint group would + obscure the surface-mirror relationship that makes the code + navigable when paired with the watch's decompiled call sites. +- **Cpp2IL comments reference paths outside the repo.** That's + intentional — the decompiled source is provenance only, not + shippable. Comments give a future contributor a breadcrumb to + re-derive the wire shape if the decompile changes. +- **`Program.cs` is long.** Most of it is one-time schema patching + (`RunPatchAsync` calls for the Postgres `EnsureCreated` path) that's + pending consolidation per [`Data/MIGRATIONS.md`](../DorkNet.Server/Data/MIGRATIONS.md). + Once that lands, the remaining ~500 lines of middleware + host + setup will be split into `Startup/*` extensions. +- **Two-provider EF.** SQLite and Postgres each have their own + EnsureCreated/Migrate dance because some Postgres-only column types + drift the model snapshot. The split is annoying but lets one + codebase serve both local dev (zero-config SQLite) and production + (managed Postgres). +- **Hardcoded `rec.net` URLs in `RoomService` + `HtrAssetMirrorService`.** + These pull canonical assets (room thumbnails, `.htr` asset data) + from the official Rec Room CDN at seed time. The alternative is + shipping the assets in the repo, which would put DorkNet on + legally weaker footing. diff --git a/docs/deploy.md b/docs/deploy.md new file mode 100644 index 0000000..ac385ee --- /dev/null +++ b/docs/deploy.md @@ -0,0 +1,287 @@ +# DorkNet — Coolify deployment + +This doc covers the production cutover from the local-machine setup +(SQLite + Kestrel direct + mkcert + per-machine cloudflared) to the +horizontally-scaled Coolify deployment (Postgres + Redis + Garage S3 + +2-replica Kestrel behind cloudflared, all on the same Coolify project). + +The plan-of-record is `BUILD_PROPER_PLAN.md` at the repo root and the +phased writeup at `~/.claude/plans/pahese-0-1-horizontal-cheeky-hickey.md`. +Anything that contradicts this doc, that doc wins. + +--- + +## Coolify project layout + +One Coolify *project* with these services: + +| Service | Image / source | Notes | +| -------------------- | ---------------------------------------------- | ---------------------------------------------------------------------------- | +| `dorknet-server` | Build from this repo's `Dockerfile` | Replicas: **2** to start. Internal port 8080. No public domain. | +| `dorknet-postgres` | Coolify one-click **Postgres** | Persistent volume on Coolify's data store. | +| `dorknet-redis` | Coolify one-click **Redis** | Persistent volume optional; used for ephemeral state + SignalR backplane. | +| `dorknet-garage` | Coolify one-click **Garage** | S3-compatible. Persistent volume (room blobs are 100s of MB). | +| `dorknet-cloudflared`| Cloudflare's `cloudflared` image, custom config | Reads `tools/cloudflared-config.coolify.yml.template` rendered with a tunnel ID. | + +All five sit on the same project network so the server reaches its +dependencies over Coolify's internal Docker DNS. + +## Required environment variables on `dorknet-server` + +Set these in Coolify's *Environment Variables* tab. Coolify expands +`${...}` references to other services in the same project, so the +connection-string lookups link automatically. + +``` +ASPNETCORE_ENVIRONMENT=Production + +# Database +ConnectionStrings__Default=Host=dorknet-postgres;Port=5432;Database=dorknet;Username=${POSTGRES_USER};Password=${POSTGRES_PASSWORD} + +# Redis +ConnectionStrings__Redis=dorknet-redis:6379 + +# JWT +DORKNET_JWT_SECRET= + +# Object storage (Garage). Bucket names are hard-coded in code; only +# the endpoint + credentials need configuring. Objects are organized +# under owner-scoped keys inside each bucket, e.g. +# players/{playerId}/camera, players/{playerId}/profile, +# players/{playerId}/rooms/{roomId}, players/{playerId}/dorm. +S3__Endpoint=http://dorknet-garage:3900 +S3__Region=garage +S3__AccessKey= +S3__SecretKey= + +# Image CDN signing. The 2020 client verifies Content-Signature on +# image downloads. Use the private half of the key whose public modulus +# the client knows for p1. Store PEM newlines as \n if your env UI is +# single-line, or use DORKNET_IMAGE_SIGNING_PRIVATE_KEY_BASE64. +DORKNET_IMAGE_SIGNING_KEY_ID=p1 +DORKNET_IMAGE_SIGNING_PRIVATE_KEY= + +# Photon (copy from local appsettings.json — same AppId across envs) +Photon__AppId= +Photon__VoiceAppId=1917ff4a-b7ad-4ad6-9ea2-9642bc174b70 +Photon__CloudRegion=us +``` + +### Cloudflare R2 instead of Garage + +DorkNet can use Cloudflare R2 without code changes because R2 speaks the +S3-compatible API. Create these three R2 buckets first: + +``` +profile-images +camera-photos +room-blobs +``` + +Then create an R2 API token with Object Read & Write access to both +buckets and set the server env vars: + +``` +S3__Endpoint=https://.r2.cloudflarestorage.com +S3__Region=auto +S3__AccessKey= +S3__SecretKey= +S3__MaxErrorRetry=1 +S3__TimeoutSeconds=8 +``` + +Do not use an R2 public bucket URL or custom domain for `S3__Endpoint`. +That value must be the S3 API endpoint ending in +`.r2.cloudflarestorage.com`. +DorkNet disables S3 chunked upload encoding in code because R2 rejects +`STREAMING-AWS4-HMAC-SHA256-PAYLOAD` request bodies. + +`appsettings.Production.json` already pins `Database:Provider=postgres` +and binds Kestrel to `http://0.0.0.0:8080`, so those don't need to be +set as env vars. + +## First-time cutover (stop-the-world) + +1. **Drain the local server.** Tell players, take it down. The cutover + is non-incremental — once the SQLite source is read, no further + writes can be accepted there. + +2. **Snapshot SQLite.** `tools/snapshot-db.ps1` writes a timestamped + copy. Keep it indefinitely as the pre-cutover baseline. + +3. **Deploy the Coolify project** with the four services above + (Postgres / Redis / Garage / cloudflared) but the server replicas + set to 0 — we want the database empty and idle while the migrator + runs. + +4. **Run the migrator** from the operator's laptop, pointed at the + newly-provisioned Postgres + Garage: + + ```pwsh + dotnet run --project Tools/MigrateSqliteToPostgres -- ` + --sqlite ./snapshots/dorknet-pre-cutover.db ` + --postgres "Host=;Port=5432;Database=dorknet;Username=...;Password=...;SSL Mode=Require" ` + --reset ` + --upload-blobs ` + --s3-endpoint http://:3900 ` + --s3-bucket room-blobs ` + --s3-access-key ... ` + --s3-secret-key ... + ``` + + The migrator runs the 9-step verification + an optional 10th step + that bulk-uploads every `RoomDataBlobs.Bytes` row to S3, idempotent + on re-run. A non-zero exit means **abort**: leave the destination + in its dirty state, fix the diff, re-run with `--reset` for a clean + slate. + +5. **Scale `dorknet-server` to 2 replicas.** First boot: one replica + wins the `pg_advisory_xact_lock` and runs `EnsureCreated` (no-op, + schema already there from the migrator) + the seed steps; the other + blocks on the lock until the first commits, sees the schema is + complete, and proceeds. + +6. **Verify `/healthz` is 200** on both replicas (Coolify shows this + in the Container tab) before pointing the tunnel at the service. + +7. **Bring up `dorknet-cloudflared`** with the rendered config (see + `tools/cloudflared-config.coolify.yml.template`). Confirm `dig` / + `curl` against `https://api.localhost/api/health/v1` returns 200. + +8. **Tell players it's back.** Done. + +## Notes on multi-replica behaviour + +* **Sticky sessions are no longer required.** Auth's + `OrphanAccountTracker` writes the pending claim to Redis, so a + player whose two HTTP requests hit different replicas still sees + the same account-creation state. + +* **SignalR groups fan out across replicas.** The Redis backplane is + registered under the channel prefix `dorknet-signalr`; both replicas + publish/subscribe to the same channels, so a notification raised on + replica B reaches a player connected to replica A. + +* **Player presence is shared.** `PlayerPresenceService` writes + `presence:player:{id}` keys to Redis with a 45 s TTL (3× heartbeat). + Either replica can answer "where is player N?". + +* **Game session ids are allocated by Postgres.** `GameSessionService` + uses a `bigserial` so two replicas can `JoinOrCreate` simultaneously + without colliding. + +* **Private instances live in Postgres.** Cross-replica visibility is + automatic — both replicas read from the same `PrivateInstances` + table. + +## Rollback + +If the cutover goes sideways before step 5 (no replicas running yet), +just delete the Postgres / Redis / Garage volumes in Coolify, set the +server replica count back to 0, and bring the local SQLite server back +up. The pre-cutover snapshot is still untouched. + +After step 5, rolling back means **restoring the SQLite snapshot** +(any data players wrote on Postgres post-cutover will be lost). Be +sure the cutover validation in step 6 is convincing before you tell +players it's safe. + +## Photon Cloud — own AppId + CustomAuth callback + +The hard-coded RecRoom Photon AppId rejects external clients (NameServer +error code 32736 / "No auth request during expected wait time") because +their dashboard has CustomAuth set to "Reject if Auth Failed" pointing +at RecRoom's own backend. Solution: register your own free AppId. + +1. Go to https://dashboard.photonengine.com → sign up → **Create a New App** + → "Photon Realtime" → save. Repeat for "Photon Voice" if you want voice. +2. Copy each **AppId** (UUID) and set in Coolify env vars: + ``` + Photon__AppId= + Photon__VoiceAppId= + Photon__CloudRegion=us + ``` +3. Restart the server. + +That alone gets multiplayer working — the Photon NameServer accepts the +client because no CustomAuth is required. + +### (Optional) wire up server-side single-session enforcement + +Server already exposes `https://auth.localhost/photon/customauth` — see +[PhotonCustomAuthController.cs](DorkNet.Server/Controllers/Auth/PhotonCustomAuthController.cs). +It accepts `userid` + `LoginLock` and validates them against +`PlayerPresenceService` (Redis). Enable it in the dashboard: + +1. Photon dashboard → your app → **Manage** → **Custom Authentication** tab. +2. **Enable Custom Authentication**. +3. URL: `https://auth.localhost/photon/customauth?userid={userid}&LoginLock={LoginLock}` +4. **Reject if Auth Failed**: **OFF for now** (see caveat below). +5. Save. + +**Caveat — current 2020 watch doesn't send AuthValues**. A sweep of +`Cpp2IL_ISIL/IsilDump/Assembly-CSharp/` turns up no caller of +`PhotonNetwork.set_AuthValues` outside Photon's own library code, so +the unmodified client connects without supplying `{userid, LoginLock}`. +With "Reject if Auth Failed" ON, every Photon connect would fail with +NameServer 32736 — same symptom we tried to solve. With it OFF, the +client connects fine, the endpoint exists for future client patching. + +### Patching the watch to send AuthValues — `DorkNet.ClientPatch/` + +The vanilla 2020 watch never sets `PhotonNetwork.AuthValues` (verified +by an exhaustive grep across both `Assembly-CSharp.dll` and the +Photon `Assembly-CSharp-firstpass.dll` library). To make Photon +forward `userid` + `LoginLock` to our `/photon/customauth` endpoint we +ship a **BepInEx 6 IL2CPP plugin** that patches +`NetworkingPeer.OpAuthenticate` and attaches the values in flight. + +Source + build instructions: [DorkNet.ClientPatch/README.md](../DorkNet.ClientPatch/README.md). + +End-to-end install on a tester's machine: + +```pwsh +.\tools\patch-client.ps1 ` + -RecRoomPath "C:\…\RecRoom\Recroom_Release_Data" ` + -PhotonAppId cb0880d9-… ` + -InstallBepInEx "C:\Downloads\BepInEx_unix_x86_64_6.0.0-be.696_…zip" +``` + +The patcher will: + +1. Do its usual asset/hosts/cert work. +2. Extract BepInEx into the client folder (skipped if already + present). +3. If `BepInEx/interop/` exists, build + `DorkNet.ClientPatch.dll` against it and drop it into + `BepInEx/plugins/`. +4. If `interop/` doesn't exist yet (BepInEx never launched), prompt + the operator to launch the game once so BepInEx generates interop + assemblies, then re-run the patcher with the same `-InstallBepInEx` + path to finish step 3. + +Once the plugin is in production: + +1. Flip [`PhotonCustomAuthController`](../DorkNet.Server/Controllers/Auth/PhotonCustomAuthController.cs) + from permissive-mode (`ResultCode 1` always) back to strict — + parse `userid` + `LoginLock`, validate against + `PlayerPresenceService.ValidateLock`, return `ResultCode 3` on + mismatch. +2. In the Photon dashboard, flip **"Reject if Auth Failed"** ON. +3. Duplicate-account login from a different LoginLock now fails + Photon auth at the NameServer — never reaches the dorm. + +## Future schema changes + +PR 4 keeps `EnsureCreated` for the first deploy (the migrator builds +the schema directly, then EnsureCreated is a no-op the server can do +under advisory-lock to be safe on multi-replica boot). Any +**post-cutover** schema change needs a real Postgres migration: + +1. `dotnet ef migrations add --project DorkNet.Server` +2. Replace the `EnsureCreated` call in `Program.cs` with + `db.Database.Migrate()` *inside* the same advisory-lock block. +3. Deploy. Both replicas race for the lock, the winner runs the + migration, the loser sees the new history row and skips. + +Until that swap happens, treat the schema as frozen. diff --git a/docs/recroom-2020-03-request-literals.csv b/docs/recroom-2020-03-request-literals.csv new file mode 100644 index 0000000..f51b400 --- /dev/null +++ b/docs/recroom-2020-03-request-literals.csv @@ -0,0 +1,88 @@ +"Family","Index","Literal" +"bootstrap-account","",".rec.net" +"bootstrap-account","","https://ns.rec.net/?v=2" +"bootstrap-account","","https://rec.net" +"bootstrap-account","","https://rec.net/password/recover" +"config","","/config/{0}" +"config","","/configuration/system.runtime.remoting" +"config","","api/config/" +"inventions","","api/inventions/" +"inventions","","api/inventions/v3/addversion" +"inventions","","api/inventions/v3/save" +"misc","","{0}v1/bulkignoreplatformusers" +"misc","","{0}v1/bulkInvite" +"misc","","{0}v1/consume" +"misc","","{0}v2/gifts/consume/" +"misc","","/room/" +"misc","","/room/{0}" +"misc","","account/{0}" +"misc","","account/{0}/bio" +"misc","","account/bulk" +"misc","","account/bulk?" +"misc","","account/bulk/" +"misc","","account/create" +"misc","","account/me" +"misc","","account/me/" +"misc","","account/me/changepassword" +"misc","","account/me/haspassword" +"misc","","account/recoverpassword" +"misc","","account/search?name=" +"misc","","Activities/Dormroom/Scenes/holoHelperRecordingStudio" +"misc","","api/activities/charades/v1/words" +"misc","","api/announcement/v1/get" +"misc","","api/avatar/" +"misc","","api/bugreporting/" +"misc","","api/catalog/v1/all?onlyAvailableSkus=true" +"misc","","api/challenge/" +"misc","","api/checklist/" +"misc","","api/communityboard/" +"misc","","api/consumables/" +"misc","","api/equipment/" +"misc","","api/gameconfigs/" +"misc","","api/groups/" +"misc","","api/images/" +"misc","","api/messages/" +"misc","","api/messages/v1/IOSClearDeviceToken" +"misc","","api/messages/v1/IOSModifyNotificationPreferences" +"misc","","api/messages/v1/IOSResetNotificationPreferencesBadgeCount" +"misc","","api/messages/v1/IOSSaveDeviceToken" +"misc","","api/messages/v3/delete" +"misc","","api/objectives/" +"misc","","api/PlayerCheer/" +"misc","","api/PlayerElo/" +"misc","","api/playerevents/" +"misc","","api/PlayerReporting/" +"misc","","api/PlayersBanned/" +"misc","","api/purchase/v1/cancelpurchase" +"misc","","api/purchase/v1/cleanuppending" +"misc","","api/purchase/v1/completepurchase" +"misc","","api/purchase/v1/initiatepurchase" +"misc","","api/purchase/v1/processpurchase" +"misc","","api/quickPlay/" +"misc","","api/relationships/" +"misc","","api/royale/" +"misc","","api/sanitize/" +"misc","","api/sanitize/v1" +"misc","","api/settings/" +"misc","","api/storefronts/" +"misc","","api/testcasemanagement/" +"misc","","api/versioncheck/v4?v={0}&p={1}" +"misc","","goto/event/{0}" +"misc","","goto/instance/{0}" +"misc","","goto/invite/{0}" +"misc","","goto/player/{0}" +"misc","","goto/room/" +"misc","","https://www.instagram.com/recroom/" +"misc","","room/{0}/instances" +"players","","/api/playerReputation/v1/{0}" +"players","","/api/playerReputation/v1/bulk" +"players","","/api/players/v1/progression/{0}" +"players","","/api/players/v1/progression/bulk" +"players","","api/players/v2/objectives" +"players","","api/playersubscriptions/" +"rooms","","api/rooms/" +"rooms","","api/rooms/v1/roomRolePermissions" +"rooms","","api/rooms/v4/saveData" +"storage-cdn","","{0}v1/datahistory/{1}" +"storage-cdn","","{0}v1/datahistory/restore" +"storage-cdn","","/data/{0}" \ No newline at end of file diff --git a/docs/recroom-2020-client-api-sweep.md b/docs/recroom-2020-client-api-sweep.md new file mode 100644 index 0000000..945f6f6 --- /dev/null +++ b/docs/recroom-2020-client-api-sweep.md @@ -0,0 +1,345 @@ +# Rec Room 2020 Client RecNet API Sweep + +This is a first-pass audit of the decompiled March and December 2020 client +material, focused on RecNet URLs, API route fragments, request wrappers, and +response DTO shapes. + +## Sources + +- December C# declarations: + `dist/RecRoom-2020.12.18-dump/DiffableCs` +- December native/ISIL body dump: + `dist/RecRoom-2020.12.18-isil/IsilDump` +- March archive: + `C:/Users/you/Documents/Recnet-archive/decompiled` + +The December readable C# dump preserves class names, fields, enum members, and +method signatures, but most method bodies are empty. For actual URL strings and +call context, the ISIL dump is the useful source. The March archive exposes a +large `Il2CppDump/stringliteral.json`; that is best for literal comparison, but +it does not always preserve call-site context. + +Generated companion files: + +- `docs/recroom-2020-client-endpoint-inventory.csv`: raw endpoint/string + inventory with build, type, method, file, line, and literal string. +- `docs/recroom-2020-client-dto-inventory.md`: public field/property inventory + for the December `Assembly-CSharp/RecNet` DTO namespace. + +## Central RecNet Stack + +The central December network wrapper is the obfuscated global class +`BPHGKAEDBPE`. + +Important declarations found in +`dist/RecRoom-2020.12.18-dump/DiffableCs/Assembly-CSharp/BPHGKAEDBPE.cs`: + +- `NameServerResponse` fields: `RecNetStatus`, `Auth`, `API`, `WWW`, + `Notifications`, `Images`, `CDN`, `Commerce`, `Matchmaking`, `Storage`, + `Chat`, `Leaderboard`, `Accounts`, `Link`, `RoomComments`, `Clubs`, `Rooms`. +- request queue item `IPJHBGCCNGH`: stores `HTTPMethods`, service enum, + relative URI, retry flag, request configuration callback, and a promise for + `HOOILIHKMEG`. +- public wrapper methods cover GET/POST-style calls with JSON strings, typed + request objects, URL encoded forms, multipart forms, and arbitrary request + configuration callbacks. + +The service enum is `LDCMJEDOFCO`: + +- `Auth` +- `API` +- `Commerce` +- `Matchmaking` +- `Notifications` +- `Images` +- `CDN` +- `Storage` +- `Chat` +- `Leaderboard` +- `Accounts` +- `Link` +- `RoomComments` +- `Clubs` +- `Rooms` + +The March literal table includes the bootstrap URL +`https://ns.rec.net/?v=2`. The December ISIL confirms the same name-server +shape through `BPHGKAEDBPE.NameServerResponse`. + +## Confirmed Route Families + +These route families were found in the December ISIL dump. Many are assembled +from multiple adjacent string literals, for example a base like +`api/inventions/` plus a format string like +`{0}v1/details?inventionId={1}`. + +### Account, Login, And Bootstrap + +- `https://ns.rec.net/?v=2` in March literals. +- `https://rec.net/password/recover` in March literals. +- `.rec.net` host suffix in March literals. +- `account/bulk`, `account/bulk?`, and `account/bulk/` in December ISIL. +- RecNet connectivity test string: + `http://www.google.com/generate_204`. + +### Player State + +- `api/players/v2/objectives` +- `/api/players/v1/progression/{0}` +- `/api/players/v1/progression/bulk` +- `/api/playerReputation/v1/{0}` +- `/api/playerReputation/v1/bulk` +- `api/avatar/v1/lockeditems?` + +The response/request DTO coverage here is outside the small `RecNet` folder in +many cases, so the raw CSV should be used with `rg` follow-up on the owning +obfuscated class names. + +### Rooms + +- `rooms/{0}` +- `rooms/bulk` +- `rooms/base` +- `rooms/createdby/me` +- `rooms/ownedby/me` +- `rooms/moderatedby/me` +- `rooms/cheeredby/me` +- `rooms/favoritedby/me` +- `rooms/visitedby/me` +- `rooms/createdby/{0}` +- `rooms/rro_ids` +- `rooms/{0}/subrooms/{1}/datahistory` +- `rooms/{0}/roles` +- `rooms/{0}/roles/{1}` +- `rooms/recommendations` +- `rooms/search` +- `rooms/hot` +- `featuredrooms/current` +- `rooms/{0}/clone` +- `rooms/{0}/name` +- `rooms/{0}/description` +- `rooms/{0}/image` +- `rooms/{0}/tags` +- `api/rooms/v1/roomRolePermissions` +- `api/rooms/v4/saveData` +- `api/rooms/v2/report` +- `api/rooms/v1/verifyRole` +- `api/rooms/v1/filters` +- `rooms/{0}/subrooms/{1}/data` + +The DorkNet server already implements many of these surfaces under +`Controllers/API/Rooms/V2`, `Controllers/Rooms`, `Controllers/Storage`, and +`Controllers/Cdn`. + +### Matchmaking + +December `RecNet/Matchmaking` ISIL contains: + +- `goto/room/` +- `room/{0}/instances` + +The client expects matchmaking and goto responses to contain room instance +identity, room/sub-room ids, Photon room ids, privacy/full/in-progress state, +and location-like fields. This matches the DorkNet notes about +`GoToController`, `MatchController`, `GameSessionService`, and +`PrivateInstanceService`. + +### Inventions + +The invention API is one of the clearest route clusters. + +Base: + +- `api/inventions/` + +Fragments: + +- `{0}v1?inventionId={1}` +- `{0}v1/update?inventionId={1}&name={2}` +- `{0}v1/update?inventionId={1}&description={2}` +- `{0}v1/update?inventionId={1}&imgName={2}` +- `{0}v1/update?inventionId={1}&permission={2}` +- `{0}v1/details?inventionId={1}` +- `{0}v1/versions?inventionId={1}` +- `{0}v1/delete?inventionId={1}` +- `{0}v3/publish?inventionId={1}&permissionLevel={2}` +- `{0}v1/unpublish?inventionId={1}` +- `{0}v1/download?inventionId={1}` +- `{0}v1/version?inventionId={1}&version={2}` +- `api/inventions/v4/save` in December +- `api/inventions/v3/addversion` in December +- `api/inventions/v1/fulllineageowner?` +- `/invention/{0}` link route + +March literals show similar routes, but older save/publish strings include: + +- `api/inventions/v3/save` +- `api/inventions/v3/addversion` +- `{0}v2/publish?inventionId={1}&permissionLevel={2}` + +That suggests December moved at least the save path from `v3` to `v4` and the +publish path from `v2` to `v3`. + +Relevant December DTOs: + +- `NewInventionRequestDTO` +- `AddVersionInventionRequestDTO` +- `NewRoomKeyRequestDTO` +- `UpdateRoomKeyRequestDTO` +- `InventionBatchRequest` +- `CheerRequest` +- `UpdatePriceRequest` +- `ReportRequest` + +See `docs/recroom-2020-client-dto-inventory.md` for the field list. + +### Room Keys + +- `api/roomkeys/v1/create` +- `api/roomkeys/v1/update` +- `api/roomkeys/v1/mine` + +Relevant DTOs: + +- `NewRoomKeyRequestDTO` +- `UpdateRoomKeyRequestDTO` + +### Player Events + +- `api/playerevents/v1/deleteResponse` +- `api/playerevents/v1/bulkInvite` +- `api/playerevents/v1/all` +- `api/playerevents/v1/report` +- `api/playerevents/v1/searchlive?` +- `api/playerevents/v1/search?` +- `api/playerevents/v1/tagfilters` + +Relevant DTOs: + +- `GetEventsForClubsRequest` +- `BulkInviteRequest` +- `DeleteResponseRequest` +- `PlayerEventDTOPage` + +### Messages And Notifications + +- `api/messages/v3/delete` +- `api/messages/v1/IOSSaveDeviceToken` +- `api/messages/v1/IOSClearDeviceToken` +- `api/messages/v1/IOSResetNotificationPreferencesBadgeCount` +- `api/messages/v1/IOSModifyNotificationPreferences` + +Relevant DTO: + +- `DeleteMessagesRequestDTO` + +The `NameServerResponse` includes `Notifications` and `Chat` service bases, so +SignalR and chat routes are likely partly constructed outside the RecNet DTO +namespace. + +### Clubs + +- `members/bulk` +- `members/bulk?` +- `api/clubreporting/v1/report` + +The `NameServerResponse` includes `Clubs`, and the DorkNet server has club/event +adjacent models. A second pass should focus on `JDJGIBLMFKK` and related +club/member UI/data classes. + +### Store, Economy, Rewards + +- `api/gamerewards/v1/pending` +- `api/gamerewards/v1/select` +- `/consume` + +The purchase error strings include token support email text, and invention +purchase flows are wired through the invention/store UI. + +### Config And Announcements + +- `api/config/` +- `api/config/v1/freegiftbutton` +- `/config/{0}` +- `api/announcement/v1/get` +- `api/activities/charades/v1/words` + +### User Reporting + +- `https://userreporting.cloud.unity3d.com` +- `https://userreporting.cloud.unity3d.com/api/userreporting/projects/{0}/ping` +- `{0}/api/userreporting` +- `api/PlayerReporting/v1/voteToKickReasons` + +This is partly Unity user reporting, partly RecNet player reporting. + +## Request And Response Shape Notes + +The December `RecNet` C# namespace gives the best directly-readable request DTOs. +Examples: + +- `AddVersionInventionRequestDTO`: `inventionId`, costs, `creationRoomId`, + `inventionDataFilename`, `referencedInventions`. +- `NewInventionRequestDTO`: name/description, costs, room id, data/image names, + and referenced inventions. +- `UpdatePriceRequest`: price-style request object for paid inventions. +- `GetRankRequestDTO`, `GetRanksRequestDTO`, `GetNearbyScoresRequestDTO`, and + `GetLeaderboardRequestDTO`: leaderboard/ranking inputs. +- `BulkInviteRequest`, `DeleteResponseRequest`, `GetEventsForClubsRequest`: + player-event request inputs. +- `DeleteMessagesRequestDTO`: message deletion body. +- `ReportRequest`: generic report payload. + +`Matchmaking.cs` and room model classes contain response model declarations, but +the most important server-facing shape is already known from DorkNet behavior: +room instances must include room instance id, room id, sub-room id, location, +Photon region and room id, capacity/full/private/in-progress flags, and enough +room data URL information to make the client download binary room blobs. + +## March Vs December + +Observed March literals include: + +- `https://ns.rec.net/?v=2` +- `https://rec.net` +- `https://rec.net/password/recover` +- `.rec.net` +- `api/inventions/v3/save` +- `api/inventions/v3/addversion` +- `{0}v2/publish?inventionId={1}&permissionLevel={2}` +- `api/rooms/v4/saveData` +- the same player progression and reputation routes as December. + +Observed December ISIL includes: + +- `api/inventions/v4/save` +- `api/inventions/v3/addversion` +- `{0}v3/publish?inventionId={1}&permissionLevel={2}` +- the larger rooms route cluster listed above. + +The safest server strategy is to support both March and December variants for +invention save/publish, and to keep room save/data endpoints tolerant of both +legacy room API and `api/rooms/v4/saveData`. + +## Important Caveats + +This is not yet a literal function-by-function human annotation of every call +site. The reason is practical: the December C# decompile has declarations but +empty bodies, while the ISIL dump has bodies but obfuscated names and low-level +instructions. The generated CSV gives the call-site line numbers needed for that +next pass. + +For any route that matters to gameplay, verify all three things before changing +DorkNet behavior: + +- the service base selected by `LDCMJEDOFCO`; +- the route string assembled around the literal fragments; +- the DTO or response class used by the promise continuation. + +Best next search commands: + +```powershell +rg -n "api/inventions|rooms/|goto/room|playerevents|roomkeys|messages" dist/RecRoom-2020.12.18-isil/IsilDump/Assembly-CSharp +rg -n "class .*Request|class .*Response|public .*;" dist/RecRoom-2020.12.18-dump/DiffableCs/Assembly-CSharp/RecNet +rg -n "https://ns.rec.net|api/inventions|api/rooms|playerevents|roomkeys" C:/Users/you/Documents/Recnet-archive/decompiled/Il2CppDump/stringliteral.json +``` diff --git a/docs/recroom-2020-client-dto-inventory.md b/docs/recroom-2020-client-dto-inventory.md new file mode 100644 index 0000000..6b4dc13 --- /dev/null +++ b/docs/recroom-2020-client-dto-inventory.md @@ -0,0 +1,278 @@ +# RecNet DTO inventory + +Generated from the December 2020 readable C# dump under `Assembly-CSharp/RecNet`. Methods are frequently empty in this dump, but fields/properties are preserved and describe request/response JSON shapes used around RecNet calls. + +## `AddVersionInventionRequestDTO.cs` + +Declaration: `internal class AddVersionInventionRequestDTO` + +- field `long inventionId` +- field `int instantiationCost` +- field `int lightsCost` +- field `int aiCost` +- field `long creationRoomId` +- field `string inventionDataFilename` +- field `List referencedInventions` + +## `BugReporting.cs` + +Declaration: `private class BugReportDTO` + +- field `string Summary` +- field `string Description` +- field `string TestCaseKey` +- field `string BuildVersion` +- field `long BuildTimestamp` +- field `Nullable BundleVersionCode` +- field `static string BBBLGFHBMGA` + +## `BulkInviteRequest.cs` + +Declaration: `public class BulkInviteRequest` + +- field `long PlayerEventId` +- field `List InvitedPlayerIds` + +## `ChatMessage.cs` + +Declaration: `public class ChatMessage : IFAIJAGLDFK, IEquatable` + +- no public fields/properties captured + +## `CheerRequest.cs` + +Declaration: `public class CheerRequest` + +- field `long InventionId` +- field `bool Cheer` + +## `DeleteMessagesRequestDTO.cs` + +Declaration: `public class DeleteMessagesRequestDTO` + +- field `List MessageIds` + +## `DeleteResponseRequest.cs` + +Declaration: `public class DeleteResponseRequest` + +- field `long PlayerEventId` + +## `Elo.cs` + +Declaration: `public class Elo : MonoBehaviour` + +- field `int Team` +- field `long PlayerId` +- field `int GameScore` +- field `string ActivityLevel` +- field `List PlayerScoreUpdates` +- enum `Activity` +- enum `ActivityLevel` + +## `GetEventsForClubsRequest.cs` + +Declaration: `public class GetEventsForClubsRequest` + +- field `List Id` + +## `GetLeaderboardRequestDTO.cs` + +Declaration: `public class GetLeaderboardRequestDTO` + +- field `MIAJPBPGHOC ObjectiveType` +- field `bool SortAscending` +- field `int Limit` + +## `GetNearbyScoresRequestDTO.cs` + +Declaration: `public class GetNearbyScoresRequestDTO : GetRankRequestDTO` + +- field `int WindowSize` + +## `GetRankRequestDTO.cs` + +Declaration: `public class GetRankRequestDTO` + +- field `int PlayerId` +- field `int StatChannel` +- field `long RoomId` +- field `GHJPLMCGKIH FilterType` +- field `JBGJIODAFBA Timeframe` +- field `bool SortAscending` + +## `GetRanksRequestDTO.cs` + +Declaration: `public class GetRanksRequestDTO : GetRankRequestDTO` + +- field `int RankStart` +- field `int RankEnd` + +## `InventionBatchRequest.cs` + +Declaration: `public class InventionBatchRequest` + +- field `List InventionIds` + +## `KickPlayerDTO.cs` + +Declaration: `public class KickPlayerDTO` + +- field `long GameSessionId` +- field `List PlayerIds` + +## `Matchmaking.cs` + +Declaration: `internal enum GIJOGKJCEKG` + +- field `bool avoidJuniors` +- field `string requestUri` +- field `int accountId` +- field `int playerIdToNotify` +- enum `PublicMatchmaking` +- enum `PublicNewInstance` +- enum `PrivateNewInstance` +- field `IReadOnlyList accountIds` +- enum `Success` +- enum `FailedToConnectToRegion` +- enum `FailedToConnectToRoom` +- enum `FailedToSpawnPlayer` +- field `long roomId` +- field `int id` +- field `FCIKKFJOMNO Platform` +- field `string PlatformId` +- field `string LockToken` +- field `float startTime` +- field `LDCMJEDOFCO service` +- field `string relativeUri` +- enum `UnknownError` +- enum `Success` +- enum `NoSuchGame` +- enum `PlayerNotOnline` +- enum `InsufficientSpace` +- enum `EventNotStarted` +- enum `EventAlreadyFinished` +- enum `BlockedFromRoom` +- enum `JuniorNotAllowed` +- enum `Banned` +- enum `AlreadyInBestInstance` +- enum `InsufficientRelationship` +- enum `UpdateRequired` +- enum `AlreadyInTargetInstance` +- enum `UGCNotAllowed` +- enum `NoSuchRoom` +- enum `RoomIsNotActive` +- enum `RoomBlockedByCreator` +- enum `RoomIsPrivate` +- enum `RoomInstanceIsPrivate` +- enum `DeviceClassNotSupported` +- enum `DeviceClassNotSupportedByRoomOwner` +- enum `MovementModeNotSupportedByRoomOwner` +- enum `EventIsPrivate` +- enum `RoomInviteExpired` +- enum `NoAvailableRegion` +- enum `NotorietyTooPoor` +- enum `BannedFromRoom` +- enum `NoSuchRoomPlaylist` +- enum `RoomPlaylistIsNotActive` +- enum `RoomPlaylistIsPrivate` +- enum `NoSuchClub` +- enum `ClubHasNoClubhouse` +- enum `ClubIsNotActive` +- enum `NotAMemberOfClub` +- enum `BannedFromClub` +- enum `InstanceJoinNotPermitted` +- field `Promise promise` +- field `Action onDelayedDetailsLoadedCallback` +- field `static ActionEvent LEJANLMCKDD` +- field `static ActionEvent BBDPBMMAPPO` +- field `static ActionEvent AIFBHJBFKJL` +- field `static ActionEvent DOKMINNFCGC` +- field `static ActionEvent EBLJHBJGDFB` +- field `static ActionEvent PMBHKNGODOG` +- field `static ActionEvent LMJHPPCDGKC` +- field `static ActionEvent OLGAMNGEKON` +- field `static ActionEvent OLCJJNMEGPP` +- field `static ActionEvent LHEICOLNLJL` + +## `ModifyTagsRequest.cs` + +Declaration: `public class ModifyTagsRequest` + +- field `long InventionId` +- field `List AutoTags` +- field `List CustomTags` + +## `NewInventionRequestDTO.cs` + +Declaration: `internal class NewInventionRequestDTO` + +- field `string name` +- field `string description` +- field `string imageName` +- field `int instantiationCost` +- field `int lightsCost` +- field `int aiCost` +- field `long creationRoomId` +- field `string inventionDataFilename` +- field `List referencedInventions` +- field `LMLJHMJEIGM creatorAccountRole` + +## `NewRoomKeyRequestDTO.cs` + +Declaration: `internal class NewRoomKeyRequestDTO` + +- field `long roomId` +- field `string name` +- field `string description` +- field `int price` + +## `PlayerEventDTOPage.cs` + +Declaration: `public class PlayerEventDTOPage : IFAIJAGLDFK` + +- no public fields/properties captured + +## `ReportRequest.cs` + +Declaration: `public class ReportRequest` + +- field `long InventionId` +- field `string Details` +- field `FDEGHHFBJJO ReportCategory` + +## `RoomComment.cs` + +Declaration: `public class RoomComment : IFAIJAGLDFK, IEquatable` + +- enum `Feedback` +- enum `Idea` +- enum `BugReport` + +## `SetStatRequestDTO.cs` + +Declaration: `public class SetStatRequestDTO` + +- field `int StatChannel` +- field `long RoomId` +- field `int StatValue` + +## `UnreadRoomComments.cs` + +Declaration: `public class UnreadRoomComments : IFAIJAGLDFK` + +- no public fields/properties captured + +## `UpdatePriceRequest.cs` + +Declaration: `public class UpdatePriceRequest` + +- field `long InventionId` +- field `int Price` + +## `UpdateRoomKeyRequestDTO.cs` + +Declaration: `internal class UpdateRoomKeyRequestDTO` + +- no public fields/properties captured + diff --git a/docs/recroom-2020-client-endpoint-inventory.csv b/docs/recroom-2020-client-endpoint-inventory.csv new file mode 100644 index 0000000..2cd20df --- /dev/null +++ b/docs/recroom-2020-client-endpoint-inventory.csv @@ -0,0 +1,143 @@ +"Build","Type","Method","File","Line","String" +"2020.03 archive literal","stringliteral.json","","Il2CppDump\stringliteral.json",,". If this persists, please contact the developers at support@recroom.com." +"2020.03 archive literal","stringliteral.json","","Il2CppDump\stringliteral.json",,". If this persists, please contact us at tokens@recroom.com." +"2020.03 archive literal","stringliteral.json","","Il2CppDump\stringliteral.json",,". Please contact us at conduct@recroom.com" +"2020.03 archive literal","stringliteral.json","","Il2CppDump\stringliteral.json",,".rec.net" +"2020.03 archive literal","stringliteral.json","","Il2CppDump\stringliteral.json",,"/api/playerReputation/v1/{0}" +"2020.03 archive literal","stringliteral.json","","Il2CppDump\stringliteral.json",,"/api/playerReputation/v1/bulk" +"2020.03 archive literal","stringliteral.json","","Il2CppDump\stringliteral.json",,"/api/players/v1/progression/{0}" +"2020.03 archive literal","stringliteral.json","","Il2CppDump\stringliteral.json",,"/api/players/v1/progression/bulk" +"2020.03 archive literal","stringliteral.json","","Il2CppDump\stringliteral.json",,"/config/{0}" +"2020.03 archive literal","stringliteral.json","","Il2CppDump\stringliteral.json",,"/data/{0}" +"2020.03 archive literal","stringliteral.json","","Il2CppDump\stringliteral.json",,"/images/Demo/" +"2020.03 archive literal","stringliteral.json","","Il2CppDump\stringliteral.json",,"/invention/{0}" +"2020.03 archive literal","stringliteral.json","","Il2CppDump\stringliteral.json",,"/room/" +"2020.03 archive literal","stringliteral.json","","Il2CppDump\stringliteral.json",,"/room/{0}" +"2020.03 archive literal","stringliteral.json","","Il2CppDump\stringliteral.json",,"A moderator has activated junior status on this account. Junior accounts support players 12 or younger. Please enter a parent / guardian?s email or contact conduct@recroom.com to appeal." +"2020.03 archive literal","stringliteral.json","","Il2CppDump\stringliteral.json",,"Contact support@recroom.com if this continues" +"2020.03 archive literal","stringliteral.json","","Il2CppDump\stringliteral.json",,"Error code FROSTBITE. Please contact support@recroom.com" +"2020.03 archive literal","stringliteral.json","","Il2CppDump\stringliteral.json",,"Error copying room. If this persists, please contact the developers at support@recroom.com." +"2020.03 archive literal","stringliteral.json","","Il2CppDump\stringliteral.json",,"Error creating room. If this persists, please contact the developers at support@recroom.com." +"2020.03 archive literal","stringliteral.json","","Il2CppDump\stringliteral.json",,"Error modifying room: {0}. If this persists, please contact the developers at support@recroom.com." +"2020.03 archive literal","stringliteral.json","","Il2CppDump\stringliteral.json",,"Error modifying tags. If this persists, please contact the developers at support@recroom.com." +"2020.03 archive literal","stringliteral.json","","Il2CppDump\stringliteral.json",,"Error purchasing item. Please contact support@recroom.com if this persists." +"2020.03 archive literal","stringliteral.json","","Il2CppDump\stringliteral.json",,"Error purchasing items. Please contact support@recroom.com if this persists." +"2020.03 archive literal","stringliteral.json","","Il2CppDump\stringliteral.json",,"Error updating invention. If this persists, please contact us at support@recroom.com" +"2020.03 archive literal","stringliteral.json","","Il2CppDump\stringliteral.json",,"Error updating room image. If this persists, please contact us at support@recroom.com" +"2020.03 archive literal","stringliteral.json","","Il2CppDump\stringliteral.json",,"Error updating. If this persists, please contact the developers at support@recroom.com." +"2020.03 archive literal","stringliteral.json","","Il2CppDump\stringliteral.json",,"Error: Please contact us at conduct@recroom.com" +"2020.03 archive literal","stringliteral.json","","Il2CppDump\stringliteral.json",,"Error: This event is under moderation review. Contact conduct@recroom.com" +"2020.03 archive literal","stringliteral.json","","Il2CppDump\stringliteral.json",,"Error: You have a room under moderation review. Contact conduct@recroom.com" +"2020.03 archive literal","stringliteral.json","","Il2CppDump\stringliteral.json",,"Failed to copy room. You have a room under moderation review. Contact conduct@recroom.com" +"2020.03 archive literal","stringliteral.json","","Il2CppDump\stringliteral.json",,"Failed to import. Please contact us at conduct@recroom.com" +"2020.03 archive literal","stringliteral.json","","Il2CppDump\stringliteral.json",,"Failed to modify room. You have a room under moderation review. Contact conduct@recroom.com" +"2020.03 archive literal","stringliteral.json","","Il2CppDump\stringliteral.json",,"Failed to room ban. Error code: {0} +If this persists, please email support@recroom.com." +"2020.03 archive literal","stringliteral.json","","Il2CppDump\stringliteral.json",,"Failed to room ban. Please contact us at conduct@recroom.com" +"2020.03 archive literal","stringliteral.json","","Il2CppDump\stringliteral.json",,"Failed to unban. Error code: {0} +If this persists, please email support@recroom.com." +"2020.03 archive literal","stringliteral.json","","Il2CppDump\stringliteral.json",,"Failed to update image. You have a room under moderation review. Email conduct@recroom.com" +"2020.03 archive literal","stringliteral.json","","Il2CppDump\stringliteral.json",,"Failed to update. Error code: {0} +If this persists, please email support@recroom.com." +"2020.03 archive literal","stringliteral.json","","Il2CppDump\stringliteral.json",,"Failed to update. Please contact us at conduct@recroom.com" +"2020.03 archive literal","stringliteral.json","","Il2CppDump\stringliteral.json",,"Failed to update. You have a room under moderation review. Contact conduct@recroom.com" +"2020.03 archive literal","stringliteral.json","","Il2CppDump\stringliteral.json",,"For more information, please contact conduct@recroom.com." +"2020.03 archive literal","stringliteral.json","","Il2CppDump\stringliteral.json",,"http://" +"2020.03 archive literal","stringliteral.json","","Il2CppDump\stringliteral.json",,"http://localhost/" +"2020.03 archive literal","stringliteral.json","","Il2CppDump\stringliteral.json",,"http://ns.exitgames.com:80/photon/n" +"2020.03 archive literal","stringliteral.json","","Il2CppDump\stringliteral.json",,"http://offline/" +"2020.03 archive literal","stringliteral.json","","Il2CppDump\stringliteral.json",,"http://schemas.microsoft.com/clr/assem/" +"2020.03 archive literal","stringliteral.json","","Il2CppDump\stringliteral.json",,"http://schemas.microsoft.com/clr/ns/" +"2020.03 archive literal","stringliteral.json","","Il2CppDump\stringliteral.json",,"http://schemas.microsoft.com/clr/nsassem/" +"2020.03 archive literal","stringliteral.json","","Il2CppDump\stringliteral.json",,"http://www.google.com/generate_204" +"2020.03 archive literal","stringliteral.json","","Il2CppDump\stringliteral.json",,"http://www.w3.org/2000/09/xmldsig#dsa-sha1" +"2020.03 archive literal","stringliteral.json","","Il2CppDump\stringliteral.json",,"http://www.w3.org/2000/09/xmldsig#rsa-sha1" +"2020.03 archive literal","stringliteral.json","","Il2CppDump\stringliteral.json",,"http://www.w3.org/2000/09/xmldsig#sha1" +"2020.03 archive literal","stringliteral.json","","Il2CppDump\stringliteral.json",,"http://www.w3.org/2000/xmlns/" +"2020.03 archive literal","stringliteral.json","","Il2CppDump\stringliteral.json",,"http://www.w3.org/2001/04/xmldsig-more#hmac-ripemd160" +"2020.03 archive literal","stringliteral.json","","Il2CppDump\stringliteral.json",,"http://www.w3.org/2001/04/xmldsig-more#hmac-sha256" +"2020.03 archive literal","stringliteral.json","","Il2CppDump\stringliteral.json",,"http://www.w3.org/2001/04/xmldsig-more#hmac-sha384" +"2020.03 archive literal","stringliteral.json","","Il2CppDump\stringliteral.json",,"http://www.w3.org/2001/04/xmldsig-more#hmac-sha512" +"2020.03 archive literal","stringliteral.json","","Il2CppDump\stringliteral.json",,"http://www.w3.org/2001/04/xmldsig-more#rsa-sha256" +"2020.03 archive literal","stringliteral.json","","Il2CppDump\stringliteral.json",,"http://www.w3.org/2001/04/xmldsig-more#rsa-sha384" +"2020.03 archive literal","stringliteral.json","","Il2CppDump\stringliteral.json",,"http://www.w3.org/2001/04/xmldsig-more#rsa-sha512" +"2020.03 archive literal","stringliteral.json","","Il2CppDump\stringliteral.json",,"http://www.w3.org/2001/04/xmldsig-more#sha384" +"2020.03 archive literal","stringliteral.json","","Il2CppDump\stringliteral.json",,"http://www.w3.org/2001/04/xmlenc#sha256" +"2020.03 archive literal","stringliteral.json","","Il2CppDump\stringliteral.json",,"http://www.w3.org/2001/04/xmlenc#sha512" +"2020.03 archive literal","stringliteral.json","","Il2CppDump\stringliteral.json",,"http://www.w3.org/2001/XMLSchema" +"2020.03 archive literal","stringliteral.json","","Il2CppDump\stringliteral.json",,"http://www.w3.org/2001/XMLSchema-instance" +"2020.03 archive literal","stringliteral.json","","Il2CppDump\stringliteral.json",,"http://www.w3.org/2003/11/xpath-datatypes" +"2020.03 archive literal","stringliteral.json","","Il2CppDump\stringliteral.json",,"http://www.w3.org/XML/1998/namespace" +"2020.03 archive literal","stringliteral.json","","Il2CppDump\stringliteral.json",,"https://api.amplitude.com/httpapi" +"2020.03 archive literal","stringliteral.json","","Il2CppDump\stringliteral.json",,"https://api.amplitude.com/identify" +"2020.03 archive literal","stringliteral.json","","Il2CppDump\stringliteral.json",,"https://besthttpdemosite.azurewebsites.net" +"2020.03 archive literal","stringliteral.json","","Il2CppDump\stringliteral.json",,"https://clips.vorwaerts-gmbh.de/big_buck_bunny.mp4" +"2020.03 archive literal","stringliteral.json","","Il2CppDump\stringliteral.json",,"https://graph.oculus.com/me?access_token=" +"2020.03 archive literal","stringliteral.json","","Il2CppDump\stringliteral.json",,"https://ns.rec.net/?v=2" +"2020.03 archive literal","stringliteral.json","","Il2CppDump\stringliteral.json",,"https://rec.net" +"2020.03 archive literal","stringliteral.json","","Il2CppDump\stringliteral.json",,"https://rec.net/password/recover" +"2020.03 archive literal","stringliteral.json","","Il2CppDump\stringliteral.json",,"https://recroom.com/code-of-conduct" +"2020.03 archive literal","stringliteral.json","","Il2CppDump\stringliteral.json",,"https://recroom.happyfox.com/home" +"2020.03 archive literal","stringliteral.json","","Il2CppDump\stringliteral.json",,"https://recroom.happyfox.com/kb/article/43-custom-rooms-tools-and-tutorials" +"2020.03 archive literal","stringliteral.json","","Il2CppDump\stringliteral.json",,"https://uk3.testmy.net/dl-102400" +"2020.03 archive literal","stringliteral.json","","Il2CppDump\stringliteral.json",,"https://unionassets.com/ios-deploy" +"2020.03 archive literal","stringliteral.json","","Il2CppDump\stringliteral.json",,"https://unionassets.com/ios-native-pro/manual" +"2020.03 archive literal","stringliteral.json","","Il2CppDump\stringliteral.json",,"https://www.instagram.com/recroom/" +"2020.03 archive literal","stringliteral.json","","Il2CppDump\stringliteral.json",,"https://www.recroom.com/comfortandsafety" +"2020.03 archive literal","stringliteral.json","","Il2CppDump\stringliteral.json",,"https://www.recroom.com/community/" +"2020.03 archive literal","stringliteral.json","","Il2CppDump\stringliteral.json",,"https://www.recroom.com/privacy-policy/" +"2020.03 archive literal","stringliteral.json","","Il2CppDump\stringliteral.json",,"If this persists, please contact support@recroom.com" +"2020.03 archive literal","stringliteral.json","","Il2CppDump\stringliteral.json",,"One of you events is under moderation review. Contact conduct@recroom.com" +"2020.03 archive literal","stringliteral.json","","Il2CppDump\stringliteral.json",,"Oops! An error has occured. + +If this persists, please contact us at support@recroom.com" +"2020.03 archive literal","stringliteral.json","","Il2CppDump\stringliteral.json",,"Rename failed: {0}. If this persists, please contact support@recroom.com" +"2020.03 archive literal","stringliteral.json","","Il2CppDump\stringliteral.json",,"Room is not active. conduct@recroom.com" +"2020.03 archive literal","stringliteral.json","","Il2CppDump\stringliteral.json",,"Room is not active. Contact us at support@recroom.com" +"2020.03 archive literal","stringliteral.json","","Il2CppDump\stringliteral.json",,"Room is under review. Contact us at conduct@recroom.com" +"2020.03 archive literal","stringliteral.json","","Il2CppDump\stringliteral.json",,"Shared on www.rec.net" +"2020.03 archive literal","stringliteral.json","","Il2CppDump\stringliteral.json",,"Unable to save selection as invention. If this persists, please contact the developers at support@recroom.com." +"2020.03 archive literal","stringliteral.json","","Il2CppDump\stringliteral.json",,"Unable to update invention. If this persists, please contact the developers at support@recroom.com." +"2020.03 archive literal","stringliteral.json","","Il2CppDump\stringliteral.json",,"Unknown error. If this persists, please contact the developers at support@recroom.com." +"2020.03 archive literal","stringliteral.json","","Il2CppDump\stringliteral.json",,"ws://" +"2020.03 archive literal","stringliteral.json","","Il2CppDump\stringliteral.json",,"ws://{0}:{1}" +"2020.03 archive literal","stringliteral.json","","Il2CppDump\stringliteral.json",,"wss://" +"2020.03 archive literal","stringliteral.json","","Il2CppDump\stringliteral.json",,"wss://{0}:{1}" +"2020.03 archive literal","stringliteral.json","","Il2CppDump\stringliteral.json",,"wss://echo.websocket.org" +"2020.03 archive literal","stringliteral.json","","Il2CppDump\stringliteral.json",,"You are banned for {0}. For any questions contact support@recroom.com" +"2020.03 archive literal","stringliteral.json","","Il2CppDump\stringliteral.json",,"Your account is in timeout. Conduct@recroom.com" +"2020.12.18 ISIL","AGUI.StackedUI.StorePurchaseSkuScreen","System.Void EOLEDECNKDH(System.String JJGHIEJCCDA, System.String AIPBOMLEOND)","AGUI\StackedUI\StorePurchaseSkuScreen.txt","1216",". If this persists, please contact us at tokens@recroom.com." +"2020.12.18 ISIL","EBAIPNMBKLK","RecRoom.Async.IPromise`1 JNLDMDCGHHJ(System.Int32 GKLPIFBPGOD)","EBAIPNMBKLK.txt","1231","/api/playerReputation/v1/{0}" +"2020.12.18 ISIL","EBAIPNMBKLK","RecRoom.Async.IPromise`1> KGPDFJMKJKL(System.Collections.Generic.List`1 ILNGMAANNDG)","EBAIPNMBKLK.txt","1631","/api/playerReputation/v1/bulk" +"2020.12.18 ISIL","ACJLCBNBJDK","RecRoom.Async.IPromise`1 AAGPDEDFCNI(System.Int32 GKLPIFBPGOD)","ACJLCBNBJDK.txt","1673","/api/players/v1/progression/{0}" +"2020.12.18 ISIL","ACJLCBNBJDK","RecRoom.Async.IPromise`1> MCFMHADBDBI(System.Collections.Generic.List`1 ILNGMAANNDG)","ACJLCBNBJDK.txt","2051","/api/players/v1/progression/bulk" +"2020.12.18 ISIL","GNPDMBPGHBH","RecRoom.Async.IPromise`1> DMHOIAJGABC(System.Collections.Generic.IReadOnlyList`1 JOGAGJIPEDN)","GNPDMBPGHBH.txt","2388","/bulk" +"2020.12.18 ISIL","GNPDMBPGHBH+MKCNPHKGFDM","RecRoom.Async.IPromise`1> b__1(System.String uri)","GNPDMBPGHBH_NestedType_MKCNPHKGFDM.txt","138","/bulk" +"2020.12.18 ISIL","FHLGJDFHOKL","RecRoom.Async.IPromise MCDNGDMJHBE()","FHLGJDFHOKL.txt","502","/config/{0}" +"2020.12.18 ISIL","COFHGNFJMOG","RecRoom.Async.IPromise`1 HDLJGFEDGLH(System.String ENJEOLBEALP, System.Nullable`1 LNBJKLOINED)","COFHGNFJMOG.txt","1242","/consume" +"2020.12.18 ISIL","KELEPAPMOGK+CJACMOFOFKJ","RecRoom.Async.IPromise`1 b__0()","KELEPAPMOGK_NestedType_CJACMOFOFKJ.txt","187","/data/{0}" +"2020.12.18 ISIL","BBHENFCNLAB+NBDMFFEGNGJ","RecRoom.Async.IPromise`1 b__0()","BBHENFCNLAB_NestedType_NBDMFFEGNGJ.txt","207","/invention/{0}" +"2020.12.18 ISIL","OJMCBOKJFOF+BPPECKDOINI","RecRoom.Async.IPromise`1 b__0()","OJMCBOKJFOF_NestedType_BPPECKDOINI.txt","206","/room/" +"2020.12.18 ISIL","KHGPLGBHIAH","System.Void PPOCPFGCALB(LGFHFDMDJKK GIGKODMKKHJ, System.Action`2 DFPGDEOCNIH, System.Action`2 AFLPGGJMPOE)","KHGPLGBHIAH.txt","2838","{0}/api/userreporting" +"2020.12.18 ISIL","AGUI.StackedUI.StorePurchaseSkuScreen","System.Void CMCFMHIDHKC(System.String MOPOMPHBHKF)","AGUI\StackedUI\StorePurchaseSkuScreen.txt","2276","An error occurred while trying to process your purchase. If this persists, please contact us at tokens@recroom.com." +"2020.12.18 ISIL","BPHGKAEDBPE","System.Void HKHMDLBEHII(NEBIGEDJGPP NAEOJENNACH, HOOILIHKMEG MAOOLFEBOOD, RecRoom.Async.Promise MCINMMCGJML)","BPHGKAEDBPE.txt","3896","http://www.google.com/generate_204" +"2020.12.18 ISIL","AmplitudeAnalytics.AmplitudeAnalyticsClient","RecRoom.Async.IPromise LogOutOfSessionEvent(CCNBJFOPLBG EMGIIALFJEK)","AmplitudeAnalytics\AmplitudeAnalyticsClient.txt","3409","https://api2.amplitude.com/httpapi" +"2020.12.18 ISIL","IOSPlatformManager","RecRoom.Async.IPromise ShowManageSubscriptionPlatformUI()","IOSPlatformManager.txt","4865","https://apps.apple.com/account/subscriptions" +"2020.12.18 ISIL","AGUI.StackedUI.MembershipBenefitsScreen","System.Void Button_Details()","AGUI\StackedUI\MembershipBenefitsScreen.txt","3840","https://recroom.com/recroomplus" +"2020.12.18 ISIL","FeaturedCreatorBulletinBoardUI","System.Void Button_HowToBuild()","FeaturedCreatorBulletinBoardUI.txt","1096","https://recroom.happyfox.com/kb/article/43-custom-rooms-tools-and-tutorials" +"2020.12.18 ISIL","SettingsWatchUIFlow","System.Void Button_iOSGamepadInstructionsPage()","SettingsWatchUIFlow.txt","11647","https://support.apple.com/en-us/HT210414" +"2020.12.18 ISIL","PDEDCLFAOKJ","KHGPLGBHIAH KEABPBDEIHL()","PDEDCLFAOKJ.txt","98","https://userreporting.cloud.unity3d.com" +"2020.12.18 ISIL","PDEDCLFAOKJ","System.Void DBHENALNJLF()","PDEDCLFAOKJ.txt","590","https://userreporting.cloud.unity3d.com" +"2020.12.18 ISIL","PDEDCLFAOKJ","System.Void DBHENALNJLF(GFHAIDKLNFD FPMCLJDEGKL)","PDEDCLFAOKJ.txt","842","https://userreporting.cloud.unity3d.com" +"2020.12.18 ISIL","PDEDCLFAOKJ","System.Void DBHENALNJLF(GFHAIDKLNFD FPMCLJDEGKL, MKFJEECKPEM EODOKLCLHJJ)","PDEDCLFAOKJ.txt","775","https://userreporting.cloud.unity3d.com" +"2020.12.18 ISIL","PDEDCLFAOKJ","System.Void DBHENALNJLF(MKFJEECKPEM EODOKLCLHJJ)","PDEDCLFAOKJ.txt","668","https://userreporting.cloud.unity3d.com" +"2020.12.18 ISIL","PDEDCLFAOKJ","System.Void DBHENALNJLF(System.String IPNNGIACMAE)","PDEDCLFAOKJ.txt","502","https://userreporting.cloud.unity3d.com" +"2020.12.18 ISIL","PDEDCLFAOKJ","System.Void DBHENALNJLF(System.String IPNNGIACMAE, GFHAIDKLNFD FPMCLJDEGKL, MKFJEECKPEM EODOKLCLHJJ)","PDEDCLFAOKJ.txt","722","https://userreporting.cloud.unity3d.com" +"2020.12.18 ISIL","PDEDCLFAOKJ","System.Void DBHENALNJLF(System.String IPNNGIACMAE, MKFJEECKPEM EODOKLCLHJJ)","PDEDCLFAOKJ.txt","418","https://userreporting.cloud.unity3d.com" +"2020.12.18 ISIL","UserReportingScript","System.Void Start()","UserReportingScript.txt","1174","https://userreporting.cloud.unity3d.com/api/userreporting/projects/{0}/ping" +"2020.12.18 ISIL","InstagramBulletinBoardUI","System.Void Button_RecRoomOnInstagram()","InstagramBulletinBoardUI.txt","1009","https://www.instagram.com/recroom/" +"2020.12.18 ISIL","InstagramBulletinBoardUI","System.Void Button_ShareYourStuff()","InstagramBulletinBoardUI.txt","1052","https://www.recroom.com/community/" +"2020.12.18 ISIL","EJDCNGBEICB","RecRoom.Async.IPromise`1 JBMCPFHGIMD(System.Int64 DDMPFMPILCE, System.Int64 HNHLJONGKHB)","EJDCNGBEICB.txt","7765","playlists/{0}/rooms/{1}" +"2020.12.18 ISIL","EJDCNGBEICB","RecRoom.Async.IPromise`1 NLOEBFMNCFM(System.Int64 DDMPFMPILCE, System.Int64 HNHLJONGKHB)","EJDCNGBEICB.txt","7679","playlists/{0}/rooms/{1}" +"2020.12.18 ISIL","RecRoom.UI.BanDetailsBulletinBoardUI","System.Void KEFHKHKHABK(System.String KCDNNDGKINC)","RecRoom\UI\BanDetailsBulletinBoardUI.txt","898","Rec.Net ban appeal" +"2020.12.18 ISIL","EJDCNGBEICB","RecRoom.Async.IPromise`1 HOPMJFDKPDL(System.Int64 HNHLJONGKHB, System.Int64 FODGKNJIGOP, System.String MEIIMAIGBJD, System.Collections.Generic.Dictionary`2 PNOBBNPJDLI, System.Int32 OMCLBIFCHLF)","EJDCNGBEICB.txt","5995","rooms/{0}/subrooms/{1}/data" diff --git a/docs/recroom-2020-client-openapi-context.md b/docs/recroom-2020-client-openapi-context.md new file mode 100644 index 0000000..3620df3 --- /dev/null +++ b/docs/recroom-2020-client-openapi-context.md @@ -0,0 +1,84 @@ +# Rec Room 2020 Client OpenAPI Context + +This companion explains `docs/recroom-2020-client-openapi.json`, an inferred OpenAPI 3.1 description of the request surface observed in the March and December 2020 Rec Room client decompiles. + +## Files + +- `docs/recroom-2020-client-openapi.json`: OpenAPI 3.1 artifact. +- `docs/recroom-2020-client-request-catalog.csv`: December ISIL call-site evidence. +- `docs/recroom-2020-03-request-literals.csv`: March string-literal evidence. +- `docs/recroom-2020-client-request-expectations.md`: human-readable request/result table. +- `docs/recroom-2020-client-response-contracts.md`: endpoint-by-endpoint + expected response body notes, including DTO members and recovered JSON parser + keys. +- `docs/recroom-2020-client-response-contracts.json`: machine-readable version + of the response contracts. + +## How To Read It + +- `x-recroom-client-route-literal` is the exact client string or fragment before OpenAPI normalization. +- `x-recroom-evidence` points back to decompiled type, method signature, file, and line. +- `x-recroom-builds` says whether the route was seen in December 2020, March 2020, or both. +- `x-recroom-inferred-http-method` is best effort. The dump often hides the high-level request builder body, so mutations are modeled as `post`/`delete` by route semantics. +- Response schemas are permissive and family-specific, but operations now carry + `x-client-response-contracts` from the response-contract pass. For exact body + shape, prefer the Markdown/JSON response contract files and the + `Client parser JSON keys` entries recovered from `PPGFHEDFBEA`. + +## Coverage Summary + +- December raw call-site rows: 367 +- March request-like literals: 87 +- OpenAPI paths: 231 +- OpenAPI operations: 245 + +| Family | Count | +| --- | ---: | +| account | 13 | +| activities | 4 | +| announcements | 1 | +| avatar | 3 | +| bootstrap | 2 | +| bug-reporting | 1 | +| clubs | 37 | +| community-board | 1 | +| config-settings | 5 | +| economy | 6 | +| elo | 1 | +| equipment | 1 | +| groups | 1 | +| images | 1 | +| inventions | 5 | +| matchmaking | 10 | +| messages | 7 | +| misc | 25 | +| objectives | 1 | +| player-events | 10 | +| players | 6 | +| playlists | 21 | +| quickplay | 1 | +| relationships | 1 | +| reporting | 5 | +| room-keys | 4 | +| rooms | 63 | +| sanitize | 2 | +| storage-cdn | 3 | +| store | 2 | +| test-case-management | 1 | +| versioncheck | 1 | + +## Service Mapping + +- `account`: account profile APIs, usually `api.rec.net` or account service; password routes may belong to auth/account host. +- `matchmaking`: `match.rec.net` style `/goto/*` and room instance discovery. +- `rooms` and `playlists`: room/playlist JSON APIs. Room blob bytes are not JSON and must remain CDN/storage binary responses. +- `inventions`: invention metadata/save/version/publish/download APIs; March and December differ on save/publish versions. +- `clubs`: club and member APIs; may use the name-server `Clubs` service base. +- `images`, `storage-cdn`: image metadata/upload plus later binary fetches. +- `messages`, `player-events`, `avatar`, `economy`, `reporting`, `config-settings`: primary API host unless the name server routes them elsewhere. + +## Important Limitations + +This is a compatibility map, not a guarantee that every method/schema is exact. It is intentionally broad so DorkNet can see every route family the client may touch. Before implementing a missing endpoint, inspect the evidence method and nearby DTO declarations, then verify with client logs. + +The highest-risk exact-shape areas are matchmaking/goto, room data descriptors, CDN/storage blobs, auth/account login, avatar gifts/items, and invention save/download. Those should be validated against runtime client behavior because wrong primitive-vs-object-vs-array shapes can crash or stall the 2020 client. diff --git a/docs/recroom-2020-client-openapi.json b/docs/recroom-2020-client-openapi.json new file mode 100644 index 0000000..5a25585 --- /dev/null +++ b/docs/recroom-2020-client-openapi.json @@ -0,0 +1,51552 @@ +{ + "openapi": "3.1.0", + "info": { + "title": "Rec Room 2020 Client Observed Request Surface", + "version": "2020.03+2020.12-inferred", + "description": "Inferred OpenAPI generated from decompiled Rec Room 2020 client request literals. Methods and schemas are best-effort where the decompile does not preserve high-level bodies." + }, + "servers": [ + { + "url": "https://api.rec.net", + "description": "Primary API service" + }, + { + "url": "https://auth.rec.net", + "description": "Auth/account password service" + }, + { + "url": "https://match.rec.net", + "description": "Matchmaking/goto service" + }, + { + "url": "https://rooms.rec.net", + "description": "Rooms service" + }, + { + "url": "https://cdn.rec.net", + "description": "CDN/binary content service" + }, + { + "url": "https://storage.rec.net", + "description": "Storage/upload service" + }, + { + "url": "https://notify.rec.net", + "description": "Notifications service" + }, + { + "url": "https://clubs.rec.net", + "description": "Club service when exposed separately" + } + ], + "security": [ + { + "BearerAuth": [] + } + ], + "tags": [ + { + "name": "account" + }, + { + "name": "activities" + }, + { + "name": "announcements" + }, + { + "name": "avatar" + }, + { + "name": "bootstrap" + }, + { + "name": "bug-reporting" + }, + { + "name": "clubs" + }, + { + "name": "community-board" + }, + { + "name": "config-settings" + }, + { + "name": "economy" + }, + { + "name": "elo" + }, + { + "name": "equipment" + }, + { + "name": "groups" + }, + { + "name": "images" + }, + { + "name": "inventions" + }, + { + "name": "matchmaking" + }, + { + "name": "messages" + }, + { + "name": "misc" + }, + { + "name": "objectives" + }, + { + "name": "player-events" + }, + { + "name": "players" + }, + { + "name": "playlists" + }, + { + "name": "quickplay" + }, + { + "name": "relationships" + }, + { + "name": "reporting" + }, + { + "name": "room-keys" + }, + { + "name": "rooms" + }, + { + "name": "sanitize" + }, + { + "name": "storage-cdn" + }, + { + "name": "store" + }, + { + "name": "test-case-management" + }, + { + "name": "versioncheck" + } + ], + "paths": { + "/account/{p0}/bio": { + "get": { + "tags": [ + "account" + ], + "summary": "GET account/{0}/bio", + "description": "Inferred from Rec Room 2020 client literal `account/{0}/bio`. Expected result: Account profile object or mutation success.", + "operationId": "client_account_account_p0_bio_get", + "parameters": [ + { + "name": "p0", + "in": "path", + "required": true, + "schema": { + "type": "string" + }, + "description": "Format placeholder from client literal account/{0}/bio" + } + ], + "responses": { + "200": { + "description": "Account profile object or mutation success.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/AccountProfile" + } + } + } + }, + "204": { + "description": "Success with no body, used by some mutation endpoints." + }, + "400": { + "description": "Client-visible validation or bad request error.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "401": { + "description": "Authentication required or token invalid.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "500": { + "description": "Server error shape tolerated by client wrappers.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + } + }, + "x-recroom-client-route-literal": "account/{0}/bio", + "x-recroom-client-family": "account", + "x-recroom-client-service": "accounts/api", + "x-recroom-builds": [ + "2020.03.10", + "2020.12.18" + ], + "x-recroom-inferred-http-method": "get", + "x-recroom-evidence": [ + { + "type": "PEGGCEDHBOF", + "method": "RecRoom.Async.IPromise`1 GBLAOEFPDAH(System.Int32 GKLPIFBPGOD)", + "file": "PEGGCEDHBOF.txt", + "line": 7866 + }, + { + "type": "PEGGCEDHBOF+<>c", + "method": "System.Void b__67_0()", + "file": "PEGGCEDHBOF_NestedType___c.txt", + "line": 587 + }, + { + "type": "stringliteral.json", + "method": "", + "file": "Recnet-archive/decompiled/Il2CppDump/stringliteral.json", + "line": null + } + ], + "x-client-response-contracts": [ + { + "kind": "object", + "raw": "FKNGFLFDIIB", + "rootType": "FKNGFLFDIIB", + "typeDescription": { + "name": "FKNGFLFDIIB", + "found": true, + "file": "FKNGFLFDIIB.cs", + "declaration": "public class FKNGFLFDIIB : IFAIJAGLDFK", + "kind": "class", + "baseType": "IFAIJAGLDFK", + "properties": [ + { + "name": "AHOEKLABOBN", + "type": "string", + "accessibility": "public", + "kind": "property" + }, + { + "name": "GAINIOENNCG", + "type": "int", + "accessibility": "public", + "kind": "property" + } + ], + "fields": [], + "enumValues": [], + "wireKeys": [ + "accountId", + "bio" + ], + "isilFile": "FKNGFLFDIIB.txt", + "isilType": "FKNGFLFDIIB" + } + }, + { + "kind": "unknown", + "raw": "", + "rootType": "unknown", + "typeDescription": { + "name": "unknown", + "found": false, + "kind": "object", + "wireKeys": [] + } + } + ] + } + }, + "/account/{p0}/clubs": { + "get": { + "tags": [ + "account" + ], + "summary": "GET account/{0}/clubs", + "description": "Inferred from Rec Room 2020 client literal `account/{0}/clubs`. Expected result: Account profile object or mutation success.", + "operationId": "client_account_account_p0_clubs_get", + "parameters": [ + { + "name": "p0", + "in": "path", + "required": true, + "schema": { + "type": "string" + }, + "description": "Format placeholder from client literal account/{0}/clubs" + } + ], + "responses": { + "200": { + "description": "Account profile object or mutation success.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/AccountProfile" + } + } + } + }, + "204": { + "description": "Success with no body, used by some mutation endpoints." + }, + "400": { + "description": "Client-visible validation or bad request error.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "401": { + "description": "Authentication required or token invalid.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "500": { + "description": "Server error shape tolerated by client wrappers.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + } + }, + "x-recroom-client-route-literal": "account/{0}/clubs", + "x-recroom-client-family": "account", + "x-recroom-client-service": "accounts/api", + "x-recroom-builds": [ + "2020.12.18" + ], + "x-recroom-inferred-http-method": "get", + "x-recroom-evidence": [ + { + "type": "JDJGIBLMFKK", + "method": "RecRoom.Async.IPromise`1> KGGMKBADLDM(System.Int32 GKLPIFBPGOD)", + "file": "JDJGIBLMFKK.txt", + "line": 9580 + } + ], + "x-client-response-contracts": [ + { + "kind": "array", + "raw": "System.Collections.Generic.List`1", + "rootType": "PLILLKHMNDA", + "item": { + "kind": "object", + "raw": "PLILLKHMNDA", + "rootType": "PLILLKHMNDA" + }, + "typeDescription": { + "name": "PLILLKHMNDA", + "found": true, + "file": "PLILLKHMNDA.cs", + "declaration": "public class PLILLKHMNDA : IFAIJAGLDFK, IEquatable", + "kind": "class", + "baseType": "IFAIJAGLDFK", + "properties": [ + { + "name": "BADIGBCKECA", + "type": "int", + "accessibility": "public", + "kind": "property" + }, + { + "name": "CCGOEDABKNN", + "type": "long", + "accessibility": "public", + "kind": "property" + }, + { + "name": "CDINMMPNAID", + "type": "JCMLDDKFKEO", + "accessibility": "public", + "kind": "property" + }, + { + "name": "CDNFGMHLDMJ", + "type": "bool", + "accessibility": "public", + "kind": "property" + }, + { + "name": "EEAOJCGAOCN", + "type": "int", + "accessibility": "public", + "kind": "property" + }, + { + "name": "EHOLKJPEGFF", + "type": "string", + "accessibility": "public", + "kind": "property" + }, + { + "name": "EPGALOMHHMI", + "type": "JCDEFCJLCHN", + "accessibility": "public", + "kind": "property" + }, + { + "name": "FIKEBGGCDFN", + "type": "string", + "accessibility": "public", + "kind": "property" + }, + { + "name": "HHCGNCLFKDM", + "type": "Nullable", + "accessibility": "public", + "kind": "property" + }, + { + "name": "IPKLLFAJJPJ", + "type": "bool", + "accessibility": "public", + "kind": "property" + }, + { + "name": "KODBEJPEFOJ", + "type": "string", + "accessibility": "public", + "kind": "property" + }, + { + "name": "LNGPBGCIAPP", + "type": "string", + "accessibility": "public", + "kind": "property" + }, + { + "name": "MNEEEGHOGAB", + "type": "DIGMAIMMHAP", + "accessibility": "public", + "kind": "property" + }, + { + "name": "PHBCFAJILGD", + "type": "PJCALHOPMKJ", + "accessibility": "public", + "kind": "property" + } + ], + "fields": [], + "enumValues": [], + "wireKeys": [ + "ClubId", + "Name", + "Description", + "MainImageName", + "State", + "CreatorAccountId", + "Category", + "Visibility", + "Joinability", + "AllowJuniors", + "MemberCount", + "IsRRO", + "ClubType" + ], + "isilFile": "PLILLKHMNDA.txt", + "isilType": "PLILLKHMNDA" + } + } + ] + } + }, + "/account/{p0}": { + "get": { + "tags": [ + "account" + ], + "summary": "GET account/{0}", + "description": "Inferred from Rec Room 2020 client literal `account/{0}`. Expected result: Account profile object or mutation success.", + "operationId": "client_account_account_p0_get", + "parameters": [ + { + "name": "p0", + "in": "path", + "required": true, + "schema": { + "type": "string" + }, + "description": "Format placeholder from client literal account/{0}" + } + ], + "responses": { + "200": { + "description": "Account profile object or mutation success.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/AccountProfile" + } + } + } + }, + "204": { + "description": "Success with no body, used by some mutation endpoints." + }, + "400": { + "description": "Client-visible validation or bad request error.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "401": { + "description": "Authentication required or token invalid.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "500": { + "description": "Server error shape tolerated by client wrappers.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + } + }, + "x-recroom-client-route-literal": "account/{0}", + "x-recroom-client-family": "account", + "x-recroom-client-service": "accounts/api", + "x-recroom-builds": [ + "2020.03.10", + "2020.12.18" + ], + "x-recroom-inferred-http-method": "get", + "x-recroom-evidence": [ + { + "type": "PEGGCEDHBOF", + "method": "RecRoom.Async.IPromise`1 NFKMGNHDCMN(System.Int32 GKLPIFBPGOD)", + "file": "PEGGCEDHBOF.txt", + "line": 3934 + }, + { + "type": "stringliteral.json", + "method": "", + "file": "Recnet-archive/decompiled/Il2CppDump/stringliteral.json", + "line": null + } + ], + "x-client-response-contracts": [ + { + "kind": "object", + "raw": "CCEOLAOLEKJ", + "rootType": "CCEOLAOLEKJ", + "typeDescription": { + "name": "CCEOLAOLEKJ", + "found": true, + "file": "CCEOLAOLEKJ.cs", + "declaration": "public class CCEOLAOLEKJ : IFAIJAGLDFK", + "kind": "class", + "baseType": "IFAIJAGLDFK", + "properties": [ + { + "name": "ACBFDMLHFPB", + "type": "DateTime", + "accessibility": "public", + "kind": "property" + }, + { + "name": "AEMHDANDFOP", + "type": "ObscuredBool", + "accessibility": "public", + "kind": "property" + }, + { + "name": "BIOGKFGIMDG", + "type": "ObscuredString", + "accessibility": "public", + "kind": "property" + }, + { + "name": "CFJLJOKIJCN", + "type": "ObscuredString", + "accessibility": "public", + "kind": "property" + }, + { + "name": "GAINIOENNCG", + "type": "ObscuredInt", + "accessibility": "public", + "kind": "property" + }, + { + "name": "IHADBCJNDIP", + "type": "ObscuredString", + "accessibility": "public", + "kind": "property" + }, + { + "name": "ILOADNHKMHK", + "type": "ENNEGEELGMC", + "accessibility": "public", + "kind": "property" + }, + { + "name": "JIIMAIDFJPN", + "type": "ObscuredBool", + "accessibility": "public", + "kind": "property" + }, + { + "name": "OOJFBECGEAD", + "type": "ObscuredString", + "accessibility": "public", + "kind": "property" + } + ], + "fields": [], + "enumValues": [], + "wireKeys": [ + "accountId", + "profileImage", + "isJunior", + "platforms", + "username", + "displayName", + "createdAt" + ], + "isilFile": "CCEOLAOLEKJ.txt", + "isilType": "CCEOLAOLEKJ" + } + } + ] + } + }, + "/account/bulk/{tail}": { + "post": { + "tags": [ + "account" + ], + "summary": "POST account/bulk/", + "description": "Inferred from Rec Room 2020 client literal `account/bulk/`. Expected result: Account profile summary array.", + "operationId": "client_account_account_bulk_tail_post", + "parameters": [ + { + "name": "tail", + "in": "path", + "required": true, + "schema": { + "type": "string" + }, + "description": "Format placeholder from client literal account/bulk/" + } + ], + "responses": { + "200": { + "description": "Account profile summary array.", + "content": { + "application/json": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/AccountProfile" + } + } + } + } + }, + "204": { + "description": "Success with no body, used by some mutation endpoints." + }, + "400": { + "description": "Client-visible validation or bad request error.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "401": { + "description": "Authentication required or token invalid.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "500": { + "description": "Server error shape tolerated by client wrappers.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + } + }, + "x-recroom-client-route-literal": "account/bulk/", + "x-recroom-client-family": "account", + "x-recroom-client-service": "accounts/api", + "x-recroom-builds": [ + "2020.03.10", + "2020.12.18" + ], + "x-recroom-inferred-http-method": "post", + "x-recroom-evidence": [ + { + "type": "PEGGCEDHBOF", + "method": "RecRoom.Async.IPromise`1> NGENLHMLHNG(System.Collections.Generic.List`1 BGELOMIEKFK, System.String MAFJOJLDFJO, System.String LOOAPLFGOEN, System.String PJIBCCFFMNJ, GPOOLJODEGM OLBDHLLJJHF)", + "file": "PEGGCEDHBOF.txt", + "line": 9080 + }, + { + "type": "stringliteral.json", + "method": "", + "file": "Recnet-archive/decompiled/Il2CppDump/stringliteral.json", + "line": null + } + ], + "requestBody": { + "required": false, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/GenericObject" + } + }, + "application/x-www-form-urlencoded": { + "schema": { + "$ref": "#/components/schemas/GenericObject" + } + }, + "multipart/form-data": { + "schema": { + "$ref": "#/components/schemas/GenericObject" + } + } + }, + "description": "Request body inferred. Exact DTO may be named in x-recroom-evidence method signature or companion DTO inventory." + }, + "x-client-response-contracts": [ + { + "kind": "array", + "raw": "System.Collections.Generic.List`1", + "rootType": "DCGAMCKNJDB", + "item": { + "kind": "object", + "raw": "DCGAMCKNJDB", + "rootType": "DCGAMCKNJDB" + }, + "typeDescription": { + "name": "DCGAMCKNJDB", + "found": true, + "file": "DCGAMCKNJDB.cs", + "declaration": "public class DCGAMCKNJDB", + "kind": "class", + "baseType": null, + "properties": [ + { + "name": "DINFEKALAJO", + "type": "GPOOLJODEGM", + "accessibility": "public", + "kind": "property" + }, + { + "name": "DJMJFEJGIHM", + "type": "CCEOLAOLEKJ", + "accessibility": "public", + "kind": "property" + }, + { + "name": "MCHJBOBNFLN", + "type": "string", + "accessibility": "public", + "kind": "property" + } + ], + "fields": [], + "enumValues": [], + "wireKeys": [], + "isilFile": "DCGAMCKNJDB.txt", + "isilType": "DCGAMCKNJDB" + } + } + ] + } + }, + "/account/bulk": { + "post": { + "tags": [ + "account" + ], + "summary": "POST account/bulk", + "description": "Inferred from Rec Room 2020 client literal `account/bulk`. Expected result: Account profile summary array.", + "operationId": "client_account_account_bulk_post", + "parameters": [], + "responses": { + "200": { + "description": "Account profile summary array.", + "content": { + "application/json": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/AccountProfile" + } + } + } + } + }, + "204": { + "description": "Success with no body, used by some mutation endpoints." + }, + "400": { + "description": "Client-visible validation or bad request error.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "401": { + "description": "Authentication required or token invalid.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "500": { + "description": "Server error shape tolerated by client wrappers.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + } + }, + "x-recroom-client-route-literal": "account/bulk", + "x-recroom-client-family": "account", + "x-recroom-client-service": "accounts/api", + "x-recroom-builds": [ + "2020.03.10", + "2020.12.18" + ], + "x-recroom-inferred-http-method": "post", + "x-recroom-evidence": [ + { + "type": "PEGGCEDHBOF", + "method": "RecRoom.Async.IPromise`1> NHCHADDMPFC(System.Collections.Generic.List`1 ILNGMAANNDG)", + "file": "PEGGCEDHBOF.txt", + "line": 4917 + }, + { + "type": "stringliteral.json", + "method": "", + "file": "Recnet-archive/decompiled/Il2CppDump/stringliteral.json", + "line": null + } + ], + "requestBody": { + "required": false, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/GenericObject" + } + }, + "application/x-www-form-urlencoded": { + "schema": { + "$ref": "#/components/schemas/GenericObject" + } + }, + "multipart/form-data": { + "schema": { + "$ref": "#/components/schemas/GenericObject" + } + } + }, + "description": "Request body inferred. Exact DTO may be named in x-recroom-evidence method signature or companion DTO inventory." + }, + "x-client-response-contracts": [ + { + "kind": "array", + "raw": "System.Collections.Generic.List`1", + "rootType": "CCEOLAOLEKJ", + "item": { + "kind": "object", + "raw": "CCEOLAOLEKJ", + "rootType": "CCEOLAOLEKJ" + }, + "typeDescription": { + "name": "CCEOLAOLEKJ", + "found": true, + "file": "CCEOLAOLEKJ.cs", + "declaration": "public class CCEOLAOLEKJ : IFAIJAGLDFK", + "kind": "class", + "baseType": "IFAIJAGLDFK", + "properties": [ + { + "name": "ACBFDMLHFPB", + "type": "DateTime", + "accessibility": "public", + "kind": "property" + }, + { + "name": "AEMHDANDFOP", + "type": "ObscuredBool", + "accessibility": "public", + "kind": "property" + }, + { + "name": "BIOGKFGIMDG", + "type": "ObscuredString", + "accessibility": "public", + "kind": "property" + }, + { + "name": "CFJLJOKIJCN", + "type": "ObscuredString", + "accessibility": "public", + "kind": "property" + }, + { + "name": "GAINIOENNCG", + "type": "ObscuredInt", + "accessibility": "public", + "kind": "property" + }, + { + "name": "IHADBCJNDIP", + "type": "ObscuredString", + "accessibility": "public", + "kind": "property" + }, + { + "name": "ILOADNHKMHK", + "type": "ENNEGEELGMC", + "accessibility": "public", + "kind": "property" + }, + { + "name": "JIIMAIDFJPN", + "type": "ObscuredBool", + "accessibility": "public", + "kind": "property" + }, + { + "name": "OOJFBECGEAD", + "type": "ObscuredString", + "accessibility": "public", + "kind": "property" + } + ], + "fields": [], + "enumValues": [], + "wireKeys": [ + "accountId", + "profileImage", + "isJunior", + "platforms", + "username", + "displayName", + "createdAt" + ], + "isilFile": "CCEOLAOLEKJ.txt", + "isilType": "CCEOLAOLEKJ" + } + } + ] + }, + "get": { + "tags": [ + "account" + ], + "summary": "GET account/bulk?", + "description": "Inferred from Rec Room 2020 client literal `account/bulk?`. Expected result: Account profile summary array.", + "operationId": "client_account_account_bulk_get", + "parameters": [], + "responses": { + "200": { + "description": "Account profile summary array.", + "content": { + "application/json": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/AccountProfile" + } + } + } + } + }, + "204": { + "description": "Success with no body, used by some mutation endpoints." + }, + "400": { + "description": "Client-visible validation or bad request error.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "401": { + "description": "Authentication required or token invalid.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "500": { + "description": "Server error shape tolerated by client wrappers.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + } + }, + "x-recroom-client-route-literal": "account/bulk?", + "x-recroom-client-family": "account", + "x-recroom-client-service": "accounts/api", + "x-recroom-builds": [ + "2020.03.10", + "2020.12.18" + ], + "x-recroom-inferred-http-method": "get", + "x-recroom-evidence": [ + { + "type": "PEGGCEDHBOF", + "method": "RecRoom.Async.IPromise`1> NHCHADDMPFC(System.Collections.Generic.List`1 ILNGMAANNDG)", + "file": "PEGGCEDHBOF.txt", + "line": 4963 + }, + { + "type": "stringliteral.json", + "method": "", + "file": "Recnet-archive/decompiled/Il2CppDump/stringliteral.json", + "line": null + } + ], + "x-client-response-contracts": [ + { + "kind": "array", + "raw": "System.Collections.Generic.List`1", + "rootType": "CCEOLAOLEKJ", + "item": { + "kind": "object", + "raw": "CCEOLAOLEKJ", + "rootType": "CCEOLAOLEKJ" + }, + "typeDescription": { + "name": "CCEOLAOLEKJ", + "found": true, + "file": "CCEOLAOLEKJ.cs", + "declaration": "public class CCEOLAOLEKJ : IFAIJAGLDFK", + "kind": "class", + "baseType": "IFAIJAGLDFK", + "properties": [ + { + "name": "ACBFDMLHFPB", + "type": "DateTime", + "accessibility": "public", + "kind": "property" + }, + { + "name": "AEMHDANDFOP", + "type": "ObscuredBool", + "accessibility": "public", + "kind": "property" + }, + { + "name": "BIOGKFGIMDG", + "type": "ObscuredString", + "accessibility": "public", + "kind": "property" + }, + { + "name": "CFJLJOKIJCN", + "type": "ObscuredString", + "accessibility": "public", + "kind": "property" + }, + { + "name": "GAINIOENNCG", + "type": "ObscuredInt", + "accessibility": "public", + "kind": "property" + }, + { + "name": "IHADBCJNDIP", + "type": "ObscuredString", + "accessibility": "public", + "kind": "property" + }, + { + "name": "ILOADNHKMHK", + "type": "ENNEGEELGMC", + "accessibility": "public", + "kind": "property" + }, + { + "name": "JIIMAIDFJPN", + "type": "ObscuredBool", + "accessibility": "public", + "kind": "property" + }, + { + "name": "OOJFBECGEAD", + "type": "ObscuredString", + "accessibility": "public", + "kind": "property" + } + ], + "fields": [], + "enumValues": [], + "wireKeys": [ + "accountId", + "profileImage", + "isJunior", + "platforms", + "username", + "displayName", + "createdAt" + ], + "isilFile": "CCEOLAOLEKJ.txt", + "isilType": "CCEOLAOLEKJ" + } + } + ] + } + }, + "/account/create": { + "post": { + "tags": [ + "account" + ], + "summary": "POST account/create", + "description": "Inferred from Rec Room 2020 client literal `account/create`. Expected result: Account profile object or mutation success.", + "operationId": "client_account_account_create_post", + "parameters": [], + "responses": { + "200": { + "description": "Account profile object or mutation success.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/AccountProfile" + } + } + } + }, + "204": { + "description": "Success with no body, used by some mutation endpoints." + }, + "400": { + "description": "Client-visible validation or bad request error.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "401": { + "description": "Authentication required or token invalid.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "500": { + "description": "Server error shape tolerated by client wrappers.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + } + }, + "x-recroom-client-route-literal": "account/create", + "x-recroom-client-family": "account", + "x-recroom-client-service": "accounts/api", + "x-recroom-builds": [ + "2020.03.10", + "2020.12.18" + ], + "x-recroom-inferred-http-method": "post", + "x-recroom-evidence": [ + { + "type": "PEGGCEDHBOF", + "method": "RecRoom.Async.IPromise`1 HHFPNFANNEG()", + "file": "PEGGCEDHBOF.txt", + "line": 5418 + }, + { + "type": "stringliteral.json", + "method": "", + "file": "Recnet-archive/decompiled/Il2CppDump/stringliteral.json", + "line": null + } + ], + "requestBody": { + "required": false, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/GenericObject" + } + }, + "application/x-www-form-urlencoded": { + "schema": { + "$ref": "#/components/schemas/GenericObject" + } + }, + "multipart/form-data": { + "schema": { + "$ref": "#/components/schemas/GenericObject" + } + } + }, + "description": "Request body inferred. Exact DTO may be named in x-recroom-evidence method signature or companion DTO inventory." + }, + "x-client-response-contracts": [ + { + "kind": "object", + "raw": "CCEOLAOLEKJ", + "rootType": "CCEOLAOLEKJ", + "typeDescription": { + "name": "CCEOLAOLEKJ", + "found": true, + "file": "CCEOLAOLEKJ.cs", + "declaration": "public class CCEOLAOLEKJ : IFAIJAGLDFK", + "kind": "class", + "baseType": "IFAIJAGLDFK", + "properties": [ + { + "name": "ACBFDMLHFPB", + "type": "DateTime", + "accessibility": "public", + "kind": "property" + }, + { + "name": "AEMHDANDFOP", + "type": "ObscuredBool", + "accessibility": "public", + "kind": "property" + }, + { + "name": "BIOGKFGIMDG", + "type": "ObscuredString", + "accessibility": "public", + "kind": "property" + }, + { + "name": "CFJLJOKIJCN", + "type": "ObscuredString", + "accessibility": "public", + "kind": "property" + }, + { + "name": "GAINIOENNCG", + "type": "ObscuredInt", + "accessibility": "public", + "kind": "property" + }, + { + "name": "IHADBCJNDIP", + "type": "ObscuredString", + "accessibility": "public", + "kind": "property" + }, + { + "name": "ILOADNHKMHK", + "type": "ENNEGEELGMC", + "accessibility": "public", + "kind": "property" + }, + { + "name": "JIIMAIDFJPN", + "type": "ObscuredBool", + "accessibility": "public", + "kind": "property" + }, + { + "name": "OOJFBECGEAD", + "type": "ObscuredString", + "accessibility": "public", + "kind": "property" + } + ], + "fields": [], + "enumValues": [], + "wireKeys": [ + "accountId", + "profileImage", + "isJunior", + "platforms", + "username", + "displayName", + "createdAt" + ], + "isilFile": "CCEOLAOLEKJ.txt", + "isilType": "CCEOLAOLEKJ" + } + } + ] + } + }, + "/account/me/{tail}": { + "get": { + "tags": [ + "account" + ], + "summary": "GET account/me/", + "description": "Inferred from Rec Room 2020 client literal `account/me/`. Expected result: Account profile object or mutation success.", + "operationId": "client_account_account_me_tail_get", + "parameters": [ + { + "name": "tail", + "in": "path", + "required": true, + "schema": { + "type": "string" + }, + "description": "Format placeholder from client literal account/me/" + } + ], + "responses": { + "200": { + "description": "Account profile object or mutation success.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/AccountProfile" + } + } + } + }, + "204": { + "description": "Success with no body, used by some mutation endpoints." + }, + "400": { + "description": "Client-visible validation or bad request error.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "401": { + "description": "Authentication required or token invalid.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "500": { + "description": "Server error shape tolerated by client wrappers.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + } + }, + "x-recroom-client-route-literal": "account/me/", + "x-recroom-client-family": "account", + "x-recroom-client-service": "accounts/api", + "x-recroom-builds": [ + "2020.03.10", + "2020.12.18" + ], + "x-recroom-inferred-http-method": "get", + "x-recroom-evidence": [ + { + "type": "PEGGCEDHBOF", + "method": "RecRoom.Async.IPromise OEBEELJDOHE(BestHTTP.HTTPMethods APAICGIHAGJ, BestHTTP.Forms.HTTPUrlEncodedForm MDOPLMHIKLP, System.String LOOAPLFGOEN)", + "file": "PEGGCEDHBOF.txt", + "line": 6704 + }, + { + "type": "stringliteral.json", + "method": "", + "file": "Recnet-archive/decompiled/Il2CppDump/stringliteral.json", + "line": null + } + ], + "x-client-response-contracts": [ + { + "kind": "success-or-empty", + "raw": "RecRoom.Async.IPromise", + "rootType": "void/success", + "typeDescription": { + "name": "void/success", + "found": false, + "kind": "object", + "wireKeys": [] + } + } + ] + } + }, + "/account/me/changepassword": { + "post": { + "tags": [ + "account" + ], + "summary": "POST account/me/changepassword", + "description": "Inferred from Rec Room 2020 client literal `account/me/changepassword`. Expected result: Boolean/success password result.", + "operationId": "client_account_account_me_changepassword_post", + "parameters": [], + "responses": { + "200": { + "description": "Boolean/success password result.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/AccountProfile" + } + } + } + }, + "204": { + "description": "Success with no body, used by some mutation endpoints." + }, + "400": { + "description": "Client-visible validation or bad request error.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "401": { + "description": "Authentication required or token invalid.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "500": { + "description": "Server error shape tolerated by client wrappers.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + } + }, + "x-recroom-client-route-literal": "account/me/changepassword", + "x-recroom-client-family": "account", + "x-recroom-client-service": "auth/accounts", + "x-recroom-builds": [ + "2020.03.10", + "2020.12.18" + ], + "x-recroom-inferred-http-method": "post", + "x-recroom-evidence": [ + { + "type": "MHOKOMMOGKM", + "method": "RecRoom.Async.IPromise PGJBIIPMMJP(BestHTTP.Forms.HTTPUrlEncodedForm MDOPLMHIKLP, System.String MKIPICKCFDM)", + "file": "MHOKOMMOGKM.txt", + "line": 2582 + }, + { + "type": "stringliteral.json", + "method": "", + "file": "Recnet-archive/decompiled/Il2CppDump/stringliteral.json", + "line": null + } + ], + "requestBody": { + "required": false, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/GenericObject" + } + }, + "application/x-www-form-urlencoded": { + "schema": { + "$ref": "#/components/schemas/GenericObject" + } + }, + "multipart/form-data": { + "schema": { + "$ref": "#/components/schemas/GenericObject" + } + } + }, + "description": "Request body inferred. Exact DTO may be named in x-recroom-evidence method signature or companion DTO inventory." + }, + "x-client-response-contracts": [ + { + "kind": "success-or-empty", + "raw": "RecRoom.Async.IPromise", + "rootType": "void/success", + "typeDescription": { + "name": "void/success", + "found": false, + "kind": "object", + "wireKeys": [] + } + } + ] + } + }, + "/account/me/haspassword": { + "get": { + "tags": [ + "account" + ], + "summary": "GET account/me/haspassword", + "description": "Inferred from Rec Room 2020 client literal `account/me/haspassword`. Expected result: Boolean/success password result.", + "operationId": "client_account_account_me_haspassword_get", + "parameters": [], + "responses": { + "200": { + "description": "Boolean/success password result.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/AccountProfile" + } + } + } + }, + "204": { + "description": "Success with no body, used by some mutation endpoints." + }, + "400": { + "description": "Client-visible validation or bad request error.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "401": { + "description": "Authentication required or token invalid.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "500": { + "description": "Server error shape tolerated by client wrappers.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + } + }, + "x-recroom-client-route-literal": "account/me/haspassword", + "x-recroom-client-family": "account", + "x-recroom-client-service": "auth/accounts", + "x-recroom-builds": [ + "2020.03.10", + "2020.12.18" + ], + "x-recroom-inferred-http-method": "get", + "x-recroom-evidence": [ + { + "type": "MHOKOMMOGKM", + "method": "RecRoom.Async.IPromise`1 PPKFJEINCCN()", + "file": "MHOKOMMOGKM.txt", + "line": 2203 + }, + { + "type": "MHOKOMMOGKM+<>c", + "method": "System.Void b__32_0()", + "file": "MHOKOMMOGKM_NestedType___c.txt", + "line": 584 + }, + { + "type": "stringliteral.json", + "method": "", + "file": "Recnet-archive/decompiled/Il2CppDump/stringliteral.json", + "line": null + } + ], + "x-client-response-contracts": [ + { + "kind": "primitive", + "raw": "System.Boolean", + "rootType": "boolean", + "typeDescription": { + "name": "boolean", + "found": false, + "kind": "primitive", + "wireKeys": [] + } + }, + { + "kind": "unknown", + "raw": "", + "rootType": "unknown", + "typeDescription": { + "name": "unknown", + "found": false, + "kind": "object", + "wireKeys": [] + } + } + ] + } + }, + "/account/me": { + "get": { + "tags": [ + "account" + ], + "summary": "GET account/me", + "description": "Inferred from Rec Room 2020 client literal `account/me`. Expected result: Account profile object or mutation success.", + "operationId": "client_account_account_me_get", + "parameters": [], + "responses": { + "200": { + "description": "Account profile object or mutation success.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/AccountProfile" + } + } + } + }, + "204": { + "description": "Success with no body, used by some mutation endpoints." + }, + "400": { + "description": "Client-visible validation or bad request error.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "401": { + "description": "Authentication required or token invalid.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "500": { + "description": "Server error shape tolerated by client wrappers.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + } + }, + "x-recroom-client-route-literal": "account/me", + "x-recroom-client-family": "account", + "x-recroom-client-service": "accounts/api", + "x-recroom-builds": [ + "2020.03.10", + "2020.12.18" + ], + "x-recroom-inferred-http-method": "get", + "x-recroom-evidence": [ + { + "type": "PEGGCEDHBOF", + "method": "RecRoom.Async.IPromise`1 HJFCFNCKDFO()", + "file": "PEGGCEDHBOF.txt", + "line": 3576 + }, + { + "type": "stringliteral.json", + "method": "", + "file": "Recnet-archive/decompiled/Il2CppDump/stringliteral.json", + "line": null + } + ], + "x-client-response-contracts": [ + { + "kind": "object", + "raw": "JJGHAFKJBEI", + "rootType": "JJGHAFKJBEI", + "typeDescription": { + "name": "JJGHAFKJBEI", + "found": true, + "file": "JJGHAFKJBEI.cs", + "declaration": "public class JJGHAFKJBEI : CCEOLAOLEKJ", + "kind": "class", + "baseType": "CCEOLAOLEKJ", + "properties": [ + { + "name": "AFBNICLHPBN", + "type": "bool", + "accessibility": "public", + "kind": "property" + }, + { + "name": "CNJIJKAHHCG", + "type": "bool", + "accessibility": "public", + "kind": "property" + }, + { + "name": "DKFIDIDDHNO", + "type": "Nullable", + "accessibility": "public", + "kind": "property" + }, + { + "name": "FHKEMGJCOND", + "type": "Nullable", + "accessibility": "public", + "kind": "property" + }, + { + "name": "LNMPGOOPELO", + "type": "int", + "accessibility": "public", + "kind": "property" + }, + { + "name": "MCDGMOKGEPJ", + "type": "ObscuredString", + "accessibility": "public", + "kind": "property" + }, + { + "name": "MDHCHOLIJEF", + "type": "ObscuredString", + "accessibility": "public", + "kind": "property" + }, + { + "name": "ONBONOFFGFC", + "type": "CINICOCNPFB", + "accessibility": "public", + "kind": "property" + } + ], + "fields": [], + "enumValues": [], + "wireKeys": [ + "email", + "phone", + "juniorState", + "parentAccountId", + "availableUsernameChanges" + ], + "isilFile": "JJGHAFKJBEI.txt", + "isilType": "JJGHAFKJBEI", + "inherits": "CCEOLAOLEKJ", + "inheritedMembers": [ + { + "name": "ACBFDMLHFPB", + "type": "DateTime", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "CCEOLAOLEKJ" + }, + { + "name": "AEMHDANDFOP", + "type": "ObscuredBool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "CCEOLAOLEKJ" + }, + { + "name": "BIOGKFGIMDG", + "type": "ObscuredString", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "CCEOLAOLEKJ" + }, + { + "name": "CFJLJOKIJCN", + "type": "ObscuredString", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "CCEOLAOLEKJ" + }, + { + "name": "GAINIOENNCG", + "type": "ObscuredInt", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "CCEOLAOLEKJ" + }, + { + "name": "IHADBCJNDIP", + "type": "ObscuredString", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "CCEOLAOLEKJ" + }, + { + "name": "ILOADNHKMHK", + "type": "ENNEGEELGMC", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "CCEOLAOLEKJ" + }, + { + "name": "JIIMAIDFJPN", + "type": "ObscuredBool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "CCEOLAOLEKJ" + }, + { + "name": "OOJFBECGEAD", + "type": "ObscuredString", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "CCEOLAOLEKJ" + } + ], + "inheritedWireKeys": [ + "accountId", + "profileImage", + "isJunior", + "platforms", + "username", + "displayName", + "createdAt" + ] + } + } + ] + } + }, + "/account/recoverpassword": { + "post": { + "tags": [ + "account" + ], + "summary": "POST account/recoverpassword", + "description": "Inferred from Rec Room 2020 client literal `account/recoverpassword`. Expected result: Boolean/success password result.", + "operationId": "client_account_account_recoverpassword_post", + "parameters": [], + "responses": { + "200": { + "description": "Boolean/success password result.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/AccountProfile" + } + } + } + }, + "204": { + "description": "Success with no body, used by some mutation endpoints." + }, + "400": { + "description": "Client-visible validation or bad request error.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "401": { + "description": "Authentication required or token invalid.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "500": { + "description": "Server error shape tolerated by client wrappers.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + } + }, + "x-recroom-client-route-literal": "account/recoverpassword", + "x-recroom-client-family": "account", + "x-recroom-client-service": "auth/accounts", + "x-recroom-builds": [ + "2020.03.10", + "2020.12.18" + ], + "x-recroom-inferred-http-method": "post", + "x-recroom-evidence": [ + { + "type": "MHOKOMMOGKM", + "method": "RecRoom.Async.IPromise PMKEHDEDNCK(System.String LJKKKECANAB)", + "file": "MHOKOMMOGKM.txt", + "line": 2779 + }, + { + "type": "stringliteral.json", + "method": "", + "file": "Recnet-archive/decompiled/Il2CppDump/stringliteral.json", + "line": null + } + ], + "requestBody": { + "required": false, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/GenericObject" + } + }, + "application/x-www-form-urlencoded": { + "schema": { + "$ref": "#/components/schemas/GenericObject" + } + }, + "multipart/form-data": { + "schema": { + "$ref": "#/components/schemas/GenericObject" + } + } + }, + "description": "Request body inferred. Exact DTO may be named in x-recroom-evidence method signature or companion DTO inventory." + }, + "x-client-response-contracts": [ + { + "kind": "success-or-empty", + "raw": "RecRoom.Async.IPromise", + "rootType": "void/success", + "typeDescription": { + "name": "void/success", + "found": false, + "kind": "object", + "wireKeys": [] + } + } + ] + } + }, + "/account/search": { + "post": { + "tags": [ + "account" + ], + "summary": "POST account/search?name=", + "description": "Inferred from Rec Room 2020 client literal `account/search?name=`. Expected result: Account profile summary array.", + "operationId": "client_account_account_search_post", + "parameters": [ + { + "name": "name", + "in": "query", + "required": false, + "schema": { + "type": "string" + }, + "description": "Client query parameter" + } + ], + "responses": { + "200": { + "description": "Account profile summary array.", + "content": { + "application/json": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/AccountProfile" + } + } + } + } + }, + "204": { + "description": "Success with no body, used by some mutation endpoints." + }, + "400": { + "description": "Client-visible validation or bad request error.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "401": { + "description": "Authentication required or token invalid.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "500": { + "description": "Server error shape tolerated by client wrappers.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + } + }, + "x-recroom-client-route-literal": "account/search?name=", + "x-recroom-client-family": "account", + "x-recroom-client-service": "accounts/api", + "x-recroom-builds": [ + "2020.03.10", + "2020.12.18" + ], + "x-recroom-inferred-http-method": "post", + "x-recroom-evidence": [ + { + "type": "PEGGCEDHBOF", + "method": "RecRoom.Async.IPromise`1> IBDGAKHBIAJ(System.String CNBKKCJAHPP)", + "file": "PEGGCEDHBOF.txt", + "line": 9372 + }, + { + "type": "stringliteral.json", + "method": "", + "file": "Recnet-archive/decompiled/Il2CppDump/stringliteral.json", + "line": null + } + ], + "requestBody": { + "required": false, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/GenericObject" + } + }, + "application/x-www-form-urlencoded": { + "schema": { + "$ref": "#/components/schemas/GenericObject" + } + }, + "multipart/form-data": { + "schema": { + "$ref": "#/components/schemas/GenericObject" + } + } + }, + "description": "Request body inferred. Exact DTO may be named in x-recroom-evidence method signature or companion DTO inventory." + }, + "x-client-response-contracts": [ + { + "kind": "array", + "raw": "System.Collections.Generic.List`1", + "rootType": "CCEOLAOLEKJ", + "item": { + "kind": "object", + "raw": "CCEOLAOLEKJ", + "rootType": "CCEOLAOLEKJ" + }, + "typeDescription": { + "name": "CCEOLAOLEKJ", + "found": true, + "file": "CCEOLAOLEKJ.cs", + "declaration": "public class CCEOLAOLEKJ : IFAIJAGLDFK", + "kind": "class", + "baseType": "IFAIJAGLDFK", + "properties": [ + { + "name": "ACBFDMLHFPB", + "type": "DateTime", + "accessibility": "public", + "kind": "property" + }, + { + "name": "AEMHDANDFOP", + "type": "ObscuredBool", + "accessibility": "public", + "kind": "property" + }, + { + "name": "BIOGKFGIMDG", + "type": "ObscuredString", + "accessibility": "public", + "kind": "property" + }, + { + "name": "CFJLJOKIJCN", + "type": "ObscuredString", + "accessibility": "public", + "kind": "property" + }, + { + "name": "GAINIOENNCG", + "type": "ObscuredInt", + "accessibility": "public", + "kind": "property" + }, + { + "name": "IHADBCJNDIP", + "type": "ObscuredString", + "accessibility": "public", + "kind": "property" + }, + { + "name": "ILOADNHKMHK", + "type": "ENNEGEELGMC", + "accessibility": "public", + "kind": "property" + }, + { + "name": "JIIMAIDFJPN", + "type": "ObscuredBool", + "accessibility": "public", + "kind": "property" + }, + { + "name": "OOJFBECGEAD", + "type": "ObscuredString", + "accessibility": "public", + "kind": "property" + } + ], + "fields": [], + "enumValues": [], + "wireKeys": [ + "accountId", + "profileImage", + "isJunior", + "platforms", + "username", + "displayName", + "createdAt" + ], + "isilFile": "CCEOLAOLEKJ.txt", + "isilType": "CCEOLAOLEKJ" + } + } + ] + } + }, + "/api/activities/charades/v1/words": { + "get": { + "tags": [ + "activities" + ], + "summary": "GET api/activities/charades/v1/words", + "description": "Inferred from Rec Room 2020 client literal `api/activities/charades/v1/words`. Expected result: Generic object/array/success result inferred from client route.", + "operationId": "client_activities_api_activities_charades_v1_words_get", + "parameters": [], + "responses": { + "200": { + "description": "Generic object/array/success result inferred from client route.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/GenericObject" + } + } + } + }, + "204": { + "description": "Success with no body, used by some mutation endpoints." + }, + "400": { + "description": "Client-visible validation or bad request error.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "401": { + "description": "Authentication required or token invalid.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "500": { + "description": "Server error shape tolerated by client wrappers.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + } + }, + "x-recroom-client-route-literal": "api/activities/charades/v1/words", + "x-recroom-client-family": "activities", + "x-recroom-client-service": "api", + "x-recroom-builds": [ + "2020.03.10", + "2020.12.18" + ], + "x-recroom-inferred-http-method": "get", + "x-recroom-evidence": [ + { + "type": "CardBox", + "method": "RecRoom.Async.IPromise`1> HMNGGFKCDGD()", + "file": "CardBox.txt", + "line": 2422 + }, + { + "type": "CardBox", + "method": "System.Void Start()", + "file": "CardBox.txt", + "line": 2221 + }, + { + "type": "stringliteral.json", + "method": "", + "file": "Recnet-archive/decompiled/Il2CppDump/stringliteral.json", + "line": null + } + ], + "x-client-response-contracts": [ + { + "kind": "array", + "raw": "System.Collections.Generic.List`1", + "rootType": "JOHPEJAMBHG", + "item": { + "kind": "object", + "raw": "JOHPEJAMBHG", + "rootType": "JOHPEJAMBHG" + }, + "typeDescription": { + "name": "JOHPEJAMBHG", + "found": true, + "file": "JOHPEJAMBHG.cs", + "declaration": "public class JOHPEJAMBHG : IFAIJAGLDFK", + "kind": "class", + "baseType": "IFAIJAGLDFK", + "properties": [ + { + "name": "CJPFKEJAOBI", + "type": "enum", + "accessibility": "internal", + "kind": "property" + }, + { + "name": "GBLNDMFIGNM", + "type": "CJPFKEJAOBI", + "accessibility": "public", + "kind": "property" + }, + { + "name": "OIIMKMHNCMN", + "type": "string", + "accessibility": "public", + "kind": "property" + } + ], + "fields": [], + "enumValues": [], + "wireKeys": [ + "EN_US", + "Difficulty" + ], + "isilFile": "JOHPEJAMBHG.txt", + "isilType": "JOHPEJAMBHG" + } + }, + { + "kind": "unknown", + "raw": "", + "rootType": "unknown", + "typeDescription": { + "name": "unknown", + "found": false, + "kind": "object", + "wireKeys": [] + } + } + ] + } + }, + "/api/challenge": { + "get": { + "tags": [ + "activities" + ], + "summary": "GET api/challenge/", + "description": "Inferred from Rec Room 2020 client literal `api/challenge/`. Expected result: Generic object/array/success result inferred from client route.", + "operationId": "client_activities_api_challenge_get", + "parameters": [], + "responses": { + "200": { + "description": "Generic object/array/success result inferred from client route.", + "content": { + "application/json": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/GenericObject" + } + } + } + } + }, + "204": { + "description": "Success with no body, used by some mutation endpoints." + }, + "400": { + "description": "Client-visible validation or bad request error.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "401": { + "description": "Authentication required or token invalid.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "500": { + "description": "Server error shape tolerated by client wrappers.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + } + }, + "x-recroom-client-route-literal": "api/challenge/", + "x-recroom-client-family": "activities", + "x-recroom-client-service": "api", + "x-recroom-builds": [ + "2020.03.10", + "2020.12.18" + ], + "x-recroom-inferred-http-method": "get", + "x-recroom-evidence": [ + { + "type": "PDMJMMMNGJE", + "method": "System.Collections.IEnumerator EOBJGALHFNP(BPHGKAEDBPE+OJDJIJDNFHE AFLPGGJMPOE)", + "file": "PDMJMMMNGJE.txt", + "line": 156 + }, + { + "type": "PDMJMMMNGJE", + "method": "System.Void EKJPOJFLGFO(System.Int32 DKDDEIHEIBL, COLBJJAIEIO FMIIMOCIHCD)", + "file": "PDMJMMMNGJE.txt", + "line": 395 + }, + { + "type": "stringliteral.json", + "method": "", + "file": "Recnet-archive/decompiled/Il2CppDump/stringliteral.json", + "line": null + } + ], + "x-client-response-contracts": [ + { + "kind": "callback-or-coroutine", + "raw": "IEnumerator", + "rootType": "callback", + "typeDescription": { + "name": "callback", + "found": false, + "kind": "object", + "wireKeys": [] + } + }, + { + "kind": "unknown", + "raw": "", + "rootType": "unknown", + "typeDescription": { + "name": "unknown", + "found": false, + "kind": "object", + "wireKeys": [] + } + } + ] + } + }, + "/api/checklist/{tail}": { + "get": { + "tags": [ + "activities" + ], + "summary": "GET api/checklist/", + "description": "Inferred from Rec Room 2020 client literal `api/checklist/`. Expected result: Generic object/array/success result inferred from client route.", + "operationId": "client_activities_api_checklist_tail_get", + "parameters": [ + { + "name": "tail", + "in": "path", + "required": true, + "schema": { + "type": "string" + }, + "description": "Format placeholder from client literal api/checklist/" + } + ], + "responses": { + "200": { + "description": "Generic object/array/success result inferred from client route.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/GenericObject" + } + } + } + }, + "204": { + "description": "Success with no body, used by some mutation endpoints." + }, + "400": { + "description": "Client-visible validation or bad request error.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "401": { + "description": "Authentication required or token invalid.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "500": { + "description": "Server error shape tolerated by client wrappers.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + } + }, + "x-recroom-client-route-literal": "api/checklist/", + "x-recroom-client-family": "activities", + "x-recroom-client-service": "api", + "x-recroom-builds": [ + "2020.03.10" + ], + "x-recroom-inferred-http-method": "get", + "x-recroom-evidence": [ + { + "type": "stringliteral.json", + "method": "", + "file": "Recnet-archive/decompiled/Il2CppDump/stringliteral.json", + "line": null + } + ] + } + }, + "/api/royale": { + "get": { + "tags": [ + "activities" + ], + "summary": "GET api/royale/", + "description": "Inferred from Rec Room 2020 client literal `api/royale/`. Expected result: Generic object/array/success result inferred from client route.", + "operationId": "client_activities_api_royale_get", + "parameters": [], + "responses": { + "200": { + "description": "Generic object/array/success result inferred from client route.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/GenericObject" + } + } + } + }, + "204": { + "description": "Success with no body, used by some mutation endpoints." + }, + "400": { + "description": "Client-visible validation or bad request error.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "401": { + "description": "Authentication required or token invalid.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "500": { + "description": "Server error shape tolerated by client wrappers.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + } + }, + "x-recroom-client-route-literal": "api/royale/", + "x-recroom-client-family": "activities", + "x-recroom-client-service": "api", + "x-recroom-builds": [ + "2020.03.10", + "2020.12.18" + ], + "x-recroom-inferred-http-method": "get", + "x-recroom-evidence": [ + { + "type": "JDFDLJJGHIP", + "method": "RecRoom.Async.IPromise`1 APOAEDMOIHO()", + "file": "JDFDLJJGHIP.txt", + "line": 266 + }, + { + "type": "JDFDLJJGHIP", + "method": "RecRoom.Async.IPromise`1 NGEFKCDJAMF(JDFDLJJGHIP+MatchCompleteStats CNALBEPOKJJ)", + "file": "JDFDLJJGHIP.txt", + "line": 477 + }, + { + "type": "stringliteral.json", + "method": "", + "file": "Recnet-archive/decompiled/Il2CppDump/stringliteral.json", + "line": null + } + ], + "x-client-response-contracts": [ + { + "kind": "object", + "raw": "JDFDLJJGHIP+GHNGAENLIHA", + "rootType": "GHNGAENLIHA", + "typeDescription": { + "name": "GHNGAENLIHA", + "found": true, + "file": "JDFDLJJGHIP.cs", + "declaration": "internal class GHNGAENLIHA : IFAIJAGLDFK", + "kind": "class", + "baseType": "IFAIJAGLDFK", + "properties": [ + { + "name": "AGCAAIBPPIJ", + "type": "string", + "accessibility": "public", + "kind": "property" + }, + { + "name": "CBKDFHNBDEM", + "type": "long", + "accessibility": "public", + "kind": "property" + }, + { + "name": "COAHPEGOOEB", + "type": "int", + "accessibility": "public", + "kind": "property" + }, + { + "name": "COLADBAICGE", + "type": "long", + "accessibility": "public", + "kind": "property" + }, + { + "name": "GMAJAGFHAPO", + "type": "int", + "accessibility": "public", + "kind": "property" + }, + { + "name": "LINLLHBFIKF", + "type": "int", + "accessibility": "public", + "kind": "property" + }, + { + "name": "MPIAOHJIBOL", + "type": "long", + "accessibility": "public", + "kind": "property" + } + ], + "fields": [], + "enumValues": [], + "wireKeys": [ + "TotalXP", + "Level", + "RankIdx", + "RankName", + "CurrentLevelXPThreshold", + "NextLevelXPThreshold", + "NextLevelAcornReward" + ], + "isilFile": "JDFDLJJGHIP_NestedType_GHNGAENLIHA.txt", + "isilType": "JDFDLJJGHIP+GHNGAENLIHA" + } + }, + { + "kind": "object", + "raw": "JDFDLJJGHIP+JLNAMKBKPNG", + "rootType": "JLNAMKBKPNG", + "typeDescription": { + "name": "JLNAMKBKPNG", + "found": true, + "file": "JDFDLJJGHIP.cs", + "declaration": "internal class JLNAMKBKPNG : IFAIJAGLDFK", + "kind": "class", + "baseType": "IFAIJAGLDFK", + "properties": [ + { + "name": "AGLPLKLAGKC", + "type": "List", + "accessibility": "public", + "kind": "property" + }, + { + "name": "HBANCPAIDGN", + "type": "long", + "accessibility": "public", + "kind": "property" + }, + { + "name": "HNBFKOEFCIE", + "type": "List", + "accessibility": "public", + "kind": "property" + } + ], + "fields": [], + "enumValues": [], + "wireKeys": [ + "TotalXPAwarded" + ], + "isilFile": "JDFDLJJGHIP_NestedType_JLNAMKBKPNG.txt", + "isilType": "JDFDLJJGHIP+JLNAMKBKPNG" + } + } + ] + } + }, + "/api/announcement/v1/get": { + "get": { + "tags": [ + "announcements" + ], + "summary": "GET api/announcement/v1/get", + "description": "Inferred from Rec Room 2020 client literal `api/announcement/v1/get`. Expected result: Generic object/array/success result inferred from client route.", + "operationId": "client_announcements_api_announcement_v1_get_get", + "parameters": [], + "responses": { + "200": { + "description": "Generic object/array/success result inferred from client route.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Announcement" + } + } + } + }, + "204": { + "description": "Success with no body, used by some mutation endpoints." + }, + "400": { + "description": "Client-visible validation or bad request error.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "401": { + "description": "Authentication required or token invalid.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "500": { + "description": "Server error shape tolerated by client wrappers.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + } + }, + "x-recroom-client-route-literal": "api/announcement/v1/get", + "x-recroom-client-family": "announcements", + "x-recroom-client-service": "api", + "x-recroom-builds": [ + "2020.03.10" + ], + "x-recroom-inferred-http-method": "get", + "x-recroom-evidence": [ + { + "type": "stringliteral.json", + "method": "", + "file": "Recnet-archive/decompiled/Il2CppDump/stringliteral.json", + "line": null + } + ], + "x-client-response-contracts": [ + { + "kind": "array", + "raw": "System.Collections.Generic.List`1", + "rootType": "MKEKHKEHPLO", + "item": { + "kind": "object", + "raw": "MKEKHKEHPLO", + "rootType": "MKEKHKEHPLO" + }, + "typeDescription": { + "name": "MKEKHKEHPLO", + "found": true, + "file": "MKEKHKEHPLO.cs", + "declaration": "public class MKEKHKEHPLO : IFAIJAGLDFK", + "kind": "class", + "baseType": "IFAIJAGLDFK", + "properties": [ + { + "name": "ACBFDMLHFPB", + "type": "DateTime", + "accessibility": "public", + "kind": "property" + }, + { + "name": "AHGCOGFEEEE", + "type": "string", + "accessibility": "public", + "kind": "property" + }, + { + "name": "CDALJPJJHDK", + "type": "FIEFAAIHMAF", + "accessibility": "public", + "kind": "property" + }, + { + "name": "COJOHODFJOL", + "type": "AIOCBINPOML", + "accessibility": "public", + "kind": "property" + }, + { + "name": "DNFIODIACBF", + "type": "long", + "accessibility": "public", + "kind": "property" + }, + { + "name": "EAFCPPAEHJA", + "type": "string", + "accessibility": "public", + "kind": "property" + }, + { + "name": "FDPLOMBGJGJ", + "type": "string", + "accessibility": "public", + "kind": "property" + }, + { + "name": "HPCKFCEFFOJ", + "type": "MBALPDDOMME", + "accessibility": "public", + "kind": "property" + }, + { + "name": "KFJBDJMKHKG", + "type": "bool", + "accessibility": "public", + "kind": "property" + }, + { + "name": "LKIBJOMNFFD", + "type": "string", + "accessibility": "public", + "kind": "property" + }, + { + "name": "PFACIBEMBNO", + "type": "string", + "accessibility": "public", + "kind": "property" + }, + { + "name": "PFJIBIPNDCA", + "type": "FCIKKFJOMNO", + "accessibility": "public", + "kind": "property" + } + ], + "fields": [], + "enumValues": [], + "wireKeys": [ + "AnnouncementId", + "AnnouncementType", + "Title", + "Body", + "ImageName", + "LinkType", + "LinkName", + "LinkUri", + "Platform", + "CreatedAt" + ], + "isilFile": "MKEKHKEHPLO.txt", + "isilType": "MKEKHKEHPLO" + } + } + ] + }, + "post": { + "tags": [ + "misc" + ], + "summary": "POST api/announcement/v1/get", + "description": "Inferred from Rec Room 2020 client literal `api/announcement/v1/get`. Expected result: Generic object/array/success result inferred from client route.", + "operationId": "client_misc_api_announcement_v1_get_post", + "parameters": [], + "responses": { + "200": { + "description": "Generic object/array/success result inferred from client route.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/GenericObject" + } + } + } + }, + "204": { + "description": "Success with no body, used by some mutation endpoints." + }, + "400": { + "description": "Client-visible validation or bad request error.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "401": { + "description": "Authentication required or token invalid.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "500": { + "description": "Server error shape tolerated by client wrappers.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + } + }, + "x-recroom-client-route-literal": "api/announcement/v1/get", + "x-recroom-client-family": "misc", + "x-recroom-client-service": "api", + "x-recroom-builds": [ + "2020.12.18" + ], + "x-recroom-inferred-http-method": "post", + "x-recroom-evidence": [ + { + "type": "NLGOJMONPKG", + "method": "RecRoom.Async.IPromise`1> ADEEFOCPDFH()", + "file": "NLGOJMONPKG.txt", + "line": 379 + } + ], + "requestBody": { + "required": false, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/GenericObject" + } + }, + "application/x-www-form-urlencoded": { + "schema": { + "$ref": "#/components/schemas/GenericObject" + } + }, + "multipart/form-data": { + "schema": { + "$ref": "#/components/schemas/GenericObject" + } + } + }, + "description": "Request body inferred. Exact DTO may be named in x-recroom-evidence method signature or companion DTO inventory." + }, + "x-client-response-contracts": [ + { + "kind": "array", + "raw": "System.Collections.Generic.List`1", + "rootType": "MKEKHKEHPLO", + "item": { + "kind": "object", + "raw": "MKEKHKEHPLO", + "rootType": "MKEKHKEHPLO" + }, + "typeDescription": { + "name": "MKEKHKEHPLO", + "found": true, + "file": "MKEKHKEHPLO.cs", + "declaration": "public class MKEKHKEHPLO : IFAIJAGLDFK", + "kind": "class", + "baseType": "IFAIJAGLDFK", + "properties": [ + { + "name": "ACBFDMLHFPB", + "type": "DateTime", + "accessibility": "public", + "kind": "property" + }, + { + "name": "AHGCOGFEEEE", + "type": "string", + "accessibility": "public", + "kind": "property" + }, + { + "name": "CDALJPJJHDK", + "type": "FIEFAAIHMAF", + "accessibility": "public", + "kind": "property" + }, + { + "name": "COJOHODFJOL", + "type": "AIOCBINPOML", + "accessibility": "public", + "kind": "property" + }, + { + "name": "DNFIODIACBF", + "type": "long", + "accessibility": "public", + "kind": "property" + }, + { + "name": "EAFCPPAEHJA", + "type": "string", + "accessibility": "public", + "kind": "property" + }, + { + "name": "FDPLOMBGJGJ", + "type": "string", + "accessibility": "public", + "kind": "property" + }, + { + "name": "HPCKFCEFFOJ", + "type": "MBALPDDOMME", + "accessibility": "public", + "kind": "property" + }, + { + "name": "KFJBDJMKHKG", + "type": "bool", + "accessibility": "public", + "kind": "property" + }, + { + "name": "LKIBJOMNFFD", + "type": "string", + "accessibility": "public", + "kind": "property" + }, + { + "name": "PFACIBEMBNO", + "type": "string", + "accessibility": "public", + "kind": "property" + }, + { + "name": "PFJIBIPNDCA", + "type": "FCIKKFJOMNO", + "accessibility": "public", + "kind": "property" + } + ], + "fields": [], + "enumValues": [], + "wireKeys": [ + "AnnouncementId", + "AnnouncementType", + "Title", + "Body", + "ImageName", + "LinkType", + "LinkName", + "LinkUri", + "Platform", + "CreatedAt" + ], + "isilFile": "MKEKHKEHPLO.txt", + "isilType": "MKEKHKEHPLO" + } + } + ] + } + }, + "/api/avatar/v1/lockeditems": { + "get": { + "tags": [ + "avatar" + ], + "summary": "GET api/avatar/v1/lockeditems?", + "description": "Inferred from Rec Room 2020 client literal `api/avatar/v1/lockeditems?`. Expected result: Avatar item/outfit/gift state or mutation success.", + "operationId": "client_avatar_api_avatar_v1_lockeditems_get", + "parameters": [], + "responses": { + "200": { + "description": "Avatar item/outfit/gift state or mutation success.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/AvatarState" + } + } + } + }, + "204": { + "description": "Success with no body, used by some mutation endpoints." + }, + "400": { + "description": "Client-visible validation or bad request error.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "401": { + "description": "Authentication required or token invalid.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "500": { + "description": "Server error shape tolerated by client wrappers.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + } + }, + "x-recroom-client-route-literal": "api/avatar/v1/lockeditems?", + "x-recroom-client-family": "avatar", + "x-recroom-client-service": "api", + "x-recroom-builds": [ + "2020.12.18" + ], + "x-recroom-inferred-http-method": "get", + "x-recroom-evidence": [ + { + "type": "NLEKGNENMCO", + "method": "RecRoom.Async.IPromise`1> KLPMNFCFOLE(System.Collections.Generic.List`1 EGLBFNGKDLD)", + "file": "NLEKGNENMCO.txt", + "line": 2931 + } + ], + "x-client-response-contracts": [ + { + "kind": "array", + "raw": "System.Collections.Generic.List`1", + "rootType": "EPFHLDCPAOK", + "item": { + "kind": "object", + "raw": "NLEKGNENMCO+EPFHLDCPAOK", + "rootType": "EPFHLDCPAOK" + }, + "typeDescription": { + "name": "EPFHLDCPAOK", + "found": true, + "file": "NLEKGNENMCO.cs", + "declaration": "internal class EPFHLDCPAOK : IFAIJAGLDFK", + "kind": "class", + "baseType": "IFAIJAGLDFK", + "properties": [ + { + "name": "AJIOGFIIAPG", + "type": "JHDGJFEOGGA", + "accessibility": "public", + "kind": "property" + }, + { + "name": "EPBGPFCIOMJ", + "type": "MCEEFCNMNAH", + "accessibility": "public", + "kind": "property" + }, + { + "name": "FAHDIMDEHEK", + "type": "string", + "accessibility": "public", + "kind": "property" + }, + { + "name": "KENGPMDDCML", + "type": "string", + "accessibility": "public", + "kind": "property" + }, + { + "name": "MAMPKNECKCM", + "type": "string", + "accessibility": "public", + "kind": "property" + } + ], + "fields": [], + "enumValues": [], + "wireKeys": [ + "AvatarItemType", + "AvatarItemDesc", + "FriendlyName", + "Tooltip", + "Rarity" + ], + "isilFile": "NLEKGNENMCO_NestedType_EPFHLDCPAOK.txt", + "isilType": "NLEKGNENMCO+EPFHLDCPAOK" + } + } + ] + } + }, + "/api/avatar/v2/gifts/consume/{tail}": { + "post": { + "tags": [ + "avatar" + ], + "summary": "POST {0}v2/gifts/consume/", + "description": "Inferred from Rec Room 2020 client literal `{0}v2/gifts/consume/`. Expected result: Avatar item/outfit/gift state or mutation success.", + "operationId": "client_avatar_api_avatar_v2_gifts_consume_tail_post", + "parameters": [ + { + "name": "tail", + "in": "path", + "required": true, + "schema": { + "type": "string" + }, + "description": "Format placeholder from client literal {0}v2/gifts/consume/" + } + ], + "responses": { + "200": { + "description": "Avatar item/outfit/gift state or mutation success.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/AvatarState" + } + } + } + }, + "204": { + "description": "Success with no body, used by some mutation endpoints." + }, + "400": { + "description": "Client-visible validation or bad request error.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "401": { + "description": "Authentication required or token invalid.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "500": { + "description": "Server error shape tolerated by client wrappers.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + } + }, + "x-recroom-client-route-literal": "{0}v2/gifts/consume/", + "x-recroom-client-family": "avatar", + "x-recroom-client-service": "api", + "x-recroom-builds": [ + "2020.03.10", + "2020.12.18" + ], + "x-recroom-inferred-http-method": "post", + "x-recroom-evidence": [ + { + "type": "NLEKGNENMCO", + "method": "RecRoom.Async.IPromise PCKICLJFOBO(NLEKGNENMCO+LOCNECLOHCA HBBAANIPIMP, BPHGKAEDBPE+CBEOHBCIPEA AFLPGGJMPOE)", + "file": "NLEKGNENMCO.txt", + "line": 2488 + }, + { + "type": "stringliteral.json", + "method": "", + "file": "Recnet-archive/decompiled/Il2CppDump/stringliteral.json", + "line": null + } + ], + "requestBody": { + "required": false, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/GenericObject" + } + }, + "application/x-www-form-urlencoded": { + "schema": { + "$ref": "#/components/schemas/GenericObject" + } + }, + "multipart/form-data": { + "schema": { + "$ref": "#/components/schemas/GenericObject" + } + } + }, + "description": "Request body inferred. Exact DTO may be named in x-recroom-evidence method signature or companion DTO inventory." + }, + "x-client-response-contracts": [ + { + "kind": "success-or-empty", + "raw": "RecRoom.Async.IPromise", + "rootType": "void/success", + "typeDescription": { + "name": "void/success", + "found": false, + "kind": "object", + "wireKeys": [] + } + } + ] + } + }, + "/api/avatar": { + "get": { + "tags": [ + "avatar" + ], + "summary": "GET api/avatar/", + "description": "Inferred from Rec Room 2020 client literal `api/avatar/`. Expected result: Avatar item/outfit/gift state or mutation success.", + "operationId": "client_avatar_api_avatar_get", + "parameters": [], + "responses": { + "200": { + "description": "Avatar item/outfit/gift state or mutation success.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/AvatarState" + } + } + } + }, + "204": { + "description": "Success with no body, used by some mutation endpoints." + }, + "400": { + "description": "Client-visible validation or bad request error.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "401": { + "description": "Authentication required or token invalid.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "500": { + "description": "Server error shape tolerated by client wrappers.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + } + }, + "x-recroom-client-route-literal": "api/avatar/", + "x-recroom-client-family": "avatar", + "x-recroom-client-service": "api", + "x-recroom-builds": [ + "2020.03.10", + "2020.12.18" + ], + "x-recroom-inferred-http-method": "get", + "x-recroom-evidence": [ + { + "type": "NLEKGNENMCO", + "method": "RecRoom.Async.IPromise DLDENNPGADN()", + "file": "NLEKGNENMCO.txt", + "line": 3480 + }, + { + "type": "NLEKGNENMCO", + "method": "RecRoom.Async.IPromise PCKICLJFOBO(NLEKGNENMCO+LOCNECLOHCA HBBAANIPIMP, BPHGKAEDBPE+CBEOHBCIPEA AFLPGGJMPOE)", + "file": "NLEKGNENMCO.txt", + "line": 2486 + }, + { + "type": "NLEKGNENMCO", + "method": "RecRoom.Async.IPromise`1 JEKOPJCCHKB(GiftManager+LCLKAFOPBLD LHOMKMINCHH, System.Nullable`1 PCCLFNLJAMG, System.Boolean DKHOCHIJLBG)", + "file": "NLEKGNENMCO.txt", + "line": 1504 + }, + { + "type": "NLEKGNENMCO", + "method": "RecRoom.Async.IPromise`1 JEKOPJCCHKB(GiftManager+LCLKAFOPBLD LHOMKMINCHH)", + "file": "NLEKGNENMCO.txt", + "line": 1794 + }, + { + "type": "NLEKGNENMCO", + "method": "RecRoom.Async.IPromise`1> EPOGKGLKJHD()", + "file": "NLEKGNENMCO.txt", + "line": 1226 + }, + { + "type": "NLEKGNENMCO", + "method": "System.Collections.IEnumerator FHJKJMFABGK(BPHGKAEDBPE+CBEOHBCIPEA AFLPGGJMPOE)", + "file": "NLEKGNENMCO.txt", + "line": 3101 + }, + { + "type": "NLEKGNENMCO", + "method": "System.Collections.IEnumerator KLHANCHHJBD(CDNNKFCCONN GKDEAOJLJFI, BPHGKAEDBPE+CBEOHBCIPEA AFLPGGJMPOE)", + "file": "NLEKGNENMCO.txt", + "line": 3229 + }, + { + "type": "NLEKGNENMCO", + "method": "System.Void OFJLGPLDEKJ()", + "file": "NLEKGNENMCO.txt", + "line": 3748 + }, + { + "type": "NLEKGNENMCO+LFBBKJKCJFF", + "method": "System.Boolean MoveNext()", + "file": "NLEKGNENMCO_NestedType_LFBBKJKCJFF.txt", + "line": 147 + }, + { + "type": "stringliteral.json", + "method": "", + "file": "Recnet-archive/decompiled/Il2CppDump/stringliteral.json", + "line": null + } + ], + "x-client-response-contracts": [ + { + "kind": "success-or-empty", + "raw": "RecRoom.Async.IPromise", + "rootType": "void/success", + "typeDescription": { + "name": "void/success", + "found": false, + "kind": "object", + "wireKeys": [] + } + }, + { + "kind": "object", + "raw": "NLEKGNENMCO+LOCNECLOHCA", + "rootType": "LOCNECLOHCA", + "typeDescription": { + "name": "LOCNECLOHCA", + "found": true, + "file": "NLEKGNENMCO.cs", + "declaration": "internal class LOCNECLOHCA : IFAIJAGLDFK, AKJKEMONOIL", + "kind": "class", + "baseType": "IFAIJAGLDFK", + "properties": [ + { + "name": "AJIOGFIIAPG", + "type": "Nullable", + "accessibility": "public", + "kind": "property" + }, + { + "name": "BFLGAPOCLAJ", + "type": "Material", + "accessibility": "public", + "kind": "property" + }, + { + "name": "BGKNIPNNCJG", + "type": "string", + "accessibility": "public", + "kind": "property" + }, + { + "name": "CPNPKDLCDOO", + "type": "LCLKAFOPBLD", + "accessibility": "public", + "kind": "property" + }, + { + "name": "DFPPNHINEFO", + "type": "string", + "accessibility": "public", + "kind": "property" + }, + { + "name": "DLHOLKNMICH", + "type": "bool", + "accessibility": "public", + "kind": "property" + }, + { + "name": "DMAJINBPHKJ", + "type": "bool", + "accessibility": "public", + "kind": "property" + }, + { + "name": "FBLEJONFPAK", + "type": "bool", + "accessibility": "public", + "kind": "property" + }, + { + "name": "GBNGBHCOPFO", + "type": "string", + "accessibility": "public", + "kind": "property" + }, + { + "name": "HAKADJAKPDO", + "type": "string", + "accessibility": "public", + "kind": "property" + }, + { + "name": "HEKHNHAJGIN", + "type": "bool", + "accessibility": "public", + "kind": "property" + }, + { + "name": "HOAJNDDEDAM", + "type": "int", + "accessibility": "public", + "kind": "property" + }, + { + "name": "IILDJGJNFIM", + "type": "ACDKILABNNC", + "accessibility": "public", + "kind": "property" + }, + { + "name": "JOPFCBEDAAD", + "type": "GiftPackageVariant", + "accessibility": "public", + "kind": "property" + }, + { + "name": "JPOHGBCEJEJ", + "type": "long", + "accessibility": "public", + "kind": "property" + }, + { + "name": "KLLFPPFHNKF", + "type": "ENNEGEELGMC", + "accessibility": "public", + "kind": "property" + }, + { + "name": "LKFAKLCIINB", + "type": "Nullable", + "accessibility": "public", + "kind": "property" + }, + { + "name": "LLEJOHGPNAE", + "type": "string", + "accessibility": "public", + "kind": "property" + }, + { + "name": "LMNGODEMEHA", + "type": "bool", + "accessibility": "public", + "kind": "property" + }, + { + "name": "MPIODIBAMDO", + "type": "Nullable", + "accessibility": "public", + "kind": "property" + }, + { + "name": "NOEDKAJMLKN", + "type": "bool", + "accessibility": "public", + "kind": "property" + }, + { + "name": "NPOIFNDCBAN", + "type": "MCEEFCNMNAH", + "accessibility": "public", + "kind": "property" + }, + { + "name": "PDACCOLOMCB", + "type": "int", + "accessibility": "public", + "kind": "property" + }, + { + "name": "PDEHPKNHLBL", + "type": "string", + "accessibility": "public", + "kind": "property" + }, + { + "name": "PFJIBIPNDCA", + "type": "FCIKKFJOMNO", + "accessibility": "public", + "kind": "property" + } + ], + "fields": [ + { + "name": "CFINKBCLFJF", + "type": "bool", + "kind": "field" + } + ], + "enumValues": [], + "wireKeys": [ + "Message", + "Id", + "FromPlayerId", + "AvatarItemType", + "AvatarItemDesc", + "ConsumableItemDesc", + "EquipmentPrefabName", + "EquipmentModificationGuid", + "Platform", + "PlatformsToSpawnOn", + "BalanceType", + "CurrencyType", + "Currency", + "Xp", + "GiftRarity", + "GiftContext" + ], + "isilFile": "NLEKGNENMCO_NestedType_LOCNECLOHCA.txt", + "isilType": "NLEKGNENMCO+LOCNECLOHCA" + } + }, + { + "kind": "array", + "raw": "System.Collections.Generic.List`1", + "rootType": "LOCNECLOHCA", + "item": { + "kind": "object", + "raw": "NLEKGNENMCO+LOCNECLOHCA", + "rootType": "LOCNECLOHCA" + }, + "typeDescription": { + "name": "LOCNECLOHCA", + "found": true, + "file": "NLEKGNENMCO.cs", + "declaration": "internal class LOCNECLOHCA : IFAIJAGLDFK, AKJKEMONOIL", + "kind": "class", + "baseType": "IFAIJAGLDFK", + "properties": [ + { + "name": "AJIOGFIIAPG", + "type": "Nullable", + "accessibility": "public", + "kind": "property" + }, + { + "name": "BFLGAPOCLAJ", + "type": "Material", + "accessibility": "public", + "kind": "property" + }, + { + "name": "BGKNIPNNCJG", + "type": "string", + "accessibility": "public", + "kind": "property" + }, + { + "name": "CPNPKDLCDOO", + "type": "LCLKAFOPBLD", + "accessibility": "public", + "kind": "property" + }, + { + "name": "DFPPNHINEFO", + "type": "string", + "accessibility": "public", + "kind": "property" + }, + { + "name": "DLHOLKNMICH", + "type": "bool", + "accessibility": "public", + "kind": "property" + }, + { + "name": "DMAJINBPHKJ", + "type": "bool", + "accessibility": "public", + "kind": "property" + }, + { + "name": "FBLEJONFPAK", + "type": "bool", + "accessibility": "public", + "kind": "property" + }, + { + "name": "GBNGBHCOPFO", + "type": "string", + "accessibility": "public", + "kind": "property" + }, + { + "name": "HAKADJAKPDO", + "type": "string", + "accessibility": "public", + "kind": "property" + }, + { + "name": "HEKHNHAJGIN", + "type": "bool", + "accessibility": "public", + "kind": "property" + }, + { + "name": "HOAJNDDEDAM", + "type": "int", + "accessibility": "public", + "kind": "property" + }, + { + "name": "IILDJGJNFIM", + "type": "ACDKILABNNC", + "accessibility": "public", + "kind": "property" + }, + { + "name": "JOPFCBEDAAD", + "type": "GiftPackageVariant", + "accessibility": "public", + "kind": "property" + }, + { + "name": "JPOHGBCEJEJ", + "type": "long", + "accessibility": "public", + "kind": "property" + }, + { + "name": "KLLFPPFHNKF", + "type": "ENNEGEELGMC", + "accessibility": "public", + "kind": "property" + }, + { + "name": "LKFAKLCIINB", + "type": "Nullable", + "accessibility": "public", + "kind": "property" + }, + { + "name": "LLEJOHGPNAE", + "type": "string", + "accessibility": "public", + "kind": "property" + }, + { + "name": "LMNGODEMEHA", + "type": "bool", + "accessibility": "public", + "kind": "property" + }, + { + "name": "MPIODIBAMDO", + "type": "Nullable", + "accessibility": "public", + "kind": "property" + }, + { + "name": "NOEDKAJMLKN", + "type": "bool", + "accessibility": "public", + "kind": "property" + }, + { + "name": "NPOIFNDCBAN", + "type": "MCEEFCNMNAH", + "accessibility": "public", + "kind": "property" + }, + { + "name": "PDACCOLOMCB", + "type": "int", + "accessibility": "public", + "kind": "property" + }, + { + "name": "PDEHPKNHLBL", + "type": "string", + "accessibility": "public", + "kind": "property" + }, + { + "name": "PFJIBIPNDCA", + "type": "FCIKKFJOMNO", + "accessibility": "public", + "kind": "property" + } + ], + "fields": [ + { + "name": "CFINKBCLFJF", + "type": "bool", + "kind": "field" + } + ], + "enumValues": [], + "wireKeys": [ + "Message", + "Id", + "FromPlayerId", + "AvatarItemType", + "AvatarItemDesc", + "ConsumableItemDesc", + "EquipmentPrefabName", + "EquipmentModificationGuid", + "Platform", + "PlatformsToSpawnOn", + "BalanceType", + "CurrencyType", + "Currency", + "Xp", + "GiftRarity", + "GiftContext" + ], + "isilFile": "NLEKGNENMCO_NestedType_LOCNECLOHCA.txt", + "isilType": "NLEKGNENMCO+LOCNECLOHCA" + } + }, + { + "kind": "callback-or-coroutine", + "raw": "IEnumerator", + "rootType": "callback", + "typeDescription": { + "name": "callback", + "found": false, + "kind": "object", + "wireKeys": [] + } + }, + { + "kind": "unknown", + "raw": "", + "rootType": "unknown", + "typeDescription": { + "name": "unknown", + "found": false, + "kind": "object", + "wireKeys": [] + } + } + ] + } + }, + "/": { + "get": { + "tags": [ + "bootstrap" + ], + "summary": "GET https://ns.rec.net/?v=2", + "description": "Inferred from Rec Room 2020 client literal `https://ns.rec.net/?v=2`. Expected result: Name-server/bootstrap response or external account page.", + "operationId": "client_bootstrap__get", + "parameters": [ + { + "name": "v", + "in": "query", + "required": false, + "schema": { + "type": "string" + }, + "description": "Client value template: 2" + } + ], + "responses": { + "200": { + "description": "Name-server/bootstrap response or external account page.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/NameServerResponse" + } + } + } + }, + "204": { + "description": "Success with no body, used by some mutation endpoints." + }, + "400": { + "description": "Client-visible validation or bad request error.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "401": { + "description": "Authentication required or token invalid.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "500": { + "description": "Server error shape tolerated by client wrappers.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + } + }, + "x-recroom-client-route-literal": "https://ns.rec.net/?v=2", + "x-recroom-client-family": "bootstrap", + "x-recroom-client-service": "nameserver/www", + "x-recroom-builds": [ + "2020.03.10" + ], + "x-recroom-inferred-http-method": "get", + "x-recroom-evidence": [ + { + "type": "stringliteral.json", + "method": "", + "file": "Recnet-archive/decompiled/Il2CppDump/stringliteral.json", + "line": null + } + ] + } + }, + "/password/recover": { + "get": { + "tags": [ + "bootstrap" + ], + "summary": "GET https://rec.net/password/recover", + "description": "Inferred from Rec Room 2020 client literal `https://rec.net/password/recover`. Expected result: Name-server/bootstrap response or external account page.", + "operationId": "client_bootstrap_password_recover_get", + "parameters": [], + "responses": { + "200": { + "description": "Name-server/bootstrap response or external account page.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/NameServerResponse" + } + } + } + }, + "204": { + "description": "Success with no body, used by some mutation endpoints." + }, + "400": { + "description": "Client-visible validation or bad request error.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "401": { + "description": "Authentication required or token invalid.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "500": { + "description": "Server error shape tolerated by client wrappers.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + } + }, + "x-recroom-client-route-literal": "https://rec.net/password/recover", + "x-recroom-client-family": "bootstrap", + "x-recroom-client-service": "nameserver/www", + "x-recroom-builds": [ + "2020.03.10" + ], + "x-recroom-inferred-http-method": "get", + "x-recroom-evidence": [ + { + "type": "stringliteral.json", + "method": "", + "file": "Recnet-archive/decompiled/Il2CppDump/stringliteral.json", + "line": null + } + ] + } + }, + "/api/bugreporting": { + "post": { + "tags": [ + "bug-reporting" + ], + "summary": "POST api/bugreporting/", + "description": "Inferred from Rec Room 2020 client literal `api/bugreporting/`. Expected result: Generic object/array/success result inferred from client route.", + "operationId": "client_bug_reporting_api_bugreporting_post", + "parameters": [], + "responses": { + "200": { + "description": "Generic object/array/success result inferred from client route.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Success" + } + } + } + }, + "204": { + "description": "Success with no body, used by some mutation endpoints." + }, + "400": { + "description": "Client-visible validation or bad request error.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "401": { + "description": "Authentication required or token invalid.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "500": { + "description": "Server error shape tolerated by client wrappers.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + } + }, + "x-recroom-client-route-literal": "api/bugreporting/", + "x-recroom-client-family": "bug-reporting", + "x-recroom-client-service": "api", + "x-recroom-builds": [ + "2020.03.10", + "2020.12.18" + ], + "x-recroom-inferred-http-method": "post", + "x-recroom-evidence": [ + { + "type": "RecNet.BugReporting", + "method": "RecRoom.Async.IPromise ReportBug(System.String summary, System.String description, System.Byte[] screenshotData, System.Byte[] outputLogData, System.String testCaseKey)", + "file": "RecNet\\BugReporting.txt", + "line": 146 + }, + { + "type": "stringliteral.json", + "method": "", + "file": "Recnet-archive/decompiled/Il2CppDump/stringliteral.json", + "line": null + } + ], + "requestBody": { + "required": false, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/GenericObject" + } + }, + "application/x-www-form-urlencoded": { + "schema": { + "$ref": "#/components/schemas/GenericObject" + } + }, + "multipart/form-data": { + "schema": { + "$ref": "#/components/schemas/GenericObject" + } + } + }, + "description": "Request body inferred. Exact DTO may be named in x-recroom-evidence method signature or companion DTO inventory." + }, + "x-client-response-contracts": [ + { + "kind": "success-or-empty", + "raw": "RecRoom.Async.IPromise", + "rootType": "void/success", + "typeDescription": { + "name": "void/success", + "found": false, + "kind": "object", + "wireKeys": [] + } + } + ] + } + }, + "/{p0}/club/{p1}": { + "get": { + "tags": [ + "clubs" + ], + "summary": "GET {0}/club/{1}", + "description": "Inferred from Rec Room 2020 client literal `{0}/club/{1}`. Expected result: Club object/list or mutation success.", + "operationId": "client_clubs_p0_club_p1_get", + "parameters": [ + { + "name": "p0", + "in": "path", + "required": true, + "schema": { + "type": "string" + }, + "description": "Format placeholder from client literal {0}/club/{1}" + }, + { + "name": "p1", + "in": "path", + "required": true, + "schema": { + "type": "string" + }, + "description": "Format placeholder from client literal {0}/club/{1}" + } + ], + "responses": { + "200": { + "description": "Club object/list or mutation success.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Club" + } + } + } + }, + "204": { + "description": "Success with no body, used by some mutation endpoints." + }, + "400": { + "description": "Client-visible validation or bad request error.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "401": { + "description": "Authentication required or token invalid.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "500": { + "description": "Server error shape tolerated by client wrappers.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + } + }, + "x-recroom-client-route-literal": "{0}/club/{1}", + "x-recroom-client-family": "clubs", + "x-recroom-client-service": "clubs", + "x-recroom-builds": [ + "2020.12.18" + ], + "x-recroom-inferred-http-method": "get", + "x-recroom-evidence": [ + { + "type": "GNPDMBPGHBH", + "method": "System.String JDEIGGFCBPF(System.Int64 AOMBLGBCENO)", + "file": "GNPDMBPGHBH.txt", + "line": 630 + } + ], + "x-client-response-contracts": [ + { + "kind": "unknown", + "raw": "", + "rootType": "unknown", + "typeDescription": { + "name": "unknown", + "found": false, + "kind": "object", + "wireKeys": [] + } + } + ] + } + }, + "/announcements/club/{p0}/{p1}/read": { + "get": { + "tags": [ + "clubs" + ], + "summary": "GET announcements/club/{0}/{1}/read", + "description": "Inferred from Rec Room 2020 client literal `announcements/club/{0}/{1}/read`. Expected result: Club object/list or mutation success.", + "operationId": "client_clubs_announcements_club_p0_p1_read_get", + "parameters": [ + { + "name": "p0", + "in": "path", + "required": true, + "schema": { + "type": "string" + }, + "description": "Format placeholder from client literal announcements/club/{0}/{1}/read" + }, + { + "name": "p1", + "in": "path", + "required": true, + "schema": { + "type": "string" + }, + "description": "Format placeholder from client literal announcements/club/{0}/{1}/read" + } + ], + "responses": { + "200": { + "description": "Club object/list or mutation success.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Club" + } + } + } + }, + "204": { + "description": "Success with no body, used by some mutation endpoints." + }, + "400": { + "description": "Client-visible validation or bad request error.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "401": { + "description": "Authentication required or token invalid.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "500": { + "description": "Server error shape tolerated by client wrappers.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + } + }, + "x-recroom-client-route-literal": "announcements/club/{0}/{1}/read", + "x-recroom-client-family": "clubs", + "x-recroom-client-service": "clubs", + "x-recroom-builds": [ + "2020.12.18" + ], + "x-recroom-inferred-http-method": "get", + "x-recroom-evidence": [ + { + "type": "JDJGIBLMFKK", + "method": "RecRoom.Async.IPromise OFBNAHIOHON(System.Int64 AOMBLGBCENO, System.Int64 LOHFFDEGIMK)", + "file": "JDJGIBLMFKK.txt", + "line": 2865 + } + ], + "x-client-response-contracts": [ + { + "kind": "success-or-empty", + "raw": "RecRoom.Async.IPromise", + "rootType": "void/success", + "typeDescription": { + "name": "void/success", + "found": false, + "kind": "object", + "wireKeys": [] + } + } + ] + } + }, + "/announcements/club/{p0}/{p1}": { + "get": { + "tags": [ + "clubs" + ], + "summary": "GET announcements/club/{0}/{1}", + "description": "Inferred from Rec Room 2020 client literal `announcements/club/{0}/{1}`. Expected result: Club object/list or mutation success.", + "operationId": "client_clubs_announcements_club_p0_p1_get", + "parameters": [ + { + "name": "p0", + "in": "path", + "required": true, + "schema": { + "type": "string" + }, + "description": "Format placeholder from client literal announcements/club/{0}/{1}" + }, + { + "name": "p1", + "in": "path", + "required": true, + "schema": { + "type": "string" + }, + "description": "Format placeholder from client literal announcements/club/{0}/{1}" + } + ], + "responses": { + "200": { + "description": "Club object/list or mutation success.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Club" + } + } + } + }, + "204": { + "description": "Success with no body, used by some mutation endpoints." + }, + "400": { + "description": "Client-visible validation or bad request error.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "401": { + "description": "Authentication required or token invalid.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "500": { + "description": "Server error shape tolerated by client wrappers.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + } + }, + "x-recroom-client-route-literal": "announcements/club/{0}/{1}", + "x-recroom-client-family": "clubs", + "x-recroom-client-service": "clubs", + "x-recroom-builds": [ + "2020.12.18" + ], + "x-recroom-inferred-http-method": "get", + "x-recroom-evidence": [ + { + "type": "JDJGIBLMFKK", + "method": "RecRoom.Async.IPromise HCGFMHLEDPE(System.Int64 AOMBLGBCENO, System.Int64 LOHFFDEGIMK)", + "file": "JDJGIBLMFKK.txt", + "line": 2462 + }, + { + "type": "JDJGIBLMFKK", + "method": "RecRoom.Async.IPromise POHLPICNHJD(JDJGIBLMFKK+FOEPIDJCLMC JMFLHIIJFKL)", + "file": "JDJGIBLMFKK.txt", + "line": 789 + } + ], + "x-client-response-contracts": [ + { + "kind": "success-or-empty", + "raw": "RecRoom.Async.IPromise", + "rootType": "void/success", + "typeDescription": { + "name": "void/success", + "found": false, + "kind": "object", + "wireKeys": [] + } + } + ] + } + }, + "/announcements/club/{p0}": { + "get": { + "tags": [ + "clubs" + ], + "summary": "GET announcements/club/{0}", + "description": "Inferred from Rec Room 2020 client literal `announcements/club/{0}`. Expected result: Club object/list or mutation success.", + "operationId": "client_clubs_announcements_club_p0_get", + "parameters": [ + { + "name": "p0", + "in": "path", + "required": true, + "schema": { + "type": "string" + }, + "description": "Format placeholder from client literal announcements/club/{0}" + } + ], + "responses": { + "200": { + "description": "Club object/list or mutation success.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Club" + } + } + } + }, + "204": { + "description": "Success with no body, used by some mutation endpoints." + }, + "400": { + "description": "Client-visible validation or bad request error.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "401": { + "description": "Authentication required or token invalid.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "500": { + "description": "Server error shape tolerated by client wrappers.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + } + }, + "x-recroom-client-route-literal": "announcements/club/{0}", + "x-recroom-client-family": "clubs", + "x-recroom-client-service": "clubs", + "x-recroom-builds": [ + "2020.12.18" + ], + "x-recroom-inferred-http-method": "get", + "x-recroom-evidence": [ + { + "type": "JDJGIBLMFKK", + "method": "RecRoom.Async.IPromise`1 ONCKANJNGLA(System.Int64 AOMBLGBCENO)", + "file": "JDJGIBLMFKK.txt", + "line": 1167 + }, + { + "type": "JDJGIBLMFKK", + "method": "RecRoom.Async.IPromise`1 AAOLBAJFCOJ(JDJGIBLMFKK+DHPONMGBFJE JMFLHIIJFKL)", + "file": "JDJGIBLMFKK.txt", + "line": 398 + } + ], + "x-client-response-contracts": [ + { + "kind": "object", + "raw": "HPACLJHLHBG", + "rootType": "HPACLJHLHBG", + "typeDescription": { + "name": "HPACLJHLHBG", + "found": true, + "file": "HPACLJHLHBG.cs", + "declaration": "public class HPACLJHLHBG : IFAIJAGLDFK", + "kind": "class", + "baseType": "IFAIJAGLDFK", + "properties": [ + { + "name": "CCGOEDABKNN", + "type": "long", + "accessibility": "public", + "kind": "property" + }, + { + "name": "IILPDPCODAE", + "type": "bool", + "accessibility": "public", + "kind": "property" + }, + { + "name": "JCCDJAADFPE", + "type": "long", + "accessibility": "public", + "kind": "property" + } + ], + "fields": [], + "enumValues": [], + "wireKeys": [ + "clubId", + "LastReadAnnouncementId" + ], + "isilFile": "HPACLJHLHBG.txt", + "isilType": "HPACLJHLHBG" + } + }, + { + "kind": "primitive", + "raw": "System.Int64", + "rootType": "number", + "typeDescription": { + "name": "number", + "found": false, + "kind": "object", + "wireKeys": [] + } + } + ] + } + }, + "/api/clubreporting/v1/report": { + "post": { + "tags": [ + "clubs" + ], + "summary": "POST api/clubreporting/v1/report", + "description": "Inferred from Rec Room 2020 client literal `api/clubreporting/v1/report`. Expected result: Club object/list or mutation success.", + "operationId": "client_clubs_api_clubreporting_v1_report_post", + "parameters": [], + "responses": { + "200": { + "description": "Club object/list or mutation success.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Club" + } + } + } + }, + "204": { + "description": "Success with no body, used by some mutation endpoints." + }, + "400": { + "description": "Client-visible validation or bad request error.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "401": { + "description": "Authentication required or token invalid.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "500": { + "description": "Server error shape tolerated by client wrappers.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + } + }, + "x-recroom-client-route-literal": "api/clubreporting/v1/report", + "x-recroom-client-family": "clubs", + "x-recroom-client-service": "clubs", + "x-recroom-builds": [ + "2020.12.18" + ], + "x-recroom-inferred-http-method": "post", + "x-recroom-evidence": [ + { + "type": "JDJGIBLMFKK", + "method": "RecRoom.Async.IPromise LBOOMIDNLPP(System.Int64 AOMBLGBCENO, LCCEEFHOBEN+CJFENPHAAHI MEABFEIBEMP, System.String EFDBFLPKHKA)", + "file": "JDJGIBLMFKK.txt", + "line": 21791 + } + ], + "requestBody": { + "required": false, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/GenericObject" + } + }, + "application/x-www-form-urlencoded": { + "schema": { + "$ref": "#/components/schemas/GenericObject" + } + }, + "multipart/form-data": { + "schema": { + "$ref": "#/components/schemas/GenericObject" + } + } + }, + "description": "Request body inferred. Exact DTO may be named in x-recroom-evidence method signature or companion DTO inventory." + }, + "x-client-response-contracts": [ + { + "kind": "success-or-empty", + "raw": "RecRoom.Async.IPromise", + "rootType": "void/success", + "typeDescription": { + "name": "void/success", + "found": false, + "kind": "object", + "wireKeys": [] + } + } + ] + } + }, + "/club/{p0}/additionalimage/{p1}": { + "post": { + "tags": [ + "clubs" + ], + "summary": "POST club/{0}/additionalimage/{1}", + "description": "Inferred from Rec Room 2020 client literal `club/{0}/additionalimage/{1}`. Expected result: Club object/list or mutation success.", + "operationId": "client_clubs_club_p0_additionalimage_p1_post", + "parameters": [ + { + "name": "p0", + "in": "path", + "required": true, + "schema": { + "type": "string" + }, + "description": "Format placeholder from client literal club/{0}/additionalimage/{1}" + }, + { + "name": "p1", + "in": "path", + "required": true, + "schema": { + "type": "string" + }, + "description": "Format placeholder from client literal club/{0}/additionalimage/{1}" + } + ], + "responses": { + "200": { + "description": "Club object/list or mutation success.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Club" + } + } + } + }, + "204": { + "description": "Success with no body, used by some mutation endpoints." + }, + "400": { + "description": "Client-visible validation or bad request error.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "401": { + "description": "Authentication required or token invalid.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "500": { + "description": "Server error shape tolerated by client wrappers.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + } + }, + "x-recroom-client-route-literal": "club/{0}/additionalimage/{1}", + "x-recroom-client-family": "clubs", + "x-recroom-client-service": "clubs", + "x-recroom-builds": [ + "2020.12.18" + ], + "x-recroom-inferred-http-method": "post", + "x-recroom-evidence": [ + { + "type": "JDJGIBLMFKK", + "method": "RecRoom.Async.IPromise`1 ELLIKHHEJFM(System.Int64 AOMBLGBCENO, System.Int32 EFBDCIJMFGD, System.String HFLPBHHAFIO)", + "file": "JDJGIBLMFKK.txt", + "line": 12298 + } + ], + "requestBody": { + "required": false, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/GenericObject" + } + }, + "application/x-www-form-urlencoded": { + "schema": { + "$ref": "#/components/schemas/GenericObject" + } + }, + "multipart/form-data": { + "schema": { + "$ref": "#/components/schemas/GenericObject" + } + } + }, + "description": "Request body inferred. Exact DTO may be named in x-recroom-evidence method signature or companion DTO inventory." + }, + "x-client-response-contracts": [ + { + "kind": "object", + "raw": "PIHMJGCGNLP", + "rootType": "PIHMJGCGNLP", + "typeDescription": { + "name": "PIHMJGCGNLP", + "found": true, + "file": "PIHMJGCGNLP.cs", + "declaration": "public class PIHMJGCGNLP : IFAIJAGLDFK", + "kind": "class", + "baseType": "IFAIJAGLDFK", + "properties": [ + { + "name": "AHDBBFIDKBN", + "type": "PPGPAHNMGEC", + "accessibility": "public", + "kind": "property" + }, + { + "name": "CMGPCPKLHLF", + "type": "JHEEFBMODPG", + "accessibility": "public", + "kind": "property" + }, + { + "name": "DOOHAKMALHL", + "type": "List", + "accessibility": "public", + "kind": "property" + }, + { + "name": "IIIFDCAPMEA", + "type": "JHEEFBMODPG", + "accessibility": "public", + "kind": "property" + }, + { + "name": "KIFHEKPKILL", + "type": "JHEEFBMODPG", + "accessibility": "public", + "kind": "property" + }, + { + "name": "NDAGAGNHNPA", + "type": "PLILLKHMNDA", + "accessibility": "public", + "kind": "property" + }, + { + "name": "NJHNHHMCILD", + "type": "JHEEFBMODPG", + "accessibility": "public", + "kind": "property" + }, + { + "name": "OPIIBPFEODL", + "type": "List", + "accessibility": "public", + "kind": "property" + } + ], + "fields": [], + "enumValues": [], + "wireKeys": [ + "Club", + "CoownerPermissions", + "ModeratorPermissions", + "MemberPermissions", + "MyMembershipType" + ], + "isilFile": "PIHMJGCGNLP.txt", + "isilType": "PIHMJGCGNLP" + } + } + ] + } + }, + "/club/{p0}/clubhouse": { + "get": { + "tags": [ + "clubs" + ], + "summary": "GET club/{0}/clubhouse", + "description": "Inferred from Rec Room 2020 client literal `club/{0}/clubhouse`. Expected result: Club object/list or mutation success.", + "operationId": "client_clubs_club_p0_clubhouse_get", + "parameters": [ + { + "name": "p0", + "in": "path", + "required": true, + "schema": { + "type": "string" + }, + "description": "Format placeholder from client literal club/{0}/clubhouse" + } + ], + "responses": { + "200": { + "description": "Club object/list or mutation success.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Club" + } + } + } + }, + "204": { + "description": "Success with no body, used by some mutation endpoints." + }, + "400": { + "description": "Client-visible validation or bad request error.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "401": { + "description": "Authentication required or token invalid.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "500": { + "description": "Server error shape tolerated by client wrappers.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + } + }, + "x-recroom-client-route-literal": "club/{0}/clubhouse", + "x-recroom-client-family": "clubs", + "x-recroom-client-service": "clubs", + "x-recroom-builds": [ + "2020.12.18" + ], + "x-recroom-inferred-http-method": "get", + "x-recroom-evidence": [ + { + "type": "JDJGIBLMFKK", + "method": "RecRoom.Async.IPromise`1 HELJLMINDFD(System.Int64 AOMBLGBCENO, System.Nullable`1 HNHLJONGKHB)", + "file": "JDJGIBLMFKK.txt", + "line": 12449 + } + ], + "x-client-response-contracts": [ + { + "kind": "object", + "raw": "PIHMJGCGNLP", + "rootType": "PIHMJGCGNLP", + "typeDescription": { + "name": "PIHMJGCGNLP", + "found": true, + "file": "PIHMJGCGNLP.cs", + "declaration": "public class PIHMJGCGNLP : IFAIJAGLDFK", + "kind": "class", + "baseType": "IFAIJAGLDFK", + "properties": [ + { + "name": "AHDBBFIDKBN", + "type": "PPGPAHNMGEC", + "accessibility": "public", + "kind": "property" + }, + { + "name": "CMGPCPKLHLF", + "type": "JHEEFBMODPG", + "accessibility": "public", + "kind": "property" + }, + { + "name": "DOOHAKMALHL", + "type": "List", + "accessibility": "public", + "kind": "property" + }, + { + "name": "IIIFDCAPMEA", + "type": "JHEEFBMODPG", + "accessibility": "public", + "kind": "property" + }, + { + "name": "KIFHEKPKILL", + "type": "JHEEFBMODPG", + "accessibility": "public", + "kind": "property" + }, + { + "name": "NDAGAGNHNPA", + "type": "PLILLKHMNDA", + "accessibility": "public", + "kind": "property" + }, + { + "name": "NJHNHHMCILD", + "type": "JHEEFBMODPG", + "accessibility": "public", + "kind": "property" + }, + { + "name": "OPIIBPFEODL", + "type": "List", + "accessibility": "public", + "kind": "property" + } + ], + "fields": [], + "enumValues": [], + "wireKeys": [ + "Club", + "CoownerPermissions", + "ModeratorPermissions", + "MemberPermissions", + "MyMembershipType" + ], + "isilFile": "PIHMJGCGNLP.txt", + "isilType": "PIHMJGCGNLP" + } + } + ] + } + }, + "/club/{p0}/details": { + "get": { + "tags": [ + "clubs" + ], + "summary": "GET club/{0}/details", + "description": "Inferred from Rec Room 2020 client literal `club/{0}/details`. Expected result: Club object/list or mutation success.", + "operationId": "client_clubs_club_p0_details_get", + "parameters": [ + { + "name": "p0", + "in": "path", + "required": true, + "schema": { + "type": "string" + }, + "description": "Format placeholder from client literal club/{0}/details" + } + ], + "responses": { + "200": { + "description": "Club object/list or mutation success.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Club" + } + } + } + }, + "204": { + "description": "Success with no body, used by some mutation endpoints." + }, + "400": { + "description": "Client-visible validation or bad request error.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "401": { + "description": "Authentication required or token invalid.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "500": { + "description": "Server error shape tolerated by client wrappers.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + } + }, + "x-recroom-client-route-literal": "club/{0}/details", + "x-recroom-client-family": "clubs", + "x-recroom-client-service": "clubs", + "x-recroom-builds": [ + "2020.12.18" + ], + "x-recroom-inferred-http-method": "get", + "x-recroom-evidence": [ + { + "type": "JDJGIBLMFKK+BKCHBCIJHBN", + "method": "RecRoom.Async.IPromise`1 b__0()", + "file": "JDJGIBLMFKK_NestedType_BKCHBCIJHBN.txt", + "line": 202 + } + ], + "x-client-response-contracts": [ + { + "kind": "object", + "raw": "PIHMJGCGNLP", + "rootType": "PIHMJGCGNLP", + "typeDescription": { + "name": "PIHMJGCGNLP", + "found": true, + "file": "PIHMJGCGNLP.cs", + "declaration": "public class PIHMJGCGNLP : IFAIJAGLDFK", + "kind": "class", + "baseType": "IFAIJAGLDFK", + "properties": [ + { + "name": "AHDBBFIDKBN", + "type": "PPGPAHNMGEC", + "accessibility": "public", + "kind": "property" + }, + { + "name": "CMGPCPKLHLF", + "type": "JHEEFBMODPG", + "accessibility": "public", + "kind": "property" + }, + { + "name": "DOOHAKMALHL", + "type": "List", + "accessibility": "public", + "kind": "property" + }, + { + "name": "IIIFDCAPMEA", + "type": "JHEEFBMODPG", + "accessibility": "public", + "kind": "property" + }, + { + "name": "KIFHEKPKILL", + "type": "JHEEFBMODPG", + "accessibility": "public", + "kind": "property" + }, + { + "name": "NDAGAGNHNPA", + "type": "PLILLKHMNDA", + "accessibility": "public", + "kind": "property" + }, + { + "name": "NJHNHHMCILD", + "type": "JHEEFBMODPG", + "accessibility": "public", + "kind": "property" + }, + { + "name": "OPIIBPFEODL", + "type": "List", + "accessibility": "public", + "kind": "property" + } + ], + "fields": [], + "enumValues": [], + "wireKeys": [ + "Club", + "CoownerPermissions", + "ModeratorPermissions", + "MemberPermissions", + "MyMembershipType" + ], + "isilFile": "PIHMJGCGNLP.txt", + "isilType": "PIHMJGCGNLP" + } + } + ] + } + }, + "/club/{p0}/mainimage": { + "post": { + "tags": [ + "clubs" + ], + "summary": "POST club/{0}/mainimage", + "description": "Inferred from Rec Room 2020 client literal `club/{0}/mainimage`. Expected result: Club object/list or mutation success.", + "operationId": "client_clubs_club_p0_mainimage_post", + "parameters": [ + { + "name": "p0", + "in": "path", + "required": true, + "schema": { + "type": "string" + }, + "description": "Format placeholder from client literal club/{0}/mainimage" + } + ], + "responses": { + "200": { + "description": "Club object/list or mutation success.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Club" + } + } + } + }, + "204": { + "description": "Success with no body, used by some mutation endpoints." + }, + "400": { + "description": "Client-visible validation or bad request error.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "401": { + "description": "Authentication required or token invalid.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "500": { + "description": "Server error shape tolerated by client wrappers.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + } + }, + "x-recroom-client-route-literal": "club/{0}/mainimage", + "x-recroom-client-family": "clubs", + "x-recroom-client-service": "clubs", + "x-recroom-builds": [ + "2020.12.18" + ], + "x-recroom-inferred-http-method": "post", + "x-recroom-evidence": [ + { + "type": "JDJGIBLMFKK", + "method": "RecRoom.Async.IPromise`1 OBFKAFGALDM(System.Int64 AOMBLGBCENO, System.String HFLPBHHAFIO)", + "file": "JDJGIBLMFKK.txt", + "line": 12138 + } + ], + "requestBody": { + "required": false, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/GenericObject" + } + }, + "application/x-www-form-urlencoded": { + "schema": { + "$ref": "#/components/schemas/GenericObject" + } + }, + "multipart/form-data": { + "schema": { + "$ref": "#/components/schemas/GenericObject" + } + } + }, + "description": "Request body inferred. Exact DTO may be named in x-recroom-evidence method signature or companion DTO inventory." + }, + "x-client-response-contracts": [ + { + "kind": "object", + "raw": "PIHMJGCGNLP", + "rootType": "PIHMJGCGNLP", + "typeDescription": { + "name": "PIHMJGCGNLP", + "found": true, + "file": "PIHMJGCGNLP.cs", + "declaration": "public class PIHMJGCGNLP : IFAIJAGLDFK", + "kind": "class", + "baseType": "IFAIJAGLDFK", + "properties": [ + { + "name": "AHDBBFIDKBN", + "type": "PPGPAHNMGEC", + "accessibility": "public", + "kind": "property" + }, + { + "name": "CMGPCPKLHLF", + "type": "JHEEFBMODPG", + "accessibility": "public", + "kind": "property" + }, + { + "name": "DOOHAKMALHL", + "type": "List", + "accessibility": "public", + "kind": "property" + }, + { + "name": "IIIFDCAPMEA", + "type": "JHEEFBMODPG", + "accessibility": "public", + "kind": "property" + }, + { + "name": "KIFHEKPKILL", + "type": "JHEEFBMODPG", + "accessibility": "public", + "kind": "property" + }, + { + "name": "NDAGAGNHNPA", + "type": "PLILLKHMNDA", + "accessibility": "public", + "kind": "property" + }, + { + "name": "NJHNHHMCILD", + "type": "JHEEFBMODPG", + "accessibility": "public", + "kind": "property" + }, + { + "name": "OPIIBPFEODL", + "type": "List", + "accessibility": "public", + "kind": "property" + } + ], + "fields": [], + "enumValues": [], + "wireKeys": [ + "Club", + "CoownerPermissions", + "ModeratorPermissions", + "MemberPermissions", + "MyMembershipType" + ], + "isilFile": "PIHMJGCGNLP.txt", + "isilType": "PIHMJGCGNLP" + } + } + ] + } + }, + "/club/{p0}/members/{p1}": { + "get": { + "tags": [ + "clubs" + ], + "summary": "GET club/{0}/members/{1}", + "description": "Inferred from Rec Room 2020 client literal `club/{0}/members/{1}`. Expected result: Club member list/object or membership mutation success.", + "operationId": "client_clubs_club_p0_members_p1_get", + "parameters": [ + { + "name": "p0", + "in": "path", + "required": true, + "schema": { + "type": "string" + }, + "description": "Format placeholder from client literal club/{0}/members/{1}" + }, + { + "name": "p1", + "in": "path", + "required": true, + "schema": { + "type": "string" + }, + "description": "Format placeholder from client literal club/{0}/members/{1}" + } + ], + "responses": { + "200": { + "description": "Club member list/object or membership mutation success.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Club" + } + } + } + }, + "204": { + "description": "Success with no body, used by some mutation endpoints." + }, + "400": { + "description": "Client-visible validation or bad request error.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "401": { + "description": "Authentication required or token invalid.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "500": { + "description": "Server error shape tolerated by client wrappers.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + } + }, + "x-recroom-client-route-literal": "club/{0}/members/{1}", + "x-recroom-client-family": "clubs", + "x-recroom-client-service": "clubs", + "x-recroom-builds": [ + "2020.12.18" + ], + "x-recroom-inferred-http-method": "get", + "x-recroom-evidence": [ + { + "type": "JDJGIBLMFKK", + "method": "RecRoom.Async.IPromise`1 BIOJPNFPNCE(System.Int64 AOMBLGBCENO, System.Int32 GKLPIFBPGOD)", + "file": "JDJGIBLMFKK.txt", + "line": 15776 + } + ], + "x-client-response-contracts": [ + { + "kind": "object", + "raw": "MFOAODGNGKB", + "rootType": "MFOAODGNGKB", + "typeDescription": { + "name": "MFOAODGNGKB", + "found": true, + "file": "MFOAODGNGKB.cs", + "declaration": "public class MFOAODGNGKB : IFAIJAGLDFK", + "kind": "class", + "baseType": "IFAIJAGLDFK", + "properties": [ + { + "name": "ACBFDMLHFPB", + "type": "DateTime", + "accessibility": "public", + "kind": "property" + }, + { + "name": "CCGOEDABKNN", + "type": "long", + "accessibility": "public", + "kind": "property" + }, + { + "name": "GAINIOENNCG", + "type": "int", + "accessibility": "public", + "kind": "property" + }, + { + "name": "JNMPJNKEJAC", + "type": "PPGPAHNMGEC", + "accessibility": "public", + "kind": "property" + } + ], + "fields": [], + "enumValues": [], + "wireKeys": [ + "AccountId", + "ClubId", + "MembershipType", + "CreatedAt" + ], + "isilFile": "MFOAODGNGKB.txt", + "isilType": "MFOAODGNGKB" + } + } + ] + } + }, + "/club/{p0}/members/acceptinvite": { + "post": { + "tags": [ + "clubs" + ], + "summary": "POST club/{0}/members/acceptinvite", + "description": "Inferred from Rec Room 2020 client literal `club/{0}/members/acceptinvite`. Expected result: Club member list/object or membership mutation success.", + "operationId": "client_clubs_club_p0_members_acceptinvite_post", + "parameters": [ + { + "name": "p0", + "in": "path", + "required": true, + "schema": { + "type": "string" + }, + "description": "Format placeholder from client literal club/{0}/members/acceptinvite" + } + ], + "responses": { + "200": { + "description": "Club member list/object or membership mutation success.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Club" + } + } + } + }, + "204": { + "description": "Success with no body, used by some mutation endpoints." + }, + "400": { + "description": "Client-visible validation or bad request error.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "401": { + "description": "Authentication required or token invalid.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "500": { + "description": "Server error shape tolerated by client wrappers.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + } + }, + "x-recroom-client-route-literal": "club/{0}/members/acceptinvite", + "x-recroom-client-family": "clubs", + "x-recroom-client-service": "clubs", + "x-recroom-builds": [ + "2020.12.18" + ], + "x-recroom-inferred-http-method": "post", + "x-recroom-evidence": [ + { + "type": "JDGDFALBCDJ", + "method": "RecRoom.Async.IPromise HHLPPOBHMOC()", + "file": "JDGDFALBCDJ.txt", + "line": 426 + }, + { + "type": "JDJGIBLMFKK", + "method": "RecRoom.Async.IPromise FAOINNCBNFC(System.Int64 AOMBLGBCENO)", + "file": "JDJGIBLMFKK.txt", + "line": 20586 + } + ], + "requestBody": { + "required": false, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/GenericObject" + } + }, + "application/x-www-form-urlencoded": { + "schema": { + "$ref": "#/components/schemas/GenericObject" + } + }, + "multipart/form-data": { + "schema": { + "$ref": "#/components/schemas/GenericObject" + } + } + }, + "description": "Request body inferred. Exact DTO may be named in x-recroom-evidence method signature or companion DTO inventory." + }, + "x-client-response-contracts": [ + { + "kind": "success-or-empty", + "raw": "RecRoom.Async.IPromise", + "rootType": "void/success", + "typeDescription": { + "name": "void/success", + "found": false, + "kind": "object", + "wireKeys": [] + } + } + ] + } + }, + "/club/{p0}/members/acceptrequest": { + "post": { + "tags": [ + "clubs" + ], + "summary": "POST club/{0}/members/acceptrequest", + "description": "Inferred from Rec Room 2020 client literal `club/{0}/members/acceptrequest`. Expected result: Club member list/object or membership mutation success.", + "operationId": "client_clubs_club_p0_members_acceptrequest_post", + "parameters": [ + { + "name": "p0", + "in": "path", + "required": true, + "schema": { + "type": "string" + }, + "description": "Format placeholder from client literal club/{0}/members/acceptrequest" + } + ], + "responses": { + "200": { + "description": "Club member list/object or membership mutation success.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Club" + } + } + } + }, + "204": { + "description": "Success with no body, used by some mutation endpoints." + }, + "400": { + "description": "Client-visible validation or bad request error.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "401": { + "description": "Authentication required or token invalid.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "500": { + "description": "Server error shape tolerated by client wrappers.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + } + }, + "x-recroom-client-route-literal": "club/{0}/members/acceptrequest", + "x-recroom-client-family": "clubs", + "x-recroom-client-service": "clubs", + "x-recroom-builds": [ + "2020.12.18" + ], + "x-recroom-inferred-http-method": "post", + "x-recroom-evidence": [ + { + "type": "JDJGIBLMFKK", + "method": "RecRoom.Async.IPromise AGEGBKKPJNN(System.Int64 AOMBLGBCENO, System.Int32 GKLPIFBPGOD)", + "file": "JDJGIBLMFKK.txt", + "line": 18418 + } + ], + "requestBody": { + "required": false, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/GenericObject" + } + }, + "application/x-www-form-urlencoded": { + "schema": { + "$ref": "#/components/schemas/GenericObject" + } + }, + "multipart/form-data": { + "schema": { + "$ref": "#/components/schemas/GenericObject" + } + } + }, + "description": "Request body inferred. Exact DTO may be named in x-recroom-evidence method signature or companion DTO inventory." + }, + "x-client-response-contracts": [ + { + "kind": "success-or-empty", + "raw": "RecRoom.Async.IPromise", + "rootType": "void/success", + "typeDescription": { + "name": "void/success", + "found": false, + "kind": "object", + "wireKeys": [] + } + } + ] + } + }, + "/club/{p0}/members/acceptrequests": { + "post": { + "tags": [ + "clubs" + ], + "summary": "POST club/{0}/members/acceptrequests", + "description": "Inferred from Rec Room 2020 client literal `club/{0}/members/acceptrequests`. Expected result: Club member list/object or membership mutation success.", + "operationId": "client_clubs_club_p0_members_acceptrequests_post", + "parameters": [ + { + "name": "p0", + "in": "path", + "required": true, + "schema": { + "type": "string" + }, + "description": "Format placeholder from client literal club/{0}/members/acceptrequests" + } + ], + "responses": { + "200": { + "description": "Club member list/object or membership mutation success.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Club" + } + } + } + }, + "204": { + "description": "Success with no body, used by some mutation endpoints." + }, + "400": { + "description": "Client-visible validation or bad request error.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "401": { + "description": "Authentication required or token invalid.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "500": { + "description": "Server error shape tolerated by client wrappers.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + } + }, + "x-recroom-client-route-literal": "club/{0}/members/acceptrequests", + "x-recroom-client-family": "clubs", + "x-recroom-client-service": "clubs", + "x-recroom-builds": [ + "2020.12.18" + ], + "x-recroom-inferred-http-method": "post", + "x-recroom-evidence": [ + { + "type": "JDJGIBLMFKK", + "method": "RecRoom.Async.IPromise FKPIMLCILLI(System.Int64 AOMBLGBCENO, System.Collections.Generic.IEnumerable`1 ILNGMAANNDG)", + "file": "JDJGIBLMFKK.txt", + "line": 18831 + } + ], + "requestBody": { + "required": false, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/GenericObject" + } + }, + "application/x-www-form-urlencoded": { + "schema": { + "$ref": "#/components/schemas/GenericObject" + } + }, + "multipart/form-data": { + "schema": { + "$ref": "#/components/schemas/GenericObject" + } + } + }, + "description": "Request body inferred. Exact DTO may be named in x-recroom-evidence method signature or companion DTO inventory." + }, + "x-client-response-contracts": [ + { + "kind": "success-or-empty", + "raw": "RecRoom.Async.IPromise", + "rootType": "void/success", + "typeDescription": { + "name": "void/success", + "found": false, + "kind": "object", + "wireKeys": [] + } + } + ] + } + }, + "/club/{p0}/members/ban": { + "post": { + "tags": [ + "clubs" + ], + "summary": "POST club/{0}/members/ban", + "description": "Inferred from Rec Room 2020 client literal `club/{0}/members/ban`. Expected result: Club member list/object or membership mutation success.", + "operationId": "client_clubs_club_p0_members_ban_post", + "parameters": [ + { + "name": "p0", + "in": "path", + "required": true, + "schema": { + "type": "string" + }, + "description": "Format placeholder from client literal club/{0}/members/ban" + } + ], + "responses": { + "200": { + "description": "Club member list/object or membership mutation success.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Club" + } + } + } + }, + "204": { + "description": "Success with no body, used by some mutation endpoints." + }, + "400": { + "description": "Client-visible validation or bad request error.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "401": { + "description": "Authentication required or token invalid.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "500": { + "description": "Server error shape tolerated by client wrappers.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + } + }, + "x-recroom-client-route-literal": "club/{0}/members/ban", + "x-recroom-client-family": "clubs", + "x-recroom-client-service": "clubs", + "x-recroom-builds": [ + "2020.12.18" + ], + "x-recroom-inferred-http-method": "post", + "x-recroom-evidence": [ + { + "type": "JDJGIBLMFKK", + "method": "RecRoom.Async.IPromise OKICAHCMPFL(System.Int64 AOMBLGBCENO, System.Int32 GKLPIFBPGOD)", + "file": "JDJGIBLMFKK.txt", + "line": 21265 + } + ], + "requestBody": { + "required": false, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/GenericObject" + } + }, + "application/x-www-form-urlencoded": { + "schema": { + "$ref": "#/components/schemas/GenericObject" + } + }, + "multipart/form-data": { + "schema": { + "$ref": "#/components/schemas/GenericObject" + } + } + }, + "description": "Request body inferred. Exact DTO may be named in x-recroom-evidence method signature or companion DTO inventory." + }, + "x-client-response-contracts": [ + { + "kind": "success-or-empty", + "raw": "RecRoom.Async.IPromise", + "rootType": "void/success", + "typeDescription": { + "name": "void/success", + "found": false, + "kind": "object", + "wireKeys": [] + } + } + ] + } + }, + "/club/{p0}/members/changetype": { + "post": { + "tags": [ + "clubs" + ], + "summary": "POST club/{0}/members/changetype", + "description": "Inferred from Rec Room 2020 client literal `club/{0}/members/changetype`. Expected result: Club member list/object or membership mutation success.", + "operationId": "client_clubs_club_p0_members_changetype_post", + "parameters": [ + { + "name": "p0", + "in": "path", + "required": true, + "schema": { + "type": "string" + }, + "description": "Format placeholder from client literal club/{0}/members/changetype" + } + ], + "responses": { + "200": { + "description": "Club member list/object or membership mutation success.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Club" + } + } + } + }, + "204": { + "description": "Success with no body, used by some mutation endpoints." + }, + "400": { + "description": "Client-visible validation or bad request error.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "401": { + "description": "Authentication required or token invalid.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "500": { + "description": "Server error shape tolerated by client wrappers.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + } + }, + "x-recroom-client-route-literal": "club/{0}/members/changetype", + "x-recroom-client-family": "clubs", + "x-recroom-client-service": "clubs", + "x-recroom-builds": [ + "2020.12.18" + ], + "x-recroom-inferred-http-method": "post", + "x-recroom-evidence": [ + { + "type": "JDJGIBLMFKK", + "method": "RecRoom.Async.IPromise JLBCCEIBFJJ(System.Int64 AOMBLGBCENO, System.Int32 GKLPIFBPGOD, PPGPAHNMGEC OCHEGLOFMEA)", + "file": "JDJGIBLMFKK.txt", + "line": 21094 + } + ], + "requestBody": { + "required": false, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/GenericObject" + } + }, + "application/x-www-form-urlencoded": { + "schema": { + "$ref": "#/components/schemas/GenericObject" + } + }, + "multipart/form-data": { + "schema": { + "$ref": "#/components/schemas/GenericObject" + } + } + }, + "description": "Request body inferred. Exact DTO may be named in x-recroom-evidence method signature or companion DTO inventory." + }, + "x-client-response-contracts": [ + { + "kind": "success-or-empty", + "raw": "RecRoom.Async.IPromise", + "rootType": "void/success", + "typeDescription": { + "name": "void/success", + "found": false, + "kind": "object", + "wireKeys": [] + } + } + ] + } + }, + "/club/{p0}/members/declineinvite": { + "post": { + "tags": [ + "clubs" + ], + "summary": "POST club/{0}/members/declineinvite", + "description": "Inferred from Rec Room 2020 client literal `club/{0}/members/declineinvite`. Expected result: Club member list/object or membership mutation success.", + "operationId": "client_clubs_club_p0_members_declineinvite_post", + "parameters": [ + { + "name": "p0", + "in": "path", + "required": true, + "schema": { + "type": "string" + }, + "description": "Format placeholder from client literal club/{0}/members/declineinvite" + } + ], + "responses": { + "200": { + "description": "Club member list/object or membership mutation success.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Club" + } + } + } + }, + "204": { + "description": "Success with no body, used by some mutation endpoints." + }, + "400": { + "description": "Client-visible validation or bad request error.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "401": { + "description": "Authentication required or token invalid.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "500": { + "description": "Server error shape tolerated by client wrappers.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + } + }, + "x-recroom-client-route-literal": "club/{0}/members/declineinvite", + "x-recroom-client-family": "clubs", + "x-recroom-client-service": "clubs", + "x-recroom-builds": [ + "2020.12.18" + ], + "x-recroom-inferred-http-method": "post", + "x-recroom-evidence": [ + { + "type": "JDGDFALBCDJ", + "method": "RecRoom.Async.IPromise BDFBMBCKJEP()", + "file": "JDGDFALBCDJ.txt", + "line": 697 + }, + { + "type": "JDJGIBLMFKK", + "method": "RecRoom.Async.IPromise GJJBKCEMLDC(System.Int64 AOMBLGBCENO)", + "file": "JDJGIBLMFKK.txt", + "line": 20675 + } + ], + "requestBody": { + "required": false, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/GenericObject" + } + }, + "application/x-www-form-urlencoded": { + "schema": { + "$ref": "#/components/schemas/GenericObject" + } + }, + "multipart/form-data": { + "schema": { + "$ref": "#/components/schemas/GenericObject" + } + } + }, + "description": "Request body inferred. Exact DTO may be named in x-recroom-evidence method signature or companion DTO inventory." + }, + "x-client-response-contracts": [ + { + "kind": "success-or-empty", + "raw": "RecRoom.Async.IPromise", + "rootType": "void/success", + "typeDescription": { + "name": "void/success", + "found": false, + "kind": "object", + "wireKeys": [] + } + } + ] + } + }, + "/club/{p0}/members/denyrequest": { + "post": { + "tags": [ + "clubs" + ], + "summary": "POST club/{0}/members/denyrequest", + "description": "Inferred from Rec Room 2020 client literal `club/{0}/members/denyrequest`. Expected result: Club member list/object or membership mutation success.", + "operationId": "client_clubs_club_p0_members_denyrequest_post", + "parameters": [ + { + "name": "p0", + "in": "path", + "required": true, + "schema": { + "type": "string" + }, + "description": "Format placeholder from client literal club/{0}/members/denyrequest" + } + ], + "responses": { + "200": { + "description": "Club member list/object or membership mutation success.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Club" + } + } + } + }, + "204": { + "description": "Success with no body, used by some mutation endpoints." + }, + "400": { + "description": "Client-visible validation or bad request error.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "401": { + "description": "Authentication required or token invalid.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "500": { + "description": "Server error shape tolerated by client wrappers.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + } + }, + "x-recroom-client-route-literal": "club/{0}/members/denyrequest", + "x-recroom-client-family": "clubs", + "x-recroom-client-service": "clubs", + "x-recroom-builds": [ + "2020.12.18" + ], + "x-recroom-inferred-http-method": "post", + "x-recroom-evidence": [ + { + "type": "JDJGIBLMFKK", + "method": "RecRoom.Async.IPromise LPCHODIHIBB(System.Int64 AOMBLGBCENO, System.Int32 GKLPIFBPGOD)", + "file": "JDJGIBLMFKK.txt", + "line": 19048 + } + ], + "requestBody": { + "required": false, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/GenericObject" + } + }, + "application/x-www-form-urlencoded": { + "schema": { + "$ref": "#/components/schemas/GenericObject" + } + }, + "multipart/form-data": { + "schema": { + "$ref": "#/components/schemas/GenericObject" + } + } + }, + "description": "Request body inferred. Exact DTO may be named in x-recroom-evidence method signature or companion DTO inventory." + }, + "x-client-response-contracts": [ + { + "kind": "success-or-empty", + "raw": "RecRoom.Async.IPromise", + "rootType": "void/success", + "typeDescription": { + "name": "void/success", + "found": false, + "kind": "object", + "wireKeys": [] + } + } + ] + } + }, + "/club/{p0}/members/denyrequests": { + "post": { + "tags": [ + "clubs" + ], + "summary": "POST club/{0}/members/denyrequests", + "description": "Inferred from Rec Room 2020 client literal `club/{0}/members/denyrequests`. Expected result: Club member list/object or membership mutation success.", + "operationId": "client_clubs_club_p0_members_denyrequests_post", + "parameters": [ + { + "name": "p0", + "in": "path", + "required": true, + "schema": { + "type": "string" + }, + "description": "Format placeholder from client literal club/{0}/members/denyrequests" + } + ], + "responses": { + "200": { + "description": "Club member list/object or membership mutation success.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Club" + } + } + } + }, + "204": { + "description": "Success with no body, used by some mutation endpoints." + }, + "400": { + "description": "Client-visible validation or bad request error.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "401": { + "description": "Authentication required or token invalid.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "500": { + "description": "Server error shape tolerated by client wrappers.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + } + }, + "x-recroom-client-route-literal": "club/{0}/members/denyrequests", + "x-recroom-client-family": "clubs", + "x-recroom-client-service": "clubs", + "x-recroom-builds": [ + "2020.12.18" + ], + "x-recroom-inferred-http-method": "post", + "x-recroom-evidence": [ + { + "type": "JDJGIBLMFKK", + "method": "RecRoom.Async.IPromise CJBOECCIIJB(System.Int64 AOMBLGBCENO, System.Collections.Generic.IEnumerable`1 ILNGMAANNDG)", + "file": "JDJGIBLMFKK.txt", + "line": 19461 + } + ], + "requestBody": { + "required": false, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/GenericObject" + } + }, + "application/x-www-form-urlencoded": { + "schema": { + "$ref": "#/components/schemas/GenericObject" + } + }, + "multipart/form-data": { + "schema": { + "$ref": "#/components/schemas/GenericObject" + } + } + }, + "description": "Request body inferred. Exact DTO may be named in x-recroom-evidence method signature or companion DTO inventory." + }, + "x-client-response-contracts": [ + { + "kind": "success-or-empty", + "raw": "RecRoom.Async.IPromise", + "rootType": "void/success", + "typeDescription": { + "name": "void/success", + "found": false, + "kind": "object", + "wireKeys": [] + } + } + ] + } + }, + "/club/{p0}/members/directJoin": { + "post": { + "tags": [ + "clubs" + ], + "summary": "POST club/{0}/members/directJoin", + "description": "Inferred from Rec Room 2020 client literal `club/{0}/members/directJoin`. Expected result: Club member list/object or membership mutation success.", + "operationId": "client_clubs_club_p0_members_directjoin_post", + "parameters": [ + { + "name": "p0", + "in": "path", + "required": true, + "schema": { + "type": "string" + }, + "description": "Format placeholder from client literal club/{0}/members/directJoin" + } + ], + "responses": { + "200": { + "description": "Club member list/object or membership mutation success.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Club" + } + } + } + }, + "204": { + "description": "Success with no body, used by some mutation endpoints." + }, + "400": { + "description": "Client-visible validation or bad request error.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "401": { + "description": "Authentication required or token invalid.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "500": { + "description": "Server error shape tolerated by client wrappers.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + } + }, + "x-recroom-client-route-literal": "club/{0}/members/directJoin", + "x-recroom-client-family": "clubs", + "x-recroom-client-service": "clubs", + "x-recroom-builds": [ + "2020.12.18" + ], + "x-recroom-inferred-http-method": "post", + "x-recroom-evidence": [ + { + "type": "JDJGIBLMFKK", + "method": "RecRoom.Async.IPromise AJCDOLIBBKC(System.Int64 AOMBLGBCENO, PJCALHOPMKJ IEGCAIGJBBP, System.Int32 ENGEEKIMIGO)", + "file": "JDJGIBLMFKK.txt", + "line": 20450 + } + ], + "requestBody": { + "required": false, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/GenericObject" + } + }, + "application/x-www-form-urlencoded": { + "schema": { + "$ref": "#/components/schemas/GenericObject" + } + }, + "multipart/form-data": { + "schema": { + "$ref": "#/components/schemas/GenericObject" + } + } + }, + "description": "Request body inferred. Exact DTO may be named in x-recroom-evidence method signature or companion DTO inventory." + }, + "x-client-response-contracts": [ + { + "kind": "success-or-empty", + "raw": "RecRoom.Async.IPromise", + "rootType": "void/success", + "typeDescription": { + "name": "void/success", + "found": false, + "kind": "object", + "wireKeys": [] + } + } + ] + } + }, + "/club/{p0}/members/invite": { + "post": { + "tags": [ + "clubs" + ], + "summary": "POST club/{0}/members/invite", + "description": "Inferred from Rec Room 2020 client literal `club/{0}/members/invite`. Expected result: Club member list/object or membership mutation success.", + "operationId": "client_clubs_club_p0_members_invite_post", + "parameters": [ + { + "name": "p0", + "in": "path", + "required": true, + "schema": { + "type": "string" + }, + "description": "Format placeholder from client literal club/{0}/members/invite" + } + ], + "responses": { + "200": { + "description": "Club member list/object or membership mutation success.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Club" + } + } + } + }, + "204": { + "description": "Success with no body, used by some mutation endpoints." + }, + "400": { + "description": "Client-visible validation or bad request error.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "401": { + "description": "Authentication required or token invalid.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "500": { + "description": "Server error shape tolerated by client wrappers.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + } + }, + "x-recroom-client-route-literal": "club/{0}/members/invite", + "x-recroom-client-family": "clubs", + "x-recroom-client-service": "clubs", + "x-recroom-builds": [ + "2020.12.18" + ], + "x-recroom-inferred-http-method": "post", + "x-recroom-evidence": [ + { + "type": "JDJGIBLMFKK", + "method": "RecRoom.Async.IPromise FEIADJMHICD(System.Int64 AOMBLGBCENO, System.Int32 GKLPIFBPGOD, PPGPAHNMGEC PGMGFKOCEDG)", + "file": "JDJGIBLMFKK.txt", + "line": 19703 + } + ], + "requestBody": { + "required": false, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/GenericObject" + } + }, + "application/x-www-form-urlencoded": { + "schema": { + "$ref": "#/components/schemas/GenericObject" + } + }, + "multipart/form-data": { + "schema": { + "$ref": "#/components/schemas/GenericObject" + } + } + }, + "description": "Request body inferred. Exact DTO may be named in x-recroom-evidence method signature or companion DTO inventory." + }, + "x-client-response-contracts": [ + { + "kind": "success-or-empty", + "raw": "RecRoom.Async.IPromise", + "rootType": "void/success", + "typeDescription": { + "name": "void/success", + "found": false, + "kind": "object", + "wireKeys": [] + } + } + ] + } + }, + "/club/{p0}/members/invitemembers": { + "post": { + "tags": [ + "clubs" + ], + "summary": "POST club/{0}/members/invitemembers", + "description": "Inferred from Rec Room 2020 client literal `club/{0}/members/invitemembers`. Expected result: Club member list/object or membership mutation success.", + "operationId": "client_clubs_club_p0_members_invitemembers_post", + "parameters": [ + { + "name": "p0", + "in": "path", + "required": true, + "schema": { + "type": "string" + }, + "description": "Format placeholder from client literal club/{0}/members/invitemembers" + } + ], + "responses": { + "200": { + "description": "Club member list/object or membership mutation success.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Club" + } + } + } + }, + "204": { + "description": "Success with no body, used by some mutation endpoints." + }, + "400": { + "description": "Client-visible validation or bad request error.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "401": { + "description": "Authentication required or token invalid.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "500": { + "description": "Server error shape tolerated by client wrappers.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + } + }, + "x-recroom-client-route-literal": "club/{0}/members/invitemembers", + "x-recroom-client-family": "clubs", + "x-recroom-client-service": "clubs", + "x-recroom-builds": [ + "2020.12.18" + ], + "x-recroom-inferred-http-method": "post", + "x-recroom-evidence": [ + { + "type": "JDJGIBLMFKK", + "method": "RecRoom.Async.IPromise HOHPKNPHCFK(System.Int64 AOMBLGBCENO, System.Collections.Generic.List`1 ILNGMAANNDG, OFIEEDOMGPA NAMECJCFEDN)", + "file": "JDJGIBLMFKK.txt", + "line": 20070 + } + ], + "requestBody": { + "required": false, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/GenericObject" + } + }, + "application/x-www-form-urlencoded": { + "schema": { + "$ref": "#/components/schemas/GenericObject" + } + }, + "multipart/form-data": { + "schema": { + "$ref": "#/components/schemas/GenericObject" + } + } + }, + "description": "Request body inferred. Exact DTO may be named in x-recroom-evidence method signature or companion DTO inventory." + }, + "x-client-response-contracts": [ + { + "kind": "success-or-empty", + "raw": "RecRoom.Async.IPromise", + "rootType": "void/success", + "typeDescription": { + "name": "void/success", + "found": false, + "kind": "object", + "wireKeys": [] + } + } + ] + } + }, + "/club/{p0}/members/leave": { + "delete": { + "tags": [ + "clubs" + ], + "summary": "DELETE club/{0}/members/leave", + "description": "Inferred from Rec Room 2020 client literal `club/{0}/members/leave`. Expected result: Club member list/object or membership mutation success.", + "operationId": "client_clubs_club_p0_members_leave_delete", + "parameters": [ + { + "name": "p0", + "in": "path", + "required": true, + "schema": { + "type": "string" + }, + "description": "Format placeholder from client literal club/{0}/members/leave" + } + ], + "responses": { + "200": { + "description": "Club member list/object or membership mutation success.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Club" + } + } + } + }, + "204": { + "description": "Success with no body, used by some mutation endpoints." + }, + "400": { + "description": "Client-visible validation or bad request error.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "401": { + "description": "Authentication required or token invalid.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "500": { + "description": "Server error shape tolerated by client wrappers.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + } + }, + "x-recroom-client-route-literal": "club/{0}/members/leave", + "x-recroom-client-family": "clubs", + "x-recroom-client-service": "clubs", + "x-recroom-builds": [ + "2020.12.18" + ], + "x-recroom-inferred-http-method": "delete", + "x-recroom-evidence": [ + { + "type": "JDJGIBLMFKK", + "method": "RecRoom.Async.IPromise EBADBEIGOEN(System.Int64 AOMBLGBCENO)", + "file": "JDJGIBLMFKK.txt", + "line": 20745 + } + ], + "requestBody": { + "required": false, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/GenericObject" + } + }, + "application/x-www-form-urlencoded": { + "schema": { + "$ref": "#/components/schemas/GenericObject" + } + }, + "multipart/form-data": { + "schema": { + "$ref": "#/components/schemas/GenericObject" + } + } + }, + "description": "Request body inferred. Exact DTO may be named in x-recroom-evidence method signature or companion DTO inventory." + }, + "x-client-response-contracts": [ + { + "kind": "success-or-empty", + "raw": "RecRoom.Async.IPromise", + "rootType": "void/success", + "typeDescription": { + "name": "void/success", + "found": false, + "kind": "object", + "wireKeys": [] + } + } + ] + } + }, + "/club/{p0}/members/remove": { + "delete": { + "tags": [ + "clubs" + ], + "summary": "DELETE club/{0}/members/remove", + "description": "Inferred from Rec Room 2020 client literal `club/{0}/members/remove`. Expected result: Club member list/object or membership mutation success.", + "operationId": "client_clubs_club_p0_members_remove_delete", + "parameters": [ + { + "name": "p0", + "in": "path", + "required": true, + "schema": { + "type": "string" + }, + "description": "Format placeholder from client literal club/{0}/members/remove" + } + ], + "responses": { + "200": { + "description": "Club member list/object or membership mutation success.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Club" + } + } + } + }, + "204": { + "description": "Success with no body, used by some mutation endpoints." + }, + "400": { + "description": "Client-visible validation or bad request error.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "401": { + "description": "Authentication required or token invalid.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "500": { + "description": "Server error shape tolerated by client wrappers.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + } + }, + "x-recroom-client-route-literal": "club/{0}/members/remove", + "x-recroom-client-family": "clubs", + "x-recroom-client-service": "clubs", + "x-recroom-builds": [ + "2020.12.18" + ], + "x-recroom-inferred-http-method": "delete", + "x-recroom-evidence": [ + { + "type": "JDJGIBLMFKK", + "method": "RecRoom.Async.IPromise LDOGPIHJOLP(System.Int64 AOMBLGBCENO, System.Int32 GKLPIFBPGOD)", + "file": "JDJGIBLMFKK.txt", + "line": 20895 + } + ], + "requestBody": { + "required": false, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/GenericObject" + } + }, + "application/x-www-form-urlencoded": { + "schema": { + "$ref": "#/components/schemas/GenericObject" + } + }, + "multipart/form-data": { + "schema": { + "$ref": "#/components/schemas/GenericObject" + } + } + }, + "description": "Request body inferred. Exact DTO may be named in x-recroom-evidence method signature or companion DTO inventory." + }, + "x-client-response-contracts": [ + { + "kind": "success-or-empty", + "raw": "RecRoom.Async.IPromise", + "rootType": "void/success", + "typeDescription": { + "name": "void/success", + "found": false, + "kind": "object", + "wireKeys": [] + } + } + ] + } + }, + "/club/{p0}/members/requesttojoin": { + "post": { + "tags": [ + "clubs" + ], + "summary": "POST club/{0}/members/requesttojoin", + "description": "Inferred from Rec Room 2020 client literal `club/{0}/members/requesttojoin`. Expected result: Club member list/object or membership mutation success.", + "operationId": "client_clubs_club_p0_members_requesttojoin_post", + "parameters": [ + { + "name": "p0", + "in": "path", + "required": true, + "schema": { + "type": "string" + }, + "description": "Format placeholder from client literal club/{0}/members/requesttojoin" + } + ], + "responses": { + "200": { + "description": "Club member list/object or membership mutation success.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Club" + } + } + } + }, + "204": { + "description": "Success with no body, used by some mutation endpoints." + }, + "400": { + "description": "Client-visible validation or bad request error.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "401": { + "description": "Authentication required or token invalid.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "500": { + "description": "Server error shape tolerated by client wrappers.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + } + }, + "x-recroom-client-route-literal": "club/{0}/members/requesttojoin", + "x-recroom-client-family": "clubs", + "x-recroom-client-service": "clubs", + "x-recroom-builds": [ + "2020.12.18" + ], + "x-recroom-inferred-http-method": "post", + "x-recroom-evidence": [ + { + "type": "JDJGIBLMFKK", + "method": "RecRoom.Async.IPromise NJKBIMEEAPE(System.Int64 AOMBLGBCENO)", + "file": "JDJGIBLMFKK.txt", + "line": 18249 + } + ], + "requestBody": { + "required": false, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/GenericObject" + } + }, + "application/x-www-form-urlencoded": { + "schema": { + "$ref": "#/components/schemas/GenericObject" + } + }, + "multipart/form-data": { + "schema": { + "$ref": "#/components/schemas/GenericObject" + } + } + }, + "description": "Request body inferred. Exact DTO may be named in x-recroom-evidence method signature or companion DTO inventory." + }, + "x-client-response-contracts": [ + { + "kind": "success-or-empty", + "raw": "RecRoom.Async.IPromise", + "rootType": "void/success", + "typeDescription": { + "name": "void/success", + "found": false, + "kind": "object", + "wireKeys": [] + } + } + ] + } + }, + "/club/{p0}/members/unban": { + "delete": { + "tags": [ + "clubs" + ], + "summary": "DELETE club/{0}/members/unban", + "description": "Inferred from Rec Room 2020 client literal `club/{0}/members/unban`. Expected result: Club member list/object or membership mutation success.", + "operationId": "client_clubs_club_p0_members_unban_delete", + "parameters": [ + { + "name": "p0", + "in": "path", + "required": true, + "schema": { + "type": "string" + }, + "description": "Format placeholder from client literal club/{0}/members/unban" + } + ], + "responses": { + "200": { + "description": "Club member list/object or membership mutation success.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Club" + } + } + } + }, + "204": { + "description": "Success with no body, used by some mutation endpoints." + }, + "400": { + "description": "Client-visible validation or bad request error.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "401": { + "description": "Authentication required or token invalid.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "500": { + "description": "Server error shape tolerated by client wrappers.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + } + }, + "x-recroom-client-route-literal": "club/{0}/members/unban", + "x-recroom-client-family": "clubs", + "x-recroom-client-service": "clubs", + "x-recroom-builds": [ + "2020.12.18" + ], + "x-recroom-inferred-http-method": "delete", + "x-recroom-evidence": [ + { + "type": "JDJGIBLMFKK", + "method": "RecRoom.Async.IPromise OGMGOBHPMFC(System.Int64 AOMBLGBCENO, System.Int32 GKLPIFBPGOD)", + "file": "JDJGIBLMFKK.txt", + "line": 21435 + } + ], + "requestBody": { + "required": false, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/GenericObject" + } + }, + "application/x-www-form-urlencoded": { + "schema": { + "$ref": "#/components/schemas/GenericObject" + } + }, + "multipart/form-data": { + "schema": { + "$ref": "#/components/schemas/GenericObject" + } + } + }, + "description": "Request body inferred. Exact DTO may be named in x-recroom-evidence method signature or companion DTO inventory." + }, + "x-client-response-contracts": [ + { + "kind": "success-or-empty", + "raw": "RecRoom.Async.IPromise", + "rootType": "void/success", + "typeDescription": { + "name": "void/success", + "found": false, + "kind": "object", + "wireKeys": [] + } + } + ] + } + }, + "/club/{p0}/modify": { + "post": { + "tags": [ + "clubs" + ], + "summary": "POST club/{0}/modify", + "description": "Inferred from Rec Room 2020 client literal `club/{0}/modify`. Expected result: Club object/list or mutation success.", + "operationId": "client_clubs_club_p0_modify_post", + "parameters": [ + { + "name": "p0", + "in": "path", + "required": true, + "schema": { + "type": "string" + }, + "description": "Format placeholder from client literal club/{0}/modify" + } + ], + "responses": { + "200": { + "description": "Club object/list or mutation success.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Club" + } + } + } + }, + "204": { + "description": "Success with no body, used by some mutation endpoints." + }, + "400": { + "description": "Client-visible validation or bad request error.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "401": { + "description": "Authentication required or token invalid.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "500": { + "description": "Server error shape tolerated by client wrappers.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + } + }, + "x-recroom-client-route-literal": "club/{0}/modify", + "x-recroom-client-family": "clubs", + "x-recroom-client-service": "clubs", + "x-recroom-builds": [ + "2020.12.18" + ], + "x-recroom-inferred-http-method": "post", + "x-recroom-evidence": [ + { + "type": "JDJGIBLMFKK", + "method": "RecRoom.Async.IPromise`1 CKJIKPOIMFE(JDJGIBLMFKK+ACNILMIFDJJ JMFLHIIJFKL)", + "file": "JDJGIBLMFKK.txt", + "line": 11620 + } + ], + "requestBody": { + "required": false, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/GenericObject" + } + }, + "application/x-www-form-urlencoded": { + "schema": { + "$ref": "#/components/schemas/GenericObject" + } + }, + "multipart/form-data": { + "schema": { + "$ref": "#/components/schemas/GenericObject" + } + } + }, + "description": "Request body inferred. Exact DTO may be named in x-recroom-evidence method signature or companion DTO inventory." + }, + "x-client-response-contracts": [ + { + "kind": "object", + "raw": "PIHMJGCGNLP", + "rootType": "PIHMJGCGNLP", + "typeDescription": { + "name": "PIHMJGCGNLP", + "found": true, + "file": "PIHMJGCGNLP.cs", + "declaration": "public class PIHMJGCGNLP : IFAIJAGLDFK", + "kind": "class", + "baseType": "IFAIJAGLDFK", + "properties": [ + { + "name": "AHDBBFIDKBN", + "type": "PPGPAHNMGEC", + "accessibility": "public", + "kind": "property" + }, + { + "name": "CMGPCPKLHLF", + "type": "JHEEFBMODPG", + "accessibility": "public", + "kind": "property" + }, + { + "name": "DOOHAKMALHL", + "type": "List", + "accessibility": "public", + "kind": "property" + }, + { + "name": "IIIFDCAPMEA", + "type": "JHEEFBMODPG", + "accessibility": "public", + "kind": "property" + }, + { + "name": "KIFHEKPKILL", + "type": "JHEEFBMODPG", + "accessibility": "public", + "kind": "property" + }, + { + "name": "NDAGAGNHNPA", + "type": "PLILLKHMNDA", + "accessibility": "public", + "kind": "property" + }, + { + "name": "NJHNHHMCILD", + "type": "JHEEFBMODPG", + "accessibility": "public", + "kind": "property" + }, + { + "name": "OPIIBPFEODL", + "type": "List", + "accessibility": "public", + "kind": "property" + } + ], + "fields": [], + "enumValues": [], + "wireKeys": [ + "Club", + "CoownerPermissions", + "ModeratorPermissions", + "MemberPermissions", + "MyMembershipType" + ], + "isilFile": "PIHMJGCGNLP.txt", + "isilType": "PIHMJGCGNLP" + } + } + ] + } + }, + "/club/{p0}/modifydetails": { + "post": { + "tags": [ + "clubs" + ], + "summary": "POST club/{0}/modifydetails", + "description": "Inferred from Rec Room 2020 client literal `club/{0}/modifydetails`. Expected result: Club object/list or mutation success.", + "operationId": "client_clubs_club_p0_modifydetails_post", + "parameters": [ + { + "name": "p0", + "in": "path", + "required": true, + "schema": { + "type": "string" + }, + "description": "Format placeholder from client literal club/{0}/modifydetails" + } + ], + "responses": { + "200": { + "description": "Club object/list or mutation success.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Club" + } + } + } + }, + "204": { + "description": "Success with no body, used by some mutation endpoints." + }, + "400": { + "description": "Client-visible validation or bad request error.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "401": { + "description": "Authentication required or token invalid.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "500": { + "description": "Server error shape tolerated by client wrappers.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + } + }, + "x-recroom-client-route-literal": "club/{0}/modifydetails", + "x-recroom-client-family": "clubs", + "x-recroom-client-service": "clubs", + "x-recroom-builds": [ + "2020.12.18" + ], + "x-recroom-inferred-http-method": "post", + "x-recroom-evidence": [ + { + "type": "JDJGIBLMFKK", + "method": "RecRoom.Async.IPromise`1 KOPAANDJNKB(JDJGIBLMFKK+FHFOCPGBMPB JMFLHIIJFKL)", + "file": "JDJGIBLMFKK.txt", + "line": 12001 + } + ], + "requestBody": { + "required": false, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/GenericObject" + } + }, + "application/x-www-form-urlencoded": { + "schema": { + "$ref": "#/components/schemas/GenericObject" + } + }, + "multipart/form-data": { + "schema": { + "$ref": "#/components/schemas/GenericObject" + } + } + }, + "description": "Request body inferred. Exact DTO may be named in x-recroom-evidence method signature or companion DTO inventory." + }, + "x-client-response-contracts": [ + { + "kind": "object", + "raw": "PIHMJGCGNLP", + "rootType": "PIHMJGCGNLP", + "typeDescription": { + "name": "PIHMJGCGNLP", + "found": true, + "file": "PIHMJGCGNLP.cs", + "declaration": "public class PIHMJGCGNLP : IFAIJAGLDFK", + "kind": "class", + "baseType": "IFAIJAGLDFK", + "properties": [ + { + "name": "AHDBBFIDKBN", + "type": "PPGPAHNMGEC", + "accessibility": "public", + "kind": "property" + }, + { + "name": "CMGPCPKLHLF", + "type": "JHEEFBMODPG", + "accessibility": "public", + "kind": "property" + }, + { + "name": "DOOHAKMALHL", + "type": "List", + "accessibility": "public", + "kind": "property" + }, + { + "name": "IIIFDCAPMEA", + "type": "JHEEFBMODPG", + "accessibility": "public", + "kind": "property" + }, + { + "name": "KIFHEKPKILL", + "type": "JHEEFBMODPG", + "accessibility": "public", + "kind": "property" + }, + { + "name": "NDAGAGNHNPA", + "type": "PLILLKHMNDA", + "accessibility": "public", + "kind": "property" + }, + { + "name": "NJHNHHMCILD", + "type": "JHEEFBMODPG", + "accessibility": "public", + "kind": "property" + }, + { + "name": "OPIIBPFEODL", + "type": "List", + "accessibility": "public", + "kind": "property" + } + ], + "fields": [], + "enumValues": [], + "wireKeys": [ + "Club", + "CoownerPermissions", + "ModeratorPermissions", + "MemberPermissions", + "MyMembershipType" + ], + "isilFile": "PIHMJGCGNLP.txt", + "isilType": "PIHMJGCGNLP" + } + } + ] + } + }, + "/club/{p0}/permissions/{p1}": { + "get": { + "tags": [ + "clubs" + ], + "summary": "GET club/{0}/permissions/{1}", + "description": "Inferred from Rec Room 2020 client literal `club/{0}/permissions/{1}`. Expected result: Club object/list or mutation success.", + "operationId": "client_clubs_club_p0_permissions_p1_get", + "parameters": [ + { + "name": "p0", + "in": "path", + "required": true, + "schema": { + "type": "string" + }, + "description": "Format placeholder from client literal club/{0}/permissions/{1}" + }, + { + "name": "p1", + "in": "path", + "required": true, + "schema": { + "type": "string" + }, + "description": "Format placeholder from client literal club/{0}/permissions/{1}" + } + ], + "responses": { + "200": { + "description": "Club object/list or mutation success.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Club" + } + } + } + }, + "204": { + "description": "Success with no body, used by some mutation endpoints." + }, + "400": { + "description": "Client-visible validation or bad request error.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "401": { + "description": "Authentication required or token invalid.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "500": { + "description": "Server error shape tolerated by client wrappers.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + } + }, + "x-recroom-client-route-literal": "club/{0}/permissions/{1}", + "x-recroom-client-family": "clubs", + "x-recroom-client-service": "clubs", + "x-recroom-builds": [ + "2020.12.18" + ], + "x-recroom-inferred-http-method": "get", + "x-recroom-evidence": [ + { + "type": "JDJGIBLMFKK", + "method": "RecRoom.Async.IPromise`1 GHBJEDCEKLK(JHEEFBMODPG OBJEOMAGODL)", + "file": "JDJGIBLMFKK.txt", + "line": 13631 + } + ], + "x-client-response-contracts": [ + { + "kind": "object", + "raw": "PIHMJGCGNLP", + "rootType": "PIHMJGCGNLP", + "typeDescription": { + "name": "PIHMJGCGNLP", + "found": true, + "file": "PIHMJGCGNLP.cs", + "declaration": "public class PIHMJGCGNLP : IFAIJAGLDFK", + "kind": "class", + "baseType": "IFAIJAGLDFK", + "properties": [ + { + "name": "AHDBBFIDKBN", + "type": "PPGPAHNMGEC", + "accessibility": "public", + "kind": "property" + }, + { + "name": "CMGPCPKLHLF", + "type": "JHEEFBMODPG", + "accessibility": "public", + "kind": "property" + }, + { + "name": "DOOHAKMALHL", + "type": "List", + "accessibility": "public", + "kind": "property" + }, + { + "name": "IIIFDCAPMEA", + "type": "JHEEFBMODPG", + "accessibility": "public", + "kind": "property" + }, + { + "name": "KIFHEKPKILL", + "type": "JHEEFBMODPG", + "accessibility": "public", + "kind": "property" + }, + { + "name": "NDAGAGNHNPA", + "type": "PLILLKHMNDA", + "accessibility": "public", + "kind": "property" + }, + { + "name": "NJHNHHMCILD", + "type": "JHEEFBMODPG", + "accessibility": "public", + "kind": "property" + }, + { + "name": "OPIIBPFEODL", + "type": "List", + "accessibility": "public", + "kind": "property" + } + ], + "fields": [], + "enumValues": [], + "wireKeys": [ + "Club", + "CoownerPermissions", + "ModeratorPermissions", + "MemberPermissions", + "MyMembershipType" + ], + "isilFile": "PIHMJGCGNLP.txt", + "isilType": "PIHMJGCGNLP" + } + } + ] + } + }, + "/club/{p0}": { + "get": { + "tags": [ + "clubs" + ], + "summary": "GET club/{0}", + "description": "Inferred from Rec Room 2020 client literal `club/{0}`. Expected result: Club object/list or mutation success.", + "operationId": "client_clubs_club_p0_get", + "parameters": [ + { + "name": "p0", + "in": "path", + "required": true, + "schema": { + "type": "string" + }, + "description": "Format placeholder from client literal club/{0}" + } + ], + "responses": { + "200": { + "description": "Club object/list or mutation success.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Club" + } + } + } + }, + "204": { + "description": "Success with no body, used by some mutation endpoints." + }, + "400": { + "description": "Client-visible validation or bad request error.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "401": { + "description": "Authentication required or token invalid.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "500": { + "description": "Server error shape tolerated by client wrappers.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + } + }, + "x-recroom-client-route-literal": "club/{0}", + "x-recroom-client-family": "clubs", + "x-recroom-client-service": "clubs", + "x-recroom-builds": [ + "2020.12.18" + ], + "x-recroom-inferred-http-method": "get", + "x-recroom-evidence": [ + { + "type": "JDJGIBLMFKK", + "method": "RecRoom.Async.IPromise IGHDIAEPHJD(System.Int64 AOMBLGBCENO, System.String DHANCEHHIDH)", + "file": "JDJGIBLMFKK.txt", + "line": 13088 + }, + { + "type": "JDJGIBLMFKK", + "method": "RecRoom.Async.IPromise`1 NKGOFNILGPL(System.Int64 AOMBLGBCENO, System.Boolean OHLIGBELLLH = True)", + "file": "JDJGIBLMFKK.txt", + "line": 10425 + } + ], + "x-client-response-contracts": [ + { + "kind": "success-or-empty", + "raw": "RecRoom.Async.IPromise", + "rootType": "void/success", + "typeDescription": { + "name": "void/success", + "found": false, + "kind": "object", + "wireKeys": [] + } + }, + { + "kind": "object", + "raw": "PLILLKHMNDA", + "rootType": "PLILLKHMNDA", + "typeDescription": { + "name": "PLILLKHMNDA", + "found": true, + "file": "PLILLKHMNDA.cs", + "declaration": "public class PLILLKHMNDA : IFAIJAGLDFK, IEquatable", + "kind": "class", + "baseType": "IFAIJAGLDFK", + "properties": [ + { + "name": "BADIGBCKECA", + "type": "int", + "accessibility": "public", + "kind": "property" + }, + { + "name": "CCGOEDABKNN", + "type": "long", + "accessibility": "public", + "kind": "property" + }, + { + "name": "CDINMMPNAID", + "type": "JCMLDDKFKEO", + "accessibility": "public", + "kind": "property" + }, + { + "name": "CDNFGMHLDMJ", + "type": "bool", + "accessibility": "public", + "kind": "property" + }, + { + "name": "EEAOJCGAOCN", + "type": "int", + "accessibility": "public", + "kind": "property" + }, + { + "name": "EHOLKJPEGFF", + "type": "string", + "accessibility": "public", + "kind": "property" + }, + { + "name": "EPGALOMHHMI", + "type": "JCDEFCJLCHN", + "accessibility": "public", + "kind": "property" + }, + { + "name": "FIKEBGGCDFN", + "type": "string", + "accessibility": "public", + "kind": "property" + }, + { + "name": "HHCGNCLFKDM", + "type": "Nullable", + "accessibility": "public", + "kind": "property" + }, + { + "name": "IPKLLFAJJPJ", + "type": "bool", + "accessibility": "public", + "kind": "property" + }, + { + "name": "KODBEJPEFOJ", + "type": "string", + "accessibility": "public", + "kind": "property" + }, + { + "name": "LNGPBGCIAPP", + "type": "string", + "accessibility": "public", + "kind": "property" + }, + { + "name": "MNEEEGHOGAB", + "type": "DIGMAIMMHAP", + "accessibility": "public", + "kind": "property" + }, + { + "name": "PHBCFAJILGD", + "type": "PJCALHOPMKJ", + "accessibility": "public", + "kind": "property" + } + ], + "fields": [], + "enumValues": [], + "wireKeys": [ + "ClubId", + "Name", + "Description", + "MainImageName", + "State", + "CreatorAccountId", + "Category", + "Visibility", + "Joinability", + "AllowJuniors", + "MemberCount", + "IsRRO", + "ClubType" + ], + "isilFile": "PLILLKHMNDA.txt", + "isilType": "PLILLKHMNDA" + } + } + ] + } + }, + "/club/account/{p0}/created": { + "post": { + "tags": [ + "clubs" + ], + "summary": "POST club/account/{0}/created", + "description": "Inferred from Rec Room 2020 client literal `club/account/{0}/created`. Expected result: Club object/list or mutation success.", + "operationId": "client_clubs_club_account_p0_created_post", + "parameters": [ + { + "name": "p0", + "in": "path", + "required": true, + "schema": { + "type": "string" + }, + "description": "Format placeholder from client literal club/account/{0}/created" + } + ], + "responses": { + "200": { + "description": "Club object/list or mutation success.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Club" + } + } + } + }, + "204": { + "description": "Success with no body, used by some mutation endpoints." + }, + "400": { + "description": "Client-visible validation or bad request error.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "401": { + "description": "Authentication required or token invalid.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "500": { + "description": "Server error shape tolerated by client wrappers.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + } + }, + "x-recroom-client-route-literal": "club/account/{0}/created", + "x-recroom-client-family": "clubs", + "x-recroom-client-service": "clubs", + "x-recroom-builds": [ + "2020.12.18" + ], + "x-recroom-inferred-http-method": "post", + "x-recroom-evidence": [ + { + "type": "JDJGIBLMFKK", + "method": "RecRoom.Async.IPromise`1> DIAPLKMFFKO(System.Int32 GKLPIFBPGOD)", + "file": "JDJGIBLMFKK.txt", + "line": 9416 + }, + { + "type": "JDJGIBLMFKK", + "method": "System.String JANFGLBCBMJ(System.Int32 GKLPIFBPGOD)", + "file": "JDJGIBLMFKK.txt", + "line": 7880 + }, + { + "type": "JDJGIBLMFKK", + "method": "System.Void PJBLNGJKLEB(System.Int64 AOMBLGBCENO)", + "file": "JDJGIBLMFKK.txt", + "line": 26175 + } + ], + "requestBody": { + "required": false, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/GenericObject" + } + }, + "application/x-www-form-urlencoded": { + "schema": { + "$ref": "#/components/schemas/GenericObject" + } + }, + "multipart/form-data": { + "schema": { + "$ref": "#/components/schemas/GenericObject" + } + } + }, + "description": "Request body inferred. Exact DTO may be named in x-recroom-evidence method signature or companion DTO inventory." + }, + "x-client-response-contracts": [ + { + "kind": "array", + "raw": "System.Collections.Generic.List`1", + "rootType": "PLILLKHMNDA", + "item": { + "kind": "object", + "raw": "PLILLKHMNDA", + "rootType": "PLILLKHMNDA" + }, + "typeDescription": { + "name": "PLILLKHMNDA", + "found": true, + "file": "PLILLKHMNDA.cs", + "declaration": "public class PLILLKHMNDA : IFAIJAGLDFK, IEquatable", + "kind": "class", + "baseType": "IFAIJAGLDFK", + "properties": [ + { + "name": "BADIGBCKECA", + "type": "int", + "accessibility": "public", + "kind": "property" + }, + { + "name": "CCGOEDABKNN", + "type": "long", + "accessibility": "public", + "kind": "property" + }, + { + "name": "CDINMMPNAID", + "type": "JCMLDDKFKEO", + "accessibility": "public", + "kind": "property" + }, + { + "name": "CDNFGMHLDMJ", + "type": "bool", + "accessibility": "public", + "kind": "property" + }, + { + "name": "EEAOJCGAOCN", + "type": "int", + "accessibility": "public", + "kind": "property" + }, + { + "name": "EHOLKJPEGFF", + "type": "string", + "accessibility": "public", + "kind": "property" + }, + { + "name": "EPGALOMHHMI", + "type": "JCDEFCJLCHN", + "accessibility": "public", + "kind": "property" + }, + { + "name": "FIKEBGGCDFN", + "type": "string", + "accessibility": "public", + "kind": "property" + }, + { + "name": "HHCGNCLFKDM", + "type": "Nullable", + "accessibility": "public", + "kind": "property" + }, + { + "name": "IPKLLFAJJPJ", + "type": "bool", + "accessibility": "public", + "kind": "property" + }, + { + "name": "KODBEJPEFOJ", + "type": "string", + "accessibility": "public", + "kind": "property" + }, + { + "name": "LNGPBGCIAPP", + "type": "string", + "accessibility": "public", + "kind": "property" + }, + { + "name": "MNEEEGHOGAB", + "type": "DIGMAIMMHAP", + "accessibility": "public", + "kind": "property" + }, + { + "name": "PHBCFAJILGD", + "type": "PJCALHOPMKJ", + "accessibility": "public", + "kind": "property" + } + ], + "fields": [], + "enumValues": [], + "wireKeys": [ + "ClubId", + "Name", + "Description", + "MainImageName", + "State", + "CreatorAccountId", + "Category", + "Visibility", + "Joinability", + "AllowJuniors", + "MemberCount", + "IsRRO", + "ClubType" + ], + "isilFile": "PLILLKHMNDA.txt", + "isilType": "PLILLKHMNDA" + } + }, + { + "kind": "unknown", + "raw": "", + "rootType": "unknown", + "typeDescription": { + "name": "unknown", + "found": false, + "kind": "object", + "wireKeys": [] + } + } + ] + } + }, + "/club/categoryTags": { + "post": { + "tags": [ + "clubs" + ], + "summary": "POST club/categoryTags", + "description": "Inferred from Rec Room 2020 client literal `club/categoryTags`. Expected result: Club object/list or mutation success.", + "operationId": "client_clubs_club_categorytags_post", + "parameters": [], + "responses": { + "200": { + "description": "Club object/list or mutation success.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Club" + } + } + } + }, + "204": { + "description": "Success with no body, used by some mutation endpoints." + }, + "400": { + "description": "Client-visible validation or bad request error.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "401": { + "description": "Authentication required or token invalid.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "500": { + "description": "Server error shape tolerated by client wrappers.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + } + }, + "x-recroom-client-route-literal": "club/categoryTags", + "x-recroom-client-family": "clubs", + "x-recroom-client-service": "clubs", + "x-recroom-builds": [ + "2020.12.18" + ], + "x-recroom-inferred-http-method": "post", + "x-recroom-evidence": [ + { + "type": "JDJGIBLMFKK", + "method": "RecRoom.Async.IPromise`1> PAKNBFOJENP()", + "file": "JDJGIBLMFKK.txt", + "line": 13314 + } + ], + "requestBody": { + "required": false, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/GenericObject" + } + }, + "application/x-www-form-urlencoded": { + "schema": { + "$ref": "#/components/schemas/GenericObject" + } + }, + "multipart/form-data": { + "schema": { + "$ref": "#/components/schemas/GenericObject" + } + } + }, + "description": "Request body inferred. Exact DTO may be named in x-recroom-evidence method signature or companion DTO inventory." + }, + "x-client-response-contracts": [ + { + "kind": "array", + "raw": "System.Collections.Generic.List`1", + "rootType": "String", + "item": { + "kind": "primitive", + "raw": "System.String", + "rootType": "string" + }, + "typeDescription": { + "name": "String", + "found": false, + "kind": "primitive", + "wireKeys": [] + } + } + ] + } + }, + "/club/create": { + "post": { + "tags": [ + "clubs" + ], + "summary": "POST club/create", + "description": "Inferred from Rec Room 2020 client literal `club/create`. Expected result: Club object/list or mutation success.", + "operationId": "client_clubs_club_create_post", + "parameters": [], + "responses": { + "200": { + "description": "Club object/list or mutation success.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Club" + } + } + } + }, + "204": { + "description": "Success with no body, used by some mutation endpoints." + }, + "400": { + "description": "Client-visible validation or bad request error.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "401": { + "description": "Authentication required or token invalid.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "500": { + "description": "Server error shape tolerated by client wrappers.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + } + }, + "x-recroom-client-route-literal": "club/create", + "x-recroom-client-family": "clubs", + "x-recroom-client-service": "clubs", + "x-recroom-builds": [ + "2020.12.18" + ], + "x-recroom-inferred-http-method": "post", + "x-recroom-evidence": [ + { + "type": "JDJGIBLMFKK", + "method": "RecRoom.Async.IPromise`1 KMPJFFBGJMD(JDJGIBLMFKK+ACNILMIFDJJ JMFLHIIJFKL)", + "file": "JDJGIBLMFKK.txt", + "line": 11381 + } + ], + "requestBody": { + "required": false, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/GenericObject" + } + }, + "application/x-www-form-urlencoded": { + "schema": { + "$ref": "#/components/schemas/GenericObject" + } + }, + "multipart/form-data": { + "schema": { + "$ref": "#/components/schemas/GenericObject" + } + } + }, + "description": "Request body inferred. Exact DTO may be named in x-recroom-evidence method signature or companion DTO inventory." + }, + "x-client-response-contracts": [ + { + "kind": "object", + "raw": "PIHMJGCGNLP", + "rootType": "PIHMJGCGNLP", + "typeDescription": { + "name": "PIHMJGCGNLP", + "found": true, + "file": "PIHMJGCGNLP.cs", + "declaration": "public class PIHMJGCGNLP : IFAIJAGLDFK", + "kind": "class", + "baseType": "IFAIJAGLDFK", + "properties": [ + { + "name": "AHDBBFIDKBN", + "type": "PPGPAHNMGEC", + "accessibility": "public", + "kind": "property" + }, + { + "name": "CMGPCPKLHLF", + "type": "JHEEFBMODPG", + "accessibility": "public", + "kind": "property" + }, + { + "name": "DOOHAKMALHL", + "type": "List", + "accessibility": "public", + "kind": "property" + }, + { + "name": "IIIFDCAPMEA", + "type": "JHEEFBMODPG", + "accessibility": "public", + "kind": "property" + }, + { + "name": "KIFHEKPKILL", + "type": "JHEEFBMODPG", + "accessibility": "public", + "kind": "property" + }, + { + "name": "NDAGAGNHNPA", + "type": "PLILLKHMNDA", + "accessibility": "public", + "kind": "property" + }, + { + "name": "NJHNHHMCILD", + "type": "JHEEFBMODPG", + "accessibility": "public", + "kind": "property" + }, + { + "name": "OPIIBPFEODL", + "type": "List", + "accessibility": "public", + "kind": "property" + } + ], + "fields": [], + "enumValues": [], + "wireKeys": [ + "Club", + "CoownerPermissions", + "ModeratorPermissions", + "MemberPermissions", + "MyMembershipType" + ], + "isilFile": "PIHMJGCGNLP.txt", + "isilType": "PIHMJGCGNLP" + } + } + ] + } + }, + "/club/home/me": { + "get": { + "tags": [ + "clubs" + ], + "summary": "GET club/home/me", + "description": "Inferred from Rec Room 2020 client literal `club/home/me`. Expected result: Club object/list or mutation success.", + "operationId": "client_clubs_club_home_me_get", + "parameters": [], + "responses": { + "200": { + "description": "Club object/list or mutation success.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Club" + } + } + } + }, + "204": { + "description": "Success with no body, used by some mutation endpoints." + }, + "400": { + "description": "Client-visible validation or bad request error.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "401": { + "description": "Authentication required or token invalid.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "500": { + "description": "Server error shape tolerated by client wrappers.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + } + }, + "x-recroom-client-route-literal": "club/home/me", + "x-recroom-client-family": "clubs", + "x-recroom-client-service": "clubs", + "x-recroom-builds": [ + "2020.12.18" + ], + "x-recroom-inferred-http-method": "get", + "x-recroom-evidence": [ + { + "type": "JDJGIBLMFKK", + "method": "RecRoom.Async.IPromise`1 DBDOOMCNNFE()", + "file": "JDJGIBLMFKK.txt", + "line": 10003 + }, + { + "type": "JDJGIBLMFKK+<>c", + "method": "System.Void b__106_0()", + "file": "JDJGIBLMFKK_NestedType___c.txt", + "line": 995 + }, + { + "type": "JDJGIBLMFKK+<>c", + "method": "System.Void b__106_2()", + "file": "JDJGIBLMFKK_NestedType___c.txt", + "line": 954 + } + ], + "x-client-response-contracts": [ + { + "kind": "object", + "raw": "PLILLKHMNDA", + "rootType": "PLILLKHMNDA", + "typeDescription": { + "name": "PLILLKHMNDA", + "found": true, + "file": "PLILLKHMNDA.cs", + "declaration": "public class PLILLKHMNDA : IFAIJAGLDFK, IEquatable", + "kind": "class", + "baseType": "IFAIJAGLDFK", + "properties": [ + { + "name": "BADIGBCKECA", + "type": "int", + "accessibility": "public", + "kind": "property" + }, + { + "name": "CCGOEDABKNN", + "type": "long", + "accessibility": "public", + "kind": "property" + }, + { + "name": "CDINMMPNAID", + "type": "JCMLDDKFKEO", + "accessibility": "public", + "kind": "property" + }, + { + "name": "CDNFGMHLDMJ", + "type": "bool", + "accessibility": "public", + "kind": "property" + }, + { + "name": "EEAOJCGAOCN", + "type": "int", + "accessibility": "public", + "kind": "property" + }, + { + "name": "EHOLKJPEGFF", + "type": "string", + "accessibility": "public", + "kind": "property" + }, + { + "name": "EPGALOMHHMI", + "type": "JCDEFCJLCHN", + "accessibility": "public", + "kind": "property" + }, + { + "name": "FIKEBGGCDFN", + "type": "string", + "accessibility": "public", + "kind": "property" + }, + { + "name": "HHCGNCLFKDM", + "type": "Nullable", + "accessibility": "public", + "kind": "property" + }, + { + "name": "IPKLLFAJJPJ", + "type": "bool", + "accessibility": "public", + "kind": "property" + }, + { + "name": "KODBEJPEFOJ", + "type": "string", + "accessibility": "public", + "kind": "property" + }, + { + "name": "LNGPBGCIAPP", + "type": "string", + "accessibility": "public", + "kind": "property" + }, + { + "name": "MNEEEGHOGAB", + "type": "DIGMAIMMHAP", + "accessibility": "public", + "kind": "property" + }, + { + "name": "PHBCFAJILGD", + "type": "PJCALHOPMKJ", + "accessibility": "public", + "kind": "property" + } + ], + "fields": [], + "enumValues": [], + "wireKeys": [ + "ClubId", + "Name", + "Description", + "MainImageName", + "State", + "CreatorAccountId", + "Category", + "Visibility", + "Joinability", + "AllowJuniors", + "MemberCount", + "IsRRO", + "ClubType" + ], + "isilFile": "PLILLKHMNDA.txt", + "isilType": "PLILLKHMNDA" + } + }, + { + "kind": "unknown", + "raw": "", + "rootType": "unknown", + "typeDescription": { + "name": "unknown", + "found": false, + "kind": "object", + "wireKeys": [] + } + } + ] + } + }, + "/club/mine/created": { + "post": { + "tags": [ + "clubs" + ], + "summary": "POST club/mine/created", + "description": "Inferred from Rec Room 2020 client literal `club/mine/created`. Expected result: Club object/list or mutation success.", + "operationId": "client_clubs_club_mine_created_post", + "parameters": [], + "responses": { + "200": { + "description": "Club object/list or mutation success.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Club" + } + } + } + }, + "204": { + "description": "Success with no body, used by some mutation endpoints." + }, + "400": { + "description": "Client-visible validation or bad request error.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "401": { + "description": "Authentication required or token invalid.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "500": { + "description": "Server error shape tolerated by client wrappers.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + } + }, + "x-recroom-client-route-literal": "club/mine/created", + "x-recroom-client-family": "clubs", + "x-recroom-client-service": "clubs", + "x-recroom-builds": [ + "2020.12.18" + ], + "x-recroom-inferred-http-method": "post", + "x-recroom-evidence": [ + { + "type": "JDJGIBLMFKK", + "method": "RecRoom.Async.IPromise`1> LJJLGJIMJED()", + "file": "JDJGIBLMFKK.txt", + "line": 9275 + }, + { + "type": "JDJGIBLMFKK", + "method": "System.Void PJBLNGJKLEB(System.Int64 AOMBLGBCENO)", + "file": "JDJGIBLMFKK.txt", + "line": 26134 + }, + { + "type": "JDJGIBLMFKK", + "method": "System.Void PJBLNGJKLEB(System.Int64 AOMBLGBCENO)", + "file": "JDJGIBLMFKK.txt", + "line": 26326 + } + ], + "requestBody": { + "required": false, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/GenericObject" + } + }, + "application/x-www-form-urlencoded": { + "schema": { + "$ref": "#/components/schemas/GenericObject" + } + }, + "multipart/form-data": { + "schema": { + "$ref": "#/components/schemas/GenericObject" + } + } + }, + "description": "Request body inferred. Exact DTO may be named in x-recroom-evidence method signature or companion DTO inventory." + }, + "x-client-response-contracts": [ + { + "kind": "array", + "raw": "System.Collections.Generic.List`1", + "rootType": "PLILLKHMNDA", + "item": { + "kind": "object", + "raw": "PLILLKHMNDA", + "rootType": "PLILLKHMNDA" + }, + "typeDescription": { + "name": "PLILLKHMNDA", + "found": true, + "file": "PLILLKHMNDA.cs", + "declaration": "public class PLILLKHMNDA : IFAIJAGLDFK, IEquatable", + "kind": "class", + "baseType": "IFAIJAGLDFK", + "properties": [ + { + "name": "BADIGBCKECA", + "type": "int", + "accessibility": "public", + "kind": "property" + }, + { + "name": "CCGOEDABKNN", + "type": "long", + "accessibility": "public", + "kind": "property" + }, + { + "name": "CDINMMPNAID", + "type": "JCMLDDKFKEO", + "accessibility": "public", + "kind": "property" + }, + { + "name": "CDNFGMHLDMJ", + "type": "bool", + "accessibility": "public", + "kind": "property" + }, + { + "name": "EEAOJCGAOCN", + "type": "int", + "accessibility": "public", + "kind": "property" + }, + { + "name": "EHOLKJPEGFF", + "type": "string", + "accessibility": "public", + "kind": "property" + }, + { + "name": "EPGALOMHHMI", + "type": "JCDEFCJLCHN", + "accessibility": "public", + "kind": "property" + }, + { + "name": "FIKEBGGCDFN", + "type": "string", + "accessibility": "public", + "kind": "property" + }, + { + "name": "HHCGNCLFKDM", + "type": "Nullable", + "accessibility": "public", + "kind": "property" + }, + { + "name": "IPKLLFAJJPJ", + "type": "bool", + "accessibility": "public", + "kind": "property" + }, + { + "name": "KODBEJPEFOJ", + "type": "string", + "accessibility": "public", + "kind": "property" + }, + { + "name": "LNGPBGCIAPP", + "type": "string", + "accessibility": "public", + "kind": "property" + }, + { + "name": "MNEEEGHOGAB", + "type": "DIGMAIMMHAP", + "accessibility": "public", + "kind": "property" + }, + { + "name": "PHBCFAJILGD", + "type": "PJCALHOPMKJ", + "accessibility": "public", + "kind": "property" + } + ], + "fields": [], + "enumValues": [], + "wireKeys": [ + "ClubId", + "Name", + "Description", + "MainImageName", + "State", + "CreatorAccountId", + "Category", + "Visibility", + "Joinability", + "AllowJuniors", + "MemberCount", + "IsRRO", + "ClubType" + ], + "isilFile": "PLILLKHMNDA.txt", + "isilType": "PLILLKHMNDA" + } + }, + { + "kind": "unknown", + "raw": "", + "rootType": "unknown", + "typeDescription": { + "name": "unknown", + "found": false, + "kind": "object", + "wireKeys": [] + } + } + ] + } + }, + "/club/mine/member": { + "get": { + "tags": [ + "clubs" + ], + "summary": "GET club/mine/member", + "description": "Inferred from Rec Room 2020 client literal `club/mine/member`. Expected result: Club object/list or mutation success.", + "operationId": "client_clubs_club_mine_member_get", + "parameters": [], + "responses": { + "200": { + "description": "Club object/list or mutation success.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Club" + } + } + } + }, + "204": { + "description": "Success with no body, used by some mutation endpoints." + }, + "400": { + "description": "Client-visible validation or bad request error.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "401": { + "description": "Authentication required or token invalid.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "500": { + "description": "Server error shape tolerated by client wrappers.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + } + }, + "x-recroom-client-route-literal": "club/mine/member", + "x-recroom-client-family": "clubs", + "x-recroom-client-service": "clubs", + "x-recroom-builds": [ + "2020.12.18" + ], + "x-recroom-inferred-http-method": "get", + "x-recroom-evidence": [ + { + "type": "JDJGIBLMFKK", + "method": "RecRoom.Async.IPromise`1> DFDMGNBKEKO()", + "file": "JDJGIBLMFKK.txt", + "line": 9817 + }, + { + "type": "JDJGIBLMFKK", + "method": "System.Void PJBLNGJKLEB(System.Int64 AOMBLGBCENO)", + "file": "JDJGIBLMFKK.txt", + "line": 26194 + }, + { + "type": "JDJGIBLMFKK", + "method": "System.Void PJBLNGJKLEB(System.Int64 AOMBLGBCENO)", + "file": "JDJGIBLMFKK.txt", + "line": 26330 + } + ], + "x-client-response-contracts": [ + { + "kind": "array", + "raw": "System.Collections.Generic.List`1", + "rootType": "PLILLKHMNDA", + "item": { + "kind": "object", + "raw": "PLILLKHMNDA", + "rootType": "PLILLKHMNDA" + }, + "typeDescription": { + "name": "PLILLKHMNDA", + "found": true, + "file": "PLILLKHMNDA.cs", + "declaration": "public class PLILLKHMNDA : IFAIJAGLDFK, IEquatable", + "kind": "class", + "baseType": "IFAIJAGLDFK", + "properties": [ + { + "name": "BADIGBCKECA", + "type": "int", + "accessibility": "public", + "kind": "property" + }, + { + "name": "CCGOEDABKNN", + "type": "long", + "accessibility": "public", + "kind": "property" + }, + { + "name": "CDINMMPNAID", + "type": "JCMLDDKFKEO", + "accessibility": "public", + "kind": "property" + }, + { + "name": "CDNFGMHLDMJ", + "type": "bool", + "accessibility": "public", + "kind": "property" + }, + { + "name": "EEAOJCGAOCN", + "type": "int", + "accessibility": "public", + "kind": "property" + }, + { + "name": "EHOLKJPEGFF", + "type": "string", + "accessibility": "public", + "kind": "property" + }, + { + "name": "EPGALOMHHMI", + "type": "JCDEFCJLCHN", + "accessibility": "public", + "kind": "property" + }, + { + "name": "FIKEBGGCDFN", + "type": "string", + "accessibility": "public", + "kind": "property" + }, + { + "name": "HHCGNCLFKDM", + "type": "Nullable", + "accessibility": "public", + "kind": "property" + }, + { + "name": "IPKLLFAJJPJ", + "type": "bool", + "accessibility": "public", + "kind": "property" + }, + { + "name": "KODBEJPEFOJ", + "type": "string", + "accessibility": "public", + "kind": "property" + }, + { + "name": "LNGPBGCIAPP", + "type": "string", + "accessibility": "public", + "kind": "property" + }, + { + "name": "MNEEEGHOGAB", + "type": "DIGMAIMMHAP", + "accessibility": "public", + "kind": "property" + }, + { + "name": "PHBCFAJILGD", + "type": "PJCALHOPMKJ", + "accessibility": "public", + "kind": "property" + } + ], + "fields": [], + "enumValues": [], + "wireKeys": [ + "ClubId", + "Name", + "Description", + "MainImageName", + "State", + "CreatorAccountId", + "Category", + "Visibility", + "Joinability", + "AllowJuniors", + "MemberCount", + "IsRRO", + "ClubType" + ], + "isilFile": "PLILLKHMNDA.txt", + "isilType": "PLILLKHMNDA" + } + }, + { + "kind": "unknown", + "raw": "", + "rootType": "unknown", + "typeDescription": { + "name": "unknown", + "found": false, + "kind": "object", + "wireKeys": [] + } + } + ] + } + }, + "/members/bulk": { + "post": { + "tags": [ + "clubs" + ], + "summary": "POST members/bulk", + "description": "Inferred from Rec Room 2020 client literal `members/bulk`. Expected result: Club member list/object or membership mutation success.", + "operationId": "client_clubs_members_bulk_post", + "parameters": [], + "responses": { + "200": { + "description": "Club member list/object or membership mutation success.", + "content": { + "application/json": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/Club" + } + } + } + } + }, + "204": { + "description": "Success with no body, used by some mutation endpoints." + }, + "400": { + "description": "Client-visible validation or bad request error.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "401": { + "description": "Authentication required or token invalid.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "500": { + "description": "Server error shape tolerated by client wrappers.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + } + }, + "x-recroom-client-route-literal": "members/bulk", + "x-recroom-client-family": "clubs", + "x-recroom-client-service": "clubs", + "x-recroom-builds": [ + "2020.12.18" + ], + "x-recroom-inferred-http-method": "post", + "x-recroom-evidence": [ + { + "type": "JDJGIBLMFKK", + "method": "RecRoom.Async.IPromise`1> DDIJPHDEDDM(System.Int64 AOMBLGBCENO, System.Collections.Generic.List`1 ILNGMAANNDG)", + "file": "JDJGIBLMFKK.txt", + "line": 16847 + } + ], + "requestBody": { + "required": false, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/GenericObject" + } + }, + "application/x-www-form-urlencoded": { + "schema": { + "$ref": "#/components/schemas/GenericObject" + } + }, + "multipart/form-data": { + "schema": { + "$ref": "#/components/schemas/GenericObject" + } + } + }, + "description": "Request body inferred. Exact DTO may be named in x-recroom-evidence method signature or companion DTO inventory." + }, + "x-client-response-contracts": [ + { + "kind": "array", + "raw": "System.Collections.Generic.List`1", + "rootType": "JHMMGLNJHIB", + "item": { + "kind": "object", + "raw": "JHMMGLNJHIB", + "rootType": "JHMMGLNJHIB" + }, + "typeDescription": { + "name": "JHMMGLNJHIB", + "found": true, + "file": "JHMMGLNJHIB.cs", + "declaration": "public class JHMMGLNJHIB : IFAIJAGLDFK", + "kind": "class", + "baseType": "IFAIJAGLDFK", + "properties": [ + { + "name": "GAINIOENNCG", + "type": "int", + "accessibility": "public", + "kind": "property" + }, + { + "name": "JNMPJNKEJAC", + "type": "PPGPAHNMGEC", + "accessibility": "public", + "kind": "property" + } + ], + "fields": [], + "enumValues": [], + "wireKeys": [ + "AccountId", + "MembershipType" + ], + "isilFile": "JHMMGLNJHIB.txt", + "isilType": "JHMMGLNJHIB" + } + } + ] + }, + "get": { + "tags": [ + "clubs" + ], + "summary": "GET members/bulk?", + "description": "Inferred from Rec Room 2020 client literal `members/bulk?`. Expected result: Club member list/object or membership mutation success.", + "operationId": "client_clubs_members_bulk_get", + "parameters": [], + "responses": { + "200": { + "description": "Club member list/object or membership mutation success.", + "content": { + "application/json": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/Club" + } + } + } + } + }, + "204": { + "description": "Success with no body, used by some mutation endpoints." + }, + "400": { + "description": "Client-visible validation or bad request error.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "401": { + "description": "Authentication required or token invalid.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "500": { + "description": "Server error shape tolerated by client wrappers.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + } + }, + "x-recroom-client-route-literal": "members/bulk?", + "x-recroom-client-family": "clubs", + "x-recroom-client-service": "clubs", + "x-recroom-builds": [ + "2020.12.18" + ], + "x-recroom-inferred-http-method": "get", + "x-recroom-evidence": [ + { + "type": "JDJGIBLMFKK", + "method": "RecRoom.Async.IPromise`1> DDIJPHDEDDM(System.Int64 AOMBLGBCENO, System.Collections.Generic.List`1 ILNGMAANNDG)", + "file": "JDJGIBLMFKK.txt", + "line": 16893 + } + ], + "x-client-response-contracts": [ + { + "kind": "array", + "raw": "System.Collections.Generic.List`1", + "rootType": "JHMMGLNJHIB", + "item": { + "kind": "object", + "raw": "JHMMGLNJHIB", + "rootType": "JHMMGLNJHIB" + }, + "typeDescription": { + "name": "JHMMGLNJHIB", + "found": true, + "file": "JHMMGLNJHIB.cs", + "declaration": "public class JHMMGLNJHIB : IFAIJAGLDFK", + "kind": "class", + "baseType": "IFAIJAGLDFK", + "properties": [ + { + "name": "GAINIOENNCG", + "type": "int", + "accessibility": "public", + "kind": "property" + }, + { + "name": "JNMPJNKEJAC", + "type": "PPGPAHNMGEC", + "accessibility": "public", + "kind": "property" + } + ], + "fields": [], + "enumValues": [], + "wireKeys": [ + "AccountId", + "MembershipType" + ], + "isilFile": "JHMMGLNJHIB.txt", + "isilType": "JHMMGLNJHIB" + } + } + ] + } + }, + "/api/communityboard": { + "get": { + "tags": [ + "community-board" + ], + "summary": "GET api/communityboard/", + "description": "Inferred from Rec Room 2020 client literal `api/communityboard/`. Expected result: Generic object/array/success result inferred from client route.", + "operationId": "client_community_board_api_communityboard_get", + "parameters": [], + "responses": { + "200": { + "description": "Generic object/array/success result inferred from client route.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/CommunityBoard" + } + } + } + }, + "204": { + "description": "Success with no body, used by some mutation endpoints." + }, + "400": { + "description": "Client-visible validation or bad request error.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "401": { + "description": "Authentication required or token invalid.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "500": { + "description": "Server error shape tolerated by client wrappers.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + } + }, + "x-recroom-client-route-literal": "api/communityboard/", + "x-recroom-client-family": "community-board", + "x-recroom-client-service": "api", + "x-recroom-builds": [ + "2020.03.10" + ], + "x-recroom-inferred-http-method": "get", + "x-recroom-evidence": [ + { + "type": "stringliteral.json", + "method": "", + "file": "Recnet-archive/decompiled/Il2CppDump/stringliteral.json", + "line": null + } + ], + "x-client-response-contracts": [ + { + "kind": "object", + "raw": "AAHIIPOCKMB+JFIKDPALHOL", + "rootType": "JFIKDPALHOL", + "typeDescription": { + "name": "JFIKDPALHOL", + "found": true, + "file": "AAHIIPOCKMB.cs", + "declaration": "internal class JFIKDPALHOL : IFAIJAGLDFK", + "kind": "class", + "baseType": "IFAIJAGLDFK", + "properties": [ + { + "name": "AIPAMBHPAHP", + "type": "DOBMPPKDFPI", + "accessibility": "public", + "kind": "property" + }, + { + "name": "GPHCPJEKDLB", + "type": "NMPFCIJPODA", + "accessibility": "public", + "kind": "property" + }, + { + "name": "LBFMANLIKEI", + "type": "List", + "accessibility": "public", + "kind": "property" + }, + { + "name": "LCJDMHPMFBP", + "type": "EHNFCEJANHA", + "accessibility": "public", + "kind": "property" + }, + { + "name": "MCCBJHDGGAG", + "type": "List", + "accessibility": "public", + "kind": "property" + } + ], + "fields": [], + "enumValues": [], + "wireKeys": [ + "FeaturedPlayer", + "FeaturedRoomGroup", + "CurrentAnnouncement" + ], + "isilFile": "AAHIIPOCKMB_NestedType_JFIKDPALHOL.txt", + "isilType": "AAHIIPOCKMB+JFIKDPALHOL" + } + } + ] + }, + "post": { + "tags": [ + "misc" + ], + "summary": "POST api/communityboard/", + "description": "Inferred from Rec Room 2020 client literal `api/communityboard/`. Expected result: Generic object/array/success result inferred from client route.", + "operationId": "client_misc_api_communityboard_post", + "parameters": [], + "responses": { + "200": { + "description": "Generic object/array/success result inferred from client route.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/GenericObject" + } + } + } + }, + "204": { + "description": "Success with no body, used by some mutation endpoints." + }, + "400": { + "description": "Client-visible validation or bad request error.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "401": { + "description": "Authentication required or token invalid.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "500": { + "description": "Server error shape tolerated by client wrappers.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + } + }, + "x-recroom-client-route-literal": "api/communityboard/", + "x-recroom-client-family": "misc", + "x-recroom-client-service": "api", + "x-recroom-builds": [ + "2020.12.18" + ], + "x-recroom-inferred-http-method": "post", + "x-recroom-evidence": [ + { + "type": "AAHIIPOCKMB", + "method": "RecRoom.Async.IPromise`1 KIAGEGDDBCG()", + "file": "AAHIIPOCKMB.txt", + "line": 941 + } + ], + "requestBody": { + "required": false, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/GenericObject" + } + }, + "application/x-www-form-urlencoded": { + "schema": { + "$ref": "#/components/schemas/GenericObject" + } + }, + "multipart/form-data": { + "schema": { + "$ref": "#/components/schemas/GenericObject" + } + } + }, + "description": "Request body inferred. Exact DTO may be named in x-recroom-evidence method signature or companion DTO inventory." + }, + "x-client-response-contracts": [ + { + "kind": "object", + "raw": "AAHIIPOCKMB+JFIKDPALHOL", + "rootType": "JFIKDPALHOL", + "typeDescription": { + "name": "JFIKDPALHOL", + "found": true, + "file": "AAHIIPOCKMB.cs", + "declaration": "internal class JFIKDPALHOL : IFAIJAGLDFK", + "kind": "class", + "baseType": "IFAIJAGLDFK", + "properties": [ + { + "name": "AIPAMBHPAHP", + "type": "DOBMPPKDFPI", + "accessibility": "public", + "kind": "property" + }, + { + "name": "GPHCPJEKDLB", + "type": "NMPFCIJPODA", + "accessibility": "public", + "kind": "property" + }, + { + "name": "LBFMANLIKEI", + "type": "List", + "accessibility": "public", + "kind": "property" + }, + { + "name": "LCJDMHPMFBP", + "type": "EHNFCEJANHA", + "accessibility": "public", + "kind": "property" + }, + { + "name": "MCCBJHDGGAG", + "type": "List", + "accessibility": "public", + "kind": "property" + } + ], + "fields": [], + "enumValues": [], + "wireKeys": [ + "FeaturedPlayer", + "FeaturedRoomGroup", + "CurrentAnnouncement" + ], + "isilFile": "AAHIIPOCKMB_NestedType_JFIKDPALHOL.txt", + "isilType": "AAHIIPOCKMB+JFIKDPALHOL" + } + } + ] + } + }, + "/api/config/v1/freegiftbutton": { + "get": { + "tags": [ + "config-settings" + ], + "summary": "GET api/config/v1/freegiftbutton", + "description": "Inferred from Rec Room 2020 client literal `api/config/v1/freegiftbutton`. Expected result: Configuration or settings object.", + "operationId": "client_config_settings_api_config_v1_freegiftbutton_get", + "parameters": [], + "responses": { + "200": { + "description": "Configuration or settings object.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ConfigObject" + } + } + } + }, + "204": { + "description": "Success with no body, used by some mutation endpoints." + }, + "400": { + "description": "Client-visible validation or bad request error.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "401": { + "description": "Authentication required or token invalid.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "500": { + "description": "Server error shape tolerated by client wrappers.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + } + }, + "x-recroom-client-route-literal": "api/config/v1/freegiftbutton", + "x-recroom-client-family": "config-settings", + "x-recroom-client-service": "api", + "x-recroom-builds": [ + "2020.12.18" + ], + "x-recroom-inferred-http-method": "get", + "x-recroom-evidence": [ + { + "type": "BBEFMHAEEEA", + "method": "RecRoom.Async.IPromise`1 GMOMCLADPII()", + "file": "BBEFMHAEEEA.txt", + "line": 1230 + } + ], + "x-client-response-contracts": [ + { + "kind": "primitive", + "raw": "System.Boolean", + "rootType": "boolean", + "typeDescription": { + "name": "boolean", + "found": false, + "kind": "primitive", + "wireKeys": [] + } + } + ] + } + }, + "/api/config": { + "get": { + "tags": [ + "config-settings" + ], + "summary": "GET api/config/", + "description": "Inferred from Rec Room 2020 client literal `api/config/`. Expected result: Configuration or settings object.", + "operationId": "client_config_settings_api_config_get", + "parameters": [], + "responses": { + "200": { + "description": "Configuration or settings object.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ConfigObject" + } + } + } + }, + "204": { + "description": "Success with no body, used by some mutation endpoints." + }, + "400": { + "description": "Client-visible validation or bad request error.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "401": { + "description": "Authentication required or token invalid.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "500": { + "description": "Server error shape tolerated by client wrappers.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + } + }, + "x-recroom-client-route-literal": "api/config/", + "x-recroom-client-family": "config-settings", + "x-recroom-client-service": "api", + "x-recroom-builds": [ + "2020.03.10", + "2020.12.18" + ], + "x-recroom-inferred-http-method": "get", + "x-recroom-evidence": [ + { + "type": "BBEFMHAEEEA", + "method": "RecRoom.Async.IPromise BGNDHOHFJBN()", + "file": "BBEFMHAEEEA.txt", + "line": 950 + }, + { + "type": "BBEFMHAEEEA", + "method": "RecRoom.Async.IPromise OHHAEMHMIFE()", + "file": "BBEFMHAEEEA.txt", + "line": 655 + }, + { + "type": "BBEFMHAEEEA", + "method": "RecRoom.Async.IPromise`1> EAJPLMPNPIJ(System.Int32 CKEBCMEKCJH)", + "file": "BBEFMHAEEEA.txt", + "line": 1147 + }, + { + "type": "stringliteral.json", + "method": "", + "file": "Recnet-archive/decompiled/Il2CppDump/stringliteral.json", + "line": null + } + ], + "x-client-response-contracts": [ + { + "kind": "success-or-empty", + "raw": "RecRoom.Async.IPromise", + "rootType": "void/success", + "typeDescription": { + "name": "void/success", + "found": false, + "kind": "object", + "wireKeys": [] + } + }, + { + "kind": "array", + "raw": "System.Collections.Generic.List`1", + "rootType": "FPDADCNKELI", + "item": { + "kind": "object", + "raw": "FPDADCNKELI", + "rootType": "FPDADCNKELI" + }, + "typeDescription": { + "name": "FPDADCNKELI", + "found": true, + "file": "FPDADCNKELI.cs", + "declaration": "public class FPDADCNKELI : IFAIJAGLDFK", + "kind": "class", + "baseType": "IFAIJAGLDFK", + "properties": [ + { + "name": "BOFMFGLDDAA", + "type": "string", + "accessibility": "public", + "kind": "property" + }, + { + "name": "CGIKDDCIDNI", + "type": "string", + "accessibility": "public", + "kind": "property" + }, + { + "name": "DBCDHIABGIP", + "type": "string", + "accessibility": "public", + "kind": "property" + }, + { + "name": "DDLLPNLMKPP", + "type": "string", + "accessibility": "public", + "kind": "property" + }, + { + "name": "EHFBCMOHHAI", + "type": "string", + "accessibility": "public", + "kind": "property" + }, + { + "name": "IDPOBNAABIK", + "type": "int", + "accessibility": "public", + "kind": "property" + }, + { + "name": "JMDDEHCPHII", + "type": "string", + "accessibility": "public", + "kind": "property" + }, + { + "name": "LLAAHHEBHFP", + "type": "FCCPDGMCPBF", + "accessibility": "public", + "kind": "property" + }, + { + "name": "LMNDJGOMBNL", + "type": "NJDEOAENIMH", + "accessibility": "public", + "kind": "property" + } + ], + "fields": [], + "enumValues": [], + "wireKeys": [ + "Version", + "ButtonNumber", + "Override", + "CustomRoomName", + "CustomTitle", + "CustomDescription", + "DefaultRoomName", + "DefaultTitle", + "DefaultDescription" + ], + "isilFile": "FPDADCNKELI.txt", + "isilType": "FPDADCNKELI" + } + } + ] + } + }, + "/api/gameconfigs/{tail}": { + "get": { + "tags": [ + "config-settings" + ], + "summary": "GET api/gameconfigs/", + "description": "Inferred from Rec Room 2020 client literal `api/gameconfigs/`. Expected result: Configuration or settings object.", + "operationId": "client_config_settings_api_gameconfigs_tail_get", + "parameters": [ + { + "name": "tail", + "in": "path", + "required": true, + "schema": { + "type": "string" + }, + "description": "Format placeholder from client literal api/gameconfigs/" + } + ], + "responses": { + "200": { + "description": "Configuration or settings object.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ConfigObject" + } + } + } + }, + "204": { + "description": "Success with no body, used by some mutation endpoints." + }, + "400": { + "description": "Client-visible validation or bad request error.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "401": { + "description": "Authentication required or token invalid.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "500": { + "description": "Server error shape tolerated by client wrappers.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + } + }, + "x-recroom-client-route-literal": "api/gameconfigs/", + "x-recroom-client-family": "config-settings", + "x-recroom-client-service": "api", + "x-recroom-builds": [ + "2020.03.10", + "2020.12.18" + ], + "x-recroom-inferred-http-method": "get", + "x-recroom-evidence": [ + { + "type": "IGCCFMFHBBN", + "method": "System.Void .cctor()", + "file": "IGCCFMFHBBN.txt", + "line": 1921 + }, + { + "type": "stringliteral.json", + "method": "", + "file": "Recnet-archive/decompiled/Il2CppDump/stringliteral.json", + "line": null + } + ], + "x-client-response-contracts": [ + { + "kind": "unknown", + "raw": "", + "rootType": "unknown", + "typeDescription": { + "name": "unknown", + "found": false, + "kind": "object", + "wireKeys": [] + } + } + ] + } + }, + "/api/settings": { + "post": { + "tags": [ + "config-settings" + ], + "summary": "POST api/settings/", + "description": "Inferred from Rec Room 2020 client literal `api/settings/`. Expected result: Configuration or settings object.", + "operationId": "client_config_settings_api_settings_post", + "parameters": [], + "responses": { + "200": { + "description": "Configuration or settings object.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ConfigObject" + } + } + } + }, + "204": { + "description": "Success with no body, used by some mutation endpoints." + }, + "400": { + "description": "Client-visible validation or bad request error.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "401": { + "description": "Authentication required or token invalid.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "500": { + "description": "Server error shape tolerated by client wrappers.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + } + }, + "x-recroom-client-route-literal": "api/settings/", + "x-recroom-client-family": "config-settings", + "x-recroom-client-service": "api", + "x-recroom-builds": [ + "2020.03.10", + "2020.12.18" + ], + "x-recroom-inferred-http-method": "post", + "x-recroom-evidence": [ + { + "type": "JFDILELKPAL", + "method": "RecRoom.Async.IPromise`1> HOHPJKNPEGK()", + "file": "JFDILELKPAL.txt", + "line": 48 + }, + { + "type": "JFDILELKPAL", + "method": "System.Collections.IEnumerator LHGBAFBJAHM(OHLKCDKEGMN AOKDECDAEEG, BPHGKAEDBPE+OJDJIJDNFHE AFLPGGJMPOE)", + "file": "JFDILELKPAL.txt", + "line": 224 + }, + { + "type": "stringliteral.json", + "method": "", + "file": "Recnet-archive/decompiled/Il2CppDump/stringliteral.json", + "line": null + } + ], + "requestBody": { + "required": false, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/GenericObject" + } + }, + "application/x-www-form-urlencoded": { + "schema": { + "$ref": "#/components/schemas/GenericObject" + } + }, + "multipart/form-data": { + "schema": { + "$ref": "#/components/schemas/GenericObject" + } + } + }, + "description": "Request body inferred. Exact DTO may be named in x-recroom-evidence method signature or companion DTO inventory." + }, + "x-client-response-contracts": [ + { + "kind": "array", + "raw": "System.Collections.Generic.List`1", + "rootType": "OHLKCDKEGMN", + "item": { + "kind": "object", + "raw": "OHLKCDKEGMN", + "rootType": "OHLKCDKEGMN" + }, + "typeDescription": { + "name": "OHLKCDKEGMN", + "found": true, + "file": "OHLKCDKEGMN.cs", + "declaration": "public class OHLKCDKEGMN : IFAIJAGLDFK, AKJKEMONOIL", + "kind": "class", + "baseType": "IFAIJAGLDFK", + "properties": [ + { + "name": "AKFDMGLACLA", + "type": "string", + "accessibility": "public", + "kind": "property" + }, + { + "name": "DMLIOOCLKKP", + "type": "string", + "accessibility": "public", + "kind": "property" + } + ], + "fields": [], + "enumValues": [], + "wireKeys": [ + "Key", + "Value" + ], + "isilFile": "OHLKCDKEGMN.txt", + "isilType": "OHLKCDKEGMN" + } + }, + { + "kind": "callback-or-coroutine", + "raw": "IEnumerator", + "rootType": "callback", + "typeDescription": { + "name": "callback", + "found": false, + "kind": "object", + "wireKeys": [] + } + } + ] + } + }, + "/config/{p0}": { + "get": { + "tags": [ + "config-settings" + ], + "summary": "GET /config/{0}", + "description": "Inferred from Rec Room 2020 client literal `/config/{0}`. Expected result: Configuration or settings object.", + "operationId": "client_config_settings_config_p0_get", + "parameters": [ + { + "name": "p0", + "in": "path", + "required": true, + "schema": { + "type": "string" + }, + "description": "Format placeholder from client literal /config/{0}" + } + ], + "responses": { + "200": { + "description": "Configuration or settings object.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ConfigObject" + } + } + } + }, + "204": { + "description": "Success with no body, used by some mutation endpoints." + }, + "400": { + "description": "Client-visible validation or bad request error.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "401": { + "description": "Authentication required or token invalid.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "500": { + "description": "Server error shape tolerated by client wrappers.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + } + }, + "x-recroom-client-route-literal": "/config/{0}", + "x-recroom-client-family": "config-settings", + "x-recroom-client-service": "api", + "x-recroom-builds": [ + "2020.03.10", + "2020.12.18" + ], + "x-recroom-inferred-http-method": "get", + "x-recroom-evidence": [ + { + "type": "FHLGJDFHOKL", + "method": "RecRoom.Async.IPromise MCDNGDMJHBE()", + "file": "FHLGJDFHOKL.txt", + "line": 502 + }, + { + "type": "stringliteral.json", + "method": "", + "file": "Recnet-archive/decompiled/Il2CppDump/stringliteral.json", + "line": null + } + ], + "x-client-response-contracts": [ + { + "kind": "success-or-empty", + "raw": "RecRoom.Async.IPromise", + "rootType": "void/success", + "typeDescription": { + "name": "void/success", + "found": false, + "kind": "object", + "wireKeys": [] + } + } + ] + } + }, + "/api/consumables/{tail}": { + "get": { + "tags": [ + "economy" + ], + "summary": "GET api/consumables/", + "description": "Inferred from Rec Room 2020 client literal `api/consumables/`. Expected result: Reward, consumable, currency, or catalog/economy result.", + "operationId": "client_economy_api_consumables_tail_get", + "parameters": [ + { + "name": "tail", + "in": "path", + "required": true, + "schema": { + "type": "string" + }, + "description": "Format placeholder from client literal api/consumables/" + } + ], + "responses": { + "200": { + "description": "Reward, consumable, currency, or catalog/economy result.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/EconomyResult" + } + } + } + }, + "204": { + "description": "Success with no body, used by some mutation endpoints." + }, + "400": { + "description": "Client-visible validation or bad request error.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "401": { + "description": "Authentication required or token invalid.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "500": { + "description": "Server error shape tolerated by client wrappers.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + } + }, + "x-recroom-client-route-literal": "api/consumables/", + "x-recroom-client-family": "economy", + "x-recroom-client-service": "api", + "x-recroom-builds": [ + "2020.03.10" + ], + "x-recroom-inferred-http-method": "get", + "x-recroom-evidence": [ + { + "type": "stringliteral.json", + "method": "", + "file": "Recnet-archive/decompiled/Il2CppDump/stringliteral.json", + "line": null + } + ], + "x-client-response-contracts": [ + { + "kind": "success-or-empty", + "raw": "RecRoom.Async.IPromise", + "rootType": "void/success", + "typeDescription": { + "name": "void/success", + "found": false, + "kind": "object", + "wireKeys": [] + } + }, + { + "kind": "array", + "raw": "System.Collections.Generic.List`1", + "rootType": "OOCCCGPICOG", + "item": { + "kind": "object", + "raw": "OOCCCGPICOG", + "rootType": "OOCCCGPICOG" + }, + "typeDescription": { + "name": "OOCCCGPICOG", + "found": true, + "file": "OOCCCGPICOG.cs", + "declaration": "public class OOCCCGPICOG : IFAIJAGLDFK, IEquatable", + "kind": "class", + "baseType": "IFAIJAGLDFK", + "properties": [ + { + "name": "ACBFDMLHFPB", + "type": "DateTime", + "accessibility": "public", + "kind": "property" + }, + { + "name": "AFPIKAOJDGM", + "type": "bool", + "accessibility": "public", + "kind": "property" + }, + { + "name": "BPFGOHALEJO", + "type": "Nullable", + "accessibility": "public", + "kind": "property" + }, + { + "name": "GMMAAAGFAKO", + "type": "KIDKMAOBOIG", + "accessibility": "public", + "kind": "property" + }, + { + "name": "HAKADJAKPDO", + "type": "string", + "accessibility": "public", + "kind": "property" + }, + { + "name": "JPOHGBCEJEJ", + "type": "long", + "accessibility": "public", + "kind": "property" + }, + { + "name": "LENBPGEABJK", + "type": "ConsumableInfo", + "accessibility": "public", + "kind": "property" + }, + { + "name": "MAPJICLFKFB", + "type": "int", + "accessibility": "public", + "kind": "property" + }, + { + "name": "MEPGPGKFENE", + "type": "int", + "accessibility": "public", + "kind": "property" + }, + { + "name": "MIKDLDEALPN", + "type": "bool", + "accessibility": "public", + "kind": "property" + } + ], + "fields": [], + "enumValues": [], + "wireKeys": [ + "Id", + "ConsumableItemDesc", + "CreatedAt", + "Count", + "InitialCount", + "IsActive", + "ActiveDurationMinutes", + "IsTransferable" + ], + "isilFile": "OOCCCGPICOG.txt", + "isilType": "OOCCCGPICOG" + } + }, + { + "kind": "callback-or-coroutine", + "raw": "IEnumerator", + "rootType": "callback", + "typeDescription": { + "name": "callback", + "found": false, + "kind": "object", + "wireKeys": [] + } + }, + { + "kind": "unknown", + "raw": "", + "rootType": "unknown", + "typeDescription": { + "name": "unknown", + "found": false, + "kind": "object", + "wireKeys": [] + } + } + ] + }, + "post": { + "tags": [ + "misc" + ], + "summary": "POST api/consumables/", + "description": "Inferred from Rec Room 2020 client literal `api/consumables/`. Expected result: Generic object/array/success result inferred from client route.", + "operationId": "client_misc_api_consumables_tail_post", + "parameters": [ + { + "name": "tail", + "in": "path", + "required": true, + "schema": { + "type": "string" + }, + "description": "Format placeholder from client literal api/consumables/" + } + ], + "responses": { + "200": { + "description": "Generic object/array/success result inferred from client route.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/GenericObject" + } + } + } + }, + "204": { + "description": "Success with no body, used by some mutation endpoints." + }, + "400": { + "description": "Client-visible validation or bad request error.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "401": { + "description": "Authentication required or token invalid.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "500": { + "description": "Server error shape tolerated by client wrappers.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + } + }, + "x-recroom-client-route-literal": "api/consumables/", + "x-recroom-client-family": "misc", + "x-recroom-client-service": "api", + "x-recroom-builds": [ + "2020.12.18" + ], + "x-recroom-inferred-http-method": "post", + "x-recroom-evidence": [ + { + "type": "FOEHJBDMMBH", + "method": "RecRoom.Async.IPromise AKFDEPEHBIN(OOCCCGPICOG BAJGNABECMN, System.Int32 HLHHPEKALPI)", + "file": "FOEHJBDMMBH.txt", + "line": 1463 + }, + { + "type": "FOEHJBDMMBH", + "method": "RecRoom.Async.IPromise`1> AFMKIHFMMBI(System.Int32 HLHHPEKALPI)", + "file": "FOEHJBDMMBH.txt", + "line": 1112 + }, + { + "type": "FOEHJBDMMBH", + "method": "System.Collections.IEnumerator LCELAOEOIJO(OOCCCGPICOG BAJGNABECMN, System.Int32 FKKAPHAMPMG, BPHGKAEDBPE+CBEOHBCIPEA AFLPGGJMPOE)", + "file": "FOEHJBDMMBH.txt", + "line": 838 + }, + { + "type": "FOEHJBDMMBH", + "method": "System.Collections.IEnumerator PKHAMBFCEJI(OOCCCGPICOG BAJGNABECMN, System.Boolean CNEIBILGFCO, BPHGKAEDBPE+CBEOHBCIPEA AFLPGGJMPOE)", + "file": "FOEHJBDMMBH.txt", + "line": 619 + }, + { + "type": "FOEHJBDMMBH+HHFGPIBAMOB", + "method": "System.Boolean MoveNext()", + "file": "FOEHJBDMMBH_NestedType_HHFGPIBAMOB.txt", + "line": 147 + } + ], + "requestBody": { + "required": false, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/GenericObject" + } + }, + "application/x-www-form-urlencoded": { + "schema": { + "$ref": "#/components/schemas/GenericObject" + } + }, + "multipart/form-data": { + "schema": { + "$ref": "#/components/schemas/GenericObject" + } + } + }, + "description": "Request body inferred. Exact DTO may be named in x-recroom-evidence method signature or companion DTO inventory." + }, + "x-client-response-contracts": [ + { + "kind": "success-or-empty", + "raw": "RecRoom.Async.IPromise", + "rootType": "void/success", + "typeDescription": { + "name": "void/success", + "found": false, + "kind": "object", + "wireKeys": [] + } + }, + { + "kind": "array", + "raw": "System.Collections.Generic.List`1", + "rootType": "OOCCCGPICOG", + "item": { + "kind": "object", + "raw": "OOCCCGPICOG", + "rootType": "OOCCCGPICOG" + }, + "typeDescription": { + "name": "OOCCCGPICOG", + "found": true, + "file": "OOCCCGPICOG.cs", + "declaration": "public class OOCCCGPICOG : IFAIJAGLDFK, IEquatable", + "kind": "class", + "baseType": "IFAIJAGLDFK", + "properties": [ + { + "name": "ACBFDMLHFPB", + "type": "DateTime", + "accessibility": "public", + "kind": "property" + }, + { + "name": "AFPIKAOJDGM", + "type": "bool", + "accessibility": "public", + "kind": "property" + }, + { + "name": "BPFGOHALEJO", + "type": "Nullable", + "accessibility": "public", + "kind": "property" + }, + { + "name": "GMMAAAGFAKO", + "type": "KIDKMAOBOIG", + "accessibility": "public", + "kind": "property" + }, + { + "name": "HAKADJAKPDO", + "type": "string", + "accessibility": "public", + "kind": "property" + }, + { + "name": "JPOHGBCEJEJ", + "type": "long", + "accessibility": "public", + "kind": "property" + }, + { + "name": "LENBPGEABJK", + "type": "ConsumableInfo", + "accessibility": "public", + "kind": "property" + }, + { + "name": "MAPJICLFKFB", + "type": "int", + "accessibility": "public", + "kind": "property" + }, + { + "name": "MEPGPGKFENE", + "type": "int", + "accessibility": "public", + "kind": "property" + }, + { + "name": "MIKDLDEALPN", + "type": "bool", + "accessibility": "public", + "kind": "property" + } + ], + "fields": [], + "enumValues": [], + "wireKeys": [ + "Id", + "ConsumableItemDesc", + "CreatedAt", + "Count", + "InitialCount", + "IsActive", + "ActiveDurationMinutes", + "IsTransferable" + ], + "isilFile": "OOCCCGPICOG.txt", + "isilType": "OOCCCGPICOG" + } + }, + { + "kind": "callback-or-coroutine", + "raw": "IEnumerator", + "rootType": "callback", + "typeDescription": { + "name": "callback", + "found": false, + "kind": "object", + "wireKeys": [] + } + }, + { + "kind": "unknown", + "raw": "", + "rootType": "unknown", + "typeDescription": { + "name": "unknown", + "found": false, + "kind": "object", + "wireKeys": [] + } + } + ] + } + }, + "/api/gamerewards/v1/pending": { + "get": { + "tags": [ + "economy" + ], + "summary": "GET api/gamerewards/v1/pending", + "description": "Inferred from Rec Room 2020 client literal `api/gamerewards/v1/pending`. Expected result: Reward, consumable, currency, or catalog/economy result.", + "operationId": "client_economy_api_gamerewards_v1_pending_get", + "parameters": [], + "responses": { + "200": { + "description": "Reward, consumable, currency, or catalog/economy result.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/EconomyResult" + } + } + } + }, + "204": { + "description": "Success with no body, used by some mutation endpoints." + }, + "400": { + "description": "Client-visible validation or bad request error.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "401": { + "description": "Authentication required or token invalid.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "500": { + "description": "Server error shape tolerated by client wrappers.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + } + }, + "x-recroom-client-route-literal": "api/gamerewards/v1/pending", + "x-recroom-client-family": "economy", + "x-recroom-client-service": "api", + "x-recroom-builds": [ + "2020.12.18" + ], + "x-recroom-inferred-http-method": "get", + "x-recroom-evidence": [ + { + "type": "COICJCJBABL", + "method": "RecRoom.Async.IPromise AOJJODPOJFO()", + "file": "COICJCJBABL.txt", + "line": 520 + } + ], + "x-client-response-contracts": [ + { + "kind": "success-or-empty", + "raw": "RecRoom.Async.IPromise", + "rootType": "void/success", + "typeDescription": { + "name": "void/success", + "found": false, + "kind": "object", + "wireKeys": [] + } + } + ] + } + }, + "/api/gamerewards/v1/select": { + "post": { + "tags": [ + "economy" + ], + "summary": "POST api/gamerewards/v1/select", + "description": "Inferred from Rec Room 2020 client literal `api/gamerewards/v1/select`. Expected result: Reward, consumable, currency, or catalog/economy result.", + "operationId": "client_economy_api_gamerewards_v1_select_post", + "parameters": [], + "responses": { + "200": { + "description": "Reward, consumable, currency, or catalog/economy result.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/EconomyResult" + } + } + } + }, + "204": { + "description": "Success with no body, used by some mutation endpoints." + }, + "400": { + "description": "Client-visible validation or bad request error.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "401": { + "description": "Authentication required or token invalid.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "500": { + "description": "Server error shape tolerated by client wrappers.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + } + }, + "x-recroom-client-route-literal": "api/gamerewards/v1/select", + "x-recroom-client-family": "economy", + "x-recroom-client-service": "api", + "x-recroom-builds": [ + "2020.12.18" + ], + "x-recroom-inferred-http-method": "post", + "x-recroom-evidence": [ + { + "type": "COICJCJBABL", + "method": "RecRoom.Async.IPromise ONNNOAKDIIP(COICJCJBABL+HNJGHCGJJFC FDAEIMEHDJJ, System.Int32 IAAKEHDHCAC)", + "file": "COICJCJBABL.txt", + "line": 1055 + } + ], + "requestBody": { + "required": false, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/GenericObject" + } + }, + "application/x-www-form-urlencoded": { + "schema": { + "$ref": "#/components/schemas/GenericObject" + } + }, + "multipart/form-data": { + "schema": { + "$ref": "#/components/schemas/GenericObject" + } + } + }, + "description": "Request body inferred. Exact DTO may be named in x-recroom-evidence method signature or companion DTO inventory." + }, + "x-client-response-contracts": [ + { + "kind": "success-or-empty", + "raw": "RecRoom.Async.IPromise", + "rootType": "void/success", + "typeDescription": { + "name": "void/success", + "found": false, + "kind": "object", + "wireKeys": [] + } + } + ] + } + }, + "/consume": { + "post": { + "tags": [ + "economy" + ], + "summary": "POST /consume", + "description": "Inferred from Rec Room 2020 client literal `/consume`. Expected result: Reward, consumable, currency, or catalog/economy result.", + "operationId": "client_economy_consume_post", + "parameters": [], + "responses": { + "200": { + "description": "Reward, consumable, currency, or catalog/economy result.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/EconomyResult" + } + } + } + }, + "204": { + "description": "Success with no body, used by some mutation endpoints." + }, + "400": { + "description": "Client-visible validation or bad request error.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "401": { + "description": "Authentication required or token invalid.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "500": { + "description": "Server error shape tolerated by client wrappers.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + } + }, + "x-recroom-client-route-literal": "/consume", + "x-recroom-client-family": "economy", + "x-recroom-client-service": "api", + "x-recroom-builds": [ + "2020.12.18" + ], + "x-recroom-inferred-http-method": "post", + "x-recroom-evidence": [ + { + "type": "COFHGNFJMOG", + "method": "RecRoom.Async.IPromise`1 HDLJGFEDGLH(System.String ENJEOLBEALP, System.Nullable`1 LNBJKLOINED)", + "file": "COFHGNFJMOG.txt", + "line": 1242 + } + ], + "requestBody": { + "required": false, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/GenericObject" + } + }, + "application/x-www-form-urlencoded": { + "schema": { + "$ref": "#/components/schemas/GenericObject" + } + }, + "multipart/form-data": { + "schema": { + "$ref": "#/components/schemas/GenericObject" + } + } + }, + "description": "Request body inferred. Exact DTO may be named in x-recroom-evidence method signature or companion DTO inventory." + }, + "x-client-response-contracts": [ + { + "kind": "object", + "raw": "PLDFOPCHHJG", + "rootType": "PLDFOPCHHJG", + "typeDescription": { + "name": "PLDFOPCHHJG", + "found": true, + "file": "PLDFOPCHHJG.cs", + "declaration": "public class PLDFOPCHHJG : IFAIJAGLDFK", + "kind": "class", + "baseType": "IFAIJAGLDFK", + "properties": [], + "fields": [ + { + "name": "ACHMMBMLGEK", + "type": "int", + "kind": "field" + }, + { + "name": "PBPPCMNHODC", + "type": "string", + "kind": "field" + }, + { + "name": "FBLEJONFPAK", + "type": "bool", + "kind": "field" + } + ], + "enumValues": [], + "wireKeys": [ + "creatorPlayerId", + "data", + "isValid" + ], + "isilFile": "PLDFOPCHHJG.txt", + "isilType": "PLDFOPCHHJG" + } + } + ] + } + }, + "/pageview/consume": { + "post": { + "tags": [ + "economy" + ], + "summary": "POST pageview/consume", + "description": "Inferred from Rec Room 2020 client literal `pageview/consume`. Expected result: Reward, consumable, currency, or catalog/economy result.", + "operationId": "client_economy_pageview_consume_post", + "parameters": [], + "responses": { + "200": { + "description": "Reward, consumable, currency, or catalog/economy result.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/EconomyResult" + } + } + } + }, + "204": { + "description": "Success with no body, used by some mutation endpoints." + }, + "400": { + "description": "Client-visible validation or bad request error.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "401": { + "description": "Authentication required or token invalid.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "500": { + "description": "Server error shape tolerated by client wrappers.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + } + }, + "x-recroom-client-route-literal": "pageview/consume", + "x-recroom-client-family": "economy", + "x-recroom-client-service": "api", + "x-recroom-builds": [ + "2020.12.18" + ], + "x-recroom-inferred-http-method": "post", + "x-recroom-evidence": [ + { + "type": "COFHGNFJMOG", + "method": "RecRoom.Async.IPromise`1 LFHFLODHPJJ()", + "file": "COFHGNFJMOG.txt", + "line": 95 + } + ], + "requestBody": { + "required": false, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/GenericObject" + } + }, + "application/x-www-form-urlencoded": { + "schema": { + "$ref": "#/components/schemas/GenericObject" + } + }, + "multipart/form-data": { + "schema": { + "$ref": "#/components/schemas/GenericObject" + } + } + }, + "description": "Request body inferred. Exact DTO may be named in x-recroom-evidence method signature or companion DTO inventory." + }, + "x-client-response-contracts": [ + { + "kind": "object", + "raw": "MPHABHIMOOO", + "rootType": "MPHABHIMOOO", + "typeDescription": { + "name": "MPHABHIMOOO", + "found": true, + "file": "MPHABHIMOOO.cs", + "declaration": "public class MPHABHIMOOO : IFAIJAGLDFK", + "kind": "class", + "baseType": "IFAIJAGLDFK", + "properties": [], + "fields": [ + { + "name": "JKKFFNGIEPL", + "type": "double", + "kind": "field" + }, + { + "name": "APBAPGCDANF", + "type": "string", + "kind": "field" + } + ], + "enumValues": [], + "wireKeys": [ + "url", + "freshnessSeconds" + ], + "isilFile": "MPHABHIMOOO.txt", + "isilType": "MPHABHIMOOO" + } + } + ] + } + }, + "/v1/consume": { + "post": { + "tags": [ + "economy" + ], + "summary": "POST {0}v1/consume", + "description": "Inferred from Rec Room 2020 client literal `{0}v1/consume`. Expected result: Reward, consumable, currency, or catalog/economy result.", + "operationId": "client_economy_v1_consume_post", + "parameters": [], + "responses": { + "200": { + "description": "Reward, consumable, currency, or catalog/economy result.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/EconomyResult" + } + } + } + }, + "204": { + "description": "Success with no body, used by some mutation endpoints." + }, + "400": { + "description": "Client-visible validation or bad request error.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "401": { + "description": "Authentication required or token invalid.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "500": { + "description": "Server error shape tolerated by client wrappers.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + } + }, + "x-recroom-client-route-literal": "{0}v1/consume", + "x-recroom-client-family": "economy", + "x-recroom-client-service": "api", + "x-recroom-builds": [ + "2020.03.10", + "2020.12.18" + ], + "x-recroom-inferred-http-method": "post", + "x-recroom-evidence": [ + { + "type": "FOEHJBDMMBH", + "method": "System.Collections.IEnumerator LCELAOEOIJO(OOCCCGPICOG BAJGNABECMN, System.Int32 FKKAPHAMPMG, BPHGKAEDBPE+CBEOHBCIPEA AFLPGGJMPOE)", + "file": "FOEHJBDMMBH.txt", + "line": 840 + }, + { + "type": "stringliteral.json", + "method": "", + "file": "Recnet-archive/decompiled/Il2CppDump/stringliteral.json", + "line": null + } + ], + "requestBody": { + "required": false, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/GenericObject" + } + }, + "application/x-www-form-urlencoded": { + "schema": { + "$ref": "#/components/schemas/GenericObject" + } + }, + "multipart/form-data": { + "schema": { + "$ref": "#/components/schemas/GenericObject" + } + } + }, + "description": "Request body inferred. Exact DTO may be named in x-recroom-evidence method signature or companion DTO inventory." + }, + "x-client-response-contracts": [ + { + "kind": "callback-or-coroutine", + "raw": "IEnumerator", + "rootType": "callback", + "typeDescription": { + "name": "callback", + "found": false, + "kind": "object", + "wireKeys": [] + } + } + ] + } + }, + "/api/PlayerElo/{tail}": { + "get": { + "tags": [ + "elo" + ], + "summary": "GET api/PlayerElo/", + "description": "Inferred from Rec Room 2020 client literal `api/PlayerElo/`. Expected result: Generic object/array/success result inferred from client route.", + "operationId": "client_elo_api_playerelo_tail_get", + "parameters": [ + { + "name": "tail", + "in": "path", + "required": true, + "schema": { + "type": "string" + }, + "description": "Format placeholder from client literal api/PlayerElo/" + } + ], + "responses": { + "200": { + "description": "Generic object/array/success result inferred from client route.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/EloResult" + } + } + } + }, + "204": { + "description": "Success with no body, used by some mutation endpoints." + }, + "400": { + "description": "Client-visible validation or bad request error.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "401": { + "description": "Authentication required or token invalid.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "500": { + "description": "Server error shape tolerated by client wrappers.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + } + }, + "x-recroom-client-route-literal": "api/PlayerElo/", + "x-recroom-client-family": "elo", + "x-recroom-client-service": "api", + "x-recroom-builds": [ + "2020.03.10", + "2020.12.18" + ], + "x-recroom-inferred-http-method": "get", + "x-recroom-evidence": [ + { + "type": "RecNet.Elo", + "method": "System.Void UpdatePlayersElo(RecNet.Elo+PlayersEloUpdateDTO JFKDDPJDCDC)", + "file": "RecNet\\Elo.txt", + "line": 141 + }, + { + "type": "stringliteral.json", + "method": "", + "file": "Recnet-archive/decompiled/Il2CppDump/stringliteral.json", + "line": null + } + ], + "x-client-response-contracts": [ + { + "kind": "unknown", + "raw": "", + "rootType": "unknown", + "typeDescription": { + "name": "unknown", + "found": false, + "kind": "object", + "wireKeys": [] + } + } + ] + } + }, + "/api/equipment": { + "get": { + "tags": [ + "equipment" + ], + "summary": "GET api/equipment/", + "description": "Inferred from Rec Room 2020 client literal `api/equipment/`. Expected result: Generic object/array/success result inferred from client route.", + "operationId": "client_equipment_api_equipment_get", + "parameters": [], + "responses": { + "200": { + "description": "Generic object/array/success result inferred from client route.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/GenericObject" + } + } + } + }, + "204": { + "description": "Success with no body, used by some mutation endpoints." + }, + "400": { + "description": "Client-visible validation or bad request error.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "401": { + "description": "Authentication required or token invalid.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "500": { + "description": "Server error shape tolerated by client wrappers.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + } + }, + "x-recroom-client-route-literal": "api/equipment/", + "x-recroom-client-family": "equipment", + "x-recroom-client-service": "api", + "x-recroom-builds": [ + "2020.03.10", + "2020.12.18" + ], + "x-recroom-inferred-http-method": "get", + "x-recroom-evidence": [ + { + "type": "ECINAMCDBJO", + "method": "System.Void EEDJOJINECJ()", + "file": "ECINAMCDBJO.txt", + "line": 468 + }, + { + "type": "ECINAMCDBJO+GPBDCOCADGE", + "method": "System.Boolean MoveNext()", + "file": "ECINAMCDBJO_NestedType_GPBDCOCADGE.txt", + "line": 147 + }, + { + "type": "stringliteral.json", + "method": "", + "file": "Recnet-archive/decompiled/Il2CppDump/stringliteral.json", + "line": null + } + ], + "x-client-response-contracts": [ + { + "kind": "unknown", + "raw": "", + "rootType": "unknown", + "typeDescription": { + "name": "unknown", + "found": false, + "kind": "object", + "wireKeys": [] + } + } + ] + } + }, + "/api/groups": { + "get": { + "tags": [ + "groups" + ], + "summary": "GET api/groups/", + "description": "Inferred from Rec Room 2020 client literal `api/groups/`. Expected result: Generic object/array/success result inferred from client route.", + "operationId": "client_groups_api_groups_get", + "parameters": [], + "responses": { + "200": { + "description": "Generic object/array/success result inferred from client route.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/GenericObject" + } + } + } + }, + "204": { + "description": "Success with no body, used by some mutation endpoints." + }, + "400": { + "description": "Client-visible validation or bad request error.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "401": { + "description": "Authentication required or token invalid.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "500": { + "description": "Server error shape tolerated by client wrappers.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + } + }, + "x-recroom-client-route-literal": "api/groups/", + "x-recroom-client-family": "groups", + "x-recroom-client-service": "api", + "x-recroom-builds": [ + "2020.03.10", + "2020.12.18" + ], + "x-recroom-inferred-http-method": "get", + "x-recroom-evidence": [ + { + "type": "EJECIMCPGMG", + "method": "RecRoom.Async.IPromise`1 CCDHCLAOFOJ(System.Int64 BCNAOOIPEJO)", + "file": "EJECIMCPGMG.txt", + "line": 337 + }, + { + "type": "EJECIMCPGMG", + "method": "RecRoom.Async.IPromise`1 PDJKECHIHNP(System.Int64 CJFGEMGOJHB)", + "file": "EJECIMCPGMG.txt", + "line": 555 + }, + { + "type": "EJECIMCPGMG", + "method": "System.Collections.IEnumerator JAHJIFFICHN(System.String NDNLEGKJGCD, System.String LJIGOCDPEJF, System.String HFLPBHHAFIO, BPHGKAEDBPE+OJDJIJDNFHE AFLPGGJMPOE)", + "file": "EJECIMCPGMG.txt", + "line": 803 + }, + { + "type": "EJECIMCPGMG", + "method": "System.Collections.IEnumerator JELCDBGNAHK(System.Int64 BCNAOOIPEJO, BPHGKAEDBPE+OJDJIJDNFHE AFLPGGJMPOE)", + "file": "EJECIMCPGMG.txt", + "line": 1213 + }, + { + "type": "EJECIMCPGMG", + "method": "System.Collections.IEnumerator KBHKIILLNAC(System.Int64 BCNAOOIPEJO, BPHGKAEDBPE+OJDJIJDNFHE AFLPGGJMPOE)", + "file": "EJECIMCPGMG.txt", + "line": 1040 + }, + { + "type": "EJECIMCPGMG", + "method": "System.Collections.IEnumerator NJFPGHDIANJ(System.String NDNLEGKJGCD, BPHGKAEDBPE+OJDJIJDNFHE AFLPGGJMPOE)", + "file": "EJECIMCPGMG.txt", + "line": 1336 + }, + { + "type": "stringliteral.json", + "method": "", + "file": "Recnet-archive/decompiled/Il2CppDump/stringliteral.json", + "line": null + } + ], + "x-client-response-contracts": [ + { + "kind": "object", + "raw": "OGKIDDEAFND", + "rootType": "OGKIDDEAFND", + "typeDescription": { + "name": "OGKIDDEAFND", + "found": true, + "file": "OGKIDDEAFND.cs", + "declaration": "public class OGKIDDEAFND : IFAIJAGLDFK", + "kind": "class", + "baseType": "IFAIJAGLDFK", + "properties": [ + { + "name": "ACBFDMLHFPB", + "type": "DateTime", + "accessibility": "public", + "kind": "property" + }, + { + "name": "ADDNGFPJPFL", + "type": "LEIHOJHGJGH", + "accessibility": "public", + "kind": "property" + }, + { + "name": "AHGCOGFEEEE", + "type": "string", + "accessibility": "public", + "kind": "property" + }, + { + "name": "CIPOELOKICH", + "type": "int", + "accessibility": "public", + "kind": "property" + }, + { + "name": "FDPKFOLAJEJ", + "type": "long", + "accessibility": "public", + "kind": "property" + }, + { + "name": "FHAOODLGNJA", + "type": "List", + "accessibility": "public", + "kind": "property" + }, + { + "name": "FIKEBGGCDFN", + "type": "string", + "accessibility": "public", + "kind": "property" + }, + { + "name": "JDMKBKGIKAO", + "type": "int", + "accessibility": "public", + "kind": "property" + }, + { + "name": "KODBEJPEFOJ", + "type": "string", + "accessibility": "public", + "kind": "property" + } + ], + "fields": [], + "enumValues": [], + "wireKeys": [ + "GroupId", + "Name", + "Description", + "CreatedAt", + "ImageName", + "BanStatus", + "CreatorId", + "NumMembers" + ], + "isilFile": "OGKIDDEAFND.txt", + "isilType": "OGKIDDEAFND" + } + }, + { + "kind": "callback-or-coroutine", + "raw": "IEnumerator", + "rootType": "callback", + "typeDescription": { + "name": "callback", + "found": false, + "kind": "object", + "wireKeys": [] + } + } + ] + } + }, + "/api/images": { + "post": { + "tags": [ + "images" + ], + "summary": "POST api/images/", + "description": "Inferred from Rec Room 2020 client literal `api/images/`. Expected result: Image metadata/upload result; image bytes fetched separately.", + "operationId": "client_images_api_images_post", + "parameters": [], + "responses": { + "200": { + "description": "Image metadata/upload result; image bytes fetched separately.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ImageResource" + } + } + } + }, + "204": { + "description": "Success with no body, used by some mutation endpoints." + }, + "400": { + "description": "Client-visible validation or bad request error.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "401": { + "description": "Authentication required or token invalid.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "500": { + "description": "Server error shape tolerated by client wrappers.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + } + }, + "x-recroom-client-route-literal": "api/images/", + "x-recroom-client-family": "images", + "x-recroom-client-service": "storage/images/cdn", + "x-recroom-builds": [ + "2020.03.10", + "2020.12.18" + ], + "x-recroom-inferred-http-method": "post", + "x-recroom-evidence": [ + { + "type": "OHDHPENHDAP", + "method": "RecRoom.Async.IPromise BFHNENPOEFB(System.Int64 LKNNMPCBCKM, System.Boolean JBAGLLBLEEN)", + "file": "OHDHPENHDAP.txt", + "line": 1571 + }, + { + "type": "OHDHPENHDAP", + "method": "RecRoom.Async.IPromise FEOMMGBJFIN(System.Int64 LKNNMPCBCKM)", + "file": "OHDHPENHDAP.txt", + "line": 2387 + }, + { + "type": "OHDHPENHDAP", + "method": "RecRoom.Async.IPromise OFEBNGPIJMB()", + "file": "OHDHPENHDAP.txt", + "line": 4467 + }, + { + "type": "OHDHPENHDAP", + "method": "RecRoom.Async.IPromise`1 KIIGBNCPAGC()", + "file": "OHDHPENHDAP.txt", + "line": 4847 + }, + { + "type": "OHDHPENHDAP", + "method": "RecRoom.Async.IPromise`1> NAMIJGLEPMH(System.Int64 HNHLJONGKHB, OHDHPENHDAP+ALLECIACGAH ECENAMEKNEH, OHDHPENHDAP+OGDBPFGJMOO MOIMCDPIPOL, System.Nullable`1 KKDPFGADCEK, System.Nullable`1 MADNOGDODFA)", + "file": "OHDHPENHDAP.txt", + "line": 5403 + }, + { + "type": "OHDHPENHDAP", + "method": "RecRoom.Async.IPromise`1 MPNCIPABNKC(System.Byte[] IJKBAADKCBM, OHDHPENHDAP+SavedImageMetaDTO FIBFIEEBFIL)", + "file": "OHDHPENHDAP.txt", + "line": 787 + }, + { + "type": "OHDHPENHDAP", + "method": "System.Collections.IEnumerator BEPFIALDMHK(BPHGKAEDBPE+OJDJIJDNFHE> AFLPGGJMPOE)", + "file": "OHDHPENHDAP.txt", + "line": 394 + }, + { + "type": "OHDHPENHDAP", + "method": "System.Collections.IEnumerator EFGBDOJHLCE(System.String HFLPBHHAFIO, OHDHPENHDAP+IMKNMFOOLGF PONCIIJOHIE, BPHGKAEDBPE+CBEOHBCIPEA AFLPGGJMPOE = null)", + "file": "OHDHPENHDAP.txt", + "line": 1203 + }, + { + "type": "OHDHPENHDAP", + "method": "System.Collections.IEnumerator GLPDDNENLHN(System.String HFLPBHHAFIO, BPHGKAEDBPE+CBEOHBCIPEA AFLPGGJMPOE = null)", + "file": "OHDHPENHDAP.txt", + "line": 1427 + }, + { + "type": "OHDHPENHDAP", + "method": "System.Collections.IEnumerator JPDBMCKFKLA(System.String HFLPBHHAFIO, BPHGKAEDBPE+CBEOHBCIPEA AFLPGGJMPOE = null)", + "file": "OHDHPENHDAP.txt", + "line": 1316 + } + ], + "requestBody": { + "required": false, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/GenericObject" + } + }, + "application/x-www-form-urlencoded": { + "schema": { + "$ref": "#/components/schemas/GenericObject" + } + }, + "multipart/form-data": { + "schema": { + "$ref": "#/components/schemas/GenericObject" + } + } + }, + "description": "Request body inferred. Exact DTO may be named in x-recroom-evidence method signature or companion DTO inventory." + }, + "x-client-response-contracts": [ + { + "kind": "success-or-empty", + "raw": "RecRoom.Async.IPromise", + "rootType": "void/success", + "typeDescription": { + "name": "void/success", + "found": false, + "kind": "object", + "wireKeys": [] + } + }, + { + "kind": "object", + "raw": "OHDHPENHDAP+OHGBAPBECNM", + "rootType": "OHGBAPBECNM", + "typeDescription": { + "name": "OHGBAPBECNM", + "found": true, + "file": "OHDHPENHDAP.cs", + "declaration": "internal class OHGBAPBECNM : IFAIJAGLDFK", + "kind": "class", + "baseType": "IFAIJAGLDFK", + "properties": [ + { + "name": "DLJBBCDLALH", + "type": "List", + "accessibility": "public", + "kind": "property" + }, + { + "name": "HJJGFONDEFO", + "type": "DateTime", + "accessibility": "public", + "kind": "property" + } + ], + "fields": [], + "enumValues": [], + "wireKeys": [ + "ValidTill" + ], + "isilFile": "OHDHPENHDAP_NestedType_OHGBAPBECNM.txt", + "isilType": "OHDHPENHDAP+OHGBAPBECNM" + } + }, + { + "kind": "array", + "raw": "System.Collections.Generic.List`1", + "rootType": "IBANLCLBGLM", + "item": { + "kind": "object", + "raw": "OHDHPENHDAP+IBANLCLBGLM", + "rootType": "IBANLCLBGLM" + }, + "typeDescription": { + "name": "IBANLCLBGLM", + "found": true, + "file": "OHDHPENHDAP.cs", + "declaration": "internal class IBANLCLBGLM : IFAIJAGLDFK", + "kind": "class", + "baseType": "IFAIJAGLDFK", + "properties": [ + { + "name": "ACBFDMLHFPB", + "type": "DateTime", + "accessibility": "public", + "kind": "property" + }, + { + "name": "AHGCOGFEEEE", + "type": "string", + "accessibility": "public", + "kind": "property" + }, + { + "name": "AHNBDMKBAPK", + "type": "int", + "accessibility": "public", + "kind": "property" + }, + { + "name": "DADOKMAOFJL", + "type": "Nullable", + "accessibility": "public", + "kind": "property" + }, + { + "name": "EEOGKBHOJGL", + "type": "int", + "accessibility": "public", + "kind": "property" + }, + { + "name": "EKGNDCHHBLE", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "GBOIPGBGDDG", + "type": "Nullable", + "accessibility": "public", + "kind": "property" + }, + { + "name": "JFEAPMIPNEP", + "type": "IMKNMFOOLGF", + "accessibility": "public", + "kind": "property" + }, + { + "name": "JHJNKJHLJBJ", + "type": "int", + "accessibility": "public", + "kind": "property" + }, + { + "name": "JPOHGBCEJEJ", + "type": "long", + "accessibility": "public", + "kind": "property" + }, + { + "name": "OJNBODIHIHF", + "type": "bool", + "accessibility": "public", + "kind": "property" + }, + { + "name": "OPLHMKFCNOL", + "type": "DLHGJJJPHPH", + "accessibility": "public", + "kind": "property" + }, + { + "name": "PKLADBPMHMC", + "type": "int", + "accessibility": "public", + "kind": "property" + } + ], + "fields": [], + "enumValues": [], + "wireKeys": [ + "Id", + "ImageName", + "PlayerId", + "RoomId", + "PlayerEventId", + "Accessibility", + "AccessibilityLocked", + "Type", + "CreatedAt", + "TaggedPlayerIds", + "CheerCount", + "CommentCount" + ], + "isilFile": "OHDHPENHDAP_NestedType_IBANLCLBGLM.txt", + "isilType": "OHDHPENHDAP+IBANLCLBGLM" + } + }, + { + "kind": "primitive", + "raw": "System.String", + "rootType": "string", + "typeDescription": { + "name": "string", + "found": false, + "kind": "primitive", + "wireKeys": [] + } + }, + { + "kind": "callback-or-coroutine", + "raw": "IEnumerator", + "rootType": "callback", + "typeDescription": { + "name": "callback", + "found": false, + "kind": "object", + "wireKeys": [] + } + } + ] + } + }, + "/api/inventions/v1/fulllineageowner": { + "post": { + "tags": [ + "inventions" + ], + "summary": "POST api/inventions/v1/fulllineageowner?", + "description": "Inferred from Rec Room 2020 client literal `api/inventions/v1/fulllineageowner?`. Expected result: Invention object, download descriptor, or mutation success.", + "operationId": "client_inventions_api_inventions_v1_fulllineageowner_post", + "parameters": [], + "responses": { + "200": { + "description": "Invention object, download descriptor, or mutation success.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Invention" + } + } + } + }, + "204": { + "description": "Success with no body, used by some mutation endpoints." + }, + "400": { + "description": "Client-visible validation or bad request error.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "401": { + "description": "Authentication required or token invalid.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "500": { + "description": "Server error shape tolerated by client wrappers.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + } + }, + "x-recroom-client-route-literal": "api/inventions/v1/fulllineageowner?", + "x-recroom-client-family": "inventions", + "x-recroom-client-service": "api", + "x-recroom-builds": [ + "2020.12.18" + ], + "x-recroom-inferred-http-method": "post", + "x-recroom-evidence": [ + { + "type": "BBHENFCNLAB", + "method": "RecRoom.Async.IPromise`1 DEOOEGHNIAJ(System.Collections.Generic.List`1 POGCOENDJDJ)", + "file": "BBHENFCNLAB.txt", + "line": 7823 + } + ], + "requestBody": { + "required": false, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/GenericObject" + } + }, + "application/x-www-form-urlencoded": { + "schema": { + "$ref": "#/components/schemas/GenericObject" + } + }, + "multipart/form-data": { + "schema": { + "$ref": "#/components/schemas/GenericObject" + } + } + }, + "description": "Request body inferred. Exact DTO may be named in x-recroom-evidence method signature or companion DTO inventory." + }, + "x-client-response-contracts": [ + { + "kind": "primitive", + "raw": "System.Boolean", + "rootType": "boolean", + "typeDescription": { + "name": "boolean", + "found": false, + "kind": "primitive", + "wireKeys": [] + } + } + ] + } + }, + "/api/inventions/v3/addversion": { + "post": { + "tags": [ + "inventions" + ], + "summary": "POST api/inventions/v3/addversion", + "description": "Inferred from Rec Room 2020 client literal `api/inventions/v3/addversion`. Expected result: Invention detail/version object.", + "operationId": "client_inventions_api_inventions_v3_addversion_post", + "parameters": [], + "responses": { + "200": { + "description": "Invention detail/version object.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Invention" + } + } + } + }, + "204": { + "description": "Success with no body, used by some mutation endpoints." + }, + "400": { + "description": "Client-visible validation or bad request error.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "401": { + "description": "Authentication required or token invalid.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "500": { + "description": "Server error shape tolerated by client wrappers.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + } + }, + "x-recroom-client-route-literal": "api/inventions/v3/addversion", + "x-recroom-client-family": "inventions", + "x-recroom-client-service": "api", + "x-recroom-builds": [ + "2020.03.10", + "2020.12.18" + ], + "x-recroom-inferred-http-method": "post", + "x-recroom-evidence": [ + { + "type": "BBHENFCNLAB+OIJIEFMJMKO", + "method": "RecRoom.Async.IPromise`1 b__0(System.String filename)", + "file": "BBHENFCNLAB_NestedType_OIJIEFMJMKO.txt", + "line": 216 + }, + { + "type": "stringliteral.json", + "method": "", + "file": "Recnet-archive/decompiled/Il2CppDump/stringliteral.json", + "line": null + } + ], + "requestBody": { + "required": false, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/GenericObject" + } + }, + "application/x-www-form-urlencoded": { + "schema": { + "$ref": "#/components/schemas/GenericObject" + } + }, + "multipart/form-data": { + "schema": { + "$ref": "#/components/schemas/GenericObject" + } + } + }, + "description": "Request body inferred. Exact DTO may be named in x-recroom-evidence method signature or companion DTO inventory." + }, + "x-client-response-contracts": [ + { + "kind": "object", + "raw": "AHEPPAEOLOD", + "rootType": "AHEPPAEOLOD", + "typeDescription": { + "name": "AHEPPAEOLOD", + "found": true, + "file": "AHEPPAEOLOD.cs", + "declaration": "public class AHEPPAEOLOD : IFAIJAGLDFK", + "kind": "class", + "baseType": "IFAIJAGLDFK", + "properties": [ + { + "name": "BFLAPPKDDIN", + "type": "NEFHINKECJJ", + "accessibility": "public", + "kind": "property" + }, + { + "name": "HIMCGOCKLLK", + "type": "JJONIELLJGH", + "accessibility": "public", + "kind": "property" + }, + { + "name": "MEPFFJLDHNP", + "type": "OBBBPCBIMME", + "accessibility": "public", + "kind": "property" + } + ], + "fields": [], + "enumValues": [], + "wireKeys": [ + "Status", + "Invention", + "InventionVersion" + ], + "isilFile": "AHEPPAEOLOD.txt", + "isilType": "AHEPPAEOLOD" + } + } + ] + } + }, + "/api/inventions/v3/save": { + "post": { + "tags": [ + "inventions" + ], + "summary": "POST api/inventions/v3/save", + "description": "Inferred from Rec Room 2020 client literal `api/inventions/v3/save`. Expected result: Invention object, download descriptor, or mutation success.", + "operationId": "client_inventions_api_inventions_v3_save_post", + "parameters": [], + "responses": { + "200": { + "description": "Invention object, download descriptor, or mutation success.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Invention" + } + } + } + }, + "204": { + "description": "Success with no body, used by some mutation endpoints." + }, + "400": { + "description": "Client-visible validation or bad request error.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "401": { + "description": "Authentication required or token invalid.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "500": { + "description": "Server error shape tolerated by client wrappers.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + } + }, + "x-recroom-client-route-literal": "api/inventions/v3/save", + "x-recroom-client-family": "inventions", + "x-recroom-client-service": "api", + "x-recroom-builds": [ + "2020.03.10" + ], + "x-recroom-inferred-http-method": "post", + "x-recroom-evidence": [ + { + "type": "stringliteral.json", + "method": "", + "file": "Recnet-archive/decompiled/Il2CppDump/stringliteral.json", + "line": null + } + ], + "requestBody": { + "required": false, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/GenericObject" + } + }, + "application/x-www-form-urlencoded": { + "schema": { + "$ref": "#/components/schemas/GenericObject" + } + }, + "multipart/form-data": { + "schema": { + "$ref": "#/components/schemas/GenericObject" + } + } + }, + "description": "Request body inferred. Exact DTO may be named in x-recroom-evidence method signature or companion DTO inventory." + } + } + }, + "/api/inventions/v4/save": { + "post": { + "tags": [ + "inventions" + ], + "summary": "POST api/inventions/v4/save", + "description": "Inferred from Rec Room 2020 client literal `api/inventions/v4/save`. Expected result: Invention object, download descriptor, or mutation success.", + "operationId": "client_inventions_api_inventions_v4_save_post", + "parameters": [], + "responses": { + "200": { + "description": "Invention object, download descriptor, or mutation success.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Invention" + } + } + } + }, + "204": { + "description": "Success with no body, used by some mutation endpoints." + }, + "400": { + "description": "Client-visible validation or bad request error.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "401": { + "description": "Authentication required or token invalid.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "500": { + "description": "Server error shape tolerated by client wrappers.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + } + }, + "x-recroom-client-route-literal": "api/inventions/v4/save", + "x-recroom-client-family": "inventions", + "x-recroom-client-service": "api", + "x-recroom-builds": [ + "2020.12.18" + ], + "x-recroom-inferred-http-method": "post", + "x-recroom-evidence": [ + { + "type": "BBHENFCNLAB+FODDLIKPKPE", + "method": "RecRoom.Async.IPromise`1 b__0(System.String filename)", + "file": "BBHENFCNLAB_NestedType_FODDLIKPKPE.txt", + "line": 250 + } + ], + "requestBody": { + "required": false, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/GenericObject" + } + }, + "application/x-www-form-urlencoded": { + "schema": { + "$ref": "#/components/schemas/GenericObject" + } + }, + "multipart/form-data": { + "schema": { + "$ref": "#/components/schemas/GenericObject" + } + } + }, + "description": "Request body inferred. Exact DTO may be named in x-recroom-evidence method signature or companion DTO inventory." + }, + "x-client-response-contracts": [ + { + "kind": "object", + "raw": "AHEPPAEOLOD", + "rootType": "AHEPPAEOLOD", + "typeDescription": { + "name": "AHEPPAEOLOD", + "found": true, + "file": "AHEPPAEOLOD.cs", + "declaration": "public class AHEPPAEOLOD : IFAIJAGLDFK", + "kind": "class", + "baseType": "IFAIJAGLDFK", + "properties": [ + { + "name": "BFLAPPKDDIN", + "type": "NEFHINKECJJ", + "accessibility": "public", + "kind": "property" + }, + { + "name": "HIMCGOCKLLK", + "type": "JJONIELLJGH", + "accessibility": "public", + "kind": "property" + }, + { + "name": "MEPFFJLDHNP", + "type": "OBBBPCBIMME", + "accessibility": "public", + "kind": "property" + } + ], + "fields": [], + "enumValues": [], + "wireKeys": [ + "Status", + "Invention", + "InventionVersion" + ], + "isilFile": "AHEPPAEOLOD.txt", + "isilType": "AHEPPAEOLOD" + } + } + ] + } + }, + "/api/inventions": { + "get": { + "tags": [ + "inventions" + ], + "summary": "GET api/inventions/", + "description": "Inferred from Rec Room 2020 client literal `api/inventions/`. Expected result: Invention object, download descriptor, or mutation success.", + "operationId": "client_inventions_api_inventions_get", + "parameters": [], + "responses": { + "200": { + "description": "Invention object, download descriptor, or mutation success.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Invention" + } + } + } + }, + "204": { + "description": "Success with no body, used by some mutation endpoints." + }, + "400": { + "description": "Client-visible validation or bad request error.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "401": { + "description": "Authentication required or token invalid.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "500": { + "description": "Server error shape tolerated by client wrappers.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + } + }, + "x-recroom-client-route-literal": "api/inventions/", + "x-recroom-client-family": "inventions", + "x-recroom-client-service": "api", + "x-recroom-builds": [ + "2020.03.10", + "2020.12.18" + ], + "x-recroom-inferred-http-method": "get", + "x-recroom-evidence": [ + { + "type": "BBHENFCNLAB", + "method": "RecRoom.Async.IPromise`1 ANJHOOPIAKM(System.Int64 OEMDIAHHILF, System.Boolean JBAGLLBLEEN)", + "file": "BBHENFCNLAB.txt", + "line": 7610 + }, + { + "type": "BBHENFCNLAB", + "method": "RecRoom.Async.IPromise`1 BEPNJCGNIKA(System.Int64 OEMDIAHHILF, HECIICKPCDN AEHDODIANMG, System.Nullable`1 MACNIENMFHJ = null)", + "file": "BBHENFCNLAB.txt", + "line": 6169 + }, + { + "type": "BBHENFCNLAB", + "method": "RecRoom.Async.IPromise`1 HDFICFBNFOK(System.Int64 OEMDIAHHILF, System.Int32 MACNIENMFHJ)", + "file": "BBHENFCNLAB.txt", + "line": 6479 + }, + { + "type": "BBHENFCNLAB", + "method": "RecRoom.Async.IPromise`1 HELBOGAPCKE(OBBBPCBIMME LDNFNNNHPPB)", + "file": "BBHENFCNLAB.txt", + "line": 5990 + }, + { + "type": "BBHENFCNLAB", + "method": "RecRoom.Async.IPromise`1 MEIJBGBINLO(System.Int64 OEMDIAHHILF)", + "file": "BBHENFCNLAB.txt", + "line": 6327 + }, + { + "type": "BBHENFCNLAB", + "method": "RecRoom.Async.IPromise`1 NNDFHPLBNEN(System.Int64 OEMDIAHHILF, System.String LJIGOCDPEJF)", + "file": "BBHENFCNLAB.txt", + "line": 1731 + }, + { + "type": "BBHENFCNLAB", + "method": "RecRoom.Async.IPromise`1 OCBDPGLHIJL(System.Int64 OEMDIAHHILF, HECIICKPCDN ODABIMLCOIP)", + "file": "BBHENFCNLAB.txt", + "line": 2123 + }, + { + "type": "BBHENFCNLAB", + "method": "RecRoom.Async.IPromise`1 ONLOPCCOFMM(System.Int64 OEMDIAHHILF, System.String DLFBBIAHDMO)", + "file": "BBHENFCNLAB.txt", + "line": 1927 + }, + { + "type": "BBHENFCNLAB", + "method": "RecRoom.Async.IPromise`1 PONHAAALIGM(System.Int64 OEMDIAHHILF, System.String MMBOKOLAJFH)", + "file": "BBHENFCNLAB.txt", + "line": 1533 + }, + { + "type": "BBHENFCNLAB", + "method": "RecRoom.Async.IPromise`1 IIPHPOBADGF(System.Int64 OEMDIAHHILF)", + "file": "BBHENFCNLAB.txt", + "line": 3629 + } + ], + "x-client-response-contracts": [ + { + "kind": "object", + "raw": "AHEPPAEOLOD", + "rootType": "AHEPPAEOLOD", + "typeDescription": { + "name": "AHEPPAEOLOD", + "found": true, + "file": "AHEPPAEOLOD.cs", + "declaration": "public class AHEPPAEOLOD : IFAIJAGLDFK", + "kind": "class", + "baseType": "IFAIJAGLDFK", + "properties": [ + { + "name": "BFLAPPKDDIN", + "type": "NEFHINKECJJ", + "accessibility": "public", + "kind": "property" + }, + { + "name": "HIMCGOCKLLK", + "type": "JJONIELLJGH", + "accessibility": "public", + "kind": "property" + }, + { + "name": "MEPFFJLDHNP", + "type": "OBBBPCBIMME", + "accessibility": "public", + "kind": "property" + } + ], + "fields": [], + "enumValues": [], + "wireKeys": [ + "Status", + "Invention", + "InventionVersion" + ], + "isilFile": "AHEPPAEOLOD.txt", + "isilType": "AHEPPAEOLOD" + } + }, + { + "kind": "object", + "raw": "HJPDBNLCGIB", + "rootType": "HJPDBNLCGIB", + "typeDescription": { + "name": "HJPDBNLCGIB", + "found": true, + "file": "HJPDBNLCGIB.cs", + "declaration": "public class HJPDBNLCGIB : IFAIJAGLDFK", + "kind": "class", + "baseType": "IFAIJAGLDFK", + "properties": [ + { + "name": "PKKADKGDHNI", + "type": "List", + "accessibility": "public", + "kind": "property" + } + ], + "fields": [], + "enumValues": [], + "wireKeys": [], + "isilFile": "HJPDBNLCGIB.txt", + "isilType": "HJPDBNLCGIB" + } + }, + { + "kind": "object", + "raw": "KLAMKCBENEA", + "rootType": "KLAMKCBENEA", + "typeDescription": { + "name": "KLAMKCBENEA", + "found": true, + "file": "KLAMKCBENEA.cs", + "declaration": "public class KLAMKCBENEA : IFAIJAGLDFK", + "kind": "class", + "baseType": "IFAIJAGLDFK", + "properties": [ + { + "name": "DFPPNHINEFO", + "type": "string", + "accessibility": "public", + "kind": "property" + }, + { + "name": "ONGEANEKLNE", + "type": "bool", + "accessibility": "public", + "kind": "property" + } + ], + "fields": [], + "enumValues": [], + "wireKeys": [ + "Success", + "Message" + ], + "isilFile": "KLAMKCBENEA.txt", + "isilType": "KLAMKCBENEA" + } + }, + { + "kind": "object", + "raw": "NJMAEIPIOAP", + "rootType": "NJMAEIPIOAP", + "typeDescription": { + "name": "NJMAEIPIOAP", + "found": true, + "file": "NJMAEIPIOAP.cs", + "declaration": "public class NJMAEIPIOAP : IFAIJAGLDFK", + "kind": "class", + "baseType": "IFAIJAGLDFK", + "properties": [ + { + "name": "GMFIDNACDJK", + "type": "OCLOGFHCPHN", + "accessibility": "public", + "kind": "property" + }, + { + "name": "PKKADKGDHNI", + "type": "List", + "accessibility": "public", + "kind": "property" + } + ], + "fields": [], + "enumValues": [], + "wireKeys": [ + "Result" + ], + "isilFile": "NJMAEIPIOAP.txt", + "isilType": "NJMAEIPIOAP" + } + }, + { + "kind": "object", + "raw": "OBBBPCBIMME", + "rootType": "OBBBPCBIMME", + "typeDescription": { + "name": "OBBBPCBIMME", + "found": true, + "file": "OBBBPCBIMME.cs", + "declaration": "public class OBBBPCBIMME : IFAIJAGLDFK", + "kind": "class", + "baseType": "IFAIJAGLDFK", + "properties": [ + { + "name": "ACBFDMLHFPB", + "type": "DateTime", + "accessibility": "public", + "kind": "property" + }, + { + "name": "ACHMMBMLGEK", + "type": "int", + "accessibility": "public", + "kind": "property" + }, + { + "name": "AHGCOGFEEEE", + "type": "string", + "accessibility": "public", + "kind": "property" + }, + { + "name": "AJHBGBHGGAL", + "type": "long", + "accessibility": "public", + "kind": "property" + }, + { + "name": "BAELCDIJIPJ", + "type": "HECIICKPCDN", + "accessibility": "public", + "kind": "property" + }, + { + "name": "DKHJPGMEOHF", + "type": "int", + "accessibility": "public", + "kind": "property" + }, + { + "name": "FIKEBGGCDFN", + "type": "string", + "accessibility": "public", + "kind": "property" + }, + { + "name": "GCAABGAEDPP", + "type": "bool", + "accessibility": "public", + "kind": "property" + }, + { + "name": "HKFOKCNGNFA", + "type": "int", + "accessibility": "public", + "kind": "property" + }, + { + "name": "ILKLEFCCJMN", + "type": "bool", + "accessibility": "public", + "kind": "property" + }, + { + "name": "JHJNKJHLJBJ", + "type": "int", + "accessibility": "public", + "kind": "property" + }, + { + "name": "JOHEKKGMAJB", + "type": "int", + "accessibility": "public", + "kind": "property" + }, + { + "name": "KBDMNJGJACC", + "type": "DateTime", + "accessibility": "public", + "kind": "property" + }, + { + "name": "KLLMGMJJICK", + "type": "Nullable", + "accessibility": "public", + "kind": "property" + }, + { + "name": "KODBEJPEFOJ", + "type": "string", + "accessibility": "public", + "kind": "property" + }, + { + "name": "MLDAFKCENMA", + "type": "string", + "accessibility": "public", + "kind": "property" + }, + { + "name": "MLMDFDKLELH", + "type": "HECIICKPCDN", + "accessibility": "public", + "kind": "property" + }, + { + "name": "MMPKOCENIIP", + "type": "bool", + "accessibility": "public", + "kind": "property" + }, + { + "name": "NFNIPMIEMPM", + "type": "bool", + "accessibility": "public", + "kind": "property" + }, + { + "name": "NPDJLOOHMDJ", + "type": "Nullable", + "accessibility": "public", + "kind": "property" + }, + { + "name": "PJNJIKKCBNA", + "type": "Nullable", + "accessibility": "public", + "kind": "property" + } + ], + "fields": [], + "enumValues": [], + "wireKeys": [ + "InventionId", + "ReplicationId", + "CreatorPlayerId", + "Name", + "Description", + "ImageName", + "CurrentVersionNumber", + "IsPublished", + "AllowTrial", + "ModifiedAt", + "CreatedAt", + "NumPlayersHaveUsedInRoom", + "NumDownloads", + "CheerCount", + "CreatorPermission", + "GeneralPermission", + "IsAGInvention", + "Price", + "HideFromPlayer" + ], + "isilFile": "OBBBPCBIMME.txt", + "isilType": "OBBBPCBIMME" + } + }, + { + "kind": "object", + "raw": "OEGPIPBKHCN", + "rootType": "OEGPIPBKHCN", + "typeDescription": { + "name": "OEGPIPBKHCN", + "found": true, + "file": "OEGPIPBKHCN.cs", + "declaration": "public class OEGPIPBKHCN : IFAIJAGLDFK", + "kind": "class", + "baseType": "IFAIJAGLDFK", + "properties": [ + { + "name": "GOAELNACNNJ", + "type": "bool", + "accessibility": "public", + "kind": "property" + } + ], + "fields": [], + "enumValues": [], + "wireKeys": [ + "IsCheering" + ], + "isilFile": "OEGPIPBKHCN.txt", + "isilType": "OEGPIPBKHCN" + } + }, + { + "kind": "array", + "raw": "System.Collections.Generic.List`1", + "rootType": "NEFHINKECJJ", + "item": { + "kind": "object", + "raw": "NEFHINKECJJ", + "rootType": "NEFHINKECJJ" + }, + "typeDescription": { + "name": "NEFHINKECJJ", + "found": true, + "file": "NEFHINKECJJ.cs", + "declaration": "public class NEFHINKECJJ : IFAIJAGLDFK", + "kind": "class", + "baseType": "IFAIJAGLDFK", + "properties": [ + { + "name": "AJHBGBHGGAL", + "type": "long", + "accessibility": "public", + "kind": "property" + }, + { + "name": "CLODONFLECP", + "type": "int", + "accessibility": "public", + "kind": "property" + }, + { + "name": "JKDGKHANBMB", + "type": "int", + "accessibility": "public", + "kind": "property" + }, + { + "name": "KHMMKHPAMLL", + "type": "int", + "accessibility": "public", + "kind": "property" + }, + { + "name": "MLDAFKCENMA", + "type": "string", + "accessibility": "public", + "kind": "property" + }, + { + "name": "NIFBPEMIADP", + "type": "string", + "accessibility": "public", + "kind": "property" + } + ], + "fields": [], + "enumValues": [], + "wireKeys": [ + "InventionId", + "ReplicationId", + "VersionNumber", + "InstantiationCost", + "LightsCost", + "BlobName" + ], + "isilFile": "NEFHINKECJJ.txt", + "isilType": "NEFHINKECJJ" + } + }, + { + "kind": "array", + "raw": "System.Collections.Generic.List`1", + "rootType": "OBBBPCBIMME", + "item": { + "kind": "object", + "raw": "OBBBPCBIMME", + "rootType": "OBBBPCBIMME" + }, + "typeDescription": { + "name": "OBBBPCBIMME", + "found": true, + "file": "OBBBPCBIMME.cs", + "declaration": "public class OBBBPCBIMME : IFAIJAGLDFK", + "kind": "class", + "baseType": "IFAIJAGLDFK", + "properties": [ + { + "name": "ACBFDMLHFPB", + "type": "DateTime", + "accessibility": "public", + "kind": "property" + }, + { + "name": "ACHMMBMLGEK", + "type": "int", + "accessibility": "public", + "kind": "property" + }, + { + "name": "AHGCOGFEEEE", + "type": "string", + "accessibility": "public", + "kind": "property" + }, + { + "name": "AJHBGBHGGAL", + "type": "long", + "accessibility": "public", + "kind": "property" + }, + { + "name": "BAELCDIJIPJ", + "type": "HECIICKPCDN", + "accessibility": "public", + "kind": "property" + }, + { + "name": "DKHJPGMEOHF", + "type": "int", + "accessibility": "public", + "kind": "property" + }, + { + "name": "FIKEBGGCDFN", + "type": "string", + "accessibility": "public", + "kind": "property" + }, + { + "name": "GCAABGAEDPP", + "type": "bool", + "accessibility": "public", + "kind": "property" + }, + { + "name": "HKFOKCNGNFA", + "type": "int", + "accessibility": "public", + "kind": "property" + }, + { + "name": "ILKLEFCCJMN", + "type": "bool", + "accessibility": "public", + "kind": "property" + }, + { + "name": "JHJNKJHLJBJ", + "type": "int", + "accessibility": "public", + "kind": "property" + }, + { + "name": "JOHEKKGMAJB", + "type": "int", + "accessibility": "public", + "kind": "property" + }, + { + "name": "KBDMNJGJACC", + "type": "DateTime", + "accessibility": "public", + "kind": "property" + }, + { + "name": "KLLMGMJJICK", + "type": "Nullable", + "accessibility": "public", + "kind": "property" + }, + { + "name": "KODBEJPEFOJ", + "type": "string", + "accessibility": "public", + "kind": "property" + }, + { + "name": "MLDAFKCENMA", + "type": "string", + "accessibility": "public", + "kind": "property" + }, + { + "name": "MLMDFDKLELH", + "type": "HECIICKPCDN", + "accessibility": "public", + "kind": "property" + }, + { + "name": "MMPKOCENIIP", + "type": "bool", + "accessibility": "public", + "kind": "property" + }, + { + "name": "NFNIPMIEMPM", + "type": "bool", + "accessibility": "public", + "kind": "property" + }, + { + "name": "NPDJLOOHMDJ", + "type": "Nullable", + "accessibility": "public", + "kind": "property" + }, + { + "name": "PJNJIKKCBNA", + "type": "Nullable", + "accessibility": "public", + "kind": "property" + } + ], + "fields": [], + "enumValues": [], + "wireKeys": [ + "InventionId", + "ReplicationId", + "CreatorPlayerId", + "Name", + "Description", + "ImageName", + "CurrentVersionNumber", + "IsPublished", + "AllowTrial", + "ModifiedAt", + "CreatedAt", + "NumPlayersHaveUsedInRoom", + "NumDownloads", + "CheerCount", + "CreatorPermission", + "GeneralPermission", + "IsAGInvention", + "Price", + "HideFromPlayer" + ], + "isilFile": "OBBBPCBIMME.txt", + "isilType": "OBBBPCBIMME" + } + }, + { + "kind": "array", + "raw": "System.Collections.Generic.List`1", + "rootType": "String", + "item": { + "kind": "primitive", + "raw": "System.String", + "rootType": "string" + }, + "typeDescription": { + "name": "String", + "found": false, + "kind": "primitive", + "wireKeys": [] + } + }, + { + "kind": "unknown", + "raw": "", + "rootType": "unknown", + "typeDescription": { + "name": "unknown", + "found": false, + "kind": "object", + "wireKeys": [] + } + } + ] + } + }, + "/goto/club/{p0}": { + "get": { + "tags": [ + "matchmaking" + ], + "summary": "GET goto/club/{0}", + "description": "Inferred from Rec Room 2020 client literal `goto/club/{0}`. Expected result: Room instance/join response with Photon and room instance fields.", + "operationId": "client_matchmaking_goto_club_p0_get", + "parameters": [ + { + "name": "p0", + "in": "path", + "required": true, + "schema": { + "type": "string" + }, + "description": "Format placeholder from client literal goto/club/{0}" + } + ], + "responses": { + "200": { + "description": "Room instance/join response with Photon and room instance fields.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RoomInstance" + } + } + } + }, + "204": { + "description": "Success with no body, used by some mutation endpoints." + }, + "400": { + "description": "Client-visible validation or bad request error.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "401": { + "description": "Authentication required or token invalid.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "500": { + "description": "Server error shape tolerated by client wrappers.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + } + }, + "x-recroom-client-route-literal": "goto/club/{0}", + "x-recroom-client-family": "matchmaking", + "x-recroom-client-service": "match", + "x-recroom-builds": [ + "2020.12.18" + ], + "x-recroom-inferred-http-method": "get", + "x-recroom-evidence": [ + { + "type": "RecNet.Matchmaking", + "method": "RecRoom.Async.IPromise`1 IIDEDGKAGOE(PLILLKHMNDA EGLGJIONCCP, System.Int32[] DKFBAOLAEFE = null, System.Boolean KJHCANCBKAL = False)", + "file": "RecNet\\Matchmaking.txt", + "line": 8847 + } + ], + "x-client-response-contracts": [ + { + "kind": "object", + "raw": "RecNet.Matchmaking+MHCKNNJOIIP", + "rootType": "MHCKNNJOIIP", + "typeDescription": { + "name": "MHCKNNJOIIP", + "found": true, + "file": "RecNet/Matchmaking.cs", + "declaration": "internal enum MHCKNNJOIIP", + "kind": "enum", + "baseType": null, + "properties": [], + "fields": [], + "enumValues": [ + { + "name": "UnknownError", + "value": "-1" + }, + { + "name": "Success", + "value": "0" + }, + { + "name": "NoSuchGame", + "value": "1" + }, + { + "name": "PlayerNotOnline", + "value": "2" + }, + { + "name": "InsufficientSpace", + "value": "3" + }, + { + "name": "EventNotStarted", + "value": "4" + }, + { + "name": "EventAlreadyFinished", + "value": "5" + }, + { + "name": "BlockedFromRoom", + "value": "7" + }, + { + "name": "JuniorNotAllowed", + "value": "11" + }, + { + "name": "Banned", + "value": "12" + }, + { + "name": "AlreadyInBestInstance", + "value": "13" + }, + { + "name": "InsufficientRelationship", + "value": "14" + }, + { + "name": "UpdateRequired", + "value": "16" + }, + { + "name": "AlreadyInTargetInstance", + "value": "17" + }, + { + "name": "UGCNotAllowed", + "value": "19" + }, + { + "name": "NoSuchRoom", + "value": "20" + }, + { + "name": "RoomIsNotActive", + "value": "22" + }, + { + "name": "RoomBlockedByCreator", + "value": "23" + }, + { + "name": "RoomIsPrivate", + "value": "25" + }, + { + "name": "RoomInstanceIsPrivate", + "value": "26" + }, + { + "name": "DeviceClassNotSupported", + "value": "30" + }, + { + "name": "DeviceClassNotSupportedByRoomOwner", + "value": "31" + }, + { + "name": "MovementModeNotSupportedByRoomOwner", + "value": "32" + }, + { + "name": "EventIsPrivate", + "value": "35" + }, + { + "name": "RoomInviteExpired", + "value": "40" + }, + { + "name": "NoAvailableRegion", + "value": "45" + }, + { + "name": "NotorietyTooPoor", + "value": "50" + }, + { + "name": "BannedFromRoom", + "value": "55" + }, + { + "name": "NoSuchRoomPlaylist", + "value": "60" + }, + { + "name": "RoomPlaylistIsNotActive", + "value": "61" + }, + { + "name": "RoomPlaylistIsPrivate", + "value": "62" + }, + { + "name": "NoSuchClub", + "value": "70" + }, + { + "name": "ClubHasNoClubhouse", + "value": "71" + }, + { + "name": "ClubIsNotActive", + "value": "73" + }, + { + "name": "NotAMemberOfClub", + "value": "74" + }, + { + "name": "BannedFromClub", + "value": "75" + }, + { + "name": "InstanceJoinNotPermitted", + "value": "76" + } + ], + "wireKeys": [] + } + } + ] + } + }, + "/goto/code/{tail}": { + "get": { + "tags": [ + "matchmaking" + ], + "summary": "GET goto/code/", + "description": "Inferred from Rec Room 2020 client literal `goto/code/`. Expected result: Room instance/join response with Photon and room instance fields.", + "operationId": "client_matchmaking_goto_code_tail_get", + "parameters": [ + { + "name": "tail", + "in": "path", + "required": true, + "schema": { + "type": "string" + }, + "description": "Format placeholder from client literal goto/code/" + } + ], + "responses": { + "200": { + "description": "Room instance/join response with Photon and room instance fields.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RoomInstance" + } + } + } + }, + "204": { + "description": "Success with no body, used by some mutation endpoints." + }, + "400": { + "description": "Client-visible validation or bad request error.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "401": { + "description": "Authentication required or token invalid.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "500": { + "description": "Server error shape tolerated by client wrappers.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + } + }, + "x-recroom-client-route-literal": "goto/code/", + "x-recroom-client-family": "matchmaking", + "x-recroom-client-service": "match", + "x-recroom-builds": [ + "2020.12.18" + ], + "x-recroom-inferred-http-method": "get", + "x-recroom-evidence": [ + { + "type": "RecNet.Matchmaking", + "method": "RecRoom.Async.IPromise`1 FBLFCNDIPMP(System.String BFGCDJFNJLE, System.String KIIFGNILJEA, System.Int32[] DKFBAOLAEFE = null, System.Boolean KJHCANCBKAL = True, System.Boolean PBCPIEHFDBH = False)", + "file": "RecNet\\Matchmaking.txt", + "line": 8226 + } + ], + "x-client-response-contracts": [ + { + "kind": "object", + "raw": "RecNet.Matchmaking+MHCKNNJOIIP", + "rootType": "MHCKNNJOIIP", + "typeDescription": { + "name": "MHCKNNJOIIP", + "found": true, + "file": "RecNet/Matchmaking.cs", + "declaration": "internal enum MHCKNNJOIIP", + "kind": "enum", + "baseType": null, + "properties": [], + "fields": [], + "enumValues": [ + { + "name": "UnknownError", + "value": "-1" + }, + { + "name": "Success", + "value": "0" + }, + { + "name": "NoSuchGame", + "value": "1" + }, + { + "name": "PlayerNotOnline", + "value": "2" + }, + { + "name": "InsufficientSpace", + "value": "3" + }, + { + "name": "EventNotStarted", + "value": "4" + }, + { + "name": "EventAlreadyFinished", + "value": "5" + }, + { + "name": "BlockedFromRoom", + "value": "7" + }, + { + "name": "JuniorNotAllowed", + "value": "11" + }, + { + "name": "Banned", + "value": "12" + }, + { + "name": "AlreadyInBestInstance", + "value": "13" + }, + { + "name": "InsufficientRelationship", + "value": "14" + }, + { + "name": "UpdateRequired", + "value": "16" + }, + { + "name": "AlreadyInTargetInstance", + "value": "17" + }, + { + "name": "UGCNotAllowed", + "value": "19" + }, + { + "name": "NoSuchRoom", + "value": "20" + }, + { + "name": "RoomIsNotActive", + "value": "22" + }, + { + "name": "RoomBlockedByCreator", + "value": "23" + }, + { + "name": "RoomIsPrivate", + "value": "25" + }, + { + "name": "RoomInstanceIsPrivate", + "value": "26" + }, + { + "name": "DeviceClassNotSupported", + "value": "30" + }, + { + "name": "DeviceClassNotSupportedByRoomOwner", + "value": "31" + }, + { + "name": "MovementModeNotSupportedByRoomOwner", + "value": "32" + }, + { + "name": "EventIsPrivate", + "value": "35" + }, + { + "name": "RoomInviteExpired", + "value": "40" + }, + { + "name": "NoAvailableRegion", + "value": "45" + }, + { + "name": "NotorietyTooPoor", + "value": "50" + }, + { + "name": "BannedFromRoom", + "value": "55" + }, + { + "name": "NoSuchRoomPlaylist", + "value": "60" + }, + { + "name": "RoomPlaylistIsNotActive", + "value": "61" + }, + { + "name": "RoomPlaylistIsPrivate", + "value": "62" + }, + { + "name": "NoSuchClub", + "value": "70" + }, + { + "name": "ClubHasNoClubhouse", + "value": "71" + }, + { + "name": "ClubIsNotActive", + "value": "73" + }, + { + "name": "NotAMemberOfClub", + "value": "74" + }, + { + "name": "BannedFromClub", + "value": "75" + }, + { + "name": "InstanceJoinNotPermitted", + "value": "76" + } + ], + "wireKeys": [] + } + } + ] + } + }, + "/goto/event/{p0}": { + "get": { + "tags": [ + "matchmaking" + ], + "summary": "GET goto/event/{0}", + "description": "Inferred from Rec Room 2020 client literal `goto/event/{0}`. Expected result: Room instance/join response with Photon and room instance fields.", + "operationId": "client_matchmaking_goto_event_p0_get", + "parameters": [ + { + "name": "p0", + "in": "path", + "required": true, + "schema": { + "type": "string" + }, + "description": "Format placeholder from client literal goto/event/{0}" + } + ], + "responses": { + "200": { + "description": "Room instance/join response with Photon and room instance fields.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RoomInstance" + } + } + } + }, + "204": { + "description": "Success with no body, used by some mutation endpoints." + }, + "400": { + "description": "Client-visible validation or bad request error.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "401": { + "description": "Authentication required or token invalid.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "500": { + "description": "Server error shape tolerated by client wrappers.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + } + }, + "x-recroom-client-route-literal": "goto/event/{0}", + "x-recroom-client-family": "matchmaking", + "x-recroom-client-service": "match", + "x-recroom-builds": [ + "2020.03.10", + "2020.12.18" + ], + "x-recroom-inferred-http-method": "get", + "x-recroom-evidence": [ + { + "type": "RecNet.Matchmaking", + "method": "RecRoom.Async.IPromise`1 MLIILFNOOOK(CCMBKDINCAH IAALIIFNHNP, System.Int32[] DKFBAOLAEFE = null, System.Boolean KJHCANCBKAL = False)", + "file": "RecNet\\Matchmaking.txt", + "line": 8737 + }, + { + "type": "stringliteral.json", + "method": "", + "file": "Recnet-archive/decompiled/Il2CppDump/stringliteral.json", + "line": null + } + ], + "x-client-response-contracts": [ + { + "kind": "object", + "raw": "RecNet.Matchmaking+MHCKNNJOIIP", + "rootType": "MHCKNNJOIIP", + "typeDescription": { + "name": "MHCKNNJOIIP", + "found": true, + "file": "RecNet/Matchmaking.cs", + "declaration": "internal enum MHCKNNJOIIP", + "kind": "enum", + "baseType": null, + "properties": [], + "fields": [], + "enumValues": [ + { + "name": "UnknownError", + "value": "-1" + }, + { + "name": "Success", + "value": "0" + }, + { + "name": "NoSuchGame", + "value": "1" + }, + { + "name": "PlayerNotOnline", + "value": "2" + }, + { + "name": "InsufficientSpace", + "value": "3" + }, + { + "name": "EventNotStarted", + "value": "4" + }, + { + "name": "EventAlreadyFinished", + "value": "5" + }, + { + "name": "BlockedFromRoom", + "value": "7" + }, + { + "name": "JuniorNotAllowed", + "value": "11" + }, + { + "name": "Banned", + "value": "12" + }, + { + "name": "AlreadyInBestInstance", + "value": "13" + }, + { + "name": "InsufficientRelationship", + "value": "14" + }, + { + "name": "UpdateRequired", + "value": "16" + }, + { + "name": "AlreadyInTargetInstance", + "value": "17" + }, + { + "name": "UGCNotAllowed", + "value": "19" + }, + { + "name": "NoSuchRoom", + "value": "20" + }, + { + "name": "RoomIsNotActive", + "value": "22" + }, + { + "name": "RoomBlockedByCreator", + "value": "23" + }, + { + "name": "RoomIsPrivate", + "value": "25" + }, + { + "name": "RoomInstanceIsPrivate", + "value": "26" + }, + { + "name": "DeviceClassNotSupported", + "value": "30" + }, + { + "name": "DeviceClassNotSupportedByRoomOwner", + "value": "31" + }, + { + "name": "MovementModeNotSupportedByRoomOwner", + "value": "32" + }, + { + "name": "EventIsPrivate", + "value": "35" + }, + { + "name": "RoomInviteExpired", + "value": "40" + }, + { + "name": "NoAvailableRegion", + "value": "45" + }, + { + "name": "NotorietyTooPoor", + "value": "50" + }, + { + "name": "BannedFromRoom", + "value": "55" + }, + { + "name": "NoSuchRoomPlaylist", + "value": "60" + }, + { + "name": "RoomPlaylistIsNotActive", + "value": "61" + }, + { + "name": "RoomPlaylistIsPrivate", + "value": "62" + }, + { + "name": "NoSuchClub", + "value": "70" + }, + { + "name": "ClubHasNoClubhouse", + "value": "71" + }, + { + "name": "ClubIsNotActive", + "value": "73" + }, + { + "name": "NotAMemberOfClub", + "value": "74" + }, + { + "name": "BannedFromClub", + "value": "75" + }, + { + "name": "InstanceJoinNotPermitted", + "value": "76" + } + ], + "wireKeys": [] + } + } + ] + } + }, + "/goto/instance/{p0}": { + "get": { + "tags": [ + "matchmaking" + ], + "summary": "GET goto/instance/{0}", + "description": "Inferred from Rec Room 2020 client literal `goto/instance/{0}`. Expected result: Room instance/join response with Photon and room instance fields.", + "operationId": "client_matchmaking_goto_instance_p0_get", + "parameters": [ + { + "name": "p0", + "in": "path", + "required": true, + "schema": { + "type": "string" + }, + "description": "Format placeholder from client literal goto/instance/{0}" + } + ], + "responses": { + "200": { + "description": "Room instance/join response with Photon and room instance fields.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RoomInstance" + } + } + } + }, + "204": { + "description": "Success with no body, used by some mutation endpoints." + }, + "400": { + "description": "Client-visible validation or bad request error.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "401": { + "description": "Authentication required or token invalid.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "500": { + "description": "Server error shape tolerated by client wrappers.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + } + }, + "x-recroom-client-route-literal": "goto/instance/{0}", + "x-recroom-client-family": "matchmaking", + "x-recroom-client-service": "match", + "x-recroom-builds": [ + "2020.03.10", + "2020.12.18" + ], + "x-recroom-inferred-http-method": "get", + "x-recroom-evidence": [ + { + "type": "RecNet.Matchmaking", + "method": "RecRoom.Async.IPromise`1 MGKFDNPCDBP(System.Int64 ANAPBECHGLI, System.Int32[] DKFBAOLAEFE = null, System.Boolean KJHCANCBKAL = False, System.String APJJOJMDLNP = null)", + "file": "RecNet\\Matchmaking.txt", + "line": 9156 + }, + { + "type": "stringliteral.json", + "method": "", + "file": "Recnet-archive/decompiled/Il2CppDump/stringliteral.json", + "line": null + } + ], + "x-client-response-contracts": [ + { + "kind": "object", + "raw": "RecNet.Matchmaking+MHCKNNJOIIP", + "rootType": "MHCKNNJOIIP", + "typeDescription": { + "name": "MHCKNNJOIIP", + "found": true, + "file": "RecNet/Matchmaking.cs", + "declaration": "internal enum MHCKNNJOIIP", + "kind": "enum", + "baseType": null, + "properties": [], + "fields": [], + "enumValues": [ + { + "name": "UnknownError", + "value": "-1" + }, + { + "name": "Success", + "value": "0" + }, + { + "name": "NoSuchGame", + "value": "1" + }, + { + "name": "PlayerNotOnline", + "value": "2" + }, + { + "name": "InsufficientSpace", + "value": "3" + }, + { + "name": "EventNotStarted", + "value": "4" + }, + { + "name": "EventAlreadyFinished", + "value": "5" + }, + { + "name": "BlockedFromRoom", + "value": "7" + }, + { + "name": "JuniorNotAllowed", + "value": "11" + }, + { + "name": "Banned", + "value": "12" + }, + { + "name": "AlreadyInBestInstance", + "value": "13" + }, + { + "name": "InsufficientRelationship", + "value": "14" + }, + { + "name": "UpdateRequired", + "value": "16" + }, + { + "name": "AlreadyInTargetInstance", + "value": "17" + }, + { + "name": "UGCNotAllowed", + "value": "19" + }, + { + "name": "NoSuchRoom", + "value": "20" + }, + { + "name": "RoomIsNotActive", + "value": "22" + }, + { + "name": "RoomBlockedByCreator", + "value": "23" + }, + { + "name": "RoomIsPrivate", + "value": "25" + }, + { + "name": "RoomInstanceIsPrivate", + "value": "26" + }, + { + "name": "DeviceClassNotSupported", + "value": "30" + }, + { + "name": "DeviceClassNotSupportedByRoomOwner", + "value": "31" + }, + { + "name": "MovementModeNotSupportedByRoomOwner", + "value": "32" + }, + { + "name": "EventIsPrivate", + "value": "35" + }, + { + "name": "RoomInviteExpired", + "value": "40" + }, + { + "name": "NoAvailableRegion", + "value": "45" + }, + { + "name": "NotorietyTooPoor", + "value": "50" + }, + { + "name": "BannedFromRoom", + "value": "55" + }, + { + "name": "NoSuchRoomPlaylist", + "value": "60" + }, + { + "name": "RoomPlaylistIsNotActive", + "value": "61" + }, + { + "name": "RoomPlaylistIsPrivate", + "value": "62" + }, + { + "name": "NoSuchClub", + "value": "70" + }, + { + "name": "ClubHasNoClubhouse", + "value": "71" + }, + { + "name": "ClubIsNotActive", + "value": "73" + }, + { + "name": "NotAMemberOfClub", + "value": "74" + }, + { + "name": "BannedFromClub", + "value": "75" + }, + { + "name": "InstanceJoinNotPermitted", + "value": "76" + } + ], + "wireKeys": [] + } + } + ] + } + }, + "/goto/invite/{p0}": { + "post": { + "tags": [ + "matchmaking" + ], + "summary": "POST goto/invite/{0}", + "description": "Inferred from Rec Room 2020 client literal `goto/invite/{0}`. Expected result: Room instance/join response with Photon and room instance fields.", + "operationId": "client_matchmaking_goto_invite_p0_post", + "parameters": [ + { + "name": "p0", + "in": "path", + "required": true, + "schema": { + "type": "string" + }, + "description": "Format placeholder from client literal goto/invite/{0}" + } + ], + "responses": { + "200": { + "description": "Room instance/join response with Photon and room instance fields.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RoomInstance" + } + } + } + }, + "204": { + "description": "Success with no body, used by some mutation endpoints." + }, + "400": { + "description": "Client-visible validation or bad request error.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "401": { + "description": "Authentication required or token invalid.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "500": { + "description": "Server error shape tolerated by client wrappers.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + } + }, + "x-recroom-client-route-literal": "goto/invite/{0}", + "x-recroom-client-family": "matchmaking", + "x-recroom-client-service": "match", + "x-recroom-builds": [ + "2020.03.10", + "2020.12.18" + ], + "x-recroom-inferred-http-method": "post", + "x-recroom-evidence": [ + { + "type": "RecNet.Matchmaking", + "method": "RecRoom.Async.IPromise`1 ALCJNMAOIGH(System.Int64 IAHDKAIJJLB, System.Int64 HNHLJONGKHB, System.Int32[] DKFBAOLAEFE = null, System.Boolean KJHCANCBKAL = False)", + "file": "RecNet\\Matchmaking.txt", + "line": 9294 + }, + { + "type": "stringliteral.json", + "method": "", + "file": "Recnet-archive/decompiled/Il2CppDump/stringliteral.json", + "line": null + } + ], + "requestBody": { + "required": false, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/GenericObject" + } + }, + "application/x-www-form-urlencoded": { + "schema": { + "$ref": "#/components/schemas/GenericObject" + } + }, + "multipart/form-data": { + "schema": { + "$ref": "#/components/schemas/GenericObject" + } + } + }, + "description": "Request body inferred. Exact DTO may be named in x-recroom-evidence method signature or companion DTO inventory." + }, + "x-client-response-contracts": [ + { + "kind": "object", + "raw": "RecNet.Matchmaking+MHCKNNJOIIP", + "rootType": "MHCKNNJOIIP", + "typeDescription": { + "name": "MHCKNNJOIIP", + "found": true, + "file": "RecNet/Matchmaking.cs", + "declaration": "internal enum MHCKNNJOIIP", + "kind": "enum", + "baseType": null, + "properties": [], + "fields": [], + "enumValues": [ + { + "name": "UnknownError", + "value": "-1" + }, + { + "name": "Success", + "value": "0" + }, + { + "name": "NoSuchGame", + "value": "1" + }, + { + "name": "PlayerNotOnline", + "value": "2" + }, + { + "name": "InsufficientSpace", + "value": "3" + }, + { + "name": "EventNotStarted", + "value": "4" + }, + { + "name": "EventAlreadyFinished", + "value": "5" + }, + { + "name": "BlockedFromRoom", + "value": "7" + }, + { + "name": "JuniorNotAllowed", + "value": "11" + }, + { + "name": "Banned", + "value": "12" + }, + { + "name": "AlreadyInBestInstance", + "value": "13" + }, + { + "name": "InsufficientRelationship", + "value": "14" + }, + { + "name": "UpdateRequired", + "value": "16" + }, + { + "name": "AlreadyInTargetInstance", + "value": "17" + }, + { + "name": "UGCNotAllowed", + "value": "19" + }, + { + "name": "NoSuchRoom", + "value": "20" + }, + { + "name": "RoomIsNotActive", + "value": "22" + }, + { + "name": "RoomBlockedByCreator", + "value": "23" + }, + { + "name": "RoomIsPrivate", + "value": "25" + }, + { + "name": "RoomInstanceIsPrivate", + "value": "26" + }, + { + "name": "DeviceClassNotSupported", + "value": "30" + }, + { + "name": "DeviceClassNotSupportedByRoomOwner", + "value": "31" + }, + { + "name": "MovementModeNotSupportedByRoomOwner", + "value": "32" + }, + { + "name": "EventIsPrivate", + "value": "35" + }, + { + "name": "RoomInviteExpired", + "value": "40" + }, + { + "name": "NoAvailableRegion", + "value": "45" + }, + { + "name": "NotorietyTooPoor", + "value": "50" + }, + { + "name": "BannedFromRoom", + "value": "55" + }, + { + "name": "NoSuchRoomPlaylist", + "value": "60" + }, + { + "name": "RoomPlaylistIsNotActive", + "value": "61" + }, + { + "name": "RoomPlaylistIsPrivate", + "value": "62" + }, + { + "name": "NoSuchClub", + "value": "70" + }, + { + "name": "ClubHasNoClubhouse", + "value": "71" + }, + { + "name": "ClubIsNotActive", + "value": "73" + }, + { + "name": "NotAMemberOfClub", + "value": "74" + }, + { + "name": "BannedFromClub", + "value": "75" + }, + { + "name": "InstanceJoinNotPermitted", + "value": "76" + } + ], + "wireKeys": [] + } + } + ] + } + }, + "/goto/none": { + "get": { + "tags": [ + "matchmaking" + ], + "summary": "GET goto/none", + "description": "Inferred from Rec Room 2020 client literal `goto/none`. Expected result: Room instance/join response with Photon and room instance fields.", + "operationId": "client_matchmaking_goto_none_get", + "parameters": [], + "responses": { + "200": { + "description": "Room instance/join response with Photon and room instance fields.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RoomInstance" + } + } + } + }, + "204": { + "description": "Success with no body, used by some mutation endpoints." + }, + "400": { + "description": "Client-visible validation or bad request error.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "401": { + "description": "Authentication required or token invalid.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "500": { + "description": "Server error shape tolerated by client wrappers.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + } + }, + "x-recroom-client-route-literal": "goto/none", + "x-recroom-client-family": "matchmaking", + "x-recroom-client-service": "match", + "x-recroom-builds": [ + "2020.12.18" + ], + "x-recroom-inferred-http-method": "get", + "x-recroom-evidence": [ + { + "type": "RecNet.Matchmaking", + "method": "RecRoom.Async.IPromise OAHNGAMAPNB()", + "file": "RecNet\\Matchmaking.txt", + "line": 10314 + } + ], + "x-client-response-contracts": [ + { + "kind": "success-or-empty", + "raw": "RecRoom.Async.IPromise", + "rootType": "void/success", + "typeDescription": { + "name": "void/success", + "found": false, + "kind": "object", + "wireKeys": [] + } + } + ] + } + }, + "/goto/player/{p0}": { + "get": { + "tags": [ + "matchmaking" + ], + "summary": "GET goto/player/{0}", + "description": "Inferred from Rec Room 2020 client literal `goto/player/{0}`. Expected result: Room instance/join response with Photon and room instance fields.", + "operationId": "client_matchmaking_goto_player_p0_get", + "parameters": [ + { + "name": "p0", + "in": "path", + "required": true, + "schema": { + "type": "string" + }, + "description": "Format placeholder from client literal goto/player/{0}" + } + ], + "responses": { + "200": { + "description": "Room instance/join response with Photon and room instance fields.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RoomInstance" + } + } + } + }, + "204": { + "description": "Success with no body, used by some mutation endpoints." + }, + "400": { + "description": "Client-visible validation or bad request error.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "401": { + "description": "Authentication required or token invalid.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "500": { + "description": "Server error shape tolerated by client wrappers.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + } + }, + "x-recroom-client-route-literal": "goto/player/{0}", + "x-recroom-client-family": "matchmaking", + "x-recroom-client-service": "match", + "x-recroom-builds": [ + "2020.03.10", + "2020.12.18" + ], + "x-recroom-inferred-http-method": "get", + "x-recroom-evidence": [ + { + "type": "RecNet.Matchmaking", + "method": "RecRoom.Async.IPromise`1 IMMCGNNPIAD(System.Int32 CJFGEMGOJHB, System.Int32[] DKFBAOLAEFE = null, System.Boolean KJHCANCBKAL = False)", + "file": "RecNet\\Matchmaking.txt", + "line": 8980 + }, + { + "type": "stringliteral.json", + "method": "", + "file": "Recnet-archive/decompiled/Il2CppDump/stringliteral.json", + "line": null + } + ], + "x-client-response-contracts": [ + { + "kind": "object", + "raw": "RecNet.Matchmaking+MHCKNNJOIIP", + "rootType": "MHCKNNJOIIP", + "typeDescription": { + "name": "MHCKNNJOIIP", + "found": true, + "file": "RecNet/Matchmaking.cs", + "declaration": "internal enum MHCKNNJOIIP", + "kind": "enum", + "baseType": null, + "properties": [], + "fields": [], + "enumValues": [ + { + "name": "UnknownError", + "value": "-1" + }, + { + "name": "Success", + "value": "0" + }, + { + "name": "NoSuchGame", + "value": "1" + }, + { + "name": "PlayerNotOnline", + "value": "2" + }, + { + "name": "InsufficientSpace", + "value": "3" + }, + { + "name": "EventNotStarted", + "value": "4" + }, + { + "name": "EventAlreadyFinished", + "value": "5" + }, + { + "name": "BlockedFromRoom", + "value": "7" + }, + { + "name": "JuniorNotAllowed", + "value": "11" + }, + { + "name": "Banned", + "value": "12" + }, + { + "name": "AlreadyInBestInstance", + "value": "13" + }, + { + "name": "InsufficientRelationship", + "value": "14" + }, + { + "name": "UpdateRequired", + "value": "16" + }, + { + "name": "AlreadyInTargetInstance", + "value": "17" + }, + { + "name": "UGCNotAllowed", + "value": "19" + }, + { + "name": "NoSuchRoom", + "value": "20" + }, + { + "name": "RoomIsNotActive", + "value": "22" + }, + { + "name": "RoomBlockedByCreator", + "value": "23" + }, + { + "name": "RoomIsPrivate", + "value": "25" + }, + { + "name": "RoomInstanceIsPrivate", + "value": "26" + }, + { + "name": "DeviceClassNotSupported", + "value": "30" + }, + { + "name": "DeviceClassNotSupportedByRoomOwner", + "value": "31" + }, + { + "name": "MovementModeNotSupportedByRoomOwner", + "value": "32" + }, + { + "name": "EventIsPrivate", + "value": "35" + }, + { + "name": "RoomInviteExpired", + "value": "40" + }, + { + "name": "NoAvailableRegion", + "value": "45" + }, + { + "name": "NotorietyTooPoor", + "value": "50" + }, + { + "name": "BannedFromRoom", + "value": "55" + }, + { + "name": "NoSuchRoomPlaylist", + "value": "60" + }, + { + "name": "RoomPlaylistIsNotActive", + "value": "61" + }, + { + "name": "RoomPlaylistIsPrivate", + "value": "62" + }, + { + "name": "NoSuchClub", + "value": "70" + }, + { + "name": "ClubHasNoClubhouse", + "value": "71" + }, + { + "name": "ClubIsNotActive", + "value": "73" + }, + { + "name": "NotAMemberOfClub", + "value": "74" + }, + { + "name": "BannedFromClub", + "value": "75" + }, + { + "name": "InstanceJoinNotPermitted", + "value": "76" + } + ], + "wireKeys": [] + } + } + ] + } + }, + "/goto/playlist/{tail}": { + "get": { + "tags": [ + "matchmaking" + ], + "summary": "GET goto/playlist/", + "description": "Inferred from Rec Room 2020 client literal `goto/playlist/`. Expected result: Room instance/join response with Photon and room instance fields.", + "operationId": "client_matchmaking_goto_playlist_tail_get", + "parameters": [ + { + "name": "tail", + "in": "path", + "required": true, + "schema": { + "type": "string" + }, + "description": "Format placeholder from client literal goto/playlist/" + } + ], + "responses": { + "200": { + "description": "Room instance/join response with Photon and room instance fields.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RoomInstance" + } + } + } + }, + "204": { + "description": "Success with no body, used by some mutation endpoints." + }, + "400": { + "description": "Client-visible validation or bad request error.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "401": { + "description": "Authentication required or token invalid.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "500": { + "description": "Server error shape tolerated by client wrappers.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + } + }, + "x-recroom-client-route-literal": "goto/playlist/", + "x-recroom-client-family": "matchmaking", + "x-recroom-client-service": "match", + "x-recroom-builds": [ + "2020.12.18" + ], + "x-recroom-inferred-http-method": "get", + "x-recroom-evidence": [ + { + "type": "RecNet.Matchmaking", + "method": "RecRoom.Async.IPromise`1 NDKKNPMAGNK(System.String APJJOJMDLNP, System.Boolean OFFEBBLOEJA = False, System.Int32[] DKFBAOLAEFE = null, System.Boolean KJHCANCBKAL = False)", + "file": "RecNet\\Matchmaking.txt", + "line": 8050 + } + ], + "x-client-response-contracts": [ + { + "kind": "object", + "raw": "RecNet.Matchmaking+MHCKNNJOIIP", + "rootType": "MHCKNNJOIIP", + "typeDescription": { + "name": "MHCKNNJOIIP", + "found": true, + "file": "RecNet/Matchmaking.cs", + "declaration": "internal enum MHCKNNJOIIP", + "kind": "enum", + "baseType": null, + "properties": [], + "fields": [], + "enumValues": [ + { + "name": "UnknownError", + "value": "-1" + }, + { + "name": "Success", + "value": "0" + }, + { + "name": "NoSuchGame", + "value": "1" + }, + { + "name": "PlayerNotOnline", + "value": "2" + }, + { + "name": "InsufficientSpace", + "value": "3" + }, + { + "name": "EventNotStarted", + "value": "4" + }, + { + "name": "EventAlreadyFinished", + "value": "5" + }, + { + "name": "BlockedFromRoom", + "value": "7" + }, + { + "name": "JuniorNotAllowed", + "value": "11" + }, + { + "name": "Banned", + "value": "12" + }, + { + "name": "AlreadyInBestInstance", + "value": "13" + }, + { + "name": "InsufficientRelationship", + "value": "14" + }, + { + "name": "UpdateRequired", + "value": "16" + }, + { + "name": "AlreadyInTargetInstance", + "value": "17" + }, + { + "name": "UGCNotAllowed", + "value": "19" + }, + { + "name": "NoSuchRoom", + "value": "20" + }, + { + "name": "RoomIsNotActive", + "value": "22" + }, + { + "name": "RoomBlockedByCreator", + "value": "23" + }, + { + "name": "RoomIsPrivate", + "value": "25" + }, + { + "name": "RoomInstanceIsPrivate", + "value": "26" + }, + { + "name": "DeviceClassNotSupported", + "value": "30" + }, + { + "name": "DeviceClassNotSupportedByRoomOwner", + "value": "31" + }, + { + "name": "MovementModeNotSupportedByRoomOwner", + "value": "32" + }, + { + "name": "EventIsPrivate", + "value": "35" + }, + { + "name": "RoomInviteExpired", + "value": "40" + }, + { + "name": "NoAvailableRegion", + "value": "45" + }, + { + "name": "NotorietyTooPoor", + "value": "50" + }, + { + "name": "BannedFromRoom", + "value": "55" + }, + { + "name": "NoSuchRoomPlaylist", + "value": "60" + }, + { + "name": "RoomPlaylistIsNotActive", + "value": "61" + }, + { + "name": "RoomPlaylistIsPrivate", + "value": "62" + }, + { + "name": "NoSuchClub", + "value": "70" + }, + { + "name": "ClubHasNoClubhouse", + "value": "71" + }, + { + "name": "ClubIsNotActive", + "value": "73" + }, + { + "name": "NotAMemberOfClub", + "value": "74" + }, + { + "name": "BannedFromClub", + "value": "75" + }, + { + "name": "InstanceJoinNotPermitted", + "value": "76" + } + ], + "wireKeys": [] + } + } + ] + } + }, + "/goto/room/{tail}": { + "get": { + "tags": [ + "matchmaking" + ], + "summary": "GET goto/room/", + "description": "Inferred from Rec Room 2020 client literal `goto/room/`. Expected result: Room instance/join response with Photon and room instance fields.", + "operationId": "client_matchmaking_goto_room_tail_get", + "parameters": [ + { + "name": "tail", + "in": "path", + "required": true, + "schema": { + "type": "string" + }, + "description": "Format placeholder from client literal goto/room/" + } + ], + "responses": { + "200": { + "description": "Room instance/join response with Photon and room instance fields.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RoomInstance" + } + } + } + }, + "204": { + "description": "Success with no body, used by some mutation endpoints." + }, + "400": { + "description": "Client-visible validation or bad request error.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "401": { + "description": "Authentication required or token invalid.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "500": { + "description": "Server error shape tolerated by client wrappers.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + } + }, + "x-recroom-client-route-literal": "goto/room/", + "x-recroom-client-family": "matchmaking", + "x-recroom-client-service": "match", + "x-recroom-builds": [ + "2020.03.10", + "2020.12.18" + ], + "x-recroom-inferred-http-method": "get", + "x-recroom-evidence": [ + { + "type": "RecNet.Matchmaking", + "method": "RecRoom.Async.IPromise`1 GDCFFOJEIMD()", + "file": "RecNet\\Matchmaking.txt", + "line": 7509 + }, + { + "type": "RecNet.Matchmaking", + "method": "RecRoom.Async.IPromise`1 ILPIDHICKMB(System.String BFGCDJFNJLE, System.Boolean OFFEBBLOEJA = False, System.Int32[] DKFBAOLAEFE = null, System.Boolean KJHCANCBKAL = False, System.String APJJOJMDLNP = null, System.Boolean PBCPIEHFDBH = False)", + "file": "RecNet\\Matchmaking.txt", + "line": 8426 + }, + { + "type": "RecNet.Matchmaking", + "method": "RecRoom.Async.IPromise`1 JLMFIHJLNBL(System.String HFDPAGDDNDE, System.Int32[] DKFBAOLAEFE, System.Boolean PBCPIEHFDBH, System.Boolean KJHCANCBKAL, System.Boolean ALOAFENOBGN)", + "file": "RecNet\\Matchmaking.txt", + "line": 7882 + }, + { + "type": "RecNet.Matchmaking", + "method": "RecRoom.Async.IPromise`1 KNCHJJDKJGK(System.String BFGCDJFNJLE, System.String HFDPAGDDNDE, RecNet.Matchmaking+GIJOGKJCEKG MIAGCJJACCL = 0, System.Int32[] DKFBAOLAEFE = null, System.Boolean PBCPIEHFDBH = False, System.Boolean KJHCANCBKAL = False, System.String APJJOJMDLNP = null)", + "file": "RecNet\\Matchmaking.txt", + "line": 8632 + }, + { + "type": "stringliteral.json", + "method": "", + "file": "Recnet-archive/decompiled/Il2CppDump/stringliteral.json", + "line": null + } + ], + "x-client-response-contracts": [ + { + "kind": "object", + "raw": "RecNet.Matchmaking+MHCKNNJOIIP", + "rootType": "MHCKNNJOIIP", + "typeDescription": { + "name": "MHCKNNJOIIP", + "found": true, + "file": "RecNet/Matchmaking.cs", + "declaration": "internal enum MHCKNNJOIIP", + "kind": "enum", + "baseType": null, + "properties": [], + "fields": [], + "enumValues": [ + { + "name": "UnknownError", + "value": "-1" + }, + { + "name": "Success", + "value": "0" + }, + { + "name": "NoSuchGame", + "value": "1" + }, + { + "name": "PlayerNotOnline", + "value": "2" + }, + { + "name": "InsufficientSpace", + "value": "3" + }, + { + "name": "EventNotStarted", + "value": "4" + }, + { + "name": "EventAlreadyFinished", + "value": "5" + }, + { + "name": "BlockedFromRoom", + "value": "7" + }, + { + "name": "JuniorNotAllowed", + "value": "11" + }, + { + "name": "Banned", + "value": "12" + }, + { + "name": "AlreadyInBestInstance", + "value": "13" + }, + { + "name": "InsufficientRelationship", + "value": "14" + }, + { + "name": "UpdateRequired", + "value": "16" + }, + { + "name": "AlreadyInTargetInstance", + "value": "17" + }, + { + "name": "UGCNotAllowed", + "value": "19" + }, + { + "name": "NoSuchRoom", + "value": "20" + }, + { + "name": "RoomIsNotActive", + "value": "22" + }, + { + "name": "RoomBlockedByCreator", + "value": "23" + }, + { + "name": "RoomIsPrivate", + "value": "25" + }, + { + "name": "RoomInstanceIsPrivate", + "value": "26" + }, + { + "name": "DeviceClassNotSupported", + "value": "30" + }, + { + "name": "DeviceClassNotSupportedByRoomOwner", + "value": "31" + }, + { + "name": "MovementModeNotSupportedByRoomOwner", + "value": "32" + }, + { + "name": "EventIsPrivate", + "value": "35" + }, + { + "name": "RoomInviteExpired", + "value": "40" + }, + { + "name": "NoAvailableRegion", + "value": "45" + }, + { + "name": "NotorietyTooPoor", + "value": "50" + }, + { + "name": "BannedFromRoom", + "value": "55" + }, + { + "name": "NoSuchRoomPlaylist", + "value": "60" + }, + { + "name": "RoomPlaylistIsNotActive", + "value": "61" + }, + { + "name": "RoomPlaylistIsPrivate", + "value": "62" + }, + { + "name": "NoSuchClub", + "value": "70" + }, + { + "name": "ClubHasNoClubhouse", + "value": "71" + }, + { + "name": "ClubIsNotActive", + "value": "73" + }, + { + "name": "NotAMemberOfClub", + "value": "74" + }, + { + "name": "BannedFromClub", + "value": "75" + }, + { + "name": "InstanceJoinNotPermitted", + "value": "76" + } + ], + "wireKeys": [] + } + } + ], + "x-client-http-response-override": { + "note": "The public client method resolves to MHCKNNJOIIP, but NDDKNMLHKBK actually parses the HTTP JSON into RecNet.Matchmaking+JKOFHKAHOHK and then maps that wrapper to the enum. This is the body the server should emit.", + "typeDescription": { + "name": "JKOFHKAHOHK", + "found": true, + "file": "RecNet/Matchmaking.cs", + "declaration": "private class JKOFHKAHOHK : IFAIJAGLDFK", + "kind": "class", + "baseType": "IFAIJAGLDFK", + "properties": [ + { + "name": "FLOHLCKCOFA", + "type": "CIIBGMBOFEI", + "accessibility": "public", + "kind": "property" + }, + { + "name": "HIKHMNNGOHD", + "type": "MHCKNNJOIIP", + "accessibility": "public", + "kind": "property" + } + ], + "fields": [], + "enumValues": [], + "wireKeys": [ + "errorCode", + "roomInstance" + ], + "isilFile": "RecNet/Matchmaking_NestedType_JKOFHKAHOHK.txt", + "isilType": "RecNet.Matchmaking+JKOFHKAHOHK" + } + } + } + }, + "/room/{p0}/instances": { + "get": { + "tags": [ + "matchmaking" + ], + "summary": "GET room/{0}/instances", + "description": "Inferred from Rec Room 2020 client literal `room/{0}/instances`. Expected result: Room instance/join response with Photon and room instance fields.", + "operationId": "client_matchmaking_room_p0_instances_get", + "parameters": [ + { + "name": "p0", + "in": "path", + "required": true, + "schema": { + "type": "string" + }, + "description": "Format placeholder from client literal room/{0}/instances" + } + ], + "responses": { + "200": { + "description": "Room instance/join response with Photon and room instance fields.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RoomInstance" + } + } + } + }, + "204": { + "description": "Success with no body, used by some mutation endpoints." + }, + "400": { + "description": "Client-visible validation or bad request error.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "401": { + "description": "Authentication required or token invalid.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "500": { + "description": "Server error shape tolerated by client wrappers.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + } + }, + "x-recroom-client-route-literal": "room/{0}/instances", + "x-recroom-client-family": "matchmaking", + "x-recroom-client-service": "match", + "x-recroom-builds": [ + "2020.03.10", + "2020.12.18" + ], + "x-recroom-inferred-http-method": "get", + "x-recroom-evidence": [ + { + "type": "RecNet.Matchmaking", + "method": "RecRoom.Async.IPromise`1> KFMFHGLEGHF(System.Int64 HNHLJONGKHB)", + "file": "RecNet\\Matchmaking.txt", + "line": 10726 + }, + { + "type": "stringliteral.json", + "method": "", + "file": "Recnet-archive/decompiled/Il2CppDump/stringliteral.json", + "line": null + } + ], + "x-client-response-contracts": [ + { + "kind": "array", + "raw": "System.Collections.Generic.List`1", + "rootType": "JHNOAKDOMHG", + "item": { + "kind": "object", + "raw": "JHNOAKDOMHG", + "rootType": "JHNOAKDOMHG" + }, + "typeDescription": { + "name": "JHNOAKDOMHG", + "found": true, + "file": "JHNOAKDOMHG.cs", + "declaration": "public class JHNOAKDOMHG : IFAIJAGLDFK", + "kind": "class", + "baseType": "IFAIJAGLDFK", + "properties": [ + { + "name": "ACBFDMLHFPB", + "type": "DateTime", + "accessibility": "public", + "kind": "property" + }, + { + "name": "AMFIPBKHEPN", + "type": "string", + "accessibility": "public", + "kind": "property" + }, + { + "name": "BGCNPBBBEIK", + "type": "long", + "accessibility": "public", + "kind": "property" + }, + { + "name": "DADOKMAOFJL", + "type": "long", + "accessibility": "public", + "kind": "property" + }, + { + "name": "EDCIALKDIGM", + "type": "string", + "accessibility": "public", + "kind": "property" + }, + { + "name": "FDMFNIJKHKC", + "type": "bool", + "accessibility": "public", + "kind": "property" + }, + { + "name": "GJPHHEHBCIJ", + "type": "long", + "accessibility": "public", + "kind": "property" + }, + { + "name": "HHGAKDNMHGO", + "type": "List", + "accessibility": "public", + "kind": "property" + }, + { + "name": "HJGLKNFDHAP", + "type": "bool", + "accessibility": "public", + "kind": "property" + }, + { + "name": "HLFNLKMCNAC", + "type": "int", + "accessibility": "public", + "kind": "property" + } + ], + "fields": [], + "enumValues": [], + "wireKeys": [ + "roomInstanceId", + "roomId", + "subRoomId", + "isFull", + "createdAt", + "playerIds" + ], + "isilFile": "JHNOAKDOMHG.txt", + "isilType": "JHNOAKDOMHG" + } + } + ] + } + }, + "/api/messages/v1/IOSClearDeviceToken": { + "post": { + "tags": [ + "messages" + ], + "summary": "POST api/messages/v1/IOSClearDeviceToken", + "description": "Inferred from Rec Room 2020 client literal `api/messages/v1/IOSClearDeviceToken`. Expected result: Message/thread object/list or message mutation success.", + "operationId": "client_messages_api_messages_v1_ioscleardevicetoken_post", + "parameters": [], + "responses": { + "200": { + "description": "Message/thread object/list or message mutation success.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Message" + } + } + } + }, + "204": { + "description": "Success with no body, used by some mutation endpoints." + }, + "400": { + "description": "Client-visible validation or bad request error.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "401": { + "description": "Authentication required or token invalid.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "500": { + "description": "Server error shape tolerated by client wrappers.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + } + }, + "x-recroom-client-route-literal": "api/messages/v1/IOSClearDeviceToken", + "x-recroom-client-family": "messages", + "x-recroom-client-service": "api/chat/notifications", + "x-recroom-builds": [ + "2020.03.10", + "2020.12.18" + ], + "x-recroom-inferred-http-method": "post", + "x-recroom-evidence": [ + { + "type": "KEBJPIGKGOI", + "method": "RecRoom.Async.IPromise LLIHILNLNEP()", + "file": "KEBJPIGKGOI.txt", + "line": 5886 + }, + { + "type": "stringliteral.json", + "method": "", + "file": "Recnet-archive/decompiled/Il2CppDump/stringliteral.json", + "line": null + } + ], + "requestBody": { + "required": false, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/GenericObject" + } + }, + "application/x-www-form-urlencoded": { + "schema": { + "$ref": "#/components/schemas/GenericObject" + } + }, + "multipart/form-data": { + "schema": { + "$ref": "#/components/schemas/GenericObject" + } + } + }, + "description": "Request body inferred. Exact DTO may be named in x-recroom-evidence method signature or companion DTO inventory." + }, + "x-client-response-contracts": [ + { + "kind": "success-or-empty", + "raw": "RecRoom.Async.IPromise", + "rootType": "void/success", + "typeDescription": { + "name": "void/success", + "found": false, + "kind": "object", + "wireKeys": [] + } + } + ] + } + }, + "/api/messages/v1/IOSModifyNotificationPreferences": { + "post": { + "tags": [ + "messages" + ], + "summary": "POST api/messages/v1/IOSModifyNotificationPreferences", + "description": "Inferred from Rec Room 2020 client literal `api/messages/v1/IOSModifyNotificationPreferences`. Expected result: Message/thread object/list or message mutation success.", + "operationId": "client_messages_api_messages_v1_iosmodifynotificationpreferences_post", + "parameters": [], + "responses": { + "200": { + "description": "Message/thread object/list or message mutation success.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Message" + } + } + } + }, + "204": { + "description": "Success with no body, used by some mutation endpoints." + }, + "400": { + "description": "Client-visible validation or bad request error.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "401": { + "description": "Authentication required or token invalid.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "500": { + "description": "Server error shape tolerated by client wrappers.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + } + }, + "x-recroom-client-route-literal": "api/messages/v1/IOSModifyNotificationPreferences", + "x-recroom-client-family": "messages", + "x-recroom-client-service": "api/chat/notifications", + "x-recroom-builds": [ + "2020.03.10", + "2020.12.18" + ], + "x-recroom-inferred-http-method": "post", + "x-recroom-evidence": [ + { + "type": "KEBJPIGKGOI", + "method": "RecRoom.Async.IPromise NDONIOINHAE(DIBODMEJOPN JFMOEGCPNAC)", + "file": "KEBJPIGKGOI.txt", + "line": 6455 + }, + { + "type": "stringliteral.json", + "method": "", + "file": "Recnet-archive/decompiled/Il2CppDump/stringliteral.json", + "line": null + } + ], + "requestBody": { + "required": false, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/GenericObject" + } + }, + "application/x-www-form-urlencoded": { + "schema": { + "$ref": "#/components/schemas/GenericObject" + } + }, + "multipart/form-data": { + "schema": { + "$ref": "#/components/schemas/GenericObject" + } + } + }, + "description": "Request body inferred. Exact DTO may be named in x-recroom-evidence method signature or companion DTO inventory." + }, + "x-client-response-contracts": [ + { + "kind": "success-or-empty", + "raw": "RecRoom.Async.IPromise", + "rootType": "void/success", + "typeDescription": { + "name": "void/success", + "found": false, + "kind": "object", + "wireKeys": [] + } + } + ] + } + }, + "/api/messages/v1/IOSResetNotificationPreferencesBadgeCount": { + "post": { + "tags": [ + "messages" + ], + "summary": "POST api/messages/v1/IOSResetNotificationPreferencesBadgeCount", + "description": "Inferred from Rec Room 2020 client literal `api/messages/v1/IOSResetNotificationPreferencesBadgeCount`. Expected result: Message/thread object/list or message mutation success.", + "operationId": "client_messages_api_messages_v1_iosresetnotificationpreferencesbadgecount_post", + "parameters": [], + "responses": { + "200": { + "description": "Message/thread object/list or message mutation success.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Message" + } + } + } + }, + "204": { + "description": "Success with no body, used by some mutation endpoints." + }, + "400": { + "description": "Client-visible validation or bad request error.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "401": { + "description": "Authentication required or token invalid.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "500": { + "description": "Server error shape tolerated by client wrappers.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + } + }, + "x-recroom-client-route-literal": "api/messages/v1/IOSResetNotificationPreferencesBadgeCount", + "x-recroom-client-family": "messages", + "x-recroom-client-service": "api/chat/notifications", + "x-recroom-builds": [ + "2020.03.10", + "2020.12.18" + ], + "x-recroom-inferred-http-method": "post", + "x-recroom-evidence": [ + { + "type": "KEBJPIGKGOI", + "method": "RecRoom.Async.IPromise GENJNKHDHCN()", + "file": "KEBJPIGKGOI.txt", + "line": 6347 + }, + { + "type": "stringliteral.json", + "method": "", + "file": "Recnet-archive/decompiled/Il2CppDump/stringliteral.json", + "line": null + } + ], + "requestBody": { + "required": false, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/GenericObject" + } + }, + "application/x-www-form-urlencoded": { + "schema": { + "$ref": "#/components/schemas/GenericObject" + } + }, + "multipart/form-data": { + "schema": { + "$ref": "#/components/schemas/GenericObject" + } + } + }, + "description": "Request body inferred. Exact DTO may be named in x-recroom-evidence method signature or companion DTO inventory." + }, + "x-client-response-contracts": [ + { + "kind": "success-or-empty", + "raw": "RecRoom.Async.IPromise", + "rootType": "void/success", + "typeDescription": { + "name": "void/success", + "found": false, + "kind": "object", + "wireKeys": [] + } + } + ] + } + }, + "/api/messages/v1/IOSSaveDeviceToken": { + "post": { + "tags": [ + "messages" + ], + "summary": "POST api/messages/v1/IOSSaveDeviceToken", + "description": "Inferred from Rec Room 2020 client literal `api/messages/v1/IOSSaveDeviceToken`. Expected result: Message/thread object/list or message mutation success.", + "operationId": "client_messages_api_messages_v1_iossavedevicetoken_post", + "parameters": [], + "responses": { + "200": { + "description": "Message/thread object/list or message mutation success.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Message" + } + } + } + }, + "204": { + "description": "Success with no body, used by some mutation endpoints." + }, + "400": { + "description": "Client-visible validation or bad request error.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "401": { + "description": "Authentication required or token invalid.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "500": { + "description": "Server error shape tolerated by client wrappers.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + } + }, + "x-recroom-client-route-literal": "api/messages/v1/IOSSaveDeviceToken", + "x-recroom-client-family": "messages", + "x-recroom-client-service": "api/chat/notifications", + "x-recroom-builds": [ + "2020.03.10", + "2020.12.18" + ], + "x-recroom-inferred-http-method": "post", + "x-recroom-evidence": [ + { + "type": "KEBJPIGKGOI", + "method": "RecRoom.Async.IPromise INNJDBLBCEO(System.String EAEICOOGLAK)", + "file": "KEBJPIGKGOI.txt", + "line": 5768 + }, + { + "type": "stringliteral.json", + "method": "", + "file": "Recnet-archive/decompiled/Il2CppDump/stringliteral.json", + "line": null + } + ], + "requestBody": { + "required": false, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/GenericObject" + } + }, + "application/x-www-form-urlencoded": { + "schema": { + "$ref": "#/components/schemas/GenericObject" + } + }, + "multipart/form-data": { + "schema": { + "$ref": "#/components/schemas/GenericObject" + } + } + }, + "description": "Request body inferred. Exact DTO may be named in x-recroom-evidence method signature or companion DTO inventory." + }, + "x-client-response-contracts": [ + { + "kind": "success-or-empty", + "raw": "RecRoom.Async.IPromise", + "rootType": "void/success", + "typeDescription": { + "name": "void/success", + "found": false, + "kind": "object", + "wireKeys": [] + } + } + ] + } + }, + "/api/messages/v3/delete": { + "delete": { + "tags": [ + "messages" + ], + "summary": "DELETE api/messages/v3/delete", + "description": "Inferred from Rec Room 2020 client literal `api/messages/v3/delete`. Expected result: Message/thread object/list or message mutation success.", + "operationId": "client_messages_api_messages_v3_delete_delete", + "parameters": [], + "responses": { + "200": { + "description": "Message/thread object/list or message mutation success.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Message" + } + } + } + }, + "204": { + "description": "Success with no body, used by some mutation endpoints." + }, + "400": { + "description": "Client-visible validation or bad request error.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "401": { + "description": "Authentication required or token invalid.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "500": { + "description": "Server error shape tolerated by client wrappers.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + } + }, + "x-recroom-client-route-literal": "api/messages/v3/delete", + "x-recroom-client-family": "messages", + "x-recroom-client-service": "api/chat/notifications", + "x-recroom-builds": [ + "2020.03.10", + "2020.12.18" + ], + "x-recroom-inferred-http-method": "delete", + "x-recroom-evidence": [ + { + "type": "KEBJPIGKGOI", + "method": "System.Collections.IEnumerator PEIBHPIEKGF(System.Collections.Generic.IEnumerable`1 OCCKKEIMPGP, BPHGKAEDBPE+CBEOHBCIPEA AFLPGGJMPOE = null)", + "file": "KEBJPIGKGOI.txt", + "line": 3138 + }, + { + "type": "stringliteral.json", + "method": "", + "file": "Recnet-archive/decompiled/Il2CppDump/stringliteral.json", + "line": null + } + ], + "requestBody": { + "required": false, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/GenericObject" + } + }, + "application/x-www-form-urlencoded": { + "schema": { + "$ref": "#/components/schemas/GenericObject" + } + }, + "multipart/form-data": { + "schema": { + "$ref": "#/components/schemas/GenericObject" + } + } + }, + "description": "Request body inferred. Exact DTO may be named in x-recroom-evidence method signature or companion DTO inventory." + }, + "x-client-response-contracts": [ + { + "kind": "callback-or-coroutine", + "raw": "IEnumerator", + "rootType": "callback", + "typeDescription": { + "name": "callback", + "found": false, + "kind": "object", + "wireKeys": [] + } + } + ] + } + }, + "/api/messages": { + "get": { + "tags": [ + "messages" + ], + "summary": "GET api/messages/", + "description": "Inferred from Rec Room 2020 client literal `api/messages/`. Expected result: Message/thread object/list or message mutation success.", + "operationId": "client_messages_api_messages_get", + "parameters": [], + "responses": { + "200": { + "description": "Message/thread object/list or message mutation success.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Message" + } + } + } + }, + "204": { + "description": "Success with no body, used by some mutation endpoints." + }, + "400": { + "description": "Client-visible validation or bad request error.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "401": { + "description": "Authentication required or token invalid.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "500": { + "description": "Server error shape tolerated by client wrappers.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + } + }, + "x-recroom-client-route-literal": "api/messages/", + "x-recroom-client-family": "messages", + "x-recroom-client-service": "api/chat/notifications", + "x-recroom-builds": [ + "2020.03.10", + "2020.12.18" + ], + "x-recroom-inferred-http-method": "get", + "x-recroom-evidence": [ + { + "type": "KEBJPIGKGOI", + "method": "RecRoom.Async.IPromise JHJDMMLABCG()", + "file": "KEBJPIGKGOI.txt", + "line": 1297 + }, + { + "type": "KEBJPIGKGOI", + "method": "RecRoom.Async.IPromise MPLDGJCCIMN(System.Int64 CJFGEMGOJHB, IGCIBGKPPMO+BBELBJELLHN JMDIPDGMIOG, System.String ABADFLCBFIJ, System.Nullable`1 HNHLJONGKHB)", + "file": "KEBJPIGKGOI.txt", + "line": 2653 + }, + { + "type": "KEBJPIGKGOI", + "method": "RecRoom.Async.IPromise`1 IIHKDEGDMEA()", + "file": "KEBJPIGKGOI.txt", + "line": 6118 + }, + { + "type": "KEBJPIGKGOI", + "method": "System.Collections.IEnumerator JFAMBLHIACA(System.Collections.Generic.List`1 FNNIKBKHAFN, BPHGKAEDBPE+CBEOHBCIPEA AFLPGGJMPOE = null)", + "file": "KEBJPIGKGOI.txt", + "line": 1476 + }, + { + "type": "KEBJPIGKGOI", + "method": "System.Collections.IEnumerator MGIABJDLBED(System.Collections.Generic.List`1 FNNIKBKHAFN, IGCIBGKPPMO+BBELBJELLHN JMDIPDGMIOG, System.String ABADFLCBFIJ, System.Nullable`1 HNHLJONGKHB, BPHGKAEDBPE+CBEOHBCIPEA AFLPGGJMPOE = null)", + "file": "KEBJPIGKGOI.txt", + "line": 2843 + }, + { + "type": "KEBJPIGKGOI", + "method": "System.Void CPGAJIACKIP()", + "file": "KEBJPIGKGOI.txt", + "line": 3306 + }, + { + "type": "stringliteral.json", + "method": "", + "file": "Recnet-archive/decompiled/Il2CppDump/stringliteral.json", + "line": null + } + ], + "x-client-response-contracts": [ + { + "kind": "success-or-empty", + "raw": "RecRoom.Async.IPromise", + "rootType": "void/success", + "typeDescription": { + "name": "void/success", + "found": false, + "kind": "object", + "wireKeys": [] + } + }, + { + "kind": "object", + "raw": "DIBODMEJOPN", + "rootType": "DIBODMEJOPN", + "typeDescription": { + "name": "DIBODMEJOPN", + "found": true, + "file": "DIBODMEJOPN.cs", + "declaration": "public class DIBODMEJOPN : IFAIJAGLDFK", + "kind": "class", + "baseType": "IFAIJAGLDFK", + "properties": [ + { + "name": "EOBAEAPOFKO", + "type": "bool", + "accessibility": "public", + "kind": "property" + }, + { + "name": "FHGBCJLIGJE", + "type": "bool", + "accessibility": "public", + "kind": "property" + }, + { + "name": "OPAAFAKPHOE", + "type": "bool", + "accessibility": "public", + "kind": "property" + } + ], + "fields": [], + "enumValues": [], + "wireKeys": [ + "ChatMessage", + "FriendInvite", + "FavoriteFriendOnline" + ], + "isilFile": "DIBODMEJOPN.txt", + "isilType": "DIBODMEJOPN" + } + }, + { + "kind": "callback-or-coroutine", + "raw": "IEnumerator", + "rootType": "callback", + "typeDescription": { + "name": "callback", + "found": false, + "kind": "object", + "wireKeys": [] + } + }, + { + "kind": "unknown", + "raw": "", + "rootType": "unknown", + "typeDescription": { + "name": "unknown", + "found": false, + "kind": "object", + "wireKeys": [] + } + } + ] + } + }, + "/api/offlineinvite/{tail}": { + "post": { + "tags": [ + "messages" + ], + "summary": "POST api/offlineinvite/", + "description": "Inferred from Rec Room 2020 client literal `api/offlineinvite/`. Expected result: Message/thread object/list or message mutation success.", + "operationId": "client_messages_api_offlineinvite_tail_post", + "parameters": [ + { + "name": "tail", + "in": "path", + "required": true, + "schema": { + "type": "string" + }, + "description": "Format placeholder from client literal api/offlineinvite/" + } + ], + "responses": { + "200": { + "description": "Message/thread object/list or message mutation success.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Message" + } + } + } + }, + "204": { + "description": "Success with no body, used by some mutation endpoints." + }, + "400": { + "description": "Client-visible validation or bad request error.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "401": { + "description": "Authentication required or token invalid.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "500": { + "description": "Server error shape tolerated by client wrappers.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + } + }, + "x-recroom-client-route-literal": "api/offlineinvite/", + "x-recroom-client-family": "messages", + "x-recroom-client-service": "api/chat/notifications", + "x-recroom-builds": [ + "2020.12.18" + ], + "x-recroom-inferred-http-method": "post", + "x-recroom-evidence": [ + { + "type": "KEBJPIGKGOI", + "method": "System.Collections.IEnumerator LPPPAHLCPCO(System.Int64 CJFGEMGOJHB, BPHGKAEDBPE+OJDJIJDNFHE AFLPGGJMPOE)", + "file": "KEBJPIGKGOI.txt", + "line": 6606 + } + ], + "requestBody": { + "required": false, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/GenericObject" + } + }, + "application/x-www-form-urlencoded": { + "schema": { + "$ref": "#/components/schemas/GenericObject" + } + }, + "multipart/form-data": { + "schema": { + "$ref": "#/components/schemas/GenericObject" + } + } + }, + "description": "Request body inferred. Exact DTO may be named in x-recroom-evidence method signature or companion DTO inventory." + }, + "x-client-response-contracts": [ + { + "kind": "callback-or-coroutine", + "raw": "IEnumerator", + "rootType": "callback", + "typeDescription": { + "name": "callback", + "found": false, + "kind": "object", + "wireKeys": [] + } + } + ] + } + }, + "/account/subscriptions": { + "get": { + "tags": [ + "misc" + ], + "summary": "GET https://apps.apple.com/account/subscriptions", + "description": "Inferred from Rec Room 2020 client literal `https://apps.apple.com/account/subscriptions`. Expected result: Generic object/array/success result inferred from client route.", + "operationId": "client_misc_account_subscriptions_get", + "parameters": [], + "responses": { + "200": { + "description": "Generic object/array/success result inferred from client route.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/GenericObject" + } + } + } + }, + "204": { + "description": "Success with no body, used by some mutation endpoints." + }, + "400": { + "description": "Client-visible validation or bad request error.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "401": { + "description": "Authentication required or token invalid.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "500": { + "description": "Server error shape tolerated by client wrappers.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + } + }, + "x-recroom-client-route-literal": "https://apps.apple.com/account/subscriptions", + "x-recroom-client-family": "misc", + "x-recroom-client-service": "api", + "x-recroom-builds": [ + "2020.12.18" + ], + "x-recroom-inferred-http-method": "get", + "x-recroom-evidence": [ + { + "type": "IOSPlatformManager", + "method": "RecRoom.Async.IPromise ShowManageSubscriptionPlatformUI()", + "file": "IOSPlatformManager.txt", + "line": 4865 + } + ], + "x-client-response-contracts": [ + { + "kind": "success-or-empty", + "raw": "RecRoom.Async.IPromise", + "rootType": "void/success", + "typeDescription": { + "name": "void/success", + "found": false, + "kind": "object", + "wireKeys": [] + } + } + ] + } + }, + "/api/catalog/v1/all": { + "post": { + "tags": [ + "misc" + ], + "summary": "POST api/catalog/v1/all?onlyAvailableSkus=true", + "description": "Inferred from Rec Room 2020 client literal `api/catalog/v1/all?onlyAvailableSkus=true`. Expected result: Generic object/array/success result inferred from client route.", + "operationId": "client_misc_api_catalog_v1_all_post", + "parameters": [ + { + "name": "onlyAvailableSkus", + "in": "query", + "required": false, + "schema": { + "type": "string" + }, + "description": "Client value template: true" + } + ], + "responses": { + "200": { + "description": "Generic object/array/success result inferred from client route.", + "content": { + "application/json": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/GenericObject" + } + } + } + } + }, + "204": { + "description": "Success with no body, used by some mutation endpoints." + }, + "400": { + "description": "Client-visible validation or bad request error.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "401": { + "description": "Authentication required or token invalid.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "500": { + "description": "Server error shape tolerated by client wrappers.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + } + }, + "x-recroom-client-route-literal": "api/catalog/v1/all?onlyAvailableSkus=true", + "x-recroom-client-family": "misc", + "x-recroom-client-service": "api", + "x-recroom-builds": [ + "2020.12.18" + ], + "x-recroom-inferred-http-method": "post", + "x-recroom-evidence": [ + { + "type": "GCBPKHFJKCE", + "method": "RecRoom.Async.IPromise`1> KABHMCDHHIP(System.Boolean INGAKMAAHKL = False)", + "file": "GCBPKHFJKCE.txt", + "line": 555 + }, + { + "type": "GCBPKHFJKCE", + "method": "RecRoom.Async.IPromise`1> KABHMCDHHIP(System.Boolean INGAKMAAHKL = False)", + "file": "GCBPKHFJKCE.txt", + "line": 577 + }, + { + "type": "GCBPKHFJKCE+<>c", + "method": "System.Void b__41_1()", + "file": "GCBPKHFJKCE_NestedType___c.txt", + "line": 791 + }, + { + "type": "GCBPKHFJKCE+<>c", + "method": "System.Void b__33_1()", + "file": "GCBPKHFJKCE_NestedType___c.txt", + "line": 246 + }, + { + "type": "GCBPKHFJKCE+<>c", + "method": "System.Void b__34_1()", + "file": "GCBPKHFJKCE_NestedType___c.txt", + "line": 332 + } + ], + "requestBody": { + "required": false, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/GenericObject" + } + }, + "application/x-www-form-urlencoded": { + "schema": { + "$ref": "#/components/schemas/GenericObject" + } + }, + "multipart/form-data": { + "schema": { + "$ref": "#/components/schemas/GenericObject" + } + } + }, + "description": "Request body inferred. Exact DTO may be named in x-recroom-evidence method signature or companion DTO inventory." + }, + "x-client-response-contracts": [ + { + "kind": "array", + "raw": "System.Collections.Generic.List`1", + "rootType": "DJGBECJHOKF", + "item": { + "kind": "object", + "raw": "GCBPKHFJKCE+DJGBECJHOKF", + "rootType": "DJGBECJHOKF" + }, + "typeDescription": { + "name": "DJGBECJHOKF", + "found": true, + "file": "GCBPKHFJKCE.cs", + "declaration": "internal class DJGBECJHOKF : IFAIJAGLDFK", + "kind": "class", + "baseType": "IFAIJAGLDFK", + "properties": [ + { + "name": "AHDFJDKLFAD", + "type": "string", + "accessibility": "public", + "kind": "property" + }, + { + "name": "AHGCOGFEEEE", + "type": "string", + "accessibility": "public", + "kind": "property" + }, + { + "name": "BEIIFHKMAEP", + "type": "string", + "accessibility": "public", + "kind": "property" + }, + { + "name": "BGBHACHJMJJ", + "type": "string", + "accessibility": "public", + "kind": "property" + }, + { + "name": "DIDDIAMJHON", + "type": "string", + "accessibility": "public", + "kind": "property" + }, + { + "name": "ELKEGFGFBCG", + "type": "string", + "accessibility": "public", + "kind": "property" + }, + { + "name": "FIKEBGGCDFN", + "type": "string", + "accessibility": "public", + "kind": "property" + }, + { + "name": "GDNNKIILCLB", + "type": "string", + "accessibility": "public", + "kind": "property" + }, + { + "name": "HFMHNNJMABK", + "type": "int", + "accessibility": "public", + "kind": "property" + }, + { + "name": "KODBEJPEFOJ", + "type": "string", + "accessibility": "public", + "kind": "property" + }, + { + "name": "LFJIIFCODHO", + "type": "bool", + "accessibility": "public", + "kind": "property" + }, + { + "name": "NPDJLOOHMDJ", + "type": "int", + "accessibility": "public", + "kind": "property" + }, + { + "name": "PBPPCMNHODC", + "type": "CACOIEJCAKJ", + "accessibility": "public", + "kind": "property" + } + ], + "fields": [], + "enumValues": [], + "wireKeys": [ + "skuId", + "name", + "description", + "imageName", + "price", + "oculusSkuId", + "psnProductLabel", + "xboxProductId", + "isSingleUse", + "data", + "appleProductId" + ], + "isilFile": "GCBPKHFJKCE_NestedType_DJGBECJHOKF.txt", + "isilType": "GCBPKHFJKCE+DJGBECJHOKF" + } + }, + { + "kind": "unknown", + "raw": "", + "rootType": "unknown", + "typeDescription": { + "name": "unknown", + "found": false, + "kind": "object", + "wireKeys": [] + } + } + ] + }, + "get": { + "tags": [ + "store" + ], + "summary": "GET api/catalog/v1/all?onlyAvailableSkus=true", + "description": "Inferred from Rec Room 2020 client literal `api/catalog/v1/all?onlyAvailableSkus=true`. Expected result: Generic object/array/success result inferred from client route.", + "operationId": "client_store_api_catalog_v1_all_get", + "parameters": [ + { + "name": "onlyAvailableSkus", + "in": "query", + "required": false, + "schema": { + "type": "string" + }, + "description": "Client value template: true" + } + ], + "responses": { + "200": { + "description": "Generic object/array/success result inferred from client route.", + "content": { + "application/json": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/StoreCatalog" + } + } + } + } + }, + "204": { + "description": "Success with no body, used by some mutation endpoints." + }, + "400": { + "description": "Client-visible validation or bad request error.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "401": { + "description": "Authentication required or token invalid.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "500": { + "description": "Server error shape tolerated by client wrappers.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + } + }, + "x-recroom-client-route-literal": "api/catalog/v1/all?onlyAvailableSkus=true", + "x-recroom-client-family": "store", + "x-recroom-client-service": "api", + "x-recroom-builds": [ + "2020.03.10" + ], + "x-recroom-inferred-http-method": "get", + "x-recroom-evidence": [ + { + "type": "stringliteral.json", + "method": "", + "file": "Recnet-archive/decompiled/Il2CppDump/stringliteral.json", + "line": null + } + ], + "x-client-response-contracts": [ + { + "kind": "array", + "raw": "System.Collections.Generic.List`1", + "rootType": "DJGBECJHOKF", + "item": { + "kind": "object", + "raw": "GCBPKHFJKCE+DJGBECJHOKF", + "rootType": "DJGBECJHOKF" + }, + "typeDescription": { + "name": "DJGBECJHOKF", + "found": true, + "file": "GCBPKHFJKCE.cs", + "declaration": "internal class DJGBECJHOKF : IFAIJAGLDFK", + "kind": "class", + "baseType": "IFAIJAGLDFK", + "properties": [ + { + "name": "AHDFJDKLFAD", + "type": "string", + "accessibility": "public", + "kind": "property" + }, + { + "name": "AHGCOGFEEEE", + "type": "string", + "accessibility": "public", + "kind": "property" + }, + { + "name": "BEIIFHKMAEP", + "type": "string", + "accessibility": "public", + "kind": "property" + }, + { + "name": "BGBHACHJMJJ", + "type": "string", + "accessibility": "public", + "kind": "property" + }, + { + "name": "DIDDIAMJHON", + "type": "string", + "accessibility": "public", + "kind": "property" + }, + { + "name": "ELKEGFGFBCG", + "type": "string", + "accessibility": "public", + "kind": "property" + }, + { + "name": "FIKEBGGCDFN", + "type": "string", + "accessibility": "public", + "kind": "property" + }, + { + "name": "GDNNKIILCLB", + "type": "string", + "accessibility": "public", + "kind": "property" + }, + { + "name": "HFMHNNJMABK", + "type": "int", + "accessibility": "public", + "kind": "property" + }, + { + "name": "KODBEJPEFOJ", + "type": "string", + "accessibility": "public", + "kind": "property" + }, + { + "name": "LFJIIFCODHO", + "type": "bool", + "accessibility": "public", + "kind": "property" + }, + { + "name": "NPDJLOOHMDJ", + "type": "int", + "accessibility": "public", + "kind": "property" + }, + { + "name": "PBPPCMNHODC", + "type": "CACOIEJCAKJ", + "accessibility": "public", + "kind": "property" + } + ], + "fields": [], + "enumValues": [], + "wireKeys": [ + "skuId", + "name", + "description", + "imageName", + "price", + "oculusSkuId", + "psnProductLabel", + "xboxProductId", + "isSingleUse", + "data", + "appleProductId" + ], + "isilFile": "GCBPKHFJKCE_NestedType_DJGBECJHOKF.txt", + "isilType": "GCBPKHFJKCE+DJGBECJHOKF" + } + }, + { + "kind": "unknown", + "raw": "", + "rootType": "unknown", + "typeDescription": { + "name": "unknown", + "found": false, + "kind": "object", + "wireKeys": [] + } + } + ] + } + }, + "/api/curatedroomplaylists": { + "get": { + "tags": [ + "misc" + ], + "summary": "GET api/curatedroomplaylists", + "description": "Inferred from Rec Room 2020 client literal `api/curatedroomplaylists`. Expected result: Generic object/array/success result inferred from client route.", + "operationId": "client_misc_api_curatedroomplaylists_get", + "parameters": [], + "responses": { + "200": { + "description": "Generic object/array/success result inferred from client route.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/GenericObject" + } + } + } + }, + "204": { + "description": "Success with no body, used by some mutation endpoints." + }, + "400": { + "description": "Client-visible validation or bad request error.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "401": { + "description": "Authentication required or token invalid.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "500": { + "description": "Server error shape tolerated by client wrappers.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + } + }, + "x-recroom-client-route-literal": "api/curatedroomplaylists", + "x-recroom-client-family": "misc", + "x-recroom-client-service": "api", + "x-recroom-builds": [ + "2020.12.18" + ], + "x-recroom-inferred-http-method": "get", + "x-recroom-evidence": [ + { + "type": "FPNPBJBCMKB", + "method": "RecRoom.Async.IPromise`1> EPPPMHMMGME()", + "file": "FPNPBJBCMKB.txt", + "line": 53 + } + ], + "x-client-response-contracts": [ + { + "kind": "array", + "raw": "System.Collections.Generic.List`1", + "rootType": "Int64", + "item": { + "kind": "primitive", + "raw": "System.Int64", + "rootType": "number" + }, + "typeDescription": { + "name": "Int64", + "found": false, + "kind": "primitive", + "wireKeys": [] + } + } + ] + } + }, + "/api/objectives/{tail}": { + "get": { + "tags": [ + "misc" + ], + "summary": "GET api/objectives/", + "description": "Inferred from Rec Room 2020 client literal `api/objectives/`. Expected result: Generic object/array/success result inferred from client route.", + "operationId": "client_misc_api_objectives_tail_get", + "parameters": [ + { + "name": "tail", + "in": "path", + "required": true, + "schema": { + "type": "string" + }, + "description": "Format placeholder from client literal api/objectives/" + } + ], + "responses": { + "200": { + "description": "Generic object/array/success result inferred from client route.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/GenericObject" + } + } + } + }, + "204": { + "description": "Success with no body, used by some mutation endpoints." + }, + "400": { + "description": "Client-visible validation or bad request error.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "401": { + "description": "Authentication required or token invalid.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "500": { + "description": "Server error shape tolerated by client wrappers.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + } + }, + "x-recroom-client-route-literal": "api/objectives/", + "x-recroom-client-family": "misc", + "x-recroom-client-service": "api", + "x-recroom-builds": [ + "2020.12.18" + ], + "x-recroom-inferred-http-method": "get", + "x-recroom-evidence": [ + { + "type": "GMPIMLJNACB", + "method": "System.Collections.IEnumerator IPCNHLDICBM(BPHGKAEDBPE+CBEOHBCIPEA AFLPGGJMPOE)", + "file": "GMPIMLJNACB.txt", + "line": 904 + }, + { + "type": "GMPIMLJNACB", + "method": "System.Void MEGMCCFCEIP(System.Int32 CKEBCMEKCJH, BPHGKAEDBPE+OJDJIJDNFHE AFLPGGJMPOE)", + "file": "GMPIMLJNACB.txt", + "line": 2171 + }, + { + "type": "GMPIMLJNACB+GIDAGFNPPLC", + "method": "System.Boolean MoveNext()", + "file": "GMPIMLJNACB_NestedType_GIDAGFNPPLC.txt", + "line": 568 + }, + { + "type": "GMPIMLJNACB+GIDAGFNPPLC", + "method": "System.Boolean MoveNext()", + "file": "GMPIMLJNACB_NestedType_GIDAGFNPPLC.txt", + "line": 623 + } + ], + "x-client-response-contracts": [ + { + "kind": "callback-or-coroutine", + "raw": "IEnumerator", + "rootType": "callback", + "typeDescription": { + "name": "callback", + "found": false, + "kind": "object", + "wireKeys": [] + } + }, + { + "kind": "unknown", + "raw": "", + "rootType": "unknown", + "typeDescription": { + "name": "unknown", + "found": false, + "kind": "object", + "wireKeys": [] + } + } + ] + }, + "post": { + "tags": [ + "objectives" + ], + "summary": "POST api/objectives/", + "description": "Inferred from Rec Room 2020 client literal `api/objectives/`. Expected result: Generic object/array/success result inferred from client route.", + "operationId": "client_objectives_api_objectives_tail_post", + "parameters": [ + { + "name": "tail", + "in": "path", + "required": true, + "schema": { + "type": "string" + }, + "description": "Format placeholder from client literal api/objectives/" + } + ], + "responses": { + "200": { + "description": "Generic object/array/success result inferred from client route.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ObjectiveList" + } + } + } + }, + "204": { + "description": "Success with no body, used by some mutation endpoints." + }, + "400": { + "description": "Client-visible validation or bad request error.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "401": { + "description": "Authentication required or token invalid.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "500": { + "description": "Server error shape tolerated by client wrappers.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + } + }, + "x-recroom-client-route-literal": "api/objectives/", + "x-recroom-client-family": "objectives", + "x-recroom-client-service": "api", + "x-recroom-builds": [ + "2020.03.10" + ], + "x-recroom-inferred-http-method": "post", + "x-recroom-evidence": [ + { + "type": "stringliteral.json", + "method": "", + "file": "Recnet-archive/decompiled/Il2CppDump/stringliteral.json", + "line": null + } + ], + "requestBody": { + "required": false, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/GenericObject" + } + }, + "application/x-www-form-urlencoded": { + "schema": { + "$ref": "#/components/schemas/GenericObject" + } + }, + "multipart/form-data": { + "schema": { + "$ref": "#/components/schemas/GenericObject" + } + } + }, + "description": "Request body inferred. Exact DTO may be named in x-recroom-evidence method signature or companion DTO inventory." + }, + "x-client-response-contracts": [ + { + "kind": "callback-or-coroutine", + "raw": "IEnumerator", + "rootType": "callback", + "typeDescription": { + "name": "callback", + "found": false, + "kind": "object", + "wireKeys": [] + } + }, + { + "kind": "unknown", + "raw": "", + "rootType": "unknown", + "typeDescription": { + "name": "unknown", + "found": false, + "kind": "object", + "wireKeys": [] + } + } + ] + } + }, + "/api/PlayerCheer/{tail}": { + "get": { + "tags": [ + "misc" + ], + "summary": "GET api/PlayerCheer/", + "description": "Inferred from Rec Room 2020 client literal `api/PlayerCheer/`. Expected result: Generic object/array/success result inferred from client route.", + "operationId": "client_misc_api_playercheer_tail_get", + "parameters": [ + { + "name": "tail", + "in": "path", + "required": true, + "schema": { + "type": "string" + }, + "description": "Format placeholder from client literal api/PlayerCheer/" + } + ], + "responses": { + "200": { + "description": "Generic object/array/success result inferred from client route.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/GenericObject" + } + } + } + }, + "204": { + "description": "Success with no body, used by some mutation endpoints." + }, + "400": { + "description": "Client-visible validation or bad request error.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "401": { + "description": "Authentication required or token invalid.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "500": { + "description": "Server error shape tolerated by client wrappers.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + } + }, + "x-recroom-client-route-literal": "api/PlayerCheer/", + "x-recroom-client-family": "misc", + "x-recroom-client-service": "api", + "x-recroom-builds": [ + "2020.03.10" + ], + "x-recroom-inferred-http-method": "get", + "x-recroom-evidence": [ + { + "type": "stringliteral.json", + "method": "", + "file": "Recnet-archive/decompiled/Il2CppDump/stringliteral.json", + "line": null + } + ] + } + }, + "/api/PlayersBanned/{tail}": { + "post": { + "tags": [ + "misc" + ], + "summary": "POST api/PlayersBanned/", + "description": "Inferred from Rec Room 2020 client literal `api/PlayersBanned/`. Expected result: Generic object/array/success result inferred from client route.", + "operationId": "client_misc_api_playersbanned_tail_post", + "parameters": [ + { + "name": "tail", + "in": "path", + "required": true, + "schema": { + "type": "string" + }, + "description": "Format placeholder from client literal api/PlayersBanned/" + } + ], + "responses": { + "200": { + "description": "Generic object/array/success result inferred from client route.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/GenericObject" + } + } + } + }, + "204": { + "description": "Success with no body, used by some mutation endpoints." + }, + "400": { + "description": "Client-visible validation or bad request error.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "401": { + "description": "Authentication required or token invalid.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "500": { + "description": "Server error shape tolerated by client wrappers.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + } + }, + "x-recroom-client-route-literal": "api/PlayersBanned/", + "x-recroom-client-family": "misc", + "x-recroom-client-service": "api", + "x-recroom-builds": [ + "2020.03.10", + "2020.12.18" + ], + "x-recroom-inferred-http-method": "post", + "x-recroom-evidence": [ + { + "type": "GGLGFENEKBJ", + "method": "System.Collections.IEnumerator BFJMPGHLGAN(System.Int64 CJFGEMGOJHB, LCCEEFHOBEN+CJFENPHAAHI MEABFEIBEMP, BPHGKAEDBPE+CBEOHBCIPEA AFLPGGJMPOE)", + "file": "GGLGFENEKBJ.txt", + "line": 586 + }, + { + "type": "stringliteral.json", + "method": "", + "file": "Recnet-archive/decompiled/Il2CppDump/stringliteral.json", + "line": null + } + ], + "requestBody": { + "required": false, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/GenericObject" + } + }, + "application/x-www-form-urlencoded": { + "schema": { + "$ref": "#/components/schemas/GenericObject" + } + }, + "multipart/form-data": { + "schema": { + "$ref": "#/components/schemas/GenericObject" + } + } + }, + "description": "Request body inferred. Exact DTO may be named in x-recroom-evidence method signature or companion DTO inventory." + }, + "x-client-response-contracts": [ + { + "kind": "callback-or-coroutine", + "raw": "IEnumerator", + "rootType": "callback", + "typeDescription": { + "name": "callback", + "found": false, + "kind": "object", + "wireKeys": [] + } + } + ] + } + }, + "/api/purchase/v1/cancelpurchase": { + "get": { + "tags": [ + "misc" + ], + "summary": "GET api/purchase/v1/cancelpurchase", + "description": "Inferred from Rec Room 2020 client literal `api/purchase/v1/cancelpurchase`. Expected result: Generic object/array/success result inferred from client route.", + "operationId": "client_misc_api_purchase_v1_cancelpurchase_get", + "parameters": [], + "responses": { + "200": { + "description": "Generic object/array/success result inferred from client route.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/GenericObject" + } + } + } + }, + "204": { + "description": "Success with no body, used by some mutation endpoints." + }, + "400": { + "description": "Client-visible validation or bad request error.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "401": { + "description": "Authentication required or token invalid.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "500": { + "description": "Server error shape tolerated by client wrappers.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + } + }, + "x-recroom-client-route-literal": "api/purchase/v1/cancelpurchase", + "x-recroom-client-family": "misc", + "x-recroom-client-service": "api", + "x-recroom-builds": [ + "2020.03.10" + ], + "x-recroom-inferred-http-method": "get", + "x-recroom-evidence": [ + { + "type": "stringliteral.json", + "method": "", + "file": "Recnet-archive/decompiled/Il2CppDump/stringliteral.json", + "line": null + } + ] + } + }, + "/api/purchase/v1/cleanuppending": { + "get": { + "tags": [ + "misc" + ], + "summary": "GET api/purchase/v1/cleanuppending", + "description": "Inferred from Rec Room 2020 client literal `api/purchase/v1/cleanuppending`. Expected result: Generic object/array/success result inferred from client route.", + "operationId": "client_misc_api_purchase_v1_cleanuppending_get", + "parameters": [], + "responses": { + "200": { + "description": "Generic object/array/success result inferred from client route.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/GenericObject" + } + } + } + }, + "204": { + "description": "Success with no body, used by some mutation endpoints." + }, + "400": { + "description": "Client-visible validation or bad request error.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "401": { + "description": "Authentication required or token invalid.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "500": { + "description": "Server error shape tolerated by client wrappers.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + } + }, + "x-recroom-client-route-literal": "api/purchase/v1/cleanuppending", + "x-recroom-client-family": "misc", + "x-recroom-client-service": "api", + "x-recroom-builds": [ + "2020.03.10" + ], + "x-recroom-inferred-http-method": "get", + "x-recroom-evidence": [ + { + "type": "stringliteral.json", + "method": "", + "file": "Recnet-archive/decompiled/Il2CppDump/stringliteral.json", + "line": null + } + ] + } + }, + "/api/purchase/v1/completepurchase": { + "get": { + "tags": [ + "misc" + ], + "summary": "GET api/purchase/v1/completepurchase", + "description": "Inferred from Rec Room 2020 client literal `api/purchase/v1/completepurchase`. Expected result: Generic object/array/success result inferred from client route.", + "operationId": "client_misc_api_purchase_v1_completepurchase_get", + "parameters": [], + "responses": { + "200": { + "description": "Generic object/array/success result inferred from client route.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/GenericObject" + } + } + } + }, + "204": { + "description": "Success with no body, used by some mutation endpoints." + }, + "400": { + "description": "Client-visible validation or bad request error.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "401": { + "description": "Authentication required or token invalid.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "500": { + "description": "Server error shape tolerated by client wrappers.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + } + }, + "x-recroom-client-route-literal": "api/purchase/v1/completepurchase", + "x-recroom-client-family": "misc", + "x-recroom-client-service": "api", + "x-recroom-builds": [ + "2020.03.10" + ], + "x-recroom-inferred-http-method": "get", + "x-recroom-evidence": [ + { + "type": "stringliteral.json", + "method": "", + "file": "Recnet-archive/decompiled/Il2CppDump/stringliteral.json", + "line": null + } + ] + } + }, + "/api/purchase/v1/initiatepurchase": { + "get": { + "tags": [ + "misc" + ], + "summary": "GET api/purchase/v1/initiatepurchase", + "description": "Inferred from Rec Room 2020 client literal `api/purchase/v1/initiatepurchase`. Expected result: Generic object/array/success result inferred from client route.", + "operationId": "client_misc_api_purchase_v1_initiatepurchase_get", + "parameters": [], + "responses": { + "200": { + "description": "Generic object/array/success result inferred from client route.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/GenericObject" + } + } + } + }, + "204": { + "description": "Success with no body, used by some mutation endpoints." + }, + "400": { + "description": "Client-visible validation or bad request error.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "401": { + "description": "Authentication required or token invalid.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "500": { + "description": "Server error shape tolerated by client wrappers.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + } + }, + "x-recroom-client-route-literal": "api/purchase/v1/initiatepurchase", + "x-recroom-client-family": "misc", + "x-recroom-client-service": "api", + "x-recroom-builds": [ + "2020.03.10" + ], + "x-recroom-inferred-http-method": "get", + "x-recroom-evidence": [ + { + "type": "stringliteral.json", + "method": "", + "file": "Recnet-archive/decompiled/Il2CppDump/stringliteral.json", + "line": null + } + ] + } + }, + "/api/purchase/v1/processpurchase": { + "get": { + "tags": [ + "misc" + ], + "summary": "GET api/purchase/v1/processpurchase", + "description": "Inferred from Rec Room 2020 client literal `api/purchase/v1/processpurchase`. Expected result: Generic object/array/success result inferred from client route.", + "operationId": "client_misc_api_purchase_v1_processpurchase_get", + "parameters": [], + "responses": { + "200": { + "description": "Generic object/array/success result inferred from client route.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/GenericObject" + } + } + } + }, + "204": { + "description": "Success with no body, used by some mutation endpoints." + }, + "400": { + "description": "Client-visible validation or bad request error.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "401": { + "description": "Authentication required or token invalid.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "500": { + "description": "Server error shape tolerated by client wrappers.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + } + }, + "x-recroom-client-route-literal": "api/purchase/v1/processpurchase", + "x-recroom-client-family": "misc", + "x-recroom-client-service": "api", + "x-recroom-builds": [ + "2020.03.10" + ], + "x-recroom-inferred-http-method": "get", + "x-recroom-evidence": [ + { + "type": "stringliteral.json", + "method": "", + "file": "Recnet-archive/decompiled/Il2CppDump/stringliteral.json", + "line": null + } + ] + } + }, + "/api/relationships/{tail}": { + "get": { + "tags": [ + "misc" + ], + "summary": "GET api/relationships/", + "description": "Inferred from Rec Room 2020 client literal `api/relationships/`. Expected result: Generic object/array/success result inferred from client route.", + "operationId": "client_misc_api_relationships_tail_get", + "parameters": [ + { + "name": "tail", + "in": "path", + "required": true, + "schema": { + "type": "string" + }, + "description": "Format placeholder from client literal api/relationships/" + } + ], + "responses": { + "200": { + "description": "Generic object/array/success result inferred from client route.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/GenericObject" + } + } + } + }, + "204": { + "description": "Success with no body, used by some mutation endpoints." + }, + "400": { + "description": "Client-visible validation or bad request error.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "401": { + "description": "Authentication required or token invalid.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "500": { + "description": "Server error shape tolerated by client wrappers.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + } + }, + "x-recroom-client-route-literal": "api/relationships/", + "x-recroom-client-family": "misc", + "x-recroom-client-service": "api", + "x-recroom-builds": [ + "2020.12.18" + ], + "x-recroom-inferred-http-method": "get", + "x-recroom-evidence": [ + { + "type": "FGPIDGLCKEF", + "method": "RecRoom.Async.IPromise`1 CGPBLDOHCEI(System.Int32 CJFGEMGOJHB, CFLMJLFBOKH+MPFKNCFEDHF DIEPOPNJNCO)", + "file": "FGPIDGLCKEF.txt", + "line": 1454 + }, + { + "type": "FGPIDGLCKEF", + "method": "RecRoom.Async.IPromise`1 DLBAPGPACLP(System.Int32 CJFGEMGOJHB)", + "file": "FGPIDGLCKEF.txt", + "line": 1827 + }, + { + "type": "FGPIDGLCKEF", + "method": "RecRoom.Async.IPromise`1 DOIEMGNIMMM(System.Int32 CJFGEMGOJHB, BPHGKAEDBPE+CBEOHBCIPEA AFLPGGJMPOE = null)", + "file": "FGPIDGLCKEF.txt", + "line": 1197 + }, + { + "type": "FGPIDGLCKEF", + "method": "RecRoom.Async.IPromise`1 JEGAGBBBADJ(System.Int32 CJFGEMGOJHB)", + "file": "FGPIDGLCKEF.txt", + "line": 1591 + }, + { + "type": "FGPIDGLCKEF", + "method": "RecRoom.Async.IPromise`1 OILLHGFDILD(System.Int32 CJFGEMGOJHB)", + "file": "FGPIDGLCKEF.txt", + "line": 1709 + }, + { + "type": "FGPIDGLCKEF", + "method": "System.Boolean CKANLFMOMCC(System.Int32 CJFGEMGOJHB, BPHGKAEDBPE+CBEOHBCIPEA AFLPGGJMPOE = null)", + "file": "FGPIDGLCKEF.txt", + "line": 2141 + }, + { + "type": "FGPIDGLCKEF", + "method": "System.Boolean GDOLAFPLNPG(System.Int32 CJFGEMGOJHB, BPHGKAEDBPE+CBEOHBCIPEA AFLPGGJMPOE = null)", + "file": "FGPIDGLCKEF.txt", + "line": 1982 + }, + { + "type": "FGPIDGLCKEF", + "method": "System.Void CPGAJIACKIP()", + "file": "FGPIDGLCKEF.txt", + "line": 3002 + }, + { + "type": "FGPIDGLCKEF", + "method": "System.Void IMLJEBCMMKK(PlatformManager+FCIKKFJOMNO FPMCLJDEGKL, System.Collections.Generic.List`1 CIBFMCPBLEO)", + "file": "FGPIDGLCKEF.txt", + "line": 2832 + }, + { + "type": "FGPIDGLCKEF", + "method": "System.Void KDKALCLMLIL(System.String GJIOCANDGPE, System.Int32 CJFGEMGOJHB, BPHGKAEDBPE+CBEOHBCIPEA AFLPGGJMPOE = null)", + "file": "FGPIDGLCKEF.txt", + "line": 2670 + } + ], + "x-client-response-contracts": [ + { + "kind": "object", + "raw": "LLKHFJDNFMM", + "rootType": "LLKHFJDNFMM", + "typeDescription": { + "name": "LLKHFJDNFMM", + "found": true, + "file": "LLKHFJDNFMM.cs", + "declaration": "public class LLKHFJDNFMM : IFAIJAGLDFK", + "kind": "class", + "baseType": "IFAIJAGLDFK", + "properties": [ + { + "name": "CJEPCMDFKNF", + "type": "enum", + "accessibility": "internal", + "kind": "property" + }, + { + "name": "DDAADAMFMCL", + "type": "enum", + "accessibility": "internal", + "kind": "property" + }, + { + "name": "COJNNAGMBJF", + "type": "int", + "accessibility": "public", + "kind": "property" + }, + { + "name": "CPAAFLNLLMP", + "type": "bool", + "accessibility": "public", + "kind": "property" + }, + { + "name": "GCGHEMGJCJG", + "type": "CJEPCMDFKNF", + "accessibility": "public", + "kind": "property" + }, + { + "name": "HHFLKFOEHNL", + "type": "CJEPCMDFKNF", + "accessibility": "public", + "kind": "property" + }, + { + "name": "LHMMBEPNODC", + "type": "CJEPCMDFKNF", + "accessibility": "public", + "kind": "property" + }, + { + "name": "MHEGHKNGOEG", + "type": "bool", + "accessibility": "public", + "kind": "property" + }, + { + "name": "NPBCMKLOCAE", + "type": "bool", + "accessibility": "public", + "kind": "property" + }, + { + "name": "OPLHMKFCNOL", + "type": "DDAADAMFMCL", + "accessibility": "public", + "kind": "property" + }, + { + "name": "PLJNOOENNDP", + "type": "bool", + "accessibility": "public", + "kind": "property" + } + ], + "fields": [], + "enumValues": [], + "wireKeys": [ + "PlayerID", + "RelationshipType", + "Muted", + "Ignored", + "Favorited" + ], + "isilFile": "LLKHFJDNFMM.txt", + "isilType": "LLKHFJDNFMM" + } + }, + { + "kind": "unknown", + "raw": "", + "rootType": "unknown", + "typeDescription": { + "name": "unknown", + "found": false, + "kind": "object", + "wireKeys": [] + } + } + ] + }, + "post": { + "tags": [ + "relationships" + ], + "summary": "POST api/relationships/", + "description": "Inferred from Rec Room 2020 client literal `api/relationships/`. Expected result: Generic object/array/success result inferred from client route.", + "operationId": "client_relationships_api_relationships_tail_post", + "parameters": [ + { + "name": "tail", + "in": "path", + "required": true, + "schema": { + "type": "string" + }, + "description": "Format placeholder from client literal api/relationships/" + } + ], + "responses": { + "200": { + "description": "Generic object/array/success result inferred from client route.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RelationshipState" + } + } + } + }, + "204": { + "description": "Success with no body, used by some mutation endpoints." + }, + "400": { + "description": "Client-visible validation or bad request error.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "401": { + "description": "Authentication required or token invalid.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "500": { + "description": "Server error shape tolerated by client wrappers.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + } + }, + "x-recroom-client-route-literal": "api/relationships/", + "x-recroom-client-family": "relationships", + "x-recroom-client-service": "api", + "x-recroom-builds": [ + "2020.03.10" + ], + "x-recroom-inferred-http-method": "post", + "x-recroom-evidence": [ + { + "type": "stringliteral.json", + "method": "", + "file": "Recnet-archive/decompiled/Il2CppDump/stringliteral.json", + "line": null + } + ], + "requestBody": { + "required": false, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/GenericObject" + } + }, + "application/x-www-form-urlencoded": { + "schema": { + "$ref": "#/components/schemas/GenericObject" + } + }, + "multipart/form-data": { + "schema": { + "$ref": "#/components/schemas/GenericObject" + } + } + }, + "description": "Request body inferred. Exact DTO may be named in x-recroom-evidence method signature or companion DTO inventory." + }, + "x-client-response-contracts": [ + { + "kind": "object", + "raw": "LLKHFJDNFMM", + "rootType": "LLKHFJDNFMM", + "typeDescription": { + "name": "LLKHFJDNFMM", + "found": true, + "file": "LLKHFJDNFMM.cs", + "declaration": "public class LLKHFJDNFMM : IFAIJAGLDFK", + "kind": "class", + "baseType": "IFAIJAGLDFK", + "properties": [ + { + "name": "CJEPCMDFKNF", + "type": "enum", + "accessibility": "internal", + "kind": "property" + }, + { + "name": "DDAADAMFMCL", + "type": "enum", + "accessibility": "internal", + "kind": "property" + }, + { + "name": "COJNNAGMBJF", + "type": "int", + "accessibility": "public", + "kind": "property" + }, + { + "name": "CPAAFLNLLMP", + "type": "bool", + "accessibility": "public", + "kind": "property" + }, + { + "name": "GCGHEMGJCJG", + "type": "CJEPCMDFKNF", + "accessibility": "public", + "kind": "property" + }, + { + "name": "HHFLKFOEHNL", + "type": "CJEPCMDFKNF", + "accessibility": "public", + "kind": "property" + }, + { + "name": "LHMMBEPNODC", + "type": "CJEPCMDFKNF", + "accessibility": "public", + "kind": "property" + }, + { + "name": "MHEGHKNGOEG", + "type": "bool", + "accessibility": "public", + "kind": "property" + }, + { + "name": "NPBCMKLOCAE", + "type": "bool", + "accessibility": "public", + "kind": "property" + }, + { + "name": "OPLHMKFCNOL", + "type": "DDAADAMFMCL", + "accessibility": "public", + "kind": "property" + }, + { + "name": "PLJNOOENNDP", + "type": "bool", + "accessibility": "public", + "kind": "property" + } + ], + "fields": [], + "enumValues": [], + "wireKeys": [ + "PlayerID", + "RelationshipType", + "Muted", + "Ignored", + "Favorited" + ], + "isilFile": "LLKHFJDNFMM.txt", + "isilType": "LLKHFJDNFMM" + } + }, + { + "kind": "unknown", + "raw": "", + "rootType": "unknown", + "typeDescription": { + "name": "unknown", + "found": false, + "kind": "object", + "wireKeys": [] + } + } + ] + } + }, + "/api/sanitize/{tail}": { + "get": { + "tags": [ + "misc" + ], + "summary": "GET api/sanitize/", + "description": "Inferred from Rec Room 2020 client literal `api/sanitize/`. Expected result: Generic object/array/success result inferred from client route.", + "operationId": "client_misc_api_sanitize_tail_get", + "parameters": [ + { + "name": "tail", + "in": "path", + "required": true, + "schema": { + "type": "string" + }, + "description": "Format placeholder from client literal api/sanitize/" + } + ], + "responses": { + "200": { + "description": "Generic object/array/success result inferred from client route.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/GenericObject" + } + } + } + }, + "204": { + "description": "Success with no body, used by some mutation endpoints." + }, + "400": { + "description": "Client-visible validation or bad request error.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "401": { + "description": "Authentication required or token invalid.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "500": { + "description": "Server error shape tolerated by client wrappers.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + } + }, + "x-recroom-client-route-literal": "api/sanitize/", + "x-recroom-client-family": "misc", + "x-recroom-client-service": "api", + "x-recroom-builds": [ + "2020.12.18" + ], + "x-recroom-inferred-http-method": "get", + "x-recroom-evidence": [ + { + "type": "NKHBKKGOIHL+BBGMJPMOHEO", + "method": "System.Boolean MoveNext()", + "file": "NKHBKKGOIHL_NestedType_BBGMJPMOHEO.txt", + "line": 353 + } + ], + "x-client-response-contracts": [ + { + "kind": "unknown", + "raw": "", + "rootType": "unknown", + "typeDescription": { + "name": "unknown", + "found": false, + "kind": "object", + "wireKeys": [] + } + } + ] + }, + "post": { + "tags": [ + "sanitize" + ], + "summary": "POST api/sanitize/", + "description": "Inferred from Rec Room 2020 client literal `api/sanitize/`. Expected result: Generic object/array/success result inferred from client route.", + "operationId": "client_sanitize_api_sanitize_tail_post", + "parameters": [ + { + "name": "tail", + "in": "path", + "required": true, + "schema": { + "type": "string" + }, + "description": "Format placeholder from client literal api/sanitize/" + } + ], + "responses": { + "200": { + "description": "Generic object/array/success result inferred from client route.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/SanitizeResult" + } + } + } + }, + "204": { + "description": "Success with no body, used by some mutation endpoints." + }, + "400": { + "description": "Client-visible validation or bad request error.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "401": { + "description": "Authentication required or token invalid.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "500": { + "description": "Server error shape tolerated by client wrappers.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + } + }, + "x-recroom-client-route-literal": "api/sanitize/", + "x-recroom-client-family": "sanitize", + "x-recroom-client-service": "api", + "x-recroom-builds": [ + "2020.03.10" + ], + "x-recroom-inferred-http-method": "post", + "x-recroom-evidence": [ + { + "type": "stringliteral.json", + "method": "", + "file": "Recnet-archive/decompiled/Il2CppDump/stringliteral.json", + "line": null + } + ], + "requestBody": { + "required": false, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/GenericObject" + } + }, + "application/x-www-form-urlencoded": { + "schema": { + "$ref": "#/components/schemas/GenericObject" + } + }, + "multipart/form-data": { + "schema": { + "$ref": "#/components/schemas/GenericObject" + } + } + }, + "description": "Request body inferred. Exact DTO may be named in x-recroom-evidence method signature or companion DTO inventory." + }, + "x-client-response-contracts": [ + { + "kind": "unknown", + "raw": "", + "rootType": "unknown", + "typeDescription": { + "name": "unknown", + "found": false, + "kind": "object", + "wireKeys": [] + } + } + ] + } + }, + "/api/storefronts/{tail}": { + "get": { + "tags": [ + "misc" + ], + "summary": "GET api/storefronts/", + "description": "Inferred from Rec Room 2020 client literal `api/storefronts/`. Expected result: Generic object/array/success result inferred from client route.", + "operationId": "client_misc_api_storefronts_tail_get", + "parameters": [ + { + "name": "tail", + "in": "path", + "required": true, + "schema": { + "type": "string" + }, + "description": "Format placeholder from client literal api/storefronts/" + } + ], + "responses": { + "200": { + "description": "Generic object/array/success result inferred from client route.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/GenericObject" + } + } + } + }, + "204": { + "description": "Success with no body, used by some mutation endpoints." + }, + "400": { + "description": "Client-visible validation or bad request error.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "401": { + "description": "Authentication required or token invalid.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "500": { + "description": "Server error shape tolerated by client wrappers.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + } + }, + "x-recroom-client-route-literal": "api/storefronts/", + "x-recroom-client-family": "misc", + "x-recroom-client-service": "api", + "x-recroom-builds": [ + "2020.12.18" + ], + "x-recroom-inferred-http-method": "get", + "x-recroom-evidence": [ + { + "type": "GEAPBDGCKMB", + "method": "RecRoom.Async.IPromise ACEPPELPFOL(System.Int32 PGGHBNCGLDN, System.Nullable`1 POAOENHPNHE, System.Int32 MNIJHBJDPPA, System.Int32 HLHHPEKALPI)", + "file": "GEAPBDGCKMB.txt", + "line": 2977 + }, + { + "type": "GEAPBDGCKMB", + "method": "RecRoom.Async.IPromise`1> LLNACOGDCMF(ACDKILABNNC DLNFAILEHOA, System.Collections.Generic.IEnumerable`1 JEDEBGBEGCE)", + "file": "GEAPBDGCKMB.txt", + "line": 5929 + }, + { + "type": "GEAPBDGCKMB", + "method": "RecRoom.Async.IPromise`1 MOANDFACFAP(System.Int64 OEMDIAHHILF, System.Int32 HBIEIIHBDCI, System.String LGHBPPIOOBM)", + "file": "GEAPBDGCKMB.txt", + "line": 3603 + }, + { + "type": "GEAPBDGCKMB", + "method": "RecRoom.Async.IPromise`1 KGGMAGALNGF(System.Int64 BGICHOOBKLD, System.Int32 HBIEIIHBDCI)", + "file": "GEAPBDGCKMB.txt", + "line": 3819 + }, + { + "type": "GEAPBDGCKMB", + "method": "RecRoom.Async.IPromise`1 MMINJDALHNB(System.Int64 OEMDIAHHILF, System.String LGHBPPIOOBM)", + "file": "GEAPBDGCKMB.txt", + "line": 4052 + }, + { + "type": "GEAPBDGCKMB", + "method": "System.Collections.IEnumerator EKLFEDLCNAC(DGOHCPBKOHD NIGCECGDCFD, ACDKILABNNC DLNFAILEHOA, System.Int32 PGGHBNCGLDN, BPHGKAEDBPE+OJDJIJDNFHE> AFLPGGJMPOE)", + "file": "GEAPBDGCKMB.txt", + "line": 3402 + }, + { + "type": "GEAPBDGCKMB", + "method": "System.Collections.IEnumerator EPCDDOGBHMF(DGOHCPBKOHD NIGCECGDCFD, ACDKILABNNC DLNFAILEHOA, System.Int32 PGGHBNCGLDN, System.Nullable`1 POAOENHPNHE, GEAPBDGCKMB+GiftItemDTO HBBAANIPIMP, BPHGKAEDBPE+OJDJIJDNFHE> AFLPGGJMPOE)", + "file": "GEAPBDGCKMB.txt", + "line": 2709 + }, + { + "type": "GEAPBDGCKMB", + "method": "System.Collections.IEnumerator GDOGAIAHEHB(DGOHCPBKOHD NIGCECGDCFD, ACDKILABNNC DLNFAILEHOA, System.Int32 PGGHBNCGLDN, BPHGKAEDBPE+OJDJIJDNFHE> AFLPGGJMPOE)", + "file": "GEAPBDGCKMB.txt", + "line": 3241 + }, + { + "type": "GEAPBDGCKMB", + "method": "System.Collections.IEnumerator IMFAFAKABHC(DGOHCPBKOHD NIGCECGDCFD, BPHGKAEDBPE+OJDJIJDNFHE AFLPGGJMPOE)", + "file": "GEAPBDGCKMB.txt", + "line": 1914 + }, + { + "type": "GEAPBDGCKMB", + "method": "System.Collections.IEnumerator LBEMMLOFBEA(ACDKILABNNC DLNFAILEHOA, BPHGKAEDBPE+OJDJIJDNFHE AFLPGGJMPOE = null)", + "file": "GEAPBDGCKMB.txt", + "line": 5640 + } + ], + "x-client-response-contracts": [ + { + "kind": "success-or-empty", + "raw": "RecRoom.Async.IPromise", + "rootType": "void/success", + "typeDescription": { + "name": "void/success", + "found": false, + "kind": "object", + "wireKeys": [] + } + }, + { + "kind": "object", + "raw": "GEAPBDGCKMB+BalanceUpdateResponseDTO`1", + "rootType": "RewardBalanceModificationDTO>", + "typeDescription": { + "name": "RewardBalanceModificationDTO>", + "found": false, + "kind": "object", + "wireKeys": [] + } + }, + { + "kind": "object", + "raw": "GEAPBDGCKMB+InventionPurchaseResponseDTO", + "rootType": "InventionPurchaseResponseDTO", + "typeDescription": { + "name": "InventionPurchaseResponseDTO", + "found": true, + "file": "GEAPBDGCKMB.cs", + "declaration": "internal class InventionPurchaseResponseDTO : IFAIJAGLDFK", + "kind": "class", + "baseType": "IFAIJAGLDFK", + "properties": [ + { + "name": "BalanceResponseDTO", + "type": "class FHLJACIDOHO :", + "accessibility": "internal", + "kind": "property" + }, + { + "name": "IFAIJAGLDFK", + "type": "class GFAPKCFGKLB :", + "accessibility": "internal", + "kind": "property" + }, + { + "name": "NKPDMHMBNEJ", + "type": "KGAACCELMHP", + "accessibility": "public", + "kind": "property" + }, + { + "name": "PBPPCMNHODC", + "type": "OBBBPCBIMME", + "accessibility": "public", + "kind": "property" + }, + { + "name": "LPDGDNGOLFM", + "type": "List", + "accessibility": "public", + "kind": "property" + }, + { + "name": "BalanceUpdateResponse", + "type": "FHLJACIDOHO", + "accessibility": "public", + "kind": "property" + }, + { + "name": "InventionResponse", + "type": "AHEPPAEOLOD", + "accessibility": "public", + "kind": "property" + } + ], + "fields": [], + "enumValues": [], + "wireKeys": [ + "InventionResponse", + "BalanceUpdateResponse" + ], + "isilFile": "GEAPBDGCKMB_NestedType_InventionPurchaseResponseDTO.txt", + "isilType": "GEAPBDGCKMB+InventionPurchaseResponseDTO" + } + }, + { + "kind": "object", + "raw": "GEAPBDGCKMB+RoomKeyPurchaseResponseDTO", + "rootType": "RoomKeyPurchaseResponseDTO", + "typeDescription": { + "name": "RoomKeyPurchaseResponseDTO", + "found": true, + "file": "GEAPBDGCKMB.cs", + "declaration": "internal class RoomKeyPurchaseResponseDTO : IFAIJAGLDFK", + "kind": "class", + "baseType": "IFAIJAGLDFK", + "properties": [ + { + "name": "BalanceResponseDTO", + "type": "class NAHADLNOINN :", + "accessibility": "internal", + "kind": "property" + }, + { + "name": "IFAIJAGLDFK", + "type": "class ELKBABHKJHC :", + "accessibility": "internal", + "kind": "property" + }, + { + "name": "NKPDMHMBNEJ", + "type": "KGAACCELMHP", + "accessibility": "public", + "kind": "property" + }, + { + "name": "PBPPCMNHODC", + "type": "AMAGKLLBGEC", + "accessibility": "public", + "kind": "property" + }, + { + "name": "LPDGDNGOLFM", + "type": "List", + "accessibility": "public", + "kind": "property" + }, + { + "name": "BalanceUpdateResponse", + "type": "NAHADLNOINN", + "accessibility": "public", + "kind": "property" + }, + { + "name": "RoomKeyResponse", + "type": "BCKIBFNPIPD", + "accessibility": "public", + "kind": "property" + } + ], + "fields": [], + "enumValues": [], + "wireKeys": [ + "RoomKeyResponse", + "BalanceUpdateResponse" + ], + "isilFile": "GEAPBDGCKMB_NestedType_RoomKeyPurchaseResponseDTO.txt", + "isilType": "GEAPBDGCKMB+RoomKeyPurchaseResponseDTO" + } + }, + { + "kind": "object", + "raw": "OBBBPCBIMME", + "rootType": "OBBBPCBIMME", + "typeDescription": { + "name": "OBBBPCBIMME", + "found": true, + "file": "OBBBPCBIMME.cs", + "declaration": "public class OBBBPCBIMME : IFAIJAGLDFK", + "kind": "class", + "baseType": "IFAIJAGLDFK", + "properties": [ + { + "name": "ACBFDMLHFPB", + "type": "DateTime", + "accessibility": "public", + "kind": "property" + }, + { + "name": "ACHMMBMLGEK", + "type": "int", + "accessibility": "public", + "kind": "property" + }, + { + "name": "AHGCOGFEEEE", + "type": "string", + "accessibility": "public", + "kind": "property" + }, + { + "name": "AJHBGBHGGAL", + "type": "long", + "accessibility": "public", + "kind": "property" + }, + { + "name": "BAELCDIJIPJ", + "type": "HECIICKPCDN", + "accessibility": "public", + "kind": "property" + }, + { + "name": "DKHJPGMEOHF", + "type": "int", + "accessibility": "public", + "kind": "property" + }, + { + "name": "FIKEBGGCDFN", + "type": "string", + "accessibility": "public", + "kind": "property" + }, + { + "name": "GCAABGAEDPP", + "type": "bool", + "accessibility": "public", + "kind": "property" + }, + { + "name": "HKFOKCNGNFA", + "type": "int", + "accessibility": "public", + "kind": "property" + }, + { + "name": "ILKLEFCCJMN", + "type": "bool", + "accessibility": "public", + "kind": "property" + }, + { + "name": "JHJNKJHLJBJ", + "type": "int", + "accessibility": "public", + "kind": "property" + }, + { + "name": "JOHEKKGMAJB", + "type": "int", + "accessibility": "public", + "kind": "property" + }, + { + "name": "KBDMNJGJACC", + "type": "DateTime", + "accessibility": "public", + "kind": "property" + }, + { + "name": "KLLMGMJJICK", + "type": "Nullable", + "accessibility": "public", + "kind": "property" + }, + { + "name": "KODBEJPEFOJ", + "type": "string", + "accessibility": "public", + "kind": "property" + }, + { + "name": "MLDAFKCENMA", + "type": "string", + "accessibility": "public", + "kind": "property" + }, + { + "name": "MLMDFDKLELH", + "type": "HECIICKPCDN", + "accessibility": "public", + "kind": "property" + }, + { + "name": "MMPKOCENIIP", + "type": "bool", + "accessibility": "public", + "kind": "property" + }, + { + "name": "NFNIPMIEMPM", + "type": "bool", + "accessibility": "public", + "kind": "property" + }, + { + "name": "NPDJLOOHMDJ", + "type": "Nullable", + "accessibility": "public", + "kind": "property" + }, + { + "name": "PJNJIKKCBNA", + "type": "Nullable", + "accessibility": "public", + "kind": "property" + } + ], + "fields": [], + "enumValues": [], + "wireKeys": [ + "InventionId", + "ReplicationId", + "CreatorPlayerId", + "Name", + "Description", + "ImageName", + "CurrentVersionNumber", + "IsPublished", + "AllowTrial", + "ModifiedAt", + "CreatedAt", + "NumPlayersHaveUsedInRoom", + "NumDownloads", + "CheerCount", + "CreatorPermission", + "GeneralPermission", + "IsAGInvention", + "Price", + "HideFromPlayer" + ], + "isilFile": "OBBBPCBIMME.txt", + "isilType": "OBBBPCBIMME" + } + }, + { + "kind": "callback-or-coroutine", + "raw": "IEnumerator", + "rootType": "callback", + "typeDescription": { + "name": "callback", + "found": false, + "kind": "object", + "wireKeys": [] + } + }, + { + "kind": "unknown", + "raw": "", + "rootType": "unknown", + "typeDescription": { + "name": "unknown", + "found": false, + "kind": "object", + "wireKeys": [] + } + }, + { + "kind": "object", + "raw": "BKFGFHDDNFG", + "rootType": "BKFGFHDDNFG", + "typeDescription": { + "name": "BKFGFHDDNFG", + "found": true, + "file": "BKFGFHDDNFG.cs", + "declaration": "public class BKFGFHDDNFG : DOPJPOEFDCN", + "kind": "class", + "baseType": "DOPJPOEFDCN", + "properties": [], + "fields": [ + { + "name": "JDGEBBMDJBC", + "type": "List", + "kind": "field" + } + ], + "enumValues": [], + "wireKeys": [], + "isilFile": "BKFGFHDDNFG.txt", + "isilType": "BKFGFHDDNFG", + "inherits": "DOPJPOEFDCN", + "inheritedMembers": [ + { + "name": "BFCBMGOPKFH", + "type": "Nullable", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "DOPJPOEFDCN" + }, + { + "name": "GHHJBILFKFB", + "type": "DateTime", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "DOPJPOEFDCN" + }, + { + "name": "GHOKCIMBEMM", + "type": "DGOHCPBKOHD", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "DOPJPOEFDCN" + } + ], + "inheritedWireKeys": [ + "StorefrontType", + "NextUpdate" + ] + } + } + ] + }, + "post": { + "tags": [ + "store" + ], + "summary": "POST api/storefronts/", + "description": "Inferred from Rec Room 2020 client literal `api/storefronts/`. Expected result: Generic object/array/success result inferred from client route.", + "operationId": "client_store_api_storefronts_tail_post", + "parameters": [ + { + "name": "tail", + "in": "path", + "required": true, + "schema": { + "type": "string" + }, + "description": "Format placeholder from client literal api/storefronts/" + } + ], + "responses": { + "200": { + "description": "Generic object/array/success result inferred from client route.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/StoreCatalog" + } + } + } + }, + "204": { + "description": "Success with no body, used by some mutation endpoints." + }, + "400": { + "description": "Client-visible validation or bad request error.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "401": { + "description": "Authentication required or token invalid.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "500": { + "description": "Server error shape tolerated by client wrappers.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + } + }, + "x-recroom-client-route-literal": "api/storefronts/", + "x-recroom-client-family": "store", + "x-recroom-client-service": "api", + "x-recroom-builds": [ + "2020.03.10" + ], + "x-recroom-inferred-http-method": "post", + "x-recroom-evidence": [ + { + "type": "stringliteral.json", + "method": "", + "file": "Recnet-archive/decompiled/Il2CppDump/stringliteral.json", + "line": null + } + ], + "requestBody": { + "required": false, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/GenericObject" + } + }, + "application/x-www-form-urlencoded": { + "schema": { + "$ref": "#/components/schemas/GenericObject" + } + }, + "multipart/form-data": { + "schema": { + "$ref": "#/components/schemas/GenericObject" + } + } + }, + "description": "Request body inferred. Exact DTO may be named in x-recroom-evidence method signature or companion DTO inventory." + }, + "x-client-response-contracts": [ + { + "kind": "success-or-empty", + "raw": "RecRoom.Async.IPromise", + "rootType": "void/success", + "typeDescription": { + "name": "void/success", + "found": false, + "kind": "object", + "wireKeys": [] + } + }, + { + "kind": "object", + "raw": "GEAPBDGCKMB+BalanceUpdateResponseDTO`1", + "rootType": "RewardBalanceModificationDTO>", + "typeDescription": { + "name": "RewardBalanceModificationDTO>", + "found": false, + "kind": "object", + "wireKeys": [] + } + }, + { + "kind": "object", + "raw": "GEAPBDGCKMB+InventionPurchaseResponseDTO", + "rootType": "InventionPurchaseResponseDTO", + "typeDescription": { + "name": "InventionPurchaseResponseDTO", + "found": true, + "file": "GEAPBDGCKMB.cs", + "declaration": "internal class InventionPurchaseResponseDTO : IFAIJAGLDFK", + "kind": "class", + "baseType": "IFAIJAGLDFK", + "properties": [ + { + "name": "BalanceResponseDTO", + "type": "class FHLJACIDOHO :", + "accessibility": "internal", + "kind": "property" + }, + { + "name": "IFAIJAGLDFK", + "type": "class GFAPKCFGKLB :", + "accessibility": "internal", + "kind": "property" + }, + { + "name": "NKPDMHMBNEJ", + "type": "KGAACCELMHP", + "accessibility": "public", + "kind": "property" + }, + { + "name": "PBPPCMNHODC", + "type": "OBBBPCBIMME", + "accessibility": "public", + "kind": "property" + }, + { + "name": "LPDGDNGOLFM", + "type": "List", + "accessibility": "public", + "kind": "property" + }, + { + "name": "BalanceUpdateResponse", + "type": "FHLJACIDOHO", + "accessibility": "public", + "kind": "property" + }, + { + "name": "InventionResponse", + "type": "AHEPPAEOLOD", + "accessibility": "public", + "kind": "property" + } + ], + "fields": [], + "enumValues": [], + "wireKeys": [ + "InventionResponse", + "BalanceUpdateResponse" + ], + "isilFile": "GEAPBDGCKMB_NestedType_InventionPurchaseResponseDTO.txt", + "isilType": "GEAPBDGCKMB+InventionPurchaseResponseDTO" + } + }, + { + "kind": "object", + "raw": "GEAPBDGCKMB+RoomKeyPurchaseResponseDTO", + "rootType": "RoomKeyPurchaseResponseDTO", + "typeDescription": { + "name": "RoomKeyPurchaseResponseDTO", + "found": true, + "file": "GEAPBDGCKMB.cs", + "declaration": "internal class RoomKeyPurchaseResponseDTO : IFAIJAGLDFK", + "kind": "class", + "baseType": "IFAIJAGLDFK", + "properties": [ + { + "name": "BalanceResponseDTO", + "type": "class NAHADLNOINN :", + "accessibility": "internal", + "kind": "property" + }, + { + "name": "IFAIJAGLDFK", + "type": "class ELKBABHKJHC :", + "accessibility": "internal", + "kind": "property" + }, + { + "name": "NKPDMHMBNEJ", + "type": "KGAACCELMHP", + "accessibility": "public", + "kind": "property" + }, + { + "name": "PBPPCMNHODC", + "type": "AMAGKLLBGEC", + "accessibility": "public", + "kind": "property" + }, + { + "name": "LPDGDNGOLFM", + "type": "List", + "accessibility": "public", + "kind": "property" + }, + { + "name": "BalanceUpdateResponse", + "type": "NAHADLNOINN", + "accessibility": "public", + "kind": "property" + }, + { + "name": "RoomKeyResponse", + "type": "BCKIBFNPIPD", + "accessibility": "public", + "kind": "property" + } + ], + "fields": [], + "enumValues": [], + "wireKeys": [ + "RoomKeyResponse", + "BalanceUpdateResponse" + ], + "isilFile": "GEAPBDGCKMB_NestedType_RoomKeyPurchaseResponseDTO.txt", + "isilType": "GEAPBDGCKMB+RoomKeyPurchaseResponseDTO" + } + }, + { + "kind": "object", + "raw": "OBBBPCBIMME", + "rootType": "OBBBPCBIMME", + "typeDescription": { + "name": "OBBBPCBIMME", + "found": true, + "file": "OBBBPCBIMME.cs", + "declaration": "public class OBBBPCBIMME : IFAIJAGLDFK", + "kind": "class", + "baseType": "IFAIJAGLDFK", + "properties": [ + { + "name": "ACBFDMLHFPB", + "type": "DateTime", + "accessibility": "public", + "kind": "property" + }, + { + "name": "ACHMMBMLGEK", + "type": "int", + "accessibility": "public", + "kind": "property" + }, + { + "name": "AHGCOGFEEEE", + "type": "string", + "accessibility": "public", + "kind": "property" + }, + { + "name": "AJHBGBHGGAL", + "type": "long", + "accessibility": "public", + "kind": "property" + }, + { + "name": "BAELCDIJIPJ", + "type": "HECIICKPCDN", + "accessibility": "public", + "kind": "property" + }, + { + "name": "DKHJPGMEOHF", + "type": "int", + "accessibility": "public", + "kind": "property" + }, + { + "name": "FIKEBGGCDFN", + "type": "string", + "accessibility": "public", + "kind": "property" + }, + { + "name": "GCAABGAEDPP", + "type": "bool", + "accessibility": "public", + "kind": "property" + }, + { + "name": "HKFOKCNGNFA", + "type": "int", + "accessibility": "public", + "kind": "property" + }, + { + "name": "ILKLEFCCJMN", + "type": "bool", + "accessibility": "public", + "kind": "property" + }, + { + "name": "JHJNKJHLJBJ", + "type": "int", + "accessibility": "public", + "kind": "property" + }, + { + "name": "JOHEKKGMAJB", + "type": "int", + "accessibility": "public", + "kind": "property" + }, + { + "name": "KBDMNJGJACC", + "type": "DateTime", + "accessibility": "public", + "kind": "property" + }, + { + "name": "KLLMGMJJICK", + "type": "Nullable", + "accessibility": "public", + "kind": "property" + }, + { + "name": "KODBEJPEFOJ", + "type": "string", + "accessibility": "public", + "kind": "property" + }, + { + "name": "MLDAFKCENMA", + "type": "string", + "accessibility": "public", + "kind": "property" + }, + { + "name": "MLMDFDKLELH", + "type": "HECIICKPCDN", + "accessibility": "public", + "kind": "property" + }, + { + "name": "MMPKOCENIIP", + "type": "bool", + "accessibility": "public", + "kind": "property" + }, + { + "name": "NFNIPMIEMPM", + "type": "bool", + "accessibility": "public", + "kind": "property" + }, + { + "name": "NPDJLOOHMDJ", + "type": "Nullable", + "accessibility": "public", + "kind": "property" + }, + { + "name": "PJNJIKKCBNA", + "type": "Nullable", + "accessibility": "public", + "kind": "property" + } + ], + "fields": [], + "enumValues": [], + "wireKeys": [ + "InventionId", + "ReplicationId", + "CreatorPlayerId", + "Name", + "Description", + "ImageName", + "CurrentVersionNumber", + "IsPublished", + "AllowTrial", + "ModifiedAt", + "CreatedAt", + "NumPlayersHaveUsedInRoom", + "NumDownloads", + "CheerCount", + "CreatorPermission", + "GeneralPermission", + "IsAGInvention", + "Price", + "HideFromPlayer" + ], + "isilFile": "OBBBPCBIMME.txt", + "isilType": "OBBBPCBIMME" + } + }, + { + "kind": "callback-or-coroutine", + "raw": "IEnumerator", + "rootType": "callback", + "typeDescription": { + "name": "callback", + "found": false, + "kind": "object", + "wireKeys": [] + } + }, + { + "kind": "unknown", + "raw": "", + "rootType": "unknown", + "typeDescription": { + "name": "unknown", + "found": false, + "kind": "object", + "wireKeys": [] + } + }, + { + "kind": "object", + "raw": "BKFGFHDDNFG", + "rootType": "BKFGFHDDNFG", + "typeDescription": { + "name": "BKFGFHDDNFG", + "found": true, + "file": "BKFGFHDDNFG.cs", + "declaration": "public class BKFGFHDDNFG : DOPJPOEFDCN", + "kind": "class", + "baseType": "DOPJPOEFDCN", + "properties": [], + "fields": [ + { + "name": "JDGEBBMDJBC", + "type": "List", + "kind": "field" + } + ], + "enumValues": [], + "wireKeys": [], + "isilFile": "BKFGFHDDNFG.txt", + "isilType": "BKFGFHDDNFG", + "inherits": "DOPJPOEFDCN", + "inheritedMembers": [ + { + "name": "BFCBMGOPKFH", + "type": "Nullable", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "DOPJPOEFDCN" + }, + { + "name": "GHHJBILFKFB", + "type": "DateTime", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "DOPJPOEFDCN" + }, + { + "name": "GHOKCIMBEMM", + "type": "DGOHCPBKOHD", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "DOPJPOEFDCN" + } + ], + "inheritedWireKeys": [ + "StorefrontType", + "NextUpdate" + ] + } + } + ] + } + }, + "/api/testcasemanagement": { + "get": { + "tags": [ + "misc" + ], + "summary": "GET api/testcasemanagement/", + "description": "Inferred from Rec Room 2020 client literal `api/testcasemanagement/`. Expected result: Generic object/array/success result inferred from client route.", + "operationId": "client_misc_api_testcasemanagement_get", + "parameters": [], + "responses": { + "200": { + "description": "Generic object/array/success result inferred from client route.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/GenericObject" + } + } + } + }, + "204": { + "description": "Success with no body, used by some mutation endpoints." + }, + "400": { + "description": "Client-visible validation or bad request error.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "401": { + "description": "Authentication required or token invalid.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "500": { + "description": "Server error shape tolerated by client wrappers.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + } + }, + "x-recroom-client-route-literal": "api/testcasemanagement/", + "x-recroom-client-family": "misc", + "x-recroom-client-service": "api", + "x-recroom-builds": [ + "2020.12.18" + ], + "x-recroom-inferred-http-method": "get", + "x-recroom-evidence": [ + { + "type": "AICHDPNIEKI", + "method": "RecRoom.Async.IPromise CNGKABDOEKH(System.String EPGHLDONDIP, NIDIHGENDJD AKPHCJFIPBB)", + "file": "AICHDPNIEKI.txt", + "line": 450 + }, + { + "type": "AICHDPNIEKI", + "method": "RecRoom.Async.IPromise DHDOAJAGEJN(System.String EPGHLDONDIP)", + "file": "AICHDPNIEKI.txt", + "line": 360 + }, + { + "type": "AICHDPNIEKI", + "method": "RecRoom.Async.IPromise KNOGAGHKIMC(System.String EPGHLDONDIP)", + "file": "AICHDPNIEKI.txt", + "line": 273 + }, + { + "type": "AICHDPNIEKI", + "method": "RecRoom.Async.IPromise`1 APEBFFOEBFA(System.String ENJEOLBEALP)", + "file": "AICHDPNIEKI.txt", + "line": 193 + }, + { + "type": "AICHDPNIEKI", + "method": "RecRoom.Async.IPromise`1 BMDDGFAEDLE(System.UInt32 ENJEOLBEALP)", + "file": "AICHDPNIEKI.txt", + "line": 122 + }, + { + "type": "AICHDPNIEKI", + "method": "RecRoom.Async.IPromise`1> AKCPPGBNGOO()", + "file": "AICHDPNIEKI.txt", + "line": 44 + } + ], + "x-client-response-contracts": [ + { + "kind": "success-or-empty", + "raw": "RecRoom.Async.IPromise", + "rootType": "void/success", + "typeDescription": { + "name": "void/success", + "found": false, + "kind": "object", + "wireKeys": [] + } + }, + { + "kind": "object", + "raw": "CPFPHLINDHN", + "rootType": "CPFPHLINDHN", + "typeDescription": { + "name": "CPFPHLINDHN", + "found": true, + "file": "CPFPHLINDHN.cs", + "declaration": "public class CPFPHLINDHN : IFAIJAGLDFK", + "kind": "class", + "baseType": "IFAIJAGLDFK", + "properties": [ + { + "name": "CPNPHDAKFCL", + "type": "int", + "accessibility": "public", + "kind": "property" + } + ], + "fields": [ + { + "name": "JPOHGBCEJEJ", + "type": "string", + "kind": "field" + }, + { + "name": "AKFDMGLACLA", + "type": "string", + "kind": "field" + }, + { + "name": "LKIBJOMNFFD", + "type": "string", + "kind": "field" + }, + { + "name": "KODBEJPEFOJ", + "type": "string", + "kind": "field" + }, + { + "name": "HEHAGDOEDHG", + "type": "string", + "kind": "field" + }, + { + "name": "HIMCGOCKLLK", + "type": "NIDIHGENDJD", + "kind": "field" + }, + { + "name": "JOPPLLFIIOM", + "type": "int", + "kind": "field" + }, + { + "name": "JFOIIDOFNCF", + "type": "List", + "kind": "field" + }, + { + "name": "GEPOBHPJHAK", + "type": "List", + "kind": "field" + }, + { + "name": "PKKADKGDHNI", + "type": "List", + "kind": "field" + }, + { + "name": "EGBGAKBPEBD", + "type": "string", + "kind": "field" + }, + { + "name": "IOBANKPIFFG", + "type": "string", + "kind": "field" + } + ], + "enumValues": [], + "wireKeys": [ + "Id", + "Key", + "Title", + "Description", + "RoomName", + "Status", + "MinNumAssignedPlayers", + "AssignedPlayerIds", + "JiraUrl", + "JiraBugUrl" + ], + "isilFile": "CPFPHLINDHN.txt", + "isilType": "CPFPHLINDHN" + } + }, + { + "kind": "object", + "raw": "FDAJOEOJFDN", + "rootType": "FDAJOEOJFDN", + "typeDescription": { + "name": "FDAJOEOJFDN", + "found": true, + "file": "FDAJOEOJFDN.cs", + "declaration": "public class FDAJOEOJFDN : IFAIJAGLDFK", + "kind": "class", + "baseType": "IFAIJAGLDFK", + "properties": [ + { + "name": "PDFFJJFCNPO", + "type": "bool", + "accessibility": "public", + "kind": "property" + }, + { + "name": "PJOBHHCOBLE", + "type": "int", + "accessibility": "public", + "kind": "property" + } + ], + "fields": [ + { + "name": "JPOHGBCEJEJ", + "type": "uint", + "kind": "field" + }, + { + "name": "FIKEBGGCDFN", + "type": "string", + "kind": "field" + }, + { + "name": "KODBEJPEFOJ", + "type": "string", + "kind": "field" + }, + { + "name": "CKDMMCJNCHB", + "type": "DateTime", + "kind": "field" + }, + { + "name": "CKNFHKKHNHP", + "type": "Nullable", + "kind": "field" + }, + { + "name": "JICMOEJKCHG", + "type": "bool", + "kind": "field" + }, + { + "name": "KODOFNJJJCK", + "type": "List", + "kind": "field" + }, + { + "name": "PKKADKGDHNI", + "type": "List", + "kind": "field" + }, + { + "name": "BPPENAKNGNG", + "type": "int", + "kind": "field" + }, + { + "name": "PFIABEONLFI", + "type": "int", + "kind": "field" + }, + { + "name": "CAKKOAKBIIG", + "type": "int", + "kind": "field" + } + ], + "enumValues": [], + "wireKeys": [ + "Id", + "Name", + "Description", + "StartDate", + "WasManuallyClosed", + "NumTestCases", + "NumPassedTestCases", + "NumFailedTestCases" + ], + "isilFile": "FDAJOEOJFDN.txt", + "isilType": "FDAJOEOJFDN" + } + }, + { + "kind": "array", + "raw": "System.Collections.Generic.List`1", + "rootType": "FDAJOEOJFDN", + "item": { + "kind": "object", + "raw": "FDAJOEOJFDN", + "rootType": "FDAJOEOJFDN" + }, + "typeDescription": { + "name": "FDAJOEOJFDN", + "found": true, + "file": "FDAJOEOJFDN.cs", + "declaration": "public class FDAJOEOJFDN : IFAIJAGLDFK", + "kind": "class", + "baseType": "IFAIJAGLDFK", + "properties": [ + { + "name": "PDFFJJFCNPO", + "type": "bool", + "accessibility": "public", + "kind": "property" + }, + { + "name": "PJOBHHCOBLE", + "type": "int", + "accessibility": "public", + "kind": "property" + } + ], + "fields": [ + { + "name": "JPOHGBCEJEJ", + "type": "uint", + "kind": "field" + }, + { + "name": "FIKEBGGCDFN", + "type": "string", + "kind": "field" + }, + { + "name": "KODBEJPEFOJ", + "type": "string", + "kind": "field" + }, + { + "name": "CKDMMCJNCHB", + "type": "DateTime", + "kind": "field" + }, + { + "name": "CKNFHKKHNHP", + "type": "Nullable", + "kind": "field" + }, + { + "name": "JICMOEJKCHG", + "type": "bool", + "kind": "field" + }, + { + "name": "KODOFNJJJCK", + "type": "List", + "kind": "field" + }, + { + "name": "PKKADKGDHNI", + "type": "List", + "kind": "field" + }, + { + "name": "BPPENAKNGNG", + "type": "int", + "kind": "field" + }, + { + "name": "PFIABEONLFI", + "type": "int", + "kind": "field" + }, + { + "name": "CAKKOAKBIIG", + "type": "int", + "kind": "field" + } + ], + "enumValues": [], + "wireKeys": [ + "Id", + "Name", + "Description", + "StartDate", + "WasManuallyClosed", + "NumTestCases", + "NumPassedTestCases", + "NumFailedTestCases" + ], + "isilFile": "FDAJOEOJFDN.txt", + "isilType": "FDAJOEOJFDN" + } + } + ] + }, + "post": { + "tags": [ + "test-case-management" + ], + "summary": "POST api/testcasemanagement/", + "description": "Inferred from Rec Room 2020 client literal `api/testcasemanagement/`. Expected result: Generic object/array/success result inferred from client route.", + "operationId": "client_test_case_management_api_testcasemanagement_post", + "parameters": [], + "responses": { + "200": { + "description": "Generic object/array/success result inferred from client route.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/GenericObject" + } + } + } + }, + "204": { + "description": "Success with no body, used by some mutation endpoints." + }, + "400": { + "description": "Client-visible validation or bad request error.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "401": { + "description": "Authentication required or token invalid.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "500": { + "description": "Server error shape tolerated by client wrappers.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + } + }, + "x-recroom-client-route-literal": "api/testcasemanagement/", + "x-recroom-client-family": "test-case-management", + "x-recroom-client-service": "api", + "x-recroom-builds": [ + "2020.03.10" + ], + "x-recroom-inferred-http-method": "post", + "x-recroom-evidence": [ + { + "type": "stringliteral.json", + "method": "", + "file": "Recnet-archive/decompiled/Il2CppDump/stringliteral.json", + "line": null + } + ], + "requestBody": { + "required": false, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/GenericObject" + } + }, + "application/x-www-form-urlencoded": { + "schema": { + "$ref": "#/components/schemas/GenericObject" + } + }, + "multipart/form-data": { + "schema": { + "$ref": "#/components/schemas/GenericObject" + } + } + }, + "description": "Request body inferred. Exact DTO may be named in x-recroom-evidence method signature or companion DTO inventory." + }, + "x-client-response-contracts": [ + { + "kind": "success-or-empty", + "raw": "RecRoom.Async.IPromise", + "rootType": "void/success", + "typeDescription": { + "name": "void/success", + "found": false, + "kind": "object", + "wireKeys": [] + } + }, + { + "kind": "object", + "raw": "CPFPHLINDHN", + "rootType": "CPFPHLINDHN", + "typeDescription": { + "name": "CPFPHLINDHN", + "found": true, + "file": "CPFPHLINDHN.cs", + "declaration": "public class CPFPHLINDHN : IFAIJAGLDFK", + "kind": "class", + "baseType": "IFAIJAGLDFK", + "properties": [ + { + "name": "CPNPHDAKFCL", + "type": "int", + "accessibility": "public", + "kind": "property" + } + ], + "fields": [ + { + "name": "JPOHGBCEJEJ", + "type": "string", + "kind": "field" + }, + { + "name": "AKFDMGLACLA", + "type": "string", + "kind": "field" + }, + { + "name": "LKIBJOMNFFD", + "type": "string", + "kind": "field" + }, + { + "name": "KODBEJPEFOJ", + "type": "string", + "kind": "field" + }, + { + "name": "HEHAGDOEDHG", + "type": "string", + "kind": "field" + }, + { + "name": "HIMCGOCKLLK", + "type": "NIDIHGENDJD", + "kind": "field" + }, + { + "name": "JOPPLLFIIOM", + "type": "int", + "kind": "field" + }, + { + "name": "JFOIIDOFNCF", + "type": "List", + "kind": "field" + }, + { + "name": "GEPOBHPJHAK", + "type": "List", + "kind": "field" + }, + { + "name": "PKKADKGDHNI", + "type": "List", + "kind": "field" + }, + { + "name": "EGBGAKBPEBD", + "type": "string", + "kind": "field" + }, + { + "name": "IOBANKPIFFG", + "type": "string", + "kind": "field" + } + ], + "enumValues": [], + "wireKeys": [ + "Id", + "Key", + "Title", + "Description", + "RoomName", + "Status", + "MinNumAssignedPlayers", + "AssignedPlayerIds", + "JiraUrl", + "JiraBugUrl" + ], + "isilFile": "CPFPHLINDHN.txt", + "isilType": "CPFPHLINDHN" + } + }, + { + "kind": "object", + "raw": "FDAJOEOJFDN", + "rootType": "FDAJOEOJFDN", + "typeDescription": { + "name": "FDAJOEOJFDN", + "found": true, + "file": "FDAJOEOJFDN.cs", + "declaration": "public class FDAJOEOJFDN : IFAIJAGLDFK", + "kind": "class", + "baseType": "IFAIJAGLDFK", + "properties": [ + { + "name": "PDFFJJFCNPO", + "type": "bool", + "accessibility": "public", + "kind": "property" + }, + { + "name": "PJOBHHCOBLE", + "type": "int", + "accessibility": "public", + "kind": "property" + } + ], + "fields": [ + { + "name": "JPOHGBCEJEJ", + "type": "uint", + "kind": "field" + }, + { + "name": "FIKEBGGCDFN", + "type": "string", + "kind": "field" + }, + { + "name": "KODBEJPEFOJ", + "type": "string", + "kind": "field" + }, + { + "name": "CKDMMCJNCHB", + "type": "DateTime", + "kind": "field" + }, + { + "name": "CKNFHKKHNHP", + "type": "Nullable", + "kind": "field" + }, + { + "name": "JICMOEJKCHG", + "type": "bool", + "kind": "field" + }, + { + "name": "KODOFNJJJCK", + "type": "List", + "kind": "field" + }, + { + "name": "PKKADKGDHNI", + "type": "List", + "kind": "field" + }, + { + "name": "BPPENAKNGNG", + "type": "int", + "kind": "field" + }, + { + "name": "PFIABEONLFI", + "type": "int", + "kind": "field" + }, + { + "name": "CAKKOAKBIIG", + "type": "int", + "kind": "field" + } + ], + "enumValues": [], + "wireKeys": [ + "Id", + "Name", + "Description", + "StartDate", + "WasManuallyClosed", + "NumTestCases", + "NumPassedTestCases", + "NumFailedTestCases" + ], + "isilFile": "FDAJOEOJFDN.txt", + "isilType": "FDAJOEOJFDN" + } + }, + { + "kind": "array", + "raw": "System.Collections.Generic.List`1", + "rootType": "FDAJOEOJFDN", + "item": { + "kind": "object", + "raw": "FDAJOEOJFDN", + "rootType": "FDAJOEOJFDN" + }, + "typeDescription": { + "name": "FDAJOEOJFDN", + "found": true, + "file": "FDAJOEOJFDN.cs", + "declaration": "public class FDAJOEOJFDN : IFAIJAGLDFK", + "kind": "class", + "baseType": "IFAIJAGLDFK", + "properties": [ + { + "name": "PDFFJJFCNPO", + "type": "bool", + "accessibility": "public", + "kind": "property" + }, + { + "name": "PJOBHHCOBLE", + "type": "int", + "accessibility": "public", + "kind": "property" + } + ], + "fields": [ + { + "name": "JPOHGBCEJEJ", + "type": "uint", + "kind": "field" + }, + { + "name": "FIKEBGGCDFN", + "type": "string", + "kind": "field" + }, + { + "name": "KODBEJPEFOJ", + "type": "string", + "kind": "field" + }, + { + "name": "CKDMMCJNCHB", + "type": "DateTime", + "kind": "field" + }, + { + "name": "CKNFHKKHNHP", + "type": "Nullable", + "kind": "field" + }, + { + "name": "JICMOEJKCHG", + "type": "bool", + "kind": "field" + }, + { + "name": "KODOFNJJJCK", + "type": "List", + "kind": "field" + }, + { + "name": "PKKADKGDHNI", + "type": "List", + "kind": "field" + }, + { + "name": "BPPENAKNGNG", + "type": "int", + "kind": "field" + }, + { + "name": "PFIABEONLFI", + "type": "int", + "kind": "field" + }, + { + "name": "CAKKOAKBIIG", + "type": "int", + "kind": "field" + } + ], + "enumValues": [], + "wireKeys": [ + "Id", + "Name", + "Description", + "StartDate", + "WasManuallyClosed", + "NumTestCases", + "NumPassedTestCases", + "NumFailedTestCases" + ], + "isilFile": "FDAJOEOJFDN.txt", + "isilType": "FDAJOEOJFDN" + } + } + ] + } + }, + "/api/versioncheck/v4": { + "get": { + "tags": [ + "misc" + ], + "summary": "GET api/versioncheck/v4?v={0}&p={1}", + "description": "Inferred from Rec Room 2020 client literal `api/versioncheck/v4?v={0}&p={1}`. Expected result: Generic object/array/success result inferred from client route.", + "operationId": "client_misc_api_versioncheck_v4_get", + "parameters": [ + { + "name": "v", + "in": "query", + "required": false, + "schema": { + "type": "string" + }, + "description": "Client value template: {p0}" + }, + { + "name": "p", + "in": "query", + "required": false, + "schema": { + "type": "string" + }, + "description": "Client value template: {p1}" + } + ], + "responses": { + "200": { + "description": "Generic object/array/success result inferred from client route.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/GenericObject" + } + } + } + }, + "204": { + "description": "Success with no body, used by some mutation endpoints." + }, + "400": { + "description": "Client-visible validation or bad request error.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "401": { + "description": "Authentication required or token invalid.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "500": { + "description": "Server error shape tolerated by client wrappers.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + } + }, + "x-recroom-client-route-literal": "api/versioncheck/v4?v={0}&p={1}", + "x-recroom-client-family": "misc", + "x-recroom-client-service": "api", + "x-recroom-builds": [ + "2020.12.18" + ], + "x-recroom-inferred-http-method": "get", + "x-recroom-evidence": [ + { + "type": "KMDHPCHFADM", + "method": "RecRoom.Async.IPromise`1 BPAGDCDBHAM()", + "file": "KMDHPCHFADM.txt", + "line": 282 + } + ], + "x-client-response-contracts": [ + { + "kind": "object", + "raw": "PCBCGDHBAEL", + "rootType": "PCBCGDHBAEL", + "typeDescription": { + "name": "PCBCGDHBAEL", + "found": true, + "file": "PCBCGDHBAEL.cs", + "declaration": "public enum PCBCGDHBAEL", + "kind": "enum", + "baseType": null, + "properties": [], + "fields": [], + "enumValues": [ + { + "name": "ValidForPlay", + "value": "0" + }, + { + "name": "ValidForMenu", + "value": "1" + }, + { + "name": "UpdateRequired", + "value": "2" + } + ], + "wireKeys": [] + } + } + ] + }, + "post": { + "tags": [ + "versioncheck" + ], + "summary": "POST api/versioncheck/v4?v={0}&p={1}", + "description": "Inferred from Rec Room 2020 client literal `api/versioncheck/v4?v={0}&p={1}`. Expected result: Generic object/array/success result inferred from client route.", + "operationId": "client_versioncheck_api_versioncheck_v4_post", + "parameters": [ + { + "name": "v", + "in": "query", + "required": false, + "schema": { + "type": "string" + }, + "description": "Client value template: {p0}" + }, + { + "name": "p", + "in": "query", + "required": false, + "schema": { + "type": "string" + }, + "description": "Client value template: {p1}" + } + ], + "responses": { + "200": { + "description": "Generic object/array/success result inferred from client route.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/VersionCheck" + } + } + } + }, + "204": { + "description": "Success with no body, used by some mutation endpoints." + }, + "400": { + "description": "Client-visible validation or bad request error.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "401": { + "description": "Authentication required or token invalid.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "500": { + "description": "Server error shape tolerated by client wrappers.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + } + }, + "x-recroom-client-route-literal": "api/versioncheck/v4?v={0}&p={1}", + "x-recroom-client-family": "versioncheck", + "x-recroom-client-service": "api", + "x-recroom-builds": [ + "2020.03.10" + ], + "x-recroom-inferred-http-method": "post", + "x-recroom-evidence": [ + { + "type": "stringliteral.json", + "method": "", + "file": "Recnet-archive/decompiled/Il2CppDump/stringliteral.json", + "line": null + } + ], + "requestBody": { + "required": false, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/GenericObject" + } + }, + "application/x-www-form-urlencoded": { + "schema": { + "$ref": "#/components/schemas/GenericObject" + } + }, + "multipart/form-data": { + "schema": { + "$ref": "#/components/schemas/GenericObject" + } + } + }, + "description": "Request body inferred. Exact DTO may be named in x-recroom-evidence method signature or companion DTO inventory." + }, + "x-client-response-contracts": [ + { + "kind": "object", + "raw": "PCBCGDHBAEL", + "rootType": "PCBCGDHBAEL", + "typeDescription": { + "name": "PCBCGDHBAEL", + "found": true, + "file": "PCBCGDHBAEL.cs", + "declaration": "public enum PCBCGDHBAEL", + "kind": "enum", + "baseType": null, + "properties": [], + "fields": [], + "enumValues": [ + { + "name": "ValidForPlay", + "value": "0" + }, + { + "name": "ValidForMenu", + "value": "1" + }, + { + "name": "UpdateRequired", + "value": "2" + } + ], + "wireKeys": [] + } + } + ] + } + }, + "/bulk": { + "post": { + "tags": [ + "misc" + ], + "summary": "POST /bulk", + "description": "Inferred from Rec Room 2020 client literal `/bulk`. Expected result: Generic object/array/success result inferred from client route.", + "operationId": "client_misc_bulk_post", + "parameters": [], + "responses": { + "200": { + "description": "Generic object/array/success result inferred from client route.", + "content": { + "application/json": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/GenericObject" + } + } + } + } + }, + "204": { + "description": "Success with no body, used by some mutation endpoints." + }, + "400": { + "description": "Client-visible validation or bad request error.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "401": { + "description": "Authentication required or token invalid.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "500": { + "description": "Server error shape tolerated by client wrappers.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + } + }, + "x-recroom-client-route-literal": "/bulk", + "x-recroom-client-family": "misc", + "x-recroom-client-service": "api", + "x-recroom-builds": [ + "2020.12.18" + ], + "x-recroom-inferred-http-method": "post", + "x-recroom-evidence": [ + { + "type": "GNPDMBPGHBH", + "method": "RecRoom.Async.IPromise`1> DMHOIAJGABC(System.Collections.Generic.IReadOnlyList`1 JOGAGJIPEDN)", + "file": "GNPDMBPGHBH.txt", + "line": 2388 + }, + { + "type": "GNPDMBPGHBH+MKCNPHKGFDM", + "method": "RecRoom.Async.IPromise`1> b__1(System.String uri)", + "file": "GNPDMBPGHBH_NestedType_MKCNPHKGFDM.txt", + "line": 138 + } + ], + "requestBody": { + "required": false, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/GenericObject" + } + }, + "application/x-www-form-urlencoded": { + "schema": { + "$ref": "#/components/schemas/GenericObject" + } + }, + "multipart/form-data": { + "schema": { + "$ref": "#/components/schemas/GenericObject" + } + } + }, + "description": "Request body inferred. Exact DTO may be named in x-recroom-evidence method signature or companion DTO inventory." + }, + "x-client-response-contracts": [ + { + "kind": "array", + "raw": "System.Collections.Generic.List`1", + "rootType": "CCMBKDINCAH", + "item": { + "kind": "object", + "raw": "CCMBKDINCAH", + "rootType": "CCMBKDINCAH" + }, + "typeDescription": { + "name": "CCMBKDINCAH", + "found": true, + "file": "CCMBKDINCAH.cs", + "declaration": "public class CCMBKDINCAH : IFAIJAGLDFK", + "kind": "class", + "baseType": "IFAIJAGLDFK", + "properties": [ + { + "name": "ACHMMBMLGEK", + "type": "int", + "accessibility": "public", + "kind": "property" + }, + { + "name": "AHGCOGFEEEE", + "type": "string", + "accessibility": "public", + "kind": "property" + }, + { + "name": "CCGOEDABKNN", + "type": "Nullable", + "accessibility": "public", + "kind": "property" + }, + { + "name": "DADOKMAOFJL", + "type": "long", + "accessibility": "public", + "kind": "property" + }, + { + "name": "FIKEBGGCDFN", + "type": "string", + "accessibility": "public", + "kind": "property" + }, + { + "name": "GBOIPGBGDDG", + "type": "long", + "accessibility": "public", + "kind": "property" + }, + { + "name": "GIHDFJNGFHH", + "type": "int", + "accessibility": "public", + "kind": "property" + }, + { + "name": "GJPHHEHBCIJ", + "type": "Nullable", + "accessibility": "public", + "kind": "property" + }, + { + "name": "JFEAPMIPNEP", + "type": "CMCAFKLAHCD", + "accessibility": "public", + "kind": "property" + }, + { + "name": "KJDKJFLBFOL", + "type": "bool", + "accessibility": "public", + "kind": "property" + }, + { + "name": "KMOAHFBMONE", + "type": "bool", + "accessibility": "public", + "kind": "property" + }, + { + "name": "KODBEJPEFOJ", + "type": "string", + "accessibility": "public", + "kind": "property" + }, + { + "name": "LCEFHPHIHBP", + "type": "bool", + "accessibility": "public", + "kind": "property" + }, + { + "name": "LIPFBIGFEBG", + "type": "DateTime", + "accessibility": "public", + "kind": "property" + }, + { + "name": "MAFEFHEPIKI", + "type": "DateTime", + "accessibility": "public", + "kind": "property" + } + ], + "fields": [], + "enumValues": [], + "wireKeys": [ + "PlayerEventId", + "Name", + "Description", + "ImageName", + "StartTime", + "EndTime", + "CreatorPlayerId", + "AttendeeCount", + "RoomId", + "Accessibility" + ], + "isilFile": "CCMBKDINCAH.txt", + "isilType": "CCMBKDINCAH" + } + } + ] + } + }, + "/data/{p0}": { + "get": { + "tags": [ + "misc" + ], + "summary": "GET /data/{0}", + "description": "Inferred from Rec Room 2020 client literal `/data/{0}`. Expected result: Generic object/array/success result inferred from client route.", + "operationId": "client_misc_data_p0_get", + "parameters": [ + { + "name": "p0", + "in": "path", + "required": true, + "schema": { + "type": "string" + }, + "description": "Format placeholder from client literal /data/{0}" + } + ], + "responses": { + "200": { + "description": "Generic object/array/success result inferred from client route.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/GenericObject" + } + } + } + }, + "204": { + "description": "Success with no body, used by some mutation endpoints." + }, + "400": { + "description": "Client-visible validation or bad request error.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "401": { + "description": "Authentication required or token invalid.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "500": { + "description": "Server error shape tolerated by client wrappers.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + } + }, + "x-recroom-client-route-literal": "/data/{0}", + "x-recroom-client-family": "misc", + "x-recroom-client-service": "api", + "x-recroom-builds": [ + "2020.12.18" + ], + "x-recroom-inferred-http-method": "get", + "x-recroom-evidence": [ + { + "type": "KELEPAPMOGK+CJACMOFOFKJ", + "method": "RecRoom.Async.IPromise`1 b__0()", + "file": "KELEPAPMOGK_NestedType_CJACMOFOFKJ.txt", + "line": 187 + } + ], + "x-client-response-contracts": [ + { + "kind": "object", + "raw": "KELEPAPMOGK+LEKKFHBPBAB", + "rootType": "LEKKFHBPBAB", + "typeDescription": { + "name": "LEKKFHBPBAB", + "found": true, + "file": "KELEPAPMOGK.cs", + "declaration": "internal class LEKKFHBPBAB", + "kind": "class", + "baseType": null, + "properties": [], + "fields": [ + { + "name": "MMBOKOLAJFH", + "type": "string", + "kind": "field" + }, + { + "name": "MGPDDEMABPB", + "type": "Byte[]", + "kind": "field" + } + ], + "enumValues": [], + "wireKeys": [], + "isilFile": "KELEPAPMOGK_NestedType_LEKKFHBPBAB.txt", + "isilType": "KELEPAPMOGK+LEKKFHBPBAB" + } + } + ] + }, + "post": { + "tags": [ + "storage-cdn" + ], + "summary": "POST /data/{0}", + "description": "Inferred from Rec Room 2020 client literal `/data/{0}`. Expected result: Generic object/array/success result inferred from client route.", + "operationId": "client_storage_cdn_data_p0_post", + "parameters": [ + { + "name": "p0", + "in": "path", + "required": true, + "schema": { + "type": "string" + }, + "description": "Format placeholder from client literal /data/{0}" + } + ], + "responses": { + "200": { + "description": "Generic object/array/success result inferred from client route.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/GenericObject" + } + } + } + }, + "204": { + "description": "Success with no body, used by some mutation endpoints." + }, + "400": { + "description": "Client-visible validation or bad request error.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "401": { + "description": "Authentication required or token invalid.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "500": { + "description": "Server error shape tolerated by client wrappers.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + } + }, + "x-recroom-client-route-literal": "/data/{0}", + "x-recroom-client-family": "storage-cdn", + "x-recroom-client-service": "storage/images/cdn", + "x-recroom-builds": [ + "2020.03.10" + ], + "x-recroom-inferred-http-method": "post", + "x-recroom-evidence": [ + { + "type": "stringliteral.json", + "method": "", + "file": "Recnet-archive/decompiled/Il2CppDump/stringliteral.json", + "line": null + } + ], + "requestBody": { + "required": false, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/GenericObject" + } + }, + "application/x-www-form-urlencoded": { + "schema": { + "$ref": "#/components/schemas/GenericObject" + } + }, + "multipart/form-data": { + "schema": { + "$ref": "#/components/schemas/GenericObject" + } + } + }, + "description": "Request body inferred. Exact DTO may be named in x-recroom-evidence method signature or companion DTO inventory." + }, + "x-client-response-contracts": [ + { + "kind": "object", + "raw": "KELEPAPMOGK+LEKKFHBPBAB", + "rootType": "LEKKFHBPBAB", + "typeDescription": { + "name": "LEKKFHBPBAB", + "found": true, + "file": "KELEPAPMOGK.cs", + "declaration": "internal class LEKKFHBPBAB", + "kind": "class", + "baseType": null, + "properties": [], + "fields": [ + { + "name": "MMBOKOLAJFH", + "type": "string", + "kind": "field" + }, + { + "name": "MGPDDEMABPB", + "type": "Byte[]", + "kind": "field" + } + ], + "enumValues": [], + "wireKeys": [], + "isilFile": "KELEPAPMOGK_NestedType_LEKKFHBPBAB.txt", + "isilType": "KELEPAPMOGK+LEKKFHBPBAB" + } + } + ] + } + }, + "/recroom/{tail}": { + "get": { + "tags": [ + "misc" + ], + "summary": "GET https://www.instagram.com/recroom/", + "description": "Inferred from Rec Room 2020 client literal `https://www.instagram.com/recroom/`. Expected result: Generic object/array/success result inferred from client route.", + "operationId": "client_misc_recroom_tail_get", + "parameters": [ + { + "name": "tail", + "in": "path", + "required": true, + "schema": { + "type": "string" + }, + "description": "Format placeholder from client literal https://www.instagram.com/recroom/" + } + ], + "responses": { + "200": { + "description": "Generic object/array/success result inferred from client route.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/GenericObject" + } + } + } + }, + "204": { + "description": "Success with no body, used by some mutation endpoints." + }, + "400": { + "description": "Client-visible validation or bad request error.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "401": { + "description": "Authentication required or token invalid.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "500": { + "description": "Server error shape tolerated by client wrappers.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + } + }, + "x-recroom-client-route-literal": "https://www.instagram.com/recroom/", + "x-recroom-client-family": "misc", + "x-recroom-client-service": "api", + "x-recroom-builds": [ + "2020.03.10", + "2020.12.18" + ], + "x-recroom-inferred-http-method": "get", + "x-recroom-evidence": [ + { + "type": "InstagramBulletinBoardUI", + "method": "System.Void Button_RecRoomOnInstagram()", + "file": "InstagramBulletinBoardUI.txt", + "line": 1009 + }, + { + "type": "stringliteral.json", + "method": "", + "file": "Recnet-archive/decompiled/Il2CppDump/stringliteral.json", + "line": null + } + ], + "x-client-response-contracts": [ + { + "kind": "unknown", + "raw": "", + "rootType": "unknown", + "typeDescription": { + "name": "unknown", + "found": false, + "kind": "object", + "wireKeys": [] + } + } + ] + } + }, + "/room/{tail}": { + "get": { + "tags": [ + "misc" + ], + "summary": "GET /room/", + "description": "Inferred from Rec Room 2020 client literal `/room/`. Expected result: Generic object/array/success result inferred from client route.", + "operationId": "client_misc_room_tail_get", + "parameters": [ + { + "name": "tail", + "in": "path", + "required": true, + "schema": { + "type": "string" + }, + "description": "Format placeholder from client literal /room/" + } + ], + "responses": { + "200": { + "description": "Generic object/array/success result inferred from client route.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/GenericObject" + } + } + } + }, + "204": { + "description": "Success with no body, used by some mutation endpoints." + }, + "400": { + "description": "Client-visible validation or bad request error.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "401": { + "description": "Authentication required or token invalid.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "500": { + "description": "Server error shape tolerated by client wrappers.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + } + }, + "x-recroom-client-route-literal": "/room/", + "x-recroom-client-family": "misc", + "x-recroom-client-service": "api", + "x-recroom-builds": [ + "2020.12.18" + ], + "x-recroom-inferred-http-method": "get", + "x-recroom-evidence": [ + { + "type": "OJMCBOKJFOF+BPPECKDOINI", + "method": "RecRoom.Async.IPromise`1 b__0()", + "file": "OJMCBOKJFOF_NestedType_BPPECKDOINI.txt", + "line": 206 + } + ], + "x-client-response-contracts": [ + { + "kind": "object", + "raw": "System.Byte[]", + "rootType": "Byte", + "typeDescription": { + "name": "Byte", + "found": false, + "kind": "primitive", + "wireKeys": [] + } + } + ] + }, + "post": { + "tags": [ + "rooms" + ], + "summary": "POST /room/", + "description": "Inferred from Rec Room 2020 client literal `/room/`. Expected result: Room detail object or mutation success.", + "operationId": "client_rooms_room_tail_post", + "parameters": [ + { + "name": "tail", + "in": "path", + "required": true, + "schema": { + "type": "string" + }, + "description": "Format placeholder from client literal /room/" + } + ], + "responses": { + "200": { + "description": "Room detail object or mutation success.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Room" + } + } + } + }, + "204": { + "description": "Success with no body, used by some mutation endpoints." + }, + "400": { + "description": "Client-visible validation or bad request error.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "401": { + "description": "Authentication required or token invalid.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "500": { + "description": "Server error shape tolerated by client wrappers.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + } + }, + "x-recroom-client-route-literal": "/room/", + "x-recroom-client-family": "rooms", + "x-recroom-client-service": "rooms/api", + "x-recroom-builds": [ + "2020.03.10" + ], + "x-recroom-inferred-http-method": "post", + "x-recroom-evidence": [ + { + "type": "stringliteral.json", + "method": "", + "file": "Recnet-archive/decompiled/Il2CppDump/stringliteral.json", + "line": null + } + ], + "requestBody": { + "required": false, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/GenericObject" + } + }, + "application/x-www-form-urlencoded": { + "schema": { + "$ref": "#/components/schemas/GenericObject" + } + }, + "multipart/form-data": { + "schema": { + "$ref": "#/components/schemas/GenericObject" + } + } + }, + "description": "Request body inferred. Exact DTO may be named in x-recroom-evidence method signature or companion DTO inventory." + }, + "x-client-response-contracts": [ + { + "kind": "object", + "raw": "System.Byte[]", + "rootType": "Byte", + "typeDescription": { + "name": "Byte", + "found": false, + "kind": "primitive", + "wireKeys": [] + } + } + ] + } + }, + "/v1/bulkignoreplatformusers": { + "post": { + "tags": [ + "misc" + ], + "summary": "POST {0}v1/bulkignoreplatformusers", + "description": "Inferred from Rec Room 2020 client literal `{0}v1/bulkignoreplatformusers`. Expected result: Generic object/array/success result inferred from client route.", + "operationId": "client_misc_v1_bulkignoreplatformusers_post", + "parameters": [], + "responses": { + "200": { + "description": "Generic object/array/success result inferred from client route.", + "content": { + "application/json": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/GenericObject" + } + } + } + } + }, + "204": { + "description": "Success with no body, used by some mutation endpoints." + }, + "400": { + "description": "Client-visible validation or bad request error.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "401": { + "description": "Authentication required or token invalid.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "500": { + "description": "Server error shape tolerated by client wrappers.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + } + }, + "x-recroom-client-route-literal": "{0}v1/bulkignoreplatformusers", + "x-recroom-client-family": "misc", + "x-recroom-client-service": "api", + "x-recroom-builds": [ + "2020.03.10", + "2020.12.18" + ], + "x-recroom-inferred-http-method": "post", + "x-recroom-evidence": [ + { + "type": "FGPIDGLCKEF", + "method": "System.Void IMLJEBCMMKK(PlatformManager+FCIKKFJOMNO FPMCLJDEGKL, System.Collections.Generic.List`1 CIBFMCPBLEO)", + "file": "FGPIDGLCKEF.txt", + "line": 2834 + }, + { + "type": "stringliteral.json", + "method": "", + "file": "Recnet-archive/decompiled/Il2CppDump/stringliteral.json", + "line": null + } + ], + "requestBody": { + "required": false, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/GenericObject" + } + }, + "application/x-www-form-urlencoded": { + "schema": { + "$ref": "#/components/schemas/GenericObject" + } + }, + "multipart/form-data": { + "schema": { + "$ref": "#/components/schemas/GenericObject" + } + } + }, + "description": "Request body inferred. Exact DTO may be named in x-recroom-evidence method signature or companion DTO inventory." + }, + "x-client-response-contracts": [ + { + "kind": "unknown", + "raw": "", + "rootType": "unknown", + "typeDescription": { + "name": "unknown", + "found": false, + "kind": "object", + "wireKeys": [] + } + } + ] + } + }, + "/v1/bulkInvite": { + "post": { + "tags": [ + "misc" + ], + "summary": "POST {0}v1/bulkInvite", + "description": "Inferred from Rec Room 2020 client literal `{0}v1/bulkInvite`. Expected result: Generic object/array/success result inferred from client route.", + "operationId": "client_misc_v1_bulkinvite_post", + "parameters": [], + "responses": { + "200": { + "description": "Generic object/array/success result inferred from client route.", + "content": { + "application/json": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/GenericObject" + } + } + } + } + }, + "204": { + "description": "Success with no body, used by some mutation endpoints." + }, + "400": { + "description": "Client-visible validation or bad request error.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "401": { + "description": "Authentication required or token invalid.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "500": { + "description": "Server error shape tolerated by client wrappers.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + } + }, + "x-recroom-client-route-literal": "{0}v1/bulkInvite", + "x-recroom-client-family": "misc", + "x-recroom-client-service": "api", + "x-recroom-builds": [ + "2020.03.10" + ], + "x-recroom-inferred-http-method": "post", + "x-recroom-evidence": [ + { + "type": "stringliteral.json", + "method": "", + "file": "Recnet-archive/decompiled/Il2CppDump/stringliteral.json", + "line": null + } + ], + "requestBody": { + "required": false, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/GenericObject" + } + }, + "application/x-www-form-urlencoded": { + "schema": { + "$ref": "#/components/schemas/GenericObject" + } + }, + "multipart/form-data": { + "schema": { + "$ref": "#/components/schemas/GenericObject" + } + } + }, + "description": "Request body inferred. Exact DTO may be named in x-recroom-evidence method signature or companion DTO inventory." + } + } + }, + "/api/playerevents/{tail}": { + "get": { + "tags": [ + "player-events" + ], + "summary": "GET api/playerevents/", + "description": "Inferred from Rec Room 2020 client literal `api/playerevents/`. Expected result: Player event object/list/page or event mutation success.", + "operationId": "client_player_events_api_playerevents_tail_get", + "parameters": [ + { + "name": "tail", + "in": "path", + "required": true, + "schema": { + "type": "string" + }, + "description": "Format placeholder from client literal api/playerevents/" + } + ], + "responses": { + "200": { + "description": "Player event object/list/page or event mutation success.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/PlayerEvent" + } + } + } + }, + "204": { + "description": "Success with no body, used by some mutation endpoints." + }, + "400": { + "description": "Client-visible validation or bad request error.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "401": { + "description": "Authentication required or token invalid.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "500": { + "description": "Server error shape tolerated by client wrappers.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + } + }, + "x-recroom-client-route-literal": "api/playerevents/", + "x-recroom-client-family": "player-events", + "x-recroom-client-service": "api", + "x-recroom-builds": [ + "2020.03.10", + "2020.12.18" + ], + "x-recroom-inferred-http-method": "get", + "x-recroom-evidence": [ + { + "type": "GNPDMBPGHBH", + "method": "RecRoom.Async.IPromise`1 BFBMDMLHHCF(CCMBKDINCAH MFJCKOBPMGA, System.Int64 HNHLJONGKHB, System.Nullable`1 FODGKNJIGOP, System.String MMBOKOLAJFH, System.String LJIGOCDPEJF, System.Collections.Generic.List`1 CAFPJPHILMN, System.String HFLPBHHAFIO, System.DateTime JODPOANPJNK, System.DateTime BCANLCHBKJE, CMCAFKLAHCD PONCIIJOHIE, System.Nullable`1 AOMBLGBCENO)", + "file": "GNPDMBPGHBH.txt", + "line": 4280 + }, + { + "type": "GNPDMBPGHBH", + "method": "RecRoom.Async.IPromise`1 EKEOHKINPDK(CCMBKDINCAH MFJCKOBPMGA)", + "file": "GNPDMBPGHBH.txt", + "line": 6151 + }, + { + "type": "GNPDMBPGHBH", + "method": "RecRoom.Async.IPromise`1> MBHCOIGLAIF(System.Int64 MOKMJAMNEFP, System.Boolean INGAKMAAHKL = False)", + "file": "GNPDMBPGHBH.txt", + "line": 3736 + }, + { + "type": "GNPDMBPGHBH", + "method": "RecRoom.Async.IPromise`1 DAICPGEMGMH(System.Int64 MOKMJAMNEFP, System.Boolean GDPJLIOIIAM = False, System.Nullable`1 AOMBLGBCENO = null)", + "file": "GNPDMBPGHBH.txt", + "line": 6434 + }, + { + "type": "GNPDMBPGHBH", + "method": "System.String HKPFFOHBENO(System.Int64 MEJJDOHGNHG)", + "file": "GNPDMBPGHBH.txt", + "line": 559 + }, + { + "type": "GNPDMBPGHBH", + "method": "System.Void DAOBEMMLHAD(System.Collections.Generic.Dictionary`2 NGPMADFHHKP)", + "file": "GNPDMBPGHBH.txt", + "line": 10757 + }, + { + "type": "GNPDMBPGHBH", + "method": "System.Void DEOJOKOFMLJ(System.Int64 MOKMJAMNEFP)", + "file": "GNPDMBPGHBH.txt", + "line": 11770 + }, + { + "type": "GNPDMBPGHBH", + "method": "System.Void JILNOOIDFKC(HJCAMMLHJAE MOMJFCHIMAM)", + "file": "GNPDMBPGHBH.txt", + "line": 11596 + }, + { + "type": "GNPDMBPGHBH+JNEMPCHKFAM", + "method": "System.Void b__0(PDOBNLOLBAF response)", + "file": "GNPDMBPGHBH_NestedType_JNEMPCHKFAM.txt", + "line": 168 + }, + { + "type": "stringliteral.json", + "method": "", + "file": "Recnet-archive/decompiled/Il2CppDump/stringliteral.json", + "line": null + } + ], + "x-client-response-contracts": [ + { + "kind": "object", + "raw": "LLNPLLBEJBE", + "rootType": "LLNPLLBEJBE", + "typeDescription": { + "name": "LLNPLLBEJBE", + "found": true, + "file": "LLNPLLBEJBE.cs", + "declaration": "public class LLNPLLBEJBE : IFAIJAGLDFK", + "kind": "class", + "baseType": "IFAIJAGLDFK", + "properties": [ + { + "name": "AANNMHFBONM", + "type": "CCMBKDINCAH", + "accessibility": "public", + "kind": "property" + }, + { + "name": "GMFIDNACDJK", + "type": "DGOPHENCPOC", + "accessibility": "public", + "kind": "property" + }, + { + "name": "POJDHKAOINA", + "type": "NJMAEIPIOAP", + "accessibility": "public", + "kind": "property" + } + ], + "fields": [], + "enumValues": [], + "wireKeys": [ + "PlayerEvent", + "Result", + "TagModifyResult" + ], + "isilFile": "LLNPLLBEJBE.txt", + "isilType": "LLNPLLBEJBE" + } + }, + { + "kind": "array", + "raw": "System.Collections.Generic.List`1", + "rootType": "PBIJAPEOEDO", + "item": { + "kind": "object", + "raw": "PBIJAPEOEDO", + "rootType": "PBIJAPEOEDO" + }, + "typeDescription": { + "name": "PBIJAPEOEDO", + "found": true, + "file": "PBIJAPEOEDO.cs", + "declaration": "public class PBIJAPEOEDO : IFAIJAGLDFK", + "kind": "class", + "baseType": "IFAIJAGLDFK", + "properties": [ + { + "name": "ACBFDMLHFPB", + "type": "DateTime", + "accessibility": "public", + "kind": "property" + }, + { + "name": "EEOGKBHOJGL", + "type": "int", + "accessibility": "public", + "kind": "property" + }, + { + "name": "FAEKONHJKNK", + "type": "long", + "accessibility": "public", + "kind": "property" + }, + { + "name": "GBOIPGBGDDG", + "type": "long", + "accessibility": "public", + "kind": "property" + }, + { + "name": "OPLHMKFCNOL", + "type": "FFEICMIIBMC", + "accessibility": "public", + "kind": "property" + } + ], + "fields": [], + "enumValues": [], + "wireKeys": [ + "PlayerEventResponseId", + "PlayerEventId", + "PlayerId", + "CreatedAt", + "Type" + ], + "isilFile": "PBIJAPEOEDO.txt", + "isilType": "PBIJAPEOEDO" + } + }, + { + "kind": "object", + "raw": "TPlayerEventDTO", + "rootType": "TPlayerEventDTO", + "typeDescription": { + "name": "TPlayerEventDTO", + "found": false, + "kind": "object", + "wireKeys": [] + } + }, + { + "kind": "unknown", + "raw": "", + "rootType": "unknown", + "typeDescription": { + "name": "unknown", + "found": false, + "kind": "object", + "wireKeys": [] + } + } + ] + } + }, + "/api/playerevents/v1/all": { + "get": { + "tags": [ + "player-events" + ], + "summary": "GET api/playerevents/v1/all", + "description": "Inferred from Rec Room 2020 client literal `api/playerevents/v1/all`. Expected result: Player event object/list/page or event mutation success.", + "operationId": "client_player_events_api_playerevents_v1_all_get", + "parameters": [], + "responses": { + "200": { + "description": "Player event object/list/page or event mutation success.", + "content": { + "application/json": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/PlayerEvent" + } + } + } + } + }, + "204": { + "description": "Success with no body, used by some mutation endpoints." + }, + "400": { + "description": "Client-visible validation or bad request error.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "401": { + "description": "Authentication required or token invalid.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "500": { + "description": "Server error shape tolerated by client wrappers.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + } + }, + "x-recroom-client-route-literal": "api/playerevents/v1/all", + "x-recroom-client-family": "player-events", + "x-recroom-client-service": "api", + "x-recroom-builds": [ + "2020.12.18" + ], + "x-recroom-inferred-http-method": "get", + "x-recroom-evidence": [ + { + "type": "GNPDMBPGHBH", + "method": "RecRoom.Async.IPromise`1 JKFDNKOBOJE()", + "file": "GNPDMBPGHBH.txt", + "line": 5504 + } + ], + "x-client-response-contracts": [ + { + "kind": "object", + "raw": "FNBMIJGOOJM", + "rootType": "FNBMIJGOOJM", + "typeDescription": { + "name": "FNBMIJGOOJM", + "found": true, + "file": "FNBMIJGOOJM.cs", + "declaration": "public class FNBMIJGOOJM : IFAIJAGLDFK", + "kind": "class", + "baseType": "IFAIJAGLDFK", + "properties": [ + { + "name": "APGINOKLOIF", + "type": "List", + "accessibility": "public", + "kind": "property" + }, + { + "name": "NLEGMBPCMIA", + "type": "List", + "accessibility": "public", + "kind": "property" + } + ], + "fields": [ + { + "name": "playerEventId", + "type": "long", + "kind": "field" + } + ], + "enumValues": [], + "wireKeys": [], + "isilFile": "FNBMIJGOOJM.txt", + "isilType": "FNBMIJGOOJM" + } + } + ] + } + }, + "/api/playerevents/v1/bulkInvite": { + "post": { + "tags": [ + "player-events" + ], + "summary": "POST api/playerevents/v1/bulkInvite", + "description": "Inferred from Rec Room 2020 client literal `api/playerevents/v1/bulkInvite`. Expected result: Player event object/list/page or event mutation success.", + "operationId": "client_player_events_api_playerevents_v1_bulkinvite_post", + "parameters": [], + "responses": { + "200": { + "description": "Player event object/list/page or event mutation success.", + "content": { + "application/json": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/PlayerEvent" + } + } + } + } + }, + "204": { + "description": "Success with no body, used by some mutation endpoints." + }, + "400": { + "description": "Client-visible validation or bad request error.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "401": { + "description": "Authentication required or token invalid.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "500": { + "description": "Server error shape tolerated by client wrappers.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + } + }, + "x-recroom-client-route-literal": "api/playerevents/v1/bulkInvite", + "x-recroom-client-family": "player-events", + "x-recroom-client-service": "api", + "x-recroom-builds": [ + "2020.12.18" + ], + "x-recroom-inferred-http-method": "post", + "x-recroom-evidence": [ + { + "type": "GNPDMBPGHBH", + "method": "RecRoom.Async.IPromise`1 MGDLGLLOEMG(System.Int64 MOKMJAMNEFP, System.Collections.Generic.List`1 MHCONOPOOKJ)", + "file": "GNPDMBPGHBH.txt", + "line": 5013 + } + ], + "requestBody": { + "required": false, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/GenericObject" + } + }, + "application/x-www-form-urlencoded": { + "schema": { + "$ref": "#/components/schemas/GenericObject" + } + }, + "multipart/form-data": { + "schema": { + "$ref": "#/components/schemas/GenericObject" + } + } + }, + "description": "Request body inferred. Exact DTO may be named in x-recroom-evidence method signature or companion DTO inventory." + }, + "x-client-response-contracts": [ + { + "kind": "object", + "raw": "PDOBNLOLBAF", + "rootType": "PDOBNLOLBAF", + "typeDescription": { + "name": "PDOBNLOLBAF", + "found": true, + "file": "PDOBNLOLBAF.cs", + "declaration": "public class PDOBNLOLBAF : IFAIJAGLDFK", + "kind": "class", + "baseType": "IFAIJAGLDFK", + "properties": [ + { + "name": "DCNIKAABNGK", + "type": "List", + "accessibility": "public", + "kind": "property" + }, + { + "name": "GMFIDNACDJK", + "type": "DGOPHENCPOC", + "accessibility": "public", + "kind": "property" + } + ], + "fields": [], + "enumValues": [], + "wireKeys": [ + "Result" + ], + "isilFile": "PDOBNLOLBAF.txt", + "isilType": "PDOBNLOLBAF" + } + } + ] + } + }, + "/api/playerevents/v1/deleteResponse": { + "delete": { + "tags": [ + "player-events" + ], + "summary": "DELETE api/playerevents/v1/deleteResponse", + "description": "Inferred from Rec Room 2020 client literal `api/playerevents/v1/deleteResponse`. Expected result: Player event object/list/page or event mutation success.", + "operationId": "client_player_events_api_playerevents_v1_deleteresponse_delete", + "parameters": [], + "responses": { + "200": { + "description": "Player event object/list/page or event mutation success.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/PlayerEvent" + } + } + } + }, + "204": { + "description": "Success with no body, used by some mutation endpoints." + }, + "400": { + "description": "Client-visible validation or bad request error.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "401": { + "description": "Authentication required or token invalid.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "500": { + "description": "Server error shape tolerated by client wrappers.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + } + }, + "x-recroom-client-route-literal": "api/playerevents/v1/deleteResponse", + "x-recroom-client-family": "player-events", + "x-recroom-client-service": "api", + "x-recroom-builds": [ + "2020.12.18" + ], + "x-recroom-inferred-http-method": "delete", + "x-recroom-evidence": [ + { + "type": "GNPDMBPGHBH", + "method": "RecRoom.Async.IPromise`1 FGFDJIKJKNN(System.Int64 MOKMJAMNEFP, FFEICMIIBMC DHLDCHCKBPC)", + "file": "GNPDMBPGHBH.txt", + "line": 4769 + } + ], + "requestBody": { + "required": false, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/GenericObject" + } + }, + "application/x-www-form-urlencoded": { + "schema": { + "$ref": "#/components/schemas/GenericObject" + } + }, + "multipart/form-data": { + "schema": { + "$ref": "#/components/schemas/GenericObject" + } + } + }, + "description": "Request body inferred. Exact DTO may be named in x-recroom-evidence method signature or companion DTO inventory." + }, + "x-client-response-contracts": [ + { + "kind": "object", + "raw": "DGOPHENCPOC", + "rootType": "DGOPHENCPOC", + "typeDescription": { + "name": "DGOPHENCPOC", + "found": true, + "file": "DGOPHENCPOC.cs", + "declaration": "public enum DGOPHENCPOC", + "kind": "enum", + "baseType": null, + "properties": [], + "fields": [], + "enumValues": [ + { + "name": "Success", + "value": "0" + }, + { + "name": "HasModeratorClosedEvent", + "value": "1" + }, + { + "name": "DoesNotExist", + "value": "2" + }, + { + "name": "PlayerDoesNotExist", + "value": "3" + }, + { + "name": "RoomDoesNotExist", + "value": "4" + }, + { + "name": "StatusUnchanged", + "value": "5" + }, + { + "name": "PrivateEvent", + "value": "6" + }, + { + "name": "SomethingWentWrong", + "value": "7" + }, + { + "name": "DoesNotOwnRoom", + "value": "8" + }, + { + "name": "ResponseDoesNotExist", + "value": "9" + }, + { + "name": "PlayerAlreadyInvited", + "value": "10" + }, + { + "name": "EventDatesInvalid", + "value": "11" + }, + { + "name": "EventTooLong", + "value": "12" + }, + { + "name": "EventTooShort", + "value": "13" + }, + { + "name": "InappropriateName", + "value": "14" + }, + { + "name": "InappropriateDescription", + "value": "15" + }, + { + "name": "SomeInvitesFailed", + "value": "16" + }, + { + "name": "CannotInviteJunior", + "value": "17" + }, + { + "name": "EventCountLimitReached", + "value": "18" + }, + { + "name": "DoesNotOwnEvent", + "value": "19" + }, + { + "name": "UnregisteredOrJuniorNotAllowed", + "value": "20" + }, + { + "name": "InvalidClubPermissions", + "value": "21" + }, + { + "name": "ImageDoesNotExist", + "value": "22" + }, + { + "name": "SubRoomDoesNotExist", + "value": "23" + }, + { + "name": "DoesNotOwnSubRoom", + "value": "24" + }, + { + "name": "ModifyTagsFailed", + "value": "25" + } + ], + "wireKeys": [] + } + } + ] + } + }, + "/api/playerevents/v1/report": { + "post": { + "tags": [ + "player-events" + ], + "summary": "POST api/playerevents/v1/report", + "description": "Inferred from Rec Room 2020 client literal `api/playerevents/v1/report`. Expected result: Player event object/list/page or event mutation success.", + "operationId": "client_player_events_api_playerevents_v1_report_post", + "parameters": [], + "responses": { + "200": { + "description": "Player event object/list/page or event mutation success.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/PlayerEvent" + } + } + } + }, + "204": { + "description": "Success with no body, used by some mutation endpoints." + }, + "400": { + "description": "Client-visible validation or bad request error.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "401": { + "description": "Authentication required or token invalid.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "500": { + "description": "Server error shape tolerated by client wrappers.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + } + }, + "x-recroom-client-route-literal": "api/playerevents/v1/report", + "x-recroom-client-family": "player-events", + "x-recroom-client-service": "api", + "x-recroom-builds": [ + "2020.12.18" + ], + "x-recroom-inferred-http-method": "post", + "x-recroom-evidence": [ + { + "type": "GNPDMBPGHBH", + "method": "RecRoom.Async.IPromise`1 GLHAJLLNPBJ(System.Int64 MOKMJAMNEFP, LCCEEFHOBEN+CJFENPHAAHI MEABFEIBEMP, System.String EFDBFLPKHKA)", + "file": "GNPDMBPGHBH.txt", + "line": 5939 + } + ], + "requestBody": { + "required": false, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/GenericObject" + } + }, + "application/x-www-form-urlencoded": { + "schema": { + "$ref": "#/components/schemas/GenericObject" + } + }, + "multipart/form-data": { + "schema": { + "$ref": "#/components/schemas/GenericObject" + } + } + }, + "description": "Request body inferred. Exact DTO may be named in x-recroom-evidence method signature or companion DTO inventory." + }, + "x-client-response-contracts": [ + { + "kind": "object", + "raw": "KLAMKCBENEA", + "rootType": "KLAMKCBENEA", + "typeDescription": { + "name": "KLAMKCBENEA", + "found": true, + "file": "KLAMKCBENEA.cs", + "declaration": "public class KLAMKCBENEA : IFAIJAGLDFK", + "kind": "class", + "baseType": "IFAIJAGLDFK", + "properties": [ + { + "name": "DFPPNHINEFO", + "type": "string", + "accessibility": "public", + "kind": "property" + }, + { + "name": "ONGEANEKLNE", + "type": "bool", + "accessibility": "public", + "kind": "property" + } + ], + "fields": [], + "enumValues": [], + "wireKeys": [ + "Success", + "Message" + ], + "isilFile": "KLAMKCBENEA.txt", + "isilType": "KLAMKCBENEA" + } + } + ] + } + }, + "/api/playerevents/v1/respond": { + "get": { + "tags": [ + "player-events" + ], + "summary": "GET api/playerevents/v1/respond", + "description": "Inferred from Rec Room 2020 client literal `api/playerevents/v1/respond`. Expected result: Player event object/list/page or event mutation success.", + "operationId": "client_player_events_api_playerevents_v1_respond_get", + "parameters": [], + "responses": { + "200": { + "description": "Player event object/list/page or event mutation success.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/PlayerEvent" + } + } + } + }, + "204": { + "description": "Success with no body, used by some mutation endpoints." + }, + "400": { + "description": "Client-visible validation or bad request error.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "401": { + "description": "Authentication required or token invalid.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "500": { + "description": "Server error shape tolerated by client wrappers.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + } + }, + "x-recroom-client-route-literal": "api/playerevents/v1/respond", + "x-recroom-client-family": "player-events", + "x-recroom-client-service": "api", + "x-recroom-builds": [ + "2020.12.18" + ], + "x-recroom-inferred-http-method": "get", + "x-recroom-evidence": [ + { + "type": "GNPDMBPGHBH", + "method": "RecRoom.Async.IPromise`1 EEJGIOKDBFI(System.Int64 MOKMJAMNEFP, FFEICMIIBMC DHLDCHCKBPC)", + "file": "GNPDMBPGHBH.txt", + "line": 4567 + } + ], + "x-client-response-contracts": [ + { + "kind": "object", + "raw": "DGOPHENCPOC", + "rootType": "DGOPHENCPOC", + "typeDescription": { + "name": "DGOPHENCPOC", + "found": true, + "file": "DGOPHENCPOC.cs", + "declaration": "public enum DGOPHENCPOC", + "kind": "enum", + "baseType": null, + "properties": [], + "fields": [], + "enumValues": [ + { + "name": "Success", + "value": "0" + }, + { + "name": "HasModeratorClosedEvent", + "value": "1" + }, + { + "name": "DoesNotExist", + "value": "2" + }, + { + "name": "PlayerDoesNotExist", + "value": "3" + }, + { + "name": "RoomDoesNotExist", + "value": "4" + }, + { + "name": "StatusUnchanged", + "value": "5" + }, + { + "name": "PrivateEvent", + "value": "6" + }, + { + "name": "SomethingWentWrong", + "value": "7" + }, + { + "name": "DoesNotOwnRoom", + "value": "8" + }, + { + "name": "ResponseDoesNotExist", + "value": "9" + }, + { + "name": "PlayerAlreadyInvited", + "value": "10" + }, + { + "name": "EventDatesInvalid", + "value": "11" + }, + { + "name": "EventTooLong", + "value": "12" + }, + { + "name": "EventTooShort", + "value": "13" + }, + { + "name": "InappropriateName", + "value": "14" + }, + { + "name": "InappropriateDescription", + "value": "15" + }, + { + "name": "SomeInvitesFailed", + "value": "16" + }, + { + "name": "CannotInviteJunior", + "value": "17" + }, + { + "name": "EventCountLimitReached", + "value": "18" + }, + { + "name": "DoesNotOwnEvent", + "value": "19" + }, + { + "name": "UnregisteredOrJuniorNotAllowed", + "value": "20" + }, + { + "name": "InvalidClubPermissions", + "value": "21" + }, + { + "name": "ImageDoesNotExist", + "value": "22" + }, + { + "name": "SubRoomDoesNotExist", + "value": "23" + }, + { + "name": "DoesNotOwnSubRoom", + "value": "24" + }, + { + "name": "ModifyTagsFailed", + "value": "25" + } + ], + "wireKeys": [] + } + } + ] + } + }, + "/api/playerevents/v1/search": { + "post": { + "tags": [ + "player-events" + ], + "summary": "POST api/playerevents/v1/search?", + "description": "Inferred from Rec Room 2020 client literal `api/playerevents/v1/search?`. Expected result: Player event object/list/page or event mutation success.", + "operationId": "client_player_events_api_playerevents_v1_search_post", + "parameters": [], + "responses": { + "200": { + "description": "Player event object/list/page or event mutation success.", + "content": { + "application/json": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/PlayerEvent" + } + } + } + } + }, + "204": { + "description": "Success with no body, used by some mutation endpoints." + }, + "400": { + "description": "Client-visible validation or bad request error.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "401": { + "description": "Authentication required or token invalid.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "500": { + "description": "Server error shape tolerated by client wrappers.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + } + }, + "x-recroom-client-route-literal": "api/playerevents/v1/search?", + "x-recroom-client-family": "player-events", + "x-recroom-client-service": "api", + "x-recroom-builds": [ + "2020.12.18" + ], + "x-recroom-inferred-http-method": "post", + "x-recroom-evidence": [ + { + "type": "GNPDMBPGHBH", + "method": "RecRoom.Async.IPromise`1> ADMFNGBFPIE(System.String CNBKKCJAHPP, GNPDMBPGHBH+EHKGCEONBIB DKLJMLFNEEN = 0, System.Nullable`1 LBFLCMJFDPC = null)", + "file": "GNPDMBPGHBH.txt", + "line": 9248 + } + ], + "requestBody": { + "required": false, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/GenericObject" + } + }, + "application/x-www-form-urlencoded": { + "schema": { + "$ref": "#/components/schemas/GenericObject" + } + }, + "multipart/form-data": { + "schema": { + "$ref": "#/components/schemas/GenericObject" + } + } + }, + "description": "Request body inferred. Exact DTO may be named in x-recroom-evidence method signature or companion DTO inventory." + }, + "x-client-response-contracts": [ + { + "kind": "array", + "raw": "System.Collections.Generic.List`1", + "rootType": "CCMBKDINCAH", + "item": { + "kind": "object", + "raw": "CCMBKDINCAH", + "rootType": "CCMBKDINCAH" + }, + "typeDescription": { + "name": "CCMBKDINCAH", + "found": true, + "file": "CCMBKDINCAH.cs", + "declaration": "public class CCMBKDINCAH : IFAIJAGLDFK", + "kind": "class", + "baseType": "IFAIJAGLDFK", + "properties": [ + { + "name": "ACHMMBMLGEK", + "type": "int", + "accessibility": "public", + "kind": "property" + }, + { + "name": "AHGCOGFEEEE", + "type": "string", + "accessibility": "public", + "kind": "property" + }, + { + "name": "CCGOEDABKNN", + "type": "Nullable", + "accessibility": "public", + "kind": "property" + }, + { + "name": "DADOKMAOFJL", + "type": "long", + "accessibility": "public", + "kind": "property" + }, + { + "name": "FIKEBGGCDFN", + "type": "string", + "accessibility": "public", + "kind": "property" + }, + { + "name": "GBOIPGBGDDG", + "type": "long", + "accessibility": "public", + "kind": "property" + }, + { + "name": "GIHDFJNGFHH", + "type": "int", + "accessibility": "public", + "kind": "property" + }, + { + "name": "GJPHHEHBCIJ", + "type": "Nullable", + "accessibility": "public", + "kind": "property" + }, + { + "name": "JFEAPMIPNEP", + "type": "CMCAFKLAHCD", + "accessibility": "public", + "kind": "property" + }, + { + "name": "KJDKJFLBFOL", + "type": "bool", + "accessibility": "public", + "kind": "property" + }, + { + "name": "KMOAHFBMONE", + "type": "bool", + "accessibility": "public", + "kind": "property" + }, + { + "name": "KODBEJPEFOJ", + "type": "string", + "accessibility": "public", + "kind": "property" + }, + { + "name": "LCEFHPHIHBP", + "type": "bool", + "accessibility": "public", + "kind": "property" + }, + { + "name": "LIPFBIGFEBG", + "type": "DateTime", + "accessibility": "public", + "kind": "property" + }, + { + "name": "MAFEFHEPIKI", + "type": "DateTime", + "accessibility": "public", + "kind": "property" + } + ], + "fields": [], + "enumValues": [], + "wireKeys": [ + "PlayerEventId", + "Name", + "Description", + "ImageName", + "StartTime", + "EndTime", + "CreatorPlayerId", + "AttendeeCount", + "RoomId", + "Accessibility" + ], + "isilFile": "CCMBKDINCAH.txt", + "isilType": "CCMBKDINCAH" + } + } + ] + } + }, + "/api/playerevents/v1/searchlive": { + "get": { + "tags": [ + "player-events" + ], + "summary": "GET api/playerevents/v1/searchlive?", + "description": "Inferred from Rec Room 2020 client literal `api/playerevents/v1/searchlive?`. Expected result: Player event object/list/page or event mutation success.", + "operationId": "client_player_events_api_playerevents_v1_searchlive_get", + "parameters": [], + "responses": { + "200": { + "description": "Player event object/list/page or event mutation success.", + "content": { + "application/json": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/PlayerEvent" + } + } + } + } + }, + "204": { + "description": "Success with no body, used by some mutation endpoints." + }, + "400": { + "description": "Client-visible validation or bad request error.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "401": { + "description": "Authentication required or token invalid.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "500": { + "description": "Server error shape tolerated by client wrappers.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + } + }, + "x-recroom-client-route-literal": "api/playerevents/v1/searchlive?", + "x-recroom-client-family": "player-events", + "x-recroom-client-service": "api", + "x-recroom-builds": [ + "2020.12.18" + ], + "x-recroom-inferred-http-method": "get", + "x-recroom-evidence": [ + { + "type": "GNPDMBPGHBH", + "method": "RecRoom.Async.IPromise`1> JBOMFDKLBOI(System.String CNBKKCJAHPP)", + "file": "GNPDMBPGHBH.txt", + "line": 8935 + } + ], + "x-client-response-contracts": [ + { + "kind": "array", + "raw": "System.Collections.Generic.List`1", + "rootType": "NMKMJKIHDNE", + "item": { + "kind": "object", + "raw": "NMKMJKIHDNE", + "rootType": "NMKMJKIHDNE" + }, + "typeDescription": { + "name": "NMKMJKIHDNE", + "found": true, + "file": "NMKMJKIHDNE.cs", + "declaration": "public class NMKMJKIHDNE : CCMBKDINCAH", + "kind": "class", + "baseType": "CCMBKDINCAH", + "properties": [ + { + "name": "FDMFNIJKHKC", + "type": "bool", + "accessibility": "public", + "kind": "property" + }, + { + "name": "HLFNLKMCNAC", + "type": "int", + "accessibility": "public", + "kind": "property" + } + ], + "fields": [], + "enumValues": [], + "wireKeys": [ + "PlayerCount", + "IsFull" + ], + "isilFile": "NMKMJKIHDNE.txt", + "isilType": "NMKMJKIHDNE", + "inherits": "CCMBKDINCAH", + "inheritedMembers": [ + { + "name": "ACHMMBMLGEK", + "type": "int", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "CCMBKDINCAH" + }, + { + "name": "AHGCOGFEEEE", + "type": "string", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "CCMBKDINCAH" + }, + { + "name": "CCGOEDABKNN", + "type": "Nullable", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "CCMBKDINCAH" + }, + { + "name": "DADOKMAOFJL", + "type": "long", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "CCMBKDINCAH" + }, + { + "name": "FIKEBGGCDFN", + "type": "string", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "CCMBKDINCAH" + }, + { + "name": "GBOIPGBGDDG", + "type": "long", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "CCMBKDINCAH" + }, + { + "name": "GIHDFJNGFHH", + "type": "int", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "CCMBKDINCAH" + }, + { + "name": "GJPHHEHBCIJ", + "type": "Nullable", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "CCMBKDINCAH" + }, + { + "name": "JFEAPMIPNEP", + "type": "CMCAFKLAHCD", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "CCMBKDINCAH" + }, + { + "name": "KJDKJFLBFOL", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "CCMBKDINCAH" + }, + { + "name": "KMOAHFBMONE", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "CCMBKDINCAH" + }, + { + "name": "KODBEJPEFOJ", + "type": "string", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "CCMBKDINCAH" + }, + { + "name": "LCEFHPHIHBP", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "CCMBKDINCAH" + }, + { + "name": "LIPFBIGFEBG", + "type": "DateTime", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "CCMBKDINCAH" + }, + { + "name": "MAFEFHEPIKI", + "type": "DateTime", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "CCMBKDINCAH" + } + ], + "inheritedWireKeys": [ + "PlayerEventId", + "Name", + "Description", + "ImageName", + "StartTime", + "EndTime", + "CreatorPlayerId", + "AttendeeCount", + "RoomId", + "Accessibility" + ] + } + } + ] + } + }, + "/api/playerevents/v1/tagfilters": { + "post": { + "tags": [ + "player-events" + ], + "summary": "POST api/playerevents/v1/tagfilters", + "description": "Inferred from Rec Room 2020 client literal `api/playerevents/v1/tagfilters`. Expected result: Player event object/list/page or event mutation success.", + "operationId": "client_player_events_api_playerevents_v1_tagfilters_post", + "parameters": [], + "responses": { + "200": { + "description": "Player event object/list/page or event mutation success.", + "content": { + "application/json": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/PlayerEvent" + } + } + } + } + }, + "204": { + "description": "Success with no body, used by some mutation endpoints." + }, + "400": { + "description": "Client-visible validation or bad request error.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "401": { + "description": "Authentication required or token invalid.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "500": { + "description": "Server error shape tolerated by client wrappers.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + } + }, + "x-recroom-client-route-literal": "api/playerevents/v1/tagfilters", + "x-recroom-client-family": "player-events", + "x-recroom-client-service": "api", + "x-recroom-builds": [ + "2020.12.18" + ], + "x-recroom-inferred-http-method": "post", + "x-recroom-evidence": [ + { + "type": "GNPDMBPGHBH", + "method": "RecRoom.Async.IPromise`1> LIPBAJOAIJC(GNPDMBPGHBH+FFCCJHEPNDK KINAEJLCDEG)", + "file": "GNPDMBPGHBH.txt", + "line": 9697 + } + ], + "requestBody": { + "required": false, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/GenericObject" + } + }, + "application/x-www-form-urlencoded": { + "schema": { + "$ref": "#/components/schemas/GenericObject" + } + }, + "multipart/form-data": { + "schema": { + "$ref": "#/components/schemas/GenericObject" + } + } + }, + "description": "Request body inferred. Exact DTO may be named in x-recroom-evidence method signature or companion DTO inventory." + }, + "x-client-response-contracts": [ + { + "kind": "array", + "raw": "System.Collections.Generic.List`1", + "rootType": "String", + "item": { + "kind": "primitive", + "raw": "System.String", + "rootType": "string" + }, + "typeDescription": { + "name": "String", + "found": false, + "kind": "primitive", + "wireKeys": [] + } + } + ] + } + }, + "/api/playerevents/v2": { + "get": { + "tags": [ + "player-events" + ], + "summary": "GET api/playerevents/v2", + "description": "Inferred from Rec Room 2020 client literal `api/playerevents/v2`. Expected result: Player event object/list/page or event mutation success.", + "operationId": "client_player_events_api_playerevents_v2_get", + "parameters": [], + "responses": { + "200": { + "description": "Player event object/list/page or event mutation success.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/PlayerEvent" + } + } + } + }, + "204": { + "description": "Success with no body, used by some mutation endpoints." + }, + "400": { + "description": "Client-visible validation or bad request error.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "401": { + "description": "Authentication required or token invalid.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "500": { + "description": "Server error shape tolerated by client wrappers.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + } + }, + "x-recroom-client-route-literal": "api/playerevents/v2", + "x-recroom-client-family": "player-events", + "x-recroom-client-service": "api", + "x-recroom-builds": [ + "2020.12.18" + ], + "x-recroom-inferred-http-method": "get", + "x-recroom-evidence": [ + { + "type": "GNPDMBPGHBH", + "method": "RecRoom.Async.IPromise`1 BBPEHLACMLH(System.Int64 HNHLJONGKHB, System.Nullable`1 FODGKNJIGOP, System.Nullable`1 AOMBLGBCENO, System.String MMBOKOLAJFH, System.String LJIGOCDPEJF, System.Collections.Generic.List`1 CAFPJPHILMN, System.String HFLPBHHAFIO, System.DateTime JODPOANPJNK, System.DateTime BCANLCHBKJE, CMCAFKLAHCD PONCIIJOHIE)", + "file": "GNPDMBPGHBH.txt", + "line": 4067 + } + ], + "x-client-response-contracts": [ + { + "kind": "object", + "raw": "LLNPLLBEJBE", + "rootType": "LLNPLLBEJBE", + "typeDescription": { + "name": "LLNPLLBEJBE", + "found": true, + "file": "LLNPLLBEJBE.cs", + "declaration": "public class LLNPLLBEJBE : IFAIJAGLDFK", + "kind": "class", + "baseType": "IFAIJAGLDFK", + "properties": [ + { + "name": "AANNMHFBONM", + "type": "CCMBKDINCAH", + "accessibility": "public", + "kind": "property" + }, + { + "name": "GMFIDNACDJK", + "type": "DGOPHENCPOC", + "accessibility": "public", + "kind": "property" + }, + { + "name": "POJDHKAOINA", + "type": "NJMAEIPIOAP", + "accessibility": "public", + "kind": "property" + } + ], + "fields": [], + "enumValues": [], + "wireKeys": [ + "PlayerEvent", + "Result", + "TagModifyResult" + ], + "isilFile": "LLNPLLBEJBE.txt", + "isilType": "LLNPLLBEJBE" + } + } + ] + } + }, + "/api/playerReputation/v1/{p0}": { + "get": { + "tags": [ + "players" + ], + "summary": "GET /api/playerReputation/v1/{0}", + "description": "Inferred from Rec Room 2020 client literal `/api/playerReputation/v1/{0}`. Expected result: Player progression/reputation/objective state.", + "operationId": "client_players_api_playerreputation_v1_p0_get", + "parameters": [ + { + "name": "p0", + "in": "path", + "required": true, + "schema": { + "type": "string" + }, + "description": "Format placeholder from client literal /api/playerReputation/v1/{0}" + } + ], + "responses": { + "200": { + "description": "Player progression/reputation/objective state.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/PlayerState" + } + } + } + }, + "204": { + "description": "Success with no body, used by some mutation endpoints." + }, + "400": { + "description": "Client-visible validation or bad request error.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "401": { + "description": "Authentication required or token invalid.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "500": { + "description": "Server error shape tolerated by client wrappers.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + } + }, + "x-recroom-client-route-literal": "/api/playerReputation/v1/{0}", + "x-recroom-client-family": "players", + "x-recroom-client-service": "api", + "x-recroom-builds": [ + "2020.03.10", + "2020.12.18" + ], + "x-recroom-inferred-http-method": "get", + "x-recroom-evidence": [ + { + "type": "EBAIPNMBKLK", + "method": "RecRoom.Async.IPromise`1 JNLDMDCGHHJ(System.Int32 GKLPIFBPGOD)", + "file": "EBAIPNMBKLK.txt", + "line": 1231 + }, + { + "type": "stringliteral.json", + "method": "", + "file": "Recnet-archive/decompiled/Il2CppDump/stringliteral.json", + "line": null + } + ], + "x-client-response-contracts": [ + { + "kind": "object", + "raw": "JGEBJEJLNBK", + "rootType": "JGEBJEJLNBK", + "typeDescription": { + "name": "JGEBJEJLNBK", + "found": true, + "file": "JGEBJEJLNBK.cs", + "declaration": "public class JGEBJEJLNBK : IFAIJAGLDFK", + "kind": "class", + "baseType": "IFAIJAGLDFK", + "properties": [ + { + "name": "AONADBLHNEE", + "type": "int", + "accessibility": "public", + "kind": "property" + }, + { + "name": "APDEKEIBJPJ", + "type": "float", + "accessibility": "public", + "kind": "property" + }, + { + "name": "GAINIOENNCG", + "type": "int", + "accessibility": "public", + "kind": "property" + }, + { + "name": "GKBDOIPKEID", + "type": "Nullable", + "accessibility": "public", + "kind": "property" + }, + { + "name": "JABKMGPLDCO", + "type": "Nullable", + "accessibility": "public", + "kind": "property" + }, + { + "name": "JKMJNPJFGNC", + "type": "int", + "accessibility": "public", + "kind": "property" + }, + { + "name": "LCHBNILBADA", + "type": "bool", + "accessibility": "public", + "kind": "property" + }, + { + "name": "MOGEADHIOBD", + "type": "int", + "accessibility": "public", + "kind": "property" + }, + { + "name": "NGPDMPELINB", + "type": "int", + "accessibility": "public", + "kind": "property" + }, + { + "name": "OCJDOOKPBAE", + "type": "int", + "accessibility": "public", + "kind": "property" + }, + { + "name": "PEDJLAMDNNA", + "type": "int", + "accessibility": "public", + "kind": "property" + } + ], + "fields": [ + { + "name": "cheer", + "type": "PIDFCOGLFHN", + "kind": "field" + } + ], + "enumValues": [], + "wireKeys": [ + "AccountId", + "Noteriety", + "CheerGeneral", + "CheerHelpful", + "CheerGreatHost", + "CheerSportsman", + "CheerCreative", + "CheerCredit", + "SelectedCheer" + ], + "isilFile": "JGEBJEJLNBK.txt", + "isilType": "JGEBJEJLNBK" + } + } + ] + } + }, + "/api/playerReputation/v1/bulk": { + "post": { + "tags": [ + "players" + ], + "summary": "POST /api/playerReputation/v1/bulk", + "description": "Inferred from Rec Room 2020 client literal `/api/playerReputation/v1/bulk`. Expected result: Player progression/reputation/objective state.", + "operationId": "client_players_api_playerreputation_v1_bulk_post", + "parameters": [], + "responses": { + "200": { + "description": "Player progression/reputation/objective state.", + "content": { + "application/json": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/PlayerState" + } + } + } + } + }, + "204": { + "description": "Success with no body, used by some mutation endpoints." + }, + "400": { + "description": "Client-visible validation or bad request error.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "401": { + "description": "Authentication required or token invalid.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "500": { + "description": "Server error shape tolerated by client wrappers.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + } + }, + "x-recroom-client-route-literal": "/api/playerReputation/v1/bulk", + "x-recroom-client-family": "players", + "x-recroom-client-service": "api", + "x-recroom-builds": [ + "2020.03.10", + "2020.12.18" + ], + "x-recroom-inferred-http-method": "post", + "x-recroom-evidence": [ + { + "type": "EBAIPNMBKLK", + "method": "RecRoom.Async.IPromise`1> KGPDFJMKJKL(System.Collections.Generic.List`1 ILNGMAANNDG)", + "file": "EBAIPNMBKLK.txt", + "line": 1631 + }, + { + "type": "stringliteral.json", + "method": "", + "file": "Recnet-archive/decompiled/Il2CppDump/stringliteral.json", + "line": null + } + ], + "requestBody": { + "required": false, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/GenericObject" + } + }, + "application/x-www-form-urlencoded": { + "schema": { + "$ref": "#/components/schemas/GenericObject" + } + }, + "multipart/form-data": { + "schema": { + "$ref": "#/components/schemas/GenericObject" + } + } + }, + "description": "Request body inferred. Exact DTO may be named in x-recroom-evidence method signature or companion DTO inventory." + }, + "x-client-response-contracts": [ + { + "kind": "array", + "raw": "System.Collections.Generic.List`1", + "rootType": "JGEBJEJLNBK", + "item": { + "kind": "object", + "raw": "JGEBJEJLNBK", + "rootType": "JGEBJEJLNBK" + }, + "typeDescription": { + "name": "JGEBJEJLNBK", + "found": true, + "file": "JGEBJEJLNBK.cs", + "declaration": "public class JGEBJEJLNBK : IFAIJAGLDFK", + "kind": "class", + "baseType": "IFAIJAGLDFK", + "properties": [ + { + "name": "AONADBLHNEE", + "type": "int", + "accessibility": "public", + "kind": "property" + }, + { + "name": "APDEKEIBJPJ", + "type": "float", + "accessibility": "public", + "kind": "property" + }, + { + "name": "GAINIOENNCG", + "type": "int", + "accessibility": "public", + "kind": "property" + }, + { + "name": "GKBDOIPKEID", + "type": "Nullable", + "accessibility": "public", + "kind": "property" + }, + { + "name": "JABKMGPLDCO", + "type": "Nullable", + "accessibility": "public", + "kind": "property" + }, + { + "name": "JKMJNPJFGNC", + "type": "int", + "accessibility": "public", + "kind": "property" + }, + { + "name": "LCHBNILBADA", + "type": "bool", + "accessibility": "public", + "kind": "property" + }, + { + "name": "MOGEADHIOBD", + "type": "int", + "accessibility": "public", + "kind": "property" + }, + { + "name": "NGPDMPELINB", + "type": "int", + "accessibility": "public", + "kind": "property" + }, + { + "name": "OCJDOOKPBAE", + "type": "int", + "accessibility": "public", + "kind": "property" + }, + { + "name": "PEDJLAMDNNA", + "type": "int", + "accessibility": "public", + "kind": "property" + } + ], + "fields": [ + { + "name": "cheer", + "type": "PIDFCOGLFHN", + "kind": "field" + } + ], + "enumValues": [], + "wireKeys": [ + "AccountId", + "Noteriety", + "CheerGeneral", + "CheerHelpful", + "CheerGreatHost", + "CheerSportsman", + "CheerCreative", + "CheerCredit", + "SelectedCheer" + ], + "isilFile": "JGEBJEJLNBK.txt", + "isilType": "JGEBJEJLNBK" + } + } + ] + } + }, + "/api/players/v1/progression/{p0}": { + "get": { + "tags": [ + "players" + ], + "summary": "GET /api/players/v1/progression/{0}", + "description": "Inferred from Rec Room 2020 client literal `/api/players/v1/progression/{0}`. Expected result: Player progression/reputation/objective state.", + "operationId": "client_players_api_players_v1_progression_p0_get", + "parameters": [ + { + "name": "p0", + "in": "path", + "required": true, + "schema": { + "type": "string" + }, + "description": "Format placeholder from client literal /api/players/v1/progression/{0}" + } + ], + "responses": { + "200": { + "description": "Player progression/reputation/objective state.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/PlayerState" + } + } + } + }, + "204": { + "description": "Success with no body, used by some mutation endpoints." + }, + "400": { + "description": "Client-visible validation or bad request error.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "401": { + "description": "Authentication required or token invalid.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "500": { + "description": "Server error shape tolerated by client wrappers.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + } + }, + "x-recroom-client-route-literal": "/api/players/v1/progression/{0}", + "x-recroom-client-family": "players", + "x-recroom-client-service": "api", + "x-recroom-builds": [ + "2020.03.10", + "2020.12.18" + ], + "x-recroom-inferred-http-method": "get", + "x-recroom-evidence": [ + { + "type": "ACJLCBNBJDK", + "method": "RecRoom.Async.IPromise`1 AAGPDEDFCNI(System.Int32 GKLPIFBPGOD)", + "file": "ACJLCBNBJDK.txt", + "line": 1673 + }, + { + "type": "stringliteral.json", + "method": "", + "file": "Recnet-archive/decompiled/Il2CppDump/stringliteral.json", + "line": null + } + ], + "x-client-response-contracts": [ + { + "kind": "object", + "raw": "NFMBDLEJEDD", + "rootType": "NFMBDLEJEDD", + "typeDescription": { + "name": "NFMBDLEJEDD", + "found": true, + "file": "NFMBDLEJEDD.cs", + "declaration": "public class NFMBDLEJEDD : IFAIJAGLDFK", + "kind": "class", + "baseType": "IFAIJAGLDFK", + "properties": [ + { + "name": "ANKCGDNCABN", + "type": "float", + "accessibility": "public", + "kind": "property" + }, + { + "name": "FHPGELHJCFG", + "type": "int", + "accessibility": "public", + "kind": "property" + }, + { + "name": "GAINIOENNCG", + "type": "int", + "accessibility": "public", + "kind": "property" + }, + { + "name": "JKJIMIDBCIH", + "type": "int", + "accessibility": "public", + "kind": "property" + }, + { + "name": "LINLLHBFIKF", + "type": "int", + "accessibility": "public", + "kind": "property" + } + ], + "fields": [], + "enumValues": [], + "wireKeys": [ + "PlayerId", + "Level", + "XP" + ], + "isilFile": "NFMBDLEJEDD.txt", + "isilType": "NFMBDLEJEDD" + } + } + ] + } + }, + "/api/players/v1/progression/bulk": { + "post": { + "tags": [ + "players" + ], + "summary": "POST /api/players/v1/progression/bulk", + "description": "Inferred from Rec Room 2020 client literal `/api/players/v1/progression/bulk`. Expected result: Player progression/reputation/objective state.", + "operationId": "client_players_api_players_v1_progression_bulk_post", + "parameters": [], + "responses": { + "200": { + "description": "Player progression/reputation/objective state.", + "content": { + "application/json": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/PlayerState" + } + } + } + } + }, + "204": { + "description": "Success with no body, used by some mutation endpoints." + }, + "400": { + "description": "Client-visible validation or bad request error.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "401": { + "description": "Authentication required or token invalid.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "500": { + "description": "Server error shape tolerated by client wrappers.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + } + }, + "x-recroom-client-route-literal": "/api/players/v1/progression/bulk", + "x-recroom-client-family": "players", + "x-recroom-client-service": "api", + "x-recroom-builds": [ + "2020.03.10", + "2020.12.18" + ], + "x-recroom-inferred-http-method": "post", + "x-recroom-evidence": [ + { + "type": "ACJLCBNBJDK", + "method": "RecRoom.Async.IPromise`1> MCFMHADBDBI(System.Collections.Generic.List`1 ILNGMAANNDG)", + "file": "ACJLCBNBJDK.txt", + "line": 2051 + }, + { + "type": "stringliteral.json", + "method": "", + "file": "Recnet-archive/decompiled/Il2CppDump/stringliteral.json", + "line": null + } + ], + "requestBody": { + "required": false, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/GenericObject" + } + }, + "application/x-www-form-urlencoded": { + "schema": { + "$ref": "#/components/schemas/GenericObject" + } + }, + "multipart/form-data": { + "schema": { + "$ref": "#/components/schemas/GenericObject" + } + } + }, + "description": "Request body inferred. Exact DTO may be named in x-recroom-evidence method signature or companion DTO inventory." + }, + "x-client-response-contracts": [ + { + "kind": "array", + "raw": "System.Collections.Generic.List`1", + "rootType": "NFMBDLEJEDD", + "item": { + "kind": "object", + "raw": "NFMBDLEJEDD", + "rootType": "NFMBDLEJEDD" + }, + "typeDescription": { + "name": "NFMBDLEJEDD", + "found": true, + "file": "NFMBDLEJEDD.cs", + "declaration": "public class NFMBDLEJEDD : IFAIJAGLDFK", + "kind": "class", + "baseType": "IFAIJAGLDFK", + "properties": [ + { + "name": "ANKCGDNCABN", + "type": "float", + "accessibility": "public", + "kind": "property" + }, + { + "name": "FHPGELHJCFG", + "type": "int", + "accessibility": "public", + "kind": "property" + }, + { + "name": "GAINIOENNCG", + "type": "int", + "accessibility": "public", + "kind": "property" + }, + { + "name": "JKJIMIDBCIH", + "type": "int", + "accessibility": "public", + "kind": "property" + }, + { + "name": "LINLLHBFIKF", + "type": "int", + "accessibility": "public", + "kind": "property" + } + ], + "fields": [], + "enumValues": [], + "wireKeys": [ + "PlayerId", + "Level", + "XP" + ], + "isilFile": "NFMBDLEJEDD.txt", + "isilType": "NFMBDLEJEDD" + } + } + ] + } + }, + "/api/players/v2/objectives": { + "get": { + "tags": [ + "players" + ], + "summary": "GET api/players/v2/objectives", + "description": "Inferred from Rec Room 2020 client literal `api/players/v2/objectives`. Expected result: Player progression/reputation/objective state.", + "operationId": "client_players_api_players_v2_objectives_get", + "parameters": [], + "responses": { + "200": { + "description": "Player progression/reputation/objective state.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/PlayerState" + } + } + } + }, + "204": { + "description": "Success with no body, used by some mutation endpoints." + }, + "400": { + "description": "Client-visible validation or bad request error.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "401": { + "description": "Authentication required or token invalid.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "500": { + "description": "Server error shape tolerated by client wrappers.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + } + }, + "x-recroom-client-route-literal": "api/players/v2/objectives", + "x-recroom-client-family": "players", + "x-recroom-client-service": "api", + "x-recroom-builds": [ + "2020.03.10", + "2020.12.18" + ], + "x-recroom-inferred-http-method": "get", + "x-recroom-evidence": [ + { + "type": "ACJLCBNBJDK", + "method": "System.Void EIFPPIPOIHB(System.Collections.Generic.List`1 DBDFCMJFNEC)", + "file": "ACJLCBNBJDK.txt", + "line": 816 + }, + { + "type": "stringliteral.json", + "method": "", + "file": "Recnet-archive/decompiled/Il2CppDump/stringliteral.json", + "line": null + } + ], + "x-client-response-contracts": [ + { + "kind": "unknown", + "raw": "", + "rootType": "unknown", + "typeDescription": { + "name": "unknown", + "found": false, + "kind": "object", + "wireKeys": [] + } + } + ] + } + }, + "/api/playersubscriptions/{tail}": { + "get": { + "tags": [ + "players" + ], + "summary": "GET api/playersubscriptions/", + "description": "Inferred from Rec Room 2020 client literal `api/playersubscriptions/`. Expected result: Player progression/reputation/objective state.", + "operationId": "client_players_api_playersubscriptions_tail_get", + "parameters": [ + { + "name": "tail", + "in": "path", + "required": true, + "schema": { + "type": "string" + }, + "description": "Format placeholder from client literal api/playersubscriptions/" + } + ], + "responses": { + "200": { + "description": "Player progression/reputation/objective state.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/PlayerState" + } + } + } + }, + "204": { + "description": "Success with no body, used by some mutation endpoints." + }, + "400": { + "description": "Client-visible validation or bad request error.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "401": { + "description": "Authentication required or token invalid.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "500": { + "description": "Server error shape tolerated by client wrappers.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + } + }, + "x-recroom-client-route-literal": "api/playersubscriptions/", + "x-recroom-client-family": "players", + "x-recroom-client-service": "api", + "x-recroom-builds": [ + "2020.03.10" + ], + "x-recroom-inferred-http-method": "get", + "x-recroom-evidence": [ + { + "type": "stringliteral.json", + "method": "", + "file": "Recnet-archive/decompiled/Il2CppDump/stringliteral.json", + "line": null + } + ] + } + }, + "/featuredrooms/current": { + "get": { + "tags": [ + "playlists" + ], + "summary": "GET featuredrooms/current", + "description": "Inferred from Rec Room 2020 client literal `featuredrooms/current`. Expected result: Playlist object/list or playlist mutation success.", + "operationId": "client_playlists_featuredrooms_current_get", + "parameters": [], + "responses": { + "200": { + "description": "Playlist object/list or playlist mutation success.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Playlist" + } + } + } + }, + "204": { + "description": "Success with no body, used by some mutation endpoints." + }, + "400": { + "description": "Client-visible validation or bad request error.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "401": { + "description": "Authentication required or token invalid.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "500": { + "description": "Server error shape tolerated by client wrappers.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + } + }, + "x-recroom-client-route-literal": "featuredrooms/current", + "x-recroom-client-family": "playlists", + "x-recroom-client-service": "rooms/api", + "x-recroom-builds": [ + "2020.12.18" + ], + "x-recroom-inferred-http-method": "get", + "x-recroom-evidence": [ + { + "type": "EJDCNGBEICB", + "method": "RecRoom.Async.IPromise`1 CFKDADKHAGB()", + "file": "EJDCNGBEICB.txt", + "line": 2687 + } + ], + "x-client-response-contracts": [ + { + "kind": "object", + "raw": "NMPFCIJPODA", + "rootType": "NMPFCIJPODA", + "typeDescription": { + "name": "NMPFCIJPODA", + "found": true, + "file": "NMPFCIJPODA.cs", + "declaration": "public class NMPFCIJPODA : IFAIJAGLDFK", + "kind": "class", + "baseType": "IFAIJAGLDFK", + "properties": [ + { + "name": "EHDPEFMCIIC", + "type": "long", + "accessibility": "public", + "kind": "property" + }, + { + "name": "FIKEBGGCDFN", + "type": "string", + "accessibility": "public", + "kind": "property" + }, + { + "name": "HEFJFKFNNCJ", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + } + ], + "fields": [], + "enumValues": [], + "wireKeys": [ + "FeaturedRoomGroupId", + "Name" + ], + "isilFile": "NMPFCIJPODA.txt", + "isilType": "NMPFCIJPODA" + } + } + ] + } + }, + "/playlists/{p0}/accessibility": { + "post": { + "tags": [ + "playlists" + ], + "summary": "POST playlists/{0}/accessibility", + "description": "Inferred from Rec Room 2020 client literal `playlists/{0}/accessibility`. Expected result: Playlist object/list or playlist mutation success.", + "operationId": "client_playlists_playlists_p0_accessibility_post", + "parameters": [ + { + "name": "p0", + "in": "path", + "required": true, + "schema": { + "type": "string" + }, + "description": "Format placeholder from client literal playlists/{0}/accessibility" + } + ], + "responses": { + "200": { + "description": "Playlist object/list or playlist mutation success.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Playlist" + } + } + } + }, + "204": { + "description": "Success with no body, used by some mutation endpoints." + }, + "400": { + "description": "Client-visible validation or bad request error.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "401": { + "description": "Authentication required or token invalid.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "500": { + "description": "Server error shape tolerated by client wrappers.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + } + }, + "x-recroom-client-route-literal": "playlists/{0}/accessibility", + "x-recroom-client-family": "playlists", + "x-recroom-client-service": "rooms/api", + "x-recroom-builds": [ + "2020.12.18" + ], + "x-recroom-inferred-http-method": "post", + "x-recroom-evidence": [ + { + "type": "EJDCNGBEICB", + "method": "RecRoom.Async.IPromise`1 KLHJFKPFPIC(System.Int64 DDMPFMPILCE, DPLPMKMFMPB PONCIIJOHIE)", + "file": "EJDCNGBEICB.txt", + "line": 7323 + } + ], + "requestBody": { + "required": false, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/GenericObject" + } + }, + "application/x-www-form-urlencoded": { + "schema": { + "$ref": "#/components/schemas/GenericObject" + } + }, + "multipart/form-data": { + "schema": { + "$ref": "#/components/schemas/GenericObject" + } + } + }, + "description": "Request body inferred. Exact DTO may be named in x-recroom-evidence method signature or companion DTO inventory." + }, + "x-client-response-contracts": [ + { + "kind": "object", + "raw": "BMFAGMFKODA", + "rootType": "BMFAGMFKODA", + "typeDescription": { + "name": "BMFAGMFKODA", + "found": true, + "file": "BMFAGMFKODA.cs", + "declaration": "public class BMFAGMFKODA : KMKPEOGJDFK", + "kind": "class", + "baseType": "KMKPEOGJDFK", + "properties": [ + { + "name": "BIODFICLKJH", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "DFKGKGIMEMP", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "FEBCCOFBKKJ", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "HEFJFKFNNCJ", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "IFMMBHKGHMB", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "KFFBPMHGFKB", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "PKKADKGDHNI", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + } + ], + "fields": [ + { + "name": "tag", + "type": "string", + "kind": "field" + } + ], + "enumValues": [], + "wireKeys": [], + "isilFile": "BMFAGMFKODA.txt", + "isilType": "BMFAGMFKODA", + "inherits": "KMKPEOGJDFK", + "inheritedMembers": [ + { + "name": "BBGEDJMGFFO", + "type": "long", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "KMKPEOGJDFK" + }, + { + "name": "ACBFDMLHFPB", + "type": "DateTime", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "AHGCOGFEEEE", + "type": "string", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "AKFBNELAMNA", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "BADIGBCKECA", + "type": "int", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "BNBLOBAEDEE", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "CDINMMPNAID", + "type": "NMJEKMMBDDE", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "CDNFGMHLDMJ", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "FIKEBGGCDFN", + "type": "string", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "GIBHIMGJNNO", + "type": "GPDIAKNEBKH", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "HPLBOMGACED", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "IGOPGMHHLKI", + "type": "string", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "JFEAPMIPNEP", + "type": "DPLPMKMFMPB", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "KHIJAFCHLIA", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "KLNJBBPNMBJ", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "KODBEJPEFOJ", + "type": "string", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "LPJLEMJFBPE", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "MGBDHBHCDMH", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "MIKDLDEALPN", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "OFONEIOEIED", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "OILEJFNPDDB", + "type": "HJPGEGENLPH", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "PEEFHKMOMKK", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + } + ], + "inheritedWireKeys": [ + "PlaylistId" + ] + } + } + ] + } + }, + "/playlists/{p0}/description": { + "post": { + "tags": [ + "playlists" + ], + "summary": "POST playlists/{0}/description", + "description": "Inferred from Rec Room 2020 client literal `playlists/{0}/description`. Expected result: Playlist object/list or playlist mutation success.", + "operationId": "client_playlists_playlists_p0_description_post", + "parameters": [ + { + "name": "p0", + "in": "path", + "required": true, + "schema": { + "type": "string" + }, + "description": "Format placeholder from client literal playlists/{0}/description" + } + ], + "responses": { + "200": { + "description": "Playlist object/list or playlist mutation success.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Playlist" + } + } + } + }, + "204": { + "description": "Success with no body, used by some mutation endpoints." + }, + "400": { + "description": "Client-visible validation or bad request error.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "401": { + "description": "Authentication required or token invalid.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "500": { + "description": "Server error shape tolerated by client wrappers.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + } + }, + "x-recroom-client-route-literal": "playlists/{0}/description", + "x-recroom-client-family": "playlists", + "x-recroom-client-service": "rooms/api", + "x-recroom-builds": [ + "2020.12.18" + ], + "x-recroom-inferred-http-method": "post", + "x-recroom-evidence": [ + { + "type": "EJDCNGBEICB", + "method": "RecRoom.Async.IPromise`1 EPJMNNPLNDM(System.Int64 DDMPFMPILCE, System.String LJIGOCDPEJF)", + "file": "EJDCNGBEICB.txt", + "line": 6839 + } + ], + "requestBody": { + "required": false, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/GenericObject" + } + }, + "application/x-www-form-urlencoded": { + "schema": { + "$ref": "#/components/schemas/GenericObject" + } + }, + "multipart/form-data": { + "schema": { + "$ref": "#/components/schemas/GenericObject" + } + } + }, + "description": "Request body inferred. Exact DTO may be named in x-recroom-evidence method signature or companion DTO inventory." + }, + "x-client-response-contracts": [ + { + "kind": "object", + "raw": "BMFAGMFKODA", + "rootType": "BMFAGMFKODA", + "typeDescription": { + "name": "BMFAGMFKODA", + "found": true, + "file": "BMFAGMFKODA.cs", + "declaration": "public class BMFAGMFKODA : KMKPEOGJDFK", + "kind": "class", + "baseType": "KMKPEOGJDFK", + "properties": [ + { + "name": "BIODFICLKJH", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "DFKGKGIMEMP", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "FEBCCOFBKKJ", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "HEFJFKFNNCJ", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "IFMMBHKGHMB", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "KFFBPMHGFKB", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "PKKADKGDHNI", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + } + ], + "fields": [ + { + "name": "tag", + "type": "string", + "kind": "field" + } + ], + "enumValues": [], + "wireKeys": [], + "isilFile": "BMFAGMFKODA.txt", + "isilType": "BMFAGMFKODA", + "inherits": "KMKPEOGJDFK", + "inheritedMembers": [ + { + "name": "BBGEDJMGFFO", + "type": "long", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "KMKPEOGJDFK" + }, + { + "name": "ACBFDMLHFPB", + "type": "DateTime", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "AHGCOGFEEEE", + "type": "string", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "AKFBNELAMNA", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "BADIGBCKECA", + "type": "int", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "BNBLOBAEDEE", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "CDINMMPNAID", + "type": "NMJEKMMBDDE", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "CDNFGMHLDMJ", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "FIKEBGGCDFN", + "type": "string", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "GIBHIMGJNNO", + "type": "GPDIAKNEBKH", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "HPLBOMGACED", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "IGOPGMHHLKI", + "type": "string", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "JFEAPMIPNEP", + "type": "DPLPMKMFMPB", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "KHIJAFCHLIA", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "KLNJBBPNMBJ", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "KODBEJPEFOJ", + "type": "string", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "LPJLEMJFBPE", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "MGBDHBHCDMH", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "MIKDLDEALPN", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "OFONEIOEIED", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "OILEJFNPDDB", + "type": "HJPGEGENLPH", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "PEEFHKMOMKK", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + } + ], + "inheritedWireKeys": [ + "PlaylistId" + ] + } + } + ] + } + }, + "/playlists/{p0}/image": { + "post": { + "tags": [ + "playlists" + ], + "summary": "POST playlists/{0}/image", + "description": "Inferred from Rec Room 2020 client literal `playlists/{0}/image`. Expected result: Playlist object/list or playlist mutation success.", + "operationId": "client_playlists_playlists_p0_image_post", + "parameters": [ + { + "name": "p0", + "in": "path", + "required": true, + "schema": { + "type": "string" + }, + "description": "Format placeholder from client literal playlists/{0}/image" + } + ], + "responses": { + "200": { + "description": "Playlist object/list or playlist mutation success.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Playlist" + } + } + } + }, + "204": { + "description": "Success with no body, used by some mutation endpoints." + }, + "400": { + "description": "Client-visible validation or bad request error.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "401": { + "description": "Authentication required or token invalid.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "500": { + "description": "Server error shape tolerated by client wrappers.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + } + }, + "x-recroom-client-route-literal": "playlists/{0}/image", + "x-recroom-client-family": "playlists", + "x-recroom-client-service": "rooms/api", + "x-recroom-builds": [ + "2020.12.18" + ], + "x-recroom-inferred-http-method": "post", + "x-recroom-evidence": [ + { + "type": "EJDCNGBEICB", + "method": "RecRoom.Async.IPromise`1 CKMILKDKDCN(System.Int64 DDMPFMPILCE, System.String HFLPBHHAFIO)", + "file": "EJDCNGBEICB.txt", + "line": 6955 + } + ], + "requestBody": { + "required": false, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/GenericObject" + } + }, + "application/x-www-form-urlencoded": { + "schema": { + "$ref": "#/components/schemas/GenericObject" + } + }, + "multipart/form-data": { + "schema": { + "$ref": "#/components/schemas/GenericObject" + } + } + }, + "description": "Request body inferred. Exact DTO may be named in x-recroom-evidence method signature or companion DTO inventory." + }, + "x-client-response-contracts": [ + { + "kind": "object", + "raw": "BMFAGMFKODA", + "rootType": "BMFAGMFKODA", + "typeDescription": { + "name": "BMFAGMFKODA", + "found": true, + "file": "BMFAGMFKODA.cs", + "declaration": "public class BMFAGMFKODA : KMKPEOGJDFK", + "kind": "class", + "baseType": "KMKPEOGJDFK", + "properties": [ + { + "name": "BIODFICLKJH", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "DFKGKGIMEMP", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "FEBCCOFBKKJ", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "HEFJFKFNNCJ", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "IFMMBHKGHMB", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "KFFBPMHGFKB", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "PKKADKGDHNI", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + } + ], + "fields": [ + { + "name": "tag", + "type": "string", + "kind": "field" + } + ], + "enumValues": [], + "wireKeys": [], + "isilFile": "BMFAGMFKODA.txt", + "isilType": "BMFAGMFKODA", + "inherits": "KMKPEOGJDFK", + "inheritedMembers": [ + { + "name": "BBGEDJMGFFO", + "type": "long", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "KMKPEOGJDFK" + }, + { + "name": "ACBFDMLHFPB", + "type": "DateTime", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "AHGCOGFEEEE", + "type": "string", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "AKFBNELAMNA", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "BADIGBCKECA", + "type": "int", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "BNBLOBAEDEE", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "CDINMMPNAID", + "type": "NMJEKMMBDDE", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "CDNFGMHLDMJ", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "FIKEBGGCDFN", + "type": "string", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "GIBHIMGJNNO", + "type": "GPDIAKNEBKH", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "HPLBOMGACED", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "IGOPGMHHLKI", + "type": "string", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "JFEAPMIPNEP", + "type": "DPLPMKMFMPB", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "KHIJAFCHLIA", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "KLNJBBPNMBJ", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "KODBEJPEFOJ", + "type": "string", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "LPJLEMJFBPE", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "MGBDHBHCDMH", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "MIKDLDEALPN", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "OFONEIOEIED", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "OILEJFNPDDB", + "type": "HJPGEGENLPH", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "PEEFHKMOMKK", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + } + ], + "inheritedWireKeys": [ + "PlaylistId" + ] + } + } + ] + } + }, + "/playlists/{p0}/interactionby/me/cheer": { + "get": { + "tags": [ + "playlists" + ], + "summary": "GET playlists/{0}/interactionby/me/cheer", + "description": "Inferred from Rec Room 2020 client literal `playlists/{0}/interactionby/me/cheer`. Expected result: Playlist object/list or playlist mutation success.", + "operationId": "client_playlists_playlists_p0_interactionby_me_cheer_get", + "parameters": [ + { + "name": "p0", + "in": "path", + "required": true, + "schema": { + "type": "string" + }, + "description": "Format placeholder from client literal playlists/{0}/interactionby/me/cheer" + } + ], + "responses": { + "200": { + "description": "Playlist object/list or playlist mutation success.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Playlist" + } + } + } + }, + "204": { + "description": "Success with no body, used by some mutation endpoints." + }, + "400": { + "description": "Client-visible validation or bad request error.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "401": { + "description": "Authentication required or token invalid.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "500": { + "description": "Server error shape tolerated by client wrappers.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + } + }, + "x-recroom-client-route-literal": "playlists/{0}/interactionby/me/cheer", + "x-recroom-client-family": "playlists", + "x-recroom-client-service": "rooms/api", + "x-recroom-builds": [ + "2020.12.18" + ], + "x-recroom-inferred-http-method": "get", + "x-recroom-evidence": [ + { + "type": "EJDCNGBEICB", + "method": "RecRoom.Async.IPromise CIPEENNIAFL(System.Int64 DDMPFMPILCE)", + "file": "EJDCNGBEICB.txt", + "line": 8733 + }, + { + "type": "EJDCNGBEICB", + "method": "RecRoom.Async.IPromise LONLMONNPGL(System.Int64 DDMPFMPILCE)", + "file": "EJDCNGBEICB.txt", + "line": 8665 + } + ], + "x-client-response-contracts": [ + { + "kind": "success-or-empty", + "raw": "RecRoom.Async.IPromise", + "rootType": "void/success", + "typeDescription": { + "name": "void/success", + "found": false, + "kind": "object", + "wireKeys": [] + } + } + ] + } + }, + "/playlists/{p0}/interactionby/me/favorite": { + "get": { + "tags": [ + "playlists" + ], + "summary": "GET playlists/{0}/interactionby/me/favorite", + "description": "Inferred from Rec Room 2020 client literal `playlists/{0}/interactionby/me/favorite`. Expected result: Playlist object/list or playlist mutation success.", + "operationId": "client_playlists_playlists_p0_interactionby_me_favorite_get", + "parameters": [ + { + "name": "p0", + "in": "path", + "required": true, + "schema": { + "type": "string" + }, + "description": "Format placeholder from client literal playlists/{0}/interactionby/me/favorite" + } + ], + "responses": { + "200": { + "description": "Playlist object/list or playlist mutation success.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Playlist" + } + } + } + }, + "204": { + "description": "Success with no body, used by some mutation endpoints." + }, + "400": { + "description": "Client-visible validation or bad request error.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "401": { + "description": "Authentication required or token invalid.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "500": { + "description": "Server error shape tolerated by client wrappers.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + } + }, + "x-recroom-client-route-literal": "playlists/{0}/interactionby/me/favorite", + "x-recroom-client-family": "playlists", + "x-recroom-client-service": "rooms/api", + "x-recroom-builds": [ + "2020.12.18" + ], + "x-recroom-inferred-http-method": "get", + "x-recroom-evidence": [ + { + "type": "EJDCNGBEICB", + "method": "RecRoom.Async.IPromise GDELFOHOPGF(System.Int64 DDMPFMPILCE)", + "file": "EJDCNGBEICB.txt", + "line": 8801 + }, + { + "type": "EJDCNGBEICB", + "method": "RecRoom.Async.IPromise NLNKEGDKFCG(System.Int64 DDMPFMPILCE)", + "file": "EJDCNGBEICB.txt", + "line": 8869 + } + ], + "x-client-response-contracts": [ + { + "kind": "success-or-empty", + "raw": "RecRoom.Async.IPromise", + "rootType": "void/success", + "typeDescription": { + "name": "void/success", + "found": false, + "kind": "object", + "wireKeys": [] + } + } + ] + } + }, + "/playlists/{p0}/interactionby/me": { + "get": { + "tags": [ + "playlists" + ], + "summary": "GET playlists/{0}/interactionby/me", + "description": "Inferred from Rec Room 2020 client literal `playlists/{0}/interactionby/me`. Expected result: Playlist object/list or playlist mutation success.", + "operationId": "client_playlists_playlists_p0_interactionby_me_get", + "parameters": [ + { + "name": "p0", + "in": "path", + "required": true, + "schema": { + "type": "string" + }, + "description": "Format placeholder from client literal playlists/{0}/interactionby/me" + } + ], + "responses": { + "200": { + "description": "Playlist object/list or playlist mutation success.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Playlist" + } + } + } + }, + "204": { + "description": "Success with no body, used by some mutation endpoints." + }, + "400": { + "description": "Client-visible validation or bad request error.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "401": { + "description": "Authentication required or token invalid.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "500": { + "description": "Server error shape tolerated by client wrappers.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + } + }, + "x-recroom-client-route-literal": "playlists/{0}/interactionby/me", + "x-recroom-client-family": "playlists", + "x-recroom-client-service": "rooms/api", + "x-recroom-builds": [ + "2020.12.18" + ], + "x-recroom-inferred-http-method": "get", + "x-recroom-evidence": [ + { + "type": "EJDCNGBEICB", + "method": "RecRoom.Async.IPromise`1 AINDJCIMJOB(System.Int64 DDMPFMPILCE)", + "file": "EJDCNGBEICB.txt", + "line": 8600 + } + ], + "x-client-response-contracts": [ + { + "kind": "object", + "raw": "CJODCLDGFCF", + "rootType": "CJODCLDGFCF", + "typeDescription": { + "name": "CJODCLDGFCF", + "found": true, + "file": "CJODCLDGFCF.cs", + "declaration": "public class CJODCLDGFCF : IFAIJAGLDFK", + "kind": "class", + "baseType": "IFAIJAGLDFK", + "properties": [ + { + "name": "EFCGNDGOFNK", + "type": "Nullable", + "accessibility": "public", + "kind": "property" + }, + { + "name": "EMBNPPMOJFJ", + "type": "bool", + "accessibility": "public", + "kind": "property" + }, + { + "name": "HHFLKFOEHNL", + "type": "bool", + "accessibility": "public", + "kind": "property" + } + ], + "fields": [], + "enumValues": [], + "wireKeys": [ + "Cheered", + "Favorited" + ], + "isilFile": "CJODCLDGFCF.txt", + "isilType": "CJODCLDGFCF" + } + } + ] + } + }, + "/playlists/{p0}/levelvoting": { + "get": { + "tags": [ + "playlists" + ], + "summary": "GET playlists/{0}/levelvoting", + "description": "Inferred from Rec Room 2020 client literal `playlists/{0}/levelvoting`. Expected result: Playlist object/list or playlist mutation success.", + "operationId": "client_playlists_playlists_p0_levelvoting_get", + "parameters": [ + { + "name": "p0", + "in": "path", + "required": true, + "schema": { + "type": "string" + }, + "description": "Format placeholder from client literal playlists/{0}/levelvoting" + } + ], + "responses": { + "200": { + "description": "Playlist object/list or playlist mutation success.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Playlist" + } + } + } + }, + "204": { + "description": "Success with no body, used by some mutation endpoints." + }, + "400": { + "description": "Client-visible validation or bad request error.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "401": { + "description": "Authentication required or token invalid.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "500": { + "description": "Server error shape tolerated by client wrappers.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + } + }, + "x-recroom-client-route-literal": "playlists/{0}/levelvoting", + "x-recroom-client-family": "playlists", + "x-recroom-client-service": "rooms/api", + "x-recroom-builds": [ + "2020.12.18" + ], + "x-recroom-inferred-http-method": "get", + "x-recroom-evidence": [ + { + "type": "EJDCNGBEICB", + "method": "RecRoom.Async.IPromise`1 PPBLDOPNJKC(System.Int64 DDMPFMPILCE, System.Boolean NNBHCDIKILH)", + "file": "EJDCNGBEICB.txt", + "line": 7582 + } + ], + "x-client-response-contracts": [ + { + "kind": "object", + "raw": "BMFAGMFKODA", + "rootType": "BMFAGMFKODA", + "typeDescription": { + "name": "BMFAGMFKODA", + "found": true, + "file": "BMFAGMFKODA.cs", + "declaration": "public class BMFAGMFKODA : KMKPEOGJDFK", + "kind": "class", + "baseType": "KMKPEOGJDFK", + "properties": [ + { + "name": "BIODFICLKJH", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "DFKGKGIMEMP", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "FEBCCOFBKKJ", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "HEFJFKFNNCJ", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "IFMMBHKGHMB", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "KFFBPMHGFKB", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "PKKADKGDHNI", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + } + ], + "fields": [ + { + "name": "tag", + "type": "string", + "kind": "field" + } + ], + "enumValues": [], + "wireKeys": [], + "isilFile": "BMFAGMFKODA.txt", + "isilType": "BMFAGMFKODA", + "inherits": "KMKPEOGJDFK", + "inheritedMembers": [ + { + "name": "BBGEDJMGFFO", + "type": "long", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "KMKPEOGJDFK" + }, + { + "name": "ACBFDMLHFPB", + "type": "DateTime", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "AHGCOGFEEEE", + "type": "string", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "AKFBNELAMNA", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "BADIGBCKECA", + "type": "int", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "BNBLOBAEDEE", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "CDINMMPNAID", + "type": "NMJEKMMBDDE", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "CDNFGMHLDMJ", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "FIKEBGGCDFN", + "type": "string", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "GIBHIMGJNNO", + "type": "GPDIAKNEBKH", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "HPLBOMGACED", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "IGOPGMHHLKI", + "type": "string", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "JFEAPMIPNEP", + "type": "DPLPMKMFMPB", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "KHIJAFCHLIA", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "KLNJBBPNMBJ", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "KODBEJPEFOJ", + "type": "string", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "LPJLEMJFBPE", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "MGBDHBHCDMH", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "MIKDLDEALPN", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "OFONEIOEIED", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "OILEJFNPDDB", + "type": "HJPGEGENLPH", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "PEEFHKMOMKK", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + } + ], + "inheritedWireKeys": [ + "PlaylistId" + ] + } + } + ] + } + }, + "/playlists/{p0}/name": { + "post": { + "tags": [ + "playlists" + ], + "summary": "POST playlists/{0}/name", + "description": "Inferred from Rec Room 2020 client literal `playlists/{0}/name`. Expected result: Playlist object/list or playlist mutation success.", + "operationId": "client_playlists_playlists_p0_name_post", + "parameters": [ + { + "name": "p0", + "in": "path", + "required": true, + "schema": { + "type": "string" + }, + "description": "Format placeholder from client literal playlists/{0}/name" + } + ], + "responses": { + "200": { + "description": "Playlist object/list or playlist mutation success.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Playlist" + } + } + } + }, + "204": { + "description": "Success with no body, used by some mutation endpoints." + }, + "400": { + "description": "Client-visible validation or bad request error.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "401": { + "description": "Authentication required or token invalid.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "500": { + "description": "Server error shape tolerated by client wrappers.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + } + }, + "x-recroom-client-route-literal": "playlists/{0}/name", + "x-recroom-client-family": "playlists", + "x-recroom-client-service": "rooms/api", + "x-recroom-builds": [ + "2020.12.18" + ], + "x-recroom-inferred-http-method": "post", + "x-recroom-evidence": [ + { + "type": "EJDCNGBEICB", + "method": "RecRoom.Async.IPromise`1 NINPMBNNHMG(System.Int64 DDMPFMPILCE, System.String MMBOKOLAJFH)", + "file": "EJDCNGBEICB.txt", + "line": 6723 + } + ], + "requestBody": { + "required": false, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/GenericObject" + } + }, + "application/x-www-form-urlencoded": { + "schema": { + "$ref": "#/components/schemas/GenericObject" + } + }, + "multipart/form-data": { + "schema": { + "$ref": "#/components/schemas/GenericObject" + } + } + }, + "description": "Request body inferred. Exact DTO may be named in x-recroom-evidence method signature or companion DTO inventory." + }, + "x-client-response-contracts": [ + { + "kind": "object", + "raw": "BMFAGMFKODA", + "rootType": "BMFAGMFKODA", + "typeDescription": { + "name": "BMFAGMFKODA", + "found": true, + "file": "BMFAGMFKODA.cs", + "declaration": "public class BMFAGMFKODA : KMKPEOGJDFK", + "kind": "class", + "baseType": "KMKPEOGJDFK", + "properties": [ + { + "name": "BIODFICLKJH", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "DFKGKGIMEMP", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "FEBCCOFBKKJ", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "HEFJFKFNNCJ", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "IFMMBHKGHMB", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "KFFBPMHGFKB", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "PKKADKGDHNI", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + } + ], + "fields": [ + { + "name": "tag", + "type": "string", + "kind": "field" + } + ], + "enumValues": [], + "wireKeys": [], + "isilFile": "BMFAGMFKODA.txt", + "isilType": "BMFAGMFKODA", + "inherits": "KMKPEOGJDFK", + "inheritedMembers": [ + { + "name": "BBGEDJMGFFO", + "type": "long", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "KMKPEOGJDFK" + }, + { + "name": "ACBFDMLHFPB", + "type": "DateTime", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "AHGCOGFEEEE", + "type": "string", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "AKFBNELAMNA", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "BADIGBCKECA", + "type": "int", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "BNBLOBAEDEE", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "CDINMMPNAID", + "type": "NMJEKMMBDDE", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "CDNFGMHLDMJ", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "FIKEBGGCDFN", + "type": "string", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "GIBHIMGJNNO", + "type": "GPDIAKNEBKH", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "HPLBOMGACED", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "IGOPGMHHLKI", + "type": "string", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "JFEAPMIPNEP", + "type": "DPLPMKMFMPB", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "KHIJAFCHLIA", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "KLNJBBPNMBJ", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "KODBEJPEFOJ", + "type": "string", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "LPJLEMJFBPE", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "MGBDHBHCDMH", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "MIKDLDEALPN", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "OFONEIOEIED", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "OILEJFNPDDB", + "type": "HJPGEGENLPH", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "PEEFHKMOMKK", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + } + ], + "inheritedWireKeys": [ + "PlaylistId" + ] + } + } + ] + } + }, + "/playlists/{p0}/restrictions": { + "post": { + "tags": [ + "playlists" + ], + "summary": "POST playlists/{0}/restrictions", + "description": "Inferred from Rec Room 2020 client literal `playlists/{0}/restrictions`. Expected result: Playlist object/list or playlist mutation success.", + "operationId": "client_playlists_playlists_p0_restrictions_post", + "parameters": [ + { + "name": "p0", + "in": "path", + "required": true, + "schema": { + "type": "string" + }, + "description": "Format placeholder from client literal playlists/{0}/restrictions" + } + ], + "responses": { + "200": { + "description": "Playlist object/list or playlist mutation success.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Playlist" + } + } + } + }, + "204": { + "description": "Success with no body, used by some mutation endpoints." + }, + "400": { + "description": "Client-visible validation or bad request error.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "401": { + "description": "Authentication required or token invalid.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "500": { + "description": "Server error shape tolerated by client wrappers.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + } + }, + "x-recroom-client-route-literal": "playlists/{0}/restrictions", + "x-recroom-client-family": "playlists", + "x-recroom-client-service": "rooms/api", + "x-recroom-builds": [ + "2020.12.18" + ], + "x-recroom-inferred-http-method": "post", + "x-recroom-evidence": [ + { + "type": "EJDCNGBEICB", + "method": "RecRoom.Async.IPromise`1 LDLJKJLNLEE(System.Int64 DDMPFMPILCE, System.Boolean DDMOEIAHJBK, System.Boolean FOBLJIBLCNI, System.Boolean GELHFEFGJLA, System.Boolean NFIGHDPKLJF)", + "file": "EJDCNGBEICB.txt", + "line": 7454 + } + ], + "requestBody": { + "required": false, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/GenericObject" + } + }, + "application/x-www-form-urlencoded": { + "schema": { + "$ref": "#/components/schemas/GenericObject" + } + }, + "multipart/form-data": { + "schema": { + "$ref": "#/components/schemas/GenericObject" + } + } + }, + "description": "Request body inferred. Exact DTO may be named in x-recroom-evidence method signature or companion DTO inventory." + }, + "x-client-response-contracts": [ + { + "kind": "object", + "raw": "BMFAGMFKODA", + "rootType": "BMFAGMFKODA", + "typeDescription": { + "name": "BMFAGMFKODA", + "found": true, + "file": "BMFAGMFKODA.cs", + "declaration": "public class BMFAGMFKODA : KMKPEOGJDFK", + "kind": "class", + "baseType": "KMKPEOGJDFK", + "properties": [ + { + "name": "BIODFICLKJH", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "DFKGKGIMEMP", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "FEBCCOFBKKJ", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "HEFJFKFNNCJ", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "IFMMBHKGHMB", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "KFFBPMHGFKB", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "PKKADKGDHNI", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + } + ], + "fields": [ + { + "name": "tag", + "type": "string", + "kind": "field" + } + ], + "enumValues": [], + "wireKeys": [], + "isilFile": "BMFAGMFKODA.txt", + "isilType": "BMFAGMFKODA", + "inherits": "KMKPEOGJDFK", + "inheritedMembers": [ + { + "name": "BBGEDJMGFFO", + "type": "long", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "KMKPEOGJDFK" + }, + { + "name": "ACBFDMLHFPB", + "type": "DateTime", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "AHGCOGFEEEE", + "type": "string", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "AKFBNELAMNA", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "BADIGBCKECA", + "type": "int", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "BNBLOBAEDEE", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "CDINMMPNAID", + "type": "NMJEKMMBDDE", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "CDNFGMHLDMJ", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "FIKEBGGCDFN", + "type": "string", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "GIBHIMGJNNO", + "type": "GPDIAKNEBKH", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "HPLBOMGACED", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "IGOPGMHHLKI", + "type": "string", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "JFEAPMIPNEP", + "type": "DPLPMKMFMPB", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "KHIJAFCHLIA", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "KLNJBBPNMBJ", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "KODBEJPEFOJ", + "type": "string", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "LPJLEMJFBPE", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "MGBDHBHCDMH", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "MIKDLDEALPN", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "OFONEIOEIED", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "OILEJFNPDDB", + "type": "HJPGEGENLPH", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "PEEFHKMOMKK", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + } + ], + "inheritedWireKeys": [ + "PlaylistId" + ] + } + } + ] + } + }, + "/playlists/{p0}/rooms/{p1}": { + "get": { + "tags": [ + "playlists" + ], + "summary": "GET playlists/{0}/rooms/{1}", + "description": "Inferred from Rec Room 2020 client literal `playlists/{0}/rooms/{1}`. Expected result: Playlist object/list or playlist mutation success.", + "operationId": "client_playlists_playlists_p0_rooms_p1_get", + "parameters": [ + { + "name": "p0", + "in": "path", + "required": true, + "schema": { + "type": "string" + }, + "description": "Format placeholder from client literal playlists/{0}/rooms/{1}" + }, + { + "name": "p1", + "in": "path", + "required": true, + "schema": { + "type": "string" + }, + "description": "Format placeholder from client literal playlists/{0}/rooms/{1}" + } + ], + "responses": { + "200": { + "description": "Playlist object/list or playlist mutation success.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Playlist" + } + } + } + }, + "204": { + "description": "Success with no body, used by some mutation endpoints." + }, + "400": { + "description": "Client-visible validation or bad request error.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "401": { + "description": "Authentication required or token invalid.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "500": { + "description": "Server error shape tolerated by client wrappers.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + } + }, + "x-recroom-client-route-literal": "playlists/{0}/rooms/{1}", + "x-recroom-client-family": "playlists", + "x-recroom-client-service": "rooms/api", + "x-recroom-builds": [ + "2020.12.18" + ], + "x-recroom-inferred-http-method": "get", + "x-recroom-evidence": [ + { + "type": "EJDCNGBEICB", + "method": "RecRoom.Async.IPromise`1 JBMCPFHGIMD(System.Int64 DDMPFMPILCE, System.Int64 HNHLJONGKHB)", + "file": "EJDCNGBEICB.txt", + "line": 7765 + }, + { + "type": "EJDCNGBEICB", + "method": "RecRoom.Async.IPromise`1 NLOEBFMNCFM(System.Int64 DDMPFMPILCE, System.Int64 HNHLJONGKHB)", + "file": "EJDCNGBEICB.txt", + "line": 7679 + } + ], + "x-client-response-contracts": [ + { + "kind": "object", + "raw": "BMFAGMFKODA", + "rootType": "BMFAGMFKODA", + "typeDescription": { + "name": "BMFAGMFKODA", + "found": true, + "file": "BMFAGMFKODA.cs", + "declaration": "public class BMFAGMFKODA : KMKPEOGJDFK", + "kind": "class", + "baseType": "KMKPEOGJDFK", + "properties": [ + { + "name": "BIODFICLKJH", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "DFKGKGIMEMP", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "FEBCCOFBKKJ", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "HEFJFKFNNCJ", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "IFMMBHKGHMB", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "KFFBPMHGFKB", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "PKKADKGDHNI", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + } + ], + "fields": [ + { + "name": "tag", + "type": "string", + "kind": "field" + } + ], + "enumValues": [], + "wireKeys": [], + "isilFile": "BMFAGMFKODA.txt", + "isilType": "BMFAGMFKODA", + "inherits": "KMKPEOGJDFK", + "inheritedMembers": [ + { + "name": "BBGEDJMGFFO", + "type": "long", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "KMKPEOGJDFK" + }, + { + "name": "ACBFDMLHFPB", + "type": "DateTime", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "AHGCOGFEEEE", + "type": "string", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "AKFBNELAMNA", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "BADIGBCKECA", + "type": "int", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "BNBLOBAEDEE", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "CDINMMPNAID", + "type": "NMJEKMMBDDE", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "CDNFGMHLDMJ", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "FIKEBGGCDFN", + "type": "string", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "GIBHIMGJNNO", + "type": "GPDIAKNEBKH", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "HPLBOMGACED", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "IGOPGMHHLKI", + "type": "string", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "JFEAPMIPNEP", + "type": "DPLPMKMFMPB", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "KHIJAFCHLIA", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "KLNJBBPNMBJ", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "KODBEJPEFOJ", + "type": "string", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "LPJLEMJFBPE", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "MGBDHBHCDMH", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "MIKDLDEALPN", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "OFONEIOEIED", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "OILEJFNPDDB", + "type": "HJPGEGENLPH", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "PEEFHKMOMKK", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + } + ], + "inheritedWireKeys": [ + "PlaylistId" + ] + } + } + ] + } + }, + "/playlists/{p0}/tags": { + "post": { + "tags": [ + "playlists" + ], + "summary": "POST playlists/{0}/tags", + "description": "Inferred from Rec Room 2020 client literal `playlists/{0}/tags`. Expected result: Playlist object/list or playlist mutation success.", + "operationId": "client_playlists_playlists_p0_tags_post", + "parameters": [ + { + "name": "p0", + "in": "path", + "required": true, + "schema": { + "type": "string" + }, + "description": "Format placeholder from client literal playlists/{0}/tags" + } + ], + "responses": { + "200": { + "description": "Playlist object/list or playlist mutation success.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Playlist" + } + } + } + }, + "204": { + "description": "Success with no body, used by some mutation endpoints." + }, + "400": { + "description": "Client-visible validation or bad request error.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "401": { + "description": "Authentication required or token invalid.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "500": { + "description": "Server error shape tolerated by client wrappers.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + } + }, + "x-recroom-client-route-literal": "playlists/{0}/tags", + "x-recroom-client-family": "playlists", + "x-recroom-client-service": "rooms/api", + "x-recroom-builds": [ + "2020.12.18" + ], + "x-recroom-inferred-http-method": "post", + "x-recroom-evidence": [ + { + "type": "EJDCNGBEICB", + "method": "RecRoom.Async.IPromise`1 ALCFNJJHDMG(System.Int64 DDMPFMPILCE, System.Collections.Generic.IReadOnlyList`1 LBAOCHFCLPO, System.Collections.Generic.IReadOnlyList`1 CAFPJPHILMN)", + "file": "EJDCNGBEICB.txt", + "line": 7080 + } + ], + "requestBody": { + "required": false, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/GenericObject" + } + }, + "application/x-www-form-urlencoded": { + "schema": { + "$ref": "#/components/schemas/GenericObject" + } + }, + "multipart/form-data": { + "schema": { + "$ref": "#/components/schemas/GenericObject" + } + } + }, + "description": "Request body inferred. Exact DTO may be named in x-recroom-evidence method signature or companion DTO inventory." + }, + "x-client-response-contracts": [ + { + "kind": "object", + "raw": "BMFAGMFKODA", + "rootType": "BMFAGMFKODA", + "typeDescription": { + "name": "BMFAGMFKODA", + "found": true, + "file": "BMFAGMFKODA.cs", + "declaration": "public class BMFAGMFKODA : KMKPEOGJDFK", + "kind": "class", + "baseType": "KMKPEOGJDFK", + "properties": [ + { + "name": "BIODFICLKJH", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "DFKGKGIMEMP", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "FEBCCOFBKKJ", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "HEFJFKFNNCJ", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "IFMMBHKGHMB", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "KFFBPMHGFKB", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "PKKADKGDHNI", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + } + ], + "fields": [ + { + "name": "tag", + "type": "string", + "kind": "field" + } + ], + "enumValues": [], + "wireKeys": [], + "isilFile": "BMFAGMFKODA.txt", + "isilType": "BMFAGMFKODA", + "inherits": "KMKPEOGJDFK", + "inheritedMembers": [ + { + "name": "BBGEDJMGFFO", + "type": "long", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "KMKPEOGJDFK" + }, + { + "name": "ACBFDMLHFPB", + "type": "DateTime", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "AHGCOGFEEEE", + "type": "string", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "AKFBNELAMNA", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "BADIGBCKECA", + "type": "int", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "BNBLOBAEDEE", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "CDINMMPNAID", + "type": "NMJEKMMBDDE", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "CDNFGMHLDMJ", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "FIKEBGGCDFN", + "type": "string", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "GIBHIMGJNNO", + "type": "GPDIAKNEBKH", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "HPLBOMGACED", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "IGOPGMHHLKI", + "type": "string", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "JFEAPMIPNEP", + "type": "DPLPMKMFMPB", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "KHIJAFCHLIA", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "KLNJBBPNMBJ", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "KODBEJPEFOJ", + "type": "string", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "LPJLEMJFBPE", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "MGBDHBHCDMH", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "MIKDLDEALPN", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "OFONEIOEIED", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "OILEJFNPDDB", + "type": "HJPGEGENLPH", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "PEEFHKMOMKK", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + } + ], + "inheritedWireKeys": [ + "PlaylistId" + ] + } + } + ] + } + }, + "/playlists/{p0}/warning": { + "post": { + "tags": [ + "playlists" + ], + "summary": "POST playlists/{0}/warning", + "description": "Inferred from Rec Room 2020 client literal `playlists/{0}/warning`. Expected result: Playlist object/list or playlist mutation success.", + "operationId": "client_playlists_playlists_p0_warning_post", + "parameters": [ + { + "name": "p0", + "in": "path", + "required": true, + "schema": { + "type": "string" + }, + "description": "Format placeholder from client literal playlists/{0}/warning" + } + ], + "responses": { + "200": { + "description": "Playlist object/list or playlist mutation success.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Playlist" + } + } + } + }, + "204": { + "description": "Success with no body, used by some mutation endpoints." + }, + "400": { + "description": "Client-visible validation or bad request error.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "401": { + "description": "Authentication required or token invalid.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "500": { + "description": "Server error shape tolerated by client wrappers.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + } + }, + "x-recroom-client-route-literal": "playlists/{0}/warning", + "x-recroom-client-family": "playlists", + "x-recroom-client-service": "rooms/api", + "x-recroom-builds": [ + "2020.12.18" + ], + "x-recroom-inferred-http-method": "post", + "x-recroom-evidence": [ + { + "type": "EJDCNGBEICB", + "method": "RecRoom.Async.IPromise`1 KEADPPJCJGG(System.Int64 DDMPFMPILCE, GPDIAKNEBKH LEFPAAGGFFA, System.String NLEBEKDHJIJ)", + "file": "EJDCNGBEICB.txt", + "line": 7206 + } + ], + "requestBody": { + "required": false, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/GenericObject" + } + }, + "application/x-www-form-urlencoded": { + "schema": { + "$ref": "#/components/schemas/GenericObject" + } + }, + "multipart/form-data": { + "schema": { + "$ref": "#/components/schemas/GenericObject" + } + } + }, + "description": "Request body inferred. Exact DTO may be named in x-recroom-evidence method signature or companion DTO inventory." + }, + "x-client-response-contracts": [ + { + "kind": "object", + "raw": "BMFAGMFKODA", + "rootType": "BMFAGMFKODA", + "typeDescription": { + "name": "BMFAGMFKODA", + "found": true, + "file": "BMFAGMFKODA.cs", + "declaration": "public class BMFAGMFKODA : KMKPEOGJDFK", + "kind": "class", + "baseType": "KMKPEOGJDFK", + "properties": [ + { + "name": "BIODFICLKJH", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "DFKGKGIMEMP", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "FEBCCOFBKKJ", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "HEFJFKFNNCJ", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "IFMMBHKGHMB", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "KFFBPMHGFKB", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "PKKADKGDHNI", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + } + ], + "fields": [ + { + "name": "tag", + "type": "string", + "kind": "field" + } + ], + "enumValues": [], + "wireKeys": [], + "isilFile": "BMFAGMFKODA.txt", + "isilType": "BMFAGMFKODA", + "inherits": "KMKPEOGJDFK", + "inheritedMembers": [ + { + "name": "BBGEDJMGFFO", + "type": "long", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "KMKPEOGJDFK" + }, + { + "name": "ACBFDMLHFPB", + "type": "DateTime", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "AHGCOGFEEEE", + "type": "string", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "AKFBNELAMNA", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "BADIGBCKECA", + "type": "int", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "BNBLOBAEDEE", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "CDINMMPNAID", + "type": "NMJEKMMBDDE", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "CDNFGMHLDMJ", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "FIKEBGGCDFN", + "type": "string", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "GIBHIMGJNNO", + "type": "GPDIAKNEBKH", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "HPLBOMGACED", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "IGOPGMHHLKI", + "type": "string", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "JFEAPMIPNEP", + "type": "DPLPMKMFMPB", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "KHIJAFCHLIA", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "KLNJBBPNMBJ", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "KODBEJPEFOJ", + "type": "string", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "LPJLEMJFBPE", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "MGBDHBHCDMH", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "MIKDLDEALPN", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "OFONEIOEIED", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "OILEJFNPDDB", + "type": "HJPGEGENLPH", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "PEEFHKMOMKK", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + } + ], + "inheritedWireKeys": [ + "PlaylistId" + ] + } + } + ] + } + }, + "/playlists/{p0}": { + "get": { + "tags": [ + "playlists" + ], + "summary": "GET playlists/{0}", + "description": "Inferred from Rec Room 2020 client literal `playlists/{0}`. Expected result: Playlist object/list or playlist mutation success.", + "operationId": "client_playlists_playlists_p0_get", + "parameters": [ + { + "name": "p0", + "in": "path", + "required": true, + "schema": { + "type": "string" + }, + "description": "Format placeholder from client literal playlists/{0}" + } + ], + "responses": { + "200": { + "description": "Playlist object/list or playlist mutation success.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Playlist" + } + } + } + }, + "204": { + "description": "Success with no body, used by some mutation endpoints." + }, + "400": { + "description": "Client-visible validation or bad request error.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "401": { + "description": "Authentication required or token invalid.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "500": { + "description": "Server error shape tolerated by client wrappers.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + } + }, + "x-recroom-client-route-literal": "playlists/{0}", + "x-recroom-client-family": "playlists", + "x-recroom-client-service": "rooms/api", + "x-recroom-builds": [ + "2020.12.18" + ], + "x-recroom-inferred-http-method": "get", + "x-recroom-evidence": [ + { + "type": "EJDCNGBEICB", + "method": "RecRoom.Async.IPromise OFACCDEDIHE(System.Int64 DDMPFMPILCE)", + "file": "EJDCNGBEICB.txt", + "line": 6619 + }, + { + "type": "EJDCNGBEICB", + "method": "RecRoom.Async.IPromise`1 POOBMHAAMLJ(System.Int64 DDMPFMPILCE)", + "file": "EJDCNGBEICB.txt", + "line": 2192 + }, + { + "type": "EJDCNGBEICB", + "method": "RecRoom.Async.IPromise`1 PDLNHHAPPIP(System.Int64 DDMPFMPILCE)", + "file": "EJDCNGBEICB.txt", + "line": 1659 + } + ], + "x-client-response-contracts": [ + { + "kind": "success-or-empty", + "raw": "RecRoom.Async.IPromise", + "rootType": "void/success", + "typeDescription": { + "name": "void/success", + "found": false, + "kind": "object", + "wireKeys": [] + } + }, + { + "kind": "object", + "raw": "BMFAGMFKODA", + "rootType": "BMFAGMFKODA", + "typeDescription": { + "name": "BMFAGMFKODA", + "found": true, + "file": "BMFAGMFKODA.cs", + "declaration": "public class BMFAGMFKODA : KMKPEOGJDFK", + "kind": "class", + "baseType": "KMKPEOGJDFK", + "properties": [ + { + "name": "BIODFICLKJH", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "DFKGKGIMEMP", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "FEBCCOFBKKJ", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "HEFJFKFNNCJ", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "IFMMBHKGHMB", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "KFFBPMHGFKB", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "PKKADKGDHNI", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + } + ], + "fields": [ + { + "name": "tag", + "type": "string", + "kind": "field" + } + ], + "enumValues": [], + "wireKeys": [], + "isilFile": "BMFAGMFKODA.txt", + "isilType": "BMFAGMFKODA", + "inherits": "KMKPEOGJDFK", + "inheritedMembers": [ + { + "name": "BBGEDJMGFFO", + "type": "long", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "KMKPEOGJDFK" + }, + { + "name": "ACBFDMLHFPB", + "type": "DateTime", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "AHGCOGFEEEE", + "type": "string", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "AKFBNELAMNA", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "BADIGBCKECA", + "type": "int", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "BNBLOBAEDEE", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "CDINMMPNAID", + "type": "NMJEKMMBDDE", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "CDNFGMHLDMJ", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "FIKEBGGCDFN", + "type": "string", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "GIBHIMGJNNO", + "type": "GPDIAKNEBKH", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "HPLBOMGACED", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "IGOPGMHHLKI", + "type": "string", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "JFEAPMIPNEP", + "type": "DPLPMKMFMPB", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "KHIJAFCHLIA", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "KLNJBBPNMBJ", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "KODBEJPEFOJ", + "type": "string", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "LPJLEMJFBPE", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "MGBDHBHCDMH", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "MIKDLDEALPN", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "OFONEIOEIED", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "OILEJFNPDDB", + "type": "HJPGEGENLPH", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "PEEFHKMOMKK", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + } + ], + "inheritedWireKeys": [ + "PlaylistId" + ] + } + }, + { + "kind": "object", + "raw": "KMKPEOGJDFK", + "rootType": "KMKPEOGJDFK", + "typeDescription": { + "name": "KMKPEOGJDFK", + "found": true, + "file": "KMKPEOGJDFK.cs", + "declaration": "public class KMKPEOGJDFK : MKAMHOIHOJK", + "kind": "class", + "baseType": "MKAMHOIHOJK", + "properties": [ + { + "name": "BBGEDJMGFFO", + "type": "long", + "accessibility": "public", + "kind": "property" + } + ], + "fields": [], + "enumValues": [], + "wireKeys": [ + "PlaylistId" + ], + "isilFile": "KMKPEOGJDFK.txt", + "isilType": "KMKPEOGJDFK", + "inherits": "MKAMHOIHOJK", + "inheritedMembers": [ + { + "name": "ACBFDMLHFPB", + "type": "DateTime", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "AHGCOGFEEEE", + "type": "string", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "AKFBNELAMNA", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "BADIGBCKECA", + "type": "int", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "BNBLOBAEDEE", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "CDINMMPNAID", + "type": "NMJEKMMBDDE", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "CDNFGMHLDMJ", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "FIKEBGGCDFN", + "type": "string", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "GIBHIMGJNNO", + "type": "GPDIAKNEBKH", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "HPLBOMGACED", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "IGOPGMHHLKI", + "type": "string", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "JFEAPMIPNEP", + "type": "DPLPMKMFMPB", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "KHIJAFCHLIA", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "KLNJBBPNMBJ", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "KODBEJPEFOJ", + "type": "string", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "LPJLEMJFBPE", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "MGBDHBHCDMH", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "MIKDLDEALPN", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "OFONEIOEIED", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "OILEJFNPDDB", + "type": "HJPGEGENLPH", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "PEEFHKMOMKK", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + } + ], + "inheritedWireKeys": [ + "Name", + "Description", + "ImageName", + "WarningMask", + "CustomWarning", + "CreatorAccountId", + "State", + "Accessibility", + "SupportsLevelVoting", + "IsRRO", + "SupportsScreens", + "SupportsWalkVR", + "SupportsTeleportVR", + "SupportsVRLow", + "SupportsQuest2", + "SupportsMobile", + "SupportsJuniors", + "CreatedAt", + "Stats" + ] + } + } + ] + } + }, + "/playlists/bulk": { + "post": { + "tags": [ + "playlists" + ], + "summary": "POST playlists/bulk", + "description": "Inferred from Rec Room 2020 client literal `playlists/bulk`. Expected result: Playlist object/list or playlist mutation success.", + "operationId": "client_playlists_playlists_bulk_post", + "parameters": [], + "responses": { + "200": { + "description": "Playlist object/list or playlist mutation success.", + "content": { + "application/json": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/Playlist" + } + } + } + } + }, + "204": { + "description": "Success with no body, used by some mutation endpoints." + }, + "400": { + "description": "Client-visible validation or bad request error.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "401": { + "description": "Authentication required or token invalid.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "500": { + "description": "Server error shape tolerated by client wrappers.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + } + }, + "x-recroom-client-route-literal": "playlists/bulk", + "x-recroom-client-family": "playlists", + "x-recroom-client-service": "rooms/api", + "x-recroom-builds": [ + "2020.12.18" + ], + "x-recroom-inferred-http-method": "post", + "x-recroom-evidence": [ + { + "type": "EJDCNGBEICB", + "method": "RecRoom.Async.IPromise`1> KHMJIKNHJHP(System.Collections.Generic.IReadOnlyList`1 ANIKDNFLDIG)", + "file": "EJDCNGBEICB.txt", + "line": 1896 + }, + { + "type": "EJDCNGBEICB", + "method": "RecRoom.Async.IPromise`1> KHMJIKNHJHP(System.Collections.Generic.IReadOnlyList`1 CJMAAFFAKDO)", + "file": "EJDCNGBEICB.txt", + "line": 2068 + } + ], + "requestBody": { + "required": false, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/GenericObject" + } + }, + "application/x-www-form-urlencoded": { + "schema": { + "$ref": "#/components/schemas/GenericObject" + } + }, + "multipart/form-data": { + "schema": { + "$ref": "#/components/schemas/GenericObject" + } + } + }, + "description": "Request body inferred. Exact DTO may be named in x-recroom-evidence method signature or companion DTO inventory." + }, + "x-client-response-contracts": [ + { + "kind": "array", + "raw": "System.Collections.Generic.List`1", + "rootType": "KMKPEOGJDFK", + "item": { + "kind": "object", + "raw": "KMKPEOGJDFK", + "rootType": "KMKPEOGJDFK" + }, + "typeDescription": { + "name": "KMKPEOGJDFK", + "found": true, + "file": "KMKPEOGJDFK.cs", + "declaration": "public class KMKPEOGJDFK : MKAMHOIHOJK", + "kind": "class", + "baseType": "MKAMHOIHOJK", + "properties": [ + { + "name": "BBGEDJMGFFO", + "type": "long", + "accessibility": "public", + "kind": "property" + } + ], + "fields": [], + "enumValues": [], + "wireKeys": [ + "PlaylistId" + ], + "isilFile": "KMKPEOGJDFK.txt", + "isilType": "KMKPEOGJDFK", + "inherits": "MKAMHOIHOJK", + "inheritedMembers": [ + { + "name": "ACBFDMLHFPB", + "type": "DateTime", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "AHGCOGFEEEE", + "type": "string", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "AKFBNELAMNA", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "BADIGBCKECA", + "type": "int", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "BNBLOBAEDEE", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "CDINMMPNAID", + "type": "NMJEKMMBDDE", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "CDNFGMHLDMJ", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "FIKEBGGCDFN", + "type": "string", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "GIBHIMGJNNO", + "type": "GPDIAKNEBKH", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "HPLBOMGACED", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "IGOPGMHHLKI", + "type": "string", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "JFEAPMIPNEP", + "type": "DPLPMKMFMPB", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "KHIJAFCHLIA", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "KLNJBBPNMBJ", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "KODBEJPEFOJ", + "type": "string", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "LPJLEMJFBPE", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "MGBDHBHCDMH", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "MIKDLDEALPN", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "OFONEIOEIED", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "OILEJFNPDDB", + "type": "HJPGEGENLPH", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "PEEFHKMOMKK", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + } + ], + "inheritedWireKeys": [ + "Name", + "Description", + "ImageName", + "WarningMask", + "CustomWarning", + "CreatorAccountId", + "State", + "Accessibility", + "SupportsLevelVoting", + "IsRRO", + "SupportsScreens", + "SupportsWalkVR", + "SupportsTeleportVR", + "SupportsVRLow", + "SupportsQuest2", + "SupportsMobile", + "SupportsJuniors", + "CreatedAt", + "Stats" + ] + } + } + ] + } + }, + "/playlists/cheeredby/me": { + "get": { + "tags": [ + "playlists" + ], + "summary": "GET playlists/cheeredby/me", + "description": "Inferred from Rec Room 2020 client literal `playlists/cheeredby/me`. Expected result: Playlist object/list or playlist mutation success.", + "operationId": "client_playlists_playlists_cheeredby_me_get", + "parameters": [], + "responses": { + "200": { + "description": "Playlist object/list or playlist mutation success.", + "content": { + "application/json": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/Playlist" + } + } + } + } + }, + "204": { + "description": "Success with no body, used by some mutation endpoints." + }, + "400": { + "description": "Client-visible validation or bad request error.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "401": { + "description": "Authentication required or token invalid.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "500": { + "description": "Server error shape tolerated by client wrappers.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + } + }, + "x-recroom-client-route-literal": "playlists/cheeredby/me", + "x-recroom-client-family": "playlists", + "x-recroom-client-service": "rooms/api", + "x-recroom-builds": [ + "2020.12.18" + ], + "x-recroom-inferred-http-method": "get", + "x-recroom-evidence": [ + { + "type": "EJDCNGBEICB", + "method": "RecRoom.Async.IPromise`1> EFFMOEGALDB()", + "file": "EJDCNGBEICB.txt", + "line": 2400 + } + ], + "x-client-response-contracts": [ + { + "kind": "array", + "raw": "System.Collections.Generic.List`1", + "rootType": "KMKPEOGJDFK", + "item": { + "kind": "object", + "raw": "KMKPEOGJDFK", + "rootType": "KMKPEOGJDFK" + }, + "typeDescription": { + "name": "KMKPEOGJDFK", + "found": true, + "file": "KMKPEOGJDFK.cs", + "declaration": "public class KMKPEOGJDFK : MKAMHOIHOJK", + "kind": "class", + "baseType": "MKAMHOIHOJK", + "properties": [ + { + "name": "BBGEDJMGFFO", + "type": "long", + "accessibility": "public", + "kind": "property" + } + ], + "fields": [], + "enumValues": [], + "wireKeys": [ + "PlaylistId" + ], + "isilFile": "KMKPEOGJDFK.txt", + "isilType": "KMKPEOGJDFK", + "inherits": "MKAMHOIHOJK", + "inheritedMembers": [ + { + "name": "ACBFDMLHFPB", + "type": "DateTime", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "AHGCOGFEEEE", + "type": "string", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "AKFBNELAMNA", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "BADIGBCKECA", + "type": "int", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "BNBLOBAEDEE", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "CDINMMPNAID", + "type": "NMJEKMMBDDE", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "CDNFGMHLDMJ", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "FIKEBGGCDFN", + "type": "string", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "GIBHIMGJNNO", + "type": "GPDIAKNEBKH", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "HPLBOMGACED", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "IGOPGMHHLKI", + "type": "string", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "JFEAPMIPNEP", + "type": "DPLPMKMFMPB", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "KHIJAFCHLIA", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "KLNJBBPNMBJ", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "KODBEJPEFOJ", + "type": "string", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "LPJLEMJFBPE", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "MGBDHBHCDMH", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "MIKDLDEALPN", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "OFONEIOEIED", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "OILEJFNPDDB", + "type": "HJPGEGENLPH", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "PEEFHKMOMKK", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + } + ], + "inheritedWireKeys": [ + "Name", + "Description", + "ImageName", + "WarningMask", + "CustomWarning", + "CreatorAccountId", + "State", + "Accessibility", + "SupportsLevelVoting", + "IsRRO", + "SupportsScreens", + "SupportsWalkVR", + "SupportsTeleportVR", + "SupportsVRLow", + "SupportsQuest2", + "SupportsMobile", + "SupportsJuniors", + "CreatedAt", + "Stats" + ] + } + } + ] + } + }, + "/playlists/createdby/me": { + "post": { + "tags": [ + "playlists" + ], + "summary": "POST playlists/createdby/me", + "description": "Inferred from Rec Room 2020 client literal `playlists/createdby/me`. Expected result: Playlist object/list or playlist mutation success.", + "operationId": "client_playlists_playlists_createdby_me_post", + "parameters": [], + "responses": { + "200": { + "description": "Playlist object/list or playlist mutation success.", + "content": { + "application/json": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/Playlist" + } + } + } + } + }, + "204": { + "description": "Success with no body, used by some mutation endpoints." + }, + "400": { + "description": "Client-visible validation or bad request error.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "401": { + "description": "Authentication required or token invalid.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "500": { + "description": "Server error shape tolerated by client wrappers.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + } + }, + "x-recroom-client-route-literal": "playlists/createdby/me", + "x-recroom-client-family": "playlists", + "x-recroom-client-service": "rooms/api", + "x-recroom-builds": [ + "2020.12.18" + ], + "x-recroom-inferred-http-method": "post", + "x-recroom-evidence": [ + { + "type": "EJDCNGBEICB", + "method": "RecRoom.Async.IPromise`1> PLPMPFDOICH()", + "file": "EJDCNGBEICB.txt", + "line": 2361 + } + ], + "requestBody": { + "required": false, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/GenericObject" + } + }, + "application/x-www-form-urlencoded": { + "schema": { + "$ref": "#/components/schemas/GenericObject" + } + }, + "multipart/form-data": { + "schema": { + "$ref": "#/components/schemas/GenericObject" + } + } + }, + "description": "Request body inferred. Exact DTO may be named in x-recroom-evidence method signature or companion DTO inventory." + }, + "x-client-response-contracts": [ + { + "kind": "array", + "raw": "System.Collections.Generic.List`1", + "rootType": "KMKPEOGJDFK", + "item": { + "kind": "object", + "raw": "KMKPEOGJDFK", + "rootType": "KMKPEOGJDFK" + }, + "typeDescription": { + "name": "KMKPEOGJDFK", + "found": true, + "file": "KMKPEOGJDFK.cs", + "declaration": "public class KMKPEOGJDFK : MKAMHOIHOJK", + "kind": "class", + "baseType": "MKAMHOIHOJK", + "properties": [ + { + "name": "BBGEDJMGFFO", + "type": "long", + "accessibility": "public", + "kind": "property" + } + ], + "fields": [], + "enumValues": [], + "wireKeys": [ + "PlaylistId" + ], + "isilFile": "KMKPEOGJDFK.txt", + "isilType": "KMKPEOGJDFK", + "inherits": "MKAMHOIHOJK", + "inheritedMembers": [ + { + "name": "ACBFDMLHFPB", + "type": "DateTime", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "AHGCOGFEEEE", + "type": "string", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "AKFBNELAMNA", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "BADIGBCKECA", + "type": "int", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "BNBLOBAEDEE", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "CDINMMPNAID", + "type": "NMJEKMMBDDE", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "CDNFGMHLDMJ", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "FIKEBGGCDFN", + "type": "string", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "GIBHIMGJNNO", + "type": "GPDIAKNEBKH", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "HPLBOMGACED", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "IGOPGMHHLKI", + "type": "string", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "JFEAPMIPNEP", + "type": "DPLPMKMFMPB", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "KHIJAFCHLIA", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "KLNJBBPNMBJ", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "KODBEJPEFOJ", + "type": "string", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "LPJLEMJFBPE", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "MGBDHBHCDMH", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "MIKDLDEALPN", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "OFONEIOEIED", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "OILEJFNPDDB", + "type": "HJPGEGENLPH", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "PEEFHKMOMKK", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + } + ], + "inheritedWireKeys": [ + "Name", + "Description", + "ImageName", + "WarningMask", + "CustomWarning", + "CreatorAccountId", + "State", + "Accessibility", + "SupportsLevelVoting", + "IsRRO", + "SupportsScreens", + "SupportsWalkVR", + "SupportsTeleportVR", + "SupportsVRLow", + "SupportsQuest2", + "SupportsMobile", + "SupportsJuniors", + "CreatedAt", + "Stats" + ] + } + } + ] + } + }, + "/playlists/favoritedby/me": { + "get": { + "tags": [ + "playlists" + ], + "summary": "GET playlists/favoritedby/me", + "description": "Inferred from Rec Room 2020 client literal `playlists/favoritedby/me`. Expected result: Playlist object/list or playlist mutation success.", + "operationId": "client_playlists_playlists_favoritedby_me_get", + "parameters": [], + "responses": { + "200": { + "description": "Playlist object/list or playlist mutation success.", + "content": { + "application/json": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/Playlist" + } + } + } + } + }, + "204": { + "description": "Success with no body, used by some mutation endpoints." + }, + "400": { + "description": "Client-visible validation or bad request error.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "401": { + "description": "Authentication required or token invalid.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "500": { + "description": "Server error shape tolerated by client wrappers.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + } + }, + "x-recroom-client-route-literal": "playlists/favoritedby/me", + "x-recroom-client-family": "playlists", + "x-recroom-client-service": "rooms/api", + "x-recroom-builds": [ + "2020.12.18" + ], + "x-recroom-inferred-http-method": "get", + "x-recroom-evidence": [ + { + "type": "EJDCNGBEICB", + "method": "RecRoom.Async.IPromise`1> FAMGMKHMCIN()", + "file": "EJDCNGBEICB.txt", + "line": 2439 + } + ], + "x-client-response-contracts": [ + { + "kind": "array", + "raw": "System.Collections.Generic.List`1", + "rootType": "KMKPEOGJDFK", + "item": { + "kind": "object", + "raw": "KMKPEOGJDFK", + "rootType": "KMKPEOGJDFK" + }, + "typeDescription": { + "name": "KMKPEOGJDFK", + "found": true, + "file": "KMKPEOGJDFK.cs", + "declaration": "public class KMKPEOGJDFK : MKAMHOIHOJK", + "kind": "class", + "baseType": "MKAMHOIHOJK", + "properties": [ + { + "name": "BBGEDJMGFFO", + "type": "long", + "accessibility": "public", + "kind": "property" + } + ], + "fields": [], + "enumValues": [], + "wireKeys": [ + "PlaylistId" + ], + "isilFile": "KMKPEOGJDFK.txt", + "isilType": "KMKPEOGJDFK", + "inherits": "MKAMHOIHOJK", + "inheritedMembers": [ + { + "name": "ACBFDMLHFPB", + "type": "DateTime", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "AHGCOGFEEEE", + "type": "string", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "AKFBNELAMNA", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "BADIGBCKECA", + "type": "int", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "BNBLOBAEDEE", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "CDINMMPNAID", + "type": "NMJEKMMBDDE", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "CDNFGMHLDMJ", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "FIKEBGGCDFN", + "type": "string", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "GIBHIMGJNNO", + "type": "GPDIAKNEBKH", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "HPLBOMGACED", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "IGOPGMHHLKI", + "type": "string", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "JFEAPMIPNEP", + "type": "DPLPMKMFMPB", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "KHIJAFCHLIA", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "KLNJBBPNMBJ", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "KODBEJPEFOJ", + "type": "string", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "LPJLEMJFBPE", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "MGBDHBHCDMH", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "MIKDLDEALPN", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "OFONEIOEIED", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "OILEJFNPDDB", + "type": "HJPGEGENLPH", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "PEEFHKMOMKK", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + } + ], + "inheritedWireKeys": [ + "Name", + "Description", + "ImageName", + "WarningMask", + "CustomWarning", + "CreatorAccountId", + "State", + "Accessibility", + "SupportsLevelVoting", + "IsRRO", + "SupportsScreens", + "SupportsWalkVR", + "SupportsTeleportVR", + "SupportsVRLow", + "SupportsQuest2", + "SupportsMobile", + "SupportsJuniors", + "CreatedAt", + "Stats" + ] + } + } + ] + } + }, + "/playlists/visitedby/me": { + "get": { + "tags": [ + "playlists" + ], + "summary": "GET playlists/visitedby/me", + "description": "Inferred from Rec Room 2020 client literal `playlists/visitedby/me`. Expected result: Playlist object/list or playlist mutation success.", + "operationId": "client_playlists_playlists_visitedby_me_get", + "parameters": [], + "responses": { + "200": { + "description": "Playlist object/list or playlist mutation success.", + "content": { + "application/json": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/Playlist" + } + } + } + } + }, + "204": { + "description": "Success with no body, used by some mutation endpoints." + }, + "400": { + "description": "Client-visible validation or bad request error.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "401": { + "description": "Authentication required or token invalid.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "500": { + "description": "Server error shape tolerated by client wrappers.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + } + }, + "x-recroom-client-route-literal": "playlists/visitedby/me", + "x-recroom-client-family": "playlists", + "x-recroom-client-service": "rooms/api", + "x-recroom-builds": [ + "2020.12.18" + ], + "x-recroom-inferred-http-method": "get", + "x-recroom-evidence": [ + { + "type": "EJDCNGBEICB", + "method": "RecRoom.Async.IPromise`1> OMMENACPKGH()", + "file": "EJDCNGBEICB.txt", + "line": 2478 + } + ], + "x-client-response-contracts": [ + { + "kind": "array", + "raw": "System.Collections.Generic.List`1", + "rootType": "KMKPEOGJDFK", + "item": { + "kind": "object", + "raw": "KMKPEOGJDFK", + "rootType": "KMKPEOGJDFK" + }, + "typeDescription": { + "name": "KMKPEOGJDFK", + "found": true, + "file": "KMKPEOGJDFK.cs", + "declaration": "public class KMKPEOGJDFK : MKAMHOIHOJK", + "kind": "class", + "baseType": "MKAMHOIHOJK", + "properties": [ + { + "name": "BBGEDJMGFFO", + "type": "long", + "accessibility": "public", + "kind": "property" + } + ], + "fields": [], + "enumValues": [], + "wireKeys": [ + "PlaylistId" + ], + "isilFile": "KMKPEOGJDFK.txt", + "isilType": "KMKPEOGJDFK", + "inherits": "MKAMHOIHOJK", + "inheritedMembers": [ + { + "name": "ACBFDMLHFPB", + "type": "DateTime", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "AHGCOGFEEEE", + "type": "string", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "AKFBNELAMNA", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "BADIGBCKECA", + "type": "int", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "BNBLOBAEDEE", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "CDINMMPNAID", + "type": "NMJEKMMBDDE", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "CDNFGMHLDMJ", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "FIKEBGGCDFN", + "type": "string", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "GIBHIMGJNNO", + "type": "GPDIAKNEBKH", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "HPLBOMGACED", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "IGOPGMHHLKI", + "type": "string", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "JFEAPMIPNEP", + "type": "DPLPMKMFMPB", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "KHIJAFCHLIA", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "KLNJBBPNMBJ", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "KODBEJPEFOJ", + "type": "string", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "LPJLEMJFBPE", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "MGBDHBHCDMH", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "MIKDLDEALPN", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "OFONEIOEIED", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "OILEJFNPDDB", + "type": "HJPGEGENLPH", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "PEEFHKMOMKK", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + } + ], + "inheritedWireKeys": [ + "Name", + "Description", + "ImageName", + "WarningMask", + "CustomWarning", + "CreatorAccountId", + "State", + "Accessibility", + "SupportsLevelVoting", + "IsRRO", + "SupportsScreens", + "SupportsWalkVR", + "SupportsTeleportVR", + "SupportsVRLow", + "SupportsQuest2", + "SupportsMobile", + "SupportsJuniors", + "CreatedAt", + "Stats" + ] + } + } + ] + } + }, + "/roomsandplaylists/hot": { + "get": { + "tags": [ + "playlists" + ], + "summary": "GET roomsandplaylists/hot", + "description": "Inferred from Rec Room 2020 client literal `roomsandplaylists/hot`. Expected result: Playlist object/list or playlist mutation success.", + "operationId": "client_playlists_roomsandplaylists_hot_get", + "parameters": [], + "responses": { + "200": { + "description": "Playlist object/list or playlist mutation success.", + "content": { + "application/json": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/Playlist" + } + } + } + } + }, + "204": { + "description": "Success with no body, used by some mutation endpoints." + }, + "400": { + "description": "Client-visible validation or bad request error.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "401": { + "description": "Authentication required or token invalid.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "500": { + "description": "Server error shape tolerated by client wrappers.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + } + }, + "x-recroom-client-route-literal": "roomsandplaylists/hot", + "x-recroom-client-family": "playlists", + "x-recroom-client-service": "rooms/api", + "x-recroom-builds": [ + "2020.12.18" + ], + "x-recroom-inferred-http-method": "get", + "x-recroom-evidence": [ + { + "type": "EJDCNGBEICB", + "method": "RecRoom.Async.IPromise`1 EBBHGHEGBKG(System.Collections.Generic.IReadOnlyList`1 CAFPJPHILMN)", + "file": "EJDCNGBEICB.txt", + "line": 2645 + } + ], + "x-client-response-contracts": [ + { + "kind": "object", + "raw": "HJKAOMOICJG", + "rootType": "HJKAOMOICJG", + "typeDescription": { + "name": "HJKAOMOICJG", + "found": true, + "file": "HJKAOMOICJG.cs", + "declaration": "public class HJKAOMOICJG : NCONANPODKN, IFAIJAGLDFK", + "kind": "class", + "baseType": "NCONANPODKN", + "properties": [], + "fields": [], + "enumValues": [], + "wireKeys": [ + "TotalResults" + ], + "isilFile": "HJKAOMOICJG.txt", + "isilType": "HJKAOMOICJG", + "inherits": "NCONANPODKN", + "inheritedMembers": [ + { + "name": "KPCBEDOLLFK", + "type": "long", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "NCONANPODKN" + }, + { + "name": "PPLMJPLEHLP", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "NCONANPODKN" + } + ], + "inheritedWireKeys": [] + } + } + ] + } + }, + "/roomsandplaylists/search": { + "get": { + "tags": [ + "playlists" + ], + "summary": "GET roomsandplaylists/search", + "description": "Inferred from Rec Room 2020 client literal `roomsandplaylists/search`. Expected result: Playlist object/list or playlist mutation success.", + "operationId": "client_playlists_roomsandplaylists_search_get", + "parameters": [], + "responses": { + "200": { + "description": "Playlist object/list or playlist mutation success.", + "content": { + "application/json": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/Playlist" + } + } + } + } + }, + "204": { + "description": "Success with no body, used by some mutation endpoints." + }, + "400": { + "description": "Client-visible validation or bad request error.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "401": { + "description": "Authentication required or token invalid.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "500": { + "description": "Server error shape tolerated by client wrappers.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + } + }, + "x-recroom-client-route-literal": "roomsandplaylists/search", + "x-recroom-client-family": "playlists", + "x-recroom-client-service": "rooms/api", + "x-recroom-builds": [ + "2020.12.18" + ], + "x-recroom-inferred-http-method": "get", + "x-recroom-evidence": [ + { + "type": "EJDCNGBEICB", + "method": "RecRoom.Async.IPromise`1 HFHDFLDCIEM(System.String CNBKKCJAHPP)", + "file": "EJDCNGBEICB.txt", + "line": 2560 + } + ], + "x-client-response-contracts": [ + { + "kind": "object", + "raw": "HJKAOMOICJG", + "rootType": "HJKAOMOICJG", + "typeDescription": { + "name": "HJKAOMOICJG", + "found": true, + "file": "HJKAOMOICJG.cs", + "declaration": "public class HJKAOMOICJG : NCONANPODKN, IFAIJAGLDFK", + "kind": "class", + "baseType": "NCONANPODKN", + "properties": [], + "fields": [], + "enumValues": [], + "wireKeys": [ + "TotalResults" + ], + "isilFile": "HJKAOMOICJG.txt", + "isilType": "HJKAOMOICJG", + "inherits": "NCONANPODKN", + "inheritedMembers": [ + { + "name": "KPCBEDOLLFK", + "type": "long", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "NCONANPODKN" + }, + { + "name": "PPLMJPLEHLP", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "NCONANPODKN" + } + ], + "inheritedWireKeys": [] + } + } + ] + } + }, + "/api/quickPlay": { + "get": { + "tags": [ + "quickplay" + ], + "summary": "GET api/quickPlay/", + "description": "Inferred from Rec Room 2020 client literal `api/quickPlay/`. Expected result: Generic object/array/success result inferred from client route.", + "operationId": "client_quickplay_api_quickplay_get", + "parameters": [], + "responses": { + "200": { + "description": "Generic object/array/success result inferred from client route.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Room" + } + } + } + }, + "204": { + "description": "Success with no body, used by some mutation endpoints." + }, + "400": { + "description": "Client-visible validation or bad request error.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "401": { + "description": "Authentication required or token invalid.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "500": { + "description": "Server error shape tolerated by client wrappers.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + } + }, + "x-recroom-client-route-literal": "api/quickPlay/", + "x-recroom-client-family": "quickplay", + "x-recroom-client-service": "api", + "x-recroom-builds": [ + "2020.03.10", + "2020.12.18" + ], + "x-recroom-inferred-http-method": "get", + "x-recroom-evidence": [ + { + "type": "ADOIEPDDEBO", + "method": "RecRoom.Async.IPromise CPGECIJBBKF()", + "file": "ADOIEPDDEBO.txt", + "line": 358 + }, + { + "type": "stringliteral.json", + "method": "", + "file": "Recnet-archive/decompiled/Il2CppDump/stringliteral.json", + "line": null + } + ], + "x-client-response-contracts": [ + { + "kind": "success-or-empty", + "raw": "RecRoom.Async.IPromise", + "rootType": "void/success", + "typeDescription": { + "name": "void/success", + "found": false, + "kind": "object", + "wireKeys": [] + } + } + ] + } + }, + "/{p0}/api/userreporting": { + "post": { + "tags": [ + "reporting" + ], + "summary": "POST {0}/api/userreporting", + "description": "Inferred from Rec Room 2020 client literal `{0}/api/userreporting`. Expected result: Report submission success or reason/code list.", + "operationId": "client_reporting_p0_api_userreporting_post", + "parameters": [ + { + "name": "p0", + "in": "path", + "required": true, + "schema": { + "type": "string" + }, + "description": "Format placeholder from client literal {0}/api/userreporting" + } + ], + "responses": { + "200": { + "description": "Report submission success or reason/code list.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Success" + } + } + } + }, + "204": { + "description": "Success with no body, used by some mutation endpoints." + }, + "400": { + "description": "Client-visible validation or bad request error.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "401": { + "description": "Authentication required or token invalid.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "500": { + "description": "Server error shape tolerated by client wrappers.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + } + }, + "x-recroom-client-route-literal": "{0}/api/userreporting", + "x-recroom-client-family": "reporting", + "x-recroom-client-service": "api", + "x-recroom-builds": [ + "2020.12.18" + ], + "x-recroom-inferred-http-method": "post", + "x-recroom-evidence": [ + { + "type": "KHGPLGBHIAH", + "method": "System.Void PPOCPFGCALB(LGFHFDMDJKK GIGKODMKKHJ, System.Action`2 DFPGDEOCNIH, System.Action`2 AFLPGGJMPOE)", + "file": "KHGPLGBHIAH.txt", + "line": 2838 + } + ], + "requestBody": { + "required": false, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/GenericObject" + } + }, + "application/x-www-form-urlencoded": { + "schema": { + "$ref": "#/components/schemas/GenericObject" + } + }, + "multipart/form-data": { + "schema": { + "$ref": "#/components/schemas/GenericObject" + } + } + }, + "description": "Request body inferred. Exact DTO may be named in x-recroom-evidence method signature or companion DTO inventory." + }, + "x-client-response-contracts": [ + { + "kind": "unknown", + "raw": "", + "rootType": "unknown", + "typeDescription": { + "name": "unknown", + "found": false, + "kind": "object", + "wireKeys": [] + } + } + ] + } + }, + "/api/banappeal/generateCode": { + "post": { + "tags": [ + "reporting" + ], + "summary": "POST api/banappeal/generateCode", + "description": "Inferred from Rec Room 2020 client literal `api/banappeal/generateCode`. Expected result: Report submission success or reason/code list.", + "operationId": "client_reporting_api_banappeal_generatecode_post", + "parameters": [], + "responses": { + "200": { + "description": "Report submission success or reason/code list.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Success" + } + } + } + }, + "204": { + "description": "Success with no body, used by some mutation endpoints." + }, + "400": { + "description": "Client-visible validation or bad request error.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "401": { + "description": "Authentication required or token invalid.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "500": { + "description": "Server error shape tolerated by client wrappers.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + } + }, + "x-recroom-client-route-literal": "api/banappeal/generateCode", + "x-recroom-client-family": "reporting", + "x-recroom-client-service": "api", + "x-recroom-builds": [ + "2020.12.18" + ], + "x-recroom-inferred-http-method": "post", + "x-recroom-evidence": [ + { + "type": "LCCEEFHOBEN", + "method": "RecRoom.Async.IPromise`1 LONDNAJPCEE()", + "file": "LCCEEFHOBEN.txt", + "line": 2149 + } + ], + "requestBody": { + "required": false, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/GenericObject" + } + }, + "application/x-www-form-urlencoded": { + "schema": { + "$ref": "#/components/schemas/GenericObject" + } + }, + "multipart/form-data": { + "schema": { + "$ref": "#/components/schemas/GenericObject" + } + } + }, + "description": "Request body inferred. Exact DTO may be named in x-recroom-evidence method signature or companion DTO inventory." + }, + "x-client-response-contracts": [ + { + "kind": "primitive", + "raw": "System.String", + "rootType": "string", + "typeDescription": { + "name": "string", + "found": false, + "kind": "primitive", + "wireKeys": [] + } + } + ] + } + }, + "/api/PlayerReporting/v1/voteToKickReasons": { + "post": { + "tags": [ + "reporting" + ], + "summary": "POST api/PlayerReporting/v1/voteToKickReasons", + "description": "Inferred from Rec Room 2020 client literal `api/PlayerReporting/v1/voteToKickReasons`. Expected result: Report submission success or reason/code list.", + "operationId": "client_reporting_api_playerreporting_v1_votetokickreasons_post", + "parameters": [], + "responses": { + "200": { + "description": "Report submission success or reason/code list.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Success" + } + } + } + }, + "204": { + "description": "Success with no body, used by some mutation endpoints." + }, + "400": { + "description": "Client-visible validation or bad request error.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "401": { + "description": "Authentication required or token invalid.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "500": { + "description": "Server error shape tolerated by client wrappers.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + } + }, + "x-recroom-client-route-literal": "api/PlayerReporting/v1/voteToKickReasons", + "x-recroom-client-family": "reporting", + "x-recroom-client-service": "api", + "x-recroom-builds": [ + "2020.12.18" + ], + "x-recroom-inferred-http-method": "post", + "x-recroom-evidence": [ + { + "type": "LCCEEFHOBEN", + "method": "RecRoom.Async.IPromise HHBLGNHOGLN()", + "file": "LCCEEFHOBEN.txt", + "line": 2417 + } + ], + "requestBody": { + "required": false, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/GenericObject" + } + }, + "application/x-www-form-urlencoded": { + "schema": { + "$ref": "#/components/schemas/GenericObject" + } + }, + "multipart/form-data": { + "schema": { + "$ref": "#/components/schemas/GenericObject" + } + } + }, + "description": "Request body inferred. Exact DTO may be named in x-recroom-evidence method signature or companion DTO inventory." + }, + "x-client-response-contracts": [ + { + "kind": "success-or-empty", + "raw": "RecRoom.Async.IPromise", + "rootType": "void/success", + "typeDescription": { + "name": "void/success", + "found": false, + "kind": "object", + "wireKeys": [] + } + } + ] + } + }, + "/api/PlayerReporting": { + "post": { + "tags": [ + "reporting" + ], + "summary": "POST api/PlayerReporting/", + "description": "Inferred from Rec Room 2020 client literal `api/PlayerReporting/`. Expected result: Report submission success or reason/code list.", + "operationId": "client_reporting_api_playerreporting_post", + "parameters": [], + "responses": { + "200": { + "description": "Report submission success or reason/code list.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Success" + } + } + } + }, + "204": { + "description": "Success with no body, used by some mutation endpoints." + }, + "400": { + "description": "Client-visible validation or bad request error.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "401": { + "description": "Authentication required or token invalid.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "500": { + "description": "Server error shape tolerated by client wrappers.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + } + }, + "x-recroom-client-route-literal": "api/PlayerReporting/", + "x-recroom-client-family": "reporting", + "x-recroom-client-service": "api", + "x-recroom-builds": [ + "2020.03.10", + "2020.12.18" + ], + "x-recroom-inferred-http-method": "post", + "x-recroom-evidence": [ + { + "type": "LCCEEFHOBEN", + "method": "RecRoom.Async.IPromise FCPPPBKJFGK(LCCEEFHOBEN+OAODPJFGPAF GEDCEIDOKJL, System.String NGPMADFHHKP, System.Nullable`1 IMHODAEGGON = null)", + "file": "LCCEEFHOBEN.txt", + "line": 5148 + }, + { + "type": "LCCEEFHOBEN", + "method": "RecRoom.Async.IPromise IEAOIIFNOGN(System.String PLDIBPNDJIO)", + "file": "LCCEEFHOBEN.txt", + "line": 4715 + }, + { + "type": "LCCEEFHOBEN", + "method": "RecRoom.Async.IPromise`1 NEFPDKLMDHK()", + "file": "LCCEEFHOBEN.txt", + "line": 895 + }, + { + "type": "LCCEEFHOBEN", + "method": "RecRoom.Async.IPromise`1 EKIAMDFOLJM(System.Int64 CJFGEMGOJHB, System.Boolean MAOOLFEBOOD, System.String EMNPHAPBBAH)", + "file": "LCCEEFHOBEN.txt", + "line": 4216 + }, + { + "type": "LCCEEFHOBEN", + "method": "RecRoom.Async.IPromise`1 FFPIOECGNBI(System.Int32 CJFGEMGOJHB)", + "file": "LCCEEFHOBEN.txt", + "line": 4447 + }, + { + "type": "LCCEEFHOBEN", + "method": "RecRoom.Async.IPromise`1 HNFAAFGGDML(System.Collections.Generic.List`1 FNNIKBKHAFN)", + "file": "LCCEEFHOBEN.txt", + "line": 4586 + }, + { + "type": "LCCEEFHOBEN", + "method": "System.Collections.IEnumerator KNAHPBPBPAM(System.Int32 CJFGEMGOJHB, LCCEEFHOBEN+CJFENPHAAHI MEABFEIBEMP, System.String EFDBFLPKHKA, System.Nullable`1 HJMMCCKCDEH, BPHGKAEDBPE+OJDJIJDNFHE AFLPGGJMPOE)", + "file": "LCCEEFHOBEN.txt", + "line": 1783 + }, + { + "type": "stringliteral.json", + "method": "", + "file": "Recnet-archive/decompiled/Il2CppDump/stringliteral.json", + "line": null + } + ], + "requestBody": { + "required": false, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/GenericObject" + } + }, + "application/x-www-form-urlencoded": { + "schema": { + "$ref": "#/components/schemas/GenericObject" + } + }, + "multipart/form-data": { + "schema": { + "$ref": "#/components/schemas/GenericObject" + } + } + }, + "description": "Request body inferred. Exact DTO may be named in x-recroom-evidence method signature or companion DTO inventory." + }, + "x-client-response-contracts": [ + { + "kind": "success-or-empty", + "raw": "RecRoom.Async.IPromise", + "rootType": "void/success", + "typeDescription": { + "name": "void/success", + "found": false, + "kind": "object", + "wireKeys": [] + } + }, + { + "kind": "object", + "raw": "HOGFDJNNMHM", + "rootType": "HOGFDJNNMHM", + "typeDescription": { + "name": "HOGFDJNNMHM", + "found": true, + "file": "HOGFDJNNMHM.cs", + "declaration": "public class HOGFDJNNMHM : IFAIJAGLDFK", + "kind": "class", + "baseType": "IFAIJAGLDFK", + "properties": [ + { + "name": "BKEOCBHCDLM", + "type": "bool", + "accessibility": "public", + "kind": "property" + }, + { + "name": "BPHIOMBNAAI", + "type": "int", + "accessibility": "public", + "kind": "property" + }, + { + "name": "DFPPNHINEFO", + "type": "string", + "accessibility": "public", + "kind": "property" + }, + { + "name": "FNLBJMNDIOB", + "type": "CJFENPHAAHI", + "accessibility": "public", + "kind": "property" + }, + { + "name": "GMJJJKFMFMN", + "type": "long", + "accessibility": "public", + "kind": "property" + }, + { + "name": "JIJDNJNOLLF", + "type": "bool", + "accessibility": "public", + "kind": "property" + }, + { + "name": "KCBFFPAOCMF", + "type": "string", + "accessibility": "public", + "kind": "property" + }, + { + "name": "MCEFCFKMIIH", + "type": "Nullable", + "accessibility": "public", + "kind": "property" + }, + { + "name": "NJCIKENCJFC", + "type": "float", + "accessibility": "public", + "kind": "property" + } + ], + "fields": [], + "enumValues": [], + "wireKeys": [ + "ReportCategory", + "Duration", + "GameSessionId", + "IsHostKick", + "PlayerIdReporter", + "VoteKickReason", + "Message", + "IsBan" + ], + "isilFile": "HOGFDJNNMHM.txt", + "isilType": "HOGFDJNNMHM" + } + }, + { + "kind": "object", + "raw": "KLAMKCBENEA", + "rootType": "KLAMKCBENEA", + "typeDescription": { + "name": "KLAMKCBENEA", + "found": true, + "file": "KLAMKCBENEA.cs", + "declaration": "public class KLAMKCBENEA : IFAIJAGLDFK", + "kind": "class", + "baseType": "IFAIJAGLDFK", + "properties": [ + { + "name": "DFPPNHINEFO", + "type": "string", + "accessibility": "public", + "kind": "property" + }, + { + "name": "ONGEANEKLNE", + "type": "bool", + "accessibility": "public", + "kind": "property" + } + ], + "fields": [], + "enumValues": [], + "wireKeys": [ + "Success", + "Message" + ], + "isilFile": "KLAMKCBENEA.txt", + "isilType": "KLAMKCBENEA" + } + }, + { + "kind": "callback-or-coroutine", + "raw": "IEnumerator", + "rootType": "callback", + "typeDescription": { + "name": "callback", + "found": false, + "kind": "object", + "wireKeys": [] + } + } + ] + } + }, + "/api/userreporting/projects/%7B0%7D/ping": { + "post": { + "tags": [ + "reporting" + ], + "summary": "POST https://userreporting.cloud.unity3d.com/api/userreporting/projects/{0}/ping", + "description": "Inferred from Rec Room 2020 client literal `https://userreporting.cloud.unity3d.com/api/userreporting/projects/{0}/ping`. Expected result: Report submission success or reason/code list.", + "operationId": "client_reporting_api_userreporting_projects_7b0_7d_ping_post", + "parameters": [], + "responses": { + "200": { + "description": "Report submission success or reason/code list.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Success" + } + } + } + }, + "204": { + "description": "Success with no body, used by some mutation endpoints." + }, + "400": { + "description": "Client-visible validation or bad request error.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "401": { + "description": "Authentication required or token invalid.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "500": { + "description": "Server error shape tolerated by client wrappers.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + } + }, + "x-recroom-client-route-literal": "https://userreporting.cloud.unity3d.com/api/userreporting/projects/{0}/ping", + "x-recroom-client-family": "reporting", + "x-recroom-client-service": "api", + "x-recroom-builds": [ + "2020.12.18" + ], + "x-recroom-inferred-http-method": "post", + "x-recroom-evidence": [ + { + "type": "UserReportingScript", + "method": "System.Void Start()", + "file": "UserReportingScript.txt", + "line": 1174 + } + ], + "requestBody": { + "required": false, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/GenericObject" + } + }, + "application/x-www-form-urlencoded": { + "schema": { + "$ref": "#/components/schemas/GenericObject" + } + }, + "multipart/form-data": { + "schema": { + "$ref": "#/components/schemas/GenericObject" + } + } + }, + "description": "Request body inferred. Exact DTO may be named in x-recroom-evidence method signature or companion DTO inventory." + }, + "x-client-response-contracts": [ + { + "kind": "unknown", + "raw": "", + "rootType": "unknown", + "typeDescription": { + "name": "unknown", + "found": false, + "kind": "object", + "wireKeys": [] + } + } + ] + } + }, + "/api/roomkeys/v1/create": { + "post": { + "tags": [ + "room-keys" + ], + "summary": "POST api/roomkeys/v1/create", + "description": "Inferred from Rec Room 2020 client literal `api/roomkeys/v1/create`. Expected result: Generic object/array/success result inferred from client route.", + "operationId": "client_room_keys_api_roomkeys_v1_create_post", + "parameters": [], + "responses": { + "200": { + "description": "Generic object/array/success result inferred from client route.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RoomKey" + } + } + } + }, + "204": { + "description": "Success with no body, used by some mutation endpoints." + }, + "400": { + "description": "Client-visible validation or bad request error.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "401": { + "description": "Authentication required or token invalid.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "500": { + "description": "Server error shape tolerated by client wrappers.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + } + }, + "x-recroom-client-route-literal": "api/roomkeys/v1/create", + "x-recroom-client-family": "room-keys", + "x-recroom-client-service": "rooms/api", + "x-recroom-builds": [ + "2020.12.18" + ], + "x-recroom-inferred-http-method": "post", + "x-recroom-evidence": [ + { + "type": "IOIBJBLIBKM", + "method": "RecRoom.Async.IPromise`1 BIAKPHAECJC(System.String MMBOKOLAJFH, System.String LJIGOCDPEJF, System.Int32 MACNIENMFHJ)", + "file": "IOIBJBLIBKM.txt", + "line": 573 + } + ], + "requestBody": { + "required": false, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/GenericObject" + } + }, + "application/x-www-form-urlencoded": { + "schema": { + "$ref": "#/components/schemas/GenericObject" + } + }, + "multipart/form-data": { + "schema": { + "$ref": "#/components/schemas/GenericObject" + } + } + }, + "description": "Request body inferred. Exact DTO may be named in x-recroom-evidence method signature or companion DTO inventory." + }, + "x-client-response-contracts": [ + { + "kind": "object", + "raw": "BCKIBFNPIPD", + "rootType": "BCKIBFNPIPD", + "typeDescription": { + "name": "BCKIBFNPIPD", + "found": true, + "file": "BCKIBFNPIPD.cs", + "declaration": "public class BCKIBFNPIPD : IFAIJAGLDFK", + "kind": "class", + "baseType": "IFAIJAGLDFK", + "properties": [ + { + "name": "HIMCGOCKLLK", + "type": "BMHHFIGBOFD", + "accessibility": "public", + "kind": "property" + }, + { + "name": "PIJLKMENAPG", + "type": "AMAGKLLBGEC", + "accessibility": "public", + "kind": "property" + } + ], + "fields": [], + "enumValues": [], + "wireKeys": [ + "Status", + "RoomKey" + ], + "isilFile": "BCKIBFNPIPD.txt", + "isilType": "BCKIBFNPIPD" + } + } + ] + } + }, + "/api/roomkeys/v1/mine": { + "get": { + "tags": [ + "room-keys" + ], + "summary": "GET api/roomkeys/v1/mine", + "description": "Inferred from Rec Room 2020 client literal `api/roomkeys/v1/mine`. Expected result: Generic object/array/success result inferred from client route.", + "operationId": "client_room_keys_api_roomkeys_v1_mine_get", + "parameters": [], + "responses": { + "200": { + "description": "Generic object/array/success result inferred from client route.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RoomKey" + } + } + } + }, + "204": { + "description": "Success with no body, used by some mutation endpoints." + }, + "400": { + "description": "Client-visible validation or bad request error.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "401": { + "description": "Authentication required or token invalid.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "500": { + "description": "Server error shape tolerated by client wrappers.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + } + }, + "x-recroom-client-route-literal": "api/roomkeys/v1/mine", + "x-recroom-client-family": "room-keys", + "x-recroom-client-service": "rooms/api", + "x-recroom-builds": [ + "2020.12.18" + ], + "x-recroom-inferred-http-method": "get", + "x-recroom-evidence": [ + { + "type": "IOIBJBLIBKM", + "method": "RecRoom.Async.IPromise`1> IGFLECMACBE()", + "file": "IOIBJBLIBKM.txt", + "line": 1704 + } + ], + "x-client-response-contracts": [ + { + "kind": "array", + "raw": "System.Collections.Generic.List`1", + "rootType": "AMAGKLLBGEC", + "item": { + "kind": "object", + "raw": "AMAGKLLBGEC", + "rootType": "AMAGKLLBGEC" + }, + "typeDescription": { + "name": "AMAGKLLBGEC", + "found": true, + "file": "AMAGKLLBGEC.cs", + "declaration": "public class AMAGKLLBGEC : IFAIJAGLDFK", + "kind": "class", + "baseType": "IFAIJAGLDFK", + "properties": [ + { + "name": "DADOKMAOFJL", + "type": "long", + "accessibility": "public", + "kind": "property" + }, + { + "name": "FIKEBGGCDFN", + "type": "string", + "accessibility": "public", + "kind": "property" + }, + { + "name": "HKCFEOMMOOL", + "type": "long", + "accessibility": "public", + "kind": "property" + }, + { + "name": "KODBEJPEFOJ", + "type": "string", + "accessibility": "public", + "kind": "property" + }, + { + "name": "MLDAFKCENMA", + "type": "Guid", + "accessibility": "public", + "kind": "property" + }, + { + "name": "NPDJLOOHMDJ", + "type": "int", + "accessibility": "public", + "kind": "property" + } + ], + "fields": [], + "enumValues": [], + "wireKeys": [ + "RoomKeyId", + "ReplicationId", + "RoomId", + "Name", + "Description", + "Price" + ], + "isilFile": "AMAGKLLBGEC.txt", + "isilType": "AMAGKLLBGEC" + } + } + ] + } + }, + "/api/roomkeys/v1/update": { + "post": { + "tags": [ + "room-keys" + ], + "summary": "POST api/roomkeys/v1/update", + "description": "Inferred from Rec Room 2020 client literal `api/roomkeys/v1/update`. Expected result: Generic object/array/success result inferred from client route.", + "operationId": "client_room_keys_api_roomkeys_v1_update_post", + "parameters": [], + "responses": { + "200": { + "description": "Generic object/array/success result inferred from client route.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RoomKey" + } + } + } + }, + "204": { + "description": "Success with no body, used by some mutation endpoints." + }, + "400": { + "description": "Client-visible validation or bad request error.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "401": { + "description": "Authentication required or token invalid.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "500": { + "description": "Server error shape tolerated by client wrappers.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + } + }, + "x-recroom-client-route-literal": "api/roomkeys/v1/update", + "x-recroom-client-family": "room-keys", + "x-recroom-client-service": "rooms/api", + "x-recroom-builds": [ + "2020.12.18" + ], + "x-recroom-inferred-http-method": "post", + "x-recroom-evidence": [ + { + "type": "IOIBJBLIBKM", + "method": "RecRoom.Async.IPromise`1 MLFKLGGPLNM(System.Int64 BGICHOOBKLD, System.String MMBOKOLAJFH = null, System.String LJIGOCDPEJF = null, System.Nullable`1 MACNIENMFHJ = null)", + "file": "IOIBJBLIBKM.txt", + "line": 1008 + } + ], + "requestBody": { + "required": false, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/GenericObject" + } + }, + "application/x-www-form-urlencoded": { + "schema": { + "$ref": "#/components/schemas/GenericObject" + } + }, + "multipart/form-data": { + "schema": { + "$ref": "#/components/schemas/GenericObject" + } + } + }, + "description": "Request body inferred. Exact DTO may be named in x-recroom-evidence method signature or companion DTO inventory." + }, + "x-client-response-contracts": [ + { + "kind": "object", + "raw": "BCKIBFNPIPD", + "rootType": "BCKIBFNPIPD", + "typeDescription": { + "name": "BCKIBFNPIPD", + "found": true, + "file": "BCKIBFNPIPD.cs", + "declaration": "public class BCKIBFNPIPD : IFAIJAGLDFK", + "kind": "class", + "baseType": "IFAIJAGLDFK", + "properties": [ + { + "name": "HIMCGOCKLLK", + "type": "BMHHFIGBOFD", + "accessibility": "public", + "kind": "property" + }, + { + "name": "PIJLKMENAPG", + "type": "AMAGKLLBGEC", + "accessibility": "public", + "kind": "property" + } + ], + "fields": [], + "enumValues": [], + "wireKeys": [ + "Status", + "RoomKey" + ], + "isilFile": "BCKIBFNPIPD.txt", + "isilType": "BCKIBFNPIPD" + } + } + ] + } + }, + "/api/roomkeys": { + "get": { + "tags": [ + "room-keys" + ], + "summary": "GET api/roomkeys/", + "description": "Inferred from Rec Room 2020 client literal `api/roomkeys/`. Expected result: Generic object/array/success result inferred from client route.", + "operationId": "client_room_keys_api_roomkeys_get", + "parameters": [], + "responses": { + "200": { + "description": "Generic object/array/success result inferred from client route.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RoomKey" + } + } + } + }, + "204": { + "description": "Success with no body, used by some mutation endpoints." + }, + "400": { + "description": "Client-visible validation or bad request error.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "401": { + "description": "Authentication required or token invalid.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "500": { + "description": "Server error shape tolerated by client wrappers.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + } + }, + "x-recroom-client-route-literal": "api/roomkeys/", + "x-recroom-client-family": "room-keys", + "x-recroom-client-service": "rooms/api", + "x-recroom-builds": [ + "2020.12.18" + ], + "x-recroom-inferred-http-method": "get", + "x-recroom-evidence": [ + { + "type": "IOIBJBLIBKM", + "method": "RecRoom.Async.IPromise`1 ACCJPCMPJMH(System.Int64 BGICHOOBKLD)", + "file": "IOIBJBLIBKM.txt", + "line": 1299 + }, + { + "type": "IOIBJBLIBKM", + "method": "RecRoom.Async.IPromise`1 HECNNKLKKAG(System.Int64 BGICHOOBKLD)", + "file": "IOIBJBLIBKM.txt", + "line": 1517 + }, + { + "type": "IOIBJBLIBKM", + "method": "RecRoom.Async.IPromise`1 LAPDFFOJCNP(System.Int32 CJFGEMGOJHB, AMAGKLLBGEC AAHIIDDIBFD)", + "file": "IOIBJBLIBKM.txt", + "line": 1866 + }, + { + "type": "IOIBJBLIBKM", + "method": "System.String JGLHIEEFHKE()", + "file": "IOIBJBLIBKM.txt", + "line": 58 + } + ], + "x-client-response-contracts": [ + { + "kind": "object", + "raw": "BMHHFIGBOFD", + "rootType": "BMHHFIGBOFD", + "typeDescription": { + "name": "BMHHFIGBOFD", + "found": true, + "file": "BMHHFIGBOFD.cs", + "declaration": "public enum BMHHFIGBOFD", + "kind": "enum", + "baseType": null, + "properties": [], + "fields": [], + "enumValues": [ + { + "name": "Success", + "value": "0" + }, + { + "name": "InvalidParameters", + "value": "1" + }, + { + "name": "DoesNotExist", + "value": "2" + }, + { + "name": "NameTooShort", + "value": "3" + }, + { + "name": "NameTooLong", + "value": "4" + }, + { + "name": "DuplicateName", + "value": "5" + }, + { + "name": "InappropriateName", + "value": "6" + }, + { + "name": "DescriptionTooShort", + "value": "7" + }, + { + "name": "DescriptionTooLong", + "value": "8" + }, + { + "name": "InappropriateDescription", + "value": "9" + }, + { + "name": "PriceTooLow", + "value": "10" + }, + { + "name": "PriceTooHigh", + "value": "11" + }, + { + "name": "PermissionDenied", + "value": "12" + }, + { + "name": "PlayerHasRoomUnderModerationReview", + "value": "13" + }, + { + "name": "JuniorStatusFail", + "value": "14" + }, + { + "name": "PlayerIsNotCoOwner", + "value": "15" + }, + { + "name": "RoomKeyLimitReached", + "value": "16" + }, + { + "name": "PlayerAlreadyOwns", + "value": "17" + }, + { + "name": "RoomUnderModerationReview", + "value": "18" + }, + { + "name": "PurchaseFailed", + "value": "19" + }, + { + "name": "RoomDoesNotExist", + "value": "20" + }, + { + "name": "PaidKeyPurchasingDisabled", + "value": "21" + }, + { + "name": "CreateOrModifyKeysDisabled", + "value": "22" + }, + { + "name": "RoomKeyUnderModerationReview", + "value": "23" + }, + { + "name": "PlayerRestrictedFromP2PSelling", + "value": "24" + }, + { + "name": "PlayerNotRecRoomPlusMember", + "value": "25" + } + ], + "wireKeys": [] + } + }, + { + "kind": "primitive", + "raw": "System.Boolean", + "rootType": "boolean", + "typeDescription": { + "name": "boolean", + "found": false, + "kind": "primitive", + "wireKeys": [] + } + }, + { + "kind": "unknown", + "raw": "", + "rootType": "unknown", + "typeDescription": { + "name": "unknown", + "found": false, + "kind": "object", + "wireKeys": [] + } + } + ] + } + }, + "/api/rooms/{tail}": { + "get": { + "tags": [ + "rooms" + ], + "summary": "GET api/rooms/", + "description": "Inferred from Rec Room 2020 client literal `api/rooms/`. Expected result: Room detail object or mutation success.", + "operationId": "client_rooms_api_rooms_tail_get", + "parameters": [ + { + "name": "tail", + "in": "path", + "required": true, + "schema": { + "type": "string" + }, + "description": "Format placeholder from client literal api/rooms/" + } + ], + "responses": { + "200": { + "description": "Room detail object or mutation success.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Room" + } + } + } + }, + "204": { + "description": "Success with no body, used by some mutation endpoints." + }, + "400": { + "description": "Client-visible validation or bad request error.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "401": { + "description": "Authentication required or token invalid.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "500": { + "description": "Server error shape tolerated by client wrappers.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + } + }, + "x-recroom-client-route-literal": "api/rooms/", + "x-recroom-client-family": "rooms", + "x-recroom-client-service": "rooms/api", + "x-recroom-builds": [ + "2020.03.10" + ], + "x-recroom-inferred-http-method": "get", + "x-recroom-evidence": [ + { + "type": "stringliteral.json", + "method": "", + "file": "Recnet-archive/decompiled/Il2CppDump/stringliteral.json", + "line": null + } + ] + } + }, + "/api/rooms/v1/filters": { + "get": { + "tags": [ + "rooms" + ], + "summary": "GET api/rooms/v1/filters", + "description": "Inferred from Rec Room 2020 client literal `api/rooms/v1/filters`. Expected result: Room detail object or mutation success.", + "operationId": "client_rooms_api_rooms_v1_filters_get", + "parameters": [], + "responses": { + "200": { + "description": "Room detail object or mutation success.", + "content": { + "application/json": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/Room" + } + } + } + } + }, + "204": { + "description": "Success with no body, used by some mutation endpoints." + }, + "400": { + "description": "Client-visible validation or bad request error.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "401": { + "description": "Authentication required or token invalid.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "500": { + "description": "Server error shape tolerated by client wrappers.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + } + }, + "x-recroom-client-route-literal": "api/rooms/v1/filters", + "x-recroom-client-family": "rooms", + "x-recroom-client-service": "rooms/api", + "x-recroom-builds": [ + "2020.12.18" + ], + "x-recroom-inferred-http-method": "get", + "x-recroom-evidence": [ + { + "type": "OJMCBOKJFOF+<>c", + "method": "RecRoom.Async.IPromise`1 b__114_0()", + "file": "OJMCBOKJFOF_NestedType___c.txt", + "line": 1617 + } + ], + "x-client-response-contracts": [ + { + "kind": "object", + "raw": "AEBEPCMAABC", + "rootType": "AEBEPCMAABC", + "typeDescription": { + "name": "AEBEPCMAABC", + "found": true, + "file": "AEBEPCMAABC.cs", + "declaration": "public class AEBEPCMAABC : IFAIJAGLDFK", + "kind": "class", + "baseType": "IFAIJAGLDFK", + "properties": [ + { + "name": "EBIKIFGHJBN", + "type": "List", + "accessibility": "public", + "kind": "property" + }, + { + "name": "JGPAPFCFDJG", + "type": "List", + "accessibility": "public", + "kind": "property" + } + ], + "fields": [], + "enumValues": [], + "wireKeys": [], + "isilFile": "AEBEPCMAABC.txt", + "isilType": "AEBEPCMAABC" + } + } + ] + } + }, + "/api/rooms/v1/roomRolePermissions": { + "get": { + "tags": [ + "rooms" + ], + "summary": "GET api/rooms/v1/roomRolePermissions", + "description": "Inferred from Rec Room 2020 client literal `api/rooms/v1/roomRolePermissions`. Expected result: Room detail object or mutation success.", + "operationId": "client_rooms_api_rooms_v1_roomrolepermissions_get", + "parameters": [], + "responses": { + "200": { + "description": "Room detail object or mutation success.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Room" + } + } + } + }, + "204": { + "description": "Success with no body, used by some mutation endpoints." + }, + "400": { + "description": "Client-visible validation or bad request error.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "401": { + "description": "Authentication required or token invalid.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "500": { + "description": "Server error shape tolerated by client wrappers.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + } + }, + "x-recroom-client-route-literal": "api/rooms/v1/roomRolePermissions", + "x-recroom-client-family": "rooms", + "x-recroom-client-service": "rooms/api", + "x-recroom-builds": [ + "2020.03.10" + ], + "x-recroom-inferred-http-method": "get", + "x-recroom-evidence": [ + { + "type": "stringliteral.json", + "method": "", + "file": "Recnet-archive/decompiled/Il2CppDump/stringliteral.json", + "line": null + } + ] + } + }, + "/api/rooms/v1/verifyRole": { + "get": { + "tags": [ + "rooms" + ], + "summary": "GET api/rooms/v1/verifyRole", + "description": "Inferred from Rec Room 2020 client literal `api/rooms/v1/verifyRole`. Expected result: Room detail object or mutation success.", + "operationId": "client_rooms_api_rooms_v1_verifyrole_get", + "parameters": [], + "responses": { + "200": { + "description": "Room detail object or mutation success.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Room" + } + } + } + }, + "204": { + "description": "Success with no body, used by some mutation endpoints." + }, + "400": { + "description": "Client-visible validation or bad request error.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "401": { + "description": "Authentication required or token invalid.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "500": { + "description": "Server error shape tolerated by client wrappers.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + } + }, + "x-recroom-client-route-literal": "api/rooms/v1/verifyRole", + "x-recroom-client-family": "rooms", + "x-recroom-client-service": "rooms/api", + "x-recroom-builds": [ + "2020.12.18" + ], + "x-recroom-inferred-http-method": "get", + "x-recroom-evidence": [ + { + "type": "OJMCBOKJFOF", + "method": "RecRoom.Async.IPromise INDDAAGNMHF(System.String LHOMKMINCHH)", + "file": "OJMCBOKJFOF.txt", + "line": 12963 + } + ], + "x-client-response-contracts": [ + { + "kind": "success-or-empty", + "raw": "RecRoom.Async.IPromise", + "rootType": "void/success", + "typeDescription": { + "name": "void/success", + "found": false, + "kind": "object", + "wireKeys": [] + } + } + ] + } + }, + "/api/rooms/v2/report": { + "post": { + "tags": [ + "rooms" + ], + "summary": "POST api/rooms/v2/report", + "description": "Inferred from Rec Room 2020 client literal `api/rooms/v2/report`. Expected result: Room detail object or mutation success.", + "operationId": "client_rooms_api_rooms_v2_report_post", + "parameters": [], + "responses": { + "200": { + "description": "Room detail object or mutation success.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Room" + } + } + } + }, + "204": { + "description": "Success with no body, used by some mutation endpoints." + }, + "400": { + "description": "Client-visible validation or bad request error.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "401": { + "description": "Authentication required or token invalid.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "500": { + "description": "Server error shape tolerated by client wrappers.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + } + }, + "x-recroom-client-route-literal": "api/rooms/v2/report", + "x-recroom-client-family": "rooms", + "x-recroom-client-service": "rooms/api", + "x-recroom-builds": [ + "2020.12.18" + ], + "x-recroom-inferred-http-method": "post", + "x-recroom-evidence": [ + { + "type": "OJMCBOKJFOF", + "method": "RecRoom.Async.IPromise GECDGBACBFP(System.Int64 HNHLJONGKHB, LCCEEFHOBEN+CJFENPHAAHI MEABFEIBEMP, System.String EFDBFLPKHKA)", + "file": "OJMCBOKJFOF.txt", + "line": 12627 + } + ], + "requestBody": { + "required": false, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/GenericObject" + } + }, + "application/x-www-form-urlencoded": { + "schema": { + "$ref": "#/components/schemas/GenericObject" + } + }, + "multipart/form-data": { + "schema": { + "$ref": "#/components/schemas/GenericObject" + } + } + }, + "description": "Request body inferred. Exact DTO may be named in x-recroom-evidence method signature or companion DTO inventory." + }, + "x-client-response-contracts": [ + { + "kind": "success-or-empty", + "raw": "RecRoom.Async.IPromise", + "rootType": "void/success", + "typeDescription": { + "name": "void/success", + "found": false, + "kind": "object", + "wireKeys": [] + } + } + ] + } + }, + "/api/rooms/v4/saveData": { + "post": { + "tags": [ + "rooms" + ], + "summary": "POST api/rooms/v4/saveData", + "description": "Inferred from Rec Room 2020 client literal `api/rooms/v4/saveData`. Expected result: Room detail object or mutation success.", + "operationId": "client_rooms_api_rooms_v4_savedata_post", + "parameters": [], + "responses": { + "200": { + "description": "Room detail object or mutation success.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Room" + } + } + } + }, + "204": { + "description": "Success with no body, used by some mutation endpoints." + }, + "400": { + "description": "Client-visible validation or bad request error.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "401": { + "description": "Authentication required or token invalid.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "500": { + "description": "Server error shape tolerated by client wrappers.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + } + }, + "x-recroom-client-route-literal": "api/rooms/v4/saveData", + "x-recroom-client-family": "rooms", + "x-recroom-client-service": "rooms/api", + "x-recroom-builds": [ + "2020.03.10" + ], + "x-recroom-inferred-http-method": "post", + "x-recroom-evidence": [ + { + "type": "stringliteral.json", + "method": "", + "file": "Recnet-archive/decompiled/Il2CppDump/stringliteral.json", + "line": null + } + ], + "requestBody": { + "required": false, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/GenericObject" + } + }, + "application/x-www-form-urlencoded": { + "schema": { + "$ref": "#/components/schemas/GenericObject" + } + }, + "multipart/form-data": { + "schema": { + "$ref": "#/components/schemas/GenericObject" + } + } + }, + "description": "Request body inferred. Exact DTO may be named in x-recroom-evidence method signature or companion DTO inventory." + } + } + }, + "/hot_rooms/{tail}": { + "get": { + "tags": [ + "rooms" + ], + "summary": "GET hot_rooms/", + "description": "Inferred from Rec Room 2020 client literal `hot_rooms/`. Expected result: Room summary/detail array or page.", + "operationId": "client_rooms_hot_rooms_tail_get", + "parameters": [ + { + "name": "tail", + "in": "path", + "required": true, + "schema": { + "type": "string" + }, + "description": "Format placeholder from client literal hot_rooms/" + } + ], + "responses": { + "200": { + "description": "Room summary/detail array or page.", + "content": { + "application/json": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/Room" + } + } + } + } + }, + "204": { + "description": "Success with no body, used by some mutation endpoints." + }, + "400": { + "description": "Client-visible validation or bad request error.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "401": { + "description": "Authentication required or token invalid.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "500": { + "description": "Server error shape tolerated by client wrappers.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + } + }, + "x-recroom-client-route-literal": "hot_rooms/", + "x-recroom-client-family": "rooms", + "x-recroom-client-service": "rooms/api", + "x-recroom-builds": [ + "2020.12.18" + ], + "x-recroom-inferred-http-method": "get", + "x-recroom-evidence": [ + { + "type": "OJMCBOKJFOF", + "method": "RecRoom.Async.IPromise`1> DLPLPKCNLNA(System.Collections.Generic.IReadOnlyList`1 CAFPJPHILMN)", + "file": "OJMCBOKJFOF.txt", + "line": 3238 + }, + { + "type": "OJMCBOKJFOF+PEBCIEAPGON", + "method": "System.String CMOIGEDECGM(System.Collections.Generic.IEnumerable`1 CAFPJPHILMN)", + "file": "OJMCBOKJFOF_NestedType_PEBCIEAPGON.txt", + "line": 39 + } + ], + "x-client-response-contracts": [ + { + "kind": "array", + "raw": "System.Collections.Generic.IReadOnlyList`1", + "rootType": "KLCOGEIGEBJ", + "item": { + "kind": "object", + "raw": "KLCOGEIGEBJ", + "rootType": "KLCOGEIGEBJ" + }, + "typeDescription": { + "name": "KLCOGEIGEBJ", + "found": true, + "file": "KLCOGEIGEBJ.cs", + "declaration": "public class KLCOGEIGEBJ : MKAMHOIHOJK", + "kind": "class", + "baseType": "MKAMHOIHOJK", + "properties": [ + { + "name": "DADOKMAOFJL", + "type": "long", + "accessibility": "public", + "kind": "property" + }, + { + "name": "GJPGKJJCPBK", + "type": "bool", + "accessibility": "public", + "kind": "property" + }, + { + "name": "IBLOJJBEKFF", + "type": "bool", + "accessibility": "public", + "kind": "property" + }, + { + "name": "JCLCOCEOAEP", + "type": "bool", + "accessibility": "public", + "kind": "property" + }, + { + "name": "JGJCBGEBLLO", + "type": "bool", + "accessibility": "public", + "kind": "property" + }, + { + "name": "PLFEEIJCHOH", + "type": "bool", + "accessibility": "public", + "kind": "property" + } + ], + "fields": [ + { + "name": "BJFNPKFOALK", + "type": "long", + "kind": "field" + } + ], + "enumValues": [], + "wireKeys": [ + "RoomId", + "IsDorm", + "CloningAllowed", + "DisableMicAutoMute", + "DisableRoomComments", + "EncryptVoiceChat" + ], + "isilFile": "KLCOGEIGEBJ.txt", + "isilType": "KLCOGEIGEBJ", + "inherits": "MKAMHOIHOJK", + "inheritedMembers": [ + { + "name": "ACBFDMLHFPB", + "type": "DateTime", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "AHGCOGFEEEE", + "type": "string", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "AKFBNELAMNA", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "BADIGBCKECA", + "type": "int", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "BNBLOBAEDEE", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "CDINMMPNAID", + "type": "NMJEKMMBDDE", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "CDNFGMHLDMJ", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "FIKEBGGCDFN", + "type": "string", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "GIBHIMGJNNO", + "type": "GPDIAKNEBKH", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "HPLBOMGACED", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "IGOPGMHHLKI", + "type": "string", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "JFEAPMIPNEP", + "type": "DPLPMKMFMPB", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "KHIJAFCHLIA", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "KLNJBBPNMBJ", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "KODBEJPEFOJ", + "type": "string", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "LPJLEMJFBPE", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "MGBDHBHCDMH", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "MIKDLDEALPN", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "OFONEIOEIED", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "OILEJFNPDDB", + "type": "HJPGEGENLPH", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "PEEFHKMOMKK", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + } + ], + "inheritedWireKeys": [ + "Name", + "Description", + "ImageName", + "WarningMask", + "CustomWarning", + "CreatorAccountId", + "State", + "Accessibility", + "SupportsLevelVoting", + "IsRRO", + "SupportsScreens", + "SupportsWalkVR", + "SupportsTeleportVR", + "SupportsVRLow", + "SupportsQuest2", + "SupportsMobile", + "SupportsJuniors", + "CreatedAt", + "Stats" + ] + } + }, + { + "kind": "unknown", + "raw": "", + "rootType": "unknown", + "typeDescription": { + "name": "unknown", + "found": false, + "kind": "object", + "wireKeys": [] + } + } + ] + } + }, + "/hot_roomsandplaylists/{tail}": { + "get": { + "tags": [ + "rooms" + ], + "summary": "GET hot_roomsandplaylists/", + "description": "Inferred from Rec Room 2020 client literal `hot_roomsandplaylists/`. Expected result: Room summary/detail array or page.", + "operationId": "client_rooms_hot_roomsandplaylists_tail_get", + "parameters": [ + { + "name": "tail", + "in": "path", + "required": true, + "schema": { + "type": "string" + }, + "description": "Format placeholder from client literal hot_roomsandplaylists/" + } + ], + "responses": { + "200": { + "description": "Room summary/detail array or page.", + "content": { + "application/json": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/Room" + } + } + } + } + }, + "204": { + "description": "Success with no body, used by some mutation endpoints." + }, + "400": { + "description": "Client-visible validation or bad request error.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "401": { + "description": "Authentication required or token invalid.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "500": { + "description": "Server error shape tolerated by client wrappers.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + } + }, + "x-recroom-client-route-literal": "hot_roomsandplaylists/", + "x-recroom-client-family": "rooms", + "x-recroom-client-service": "rooms/api", + "x-recroom-builds": [ + "2020.12.18" + ], + "x-recroom-inferred-http-method": "get", + "x-recroom-evidence": [ + { + "type": "OJMCBOKJFOF", + "method": "RecRoom.Async.IPromise`1> EBBHGHEGBKG(System.Collections.Generic.IReadOnlyList`1 CAFPJPHILMN)", + "file": "OJMCBOKJFOF.txt", + "line": 4743 + }, + { + "type": "OJMCBOKJFOF+PEBCIEAPGON", + "method": "System.String MMCLGMMDDJF(System.Collections.Generic.IEnumerable`1 CAFPJPHILMN)", + "file": "OJMCBOKJFOF_NestedType_PEBCIEAPGON.txt", + "line": 121 + } + ], + "x-client-response-contracts": [ + { + "kind": "array", + "raw": "System.Collections.Generic.IReadOnlyList`1", + "rootType": "MKAMHOIHOJK", + "item": { + "kind": "object", + "raw": "MKAMHOIHOJK", + "rootType": "MKAMHOIHOJK" + }, + "typeDescription": { + "name": "MKAMHOIHOJK", + "found": true, + "file": "MKAMHOIHOJK.cs", + "declaration": "public abstract class MKAMHOIHOJK : IFAIJAGLDFK, AKJKEMONOIL", + "kind": "class", + "baseType": "IFAIJAGLDFK", + "properties": [ + { + "name": "ACBFDMLHFPB", + "type": "DateTime", + "accessibility": "public", + "kind": "property" + }, + { + "name": "AHGCOGFEEEE", + "type": "string", + "accessibility": "public", + "kind": "property" + }, + { + "name": "AKFBNELAMNA", + "type": "bool", + "accessibility": "public", + "kind": "property" + }, + { + "name": "BADIGBCKECA", + "type": "int", + "accessibility": "public", + "kind": "property" + }, + { + "name": "BNBLOBAEDEE", + "type": "bool", + "accessibility": "public", + "kind": "property" + }, + { + "name": "CDINMMPNAID", + "type": "NMJEKMMBDDE", + "accessibility": "public", + "kind": "property" + }, + { + "name": "CDNFGMHLDMJ", + "type": "bool", + "accessibility": "public", + "kind": "property" + }, + { + "name": "FIKEBGGCDFN", + "type": "string", + "accessibility": "public", + "kind": "property" + }, + { + "name": "GIBHIMGJNNO", + "type": "GPDIAKNEBKH", + "accessibility": "public", + "kind": "property" + }, + { + "name": "HPLBOMGACED", + "type": "bool", + "accessibility": "public", + "kind": "property" + }, + { + "name": "IGOPGMHHLKI", + "type": "string", + "accessibility": "public", + "kind": "property" + }, + { + "name": "JFEAPMIPNEP", + "type": "DPLPMKMFMPB", + "accessibility": "public", + "kind": "property" + }, + { + "name": "KHIJAFCHLIA", + "type": "bool", + "accessibility": "public", + "kind": "property" + }, + { + "name": "KLNJBBPNMBJ", + "type": "bool", + "accessibility": "public", + "kind": "property" + }, + { + "name": "KODBEJPEFOJ", + "type": "string", + "accessibility": "public", + "kind": "property" + }, + { + "name": "LPJLEMJFBPE", + "type": "bool", + "accessibility": "public", + "kind": "property" + }, + { + "name": "MGBDHBHCDMH", + "type": "bool", + "accessibility": "public", + "kind": "property" + }, + { + "name": "MIKDLDEALPN", + "type": "bool", + "accessibility": "public", + "kind": "property" + }, + { + "name": "OFONEIOEIED", + "type": "bool", + "accessibility": "public", + "kind": "property" + }, + { + "name": "OILEJFNPDDB", + "type": "HJPGEGENLPH", + "accessibility": "public", + "kind": "property" + }, + { + "name": "PEEFHKMOMKK", + "type": "bool", + "accessibility": "public", + "kind": "property" + } + ], + "fields": [], + "enumValues": [], + "wireKeys": [ + "Name", + "Description", + "ImageName", + "WarningMask", + "CustomWarning", + "CreatorAccountId", + "State", + "Accessibility", + "SupportsLevelVoting", + "IsRRO", + "SupportsScreens", + "SupportsWalkVR", + "SupportsTeleportVR", + "SupportsVRLow", + "SupportsQuest2", + "SupportsMobile", + "SupportsJuniors", + "CreatedAt", + "Stats" + ], + "isilFile": "MKAMHOIHOJK.txt", + "isilType": "MKAMHOIHOJK" + } + }, + { + "kind": "unknown", + "raw": "", + "rootType": "unknown", + "typeDescription": { + "name": "unknown", + "found": false, + "kind": "object", + "wireKeys": [] + } + } + ] + } + }, + "/room/{p0}": { + "get": { + "tags": [ + "rooms" + ], + "summary": "GET /room/{0}", + "description": "Inferred from Rec Room 2020 client literal `/room/{0}`. Expected result: Room detail object or mutation success.", + "operationId": "client_rooms_room_p0_get", + "parameters": [ + { + "name": "p0", + "in": "path", + "required": true, + "schema": { + "type": "string" + }, + "description": "Format placeholder from client literal /room/{0}" + } + ], + "responses": { + "200": { + "description": "Room detail object or mutation success.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Room" + } + } + } + }, + "204": { + "description": "Success with no body, used by some mutation endpoints." + }, + "400": { + "description": "Client-visible validation or bad request error.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "401": { + "description": "Authentication required or token invalid.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "500": { + "description": "Server error shape tolerated by client wrappers.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + } + }, + "x-recroom-client-route-literal": "/room/{0}", + "x-recroom-client-family": "rooms", + "x-recroom-client-service": "rooms/api", + "x-recroom-builds": [ + "2020.03.10" + ], + "x-recroom-inferred-http-method": "get", + "x-recroom-evidence": [ + { + "type": "stringliteral.json", + "method": "", + "file": "Recnet-archive/decompiled/Il2CppDump/stringliteral.json", + "line": null + } + ] + } + }, + "/rooms/{p0}/accessibility": { + "post": { + "tags": [ + "rooms" + ], + "summary": "POST rooms/{0}/accessibility", + "description": "Inferred from Rec Room 2020 client literal `rooms/{0}/accessibility`. Expected result: Room detail object or mutation success.", + "operationId": "client_rooms_rooms_p0_accessibility_post", + "parameters": [ + { + "name": "p0", + "in": "path", + "required": true, + "schema": { + "type": "string" + }, + "description": "Format placeholder from client literal rooms/{0}/accessibility" + } + ], + "responses": { + "200": { + "description": "Room detail object or mutation success.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Room" + } + } + } + }, + "204": { + "description": "Success with no body, used by some mutation endpoints." + }, + "400": { + "description": "Client-visible validation or bad request error.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "401": { + "description": "Authentication required or token invalid.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "500": { + "description": "Server error shape tolerated by client wrappers.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + } + }, + "x-recroom-client-route-literal": "rooms/{0}/accessibility", + "x-recroom-client-family": "rooms", + "x-recroom-client-service": "rooms/api", + "x-recroom-builds": [ + "2020.12.18" + ], + "x-recroom-inferred-http-method": "post", + "x-recroom-evidence": [ + { + "type": "EJDCNGBEICB", + "method": "RecRoom.Async.IPromise`1 BPDNAMEDJEG(System.Int64 HNHLJONGKHB, DPLPMKMFMPB PONCIIJOHIE)", + "file": "EJDCNGBEICB.txt", + "line": 3627 + } + ], + "requestBody": { + "required": false, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/GenericObject" + } + }, + "application/x-www-form-urlencoded": { + "schema": { + "$ref": "#/components/schemas/GenericObject" + } + }, + "multipart/form-data": { + "schema": { + "$ref": "#/components/schemas/GenericObject" + } + } + }, + "description": "Request body inferred. Exact DTO may be named in x-recroom-evidence method signature or companion DTO inventory." + }, + "x-client-response-contracts": [ + { + "kind": "object", + "raw": "PPENFJMFPNE", + "rootType": "PPENFJMFPNE", + "typeDescription": { + "name": "PPENFJMFPNE", + "found": true, + "file": "PPENFJMFPNE.cs", + "declaration": "public class PPENFJMFPNE : KLCOGEIGEBJ", + "kind": "class", + "baseType": "KLCOGEIGEBJ", + "properties": [ + { + "name": "ADCDBMGNIPB", + "type": "List", + "accessibility": "public", + "kind": "property" + }, + { + "name": "BIODFICLKJH", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "DFKGKGIMEMP", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "EJGIMHADHBC", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "FEBCCOFBKKJ", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "IAJDFKODGFL", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "KFFBPMHGFKB", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "LHGHLGDINFL", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "LOJPLANGFMG", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "MMBJPGGOFHL", + "type": "LMLJHMJEIGM", + "accessibility": "public", + "kind": "property" + }, + { + "name": "MMFBPKOOLNG", + "type": "bool", + "accessibility": "public", + "kind": "property" + }, + { + "name": "OHNIGJECGKL", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "PKKADKGDHNI", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + } + ], + "fields": [ + { + "name": "accountId", + "type": "int", + "kind": "field" + }, + { + "name": "tag", + "type": "string", + "kind": "field" + }, + { + "name": "IGAPDHGFLDC", + "type": "PPENFJMFPNE", + "kind": "field" + } + ], + "enumValues": [], + "wireKeys": [], + "isilFile": "PPENFJMFPNE.txt", + "isilType": "PPENFJMFPNE", + "inherits": "KLCOGEIGEBJ", + "inheritedMembers": [ + { + "name": "DADOKMAOFJL", + "type": "long", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "KLCOGEIGEBJ" + }, + { + "name": "GJPGKJJCPBK", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "KLCOGEIGEBJ" + }, + { + "name": "IBLOJJBEKFF", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "KLCOGEIGEBJ" + }, + { + "name": "JCLCOCEOAEP", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "KLCOGEIGEBJ" + }, + { + "name": "JGJCBGEBLLO", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "KLCOGEIGEBJ" + }, + { + "name": "PLFEEIJCHOH", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "KLCOGEIGEBJ" + }, + { + "name": "BJFNPKFOALK", + "type": "long", + "kind": "field", + "inheritedFrom": "KLCOGEIGEBJ" + }, + { + "name": "ACBFDMLHFPB", + "type": "DateTime", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "AHGCOGFEEEE", + "type": "string", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "AKFBNELAMNA", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "BADIGBCKECA", + "type": "int", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "BNBLOBAEDEE", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "CDINMMPNAID", + "type": "NMJEKMMBDDE", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "CDNFGMHLDMJ", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "FIKEBGGCDFN", + "type": "string", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "GIBHIMGJNNO", + "type": "GPDIAKNEBKH", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "HPLBOMGACED", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "IGOPGMHHLKI", + "type": "string", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "JFEAPMIPNEP", + "type": "DPLPMKMFMPB", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "KHIJAFCHLIA", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "KLNJBBPNMBJ", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "KODBEJPEFOJ", + "type": "string", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "LPJLEMJFBPE", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "MGBDHBHCDMH", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "MIKDLDEALPN", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "OFONEIOEIED", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "OILEJFNPDDB", + "type": "HJPGEGENLPH", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "PEEFHKMOMKK", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + } + ], + "inheritedWireKeys": [ + "RoomId", + "IsDorm", + "CloningAllowed", + "DisableMicAutoMute", + "DisableRoomComments", + "EncryptVoiceChat" + ] + } + } + ] + } + }, + "/rooms/{p0}/automute": { + "post": { + "tags": [ + "rooms" + ], + "summary": "POST rooms/{0}/automute", + "description": "Inferred from Rec Room 2020 client literal `rooms/{0}/automute`. Expected result: Room detail object or mutation success.", + "operationId": "client_rooms_rooms_p0_automute_post", + "parameters": [ + { + "name": "p0", + "in": "path", + "required": true, + "schema": { + "type": "string" + }, + "description": "Format placeholder from client literal rooms/{0}/automute" + } + ], + "responses": { + "200": { + "description": "Room detail object or mutation success.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Room" + } + } + } + }, + "204": { + "description": "Success with no body, used by some mutation endpoints." + }, + "400": { + "description": "Client-visible validation or bad request error.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "401": { + "description": "Authentication required or token invalid.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "500": { + "description": "Server error shape tolerated by client wrappers.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + } + }, + "x-recroom-client-route-literal": "rooms/{0}/automute", + "x-recroom-client-family": "rooms", + "x-recroom-client-service": "rooms/api", + "x-recroom-builds": [ + "2020.12.18" + ], + "x-recroom-inferred-http-method": "post", + "x-recroom-evidence": [ + { + "type": "EJDCNGBEICB", + "method": "RecRoom.Async.IPromise`1 CPFCPEONBGC(System.Int64 HNHLJONGKHB, System.Boolean AMJMLANOGKE)", + "file": "EJDCNGBEICB.txt", + "line": 4014 + } + ], + "requestBody": { + "required": false, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/GenericObject" + } + }, + "application/x-www-form-urlencoded": { + "schema": { + "$ref": "#/components/schemas/GenericObject" + } + }, + "multipart/form-data": { + "schema": { + "$ref": "#/components/schemas/GenericObject" + } + } + }, + "description": "Request body inferred. Exact DTO may be named in x-recroom-evidence method signature or companion DTO inventory." + }, + "x-client-response-contracts": [ + { + "kind": "object", + "raw": "PPENFJMFPNE", + "rootType": "PPENFJMFPNE", + "typeDescription": { + "name": "PPENFJMFPNE", + "found": true, + "file": "PPENFJMFPNE.cs", + "declaration": "public class PPENFJMFPNE : KLCOGEIGEBJ", + "kind": "class", + "baseType": "KLCOGEIGEBJ", + "properties": [ + { + "name": "ADCDBMGNIPB", + "type": "List", + "accessibility": "public", + "kind": "property" + }, + { + "name": "BIODFICLKJH", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "DFKGKGIMEMP", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "EJGIMHADHBC", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "FEBCCOFBKKJ", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "IAJDFKODGFL", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "KFFBPMHGFKB", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "LHGHLGDINFL", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "LOJPLANGFMG", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "MMBJPGGOFHL", + "type": "LMLJHMJEIGM", + "accessibility": "public", + "kind": "property" + }, + { + "name": "MMFBPKOOLNG", + "type": "bool", + "accessibility": "public", + "kind": "property" + }, + { + "name": "OHNIGJECGKL", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "PKKADKGDHNI", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + } + ], + "fields": [ + { + "name": "accountId", + "type": "int", + "kind": "field" + }, + { + "name": "tag", + "type": "string", + "kind": "field" + }, + { + "name": "IGAPDHGFLDC", + "type": "PPENFJMFPNE", + "kind": "field" + } + ], + "enumValues": [], + "wireKeys": [], + "isilFile": "PPENFJMFPNE.txt", + "isilType": "PPENFJMFPNE", + "inherits": "KLCOGEIGEBJ", + "inheritedMembers": [ + { + "name": "DADOKMAOFJL", + "type": "long", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "KLCOGEIGEBJ" + }, + { + "name": "GJPGKJJCPBK", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "KLCOGEIGEBJ" + }, + { + "name": "IBLOJJBEKFF", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "KLCOGEIGEBJ" + }, + { + "name": "JCLCOCEOAEP", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "KLCOGEIGEBJ" + }, + { + "name": "JGJCBGEBLLO", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "KLCOGEIGEBJ" + }, + { + "name": "PLFEEIJCHOH", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "KLCOGEIGEBJ" + }, + { + "name": "BJFNPKFOALK", + "type": "long", + "kind": "field", + "inheritedFrom": "KLCOGEIGEBJ" + }, + { + "name": "ACBFDMLHFPB", + "type": "DateTime", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "AHGCOGFEEEE", + "type": "string", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "AKFBNELAMNA", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "BADIGBCKECA", + "type": "int", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "BNBLOBAEDEE", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "CDINMMPNAID", + "type": "NMJEKMMBDDE", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "CDNFGMHLDMJ", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "FIKEBGGCDFN", + "type": "string", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "GIBHIMGJNNO", + "type": "GPDIAKNEBKH", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "HPLBOMGACED", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "IGOPGMHHLKI", + "type": "string", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "JFEAPMIPNEP", + "type": "DPLPMKMFMPB", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "KHIJAFCHLIA", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "KLNJBBPNMBJ", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "KODBEJPEFOJ", + "type": "string", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "LPJLEMJFBPE", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "MGBDHBHCDMH", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "MIKDLDEALPN", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "OFONEIOEIED", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "OILEJFNPDDB", + "type": "HJPGEGENLPH", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "PEEFHKMOMKK", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + } + ], + "inheritedWireKeys": [ + "RoomId", + "IsDorm", + "CloningAllowed", + "DisableMicAutoMute", + "DisableRoomComments", + "EncryptVoiceChat" + ] + } + } + ] + } + }, + "/rooms/{p0}/bans/{p1}": { + "post": { + "tags": [ + "rooms" + ], + "summary": "POST rooms/{0}/bans/{1}", + "description": "Inferred from Rec Room 2020 client literal `rooms/{0}/bans/{1}`. Expected result: Room detail object or mutation success.", + "operationId": "client_rooms_rooms_p0_bans_p1_post", + "parameters": [ + { + "name": "p0", + "in": "path", + "required": true, + "schema": { + "type": "string" + }, + "description": "Format placeholder from client literal rooms/{0}/bans/{1}" + }, + { + "name": "p1", + "in": "path", + "required": true, + "schema": { + "type": "string" + }, + "description": "Format placeholder from client literal rooms/{0}/bans/{1}" + } + ], + "responses": { + "200": { + "description": "Room detail object or mutation success.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Room" + } + } + } + }, + "204": { + "description": "Success with no body, used by some mutation endpoints." + }, + "400": { + "description": "Client-visible validation or bad request error.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "401": { + "description": "Authentication required or token invalid.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "500": { + "description": "Server error shape tolerated by client wrappers.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + } + }, + "x-recroom-client-route-literal": "rooms/{0}/bans/{1}", + "x-recroom-client-family": "rooms", + "x-recroom-client-service": "rooms/api", + "x-recroom-builds": [ + "2020.12.18" + ], + "x-recroom-inferred-http-method": "post", + "x-recroom-evidence": [ + { + "type": "EJDCNGBEICB", + "method": "RecRoom.Async.IPromise JMCJDGIMKPK(System.Int64 HNHLJONGKHB, System.Int32 GKLPIFBPGOD, KKIHLHLNGCK BFNPCCFPJHP)", + "file": "EJDCNGBEICB.txt", + "line": 8186 + } + ], + "requestBody": { + "required": false, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/GenericObject" + } + }, + "application/x-www-form-urlencoded": { + "schema": { + "$ref": "#/components/schemas/GenericObject" + } + }, + "multipart/form-data": { + "schema": { + "$ref": "#/components/schemas/GenericObject" + } + } + }, + "description": "Request body inferred. Exact DTO may be named in x-recroom-evidence method signature or companion DTO inventory." + }, + "x-client-response-contracts": [ + { + "kind": "success-or-empty", + "raw": "RecRoom.Async.IPromise", + "rootType": "void/success", + "typeDescription": { + "name": "void/success", + "found": false, + "kind": "object", + "wireKeys": [] + } + } + ] + } + }, + "/rooms/{p0}/bans/import": { + "post": { + "tags": [ + "rooms" + ], + "summary": "POST rooms/{0}/bans/import", + "description": "Inferred from Rec Room 2020 client literal `rooms/{0}/bans/import`. Expected result: Room detail object or mutation success.", + "operationId": "client_rooms_rooms_p0_bans_import_post", + "parameters": [ + { + "name": "p0", + "in": "path", + "required": true, + "schema": { + "type": "string" + }, + "description": "Format placeholder from client literal rooms/{0}/bans/import" + } + ], + "responses": { + "200": { + "description": "Room detail object or mutation success.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Room" + } + } + } + }, + "204": { + "description": "Success with no body, used by some mutation endpoints." + }, + "400": { + "description": "Client-visible validation or bad request error.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "401": { + "description": "Authentication required or token invalid.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "500": { + "description": "Server error shape tolerated by client wrappers.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + } + }, + "x-recroom-client-route-literal": "rooms/{0}/bans/import", + "x-recroom-client-family": "rooms", + "x-recroom-client-service": "rooms/api", + "x-recroom-builds": [ + "2020.12.18" + ], + "x-recroom-inferred-http-method": "post", + "x-recroom-evidence": [ + { + "type": "EJDCNGBEICB", + "method": "RecRoom.Async.IPromise PFEEJDKPPJI(System.Int64 HNHLJONGKHB, System.Int64 MOKIOEJBMFC)", + "file": "EJDCNGBEICB.txt", + "line": 8054 + } + ], + "requestBody": { + "required": false, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/GenericObject" + } + }, + "application/x-www-form-urlencoded": { + "schema": { + "$ref": "#/components/schemas/GenericObject" + } + }, + "multipart/form-data": { + "schema": { + "$ref": "#/components/schemas/GenericObject" + } + } + }, + "description": "Request body inferred. Exact DTO may be named in x-recroom-evidence method signature or companion DTO inventory." + }, + "x-client-response-contracts": [ + { + "kind": "success-or-empty", + "raw": "RecRoom.Async.IPromise", + "rootType": "void/success", + "typeDescription": { + "name": "void/success", + "found": false, + "kind": "object", + "wireKeys": [] + } + } + ] + } + }, + "/rooms/{p0}/bans": { + "post": { + "tags": [ + "rooms" + ], + "summary": "POST rooms/{0}/bans", + "description": "Inferred from Rec Room 2020 client literal `rooms/{0}/bans`. Expected result: Room detail object or mutation success.", + "operationId": "client_rooms_rooms_p0_bans_post", + "parameters": [ + { + "name": "p0", + "in": "path", + "required": true, + "schema": { + "type": "string" + }, + "description": "Format placeholder from client literal rooms/{0}/bans" + } + ], + "responses": { + "200": { + "description": "Room detail object or mutation success.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Room" + } + } + } + }, + "204": { + "description": "Success with no body, used by some mutation endpoints." + }, + "400": { + "description": "Client-visible validation or bad request error.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "401": { + "description": "Authentication required or token invalid.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "500": { + "description": "Server error shape tolerated by client wrappers.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + } + }, + "x-recroom-client-route-literal": "rooms/{0}/bans", + "x-recroom-client-family": "rooms", + "x-recroom-client-service": "rooms/api", + "x-recroom-builds": [ + "2020.12.18" + ], + "x-recroom-inferred-http-method": "post", + "x-recroom-evidence": [ + { + "type": "EJDCNGBEICB", + "method": "RecRoom.Async.IPromise CCGIGHDHNLM(System.Int64 HNHLJONGKHB, System.Collections.Generic.IReadOnlyList`1 ILNGMAANNDG, KKIHLHLNGCK BFNPCCFPJHP)", + "file": "EJDCNGBEICB.txt", + "line": 7939 + }, + { + "type": "EJDCNGBEICB", + "method": "RecRoom.Async.IPromise`1> CGIPIOMBAFM(System.Int64 HNHLJONGKHB)", + "file": "EJDCNGBEICB.txt", + "line": 7832 + } + ], + "requestBody": { + "required": false, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/GenericObject" + } + }, + "application/x-www-form-urlencoded": { + "schema": { + "$ref": "#/components/schemas/GenericObject" + } + }, + "multipart/form-data": { + "schema": { + "$ref": "#/components/schemas/GenericObject" + } + } + }, + "description": "Request body inferred. Exact DTO may be named in x-recroom-evidence method signature or companion DTO inventory." + }, + "x-client-response-contracts": [ + { + "kind": "success-or-empty", + "raw": "RecRoom.Async.IPromise", + "rootType": "void/success", + "typeDescription": { + "name": "void/success", + "found": false, + "kind": "object", + "wireKeys": [] + } + }, + { + "kind": "array", + "raw": "System.Collections.Generic.List`1", + "rootType": "EAENIFLCDGI", + "item": { + "kind": "object", + "raw": "EAENIFLCDGI", + "rootType": "EAENIFLCDGI" + }, + "typeDescription": { + "name": "EAENIFLCDGI", + "found": true, + "file": "EAENIFLCDGI.cs", + "declaration": "public class EAENIFLCDGI : IFAIJAGLDFK", + "kind": "class", + "baseType": "IFAIJAGLDFK", + "properties": [ + { + "name": "CCNHPFAKKBD", + "type": "DateTime", + "accessibility": "public", + "kind": "property" + }, + { + "name": "GAINIOENNCG", + "type": "int", + "accessibility": "public", + "kind": "property" + } + ], + "fields": [], + "enumValues": [], + "wireKeys": [ + "AccountId", + "BanStartTime" + ], + "isilFile": "EAENIFLCDGI.txt", + "isilType": "EAENIFLCDGI" + } + } + ] + } + }, + "/rooms/{p0}/clone": { + "post": { + "tags": [ + "rooms" + ], + "summary": "POST rooms/{0}/clone", + "description": "Inferred from Rec Room 2020 client literal `rooms/{0}/clone`. Expected result: Room detail object or mutation success.", + "operationId": "client_rooms_rooms_p0_clone_post", + "parameters": [ + { + "name": "p0", + "in": "path", + "required": true, + "schema": { + "type": "string" + }, + "description": "Format placeholder from client literal rooms/{0}/clone" + } + ], + "responses": { + "200": { + "description": "Room detail object or mutation success.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Room" + } + } + } + }, + "204": { + "description": "Success with no body, used by some mutation endpoints." + }, + "400": { + "description": "Client-visible validation or bad request error.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "401": { + "description": "Authentication required or token invalid.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "500": { + "description": "Server error shape tolerated by client wrappers.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + } + }, + "x-recroom-client-route-literal": "rooms/{0}/clone", + "x-recroom-client-family": "rooms", + "x-recroom-client-service": "rooms/api", + "x-recroom-builds": [ + "2020.12.18" + ], + "x-recroom-inferred-http-method": "post", + "x-recroom-evidence": [ + { + "type": "EJDCNGBEICB", + "method": "RecRoom.Async.IPromise`1 OFFECAMHJGE(System.Int64 HNHLJONGKHB, System.String MMBOKOLAJFH)", + "file": "EJDCNGBEICB.txt", + "line": 2843 + } + ], + "requestBody": { + "required": false, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/GenericObject" + } + }, + "application/x-www-form-urlencoded": { + "schema": { + "$ref": "#/components/schemas/GenericObject" + } + }, + "multipart/form-data": { + "schema": { + "$ref": "#/components/schemas/GenericObject" + } + } + }, + "description": "Request body inferred. Exact DTO may be named in x-recroom-evidence method signature or companion DTO inventory." + }, + "x-client-response-contracts": [ + { + "kind": "object", + "raw": "PPENFJMFPNE", + "rootType": "PPENFJMFPNE", + "typeDescription": { + "name": "PPENFJMFPNE", + "found": true, + "file": "PPENFJMFPNE.cs", + "declaration": "public class PPENFJMFPNE : KLCOGEIGEBJ", + "kind": "class", + "baseType": "KLCOGEIGEBJ", + "properties": [ + { + "name": "ADCDBMGNIPB", + "type": "List", + "accessibility": "public", + "kind": "property" + }, + { + "name": "BIODFICLKJH", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "DFKGKGIMEMP", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "EJGIMHADHBC", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "FEBCCOFBKKJ", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "IAJDFKODGFL", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "KFFBPMHGFKB", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "LHGHLGDINFL", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "LOJPLANGFMG", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "MMBJPGGOFHL", + "type": "LMLJHMJEIGM", + "accessibility": "public", + "kind": "property" + }, + { + "name": "MMFBPKOOLNG", + "type": "bool", + "accessibility": "public", + "kind": "property" + }, + { + "name": "OHNIGJECGKL", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "PKKADKGDHNI", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + } + ], + "fields": [ + { + "name": "accountId", + "type": "int", + "kind": "field" + }, + { + "name": "tag", + "type": "string", + "kind": "field" + }, + { + "name": "IGAPDHGFLDC", + "type": "PPENFJMFPNE", + "kind": "field" + } + ], + "enumValues": [], + "wireKeys": [], + "isilFile": "PPENFJMFPNE.txt", + "isilType": "PPENFJMFPNE", + "inherits": "KLCOGEIGEBJ", + "inheritedMembers": [ + { + "name": "DADOKMAOFJL", + "type": "long", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "KLCOGEIGEBJ" + }, + { + "name": "GJPGKJJCPBK", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "KLCOGEIGEBJ" + }, + { + "name": "IBLOJJBEKFF", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "KLCOGEIGEBJ" + }, + { + "name": "JCLCOCEOAEP", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "KLCOGEIGEBJ" + }, + { + "name": "JGJCBGEBLLO", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "KLCOGEIGEBJ" + }, + { + "name": "PLFEEIJCHOH", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "KLCOGEIGEBJ" + }, + { + "name": "BJFNPKFOALK", + "type": "long", + "kind": "field", + "inheritedFrom": "KLCOGEIGEBJ" + }, + { + "name": "ACBFDMLHFPB", + "type": "DateTime", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "AHGCOGFEEEE", + "type": "string", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "AKFBNELAMNA", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "BADIGBCKECA", + "type": "int", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "BNBLOBAEDEE", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "CDINMMPNAID", + "type": "NMJEKMMBDDE", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "CDNFGMHLDMJ", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "FIKEBGGCDFN", + "type": "string", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "GIBHIMGJNNO", + "type": "GPDIAKNEBKH", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "HPLBOMGACED", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "IGOPGMHHLKI", + "type": "string", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "JFEAPMIPNEP", + "type": "DPLPMKMFMPB", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "KHIJAFCHLIA", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "KLNJBBPNMBJ", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "KODBEJPEFOJ", + "type": "string", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "LPJLEMJFBPE", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "MGBDHBHCDMH", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "MIKDLDEALPN", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "OFONEIOEIED", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "OILEJFNPDDB", + "type": "HJPGEGENLPH", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "PEEFHKMOMKK", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + } + ], + "inheritedWireKeys": [ + "RoomId", + "IsDorm", + "CloningAllowed", + "DisableMicAutoMute", + "DisableRoomComments", + "EncryptVoiceChat" + ] + } + } + ] + } + }, + "/rooms/{p0}/cloning": { + "post": { + "tags": [ + "rooms" + ], + "summary": "POST rooms/{0}/cloning", + "description": "Inferred from Rec Room 2020 client literal `rooms/{0}/cloning`. Expected result: Room detail object or mutation success.", + "operationId": "client_rooms_rooms_p0_cloning_post", + "parameters": [ + { + "name": "p0", + "in": "path", + "required": true, + "schema": { + "type": "string" + }, + "description": "Format placeholder from client literal rooms/{0}/cloning" + } + ], + "responses": { + "200": { + "description": "Room detail object or mutation success.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Room" + } + } + } + }, + "204": { + "description": "Success with no body, used by some mutation endpoints." + }, + "400": { + "description": "Client-visible validation or bad request error.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "401": { + "description": "Authentication required or token invalid.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "500": { + "description": "Server error shape tolerated by client wrappers.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + } + }, + "x-recroom-client-route-literal": "rooms/{0}/cloning", + "x-recroom-client-family": "rooms", + "x-recroom-client-service": "rooms/api", + "x-recroom-builds": [ + "2020.12.18" + ], + "x-recroom-inferred-http-method": "post", + "x-recroom-evidence": [ + { + "type": "EJDCNGBEICB", + "method": "RecRoom.Async.IPromise`1 LJMHBJKDJCE(System.Int64 HNHLJONGKHB, System.Boolean GPAGMKFIKNG)", + "file": "EJDCNGBEICB.txt", + "line": 3886 + } + ], + "requestBody": { + "required": false, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/GenericObject" + } + }, + "application/x-www-form-urlencoded": { + "schema": { + "$ref": "#/components/schemas/GenericObject" + } + }, + "multipart/form-data": { + "schema": { + "$ref": "#/components/schemas/GenericObject" + } + } + }, + "description": "Request body inferred. Exact DTO may be named in x-recroom-evidence method signature or companion DTO inventory." + }, + "x-client-response-contracts": [ + { + "kind": "object", + "raw": "PPENFJMFPNE", + "rootType": "PPENFJMFPNE", + "typeDescription": { + "name": "PPENFJMFPNE", + "found": true, + "file": "PPENFJMFPNE.cs", + "declaration": "public class PPENFJMFPNE : KLCOGEIGEBJ", + "kind": "class", + "baseType": "KLCOGEIGEBJ", + "properties": [ + { + "name": "ADCDBMGNIPB", + "type": "List", + "accessibility": "public", + "kind": "property" + }, + { + "name": "BIODFICLKJH", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "DFKGKGIMEMP", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "EJGIMHADHBC", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "FEBCCOFBKKJ", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "IAJDFKODGFL", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "KFFBPMHGFKB", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "LHGHLGDINFL", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "LOJPLANGFMG", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "MMBJPGGOFHL", + "type": "LMLJHMJEIGM", + "accessibility": "public", + "kind": "property" + }, + { + "name": "MMFBPKOOLNG", + "type": "bool", + "accessibility": "public", + "kind": "property" + }, + { + "name": "OHNIGJECGKL", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "PKKADKGDHNI", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + } + ], + "fields": [ + { + "name": "accountId", + "type": "int", + "kind": "field" + }, + { + "name": "tag", + "type": "string", + "kind": "field" + }, + { + "name": "IGAPDHGFLDC", + "type": "PPENFJMFPNE", + "kind": "field" + } + ], + "enumValues": [], + "wireKeys": [], + "isilFile": "PPENFJMFPNE.txt", + "isilType": "PPENFJMFPNE", + "inherits": "KLCOGEIGEBJ", + "inheritedMembers": [ + { + "name": "DADOKMAOFJL", + "type": "long", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "KLCOGEIGEBJ" + }, + { + "name": "GJPGKJJCPBK", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "KLCOGEIGEBJ" + }, + { + "name": "IBLOJJBEKFF", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "KLCOGEIGEBJ" + }, + { + "name": "JCLCOCEOAEP", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "KLCOGEIGEBJ" + }, + { + "name": "JGJCBGEBLLO", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "KLCOGEIGEBJ" + }, + { + "name": "PLFEEIJCHOH", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "KLCOGEIGEBJ" + }, + { + "name": "BJFNPKFOALK", + "type": "long", + "kind": "field", + "inheritedFrom": "KLCOGEIGEBJ" + }, + { + "name": "ACBFDMLHFPB", + "type": "DateTime", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "AHGCOGFEEEE", + "type": "string", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "AKFBNELAMNA", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "BADIGBCKECA", + "type": "int", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "BNBLOBAEDEE", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "CDINMMPNAID", + "type": "NMJEKMMBDDE", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "CDNFGMHLDMJ", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "FIKEBGGCDFN", + "type": "string", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "GIBHIMGJNNO", + "type": "GPDIAKNEBKH", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "HPLBOMGACED", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "IGOPGMHHLKI", + "type": "string", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "JFEAPMIPNEP", + "type": "DPLPMKMFMPB", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "KHIJAFCHLIA", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "KLNJBBPNMBJ", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "KODBEJPEFOJ", + "type": "string", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "LPJLEMJFBPE", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "MGBDHBHCDMH", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "MIKDLDEALPN", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "OFONEIOEIED", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "OILEJFNPDDB", + "type": "HJPGEGENLPH", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "PEEFHKMOMKK", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + } + ], + "inheritedWireKeys": [ + "RoomId", + "IsDorm", + "CloningAllowed", + "DisableMicAutoMute", + "DisableRoomComments", + "EncryptVoiceChat" + ] + } + } + ] + } + }, + "/rooms/{p0}/comments": { + "post": { + "tags": [ + "rooms" + ], + "summary": "POST rooms/{0}/comments", + "description": "Inferred from Rec Room 2020 client literal `rooms/{0}/comments`. Expected result: Room detail object or mutation success.", + "operationId": "client_rooms_rooms_p0_comments_post", + "parameters": [ + { + "name": "p0", + "in": "path", + "required": true, + "schema": { + "type": "string" + }, + "description": "Format placeholder from client literal rooms/{0}/comments" + } + ], + "responses": { + "200": { + "description": "Room detail object or mutation success.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Room" + } + } + } + }, + "204": { + "description": "Success with no body, used by some mutation endpoints." + }, + "400": { + "description": "Client-visible validation or bad request error.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "401": { + "description": "Authentication required or token invalid.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "500": { + "description": "Server error shape tolerated by client wrappers.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + } + }, + "x-recroom-client-route-literal": "rooms/{0}/comments", + "x-recroom-client-family": "rooms", + "x-recroom-client-service": "rooms/api", + "x-recroom-builds": [ + "2020.12.18" + ], + "x-recroom-inferred-http-method": "post", + "x-recroom-evidence": [ + { + "type": "EJDCNGBEICB", + "method": "RecRoom.Async.IPromise`1 FKADIOPHOGC(System.Int64 HNHLJONGKHB, System.Boolean IMHMMPKLGEP)", + "file": "EJDCNGBEICB.txt", + "line": 4142 + } + ], + "requestBody": { + "required": false, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/GenericObject" + } + }, + "application/x-www-form-urlencoded": { + "schema": { + "$ref": "#/components/schemas/GenericObject" + } + }, + "multipart/form-data": { + "schema": { + "$ref": "#/components/schemas/GenericObject" + } + } + }, + "description": "Request body inferred. Exact DTO may be named in x-recroom-evidence method signature or companion DTO inventory." + }, + "x-client-response-contracts": [ + { + "kind": "object", + "raw": "PPENFJMFPNE", + "rootType": "PPENFJMFPNE", + "typeDescription": { + "name": "PPENFJMFPNE", + "found": true, + "file": "PPENFJMFPNE.cs", + "declaration": "public class PPENFJMFPNE : KLCOGEIGEBJ", + "kind": "class", + "baseType": "KLCOGEIGEBJ", + "properties": [ + { + "name": "ADCDBMGNIPB", + "type": "List", + "accessibility": "public", + "kind": "property" + }, + { + "name": "BIODFICLKJH", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "DFKGKGIMEMP", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "EJGIMHADHBC", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "FEBCCOFBKKJ", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "IAJDFKODGFL", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "KFFBPMHGFKB", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "LHGHLGDINFL", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "LOJPLANGFMG", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "MMBJPGGOFHL", + "type": "LMLJHMJEIGM", + "accessibility": "public", + "kind": "property" + }, + { + "name": "MMFBPKOOLNG", + "type": "bool", + "accessibility": "public", + "kind": "property" + }, + { + "name": "OHNIGJECGKL", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "PKKADKGDHNI", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + } + ], + "fields": [ + { + "name": "accountId", + "type": "int", + "kind": "field" + }, + { + "name": "tag", + "type": "string", + "kind": "field" + }, + { + "name": "IGAPDHGFLDC", + "type": "PPENFJMFPNE", + "kind": "field" + } + ], + "enumValues": [], + "wireKeys": [], + "isilFile": "PPENFJMFPNE.txt", + "isilType": "PPENFJMFPNE", + "inherits": "KLCOGEIGEBJ", + "inheritedMembers": [ + { + "name": "DADOKMAOFJL", + "type": "long", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "KLCOGEIGEBJ" + }, + { + "name": "GJPGKJJCPBK", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "KLCOGEIGEBJ" + }, + { + "name": "IBLOJJBEKFF", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "KLCOGEIGEBJ" + }, + { + "name": "JCLCOCEOAEP", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "KLCOGEIGEBJ" + }, + { + "name": "JGJCBGEBLLO", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "KLCOGEIGEBJ" + }, + { + "name": "PLFEEIJCHOH", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "KLCOGEIGEBJ" + }, + { + "name": "BJFNPKFOALK", + "type": "long", + "kind": "field", + "inheritedFrom": "KLCOGEIGEBJ" + }, + { + "name": "ACBFDMLHFPB", + "type": "DateTime", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "AHGCOGFEEEE", + "type": "string", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "AKFBNELAMNA", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "BADIGBCKECA", + "type": "int", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "BNBLOBAEDEE", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "CDINMMPNAID", + "type": "NMJEKMMBDDE", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "CDNFGMHLDMJ", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "FIKEBGGCDFN", + "type": "string", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "GIBHIMGJNNO", + "type": "GPDIAKNEBKH", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "HPLBOMGACED", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "IGOPGMHHLKI", + "type": "string", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "JFEAPMIPNEP", + "type": "DPLPMKMFMPB", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "KHIJAFCHLIA", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "KLNJBBPNMBJ", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "KODBEJPEFOJ", + "type": "string", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "LPJLEMJFBPE", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "MGBDHBHCDMH", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "MIKDLDEALPN", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "OFONEIOEIED", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "OILEJFNPDDB", + "type": "HJPGEGENLPH", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "PEEFHKMOMKK", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + } + ], + "inheritedWireKeys": [ + "RoomId", + "IsDorm", + "CloningAllowed", + "DisableMicAutoMute", + "DisableRoomComments", + "EncryptVoiceChat" + ] + } + } + ] + } + }, + "/rooms/{p0}/description": { + "post": { + "tags": [ + "rooms" + ], + "summary": "POST rooms/{0}/description", + "description": "Inferred from Rec Room 2020 client literal `rooms/{0}/description`. Expected result: Room detail object or mutation success.", + "operationId": "client_rooms_rooms_p0_description_post", + "parameters": [ + { + "name": "p0", + "in": "path", + "required": true, + "schema": { + "type": "string" + }, + "description": "Format placeholder from client literal rooms/{0}/description" + } + ], + "responses": { + "200": { + "description": "Room detail object or mutation success.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Room" + } + } + } + }, + "204": { + "description": "Success with no body, used by some mutation endpoints." + }, + "400": { + "description": "Client-visible validation or bad request error.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "401": { + "description": "Authentication required or token invalid.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "500": { + "description": "Server error shape tolerated by client wrappers.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + } + }, + "x-recroom-client-route-literal": "rooms/{0}/description", + "x-recroom-client-family": "rooms", + "x-recroom-client-service": "rooms/api", + "x-recroom-builds": [ + "2020.12.18" + ], + "x-recroom-inferred-http-method": "post", + "x-recroom-evidence": [ + { + "type": "EJDCNGBEICB", + "method": "RecRoom.Async.IPromise`1 CFNFNEJGCEF(System.Int64 HNHLJONGKHB, System.String LJIGOCDPEJF)", + "file": "EJDCNGBEICB.txt", + "line": 3143 + } + ], + "requestBody": { + "required": false, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/GenericObject" + } + }, + "application/x-www-form-urlencoded": { + "schema": { + "$ref": "#/components/schemas/GenericObject" + } + }, + "multipart/form-data": { + "schema": { + "$ref": "#/components/schemas/GenericObject" + } + } + }, + "description": "Request body inferred. Exact DTO may be named in x-recroom-evidence method signature or companion DTO inventory." + }, + "x-client-response-contracts": [ + { + "kind": "object", + "raw": "PPENFJMFPNE", + "rootType": "PPENFJMFPNE", + "typeDescription": { + "name": "PPENFJMFPNE", + "found": true, + "file": "PPENFJMFPNE.cs", + "declaration": "public class PPENFJMFPNE : KLCOGEIGEBJ", + "kind": "class", + "baseType": "KLCOGEIGEBJ", + "properties": [ + { + "name": "ADCDBMGNIPB", + "type": "List", + "accessibility": "public", + "kind": "property" + }, + { + "name": "BIODFICLKJH", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "DFKGKGIMEMP", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "EJGIMHADHBC", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "FEBCCOFBKKJ", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "IAJDFKODGFL", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "KFFBPMHGFKB", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "LHGHLGDINFL", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "LOJPLANGFMG", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "MMBJPGGOFHL", + "type": "LMLJHMJEIGM", + "accessibility": "public", + "kind": "property" + }, + { + "name": "MMFBPKOOLNG", + "type": "bool", + "accessibility": "public", + "kind": "property" + }, + { + "name": "OHNIGJECGKL", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "PKKADKGDHNI", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + } + ], + "fields": [ + { + "name": "accountId", + "type": "int", + "kind": "field" + }, + { + "name": "tag", + "type": "string", + "kind": "field" + }, + { + "name": "IGAPDHGFLDC", + "type": "PPENFJMFPNE", + "kind": "field" + } + ], + "enumValues": [], + "wireKeys": [], + "isilFile": "PPENFJMFPNE.txt", + "isilType": "PPENFJMFPNE", + "inherits": "KLCOGEIGEBJ", + "inheritedMembers": [ + { + "name": "DADOKMAOFJL", + "type": "long", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "KLCOGEIGEBJ" + }, + { + "name": "GJPGKJJCPBK", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "KLCOGEIGEBJ" + }, + { + "name": "IBLOJJBEKFF", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "KLCOGEIGEBJ" + }, + { + "name": "JCLCOCEOAEP", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "KLCOGEIGEBJ" + }, + { + "name": "JGJCBGEBLLO", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "KLCOGEIGEBJ" + }, + { + "name": "PLFEEIJCHOH", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "KLCOGEIGEBJ" + }, + { + "name": "BJFNPKFOALK", + "type": "long", + "kind": "field", + "inheritedFrom": "KLCOGEIGEBJ" + }, + { + "name": "ACBFDMLHFPB", + "type": "DateTime", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "AHGCOGFEEEE", + "type": "string", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "AKFBNELAMNA", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "BADIGBCKECA", + "type": "int", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "BNBLOBAEDEE", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "CDINMMPNAID", + "type": "NMJEKMMBDDE", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "CDNFGMHLDMJ", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "FIKEBGGCDFN", + "type": "string", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "GIBHIMGJNNO", + "type": "GPDIAKNEBKH", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "HPLBOMGACED", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "IGOPGMHHLKI", + "type": "string", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "JFEAPMIPNEP", + "type": "DPLPMKMFMPB", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "KHIJAFCHLIA", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "KLNJBBPNMBJ", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "KODBEJPEFOJ", + "type": "string", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "LPJLEMJFBPE", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "MGBDHBHCDMH", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "MIKDLDEALPN", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "OFONEIOEIED", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "OILEJFNPDDB", + "type": "HJPGEGENLPH", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "PEEFHKMOMKK", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + } + ], + "inheritedWireKeys": [ + "RoomId", + "IsDorm", + "CloningAllowed", + "DisableMicAutoMute", + "DisableRoomComments", + "EncryptVoiceChat" + ] + } + } + ] + } + }, + "/rooms/{p0}/image": { + "post": { + "tags": [ + "rooms" + ], + "summary": "POST rooms/{0}/image", + "description": "Inferred from Rec Room 2020 client literal `rooms/{0}/image`. Expected result: Room detail object or mutation success.", + "operationId": "client_rooms_rooms_p0_image_post", + "parameters": [ + { + "name": "p0", + "in": "path", + "required": true, + "schema": { + "type": "string" + }, + "description": "Format placeholder from client literal rooms/{0}/image" + } + ], + "responses": { + "200": { + "description": "Room detail object or mutation success.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Room" + } + } + } + }, + "204": { + "description": "Success with no body, used by some mutation endpoints." + }, + "400": { + "description": "Client-visible validation or bad request error.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "401": { + "description": "Authentication required or token invalid.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "500": { + "description": "Server error shape tolerated by client wrappers.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + } + }, + "x-recroom-client-route-literal": "rooms/{0}/image", + "x-recroom-client-family": "rooms", + "x-recroom-client-service": "rooms/api", + "x-recroom-builds": [ + "2020.12.18" + ], + "x-recroom-inferred-http-method": "post", + "x-recroom-evidence": [ + { + "type": "EJDCNGBEICB", + "method": "RecRoom.Async.IPromise`1 OGFLGMMNMDD(System.Int64 HNHLJONGKHB, System.String HFLPBHHAFIO)", + "file": "EJDCNGBEICB.txt", + "line": 3259 + } + ], + "requestBody": { + "required": false, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/GenericObject" + } + }, + "application/x-www-form-urlencoded": { + "schema": { + "$ref": "#/components/schemas/GenericObject" + } + }, + "multipart/form-data": { + "schema": { + "$ref": "#/components/schemas/GenericObject" + } + } + }, + "description": "Request body inferred. Exact DTO may be named in x-recroom-evidence method signature or companion DTO inventory." + }, + "x-client-response-contracts": [ + { + "kind": "object", + "raw": "PPENFJMFPNE", + "rootType": "PPENFJMFPNE", + "typeDescription": { + "name": "PPENFJMFPNE", + "found": true, + "file": "PPENFJMFPNE.cs", + "declaration": "public class PPENFJMFPNE : KLCOGEIGEBJ", + "kind": "class", + "baseType": "KLCOGEIGEBJ", + "properties": [ + { + "name": "ADCDBMGNIPB", + "type": "List", + "accessibility": "public", + "kind": "property" + }, + { + "name": "BIODFICLKJH", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "DFKGKGIMEMP", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "EJGIMHADHBC", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "FEBCCOFBKKJ", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "IAJDFKODGFL", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "KFFBPMHGFKB", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "LHGHLGDINFL", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "LOJPLANGFMG", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "MMBJPGGOFHL", + "type": "LMLJHMJEIGM", + "accessibility": "public", + "kind": "property" + }, + { + "name": "MMFBPKOOLNG", + "type": "bool", + "accessibility": "public", + "kind": "property" + }, + { + "name": "OHNIGJECGKL", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "PKKADKGDHNI", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + } + ], + "fields": [ + { + "name": "accountId", + "type": "int", + "kind": "field" + }, + { + "name": "tag", + "type": "string", + "kind": "field" + }, + { + "name": "IGAPDHGFLDC", + "type": "PPENFJMFPNE", + "kind": "field" + } + ], + "enumValues": [], + "wireKeys": [], + "isilFile": "PPENFJMFPNE.txt", + "isilType": "PPENFJMFPNE", + "inherits": "KLCOGEIGEBJ", + "inheritedMembers": [ + { + "name": "DADOKMAOFJL", + "type": "long", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "KLCOGEIGEBJ" + }, + { + "name": "GJPGKJJCPBK", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "KLCOGEIGEBJ" + }, + { + "name": "IBLOJJBEKFF", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "KLCOGEIGEBJ" + }, + { + "name": "JCLCOCEOAEP", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "KLCOGEIGEBJ" + }, + { + "name": "JGJCBGEBLLO", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "KLCOGEIGEBJ" + }, + { + "name": "PLFEEIJCHOH", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "KLCOGEIGEBJ" + }, + { + "name": "BJFNPKFOALK", + "type": "long", + "kind": "field", + "inheritedFrom": "KLCOGEIGEBJ" + }, + { + "name": "ACBFDMLHFPB", + "type": "DateTime", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "AHGCOGFEEEE", + "type": "string", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "AKFBNELAMNA", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "BADIGBCKECA", + "type": "int", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "BNBLOBAEDEE", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "CDINMMPNAID", + "type": "NMJEKMMBDDE", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "CDNFGMHLDMJ", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "FIKEBGGCDFN", + "type": "string", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "GIBHIMGJNNO", + "type": "GPDIAKNEBKH", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "HPLBOMGACED", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "IGOPGMHHLKI", + "type": "string", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "JFEAPMIPNEP", + "type": "DPLPMKMFMPB", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "KHIJAFCHLIA", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "KLNJBBPNMBJ", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "KODBEJPEFOJ", + "type": "string", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "LPJLEMJFBPE", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "MGBDHBHCDMH", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "MIKDLDEALPN", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "OFONEIOEIED", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "OILEJFNPDDB", + "type": "HJPGEGENLPH", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "PEEFHKMOMKK", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + } + ], + "inheritedWireKeys": [ + "RoomId", + "IsDorm", + "CloningAllowed", + "DisableMicAutoMute", + "DisableRoomComments", + "EncryptVoiceChat" + ] + } + } + ] + } + }, + "/rooms/{p0}/interactionby/me/cheer": { + "get": { + "tags": [ + "rooms" + ], + "summary": "GET rooms/{0}/interactionby/me/cheer", + "description": "Inferred from Rec Room 2020 client literal `rooms/{0}/interactionby/me/cheer`. Expected result: Room detail object or mutation success.", + "operationId": "client_rooms_rooms_p0_interactionby_me_cheer_get", + "parameters": [ + { + "name": "p0", + "in": "path", + "required": true, + "schema": { + "type": "string" + }, + "description": "Format placeholder from client literal rooms/{0}/interactionby/me/cheer" + } + ], + "responses": { + "200": { + "description": "Room detail object or mutation success.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Room" + } + } + } + }, + "204": { + "description": "Success with no body, used by some mutation endpoints." + }, + "400": { + "description": "Client-visible validation or bad request error.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "401": { + "description": "Authentication required or token invalid.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "500": { + "description": "Server error shape tolerated by client wrappers.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + } + }, + "x-recroom-client-route-literal": "rooms/{0}/interactionby/me/cheer", + "x-recroom-client-family": "rooms", + "x-recroom-client-service": "rooms/api", + "x-recroom-builds": [ + "2020.12.18" + ], + "x-recroom-inferred-http-method": "get", + "x-recroom-evidence": [ + { + "type": "EJDCNGBEICB", + "method": "RecRoom.Async.IPromise JFBCCIHBKPP(System.Int64 HNHLJONGKHB)", + "file": "EJDCNGBEICB.txt", + "line": 8399 + }, + { + "type": "EJDCNGBEICB", + "method": "RecRoom.Async.IPromise PEKMAJPMCDE(System.Int64 HNHLJONGKHB)", + "file": "EJDCNGBEICB.txt", + "line": 8331 + } + ], + "x-client-response-contracts": [ + { + "kind": "success-or-empty", + "raw": "RecRoom.Async.IPromise", + "rootType": "void/success", + "typeDescription": { + "name": "void/success", + "found": false, + "kind": "object", + "wireKeys": [] + } + } + ] + } + }, + "/rooms/{p0}/interactionby/me/favorite": { + "get": { + "tags": [ + "rooms" + ], + "summary": "GET rooms/{0}/interactionby/me/favorite", + "description": "Inferred from Rec Room 2020 client literal `rooms/{0}/interactionby/me/favorite`. Expected result: Room detail object or mutation success.", + "operationId": "client_rooms_rooms_p0_interactionby_me_favorite_get", + "parameters": [ + { + "name": "p0", + "in": "path", + "required": true, + "schema": { + "type": "string" + }, + "description": "Format placeholder from client literal rooms/{0}/interactionby/me/favorite" + } + ], + "responses": { + "200": { + "description": "Room detail object or mutation success.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Room" + } + } + } + }, + "204": { + "description": "Success with no body, used by some mutation endpoints." + }, + "400": { + "description": "Client-visible validation or bad request error.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "401": { + "description": "Authentication required or token invalid.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "500": { + "description": "Server error shape tolerated by client wrappers.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + } + }, + "x-recroom-client-route-literal": "rooms/{0}/interactionby/me/favorite", + "x-recroom-client-family": "rooms", + "x-recroom-client-service": "rooms/api", + "x-recroom-builds": [ + "2020.12.18" + ], + "x-recroom-inferred-http-method": "get", + "x-recroom-evidence": [ + { + "type": "EJDCNGBEICB", + "method": "RecRoom.Async.IPromise DIFNOPBMBFO(System.Int64 HNHLJONGKHB)", + "file": "EJDCNGBEICB.txt", + "line": 8467 + }, + { + "type": "EJDCNGBEICB", + "method": "RecRoom.Async.IPromise OGBLBGALMDC(System.Int64 HNHLJONGKHB)", + "file": "EJDCNGBEICB.txt", + "line": 8535 + } + ], + "x-client-response-contracts": [ + { + "kind": "success-or-empty", + "raw": "RecRoom.Async.IPromise", + "rootType": "void/success", + "typeDescription": { + "name": "void/success", + "found": false, + "kind": "object", + "wireKeys": [] + } + } + ] + } + }, + "/rooms/{p0}/interactionby/me": { + "get": { + "tags": [ + "rooms" + ], + "summary": "GET rooms/{0}/interactionby/me", + "description": "Inferred from Rec Room 2020 client literal `rooms/{0}/interactionby/me`. Expected result: Room detail object or mutation success.", + "operationId": "client_rooms_rooms_p0_interactionby_me_get", + "parameters": [ + { + "name": "p0", + "in": "path", + "required": true, + "schema": { + "type": "string" + }, + "description": "Format placeholder from client literal rooms/{0}/interactionby/me" + } + ], + "responses": { + "200": { + "description": "Room detail object or mutation success.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Room" + } + } + } + }, + "204": { + "description": "Success with no body, used by some mutation endpoints." + }, + "400": { + "description": "Client-visible validation or bad request error.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "401": { + "description": "Authentication required or token invalid.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "500": { + "description": "Server error shape tolerated by client wrappers.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + } + }, + "x-recroom-client-route-literal": "rooms/{0}/interactionby/me", + "x-recroom-client-family": "rooms", + "x-recroom-client-service": "rooms/api", + "x-recroom-builds": [ + "2020.12.18" + ], + "x-recroom-inferred-http-method": "get", + "x-recroom-evidence": [ + { + "type": "EJDCNGBEICB", + "method": "RecRoom.Async.IPromise`1 CKJBGHEIGBI(System.Int64 HNHLJONGKHB)", + "file": "EJDCNGBEICB.txt", + "line": 8266 + } + ], + "x-client-response-contracts": [ + { + "kind": "object", + "raw": "CJODCLDGFCF", + "rootType": "CJODCLDGFCF", + "typeDescription": { + "name": "CJODCLDGFCF", + "found": true, + "file": "CJODCLDGFCF.cs", + "declaration": "public class CJODCLDGFCF : IFAIJAGLDFK", + "kind": "class", + "baseType": "IFAIJAGLDFK", + "properties": [ + { + "name": "EFCGNDGOFNK", + "type": "Nullable", + "accessibility": "public", + "kind": "property" + }, + { + "name": "EMBNPPMOJFJ", + "type": "bool", + "accessibility": "public", + "kind": "property" + }, + { + "name": "HHFLKFOEHNL", + "type": "bool", + "accessibility": "public", + "kind": "property" + } + ], + "fields": [], + "enumValues": [], + "wireKeys": [ + "Cheered", + "Favorited" + ], + "isilFile": "CJODCLDGFCF.txt", + "isilType": "CJODCLDGFCF" + } + } + ] + } + }, + "/rooms/{p0}/modify": { + "post": { + "tags": [ + "rooms" + ], + "summary": "POST rooms/{0}/modify", + "description": "Inferred from Rec Room 2020 client literal `rooms/{0}/modify`. Expected result: Room detail object or mutation success.", + "operationId": "client_rooms_rooms_p0_modify_post", + "parameters": [ + { + "name": "p0", + "in": "path", + "required": true, + "schema": { + "type": "string" + }, + "description": "Format placeholder from client literal rooms/{0}/modify" + } + ], + "responses": { + "200": { + "description": "Room detail object or mutation success.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Room" + } + } + } + }, + "204": { + "description": "Success with no body, used by some mutation endpoints." + }, + "400": { + "description": "Client-visible validation or bad request error.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "401": { + "description": "Authentication required or token invalid.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "500": { + "description": "Server error shape tolerated by client wrappers.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + } + }, + "x-recroom-client-route-literal": "rooms/{0}/modify", + "x-recroom-client-family": "rooms", + "x-recroom-client-service": "rooms/api", + "x-recroom-builds": [ + "2020.12.18" + ], + "x-recroom-inferred-http-method": "post", + "x-recroom-evidence": [ + { + "type": "EJDCNGBEICB", + "method": "RecRoom.Async.IPromise`1 ONBIHBFOJKD(System.Int64 HNHLJONGKHB, System.String MMBOKOLAJFH, System.String LJIGOCDPEJF, DPLPMKMFMPB PONCIIJOHIE, System.Boolean DDMOEIAHJBK, System.Boolean FOBLJIBLCNI, System.Boolean GELHFEFGJLA, System.Boolean NFIGHDPKLJF, System.Boolean GPAGMKFIKNG, System.Boolean AMJMLANOGKE, System.Boolean IMHMMPKLGEP, System.Boolean NFJINCPDPDG)", + "file": "EJDCNGBEICB.txt", + "line": 6298 + } + ], + "requestBody": { + "required": false, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/GenericObject" + } + }, + "application/x-www-form-urlencoded": { + "schema": { + "$ref": "#/components/schemas/GenericObject" + } + }, + "multipart/form-data": { + "schema": { + "$ref": "#/components/schemas/GenericObject" + } + } + }, + "description": "Request body inferred. Exact DTO may be named in x-recroom-evidence method signature or companion DTO inventory." + }, + "x-client-response-contracts": [ + { + "kind": "object", + "raw": "PPENFJMFPNE", + "rootType": "PPENFJMFPNE", + "typeDescription": { + "name": "PPENFJMFPNE", + "found": true, + "file": "PPENFJMFPNE.cs", + "declaration": "public class PPENFJMFPNE : KLCOGEIGEBJ", + "kind": "class", + "baseType": "KLCOGEIGEBJ", + "properties": [ + { + "name": "ADCDBMGNIPB", + "type": "List", + "accessibility": "public", + "kind": "property" + }, + { + "name": "BIODFICLKJH", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "DFKGKGIMEMP", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "EJGIMHADHBC", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "FEBCCOFBKKJ", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "IAJDFKODGFL", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "KFFBPMHGFKB", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "LHGHLGDINFL", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "LOJPLANGFMG", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "MMBJPGGOFHL", + "type": "LMLJHMJEIGM", + "accessibility": "public", + "kind": "property" + }, + { + "name": "MMFBPKOOLNG", + "type": "bool", + "accessibility": "public", + "kind": "property" + }, + { + "name": "OHNIGJECGKL", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "PKKADKGDHNI", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + } + ], + "fields": [ + { + "name": "accountId", + "type": "int", + "kind": "field" + }, + { + "name": "tag", + "type": "string", + "kind": "field" + }, + { + "name": "IGAPDHGFLDC", + "type": "PPENFJMFPNE", + "kind": "field" + } + ], + "enumValues": [], + "wireKeys": [], + "isilFile": "PPENFJMFPNE.txt", + "isilType": "PPENFJMFPNE", + "inherits": "KLCOGEIGEBJ", + "inheritedMembers": [ + { + "name": "DADOKMAOFJL", + "type": "long", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "KLCOGEIGEBJ" + }, + { + "name": "GJPGKJJCPBK", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "KLCOGEIGEBJ" + }, + { + "name": "IBLOJJBEKFF", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "KLCOGEIGEBJ" + }, + { + "name": "JCLCOCEOAEP", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "KLCOGEIGEBJ" + }, + { + "name": "JGJCBGEBLLO", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "KLCOGEIGEBJ" + }, + { + "name": "PLFEEIJCHOH", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "KLCOGEIGEBJ" + }, + { + "name": "BJFNPKFOALK", + "type": "long", + "kind": "field", + "inheritedFrom": "KLCOGEIGEBJ" + }, + { + "name": "ACBFDMLHFPB", + "type": "DateTime", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "AHGCOGFEEEE", + "type": "string", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "AKFBNELAMNA", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "BADIGBCKECA", + "type": "int", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "BNBLOBAEDEE", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "CDINMMPNAID", + "type": "NMJEKMMBDDE", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "CDNFGMHLDMJ", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "FIKEBGGCDFN", + "type": "string", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "GIBHIMGJNNO", + "type": "GPDIAKNEBKH", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "HPLBOMGACED", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "IGOPGMHHLKI", + "type": "string", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "JFEAPMIPNEP", + "type": "DPLPMKMFMPB", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "KHIJAFCHLIA", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "KLNJBBPNMBJ", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "KODBEJPEFOJ", + "type": "string", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "LPJLEMJFBPE", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "MGBDHBHCDMH", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "MIKDLDEALPN", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "OFONEIOEIED", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "OILEJFNPDDB", + "type": "HJPGEGENLPH", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "PEEFHKMOMKK", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + } + ], + "inheritedWireKeys": [ + "RoomId", + "IsDorm", + "CloningAllowed", + "DisableMicAutoMute", + "DisableRoomComments", + "EncryptVoiceChat" + ] + } + } + ] + } + }, + "/rooms/{p0}/name": { + "post": { + "tags": [ + "rooms" + ], + "summary": "POST rooms/{0}/name", + "description": "Inferred from Rec Room 2020 client literal `rooms/{0}/name`. Expected result: Room detail object or mutation success.", + "operationId": "client_rooms_rooms_p0_name_post", + "parameters": [ + { + "name": "p0", + "in": "path", + "required": true, + "schema": { + "type": "string" + }, + "description": "Format placeholder from client literal rooms/{0}/name" + } + ], + "responses": { + "200": { + "description": "Room detail object or mutation success.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Room" + } + } + } + }, + "204": { + "description": "Success with no body, used by some mutation endpoints." + }, + "400": { + "description": "Client-visible validation or bad request error.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "401": { + "description": "Authentication required or token invalid.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "500": { + "description": "Server error shape tolerated by client wrappers.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + } + }, + "x-recroom-client-route-literal": "rooms/{0}/name", + "x-recroom-client-family": "rooms", + "x-recroom-client-service": "rooms/api", + "x-recroom-builds": [ + "2020.12.18" + ], + "x-recroom-inferred-http-method": "post", + "x-recroom-evidence": [ + { + "type": "EJDCNGBEICB", + "method": "RecRoom.Async.IPromise`1 DNOGGKKHMHI(System.Int64 HNHLJONGKHB, System.String MMBOKOLAJFH)", + "file": "EJDCNGBEICB.txt", + "line": 3027 + } + ], + "requestBody": { + "required": false, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/GenericObject" + } + }, + "application/x-www-form-urlencoded": { + "schema": { + "$ref": "#/components/schemas/GenericObject" + } + }, + "multipart/form-data": { + "schema": { + "$ref": "#/components/schemas/GenericObject" + } + } + }, + "description": "Request body inferred. Exact DTO may be named in x-recroom-evidence method signature or companion DTO inventory." + }, + "x-client-response-contracts": [ + { + "kind": "object", + "raw": "PPENFJMFPNE", + "rootType": "PPENFJMFPNE", + "typeDescription": { + "name": "PPENFJMFPNE", + "found": true, + "file": "PPENFJMFPNE.cs", + "declaration": "public class PPENFJMFPNE : KLCOGEIGEBJ", + "kind": "class", + "baseType": "KLCOGEIGEBJ", + "properties": [ + { + "name": "ADCDBMGNIPB", + "type": "List", + "accessibility": "public", + "kind": "property" + }, + { + "name": "BIODFICLKJH", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "DFKGKGIMEMP", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "EJGIMHADHBC", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "FEBCCOFBKKJ", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "IAJDFKODGFL", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "KFFBPMHGFKB", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "LHGHLGDINFL", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "LOJPLANGFMG", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "MMBJPGGOFHL", + "type": "LMLJHMJEIGM", + "accessibility": "public", + "kind": "property" + }, + { + "name": "MMFBPKOOLNG", + "type": "bool", + "accessibility": "public", + "kind": "property" + }, + { + "name": "OHNIGJECGKL", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "PKKADKGDHNI", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + } + ], + "fields": [ + { + "name": "accountId", + "type": "int", + "kind": "field" + }, + { + "name": "tag", + "type": "string", + "kind": "field" + }, + { + "name": "IGAPDHGFLDC", + "type": "PPENFJMFPNE", + "kind": "field" + } + ], + "enumValues": [], + "wireKeys": [], + "isilFile": "PPENFJMFPNE.txt", + "isilType": "PPENFJMFPNE", + "inherits": "KLCOGEIGEBJ", + "inheritedMembers": [ + { + "name": "DADOKMAOFJL", + "type": "long", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "KLCOGEIGEBJ" + }, + { + "name": "GJPGKJJCPBK", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "KLCOGEIGEBJ" + }, + { + "name": "IBLOJJBEKFF", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "KLCOGEIGEBJ" + }, + { + "name": "JCLCOCEOAEP", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "KLCOGEIGEBJ" + }, + { + "name": "JGJCBGEBLLO", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "KLCOGEIGEBJ" + }, + { + "name": "PLFEEIJCHOH", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "KLCOGEIGEBJ" + }, + { + "name": "BJFNPKFOALK", + "type": "long", + "kind": "field", + "inheritedFrom": "KLCOGEIGEBJ" + }, + { + "name": "ACBFDMLHFPB", + "type": "DateTime", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "AHGCOGFEEEE", + "type": "string", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "AKFBNELAMNA", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "BADIGBCKECA", + "type": "int", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "BNBLOBAEDEE", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "CDINMMPNAID", + "type": "NMJEKMMBDDE", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "CDNFGMHLDMJ", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "FIKEBGGCDFN", + "type": "string", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "GIBHIMGJNNO", + "type": "GPDIAKNEBKH", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "HPLBOMGACED", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "IGOPGMHHLKI", + "type": "string", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "JFEAPMIPNEP", + "type": "DPLPMKMFMPB", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "KHIJAFCHLIA", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "KLNJBBPNMBJ", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "KODBEJPEFOJ", + "type": "string", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "LPJLEMJFBPE", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "MGBDHBHCDMH", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "MIKDLDEALPN", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "OFONEIOEIED", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "OILEJFNPDDB", + "type": "HJPGEGENLPH", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "PEEFHKMOMKK", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + } + ], + "inheritedWireKeys": [ + "RoomId", + "IsDorm", + "CloningAllowed", + "DisableMicAutoMute", + "DisableRoomComments", + "EncryptVoiceChat" + ] + } + } + ] + } + }, + "/rooms/{p0}/promo_external/{p1}/{p2}": { + "post": { + "tags": [ + "rooms" + ], + "summary": "POST rooms/{0}/promo_external/{1}/{2}", + "description": "Inferred from Rec Room 2020 client literal `rooms/{0}/promo_external/{1}/{2}`. Expected result: Room detail object or mutation success.", + "operationId": "client_rooms_rooms_p0_promo_external_p1_p2_post", + "parameters": [ + { + "name": "p0", + "in": "path", + "required": true, + "schema": { + "type": "string" + }, + "description": "Format placeholder from client literal rooms/{0}/promo_external/{1}/{2}" + }, + { + "name": "p1", + "in": "path", + "required": true, + "schema": { + "type": "string" + }, + "description": "Format placeholder from client literal rooms/{0}/promo_external/{1}/{2}" + }, + { + "name": "p2", + "in": "path", + "required": true, + "schema": { + "type": "string" + }, + "description": "Format placeholder from client literal rooms/{0}/promo_external/{1}/{2}" + } + ], + "responses": { + "200": { + "description": "Room detail object or mutation success.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Room" + } + } + } + }, + "204": { + "description": "Success with no body, used by some mutation endpoints." + }, + "400": { + "description": "Client-visible validation or bad request error.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "401": { + "description": "Authentication required or token invalid.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "500": { + "description": "Server error shape tolerated by client wrappers.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + } + }, + "x-recroom-client-route-literal": "rooms/{0}/promo_external/{1}/{2}", + "x-recroom-client-family": "rooms", + "x-recroom-client-service": "rooms/api", + "x-recroom-builds": [ + "2020.12.18" + ], + "x-recroom-inferred-http-method": "post", + "x-recroom-evidence": [ + { + "type": "EJDCNGBEICB", + "method": "RecRoom.Async.IPromise`1 ACGJJCMFOCH(System.Int64 HNHLJONGKHB, NCELIDGFOEM GEDCEIDOKJL, System.String EKGHGPLFMPJ)", + "file": "EJDCNGBEICB.txt", + "line": 4970 + } + ], + "requestBody": { + "required": false, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/GenericObject" + } + }, + "application/x-www-form-urlencoded": { + "schema": { + "$ref": "#/components/schemas/GenericObject" + } + }, + "multipart/form-data": { + "schema": { + "$ref": "#/components/schemas/GenericObject" + } + } + }, + "description": "Request body inferred. Exact DTO may be named in x-recroom-evidence method signature or companion DTO inventory." + }, + "x-client-response-contracts": [ + { + "kind": "object", + "raw": "PPENFJMFPNE", + "rootType": "PPENFJMFPNE", + "typeDescription": { + "name": "PPENFJMFPNE", + "found": true, + "file": "PPENFJMFPNE.cs", + "declaration": "public class PPENFJMFPNE : KLCOGEIGEBJ", + "kind": "class", + "baseType": "KLCOGEIGEBJ", + "properties": [ + { + "name": "ADCDBMGNIPB", + "type": "List", + "accessibility": "public", + "kind": "property" + }, + { + "name": "BIODFICLKJH", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "DFKGKGIMEMP", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "EJGIMHADHBC", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "FEBCCOFBKKJ", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "IAJDFKODGFL", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "KFFBPMHGFKB", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "LHGHLGDINFL", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "LOJPLANGFMG", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "MMBJPGGOFHL", + "type": "LMLJHMJEIGM", + "accessibility": "public", + "kind": "property" + }, + { + "name": "MMFBPKOOLNG", + "type": "bool", + "accessibility": "public", + "kind": "property" + }, + { + "name": "OHNIGJECGKL", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "PKKADKGDHNI", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + } + ], + "fields": [ + { + "name": "accountId", + "type": "int", + "kind": "field" + }, + { + "name": "tag", + "type": "string", + "kind": "field" + }, + { + "name": "IGAPDHGFLDC", + "type": "PPENFJMFPNE", + "kind": "field" + } + ], + "enumValues": [], + "wireKeys": [], + "isilFile": "PPENFJMFPNE.txt", + "isilType": "PPENFJMFPNE", + "inherits": "KLCOGEIGEBJ", + "inheritedMembers": [ + { + "name": "DADOKMAOFJL", + "type": "long", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "KLCOGEIGEBJ" + }, + { + "name": "GJPGKJJCPBK", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "KLCOGEIGEBJ" + }, + { + "name": "IBLOJJBEKFF", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "KLCOGEIGEBJ" + }, + { + "name": "JCLCOCEOAEP", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "KLCOGEIGEBJ" + }, + { + "name": "JGJCBGEBLLO", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "KLCOGEIGEBJ" + }, + { + "name": "PLFEEIJCHOH", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "KLCOGEIGEBJ" + }, + { + "name": "BJFNPKFOALK", + "type": "long", + "kind": "field", + "inheritedFrom": "KLCOGEIGEBJ" + }, + { + "name": "ACBFDMLHFPB", + "type": "DateTime", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "AHGCOGFEEEE", + "type": "string", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "AKFBNELAMNA", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "BADIGBCKECA", + "type": "int", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "BNBLOBAEDEE", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "CDINMMPNAID", + "type": "NMJEKMMBDDE", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "CDNFGMHLDMJ", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "FIKEBGGCDFN", + "type": "string", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "GIBHIMGJNNO", + "type": "GPDIAKNEBKH", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "HPLBOMGACED", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "IGOPGMHHLKI", + "type": "string", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "JFEAPMIPNEP", + "type": "DPLPMKMFMPB", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "KHIJAFCHLIA", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "KLNJBBPNMBJ", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "KODBEJPEFOJ", + "type": "string", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "LPJLEMJFBPE", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "MGBDHBHCDMH", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "MIKDLDEALPN", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "OFONEIOEIED", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "OILEJFNPDDB", + "type": "HJPGEGENLPH", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "PEEFHKMOMKK", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + } + ], + "inheritedWireKeys": [ + "RoomId", + "IsDorm", + "CloningAllowed", + "DisableMicAutoMute", + "DisableRoomComments", + "EncryptVoiceChat" + ] + } + } + ] + } + }, + "/rooms/{p0}/promo_external": { + "post": { + "tags": [ + "rooms" + ], + "summary": "POST rooms/{0}/promo_external", + "description": "Inferred from Rec Room 2020 client literal `rooms/{0}/promo_external`. Expected result: Room detail object or mutation success.", + "operationId": "client_rooms_rooms_p0_promo_external_post", + "parameters": [ + { + "name": "p0", + "in": "path", + "required": true, + "schema": { + "type": "string" + }, + "description": "Format placeholder from client literal rooms/{0}/promo_external" + } + ], + "responses": { + "200": { + "description": "Room detail object or mutation success.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Room" + } + } + } + }, + "204": { + "description": "Success with no body, used by some mutation endpoints." + }, + "400": { + "description": "Client-visible validation or bad request error.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "401": { + "description": "Authentication required or token invalid.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "500": { + "description": "Server error shape tolerated by client wrappers.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + } + }, + "x-recroom-client-route-literal": "rooms/{0}/promo_external", + "x-recroom-client-family": "rooms", + "x-recroom-client-service": "rooms/api", + "x-recroom-builds": [ + "2020.12.18" + ], + "x-recroom-inferred-http-method": "post", + "x-recroom-evidence": [ + { + "type": "EJDCNGBEICB", + "method": "RecRoom.Async.IPromise`1 FBNHEKCLMCJ(System.Int64 HNHLJONGKHB, NCELIDGFOEM GEDCEIDOKJL, System.String EKGHGPLFMPJ)", + "file": "EJDCNGBEICB.txt", + "line": 4862 + } + ], + "requestBody": { + "required": false, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/GenericObject" + } + }, + "application/x-www-form-urlencoded": { + "schema": { + "$ref": "#/components/schemas/GenericObject" + } + }, + "multipart/form-data": { + "schema": { + "$ref": "#/components/schemas/GenericObject" + } + } + }, + "description": "Request body inferred. Exact DTO may be named in x-recroom-evidence method signature or companion DTO inventory." + }, + "x-client-response-contracts": [ + { + "kind": "object", + "raw": "PPENFJMFPNE", + "rootType": "PPENFJMFPNE", + "typeDescription": { + "name": "PPENFJMFPNE", + "found": true, + "file": "PPENFJMFPNE.cs", + "declaration": "public class PPENFJMFPNE : KLCOGEIGEBJ", + "kind": "class", + "baseType": "KLCOGEIGEBJ", + "properties": [ + { + "name": "ADCDBMGNIPB", + "type": "List", + "accessibility": "public", + "kind": "property" + }, + { + "name": "BIODFICLKJH", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "DFKGKGIMEMP", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "EJGIMHADHBC", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "FEBCCOFBKKJ", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "IAJDFKODGFL", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "KFFBPMHGFKB", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "LHGHLGDINFL", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "LOJPLANGFMG", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "MMBJPGGOFHL", + "type": "LMLJHMJEIGM", + "accessibility": "public", + "kind": "property" + }, + { + "name": "MMFBPKOOLNG", + "type": "bool", + "accessibility": "public", + "kind": "property" + }, + { + "name": "OHNIGJECGKL", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "PKKADKGDHNI", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + } + ], + "fields": [ + { + "name": "accountId", + "type": "int", + "kind": "field" + }, + { + "name": "tag", + "type": "string", + "kind": "field" + }, + { + "name": "IGAPDHGFLDC", + "type": "PPENFJMFPNE", + "kind": "field" + } + ], + "enumValues": [], + "wireKeys": [], + "isilFile": "PPENFJMFPNE.txt", + "isilType": "PPENFJMFPNE", + "inherits": "KLCOGEIGEBJ", + "inheritedMembers": [ + { + "name": "DADOKMAOFJL", + "type": "long", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "KLCOGEIGEBJ" + }, + { + "name": "GJPGKJJCPBK", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "KLCOGEIGEBJ" + }, + { + "name": "IBLOJJBEKFF", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "KLCOGEIGEBJ" + }, + { + "name": "JCLCOCEOAEP", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "KLCOGEIGEBJ" + }, + { + "name": "JGJCBGEBLLO", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "KLCOGEIGEBJ" + }, + { + "name": "PLFEEIJCHOH", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "KLCOGEIGEBJ" + }, + { + "name": "BJFNPKFOALK", + "type": "long", + "kind": "field", + "inheritedFrom": "KLCOGEIGEBJ" + }, + { + "name": "ACBFDMLHFPB", + "type": "DateTime", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "AHGCOGFEEEE", + "type": "string", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "AKFBNELAMNA", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "BADIGBCKECA", + "type": "int", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "BNBLOBAEDEE", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "CDINMMPNAID", + "type": "NMJEKMMBDDE", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "CDNFGMHLDMJ", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "FIKEBGGCDFN", + "type": "string", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "GIBHIMGJNNO", + "type": "GPDIAKNEBKH", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "HPLBOMGACED", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "IGOPGMHHLKI", + "type": "string", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "JFEAPMIPNEP", + "type": "DPLPMKMFMPB", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "KHIJAFCHLIA", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "KLNJBBPNMBJ", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "KODBEJPEFOJ", + "type": "string", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "LPJLEMJFBPE", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "MGBDHBHCDMH", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "MIKDLDEALPN", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "OFONEIOEIED", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "OILEJFNPDDB", + "type": "HJPGEGENLPH", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "PEEFHKMOMKK", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + } + ], + "inheritedWireKeys": [ + "RoomId", + "IsDorm", + "CloningAllowed", + "DisableMicAutoMute", + "DisableRoomComments", + "EncryptVoiceChat" + ] + } + } + ] + } + }, + "/rooms/{p0}/promo_images/{p1}": { + "post": { + "tags": [ + "rooms" + ], + "summary": "POST rooms/{0}/promo_images/{1}", + "description": "Inferred from Rec Room 2020 client literal `rooms/{0}/promo_images/{1}`. Expected result: Room detail object or mutation success.", + "operationId": "client_rooms_rooms_p0_promo_images_p1_post", + "parameters": [ + { + "name": "p0", + "in": "path", + "required": true, + "schema": { + "type": "string" + }, + "description": "Format placeholder from client literal rooms/{0}/promo_images/{1}" + }, + { + "name": "p1", + "in": "path", + "required": true, + "schema": { + "type": "string" + }, + "description": "Format placeholder from client literal rooms/{0}/promo_images/{1}" + } + ], + "responses": { + "200": { + "description": "Room detail object or mutation success.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Room" + } + } + } + }, + "204": { + "description": "Success with no body, used by some mutation endpoints." + }, + "400": { + "description": "Client-visible validation or bad request error.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "401": { + "description": "Authentication required or token invalid.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "500": { + "description": "Server error shape tolerated by client wrappers.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + } + }, + "x-recroom-client-route-literal": "rooms/{0}/promo_images/{1}", + "x-recroom-client-family": "rooms", + "x-recroom-client-service": "rooms/api", + "x-recroom-builds": [ + "2020.12.18" + ], + "x-recroom-inferred-http-method": "post", + "x-recroom-evidence": [ + { + "type": "EJDCNGBEICB", + "method": "RecRoom.Async.IPromise`1 OCCGHPAHGKC(System.Int64 HNHLJONGKHB, System.String HFLPBHHAFIO)", + "file": "EJDCNGBEICB.txt", + "line": 4749 + } + ], + "requestBody": { + "required": false, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/GenericObject" + } + }, + "application/x-www-form-urlencoded": { + "schema": { + "$ref": "#/components/schemas/GenericObject" + } + }, + "multipart/form-data": { + "schema": { + "$ref": "#/components/schemas/GenericObject" + } + } + }, + "description": "Request body inferred. Exact DTO may be named in x-recroom-evidence method signature or companion DTO inventory." + }, + "x-client-response-contracts": [ + { + "kind": "object", + "raw": "PPENFJMFPNE", + "rootType": "PPENFJMFPNE", + "typeDescription": { + "name": "PPENFJMFPNE", + "found": true, + "file": "PPENFJMFPNE.cs", + "declaration": "public class PPENFJMFPNE : KLCOGEIGEBJ", + "kind": "class", + "baseType": "KLCOGEIGEBJ", + "properties": [ + { + "name": "ADCDBMGNIPB", + "type": "List", + "accessibility": "public", + "kind": "property" + }, + { + "name": "BIODFICLKJH", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "DFKGKGIMEMP", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "EJGIMHADHBC", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "FEBCCOFBKKJ", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "IAJDFKODGFL", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "KFFBPMHGFKB", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "LHGHLGDINFL", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "LOJPLANGFMG", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "MMBJPGGOFHL", + "type": "LMLJHMJEIGM", + "accessibility": "public", + "kind": "property" + }, + { + "name": "MMFBPKOOLNG", + "type": "bool", + "accessibility": "public", + "kind": "property" + }, + { + "name": "OHNIGJECGKL", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "PKKADKGDHNI", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + } + ], + "fields": [ + { + "name": "accountId", + "type": "int", + "kind": "field" + }, + { + "name": "tag", + "type": "string", + "kind": "field" + }, + { + "name": "IGAPDHGFLDC", + "type": "PPENFJMFPNE", + "kind": "field" + } + ], + "enumValues": [], + "wireKeys": [], + "isilFile": "PPENFJMFPNE.txt", + "isilType": "PPENFJMFPNE", + "inherits": "KLCOGEIGEBJ", + "inheritedMembers": [ + { + "name": "DADOKMAOFJL", + "type": "long", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "KLCOGEIGEBJ" + }, + { + "name": "GJPGKJJCPBK", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "KLCOGEIGEBJ" + }, + { + "name": "IBLOJJBEKFF", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "KLCOGEIGEBJ" + }, + { + "name": "JCLCOCEOAEP", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "KLCOGEIGEBJ" + }, + { + "name": "JGJCBGEBLLO", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "KLCOGEIGEBJ" + }, + { + "name": "PLFEEIJCHOH", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "KLCOGEIGEBJ" + }, + { + "name": "BJFNPKFOALK", + "type": "long", + "kind": "field", + "inheritedFrom": "KLCOGEIGEBJ" + }, + { + "name": "ACBFDMLHFPB", + "type": "DateTime", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "AHGCOGFEEEE", + "type": "string", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "AKFBNELAMNA", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "BADIGBCKECA", + "type": "int", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "BNBLOBAEDEE", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "CDINMMPNAID", + "type": "NMJEKMMBDDE", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "CDNFGMHLDMJ", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "FIKEBGGCDFN", + "type": "string", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "GIBHIMGJNNO", + "type": "GPDIAKNEBKH", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "HPLBOMGACED", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "IGOPGMHHLKI", + "type": "string", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "JFEAPMIPNEP", + "type": "DPLPMKMFMPB", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "KHIJAFCHLIA", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "KLNJBBPNMBJ", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "KODBEJPEFOJ", + "type": "string", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "LPJLEMJFBPE", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "MGBDHBHCDMH", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "MIKDLDEALPN", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "OFONEIOEIED", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "OILEJFNPDDB", + "type": "HJPGEGENLPH", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "PEEFHKMOMKK", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + } + ], + "inheritedWireKeys": [ + "RoomId", + "IsDorm", + "CloningAllowed", + "DisableMicAutoMute", + "DisableRoomComments", + "EncryptVoiceChat" + ] + } + } + ] + } + }, + "/rooms/{p0}/promo_images": { + "post": { + "tags": [ + "rooms" + ], + "summary": "POST rooms/{0}/promo_images", + "description": "Inferred from Rec Room 2020 client literal `rooms/{0}/promo_images`. Expected result: Room detail object or mutation success.", + "operationId": "client_rooms_rooms_p0_promo_images_post", + "parameters": [ + { + "name": "p0", + "in": "path", + "required": true, + "schema": { + "type": "string" + }, + "description": "Format placeholder from client literal rooms/{0}/promo_images" + } + ], + "responses": { + "200": { + "description": "Room detail object or mutation success.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Room" + } + } + } + }, + "204": { + "description": "Success with no body, used by some mutation endpoints." + }, + "400": { + "description": "Client-visible validation or bad request error.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "401": { + "description": "Authentication required or token invalid.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "500": { + "description": "Server error shape tolerated by client wrappers.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + } + }, + "x-recroom-client-route-literal": "rooms/{0}/promo_images", + "x-recroom-client-family": "rooms", + "x-recroom-client-service": "rooms/api", + "x-recroom-builds": [ + "2020.12.18" + ], + "x-recroom-inferred-http-method": "post", + "x-recroom-evidence": [ + { + "type": "EJDCNGBEICB", + "method": "RecRoom.Async.IPromise`1 CFIKOAAOHFH(System.Int64 HNHLJONGKHB, System.String HFLPBHHAFIO)", + "file": "EJDCNGBEICB.txt", + "line": 4663 + } + ], + "requestBody": { + "required": false, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/GenericObject" + } + }, + "application/x-www-form-urlencoded": { + "schema": { + "$ref": "#/components/schemas/GenericObject" + } + }, + "multipart/form-data": { + "schema": { + "$ref": "#/components/schemas/GenericObject" + } + } + }, + "description": "Request body inferred. Exact DTO may be named in x-recroom-evidence method signature or companion DTO inventory." + }, + "x-client-response-contracts": [ + { + "kind": "object", + "raw": "PPENFJMFPNE", + "rootType": "PPENFJMFPNE", + "typeDescription": { + "name": "PPENFJMFPNE", + "found": true, + "file": "PPENFJMFPNE.cs", + "declaration": "public class PPENFJMFPNE : KLCOGEIGEBJ", + "kind": "class", + "baseType": "KLCOGEIGEBJ", + "properties": [ + { + "name": "ADCDBMGNIPB", + "type": "List", + "accessibility": "public", + "kind": "property" + }, + { + "name": "BIODFICLKJH", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "DFKGKGIMEMP", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "EJGIMHADHBC", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "FEBCCOFBKKJ", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "IAJDFKODGFL", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "KFFBPMHGFKB", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "LHGHLGDINFL", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "LOJPLANGFMG", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "MMBJPGGOFHL", + "type": "LMLJHMJEIGM", + "accessibility": "public", + "kind": "property" + }, + { + "name": "MMFBPKOOLNG", + "type": "bool", + "accessibility": "public", + "kind": "property" + }, + { + "name": "OHNIGJECGKL", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "PKKADKGDHNI", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + } + ], + "fields": [ + { + "name": "accountId", + "type": "int", + "kind": "field" + }, + { + "name": "tag", + "type": "string", + "kind": "field" + }, + { + "name": "IGAPDHGFLDC", + "type": "PPENFJMFPNE", + "kind": "field" + } + ], + "enumValues": [], + "wireKeys": [], + "isilFile": "PPENFJMFPNE.txt", + "isilType": "PPENFJMFPNE", + "inherits": "KLCOGEIGEBJ", + "inheritedMembers": [ + { + "name": "DADOKMAOFJL", + "type": "long", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "KLCOGEIGEBJ" + }, + { + "name": "GJPGKJJCPBK", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "KLCOGEIGEBJ" + }, + { + "name": "IBLOJJBEKFF", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "KLCOGEIGEBJ" + }, + { + "name": "JCLCOCEOAEP", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "KLCOGEIGEBJ" + }, + { + "name": "JGJCBGEBLLO", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "KLCOGEIGEBJ" + }, + { + "name": "PLFEEIJCHOH", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "KLCOGEIGEBJ" + }, + { + "name": "BJFNPKFOALK", + "type": "long", + "kind": "field", + "inheritedFrom": "KLCOGEIGEBJ" + }, + { + "name": "ACBFDMLHFPB", + "type": "DateTime", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "AHGCOGFEEEE", + "type": "string", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "AKFBNELAMNA", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "BADIGBCKECA", + "type": "int", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "BNBLOBAEDEE", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "CDINMMPNAID", + "type": "NMJEKMMBDDE", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "CDNFGMHLDMJ", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "FIKEBGGCDFN", + "type": "string", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "GIBHIMGJNNO", + "type": "GPDIAKNEBKH", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "HPLBOMGACED", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "IGOPGMHHLKI", + "type": "string", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "JFEAPMIPNEP", + "type": "DPLPMKMFMPB", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "KHIJAFCHLIA", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "KLNJBBPNMBJ", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "KODBEJPEFOJ", + "type": "string", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "LPJLEMJFBPE", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "MGBDHBHCDMH", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "MIKDLDEALPN", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "OFONEIOEIED", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "OILEJFNPDDB", + "type": "HJPGEGENLPH", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "PEEFHKMOMKK", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + } + ], + "inheritedWireKeys": [ + "RoomId", + "IsDorm", + "CloningAllowed", + "DisableMicAutoMute", + "DisableRoomComments", + "EncryptVoiceChat" + ] + } + } + ] + } + }, + "/rooms/{p0}/restrictions": { + "post": { + "tags": [ + "rooms" + ], + "summary": "POST rooms/{0}/restrictions", + "description": "Inferred from Rec Room 2020 client literal `rooms/{0}/restrictions`. Expected result: Room detail object or mutation success.", + "operationId": "client_rooms_rooms_p0_restrictions_post", + "parameters": [ + { + "name": "p0", + "in": "path", + "required": true, + "schema": { + "type": "string" + }, + "description": "Format placeholder from client literal rooms/{0}/restrictions" + } + ], + "responses": { + "200": { + "description": "Room detail object or mutation success.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Room" + } + } + } + }, + "204": { + "description": "Success with no body, used by some mutation endpoints." + }, + "400": { + "description": "Client-visible validation or bad request error.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "401": { + "description": "Authentication required or token invalid.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "500": { + "description": "Server error shape tolerated by client wrappers.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + } + }, + "x-recroom-client-route-literal": "rooms/{0}/restrictions", + "x-recroom-client-family": "rooms", + "x-recroom-client-service": "rooms/api", + "x-recroom-builds": [ + "2020.12.18" + ], + "x-recroom-inferred-http-method": "post", + "x-recroom-evidence": [ + { + "type": "EJDCNGBEICB", + "method": "RecRoom.Async.IPromise`1 ONEJODPFOMP(System.Int64 HNHLJONGKHB, System.Boolean DDMOEIAHJBK, System.Boolean FOBLJIBLCNI, System.Boolean GELHFEFGJLA, System.Boolean NFIGHDPKLJF)", + "file": "EJDCNGBEICB.txt", + "line": 3758 + } + ], + "requestBody": { + "required": false, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/GenericObject" + } + }, + "application/x-www-form-urlencoded": { + "schema": { + "$ref": "#/components/schemas/GenericObject" + } + }, + "multipart/form-data": { + "schema": { + "$ref": "#/components/schemas/GenericObject" + } + } + }, + "description": "Request body inferred. Exact DTO may be named in x-recroom-evidence method signature or companion DTO inventory." + }, + "x-client-response-contracts": [ + { + "kind": "object", + "raw": "PPENFJMFPNE", + "rootType": "PPENFJMFPNE", + "typeDescription": { + "name": "PPENFJMFPNE", + "found": true, + "file": "PPENFJMFPNE.cs", + "declaration": "public class PPENFJMFPNE : KLCOGEIGEBJ", + "kind": "class", + "baseType": "KLCOGEIGEBJ", + "properties": [ + { + "name": "ADCDBMGNIPB", + "type": "List", + "accessibility": "public", + "kind": "property" + }, + { + "name": "BIODFICLKJH", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "DFKGKGIMEMP", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "EJGIMHADHBC", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "FEBCCOFBKKJ", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "IAJDFKODGFL", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "KFFBPMHGFKB", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "LHGHLGDINFL", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "LOJPLANGFMG", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "MMBJPGGOFHL", + "type": "LMLJHMJEIGM", + "accessibility": "public", + "kind": "property" + }, + { + "name": "MMFBPKOOLNG", + "type": "bool", + "accessibility": "public", + "kind": "property" + }, + { + "name": "OHNIGJECGKL", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "PKKADKGDHNI", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + } + ], + "fields": [ + { + "name": "accountId", + "type": "int", + "kind": "field" + }, + { + "name": "tag", + "type": "string", + "kind": "field" + }, + { + "name": "IGAPDHGFLDC", + "type": "PPENFJMFPNE", + "kind": "field" + } + ], + "enumValues": [], + "wireKeys": [], + "isilFile": "PPENFJMFPNE.txt", + "isilType": "PPENFJMFPNE", + "inherits": "KLCOGEIGEBJ", + "inheritedMembers": [ + { + "name": "DADOKMAOFJL", + "type": "long", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "KLCOGEIGEBJ" + }, + { + "name": "GJPGKJJCPBK", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "KLCOGEIGEBJ" + }, + { + "name": "IBLOJJBEKFF", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "KLCOGEIGEBJ" + }, + { + "name": "JCLCOCEOAEP", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "KLCOGEIGEBJ" + }, + { + "name": "JGJCBGEBLLO", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "KLCOGEIGEBJ" + }, + { + "name": "PLFEEIJCHOH", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "KLCOGEIGEBJ" + }, + { + "name": "BJFNPKFOALK", + "type": "long", + "kind": "field", + "inheritedFrom": "KLCOGEIGEBJ" + }, + { + "name": "ACBFDMLHFPB", + "type": "DateTime", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "AHGCOGFEEEE", + "type": "string", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "AKFBNELAMNA", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "BADIGBCKECA", + "type": "int", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "BNBLOBAEDEE", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "CDINMMPNAID", + "type": "NMJEKMMBDDE", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "CDNFGMHLDMJ", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "FIKEBGGCDFN", + "type": "string", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "GIBHIMGJNNO", + "type": "GPDIAKNEBKH", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "HPLBOMGACED", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "IGOPGMHHLKI", + "type": "string", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "JFEAPMIPNEP", + "type": "DPLPMKMFMPB", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "KHIJAFCHLIA", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "KLNJBBPNMBJ", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "KODBEJPEFOJ", + "type": "string", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "LPJLEMJFBPE", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "MGBDHBHCDMH", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "MIKDLDEALPN", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "OFONEIOEIED", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "OILEJFNPDDB", + "type": "HJPGEGENLPH", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "PEEFHKMOMKK", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + } + ], + "inheritedWireKeys": [ + "RoomId", + "IsDorm", + "CloningAllowed", + "DisableMicAutoMute", + "DisableRoomComments", + "EncryptVoiceChat" + ] + } + } + ] + } + }, + "/rooms/{p0}/roles/{p1}/invite": { + "post": { + "tags": [ + "rooms" + ], + "summary": "POST rooms/{0}/roles/{1}/invite", + "description": "Inferred from Rec Room 2020 client literal `rooms/{0}/roles/{1}/invite`. Expected result: Room detail object or mutation success.", + "operationId": "client_rooms_rooms_p0_roles_p1_invite_post", + "parameters": [ + { + "name": "p0", + "in": "path", + "required": true, + "schema": { + "type": "string" + }, + "description": "Format placeholder from client literal rooms/{0}/roles/{1}/invite" + }, + { + "name": "p1", + "in": "path", + "required": true, + "schema": { + "type": "string" + }, + "description": "Format placeholder from client literal rooms/{0}/roles/{1}/invite" + } + ], + "responses": { + "200": { + "description": "Room detail object or mutation success.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Room" + } + } + } + }, + "204": { + "description": "Success with no body, used by some mutation endpoints." + }, + "400": { + "description": "Client-visible validation or bad request error.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "401": { + "description": "Authentication required or token invalid.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "500": { + "description": "Server error shape tolerated by client wrappers.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + } + }, + "x-recroom-client-route-literal": "rooms/{0}/roles/{1}/invite", + "x-recroom-client-family": "rooms", + "x-recroom-client-service": "rooms/api", + "x-recroom-builds": [ + "2020.12.18" + ], + "x-recroom-inferred-http-method": "post", + "x-recroom-evidence": [ + { + "type": "EJDCNGBEICB", + "method": "RecRoom.Async.IPromise`1 MJOHJLHDMHB(System.Int64 HNHLJONGKHB, System.Int32 GKLPIFBPGOD, LMLJHMJEIGM IENKDAKBEDP)", + "file": "EJDCNGBEICB.txt", + "line": 4543 + } + ], + "requestBody": { + "required": false, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/GenericObject" + } + }, + "application/x-www-form-urlencoded": { + "schema": { + "$ref": "#/components/schemas/GenericObject" + } + }, + "multipart/form-data": { + "schema": { + "$ref": "#/components/schemas/GenericObject" + } + } + }, + "description": "Request body inferred. Exact DTO may be named in x-recroom-evidence method signature or companion DTO inventory." + }, + "x-client-response-contracts": [ + { + "kind": "object", + "raw": "PPENFJMFPNE", + "rootType": "PPENFJMFPNE", + "typeDescription": { + "name": "PPENFJMFPNE", + "found": true, + "file": "PPENFJMFPNE.cs", + "declaration": "public class PPENFJMFPNE : KLCOGEIGEBJ", + "kind": "class", + "baseType": "KLCOGEIGEBJ", + "properties": [ + { + "name": "ADCDBMGNIPB", + "type": "List", + "accessibility": "public", + "kind": "property" + }, + { + "name": "BIODFICLKJH", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "DFKGKGIMEMP", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "EJGIMHADHBC", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "FEBCCOFBKKJ", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "IAJDFKODGFL", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "KFFBPMHGFKB", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "LHGHLGDINFL", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "LOJPLANGFMG", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "MMBJPGGOFHL", + "type": "LMLJHMJEIGM", + "accessibility": "public", + "kind": "property" + }, + { + "name": "MMFBPKOOLNG", + "type": "bool", + "accessibility": "public", + "kind": "property" + }, + { + "name": "OHNIGJECGKL", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "PKKADKGDHNI", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + } + ], + "fields": [ + { + "name": "accountId", + "type": "int", + "kind": "field" + }, + { + "name": "tag", + "type": "string", + "kind": "field" + }, + { + "name": "IGAPDHGFLDC", + "type": "PPENFJMFPNE", + "kind": "field" + } + ], + "enumValues": [], + "wireKeys": [], + "isilFile": "PPENFJMFPNE.txt", + "isilType": "PPENFJMFPNE", + "inherits": "KLCOGEIGEBJ", + "inheritedMembers": [ + { + "name": "DADOKMAOFJL", + "type": "long", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "KLCOGEIGEBJ" + }, + { + "name": "GJPGKJJCPBK", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "KLCOGEIGEBJ" + }, + { + "name": "IBLOJJBEKFF", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "KLCOGEIGEBJ" + }, + { + "name": "JCLCOCEOAEP", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "KLCOGEIGEBJ" + }, + { + "name": "JGJCBGEBLLO", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "KLCOGEIGEBJ" + }, + { + "name": "PLFEEIJCHOH", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "KLCOGEIGEBJ" + }, + { + "name": "BJFNPKFOALK", + "type": "long", + "kind": "field", + "inheritedFrom": "KLCOGEIGEBJ" + }, + { + "name": "ACBFDMLHFPB", + "type": "DateTime", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "AHGCOGFEEEE", + "type": "string", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "AKFBNELAMNA", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "BADIGBCKECA", + "type": "int", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "BNBLOBAEDEE", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "CDINMMPNAID", + "type": "NMJEKMMBDDE", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "CDNFGMHLDMJ", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "FIKEBGGCDFN", + "type": "string", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "GIBHIMGJNNO", + "type": "GPDIAKNEBKH", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "HPLBOMGACED", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "IGOPGMHHLKI", + "type": "string", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "JFEAPMIPNEP", + "type": "DPLPMKMFMPB", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "KHIJAFCHLIA", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "KLNJBBPNMBJ", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "KODBEJPEFOJ", + "type": "string", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "LPJLEMJFBPE", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "MGBDHBHCDMH", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "MIKDLDEALPN", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "OFONEIOEIED", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "OILEJFNPDDB", + "type": "HJPGEGENLPH", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "PEEFHKMOMKK", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + } + ], + "inheritedWireKeys": [ + "RoomId", + "IsDorm", + "CloningAllowed", + "DisableMicAutoMute", + "DisableRoomComments", + "EncryptVoiceChat" + ] + } + } + ] + } + }, + "/rooms/{p0}/roles/{p1}": { + "get": { + "tags": [ + "rooms" + ], + "summary": "GET rooms/{0}/roles/{1}", + "description": "Inferred from Rec Room 2020 client literal `rooms/{0}/roles/{1}`. Expected result: Room detail object or mutation success.", + "operationId": "client_rooms_rooms_p0_roles_p1_get", + "parameters": [ + { + "name": "p0", + "in": "path", + "required": true, + "schema": { + "type": "string" + }, + "description": "Format placeholder from client literal rooms/{0}/roles/{1}" + }, + { + "name": "p1", + "in": "path", + "required": true, + "schema": { + "type": "string" + }, + "description": "Format placeholder from client literal rooms/{0}/roles/{1}" + } + ], + "responses": { + "200": { + "description": "Room detail object or mutation success.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Room" + } + } + } + }, + "204": { + "description": "Success with no body, used by some mutation endpoints." + }, + "400": { + "description": "Client-visible validation or bad request error.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "401": { + "description": "Authentication required or token invalid.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "500": { + "description": "Server error shape tolerated by client wrappers.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + } + }, + "x-recroom-client-route-literal": "rooms/{0}/roles/{1}", + "x-recroom-client-family": "rooms", + "x-recroom-client-service": "rooms/api", + "x-recroom-builds": [ + "2020.12.18" + ], + "x-recroom-inferred-http-method": "get", + "x-recroom-evidence": [ + { + "type": "EJDCNGBEICB", + "method": "RecRoom.Async.IPromise`1 EKOKFNHHMPI(System.Int64 HNHLJONGKHB, System.Int32 GKLPIFBPGOD)", + "file": "EJDCNGBEICB.txt", + "line": 1332 + }, + { + "type": "EJDCNGBEICB", + "method": "RecRoom.Async.IPromise`1 GJMGBBCIHGH(System.Int64 HNHLJONGKHB, System.Int32 GKLPIFBPGOD, LMLJHMJEIGM IENKDAKBEDP)", + "file": "EJDCNGBEICB.txt", + "line": 4405 + } + ], + "x-client-response-contracts": [ + { + "kind": "object", + "raw": "CGCEKBCIHJC", + "rootType": "CGCEKBCIHJC", + "typeDescription": { + "name": "CGCEKBCIHJC", + "found": true, + "file": "CGCEKBCIHJC.cs", + "declaration": "public class CGCEKBCIHJC : IFAIJAGLDFK", + "kind": "class", + "baseType": "IFAIJAGLDFK", + "properties": [ + { + "name": "CAILAOLBBPM", + "type": "LMLJHMJEIGM", + "accessibility": "public", + "kind": "property" + }, + { + "name": "CBLIGPOFEDE", + "type": "LMLJHMJEIGM", + "accessibility": "public", + "kind": "property" + }, + { + "name": "GAINIOENNCG", + "type": "ObscuredInt", + "accessibility": "public", + "kind": "property" + } + ], + "fields": [], + "enumValues": [], + "wireKeys": [ + "AccountId", + "Role", + "InvitedRole" + ], + "isilFile": "CGCEKBCIHJC.txt", + "isilType": "CGCEKBCIHJC" + } + }, + { + "kind": "object", + "raw": "PPENFJMFPNE", + "rootType": "PPENFJMFPNE", + "typeDescription": { + "name": "PPENFJMFPNE", + "found": true, + "file": "PPENFJMFPNE.cs", + "declaration": "public class PPENFJMFPNE : KLCOGEIGEBJ", + "kind": "class", + "baseType": "KLCOGEIGEBJ", + "properties": [ + { + "name": "ADCDBMGNIPB", + "type": "List", + "accessibility": "public", + "kind": "property" + }, + { + "name": "BIODFICLKJH", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "DFKGKGIMEMP", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "EJGIMHADHBC", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "FEBCCOFBKKJ", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "IAJDFKODGFL", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "KFFBPMHGFKB", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "LHGHLGDINFL", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "LOJPLANGFMG", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "MMBJPGGOFHL", + "type": "LMLJHMJEIGM", + "accessibility": "public", + "kind": "property" + }, + { + "name": "MMFBPKOOLNG", + "type": "bool", + "accessibility": "public", + "kind": "property" + }, + { + "name": "OHNIGJECGKL", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "PKKADKGDHNI", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + } + ], + "fields": [ + { + "name": "accountId", + "type": "int", + "kind": "field" + }, + { + "name": "tag", + "type": "string", + "kind": "field" + }, + { + "name": "IGAPDHGFLDC", + "type": "PPENFJMFPNE", + "kind": "field" + } + ], + "enumValues": [], + "wireKeys": [], + "isilFile": "PPENFJMFPNE.txt", + "isilType": "PPENFJMFPNE", + "inherits": "KLCOGEIGEBJ", + "inheritedMembers": [ + { + "name": "DADOKMAOFJL", + "type": "long", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "KLCOGEIGEBJ" + }, + { + "name": "GJPGKJJCPBK", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "KLCOGEIGEBJ" + }, + { + "name": "IBLOJJBEKFF", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "KLCOGEIGEBJ" + }, + { + "name": "JCLCOCEOAEP", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "KLCOGEIGEBJ" + }, + { + "name": "JGJCBGEBLLO", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "KLCOGEIGEBJ" + }, + { + "name": "PLFEEIJCHOH", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "KLCOGEIGEBJ" + }, + { + "name": "BJFNPKFOALK", + "type": "long", + "kind": "field", + "inheritedFrom": "KLCOGEIGEBJ" + }, + { + "name": "ACBFDMLHFPB", + "type": "DateTime", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "AHGCOGFEEEE", + "type": "string", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "AKFBNELAMNA", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "BADIGBCKECA", + "type": "int", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "BNBLOBAEDEE", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "CDINMMPNAID", + "type": "NMJEKMMBDDE", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "CDNFGMHLDMJ", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "FIKEBGGCDFN", + "type": "string", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "GIBHIMGJNNO", + "type": "GPDIAKNEBKH", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "HPLBOMGACED", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "IGOPGMHHLKI", + "type": "string", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "JFEAPMIPNEP", + "type": "DPLPMKMFMPB", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "KHIJAFCHLIA", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "KLNJBBPNMBJ", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "KODBEJPEFOJ", + "type": "string", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "LPJLEMJFBPE", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "MGBDHBHCDMH", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "MIKDLDEALPN", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "OFONEIOEIED", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "OILEJFNPDDB", + "type": "HJPGEGENLPH", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "PEEFHKMOMKK", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + } + ], + "inheritedWireKeys": [ + "RoomId", + "IsDorm", + "CloningAllowed", + "DisableMicAutoMute", + "DisableRoomComments", + "EncryptVoiceChat" + ] + } + } + ] + } + }, + "/rooms/{p0}/roles": { + "get": { + "tags": [ + "rooms" + ], + "summary": "GET rooms/{0}/roles", + "description": "Inferred from Rec Room 2020 client literal `rooms/{0}/roles`. Expected result: Room detail object or mutation success.", + "operationId": "client_rooms_rooms_p0_roles_get", + "parameters": [ + { + "name": "p0", + "in": "path", + "required": true, + "schema": { + "type": "string" + }, + "description": "Format placeholder from client literal rooms/{0}/roles" + } + ], + "responses": { + "200": { + "description": "Room detail object or mutation success.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Room" + } + } + } + }, + "204": { + "description": "Success with no body, used by some mutation endpoints." + }, + "400": { + "description": "Client-visible validation or bad request error.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "401": { + "description": "Authentication required or token invalid.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "500": { + "description": "Server error shape tolerated by client wrappers.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + } + }, + "x-recroom-client-route-literal": "rooms/{0}/roles", + "x-recroom-client-family": "rooms", + "x-recroom-client-service": "rooms/api", + "x-recroom-builds": [ + "2020.12.18" + ], + "x-recroom-inferred-http-method": "get", + "x-recroom-evidence": [ + { + "type": "EJDCNGBEICB", + "method": "RecRoom.Async.IPromise`1> MIGOLOHAIAL(System.Int64 HNHLJONGKHB)", + "file": "EJDCNGBEICB.txt", + "line": 1254 + } + ], + "x-client-response-contracts": [ + { + "kind": "array", + "raw": "System.Collections.Generic.List`1", + "rootType": "CGCEKBCIHJC", + "item": { + "kind": "object", + "raw": "CGCEKBCIHJC", + "rootType": "CGCEKBCIHJC" + }, + "typeDescription": { + "name": "CGCEKBCIHJC", + "found": true, + "file": "CGCEKBCIHJC.cs", + "declaration": "public class CGCEKBCIHJC : IFAIJAGLDFK", + "kind": "class", + "baseType": "IFAIJAGLDFK", + "properties": [ + { + "name": "CAILAOLBBPM", + "type": "LMLJHMJEIGM", + "accessibility": "public", + "kind": "property" + }, + { + "name": "CBLIGPOFEDE", + "type": "LMLJHMJEIGM", + "accessibility": "public", + "kind": "property" + }, + { + "name": "GAINIOENNCG", + "type": "ObscuredInt", + "accessibility": "public", + "kind": "property" + } + ], + "fields": [], + "enumValues": [], + "wireKeys": [ + "AccountId", + "Role", + "InvitedRole" + ], + "isilFile": "CGCEKBCIHJC.txt", + "isilType": "CGCEKBCIHJC" + } + } + ] + } + }, + "/rooms/{p0}/subrooms/{p1}/accessibility": { + "post": { + "tags": [ + "rooms" + ], + "summary": "POST rooms/{0}/subrooms/{1}/accessibility", + "description": "Inferred from Rec Room 2020 client literal `rooms/{0}/subrooms/{1}/accessibility`. Expected result: Room detail object or mutation success.", + "operationId": "client_rooms_rooms_p0_subrooms_p1_accessibility_post", + "parameters": [ + { + "name": "p0", + "in": "path", + "required": true, + "schema": { + "type": "string" + }, + "description": "Format placeholder from client literal rooms/{0}/subrooms/{1}/accessibility" + }, + { + "name": "p1", + "in": "path", + "required": true, + "schema": { + "type": "string" + }, + "description": "Format placeholder from client literal rooms/{0}/subrooms/{1}/accessibility" + } + ], + "responses": { + "200": { + "description": "Room detail object or mutation success.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Room" + } + } + } + }, + "204": { + "description": "Success with no body, used by some mutation endpoints." + }, + "400": { + "description": "Client-visible validation or bad request error.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "401": { + "description": "Authentication required or token invalid.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "500": { + "description": "Server error shape tolerated by client wrappers.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + } + }, + "x-recroom-client-route-literal": "rooms/{0}/subrooms/{1}/accessibility", + "x-recroom-client-family": "rooms", + "x-recroom-client-service": "rooms/api", + "x-recroom-builds": [ + "2020.12.18" + ], + "x-recroom-inferred-http-method": "post", + "x-recroom-evidence": [ + { + "type": "EJDCNGBEICB", + "method": "RecRoom.Async.IPromise`1 POGEHAMJBPE(System.Int64 HNHLJONGKHB, System.Int64 FODGKNJIGOP, DPLPMKMFMPB PONCIIJOHIE)", + "file": "EJDCNGBEICB.txt", + "line": 5369 + } + ], + "requestBody": { + "required": false, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/GenericObject" + } + }, + "application/x-www-form-urlencoded": { + "schema": { + "$ref": "#/components/schemas/GenericObject" + } + }, + "multipart/form-data": { + "schema": { + "$ref": "#/components/schemas/GenericObject" + } + } + }, + "description": "Request body inferred. Exact DTO may be named in x-recroom-evidence method signature or companion DTO inventory." + }, + "x-client-response-contracts": [ + { + "kind": "object", + "raw": "PPENFJMFPNE", + "rootType": "PPENFJMFPNE", + "typeDescription": { + "name": "PPENFJMFPNE", + "found": true, + "file": "PPENFJMFPNE.cs", + "declaration": "public class PPENFJMFPNE : KLCOGEIGEBJ", + "kind": "class", + "baseType": "KLCOGEIGEBJ", + "properties": [ + { + "name": "ADCDBMGNIPB", + "type": "List", + "accessibility": "public", + "kind": "property" + }, + { + "name": "BIODFICLKJH", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "DFKGKGIMEMP", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "EJGIMHADHBC", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "FEBCCOFBKKJ", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "IAJDFKODGFL", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "KFFBPMHGFKB", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "LHGHLGDINFL", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "LOJPLANGFMG", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "MMBJPGGOFHL", + "type": "LMLJHMJEIGM", + "accessibility": "public", + "kind": "property" + }, + { + "name": "MMFBPKOOLNG", + "type": "bool", + "accessibility": "public", + "kind": "property" + }, + { + "name": "OHNIGJECGKL", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "PKKADKGDHNI", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + } + ], + "fields": [ + { + "name": "accountId", + "type": "int", + "kind": "field" + }, + { + "name": "tag", + "type": "string", + "kind": "field" + }, + { + "name": "IGAPDHGFLDC", + "type": "PPENFJMFPNE", + "kind": "field" + } + ], + "enumValues": [], + "wireKeys": [], + "isilFile": "PPENFJMFPNE.txt", + "isilType": "PPENFJMFPNE", + "inherits": "KLCOGEIGEBJ", + "inheritedMembers": [ + { + "name": "DADOKMAOFJL", + "type": "long", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "KLCOGEIGEBJ" + }, + { + "name": "GJPGKJJCPBK", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "KLCOGEIGEBJ" + }, + { + "name": "IBLOJJBEKFF", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "KLCOGEIGEBJ" + }, + { + "name": "JCLCOCEOAEP", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "KLCOGEIGEBJ" + }, + { + "name": "JGJCBGEBLLO", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "KLCOGEIGEBJ" + }, + { + "name": "PLFEEIJCHOH", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "KLCOGEIGEBJ" + }, + { + "name": "BJFNPKFOALK", + "type": "long", + "kind": "field", + "inheritedFrom": "KLCOGEIGEBJ" + }, + { + "name": "ACBFDMLHFPB", + "type": "DateTime", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "AHGCOGFEEEE", + "type": "string", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "AKFBNELAMNA", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "BADIGBCKECA", + "type": "int", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "BNBLOBAEDEE", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "CDINMMPNAID", + "type": "NMJEKMMBDDE", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "CDNFGMHLDMJ", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "FIKEBGGCDFN", + "type": "string", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "GIBHIMGJNNO", + "type": "GPDIAKNEBKH", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "HPLBOMGACED", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "IGOPGMHHLKI", + "type": "string", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "JFEAPMIPNEP", + "type": "DPLPMKMFMPB", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "KHIJAFCHLIA", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "KLNJBBPNMBJ", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "KODBEJPEFOJ", + "type": "string", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "LPJLEMJFBPE", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "MGBDHBHCDMH", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "MIKDLDEALPN", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "OFONEIOEIED", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "OILEJFNPDDB", + "type": "HJPGEGENLPH", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "PEEFHKMOMKK", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + } + ], + "inheritedWireKeys": [ + "RoomId", + "IsDorm", + "CloningAllowed", + "DisableMicAutoMute", + "DisableRoomComments", + "EncryptVoiceChat" + ] + } + } + ] + } + }, + "/rooms/{p0}/subrooms/{p1}/clone": { + "post": { + "tags": [ + "rooms" + ], + "summary": "POST rooms/{0}/subrooms/{1}/clone", + "description": "Inferred from Rec Room 2020 client literal `rooms/{0}/subrooms/{1}/clone`. Expected result: Room detail object or mutation success.", + "operationId": "client_rooms_rooms_p0_subrooms_p1_clone_post", + "parameters": [ + { + "name": "p0", + "in": "path", + "required": true, + "schema": { + "type": "string" + }, + "description": "Format placeholder from client literal rooms/{0}/subrooms/{1}/clone" + }, + { + "name": "p1", + "in": "path", + "required": true, + "schema": { + "type": "string" + }, + "description": "Format placeholder from client literal rooms/{0}/subrooms/{1}/clone" + } + ], + "responses": { + "200": { + "description": "Room detail object or mutation success.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Room" + } + } + } + }, + "204": { + "description": "Success with no body, used by some mutation endpoints." + }, + "400": { + "description": "Client-visible validation or bad request error.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "401": { + "description": "Authentication required or token invalid.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "500": { + "description": "Server error shape tolerated by client wrappers.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + } + }, + "x-recroom-client-route-literal": "rooms/{0}/subrooms/{1}/clone", + "x-recroom-client-family": "rooms", + "x-recroom-client-service": "rooms/api", + "x-recroom-builds": [ + "2020.12.18" + ], + "x-recroom-inferred-http-method": "post", + "x-recroom-evidence": [ + { + "type": "EJDCNGBEICB", + "method": "RecRoom.Async.IPromise`1 OGEKCICPACB(System.Int64 HNHLJONGKHB, System.Int64 FODGKNJIGOP)", + "file": "EJDCNGBEICB.txt", + "line": 5583 + } + ], + "requestBody": { + "required": false, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/GenericObject" + } + }, + "application/x-www-form-urlencoded": { + "schema": { + "$ref": "#/components/schemas/GenericObject" + } + }, + "multipart/form-data": { + "schema": { + "$ref": "#/components/schemas/GenericObject" + } + } + }, + "description": "Request body inferred. Exact DTO may be named in x-recroom-evidence method signature or companion DTO inventory." + }, + "x-client-response-contracts": [ + { + "kind": "object", + "raw": "PPENFJMFPNE", + "rootType": "PPENFJMFPNE", + "typeDescription": { + "name": "PPENFJMFPNE", + "found": true, + "file": "PPENFJMFPNE.cs", + "declaration": "public class PPENFJMFPNE : KLCOGEIGEBJ", + "kind": "class", + "baseType": "KLCOGEIGEBJ", + "properties": [ + { + "name": "ADCDBMGNIPB", + "type": "List", + "accessibility": "public", + "kind": "property" + }, + { + "name": "BIODFICLKJH", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "DFKGKGIMEMP", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "EJGIMHADHBC", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "FEBCCOFBKKJ", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "IAJDFKODGFL", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "KFFBPMHGFKB", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "LHGHLGDINFL", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "LOJPLANGFMG", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "MMBJPGGOFHL", + "type": "LMLJHMJEIGM", + "accessibility": "public", + "kind": "property" + }, + { + "name": "MMFBPKOOLNG", + "type": "bool", + "accessibility": "public", + "kind": "property" + }, + { + "name": "OHNIGJECGKL", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "PKKADKGDHNI", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + } + ], + "fields": [ + { + "name": "accountId", + "type": "int", + "kind": "field" + }, + { + "name": "tag", + "type": "string", + "kind": "field" + }, + { + "name": "IGAPDHGFLDC", + "type": "PPENFJMFPNE", + "kind": "field" + } + ], + "enumValues": [], + "wireKeys": [], + "isilFile": "PPENFJMFPNE.txt", + "isilType": "PPENFJMFPNE", + "inherits": "KLCOGEIGEBJ", + "inheritedMembers": [ + { + "name": "DADOKMAOFJL", + "type": "long", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "KLCOGEIGEBJ" + }, + { + "name": "GJPGKJJCPBK", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "KLCOGEIGEBJ" + }, + { + "name": "IBLOJJBEKFF", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "KLCOGEIGEBJ" + }, + { + "name": "JCLCOCEOAEP", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "KLCOGEIGEBJ" + }, + { + "name": "JGJCBGEBLLO", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "KLCOGEIGEBJ" + }, + { + "name": "PLFEEIJCHOH", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "KLCOGEIGEBJ" + }, + { + "name": "BJFNPKFOALK", + "type": "long", + "kind": "field", + "inheritedFrom": "KLCOGEIGEBJ" + }, + { + "name": "ACBFDMLHFPB", + "type": "DateTime", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "AHGCOGFEEEE", + "type": "string", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "AKFBNELAMNA", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "BADIGBCKECA", + "type": "int", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "BNBLOBAEDEE", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "CDINMMPNAID", + "type": "NMJEKMMBDDE", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "CDNFGMHLDMJ", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "FIKEBGGCDFN", + "type": "string", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "GIBHIMGJNNO", + "type": "GPDIAKNEBKH", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "HPLBOMGACED", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "IGOPGMHHLKI", + "type": "string", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "JFEAPMIPNEP", + "type": "DPLPMKMFMPB", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "KHIJAFCHLIA", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "KLNJBBPNMBJ", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "KODBEJPEFOJ", + "type": "string", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "LPJLEMJFBPE", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "MGBDHBHCDMH", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "MIKDLDEALPN", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "OFONEIOEIED", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "OILEJFNPDDB", + "type": "HJPGEGENLPH", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "PEEFHKMOMKK", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + } + ], + "inheritedWireKeys": [ + "RoomId", + "IsDorm", + "CloningAllowed", + "DisableMicAutoMute", + "DisableRoomComments", + "EncryptVoiceChat" + ] + } + } + ] + } + }, + "/rooms/{p0}/subrooms/{p1}/datahistory": { + "get": { + "tags": [ + "rooms" + ], + "summary": "GET rooms/{0}/subrooms/{1}/datahistory", + "description": "Inferred from Rec Room 2020 client literal `rooms/{0}/subrooms/{1}/datahistory`. Expected result: Room data descriptor; binary blob must be fetched separately from CDN/storage.", + "operationId": "client_rooms_rooms_p0_subrooms_p1_datahistory_get", + "parameters": [ + { + "name": "p0", + "in": "path", + "required": true, + "schema": { + "type": "string" + }, + "description": "Format placeholder from client literal rooms/{0}/subrooms/{1}/datahistory" + }, + { + "name": "p1", + "in": "path", + "required": true, + "schema": { + "type": "string" + }, + "description": "Format placeholder from client literal rooms/{0}/subrooms/{1}/datahistory" + } + ], + "responses": { + "200": { + "description": "Room data descriptor; binary blob must be fetched separately from CDN/storage.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Room" + } + } + } + }, + "204": { + "description": "Success with no body, used by some mutation endpoints." + }, + "400": { + "description": "Client-visible validation or bad request error.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "401": { + "description": "Authentication required or token invalid.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "500": { + "description": "Server error shape tolerated by client wrappers.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + } + }, + "x-recroom-client-route-literal": "rooms/{0}/subrooms/{1}/datahistory", + "x-recroom-client-family": "rooms", + "x-recroom-client-service": "rooms/api", + "x-recroom-builds": [ + "2020.12.18" + ], + "x-recroom-inferred-http-method": "get", + "x-recroom-evidence": [ + { + "type": "EJDCNGBEICB", + "method": "RecRoom.Async.IPromise`1> HAMBEABEIOC(System.Int64 HNHLJONGKHB, System.Int64 FODGKNJIGOP)", + "file": "EJDCNGBEICB.txt", + "line": 1190 + } + ], + "x-client-response-contracts": [ + { + "kind": "array", + "raw": "System.Collections.Generic.List`1", + "rootType": "DHOCPFIOKHD", + "item": { + "kind": "object", + "raw": "DHOCPFIOKHD", + "rootType": "DHOCPFIOKHD" + }, + "typeDescription": { + "name": "DHOCPFIOKHD", + "found": true, + "file": "DHOCPFIOKHD.cs", + "declaration": "public class DHOCPFIOKHD : IFAIJAGLDFK", + "kind": "class", + "baseType": "IFAIJAGLDFK", + "properties": [ + { + "name": "ACBFDMLHFPB", + "type": "DateTime", + "accessibility": "public", + "kind": "property" + }, + { + "name": "CHKBMPIFNHH", + "type": "string", + "accessibility": "public", + "kind": "property" + }, + { + "name": "GJPHHEHBCIJ", + "type": "long", + "accessibility": "public", + "kind": "property" + }, + { + "name": "NDCONBJJDPM", + "type": "int", + "accessibility": "public", + "kind": "property" + } + ], + "fields": [], + "enumValues": [], + "wireKeys": [ + "SubRoomId", + "DataBlob", + "SavedByAccountId", + "CreatedAt" + ], + "isilFile": "DHOCPFIOKHD.txt", + "isilType": "DHOCPFIOKHD" + } + } + ] + } + }, + "/rooms/{p0}/subrooms/{p1}/data": { + "get": { + "tags": [ + "rooms" + ], + "summary": "GET rooms/{0}/subrooms/{1}/data", + "description": "Inferred from Rec Room 2020 client literal `rooms/{0}/subrooms/{1}/data`. Expected result: Room data descriptor; binary blob must be fetched separately from CDN/storage.", + "operationId": "client_rooms_rooms_p0_subrooms_p1_data_get", + "parameters": [ + { + "name": "p0", + "in": "path", + "required": true, + "schema": { + "type": "string" + }, + "description": "Format placeholder from client literal rooms/{0}/subrooms/{1}/data" + }, + { + "name": "p1", + "in": "path", + "required": true, + "schema": { + "type": "string" + }, + "description": "Format placeholder from client literal rooms/{0}/subrooms/{1}/data" + } + ], + "responses": { + "200": { + "description": "Room data descriptor; binary blob must be fetched separately from CDN/storage.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Room" + } + } + } + }, + "204": { + "description": "Success with no body, used by some mutation endpoints." + }, + "400": { + "description": "Client-visible validation or bad request error.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "401": { + "description": "Authentication required or token invalid.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "500": { + "description": "Server error shape tolerated by client wrappers.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + } + }, + "x-recroom-client-route-literal": "rooms/{0}/subrooms/{1}/data", + "x-recroom-client-family": "rooms", + "x-recroom-client-service": "rooms/api", + "x-recroom-builds": [ + "2020.12.18" + ], + "x-recroom-inferred-http-method": "get", + "x-recroom-evidence": [ + { + "type": "EJDCNGBEICB", + "method": "RecRoom.Async.IPromise`1 HOPMJFDKPDL(System.Int64 HNHLJONGKHB, System.Int64 FODGKNJIGOP, System.String MEIIMAIGBJD, System.Collections.Generic.Dictionary`2 PNOBBNPJDLI, System.Int32 OMCLBIFCHLF)", + "file": "EJDCNGBEICB.txt", + "line": 5995 + } + ], + "x-client-response-contracts": [ + { + "kind": "object", + "raw": "PPENFJMFPNE", + "rootType": "PPENFJMFPNE", + "typeDescription": { + "name": "PPENFJMFPNE", + "found": true, + "file": "PPENFJMFPNE.cs", + "declaration": "public class PPENFJMFPNE : KLCOGEIGEBJ", + "kind": "class", + "baseType": "KLCOGEIGEBJ", + "properties": [ + { + "name": "ADCDBMGNIPB", + "type": "List", + "accessibility": "public", + "kind": "property" + }, + { + "name": "BIODFICLKJH", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "DFKGKGIMEMP", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "EJGIMHADHBC", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "FEBCCOFBKKJ", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "IAJDFKODGFL", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "KFFBPMHGFKB", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "LHGHLGDINFL", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "LOJPLANGFMG", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "MMBJPGGOFHL", + "type": "LMLJHMJEIGM", + "accessibility": "public", + "kind": "property" + }, + { + "name": "MMFBPKOOLNG", + "type": "bool", + "accessibility": "public", + "kind": "property" + }, + { + "name": "OHNIGJECGKL", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "PKKADKGDHNI", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + } + ], + "fields": [ + { + "name": "accountId", + "type": "int", + "kind": "field" + }, + { + "name": "tag", + "type": "string", + "kind": "field" + }, + { + "name": "IGAPDHGFLDC", + "type": "PPENFJMFPNE", + "kind": "field" + } + ], + "enumValues": [], + "wireKeys": [], + "isilFile": "PPENFJMFPNE.txt", + "isilType": "PPENFJMFPNE", + "inherits": "KLCOGEIGEBJ", + "inheritedMembers": [ + { + "name": "DADOKMAOFJL", + "type": "long", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "KLCOGEIGEBJ" + }, + { + "name": "GJPGKJJCPBK", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "KLCOGEIGEBJ" + }, + { + "name": "IBLOJJBEKFF", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "KLCOGEIGEBJ" + }, + { + "name": "JCLCOCEOAEP", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "KLCOGEIGEBJ" + }, + { + "name": "JGJCBGEBLLO", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "KLCOGEIGEBJ" + }, + { + "name": "PLFEEIJCHOH", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "KLCOGEIGEBJ" + }, + { + "name": "BJFNPKFOALK", + "type": "long", + "kind": "field", + "inheritedFrom": "KLCOGEIGEBJ" + }, + { + "name": "ACBFDMLHFPB", + "type": "DateTime", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "AHGCOGFEEEE", + "type": "string", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "AKFBNELAMNA", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "BADIGBCKECA", + "type": "int", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "BNBLOBAEDEE", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "CDINMMPNAID", + "type": "NMJEKMMBDDE", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "CDNFGMHLDMJ", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "FIKEBGGCDFN", + "type": "string", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "GIBHIMGJNNO", + "type": "GPDIAKNEBKH", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "HPLBOMGACED", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "IGOPGMHHLKI", + "type": "string", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "JFEAPMIPNEP", + "type": "DPLPMKMFMPB", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "KHIJAFCHLIA", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "KLNJBBPNMBJ", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "KODBEJPEFOJ", + "type": "string", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "LPJLEMJFBPE", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "MGBDHBHCDMH", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "MIKDLDEALPN", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "OFONEIOEIED", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "OILEJFNPDDB", + "type": "HJPGEGENLPH", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "PEEFHKMOMKK", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + } + ], + "inheritedWireKeys": [ + "RoomId", + "IsDorm", + "CloningAllowed", + "DisableMicAutoMute", + "DisableRoomComments", + "EncryptVoiceChat" + ] + } + } + ] + } + }, + "/rooms/{p0}/subrooms/{p1}/maxplayers": { + "get": { + "tags": [ + "rooms" + ], + "summary": "GET rooms/{0}/subrooms/{1}/maxplayers", + "description": "Inferred from Rec Room 2020 client literal `rooms/{0}/subrooms/{1}/maxplayers`. Expected result: Room detail object or mutation success.", + "operationId": "client_rooms_rooms_p0_subrooms_p1_maxplayers_get", + "parameters": [ + { + "name": "p0", + "in": "path", + "required": true, + "schema": { + "type": "string" + }, + "description": "Format placeholder from client literal rooms/{0}/subrooms/{1}/maxplayers" + }, + { + "name": "p1", + "in": "path", + "required": true, + "schema": { + "type": "string" + }, + "description": "Format placeholder from client literal rooms/{0}/subrooms/{1}/maxplayers" + } + ], + "responses": { + "200": { + "description": "Room detail object or mutation success.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Room" + } + } + } + }, + "204": { + "description": "Success with no body, used by some mutation endpoints." + }, + "400": { + "description": "Client-visible validation or bad request error.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "401": { + "description": "Authentication required or token invalid.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "500": { + "description": "Server error shape tolerated by client wrappers.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + } + }, + "x-recroom-client-route-literal": "rooms/{0}/subrooms/{1}/maxplayers", + "x-recroom-client-family": "rooms", + "x-recroom-client-service": "rooms/api", + "x-recroom-builds": [ + "2020.12.18" + ], + "x-recroom-inferred-http-method": "get", + "x-recroom-evidence": [ + { + "type": "EJDCNGBEICB", + "method": "RecRoom.Async.IPromise`1 AEBGMLMMKOJ(System.Int64 HNHLJONGKHB, System.Int64 FODGKNJIGOP, System.Int32 FFAGIDMFPIF)", + "file": "EJDCNGBEICB.txt", + "line": 5235 + } + ], + "x-client-response-contracts": [ + { + "kind": "object", + "raw": "PPENFJMFPNE", + "rootType": "PPENFJMFPNE", + "typeDescription": { + "name": "PPENFJMFPNE", + "found": true, + "file": "PPENFJMFPNE.cs", + "declaration": "public class PPENFJMFPNE : KLCOGEIGEBJ", + "kind": "class", + "baseType": "KLCOGEIGEBJ", + "properties": [ + { + "name": "ADCDBMGNIPB", + "type": "List", + "accessibility": "public", + "kind": "property" + }, + { + "name": "BIODFICLKJH", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "DFKGKGIMEMP", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "EJGIMHADHBC", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "FEBCCOFBKKJ", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "IAJDFKODGFL", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "KFFBPMHGFKB", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "LHGHLGDINFL", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "LOJPLANGFMG", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "MMBJPGGOFHL", + "type": "LMLJHMJEIGM", + "accessibility": "public", + "kind": "property" + }, + { + "name": "MMFBPKOOLNG", + "type": "bool", + "accessibility": "public", + "kind": "property" + }, + { + "name": "OHNIGJECGKL", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "PKKADKGDHNI", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + } + ], + "fields": [ + { + "name": "accountId", + "type": "int", + "kind": "field" + }, + { + "name": "tag", + "type": "string", + "kind": "field" + }, + { + "name": "IGAPDHGFLDC", + "type": "PPENFJMFPNE", + "kind": "field" + } + ], + "enumValues": [], + "wireKeys": [], + "isilFile": "PPENFJMFPNE.txt", + "isilType": "PPENFJMFPNE", + "inherits": "KLCOGEIGEBJ", + "inheritedMembers": [ + { + "name": "DADOKMAOFJL", + "type": "long", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "KLCOGEIGEBJ" + }, + { + "name": "GJPGKJJCPBK", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "KLCOGEIGEBJ" + }, + { + "name": "IBLOJJBEKFF", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "KLCOGEIGEBJ" + }, + { + "name": "JCLCOCEOAEP", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "KLCOGEIGEBJ" + }, + { + "name": "JGJCBGEBLLO", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "KLCOGEIGEBJ" + }, + { + "name": "PLFEEIJCHOH", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "KLCOGEIGEBJ" + }, + { + "name": "BJFNPKFOALK", + "type": "long", + "kind": "field", + "inheritedFrom": "KLCOGEIGEBJ" + }, + { + "name": "ACBFDMLHFPB", + "type": "DateTime", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "AHGCOGFEEEE", + "type": "string", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "AKFBNELAMNA", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "BADIGBCKECA", + "type": "int", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "BNBLOBAEDEE", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "CDINMMPNAID", + "type": "NMJEKMMBDDE", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "CDNFGMHLDMJ", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "FIKEBGGCDFN", + "type": "string", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "GIBHIMGJNNO", + "type": "GPDIAKNEBKH", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "HPLBOMGACED", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "IGOPGMHHLKI", + "type": "string", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "JFEAPMIPNEP", + "type": "DPLPMKMFMPB", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "KHIJAFCHLIA", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "KLNJBBPNMBJ", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "KODBEJPEFOJ", + "type": "string", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "LPJLEMJFBPE", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "MGBDHBHCDMH", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "MIKDLDEALPN", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "OFONEIOEIED", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "OILEJFNPDDB", + "type": "HJPGEGENLPH", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "PEEFHKMOMKK", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + } + ], + "inheritedWireKeys": [ + "RoomId", + "IsDorm", + "CloningAllowed", + "DisableMicAutoMute", + "DisableRoomComments", + "EncryptVoiceChat" + ] + } + } + ] + } + }, + "/rooms/{p0}/subrooms/{p1}/modify": { + "post": { + "tags": [ + "rooms" + ], + "summary": "POST rooms/{0}/subrooms/{1}/modify", + "description": "Inferred from Rec Room 2020 client literal `rooms/{0}/subrooms/{1}/modify`. Expected result: Room detail object or mutation success.", + "operationId": "client_rooms_rooms_p0_subrooms_p1_modify_post", + "parameters": [ + { + "name": "p0", + "in": "path", + "required": true, + "schema": { + "type": "string" + }, + "description": "Format placeholder from client literal rooms/{0}/subrooms/{1}/modify" + }, + { + "name": "p1", + "in": "path", + "required": true, + "schema": { + "type": "string" + }, + "description": "Format placeholder from client literal rooms/{0}/subrooms/{1}/modify" + } + ], + "responses": { + "200": { + "description": "Room detail object or mutation success.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Room" + } + } + } + }, + "204": { + "description": "Success with no body, used by some mutation endpoints." + }, + "400": { + "description": "Client-visible validation or bad request error.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "401": { + "description": "Authentication required or token invalid.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "500": { + "description": "Server error shape tolerated by client wrappers.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + } + }, + "x-recroom-client-route-literal": "rooms/{0}/subrooms/{1}/modify", + "x-recroom-client-family": "rooms", + "x-recroom-client-service": "rooms/api", + "x-recroom-builds": [ + "2020.12.18" + ], + "x-recroom-inferred-http-method": "post", + "x-recroom-evidence": [ + { + "type": "EJDCNGBEICB", + "method": "RecRoom.Async.IPromise`1 JGMMJMHLHBO(System.Int64 HNHLJONGKHB, System.Int64 FODGKNJIGOP, System.String MMBOKOLAJFH, DPLPMKMFMPB PONCIIJOHIE, System.Int32 FFAGIDMFPIF)", + "file": "EJDCNGBEICB.txt", + "line": 6443 + } + ], + "requestBody": { + "required": false, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/GenericObject" + } + }, + "application/x-www-form-urlencoded": { + "schema": { + "$ref": "#/components/schemas/GenericObject" + } + }, + "multipart/form-data": { + "schema": { + "$ref": "#/components/schemas/GenericObject" + } + } + }, + "description": "Request body inferred. Exact DTO may be named in x-recroom-evidence method signature or companion DTO inventory." + }, + "x-client-response-contracts": [ + { + "kind": "object", + "raw": "PPENFJMFPNE", + "rootType": "PPENFJMFPNE", + "typeDescription": { + "name": "PPENFJMFPNE", + "found": true, + "file": "PPENFJMFPNE.cs", + "declaration": "public class PPENFJMFPNE : KLCOGEIGEBJ", + "kind": "class", + "baseType": "KLCOGEIGEBJ", + "properties": [ + { + "name": "ADCDBMGNIPB", + "type": "List", + "accessibility": "public", + "kind": "property" + }, + { + "name": "BIODFICLKJH", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "DFKGKGIMEMP", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "EJGIMHADHBC", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "FEBCCOFBKKJ", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "IAJDFKODGFL", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "KFFBPMHGFKB", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "LHGHLGDINFL", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "LOJPLANGFMG", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "MMBJPGGOFHL", + "type": "LMLJHMJEIGM", + "accessibility": "public", + "kind": "property" + }, + { + "name": "MMFBPKOOLNG", + "type": "bool", + "accessibility": "public", + "kind": "property" + }, + { + "name": "OHNIGJECGKL", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "PKKADKGDHNI", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + } + ], + "fields": [ + { + "name": "accountId", + "type": "int", + "kind": "field" + }, + { + "name": "tag", + "type": "string", + "kind": "field" + }, + { + "name": "IGAPDHGFLDC", + "type": "PPENFJMFPNE", + "kind": "field" + } + ], + "enumValues": [], + "wireKeys": [], + "isilFile": "PPENFJMFPNE.txt", + "isilType": "PPENFJMFPNE", + "inherits": "KLCOGEIGEBJ", + "inheritedMembers": [ + { + "name": "DADOKMAOFJL", + "type": "long", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "KLCOGEIGEBJ" + }, + { + "name": "GJPGKJJCPBK", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "KLCOGEIGEBJ" + }, + { + "name": "IBLOJJBEKFF", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "KLCOGEIGEBJ" + }, + { + "name": "JCLCOCEOAEP", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "KLCOGEIGEBJ" + }, + { + "name": "JGJCBGEBLLO", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "KLCOGEIGEBJ" + }, + { + "name": "PLFEEIJCHOH", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "KLCOGEIGEBJ" + }, + { + "name": "BJFNPKFOALK", + "type": "long", + "kind": "field", + "inheritedFrom": "KLCOGEIGEBJ" + }, + { + "name": "ACBFDMLHFPB", + "type": "DateTime", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "AHGCOGFEEEE", + "type": "string", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "AKFBNELAMNA", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "BADIGBCKECA", + "type": "int", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "BNBLOBAEDEE", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "CDINMMPNAID", + "type": "NMJEKMMBDDE", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "CDNFGMHLDMJ", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "FIKEBGGCDFN", + "type": "string", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "GIBHIMGJNNO", + "type": "GPDIAKNEBKH", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "HPLBOMGACED", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "IGOPGMHHLKI", + "type": "string", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "JFEAPMIPNEP", + "type": "DPLPMKMFMPB", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "KHIJAFCHLIA", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "KLNJBBPNMBJ", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "KODBEJPEFOJ", + "type": "string", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "LPJLEMJFBPE", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "MGBDHBHCDMH", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "MIKDLDEALPN", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "OFONEIOEIED", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "OILEJFNPDDB", + "type": "HJPGEGENLPH", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "PEEFHKMOMKK", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + } + ], + "inheritedWireKeys": [ + "RoomId", + "IsDorm", + "CloningAllowed", + "DisableMicAutoMute", + "DisableRoomComments", + "EncryptVoiceChat" + ] + } + } + ] + } + }, + "/rooms/{p0}/subrooms/{p1}/move": { + "post": { + "tags": [ + "rooms" + ], + "summary": "POST rooms/{0}/subrooms/{1}/move", + "description": "Inferred from Rec Room 2020 client literal `rooms/{0}/subrooms/{1}/move`. Expected result: Room detail object or mutation success.", + "operationId": "client_rooms_rooms_p0_subrooms_p1_move_post", + "parameters": [ + { + "name": "p0", + "in": "path", + "required": true, + "schema": { + "type": "string" + }, + "description": "Format placeholder from client literal rooms/{0}/subrooms/{1}/move" + }, + { + "name": "p1", + "in": "path", + "required": true, + "schema": { + "type": "string" + }, + "description": "Format placeholder from client literal rooms/{0}/subrooms/{1}/move" + } + ], + "responses": { + "200": { + "description": "Room detail object or mutation success.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Room" + } + } + } + }, + "204": { + "description": "Success with no body, used by some mutation endpoints." + }, + "400": { + "description": "Client-visible validation or bad request error.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "401": { + "description": "Authentication required or token invalid.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "500": { + "description": "Server error shape tolerated by client wrappers.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + } + }, + "x-recroom-client-route-literal": "rooms/{0}/subrooms/{1}/move", + "x-recroom-client-family": "rooms", + "x-recroom-client-service": "rooms/api", + "x-recroom-builds": [ + "2020.12.18" + ], + "x-recroom-inferred-http-method": "post", + "x-recroom-evidence": [ + { + "type": "EJDCNGBEICB", + "method": "RecRoom.Async.IPromise`1 OHHAFAIEFHP(System.Int64 HNHLJONGKHB, System.Int64 FODGKNJIGOP, System.Nullable`1 PAJMLPFMBEJ)", + "file": "EJDCNGBEICB.txt", + "line": 5709 + } + ], + "requestBody": { + "required": false, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/GenericObject" + } + }, + "application/x-www-form-urlencoded": { + "schema": { + "$ref": "#/components/schemas/GenericObject" + } + }, + "multipart/form-data": { + "schema": { + "$ref": "#/components/schemas/GenericObject" + } + } + }, + "description": "Request body inferred. Exact DTO may be named in x-recroom-evidence method signature or companion DTO inventory." + }, + "x-client-response-contracts": [ + { + "kind": "object", + "raw": "PPENFJMFPNE", + "rootType": "PPENFJMFPNE", + "typeDescription": { + "name": "PPENFJMFPNE", + "found": true, + "file": "PPENFJMFPNE.cs", + "declaration": "public class PPENFJMFPNE : KLCOGEIGEBJ", + "kind": "class", + "baseType": "KLCOGEIGEBJ", + "properties": [ + { + "name": "ADCDBMGNIPB", + "type": "List", + "accessibility": "public", + "kind": "property" + }, + { + "name": "BIODFICLKJH", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "DFKGKGIMEMP", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "EJGIMHADHBC", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "FEBCCOFBKKJ", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "IAJDFKODGFL", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "KFFBPMHGFKB", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "LHGHLGDINFL", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "LOJPLANGFMG", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "MMBJPGGOFHL", + "type": "LMLJHMJEIGM", + "accessibility": "public", + "kind": "property" + }, + { + "name": "MMFBPKOOLNG", + "type": "bool", + "accessibility": "public", + "kind": "property" + }, + { + "name": "OHNIGJECGKL", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "PKKADKGDHNI", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + } + ], + "fields": [ + { + "name": "accountId", + "type": "int", + "kind": "field" + }, + { + "name": "tag", + "type": "string", + "kind": "field" + }, + { + "name": "IGAPDHGFLDC", + "type": "PPENFJMFPNE", + "kind": "field" + } + ], + "enumValues": [], + "wireKeys": [], + "isilFile": "PPENFJMFPNE.txt", + "isilType": "PPENFJMFPNE", + "inherits": "KLCOGEIGEBJ", + "inheritedMembers": [ + { + "name": "DADOKMAOFJL", + "type": "long", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "KLCOGEIGEBJ" + }, + { + "name": "GJPGKJJCPBK", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "KLCOGEIGEBJ" + }, + { + "name": "IBLOJJBEKFF", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "KLCOGEIGEBJ" + }, + { + "name": "JCLCOCEOAEP", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "KLCOGEIGEBJ" + }, + { + "name": "JGJCBGEBLLO", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "KLCOGEIGEBJ" + }, + { + "name": "PLFEEIJCHOH", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "KLCOGEIGEBJ" + }, + { + "name": "BJFNPKFOALK", + "type": "long", + "kind": "field", + "inheritedFrom": "KLCOGEIGEBJ" + }, + { + "name": "ACBFDMLHFPB", + "type": "DateTime", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "AHGCOGFEEEE", + "type": "string", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "AKFBNELAMNA", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "BADIGBCKECA", + "type": "int", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "BNBLOBAEDEE", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "CDINMMPNAID", + "type": "NMJEKMMBDDE", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "CDNFGMHLDMJ", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "FIKEBGGCDFN", + "type": "string", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "GIBHIMGJNNO", + "type": "GPDIAKNEBKH", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "HPLBOMGACED", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "IGOPGMHHLKI", + "type": "string", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "JFEAPMIPNEP", + "type": "DPLPMKMFMPB", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "KHIJAFCHLIA", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "KLNJBBPNMBJ", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "KODBEJPEFOJ", + "type": "string", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "LPJLEMJFBPE", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "MGBDHBHCDMH", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "MIKDLDEALPN", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "OFONEIOEIED", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "OILEJFNPDDB", + "type": "HJPGEGENLPH", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "PEEFHKMOMKK", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + } + ], + "inheritedWireKeys": [ + "RoomId", + "IsDorm", + "CloningAllowed", + "DisableMicAutoMute", + "DisableRoomComments", + "EncryptVoiceChat" + ] + } + } + ] + } + }, + "/rooms/{p0}/subrooms/{p1}/name": { + "post": { + "tags": [ + "rooms" + ], + "summary": "POST rooms/{0}/subrooms/{1}/name", + "description": "Inferred from Rec Room 2020 client literal `rooms/{0}/subrooms/{1}/name`. Expected result: Room detail object or mutation success.", + "operationId": "client_rooms_rooms_p0_subrooms_p1_name_post", + "parameters": [ + { + "name": "p0", + "in": "path", + "required": true, + "schema": { + "type": "string" + }, + "description": "Format placeholder from client literal rooms/{0}/subrooms/{1}/name" + }, + { + "name": "p1", + "in": "path", + "required": true, + "schema": { + "type": "string" + }, + "description": "Format placeholder from client literal rooms/{0}/subrooms/{1}/name" + } + ], + "responses": { + "200": { + "description": "Room detail object or mutation success.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Room" + } + } + } + }, + "204": { + "description": "Success with no body, used by some mutation endpoints." + }, + "400": { + "description": "Client-visible validation or bad request error.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "401": { + "description": "Authentication required or token invalid.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "500": { + "description": "Server error shape tolerated by client wrappers.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + } + }, + "x-recroom-client-route-literal": "rooms/{0}/subrooms/{1}/name", + "x-recroom-client-family": "rooms", + "x-recroom-client-service": "rooms/api", + "x-recroom-builds": [ + "2020.12.18" + ], + "x-recroom-inferred-http-method": "post", + "x-recroom-evidence": [ + { + "type": "EJDCNGBEICB", + "method": "RecRoom.Async.IPromise`1 PAENPJCDLNJ(System.Int64 HNHLJONGKHB, System.Int64 FODGKNJIGOP, System.String MMBOKOLAJFH)", + "file": "EJDCNGBEICB.txt", + "line": 5099 + } + ], + "requestBody": { + "required": false, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/GenericObject" + } + }, + "application/x-www-form-urlencoded": { + "schema": { + "$ref": "#/components/schemas/GenericObject" + } + }, + "multipart/form-data": { + "schema": { + "$ref": "#/components/schemas/GenericObject" + } + } + }, + "description": "Request body inferred. Exact DTO may be named in x-recroom-evidence method signature or companion DTO inventory." + }, + "x-client-response-contracts": [ + { + "kind": "object", + "raw": "PPENFJMFPNE", + "rootType": "PPENFJMFPNE", + "typeDescription": { + "name": "PPENFJMFPNE", + "found": true, + "file": "PPENFJMFPNE.cs", + "declaration": "public class PPENFJMFPNE : KLCOGEIGEBJ", + "kind": "class", + "baseType": "KLCOGEIGEBJ", + "properties": [ + { + "name": "ADCDBMGNIPB", + "type": "List", + "accessibility": "public", + "kind": "property" + }, + { + "name": "BIODFICLKJH", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "DFKGKGIMEMP", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "EJGIMHADHBC", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "FEBCCOFBKKJ", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "IAJDFKODGFL", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "KFFBPMHGFKB", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "LHGHLGDINFL", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "LOJPLANGFMG", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "MMBJPGGOFHL", + "type": "LMLJHMJEIGM", + "accessibility": "public", + "kind": "property" + }, + { + "name": "MMFBPKOOLNG", + "type": "bool", + "accessibility": "public", + "kind": "property" + }, + { + "name": "OHNIGJECGKL", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "PKKADKGDHNI", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + } + ], + "fields": [ + { + "name": "accountId", + "type": "int", + "kind": "field" + }, + { + "name": "tag", + "type": "string", + "kind": "field" + }, + { + "name": "IGAPDHGFLDC", + "type": "PPENFJMFPNE", + "kind": "field" + } + ], + "enumValues": [], + "wireKeys": [], + "isilFile": "PPENFJMFPNE.txt", + "isilType": "PPENFJMFPNE", + "inherits": "KLCOGEIGEBJ", + "inheritedMembers": [ + { + "name": "DADOKMAOFJL", + "type": "long", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "KLCOGEIGEBJ" + }, + { + "name": "GJPGKJJCPBK", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "KLCOGEIGEBJ" + }, + { + "name": "IBLOJJBEKFF", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "KLCOGEIGEBJ" + }, + { + "name": "JCLCOCEOAEP", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "KLCOGEIGEBJ" + }, + { + "name": "JGJCBGEBLLO", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "KLCOGEIGEBJ" + }, + { + "name": "PLFEEIJCHOH", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "KLCOGEIGEBJ" + }, + { + "name": "BJFNPKFOALK", + "type": "long", + "kind": "field", + "inheritedFrom": "KLCOGEIGEBJ" + }, + { + "name": "ACBFDMLHFPB", + "type": "DateTime", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "AHGCOGFEEEE", + "type": "string", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "AKFBNELAMNA", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "BADIGBCKECA", + "type": "int", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "BNBLOBAEDEE", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "CDINMMPNAID", + "type": "NMJEKMMBDDE", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "CDNFGMHLDMJ", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "FIKEBGGCDFN", + "type": "string", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "GIBHIMGJNNO", + "type": "GPDIAKNEBKH", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "HPLBOMGACED", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "IGOPGMHHLKI", + "type": "string", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "JFEAPMIPNEP", + "type": "DPLPMKMFMPB", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "KHIJAFCHLIA", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "KLNJBBPNMBJ", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "KODBEJPEFOJ", + "type": "string", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "LPJLEMJFBPE", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "MGBDHBHCDMH", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "MIKDLDEALPN", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "OFONEIOEIED", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "OILEJFNPDDB", + "type": "HJPGEGENLPH", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "PEEFHKMOMKK", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + } + ], + "inheritedWireKeys": [ + "RoomId", + "IsDorm", + "CloningAllowed", + "DisableMicAutoMute", + "DisableRoomComments", + "EncryptVoiceChat" + ] + } + } + ] + } + }, + "/rooms/{p0}/subrooms/{p1}/restoredata": { + "post": { + "tags": [ + "rooms" + ], + "summary": "POST rooms/{0}/subrooms/{1}/restoredata", + "description": "Inferred from Rec Room 2020 client literal `rooms/{0}/subrooms/{1}/restoredata`. Expected result: Room data descriptor; binary blob must be fetched separately from CDN/storage.", + "operationId": "client_rooms_rooms_p0_subrooms_p1_restoredata_post", + "parameters": [ + { + "name": "p0", + "in": "path", + "required": true, + "schema": { + "type": "string" + }, + "description": "Format placeholder from client literal rooms/{0}/subrooms/{1}/restoredata" + }, + { + "name": "p1", + "in": "path", + "required": true, + "schema": { + "type": "string" + }, + "description": "Format placeholder from client literal rooms/{0}/subrooms/{1}/restoredata" + } + ], + "responses": { + "200": { + "description": "Room data descriptor; binary blob must be fetched separately from CDN/storage.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Room" + } + } + } + }, + "204": { + "description": "Success with no body, used by some mutation endpoints." + }, + "400": { + "description": "Client-visible validation or bad request error.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "401": { + "description": "Authentication required or token invalid.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "500": { + "description": "Server error shape tolerated by client wrappers.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + } + }, + "x-recroom-client-route-literal": "rooms/{0}/subrooms/{1}/restoredata", + "x-recroom-client-family": "rooms", + "x-recroom-client-service": "rooms/api", + "x-recroom-builds": [ + "2020.12.18" + ], + "x-recroom-inferred-http-method": "post", + "x-recroom-evidence": [ + { + "type": "EJDCNGBEICB", + "method": "RecRoom.Async.IPromise`1 BBGBOCGJFOP(System.Int64 HNHLJONGKHB, System.Int64 FODGKNJIGOP, System.String MEIIMAIGBJD)", + "file": "EJDCNGBEICB.txt", + "line": 6133 + } + ], + "requestBody": { + "required": false, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/GenericObject" + } + }, + "application/x-www-form-urlencoded": { + "schema": { + "$ref": "#/components/schemas/GenericObject" + } + }, + "multipart/form-data": { + "schema": { + "$ref": "#/components/schemas/GenericObject" + } + } + }, + "description": "Request body inferred. Exact DTO may be named in x-recroom-evidence method signature or companion DTO inventory." + }, + "x-client-response-contracts": [ + { + "kind": "object", + "raw": "PPENFJMFPNE", + "rootType": "PPENFJMFPNE", + "typeDescription": { + "name": "PPENFJMFPNE", + "found": true, + "file": "PPENFJMFPNE.cs", + "declaration": "public class PPENFJMFPNE : KLCOGEIGEBJ", + "kind": "class", + "baseType": "KLCOGEIGEBJ", + "properties": [ + { + "name": "ADCDBMGNIPB", + "type": "List", + "accessibility": "public", + "kind": "property" + }, + { + "name": "BIODFICLKJH", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "DFKGKGIMEMP", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "EJGIMHADHBC", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "FEBCCOFBKKJ", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "IAJDFKODGFL", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "KFFBPMHGFKB", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "LHGHLGDINFL", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "LOJPLANGFMG", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "MMBJPGGOFHL", + "type": "LMLJHMJEIGM", + "accessibility": "public", + "kind": "property" + }, + { + "name": "MMFBPKOOLNG", + "type": "bool", + "accessibility": "public", + "kind": "property" + }, + { + "name": "OHNIGJECGKL", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "PKKADKGDHNI", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + } + ], + "fields": [ + { + "name": "accountId", + "type": "int", + "kind": "field" + }, + { + "name": "tag", + "type": "string", + "kind": "field" + }, + { + "name": "IGAPDHGFLDC", + "type": "PPENFJMFPNE", + "kind": "field" + } + ], + "enumValues": [], + "wireKeys": [], + "isilFile": "PPENFJMFPNE.txt", + "isilType": "PPENFJMFPNE", + "inherits": "KLCOGEIGEBJ", + "inheritedMembers": [ + { + "name": "DADOKMAOFJL", + "type": "long", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "KLCOGEIGEBJ" + }, + { + "name": "GJPGKJJCPBK", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "KLCOGEIGEBJ" + }, + { + "name": "IBLOJJBEKFF", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "KLCOGEIGEBJ" + }, + { + "name": "JCLCOCEOAEP", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "KLCOGEIGEBJ" + }, + { + "name": "JGJCBGEBLLO", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "KLCOGEIGEBJ" + }, + { + "name": "PLFEEIJCHOH", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "KLCOGEIGEBJ" + }, + { + "name": "BJFNPKFOALK", + "type": "long", + "kind": "field", + "inheritedFrom": "KLCOGEIGEBJ" + }, + { + "name": "ACBFDMLHFPB", + "type": "DateTime", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "AHGCOGFEEEE", + "type": "string", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "AKFBNELAMNA", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "BADIGBCKECA", + "type": "int", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "BNBLOBAEDEE", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "CDINMMPNAID", + "type": "NMJEKMMBDDE", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "CDNFGMHLDMJ", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "FIKEBGGCDFN", + "type": "string", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "GIBHIMGJNNO", + "type": "GPDIAKNEBKH", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "HPLBOMGACED", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "IGOPGMHHLKI", + "type": "string", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "JFEAPMIPNEP", + "type": "DPLPMKMFMPB", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "KHIJAFCHLIA", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "KLNJBBPNMBJ", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "KODBEJPEFOJ", + "type": "string", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "LPJLEMJFBPE", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "MGBDHBHCDMH", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "MIKDLDEALPN", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "OFONEIOEIED", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "OILEJFNPDDB", + "type": "HJPGEGENLPH", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "PEEFHKMOMKK", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + } + ], + "inheritedWireKeys": [ + "RoomId", + "IsDorm", + "CloningAllowed", + "DisableMicAutoMute", + "DisableRoomComments", + "EncryptVoiceChat" + ] + } + } + ] + } + }, + "/rooms/{p0}/subrooms/{p1}": { + "get": { + "tags": [ + "rooms" + ], + "summary": "GET rooms/{0}/subrooms/{1}", + "description": "Inferred from Rec Room 2020 client literal `rooms/{0}/subrooms/{1}`. Expected result: Room detail object or mutation success.", + "operationId": "client_rooms_rooms_p0_subrooms_p1_get", + "parameters": [ + { + "name": "p0", + "in": "path", + "required": true, + "schema": { + "type": "string" + }, + "description": "Format placeholder from client literal rooms/{0}/subrooms/{1}" + }, + { + "name": "p1", + "in": "path", + "required": true, + "schema": { + "type": "string" + }, + "description": "Format placeholder from client literal rooms/{0}/subrooms/{1}" + } + ], + "responses": { + "200": { + "description": "Room detail object or mutation success.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Room" + } + } + } + }, + "204": { + "description": "Success with no body, used by some mutation endpoints." + }, + "400": { + "description": "Client-visible validation or bad request error.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "401": { + "description": "Authentication required or token invalid.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "500": { + "description": "Server error shape tolerated by client wrappers.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + } + }, + "x-recroom-client-route-literal": "rooms/{0}/subrooms/{1}", + "x-recroom-client-family": "rooms", + "x-recroom-client-service": "rooms/api", + "x-recroom-builds": [ + "2020.12.18" + ], + "x-recroom-inferred-http-method": "get", + "x-recroom-evidence": [ + { + "type": "EJDCNGBEICB", + "method": "RecRoom.Async.IPromise`1 GGCOGNAGPBP(System.Int64 HNHLJONGKHB, System.Int64 FODGKNJIGOP)", + "file": "EJDCNGBEICB.txt", + "line": 5809 + } + ], + "x-client-response-contracts": [ + { + "kind": "object", + "raw": "PPENFJMFPNE", + "rootType": "PPENFJMFPNE", + "typeDescription": { + "name": "PPENFJMFPNE", + "found": true, + "file": "PPENFJMFPNE.cs", + "declaration": "public class PPENFJMFPNE : KLCOGEIGEBJ", + "kind": "class", + "baseType": "KLCOGEIGEBJ", + "properties": [ + { + "name": "ADCDBMGNIPB", + "type": "List", + "accessibility": "public", + "kind": "property" + }, + { + "name": "BIODFICLKJH", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "DFKGKGIMEMP", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "EJGIMHADHBC", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "FEBCCOFBKKJ", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "IAJDFKODGFL", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "KFFBPMHGFKB", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "LHGHLGDINFL", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "LOJPLANGFMG", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "MMBJPGGOFHL", + "type": "LMLJHMJEIGM", + "accessibility": "public", + "kind": "property" + }, + { + "name": "MMFBPKOOLNG", + "type": "bool", + "accessibility": "public", + "kind": "property" + }, + { + "name": "OHNIGJECGKL", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "PKKADKGDHNI", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + } + ], + "fields": [ + { + "name": "accountId", + "type": "int", + "kind": "field" + }, + { + "name": "tag", + "type": "string", + "kind": "field" + }, + { + "name": "IGAPDHGFLDC", + "type": "PPENFJMFPNE", + "kind": "field" + } + ], + "enumValues": [], + "wireKeys": [], + "isilFile": "PPENFJMFPNE.txt", + "isilType": "PPENFJMFPNE", + "inherits": "KLCOGEIGEBJ", + "inheritedMembers": [ + { + "name": "DADOKMAOFJL", + "type": "long", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "KLCOGEIGEBJ" + }, + { + "name": "GJPGKJJCPBK", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "KLCOGEIGEBJ" + }, + { + "name": "IBLOJJBEKFF", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "KLCOGEIGEBJ" + }, + { + "name": "JCLCOCEOAEP", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "KLCOGEIGEBJ" + }, + { + "name": "JGJCBGEBLLO", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "KLCOGEIGEBJ" + }, + { + "name": "PLFEEIJCHOH", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "KLCOGEIGEBJ" + }, + { + "name": "BJFNPKFOALK", + "type": "long", + "kind": "field", + "inheritedFrom": "KLCOGEIGEBJ" + }, + { + "name": "ACBFDMLHFPB", + "type": "DateTime", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "AHGCOGFEEEE", + "type": "string", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "AKFBNELAMNA", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "BADIGBCKECA", + "type": "int", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "BNBLOBAEDEE", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "CDINMMPNAID", + "type": "NMJEKMMBDDE", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "CDNFGMHLDMJ", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "FIKEBGGCDFN", + "type": "string", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "GIBHIMGJNNO", + "type": "GPDIAKNEBKH", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "HPLBOMGACED", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "IGOPGMHHLKI", + "type": "string", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "JFEAPMIPNEP", + "type": "DPLPMKMFMPB", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "KHIJAFCHLIA", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "KLNJBBPNMBJ", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "KODBEJPEFOJ", + "type": "string", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "LPJLEMJFBPE", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "MGBDHBHCDMH", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "MIKDLDEALPN", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "OFONEIOEIED", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "OILEJFNPDDB", + "type": "HJPGEGENLPH", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "PEEFHKMOMKK", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + } + ], + "inheritedWireKeys": [ + "RoomId", + "IsDorm", + "CloningAllowed", + "DisableMicAutoMute", + "DisableRoomComments", + "EncryptVoiceChat" + ] + } + } + ] + } + }, + "/rooms/{p0}/subrooms": { + "get": { + "tags": [ + "rooms" + ], + "summary": "GET rooms/{0}/subrooms", + "description": "Inferred from Rec Room 2020 client literal `rooms/{0}/subrooms`. Expected result: Room detail object or mutation success.", + "operationId": "client_rooms_rooms_p0_subrooms_get", + "parameters": [ + { + "name": "p0", + "in": "path", + "required": true, + "schema": { + "type": "string" + }, + "description": "Format placeholder from client literal rooms/{0}/subrooms" + } + ], + "responses": { + "200": { + "description": "Room detail object or mutation success.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Room" + } + } + } + }, + "204": { + "description": "Success with no body, used by some mutation endpoints." + }, + "400": { + "description": "Client-visible validation or bad request error.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "401": { + "description": "Authentication required or token invalid.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "500": { + "description": "Server error shape tolerated by client wrappers.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + } + }, + "x-recroom-client-route-literal": "rooms/{0}/subrooms", + "x-recroom-client-family": "rooms", + "x-recroom-client-service": "rooms/api", + "x-recroom-builds": [ + "2020.12.18" + ], + "x-recroom-inferred-http-method": "get", + "x-recroom-evidence": [ + { + "type": "EJDCNGBEICB", + "method": "RecRoom.Async.IPromise`1 OGNIELCNHIM(System.Int64 HNHLJONGKHB, System.String MMBOKOLAJFH)", + "file": "EJDCNGBEICB.txt", + "line": 5487 + } + ], + "x-client-response-contracts": [ + { + "kind": "object", + "raw": "PPENFJMFPNE", + "rootType": "PPENFJMFPNE", + "typeDescription": { + "name": "PPENFJMFPNE", + "found": true, + "file": "PPENFJMFPNE.cs", + "declaration": "public class PPENFJMFPNE : KLCOGEIGEBJ", + "kind": "class", + "baseType": "KLCOGEIGEBJ", + "properties": [ + { + "name": "ADCDBMGNIPB", + "type": "List", + "accessibility": "public", + "kind": "property" + }, + { + "name": "BIODFICLKJH", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "DFKGKGIMEMP", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "EJGIMHADHBC", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "FEBCCOFBKKJ", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "IAJDFKODGFL", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "KFFBPMHGFKB", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "LHGHLGDINFL", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "LOJPLANGFMG", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "MMBJPGGOFHL", + "type": "LMLJHMJEIGM", + "accessibility": "public", + "kind": "property" + }, + { + "name": "MMFBPKOOLNG", + "type": "bool", + "accessibility": "public", + "kind": "property" + }, + { + "name": "OHNIGJECGKL", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "PKKADKGDHNI", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + } + ], + "fields": [ + { + "name": "accountId", + "type": "int", + "kind": "field" + }, + { + "name": "tag", + "type": "string", + "kind": "field" + }, + { + "name": "IGAPDHGFLDC", + "type": "PPENFJMFPNE", + "kind": "field" + } + ], + "enumValues": [], + "wireKeys": [], + "isilFile": "PPENFJMFPNE.txt", + "isilType": "PPENFJMFPNE", + "inherits": "KLCOGEIGEBJ", + "inheritedMembers": [ + { + "name": "DADOKMAOFJL", + "type": "long", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "KLCOGEIGEBJ" + }, + { + "name": "GJPGKJJCPBK", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "KLCOGEIGEBJ" + }, + { + "name": "IBLOJJBEKFF", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "KLCOGEIGEBJ" + }, + { + "name": "JCLCOCEOAEP", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "KLCOGEIGEBJ" + }, + { + "name": "JGJCBGEBLLO", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "KLCOGEIGEBJ" + }, + { + "name": "PLFEEIJCHOH", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "KLCOGEIGEBJ" + }, + { + "name": "BJFNPKFOALK", + "type": "long", + "kind": "field", + "inheritedFrom": "KLCOGEIGEBJ" + }, + { + "name": "ACBFDMLHFPB", + "type": "DateTime", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "AHGCOGFEEEE", + "type": "string", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "AKFBNELAMNA", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "BADIGBCKECA", + "type": "int", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "BNBLOBAEDEE", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "CDINMMPNAID", + "type": "NMJEKMMBDDE", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "CDNFGMHLDMJ", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "FIKEBGGCDFN", + "type": "string", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "GIBHIMGJNNO", + "type": "GPDIAKNEBKH", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "HPLBOMGACED", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "IGOPGMHHLKI", + "type": "string", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "JFEAPMIPNEP", + "type": "DPLPMKMFMPB", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "KHIJAFCHLIA", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "KLNJBBPNMBJ", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "KODBEJPEFOJ", + "type": "string", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "LPJLEMJFBPE", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "MGBDHBHCDMH", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "MIKDLDEALPN", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "OFONEIOEIED", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "OILEJFNPDDB", + "type": "HJPGEGENLPH", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "PEEFHKMOMKK", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + } + ], + "inheritedWireKeys": [ + "RoomId", + "IsDorm", + "CloningAllowed", + "DisableMicAutoMute", + "DisableRoomComments", + "EncryptVoiceChat" + ] + } + } + ] + } + }, + "/rooms/{p0}/tags": { + "post": { + "tags": [ + "rooms" + ], + "summary": "POST rooms/{0}/tags", + "description": "Inferred from Rec Room 2020 client literal `rooms/{0}/tags`. Expected result: Room detail object or mutation success.", + "operationId": "client_rooms_rooms_p0_tags_post", + "parameters": [ + { + "name": "p0", + "in": "path", + "required": true, + "schema": { + "type": "string" + }, + "description": "Format placeholder from client literal rooms/{0}/tags" + } + ], + "responses": { + "200": { + "description": "Room detail object or mutation success.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Room" + } + } + } + }, + "204": { + "description": "Success with no body, used by some mutation endpoints." + }, + "400": { + "description": "Client-visible validation or bad request error.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "401": { + "description": "Authentication required or token invalid.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "500": { + "description": "Server error shape tolerated by client wrappers.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + } + }, + "x-recroom-client-route-literal": "rooms/{0}/tags", + "x-recroom-client-family": "rooms", + "x-recroom-client-service": "rooms/api", + "x-recroom-builds": [ + "2020.12.18" + ], + "x-recroom-inferred-http-method": "post", + "x-recroom-evidence": [ + { + "type": "EJDCNGBEICB", + "method": "RecRoom.Async.IPromise`1 ENGFFJGIEGH(System.Int64 HNHLJONGKHB, System.Collections.Generic.IReadOnlyList`1 LBAOCHFCLPO, System.Collections.Generic.IReadOnlyList`1 CAFPJPHILMN)", + "file": "EJDCNGBEICB.txt", + "line": 3384 + } + ], + "requestBody": { + "required": false, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/GenericObject" + } + }, + "application/x-www-form-urlencoded": { + "schema": { + "$ref": "#/components/schemas/GenericObject" + } + }, + "multipart/form-data": { + "schema": { + "$ref": "#/components/schemas/GenericObject" + } + } + }, + "description": "Request body inferred. Exact DTO may be named in x-recroom-evidence method signature or companion DTO inventory." + }, + "x-client-response-contracts": [ + { + "kind": "object", + "raw": "PPENFJMFPNE", + "rootType": "PPENFJMFPNE", + "typeDescription": { + "name": "PPENFJMFPNE", + "found": true, + "file": "PPENFJMFPNE.cs", + "declaration": "public class PPENFJMFPNE : KLCOGEIGEBJ", + "kind": "class", + "baseType": "KLCOGEIGEBJ", + "properties": [ + { + "name": "ADCDBMGNIPB", + "type": "List", + "accessibility": "public", + "kind": "property" + }, + { + "name": "BIODFICLKJH", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "DFKGKGIMEMP", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "EJGIMHADHBC", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "FEBCCOFBKKJ", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "IAJDFKODGFL", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "KFFBPMHGFKB", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "LHGHLGDINFL", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "LOJPLANGFMG", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "MMBJPGGOFHL", + "type": "LMLJHMJEIGM", + "accessibility": "public", + "kind": "property" + }, + { + "name": "MMFBPKOOLNG", + "type": "bool", + "accessibility": "public", + "kind": "property" + }, + { + "name": "OHNIGJECGKL", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "PKKADKGDHNI", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + } + ], + "fields": [ + { + "name": "accountId", + "type": "int", + "kind": "field" + }, + { + "name": "tag", + "type": "string", + "kind": "field" + }, + { + "name": "IGAPDHGFLDC", + "type": "PPENFJMFPNE", + "kind": "field" + } + ], + "enumValues": [], + "wireKeys": [], + "isilFile": "PPENFJMFPNE.txt", + "isilType": "PPENFJMFPNE", + "inherits": "KLCOGEIGEBJ", + "inheritedMembers": [ + { + "name": "DADOKMAOFJL", + "type": "long", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "KLCOGEIGEBJ" + }, + { + "name": "GJPGKJJCPBK", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "KLCOGEIGEBJ" + }, + { + "name": "IBLOJJBEKFF", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "KLCOGEIGEBJ" + }, + { + "name": "JCLCOCEOAEP", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "KLCOGEIGEBJ" + }, + { + "name": "JGJCBGEBLLO", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "KLCOGEIGEBJ" + }, + { + "name": "PLFEEIJCHOH", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "KLCOGEIGEBJ" + }, + { + "name": "BJFNPKFOALK", + "type": "long", + "kind": "field", + "inheritedFrom": "KLCOGEIGEBJ" + }, + { + "name": "ACBFDMLHFPB", + "type": "DateTime", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "AHGCOGFEEEE", + "type": "string", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "AKFBNELAMNA", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "BADIGBCKECA", + "type": "int", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "BNBLOBAEDEE", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "CDINMMPNAID", + "type": "NMJEKMMBDDE", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "CDNFGMHLDMJ", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "FIKEBGGCDFN", + "type": "string", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "GIBHIMGJNNO", + "type": "GPDIAKNEBKH", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "HPLBOMGACED", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "IGOPGMHHLKI", + "type": "string", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "JFEAPMIPNEP", + "type": "DPLPMKMFMPB", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "KHIJAFCHLIA", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "KLNJBBPNMBJ", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "KODBEJPEFOJ", + "type": "string", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "LPJLEMJFBPE", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "MGBDHBHCDMH", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "MIKDLDEALPN", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "OFONEIOEIED", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "OILEJFNPDDB", + "type": "HJPGEGENLPH", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "PEEFHKMOMKK", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + } + ], + "inheritedWireKeys": [ + "RoomId", + "IsDorm", + "CloningAllowed", + "DisableMicAutoMute", + "DisableRoomComments", + "EncryptVoiceChat" + ] + } + } + ] + } + }, + "/rooms/{p0}/voice_chat_encryption": { + "post": { + "tags": [ + "rooms" + ], + "summary": "POST rooms/{0}/voice_chat_encryption", + "description": "Inferred from Rec Room 2020 client literal `rooms/{0}/voice_chat_encryption`. Expected result: Room detail object or mutation success.", + "operationId": "client_rooms_rooms_p0_voice_chat_encryption_post", + "parameters": [ + { + "name": "p0", + "in": "path", + "required": true, + "schema": { + "type": "string" + }, + "description": "Format placeholder from client literal rooms/{0}/voice_chat_encryption" + } + ], + "responses": { + "200": { + "description": "Room detail object or mutation success.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Room" + } + } + } + }, + "204": { + "description": "Success with no body, used by some mutation endpoints." + }, + "400": { + "description": "Client-visible validation or bad request error.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "401": { + "description": "Authentication required or token invalid.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "500": { + "description": "Server error shape tolerated by client wrappers.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + } + }, + "x-recroom-client-route-literal": "rooms/{0}/voice_chat_encryption", + "x-recroom-client-family": "rooms", + "x-recroom-client-service": "rooms/api", + "x-recroom-builds": [ + "2020.12.18" + ], + "x-recroom-inferred-http-method": "post", + "x-recroom-evidence": [ + { + "type": "EJDCNGBEICB", + "method": "RecRoom.Async.IPromise`1 PKDCMMFILPB(System.Int64 HNHLJONGKHB, System.Boolean NFJINCPDPDG)", + "file": "EJDCNGBEICB.txt", + "line": 4270 + } + ], + "requestBody": { + "required": false, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/GenericObject" + } + }, + "application/x-www-form-urlencoded": { + "schema": { + "$ref": "#/components/schemas/GenericObject" + } + }, + "multipart/form-data": { + "schema": { + "$ref": "#/components/schemas/GenericObject" + } + } + }, + "description": "Request body inferred. Exact DTO may be named in x-recroom-evidence method signature or companion DTO inventory." + }, + "x-client-response-contracts": [ + { + "kind": "object", + "raw": "PPENFJMFPNE", + "rootType": "PPENFJMFPNE", + "typeDescription": { + "name": "PPENFJMFPNE", + "found": true, + "file": "PPENFJMFPNE.cs", + "declaration": "public class PPENFJMFPNE : KLCOGEIGEBJ", + "kind": "class", + "baseType": "KLCOGEIGEBJ", + "properties": [ + { + "name": "ADCDBMGNIPB", + "type": "List", + "accessibility": "public", + "kind": "property" + }, + { + "name": "BIODFICLKJH", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "DFKGKGIMEMP", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "EJGIMHADHBC", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "FEBCCOFBKKJ", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "IAJDFKODGFL", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "KFFBPMHGFKB", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "LHGHLGDINFL", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "LOJPLANGFMG", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "MMBJPGGOFHL", + "type": "LMLJHMJEIGM", + "accessibility": "public", + "kind": "property" + }, + { + "name": "MMFBPKOOLNG", + "type": "bool", + "accessibility": "public", + "kind": "property" + }, + { + "name": "OHNIGJECGKL", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "PKKADKGDHNI", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + } + ], + "fields": [ + { + "name": "accountId", + "type": "int", + "kind": "field" + }, + { + "name": "tag", + "type": "string", + "kind": "field" + }, + { + "name": "IGAPDHGFLDC", + "type": "PPENFJMFPNE", + "kind": "field" + } + ], + "enumValues": [], + "wireKeys": [], + "isilFile": "PPENFJMFPNE.txt", + "isilType": "PPENFJMFPNE", + "inherits": "KLCOGEIGEBJ", + "inheritedMembers": [ + { + "name": "DADOKMAOFJL", + "type": "long", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "KLCOGEIGEBJ" + }, + { + "name": "GJPGKJJCPBK", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "KLCOGEIGEBJ" + }, + { + "name": "IBLOJJBEKFF", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "KLCOGEIGEBJ" + }, + { + "name": "JCLCOCEOAEP", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "KLCOGEIGEBJ" + }, + { + "name": "JGJCBGEBLLO", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "KLCOGEIGEBJ" + }, + { + "name": "PLFEEIJCHOH", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "KLCOGEIGEBJ" + }, + { + "name": "BJFNPKFOALK", + "type": "long", + "kind": "field", + "inheritedFrom": "KLCOGEIGEBJ" + }, + { + "name": "ACBFDMLHFPB", + "type": "DateTime", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "AHGCOGFEEEE", + "type": "string", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "AKFBNELAMNA", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "BADIGBCKECA", + "type": "int", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "BNBLOBAEDEE", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "CDINMMPNAID", + "type": "NMJEKMMBDDE", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "CDNFGMHLDMJ", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "FIKEBGGCDFN", + "type": "string", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "GIBHIMGJNNO", + "type": "GPDIAKNEBKH", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "HPLBOMGACED", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "IGOPGMHHLKI", + "type": "string", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "JFEAPMIPNEP", + "type": "DPLPMKMFMPB", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "KHIJAFCHLIA", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "KLNJBBPNMBJ", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "KODBEJPEFOJ", + "type": "string", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "LPJLEMJFBPE", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "MGBDHBHCDMH", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "MIKDLDEALPN", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "OFONEIOEIED", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "OILEJFNPDDB", + "type": "HJPGEGENLPH", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "PEEFHKMOMKK", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + } + ], + "inheritedWireKeys": [ + "RoomId", + "IsDorm", + "CloningAllowed", + "DisableMicAutoMute", + "DisableRoomComments", + "EncryptVoiceChat" + ] + } + } + ] + } + }, + "/rooms/{p0}/warning": { + "post": { + "tags": [ + "rooms" + ], + "summary": "POST rooms/{0}/warning", + "description": "Inferred from Rec Room 2020 client literal `rooms/{0}/warning`. Expected result: Room detail object or mutation success.", + "operationId": "client_rooms_rooms_p0_warning_post", + "parameters": [ + { + "name": "p0", + "in": "path", + "required": true, + "schema": { + "type": "string" + }, + "description": "Format placeholder from client literal rooms/{0}/warning" + } + ], + "responses": { + "200": { + "description": "Room detail object or mutation success.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Room" + } + } + } + }, + "204": { + "description": "Success with no body, used by some mutation endpoints." + }, + "400": { + "description": "Client-visible validation or bad request error.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "401": { + "description": "Authentication required or token invalid.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "500": { + "description": "Server error shape tolerated by client wrappers.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + } + }, + "x-recroom-client-route-literal": "rooms/{0}/warning", + "x-recroom-client-family": "rooms", + "x-recroom-client-service": "rooms/api", + "x-recroom-builds": [ + "2020.12.18" + ], + "x-recroom-inferred-http-method": "post", + "x-recroom-evidence": [ + { + "type": "EJDCNGBEICB", + "method": "RecRoom.Async.IPromise`1 NPACFDBCHDP(System.Int64 HNHLJONGKHB, GPDIAKNEBKH LEFPAAGGFFA, System.String NLEBEKDHJIJ)", + "file": "EJDCNGBEICB.txt", + "line": 3510 + } + ], + "requestBody": { + "required": false, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/GenericObject" + } + }, + "application/x-www-form-urlencoded": { + "schema": { + "$ref": "#/components/schemas/GenericObject" + } + }, + "multipart/form-data": { + "schema": { + "$ref": "#/components/schemas/GenericObject" + } + } + }, + "description": "Request body inferred. Exact DTO may be named in x-recroom-evidence method signature or companion DTO inventory." + }, + "x-client-response-contracts": [ + { + "kind": "object", + "raw": "PPENFJMFPNE", + "rootType": "PPENFJMFPNE", + "typeDescription": { + "name": "PPENFJMFPNE", + "found": true, + "file": "PPENFJMFPNE.cs", + "declaration": "public class PPENFJMFPNE : KLCOGEIGEBJ", + "kind": "class", + "baseType": "KLCOGEIGEBJ", + "properties": [ + { + "name": "ADCDBMGNIPB", + "type": "List", + "accessibility": "public", + "kind": "property" + }, + { + "name": "BIODFICLKJH", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "DFKGKGIMEMP", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "EJGIMHADHBC", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "FEBCCOFBKKJ", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "IAJDFKODGFL", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "KFFBPMHGFKB", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "LHGHLGDINFL", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "LOJPLANGFMG", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "MMBJPGGOFHL", + "type": "LMLJHMJEIGM", + "accessibility": "public", + "kind": "property" + }, + { + "name": "MMFBPKOOLNG", + "type": "bool", + "accessibility": "public", + "kind": "property" + }, + { + "name": "OHNIGJECGKL", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "PKKADKGDHNI", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + } + ], + "fields": [ + { + "name": "accountId", + "type": "int", + "kind": "field" + }, + { + "name": "tag", + "type": "string", + "kind": "field" + }, + { + "name": "IGAPDHGFLDC", + "type": "PPENFJMFPNE", + "kind": "field" + } + ], + "enumValues": [], + "wireKeys": [], + "isilFile": "PPENFJMFPNE.txt", + "isilType": "PPENFJMFPNE", + "inherits": "KLCOGEIGEBJ", + "inheritedMembers": [ + { + "name": "DADOKMAOFJL", + "type": "long", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "KLCOGEIGEBJ" + }, + { + "name": "GJPGKJJCPBK", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "KLCOGEIGEBJ" + }, + { + "name": "IBLOJJBEKFF", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "KLCOGEIGEBJ" + }, + { + "name": "JCLCOCEOAEP", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "KLCOGEIGEBJ" + }, + { + "name": "JGJCBGEBLLO", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "KLCOGEIGEBJ" + }, + { + "name": "PLFEEIJCHOH", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "KLCOGEIGEBJ" + }, + { + "name": "BJFNPKFOALK", + "type": "long", + "kind": "field", + "inheritedFrom": "KLCOGEIGEBJ" + }, + { + "name": "ACBFDMLHFPB", + "type": "DateTime", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "AHGCOGFEEEE", + "type": "string", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "AKFBNELAMNA", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "BADIGBCKECA", + "type": "int", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "BNBLOBAEDEE", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "CDINMMPNAID", + "type": "NMJEKMMBDDE", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "CDNFGMHLDMJ", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "FIKEBGGCDFN", + "type": "string", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "GIBHIMGJNNO", + "type": "GPDIAKNEBKH", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "HPLBOMGACED", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "IGOPGMHHLKI", + "type": "string", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "JFEAPMIPNEP", + "type": "DPLPMKMFMPB", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "KHIJAFCHLIA", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "KLNJBBPNMBJ", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "KODBEJPEFOJ", + "type": "string", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "LPJLEMJFBPE", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "MGBDHBHCDMH", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "MIKDLDEALPN", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "OFONEIOEIED", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "OILEJFNPDDB", + "type": "HJPGEGENLPH", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "PEEFHKMOMKK", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + } + ], + "inheritedWireKeys": [ + "RoomId", + "IsDorm", + "CloningAllowed", + "DisableMicAutoMute", + "DisableRoomComments", + "EncryptVoiceChat" + ] + } + } + ] + } + }, + "/rooms/{p0}": { + "get": { + "tags": [ + "rooms" + ], + "summary": "GET rooms/{0}", + "description": "Inferred from Rec Room 2020 client literal `rooms/{0}`. Expected result: Room detail object or mutation success.", + "operationId": "client_rooms_rooms_p0_get", + "parameters": [ + { + "name": "p0", + "in": "path", + "required": true, + "schema": { + "type": "string" + }, + "description": "Format placeholder from client literal rooms/{0}" + } + ], + "responses": { + "200": { + "description": "Room detail object or mutation success.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Room" + } + } + } + }, + "204": { + "description": "Success with no body, used by some mutation endpoints." + }, + "400": { + "description": "Client-visible validation or bad request error.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "401": { + "description": "Authentication required or token invalid.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "500": { + "description": "Server error shape tolerated by client wrappers.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + } + }, + "x-recroom-client-route-literal": "rooms/{0}", + "x-recroom-client-family": "rooms", + "x-recroom-client-service": "rooms/api", + "x-recroom-builds": [ + "2020.12.18" + ], + "x-recroom-inferred-http-method": "get", + "x-recroom-evidence": [ + { + "type": "EJDCNGBEICB", + "method": "RecRoom.Async.IPromise KHDPHIGPEEH(System.Int64 HNHLJONGKHB)", + "file": "EJDCNGBEICB.txt", + "line": 2923 + }, + { + "type": "EJDCNGBEICB", + "method": "RecRoom.Async.IPromise`1 NHBPIIGDAJP(System.Int64 HNHLJONGKHB)", + "file": "EJDCNGBEICB.txt", + "line": 82 + }, + { + "type": "EJDCNGBEICB", + "method": "RecRoom.Async.IPromise`1 CJKHNIIJFIN(System.Int64 HNHLJONGKHB)", + "file": "EJDCNGBEICB.txt", + "line": 615 + } + ], + "x-client-response-contracts": [ + { + "kind": "success-or-empty", + "raw": "RecRoom.Async.IPromise", + "rootType": "void/success", + "typeDescription": { + "name": "void/success", + "found": false, + "kind": "object", + "wireKeys": [] + } + }, + { + "kind": "object", + "raw": "KLCOGEIGEBJ", + "rootType": "KLCOGEIGEBJ", + "typeDescription": { + "name": "KLCOGEIGEBJ", + "found": true, + "file": "KLCOGEIGEBJ.cs", + "declaration": "public class KLCOGEIGEBJ : MKAMHOIHOJK", + "kind": "class", + "baseType": "MKAMHOIHOJK", + "properties": [ + { + "name": "DADOKMAOFJL", + "type": "long", + "accessibility": "public", + "kind": "property" + }, + { + "name": "GJPGKJJCPBK", + "type": "bool", + "accessibility": "public", + "kind": "property" + }, + { + "name": "IBLOJJBEKFF", + "type": "bool", + "accessibility": "public", + "kind": "property" + }, + { + "name": "JCLCOCEOAEP", + "type": "bool", + "accessibility": "public", + "kind": "property" + }, + { + "name": "JGJCBGEBLLO", + "type": "bool", + "accessibility": "public", + "kind": "property" + }, + { + "name": "PLFEEIJCHOH", + "type": "bool", + "accessibility": "public", + "kind": "property" + } + ], + "fields": [ + { + "name": "BJFNPKFOALK", + "type": "long", + "kind": "field" + } + ], + "enumValues": [], + "wireKeys": [ + "RoomId", + "IsDorm", + "CloningAllowed", + "DisableMicAutoMute", + "DisableRoomComments", + "EncryptVoiceChat" + ], + "isilFile": "KLCOGEIGEBJ.txt", + "isilType": "KLCOGEIGEBJ", + "inherits": "MKAMHOIHOJK", + "inheritedMembers": [ + { + "name": "ACBFDMLHFPB", + "type": "DateTime", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "AHGCOGFEEEE", + "type": "string", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "AKFBNELAMNA", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "BADIGBCKECA", + "type": "int", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "BNBLOBAEDEE", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "CDINMMPNAID", + "type": "NMJEKMMBDDE", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "CDNFGMHLDMJ", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "FIKEBGGCDFN", + "type": "string", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "GIBHIMGJNNO", + "type": "GPDIAKNEBKH", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "HPLBOMGACED", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "IGOPGMHHLKI", + "type": "string", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "JFEAPMIPNEP", + "type": "DPLPMKMFMPB", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "KHIJAFCHLIA", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "KLNJBBPNMBJ", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "KODBEJPEFOJ", + "type": "string", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "LPJLEMJFBPE", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "MGBDHBHCDMH", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "MIKDLDEALPN", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "OFONEIOEIED", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "OILEJFNPDDB", + "type": "HJPGEGENLPH", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "PEEFHKMOMKK", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + } + ], + "inheritedWireKeys": [ + "Name", + "Description", + "ImageName", + "WarningMask", + "CustomWarning", + "CreatorAccountId", + "State", + "Accessibility", + "SupportsLevelVoting", + "IsRRO", + "SupportsScreens", + "SupportsWalkVR", + "SupportsTeleportVR", + "SupportsVRLow", + "SupportsQuest2", + "SupportsMobile", + "SupportsJuniors", + "CreatedAt", + "Stats" + ] + } + }, + { + "kind": "object", + "raw": "PPENFJMFPNE", + "rootType": "PPENFJMFPNE", + "typeDescription": { + "name": "PPENFJMFPNE", + "found": true, + "file": "PPENFJMFPNE.cs", + "declaration": "public class PPENFJMFPNE : KLCOGEIGEBJ", + "kind": "class", + "baseType": "KLCOGEIGEBJ", + "properties": [ + { + "name": "ADCDBMGNIPB", + "type": "List", + "accessibility": "public", + "kind": "property" + }, + { + "name": "BIODFICLKJH", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "DFKGKGIMEMP", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "EJGIMHADHBC", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "FEBCCOFBKKJ", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "IAJDFKODGFL", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "KFFBPMHGFKB", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "LHGHLGDINFL", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "LOJPLANGFMG", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "MMBJPGGOFHL", + "type": "LMLJHMJEIGM", + "accessibility": "public", + "kind": "property" + }, + { + "name": "MMFBPKOOLNG", + "type": "bool", + "accessibility": "public", + "kind": "property" + }, + { + "name": "OHNIGJECGKL", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "PKKADKGDHNI", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + } + ], + "fields": [ + { + "name": "accountId", + "type": "int", + "kind": "field" + }, + { + "name": "tag", + "type": "string", + "kind": "field" + }, + { + "name": "IGAPDHGFLDC", + "type": "PPENFJMFPNE", + "kind": "field" + } + ], + "enumValues": [], + "wireKeys": [], + "isilFile": "PPENFJMFPNE.txt", + "isilType": "PPENFJMFPNE", + "inherits": "KLCOGEIGEBJ", + "inheritedMembers": [ + { + "name": "DADOKMAOFJL", + "type": "long", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "KLCOGEIGEBJ" + }, + { + "name": "GJPGKJJCPBK", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "KLCOGEIGEBJ" + }, + { + "name": "IBLOJJBEKFF", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "KLCOGEIGEBJ" + }, + { + "name": "JCLCOCEOAEP", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "KLCOGEIGEBJ" + }, + { + "name": "JGJCBGEBLLO", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "KLCOGEIGEBJ" + }, + { + "name": "PLFEEIJCHOH", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "KLCOGEIGEBJ" + }, + { + "name": "BJFNPKFOALK", + "type": "long", + "kind": "field", + "inheritedFrom": "KLCOGEIGEBJ" + }, + { + "name": "ACBFDMLHFPB", + "type": "DateTime", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "AHGCOGFEEEE", + "type": "string", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "AKFBNELAMNA", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "BADIGBCKECA", + "type": "int", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "BNBLOBAEDEE", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "CDINMMPNAID", + "type": "NMJEKMMBDDE", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "CDNFGMHLDMJ", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "FIKEBGGCDFN", + "type": "string", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "GIBHIMGJNNO", + "type": "GPDIAKNEBKH", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "HPLBOMGACED", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "IGOPGMHHLKI", + "type": "string", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "JFEAPMIPNEP", + "type": "DPLPMKMFMPB", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "KHIJAFCHLIA", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "KLNJBBPNMBJ", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "KODBEJPEFOJ", + "type": "string", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "LPJLEMJFBPE", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "MGBDHBHCDMH", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "MIKDLDEALPN", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "OFONEIOEIED", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "OILEJFNPDDB", + "type": "HJPGEGENLPH", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "PEEFHKMOMKK", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + } + ], + "inheritedWireKeys": [ + "RoomId", + "IsDorm", + "CloningAllowed", + "DisableMicAutoMute", + "DisableRoomComments", + "EncryptVoiceChat" + ] + } + } + ] + } + }, + "/rooms/base": { + "get": { + "tags": [ + "rooms" + ], + "summary": "GET rooms/base", + "description": "Inferred from Rec Room 2020 client literal `rooms/base`. Expected result: Room detail object or mutation success.", + "operationId": "client_rooms_rooms_base_get", + "parameters": [], + "responses": { + "200": { + "description": "Room detail object or mutation success.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Room" + } + } + } + }, + "204": { + "description": "Success with no body, used by some mutation endpoints." + }, + "400": { + "description": "Client-visible validation or bad request error.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "401": { + "description": "Authentication required or token invalid.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "500": { + "description": "Server error shape tolerated by client wrappers.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + } + }, + "x-recroom-client-route-literal": "rooms/base", + "x-recroom-client-family": "rooms", + "x-recroom-client-service": "rooms/api", + "x-recroom-builds": [ + "2020.12.18" + ], + "x-recroom-inferred-http-method": "get", + "x-recroom-evidence": [ + { + "type": "EJDCNGBEICB", + "method": "RecRoom.Async.IPromise`1> LMEPPFGPHLA()", + "file": "EJDCNGBEICB.txt", + "line": 784 + } + ], + "x-client-response-contracts": [ + { + "kind": "array", + "raw": "System.Collections.Generic.List`1", + "rootType": "KLCOGEIGEBJ", + "item": { + "kind": "object", + "raw": "KLCOGEIGEBJ", + "rootType": "KLCOGEIGEBJ" + }, + "typeDescription": { + "name": "KLCOGEIGEBJ", + "found": true, + "file": "KLCOGEIGEBJ.cs", + "declaration": "public class KLCOGEIGEBJ : MKAMHOIHOJK", + "kind": "class", + "baseType": "MKAMHOIHOJK", + "properties": [ + { + "name": "DADOKMAOFJL", + "type": "long", + "accessibility": "public", + "kind": "property" + }, + { + "name": "GJPGKJJCPBK", + "type": "bool", + "accessibility": "public", + "kind": "property" + }, + { + "name": "IBLOJJBEKFF", + "type": "bool", + "accessibility": "public", + "kind": "property" + }, + { + "name": "JCLCOCEOAEP", + "type": "bool", + "accessibility": "public", + "kind": "property" + }, + { + "name": "JGJCBGEBLLO", + "type": "bool", + "accessibility": "public", + "kind": "property" + }, + { + "name": "PLFEEIJCHOH", + "type": "bool", + "accessibility": "public", + "kind": "property" + } + ], + "fields": [ + { + "name": "BJFNPKFOALK", + "type": "long", + "kind": "field" + } + ], + "enumValues": [], + "wireKeys": [ + "RoomId", + "IsDorm", + "CloningAllowed", + "DisableMicAutoMute", + "DisableRoomComments", + "EncryptVoiceChat" + ], + "isilFile": "KLCOGEIGEBJ.txt", + "isilType": "KLCOGEIGEBJ", + "inherits": "MKAMHOIHOJK", + "inheritedMembers": [ + { + "name": "ACBFDMLHFPB", + "type": "DateTime", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "AHGCOGFEEEE", + "type": "string", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "AKFBNELAMNA", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "BADIGBCKECA", + "type": "int", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "BNBLOBAEDEE", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "CDINMMPNAID", + "type": "NMJEKMMBDDE", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "CDNFGMHLDMJ", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "FIKEBGGCDFN", + "type": "string", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "GIBHIMGJNNO", + "type": "GPDIAKNEBKH", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "HPLBOMGACED", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "IGOPGMHHLKI", + "type": "string", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "JFEAPMIPNEP", + "type": "DPLPMKMFMPB", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "KHIJAFCHLIA", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "KLNJBBPNMBJ", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "KODBEJPEFOJ", + "type": "string", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "LPJLEMJFBPE", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "MGBDHBHCDMH", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "MIKDLDEALPN", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "OFONEIOEIED", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "OILEJFNPDDB", + "type": "HJPGEGENLPH", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "PEEFHKMOMKK", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + } + ], + "inheritedWireKeys": [ + "Name", + "Description", + "ImageName", + "WarningMask", + "CustomWarning", + "CreatorAccountId", + "State", + "Accessibility", + "SupportsLevelVoting", + "IsRRO", + "SupportsScreens", + "SupportsWalkVR", + "SupportsTeleportVR", + "SupportsVRLow", + "SupportsQuest2", + "SupportsMobile", + "SupportsJuniors", + "CreatedAt", + "Stats" + ] + } + } + ] + } + }, + "/rooms/bulk": { + "post": { + "tags": [ + "rooms" + ], + "summary": "POST rooms/bulk", + "description": "Inferred from Rec Room 2020 client literal `rooms/bulk`. Expected result: Room summary/detail array or page.", + "operationId": "client_rooms_rooms_bulk_post", + "parameters": [], + "responses": { + "200": { + "description": "Room summary/detail array or page.", + "content": { + "application/json": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/Room" + } + } + } + } + }, + "204": { + "description": "Success with no body, used by some mutation endpoints." + }, + "400": { + "description": "Client-visible validation or bad request error.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "401": { + "description": "Authentication required or token invalid.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "500": { + "description": "Server error shape tolerated by client wrappers.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + } + }, + "x-recroom-client-route-literal": "rooms/bulk", + "x-recroom-client-family": "rooms", + "x-recroom-client-service": "rooms/api", + "x-recroom-builds": [ + "2020.12.18" + ], + "x-recroom-inferred-http-method": "post", + "x-recroom-evidence": [ + { + "type": "EJDCNGBEICB", + "method": "RecRoom.Async.IPromise`1> JPNBCKICJIH(System.Collections.Generic.IReadOnlyList`1 HJLOFEGEMHE)", + "file": "EJDCNGBEICB.txt", + "line": 319 + }, + { + "type": "EJDCNGBEICB", + "method": "RecRoom.Async.IPromise`1> JPNBCKICJIH(System.Collections.Generic.IReadOnlyList`1 GDDBDIJIBCO)", + "file": "EJDCNGBEICB.txt", + "line": 491 + } + ], + "requestBody": { + "required": false, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/GenericObject" + } + }, + "application/x-www-form-urlencoded": { + "schema": { + "$ref": "#/components/schemas/GenericObject" + } + }, + "multipart/form-data": { + "schema": { + "$ref": "#/components/schemas/GenericObject" + } + } + }, + "description": "Request body inferred. Exact DTO may be named in x-recroom-evidence method signature or companion DTO inventory." + }, + "x-client-response-contracts": [ + { + "kind": "array", + "raw": "System.Collections.Generic.List`1", + "rootType": "KLCOGEIGEBJ", + "item": { + "kind": "object", + "raw": "KLCOGEIGEBJ", + "rootType": "KLCOGEIGEBJ" + }, + "typeDescription": { + "name": "KLCOGEIGEBJ", + "found": true, + "file": "KLCOGEIGEBJ.cs", + "declaration": "public class KLCOGEIGEBJ : MKAMHOIHOJK", + "kind": "class", + "baseType": "MKAMHOIHOJK", + "properties": [ + { + "name": "DADOKMAOFJL", + "type": "long", + "accessibility": "public", + "kind": "property" + }, + { + "name": "GJPGKJJCPBK", + "type": "bool", + "accessibility": "public", + "kind": "property" + }, + { + "name": "IBLOJJBEKFF", + "type": "bool", + "accessibility": "public", + "kind": "property" + }, + { + "name": "JCLCOCEOAEP", + "type": "bool", + "accessibility": "public", + "kind": "property" + }, + { + "name": "JGJCBGEBLLO", + "type": "bool", + "accessibility": "public", + "kind": "property" + }, + { + "name": "PLFEEIJCHOH", + "type": "bool", + "accessibility": "public", + "kind": "property" + } + ], + "fields": [ + { + "name": "BJFNPKFOALK", + "type": "long", + "kind": "field" + } + ], + "enumValues": [], + "wireKeys": [ + "RoomId", + "IsDorm", + "CloningAllowed", + "DisableMicAutoMute", + "DisableRoomComments", + "EncryptVoiceChat" + ], + "isilFile": "KLCOGEIGEBJ.txt", + "isilType": "KLCOGEIGEBJ", + "inherits": "MKAMHOIHOJK", + "inheritedMembers": [ + { + "name": "ACBFDMLHFPB", + "type": "DateTime", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "AHGCOGFEEEE", + "type": "string", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "AKFBNELAMNA", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "BADIGBCKECA", + "type": "int", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "BNBLOBAEDEE", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "CDINMMPNAID", + "type": "NMJEKMMBDDE", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "CDNFGMHLDMJ", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "FIKEBGGCDFN", + "type": "string", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "GIBHIMGJNNO", + "type": "GPDIAKNEBKH", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "HPLBOMGACED", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "IGOPGMHHLKI", + "type": "string", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "JFEAPMIPNEP", + "type": "DPLPMKMFMPB", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "KHIJAFCHLIA", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "KLNJBBPNMBJ", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "KODBEJPEFOJ", + "type": "string", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "LPJLEMJFBPE", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "MGBDHBHCDMH", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "MIKDLDEALPN", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "OFONEIOEIED", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "OILEJFNPDDB", + "type": "HJPGEGENLPH", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "PEEFHKMOMKK", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + } + ], + "inheritedWireKeys": [ + "Name", + "Description", + "ImageName", + "WarningMask", + "CustomWarning", + "CreatorAccountId", + "State", + "Accessibility", + "SupportsLevelVoting", + "IsRRO", + "SupportsScreens", + "SupportsWalkVR", + "SupportsTeleportVR", + "SupportsVRLow", + "SupportsQuest2", + "SupportsMobile", + "SupportsJuniors", + "CreatedAt", + "Stats" + ] + } + } + ] + } + }, + "/rooms/cheeredby/me": { + "get": { + "tags": [ + "rooms" + ], + "summary": "GET rooms/cheeredby/me", + "description": "Inferred from Rec Room 2020 client literal `rooms/cheeredby/me`. Expected result: Room summary/detail array or page.", + "operationId": "client_rooms_rooms_cheeredby_me_get", + "parameters": [], + "responses": { + "200": { + "description": "Room summary/detail array or page.", + "content": { + "application/json": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/Room" + } + } + } + } + }, + "204": { + "description": "Success with no body, used by some mutation endpoints." + }, + "400": { + "description": "Client-visible validation or bad request error.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "401": { + "description": "Authentication required or token invalid.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "500": { + "description": "Server error shape tolerated by client wrappers.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + } + }, + "x-recroom-client-route-literal": "rooms/cheeredby/me", + "x-recroom-client-family": "rooms", + "x-recroom-client-service": "rooms/api", + "x-recroom-builds": [ + "2020.12.18" + ], + "x-recroom-inferred-http-method": "get", + "x-recroom-evidence": [ + { + "type": "EJDCNGBEICB", + "method": "RecRoom.Async.IPromise`1> GBMMJLHIDOK()", + "file": "EJDCNGBEICB.txt", + "line": 940 + } + ], + "x-client-response-contracts": [ + { + "kind": "array", + "raw": "System.Collections.Generic.List`1", + "rootType": "KLCOGEIGEBJ", + "item": { + "kind": "object", + "raw": "KLCOGEIGEBJ", + "rootType": "KLCOGEIGEBJ" + }, + "typeDescription": { + "name": "KLCOGEIGEBJ", + "found": true, + "file": "KLCOGEIGEBJ.cs", + "declaration": "public class KLCOGEIGEBJ : MKAMHOIHOJK", + "kind": "class", + "baseType": "MKAMHOIHOJK", + "properties": [ + { + "name": "DADOKMAOFJL", + "type": "long", + "accessibility": "public", + "kind": "property" + }, + { + "name": "GJPGKJJCPBK", + "type": "bool", + "accessibility": "public", + "kind": "property" + }, + { + "name": "IBLOJJBEKFF", + "type": "bool", + "accessibility": "public", + "kind": "property" + }, + { + "name": "JCLCOCEOAEP", + "type": "bool", + "accessibility": "public", + "kind": "property" + }, + { + "name": "JGJCBGEBLLO", + "type": "bool", + "accessibility": "public", + "kind": "property" + }, + { + "name": "PLFEEIJCHOH", + "type": "bool", + "accessibility": "public", + "kind": "property" + } + ], + "fields": [ + { + "name": "BJFNPKFOALK", + "type": "long", + "kind": "field" + } + ], + "enumValues": [], + "wireKeys": [ + "RoomId", + "IsDorm", + "CloningAllowed", + "DisableMicAutoMute", + "DisableRoomComments", + "EncryptVoiceChat" + ], + "isilFile": "KLCOGEIGEBJ.txt", + "isilType": "KLCOGEIGEBJ", + "inherits": "MKAMHOIHOJK", + "inheritedMembers": [ + { + "name": "ACBFDMLHFPB", + "type": "DateTime", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "AHGCOGFEEEE", + "type": "string", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "AKFBNELAMNA", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "BADIGBCKECA", + "type": "int", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "BNBLOBAEDEE", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "CDINMMPNAID", + "type": "NMJEKMMBDDE", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "CDNFGMHLDMJ", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "FIKEBGGCDFN", + "type": "string", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "GIBHIMGJNNO", + "type": "GPDIAKNEBKH", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "HPLBOMGACED", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "IGOPGMHHLKI", + "type": "string", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "JFEAPMIPNEP", + "type": "DPLPMKMFMPB", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "KHIJAFCHLIA", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "KLNJBBPNMBJ", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "KODBEJPEFOJ", + "type": "string", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "LPJLEMJFBPE", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "MGBDHBHCDMH", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "MIKDLDEALPN", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "OFONEIOEIED", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "OILEJFNPDDB", + "type": "HJPGEGENLPH", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "PEEFHKMOMKK", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + } + ], + "inheritedWireKeys": [ + "Name", + "Description", + "ImageName", + "WarningMask", + "CustomWarning", + "CreatorAccountId", + "State", + "Accessibility", + "SupportsLevelVoting", + "IsRRO", + "SupportsScreens", + "SupportsWalkVR", + "SupportsTeleportVR", + "SupportsVRLow", + "SupportsQuest2", + "SupportsMobile", + "SupportsJuniors", + "CreatedAt", + "Stats" + ] + } + } + ] + } + }, + "/rooms/createdby/{p0}": { + "post": { + "tags": [ + "rooms" + ], + "summary": "POST rooms/createdby/{0}", + "description": "Inferred from Rec Room 2020 client literal `rooms/createdby/{0}`. Expected result: Room summary/detail array or page.", + "operationId": "client_rooms_rooms_createdby_p0_post", + "parameters": [ + { + "name": "p0", + "in": "path", + "required": true, + "schema": { + "type": "string" + }, + "description": "Format placeholder from client literal rooms/createdby/{0}" + } + ], + "responses": { + "200": { + "description": "Room summary/detail array or page.", + "content": { + "application/json": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/Room" + } + } + } + } + }, + "204": { + "description": "Success with no body, used by some mutation endpoints." + }, + "400": { + "description": "Client-visible validation or bad request error.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "401": { + "description": "Authentication required or token invalid.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "500": { + "description": "Server error shape tolerated by client wrappers.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + } + }, + "x-recroom-client-route-literal": "rooms/createdby/{0}", + "x-recroom-client-family": "rooms", + "x-recroom-client-service": "rooms/api", + "x-recroom-builds": [ + "2020.12.18" + ], + "x-recroom-inferred-http-method": "post", + "x-recroom-evidence": [ + { + "type": "EJDCNGBEICB", + "method": "RecRoom.Async.IPromise`1> OGIHJJDNPKJ(System.Int32 GKLPIFBPGOD)", + "file": "EJDCNGBEICB.txt", + "line": 1073 + } + ], + "requestBody": { + "required": false, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/GenericObject" + } + }, + "application/x-www-form-urlencoded": { + "schema": { + "$ref": "#/components/schemas/GenericObject" + } + }, + "multipart/form-data": { + "schema": { + "$ref": "#/components/schemas/GenericObject" + } + } + }, + "description": "Request body inferred. Exact DTO may be named in x-recroom-evidence method signature or companion DTO inventory." + }, + "x-client-response-contracts": [ + { + "kind": "array", + "raw": "System.Collections.Generic.List`1", + "rootType": "KLCOGEIGEBJ", + "item": { + "kind": "object", + "raw": "KLCOGEIGEBJ", + "rootType": "KLCOGEIGEBJ" + }, + "typeDescription": { + "name": "KLCOGEIGEBJ", + "found": true, + "file": "KLCOGEIGEBJ.cs", + "declaration": "public class KLCOGEIGEBJ : MKAMHOIHOJK", + "kind": "class", + "baseType": "MKAMHOIHOJK", + "properties": [ + { + "name": "DADOKMAOFJL", + "type": "long", + "accessibility": "public", + "kind": "property" + }, + { + "name": "GJPGKJJCPBK", + "type": "bool", + "accessibility": "public", + "kind": "property" + }, + { + "name": "IBLOJJBEKFF", + "type": "bool", + "accessibility": "public", + "kind": "property" + }, + { + "name": "JCLCOCEOAEP", + "type": "bool", + "accessibility": "public", + "kind": "property" + }, + { + "name": "JGJCBGEBLLO", + "type": "bool", + "accessibility": "public", + "kind": "property" + }, + { + "name": "PLFEEIJCHOH", + "type": "bool", + "accessibility": "public", + "kind": "property" + } + ], + "fields": [ + { + "name": "BJFNPKFOALK", + "type": "long", + "kind": "field" + } + ], + "enumValues": [], + "wireKeys": [ + "RoomId", + "IsDorm", + "CloningAllowed", + "DisableMicAutoMute", + "DisableRoomComments", + "EncryptVoiceChat" + ], + "isilFile": "KLCOGEIGEBJ.txt", + "isilType": "KLCOGEIGEBJ", + "inherits": "MKAMHOIHOJK", + "inheritedMembers": [ + { + "name": "ACBFDMLHFPB", + "type": "DateTime", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "AHGCOGFEEEE", + "type": "string", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "AKFBNELAMNA", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "BADIGBCKECA", + "type": "int", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "BNBLOBAEDEE", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "CDINMMPNAID", + "type": "NMJEKMMBDDE", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "CDNFGMHLDMJ", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "FIKEBGGCDFN", + "type": "string", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "GIBHIMGJNNO", + "type": "GPDIAKNEBKH", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "HPLBOMGACED", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "IGOPGMHHLKI", + "type": "string", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "JFEAPMIPNEP", + "type": "DPLPMKMFMPB", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "KHIJAFCHLIA", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "KLNJBBPNMBJ", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "KODBEJPEFOJ", + "type": "string", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "LPJLEMJFBPE", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "MGBDHBHCDMH", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "MIKDLDEALPN", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "OFONEIOEIED", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "OILEJFNPDDB", + "type": "HJPGEGENLPH", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "PEEFHKMOMKK", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + } + ], + "inheritedWireKeys": [ + "Name", + "Description", + "ImageName", + "WarningMask", + "CustomWarning", + "CreatorAccountId", + "State", + "Accessibility", + "SupportsLevelVoting", + "IsRRO", + "SupportsScreens", + "SupportsWalkVR", + "SupportsTeleportVR", + "SupportsVRLow", + "SupportsQuest2", + "SupportsMobile", + "SupportsJuniors", + "CreatedAt", + "Stats" + ] + } + } + ] + } + }, + "/rooms/createdby/me": { + "post": { + "tags": [ + "rooms" + ], + "summary": "POST rooms/createdby/me", + "description": "Inferred from Rec Room 2020 client literal `rooms/createdby/me`. Expected result: Room summary/detail array or page.", + "operationId": "client_rooms_rooms_createdby_me_post", + "parameters": [], + "responses": { + "200": { + "description": "Room summary/detail array or page.", + "content": { + "application/json": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/Room" + } + } + } + } + }, + "204": { + "description": "Success with no body, used by some mutation endpoints." + }, + "400": { + "description": "Client-visible validation or bad request error.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "401": { + "description": "Authentication required or token invalid.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "500": { + "description": "Server error shape tolerated by client wrappers.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + } + }, + "x-recroom-client-route-literal": "rooms/createdby/me", + "x-recroom-client-family": "rooms", + "x-recroom-client-service": "rooms/api", + "x-recroom-builds": [ + "2020.12.18" + ], + "x-recroom-inferred-http-method": "post", + "x-recroom-evidence": [ + { + "type": "EJDCNGBEICB", + "method": "RecRoom.Async.IPromise`1> MIMNJGKEGMF()", + "file": "EJDCNGBEICB.txt", + "line": 823 + } + ], + "requestBody": { + "required": false, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/GenericObject" + } + }, + "application/x-www-form-urlencoded": { + "schema": { + "$ref": "#/components/schemas/GenericObject" + } + }, + "multipart/form-data": { + "schema": { + "$ref": "#/components/schemas/GenericObject" + } + } + }, + "description": "Request body inferred. Exact DTO may be named in x-recroom-evidence method signature or companion DTO inventory." + }, + "x-client-response-contracts": [ + { + "kind": "array", + "raw": "System.Collections.Generic.List`1", + "rootType": "KLCOGEIGEBJ", + "item": { + "kind": "object", + "raw": "KLCOGEIGEBJ", + "rootType": "KLCOGEIGEBJ" + }, + "typeDescription": { + "name": "KLCOGEIGEBJ", + "found": true, + "file": "KLCOGEIGEBJ.cs", + "declaration": "public class KLCOGEIGEBJ : MKAMHOIHOJK", + "kind": "class", + "baseType": "MKAMHOIHOJK", + "properties": [ + { + "name": "DADOKMAOFJL", + "type": "long", + "accessibility": "public", + "kind": "property" + }, + { + "name": "GJPGKJJCPBK", + "type": "bool", + "accessibility": "public", + "kind": "property" + }, + { + "name": "IBLOJJBEKFF", + "type": "bool", + "accessibility": "public", + "kind": "property" + }, + { + "name": "JCLCOCEOAEP", + "type": "bool", + "accessibility": "public", + "kind": "property" + }, + { + "name": "JGJCBGEBLLO", + "type": "bool", + "accessibility": "public", + "kind": "property" + }, + { + "name": "PLFEEIJCHOH", + "type": "bool", + "accessibility": "public", + "kind": "property" + } + ], + "fields": [ + { + "name": "BJFNPKFOALK", + "type": "long", + "kind": "field" + } + ], + "enumValues": [], + "wireKeys": [ + "RoomId", + "IsDorm", + "CloningAllowed", + "DisableMicAutoMute", + "DisableRoomComments", + "EncryptVoiceChat" + ], + "isilFile": "KLCOGEIGEBJ.txt", + "isilType": "KLCOGEIGEBJ", + "inherits": "MKAMHOIHOJK", + "inheritedMembers": [ + { + "name": "ACBFDMLHFPB", + "type": "DateTime", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "AHGCOGFEEEE", + "type": "string", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "AKFBNELAMNA", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "BADIGBCKECA", + "type": "int", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "BNBLOBAEDEE", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "CDINMMPNAID", + "type": "NMJEKMMBDDE", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "CDNFGMHLDMJ", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "FIKEBGGCDFN", + "type": "string", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "GIBHIMGJNNO", + "type": "GPDIAKNEBKH", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "HPLBOMGACED", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "IGOPGMHHLKI", + "type": "string", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "JFEAPMIPNEP", + "type": "DPLPMKMFMPB", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "KHIJAFCHLIA", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "KLNJBBPNMBJ", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "KODBEJPEFOJ", + "type": "string", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "LPJLEMJFBPE", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "MGBDHBHCDMH", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "MIKDLDEALPN", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "OFONEIOEIED", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "OILEJFNPDDB", + "type": "HJPGEGENLPH", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "PEEFHKMOMKK", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + } + ], + "inheritedWireKeys": [ + "Name", + "Description", + "ImageName", + "WarningMask", + "CustomWarning", + "CreatorAccountId", + "State", + "Accessibility", + "SupportsLevelVoting", + "IsRRO", + "SupportsScreens", + "SupportsWalkVR", + "SupportsTeleportVR", + "SupportsVRLow", + "SupportsQuest2", + "SupportsMobile", + "SupportsJuniors", + "CreatedAt", + "Stats" + ] + } + } + ] + } + }, + "/rooms/favoritedby/me": { + "get": { + "tags": [ + "rooms" + ], + "summary": "GET rooms/favoritedby/me", + "description": "Inferred from Rec Room 2020 client literal `rooms/favoritedby/me`. Expected result: Room summary/detail array or page.", + "operationId": "client_rooms_rooms_favoritedby_me_get", + "parameters": [], + "responses": { + "200": { + "description": "Room summary/detail array or page.", + "content": { + "application/json": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/Room" + } + } + } + } + }, + "204": { + "description": "Success with no body, used by some mutation endpoints." + }, + "400": { + "description": "Client-visible validation or bad request error.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "401": { + "description": "Authentication required or token invalid.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "500": { + "description": "Server error shape tolerated by client wrappers.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + } + }, + "x-recroom-client-route-literal": "rooms/favoritedby/me", + "x-recroom-client-family": "rooms", + "x-recroom-client-service": "rooms/api", + "x-recroom-builds": [ + "2020.12.18" + ], + "x-recroom-inferred-http-method": "get", + "x-recroom-evidence": [ + { + "type": "EJDCNGBEICB", + "method": "RecRoom.Async.IPromise`1> AFOLHKGPMOC()", + "file": "EJDCNGBEICB.txt", + "line": 979 + } + ], + "x-client-response-contracts": [ + { + "kind": "array", + "raw": "System.Collections.Generic.List`1", + "rootType": "KLCOGEIGEBJ", + "item": { + "kind": "object", + "raw": "KLCOGEIGEBJ", + "rootType": "KLCOGEIGEBJ" + }, + "typeDescription": { + "name": "KLCOGEIGEBJ", + "found": true, + "file": "KLCOGEIGEBJ.cs", + "declaration": "public class KLCOGEIGEBJ : MKAMHOIHOJK", + "kind": "class", + "baseType": "MKAMHOIHOJK", + "properties": [ + { + "name": "DADOKMAOFJL", + "type": "long", + "accessibility": "public", + "kind": "property" + }, + { + "name": "GJPGKJJCPBK", + "type": "bool", + "accessibility": "public", + "kind": "property" + }, + { + "name": "IBLOJJBEKFF", + "type": "bool", + "accessibility": "public", + "kind": "property" + }, + { + "name": "JCLCOCEOAEP", + "type": "bool", + "accessibility": "public", + "kind": "property" + }, + { + "name": "JGJCBGEBLLO", + "type": "bool", + "accessibility": "public", + "kind": "property" + }, + { + "name": "PLFEEIJCHOH", + "type": "bool", + "accessibility": "public", + "kind": "property" + } + ], + "fields": [ + { + "name": "BJFNPKFOALK", + "type": "long", + "kind": "field" + } + ], + "enumValues": [], + "wireKeys": [ + "RoomId", + "IsDorm", + "CloningAllowed", + "DisableMicAutoMute", + "DisableRoomComments", + "EncryptVoiceChat" + ], + "isilFile": "KLCOGEIGEBJ.txt", + "isilType": "KLCOGEIGEBJ", + "inherits": "MKAMHOIHOJK", + "inheritedMembers": [ + { + "name": "ACBFDMLHFPB", + "type": "DateTime", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "AHGCOGFEEEE", + "type": "string", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "AKFBNELAMNA", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "BADIGBCKECA", + "type": "int", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "BNBLOBAEDEE", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "CDINMMPNAID", + "type": "NMJEKMMBDDE", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "CDNFGMHLDMJ", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "FIKEBGGCDFN", + "type": "string", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "GIBHIMGJNNO", + "type": "GPDIAKNEBKH", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "HPLBOMGACED", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "IGOPGMHHLKI", + "type": "string", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "JFEAPMIPNEP", + "type": "DPLPMKMFMPB", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "KHIJAFCHLIA", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "KLNJBBPNMBJ", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "KODBEJPEFOJ", + "type": "string", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "LPJLEMJFBPE", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "MGBDHBHCDMH", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "MIKDLDEALPN", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "OFONEIOEIED", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "OILEJFNPDDB", + "type": "HJPGEGENLPH", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "PEEFHKMOMKK", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + } + ], + "inheritedWireKeys": [ + "Name", + "Description", + "ImageName", + "WarningMask", + "CustomWarning", + "CreatorAccountId", + "State", + "Accessibility", + "SupportsLevelVoting", + "IsRRO", + "SupportsScreens", + "SupportsWalkVR", + "SupportsTeleportVR", + "SupportsVRLow", + "SupportsQuest2", + "SupportsMobile", + "SupportsJuniors", + "CreatedAt", + "Stats" + ] + } + } + ] + } + }, + "/rooms/hot": { + "get": { + "tags": [ + "rooms" + ], + "summary": "GET rooms/hot", + "description": "Inferred from Rec Room 2020 client literal `rooms/hot`. Expected result: Room summary/detail array or page.", + "operationId": "client_rooms_rooms_hot_get", + "parameters": [], + "responses": { + "200": { + "description": "Room summary/detail array or page.", + "content": { + "application/json": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/Room" + } + } + } + } + }, + "204": { + "description": "Success with no body, used by some mutation endpoints." + }, + "400": { + "description": "Client-visible validation or bad request error.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "401": { + "description": "Authentication required or token invalid.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "500": { + "description": "Server error shape tolerated by client wrappers.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + } + }, + "x-recroom-client-route-literal": "rooms/hot", + "x-recroom-client-family": "rooms", + "x-recroom-client-service": "rooms/api", + "x-recroom-builds": [ + "2020.12.18" + ], + "x-recroom-inferred-http-method": "get", + "x-recroom-evidence": [ + { + "type": "EJDCNGBEICB", + "method": "RecRoom.Async.IPromise`1 DLPLPKCNLNA(System.Collections.Generic.IReadOnlyList`1 CAFPJPHILMN)", + "file": "EJDCNGBEICB.txt", + "line": 1601 + } + ], + "x-client-response-contracts": [ + { + "kind": "object", + "raw": "IDLBPALJJDJ", + "rootType": "IDLBPALJJDJ", + "typeDescription": { + "name": "IDLBPALJJDJ", + "found": true, + "file": "IDLBPALJJDJ.cs", + "declaration": "public class IDLBPALJJDJ : NCONANPODKN, IFAIJAGLDFK", + "kind": "class", + "baseType": "NCONANPODKN", + "properties": [], + "fields": [], + "enumValues": [], + "wireKeys": [ + "TotalResults" + ], + "isilFile": "IDLBPALJJDJ.txt", + "isilType": "IDLBPALJJDJ", + "inherits": "NCONANPODKN", + "inheritedMembers": [ + { + "name": "KPCBEDOLLFK", + "type": "long", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "NCONANPODKN" + }, + { + "name": "PPLMJPLEHLP", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "NCONANPODKN" + } + ], + "inheritedWireKeys": [] + } + } + ] + } + }, + "/rooms/moderatedby/me": { + "get": { + "tags": [ + "rooms" + ], + "summary": "GET rooms/moderatedby/me", + "description": "Inferred from Rec Room 2020 client literal `rooms/moderatedby/me`. Expected result: Room summary/detail array or page.", + "operationId": "client_rooms_rooms_moderatedby_me_get", + "parameters": [], + "responses": { + "200": { + "description": "Room summary/detail array or page.", + "content": { + "application/json": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/Room" + } + } + } + } + }, + "204": { + "description": "Success with no body, used by some mutation endpoints." + }, + "400": { + "description": "Client-visible validation or bad request error.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "401": { + "description": "Authentication required or token invalid.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "500": { + "description": "Server error shape tolerated by client wrappers.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + } + }, + "x-recroom-client-route-literal": "rooms/moderatedby/me", + "x-recroom-client-family": "rooms", + "x-recroom-client-service": "rooms/api", + "x-recroom-builds": [ + "2020.12.18" + ], + "x-recroom-inferred-http-method": "get", + "x-recroom-evidence": [ + { + "type": "EJDCNGBEICB", + "method": "RecRoom.Async.IPromise`1> IMKAAIEFGIG()", + "file": "EJDCNGBEICB.txt", + "line": 901 + } + ], + "x-client-response-contracts": [ + { + "kind": "array", + "raw": "System.Collections.Generic.List`1", + "rootType": "KLCOGEIGEBJ", + "item": { + "kind": "object", + "raw": "KLCOGEIGEBJ", + "rootType": "KLCOGEIGEBJ" + }, + "typeDescription": { + "name": "KLCOGEIGEBJ", + "found": true, + "file": "KLCOGEIGEBJ.cs", + "declaration": "public class KLCOGEIGEBJ : MKAMHOIHOJK", + "kind": "class", + "baseType": "MKAMHOIHOJK", + "properties": [ + { + "name": "DADOKMAOFJL", + "type": "long", + "accessibility": "public", + "kind": "property" + }, + { + "name": "GJPGKJJCPBK", + "type": "bool", + "accessibility": "public", + "kind": "property" + }, + { + "name": "IBLOJJBEKFF", + "type": "bool", + "accessibility": "public", + "kind": "property" + }, + { + "name": "JCLCOCEOAEP", + "type": "bool", + "accessibility": "public", + "kind": "property" + }, + { + "name": "JGJCBGEBLLO", + "type": "bool", + "accessibility": "public", + "kind": "property" + }, + { + "name": "PLFEEIJCHOH", + "type": "bool", + "accessibility": "public", + "kind": "property" + } + ], + "fields": [ + { + "name": "BJFNPKFOALK", + "type": "long", + "kind": "field" + } + ], + "enumValues": [], + "wireKeys": [ + "RoomId", + "IsDorm", + "CloningAllowed", + "DisableMicAutoMute", + "DisableRoomComments", + "EncryptVoiceChat" + ], + "isilFile": "KLCOGEIGEBJ.txt", + "isilType": "KLCOGEIGEBJ", + "inherits": "MKAMHOIHOJK", + "inheritedMembers": [ + { + "name": "ACBFDMLHFPB", + "type": "DateTime", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "AHGCOGFEEEE", + "type": "string", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "AKFBNELAMNA", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "BADIGBCKECA", + "type": "int", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "BNBLOBAEDEE", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "CDINMMPNAID", + "type": "NMJEKMMBDDE", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "CDNFGMHLDMJ", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "FIKEBGGCDFN", + "type": "string", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "GIBHIMGJNNO", + "type": "GPDIAKNEBKH", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "HPLBOMGACED", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "IGOPGMHHLKI", + "type": "string", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "JFEAPMIPNEP", + "type": "DPLPMKMFMPB", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "KHIJAFCHLIA", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "KLNJBBPNMBJ", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "KODBEJPEFOJ", + "type": "string", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "LPJLEMJFBPE", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "MGBDHBHCDMH", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "MIKDLDEALPN", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "OFONEIOEIED", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "OILEJFNPDDB", + "type": "HJPGEGENLPH", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "PEEFHKMOMKK", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + } + ], + "inheritedWireKeys": [ + "Name", + "Description", + "ImageName", + "WarningMask", + "CustomWarning", + "CreatorAccountId", + "State", + "Accessibility", + "SupportsLevelVoting", + "IsRRO", + "SupportsScreens", + "SupportsWalkVR", + "SupportsTeleportVR", + "SupportsVRLow", + "SupportsQuest2", + "SupportsMobile", + "SupportsJuniors", + "CreatedAt", + "Stats" + ] + } + } + ] + } + }, + "/rooms/ownedby/me": { + "get": { + "tags": [ + "rooms" + ], + "summary": "GET rooms/ownedby/me", + "description": "Inferred from Rec Room 2020 client literal `rooms/ownedby/me`. Expected result: Room summary/detail array or page.", + "operationId": "client_rooms_rooms_ownedby_me_get", + "parameters": [], + "responses": { + "200": { + "description": "Room summary/detail array or page.", + "content": { + "application/json": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/Room" + } + } + } + } + }, + "204": { + "description": "Success with no body, used by some mutation endpoints." + }, + "400": { + "description": "Client-visible validation or bad request error.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "401": { + "description": "Authentication required or token invalid.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "500": { + "description": "Server error shape tolerated by client wrappers.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + } + }, + "x-recroom-client-route-literal": "rooms/ownedby/me", + "x-recroom-client-family": "rooms", + "x-recroom-client-service": "rooms/api", + "x-recroom-builds": [ + "2020.12.18" + ], + "x-recroom-inferred-http-method": "get", + "x-recroom-evidence": [ + { + "type": "EJDCNGBEICB", + "method": "RecRoom.Async.IPromise`1> FNMNFLFIBJG()", + "file": "EJDCNGBEICB.txt", + "line": 862 + } + ], + "x-client-response-contracts": [ + { + "kind": "array", + "raw": "System.Collections.Generic.List`1", + "rootType": "KLCOGEIGEBJ", + "item": { + "kind": "object", + "raw": "KLCOGEIGEBJ", + "rootType": "KLCOGEIGEBJ" + }, + "typeDescription": { + "name": "KLCOGEIGEBJ", + "found": true, + "file": "KLCOGEIGEBJ.cs", + "declaration": "public class KLCOGEIGEBJ : MKAMHOIHOJK", + "kind": "class", + "baseType": "MKAMHOIHOJK", + "properties": [ + { + "name": "DADOKMAOFJL", + "type": "long", + "accessibility": "public", + "kind": "property" + }, + { + "name": "GJPGKJJCPBK", + "type": "bool", + "accessibility": "public", + "kind": "property" + }, + { + "name": "IBLOJJBEKFF", + "type": "bool", + "accessibility": "public", + "kind": "property" + }, + { + "name": "JCLCOCEOAEP", + "type": "bool", + "accessibility": "public", + "kind": "property" + }, + { + "name": "JGJCBGEBLLO", + "type": "bool", + "accessibility": "public", + "kind": "property" + }, + { + "name": "PLFEEIJCHOH", + "type": "bool", + "accessibility": "public", + "kind": "property" + } + ], + "fields": [ + { + "name": "BJFNPKFOALK", + "type": "long", + "kind": "field" + } + ], + "enumValues": [], + "wireKeys": [ + "RoomId", + "IsDorm", + "CloningAllowed", + "DisableMicAutoMute", + "DisableRoomComments", + "EncryptVoiceChat" + ], + "isilFile": "KLCOGEIGEBJ.txt", + "isilType": "KLCOGEIGEBJ", + "inherits": "MKAMHOIHOJK", + "inheritedMembers": [ + { + "name": "ACBFDMLHFPB", + "type": "DateTime", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "AHGCOGFEEEE", + "type": "string", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "AKFBNELAMNA", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "BADIGBCKECA", + "type": "int", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "BNBLOBAEDEE", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "CDINMMPNAID", + "type": "NMJEKMMBDDE", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "CDNFGMHLDMJ", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "FIKEBGGCDFN", + "type": "string", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "GIBHIMGJNNO", + "type": "GPDIAKNEBKH", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "HPLBOMGACED", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "IGOPGMHHLKI", + "type": "string", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "JFEAPMIPNEP", + "type": "DPLPMKMFMPB", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "KHIJAFCHLIA", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "KLNJBBPNMBJ", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "KODBEJPEFOJ", + "type": "string", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "LPJLEMJFBPE", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "MGBDHBHCDMH", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "MIKDLDEALPN", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "OFONEIOEIED", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "OILEJFNPDDB", + "type": "HJPGEGENLPH", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "PEEFHKMOMKK", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + } + ], + "inheritedWireKeys": [ + "Name", + "Description", + "ImageName", + "WarningMask", + "CustomWarning", + "CreatorAccountId", + "State", + "Accessibility", + "SupportsLevelVoting", + "IsRRO", + "SupportsScreens", + "SupportsWalkVR", + "SupportsTeleportVR", + "SupportsVRLow", + "SupportsQuest2", + "SupportsMobile", + "SupportsJuniors", + "CreatedAt", + "Stats" + ] + } + } + ] + } + }, + "/rooms/recommendations": { + "get": { + "tags": [ + "rooms" + ], + "summary": "GET rooms/recommendations", + "description": "Inferred from Rec Room 2020 client literal `rooms/recommendations`. Expected result: Room summary/detail array or page.", + "operationId": "client_rooms_rooms_recommendations_get", + "parameters": [], + "responses": { + "200": { + "description": "Room summary/detail array or page.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Room" + } + } + } + }, + "204": { + "description": "Success with no body, used by some mutation endpoints." + }, + "400": { + "description": "Client-visible validation or bad request error.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "401": { + "description": "Authentication required or token invalid.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "500": { + "description": "Server error shape tolerated by client wrappers.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + } + }, + "x-recroom-client-route-literal": "rooms/recommendations", + "x-recroom-client-family": "rooms", + "x-recroom-client-service": "rooms/api", + "x-recroom-builds": [ + "2020.12.18" + ], + "x-recroom-inferred-http-method": "get", + "x-recroom-evidence": [ + { + "type": "EJDCNGBEICB", + "method": "RecRoom.Async.IPromise`1> JDKLBNBIEAH(AMBEPDPIBPA NLNJMJGOKFI, System.Int16 NJJLKKLALND)", + "file": "EJDCNGBEICB.txt", + "line": 1430 + } + ], + "x-client-response-contracts": [ + { + "kind": "array", + "raw": "System.Collections.Generic.List`1", + "rootType": "ANNKHNFLMNP", + "item": { + "kind": "object", + "raw": "ANNKHNFLMNP", + "rootType": "ANNKHNFLMNP" + }, + "typeDescription": { + "name": "ANNKHNFLMNP", + "found": true, + "file": "ANNKHNFLMNP.cs", + "declaration": "public class ANNKHNFLMNP : IFAIJAGLDFK", + "kind": "class", + "baseType": "IFAIJAGLDFK", + "properties": [ + { + "name": "CGKFPHCGBKL", + "type": "KLCOGEIGEBJ", + "accessibility": "public", + "kind": "property" + }, + { + "name": "HEFJFKFNNCJ", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + } + ], + "fields": [], + "enumValues": [], + "wireKeys": [ + "SeedRoom" + ], + "isilFile": "ANNKHNFLMNP.txt", + "isilType": "ANNKHNFLMNP" + } + } + ] + } + }, + "/rooms/rro_ids": { + "post": { + "tags": [ + "rooms" + ], + "summary": "POST rooms/rro_ids", + "description": "Inferred from Rec Room 2020 client literal `rooms/rro_ids`. Expected result: Room detail object or mutation success.", + "operationId": "client_rooms_rooms_rro_ids_post", + "parameters": [], + "responses": { + "200": { + "description": "Room detail object or mutation success.", + "content": { + "application/json": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/Room" + } + } + } + } + }, + "204": { + "description": "Success with no body, used by some mutation endpoints." + }, + "400": { + "description": "Client-visible validation or bad request error.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "401": { + "description": "Authentication required or token invalid.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "500": { + "description": "Server error shape tolerated by client wrappers.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + } + }, + "x-recroom-client-route-literal": "rooms/rro_ids", + "x-recroom-client-family": "rooms", + "x-recroom-client-service": "rooms/api", + "x-recroom-builds": [ + "2020.12.18" + ], + "x-recroom-inferred-http-method": "post", + "x-recroom-evidence": [ + { + "type": "EJDCNGBEICB", + "method": "RecRoom.Async.IPromise`1> JDMBPCJDEMF()", + "file": "EJDCNGBEICB.txt", + "line": 1119 + } + ], + "requestBody": { + "required": false, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/GenericObject" + } + }, + "application/x-www-form-urlencoded": { + "schema": { + "$ref": "#/components/schemas/GenericObject" + } + }, + "multipart/form-data": { + "schema": { + "$ref": "#/components/schemas/GenericObject" + } + } + }, + "description": "Request body inferred. Exact DTO may be named in x-recroom-evidence method signature or companion DTO inventory." + }, + "x-client-response-contracts": [ + { + "kind": "array", + "raw": "System.Collections.Generic.List`1", + "rootType": "Int64", + "item": { + "kind": "primitive", + "raw": "System.Int64", + "rootType": "number" + }, + "typeDescription": { + "name": "Int64", + "found": false, + "kind": "primitive", + "wireKeys": [] + } + } + ] + } + }, + "/rooms/search": { + "get": { + "tags": [ + "rooms" + ], + "summary": "GET rooms/search", + "description": "Inferred from Rec Room 2020 client literal `rooms/search`. Expected result: Room summary/detail array or page.", + "operationId": "client_rooms_rooms_search_get", + "parameters": [], + "responses": { + "200": { + "description": "Room summary/detail array or page.", + "content": { + "application/json": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/Room" + } + } + } + } + }, + "204": { + "description": "Success with no body, used by some mutation endpoints." + }, + "400": { + "description": "Client-visible validation or bad request error.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "401": { + "description": "Authentication required or token invalid.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "500": { + "description": "Server error shape tolerated by client wrappers.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + } + }, + "x-recroom-client-route-literal": "rooms/search", + "x-recroom-client-family": "rooms", + "x-recroom-client-service": "rooms/api", + "x-recroom-builds": [ + "2020.12.18" + ], + "x-recroom-inferred-http-method": "get", + "x-recroom-evidence": [ + { + "type": "EJDCNGBEICB", + "method": "RecRoom.Async.IPromise`1 IAAKGMOGLFN(System.String CNBKKCJAHPP)", + "file": "EJDCNGBEICB.txt", + "line": 1516 + } + ], + "x-client-response-contracts": [ + { + "kind": "object", + "raw": "IDLBPALJJDJ", + "rootType": "IDLBPALJJDJ", + "typeDescription": { + "name": "IDLBPALJJDJ", + "found": true, + "file": "IDLBPALJJDJ.cs", + "declaration": "public class IDLBPALJJDJ : NCONANPODKN, IFAIJAGLDFK", + "kind": "class", + "baseType": "NCONANPODKN", + "properties": [], + "fields": [], + "enumValues": [], + "wireKeys": [ + "TotalResults" + ], + "isilFile": "IDLBPALJJDJ.txt", + "isilType": "IDLBPALJJDJ", + "inherits": "NCONANPODKN", + "inheritedMembers": [ + { + "name": "KPCBEDOLLFK", + "type": "long", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "NCONANPODKN" + }, + { + "name": "PPLMJPLEHLP", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "NCONANPODKN" + } + ], + "inheritedWireKeys": [] + } + } + ] + } + }, + "/rooms/visitedby/me": { + "get": { + "tags": [ + "rooms" + ], + "summary": "GET rooms/visitedby/me", + "description": "Inferred from Rec Room 2020 client literal `rooms/visitedby/me`. Expected result: Room summary/detail array or page.", + "operationId": "client_rooms_rooms_visitedby_me_get", + "parameters": [], + "responses": { + "200": { + "description": "Room summary/detail array or page.", + "content": { + "application/json": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/Room" + } + } + } + } + }, + "204": { + "description": "Success with no body, used by some mutation endpoints." + }, + "400": { + "description": "Client-visible validation or bad request error.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "401": { + "description": "Authentication required or token invalid.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "500": { + "description": "Server error shape tolerated by client wrappers.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + } + }, + "x-recroom-client-route-literal": "rooms/visitedby/me", + "x-recroom-client-family": "rooms", + "x-recroom-client-service": "rooms/api", + "x-recroom-builds": [ + "2020.12.18" + ], + "x-recroom-inferred-http-method": "get", + "x-recroom-evidence": [ + { + "type": "EJDCNGBEICB", + "method": "RecRoom.Async.IPromise`1> INKFBOEEKDA()", + "file": "EJDCNGBEICB.txt", + "line": 1018 + } + ], + "x-client-response-contracts": [ + { + "kind": "array", + "raw": "System.Collections.Generic.List`1", + "rootType": "KLCOGEIGEBJ", + "item": { + "kind": "object", + "raw": "KLCOGEIGEBJ", + "rootType": "KLCOGEIGEBJ" + }, + "typeDescription": { + "name": "KLCOGEIGEBJ", + "found": true, + "file": "KLCOGEIGEBJ.cs", + "declaration": "public class KLCOGEIGEBJ : MKAMHOIHOJK", + "kind": "class", + "baseType": "MKAMHOIHOJK", + "properties": [ + { + "name": "DADOKMAOFJL", + "type": "long", + "accessibility": "public", + "kind": "property" + }, + { + "name": "GJPGKJJCPBK", + "type": "bool", + "accessibility": "public", + "kind": "property" + }, + { + "name": "IBLOJJBEKFF", + "type": "bool", + "accessibility": "public", + "kind": "property" + }, + { + "name": "JCLCOCEOAEP", + "type": "bool", + "accessibility": "public", + "kind": "property" + }, + { + "name": "JGJCBGEBLLO", + "type": "bool", + "accessibility": "public", + "kind": "property" + }, + { + "name": "PLFEEIJCHOH", + "type": "bool", + "accessibility": "public", + "kind": "property" + } + ], + "fields": [ + { + "name": "BJFNPKFOALK", + "type": "long", + "kind": "field" + } + ], + "enumValues": [], + "wireKeys": [ + "RoomId", + "IsDorm", + "CloningAllowed", + "DisableMicAutoMute", + "DisableRoomComments", + "EncryptVoiceChat" + ], + "isilFile": "KLCOGEIGEBJ.txt", + "isilType": "KLCOGEIGEBJ", + "inherits": "MKAMHOIHOJK", + "inheritedMembers": [ + { + "name": "ACBFDMLHFPB", + "type": "DateTime", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "AHGCOGFEEEE", + "type": "string", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "AKFBNELAMNA", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "BADIGBCKECA", + "type": "int", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "BNBLOBAEDEE", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "CDINMMPNAID", + "type": "NMJEKMMBDDE", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "CDNFGMHLDMJ", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "FIKEBGGCDFN", + "type": "string", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "GIBHIMGJNNO", + "type": "GPDIAKNEBKH", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "HPLBOMGACED", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "IGOPGMHHLKI", + "type": "string", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "JFEAPMIPNEP", + "type": "DPLPMKMFMPB", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "KHIJAFCHLIA", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "KLNJBBPNMBJ", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "KODBEJPEFOJ", + "type": "string", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "LPJLEMJFBPE", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "MGBDHBHCDMH", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "MIKDLDEALPN", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "OFONEIOEIED", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "OILEJFNPDDB", + "type": "HJPGEGENLPH", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "PEEFHKMOMKK", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + } + ], + "inheritedWireKeys": [ + "Name", + "Description", + "ImageName", + "WarningMask", + "CustomWarning", + "CreatorAccountId", + "State", + "Accessibility", + "SupportsLevelVoting", + "IsRRO", + "SupportsScreens", + "SupportsWalkVR", + "SupportsTeleportVR", + "SupportsVRLow", + "SupportsQuest2", + "SupportsMobile", + "SupportsJuniors", + "CreatedAt", + "Stats" + ] + } + } + ] + } + }, + "/search_rooms/{tail}": { + "get": { + "tags": [ + "rooms" + ], + "summary": "GET search_rooms/", + "description": "Inferred from Rec Room 2020 client literal `search_rooms/`. Expected result: Room summary/detail array or page.", + "operationId": "client_rooms_search_rooms_tail_get", + "parameters": [ + { + "name": "tail", + "in": "path", + "required": true, + "schema": { + "type": "string" + }, + "description": "Format placeholder from client literal search_rooms/" + } + ], + "responses": { + "200": { + "description": "Room summary/detail array or page.", + "content": { + "application/json": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/Room" + } + } + } + } + }, + "204": { + "description": "Success with no body, used by some mutation endpoints." + }, + "400": { + "description": "Client-visible validation or bad request error.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "401": { + "description": "Authentication required or token invalid.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "500": { + "description": "Server error shape tolerated by client wrappers.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + } + }, + "x-recroom-client-route-literal": "search_rooms/", + "x-recroom-client-family": "rooms", + "x-recroom-client-service": "rooms/api", + "x-recroom-builds": [ + "2020.12.18" + ], + "x-recroom-inferred-http-method": "get", + "x-recroom-evidence": [ + { + "type": "OJMCBOKJFOF", + "method": "RecRoom.Async.IPromise`1> IAAKGMOGLFN(System.String CNBKKCJAHPP)", + "file": "OJMCBOKJFOF.txt", + "line": 3064 + }, + { + "type": "OJMCBOKJFOF+PEBCIEAPGON", + "method": "System.String IAAKGMOGLFN(System.String CNBKKCJAHPP)", + "file": "OJMCBOKJFOF_NestedType_PEBCIEAPGON.txt", + "line": 76 + } + ], + "x-client-response-contracts": [ + { + "kind": "array", + "raw": "System.Collections.Generic.IReadOnlyList`1", + "rootType": "KLCOGEIGEBJ", + "item": { + "kind": "object", + "raw": "KLCOGEIGEBJ", + "rootType": "KLCOGEIGEBJ" + }, + "typeDescription": { + "name": "KLCOGEIGEBJ", + "found": true, + "file": "KLCOGEIGEBJ.cs", + "declaration": "public class KLCOGEIGEBJ : MKAMHOIHOJK", + "kind": "class", + "baseType": "MKAMHOIHOJK", + "properties": [ + { + "name": "DADOKMAOFJL", + "type": "long", + "accessibility": "public", + "kind": "property" + }, + { + "name": "GJPGKJJCPBK", + "type": "bool", + "accessibility": "public", + "kind": "property" + }, + { + "name": "IBLOJJBEKFF", + "type": "bool", + "accessibility": "public", + "kind": "property" + }, + { + "name": "JCLCOCEOAEP", + "type": "bool", + "accessibility": "public", + "kind": "property" + }, + { + "name": "JGJCBGEBLLO", + "type": "bool", + "accessibility": "public", + "kind": "property" + }, + { + "name": "PLFEEIJCHOH", + "type": "bool", + "accessibility": "public", + "kind": "property" + } + ], + "fields": [ + { + "name": "BJFNPKFOALK", + "type": "long", + "kind": "field" + } + ], + "enumValues": [], + "wireKeys": [ + "RoomId", + "IsDorm", + "CloningAllowed", + "DisableMicAutoMute", + "DisableRoomComments", + "EncryptVoiceChat" + ], + "isilFile": "KLCOGEIGEBJ.txt", + "isilType": "KLCOGEIGEBJ", + "inherits": "MKAMHOIHOJK", + "inheritedMembers": [ + { + "name": "ACBFDMLHFPB", + "type": "DateTime", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "AHGCOGFEEEE", + "type": "string", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "AKFBNELAMNA", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "BADIGBCKECA", + "type": "int", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "BNBLOBAEDEE", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "CDINMMPNAID", + "type": "NMJEKMMBDDE", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "CDNFGMHLDMJ", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "FIKEBGGCDFN", + "type": "string", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "GIBHIMGJNNO", + "type": "GPDIAKNEBKH", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "HPLBOMGACED", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "IGOPGMHHLKI", + "type": "string", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "JFEAPMIPNEP", + "type": "DPLPMKMFMPB", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "KHIJAFCHLIA", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "KLNJBBPNMBJ", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "KODBEJPEFOJ", + "type": "string", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "LPJLEMJFBPE", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "MGBDHBHCDMH", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "MIKDLDEALPN", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "OFONEIOEIED", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "OILEJFNPDDB", + "type": "HJPGEGENLPH", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "PEEFHKMOMKK", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + } + ], + "inheritedWireKeys": [ + "Name", + "Description", + "ImageName", + "WarningMask", + "CustomWarning", + "CreatorAccountId", + "State", + "Accessibility", + "SupportsLevelVoting", + "IsRRO", + "SupportsScreens", + "SupportsWalkVR", + "SupportsTeleportVR", + "SupportsVRLow", + "SupportsQuest2", + "SupportsMobile", + "SupportsJuniors", + "CreatedAt", + "Stats" + ] + } + }, + { + "kind": "unknown", + "raw": "", + "rootType": "unknown", + "typeDescription": { + "name": "unknown", + "found": false, + "kind": "object", + "wireKeys": [] + } + } + ] + } + }, + "/search_roomsandplaylists/{tail}": { + "get": { + "tags": [ + "rooms" + ], + "summary": "GET search_roomsandplaylists/", + "description": "Inferred from Rec Room 2020 client literal `search_roomsandplaylists/`. Expected result: Room summary/detail array or page.", + "operationId": "client_rooms_search_roomsandplaylists_tail_get", + "parameters": [ + { + "name": "tail", + "in": "path", + "required": true, + "schema": { + "type": "string" + }, + "description": "Format placeholder from client literal search_roomsandplaylists/" + } + ], + "responses": { + "200": { + "description": "Room summary/detail array or page.", + "content": { + "application/json": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/Room" + } + } + } + } + }, + "204": { + "description": "Success with no body, used by some mutation endpoints." + }, + "400": { + "description": "Client-visible validation or bad request error.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "401": { + "description": "Authentication required or token invalid.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "500": { + "description": "Server error shape tolerated by client wrappers.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + } + }, + "x-recroom-client-route-literal": "search_roomsandplaylists/", + "x-recroom-client-family": "rooms", + "x-recroom-client-service": "rooms/api", + "x-recroom-builds": [ + "2020.12.18" + ], + "x-recroom-inferred-http-method": "get", + "x-recroom-evidence": [ + { + "type": "OJMCBOKJFOF", + "method": "RecRoom.Async.IPromise`1> HFHDFLDCIEM(System.String CNBKKCJAHPP)", + "file": "OJMCBOKJFOF.txt", + "line": 4567 + }, + { + "type": "OJMCBOKJFOF+PEBCIEAPGON", + "method": "System.String HFHDFLDCIEM(System.String CNBKKCJAHPP)", + "file": "OJMCBOKJFOF_NestedType_PEBCIEAPGON.txt", + "line": 158 + } + ], + "x-client-response-contracts": [ + { + "kind": "array", + "raw": "System.Collections.Generic.IReadOnlyList`1", + "rootType": "MKAMHOIHOJK", + "item": { + "kind": "object", + "raw": "MKAMHOIHOJK", + "rootType": "MKAMHOIHOJK" + }, + "typeDescription": { + "name": "MKAMHOIHOJK", + "found": true, + "file": "MKAMHOIHOJK.cs", + "declaration": "public abstract class MKAMHOIHOJK : IFAIJAGLDFK, AKJKEMONOIL", + "kind": "class", + "baseType": "IFAIJAGLDFK", + "properties": [ + { + "name": "ACBFDMLHFPB", + "type": "DateTime", + "accessibility": "public", + "kind": "property" + }, + { + "name": "AHGCOGFEEEE", + "type": "string", + "accessibility": "public", + "kind": "property" + }, + { + "name": "AKFBNELAMNA", + "type": "bool", + "accessibility": "public", + "kind": "property" + }, + { + "name": "BADIGBCKECA", + "type": "int", + "accessibility": "public", + "kind": "property" + }, + { + "name": "BNBLOBAEDEE", + "type": "bool", + "accessibility": "public", + "kind": "property" + }, + { + "name": "CDINMMPNAID", + "type": "NMJEKMMBDDE", + "accessibility": "public", + "kind": "property" + }, + { + "name": "CDNFGMHLDMJ", + "type": "bool", + "accessibility": "public", + "kind": "property" + }, + { + "name": "FIKEBGGCDFN", + "type": "string", + "accessibility": "public", + "kind": "property" + }, + { + "name": "GIBHIMGJNNO", + "type": "GPDIAKNEBKH", + "accessibility": "public", + "kind": "property" + }, + { + "name": "HPLBOMGACED", + "type": "bool", + "accessibility": "public", + "kind": "property" + }, + { + "name": "IGOPGMHHLKI", + "type": "string", + "accessibility": "public", + "kind": "property" + }, + { + "name": "JFEAPMIPNEP", + "type": "DPLPMKMFMPB", + "accessibility": "public", + "kind": "property" + }, + { + "name": "KHIJAFCHLIA", + "type": "bool", + "accessibility": "public", + "kind": "property" + }, + { + "name": "KLNJBBPNMBJ", + "type": "bool", + "accessibility": "public", + "kind": "property" + }, + { + "name": "KODBEJPEFOJ", + "type": "string", + "accessibility": "public", + "kind": "property" + }, + { + "name": "LPJLEMJFBPE", + "type": "bool", + "accessibility": "public", + "kind": "property" + }, + { + "name": "MGBDHBHCDMH", + "type": "bool", + "accessibility": "public", + "kind": "property" + }, + { + "name": "MIKDLDEALPN", + "type": "bool", + "accessibility": "public", + "kind": "property" + }, + { + "name": "OFONEIOEIED", + "type": "bool", + "accessibility": "public", + "kind": "property" + }, + { + "name": "OILEJFNPDDB", + "type": "HJPGEGENLPH", + "accessibility": "public", + "kind": "property" + }, + { + "name": "PEEFHKMOMKK", + "type": "bool", + "accessibility": "public", + "kind": "property" + } + ], + "fields": [], + "enumValues": [], + "wireKeys": [ + "Name", + "Description", + "ImageName", + "WarningMask", + "CustomWarning", + "CreatorAccountId", + "State", + "Accessibility", + "SupportsLevelVoting", + "IsRRO", + "SupportsScreens", + "SupportsWalkVR", + "SupportsTeleportVR", + "SupportsVRLow", + "SupportsQuest2", + "SupportsMobile", + "SupportsJuniors", + "CreatedAt", + "Stats" + ], + "isilFile": "MKAMHOIHOJK.txt", + "isilType": "MKAMHOIHOJK" + } + }, + { + "kind": "unknown", + "raw": "", + "rootType": "unknown", + "typeDescription": { + "name": "unknown", + "found": false, + "kind": "object", + "wireKeys": [] + } + } + ] + } + }, + "/api/sanitize/v1": { + "get": { + "tags": [ + "sanitize" + ], + "summary": "GET api/sanitize/v1", + "description": "Inferred from Rec Room 2020 client literal `api/sanitize/v1`. Expected result: Generic object/array/success result inferred from client route.", + "operationId": "client_sanitize_api_sanitize_v1_get", + "parameters": [], + "responses": { + "200": { + "description": "Generic object/array/success result inferred from client route.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/SanitizeResult" + } + } + } + }, + "204": { + "description": "Success with no body, used by some mutation endpoints." + }, + "400": { + "description": "Client-visible validation or bad request error.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "401": { + "description": "Authentication required or token invalid.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "500": { + "description": "Server error shape tolerated by client wrappers.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + } + }, + "x-recroom-client-route-literal": "api/sanitize/v1", + "x-recroom-client-family": "sanitize", + "x-recroom-client-service": "api", + "x-recroom-builds": [ + "2020.03.10" + ], + "x-recroom-inferred-http-method": "get", + "x-recroom-evidence": [ + { + "type": "stringliteral.json", + "method": "", + "file": "Recnet-archive/decompiled/Il2CppDump/stringliteral.json", + "line": null + } + ] + } + }, + "/v1/datahistory/{p1}": { + "get": { + "tags": [ + "storage-cdn" + ], + "summary": "GET {0}v1/datahistory/{1}", + "description": "Inferred from Rec Room 2020 client literal `{0}v1/datahistory/{1}`. Expected result: Generic object/array/success result inferred from client route.", + "operationId": "client_storage_cdn_v1_datahistory_p1_get", + "parameters": [ + { + "name": "p1", + "in": "path", + "required": true, + "schema": { + "type": "string" + }, + "description": "Format placeholder from client literal {0}v1/datahistory/{1}" + } + ], + "responses": { + "200": { + "description": "Generic object/array/success result inferred from client route.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/GenericObject" + } + } + } + }, + "204": { + "description": "Success with no body, used by some mutation endpoints." + }, + "400": { + "description": "Client-visible validation or bad request error.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "401": { + "description": "Authentication required or token invalid.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "500": { + "description": "Server error shape tolerated by client wrappers.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + } + }, + "x-recroom-client-route-literal": "{0}v1/datahistory/{1}", + "x-recroom-client-family": "storage-cdn", + "x-recroom-client-service": "storage/images/cdn", + "x-recroom-builds": [ + "2020.03.10" + ], + "x-recroom-inferred-http-method": "get", + "x-recroom-evidence": [ + { + "type": "stringliteral.json", + "method": "", + "file": "Recnet-archive/decompiled/Il2CppDump/stringliteral.json", + "line": null + } + ] + } + }, + "/v1/datahistory/restore": { + "post": { + "tags": [ + "storage-cdn" + ], + "summary": "POST {0}v1/datahistory/restore", + "description": "Inferred from Rec Room 2020 client literal `{0}v1/datahistory/restore`. Expected result: Generic object/array/success result inferred from client route.", + "operationId": "client_storage_cdn_v1_datahistory_restore_post", + "parameters": [], + "responses": { + "200": { + "description": "Generic object/array/success result inferred from client route.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/GenericObject" + } + } + } + }, + "204": { + "description": "Success with no body, used by some mutation endpoints." + }, + "400": { + "description": "Client-visible validation or bad request error.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "401": { + "description": "Authentication required or token invalid.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "500": { + "description": "Server error shape tolerated by client wrappers.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + } + }, + "x-recroom-client-route-literal": "{0}v1/datahistory/restore", + "x-recroom-client-family": "storage-cdn", + "x-recroom-client-service": "storage/images/cdn", + "x-recroom-builds": [ + "2020.03.10" + ], + "x-recroom-inferred-http-method": "post", + "x-recroom-evidence": [ + { + "type": "stringliteral.json", + "method": "", + "file": "Recnet-archive/decompiled/Il2CppDump/stringliteral.json", + "line": null + } + ], + "requestBody": { + "required": false, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/GenericObject" + } + }, + "application/x-www-form-urlencoded": { + "schema": { + "$ref": "#/components/schemas/GenericObject" + } + }, + "multipart/form-data": { + "schema": { + "$ref": "#/components/schemas/GenericObject" + } + } + }, + "description": "Request body inferred. Exact DTO may be named in x-recroom-evidence method signature or companion DTO inventory." + } + } + } + }, + "components": { + "securitySchemes": { + "BearerAuth": { + "type": "http", + "scheme": "bearer", + "bearerFormat": "JWT" + } + }, + "schemas": { + "GenericObject": { + "type": "object", + "additionalProperties": true + }, + "Success": { + "type": "object", + "additionalProperties": true, + "properties": { + "Success": { + "type": "boolean" + }, + "success": { + "type": "boolean" + }, + "Message": { + "type": "string" + }, + "message": { + "type": "string" + } + } + }, + "ErrorResponse": { + "type": "object", + "additionalProperties": true, + "properties": { + "error": { + "type": "string" + }, + "error_description": { + "type": "string" + }, + "Message": { + "type": "string" + }, + "Error": { + "type": "string" + }, + "Code": { + "type": "integer" + } + } + }, + "NameServerResponse": { + "type": "object", + "additionalProperties": true, + "properties": { + "RecNetStatus": { + "type": "string" + }, + "Auth": { + "type": "string" + }, + "API": { + "type": "string" + }, + "WWW": { + "type": "string" + }, + "Notifications": { + "type": "string" + }, + "Images": { + "type": "string" + }, + "CDN": { + "type": "string" + }, + "Commerce": { + "type": "string" + }, + "Matchmaking": { + "type": "string" + }, + "Storage": { + "type": "string" + }, + "Chat": { + "type": "string" + }, + "Leaderboard": { + "type": "string" + }, + "Accounts": { + "type": "string" + }, + "Link": { + "type": "string" + }, + "RoomComments": { + "type": "string" + }, + "Clubs": { + "type": "string" + }, + "Rooms": { + "type": "string" + } + } + }, + "AccountProfile": { + "type": "object", + "additionalProperties": true, + "properties": { + "Id": { + "type": "integer" + }, + "Username": { + "type": "string" + }, + "DisplayName": { + "type": "string" + }, + "Bio": { + "type": "string" + }, + "ProfileImageName": { + "type": "string" + } + } + }, + "PlayerState": { + "type": "object", + "additionalProperties": true, + "properties": { + "PlayerId": { + "type": "integer" + }, + "Level": { + "type": "integer" + }, + "XP": { + "type": "integer" + }, + "Reputation": { + "type": "object", + "additionalProperties": true + } + } + }, + "AvatarState": { + "type": "object", + "additionalProperties": true, + "properties": { + "OutfitSelections": { + "type": "array", + "items": { + "type": "object", + "additionalProperties": true + } + }, + "Gifts": { + "type": "array", + "items": { + "type": "object", + "additionalProperties": true + } + } + } + }, + "Room": { + "type": "object", + "additionalProperties": true, + "properties": { + "RoomId": { + "type": "integer" + }, + "Id": { + "type": "integer" + }, + "Name": { + "type": "string" + }, + "Description": { + "type": "string" + }, + "CreatorAccountId": { + "type": "integer" + }, + "ImageName": { + "type": "string" + }, + "DataBlobName": { + "type": "string" + }, + "Scenes": { + "type": "array", + "items": { + "type": "object", + "additionalProperties": true + } + } + } + }, + "Playlist": { + "type": "object", + "additionalProperties": true, + "properties": { + "Id": { + "type": "integer" + }, + "Name": { + "type": "string" + }, + "Description": { + "type": "string" + }, + "Rooms": { + "type": "array", + "items": { + "$ref": "#/components/schemas/Room" + } + } + } + }, + "RoomInstance": { + "type": "object", + "additionalProperties": true, + "properties": { + "roomInstanceId": { + "type": "string" + }, + "roomId": { + "type": "integer" + }, + "subRoomId": { + "type": "integer" + }, + "location": { + "type": "string" + }, + "photonRegionId": { + "type": "string" + }, + "photonRoomId": { + "type": "string" + }, + "maxCapacity": { + "type": "integer" + }, + "isFull": { + "type": "boolean" + }, + "isPrivate": { + "type": "boolean" + }, + "isInProgress": { + "type": "boolean" + } + } + }, + "Invention": { + "type": "object", + "additionalProperties": true, + "properties": { + "InventionId": { + "type": "integer" + }, + "Id": { + "type": "integer" + }, + "Name": { + "type": "string" + }, + "Description": { + "type": "string" + }, + "CreatorAccountId": { + "type": "integer" + }, + "ImageName": { + "type": "string" + }, + "InventionDataFilename": { + "type": "string" + }, + "Version": { + "type": "integer" + }, + "Permission": { + "type": "integer" + } + } + }, + "ImageResource": { + "type": "object", + "additionalProperties": true, + "properties": { + "ImageName": { + "type": "string" + }, + "Name": { + "type": "string" + }, + "Url": { + "type": "string" + }, + "PlayerId": { + "type": "integer" + }, + "RoomId": { + "type": "integer" + } + } + }, + "RoomKey": { + "type": "object", + "additionalProperties": true, + "properties": { + "Id": { + "type": "integer" + }, + "RoomId": { + "type": "integer" + }, + "Key": { + "type": "string" + } + } + }, + "PlayerEvent": { + "type": "object", + "additionalProperties": true, + "properties": { + "PlayerEventId": { + "type": "integer" + }, + "Id": { + "type": "integer" + }, + "Name": { + "type": "string" + }, + "StartTime": { + "type": "string" + }, + "RoomId": { + "type": "integer" + } + } + }, + "Message": { + "type": "object", + "additionalProperties": true, + "properties": { + "Id": { + "type": "integer" + }, + "FromPlayerId": { + "type": "integer" + }, + "ToPlayerId": { + "type": "integer" + }, + "Body": { + "type": "string" + }, + "CreatedAt": { + "type": "string" + } + } + }, + "Club": { + "type": "object", + "additionalProperties": true, + "properties": { + "Id": { + "type": "integer" + }, + "Name": { + "type": "string" + }, + "Description": { + "type": "string" + }, + "MemberCount": { + "type": "integer" + } + } + }, + "EconomyResult": { + "type": "object", + "additionalProperties": true, + "properties": { + "CurrencyType": { + "type": "integer" + }, + "Balance": { + "type": "integer" + }, + "Items": { + "type": "array", + "items": { + "type": "object", + "additionalProperties": true + } + } + } + }, + "ConfigObject": { + "type": "object", + "additionalProperties": true + }, + "EloResult": { + "type": "object", + "additionalProperties": true, + "properties": { + "PlayerId": { + "type": "integer" + }, + "Elo": { + "type": "number" + }, + "Rank": { + "type": "integer" + } + } + }, + "StoreCatalog": { + "type": "object", + "additionalProperties": true, + "properties": { + "Items": { + "type": "array", + "items": { + "type": "object", + "additionalProperties": true + } + } + } + }, + "RelationshipState": { + "type": "object", + "additionalProperties": true, + "properties": { + "PlayerId": { + "type": "integer" + }, + "RelationshipType": { + "type": "integer" + } + } + }, + "CommunityBoard": { + "type": "object", + "additionalProperties": true + }, + "ObjectiveList": { + "type": "object", + "additionalProperties": true, + "properties": { + "Objectives": { + "type": "array", + "items": { + "type": "object", + "additionalProperties": true + } + } + } + }, + "SanitizeResult": { + "type": "object", + "additionalProperties": true, + "properties": { + "Text": { + "type": "string" + }, + "IsValid": { + "type": "boolean" + } + } + }, + "Announcement": { + "type": "object", + "additionalProperties": true, + "properties": { + "Id": { + "type": "integer" + }, + "Title": { + "type": "string" + }, + "Body": { + "type": "string" + } + } + }, + "VersionCheck": { + "type": "object", + "additionalProperties": true, + "properties": { + "ValidVersion": { + "type": "boolean" + }, + "UpdateRequired": { + "type": "boolean" + }, + "Message": { + "type": "string" + } + } + } + } + }, + "x-recroom-generation": { + "sources": [ + "C:/Users/you/Documents/Recnet/docs/recroom-2020-client-request-catalog.csv", + "C:/Users/you/Documents/Recnet/docs/recroom-2020-03-request-literals.csv" + ], + "decemberRows": 367, + "marchRows": 87, + "uniqueOperations": 245, + "notes": [ + "HTTP methods are inferred from route names, request-body evidence, and client wrapper signatures.", + "Schemas are permissive and family-specific because the IL2CPP C# dump often preserves DTO fields but not generic continuation bodies.", + "Use x-recroom-evidence for exact client class, method signature, file, and line." + ], + "responseContracts": "C:/Users/you/Documents/Recnet/docs/recroom-2020-client-response-contracts.json" + } +} diff --git a/docs/recroom-2020-client-request-catalog.csv b/docs/recroom-2020-client-request-catalog.csv new file mode 100644 index 0000000..ce9faae --- /dev/null +++ b/docs/recroom-2020-client-request-catalog.csv @@ -0,0 +1,368 @@ +"Family","Type","Method","File","Line","Route" +"account","PEGGCEDHBOF","RecRoom.Async.IPromise`1 NFKMGNHDCMN(System.Int32 GKLPIFBPGOD)","PEGGCEDHBOF.txt","3934","account/{0}" +"account","PEGGCEDHBOF","RecRoom.Async.IPromise`1 GBLAOEFPDAH(System.Int32 GKLPIFBPGOD)","PEGGCEDHBOF.txt","7866","account/{0}/bio" +"account","PEGGCEDHBOF+<>c","System.Void b__67_0()","PEGGCEDHBOF_NestedType___c.txt","587","account/{0}/bio" +"account","JDJGIBLMFKK","RecRoom.Async.IPromise`1> KGGMKBADLDM(System.Int32 GKLPIFBPGOD)","JDJGIBLMFKK.txt","9580","account/{0}/clubs" +"account","PEGGCEDHBOF","RecRoom.Async.IPromise`1> NHCHADDMPFC(System.Collections.Generic.List`1 ILNGMAANNDG)","PEGGCEDHBOF.txt","4917","account/bulk" +"account","PEGGCEDHBOF","RecRoom.Async.IPromise`1> NHCHADDMPFC(System.Collections.Generic.List`1 ILNGMAANNDG)","PEGGCEDHBOF.txt","4963","account/bulk?" +"account","PEGGCEDHBOF","RecRoom.Async.IPromise`1> NGENLHMLHNG(System.Collections.Generic.List`1 BGELOMIEKFK, System.String MAFJOJLDFJO, System.String LOOAPLFGOEN, System.String PJIBCCFFMNJ, GPOOLJODEGM OLBDHLLJJHF)","PEGGCEDHBOF.txt","9080","account/bulk/" +"account","PEGGCEDHBOF","RecRoom.Async.IPromise`1 HHFPNFANNEG()","PEGGCEDHBOF.txt","5418","account/create" +"account","PEGGCEDHBOF","RecRoom.Async.IPromise`1 HJFCFNCKDFO()","PEGGCEDHBOF.txt","3576","account/me" +"account","PEGGCEDHBOF","RecRoom.Async.IPromise OEBEELJDOHE(BestHTTP.HTTPMethods APAICGIHAGJ, BestHTTP.Forms.HTTPUrlEncodedForm MDOPLMHIKLP, System.String LOOAPLFGOEN)","PEGGCEDHBOF.txt","6704","account/me/" +"account","MHOKOMMOGKM","RecRoom.Async.IPromise PGJBIIPMMJP(BestHTTP.Forms.HTTPUrlEncodedForm MDOPLMHIKLP, System.String MKIPICKCFDM)","MHOKOMMOGKM.txt","2582","account/me/changepassword" +"account","MHOKOMMOGKM","RecRoom.Async.IPromise`1 PPKFJEINCCN()","MHOKOMMOGKM.txt","2203","account/me/haspassword" +"account","MHOKOMMOGKM+<>c","System.Void b__32_0()","MHOKOMMOGKM_NestedType___c.txt","584","account/me/haspassword" +"account","MHOKOMMOGKM","RecRoom.Async.IPromise PMKEHDEDNCK(System.String LJKKKECANAB)","MHOKOMMOGKM.txt","2779","account/recoverpassword" +"account","PEGGCEDHBOF","RecRoom.Async.IPromise`1> IBDGAKHBIAJ(System.String CNBKKCJAHPP)","PEGGCEDHBOF.txt","9372","account/search?name=" +"activities","CardBox","RecRoom.Async.IPromise`1> HMNGGFKCDGD()","CardBox.txt","2422","api/activities/charades/v1/words" +"activities","CardBox","System.Void Start()","CardBox.txt","2221","api/activities/charades/v1/words" +"activities","PDMJMMMNGJE","System.Collections.IEnumerator EOBJGALHFNP(BPHGKAEDBPE+OJDJIJDNFHE AFLPGGJMPOE)","PDMJMMMNGJE.txt","156","api/challenge/" +"activities","PDMJMMMNGJE","System.Void EKJPOJFLGFO(System.Int32 DKDDEIHEIBL, COLBJJAIEIO FMIIMOCIHCD)","PDMJMMMNGJE.txt","395","api/challenge/" +"activities","JDFDLJJGHIP","RecRoom.Async.IPromise`1 APOAEDMOIHO()","JDFDLJJGHIP.txt","266","api/royale/" +"activities","JDFDLJJGHIP","RecRoom.Async.IPromise`1 NGEFKCDJAMF(JDFDLJJGHIP+MatchCompleteStats CNALBEPOKJJ)","JDFDLJJGHIP.txt","477","api/royale/" +"avatar","NLEKGNENMCO","RecRoom.Async.IPromise PCKICLJFOBO(NLEKGNENMCO+LOCNECLOHCA HBBAANIPIMP, BPHGKAEDBPE+CBEOHBCIPEA AFLPGGJMPOE)","NLEKGNENMCO.txt","2488","{0}v2/gifts/consume/" +"avatar","NLEKGNENMCO","RecRoom.Async.IPromise DLDENNPGADN()","NLEKGNENMCO.txt","3480","api/avatar/" +"avatar","NLEKGNENMCO","RecRoom.Async.IPromise PCKICLJFOBO(NLEKGNENMCO+LOCNECLOHCA HBBAANIPIMP, BPHGKAEDBPE+CBEOHBCIPEA AFLPGGJMPOE)","NLEKGNENMCO.txt","2486","api/avatar/" +"avatar","NLEKGNENMCO","RecRoom.Async.IPromise`1 JEKOPJCCHKB(GiftManager+LCLKAFOPBLD LHOMKMINCHH, System.Nullable`1 PCCLFNLJAMG, System.Boolean DKHOCHIJLBG)","NLEKGNENMCO.txt","1504","api/avatar/" +"avatar","NLEKGNENMCO","RecRoom.Async.IPromise`1 JEKOPJCCHKB(GiftManager+LCLKAFOPBLD LHOMKMINCHH)","NLEKGNENMCO.txt","1794","api/avatar/" +"avatar","NLEKGNENMCO","RecRoom.Async.IPromise`1> EPOGKGLKJHD()","NLEKGNENMCO.txt","1226","api/avatar/" +"avatar","NLEKGNENMCO","System.Collections.IEnumerator FHJKJMFABGK(BPHGKAEDBPE+CBEOHBCIPEA AFLPGGJMPOE)","NLEKGNENMCO.txt","3101","api/avatar/" +"avatar","NLEKGNENMCO","System.Collections.IEnumerator KLHANCHHJBD(CDNNKFCCONN GKDEAOJLJFI, BPHGKAEDBPE+CBEOHBCIPEA AFLPGGJMPOE)","NLEKGNENMCO.txt","3229","api/avatar/" +"avatar","NLEKGNENMCO","System.Void OFJLGPLDEKJ()","NLEKGNENMCO.txt","3748","api/avatar/" +"avatar","NLEKGNENMCO+LFBBKJKCJFF","System.Boolean MoveNext()","NLEKGNENMCO_NestedType_LFBBKJKCJFF.txt","147","api/avatar/" +"avatar","NLEKGNENMCO","RecRoom.Async.IPromise`1> KLPMNFCFOLE(System.Collections.Generic.List`1 EGLBFNGKDLD)","NLEKGNENMCO.txt","2931","api/avatar/v1/lockeditems?" +"bug-reporting","RecNet.BugReporting","RecRoom.Async.IPromise ReportBug(System.String summary, System.String description, System.Byte[] screenshotData, System.Byte[] outputLogData, System.String testCaseKey)","RecNet\BugReporting.txt","146","api/bugreporting/" +"clubs","GNPDMBPGHBH","System.String JDEIGGFCBPF(System.Int64 AOMBLGBCENO)","GNPDMBPGHBH.txt","630","{0}/club/{1}" +"clubs","JDJGIBLMFKK","RecRoom.Async.IPromise`1 ONCKANJNGLA(System.Int64 AOMBLGBCENO)","JDJGIBLMFKK.txt","1167","announcements/club/{0}" +"clubs","JDJGIBLMFKK","RecRoom.Async.IPromise`1 AAOLBAJFCOJ(JDJGIBLMFKK+DHPONMGBFJE JMFLHIIJFKL)","JDJGIBLMFKK.txt","398","announcements/club/{0}" +"clubs","JDJGIBLMFKK","RecRoom.Async.IPromise HCGFMHLEDPE(System.Int64 AOMBLGBCENO, System.Int64 LOHFFDEGIMK)","JDJGIBLMFKK.txt","2462","announcements/club/{0}/{1}" +"clubs","JDJGIBLMFKK","RecRoom.Async.IPromise POHLPICNHJD(JDJGIBLMFKK+FOEPIDJCLMC JMFLHIIJFKL)","JDJGIBLMFKK.txt","789","announcements/club/{0}/{1}" +"clubs","JDJGIBLMFKK","RecRoom.Async.IPromise OFBNAHIOHON(System.Int64 AOMBLGBCENO, System.Int64 LOHFFDEGIMK)","JDJGIBLMFKK.txt","2865","announcements/club/{0}/{1}/read" +"clubs","JDJGIBLMFKK","RecRoom.Async.IPromise LBOOMIDNLPP(System.Int64 AOMBLGBCENO, LCCEEFHOBEN+CJFENPHAAHI MEABFEIBEMP, System.String EFDBFLPKHKA)","JDJGIBLMFKK.txt","21791","api/clubreporting/v1/report" +"clubs","JDJGIBLMFKK","RecRoom.Async.IPromise IGHDIAEPHJD(System.Int64 AOMBLGBCENO, System.String DHANCEHHIDH)","JDJGIBLMFKK.txt","13088","club/{0}" +"clubs","JDJGIBLMFKK","RecRoom.Async.IPromise`1 NKGOFNILGPL(System.Int64 AOMBLGBCENO, System.Boolean OHLIGBELLLH = True)","JDJGIBLMFKK.txt","10425","club/{0}" +"clubs","JDJGIBLMFKK","RecRoom.Async.IPromise`1 ELLIKHHEJFM(System.Int64 AOMBLGBCENO, System.Int32 EFBDCIJMFGD, System.String HFLPBHHAFIO)","JDJGIBLMFKK.txt","12298","club/{0}/additionalimage/{1}" +"clubs","JDJGIBLMFKK","RecRoom.Async.IPromise`1 HELJLMINDFD(System.Int64 AOMBLGBCENO, System.Nullable`1 HNHLJONGKHB)","JDJGIBLMFKK.txt","12449","club/{0}/clubhouse" +"clubs","JDJGIBLMFKK+BKCHBCIJHBN","RecRoom.Async.IPromise`1 b__0()","JDJGIBLMFKK_NestedType_BKCHBCIJHBN.txt","202","club/{0}/details" +"clubs","JDJGIBLMFKK","RecRoom.Async.IPromise`1 OBFKAFGALDM(System.Int64 AOMBLGBCENO, System.String HFLPBHHAFIO)","JDJGIBLMFKK.txt","12138","club/{0}/mainimage" +"clubs","JDJGIBLMFKK","RecRoom.Async.IPromise`1 BIOJPNFPNCE(System.Int64 AOMBLGBCENO, System.Int32 GKLPIFBPGOD)","JDJGIBLMFKK.txt","15776","club/{0}/members/{1}" +"clubs","JDGDFALBCDJ","RecRoom.Async.IPromise HHLPPOBHMOC()","JDGDFALBCDJ.txt","426","club/{0}/members/acceptinvite" +"clubs","JDJGIBLMFKK","RecRoom.Async.IPromise FAOINNCBNFC(System.Int64 AOMBLGBCENO)","JDJGIBLMFKK.txt","20586","club/{0}/members/acceptinvite" +"clubs","JDJGIBLMFKK","RecRoom.Async.IPromise AGEGBKKPJNN(System.Int64 AOMBLGBCENO, System.Int32 GKLPIFBPGOD)","JDJGIBLMFKK.txt","18418","club/{0}/members/acceptrequest" +"clubs","JDJGIBLMFKK","RecRoom.Async.IPromise FKPIMLCILLI(System.Int64 AOMBLGBCENO, System.Collections.Generic.IEnumerable`1 ILNGMAANNDG)","JDJGIBLMFKK.txt","18831","club/{0}/members/acceptrequests" +"clubs","JDJGIBLMFKK","RecRoom.Async.IPromise OKICAHCMPFL(System.Int64 AOMBLGBCENO, System.Int32 GKLPIFBPGOD)","JDJGIBLMFKK.txt","21265","club/{0}/members/ban" +"clubs","JDJGIBLMFKK","RecRoom.Async.IPromise JLBCCEIBFJJ(System.Int64 AOMBLGBCENO, System.Int32 GKLPIFBPGOD, PPGPAHNMGEC OCHEGLOFMEA)","JDJGIBLMFKK.txt","21094","club/{0}/members/changetype" +"clubs","JDGDFALBCDJ","RecRoom.Async.IPromise BDFBMBCKJEP()","JDGDFALBCDJ.txt","697","club/{0}/members/declineinvite" +"clubs","JDJGIBLMFKK","RecRoom.Async.IPromise GJJBKCEMLDC(System.Int64 AOMBLGBCENO)","JDJGIBLMFKK.txt","20675","club/{0}/members/declineinvite" +"clubs","JDJGIBLMFKK","RecRoom.Async.IPromise LPCHODIHIBB(System.Int64 AOMBLGBCENO, System.Int32 GKLPIFBPGOD)","JDJGIBLMFKK.txt","19048","club/{0}/members/denyrequest" +"clubs","JDJGIBLMFKK","RecRoom.Async.IPromise CJBOECCIIJB(System.Int64 AOMBLGBCENO, System.Collections.Generic.IEnumerable`1 ILNGMAANNDG)","JDJGIBLMFKK.txt","19461","club/{0}/members/denyrequests" +"clubs","JDJGIBLMFKK","RecRoom.Async.IPromise AJCDOLIBBKC(System.Int64 AOMBLGBCENO, PJCALHOPMKJ IEGCAIGJBBP, System.Int32 ENGEEKIMIGO)","JDJGIBLMFKK.txt","20450","club/{0}/members/directJoin" +"clubs","JDJGIBLMFKK","RecRoom.Async.IPromise FEIADJMHICD(System.Int64 AOMBLGBCENO, System.Int32 GKLPIFBPGOD, PPGPAHNMGEC PGMGFKOCEDG)","JDJGIBLMFKK.txt","19703","club/{0}/members/invite" +"clubs","JDJGIBLMFKK","RecRoom.Async.IPromise HOHPKNPHCFK(System.Int64 AOMBLGBCENO, System.Collections.Generic.List`1 ILNGMAANNDG, OFIEEDOMGPA NAMECJCFEDN)","JDJGIBLMFKK.txt","20070","club/{0}/members/invitemembers" +"clubs","JDJGIBLMFKK","RecRoom.Async.IPromise EBADBEIGOEN(System.Int64 AOMBLGBCENO)","JDJGIBLMFKK.txt","20745","club/{0}/members/leave" +"clubs","JDJGIBLMFKK","RecRoom.Async.IPromise LDOGPIHJOLP(System.Int64 AOMBLGBCENO, System.Int32 GKLPIFBPGOD)","JDJGIBLMFKK.txt","20895","club/{0}/members/remove" +"clubs","JDJGIBLMFKK","RecRoom.Async.IPromise NJKBIMEEAPE(System.Int64 AOMBLGBCENO)","JDJGIBLMFKK.txt","18249","club/{0}/members/requesttojoin" +"clubs","JDJGIBLMFKK","RecRoom.Async.IPromise OGMGOBHPMFC(System.Int64 AOMBLGBCENO, System.Int32 GKLPIFBPGOD)","JDJGIBLMFKK.txt","21435","club/{0}/members/unban" +"clubs","JDJGIBLMFKK","RecRoom.Async.IPromise`1 CKJIKPOIMFE(JDJGIBLMFKK+ACNILMIFDJJ JMFLHIIJFKL)","JDJGIBLMFKK.txt","11620","club/{0}/modify" +"clubs","JDJGIBLMFKK","RecRoom.Async.IPromise`1 KOPAANDJNKB(JDJGIBLMFKK+FHFOCPGBMPB JMFLHIIJFKL)","JDJGIBLMFKK.txt","12001","club/{0}/modifydetails" +"clubs","JDJGIBLMFKK","RecRoom.Async.IPromise`1 GHBJEDCEKLK(JHEEFBMODPG OBJEOMAGODL)","JDJGIBLMFKK.txt","13631","club/{0}/permissions/{1}" +"clubs","JDJGIBLMFKK","RecRoom.Async.IPromise`1> DIAPLKMFFKO(System.Int32 GKLPIFBPGOD)","JDJGIBLMFKK.txt","9416","club/account/{0}/created" +"clubs","JDJGIBLMFKK","System.String JANFGLBCBMJ(System.Int32 GKLPIFBPGOD)","JDJGIBLMFKK.txt","7880","club/account/{0}/created" +"clubs","JDJGIBLMFKK","System.Void PJBLNGJKLEB(System.Int64 AOMBLGBCENO)","JDJGIBLMFKK.txt","26175","club/account/{0}/created" +"clubs","JDJGIBLMFKK","RecRoom.Async.IPromise`1> PAKNBFOJENP()","JDJGIBLMFKK.txt","13314","club/categoryTags" +"clubs","JDJGIBLMFKK","RecRoom.Async.IPromise`1 KMPJFFBGJMD(JDJGIBLMFKK+ACNILMIFDJJ JMFLHIIJFKL)","JDJGIBLMFKK.txt","11381","club/create" +"clubs","JDJGIBLMFKK","RecRoom.Async.IPromise`1 DBDOOMCNNFE()","JDJGIBLMFKK.txt","10003","club/home/me" +"clubs","JDJGIBLMFKK+<>c","System.Void b__106_0()","JDJGIBLMFKK_NestedType___c.txt","995","club/home/me" +"clubs","JDJGIBLMFKK+<>c","System.Void b__106_2()","JDJGIBLMFKK_NestedType___c.txt","954","club/home/me" +"clubs","JDJGIBLMFKK","RecRoom.Async.IPromise`1> LJJLGJIMJED()","JDJGIBLMFKK.txt","9275","club/mine/created" +"clubs","JDJGIBLMFKK","System.Void PJBLNGJKLEB(System.Int64 AOMBLGBCENO)","JDJGIBLMFKK.txt","26134","club/mine/created" +"clubs","JDJGIBLMFKK","System.Void PJBLNGJKLEB(System.Int64 AOMBLGBCENO)","JDJGIBLMFKK.txt","26326","club/mine/created" +"clubs","JDJGIBLMFKK","RecRoom.Async.IPromise`1> DFDMGNBKEKO()","JDJGIBLMFKK.txt","9817","club/mine/member" +"clubs","JDJGIBLMFKK","System.Void PJBLNGJKLEB(System.Int64 AOMBLGBCENO)","JDJGIBLMFKK.txt","26194","club/mine/member" +"clubs","JDJGIBLMFKK","System.Void PJBLNGJKLEB(System.Int64 AOMBLGBCENO)","JDJGIBLMFKK.txt","26330","club/mine/member" +"clubs","JDJGIBLMFKK","RecRoom.Async.IPromise`1> DDIJPHDEDDM(System.Int64 AOMBLGBCENO, System.Collections.Generic.List`1 ILNGMAANNDG)","JDJGIBLMFKK.txt","16847","members/bulk" +"clubs","JDJGIBLMFKK","RecRoom.Async.IPromise`1> DDIJPHDEDDM(System.Int64 AOMBLGBCENO, System.Collections.Generic.List`1 ILNGMAANNDG)","JDJGIBLMFKK.txt","16893","members/bulk?" +"config-settings","FHLGJDFHOKL","RecRoom.Async.IPromise MCDNGDMJHBE()","FHLGJDFHOKL.txt","502","/config/{0}" +"config-settings","BBEFMHAEEEA","RecRoom.Async.IPromise BGNDHOHFJBN()","BBEFMHAEEEA.txt","950","api/config/" +"config-settings","BBEFMHAEEEA","RecRoom.Async.IPromise OHHAEMHMIFE()","BBEFMHAEEEA.txt","655","api/config/" +"config-settings","BBEFMHAEEEA","RecRoom.Async.IPromise`1> EAJPLMPNPIJ(System.Int32 CKEBCMEKCJH)","BBEFMHAEEEA.txt","1147","api/config/" +"config-settings","BBEFMHAEEEA","RecRoom.Async.IPromise`1 GMOMCLADPII()","BBEFMHAEEEA.txt","1230","api/config/v1/freegiftbutton" +"config-settings","IGCCFMFHBBN","System.Void .cctor()","IGCCFMFHBBN.txt","1921","api/gameconfigs/" +"config-settings","JFDILELKPAL","RecRoom.Async.IPromise`1> HOHPJKNPEGK()","JFDILELKPAL.txt","48","api/settings/" +"config-settings","JFDILELKPAL","System.Collections.IEnumerator LHGBAFBJAHM(OHLKCDKEGMN AOKDECDAEEG, BPHGKAEDBPE+OJDJIJDNFHE AFLPGGJMPOE)","JFDILELKPAL.txt","224","api/settings/" +"economy","FOEHJBDMMBH","System.Collections.IEnumerator LCELAOEOIJO(OOCCCGPICOG BAJGNABECMN, System.Int32 FKKAPHAMPMG, BPHGKAEDBPE+CBEOHBCIPEA AFLPGGJMPOE)","FOEHJBDMMBH.txt","840","{0}v1/consume" +"economy","COFHGNFJMOG","RecRoom.Async.IPromise`1 HDLJGFEDGLH(System.String ENJEOLBEALP, System.Nullable`1 LNBJKLOINED)","COFHGNFJMOG.txt","1242","/consume" +"economy","COICJCJBABL","RecRoom.Async.IPromise AOJJODPOJFO()","COICJCJBABL.txt","520","api/gamerewards/v1/pending" +"economy","COICJCJBABL","RecRoom.Async.IPromise ONNNOAKDIIP(COICJCJBABL+HNJGHCGJJFC FDAEIMEHDJJ, System.Int32 IAAKEHDHCAC)","COICJCJBABL.txt","1055","api/gamerewards/v1/select" +"economy","COFHGNFJMOG","RecRoom.Async.IPromise`1 LFHFLODHPJJ()","COFHGNFJMOG.txt","95","pageview/consume" +"elo","RecNet.Elo","System.Void UpdatePlayersElo(RecNet.Elo+PlayersEloUpdateDTO JFKDDPJDCDC)","RecNet\Elo.txt","141","api/PlayerElo/" +"equipment","ECINAMCDBJO","System.Void EEDJOJINECJ()","ECINAMCDBJO.txt","468","api/equipment/" +"equipment","ECINAMCDBJO+GPBDCOCADGE","System.Boolean MoveNext()","ECINAMCDBJO_NestedType_GPBDCOCADGE.txt","147","api/equipment/" +"groups","EJECIMCPGMG","RecRoom.Async.IPromise`1 CCDHCLAOFOJ(System.Int64 BCNAOOIPEJO)","EJECIMCPGMG.txt","337","api/groups/" +"groups","EJECIMCPGMG","RecRoom.Async.IPromise`1 PDJKECHIHNP(System.Int64 CJFGEMGOJHB)","EJECIMCPGMG.txt","555","api/groups/" +"groups","EJECIMCPGMG","System.Collections.IEnumerator JAHJIFFICHN(System.String NDNLEGKJGCD, System.String LJIGOCDPEJF, System.String HFLPBHHAFIO, BPHGKAEDBPE+OJDJIJDNFHE AFLPGGJMPOE)","EJECIMCPGMG.txt","803","api/groups/" +"groups","EJECIMCPGMG","System.Collections.IEnumerator JELCDBGNAHK(System.Int64 BCNAOOIPEJO, BPHGKAEDBPE+OJDJIJDNFHE AFLPGGJMPOE)","EJECIMCPGMG.txt","1213","api/groups/" +"groups","EJECIMCPGMG","System.Collections.IEnumerator KBHKIILLNAC(System.Int64 BCNAOOIPEJO, BPHGKAEDBPE+OJDJIJDNFHE AFLPGGJMPOE)","EJECIMCPGMG.txt","1040","api/groups/" +"groups","EJECIMCPGMG","System.Collections.IEnumerator NJFPGHDIANJ(System.String NDNLEGKJGCD, BPHGKAEDBPE+OJDJIJDNFHE AFLPGGJMPOE)","EJECIMCPGMG.txt","1336","api/groups/" +"images","OHDHPENHDAP","RecRoom.Async.IPromise BFHNENPOEFB(System.Int64 LKNNMPCBCKM, System.Boolean JBAGLLBLEEN)","OHDHPENHDAP.txt","1571","api/images/" +"images","OHDHPENHDAP","RecRoom.Async.IPromise FEOMMGBJFIN(System.Int64 LKNNMPCBCKM)","OHDHPENHDAP.txt","2387","api/images/" +"images","OHDHPENHDAP","RecRoom.Async.IPromise OFEBNGPIJMB()","OHDHPENHDAP.txt","4467","api/images/" +"images","OHDHPENHDAP","RecRoom.Async.IPromise`1 KIIGBNCPAGC()","OHDHPENHDAP.txt","4847","api/images/" +"images","OHDHPENHDAP","RecRoom.Async.IPromise`1> NAMIJGLEPMH(System.Int64 HNHLJONGKHB, OHDHPENHDAP+ALLECIACGAH ECENAMEKNEH, OHDHPENHDAP+OGDBPFGJMOO MOIMCDPIPOL, System.Nullable`1 KKDPFGADCEK, System.Nullable`1 MADNOGDODFA)","OHDHPENHDAP.txt","5403","api/images/" +"images","OHDHPENHDAP","RecRoom.Async.IPromise`1 MPNCIPABNKC(System.Byte[] IJKBAADKCBM, OHDHPENHDAP+SavedImageMetaDTO FIBFIEEBFIL)","OHDHPENHDAP.txt","787","api/images/" +"images","OHDHPENHDAP","System.Collections.IEnumerator BEPFIALDMHK(BPHGKAEDBPE+OJDJIJDNFHE> AFLPGGJMPOE)","OHDHPENHDAP.txt","394","api/images/" +"images","OHDHPENHDAP","System.Collections.IEnumerator EFGBDOJHLCE(System.String HFLPBHHAFIO, OHDHPENHDAP+IMKNMFOOLGF PONCIIJOHIE, BPHGKAEDBPE+CBEOHBCIPEA AFLPGGJMPOE = null)","OHDHPENHDAP.txt","1203","api/images/" +"images","OHDHPENHDAP","System.Collections.IEnumerator GLPDDNENLHN(System.String HFLPBHHAFIO, BPHGKAEDBPE+CBEOHBCIPEA AFLPGGJMPOE = null)","OHDHPENHDAP.txt","1427","api/images/" +"images","OHDHPENHDAP","System.Collections.IEnumerator JPDBMCKFKLA(System.String HFLPBHHAFIO, BPHGKAEDBPE+CBEOHBCIPEA AFLPGGJMPOE = null)","OHDHPENHDAP.txt","1316","api/images/" +"inventions","BBHENFCNLAB","RecRoom.Async.IPromise`1 ANJHOOPIAKM(System.Int64 OEMDIAHHILF, System.Boolean JBAGLLBLEEN)","BBHENFCNLAB.txt","7610","api/inventions/" +"inventions","BBHENFCNLAB","RecRoom.Async.IPromise`1 BEPNJCGNIKA(System.Int64 OEMDIAHHILF, HECIICKPCDN AEHDODIANMG, System.Nullable`1 MACNIENMFHJ = null)","BBHENFCNLAB.txt","6169","api/inventions/" +"inventions","BBHENFCNLAB","RecRoom.Async.IPromise`1 HDFICFBNFOK(System.Int64 OEMDIAHHILF, System.Int32 MACNIENMFHJ)","BBHENFCNLAB.txt","6479","api/inventions/" +"inventions","BBHENFCNLAB","RecRoom.Async.IPromise`1 HELBOGAPCKE(OBBBPCBIMME LDNFNNNHPPB)","BBHENFCNLAB.txt","5990","api/inventions/" +"inventions","BBHENFCNLAB","RecRoom.Async.IPromise`1 MEIJBGBINLO(System.Int64 OEMDIAHHILF)","BBHENFCNLAB.txt","6327","api/inventions/" +"inventions","BBHENFCNLAB","RecRoom.Async.IPromise`1 NNDFHPLBNEN(System.Int64 OEMDIAHHILF, System.String LJIGOCDPEJF)","BBHENFCNLAB.txt","1731","api/inventions/" +"inventions","BBHENFCNLAB","RecRoom.Async.IPromise`1 OCBDPGLHIJL(System.Int64 OEMDIAHHILF, HECIICKPCDN ODABIMLCOIP)","BBHENFCNLAB.txt","2123","api/inventions/" +"inventions","BBHENFCNLAB","RecRoom.Async.IPromise`1 ONLOPCCOFMM(System.Int64 OEMDIAHHILF, System.String DLFBBIAHDMO)","BBHENFCNLAB.txt","1927","api/inventions/" +"inventions","BBHENFCNLAB","RecRoom.Async.IPromise`1 PONHAAALIGM(System.Int64 OEMDIAHHILF, System.String MMBOKOLAJFH)","BBHENFCNLAB.txt","1533","api/inventions/" +"inventions","BBHENFCNLAB","RecRoom.Async.IPromise`1 IIPHPOBADGF(System.Int64 OEMDIAHHILF)","BBHENFCNLAB.txt","3629","api/inventions/" +"inventions","BBHENFCNLAB","RecRoom.Async.IPromise`1 BGDGKHPGEHK(System.Int64 OEMDIAHHILF, FDEGHHFBJJO MEABFEIBEMP, System.String EFDBFLPKHKA)","BBHENFCNLAB.txt","7417","api/inventions/" +"inventions","BBHENFCNLAB","RecRoom.Async.IPromise`1 LGFHMMCICNF(System.Int64 OEMDIAHHILF, System.Collections.Generic.List`1 LBAOCHFCLPO, System.Collections.Generic.List`1 NOLONFFJNPA)","BBHENFCNLAB.txt","2353","api/inventions/" +"inventions","BBHENFCNLAB","RecRoom.Async.IPromise`1 FDEMGICNKPI(System.Int64 OEMDIAHHILF)","BBHENFCNLAB.txt","3127","api/inventions/" +"inventions","BBHENFCNLAB","RecRoom.Async.IPromise`1 IGFBODDECPK(System.Int64 OEMDIAHHILF)","BBHENFCNLAB.txt","5821","api/inventions/" +"inventions","BBHENFCNLAB","RecRoom.Async.IPromise`1> OFPICHENKNL(System.Int64 OEMDIAHHILF)","BBHENFCNLAB.txt","4782","api/inventions/" +"inventions","BBHENFCNLAB","RecRoom.Async.IPromise`1> CKGFHIMCOHI(System.String DFAMLDBFENB)","BBHENFCNLAB.txt","7207","api/inventions/" +"inventions","BBHENFCNLAB","RecRoom.Async.IPromise`1> CKGFHIMCOHI(System.String DFAMLDBFENB)","BBHENFCNLAB.txt","7213","api/inventions/" +"inventions","BBHENFCNLAB","RecRoom.Async.IPromise`1> FDABNLEFMKM(System.Int64 HNHLJONGKHB)","BBHENFCNLAB.txt","5140","api/inventions/" +"inventions","BBHENFCNLAB","RecRoom.Async.IPromise`1> JLGCBDHFIBH(System.Collections.Generic.List`1 POGCOENDJDJ)","BBHENFCNLAB.txt","3371","api/inventions/" +"inventions","BBHENFCNLAB","RecRoom.Async.IPromise`1> LIPBAJOAIJC(BBHENFCNLAB+IBIIKANECFA KINAEJLCDEG)","BBHENFCNLAB.txt","5586","api/inventions/" +"inventions","BBHENFCNLAB","System.Boolean JDBIJGFEJAM(System.Int64 OEMDIAHHILF)","BBHENFCNLAB.txt","10717","api/inventions/" +"inventions","BBHENFCNLAB","System.String HFIOIJPLLDP(System.Int64 OEMDIAHHILF)","BBHENFCNLAB.txt","41","api/inventions/" +"inventions","BBHENFCNLAB","System.String HIGLOHHJDBN(System.Int64 HNHLJONGKHB)","BBHENFCNLAB.txt","4912","api/inventions/" +"inventions","BBHENFCNLAB","System.String KKGEAELNFCK(System.Int64 OEMDIAHHILF)","BBHENFCNLAB.txt","89","api/inventions/" +"inventions","BBHENFCNLAB","System.String PAMAJCIBNDP(System.Int64 OEMDIAHHILF, System.Int32 INKNNJHDIFD)","BBHENFCNLAB.txt","11125","api/inventions/" +"inventions","BBHENFCNLAB","System.Void .cctor()","BBHENFCNLAB.txt","11687","api/inventions/" +"inventions","BBHENFCNLAB","System.Void KJMGMEKAEFF(System.Int64 HNHLJONGKHB)","BBHENFCNLAB.txt","4992","api/inventions/" +"inventions","BBHENFCNLAB","System.Void LBGHAEMBHHA(System.Int64 OEMDIAHHILF)","BBHENFCNLAB.txt","3891","api/inventions/" +"inventions","BBHENFCNLAB","System.Void LPIPNOFPFEP(System.Int64 OEMDIAHHILF, System.Collections.Generic.List`1 LBAOCHFCLPO)","BBHENFCNLAB.txt","11351","api/inventions/" +"inventions","BBHENFCNLAB","RecRoom.Async.IPromise`1 DEOOEGHNIAJ(System.Collections.Generic.List`1 POGCOENDJDJ)","BBHENFCNLAB.txt","7823","api/inventions/v1/fulllineageowner?" +"inventions","BBHENFCNLAB+OIJIEFMJMKO","RecRoom.Async.IPromise`1 b__0(System.String filename)","BBHENFCNLAB_NestedType_OIJIEFMJMKO.txt","216","api/inventions/v3/addversion" +"inventions","BBHENFCNLAB+FODDLIKPKPE","RecRoom.Async.IPromise`1 b__0(System.String filename)","BBHENFCNLAB_NestedType_FODDLIKPKPE.txt","250","api/inventions/v4/save" +"matchmaking","RecNet.Matchmaking","RecRoom.Async.IPromise`1 IIDEDGKAGOE(PLILLKHMNDA EGLGJIONCCP, System.Int32[] DKFBAOLAEFE = null, System.Boolean KJHCANCBKAL = False)","RecNet\Matchmaking.txt","8847","goto/club/{0}" +"matchmaking","RecNet.Matchmaking","RecRoom.Async.IPromise`1 FBLFCNDIPMP(System.String BFGCDJFNJLE, System.String KIIFGNILJEA, System.Int32[] DKFBAOLAEFE = null, System.Boolean KJHCANCBKAL = True, System.Boolean PBCPIEHFDBH = False)","RecNet\Matchmaking.txt","8226","goto/code/" +"matchmaking","RecNet.Matchmaking","RecRoom.Async.IPromise`1 MLIILFNOOOK(CCMBKDINCAH IAALIIFNHNP, System.Int32[] DKFBAOLAEFE = null, System.Boolean KJHCANCBKAL = False)","RecNet\Matchmaking.txt","8737","goto/event/{0}" +"matchmaking","RecNet.Matchmaking","RecRoom.Async.IPromise`1 MGKFDNPCDBP(System.Int64 ANAPBECHGLI, System.Int32[] DKFBAOLAEFE = null, System.Boolean KJHCANCBKAL = False, System.String APJJOJMDLNP = null)","RecNet\Matchmaking.txt","9156","goto/instance/{0}" +"matchmaking","RecNet.Matchmaking","RecRoom.Async.IPromise`1 ALCJNMAOIGH(System.Int64 IAHDKAIJJLB, System.Int64 HNHLJONGKHB, System.Int32[] DKFBAOLAEFE = null, System.Boolean KJHCANCBKAL = False)","RecNet\Matchmaking.txt","9294","goto/invite/{0}" +"matchmaking","RecNet.Matchmaking","RecRoom.Async.IPromise OAHNGAMAPNB()","RecNet\Matchmaking.txt","10314","goto/none" +"matchmaking","RecNet.Matchmaking","RecRoom.Async.IPromise`1 IMMCGNNPIAD(System.Int32 CJFGEMGOJHB, System.Int32[] DKFBAOLAEFE = null, System.Boolean KJHCANCBKAL = False)","RecNet\Matchmaking.txt","8980","goto/player/{0}" +"matchmaking","RecNet.Matchmaking","RecRoom.Async.IPromise`1 NDKKNPMAGNK(System.String APJJOJMDLNP, System.Boolean OFFEBBLOEJA = False, System.Int32[] DKFBAOLAEFE = null, System.Boolean KJHCANCBKAL = False)","RecNet\Matchmaking.txt","8050","goto/playlist/" +"matchmaking","RecNet.Matchmaking","RecRoom.Async.IPromise`1 GDCFFOJEIMD()","RecNet\Matchmaking.txt","7509","goto/room/" +"matchmaking","RecNet.Matchmaking","RecRoom.Async.IPromise`1 ILPIDHICKMB(System.String BFGCDJFNJLE, System.Boolean OFFEBBLOEJA = False, System.Int32[] DKFBAOLAEFE = null, System.Boolean KJHCANCBKAL = False, System.String APJJOJMDLNP = null, System.Boolean PBCPIEHFDBH = False)","RecNet\Matchmaking.txt","8426","goto/room/" +"matchmaking","RecNet.Matchmaking","RecRoom.Async.IPromise`1 JLMFIHJLNBL(System.String HFDPAGDDNDE, System.Int32[] DKFBAOLAEFE, System.Boolean PBCPIEHFDBH, System.Boolean KJHCANCBKAL, System.Boolean ALOAFENOBGN)","RecNet\Matchmaking.txt","7882","goto/room/" +"matchmaking","RecNet.Matchmaking","RecRoom.Async.IPromise`1 KNCHJJDKJGK(System.String BFGCDJFNJLE, System.String HFDPAGDDNDE, RecNet.Matchmaking+GIJOGKJCEKG MIAGCJJACCL = 0, System.Int32[] DKFBAOLAEFE = null, System.Boolean PBCPIEHFDBH = False, System.Boolean KJHCANCBKAL = False, System.String APJJOJMDLNP = null)","RecNet\Matchmaking.txt","8632","goto/room/" +"matchmaking","RecNet.Matchmaking","RecRoom.Async.IPromise`1> KFMFHGLEGHF(System.Int64 HNHLJONGKHB)","RecNet\Matchmaking.txt","10726","room/{0}/instances" +"messages","KEBJPIGKGOI","RecRoom.Async.IPromise JHJDMMLABCG()","KEBJPIGKGOI.txt","1297","api/messages/" +"messages","KEBJPIGKGOI","RecRoom.Async.IPromise MPLDGJCCIMN(System.Int64 CJFGEMGOJHB, IGCIBGKPPMO+BBELBJELLHN JMDIPDGMIOG, System.String ABADFLCBFIJ, System.Nullable`1 HNHLJONGKHB)","KEBJPIGKGOI.txt","2653","api/messages/" +"messages","KEBJPIGKGOI","RecRoom.Async.IPromise`1 IIHKDEGDMEA()","KEBJPIGKGOI.txt","6118","api/messages/" +"messages","KEBJPIGKGOI","System.Collections.IEnumerator JFAMBLHIACA(System.Collections.Generic.List`1 FNNIKBKHAFN, BPHGKAEDBPE+CBEOHBCIPEA AFLPGGJMPOE = null)","KEBJPIGKGOI.txt","1476","api/messages/" +"messages","KEBJPIGKGOI","System.Collections.IEnumerator MGIABJDLBED(System.Collections.Generic.List`1 FNNIKBKHAFN, IGCIBGKPPMO+BBELBJELLHN JMDIPDGMIOG, System.String ABADFLCBFIJ, System.Nullable`1 HNHLJONGKHB, BPHGKAEDBPE+CBEOHBCIPEA AFLPGGJMPOE = null)","KEBJPIGKGOI.txt","2843","api/messages/" +"messages","KEBJPIGKGOI","System.Void CPGAJIACKIP()","KEBJPIGKGOI.txt","3306","api/messages/" +"messages","KEBJPIGKGOI","RecRoom.Async.IPromise LLIHILNLNEP()","KEBJPIGKGOI.txt","5886","api/messages/v1/IOSClearDeviceToken" +"messages","KEBJPIGKGOI","RecRoom.Async.IPromise NDONIOINHAE(DIBODMEJOPN JFMOEGCPNAC)","KEBJPIGKGOI.txt","6455","api/messages/v1/IOSModifyNotificationPreferences" +"messages","KEBJPIGKGOI","RecRoom.Async.IPromise GENJNKHDHCN()","KEBJPIGKGOI.txt","6347","api/messages/v1/IOSResetNotificationPreferencesBadgeCount" +"messages","KEBJPIGKGOI","RecRoom.Async.IPromise INNJDBLBCEO(System.String EAEICOOGLAK)","KEBJPIGKGOI.txt","5768","api/messages/v1/IOSSaveDeviceToken" +"messages","KEBJPIGKGOI","System.Collections.IEnumerator PEIBHPIEKGF(System.Collections.Generic.IEnumerable`1 OCCKKEIMPGP, BPHGKAEDBPE+CBEOHBCIPEA AFLPGGJMPOE = null)","KEBJPIGKGOI.txt","3138","api/messages/v3/delete" +"messages","KEBJPIGKGOI","System.Collections.IEnumerator LPPPAHLCPCO(System.Int64 CJFGEMGOJHB, BPHGKAEDBPE+OJDJIJDNFHE AFLPGGJMPOE)","KEBJPIGKGOI.txt","6606","api/offlineinvite/" +"misc","FGPIDGLCKEF","System.Void IMLJEBCMMKK(PlatformManager+FCIKKFJOMNO FPMCLJDEGKL, System.Collections.Generic.List`1 CIBFMCPBLEO)","FGPIDGLCKEF.txt","2834","{0}v1/bulkignoreplatformusers" +"misc","GNPDMBPGHBH","RecRoom.Async.IPromise`1> DMHOIAJGABC(System.Collections.Generic.IReadOnlyList`1 JOGAGJIPEDN)","GNPDMBPGHBH.txt","2388","/bulk" +"misc","GNPDMBPGHBH+MKCNPHKGFDM","RecRoom.Async.IPromise`1> b__1(System.String uri)","GNPDMBPGHBH_NestedType_MKCNPHKGFDM.txt","138","/bulk" +"misc","KELEPAPMOGK+CJACMOFOFKJ","RecRoom.Async.IPromise`1 b__0()","KELEPAPMOGK_NestedType_CJACMOFOFKJ.txt","187","/data/{0}" +"misc","OJMCBOKJFOF+BPPECKDOINI","RecRoom.Async.IPromise`1 b__0()","OJMCBOKJFOF_NestedType_BPPECKDOINI.txt","206","/room/" +"misc","NLGOJMONPKG","RecRoom.Async.IPromise`1> ADEEFOCPDFH()","NLGOJMONPKG.txt","379","api/announcement/v1/get" +"misc","GCBPKHFJKCE","RecRoom.Async.IPromise`1> KABHMCDHHIP(System.Boolean INGAKMAAHKL = False)","GCBPKHFJKCE.txt","555","api/catalog/v1/all?onlyAvailableSkus=true" +"misc","GCBPKHFJKCE","RecRoom.Async.IPromise`1> KABHMCDHHIP(System.Boolean INGAKMAAHKL = False)","GCBPKHFJKCE.txt","577","api/catalog/v1/all?onlyAvailableSkus=true" +"misc","GCBPKHFJKCE+<>c","System.Void b__41_1()","GCBPKHFJKCE_NestedType___c.txt","791","api/catalog/v1/all?onlyAvailableSkus=true" +"misc","GCBPKHFJKCE+<>c","System.Void b__33_1()","GCBPKHFJKCE_NestedType___c.txt","246","api/catalog/v1/all?onlyAvailableSkus=true" +"misc","GCBPKHFJKCE+<>c","System.Void b__34_1()","GCBPKHFJKCE_NestedType___c.txt","332","api/catalog/v1/all?onlyAvailableSkus=true" +"misc","AAHIIPOCKMB","RecRoom.Async.IPromise`1 KIAGEGDDBCG()","AAHIIPOCKMB.txt","941","api/communityboard/" +"misc","FOEHJBDMMBH","RecRoom.Async.IPromise AKFDEPEHBIN(OOCCCGPICOG BAJGNABECMN, System.Int32 HLHHPEKALPI)","FOEHJBDMMBH.txt","1463","api/consumables/" +"misc","FOEHJBDMMBH","RecRoom.Async.IPromise`1> AFMKIHFMMBI(System.Int32 HLHHPEKALPI)","FOEHJBDMMBH.txt","1112","api/consumables/" +"misc","FOEHJBDMMBH","System.Collections.IEnumerator LCELAOEOIJO(OOCCCGPICOG BAJGNABECMN, System.Int32 FKKAPHAMPMG, BPHGKAEDBPE+CBEOHBCIPEA AFLPGGJMPOE)","FOEHJBDMMBH.txt","838","api/consumables/" +"misc","FOEHJBDMMBH","System.Collections.IEnumerator PKHAMBFCEJI(OOCCCGPICOG BAJGNABECMN, System.Boolean CNEIBILGFCO, BPHGKAEDBPE+CBEOHBCIPEA AFLPGGJMPOE)","FOEHJBDMMBH.txt","619","api/consumables/" +"misc","FOEHJBDMMBH+HHFGPIBAMOB","System.Boolean MoveNext()","FOEHJBDMMBH_NestedType_HHFGPIBAMOB.txt","147","api/consumables/" +"misc","FPNPBJBCMKB","RecRoom.Async.IPromise`1> EPPPMHMMGME()","FPNPBJBCMKB.txt","53","api/curatedroomplaylists" +"misc","GMPIMLJNACB","System.Collections.IEnumerator IPCNHLDICBM(BPHGKAEDBPE+CBEOHBCIPEA AFLPGGJMPOE)","GMPIMLJNACB.txt","904","api/objectives/" +"misc","GMPIMLJNACB","System.Void MEGMCCFCEIP(System.Int32 CKEBCMEKCJH, BPHGKAEDBPE+OJDJIJDNFHE AFLPGGJMPOE)","GMPIMLJNACB.txt","2171","api/objectives/" +"misc","GMPIMLJNACB+GIDAGFNPPLC","System.Boolean MoveNext()","GMPIMLJNACB_NestedType_GIDAGFNPPLC.txt","568","api/objectives/" +"misc","GMPIMLJNACB+GIDAGFNPPLC","System.Boolean MoveNext()","GMPIMLJNACB_NestedType_GIDAGFNPPLC.txt","623","api/objectives/" +"misc","GGLGFENEKBJ","System.Collections.IEnumerator BFJMPGHLGAN(System.Int64 CJFGEMGOJHB, LCCEEFHOBEN+CJFENPHAAHI MEABFEIBEMP, BPHGKAEDBPE+CBEOHBCIPEA AFLPGGJMPOE)","GGLGFENEKBJ.txt","586","api/PlayersBanned/" +"misc","FGPIDGLCKEF","RecRoom.Async.IPromise`1 CGPBLDOHCEI(System.Int32 CJFGEMGOJHB, CFLMJLFBOKH+MPFKNCFEDHF DIEPOPNJNCO)","FGPIDGLCKEF.txt","1454","api/relationships/" +"misc","FGPIDGLCKEF","RecRoom.Async.IPromise`1 DLBAPGPACLP(System.Int32 CJFGEMGOJHB)","FGPIDGLCKEF.txt","1827","api/relationships/" +"misc","FGPIDGLCKEF","RecRoom.Async.IPromise`1 DOIEMGNIMMM(System.Int32 CJFGEMGOJHB, BPHGKAEDBPE+CBEOHBCIPEA AFLPGGJMPOE = null)","FGPIDGLCKEF.txt","1197","api/relationships/" +"misc","FGPIDGLCKEF","RecRoom.Async.IPromise`1 JEGAGBBBADJ(System.Int32 CJFGEMGOJHB)","FGPIDGLCKEF.txt","1591","api/relationships/" +"misc","FGPIDGLCKEF","RecRoom.Async.IPromise`1 OILLHGFDILD(System.Int32 CJFGEMGOJHB)","FGPIDGLCKEF.txt","1709","api/relationships/" +"misc","FGPIDGLCKEF","System.Boolean CKANLFMOMCC(System.Int32 CJFGEMGOJHB, BPHGKAEDBPE+CBEOHBCIPEA AFLPGGJMPOE = null)","FGPIDGLCKEF.txt","2141","api/relationships/" +"misc","FGPIDGLCKEF","System.Boolean GDOLAFPLNPG(System.Int32 CJFGEMGOJHB, BPHGKAEDBPE+CBEOHBCIPEA AFLPGGJMPOE = null)","FGPIDGLCKEF.txt","1982","api/relationships/" +"misc","FGPIDGLCKEF","System.Void CPGAJIACKIP()","FGPIDGLCKEF.txt","3002","api/relationships/" +"misc","FGPIDGLCKEF","System.Void IMLJEBCMMKK(PlatformManager+FCIKKFJOMNO FPMCLJDEGKL, System.Collections.Generic.List`1 CIBFMCPBLEO)","FGPIDGLCKEF.txt","2832","api/relationships/" +"misc","FGPIDGLCKEF","System.Void KDKALCLMLIL(System.String GJIOCANDGPE, System.Int32 CJFGEMGOJHB, BPHGKAEDBPE+CBEOHBCIPEA AFLPGGJMPOE = null)","FGPIDGLCKEF.txt","2670","api/relationships/" +"misc","NKHBKKGOIHL+BBGMJPMOHEO","System.Boolean MoveNext()","NKHBKKGOIHL_NestedType_BBGMJPMOHEO.txt","353","api/sanitize/" +"misc","GEAPBDGCKMB","RecRoom.Async.IPromise ACEPPELPFOL(System.Int32 PGGHBNCGLDN, System.Nullable`1 POAOENHPNHE, System.Int32 MNIJHBJDPPA, System.Int32 HLHHPEKALPI)","GEAPBDGCKMB.txt","2977","api/storefronts/" +"misc","GEAPBDGCKMB","RecRoom.Async.IPromise`1> LLNACOGDCMF(ACDKILABNNC DLNFAILEHOA, System.Collections.Generic.IEnumerable`1 JEDEBGBEGCE)","GEAPBDGCKMB.txt","5929","api/storefronts/" +"misc","GEAPBDGCKMB","RecRoom.Async.IPromise`1 MOANDFACFAP(System.Int64 OEMDIAHHILF, System.Int32 HBIEIIHBDCI, System.String LGHBPPIOOBM)","GEAPBDGCKMB.txt","3603","api/storefronts/" +"misc","GEAPBDGCKMB","RecRoom.Async.IPromise`1 KGGMAGALNGF(System.Int64 BGICHOOBKLD, System.Int32 HBIEIIHBDCI)","GEAPBDGCKMB.txt","3819","api/storefronts/" +"misc","GEAPBDGCKMB","RecRoom.Async.IPromise`1 MMINJDALHNB(System.Int64 OEMDIAHHILF, System.String LGHBPPIOOBM)","GEAPBDGCKMB.txt","4052","api/storefronts/" +"misc","GEAPBDGCKMB","System.Collections.IEnumerator EKLFEDLCNAC(DGOHCPBKOHD NIGCECGDCFD, ACDKILABNNC DLNFAILEHOA, System.Int32 PGGHBNCGLDN, BPHGKAEDBPE+OJDJIJDNFHE> AFLPGGJMPOE)","GEAPBDGCKMB.txt","3402","api/storefronts/" +"misc","GEAPBDGCKMB","System.Collections.IEnumerator EPCDDOGBHMF(DGOHCPBKOHD NIGCECGDCFD, ACDKILABNNC DLNFAILEHOA, System.Int32 PGGHBNCGLDN, System.Nullable`1 POAOENHPNHE, GEAPBDGCKMB+GiftItemDTO HBBAANIPIMP, BPHGKAEDBPE+OJDJIJDNFHE> AFLPGGJMPOE)","GEAPBDGCKMB.txt","2709","api/storefronts/" +"misc","GEAPBDGCKMB","System.Collections.IEnumerator GDOGAIAHEHB(DGOHCPBKOHD NIGCECGDCFD, ACDKILABNNC DLNFAILEHOA, System.Int32 PGGHBNCGLDN, BPHGKAEDBPE+OJDJIJDNFHE> AFLPGGJMPOE)","GEAPBDGCKMB.txt","3241","api/storefronts/" +"misc","GEAPBDGCKMB","System.Collections.IEnumerator IMFAFAKABHC(DGOHCPBKOHD NIGCECGDCFD, BPHGKAEDBPE+OJDJIJDNFHE AFLPGGJMPOE)","GEAPBDGCKMB.txt","1914","api/storefronts/" +"misc","GEAPBDGCKMB","System.Collections.IEnumerator LBEMMLOFBEA(ACDKILABNNC DLNFAILEHOA, BPHGKAEDBPE+OJDJIJDNFHE AFLPGGJMPOE = null)","GEAPBDGCKMB.txt","5640","api/storefronts/" +"misc","GEAPBDGCKMB","System.Nullable`1 KJKDMOHACHJ(ACDKILABNNC DLNFAILEHOA, System.Predicate`1 NLDOHBCJOIO = null)","GEAPBDGCKMB.txt","4616","api/storefronts/" +"misc","GEAPBDGCKMB","System.Void EIFPPIPOIHB(System.Collections.Generic.List`1 DBDFCMJFNEC)","GEAPBDGCKMB.txt","2257","api/storefronts/" +"misc","GEAPBDGCKMB","System.Void IMLMGPJIKCK(ACDKILABNNC DLNFAILEHOA, DKMCNBHMKIK IGNJPMALPIK, BPHGKAEDBPE+OJDJIJDNFHE AFLPGGJMPOE = null)","GEAPBDGCKMB.txt","5781","api/storefronts/" +"misc","GEAPBDGCKMB+PPPNGFBHEDH","RecRoom.Async.IPromise`1 b__0()","GEAPBDGCKMB_NestedType_PPPNGFBHEDH.txt","236","api/storefronts/" +"misc","AICHDPNIEKI","RecRoom.Async.IPromise CNGKABDOEKH(System.String EPGHLDONDIP, NIDIHGENDJD AKPHCJFIPBB)","AICHDPNIEKI.txt","450","api/testcasemanagement/" +"misc","AICHDPNIEKI","RecRoom.Async.IPromise DHDOAJAGEJN(System.String EPGHLDONDIP)","AICHDPNIEKI.txt","360","api/testcasemanagement/" +"misc","AICHDPNIEKI","RecRoom.Async.IPromise KNOGAGHKIMC(System.String EPGHLDONDIP)","AICHDPNIEKI.txt","273","api/testcasemanagement/" +"misc","AICHDPNIEKI","RecRoom.Async.IPromise`1 APEBFFOEBFA(System.String ENJEOLBEALP)","AICHDPNIEKI.txt","193","api/testcasemanagement/" +"misc","AICHDPNIEKI","RecRoom.Async.IPromise`1 BMDDGFAEDLE(System.UInt32 ENJEOLBEALP)","AICHDPNIEKI.txt","122","api/testcasemanagement/" +"misc","AICHDPNIEKI","RecRoom.Async.IPromise`1> AKCPPGBNGOO()","AICHDPNIEKI.txt","44","api/testcasemanagement/" +"misc","KMDHPCHFADM","RecRoom.Async.IPromise`1 BPAGDCDBHAM()","KMDHPCHFADM.txt","282","api/versioncheck/v4?v={0}&p={1}" +"misc","IOSPlatformManager","RecRoom.Async.IPromise ShowManageSubscriptionPlatformUI()","IOSPlatformManager.txt","4865","https://apps.apple.com/account/subscriptions" +"misc","InstagramBulletinBoardUI","System.Void Button_RecRoomOnInstagram()","InstagramBulletinBoardUI.txt","1009","https://www.instagram.com/recroom/" +"player-events","GNPDMBPGHBH","RecRoom.Async.IPromise`1 BFBMDMLHHCF(CCMBKDINCAH MFJCKOBPMGA, System.Int64 HNHLJONGKHB, System.Nullable`1 FODGKNJIGOP, System.String MMBOKOLAJFH, System.String LJIGOCDPEJF, System.Collections.Generic.List`1 CAFPJPHILMN, System.String HFLPBHHAFIO, System.DateTime JODPOANPJNK, System.DateTime BCANLCHBKJE, CMCAFKLAHCD PONCIIJOHIE, System.Nullable`1 AOMBLGBCENO)","GNPDMBPGHBH.txt","4280","api/playerevents/" +"player-events","GNPDMBPGHBH","RecRoom.Async.IPromise`1 EKEOHKINPDK(CCMBKDINCAH MFJCKOBPMGA)","GNPDMBPGHBH.txt","6151","api/playerevents/" +"player-events","GNPDMBPGHBH","RecRoom.Async.IPromise`1> MBHCOIGLAIF(System.Int64 MOKMJAMNEFP, System.Boolean INGAKMAAHKL = False)","GNPDMBPGHBH.txt","3736","api/playerevents/" +"player-events","GNPDMBPGHBH","RecRoom.Async.IPromise`1 DAICPGEMGMH(System.Int64 MOKMJAMNEFP, System.Boolean GDPJLIOIIAM = False, System.Nullable`1 AOMBLGBCENO = null)","GNPDMBPGHBH.txt","6434","api/playerevents/" +"player-events","GNPDMBPGHBH","System.String HKPFFOHBENO(System.Int64 MEJJDOHGNHG)","GNPDMBPGHBH.txt","559","api/playerevents/" +"player-events","GNPDMBPGHBH","System.Void DAOBEMMLHAD(System.Collections.Generic.Dictionary`2 NGPMADFHHKP)","GNPDMBPGHBH.txt","10757","api/playerevents/" +"player-events","GNPDMBPGHBH","System.Void DEOJOKOFMLJ(System.Int64 MOKMJAMNEFP)","GNPDMBPGHBH.txt","11770","api/playerevents/" +"player-events","GNPDMBPGHBH","System.Void JILNOOIDFKC(HJCAMMLHJAE MOMJFCHIMAM)","GNPDMBPGHBH.txt","11596","api/playerevents/" +"player-events","GNPDMBPGHBH+JNEMPCHKFAM","System.Void b__0(PDOBNLOLBAF response)","GNPDMBPGHBH_NestedType_JNEMPCHKFAM.txt","168","api/playerevents/" +"player-events","GNPDMBPGHBH","RecRoom.Async.IPromise`1 JKFDNKOBOJE()","GNPDMBPGHBH.txt","5504","api/playerevents/v1/all" +"player-events","GNPDMBPGHBH","RecRoom.Async.IPromise`1 MGDLGLLOEMG(System.Int64 MOKMJAMNEFP, System.Collections.Generic.List`1 MHCONOPOOKJ)","GNPDMBPGHBH.txt","5013","api/playerevents/v1/bulkInvite" +"player-events","GNPDMBPGHBH","RecRoom.Async.IPromise`1 FGFDJIKJKNN(System.Int64 MOKMJAMNEFP, FFEICMIIBMC DHLDCHCKBPC)","GNPDMBPGHBH.txt","4769","api/playerevents/v1/deleteResponse" +"player-events","GNPDMBPGHBH","RecRoom.Async.IPromise`1 GLHAJLLNPBJ(System.Int64 MOKMJAMNEFP, LCCEEFHOBEN+CJFENPHAAHI MEABFEIBEMP, System.String EFDBFLPKHKA)","GNPDMBPGHBH.txt","5939","api/playerevents/v1/report" +"player-events","GNPDMBPGHBH","RecRoom.Async.IPromise`1 EEJGIOKDBFI(System.Int64 MOKMJAMNEFP, FFEICMIIBMC DHLDCHCKBPC)","GNPDMBPGHBH.txt","4567","api/playerevents/v1/respond" +"player-events","GNPDMBPGHBH","RecRoom.Async.IPromise`1> ADMFNGBFPIE(System.String CNBKKCJAHPP, GNPDMBPGHBH+EHKGCEONBIB DKLJMLFNEEN = 0, System.Nullable`1 LBFLCMJFDPC = null)","GNPDMBPGHBH.txt","9248","api/playerevents/v1/search?" +"player-events","GNPDMBPGHBH","RecRoom.Async.IPromise`1> JBOMFDKLBOI(System.String CNBKKCJAHPP)","GNPDMBPGHBH.txt","8935","api/playerevents/v1/searchlive?" +"player-events","GNPDMBPGHBH","RecRoom.Async.IPromise`1> LIPBAJOAIJC(GNPDMBPGHBH+FFCCJHEPNDK KINAEJLCDEG)","GNPDMBPGHBH.txt","9697","api/playerevents/v1/tagfilters" +"player-events","GNPDMBPGHBH","RecRoom.Async.IPromise`1 BBPEHLACMLH(System.Int64 HNHLJONGKHB, System.Nullable`1 FODGKNJIGOP, System.Nullable`1 AOMBLGBCENO, System.String MMBOKOLAJFH, System.String LJIGOCDPEJF, System.Collections.Generic.List`1 CAFPJPHILMN, System.String HFLPBHHAFIO, System.DateTime JODPOANPJNK, System.DateTime BCANLCHBKJE, CMCAFKLAHCD PONCIIJOHIE)","GNPDMBPGHBH.txt","4067","api/playerevents/v2" +"players","EBAIPNMBKLK","RecRoom.Async.IPromise`1 JNLDMDCGHHJ(System.Int32 GKLPIFBPGOD)","EBAIPNMBKLK.txt","1231","/api/playerReputation/v1/{0}" +"players","EBAIPNMBKLK","RecRoom.Async.IPromise`1> KGPDFJMKJKL(System.Collections.Generic.List`1 ILNGMAANNDG)","EBAIPNMBKLK.txt","1631","/api/playerReputation/v1/bulk" +"players","ACJLCBNBJDK","RecRoom.Async.IPromise`1 AAGPDEDFCNI(System.Int32 GKLPIFBPGOD)","ACJLCBNBJDK.txt","1673","/api/players/v1/progression/{0}" +"players","ACJLCBNBJDK","RecRoom.Async.IPromise`1> MCFMHADBDBI(System.Collections.Generic.List`1 ILNGMAANNDG)","ACJLCBNBJDK.txt","2051","/api/players/v1/progression/bulk" +"players","ACJLCBNBJDK","System.Void EIFPPIPOIHB(System.Collections.Generic.List`1 DBDFCMJFNEC)","ACJLCBNBJDK.txt","816","api/players/v2/objectives" +"playlists","EJDCNGBEICB","RecRoom.Async.IPromise`1 CFKDADKHAGB()","EJDCNGBEICB.txt","2687","featuredrooms/current" +"playlists","EJDCNGBEICB","RecRoom.Async.IPromise OFACCDEDIHE(System.Int64 DDMPFMPILCE)","EJDCNGBEICB.txt","6619","playlists/{0}" +"playlists","EJDCNGBEICB","RecRoom.Async.IPromise`1 POOBMHAAMLJ(System.Int64 DDMPFMPILCE)","EJDCNGBEICB.txt","2192","playlists/{0}" +"playlists","EJDCNGBEICB","RecRoom.Async.IPromise`1 PDLNHHAPPIP(System.Int64 DDMPFMPILCE)","EJDCNGBEICB.txt","1659","playlists/{0}" +"playlists","EJDCNGBEICB","RecRoom.Async.IPromise`1 KLHJFKPFPIC(System.Int64 DDMPFMPILCE, DPLPMKMFMPB PONCIIJOHIE)","EJDCNGBEICB.txt","7323","playlists/{0}/accessibility" +"playlists","EJDCNGBEICB","RecRoom.Async.IPromise`1 EPJMNNPLNDM(System.Int64 DDMPFMPILCE, System.String LJIGOCDPEJF)","EJDCNGBEICB.txt","6839","playlists/{0}/description" +"playlists","EJDCNGBEICB","RecRoom.Async.IPromise`1 CKMILKDKDCN(System.Int64 DDMPFMPILCE, System.String HFLPBHHAFIO)","EJDCNGBEICB.txt","6955","playlists/{0}/image" +"playlists","EJDCNGBEICB","RecRoom.Async.IPromise`1 AINDJCIMJOB(System.Int64 DDMPFMPILCE)","EJDCNGBEICB.txt","8600","playlists/{0}/interactionby/me" +"playlists","EJDCNGBEICB","RecRoom.Async.IPromise CIPEENNIAFL(System.Int64 DDMPFMPILCE)","EJDCNGBEICB.txt","8733","playlists/{0}/interactionby/me/cheer" +"playlists","EJDCNGBEICB","RecRoom.Async.IPromise LONLMONNPGL(System.Int64 DDMPFMPILCE)","EJDCNGBEICB.txt","8665","playlists/{0}/interactionby/me/cheer" +"playlists","EJDCNGBEICB","RecRoom.Async.IPromise GDELFOHOPGF(System.Int64 DDMPFMPILCE)","EJDCNGBEICB.txt","8801","playlists/{0}/interactionby/me/favorite" +"playlists","EJDCNGBEICB","RecRoom.Async.IPromise NLNKEGDKFCG(System.Int64 DDMPFMPILCE)","EJDCNGBEICB.txt","8869","playlists/{0}/interactionby/me/favorite" +"playlists","EJDCNGBEICB","RecRoom.Async.IPromise`1 PPBLDOPNJKC(System.Int64 DDMPFMPILCE, System.Boolean NNBHCDIKILH)","EJDCNGBEICB.txt","7582","playlists/{0}/levelvoting" +"playlists","EJDCNGBEICB","RecRoom.Async.IPromise`1 NINPMBNNHMG(System.Int64 DDMPFMPILCE, System.String MMBOKOLAJFH)","EJDCNGBEICB.txt","6723","playlists/{0}/name" +"playlists","EJDCNGBEICB","RecRoom.Async.IPromise`1 LDLJKJLNLEE(System.Int64 DDMPFMPILCE, System.Boolean DDMOEIAHJBK, System.Boolean FOBLJIBLCNI, System.Boolean GELHFEFGJLA, System.Boolean NFIGHDPKLJF)","EJDCNGBEICB.txt","7454","playlists/{0}/restrictions" +"playlists","EJDCNGBEICB","RecRoom.Async.IPromise`1 JBMCPFHGIMD(System.Int64 DDMPFMPILCE, System.Int64 HNHLJONGKHB)","EJDCNGBEICB.txt","7765","playlists/{0}/rooms/{1}" +"playlists","EJDCNGBEICB","RecRoom.Async.IPromise`1 NLOEBFMNCFM(System.Int64 DDMPFMPILCE, System.Int64 HNHLJONGKHB)","EJDCNGBEICB.txt","7679","playlists/{0}/rooms/{1}" +"playlists","EJDCNGBEICB","RecRoom.Async.IPromise`1 ALCFNJJHDMG(System.Int64 DDMPFMPILCE, System.Collections.Generic.IReadOnlyList`1 LBAOCHFCLPO, System.Collections.Generic.IReadOnlyList`1 CAFPJPHILMN)","EJDCNGBEICB.txt","7080","playlists/{0}/tags" +"playlists","EJDCNGBEICB","RecRoom.Async.IPromise`1 KEADPPJCJGG(System.Int64 DDMPFMPILCE, GPDIAKNEBKH LEFPAAGGFFA, System.String NLEBEKDHJIJ)","EJDCNGBEICB.txt","7206","playlists/{0}/warning" +"playlists","EJDCNGBEICB","RecRoom.Async.IPromise`1> KHMJIKNHJHP(System.Collections.Generic.IReadOnlyList`1 ANIKDNFLDIG)","EJDCNGBEICB.txt","1896","playlists/bulk" +"playlists","EJDCNGBEICB","RecRoom.Async.IPromise`1> KHMJIKNHJHP(System.Collections.Generic.IReadOnlyList`1 CJMAAFFAKDO)","EJDCNGBEICB.txt","2068","playlists/bulk" +"playlists","EJDCNGBEICB","RecRoom.Async.IPromise`1> EFFMOEGALDB()","EJDCNGBEICB.txt","2400","playlists/cheeredby/me" +"playlists","EJDCNGBEICB","RecRoom.Async.IPromise`1> PLPMPFDOICH()","EJDCNGBEICB.txt","2361","playlists/createdby/me" +"playlists","EJDCNGBEICB","RecRoom.Async.IPromise`1> FAMGMKHMCIN()","EJDCNGBEICB.txt","2439","playlists/favoritedby/me" +"playlists","EJDCNGBEICB","RecRoom.Async.IPromise`1> OMMENACPKGH()","EJDCNGBEICB.txt","2478","playlists/visitedby/me" +"playlists","EJDCNGBEICB","RecRoom.Async.IPromise`1 EBBHGHEGBKG(System.Collections.Generic.IReadOnlyList`1 CAFPJPHILMN)","EJDCNGBEICB.txt","2645","roomsandplaylists/hot" +"playlists","EJDCNGBEICB","RecRoom.Async.IPromise`1 HFHDFLDCIEM(System.String CNBKKCJAHPP)","EJDCNGBEICB.txt","2560","roomsandplaylists/search" +"quickplay","ADOIEPDDEBO","RecRoom.Async.IPromise CPGECIJBBKF()","ADOIEPDDEBO.txt","358","api/quickPlay/" +"reporting","KHGPLGBHIAH","System.Void PPOCPFGCALB(LGFHFDMDJKK GIGKODMKKHJ, System.Action`2 DFPGDEOCNIH, System.Action`2 AFLPGGJMPOE)","KHGPLGBHIAH.txt","2838","{0}/api/userreporting" +"reporting","LCCEEFHOBEN","RecRoom.Async.IPromise`1 LONDNAJPCEE()","LCCEEFHOBEN.txt","2149","api/banappeal/generateCode" +"reporting","LCCEEFHOBEN","RecRoom.Async.IPromise FCPPPBKJFGK(LCCEEFHOBEN+OAODPJFGPAF GEDCEIDOKJL, System.String NGPMADFHHKP, System.Nullable`1 IMHODAEGGON = null)","LCCEEFHOBEN.txt","5148","api/PlayerReporting/" +"reporting","LCCEEFHOBEN","RecRoom.Async.IPromise IEAOIIFNOGN(System.String PLDIBPNDJIO)","LCCEEFHOBEN.txt","4715","api/PlayerReporting/" +"reporting","LCCEEFHOBEN","RecRoom.Async.IPromise`1 NEFPDKLMDHK()","LCCEEFHOBEN.txt","895","api/PlayerReporting/" +"reporting","LCCEEFHOBEN","RecRoom.Async.IPromise`1 EKIAMDFOLJM(System.Int64 CJFGEMGOJHB, System.Boolean MAOOLFEBOOD, System.String EMNPHAPBBAH)","LCCEEFHOBEN.txt","4216","api/PlayerReporting/" +"reporting","LCCEEFHOBEN","RecRoom.Async.IPromise`1 FFPIOECGNBI(System.Int32 CJFGEMGOJHB)","LCCEEFHOBEN.txt","4447","api/PlayerReporting/" +"reporting","LCCEEFHOBEN","RecRoom.Async.IPromise`1 HNFAAFGGDML(System.Collections.Generic.List`1 FNNIKBKHAFN)","LCCEEFHOBEN.txt","4586","api/PlayerReporting/" +"reporting","LCCEEFHOBEN","System.Collections.IEnumerator KNAHPBPBPAM(System.Int32 CJFGEMGOJHB, LCCEEFHOBEN+CJFENPHAAHI MEABFEIBEMP, System.String EFDBFLPKHKA, System.Nullable`1 HJMMCCKCDEH, BPHGKAEDBPE+OJDJIJDNFHE AFLPGGJMPOE)","LCCEEFHOBEN.txt","1783","api/PlayerReporting/" +"reporting","LCCEEFHOBEN","RecRoom.Async.IPromise HHBLGNHOGLN()","LCCEEFHOBEN.txt","2417","api/PlayerReporting/v1/voteToKickReasons" +"reporting","UserReportingScript","System.Void Start()","UserReportingScript.txt","1174","https://userreporting.cloud.unity3d.com/api/userreporting/projects/{0}/ping" +"room-keys","IOIBJBLIBKM","RecRoom.Async.IPromise`1 ACCJPCMPJMH(System.Int64 BGICHOOBKLD)","IOIBJBLIBKM.txt","1299","api/roomkeys/" +"room-keys","IOIBJBLIBKM","RecRoom.Async.IPromise`1 HECNNKLKKAG(System.Int64 BGICHOOBKLD)","IOIBJBLIBKM.txt","1517","api/roomkeys/" +"room-keys","IOIBJBLIBKM","RecRoom.Async.IPromise`1 LAPDFFOJCNP(System.Int32 CJFGEMGOJHB, AMAGKLLBGEC AAHIIDDIBFD)","IOIBJBLIBKM.txt","1866","api/roomkeys/" +"room-keys","IOIBJBLIBKM","System.String JGLHIEEFHKE()","IOIBJBLIBKM.txt","58","api/roomkeys/" +"room-keys","IOIBJBLIBKM","RecRoom.Async.IPromise`1 BIAKPHAECJC(System.String MMBOKOLAJFH, System.String LJIGOCDPEJF, System.Int32 MACNIENMFHJ)","IOIBJBLIBKM.txt","573","api/roomkeys/v1/create" +"room-keys","IOIBJBLIBKM","RecRoom.Async.IPromise`1> IGFLECMACBE()","IOIBJBLIBKM.txt","1704","api/roomkeys/v1/mine" +"room-keys","IOIBJBLIBKM","RecRoom.Async.IPromise`1 MLFKLGGPLNM(System.Int64 BGICHOOBKLD, System.String MMBOKOLAJFH = null, System.String LJIGOCDPEJF = null, System.Nullable`1 MACNIENMFHJ = null)","IOIBJBLIBKM.txt","1008","api/roomkeys/v1/update" +"rooms","OJMCBOKJFOF+<>c","RecRoom.Async.IPromise`1 b__114_0()","OJMCBOKJFOF_NestedType___c.txt","1617","api/rooms/v1/filters" +"rooms","OJMCBOKJFOF","RecRoom.Async.IPromise INDDAAGNMHF(System.String LHOMKMINCHH)","OJMCBOKJFOF.txt","12963","api/rooms/v1/verifyRole" +"rooms","OJMCBOKJFOF","RecRoom.Async.IPromise GECDGBACBFP(System.Int64 HNHLJONGKHB, LCCEEFHOBEN+CJFENPHAAHI MEABFEIBEMP, System.String EFDBFLPKHKA)","OJMCBOKJFOF.txt","12627","api/rooms/v2/report" +"rooms","OJMCBOKJFOF","RecRoom.Async.IPromise`1> DLPLPKCNLNA(System.Collections.Generic.IReadOnlyList`1 CAFPJPHILMN)","OJMCBOKJFOF.txt","3238","hot_rooms/" +"rooms","OJMCBOKJFOF+PEBCIEAPGON","System.String CMOIGEDECGM(System.Collections.Generic.IEnumerable`1 CAFPJPHILMN)","OJMCBOKJFOF_NestedType_PEBCIEAPGON.txt","39","hot_rooms/" +"rooms","OJMCBOKJFOF","RecRoom.Async.IPromise`1> EBBHGHEGBKG(System.Collections.Generic.IReadOnlyList`1 CAFPJPHILMN)","OJMCBOKJFOF.txt","4743","hot_roomsandplaylists/" +"rooms","OJMCBOKJFOF+PEBCIEAPGON","System.String MMCLGMMDDJF(System.Collections.Generic.IEnumerable`1 CAFPJPHILMN)","OJMCBOKJFOF_NestedType_PEBCIEAPGON.txt","121","hot_roomsandplaylists/" +"rooms","EJDCNGBEICB","RecRoom.Async.IPromise KHDPHIGPEEH(System.Int64 HNHLJONGKHB)","EJDCNGBEICB.txt","2923","rooms/{0}" +"rooms","EJDCNGBEICB","RecRoom.Async.IPromise`1 NHBPIIGDAJP(System.Int64 HNHLJONGKHB)","EJDCNGBEICB.txt","82","rooms/{0}" +"rooms","EJDCNGBEICB","RecRoom.Async.IPromise`1 CJKHNIIJFIN(System.Int64 HNHLJONGKHB)","EJDCNGBEICB.txt","615","rooms/{0}" +"rooms","EJDCNGBEICB","RecRoom.Async.IPromise`1 BPDNAMEDJEG(System.Int64 HNHLJONGKHB, DPLPMKMFMPB PONCIIJOHIE)","EJDCNGBEICB.txt","3627","rooms/{0}/accessibility" +"rooms","EJDCNGBEICB","RecRoom.Async.IPromise`1 CPFCPEONBGC(System.Int64 HNHLJONGKHB, System.Boolean AMJMLANOGKE)","EJDCNGBEICB.txt","4014","rooms/{0}/automute" +"rooms","EJDCNGBEICB","RecRoom.Async.IPromise CCGIGHDHNLM(System.Int64 HNHLJONGKHB, System.Collections.Generic.IReadOnlyList`1 ILNGMAANNDG, KKIHLHLNGCK BFNPCCFPJHP)","EJDCNGBEICB.txt","7939","rooms/{0}/bans" +"rooms","EJDCNGBEICB","RecRoom.Async.IPromise`1> CGIPIOMBAFM(System.Int64 HNHLJONGKHB)","EJDCNGBEICB.txt","7832","rooms/{0}/bans" +"rooms","EJDCNGBEICB","RecRoom.Async.IPromise JMCJDGIMKPK(System.Int64 HNHLJONGKHB, System.Int32 GKLPIFBPGOD, KKIHLHLNGCK BFNPCCFPJHP)","EJDCNGBEICB.txt","8186","rooms/{0}/bans/{1}" +"rooms","EJDCNGBEICB","RecRoom.Async.IPromise PFEEJDKPPJI(System.Int64 HNHLJONGKHB, System.Int64 MOKIOEJBMFC)","EJDCNGBEICB.txt","8054","rooms/{0}/bans/import" +"rooms","EJDCNGBEICB","RecRoom.Async.IPromise`1 OFFECAMHJGE(System.Int64 HNHLJONGKHB, System.String MMBOKOLAJFH)","EJDCNGBEICB.txt","2843","rooms/{0}/clone" +"rooms","EJDCNGBEICB","RecRoom.Async.IPromise`1 LJMHBJKDJCE(System.Int64 HNHLJONGKHB, System.Boolean GPAGMKFIKNG)","EJDCNGBEICB.txt","3886","rooms/{0}/cloning" +"rooms","EJDCNGBEICB","RecRoom.Async.IPromise`1 FKADIOPHOGC(System.Int64 HNHLJONGKHB, System.Boolean IMHMMPKLGEP)","EJDCNGBEICB.txt","4142","rooms/{0}/comments" +"rooms","EJDCNGBEICB","RecRoom.Async.IPromise`1 CFNFNEJGCEF(System.Int64 HNHLJONGKHB, System.String LJIGOCDPEJF)","EJDCNGBEICB.txt","3143","rooms/{0}/description" +"rooms","EJDCNGBEICB","RecRoom.Async.IPromise`1 OGFLGMMNMDD(System.Int64 HNHLJONGKHB, System.String HFLPBHHAFIO)","EJDCNGBEICB.txt","3259","rooms/{0}/image" +"rooms","EJDCNGBEICB","RecRoom.Async.IPromise`1 CKJBGHEIGBI(System.Int64 HNHLJONGKHB)","EJDCNGBEICB.txt","8266","rooms/{0}/interactionby/me" +"rooms","EJDCNGBEICB","RecRoom.Async.IPromise JFBCCIHBKPP(System.Int64 HNHLJONGKHB)","EJDCNGBEICB.txt","8399","rooms/{0}/interactionby/me/cheer" +"rooms","EJDCNGBEICB","RecRoom.Async.IPromise PEKMAJPMCDE(System.Int64 HNHLJONGKHB)","EJDCNGBEICB.txt","8331","rooms/{0}/interactionby/me/cheer" +"rooms","EJDCNGBEICB","RecRoom.Async.IPromise DIFNOPBMBFO(System.Int64 HNHLJONGKHB)","EJDCNGBEICB.txt","8467","rooms/{0}/interactionby/me/favorite" +"rooms","EJDCNGBEICB","RecRoom.Async.IPromise OGBLBGALMDC(System.Int64 HNHLJONGKHB)","EJDCNGBEICB.txt","8535","rooms/{0}/interactionby/me/favorite" +"rooms","EJDCNGBEICB","RecRoom.Async.IPromise`1 ONBIHBFOJKD(System.Int64 HNHLJONGKHB, System.String MMBOKOLAJFH, System.String LJIGOCDPEJF, DPLPMKMFMPB PONCIIJOHIE, System.Boolean DDMOEIAHJBK, System.Boolean FOBLJIBLCNI, System.Boolean GELHFEFGJLA, System.Boolean NFIGHDPKLJF, System.Boolean GPAGMKFIKNG, System.Boolean AMJMLANOGKE, System.Boolean IMHMMPKLGEP, System.Boolean NFJINCPDPDG)","EJDCNGBEICB.txt","6298","rooms/{0}/modify" +"rooms","EJDCNGBEICB","RecRoom.Async.IPromise`1 DNOGGKKHMHI(System.Int64 HNHLJONGKHB, System.String MMBOKOLAJFH)","EJDCNGBEICB.txt","3027","rooms/{0}/name" +"rooms","EJDCNGBEICB","RecRoom.Async.IPromise`1 FBNHEKCLMCJ(System.Int64 HNHLJONGKHB, NCELIDGFOEM GEDCEIDOKJL, System.String EKGHGPLFMPJ)","EJDCNGBEICB.txt","4862","rooms/{0}/promo_external" +"rooms","EJDCNGBEICB","RecRoom.Async.IPromise`1 ACGJJCMFOCH(System.Int64 HNHLJONGKHB, NCELIDGFOEM GEDCEIDOKJL, System.String EKGHGPLFMPJ)","EJDCNGBEICB.txt","4970","rooms/{0}/promo_external/{1}/{2}" +"rooms","EJDCNGBEICB","RecRoom.Async.IPromise`1 CFIKOAAOHFH(System.Int64 HNHLJONGKHB, System.String HFLPBHHAFIO)","EJDCNGBEICB.txt","4663","rooms/{0}/promo_images" +"rooms","EJDCNGBEICB","RecRoom.Async.IPromise`1 OCCGHPAHGKC(System.Int64 HNHLJONGKHB, System.String HFLPBHHAFIO)","EJDCNGBEICB.txt","4749","rooms/{0}/promo_images/{1}" +"rooms","EJDCNGBEICB","RecRoom.Async.IPromise`1 ONEJODPFOMP(System.Int64 HNHLJONGKHB, System.Boolean DDMOEIAHJBK, System.Boolean FOBLJIBLCNI, System.Boolean GELHFEFGJLA, System.Boolean NFIGHDPKLJF)","EJDCNGBEICB.txt","3758","rooms/{0}/restrictions" +"rooms","EJDCNGBEICB","RecRoom.Async.IPromise`1> MIGOLOHAIAL(System.Int64 HNHLJONGKHB)","EJDCNGBEICB.txt","1254","rooms/{0}/roles" +"rooms","EJDCNGBEICB","RecRoom.Async.IPromise`1 EKOKFNHHMPI(System.Int64 HNHLJONGKHB, System.Int32 GKLPIFBPGOD)","EJDCNGBEICB.txt","1332","rooms/{0}/roles/{1}" +"rooms","EJDCNGBEICB","RecRoom.Async.IPromise`1 GJMGBBCIHGH(System.Int64 HNHLJONGKHB, System.Int32 GKLPIFBPGOD, LMLJHMJEIGM IENKDAKBEDP)","EJDCNGBEICB.txt","4405","rooms/{0}/roles/{1}" +"rooms","EJDCNGBEICB","RecRoom.Async.IPromise`1 MJOHJLHDMHB(System.Int64 HNHLJONGKHB, System.Int32 GKLPIFBPGOD, LMLJHMJEIGM IENKDAKBEDP)","EJDCNGBEICB.txt","4543","rooms/{0}/roles/{1}/invite" +"rooms","EJDCNGBEICB","RecRoom.Async.IPromise`1 OGNIELCNHIM(System.Int64 HNHLJONGKHB, System.String MMBOKOLAJFH)","EJDCNGBEICB.txt","5487","rooms/{0}/subrooms" +"rooms","EJDCNGBEICB","RecRoom.Async.IPromise`1 GGCOGNAGPBP(System.Int64 HNHLJONGKHB, System.Int64 FODGKNJIGOP)","EJDCNGBEICB.txt","5809","rooms/{0}/subrooms/{1}" +"rooms","EJDCNGBEICB","RecRoom.Async.IPromise`1 POGEHAMJBPE(System.Int64 HNHLJONGKHB, System.Int64 FODGKNJIGOP, DPLPMKMFMPB PONCIIJOHIE)","EJDCNGBEICB.txt","5369","rooms/{0}/subrooms/{1}/accessibility" +"rooms","EJDCNGBEICB","RecRoom.Async.IPromise`1 OGEKCICPACB(System.Int64 HNHLJONGKHB, System.Int64 FODGKNJIGOP)","EJDCNGBEICB.txt","5583","rooms/{0}/subrooms/{1}/clone" +"rooms","EJDCNGBEICB","RecRoom.Async.IPromise`1 HOPMJFDKPDL(System.Int64 HNHLJONGKHB, System.Int64 FODGKNJIGOP, System.String MEIIMAIGBJD, System.Collections.Generic.Dictionary`2 PNOBBNPJDLI, System.Int32 OMCLBIFCHLF)","EJDCNGBEICB.txt","5995","rooms/{0}/subrooms/{1}/data" +"rooms","EJDCNGBEICB","RecRoom.Async.IPromise`1> HAMBEABEIOC(System.Int64 HNHLJONGKHB, System.Int64 FODGKNJIGOP)","EJDCNGBEICB.txt","1190","rooms/{0}/subrooms/{1}/datahistory" +"rooms","EJDCNGBEICB","RecRoom.Async.IPromise`1 AEBGMLMMKOJ(System.Int64 HNHLJONGKHB, System.Int64 FODGKNJIGOP, System.Int32 FFAGIDMFPIF)","EJDCNGBEICB.txt","5235","rooms/{0}/subrooms/{1}/maxplayers" +"rooms","EJDCNGBEICB","RecRoom.Async.IPromise`1 JGMMJMHLHBO(System.Int64 HNHLJONGKHB, System.Int64 FODGKNJIGOP, System.String MMBOKOLAJFH, DPLPMKMFMPB PONCIIJOHIE, System.Int32 FFAGIDMFPIF)","EJDCNGBEICB.txt","6443","rooms/{0}/subrooms/{1}/modify" +"rooms","EJDCNGBEICB","RecRoom.Async.IPromise`1 OHHAFAIEFHP(System.Int64 HNHLJONGKHB, System.Int64 FODGKNJIGOP, System.Nullable`1 PAJMLPFMBEJ)","EJDCNGBEICB.txt","5709","rooms/{0}/subrooms/{1}/move" +"rooms","EJDCNGBEICB","RecRoom.Async.IPromise`1 PAENPJCDLNJ(System.Int64 HNHLJONGKHB, System.Int64 FODGKNJIGOP, System.String MMBOKOLAJFH)","EJDCNGBEICB.txt","5099","rooms/{0}/subrooms/{1}/name" +"rooms","EJDCNGBEICB","RecRoom.Async.IPromise`1 BBGBOCGJFOP(System.Int64 HNHLJONGKHB, System.Int64 FODGKNJIGOP, System.String MEIIMAIGBJD)","EJDCNGBEICB.txt","6133","rooms/{0}/subrooms/{1}/restoredata" +"rooms","EJDCNGBEICB","RecRoom.Async.IPromise`1 ENGFFJGIEGH(System.Int64 HNHLJONGKHB, System.Collections.Generic.IReadOnlyList`1 LBAOCHFCLPO, System.Collections.Generic.IReadOnlyList`1 CAFPJPHILMN)","EJDCNGBEICB.txt","3384","rooms/{0}/tags" +"rooms","EJDCNGBEICB","RecRoom.Async.IPromise`1 PKDCMMFILPB(System.Int64 HNHLJONGKHB, System.Boolean NFJINCPDPDG)","EJDCNGBEICB.txt","4270","rooms/{0}/voice_chat_encryption" +"rooms","EJDCNGBEICB","RecRoom.Async.IPromise`1 NPACFDBCHDP(System.Int64 HNHLJONGKHB, GPDIAKNEBKH LEFPAAGGFFA, System.String NLEBEKDHJIJ)","EJDCNGBEICB.txt","3510","rooms/{0}/warning" +"rooms","EJDCNGBEICB","RecRoom.Async.IPromise`1> LMEPPFGPHLA()","EJDCNGBEICB.txt","784","rooms/base" +"rooms","EJDCNGBEICB","RecRoom.Async.IPromise`1> JPNBCKICJIH(System.Collections.Generic.IReadOnlyList`1 HJLOFEGEMHE)","EJDCNGBEICB.txt","319","rooms/bulk" +"rooms","EJDCNGBEICB","RecRoom.Async.IPromise`1> JPNBCKICJIH(System.Collections.Generic.IReadOnlyList`1 GDDBDIJIBCO)","EJDCNGBEICB.txt","491","rooms/bulk" +"rooms","EJDCNGBEICB","RecRoom.Async.IPromise`1> GBMMJLHIDOK()","EJDCNGBEICB.txt","940","rooms/cheeredby/me" +"rooms","EJDCNGBEICB","RecRoom.Async.IPromise`1> OGIHJJDNPKJ(System.Int32 GKLPIFBPGOD)","EJDCNGBEICB.txt","1073","rooms/createdby/{0}" +"rooms","EJDCNGBEICB","RecRoom.Async.IPromise`1> MIMNJGKEGMF()","EJDCNGBEICB.txt","823","rooms/createdby/me" +"rooms","EJDCNGBEICB","RecRoom.Async.IPromise`1> AFOLHKGPMOC()","EJDCNGBEICB.txt","979","rooms/favoritedby/me" +"rooms","EJDCNGBEICB","RecRoom.Async.IPromise`1 DLPLPKCNLNA(System.Collections.Generic.IReadOnlyList`1 CAFPJPHILMN)","EJDCNGBEICB.txt","1601","rooms/hot" +"rooms","EJDCNGBEICB","RecRoom.Async.IPromise`1> IMKAAIEFGIG()","EJDCNGBEICB.txt","901","rooms/moderatedby/me" +"rooms","EJDCNGBEICB","RecRoom.Async.IPromise`1> FNMNFLFIBJG()","EJDCNGBEICB.txt","862","rooms/ownedby/me" +"rooms","EJDCNGBEICB","RecRoom.Async.IPromise`1> JDKLBNBIEAH(AMBEPDPIBPA NLNJMJGOKFI, System.Int16 NJJLKKLALND)","EJDCNGBEICB.txt","1430","rooms/recommendations" +"rooms","EJDCNGBEICB","RecRoom.Async.IPromise`1> JDMBPCJDEMF()","EJDCNGBEICB.txt","1119","rooms/rro_ids" +"rooms","EJDCNGBEICB","RecRoom.Async.IPromise`1 IAAKGMOGLFN(System.String CNBKKCJAHPP)","EJDCNGBEICB.txt","1516","rooms/search" +"rooms","EJDCNGBEICB","RecRoom.Async.IPromise`1> INKFBOEEKDA()","EJDCNGBEICB.txt","1018","rooms/visitedby/me" +"rooms","OJMCBOKJFOF","RecRoom.Async.IPromise`1> IAAKGMOGLFN(System.String CNBKKCJAHPP)","OJMCBOKJFOF.txt","3064","search_rooms/" +"rooms","OJMCBOKJFOF+PEBCIEAPGON","System.String IAAKGMOGLFN(System.String CNBKKCJAHPP)","OJMCBOKJFOF_NestedType_PEBCIEAPGON.txt","76","search_rooms/" +"rooms","OJMCBOKJFOF","RecRoom.Async.IPromise`1> HFHDFLDCIEM(System.String CNBKKCJAHPP)","OJMCBOKJFOF.txt","4567","search_roomsandplaylists/" +"rooms","OJMCBOKJFOF+PEBCIEAPGON","System.String HFHDFLDCIEM(System.String CNBKKCJAHPP)","OJMCBOKJFOF_NestedType_PEBCIEAPGON.txt","158","search_roomsandplaylists/" \ No newline at end of file diff --git a/docs/recroom-2020-client-request-expectations.md b/docs/recroom-2020-client-request-expectations.md new file mode 100644 index 0000000..8263b90 --- /dev/null +++ b/docs/recroom-2020-client-request-expectations.md @@ -0,0 +1,427 @@ +# Rec Room 2020 Client Request Expectations + +Generated from `dist/RecRoom-2020.12.18-isil/IsilDump/Assembly-CSharp` and grouped by unique route literal or route fragment. The readable C# dump often has empty method bodies, so expected results below are inferred from route names, nearby DTO declarations, known DorkNet server behavior, and the client service family. Use the CSV for exact call-site file/line context. + +Catalog CSV: `docs/recroom-2020-client-request-catalog.csv` + +Unique route/fragments: 214; raw call-site rows: 367. + +## account + +| Request literal / fragment | Expected result shape | Evidence | +| --- | --- | --- | +| `account/{0}` | Account profile object or success result, depending on verb. | PEGGCEDHBOF:RecRoom.Async.IPromise`1 NFKMGNHDCMN(System.Int32 GKLPIFBPGOD):PEGGCEDHBOF.txt:3934 | +| `account/{0}/bio` | Updated or fetched profile bio string/state; usually success object or account profile refresh. | PEGGCEDHBOF:RecRoom.Async.IPromise`1 GBLAOEFPDAH(System.Int32 GKLPIFBPGOD):PEGGCEDHBOF.txt:7866
PEGGCEDHBOF+<>c:System.Void b__67_0():PEGGCEDHBOF_NestedType___c.txt:587 | +| `account/{0}/clubs` | Account profile object or success result, depending on verb. | JDJGIBLMFKK:RecRoom.Async.IPromise`1> KGGMKBADLDM(System.Int32 GKLPIFBPGOD):JDJGIBLMFKK.txt:9580 | +| `account/bulk` | Array/list of account profile summaries for requested ids. | PEGGCEDHBOF:RecRoom.Async.IPromise`1> NHCHADDMPFC(System.Collections.Generic.List`1 ILNGMAANNDG):PEGGCEDHBOF.txt:4917 | +| `account/bulk?` | Array/list of account profile summaries for requested ids. | PEGGCEDHBOF:RecRoom.Async.IPromise`1> NHCHADDMPFC(System.Collections.Generic.List`1 ILNGMAANNDG):PEGGCEDHBOF.txt:4963 | +| `account/bulk/` | Array/list of account profile summaries for requested ids. | PEGGCEDHBOF:RecRoom.Async.IPromise`1> NGENLHMLHNG(System.Collections.Generic.List`1 BGELOMIEKFK, System.String MAFJOJLDFJO, System.String LOOAPLFGOEN, System.String PJIBCCFFMNJ, GPOOLJODEGM OLBDHLLJJHF):PEGGCEDHBOF.txt:9080 | +| `account/create` | Created account/player profile object plus login/account state; errors for duplicate or invalid names. | PEGGCEDHBOF:RecRoom.Async.IPromise`1 HHFPNFANNEG():PEGGCEDHBOF.txt:5418 | +| `account/me` | Current account/player profile object for the authenticated player. | PEGGCEDHBOF:RecRoom.Async.IPromise`1 HJFCFNCKDFO():PEGGCEDHBOF.txt:3576 | +| `account/me/` | Account profile object or success result, depending on verb. | PEGGCEDHBOF:RecRoom.Async.IPromise OEBEELJDOHE(BestHTTP.HTTPMethods APAICGIHAGJ, BestHTTP.Forms.HTTPUrlEncodedForm MDOPLMHIKLP, System.String LOOAPLFGOEN):PEGGCEDHBOF.txt:6704 | +| `account/me/changepassword` | Boolean/success result for password status/change/recovery. | MHOKOMMOGKM:RecRoom.Async.IPromise PGJBIIPMMJP(BestHTTP.Forms.HTTPUrlEncodedForm MDOPLMHIKLP, System.String MKIPICKCFDM):MHOKOMMOGKM.txt:2582 | +| `account/me/haspassword` | Boolean/success result for password status/change/recovery. | MHOKOMMOGKM:RecRoom.Async.IPromise`1 PPKFJEINCCN():MHOKOMMOGKM.txt:2203
MHOKOMMOGKM+<>c:System.Void b__32_0():MHOKOMMOGKM_NestedType___c.txt:584 | +| `account/recoverpassword` | Boolean/success result for password status/change/recovery. | MHOKOMMOGKM:RecRoom.Async.IPromise PMKEHDEDNCK(System.String LJKKKECANAB):MHOKOMMOGKM.txt:2779 | +| `account/search?name=` | Array/list of matching account profile summaries. | PEGGCEDHBOF:RecRoom.Async.IPromise`1> IBDGAKHBIAJ(System.String CNBKKCJAHPP):PEGGCEDHBOF.txt:9372 | + +## activities + +| Request literal / fragment | Expected result shape | Evidence | +| --- | --- | --- | +| `api/activities/charades/v1/words` | Activity-specific DTO/list, challenge/royale state, or charades word list. | CardBox:RecRoom.Async.IPromise`1> HMNGGFKCDGD():CardBox.txt:2422
CardBox:System.Void Start():CardBox.txt:2221 | +| `api/challenge/` | Activity-specific DTO/list, challenge/royale state, or charades word list. | PDMJMMMNGJE:System.Collections.IEnumerator EOBJGALHFNP(BPHGKAEDBPE+OJDJIJDNFHE AFLPGGJMPOE):PDMJMMMNGJE.txt:156
PDMJMMMNGJE:System.Void EKJPOJFLGFO(System.Int32 DKDDEIHEIBL, COLBJJAIEIO FMIIMOCIHCD):PDMJMMMNGJE.txt:395 | +| `api/royale/` | Activity-specific DTO/list, challenge/royale state, or charades word list. | JDFDLJJGHIP:RecRoom.Async.IPromise`1 APOAEDMOIHO():JDFDLJJGHIP.txt:266
JDFDLJJGHIP:RecRoom.Async.IPromise`1 NGEFKCDJAMF(JDFDLJJGHIP+MatchCompleteStats CNALBEPOKJJ):JDFDLJJGHIP.txt:477 | + +## avatar + +| Request literal / fragment | Expected result shape | Evidence | +| --- | --- | --- | +| `{0}v2/gifts/consume/` | Gift consumption success plus updated avatar/inventory/gift state. | NLEKGNENMCO:RecRoom.Async.IPromise PCKICLJFOBO(NLEKGNENMCO+LOCNECLOHCA HBBAANIPIMP, BPHGKAEDBPE+CBEOHBCIPEA AFLPGGJMPOE):NLEKGNENMCO.txt:2488 | +| `api/avatar/` | Avatar item/outfit/gift DTO or success result. | NLEKGNENMCO:RecRoom.Async.IPromise DLDENNPGADN():NLEKGNENMCO.txt:3480
NLEKGNENMCO:RecRoom.Async.IPromise PCKICLJFOBO(NLEKGNENMCO+LOCNECLOHCA HBBAANIPIMP, BPHGKAEDBPE+CBEOHBCIPEA AFLPGGJMPOE):NLEKGNENMCO.txt:2486
NLEKGNENMCO:RecRoom.Async.IPromise`1 JEKOPJCCHKB(GiftManager+LCLKAFOPBLD LHOMKMINCHH, System.Nullable`1 PCCLFNLJAMG, System.Boolean DKHOCHIJLBG):NLEKGNENMCO.txt:1504 | +| `api/avatar/v1/lockeditems?` | List of locked/unavailable avatar item ids. | NLEKGNENMCO:RecRoom.Async.IPromise`1> KLPMNFCFOLE(System.Collections.Generic.List`1 EGLBFNGKDLD):NLEKGNENMCO.txt:2931 | + +## bug-reporting + +| Request literal / fragment | Expected result shape | Evidence | +| --- | --- | --- | +| `api/bugreporting/` | Bug report submission success DTO. | RecNet.BugReporting:RecRoom.Async.IPromise ReportBug(System.String summary, System.String description, System.Byte[] screenshotData, System.Byte[] outputLogData, System.String testCaseKey):RecNet\BugReporting.txt:146 | + +## clubs + +| Request literal / fragment | Expected result shape | Evidence | +| --- | --- | --- | +| `{0}/club/{1}` | Club DTO/list or membership mutation success. | GNPDMBPGHBH:System.String JDEIGGFCBPF(System.Int64 AOMBLGBCENO):GNPDMBPGHBH.txt:630 | +| `announcements/club/{0}` | Club DTO/list or membership mutation success. | JDJGIBLMFKK:RecRoom.Async.IPromise`1 ONCKANJNGLA(System.Int64 AOMBLGBCENO):JDJGIBLMFKK.txt:1167
JDJGIBLMFKK:RecRoom.Async.IPromise`1 AAOLBAJFCOJ(JDJGIBLMFKK+DHPONMGBFJE JMFLHIIJFKL):JDJGIBLMFKK.txt:398 | +| `announcements/club/{0}/{1}` | Club DTO/list or membership mutation success. | JDJGIBLMFKK:RecRoom.Async.IPromise HCGFMHLEDPE(System.Int64 AOMBLGBCENO, System.Int64 LOHFFDEGIMK):JDJGIBLMFKK.txt:2462
JDJGIBLMFKK:RecRoom.Async.IPromise POHLPICNHJD(JDJGIBLMFKK+FOEPIDJCLMC JMFLHIIJFKL):JDJGIBLMFKK.txt:789 | +| `announcements/club/{0}/{1}/read` | Club DTO/list or membership mutation success. | JDJGIBLMFKK:RecRoom.Async.IPromise OFBNAHIOHON(System.Int64 AOMBLGBCENO, System.Int64 LOHFFDEGIMK):JDJGIBLMFKK.txt:2865 | +| `api/clubreporting/v1/report` | Report submission success. | JDJGIBLMFKK:RecRoom.Async.IPromise LBOOMIDNLPP(System.Int64 AOMBLGBCENO, LCCEEFHOBEN+CJFENPHAAHI MEABFEIBEMP, System.String EFDBFLPKHKA):JDJGIBLMFKK.txt:21791 | +| `club/{0}` | Club DTO/list or membership mutation success. | JDJGIBLMFKK:RecRoom.Async.IPromise IGHDIAEPHJD(System.Int64 AOMBLGBCENO, System.String DHANCEHHIDH):JDJGIBLMFKK.txt:13088
JDJGIBLMFKK:RecRoom.Async.IPromise`1 NKGOFNILGPL(System.Int64 AOMBLGBCENO, System.Boolean OHLIGBELLLH = True):JDJGIBLMFKK.txt:10425 | +| `club/{0}/additionalimage/{1}` | Club DTO/list or membership mutation success. | JDJGIBLMFKK:RecRoom.Async.IPromise`1 ELLIKHHEJFM(System.Int64 AOMBLGBCENO, System.Int32 EFBDCIJMFGD, System.String HFLPBHHAFIO):JDJGIBLMFKK.txt:12298 | +| `club/{0}/clubhouse` | Club DTO/list or membership mutation success. | JDJGIBLMFKK:RecRoom.Async.IPromise`1 HELJLMINDFD(System.Int64 AOMBLGBCENO, System.Nullable`1 HNHLJONGKHB):JDJGIBLMFKK.txt:12449 | +| `club/{0}/details` | Club DTO/list or membership mutation success. | JDJGIBLMFKK+BKCHBCIJHBN:RecRoom.Async.IPromise`1 b__0():JDJGIBLMFKK_NestedType_BKCHBCIJHBN.txt:202 | +| `club/{0}/mainimage` | Club DTO/list or membership mutation success. | JDJGIBLMFKK:RecRoom.Async.IPromise`1 OBFKAFGALDM(System.Int64 AOMBLGBCENO, System.String HFLPBHHAFIO):JDJGIBLMFKK.txt:12138 | +| `club/{0}/members/{1}` | Club membership mutation success or updated member DTO/list. | JDJGIBLMFKK:RecRoom.Async.IPromise`1 BIOJPNFPNCE(System.Int64 AOMBLGBCENO, System.Int32 GKLPIFBPGOD):JDJGIBLMFKK.txt:15776 | +| `club/{0}/members/acceptinvite` | Club membership mutation success or updated member DTO/list. | JDGDFALBCDJ:RecRoom.Async.IPromise HHLPPOBHMOC():JDGDFALBCDJ.txt:426
JDJGIBLMFKK:RecRoom.Async.IPromise FAOINNCBNFC(System.Int64 AOMBLGBCENO):JDJGIBLMFKK.txt:20586 | +| `club/{0}/members/acceptrequest` | Club membership mutation success or updated member DTO/list. | JDJGIBLMFKK:RecRoom.Async.IPromise AGEGBKKPJNN(System.Int64 AOMBLGBCENO, System.Int32 GKLPIFBPGOD):JDJGIBLMFKK.txt:18418 | +| `club/{0}/members/acceptrequests` | Club membership mutation success or updated member DTO/list. | JDJGIBLMFKK:RecRoom.Async.IPromise FKPIMLCILLI(System.Int64 AOMBLGBCENO, System.Collections.Generic.IEnumerable`1 ILNGMAANNDG):JDJGIBLMFKK.txt:18831 | +| `club/{0}/members/ban` | Club membership mutation success or updated member DTO/list. | JDJGIBLMFKK:RecRoom.Async.IPromise OKICAHCMPFL(System.Int64 AOMBLGBCENO, System.Int32 GKLPIFBPGOD):JDJGIBLMFKK.txt:21265 | +| `club/{0}/members/changetype` | Club membership mutation success or updated member DTO/list. | JDJGIBLMFKK:RecRoom.Async.IPromise JLBCCEIBFJJ(System.Int64 AOMBLGBCENO, System.Int32 GKLPIFBPGOD, PPGPAHNMGEC OCHEGLOFMEA):JDJGIBLMFKK.txt:21094 | +| `club/{0}/members/declineinvite` | Club membership mutation success or updated member DTO/list. | JDGDFALBCDJ:RecRoom.Async.IPromise BDFBMBCKJEP():JDGDFALBCDJ.txt:697
JDJGIBLMFKK:RecRoom.Async.IPromise GJJBKCEMLDC(System.Int64 AOMBLGBCENO):JDJGIBLMFKK.txt:20675 | +| `club/{0}/members/denyrequest` | Club membership mutation success or updated member DTO/list. | JDJGIBLMFKK:RecRoom.Async.IPromise LPCHODIHIBB(System.Int64 AOMBLGBCENO, System.Int32 GKLPIFBPGOD):JDJGIBLMFKK.txt:19048 | +| `club/{0}/members/denyrequests` | Club membership mutation success or updated member DTO/list. | JDJGIBLMFKK:RecRoom.Async.IPromise CJBOECCIIJB(System.Int64 AOMBLGBCENO, System.Collections.Generic.IEnumerable`1 ILNGMAANNDG):JDJGIBLMFKK.txt:19461 | +| `club/{0}/members/directJoin` | Club membership mutation success or updated member DTO/list. | JDJGIBLMFKK:RecRoom.Async.IPromise AJCDOLIBBKC(System.Int64 AOMBLGBCENO, PJCALHOPMKJ IEGCAIGJBBP, System.Int32 ENGEEKIMIGO):JDJGIBLMFKK.txt:20450 | +| `club/{0}/members/invite` | Club membership mutation success or updated member DTO/list. | JDJGIBLMFKK:RecRoom.Async.IPromise FEIADJMHICD(System.Int64 AOMBLGBCENO, System.Int32 GKLPIFBPGOD, PPGPAHNMGEC PGMGFKOCEDG):JDJGIBLMFKK.txt:19703 | +| `club/{0}/members/invitemembers` | Club membership mutation success or updated member DTO/list. | JDJGIBLMFKK:RecRoom.Async.IPromise HOHPKNPHCFK(System.Int64 AOMBLGBCENO, System.Collections.Generic.List`1 ILNGMAANNDG, OFIEEDOMGPA NAMECJCFEDN):JDJGIBLMFKK.txt:20070 | +| `club/{0}/members/leave` | Club membership mutation success or updated member DTO/list. | JDJGIBLMFKK:RecRoom.Async.IPromise EBADBEIGOEN(System.Int64 AOMBLGBCENO):JDJGIBLMFKK.txt:20745 | +| `club/{0}/members/remove` | Club membership mutation success or updated member DTO/list. | JDJGIBLMFKK:RecRoom.Async.IPromise LDOGPIHJOLP(System.Int64 AOMBLGBCENO, System.Int32 GKLPIFBPGOD):JDJGIBLMFKK.txt:20895 | +| `club/{0}/members/requesttojoin` | Club membership mutation success or updated member DTO/list. | JDJGIBLMFKK:RecRoom.Async.IPromise NJKBIMEEAPE(System.Int64 AOMBLGBCENO):JDJGIBLMFKK.txt:18249 | +| `club/{0}/members/unban` | Club membership mutation success or updated member DTO/list. | JDJGIBLMFKK:RecRoom.Async.IPromise OGMGOBHPMFC(System.Int64 AOMBLGBCENO, System.Int32 GKLPIFBPGOD):JDJGIBLMFKK.txt:21435 | +| `club/{0}/modify` | Club DTO/list or membership mutation success. | JDJGIBLMFKK:RecRoom.Async.IPromise`1 CKJIKPOIMFE(JDJGIBLMFKK+ACNILMIFDJJ JMFLHIIJFKL):JDJGIBLMFKK.txt:11620 | +| `club/{0}/modifydetails` | Club DTO/list or membership mutation success. | JDJGIBLMFKK:RecRoom.Async.IPromise`1 KOPAANDJNKB(JDJGIBLMFKK+FHFOCPGBMPB JMFLHIIJFKL):JDJGIBLMFKK.txt:12001 | +| `club/{0}/permissions/{1}` | Club DTO/list or membership mutation success. | JDJGIBLMFKK:RecRoom.Async.IPromise`1 GHBJEDCEKLK(JHEEFBMODPG OBJEOMAGODL):JDJGIBLMFKK.txt:13631 | +| `club/account/{0}/created` | List/page of clubs related to an account. | JDJGIBLMFKK:RecRoom.Async.IPromise`1> DIAPLKMFFKO(System.Int32 GKLPIFBPGOD):JDJGIBLMFKK.txt:9416
JDJGIBLMFKK:System.String JANFGLBCBMJ(System.Int32 GKLPIFBPGOD):JDJGIBLMFKK.txt:7880
JDJGIBLMFKK:System.Void PJBLNGJKLEB(System.Int64 AOMBLGBCENO):JDJGIBLMFKK.txt:26175 | +| `club/categoryTags` | Club DTO/list or membership mutation success. | JDJGIBLMFKK:RecRoom.Async.IPromise`1> PAKNBFOJENP():JDJGIBLMFKK.txt:13314 | +| `club/create` | Club DTO/list or membership mutation success. | JDJGIBLMFKK:RecRoom.Async.IPromise`1 KMPJFFBGJMD(JDJGIBLMFKK+ACNILMIFDJJ JMFLHIIJFKL):JDJGIBLMFKK.txt:11381 | +| `club/home/me` | Club DTO/list or membership mutation success. | JDJGIBLMFKK:RecRoom.Async.IPromise`1 DBDOOMCNNFE():JDJGIBLMFKK.txt:10003
JDJGIBLMFKK+<>c:System.Void b__106_0():JDJGIBLMFKK_NestedType___c.txt:995
JDJGIBLMFKK+<>c:System.Void b__106_2():JDJGIBLMFKK_NestedType___c.txt:954 | +| `club/mine/created` | Club DTO/list or membership mutation success. | JDJGIBLMFKK:RecRoom.Async.IPromise`1> LJJLGJIMJED():JDJGIBLMFKK.txt:9275
JDJGIBLMFKK:System.Void PJBLNGJKLEB(System.Int64 AOMBLGBCENO):JDJGIBLMFKK.txt:26134
JDJGIBLMFKK:System.Void PJBLNGJKLEB(System.Int64 AOMBLGBCENO):JDJGIBLMFKK.txt:26326 | +| `club/mine/member` | Club DTO/list or membership mutation success. | JDJGIBLMFKK:RecRoom.Async.IPromise`1> DFDMGNBKEKO():JDJGIBLMFKK.txt:9817
JDJGIBLMFKK:System.Void PJBLNGJKLEB(System.Int64 AOMBLGBCENO):JDJGIBLMFKK.txt:26194
JDJGIBLMFKK:System.Void PJBLNGJKLEB(System.Int64 AOMBLGBCENO):JDJGIBLMFKK.txt:26330 | +| `members/bulk` | Bulk member/account membership DTO list. | JDJGIBLMFKK:RecRoom.Async.IPromise`1> DDIJPHDEDDM(System.Int64 AOMBLGBCENO, System.Collections.Generic.List`1 ILNGMAANNDG):JDJGIBLMFKK.txt:16847 | +| `members/bulk?` | Bulk member/account membership DTO list. | JDJGIBLMFKK:RecRoom.Async.IPromise`1> DDIJPHDEDDM(System.Int64 AOMBLGBCENO, System.Collections.Generic.List`1 ILNGMAANNDG):JDJGIBLMFKK.txt:16893 | + +## config-settings + +| Request literal / fragment | Expected result shape | Evidence | +| --- | --- | --- | +| `/config/{0}` | Config DTO/object for requested key. | FHLGJDFHOKL:RecRoom.Async.IPromise MCDNGDMJHBE():FHLGJDFHOKL.txt:502 | +| `api/config/` | Config DTO/object for requested key. | BBEFMHAEEEA:RecRoom.Async.IPromise BGNDHOHFJBN():BBEFMHAEEEA.txt:950
BBEFMHAEEEA:RecRoom.Async.IPromise OHHAEMHMIFE():BBEFMHAEEEA.txt:655
BBEFMHAEEEA:RecRoom.Async.IPromise`1> EAJPLMPNPIJ(System.Int32 CKEBCMEKCJH):BBEFMHAEEEA.txt:1147 | +| `api/config/v1/freegiftbutton` | Config DTO/object for requested key. | BBEFMHAEEEA:RecRoom.Async.IPromise`1 GMOMCLADPII():BBEFMHAEEEA.txt:1230 | +| `api/gameconfigs/` | Config DTO/object for requested key. | IGCCFMFHBBN:System.Void .cctor():IGCCFMFHBBN.txt:1921 | +| `api/settings/` | Player settings DTO or mutation success. | JFDILELKPAL:RecRoom.Async.IPromise`1> HOHPJKNPEGK():JFDILELKPAL.txt:48
JFDILELKPAL:System.Collections.IEnumerator LHGBAFBJAHM(OHLKCDKEGMN AOKDECDAEEG, BPHGKAEDBPE+OJDJIJDNFHE AFLPGGJMPOE):JFDILELKPAL.txt:224 | + +## economy + +| Request literal / fragment | Expected result shape | Evidence | +| --- | --- | --- | +| `{0}v1/consume` | Consumable/pageview consumption success. | FOEHJBDMMBH:System.Collections.IEnumerator LCELAOEOIJO(OOCCCGPICOG BAJGNABECMN, System.Int32 FKKAPHAMPMG, BPHGKAEDBPE+CBEOHBCIPEA AFLPGGJMPOE):FOEHJBDMMBH.txt:840 | +| `/consume` | Consumable/pageview consumption success. | COFHGNFJMOG:RecRoom.Async.IPromise`1 HDLJGFEDGLH(System.String ENJEOLBEALP, System.Nullable`1 LNBJKLOINED):COFHGNFJMOG.txt:1242 | +| `api/gamerewards/v1/pending` | List of pending reward choices. | COICJCJBABL:RecRoom.Async.IPromise AOJJODPOJFO():COICJCJBABL.txt:520 | +| `api/gamerewards/v1/select` | Reward selection success plus granted inventory/currency item. | COICJCJBABL:RecRoom.Async.IPromise ONNNOAKDIIP(COICJCJBABL+HNJGHCGJJFC FDAEIMEHDJJ, System.Int32 IAAKEHDHCAC):COICJCJBABL.txt:1055 | +| `pageview/consume` | Consumable/pageview consumption success. | COFHGNFJMOG:RecRoom.Async.IPromise`1 LFHFLODHPJJ():COFHGNFJMOG.txt:95 | + +## elo + +| Request literal / fragment | Expected result shape | Evidence | +| --- | --- | --- | +| `api/PlayerElo/` | Player ELO/rating update or lookup DTO. | RecNet.Elo:System.Void UpdatePlayersElo(RecNet.Elo+PlayersEloUpdateDTO JFKDDPJDCDC):RecNet\Elo.txt:141 | + +## equipment + +| Request literal / fragment | Expected result shape | Evidence | +| --- | --- | --- | +| `api/equipment/` | Equipment/loadout DTO or mutation success. | ECINAMCDBJO:System.Void EEDJOJINECJ():ECINAMCDBJO.txt:468
ECINAMCDBJO+GPBDCOCADGE:System.Boolean MoveNext():ECINAMCDBJO_NestedType_GPBDCOCADGE.txt:147 | + +## groups + +| Request literal / fragment | Expected result shape | Evidence | +| --- | --- | --- | +| `api/groups/` | Group DTO/list or group membership mutation success. | EJECIMCPGMG:RecRoom.Async.IPromise`1 CCDHCLAOFOJ(System.Int64 BCNAOOIPEJO):EJECIMCPGMG.txt:337
EJECIMCPGMG:RecRoom.Async.IPromise`1 PDJKECHIHNP(System.Int64 CJFGEMGOJHB):EJECIMCPGMG.txt:555
EJECIMCPGMG:System.Collections.IEnumerator JAHJIFFICHN(System.String NDNLEGKJGCD, System.String LJIGOCDPEJF, System.String HFLPBHHAFIO, BPHGKAEDBPE+OJDJIJDNFHE AFLPGGJMPOE):EJECIMCPGMG.txt:803 | + +## images + +| Request literal / fragment | Expected result shape | Evidence | +| --- | --- | --- | +| `api/images/` | Image upload/detail/list result; image binary is later fetched from image/CDN/storage URL. | OHDHPENHDAP:RecRoom.Async.IPromise BFHNENPOEFB(System.Int64 LKNNMPCBCKM, System.Boolean JBAGLLBLEEN):OHDHPENHDAP.txt:1571
OHDHPENHDAP:RecRoom.Async.IPromise FEOMMGBJFIN(System.Int64 LKNNMPCBCKM):OHDHPENHDAP.txt:2387
OHDHPENHDAP:RecRoom.Async.IPromise OFEBNGPIJMB():OHDHPENHDAP.txt:4467 | + +## inventions + +| Request literal / fragment | Expected result shape | Evidence | +| --- | --- | --- | +| `api/inventions/` | Invention DTO or mutation success. | BBHENFCNLAB:RecRoom.Async.IPromise`1 ANJHOOPIAKM(System.Int64 OEMDIAHHILF, System.Boolean JBAGLLBLEEN):BBHENFCNLAB.txt:7610
BBHENFCNLAB:RecRoom.Async.IPromise`1 BEPNJCGNIKA(System.Int64 OEMDIAHHILF, HECIICKPCDN AEHDODIANMG, System.Nullable`1 MACNIENMFHJ = null):BBHENFCNLAB.txt:6169
BBHENFCNLAB:RecRoom.Async.IPromise`1 HDFICFBNFOK(System.Int64 OEMDIAHHILF, System.Int32 MACNIENMFHJ):BBHENFCNLAB.txt:6479 | +| `api/inventions/v1/fulllineageowner?` | Boolean/ownership lineage result. | BBHENFCNLAB:RecRoom.Async.IPromise`1 DEOOEGHNIAJ(System.Collections.Generic.List`1 POGCOENDJDJ):BBHENFCNLAB.txt:7823 | +| `api/inventions/v3/addversion` | Saved invention/version DTO containing invention id, version/data/image blob names, costs, ownership, and permission metadata. | BBHENFCNLAB+OIJIEFMJMKO:RecRoom.Async.IPromise`1 b__0(System.String filename):BBHENFCNLAB_NestedType_OIJIEFMJMKO.txt:216 | +| `api/inventions/v4/save` | Saved invention/version DTO containing invention id, version/data/image blob names, costs, ownership, and permission metadata. | BBHENFCNLAB+FODDLIKPKPE:RecRoom.Async.IPromise`1 b__0(System.String filename):BBHENFCNLAB_NestedType_FODDLIKPKPE.txt:250 | + +## matchmaking + +| Request literal / fragment | Expected result shape | Evidence | +| --- | --- | --- | +| `goto/club/{0}` | Room instance/join response: roomInstanceId, roomId, subRoomId, location, photonRegionId, photonRoomId, maxCapacity, isFull, isPrivate, isInProgress. | RecNet.Matchmaking:RecRoom.Async.IPromise`1 IIDEDGKAGOE(PLILLKHMNDA EGLGJIONCCP, System.Int32[] DKFBAOLAEFE = null, System.Boolean KJHCANCBKAL = False):RecNet\Matchmaking.txt:8847 | +| `goto/code/` | Room instance/join response: roomInstanceId, roomId, subRoomId, location, photonRegionId, photonRoomId, maxCapacity, isFull, isPrivate, isInProgress. | RecNet.Matchmaking:RecRoom.Async.IPromise`1 FBLFCNDIPMP(System.String BFGCDJFNJLE, System.String KIIFGNILJEA, System.Int32[] DKFBAOLAEFE = null, System.Boolean KJHCANCBKAL = True, System.Boolean PBCPIEHFDBH = False):RecNet\Matchmaking.txt:8226 | +| `goto/event/{0}` | Room instance/join response: roomInstanceId, roomId, subRoomId, location, photonRegionId, photonRoomId, maxCapacity, isFull, isPrivate, isInProgress. | RecNet.Matchmaking:RecRoom.Async.IPromise`1 MLIILFNOOOK(CCMBKDINCAH IAALIIFNHNP, System.Int32[] DKFBAOLAEFE = null, System.Boolean KJHCANCBKAL = False):RecNet\Matchmaking.txt:8737 | +| `goto/instance/{0}` | Room instance/join response: roomInstanceId, roomId, subRoomId, location, photonRegionId, photonRoomId, maxCapacity, isFull, isPrivate, isInProgress. | RecNet.Matchmaking:RecRoom.Async.IPromise`1 MGKFDNPCDBP(System.Int64 ANAPBECHGLI, System.Int32[] DKFBAOLAEFE = null, System.Boolean KJHCANCBKAL = False, System.String APJJOJMDLNP = null):RecNet\Matchmaking.txt:9156 | +| `goto/invite/{0}` | Room instance/join response: roomInstanceId, roomId, subRoomId, location, photonRegionId, photonRoomId, maxCapacity, isFull, isPrivate, isInProgress. | RecNet.Matchmaking:RecRoom.Async.IPromise`1 ALCJNMAOIGH(System.Int64 IAHDKAIJJLB, System.Int64 HNHLJONGKHB, System.Int32[] DKFBAOLAEFE = null, System.Boolean KJHCANCBKAL = False):RecNet\Matchmaking.txt:9294 | +| `goto/none` | Room instance/join response: roomInstanceId, roomId, subRoomId, location, photonRegionId, photonRoomId, maxCapacity, isFull, isPrivate, isInProgress. | RecNet.Matchmaking:RecRoom.Async.IPromise OAHNGAMAPNB():RecNet\Matchmaking.txt:10314 | +| `goto/player/{0}` | Room instance/join response: roomInstanceId, roomId, subRoomId, location, photonRegionId, photonRoomId, maxCapacity, isFull, isPrivate, isInProgress. | RecNet.Matchmaking:RecRoom.Async.IPromise`1 IMMCGNNPIAD(System.Int32 CJFGEMGOJHB, System.Int32[] DKFBAOLAEFE = null, System.Boolean KJHCANCBKAL = False):RecNet\Matchmaking.txt:8980 | +| `goto/playlist/` | Room instance/join response: roomInstanceId, roomId, subRoomId, location, photonRegionId, photonRoomId, maxCapacity, isFull, isPrivate, isInProgress. | RecNet.Matchmaking:RecRoom.Async.IPromise`1 NDKKNPMAGNK(System.String APJJOJMDLNP, System.Boolean OFFEBBLOEJA = False, System.Int32[] DKFBAOLAEFE = null, System.Boolean KJHCANCBKAL = False):RecNet\Matchmaking.txt:8050 | +| `goto/room/` | Room instance/join response: roomInstanceId, roomId, subRoomId, location, photonRegionId, photonRoomId, maxCapacity, isFull, isPrivate, isInProgress. | RecNet.Matchmaking:RecRoom.Async.IPromise`1 GDCFFOJEIMD():RecNet\Matchmaking.txt:7509
RecNet.Matchmaking:RecRoom.Async.IPromise`1 ILPIDHICKMB(System.String BFGCDJFNJLE, System.Boolean OFFEBBLOEJA = False, System.Int32[] DKFBAOLAEFE = null, System.Boolean KJHCANCBKAL = False, System.String APJJOJMDLNP = null, System.Boolean PBCPIEHFDBH = False):RecNet\Matchmaking.txt:8426
RecNet.Matchmaking:RecRoom.Async.IPromise`1 JLMFIHJLNBL(System.String HFDPAGDDNDE, System.Int32[] DKFBAOLAEFE, System.Boolean PBCPIEHFDBH, System.Boolean KJHCANCBKAL, System.Boolean ALOAFENOBGN):RecNet\Matchmaking.txt:7882 | +| `room/{0}/instances` | List of active room instances with Photon and capacity/state fields. | RecNet.Matchmaking:RecRoom.Async.IPromise`1> KFMFHGLEGHF(System.Int64 HNHLJONGKHB):RecNet\Matchmaking.txt:10726 | + +## messages + +| Request literal / fragment | Expected result shape | Evidence | +| --- | --- | --- | +| `api/messages/` | Message/thread DTO/list or mutation success. | KEBJPIGKGOI:RecRoom.Async.IPromise JHJDMMLABCG():KEBJPIGKGOI.txt:1297
KEBJPIGKGOI:RecRoom.Async.IPromise MPLDGJCCIMN(System.Int64 CJFGEMGOJHB, IGCIBGKPPMO+BBELBJELLHN JMDIPDGMIOG, System.String ABADFLCBFIJ, System.Nullable`1 HNHLJONGKHB):KEBJPIGKGOI.txt:2653
KEBJPIGKGOI:RecRoom.Async.IPromise`1 IIHKDEGDMEA():KEBJPIGKGOI.txt:6118 | +| `api/messages/v1/IOSClearDeviceToken` | Primitive success result for device token/preferences mutation. | KEBJPIGKGOI:RecRoom.Async.IPromise LLIHILNLNEP():KEBJPIGKGOI.txt:5886 | +| `api/messages/v1/IOSModifyNotificationPreferences` | Primitive success result for device token/preferences mutation. | KEBJPIGKGOI:RecRoom.Async.IPromise NDONIOINHAE(DIBODMEJOPN JFMOEGCPNAC):KEBJPIGKGOI.txt:6455 | +| `api/messages/v1/IOSResetNotificationPreferencesBadgeCount` | Primitive success result for device token/preferences mutation. | KEBJPIGKGOI:RecRoom.Async.IPromise GENJNKHDHCN():KEBJPIGKGOI.txt:6347 | +| `api/messages/v1/IOSSaveDeviceToken` | Primitive success result for device token/preferences mutation. | KEBJPIGKGOI:RecRoom.Async.IPromise INNJDBLBCEO(System.String EAEICOOGLAK):KEBJPIGKGOI.txt:5768 | +| `api/messages/v3/delete` | Delete success result for message ids. | KEBJPIGKGOI:System.Collections.IEnumerator PEIBHPIEKGF(System.Collections.Generic.IEnumerable`1 OCCKKEIMPGP, BPHGKAEDBPE+CBEOHBCIPEA AFLPGGJMPOE = null):KEBJPIGKGOI.txt:3138 | +| `api/offlineinvite/` | Offline invite DTO/list or success result. | KEBJPIGKGOI:System.Collections.IEnumerator LPPPAHLCPCO(System.Int64 CJFGEMGOJHB, BPHGKAEDBPE+OJDJIJDNFHE AFLPGGJMPOE):KEBJPIGKGOI.txt:6606 | + +## misc + +| Request literal / fragment | Expected result shape | Evidence | +| --- | --- | --- | +| `{0}v1/bulkignoreplatformusers` | Unknown/route-fragment result; inspect continuation for exact DTO. | FGPIDGLCKEF:System.Void IMLJEBCMMKK(PlatformManager+FCIKKFJOMNO FPMCLJDEGKL, System.Collections.Generic.List`1 CIBFMCPBLEO):FGPIDGLCKEF.txt:2834 | +| `/bulk` | Unknown/route-fragment result; inspect continuation for exact DTO. | GNPDMBPGHBH:RecRoom.Async.IPromise`1> DMHOIAJGABC(System.Collections.Generic.IReadOnlyList`1 JOGAGJIPEDN):GNPDMBPGHBH.txt:2388
GNPDMBPGHBH+MKCNPHKGFDM:RecRoom.Async.IPromise`1> b__1(System.String uri):GNPDMBPGHBH_NestedType_MKCNPHKGFDM.txt:138 | +| `/data/{0}` | Unknown/route-fragment result; inspect continuation for exact DTO. | KELEPAPMOGK+CJACMOFOFKJ:RecRoom.Async.IPromise`1 b__0():KELEPAPMOGK_NestedType_CJACMOFOFKJ.txt:187 | +| `/room/` | Unknown/route-fragment result; inspect continuation for exact DTO. | OJMCBOKJFOF+BPPECKDOINI:RecRoom.Async.IPromise`1 b__0():OJMCBOKJFOF_NestedType_BPPECKDOINI.txt:206 | +| `api/announcement/v1/get` | Announcement DTO/list. | NLGOJMONPKG:RecRoom.Async.IPromise`1> ADEEFOCPDFH():NLGOJMONPKG.txt:379 | +| `api/catalog/v1/all?onlyAvailableSkus=true` | Store catalog/storefront DTO list. | GCBPKHFJKCE:RecRoom.Async.IPromise`1> KABHMCDHHIP(System.Boolean INGAKMAAHKL = False):GCBPKHFJKCE.txt:555
GCBPKHFJKCE:RecRoom.Async.IPromise`1> KABHMCDHHIP(System.Boolean INGAKMAAHKL = False):GCBPKHFJKCE.txt:577
GCBPKHFJKCE+<>c:System.Void b__41_1():GCBPKHFJKCE_NestedType___c.txt:791 | +| `api/communityboard/` | Community board state DTO. | AAHIIPOCKMB:RecRoom.Async.IPromise`1 KIAGEGDDBCG():AAHIIPOCKMB.txt:941 | +| `api/consumables/` | Consumables inventory/list or consume result. | FOEHJBDMMBH:RecRoom.Async.IPromise AKFDEPEHBIN(OOCCCGPICOG BAJGNABECMN, System.Int32 HLHHPEKALPI):FOEHJBDMMBH.txt:1463
FOEHJBDMMBH:RecRoom.Async.IPromise`1> AFMKIHFMMBI(System.Int32 HLHHPEKALPI):FOEHJBDMMBH.txt:1112
FOEHJBDMMBH:System.Collections.IEnumerator LCELAOEOIJO(OOCCCGPICOG BAJGNABECMN, System.Int32 FKKAPHAMPMG, BPHGKAEDBPE+CBEOHBCIPEA AFLPGGJMPOE):FOEHJBDMMBH.txt:838 | +| `api/curatedroomplaylists` | Unknown/route-fragment result; inspect continuation for exact DTO. | FPNPBJBCMKB:RecRoom.Async.IPromise`1> EPPPMHMMGME():FPNPBJBCMKB.txt:53 | +| `api/objectives/` | Objectives/progress list. | GMPIMLJNACB:System.Collections.IEnumerator IPCNHLDICBM(BPHGKAEDBPE+CBEOHBCIPEA AFLPGGJMPOE):GMPIMLJNACB.txt:904
GMPIMLJNACB:System.Void MEGMCCFCEIP(System.Int32 CKEBCMEKCJH, BPHGKAEDBPE+OJDJIJDNFHE AFLPGGJMPOE):GMPIMLJNACB.txt:2171
GMPIMLJNACB+GIDAGFNPPLC:System.Boolean MoveNext():GMPIMLJNACB_NestedType_GIDAGFNPPLC.txt:568 | +| `api/PlayersBanned/` | Banned-player list or boolean state. | GGLGFENEKBJ:System.Collections.IEnumerator BFJMPGHLGAN(System.Int64 CJFGEMGOJHB, LCCEEFHOBEN+CJFENPHAAHI MEABFEIBEMP, BPHGKAEDBPE+CBEOHBCIPEA AFLPGGJMPOE):GGLGFENEKBJ.txt:586 | +| `api/relationships/` | Relationship list/state or mutation success. | FGPIDGLCKEF:RecRoom.Async.IPromise`1 CGPBLDOHCEI(System.Int32 CJFGEMGOJHB, CFLMJLFBOKH+MPFKNCFEDHF DIEPOPNJNCO):FGPIDGLCKEF.txt:1454
FGPIDGLCKEF:RecRoom.Async.IPromise`1 DLBAPGPACLP(System.Int32 CJFGEMGOJHB):FGPIDGLCKEF.txt:1827
FGPIDGLCKEF:RecRoom.Async.IPromise`1 DOIEMGNIMMM(System.Int32 CJFGEMGOJHB, BPHGKAEDBPE+CBEOHBCIPEA AFLPGGJMPOE = null):FGPIDGLCKEF.txt:1197 | +| `api/sanitize/` | Sanitized text/string result. | NKHBKKGOIHL+BBGMJPMOHEO:System.Boolean MoveNext():NKHBKKGOIHL_NestedType_BBGMJPMOHEO.txt:353 | +| `api/storefronts/` | Store catalog/storefront DTO list. | GEAPBDGCKMB:RecRoom.Async.IPromise ACEPPELPFOL(System.Int32 PGGHBNCGLDN, System.Nullable`1 POAOENHPNHE, System.Int32 MNIJHBJDPPA, System.Int32 HLHHPEKALPI):GEAPBDGCKMB.txt:2977
GEAPBDGCKMB:RecRoom.Async.IPromise`1> LLNACOGDCMF(ACDKILABNNC DLNFAILEHOA, System.Collections.Generic.IEnumerable`1 JEDEBGBEGCE):GEAPBDGCKMB.txt:5929
GEAPBDGCKMB:RecRoom.Async.IPromise`1 MOANDFACFAP(System.Int64 OEMDIAHHILF, System.Int32 HBIEIIHBDCI, System.String LGHBPPIOOBM):GEAPBDGCKMB.txt:3603 | +| `api/testcasemanagement/` | Test-case management DTO; likely internal/dev only. | AICHDPNIEKI:RecRoom.Async.IPromise CNGKABDOEKH(System.String EPGHLDONDIP, NIDIHGENDJD AKPHCJFIPBB):AICHDPNIEKI.txt:450
AICHDPNIEKI:RecRoom.Async.IPromise DHDOAJAGEJN(System.String EPGHLDONDIP):AICHDPNIEKI.txt:360
AICHDPNIEKI:RecRoom.Async.IPromise KNOGAGHKIMC(System.String EPGHLDONDIP):AICHDPNIEKI.txt:273 | +| `api/versioncheck/v4?v={0}&p={1}` | Unknown/route-fragment result; inspect continuation for exact DTO. | KMDHPCHFADM:RecRoom.Async.IPromise`1 BPAGDCDBHAM():KMDHPCHFADM.txt:282 | +| `https://apps.apple.com/account/subscriptions` | External URL launch; no RecNet JSON expected. | IOSPlatformManager:RecRoom.Async.IPromise ShowManageSubscriptionPlatformUI():IOSPlatformManager.txt:4865 | +| `https://www.instagram.com/recroom/` | External URL launch; no RecNet JSON expected. | InstagramBulletinBoardUI:System.Void Button_RecRoomOnInstagram():InstagramBulletinBoardUI.txt:1009 | + +## player-events + +| Request literal / fragment | Expected result shape | Evidence | +| --- | --- | --- | +| `api/playerevents/` | Player event DTO/list or mutation success. | GNPDMBPGHBH:RecRoom.Async.IPromise`1 BFBMDMLHHCF(CCMBKDINCAH MFJCKOBPMGA, System.Int64 HNHLJONGKHB, System.Nullable`1 FODGKNJIGOP, System.String MMBOKOLAJFH, System.String LJIGOCDPEJF, System.Collections.Generic.List`1 CAFPJPHILMN, System.String HFLPBHHAFIO, System.DateTime JODPOANPJNK, System.DateTime BCANLCHBKJE, CMCAFKLAHCD PONCIIJOHIE, System.Nullable`1 AOMBLGBCENO):GNPDMBPGHBH.txt:4280
GNPDMBPGHBH:RecRoom.Async.IPromise`1 EKEOHKINPDK(CCMBKDINCAH MFJCKOBPMGA):GNPDMBPGHBH.txt:6151
GNPDMBPGHBH:RecRoom.Async.IPromise`1> MBHCOIGLAIF(System.Int64 MOKMJAMNEFP, System.Boolean INGAKMAAHKL = False):GNPDMBPGHBH.txt:3736 | +| `api/playerevents/v1/all` | Paged event list or filter/tag list. | GNPDMBPGHBH:RecRoom.Async.IPromise`1 JKFDNKOBOJE():GNPDMBPGHBH.txt:5504 | +| `api/playerevents/v1/bulkInvite` | Bulk invite success/failure result. | GNPDMBPGHBH:RecRoom.Async.IPromise`1 MGDLGLLOEMG(System.Int64 MOKMJAMNEFP, System.Collections.Generic.List`1 MHCONOPOOKJ):GNPDMBPGHBH.txt:5013 | +| `api/playerevents/v1/deleteResponse` | Primitive success or report/delete result. | GNPDMBPGHBH:RecRoom.Async.IPromise`1 FGFDJIKJKNN(System.Int64 MOKMJAMNEFP, FFEICMIIBMC DHLDCHCKBPC):GNPDMBPGHBH.txt:4769 | +| `api/playerevents/v1/report` | Primitive success or report/delete result. | GNPDMBPGHBH:RecRoom.Async.IPromise`1 GLHAJLLNPBJ(System.Int64 MOKMJAMNEFP, LCCEEFHOBEN+CJFENPHAAHI MEABFEIBEMP, System.String EFDBFLPKHKA):GNPDMBPGHBH.txt:5939 | +| `api/playerevents/v1/respond` | Player event DTO/list or mutation success. | GNPDMBPGHBH:RecRoom.Async.IPromise`1 EEJGIOKDBFI(System.Int64 MOKMJAMNEFP, FFEICMIIBMC DHLDCHCKBPC):GNPDMBPGHBH.txt:4567 | +| `api/playerevents/v1/search?` | Paged event list or filter/tag list. | GNPDMBPGHBH:RecRoom.Async.IPromise`1> ADMFNGBFPIE(System.String CNBKKCJAHPP, GNPDMBPGHBH+EHKGCEONBIB DKLJMLFNEEN = 0, System.Nullable`1 LBFLCMJFDPC = null):GNPDMBPGHBH.txt:9248 | +| `api/playerevents/v1/searchlive?` | Paged event list or filter/tag list. | GNPDMBPGHBH:RecRoom.Async.IPromise`1> JBOMFDKLBOI(System.String CNBKKCJAHPP):GNPDMBPGHBH.txt:8935 | +| `api/playerevents/v1/tagfilters` | Paged event list or filter/tag list. | GNPDMBPGHBH:RecRoom.Async.IPromise`1> LIPBAJOAIJC(GNPDMBPGHBH+FFCCJHEPNDK KINAEJLCDEG):GNPDMBPGHBH.txt:9697 | +| `api/playerevents/v2` | Player event DTO/list or mutation success. | GNPDMBPGHBH:RecRoom.Async.IPromise`1 BBPEHLACMLH(System.Int64 HNHLJONGKHB, System.Nullable`1 FODGKNJIGOP, System.Nullable`1 AOMBLGBCENO, System.String MMBOKOLAJFH, System.String LJIGOCDPEJF, System.Collections.Generic.List`1 CAFPJPHILMN, System.String HFLPBHHAFIO, System.DateTime JODPOANPJNK, System.DateTime BCANLCHBKJE, CMCAFKLAHCD PONCIIJOHIE):GNPDMBPGHBH.txt:4067 | + +## players + +| Request literal / fragment | Expected result shape | Evidence | +| --- | --- | --- | +| `/api/playerReputation/v1/{0}` | Player reputation DTO or bulk map/list with moderation/reputation state. | EBAIPNMBKLK:RecRoom.Async.IPromise`1 JNLDMDCGHHJ(System.Int32 GKLPIFBPGOD):EBAIPNMBKLK.txt:1231 | +| `/api/playerReputation/v1/bulk` | Player reputation DTO or bulk map/list with moderation/reputation state. | EBAIPNMBKLK:RecRoom.Async.IPromise`1> KGPDFJMKJKL(System.Collections.Generic.List`1 ILNGMAANNDG):EBAIPNMBKLK.txt:1631 | +| `/api/players/v1/progression/{0}` | Player progression DTO or bulk map/list with XP, level, currencies/progress fields. | ACJLCBNBJDK:RecRoom.Async.IPromise`1 AAGPDEDFCNI(System.Int32 GKLPIFBPGOD):ACJLCBNBJDK.txt:1673 | +| `/api/players/v1/progression/bulk` | Player progression DTO or bulk map/list with XP, level, currencies/progress fields. | ACJLCBNBJDK:RecRoom.Async.IPromise`1> MCFMHADBDBI(System.Collections.Generic.List`1 ILNGMAANNDG):ACJLCBNBJDK.txt:2051 | +| `api/players/v2/objectives` | Objective/progression list for the current player. | ACJLCBNBJDK:System.Void EIFPPIPOIHB(System.Collections.Generic.List`1 DBDFCMJFNEC):ACJLCBNBJDK.txt:816 | + +## playlists + +| Request literal / fragment | Expected result shape | Evidence | +| --- | --- | --- | +| `featuredrooms/current` | Paged list of playlist or mixed room-playlist summaries. | EJDCNGBEICB:RecRoom.Async.IPromise`1 CFKDADKHAGB():EJDCNGBEICB.txt:2687 | +| `playlists/{0}` | Playlist detail DTO or list. | EJDCNGBEICB:RecRoom.Async.IPromise OFACCDEDIHE(System.Int64 DDMPFMPILCE):EJDCNGBEICB.txt:6619
EJDCNGBEICB:RecRoom.Async.IPromise`1 POOBMHAAMLJ(System.Int64 DDMPFMPILCE):EJDCNGBEICB.txt:2192
EJDCNGBEICB:RecRoom.Async.IPromise`1 PDLNHHAPPIP(System.Int64 DDMPFMPILCE):EJDCNGBEICB.txt:1659 | +| `playlists/{0}/accessibility` | Mutation success result with updated playlist detail or primitive success. | EJDCNGBEICB:RecRoom.Async.IPromise`1 KLHJFKPFPIC(System.Int64 DDMPFMPILCE, DPLPMKMFMPB PONCIIJOHIE):EJDCNGBEICB.txt:7323 | +| `playlists/{0}/description` | Mutation success result with updated playlist detail or primitive success. | EJDCNGBEICB:RecRoom.Async.IPromise`1 EPJMNNPLNDM(System.Int64 DDMPFMPILCE, System.String LJIGOCDPEJF):EJDCNGBEICB.txt:6839 | +| `playlists/{0}/image` | Mutation success result with updated playlist detail or primitive success. | EJDCNGBEICB:RecRoom.Async.IPromise`1 CKMILKDKDCN(System.Int64 DDMPFMPILCE, System.String HFLPBHHAFIO):EJDCNGBEICB.txt:6955 | +| `playlists/{0}/interactionby/me` | Playlist detail DTO or list. | EJDCNGBEICB:RecRoom.Async.IPromise`1 AINDJCIMJOB(System.Int64 DDMPFMPILCE):EJDCNGBEICB.txt:8600 | +| `playlists/{0}/interactionby/me/cheer` | Playlist detail DTO or list. | EJDCNGBEICB:RecRoom.Async.IPromise CIPEENNIAFL(System.Int64 DDMPFMPILCE):EJDCNGBEICB.txt:8733
EJDCNGBEICB:RecRoom.Async.IPromise LONLMONNPGL(System.Int64 DDMPFMPILCE):EJDCNGBEICB.txt:8665 | +| `playlists/{0}/interactionby/me/favorite` | Playlist detail DTO or list. | EJDCNGBEICB:RecRoom.Async.IPromise GDELFOHOPGF(System.Int64 DDMPFMPILCE):EJDCNGBEICB.txt:8801
EJDCNGBEICB:RecRoom.Async.IPromise NLNKEGDKFCG(System.Int64 DDMPFMPILCE):EJDCNGBEICB.txt:8869 | +| `playlists/{0}/levelvoting` | Mutation success result with updated playlist detail or primitive success. | EJDCNGBEICB:RecRoom.Async.IPromise`1 PPBLDOPNJKC(System.Int64 DDMPFMPILCE, System.Boolean NNBHCDIKILH):EJDCNGBEICB.txt:7582 | +| `playlists/{0}/name` | Mutation success result with updated playlist detail or primitive success. | EJDCNGBEICB:RecRoom.Async.IPromise`1 NINPMBNNHMG(System.Int64 DDMPFMPILCE, System.String MMBOKOLAJFH):EJDCNGBEICB.txt:6723 | +| `playlists/{0}/restrictions` | Mutation success result with updated playlist detail or primitive success. | EJDCNGBEICB:RecRoom.Async.IPromise`1 LDLJKJLNLEE(System.Int64 DDMPFMPILCE, System.Boolean DDMOEIAHJBK, System.Boolean FOBLJIBLCNI, System.Boolean GELHFEFGJLA, System.Boolean NFIGHDPKLJF):EJDCNGBEICB.txt:7454 | +| `playlists/{0}/rooms/{1}` | Success result for adding/removing/reordering room in playlist. | EJDCNGBEICB:RecRoom.Async.IPromise`1 JBMCPFHGIMD(System.Int64 DDMPFMPILCE, System.Int64 HNHLJONGKHB):EJDCNGBEICB.txt:7765
EJDCNGBEICB:RecRoom.Async.IPromise`1 NLOEBFMNCFM(System.Int64 DDMPFMPILCE, System.Int64 HNHLJONGKHB):EJDCNGBEICB.txt:7679 | +| `playlists/{0}/tags` | Mutation success result with updated playlist detail or primitive success. | EJDCNGBEICB:RecRoom.Async.IPromise`1 ALCFNJJHDMG(System.Int64 DDMPFMPILCE, System.Collections.Generic.IReadOnlyList`1 LBAOCHFCLPO, System.Collections.Generic.IReadOnlyList`1 CAFPJPHILMN):EJDCNGBEICB.txt:7080 | +| `playlists/{0}/warning` | Mutation success result with updated playlist detail or primitive success. | EJDCNGBEICB:RecRoom.Async.IPromise`1 KEADPPJCJGG(System.Int64 DDMPFMPILCE, GPDIAKNEBKH LEFPAAGGFFA, System.String NLEBEKDHJIJ):EJDCNGBEICB.txt:7206 | +| `playlists/bulk` | Array/list of playlist detail DTOs. | EJDCNGBEICB:RecRoom.Async.IPromise`1> KHMJIKNHJHP(System.Collections.Generic.IReadOnlyList`1 ANIKDNFLDIG):EJDCNGBEICB.txt:1896
EJDCNGBEICB:RecRoom.Async.IPromise`1> KHMJIKNHJHP(System.Collections.Generic.IReadOnlyList`1 CJMAAFFAKDO):EJDCNGBEICB.txt:2068 | +| `playlists/cheeredby/me` | Paged list of playlist or mixed room-playlist summaries. | EJDCNGBEICB:RecRoom.Async.IPromise`1> EFFMOEGALDB():EJDCNGBEICB.txt:2400 | +| `playlists/createdby/me` | Paged list of playlist or mixed room-playlist summaries. | EJDCNGBEICB:RecRoom.Async.IPromise`1> PLPMPFDOICH():EJDCNGBEICB.txt:2361 | +| `playlists/favoritedby/me` | Paged list of playlist or mixed room-playlist summaries. | EJDCNGBEICB:RecRoom.Async.IPromise`1> FAMGMKHMCIN():EJDCNGBEICB.txt:2439 | +| `playlists/visitedby/me` | Paged list of playlist or mixed room-playlist summaries. | EJDCNGBEICB:RecRoom.Async.IPromise`1> OMMENACPKGH():EJDCNGBEICB.txt:2478 | +| `roomsandplaylists/hot` | Paged list of playlist or mixed room-playlist summaries. | EJDCNGBEICB:RecRoom.Async.IPromise`1 EBBHGHEGBKG(System.Collections.Generic.IReadOnlyList`1 CAFPJPHILMN):EJDCNGBEICB.txt:2645 | +| `roomsandplaylists/search` | Paged list of playlist or mixed room-playlist summaries. | EJDCNGBEICB:RecRoom.Async.IPromise`1 HFHDFLDCIEM(System.String CNBKKCJAHPP):EJDCNGBEICB.txt:2560 | + +## quickplay + +| Request literal / fragment | Expected result shape | Evidence | +| --- | --- | --- | +| `api/quickPlay/` | Quick-play room/activity selection DTO. | ADOIEPDDEBO:RecRoom.Async.IPromise CPGECIJBBKF():ADOIEPDDEBO.txt:358 | + +## reporting + +| Request literal / fragment | Expected result shape | Evidence | +| --- | --- | --- | +| `{0}/api/userreporting` | Report submission or Unity user-reporting success DTO. | KHGPLGBHIAH:System.Void PPOCPFGCALB(LGFHFDMDJKK GIGKODMKKHJ, System.Action`2 DFPGDEOCNIH, System.Action`2 AFLPGGJMPOE):KHGPLGBHIAH.txt:2838 | +| `api/banappeal/generateCode` | Generated ban appeal code DTO/string. | LCCEEFHOBEN:RecRoom.Async.IPromise`1 LONDNAJPCEE():LCCEEFHOBEN.txt:2149 | +| `api/PlayerReporting/` | Report submission or Unity user-reporting success DTO. | LCCEEFHOBEN:RecRoom.Async.IPromise FCPPPBKJFGK(LCCEEFHOBEN+OAODPJFGPAF GEDCEIDOKJL, System.String NGPMADFHHKP, System.Nullable`1 IMHODAEGGON = null):LCCEEFHOBEN.txt:5148
LCCEEFHOBEN:RecRoom.Async.IPromise IEAOIIFNOGN(System.String PLDIBPNDJIO):LCCEEFHOBEN.txt:4715
LCCEEFHOBEN:RecRoom.Async.IPromise`1 NEFPDKLMDHK():LCCEEFHOBEN.txt:895 | +| `api/PlayerReporting/v1/voteToKickReasons` | List of vote-to-kick/player-report reason DTOs. | LCCEEFHOBEN:RecRoom.Async.IPromise HHBLGNHOGLN():LCCEEFHOBEN.txt:2417 | +| `https://userreporting.cloud.unity3d.com/api/userreporting/projects/{0}/ping` | Report submission or Unity user-reporting success DTO. | UserReportingScript:System.Void Start():UserReportingScript.txt:1174 | + +## room-keys + +| Request literal / fragment | Expected result shape | Evidence | +| --- | --- | --- | +| `api/roomkeys/` | Room key DTO/list or success result for create/update/delete/mine flows. | IOIBJBLIBKM:RecRoom.Async.IPromise`1 ACCJPCMPJMH(System.Int64 BGICHOOBKLD):IOIBJBLIBKM.txt:1299
IOIBJBLIBKM:RecRoom.Async.IPromise`1 HECNNKLKKAG(System.Int64 BGICHOOBKLD):IOIBJBLIBKM.txt:1517
IOIBJBLIBKM:RecRoom.Async.IPromise`1 LAPDFFOJCNP(System.Int32 CJFGEMGOJHB, AMAGKLLBGEC AAHIIDDIBFD):IOIBJBLIBKM.txt:1866 | +| `api/roomkeys/v1/create` | Room key DTO/list or success result for create/update/delete/mine flows. | IOIBJBLIBKM:RecRoom.Async.IPromise`1 BIAKPHAECJC(System.String MMBOKOLAJFH, System.String LJIGOCDPEJF, System.Int32 MACNIENMFHJ):IOIBJBLIBKM.txt:573 | +| `api/roomkeys/v1/mine` | Room key DTO/list or success result for create/update/delete/mine flows. | IOIBJBLIBKM:RecRoom.Async.IPromise`1> IGFLECMACBE():IOIBJBLIBKM.txt:1704 | +| `api/roomkeys/v1/update` | Room key DTO/list or success result for create/update/delete/mine flows. | IOIBJBLIBKM:RecRoom.Async.IPromise`1 MLFKLGGPLNM(System.Int64 BGICHOOBKLD, System.String MMBOKOLAJFH = null, System.String LJIGOCDPEJF = null, System.Nullable`1 MACNIENMFHJ = null):IOIBJBLIBKM.txt:1008 | + +## rooms + +| Request literal / fragment | Expected result shape | Evidence | +| --- | --- | --- | +| `api/rooms/v1/filters` | Report/permission/filter DTO or primitive success/boolean. | OJMCBOKJFOF+<>c:RecRoom.Async.IPromise`1 b__114_0():OJMCBOKJFOF_NestedType___c.txt:1617 | +| `api/rooms/v1/verifyRole` | Report/permission/filter DTO or primitive success/boolean. | OJMCBOKJFOF:RecRoom.Async.IPromise INDDAAGNMHF(System.String LHOMKMINCHH):OJMCBOKJFOF.txt:12963 | +| `api/rooms/v2/report` | Report/permission/filter DTO or primitive success/boolean. | OJMCBOKJFOF:RecRoom.Async.IPromise GECDGBACBFP(System.Int64 HNHLJONGKHB, LCCEEFHOBEN+CJFENPHAAHI MEABFEIBEMP, System.String EFDBFLPKHKA):OJMCBOKJFOF.txt:12627 | +| `hot_rooms/` | Paged array/list of room summaries/details. | OJMCBOKJFOF:RecRoom.Async.IPromise`1> DLPLPKCNLNA(System.Collections.Generic.IReadOnlyList`1 CAFPJPHILMN):OJMCBOKJFOF.txt:3238
OJMCBOKJFOF+PEBCIEAPGON:System.String CMOIGEDECGM(System.Collections.Generic.IEnumerable`1 CAFPJPHILMN):OJMCBOKJFOF_NestedType_PEBCIEAPGON.txt:39 | +| `hot_roomsandplaylists/` | Paged array/list of room summaries/details. | OJMCBOKJFOF:RecRoom.Async.IPromise`1> EBBHGHEGBKG(System.Collections.Generic.IReadOnlyList`1 CAFPJPHILMN):OJMCBOKJFOF.txt:4743
OJMCBOKJFOF+PEBCIEAPGON:System.String MMCLGMMDDJF(System.Collections.Generic.IEnumerable`1 CAFPJPHILMN):OJMCBOKJFOF_NestedType_PEBCIEAPGON.txt:121 | +| `rooms/{0}` | Room detail DTO, room summary list, or mutation success depending on verb. | EJDCNGBEICB:RecRoom.Async.IPromise KHDPHIGPEEH(System.Int64 HNHLJONGKHB):EJDCNGBEICB.txt:2923
EJDCNGBEICB:RecRoom.Async.IPromise`1 NHBPIIGDAJP(System.Int64 HNHLJONGKHB):EJDCNGBEICB.txt:82
EJDCNGBEICB:RecRoom.Async.IPromise`1 CJKHNIIJFIN(System.Int64 HNHLJONGKHB):EJDCNGBEICB.txt:615 | +| `rooms/{0}/accessibility` | Mutation success result, usually with updated room detail or primitive success. | EJDCNGBEICB:RecRoom.Async.IPromise`1 BPDNAMEDJEG(System.Int64 HNHLJONGKHB, DPLPMKMFMPB PONCIIJOHIE):EJDCNGBEICB.txt:3627 | +| `rooms/{0}/automute` | Mutation success result, usually with updated room detail or primitive success. | EJDCNGBEICB:RecRoom.Async.IPromise`1 CPFCPEONBGC(System.Int64 HNHLJONGKHB, System.Boolean AMJMLANOGKE):EJDCNGBEICB.txt:4014 | +| `rooms/{0}/bans` | Room ban list or success result for ban/import/unban mutation. | EJDCNGBEICB:RecRoom.Async.IPromise CCGIGHDHNLM(System.Int64 HNHLJONGKHB, System.Collections.Generic.IReadOnlyList`1 ILNGMAANNDG, KKIHLHLNGCK BFNPCCFPJHP):EJDCNGBEICB.txt:7939
EJDCNGBEICB:RecRoom.Async.IPromise`1> CGIPIOMBAFM(System.Int64 HNHLJONGKHB):EJDCNGBEICB.txt:7832 | +| `rooms/{0}/bans/{1}` | Room ban list or success result for ban/import/unban mutation. | EJDCNGBEICB:RecRoom.Async.IPromise JMCJDGIMKPK(System.Int64 HNHLJONGKHB, System.Int32 GKLPIFBPGOD, KKIHLHLNGCK BFNPCCFPJHP):EJDCNGBEICB.txt:8186 | +| `rooms/{0}/bans/import` | Room ban list or success result for ban/import/unban mutation. | EJDCNGBEICB:RecRoom.Async.IPromise PFEEJDKPPJI(System.Int64 HNHLJONGKHB, System.Int64 MOKIOEJBMFC):EJDCNGBEICB.txt:8054 | +| `rooms/{0}/clone` | New cloned room/subroom detail DTO. | EJDCNGBEICB:RecRoom.Async.IPromise`1 OFFECAMHJGE(System.Int64 HNHLJONGKHB, System.String MMBOKOLAJFH):EJDCNGBEICB.txt:2843 | +| `rooms/{0}/cloning` | Mutation success result, usually with updated room detail or primitive success. | EJDCNGBEICB:RecRoom.Async.IPromise`1 LJMHBJKDJCE(System.Int64 HNHLJONGKHB, System.Boolean GPAGMKFIKNG):EJDCNGBEICB.txt:3886 | +| `rooms/{0}/comments` | Mutation success result, usually with updated room detail or primitive success. | EJDCNGBEICB:RecRoom.Async.IPromise`1 FKADIOPHOGC(System.Int64 HNHLJONGKHB, System.Boolean IMHMMPKLGEP):EJDCNGBEICB.txt:4142 | +| `rooms/{0}/description` | Mutation success result, usually with updated room detail or primitive success. | EJDCNGBEICB:RecRoom.Async.IPromise`1 CFNFNEJGCEF(System.Int64 HNHLJONGKHB, System.String LJIGOCDPEJF):EJDCNGBEICB.txt:3143 | +| `rooms/{0}/image` | Mutation success result, usually with updated room detail or primitive success. | EJDCNGBEICB:RecRoom.Async.IPromise`1 OGFLGMMNMDD(System.Int64 HNHLJONGKHB, System.String HFLPBHHAFIO):EJDCNGBEICB.txt:3259 | +| `rooms/{0}/interactionby/me` | Room detail DTO, room summary list, or mutation success depending on verb. | EJDCNGBEICB:RecRoom.Async.IPromise`1 CKJBGHEIGBI(System.Int64 HNHLJONGKHB):EJDCNGBEICB.txt:8266 | +| `rooms/{0}/interactionby/me/cheer` | Room detail DTO, room summary list, or mutation success depending on verb. | EJDCNGBEICB:RecRoom.Async.IPromise JFBCCIHBKPP(System.Int64 HNHLJONGKHB):EJDCNGBEICB.txt:8399
EJDCNGBEICB:RecRoom.Async.IPromise PEKMAJPMCDE(System.Int64 HNHLJONGKHB):EJDCNGBEICB.txt:8331 | +| `rooms/{0}/interactionby/me/favorite` | Room detail DTO, room summary list, or mutation success depending on verb. | EJDCNGBEICB:RecRoom.Async.IPromise DIFNOPBMBFO(System.Int64 HNHLJONGKHB):EJDCNGBEICB.txt:8467
EJDCNGBEICB:RecRoom.Async.IPromise OGBLBGALMDC(System.Int64 HNHLJONGKHB):EJDCNGBEICB.txt:8535 | +| `rooms/{0}/modify` | Mutation success result, usually with updated room detail or primitive success. | EJDCNGBEICB:RecRoom.Async.IPromise`1 ONBIHBFOJKD(System.Int64 HNHLJONGKHB, System.String MMBOKOLAJFH, System.String LJIGOCDPEJF, DPLPMKMFMPB PONCIIJOHIE, System.Boolean DDMOEIAHJBK, System.Boolean FOBLJIBLCNI, System.Boolean GELHFEFGJLA, System.Boolean NFIGHDPKLJF, System.Boolean GPAGMKFIKNG, System.Boolean AMJMLANOGKE, System.Boolean IMHMMPKLGEP, System.Boolean NFJINCPDPDG):EJDCNGBEICB.txt:6298 | +| `rooms/{0}/name` | Mutation success result, usually with updated room detail or primitive success. | EJDCNGBEICB:RecRoom.Async.IPromise`1 DNOGGKKHMHI(System.Int64 HNHLJONGKHB, System.String MMBOKOLAJFH):EJDCNGBEICB.txt:3027 | +| `rooms/{0}/promo_external` | Promo image/external-link list or mutation success. | EJDCNGBEICB:RecRoom.Async.IPromise`1 FBNHEKCLMCJ(System.Int64 HNHLJONGKHB, NCELIDGFOEM GEDCEIDOKJL, System.String EKGHGPLFMPJ):EJDCNGBEICB.txt:4862 | +| `rooms/{0}/promo_external/{1}/{2}` | Promo image/external-link list or mutation success. | EJDCNGBEICB:RecRoom.Async.IPromise`1 ACGJJCMFOCH(System.Int64 HNHLJONGKHB, NCELIDGFOEM GEDCEIDOKJL, System.String EKGHGPLFMPJ):EJDCNGBEICB.txt:4970 | +| `rooms/{0}/promo_images` | Promo image/external-link list or mutation success. | EJDCNGBEICB:RecRoom.Async.IPromise`1 CFIKOAAOHFH(System.Int64 HNHLJONGKHB, System.String HFLPBHHAFIO):EJDCNGBEICB.txt:4663 | +| `rooms/{0}/promo_images/{1}` | Promo image/external-link list or mutation success. | EJDCNGBEICB:RecRoom.Async.IPromise`1 OCCGHPAHGKC(System.Int64 HNHLJONGKHB, System.String HFLPBHHAFIO):EJDCNGBEICB.txt:4749 | +| `rooms/{0}/restrictions` | Mutation success result, usually with updated room detail or primitive success. | EJDCNGBEICB:RecRoom.Async.IPromise`1 ONEJODPFOMP(System.Int64 HNHLJONGKHB, System.Boolean DDMOEIAHJBK, System.Boolean FOBLJIBLCNI, System.Boolean GELHFEFGJLA, System.Boolean NFIGHDPKLJF):EJDCNGBEICB.txt:3758 | +| `rooms/{0}/roles` | Room role list/detail or success result for role mutation/invite. | EJDCNGBEICB:RecRoom.Async.IPromise`1> MIGOLOHAIAL(System.Int64 HNHLJONGKHB):EJDCNGBEICB.txt:1254 | +| `rooms/{0}/roles/{1}` | Room role list/detail or success result for role mutation/invite. | EJDCNGBEICB:RecRoom.Async.IPromise`1 EKOKFNHHMPI(System.Int64 HNHLJONGKHB, System.Int32 GKLPIFBPGOD):EJDCNGBEICB.txt:1332
EJDCNGBEICB:RecRoom.Async.IPromise`1 GJMGBBCIHGH(System.Int64 HNHLJONGKHB, System.Int32 GKLPIFBPGOD, LMLJHMJEIGM IENKDAKBEDP):EJDCNGBEICB.txt:4405 | +| `rooms/{0}/roles/{1}/invite` | Room role list/detail or success result for role mutation/invite. | EJDCNGBEICB:RecRoom.Async.IPromise`1 MJOHJLHDMHB(System.Int64 HNHLJONGKHB, System.Int32 GKLPIFBPGOD, LMLJHMJEIGM IENKDAKBEDP):EJDCNGBEICB.txt:4543 | +| `rooms/{0}/subrooms` | Room detail DTO, room summary list, or mutation success depending on verb. | EJDCNGBEICB:RecRoom.Async.IPromise`1 OGNIELCNHIM(System.Int64 HNHLJONGKHB, System.String MMBOKOLAJFH):EJDCNGBEICB.txt:5487 | +| `rooms/{0}/subrooms/{1}` | Room detail DTO, room summary list, or mutation success depending on verb. | EJDCNGBEICB:RecRoom.Async.IPromise`1 GGCOGNAGPBP(System.Int64 HNHLJONGKHB, System.Int64 FODGKNJIGOP):EJDCNGBEICB.txt:5809 | +| `rooms/{0}/subrooms/{1}/accessibility` | Mutation success result, usually with updated room detail or primitive success. | EJDCNGBEICB:RecRoom.Async.IPromise`1 POGEHAMJBPE(System.Int64 HNHLJONGKHB, System.Int64 FODGKNJIGOP, DPLPMKMFMPB PONCIIJOHIE):EJDCNGBEICB.txt:5369 | +| `rooms/{0}/subrooms/{1}/clone` | New cloned room/subroom detail DTO. | EJDCNGBEICB:RecRoom.Async.IPromise`1 OGEKCICPACB(System.Int64 HNHLJONGKHB, System.Int64 FODGKNJIGOP):EJDCNGBEICB.txt:5583 | +| `rooms/{0}/subrooms/{1}/data` | Room/subroom data descriptor pointing to binary room blob; blob itself must be bytes from CDN/storage. | EJDCNGBEICB:RecRoom.Async.IPromise`1 HOPMJFDKPDL(System.Int64 HNHLJONGKHB, System.Int64 FODGKNJIGOP, System.String MEIIMAIGBJD, System.Collections.Generic.Dictionary`2 PNOBBNPJDLI, System.Int32 OMCLBIFCHLF):EJDCNGBEICB.txt:5995 | +| `rooms/{0}/subrooms/{1}/datahistory` | List/page of saved room data blob history entries. | EJDCNGBEICB:RecRoom.Async.IPromise`1> HAMBEABEIOC(System.Int64 HNHLJONGKHB, System.Int64 FODGKNJIGOP):EJDCNGBEICB.txt:1190 | +| `rooms/{0}/subrooms/{1}/maxplayers` | Room detail DTO, room summary list, or mutation success depending on verb. | EJDCNGBEICB:RecRoom.Async.IPromise`1 AEBGMLMMKOJ(System.Int64 HNHLJONGKHB, System.Int64 FODGKNJIGOP, System.Int32 FFAGIDMFPIF):EJDCNGBEICB.txt:5235 | +| `rooms/{0}/subrooms/{1}/modify` | Mutation success result, usually with updated room detail or primitive success. | EJDCNGBEICB:RecRoom.Async.IPromise`1 JGMMJMHLHBO(System.Int64 HNHLJONGKHB, System.Int64 FODGKNJIGOP, System.String MMBOKOLAJFH, DPLPMKMFMPB PONCIIJOHIE, System.Int32 FFAGIDMFPIF):EJDCNGBEICB.txt:6443 | +| `rooms/{0}/subrooms/{1}/move` | Room detail DTO, room summary list, or mutation success depending on verb. | EJDCNGBEICB:RecRoom.Async.IPromise`1 OHHAFAIEFHP(System.Int64 HNHLJONGKHB, System.Int64 FODGKNJIGOP, System.Nullable`1 PAJMLPFMBEJ):EJDCNGBEICB.txt:5709 | +| `rooms/{0}/subrooms/{1}/name` | Mutation success result, usually with updated room detail or primitive success. | EJDCNGBEICB:RecRoom.Async.IPromise`1 PAENPJCDLNJ(System.Int64 HNHLJONGKHB, System.Int64 FODGKNJIGOP, System.String MMBOKOLAJFH):EJDCNGBEICB.txt:5099 | +| `rooms/{0}/subrooms/{1}/restoredata` | Success result and refreshed room data descriptor/current blob pointer. | EJDCNGBEICB:RecRoom.Async.IPromise`1 BBGBOCGJFOP(System.Int64 HNHLJONGKHB, System.Int64 FODGKNJIGOP, System.String MEIIMAIGBJD):EJDCNGBEICB.txt:6133 | +| `rooms/{0}/tags` | Mutation success result, usually with updated room detail or primitive success. | EJDCNGBEICB:RecRoom.Async.IPromise`1 ENGFFJGIEGH(System.Int64 HNHLJONGKHB, System.Collections.Generic.IReadOnlyList`1 LBAOCHFCLPO, System.Collections.Generic.IReadOnlyList`1 CAFPJPHILMN):EJDCNGBEICB.txt:3384 | +| `rooms/{0}/voice_chat_encryption` | Mutation success result, usually with updated room detail or primitive success. | EJDCNGBEICB:RecRoom.Async.IPromise`1 PKDCMMFILPB(System.Int64 HNHLJONGKHB, System.Boolean NFJINCPDPDG):EJDCNGBEICB.txt:4270 | +| `rooms/{0}/warning` | Mutation success result, usually with updated room detail or primitive success. | EJDCNGBEICB:RecRoom.Async.IPromise`1 NPACFDBCHDP(System.Int64 HNHLJONGKHB, GPDIAKNEBKH LEFPAAGGFFA, System.String NLEBEKDHJIJ):EJDCNGBEICB.txt:3510 | +| `rooms/base` | Room detail DTO, room summary list, or mutation success depending on verb. | EJDCNGBEICB:RecRoom.Async.IPromise`1> LMEPPFGPHLA():EJDCNGBEICB.txt:784 | +| `rooms/bulk` | Array/list of room detail DTOs for requested room ids. | EJDCNGBEICB:RecRoom.Async.IPromise`1> JPNBCKICJIH(System.Collections.Generic.IReadOnlyList`1 HJLOFEGEMHE):EJDCNGBEICB.txt:319
EJDCNGBEICB:RecRoom.Async.IPromise`1> JPNBCKICJIH(System.Collections.Generic.IReadOnlyList`1 GDDBDIJIBCO):EJDCNGBEICB.txt:491 | +| `rooms/cheeredby/me` | Paged array/list of room summaries/details. | EJDCNGBEICB:RecRoom.Async.IPromise`1> GBMMJLHIDOK():EJDCNGBEICB.txt:940 | +| `rooms/createdby/{0}` | Paged array/list of room summaries/details. | EJDCNGBEICB:RecRoom.Async.IPromise`1> OGIHJJDNPKJ(System.Int32 GKLPIFBPGOD):EJDCNGBEICB.txt:1073 | +| `rooms/createdby/me` | Paged array/list of room summaries/details. | EJDCNGBEICB:RecRoom.Async.IPromise`1> MIMNJGKEGMF():EJDCNGBEICB.txt:823 | +| `rooms/favoritedby/me` | Paged array/list of room summaries/details. | EJDCNGBEICB:RecRoom.Async.IPromise`1> AFOLHKGPMOC():EJDCNGBEICB.txt:979 | +| `rooms/hot` | Paged array/list of room summaries/details. | EJDCNGBEICB:RecRoom.Async.IPromise`1 DLPLPKCNLNA(System.Collections.Generic.IReadOnlyList`1 CAFPJPHILMN):EJDCNGBEICB.txt:1601 | +| `rooms/moderatedby/me` | Paged array/list of room summaries/details. | EJDCNGBEICB:RecRoom.Async.IPromise`1> IMKAAIEFGIG():EJDCNGBEICB.txt:901 | +| `rooms/ownedby/me` | Paged array/list of room summaries/details. | EJDCNGBEICB:RecRoom.Async.IPromise`1> FNMNFLFIBJG():EJDCNGBEICB.txt:862 | +| `rooms/recommendations` | Paged array/list of room summaries/details. | EJDCNGBEICB:RecRoom.Async.IPromise`1> JDKLBNBIEAH(AMBEPDPIBPA NLNJMJGOKFI, System.Int16 NJJLKKLALND):EJDCNGBEICB.txt:1430 | +| `rooms/rro_ids` | Room detail DTO, room summary list, or mutation success depending on verb. | EJDCNGBEICB:RecRoom.Async.IPromise`1> JDMBPCJDEMF():EJDCNGBEICB.txt:1119 | +| `rooms/search` | Paged array/list of room summaries/details. | EJDCNGBEICB:RecRoom.Async.IPromise`1 IAAKGMOGLFN(System.String CNBKKCJAHPP):EJDCNGBEICB.txt:1516 | +| `rooms/visitedby/me` | Paged array/list of room summaries/details. | EJDCNGBEICB:RecRoom.Async.IPromise`1> INKFBOEEKDA():EJDCNGBEICB.txt:1018 | +| `search_rooms/` | Paged array/list of room summaries/details. | OJMCBOKJFOF:RecRoom.Async.IPromise`1> IAAKGMOGLFN(System.String CNBKKCJAHPP):OJMCBOKJFOF.txt:3064
OJMCBOKJFOF+PEBCIEAPGON:System.String IAAKGMOGLFN(System.String CNBKKCJAHPP):OJMCBOKJFOF_NestedType_PEBCIEAPGON.txt:76 | +| `search_roomsandplaylists/` | Paged array/list of room summaries/details. | OJMCBOKJFOF:RecRoom.Async.IPromise`1> HFHDFLDCIEM(System.String CNBKKCJAHPP):OJMCBOKJFOF.txt:4567
OJMCBOKJFOF+PEBCIEAPGON:System.String HFHDFLDCIEM(System.String CNBKKCJAHPP):OJMCBOKJFOF_NestedType_PEBCIEAPGON.txt:158 | + +## March 2020 Literal Appendix + +The March archive exposes request evidence mostly through `Il2CppDump/stringliteral.json`, so this appendix lists request-like literals without method-body context. Expected shapes follow the same rules as the December catalog. + +CSV: `docs/recroom-2020-03-request-literals.csv` + +| Family | Literal | Expected result shape | +| --- | --- | --- | +| bootstrap-account | `.rec.net` | Name-server/bootstrap URL, RecNet host suffix, or account recovery page; name-server returns service base URL object. | +| bootstrap-account | `https://ns.rec.net/?v=2` | Name-server/bootstrap URL, RecNet host suffix, or account recovery page; name-server returns service base URL object. | +| bootstrap-account | `https://rec.net` | Name-server/bootstrap URL, RecNet host suffix, or account recovery page; name-server returns service base URL object. | +| bootstrap-account | `https://rec.net/password/recover` | Name-server/bootstrap URL, RecNet host suffix, or account recovery page; name-server returns service base URL object. | +| config | `/config/{0}` | Config object for requested key. | +| config | `/configuration/system.runtime.remoting` | Config object for requested key. | +| config | `api/config/` | Config object for requested key. | +| inventions | `api/inventions/` | Invention detail/version/save/publish/download DTO or mutation success. | +| inventions | `api/inventions/v3/addversion` | Invention detail/version/save/publish/download DTO or mutation success. | +| inventions | `api/inventions/v3/save` | Invention detail/version/save/publish/download DTO or mutation success. | +| misc | `{0}v1/bulkignoreplatformusers` | Route literal needing call-site confirmation. | +| misc | `{0}v1/bulkInvite` | Route literal needing call-site confirmation. | +| misc | `{0}v1/consume` | Route literal needing call-site confirmation. | +| misc | `{0}v2/gifts/consume/` | Route literal needing call-site confirmation. | +| misc | `/room/` | Route literal needing call-site confirmation. | +| misc | `/room/{0}` | Route literal needing call-site confirmation. | +| misc | `account/{0}` | Route literal needing call-site confirmation. | +| misc | `account/{0}/bio` | Route literal needing call-site confirmation. | +| misc | `account/bulk` | Route literal needing call-site confirmation. | +| misc | `account/bulk?` | Route literal needing call-site confirmation. | +| misc | `account/bulk/` | Route literal needing call-site confirmation. | +| misc | `account/create` | Route literal needing call-site confirmation. | +| misc | `account/me` | Route literal needing call-site confirmation. | +| misc | `account/me/` | Route literal needing call-site confirmation. | +| misc | `account/me/changepassword` | Route literal needing call-site confirmation. | +| misc | `account/me/haspassword` | Route literal needing call-site confirmation. | +| misc | `account/recoverpassword` | Route literal needing call-site confirmation. | +| misc | `account/search?name=` | Route literal needing call-site confirmation. | +| misc | `Activities/Dormroom/Scenes/holoHelperRecordingStudio` | Route literal needing call-site confirmation. | +| misc | `api/activities/charades/v1/words` | Route literal needing call-site confirmation. | +| misc | `api/announcement/v1/get` | Route literal needing call-site confirmation. | +| misc | `api/avatar/` | Route literal needing call-site confirmation. | +| misc | `api/bugreporting/` | Route literal needing call-site confirmation. | +| misc | `api/catalog/v1/all?onlyAvailableSkus=true` | Route literal needing call-site confirmation. | +| misc | `api/challenge/` | Route literal needing call-site confirmation. | +| misc | `api/checklist/` | Route literal needing call-site confirmation. | +| misc | `api/communityboard/` | Route literal needing call-site confirmation. | +| misc | `api/consumables/` | Route literal needing call-site confirmation. | +| misc | `api/equipment/` | Route literal needing call-site confirmation. | +| misc | `api/gameconfigs/` | Route literal needing call-site confirmation. | +| misc | `api/groups/` | Route literal needing call-site confirmation. | +| misc | `api/images/` | Route literal needing call-site confirmation. | +| misc | `api/messages/` | Route literal needing call-site confirmation. | +| misc | `api/messages/v1/IOSClearDeviceToken` | Route literal needing call-site confirmation. | +| misc | `api/messages/v1/IOSModifyNotificationPreferences` | Route literal needing call-site confirmation. | +| misc | `api/messages/v1/IOSResetNotificationPreferencesBadgeCount` | Route literal needing call-site confirmation. | +| misc | `api/messages/v1/IOSSaveDeviceToken` | Route literal needing call-site confirmation. | +| misc | `api/messages/v3/delete` | Route literal needing call-site confirmation. | +| misc | `api/objectives/` | Route literal needing call-site confirmation. | +| misc | `api/PlayerCheer/` | Route literal needing call-site confirmation. | +| misc | `api/PlayerElo/` | Route literal needing call-site confirmation. | +| misc | `api/playerevents/` | Route literal needing call-site confirmation. | +| misc | `api/PlayerReporting/` | Route literal needing call-site confirmation. | +| misc | `api/PlayersBanned/` | Route literal needing call-site confirmation. | +| misc | `api/purchase/v1/cancelpurchase` | Route literal needing call-site confirmation. | +| misc | `api/purchase/v1/cleanuppending` | Route literal needing call-site confirmation. | +| misc | `api/purchase/v1/completepurchase` | Route literal needing call-site confirmation. | +| misc | `api/purchase/v1/initiatepurchase` | Route literal needing call-site confirmation. | +| misc | `api/purchase/v1/processpurchase` | Route literal needing call-site confirmation. | +| misc | `api/quickPlay/` | Route literal needing call-site confirmation. | +| misc | `api/relationships/` | Route literal needing call-site confirmation. | +| misc | `api/royale/` | Route literal needing call-site confirmation. | +| misc | `api/sanitize/` | Route literal needing call-site confirmation. | +| misc | `api/sanitize/v1` | Route literal needing call-site confirmation. | +| misc | `api/settings/` | Route literal needing call-site confirmation. | +| misc | `api/storefronts/` | Route literal needing call-site confirmation. | +| misc | `api/testcasemanagement/` | Route literal needing call-site confirmation. | +| misc | `api/versioncheck/v4?v={0}&p={1}` | Route literal needing call-site confirmation. | +| misc | `goto/event/{0}` | Route literal needing call-site confirmation. | +| misc | `goto/instance/{0}` | Route literal needing call-site confirmation. | +| misc | `goto/invite/{0}` | Route literal needing call-site confirmation. | +| misc | `goto/player/{0}` | Route literal needing call-site confirmation. | +| misc | `goto/room/` | Route literal needing call-site confirmation. | +| misc | `https://www.instagram.com/recroom/` | Route literal needing call-site confirmation. | +| misc | `room/{0}/instances` | Route literal needing call-site confirmation. | +| players | `/api/playerReputation/v1/{0}` | Player progression/reputation DTO or bulk list/map. | +| players | `/api/playerReputation/v1/bulk` | Player progression/reputation DTO or bulk list/map. | +| players | `/api/players/v1/progression/{0}` | Player progression/reputation DTO or bulk list/map. | +| players | `/api/players/v1/progression/bulk` | Player progression/reputation DTO or bulk list/map. | +| players | `api/players/v2/objectives` | Player progression/reputation DTO or bulk list/map. | +| players | `api/playersubscriptions/` | Player progression/reputation DTO or bulk list/map. | +| rooms | `api/rooms/` | Room detail/list/save-data response or room data descriptor. | +| rooms | `api/rooms/v1/roomRolePermissions` | Room detail/list/save-data response or room data descriptor. | +| rooms | `api/rooms/v4/saveData` | Room detail/list/save-data response or room data descriptor. | +| storage-cdn | `{0}v1/datahistory/{1}` | Storage/CDN data descriptor or binary-addressing fragment. | +| storage-cdn | `{0}v1/datahistory/restore` | Storage/CDN data descriptor or binary-addressing fragment. | +| storage-cdn | `/data/{0}` | Storage/CDN data descriptor or binary-addressing fragment. | diff --git a/docs/recroom-2020-client-response-contracts.json b/docs/recroom-2020-client-response-contracts.json new file mode 100644 index 0000000..50418f1 --- /dev/null +++ b/docs/recroom-2020-client-response-contracts.json @@ -0,0 +1,60579 @@ +{ + "source": "C:/Users/you/Documents/Recnet/docs/recroom-2020-client-request-catalog.csv", + "routes": [ + { + "family": "account", + "route": "account/{0}", + "evidence": [ + { + "type": "PEGGCEDHBOF", + "method": "RecRoom.Async.IPromise`1 NFKMGNHDCMN(System.Int32 GKLPIFBPGOD)", + "file": "PEGGCEDHBOF.txt", + "line": 3934, + "contract": { + "kind": "object", + "raw": "CCEOLAOLEKJ", + "rootType": "CCEOLAOLEKJ", + "typeDescription": { + "name": "CCEOLAOLEKJ", + "found": true, + "file": "CCEOLAOLEKJ.cs", + "declaration": "public class CCEOLAOLEKJ : IFAIJAGLDFK", + "kind": "class", + "baseType": "IFAIJAGLDFK", + "properties": [ + { + "name": "ACBFDMLHFPB", + "type": "DateTime", + "accessibility": "public", + "kind": "property" + }, + { + "name": "AEMHDANDFOP", + "type": "ObscuredBool", + "accessibility": "public", + "kind": "property" + }, + { + "name": "BIOGKFGIMDG", + "type": "ObscuredString", + "accessibility": "public", + "kind": "property" + }, + { + "name": "CFJLJOKIJCN", + "type": "ObscuredString", + "accessibility": "public", + "kind": "property" + }, + { + "name": "GAINIOENNCG", + "type": "ObscuredInt", + "accessibility": "public", + "kind": "property" + }, + { + "name": "IHADBCJNDIP", + "type": "ObscuredString", + "accessibility": "public", + "kind": "property" + }, + { + "name": "ILOADNHKMHK", + "type": "ENNEGEELGMC", + "accessibility": "public", + "kind": "property" + }, + { + "name": "JIIMAIDFJPN", + "type": "ObscuredBool", + "accessibility": "public", + "kind": "property" + }, + { + "name": "OOJFBECGEAD", + "type": "ObscuredString", + "accessibility": "public", + "kind": "property" + } + ], + "fields": [], + "enumValues": [], + "wireKeys": [ + "accountId", + "profileImage", + "isJunior", + "platforms", + "username", + "displayName", + "createdAt" + ], + "isilFile": "CCEOLAOLEKJ.txt", + "isilType": "CCEOLAOLEKJ" + } + } + } + ], + "contracts": [ + { + "kind": "object", + "raw": "CCEOLAOLEKJ", + "rootType": "CCEOLAOLEKJ", + "typeDescription": { + "name": "CCEOLAOLEKJ", + "found": true, + "file": "CCEOLAOLEKJ.cs", + "declaration": "public class CCEOLAOLEKJ : IFAIJAGLDFK", + "kind": "class", + "baseType": "IFAIJAGLDFK", + "properties": [ + { + "name": "ACBFDMLHFPB", + "type": "DateTime", + "accessibility": "public", + "kind": "property" + }, + { + "name": "AEMHDANDFOP", + "type": "ObscuredBool", + "accessibility": "public", + "kind": "property" + }, + { + "name": "BIOGKFGIMDG", + "type": "ObscuredString", + "accessibility": "public", + "kind": "property" + }, + { + "name": "CFJLJOKIJCN", + "type": "ObscuredString", + "accessibility": "public", + "kind": "property" + }, + { + "name": "GAINIOENNCG", + "type": "ObscuredInt", + "accessibility": "public", + "kind": "property" + }, + { + "name": "IHADBCJNDIP", + "type": "ObscuredString", + "accessibility": "public", + "kind": "property" + }, + { + "name": "ILOADNHKMHK", + "type": "ENNEGEELGMC", + "accessibility": "public", + "kind": "property" + }, + { + "name": "JIIMAIDFJPN", + "type": "ObscuredBool", + "accessibility": "public", + "kind": "property" + }, + { + "name": "OOJFBECGEAD", + "type": "ObscuredString", + "accessibility": "public", + "kind": "property" + } + ], + "fields": [], + "enumValues": [], + "wireKeys": [ + "accountId", + "profileImage", + "isJunior", + "platforms", + "username", + "displayName", + "createdAt" + ], + "isilFile": "CCEOLAOLEKJ.txt", + "isilType": "CCEOLAOLEKJ" + } + } + ] + }, + { + "family": "account", + "route": "account/{0}/bio", + "evidence": [ + { + "type": "PEGGCEDHBOF", + "method": "RecRoom.Async.IPromise`1 GBLAOEFPDAH(System.Int32 GKLPIFBPGOD)", + "file": "PEGGCEDHBOF.txt", + "line": 7866, + "contract": { + "kind": "object", + "raw": "FKNGFLFDIIB", + "rootType": "FKNGFLFDIIB", + "typeDescription": { + "name": "FKNGFLFDIIB", + "found": true, + "file": "FKNGFLFDIIB.cs", + "declaration": "public class FKNGFLFDIIB : IFAIJAGLDFK", + "kind": "class", + "baseType": "IFAIJAGLDFK", + "properties": [ + { + "name": "AHOEKLABOBN", + "type": "string", + "accessibility": "public", + "kind": "property" + }, + { + "name": "GAINIOENNCG", + "type": "int", + "accessibility": "public", + "kind": "property" + } + ], + "fields": [], + "enumValues": [], + "wireKeys": [ + "accountId", + "bio" + ], + "isilFile": "FKNGFLFDIIB.txt", + "isilType": "FKNGFLFDIIB" + } + } + }, + { + "type": "PEGGCEDHBOF+<>c", + "method": "System.Void b__67_0()", + "file": "PEGGCEDHBOF_NestedType___c.txt", + "line": 587, + "contract": { + "kind": "unknown", + "raw": "", + "rootType": "unknown" + } + } + ], + "contracts": [ + { + "kind": "object", + "raw": "FKNGFLFDIIB", + "rootType": "FKNGFLFDIIB", + "typeDescription": { + "name": "FKNGFLFDIIB", + "found": true, + "file": "FKNGFLFDIIB.cs", + "declaration": "public class FKNGFLFDIIB : IFAIJAGLDFK", + "kind": "class", + "baseType": "IFAIJAGLDFK", + "properties": [ + { + "name": "AHOEKLABOBN", + "type": "string", + "accessibility": "public", + "kind": "property" + }, + { + "name": "GAINIOENNCG", + "type": "int", + "accessibility": "public", + "kind": "property" + } + ], + "fields": [], + "enumValues": [], + "wireKeys": [ + "accountId", + "bio" + ], + "isilFile": "FKNGFLFDIIB.txt", + "isilType": "FKNGFLFDIIB" + } + }, + { + "kind": "unknown", + "raw": "", + "rootType": "unknown", + "typeDescription": { + "name": "unknown", + "found": false, + "kind": "object", + "wireKeys": [] + } + } + ] + }, + { + "family": "account", + "route": "account/{0}/clubs", + "evidence": [ + { + "type": "JDJGIBLMFKK", + "method": "RecRoom.Async.IPromise`1> KGGMKBADLDM(System.Int32 GKLPIFBPGOD)", + "file": "JDJGIBLMFKK.txt", + "line": 9580, + "contract": { + "kind": "array", + "raw": "System.Collections.Generic.List`1", + "rootType": "PLILLKHMNDA", + "item": { + "kind": "object", + "raw": "PLILLKHMNDA", + "rootType": "PLILLKHMNDA" + }, + "typeDescription": { + "name": "PLILLKHMNDA", + "found": true, + "file": "PLILLKHMNDA.cs", + "declaration": "public class PLILLKHMNDA : IFAIJAGLDFK, IEquatable", + "kind": "class", + "baseType": "IFAIJAGLDFK", + "properties": [ + { + "name": "BADIGBCKECA", + "type": "int", + "accessibility": "public", + "kind": "property" + }, + { + "name": "CCGOEDABKNN", + "type": "long", + "accessibility": "public", + "kind": "property" + }, + { + "name": "CDINMMPNAID", + "type": "JCMLDDKFKEO", + "accessibility": "public", + "kind": "property" + }, + { + "name": "CDNFGMHLDMJ", + "type": "bool", + "accessibility": "public", + "kind": "property" + }, + { + "name": "EEAOJCGAOCN", + "type": "int", + "accessibility": "public", + "kind": "property" + }, + { + "name": "EHOLKJPEGFF", + "type": "string", + "accessibility": "public", + "kind": "property" + }, + { + "name": "EPGALOMHHMI", + "type": "JCDEFCJLCHN", + "accessibility": "public", + "kind": "property" + }, + { + "name": "FIKEBGGCDFN", + "type": "string", + "accessibility": "public", + "kind": "property" + }, + { + "name": "HHCGNCLFKDM", + "type": "Nullable", + "accessibility": "public", + "kind": "property" + }, + { + "name": "IPKLLFAJJPJ", + "type": "bool", + "accessibility": "public", + "kind": "property" + }, + { + "name": "KODBEJPEFOJ", + "type": "string", + "accessibility": "public", + "kind": "property" + }, + { + "name": "LNGPBGCIAPP", + "type": "string", + "accessibility": "public", + "kind": "property" + }, + { + "name": "MNEEEGHOGAB", + "type": "DIGMAIMMHAP", + "accessibility": "public", + "kind": "property" + }, + { + "name": "PHBCFAJILGD", + "type": "PJCALHOPMKJ", + "accessibility": "public", + "kind": "property" + } + ], + "fields": [], + "enumValues": [], + "wireKeys": [ + "ClubId", + "Name", + "Description", + "MainImageName", + "State", + "CreatorAccountId", + "Category", + "Visibility", + "Joinability", + "AllowJuniors", + "MemberCount", + "IsRRO", + "ClubType" + ], + "isilFile": "PLILLKHMNDA.txt", + "isilType": "PLILLKHMNDA" + } + } + } + ], + "contracts": [ + { + "kind": "array", + "raw": "System.Collections.Generic.List`1", + "rootType": "PLILLKHMNDA", + "item": { + "kind": "object", + "raw": "PLILLKHMNDA", + "rootType": "PLILLKHMNDA" + }, + "typeDescription": { + "name": "PLILLKHMNDA", + "found": true, + "file": "PLILLKHMNDA.cs", + "declaration": "public class PLILLKHMNDA : IFAIJAGLDFK, IEquatable", + "kind": "class", + "baseType": "IFAIJAGLDFK", + "properties": [ + { + "name": "BADIGBCKECA", + "type": "int", + "accessibility": "public", + "kind": "property" + }, + { + "name": "CCGOEDABKNN", + "type": "long", + "accessibility": "public", + "kind": "property" + }, + { + "name": "CDINMMPNAID", + "type": "JCMLDDKFKEO", + "accessibility": "public", + "kind": "property" + }, + { + "name": "CDNFGMHLDMJ", + "type": "bool", + "accessibility": "public", + "kind": "property" + }, + { + "name": "EEAOJCGAOCN", + "type": "int", + "accessibility": "public", + "kind": "property" + }, + { + "name": "EHOLKJPEGFF", + "type": "string", + "accessibility": "public", + "kind": "property" + }, + { + "name": "EPGALOMHHMI", + "type": "JCDEFCJLCHN", + "accessibility": "public", + "kind": "property" + }, + { + "name": "FIKEBGGCDFN", + "type": "string", + "accessibility": "public", + "kind": "property" + }, + { + "name": "HHCGNCLFKDM", + "type": "Nullable", + "accessibility": "public", + "kind": "property" + }, + { + "name": "IPKLLFAJJPJ", + "type": "bool", + "accessibility": "public", + "kind": "property" + }, + { + "name": "KODBEJPEFOJ", + "type": "string", + "accessibility": "public", + "kind": "property" + }, + { + "name": "LNGPBGCIAPP", + "type": "string", + "accessibility": "public", + "kind": "property" + }, + { + "name": "MNEEEGHOGAB", + "type": "DIGMAIMMHAP", + "accessibility": "public", + "kind": "property" + }, + { + "name": "PHBCFAJILGD", + "type": "PJCALHOPMKJ", + "accessibility": "public", + "kind": "property" + } + ], + "fields": [], + "enumValues": [], + "wireKeys": [ + "ClubId", + "Name", + "Description", + "MainImageName", + "State", + "CreatorAccountId", + "Category", + "Visibility", + "Joinability", + "AllowJuniors", + "MemberCount", + "IsRRO", + "ClubType" + ], + "isilFile": "PLILLKHMNDA.txt", + "isilType": "PLILLKHMNDA" + } + } + ] + }, + { + "family": "account", + "route": "account/bulk", + "evidence": [ + { + "type": "PEGGCEDHBOF", + "method": "RecRoom.Async.IPromise`1> NHCHADDMPFC(System.Collections.Generic.List`1 ILNGMAANNDG)", + "file": "PEGGCEDHBOF.txt", + "line": 4917, + "contract": { + "kind": "array", + "raw": "System.Collections.Generic.List`1", + "rootType": "CCEOLAOLEKJ", + "item": { + "kind": "object", + "raw": "CCEOLAOLEKJ", + "rootType": "CCEOLAOLEKJ" + }, + "typeDescription": { + "name": "CCEOLAOLEKJ", + "found": true, + "file": "CCEOLAOLEKJ.cs", + "declaration": "public class CCEOLAOLEKJ : IFAIJAGLDFK", + "kind": "class", + "baseType": "IFAIJAGLDFK", + "properties": [ + { + "name": "ACBFDMLHFPB", + "type": "DateTime", + "accessibility": "public", + "kind": "property" + }, + { + "name": "AEMHDANDFOP", + "type": "ObscuredBool", + "accessibility": "public", + "kind": "property" + }, + { + "name": "BIOGKFGIMDG", + "type": "ObscuredString", + "accessibility": "public", + "kind": "property" + }, + { + "name": "CFJLJOKIJCN", + "type": "ObscuredString", + "accessibility": "public", + "kind": "property" + }, + { + "name": "GAINIOENNCG", + "type": "ObscuredInt", + "accessibility": "public", + "kind": "property" + }, + { + "name": "IHADBCJNDIP", + "type": "ObscuredString", + "accessibility": "public", + "kind": "property" + }, + { + "name": "ILOADNHKMHK", + "type": "ENNEGEELGMC", + "accessibility": "public", + "kind": "property" + }, + { + "name": "JIIMAIDFJPN", + "type": "ObscuredBool", + "accessibility": "public", + "kind": "property" + }, + { + "name": "OOJFBECGEAD", + "type": "ObscuredString", + "accessibility": "public", + "kind": "property" + } + ], + "fields": [], + "enumValues": [], + "wireKeys": [ + "accountId", + "profileImage", + "isJunior", + "platforms", + "username", + "displayName", + "createdAt" + ], + "isilFile": "CCEOLAOLEKJ.txt", + "isilType": "CCEOLAOLEKJ" + } + } + } + ], + "contracts": [ + { + "kind": "array", + "raw": "System.Collections.Generic.List`1", + "rootType": "CCEOLAOLEKJ", + "item": { + "kind": "object", + "raw": "CCEOLAOLEKJ", + "rootType": "CCEOLAOLEKJ" + }, + "typeDescription": { + "name": "CCEOLAOLEKJ", + "found": true, + "file": "CCEOLAOLEKJ.cs", + "declaration": "public class CCEOLAOLEKJ : IFAIJAGLDFK", + "kind": "class", + "baseType": "IFAIJAGLDFK", + "properties": [ + { + "name": "ACBFDMLHFPB", + "type": "DateTime", + "accessibility": "public", + "kind": "property" + }, + { + "name": "AEMHDANDFOP", + "type": "ObscuredBool", + "accessibility": "public", + "kind": "property" + }, + { + "name": "BIOGKFGIMDG", + "type": "ObscuredString", + "accessibility": "public", + "kind": "property" + }, + { + "name": "CFJLJOKIJCN", + "type": "ObscuredString", + "accessibility": "public", + "kind": "property" + }, + { + "name": "GAINIOENNCG", + "type": "ObscuredInt", + "accessibility": "public", + "kind": "property" + }, + { + "name": "IHADBCJNDIP", + "type": "ObscuredString", + "accessibility": "public", + "kind": "property" + }, + { + "name": "ILOADNHKMHK", + "type": "ENNEGEELGMC", + "accessibility": "public", + "kind": "property" + }, + { + "name": "JIIMAIDFJPN", + "type": "ObscuredBool", + "accessibility": "public", + "kind": "property" + }, + { + "name": "OOJFBECGEAD", + "type": "ObscuredString", + "accessibility": "public", + "kind": "property" + } + ], + "fields": [], + "enumValues": [], + "wireKeys": [ + "accountId", + "profileImage", + "isJunior", + "platforms", + "username", + "displayName", + "createdAt" + ], + "isilFile": "CCEOLAOLEKJ.txt", + "isilType": "CCEOLAOLEKJ" + } + } + ] + }, + { + "family": "account", + "route": "account/bulk?", + "evidence": [ + { + "type": "PEGGCEDHBOF", + "method": "RecRoom.Async.IPromise`1> NHCHADDMPFC(System.Collections.Generic.List`1 ILNGMAANNDG)", + "file": "PEGGCEDHBOF.txt", + "line": 4963, + "contract": { + "kind": "array", + "raw": "System.Collections.Generic.List`1", + "rootType": "CCEOLAOLEKJ", + "item": { + "kind": "object", + "raw": "CCEOLAOLEKJ", + "rootType": "CCEOLAOLEKJ" + }, + "typeDescription": { + "name": "CCEOLAOLEKJ", + "found": true, + "file": "CCEOLAOLEKJ.cs", + "declaration": "public class CCEOLAOLEKJ : IFAIJAGLDFK", + "kind": "class", + "baseType": "IFAIJAGLDFK", + "properties": [ + { + "name": "ACBFDMLHFPB", + "type": "DateTime", + "accessibility": "public", + "kind": "property" + }, + { + "name": "AEMHDANDFOP", + "type": "ObscuredBool", + "accessibility": "public", + "kind": "property" + }, + { + "name": "BIOGKFGIMDG", + "type": "ObscuredString", + "accessibility": "public", + "kind": "property" + }, + { + "name": "CFJLJOKIJCN", + "type": "ObscuredString", + "accessibility": "public", + "kind": "property" + }, + { + "name": "GAINIOENNCG", + "type": "ObscuredInt", + "accessibility": "public", + "kind": "property" + }, + { + "name": "IHADBCJNDIP", + "type": "ObscuredString", + "accessibility": "public", + "kind": "property" + }, + { + "name": "ILOADNHKMHK", + "type": "ENNEGEELGMC", + "accessibility": "public", + "kind": "property" + }, + { + "name": "JIIMAIDFJPN", + "type": "ObscuredBool", + "accessibility": "public", + "kind": "property" + }, + { + "name": "OOJFBECGEAD", + "type": "ObscuredString", + "accessibility": "public", + "kind": "property" + } + ], + "fields": [], + "enumValues": [], + "wireKeys": [ + "accountId", + "profileImage", + "isJunior", + "platforms", + "username", + "displayName", + "createdAt" + ], + "isilFile": "CCEOLAOLEKJ.txt", + "isilType": "CCEOLAOLEKJ" + } + } + } + ], + "contracts": [ + { + "kind": "array", + "raw": "System.Collections.Generic.List`1", + "rootType": "CCEOLAOLEKJ", + "item": { + "kind": "object", + "raw": "CCEOLAOLEKJ", + "rootType": "CCEOLAOLEKJ" + }, + "typeDescription": { + "name": "CCEOLAOLEKJ", + "found": true, + "file": "CCEOLAOLEKJ.cs", + "declaration": "public class CCEOLAOLEKJ : IFAIJAGLDFK", + "kind": "class", + "baseType": "IFAIJAGLDFK", + "properties": [ + { + "name": "ACBFDMLHFPB", + "type": "DateTime", + "accessibility": "public", + "kind": "property" + }, + { + "name": "AEMHDANDFOP", + "type": "ObscuredBool", + "accessibility": "public", + "kind": "property" + }, + { + "name": "BIOGKFGIMDG", + "type": "ObscuredString", + "accessibility": "public", + "kind": "property" + }, + { + "name": "CFJLJOKIJCN", + "type": "ObscuredString", + "accessibility": "public", + "kind": "property" + }, + { + "name": "GAINIOENNCG", + "type": "ObscuredInt", + "accessibility": "public", + "kind": "property" + }, + { + "name": "IHADBCJNDIP", + "type": "ObscuredString", + "accessibility": "public", + "kind": "property" + }, + { + "name": "ILOADNHKMHK", + "type": "ENNEGEELGMC", + "accessibility": "public", + "kind": "property" + }, + { + "name": "JIIMAIDFJPN", + "type": "ObscuredBool", + "accessibility": "public", + "kind": "property" + }, + { + "name": "OOJFBECGEAD", + "type": "ObscuredString", + "accessibility": "public", + "kind": "property" + } + ], + "fields": [], + "enumValues": [], + "wireKeys": [ + "accountId", + "profileImage", + "isJunior", + "platforms", + "username", + "displayName", + "createdAt" + ], + "isilFile": "CCEOLAOLEKJ.txt", + "isilType": "CCEOLAOLEKJ" + } + } + ] + }, + { + "family": "account", + "route": "account/bulk/", + "evidence": [ + { + "type": "PEGGCEDHBOF", + "method": "RecRoom.Async.IPromise`1> NGENLHMLHNG(System.Collections.Generic.List`1 BGELOMIEKFK, System.String MAFJOJLDFJO, System.String LOOAPLFGOEN, System.String PJIBCCFFMNJ, GPOOLJODEGM OLBDHLLJJHF)", + "file": "PEGGCEDHBOF.txt", + "line": 9080, + "contract": { + "kind": "array", + "raw": "System.Collections.Generic.List`1", + "rootType": "DCGAMCKNJDB", + "item": { + "kind": "object", + "raw": "DCGAMCKNJDB", + "rootType": "DCGAMCKNJDB" + }, + "typeDescription": { + "name": "DCGAMCKNJDB", + "found": true, + "file": "DCGAMCKNJDB.cs", + "declaration": "public class DCGAMCKNJDB", + "kind": "class", + "baseType": null, + "properties": [ + { + "name": "DINFEKALAJO", + "type": "GPOOLJODEGM", + "accessibility": "public", + "kind": "property" + }, + { + "name": "DJMJFEJGIHM", + "type": "CCEOLAOLEKJ", + "accessibility": "public", + "kind": "property" + }, + { + "name": "MCHJBOBNFLN", + "type": "string", + "accessibility": "public", + "kind": "property" + } + ], + "fields": [], + "enumValues": [], + "wireKeys": [], + "isilFile": "DCGAMCKNJDB.txt", + "isilType": "DCGAMCKNJDB" + } + } + } + ], + "contracts": [ + { + "kind": "array", + "raw": "System.Collections.Generic.List`1", + "rootType": "DCGAMCKNJDB", + "item": { + "kind": "object", + "raw": "DCGAMCKNJDB", + "rootType": "DCGAMCKNJDB" + }, + "typeDescription": { + "name": "DCGAMCKNJDB", + "found": true, + "file": "DCGAMCKNJDB.cs", + "declaration": "public class DCGAMCKNJDB", + "kind": "class", + "baseType": null, + "properties": [ + { + "name": "DINFEKALAJO", + "type": "GPOOLJODEGM", + "accessibility": "public", + "kind": "property" + }, + { + "name": "DJMJFEJGIHM", + "type": "CCEOLAOLEKJ", + "accessibility": "public", + "kind": "property" + }, + { + "name": "MCHJBOBNFLN", + "type": "string", + "accessibility": "public", + "kind": "property" + } + ], + "fields": [], + "enumValues": [], + "wireKeys": [], + "isilFile": "DCGAMCKNJDB.txt", + "isilType": "DCGAMCKNJDB" + } + } + ] + }, + { + "family": "account", + "route": "account/create", + "evidence": [ + { + "type": "PEGGCEDHBOF", + "method": "RecRoom.Async.IPromise`1 HHFPNFANNEG()", + "file": "PEGGCEDHBOF.txt", + "line": 5418, + "contract": { + "kind": "object", + "raw": "CCEOLAOLEKJ", + "rootType": "CCEOLAOLEKJ", + "typeDescription": { + "name": "CCEOLAOLEKJ", + "found": true, + "file": "CCEOLAOLEKJ.cs", + "declaration": "public class CCEOLAOLEKJ : IFAIJAGLDFK", + "kind": "class", + "baseType": "IFAIJAGLDFK", + "properties": [ + { + "name": "ACBFDMLHFPB", + "type": "DateTime", + "accessibility": "public", + "kind": "property" + }, + { + "name": "AEMHDANDFOP", + "type": "ObscuredBool", + "accessibility": "public", + "kind": "property" + }, + { + "name": "BIOGKFGIMDG", + "type": "ObscuredString", + "accessibility": "public", + "kind": "property" + }, + { + "name": "CFJLJOKIJCN", + "type": "ObscuredString", + "accessibility": "public", + "kind": "property" + }, + { + "name": "GAINIOENNCG", + "type": "ObscuredInt", + "accessibility": "public", + "kind": "property" + }, + { + "name": "IHADBCJNDIP", + "type": "ObscuredString", + "accessibility": "public", + "kind": "property" + }, + { + "name": "ILOADNHKMHK", + "type": "ENNEGEELGMC", + "accessibility": "public", + "kind": "property" + }, + { + "name": "JIIMAIDFJPN", + "type": "ObscuredBool", + "accessibility": "public", + "kind": "property" + }, + { + "name": "OOJFBECGEAD", + "type": "ObscuredString", + "accessibility": "public", + "kind": "property" + } + ], + "fields": [], + "enumValues": [], + "wireKeys": [ + "accountId", + "profileImage", + "isJunior", + "platforms", + "username", + "displayName", + "createdAt" + ], + "isilFile": "CCEOLAOLEKJ.txt", + "isilType": "CCEOLAOLEKJ" + } + } + } + ], + "contracts": [ + { + "kind": "object", + "raw": "CCEOLAOLEKJ", + "rootType": "CCEOLAOLEKJ", + "typeDescription": { + "name": "CCEOLAOLEKJ", + "found": true, + "file": "CCEOLAOLEKJ.cs", + "declaration": "public class CCEOLAOLEKJ : IFAIJAGLDFK", + "kind": "class", + "baseType": "IFAIJAGLDFK", + "properties": [ + { + "name": "ACBFDMLHFPB", + "type": "DateTime", + "accessibility": "public", + "kind": "property" + }, + { + "name": "AEMHDANDFOP", + "type": "ObscuredBool", + "accessibility": "public", + "kind": "property" + }, + { + "name": "BIOGKFGIMDG", + "type": "ObscuredString", + "accessibility": "public", + "kind": "property" + }, + { + "name": "CFJLJOKIJCN", + "type": "ObscuredString", + "accessibility": "public", + "kind": "property" + }, + { + "name": "GAINIOENNCG", + "type": "ObscuredInt", + "accessibility": "public", + "kind": "property" + }, + { + "name": "IHADBCJNDIP", + "type": "ObscuredString", + "accessibility": "public", + "kind": "property" + }, + { + "name": "ILOADNHKMHK", + "type": "ENNEGEELGMC", + "accessibility": "public", + "kind": "property" + }, + { + "name": "JIIMAIDFJPN", + "type": "ObscuredBool", + "accessibility": "public", + "kind": "property" + }, + { + "name": "OOJFBECGEAD", + "type": "ObscuredString", + "accessibility": "public", + "kind": "property" + } + ], + "fields": [], + "enumValues": [], + "wireKeys": [ + "accountId", + "profileImage", + "isJunior", + "platforms", + "username", + "displayName", + "createdAt" + ], + "isilFile": "CCEOLAOLEKJ.txt", + "isilType": "CCEOLAOLEKJ" + } + } + ] + }, + { + "family": "account", + "route": "account/me", + "evidence": [ + { + "type": "PEGGCEDHBOF", + "method": "RecRoom.Async.IPromise`1 HJFCFNCKDFO()", + "file": "PEGGCEDHBOF.txt", + "line": 3576, + "contract": { + "kind": "object", + "raw": "JJGHAFKJBEI", + "rootType": "JJGHAFKJBEI", + "typeDescription": { + "name": "JJGHAFKJBEI", + "found": true, + "file": "JJGHAFKJBEI.cs", + "declaration": "public class JJGHAFKJBEI : CCEOLAOLEKJ", + "kind": "class", + "baseType": "CCEOLAOLEKJ", + "properties": [ + { + "name": "AFBNICLHPBN", + "type": "bool", + "accessibility": "public", + "kind": "property" + }, + { + "name": "CNJIJKAHHCG", + "type": "bool", + "accessibility": "public", + "kind": "property" + }, + { + "name": "DKFIDIDDHNO", + "type": "Nullable", + "accessibility": "public", + "kind": "property" + }, + { + "name": "FHKEMGJCOND", + "type": "Nullable", + "accessibility": "public", + "kind": "property" + }, + { + "name": "LNMPGOOPELO", + "type": "int", + "accessibility": "public", + "kind": "property" + }, + { + "name": "MCDGMOKGEPJ", + "type": "ObscuredString", + "accessibility": "public", + "kind": "property" + }, + { + "name": "MDHCHOLIJEF", + "type": "ObscuredString", + "accessibility": "public", + "kind": "property" + }, + { + "name": "ONBONOFFGFC", + "type": "CINICOCNPFB", + "accessibility": "public", + "kind": "property" + } + ], + "fields": [], + "enumValues": [], + "wireKeys": [ + "email", + "phone", + "juniorState", + "parentAccountId", + "availableUsernameChanges" + ], + "isilFile": "JJGHAFKJBEI.txt", + "isilType": "JJGHAFKJBEI", + "inherits": "CCEOLAOLEKJ", + "inheritedMembers": [ + { + "name": "ACBFDMLHFPB", + "type": "DateTime", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "CCEOLAOLEKJ" + }, + { + "name": "AEMHDANDFOP", + "type": "ObscuredBool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "CCEOLAOLEKJ" + }, + { + "name": "BIOGKFGIMDG", + "type": "ObscuredString", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "CCEOLAOLEKJ" + }, + { + "name": "CFJLJOKIJCN", + "type": "ObscuredString", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "CCEOLAOLEKJ" + }, + { + "name": "GAINIOENNCG", + "type": "ObscuredInt", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "CCEOLAOLEKJ" + }, + { + "name": "IHADBCJNDIP", + "type": "ObscuredString", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "CCEOLAOLEKJ" + }, + { + "name": "ILOADNHKMHK", + "type": "ENNEGEELGMC", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "CCEOLAOLEKJ" + }, + { + "name": "JIIMAIDFJPN", + "type": "ObscuredBool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "CCEOLAOLEKJ" + }, + { + "name": "OOJFBECGEAD", + "type": "ObscuredString", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "CCEOLAOLEKJ" + } + ], + "inheritedWireKeys": [ + "accountId", + "profileImage", + "isJunior", + "platforms", + "username", + "displayName", + "createdAt" + ] + } + } + } + ], + "contracts": [ + { + "kind": "object", + "raw": "JJGHAFKJBEI", + "rootType": "JJGHAFKJBEI", + "typeDescription": { + "name": "JJGHAFKJBEI", + "found": true, + "file": "JJGHAFKJBEI.cs", + "declaration": "public class JJGHAFKJBEI : CCEOLAOLEKJ", + "kind": "class", + "baseType": "CCEOLAOLEKJ", + "properties": [ + { + "name": "AFBNICLHPBN", + "type": "bool", + "accessibility": "public", + "kind": "property" + }, + { + "name": "CNJIJKAHHCG", + "type": "bool", + "accessibility": "public", + "kind": "property" + }, + { + "name": "DKFIDIDDHNO", + "type": "Nullable", + "accessibility": "public", + "kind": "property" + }, + { + "name": "FHKEMGJCOND", + "type": "Nullable", + "accessibility": "public", + "kind": "property" + }, + { + "name": "LNMPGOOPELO", + "type": "int", + "accessibility": "public", + "kind": "property" + }, + { + "name": "MCDGMOKGEPJ", + "type": "ObscuredString", + "accessibility": "public", + "kind": "property" + }, + { + "name": "MDHCHOLIJEF", + "type": "ObscuredString", + "accessibility": "public", + "kind": "property" + }, + { + "name": "ONBONOFFGFC", + "type": "CINICOCNPFB", + "accessibility": "public", + "kind": "property" + } + ], + "fields": [], + "enumValues": [], + "wireKeys": [ + "email", + "phone", + "juniorState", + "parentAccountId", + "availableUsernameChanges" + ], + "isilFile": "JJGHAFKJBEI.txt", + "isilType": "JJGHAFKJBEI", + "inherits": "CCEOLAOLEKJ", + "inheritedMembers": [ + { + "name": "ACBFDMLHFPB", + "type": "DateTime", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "CCEOLAOLEKJ" + }, + { + "name": "AEMHDANDFOP", + "type": "ObscuredBool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "CCEOLAOLEKJ" + }, + { + "name": "BIOGKFGIMDG", + "type": "ObscuredString", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "CCEOLAOLEKJ" + }, + { + "name": "CFJLJOKIJCN", + "type": "ObscuredString", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "CCEOLAOLEKJ" + }, + { + "name": "GAINIOENNCG", + "type": "ObscuredInt", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "CCEOLAOLEKJ" + }, + { + "name": "IHADBCJNDIP", + "type": "ObscuredString", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "CCEOLAOLEKJ" + }, + { + "name": "ILOADNHKMHK", + "type": "ENNEGEELGMC", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "CCEOLAOLEKJ" + }, + { + "name": "JIIMAIDFJPN", + "type": "ObscuredBool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "CCEOLAOLEKJ" + }, + { + "name": "OOJFBECGEAD", + "type": "ObscuredString", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "CCEOLAOLEKJ" + } + ], + "inheritedWireKeys": [ + "accountId", + "profileImage", + "isJunior", + "platforms", + "username", + "displayName", + "createdAt" + ] + } + } + ] + }, + { + "family": "account", + "route": "account/me/", + "evidence": [ + { + "type": "PEGGCEDHBOF", + "method": "RecRoom.Async.IPromise OEBEELJDOHE(BestHTTP.HTTPMethods APAICGIHAGJ, BestHTTP.Forms.HTTPUrlEncodedForm MDOPLMHIKLP, System.String LOOAPLFGOEN)", + "file": "PEGGCEDHBOF.txt", + "line": 6704, + "contract": { + "kind": "success-or-empty", + "raw": "RecRoom.Async.IPromise", + "rootType": "void/success", + "typeDescription": { + "name": "void/success", + "found": false, + "kind": "object", + "wireKeys": [] + } + } + } + ], + "contracts": [ + { + "kind": "success-or-empty", + "raw": "RecRoom.Async.IPromise", + "rootType": "void/success", + "typeDescription": { + "name": "void/success", + "found": false, + "kind": "object", + "wireKeys": [] + } + } + ] + }, + { + "family": "account", + "route": "account/me/changepassword", + "evidence": [ + { + "type": "MHOKOMMOGKM", + "method": "RecRoom.Async.IPromise PGJBIIPMMJP(BestHTTP.Forms.HTTPUrlEncodedForm MDOPLMHIKLP, System.String MKIPICKCFDM)", + "file": "MHOKOMMOGKM.txt", + "line": 2582, + "contract": { + "kind": "success-or-empty", + "raw": "RecRoom.Async.IPromise", + "rootType": "void/success", + "typeDescription": { + "name": "void/success", + "found": false, + "kind": "object", + "wireKeys": [] + } + } + } + ], + "contracts": [ + { + "kind": "success-or-empty", + "raw": "RecRoom.Async.IPromise", + "rootType": "void/success", + "typeDescription": { + "name": "void/success", + "found": false, + "kind": "object", + "wireKeys": [] + } + } + ] + }, + { + "family": "account", + "route": "account/me/haspassword", + "evidence": [ + { + "type": "MHOKOMMOGKM", + "method": "RecRoom.Async.IPromise`1 PPKFJEINCCN()", + "file": "MHOKOMMOGKM.txt", + "line": 2203, + "contract": { + "kind": "primitive", + "raw": "System.Boolean", + "rootType": "boolean", + "typeDescription": { + "name": "boolean", + "found": false, + "kind": "primitive", + "wireKeys": [] + } + } + }, + { + "type": "MHOKOMMOGKM+<>c", + "method": "System.Void b__32_0()", + "file": "MHOKOMMOGKM_NestedType___c.txt", + "line": 584, + "contract": { + "kind": "unknown", + "raw": "", + "rootType": "unknown" + } + } + ], + "contracts": [ + { + "kind": "primitive", + "raw": "System.Boolean", + "rootType": "boolean", + "typeDescription": { + "name": "boolean", + "found": false, + "kind": "primitive", + "wireKeys": [] + } + }, + { + "kind": "unknown", + "raw": "", + "rootType": "unknown", + "typeDescription": { + "name": "unknown", + "found": false, + "kind": "object", + "wireKeys": [] + } + } + ] + }, + { + "family": "account", + "route": "account/recoverpassword", + "evidence": [ + { + "type": "MHOKOMMOGKM", + "method": "RecRoom.Async.IPromise PMKEHDEDNCK(System.String LJKKKECANAB)", + "file": "MHOKOMMOGKM.txt", + "line": 2779, + "contract": { + "kind": "success-or-empty", + "raw": "RecRoom.Async.IPromise", + "rootType": "void/success", + "typeDescription": { + "name": "void/success", + "found": false, + "kind": "object", + "wireKeys": [] + } + } + } + ], + "contracts": [ + { + "kind": "success-or-empty", + "raw": "RecRoom.Async.IPromise", + "rootType": "void/success", + "typeDescription": { + "name": "void/success", + "found": false, + "kind": "object", + "wireKeys": [] + } + } + ] + }, + { + "family": "account", + "route": "account/search?name=", + "evidence": [ + { + "type": "PEGGCEDHBOF", + "method": "RecRoom.Async.IPromise`1> IBDGAKHBIAJ(System.String CNBKKCJAHPP)", + "file": "PEGGCEDHBOF.txt", + "line": 9372, + "contract": { + "kind": "array", + "raw": "System.Collections.Generic.List`1", + "rootType": "CCEOLAOLEKJ", + "item": { + "kind": "object", + "raw": "CCEOLAOLEKJ", + "rootType": "CCEOLAOLEKJ" + }, + "typeDescription": { + "name": "CCEOLAOLEKJ", + "found": true, + "file": "CCEOLAOLEKJ.cs", + "declaration": "public class CCEOLAOLEKJ : IFAIJAGLDFK", + "kind": "class", + "baseType": "IFAIJAGLDFK", + "properties": [ + { + "name": "ACBFDMLHFPB", + "type": "DateTime", + "accessibility": "public", + "kind": "property" + }, + { + "name": "AEMHDANDFOP", + "type": "ObscuredBool", + "accessibility": "public", + "kind": "property" + }, + { + "name": "BIOGKFGIMDG", + "type": "ObscuredString", + "accessibility": "public", + "kind": "property" + }, + { + "name": "CFJLJOKIJCN", + "type": "ObscuredString", + "accessibility": "public", + "kind": "property" + }, + { + "name": "GAINIOENNCG", + "type": "ObscuredInt", + "accessibility": "public", + "kind": "property" + }, + { + "name": "IHADBCJNDIP", + "type": "ObscuredString", + "accessibility": "public", + "kind": "property" + }, + { + "name": "ILOADNHKMHK", + "type": "ENNEGEELGMC", + "accessibility": "public", + "kind": "property" + }, + { + "name": "JIIMAIDFJPN", + "type": "ObscuredBool", + "accessibility": "public", + "kind": "property" + }, + { + "name": "OOJFBECGEAD", + "type": "ObscuredString", + "accessibility": "public", + "kind": "property" + } + ], + "fields": [], + "enumValues": [], + "wireKeys": [ + "accountId", + "profileImage", + "isJunior", + "platforms", + "username", + "displayName", + "createdAt" + ], + "isilFile": "CCEOLAOLEKJ.txt", + "isilType": "CCEOLAOLEKJ" + } + } + } + ], + "contracts": [ + { + "kind": "array", + "raw": "System.Collections.Generic.List`1", + "rootType": "CCEOLAOLEKJ", + "item": { + "kind": "object", + "raw": "CCEOLAOLEKJ", + "rootType": "CCEOLAOLEKJ" + }, + "typeDescription": { + "name": "CCEOLAOLEKJ", + "found": true, + "file": "CCEOLAOLEKJ.cs", + "declaration": "public class CCEOLAOLEKJ : IFAIJAGLDFK", + "kind": "class", + "baseType": "IFAIJAGLDFK", + "properties": [ + { + "name": "ACBFDMLHFPB", + "type": "DateTime", + "accessibility": "public", + "kind": "property" + }, + { + "name": "AEMHDANDFOP", + "type": "ObscuredBool", + "accessibility": "public", + "kind": "property" + }, + { + "name": "BIOGKFGIMDG", + "type": "ObscuredString", + "accessibility": "public", + "kind": "property" + }, + { + "name": "CFJLJOKIJCN", + "type": "ObscuredString", + "accessibility": "public", + "kind": "property" + }, + { + "name": "GAINIOENNCG", + "type": "ObscuredInt", + "accessibility": "public", + "kind": "property" + }, + { + "name": "IHADBCJNDIP", + "type": "ObscuredString", + "accessibility": "public", + "kind": "property" + }, + { + "name": "ILOADNHKMHK", + "type": "ENNEGEELGMC", + "accessibility": "public", + "kind": "property" + }, + { + "name": "JIIMAIDFJPN", + "type": "ObscuredBool", + "accessibility": "public", + "kind": "property" + }, + { + "name": "OOJFBECGEAD", + "type": "ObscuredString", + "accessibility": "public", + "kind": "property" + } + ], + "fields": [], + "enumValues": [], + "wireKeys": [ + "accountId", + "profileImage", + "isJunior", + "platforms", + "username", + "displayName", + "createdAt" + ], + "isilFile": "CCEOLAOLEKJ.txt", + "isilType": "CCEOLAOLEKJ" + } + } + ] + }, + { + "family": "activities", + "route": "api/activities/charades/v1/words", + "evidence": [ + { + "type": "CardBox", + "method": "RecRoom.Async.IPromise`1> HMNGGFKCDGD()", + "file": "CardBox.txt", + "line": 2422, + "contract": { + "kind": "array", + "raw": "System.Collections.Generic.List`1", + "rootType": "JOHPEJAMBHG", + "item": { + "kind": "object", + "raw": "JOHPEJAMBHG", + "rootType": "JOHPEJAMBHG" + }, + "typeDescription": { + "name": "JOHPEJAMBHG", + "found": true, + "file": "JOHPEJAMBHG.cs", + "declaration": "public class JOHPEJAMBHG : IFAIJAGLDFK", + "kind": "class", + "baseType": "IFAIJAGLDFK", + "properties": [ + { + "name": "CJPFKEJAOBI", + "type": "enum", + "accessibility": "internal", + "kind": "property" + }, + { + "name": "GBLNDMFIGNM", + "type": "CJPFKEJAOBI", + "accessibility": "public", + "kind": "property" + }, + { + "name": "OIIMKMHNCMN", + "type": "string", + "accessibility": "public", + "kind": "property" + } + ], + "fields": [], + "enumValues": [], + "wireKeys": [ + "EN_US", + "Difficulty" + ], + "isilFile": "JOHPEJAMBHG.txt", + "isilType": "JOHPEJAMBHG" + } + } + }, + { + "type": "CardBox", + "method": "System.Void Start()", + "file": "CardBox.txt", + "line": 2221, + "contract": { + "kind": "unknown", + "raw": "", + "rootType": "unknown" + } + } + ], + "contracts": [ + { + "kind": "array", + "raw": "System.Collections.Generic.List`1", + "rootType": "JOHPEJAMBHG", + "item": { + "kind": "object", + "raw": "JOHPEJAMBHG", + "rootType": "JOHPEJAMBHG" + }, + "typeDescription": { + "name": "JOHPEJAMBHG", + "found": true, + "file": "JOHPEJAMBHG.cs", + "declaration": "public class JOHPEJAMBHG : IFAIJAGLDFK", + "kind": "class", + "baseType": "IFAIJAGLDFK", + "properties": [ + { + "name": "CJPFKEJAOBI", + "type": "enum", + "accessibility": "internal", + "kind": "property" + }, + { + "name": "GBLNDMFIGNM", + "type": "CJPFKEJAOBI", + "accessibility": "public", + "kind": "property" + }, + { + "name": "OIIMKMHNCMN", + "type": "string", + "accessibility": "public", + "kind": "property" + } + ], + "fields": [], + "enumValues": [], + "wireKeys": [ + "EN_US", + "Difficulty" + ], + "isilFile": "JOHPEJAMBHG.txt", + "isilType": "JOHPEJAMBHG" + } + }, + { + "kind": "unknown", + "raw": "", + "rootType": "unknown", + "typeDescription": { + "name": "unknown", + "found": false, + "kind": "object", + "wireKeys": [] + } + } + ] + }, + { + "family": "activities", + "route": "api/challenge/", + "evidence": [ + { + "type": "PDMJMMMNGJE", + "method": "System.Collections.IEnumerator EOBJGALHFNP(BPHGKAEDBPE+OJDJIJDNFHE AFLPGGJMPOE)", + "file": "PDMJMMMNGJE.txt", + "line": 156, + "contract": { + "kind": "callback-or-coroutine", + "raw": "IEnumerator", + "rootType": "callback", + "typeDescription": { + "name": "callback", + "found": false, + "kind": "object", + "wireKeys": [] + } + } + }, + { + "type": "PDMJMMMNGJE", + "method": "System.Void EKJPOJFLGFO(System.Int32 DKDDEIHEIBL, COLBJJAIEIO FMIIMOCIHCD)", + "file": "PDMJMMMNGJE.txt", + "line": 395, + "contract": { + "kind": "unknown", + "raw": "", + "rootType": "unknown" + } + } + ], + "contracts": [ + { + "kind": "callback-or-coroutine", + "raw": "IEnumerator", + "rootType": "callback", + "typeDescription": { + "name": "callback", + "found": false, + "kind": "object", + "wireKeys": [] + } + }, + { + "kind": "unknown", + "raw": "", + "rootType": "unknown", + "typeDescription": { + "name": "unknown", + "found": false, + "kind": "object", + "wireKeys": [] + } + } + ] + }, + { + "family": "activities", + "route": "api/royale/", + "evidence": [ + { + "type": "JDFDLJJGHIP", + "method": "RecRoom.Async.IPromise`1 APOAEDMOIHO()", + "file": "JDFDLJJGHIP.txt", + "line": 266, + "contract": { + "kind": "object", + "raw": "JDFDLJJGHIP+GHNGAENLIHA", + "rootType": "GHNGAENLIHA", + "typeDescription": { + "name": "GHNGAENLIHA", + "found": true, + "file": "JDFDLJJGHIP.cs", + "declaration": "internal class GHNGAENLIHA : IFAIJAGLDFK", + "kind": "class", + "baseType": "IFAIJAGLDFK", + "properties": [ + { + "name": "AGCAAIBPPIJ", + "type": "string", + "accessibility": "public", + "kind": "property" + }, + { + "name": "CBKDFHNBDEM", + "type": "long", + "accessibility": "public", + "kind": "property" + }, + { + "name": "COAHPEGOOEB", + "type": "int", + "accessibility": "public", + "kind": "property" + }, + { + "name": "COLADBAICGE", + "type": "long", + "accessibility": "public", + "kind": "property" + }, + { + "name": "GMAJAGFHAPO", + "type": "int", + "accessibility": "public", + "kind": "property" + }, + { + "name": "LINLLHBFIKF", + "type": "int", + "accessibility": "public", + "kind": "property" + }, + { + "name": "MPIAOHJIBOL", + "type": "long", + "accessibility": "public", + "kind": "property" + } + ], + "fields": [], + "enumValues": [], + "wireKeys": [ + "TotalXP", + "Level", + "RankIdx", + "RankName", + "CurrentLevelXPThreshold", + "NextLevelXPThreshold", + "NextLevelAcornReward" + ], + "isilFile": "JDFDLJJGHIP_NestedType_GHNGAENLIHA.txt", + "isilType": "JDFDLJJGHIP+GHNGAENLIHA" + } + } + }, + { + "type": "JDFDLJJGHIP", + "method": "RecRoom.Async.IPromise`1 NGEFKCDJAMF(JDFDLJJGHIP+MatchCompleteStats CNALBEPOKJJ)", + "file": "JDFDLJJGHIP.txt", + "line": 477, + "contract": { + "kind": "object", + "raw": "JDFDLJJGHIP+JLNAMKBKPNG", + "rootType": "JLNAMKBKPNG", + "typeDescription": { + "name": "JLNAMKBKPNG", + "found": true, + "file": "JDFDLJJGHIP.cs", + "declaration": "internal class JLNAMKBKPNG : IFAIJAGLDFK", + "kind": "class", + "baseType": "IFAIJAGLDFK", + "properties": [ + { + "name": "AGLPLKLAGKC", + "type": "List", + "accessibility": "public", + "kind": "property" + }, + { + "name": "HBANCPAIDGN", + "type": "long", + "accessibility": "public", + "kind": "property" + }, + { + "name": "HNBFKOEFCIE", + "type": "List", + "accessibility": "public", + "kind": "property" + } + ], + "fields": [], + "enumValues": [], + "wireKeys": [ + "TotalXPAwarded" + ], + "isilFile": "JDFDLJJGHIP_NestedType_JLNAMKBKPNG.txt", + "isilType": "JDFDLJJGHIP+JLNAMKBKPNG" + } + } + } + ], + "contracts": [ + { + "kind": "object", + "raw": "JDFDLJJGHIP+GHNGAENLIHA", + "rootType": "GHNGAENLIHA", + "typeDescription": { + "name": "GHNGAENLIHA", + "found": true, + "file": "JDFDLJJGHIP.cs", + "declaration": "internal class GHNGAENLIHA : IFAIJAGLDFK", + "kind": "class", + "baseType": "IFAIJAGLDFK", + "properties": [ + { + "name": "AGCAAIBPPIJ", + "type": "string", + "accessibility": "public", + "kind": "property" + }, + { + "name": "CBKDFHNBDEM", + "type": "long", + "accessibility": "public", + "kind": "property" + }, + { + "name": "COAHPEGOOEB", + "type": "int", + "accessibility": "public", + "kind": "property" + }, + { + "name": "COLADBAICGE", + "type": "long", + "accessibility": "public", + "kind": "property" + }, + { + "name": "GMAJAGFHAPO", + "type": "int", + "accessibility": "public", + "kind": "property" + }, + { + "name": "LINLLHBFIKF", + "type": "int", + "accessibility": "public", + "kind": "property" + }, + { + "name": "MPIAOHJIBOL", + "type": "long", + "accessibility": "public", + "kind": "property" + } + ], + "fields": [], + "enumValues": [], + "wireKeys": [ + "TotalXP", + "Level", + "RankIdx", + "RankName", + "CurrentLevelXPThreshold", + "NextLevelXPThreshold", + "NextLevelAcornReward" + ], + "isilFile": "JDFDLJJGHIP_NestedType_GHNGAENLIHA.txt", + "isilType": "JDFDLJJGHIP+GHNGAENLIHA" + } + }, + { + "kind": "object", + "raw": "JDFDLJJGHIP+JLNAMKBKPNG", + "rootType": "JLNAMKBKPNG", + "typeDescription": { + "name": "JLNAMKBKPNG", + "found": true, + "file": "JDFDLJJGHIP.cs", + "declaration": "internal class JLNAMKBKPNG : IFAIJAGLDFK", + "kind": "class", + "baseType": "IFAIJAGLDFK", + "properties": [ + { + "name": "AGLPLKLAGKC", + "type": "List", + "accessibility": "public", + "kind": "property" + }, + { + "name": "HBANCPAIDGN", + "type": "long", + "accessibility": "public", + "kind": "property" + }, + { + "name": "HNBFKOEFCIE", + "type": "List", + "accessibility": "public", + "kind": "property" + } + ], + "fields": [], + "enumValues": [], + "wireKeys": [ + "TotalXPAwarded" + ], + "isilFile": "JDFDLJJGHIP_NestedType_JLNAMKBKPNG.txt", + "isilType": "JDFDLJJGHIP+JLNAMKBKPNG" + } + } + ] + }, + { + "family": "avatar", + "route": "{0}v2/gifts/consume/", + "evidence": [ + { + "type": "NLEKGNENMCO", + "method": "RecRoom.Async.IPromise PCKICLJFOBO(NLEKGNENMCO+LOCNECLOHCA HBBAANIPIMP, BPHGKAEDBPE+CBEOHBCIPEA AFLPGGJMPOE)", + "file": "NLEKGNENMCO.txt", + "line": 2488, + "contract": { + "kind": "success-or-empty", + "raw": "RecRoom.Async.IPromise", + "rootType": "void/success", + "typeDescription": { + "name": "void/success", + "found": false, + "kind": "object", + "wireKeys": [] + } + } + } + ], + "contracts": [ + { + "kind": "success-or-empty", + "raw": "RecRoom.Async.IPromise", + "rootType": "void/success", + "typeDescription": { + "name": "void/success", + "found": false, + "kind": "object", + "wireKeys": [] + } + } + ] + }, + { + "family": "avatar", + "route": "api/avatar/", + "evidence": [ + { + "type": "NLEKGNENMCO", + "method": "RecRoom.Async.IPromise DLDENNPGADN()", + "file": "NLEKGNENMCO.txt", + "line": 3480, + "contract": { + "kind": "success-or-empty", + "raw": "RecRoom.Async.IPromise", + "rootType": "void/success", + "typeDescription": { + "name": "void/success", + "found": false, + "kind": "object", + "wireKeys": [] + } + } + }, + { + "type": "NLEKGNENMCO", + "method": "RecRoom.Async.IPromise PCKICLJFOBO(NLEKGNENMCO+LOCNECLOHCA HBBAANIPIMP, BPHGKAEDBPE+CBEOHBCIPEA AFLPGGJMPOE)", + "file": "NLEKGNENMCO.txt", + "line": 2486, + "contract": { + "kind": "success-or-empty", + "raw": "RecRoom.Async.IPromise", + "rootType": "void/success", + "typeDescription": { + "name": "void/success", + "found": false, + "kind": "object", + "wireKeys": [] + } + } + }, + { + "type": "NLEKGNENMCO", + "method": "RecRoom.Async.IPromise`1 JEKOPJCCHKB(GiftManager+LCLKAFOPBLD LHOMKMINCHH, System.Nullable`1 PCCLFNLJAMG, System.Boolean DKHOCHIJLBG)", + "file": "NLEKGNENMCO.txt", + "line": 1504, + "contract": { + "kind": "object", + "raw": "NLEKGNENMCO+LOCNECLOHCA", + "rootType": "LOCNECLOHCA", + "typeDescription": { + "name": "LOCNECLOHCA", + "found": true, + "file": "NLEKGNENMCO.cs", + "declaration": "internal class LOCNECLOHCA : IFAIJAGLDFK, AKJKEMONOIL", + "kind": "class", + "baseType": "IFAIJAGLDFK", + "properties": [ + { + "name": "AJIOGFIIAPG", + "type": "Nullable", + "accessibility": "public", + "kind": "property" + }, + { + "name": "BFLGAPOCLAJ", + "type": "Material", + "accessibility": "public", + "kind": "property" + }, + { + "name": "BGKNIPNNCJG", + "type": "string", + "accessibility": "public", + "kind": "property" + }, + { + "name": "CPNPKDLCDOO", + "type": "LCLKAFOPBLD", + "accessibility": "public", + "kind": "property" + }, + { + "name": "DFPPNHINEFO", + "type": "string", + "accessibility": "public", + "kind": "property" + }, + { + "name": "DLHOLKNMICH", + "type": "bool", + "accessibility": "public", + "kind": "property" + }, + { + "name": "DMAJINBPHKJ", + "type": "bool", + "accessibility": "public", + "kind": "property" + }, + { + "name": "FBLEJONFPAK", + "type": "bool", + "accessibility": "public", + "kind": "property" + }, + { + "name": "GBNGBHCOPFO", + "type": "string", + "accessibility": "public", + "kind": "property" + }, + { + "name": "HAKADJAKPDO", + "type": "string", + "accessibility": "public", + "kind": "property" + }, + { + "name": "HEKHNHAJGIN", + "type": "bool", + "accessibility": "public", + "kind": "property" + }, + { + "name": "HOAJNDDEDAM", + "type": "int", + "accessibility": "public", + "kind": "property" + }, + { + "name": "IILDJGJNFIM", + "type": "ACDKILABNNC", + "accessibility": "public", + "kind": "property" + }, + { + "name": "JOPFCBEDAAD", + "type": "GiftPackageVariant", + "accessibility": "public", + "kind": "property" + }, + { + "name": "JPOHGBCEJEJ", + "type": "long", + "accessibility": "public", + "kind": "property" + }, + { + "name": "KLLFPPFHNKF", + "type": "ENNEGEELGMC", + "accessibility": "public", + "kind": "property" + }, + { + "name": "LKFAKLCIINB", + "type": "Nullable", + "accessibility": "public", + "kind": "property" + }, + { + "name": "LLEJOHGPNAE", + "type": "string", + "accessibility": "public", + "kind": "property" + }, + { + "name": "LMNGODEMEHA", + "type": "bool", + "accessibility": "public", + "kind": "property" + }, + { + "name": "MPIODIBAMDO", + "type": "Nullable", + "accessibility": "public", + "kind": "property" + }, + { + "name": "NOEDKAJMLKN", + "type": "bool", + "accessibility": "public", + "kind": "property" + }, + { + "name": "NPOIFNDCBAN", + "type": "MCEEFCNMNAH", + "accessibility": "public", + "kind": "property" + }, + { + "name": "PDACCOLOMCB", + "type": "int", + "accessibility": "public", + "kind": "property" + }, + { + "name": "PDEHPKNHLBL", + "type": "string", + "accessibility": "public", + "kind": "property" + }, + { + "name": "PFJIBIPNDCA", + "type": "FCIKKFJOMNO", + "accessibility": "public", + "kind": "property" + } + ], + "fields": [ + { + "name": "CFINKBCLFJF", + "type": "bool", + "kind": "field" + } + ], + "enumValues": [], + "wireKeys": [ + "Message", + "Id", + "FromPlayerId", + "AvatarItemType", + "AvatarItemDesc", + "ConsumableItemDesc", + "EquipmentPrefabName", + "EquipmentModificationGuid", + "Platform", + "PlatformsToSpawnOn", + "BalanceType", + "CurrencyType", + "Currency", + "Xp", + "GiftRarity", + "GiftContext" + ], + "isilFile": "NLEKGNENMCO_NestedType_LOCNECLOHCA.txt", + "isilType": "NLEKGNENMCO+LOCNECLOHCA" + } + } + }, + { + "type": "NLEKGNENMCO", + "method": "RecRoom.Async.IPromise`1 JEKOPJCCHKB(GiftManager+LCLKAFOPBLD LHOMKMINCHH)", + "file": "NLEKGNENMCO.txt", + "line": 1794, + "contract": { + "kind": "object", + "raw": "NLEKGNENMCO+LOCNECLOHCA", + "rootType": "LOCNECLOHCA", + "typeDescription": { + "name": "LOCNECLOHCA", + "found": true, + "file": "NLEKGNENMCO.cs", + "declaration": "internal class LOCNECLOHCA : IFAIJAGLDFK, AKJKEMONOIL", + "kind": "class", + "baseType": "IFAIJAGLDFK", + "properties": [ + { + "name": "AJIOGFIIAPG", + "type": "Nullable", + "accessibility": "public", + "kind": "property" + }, + { + "name": "BFLGAPOCLAJ", + "type": "Material", + "accessibility": "public", + "kind": "property" + }, + { + "name": "BGKNIPNNCJG", + "type": "string", + "accessibility": "public", + "kind": "property" + }, + { + "name": "CPNPKDLCDOO", + "type": "LCLKAFOPBLD", + "accessibility": "public", + "kind": "property" + }, + { + "name": "DFPPNHINEFO", + "type": "string", + "accessibility": "public", + "kind": "property" + }, + { + "name": "DLHOLKNMICH", + "type": "bool", + "accessibility": "public", + "kind": "property" + }, + { + "name": "DMAJINBPHKJ", + "type": "bool", + "accessibility": "public", + "kind": "property" + }, + { + "name": "FBLEJONFPAK", + "type": "bool", + "accessibility": "public", + "kind": "property" + }, + { + "name": "GBNGBHCOPFO", + "type": "string", + "accessibility": "public", + "kind": "property" + }, + { + "name": "HAKADJAKPDO", + "type": "string", + "accessibility": "public", + "kind": "property" + }, + { + "name": "HEKHNHAJGIN", + "type": "bool", + "accessibility": "public", + "kind": "property" + }, + { + "name": "HOAJNDDEDAM", + "type": "int", + "accessibility": "public", + "kind": "property" + }, + { + "name": "IILDJGJNFIM", + "type": "ACDKILABNNC", + "accessibility": "public", + "kind": "property" + }, + { + "name": "JOPFCBEDAAD", + "type": "GiftPackageVariant", + "accessibility": "public", + "kind": "property" + }, + { + "name": "JPOHGBCEJEJ", + "type": "long", + "accessibility": "public", + "kind": "property" + }, + { + "name": "KLLFPPFHNKF", + "type": "ENNEGEELGMC", + "accessibility": "public", + "kind": "property" + }, + { + "name": "LKFAKLCIINB", + "type": "Nullable", + "accessibility": "public", + "kind": "property" + }, + { + "name": "LLEJOHGPNAE", + "type": "string", + "accessibility": "public", + "kind": "property" + }, + { + "name": "LMNGODEMEHA", + "type": "bool", + "accessibility": "public", + "kind": "property" + }, + { + "name": "MPIODIBAMDO", + "type": "Nullable", + "accessibility": "public", + "kind": "property" + }, + { + "name": "NOEDKAJMLKN", + "type": "bool", + "accessibility": "public", + "kind": "property" + }, + { + "name": "NPOIFNDCBAN", + "type": "MCEEFCNMNAH", + "accessibility": "public", + "kind": "property" + }, + { + "name": "PDACCOLOMCB", + "type": "int", + "accessibility": "public", + "kind": "property" + }, + { + "name": "PDEHPKNHLBL", + "type": "string", + "accessibility": "public", + "kind": "property" + }, + { + "name": "PFJIBIPNDCA", + "type": "FCIKKFJOMNO", + "accessibility": "public", + "kind": "property" + } + ], + "fields": [ + { + "name": "CFINKBCLFJF", + "type": "bool", + "kind": "field" + } + ], + "enumValues": [], + "wireKeys": [ + "Message", + "Id", + "FromPlayerId", + "AvatarItemType", + "AvatarItemDesc", + "ConsumableItemDesc", + "EquipmentPrefabName", + "EquipmentModificationGuid", + "Platform", + "PlatformsToSpawnOn", + "BalanceType", + "CurrencyType", + "Currency", + "Xp", + "GiftRarity", + "GiftContext" + ], + "isilFile": "NLEKGNENMCO_NestedType_LOCNECLOHCA.txt", + "isilType": "NLEKGNENMCO+LOCNECLOHCA" + } + } + }, + { + "type": "NLEKGNENMCO", + "method": "RecRoom.Async.IPromise`1> EPOGKGLKJHD()", + "file": "NLEKGNENMCO.txt", + "line": 1226, + "contract": { + "kind": "array", + "raw": "System.Collections.Generic.List`1", + "rootType": "LOCNECLOHCA", + "item": { + "kind": "object", + "raw": "NLEKGNENMCO+LOCNECLOHCA", + "rootType": "LOCNECLOHCA" + }, + "typeDescription": { + "name": "LOCNECLOHCA", + "found": true, + "file": "NLEKGNENMCO.cs", + "declaration": "internal class LOCNECLOHCA : IFAIJAGLDFK, AKJKEMONOIL", + "kind": "class", + "baseType": "IFAIJAGLDFK", + "properties": [ + { + "name": "AJIOGFIIAPG", + "type": "Nullable", + "accessibility": "public", + "kind": "property" + }, + { + "name": "BFLGAPOCLAJ", + "type": "Material", + "accessibility": "public", + "kind": "property" + }, + { + "name": "BGKNIPNNCJG", + "type": "string", + "accessibility": "public", + "kind": "property" + }, + { + "name": "CPNPKDLCDOO", + "type": "LCLKAFOPBLD", + "accessibility": "public", + "kind": "property" + }, + { + "name": "DFPPNHINEFO", + "type": "string", + "accessibility": "public", + "kind": "property" + }, + { + "name": "DLHOLKNMICH", + "type": "bool", + "accessibility": "public", + "kind": "property" + }, + { + "name": "DMAJINBPHKJ", + "type": "bool", + "accessibility": "public", + "kind": "property" + }, + { + "name": "FBLEJONFPAK", + "type": "bool", + "accessibility": "public", + "kind": "property" + }, + { + "name": "GBNGBHCOPFO", + "type": "string", + "accessibility": "public", + "kind": "property" + }, + { + "name": "HAKADJAKPDO", + "type": "string", + "accessibility": "public", + "kind": "property" + }, + { + "name": "HEKHNHAJGIN", + "type": "bool", + "accessibility": "public", + "kind": "property" + }, + { + "name": "HOAJNDDEDAM", + "type": "int", + "accessibility": "public", + "kind": "property" + }, + { + "name": "IILDJGJNFIM", + "type": "ACDKILABNNC", + "accessibility": "public", + "kind": "property" + }, + { + "name": "JOPFCBEDAAD", + "type": "GiftPackageVariant", + "accessibility": "public", + "kind": "property" + }, + { + "name": "JPOHGBCEJEJ", + "type": "long", + "accessibility": "public", + "kind": "property" + }, + { + "name": "KLLFPPFHNKF", + "type": "ENNEGEELGMC", + "accessibility": "public", + "kind": "property" + }, + { + "name": "LKFAKLCIINB", + "type": "Nullable", + "accessibility": "public", + "kind": "property" + }, + { + "name": "LLEJOHGPNAE", + "type": "string", + "accessibility": "public", + "kind": "property" + }, + { + "name": "LMNGODEMEHA", + "type": "bool", + "accessibility": "public", + "kind": "property" + }, + { + "name": "MPIODIBAMDO", + "type": "Nullable", + "accessibility": "public", + "kind": "property" + }, + { + "name": "NOEDKAJMLKN", + "type": "bool", + "accessibility": "public", + "kind": "property" + }, + { + "name": "NPOIFNDCBAN", + "type": "MCEEFCNMNAH", + "accessibility": "public", + "kind": "property" + }, + { + "name": "PDACCOLOMCB", + "type": "int", + "accessibility": "public", + "kind": "property" + }, + { + "name": "PDEHPKNHLBL", + "type": "string", + "accessibility": "public", + "kind": "property" + }, + { + "name": "PFJIBIPNDCA", + "type": "FCIKKFJOMNO", + "accessibility": "public", + "kind": "property" + } + ], + "fields": [ + { + "name": "CFINKBCLFJF", + "type": "bool", + "kind": "field" + } + ], + "enumValues": [], + "wireKeys": [ + "Message", + "Id", + "FromPlayerId", + "AvatarItemType", + "AvatarItemDesc", + "ConsumableItemDesc", + "EquipmentPrefabName", + "EquipmentModificationGuid", + "Platform", + "PlatformsToSpawnOn", + "BalanceType", + "CurrencyType", + "Currency", + "Xp", + "GiftRarity", + "GiftContext" + ], + "isilFile": "NLEKGNENMCO_NestedType_LOCNECLOHCA.txt", + "isilType": "NLEKGNENMCO+LOCNECLOHCA" + } + } + }, + { + "type": "NLEKGNENMCO", + "method": "System.Collections.IEnumerator FHJKJMFABGK(BPHGKAEDBPE+CBEOHBCIPEA AFLPGGJMPOE)", + "file": "NLEKGNENMCO.txt", + "line": 3101, + "contract": { + "kind": "callback-or-coroutine", + "raw": "IEnumerator", + "rootType": "callback", + "typeDescription": { + "name": "callback", + "found": false, + "kind": "object", + "wireKeys": [] + } + } + }, + { + "type": "NLEKGNENMCO", + "method": "System.Collections.IEnumerator KLHANCHHJBD(CDNNKFCCONN GKDEAOJLJFI, BPHGKAEDBPE+CBEOHBCIPEA AFLPGGJMPOE)", + "file": "NLEKGNENMCO.txt", + "line": 3229, + "contract": { + "kind": "callback-or-coroutine", + "raw": "IEnumerator", + "rootType": "callback", + "typeDescription": { + "name": "callback", + "found": false, + "kind": "object", + "wireKeys": [] + } + } + }, + { + "type": "NLEKGNENMCO", + "method": "System.Void OFJLGPLDEKJ()", + "file": "NLEKGNENMCO.txt", + "line": 3748, + "contract": { + "kind": "unknown", + "raw": "", + "rootType": "unknown" + } + }, + { + "type": "NLEKGNENMCO+LFBBKJKCJFF", + "method": "System.Boolean MoveNext()", + "file": "NLEKGNENMCO_NestedType_LFBBKJKCJFF.txt", + "line": 147, + "contract": { + "kind": "unknown", + "raw": "", + "rootType": "unknown" + } + } + ], + "contracts": [ + { + "kind": "success-or-empty", + "raw": "RecRoom.Async.IPromise", + "rootType": "void/success", + "typeDescription": { + "name": "void/success", + "found": false, + "kind": "object", + "wireKeys": [] + } + }, + { + "kind": "object", + "raw": "NLEKGNENMCO+LOCNECLOHCA", + "rootType": "LOCNECLOHCA", + "typeDescription": { + "name": "LOCNECLOHCA", + "found": true, + "file": "NLEKGNENMCO.cs", + "declaration": "internal class LOCNECLOHCA : IFAIJAGLDFK, AKJKEMONOIL", + "kind": "class", + "baseType": "IFAIJAGLDFK", + "properties": [ + { + "name": "AJIOGFIIAPG", + "type": "Nullable", + "accessibility": "public", + "kind": "property" + }, + { + "name": "BFLGAPOCLAJ", + "type": "Material", + "accessibility": "public", + "kind": "property" + }, + { + "name": "BGKNIPNNCJG", + "type": "string", + "accessibility": "public", + "kind": "property" + }, + { + "name": "CPNPKDLCDOO", + "type": "LCLKAFOPBLD", + "accessibility": "public", + "kind": "property" + }, + { + "name": "DFPPNHINEFO", + "type": "string", + "accessibility": "public", + "kind": "property" + }, + { + "name": "DLHOLKNMICH", + "type": "bool", + "accessibility": "public", + "kind": "property" + }, + { + "name": "DMAJINBPHKJ", + "type": "bool", + "accessibility": "public", + "kind": "property" + }, + { + "name": "FBLEJONFPAK", + "type": "bool", + "accessibility": "public", + "kind": "property" + }, + { + "name": "GBNGBHCOPFO", + "type": "string", + "accessibility": "public", + "kind": "property" + }, + { + "name": "HAKADJAKPDO", + "type": "string", + "accessibility": "public", + "kind": "property" + }, + { + "name": "HEKHNHAJGIN", + "type": "bool", + "accessibility": "public", + "kind": "property" + }, + { + "name": "HOAJNDDEDAM", + "type": "int", + "accessibility": "public", + "kind": "property" + }, + { + "name": "IILDJGJNFIM", + "type": "ACDKILABNNC", + "accessibility": "public", + "kind": "property" + }, + { + "name": "JOPFCBEDAAD", + "type": "GiftPackageVariant", + "accessibility": "public", + "kind": "property" + }, + { + "name": "JPOHGBCEJEJ", + "type": "long", + "accessibility": "public", + "kind": "property" + }, + { + "name": "KLLFPPFHNKF", + "type": "ENNEGEELGMC", + "accessibility": "public", + "kind": "property" + }, + { + "name": "LKFAKLCIINB", + "type": "Nullable", + "accessibility": "public", + "kind": "property" + }, + { + "name": "LLEJOHGPNAE", + "type": "string", + "accessibility": "public", + "kind": "property" + }, + { + "name": "LMNGODEMEHA", + "type": "bool", + "accessibility": "public", + "kind": "property" + }, + { + "name": "MPIODIBAMDO", + "type": "Nullable", + "accessibility": "public", + "kind": "property" + }, + { + "name": "NOEDKAJMLKN", + "type": "bool", + "accessibility": "public", + "kind": "property" + }, + { + "name": "NPOIFNDCBAN", + "type": "MCEEFCNMNAH", + "accessibility": "public", + "kind": "property" + }, + { + "name": "PDACCOLOMCB", + "type": "int", + "accessibility": "public", + "kind": "property" + }, + { + "name": "PDEHPKNHLBL", + "type": "string", + "accessibility": "public", + "kind": "property" + }, + { + "name": "PFJIBIPNDCA", + "type": "FCIKKFJOMNO", + "accessibility": "public", + "kind": "property" + } + ], + "fields": [ + { + "name": "CFINKBCLFJF", + "type": "bool", + "kind": "field" + } + ], + "enumValues": [], + "wireKeys": [ + "Message", + "Id", + "FromPlayerId", + "AvatarItemType", + "AvatarItemDesc", + "ConsumableItemDesc", + "EquipmentPrefabName", + "EquipmentModificationGuid", + "Platform", + "PlatformsToSpawnOn", + "BalanceType", + "CurrencyType", + "Currency", + "Xp", + "GiftRarity", + "GiftContext" + ], + "isilFile": "NLEKGNENMCO_NestedType_LOCNECLOHCA.txt", + "isilType": "NLEKGNENMCO+LOCNECLOHCA" + } + }, + { + "kind": "array", + "raw": "System.Collections.Generic.List`1", + "rootType": "LOCNECLOHCA", + "item": { + "kind": "object", + "raw": "NLEKGNENMCO+LOCNECLOHCA", + "rootType": "LOCNECLOHCA" + }, + "typeDescription": { + "name": "LOCNECLOHCA", + "found": true, + "file": "NLEKGNENMCO.cs", + "declaration": "internal class LOCNECLOHCA : IFAIJAGLDFK, AKJKEMONOIL", + "kind": "class", + "baseType": "IFAIJAGLDFK", + "properties": [ + { + "name": "AJIOGFIIAPG", + "type": "Nullable", + "accessibility": "public", + "kind": "property" + }, + { + "name": "BFLGAPOCLAJ", + "type": "Material", + "accessibility": "public", + "kind": "property" + }, + { + "name": "BGKNIPNNCJG", + "type": "string", + "accessibility": "public", + "kind": "property" + }, + { + "name": "CPNPKDLCDOO", + "type": "LCLKAFOPBLD", + "accessibility": "public", + "kind": "property" + }, + { + "name": "DFPPNHINEFO", + "type": "string", + "accessibility": "public", + "kind": "property" + }, + { + "name": "DLHOLKNMICH", + "type": "bool", + "accessibility": "public", + "kind": "property" + }, + { + "name": "DMAJINBPHKJ", + "type": "bool", + "accessibility": "public", + "kind": "property" + }, + { + "name": "FBLEJONFPAK", + "type": "bool", + "accessibility": "public", + "kind": "property" + }, + { + "name": "GBNGBHCOPFO", + "type": "string", + "accessibility": "public", + "kind": "property" + }, + { + "name": "HAKADJAKPDO", + "type": "string", + "accessibility": "public", + "kind": "property" + }, + { + "name": "HEKHNHAJGIN", + "type": "bool", + "accessibility": "public", + "kind": "property" + }, + { + "name": "HOAJNDDEDAM", + "type": "int", + "accessibility": "public", + "kind": "property" + }, + { + "name": "IILDJGJNFIM", + "type": "ACDKILABNNC", + "accessibility": "public", + "kind": "property" + }, + { + "name": "JOPFCBEDAAD", + "type": "GiftPackageVariant", + "accessibility": "public", + "kind": "property" + }, + { + "name": "JPOHGBCEJEJ", + "type": "long", + "accessibility": "public", + "kind": "property" + }, + { + "name": "KLLFPPFHNKF", + "type": "ENNEGEELGMC", + "accessibility": "public", + "kind": "property" + }, + { + "name": "LKFAKLCIINB", + "type": "Nullable", + "accessibility": "public", + "kind": "property" + }, + { + "name": "LLEJOHGPNAE", + "type": "string", + "accessibility": "public", + "kind": "property" + }, + { + "name": "LMNGODEMEHA", + "type": "bool", + "accessibility": "public", + "kind": "property" + }, + { + "name": "MPIODIBAMDO", + "type": "Nullable", + "accessibility": "public", + "kind": "property" + }, + { + "name": "NOEDKAJMLKN", + "type": "bool", + "accessibility": "public", + "kind": "property" + }, + { + "name": "NPOIFNDCBAN", + "type": "MCEEFCNMNAH", + "accessibility": "public", + "kind": "property" + }, + { + "name": "PDACCOLOMCB", + "type": "int", + "accessibility": "public", + "kind": "property" + }, + { + "name": "PDEHPKNHLBL", + "type": "string", + "accessibility": "public", + "kind": "property" + }, + { + "name": "PFJIBIPNDCA", + "type": "FCIKKFJOMNO", + "accessibility": "public", + "kind": "property" + } + ], + "fields": [ + { + "name": "CFINKBCLFJF", + "type": "bool", + "kind": "field" + } + ], + "enumValues": [], + "wireKeys": [ + "Message", + "Id", + "FromPlayerId", + "AvatarItemType", + "AvatarItemDesc", + "ConsumableItemDesc", + "EquipmentPrefabName", + "EquipmentModificationGuid", + "Platform", + "PlatformsToSpawnOn", + "BalanceType", + "CurrencyType", + "Currency", + "Xp", + "GiftRarity", + "GiftContext" + ], + "isilFile": "NLEKGNENMCO_NestedType_LOCNECLOHCA.txt", + "isilType": "NLEKGNENMCO+LOCNECLOHCA" + } + }, + { + "kind": "callback-or-coroutine", + "raw": "IEnumerator", + "rootType": "callback", + "typeDescription": { + "name": "callback", + "found": false, + "kind": "object", + "wireKeys": [] + } + }, + { + "kind": "unknown", + "raw": "", + "rootType": "unknown", + "typeDescription": { + "name": "unknown", + "found": false, + "kind": "object", + "wireKeys": [] + } + } + ] + }, + { + "family": "avatar", + "route": "api/avatar/v1/lockeditems?", + "evidence": [ + { + "type": "NLEKGNENMCO", + "method": "RecRoom.Async.IPromise`1> KLPMNFCFOLE(System.Collections.Generic.List`1 EGLBFNGKDLD)", + "file": "NLEKGNENMCO.txt", + "line": 2931, + "contract": { + "kind": "array", + "raw": "System.Collections.Generic.List`1", + "rootType": "EPFHLDCPAOK", + "item": { + "kind": "object", + "raw": "NLEKGNENMCO+EPFHLDCPAOK", + "rootType": "EPFHLDCPAOK" + }, + "typeDescription": { + "name": "EPFHLDCPAOK", + "found": true, + "file": "NLEKGNENMCO.cs", + "declaration": "internal class EPFHLDCPAOK : IFAIJAGLDFK", + "kind": "class", + "baseType": "IFAIJAGLDFK", + "properties": [ + { + "name": "AJIOGFIIAPG", + "type": "JHDGJFEOGGA", + "accessibility": "public", + "kind": "property" + }, + { + "name": "EPBGPFCIOMJ", + "type": "MCEEFCNMNAH", + "accessibility": "public", + "kind": "property" + }, + { + "name": "FAHDIMDEHEK", + "type": "string", + "accessibility": "public", + "kind": "property" + }, + { + "name": "KENGPMDDCML", + "type": "string", + "accessibility": "public", + "kind": "property" + }, + { + "name": "MAMPKNECKCM", + "type": "string", + "accessibility": "public", + "kind": "property" + } + ], + "fields": [], + "enumValues": [], + "wireKeys": [ + "AvatarItemType", + "AvatarItemDesc", + "FriendlyName", + "Tooltip", + "Rarity" + ], + "isilFile": "NLEKGNENMCO_NestedType_EPFHLDCPAOK.txt", + "isilType": "NLEKGNENMCO+EPFHLDCPAOK" + } + } + } + ], + "contracts": [ + { + "kind": "array", + "raw": "System.Collections.Generic.List`1", + "rootType": "EPFHLDCPAOK", + "item": { + "kind": "object", + "raw": "NLEKGNENMCO+EPFHLDCPAOK", + "rootType": "EPFHLDCPAOK" + }, + "typeDescription": { + "name": "EPFHLDCPAOK", + "found": true, + "file": "NLEKGNENMCO.cs", + "declaration": "internal class EPFHLDCPAOK : IFAIJAGLDFK", + "kind": "class", + "baseType": "IFAIJAGLDFK", + "properties": [ + { + "name": "AJIOGFIIAPG", + "type": "JHDGJFEOGGA", + "accessibility": "public", + "kind": "property" + }, + { + "name": "EPBGPFCIOMJ", + "type": "MCEEFCNMNAH", + "accessibility": "public", + "kind": "property" + }, + { + "name": "FAHDIMDEHEK", + "type": "string", + "accessibility": "public", + "kind": "property" + }, + { + "name": "KENGPMDDCML", + "type": "string", + "accessibility": "public", + "kind": "property" + }, + { + "name": "MAMPKNECKCM", + "type": "string", + "accessibility": "public", + "kind": "property" + } + ], + "fields": [], + "enumValues": [], + "wireKeys": [ + "AvatarItemType", + "AvatarItemDesc", + "FriendlyName", + "Tooltip", + "Rarity" + ], + "isilFile": "NLEKGNENMCO_NestedType_EPFHLDCPAOK.txt", + "isilType": "NLEKGNENMCO+EPFHLDCPAOK" + } + } + ] + }, + { + "family": "bug-reporting", + "route": "api/bugreporting/", + "evidence": [ + { + "type": "RecNet.BugReporting", + "method": "RecRoom.Async.IPromise ReportBug(System.String summary, System.String description, System.Byte[] screenshotData, System.Byte[] outputLogData, System.String testCaseKey)", + "file": "RecNet\\BugReporting.txt", + "line": 146, + "contract": { + "kind": "success-or-empty", + "raw": "RecRoom.Async.IPromise", + "rootType": "void/success", + "typeDescription": { + "name": "void/success", + "found": false, + "kind": "object", + "wireKeys": [] + } + } + } + ], + "contracts": [ + { + "kind": "success-or-empty", + "raw": "RecRoom.Async.IPromise", + "rootType": "void/success", + "typeDescription": { + "name": "void/success", + "found": false, + "kind": "object", + "wireKeys": [] + } + } + ] + }, + { + "family": "clubs", + "route": "{0}/club/{1}", + "evidence": [ + { + "type": "GNPDMBPGHBH", + "method": "System.String JDEIGGFCBPF(System.Int64 AOMBLGBCENO)", + "file": "GNPDMBPGHBH.txt", + "line": 630, + "contract": { + "kind": "unknown", + "raw": "", + "rootType": "unknown" + } + } + ], + "contracts": [ + { + "kind": "unknown", + "raw": "", + "rootType": "unknown", + "typeDescription": { + "name": "unknown", + "found": false, + "kind": "object", + "wireKeys": [] + } + } + ] + }, + { + "family": "clubs", + "route": "announcements/club/{0}", + "evidence": [ + { + "type": "JDJGIBLMFKK", + "method": "RecRoom.Async.IPromise`1 ONCKANJNGLA(System.Int64 AOMBLGBCENO)", + "file": "JDJGIBLMFKK.txt", + "line": 1167, + "contract": { + "kind": "object", + "raw": "HPACLJHLHBG", + "rootType": "HPACLJHLHBG", + "typeDescription": { + "name": "HPACLJHLHBG", + "found": true, + "file": "HPACLJHLHBG.cs", + "declaration": "public class HPACLJHLHBG : IFAIJAGLDFK", + "kind": "class", + "baseType": "IFAIJAGLDFK", + "properties": [ + { + "name": "CCGOEDABKNN", + "type": "long", + "accessibility": "public", + "kind": "property" + }, + { + "name": "IILPDPCODAE", + "type": "bool", + "accessibility": "public", + "kind": "property" + }, + { + "name": "JCCDJAADFPE", + "type": "long", + "accessibility": "public", + "kind": "property" + } + ], + "fields": [], + "enumValues": [], + "wireKeys": [ + "clubId", + "LastReadAnnouncementId" + ], + "isilFile": "HPACLJHLHBG.txt", + "isilType": "HPACLJHLHBG" + } + } + }, + { + "type": "JDJGIBLMFKK", + "method": "RecRoom.Async.IPromise`1 AAOLBAJFCOJ(JDJGIBLMFKK+DHPONMGBFJE JMFLHIIJFKL)", + "file": "JDJGIBLMFKK.txt", + "line": 398, + "contract": { + "kind": "primitive", + "raw": "System.Int64", + "rootType": "number", + "typeDescription": { + "name": "number", + "found": false, + "kind": "object", + "wireKeys": [] + } + } + } + ], + "contracts": [ + { + "kind": "object", + "raw": "HPACLJHLHBG", + "rootType": "HPACLJHLHBG", + "typeDescription": { + "name": "HPACLJHLHBG", + "found": true, + "file": "HPACLJHLHBG.cs", + "declaration": "public class HPACLJHLHBG : IFAIJAGLDFK", + "kind": "class", + "baseType": "IFAIJAGLDFK", + "properties": [ + { + "name": "CCGOEDABKNN", + "type": "long", + "accessibility": "public", + "kind": "property" + }, + { + "name": "IILPDPCODAE", + "type": "bool", + "accessibility": "public", + "kind": "property" + }, + { + "name": "JCCDJAADFPE", + "type": "long", + "accessibility": "public", + "kind": "property" + } + ], + "fields": [], + "enumValues": [], + "wireKeys": [ + "clubId", + "LastReadAnnouncementId" + ], + "isilFile": "HPACLJHLHBG.txt", + "isilType": "HPACLJHLHBG" + } + }, + { + "kind": "primitive", + "raw": "System.Int64", + "rootType": "number", + "typeDescription": { + "name": "number", + "found": false, + "kind": "object", + "wireKeys": [] + } + } + ] + }, + { + "family": "clubs", + "route": "announcements/club/{0}/{1}", + "evidence": [ + { + "type": "JDJGIBLMFKK", + "method": "RecRoom.Async.IPromise HCGFMHLEDPE(System.Int64 AOMBLGBCENO, System.Int64 LOHFFDEGIMK)", + "file": "JDJGIBLMFKK.txt", + "line": 2462, + "contract": { + "kind": "success-or-empty", + "raw": "RecRoom.Async.IPromise", + "rootType": "void/success", + "typeDescription": { + "name": "void/success", + "found": false, + "kind": "object", + "wireKeys": [] + } + } + }, + { + "type": "JDJGIBLMFKK", + "method": "RecRoom.Async.IPromise POHLPICNHJD(JDJGIBLMFKK+FOEPIDJCLMC JMFLHIIJFKL)", + "file": "JDJGIBLMFKK.txt", + "line": 789, + "contract": { + "kind": "success-or-empty", + "raw": "RecRoom.Async.IPromise", + "rootType": "void/success", + "typeDescription": { + "name": "void/success", + "found": false, + "kind": "object", + "wireKeys": [] + } + } + } + ], + "contracts": [ + { + "kind": "success-or-empty", + "raw": "RecRoom.Async.IPromise", + "rootType": "void/success", + "typeDescription": { + "name": "void/success", + "found": false, + "kind": "object", + "wireKeys": [] + } + } + ] + }, + { + "family": "clubs", + "route": "announcements/club/{0}/{1}/read", + "evidence": [ + { + "type": "JDJGIBLMFKK", + "method": "RecRoom.Async.IPromise OFBNAHIOHON(System.Int64 AOMBLGBCENO, System.Int64 LOHFFDEGIMK)", + "file": "JDJGIBLMFKK.txt", + "line": 2865, + "contract": { + "kind": "success-or-empty", + "raw": "RecRoom.Async.IPromise", + "rootType": "void/success", + "typeDescription": { + "name": "void/success", + "found": false, + "kind": "object", + "wireKeys": [] + } + } + } + ], + "contracts": [ + { + "kind": "success-or-empty", + "raw": "RecRoom.Async.IPromise", + "rootType": "void/success", + "typeDescription": { + "name": "void/success", + "found": false, + "kind": "object", + "wireKeys": [] + } + } + ] + }, + { + "family": "clubs", + "route": "api/clubreporting/v1/report", + "evidence": [ + { + "type": "JDJGIBLMFKK", + "method": "RecRoom.Async.IPromise LBOOMIDNLPP(System.Int64 AOMBLGBCENO, LCCEEFHOBEN+CJFENPHAAHI MEABFEIBEMP, System.String EFDBFLPKHKA)", + "file": "JDJGIBLMFKK.txt", + "line": 21791, + "contract": { + "kind": "success-or-empty", + "raw": "RecRoom.Async.IPromise", + "rootType": "void/success", + "typeDescription": { + "name": "void/success", + "found": false, + "kind": "object", + "wireKeys": [] + } + } + } + ], + "contracts": [ + { + "kind": "success-or-empty", + "raw": "RecRoom.Async.IPromise", + "rootType": "void/success", + "typeDescription": { + "name": "void/success", + "found": false, + "kind": "object", + "wireKeys": [] + } + } + ] + }, + { + "family": "clubs", + "route": "club/{0}", + "evidence": [ + { + "type": "JDJGIBLMFKK", + "method": "RecRoom.Async.IPromise IGHDIAEPHJD(System.Int64 AOMBLGBCENO, System.String DHANCEHHIDH)", + "file": "JDJGIBLMFKK.txt", + "line": 13088, + "contract": { + "kind": "success-or-empty", + "raw": "RecRoom.Async.IPromise", + "rootType": "void/success", + "typeDescription": { + "name": "void/success", + "found": false, + "kind": "object", + "wireKeys": [] + } + } + }, + { + "type": "JDJGIBLMFKK", + "method": "RecRoom.Async.IPromise`1 NKGOFNILGPL(System.Int64 AOMBLGBCENO, System.Boolean OHLIGBELLLH = True)", + "file": "JDJGIBLMFKK.txt", + "line": 10425, + "contract": { + "kind": "object", + "raw": "PLILLKHMNDA", + "rootType": "PLILLKHMNDA", + "typeDescription": { + "name": "PLILLKHMNDA", + "found": true, + "file": "PLILLKHMNDA.cs", + "declaration": "public class PLILLKHMNDA : IFAIJAGLDFK, IEquatable", + "kind": "class", + "baseType": "IFAIJAGLDFK", + "properties": [ + { + "name": "BADIGBCKECA", + "type": "int", + "accessibility": "public", + "kind": "property" + }, + { + "name": "CCGOEDABKNN", + "type": "long", + "accessibility": "public", + "kind": "property" + }, + { + "name": "CDINMMPNAID", + "type": "JCMLDDKFKEO", + "accessibility": "public", + "kind": "property" + }, + { + "name": "CDNFGMHLDMJ", + "type": "bool", + "accessibility": "public", + "kind": "property" + }, + { + "name": "EEAOJCGAOCN", + "type": "int", + "accessibility": "public", + "kind": "property" + }, + { + "name": "EHOLKJPEGFF", + "type": "string", + "accessibility": "public", + "kind": "property" + }, + { + "name": "EPGALOMHHMI", + "type": "JCDEFCJLCHN", + "accessibility": "public", + "kind": "property" + }, + { + "name": "FIKEBGGCDFN", + "type": "string", + "accessibility": "public", + "kind": "property" + }, + { + "name": "HHCGNCLFKDM", + "type": "Nullable", + "accessibility": "public", + "kind": "property" + }, + { + "name": "IPKLLFAJJPJ", + "type": "bool", + "accessibility": "public", + "kind": "property" + }, + { + "name": "KODBEJPEFOJ", + "type": "string", + "accessibility": "public", + "kind": "property" + }, + { + "name": "LNGPBGCIAPP", + "type": "string", + "accessibility": "public", + "kind": "property" + }, + { + "name": "MNEEEGHOGAB", + "type": "DIGMAIMMHAP", + "accessibility": "public", + "kind": "property" + }, + { + "name": "PHBCFAJILGD", + "type": "PJCALHOPMKJ", + "accessibility": "public", + "kind": "property" + } + ], + "fields": [], + "enumValues": [], + "wireKeys": [ + "ClubId", + "Name", + "Description", + "MainImageName", + "State", + "CreatorAccountId", + "Category", + "Visibility", + "Joinability", + "AllowJuniors", + "MemberCount", + "IsRRO", + "ClubType" + ], + "isilFile": "PLILLKHMNDA.txt", + "isilType": "PLILLKHMNDA" + } + } + } + ], + "contracts": [ + { + "kind": "success-or-empty", + "raw": "RecRoom.Async.IPromise", + "rootType": "void/success", + "typeDescription": { + "name": "void/success", + "found": false, + "kind": "object", + "wireKeys": [] + } + }, + { + "kind": "object", + "raw": "PLILLKHMNDA", + "rootType": "PLILLKHMNDA", + "typeDescription": { + "name": "PLILLKHMNDA", + "found": true, + "file": "PLILLKHMNDA.cs", + "declaration": "public class PLILLKHMNDA : IFAIJAGLDFK, IEquatable", + "kind": "class", + "baseType": "IFAIJAGLDFK", + "properties": [ + { + "name": "BADIGBCKECA", + "type": "int", + "accessibility": "public", + "kind": "property" + }, + { + "name": "CCGOEDABKNN", + "type": "long", + "accessibility": "public", + "kind": "property" + }, + { + "name": "CDINMMPNAID", + "type": "JCMLDDKFKEO", + "accessibility": "public", + "kind": "property" + }, + { + "name": "CDNFGMHLDMJ", + "type": "bool", + "accessibility": "public", + "kind": "property" + }, + { + "name": "EEAOJCGAOCN", + "type": "int", + "accessibility": "public", + "kind": "property" + }, + { + "name": "EHOLKJPEGFF", + "type": "string", + "accessibility": "public", + "kind": "property" + }, + { + "name": "EPGALOMHHMI", + "type": "JCDEFCJLCHN", + "accessibility": "public", + "kind": "property" + }, + { + "name": "FIKEBGGCDFN", + "type": "string", + "accessibility": "public", + "kind": "property" + }, + { + "name": "HHCGNCLFKDM", + "type": "Nullable", + "accessibility": "public", + "kind": "property" + }, + { + "name": "IPKLLFAJJPJ", + "type": "bool", + "accessibility": "public", + "kind": "property" + }, + { + "name": "KODBEJPEFOJ", + "type": "string", + "accessibility": "public", + "kind": "property" + }, + { + "name": "LNGPBGCIAPP", + "type": "string", + "accessibility": "public", + "kind": "property" + }, + { + "name": "MNEEEGHOGAB", + "type": "DIGMAIMMHAP", + "accessibility": "public", + "kind": "property" + }, + { + "name": "PHBCFAJILGD", + "type": "PJCALHOPMKJ", + "accessibility": "public", + "kind": "property" + } + ], + "fields": [], + "enumValues": [], + "wireKeys": [ + "ClubId", + "Name", + "Description", + "MainImageName", + "State", + "CreatorAccountId", + "Category", + "Visibility", + "Joinability", + "AllowJuniors", + "MemberCount", + "IsRRO", + "ClubType" + ], + "isilFile": "PLILLKHMNDA.txt", + "isilType": "PLILLKHMNDA" + } + } + ] + }, + { + "family": "clubs", + "route": "club/{0}/additionalimage/{1}", + "evidence": [ + { + "type": "JDJGIBLMFKK", + "method": "RecRoom.Async.IPromise`1 ELLIKHHEJFM(System.Int64 AOMBLGBCENO, System.Int32 EFBDCIJMFGD, System.String HFLPBHHAFIO)", + "file": "JDJGIBLMFKK.txt", + "line": 12298, + "contract": { + "kind": "object", + "raw": "PIHMJGCGNLP", + "rootType": "PIHMJGCGNLP", + "typeDescription": { + "name": "PIHMJGCGNLP", + "found": true, + "file": "PIHMJGCGNLP.cs", + "declaration": "public class PIHMJGCGNLP : IFAIJAGLDFK", + "kind": "class", + "baseType": "IFAIJAGLDFK", + "properties": [ + { + "name": "AHDBBFIDKBN", + "type": "PPGPAHNMGEC", + "accessibility": "public", + "kind": "property" + }, + { + "name": "CMGPCPKLHLF", + "type": "JHEEFBMODPG", + "accessibility": "public", + "kind": "property" + }, + { + "name": "DOOHAKMALHL", + "type": "List", + "accessibility": "public", + "kind": "property" + }, + { + "name": "IIIFDCAPMEA", + "type": "JHEEFBMODPG", + "accessibility": "public", + "kind": "property" + }, + { + "name": "KIFHEKPKILL", + "type": "JHEEFBMODPG", + "accessibility": "public", + "kind": "property" + }, + { + "name": "NDAGAGNHNPA", + "type": "PLILLKHMNDA", + "accessibility": "public", + "kind": "property" + }, + { + "name": "NJHNHHMCILD", + "type": "JHEEFBMODPG", + "accessibility": "public", + "kind": "property" + }, + { + "name": "OPIIBPFEODL", + "type": "List", + "accessibility": "public", + "kind": "property" + } + ], + "fields": [], + "enumValues": [], + "wireKeys": [ + "Club", + "CoownerPermissions", + "ModeratorPermissions", + "MemberPermissions", + "MyMembershipType" + ], + "isilFile": "PIHMJGCGNLP.txt", + "isilType": "PIHMJGCGNLP" + } + } + } + ], + "contracts": [ + { + "kind": "object", + "raw": "PIHMJGCGNLP", + "rootType": "PIHMJGCGNLP", + "typeDescription": { + "name": "PIHMJGCGNLP", + "found": true, + "file": "PIHMJGCGNLP.cs", + "declaration": "public class PIHMJGCGNLP : IFAIJAGLDFK", + "kind": "class", + "baseType": "IFAIJAGLDFK", + "properties": [ + { + "name": "AHDBBFIDKBN", + "type": "PPGPAHNMGEC", + "accessibility": "public", + "kind": "property" + }, + { + "name": "CMGPCPKLHLF", + "type": "JHEEFBMODPG", + "accessibility": "public", + "kind": "property" + }, + { + "name": "DOOHAKMALHL", + "type": "List", + "accessibility": "public", + "kind": "property" + }, + { + "name": "IIIFDCAPMEA", + "type": "JHEEFBMODPG", + "accessibility": "public", + "kind": "property" + }, + { + "name": "KIFHEKPKILL", + "type": "JHEEFBMODPG", + "accessibility": "public", + "kind": "property" + }, + { + "name": "NDAGAGNHNPA", + "type": "PLILLKHMNDA", + "accessibility": "public", + "kind": "property" + }, + { + "name": "NJHNHHMCILD", + "type": "JHEEFBMODPG", + "accessibility": "public", + "kind": "property" + }, + { + "name": "OPIIBPFEODL", + "type": "List", + "accessibility": "public", + "kind": "property" + } + ], + "fields": [], + "enumValues": [], + "wireKeys": [ + "Club", + "CoownerPermissions", + "ModeratorPermissions", + "MemberPermissions", + "MyMembershipType" + ], + "isilFile": "PIHMJGCGNLP.txt", + "isilType": "PIHMJGCGNLP" + } + } + ] + }, + { + "family": "clubs", + "route": "club/{0}/clubhouse", + "evidence": [ + { + "type": "JDJGIBLMFKK", + "method": "RecRoom.Async.IPromise`1 HELJLMINDFD(System.Int64 AOMBLGBCENO, System.Nullable`1 HNHLJONGKHB)", + "file": "JDJGIBLMFKK.txt", + "line": 12449, + "contract": { + "kind": "object", + "raw": "PIHMJGCGNLP", + "rootType": "PIHMJGCGNLP", + "typeDescription": { + "name": "PIHMJGCGNLP", + "found": true, + "file": "PIHMJGCGNLP.cs", + "declaration": "public class PIHMJGCGNLP : IFAIJAGLDFK", + "kind": "class", + "baseType": "IFAIJAGLDFK", + "properties": [ + { + "name": "AHDBBFIDKBN", + "type": "PPGPAHNMGEC", + "accessibility": "public", + "kind": "property" + }, + { + "name": "CMGPCPKLHLF", + "type": "JHEEFBMODPG", + "accessibility": "public", + "kind": "property" + }, + { + "name": "DOOHAKMALHL", + "type": "List", + "accessibility": "public", + "kind": "property" + }, + { + "name": "IIIFDCAPMEA", + "type": "JHEEFBMODPG", + "accessibility": "public", + "kind": "property" + }, + { + "name": "KIFHEKPKILL", + "type": "JHEEFBMODPG", + "accessibility": "public", + "kind": "property" + }, + { + "name": "NDAGAGNHNPA", + "type": "PLILLKHMNDA", + "accessibility": "public", + "kind": "property" + }, + { + "name": "NJHNHHMCILD", + "type": "JHEEFBMODPG", + "accessibility": "public", + "kind": "property" + }, + { + "name": "OPIIBPFEODL", + "type": "List", + "accessibility": "public", + "kind": "property" + } + ], + "fields": [], + "enumValues": [], + "wireKeys": [ + "Club", + "CoownerPermissions", + "ModeratorPermissions", + "MemberPermissions", + "MyMembershipType" + ], + "isilFile": "PIHMJGCGNLP.txt", + "isilType": "PIHMJGCGNLP" + } + } + } + ], + "contracts": [ + { + "kind": "object", + "raw": "PIHMJGCGNLP", + "rootType": "PIHMJGCGNLP", + "typeDescription": { + "name": "PIHMJGCGNLP", + "found": true, + "file": "PIHMJGCGNLP.cs", + "declaration": "public class PIHMJGCGNLP : IFAIJAGLDFK", + "kind": "class", + "baseType": "IFAIJAGLDFK", + "properties": [ + { + "name": "AHDBBFIDKBN", + "type": "PPGPAHNMGEC", + "accessibility": "public", + "kind": "property" + }, + { + "name": "CMGPCPKLHLF", + "type": "JHEEFBMODPG", + "accessibility": "public", + "kind": "property" + }, + { + "name": "DOOHAKMALHL", + "type": "List", + "accessibility": "public", + "kind": "property" + }, + { + "name": "IIIFDCAPMEA", + "type": "JHEEFBMODPG", + "accessibility": "public", + "kind": "property" + }, + { + "name": "KIFHEKPKILL", + "type": "JHEEFBMODPG", + "accessibility": "public", + "kind": "property" + }, + { + "name": "NDAGAGNHNPA", + "type": "PLILLKHMNDA", + "accessibility": "public", + "kind": "property" + }, + { + "name": "NJHNHHMCILD", + "type": "JHEEFBMODPG", + "accessibility": "public", + "kind": "property" + }, + { + "name": "OPIIBPFEODL", + "type": "List", + "accessibility": "public", + "kind": "property" + } + ], + "fields": [], + "enumValues": [], + "wireKeys": [ + "Club", + "CoownerPermissions", + "ModeratorPermissions", + "MemberPermissions", + "MyMembershipType" + ], + "isilFile": "PIHMJGCGNLP.txt", + "isilType": "PIHMJGCGNLP" + } + } + ] + }, + { + "family": "clubs", + "route": "club/{0}/details", + "evidence": [ + { + "type": "JDJGIBLMFKK+BKCHBCIJHBN", + "method": "RecRoom.Async.IPromise`1 b__0()", + "file": "JDJGIBLMFKK_NestedType_BKCHBCIJHBN.txt", + "line": 202, + "contract": { + "kind": "object", + "raw": "PIHMJGCGNLP", + "rootType": "PIHMJGCGNLP", + "typeDescription": { + "name": "PIHMJGCGNLP", + "found": true, + "file": "PIHMJGCGNLP.cs", + "declaration": "public class PIHMJGCGNLP : IFAIJAGLDFK", + "kind": "class", + "baseType": "IFAIJAGLDFK", + "properties": [ + { + "name": "AHDBBFIDKBN", + "type": "PPGPAHNMGEC", + "accessibility": "public", + "kind": "property" + }, + { + "name": "CMGPCPKLHLF", + "type": "JHEEFBMODPG", + "accessibility": "public", + "kind": "property" + }, + { + "name": "DOOHAKMALHL", + "type": "List", + "accessibility": "public", + "kind": "property" + }, + { + "name": "IIIFDCAPMEA", + "type": "JHEEFBMODPG", + "accessibility": "public", + "kind": "property" + }, + { + "name": "KIFHEKPKILL", + "type": "JHEEFBMODPG", + "accessibility": "public", + "kind": "property" + }, + { + "name": "NDAGAGNHNPA", + "type": "PLILLKHMNDA", + "accessibility": "public", + "kind": "property" + }, + { + "name": "NJHNHHMCILD", + "type": "JHEEFBMODPG", + "accessibility": "public", + "kind": "property" + }, + { + "name": "OPIIBPFEODL", + "type": "List", + "accessibility": "public", + "kind": "property" + } + ], + "fields": [], + "enumValues": [], + "wireKeys": [ + "Club", + "CoownerPermissions", + "ModeratorPermissions", + "MemberPermissions", + "MyMembershipType" + ], + "isilFile": "PIHMJGCGNLP.txt", + "isilType": "PIHMJGCGNLP" + } + } + } + ], + "contracts": [ + { + "kind": "object", + "raw": "PIHMJGCGNLP", + "rootType": "PIHMJGCGNLP", + "typeDescription": { + "name": "PIHMJGCGNLP", + "found": true, + "file": "PIHMJGCGNLP.cs", + "declaration": "public class PIHMJGCGNLP : IFAIJAGLDFK", + "kind": "class", + "baseType": "IFAIJAGLDFK", + "properties": [ + { + "name": "AHDBBFIDKBN", + "type": "PPGPAHNMGEC", + "accessibility": "public", + "kind": "property" + }, + { + "name": "CMGPCPKLHLF", + "type": "JHEEFBMODPG", + "accessibility": "public", + "kind": "property" + }, + { + "name": "DOOHAKMALHL", + "type": "List", + "accessibility": "public", + "kind": "property" + }, + { + "name": "IIIFDCAPMEA", + "type": "JHEEFBMODPG", + "accessibility": "public", + "kind": "property" + }, + { + "name": "KIFHEKPKILL", + "type": "JHEEFBMODPG", + "accessibility": "public", + "kind": "property" + }, + { + "name": "NDAGAGNHNPA", + "type": "PLILLKHMNDA", + "accessibility": "public", + "kind": "property" + }, + { + "name": "NJHNHHMCILD", + "type": "JHEEFBMODPG", + "accessibility": "public", + "kind": "property" + }, + { + "name": "OPIIBPFEODL", + "type": "List", + "accessibility": "public", + "kind": "property" + } + ], + "fields": [], + "enumValues": [], + "wireKeys": [ + "Club", + "CoownerPermissions", + "ModeratorPermissions", + "MemberPermissions", + "MyMembershipType" + ], + "isilFile": "PIHMJGCGNLP.txt", + "isilType": "PIHMJGCGNLP" + } + } + ] + }, + { + "family": "clubs", + "route": "club/{0}/mainimage", + "evidence": [ + { + "type": "JDJGIBLMFKK", + "method": "RecRoom.Async.IPromise`1 OBFKAFGALDM(System.Int64 AOMBLGBCENO, System.String HFLPBHHAFIO)", + "file": "JDJGIBLMFKK.txt", + "line": 12138, + "contract": { + "kind": "object", + "raw": "PIHMJGCGNLP", + "rootType": "PIHMJGCGNLP", + "typeDescription": { + "name": "PIHMJGCGNLP", + "found": true, + "file": "PIHMJGCGNLP.cs", + "declaration": "public class PIHMJGCGNLP : IFAIJAGLDFK", + "kind": "class", + "baseType": "IFAIJAGLDFK", + "properties": [ + { + "name": "AHDBBFIDKBN", + "type": "PPGPAHNMGEC", + "accessibility": "public", + "kind": "property" + }, + { + "name": "CMGPCPKLHLF", + "type": "JHEEFBMODPG", + "accessibility": "public", + "kind": "property" + }, + { + "name": "DOOHAKMALHL", + "type": "List", + "accessibility": "public", + "kind": "property" + }, + { + "name": "IIIFDCAPMEA", + "type": "JHEEFBMODPG", + "accessibility": "public", + "kind": "property" + }, + { + "name": "KIFHEKPKILL", + "type": "JHEEFBMODPG", + "accessibility": "public", + "kind": "property" + }, + { + "name": "NDAGAGNHNPA", + "type": "PLILLKHMNDA", + "accessibility": "public", + "kind": "property" + }, + { + "name": "NJHNHHMCILD", + "type": "JHEEFBMODPG", + "accessibility": "public", + "kind": "property" + }, + { + "name": "OPIIBPFEODL", + "type": "List", + "accessibility": "public", + "kind": "property" + } + ], + "fields": [], + "enumValues": [], + "wireKeys": [ + "Club", + "CoownerPermissions", + "ModeratorPermissions", + "MemberPermissions", + "MyMembershipType" + ], + "isilFile": "PIHMJGCGNLP.txt", + "isilType": "PIHMJGCGNLP" + } + } + } + ], + "contracts": [ + { + "kind": "object", + "raw": "PIHMJGCGNLP", + "rootType": "PIHMJGCGNLP", + "typeDescription": { + "name": "PIHMJGCGNLP", + "found": true, + "file": "PIHMJGCGNLP.cs", + "declaration": "public class PIHMJGCGNLP : IFAIJAGLDFK", + "kind": "class", + "baseType": "IFAIJAGLDFK", + "properties": [ + { + "name": "AHDBBFIDKBN", + "type": "PPGPAHNMGEC", + "accessibility": "public", + "kind": "property" + }, + { + "name": "CMGPCPKLHLF", + "type": "JHEEFBMODPG", + "accessibility": "public", + "kind": "property" + }, + { + "name": "DOOHAKMALHL", + "type": "List", + "accessibility": "public", + "kind": "property" + }, + { + "name": "IIIFDCAPMEA", + "type": "JHEEFBMODPG", + "accessibility": "public", + "kind": "property" + }, + { + "name": "KIFHEKPKILL", + "type": "JHEEFBMODPG", + "accessibility": "public", + "kind": "property" + }, + { + "name": "NDAGAGNHNPA", + "type": "PLILLKHMNDA", + "accessibility": "public", + "kind": "property" + }, + { + "name": "NJHNHHMCILD", + "type": "JHEEFBMODPG", + "accessibility": "public", + "kind": "property" + }, + { + "name": "OPIIBPFEODL", + "type": "List", + "accessibility": "public", + "kind": "property" + } + ], + "fields": [], + "enumValues": [], + "wireKeys": [ + "Club", + "CoownerPermissions", + "ModeratorPermissions", + "MemberPermissions", + "MyMembershipType" + ], + "isilFile": "PIHMJGCGNLP.txt", + "isilType": "PIHMJGCGNLP" + } + } + ] + }, + { + "family": "clubs", + "route": "club/{0}/members/{1}", + "evidence": [ + { + "type": "JDJGIBLMFKK", + "method": "RecRoom.Async.IPromise`1 BIOJPNFPNCE(System.Int64 AOMBLGBCENO, System.Int32 GKLPIFBPGOD)", + "file": "JDJGIBLMFKK.txt", + "line": 15776, + "contract": { + "kind": "object", + "raw": "MFOAODGNGKB", + "rootType": "MFOAODGNGKB", + "typeDescription": { + "name": "MFOAODGNGKB", + "found": true, + "file": "MFOAODGNGKB.cs", + "declaration": "public class MFOAODGNGKB : IFAIJAGLDFK", + "kind": "class", + "baseType": "IFAIJAGLDFK", + "properties": [ + { + "name": "ACBFDMLHFPB", + "type": "DateTime", + "accessibility": "public", + "kind": "property" + }, + { + "name": "CCGOEDABKNN", + "type": "long", + "accessibility": "public", + "kind": "property" + }, + { + "name": "GAINIOENNCG", + "type": "int", + "accessibility": "public", + "kind": "property" + }, + { + "name": "JNMPJNKEJAC", + "type": "PPGPAHNMGEC", + "accessibility": "public", + "kind": "property" + } + ], + "fields": [], + "enumValues": [], + "wireKeys": [ + "AccountId", + "ClubId", + "MembershipType", + "CreatedAt" + ], + "isilFile": "MFOAODGNGKB.txt", + "isilType": "MFOAODGNGKB" + } + } + } + ], + "contracts": [ + { + "kind": "object", + "raw": "MFOAODGNGKB", + "rootType": "MFOAODGNGKB", + "typeDescription": { + "name": "MFOAODGNGKB", + "found": true, + "file": "MFOAODGNGKB.cs", + "declaration": "public class MFOAODGNGKB : IFAIJAGLDFK", + "kind": "class", + "baseType": "IFAIJAGLDFK", + "properties": [ + { + "name": "ACBFDMLHFPB", + "type": "DateTime", + "accessibility": "public", + "kind": "property" + }, + { + "name": "CCGOEDABKNN", + "type": "long", + "accessibility": "public", + "kind": "property" + }, + { + "name": "GAINIOENNCG", + "type": "int", + "accessibility": "public", + "kind": "property" + }, + { + "name": "JNMPJNKEJAC", + "type": "PPGPAHNMGEC", + "accessibility": "public", + "kind": "property" + } + ], + "fields": [], + "enumValues": [], + "wireKeys": [ + "AccountId", + "ClubId", + "MembershipType", + "CreatedAt" + ], + "isilFile": "MFOAODGNGKB.txt", + "isilType": "MFOAODGNGKB" + } + } + ] + }, + { + "family": "clubs", + "route": "club/{0}/members/acceptinvite", + "evidence": [ + { + "type": "JDGDFALBCDJ", + "method": "RecRoom.Async.IPromise HHLPPOBHMOC()", + "file": "JDGDFALBCDJ.txt", + "line": 426, + "contract": { + "kind": "success-or-empty", + "raw": "RecRoom.Async.IPromise", + "rootType": "void/success", + "typeDescription": { + "name": "void/success", + "found": false, + "kind": "object", + "wireKeys": [] + } + } + }, + { + "type": "JDJGIBLMFKK", + "method": "RecRoom.Async.IPromise FAOINNCBNFC(System.Int64 AOMBLGBCENO)", + "file": "JDJGIBLMFKK.txt", + "line": 20586, + "contract": { + "kind": "success-or-empty", + "raw": "RecRoom.Async.IPromise", + "rootType": "void/success", + "typeDescription": { + "name": "void/success", + "found": false, + "kind": "object", + "wireKeys": [] + } + } + } + ], + "contracts": [ + { + "kind": "success-or-empty", + "raw": "RecRoom.Async.IPromise", + "rootType": "void/success", + "typeDescription": { + "name": "void/success", + "found": false, + "kind": "object", + "wireKeys": [] + } + } + ] + }, + { + "family": "clubs", + "route": "club/{0}/members/acceptrequest", + "evidence": [ + { + "type": "JDJGIBLMFKK", + "method": "RecRoom.Async.IPromise AGEGBKKPJNN(System.Int64 AOMBLGBCENO, System.Int32 GKLPIFBPGOD)", + "file": "JDJGIBLMFKK.txt", + "line": 18418, + "contract": { + "kind": "success-or-empty", + "raw": "RecRoom.Async.IPromise", + "rootType": "void/success", + "typeDescription": { + "name": "void/success", + "found": false, + "kind": "object", + "wireKeys": [] + } + } + } + ], + "contracts": [ + { + "kind": "success-or-empty", + "raw": "RecRoom.Async.IPromise", + "rootType": "void/success", + "typeDescription": { + "name": "void/success", + "found": false, + "kind": "object", + "wireKeys": [] + } + } + ] + }, + { + "family": "clubs", + "route": "club/{0}/members/acceptrequests", + "evidence": [ + { + "type": "JDJGIBLMFKK", + "method": "RecRoom.Async.IPromise FKPIMLCILLI(System.Int64 AOMBLGBCENO, System.Collections.Generic.IEnumerable`1 ILNGMAANNDG)", + "file": "JDJGIBLMFKK.txt", + "line": 18831, + "contract": { + "kind": "success-or-empty", + "raw": "RecRoom.Async.IPromise", + "rootType": "void/success", + "typeDescription": { + "name": "void/success", + "found": false, + "kind": "object", + "wireKeys": [] + } + } + } + ], + "contracts": [ + { + "kind": "success-or-empty", + "raw": "RecRoom.Async.IPromise", + "rootType": "void/success", + "typeDescription": { + "name": "void/success", + "found": false, + "kind": "object", + "wireKeys": [] + } + } + ] + }, + { + "family": "clubs", + "route": "club/{0}/members/ban", + "evidence": [ + { + "type": "JDJGIBLMFKK", + "method": "RecRoom.Async.IPromise OKICAHCMPFL(System.Int64 AOMBLGBCENO, System.Int32 GKLPIFBPGOD)", + "file": "JDJGIBLMFKK.txt", + "line": 21265, + "contract": { + "kind": "success-or-empty", + "raw": "RecRoom.Async.IPromise", + "rootType": "void/success", + "typeDescription": { + "name": "void/success", + "found": false, + "kind": "object", + "wireKeys": [] + } + } + } + ], + "contracts": [ + { + "kind": "success-or-empty", + "raw": "RecRoom.Async.IPromise", + "rootType": "void/success", + "typeDescription": { + "name": "void/success", + "found": false, + "kind": "object", + "wireKeys": [] + } + } + ] + }, + { + "family": "clubs", + "route": "club/{0}/members/changetype", + "evidence": [ + { + "type": "JDJGIBLMFKK", + "method": "RecRoom.Async.IPromise JLBCCEIBFJJ(System.Int64 AOMBLGBCENO, System.Int32 GKLPIFBPGOD, PPGPAHNMGEC OCHEGLOFMEA)", + "file": "JDJGIBLMFKK.txt", + "line": 21094, + "contract": { + "kind": "success-or-empty", + "raw": "RecRoom.Async.IPromise", + "rootType": "void/success", + "typeDescription": { + "name": "void/success", + "found": false, + "kind": "object", + "wireKeys": [] + } + } + } + ], + "contracts": [ + { + "kind": "success-or-empty", + "raw": "RecRoom.Async.IPromise", + "rootType": "void/success", + "typeDescription": { + "name": "void/success", + "found": false, + "kind": "object", + "wireKeys": [] + } + } + ] + }, + { + "family": "clubs", + "route": "club/{0}/members/declineinvite", + "evidence": [ + { + "type": "JDGDFALBCDJ", + "method": "RecRoom.Async.IPromise BDFBMBCKJEP()", + "file": "JDGDFALBCDJ.txt", + "line": 697, + "contract": { + "kind": "success-or-empty", + "raw": "RecRoom.Async.IPromise", + "rootType": "void/success", + "typeDescription": { + "name": "void/success", + "found": false, + "kind": "object", + "wireKeys": [] + } + } + }, + { + "type": "JDJGIBLMFKK", + "method": "RecRoom.Async.IPromise GJJBKCEMLDC(System.Int64 AOMBLGBCENO)", + "file": "JDJGIBLMFKK.txt", + "line": 20675, + "contract": { + "kind": "success-or-empty", + "raw": "RecRoom.Async.IPromise", + "rootType": "void/success", + "typeDescription": { + "name": "void/success", + "found": false, + "kind": "object", + "wireKeys": [] + } + } + } + ], + "contracts": [ + { + "kind": "success-or-empty", + "raw": "RecRoom.Async.IPromise", + "rootType": "void/success", + "typeDescription": { + "name": "void/success", + "found": false, + "kind": "object", + "wireKeys": [] + } + } + ] + }, + { + "family": "clubs", + "route": "club/{0}/members/denyrequest", + "evidence": [ + { + "type": "JDJGIBLMFKK", + "method": "RecRoom.Async.IPromise LPCHODIHIBB(System.Int64 AOMBLGBCENO, System.Int32 GKLPIFBPGOD)", + "file": "JDJGIBLMFKK.txt", + "line": 19048, + "contract": { + "kind": "success-or-empty", + "raw": "RecRoom.Async.IPromise", + "rootType": "void/success", + "typeDescription": { + "name": "void/success", + "found": false, + "kind": "object", + "wireKeys": [] + } + } + } + ], + "contracts": [ + { + "kind": "success-or-empty", + "raw": "RecRoom.Async.IPromise", + "rootType": "void/success", + "typeDescription": { + "name": "void/success", + "found": false, + "kind": "object", + "wireKeys": [] + } + } + ] + }, + { + "family": "clubs", + "route": "club/{0}/members/denyrequests", + "evidence": [ + { + "type": "JDJGIBLMFKK", + "method": "RecRoom.Async.IPromise CJBOECCIIJB(System.Int64 AOMBLGBCENO, System.Collections.Generic.IEnumerable`1 ILNGMAANNDG)", + "file": "JDJGIBLMFKK.txt", + "line": 19461, + "contract": { + "kind": "success-or-empty", + "raw": "RecRoom.Async.IPromise", + "rootType": "void/success", + "typeDescription": { + "name": "void/success", + "found": false, + "kind": "object", + "wireKeys": [] + } + } + } + ], + "contracts": [ + { + "kind": "success-or-empty", + "raw": "RecRoom.Async.IPromise", + "rootType": "void/success", + "typeDescription": { + "name": "void/success", + "found": false, + "kind": "object", + "wireKeys": [] + } + } + ] + }, + { + "family": "clubs", + "route": "club/{0}/members/directJoin", + "evidence": [ + { + "type": "JDJGIBLMFKK", + "method": "RecRoom.Async.IPromise AJCDOLIBBKC(System.Int64 AOMBLGBCENO, PJCALHOPMKJ IEGCAIGJBBP, System.Int32 ENGEEKIMIGO)", + "file": "JDJGIBLMFKK.txt", + "line": 20450, + "contract": { + "kind": "success-or-empty", + "raw": "RecRoom.Async.IPromise", + "rootType": "void/success", + "typeDescription": { + "name": "void/success", + "found": false, + "kind": "object", + "wireKeys": [] + } + } + } + ], + "contracts": [ + { + "kind": "success-or-empty", + "raw": "RecRoom.Async.IPromise", + "rootType": "void/success", + "typeDescription": { + "name": "void/success", + "found": false, + "kind": "object", + "wireKeys": [] + } + } + ] + }, + { + "family": "clubs", + "route": "club/{0}/members/invite", + "evidence": [ + { + "type": "JDJGIBLMFKK", + "method": "RecRoom.Async.IPromise FEIADJMHICD(System.Int64 AOMBLGBCENO, System.Int32 GKLPIFBPGOD, PPGPAHNMGEC PGMGFKOCEDG)", + "file": "JDJGIBLMFKK.txt", + "line": 19703, + "contract": { + "kind": "success-or-empty", + "raw": "RecRoom.Async.IPromise", + "rootType": "void/success", + "typeDescription": { + "name": "void/success", + "found": false, + "kind": "object", + "wireKeys": [] + } + } + } + ], + "contracts": [ + { + "kind": "success-or-empty", + "raw": "RecRoom.Async.IPromise", + "rootType": "void/success", + "typeDescription": { + "name": "void/success", + "found": false, + "kind": "object", + "wireKeys": [] + } + } + ] + }, + { + "family": "clubs", + "route": "club/{0}/members/invitemembers", + "evidence": [ + { + "type": "JDJGIBLMFKK", + "method": "RecRoom.Async.IPromise HOHPKNPHCFK(System.Int64 AOMBLGBCENO, System.Collections.Generic.List`1 ILNGMAANNDG, OFIEEDOMGPA NAMECJCFEDN)", + "file": "JDJGIBLMFKK.txt", + "line": 20070, + "contract": { + "kind": "success-or-empty", + "raw": "RecRoom.Async.IPromise", + "rootType": "void/success", + "typeDescription": { + "name": "void/success", + "found": false, + "kind": "object", + "wireKeys": [] + } + } + } + ], + "contracts": [ + { + "kind": "success-or-empty", + "raw": "RecRoom.Async.IPromise", + "rootType": "void/success", + "typeDescription": { + "name": "void/success", + "found": false, + "kind": "object", + "wireKeys": [] + } + } + ] + }, + { + "family": "clubs", + "route": "club/{0}/members/leave", + "evidence": [ + { + "type": "JDJGIBLMFKK", + "method": "RecRoom.Async.IPromise EBADBEIGOEN(System.Int64 AOMBLGBCENO)", + "file": "JDJGIBLMFKK.txt", + "line": 20745, + "contract": { + "kind": "success-or-empty", + "raw": "RecRoom.Async.IPromise", + "rootType": "void/success", + "typeDescription": { + "name": "void/success", + "found": false, + "kind": "object", + "wireKeys": [] + } + } + } + ], + "contracts": [ + { + "kind": "success-or-empty", + "raw": "RecRoom.Async.IPromise", + "rootType": "void/success", + "typeDescription": { + "name": "void/success", + "found": false, + "kind": "object", + "wireKeys": [] + } + } + ] + }, + { + "family": "clubs", + "route": "club/{0}/members/remove", + "evidence": [ + { + "type": "JDJGIBLMFKK", + "method": "RecRoom.Async.IPromise LDOGPIHJOLP(System.Int64 AOMBLGBCENO, System.Int32 GKLPIFBPGOD)", + "file": "JDJGIBLMFKK.txt", + "line": 20895, + "contract": { + "kind": "success-or-empty", + "raw": "RecRoom.Async.IPromise", + "rootType": "void/success", + "typeDescription": { + "name": "void/success", + "found": false, + "kind": "object", + "wireKeys": [] + } + } + } + ], + "contracts": [ + { + "kind": "success-or-empty", + "raw": "RecRoom.Async.IPromise", + "rootType": "void/success", + "typeDescription": { + "name": "void/success", + "found": false, + "kind": "object", + "wireKeys": [] + } + } + ] + }, + { + "family": "clubs", + "route": "club/{0}/members/requesttojoin", + "evidence": [ + { + "type": "JDJGIBLMFKK", + "method": "RecRoom.Async.IPromise NJKBIMEEAPE(System.Int64 AOMBLGBCENO)", + "file": "JDJGIBLMFKK.txt", + "line": 18249, + "contract": { + "kind": "success-or-empty", + "raw": "RecRoom.Async.IPromise", + "rootType": "void/success", + "typeDescription": { + "name": "void/success", + "found": false, + "kind": "object", + "wireKeys": [] + } + } + } + ], + "contracts": [ + { + "kind": "success-or-empty", + "raw": "RecRoom.Async.IPromise", + "rootType": "void/success", + "typeDescription": { + "name": "void/success", + "found": false, + "kind": "object", + "wireKeys": [] + } + } + ] + }, + { + "family": "clubs", + "route": "club/{0}/members/unban", + "evidence": [ + { + "type": "JDJGIBLMFKK", + "method": "RecRoom.Async.IPromise OGMGOBHPMFC(System.Int64 AOMBLGBCENO, System.Int32 GKLPIFBPGOD)", + "file": "JDJGIBLMFKK.txt", + "line": 21435, + "contract": { + "kind": "success-or-empty", + "raw": "RecRoom.Async.IPromise", + "rootType": "void/success", + "typeDescription": { + "name": "void/success", + "found": false, + "kind": "object", + "wireKeys": [] + } + } + } + ], + "contracts": [ + { + "kind": "success-or-empty", + "raw": "RecRoom.Async.IPromise", + "rootType": "void/success", + "typeDescription": { + "name": "void/success", + "found": false, + "kind": "object", + "wireKeys": [] + } + } + ] + }, + { + "family": "clubs", + "route": "club/{0}/modify", + "evidence": [ + { + "type": "JDJGIBLMFKK", + "method": "RecRoom.Async.IPromise`1 CKJIKPOIMFE(JDJGIBLMFKK+ACNILMIFDJJ JMFLHIIJFKL)", + "file": "JDJGIBLMFKK.txt", + "line": 11620, + "contract": { + "kind": "object", + "raw": "PIHMJGCGNLP", + "rootType": "PIHMJGCGNLP", + "typeDescription": { + "name": "PIHMJGCGNLP", + "found": true, + "file": "PIHMJGCGNLP.cs", + "declaration": "public class PIHMJGCGNLP : IFAIJAGLDFK", + "kind": "class", + "baseType": "IFAIJAGLDFK", + "properties": [ + { + "name": "AHDBBFIDKBN", + "type": "PPGPAHNMGEC", + "accessibility": "public", + "kind": "property" + }, + { + "name": "CMGPCPKLHLF", + "type": "JHEEFBMODPG", + "accessibility": "public", + "kind": "property" + }, + { + "name": "DOOHAKMALHL", + "type": "List", + "accessibility": "public", + "kind": "property" + }, + { + "name": "IIIFDCAPMEA", + "type": "JHEEFBMODPG", + "accessibility": "public", + "kind": "property" + }, + { + "name": "KIFHEKPKILL", + "type": "JHEEFBMODPG", + "accessibility": "public", + "kind": "property" + }, + { + "name": "NDAGAGNHNPA", + "type": "PLILLKHMNDA", + "accessibility": "public", + "kind": "property" + }, + { + "name": "NJHNHHMCILD", + "type": "JHEEFBMODPG", + "accessibility": "public", + "kind": "property" + }, + { + "name": "OPIIBPFEODL", + "type": "List", + "accessibility": "public", + "kind": "property" + } + ], + "fields": [], + "enumValues": [], + "wireKeys": [ + "Club", + "CoownerPermissions", + "ModeratorPermissions", + "MemberPermissions", + "MyMembershipType" + ], + "isilFile": "PIHMJGCGNLP.txt", + "isilType": "PIHMJGCGNLP" + } + } + } + ], + "contracts": [ + { + "kind": "object", + "raw": "PIHMJGCGNLP", + "rootType": "PIHMJGCGNLP", + "typeDescription": { + "name": "PIHMJGCGNLP", + "found": true, + "file": "PIHMJGCGNLP.cs", + "declaration": "public class PIHMJGCGNLP : IFAIJAGLDFK", + "kind": "class", + "baseType": "IFAIJAGLDFK", + "properties": [ + { + "name": "AHDBBFIDKBN", + "type": "PPGPAHNMGEC", + "accessibility": "public", + "kind": "property" + }, + { + "name": "CMGPCPKLHLF", + "type": "JHEEFBMODPG", + "accessibility": "public", + "kind": "property" + }, + { + "name": "DOOHAKMALHL", + "type": "List", + "accessibility": "public", + "kind": "property" + }, + { + "name": "IIIFDCAPMEA", + "type": "JHEEFBMODPG", + "accessibility": "public", + "kind": "property" + }, + { + "name": "KIFHEKPKILL", + "type": "JHEEFBMODPG", + "accessibility": "public", + "kind": "property" + }, + { + "name": "NDAGAGNHNPA", + "type": "PLILLKHMNDA", + "accessibility": "public", + "kind": "property" + }, + { + "name": "NJHNHHMCILD", + "type": "JHEEFBMODPG", + "accessibility": "public", + "kind": "property" + }, + { + "name": "OPIIBPFEODL", + "type": "List", + "accessibility": "public", + "kind": "property" + } + ], + "fields": [], + "enumValues": [], + "wireKeys": [ + "Club", + "CoownerPermissions", + "ModeratorPermissions", + "MemberPermissions", + "MyMembershipType" + ], + "isilFile": "PIHMJGCGNLP.txt", + "isilType": "PIHMJGCGNLP" + } + } + ] + }, + { + "family": "clubs", + "route": "club/{0}/modifydetails", + "evidence": [ + { + "type": "JDJGIBLMFKK", + "method": "RecRoom.Async.IPromise`1 KOPAANDJNKB(JDJGIBLMFKK+FHFOCPGBMPB JMFLHIIJFKL)", + "file": "JDJGIBLMFKK.txt", + "line": 12001, + "contract": { + "kind": "object", + "raw": "PIHMJGCGNLP", + "rootType": "PIHMJGCGNLP", + "typeDescription": { + "name": "PIHMJGCGNLP", + "found": true, + "file": "PIHMJGCGNLP.cs", + "declaration": "public class PIHMJGCGNLP : IFAIJAGLDFK", + "kind": "class", + "baseType": "IFAIJAGLDFK", + "properties": [ + { + "name": "AHDBBFIDKBN", + "type": "PPGPAHNMGEC", + "accessibility": "public", + "kind": "property" + }, + { + "name": "CMGPCPKLHLF", + "type": "JHEEFBMODPG", + "accessibility": "public", + "kind": "property" + }, + { + "name": "DOOHAKMALHL", + "type": "List", + "accessibility": "public", + "kind": "property" + }, + { + "name": "IIIFDCAPMEA", + "type": "JHEEFBMODPG", + "accessibility": "public", + "kind": "property" + }, + { + "name": "KIFHEKPKILL", + "type": "JHEEFBMODPG", + "accessibility": "public", + "kind": "property" + }, + { + "name": "NDAGAGNHNPA", + "type": "PLILLKHMNDA", + "accessibility": "public", + "kind": "property" + }, + { + "name": "NJHNHHMCILD", + "type": "JHEEFBMODPG", + "accessibility": "public", + "kind": "property" + }, + { + "name": "OPIIBPFEODL", + "type": "List", + "accessibility": "public", + "kind": "property" + } + ], + "fields": [], + "enumValues": [], + "wireKeys": [ + "Club", + "CoownerPermissions", + "ModeratorPermissions", + "MemberPermissions", + "MyMembershipType" + ], + "isilFile": "PIHMJGCGNLP.txt", + "isilType": "PIHMJGCGNLP" + } + } + } + ], + "contracts": [ + { + "kind": "object", + "raw": "PIHMJGCGNLP", + "rootType": "PIHMJGCGNLP", + "typeDescription": { + "name": "PIHMJGCGNLP", + "found": true, + "file": "PIHMJGCGNLP.cs", + "declaration": "public class PIHMJGCGNLP : IFAIJAGLDFK", + "kind": "class", + "baseType": "IFAIJAGLDFK", + "properties": [ + { + "name": "AHDBBFIDKBN", + "type": "PPGPAHNMGEC", + "accessibility": "public", + "kind": "property" + }, + { + "name": "CMGPCPKLHLF", + "type": "JHEEFBMODPG", + "accessibility": "public", + "kind": "property" + }, + { + "name": "DOOHAKMALHL", + "type": "List", + "accessibility": "public", + "kind": "property" + }, + { + "name": "IIIFDCAPMEA", + "type": "JHEEFBMODPG", + "accessibility": "public", + "kind": "property" + }, + { + "name": "KIFHEKPKILL", + "type": "JHEEFBMODPG", + "accessibility": "public", + "kind": "property" + }, + { + "name": "NDAGAGNHNPA", + "type": "PLILLKHMNDA", + "accessibility": "public", + "kind": "property" + }, + { + "name": "NJHNHHMCILD", + "type": "JHEEFBMODPG", + "accessibility": "public", + "kind": "property" + }, + { + "name": "OPIIBPFEODL", + "type": "List", + "accessibility": "public", + "kind": "property" + } + ], + "fields": [], + "enumValues": [], + "wireKeys": [ + "Club", + "CoownerPermissions", + "ModeratorPermissions", + "MemberPermissions", + "MyMembershipType" + ], + "isilFile": "PIHMJGCGNLP.txt", + "isilType": "PIHMJGCGNLP" + } + } + ] + }, + { + "family": "clubs", + "route": "club/{0}/permissions/{1}", + "evidence": [ + { + "type": "JDJGIBLMFKK", + "method": "RecRoom.Async.IPromise`1 GHBJEDCEKLK(JHEEFBMODPG OBJEOMAGODL)", + "file": "JDJGIBLMFKK.txt", + "line": 13631, + "contract": { + "kind": "object", + "raw": "PIHMJGCGNLP", + "rootType": "PIHMJGCGNLP", + "typeDescription": { + "name": "PIHMJGCGNLP", + "found": true, + "file": "PIHMJGCGNLP.cs", + "declaration": "public class PIHMJGCGNLP : IFAIJAGLDFK", + "kind": "class", + "baseType": "IFAIJAGLDFK", + "properties": [ + { + "name": "AHDBBFIDKBN", + "type": "PPGPAHNMGEC", + "accessibility": "public", + "kind": "property" + }, + { + "name": "CMGPCPKLHLF", + "type": "JHEEFBMODPG", + "accessibility": "public", + "kind": "property" + }, + { + "name": "DOOHAKMALHL", + "type": "List", + "accessibility": "public", + "kind": "property" + }, + { + "name": "IIIFDCAPMEA", + "type": "JHEEFBMODPG", + "accessibility": "public", + "kind": "property" + }, + { + "name": "KIFHEKPKILL", + "type": "JHEEFBMODPG", + "accessibility": "public", + "kind": "property" + }, + { + "name": "NDAGAGNHNPA", + "type": "PLILLKHMNDA", + "accessibility": "public", + "kind": "property" + }, + { + "name": "NJHNHHMCILD", + "type": "JHEEFBMODPG", + "accessibility": "public", + "kind": "property" + }, + { + "name": "OPIIBPFEODL", + "type": "List", + "accessibility": "public", + "kind": "property" + } + ], + "fields": [], + "enumValues": [], + "wireKeys": [ + "Club", + "CoownerPermissions", + "ModeratorPermissions", + "MemberPermissions", + "MyMembershipType" + ], + "isilFile": "PIHMJGCGNLP.txt", + "isilType": "PIHMJGCGNLP" + } + } + } + ], + "contracts": [ + { + "kind": "object", + "raw": "PIHMJGCGNLP", + "rootType": "PIHMJGCGNLP", + "typeDescription": { + "name": "PIHMJGCGNLP", + "found": true, + "file": "PIHMJGCGNLP.cs", + "declaration": "public class PIHMJGCGNLP : IFAIJAGLDFK", + "kind": "class", + "baseType": "IFAIJAGLDFK", + "properties": [ + { + "name": "AHDBBFIDKBN", + "type": "PPGPAHNMGEC", + "accessibility": "public", + "kind": "property" + }, + { + "name": "CMGPCPKLHLF", + "type": "JHEEFBMODPG", + "accessibility": "public", + "kind": "property" + }, + { + "name": "DOOHAKMALHL", + "type": "List", + "accessibility": "public", + "kind": "property" + }, + { + "name": "IIIFDCAPMEA", + "type": "JHEEFBMODPG", + "accessibility": "public", + "kind": "property" + }, + { + "name": "KIFHEKPKILL", + "type": "JHEEFBMODPG", + "accessibility": "public", + "kind": "property" + }, + { + "name": "NDAGAGNHNPA", + "type": "PLILLKHMNDA", + "accessibility": "public", + "kind": "property" + }, + { + "name": "NJHNHHMCILD", + "type": "JHEEFBMODPG", + "accessibility": "public", + "kind": "property" + }, + { + "name": "OPIIBPFEODL", + "type": "List", + "accessibility": "public", + "kind": "property" + } + ], + "fields": [], + "enumValues": [], + "wireKeys": [ + "Club", + "CoownerPermissions", + "ModeratorPermissions", + "MemberPermissions", + "MyMembershipType" + ], + "isilFile": "PIHMJGCGNLP.txt", + "isilType": "PIHMJGCGNLP" + } + } + ] + }, + { + "family": "clubs", + "route": "club/account/{0}/created", + "evidence": [ + { + "type": "JDJGIBLMFKK", + "method": "RecRoom.Async.IPromise`1> DIAPLKMFFKO(System.Int32 GKLPIFBPGOD)", + "file": "JDJGIBLMFKK.txt", + "line": 9416, + "contract": { + "kind": "array", + "raw": "System.Collections.Generic.List`1", + "rootType": "PLILLKHMNDA", + "item": { + "kind": "object", + "raw": "PLILLKHMNDA", + "rootType": "PLILLKHMNDA" + }, + "typeDescription": { + "name": "PLILLKHMNDA", + "found": true, + "file": "PLILLKHMNDA.cs", + "declaration": "public class PLILLKHMNDA : IFAIJAGLDFK, IEquatable", + "kind": "class", + "baseType": "IFAIJAGLDFK", + "properties": [ + { + "name": "BADIGBCKECA", + "type": "int", + "accessibility": "public", + "kind": "property" + }, + { + "name": "CCGOEDABKNN", + "type": "long", + "accessibility": "public", + "kind": "property" + }, + { + "name": "CDINMMPNAID", + "type": "JCMLDDKFKEO", + "accessibility": "public", + "kind": "property" + }, + { + "name": "CDNFGMHLDMJ", + "type": "bool", + "accessibility": "public", + "kind": "property" + }, + { + "name": "EEAOJCGAOCN", + "type": "int", + "accessibility": "public", + "kind": "property" + }, + { + "name": "EHOLKJPEGFF", + "type": "string", + "accessibility": "public", + "kind": "property" + }, + { + "name": "EPGALOMHHMI", + "type": "JCDEFCJLCHN", + "accessibility": "public", + "kind": "property" + }, + { + "name": "FIKEBGGCDFN", + "type": "string", + "accessibility": "public", + "kind": "property" + }, + { + "name": "HHCGNCLFKDM", + "type": "Nullable", + "accessibility": "public", + "kind": "property" + }, + { + "name": "IPKLLFAJJPJ", + "type": "bool", + "accessibility": "public", + "kind": "property" + }, + { + "name": "KODBEJPEFOJ", + "type": "string", + "accessibility": "public", + "kind": "property" + }, + { + "name": "LNGPBGCIAPP", + "type": "string", + "accessibility": "public", + "kind": "property" + }, + { + "name": "MNEEEGHOGAB", + "type": "DIGMAIMMHAP", + "accessibility": "public", + "kind": "property" + }, + { + "name": "PHBCFAJILGD", + "type": "PJCALHOPMKJ", + "accessibility": "public", + "kind": "property" + } + ], + "fields": [], + "enumValues": [], + "wireKeys": [ + "ClubId", + "Name", + "Description", + "MainImageName", + "State", + "CreatorAccountId", + "Category", + "Visibility", + "Joinability", + "AllowJuniors", + "MemberCount", + "IsRRO", + "ClubType" + ], + "isilFile": "PLILLKHMNDA.txt", + "isilType": "PLILLKHMNDA" + } + } + }, + { + "type": "JDJGIBLMFKK", + "method": "System.String JANFGLBCBMJ(System.Int32 GKLPIFBPGOD)", + "file": "JDJGIBLMFKK.txt", + "line": 7880, + "contract": { + "kind": "unknown", + "raw": "", + "rootType": "unknown" + } + }, + { + "type": "JDJGIBLMFKK", + "method": "System.Void PJBLNGJKLEB(System.Int64 AOMBLGBCENO)", + "file": "JDJGIBLMFKK.txt", + "line": 26175, + "contract": { + "kind": "unknown", + "raw": "", + "rootType": "unknown" + } + } + ], + "contracts": [ + { + "kind": "array", + "raw": "System.Collections.Generic.List`1", + "rootType": "PLILLKHMNDA", + "item": { + "kind": "object", + "raw": "PLILLKHMNDA", + "rootType": "PLILLKHMNDA" + }, + "typeDescription": { + "name": "PLILLKHMNDA", + "found": true, + "file": "PLILLKHMNDA.cs", + "declaration": "public class PLILLKHMNDA : IFAIJAGLDFK, IEquatable", + "kind": "class", + "baseType": "IFAIJAGLDFK", + "properties": [ + { + "name": "BADIGBCKECA", + "type": "int", + "accessibility": "public", + "kind": "property" + }, + { + "name": "CCGOEDABKNN", + "type": "long", + "accessibility": "public", + "kind": "property" + }, + { + "name": "CDINMMPNAID", + "type": "JCMLDDKFKEO", + "accessibility": "public", + "kind": "property" + }, + { + "name": "CDNFGMHLDMJ", + "type": "bool", + "accessibility": "public", + "kind": "property" + }, + { + "name": "EEAOJCGAOCN", + "type": "int", + "accessibility": "public", + "kind": "property" + }, + { + "name": "EHOLKJPEGFF", + "type": "string", + "accessibility": "public", + "kind": "property" + }, + { + "name": "EPGALOMHHMI", + "type": "JCDEFCJLCHN", + "accessibility": "public", + "kind": "property" + }, + { + "name": "FIKEBGGCDFN", + "type": "string", + "accessibility": "public", + "kind": "property" + }, + { + "name": "HHCGNCLFKDM", + "type": "Nullable", + "accessibility": "public", + "kind": "property" + }, + { + "name": "IPKLLFAJJPJ", + "type": "bool", + "accessibility": "public", + "kind": "property" + }, + { + "name": "KODBEJPEFOJ", + "type": "string", + "accessibility": "public", + "kind": "property" + }, + { + "name": "LNGPBGCIAPP", + "type": "string", + "accessibility": "public", + "kind": "property" + }, + { + "name": "MNEEEGHOGAB", + "type": "DIGMAIMMHAP", + "accessibility": "public", + "kind": "property" + }, + { + "name": "PHBCFAJILGD", + "type": "PJCALHOPMKJ", + "accessibility": "public", + "kind": "property" + } + ], + "fields": [], + "enumValues": [], + "wireKeys": [ + "ClubId", + "Name", + "Description", + "MainImageName", + "State", + "CreatorAccountId", + "Category", + "Visibility", + "Joinability", + "AllowJuniors", + "MemberCount", + "IsRRO", + "ClubType" + ], + "isilFile": "PLILLKHMNDA.txt", + "isilType": "PLILLKHMNDA" + } + }, + { + "kind": "unknown", + "raw": "", + "rootType": "unknown", + "typeDescription": { + "name": "unknown", + "found": false, + "kind": "object", + "wireKeys": [] + } + } + ] + }, + { + "family": "clubs", + "route": "club/categoryTags", + "evidence": [ + { + "type": "JDJGIBLMFKK", + "method": "RecRoom.Async.IPromise`1> PAKNBFOJENP()", + "file": "JDJGIBLMFKK.txt", + "line": 13314, + "contract": { + "kind": "array", + "raw": "System.Collections.Generic.List`1", + "rootType": "String", + "item": { + "kind": "primitive", + "raw": "System.String", + "rootType": "string" + }, + "typeDescription": { + "name": "String", + "found": false, + "kind": "primitive", + "wireKeys": [] + } + } + } + ], + "contracts": [ + { + "kind": "array", + "raw": "System.Collections.Generic.List`1", + "rootType": "String", + "item": { + "kind": "primitive", + "raw": "System.String", + "rootType": "string" + }, + "typeDescription": { + "name": "String", + "found": false, + "kind": "primitive", + "wireKeys": [] + } + } + ] + }, + { + "family": "clubs", + "route": "club/create", + "evidence": [ + { + "type": "JDJGIBLMFKK", + "method": "RecRoom.Async.IPromise`1 KMPJFFBGJMD(JDJGIBLMFKK+ACNILMIFDJJ JMFLHIIJFKL)", + "file": "JDJGIBLMFKK.txt", + "line": 11381, + "contract": { + "kind": "object", + "raw": "PIHMJGCGNLP", + "rootType": "PIHMJGCGNLP", + "typeDescription": { + "name": "PIHMJGCGNLP", + "found": true, + "file": "PIHMJGCGNLP.cs", + "declaration": "public class PIHMJGCGNLP : IFAIJAGLDFK", + "kind": "class", + "baseType": "IFAIJAGLDFK", + "properties": [ + { + "name": "AHDBBFIDKBN", + "type": "PPGPAHNMGEC", + "accessibility": "public", + "kind": "property" + }, + { + "name": "CMGPCPKLHLF", + "type": "JHEEFBMODPG", + "accessibility": "public", + "kind": "property" + }, + { + "name": "DOOHAKMALHL", + "type": "List", + "accessibility": "public", + "kind": "property" + }, + { + "name": "IIIFDCAPMEA", + "type": "JHEEFBMODPG", + "accessibility": "public", + "kind": "property" + }, + { + "name": "KIFHEKPKILL", + "type": "JHEEFBMODPG", + "accessibility": "public", + "kind": "property" + }, + { + "name": "NDAGAGNHNPA", + "type": "PLILLKHMNDA", + "accessibility": "public", + "kind": "property" + }, + { + "name": "NJHNHHMCILD", + "type": "JHEEFBMODPG", + "accessibility": "public", + "kind": "property" + }, + { + "name": "OPIIBPFEODL", + "type": "List", + "accessibility": "public", + "kind": "property" + } + ], + "fields": [], + "enumValues": [], + "wireKeys": [ + "Club", + "CoownerPermissions", + "ModeratorPermissions", + "MemberPermissions", + "MyMembershipType" + ], + "isilFile": "PIHMJGCGNLP.txt", + "isilType": "PIHMJGCGNLP" + } + } + } + ], + "contracts": [ + { + "kind": "object", + "raw": "PIHMJGCGNLP", + "rootType": "PIHMJGCGNLP", + "typeDescription": { + "name": "PIHMJGCGNLP", + "found": true, + "file": "PIHMJGCGNLP.cs", + "declaration": "public class PIHMJGCGNLP : IFAIJAGLDFK", + "kind": "class", + "baseType": "IFAIJAGLDFK", + "properties": [ + { + "name": "AHDBBFIDKBN", + "type": "PPGPAHNMGEC", + "accessibility": "public", + "kind": "property" + }, + { + "name": "CMGPCPKLHLF", + "type": "JHEEFBMODPG", + "accessibility": "public", + "kind": "property" + }, + { + "name": "DOOHAKMALHL", + "type": "List", + "accessibility": "public", + "kind": "property" + }, + { + "name": "IIIFDCAPMEA", + "type": "JHEEFBMODPG", + "accessibility": "public", + "kind": "property" + }, + { + "name": "KIFHEKPKILL", + "type": "JHEEFBMODPG", + "accessibility": "public", + "kind": "property" + }, + { + "name": "NDAGAGNHNPA", + "type": "PLILLKHMNDA", + "accessibility": "public", + "kind": "property" + }, + { + "name": "NJHNHHMCILD", + "type": "JHEEFBMODPG", + "accessibility": "public", + "kind": "property" + }, + { + "name": "OPIIBPFEODL", + "type": "List", + "accessibility": "public", + "kind": "property" + } + ], + "fields": [], + "enumValues": [], + "wireKeys": [ + "Club", + "CoownerPermissions", + "ModeratorPermissions", + "MemberPermissions", + "MyMembershipType" + ], + "isilFile": "PIHMJGCGNLP.txt", + "isilType": "PIHMJGCGNLP" + } + } + ] + }, + { + "family": "clubs", + "route": "club/home/me", + "evidence": [ + { + "type": "JDJGIBLMFKK", + "method": "RecRoom.Async.IPromise`1 DBDOOMCNNFE()", + "file": "JDJGIBLMFKK.txt", + "line": 10003, + "contract": { + "kind": "object", + "raw": "PLILLKHMNDA", + "rootType": "PLILLKHMNDA", + "typeDescription": { + "name": "PLILLKHMNDA", + "found": true, + "file": "PLILLKHMNDA.cs", + "declaration": "public class PLILLKHMNDA : IFAIJAGLDFK, IEquatable", + "kind": "class", + "baseType": "IFAIJAGLDFK", + "properties": [ + { + "name": "BADIGBCKECA", + "type": "int", + "accessibility": "public", + "kind": "property" + }, + { + "name": "CCGOEDABKNN", + "type": "long", + "accessibility": "public", + "kind": "property" + }, + { + "name": "CDINMMPNAID", + "type": "JCMLDDKFKEO", + "accessibility": "public", + "kind": "property" + }, + { + "name": "CDNFGMHLDMJ", + "type": "bool", + "accessibility": "public", + "kind": "property" + }, + { + "name": "EEAOJCGAOCN", + "type": "int", + "accessibility": "public", + "kind": "property" + }, + { + "name": "EHOLKJPEGFF", + "type": "string", + "accessibility": "public", + "kind": "property" + }, + { + "name": "EPGALOMHHMI", + "type": "JCDEFCJLCHN", + "accessibility": "public", + "kind": "property" + }, + { + "name": "FIKEBGGCDFN", + "type": "string", + "accessibility": "public", + "kind": "property" + }, + { + "name": "HHCGNCLFKDM", + "type": "Nullable", + "accessibility": "public", + "kind": "property" + }, + { + "name": "IPKLLFAJJPJ", + "type": "bool", + "accessibility": "public", + "kind": "property" + }, + { + "name": "KODBEJPEFOJ", + "type": "string", + "accessibility": "public", + "kind": "property" + }, + { + "name": "LNGPBGCIAPP", + "type": "string", + "accessibility": "public", + "kind": "property" + }, + { + "name": "MNEEEGHOGAB", + "type": "DIGMAIMMHAP", + "accessibility": "public", + "kind": "property" + }, + { + "name": "PHBCFAJILGD", + "type": "PJCALHOPMKJ", + "accessibility": "public", + "kind": "property" + } + ], + "fields": [], + "enumValues": [], + "wireKeys": [ + "ClubId", + "Name", + "Description", + "MainImageName", + "State", + "CreatorAccountId", + "Category", + "Visibility", + "Joinability", + "AllowJuniors", + "MemberCount", + "IsRRO", + "ClubType" + ], + "isilFile": "PLILLKHMNDA.txt", + "isilType": "PLILLKHMNDA" + } + } + }, + { + "type": "JDJGIBLMFKK+<>c", + "method": "System.Void b__106_0()", + "file": "JDJGIBLMFKK_NestedType___c.txt", + "line": 995, + "contract": { + "kind": "unknown", + "raw": "", + "rootType": "unknown" + } + }, + { + "type": "JDJGIBLMFKK+<>c", + "method": "System.Void b__106_2()", + "file": "JDJGIBLMFKK_NestedType___c.txt", + "line": 954, + "contract": { + "kind": "unknown", + "raw": "", + "rootType": "unknown" + } + } + ], + "contracts": [ + { + "kind": "object", + "raw": "PLILLKHMNDA", + "rootType": "PLILLKHMNDA", + "typeDescription": { + "name": "PLILLKHMNDA", + "found": true, + "file": "PLILLKHMNDA.cs", + "declaration": "public class PLILLKHMNDA : IFAIJAGLDFK, IEquatable", + "kind": "class", + "baseType": "IFAIJAGLDFK", + "properties": [ + { + "name": "BADIGBCKECA", + "type": "int", + "accessibility": "public", + "kind": "property" + }, + { + "name": "CCGOEDABKNN", + "type": "long", + "accessibility": "public", + "kind": "property" + }, + { + "name": "CDINMMPNAID", + "type": "JCMLDDKFKEO", + "accessibility": "public", + "kind": "property" + }, + { + "name": "CDNFGMHLDMJ", + "type": "bool", + "accessibility": "public", + "kind": "property" + }, + { + "name": "EEAOJCGAOCN", + "type": "int", + "accessibility": "public", + "kind": "property" + }, + { + "name": "EHOLKJPEGFF", + "type": "string", + "accessibility": "public", + "kind": "property" + }, + { + "name": "EPGALOMHHMI", + "type": "JCDEFCJLCHN", + "accessibility": "public", + "kind": "property" + }, + { + "name": "FIKEBGGCDFN", + "type": "string", + "accessibility": "public", + "kind": "property" + }, + { + "name": "HHCGNCLFKDM", + "type": "Nullable", + "accessibility": "public", + "kind": "property" + }, + { + "name": "IPKLLFAJJPJ", + "type": "bool", + "accessibility": "public", + "kind": "property" + }, + { + "name": "KODBEJPEFOJ", + "type": "string", + "accessibility": "public", + "kind": "property" + }, + { + "name": "LNGPBGCIAPP", + "type": "string", + "accessibility": "public", + "kind": "property" + }, + { + "name": "MNEEEGHOGAB", + "type": "DIGMAIMMHAP", + "accessibility": "public", + "kind": "property" + }, + { + "name": "PHBCFAJILGD", + "type": "PJCALHOPMKJ", + "accessibility": "public", + "kind": "property" + } + ], + "fields": [], + "enumValues": [], + "wireKeys": [ + "ClubId", + "Name", + "Description", + "MainImageName", + "State", + "CreatorAccountId", + "Category", + "Visibility", + "Joinability", + "AllowJuniors", + "MemberCount", + "IsRRO", + "ClubType" + ], + "isilFile": "PLILLKHMNDA.txt", + "isilType": "PLILLKHMNDA" + } + }, + { + "kind": "unknown", + "raw": "", + "rootType": "unknown", + "typeDescription": { + "name": "unknown", + "found": false, + "kind": "object", + "wireKeys": [] + } + } + ] + }, + { + "family": "clubs", + "route": "club/mine/created", + "evidence": [ + { + "type": "JDJGIBLMFKK", + "method": "RecRoom.Async.IPromise`1> LJJLGJIMJED()", + "file": "JDJGIBLMFKK.txt", + "line": 9275, + "contract": { + "kind": "array", + "raw": "System.Collections.Generic.List`1", + "rootType": "PLILLKHMNDA", + "item": { + "kind": "object", + "raw": "PLILLKHMNDA", + "rootType": "PLILLKHMNDA" + }, + "typeDescription": { + "name": "PLILLKHMNDA", + "found": true, + "file": "PLILLKHMNDA.cs", + "declaration": "public class PLILLKHMNDA : IFAIJAGLDFK, IEquatable", + "kind": "class", + "baseType": "IFAIJAGLDFK", + "properties": [ + { + "name": "BADIGBCKECA", + "type": "int", + "accessibility": "public", + "kind": "property" + }, + { + "name": "CCGOEDABKNN", + "type": "long", + "accessibility": "public", + "kind": "property" + }, + { + "name": "CDINMMPNAID", + "type": "JCMLDDKFKEO", + "accessibility": "public", + "kind": "property" + }, + { + "name": "CDNFGMHLDMJ", + "type": "bool", + "accessibility": "public", + "kind": "property" + }, + { + "name": "EEAOJCGAOCN", + "type": "int", + "accessibility": "public", + "kind": "property" + }, + { + "name": "EHOLKJPEGFF", + "type": "string", + "accessibility": "public", + "kind": "property" + }, + { + "name": "EPGALOMHHMI", + "type": "JCDEFCJLCHN", + "accessibility": "public", + "kind": "property" + }, + { + "name": "FIKEBGGCDFN", + "type": "string", + "accessibility": "public", + "kind": "property" + }, + { + "name": "HHCGNCLFKDM", + "type": "Nullable", + "accessibility": "public", + "kind": "property" + }, + { + "name": "IPKLLFAJJPJ", + "type": "bool", + "accessibility": "public", + "kind": "property" + }, + { + "name": "KODBEJPEFOJ", + "type": "string", + "accessibility": "public", + "kind": "property" + }, + { + "name": "LNGPBGCIAPP", + "type": "string", + "accessibility": "public", + "kind": "property" + }, + { + "name": "MNEEEGHOGAB", + "type": "DIGMAIMMHAP", + "accessibility": "public", + "kind": "property" + }, + { + "name": "PHBCFAJILGD", + "type": "PJCALHOPMKJ", + "accessibility": "public", + "kind": "property" + } + ], + "fields": [], + "enumValues": [], + "wireKeys": [ + "ClubId", + "Name", + "Description", + "MainImageName", + "State", + "CreatorAccountId", + "Category", + "Visibility", + "Joinability", + "AllowJuniors", + "MemberCount", + "IsRRO", + "ClubType" + ], + "isilFile": "PLILLKHMNDA.txt", + "isilType": "PLILLKHMNDA" + } + } + }, + { + "type": "JDJGIBLMFKK", + "method": "System.Void PJBLNGJKLEB(System.Int64 AOMBLGBCENO)", + "file": "JDJGIBLMFKK.txt", + "line": 26134, + "contract": { + "kind": "unknown", + "raw": "", + "rootType": "unknown" + } + }, + { + "type": "JDJGIBLMFKK", + "method": "System.Void PJBLNGJKLEB(System.Int64 AOMBLGBCENO)", + "file": "JDJGIBLMFKK.txt", + "line": 26326, + "contract": { + "kind": "unknown", + "raw": "", + "rootType": "unknown" + } + } + ], + "contracts": [ + { + "kind": "array", + "raw": "System.Collections.Generic.List`1", + "rootType": "PLILLKHMNDA", + "item": { + "kind": "object", + "raw": "PLILLKHMNDA", + "rootType": "PLILLKHMNDA" + }, + "typeDescription": { + "name": "PLILLKHMNDA", + "found": true, + "file": "PLILLKHMNDA.cs", + "declaration": "public class PLILLKHMNDA : IFAIJAGLDFK, IEquatable", + "kind": "class", + "baseType": "IFAIJAGLDFK", + "properties": [ + { + "name": "BADIGBCKECA", + "type": "int", + "accessibility": "public", + "kind": "property" + }, + { + "name": "CCGOEDABKNN", + "type": "long", + "accessibility": "public", + "kind": "property" + }, + { + "name": "CDINMMPNAID", + "type": "JCMLDDKFKEO", + "accessibility": "public", + "kind": "property" + }, + { + "name": "CDNFGMHLDMJ", + "type": "bool", + "accessibility": "public", + "kind": "property" + }, + { + "name": "EEAOJCGAOCN", + "type": "int", + "accessibility": "public", + "kind": "property" + }, + { + "name": "EHOLKJPEGFF", + "type": "string", + "accessibility": "public", + "kind": "property" + }, + { + "name": "EPGALOMHHMI", + "type": "JCDEFCJLCHN", + "accessibility": "public", + "kind": "property" + }, + { + "name": "FIKEBGGCDFN", + "type": "string", + "accessibility": "public", + "kind": "property" + }, + { + "name": "HHCGNCLFKDM", + "type": "Nullable", + "accessibility": "public", + "kind": "property" + }, + { + "name": "IPKLLFAJJPJ", + "type": "bool", + "accessibility": "public", + "kind": "property" + }, + { + "name": "KODBEJPEFOJ", + "type": "string", + "accessibility": "public", + "kind": "property" + }, + { + "name": "LNGPBGCIAPP", + "type": "string", + "accessibility": "public", + "kind": "property" + }, + { + "name": "MNEEEGHOGAB", + "type": "DIGMAIMMHAP", + "accessibility": "public", + "kind": "property" + }, + { + "name": "PHBCFAJILGD", + "type": "PJCALHOPMKJ", + "accessibility": "public", + "kind": "property" + } + ], + "fields": [], + "enumValues": [], + "wireKeys": [ + "ClubId", + "Name", + "Description", + "MainImageName", + "State", + "CreatorAccountId", + "Category", + "Visibility", + "Joinability", + "AllowJuniors", + "MemberCount", + "IsRRO", + "ClubType" + ], + "isilFile": "PLILLKHMNDA.txt", + "isilType": "PLILLKHMNDA" + } + }, + { + "kind": "unknown", + "raw": "", + "rootType": "unknown", + "typeDescription": { + "name": "unknown", + "found": false, + "kind": "object", + "wireKeys": [] + } + } + ] + }, + { + "family": "clubs", + "route": "club/mine/member", + "evidence": [ + { + "type": "JDJGIBLMFKK", + "method": "RecRoom.Async.IPromise`1> DFDMGNBKEKO()", + "file": "JDJGIBLMFKK.txt", + "line": 9817, + "contract": { + "kind": "array", + "raw": "System.Collections.Generic.List`1", + "rootType": "PLILLKHMNDA", + "item": { + "kind": "object", + "raw": "PLILLKHMNDA", + "rootType": "PLILLKHMNDA" + }, + "typeDescription": { + "name": "PLILLKHMNDA", + "found": true, + "file": "PLILLKHMNDA.cs", + "declaration": "public class PLILLKHMNDA : IFAIJAGLDFK, IEquatable", + "kind": "class", + "baseType": "IFAIJAGLDFK", + "properties": [ + { + "name": "BADIGBCKECA", + "type": "int", + "accessibility": "public", + "kind": "property" + }, + { + "name": "CCGOEDABKNN", + "type": "long", + "accessibility": "public", + "kind": "property" + }, + { + "name": "CDINMMPNAID", + "type": "JCMLDDKFKEO", + "accessibility": "public", + "kind": "property" + }, + { + "name": "CDNFGMHLDMJ", + "type": "bool", + "accessibility": "public", + "kind": "property" + }, + { + "name": "EEAOJCGAOCN", + "type": "int", + "accessibility": "public", + "kind": "property" + }, + { + "name": "EHOLKJPEGFF", + "type": "string", + "accessibility": "public", + "kind": "property" + }, + { + "name": "EPGALOMHHMI", + "type": "JCDEFCJLCHN", + "accessibility": "public", + "kind": "property" + }, + { + "name": "FIKEBGGCDFN", + "type": "string", + "accessibility": "public", + "kind": "property" + }, + { + "name": "HHCGNCLFKDM", + "type": "Nullable", + "accessibility": "public", + "kind": "property" + }, + { + "name": "IPKLLFAJJPJ", + "type": "bool", + "accessibility": "public", + "kind": "property" + }, + { + "name": "KODBEJPEFOJ", + "type": "string", + "accessibility": "public", + "kind": "property" + }, + { + "name": "LNGPBGCIAPP", + "type": "string", + "accessibility": "public", + "kind": "property" + }, + { + "name": "MNEEEGHOGAB", + "type": "DIGMAIMMHAP", + "accessibility": "public", + "kind": "property" + }, + { + "name": "PHBCFAJILGD", + "type": "PJCALHOPMKJ", + "accessibility": "public", + "kind": "property" + } + ], + "fields": [], + "enumValues": [], + "wireKeys": [ + "ClubId", + "Name", + "Description", + "MainImageName", + "State", + "CreatorAccountId", + "Category", + "Visibility", + "Joinability", + "AllowJuniors", + "MemberCount", + "IsRRO", + "ClubType" + ], + "isilFile": "PLILLKHMNDA.txt", + "isilType": "PLILLKHMNDA" + } + } + }, + { + "type": "JDJGIBLMFKK", + "method": "System.Void PJBLNGJKLEB(System.Int64 AOMBLGBCENO)", + "file": "JDJGIBLMFKK.txt", + "line": 26194, + "contract": { + "kind": "unknown", + "raw": "", + "rootType": "unknown" + } + }, + { + "type": "JDJGIBLMFKK", + "method": "System.Void PJBLNGJKLEB(System.Int64 AOMBLGBCENO)", + "file": "JDJGIBLMFKK.txt", + "line": 26330, + "contract": { + "kind": "unknown", + "raw": "", + "rootType": "unknown" + } + } + ], + "contracts": [ + { + "kind": "array", + "raw": "System.Collections.Generic.List`1", + "rootType": "PLILLKHMNDA", + "item": { + "kind": "object", + "raw": "PLILLKHMNDA", + "rootType": "PLILLKHMNDA" + }, + "typeDescription": { + "name": "PLILLKHMNDA", + "found": true, + "file": "PLILLKHMNDA.cs", + "declaration": "public class PLILLKHMNDA : IFAIJAGLDFK, IEquatable", + "kind": "class", + "baseType": "IFAIJAGLDFK", + "properties": [ + { + "name": "BADIGBCKECA", + "type": "int", + "accessibility": "public", + "kind": "property" + }, + { + "name": "CCGOEDABKNN", + "type": "long", + "accessibility": "public", + "kind": "property" + }, + { + "name": "CDINMMPNAID", + "type": "JCMLDDKFKEO", + "accessibility": "public", + "kind": "property" + }, + { + "name": "CDNFGMHLDMJ", + "type": "bool", + "accessibility": "public", + "kind": "property" + }, + { + "name": "EEAOJCGAOCN", + "type": "int", + "accessibility": "public", + "kind": "property" + }, + { + "name": "EHOLKJPEGFF", + "type": "string", + "accessibility": "public", + "kind": "property" + }, + { + "name": "EPGALOMHHMI", + "type": "JCDEFCJLCHN", + "accessibility": "public", + "kind": "property" + }, + { + "name": "FIKEBGGCDFN", + "type": "string", + "accessibility": "public", + "kind": "property" + }, + { + "name": "HHCGNCLFKDM", + "type": "Nullable", + "accessibility": "public", + "kind": "property" + }, + { + "name": "IPKLLFAJJPJ", + "type": "bool", + "accessibility": "public", + "kind": "property" + }, + { + "name": "KODBEJPEFOJ", + "type": "string", + "accessibility": "public", + "kind": "property" + }, + { + "name": "LNGPBGCIAPP", + "type": "string", + "accessibility": "public", + "kind": "property" + }, + { + "name": "MNEEEGHOGAB", + "type": "DIGMAIMMHAP", + "accessibility": "public", + "kind": "property" + }, + { + "name": "PHBCFAJILGD", + "type": "PJCALHOPMKJ", + "accessibility": "public", + "kind": "property" + } + ], + "fields": [], + "enumValues": [], + "wireKeys": [ + "ClubId", + "Name", + "Description", + "MainImageName", + "State", + "CreatorAccountId", + "Category", + "Visibility", + "Joinability", + "AllowJuniors", + "MemberCount", + "IsRRO", + "ClubType" + ], + "isilFile": "PLILLKHMNDA.txt", + "isilType": "PLILLKHMNDA" + } + }, + { + "kind": "unknown", + "raw": "", + "rootType": "unknown", + "typeDescription": { + "name": "unknown", + "found": false, + "kind": "object", + "wireKeys": [] + } + } + ] + }, + { + "family": "clubs", + "route": "members/bulk", + "evidence": [ + { + "type": "JDJGIBLMFKK", + "method": "RecRoom.Async.IPromise`1> DDIJPHDEDDM(System.Int64 AOMBLGBCENO, System.Collections.Generic.List`1 ILNGMAANNDG)", + "file": "JDJGIBLMFKK.txt", + "line": 16847, + "contract": { + "kind": "array", + "raw": "System.Collections.Generic.List`1", + "rootType": "JHMMGLNJHIB", + "item": { + "kind": "object", + "raw": "JHMMGLNJHIB", + "rootType": "JHMMGLNJHIB" + }, + "typeDescription": { + "name": "JHMMGLNJHIB", + "found": true, + "file": "JHMMGLNJHIB.cs", + "declaration": "public class JHMMGLNJHIB : IFAIJAGLDFK", + "kind": "class", + "baseType": "IFAIJAGLDFK", + "properties": [ + { + "name": "GAINIOENNCG", + "type": "int", + "accessibility": "public", + "kind": "property" + }, + { + "name": "JNMPJNKEJAC", + "type": "PPGPAHNMGEC", + "accessibility": "public", + "kind": "property" + } + ], + "fields": [], + "enumValues": [], + "wireKeys": [ + "AccountId", + "MembershipType" + ], + "isilFile": "JHMMGLNJHIB.txt", + "isilType": "JHMMGLNJHIB" + } + } + } + ], + "contracts": [ + { + "kind": "array", + "raw": "System.Collections.Generic.List`1", + "rootType": "JHMMGLNJHIB", + "item": { + "kind": "object", + "raw": "JHMMGLNJHIB", + "rootType": "JHMMGLNJHIB" + }, + "typeDescription": { + "name": "JHMMGLNJHIB", + "found": true, + "file": "JHMMGLNJHIB.cs", + "declaration": "public class JHMMGLNJHIB : IFAIJAGLDFK", + "kind": "class", + "baseType": "IFAIJAGLDFK", + "properties": [ + { + "name": "GAINIOENNCG", + "type": "int", + "accessibility": "public", + "kind": "property" + }, + { + "name": "JNMPJNKEJAC", + "type": "PPGPAHNMGEC", + "accessibility": "public", + "kind": "property" + } + ], + "fields": [], + "enumValues": [], + "wireKeys": [ + "AccountId", + "MembershipType" + ], + "isilFile": "JHMMGLNJHIB.txt", + "isilType": "JHMMGLNJHIB" + } + } + ] + }, + { + "family": "clubs", + "route": "members/bulk?", + "evidence": [ + { + "type": "JDJGIBLMFKK", + "method": "RecRoom.Async.IPromise`1> DDIJPHDEDDM(System.Int64 AOMBLGBCENO, System.Collections.Generic.List`1 ILNGMAANNDG)", + "file": "JDJGIBLMFKK.txt", + "line": 16893, + "contract": { + "kind": "array", + "raw": "System.Collections.Generic.List`1", + "rootType": "JHMMGLNJHIB", + "item": { + "kind": "object", + "raw": "JHMMGLNJHIB", + "rootType": "JHMMGLNJHIB" + }, + "typeDescription": { + "name": "JHMMGLNJHIB", + "found": true, + "file": "JHMMGLNJHIB.cs", + "declaration": "public class JHMMGLNJHIB : IFAIJAGLDFK", + "kind": "class", + "baseType": "IFAIJAGLDFK", + "properties": [ + { + "name": "GAINIOENNCG", + "type": "int", + "accessibility": "public", + "kind": "property" + }, + { + "name": "JNMPJNKEJAC", + "type": "PPGPAHNMGEC", + "accessibility": "public", + "kind": "property" + } + ], + "fields": [], + "enumValues": [], + "wireKeys": [ + "AccountId", + "MembershipType" + ], + "isilFile": "JHMMGLNJHIB.txt", + "isilType": "JHMMGLNJHIB" + } + } + } + ], + "contracts": [ + { + "kind": "array", + "raw": "System.Collections.Generic.List`1", + "rootType": "JHMMGLNJHIB", + "item": { + "kind": "object", + "raw": "JHMMGLNJHIB", + "rootType": "JHMMGLNJHIB" + }, + "typeDescription": { + "name": "JHMMGLNJHIB", + "found": true, + "file": "JHMMGLNJHIB.cs", + "declaration": "public class JHMMGLNJHIB : IFAIJAGLDFK", + "kind": "class", + "baseType": "IFAIJAGLDFK", + "properties": [ + { + "name": "GAINIOENNCG", + "type": "int", + "accessibility": "public", + "kind": "property" + }, + { + "name": "JNMPJNKEJAC", + "type": "PPGPAHNMGEC", + "accessibility": "public", + "kind": "property" + } + ], + "fields": [], + "enumValues": [], + "wireKeys": [ + "AccountId", + "MembershipType" + ], + "isilFile": "JHMMGLNJHIB.txt", + "isilType": "JHMMGLNJHIB" + } + } + ] + }, + { + "family": "config-settings", + "route": "/config/{0}", + "evidence": [ + { + "type": "FHLGJDFHOKL", + "method": "RecRoom.Async.IPromise MCDNGDMJHBE()", + "file": "FHLGJDFHOKL.txt", + "line": 502, + "contract": { + "kind": "success-or-empty", + "raw": "RecRoom.Async.IPromise", + "rootType": "void/success", + "typeDescription": { + "name": "void/success", + "found": false, + "kind": "object", + "wireKeys": [] + } + } + } + ], + "contracts": [ + { + "kind": "success-or-empty", + "raw": "RecRoom.Async.IPromise", + "rootType": "void/success", + "typeDescription": { + "name": "void/success", + "found": false, + "kind": "object", + "wireKeys": [] + } + } + ] + }, + { + "family": "config-settings", + "route": "api/config/", + "evidence": [ + { + "type": "BBEFMHAEEEA", + "method": "RecRoom.Async.IPromise BGNDHOHFJBN()", + "file": "BBEFMHAEEEA.txt", + "line": 950, + "contract": { + "kind": "success-or-empty", + "raw": "RecRoom.Async.IPromise", + "rootType": "void/success", + "typeDescription": { + "name": "void/success", + "found": false, + "kind": "object", + "wireKeys": [] + } + } + }, + { + "type": "BBEFMHAEEEA", + "method": "RecRoom.Async.IPromise OHHAEMHMIFE()", + "file": "BBEFMHAEEEA.txt", + "line": 655, + "contract": { + "kind": "success-or-empty", + "raw": "RecRoom.Async.IPromise", + "rootType": "void/success", + "typeDescription": { + "name": "void/success", + "found": false, + "kind": "object", + "wireKeys": [] + } + } + }, + { + "type": "BBEFMHAEEEA", + "method": "RecRoom.Async.IPromise`1> EAJPLMPNPIJ(System.Int32 CKEBCMEKCJH)", + "file": "BBEFMHAEEEA.txt", + "line": 1147, + "contract": { + "kind": "array", + "raw": "System.Collections.Generic.List`1", + "rootType": "FPDADCNKELI", + "item": { + "kind": "object", + "raw": "FPDADCNKELI", + "rootType": "FPDADCNKELI" + }, + "typeDescription": { + "name": "FPDADCNKELI", + "found": true, + "file": "FPDADCNKELI.cs", + "declaration": "public class FPDADCNKELI : IFAIJAGLDFK", + "kind": "class", + "baseType": "IFAIJAGLDFK", + "properties": [ + { + "name": "BOFMFGLDDAA", + "type": "string", + "accessibility": "public", + "kind": "property" + }, + { + "name": "CGIKDDCIDNI", + "type": "string", + "accessibility": "public", + "kind": "property" + }, + { + "name": "DBCDHIABGIP", + "type": "string", + "accessibility": "public", + "kind": "property" + }, + { + "name": "DDLLPNLMKPP", + "type": "string", + "accessibility": "public", + "kind": "property" + }, + { + "name": "EHFBCMOHHAI", + "type": "string", + "accessibility": "public", + "kind": "property" + }, + { + "name": "IDPOBNAABIK", + "type": "int", + "accessibility": "public", + "kind": "property" + }, + { + "name": "JMDDEHCPHII", + "type": "string", + "accessibility": "public", + "kind": "property" + }, + { + "name": "LLAAHHEBHFP", + "type": "FCCPDGMCPBF", + "accessibility": "public", + "kind": "property" + }, + { + "name": "LMNDJGOMBNL", + "type": "NJDEOAENIMH", + "accessibility": "public", + "kind": "property" + } + ], + "fields": [], + "enumValues": [], + "wireKeys": [ + "Version", + "ButtonNumber", + "Override", + "CustomRoomName", + "CustomTitle", + "CustomDescription", + "DefaultRoomName", + "DefaultTitle", + "DefaultDescription" + ], + "isilFile": "FPDADCNKELI.txt", + "isilType": "FPDADCNKELI" + } + } + } + ], + "contracts": [ + { + "kind": "success-or-empty", + "raw": "RecRoom.Async.IPromise", + "rootType": "void/success", + "typeDescription": { + "name": "void/success", + "found": false, + "kind": "object", + "wireKeys": [] + } + }, + { + "kind": "array", + "raw": "System.Collections.Generic.List`1", + "rootType": "FPDADCNKELI", + "item": { + "kind": "object", + "raw": "FPDADCNKELI", + "rootType": "FPDADCNKELI" + }, + "typeDescription": { + "name": "FPDADCNKELI", + "found": true, + "file": "FPDADCNKELI.cs", + "declaration": "public class FPDADCNKELI : IFAIJAGLDFK", + "kind": "class", + "baseType": "IFAIJAGLDFK", + "properties": [ + { + "name": "BOFMFGLDDAA", + "type": "string", + "accessibility": "public", + "kind": "property" + }, + { + "name": "CGIKDDCIDNI", + "type": "string", + "accessibility": "public", + "kind": "property" + }, + { + "name": "DBCDHIABGIP", + "type": "string", + "accessibility": "public", + "kind": "property" + }, + { + "name": "DDLLPNLMKPP", + "type": "string", + "accessibility": "public", + "kind": "property" + }, + { + "name": "EHFBCMOHHAI", + "type": "string", + "accessibility": "public", + "kind": "property" + }, + { + "name": "IDPOBNAABIK", + "type": "int", + "accessibility": "public", + "kind": "property" + }, + { + "name": "JMDDEHCPHII", + "type": "string", + "accessibility": "public", + "kind": "property" + }, + { + "name": "LLAAHHEBHFP", + "type": "FCCPDGMCPBF", + "accessibility": "public", + "kind": "property" + }, + { + "name": "LMNDJGOMBNL", + "type": "NJDEOAENIMH", + "accessibility": "public", + "kind": "property" + } + ], + "fields": [], + "enumValues": [], + "wireKeys": [ + "Version", + "ButtonNumber", + "Override", + "CustomRoomName", + "CustomTitle", + "CustomDescription", + "DefaultRoomName", + "DefaultTitle", + "DefaultDescription" + ], + "isilFile": "FPDADCNKELI.txt", + "isilType": "FPDADCNKELI" + } + } + ] + }, + { + "family": "config-settings", + "route": "api/config/v1/freegiftbutton", + "evidence": [ + { + "type": "BBEFMHAEEEA", + "method": "RecRoom.Async.IPromise`1 GMOMCLADPII()", + "file": "BBEFMHAEEEA.txt", + "line": 1230, + "contract": { + "kind": "primitive", + "raw": "System.Boolean", + "rootType": "boolean", + "typeDescription": { + "name": "boolean", + "found": false, + "kind": "primitive", + "wireKeys": [] + } + } + } + ], + "contracts": [ + { + "kind": "primitive", + "raw": "System.Boolean", + "rootType": "boolean", + "typeDescription": { + "name": "boolean", + "found": false, + "kind": "primitive", + "wireKeys": [] + } + } + ] + }, + { + "family": "config-settings", + "route": "api/gameconfigs/", + "evidence": [ + { + "type": "IGCCFMFHBBN", + "method": "System.Void .cctor()", + "file": "IGCCFMFHBBN.txt", + "line": 1921, + "contract": { + "kind": "unknown", + "raw": "", + "rootType": "unknown" + } + } + ], + "contracts": [ + { + "kind": "unknown", + "raw": "", + "rootType": "unknown", + "typeDescription": { + "name": "unknown", + "found": false, + "kind": "object", + "wireKeys": [] + } + } + ] + }, + { + "family": "config-settings", + "route": "api/settings/", + "evidence": [ + { + "type": "JFDILELKPAL", + "method": "RecRoom.Async.IPromise`1> HOHPJKNPEGK()", + "file": "JFDILELKPAL.txt", + "line": 48, + "contract": { + "kind": "array", + "raw": "System.Collections.Generic.List`1", + "rootType": "OHLKCDKEGMN", + "item": { + "kind": "object", + "raw": "OHLKCDKEGMN", + "rootType": "OHLKCDKEGMN" + }, + "typeDescription": { + "name": "OHLKCDKEGMN", + "found": true, + "file": "OHLKCDKEGMN.cs", + "declaration": "public class OHLKCDKEGMN : IFAIJAGLDFK, AKJKEMONOIL", + "kind": "class", + "baseType": "IFAIJAGLDFK", + "properties": [ + { + "name": "AKFDMGLACLA", + "type": "string", + "accessibility": "public", + "kind": "property" + }, + { + "name": "DMLIOOCLKKP", + "type": "string", + "accessibility": "public", + "kind": "property" + } + ], + "fields": [], + "enumValues": [], + "wireKeys": [ + "Key", + "Value" + ], + "isilFile": "OHLKCDKEGMN.txt", + "isilType": "OHLKCDKEGMN" + } + } + }, + { + "type": "JFDILELKPAL", + "method": "System.Collections.IEnumerator LHGBAFBJAHM(OHLKCDKEGMN AOKDECDAEEG, BPHGKAEDBPE+OJDJIJDNFHE AFLPGGJMPOE)", + "file": "JFDILELKPAL.txt", + "line": 224, + "contract": { + "kind": "callback-or-coroutine", + "raw": "IEnumerator", + "rootType": "callback", + "typeDescription": { + "name": "callback", + "found": false, + "kind": "object", + "wireKeys": [] + } + } + } + ], + "contracts": [ + { + "kind": "array", + "raw": "System.Collections.Generic.List`1", + "rootType": "OHLKCDKEGMN", + "item": { + "kind": "object", + "raw": "OHLKCDKEGMN", + "rootType": "OHLKCDKEGMN" + }, + "typeDescription": { + "name": "OHLKCDKEGMN", + "found": true, + "file": "OHLKCDKEGMN.cs", + "declaration": "public class OHLKCDKEGMN : IFAIJAGLDFK, AKJKEMONOIL", + "kind": "class", + "baseType": "IFAIJAGLDFK", + "properties": [ + { + "name": "AKFDMGLACLA", + "type": "string", + "accessibility": "public", + "kind": "property" + }, + { + "name": "DMLIOOCLKKP", + "type": "string", + "accessibility": "public", + "kind": "property" + } + ], + "fields": [], + "enumValues": [], + "wireKeys": [ + "Key", + "Value" + ], + "isilFile": "OHLKCDKEGMN.txt", + "isilType": "OHLKCDKEGMN" + } + }, + { + "kind": "callback-or-coroutine", + "raw": "IEnumerator", + "rootType": "callback", + "typeDescription": { + "name": "callback", + "found": false, + "kind": "object", + "wireKeys": [] + } + } + ] + }, + { + "family": "economy", + "route": "{0}v1/consume", + "evidence": [ + { + "type": "FOEHJBDMMBH", + "method": "System.Collections.IEnumerator LCELAOEOIJO(OOCCCGPICOG BAJGNABECMN, System.Int32 FKKAPHAMPMG, BPHGKAEDBPE+CBEOHBCIPEA AFLPGGJMPOE)", + "file": "FOEHJBDMMBH.txt", + "line": 840, + "contract": { + "kind": "callback-or-coroutine", + "raw": "IEnumerator", + "rootType": "callback", + "typeDescription": { + "name": "callback", + "found": false, + "kind": "object", + "wireKeys": [] + } + } + } + ], + "contracts": [ + { + "kind": "callback-or-coroutine", + "raw": "IEnumerator", + "rootType": "callback", + "typeDescription": { + "name": "callback", + "found": false, + "kind": "object", + "wireKeys": [] + } + } + ] + }, + { + "family": "economy", + "route": "/consume", + "evidence": [ + { + "type": "COFHGNFJMOG", + "method": "RecRoom.Async.IPromise`1 HDLJGFEDGLH(System.String ENJEOLBEALP, System.Nullable`1 LNBJKLOINED)", + "file": "COFHGNFJMOG.txt", + "line": 1242, + "contract": { + "kind": "object", + "raw": "PLDFOPCHHJG", + "rootType": "PLDFOPCHHJG", + "typeDescription": { + "name": "PLDFOPCHHJG", + "found": true, + "file": "PLDFOPCHHJG.cs", + "declaration": "public class PLDFOPCHHJG : IFAIJAGLDFK", + "kind": "class", + "baseType": "IFAIJAGLDFK", + "properties": [], + "fields": [ + { + "name": "ACHMMBMLGEK", + "type": "int", + "kind": "field" + }, + { + "name": "PBPPCMNHODC", + "type": "string", + "kind": "field" + }, + { + "name": "FBLEJONFPAK", + "type": "bool", + "kind": "field" + } + ], + "enumValues": [], + "wireKeys": [ + "creatorPlayerId", + "data", + "isValid" + ], + "isilFile": "PLDFOPCHHJG.txt", + "isilType": "PLDFOPCHHJG" + } + } + } + ], + "contracts": [ + { + "kind": "object", + "raw": "PLDFOPCHHJG", + "rootType": "PLDFOPCHHJG", + "typeDescription": { + "name": "PLDFOPCHHJG", + "found": true, + "file": "PLDFOPCHHJG.cs", + "declaration": "public class PLDFOPCHHJG : IFAIJAGLDFK", + "kind": "class", + "baseType": "IFAIJAGLDFK", + "properties": [], + "fields": [ + { + "name": "ACHMMBMLGEK", + "type": "int", + "kind": "field" + }, + { + "name": "PBPPCMNHODC", + "type": "string", + "kind": "field" + }, + { + "name": "FBLEJONFPAK", + "type": "bool", + "kind": "field" + } + ], + "enumValues": [], + "wireKeys": [ + "creatorPlayerId", + "data", + "isValid" + ], + "isilFile": "PLDFOPCHHJG.txt", + "isilType": "PLDFOPCHHJG" + } + } + ] + }, + { + "family": "economy", + "route": "api/gamerewards/v1/pending", + "evidence": [ + { + "type": "COICJCJBABL", + "method": "RecRoom.Async.IPromise AOJJODPOJFO()", + "file": "COICJCJBABL.txt", + "line": 520, + "contract": { + "kind": "success-or-empty", + "raw": "RecRoom.Async.IPromise", + "rootType": "void/success", + "typeDescription": { + "name": "void/success", + "found": false, + "kind": "object", + "wireKeys": [] + } + } + } + ], + "contracts": [ + { + "kind": "success-or-empty", + "raw": "RecRoom.Async.IPromise", + "rootType": "void/success", + "typeDescription": { + "name": "void/success", + "found": false, + "kind": "object", + "wireKeys": [] + } + } + ] + }, + { + "family": "economy", + "route": "api/gamerewards/v1/select", + "evidence": [ + { + "type": "COICJCJBABL", + "method": "RecRoom.Async.IPromise ONNNOAKDIIP(COICJCJBABL+HNJGHCGJJFC FDAEIMEHDJJ, System.Int32 IAAKEHDHCAC)", + "file": "COICJCJBABL.txt", + "line": 1055, + "contract": { + "kind": "success-or-empty", + "raw": "RecRoom.Async.IPromise", + "rootType": "void/success", + "typeDescription": { + "name": "void/success", + "found": false, + "kind": "object", + "wireKeys": [] + } + } + } + ], + "contracts": [ + { + "kind": "success-or-empty", + "raw": "RecRoom.Async.IPromise", + "rootType": "void/success", + "typeDescription": { + "name": "void/success", + "found": false, + "kind": "object", + "wireKeys": [] + } + } + ] + }, + { + "family": "economy", + "route": "pageview/consume", + "evidence": [ + { + "type": "COFHGNFJMOG", + "method": "RecRoom.Async.IPromise`1 LFHFLODHPJJ()", + "file": "COFHGNFJMOG.txt", + "line": 95, + "contract": { + "kind": "object", + "raw": "MPHABHIMOOO", + "rootType": "MPHABHIMOOO", + "typeDescription": { + "name": "MPHABHIMOOO", + "found": true, + "file": "MPHABHIMOOO.cs", + "declaration": "public class MPHABHIMOOO : IFAIJAGLDFK", + "kind": "class", + "baseType": "IFAIJAGLDFK", + "properties": [], + "fields": [ + { + "name": "JKKFFNGIEPL", + "type": "double", + "kind": "field" + }, + { + "name": "APBAPGCDANF", + "type": "string", + "kind": "field" + } + ], + "enumValues": [], + "wireKeys": [ + "url", + "freshnessSeconds" + ], + "isilFile": "MPHABHIMOOO.txt", + "isilType": "MPHABHIMOOO" + } + } + } + ], + "contracts": [ + { + "kind": "object", + "raw": "MPHABHIMOOO", + "rootType": "MPHABHIMOOO", + "typeDescription": { + "name": "MPHABHIMOOO", + "found": true, + "file": "MPHABHIMOOO.cs", + "declaration": "public class MPHABHIMOOO : IFAIJAGLDFK", + "kind": "class", + "baseType": "IFAIJAGLDFK", + "properties": [], + "fields": [ + { + "name": "JKKFFNGIEPL", + "type": "double", + "kind": "field" + }, + { + "name": "APBAPGCDANF", + "type": "string", + "kind": "field" + } + ], + "enumValues": [], + "wireKeys": [ + "url", + "freshnessSeconds" + ], + "isilFile": "MPHABHIMOOO.txt", + "isilType": "MPHABHIMOOO" + } + } + ] + }, + { + "family": "elo", + "route": "api/PlayerElo/", + "evidence": [ + { + "type": "RecNet.Elo", + "method": "System.Void UpdatePlayersElo(RecNet.Elo+PlayersEloUpdateDTO JFKDDPJDCDC)", + "file": "RecNet\\Elo.txt", + "line": 141, + "contract": { + "kind": "unknown", + "raw": "", + "rootType": "unknown" + } + } + ], + "contracts": [ + { + "kind": "unknown", + "raw": "", + "rootType": "unknown", + "typeDescription": { + "name": "unknown", + "found": false, + "kind": "object", + "wireKeys": [] + } + } + ] + }, + { + "family": "equipment", + "route": "api/equipment/", + "evidence": [ + { + "type": "ECINAMCDBJO", + "method": "System.Void EEDJOJINECJ()", + "file": "ECINAMCDBJO.txt", + "line": 468, + "contract": { + "kind": "unknown", + "raw": "", + "rootType": "unknown" + } + }, + { + "type": "ECINAMCDBJO+GPBDCOCADGE", + "method": "System.Boolean MoveNext()", + "file": "ECINAMCDBJO_NestedType_GPBDCOCADGE.txt", + "line": 147, + "contract": { + "kind": "unknown", + "raw": "", + "rootType": "unknown" + } + } + ], + "contracts": [ + { + "kind": "unknown", + "raw": "", + "rootType": "unknown", + "typeDescription": { + "name": "unknown", + "found": false, + "kind": "object", + "wireKeys": [] + } + } + ] + }, + { + "family": "groups", + "route": "api/groups/", + "evidence": [ + { + "type": "EJECIMCPGMG", + "method": "RecRoom.Async.IPromise`1 CCDHCLAOFOJ(System.Int64 BCNAOOIPEJO)", + "file": "EJECIMCPGMG.txt", + "line": 337, + "contract": { + "kind": "object", + "raw": "OGKIDDEAFND", + "rootType": "OGKIDDEAFND", + "typeDescription": { + "name": "OGKIDDEAFND", + "found": true, + "file": "OGKIDDEAFND.cs", + "declaration": "public class OGKIDDEAFND : IFAIJAGLDFK", + "kind": "class", + "baseType": "IFAIJAGLDFK", + "properties": [ + { + "name": "ACBFDMLHFPB", + "type": "DateTime", + "accessibility": "public", + "kind": "property" + }, + { + "name": "ADDNGFPJPFL", + "type": "LEIHOJHGJGH", + "accessibility": "public", + "kind": "property" + }, + { + "name": "AHGCOGFEEEE", + "type": "string", + "accessibility": "public", + "kind": "property" + }, + { + "name": "CIPOELOKICH", + "type": "int", + "accessibility": "public", + "kind": "property" + }, + { + "name": "FDPKFOLAJEJ", + "type": "long", + "accessibility": "public", + "kind": "property" + }, + { + "name": "FHAOODLGNJA", + "type": "List", + "accessibility": "public", + "kind": "property" + }, + { + "name": "FIKEBGGCDFN", + "type": "string", + "accessibility": "public", + "kind": "property" + }, + { + "name": "JDMKBKGIKAO", + "type": "int", + "accessibility": "public", + "kind": "property" + }, + { + "name": "KODBEJPEFOJ", + "type": "string", + "accessibility": "public", + "kind": "property" + } + ], + "fields": [], + "enumValues": [], + "wireKeys": [ + "GroupId", + "Name", + "Description", + "CreatedAt", + "ImageName", + "BanStatus", + "CreatorId", + "NumMembers" + ], + "isilFile": "OGKIDDEAFND.txt", + "isilType": "OGKIDDEAFND" + } + } + }, + { + "type": "EJECIMCPGMG", + "method": "RecRoom.Async.IPromise`1 PDJKECHIHNP(System.Int64 CJFGEMGOJHB)", + "file": "EJECIMCPGMG.txt", + "line": 555, + "contract": { + "kind": "object", + "raw": "OGKIDDEAFND", + "rootType": "OGKIDDEAFND", + "typeDescription": { + "name": "OGKIDDEAFND", + "found": true, + "file": "OGKIDDEAFND.cs", + "declaration": "public class OGKIDDEAFND : IFAIJAGLDFK", + "kind": "class", + "baseType": "IFAIJAGLDFK", + "properties": [ + { + "name": "ACBFDMLHFPB", + "type": "DateTime", + "accessibility": "public", + "kind": "property" + }, + { + "name": "ADDNGFPJPFL", + "type": "LEIHOJHGJGH", + "accessibility": "public", + "kind": "property" + }, + { + "name": "AHGCOGFEEEE", + "type": "string", + "accessibility": "public", + "kind": "property" + }, + { + "name": "CIPOELOKICH", + "type": "int", + "accessibility": "public", + "kind": "property" + }, + { + "name": "FDPKFOLAJEJ", + "type": "long", + "accessibility": "public", + "kind": "property" + }, + { + "name": "FHAOODLGNJA", + "type": "List", + "accessibility": "public", + "kind": "property" + }, + { + "name": "FIKEBGGCDFN", + "type": "string", + "accessibility": "public", + "kind": "property" + }, + { + "name": "JDMKBKGIKAO", + "type": "int", + "accessibility": "public", + "kind": "property" + }, + { + "name": "KODBEJPEFOJ", + "type": "string", + "accessibility": "public", + "kind": "property" + } + ], + "fields": [], + "enumValues": [], + "wireKeys": [ + "GroupId", + "Name", + "Description", + "CreatedAt", + "ImageName", + "BanStatus", + "CreatorId", + "NumMembers" + ], + "isilFile": "OGKIDDEAFND.txt", + "isilType": "OGKIDDEAFND" + } + } + }, + { + "type": "EJECIMCPGMG", + "method": "System.Collections.IEnumerator JAHJIFFICHN(System.String NDNLEGKJGCD, System.String LJIGOCDPEJF, System.String HFLPBHHAFIO, BPHGKAEDBPE+OJDJIJDNFHE AFLPGGJMPOE)", + "file": "EJECIMCPGMG.txt", + "line": 803, + "contract": { + "kind": "callback-or-coroutine", + "raw": "IEnumerator", + "rootType": "callback", + "typeDescription": { + "name": "callback", + "found": false, + "kind": "object", + "wireKeys": [] + } + } + }, + { + "type": "EJECIMCPGMG", + "method": "System.Collections.IEnumerator JELCDBGNAHK(System.Int64 BCNAOOIPEJO, BPHGKAEDBPE+OJDJIJDNFHE AFLPGGJMPOE)", + "file": "EJECIMCPGMG.txt", + "line": 1213, + "contract": { + "kind": "callback-or-coroutine", + "raw": "IEnumerator", + "rootType": "callback", + "typeDescription": { + "name": "callback", + "found": false, + "kind": "object", + "wireKeys": [] + } + } + }, + { + "type": "EJECIMCPGMG", + "method": "System.Collections.IEnumerator KBHKIILLNAC(System.Int64 BCNAOOIPEJO, BPHGKAEDBPE+OJDJIJDNFHE AFLPGGJMPOE)", + "file": "EJECIMCPGMG.txt", + "line": 1040, + "contract": { + "kind": "callback-or-coroutine", + "raw": "IEnumerator", + "rootType": "callback", + "typeDescription": { + "name": "callback", + "found": false, + "kind": "object", + "wireKeys": [] + } + } + }, + { + "type": "EJECIMCPGMG", + "method": "System.Collections.IEnumerator NJFPGHDIANJ(System.String NDNLEGKJGCD, BPHGKAEDBPE+OJDJIJDNFHE AFLPGGJMPOE)", + "file": "EJECIMCPGMG.txt", + "line": 1336, + "contract": { + "kind": "callback-or-coroutine", + "raw": "IEnumerator", + "rootType": "callback", + "typeDescription": { + "name": "callback", + "found": false, + "kind": "object", + "wireKeys": [] + } + } + } + ], + "contracts": [ + { + "kind": "object", + "raw": "OGKIDDEAFND", + "rootType": "OGKIDDEAFND", + "typeDescription": { + "name": "OGKIDDEAFND", + "found": true, + "file": "OGKIDDEAFND.cs", + "declaration": "public class OGKIDDEAFND : IFAIJAGLDFK", + "kind": "class", + "baseType": "IFAIJAGLDFK", + "properties": [ + { + "name": "ACBFDMLHFPB", + "type": "DateTime", + "accessibility": "public", + "kind": "property" + }, + { + "name": "ADDNGFPJPFL", + "type": "LEIHOJHGJGH", + "accessibility": "public", + "kind": "property" + }, + { + "name": "AHGCOGFEEEE", + "type": "string", + "accessibility": "public", + "kind": "property" + }, + { + "name": "CIPOELOKICH", + "type": "int", + "accessibility": "public", + "kind": "property" + }, + { + "name": "FDPKFOLAJEJ", + "type": "long", + "accessibility": "public", + "kind": "property" + }, + { + "name": "FHAOODLGNJA", + "type": "List", + "accessibility": "public", + "kind": "property" + }, + { + "name": "FIKEBGGCDFN", + "type": "string", + "accessibility": "public", + "kind": "property" + }, + { + "name": "JDMKBKGIKAO", + "type": "int", + "accessibility": "public", + "kind": "property" + }, + { + "name": "KODBEJPEFOJ", + "type": "string", + "accessibility": "public", + "kind": "property" + } + ], + "fields": [], + "enumValues": [], + "wireKeys": [ + "GroupId", + "Name", + "Description", + "CreatedAt", + "ImageName", + "BanStatus", + "CreatorId", + "NumMembers" + ], + "isilFile": "OGKIDDEAFND.txt", + "isilType": "OGKIDDEAFND" + } + }, + { + "kind": "callback-or-coroutine", + "raw": "IEnumerator", + "rootType": "callback", + "typeDescription": { + "name": "callback", + "found": false, + "kind": "object", + "wireKeys": [] + } + } + ] + }, + { + "family": "images", + "route": "api/images/", + "evidence": [ + { + "type": "OHDHPENHDAP", + "method": "RecRoom.Async.IPromise BFHNENPOEFB(System.Int64 LKNNMPCBCKM, System.Boolean JBAGLLBLEEN)", + "file": "OHDHPENHDAP.txt", + "line": 1571, + "contract": { + "kind": "success-or-empty", + "raw": "RecRoom.Async.IPromise", + "rootType": "void/success", + "typeDescription": { + "name": "void/success", + "found": false, + "kind": "object", + "wireKeys": [] + } + } + }, + { + "type": "OHDHPENHDAP", + "method": "RecRoom.Async.IPromise FEOMMGBJFIN(System.Int64 LKNNMPCBCKM)", + "file": "OHDHPENHDAP.txt", + "line": 2387, + "contract": { + "kind": "success-or-empty", + "raw": "RecRoom.Async.IPromise", + "rootType": "void/success", + "typeDescription": { + "name": "void/success", + "found": false, + "kind": "object", + "wireKeys": [] + } + } + }, + { + "type": "OHDHPENHDAP", + "method": "RecRoom.Async.IPromise OFEBNGPIJMB()", + "file": "OHDHPENHDAP.txt", + "line": 4467, + "contract": { + "kind": "success-or-empty", + "raw": "RecRoom.Async.IPromise", + "rootType": "void/success", + "typeDescription": { + "name": "void/success", + "found": false, + "kind": "object", + "wireKeys": [] + } + } + }, + { + "type": "OHDHPENHDAP", + "method": "RecRoom.Async.IPromise`1 KIIGBNCPAGC()", + "file": "OHDHPENHDAP.txt", + "line": 4847, + "contract": { + "kind": "object", + "raw": "OHDHPENHDAP+OHGBAPBECNM", + "rootType": "OHGBAPBECNM", + "typeDescription": { + "name": "OHGBAPBECNM", + "found": true, + "file": "OHDHPENHDAP.cs", + "declaration": "internal class OHGBAPBECNM : IFAIJAGLDFK", + "kind": "class", + "baseType": "IFAIJAGLDFK", + "properties": [ + { + "name": "DLJBBCDLALH", + "type": "List", + "accessibility": "public", + "kind": "property" + }, + { + "name": "HJJGFONDEFO", + "type": "DateTime", + "accessibility": "public", + "kind": "property" + } + ], + "fields": [], + "enumValues": [], + "wireKeys": [ + "ValidTill" + ], + "isilFile": "OHDHPENHDAP_NestedType_OHGBAPBECNM.txt", + "isilType": "OHDHPENHDAP+OHGBAPBECNM" + } + } + }, + { + "type": "OHDHPENHDAP", + "method": "RecRoom.Async.IPromise`1> NAMIJGLEPMH(System.Int64 HNHLJONGKHB, OHDHPENHDAP+ALLECIACGAH ECENAMEKNEH, OHDHPENHDAP+OGDBPFGJMOO MOIMCDPIPOL, System.Nullable`1 KKDPFGADCEK, System.Nullable`1 MADNOGDODFA)", + "file": "OHDHPENHDAP.txt", + "line": 5403, + "contract": { + "kind": "array", + "raw": "System.Collections.Generic.List`1", + "rootType": "IBANLCLBGLM", + "item": { + "kind": "object", + "raw": "OHDHPENHDAP+IBANLCLBGLM", + "rootType": "IBANLCLBGLM" + }, + "typeDescription": { + "name": "IBANLCLBGLM", + "found": true, + "file": "OHDHPENHDAP.cs", + "declaration": "internal class IBANLCLBGLM : IFAIJAGLDFK", + "kind": "class", + "baseType": "IFAIJAGLDFK", + "properties": [ + { + "name": "ACBFDMLHFPB", + "type": "DateTime", + "accessibility": "public", + "kind": "property" + }, + { + "name": "AHGCOGFEEEE", + "type": "string", + "accessibility": "public", + "kind": "property" + }, + { + "name": "AHNBDMKBAPK", + "type": "int", + "accessibility": "public", + "kind": "property" + }, + { + "name": "DADOKMAOFJL", + "type": "Nullable", + "accessibility": "public", + "kind": "property" + }, + { + "name": "EEOGKBHOJGL", + "type": "int", + "accessibility": "public", + "kind": "property" + }, + { + "name": "EKGNDCHHBLE", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "GBOIPGBGDDG", + "type": "Nullable", + "accessibility": "public", + "kind": "property" + }, + { + "name": "JFEAPMIPNEP", + "type": "IMKNMFOOLGF", + "accessibility": "public", + "kind": "property" + }, + { + "name": "JHJNKJHLJBJ", + "type": "int", + "accessibility": "public", + "kind": "property" + }, + { + "name": "JPOHGBCEJEJ", + "type": "long", + "accessibility": "public", + "kind": "property" + }, + { + "name": "OJNBODIHIHF", + "type": "bool", + "accessibility": "public", + "kind": "property" + }, + { + "name": "OPLHMKFCNOL", + "type": "DLHGJJJPHPH", + "accessibility": "public", + "kind": "property" + }, + { + "name": "PKLADBPMHMC", + "type": "int", + "accessibility": "public", + "kind": "property" + } + ], + "fields": [], + "enumValues": [], + "wireKeys": [ + "Id", + "ImageName", + "PlayerId", + "RoomId", + "PlayerEventId", + "Accessibility", + "AccessibilityLocked", + "Type", + "CreatedAt", + "TaggedPlayerIds", + "CheerCount", + "CommentCount" + ], + "isilFile": "OHDHPENHDAP_NestedType_IBANLCLBGLM.txt", + "isilType": "OHDHPENHDAP+IBANLCLBGLM" + } + } + }, + { + "type": "OHDHPENHDAP", + "method": "RecRoom.Async.IPromise`1 MPNCIPABNKC(System.Byte[] IJKBAADKCBM, OHDHPENHDAP+SavedImageMetaDTO FIBFIEEBFIL)", + "file": "OHDHPENHDAP.txt", + "line": 787, + "contract": { + "kind": "primitive", + "raw": "System.String", + "rootType": "string", + "typeDescription": { + "name": "string", + "found": false, + "kind": "primitive", + "wireKeys": [] + } + } + }, + { + "type": "OHDHPENHDAP", + "method": "System.Collections.IEnumerator BEPFIALDMHK(BPHGKAEDBPE+OJDJIJDNFHE> AFLPGGJMPOE)", + "file": "OHDHPENHDAP.txt", + "line": 394, + "contract": { + "kind": "callback-or-coroutine", + "raw": "IEnumerator", + "rootType": "callback", + "typeDescription": { + "name": "callback", + "found": false, + "kind": "object", + "wireKeys": [] + } + } + }, + { + "type": "OHDHPENHDAP", + "method": "System.Collections.IEnumerator EFGBDOJHLCE(System.String HFLPBHHAFIO, OHDHPENHDAP+IMKNMFOOLGF PONCIIJOHIE, BPHGKAEDBPE+CBEOHBCIPEA AFLPGGJMPOE = null)", + "file": "OHDHPENHDAP.txt", + "line": 1203, + "contract": { + "kind": "callback-or-coroutine", + "raw": "IEnumerator", + "rootType": "callback", + "typeDescription": { + "name": "callback", + "found": false, + "kind": "object", + "wireKeys": [] + } + } + }, + { + "type": "OHDHPENHDAP", + "method": "System.Collections.IEnumerator GLPDDNENLHN(System.String HFLPBHHAFIO, BPHGKAEDBPE+CBEOHBCIPEA AFLPGGJMPOE = null)", + "file": "OHDHPENHDAP.txt", + "line": 1427, + "contract": { + "kind": "callback-or-coroutine", + "raw": "IEnumerator", + "rootType": "callback", + "typeDescription": { + "name": "callback", + "found": false, + "kind": "object", + "wireKeys": [] + } + } + }, + { + "type": "OHDHPENHDAP", + "method": "System.Collections.IEnumerator JPDBMCKFKLA(System.String HFLPBHHAFIO, BPHGKAEDBPE+CBEOHBCIPEA AFLPGGJMPOE = null)", + "file": "OHDHPENHDAP.txt", + "line": 1316, + "contract": { + "kind": "callback-or-coroutine", + "raw": "IEnumerator", + "rootType": "callback", + "typeDescription": { + "name": "callback", + "found": false, + "kind": "object", + "wireKeys": [] + } + } + } + ], + "contracts": [ + { + "kind": "success-or-empty", + "raw": "RecRoom.Async.IPromise", + "rootType": "void/success", + "typeDescription": { + "name": "void/success", + "found": false, + "kind": "object", + "wireKeys": [] + } + }, + { + "kind": "object", + "raw": "OHDHPENHDAP+OHGBAPBECNM", + "rootType": "OHGBAPBECNM", + "typeDescription": { + "name": "OHGBAPBECNM", + "found": true, + "file": "OHDHPENHDAP.cs", + "declaration": "internal class OHGBAPBECNM : IFAIJAGLDFK", + "kind": "class", + "baseType": "IFAIJAGLDFK", + "properties": [ + { + "name": "DLJBBCDLALH", + "type": "List", + "accessibility": "public", + "kind": "property" + }, + { + "name": "HJJGFONDEFO", + "type": "DateTime", + "accessibility": "public", + "kind": "property" + } + ], + "fields": [], + "enumValues": [], + "wireKeys": [ + "ValidTill" + ], + "isilFile": "OHDHPENHDAP_NestedType_OHGBAPBECNM.txt", + "isilType": "OHDHPENHDAP+OHGBAPBECNM" + } + }, + { + "kind": "array", + "raw": "System.Collections.Generic.List`1", + "rootType": "IBANLCLBGLM", + "item": { + "kind": "object", + "raw": "OHDHPENHDAP+IBANLCLBGLM", + "rootType": "IBANLCLBGLM" + }, + "typeDescription": { + "name": "IBANLCLBGLM", + "found": true, + "file": "OHDHPENHDAP.cs", + "declaration": "internal class IBANLCLBGLM : IFAIJAGLDFK", + "kind": "class", + "baseType": "IFAIJAGLDFK", + "properties": [ + { + "name": "ACBFDMLHFPB", + "type": "DateTime", + "accessibility": "public", + "kind": "property" + }, + { + "name": "AHGCOGFEEEE", + "type": "string", + "accessibility": "public", + "kind": "property" + }, + { + "name": "AHNBDMKBAPK", + "type": "int", + "accessibility": "public", + "kind": "property" + }, + { + "name": "DADOKMAOFJL", + "type": "Nullable", + "accessibility": "public", + "kind": "property" + }, + { + "name": "EEOGKBHOJGL", + "type": "int", + "accessibility": "public", + "kind": "property" + }, + { + "name": "EKGNDCHHBLE", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "GBOIPGBGDDG", + "type": "Nullable", + "accessibility": "public", + "kind": "property" + }, + { + "name": "JFEAPMIPNEP", + "type": "IMKNMFOOLGF", + "accessibility": "public", + "kind": "property" + }, + { + "name": "JHJNKJHLJBJ", + "type": "int", + "accessibility": "public", + "kind": "property" + }, + { + "name": "JPOHGBCEJEJ", + "type": "long", + "accessibility": "public", + "kind": "property" + }, + { + "name": "OJNBODIHIHF", + "type": "bool", + "accessibility": "public", + "kind": "property" + }, + { + "name": "OPLHMKFCNOL", + "type": "DLHGJJJPHPH", + "accessibility": "public", + "kind": "property" + }, + { + "name": "PKLADBPMHMC", + "type": "int", + "accessibility": "public", + "kind": "property" + } + ], + "fields": [], + "enumValues": [], + "wireKeys": [ + "Id", + "ImageName", + "PlayerId", + "RoomId", + "PlayerEventId", + "Accessibility", + "AccessibilityLocked", + "Type", + "CreatedAt", + "TaggedPlayerIds", + "CheerCount", + "CommentCount" + ], + "isilFile": "OHDHPENHDAP_NestedType_IBANLCLBGLM.txt", + "isilType": "OHDHPENHDAP+IBANLCLBGLM" + } + }, + { + "kind": "primitive", + "raw": "System.String", + "rootType": "string", + "typeDescription": { + "name": "string", + "found": false, + "kind": "primitive", + "wireKeys": [] + } + }, + { + "kind": "callback-or-coroutine", + "raw": "IEnumerator", + "rootType": "callback", + "typeDescription": { + "name": "callback", + "found": false, + "kind": "object", + "wireKeys": [] + } + } + ] + }, + { + "family": "inventions", + "route": "api/inventions/", + "evidence": [ + { + "type": "BBHENFCNLAB", + "method": "RecRoom.Async.IPromise`1 ANJHOOPIAKM(System.Int64 OEMDIAHHILF, System.Boolean JBAGLLBLEEN)", + "file": "BBHENFCNLAB.txt", + "line": 7610, + "contract": { + "kind": "object", + "raw": "AHEPPAEOLOD", + "rootType": "AHEPPAEOLOD", + "typeDescription": { + "name": "AHEPPAEOLOD", + "found": true, + "file": "AHEPPAEOLOD.cs", + "declaration": "public class AHEPPAEOLOD : IFAIJAGLDFK", + "kind": "class", + "baseType": "IFAIJAGLDFK", + "properties": [ + { + "name": "BFLAPPKDDIN", + "type": "NEFHINKECJJ", + "accessibility": "public", + "kind": "property" + }, + { + "name": "HIMCGOCKLLK", + "type": "JJONIELLJGH", + "accessibility": "public", + "kind": "property" + }, + { + "name": "MEPFFJLDHNP", + "type": "OBBBPCBIMME", + "accessibility": "public", + "kind": "property" + } + ], + "fields": [], + "enumValues": [], + "wireKeys": [ + "Status", + "Invention", + "InventionVersion" + ], + "isilFile": "AHEPPAEOLOD.txt", + "isilType": "AHEPPAEOLOD" + } + } + }, + { + "type": "BBHENFCNLAB", + "method": "RecRoom.Async.IPromise`1 BEPNJCGNIKA(System.Int64 OEMDIAHHILF, HECIICKPCDN AEHDODIANMG, System.Nullable`1 MACNIENMFHJ = null)", + "file": "BBHENFCNLAB.txt", + "line": 6169, + "contract": { + "kind": "object", + "raw": "AHEPPAEOLOD", + "rootType": "AHEPPAEOLOD", + "typeDescription": { + "name": "AHEPPAEOLOD", + "found": true, + "file": "AHEPPAEOLOD.cs", + "declaration": "public class AHEPPAEOLOD : IFAIJAGLDFK", + "kind": "class", + "baseType": "IFAIJAGLDFK", + "properties": [ + { + "name": "BFLAPPKDDIN", + "type": "NEFHINKECJJ", + "accessibility": "public", + "kind": "property" + }, + { + "name": "HIMCGOCKLLK", + "type": "JJONIELLJGH", + "accessibility": "public", + "kind": "property" + }, + { + "name": "MEPFFJLDHNP", + "type": "OBBBPCBIMME", + "accessibility": "public", + "kind": "property" + } + ], + "fields": [], + "enumValues": [], + "wireKeys": [ + "Status", + "Invention", + "InventionVersion" + ], + "isilFile": "AHEPPAEOLOD.txt", + "isilType": "AHEPPAEOLOD" + } + } + }, + { + "type": "BBHENFCNLAB", + "method": "RecRoom.Async.IPromise`1 HDFICFBNFOK(System.Int64 OEMDIAHHILF, System.Int32 MACNIENMFHJ)", + "file": "BBHENFCNLAB.txt", + "line": 6479, + "contract": { + "kind": "object", + "raw": "AHEPPAEOLOD", + "rootType": "AHEPPAEOLOD", + "typeDescription": { + "name": "AHEPPAEOLOD", + "found": true, + "file": "AHEPPAEOLOD.cs", + "declaration": "public class AHEPPAEOLOD : IFAIJAGLDFK", + "kind": "class", + "baseType": "IFAIJAGLDFK", + "properties": [ + { + "name": "BFLAPPKDDIN", + "type": "NEFHINKECJJ", + "accessibility": "public", + "kind": "property" + }, + { + "name": "HIMCGOCKLLK", + "type": "JJONIELLJGH", + "accessibility": "public", + "kind": "property" + }, + { + "name": "MEPFFJLDHNP", + "type": "OBBBPCBIMME", + "accessibility": "public", + "kind": "property" + } + ], + "fields": [], + "enumValues": [], + "wireKeys": [ + "Status", + "Invention", + "InventionVersion" + ], + "isilFile": "AHEPPAEOLOD.txt", + "isilType": "AHEPPAEOLOD" + } + } + }, + { + "type": "BBHENFCNLAB", + "method": "RecRoom.Async.IPromise`1 HELBOGAPCKE(OBBBPCBIMME LDNFNNNHPPB)", + "file": "BBHENFCNLAB.txt", + "line": 5990, + "contract": { + "kind": "object", + "raw": "AHEPPAEOLOD", + "rootType": "AHEPPAEOLOD", + "typeDescription": { + "name": "AHEPPAEOLOD", + "found": true, + "file": "AHEPPAEOLOD.cs", + "declaration": "public class AHEPPAEOLOD : IFAIJAGLDFK", + "kind": "class", + "baseType": "IFAIJAGLDFK", + "properties": [ + { + "name": "BFLAPPKDDIN", + "type": "NEFHINKECJJ", + "accessibility": "public", + "kind": "property" + }, + { + "name": "HIMCGOCKLLK", + "type": "JJONIELLJGH", + "accessibility": "public", + "kind": "property" + }, + { + "name": "MEPFFJLDHNP", + "type": "OBBBPCBIMME", + "accessibility": "public", + "kind": "property" + } + ], + "fields": [], + "enumValues": [], + "wireKeys": [ + "Status", + "Invention", + "InventionVersion" + ], + "isilFile": "AHEPPAEOLOD.txt", + "isilType": "AHEPPAEOLOD" + } + } + }, + { + "type": "BBHENFCNLAB", + "method": "RecRoom.Async.IPromise`1 MEIJBGBINLO(System.Int64 OEMDIAHHILF)", + "file": "BBHENFCNLAB.txt", + "line": 6327, + "contract": { + "kind": "object", + "raw": "AHEPPAEOLOD", + "rootType": "AHEPPAEOLOD", + "typeDescription": { + "name": "AHEPPAEOLOD", + "found": true, + "file": "AHEPPAEOLOD.cs", + "declaration": "public class AHEPPAEOLOD : IFAIJAGLDFK", + "kind": "class", + "baseType": "IFAIJAGLDFK", + "properties": [ + { + "name": "BFLAPPKDDIN", + "type": "NEFHINKECJJ", + "accessibility": "public", + "kind": "property" + }, + { + "name": "HIMCGOCKLLK", + "type": "JJONIELLJGH", + "accessibility": "public", + "kind": "property" + }, + { + "name": "MEPFFJLDHNP", + "type": "OBBBPCBIMME", + "accessibility": "public", + "kind": "property" + } + ], + "fields": [], + "enumValues": [], + "wireKeys": [ + "Status", + "Invention", + "InventionVersion" + ], + "isilFile": "AHEPPAEOLOD.txt", + "isilType": "AHEPPAEOLOD" + } + } + }, + { + "type": "BBHENFCNLAB", + "method": "RecRoom.Async.IPromise`1 NNDFHPLBNEN(System.Int64 OEMDIAHHILF, System.String LJIGOCDPEJF)", + "file": "BBHENFCNLAB.txt", + "line": 1731, + "contract": { + "kind": "object", + "raw": "AHEPPAEOLOD", + "rootType": "AHEPPAEOLOD", + "typeDescription": { + "name": "AHEPPAEOLOD", + "found": true, + "file": "AHEPPAEOLOD.cs", + "declaration": "public class AHEPPAEOLOD : IFAIJAGLDFK", + "kind": "class", + "baseType": "IFAIJAGLDFK", + "properties": [ + { + "name": "BFLAPPKDDIN", + "type": "NEFHINKECJJ", + "accessibility": "public", + "kind": "property" + }, + { + "name": "HIMCGOCKLLK", + "type": "JJONIELLJGH", + "accessibility": "public", + "kind": "property" + }, + { + "name": "MEPFFJLDHNP", + "type": "OBBBPCBIMME", + "accessibility": "public", + "kind": "property" + } + ], + "fields": [], + "enumValues": [], + "wireKeys": [ + "Status", + "Invention", + "InventionVersion" + ], + "isilFile": "AHEPPAEOLOD.txt", + "isilType": "AHEPPAEOLOD" + } + } + }, + { + "type": "BBHENFCNLAB", + "method": "RecRoom.Async.IPromise`1 OCBDPGLHIJL(System.Int64 OEMDIAHHILF, HECIICKPCDN ODABIMLCOIP)", + "file": "BBHENFCNLAB.txt", + "line": 2123, + "contract": { + "kind": "object", + "raw": "AHEPPAEOLOD", + "rootType": "AHEPPAEOLOD", + "typeDescription": { + "name": "AHEPPAEOLOD", + "found": true, + "file": "AHEPPAEOLOD.cs", + "declaration": "public class AHEPPAEOLOD : IFAIJAGLDFK", + "kind": "class", + "baseType": "IFAIJAGLDFK", + "properties": [ + { + "name": "BFLAPPKDDIN", + "type": "NEFHINKECJJ", + "accessibility": "public", + "kind": "property" + }, + { + "name": "HIMCGOCKLLK", + "type": "JJONIELLJGH", + "accessibility": "public", + "kind": "property" + }, + { + "name": "MEPFFJLDHNP", + "type": "OBBBPCBIMME", + "accessibility": "public", + "kind": "property" + } + ], + "fields": [], + "enumValues": [], + "wireKeys": [ + "Status", + "Invention", + "InventionVersion" + ], + "isilFile": "AHEPPAEOLOD.txt", + "isilType": "AHEPPAEOLOD" + } + } + }, + { + "type": "BBHENFCNLAB", + "method": "RecRoom.Async.IPromise`1 ONLOPCCOFMM(System.Int64 OEMDIAHHILF, System.String DLFBBIAHDMO)", + "file": "BBHENFCNLAB.txt", + "line": 1927, + "contract": { + "kind": "object", + "raw": "AHEPPAEOLOD", + "rootType": "AHEPPAEOLOD", + "typeDescription": { + "name": "AHEPPAEOLOD", + "found": true, + "file": "AHEPPAEOLOD.cs", + "declaration": "public class AHEPPAEOLOD : IFAIJAGLDFK", + "kind": "class", + "baseType": "IFAIJAGLDFK", + "properties": [ + { + "name": "BFLAPPKDDIN", + "type": "NEFHINKECJJ", + "accessibility": "public", + "kind": "property" + }, + { + "name": "HIMCGOCKLLK", + "type": "JJONIELLJGH", + "accessibility": "public", + "kind": "property" + }, + { + "name": "MEPFFJLDHNP", + "type": "OBBBPCBIMME", + "accessibility": "public", + "kind": "property" + } + ], + "fields": [], + "enumValues": [], + "wireKeys": [ + "Status", + "Invention", + "InventionVersion" + ], + "isilFile": "AHEPPAEOLOD.txt", + "isilType": "AHEPPAEOLOD" + } + } + }, + { + "type": "BBHENFCNLAB", + "method": "RecRoom.Async.IPromise`1 PONHAAALIGM(System.Int64 OEMDIAHHILF, System.String MMBOKOLAJFH)", + "file": "BBHENFCNLAB.txt", + "line": 1533, + "contract": { + "kind": "object", + "raw": "AHEPPAEOLOD", + "rootType": "AHEPPAEOLOD", + "typeDescription": { + "name": "AHEPPAEOLOD", + "found": true, + "file": "AHEPPAEOLOD.cs", + "declaration": "public class AHEPPAEOLOD : IFAIJAGLDFK", + "kind": "class", + "baseType": "IFAIJAGLDFK", + "properties": [ + { + "name": "BFLAPPKDDIN", + "type": "NEFHINKECJJ", + "accessibility": "public", + "kind": "property" + }, + { + "name": "HIMCGOCKLLK", + "type": "JJONIELLJGH", + "accessibility": "public", + "kind": "property" + }, + { + "name": "MEPFFJLDHNP", + "type": "OBBBPCBIMME", + "accessibility": "public", + "kind": "property" + } + ], + "fields": [], + "enumValues": [], + "wireKeys": [ + "Status", + "Invention", + "InventionVersion" + ], + "isilFile": "AHEPPAEOLOD.txt", + "isilType": "AHEPPAEOLOD" + } + } + }, + { + "type": "BBHENFCNLAB", + "method": "RecRoom.Async.IPromise`1 IIPHPOBADGF(System.Int64 OEMDIAHHILF)", + "file": "BBHENFCNLAB.txt", + "line": 3629, + "contract": { + "kind": "object", + "raw": "HJPDBNLCGIB", + "rootType": "HJPDBNLCGIB", + "typeDescription": { + "name": "HJPDBNLCGIB", + "found": true, + "file": "HJPDBNLCGIB.cs", + "declaration": "public class HJPDBNLCGIB : IFAIJAGLDFK", + "kind": "class", + "baseType": "IFAIJAGLDFK", + "properties": [ + { + "name": "PKKADKGDHNI", + "type": "List", + "accessibility": "public", + "kind": "property" + } + ], + "fields": [], + "enumValues": [], + "wireKeys": [], + "isilFile": "HJPDBNLCGIB.txt", + "isilType": "HJPDBNLCGIB" + } + } + }, + { + "type": "BBHENFCNLAB", + "method": "RecRoom.Async.IPromise`1 BGDGKHPGEHK(System.Int64 OEMDIAHHILF, FDEGHHFBJJO MEABFEIBEMP, System.String EFDBFLPKHKA)", + "file": "BBHENFCNLAB.txt", + "line": 7417, + "contract": { + "kind": "object", + "raw": "KLAMKCBENEA", + "rootType": "KLAMKCBENEA", + "typeDescription": { + "name": "KLAMKCBENEA", + "found": true, + "file": "KLAMKCBENEA.cs", + "declaration": "public class KLAMKCBENEA : IFAIJAGLDFK", + "kind": "class", + "baseType": "IFAIJAGLDFK", + "properties": [ + { + "name": "DFPPNHINEFO", + "type": "string", + "accessibility": "public", + "kind": "property" + }, + { + "name": "ONGEANEKLNE", + "type": "bool", + "accessibility": "public", + "kind": "property" + } + ], + "fields": [], + "enumValues": [], + "wireKeys": [ + "Success", + "Message" + ], + "isilFile": "KLAMKCBENEA.txt", + "isilType": "KLAMKCBENEA" + } + } + }, + { + "type": "BBHENFCNLAB", + "method": "RecRoom.Async.IPromise`1 LGFHMMCICNF(System.Int64 OEMDIAHHILF, System.Collections.Generic.List`1 LBAOCHFCLPO, System.Collections.Generic.List`1 NOLONFFJNPA)", + "file": "BBHENFCNLAB.txt", + "line": 2353, + "contract": { + "kind": "object", + "raw": "NJMAEIPIOAP", + "rootType": "NJMAEIPIOAP", + "typeDescription": { + "name": "NJMAEIPIOAP", + "found": true, + "file": "NJMAEIPIOAP.cs", + "declaration": "public class NJMAEIPIOAP : IFAIJAGLDFK", + "kind": "class", + "baseType": "IFAIJAGLDFK", + "properties": [ + { + "name": "GMFIDNACDJK", + "type": "OCLOGFHCPHN", + "accessibility": "public", + "kind": "property" + }, + { + "name": "PKKADKGDHNI", + "type": "List", + "accessibility": "public", + "kind": "property" + } + ], + "fields": [], + "enumValues": [], + "wireKeys": [ + "Result" + ], + "isilFile": "NJMAEIPIOAP.txt", + "isilType": "NJMAEIPIOAP" + } + } + }, + { + "type": "BBHENFCNLAB", + "method": "RecRoom.Async.IPromise`1 FDEMGICNKPI(System.Int64 OEMDIAHHILF)", + "file": "BBHENFCNLAB.txt", + "line": 3127, + "contract": { + "kind": "object", + "raw": "OBBBPCBIMME", + "rootType": "OBBBPCBIMME", + "typeDescription": { + "name": "OBBBPCBIMME", + "found": true, + "file": "OBBBPCBIMME.cs", + "declaration": "public class OBBBPCBIMME : IFAIJAGLDFK", + "kind": "class", + "baseType": "IFAIJAGLDFK", + "properties": [ + { + "name": "ACBFDMLHFPB", + "type": "DateTime", + "accessibility": "public", + "kind": "property" + }, + { + "name": "ACHMMBMLGEK", + "type": "int", + "accessibility": "public", + "kind": "property" + }, + { + "name": "AHGCOGFEEEE", + "type": "string", + "accessibility": "public", + "kind": "property" + }, + { + "name": "AJHBGBHGGAL", + "type": "long", + "accessibility": "public", + "kind": "property" + }, + { + "name": "BAELCDIJIPJ", + "type": "HECIICKPCDN", + "accessibility": "public", + "kind": "property" + }, + { + "name": "DKHJPGMEOHF", + "type": "int", + "accessibility": "public", + "kind": "property" + }, + { + "name": "FIKEBGGCDFN", + "type": "string", + "accessibility": "public", + "kind": "property" + }, + { + "name": "GCAABGAEDPP", + "type": "bool", + "accessibility": "public", + "kind": "property" + }, + { + "name": "HKFOKCNGNFA", + "type": "int", + "accessibility": "public", + "kind": "property" + }, + { + "name": "ILKLEFCCJMN", + "type": "bool", + "accessibility": "public", + "kind": "property" + }, + { + "name": "JHJNKJHLJBJ", + "type": "int", + "accessibility": "public", + "kind": "property" + }, + { + "name": "JOHEKKGMAJB", + "type": "int", + "accessibility": "public", + "kind": "property" + }, + { + "name": "KBDMNJGJACC", + "type": "DateTime", + "accessibility": "public", + "kind": "property" + }, + { + "name": "KLLMGMJJICK", + "type": "Nullable", + "accessibility": "public", + "kind": "property" + }, + { + "name": "KODBEJPEFOJ", + "type": "string", + "accessibility": "public", + "kind": "property" + }, + { + "name": "MLDAFKCENMA", + "type": "string", + "accessibility": "public", + "kind": "property" + }, + { + "name": "MLMDFDKLELH", + "type": "HECIICKPCDN", + "accessibility": "public", + "kind": "property" + }, + { + "name": "MMPKOCENIIP", + "type": "bool", + "accessibility": "public", + "kind": "property" + }, + { + "name": "NFNIPMIEMPM", + "type": "bool", + "accessibility": "public", + "kind": "property" + }, + { + "name": "NPDJLOOHMDJ", + "type": "Nullable", + "accessibility": "public", + "kind": "property" + }, + { + "name": "PJNJIKKCBNA", + "type": "Nullable", + "accessibility": "public", + "kind": "property" + } + ], + "fields": [], + "enumValues": [], + "wireKeys": [ + "InventionId", + "ReplicationId", + "CreatorPlayerId", + "Name", + "Description", + "ImageName", + "CurrentVersionNumber", + "IsPublished", + "AllowTrial", + "ModifiedAt", + "CreatedAt", + "NumPlayersHaveUsedInRoom", + "NumDownloads", + "CheerCount", + "CreatorPermission", + "GeneralPermission", + "IsAGInvention", + "Price", + "HideFromPlayer" + ], + "isilFile": "OBBBPCBIMME.txt", + "isilType": "OBBBPCBIMME" + } + } + }, + { + "type": "BBHENFCNLAB", + "method": "RecRoom.Async.IPromise`1 IGFBODDECPK(System.Int64 OEMDIAHHILF)", + "file": "BBHENFCNLAB.txt", + "line": 5821, + "contract": { + "kind": "object", + "raw": "OEGPIPBKHCN", + "rootType": "OEGPIPBKHCN", + "typeDescription": { + "name": "OEGPIPBKHCN", + "found": true, + "file": "OEGPIPBKHCN.cs", + "declaration": "public class OEGPIPBKHCN : IFAIJAGLDFK", + "kind": "class", + "baseType": "IFAIJAGLDFK", + "properties": [ + { + "name": "GOAELNACNNJ", + "type": "bool", + "accessibility": "public", + "kind": "property" + } + ], + "fields": [], + "enumValues": [], + "wireKeys": [ + "IsCheering" + ], + "isilFile": "OEGPIPBKHCN.txt", + "isilType": "OEGPIPBKHCN" + } + } + }, + { + "type": "BBHENFCNLAB", + "method": "RecRoom.Async.IPromise`1> OFPICHENKNL(System.Int64 OEMDIAHHILF)", + "file": "BBHENFCNLAB.txt", + "line": 4782, + "contract": { + "kind": "array", + "raw": "System.Collections.Generic.List`1", + "rootType": "NEFHINKECJJ", + "item": { + "kind": "object", + "raw": "NEFHINKECJJ", + "rootType": "NEFHINKECJJ" + }, + "typeDescription": { + "name": "NEFHINKECJJ", + "found": true, + "file": "NEFHINKECJJ.cs", + "declaration": "public class NEFHINKECJJ : IFAIJAGLDFK", + "kind": "class", + "baseType": "IFAIJAGLDFK", + "properties": [ + { + "name": "AJHBGBHGGAL", + "type": "long", + "accessibility": "public", + "kind": "property" + }, + { + "name": "CLODONFLECP", + "type": "int", + "accessibility": "public", + "kind": "property" + }, + { + "name": "JKDGKHANBMB", + "type": "int", + "accessibility": "public", + "kind": "property" + }, + { + "name": "KHMMKHPAMLL", + "type": "int", + "accessibility": "public", + "kind": "property" + }, + { + "name": "MLDAFKCENMA", + "type": "string", + "accessibility": "public", + "kind": "property" + }, + { + "name": "NIFBPEMIADP", + "type": "string", + "accessibility": "public", + "kind": "property" + } + ], + "fields": [], + "enumValues": [], + "wireKeys": [ + "InventionId", + "ReplicationId", + "VersionNumber", + "InstantiationCost", + "LightsCost", + "BlobName" + ], + "isilFile": "NEFHINKECJJ.txt", + "isilType": "NEFHINKECJJ" + } + } + }, + { + "type": "BBHENFCNLAB", + "method": "RecRoom.Async.IPromise`1> CKGFHIMCOHI(System.String DFAMLDBFENB)", + "file": "BBHENFCNLAB.txt", + "line": 7207, + "contract": { + "kind": "array", + "raw": "System.Collections.Generic.List`1", + "rootType": "OBBBPCBIMME", + "item": { + "kind": "object", + "raw": "OBBBPCBIMME", + "rootType": "OBBBPCBIMME" + }, + "typeDescription": { + "name": "OBBBPCBIMME", + "found": true, + "file": "OBBBPCBIMME.cs", + "declaration": "public class OBBBPCBIMME : IFAIJAGLDFK", + "kind": "class", + "baseType": "IFAIJAGLDFK", + "properties": [ + { + "name": "ACBFDMLHFPB", + "type": "DateTime", + "accessibility": "public", + "kind": "property" + }, + { + "name": "ACHMMBMLGEK", + "type": "int", + "accessibility": "public", + "kind": "property" + }, + { + "name": "AHGCOGFEEEE", + "type": "string", + "accessibility": "public", + "kind": "property" + }, + { + "name": "AJHBGBHGGAL", + "type": "long", + "accessibility": "public", + "kind": "property" + }, + { + "name": "BAELCDIJIPJ", + "type": "HECIICKPCDN", + "accessibility": "public", + "kind": "property" + }, + { + "name": "DKHJPGMEOHF", + "type": "int", + "accessibility": "public", + "kind": "property" + }, + { + "name": "FIKEBGGCDFN", + "type": "string", + "accessibility": "public", + "kind": "property" + }, + { + "name": "GCAABGAEDPP", + "type": "bool", + "accessibility": "public", + "kind": "property" + }, + { + "name": "HKFOKCNGNFA", + "type": "int", + "accessibility": "public", + "kind": "property" + }, + { + "name": "ILKLEFCCJMN", + "type": "bool", + "accessibility": "public", + "kind": "property" + }, + { + "name": "JHJNKJHLJBJ", + "type": "int", + "accessibility": "public", + "kind": "property" + }, + { + "name": "JOHEKKGMAJB", + "type": "int", + "accessibility": "public", + "kind": "property" + }, + { + "name": "KBDMNJGJACC", + "type": "DateTime", + "accessibility": "public", + "kind": "property" + }, + { + "name": "KLLMGMJJICK", + "type": "Nullable", + "accessibility": "public", + "kind": "property" + }, + { + "name": "KODBEJPEFOJ", + "type": "string", + "accessibility": "public", + "kind": "property" + }, + { + "name": "MLDAFKCENMA", + "type": "string", + "accessibility": "public", + "kind": "property" + }, + { + "name": "MLMDFDKLELH", + "type": "HECIICKPCDN", + "accessibility": "public", + "kind": "property" + }, + { + "name": "MMPKOCENIIP", + "type": "bool", + "accessibility": "public", + "kind": "property" + }, + { + "name": "NFNIPMIEMPM", + "type": "bool", + "accessibility": "public", + "kind": "property" + }, + { + "name": "NPDJLOOHMDJ", + "type": "Nullable", + "accessibility": "public", + "kind": "property" + }, + { + "name": "PJNJIKKCBNA", + "type": "Nullable", + "accessibility": "public", + "kind": "property" + } + ], + "fields": [], + "enumValues": [], + "wireKeys": [ + "InventionId", + "ReplicationId", + "CreatorPlayerId", + "Name", + "Description", + "ImageName", + "CurrentVersionNumber", + "IsPublished", + "AllowTrial", + "ModifiedAt", + "CreatedAt", + "NumPlayersHaveUsedInRoom", + "NumDownloads", + "CheerCount", + "CreatorPermission", + "GeneralPermission", + "IsAGInvention", + "Price", + "HideFromPlayer" + ], + "isilFile": "OBBBPCBIMME.txt", + "isilType": "OBBBPCBIMME" + } + } + }, + { + "type": "BBHENFCNLAB", + "method": "RecRoom.Async.IPromise`1> CKGFHIMCOHI(System.String DFAMLDBFENB)", + "file": "BBHENFCNLAB.txt", + "line": 7213, + "contract": { + "kind": "array", + "raw": "System.Collections.Generic.List`1", + "rootType": "OBBBPCBIMME", + "item": { + "kind": "object", + "raw": "OBBBPCBIMME", + "rootType": "OBBBPCBIMME" + }, + "typeDescription": { + "name": "OBBBPCBIMME", + "found": true, + "file": "OBBBPCBIMME.cs", + "declaration": "public class OBBBPCBIMME : IFAIJAGLDFK", + "kind": "class", + "baseType": "IFAIJAGLDFK", + "properties": [ + { + "name": "ACBFDMLHFPB", + "type": "DateTime", + "accessibility": "public", + "kind": "property" + }, + { + "name": "ACHMMBMLGEK", + "type": "int", + "accessibility": "public", + "kind": "property" + }, + { + "name": "AHGCOGFEEEE", + "type": "string", + "accessibility": "public", + "kind": "property" + }, + { + "name": "AJHBGBHGGAL", + "type": "long", + "accessibility": "public", + "kind": "property" + }, + { + "name": "BAELCDIJIPJ", + "type": "HECIICKPCDN", + "accessibility": "public", + "kind": "property" + }, + { + "name": "DKHJPGMEOHF", + "type": "int", + "accessibility": "public", + "kind": "property" + }, + { + "name": "FIKEBGGCDFN", + "type": "string", + "accessibility": "public", + "kind": "property" + }, + { + "name": "GCAABGAEDPP", + "type": "bool", + "accessibility": "public", + "kind": "property" + }, + { + "name": "HKFOKCNGNFA", + "type": "int", + "accessibility": "public", + "kind": "property" + }, + { + "name": "ILKLEFCCJMN", + "type": "bool", + "accessibility": "public", + "kind": "property" + }, + { + "name": "JHJNKJHLJBJ", + "type": "int", + "accessibility": "public", + "kind": "property" + }, + { + "name": "JOHEKKGMAJB", + "type": "int", + "accessibility": "public", + "kind": "property" + }, + { + "name": "KBDMNJGJACC", + "type": "DateTime", + "accessibility": "public", + "kind": "property" + }, + { + "name": "KLLMGMJJICK", + "type": "Nullable", + "accessibility": "public", + "kind": "property" + }, + { + "name": "KODBEJPEFOJ", + "type": "string", + "accessibility": "public", + "kind": "property" + }, + { + "name": "MLDAFKCENMA", + "type": "string", + "accessibility": "public", + "kind": "property" + }, + { + "name": "MLMDFDKLELH", + "type": "HECIICKPCDN", + "accessibility": "public", + "kind": "property" + }, + { + "name": "MMPKOCENIIP", + "type": "bool", + "accessibility": "public", + "kind": "property" + }, + { + "name": "NFNIPMIEMPM", + "type": "bool", + "accessibility": "public", + "kind": "property" + }, + { + "name": "NPDJLOOHMDJ", + "type": "Nullable", + "accessibility": "public", + "kind": "property" + }, + { + "name": "PJNJIKKCBNA", + "type": "Nullable", + "accessibility": "public", + "kind": "property" + } + ], + "fields": [], + "enumValues": [], + "wireKeys": [ + "InventionId", + "ReplicationId", + "CreatorPlayerId", + "Name", + "Description", + "ImageName", + "CurrentVersionNumber", + "IsPublished", + "AllowTrial", + "ModifiedAt", + "CreatedAt", + "NumPlayersHaveUsedInRoom", + "NumDownloads", + "CheerCount", + "CreatorPermission", + "GeneralPermission", + "IsAGInvention", + "Price", + "HideFromPlayer" + ], + "isilFile": "OBBBPCBIMME.txt", + "isilType": "OBBBPCBIMME" + } + } + }, + { + "type": "BBHENFCNLAB", + "method": "RecRoom.Async.IPromise`1> FDABNLEFMKM(System.Int64 HNHLJONGKHB)", + "file": "BBHENFCNLAB.txt", + "line": 5140, + "contract": { + "kind": "array", + "raw": "System.Collections.Generic.List`1", + "rootType": "OBBBPCBIMME", + "item": { + "kind": "object", + "raw": "OBBBPCBIMME", + "rootType": "OBBBPCBIMME" + }, + "typeDescription": { + "name": "OBBBPCBIMME", + "found": true, + "file": "OBBBPCBIMME.cs", + "declaration": "public class OBBBPCBIMME : IFAIJAGLDFK", + "kind": "class", + "baseType": "IFAIJAGLDFK", + "properties": [ + { + "name": "ACBFDMLHFPB", + "type": "DateTime", + "accessibility": "public", + "kind": "property" + }, + { + "name": "ACHMMBMLGEK", + "type": "int", + "accessibility": "public", + "kind": "property" + }, + { + "name": "AHGCOGFEEEE", + "type": "string", + "accessibility": "public", + "kind": "property" + }, + { + "name": "AJHBGBHGGAL", + "type": "long", + "accessibility": "public", + "kind": "property" + }, + { + "name": "BAELCDIJIPJ", + "type": "HECIICKPCDN", + "accessibility": "public", + "kind": "property" + }, + { + "name": "DKHJPGMEOHF", + "type": "int", + "accessibility": "public", + "kind": "property" + }, + { + "name": "FIKEBGGCDFN", + "type": "string", + "accessibility": "public", + "kind": "property" + }, + { + "name": "GCAABGAEDPP", + "type": "bool", + "accessibility": "public", + "kind": "property" + }, + { + "name": "HKFOKCNGNFA", + "type": "int", + "accessibility": "public", + "kind": "property" + }, + { + "name": "ILKLEFCCJMN", + "type": "bool", + "accessibility": "public", + "kind": "property" + }, + { + "name": "JHJNKJHLJBJ", + "type": "int", + "accessibility": "public", + "kind": "property" + }, + { + "name": "JOHEKKGMAJB", + "type": "int", + "accessibility": "public", + "kind": "property" + }, + { + "name": "KBDMNJGJACC", + "type": "DateTime", + "accessibility": "public", + "kind": "property" + }, + { + "name": "KLLMGMJJICK", + "type": "Nullable", + "accessibility": "public", + "kind": "property" + }, + { + "name": "KODBEJPEFOJ", + "type": "string", + "accessibility": "public", + "kind": "property" + }, + { + "name": "MLDAFKCENMA", + "type": "string", + "accessibility": "public", + "kind": "property" + }, + { + "name": "MLMDFDKLELH", + "type": "HECIICKPCDN", + "accessibility": "public", + "kind": "property" + }, + { + "name": "MMPKOCENIIP", + "type": "bool", + "accessibility": "public", + "kind": "property" + }, + { + "name": "NFNIPMIEMPM", + "type": "bool", + "accessibility": "public", + "kind": "property" + }, + { + "name": "NPDJLOOHMDJ", + "type": "Nullable", + "accessibility": "public", + "kind": "property" + }, + { + "name": "PJNJIKKCBNA", + "type": "Nullable", + "accessibility": "public", + "kind": "property" + } + ], + "fields": [], + "enumValues": [], + "wireKeys": [ + "InventionId", + "ReplicationId", + "CreatorPlayerId", + "Name", + "Description", + "ImageName", + "CurrentVersionNumber", + "IsPublished", + "AllowTrial", + "ModifiedAt", + "CreatedAt", + "NumPlayersHaveUsedInRoom", + "NumDownloads", + "CheerCount", + "CreatorPermission", + "GeneralPermission", + "IsAGInvention", + "Price", + "HideFromPlayer" + ], + "isilFile": "OBBBPCBIMME.txt", + "isilType": "OBBBPCBIMME" + } + } + }, + { + "type": "BBHENFCNLAB", + "method": "RecRoom.Async.IPromise`1> JLGCBDHFIBH(System.Collections.Generic.List`1 POGCOENDJDJ)", + "file": "BBHENFCNLAB.txt", + "line": 3371, + "contract": { + "kind": "array", + "raw": "System.Collections.Generic.List`1", + "rootType": "OBBBPCBIMME", + "item": { + "kind": "object", + "raw": "OBBBPCBIMME", + "rootType": "OBBBPCBIMME" + }, + "typeDescription": { + "name": "OBBBPCBIMME", + "found": true, + "file": "OBBBPCBIMME.cs", + "declaration": "public class OBBBPCBIMME : IFAIJAGLDFK", + "kind": "class", + "baseType": "IFAIJAGLDFK", + "properties": [ + { + "name": "ACBFDMLHFPB", + "type": "DateTime", + "accessibility": "public", + "kind": "property" + }, + { + "name": "ACHMMBMLGEK", + "type": "int", + "accessibility": "public", + "kind": "property" + }, + { + "name": "AHGCOGFEEEE", + "type": "string", + "accessibility": "public", + "kind": "property" + }, + { + "name": "AJHBGBHGGAL", + "type": "long", + "accessibility": "public", + "kind": "property" + }, + { + "name": "BAELCDIJIPJ", + "type": "HECIICKPCDN", + "accessibility": "public", + "kind": "property" + }, + { + "name": "DKHJPGMEOHF", + "type": "int", + "accessibility": "public", + "kind": "property" + }, + { + "name": "FIKEBGGCDFN", + "type": "string", + "accessibility": "public", + "kind": "property" + }, + { + "name": "GCAABGAEDPP", + "type": "bool", + "accessibility": "public", + "kind": "property" + }, + { + "name": "HKFOKCNGNFA", + "type": "int", + "accessibility": "public", + "kind": "property" + }, + { + "name": "ILKLEFCCJMN", + "type": "bool", + "accessibility": "public", + "kind": "property" + }, + { + "name": "JHJNKJHLJBJ", + "type": "int", + "accessibility": "public", + "kind": "property" + }, + { + "name": "JOHEKKGMAJB", + "type": "int", + "accessibility": "public", + "kind": "property" + }, + { + "name": "KBDMNJGJACC", + "type": "DateTime", + "accessibility": "public", + "kind": "property" + }, + { + "name": "KLLMGMJJICK", + "type": "Nullable", + "accessibility": "public", + "kind": "property" + }, + { + "name": "KODBEJPEFOJ", + "type": "string", + "accessibility": "public", + "kind": "property" + }, + { + "name": "MLDAFKCENMA", + "type": "string", + "accessibility": "public", + "kind": "property" + }, + { + "name": "MLMDFDKLELH", + "type": "HECIICKPCDN", + "accessibility": "public", + "kind": "property" + }, + { + "name": "MMPKOCENIIP", + "type": "bool", + "accessibility": "public", + "kind": "property" + }, + { + "name": "NFNIPMIEMPM", + "type": "bool", + "accessibility": "public", + "kind": "property" + }, + { + "name": "NPDJLOOHMDJ", + "type": "Nullable", + "accessibility": "public", + "kind": "property" + }, + { + "name": "PJNJIKKCBNA", + "type": "Nullable", + "accessibility": "public", + "kind": "property" + } + ], + "fields": [], + "enumValues": [], + "wireKeys": [ + "InventionId", + "ReplicationId", + "CreatorPlayerId", + "Name", + "Description", + "ImageName", + "CurrentVersionNumber", + "IsPublished", + "AllowTrial", + "ModifiedAt", + "CreatedAt", + "NumPlayersHaveUsedInRoom", + "NumDownloads", + "CheerCount", + "CreatorPermission", + "GeneralPermission", + "IsAGInvention", + "Price", + "HideFromPlayer" + ], + "isilFile": "OBBBPCBIMME.txt", + "isilType": "OBBBPCBIMME" + } + } + }, + { + "type": "BBHENFCNLAB", + "method": "RecRoom.Async.IPromise`1> LIPBAJOAIJC(BBHENFCNLAB+IBIIKANECFA KINAEJLCDEG)", + "file": "BBHENFCNLAB.txt", + "line": 5586, + "contract": { + "kind": "array", + "raw": "System.Collections.Generic.List`1", + "rootType": "String", + "item": { + "kind": "primitive", + "raw": "System.String", + "rootType": "string" + }, + "typeDescription": { + "name": "String", + "found": false, + "kind": "primitive", + "wireKeys": [] + } + } + }, + { + "type": "BBHENFCNLAB", + "method": "System.Boolean JDBIJGFEJAM(System.Int64 OEMDIAHHILF)", + "file": "BBHENFCNLAB.txt", + "line": 10717, + "contract": { + "kind": "unknown", + "raw": "", + "rootType": "unknown" + } + }, + { + "type": "BBHENFCNLAB", + "method": "System.String HFIOIJPLLDP(System.Int64 OEMDIAHHILF)", + "file": "BBHENFCNLAB.txt", + "line": 41, + "contract": { + "kind": "unknown", + "raw": "", + "rootType": "unknown" + } + }, + { + "type": "BBHENFCNLAB", + "method": "System.String HIGLOHHJDBN(System.Int64 HNHLJONGKHB)", + "file": "BBHENFCNLAB.txt", + "line": 4912, + "contract": { + "kind": "unknown", + "raw": "", + "rootType": "unknown" + } + }, + { + "type": "BBHENFCNLAB", + "method": "System.String KKGEAELNFCK(System.Int64 OEMDIAHHILF)", + "file": "BBHENFCNLAB.txt", + "line": 89, + "contract": { + "kind": "unknown", + "raw": "", + "rootType": "unknown" + } + }, + { + "type": "BBHENFCNLAB", + "method": "System.String PAMAJCIBNDP(System.Int64 OEMDIAHHILF, System.Int32 INKNNJHDIFD)", + "file": "BBHENFCNLAB.txt", + "line": 11125, + "contract": { + "kind": "unknown", + "raw": "", + "rootType": "unknown" + } + }, + { + "type": "BBHENFCNLAB", + "method": "System.Void .cctor()", + "file": "BBHENFCNLAB.txt", + "line": 11687, + "contract": { + "kind": "unknown", + "raw": "", + "rootType": "unknown" + } + }, + { + "type": "BBHENFCNLAB", + "method": "System.Void KJMGMEKAEFF(System.Int64 HNHLJONGKHB)", + "file": "BBHENFCNLAB.txt", + "line": 4992, + "contract": { + "kind": "unknown", + "raw": "", + "rootType": "unknown" + } + }, + { + "type": "BBHENFCNLAB", + "method": "System.Void LBGHAEMBHHA(System.Int64 OEMDIAHHILF)", + "file": "BBHENFCNLAB.txt", + "line": 3891, + "contract": { + "kind": "unknown", + "raw": "", + "rootType": "unknown" + } + }, + { + "type": "BBHENFCNLAB", + "method": "System.Void LPIPNOFPFEP(System.Int64 OEMDIAHHILF, System.Collections.Generic.List`1 LBAOCHFCLPO)", + "file": "BBHENFCNLAB.txt", + "line": 11351, + "contract": { + "kind": "unknown", + "raw": "", + "rootType": "unknown" + } + } + ], + "contracts": [ + { + "kind": "object", + "raw": "AHEPPAEOLOD", + "rootType": "AHEPPAEOLOD", + "typeDescription": { + "name": "AHEPPAEOLOD", + "found": true, + "file": "AHEPPAEOLOD.cs", + "declaration": "public class AHEPPAEOLOD : IFAIJAGLDFK", + "kind": "class", + "baseType": "IFAIJAGLDFK", + "properties": [ + { + "name": "BFLAPPKDDIN", + "type": "NEFHINKECJJ", + "accessibility": "public", + "kind": "property" + }, + { + "name": "HIMCGOCKLLK", + "type": "JJONIELLJGH", + "accessibility": "public", + "kind": "property" + }, + { + "name": "MEPFFJLDHNP", + "type": "OBBBPCBIMME", + "accessibility": "public", + "kind": "property" + } + ], + "fields": [], + "enumValues": [], + "wireKeys": [ + "Status", + "Invention", + "InventionVersion" + ], + "isilFile": "AHEPPAEOLOD.txt", + "isilType": "AHEPPAEOLOD" + } + }, + { + "kind": "object", + "raw": "HJPDBNLCGIB", + "rootType": "HJPDBNLCGIB", + "typeDescription": { + "name": "HJPDBNLCGIB", + "found": true, + "file": "HJPDBNLCGIB.cs", + "declaration": "public class HJPDBNLCGIB : IFAIJAGLDFK", + "kind": "class", + "baseType": "IFAIJAGLDFK", + "properties": [ + { + "name": "PKKADKGDHNI", + "type": "List", + "accessibility": "public", + "kind": "property" + } + ], + "fields": [], + "enumValues": [], + "wireKeys": [], + "isilFile": "HJPDBNLCGIB.txt", + "isilType": "HJPDBNLCGIB" + } + }, + { + "kind": "object", + "raw": "KLAMKCBENEA", + "rootType": "KLAMKCBENEA", + "typeDescription": { + "name": "KLAMKCBENEA", + "found": true, + "file": "KLAMKCBENEA.cs", + "declaration": "public class KLAMKCBENEA : IFAIJAGLDFK", + "kind": "class", + "baseType": "IFAIJAGLDFK", + "properties": [ + { + "name": "DFPPNHINEFO", + "type": "string", + "accessibility": "public", + "kind": "property" + }, + { + "name": "ONGEANEKLNE", + "type": "bool", + "accessibility": "public", + "kind": "property" + } + ], + "fields": [], + "enumValues": [], + "wireKeys": [ + "Success", + "Message" + ], + "isilFile": "KLAMKCBENEA.txt", + "isilType": "KLAMKCBENEA" + } + }, + { + "kind": "object", + "raw": "NJMAEIPIOAP", + "rootType": "NJMAEIPIOAP", + "typeDescription": { + "name": "NJMAEIPIOAP", + "found": true, + "file": "NJMAEIPIOAP.cs", + "declaration": "public class NJMAEIPIOAP : IFAIJAGLDFK", + "kind": "class", + "baseType": "IFAIJAGLDFK", + "properties": [ + { + "name": "GMFIDNACDJK", + "type": "OCLOGFHCPHN", + "accessibility": "public", + "kind": "property" + }, + { + "name": "PKKADKGDHNI", + "type": "List", + "accessibility": "public", + "kind": "property" + } + ], + "fields": [], + "enumValues": [], + "wireKeys": [ + "Result" + ], + "isilFile": "NJMAEIPIOAP.txt", + "isilType": "NJMAEIPIOAP" + } + }, + { + "kind": "object", + "raw": "OBBBPCBIMME", + "rootType": "OBBBPCBIMME", + "typeDescription": { + "name": "OBBBPCBIMME", + "found": true, + "file": "OBBBPCBIMME.cs", + "declaration": "public class OBBBPCBIMME : IFAIJAGLDFK", + "kind": "class", + "baseType": "IFAIJAGLDFK", + "properties": [ + { + "name": "ACBFDMLHFPB", + "type": "DateTime", + "accessibility": "public", + "kind": "property" + }, + { + "name": "ACHMMBMLGEK", + "type": "int", + "accessibility": "public", + "kind": "property" + }, + { + "name": "AHGCOGFEEEE", + "type": "string", + "accessibility": "public", + "kind": "property" + }, + { + "name": "AJHBGBHGGAL", + "type": "long", + "accessibility": "public", + "kind": "property" + }, + { + "name": "BAELCDIJIPJ", + "type": "HECIICKPCDN", + "accessibility": "public", + "kind": "property" + }, + { + "name": "DKHJPGMEOHF", + "type": "int", + "accessibility": "public", + "kind": "property" + }, + { + "name": "FIKEBGGCDFN", + "type": "string", + "accessibility": "public", + "kind": "property" + }, + { + "name": "GCAABGAEDPP", + "type": "bool", + "accessibility": "public", + "kind": "property" + }, + { + "name": "HKFOKCNGNFA", + "type": "int", + "accessibility": "public", + "kind": "property" + }, + { + "name": "ILKLEFCCJMN", + "type": "bool", + "accessibility": "public", + "kind": "property" + }, + { + "name": "JHJNKJHLJBJ", + "type": "int", + "accessibility": "public", + "kind": "property" + }, + { + "name": "JOHEKKGMAJB", + "type": "int", + "accessibility": "public", + "kind": "property" + }, + { + "name": "KBDMNJGJACC", + "type": "DateTime", + "accessibility": "public", + "kind": "property" + }, + { + "name": "KLLMGMJJICK", + "type": "Nullable", + "accessibility": "public", + "kind": "property" + }, + { + "name": "KODBEJPEFOJ", + "type": "string", + "accessibility": "public", + "kind": "property" + }, + { + "name": "MLDAFKCENMA", + "type": "string", + "accessibility": "public", + "kind": "property" + }, + { + "name": "MLMDFDKLELH", + "type": "HECIICKPCDN", + "accessibility": "public", + "kind": "property" + }, + { + "name": "MMPKOCENIIP", + "type": "bool", + "accessibility": "public", + "kind": "property" + }, + { + "name": "NFNIPMIEMPM", + "type": "bool", + "accessibility": "public", + "kind": "property" + }, + { + "name": "NPDJLOOHMDJ", + "type": "Nullable", + "accessibility": "public", + "kind": "property" + }, + { + "name": "PJNJIKKCBNA", + "type": "Nullable", + "accessibility": "public", + "kind": "property" + } + ], + "fields": [], + "enumValues": [], + "wireKeys": [ + "InventionId", + "ReplicationId", + "CreatorPlayerId", + "Name", + "Description", + "ImageName", + "CurrentVersionNumber", + "IsPublished", + "AllowTrial", + "ModifiedAt", + "CreatedAt", + "NumPlayersHaveUsedInRoom", + "NumDownloads", + "CheerCount", + "CreatorPermission", + "GeneralPermission", + "IsAGInvention", + "Price", + "HideFromPlayer" + ], + "isilFile": "OBBBPCBIMME.txt", + "isilType": "OBBBPCBIMME" + } + }, + { + "kind": "object", + "raw": "OEGPIPBKHCN", + "rootType": "OEGPIPBKHCN", + "typeDescription": { + "name": "OEGPIPBKHCN", + "found": true, + "file": "OEGPIPBKHCN.cs", + "declaration": "public class OEGPIPBKHCN : IFAIJAGLDFK", + "kind": "class", + "baseType": "IFAIJAGLDFK", + "properties": [ + { + "name": "GOAELNACNNJ", + "type": "bool", + "accessibility": "public", + "kind": "property" + } + ], + "fields": [], + "enumValues": [], + "wireKeys": [ + "IsCheering" + ], + "isilFile": "OEGPIPBKHCN.txt", + "isilType": "OEGPIPBKHCN" + } + }, + { + "kind": "array", + "raw": "System.Collections.Generic.List`1", + "rootType": "NEFHINKECJJ", + "item": { + "kind": "object", + "raw": "NEFHINKECJJ", + "rootType": "NEFHINKECJJ" + }, + "typeDescription": { + "name": "NEFHINKECJJ", + "found": true, + "file": "NEFHINKECJJ.cs", + "declaration": "public class NEFHINKECJJ : IFAIJAGLDFK", + "kind": "class", + "baseType": "IFAIJAGLDFK", + "properties": [ + { + "name": "AJHBGBHGGAL", + "type": "long", + "accessibility": "public", + "kind": "property" + }, + { + "name": "CLODONFLECP", + "type": "int", + "accessibility": "public", + "kind": "property" + }, + { + "name": "JKDGKHANBMB", + "type": "int", + "accessibility": "public", + "kind": "property" + }, + { + "name": "KHMMKHPAMLL", + "type": "int", + "accessibility": "public", + "kind": "property" + }, + { + "name": "MLDAFKCENMA", + "type": "string", + "accessibility": "public", + "kind": "property" + }, + { + "name": "NIFBPEMIADP", + "type": "string", + "accessibility": "public", + "kind": "property" + } + ], + "fields": [], + "enumValues": [], + "wireKeys": [ + "InventionId", + "ReplicationId", + "VersionNumber", + "InstantiationCost", + "LightsCost", + "BlobName" + ], + "isilFile": "NEFHINKECJJ.txt", + "isilType": "NEFHINKECJJ" + } + }, + { + "kind": "array", + "raw": "System.Collections.Generic.List`1", + "rootType": "OBBBPCBIMME", + "item": { + "kind": "object", + "raw": "OBBBPCBIMME", + "rootType": "OBBBPCBIMME" + }, + "typeDescription": { + "name": "OBBBPCBIMME", + "found": true, + "file": "OBBBPCBIMME.cs", + "declaration": "public class OBBBPCBIMME : IFAIJAGLDFK", + "kind": "class", + "baseType": "IFAIJAGLDFK", + "properties": [ + { + "name": "ACBFDMLHFPB", + "type": "DateTime", + "accessibility": "public", + "kind": "property" + }, + { + "name": "ACHMMBMLGEK", + "type": "int", + "accessibility": "public", + "kind": "property" + }, + { + "name": "AHGCOGFEEEE", + "type": "string", + "accessibility": "public", + "kind": "property" + }, + { + "name": "AJHBGBHGGAL", + "type": "long", + "accessibility": "public", + "kind": "property" + }, + { + "name": "BAELCDIJIPJ", + "type": "HECIICKPCDN", + "accessibility": "public", + "kind": "property" + }, + { + "name": "DKHJPGMEOHF", + "type": "int", + "accessibility": "public", + "kind": "property" + }, + { + "name": "FIKEBGGCDFN", + "type": "string", + "accessibility": "public", + "kind": "property" + }, + { + "name": "GCAABGAEDPP", + "type": "bool", + "accessibility": "public", + "kind": "property" + }, + { + "name": "HKFOKCNGNFA", + "type": "int", + "accessibility": "public", + "kind": "property" + }, + { + "name": "ILKLEFCCJMN", + "type": "bool", + "accessibility": "public", + "kind": "property" + }, + { + "name": "JHJNKJHLJBJ", + "type": "int", + "accessibility": "public", + "kind": "property" + }, + { + "name": "JOHEKKGMAJB", + "type": "int", + "accessibility": "public", + "kind": "property" + }, + { + "name": "KBDMNJGJACC", + "type": "DateTime", + "accessibility": "public", + "kind": "property" + }, + { + "name": "KLLMGMJJICK", + "type": "Nullable", + "accessibility": "public", + "kind": "property" + }, + { + "name": "KODBEJPEFOJ", + "type": "string", + "accessibility": "public", + "kind": "property" + }, + { + "name": "MLDAFKCENMA", + "type": "string", + "accessibility": "public", + "kind": "property" + }, + { + "name": "MLMDFDKLELH", + "type": "HECIICKPCDN", + "accessibility": "public", + "kind": "property" + }, + { + "name": "MMPKOCENIIP", + "type": "bool", + "accessibility": "public", + "kind": "property" + }, + { + "name": "NFNIPMIEMPM", + "type": "bool", + "accessibility": "public", + "kind": "property" + }, + { + "name": "NPDJLOOHMDJ", + "type": "Nullable", + "accessibility": "public", + "kind": "property" + }, + { + "name": "PJNJIKKCBNA", + "type": "Nullable", + "accessibility": "public", + "kind": "property" + } + ], + "fields": [], + "enumValues": [], + "wireKeys": [ + "InventionId", + "ReplicationId", + "CreatorPlayerId", + "Name", + "Description", + "ImageName", + "CurrentVersionNumber", + "IsPublished", + "AllowTrial", + "ModifiedAt", + "CreatedAt", + "NumPlayersHaveUsedInRoom", + "NumDownloads", + "CheerCount", + "CreatorPermission", + "GeneralPermission", + "IsAGInvention", + "Price", + "HideFromPlayer" + ], + "isilFile": "OBBBPCBIMME.txt", + "isilType": "OBBBPCBIMME" + } + }, + { + "kind": "array", + "raw": "System.Collections.Generic.List`1", + "rootType": "String", + "item": { + "kind": "primitive", + "raw": "System.String", + "rootType": "string" + }, + "typeDescription": { + "name": "String", + "found": false, + "kind": "primitive", + "wireKeys": [] + } + }, + { + "kind": "unknown", + "raw": "", + "rootType": "unknown", + "typeDescription": { + "name": "unknown", + "found": false, + "kind": "object", + "wireKeys": [] + } + } + ] + }, + { + "family": "inventions", + "route": "api/inventions/v1/fulllineageowner?", + "evidence": [ + { + "type": "BBHENFCNLAB", + "method": "RecRoom.Async.IPromise`1 DEOOEGHNIAJ(System.Collections.Generic.List`1 POGCOENDJDJ)", + "file": "BBHENFCNLAB.txt", + "line": 7823, + "contract": { + "kind": "primitive", + "raw": "System.Boolean", + "rootType": "boolean", + "typeDescription": { + "name": "boolean", + "found": false, + "kind": "primitive", + "wireKeys": [] + } + } + } + ], + "contracts": [ + { + "kind": "primitive", + "raw": "System.Boolean", + "rootType": "boolean", + "typeDescription": { + "name": "boolean", + "found": false, + "kind": "primitive", + "wireKeys": [] + } + } + ] + }, + { + "family": "inventions", + "route": "api/inventions/v3/addversion", + "evidence": [ + { + "type": "BBHENFCNLAB+OIJIEFMJMKO", + "method": "RecRoom.Async.IPromise`1 b__0(System.String filename)", + "file": "BBHENFCNLAB_NestedType_OIJIEFMJMKO.txt", + "line": 216, + "contract": { + "kind": "object", + "raw": "AHEPPAEOLOD", + "rootType": "AHEPPAEOLOD", + "typeDescription": { + "name": "AHEPPAEOLOD", + "found": true, + "file": "AHEPPAEOLOD.cs", + "declaration": "public class AHEPPAEOLOD : IFAIJAGLDFK", + "kind": "class", + "baseType": "IFAIJAGLDFK", + "properties": [ + { + "name": "BFLAPPKDDIN", + "type": "NEFHINKECJJ", + "accessibility": "public", + "kind": "property" + }, + { + "name": "HIMCGOCKLLK", + "type": "JJONIELLJGH", + "accessibility": "public", + "kind": "property" + }, + { + "name": "MEPFFJLDHNP", + "type": "OBBBPCBIMME", + "accessibility": "public", + "kind": "property" + } + ], + "fields": [], + "enumValues": [], + "wireKeys": [ + "Status", + "Invention", + "InventionVersion" + ], + "isilFile": "AHEPPAEOLOD.txt", + "isilType": "AHEPPAEOLOD" + } + } + } + ], + "contracts": [ + { + "kind": "object", + "raw": "AHEPPAEOLOD", + "rootType": "AHEPPAEOLOD", + "typeDescription": { + "name": "AHEPPAEOLOD", + "found": true, + "file": "AHEPPAEOLOD.cs", + "declaration": "public class AHEPPAEOLOD : IFAIJAGLDFK", + "kind": "class", + "baseType": "IFAIJAGLDFK", + "properties": [ + { + "name": "BFLAPPKDDIN", + "type": "NEFHINKECJJ", + "accessibility": "public", + "kind": "property" + }, + { + "name": "HIMCGOCKLLK", + "type": "JJONIELLJGH", + "accessibility": "public", + "kind": "property" + }, + { + "name": "MEPFFJLDHNP", + "type": "OBBBPCBIMME", + "accessibility": "public", + "kind": "property" + } + ], + "fields": [], + "enumValues": [], + "wireKeys": [ + "Status", + "Invention", + "InventionVersion" + ], + "isilFile": "AHEPPAEOLOD.txt", + "isilType": "AHEPPAEOLOD" + } + } + ] + }, + { + "family": "inventions", + "route": "api/inventions/v4/save", + "evidence": [ + { + "type": "BBHENFCNLAB+FODDLIKPKPE", + "method": "RecRoom.Async.IPromise`1 b__0(System.String filename)", + "file": "BBHENFCNLAB_NestedType_FODDLIKPKPE.txt", + "line": 250, + "contract": { + "kind": "object", + "raw": "AHEPPAEOLOD", + "rootType": "AHEPPAEOLOD", + "typeDescription": { + "name": "AHEPPAEOLOD", + "found": true, + "file": "AHEPPAEOLOD.cs", + "declaration": "public class AHEPPAEOLOD : IFAIJAGLDFK", + "kind": "class", + "baseType": "IFAIJAGLDFK", + "properties": [ + { + "name": "BFLAPPKDDIN", + "type": "NEFHINKECJJ", + "accessibility": "public", + "kind": "property" + }, + { + "name": "HIMCGOCKLLK", + "type": "JJONIELLJGH", + "accessibility": "public", + "kind": "property" + }, + { + "name": "MEPFFJLDHNP", + "type": "OBBBPCBIMME", + "accessibility": "public", + "kind": "property" + } + ], + "fields": [], + "enumValues": [], + "wireKeys": [ + "Status", + "Invention", + "InventionVersion" + ], + "isilFile": "AHEPPAEOLOD.txt", + "isilType": "AHEPPAEOLOD" + } + } + } + ], + "contracts": [ + { + "kind": "object", + "raw": "AHEPPAEOLOD", + "rootType": "AHEPPAEOLOD", + "typeDescription": { + "name": "AHEPPAEOLOD", + "found": true, + "file": "AHEPPAEOLOD.cs", + "declaration": "public class AHEPPAEOLOD : IFAIJAGLDFK", + "kind": "class", + "baseType": "IFAIJAGLDFK", + "properties": [ + { + "name": "BFLAPPKDDIN", + "type": "NEFHINKECJJ", + "accessibility": "public", + "kind": "property" + }, + { + "name": "HIMCGOCKLLK", + "type": "JJONIELLJGH", + "accessibility": "public", + "kind": "property" + }, + { + "name": "MEPFFJLDHNP", + "type": "OBBBPCBIMME", + "accessibility": "public", + "kind": "property" + } + ], + "fields": [], + "enumValues": [], + "wireKeys": [ + "Status", + "Invention", + "InventionVersion" + ], + "isilFile": "AHEPPAEOLOD.txt", + "isilType": "AHEPPAEOLOD" + } + } + ] + }, + { + "family": "matchmaking", + "route": "goto/club/{0}", + "evidence": [ + { + "type": "RecNet.Matchmaking", + "method": "RecRoom.Async.IPromise`1 IIDEDGKAGOE(PLILLKHMNDA EGLGJIONCCP, System.Int32[] DKFBAOLAEFE = null, System.Boolean KJHCANCBKAL = False)", + "file": "RecNet\\Matchmaking.txt", + "line": 8847, + "contract": { + "kind": "object", + "raw": "RecNet.Matchmaking+MHCKNNJOIIP", + "rootType": "MHCKNNJOIIP", + "typeDescription": { + "name": "MHCKNNJOIIP", + "found": true, + "file": "RecNet/Matchmaking.cs", + "declaration": "internal enum MHCKNNJOIIP", + "kind": "enum", + "baseType": null, + "properties": [], + "fields": [], + "enumValues": [ + { + "name": "UnknownError", + "value": "-1" + }, + { + "name": "Success", + "value": "0" + }, + { + "name": "NoSuchGame", + "value": "1" + }, + { + "name": "PlayerNotOnline", + "value": "2" + }, + { + "name": "InsufficientSpace", + "value": "3" + }, + { + "name": "EventNotStarted", + "value": "4" + }, + { + "name": "EventAlreadyFinished", + "value": "5" + }, + { + "name": "BlockedFromRoom", + "value": "7" + }, + { + "name": "JuniorNotAllowed", + "value": "11" + }, + { + "name": "Banned", + "value": "12" + }, + { + "name": "AlreadyInBestInstance", + "value": "13" + }, + { + "name": "InsufficientRelationship", + "value": "14" + }, + { + "name": "UpdateRequired", + "value": "16" + }, + { + "name": "AlreadyInTargetInstance", + "value": "17" + }, + { + "name": "UGCNotAllowed", + "value": "19" + }, + { + "name": "NoSuchRoom", + "value": "20" + }, + { + "name": "RoomIsNotActive", + "value": "22" + }, + { + "name": "RoomBlockedByCreator", + "value": "23" + }, + { + "name": "RoomIsPrivate", + "value": "25" + }, + { + "name": "RoomInstanceIsPrivate", + "value": "26" + }, + { + "name": "DeviceClassNotSupported", + "value": "30" + }, + { + "name": "DeviceClassNotSupportedByRoomOwner", + "value": "31" + }, + { + "name": "MovementModeNotSupportedByRoomOwner", + "value": "32" + }, + { + "name": "EventIsPrivate", + "value": "35" + }, + { + "name": "RoomInviteExpired", + "value": "40" + }, + { + "name": "NoAvailableRegion", + "value": "45" + }, + { + "name": "NotorietyTooPoor", + "value": "50" + }, + { + "name": "BannedFromRoom", + "value": "55" + }, + { + "name": "NoSuchRoomPlaylist", + "value": "60" + }, + { + "name": "RoomPlaylistIsNotActive", + "value": "61" + }, + { + "name": "RoomPlaylistIsPrivate", + "value": "62" + }, + { + "name": "NoSuchClub", + "value": "70" + }, + { + "name": "ClubHasNoClubhouse", + "value": "71" + }, + { + "name": "ClubIsNotActive", + "value": "73" + }, + { + "name": "NotAMemberOfClub", + "value": "74" + }, + { + "name": "BannedFromClub", + "value": "75" + }, + { + "name": "InstanceJoinNotPermitted", + "value": "76" + } + ], + "wireKeys": [] + } + } + } + ], + "contracts": [ + { + "kind": "object", + "raw": "RecNet.Matchmaking+MHCKNNJOIIP", + "rootType": "MHCKNNJOIIP", + "typeDescription": { + "name": "MHCKNNJOIIP", + "found": true, + "file": "RecNet/Matchmaking.cs", + "declaration": "internal enum MHCKNNJOIIP", + "kind": "enum", + "baseType": null, + "properties": [], + "fields": [], + "enumValues": [ + { + "name": "UnknownError", + "value": "-1" + }, + { + "name": "Success", + "value": "0" + }, + { + "name": "NoSuchGame", + "value": "1" + }, + { + "name": "PlayerNotOnline", + "value": "2" + }, + { + "name": "InsufficientSpace", + "value": "3" + }, + { + "name": "EventNotStarted", + "value": "4" + }, + { + "name": "EventAlreadyFinished", + "value": "5" + }, + { + "name": "BlockedFromRoom", + "value": "7" + }, + { + "name": "JuniorNotAllowed", + "value": "11" + }, + { + "name": "Banned", + "value": "12" + }, + { + "name": "AlreadyInBestInstance", + "value": "13" + }, + { + "name": "InsufficientRelationship", + "value": "14" + }, + { + "name": "UpdateRequired", + "value": "16" + }, + { + "name": "AlreadyInTargetInstance", + "value": "17" + }, + { + "name": "UGCNotAllowed", + "value": "19" + }, + { + "name": "NoSuchRoom", + "value": "20" + }, + { + "name": "RoomIsNotActive", + "value": "22" + }, + { + "name": "RoomBlockedByCreator", + "value": "23" + }, + { + "name": "RoomIsPrivate", + "value": "25" + }, + { + "name": "RoomInstanceIsPrivate", + "value": "26" + }, + { + "name": "DeviceClassNotSupported", + "value": "30" + }, + { + "name": "DeviceClassNotSupportedByRoomOwner", + "value": "31" + }, + { + "name": "MovementModeNotSupportedByRoomOwner", + "value": "32" + }, + { + "name": "EventIsPrivate", + "value": "35" + }, + { + "name": "RoomInviteExpired", + "value": "40" + }, + { + "name": "NoAvailableRegion", + "value": "45" + }, + { + "name": "NotorietyTooPoor", + "value": "50" + }, + { + "name": "BannedFromRoom", + "value": "55" + }, + { + "name": "NoSuchRoomPlaylist", + "value": "60" + }, + { + "name": "RoomPlaylistIsNotActive", + "value": "61" + }, + { + "name": "RoomPlaylistIsPrivate", + "value": "62" + }, + { + "name": "NoSuchClub", + "value": "70" + }, + { + "name": "ClubHasNoClubhouse", + "value": "71" + }, + { + "name": "ClubIsNotActive", + "value": "73" + }, + { + "name": "NotAMemberOfClub", + "value": "74" + }, + { + "name": "BannedFromClub", + "value": "75" + }, + { + "name": "InstanceJoinNotPermitted", + "value": "76" + } + ], + "wireKeys": [] + } + } + ] + }, + { + "family": "matchmaking", + "route": "goto/code/", + "evidence": [ + { + "type": "RecNet.Matchmaking", + "method": "RecRoom.Async.IPromise`1 FBLFCNDIPMP(System.String BFGCDJFNJLE, System.String KIIFGNILJEA, System.Int32[] DKFBAOLAEFE = null, System.Boolean KJHCANCBKAL = True, System.Boolean PBCPIEHFDBH = False)", + "file": "RecNet\\Matchmaking.txt", + "line": 8226, + "contract": { + "kind": "object", + "raw": "RecNet.Matchmaking+MHCKNNJOIIP", + "rootType": "MHCKNNJOIIP", + "typeDescription": { + "name": "MHCKNNJOIIP", + "found": true, + "file": "RecNet/Matchmaking.cs", + "declaration": "internal enum MHCKNNJOIIP", + "kind": "enum", + "baseType": null, + "properties": [], + "fields": [], + "enumValues": [ + { + "name": "UnknownError", + "value": "-1" + }, + { + "name": "Success", + "value": "0" + }, + { + "name": "NoSuchGame", + "value": "1" + }, + { + "name": "PlayerNotOnline", + "value": "2" + }, + { + "name": "InsufficientSpace", + "value": "3" + }, + { + "name": "EventNotStarted", + "value": "4" + }, + { + "name": "EventAlreadyFinished", + "value": "5" + }, + { + "name": "BlockedFromRoom", + "value": "7" + }, + { + "name": "JuniorNotAllowed", + "value": "11" + }, + { + "name": "Banned", + "value": "12" + }, + { + "name": "AlreadyInBestInstance", + "value": "13" + }, + { + "name": "InsufficientRelationship", + "value": "14" + }, + { + "name": "UpdateRequired", + "value": "16" + }, + { + "name": "AlreadyInTargetInstance", + "value": "17" + }, + { + "name": "UGCNotAllowed", + "value": "19" + }, + { + "name": "NoSuchRoom", + "value": "20" + }, + { + "name": "RoomIsNotActive", + "value": "22" + }, + { + "name": "RoomBlockedByCreator", + "value": "23" + }, + { + "name": "RoomIsPrivate", + "value": "25" + }, + { + "name": "RoomInstanceIsPrivate", + "value": "26" + }, + { + "name": "DeviceClassNotSupported", + "value": "30" + }, + { + "name": "DeviceClassNotSupportedByRoomOwner", + "value": "31" + }, + { + "name": "MovementModeNotSupportedByRoomOwner", + "value": "32" + }, + { + "name": "EventIsPrivate", + "value": "35" + }, + { + "name": "RoomInviteExpired", + "value": "40" + }, + { + "name": "NoAvailableRegion", + "value": "45" + }, + { + "name": "NotorietyTooPoor", + "value": "50" + }, + { + "name": "BannedFromRoom", + "value": "55" + }, + { + "name": "NoSuchRoomPlaylist", + "value": "60" + }, + { + "name": "RoomPlaylistIsNotActive", + "value": "61" + }, + { + "name": "RoomPlaylistIsPrivate", + "value": "62" + }, + { + "name": "NoSuchClub", + "value": "70" + }, + { + "name": "ClubHasNoClubhouse", + "value": "71" + }, + { + "name": "ClubIsNotActive", + "value": "73" + }, + { + "name": "NotAMemberOfClub", + "value": "74" + }, + { + "name": "BannedFromClub", + "value": "75" + }, + { + "name": "InstanceJoinNotPermitted", + "value": "76" + } + ], + "wireKeys": [] + } + } + } + ], + "contracts": [ + { + "kind": "object", + "raw": "RecNet.Matchmaking+MHCKNNJOIIP", + "rootType": "MHCKNNJOIIP", + "typeDescription": { + "name": "MHCKNNJOIIP", + "found": true, + "file": "RecNet/Matchmaking.cs", + "declaration": "internal enum MHCKNNJOIIP", + "kind": "enum", + "baseType": null, + "properties": [], + "fields": [], + "enumValues": [ + { + "name": "UnknownError", + "value": "-1" + }, + { + "name": "Success", + "value": "0" + }, + { + "name": "NoSuchGame", + "value": "1" + }, + { + "name": "PlayerNotOnline", + "value": "2" + }, + { + "name": "InsufficientSpace", + "value": "3" + }, + { + "name": "EventNotStarted", + "value": "4" + }, + { + "name": "EventAlreadyFinished", + "value": "5" + }, + { + "name": "BlockedFromRoom", + "value": "7" + }, + { + "name": "JuniorNotAllowed", + "value": "11" + }, + { + "name": "Banned", + "value": "12" + }, + { + "name": "AlreadyInBestInstance", + "value": "13" + }, + { + "name": "InsufficientRelationship", + "value": "14" + }, + { + "name": "UpdateRequired", + "value": "16" + }, + { + "name": "AlreadyInTargetInstance", + "value": "17" + }, + { + "name": "UGCNotAllowed", + "value": "19" + }, + { + "name": "NoSuchRoom", + "value": "20" + }, + { + "name": "RoomIsNotActive", + "value": "22" + }, + { + "name": "RoomBlockedByCreator", + "value": "23" + }, + { + "name": "RoomIsPrivate", + "value": "25" + }, + { + "name": "RoomInstanceIsPrivate", + "value": "26" + }, + { + "name": "DeviceClassNotSupported", + "value": "30" + }, + { + "name": "DeviceClassNotSupportedByRoomOwner", + "value": "31" + }, + { + "name": "MovementModeNotSupportedByRoomOwner", + "value": "32" + }, + { + "name": "EventIsPrivate", + "value": "35" + }, + { + "name": "RoomInviteExpired", + "value": "40" + }, + { + "name": "NoAvailableRegion", + "value": "45" + }, + { + "name": "NotorietyTooPoor", + "value": "50" + }, + { + "name": "BannedFromRoom", + "value": "55" + }, + { + "name": "NoSuchRoomPlaylist", + "value": "60" + }, + { + "name": "RoomPlaylistIsNotActive", + "value": "61" + }, + { + "name": "RoomPlaylistIsPrivate", + "value": "62" + }, + { + "name": "NoSuchClub", + "value": "70" + }, + { + "name": "ClubHasNoClubhouse", + "value": "71" + }, + { + "name": "ClubIsNotActive", + "value": "73" + }, + { + "name": "NotAMemberOfClub", + "value": "74" + }, + { + "name": "BannedFromClub", + "value": "75" + }, + { + "name": "InstanceJoinNotPermitted", + "value": "76" + } + ], + "wireKeys": [] + } + } + ] + }, + { + "family": "matchmaking", + "route": "goto/event/{0}", + "evidence": [ + { + "type": "RecNet.Matchmaking", + "method": "RecRoom.Async.IPromise`1 MLIILFNOOOK(CCMBKDINCAH IAALIIFNHNP, System.Int32[] DKFBAOLAEFE = null, System.Boolean KJHCANCBKAL = False)", + "file": "RecNet\\Matchmaking.txt", + "line": 8737, + "contract": { + "kind": "object", + "raw": "RecNet.Matchmaking+MHCKNNJOIIP", + "rootType": "MHCKNNJOIIP", + "typeDescription": { + "name": "MHCKNNJOIIP", + "found": true, + "file": "RecNet/Matchmaking.cs", + "declaration": "internal enum MHCKNNJOIIP", + "kind": "enum", + "baseType": null, + "properties": [], + "fields": [], + "enumValues": [ + { + "name": "UnknownError", + "value": "-1" + }, + { + "name": "Success", + "value": "0" + }, + { + "name": "NoSuchGame", + "value": "1" + }, + { + "name": "PlayerNotOnline", + "value": "2" + }, + { + "name": "InsufficientSpace", + "value": "3" + }, + { + "name": "EventNotStarted", + "value": "4" + }, + { + "name": "EventAlreadyFinished", + "value": "5" + }, + { + "name": "BlockedFromRoom", + "value": "7" + }, + { + "name": "JuniorNotAllowed", + "value": "11" + }, + { + "name": "Banned", + "value": "12" + }, + { + "name": "AlreadyInBestInstance", + "value": "13" + }, + { + "name": "InsufficientRelationship", + "value": "14" + }, + { + "name": "UpdateRequired", + "value": "16" + }, + { + "name": "AlreadyInTargetInstance", + "value": "17" + }, + { + "name": "UGCNotAllowed", + "value": "19" + }, + { + "name": "NoSuchRoom", + "value": "20" + }, + { + "name": "RoomIsNotActive", + "value": "22" + }, + { + "name": "RoomBlockedByCreator", + "value": "23" + }, + { + "name": "RoomIsPrivate", + "value": "25" + }, + { + "name": "RoomInstanceIsPrivate", + "value": "26" + }, + { + "name": "DeviceClassNotSupported", + "value": "30" + }, + { + "name": "DeviceClassNotSupportedByRoomOwner", + "value": "31" + }, + { + "name": "MovementModeNotSupportedByRoomOwner", + "value": "32" + }, + { + "name": "EventIsPrivate", + "value": "35" + }, + { + "name": "RoomInviteExpired", + "value": "40" + }, + { + "name": "NoAvailableRegion", + "value": "45" + }, + { + "name": "NotorietyTooPoor", + "value": "50" + }, + { + "name": "BannedFromRoom", + "value": "55" + }, + { + "name": "NoSuchRoomPlaylist", + "value": "60" + }, + { + "name": "RoomPlaylistIsNotActive", + "value": "61" + }, + { + "name": "RoomPlaylistIsPrivate", + "value": "62" + }, + { + "name": "NoSuchClub", + "value": "70" + }, + { + "name": "ClubHasNoClubhouse", + "value": "71" + }, + { + "name": "ClubIsNotActive", + "value": "73" + }, + { + "name": "NotAMemberOfClub", + "value": "74" + }, + { + "name": "BannedFromClub", + "value": "75" + }, + { + "name": "InstanceJoinNotPermitted", + "value": "76" + } + ], + "wireKeys": [] + } + } + } + ], + "contracts": [ + { + "kind": "object", + "raw": "RecNet.Matchmaking+MHCKNNJOIIP", + "rootType": "MHCKNNJOIIP", + "typeDescription": { + "name": "MHCKNNJOIIP", + "found": true, + "file": "RecNet/Matchmaking.cs", + "declaration": "internal enum MHCKNNJOIIP", + "kind": "enum", + "baseType": null, + "properties": [], + "fields": [], + "enumValues": [ + { + "name": "UnknownError", + "value": "-1" + }, + { + "name": "Success", + "value": "0" + }, + { + "name": "NoSuchGame", + "value": "1" + }, + { + "name": "PlayerNotOnline", + "value": "2" + }, + { + "name": "InsufficientSpace", + "value": "3" + }, + { + "name": "EventNotStarted", + "value": "4" + }, + { + "name": "EventAlreadyFinished", + "value": "5" + }, + { + "name": "BlockedFromRoom", + "value": "7" + }, + { + "name": "JuniorNotAllowed", + "value": "11" + }, + { + "name": "Banned", + "value": "12" + }, + { + "name": "AlreadyInBestInstance", + "value": "13" + }, + { + "name": "InsufficientRelationship", + "value": "14" + }, + { + "name": "UpdateRequired", + "value": "16" + }, + { + "name": "AlreadyInTargetInstance", + "value": "17" + }, + { + "name": "UGCNotAllowed", + "value": "19" + }, + { + "name": "NoSuchRoom", + "value": "20" + }, + { + "name": "RoomIsNotActive", + "value": "22" + }, + { + "name": "RoomBlockedByCreator", + "value": "23" + }, + { + "name": "RoomIsPrivate", + "value": "25" + }, + { + "name": "RoomInstanceIsPrivate", + "value": "26" + }, + { + "name": "DeviceClassNotSupported", + "value": "30" + }, + { + "name": "DeviceClassNotSupportedByRoomOwner", + "value": "31" + }, + { + "name": "MovementModeNotSupportedByRoomOwner", + "value": "32" + }, + { + "name": "EventIsPrivate", + "value": "35" + }, + { + "name": "RoomInviteExpired", + "value": "40" + }, + { + "name": "NoAvailableRegion", + "value": "45" + }, + { + "name": "NotorietyTooPoor", + "value": "50" + }, + { + "name": "BannedFromRoom", + "value": "55" + }, + { + "name": "NoSuchRoomPlaylist", + "value": "60" + }, + { + "name": "RoomPlaylistIsNotActive", + "value": "61" + }, + { + "name": "RoomPlaylistIsPrivate", + "value": "62" + }, + { + "name": "NoSuchClub", + "value": "70" + }, + { + "name": "ClubHasNoClubhouse", + "value": "71" + }, + { + "name": "ClubIsNotActive", + "value": "73" + }, + { + "name": "NotAMemberOfClub", + "value": "74" + }, + { + "name": "BannedFromClub", + "value": "75" + }, + { + "name": "InstanceJoinNotPermitted", + "value": "76" + } + ], + "wireKeys": [] + } + } + ] + }, + { + "family": "matchmaking", + "route": "goto/instance/{0}", + "evidence": [ + { + "type": "RecNet.Matchmaking", + "method": "RecRoom.Async.IPromise`1 MGKFDNPCDBP(System.Int64 ANAPBECHGLI, System.Int32[] DKFBAOLAEFE = null, System.Boolean KJHCANCBKAL = False, System.String APJJOJMDLNP = null)", + "file": "RecNet\\Matchmaking.txt", + "line": 9156, + "contract": { + "kind": "object", + "raw": "RecNet.Matchmaking+MHCKNNJOIIP", + "rootType": "MHCKNNJOIIP", + "typeDescription": { + "name": "MHCKNNJOIIP", + "found": true, + "file": "RecNet/Matchmaking.cs", + "declaration": "internal enum MHCKNNJOIIP", + "kind": "enum", + "baseType": null, + "properties": [], + "fields": [], + "enumValues": [ + { + "name": "UnknownError", + "value": "-1" + }, + { + "name": "Success", + "value": "0" + }, + { + "name": "NoSuchGame", + "value": "1" + }, + { + "name": "PlayerNotOnline", + "value": "2" + }, + { + "name": "InsufficientSpace", + "value": "3" + }, + { + "name": "EventNotStarted", + "value": "4" + }, + { + "name": "EventAlreadyFinished", + "value": "5" + }, + { + "name": "BlockedFromRoom", + "value": "7" + }, + { + "name": "JuniorNotAllowed", + "value": "11" + }, + { + "name": "Banned", + "value": "12" + }, + { + "name": "AlreadyInBestInstance", + "value": "13" + }, + { + "name": "InsufficientRelationship", + "value": "14" + }, + { + "name": "UpdateRequired", + "value": "16" + }, + { + "name": "AlreadyInTargetInstance", + "value": "17" + }, + { + "name": "UGCNotAllowed", + "value": "19" + }, + { + "name": "NoSuchRoom", + "value": "20" + }, + { + "name": "RoomIsNotActive", + "value": "22" + }, + { + "name": "RoomBlockedByCreator", + "value": "23" + }, + { + "name": "RoomIsPrivate", + "value": "25" + }, + { + "name": "RoomInstanceIsPrivate", + "value": "26" + }, + { + "name": "DeviceClassNotSupported", + "value": "30" + }, + { + "name": "DeviceClassNotSupportedByRoomOwner", + "value": "31" + }, + { + "name": "MovementModeNotSupportedByRoomOwner", + "value": "32" + }, + { + "name": "EventIsPrivate", + "value": "35" + }, + { + "name": "RoomInviteExpired", + "value": "40" + }, + { + "name": "NoAvailableRegion", + "value": "45" + }, + { + "name": "NotorietyTooPoor", + "value": "50" + }, + { + "name": "BannedFromRoom", + "value": "55" + }, + { + "name": "NoSuchRoomPlaylist", + "value": "60" + }, + { + "name": "RoomPlaylistIsNotActive", + "value": "61" + }, + { + "name": "RoomPlaylistIsPrivate", + "value": "62" + }, + { + "name": "NoSuchClub", + "value": "70" + }, + { + "name": "ClubHasNoClubhouse", + "value": "71" + }, + { + "name": "ClubIsNotActive", + "value": "73" + }, + { + "name": "NotAMemberOfClub", + "value": "74" + }, + { + "name": "BannedFromClub", + "value": "75" + }, + { + "name": "InstanceJoinNotPermitted", + "value": "76" + } + ], + "wireKeys": [] + } + } + } + ], + "contracts": [ + { + "kind": "object", + "raw": "RecNet.Matchmaking+MHCKNNJOIIP", + "rootType": "MHCKNNJOIIP", + "typeDescription": { + "name": "MHCKNNJOIIP", + "found": true, + "file": "RecNet/Matchmaking.cs", + "declaration": "internal enum MHCKNNJOIIP", + "kind": "enum", + "baseType": null, + "properties": [], + "fields": [], + "enumValues": [ + { + "name": "UnknownError", + "value": "-1" + }, + { + "name": "Success", + "value": "0" + }, + { + "name": "NoSuchGame", + "value": "1" + }, + { + "name": "PlayerNotOnline", + "value": "2" + }, + { + "name": "InsufficientSpace", + "value": "3" + }, + { + "name": "EventNotStarted", + "value": "4" + }, + { + "name": "EventAlreadyFinished", + "value": "5" + }, + { + "name": "BlockedFromRoom", + "value": "7" + }, + { + "name": "JuniorNotAllowed", + "value": "11" + }, + { + "name": "Banned", + "value": "12" + }, + { + "name": "AlreadyInBestInstance", + "value": "13" + }, + { + "name": "InsufficientRelationship", + "value": "14" + }, + { + "name": "UpdateRequired", + "value": "16" + }, + { + "name": "AlreadyInTargetInstance", + "value": "17" + }, + { + "name": "UGCNotAllowed", + "value": "19" + }, + { + "name": "NoSuchRoom", + "value": "20" + }, + { + "name": "RoomIsNotActive", + "value": "22" + }, + { + "name": "RoomBlockedByCreator", + "value": "23" + }, + { + "name": "RoomIsPrivate", + "value": "25" + }, + { + "name": "RoomInstanceIsPrivate", + "value": "26" + }, + { + "name": "DeviceClassNotSupported", + "value": "30" + }, + { + "name": "DeviceClassNotSupportedByRoomOwner", + "value": "31" + }, + { + "name": "MovementModeNotSupportedByRoomOwner", + "value": "32" + }, + { + "name": "EventIsPrivate", + "value": "35" + }, + { + "name": "RoomInviteExpired", + "value": "40" + }, + { + "name": "NoAvailableRegion", + "value": "45" + }, + { + "name": "NotorietyTooPoor", + "value": "50" + }, + { + "name": "BannedFromRoom", + "value": "55" + }, + { + "name": "NoSuchRoomPlaylist", + "value": "60" + }, + { + "name": "RoomPlaylistIsNotActive", + "value": "61" + }, + { + "name": "RoomPlaylistIsPrivate", + "value": "62" + }, + { + "name": "NoSuchClub", + "value": "70" + }, + { + "name": "ClubHasNoClubhouse", + "value": "71" + }, + { + "name": "ClubIsNotActive", + "value": "73" + }, + { + "name": "NotAMemberOfClub", + "value": "74" + }, + { + "name": "BannedFromClub", + "value": "75" + }, + { + "name": "InstanceJoinNotPermitted", + "value": "76" + } + ], + "wireKeys": [] + } + } + ] + }, + { + "family": "matchmaking", + "route": "goto/invite/{0}", + "evidence": [ + { + "type": "RecNet.Matchmaking", + "method": "RecRoom.Async.IPromise`1 ALCJNMAOIGH(System.Int64 IAHDKAIJJLB, System.Int64 HNHLJONGKHB, System.Int32[] DKFBAOLAEFE = null, System.Boolean KJHCANCBKAL = False)", + "file": "RecNet\\Matchmaking.txt", + "line": 9294, + "contract": { + "kind": "object", + "raw": "RecNet.Matchmaking+MHCKNNJOIIP", + "rootType": "MHCKNNJOIIP", + "typeDescription": { + "name": "MHCKNNJOIIP", + "found": true, + "file": "RecNet/Matchmaking.cs", + "declaration": "internal enum MHCKNNJOIIP", + "kind": "enum", + "baseType": null, + "properties": [], + "fields": [], + "enumValues": [ + { + "name": "UnknownError", + "value": "-1" + }, + { + "name": "Success", + "value": "0" + }, + { + "name": "NoSuchGame", + "value": "1" + }, + { + "name": "PlayerNotOnline", + "value": "2" + }, + { + "name": "InsufficientSpace", + "value": "3" + }, + { + "name": "EventNotStarted", + "value": "4" + }, + { + "name": "EventAlreadyFinished", + "value": "5" + }, + { + "name": "BlockedFromRoom", + "value": "7" + }, + { + "name": "JuniorNotAllowed", + "value": "11" + }, + { + "name": "Banned", + "value": "12" + }, + { + "name": "AlreadyInBestInstance", + "value": "13" + }, + { + "name": "InsufficientRelationship", + "value": "14" + }, + { + "name": "UpdateRequired", + "value": "16" + }, + { + "name": "AlreadyInTargetInstance", + "value": "17" + }, + { + "name": "UGCNotAllowed", + "value": "19" + }, + { + "name": "NoSuchRoom", + "value": "20" + }, + { + "name": "RoomIsNotActive", + "value": "22" + }, + { + "name": "RoomBlockedByCreator", + "value": "23" + }, + { + "name": "RoomIsPrivate", + "value": "25" + }, + { + "name": "RoomInstanceIsPrivate", + "value": "26" + }, + { + "name": "DeviceClassNotSupported", + "value": "30" + }, + { + "name": "DeviceClassNotSupportedByRoomOwner", + "value": "31" + }, + { + "name": "MovementModeNotSupportedByRoomOwner", + "value": "32" + }, + { + "name": "EventIsPrivate", + "value": "35" + }, + { + "name": "RoomInviteExpired", + "value": "40" + }, + { + "name": "NoAvailableRegion", + "value": "45" + }, + { + "name": "NotorietyTooPoor", + "value": "50" + }, + { + "name": "BannedFromRoom", + "value": "55" + }, + { + "name": "NoSuchRoomPlaylist", + "value": "60" + }, + { + "name": "RoomPlaylistIsNotActive", + "value": "61" + }, + { + "name": "RoomPlaylistIsPrivate", + "value": "62" + }, + { + "name": "NoSuchClub", + "value": "70" + }, + { + "name": "ClubHasNoClubhouse", + "value": "71" + }, + { + "name": "ClubIsNotActive", + "value": "73" + }, + { + "name": "NotAMemberOfClub", + "value": "74" + }, + { + "name": "BannedFromClub", + "value": "75" + }, + { + "name": "InstanceJoinNotPermitted", + "value": "76" + } + ], + "wireKeys": [] + } + } + } + ], + "contracts": [ + { + "kind": "object", + "raw": "RecNet.Matchmaking+MHCKNNJOIIP", + "rootType": "MHCKNNJOIIP", + "typeDescription": { + "name": "MHCKNNJOIIP", + "found": true, + "file": "RecNet/Matchmaking.cs", + "declaration": "internal enum MHCKNNJOIIP", + "kind": "enum", + "baseType": null, + "properties": [], + "fields": [], + "enumValues": [ + { + "name": "UnknownError", + "value": "-1" + }, + { + "name": "Success", + "value": "0" + }, + { + "name": "NoSuchGame", + "value": "1" + }, + { + "name": "PlayerNotOnline", + "value": "2" + }, + { + "name": "InsufficientSpace", + "value": "3" + }, + { + "name": "EventNotStarted", + "value": "4" + }, + { + "name": "EventAlreadyFinished", + "value": "5" + }, + { + "name": "BlockedFromRoom", + "value": "7" + }, + { + "name": "JuniorNotAllowed", + "value": "11" + }, + { + "name": "Banned", + "value": "12" + }, + { + "name": "AlreadyInBestInstance", + "value": "13" + }, + { + "name": "InsufficientRelationship", + "value": "14" + }, + { + "name": "UpdateRequired", + "value": "16" + }, + { + "name": "AlreadyInTargetInstance", + "value": "17" + }, + { + "name": "UGCNotAllowed", + "value": "19" + }, + { + "name": "NoSuchRoom", + "value": "20" + }, + { + "name": "RoomIsNotActive", + "value": "22" + }, + { + "name": "RoomBlockedByCreator", + "value": "23" + }, + { + "name": "RoomIsPrivate", + "value": "25" + }, + { + "name": "RoomInstanceIsPrivate", + "value": "26" + }, + { + "name": "DeviceClassNotSupported", + "value": "30" + }, + { + "name": "DeviceClassNotSupportedByRoomOwner", + "value": "31" + }, + { + "name": "MovementModeNotSupportedByRoomOwner", + "value": "32" + }, + { + "name": "EventIsPrivate", + "value": "35" + }, + { + "name": "RoomInviteExpired", + "value": "40" + }, + { + "name": "NoAvailableRegion", + "value": "45" + }, + { + "name": "NotorietyTooPoor", + "value": "50" + }, + { + "name": "BannedFromRoom", + "value": "55" + }, + { + "name": "NoSuchRoomPlaylist", + "value": "60" + }, + { + "name": "RoomPlaylistIsNotActive", + "value": "61" + }, + { + "name": "RoomPlaylistIsPrivate", + "value": "62" + }, + { + "name": "NoSuchClub", + "value": "70" + }, + { + "name": "ClubHasNoClubhouse", + "value": "71" + }, + { + "name": "ClubIsNotActive", + "value": "73" + }, + { + "name": "NotAMemberOfClub", + "value": "74" + }, + { + "name": "BannedFromClub", + "value": "75" + }, + { + "name": "InstanceJoinNotPermitted", + "value": "76" + } + ], + "wireKeys": [] + } + } + ] + }, + { + "family": "matchmaking", + "route": "goto/none", + "evidence": [ + { + "type": "RecNet.Matchmaking", + "method": "RecRoom.Async.IPromise OAHNGAMAPNB()", + "file": "RecNet\\Matchmaking.txt", + "line": 10314, + "contract": { + "kind": "success-or-empty", + "raw": "RecRoom.Async.IPromise", + "rootType": "void/success", + "typeDescription": { + "name": "void/success", + "found": false, + "kind": "object", + "wireKeys": [] + } + } + } + ], + "contracts": [ + { + "kind": "success-or-empty", + "raw": "RecRoom.Async.IPromise", + "rootType": "void/success", + "typeDescription": { + "name": "void/success", + "found": false, + "kind": "object", + "wireKeys": [] + } + } + ] + }, + { + "family": "matchmaking", + "route": "goto/player/{0}", + "evidence": [ + { + "type": "RecNet.Matchmaking", + "method": "RecRoom.Async.IPromise`1 IMMCGNNPIAD(System.Int32 CJFGEMGOJHB, System.Int32[] DKFBAOLAEFE = null, System.Boolean KJHCANCBKAL = False)", + "file": "RecNet\\Matchmaking.txt", + "line": 8980, + "contract": { + "kind": "object", + "raw": "RecNet.Matchmaking+MHCKNNJOIIP", + "rootType": "MHCKNNJOIIP", + "typeDescription": { + "name": "MHCKNNJOIIP", + "found": true, + "file": "RecNet/Matchmaking.cs", + "declaration": "internal enum MHCKNNJOIIP", + "kind": "enum", + "baseType": null, + "properties": [], + "fields": [], + "enumValues": [ + { + "name": "UnknownError", + "value": "-1" + }, + { + "name": "Success", + "value": "0" + }, + { + "name": "NoSuchGame", + "value": "1" + }, + { + "name": "PlayerNotOnline", + "value": "2" + }, + { + "name": "InsufficientSpace", + "value": "3" + }, + { + "name": "EventNotStarted", + "value": "4" + }, + { + "name": "EventAlreadyFinished", + "value": "5" + }, + { + "name": "BlockedFromRoom", + "value": "7" + }, + { + "name": "JuniorNotAllowed", + "value": "11" + }, + { + "name": "Banned", + "value": "12" + }, + { + "name": "AlreadyInBestInstance", + "value": "13" + }, + { + "name": "InsufficientRelationship", + "value": "14" + }, + { + "name": "UpdateRequired", + "value": "16" + }, + { + "name": "AlreadyInTargetInstance", + "value": "17" + }, + { + "name": "UGCNotAllowed", + "value": "19" + }, + { + "name": "NoSuchRoom", + "value": "20" + }, + { + "name": "RoomIsNotActive", + "value": "22" + }, + { + "name": "RoomBlockedByCreator", + "value": "23" + }, + { + "name": "RoomIsPrivate", + "value": "25" + }, + { + "name": "RoomInstanceIsPrivate", + "value": "26" + }, + { + "name": "DeviceClassNotSupported", + "value": "30" + }, + { + "name": "DeviceClassNotSupportedByRoomOwner", + "value": "31" + }, + { + "name": "MovementModeNotSupportedByRoomOwner", + "value": "32" + }, + { + "name": "EventIsPrivate", + "value": "35" + }, + { + "name": "RoomInviteExpired", + "value": "40" + }, + { + "name": "NoAvailableRegion", + "value": "45" + }, + { + "name": "NotorietyTooPoor", + "value": "50" + }, + { + "name": "BannedFromRoom", + "value": "55" + }, + { + "name": "NoSuchRoomPlaylist", + "value": "60" + }, + { + "name": "RoomPlaylistIsNotActive", + "value": "61" + }, + { + "name": "RoomPlaylistIsPrivate", + "value": "62" + }, + { + "name": "NoSuchClub", + "value": "70" + }, + { + "name": "ClubHasNoClubhouse", + "value": "71" + }, + { + "name": "ClubIsNotActive", + "value": "73" + }, + { + "name": "NotAMemberOfClub", + "value": "74" + }, + { + "name": "BannedFromClub", + "value": "75" + }, + { + "name": "InstanceJoinNotPermitted", + "value": "76" + } + ], + "wireKeys": [] + } + } + } + ], + "contracts": [ + { + "kind": "object", + "raw": "RecNet.Matchmaking+MHCKNNJOIIP", + "rootType": "MHCKNNJOIIP", + "typeDescription": { + "name": "MHCKNNJOIIP", + "found": true, + "file": "RecNet/Matchmaking.cs", + "declaration": "internal enum MHCKNNJOIIP", + "kind": "enum", + "baseType": null, + "properties": [], + "fields": [], + "enumValues": [ + { + "name": "UnknownError", + "value": "-1" + }, + { + "name": "Success", + "value": "0" + }, + { + "name": "NoSuchGame", + "value": "1" + }, + { + "name": "PlayerNotOnline", + "value": "2" + }, + { + "name": "InsufficientSpace", + "value": "3" + }, + { + "name": "EventNotStarted", + "value": "4" + }, + { + "name": "EventAlreadyFinished", + "value": "5" + }, + { + "name": "BlockedFromRoom", + "value": "7" + }, + { + "name": "JuniorNotAllowed", + "value": "11" + }, + { + "name": "Banned", + "value": "12" + }, + { + "name": "AlreadyInBestInstance", + "value": "13" + }, + { + "name": "InsufficientRelationship", + "value": "14" + }, + { + "name": "UpdateRequired", + "value": "16" + }, + { + "name": "AlreadyInTargetInstance", + "value": "17" + }, + { + "name": "UGCNotAllowed", + "value": "19" + }, + { + "name": "NoSuchRoom", + "value": "20" + }, + { + "name": "RoomIsNotActive", + "value": "22" + }, + { + "name": "RoomBlockedByCreator", + "value": "23" + }, + { + "name": "RoomIsPrivate", + "value": "25" + }, + { + "name": "RoomInstanceIsPrivate", + "value": "26" + }, + { + "name": "DeviceClassNotSupported", + "value": "30" + }, + { + "name": "DeviceClassNotSupportedByRoomOwner", + "value": "31" + }, + { + "name": "MovementModeNotSupportedByRoomOwner", + "value": "32" + }, + { + "name": "EventIsPrivate", + "value": "35" + }, + { + "name": "RoomInviteExpired", + "value": "40" + }, + { + "name": "NoAvailableRegion", + "value": "45" + }, + { + "name": "NotorietyTooPoor", + "value": "50" + }, + { + "name": "BannedFromRoom", + "value": "55" + }, + { + "name": "NoSuchRoomPlaylist", + "value": "60" + }, + { + "name": "RoomPlaylistIsNotActive", + "value": "61" + }, + { + "name": "RoomPlaylistIsPrivate", + "value": "62" + }, + { + "name": "NoSuchClub", + "value": "70" + }, + { + "name": "ClubHasNoClubhouse", + "value": "71" + }, + { + "name": "ClubIsNotActive", + "value": "73" + }, + { + "name": "NotAMemberOfClub", + "value": "74" + }, + { + "name": "BannedFromClub", + "value": "75" + }, + { + "name": "InstanceJoinNotPermitted", + "value": "76" + } + ], + "wireKeys": [] + } + } + ] + }, + { + "family": "matchmaking", + "route": "goto/playlist/", + "evidence": [ + { + "type": "RecNet.Matchmaking", + "method": "RecRoom.Async.IPromise`1 NDKKNPMAGNK(System.String APJJOJMDLNP, System.Boolean OFFEBBLOEJA = False, System.Int32[] DKFBAOLAEFE = null, System.Boolean KJHCANCBKAL = False)", + "file": "RecNet\\Matchmaking.txt", + "line": 8050, + "contract": { + "kind": "object", + "raw": "RecNet.Matchmaking+MHCKNNJOIIP", + "rootType": "MHCKNNJOIIP", + "typeDescription": { + "name": "MHCKNNJOIIP", + "found": true, + "file": "RecNet/Matchmaking.cs", + "declaration": "internal enum MHCKNNJOIIP", + "kind": "enum", + "baseType": null, + "properties": [], + "fields": [], + "enumValues": [ + { + "name": "UnknownError", + "value": "-1" + }, + { + "name": "Success", + "value": "0" + }, + { + "name": "NoSuchGame", + "value": "1" + }, + { + "name": "PlayerNotOnline", + "value": "2" + }, + { + "name": "InsufficientSpace", + "value": "3" + }, + { + "name": "EventNotStarted", + "value": "4" + }, + { + "name": "EventAlreadyFinished", + "value": "5" + }, + { + "name": "BlockedFromRoom", + "value": "7" + }, + { + "name": "JuniorNotAllowed", + "value": "11" + }, + { + "name": "Banned", + "value": "12" + }, + { + "name": "AlreadyInBestInstance", + "value": "13" + }, + { + "name": "InsufficientRelationship", + "value": "14" + }, + { + "name": "UpdateRequired", + "value": "16" + }, + { + "name": "AlreadyInTargetInstance", + "value": "17" + }, + { + "name": "UGCNotAllowed", + "value": "19" + }, + { + "name": "NoSuchRoom", + "value": "20" + }, + { + "name": "RoomIsNotActive", + "value": "22" + }, + { + "name": "RoomBlockedByCreator", + "value": "23" + }, + { + "name": "RoomIsPrivate", + "value": "25" + }, + { + "name": "RoomInstanceIsPrivate", + "value": "26" + }, + { + "name": "DeviceClassNotSupported", + "value": "30" + }, + { + "name": "DeviceClassNotSupportedByRoomOwner", + "value": "31" + }, + { + "name": "MovementModeNotSupportedByRoomOwner", + "value": "32" + }, + { + "name": "EventIsPrivate", + "value": "35" + }, + { + "name": "RoomInviteExpired", + "value": "40" + }, + { + "name": "NoAvailableRegion", + "value": "45" + }, + { + "name": "NotorietyTooPoor", + "value": "50" + }, + { + "name": "BannedFromRoom", + "value": "55" + }, + { + "name": "NoSuchRoomPlaylist", + "value": "60" + }, + { + "name": "RoomPlaylistIsNotActive", + "value": "61" + }, + { + "name": "RoomPlaylistIsPrivate", + "value": "62" + }, + { + "name": "NoSuchClub", + "value": "70" + }, + { + "name": "ClubHasNoClubhouse", + "value": "71" + }, + { + "name": "ClubIsNotActive", + "value": "73" + }, + { + "name": "NotAMemberOfClub", + "value": "74" + }, + { + "name": "BannedFromClub", + "value": "75" + }, + { + "name": "InstanceJoinNotPermitted", + "value": "76" + } + ], + "wireKeys": [] + } + } + } + ], + "contracts": [ + { + "kind": "object", + "raw": "RecNet.Matchmaking+MHCKNNJOIIP", + "rootType": "MHCKNNJOIIP", + "typeDescription": { + "name": "MHCKNNJOIIP", + "found": true, + "file": "RecNet/Matchmaking.cs", + "declaration": "internal enum MHCKNNJOIIP", + "kind": "enum", + "baseType": null, + "properties": [], + "fields": [], + "enumValues": [ + { + "name": "UnknownError", + "value": "-1" + }, + { + "name": "Success", + "value": "0" + }, + { + "name": "NoSuchGame", + "value": "1" + }, + { + "name": "PlayerNotOnline", + "value": "2" + }, + { + "name": "InsufficientSpace", + "value": "3" + }, + { + "name": "EventNotStarted", + "value": "4" + }, + { + "name": "EventAlreadyFinished", + "value": "5" + }, + { + "name": "BlockedFromRoom", + "value": "7" + }, + { + "name": "JuniorNotAllowed", + "value": "11" + }, + { + "name": "Banned", + "value": "12" + }, + { + "name": "AlreadyInBestInstance", + "value": "13" + }, + { + "name": "InsufficientRelationship", + "value": "14" + }, + { + "name": "UpdateRequired", + "value": "16" + }, + { + "name": "AlreadyInTargetInstance", + "value": "17" + }, + { + "name": "UGCNotAllowed", + "value": "19" + }, + { + "name": "NoSuchRoom", + "value": "20" + }, + { + "name": "RoomIsNotActive", + "value": "22" + }, + { + "name": "RoomBlockedByCreator", + "value": "23" + }, + { + "name": "RoomIsPrivate", + "value": "25" + }, + { + "name": "RoomInstanceIsPrivate", + "value": "26" + }, + { + "name": "DeviceClassNotSupported", + "value": "30" + }, + { + "name": "DeviceClassNotSupportedByRoomOwner", + "value": "31" + }, + { + "name": "MovementModeNotSupportedByRoomOwner", + "value": "32" + }, + { + "name": "EventIsPrivate", + "value": "35" + }, + { + "name": "RoomInviteExpired", + "value": "40" + }, + { + "name": "NoAvailableRegion", + "value": "45" + }, + { + "name": "NotorietyTooPoor", + "value": "50" + }, + { + "name": "BannedFromRoom", + "value": "55" + }, + { + "name": "NoSuchRoomPlaylist", + "value": "60" + }, + { + "name": "RoomPlaylistIsNotActive", + "value": "61" + }, + { + "name": "RoomPlaylistIsPrivate", + "value": "62" + }, + { + "name": "NoSuchClub", + "value": "70" + }, + { + "name": "ClubHasNoClubhouse", + "value": "71" + }, + { + "name": "ClubIsNotActive", + "value": "73" + }, + { + "name": "NotAMemberOfClub", + "value": "74" + }, + { + "name": "BannedFromClub", + "value": "75" + }, + { + "name": "InstanceJoinNotPermitted", + "value": "76" + } + ], + "wireKeys": [] + } + } + ] + }, + { + "family": "matchmaking", + "route": "goto/room/", + "evidence": [ + { + "type": "RecNet.Matchmaking", + "method": "RecRoom.Async.IPromise`1 GDCFFOJEIMD()", + "file": "RecNet\\Matchmaking.txt", + "line": 7509, + "contract": { + "kind": "object", + "raw": "RecNet.Matchmaking+MHCKNNJOIIP", + "rootType": "MHCKNNJOIIP", + "typeDescription": { + "name": "MHCKNNJOIIP", + "found": true, + "file": "RecNet/Matchmaking.cs", + "declaration": "internal enum MHCKNNJOIIP", + "kind": "enum", + "baseType": null, + "properties": [], + "fields": [], + "enumValues": [ + { + "name": "UnknownError", + "value": "-1" + }, + { + "name": "Success", + "value": "0" + }, + { + "name": "NoSuchGame", + "value": "1" + }, + { + "name": "PlayerNotOnline", + "value": "2" + }, + { + "name": "InsufficientSpace", + "value": "3" + }, + { + "name": "EventNotStarted", + "value": "4" + }, + { + "name": "EventAlreadyFinished", + "value": "5" + }, + { + "name": "BlockedFromRoom", + "value": "7" + }, + { + "name": "JuniorNotAllowed", + "value": "11" + }, + { + "name": "Banned", + "value": "12" + }, + { + "name": "AlreadyInBestInstance", + "value": "13" + }, + { + "name": "InsufficientRelationship", + "value": "14" + }, + { + "name": "UpdateRequired", + "value": "16" + }, + { + "name": "AlreadyInTargetInstance", + "value": "17" + }, + { + "name": "UGCNotAllowed", + "value": "19" + }, + { + "name": "NoSuchRoom", + "value": "20" + }, + { + "name": "RoomIsNotActive", + "value": "22" + }, + { + "name": "RoomBlockedByCreator", + "value": "23" + }, + { + "name": "RoomIsPrivate", + "value": "25" + }, + { + "name": "RoomInstanceIsPrivate", + "value": "26" + }, + { + "name": "DeviceClassNotSupported", + "value": "30" + }, + { + "name": "DeviceClassNotSupportedByRoomOwner", + "value": "31" + }, + { + "name": "MovementModeNotSupportedByRoomOwner", + "value": "32" + }, + { + "name": "EventIsPrivate", + "value": "35" + }, + { + "name": "RoomInviteExpired", + "value": "40" + }, + { + "name": "NoAvailableRegion", + "value": "45" + }, + { + "name": "NotorietyTooPoor", + "value": "50" + }, + { + "name": "BannedFromRoom", + "value": "55" + }, + { + "name": "NoSuchRoomPlaylist", + "value": "60" + }, + { + "name": "RoomPlaylistIsNotActive", + "value": "61" + }, + { + "name": "RoomPlaylistIsPrivate", + "value": "62" + }, + { + "name": "NoSuchClub", + "value": "70" + }, + { + "name": "ClubHasNoClubhouse", + "value": "71" + }, + { + "name": "ClubIsNotActive", + "value": "73" + }, + { + "name": "NotAMemberOfClub", + "value": "74" + }, + { + "name": "BannedFromClub", + "value": "75" + }, + { + "name": "InstanceJoinNotPermitted", + "value": "76" + } + ], + "wireKeys": [] + } + } + }, + { + "type": "RecNet.Matchmaking", + "method": "RecRoom.Async.IPromise`1 ILPIDHICKMB(System.String BFGCDJFNJLE, System.Boolean OFFEBBLOEJA = False, System.Int32[] DKFBAOLAEFE = null, System.Boolean KJHCANCBKAL = False, System.String APJJOJMDLNP = null, System.Boolean PBCPIEHFDBH = False)", + "file": "RecNet\\Matchmaking.txt", + "line": 8426, + "contract": { + "kind": "object", + "raw": "RecNet.Matchmaking+MHCKNNJOIIP", + "rootType": "MHCKNNJOIIP", + "typeDescription": { + "name": "MHCKNNJOIIP", + "found": true, + "file": "RecNet/Matchmaking.cs", + "declaration": "internal enum MHCKNNJOIIP", + "kind": "enum", + "baseType": null, + "properties": [], + "fields": [], + "enumValues": [ + { + "name": "UnknownError", + "value": "-1" + }, + { + "name": "Success", + "value": "0" + }, + { + "name": "NoSuchGame", + "value": "1" + }, + { + "name": "PlayerNotOnline", + "value": "2" + }, + { + "name": "InsufficientSpace", + "value": "3" + }, + { + "name": "EventNotStarted", + "value": "4" + }, + { + "name": "EventAlreadyFinished", + "value": "5" + }, + { + "name": "BlockedFromRoom", + "value": "7" + }, + { + "name": "JuniorNotAllowed", + "value": "11" + }, + { + "name": "Banned", + "value": "12" + }, + { + "name": "AlreadyInBestInstance", + "value": "13" + }, + { + "name": "InsufficientRelationship", + "value": "14" + }, + { + "name": "UpdateRequired", + "value": "16" + }, + { + "name": "AlreadyInTargetInstance", + "value": "17" + }, + { + "name": "UGCNotAllowed", + "value": "19" + }, + { + "name": "NoSuchRoom", + "value": "20" + }, + { + "name": "RoomIsNotActive", + "value": "22" + }, + { + "name": "RoomBlockedByCreator", + "value": "23" + }, + { + "name": "RoomIsPrivate", + "value": "25" + }, + { + "name": "RoomInstanceIsPrivate", + "value": "26" + }, + { + "name": "DeviceClassNotSupported", + "value": "30" + }, + { + "name": "DeviceClassNotSupportedByRoomOwner", + "value": "31" + }, + { + "name": "MovementModeNotSupportedByRoomOwner", + "value": "32" + }, + { + "name": "EventIsPrivate", + "value": "35" + }, + { + "name": "RoomInviteExpired", + "value": "40" + }, + { + "name": "NoAvailableRegion", + "value": "45" + }, + { + "name": "NotorietyTooPoor", + "value": "50" + }, + { + "name": "BannedFromRoom", + "value": "55" + }, + { + "name": "NoSuchRoomPlaylist", + "value": "60" + }, + { + "name": "RoomPlaylistIsNotActive", + "value": "61" + }, + { + "name": "RoomPlaylistIsPrivate", + "value": "62" + }, + { + "name": "NoSuchClub", + "value": "70" + }, + { + "name": "ClubHasNoClubhouse", + "value": "71" + }, + { + "name": "ClubIsNotActive", + "value": "73" + }, + { + "name": "NotAMemberOfClub", + "value": "74" + }, + { + "name": "BannedFromClub", + "value": "75" + }, + { + "name": "InstanceJoinNotPermitted", + "value": "76" + } + ], + "wireKeys": [] + } + } + }, + { + "type": "RecNet.Matchmaking", + "method": "RecRoom.Async.IPromise`1 JLMFIHJLNBL(System.String HFDPAGDDNDE, System.Int32[] DKFBAOLAEFE, System.Boolean PBCPIEHFDBH, System.Boolean KJHCANCBKAL, System.Boolean ALOAFENOBGN)", + "file": "RecNet\\Matchmaking.txt", + "line": 7882, + "contract": { + "kind": "object", + "raw": "RecNet.Matchmaking+MHCKNNJOIIP", + "rootType": "MHCKNNJOIIP", + "typeDescription": { + "name": "MHCKNNJOIIP", + "found": true, + "file": "RecNet/Matchmaking.cs", + "declaration": "internal enum MHCKNNJOIIP", + "kind": "enum", + "baseType": null, + "properties": [], + "fields": [], + "enumValues": [ + { + "name": "UnknownError", + "value": "-1" + }, + { + "name": "Success", + "value": "0" + }, + { + "name": "NoSuchGame", + "value": "1" + }, + { + "name": "PlayerNotOnline", + "value": "2" + }, + { + "name": "InsufficientSpace", + "value": "3" + }, + { + "name": "EventNotStarted", + "value": "4" + }, + { + "name": "EventAlreadyFinished", + "value": "5" + }, + { + "name": "BlockedFromRoom", + "value": "7" + }, + { + "name": "JuniorNotAllowed", + "value": "11" + }, + { + "name": "Banned", + "value": "12" + }, + { + "name": "AlreadyInBestInstance", + "value": "13" + }, + { + "name": "InsufficientRelationship", + "value": "14" + }, + { + "name": "UpdateRequired", + "value": "16" + }, + { + "name": "AlreadyInTargetInstance", + "value": "17" + }, + { + "name": "UGCNotAllowed", + "value": "19" + }, + { + "name": "NoSuchRoom", + "value": "20" + }, + { + "name": "RoomIsNotActive", + "value": "22" + }, + { + "name": "RoomBlockedByCreator", + "value": "23" + }, + { + "name": "RoomIsPrivate", + "value": "25" + }, + { + "name": "RoomInstanceIsPrivate", + "value": "26" + }, + { + "name": "DeviceClassNotSupported", + "value": "30" + }, + { + "name": "DeviceClassNotSupportedByRoomOwner", + "value": "31" + }, + { + "name": "MovementModeNotSupportedByRoomOwner", + "value": "32" + }, + { + "name": "EventIsPrivate", + "value": "35" + }, + { + "name": "RoomInviteExpired", + "value": "40" + }, + { + "name": "NoAvailableRegion", + "value": "45" + }, + { + "name": "NotorietyTooPoor", + "value": "50" + }, + { + "name": "BannedFromRoom", + "value": "55" + }, + { + "name": "NoSuchRoomPlaylist", + "value": "60" + }, + { + "name": "RoomPlaylistIsNotActive", + "value": "61" + }, + { + "name": "RoomPlaylistIsPrivate", + "value": "62" + }, + { + "name": "NoSuchClub", + "value": "70" + }, + { + "name": "ClubHasNoClubhouse", + "value": "71" + }, + { + "name": "ClubIsNotActive", + "value": "73" + }, + { + "name": "NotAMemberOfClub", + "value": "74" + }, + { + "name": "BannedFromClub", + "value": "75" + }, + { + "name": "InstanceJoinNotPermitted", + "value": "76" + } + ], + "wireKeys": [] + } + } + }, + { + "type": "RecNet.Matchmaking", + "method": "RecRoom.Async.IPromise`1 KNCHJJDKJGK(System.String BFGCDJFNJLE, System.String HFDPAGDDNDE, RecNet.Matchmaking+GIJOGKJCEKG MIAGCJJACCL = 0, System.Int32[] DKFBAOLAEFE = null, System.Boolean PBCPIEHFDBH = False, System.Boolean KJHCANCBKAL = False, System.String APJJOJMDLNP = null)", + "file": "RecNet\\Matchmaking.txt", + "line": 8632, + "contract": { + "kind": "object", + "raw": "RecNet.Matchmaking+MHCKNNJOIIP", + "rootType": "MHCKNNJOIIP", + "typeDescription": { + "name": "MHCKNNJOIIP", + "found": true, + "file": "RecNet/Matchmaking.cs", + "declaration": "internal enum MHCKNNJOIIP", + "kind": "enum", + "baseType": null, + "properties": [], + "fields": [], + "enumValues": [ + { + "name": "UnknownError", + "value": "-1" + }, + { + "name": "Success", + "value": "0" + }, + { + "name": "NoSuchGame", + "value": "1" + }, + { + "name": "PlayerNotOnline", + "value": "2" + }, + { + "name": "InsufficientSpace", + "value": "3" + }, + { + "name": "EventNotStarted", + "value": "4" + }, + { + "name": "EventAlreadyFinished", + "value": "5" + }, + { + "name": "BlockedFromRoom", + "value": "7" + }, + { + "name": "JuniorNotAllowed", + "value": "11" + }, + { + "name": "Banned", + "value": "12" + }, + { + "name": "AlreadyInBestInstance", + "value": "13" + }, + { + "name": "InsufficientRelationship", + "value": "14" + }, + { + "name": "UpdateRequired", + "value": "16" + }, + { + "name": "AlreadyInTargetInstance", + "value": "17" + }, + { + "name": "UGCNotAllowed", + "value": "19" + }, + { + "name": "NoSuchRoom", + "value": "20" + }, + { + "name": "RoomIsNotActive", + "value": "22" + }, + { + "name": "RoomBlockedByCreator", + "value": "23" + }, + { + "name": "RoomIsPrivate", + "value": "25" + }, + { + "name": "RoomInstanceIsPrivate", + "value": "26" + }, + { + "name": "DeviceClassNotSupported", + "value": "30" + }, + { + "name": "DeviceClassNotSupportedByRoomOwner", + "value": "31" + }, + { + "name": "MovementModeNotSupportedByRoomOwner", + "value": "32" + }, + { + "name": "EventIsPrivate", + "value": "35" + }, + { + "name": "RoomInviteExpired", + "value": "40" + }, + { + "name": "NoAvailableRegion", + "value": "45" + }, + { + "name": "NotorietyTooPoor", + "value": "50" + }, + { + "name": "BannedFromRoom", + "value": "55" + }, + { + "name": "NoSuchRoomPlaylist", + "value": "60" + }, + { + "name": "RoomPlaylistIsNotActive", + "value": "61" + }, + { + "name": "RoomPlaylistIsPrivate", + "value": "62" + }, + { + "name": "NoSuchClub", + "value": "70" + }, + { + "name": "ClubHasNoClubhouse", + "value": "71" + }, + { + "name": "ClubIsNotActive", + "value": "73" + }, + { + "name": "NotAMemberOfClub", + "value": "74" + }, + { + "name": "BannedFromClub", + "value": "75" + }, + { + "name": "InstanceJoinNotPermitted", + "value": "76" + } + ], + "wireKeys": [] + } + } + } + ], + "contracts": [ + { + "kind": "object", + "raw": "RecNet.Matchmaking+MHCKNNJOIIP", + "rootType": "MHCKNNJOIIP", + "typeDescription": { + "name": "MHCKNNJOIIP", + "found": true, + "file": "RecNet/Matchmaking.cs", + "declaration": "internal enum MHCKNNJOIIP", + "kind": "enum", + "baseType": null, + "properties": [], + "fields": [], + "enumValues": [ + { + "name": "UnknownError", + "value": "-1" + }, + { + "name": "Success", + "value": "0" + }, + { + "name": "NoSuchGame", + "value": "1" + }, + { + "name": "PlayerNotOnline", + "value": "2" + }, + { + "name": "InsufficientSpace", + "value": "3" + }, + { + "name": "EventNotStarted", + "value": "4" + }, + { + "name": "EventAlreadyFinished", + "value": "5" + }, + { + "name": "BlockedFromRoom", + "value": "7" + }, + { + "name": "JuniorNotAllowed", + "value": "11" + }, + { + "name": "Banned", + "value": "12" + }, + { + "name": "AlreadyInBestInstance", + "value": "13" + }, + { + "name": "InsufficientRelationship", + "value": "14" + }, + { + "name": "UpdateRequired", + "value": "16" + }, + { + "name": "AlreadyInTargetInstance", + "value": "17" + }, + { + "name": "UGCNotAllowed", + "value": "19" + }, + { + "name": "NoSuchRoom", + "value": "20" + }, + { + "name": "RoomIsNotActive", + "value": "22" + }, + { + "name": "RoomBlockedByCreator", + "value": "23" + }, + { + "name": "RoomIsPrivate", + "value": "25" + }, + { + "name": "RoomInstanceIsPrivate", + "value": "26" + }, + { + "name": "DeviceClassNotSupported", + "value": "30" + }, + { + "name": "DeviceClassNotSupportedByRoomOwner", + "value": "31" + }, + { + "name": "MovementModeNotSupportedByRoomOwner", + "value": "32" + }, + { + "name": "EventIsPrivate", + "value": "35" + }, + { + "name": "RoomInviteExpired", + "value": "40" + }, + { + "name": "NoAvailableRegion", + "value": "45" + }, + { + "name": "NotorietyTooPoor", + "value": "50" + }, + { + "name": "BannedFromRoom", + "value": "55" + }, + { + "name": "NoSuchRoomPlaylist", + "value": "60" + }, + { + "name": "RoomPlaylistIsNotActive", + "value": "61" + }, + { + "name": "RoomPlaylistIsPrivate", + "value": "62" + }, + { + "name": "NoSuchClub", + "value": "70" + }, + { + "name": "ClubHasNoClubhouse", + "value": "71" + }, + { + "name": "ClubIsNotActive", + "value": "73" + }, + { + "name": "NotAMemberOfClub", + "value": "74" + }, + { + "name": "BannedFromClub", + "value": "75" + }, + { + "name": "InstanceJoinNotPermitted", + "value": "76" + } + ], + "wireKeys": [] + } + } + ], + "httpResponseOverride": { + "note": "The public client method resolves to MHCKNNJOIIP, but NDDKNMLHKBK actually parses the HTTP JSON into RecNet.Matchmaking+JKOFHKAHOHK and then maps that wrapper to the enum. This is the body the server should emit.", + "typeDescription": { + "name": "JKOFHKAHOHK", + "found": true, + "file": "RecNet/Matchmaking.cs", + "declaration": "private class JKOFHKAHOHK : IFAIJAGLDFK", + "kind": "class", + "baseType": "IFAIJAGLDFK", + "properties": [ + { + "name": "FLOHLCKCOFA", + "type": "CIIBGMBOFEI", + "accessibility": "public", + "kind": "property" + }, + { + "name": "HIKHMNNGOHD", + "type": "MHCKNNJOIIP", + "accessibility": "public", + "kind": "property" + } + ], + "fields": [], + "enumValues": [], + "wireKeys": [ + "errorCode", + "roomInstance" + ], + "isilFile": "RecNet/Matchmaking_NestedType_JKOFHKAHOHK.txt", + "isilType": "RecNet.Matchmaking+JKOFHKAHOHK" + } + } + }, + { + "family": "matchmaking", + "route": "room/{0}/instances", + "evidence": [ + { + "type": "RecNet.Matchmaking", + "method": "RecRoom.Async.IPromise`1> KFMFHGLEGHF(System.Int64 HNHLJONGKHB)", + "file": "RecNet\\Matchmaking.txt", + "line": 10726, + "contract": { + "kind": "array", + "raw": "System.Collections.Generic.List`1", + "rootType": "JHNOAKDOMHG", + "item": { + "kind": "object", + "raw": "JHNOAKDOMHG", + "rootType": "JHNOAKDOMHG" + }, + "typeDescription": { + "name": "JHNOAKDOMHG", + "found": true, + "file": "JHNOAKDOMHG.cs", + "declaration": "public class JHNOAKDOMHG : IFAIJAGLDFK", + "kind": "class", + "baseType": "IFAIJAGLDFK", + "properties": [ + { + "name": "ACBFDMLHFPB", + "type": "DateTime", + "accessibility": "public", + "kind": "property" + }, + { + "name": "AMFIPBKHEPN", + "type": "string", + "accessibility": "public", + "kind": "property" + }, + { + "name": "BGCNPBBBEIK", + "type": "long", + "accessibility": "public", + "kind": "property" + }, + { + "name": "DADOKMAOFJL", + "type": "long", + "accessibility": "public", + "kind": "property" + }, + { + "name": "EDCIALKDIGM", + "type": "string", + "accessibility": "public", + "kind": "property" + }, + { + "name": "FDMFNIJKHKC", + "type": "bool", + "accessibility": "public", + "kind": "property" + }, + { + "name": "GJPHHEHBCIJ", + "type": "long", + "accessibility": "public", + "kind": "property" + }, + { + "name": "HHGAKDNMHGO", + "type": "List", + "accessibility": "public", + "kind": "property" + }, + { + "name": "HJGLKNFDHAP", + "type": "bool", + "accessibility": "public", + "kind": "property" + }, + { + "name": "HLFNLKMCNAC", + "type": "int", + "accessibility": "public", + "kind": "property" + } + ], + "fields": [], + "enumValues": [], + "wireKeys": [ + "roomInstanceId", + "roomId", + "subRoomId", + "isFull", + "createdAt", + "playerIds" + ], + "isilFile": "JHNOAKDOMHG.txt", + "isilType": "JHNOAKDOMHG" + } + } + } + ], + "contracts": [ + { + "kind": "array", + "raw": "System.Collections.Generic.List`1", + "rootType": "JHNOAKDOMHG", + "item": { + "kind": "object", + "raw": "JHNOAKDOMHG", + "rootType": "JHNOAKDOMHG" + }, + "typeDescription": { + "name": "JHNOAKDOMHG", + "found": true, + "file": "JHNOAKDOMHG.cs", + "declaration": "public class JHNOAKDOMHG : IFAIJAGLDFK", + "kind": "class", + "baseType": "IFAIJAGLDFK", + "properties": [ + { + "name": "ACBFDMLHFPB", + "type": "DateTime", + "accessibility": "public", + "kind": "property" + }, + { + "name": "AMFIPBKHEPN", + "type": "string", + "accessibility": "public", + "kind": "property" + }, + { + "name": "BGCNPBBBEIK", + "type": "long", + "accessibility": "public", + "kind": "property" + }, + { + "name": "DADOKMAOFJL", + "type": "long", + "accessibility": "public", + "kind": "property" + }, + { + "name": "EDCIALKDIGM", + "type": "string", + "accessibility": "public", + "kind": "property" + }, + { + "name": "FDMFNIJKHKC", + "type": "bool", + "accessibility": "public", + "kind": "property" + }, + { + "name": "GJPHHEHBCIJ", + "type": "long", + "accessibility": "public", + "kind": "property" + }, + { + "name": "HHGAKDNMHGO", + "type": "List", + "accessibility": "public", + "kind": "property" + }, + { + "name": "HJGLKNFDHAP", + "type": "bool", + "accessibility": "public", + "kind": "property" + }, + { + "name": "HLFNLKMCNAC", + "type": "int", + "accessibility": "public", + "kind": "property" + } + ], + "fields": [], + "enumValues": [], + "wireKeys": [ + "roomInstanceId", + "roomId", + "subRoomId", + "isFull", + "createdAt", + "playerIds" + ], + "isilFile": "JHNOAKDOMHG.txt", + "isilType": "JHNOAKDOMHG" + } + } + ] + }, + { + "family": "messages", + "route": "api/messages/", + "evidence": [ + { + "type": "KEBJPIGKGOI", + "method": "RecRoom.Async.IPromise JHJDMMLABCG()", + "file": "KEBJPIGKGOI.txt", + "line": 1297, + "contract": { + "kind": "success-or-empty", + "raw": "RecRoom.Async.IPromise", + "rootType": "void/success", + "typeDescription": { + "name": "void/success", + "found": false, + "kind": "object", + "wireKeys": [] + } + } + }, + { + "type": "KEBJPIGKGOI", + "method": "RecRoom.Async.IPromise MPLDGJCCIMN(System.Int64 CJFGEMGOJHB, IGCIBGKPPMO+BBELBJELLHN JMDIPDGMIOG, System.String ABADFLCBFIJ, System.Nullable`1 HNHLJONGKHB)", + "file": "KEBJPIGKGOI.txt", + "line": 2653, + "contract": { + "kind": "success-or-empty", + "raw": "RecRoom.Async.IPromise", + "rootType": "void/success", + "typeDescription": { + "name": "void/success", + "found": false, + "kind": "object", + "wireKeys": [] + } + } + }, + { + "type": "KEBJPIGKGOI", + "method": "RecRoom.Async.IPromise`1 IIHKDEGDMEA()", + "file": "KEBJPIGKGOI.txt", + "line": 6118, + "contract": { + "kind": "object", + "raw": "DIBODMEJOPN", + "rootType": "DIBODMEJOPN", + "typeDescription": { + "name": "DIBODMEJOPN", + "found": true, + "file": "DIBODMEJOPN.cs", + "declaration": "public class DIBODMEJOPN : IFAIJAGLDFK", + "kind": "class", + "baseType": "IFAIJAGLDFK", + "properties": [ + { + "name": "EOBAEAPOFKO", + "type": "bool", + "accessibility": "public", + "kind": "property" + }, + { + "name": "FHGBCJLIGJE", + "type": "bool", + "accessibility": "public", + "kind": "property" + }, + { + "name": "OPAAFAKPHOE", + "type": "bool", + "accessibility": "public", + "kind": "property" + } + ], + "fields": [], + "enumValues": [], + "wireKeys": [ + "ChatMessage", + "FriendInvite", + "FavoriteFriendOnline" + ], + "isilFile": "DIBODMEJOPN.txt", + "isilType": "DIBODMEJOPN" + } + } + }, + { + "type": "KEBJPIGKGOI", + "method": "System.Collections.IEnumerator JFAMBLHIACA(System.Collections.Generic.List`1 FNNIKBKHAFN, BPHGKAEDBPE+CBEOHBCIPEA AFLPGGJMPOE = null)", + "file": "KEBJPIGKGOI.txt", + "line": 1476, + "contract": { + "kind": "callback-or-coroutine", + "raw": "IEnumerator", + "rootType": "callback", + "typeDescription": { + "name": "callback", + "found": false, + "kind": "object", + "wireKeys": [] + } + } + }, + { + "type": "KEBJPIGKGOI", + "method": "System.Collections.IEnumerator MGIABJDLBED(System.Collections.Generic.List`1 FNNIKBKHAFN, IGCIBGKPPMO+BBELBJELLHN JMDIPDGMIOG, System.String ABADFLCBFIJ, System.Nullable`1 HNHLJONGKHB, BPHGKAEDBPE+CBEOHBCIPEA AFLPGGJMPOE = null)", + "file": "KEBJPIGKGOI.txt", + "line": 2843, + "contract": { + "kind": "callback-or-coroutine", + "raw": "IEnumerator", + "rootType": "callback", + "typeDescription": { + "name": "callback", + "found": false, + "kind": "object", + "wireKeys": [] + } + } + }, + { + "type": "KEBJPIGKGOI", + "method": "System.Void CPGAJIACKIP()", + "file": "KEBJPIGKGOI.txt", + "line": 3306, + "contract": { + "kind": "unknown", + "raw": "", + "rootType": "unknown" + } + } + ], + "contracts": [ + { + "kind": "success-or-empty", + "raw": "RecRoom.Async.IPromise", + "rootType": "void/success", + "typeDescription": { + "name": "void/success", + "found": false, + "kind": "object", + "wireKeys": [] + } + }, + { + "kind": "object", + "raw": "DIBODMEJOPN", + "rootType": "DIBODMEJOPN", + "typeDescription": { + "name": "DIBODMEJOPN", + "found": true, + "file": "DIBODMEJOPN.cs", + "declaration": "public class DIBODMEJOPN : IFAIJAGLDFK", + "kind": "class", + "baseType": "IFAIJAGLDFK", + "properties": [ + { + "name": "EOBAEAPOFKO", + "type": "bool", + "accessibility": "public", + "kind": "property" + }, + { + "name": "FHGBCJLIGJE", + "type": "bool", + "accessibility": "public", + "kind": "property" + }, + { + "name": "OPAAFAKPHOE", + "type": "bool", + "accessibility": "public", + "kind": "property" + } + ], + "fields": [], + "enumValues": [], + "wireKeys": [ + "ChatMessage", + "FriendInvite", + "FavoriteFriendOnline" + ], + "isilFile": "DIBODMEJOPN.txt", + "isilType": "DIBODMEJOPN" + } + }, + { + "kind": "callback-or-coroutine", + "raw": "IEnumerator", + "rootType": "callback", + "typeDescription": { + "name": "callback", + "found": false, + "kind": "object", + "wireKeys": [] + } + }, + { + "kind": "unknown", + "raw": "", + "rootType": "unknown", + "typeDescription": { + "name": "unknown", + "found": false, + "kind": "object", + "wireKeys": [] + } + } + ] + }, + { + "family": "messages", + "route": "api/messages/v1/IOSClearDeviceToken", + "evidence": [ + { + "type": "KEBJPIGKGOI", + "method": "RecRoom.Async.IPromise LLIHILNLNEP()", + "file": "KEBJPIGKGOI.txt", + "line": 5886, + "contract": { + "kind": "success-or-empty", + "raw": "RecRoom.Async.IPromise", + "rootType": "void/success", + "typeDescription": { + "name": "void/success", + "found": false, + "kind": "object", + "wireKeys": [] + } + } + } + ], + "contracts": [ + { + "kind": "success-or-empty", + "raw": "RecRoom.Async.IPromise", + "rootType": "void/success", + "typeDescription": { + "name": "void/success", + "found": false, + "kind": "object", + "wireKeys": [] + } + } + ] + }, + { + "family": "messages", + "route": "api/messages/v1/IOSModifyNotificationPreferences", + "evidence": [ + { + "type": "KEBJPIGKGOI", + "method": "RecRoom.Async.IPromise NDONIOINHAE(DIBODMEJOPN JFMOEGCPNAC)", + "file": "KEBJPIGKGOI.txt", + "line": 6455, + "contract": { + "kind": "success-or-empty", + "raw": "RecRoom.Async.IPromise", + "rootType": "void/success", + "typeDescription": { + "name": "void/success", + "found": false, + "kind": "object", + "wireKeys": [] + } + } + } + ], + "contracts": [ + { + "kind": "success-or-empty", + "raw": "RecRoom.Async.IPromise", + "rootType": "void/success", + "typeDescription": { + "name": "void/success", + "found": false, + "kind": "object", + "wireKeys": [] + } + } + ] + }, + { + "family": "messages", + "route": "api/messages/v1/IOSResetNotificationPreferencesBadgeCount", + "evidence": [ + { + "type": "KEBJPIGKGOI", + "method": "RecRoom.Async.IPromise GENJNKHDHCN()", + "file": "KEBJPIGKGOI.txt", + "line": 6347, + "contract": { + "kind": "success-or-empty", + "raw": "RecRoom.Async.IPromise", + "rootType": "void/success", + "typeDescription": { + "name": "void/success", + "found": false, + "kind": "object", + "wireKeys": [] + } + } + } + ], + "contracts": [ + { + "kind": "success-or-empty", + "raw": "RecRoom.Async.IPromise", + "rootType": "void/success", + "typeDescription": { + "name": "void/success", + "found": false, + "kind": "object", + "wireKeys": [] + } + } + ] + }, + { + "family": "messages", + "route": "api/messages/v1/IOSSaveDeviceToken", + "evidence": [ + { + "type": "KEBJPIGKGOI", + "method": "RecRoom.Async.IPromise INNJDBLBCEO(System.String EAEICOOGLAK)", + "file": "KEBJPIGKGOI.txt", + "line": 5768, + "contract": { + "kind": "success-or-empty", + "raw": "RecRoom.Async.IPromise", + "rootType": "void/success", + "typeDescription": { + "name": "void/success", + "found": false, + "kind": "object", + "wireKeys": [] + } + } + } + ], + "contracts": [ + { + "kind": "success-or-empty", + "raw": "RecRoom.Async.IPromise", + "rootType": "void/success", + "typeDescription": { + "name": "void/success", + "found": false, + "kind": "object", + "wireKeys": [] + } + } + ] + }, + { + "family": "messages", + "route": "api/messages/v3/delete", + "evidence": [ + { + "type": "KEBJPIGKGOI", + "method": "System.Collections.IEnumerator PEIBHPIEKGF(System.Collections.Generic.IEnumerable`1 OCCKKEIMPGP, BPHGKAEDBPE+CBEOHBCIPEA AFLPGGJMPOE = null)", + "file": "KEBJPIGKGOI.txt", + "line": 3138, + "contract": { + "kind": "callback-or-coroutine", + "raw": "IEnumerator", + "rootType": "callback", + "typeDescription": { + "name": "callback", + "found": false, + "kind": "object", + "wireKeys": [] + } + } + } + ], + "contracts": [ + { + "kind": "callback-or-coroutine", + "raw": "IEnumerator", + "rootType": "callback", + "typeDescription": { + "name": "callback", + "found": false, + "kind": "object", + "wireKeys": [] + } + } + ] + }, + { + "family": "messages", + "route": "api/offlineinvite/", + "evidence": [ + { + "type": "KEBJPIGKGOI", + "method": "System.Collections.IEnumerator LPPPAHLCPCO(System.Int64 CJFGEMGOJHB, BPHGKAEDBPE+OJDJIJDNFHE AFLPGGJMPOE)", + "file": "KEBJPIGKGOI.txt", + "line": 6606, + "contract": { + "kind": "callback-or-coroutine", + "raw": "IEnumerator", + "rootType": "callback", + "typeDescription": { + "name": "callback", + "found": false, + "kind": "object", + "wireKeys": [] + } + } + } + ], + "contracts": [ + { + "kind": "callback-or-coroutine", + "raw": "IEnumerator", + "rootType": "callback", + "typeDescription": { + "name": "callback", + "found": false, + "kind": "object", + "wireKeys": [] + } + } + ] + }, + { + "family": "misc", + "route": "{0}v1/bulkignoreplatformusers", + "evidence": [ + { + "type": "FGPIDGLCKEF", + "method": "System.Void IMLJEBCMMKK(PlatformManager+FCIKKFJOMNO FPMCLJDEGKL, System.Collections.Generic.List`1 CIBFMCPBLEO)", + "file": "FGPIDGLCKEF.txt", + "line": 2834, + "contract": { + "kind": "unknown", + "raw": "", + "rootType": "unknown" + } + } + ], + "contracts": [ + { + "kind": "unknown", + "raw": "", + "rootType": "unknown", + "typeDescription": { + "name": "unknown", + "found": false, + "kind": "object", + "wireKeys": [] + } + } + ] + }, + { + "family": "misc", + "route": "/bulk", + "evidence": [ + { + "type": "GNPDMBPGHBH", + "method": "RecRoom.Async.IPromise`1> DMHOIAJGABC(System.Collections.Generic.IReadOnlyList`1 JOGAGJIPEDN)", + "file": "GNPDMBPGHBH.txt", + "line": 2388, + "contract": { + "kind": "array", + "raw": "System.Collections.Generic.List`1", + "rootType": "CCMBKDINCAH", + "item": { + "kind": "object", + "raw": "CCMBKDINCAH", + "rootType": "CCMBKDINCAH" + }, + "typeDescription": { + "name": "CCMBKDINCAH", + "found": true, + "file": "CCMBKDINCAH.cs", + "declaration": "public class CCMBKDINCAH : IFAIJAGLDFK", + "kind": "class", + "baseType": "IFAIJAGLDFK", + "properties": [ + { + "name": "ACHMMBMLGEK", + "type": "int", + "accessibility": "public", + "kind": "property" + }, + { + "name": "AHGCOGFEEEE", + "type": "string", + "accessibility": "public", + "kind": "property" + }, + { + "name": "CCGOEDABKNN", + "type": "Nullable", + "accessibility": "public", + "kind": "property" + }, + { + "name": "DADOKMAOFJL", + "type": "long", + "accessibility": "public", + "kind": "property" + }, + { + "name": "FIKEBGGCDFN", + "type": "string", + "accessibility": "public", + "kind": "property" + }, + { + "name": "GBOIPGBGDDG", + "type": "long", + "accessibility": "public", + "kind": "property" + }, + { + "name": "GIHDFJNGFHH", + "type": "int", + "accessibility": "public", + "kind": "property" + }, + { + "name": "GJPHHEHBCIJ", + "type": "Nullable", + "accessibility": "public", + "kind": "property" + }, + { + "name": "JFEAPMIPNEP", + "type": "CMCAFKLAHCD", + "accessibility": "public", + "kind": "property" + }, + { + "name": "KJDKJFLBFOL", + "type": "bool", + "accessibility": "public", + "kind": "property" + }, + { + "name": "KMOAHFBMONE", + "type": "bool", + "accessibility": "public", + "kind": "property" + }, + { + "name": "KODBEJPEFOJ", + "type": "string", + "accessibility": "public", + "kind": "property" + }, + { + "name": "LCEFHPHIHBP", + "type": "bool", + "accessibility": "public", + "kind": "property" + }, + { + "name": "LIPFBIGFEBG", + "type": "DateTime", + "accessibility": "public", + "kind": "property" + }, + { + "name": "MAFEFHEPIKI", + "type": "DateTime", + "accessibility": "public", + "kind": "property" + } + ], + "fields": [], + "enumValues": [], + "wireKeys": [ + "PlayerEventId", + "Name", + "Description", + "ImageName", + "StartTime", + "EndTime", + "CreatorPlayerId", + "AttendeeCount", + "RoomId", + "Accessibility" + ], + "isilFile": "CCMBKDINCAH.txt", + "isilType": "CCMBKDINCAH" + } + } + }, + { + "type": "GNPDMBPGHBH+MKCNPHKGFDM", + "method": "RecRoom.Async.IPromise`1> b__1(System.String uri)", + "file": "GNPDMBPGHBH_NestedType_MKCNPHKGFDM.txt", + "line": 138, + "contract": { + "kind": "array", + "raw": "System.Collections.Generic.List`1", + "rootType": "CCMBKDINCAH", + "item": { + "kind": "object", + "raw": "CCMBKDINCAH", + "rootType": "CCMBKDINCAH" + }, + "typeDescription": { + "name": "CCMBKDINCAH", + "found": true, + "file": "CCMBKDINCAH.cs", + "declaration": "public class CCMBKDINCAH : IFAIJAGLDFK", + "kind": "class", + "baseType": "IFAIJAGLDFK", + "properties": [ + { + "name": "ACHMMBMLGEK", + "type": "int", + "accessibility": "public", + "kind": "property" + }, + { + "name": "AHGCOGFEEEE", + "type": "string", + "accessibility": "public", + "kind": "property" + }, + { + "name": "CCGOEDABKNN", + "type": "Nullable", + "accessibility": "public", + "kind": "property" + }, + { + "name": "DADOKMAOFJL", + "type": "long", + "accessibility": "public", + "kind": "property" + }, + { + "name": "FIKEBGGCDFN", + "type": "string", + "accessibility": "public", + "kind": "property" + }, + { + "name": "GBOIPGBGDDG", + "type": "long", + "accessibility": "public", + "kind": "property" + }, + { + "name": "GIHDFJNGFHH", + "type": "int", + "accessibility": "public", + "kind": "property" + }, + { + "name": "GJPHHEHBCIJ", + "type": "Nullable", + "accessibility": "public", + "kind": "property" + }, + { + "name": "JFEAPMIPNEP", + "type": "CMCAFKLAHCD", + "accessibility": "public", + "kind": "property" + }, + { + "name": "KJDKJFLBFOL", + "type": "bool", + "accessibility": "public", + "kind": "property" + }, + { + "name": "KMOAHFBMONE", + "type": "bool", + "accessibility": "public", + "kind": "property" + }, + { + "name": "KODBEJPEFOJ", + "type": "string", + "accessibility": "public", + "kind": "property" + }, + { + "name": "LCEFHPHIHBP", + "type": "bool", + "accessibility": "public", + "kind": "property" + }, + { + "name": "LIPFBIGFEBG", + "type": "DateTime", + "accessibility": "public", + "kind": "property" + }, + { + "name": "MAFEFHEPIKI", + "type": "DateTime", + "accessibility": "public", + "kind": "property" + } + ], + "fields": [], + "enumValues": [], + "wireKeys": [ + "PlayerEventId", + "Name", + "Description", + "ImageName", + "StartTime", + "EndTime", + "CreatorPlayerId", + "AttendeeCount", + "RoomId", + "Accessibility" + ], + "isilFile": "CCMBKDINCAH.txt", + "isilType": "CCMBKDINCAH" + } + } + } + ], + "contracts": [ + { + "kind": "array", + "raw": "System.Collections.Generic.List`1", + "rootType": "CCMBKDINCAH", + "item": { + "kind": "object", + "raw": "CCMBKDINCAH", + "rootType": "CCMBKDINCAH" + }, + "typeDescription": { + "name": "CCMBKDINCAH", + "found": true, + "file": "CCMBKDINCAH.cs", + "declaration": "public class CCMBKDINCAH : IFAIJAGLDFK", + "kind": "class", + "baseType": "IFAIJAGLDFK", + "properties": [ + { + "name": "ACHMMBMLGEK", + "type": "int", + "accessibility": "public", + "kind": "property" + }, + { + "name": "AHGCOGFEEEE", + "type": "string", + "accessibility": "public", + "kind": "property" + }, + { + "name": "CCGOEDABKNN", + "type": "Nullable", + "accessibility": "public", + "kind": "property" + }, + { + "name": "DADOKMAOFJL", + "type": "long", + "accessibility": "public", + "kind": "property" + }, + { + "name": "FIKEBGGCDFN", + "type": "string", + "accessibility": "public", + "kind": "property" + }, + { + "name": "GBOIPGBGDDG", + "type": "long", + "accessibility": "public", + "kind": "property" + }, + { + "name": "GIHDFJNGFHH", + "type": "int", + "accessibility": "public", + "kind": "property" + }, + { + "name": "GJPHHEHBCIJ", + "type": "Nullable", + "accessibility": "public", + "kind": "property" + }, + { + "name": "JFEAPMIPNEP", + "type": "CMCAFKLAHCD", + "accessibility": "public", + "kind": "property" + }, + { + "name": "KJDKJFLBFOL", + "type": "bool", + "accessibility": "public", + "kind": "property" + }, + { + "name": "KMOAHFBMONE", + "type": "bool", + "accessibility": "public", + "kind": "property" + }, + { + "name": "KODBEJPEFOJ", + "type": "string", + "accessibility": "public", + "kind": "property" + }, + { + "name": "LCEFHPHIHBP", + "type": "bool", + "accessibility": "public", + "kind": "property" + }, + { + "name": "LIPFBIGFEBG", + "type": "DateTime", + "accessibility": "public", + "kind": "property" + }, + { + "name": "MAFEFHEPIKI", + "type": "DateTime", + "accessibility": "public", + "kind": "property" + } + ], + "fields": [], + "enumValues": [], + "wireKeys": [ + "PlayerEventId", + "Name", + "Description", + "ImageName", + "StartTime", + "EndTime", + "CreatorPlayerId", + "AttendeeCount", + "RoomId", + "Accessibility" + ], + "isilFile": "CCMBKDINCAH.txt", + "isilType": "CCMBKDINCAH" + } + } + ] + }, + { + "family": "misc", + "route": "/data/{0}", + "evidence": [ + { + "type": "KELEPAPMOGK+CJACMOFOFKJ", + "method": "RecRoom.Async.IPromise`1 b__0()", + "file": "KELEPAPMOGK_NestedType_CJACMOFOFKJ.txt", + "line": 187, + "contract": { + "kind": "object", + "raw": "KELEPAPMOGK+LEKKFHBPBAB", + "rootType": "LEKKFHBPBAB", + "typeDescription": { + "name": "LEKKFHBPBAB", + "found": true, + "file": "KELEPAPMOGK.cs", + "declaration": "internal class LEKKFHBPBAB", + "kind": "class", + "baseType": null, + "properties": [], + "fields": [ + { + "name": "MMBOKOLAJFH", + "type": "string", + "kind": "field" + }, + { + "name": "MGPDDEMABPB", + "type": "Byte[]", + "kind": "field" + } + ], + "enumValues": [], + "wireKeys": [], + "isilFile": "KELEPAPMOGK_NestedType_LEKKFHBPBAB.txt", + "isilType": "KELEPAPMOGK+LEKKFHBPBAB" + } + } + } + ], + "contracts": [ + { + "kind": "object", + "raw": "KELEPAPMOGK+LEKKFHBPBAB", + "rootType": "LEKKFHBPBAB", + "typeDescription": { + "name": "LEKKFHBPBAB", + "found": true, + "file": "KELEPAPMOGK.cs", + "declaration": "internal class LEKKFHBPBAB", + "kind": "class", + "baseType": null, + "properties": [], + "fields": [ + { + "name": "MMBOKOLAJFH", + "type": "string", + "kind": "field" + }, + { + "name": "MGPDDEMABPB", + "type": "Byte[]", + "kind": "field" + } + ], + "enumValues": [], + "wireKeys": [], + "isilFile": "KELEPAPMOGK_NestedType_LEKKFHBPBAB.txt", + "isilType": "KELEPAPMOGK+LEKKFHBPBAB" + } + } + ] + }, + { + "family": "misc", + "route": "/room/", + "evidence": [ + { + "type": "OJMCBOKJFOF+BPPECKDOINI", + "method": "RecRoom.Async.IPromise`1 b__0()", + "file": "OJMCBOKJFOF_NestedType_BPPECKDOINI.txt", + "line": 206, + "contract": { + "kind": "object", + "raw": "System.Byte[]", + "rootType": "Byte", + "typeDescription": { + "name": "Byte", + "found": false, + "kind": "primitive", + "wireKeys": [] + } + } + } + ], + "contracts": [ + { + "kind": "object", + "raw": "System.Byte[]", + "rootType": "Byte", + "typeDescription": { + "name": "Byte", + "found": false, + "kind": "primitive", + "wireKeys": [] + } + } + ] + }, + { + "family": "misc", + "route": "api/announcement/v1/get", + "evidence": [ + { + "type": "NLGOJMONPKG", + "method": "RecRoom.Async.IPromise`1> ADEEFOCPDFH()", + "file": "NLGOJMONPKG.txt", + "line": 379, + "contract": { + "kind": "array", + "raw": "System.Collections.Generic.List`1", + "rootType": "MKEKHKEHPLO", + "item": { + "kind": "object", + "raw": "MKEKHKEHPLO", + "rootType": "MKEKHKEHPLO" + }, + "typeDescription": { + "name": "MKEKHKEHPLO", + "found": true, + "file": "MKEKHKEHPLO.cs", + "declaration": "public class MKEKHKEHPLO : IFAIJAGLDFK", + "kind": "class", + "baseType": "IFAIJAGLDFK", + "properties": [ + { + "name": "ACBFDMLHFPB", + "type": "DateTime", + "accessibility": "public", + "kind": "property" + }, + { + "name": "AHGCOGFEEEE", + "type": "string", + "accessibility": "public", + "kind": "property" + }, + { + "name": "CDALJPJJHDK", + "type": "FIEFAAIHMAF", + "accessibility": "public", + "kind": "property" + }, + { + "name": "COJOHODFJOL", + "type": "AIOCBINPOML", + "accessibility": "public", + "kind": "property" + }, + { + "name": "DNFIODIACBF", + "type": "long", + "accessibility": "public", + "kind": "property" + }, + { + "name": "EAFCPPAEHJA", + "type": "string", + "accessibility": "public", + "kind": "property" + }, + { + "name": "FDPLOMBGJGJ", + "type": "string", + "accessibility": "public", + "kind": "property" + }, + { + "name": "HPCKFCEFFOJ", + "type": "MBALPDDOMME", + "accessibility": "public", + "kind": "property" + }, + { + "name": "KFJBDJMKHKG", + "type": "bool", + "accessibility": "public", + "kind": "property" + }, + { + "name": "LKIBJOMNFFD", + "type": "string", + "accessibility": "public", + "kind": "property" + }, + { + "name": "PFACIBEMBNO", + "type": "string", + "accessibility": "public", + "kind": "property" + }, + { + "name": "PFJIBIPNDCA", + "type": "FCIKKFJOMNO", + "accessibility": "public", + "kind": "property" + } + ], + "fields": [], + "enumValues": [], + "wireKeys": [ + "AnnouncementId", + "AnnouncementType", + "Title", + "Body", + "ImageName", + "LinkType", + "LinkName", + "LinkUri", + "Platform", + "CreatedAt" + ], + "isilFile": "MKEKHKEHPLO.txt", + "isilType": "MKEKHKEHPLO" + } + } + } + ], + "contracts": [ + { + "kind": "array", + "raw": "System.Collections.Generic.List`1", + "rootType": "MKEKHKEHPLO", + "item": { + "kind": "object", + "raw": "MKEKHKEHPLO", + "rootType": "MKEKHKEHPLO" + }, + "typeDescription": { + "name": "MKEKHKEHPLO", + "found": true, + "file": "MKEKHKEHPLO.cs", + "declaration": "public class MKEKHKEHPLO : IFAIJAGLDFK", + "kind": "class", + "baseType": "IFAIJAGLDFK", + "properties": [ + { + "name": "ACBFDMLHFPB", + "type": "DateTime", + "accessibility": "public", + "kind": "property" + }, + { + "name": "AHGCOGFEEEE", + "type": "string", + "accessibility": "public", + "kind": "property" + }, + { + "name": "CDALJPJJHDK", + "type": "FIEFAAIHMAF", + "accessibility": "public", + "kind": "property" + }, + { + "name": "COJOHODFJOL", + "type": "AIOCBINPOML", + "accessibility": "public", + "kind": "property" + }, + { + "name": "DNFIODIACBF", + "type": "long", + "accessibility": "public", + "kind": "property" + }, + { + "name": "EAFCPPAEHJA", + "type": "string", + "accessibility": "public", + "kind": "property" + }, + { + "name": "FDPLOMBGJGJ", + "type": "string", + "accessibility": "public", + "kind": "property" + }, + { + "name": "HPCKFCEFFOJ", + "type": "MBALPDDOMME", + "accessibility": "public", + "kind": "property" + }, + { + "name": "KFJBDJMKHKG", + "type": "bool", + "accessibility": "public", + "kind": "property" + }, + { + "name": "LKIBJOMNFFD", + "type": "string", + "accessibility": "public", + "kind": "property" + }, + { + "name": "PFACIBEMBNO", + "type": "string", + "accessibility": "public", + "kind": "property" + }, + { + "name": "PFJIBIPNDCA", + "type": "FCIKKFJOMNO", + "accessibility": "public", + "kind": "property" + } + ], + "fields": [], + "enumValues": [], + "wireKeys": [ + "AnnouncementId", + "AnnouncementType", + "Title", + "Body", + "ImageName", + "LinkType", + "LinkName", + "LinkUri", + "Platform", + "CreatedAt" + ], + "isilFile": "MKEKHKEHPLO.txt", + "isilType": "MKEKHKEHPLO" + } + } + ] + }, + { + "family": "misc", + "route": "api/catalog/v1/all?onlyAvailableSkus=true", + "evidence": [ + { + "type": "GCBPKHFJKCE", + "method": "RecRoom.Async.IPromise`1> KABHMCDHHIP(System.Boolean INGAKMAAHKL = False)", + "file": "GCBPKHFJKCE.txt", + "line": 555, + "contract": { + "kind": "array", + "raw": "System.Collections.Generic.List`1", + "rootType": "DJGBECJHOKF", + "item": { + "kind": "object", + "raw": "GCBPKHFJKCE+DJGBECJHOKF", + "rootType": "DJGBECJHOKF" + }, + "typeDescription": { + "name": "DJGBECJHOKF", + "found": true, + "file": "GCBPKHFJKCE.cs", + "declaration": "internal class DJGBECJHOKF : IFAIJAGLDFK", + "kind": "class", + "baseType": "IFAIJAGLDFK", + "properties": [ + { + "name": "AHDFJDKLFAD", + "type": "string", + "accessibility": "public", + "kind": "property" + }, + { + "name": "AHGCOGFEEEE", + "type": "string", + "accessibility": "public", + "kind": "property" + }, + { + "name": "BEIIFHKMAEP", + "type": "string", + "accessibility": "public", + "kind": "property" + }, + { + "name": "BGBHACHJMJJ", + "type": "string", + "accessibility": "public", + "kind": "property" + }, + { + "name": "DIDDIAMJHON", + "type": "string", + "accessibility": "public", + "kind": "property" + }, + { + "name": "ELKEGFGFBCG", + "type": "string", + "accessibility": "public", + "kind": "property" + }, + { + "name": "FIKEBGGCDFN", + "type": "string", + "accessibility": "public", + "kind": "property" + }, + { + "name": "GDNNKIILCLB", + "type": "string", + "accessibility": "public", + "kind": "property" + }, + { + "name": "HFMHNNJMABK", + "type": "int", + "accessibility": "public", + "kind": "property" + }, + { + "name": "KODBEJPEFOJ", + "type": "string", + "accessibility": "public", + "kind": "property" + }, + { + "name": "LFJIIFCODHO", + "type": "bool", + "accessibility": "public", + "kind": "property" + }, + { + "name": "NPDJLOOHMDJ", + "type": "int", + "accessibility": "public", + "kind": "property" + }, + { + "name": "PBPPCMNHODC", + "type": "CACOIEJCAKJ", + "accessibility": "public", + "kind": "property" + } + ], + "fields": [], + "enumValues": [], + "wireKeys": [ + "skuId", + "name", + "description", + "imageName", + "price", + "oculusSkuId", + "psnProductLabel", + "xboxProductId", + "isSingleUse", + "data", + "appleProductId" + ], + "isilFile": "GCBPKHFJKCE_NestedType_DJGBECJHOKF.txt", + "isilType": "GCBPKHFJKCE+DJGBECJHOKF" + } + } + }, + { + "type": "GCBPKHFJKCE", + "method": "RecRoom.Async.IPromise`1> KABHMCDHHIP(System.Boolean INGAKMAAHKL = False)", + "file": "GCBPKHFJKCE.txt", + "line": 577, + "contract": { + "kind": "array", + "raw": "System.Collections.Generic.List`1", + "rootType": "DJGBECJHOKF", + "item": { + "kind": "object", + "raw": "GCBPKHFJKCE+DJGBECJHOKF", + "rootType": "DJGBECJHOKF" + }, + "typeDescription": { + "name": "DJGBECJHOKF", + "found": true, + "file": "GCBPKHFJKCE.cs", + "declaration": "internal class DJGBECJHOKF : IFAIJAGLDFK", + "kind": "class", + "baseType": "IFAIJAGLDFK", + "properties": [ + { + "name": "AHDFJDKLFAD", + "type": "string", + "accessibility": "public", + "kind": "property" + }, + { + "name": "AHGCOGFEEEE", + "type": "string", + "accessibility": "public", + "kind": "property" + }, + { + "name": "BEIIFHKMAEP", + "type": "string", + "accessibility": "public", + "kind": "property" + }, + { + "name": "BGBHACHJMJJ", + "type": "string", + "accessibility": "public", + "kind": "property" + }, + { + "name": "DIDDIAMJHON", + "type": "string", + "accessibility": "public", + "kind": "property" + }, + { + "name": "ELKEGFGFBCG", + "type": "string", + "accessibility": "public", + "kind": "property" + }, + { + "name": "FIKEBGGCDFN", + "type": "string", + "accessibility": "public", + "kind": "property" + }, + { + "name": "GDNNKIILCLB", + "type": "string", + "accessibility": "public", + "kind": "property" + }, + { + "name": "HFMHNNJMABK", + "type": "int", + "accessibility": "public", + "kind": "property" + }, + { + "name": "KODBEJPEFOJ", + "type": "string", + "accessibility": "public", + "kind": "property" + }, + { + "name": "LFJIIFCODHO", + "type": "bool", + "accessibility": "public", + "kind": "property" + }, + { + "name": "NPDJLOOHMDJ", + "type": "int", + "accessibility": "public", + "kind": "property" + }, + { + "name": "PBPPCMNHODC", + "type": "CACOIEJCAKJ", + "accessibility": "public", + "kind": "property" + } + ], + "fields": [], + "enumValues": [], + "wireKeys": [ + "skuId", + "name", + "description", + "imageName", + "price", + "oculusSkuId", + "psnProductLabel", + "xboxProductId", + "isSingleUse", + "data", + "appleProductId" + ], + "isilFile": "GCBPKHFJKCE_NestedType_DJGBECJHOKF.txt", + "isilType": "GCBPKHFJKCE+DJGBECJHOKF" + } + } + }, + { + "type": "GCBPKHFJKCE+<>c", + "method": "System.Void b__41_1()", + "file": "GCBPKHFJKCE_NestedType___c.txt", + "line": 791, + "contract": { + "kind": "unknown", + "raw": "", + "rootType": "unknown" + } + }, + { + "type": "GCBPKHFJKCE+<>c", + "method": "System.Void b__33_1()", + "file": "GCBPKHFJKCE_NestedType___c.txt", + "line": 246, + "contract": { + "kind": "unknown", + "raw": "", + "rootType": "unknown" + } + }, + { + "type": "GCBPKHFJKCE+<>c", + "method": "System.Void b__34_1()", + "file": "GCBPKHFJKCE_NestedType___c.txt", + "line": 332, + "contract": { + "kind": "unknown", + "raw": "", + "rootType": "unknown" + } + } + ], + "contracts": [ + { + "kind": "array", + "raw": "System.Collections.Generic.List`1", + "rootType": "DJGBECJHOKF", + "item": { + "kind": "object", + "raw": "GCBPKHFJKCE+DJGBECJHOKF", + "rootType": "DJGBECJHOKF" + }, + "typeDescription": { + "name": "DJGBECJHOKF", + "found": true, + "file": "GCBPKHFJKCE.cs", + "declaration": "internal class DJGBECJHOKF : IFAIJAGLDFK", + "kind": "class", + "baseType": "IFAIJAGLDFK", + "properties": [ + { + "name": "AHDFJDKLFAD", + "type": "string", + "accessibility": "public", + "kind": "property" + }, + { + "name": "AHGCOGFEEEE", + "type": "string", + "accessibility": "public", + "kind": "property" + }, + { + "name": "BEIIFHKMAEP", + "type": "string", + "accessibility": "public", + "kind": "property" + }, + { + "name": "BGBHACHJMJJ", + "type": "string", + "accessibility": "public", + "kind": "property" + }, + { + "name": "DIDDIAMJHON", + "type": "string", + "accessibility": "public", + "kind": "property" + }, + { + "name": "ELKEGFGFBCG", + "type": "string", + "accessibility": "public", + "kind": "property" + }, + { + "name": "FIKEBGGCDFN", + "type": "string", + "accessibility": "public", + "kind": "property" + }, + { + "name": "GDNNKIILCLB", + "type": "string", + "accessibility": "public", + "kind": "property" + }, + { + "name": "HFMHNNJMABK", + "type": "int", + "accessibility": "public", + "kind": "property" + }, + { + "name": "KODBEJPEFOJ", + "type": "string", + "accessibility": "public", + "kind": "property" + }, + { + "name": "LFJIIFCODHO", + "type": "bool", + "accessibility": "public", + "kind": "property" + }, + { + "name": "NPDJLOOHMDJ", + "type": "int", + "accessibility": "public", + "kind": "property" + }, + { + "name": "PBPPCMNHODC", + "type": "CACOIEJCAKJ", + "accessibility": "public", + "kind": "property" + } + ], + "fields": [], + "enumValues": [], + "wireKeys": [ + "skuId", + "name", + "description", + "imageName", + "price", + "oculusSkuId", + "psnProductLabel", + "xboxProductId", + "isSingleUse", + "data", + "appleProductId" + ], + "isilFile": "GCBPKHFJKCE_NestedType_DJGBECJHOKF.txt", + "isilType": "GCBPKHFJKCE+DJGBECJHOKF" + } + }, + { + "kind": "unknown", + "raw": "", + "rootType": "unknown", + "typeDescription": { + "name": "unknown", + "found": false, + "kind": "object", + "wireKeys": [] + } + } + ] + }, + { + "family": "misc", + "route": "api/communityboard/", + "evidence": [ + { + "type": "AAHIIPOCKMB", + "method": "RecRoom.Async.IPromise`1 KIAGEGDDBCG()", + "file": "AAHIIPOCKMB.txt", + "line": 941, + "contract": { + "kind": "object", + "raw": "AAHIIPOCKMB+JFIKDPALHOL", + "rootType": "JFIKDPALHOL", + "typeDescription": { + "name": "JFIKDPALHOL", + "found": true, + "file": "AAHIIPOCKMB.cs", + "declaration": "internal class JFIKDPALHOL : IFAIJAGLDFK", + "kind": "class", + "baseType": "IFAIJAGLDFK", + "properties": [ + { + "name": "AIPAMBHPAHP", + "type": "DOBMPPKDFPI", + "accessibility": "public", + "kind": "property" + }, + { + "name": "GPHCPJEKDLB", + "type": "NMPFCIJPODA", + "accessibility": "public", + "kind": "property" + }, + { + "name": "LBFMANLIKEI", + "type": "List", + "accessibility": "public", + "kind": "property" + }, + { + "name": "LCJDMHPMFBP", + "type": "EHNFCEJANHA", + "accessibility": "public", + "kind": "property" + }, + { + "name": "MCCBJHDGGAG", + "type": "List", + "accessibility": "public", + "kind": "property" + } + ], + "fields": [], + "enumValues": [], + "wireKeys": [ + "FeaturedPlayer", + "FeaturedRoomGroup", + "CurrentAnnouncement" + ], + "isilFile": "AAHIIPOCKMB_NestedType_JFIKDPALHOL.txt", + "isilType": "AAHIIPOCKMB+JFIKDPALHOL" + } + } + } + ], + "contracts": [ + { + "kind": "object", + "raw": "AAHIIPOCKMB+JFIKDPALHOL", + "rootType": "JFIKDPALHOL", + "typeDescription": { + "name": "JFIKDPALHOL", + "found": true, + "file": "AAHIIPOCKMB.cs", + "declaration": "internal class JFIKDPALHOL : IFAIJAGLDFK", + "kind": "class", + "baseType": "IFAIJAGLDFK", + "properties": [ + { + "name": "AIPAMBHPAHP", + "type": "DOBMPPKDFPI", + "accessibility": "public", + "kind": "property" + }, + { + "name": "GPHCPJEKDLB", + "type": "NMPFCIJPODA", + "accessibility": "public", + "kind": "property" + }, + { + "name": "LBFMANLIKEI", + "type": "List", + "accessibility": "public", + "kind": "property" + }, + { + "name": "LCJDMHPMFBP", + "type": "EHNFCEJANHA", + "accessibility": "public", + "kind": "property" + }, + { + "name": "MCCBJHDGGAG", + "type": "List", + "accessibility": "public", + "kind": "property" + } + ], + "fields": [], + "enumValues": [], + "wireKeys": [ + "FeaturedPlayer", + "FeaturedRoomGroup", + "CurrentAnnouncement" + ], + "isilFile": "AAHIIPOCKMB_NestedType_JFIKDPALHOL.txt", + "isilType": "AAHIIPOCKMB+JFIKDPALHOL" + } + } + ] + }, + { + "family": "misc", + "route": "api/consumables/", + "evidence": [ + { + "type": "FOEHJBDMMBH", + "method": "RecRoom.Async.IPromise AKFDEPEHBIN(OOCCCGPICOG BAJGNABECMN, System.Int32 HLHHPEKALPI)", + "file": "FOEHJBDMMBH.txt", + "line": 1463, + "contract": { + "kind": "success-or-empty", + "raw": "RecRoom.Async.IPromise", + "rootType": "void/success", + "typeDescription": { + "name": "void/success", + "found": false, + "kind": "object", + "wireKeys": [] + } + } + }, + { + "type": "FOEHJBDMMBH", + "method": "RecRoom.Async.IPromise`1> AFMKIHFMMBI(System.Int32 HLHHPEKALPI)", + "file": "FOEHJBDMMBH.txt", + "line": 1112, + "contract": { + "kind": "array", + "raw": "System.Collections.Generic.List`1", + "rootType": "OOCCCGPICOG", + "item": { + "kind": "object", + "raw": "OOCCCGPICOG", + "rootType": "OOCCCGPICOG" + }, + "typeDescription": { + "name": "OOCCCGPICOG", + "found": true, + "file": "OOCCCGPICOG.cs", + "declaration": "public class OOCCCGPICOG : IFAIJAGLDFK, IEquatable", + "kind": "class", + "baseType": "IFAIJAGLDFK", + "properties": [ + { + "name": "ACBFDMLHFPB", + "type": "DateTime", + "accessibility": "public", + "kind": "property" + }, + { + "name": "AFPIKAOJDGM", + "type": "bool", + "accessibility": "public", + "kind": "property" + }, + { + "name": "BPFGOHALEJO", + "type": "Nullable", + "accessibility": "public", + "kind": "property" + }, + { + "name": "GMMAAAGFAKO", + "type": "KIDKMAOBOIG", + "accessibility": "public", + "kind": "property" + }, + { + "name": "HAKADJAKPDO", + "type": "string", + "accessibility": "public", + "kind": "property" + }, + { + "name": "JPOHGBCEJEJ", + "type": "long", + "accessibility": "public", + "kind": "property" + }, + { + "name": "LENBPGEABJK", + "type": "ConsumableInfo", + "accessibility": "public", + "kind": "property" + }, + { + "name": "MAPJICLFKFB", + "type": "int", + "accessibility": "public", + "kind": "property" + }, + { + "name": "MEPGPGKFENE", + "type": "int", + "accessibility": "public", + "kind": "property" + }, + { + "name": "MIKDLDEALPN", + "type": "bool", + "accessibility": "public", + "kind": "property" + } + ], + "fields": [], + "enumValues": [], + "wireKeys": [ + "Id", + "ConsumableItemDesc", + "CreatedAt", + "Count", + "InitialCount", + "IsActive", + "ActiveDurationMinutes", + "IsTransferable" + ], + "isilFile": "OOCCCGPICOG.txt", + "isilType": "OOCCCGPICOG" + } + } + }, + { + "type": "FOEHJBDMMBH", + "method": "System.Collections.IEnumerator LCELAOEOIJO(OOCCCGPICOG BAJGNABECMN, System.Int32 FKKAPHAMPMG, BPHGKAEDBPE+CBEOHBCIPEA AFLPGGJMPOE)", + "file": "FOEHJBDMMBH.txt", + "line": 838, + "contract": { + "kind": "callback-or-coroutine", + "raw": "IEnumerator", + "rootType": "callback", + "typeDescription": { + "name": "callback", + "found": false, + "kind": "object", + "wireKeys": [] + } + } + }, + { + "type": "FOEHJBDMMBH", + "method": "System.Collections.IEnumerator PKHAMBFCEJI(OOCCCGPICOG BAJGNABECMN, System.Boolean CNEIBILGFCO, BPHGKAEDBPE+CBEOHBCIPEA AFLPGGJMPOE)", + "file": "FOEHJBDMMBH.txt", + "line": 619, + "contract": { + "kind": "callback-or-coroutine", + "raw": "IEnumerator", + "rootType": "callback", + "typeDescription": { + "name": "callback", + "found": false, + "kind": "object", + "wireKeys": [] + } + } + }, + { + "type": "FOEHJBDMMBH+HHFGPIBAMOB", + "method": "System.Boolean MoveNext()", + "file": "FOEHJBDMMBH_NestedType_HHFGPIBAMOB.txt", + "line": 147, + "contract": { + "kind": "unknown", + "raw": "", + "rootType": "unknown" + } + } + ], + "contracts": [ + { + "kind": "success-or-empty", + "raw": "RecRoom.Async.IPromise", + "rootType": "void/success", + "typeDescription": { + "name": "void/success", + "found": false, + "kind": "object", + "wireKeys": [] + } + }, + { + "kind": "array", + "raw": "System.Collections.Generic.List`1", + "rootType": "OOCCCGPICOG", + "item": { + "kind": "object", + "raw": "OOCCCGPICOG", + "rootType": "OOCCCGPICOG" + }, + "typeDescription": { + "name": "OOCCCGPICOG", + "found": true, + "file": "OOCCCGPICOG.cs", + "declaration": "public class OOCCCGPICOG : IFAIJAGLDFK, IEquatable", + "kind": "class", + "baseType": "IFAIJAGLDFK", + "properties": [ + { + "name": "ACBFDMLHFPB", + "type": "DateTime", + "accessibility": "public", + "kind": "property" + }, + { + "name": "AFPIKAOJDGM", + "type": "bool", + "accessibility": "public", + "kind": "property" + }, + { + "name": "BPFGOHALEJO", + "type": "Nullable", + "accessibility": "public", + "kind": "property" + }, + { + "name": "GMMAAAGFAKO", + "type": "KIDKMAOBOIG", + "accessibility": "public", + "kind": "property" + }, + { + "name": "HAKADJAKPDO", + "type": "string", + "accessibility": "public", + "kind": "property" + }, + { + "name": "JPOHGBCEJEJ", + "type": "long", + "accessibility": "public", + "kind": "property" + }, + { + "name": "LENBPGEABJK", + "type": "ConsumableInfo", + "accessibility": "public", + "kind": "property" + }, + { + "name": "MAPJICLFKFB", + "type": "int", + "accessibility": "public", + "kind": "property" + }, + { + "name": "MEPGPGKFENE", + "type": "int", + "accessibility": "public", + "kind": "property" + }, + { + "name": "MIKDLDEALPN", + "type": "bool", + "accessibility": "public", + "kind": "property" + } + ], + "fields": [], + "enumValues": [], + "wireKeys": [ + "Id", + "ConsumableItemDesc", + "CreatedAt", + "Count", + "InitialCount", + "IsActive", + "ActiveDurationMinutes", + "IsTransferable" + ], + "isilFile": "OOCCCGPICOG.txt", + "isilType": "OOCCCGPICOG" + } + }, + { + "kind": "callback-or-coroutine", + "raw": "IEnumerator", + "rootType": "callback", + "typeDescription": { + "name": "callback", + "found": false, + "kind": "object", + "wireKeys": [] + } + }, + { + "kind": "unknown", + "raw": "", + "rootType": "unknown", + "typeDescription": { + "name": "unknown", + "found": false, + "kind": "object", + "wireKeys": [] + } + } + ] + }, + { + "family": "misc", + "route": "api/curatedroomplaylists", + "evidence": [ + { + "type": "FPNPBJBCMKB", + "method": "RecRoom.Async.IPromise`1> EPPPMHMMGME()", + "file": "FPNPBJBCMKB.txt", + "line": 53, + "contract": { + "kind": "array", + "raw": "System.Collections.Generic.List`1", + "rootType": "Int64", + "item": { + "kind": "primitive", + "raw": "System.Int64", + "rootType": "number" + }, + "typeDescription": { + "name": "Int64", + "found": false, + "kind": "primitive", + "wireKeys": [] + } + } + } + ], + "contracts": [ + { + "kind": "array", + "raw": "System.Collections.Generic.List`1", + "rootType": "Int64", + "item": { + "kind": "primitive", + "raw": "System.Int64", + "rootType": "number" + }, + "typeDescription": { + "name": "Int64", + "found": false, + "kind": "primitive", + "wireKeys": [] + } + } + ] + }, + { + "family": "misc", + "route": "api/objectives/", + "evidence": [ + { + "type": "GMPIMLJNACB", + "method": "System.Collections.IEnumerator IPCNHLDICBM(BPHGKAEDBPE+CBEOHBCIPEA AFLPGGJMPOE)", + "file": "GMPIMLJNACB.txt", + "line": 904, + "contract": { + "kind": "callback-or-coroutine", + "raw": "IEnumerator", + "rootType": "callback", + "typeDescription": { + "name": "callback", + "found": false, + "kind": "object", + "wireKeys": [] + } + } + }, + { + "type": "GMPIMLJNACB", + "method": "System.Void MEGMCCFCEIP(System.Int32 CKEBCMEKCJH, BPHGKAEDBPE+OJDJIJDNFHE AFLPGGJMPOE)", + "file": "GMPIMLJNACB.txt", + "line": 2171, + "contract": { + "kind": "unknown", + "raw": "", + "rootType": "unknown" + } + }, + { + "type": "GMPIMLJNACB+GIDAGFNPPLC", + "method": "System.Boolean MoveNext()", + "file": "GMPIMLJNACB_NestedType_GIDAGFNPPLC.txt", + "line": 568, + "contract": { + "kind": "unknown", + "raw": "", + "rootType": "unknown" + } + }, + { + "type": "GMPIMLJNACB+GIDAGFNPPLC", + "method": "System.Boolean MoveNext()", + "file": "GMPIMLJNACB_NestedType_GIDAGFNPPLC.txt", + "line": 623, + "contract": { + "kind": "unknown", + "raw": "", + "rootType": "unknown" + } + } + ], + "contracts": [ + { + "kind": "callback-or-coroutine", + "raw": "IEnumerator", + "rootType": "callback", + "typeDescription": { + "name": "callback", + "found": false, + "kind": "object", + "wireKeys": [] + } + }, + { + "kind": "unknown", + "raw": "", + "rootType": "unknown", + "typeDescription": { + "name": "unknown", + "found": false, + "kind": "object", + "wireKeys": [] + } + } + ] + }, + { + "family": "misc", + "route": "api/PlayersBanned/", + "evidence": [ + { + "type": "GGLGFENEKBJ", + "method": "System.Collections.IEnumerator BFJMPGHLGAN(System.Int64 CJFGEMGOJHB, LCCEEFHOBEN+CJFENPHAAHI MEABFEIBEMP, BPHGKAEDBPE+CBEOHBCIPEA AFLPGGJMPOE)", + "file": "GGLGFENEKBJ.txt", + "line": 586, + "contract": { + "kind": "callback-or-coroutine", + "raw": "IEnumerator", + "rootType": "callback", + "typeDescription": { + "name": "callback", + "found": false, + "kind": "object", + "wireKeys": [] + } + } + } + ], + "contracts": [ + { + "kind": "callback-or-coroutine", + "raw": "IEnumerator", + "rootType": "callback", + "typeDescription": { + "name": "callback", + "found": false, + "kind": "object", + "wireKeys": [] + } + } + ] + }, + { + "family": "misc", + "route": "api/relationships/", + "evidence": [ + { + "type": "FGPIDGLCKEF", + "method": "RecRoom.Async.IPromise`1 CGPBLDOHCEI(System.Int32 CJFGEMGOJHB, CFLMJLFBOKH+MPFKNCFEDHF DIEPOPNJNCO)", + "file": "FGPIDGLCKEF.txt", + "line": 1454, + "contract": { + "kind": "object", + "raw": "LLKHFJDNFMM", + "rootType": "LLKHFJDNFMM", + "typeDescription": { + "name": "LLKHFJDNFMM", + "found": true, + "file": "LLKHFJDNFMM.cs", + "declaration": "public class LLKHFJDNFMM : IFAIJAGLDFK", + "kind": "class", + "baseType": "IFAIJAGLDFK", + "properties": [ + { + "name": "CJEPCMDFKNF", + "type": "enum", + "accessibility": "internal", + "kind": "property" + }, + { + "name": "DDAADAMFMCL", + "type": "enum", + "accessibility": "internal", + "kind": "property" + }, + { + "name": "COJNNAGMBJF", + "type": "int", + "accessibility": "public", + "kind": "property" + }, + { + "name": "CPAAFLNLLMP", + "type": "bool", + "accessibility": "public", + "kind": "property" + }, + { + "name": "GCGHEMGJCJG", + "type": "CJEPCMDFKNF", + "accessibility": "public", + "kind": "property" + }, + { + "name": "HHFLKFOEHNL", + "type": "CJEPCMDFKNF", + "accessibility": "public", + "kind": "property" + }, + { + "name": "LHMMBEPNODC", + "type": "CJEPCMDFKNF", + "accessibility": "public", + "kind": "property" + }, + { + "name": "MHEGHKNGOEG", + "type": "bool", + "accessibility": "public", + "kind": "property" + }, + { + "name": "NPBCMKLOCAE", + "type": "bool", + "accessibility": "public", + "kind": "property" + }, + { + "name": "OPLHMKFCNOL", + "type": "DDAADAMFMCL", + "accessibility": "public", + "kind": "property" + }, + { + "name": "PLJNOOENNDP", + "type": "bool", + "accessibility": "public", + "kind": "property" + } + ], + "fields": [], + "enumValues": [], + "wireKeys": [ + "PlayerID", + "RelationshipType", + "Muted", + "Ignored", + "Favorited" + ], + "isilFile": "LLKHFJDNFMM.txt", + "isilType": "LLKHFJDNFMM" + } + } + }, + { + "type": "FGPIDGLCKEF", + "method": "RecRoom.Async.IPromise`1 DLBAPGPACLP(System.Int32 CJFGEMGOJHB)", + "file": "FGPIDGLCKEF.txt", + "line": 1827, + "contract": { + "kind": "object", + "raw": "LLKHFJDNFMM", + "rootType": "LLKHFJDNFMM", + "typeDescription": { + "name": "LLKHFJDNFMM", + "found": true, + "file": "LLKHFJDNFMM.cs", + "declaration": "public class LLKHFJDNFMM : IFAIJAGLDFK", + "kind": "class", + "baseType": "IFAIJAGLDFK", + "properties": [ + { + "name": "CJEPCMDFKNF", + "type": "enum", + "accessibility": "internal", + "kind": "property" + }, + { + "name": "DDAADAMFMCL", + "type": "enum", + "accessibility": "internal", + "kind": "property" + }, + { + "name": "COJNNAGMBJF", + "type": "int", + "accessibility": "public", + "kind": "property" + }, + { + "name": "CPAAFLNLLMP", + "type": "bool", + "accessibility": "public", + "kind": "property" + }, + { + "name": "GCGHEMGJCJG", + "type": "CJEPCMDFKNF", + "accessibility": "public", + "kind": "property" + }, + { + "name": "HHFLKFOEHNL", + "type": "CJEPCMDFKNF", + "accessibility": "public", + "kind": "property" + }, + { + "name": "LHMMBEPNODC", + "type": "CJEPCMDFKNF", + "accessibility": "public", + "kind": "property" + }, + { + "name": "MHEGHKNGOEG", + "type": "bool", + "accessibility": "public", + "kind": "property" + }, + { + "name": "NPBCMKLOCAE", + "type": "bool", + "accessibility": "public", + "kind": "property" + }, + { + "name": "OPLHMKFCNOL", + "type": "DDAADAMFMCL", + "accessibility": "public", + "kind": "property" + }, + { + "name": "PLJNOOENNDP", + "type": "bool", + "accessibility": "public", + "kind": "property" + } + ], + "fields": [], + "enumValues": [], + "wireKeys": [ + "PlayerID", + "RelationshipType", + "Muted", + "Ignored", + "Favorited" + ], + "isilFile": "LLKHFJDNFMM.txt", + "isilType": "LLKHFJDNFMM" + } + } + }, + { + "type": "FGPIDGLCKEF", + "method": "RecRoom.Async.IPromise`1 DOIEMGNIMMM(System.Int32 CJFGEMGOJHB, BPHGKAEDBPE+CBEOHBCIPEA AFLPGGJMPOE = null)", + "file": "FGPIDGLCKEF.txt", + "line": 1197, + "contract": { + "kind": "object", + "raw": "LLKHFJDNFMM", + "rootType": "LLKHFJDNFMM", + "typeDescription": { + "name": "LLKHFJDNFMM", + "found": true, + "file": "LLKHFJDNFMM.cs", + "declaration": "public class LLKHFJDNFMM : IFAIJAGLDFK", + "kind": "class", + "baseType": "IFAIJAGLDFK", + "properties": [ + { + "name": "CJEPCMDFKNF", + "type": "enum", + "accessibility": "internal", + "kind": "property" + }, + { + "name": "DDAADAMFMCL", + "type": "enum", + "accessibility": "internal", + "kind": "property" + }, + { + "name": "COJNNAGMBJF", + "type": "int", + "accessibility": "public", + "kind": "property" + }, + { + "name": "CPAAFLNLLMP", + "type": "bool", + "accessibility": "public", + "kind": "property" + }, + { + "name": "GCGHEMGJCJG", + "type": "CJEPCMDFKNF", + "accessibility": "public", + "kind": "property" + }, + { + "name": "HHFLKFOEHNL", + "type": "CJEPCMDFKNF", + "accessibility": "public", + "kind": "property" + }, + { + "name": "LHMMBEPNODC", + "type": "CJEPCMDFKNF", + "accessibility": "public", + "kind": "property" + }, + { + "name": "MHEGHKNGOEG", + "type": "bool", + "accessibility": "public", + "kind": "property" + }, + { + "name": "NPBCMKLOCAE", + "type": "bool", + "accessibility": "public", + "kind": "property" + }, + { + "name": "OPLHMKFCNOL", + "type": "DDAADAMFMCL", + "accessibility": "public", + "kind": "property" + }, + { + "name": "PLJNOOENNDP", + "type": "bool", + "accessibility": "public", + "kind": "property" + } + ], + "fields": [], + "enumValues": [], + "wireKeys": [ + "PlayerID", + "RelationshipType", + "Muted", + "Ignored", + "Favorited" + ], + "isilFile": "LLKHFJDNFMM.txt", + "isilType": "LLKHFJDNFMM" + } + } + }, + { + "type": "FGPIDGLCKEF", + "method": "RecRoom.Async.IPromise`1 JEGAGBBBADJ(System.Int32 CJFGEMGOJHB)", + "file": "FGPIDGLCKEF.txt", + "line": 1591, + "contract": { + "kind": "object", + "raw": "LLKHFJDNFMM", + "rootType": "LLKHFJDNFMM", + "typeDescription": { + "name": "LLKHFJDNFMM", + "found": true, + "file": "LLKHFJDNFMM.cs", + "declaration": "public class LLKHFJDNFMM : IFAIJAGLDFK", + "kind": "class", + "baseType": "IFAIJAGLDFK", + "properties": [ + { + "name": "CJEPCMDFKNF", + "type": "enum", + "accessibility": "internal", + "kind": "property" + }, + { + "name": "DDAADAMFMCL", + "type": "enum", + "accessibility": "internal", + "kind": "property" + }, + { + "name": "COJNNAGMBJF", + "type": "int", + "accessibility": "public", + "kind": "property" + }, + { + "name": "CPAAFLNLLMP", + "type": "bool", + "accessibility": "public", + "kind": "property" + }, + { + "name": "GCGHEMGJCJG", + "type": "CJEPCMDFKNF", + "accessibility": "public", + "kind": "property" + }, + { + "name": "HHFLKFOEHNL", + "type": "CJEPCMDFKNF", + "accessibility": "public", + "kind": "property" + }, + { + "name": "LHMMBEPNODC", + "type": "CJEPCMDFKNF", + "accessibility": "public", + "kind": "property" + }, + { + "name": "MHEGHKNGOEG", + "type": "bool", + "accessibility": "public", + "kind": "property" + }, + { + "name": "NPBCMKLOCAE", + "type": "bool", + "accessibility": "public", + "kind": "property" + }, + { + "name": "OPLHMKFCNOL", + "type": "DDAADAMFMCL", + "accessibility": "public", + "kind": "property" + }, + { + "name": "PLJNOOENNDP", + "type": "bool", + "accessibility": "public", + "kind": "property" + } + ], + "fields": [], + "enumValues": [], + "wireKeys": [ + "PlayerID", + "RelationshipType", + "Muted", + "Ignored", + "Favorited" + ], + "isilFile": "LLKHFJDNFMM.txt", + "isilType": "LLKHFJDNFMM" + } + } + }, + { + "type": "FGPIDGLCKEF", + "method": "RecRoom.Async.IPromise`1 OILLHGFDILD(System.Int32 CJFGEMGOJHB)", + "file": "FGPIDGLCKEF.txt", + "line": 1709, + "contract": { + "kind": "object", + "raw": "LLKHFJDNFMM", + "rootType": "LLKHFJDNFMM", + "typeDescription": { + "name": "LLKHFJDNFMM", + "found": true, + "file": "LLKHFJDNFMM.cs", + "declaration": "public class LLKHFJDNFMM : IFAIJAGLDFK", + "kind": "class", + "baseType": "IFAIJAGLDFK", + "properties": [ + { + "name": "CJEPCMDFKNF", + "type": "enum", + "accessibility": "internal", + "kind": "property" + }, + { + "name": "DDAADAMFMCL", + "type": "enum", + "accessibility": "internal", + "kind": "property" + }, + { + "name": "COJNNAGMBJF", + "type": "int", + "accessibility": "public", + "kind": "property" + }, + { + "name": "CPAAFLNLLMP", + "type": "bool", + "accessibility": "public", + "kind": "property" + }, + { + "name": "GCGHEMGJCJG", + "type": "CJEPCMDFKNF", + "accessibility": "public", + "kind": "property" + }, + { + "name": "HHFLKFOEHNL", + "type": "CJEPCMDFKNF", + "accessibility": "public", + "kind": "property" + }, + { + "name": "LHMMBEPNODC", + "type": "CJEPCMDFKNF", + "accessibility": "public", + "kind": "property" + }, + { + "name": "MHEGHKNGOEG", + "type": "bool", + "accessibility": "public", + "kind": "property" + }, + { + "name": "NPBCMKLOCAE", + "type": "bool", + "accessibility": "public", + "kind": "property" + }, + { + "name": "OPLHMKFCNOL", + "type": "DDAADAMFMCL", + "accessibility": "public", + "kind": "property" + }, + { + "name": "PLJNOOENNDP", + "type": "bool", + "accessibility": "public", + "kind": "property" + } + ], + "fields": [], + "enumValues": [], + "wireKeys": [ + "PlayerID", + "RelationshipType", + "Muted", + "Ignored", + "Favorited" + ], + "isilFile": "LLKHFJDNFMM.txt", + "isilType": "LLKHFJDNFMM" + } + } + }, + { + "type": "FGPIDGLCKEF", + "method": "System.Boolean CKANLFMOMCC(System.Int32 CJFGEMGOJHB, BPHGKAEDBPE+CBEOHBCIPEA AFLPGGJMPOE = null)", + "file": "FGPIDGLCKEF.txt", + "line": 2141, + "contract": { + "kind": "unknown", + "raw": "", + "rootType": "unknown" + } + }, + { + "type": "FGPIDGLCKEF", + "method": "System.Boolean GDOLAFPLNPG(System.Int32 CJFGEMGOJHB, BPHGKAEDBPE+CBEOHBCIPEA AFLPGGJMPOE = null)", + "file": "FGPIDGLCKEF.txt", + "line": 1982, + "contract": { + "kind": "unknown", + "raw": "", + "rootType": "unknown" + } + }, + { + "type": "FGPIDGLCKEF", + "method": "System.Void CPGAJIACKIP()", + "file": "FGPIDGLCKEF.txt", + "line": 3002, + "contract": { + "kind": "unknown", + "raw": "", + "rootType": "unknown" + } + }, + { + "type": "FGPIDGLCKEF", + "method": "System.Void IMLJEBCMMKK(PlatformManager+FCIKKFJOMNO FPMCLJDEGKL, System.Collections.Generic.List`1 CIBFMCPBLEO)", + "file": "FGPIDGLCKEF.txt", + "line": 2832, + "contract": { + "kind": "unknown", + "raw": "", + "rootType": "unknown" + } + }, + { + "type": "FGPIDGLCKEF", + "method": "System.Void KDKALCLMLIL(System.String GJIOCANDGPE, System.Int32 CJFGEMGOJHB, BPHGKAEDBPE+CBEOHBCIPEA AFLPGGJMPOE = null)", + "file": "FGPIDGLCKEF.txt", + "line": 2670, + "contract": { + "kind": "unknown", + "raw": "", + "rootType": "unknown" + } + } + ], + "contracts": [ + { + "kind": "object", + "raw": "LLKHFJDNFMM", + "rootType": "LLKHFJDNFMM", + "typeDescription": { + "name": "LLKHFJDNFMM", + "found": true, + "file": "LLKHFJDNFMM.cs", + "declaration": "public class LLKHFJDNFMM : IFAIJAGLDFK", + "kind": "class", + "baseType": "IFAIJAGLDFK", + "properties": [ + { + "name": "CJEPCMDFKNF", + "type": "enum", + "accessibility": "internal", + "kind": "property" + }, + { + "name": "DDAADAMFMCL", + "type": "enum", + "accessibility": "internal", + "kind": "property" + }, + { + "name": "COJNNAGMBJF", + "type": "int", + "accessibility": "public", + "kind": "property" + }, + { + "name": "CPAAFLNLLMP", + "type": "bool", + "accessibility": "public", + "kind": "property" + }, + { + "name": "GCGHEMGJCJG", + "type": "CJEPCMDFKNF", + "accessibility": "public", + "kind": "property" + }, + { + "name": "HHFLKFOEHNL", + "type": "CJEPCMDFKNF", + "accessibility": "public", + "kind": "property" + }, + { + "name": "LHMMBEPNODC", + "type": "CJEPCMDFKNF", + "accessibility": "public", + "kind": "property" + }, + { + "name": "MHEGHKNGOEG", + "type": "bool", + "accessibility": "public", + "kind": "property" + }, + { + "name": "NPBCMKLOCAE", + "type": "bool", + "accessibility": "public", + "kind": "property" + }, + { + "name": "OPLHMKFCNOL", + "type": "DDAADAMFMCL", + "accessibility": "public", + "kind": "property" + }, + { + "name": "PLJNOOENNDP", + "type": "bool", + "accessibility": "public", + "kind": "property" + } + ], + "fields": [], + "enumValues": [], + "wireKeys": [ + "PlayerID", + "RelationshipType", + "Muted", + "Ignored", + "Favorited" + ], + "isilFile": "LLKHFJDNFMM.txt", + "isilType": "LLKHFJDNFMM" + } + }, + { + "kind": "unknown", + "raw": "", + "rootType": "unknown", + "typeDescription": { + "name": "unknown", + "found": false, + "kind": "object", + "wireKeys": [] + } + } + ] + }, + { + "family": "misc", + "route": "api/sanitize/", + "evidence": [ + { + "type": "NKHBKKGOIHL+BBGMJPMOHEO", + "method": "System.Boolean MoveNext()", + "file": "NKHBKKGOIHL_NestedType_BBGMJPMOHEO.txt", + "line": 353, + "contract": { + "kind": "unknown", + "raw": "", + "rootType": "unknown" + } + } + ], + "contracts": [ + { + "kind": "unknown", + "raw": "", + "rootType": "unknown", + "typeDescription": { + "name": "unknown", + "found": false, + "kind": "object", + "wireKeys": [] + } + } + ] + }, + { + "family": "misc", + "route": "api/storefronts/", + "evidence": [ + { + "type": "GEAPBDGCKMB", + "method": "RecRoom.Async.IPromise ACEPPELPFOL(System.Int32 PGGHBNCGLDN, System.Nullable`1 POAOENHPNHE, System.Int32 MNIJHBJDPPA, System.Int32 HLHHPEKALPI)", + "file": "GEAPBDGCKMB.txt", + "line": 2977, + "contract": { + "kind": "success-or-empty", + "raw": "RecRoom.Async.IPromise", + "rootType": "void/success", + "typeDescription": { + "name": "void/success", + "found": false, + "kind": "object", + "wireKeys": [] + } + } + }, + { + "type": "GEAPBDGCKMB", + "method": "RecRoom.Async.IPromise`1> LLNACOGDCMF(ACDKILABNNC DLNFAILEHOA, System.Collections.Generic.IEnumerable`1 JEDEBGBEGCE)", + "file": "GEAPBDGCKMB.txt", + "line": 5929, + "contract": { + "kind": "object", + "raw": "GEAPBDGCKMB+BalanceUpdateResponseDTO`1", + "rootType": "RewardBalanceModificationDTO>", + "typeDescription": { + "name": "RewardBalanceModificationDTO>", + "found": false, + "kind": "object", + "wireKeys": [] + } + } + }, + { + "type": "GEAPBDGCKMB", + "method": "RecRoom.Async.IPromise`1 MOANDFACFAP(System.Int64 OEMDIAHHILF, System.Int32 HBIEIIHBDCI, System.String LGHBPPIOOBM)", + "file": "GEAPBDGCKMB.txt", + "line": 3603, + "contract": { + "kind": "object", + "raw": "GEAPBDGCKMB+InventionPurchaseResponseDTO", + "rootType": "InventionPurchaseResponseDTO", + "typeDescription": { + "name": "InventionPurchaseResponseDTO", + "found": true, + "file": "GEAPBDGCKMB.cs", + "declaration": "internal class InventionPurchaseResponseDTO : IFAIJAGLDFK", + "kind": "class", + "baseType": "IFAIJAGLDFK", + "properties": [ + { + "name": "BalanceResponseDTO", + "type": "class FHLJACIDOHO :", + "accessibility": "internal", + "kind": "property" + }, + { + "name": "IFAIJAGLDFK", + "type": "class GFAPKCFGKLB :", + "accessibility": "internal", + "kind": "property" + }, + { + "name": "NKPDMHMBNEJ", + "type": "KGAACCELMHP", + "accessibility": "public", + "kind": "property" + }, + { + "name": "PBPPCMNHODC", + "type": "OBBBPCBIMME", + "accessibility": "public", + "kind": "property" + }, + { + "name": "LPDGDNGOLFM", + "type": "List", + "accessibility": "public", + "kind": "property" + }, + { + "name": "BalanceUpdateResponse", + "type": "FHLJACIDOHO", + "accessibility": "public", + "kind": "property" + }, + { + "name": "InventionResponse", + "type": "AHEPPAEOLOD", + "accessibility": "public", + "kind": "property" + } + ], + "fields": [], + "enumValues": [], + "wireKeys": [ + "InventionResponse", + "BalanceUpdateResponse" + ], + "isilFile": "GEAPBDGCKMB_NestedType_InventionPurchaseResponseDTO.txt", + "isilType": "GEAPBDGCKMB+InventionPurchaseResponseDTO" + } + } + }, + { + "type": "GEAPBDGCKMB", + "method": "RecRoom.Async.IPromise`1 KGGMAGALNGF(System.Int64 BGICHOOBKLD, System.Int32 HBIEIIHBDCI)", + "file": "GEAPBDGCKMB.txt", + "line": 3819, + "contract": { + "kind": "object", + "raw": "GEAPBDGCKMB+RoomKeyPurchaseResponseDTO", + "rootType": "RoomKeyPurchaseResponseDTO", + "typeDescription": { + "name": "RoomKeyPurchaseResponseDTO", + "found": true, + "file": "GEAPBDGCKMB.cs", + "declaration": "internal class RoomKeyPurchaseResponseDTO : IFAIJAGLDFK", + "kind": "class", + "baseType": "IFAIJAGLDFK", + "properties": [ + { + "name": "BalanceResponseDTO", + "type": "class NAHADLNOINN :", + "accessibility": "internal", + "kind": "property" + }, + { + "name": "IFAIJAGLDFK", + "type": "class ELKBABHKJHC :", + "accessibility": "internal", + "kind": "property" + }, + { + "name": "NKPDMHMBNEJ", + "type": "KGAACCELMHP", + "accessibility": "public", + "kind": "property" + }, + { + "name": "PBPPCMNHODC", + "type": "AMAGKLLBGEC", + "accessibility": "public", + "kind": "property" + }, + { + "name": "LPDGDNGOLFM", + "type": "List", + "accessibility": "public", + "kind": "property" + }, + { + "name": "BalanceUpdateResponse", + "type": "NAHADLNOINN", + "accessibility": "public", + "kind": "property" + }, + { + "name": "RoomKeyResponse", + "type": "BCKIBFNPIPD", + "accessibility": "public", + "kind": "property" + } + ], + "fields": [], + "enumValues": [], + "wireKeys": [ + "RoomKeyResponse", + "BalanceUpdateResponse" + ], + "isilFile": "GEAPBDGCKMB_NestedType_RoomKeyPurchaseResponseDTO.txt", + "isilType": "GEAPBDGCKMB+RoomKeyPurchaseResponseDTO" + } + } + }, + { + "type": "GEAPBDGCKMB", + "method": "RecRoom.Async.IPromise`1 MMINJDALHNB(System.Int64 OEMDIAHHILF, System.String LGHBPPIOOBM)", + "file": "GEAPBDGCKMB.txt", + "line": 4052, + "contract": { + "kind": "object", + "raw": "OBBBPCBIMME", + "rootType": "OBBBPCBIMME", + "typeDescription": { + "name": "OBBBPCBIMME", + "found": true, + "file": "OBBBPCBIMME.cs", + "declaration": "public class OBBBPCBIMME : IFAIJAGLDFK", + "kind": "class", + "baseType": "IFAIJAGLDFK", + "properties": [ + { + "name": "ACBFDMLHFPB", + "type": "DateTime", + "accessibility": "public", + "kind": "property" + }, + { + "name": "ACHMMBMLGEK", + "type": "int", + "accessibility": "public", + "kind": "property" + }, + { + "name": "AHGCOGFEEEE", + "type": "string", + "accessibility": "public", + "kind": "property" + }, + { + "name": "AJHBGBHGGAL", + "type": "long", + "accessibility": "public", + "kind": "property" + }, + { + "name": "BAELCDIJIPJ", + "type": "HECIICKPCDN", + "accessibility": "public", + "kind": "property" + }, + { + "name": "DKHJPGMEOHF", + "type": "int", + "accessibility": "public", + "kind": "property" + }, + { + "name": "FIKEBGGCDFN", + "type": "string", + "accessibility": "public", + "kind": "property" + }, + { + "name": "GCAABGAEDPP", + "type": "bool", + "accessibility": "public", + "kind": "property" + }, + { + "name": "HKFOKCNGNFA", + "type": "int", + "accessibility": "public", + "kind": "property" + }, + { + "name": "ILKLEFCCJMN", + "type": "bool", + "accessibility": "public", + "kind": "property" + }, + { + "name": "JHJNKJHLJBJ", + "type": "int", + "accessibility": "public", + "kind": "property" + }, + { + "name": "JOHEKKGMAJB", + "type": "int", + "accessibility": "public", + "kind": "property" + }, + { + "name": "KBDMNJGJACC", + "type": "DateTime", + "accessibility": "public", + "kind": "property" + }, + { + "name": "KLLMGMJJICK", + "type": "Nullable", + "accessibility": "public", + "kind": "property" + }, + { + "name": "KODBEJPEFOJ", + "type": "string", + "accessibility": "public", + "kind": "property" + }, + { + "name": "MLDAFKCENMA", + "type": "string", + "accessibility": "public", + "kind": "property" + }, + { + "name": "MLMDFDKLELH", + "type": "HECIICKPCDN", + "accessibility": "public", + "kind": "property" + }, + { + "name": "MMPKOCENIIP", + "type": "bool", + "accessibility": "public", + "kind": "property" + }, + { + "name": "NFNIPMIEMPM", + "type": "bool", + "accessibility": "public", + "kind": "property" + }, + { + "name": "NPDJLOOHMDJ", + "type": "Nullable", + "accessibility": "public", + "kind": "property" + }, + { + "name": "PJNJIKKCBNA", + "type": "Nullable", + "accessibility": "public", + "kind": "property" + } + ], + "fields": [], + "enumValues": [], + "wireKeys": [ + "InventionId", + "ReplicationId", + "CreatorPlayerId", + "Name", + "Description", + "ImageName", + "CurrentVersionNumber", + "IsPublished", + "AllowTrial", + "ModifiedAt", + "CreatedAt", + "NumPlayersHaveUsedInRoom", + "NumDownloads", + "CheerCount", + "CreatorPermission", + "GeneralPermission", + "IsAGInvention", + "Price", + "HideFromPlayer" + ], + "isilFile": "OBBBPCBIMME.txt", + "isilType": "OBBBPCBIMME" + } + } + }, + { + "type": "GEAPBDGCKMB", + "method": "System.Collections.IEnumerator EKLFEDLCNAC(DGOHCPBKOHD NIGCECGDCFD, ACDKILABNNC DLNFAILEHOA, System.Int32 PGGHBNCGLDN, BPHGKAEDBPE+OJDJIJDNFHE> AFLPGGJMPOE)", + "file": "GEAPBDGCKMB.txt", + "line": 3402, + "contract": { + "kind": "callback-or-coroutine", + "raw": "IEnumerator", + "rootType": "callback", + "typeDescription": { + "name": "callback", + "found": false, + "kind": "object", + "wireKeys": [] + } + } + }, + { + "type": "GEAPBDGCKMB", + "method": "System.Collections.IEnumerator EPCDDOGBHMF(DGOHCPBKOHD NIGCECGDCFD, ACDKILABNNC DLNFAILEHOA, System.Int32 PGGHBNCGLDN, System.Nullable`1 POAOENHPNHE, GEAPBDGCKMB+GiftItemDTO HBBAANIPIMP, BPHGKAEDBPE+OJDJIJDNFHE> AFLPGGJMPOE)", + "file": "GEAPBDGCKMB.txt", + "line": 2709, + "contract": { + "kind": "callback-or-coroutine", + "raw": "IEnumerator", + "rootType": "callback", + "typeDescription": { + "name": "callback", + "found": false, + "kind": "object", + "wireKeys": [] + } + } + }, + { + "type": "GEAPBDGCKMB", + "method": "System.Collections.IEnumerator GDOGAIAHEHB(DGOHCPBKOHD NIGCECGDCFD, ACDKILABNNC DLNFAILEHOA, System.Int32 PGGHBNCGLDN, BPHGKAEDBPE+OJDJIJDNFHE> AFLPGGJMPOE)", + "file": "GEAPBDGCKMB.txt", + "line": 3241, + "contract": { + "kind": "callback-or-coroutine", + "raw": "IEnumerator", + "rootType": "callback", + "typeDescription": { + "name": "callback", + "found": false, + "kind": "object", + "wireKeys": [] + } + } + }, + { + "type": "GEAPBDGCKMB", + "method": "System.Collections.IEnumerator IMFAFAKABHC(DGOHCPBKOHD NIGCECGDCFD, BPHGKAEDBPE+OJDJIJDNFHE AFLPGGJMPOE)", + "file": "GEAPBDGCKMB.txt", + "line": 1914, + "contract": { + "kind": "callback-or-coroutine", + "raw": "IEnumerator", + "rootType": "callback", + "typeDescription": { + "name": "callback", + "found": false, + "kind": "object", + "wireKeys": [] + } + } + }, + { + "type": "GEAPBDGCKMB", + "method": "System.Collections.IEnumerator LBEMMLOFBEA(ACDKILABNNC DLNFAILEHOA, BPHGKAEDBPE+OJDJIJDNFHE AFLPGGJMPOE = null)", + "file": "GEAPBDGCKMB.txt", + "line": 5640, + "contract": { + "kind": "callback-or-coroutine", + "raw": "IEnumerator", + "rootType": "callback", + "typeDescription": { + "name": "callback", + "found": false, + "kind": "object", + "wireKeys": [] + } + } + }, + { + "type": "GEAPBDGCKMB", + "method": "System.Nullable`1 KJKDMOHACHJ(ACDKILABNNC DLNFAILEHOA, System.Predicate`1 NLDOHBCJOIO = null)", + "file": "GEAPBDGCKMB.txt", + "line": 4616, + "contract": { + "kind": "unknown", + "raw": "", + "rootType": "unknown" + } + }, + { + "type": "GEAPBDGCKMB", + "method": "System.Void EIFPPIPOIHB(System.Collections.Generic.List`1 DBDFCMJFNEC)", + "file": "GEAPBDGCKMB.txt", + "line": 2257, + "contract": { + "kind": "unknown", + "raw": "", + "rootType": "unknown" + } + }, + { + "type": "GEAPBDGCKMB", + "method": "System.Void IMLMGPJIKCK(ACDKILABNNC DLNFAILEHOA, DKMCNBHMKIK IGNJPMALPIK, BPHGKAEDBPE+OJDJIJDNFHE AFLPGGJMPOE = null)", + "file": "GEAPBDGCKMB.txt", + "line": 5781, + "contract": { + "kind": "unknown", + "raw": "", + "rootType": "unknown" + } + }, + { + "type": "GEAPBDGCKMB+PPPNGFBHEDH", + "method": "RecRoom.Async.IPromise`1 b__0()", + "file": "GEAPBDGCKMB_NestedType_PPPNGFBHEDH.txt", + "line": 236, + "contract": { + "kind": "object", + "raw": "BKFGFHDDNFG", + "rootType": "BKFGFHDDNFG", + "typeDescription": { + "name": "BKFGFHDDNFG", + "found": true, + "file": "BKFGFHDDNFG.cs", + "declaration": "public class BKFGFHDDNFG : DOPJPOEFDCN", + "kind": "class", + "baseType": "DOPJPOEFDCN", + "properties": [], + "fields": [ + { + "name": "JDGEBBMDJBC", + "type": "List", + "kind": "field" + } + ], + "enumValues": [], + "wireKeys": [], + "isilFile": "BKFGFHDDNFG.txt", + "isilType": "BKFGFHDDNFG", + "inherits": "DOPJPOEFDCN", + "inheritedMembers": [ + { + "name": "BFCBMGOPKFH", + "type": "Nullable", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "DOPJPOEFDCN" + }, + { + "name": "GHHJBILFKFB", + "type": "DateTime", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "DOPJPOEFDCN" + }, + { + "name": "GHOKCIMBEMM", + "type": "DGOHCPBKOHD", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "DOPJPOEFDCN" + } + ], + "inheritedWireKeys": [ + "StorefrontType", + "NextUpdate" + ] + } + } + } + ], + "contracts": [ + { + "kind": "success-or-empty", + "raw": "RecRoom.Async.IPromise", + "rootType": "void/success", + "typeDescription": { + "name": "void/success", + "found": false, + "kind": "object", + "wireKeys": [] + } + }, + { + "kind": "object", + "raw": "GEAPBDGCKMB+BalanceUpdateResponseDTO`1", + "rootType": "RewardBalanceModificationDTO>", + "typeDescription": { + "name": "RewardBalanceModificationDTO>", + "found": false, + "kind": "object", + "wireKeys": [] + } + }, + { + "kind": "object", + "raw": "GEAPBDGCKMB+InventionPurchaseResponseDTO", + "rootType": "InventionPurchaseResponseDTO", + "typeDescription": { + "name": "InventionPurchaseResponseDTO", + "found": true, + "file": "GEAPBDGCKMB.cs", + "declaration": "internal class InventionPurchaseResponseDTO : IFAIJAGLDFK", + "kind": "class", + "baseType": "IFAIJAGLDFK", + "properties": [ + { + "name": "BalanceResponseDTO", + "type": "class FHLJACIDOHO :", + "accessibility": "internal", + "kind": "property" + }, + { + "name": "IFAIJAGLDFK", + "type": "class GFAPKCFGKLB :", + "accessibility": "internal", + "kind": "property" + }, + { + "name": "NKPDMHMBNEJ", + "type": "KGAACCELMHP", + "accessibility": "public", + "kind": "property" + }, + { + "name": "PBPPCMNHODC", + "type": "OBBBPCBIMME", + "accessibility": "public", + "kind": "property" + }, + { + "name": "LPDGDNGOLFM", + "type": "List", + "accessibility": "public", + "kind": "property" + }, + { + "name": "BalanceUpdateResponse", + "type": "FHLJACIDOHO", + "accessibility": "public", + "kind": "property" + }, + { + "name": "InventionResponse", + "type": "AHEPPAEOLOD", + "accessibility": "public", + "kind": "property" + } + ], + "fields": [], + "enumValues": [], + "wireKeys": [ + "InventionResponse", + "BalanceUpdateResponse" + ], + "isilFile": "GEAPBDGCKMB_NestedType_InventionPurchaseResponseDTO.txt", + "isilType": "GEAPBDGCKMB+InventionPurchaseResponseDTO" + } + }, + { + "kind": "object", + "raw": "GEAPBDGCKMB+RoomKeyPurchaseResponseDTO", + "rootType": "RoomKeyPurchaseResponseDTO", + "typeDescription": { + "name": "RoomKeyPurchaseResponseDTO", + "found": true, + "file": "GEAPBDGCKMB.cs", + "declaration": "internal class RoomKeyPurchaseResponseDTO : IFAIJAGLDFK", + "kind": "class", + "baseType": "IFAIJAGLDFK", + "properties": [ + { + "name": "BalanceResponseDTO", + "type": "class NAHADLNOINN :", + "accessibility": "internal", + "kind": "property" + }, + { + "name": "IFAIJAGLDFK", + "type": "class ELKBABHKJHC :", + "accessibility": "internal", + "kind": "property" + }, + { + "name": "NKPDMHMBNEJ", + "type": "KGAACCELMHP", + "accessibility": "public", + "kind": "property" + }, + { + "name": "PBPPCMNHODC", + "type": "AMAGKLLBGEC", + "accessibility": "public", + "kind": "property" + }, + { + "name": "LPDGDNGOLFM", + "type": "List", + "accessibility": "public", + "kind": "property" + }, + { + "name": "BalanceUpdateResponse", + "type": "NAHADLNOINN", + "accessibility": "public", + "kind": "property" + }, + { + "name": "RoomKeyResponse", + "type": "BCKIBFNPIPD", + "accessibility": "public", + "kind": "property" + } + ], + "fields": [], + "enumValues": [], + "wireKeys": [ + "RoomKeyResponse", + "BalanceUpdateResponse" + ], + "isilFile": "GEAPBDGCKMB_NestedType_RoomKeyPurchaseResponseDTO.txt", + "isilType": "GEAPBDGCKMB+RoomKeyPurchaseResponseDTO" + } + }, + { + "kind": "object", + "raw": "OBBBPCBIMME", + "rootType": "OBBBPCBIMME", + "typeDescription": { + "name": "OBBBPCBIMME", + "found": true, + "file": "OBBBPCBIMME.cs", + "declaration": "public class OBBBPCBIMME : IFAIJAGLDFK", + "kind": "class", + "baseType": "IFAIJAGLDFK", + "properties": [ + { + "name": "ACBFDMLHFPB", + "type": "DateTime", + "accessibility": "public", + "kind": "property" + }, + { + "name": "ACHMMBMLGEK", + "type": "int", + "accessibility": "public", + "kind": "property" + }, + { + "name": "AHGCOGFEEEE", + "type": "string", + "accessibility": "public", + "kind": "property" + }, + { + "name": "AJHBGBHGGAL", + "type": "long", + "accessibility": "public", + "kind": "property" + }, + { + "name": "BAELCDIJIPJ", + "type": "HECIICKPCDN", + "accessibility": "public", + "kind": "property" + }, + { + "name": "DKHJPGMEOHF", + "type": "int", + "accessibility": "public", + "kind": "property" + }, + { + "name": "FIKEBGGCDFN", + "type": "string", + "accessibility": "public", + "kind": "property" + }, + { + "name": "GCAABGAEDPP", + "type": "bool", + "accessibility": "public", + "kind": "property" + }, + { + "name": "HKFOKCNGNFA", + "type": "int", + "accessibility": "public", + "kind": "property" + }, + { + "name": "ILKLEFCCJMN", + "type": "bool", + "accessibility": "public", + "kind": "property" + }, + { + "name": "JHJNKJHLJBJ", + "type": "int", + "accessibility": "public", + "kind": "property" + }, + { + "name": "JOHEKKGMAJB", + "type": "int", + "accessibility": "public", + "kind": "property" + }, + { + "name": "KBDMNJGJACC", + "type": "DateTime", + "accessibility": "public", + "kind": "property" + }, + { + "name": "KLLMGMJJICK", + "type": "Nullable", + "accessibility": "public", + "kind": "property" + }, + { + "name": "KODBEJPEFOJ", + "type": "string", + "accessibility": "public", + "kind": "property" + }, + { + "name": "MLDAFKCENMA", + "type": "string", + "accessibility": "public", + "kind": "property" + }, + { + "name": "MLMDFDKLELH", + "type": "HECIICKPCDN", + "accessibility": "public", + "kind": "property" + }, + { + "name": "MMPKOCENIIP", + "type": "bool", + "accessibility": "public", + "kind": "property" + }, + { + "name": "NFNIPMIEMPM", + "type": "bool", + "accessibility": "public", + "kind": "property" + }, + { + "name": "NPDJLOOHMDJ", + "type": "Nullable", + "accessibility": "public", + "kind": "property" + }, + { + "name": "PJNJIKKCBNA", + "type": "Nullable", + "accessibility": "public", + "kind": "property" + } + ], + "fields": [], + "enumValues": [], + "wireKeys": [ + "InventionId", + "ReplicationId", + "CreatorPlayerId", + "Name", + "Description", + "ImageName", + "CurrentVersionNumber", + "IsPublished", + "AllowTrial", + "ModifiedAt", + "CreatedAt", + "NumPlayersHaveUsedInRoom", + "NumDownloads", + "CheerCount", + "CreatorPermission", + "GeneralPermission", + "IsAGInvention", + "Price", + "HideFromPlayer" + ], + "isilFile": "OBBBPCBIMME.txt", + "isilType": "OBBBPCBIMME" + } + }, + { + "kind": "callback-or-coroutine", + "raw": "IEnumerator", + "rootType": "callback", + "typeDescription": { + "name": "callback", + "found": false, + "kind": "object", + "wireKeys": [] + } + }, + { + "kind": "unknown", + "raw": "", + "rootType": "unknown", + "typeDescription": { + "name": "unknown", + "found": false, + "kind": "object", + "wireKeys": [] + } + }, + { + "kind": "object", + "raw": "BKFGFHDDNFG", + "rootType": "BKFGFHDDNFG", + "typeDescription": { + "name": "BKFGFHDDNFG", + "found": true, + "file": "BKFGFHDDNFG.cs", + "declaration": "public class BKFGFHDDNFG : DOPJPOEFDCN", + "kind": "class", + "baseType": "DOPJPOEFDCN", + "properties": [], + "fields": [ + { + "name": "JDGEBBMDJBC", + "type": "List", + "kind": "field" + } + ], + "enumValues": [], + "wireKeys": [], + "isilFile": "BKFGFHDDNFG.txt", + "isilType": "BKFGFHDDNFG", + "inherits": "DOPJPOEFDCN", + "inheritedMembers": [ + { + "name": "BFCBMGOPKFH", + "type": "Nullable", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "DOPJPOEFDCN" + }, + { + "name": "GHHJBILFKFB", + "type": "DateTime", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "DOPJPOEFDCN" + }, + { + "name": "GHOKCIMBEMM", + "type": "DGOHCPBKOHD", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "DOPJPOEFDCN" + } + ], + "inheritedWireKeys": [ + "StorefrontType", + "NextUpdate" + ] + } + } + ] + }, + { + "family": "misc", + "route": "api/testcasemanagement/", + "evidence": [ + { + "type": "AICHDPNIEKI", + "method": "RecRoom.Async.IPromise CNGKABDOEKH(System.String EPGHLDONDIP, NIDIHGENDJD AKPHCJFIPBB)", + "file": "AICHDPNIEKI.txt", + "line": 450, + "contract": { + "kind": "success-or-empty", + "raw": "RecRoom.Async.IPromise", + "rootType": "void/success", + "typeDescription": { + "name": "void/success", + "found": false, + "kind": "object", + "wireKeys": [] + } + } + }, + { + "type": "AICHDPNIEKI", + "method": "RecRoom.Async.IPromise DHDOAJAGEJN(System.String EPGHLDONDIP)", + "file": "AICHDPNIEKI.txt", + "line": 360, + "contract": { + "kind": "success-or-empty", + "raw": "RecRoom.Async.IPromise", + "rootType": "void/success", + "typeDescription": { + "name": "void/success", + "found": false, + "kind": "object", + "wireKeys": [] + } + } + }, + { + "type": "AICHDPNIEKI", + "method": "RecRoom.Async.IPromise KNOGAGHKIMC(System.String EPGHLDONDIP)", + "file": "AICHDPNIEKI.txt", + "line": 273, + "contract": { + "kind": "success-or-empty", + "raw": "RecRoom.Async.IPromise", + "rootType": "void/success", + "typeDescription": { + "name": "void/success", + "found": false, + "kind": "object", + "wireKeys": [] + } + } + }, + { + "type": "AICHDPNIEKI", + "method": "RecRoom.Async.IPromise`1 APEBFFOEBFA(System.String ENJEOLBEALP)", + "file": "AICHDPNIEKI.txt", + "line": 193, + "contract": { + "kind": "object", + "raw": "CPFPHLINDHN", + "rootType": "CPFPHLINDHN", + "typeDescription": { + "name": "CPFPHLINDHN", + "found": true, + "file": "CPFPHLINDHN.cs", + "declaration": "public class CPFPHLINDHN : IFAIJAGLDFK", + "kind": "class", + "baseType": "IFAIJAGLDFK", + "properties": [ + { + "name": "CPNPHDAKFCL", + "type": "int", + "accessibility": "public", + "kind": "property" + } + ], + "fields": [ + { + "name": "JPOHGBCEJEJ", + "type": "string", + "kind": "field" + }, + { + "name": "AKFDMGLACLA", + "type": "string", + "kind": "field" + }, + { + "name": "LKIBJOMNFFD", + "type": "string", + "kind": "field" + }, + { + "name": "KODBEJPEFOJ", + "type": "string", + "kind": "field" + }, + { + "name": "HEHAGDOEDHG", + "type": "string", + "kind": "field" + }, + { + "name": "HIMCGOCKLLK", + "type": "NIDIHGENDJD", + "kind": "field" + }, + { + "name": "JOPPLLFIIOM", + "type": "int", + "kind": "field" + }, + { + "name": "JFOIIDOFNCF", + "type": "List", + "kind": "field" + }, + { + "name": "GEPOBHPJHAK", + "type": "List", + "kind": "field" + }, + { + "name": "PKKADKGDHNI", + "type": "List", + "kind": "field" + }, + { + "name": "EGBGAKBPEBD", + "type": "string", + "kind": "field" + }, + { + "name": "IOBANKPIFFG", + "type": "string", + "kind": "field" + } + ], + "enumValues": [], + "wireKeys": [ + "Id", + "Key", + "Title", + "Description", + "RoomName", + "Status", + "MinNumAssignedPlayers", + "AssignedPlayerIds", + "JiraUrl", + "JiraBugUrl" + ], + "isilFile": "CPFPHLINDHN.txt", + "isilType": "CPFPHLINDHN" + } + } + }, + { + "type": "AICHDPNIEKI", + "method": "RecRoom.Async.IPromise`1 BMDDGFAEDLE(System.UInt32 ENJEOLBEALP)", + "file": "AICHDPNIEKI.txt", + "line": 122, + "contract": { + "kind": "object", + "raw": "FDAJOEOJFDN", + "rootType": "FDAJOEOJFDN", + "typeDescription": { + "name": "FDAJOEOJFDN", + "found": true, + "file": "FDAJOEOJFDN.cs", + "declaration": "public class FDAJOEOJFDN : IFAIJAGLDFK", + "kind": "class", + "baseType": "IFAIJAGLDFK", + "properties": [ + { + "name": "PDFFJJFCNPO", + "type": "bool", + "accessibility": "public", + "kind": "property" + }, + { + "name": "PJOBHHCOBLE", + "type": "int", + "accessibility": "public", + "kind": "property" + } + ], + "fields": [ + { + "name": "JPOHGBCEJEJ", + "type": "uint", + "kind": "field" + }, + { + "name": "FIKEBGGCDFN", + "type": "string", + "kind": "field" + }, + { + "name": "KODBEJPEFOJ", + "type": "string", + "kind": "field" + }, + { + "name": "CKDMMCJNCHB", + "type": "DateTime", + "kind": "field" + }, + { + "name": "CKNFHKKHNHP", + "type": "Nullable", + "kind": "field" + }, + { + "name": "JICMOEJKCHG", + "type": "bool", + "kind": "field" + }, + { + "name": "KODOFNJJJCK", + "type": "List", + "kind": "field" + }, + { + "name": "PKKADKGDHNI", + "type": "List", + "kind": "field" + }, + { + "name": "BPPENAKNGNG", + "type": "int", + "kind": "field" + }, + { + "name": "PFIABEONLFI", + "type": "int", + "kind": "field" + }, + { + "name": "CAKKOAKBIIG", + "type": "int", + "kind": "field" + } + ], + "enumValues": [], + "wireKeys": [ + "Id", + "Name", + "Description", + "StartDate", + "WasManuallyClosed", + "NumTestCases", + "NumPassedTestCases", + "NumFailedTestCases" + ], + "isilFile": "FDAJOEOJFDN.txt", + "isilType": "FDAJOEOJFDN" + } + } + }, + { + "type": "AICHDPNIEKI", + "method": "RecRoom.Async.IPromise`1> AKCPPGBNGOO()", + "file": "AICHDPNIEKI.txt", + "line": 44, + "contract": { + "kind": "array", + "raw": "System.Collections.Generic.List`1", + "rootType": "FDAJOEOJFDN", + "item": { + "kind": "object", + "raw": "FDAJOEOJFDN", + "rootType": "FDAJOEOJFDN" + }, + "typeDescription": { + "name": "FDAJOEOJFDN", + "found": true, + "file": "FDAJOEOJFDN.cs", + "declaration": "public class FDAJOEOJFDN : IFAIJAGLDFK", + "kind": "class", + "baseType": "IFAIJAGLDFK", + "properties": [ + { + "name": "PDFFJJFCNPO", + "type": "bool", + "accessibility": "public", + "kind": "property" + }, + { + "name": "PJOBHHCOBLE", + "type": "int", + "accessibility": "public", + "kind": "property" + } + ], + "fields": [ + { + "name": "JPOHGBCEJEJ", + "type": "uint", + "kind": "field" + }, + { + "name": "FIKEBGGCDFN", + "type": "string", + "kind": "field" + }, + { + "name": "KODBEJPEFOJ", + "type": "string", + "kind": "field" + }, + { + "name": "CKDMMCJNCHB", + "type": "DateTime", + "kind": "field" + }, + { + "name": "CKNFHKKHNHP", + "type": "Nullable", + "kind": "field" + }, + { + "name": "JICMOEJKCHG", + "type": "bool", + "kind": "field" + }, + { + "name": "KODOFNJJJCK", + "type": "List", + "kind": "field" + }, + { + "name": "PKKADKGDHNI", + "type": "List", + "kind": "field" + }, + { + "name": "BPPENAKNGNG", + "type": "int", + "kind": "field" + }, + { + "name": "PFIABEONLFI", + "type": "int", + "kind": "field" + }, + { + "name": "CAKKOAKBIIG", + "type": "int", + "kind": "field" + } + ], + "enumValues": [], + "wireKeys": [ + "Id", + "Name", + "Description", + "StartDate", + "WasManuallyClosed", + "NumTestCases", + "NumPassedTestCases", + "NumFailedTestCases" + ], + "isilFile": "FDAJOEOJFDN.txt", + "isilType": "FDAJOEOJFDN" + } + } + } + ], + "contracts": [ + { + "kind": "success-or-empty", + "raw": "RecRoom.Async.IPromise", + "rootType": "void/success", + "typeDescription": { + "name": "void/success", + "found": false, + "kind": "object", + "wireKeys": [] + } + }, + { + "kind": "object", + "raw": "CPFPHLINDHN", + "rootType": "CPFPHLINDHN", + "typeDescription": { + "name": "CPFPHLINDHN", + "found": true, + "file": "CPFPHLINDHN.cs", + "declaration": "public class CPFPHLINDHN : IFAIJAGLDFK", + "kind": "class", + "baseType": "IFAIJAGLDFK", + "properties": [ + { + "name": "CPNPHDAKFCL", + "type": "int", + "accessibility": "public", + "kind": "property" + } + ], + "fields": [ + { + "name": "JPOHGBCEJEJ", + "type": "string", + "kind": "field" + }, + { + "name": "AKFDMGLACLA", + "type": "string", + "kind": "field" + }, + { + "name": "LKIBJOMNFFD", + "type": "string", + "kind": "field" + }, + { + "name": "KODBEJPEFOJ", + "type": "string", + "kind": "field" + }, + { + "name": "HEHAGDOEDHG", + "type": "string", + "kind": "field" + }, + { + "name": "HIMCGOCKLLK", + "type": "NIDIHGENDJD", + "kind": "field" + }, + { + "name": "JOPPLLFIIOM", + "type": "int", + "kind": "field" + }, + { + "name": "JFOIIDOFNCF", + "type": "List", + "kind": "field" + }, + { + "name": "GEPOBHPJHAK", + "type": "List", + "kind": "field" + }, + { + "name": "PKKADKGDHNI", + "type": "List", + "kind": "field" + }, + { + "name": "EGBGAKBPEBD", + "type": "string", + "kind": "field" + }, + { + "name": "IOBANKPIFFG", + "type": "string", + "kind": "field" + } + ], + "enumValues": [], + "wireKeys": [ + "Id", + "Key", + "Title", + "Description", + "RoomName", + "Status", + "MinNumAssignedPlayers", + "AssignedPlayerIds", + "JiraUrl", + "JiraBugUrl" + ], + "isilFile": "CPFPHLINDHN.txt", + "isilType": "CPFPHLINDHN" + } + }, + { + "kind": "object", + "raw": "FDAJOEOJFDN", + "rootType": "FDAJOEOJFDN", + "typeDescription": { + "name": "FDAJOEOJFDN", + "found": true, + "file": "FDAJOEOJFDN.cs", + "declaration": "public class FDAJOEOJFDN : IFAIJAGLDFK", + "kind": "class", + "baseType": "IFAIJAGLDFK", + "properties": [ + { + "name": "PDFFJJFCNPO", + "type": "bool", + "accessibility": "public", + "kind": "property" + }, + { + "name": "PJOBHHCOBLE", + "type": "int", + "accessibility": "public", + "kind": "property" + } + ], + "fields": [ + { + "name": "JPOHGBCEJEJ", + "type": "uint", + "kind": "field" + }, + { + "name": "FIKEBGGCDFN", + "type": "string", + "kind": "field" + }, + { + "name": "KODBEJPEFOJ", + "type": "string", + "kind": "field" + }, + { + "name": "CKDMMCJNCHB", + "type": "DateTime", + "kind": "field" + }, + { + "name": "CKNFHKKHNHP", + "type": "Nullable", + "kind": "field" + }, + { + "name": "JICMOEJKCHG", + "type": "bool", + "kind": "field" + }, + { + "name": "KODOFNJJJCK", + "type": "List", + "kind": "field" + }, + { + "name": "PKKADKGDHNI", + "type": "List", + "kind": "field" + }, + { + "name": "BPPENAKNGNG", + "type": "int", + "kind": "field" + }, + { + "name": "PFIABEONLFI", + "type": "int", + "kind": "field" + }, + { + "name": "CAKKOAKBIIG", + "type": "int", + "kind": "field" + } + ], + "enumValues": [], + "wireKeys": [ + "Id", + "Name", + "Description", + "StartDate", + "WasManuallyClosed", + "NumTestCases", + "NumPassedTestCases", + "NumFailedTestCases" + ], + "isilFile": "FDAJOEOJFDN.txt", + "isilType": "FDAJOEOJFDN" + } + }, + { + "kind": "array", + "raw": "System.Collections.Generic.List`1", + "rootType": "FDAJOEOJFDN", + "item": { + "kind": "object", + "raw": "FDAJOEOJFDN", + "rootType": "FDAJOEOJFDN" + }, + "typeDescription": { + "name": "FDAJOEOJFDN", + "found": true, + "file": "FDAJOEOJFDN.cs", + "declaration": "public class FDAJOEOJFDN : IFAIJAGLDFK", + "kind": "class", + "baseType": "IFAIJAGLDFK", + "properties": [ + { + "name": "PDFFJJFCNPO", + "type": "bool", + "accessibility": "public", + "kind": "property" + }, + { + "name": "PJOBHHCOBLE", + "type": "int", + "accessibility": "public", + "kind": "property" + } + ], + "fields": [ + { + "name": "JPOHGBCEJEJ", + "type": "uint", + "kind": "field" + }, + { + "name": "FIKEBGGCDFN", + "type": "string", + "kind": "field" + }, + { + "name": "KODBEJPEFOJ", + "type": "string", + "kind": "field" + }, + { + "name": "CKDMMCJNCHB", + "type": "DateTime", + "kind": "field" + }, + { + "name": "CKNFHKKHNHP", + "type": "Nullable", + "kind": "field" + }, + { + "name": "JICMOEJKCHG", + "type": "bool", + "kind": "field" + }, + { + "name": "KODOFNJJJCK", + "type": "List", + "kind": "field" + }, + { + "name": "PKKADKGDHNI", + "type": "List", + "kind": "field" + }, + { + "name": "BPPENAKNGNG", + "type": "int", + "kind": "field" + }, + { + "name": "PFIABEONLFI", + "type": "int", + "kind": "field" + }, + { + "name": "CAKKOAKBIIG", + "type": "int", + "kind": "field" + } + ], + "enumValues": [], + "wireKeys": [ + "Id", + "Name", + "Description", + "StartDate", + "WasManuallyClosed", + "NumTestCases", + "NumPassedTestCases", + "NumFailedTestCases" + ], + "isilFile": "FDAJOEOJFDN.txt", + "isilType": "FDAJOEOJFDN" + } + } + ] + }, + { + "family": "misc", + "route": "api/versioncheck/v4?v={0}&p={1}", + "evidence": [ + { + "type": "KMDHPCHFADM", + "method": "RecRoom.Async.IPromise`1 BPAGDCDBHAM()", + "file": "KMDHPCHFADM.txt", + "line": 282, + "contract": { + "kind": "object", + "raw": "PCBCGDHBAEL", + "rootType": "PCBCGDHBAEL", + "typeDescription": { + "name": "PCBCGDHBAEL", + "found": true, + "file": "PCBCGDHBAEL.cs", + "declaration": "public enum PCBCGDHBAEL", + "kind": "enum", + "baseType": null, + "properties": [], + "fields": [], + "enumValues": [ + { + "name": "ValidForPlay", + "value": "0" + }, + { + "name": "ValidForMenu", + "value": "1" + }, + { + "name": "UpdateRequired", + "value": "2" + } + ], + "wireKeys": [] + } + } + } + ], + "contracts": [ + { + "kind": "object", + "raw": "PCBCGDHBAEL", + "rootType": "PCBCGDHBAEL", + "typeDescription": { + "name": "PCBCGDHBAEL", + "found": true, + "file": "PCBCGDHBAEL.cs", + "declaration": "public enum PCBCGDHBAEL", + "kind": "enum", + "baseType": null, + "properties": [], + "fields": [], + "enumValues": [ + { + "name": "ValidForPlay", + "value": "0" + }, + { + "name": "ValidForMenu", + "value": "1" + }, + { + "name": "UpdateRequired", + "value": "2" + } + ], + "wireKeys": [] + } + } + ] + }, + { + "family": "misc", + "route": "https://apps.apple.com/account/subscriptions", + "evidence": [ + { + "type": "IOSPlatformManager", + "method": "RecRoom.Async.IPromise ShowManageSubscriptionPlatformUI()", + "file": "IOSPlatformManager.txt", + "line": 4865, + "contract": { + "kind": "success-or-empty", + "raw": "RecRoom.Async.IPromise", + "rootType": "void/success", + "typeDescription": { + "name": "void/success", + "found": false, + "kind": "object", + "wireKeys": [] + } + } + } + ], + "contracts": [ + { + "kind": "success-or-empty", + "raw": "RecRoom.Async.IPromise", + "rootType": "void/success", + "typeDescription": { + "name": "void/success", + "found": false, + "kind": "object", + "wireKeys": [] + } + } + ] + }, + { + "family": "misc", + "route": "https://www.instagram.com/recroom/", + "evidence": [ + { + "type": "InstagramBulletinBoardUI", + "method": "System.Void Button_RecRoomOnInstagram()", + "file": "InstagramBulletinBoardUI.txt", + "line": 1009, + "contract": { + "kind": "unknown", + "raw": "", + "rootType": "unknown" + } + } + ], + "contracts": [ + { + "kind": "unknown", + "raw": "", + "rootType": "unknown", + "typeDescription": { + "name": "unknown", + "found": false, + "kind": "object", + "wireKeys": [] + } + } + ] + }, + { + "family": "player-events", + "route": "api/playerevents/", + "evidence": [ + { + "type": "GNPDMBPGHBH", + "method": "RecRoom.Async.IPromise`1 BFBMDMLHHCF(CCMBKDINCAH MFJCKOBPMGA, System.Int64 HNHLJONGKHB, System.Nullable`1 FODGKNJIGOP, System.String MMBOKOLAJFH, System.String LJIGOCDPEJF, System.Collections.Generic.List`1 CAFPJPHILMN, System.String HFLPBHHAFIO, System.DateTime JODPOANPJNK, System.DateTime BCANLCHBKJE, CMCAFKLAHCD PONCIIJOHIE, System.Nullable`1 AOMBLGBCENO)", + "file": "GNPDMBPGHBH.txt", + "line": 4280, + "contract": { + "kind": "object", + "raw": "LLNPLLBEJBE", + "rootType": "LLNPLLBEJBE", + "typeDescription": { + "name": "LLNPLLBEJBE", + "found": true, + "file": "LLNPLLBEJBE.cs", + "declaration": "public class LLNPLLBEJBE : IFAIJAGLDFK", + "kind": "class", + "baseType": "IFAIJAGLDFK", + "properties": [ + { + "name": "AANNMHFBONM", + "type": "CCMBKDINCAH", + "accessibility": "public", + "kind": "property" + }, + { + "name": "GMFIDNACDJK", + "type": "DGOPHENCPOC", + "accessibility": "public", + "kind": "property" + }, + { + "name": "POJDHKAOINA", + "type": "NJMAEIPIOAP", + "accessibility": "public", + "kind": "property" + } + ], + "fields": [], + "enumValues": [], + "wireKeys": [ + "PlayerEvent", + "Result", + "TagModifyResult" + ], + "isilFile": "LLNPLLBEJBE.txt", + "isilType": "LLNPLLBEJBE" + } + } + }, + { + "type": "GNPDMBPGHBH", + "method": "RecRoom.Async.IPromise`1 EKEOHKINPDK(CCMBKDINCAH MFJCKOBPMGA)", + "file": "GNPDMBPGHBH.txt", + "line": 6151, + "contract": { + "kind": "object", + "raw": "LLNPLLBEJBE", + "rootType": "LLNPLLBEJBE", + "typeDescription": { + "name": "LLNPLLBEJBE", + "found": true, + "file": "LLNPLLBEJBE.cs", + "declaration": "public class LLNPLLBEJBE : IFAIJAGLDFK", + "kind": "class", + "baseType": "IFAIJAGLDFK", + "properties": [ + { + "name": "AANNMHFBONM", + "type": "CCMBKDINCAH", + "accessibility": "public", + "kind": "property" + }, + { + "name": "GMFIDNACDJK", + "type": "DGOPHENCPOC", + "accessibility": "public", + "kind": "property" + }, + { + "name": "POJDHKAOINA", + "type": "NJMAEIPIOAP", + "accessibility": "public", + "kind": "property" + } + ], + "fields": [], + "enumValues": [], + "wireKeys": [ + "PlayerEvent", + "Result", + "TagModifyResult" + ], + "isilFile": "LLNPLLBEJBE.txt", + "isilType": "LLNPLLBEJBE" + } + } + }, + { + "type": "GNPDMBPGHBH", + "method": "RecRoom.Async.IPromise`1> MBHCOIGLAIF(System.Int64 MOKMJAMNEFP, System.Boolean INGAKMAAHKL = False)", + "file": "GNPDMBPGHBH.txt", + "line": 3736, + "contract": { + "kind": "array", + "raw": "System.Collections.Generic.List`1", + "rootType": "PBIJAPEOEDO", + "item": { + "kind": "object", + "raw": "PBIJAPEOEDO", + "rootType": "PBIJAPEOEDO" + }, + "typeDescription": { + "name": "PBIJAPEOEDO", + "found": true, + "file": "PBIJAPEOEDO.cs", + "declaration": "public class PBIJAPEOEDO : IFAIJAGLDFK", + "kind": "class", + "baseType": "IFAIJAGLDFK", + "properties": [ + { + "name": "ACBFDMLHFPB", + "type": "DateTime", + "accessibility": "public", + "kind": "property" + }, + { + "name": "EEOGKBHOJGL", + "type": "int", + "accessibility": "public", + "kind": "property" + }, + { + "name": "FAEKONHJKNK", + "type": "long", + "accessibility": "public", + "kind": "property" + }, + { + "name": "GBOIPGBGDDG", + "type": "long", + "accessibility": "public", + "kind": "property" + }, + { + "name": "OPLHMKFCNOL", + "type": "FFEICMIIBMC", + "accessibility": "public", + "kind": "property" + } + ], + "fields": [], + "enumValues": [], + "wireKeys": [ + "PlayerEventResponseId", + "PlayerEventId", + "PlayerId", + "CreatedAt", + "Type" + ], + "isilFile": "PBIJAPEOEDO.txt", + "isilType": "PBIJAPEOEDO" + } + } + }, + { + "type": "GNPDMBPGHBH", + "method": "RecRoom.Async.IPromise`1 DAICPGEMGMH(System.Int64 MOKMJAMNEFP, System.Boolean GDPJLIOIIAM = False, System.Nullable`1 AOMBLGBCENO = null)", + "file": "GNPDMBPGHBH.txt", + "line": 6434, + "contract": { + "kind": "object", + "raw": "TPlayerEventDTO", + "rootType": "TPlayerEventDTO", + "typeDescription": { + "name": "TPlayerEventDTO", + "found": false, + "kind": "object", + "wireKeys": [] + } + } + }, + { + "type": "GNPDMBPGHBH", + "method": "System.String HKPFFOHBENO(System.Int64 MEJJDOHGNHG)", + "file": "GNPDMBPGHBH.txt", + "line": 559, + "contract": { + "kind": "unknown", + "raw": "", + "rootType": "unknown" + } + }, + { + "type": "GNPDMBPGHBH", + "method": "System.Void DAOBEMMLHAD(System.Collections.Generic.Dictionary`2 NGPMADFHHKP)", + "file": "GNPDMBPGHBH.txt", + "line": 10757, + "contract": { + "kind": "unknown", + "raw": "", + "rootType": "unknown" + } + }, + { + "type": "GNPDMBPGHBH", + "method": "System.Void DEOJOKOFMLJ(System.Int64 MOKMJAMNEFP)", + "file": "GNPDMBPGHBH.txt", + "line": 11770, + "contract": { + "kind": "unknown", + "raw": "", + "rootType": "unknown" + } + }, + { + "type": "GNPDMBPGHBH", + "method": "System.Void JILNOOIDFKC(HJCAMMLHJAE MOMJFCHIMAM)", + "file": "GNPDMBPGHBH.txt", + "line": 11596, + "contract": { + "kind": "unknown", + "raw": "", + "rootType": "unknown" + } + }, + { + "type": "GNPDMBPGHBH+JNEMPCHKFAM", + "method": "System.Void b__0(PDOBNLOLBAF response)", + "file": "GNPDMBPGHBH_NestedType_JNEMPCHKFAM.txt", + "line": 168, + "contract": { + "kind": "unknown", + "raw": "", + "rootType": "unknown" + } + } + ], + "contracts": [ + { + "kind": "object", + "raw": "LLNPLLBEJBE", + "rootType": "LLNPLLBEJBE", + "typeDescription": { + "name": "LLNPLLBEJBE", + "found": true, + "file": "LLNPLLBEJBE.cs", + "declaration": "public class LLNPLLBEJBE : IFAIJAGLDFK", + "kind": "class", + "baseType": "IFAIJAGLDFK", + "properties": [ + { + "name": "AANNMHFBONM", + "type": "CCMBKDINCAH", + "accessibility": "public", + "kind": "property" + }, + { + "name": "GMFIDNACDJK", + "type": "DGOPHENCPOC", + "accessibility": "public", + "kind": "property" + }, + { + "name": "POJDHKAOINA", + "type": "NJMAEIPIOAP", + "accessibility": "public", + "kind": "property" + } + ], + "fields": [], + "enumValues": [], + "wireKeys": [ + "PlayerEvent", + "Result", + "TagModifyResult" + ], + "isilFile": "LLNPLLBEJBE.txt", + "isilType": "LLNPLLBEJBE" + } + }, + { + "kind": "array", + "raw": "System.Collections.Generic.List`1", + "rootType": "PBIJAPEOEDO", + "item": { + "kind": "object", + "raw": "PBIJAPEOEDO", + "rootType": "PBIJAPEOEDO" + }, + "typeDescription": { + "name": "PBIJAPEOEDO", + "found": true, + "file": "PBIJAPEOEDO.cs", + "declaration": "public class PBIJAPEOEDO : IFAIJAGLDFK", + "kind": "class", + "baseType": "IFAIJAGLDFK", + "properties": [ + { + "name": "ACBFDMLHFPB", + "type": "DateTime", + "accessibility": "public", + "kind": "property" + }, + { + "name": "EEOGKBHOJGL", + "type": "int", + "accessibility": "public", + "kind": "property" + }, + { + "name": "FAEKONHJKNK", + "type": "long", + "accessibility": "public", + "kind": "property" + }, + { + "name": "GBOIPGBGDDG", + "type": "long", + "accessibility": "public", + "kind": "property" + }, + { + "name": "OPLHMKFCNOL", + "type": "FFEICMIIBMC", + "accessibility": "public", + "kind": "property" + } + ], + "fields": [], + "enumValues": [], + "wireKeys": [ + "PlayerEventResponseId", + "PlayerEventId", + "PlayerId", + "CreatedAt", + "Type" + ], + "isilFile": "PBIJAPEOEDO.txt", + "isilType": "PBIJAPEOEDO" + } + }, + { + "kind": "object", + "raw": "TPlayerEventDTO", + "rootType": "TPlayerEventDTO", + "typeDescription": { + "name": "TPlayerEventDTO", + "found": false, + "kind": "object", + "wireKeys": [] + } + }, + { + "kind": "unknown", + "raw": "", + "rootType": "unknown", + "typeDescription": { + "name": "unknown", + "found": false, + "kind": "object", + "wireKeys": [] + } + } + ] + }, + { + "family": "player-events", + "route": "api/playerevents/v1/all", + "evidence": [ + { + "type": "GNPDMBPGHBH", + "method": "RecRoom.Async.IPromise`1 JKFDNKOBOJE()", + "file": "GNPDMBPGHBH.txt", + "line": 5504, + "contract": { + "kind": "object", + "raw": "FNBMIJGOOJM", + "rootType": "FNBMIJGOOJM", + "typeDescription": { + "name": "FNBMIJGOOJM", + "found": true, + "file": "FNBMIJGOOJM.cs", + "declaration": "public class FNBMIJGOOJM : IFAIJAGLDFK", + "kind": "class", + "baseType": "IFAIJAGLDFK", + "properties": [ + { + "name": "APGINOKLOIF", + "type": "List", + "accessibility": "public", + "kind": "property" + }, + { + "name": "NLEGMBPCMIA", + "type": "List", + "accessibility": "public", + "kind": "property" + } + ], + "fields": [ + { + "name": "playerEventId", + "type": "long", + "kind": "field" + } + ], + "enumValues": [], + "wireKeys": [], + "isilFile": "FNBMIJGOOJM.txt", + "isilType": "FNBMIJGOOJM" + } + } + } + ], + "contracts": [ + { + "kind": "object", + "raw": "FNBMIJGOOJM", + "rootType": "FNBMIJGOOJM", + "typeDescription": { + "name": "FNBMIJGOOJM", + "found": true, + "file": "FNBMIJGOOJM.cs", + "declaration": "public class FNBMIJGOOJM : IFAIJAGLDFK", + "kind": "class", + "baseType": "IFAIJAGLDFK", + "properties": [ + { + "name": "APGINOKLOIF", + "type": "List", + "accessibility": "public", + "kind": "property" + }, + { + "name": "NLEGMBPCMIA", + "type": "List", + "accessibility": "public", + "kind": "property" + } + ], + "fields": [ + { + "name": "playerEventId", + "type": "long", + "kind": "field" + } + ], + "enumValues": [], + "wireKeys": [], + "isilFile": "FNBMIJGOOJM.txt", + "isilType": "FNBMIJGOOJM" + } + } + ] + }, + { + "family": "player-events", + "route": "api/playerevents/v1/bulkInvite", + "evidence": [ + { + "type": "GNPDMBPGHBH", + "method": "RecRoom.Async.IPromise`1 MGDLGLLOEMG(System.Int64 MOKMJAMNEFP, System.Collections.Generic.List`1 MHCONOPOOKJ)", + "file": "GNPDMBPGHBH.txt", + "line": 5013, + "contract": { + "kind": "object", + "raw": "PDOBNLOLBAF", + "rootType": "PDOBNLOLBAF", + "typeDescription": { + "name": "PDOBNLOLBAF", + "found": true, + "file": "PDOBNLOLBAF.cs", + "declaration": "public class PDOBNLOLBAF : IFAIJAGLDFK", + "kind": "class", + "baseType": "IFAIJAGLDFK", + "properties": [ + { + "name": "DCNIKAABNGK", + "type": "List", + "accessibility": "public", + "kind": "property" + }, + { + "name": "GMFIDNACDJK", + "type": "DGOPHENCPOC", + "accessibility": "public", + "kind": "property" + } + ], + "fields": [], + "enumValues": [], + "wireKeys": [ + "Result" + ], + "isilFile": "PDOBNLOLBAF.txt", + "isilType": "PDOBNLOLBAF" + } + } + } + ], + "contracts": [ + { + "kind": "object", + "raw": "PDOBNLOLBAF", + "rootType": "PDOBNLOLBAF", + "typeDescription": { + "name": "PDOBNLOLBAF", + "found": true, + "file": "PDOBNLOLBAF.cs", + "declaration": "public class PDOBNLOLBAF : IFAIJAGLDFK", + "kind": "class", + "baseType": "IFAIJAGLDFK", + "properties": [ + { + "name": "DCNIKAABNGK", + "type": "List", + "accessibility": "public", + "kind": "property" + }, + { + "name": "GMFIDNACDJK", + "type": "DGOPHENCPOC", + "accessibility": "public", + "kind": "property" + } + ], + "fields": [], + "enumValues": [], + "wireKeys": [ + "Result" + ], + "isilFile": "PDOBNLOLBAF.txt", + "isilType": "PDOBNLOLBAF" + } + } + ] + }, + { + "family": "player-events", + "route": "api/playerevents/v1/deleteResponse", + "evidence": [ + { + "type": "GNPDMBPGHBH", + "method": "RecRoom.Async.IPromise`1 FGFDJIKJKNN(System.Int64 MOKMJAMNEFP, FFEICMIIBMC DHLDCHCKBPC)", + "file": "GNPDMBPGHBH.txt", + "line": 4769, + "contract": { + "kind": "object", + "raw": "DGOPHENCPOC", + "rootType": "DGOPHENCPOC", + "typeDescription": { + "name": "DGOPHENCPOC", + "found": true, + "file": "DGOPHENCPOC.cs", + "declaration": "public enum DGOPHENCPOC", + "kind": "enum", + "baseType": null, + "properties": [], + "fields": [], + "enumValues": [ + { + "name": "Success", + "value": "0" + }, + { + "name": "HasModeratorClosedEvent", + "value": "1" + }, + { + "name": "DoesNotExist", + "value": "2" + }, + { + "name": "PlayerDoesNotExist", + "value": "3" + }, + { + "name": "RoomDoesNotExist", + "value": "4" + }, + { + "name": "StatusUnchanged", + "value": "5" + }, + { + "name": "PrivateEvent", + "value": "6" + }, + { + "name": "SomethingWentWrong", + "value": "7" + }, + { + "name": "DoesNotOwnRoom", + "value": "8" + }, + { + "name": "ResponseDoesNotExist", + "value": "9" + }, + { + "name": "PlayerAlreadyInvited", + "value": "10" + }, + { + "name": "EventDatesInvalid", + "value": "11" + }, + { + "name": "EventTooLong", + "value": "12" + }, + { + "name": "EventTooShort", + "value": "13" + }, + { + "name": "InappropriateName", + "value": "14" + }, + { + "name": "InappropriateDescription", + "value": "15" + }, + { + "name": "SomeInvitesFailed", + "value": "16" + }, + { + "name": "CannotInviteJunior", + "value": "17" + }, + { + "name": "EventCountLimitReached", + "value": "18" + }, + { + "name": "DoesNotOwnEvent", + "value": "19" + }, + { + "name": "UnregisteredOrJuniorNotAllowed", + "value": "20" + }, + { + "name": "InvalidClubPermissions", + "value": "21" + }, + { + "name": "ImageDoesNotExist", + "value": "22" + }, + { + "name": "SubRoomDoesNotExist", + "value": "23" + }, + { + "name": "DoesNotOwnSubRoom", + "value": "24" + }, + { + "name": "ModifyTagsFailed", + "value": "25" + } + ], + "wireKeys": [] + } + } + } + ], + "contracts": [ + { + "kind": "object", + "raw": "DGOPHENCPOC", + "rootType": "DGOPHENCPOC", + "typeDescription": { + "name": "DGOPHENCPOC", + "found": true, + "file": "DGOPHENCPOC.cs", + "declaration": "public enum DGOPHENCPOC", + "kind": "enum", + "baseType": null, + "properties": [], + "fields": [], + "enumValues": [ + { + "name": "Success", + "value": "0" + }, + { + "name": "HasModeratorClosedEvent", + "value": "1" + }, + { + "name": "DoesNotExist", + "value": "2" + }, + { + "name": "PlayerDoesNotExist", + "value": "3" + }, + { + "name": "RoomDoesNotExist", + "value": "4" + }, + { + "name": "StatusUnchanged", + "value": "5" + }, + { + "name": "PrivateEvent", + "value": "6" + }, + { + "name": "SomethingWentWrong", + "value": "7" + }, + { + "name": "DoesNotOwnRoom", + "value": "8" + }, + { + "name": "ResponseDoesNotExist", + "value": "9" + }, + { + "name": "PlayerAlreadyInvited", + "value": "10" + }, + { + "name": "EventDatesInvalid", + "value": "11" + }, + { + "name": "EventTooLong", + "value": "12" + }, + { + "name": "EventTooShort", + "value": "13" + }, + { + "name": "InappropriateName", + "value": "14" + }, + { + "name": "InappropriateDescription", + "value": "15" + }, + { + "name": "SomeInvitesFailed", + "value": "16" + }, + { + "name": "CannotInviteJunior", + "value": "17" + }, + { + "name": "EventCountLimitReached", + "value": "18" + }, + { + "name": "DoesNotOwnEvent", + "value": "19" + }, + { + "name": "UnregisteredOrJuniorNotAllowed", + "value": "20" + }, + { + "name": "InvalidClubPermissions", + "value": "21" + }, + { + "name": "ImageDoesNotExist", + "value": "22" + }, + { + "name": "SubRoomDoesNotExist", + "value": "23" + }, + { + "name": "DoesNotOwnSubRoom", + "value": "24" + }, + { + "name": "ModifyTagsFailed", + "value": "25" + } + ], + "wireKeys": [] + } + } + ] + }, + { + "family": "player-events", + "route": "api/playerevents/v1/report", + "evidence": [ + { + "type": "GNPDMBPGHBH", + "method": "RecRoom.Async.IPromise`1 GLHAJLLNPBJ(System.Int64 MOKMJAMNEFP, LCCEEFHOBEN+CJFENPHAAHI MEABFEIBEMP, System.String EFDBFLPKHKA)", + "file": "GNPDMBPGHBH.txt", + "line": 5939, + "contract": { + "kind": "object", + "raw": "KLAMKCBENEA", + "rootType": "KLAMKCBENEA", + "typeDescription": { + "name": "KLAMKCBENEA", + "found": true, + "file": "KLAMKCBENEA.cs", + "declaration": "public class KLAMKCBENEA : IFAIJAGLDFK", + "kind": "class", + "baseType": "IFAIJAGLDFK", + "properties": [ + { + "name": "DFPPNHINEFO", + "type": "string", + "accessibility": "public", + "kind": "property" + }, + { + "name": "ONGEANEKLNE", + "type": "bool", + "accessibility": "public", + "kind": "property" + } + ], + "fields": [], + "enumValues": [], + "wireKeys": [ + "Success", + "Message" + ], + "isilFile": "KLAMKCBENEA.txt", + "isilType": "KLAMKCBENEA" + } + } + } + ], + "contracts": [ + { + "kind": "object", + "raw": "KLAMKCBENEA", + "rootType": "KLAMKCBENEA", + "typeDescription": { + "name": "KLAMKCBENEA", + "found": true, + "file": "KLAMKCBENEA.cs", + "declaration": "public class KLAMKCBENEA : IFAIJAGLDFK", + "kind": "class", + "baseType": "IFAIJAGLDFK", + "properties": [ + { + "name": "DFPPNHINEFO", + "type": "string", + "accessibility": "public", + "kind": "property" + }, + { + "name": "ONGEANEKLNE", + "type": "bool", + "accessibility": "public", + "kind": "property" + } + ], + "fields": [], + "enumValues": [], + "wireKeys": [ + "Success", + "Message" + ], + "isilFile": "KLAMKCBENEA.txt", + "isilType": "KLAMKCBENEA" + } + } + ] + }, + { + "family": "player-events", + "route": "api/playerevents/v1/respond", + "evidence": [ + { + "type": "GNPDMBPGHBH", + "method": "RecRoom.Async.IPromise`1 EEJGIOKDBFI(System.Int64 MOKMJAMNEFP, FFEICMIIBMC DHLDCHCKBPC)", + "file": "GNPDMBPGHBH.txt", + "line": 4567, + "contract": { + "kind": "object", + "raw": "DGOPHENCPOC", + "rootType": "DGOPHENCPOC", + "typeDescription": { + "name": "DGOPHENCPOC", + "found": true, + "file": "DGOPHENCPOC.cs", + "declaration": "public enum DGOPHENCPOC", + "kind": "enum", + "baseType": null, + "properties": [], + "fields": [], + "enumValues": [ + { + "name": "Success", + "value": "0" + }, + { + "name": "HasModeratorClosedEvent", + "value": "1" + }, + { + "name": "DoesNotExist", + "value": "2" + }, + { + "name": "PlayerDoesNotExist", + "value": "3" + }, + { + "name": "RoomDoesNotExist", + "value": "4" + }, + { + "name": "StatusUnchanged", + "value": "5" + }, + { + "name": "PrivateEvent", + "value": "6" + }, + { + "name": "SomethingWentWrong", + "value": "7" + }, + { + "name": "DoesNotOwnRoom", + "value": "8" + }, + { + "name": "ResponseDoesNotExist", + "value": "9" + }, + { + "name": "PlayerAlreadyInvited", + "value": "10" + }, + { + "name": "EventDatesInvalid", + "value": "11" + }, + { + "name": "EventTooLong", + "value": "12" + }, + { + "name": "EventTooShort", + "value": "13" + }, + { + "name": "InappropriateName", + "value": "14" + }, + { + "name": "InappropriateDescription", + "value": "15" + }, + { + "name": "SomeInvitesFailed", + "value": "16" + }, + { + "name": "CannotInviteJunior", + "value": "17" + }, + { + "name": "EventCountLimitReached", + "value": "18" + }, + { + "name": "DoesNotOwnEvent", + "value": "19" + }, + { + "name": "UnregisteredOrJuniorNotAllowed", + "value": "20" + }, + { + "name": "InvalidClubPermissions", + "value": "21" + }, + { + "name": "ImageDoesNotExist", + "value": "22" + }, + { + "name": "SubRoomDoesNotExist", + "value": "23" + }, + { + "name": "DoesNotOwnSubRoom", + "value": "24" + }, + { + "name": "ModifyTagsFailed", + "value": "25" + } + ], + "wireKeys": [] + } + } + } + ], + "contracts": [ + { + "kind": "object", + "raw": "DGOPHENCPOC", + "rootType": "DGOPHENCPOC", + "typeDescription": { + "name": "DGOPHENCPOC", + "found": true, + "file": "DGOPHENCPOC.cs", + "declaration": "public enum DGOPHENCPOC", + "kind": "enum", + "baseType": null, + "properties": [], + "fields": [], + "enumValues": [ + { + "name": "Success", + "value": "0" + }, + { + "name": "HasModeratorClosedEvent", + "value": "1" + }, + { + "name": "DoesNotExist", + "value": "2" + }, + { + "name": "PlayerDoesNotExist", + "value": "3" + }, + { + "name": "RoomDoesNotExist", + "value": "4" + }, + { + "name": "StatusUnchanged", + "value": "5" + }, + { + "name": "PrivateEvent", + "value": "6" + }, + { + "name": "SomethingWentWrong", + "value": "7" + }, + { + "name": "DoesNotOwnRoom", + "value": "8" + }, + { + "name": "ResponseDoesNotExist", + "value": "9" + }, + { + "name": "PlayerAlreadyInvited", + "value": "10" + }, + { + "name": "EventDatesInvalid", + "value": "11" + }, + { + "name": "EventTooLong", + "value": "12" + }, + { + "name": "EventTooShort", + "value": "13" + }, + { + "name": "InappropriateName", + "value": "14" + }, + { + "name": "InappropriateDescription", + "value": "15" + }, + { + "name": "SomeInvitesFailed", + "value": "16" + }, + { + "name": "CannotInviteJunior", + "value": "17" + }, + { + "name": "EventCountLimitReached", + "value": "18" + }, + { + "name": "DoesNotOwnEvent", + "value": "19" + }, + { + "name": "UnregisteredOrJuniorNotAllowed", + "value": "20" + }, + { + "name": "InvalidClubPermissions", + "value": "21" + }, + { + "name": "ImageDoesNotExist", + "value": "22" + }, + { + "name": "SubRoomDoesNotExist", + "value": "23" + }, + { + "name": "DoesNotOwnSubRoom", + "value": "24" + }, + { + "name": "ModifyTagsFailed", + "value": "25" + } + ], + "wireKeys": [] + } + } + ] + }, + { + "family": "player-events", + "route": "api/playerevents/v1/search?", + "evidence": [ + { + "type": "GNPDMBPGHBH", + "method": "RecRoom.Async.IPromise`1> ADMFNGBFPIE(System.String CNBKKCJAHPP, GNPDMBPGHBH+EHKGCEONBIB DKLJMLFNEEN = 0, System.Nullable`1 LBFLCMJFDPC = null)", + "file": "GNPDMBPGHBH.txt", + "line": 9248, + "contract": { + "kind": "array", + "raw": "System.Collections.Generic.List`1", + "rootType": "CCMBKDINCAH", + "item": { + "kind": "object", + "raw": "CCMBKDINCAH", + "rootType": "CCMBKDINCAH" + }, + "typeDescription": { + "name": "CCMBKDINCAH", + "found": true, + "file": "CCMBKDINCAH.cs", + "declaration": "public class CCMBKDINCAH : IFAIJAGLDFK", + "kind": "class", + "baseType": "IFAIJAGLDFK", + "properties": [ + { + "name": "ACHMMBMLGEK", + "type": "int", + "accessibility": "public", + "kind": "property" + }, + { + "name": "AHGCOGFEEEE", + "type": "string", + "accessibility": "public", + "kind": "property" + }, + { + "name": "CCGOEDABKNN", + "type": "Nullable", + "accessibility": "public", + "kind": "property" + }, + { + "name": "DADOKMAOFJL", + "type": "long", + "accessibility": "public", + "kind": "property" + }, + { + "name": "FIKEBGGCDFN", + "type": "string", + "accessibility": "public", + "kind": "property" + }, + { + "name": "GBOIPGBGDDG", + "type": "long", + "accessibility": "public", + "kind": "property" + }, + { + "name": "GIHDFJNGFHH", + "type": "int", + "accessibility": "public", + "kind": "property" + }, + { + "name": "GJPHHEHBCIJ", + "type": "Nullable", + "accessibility": "public", + "kind": "property" + }, + { + "name": "JFEAPMIPNEP", + "type": "CMCAFKLAHCD", + "accessibility": "public", + "kind": "property" + }, + { + "name": "KJDKJFLBFOL", + "type": "bool", + "accessibility": "public", + "kind": "property" + }, + { + "name": "KMOAHFBMONE", + "type": "bool", + "accessibility": "public", + "kind": "property" + }, + { + "name": "KODBEJPEFOJ", + "type": "string", + "accessibility": "public", + "kind": "property" + }, + { + "name": "LCEFHPHIHBP", + "type": "bool", + "accessibility": "public", + "kind": "property" + }, + { + "name": "LIPFBIGFEBG", + "type": "DateTime", + "accessibility": "public", + "kind": "property" + }, + { + "name": "MAFEFHEPIKI", + "type": "DateTime", + "accessibility": "public", + "kind": "property" + } + ], + "fields": [], + "enumValues": [], + "wireKeys": [ + "PlayerEventId", + "Name", + "Description", + "ImageName", + "StartTime", + "EndTime", + "CreatorPlayerId", + "AttendeeCount", + "RoomId", + "Accessibility" + ], + "isilFile": "CCMBKDINCAH.txt", + "isilType": "CCMBKDINCAH" + } + } + } + ], + "contracts": [ + { + "kind": "array", + "raw": "System.Collections.Generic.List`1", + "rootType": "CCMBKDINCAH", + "item": { + "kind": "object", + "raw": "CCMBKDINCAH", + "rootType": "CCMBKDINCAH" + }, + "typeDescription": { + "name": "CCMBKDINCAH", + "found": true, + "file": "CCMBKDINCAH.cs", + "declaration": "public class CCMBKDINCAH : IFAIJAGLDFK", + "kind": "class", + "baseType": "IFAIJAGLDFK", + "properties": [ + { + "name": "ACHMMBMLGEK", + "type": "int", + "accessibility": "public", + "kind": "property" + }, + { + "name": "AHGCOGFEEEE", + "type": "string", + "accessibility": "public", + "kind": "property" + }, + { + "name": "CCGOEDABKNN", + "type": "Nullable", + "accessibility": "public", + "kind": "property" + }, + { + "name": "DADOKMAOFJL", + "type": "long", + "accessibility": "public", + "kind": "property" + }, + { + "name": "FIKEBGGCDFN", + "type": "string", + "accessibility": "public", + "kind": "property" + }, + { + "name": "GBOIPGBGDDG", + "type": "long", + "accessibility": "public", + "kind": "property" + }, + { + "name": "GIHDFJNGFHH", + "type": "int", + "accessibility": "public", + "kind": "property" + }, + { + "name": "GJPHHEHBCIJ", + "type": "Nullable", + "accessibility": "public", + "kind": "property" + }, + { + "name": "JFEAPMIPNEP", + "type": "CMCAFKLAHCD", + "accessibility": "public", + "kind": "property" + }, + { + "name": "KJDKJFLBFOL", + "type": "bool", + "accessibility": "public", + "kind": "property" + }, + { + "name": "KMOAHFBMONE", + "type": "bool", + "accessibility": "public", + "kind": "property" + }, + { + "name": "KODBEJPEFOJ", + "type": "string", + "accessibility": "public", + "kind": "property" + }, + { + "name": "LCEFHPHIHBP", + "type": "bool", + "accessibility": "public", + "kind": "property" + }, + { + "name": "LIPFBIGFEBG", + "type": "DateTime", + "accessibility": "public", + "kind": "property" + }, + { + "name": "MAFEFHEPIKI", + "type": "DateTime", + "accessibility": "public", + "kind": "property" + } + ], + "fields": [], + "enumValues": [], + "wireKeys": [ + "PlayerEventId", + "Name", + "Description", + "ImageName", + "StartTime", + "EndTime", + "CreatorPlayerId", + "AttendeeCount", + "RoomId", + "Accessibility" + ], + "isilFile": "CCMBKDINCAH.txt", + "isilType": "CCMBKDINCAH" + } + } + ] + }, + { + "family": "player-events", + "route": "api/playerevents/v1/searchlive?", + "evidence": [ + { + "type": "GNPDMBPGHBH", + "method": "RecRoom.Async.IPromise`1> JBOMFDKLBOI(System.String CNBKKCJAHPP)", + "file": "GNPDMBPGHBH.txt", + "line": 8935, + "contract": { + "kind": "array", + "raw": "System.Collections.Generic.List`1", + "rootType": "NMKMJKIHDNE", + "item": { + "kind": "object", + "raw": "NMKMJKIHDNE", + "rootType": "NMKMJKIHDNE" + }, + "typeDescription": { + "name": "NMKMJKIHDNE", + "found": true, + "file": "NMKMJKIHDNE.cs", + "declaration": "public class NMKMJKIHDNE : CCMBKDINCAH", + "kind": "class", + "baseType": "CCMBKDINCAH", + "properties": [ + { + "name": "FDMFNIJKHKC", + "type": "bool", + "accessibility": "public", + "kind": "property" + }, + { + "name": "HLFNLKMCNAC", + "type": "int", + "accessibility": "public", + "kind": "property" + } + ], + "fields": [], + "enumValues": [], + "wireKeys": [ + "PlayerCount", + "IsFull" + ], + "isilFile": "NMKMJKIHDNE.txt", + "isilType": "NMKMJKIHDNE", + "inherits": "CCMBKDINCAH", + "inheritedMembers": [ + { + "name": "ACHMMBMLGEK", + "type": "int", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "CCMBKDINCAH" + }, + { + "name": "AHGCOGFEEEE", + "type": "string", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "CCMBKDINCAH" + }, + { + "name": "CCGOEDABKNN", + "type": "Nullable", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "CCMBKDINCAH" + }, + { + "name": "DADOKMAOFJL", + "type": "long", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "CCMBKDINCAH" + }, + { + "name": "FIKEBGGCDFN", + "type": "string", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "CCMBKDINCAH" + }, + { + "name": "GBOIPGBGDDG", + "type": "long", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "CCMBKDINCAH" + }, + { + "name": "GIHDFJNGFHH", + "type": "int", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "CCMBKDINCAH" + }, + { + "name": "GJPHHEHBCIJ", + "type": "Nullable", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "CCMBKDINCAH" + }, + { + "name": "JFEAPMIPNEP", + "type": "CMCAFKLAHCD", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "CCMBKDINCAH" + }, + { + "name": "KJDKJFLBFOL", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "CCMBKDINCAH" + }, + { + "name": "KMOAHFBMONE", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "CCMBKDINCAH" + }, + { + "name": "KODBEJPEFOJ", + "type": "string", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "CCMBKDINCAH" + }, + { + "name": "LCEFHPHIHBP", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "CCMBKDINCAH" + }, + { + "name": "LIPFBIGFEBG", + "type": "DateTime", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "CCMBKDINCAH" + }, + { + "name": "MAFEFHEPIKI", + "type": "DateTime", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "CCMBKDINCAH" + } + ], + "inheritedWireKeys": [ + "PlayerEventId", + "Name", + "Description", + "ImageName", + "StartTime", + "EndTime", + "CreatorPlayerId", + "AttendeeCount", + "RoomId", + "Accessibility" + ] + } + } + } + ], + "contracts": [ + { + "kind": "array", + "raw": "System.Collections.Generic.List`1", + "rootType": "NMKMJKIHDNE", + "item": { + "kind": "object", + "raw": "NMKMJKIHDNE", + "rootType": "NMKMJKIHDNE" + }, + "typeDescription": { + "name": "NMKMJKIHDNE", + "found": true, + "file": "NMKMJKIHDNE.cs", + "declaration": "public class NMKMJKIHDNE : CCMBKDINCAH", + "kind": "class", + "baseType": "CCMBKDINCAH", + "properties": [ + { + "name": "FDMFNIJKHKC", + "type": "bool", + "accessibility": "public", + "kind": "property" + }, + { + "name": "HLFNLKMCNAC", + "type": "int", + "accessibility": "public", + "kind": "property" + } + ], + "fields": [], + "enumValues": [], + "wireKeys": [ + "PlayerCount", + "IsFull" + ], + "isilFile": "NMKMJKIHDNE.txt", + "isilType": "NMKMJKIHDNE", + "inherits": "CCMBKDINCAH", + "inheritedMembers": [ + { + "name": "ACHMMBMLGEK", + "type": "int", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "CCMBKDINCAH" + }, + { + "name": "AHGCOGFEEEE", + "type": "string", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "CCMBKDINCAH" + }, + { + "name": "CCGOEDABKNN", + "type": "Nullable", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "CCMBKDINCAH" + }, + { + "name": "DADOKMAOFJL", + "type": "long", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "CCMBKDINCAH" + }, + { + "name": "FIKEBGGCDFN", + "type": "string", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "CCMBKDINCAH" + }, + { + "name": "GBOIPGBGDDG", + "type": "long", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "CCMBKDINCAH" + }, + { + "name": "GIHDFJNGFHH", + "type": "int", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "CCMBKDINCAH" + }, + { + "name": "GJPHHEHBCIJ", + "type": "Nullable", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "CCMBKDINCAH" + }, + { + "name": "JFEAPMIPNEP", + "type": "CMCAFKLAHCD", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "CCMBKDINCAH" + }, + { + "name": "KJDKJFLBFOL", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "CCMBKDINCAH" + }, + { + "name": "KMOAHFBMONE", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "CCMBKDINCAH" + }, + { + "name": "KODBEJPEFOJ", + "type": "string", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "CCMBKDINCAH" + }, + { + "name": "LCEFHPHIHBP", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "CCMBKDINCAH" + }, + { + "name": "LIPFBIGFEBG", + "type": "DateTime", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "CCMBKDINCAH" + }, + { + "name": "MAFEFHEPIKI", + "type": "DateTime", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "CCMBKDINCAH" + } + ], + "inheritedWireKeys": [ + "PlayerEventId", + "Name", + "Description", + "ImageName", + "StartTime", + "EndTime", + "CreatorPlayerId", + "AttendeeCount", + "RoomId", + "Accessibility" + ] + } + } + ] + }, + { + "family": "player-events", + "route": "api/playerevents/v1/tagfilters", + "evidence": [ + { + "type": "GNPDMBPGHBH", + "method": "RecRoom.Async.IPromise`1> LIPBAJOAIJC(GNPDMBPGHBH+FFCCJHEPNDK KINAEJLCDEG)", + "file": "GNPDMBPGHBH.txt", + "line": 9697, + "contract": { + "kind": "array", + "raw": "System.Collections.Generic.List`1", + "rootType": "String", + "item": { + "kind": "primitive", + "raw": "System.String", + "rootType": "string" + }, + "typeDescription": { + "name": "String", + "found": false, + "kind": "primitive", + "wireKeys": [] + } + } + } + ], + "contracts": [ + { + "kind": "array", + "raw": "System.Collections.Generic.List`1", + "rootType": "String", + "item": { + "kind": "primitive", + "raw": "System.String", + "rootType": "string" + }, + "typeDescription": { + "name": "String", + "found": false, + "kind": "primitive", + "wireKeys": [] + } + } + ] + }, + { + "family": "player-events", + "route": "api/playerevents/v2", + "evidence": [ + { + "type": "GNPDMBPGHBH", + "method": "RecRoom.Async.IPromise`1 BBPEHLACMLH(System.Int64 HNHLJONGKHB, System.Nullable`1 FODGKNJIGOP, System.Nullable`1 AOMBLGBCENO, System.String MMBOKOLAJFH, System.String LJIGOCDPEJF, System.Collections.Generic.List`1 CAFPJPHILMN, System.String HFLPBHHAFIO, System.DateTime JODPOANPJNK, System.DateTime BCANLCHBKJE, CMCAFKLAHCD PONCIIJOHIE)", + "file": "GNPDMBPGHBH.txt", + "line": 4067, + "contract": { + "kind": "object", + "raw": "LLNPLLBEJBE", + "rootType": "LLNPLLBEJBE", + "typeDescription": { + "name": "LLNPLLBEJBE", + "found": true, + "file": "LLNPLLBEJBE.cs", + "declaration": "public class LLNPLLBEJBE : IFAIJAGLDFK", + "kind": "class", + "baseType": "IFAIJAGLDFK", + "properties": [ + { + "name": "AANNMHFBONM", + "type": "CCMBKDINCAH", + "accessibility": "public", + "kind": "property" + }, + { + "name": "GMFIDNACDJK", + "type": "DGOPHENCPOC", + "accessibility": "public", + "kind": "property" + }, + { + "name": "POJDHKAOINA", + "type": "NJMAEIPIOAP", + "accessibility": "public", + "kind": "property" + } + ], + "fields": [], + "enumValues": [], + "wireKeys": [ + "PlayerEvent", + "Result", + "TagModifyResult" + ], + "isilFile": "LLNPLLBEJBE.txt", + "isilType": "LLNPLLBEJBE" + } + } + } + ], + "contracts": [ + { + "kind": "object", + "raw": "LLNPLLBEJBE", + "rootType": "LLNPLLBEJBE", + "typeDescription": { + "name": "LLNPLLBEJBE", + "found": true, + "file": "LLNPLLBEJBE.cs", + "declaration": "public class LLNPLLBEJBE : IFAIJAGLDFK", + "kind": "class", + "baseType": "IFAIJAGLDFK", + "properties": [ + { + "name": "AANNMHFBONM", + "type": "CCMBKDINCAH", + "accessibility": "public", + "kind": "property" + }, + { + "name": "GMFIDNACDJK", + "type": "DGOPHENCPOC", + "accessibility": "public", + "kind": "property" + }, + { + "name": "POJDHKAOINA", + "type": "NJMAEIPIOAP", + "accessibility": "public", + "kind": "property" + } + ], + "fields": [], + "enumValues": [], + "wireKeys": [ + "PlayerEvent", + "Result", + "TagModifyResult" + ], + "isilFile": "LLNPLLBEJBE.txt", + "isilType": "LLNPLLBEJBE" + } + } + ] + }, + { + "family": "players", + "route": "/api/playerReputation/v1/{0}", + "evidence": [ + { + "type": "EBAIPNMBKLK", + "method": "RecRoom.Async.IPromise`1 JNLDMDCGHHJ(System.Int32 GKLPIFBPGOD)", + "file": "EBAIPNMBKLK.txt", + "line": 1231, + "contract": { + "kind": "object", + "raw": "JGEBJEJLNBK", + "rootType": "JGEBJEJLNBK", + "typeDescription": { + "name": "JGEBJEJLNBK", + "found": true, + "file": "JGEBJEJLNBK.cs", + "declaration": "public class JGEBJEJLNBK : IFAIJAGLDFK", + "kind": "class", + "baseType": "IFAIJAGLDFK", + "properties": [ + { + "name": "AONADBLHNEE", + "type": "int", + "accessibility": "public", + "kind": "property" + }, + { + "name": "APDEKEIBJPJ", + "type": "float", + "accessibility": "public", + "kind": "property" + }, + { + "name": "GAINIOENNCG", + "type": "int", + "accessibility": "public", + "kind": "property" + }, + { + "name": "GKBDOIPKEID", + "type": "Nullable", + "accessibility": "public", + "kind": "property" + }, + { + "name": "JABKMGPLDCO", + "type": "Nullable", + "accessibility": "public", + "kind": "property" + }, + { + "name": "JKMJNPJFGNC", + "type": "int", + "accessibility": "public", + "kind": "property" + }, + { + "name": "LCHBNILBADA", + "type": "bool", + "accessibility": "public", + "kind": "property" + }, + { + "name": "MOGEADHIOBD", + "type": "int", + "accessibility": "public", + "kind": "property" + }, + { + "name": "NGPDMPELINB", + "type": "int", + "accessibility": "public", + "kind": "property" + }, + { + "name": "OCJDOOKPBAE", + "type": "int", + "accessibility": "public", + "kind": "property" + }, + { + "name": "PEDJLAMDNNA", + "type": "int", + "accessibility": "public", + "kind": "property" + } + ], + "fields": [ + { + "name": "cheer", + "type": "PIDFCOGLFHN", + "kind": "field" + } + ], + "enumValues": [], + "wireKeys": [ + "AccountId", + "Noteriety", + "CheerGeneral", + "CheerHelpful", + "CheerGreatHost", + "CheerSportsman", + "CheerCreative", + "CheerCredit", + "SelectedCheer" + ], + "isilFile": "JGEBJEJLNBK.txt", + "isilType": "JGEBJEJLNBK" + } + } + } + ], + "contracts": [ + { + "kind": "object", + "raw": "JGEBJEJLNBK", + "rootType": "JGEBJEJLNBK", + "typeDescription": { + "name": "JGEBJEJLNBK", + "found": true, + "file": "JGEBJEJLNBK.cs", + "declaration": "public class JGEBJEJLNBK : IFAIJAGLDFK", + "kind": "class", + "baseType": "IFAIJAGLDFK", + "properties": [ + { + "name": "AONADBLHNEE", + "type": "int", + "accessibility": "public", + "kind": "property" + }, + { + "name": "APDEKEIBJPJ", + "type": "float", + "accessibility": "public", + "kind": "property" + }, + { + "name": "GAINIOENNCG", + "type": "int", + "accessibility": "public", + "kind": "property" + }, + { + "name": "GKBDOIPKEID", + "type": "Nullable", + "accessibility": "public", + "kind": "property" + }, + { + "name": "JABKMGPLDCO", + "type": "Nullable", + "accessibility": "public", + "kind": "property" + }, + { + "name": "JKMJNPJFGNC", + "type": "int", + "accessibility": "public", + "kind": "property" + }, + { + "name": "LCHBNILBADA", + "type": "bool", + "accessibility": "public", + "kind": "property" + }, + { + "name": "MOGEADHIOBD", + "type": "int", + "accessibility": "public", + "kind": "property" + }, + { + "name": "NGPDMPELINB", + "type": "int", + "accessibility": "public", + "kind": "property" + }, + { + "name": "OCJDOOKPBAE", + "type": "int", + "accessibility": "public", + "kind": "property" + }, + { + "name": "PEDJLAMDNNA", + "type": "int", + "accessibility": "public", + "kind": "property" + } + ], + "fields": [ + { + "name": "cheer", + "type": "PIDFCOGLFHN", + "kind": "field" + } + ], + "enumValues": [], + "wireKeys": [ + "AccountId", + "Noteriety", + "CheerGeneral", + "CheerHelpful", + "CheerGreatHost", + "CheerSportsman", + "CheerCreative", + "CheerCredit", + "SelectedCheer" + ], + "isilFile": "JGEBJEJLNBK.txt", + "isilType": "JGEBJEJLNBK" + } + } + ] + }, + { + "family": "players", + "route": "/api/playerReputation/v1/bulk", + "evidence": [ + { + "type": "EBAIPNMBKLK", + "method": "RecRoom.Async.IPromise`1> KGPDFJMKJKL(System.Collections.Generic.List`1 ILNGMAANNDG)", + "file": "EBAIPNMBKLK.txt", + "line": 1631, + "contract": { + "kind": "array", + "raw": "System.Collections.Generic.List`1", + "rootType": "JGEBJEJLNBK", + "item": { + "kind": "object", + "raw": "JGEBJEJLNBK", + "rootType": "JGEBJEJLNBK" + }, + "typeDescription": { + "name": "JGEBJEJLNBK", + "found": true, + "file": "JGEBJEJLNBK.cs", + "declaration": "public class JGEBJEJLNBK : IFAIJAGLDFK", + "kind": "class", + "baseType": "IFAIJAGLDFK", + "properties": [ + { + "name": "AONADBLHNEE", + "type": "int", + "accessibility": "public", + "kind": "property" + }, + { + "name": "APDEKEIBJPJ", + "type": "float", + "accessibility": "public", + "kind": "property" + }, + { + "name": "GAINIOENNCG", + "type": "int", + "accessibility": "public", + "kind": "property" + }, + { + "name": "GKBDOIPKEID", + "type": "Nullable", + "accessibility": "public", + "kind": "property" + }, + { + "name": "JABKMGPLDCO", + "type": "Nullable", + "accessibility": "public", + "kind": "property" + }, + { + "name": "JKMJNPJFGNC", + "type": "int", + "accessibility": "public", + "kind": "property" + }, + { + "name": "LCHBNILBADA", + "type": "bool", + "accessibility": "public", + "kind": "property" + }, + { + "name": "MOGEADHIOBD", + "type": "int", + "accessibility": "public", + "kind": "property" + }, + { + "name": "NGPDMPELINB", + "type": "int", + "accessibility": "public", + "kind": "property" + }, + { + "name": "OCJDOOKPBAE", + "type": "int", + "accessibility": "public", + "kind": "property" + }, + { + "name": "PEDJLAMDNNA", + "type": "int", + "accessibility": "public", + "kind": "property" + } + ], + "fields": [ + { + "name": "cheer", + "type": "PIDFCOGLFHN", + "kind": "field" + } + ], + "enumValues": [], + "wireKeys": [ + "AccountId", + "Noteriety", + "CheerGeneral", + "CheerHelpful", + "CheerGreatHost", + "CheerSportsman", + "CheerCreative", + "CheerCredit", + "SelectedCheer" + ], + "isilFile": "JGEBJEJLNBK.txt", + "isilType": "JGEBJEJLNBK" + } + } + } + ], + "contracts": [ + { + "kind": "array", + "raw": "System.Collections.Generic.List`1", + "rootType": "JGEBJEJLNBK", + "item": { + "kind": "object", + "raw": "JGEBJEJLNBK", + "rootType": "JGEBJEJLNBK" + }, + "typeDescription": { + "name": "JGEBJEJLNBK", + "found": true, + "file": "JGEBJEJLNBK.cs", + "declaration": "public class JGEBJEJLNBK : IFAIJAGLDFK", + "kind": "class", + "baseType": "IFAIJAGLDFK", + "properties": [ + { + "name": "AONADBLHNEE", + "type": "int", + "accessibility": "public", + "kind": "property" + }, + { + "name": "APDEKEIBJPJ", + "type": "float", + "accessibility": "public", + "kind": "property" + }, + { + "name": "GAINIOENNCG", + "type": "int", + "accessibility": "public", + "kind": "property" + }, + { + "name": "GKBDOIPKEID", + "type": "Nullable", + "accessibility": "public", + "kind": "property" + }, + { + "name": "JABKMGPLDCO", + "type": "Nullable", + "accessibility": "public", + "kind": "property" + }, + { + "name": "JKMJNPJFGNC", + "type": "int", + "accessibility": "public", + "kind": "property" + }, + { + "name": "LCHBNILBADA", + "type": "bool", + "accessibility": "public", + "kind": "property" + }, + { + "name": "MOGEADHIOBD", + "type": "int", + "accessibility": "public", + "kind": "property" + }, + { + "name": "NGPDMPELINB", + "type": "int", + "accessibility": "public", + "kind": "property" + }, + { + "name": "OCJDOOKPBAE", + "type": "int", + "accessibility": "public", + "kind": "property" + }, + { + "name": "PEDJLAMDNNA", + "type": "int", + "accessibility": "public", + "kind": "property" + } + ], + "fields": [ + { + "name": "cheer", + "type": "PIDFCOGLFHN", + "kind": "field" + } + ], + "enumValues": [], + "wireKeys": [ + "AccountId", + "Noteriety", + "CheerGeneral", + "CheerHelpful", + "CheerGreatHost", + "CheerSportsman", + "CheerCreative", + "CheerCredit", + "SelectedCheer" + ], + "isilFile": "JGEBJEJLNBK.txt", + "isilType": "JGEBJEJLNBK" + } + } + ] + }, + { + "family": "players", + "route": "/api/players/v1/progression/{0}", + "evidence": [ + { + "type": "ACJLCBNBJDK", + "method": "RecRoom.Async.IPromise`1 AAGPDEDFCNI(System.Int32 GKLPIFBPGOD)", + "file": "ACJLCBNBJDK.txt", + "line": 1673, + "contract": { + "kind": "object", + "raw": "NFMBDLEJEDD", + "rootType": "NFMBDLEJEDD", + "typeDescription": { + "name": "NFMBDLEJEDD", + "found": true, + "file": "NFMBDLEJEDD.cs", + "declaration": "public class NFMBDLEJEDD : IFAIJAGLDFK", + "kind": "class", + "baseType": "IFAIJAGLDFK", + "properties": [ + { + "name": "ANKCGDNCABN", + "type": "float", + "accessibility": "public", + "kind": "property" + }, + { + "name": "FHPGELHJCFG", + "type": "int", + "accessibility": "public", + "kind": "property" + }, + { + "name": "GAINIOENNCG", + "type": "int", + "accessibility": "public", + "kind": "property" + }, + { + "name": "JKJIMIDBCIH", + "type": "int", + "accessibility": "public", + "kind": "property" + }, + { + "name": "LINLLHBFIKF", + "type": "int", + "accessibility": "public", + "kind": "property" + } + ], + "fields": [], + "enumValues": [], + "wireKeys": [ + "PlayerId", + "Level", + "XP" + ], + "isilFile": "NFMBDLEJEDD.txt", + "isilType": "NFMBDLEJEDD" + } + } + } + ], + "contracts": [ + { + "kind": "object", + "raw": "NFMBDLEJEDD", + "rootType": "NFMBDLEJEDD", + "typeDescription": { + "name": "NFMBDLEJEDD", + "found": true, + "file": "NFMBDLEJEDD.cs", + "declaration": "public class NFMBDLEJEDD : IFAIJAGLDFK", + "kind": "class", + "baseType": "IFAIJAGLDFK", + "properties": [ + { + "name": "ANKCGDNCABN", + "type": "float", + "accessibility": "public", + "kind": "property" + }, + { + "name": "FHPGELHJCFG", + "type": "int", + "accessibility": "public", + "kind": "property" + }, + { + "name": "GAINIOENNCG", + "type": "int", + "accessibility": "public", + "kind": "property" + }, + { + "name": "JKJIMIDBCIH", + "type": "int", + "accessibility": "public", + "kind": "property" + }, + { + "name": "LINLLHBFIKF", + "type": "int", + "accessibility": "public", + "kind": "property" + } + ], + "fields": [], + "enumValues": [], + "wireKeys": [ + "PlayerId", + "Level", + "XP" + ], + "isilFile": "NFMBDLEJEDD.txt", + "isilType": "NFMBDLEJEDD" + } + } + ] + }, + { + "family": "players", + "route": "/api/players/v1/progression/bulk", + "evidence": [ + { + "type": "ACJLCBNBJDK", + "method": "RecRoom.Async.IPromise`1> MCFMHADBDBI(System.Collections.Generic.List`1 ILNGMAANNDG)", + "file": "ACJLCBNBJDK.txt", + "line": 2051, + "contract": { + "kind": "array", + "raw": "System.Collections.Generic.List`1", + "rootType": "NFMBDLEJEDD", + "item": { + "kind": "object", + "raw": "NFMBDLEJEDD", + "rootType": "NFMBDLEJEDD" + }, + "typeDescription": { + "name": "NFMBDLEJEDD", + "found": true, + "file": "NFMBDLEJEDD.cs", + "declaration": "public class NFMBDLEJEDD : IFAIJAGLDFK", + "kind": "class", + "baseType": "IFAIJAGLDFK", + "properties": [ + { + "name": "ANKCGDNCABN", + "type": "float", + "accessibility": "public", + "kind": "property" + }, + { + "name": "FHPGELHJCFG", + "type": "int", + "accessibility": "public", + "kind": "property" + }, + { + "name": "GAINIOENNCG", + "type": "int", + "accessibility": "public", + "kind": "property" + }, + { + "name": "JKJIMIDBCIH", + "type": "int", + "accessibility": "public", + "kind": "property" + }, + { + "name": "LINLLHBFIKF", + "type": "int", + "accessibility": "public", + "kind": "property" + } + ], + "fields": [], + "enumValues": [], + "wireKeys": [ + "PlayerId", + "Level", + "XP" + ], + "isilFile": "NFMBDLEJEDD.txt", + "isilType": "NFMBDLEJEDD" + } + } + } + ], + "contracts": [ + { + "kind": "array", + "raw": "System.Collections.Generic.List`1", + "rootType": "NFMBDLEJEDD", + "item": { + "kind": "object", + "raw": "NFMBDLEJEDD", + "rootType": "NFMBDLEJEDD" + }, + "typeDescription": { + "name": "NFMBDLEJEDD", + "found": true, + "file": "NFMBDLEJEDD.cs", + "declaration": "public class NFMBDLEJEDD : IFAIJAGLDFK", + "kind": "class", + "baseType": "IFAIJAGLDFK", + "properties": [ + { + "name": "ANKCGDNCABN", + "type": "float", + "accessibility": "public", + "kind": "property" + }, + { + "name": "FHPGELHJCFG", + "type": "int", + "accessibility": "public", + "kind": "property" + }, + { + "name": "GAINIOENNCG", + "type": "int", + "accessibility": "public", + "kind": "property" + }, + { + "name": "JKJIMIDBCIH", + "type": "int", + "accessibility": "public", + "kind": "property" + }, + { + "name": "LINLLHBFIKF", + "type": "int", + "accessibility": "public", + "kind": "property" + } + ], + "fields": [], + "enumValues": [], + "wireKeys": [ + "PlayerId", + "Level", + "XP" + ], + "isilFile": "NFMBDLEJEDD.txt", + "isilType": "NFMBDLEJEDD" + } + } + ] + }, + { + "family": "players", + "route": "api/players/v2/objectives", + "evidence": [ + { + "type": "ACJLCBNBJDK", + "method": "System.Void EIFPPIPOIHB(System.Collections.Generic.List`1 DBDFCMJFNEC)", + "file": "ACJLCBNBJDK.txt", + "line": 816, + "contract": { + "kind": "unknown", + "raw": "", + "rootType": "unknown" + } + } + ], + "contracts": [ + { + "kind": "unknown", + "raw": "", + "rootType": "unknown", + "typeDescription": { + "name": "unknown", + "found": false, + "kind": "object", + "wireKeys": [] + } + } + ] + }, + { + "family": "playlists", + "route": "featuredrooms/current", + "evidence": [ + { + "type": "EJDCNGBEICB", + "method": "RecRoom.Async.IPromise`1 CFKDADKHAGB()", + "file": "EJDCNGBEICB.txt", + "line": 2687, + "contract": { + "kind": "object", + "raw": "NMPFCIJPODA", + "rootType": "NMPFCIJPODA", + "typeDescription": { + "name": "NMPFCIJPODA", + "found": true, + "file": "NMPFCIJPODA.cs", + "declaration": "public class NMPFCIJPODA : IFAIJAGLDFK", + "kind": "class", + "baseType": "IFAIJAGLDFK", + "properties": [ + { + "name": "EHDPEFMCIIC", + "type": "long", + "accessibility": "public", + "kind": "property" + }, + { + "name": "FIKEBGGCDFN", + "type": "string", + "accessibility": "public", + "kind": "property" + }, + { + "name": "HEFJFKFNNCJ", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + } + ], + "fields": [], + "enumValues": [], + "wireKeys": [ + "FeaturedRoomGroupId", + "Name" + ], + "isilFile": "NMPFCIJPODA.txt", + "isilType": "NMPFCIJPODA" + } + } + } + ], + "contracts": [ + { + "kind": "object", + "raw": "NMPFCIJPODA", + "rootType": "NMPFCIJPODA", + "typeDescription": { + "name": "NMPFCIJPODA", + "found": true, + "file": "NMPFCIJPODA.cs", + "declaration": "public class NMPFCIJPODA : IFAIJAGLDFK", + "kind": "class", + "baseType": "IFAIJAGLDFK", + "properties": [ + { + "name": "EHDPEFMCIIC", + "type": "long", + "accessibility": "public", + "kind": "property" + }, + { + "name": "FIKEBGGCDFN", + "type": "string", + "accessibility": "public", + "kind": "property" + }, + { + "name": "HEFJFKFNNCJ", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + } + ], + "fields": [], + "enumValues": [], + "wireKeys": [ + "FeaturedRoomGroupId", + "Name" + ], + "isilFile": "NMPFCIJPODA.txt", + "isilType": "NMPFCIJPODA" + } + } + ] + }, + { + "family": "playlists", + "route": "playlists/{0}", + "evidence": [ + { + "type": "EJDCNGBEICB", + "method": "RecRoom.Async.IPromise OFACCDEDIHE(System.Int64 DDMPFMPILCE)", + "file": "EJDCNGBEICB.txt", + "line": 6619, + "contract": { + "kind": "success-or-empty", + "raw": "RecRoom.Async.IPromise", + "rootType": "void/success", + "typeDescription": { + "name": "void/success", + "found": false, + "kind": "object", + "wireKeys": [] + } + } + }, + { + "type": "EJDCNGBEICB", + "method": "RecRoom.Async.IPromise`1 POOBMHAAMLJ(System.Int64 DDMPFMPILCE)", + "file": "EJDCNGBEICB.txt", + "line": 2192, + "contract": { + "kind": "object", + "raw": "BMFAGMFKODA", + "rootType": "BMFAGMFKODA", + "typeDescription": { + "name": "BMFAGMFKODA", + "found": true, + "file": "BMFAGMFKODA.cs", + "declaration": "public class BMFAGMFKODA : KMKPEOGJDFK", + "kind": "class", + "baseType": "KMKPEOGJDFK", + "properties": [ + { + "name": "BIODFICLKJH", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "DFKGKGIMEMP", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "FEBCCOFBKKJ", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "HEFJFKFNNCJ", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "IFMMBHKGHMB", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "KFFBPMHGFKB", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "PKKADKGDHNI", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + } + ], + "fields": [ + { + "name": "tag", + "type": "string", + "kind": "field" + } + ], + "enumValues": [], + "wireKeys": [], + "isilFile": "BMFAGMFKODA.txt", + "isilType": "BMFAGMFKODA", + "inherits": "KMKPEOGJDFK", + "inheritedMembers": [ + { + "name": "BBGEDJMGFFO", + "type": "long", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "KMKPEOGJDFK" + }, + { + "name": "ACBFDMLHFPB", + "type": "DateTime", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "AHGCOGFEEEE", + "type": "string", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "AKFBNELAMNA", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "BADIGBCKECA", + "type": "int", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "BNBLOBAEDEE", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "CDINMMPNAID", + "type": "NMJEKMMBDDE", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "CDNFGMHLDMJ", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "FIKEBGGCDFN", + "type": "string", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "GIBHIMGJNNO", + "type": "GPDIAKNEBKH", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "HPLBOMGACED", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "IGOPGMHHLKI", + "type": "string", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "JFEAPMIPNEP", + "type": "DPLPMKMFMPB", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "KHIJAFCHLIA", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "KLNJBBPNMBJ", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "KODBEJPEFOJ", + "type": "string", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "LPJLEMJFBPE", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "MGBDHBHCDMH", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "MIKDLDEALPN", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "OFONEIOEIED", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "OILEJFNPDDB", + "type": "HJPGEGENLPH", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "PEEFHKMOMKK", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + } + ], + "inheritedWireKeys": [ + "PlaylistId" + ] + } + } + }, + { + "type": "EJDCNGBEICB", + "method": "RecRoom.Async.IPromise`1 PDLNHHAPPIP(System.Int64 DDMPFMPILCE)", + "file": "EJDCNGBEICB.txt", + "line": 1659, + "contract": { + "kind": "object", + "raw": "KMKPEOGJDFK", + "rootType": "KMKPEOGJDFK", + "typeDescription": { + "name": "KMKPEOGJDFK", + "found": true, + "file": "KMKPEOGJDFK.cs", + "declaration": "public class KMKPEOGJDFK : MKAMHOIHOJK", + "kind": "class", + "baseType": "MKAMHOIHOJK", + "properties": [ + { + "name": "BBGEDJMGFFO", + "type": "long", + "accessibility": "public", + "kind": "property" + } + ], + "fields": [], + "enumValues": [], + "wireKeys": [ + "PlaylistId" + ], + "isilFile": "KMKPEOGJDFK.txt", + "isilType": "KMKPEOGJDFK", + "inherits": "MKAMHOIHOJK", + "inheritedMembers": [ + { + "name": "ACBFDMLHFPB", + "type": "DateTime", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "AHGCOGFEEEE", + "type": "string", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "AKFBNELAMNA", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "BADIGBCKECA", + "type": "int", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "BNBLOBAEDEE", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "CDINMMPNAID", + "type": "NMJEKMMBDDE", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "CDNFGMHLDMJ", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "FIKEBGGCDFN", + "type": "string", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "GIBHIMGJNNO", + "type": "GPDIAKNEBKH", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "HPLBOMGACED", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "IGOPGMHHLKI", + "type": "string", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "JFEAPMIPNEP", + "type": "DPLPMKMFMPB", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "KHIJAFCHLIA", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "KLNJBBPNMBJ", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "KODBEJPEFOJ", + "type": "string", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "LPJLEMJFBPE", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "MGBDHBHCDMH", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "MIKDLDEALPN", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "OFONEIOEIED", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "OILEJFNPDDB", + "type": "HJPGEGENLPH", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "PEEFHKMOMKK", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + } + ], + "inheritedWireKeys": [ + "Name", + "Description", + "ImageName", + "WarningMask", + "CustomWarning", + "CreatorAccountId", + "State", + "Accessibility", + "SupportsLevelVoting", + "IsRRO", + "SupportsScreens", + "SupportsWalkVR", + "SupportsTeleportVR", + "SupportsVRLow", + "SupportsQuest2", + "SupportsMobile", + "SupportsJuniors", + "CreatedAt", + "Stats" + ] + } + } + } + ], + "contracts": [ + { + "kind": "success-or-empty", + "raw": "RecRoom.Async.IPromise", + "rootType": "void/success", + "typeDescription": { + "name": "void/success", + "found": false, + "kind": "object", + "wireKeys": [] + } + }, + { + "kind": "object", + "raw": "BMFAGMFKODA", + "rootType": "BMFAGMFKODA", + "typeDescription": { + "name": "BMFAGMFKODA", + "found": true, + "file": "BMFAGMFKODA.cs", + "declaration": "public class BMFAGMFKODA : KMKPEOGJDFK", + "kind": "class", + "baseType": "KMKPEOGJDFK", + "properties": [ + { + "name": "BIODFICLKJH", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "DFKGKGIMEMP", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "FEBCCOFBKKJ", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "HEFJFKFNNCJ", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "IFMMBHKGHMB", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "KFFBPMHGFKB", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "PKKADKGDHNI", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + } + ], + "fields": [ + { + "name": "tag", + "type": "string", + "kind": "field" + } + ], + "enumValues": [], + "wireKeys": [], + "isilFile": "BMFAGMFKODA.txt", + "isilType": "BMFAGMFKODA", + "inherits": "KMKPEOGJDFK", + "inheritedMembers": [ + { + "name": "BBGEDJMGFFO", + "type": "long", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "KMKPEOGJDFK" + }, + { + "name": "ACBFDMLHFPB", + "type": "DateTime", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "AHGCOGFEEEE", + "type": "string", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "AKFBNELAMNA", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "BADIGBCKECA", + "type": "int", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "BNBLOBAEDEE", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "CDINMMPNAID", + "type": "NMJEKMMBDDE", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "CDNFGMHLDMJ", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "FIKEBGGCDFN", + "type": "string", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "GIBHIMGJNNO", + "type": "GPDIAKNEBKH", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "HPLBOMGACED", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "IGOPGMHHLKI", + "type": "string", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "JFEAPMIPNEP", + "type": "DPLPMKMFMPB", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "KHIJAFCHLIA", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "KLNJBBPNMBJ", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "KODBEJPEFOJ", + "type": "string", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "LPJLEMJFBPE", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "MGBDHBHCDMH", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "MIKDLDEALPN", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "OFONEIOEIED", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "OILEJFNPDDB", + "type": "HJPGEGENLPH", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "PEEFHKMOMKK", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + } + ], + "inheritedWireKeys": [ + "PlaylistId" + ] + } + }, + { + "kind": "object", + "raw": "KMKPEOGJDFK", + "rootType": "KMKPEOGJDFK", + "typeDescription": { + "name": "KMKPEOGJDFK", + "found": true, + "file": "KMKPEOGJDFK.cs", + "declaration": "public class KMKPEOGJDFK : MKAMHOIHOJK", + "kind": "class", + "baseType": "MKAMHOIHOJK", + "properties": [ + { + "name": "BBGEDJMGFFO", + "type": "long", + "accessibility": "public", + "kind": "property" + } + ], + "fields": [], + "enumValues": [], + "wireKeys": [ + "PlaylistId" + ], + "isilFile": "KMKPEOGJDFK.txt", + "isilType": "KMKPEOGJDFK", + "inherits": "MKAMHOIHOJK", + "inheritedMembers": [ + { + "name": "ACBFDMLHFPB", + "type": "DateTime", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "AHGCOGFEEEE", + "type": "string", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "AKFBNELAMNA", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "BADIGBCKECA", + "type": "int", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "BNBLOBAEDEE", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "CDINMMPNAID", + "type": "NMJEKMMBDDE", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "CDNFGMHLDMJ", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "FIKEBGGCDFN", + "type": "string", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "GIBHIMGJNNO", + "type": "GPDIAKNEBKH", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "HPLBOMGACED", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "IGOPGMHHLKI", + "type": "string", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "JFEAPMIPNEP", + "type": "DPLPMKMFMPB", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "KHIJAFCHLIA", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "KLNJBBPNMBJ", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "KODBEJPEFOJ", + "type": "string", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "LPJLEMJFBPE", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "MGBDHBHCDMH", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "MIKDLDEALPN", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "OFONEIOEIED", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "OILEJFNPDDB", + "type": "HJPGEGENLPH", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "PEEFHKMOMKK", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + } + ], + "inheritedWireKeys": [ + "Name", + "Description", + "ImageName", + "WarningMask", + "CustomWarning", + "CreatorAccountId", + "State", + "Accessibility", + "SupportsLevelVoting", + "IsRRO", + "SupportsScreens", + "SupportsWalkVR", + "SupportsTeleportVR", + "SupportsVRLow", + "SupportsQuest2", + "SupportsMobile", + "SupportsJuniors", + "CreatedAt", + "Stats" + ] + } + } + ] + }, + { + "family": "playlists", + "route": "playlists/{0}/accessibility", + "evidence": [ + { + "type": "EJDCNGBEICB", + "method": "RecRoom.Async.IPromise`1 KLHJFKPFPIC(System.Int64 DDMPFMPILCE, DPLPMKMFMPB PONCIIJOHIE)", + "file": "EJDCNGBEICB.txt", + "line": 7323, + "contract": { + "kind": "object", + "raw": "BMFAGMFKODA", + "rootType": "BMFAGMFKODA", + "typeDescription": { + "name": "BMFAGMFKODA", + "found": true, + "file": "BMFAGMFKODA.cs", + "declaration": "public class BMFAGMFKODA : KMKPEOGJDFK", + "kind": "class", + "baseType": "KMKPEOGJDFK", + "properties": [ + { + "name": "BIODFICLKJH", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "DFKGKGIMEMP", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "FEBCCOFBKKJ", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "HEFJFKFNNCJ", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "IFMMBHKGHMB", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "KFFBPMHGFKB", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "PKKADKGDHNI", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + } + ], + "fields": [ + { + "name": "tag", + "type": "string", + "kind": "field" + } + ], + "enumValues": [], + "wireKeys": [], + "isilFile": "BMFAGMFKODA.txt", + "isilType": "BMFAGMFKODA", + "inherits": "KMKPEOGJDFK", + "inheritedMembers": [ + { + "name": "BBGEDJMGFFO", + "type": "long", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "KMKPEOGJDFK" + }, + { + "name": "ACBFDMLHFPB", + "type": "DateTime", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "AHGCOGFEEEE", + "type": "string", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "AKFBNELAMNA", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "BADIGBCKECA", + "type": "int", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "BNBLOBAEDEE", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "CDINMMPNAID", + "type": "NMJEKMMBDDE", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "CDNFGMHLDMJ", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "FIKEBGGCDFN", + "type": "string", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "GIBHIMGJNNO", + "type": "GPDIAKNEBKH", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "HPLBOMGACED", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "IGOPGMHHLKI", + "type": "string", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "JFEAPMIPNEP", + "type": "DPLPMKMFMPB", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "KHIJAFCHLIA", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "KLNJBBPNMBJ", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "KODBEJPEFOJ", + "type": "string", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "LPJLEMJFBPE", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "MGBDHBHCDMH", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "MIKDLDEALPN", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "OFONEIOEIED", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "OILEJFNPDDB", + "type": "HJPGEGENLPH", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "PEEFHKMOMKK", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + } + ], + "inheritedWireKeys": [ + "PlaylistId" + ] + } + } + } + ], + "contracts": [ + { + "kind": "object", + "raw": "BMFAGMFKODA", + "rootType": "BMFAGMFKODA", + "typeDescription": { + "name": "BMFAGMFKODA", + "found": true, + "file": "BMFAGMFKODA.cs", + "declaration": "public class BMFAGMFKODA : KMKPEOGJDFK", + "kind": "class", + "baseType": "KMKPEOGJDFK", + "properties": [ + { + "name": "BIODFICLKJH", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "DFKGKGIMEMP", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "FEBCCOFBKKJ", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "HEFJFKFNNCJ", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "IFMMBHKGHMB", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "KFFBPMHGFKB", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "PKKADKGDHNI", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + } + ], + "fields": [ + { + "name": "tag", + "type": "string", + "kind": "field" + } + ], + "enumValues": [], + "wireKeys": [], + "isilFile": "BMFAGMFKODA.txt", + "isilType": "BMFAGMFKODA", + "inherits": "KMKPEOGJDFK", + "inheritedMembers": [ + { + "name": "BBGEDJMGFFO", + "type": "long", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "KMKPEOGJDFK" + }, + { + "name": "ACBFDMLHFPB", + "type": "DateTime", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "AHGCOGFEEEE", + "type": "string", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "AKFBNELAMNA", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "BADIGBCKECA", + "type": "int", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "BNBLOBAEDEE", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "CDINMMPNAID", + "type": "NMJEKMMBDDE", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "CDNFGMHLDMJ", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "FIKEBGGCDFN", + "type": "string", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "GIBHIMGJNNO", + "type": "GPDIAKNEBKH", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "HPLBOMGACED", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "IGOPGMHHLKI", + "type": "string", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "JFEAPMIPNEP", + "type": "DPLPMKMFMPB", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "KHIJAFCHLIA", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "KLNJBBPNMBJ", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "KODBEJPEFOJ", + "type": "string", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "LPJLEMJFBPE", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "MGBDHBHCDMH", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "MIKDLDEALPN", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "OFONEIOEIED", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "OILEJFNPDDB", + "type": "HJPGEGENLPH", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "PEEFHKMOMKK", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + } + ], + "inheritedWireKeys": [ + "PlaylistId" + ] + } + } + ] + }, + { + "family": "playlists", + "route": "playlists/{0}/description", + "evidence": [ + { + "type": "EJDCNGBEICB", + "method": "RecRoom.Async.IPromise`1 EPJMNNPLNDM(System.Int64 DDMPFMPILCE, System.String LJIGOCDPEJF)", + "file": "EJDCNGBEICB.txt", + "line": 6839, + "contract": { + "kind": "object", + "raw": "BMFAGMFKODA", + "rootType": "BMFAGMFKODA", + "typeDescription": { + "name": "BMFAGMFKODA", + "found": true, + "file": "BMFAGMFKODA.cs", + "declaration": "public class BMFAGMFKODA : KMKPEOGJDFK", + "kind": "class", + "baseType": "KMKPEOGJDFK", + "properties": [ + { + "name": "BIODFICLKJH", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "DFKGKGIMEMP", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "FEBCCOFBKKJ", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "HEFJFKFNNCJ", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "IFMMBHKGHMB", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "KFFBPMHGFKB", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "PKKADKGDHNI", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + } + ], + "fields": [ + { + "name": "tag", + "type": "string", + "kind": "field" + } + ], + "enumValues": [], + "wireKeys": [], + "isilFile": "BMFAGMFKODA.txt", + "isilType": "BMFAGMFKODA", + "inherits": "KMKPEOGJDFK", + "inheritedMembers": [ + { + "name": "BBGEDJMGFFO", + "type": "long", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "KMKPEOGJDFK" + }, + { + "name": "ACBFDMLHFPB", + "type": "DateTime", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "AHGCOGFEEEE", + "type": "string", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "AKFBNELAMNA", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "BADIGBCKECA", + "type": "int", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "BNBLOBAEDEE", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "CDINMMPNAID", + "type": "NMJEKMMBDDE", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "CDNFGMHLDMJ", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "FIKEBGGCDFN", + "type": "string", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "GIBHIMGJNNO", + "type": "GPDIAKNEBKH", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "HPLBOMGACED", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "IGOPGMHHLKI", + "type": "string", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "JFEAPMIPNEP", + "type": "DPLPMKMFMPB", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "KHIJAFCHLIA", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "KLNJBBPNMBJ", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "KODBEJPEFOJ", + "type": "string", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "LPJLEMJFBPE", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "MGBDHBHCDMH", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "MIKDLDEALPN", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "OFONEIOEIED", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "OILEJFNPDDB", + "type": "HJPGEGENLPH", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "PEEFHKMOMKK", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + } + ], + "inheritedWireKeys": [ + "PlaylistId" + ] + } + } + } + ], + "contracts": [ + { + "kind": "object", + "raw": "BMFAGMFKODA", + "rootType": "BMFAGMFKODA", + "typeDescription": { + "name": "BMFAGMFKODA", + "found": true, + "file": "BMFAGMFKODA.cs", + "declaration": "public class BMFAGMFKODA : KMKPEOGJDFK", + "kind": "class", + "baseType": "KMKPEOGJDFK", + "properties": [ + { + "name": "BIODFICLKJH", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "DFKGKGIMEMP", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "FEBCCOFBKKJ", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "HEFJFKFNNCJ", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "IFMMBHKGHMB", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "KFFBPMHGFKB", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "PKKADKGDHNI", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + } + ], + "fields": [ + { + "name": "tag", + "type": "string", + "kind": "field" + } + ], + "enumValues": [], + "wireKeys": [], + "isilFile": "BMFAGMFKODA.txt", + "isilType": "BMFAGMFKODA", + "inherits": "KMKPEOGJDFK", + "inheritedMembers": [ + { + "name": "BBGEDJMGFFO", + "type": "long", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "KMKPEOGJDFK" + }, + { + "name": "ACBFDMLHFPB", + "type": "DateTime", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "AHGCOGFEEEE", + "type": "string", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "AKFBNELAMNA", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "BADIGBCKECA", + "type": "int", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "BNBLOBAEDEE", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "CDINMMPNAID", + "type": "NMJEKMMBDDE", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "CDNFGMHLDMJ", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "FIKEBGGCDFN", + "type": "string", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "GIBHIMGJNNO", + "type": "GPDIAKNEBKH", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "HPLBOMGACED", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "IGOPGMHHLKI", + "type": "string", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "JFEAPMIPNEP", + "type": "DPLPMKMFMPB", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "KHIJAFCHLIA", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "KLNJBBPNMBJ", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "KODBEJPEFOJ", + "type": "string", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "LPJLEMJFBPE", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "MGBDHBHCDMH", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "MIKDLDEALPN", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "OFONEIOEIED", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "OILEJFNPDDB", + "type": "HJPGEGENLPH", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "PEEFHKMOMKK", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + } + ], + "inheritedWireKeys": [ + "PlaylistId" + ] + } + } + ] + }, + { + "family": "playlists", + "route": "playlists/{0}/image", + "evidence": [ + { + "type": "EJDCNGBEICB", + "method": "RecRoom.Async.IPromise`1 CKMILKDKDCN(System.Int64 DDMPFMPILCE, System.String HFLPBHHAFIO)", + "file": "EJDCNGBEICB.txt", + "line": 6955, + "contract": { + "kind": "object", + "raw": "BMFAGMFKODA", + "rootType": "BMFAGMFKODA", + "typeDescription": { + "name": "BMFAGMFKODA", + "found": true, + "file": "BMFAGMFKODA.cs", + "declaration": "public class BMFAGMFKODA : KMKPEOGJDFK", + "kind": "class", + "baseType": "KMKPEOGJDFK", + "properties": [ + { + "name": "BIODFICLKJH", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "DFKGKGIMEMP", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "FEBCCOFBKKJ", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "HEFJFKFNNCJ", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "IFMMBHKGHMB", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "KFFBPMHGFKB", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "PKKADKGDHNI", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + } + ], + "fields": [ + { + "name": "tag", + "type": "string", + "kind": "field" + } + ], + "enumValues": [], + "wireKeys": [], + "isilFile": "BMFAGMFKODA.txt", + "isilType": "BMFAGMFKODA", + "inherits": "KMKPEOGJDFK", + "inheritedMembers": [ + { + "name": "BBGEDJMGFFO", + "type": "long", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "KMKPEOGJDFK" + }, + { + "name": "ACBFDMLHFPB", + "type": "DateTime", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "AHGCOGFEEEE", + "type": "string", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "AKFBNELAMNA", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "BADIGBCKECA", + "type": "int", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "BNBLOBAEDEE", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "CDINMMPNAID", + "type": "NMJEKMMBDDE", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "CDNFGMHLDMJ", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "FIKEBGGCDFN", + "type": "string", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "GIBHIMGJNNO", + "type": "GPDIAKNEBKH", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "HPLBOMGACED", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "IGOPGMHHLKI", + "type": "string", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "JFEAPMIPNEP", + "type": "DPLPMKMFMPB", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "KHIJAFCHLIA", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "KLNJBBPNMBJ", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "KODBEJPEFOJ", + "type": "string", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "LPJLEMJFBPE", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "MGBDHBHCDMH", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "MIKDLDEALPN", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "OFONEIOEIED", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "OILEJFNPDDB", + "type": "HJPGEGENLPH", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "PEEFHKMOMKK", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + } + ], + "inheritedWireKeys": [ + "PlaylistId" + ] + } + } + } + ], + "contracts": [ + { + "kind": "object", + "raw": "BMFAGMFKODA", + "rootType": "BMFAGMFKODA", + "typeDescription": { + "name": "BMFAGMFKODA", + "found": true, + "file": "BMFAGMFKODA.cs", + "declaration": "public class BMFAGMFKODA : KMKPEOGJDFK", + "kind": "class", + "baseType": "KMKPEOGJDFK", + "properties": [ + { + "name": "BIODFICLKJH", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "DFKGKGIMEMP", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "FEBCCOFBKKJ", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "HEFJFKFNNCJ", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "IFMMBHKGHMB", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "KFFBPMHGFKB", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "PKKADKGDHNI", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + } + ], + "fields": [ + { + "name": "tag", + "type": "string", + "kind": "field" + } + ], + "enumValues": [], + "wireKeys": [], + "isilFile": "BMFAGMFKODA.txt", + "isilType": "BMFAGMFKODA", + "inherits": "KMKPEOGJDFK", + "inheritedMembers": [ + { + "name": "BBGEDJMGFFO", + "type": "long", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "KMKPEOGJDFK" + }, + { + "name": "ACBFDMLHFPB", + "type": "DateTime", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "AHGCOGFEEEE", + "type": "string", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "AKFBNELAMNA", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "BADIGBCKECA", + "type": "int", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "BNBLOBAEDEE", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "CDINMMPNAID", + "type": "NMJEKMMBDDE", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "CDNFGMHLDMJ", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "FIKEBGGCDFN", + "type": "string", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "GIBHIMGJNNO", + "type": "GPDIAKNEBKH", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "HPLBOMGACED", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "IGOPGMHHLKI", + "type": "string", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "JFEAPMIPNEP", + "type": "DPLPMKMFMPB", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "KHIJAFCHLIA", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "KLNJBBPNMBJ", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "KODBEJPEFOJ", + "type": "string", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "LPJLEMJFBPE", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "MGBDHBHCDMH", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "MIKDLDEALPN", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "OFONEIOEIED", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "OILEJFNPDDB", + "type": "HJPGEGENLPH", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "PEEFHKMOMKK", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + } + ], + "inheritedWireKeys": [ + "PlaylistId" + ] + } + } + ] + }, + { + "family": "playlists", + "route": "playlists/{0}/interactionby/me", + "evidence": [ + { + "type": "EJDCNGBEICB", + "method": "RecRoom.Async.IPromise`1 AINDJCIMJOB(System.Int64 DDMPFMPILCE)", + "file": "EJDCNGBEICB.txt", + "line": 8600, + "contract": { + "kind": "object", + "raw": "CJODCLDGFCF", + "rootType": "CJODCLDGFCF", + "typeDescription": { + "name": "CJODCLDGFCF", + "found": true, + "file": "CJODCLDGFCF.cs", + "declaration": "public class CJODCLDGFCF : IFAIJAGLDFK", + "kind": "class", + "baseType": "IFAIJAGLDFK", + "properties": [ + { + "name": "EFCGNDGOFNK", + "type": "Nullable", + "accessibility": "public", + "kind": "property" + }, + { + "name": "EMBNPPMOJFJ", + "type": "bool", + "accessibility": "public", + "kind": "property" + }, + { + "name": "HHFLKFOEHNL", + "type": "bool", + "accessibility": "public", + "kind": "property" + } + ], + "fields": [], + "enumValues": [], + "wireKeys": [ + "Cheered", + "Favorited" + ], + "isilFile": "CJODCLDGFCF.txt", + "isilType": "CJODCLDGFCF" + } + } + } + ], + "contracts": [ + { + "kind": "object", + "raw": "CJODCLDGFCF", + "rootType": "CJODCLDGFCF", + "typeDescription": { + "name": "CJODCLDGFCF", + "found": true, + "file": "CJODCLDGFCF.cs", + "declaration": "public class CJODCLDGFCF : IFAIJAGLDFK", + "kind": "class", + "baseType": "IFAIJAGLDFK", + "properties": [ + { + "name": "EFCGNDGOFNK", + "type": "Nullable", + "accessibility": "public", + "kind": "property" + }, + { + "name": "EMBNPPMOJFJ", + "type": "bool", + "accessibility": "public", + "kind": "property" + }, + { + "name": "HHFLKFOEHNL", + "type": "bool", + "accessibility": "public", + "kind": "property" + } + ], + "fields": [], + "enumValues": [], + "wireKeys": [ + "Cheered", + "Favorited" + ], + "isilFile": "CJODCLDGFCF.txt", + "isilType": "CJODCLDGFCF" + } + } + ] + }, + { + "family": "playlists", + "route": "playlists/{0}/interactionby/me/cheer", + "evidence": [ + { + "type": "EJDCNGBEICB", + "method": "RecRoom.Async.IPromise CIPEENNIAFL(System.Int64 DDMPFMPILCE)", + "file": "EJDCNGBEICB.txt", + "line": 8733, + "contract": { + "kind": "success-or-empty", + "raw": "RecRoom.Async.IPromise", + "rootType": "void/success", + "typeDescription": { + "name": "void/success", + "found": false, + "kind": "object", + "wireKeys": [] + } + } + }, + { + "type": "EJDCNGBEICB", + "method": "RecRoom.Async.IPromise LONLMONNPGL(System.Int64 DDMPFMPILCE)", + "file": "EJDCNGBEICB.txt", + "line": 8665, + "contract": { + "kind": "success-or-empty", + "raw": "RecRoom.Async.IPromise", + "rootType": "void/success", + "typeDescription": { + "name": "void/success", + "found": false, + "kind": "object", + "wireKeys": [] + } + } + } + ], + "contracts": [ + { + "kind": "success-or-empty", + "raw": "RecRoom.Async.IPromise", + "rootType": "void/success", + "typeDescription": { + "name": "void/success", + "found": false, + "kind": "object", + "wireKeys": [] + } + } + ] + }, + { + "family": "playlists", + "route": "playlists/{0}/interactionby/me/favorite", + "evidence": [ + { + "type": "EJDCNGBEICB", + "method": "RecRoom.Async.IPromise GDELFOHOPGF(System.Int64 DDMPFMPILCE)", + "file": "EJDCNGBEICB.txt", + "line": 8801, + "contract": { + "kind": "success-or-empty", + "raw": "RecRoom.Async.IPromise", + "rootType": "void/success", + "typeDescription": { + "name": "void/success", + "found": false, + "kind": "object", + "wireKeys": [] + } + } + }, + { + "type": "EJDCNGBEICB", + "method": "RecRoom.Async.IPromise NLNKEGDKFCG(System.Int64 DDMPFMPILCE)", + "file": "EJDCNGBEICB.txt", + "line": 8869, + "contract": { + "kind": "success-or-empty", + "raw": "RecRoom.Async.IPromise", + "rootType": "void/success", + "typeDescription": { + "name": "void/success", + "found": false, + "kind": "object", + "wireKeys": [] + } + } + } + ], + "contracts": [ + { + "kind": "success-or-empty", + "raw": "RecRoom.Async.IPromise", + "rootType": "void/success", + "typeDescription": { + "name": "void/success", + "found": false, + "kind": "object", + "wireKeys": [] + } + } + ] + }, + { + "family": "playlists", + "route": "playlists/{0}/levelvoting", + "evidence": [ + { + "type": "EJDCNGBEICB", + "method": "RecRoom.Async.IPromise`1 PPBLDOPNJKC(System.Int64 DDMPFMPILCE, System.Boolean NNBHCDIKILH)", + "file": "EJDCNGBEICB.txt", + "line": 7582, + "contract": { + "kind": "object", + "raw": "BMFAGMFKODA", + "rootType": "BMFAGMFKODA", + "typeDescription": { + "name": "BMFAGMFKODA", + "found": true, + "file": "BMFAGMFKODA.cs", + "declaration": "public class BMFAGMFKODA : KMKPEOGJDFK", + "kind": "class", + "baseType": "KMKPEOGJDFK", + "properties": [ + { + "name": "BIODFICLKJH", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "DFKGKGIMEMP", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "FEBCCOFBKKJ", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "HEFJFKFNNCJ", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "IFMMBHKGHMB", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "KFFBPMHGFKB", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "PKKADKGDHNI", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + } + ], + "fields": [ + { + "name": "tag", + "type": "string", + "kind": "field" + } + ], + "enumValues": [], + "wireKeys": [], + "isilFile": "BMFAGMFKODA.txt", + "isilType": "BMFAGMFKODA", + "inherits": "KMKPEOGJDFK", + "inheritedMembers": [ + { + "name": "BBGEDJMGFFO", + "type": "long", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "KMKPEOGJDFK" + }, + { + "name": "ACBFDMLHFPB", + "type": "DateTime", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "AHGCOGFEEEE", + "type": "string", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "AKFBNELAMNA", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "BADIGBCKECA", + "type": "int", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "BNBLOBAEDEE", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "CDINMMPNAID", + "type": "NMJEKMMBDDE", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "CDNFGMHLDMJ", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "FIKEBGGCDFN", + "type": "string", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "GIBHIMGJNNO", + "type": "GPDIAKNEBKH", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "HPLBOMGACED", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "IGOPGMHHLKI", + "type": "string", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "JFEAPMIPNEP", + "type": "DPLPMKMFMPB", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "KHIJAFCHLIA", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "KLNJBBPNMBJ", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "KODBEJPEFOJ", + "type": "string", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "LPJLEMJFBPE", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "MGBDHBHCDMH", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "MIKDLDEALPN", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "OFONEIOEIED", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "OILEJFNPDDB", + "type": "HJPGEGENLPH", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "PEEFHKMOMKK", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + } + ], + "inheritedWireKeys": [ + "PlaylistId" + ] + } + } + } + ], + "contracts": [ + { + "kind": "object", + "raw": "BMFAGMFKODA", + "rootType": "BMFAGMFKODA", + "typeDescription": { + "name": "BMFAGMFKODA", + "found": true, + "file": "BMFAGMFKODA.cs", + "declaration": "public class BMFAGMFKODA : KMKPEOGJDFK", + "kind": "class", + "baseType": "KMKPEOGJDFK", + "properties": [ + { + "name": "BIODFICLKJH", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "DFKGKGIMEMP", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "FEBCCOFBKKJ", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "HEFJFKFNNCJ", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "IFMMBHKGHMB", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "KFFBPMHGFKB", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "PKKADKGDHNI", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + } + ], + "fields": [ + { + "name": "tag", + "type": "string", + "kind": "field" + } + ], + "enumValues": [], + "wireKeys": [], + "isilFile": "BMFAGMFKODA.txt", + "isilType": "BMFAGMFKODA", + "inherits": "KMKPEOGJDFK", + "inheritedMembers": [ + { + "name": "BBGEDJMGFFO", + "type": "long", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "KMKPEOGJDFK" + }, + { + "name": "ACBFDMLHFPB", + "type": "DateTime", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "AHGCOGFEEEE", + "type": "string", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "AKFBNELAMNA", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "BADIGBCKECA", + "type": "int", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "BNBLOBAEDEE", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "CDINMMPNAID", + "type": "NMJEKMMBDDE", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "CDNFGMHLDMJ", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "FIKEBGGCDFN", + "type": "string", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "GIBHIMGJNNO", + "type": "GPDIAKNEBKH", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "HPLBOMGACED", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "IGOPGMHHLKI", + "type": "string", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "JFEAPMIPNEP", + "type": "DPLPMKMFMPB", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "KHIJAFCHLIA", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "KLNJBBPNMBJ", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "KODBEJPEFOJ", + "type": "string", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "LPJLEMJFBPE", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "MGBDHBHCDMH", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "MIKDLDEALPN", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "OFONEIOEIED", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "OILEJFNPDDB", + "type": "HJPGEGENLPH", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "PEEFHKMOMKK", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + } + ], + "inheritedWireKeys": [ + "PlaylistId" + ] + } + } + ] + }, + { + "family": "playlists", + "route": "playlists/{0}/name", + "evidence": [ + { + "type": "EJDCNGBEICB", + "method": "RecRoom.Async.IPromise`1 NINPMBNNHMG(System.Int64 DDMPFMPILCE, System.String MMBOKOLAJFH)", + "file": "EJDCNGBEICB.txt", + "line": 6723, + "contract": { + "kind": "object", + "raw": "BMFAGMFKODA", + "rootType": "BMFAGMFKODA", + "typeDescription": { + "name": "BMFAGMFKODA", + "found": true, + "file": "BMFAGMFKODA.cs", + "declaration": "public class BMFAGMFKODA : KMKPEOGJDFK", + "kind": "class", + "baseType": "KMKPEOGJDFK", + "properties": [ + { + "name": "BIODFICLKJH", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "DFKGKGIMEMP", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "FEBCCOFBKKJ", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "HEFJFKFNNCJ", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "IFMMBHKGHMB", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "KFFBPMHGFKB", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "PKKADKGDHNI", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + } + ], + "fields": [ + { + "name": "tag", + "type": "string", + "kind": "field" + } + ], + "enumValues": [], + "wireKeys": [], + "isilFile": "BMFAGMFKODA.txt", + "isilType": "BMFAGMFKODA", + "inherits": "KMKPEOGJDFK", + "inheritedMembers": [ + { + "name": "BBGEDJMGFFO", + "type": "long", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "KMKPEOGJDFK" + }, + { + "name": "ACBFDMLHFPB", + "type": "DateTime", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "AHGCOGFEEEE", + "type": "string", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "AKFBNELAMNA", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "BADIGBCKECA", + "type": "int", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "BNBLOBAEDEE", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "CDINMMPNAID", + "type": "NMJEKMMBDDE", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "CDNFGMHLDMJ", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "FIKEBGGCDFN", + "type": "string", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "GIBHIMGJNNO", + "type": "GPDIAKNEBKH", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "HPLBOMGACED", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "IGOPGMHHLKI", + "type": "string", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "JFEAPMIPNEP", + "type": "DPLPMKMFMPB", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "KHIJAFCHLIA", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "KLNJBBPNMBJ", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "KODBEJPEFOJ", + "type": "string", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "LPJLEMJFBPE", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "MGBDHBHCDMH", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "MIKDLDEALPN", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "OFONEIOEIED", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "OILEJFNPDDB", + "type": "HJPGEGENLPH", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "PEEFHKMOMKK", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + } + ], + "inheritedWireKeys": [ + "PlaylistId" + ] + } + } + } + ], + "contracts": [ + { + "kind": "object", + "raw": "BMFAGMFKODA", + "rootType": "BMFAGMFKODA", + "typeDescription": { + "name": "BMFAGMFKODA", + "found": true, + "file": "BMFAGMFKODA.cs", + "declaration": "public class BMFAGMFKODA : KMKPEOGJDFK", + "kind": "class", + "baseType": "KMKPEOGJDFK", + "properties": [ + { + "name": "BIODFICLKJH", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "DFKGKGIMEMP", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "FEBCCOFBKKJ", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "HEFJFKFNNCJ", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "IFMMBHKGHMB", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "KFFBPMHGFKB", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "PKKADKGDHNI", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + } + ], + "fields": [ + { + "name": "tag", + "type": "string", + "kind": "field" + } + ], + "enumValues": [], + "wireKeys": [], + "isilFile": "BMFAGMFKODA.txt", + "isilType": "BMFAGMFKODA", + "inherits": "KMKPEOGJDFK", + "inheritedMembers": [ + { + "name": "BBGEDJMGFFO", + "type": "long", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "KMKPEOGJDFK" + }, + { + "name": "ACBFDMLHFPB", + "type": "DateTime", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "AHGCOGFEEEE", + "type": "string", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "AKFBNELAMNA", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "BADIGBCKECA", + "type": "int", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "BNBLOBAEDEE", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "CDINMMPNAID", + "type": "NMJEKMMBDDE", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "CDNFGMHLDMJ", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "FIKEBGGCDFN", + "type": "string", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "GIBHIMGJNNO", + "type": "GPDIAKNEBKH", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "HPLBOMGACED", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "IGOPGMHHLKI", + "type": "string", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "JFEAPMIPNEP", + "type": "DPLPMKMFMPB", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "KHIJAFCHLIA", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "KLNJBBPNMBJ", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "KODBEJPEFOJ", + "type": "string", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "LPJLEMJFBPE", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "MGBDHBHCDMH", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "MIKDLDEALPN", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "OFONEIOEIED", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "OILEJFNPDDB", + "type": "HJPGEGENLPH", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "PEEFHKMOMKK", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + } + ], + "inheritedWireKeys": [ + "PlaylistId" + ] + } + } + ] + }, + { + "family": "playlists", + "route": "playlists/{0}/restrictions", + "evidence": [ + { + "type": "EJDCNGBEICB", + "method": "RecRoom.Async.IPromise`1 LDLJKJLNLEE(System.Int64 DDMPFMPILCE, System.Boolean DDMOEIAHJBK, System.Boolean FOBLJIBLCNI, System.Boolean GELHFEFGJLA, System.Boolean NFIGHDPKLJF)", + "file": "EJDCNGBEICB.txt", + "line": 7454, + "contract": { + "kind": "object", + "raw": "BMFAGMFKODA", + "rootType": "BMFAGMFKODA", + "typeDescription": { + "name": "BMFAGMFKODA", + "found": true, + "file": "BMFAGMFKODA.cs", + "declaration": "public class BMFAGMFKODA : KMKPEOGJDFK", + "kind": "class", + "baseType": "KMKPEOGJDFK", + "properties": [ + { + "name": "BIODFICLKJH", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "DFKGKGIMEMP", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "FEBCCOFBKKJ", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "HEFJFKFNNCJ", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "IFMMBHKGHMB", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "KFFBPMHGFKB", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "PKKADKGDHNI", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + } + ], + "fields": [ + { + "name": "tag", + "type": "string", + "kind": "field" + } + ], + "enumValues": [], + "wireKeys": [], + "isilFile": "BMFAGMFKODA.txt", + "isilType": "BMFAGMFKODA", + "inherits": "KMKPEOGJDFK", + "inheritedMembers": [ + { + "name": "BBGEDJMGFFO", + "type": "long", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "KMKPEOGJDFK" + }, + { + "name": "ACBFDMLHFPB", + "type": "DateTime", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "AHGCOGFEEEE", + "type": "string", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "AKFBNELAMNA", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "BADIGBCKECA", + "type": "int", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "BNBLOBAEDEE", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "CDINMMPNAID", + "type": "NMJEKMMBDDE", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "CDNFGMHLDMJ", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "FIKEBGGCDFN", + "type": "string", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "GIBHIMGJNNO", + "type": "GPDIAKNEBKH", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "HPLBOMGACED", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "IGOPGMHHLKI", + "type": "string", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "JFEAPMIPNEP", + "type": "DPLPMKMFMPB", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "KHIJAFCHLIA", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "KLNJBBPNMBJ", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "KODBEJPEFOJ", + "type": "string", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "LPJLEMJFBPE", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "MGBDHBHCDMH", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "MIKDLDEALPN", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "OFONEIOEIED", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "OILEJFNPDDB", + "type": "HJPGEGENLPH", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "PEEFHKMOMKK", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + } + ], + "inheritedWireKeys": [ + "PlaylistId" + ] + } + } + } + ], + "contracts": [ + { + "kind": "object", + "raw": "BMFAGMFKODA", + "rootType": "BMFAGMFKODA", + "typeDescription": { + "name": "BMFAGMFKODA", + "found": true, + "file": "BMFAGMFKODA.cs", + "declaration": "public class BMFAGMFKODA : KMKPEOGJDFK", + "kind": "class", + "baseType": "KMKPEOGJDFK", + "properties": [ + { + "name": "BIODFICLKJH", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "DFKGKGIMEMP", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "FEBCCOFBKKJ", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "HEFJFKFNNCJ", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "IFMMBHKGHMB", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "KFFBPMHGFKB", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "PKKADKGDHNI", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + } + ], + "fields": [ + { + "name": "tag", + "type": "string", + "kind": "field" + } + ], + "enumValues": [], + "wireKeys": [], + "isilFile": "BMFAGMFKODA.txt", + "isilType": "BMFAGMFKODA", + "inherits": "KMKPEOGJDFK", + "inheritedMembers": [ + { + "name": "BBGEDJMGFFO", + "type": "long", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "KMKPEOGJDFK" + }, + { + "name": "ACBFDMLHFPB", + "type": "DateTime", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "AHGCOGFEEEE", + "type": "string", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "AKFBNELAMNA", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "BADIGBCKECA", + "type": "int", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "BNBLOBAEDEE", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "CDINMMPNAID", + "type": "NMJEKMMBDDE", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "CDNFGMHLDMJ", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "FIKEBGGCDFN", + "type": "string", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "GIBHIMGJNNO", + "type": "GPDIAKNEBKH", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "HPLBOMGACED", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "IGOPGMHHLKI", + "type": "string", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "JFEAPMIPNEP", + "type": "DPLPMKMFMPB", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "KHIJAFCHLIA", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "KLNJBBPNMBJ", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "KODBEJPEFOJ", + "type": "string", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "LPJLEMJFBPE", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "MGBDHBHCDMH", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "MIKDLDEALPN", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "OFONEIOEIED", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "OILEJFNPDDB", + "type": "HJPGEGENLPH", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "PEEFHKMOMKK", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + } + ], + "inheritedWireKeys": [ + "PlaylistId" + ] + } + } + ] + }, + { + "family": "playlists", + "route": "playlists/{0}/rooms/{1}", + "evidence": [ + { + "type": "EJDCNGBEICB", + "method": "RecRoom.Async.IPromise`1 JBMCPFHGIMD(System.Int64 DDMPFMPILCE, System.Int64 HNHLJONGKHB)", + "file": "EJDCNGBEICB.txt", + "line": 7765, + "contract": { + "kind": "object", + "raw": "BMFAGMFKODA", + "rootType": "BMFAGMFKODA", + "typeDescription": { + "name": "BMFAGMFKODA", + "found": true, + "file": "BMFAGMFKODA.cs", + "declaration": "public class BMFAGMFKODA : KMKPEOGJDFK", + "kind": "class", + "baseType": "KMKPEOGJDFK", + "properties": [ + { + "name": "BIODFICLKJH", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "DFKGKGIMEMP", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "FEBCCOFBKKJ", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "HEFJFKFNNCJ", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "IFMMBHKGHMB", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "KFFBPMHGFKB", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "PKKADKGDHNI", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + } + ], + "fields": [ + { + "name": "tag", + "type": "string", + "kind": "field" + } + ], + "enumValues": [], + "wireKeys": [], + "isilFile": "BMFAGMFKODA.txt", + "isilType": "BMFAGMFKODA", + "inherits": "KMKPEOGJDFK", + "inheritedMembers": [ + { + "name": "BBGEDJMGFFO", + "type": "long", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "KMKPEOGJDFK" + }, + { + "name": "ACBFDMLHFPB", + "type": "DateTime", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "AHGCOGFEEEE", + "type": "string", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "AKFBNELAMNA", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "BADIGBCKECA", + "type": "int", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "BNBLOBAEDEE", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "CDINMMPNAID", + "type": "NMJEKMMBDDE", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "CDNFGMHLDMJ", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "FIKEBGGCDFN", + "type": "string", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "GIBHIMGJNNO", + "type": "GPDIAKNEBKH", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "HPLBOMGACED", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "IGOPGMHHLKI", + "type": "string", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "JFEAPMIPNEP", + "type": "DPLPMKMFMPB", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "KHIJAFCHLIA", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "KLNJBBPNMBJ", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "KODBEJPEFOJ", + "type": "string", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "LPJLEMJFBPE", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "MGBDHBHCDMH", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "MIKDLDEALPN", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "OFONEIOEIED", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "OILEJFNPDDB", + "type": "HJPGEGENLPH", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "PEEFHKMOMKK", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + } + ], + "inheritedWireKeys": [ + "PlaylistId" + ] + } + } + }, + { + "type": "EJDCNGBEICB", + "method": "RecRoom.Async.IPromise`1 NLOEBFMNCFM(System.Int64 DDMPFMPILCE, System.Int64 HNHLJONGKHB)", + "file": "EJDCNGBEICB.txt", + "line": 7679, + "contract": { + "kind": "object", + "raw": "BMFAGMFKODA", + "rootType": "BMFAGMFKODA", + "typeDescription": { + "name": "BMFAGMFKODA", + "found": true, + "file": "BMFAGMFKODA.cs", + "declaration": "public class BMFAGMFKODA : KMKPEOGJDFK", + "kind": "class", + "baseType": "KMKPEOGJDFK", + "properties": [ + { + "name": "BIODFICLKJH", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "DFKGKGIMEMP", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "FEBCCOFBKKJ", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "HEFJFKFNNCJ", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "IFMMBHKGHMB", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "KFFBPMHGFKB", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "PKKADKGDHNI", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + } + ], + "fields": [ + { + "name": "tag", + "type": "string", + "kind": "field" + } + ], + "enumValues": [], + "wireKeys": [], + "isilFile": "BMFAGMFKODA.txt", + "isilType": "BMFAGMFKODA", + "inherits": "KMKPEOGJDFK", + "inheritedMembers": [ + { + "name": "BBGEDJMGFFO", + "type": "long", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "KMKPEOGJDFK" + }, + { + "name": "ACBFDMLHFPB", + "type": "DateTime", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "AHGCOGFEEEE", + "type": "string", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "AKFBNELAMNA", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "BADIGBCKECA", + "type": "int", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "BNBLOBAEDEE", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "CDINMMPNAID", + "type": "NMJEKMMBDDE", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "CDNFGMHLDMJ", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "FIKEBGGCDFN", + "type": "string", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "GIBHIMGJNNO", + "type": "GPDIAKNEBKH", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "HPLBOMGACED", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "IGOPGMHHLKI", + "type": "string", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "JFEAPMIPNEP", + "type": "DPLPMKMFMPB", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "KHIJAFCHLIA", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "KLNJBBPNMBJ", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "KODBEJPEFOJ", + "type": "string", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "LPJLEMJFBPE", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "MGBDHBHCDMH", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "MIKDLDEALPN", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "OFONEIOEIED", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "OILEJFNPDDB", + "type": "HJPGEGENLPH", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "PEEFHKMOMKK", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + } + ], + "inheritedWireKeys": [ + "PlaylistId" + ] + } + } + } + ], + "contracts": [ + { + "kind": "object", + "raw": "BMFAGMFKODA", + "rootType": "BMFAGMFKODA", + "typeDescription": { + "name": "BMFAGMFKODA", + "found": true, + "file": "BMFAGMFKODA.cs", + "declaration": "public class BMFAGMFKODA : KMKPEOGJDFK", + "kind": "class", + "baseType": "KMKPEOGJDFK", + "properties": [ + { + "name": "BIODFICLKJH", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "DFKGKGIMEMP", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "FEBCCOFBKKJ", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "HEFJFKFNNCJ", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "IFMMBHKGHMB", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "KFFBPMHGFKB", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "PKKADKGDHNI", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + } + ], + "fields": [ + { + "name": "tag", + "type": "string", + "kind": "field" + } + ], + "enumValues": [], + "wireKeys": [], + "isilFile": "BMFAGMFKODA.txt", + "isilType": "BMFAGMFKODA", + "inherits": "KMKPEOGJDFK", + "inheritedMembers": [ + { + "name": "BBGEDJMGFFO", + "type": "long", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "KMKPEOGJDFK" + }, + { + "name": "ACBFDMLHFPB", + "type": "DateTime", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "AHGCOGFEEEE", + "type": "string", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "AKFBNELAMNA", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "BADIGBCKECA", + "type": "int", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "BNBLOBAEDEE", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "CDINMMPNAID", + "type": "NMJEKMMBDDE", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "CDNFGMHLDMJ", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "FIKEBGGCDFN", + "type": "string", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "GIBHIMGJNNO", + "type": "GPDIAKNEBKH", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "HPLBOMGACED", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "IGOPGMHHLKI", + "type": "string", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "JFEAPMIPNEP", + "type": "DPLPMKMFMPB", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "KHIJAFCHLIA", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "KLNJBBPNMBJ", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "KODBEJPEFOJ", + "type": "string", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "LPJLEMJFBPE", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "MGBDHBHCDMH", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "MIKDLDEALPN", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "OFONEIOEIED", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "OILEJFNPDDB", + "type": "HJPGEGENLPH", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "PEEFHKMOMKK", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + } + ], + "inheritedWireKeys": [ + "PlaylistId" + ] + } + } + ] + }, + { + "family": "playlists", + "route": "playlists/{0}/tags", + "evidence": [ + { + "type": "EJDCNGBEICB", + "method": "RecRoom.Async.IPromise`1 ALCFNJJHDMG(System.Int64 DDMPFMPILCE, System.Collections.Generic.IReadOnlyList`1 LBAOCHFCLPO, System.Collections.Generic.IReadOnlyList`1 CAFPJPHILMN)", + "file": "EJDCNGBEICB.txt", + "line": 7080, + "contract": { + "kind": "object", + "raw": "BMFAGMFKODA", + "rootType": "BMFAGMFKODA", + "typeDescription": { + "name": "BMFAGMFKODA", + "found": true, + "file": "BMFAGMFKODA.cs", + "declaration": "public class BMFAGMFKODA : KMKPEOGJDFK", + "kind": "class", + "baseType": "KMKPEOGJDFK", + "properties": [ + { + "name": "BIODFICLKJH", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "DFKGKGIMEMP", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "FEBCCOFBKKJ", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "HEFJFKFNNCJ", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "IFMMBHKGHMB", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "KFFBPMHGFKB", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "PKKADKGDHNI", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + } + ], + "fields": [ + { + "name": "tag", + "type": "string", + "kind": "field" + } + ], + "enumValues": [], + "wireKeys": [], + "isilFile": "BMFAGMFKODA.txt", + "isilType": "BMFAGMFKODA", + "inherits": "KMKPEOGJDFK", + "inheritedMembers": [ + { + "name": "BBGEDJMGFFO", + "type": "long", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "KMKPEOGJDFK" + }, + { + "name": "ACBFDMLHFPB", + "type": "DateTime", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "AHGCOGFEEEE", + "type": "string", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "AKFBNELAMNA", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "BADIGBCKECA", + "type": "int", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "BNBLOBAEDEE", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "CDINMMPNAID", + "type": "NMJEKMMBDDE", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "CDNFGMHLDMJ", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "FIKEBGGCDFN", + "type": "string", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "GIBHIMGJNNO", + "type": "GPDIAKNEBKH", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "HPLBOMGACED", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "IGOPGMHHLKI", + "type": "string", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "JFEAPMIPNEP", + "type": "DPLPMKMFMPB", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "KHIJAFCHLIA", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "KLNJBBPNMBJ", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "KODBEJPEFOJ", + "type": "string", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "LPJLEMJFBPE", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "MGBDHBHCDMH", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "MIKDLDEALPN", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "OFONEIOEIED", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "OILEJFNPDDB", + "type": "HJPGEGENLPH", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "PEEFHKMOMKK", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + } + ], + "inheritedWireKeys": [ + "PlaylistId" + ] + } + } + } + ], + "contracts": [ + { + "kind": "object", + "raw": "BMFAGMFKODA", + "rootType": "BMFAGMFKODA", + "typeDescription": { + "name": "BMFAGMFKODA", + "found": true, + "file": "BMFAGMFKODA.cs", + "declaration": "public class BMFAGMFKODA : KMKPEOGJDFK", + "kind": "class", + "baseType": "KMKPEOGJDFK", + "properties": [ + { + "name": "BIODFICLKJH", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "DFKGKGIMEMP", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "FEBCCOFBKKJ", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "HEFJFKFNNCJ", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "IFMMBHKGHMB", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "KFFBPMHGFKB", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "PKKADKGDHNI", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + } + ], + "fields": [ + { + "name": "tag", + "type": "string", + "kind": "field" + } + ], + "enumValues": [], + "wireKeys": [], + "isilFile": "BMFAGMFKODA.txt", + "isilType": "BMFAGMFKODA", + "inherits": "KMKPEOGJDFK", + "inheritedMembers": [ + { + "name": "BBGEDJMGFFO", + "type": "long", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "KMKPEOGJDFK" + }, + { + "name": "ACBFDMLHFPB", + "type": "DateTime", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "AHGCOGFEEEE", + "type": "string", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "AKFBNELAMNA", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "BADIGBCKECA", + "type": "int", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "BNBLOBAEDEE", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "CDINMMPNAID", + "type": "NMJEKMMBDDE", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "CDNFGMHLDMJ", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "FIKEBGGCDFN", + "type": "string", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "GIBHIMGJNNO", + "type": "GPDIAKNEBKH", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "HPLBOMGACED", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "IGOPGMHHLKI", + "type": "string", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "JFEAPMIPNEP", + "type": "DPLPMKMFMPB", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "KHIJAFCHLIA", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "KLNJBBPNMBJ", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "KODBEJPEFOJ", + "type": "string", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "LPJLEMJFBPE", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "MGBDHBHCDMH", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "MIKDLDEALPN", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "OFONEIOEIED", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "OILEJFNPDDB", + "type": "HJPGEGENLPH", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "PEEFHKMOMKK", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + } + ], + "inheritedWireKeys": [ + "PlaylistId" + ] + } + } + ] + }, + { + "family": "playlists", + "route": "playlists/{0}/warning", + "evidence": [ + { + "type": "EJDCNGBEICB", + "method": "RecRoom.Async.IPromise`1 KEADPPJCJGG(System.Int64 DDMPFMPILCE, GPDIAKNEBKH LEFPAAGGFFA, System.String NLEBEKDHJIJ)", + "file": "EJDCNGBEICB.txt", + "line": 7206, + "contract": { + "kind": "object", + "raw": "BMFAGMFKODA", + "rootType": "BMFAGMFKODA", + "typeDescription": { + "name": "BMFAGMFKODA", + "found": true, + "file": "BMFAGMFKODA.cs", + "declaration": "public class BMFAGMFKODA : KMKPEOGJDFK", + "kind": "class", + "baseType": "KMKPEOGJDFK", + "properties": [ + { + "name": "BIODFICLKJH", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "DFKGKGIMEMP", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "FEBCCOFBKKJ", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "HEFJFKFNNCJ", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "IFMMBHKGHMB", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "KFFBPMHGFKB", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "PKKADKGDHNI", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + } + ], + "fields": [ + { + "name": "tag", + "type": "string", + "kind": "field" + } + ], + "enumValues": [], + "wireKeys": [], + "isilFile": "BMFAGMFKODA.txt", + "isilType": "BMFAGMFKODA", + "inherits": "KMKPEOGJDFK", + "inheritedMembers": [ + { + "name": "BBGEDJMGFFO", + "type": "long", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "KMKPEOGJDFK" + }, + { + "name": "ACBFDMLHFPB", + "type": "DateTime", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "AHGCOGFEEEE", + "type": "string", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "AKFBNELAMNA", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "BADIGBCKECA", + "type": "int", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "BNBLOBAEDEE", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "CDINMMPNAID", + "type": "NMJEKMMBDDE", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "CDNFGMHLDMJ", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "FIKEBGGCDFN", + "type": "string", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "GIBHIMGJNNO", + "type": "GPDIAKNEBKH", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "HPLBOMGACED", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "IGOPGMHHLKI", + "type": "string", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "JFEAPMIPNEP", + "type": "DPLPMKMFMPB", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "KHIJAFCHLIA", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "KLNJBBPNMBJ", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "KODBEJPEFOJ", + "type": "string", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "LPJLEMJFBPE", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "MGBDHBHCDMH", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "MIKDLDEALPN", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "OFONEIOEIED", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "OILEJFNPDDB", + "type": "HJPGEGENLPH", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "PEEFHKMOMKK", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + } + ], + "inheritedWireKeys": [ + "PlaylistId" + ] + } + } + } + ], + "contracts": [ + { + "kind": "object", + "raw": "BMFAGMFKODA", + "rootType": "BMFAGMFKODA", + "typeDescription": { + "name": "BMFAGMFKODA", + "found": true, + "file": "BMFAGMFKODA.cs", + "declaration": "public class BMFAGMFKODA : KMKPEOGJDFK", + "kind": "class", + "baseType": "KMKPEOGJDFK", + "properties": [ + { + "name": "BIODFICLKJH", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "DFKGKGIMEMP", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "FEBCCOFBKKJ", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "HEFJFKFNNCJ", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "IFMMBHKGHMB", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "KFFBPMHGFKB", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "PKKADKGDHNI", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + } + ], + "fields": [ + { + "name": "tag", + "type": "string", + "kind": "field" + } + ], + "enumValues": [], + "wireKeys": [], + "isilFile": "BMFAGMFKODA.txt", + "isilType": "BMFAGMFKODA", + "inherits": "KMKPEOGJDFK", + "inheritedMembers": [ + { + "name": "BBGEDJMGFFO", + "type": "long", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "KMKPEOGJDFK" + }, + { + "name": "ACBFDMLHFPB", + "type": "DateTime", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "AHGCOGFEEEE", + "type": "string", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "AKFBNELAMNA", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "BADIGBCKECA", + "type": "int", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "BNBLOBAEDEE", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "CDINMMPNAID", + "type": "NMJEKMMBDDE", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "CDNFGMHLDMJ", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "FIKEBGGCDFN", + "type": "string", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "GIBHIMGJNNO", + "type": "GPDIAKNEBKH", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "HPLBOMGACED", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "IGOPGMHHLKI", + "type": "string", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "JFEAPMIPNEP", + "type": "DPLPMKMFMPB", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "KHIJAFCHLIA", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "KLNJBBPNMBJ", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "KODBEJPEFOJ", + "type": "string", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "LPJLEMJFBPE", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "MGBDHBHCDMH", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "MIKDLDEALPN", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "OFONEIOEIED", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "OILEJFNPDDB", + "type": "HJPGEGENLPH", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "PEEFHKMOMKK", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + } + ], + "inheritedWireKeys": [ + "PlaylistId" + ] + } + } + ] + }, + { + "family": "playlists", + "route": "playlists/bulk", + "evidence": [ + { + "type": "EJDCNGBEICB", + "method": "RecRoom.Async.IPromise`1> KHMJIKNHJHP(System.Collections.Generic.IReadOnlyList`1 ANIKDNFLDIG)", + "file": "EJDCNGBEICB.txt", + "line": 1896, + "contract": { + "kind": "array", + "raw": "System.Collections.Generic.List`1", + "rootType": "KMKPEOGJDFK", + "item": { + "kind": "object", + "raw": "KMKPEOGJDFK", + "rootType": "KMKPEOGJDFK" + }, + "typeDescription": { + "name": "KMKPEOGJDFK", + "found": true, + "file": "KMKPEOGJDFK.cs", + "declaration": "public class KMKPEOGJDFK : MKAMHOIHOJK", + "kind": "class", + "baseType": "MKAMHOIHOJK", + "properties": [ + { + "name": "BBGEDJMGFFO", + "type": "long", + "accessibility": "public", + "kind": "property" + } + ], + "fields": [], + "enumValues": [], + "wireKeys": [ + "PlaylistId" + ], + "isilFile": "KMKPEOGJDFK.txt", + "isilType": "KMKPEOGJDFK", + "inherits": "MKAMHOIHOJK", + "inheritedMembers": [ + { + "name": "ACBFDMLHFPB", + "type": "DateTime", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "AHGCOGFEEEE", + "type": "string", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "AKFBNELAMNA", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "BADIGBCKECA", + "type": "int", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "BNBLOBAEDEE", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "CDINMMPNAID", + "type": "NMJEKMMBDDE", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "CDNFGMHLDMJ", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "FIKEBGGCDFN", + "type": "string", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "GIBHIMGJNNO", + "type": "GPDIAKNEBKH", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "HPLBOMGACED", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "IGOPGMHHLKI", + "type": "string", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "JFEAPMIPNEP", + "type": "DPLPMKMFMPB", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "KHIJAFCHLIA", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "KLNJBBPNMBJ", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "KODBEJPEFOJ", + "type": "string", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "LPJLEMJFBPE", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "MGBDHBHCDMH", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "MIKDLDEALPN", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "OFONEIOEIED", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "OILEJFNPDDB", + "type": "HJPGEGENLPH", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "PEEFHKMOMKK", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + } + ], + "inheritedWireKeys": [ + "Name", + "Description", + "ImageName", + "WarningMask", + "CustomWarning", + "CreatorAccountId", + "State", + "Accessibility", + "SupportsLevelVoting", + "IsRRO", + "SupportsScreens", + "SupportsWalkVR", + "SupportsTeleportVR", + "SupportsVRLow", + "SupportsQuest2", + "SupportsMobile", + "SupportsJuniors", + "CreatedAt", + "Stats" + ] + } + } + }, + { + "type": "EJDCNGBEICB", + "method": "RecRoom.Async.IPromise`1> KHMJIKNHJHP(System.Collections.Generic.IReadOnlyList`1 CJMAAFFAKDO)", + "file": "EJDCNGBEICB.txt", + "line": 2068, + "contract": { + "kind": "array", + "raw": "System.Collections.Generic.List`1", + "rootType": "KMKPEOGJDFK", + "item": { + "kind": "object", + "raw": "KMKPEOGJDFK", + "rootType": "KMKPEOGJDFK" + }, + "typeDescription": { + "name": "KMKPEOGJDFK", + "found": true, + "file": "KMKPEOGJDFK.cs", + "declaration": "public class KMKPEOGJDFK : MKAMHOIHOJK", + "kind": "class", + "baseType": "MKAMHOIHOJK", + "properties": [ + { + "name": "BBGEDJMGFFO", + "type": "long", + "accessibility": "public", + "kind": "property" + } + ], + "fields": [], + "enumValues": [], + "wireKeys": [ + "PlaylistId" + ], + "isilFile": "KMKPEOGJDFK.txt", + "isilType": "KMKPEOGJDFK", + "inherits": "MKAMHOIHOJK", + "inheritedMembers": [ + { + "name": "ACBFDMLHFPB", + "type": "DateTime", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "AHGCOGFEEEE", + "type": "string", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "AKFBNELAMNA", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "BADIGBCKECA", + "type": "int", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "BNBLOBAEDEE", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "CDINMMPNAID", + "type": "NMJEKMMBDDE", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "CDNFGMHLDMJ", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "FIKEBGGCDFN", + "type": "string", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "GIBHIMGJNNO", + "type": "GPDIAKNEBKH", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "HPLBOMGACED", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "IGOPGMHHLKI", + "type": "string", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "JFEAPMIPNEP", + "type": "DPLPMKMFMPB", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "KHIJAFCHLIA", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "KLNJBBPNMBJ", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "KODBEJPEFOJ", + "type": "string", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "LPJLEMJFBPE", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "MGBDHBHCDMH", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "MIKDLDEALPN", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "OFONEIOEIED", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "OILEJFNPDDB", + "type": "HJPGEGENLPH", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "PEEFHKMOMKK", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + } + ], + "inheritedWireKeys": [ + "Name", + "Description", + "ImageName", + "WarningMask", + "CustomWarning", + "CreatorAccountId", + "State", + "Accessibility", + "SupportsLevelVoting", + "IsRRO", + "SupportsScreens", + "SupportsWalkVR", + "SupportsTeleportVR", + "SupportsVRLow", + "SupportsQuest2", + "SupportsMobile", + "SupportsJuniors", + "CreatedAt", + "Stats" + ] + } + } + } + ], + "contracts": [ + { + "kind": "array", + "raw": "System.Collections.Generic.List`1", + "rootType": "KMKPEOGJDFK", + "item": { + "kind": "object", + "raw": "KMKPEOGJDFK", + "rootType": "KMKPEOGJDFK" + }, + "typeDescription": { + "name": "KMKPEOGJDFK", + "found": true, + "file": "KMKPEOGJDFK.cs", + "declaration": "public class KMKPEOGJDFK : MKAMHOIHOJK", + "kind": "class", + "baseType": "MKAMHOIHOJK", + "properties": [ + { + "name": "BBGEDJMGFFO", + "type": "long", + "accessibility": "public", + "kind": "property" + } + ], + "fields": [], + "enumValues": [], + "wireKeys": [ + "PlaylistId" + ], + "isilFile": "KMKPEOGJDFK.txt", + "isilType": "KMKPEOGJDFK", + "inherits": "MKAMHOIHOJK", + "inheritedMembers": [ + { + "name": "ACBFDMLHFPB", + "type": "DateTime", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "AHGCOGFEEEE", + "type": "string", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "AKFBNELAMNA", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "BADIGBCKECA", + "type": "int", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "BNBLOBAEDEE", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "CDINMMPNAID", + "type": "NMJEKMMBDDE", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "CDNFGMHLDMJ", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "FIKEBGGCDFN", + "type": "string", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "GIBHIMGJNNO", + "type": "GPDIAKNEBKH", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "HPLBOMGACED", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "IGOPGMHHLKI", + "type": "string", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "JFEAPMIPNEP", + "type": "DPLPMKMFMPB", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "KHIJAFCHLIA", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "KLNJBBPNMBJ", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "KODBEJPEFOJ", + "type": "string", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "LPJLEMJFBPE", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "MGBDHBHCDMH", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "MIKDLDEALPN", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "OFONEIOEIED", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "OILEJFNPDDB", + "type": "HJPGEGENLPH", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "PEEFHKMOMKK", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + } + ], + "inheritedWireKeys": [ + "Name", + "Description", + "ImageName", + "WarningMask", + "CustomWarning", + "CreatorAccountId", + "State", + "Accessibility", + "SupportsLevelVoting", + "IsRRO", + "SupportsScreens", + "SupportsWalkVR", + "SupportsTeleportVR", + "SupportsVRLow", + "SupportsQuest2", + "SupportsMobile", + "SupportsJuniors", + "CreatedAt", + "Stats" + ] + } + } + ] + }, + { + "family": "playlists", + "route": "playlists/cheeredby/me", + "evidence": [ + { + "type": "EJDCNGBEICB", + "method": "RecRoom.Async.IPromise`1> EFFMOEGALDB()", + "file": "EJDCNGBEICB.txt", + "line": 2400, + "contract": { + "kind": "array", + "raw": "System.Collections.Generic.List`1", + "rootType": "KMKPEOGJDFK", + "item": { + "kind": "object", + "raw": "KMKPEOGJDFK", + "rootType": "KMKPEOGJDFK" + }, + "typeDescription": { + "name": "KMKPEOGJDFK", + "found": true, + "file": "KMKPEOGJDFK.cs", + "declaration": "public class KMKPEOGJDFK : MKAMHOIHOJK", + "kind": "class", + "baseType": "MKAMHOIHOJK", + "properties": [ + { + "name": "BBGEDJMGFFO", + "type": "long", + "accessibility": "public", + "kind": "property" + } + ], + "fields": [], + "enumValues": [], + "wireKeys": [ + "PlaylistId" + ], + "isilFile": "KMKPEOGJDFK.txt", + "isilType": "KMKPEOGJDFK", + "inherits": "MKAMHOIHOJK", + "inheritedMembers": [ + { + "name": "ACBFDMLHFPB", + "type": "DateTime", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "AHGCOGFEEEE", + "type": "string", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "AKFBNELAMNA", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "BADIGBCKECA", + "type": "int", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "BNBLOBAEDEE", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "CDINMMPNAID", + "type": "NMJEKMMBDDE", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "CDNFGMHLDMJ", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "FIKEBGGCDFN", + "type": "string", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "GIBHIMGJNNO", + "type": "GPDIAKNEBKH", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "HPLBOMGACED", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "IGOPGMHHLKI", + "type": "string", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "JFEAPMIPNEP", + "type": "DPLPMKMFMPB", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "KHIJAFCHLIA", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "KLNJBBPNMBJ", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "KODBEJPEFOJ", + "type": "string", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "LPJLEMJFBPE", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "MGBDHBHCDMH", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "MIKDLDEALPN", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "OFONEIOEIED", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "OILEJFNPDDB", + "type": "HJPGEGENLPH", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "PEEFHKMOMKK", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + } + ], + "inheritedWireKeys": [ + "Name", + "Description", + "ImageName", + "WarningMask", + "CustomWarning", + "CreatorAccountId", + "State", + "Accessibility", + "SupportsLevelVoting", + "IsRRO", + "SupportsScreens", + "SupportsWalkVR", + "SupportsTeleportVR", + "SupportsVRLow", + "SupportsQuest2", + "SupportsMobile", + "SupportsJuniors", + "CreatedAt", + "Stats" + ] + } + } + } + ], + "contracts": [ + { + "kind": "array", + "raw": "System.Collections.Generic.List`1", + "rootType": "KMKPEOGJDFK", + "item": { + "kind": "object", + "raw": "KMKPEOGJDFK", + "rootType": "KMKPEOGJDFK" + }, + "typeDescription": { + "name": "KMKPEOGJDFK", + "found": true, + "file": "KMKPEOGJDFK.cs", + "declaration": "public class KMKPEOGJDFK : MKAMHOIHOJK", + "kind": "class", + "baseType": "MKAMHOIHOJK", + "properties": [ + { + "name": "BBGEDJMGFFO", + "type": "long", + "accessibility": "public", + "kind": "property" + } + ], + "fields": [], + "enumValues": [], + "wireKeys": [ + "PlaylistId" + ], + "isilFile": "KMKPEOGJDFK.txt", + "isilType": "KMKPEOGJDFK", + "inherits": "MKAMHOIHOJK", + "inheritedMembers": [ + { + "name": "ACBFDMLHFPB", + "type": "DateTime", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "AHGCOGFEEEE", + "type": "string", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "AKFBNELAMNA", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "BADIGBCKECA", + "type": "int", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "BNBLOBAEDEE", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "CDINMMPNAID", + "type": "NMJEKMMBDDE", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "CDNFGMHLDMJ", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "FIKEBGGCDFN", + "type": "string", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "GIBHIMGJNNO", + "type": "GPDIAKNEBKH", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "HPLBOMGACED", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "IGOPGMHHLKI", + "type": "string", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "JFEAPMIPNEP", + "type": "DPLPMKMFMPB", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "KHIJAFCHLIA", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "KLNJBBPNMBJ", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "KODBEJPEFOJ", + "type": "string", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "LPJLEMJFBPE", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "MGBDHBHCDMH", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "MIKDLDEALPN", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "OFONEIOEIED", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "OILEJFNPDDB", + "type": "HJPGEGENLPH", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "PEEFHKMOMKK", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + } + ], + "inheritedWireKeys": [ + "Name", + "Description", + "ImageName", + "WarningMask", + "CustomWarning", + "CreatorAccountId", + "State", + "Accessibility", + "SupportsLevelVoting", + "IsRRO", + "SupportsScreens", + "SupportsWalkVR", + "SupportsTeleportVR", + "SupportsVRLow", + "SupportsQuest2", + "SupportsMobile", + "SupportsJuniors", + "CreatedAt", + "Stats" + ] + } + } + ] + }, + { + "family": "playlists", + "route": "playlists/createdby/me", + "evidence": [ + { + "type": "EJDCNGBEICB", + "method": "RecRoom.Async.IPromise`1> PLPMPFDOICH()", + "file": "EJDCNGBEICB.txt", + "line": 2361, + "contract": { + "kind": "array", + "raw": "System.Collections.Generic.List`1", + "rootType": "KMKPEOGJDFK", + "item": { + "kind": "object", + "raw": "KMKPEOGJDFK", + "rootType": "KMKPEOGJDFK" + }, + "typeDescription": { + "name": "KMKPEOGJDFK", + "found": true, + "file": "KMKPEOGJDFK.cs", + "declaration": "public class KMKPEOGJDFK : MKAMHOIHOJK", + "kind": "class", + "baseType": "MKAMHOIHOJK", + "properties": [ + { + "name": "BBGEDJMGFFO", + "type": "long", + "accessibility": "public", + "kind": "property" + } + ], + "fields": [], + "enumValues": [], + "wireKeys": [ + "PlaylistId" + ], + "isilFile": "KMKPEOGJDFK.txt", + "isilType": "KMKPEOGJDFK", + "inherits": "MKAMHOIHOJK", + "inheritedMembers": [ + { + "name": "ACBFDMLHFPB", + "type": "DateTime", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "AHGCOGFEEEE", + "type": "string", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "AKFBNELAMNA", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "BADIGBCKECA", + "type": "int", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "BNBLOBAEDEE", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "CDINMMPNAID", + "type": "NMJEKMMBDDE", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "CDNFGMHLDMJ", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "FIKEBGGCDFN", + "type": "string", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "GIBHIMGJNNO", + "type": "GPDIAKNEBKH", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "HPLBOMGACED", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "IGOPGMHHLKI", + "type": "string", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "JFEAPMIPNEP", + "type": "DPLPMKMFMPB", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "KHIJAFCHLIA", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "KLNJBBPNMBJ", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "KODBEJPEFOJ", + "type": "string", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "LPJLEMJFBPE", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "MGBDHBHCDMH", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "MIKDLDEALPN", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "OFONEIOEIED", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "OILEJFNPDDB", + "type": "HJPGEGENLPH", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "PEEFHKMOMKK", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + } + ], + "inheritedWireKeys": [ + "Name", + "Description", + "ImageName", + "WarningMask", + "CustomWarning", + "CreatorAccountId", + "State", + "Accessibility", + "SupportsLevelVoting", + "IsRRO", + "SupportsScreens", + "SupportsWalkVR", + "SupportsTeleportVR", + "SupportsVRLow", + "SupportsQuest2", + "SupportsMobile", + "SupportsJuniors", + "CreatedAt", + "Stats" + ] + } + } + } + ], + "contracts": [ + { + "kind": "array", + "raw": "System.Collections.Generic.List`1", + "rootType": "KMKPEOGJDFK", + "item": { + "kind": "object", + "raw": "KMKPEOGJDFK", + "rootType": "KMKPEOGJDFK" + }, + "typeDescription": { + "name": "KMKPEOGJDFK", + "found": true, + "file": "KMKPEOGJDFK.cs", + "declaration": "public class KMKPEOGJDFK : MKAMHOIHOJK", + "kind": "class", + "baseType": "MKAMHOIHOJK", + "properties": [ + { + "name": "BBGEDJMGFFO", + "type": "long", + "accessibility": "public", + "kind": "property" + } + ], + "fields": [], + "enumValues": [], + "wireKeys": [ + "PlaylistId" + ], + "isilFile": "KMKPEOGJDFK.txt", + "isilType": "KMKPEOGJDFK", + "inherits": "MKAMHOIHOJK", + "inheritedMembers": [ + { + "name": "ACBFDMLHFPB", + "type": "DateTime", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "AHGCOGFEEEE", + "type": "string", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "AKFBNELAMNA", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "BADIGBCKECA", + "type": "int", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "BNBLOBAEDEE", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "CDINMMPNAID", + "type": "NMJEKMMBDDE", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "CDNFGMHLDMJ", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "FIKEBGGCDFN", + "type": "string", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "GIBHIMGJNNO", + "type": "GPDIAKNEBKH", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "HPLBOMGACED", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "IGOPGMHHLKI", + "type": "string", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "JFEAPMIPNEP", + "type": "DPLPMKMFMPB", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "KHIJAFCHLIA", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "KLNJBBPNMBJ", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "KODBEJPEFOJ", + "type": "string", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "LPJLEMJFBPE", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "MGBDHBHCDMH", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "MIKDLDEALPN", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "OFONEIOEIED", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "OILEJFNPDDB", + "type": "HJPGEGENLPH", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "PEEFHKMOMKK", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + } + ], + "inheritedWireKeys": [ + "Name", + "Description", + "ImageName", + "WarningMask", + "CustomWarning", + "CreatorAccountId", + "State", + "Accessibility", + "SupportsLevelVoting", + "IsRRO", + "SupportsScreens", + "SupportsWalkVR", + "SupportsTeleportVR", + "SupportsVRLow", + "SupportsQuest2", + "SupportsMobile", + "SupportsJuniors", + "CreatedAt", + "Stats" + ] + } + } + ] + }, + { + "family": "playlists", + "route": "playlists/favoritedby/me", + "evidence": [ + { + "type": "EJDCNGBEICB", + "method": "RecRoom.Async.IPromise`1> FAMGMKHMCIN()", + "file": "EJDCNGBEICB.txt", + "line": 2439, + "contract": { + "kind": "array", + "raw": "System.Collections.Generic.List`1", + "rootType": "KMKPEOGJDFK", + "item": { + "kind": "object", + "raw": "KMKPEOGJDFK", + "rootType": "KMKPEOGJDFK" + }, + "typeDescription": { + "name": "KMKPEOGJDFK", + "found": true, + "file": "KMKPEOGJDFK.cs", + "declaration": "public class KMKPEOGJDFK : MKAMHOIHOJK", + "kind": "class", + "baseType": "MKAMHOIHOJK", + "properties": [ + { + "name": "BBGEDJMGFFO", + "type": "long", + "accessibility": "public", + "kind": "property" + } + ], + "fields": [], + "enumValues": [], + "wireKeys": [ + "PlaylistId" + ], + "isilFile": "KMKPEOGJDFK.txt", + "isilType": "KMKPEOGJDFK", + "inherits": "MKAMHOIHOJK", + "inheritedMembers": [ + { + "name": "ACBFDMLHFPB", + "type": "DateTime", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "AHGCOGFEEEE", + "type": "string", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "AKFBNELAMNA", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "BADIGBCKECA", + "type": "int", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "BNBLOBAEDEE", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "CDINMMPNAID", + "type": "NMJEKMMBDDE", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "CDNFGMHLDMJ", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "FIKEBGGCDFN", + "type": "string", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "GIBHIMGJNNO", + "type": "GPDIAKNEBKH", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "HPLBOMGACED", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "IGOPGMHHLKI", + "type": "string", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "JFEAPMIPNEP", + "type": "DPLPMKMFMPB", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "KHIJAFCHLIA", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "KLNJBBPNMBJ", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "KODBEJPEFOJ", + "type": "string", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "LPJLEMJFBPE", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "MGBDHBHCDMH", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "MIKDLDEALPN", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "OFONEIOEIED", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "OILEJFNPDDB", + "type": "HJPGEGENLPH", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "PEEFHKMOMKK", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + } + ], + "inheritedWireKeys": [ + "Name", + "Description", + "ImageName", + "WarningMask", + "CustomWarning", + "CreatorAccountId", + "State", + "Accessibility", + "SupportsLevelVoting", + "IsRRO", + "SupportsScreens", + "SupportsWalkVR", + "SupportsTeleportVR", + "SupportsVRLow", + "SupportsQuest2", + "SupportsMobile", + "SupportsJuniors", + "CreatedAt", + "Stats" + ] + } + } + } + ], + "contracts": [ + { + "kind": "array", + "raw": "System.Collections.Generic.List`1", + "rootType": "KMKPEOGJDFK", + "item": { + "kind": "object", + "raw": "KMKPEOGJDFK", + "rootType": "KMKPEOGJDFK" + }, + "typeDescription": { + "name": "KMKPEOGJDFK", + "found": true, + "file": "KMKPEOGJDFK.cs", + "declaration": "public class KMKPEOGJDFK : MKAMHOIHOJK", + "kind": "class", + "baseType": "MKAMHOIHOJK", + "properties": [ + { + "name": "BBGEDJMGFFO", + "type": "long", + "accessibility": "public", + "kind": "property" + } + ], + "fields": [], + "enumValues": [], + "wireKeys": [ + "PlaylistId" + ], + "isilFile": "KMKPEOGJDFK.txt", + "isilType": "KMKPEOGJDFK", + "inherits": "MKAMHOIHOJK", + "inheritedMembers": [ + { + "name": "ACBFDMLHFPB", + "type": "DateTime", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "AHGCOGFEEEE", + "type": "string", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "AKFBNELAMNA", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "BADIGBCKECA", + "type": "int", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "BNBLOBAEDEE", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "CDINMMPNAID", + "type": "NMJEKMMBDDE", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "CDNFGMHLDMJ", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "FIKEBGGCDFN", + "type": "string", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "GIBHIMGJNNO", + "type": "GPDIAKNEBKH", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "HPLBOMGACED", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "IGOPGMHHLKI", + "type": "string", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "JFEAPMIPNEP", + "type": "DPLPMKMFMPB", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "KHIJAFCHLIA", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "KLNJBBPNMBJ", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "KODBEJPEFOJ", + "type": "string", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "LPJLEMJFBPE", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "MGBDHBHCDMH", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "MIKDLDEALPN", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "OFONEIOEIED", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "OILEJFNPDDB", + "type": "HJPGEGENLPH", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "PEEFHKMOMKK", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + } + ], + "inheritedWireKeys": [ + "Name", + "Description", + "ImageName", + "WarningMask", + "CustomWarning", + "CreatorAccountId", + "State", + "Accessibility", + "SupportsLevelVoting", + "IsRRO", + "SupportsScreens", + "SupportsWalkVR", + "SupportsTeleportVR", + "SupportsVRLow", + "SupportsQuest2", + "SupportsMobile", + "SupportsJuniors", + "CreatedAt", + "Stats" + ] + } + } + ] + }, + { + "family": "playlists", + "route": "playlists/visitedby/me", + "evidence": [ + { + "type": "EJDCNGBEICB", + "method": "RecRoom.Async.IPromise`1> OMMENACPKGH()", + "file": "EJDCNGBEICB.txt", + "line": 2478, + "contract": { + "kind": "array", + "raw": "System.Collections.Generic.List`1", + "rootType": "KMKPEOGJDFK", + "item": { + "kind": "object", + "raw": "KMKPEOGJDFK", + "rootType": "KMKPEOGJDFK" + }, + "typeDescription": { + "name": "KMKPEOGJDFK", + "found": true, + "file": "KMKPEOGJDFK.cs", + "declaration": "public class KMKPEOGJDFK : MKAMHOIHOJK", + "kind": "class", + "baseType": "MKAMHOIHOJK", + "properties": [ + { + "name": "BBGEDJMGFFO", + "type": "long", + "accessibility": "public", + "kind": "property" + } + ], + "fields": [], + "enumValues": [], + "wireKeys": [ + "PlaylistId" + ], + "isilFile": "KMKPEOGJDFK.txt", + "isilType": "KMKPEOGJDFK", + "inherits": "MKAMHOIHOJK", + "inheritedMembers": [ + { + "name": "ACBFDMLHFPB", + "type": "DateTime", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "AHGCOGFEEEE", + "type": "string", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "AKFBNELAMNA", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "BADIGBCKECA", + "type": "int", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "BNBLOBAEDEE", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "CDINMMPNAID", + "type": "NMJEKMMBDDE", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "CDNFGMHLDMJ", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "FIKEBGGCDFN", + "type": "string", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "GIBHIMGJNNO", + "type": "GPDIAKNEBKH", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "HPLBOMGACED", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "IGOPGMHHLKI", + "type": "string", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "JFEAPMIPNEP", + "type": "DPLPMKMFMPB", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "KHIJAFCHLIA", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "KLNJBBPNMBJ", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "KODBEJPEFOJ", + "type": "string", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "LPJLEMJFBPE", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "MGBDHBHCDMH", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "MIKDLDEALPN", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "OFONEIOEIED", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "OILEJFNPDDB", + "type": "HJPGEGENLPH", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "PEEFHKMOMKK", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + } + ], + "inheritedWireKeys": [ + "Name", + "Description", + "ImageName", + "WarningMask", + "CustomWarning", + "CreatorAccountId", + "State", + "Accessibility", + "SupportsLevelVoting", + "IsRRO", + "SupportsScreens", + "SupportsWalkVR", + "SupportsTeleportVR", + "SupportsVRLow", + "SupportsQuest2", + "SupportsMobile", + "SupportsJuniors", + "CreatedAt", + "Stats" + ] + } + } + } + ], + "contracts": [ + { + "kind": "array", + "raw": "System.Collections.Generic.List`1", + "rootType": "KMKPEOGJDFK", + "item": { + "kind": "object", + "raw": "KMKPEOGJDFK", + "rootType": "KMKPEOGJDFK" + }, + "typeDescription": { + "name": "KMKPEOGJDFK", + "found": true, + "file": "KMKPEOGJDFK.cs", + "declaration": "public class KMKPEOGJDFK : MKAMHOIHOJK", + "kind": "class", + "baseType": "MKAMHOIHOJK", + "properties": [ + { + "name": "BBGEDJMGFFO", + "type": "long", + "accessibility": "public", + "kind": "property" + } + ], + "fields": [], + "enumValues": [], + "wireKeys": [ + "PlaylistId" + ], + "isilFile": "KMKPEOGJDFK.txt", + "isilType": "KMKPEOGJDFK", + "inherits": "MKAMHOIHOJK", + "inheritedMembers": [ + { + "name": "ACBFDMLHFPB", + "type": "DateTime", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "AHGCOGFEEEE", + "type": "string", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "AKFBNELAMNA", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "BADIGBCKECA", + "type": "int", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "BNBLOBAEDEE", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "CDINMMPNAID", + "type": "NMJEKMMBDDE", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "CDNFGMHLDMJ", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "FIKEBGGCDFN", + "type": "string", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "GIBHIMGJNNO", + "type": "GPDIAKNEBKH", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "HPLBOMGACED", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "IGOPGMHHLKI", + "type": "string", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "JFEAPMIPNEP", + "type": "DPLPMKMFMPB", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "KHIJAFCHLIA", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "KLNJBBPNMBJ", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "KODBEJPEFOJ", + "type": "string", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "LPJLEMJFBPE", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "MGBDHBHCDMH", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "MIKDLDEALPN", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "OFONEIOEIED", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "OILEJFNPDDB", + "type": "HJPGEGENLPH", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "PEEFHKMOMKK", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + } + ], + "inheritedWireKeys": [ + "Name", + "Description", + "ImageName", + "WarningMask", + "CustomWarning", + "CreatorAccountId", + "State", + "Accessibility", + "SupportsLevelVoting", + "IsRRO", + "SupportsScreens", + "SupportsWalkVR", + "SupportsTeleportVR", + "SupportsVRLow", + "SupportsQuest2", + "SupportsMobile", + "SupportsJuniors", + "CreatedAt", + "Stats" + ] + } + } + ] + }, + { + "family": "playlists", + "route": "roomsandplaylists/hot", + "evidence": [ + { + "type": "EJDCNGBEICB", + "method": "RecRoom.Async.IPromise`1 EBBHGHEGBKG(System.Collections.Generic.IReadOnlyList`1 CAFPJPHILMN)", + "file": "EJDCNGBEICB.txt", + "line": 2645, + "contract": { + "kind": "object", + "raw": "HJKAOMOICJG", + "rootType": "HJKAOMOICJG", + "typeDescription": { + "name": "HJKAOMOICJG", + "found": true, + "file": "HJKAOMOICJG.cs", + "declaration": "public class HJKAOMOICJG : NCONANPODKN, IFAIJAGLDFK", + "kind": "class", + "baseType": "NCONANPODKN", + "properties": [], + "fields": [], + "enumValues": [], + "wireKeys": [ + "TotalResults" + ], + "isilFile": "HJKAOMOICJG.txt", + "isilType": "HJKAOMOICJG", + "inherits": "NCONANPODKN", + "inheritedMembers": [ + { + "name": "KPCBEDOLLFK", + "type": "long", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "NCONANPODKN" + }, + { + "name": "PPLMJPLEHLP", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "NCONANPODKN" + } + ], + "inheritedWireKeys": [] + } + } + } + ], + "contracts": [ + { + "kind": "object", + "raw": "HJKAOMOICJG", + "rootType": "HJKAOMOICJG", + "typeDescription": { + "name": "HJKAOMOICJG", + "found": true, + "file": "HJKAOMOICJG.cs", + "declaration": "public class HJKAOMOICJG : NCONANPODKN, IFAIJAGLDFK", + "kind": "class", + "baseType": "NCONANPODKN", + "properties": [], + "fields": [], + "enumValues": [], + "wireKeys": [ + "TotalResults" + ], + "isilFile": "HJKAOMOICJG.txt", + "isilType": "HJKAOMOICJG", + "inherits": "NCONANPODKN", + "inheritedMembers": [ + { + "name": "KPCBEDOLLFK", + "type": "long", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "NCONANPODKN" + }, + { + "name": "PPLMJPLEHLP", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "NCONANPODKN" + } + ], + "inheritedWireKeys": [] + } + } + ] + }, + { + "family": "playlists", + "route": "roomsandplaylists/search", + "evidence": [ + { + "type": "EJDCNGBEICB", + "method": "RecRoom.Async.IPromise`1 HFHDFLDCIEM(System.String CNBKKCJAHPP)", + "file": "EJDCNGBEICB.txt", + "line": 2560, + "contract": { + "kind": "object", + "raw": "HJKAOMOICJG", + "rootType": "HJKAOMOICJG", + "typeDescription": { + "name": "HJKAOMOICJG", + "found": true, + "file": "HJKAOMOICJG.cs", + "declaration": "public class HJKAOMOICJG : NCONANPODKN, IFAIJAGLDFK", + "kind": "class", + "baseType": "NCONANPODKN", + "properties": [], + "fields": [], + "enumValues": [], + "wireKeys": [ + "TotalResults" + ], + "isilFile": "HJKAOMOICJG.txt", + "isilType": "HJKAOMOICJG", + "inherits": "NCONANPODKN", + "inheritedMembers": [ + { + "name": "KPCBEDOLLFK", + "type": "long", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "NCONANPODKN" + }, + { + "name": "PPLMJPLEHLP", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "NCONANPODKN" + } + ], + "inheritedWireKeys": [] + } + } + } + ], + "contracts": [ + { + "kind": "object", + "raw": "HJKAOMOICJG", + "rootType": "HJKAOMOICJG", + "typeDescription": { + "name": "HJKAOMOICJG", + "found": true, + "file": "HJKAOMOICJG.cs", + "declaration": "public class HJKAOMOICJG : NCONANPODKN, IFAIJAGLDFK", + "kind": "class", + "baseType": "NCONANPODKN", + "properties": [], + "fields": [], + "enumValues": [], + "wireKeys": [ + "TotalResults" + ], + "isilFile": "HJKAOMOICJG.txt", + "isilType": "HJKAOMOICJG", + "inherits": "NCONANPODKN", + "inheritedMembers": [ + { + "name": "KPCBEDOLLFK", + "type": "long", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "NCONANPODKN" + }, + { + "name": "PPLMJPLEHLP", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "NCONANPODKN" + } + ], + "inheritedWireKeys": [] + } + } + ] + }, + { + "family": "quickplay", + "route": "api/quickPlay/", + "evidence": [ + { + "type": "ADOIEPDDEBO", + "method": "RecRoom.Async.IPromise CPGECIJBBKF()", + "file": "ADOIEPDDEBO.txt", + "line": 358, + "contract": { + "kind": "success-or-empty", + "raw": "RecRoom.Async.IPromise", + "rootType": "void/success", + "typeDescription": { + "name": "void/success", + "found": false, + "kind": "object", + "wireKeys": [] + } + } + } + ], + "contracts": [ + { + "kind": "success-or-empty", + "raw": "RecRoom.Async.IPromise", + "rootType": "void/success", + "typeDescription": { + "name": "void/success", + "found": false, + "kind": "object", + "wireKeys": [] + } + } + ] + }, + { + "family": "reporting", + "route": "{0}/api/userreporting", + "evidence": [ + { + "type": "KHGPLGBHIAH", + "method": "System.Void PPOCPFGCALB(LGFHFDMDJKK GIGKODMKKHJ, System.Action`2 DFPGDEOCNIH, System.Action`2 AFLPGGJMPOE)", + "file": "KHGPLGBHIAH.txt", + "line": 2838, + "contract": { + "kind": "unknown", + "raw": "", + "rootType": "unknown" + } + } + ], + "contracts": [ + { + "kind": "unknown", + "raw": "", + "rootType": "unknown", + "typeDescription": { + "name": "unknown", + "found": false, + "kind": "object", + "wireKeys": [] + } + } + ] + }, + { + "family": "reporting", + "route": "api/banappeal/generateCode", + "evidence": [ + { + "type": "LCCEEFHOBEN", + "method": "RecRoom.Async.IPromise`1 LONDNAJPCEE()", + "file": "LCCEEFHOBEN.txt", + "line": 2149, + "contract": { + "kind": "primitive", + "raw": "System.String", + "rootType": "string", + "typeDescription": { + "name": "string", + "found": false, + "kind": "primitive", + "wireKeys": [] + } + } + } + ], + "contracts": [ + { + "kind": "primitive", + "raw": "System.String", + "rootType": "string", + "typeDescription": { + "name": "string", + "found": false, + "kind": "primitive", + "wireKeys": [] + } + } + ] + }, + { + "family": "reporting", + "route": "api/PlayerReporting/", + "evidence": [ + { + "type": "LCCEEFHOBEN", + "method": "RecRoom.Async.IPromise FCPPPBKJFGK(LCCEEFHOBEN+OAODPJFGPAF GEDCEIDOKJL, System.String NGPMADFHHKP, System.Nullable`1 IMHODAEGGON = null)", + "file": "LCCEEFHOBEN.txt", + "line": 5148, + "contract": { + "kind": "success-or-empty", + "raw": "RecRoom.Async.IPromise", + "rootType": "void/success", + "typeDescription": { + "name": "void/success", + "found": false, + "kind": "object", + "wireKeys": [] + } + } + }, + { + "type": "LCCEEFHOBEN", + "method": "RecRoom.Async.IPromise IEAOIIFNOGN(System.String PLDIBPNDJIO)", + "file": "LCCEEFHOBEN.txt", + "line": 4715, + "contract": { + "kind": "success-or-empty", + "raw": "RecRoom.Async.IPromise", + "rootType": "void/success", + "typeDescription": { + "name": "void/success", + "found": false, + "kind": "object", + "wireKeys": [] + } + } + }, + { + "type": "LCCEEFHOBEN", + "method": "RecRoom.Async.IPromise`1 NEFPDKLMDHK()", + "file": "LCCEEFHOBEN.txt", + "line": 895, + "contract": { + "kind": "object", + "raw": "HOGFDJNNMHM", + "rootType": "HOGFDJNNMHM", + "typeDescription": { + "name": "HOGFDJNNMHM", + "found": true, + "file": "HOGFDJNNMHM.cs", + "declaration": "public class HOGFDJNNMHM : IFAIJAGLDFK", + "kind": "class", + "baseType": "IFAIJAGLDFK", + "properties": [ + { + "name": "BKEOCBHCDLM", + "type": "bool", + "accessibility": "public", + "kind": "property" + }, + { + "name": "BPHIOMBNAAI", + "type": "int", + "accessibility": "public", + "kind": "property" + }, + { + "name": "DFPPNHINEFO", + "type": "string", + "accessibility": "public", + "kind": "property" + }, + { + "name": "FNLBJMNDIOB", + "type": "CJFENPHAAHI", + "accessibility": "public", + "kind": "property" + }, + { + "name": "GMJJJKFMFMN", + "type": "long", + "accessibility": "public", + "kind": "property" + }, + { + "name": "JIJDNJNOLLF", + "type": "bool", + "accessibility": "public", + "kind": "property" + }, + { + "name": "KCBFFPAOCMF", + "type": "string", + "accessibility": "public", + "kind": "property" + }, + { + "name": "MCEFCFKMIIH", + "type": "Nullable", + "accessibility": "public", + "kind": "property" + }, + { + "name": "NJCIKENCJFC", + "type": "float", + "accessibility": "public", + "kind": "property" + } + ], + "fields": [], + "enumValues": [], + "wireKeys": [ + "ReportCategory", + "Duration", + "GameSessionId", + "IsHostKick", + "PlayerIdReporter", + "VoteKickReason", + "Message", + "IsBan" + ], + "isilFile": "HOGFDJNNMHM.txt", + "isilType": "HOGFDJNNMHM" + } + } + }, + { + "type": "LCCEEFHOBEN", + "method": "RecRoom.Async.IPromise`1 EKIAMDFOLJM(System.Int64 CJFGEMGOJHB, System.Boolean MAOOLFEBOOD, System.String EMNPHAPBBAH)", + "file": "LCCEEFHOBEN.txt", + "line": 4216, + "contract": { + "kind": "object", + "raw": "KLAMKCBENEA", + "rootType": "KLAMKCBENEA", + "typeDescription": { + "name": "KLAMKCBENEA", + "found": true, + "file": "KLAMKCBENEA.cs", + "declaration": "public class KLAMKCBENEA : IFAIJAGLDFK", + "kind": "class", + "baseType": "IFAIJAGLDFK", + "properties": [ + { + "name": "DFPPNHINEFO", + "type": "string", + "accessibility": "public", + "kind": "property" + }, + { + "name": "ONGEANEKLNE", + "type": "bool", + "accessibility": "public", + "kind": "property" + } + ], + "fields": [], + "enumValues": [], + "wireKeys": [ + "Success", + "Message" + ], + "isilFile": "KLAMKCBENEA.txt", + "isilType": "KLAMKCBENEA" + } + } + }, + { + "type": "LCCEEFHOBEN", + "method": "RecRoom.Async.IPromise`1 FFPIOECGNBI(System.Int32 CJFGEMGOJHB)", + "file": "LCCEEFHOBEN.txt", + "line": 4447, + "contract": { + "kind": "object", + "raw": "KLAMKCBENEA", + "rootType": "KLAMKCBENEA", + "typeDescription": { + "name": "KLAMKCBENEA", + "found": true, + "file": "KLAMKCBENEA.cs", + "declaration": "public class KLAMKCBENEA : IFAIJAGLDFK", + "kind": "class", + "baseType": "IFAIJAGLDFK", + "properties": [ + { + "name": "DFPPNHINEFO", + "type": "string", + "accessibility": "public", + "kind": "property" + }, + { + "name": "ONGEANEKLNE", + "type": "bool", + "accessibility": "public", + "kind": "property" + } + ], + "fields": [], + "enumValues": [], + "wireKeys": [ + "Success", + "Message" + ], + "isilFile": "KLAMKCBENEA.txt", + "isilType": "KLAMKCBENEA" + } + } + }, + { + "type": "LCCEEFHOBEN", + "method": "RecRoom.Async.IPromise`1 HNFAAFGGDML(System.Collections.Generic.List`1 FNNIKBKHAFN)", + "file": "LCCEEFHOBEN.txt", + "line": 4586, + "contract": { + "kind": "object", + "raw": "KLAMKCBENEA", + "rootType": "KLAMKCBENEA", + "typeDescription": { + "name": "KLAMKCBENEA", + "found": true, + "file": "KLAMKCBENEA.cs", + "declaration": "public class KLAMKCBENEA : IFAIJAGLDFK", + "kind": "class", + "baseType": "IFAIJAGLDFK", + "properties": [ + { + "name": "DFPPNHINEFO", + "type": "string", + "accessibility": "public", + "kind": "property" + }, + { + "name": "ONGEANEKLNE", + "type": "bool", + "accessibility": "public", + "kind": "property" + } + ], + "fields": [], + "enumValues": [], + "wireKeys": [ + "Success", + "Message" + ], + "isilFile": "KLAMKCBENEA.txt", + "isilType": "KLAMKCBENEA" + } + } + }, + { + "type": "LCCEEFHOBEN", + "method": "System.Collections.IEnumerator KNAHPBPBPAM(System.Int32 CJFGEMGOJHB, LCCEEFHOBEN+CJFENPHAAHI MEABFEIBEMP, System.String EFDBFLPKHKA, System.Nullable`1 HJMMCCKCDEH, BPHGKAEDBPE+OJDJIJDNFHE AFLPGGJMPOE)", + "file": "LCCEEFHOBEN.txt", + "line": 1783, + "contract": { + "kind": "callback-or-coroutine", + "raw": "IEnumerator", + "rootType": "callback", + "typeDescription": { + "name": "callback", + "found": false, + "kind": "object", + "wireKeys": [] + } + } + } + ], + "contracts": [ + { + "kind": "success-or-empty", + "raw": "RecRoom.Async.IPromise", + "rootType": "void/success", + "typeDescription": { + "name": "void/success", + "found": false, + "kind": "object", + "wireKeys": [] + } + }, + { + "kind": "object", + "raw": "HOGFDJNNMHM", + "rootType": "HOGFDJNNMHM", + "typeDescription": { + "name": "HOGFDJNNMHM", + "found": true, + "file": "HOGFDJNNMHM.cs", + "declaration": "public class HOGFDJNNMHM : IFAIJAGLDFK", + "kind": "class", + "baseType": "IFAIJAGLDFK", + "properties": [ + { + "name": "BKEOCBHCDLM", + "type": "bool", + "accessibility": "public", + "kind": "property" + }, + { + "name": "BPHIOMBNAAI", + "type": "int", + "accessibility": "public", + "kind": "property" + }, + { + "name": "DFPPNHINEFO", + "type": "string", + "accessibility": "public", + "kind": "property" + }, + { + "name": "FNLBJMNDIOB", + "type": "CJFENPHAAHI", + "accessibility": "public", + "kind": "property" + }, + { + "name": "GMJJJKFMFMN", + "type": "long", + "accessibility": "public", + "kind": "property" + }, + { + "name": "JIJDNJNOLLF", + "type": "bool", + "accessibility": "public", + "kind": "property" + }, + { + "name": "KCBFFPAOCMF", + "type": "string", + "accessibility": "public", + "kind": "property" + }, + { + "name": "MCEFCFKMIIH", + "type": "Nullable", + "accessibility": "public", + "kind": "property" + }, + { + "name": "NJCIKENCJFC", + "type": "float", + "accessibility": "public", + "kind": "property" + } + ], + "fields": [], + "enumValues": [], + "wireKeys": [ + "ReportCategory", + "Duration", + "GameSessionId", + "IsHostKick", + "PlayerIdReporter", + "VoteKickReason", + "Message", + "IsBan" + ], + "isilFile": "HOGFDJNNMHM.txt", + "isilType": "HOGFDJNNMHM" + } + }, + { + "kind": "object", + "raw": "KLAMKCBENEA", + "rootType": "KLAMKCBENEA", + "typeDescription": { + "name": "KLAMKCBENEA", + "found": true, + "file": "KLAMKCBENEA.cs", + "declaration": "public class KLAMKCBENEA : IFAIJAGLDFK", + "kind": "class", + "baseType": "IFAIJAGLDFK", + "properties": [ + { + "name": "DFPPNHINEFO", + "type": "string", + "accessibility": "public", + "kind": "property" + }, + { + "name": "ONGEANEKLNE", + "type": "bool", + "accessibility": "public", + "kind": "property" + } + ], + "fields": [], + "enumValues": [], + "wireKeys": [ + "Success", + "Message" + ], + "isilFile": "KLAMKCBENEA.txt", + "isilType": "KLAMKCBENEA" + } + }, + { + "kind": "callback-or-coroutine", + "raw": "IEnumerator", + "rootType": "callback", + "typeDescription": { + "name": "callback", + "found": false, + "kind": "object", + "wireKeys": [] + } + } + ] + }, + { + "family": "reporting", + "route": "api/PlayerReporting/v1/voteToKickReasons", + "evidence": [ + { + "type": "LCCEEFHOBEN", + "method": "RecRoom.Async.IPromise HHBLGNHOGLN()", + "file": "LCCEEFHOBEN.txt", + "line": 2417, + "contract": { + "kind": "success-or-empty", + "raw": "RecRoom.Async.IPromise", + "rootType": "void/success", + "typeDescription": { + "name": "void/success", + "found": false, + "kind": "object", + "wireKeys": [] + } + } + } + ], + "contracts": [ + { + "kind": "success-or-empty", + "raw": "RecRoom.Async.IPromise", + "rootType": "void/success", + "typeDescription": { + "name": "void/success", + "found": false, + "kind": "object", + "wireKeys": [] + } + } + ] + }, + { + "family": "reporting", + "route": "https://userreporting.cloud.unity3d.com/api/userreporting/projects/{0}/ping", + "evidence": [ + { + "type": "UserReportingScript", + "method": "System.Void Start()", + "file": "UserReportingScript.txt", + "line": 1174, + "contract": { + "kind": "unknown", + "raw": "", + "rootType": "unknown" + } + } + ], + "contracts": [ + { + "kind": "unknown", + "raw": "", + "rootType": "unknown", + "typeDescription": { + "name": "unknown", + "found": false, + "kind": "object", + "wireKeys": [] + } + } + ] + }, + { + "family": "room-keys", + "route": "api/roomkeys/", + "evidence": [ + { + "type": "IOIBJBLIBKM", + "method": "RecRoom.Async.IPromise`1 ACCJPCMPJMH(System.Int64 BGICHOOBKLD)", + "file": "IOIBJBLIBKM.txt", + "line": 1299, + "contract": { + "kind": "object", + "raw": "BMHHFIGBOFD", + "rootType": "BMHHFIGBOFD", + "typeDescription": { + "name": "BMHHFIGBOFD", + "found": true, + "file": "BMHHFIGBOFD.cs", + "declaration": "public enum BMHHFIGBOFD", + "kind": "enum", + "baseType": null, + "properties": [], + "fields": [], + "enumValues": [ + { + "name": "Success", + "value": "0" + }, + { + "name": "InvalidParameters", + "value": "1" + }, + { + "name": "DoesNotExist", + "value": "2" + }, + { + "name": "NameTooShort", + "value": "3" + }, + { + "name": "NameTooLong", + "value": "4" + }, + { + "name": "DuplicateName", + "value": "5" + }, + { + "name": "InappropriateName", + "value": "6" + }, + { + "name": "DescriptionTooShort", + "value": "7" + }, + { + "name": "DescriptionTooLong", + "value": "8" + }, + { + "name": "InappropriateDescription", + "value": "9" + }, + { + "name": "PriceTooLow", + "value": "10" + }, + { + "name": "PriceTooHigh", + "value": "11" + }, + { + "name": "PermissionDenied", + "value": "12" + }, + { + "name": "PlayerHasRoomUnderModerationReview", + "value": "13" + }, + { + "name": "JuniorStatusFail", + "value": "14" + }, + { + "name": "PlayerIsNotCoOwner", + "value": "15" + }, + { + "name": "RoomKeyLimitReached", + "value": "16" + }, + { + "name": "PlayerAlreadyOwns", + "value": "17" + }, + { + "name": "RoomUnderModerationReview", + "value": "18" + }, + { + "name": "PurchaseFailed", + "value": "19" + }, + { + "name": "RoomDoesNotExist", + "value": "20" + }, + { + "name": "PaidKeyPurchasingDisabled", + "value": "21" + }, + { + "name": "CreateOrModifyKeysDisabled", + "value": "22" + }, + { + "name": "RoomKeyUnderModerationReview", + "value": "23" + }, + { + "name": "PlayerRestrictedFromP2PSelling", + "value": "24" + }, + { + "name": "PlayerNotRecRoomPlusMember", + "value": "25" + } + ], + "wireKeys": [] + } + } + }, + { + "type": "IOIBJBLIBKM", + "method": "RecRoom.Async.IPromise`1 HECNNKLKKAG(System.Int64 BGICHOOBKLD)", + "file": "IOIBJBLIBKM.txt", + "line": 1517, + "contract": { + "kind": "primitive", + "raw": "System.Boolean", + "rootType": "boolean", + "typeDescription": { + "name": "boolean", + "found": false, + "kind": "primitive", + "wireKeys": [] + } + } + }, + { + "type": "IOIBJBLIBKM", + "method": "RecRoom.Async.IPromise`1 LAPDFFOJCNP(System.Int32 CJFGEMGOJHB, AMAGKLLBGEC AAHIIDDIBFD)", + "file": "IOIBJBLIBKM.txt", + "line": 1866, + "contract": { + "kind": "primitive", + "raw": "System.Boolean", + "rootType": "boolean", + "typeDescription": { + "name": "boolean", + "found": false, + "kind": "primitive", + "wireKeys": [] + } + } + }, + { + "type": "IOIBJBLIBKM", + "method": "System.String JGLHIEEFHKE()", + "file": "IOIBJBLIBKM.txt", + "line": 58, + "contract": { + "kind": "unknown", + "raw": "", + "rootType": "unknown" + } + } + ], + "contracts": [ + { + "kind": "object", + "raw": "BMHHFIGBOFD", + "rootType": "BMHHFIGBOFD", + "typeDescription": { + "name": "BMHHFIGBOFD", + "found": true, + "file": "BMHHFIGBOFD.cs", + "declaration": "public enum BMHHFIGBOFD", + "kind": "enum", + "baseType": null, + "properties": [], + "fields": [], + "enumValues": [ + { + "name": "Success", + "value": "0" + }, + { + "name": "InvalidParameters", + "value": "1" + }, + { + "name": "DoesNotExist", + "value": "2" + }, + { + "name": "NameTooShort", + "value": "3" + }, + { + "name": "NameTooLong", + "value": "4" + }, + { + "name": "DuplicateName", + "value": "5" + }, + { + "name": "InappropriateName", + "value": "6" + }, + { + "name": "DescriptionTooShort", + "value": "7" + }, + { + "name": "DescriptionTooLong", + "value": "8" + }, + { + "name": "InappropriateDescription", + "value": "9" + }, + { + "name": "PriceTooLow", + "value": "10" + }, + { + "name": "PriceTooHigh", + "value": "11" + }, + { + "name": "PermissionDenied", + "value": "12" + }, + { + "name": "PlayerHasRoomUnderModerationReview", + "value": "13" + }, + { + "name": "JuniorStatusFail", + "value": "14" + }, + { + "name": "PlayerIsNotCoOwner", + "value": "15" + }, + { + "name": "RoomKeyLimitReached", + "value": "16" + }, + { + "name": "PlayerAlreadyOwns", + "value": "17" + }, + { + "name": "RoomUnderModerationReview", + "value": "18" + }, + { + "name": "PurchaseFailed", + "value": "19" + }, + { + "name": "RoomDoesNotExist", + "value": "20" + }, + { + "name": "PaidKeyPurchasingDisabled", + "value": "21" + }, + { + "name": "CreateOrModifyKeysDisabled", + "value": "22" + }, + { + "name": "RoomKeyUnderModerationReview", + "value": "23" + }, + { + "name": "PlayerRestrictedFromP2PSelling", + "value": "24" + }, + { + "name": "PlayerNotRecRoomPlusMember", + "value": "25" + } + ], + "wireKeys": [] + } + }, + { + "kind": "primitive", + "raw": "System.Boolean", + "rootType": "boolean", + "typeDescription": { + "name": "boolean", + "found": false, + "kind": "primitive", + "wireKeys": [] + } + }, + { + "kind": "unknown", + "raw": "", + "rootType": "unknown", + "typeDescription": { + "name": "unknown", + "found": false, + "kind": "object", + "wireKeys": [] + } + } + ] + }, + { + "family": "room-keys", + "route": "api/roomkeys/v1/create", + "evidence": [ + { + "type": "IOIBJBLIBKM", + "method": "RecRoom.Async.IPromise`1 BIAKPHAECJC(System.String MMBOKOLAJFH, System.String LJIGOCDPEJF, System.Int32 MACNIENMFHJ)", + "file": "IOIBJBLIBKM.txt", + "line": 573, + "contract": { + "kind": "object", + "raw": "BCKIBFNPIPD", + "rootType": "BCKIBFNPIPD", + "typeDescription": { + "name": "BCKIBFNPIPD", + "found": true, + "file": "BCKIBFNPIPD.cs", + "declaration": "public class BCKIBFNPIPD : IFAIJAGLDFK", + "kind": "class", + "baseType": "IFAIJAGLDFK", + "properties": [ + { + "name": "HIMCGOCKLLK", + "type": "BMHHFIGBOFD", + "accessibility": "public", + "kind": "property" + }, + { + "name": "PIJLKMENAPG", + "type": "AMAGKLLBGEC", + "accessibility": "public", + "kind": "property" + } + ], + "fields": [], + "enumValues": [], + "wireKeys": [ + "Status", + "RoomKey" + ], + "isilFile": "BCKIBFNPIPD.txt", + "isilType": "BCKIBFNPIPD" + } + } + } + ], + "contracts": [ + { + "kind": "object", + "raw": "BCKIBFNPIPD", + "rootType": "BCKIBFNPIPD", + "typeDescription": { + "name": "BCKIBFNPIPD", + "found": true, + "file": "BCKIBFNPIPD.cs", + "declaration": "public class BCKIBFNPIPD : IFAIJAGLDFK", + "kind": "class", + "baseType": "IFAIJAGLDFK", + "properties": [ + { + "name": "HIMCGOCKLLK", + "type": "BMHHFIGBOFD", + "accessibility": "public", + "kind": "property" + }, + { + "name": "PIJLKMENAPG", + "type": "AMAGKLLBGEC", + "accessibility": "public", + "kind": "property" + } + ], + "fields": [], + "enumValues": [], + "wireKeys": [ + "Status", + "RoomKey" + ], + "isilFile": "BCKIBFNPIPD.txt", + "isilType": "BCKIBFNPIPD" + } + } + ] + }, + { + "family": "room-keys", + "route": "api/roomkeys/v1/mine", + "evidence": [ + { + "type": "IOIBJBLIBKM", + "method": "RecRoom.Async.IPromise`1> IGFLECMACBE()", + "file": "IOIBJBLIBKM.txt", + "line": 1704, + "contract": { + "kind": "array", + "raw": "System.Collections.Generic.List`1", + "rootType": "AMAGKLLBGEC", + "item": { + "kind": "object", + "raw": "AMAGKLLBGEC", + "rootType": "AMAGKLLBGEC" + }, + "typeDescription": { + "name": "AMAGKLLBGEC", + "found": true, + "file": "AMAGKLLBGEC.cs", + "declaration": "public class AMAGKLLBGEC : IFAIJAGLDFK", + "kind": "class", + "baseType": "IFAIJAGLDFK", + "properties": [ + { + "name": "DADOKMAOFJL", + "type": "long", + "accessibility": "public", + "kind": "property" + }, + { + "name": "FIKEBGGCDFN", + "type": "string", + "accessibility": "public", + "kind": "property" + }, + { + "name": "HKCFEOMMOOL", + "type": "long", + "accessibility": "public", + "kind": "property" + }, + { + "name": "KODBEJPEFOJ", + "type": "string", + "accessibility": "public", + "kind": "property" + }, + { + "name": "MLDAFKCENMA", + "type": "Guid", + "accessibility": "public", + "kind": "property" + }, + { + "name": "NPDJLOOHMDJ", + "type": "int", + "accessibility": "public", + "kind": "property" + } + ], + "fields": [], + "enumValues": [], + "wireKeys": [ + "RoomKeyId", + "ReplicationId", + "RoomId", + "Name", + "Description", + "Price" + ], + "isilFile": "AMAGKLLBGEC.txt", + "isilType": "AMAGKLLBGEC" + } + } + } + ], + "contracts": [ + { + "kind": "array", + "raw": "System.Collections.Generic.List`1", + "rootType": "AMAGKLLBGEC", + "item": { + "kind": "object", + "raw": "AMAGKLLBGEC", + "rootType": "AMAGKLLBGEC" + }, + "typeDescription": { + "name": "AMAGKLLBGEC", + "found": true, + "file": "AMAGKLLBGEC.cs", + "declaration": "public class AMAGKLLBGEC : IFAIJAGLDFK", + "kind": "class", + "baseType": "IFAIJAGLDFK", + "properties": [ + { + "name": "DADOKMAOFJL", + "type": "long", + "accessibility": "public", + "kind": "property" + }, + { + "name": "FIKEBGGCDFN", + "type": "string", + "accessibility": "public", + "kind": "property" + }, + { + "name": "HKCFEOMMOOL", + "type": "long", + "accessibility": "public", + "kind": "property" + }, + { + "name": "KODBEJPEFOJ", + "type": "string", + "accessibility": "public", + "kind": "property" + }, + { + "name": "MLDAFKCENMA", + "type": "Guid", + "accessibility": "public", + "kind": "property" + }, + { + "name": "NPDJLOOHMDJ", + "type": "int", + "accessibility": "public", + "kind": "property" + } + ], + "fields": [], + "enumValues": [], + "wireKeys": [ + "RoomKeyId", + "ReplicationId", + "RoomId", + "Name", + "Description", + "Price" + ], + "isilFile": "AMAGKLLBGEC.txt", + "isilType": "AMAGKLLBGEC" + } + } + ] + }, + { + "family": "room-keys", + "route": "api/roomkeys/v1/update", + "evidence": [ + { + "type": "IOIBJBLIBKM", + "method": "RecRoom.Async.IPromise`1 MLFKLGGPLNM(System.Int64 BGICHOOBKLD, System.String MMBOKOLAJFH = null, System.String LJIGOCDPEJF = null, System.Nullable`1 MACNIENMFHJ = null)", + "file": "IOIBJBLIBKM.txt", + "line": 1008, + "contract": { + "kind": "object", + "raw": "BCKIBFNPIPD", + "rootType": "BCKIBFNPIPD", + "typeDescription": { + "name": "BCKIBFNPIPD", + "found": true, + "file": "BCKIBFNPIPD.cs", + "declaration": "public class BCKIBFNPIPD : IFAIJAGLDFK", + "kind": "class", + "baseType": "IFAIJAGLDFK", + "properties": [ + { + "name": "HIMCGOCKLLK", + "type": "BMHHFIGBOFD", + "accessibility": "public", + "kind": "property" + }, + { + "name": "PIJLKMENAPG", + "type": "AMAGKLLBGEC", + "accessibility": "public", + "kind": "property" + } + ], + "fields": [], + "enumValues": [], + "wireKeys": [ + "Status", + "RoomKey" + ], + "isilFile": "BCKIBFNPIPD.txt", + "isilType": "BCKIBFNPIPD" + } + } + } + ], + "contracts": [ + { + "kind": "object", + "raw": "BCKIBFNPIPD", + "rootType": "BCKIBFNPIPD", + "typeDescription": { + "name": "BCKIBFNPIPD", + "found": true, + "file": "BCKIBFNPIPD.cs", + "declaration": "public class BCKIBFNPIPD : IFAIJAGLDFK", + "kind": "class", + "baseType": "IFAIJAGLDFK", + "properties": [ + { + "name": "HIMCGOCKLLK", + "type": "BMHHFIGBOFD", + "accessibility": "public", + "kind": "property" + }, + { + "name": "PIJLKMENAPG", + "type": "AMAGKLLBGEC", + "accessibility": "public", + "kind": "property" + } + ], + "fields": [], + "enumValues": [], + "wireKeys": [ + "Status", + "RoomKey" + ], + "isilFile": "BCKIBFNPIPD.txt", + "isilType": "BCKIBFNPIPD" + } + } + ] + }, + { + "family": "rooms", + "route": "api/rooms/v1/filters", + "evidence": [ + { + "type": "OJMCBOKJFOF+<>c", + "method": "RecRoom.Async.IPromise`1 b__114_0()", + "file": "OJMCBOKJFOF_NestedType___c.txt", + "line": 1617, + "contract": { + "kind": "object", + "raw": "AEBEPCMAABC", + "rootType": "AEBEPCMAABC", + "typeDescription": { + "name": "AEBEPCMAABC", + "found": true, + "file": "AEBEPCMAABC.cs", + "declaration": "public class AEBEPCMAABC : IFAIJAGLDFK", + "kind": "class", + "baseType": "IFAIJAGLDFK", + "properties": [ + { + "name": "EBIKIFGHJBN", + "type": "List", + "accessibility": "public", + "kind": "property" + }, + { + "name": "JGPAPFCFDJG", + "type": "List", + "accessibility": "public", + "kind": "property" + } + ], + "fields": [], + "enumValues": [], + "wireKeys": [], + "isilFile": "AEBEPCMAABC.txt", + "isilType": "AEBEPCMAABC" + } + } + } + ], + "contracts": [ + { + "kind": "object", + "raw": "AEBEPCMAABC", + "rootType": "AEBEPCMAABC", + "typeDescription": { + "name": "AEBEPCMAABC", + "found": true, + "file": "AEBEPCMAABC.cs", + "declaration": "public class AEBEPCMAABC : IFAIJAGLDFK", + "kind": "class", + "baseType": "IFAIJAGLDFK", + "properties": [ + { + "name": "EBIKIFGHJBN", + "type": "List", + "accessibility": "public", + "kind": "property" + }, + { + "name": "JGPAPFCFDJG", + "type": "List", + "accessibility": "public", + "kind": "property" + } + ], + "fields": [], + "enumValues": [], + "wireKeys": [], + "isilFile": "AEBEPCMAABC.txt", + "isilType": "AEBEPCMAABC" + } + } + ] + }, + { + "family": "rooms", + "route": "api/rooms/v1/verifyRole", + "evidence": [ + { + "type": "OJMCBOKJFOF", + "method": "RecRoom.Async.IPromise INDDAAGNMHF(System.String LHOMKMINCHH)", + "file": "OJMCBOKJFOF.txt", + "line": 12963, + "contract": { + "kind": "success-or-empty", + "raw": "RecRoom.Async.IPromise", + "rootType": "void/success", + "typeDescription": { + "name": "void/success", + "found": false, + "kind": "object", + "wireKeys": [] + } + } + } + ], + "contracts": [ + { + "kind": "success-or-empty", + "raw": "RecRoom.Async.IPromise", + "rootType": "void/success", + "typeDescription": { + "name": "void/success", + "found": false, + "kind": "object", + "wireKeys": [] + } + } + ] + }, + { + "family": "rooms", + "route": "api/rooms/v2/report", + "evidence": [ + { + "type": "OJMCBOKJFOF", + "method": "RecRoom.Async.IPromise GECDGBACBFP(System.Int64 HNHLJONGKHB, LCCEEFHOBEN+CJFENPHAAHI MEABFEIBEMP, System.String EFDBFLPKHKA)", + "file": "OJMCBOKJFOF.txt", + "line": 12627, + "contract": { + "kind": "success-or-empty", + "raw": "RecRoom.Async.IPromise", + "rootType": "void/success", + "typeDescription": { + "name": "void/success", + "found": false, + "kind": "object", + "wireKeys": [] + } + } + } + ], + "contracts": [ + { + "kind": "success-or-empty", + "raw": "RecRoom.Async.IPromise", + "rootType": "void/success", + "typeDescription": { + "name": "void/success", + "found": false, + "kind": "object", + "wireKeys": [] + } + } + ] + }, + { + "family": "rooms", + "route": "hot_rooms/", + "evidence": [ + { + "type": "OJMCBOKJFOF", + "method": "RecRoom.Async.IPromise`1> DLPLPKCNLNA(System.Collections.Generic.IReadOnlyList`1 CAFPJPHILMN)", + "file": "OJMCBOKJFOF.txt", + "line": 3238, + "contract": { + "kind": "array", + "raw": "System.Collections.Generic.IReadOnlyList`1", + "rootType": "KLCOGEIGEBJ", + "item": { + "kind": "object", + "raw": "KLCOGEIGEBJ", + "rootType": "KLCOGEIGEBJ" + }, + "typeDescription": { + "name": "KLCOGEIGEBJ", + "found": true, + "file": "KLCOGEIGEBJ.cs", + "declaration": "public class KLCOGEIGEBJ : MKAMHOIHOJK", + "kind": "class", + "baseType": "MKAMHOIHOJK", + "properties": [ + { + "name": "DADOKMAOFJL", + "type": "long", + "accessibility": "public", + "kind": "property" + }, + { + "name": "GJPGKJJCPBK", + "type": "bool", + "accessibility": "public", + "kind": "property" + }, + { + "name": "IBLOJJBEKFF", + "type": "bool", + "accessibility": "public", + "kind": "property" + }, + { + "name": "JCLCOCEOAEP", + "type": "bool", + "accessibility": "public", + "kind": "property" + }, + { + "name": "JGJCBGEBLLO", + "type": "bool", + "accessibility": "public", + "kind": "property" + }, + { + "name": "PLFEEIJCHOH", + "type": "bool", + "accessibility": "public", + "kind": "property" + } + ], + "fields": [ + { + "name": "BJFNPKFOALK", + "type": "long", + "kind": "field" + } + ], + "enumValues": [], + "wireKeys": [ + "RoomId", + "IsDorm", + "CloningAllowed", + "DisableMicAutoMute", + "DisableRoomComments", + "EncryptVoiceChat" + ], + "isilFile": "KLCOGEIGEBJ.txt", + "isilType": "KLCOGEIGEBJ", + "inherits": "MKAMHOIHOJK", + "inheritedMembers": [ + { + "name": "ACBFDMLHFPB", + "type": "DateTime", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "AHGCOGFEEEE", + "type": "string", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "AKFBNELAMNA", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "BADIGBCKECA", + "type": "int", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "BNBLOBAEDEE", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "CDINMMPNAID", + "type": "NMJEKMMBDDE", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "CDNFGMHLDMJ", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "FIKEBGGCDFN", + "type": "string", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "GIBHIMGJNNO", + "type": "GPDIAKNEBKH", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "HPLBOMGACED", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "IGOPGMHHLKI", + "type": "string", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "JFEAPMIPNEP", + "type": "DPLPMKMFMPB", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "KHIJAFCHLIA", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "KLNJBBPNMBJ", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "KODBEJPEFOJ", + "type": "string", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "LPJLEMJFBPE", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "MGBDHBHCDMH", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "MIKDLDEALPN", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "OFONEIOEIED", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "OILEJFNPDDB", + "type": "HJPGEGENLPH", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "PEEFHKMOMKK", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + } + ], + "inheritedWireKeys": [ + "Name", + "Description", + "ImageName", + "WarningMask", + "CustomWarning", + "CreatorAccountId", + "State", + "Accessibility", + "SupportsLevelVoting", + "IsRRO", + "SupportsScreens", + "SupportsWalkVR", + "SupportsTeleportVR", + "SupportsVRLow", + "SupportsQuest2", + "SupportsMobile", + "SupportsJuniors", + "CreatedAt", + "Stats" + ] + } + } + }, + { + "type": "OJMCBOKJFOF+PEBCIEAPGON", + "method": "System.String CMOIGEDECGM(System.Collections.Generic.IEnumerable`1 CAFPJPHILMN)", + "file": "OJMCBOKJFOF_NestedType_PEBCIEAPGON.txt", + "line": 39, + "contract": { + "kind": "unknown", + "raw": "", + "rootType": "unknown" + } + } + ], + "contracts": [ + { + "kind": "array", + "raw": "System.Collections.Generic.IReadOnlyList`1", + "rootType": "KLCOGEIGEBJ", + "item": { + "kind": "object", + "raw": "KLCOGEIGEBJ", + "rootType": "KLCOGEIGEBJ" + }, + "typeDescription": { + "name": "KLCOGEIGEBJ", + "found": true, + "file": "KLCOGEIGEBJ.cs", + "declaration": "public class KLCOGEIGEBJ : MKAMHOIHOJK", + "kind": "class", + "baseType": "MKAMHOIHOJK", + "properties": [ + { + "name": "DADOKMAOFJL", + "type": "long", + "accessibility": "public", + "kind": "property" + }, + { + "name": "GJPGKJJCPBK", + "type": "bool", + "accessibility": "public", + "kind": "property" + }, + { + "name": "IBLOJJBEKFF", + "type": "bool", + "accessibility": "public", + "kind": "property" + }, + { + "name": "JCLCOCEOAEP", + "type": "bool", + "accessibility": "public", + "kind": "property" + }, + { + "name": "JGJCBGEBLLO", + "type": "bool", + "accessibility": "public", + "kind": "property" + }, + { + "name": "PLFEEIJCHOH", + "type": "bool", + "accessibility": "public", + "kind": "property" + } + ], + "fields": [ + { + "name": "BJFNPKFOALK", + "type": "long", + "kind": "field" + } + ], + "enumValues": [], + "wireKeys": [ + "RoomId", + "IsDorm", + "CloningAllowed", + "DisableMicAutoMute", + "DisableRoomComments", + "EncryptVoiceChat" + ], + "isilFile": "KLCOGEIGEBJ.txt", + "isilType": "KLCOGEIGEBJ", + "inherits": "MKAMHOIHOJK", + "inheritedMembers": [ + { + "name": "ACBFDMLHFPB", + "type": "DateTime", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "AHGCOGFEEEE", + "type": "string", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "AKFBNELAMNA", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "BADIGBCKECA", + "type": "int", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "BNBLOBAEDEE", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "CDINMMPNAID", + "type": "NMJEKMMBDDE", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "CDNFGMHLDMJ", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "FIKEBGGCDFN", + "type": "string", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "GIBHIMGJNNO", + "type": "GPDIAKNEBKH", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "HPLBOMGACED", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "IGOPGMHHLKI", + "type": "string", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "JFEAPMIPNEP", + "type": "DPLPMKMFMPB", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "KHIJAFCHLIA", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "KLNJBBPNMBJ", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "KODBEJPEFOJ", + "type": "string", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "LPJLEMJFBPE", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "MGBDHBHCDMH", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "MIKDLDEALPN", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "OFONEIOEIED", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "OILEJFNPDDB", + "type": "HJPGEGENLPH", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "PEEFHKMOMKK", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + } + ], + "inheritedWireKeys": [ + "Name", + "Description", + "ImageName", + "WarningMask", + "CustomWarning", + "CreatorAccountId", + "State", + "Accessibility", + "SupportsLevelVoting", + "IsRRO", + "SupportsScreens", + "SupportsWalkVR", + "SupportsTeleportVR", + "SupportsVRLow", + "SupportsQuest2", + "SupportsMobile", + "SupportsJuniors", + "CreatedAt", + "Stats" + ] + } + }, + { + "kind": "unknown", + "raw": "", + "rootType": "unknown", + "typeDescription": { + "name": "unknown", + "found": false, + "kind": "object", + "wireKeys": [] + } + } + ] + }, + { + "family": "rooms", + "route": "hot_roomsandplaylists/", + "evidence": [ + { + "type": "OJMCBOKJFOF", + "method": "RecRoom.Async.IPromise`1> EBBHGHEGBKG(System.Collections.Generic.IReadOnlyList`1 CAFPJPHILMN)", + "file": "OJMCBOKJFOF.txt", + "line": 4743, + "contract": { + "kind": "array", + "raw": "System.Collections.Generic.IReadOnlyList`1", + "rootType": "MKAMHOIHOJK", + "item": { + "kind": "object", + "raw": "MKAMHOIHOJK", + "rootType": "MKAMHOIHOJK" + }, + "typeDescription": { + "name": "MKAMHOIHOJK", + "found": true, + "file": "MKAMHOIHOJK.cs", + "declaration": "public abstract class MKAMHOIHOJK : IFAIJAGLDFK, AKJKEMONOIL", + "kind": "class", + "baseType": "IFAIJAGLDFK", + "properties": [ + { + "name": "ACBFDMLHFPB", + "type": "DateTime", + "accessibility": "public", + "kind": "property" + }, + { + "name": "AHGCOGFEEEE", + "type": "string", + "accessibility": "public", + "kind": "property" + }, + { + "name": "AKFBNELAMNA", + "type": "bool", + "accessibility": "public", + "kind": "property" + }, + { + "name": "BADIGBCKECA", + "type": "int", + "accessibility": "public", + "kind": "property" + }, + { + "name": "BNBLOBAEDEE", + "type": "bool", + "accessibility": "public", + "kind": "property" + }, + { + "name": "CDINMMPNAID", + "type": "NMJEKMMBDDE", + "accessibility": "public", + "kind": "property" + }, + { + "name": "CDNFGMHLDMJ", + "type": "bool", + "accessibility": "public", + "kind": "property" + }, + { + "name": "FIKEBGGCDFN", + "type": "string", + "accessibility": "public", + "kind": "property" + }, + { + "name": "GIBHIMGJNNO", + "type": "GPDIAKNEBKH", + "accessibility": "public", + "kind": "property" + }, + { + "name": "HPLBOMGACED", + "type": "bool", + "accessibility": "public", + "kind": "property" + }, + { + "name": "IGOPGMHHLKI", + "type": "string", + "accessibility": "public", + "kind": "property" + }, + { + "name": "JFEAPMIPNEP", + "type": "DPLPMKMFMPB", + "accessibility": "public", + "kind": "property" + }, + { + "name": "KHIJAFCHLIA", + "type": "bool", + "accessibility": "public", + "kind": "property" + }, + { + "name": "KLNJBBPNMBJ", + "type": "bool", + "accessibility": "public", + "kind": "property" + }, + { + "name": "KODBEJPEFOJ", + "type": "string", + "accessibility": "public", + "kind": "property" + }, + { + "name": "LPJLEMJFBPE", + "type": "bool", + "accessibility": "public", + "kind": "property" + }, + { + "name": "MGBDHBHCDMH", + "type": "bool", + "accessibility": "public", + "kind": "property" + }, + { + "name": "MIKDLDEALPN", + "type": "bool", + "accessibility": "public", + "kind": "property" + }, + { + "name": "OFONEIOEIED", + "type": "bool", + "accessibility": "public", + "kind": "property" + }, + { + "name": "OILEJFNPDDB", + "type": "HJPGEGENLPH", + "accessibility": "public", + "kind": "property" + }, + { + "name": "PEEFHKMOMKK", + "type": "bool", + "accessibility": "public", + "kind": "property" + } + ], + "fields": [], + "enumValues": [], + "wireKeys": [ + "Name", + "Description", + "ImageName", + "WarningMask", + "CustomWarning", + "CreatorAccountId", + "State", + "Accessibility", + "SupportsLevelVoting", + "IsRRO", + "SupportsScreens", + "SupportsWalkVR", + "SupportsTeleportVR", + "SupportsVRLow", + "SupportsQuest2", + "SupportsMobile", + "SupportsJuniors", + "CreatedAt", + "Stats" + ], + "isilFile": "MKAMHOIHOJK.txt", + "isilType": "MKAMHOIHOJK" + } + } + }, + { + "type": "OJMCBOKJFOF+PEBCIEAPGON", + "method": "System.String MMCLGMMDDJF(System.Collections.Generic.IEnumerable`1 CAFPJPHILMN)", + "file": "OJMCBOKJFOF_NestedType_PEBCIEAPGON.txt", + "line": 121, + "contract": { + "kind": "unknown", + "raw": "", + "rootType": "unknown" + } + } + ], + "contracts": [ + { + "kind": "array", + "raw": "System.Collections.Generic.IReadOnlyList`1", + "rootType": "MKAMHOIHOJK", + "item": { + "kind": "object", + "raw": "MKAMHOIHOJK", + "rootType": "MKAMHOIHOJK" + }, + "typeDescription": { + "name": "MKAMHOIHOJK", + "found": true, + "file": "MKAMHOIHOJK.cs", + "declaration": "public abstract class MKAMHOIHOJK : IFAIJAGLDFK, AKJKEMONOIL", + "kind": "class", + "baseType": "IFAIJAGLDFK", + "properties": [ + { + "name": "ACBFDMLHFPB", + "type": "DateTime", + "accessibility": "public", + "kind": "property" + }, + { + "name": "AHGCOGFEEEE", + "type": "string", + "accessibility": "public", + "kind": "property" + }, + { + "name": "AKFBNELAMNA", + "type": "bool", + "accessibility": "public", + "kind": "property" + }, + { + "name": "BADIGBCKECA", + "type": "int", + "accessibility": "public", + "kind": "property" + }, + { + "name": "BNBLOBAEDEE", + "type": "bool", + "accessibility": "public", + "kind": "property" + }, + { + "name": "CDINMMPNAID", + "type": "NMJEKMMBDDE", + "accessibility": "public", + "kind": "property" + }, + { + "name": "CDNFGMHLDMJ", + "type": "bool", + "accessibility": "public", + "kind": "property" + }, + { + "name": "FIKEBGGCDFN", + "type": "string", + "accessibility": "public", + "kind": "property" + }, + { + "name": "GIBHIMGJNNO", + "type": "GPDIAKNEBKH", + "accessibility": "public", + "kind": "property" + }, + { + "name": "HPLBOMGACED", + "type": "bool", + "accessibility": "public", + "kind": "property" + }, + { + "name": "IGOPGMHHLKI", + "type": "string", + "accessibility": "public", + "kind": "property" + }, + { + "name": "JFEAPMIPNEP", + "type": "DPLPMKMFMPB", + "accessibility": "public", + "kind": "property" + }, + { + "name": "KHIJAFCHLIA", + "type": "bool", + "accessibility": "public", + "kind": "property" + }, + { + "name": "KLNJBBPNMBJ", + "type": "bool", + "accessibility": "public", + "kind": "property" + }, + { + "name": "KODBEJPEFOJ", + "type": "string", + "accessibility": "public", + "kind": "property" + }, + { + "name": "LPJLEMJFBPE", + "type": "bool", + "accessibility": "public", + "kind": "property" + }, + { + "name": "MGBDHBHCDMH", + "type": "bool", + "accessibility": "public", + "kind": "property" + }, + { + "name": "MIKDLDEALPN", + "type": "bool", + "accessibility": "public", + "kind": "property" + }, + { + "name": "OFONEIOEIED", + "type": "bool", + "accessibility": "public", + "kind": "property" + }, + { + "name": "OILEJFNPDDB", + "type": "HJPGEGENLPH", + "accessibility": "public", + "kind": "property" + }, + { + "name": "PEEFHKMOMKK", + "type": "bool", + "accessibility": "public", + "kind": "property" + } + ], + "fields": [], + "enumValues": [], + "wireKeys": [ + "Name", + "Description", + "ImageName", + "WarningMask", + "CustomWarning", + "CreatorAccountId", + "State", + "Accessibility", + "SupportsLevelVoting", + "IsRRO", + "SupportsScreens", + "SupportsWalkVR", + "SupportsTeleportVR", + "SupportsVRLow", + "SupportsQuest2", + "SupportsMobile", + "SupportsJuniors", + "CreatedAt", + "Stats" + ], + "isilFile": "MKAMHOIHOJK.txt", + "isilType": "MKAMHOIHOJK" + } + }, + { + "kind": "unknown", + "raw": "", + "rootType": "unknown", + "typeDescription": { + "name": "unknown", + "found": false, + "kind": "object", + "wireKeys": [] + } + } + ] + }, + { + "family": "rooms", + "route": "rooms/{0}", + "evidence": [ + { + "type": "EJDCNGBEICB", + "method": "RecRoom.Async.IPromise KHDPHIGPEEH(System.Int64 HNHLJONGKHB)", + "file": "EJDCNGBEICB.txt", + "line": 2923, + "contract": { + "kind": "success-or-empty", + "raw": "RecRoom.Async.IPromise", + "rootType": "void/success", + "typeDescription": { + "name": "void/success", + "found": false, + "kind": "object", + "wireKeys": [] + } + } + }, + { + "type": "EJDCNGBEICB", + "method": "RecRoom.Async.IPromise`1 NHBPIIGDAJP(System.Int64 HNHLJONGKHB)", + "file": "EJDCNGBEICB.txt", + "line": 82, + "contract": { + "kind": "object", + "raw": "KLCOGEIGEBJ", + "rootType": "KLCOGEIGEBJ", + "typeDescription": { + "name": "KLCOGEIGEBJ", + "found": true, + "file": "KLCOGEIGEBJ.cs", + "declaration": "public class KLCOGEIGEBJ : MKAMHOIHOJK", + "kind": "class", + "baseType": "MKAMHOIHOJK", + "properties": [ + { + "name": "DADOKMAOFJL", + "type": "long", + "accessibility": "public", + "kind": "property" + }, + { + "name": "GJPGKJJCPBK", + "type": "bool", + "accessibility": "public", + "kind": "property" + }, + { + "name": "IBLOJJBEKFF", + "type": "bool", + "accessibility": "public", + "kind": "property" + }, + { + "name": "JCLCOCEOAEP", + "type": "bool", + "accessibility": "public", + "kind": "property" + }, + { + "name": "JGJCBGEBLLO", + "type": "bool", + "accessibility": "public", + "kind": "property" + }, + { + "name": "PLFEEIJCHOH", + "type": "bool", + "accessibility": "public", + "kind": "property" + } + ], + "fields": [ + { + "name": "BJFNPKFOALK", + "type": "long", + "kind": "field" + } + ], + "enumValues": [], + "wireKeys": [ + "RoomId", + "IsDorm", + "CloningAllowed", + "DisableMicAutoMute", + "DisableRoomComments", + "EncryptVoiceChat" + ], + "isilFile": "KLCOGEIGEBJ.txt", + "isilType": "KLCOGEIGEBJ", + "inherits": "MKAMHOIHOJK", + "inheritedMembers": [ + { + "name": "ACBFDMLHFPB", + "type": "DateTime", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "AHGCOGFEEEE", + "type": "string", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "AKFBNELAMNA", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "BADIGBCKECA", + "type": "int", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "BNBLOBAEDEE", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "CDINMMPNAID", + "type": "NMJEKMMBDDE", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "CDNFGMHLDMJ", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "FIKEBGGCDFN", + "type": "string", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "GIBHIMGJNNO", + "type": "GPDIAKNEBKH", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "HPLBOMGACED", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "IGOPGMHHLKI", + "type": "string", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "JFEAPMIPNEP", + "type": "DPLPMKMFMPB", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "KHIJAFCHLIA", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "KLNJBBPNMBJ", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "KODBEJPEFOJ", + "type": "string", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "LPJLEMJFBPE", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "MGBDHBHCDMH", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "MIKDLDEALPN", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "OFONEIOEIED", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "OILEJFNPDDB", + "type": "HJPGEGENLPH", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "PEEFHKMOMKK", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + } + ], + "inheritedWireKeys": [ + "Name", + "Description", + "ImageName", + "WarningMask", + "CustomWarning", + "CreatorAccountId", + "State", + "Accessibility", + "SupportsLevelVoting", + "IsRRO", + "SupportsScreens", + "SupportsWalkVR", + "SupportsTeleportVR", + "SupportsVRLow", + "SupportsQuest2", + "SupportsMobile", + "SupportsJuniors", + "CreatedAt", + "Stats" + ] + } + } + }, + { + "type": "EJDCNGBEICB", + "method": "RecRoom.Async.IPromise`1 CJKHNIIJFIN(System.Int64 HNHLJONGKHB)", + "file": "EJDCNGBEICB.txt", + "line": 615, + "contract": { + "kind": "object", + "raw": "PPENFJMFPNE", + "rootType": "PPENFJMFPNE", + "typeDescription": { + "name": "PPENFJMFPNE", + "found": true, + "file": "PPENFJMFPNE.cs", + "declaration": "public class PPENFJMFPNE : KLCOGEIGEBJ", + "kind": "class", + "baseType": "KLCOGEIGEBJ", + "properties": [ + { + "name": "ADCDBMGNIPB", + "type": "List", + "accessibility": "public", + "kind": "property" + }, + { + "name": "BIODFICLKJH", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "DFKGKGIMEMP", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "EJGIMHADHBC", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "FEBCCOFBKKJ", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "IAJDFKODGFL", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "KFFBPMHGFKB", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "LHGHLGDINFL", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "LOJPLANGFMG", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "MMBJPGGOFHL", + "type": "LMLJHMJEIGM", + "accessibility": "public", + "kind": "property" + }, + { + "name": "MMFBPKOOLNG", + "type": "bool", + "accessibility": "public", + "kind": "property" + }, + { + "name": "OHNIGJECGKL", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "PKKADKGDHNI", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + } + ], + "fields": [ + { + "name": "accountId", + "type": "int", + "kind": "field" + }, + { + "name": "tag", + "type": "string", + "kind": "field" + }, + { + "name": "IGAPDHGFLDC", + "type": "PPENFJMFPNE", + "kind": "field" + } + ], + "enumValues": [], + "wireKeys": [], + "isilFile": "PPENFJMFPNE.txt", + "isilType": "PPENFJMFPNE", + "inherits": "KLCOGEIGEBJ", + "inheritedMembers": [ + { + "name": "DADOKMAOFJL", + "type": "long", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "KLCOGEIGEBJ" + }, + { + "name": "GJPGKJJCPBK", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "KLCOGEIGEBJ" + }, + { + "name": "IBLOJJBEKFF", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "KLCOGEIGEBJ" + }, + { + "name": "JCLCOCEOAEP", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "KLCOGEIGEBJ" + }, + { + "name": "JGJCBGEBLLO", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "KLCOGEIGEBJ" + }, + { + "name": "PLFEEIJCHOH", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "KLCOGEIGEBJ" + }, + { + "name": "BJFNPKFOALK", + "type": "long", + "kind": "field", + "inheritedFrom": "KLCOGEIGEBJ" + }, + { + "name": "ACBFDMLHFPB", + "type": "DateTime", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "AHGCOGFEEEE", + "type": "string", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "AKFBNELAMNA", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "BADIGBCKECA", + "type": "int", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "BNBLOBAEDEE", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "CDINMMPNAID", + "type": "NMJEKMMBDDE", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "CDNFGMHLDMJ", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "FIKEBGGCDFN", + "type": "string", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "GIBHIMGJNNO", + "type": "GPDIAKNEBKH", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "HPLBOMGACED", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "IGOPGMHHLKI", + "type": "string", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "JFEAPMIPNEP", + "type": "DPLPMKMFMPB", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "KHIJAFCHLIA", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "KLNJBBPNMBJ", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "KODBEJPEFOJ", + "type": "string", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "LPJLEMJFBPE", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "MGBDHBHCDMH", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "MIKDLDEALPN", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "OFONEIOEIED", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "OILEJFNPDDB", + "type": "HJPGEGENLPH", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "PEEFHKMOMKK", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + } + ], + "inheritedWireKeys": [ + "RoomId", + "IsDorm", + "CloningAllowed", + "DisableMicAutoMute", + "DisableRoomComments", + "EncryptVoiceChat" + ] + } + } + } + ], + "contracts": [ + { + "kind": "success-or-empty", + "raw": "RecRoom.Async.IPromise", + "rootType": "void/success", + "typeDescription": { + "name": "void/success", + "found": false, + "kind": "object", + "wireKeys": [] + } + }, + { + "kind": "object", + "raw": "KLCOGEIGEBJ", + "rootType": "KLCOGEIGEBJ", + "typeDescription": { + "name": "KLCOGEIGEBJ", + "found": true, + "file": "KLCOGEIGEBJ.cs", + "declaration": "public class KLCOGEIGEBJ : MKAMHOIHOJK", + "kind": "class", + "baseType": "MKAMHOIHOJK", + "properties": [ + { + "name": "DADOKMAOFJL", + "type": "long", + "accessibility": "public", + "kind": "property" + }, + { + "name": "GJPGKJJCPBK", + "type": "bool", + "accessibility": "public", + "kind": "property" + }, + { + "name": "IBLOJJBEKFF", + "type": "bool", + "accessibility": "public", + "kind": "property" + }, + { + "name": "JCLCOCEOAEP", + "type": "bool", + "accessibility": "public", + "kind": "property" + }, + { + "name": "JGJCBGEBLLO", + "type": "bool", + "accessibility": "public", + "kind": "property" + }, + { + "name": "PLFEEIJCHOH", + "type": "bool", + "accessibility": "public", + "kind": "property" + } + ], + "fields": [ + { + "name": "BJFNPKFOALK", + "type": "long", + "kind": "field" + } + ], + "enumValues": [], + "wireKeys": [ + "RoomId", + "IsDorm", + "CloningAllowed", + "DisableMicAutoMute", + "DisableRoomComments", + "EncryptVoiceChat" + ], + "isilFile": "KLCOGEIGEBJ.txt", + "isilType": "KLCOGEIGEBJ", + "inherits": "MKAMHOIHOJK", + "inheritedMembers": [ + { + "name": "ACBFDMLHFPB", + "type": "DateTime", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "AHGCOGFEEEE", + "type": "string", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "AKFBNELAMNA", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "BADIGBCKECA", + "type": "int", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "BNBLOBAEDEE", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "CDINMMPNAID", + "type": "NMJEKMMBDDE", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "CDNFGMHLDMJ", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "FIKEBGGCDFN", + "type": "string", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "GIBHIMGJNNO", + "type": "GPDIAKNEBKH", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "HPLBOMGACED", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "IGOPGMHHLKI", + "type": "string", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "JFEAPMIPNEP", + "type": "DPLPMKMFMPB", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "KHIJAFCHLIA", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "KLNJBBPNMBJ", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "KODBEJPEFOJ", + "type": "string", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "LPJLEMJFBPE", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "MGBDHBHCDMH", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "MIKDLDEALPN", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "OFONEIOEIED", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "OILEJFNPDDB", + "type": "HJPGEGENLPH", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "PEEFHKMOMKK", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + } + ], + "inheritedWireKeys": [ + "Name", + "Description", + "ImageName", + "WarningMask", + "CustomWarning", + "CreatorAccountId", + "State", + "Accessibility", + "SupportsLevelVoting", + "IsRRO", + "SupportsScreens", + "SupportsWalkVR", + "SupportsTeleportVR", + "SupportsVRLow", + "SupportsQuest2", + "SupportsMobile", + "SupportsJuniors", + "CreatedAt", + "Stats" + ] + } + }, + { + "kind": "object", + "raw": "PPENFJMFPNE", + "rootType": "PPENFJMFPNE", + "typeDescription": { + "name": "PPENFJMFPNE", + "found": true, + "file": "PPENFJMFPNE.cs", + "declaration": "public class PPENFJMFPNE : KLCOGEIGEBJ", + "kind": "class", + "baseType": "KLCOGEIGEBJ", + "properties": [ + { + "name": "ADCDBMGNIPB", + "type": "List", + "accessibility": "public", + "kind": "property" + }, + { + "name": "BIODFICLKJH", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "DFKGKGIMEMP", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "EJGIMHADHBC", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "FEBCCOFBKKJ", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "IAJDFKODGFL", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "KFFBPMHGFKB", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "LHGHLGDINFL", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "LOJPLANGFMG", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "MMBJPGGOFHL", + "type": "LMLJHMJEIGM", + "accessibility": "public", + "kind": "property" + }, + { + "name": "MMFBPKOOLNG", + "type": "bool", + "accessibility": "public", + "kind": "property" + }, + { + "name": "OHNIGJECGKL", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "PKKADKGDHNI", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + } + ], + "fields": [ + { + "name": "accountId", + "type": "int", + "kind": "field" + }, + { + "name": "tag", + "type": "string", + "kind": "field" + }, + { + "name": "IGAPDHGFLDC", + "type": "PPENFJMFPNE", + "kind": "field" + } + ], + "enumValues": [], + "wireKeys": [], + "isilFile": "PPENFJMFPNE.txt", + "isilType": "PPENFJMFPNE", + "inherits": "KLCOGEIGEBJ", + "inheritedMembers": [ + { + "name": "DADOKMAOFJL", + "type": "long", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "KLCOGEIGEBJ" + }, + { + "name": "GJPGKJJCPBK", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "KLCOGEIGEBJ" + }, + { + "name": "IBLOJJBEKFF", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "KLCOGEIGEBJ" + }, + { + "name": "JCLCOCEOAEP", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "KLCOGEIGEBJ" + }, + { + "name": "JGJCBGEBLLO", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "KLCOGEIGEBJ" + }, + { + "name": "PLFEEIJCHOH", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "KLCOGEIGEBJ" + }, + { + "name": "BJFNPKFOALK", + "type": "long", + "kind": "field", + "inheritedFrom": "KLCOGEIGEBJ" + }, + { + "name": "ACBFDMLHFPB", + "type": "DateTime", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "AHGCOGFEEEE", + "type": "string", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "AKFBNELAMNA", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "BADIGBCKECA", + "type": "int", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "BNBLOBAEDEE", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "CDINMMPNAID", + "type": "NMJEKMMBDDE", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "CDNFGMHLDMJ", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "FIKEBGGCDFN", + "type": "string", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "GIBHIMGJNNO", + "type": "GPDIAKNEBKH", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "HPLBOMGACED", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "IGOPGMHHLKI", + "type": "string", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "JFEAPMIPNEP", + "type": "DPLPMKMFMPB", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "KHIJAFCHLIA", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "KLNJBBPNMBJ", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "KODBEJPEFOJ", + "type": "string", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "LPJLEMJFBPE", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "MGBDHBHCDMH", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "MIKDLDEALPN", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "OFONEIOEIED", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "OILEJFNPDDB", + "type": "HJPGEGENLPH", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "PEEFHKMOMKK", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + } + ], + "inheritedWireKeys": [ + "RoomId", + "IsDorm", + "CloningAllowed", + "DisableMicAutoMute", + "DisableRoomComments", + "EncryptVoiceChat" + ] + } + } + ] + }, + { + "family": "rooms", + "route": "rooms/{0}/accessibility", + "evidence": [ + { + "type": "EJDCNGBEICB", + "method": "RecRoom.Async.IPromise`1 BPDNAMEDJEG(System.Int64 HNHLJONGKHB, DPLPMKMFMPB PONCIIJOHIE)", + "file": "EJDCNGBEICB.txt", + "line": 3627, + "contract": { + "kind": "object", + "raw": "PPENFJMFPNE", + "rootType": "PPENFJMFPNE", + "typeDescription": { + "name": "PPENFJMFPNE", + "found": true, + "file": "PPENFJMFPNE.cs", + "declaration": "public class PPENFJMFPNE : KLCOGEIGEBJ", + "kind": "class", + "baseType": "KLCOGEIGEBJ", + "properties": [ + { + "name": "ADCDBMGNIPB", + "type": "List", + "accessibility": "public", + "kind": "property" + }, + { + "name": "BIODFICLKJH", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "DFKGKGIMEMP", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "EJGIMHADHBC", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "FEBCCOFBKKJ", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "IAJDFKODGFL", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "KFFBPMHGFKB", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "LHGHLGDINFL", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "LOJPLANGFMG", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "MMBJPGGOFHL", + "type": "LMLJHMJEIGM", + "accessibility": "public", + "kind": "property" + }, + { + "name": "MMFBPKOOLNG", + "type": "bool", + "accessibility": "public", + "kind": "property" + }, + { + "name": "OHNIGJECGKL", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "PKKADKGDHNI", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + } + ], + "fields": [ + { + "name": "accountId", + "type": "int", + "kind": "field" + }, + { + "name": "tag", + "type": "string", + "kind": "field" + }, + { + "name": "IGAPDHGFLDC", + "type": "PPENFJMFPNE", + "kind": "field" + } + ], + "enumValues": [], + "wireKeys": [], + "isilFile": "PPENFJMFPNE.txt", + "isilType": "PPENFJMFPNE", + "inherits": "KLCOGEIGEBJ", + "inheritedMembers": [ + { + "name": "DADOKMAOFJL", + "type": "long", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "KLCOGEIGEBJ" + }, + { + "name": "GJPGKJJCPBK", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "KLCOGEIGEBJ" + }, + { + "name": "IBLOJJBEKFF", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "KLCOGEIGEBJ" + }, + { + "name": "JCLCOCEOAEP", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "KLCOGEIGEBJ" + }, + { + "name": "JGJCBGEBLLO", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "KLCOGEIGEBJ" + }, + { + "name": "PLFEEIJCHOH", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "KLCOGEIGEBJ" + }, + { + "name": "BJFNPKFOALK", + "type": "long", + "kind": "field", + "inheritedFrom": "KLCOGEIGEBJ" + }, + { + "name": "ACBFDMLHFPB", + "type": "DateTime", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "AHGCOGFEEEE", + "type": "string", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "AKFBNELAMNA", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "BADIGBCKECA", + "type": "int", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "BNBLOBAEDEE", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "CDINMMPNAID", + "type": "NMJEKMMBDDE", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "CDNFGMHLDMJ", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "FIKEBGGCDFN", + "type": "string", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "GIBHIMGJNNO", + "type": "GPDIAKNEBKH", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "HPLBOMGACED", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "IGOPGMHHLKI", + "type": "string", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "JFEAPMIPNEP", + "type": "DPLPMKMFMPB", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "KHIJAFCHLIA", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "KLNJBBPNMBJ", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "KODBEJPEFOJ", + "type": "string", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "LPJLEMJFBPE", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "MGBDHBHCDMH", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "MIKDLDEALPN", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "OFONEIOEIED", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "OILEJFNPDDB", + "type": "HJPGEGENLPH", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "PEEFHKMOMKK", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + } + ], + "inheritedWireKeys": [ + "RoomId", + "IsDorm", + "CloningAllowed", + "DisableMicAutoMute", + "DisableRoomComments", + "EncryptVoiceChat" + ] + } + } + } + ], + "contracts": [ + { + "kind": "object", + "raw": "PPENFJMFPNE", + "rootType": "PPENFJMFPNE", + "typeDescription": { + "name": "PPENFJMFPNE", + "found": true, + "file": "PPENFJMFPNE.cs", + "declaration": "public class PPENFJMFPNE : KLCOGEIGEBJ", + "kind": "class", + "baseType": "KLCOGEIGEBJ", + "properties": [ + { + "name": "ADCDBMGNIPB", + "type": "List", + "accessibility": "public", + "kind": "property" + }, + { + "name": "BIODFICLKJH", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "DFKGKGIMEMP", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "EJGIMHADHBC", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "FEBCCOFBKKJ", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "IAJDFKODGFL", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "KFFBPMHGFKB", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "LHGHLGDINFL", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "LOJPLANGFMG", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "MMBJPGGOFHL", + "type": "LMLJHMJEIGM", + "accessibility": "public", + "kind": "property" + }, + { + "name": "MMFBPKOOLNG", + "type": "bool", + "accessibility": "public", + "kind": "property" + }, + { + "name": "OHNIGJECGKL", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "PKKADKGDHNI", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + } + ], + "fields": [ + { + "name": "accountId", + "type": "int", + "kind": "field" + }, + { + "name": "tag", + "type": "string", + "kind": "field" + }, + { + "name": "IGAPDHGFLDC", + "type": "PPENFJMFPNE", + "kind": "field" + } + ], + "enumValues": [], + "wireKeys": [], + "isilFile": "PPENFJMFPNE.txt", + "isilType": "PPENFJMFPNE", + "inherits": "KLCOGEIGEBJ", + "inheritedMembers": [ + { + "name": "DADOKMAOFJL", + "type": "long", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "KLCOGEIGEBJ" + }, + { + "name": "GJPGKJJCPBK", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "KLCOGEIGEBJ" + }, + { + "name": "IBLOJJBEKFF", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "KLCOGEIGEBJ" + }, + { + "name": "JCLCOCEOAEP", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "KLCOGEIGEBJ" + }, + { + "name": "JGJCBGEBLLO", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "KLCOGEIGEBJ" + }, + { + "name": "PLFEEIJCHOH", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "KLCOGEIGEBJ" + }, + { + "name": "BJFNPKFOALK", + "type": "long", + "kind": "field", + "inheritedFrom": "KLCOGEIGEBJ" + }, + { + "name": "ACBFDMLHFPB", + "type": "DateTime", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "AHGCOGFEEEE", + "type": "string", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "AKFBNELAMNA", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "BADIGBCKECA", + "type": "int", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "BNBLOBAEDEE", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "CDINMMPNAID", + "type": "NMJEKMMBDDE", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "CDNFGMHLDMJ", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "FIKEBGGCDFN", + "type": "string", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "GIBHIMGJNNO", + "type": "GPDIAKNEBKH", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "HPLBOMGACED", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "IGOPGMHHLKI", + "type": "string", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "JFEAPMIPNEP", + "type": "DPLPMKMFMPB", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "KHIJAFCHLIA", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "KLNJBBPNMBJ", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "KODBEJPEFOJ", + "type": "string", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "LPJLEMJFBPE", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "MGBDHBHCDMH", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "MIKDLDEALPN", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "OFONEIOEIED", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "OILEJFNPDDB", + "type": "HJPGEGENLPH", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "PEEFHKMOMKK", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + } + ], + "inheritedWireKeys": [ + "RoomId", + "IsDorm", + "CloningAllowed", + "DisableMicAutoMute", + "DisableRoomComments", + "EncryptVoiceChat" + ] + } + } + ] + }, + { + "family": "rooms", + "route": "rooms/{0}/automute", + "evidence": [ + { + "type": "EJDCNGBEICB", + "method": "RecRoom.Async.IPromise`1 CPFCPEONBGC(System.Int64 HNHLJONGKHB, System.Boolean AMJMLANOGKE)", + "file": "EJDCNGBEICB.txt", + "line": 4014, + "contract": { + "kind": "object", + "raw": "PPENFJMFPNE", + "rootType": "PPENFJMFPNE", + "typeDescription": { + "name": "PPENFJMFPNE", + "found": true, + "file": "PPENFJMFPNE.cs", + "declaration": "public class PPENFJMFPNE : KLCOGEIGEBJ", + "kind": "class", + "baseType": "KLCOGEIGEBJ", + "properties": [ + { + "name": "ADCDBMGNIPB", + "type": "List", + "accessibility": "public", + "kind": "property" + }, + { + "name": "BIODFICLKJH", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "DFKGKGIMEMP", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "EJGIMHADHBC", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "FEBCCOFBKKJ", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "IAJDFKODGFL", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "KFFBPMHGFKB", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "LHGHLGDINFL", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "LOJPLANGFMG", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "MMBJPGGOFHL", + "type": "LMLJHMJEIGM", + "accessibility": "public", + "kind": "property" + }, + { + "name": "MMFBPKOOLNG", + "type": "bool", + "accessibility": "public", + "kind": "property" + }, + { + "name": "OHNIGJECGKL", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "PKKADKGDHNI", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + } + ], + "fields": [ + { + "name": "accountId", + "type": "int", + "kind": "field" + }, + { + "name": "tag", + "type": "string", + "kind": "field" + }, + { + "name": "IGAPDHGFLDC", + "type": "PPENFJMFPNE", + "kind": "field" + } + ], + "enumValues": [], + "wireKeys": [], + "isilFile": "PPENFJMFPNE.txt", + "isilType": "PPENFJMFPNE", + "inherits": "KLCOGEIGEBJ", + "inheritedMembers": [ + { + "name": "DADOKMAOFJL", + "type": "long", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "KLCOGEIGEBJ" + }, + { + "name": "GJPGKJJCPBK", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "KLCOGEIGEBJ" + }, + { + "name": "IBLOJJBEKFF", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "KLCOGEIGEBJ" + }, + { + "name": "JCLCOCEOAEP", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "KLCOGEIGEBJ" + }, + { + "name": "JGJCBGEBLLO", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "KLCOGEIGEBJ" + }, + { + "name": "PLFEEIJCHOH", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "KLCOGEIGEBJ" + }, + { + "name": "BJFNPKFOALK", + "type": "long", + "kind": "field", + "inheritedFrom": "KLCOGEIGEBJ" + }, + { + "name": "ACBFDMLHFPB", + "type": "DateTime", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "AHGCOGFEEEE", + "type": "string", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "AKFBNELAMNA", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "BADIGBCKECA", + "type": "int", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "BNBLOBAEDEE", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "CDINMMPNAID", + "type": "NMJEKMMBDDE", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "CDNFGMHLDMJ", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "FIKEBGGCDFN", + "type": "string", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "GIBHIMGJNNO", + "type": "GPDIAKNEBKH", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "HPLBOMGACED", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "IGOPGMHHLKI", + "type": "string", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "JFEAPMIPNEP", + "type": "DPLPMKMFMPB", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "KHIJAFCHLIA", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "KLNJBBPNMBJ", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "KODBEJPEFOJ", + "type": "string", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "LPJLEMJFBPE", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "MGBDHBHCDMH", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "MIKDLDEALPN", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "OFONEIOEIED", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "OILEJFNPDDB", + "type": "HJPGEGENLPH", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "PEEFHKMOMKK", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + } + ], + "inheritedWireKeys": [ + "RoomId", + "IsDorm", + "CloningAllowed", + "DisableMicAutoMute", + "DisableRoomComments", + "EncryptVoiceChat" + ] + } + } + } + ], + "contracts": [ + { + "kind": "object", + "raw": "PPENFJMFPNE", + "rootType": "PPENFJMFPNE", + "typeDescription": { + "name": "PPENFJMFPNE", + "found": true, + "file": "PPENFJMFPNE.cs", + "declaration": "public class PPENFJMFPNE : KLCOGEIGEBJ", + "kind": "class", + "baseType": "KLCOGEIGEBJ", + "properties": [ + { + "name": "ADCDBMGNIPB", + "type": "List", + "accessibility": "public", + "kind": "property" + }, + { + "name": "BIODFICLKJH", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "DFKGKGIMEMP", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "EJGIMHADHBC", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "FEBCCOFBKKJ", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "IAJDFKODGFL", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "KFFBPMHGFKB", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "LHGHLGDINFL", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "LOJPLANGFMG", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "MMBJPGGOFHL", + "type": "LMLJHMJEIGM", + "accessibility": "public", + "kind": "property" + }, + { + "name": "MMFBPKOOLNG", + "type": "bool", + "accessibility": "public", + "kind": "property" + }, + { + "name": "OHNIGJECGKL", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "PKKADKGDHNI", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + } + ], + "fields": [ + { + "name": "accountId", + "type": "int", + "kind": "field" + }, + { + "name": "tag", + "type": "string", + "kind": "field" + }, + { + "name": "IGAPDHGFLDC", + "type": "PPENFJMFPNE", + "kind": "field" + } + ], + "enumValues": [], + "wireKeys": [], + "isilFile": "PPENFJMFPNE.txt", + "isilType": "PPENFJMFPNE", + "inherits": "KLCOGEIGEBJ", + "inheritedMembers": [ + { + "name": "DADOKMAOFJL", + "type": "long", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "KLCOGEIGEBJ" + }, + { + "name": "GJPGKJJCPBK", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "KLCOGEIGEBJ" + }, + { + "name": "IBLOJJBEKFF", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "KLCOGEIGEBJ" + }, + { + "name": "JCLCOCEOAEP", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "KLCOGEIGEBJ" + }, + { + "name": "JGJCBGEBLLO", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "KLCOGEIGEBJ" + }, + { + "name": "PLFEEIJCHOH", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "KLCOGEIGEBJ" + }, + { + "name": "BJFNPKFOALK", + "type": "long", + "kind": "field", + "inheritedFrom": "KLCOGEIGEBJ" + }, + { + "name": "ACBFDMLHFPB", + "type": "DateTime", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "AHGCOGFEEEE", + "type": "string", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "AKFBNELAMNA", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "BADIGBCKECA", + "type": "int", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "BNBLOBAEDEE", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "CDINMMPNAID", + "type": "NMJEKMMBDDE", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "CDNFGMHLDMJ", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "FIKEBGGCDFN", + "type": "string", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "GIBHIMGJNNO", + "type": "GPDIAKNEBKH", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "HPLBOMGACED", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "IGOPGMHHLKI", + "type": "string", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "JFEAPMIPNEP", + "type": "DPLPMKMFMPB", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "KHIJAFCHLIA", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "KLNJBBPNMBJ", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "KODBEJPEFOJ", + "type": "string", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "LPJLEMJFBPE", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "MGBDHBHCDMH", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "MIKDLDEALPN", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "OFONEIOEIED", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "OILEJFNPDDB", + "type": "HJPGEGENLPH", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "PEEFHKMOMKK", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + } + ], + "inheritedWireKeys": [ + "RoomId", + "IsDorm", + "CloningAllowed", + "DisableMicAutoMute", + "DisableRoomComments", + "EncryptVoiceChat" + ] + } + } + ] + }, + { + "family": "rooms", + "route": "rooms/{0}/bans", + "evidence": [ + { + "type": "EJDCNGBEICB", + "method": "RecRoom.Async.IPromise CCGIGHDHNLM(System.Int64 HNHLJONGKHB, System.Collections.Generic.IReadOnlyList`1 ILNGMAANNDG, KKIHLHLNGCK BFNPCCFPJHP)", + "file": "EJDCNGBEICB.txt", + "line": 7939, + "contract": { + "kind": "success-or-empty", + "raw": "RecRoom.Async.IPromise", + "rootType": "void/success", + "typeDescription": { + "name": "void/success", + "found": false, + "kind": "object", + "wireKeys": [] + } + } + }, + { + "type": "EJDCNGBEICB", + "method": "RecRoom.Async.IPromise`1> CGIPIOMBAFM(System.Int64 HNHLJONGKHB)", + "file": "EJDCNGBEICB.txt", + "line": 7832, + "contract": { + "kind": "array", + "raw": "System.Collections.Generic.List`1", + "rootType": "EAENIFLCDGI", + "item": { + "kind": "object", + "raw": "EAENIFLCDGI", + "rootType": "EAENIFLCDGI" + }, + "typeDescription": { + "name": "EAENIFLCDGI", + "found": true, + "file": "EAENIFLCDGI.cs", + "declaration": "public class EAENIFLCDGI : IFAIJAGLDFK", + "kind": "class", + "baseType": "IFAIJAGLDFK", + "properties": [ + { + "name": "CCNHPFAKKBD", + "type": "DateTime", + "accessibility": "public", + "kind": "property" + }, + { + "name": "GAINIOENNCG", + "type": "int", + "accessibility": "public", + "kind": "property" + } + ], + "fields": [], + "enumValues": [], + "wireKeys": [ + "AccountId", + "BanStartTime" + ], + "isilFile": "EAENIFLCDGI.txt", + "isilType": "EAENIFLCDGI" + } + } + } + ], + "contracts": [ + { + "kind": "success-or-empty", + "raw": "RecRoom.Async.IPromise", + "rootType": "void/success", + "typeDescription": { + "name": "void/success", + "found": false, + "kind": "object", + "wireKeys": [] + } + }, + { + "kind": "array", + "raw": "System.Collections.Generic.List`1", + "rootType": "EAENIFLCDGI", + "item": { + "kind": "object", + "raw": "EAENIFLCDGI", + "rootType": "EAENIFLCDGI" + }, + "typeDescription": { + "name": "EAENIFLCDGI", + "found": true, + "file": "EAENIFLCDGI.cs", + "declaration": "public class EAENIFLCDGI : IFAIJAGLDFK", + "kind": "class", + "baseType": "IFAIJAGLDFK", + "properties": [ + { + "name": "CCNHPFAKKBD", + "type": "DateTime", + "accessibility": "public", + "kind": "property" + }, + { + "name": "GAINIOENNCG", + "type": "int", + "accessibility": "public", + "kind": "property" + } + ], + "fields": [], + "enumValues": [], + "wireKeys": [ + "AccountId", + "BanStartTime" + ], + "isilFile": "EAENIFLCDGI.txt", + "isilType": "EAENIFLCDGI" + } + } + ] + }, + { + "family": "rooms", + "route": "rooms/{0}/bans/{1}", + "evidence": [ + { + "type": "EJDCNGBEICB", + "method": "RecRoom.Async.IPromise JMCJDGIMKPK(System.Int64 HNHLJONGKHB, System.Int32 GKLPIFBPGOD, KKIHLHLNGCK BFNPCCFPJHP)", + "file": "EJDCNGBEICB.txt", + "line": 8186, + "contract": { + "kind": "success-or-empty", + "raw": "RecRoom.Async.IPromise", + "rootType": "void/success", + "typeDescription": { + "name": "void/success", + "found": false, + "kind": "object", + "wireKeys": [] + } + } + } + ], + "contracts": [ + { + "kind": "success-or-empty", + "raw": "RecRoom.Async.IPromise", + "rootType": "void/success", + "typeDescription": { + "name": "void/success", + "found": false, + "kind": "object", + "wireKeys": [] + } + } + ] + }, + { + "family": "rooms", + "route": "rooms/{0}/bans/import", + "evidence": [ + { + "type": "EJDCNGBEICB", + "method": "RecRoom.Async.IPromise PFEEJDKPPJI(System.Int64 HNHLJONGKHB, System.Int64 MOKIOEJBMFC)", + "file": "EJDCNGBEICB.txt", + "line": 8054, + "contract": { + "kind": "success-or-empty", + "raw": "RecRoom.Async.IPromise", + "rootType": "void/success", + "typeDescription": { + "name": "void/success", + "found": false, + "kind": "object", + "wireKeys": [] + } + } + } + ], + "contracts": [ + { + "kind": "success-or-empty", + "raw": "RecRoom.Async.IPromise", + "rootType": "void/success", + "typeDescription": { + "name": "void/success", + "found": false, + "kind": "object", + "wireKeys": [] + } + } + ] + }, + { + "family": "rooms", + "route": "rooms/{0}/clone", + "evidence": [ + { + "type": "EJDCNGBEICB", + "method": "RecRoom.Async.IPromise`1 OFFECAMHJGE(System.Int64 HNHLJONGKHB, System.String MMBOKOLAJFH)", + "file": "EJDCNGBEICB.txt", + "line": 2843, + "contract": { + "kind": "object", + "raw": "PPENFJMFPNE", + "rootType": "PPENFJMFPNE", + "typeDescription": { + "name": "PPENFJMFPNE", + "found": true, + "file": "PPENFJMFPNE.cs", + "declaration": "public class PPENFJMFPNE : KLCOGEIGEBJ", + "kind": "class", + "baseType": "KLCOGEIGEBJ", + "properties": [ + { + "name": "ADCDBMGNIPB", + "type": "List", + "accessibility": "public", + "kind": "property" + }, + { + "name": "BIODFICLKJH", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "DFKGKGIMEMP", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "EJGIMHADHBC", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "FEBCCOFBKKJ", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "IAJDFKODGFL", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "KFFBPMHGFKB", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "LHGHLGDINFL", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "LOJPLANGFMG", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "MMBJPGGOFHL", + "type": "LMLJHMJEIGM", + "accessibility": "public", + "kind": "property" + }, + { + "name": "MMFBPKOOLNG", + "type": "bool", + "accessibility": "public", + "kind": "property" + }, + { + "name": "OHNIGJECGKL", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "PKKADKGDHNI", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + } + ], + "fields": [ + { + "name": "accountId", + "type": "int", + "kind": "field" + }, + { + "name": "tag", + "type": "string", + "kind": "field" + }, + { + "name": "IGAPDHGFLDC", + "type": "PPENFJMFPNE", + "kind": "field" + } + ], + "enumValues": [], + "wireKeys": [], + "isilFile": "PPENFJMFPNE.txt", + "isilType": "PPENFJMFPNE", + "inherits": "KLCOGEIGEBJ", + "inheritedMembers": [ + { + "name": "DADOKMAOFJL", + "type": "long", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "KLCOGEIGEBJ" + }, + { + "name": "GJPGKJJCPBK", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "KLCOGEIGEBJ" + }, + { + "name": "IBLOJJBEKFF", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "KLCOGEIGEBJ" + }, + { + "name": "JCLCOCEOAEP", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "KLCOGEIGEBJ" + }, + { + "name": "JGJCBGEBLLO", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "KLCOGEIGEBJ" + }, + { + "name": "PLFEEIJCHOH", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "KLCOGEIGEBJ" + }, + { + "name": "BJFNPKFOALK", + "type": "long", + "kind": "field", + "inheritedFrom": "KLCOGEIGEBJ" + }, + { + "name": "ACBFDMLHFPB", + "type": "DateTime", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "AHGCOGFEEEE", + "type": "string", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "AKFBNELAMNA", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "BADIGBCKECA", + "type": "int", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "BNBLOBAEDEE", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "CDINMMPNAID", + "type": "NMJEKMMBDDE", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "CDNFGMHLDMJ", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "FIKEBGGCDFN", + "type": "string", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "GIBHIMGJNNO", + "type": "GPDIAKNEBKH", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "HPLBOMGACED", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "IGOPGMHHLKI", + "type": "string", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "JFEAPMIPNEP", + "type": "DPLPMKMFMPB", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "KHIJAFCHLIA", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "KLNJBBPNMBJ", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "KODBEJPEFOJ", + "type": "string", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "LPJLEMJFBPE", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "MGBDHBHCDMH", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "MIKDLDEALPN", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "OFONEIOEIED", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "OILEJFNPDDB", + "type": "HJPGEGENLPH", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "PEEFHKMOMKK", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + } + ], + "inheritedWireKeys": [ + "RoomId", + "IsDorm", + "CloningAllowed", + "DisableMicAutoMute", + "DisableRoomComments", + "EncryptVoiceChat" + ] + } + } + } + ], + "contracts": [ + { + "kind": "object", + "raw": "PPENFJMFPNE", + "rootType": "PPENFJMFPNE", + "typeDescription": { + "name": "PPENFJMFPNE", + "found": true, + "file": "PPENFJMFPNE.cs", + "declaration": "public class PPENFJMFPNE : KLCOGEIGEBJ", + "kind": "class", + "baseType": "KLCOGEIGEBJ", + "properties": [ + { + "name": "ADCDBMGNIPB", + "type": "List", + "accessibility": "public", + "kind": "property" + }, + { + "name": "BIODFICLKJH", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "DFKGKGIMEMP", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "EJGIMHADHBC", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "FEBCCOFBKKJ", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "IAJDFKODGFL", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "KFFBPMHGFKB", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "LHGHLGDINFL", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "LOJPLANGFMG", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "MMBJPGGOFHL", + "type": "LMLJHMJEIGM", + "accessibility": "public", + "kind": "property" + }, + { + "name": "MMFBPKOOLNG", + "type": "bool", + "accessibility": "public", + "kind": "property" + }, + { + "name": "OHNIGJECGKL", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "PKKADKGDHNI", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + } + ], + "fields": [ + { + "name": "accountId", + "type": "int", + "kind": "field" + }, + { + "name": "tag", + "type": "string", + "kind": "field" + }, + { + "name": "IGAPDHGFLDC", + "type": "PPENFJMFPNE", + "kind": "field" + } + ], + "enumValues": [], + "wireKeys": [], + "isilFile": "PPENFJMFPNE.txt", + "isilType": "PPENFJMFPNE", + "inherits": "KLCOGEIGEBJ", + "inheritedMembers": [ + { + "name": "DADOKMAOFJL", + "type": "long", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "KLCOGEIGEBJ" + }, + { + "name": "GJPGKJJCPBK", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "KLCOGEIGEBJ" + }, + { + "name": "IBLOJJBEKFF", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "KLCOGEIGEBJ" + }, + { + "name": "JCLCOCEOAEP", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "KLCOGEIGEBJ" + }, + { + "name": "JGJCBGEBLLO", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "KLCOGEIGEBJ" + }, + { + "name": "PLFEEIJCHOH", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "KLCOGEIGEBJ" + }, + { + "name": "BJFNPKFOALK", + "type": "long", + "kind": "field", + "inheritedFrom": "KLCOGEIGEBJ" + }, + { + "name": "ACBFDMLHFPB", + "type": "DateTime", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "AHGCOGFEEEE", + "type": "string", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "AKFBNELAMNA", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "BADIGBCKECA", + "type": "int", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "BNBLOBAEDEE", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "CDINMMPNAID", + "type": "NMJEKMMBDDE", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "CDNFGMHLDMJ", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "FIKEBGGCDFN", + "type": "string", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "GIBHIMGJNNO", + "type": "GPDIAKNEBKH", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "HPLBOMGACED", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "IGOPGMHHLKI", + "type": "string", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "JFEAPMIPNEP", + "type": "DPLPMKMFMPB", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "KHIJAFCHLIA", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "KLNJBBPNMBJ", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "KODBEJPEFOJ", + "type": "string", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "LPJLEMJFBPE", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "MGBDHBHCDMH", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "MIKDLDEALPN", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "OFONEIOEIED", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "OILEJFNPDDB", + "type": "HJPGEGENLPH", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "PEEFHKMOMKK", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + } + ], + "inheritedWireKeys": [ + "RoomId", + "IsDorm", + "CloningAllowed", + "DisableMicAutoMute", + "DisableRoomComments", + "EncryptVoiceChat" + ] + } + } + ] + }, + { + "family": "rooms", + "route": "rooms/{0}/cloning", + "evidence": [ + { + "type": "EJDCNGBEICB", + "method": "RecRoom.Async.IPromise`1 LJMHBJKDJCE(System.Int64 HNHLJONGKHB, System.Boolean GPAGMKFIKNG)", + "file": "EJDCNGBEICB.txt", + "line": 3886, + "contract": { + "kind": "object", + "raw": "PPENFJMFPNE", + "rootType": "PPENFJMFPNE", + "typeDescription": { + "name": "PPENFJMFPNE", + "found": true, + "file": "PPENFJMFPNE.cs", + "declaration": "public class PPENFJMFPNE : KLCOGEIGEBJ", + "kind": "class", + "baseType": "KLCOGEIGEBJ", + "properties": [ + { + "name": "ADCDBMGNIPB", + "type": "List", + "accessibility": "public", + "kind": "property" + }, + { + "name": "BIODFICLKJH", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "DFKGKGIMEMP", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "EJGIMHADHBC", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "FEBCCOFBKKJ", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "IAJDFKODGFL", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "KFFBPMHGFKB", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "LHGHLGDINFL", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "LOJPLANGFMG", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "MMBJPGGOFHL", + "type": "LMLJHMJEIGM", + "accessibility": "public", + "kind": "property" + }, + { + "name": "MMFBPKOOLNG", + "type": "bool", + "accessibility": "public", + "kind": "property" + }, + { + "name": "OHNIGJECGKL", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "PKKADKGDHNI", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + } + ], + "fields": [ + { + "name": "accountId", + "type": "int", + "kind": "field" + }, + { + "name": "tag", + "type": "string", + "kind": "field" + }, + { + "name": "IGAPDHGFLDC", + "type": "PPENFJMFPNE", + "kind": "field" + } + ], + "enumValues": [], + "wireKeys": [], + "isilFile": "PPENFJMFPNE.txt", + "isilType": "PPENFJMFPNE", + "inherits": "KLCOGEIGEBJ", + "inheritedMembers": [ + { + "name": "DADOKMAOFJL", + "type": "long", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "KLCOGEIGEBJ" + }, + { + "name": "GJPGKJJCPBK", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "KLCOGEIGEBJ" + }, + { + "name": "IBLOJJBEKFF", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "KLCOGEIGEBJ" + }, + { + "name": "JCLCOCEOAEP", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "KLCOGEIGEBJ" + }, + { + "name": "JGJCBGEBLLO", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "KLCOGEIGEBJ" + }, + { + "name": "PLFEEIJCHOH", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "KLCOGEIGEBJ" + }, + { + "name": "BJFNPKFOALK", + "type": "long", + "kind": "field", + "inheritedFrom": "KLCOGEIGEBJ" + }, + { + "name": "ACBFDMLHFPB", + "type": "DateTime", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "AHGCOGFEEEE", + "type": "string", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "AKFBNELAMNA", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "BADIGBCKECA", + "type": "int", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "BNBLOBAEDEE", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "CDINMMPNAID", + "type": "NMJEKMMBDDE", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "CDNFGMHLDMJ", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "FIKEBGGCDFN", + "type": "string", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "GIBHIMGJNNO", + "type": "GPDIAKNEBKH", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "HPLBOMGACED", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "IGOPGMHHLKI", + "type": "string", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "JFEAPMIPNEP", + "type": "DPLPMKMFMPB", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "KHIJAFCHLIA", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "KLNJBBPNMBJ", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "KODBEJPEFOJ", + "type": "string", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "LPJLEMJFBPE", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "MGBDHBHCDMH", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "MIKDLDEALPN", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "OFONEIOEIED", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "OILEJFNPDDB", + "type": "HJPGEGENLPH", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "PEEFHKMOMKK", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + } + ], + "inheritedWireKeys": [ + "RoomId", + "IsDorm", + "CloningAllowed", + "DisableMicAutoMute", + "DisableRoomComments", + "EncryptVoiceChat" + ] + } + } + } + ], + "contracts": [ + { + "kind": "object", + "raw": "PPENFJMFPNE", + "rootType": "PPENFJMFPNE", + "typeDescription": { + "name": "PPENFJMFPNE", + "found": true, + "file": "PPENFJMFPNE.cs", + "declaration": "public class PPENFJMFPNE : KLCOGEIGEBJ", + "kind": "class", + "baseType": "KLCOGEIGEBJ", + "properties": [ + { + "name": "ADCDBMGNIPB", + "type": "List", + "accessibility": "public", + "kind": "property" + }, + { + "name": "BIODFICLKJH", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "DFKGKGIMEMP", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "EJGIMHADHBC", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "FEBCCOFBKKJ", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "IAJDFKODGFL", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "KFFBPMHGFKB", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "LHGHLGDINFL", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "LOJPLANGFMG", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "MMBJPGGOFHL", + "type": "LMLJHMJEIGM", + "accessibility": "public", + "kind": "property" + }, + { + "name": "MMFBPKOOLNG", + "type": "bool", + "accessibility": "public", + "kind": "property" + }, + { + "name": "OHNIGJECGKL", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "PKKADKGDHNI", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + } + ], + "fields": [ + { + "name": "accountId", + "type": "int", + "kind": "field" + }, + { + "name": "tag", + "type": "string", + "kind": "field" + }, + { + "name": "IGAPDHGFLDC", + "type": "PPENFJMFPNE", + "kind": "field" + } + ], + "enumValues": [], + "wireKeys": [], + "isilFile": "PPENFJMFPNE.txt", + "isilType": "PPENFJMFPNE", + "inherits": "KLCOGEIGEBJ", + "inheritedMembers": [ + { + "name": "DADOKMAOFJL", + "type": "long", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "KLCOGEIGEBJ" + }, + { + "name": "GJPGKJJCPBK", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "KLCOGEIGEBJ" + }, + { + "name": "IBLOJJBEKFF", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "KLCOGEIGEBJ" + }, + { + "name": "JCLCOCEOAEP", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "KLCOGEIGEBJ" + }, + { + "name": "JGJCBGEBLLO", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "KLCOGEIGEBJ" + }, + { + "name": "PLFEEIJCHOH", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "KLCOGEIGEBJ" + }, + { + "name": "BJFNPKFOALK", + "type": "long", + "kind": "field", + "inheritedFrom": "KLCOGEIGEBJ" + }, + { + "name": "ACBFDMLHFPB", + "type": "DateTime", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "AHGCOGFEEEE", + "type": "string", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "AKFBNELAMNA", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "BADIGBCKECA", + "type": "int", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "BNBLOBAEDEE", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "CDINMMPNAID", + "type": "NMJEKMMBDDE", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "CDNFGMHLDMJ", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "FIKEBGGCDFN", + "type": "string", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "GIBHIMGJNNO", + "type": "GPDIAKNEBKH", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "HPLBOMGACED", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "IGOPGMHHLKI", + "type": "string", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "JFEAPMIPNEP", + "type": "DPLPMKMFMPB", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "KHIJAFCHLIA", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "KLNJBBPNMBJ", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "KODBEJPEFOJ", + "type": "string", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "LPJLEMJFBPE", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "MGBDHBHCDMH", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "MIKDLDEALPN", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "OFONEIOEIED", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "OILEJFNPDDB", + "type": "HJPGEGENLPH", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "PEEFHKMOMKK", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + } + ], + "inheritedWireKeys": [ + "RoomId", + "IsDorm", + "CloningAllowed", + "DisableMicAutoMute", + "DisableRoomComments", + "EncryptVoiceChat" + ] + } + } + ] + }, + { + "family": "rooms", + "route": "rooms/{0}/comments", + "evidence": [ + { + "type": "EJDCNGBEICB", + "method": "RecRoom.Async.IPromise`1 FKADIOPHOGC(System.Int64 HNHLJONGKHB, System.Boolean IMHMMPKLGEP)", + "file": "EJDCNGBEICB.txt", + "line": 4142, + "contract": { + "kind": "object", + "raw": "PPENFJMFPNE", + "rootType": "PPENFJMFPNE", + "typeDescription": { + "name": "PPENFJMFPNE", + "found": true, + "file": "PPENFJMFPNE.cs", + "declaration": "public class PPENFJMFPNE : KLCOGEIGEBJ", + "kind": "class", + "baseType": "KLCOGEIGEBJ", + "properties": [ + { + "name": "ADCDBMGNIPB", + "type": "List", + "accessibility": "public", + "kind": "property" + }, + { + "name": "BIODFICLKJH", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "DFKGKGIMEMP", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "EJGIMHADHBC", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "FEBCCOFBKKJ", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "IAJDFKODGFL", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "KFFBPMHGFKB", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "LHGHLGDINFL", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "LOJPLANGFMG", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "MMBJPGGOFHL", + "type": "LMLJHMJEIGM", + "accessibility": "public", + "kind": "property" + }, + { + "name": "MMFBPKOOLNG", + "type": "bool", + "accessibility": "public", + "kind": "property" + }, + { + "name": "OHNIGJECGKL", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "PKKADKGDHNI", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + } + ], + "fields": [ + { + "name": "accountId", + "type": "int", + "kind": "field" + }, + { + "name": "tag", + "type": "string", + "kind": "field" + }, + { + "name": "IGAPDHGFLDC", + "type": "PPENFJMFPNE", + "kind": "field" + } + ], + "enumValues": [], + "wireKeys": [], + "isilFile": "PPENFJMFPNE.txt", + "isilType": "PPENFJMFPNE", + "inherits": "KLCOGEIGEBJ", + "inheritedMembers": [ + { + "name": "DADOKMAOFJL", + "type": "long", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "KLCOGEIGEBJ" + }, + { + "name": "GJPGKJJCPBK", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "KLCOGEIGEBJ" + }, + { + "name": "IBLOJJBEKFF", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "KLCOGEIGEBJ" + }, + { + "name": "JCLCOCEOAEP", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "KLCOGEIGEBJ" + }, + { + "name": "JGJCBGEBLLO", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "KLCOGEIGEBJ" + }, + { + "name": "PLFEEIJCHOH", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "KLCOGEIGEBJ" + }, + { + "name": "BJFNPKFOALK", + "type": "long", + "kind": "field", + "inheritedFrom": "KLCOGEIGEBJ" + }, + { + "name": "ACBFDMLHFPB", + "type": "DateTime", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "AHGCOGFEEEE", + "type": "string", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "AKFBNELAMNA", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "BADIGBCKECA", + "type": "int", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "BNBLOBAEDEE", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "CDINMMPNAID", + "type": "NMJEKMMBDDE", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "CDNFGMHLDMJ", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "FIKEBGGCDFN", + "type": "string", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "GIBHIMGJNNO", + "type": "GPDIAKNEBKH", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "HPLBOMGACED", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "IGOPGMHHLKI", + "type": "string", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "JFEAPMIPNEP", + "type": "DPLPMKMFMPB", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "KHIJAFCHLIA", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "KLNJBBPNMBJ", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "KODBEJPEFOJ", + "type": "string", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "LPJLEMJFBPE", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "MGBDHBHCDMH", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "MIKDLDEALPN", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "OFONEIOEIED", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "OILEJFNPDDB", + "type": "HJPGEGENLPH", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "PEEFHKMOMKK", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + } + ], + "inheritedWireKeys": [ + "RoomId", + "IsDorm", + "CloningAllowed", + "DisableMicAutoMute", + "DisableRoomComments", + "EncryptVoiceChat" + ] + } + } + } + ], + "contracts": [ + { + "kind": "object", + "raw": "PPENFJMFPNE", + "rootType": "PPENFJMFPNE", + "typeDescription": { + "name": "PPENFJMFPNE", + "found": true, + "file": "PPENFJMFPNE.cs", + "declaration": "public class PPENFJMFPNE : KLCOGEIGEBJ", + "kind": "class", + "baseType": "KLCOGEIGEBJ", + "properties": [ + { + "name": "ADCDBMGNIPB", + "type": "List", + "accessibility": "public", + "kind": "property" + }, + { + "name": "BIODFICLKJH", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "DFKGKGIMEMP", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "EJGIMHADHBC", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "FEBCCOFBKKJ", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "IAJDFKODGFL", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "KFFBPMHGFKB", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "LHGHLGDINFL", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "LOJPLANGFMG", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "MMBJPGGOFHL", + "type": "LMLJHMJEIGM", + "accessibility": "public", + "kind": "property" + }, + { + "name": "MMFBPKOOLNG", + "type": "bool", + "accessibility": "public", + "kind": "property" + }, + { + "name": "OHNIGJECGKL", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "PKKADKGDHNI", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + } + ], + "fields": [ + { + "name": "accountId", + "type": "int", + "kind": "field" + }, + { + "name": "tag", + "type": "string", + "kind": "field" + }, + { + "name": "IGAPDHGFLDC", + "type": "PPENFJMFPNE", + "kind": "field" + } + ], + "enumValues": [], + "wireKeys": [], + "isilFile": "PPENFJMFPNE.txt", + "isilType": "PPENFJMFPNE", + "inherits": "KLCOGEIGEBJ", + "inheritedMembers": [ + { + "name": "DADOKMAOFJL", + "type": "long", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "KLCOGEIGEBJ" + }, + { + "name": "GJPGKJJCPBK", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "KLCOGEIGEBJ" + }, + { + "name": "IBLOJJBEKFF", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "KLCOGEIGEBJ" + }, + { + "name": "JCLCOCEOAEP", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "KLCOGEIGEBJ" + }, + { + "name": "JGJCBGEBLLO", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "KLCOGEIGEBJ" + }, + { + "name": "PLFEEIJCHOH", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "KLCOGEIGEBJ" + }, + { + "name": "BJFNPKFOALK", + "type": "long", + "kind": "field", + "inheritedFrom": "KLCOGEIGEBJ" + }, + { + "name": "ACBFDMLHFPB", + "type": "DateTime", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "AHGCOGFEEEE", + "type": "string", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "AKFBNELAMNA", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "BADIGBCKECA", + "type": "int", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "BNBLOBAEDEE", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "CDINMMPNAID", + "type": "NMJEKMMBDDE", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "CDNFGMHLDMJ", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "FIKEBGGCDFN", + "type": "string", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "GIBHIMGJNNO", + "type": "GPDIAKNEBKH", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "HPLBOMGACED", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "IGOPGMHHLKI", + "type": "string", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "JFEAPMIPNEP", + "type": "DPLPMKMFMPB", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "KHIJAFCHLIA", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "KLNJBBPNMBJ", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "KODBEJPEFOJ", + "type": "string", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "LPJLEMJFBPE", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "MGBDHBHCDMH", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "MIKDLDEALPN", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "OFONEIOEIED", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "OILEJFNPDDB", + "type": "HJPGEGENLPH", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "PEEFHKMOMKK", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + } + ], + "inheritedWireKeys": [ + "RoomId", + "IsDorm", + "CloningAllowed", + "DisableMicAutoMute", + "DisableRoomComments", + "EncryptVoiceChat" + ] + } + } + ] + }, + { + "family": "rooms", + "route": "rooms/{0}/description", + "evidence": [ + { + "type": "EJDCNGBEICB", + "method": "RecRoom.Async.IPromise`1 CFNFNEJGCEF(System.Int64 HNHLJONGKHB, System.String LJIGOCDPEJF)", + "file": "EJDCNGBEICB.txt", + "line": 3143, + "contract": { + "kind": "object", + "raw": "PPENFJMFPNE", + "rootType": "PPENFJMFPNE", + "typeDescription": { + "name": "PPENFJMFPNE", + "found": true, + "file": "PPENFJMFPNE.cs", + "declaration": "public class PPENFJMFPNE : KLCOGEIGEBJ", + "kind": "class", + "baseType": "KLCOGEIGEBJ", + "properties": [ + { + "name": "ADCDBMGNIPB", + "type": "List", + "accessibility": "public", + "kind": "property" + }, + { + "name": "BIODFICLKJH", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "DFKGKGIMEMP", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "EJGIMHADHBC", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "FEBCCOFBKKJ", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "IAJDFKODGFL", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "KFFBPMHGFKB", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "LHGHLGDINFL", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "LOJPLANGFMG", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "MMBJPGGOFHL", + "type": "LMLJHMJEIGM", + "accessibility": "public", + "kind": "property" + }, + { + "name": "MMFBPKOOLNG", + "type": "bool", + "accessibility": "public", + "kind": "property" + }, + { + "name": "OHNIGJECGKL", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "PKKADKGDHNI", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + } + ], + "fields": [ + { + "name": "accountId", + "type": "int", + "kind": "field" + }, + { + "name": "tag", + "type": "string", + "kind": "field" + }, + { + "name": "IGAPDHGFLDC", + "type": "PPENFJMFPNE", + "kind": "field" + } + ], + "enumValues": [], + "wireKeys": [], + "isilFile": "PPENFJMFPNE.txt", + "isilType": "PPENFJMFPNE", + "inherits": "KLCOGEIGEBJ", + "inheritedMembers": [ + { + "name": "DADOKMAOFJL", + "type": "long", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "KLCOGEIGEBJ" + }, + { + "name": "GJPGKJJCPBK", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "KLCOGEIGEBJ" + }, + { + "name": "IBLOJJBEKFF", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "KLCOGEIGEBJ" + }, + { + "name": "JCLCOCEOAEP", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "KLCOGEIGEBJ" + }, + { + "name": "JGJCBGEBLLO", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "KLCOGEIGEBJ" + }, + { + "name": "PLFEEIJCHOH", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "KLCOGEIGEBJ" + }, + { + "name": "BJFNPKFOALK", + "type": "long", + "kind": "field", + "inheritedFrom": "KLCOGEIGEBJ" + }, + { + "name": "ACBFDMLHFPB", + "type": "DateTime", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "AHGCOGFEEEE", + "type": "string", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "AKFBNELAMNA", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "BADIGBCKECA", + "type": "int", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "BNBLOBAEDEE", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "CDINMMPNAID", + "type": "NMJEKMMBDDE", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "CDNFGMHLDMJ", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "FIKEBGGCDFN", + "type": "string", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "GIBHIMGJNNO", + "type": "GPDIAKNEBKH", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "HPLBOMGACED", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "IGOPGMHHLKI", + "type": "string", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "JFEAPMIPNEP", + "type": "DPLPMKMFMPB", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "KHIJAFCHLIA", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "KLNJBBPNMBJ", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "KODBEJPEFOJ", + "type": "string", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "LPJLEMJFBPE", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "MGBDHBHCDMH", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "MIKDLDEALPN", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "OFONEIOEIED", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "OILEJFNPDDB", + "type": "HJPGEGENLPH", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "PEEFHKMOMKK", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + } + ], + "inheritedWireKeys": [ + "RoomId", + "IsDorm", + "CloningAllowed", + "DisableMicAutoMute", + "DisableRoomComments", + "EncryptVoiceChat" + ] + } + } + } + ], + "contracts": [ + { + "kind": "object", + "raw": "PPENFJMFPNE", + "rootType": "PPENFJMFPNE", + "typeDescription": { + "name": "PPENFJMFPNE", + "found": true, + "file": "PPENFJMFPNE.cs", + "declaration": "public class PPENFJMFPNE : KLCOGEIGEBJ", + "kind": "class", + "baseType": "KLCOGEIGEBJ", + "properties": [ + { + "name": "ADCDBMGNIPB", + "type": "List", + "accessibility": "public", + "kind": "property" + }, + { + "name": "BIODFICLKJH", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "DFKGKGIMEMP", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "EJGIMHADHBC", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "FEBCCOFBKKJ", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "IAJDFKODGFL", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "KFFBPMHGFKB", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "LHGHLGDINFL", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "LOJPLANGFMG", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "MMBJPGGOFHL", + "type": "LMLJHMJEIGM", + "accessibility": "public", + "kind": "property" + }, + { + "name": "MMFBPKOOLNG", + "type": "bool", + "accessibility": "public", + "kind": "property" + }, + { + "name": "OHNIGJECGKL", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "PKKADKGDHNI", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + } + ], + "fields": [ + { + "name": "accountId", + "type": "int", + "kind": "field" + }, + { + "name": "tag", + "type": "string", + "kind": "field" + }, + { + "name": "IGAPDHGFLDC", + "type": "PPENFJMFPNE", + "kind": "field" + } + ], + "enumValues": [], + "wireKeys": [], + "isilFile": "PPENFJMFPNE.txt", + "isilType": "PPENFJMFPNE", + "inherits": "KLCOGEIGEBJ", + "inheritedMembers": [ + { + "name": "DADOKMAOFJL", + "type": "long", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "KLCOGEIGEBJ" + }, + { + "name": "GJPGKJJCPBK", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "KLCOGEIGEBJ" + }, + { + "name": "IBLOJJBEKFF", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "KLCOGEIGEBJ" + }, + { + "name": "JCLCOCEOAEP", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "KLCOGEIGEBJ" + }, + { + "name": "JGJCBGEBLLO", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "KLCOGEIGEBJ" + }, + { + "name": "PLFEEIJCHOH", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "KLCOGEIGEBJ" + }, + { + "name": "BJFNPKFOALK", + "type": "long", + "kind": "field", + "inheritedFrom": "KLCOGEIGEBJ" + }, + { + "name": "ACBFDMLHFPB", + "type": "DateTime", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "AHGCOGFEEEE", + "type": "string", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "AKFBNELAMNA", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "BADIGBCKECA", + "type": "int", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "BNBLOBAEDEE", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "CDINMMPNAID", + "type": "NMJEKMMBDDE", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "CDNFGMHLDMJ", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "FIKEBGGCDFN", + "type": "string", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "GIBHIMGJNNO", + "type": "GPDIAKNEBKH", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "HPLBOMGACED", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "IGOPGMHHLKI", + "type": "string", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "JFEAPMIPNEP", + "type": "DPLPMKMFMPB", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "KHIJAFCHLIA", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "KLNJBBPNMBJ", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "KODBEJPEFOJ", + "type": "string", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "LPJLEMJFBPE", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "MGBDHBHCDMH", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "MIKDLDEALPN", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "OFONEIOEIED", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "OILEJFNPDDB", + "type": "HJPGEGENLPH", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "PEEFHKMOMKK", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + } + ], + "inheritedWireKeys": [ + "RoomId", + "IsDorm", + "CloningAllowed", + "DisableMicAutoMute", + "DisableRoomComments", + "EncryptVoiceChat" + ] + } + } + ] + }, + { + "family": "rooms", + "route": "rooms/{0}/image", + "evidence": [ + { + "type": "EJDCNGBEICB", + "method": "RecRoom.Async.IPromise`1 OGFLGMMNMDD(System.Int64 HNHLJONGKHB, System.String HFLPBHHAFIO)", + "file": "EJDCNGBEICB.txt", + "line": 3259, + "contract": { + "kind": "object", + "raw": "PPENFJMFPNE", + "rootType": "PPENFJMFPNE", + "typeDescription": { + "name": "PPENFJMFPNE", + "found": true, + "file": "PPENFJMFPNE.cs", + "declaration": "public class PPENFJMFPNE : KLCOGEIGEBJ", + "kind": "class", + "baseType": "KLCOGEIGEBJ", + "properties": [ + { + "name": "ADCDBMGNIPB", + "type": "List", + "accessibility": "public", + "kind": "property" + }, + { + "name": "BIODFICLKJH", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "DFKGKGIMEMP", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "EJGIMHADHBC", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "FEBCCOFBKKJ", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "IAJDFKODGFL", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "KFFBPMHGFKB", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "LHGHLGDINFL", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "LOJPLANGFMG", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "MMBJPGGOFHL", + "type": "LMLJHMJEIGM", + "accessibility": "public", + "kind": "property" + }, + { + "name": "MMFBPKOOLNG", + "type": "bool", + "accessibility": "public", + "kind": "property" + }, + { + "name": "OHNIGJECGKL", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "PKKADKGDHNI", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + } + ], + "fields": [ + { + "name": "accountId", + "type": "int", + "kind": "field" + }, + { + "name": "tag", + "type": "string", + "kind": "field" + }, + { + "name": "IGAPDHGFLDC", + "type": "PPENFJMFPNE", + "kind": "field" + } + ], + "enumValues": [], + "wireKeys": [], + "isilFile": "PPENFJMFPNE.txt", + "isilType": "PPENFJMFPNE", + "inherits": "KLCOGEIGEBJ", + "inheritedMembers": [ + { + "name": "DADOKMAOFJL", + "type": "long", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "KLCOGEIGEBJ" + }, + { + "name": "GJPGKJJCPBK", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "KLCOGEIGEBJ" + }, + { + "name": "IBLOJJBEKFF", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "KLCOGEIGEBJ" + }, + { + "name": "JCLCOCEOAEP", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "KLCOGEIGEBJ" + }, + { + "name": "JGJCBGEBLLO", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "KLCOGEIGEBJ" + }, + { + "name": "PLFEEIJCHOH", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "KLCOGEIGEBJ" + }, + { + "name": "BJFNPKFOALK", + "type": "long", + "kind": "field", + "inheritedFrom": "KLCOGEIGEBJ" + }, + { + "name": "ACBFDMLHFPB", + "type": "DateTime", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "AHGCOGFEEEE", + "type": "string", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "AKFBNELAMNA", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "BADIGBCKECA", + "type": "int", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "BNBLOBAEDEE", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "CDINMMPNAID", + "type": "NMJEKMMBDDE", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "CDNFGMHLDMJ", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "FIKEBGGCDFN", + "type": "string", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "GIBHIMGJNNO", + "type": "GPDIAKNEBKH", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "HPLBOMGACED", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "IGOPGMHHLKI", + "type": "string", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "JFEAPMIPNEP", + "type": "DPLPMKMFMPB", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "KHIJAFCHLIA", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "KLNJBBPNMBJ", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "KODBEJPEFOJ", + "type": "string", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "LPJLEMJFBPE", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "MGBDHBHCDMH", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "MIKDLDEALPN", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "OFONEIOEIED", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "OILEJFNPDDB", + "type": "HJPGEGENLPH", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "PEEFHKMOMKK", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + } + ], + "inheritedWireKeys": [ + "RoomId", + "IsDorm", + "CloningAllowed", + "DisableMicAutoMute", + "DisableRoomComments", + "EncryptVoiceChat" + ] + } + } + } + ], + "contracts": [ + { + "kind": "object", + "raw": "PPENFJMFPNE", + "rootType": "PPENFJMFPNE", + "typeDescription": { + "name": "PPENFJMFPNE", + "found": true, + "file": "PPENFJMFPNE.cs", + "declaration": "public class PPENFJMFPNE : KLCOGEIGEBJ", + "kind": "class", + "baseType": "KLCOGEIGEBJ", + "properties": [ + { + "name": "ADCDBMGNIPB", + "type": "List", + "accessibility": "public", + "kind": "property" + }, + { + "name": "BIODFICLKJH", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "DFKGKGIMEMP", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "EJGIMHADHBC", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "FEBCCOFBKKJ", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "IAJDFKODGFL", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "KFFBPMHGFKB", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "LHGHLGDINFL", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "LOJPLANGFMG", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "MMBJPGGOFHL", + "type": "LMLJHMJEIGM", + "accessibility": "public", + "kind": "property" + }, + { + "name": "MMFBPKOOLNG", + "type": "bool", + "accessibility": "public", + "kind": "property" + }, + { + "name": "OHNIGJECGKL", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "PKKADKGDHNI", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + } + ], + "fields": [ + { + "name": "accountId", + "type": "int", + "kind": "field" + }, + { + "name": "tag", + "type": "string", + "kind": "field" + }, + { + "name": "IGAPDHGFLDC", + "type": "PPENFJMFPNE", + "kind": "field" + } + ], + "enumValues": [], + "wireKeys": [], + "isilFile": "PPENFJMFPNE.txt", + "isilType": "PPENFJMFPNE", + "inherits": "KLCOGEIGEBJ", + "inheritedMembers": [ + { + "name": "DADOKMAOFJL", + "type": "long", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "KLCOGEIGEBJ" + }, + { + "name": "GJPGKJJCPBK", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "KLCOGEIGEBJ" + }, + { + "name": "IBLOJJBEKFF", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "KLCOGEIGEBJ" + }, + { + "name": "JCLCOCEOAEP", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "KLCOGEIGEBJ" + }, + { + "name": "JGJCBGEBLLO", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "KLCOGEIGEBJ" + }, + { + "name": "PLFEEIJCHOH", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "KLCOGEIGEBJ" + }, + { + "name": "BJFNPKFOALK", + "type": "long", + "kind": "field", + "inheritedFrom": "KLCOGEIGEBJ" + }, + { + "name": "ACBFDMLHFPB", + "type": "DateTime", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "AHGCOGFEEEE", + "type": "string", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "AKFBNELAMNA", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "BADIGBCKECA", + "type": "int", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "BNBLOBAEDEE", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "CDINMMPNAID", + "type": "NMJEKMMBDDE", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "CDNFGMHLDMJ", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "FIKEBGGCDFN", + "type": "string", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "GIBHIMGJNNO", + "type": "GPDIAKNEBKH", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "HPLBOMGACED", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "IGOPGMHHLKI", + "type": "string", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "JFEAPMIPNEP", + "type": "DPLPMKMFMPB", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "KHIJAFCHLIA", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "KLNJBBPNMBJ", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "KODBEJPEFOJ", + "type": "string", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "LPJLEMJFBPE", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "MGBDHBHCDMH", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "MIKDLDEALPN", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "OFONEIOEIED", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "OILEJFNPDDB", + "type": "HJPGEGENLPH", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "PEEFHKMOMKK", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + } + ], + "inheritedWireKeys": [ + "RoomId", + "IsDorm", + "CloningAllowed", + "DisableMicAutoMute", + "DisableRoomComments", + "EncryptVoiceChat" + ] + } + } + ] + }, + { + "family": "rooms", + "route": "rooms/{0}/interactionby/me", + "evidence": [ + { + "type": "EJDCNGBEICB", + "method": "RecRoom.Async.IPromise`1 CKJBGHEIGBI(System.Int64 HNHLJONGKHB)", + "file": "EJDCNGBEICB.txt", + "line": 8266, + "contract": { + "kind": "object", + "raw": "CJODCLDGFCF", + "rootType": "CJODCLDGFCF", + "typeDescription": { + "name": "CJODCLDGFCF", + "found": true, + "file": "CJODCLDGFCF.cs", + "declaration": "public class CJODCLDGFCF : IFAIJAGLDFK", + "kind": "class", + "baseType": "IFAIJAGLDFK", + "properties": [ + { + "name": "EFCGNDGOFNK", + "type": "Nullable", + "accessibility": "public", + "kind": "property" + }, + { + "name": "EMBNPPMOJFJ", + "type": "bool", + "accessibility": "public", + "kind": "property" + }, + { + "name": "HHFLKFOEHNL", + "type": "bool", + "accessibility": "public", + "kind": "property" + } + ], + "fields": [], + "enumValues": [], + "wireKeys": [ + "Cheered", + "Favorited" + ], + "isilFile": "CJODCLDGFCF.txt", + "isilType": "CJODCLDGFCF" + } + } + } + ], + "contracts": [ + { + "kind": "object", + "raw": "CJODCLDGFCF", + "rootType": "CJODCLDGFCF", + "typeDescription": { + "name": "CJODCLDGFCF", + "found": true, + "file": "CJODCLDGFCF.cs", + "declaration": "public class CJODCLDGFCF : IFAIJAGLDFK", + "kind": "class", + "baseType": "IFAIJAGLDFK", + "properties": [ + { + "name": "EFCGNDGOFNK", + "type": "Nullable", + "accessibility": "public", + "kind": "property" + }, + { + "name": "EMBNPPMOJFJ", + "type": "bool", + "accessibility": "public", + "kind": "property" + }, + { + "name": "HHFLKFOEHNL", + "type": "bool", + "accessibility": "public", + "kind": "property" + } + ], + "fields": [], + "enumValues": [], + "wireKeys": [ + "Cheered", + "Favorited" + ], + "isilFile": "CJODCLDGFCF.txt", + "isilType": "CJODCLDGFCF" + } + } + ] + }, + { + "family": "rooms", + "route": "rooms/{0}/interactionby/me/cheer", + "evidence": [ + { + "type": "EJDCNGBEICB", + "method": "RecRoom.Async.IPromise JFBCCIHBKPP(System.Int64 HNHLJONGKHB)", + "file": "EJDCNGBEICB.txt", + "line": 8399, + "contract": { + "kind": "success-or-empty", + "raw": "RecRoom.Async.IPromise", + "rootType": "void/success", + "typeDescription": { + "name": "void/success", + "found": false, + "kind": "object", + "wireKeys": [] + } + } + }, + { + "type": "EJDCNGBEICB", + "method": "RecRoom.Async.IPromise PEKMAJPMCDE(System.Int64 HNHLJONGKHB)", + "file": "EJDCNGBEICB.txt", + "line": 8331, + "contract": { + "kind": "success-or-empty", + "raw": "RecRoom.Async.IPromise", + "rootType": "void/success", + "typeDescription": { + "name": "void/success", + "found": false, + "kind": "object", + "wireKeys": [] + } + } + } + ], + "contracts": [ + { + "kind": "success-or-empty", + "raw": "RecRoom.Async.IPromise", + "rootType": "void/success", + "typeDescription": { + "name": "void/success", + "found": false, + "kind": "object", + "wireKeys": [] + } + } + ] + }, + { + "family": "rooms", + "route": "rooms/{0}/interactionby/me/favorite", + "evidence": [ + { + "type": "EJDCNGBEICB", + "method": "RecRoom.Async.IPromise DIFNOPBMBFO(System.Int64 HNHLJONGKHB)", + "file": "EJDCNGBEICB.txt", + "line": 8467, + "contract": { + "kind": "success-or-empty", + "raw": "RecRoom.Async.IPromise", + "rootType": "void/success", + "typeDescription": { + "name": "void/success", + "found": false, + "kind": "object", + "wireKeys": [] + } + } + }, + { + "type": "EJDCNGBEICB", + "method": "RecRoom.Async.IPromise OGBLBGALMDC(System.Int64 HNHLJONGKHB)", + "file": "EJDCNGBEICB.txt", + "line": 8535, + "contract": { + "kind": "success-or-empty", + "raw": "RecRoom.Async.IPromise", + "rootType": "void/success", + "typeDescription": { + "name": "void/success", + "found": false, + "kind": "object", + "wireKeys": [] + } + } + } + ], + "contracts": [ + { + "kind": "success-or-empty", + "raw": "RecRoom.Async.IPromise", + "rootType": "void/success", + "typeDescription": { + "name": "void/success", + "found": false, + "kind": "object", + "wireKeys": [] + } + } + ] + }, + { + "family": "rooms", + "route": "rooms/{0}/modify", + "evidence": [ + { + "type": "EJDCNGBEICB", + "method": "RecRoom.Async.IPromise`1 ONBIHBFOJKD(System.Int64 HNHLJONGKHB, System.String MMBOKOLAJFH, System.String LJIGOCDPEJF, DPLPMKMFMPB PONCIIJOHIE, System.Boolean DDMOEIAHJBK, System.Boolean FOBLJIBLCNI, System.Boolean GELHFEFGJLA, System.Boolean NFIGHDPKLJF, System.Boolean GPAGMKFIKNG, System.Boolean AMJMLANOGKE, System.Boolean IMHMMPKLGEP, System.Boolean NFJINCPDPDG)", + "file": "EJDCNGBEICB.txt", + "line": 6298, + "contract": { + "kind": "object", + "raw": "PPENFJMFPNE", + "rootType": "PPENFJMFPNE", + "typeDescription": { + "name": "PPENFJMFPNE", + "found": true, + "file": "PPENFJMFPNE.cs", + "declaration": "public class PPENFJMFPNE : KLCOGEIGEBJ", + "kind": "class", + "baseType": "KLCOGEIGEBJ", + "properties": [ + { + "name": "ADCDBMGNIPB", + "type": "List", + "accessibility": "public", + "kind": "property" + }, + { + "name": "BIODFICLKJH", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "DFKGKGIMEMP", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "EJGIMHADHBC", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "FEBCCOFBKKJ", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "IAJDFKODGFL", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "KFFBPMHGFKB", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "LHGHLGDINFL", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "LOJPLANGFMG", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "MMBJPGGOFHL", + "type": "LMLJHMJEIGM", + "accessibility": "public", + "kind": "property" + }, + { + "name": "MMFBPKOOLNG", + "type": "bool", + "accessibility": "public", + "kind": "property" + }, + { + "name": "OHNIGJECGKL", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "PKKADKGDHNI", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + } + ], + "fields": [ + { + "name": "accountId", + "type": "int", + "kind": "field" + }, + { + "name": "tag", + "type": "string", + "kind": "field" + }, + { + "name": "IGAPDHGFLDC", + "type": "PPENFJMFPNE", + "kind": "field" + } + ], + "enumValues": [], + "wireKeys": [], + "isilFile": "PPENFJMFPNE.txt", + "isilType": "PPENFJMFPNE", + "inherits": "KLCOGEIGEBJ", + "inheritedMembers": [ + { + "name": "DADOKMAOFJL", + "type": "long", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "KLCOGEIGEBJ" + }, + { + "name": "GJPGKJJCPBK", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "KLCOGEIGEBJ" + }, + { + "name": "IBLOJJBEKFF", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "KLCOGEIGEBJ" + }, + { + "name": "JCLCOCEOAEP", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "KLCOGEIGEBJ" + }, + { + "name": "JGJCBGEBLLO", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "KLCOGEIGEBJ" + }, + { + "name": "PLFEEIJCHOH", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "KLCOGEIGEBJ" + }, + { + "name": "BJFNPKFOALK", + "type": "long", + "kind": "field", + "inheritedFrom": "KLCOGEIGEBJ" + }, + { + "name": "ACBFDMLHFPB", + "type": "DateTime", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "AHGCOGFEEEE", + "type": "string", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "AKFBNELAMNA", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "BADIGBCKECA", + "type": "int", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "BNBLOBAEDEE", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "CDINMMPNAID", + "type": "NMJEKMMBDDE", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "CDNFGMHLDMJ", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "FIKEBGGCDFN", + "type": "string", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "GIBHIMGJNNO", + "type": "GPDIAKNEBKH", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "HPLBOMGACED", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "IGOPGMHHLKI", + "type": "string", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "JFEAPMIPNEP", + "type": "DPLPMKMFMPB", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "KHIJAFCHLIA", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "KLNJBBPNMBJ", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "KODBEJPEFOJ", + "type": "string", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "LPJLEMJFBPE", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "MGBDHBHCDMH", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "MIKDLDEALPN", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "OFONEIOEIED", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "OILEJFNPDDB", + "type": "HJPGEGENLPH", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "PEEFHKMOMKK", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + } + ], + "inheritedWireKeys": [ + "RoomId", + "IsDorm", + "CloningAllowed", + "DisableMicAutoMute", + "DisableRoomComments", + "EncryptVoiceChat" + ] + } + } + } + ], + "contracts": [ + { + "kind": "object", + "raw": "PPENFJMFPNE", + "rootType": "PPENFJMFPNE", + "typeDescription": { + "name": "PPENFJMFPNE", + "found": true, + "file": "PPENFJMFPNE.cs", + "declaration": "public class PPENFJMFPNE : KLCOGEIGEBJ", + "kind": "class", + "baseType": "KLCOGEIGEBJ", + "properties": [ + { + "name": "ADCDBMGNIPB", + "type": "List", + "accessibility": "public", + "kind": "property" + }, + { + "name": "BIODFICLKJH", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "DFKGKGIMEMP", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "EJGIMHADHBC", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "FEBCCOFBKKJ", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "IAJDFKODGFL", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "KFFBPMHGFKB", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "LHGHLGDINFL", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "LOJPLANGFMG", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "MMBJPGGOFHL", + "type": "LMLJHMJEIGM", + "accessibility": "public", + "kind": "property" + }, + { + "name": "MMFBPKOOLNG", + "type": "bool", + "accessibility": "public", + "kind": "property" + }, + { + "name": "OHNIGJECGKL", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "PKKADKGDHNI", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + } + ], + "fields": [ + { + "name": "accountId", + "type": "int", + "kind": "field" + }, + { + "name": "tag", + "type": "string", + "kind": "field" + }, + { + "name": "IGAPDHGFLDC", + "type": "PPENFJMFPNE", + "kind": "field" + } + ], + "enumValues": [], + "wireKeys": [], + "isilFile": "PPENFJMFPNE.txt", + "isilType": "PPENFJMFPNE", + "inherits": "KLCOGEIGEBJ", + "inheritedMembers": [ + { + "name": "DADOKMAOFJL", + "type": "long", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "KLCOGEIGEBJ" + }, + { + "name": "GJPGKJJCPBK", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "KLCOGEIGEBJ" + }, + { + "name": "IBLOJJBEKFF", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "KLCOGEIGEBJ" + }, + { + "name": "JCLCOCEOAEP", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "KLCOGEIGEBJ" + }, + { + "name": "JGJCBGEBLLO", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "KLCOGEIGEBJ" + }, + { + "name": "PLFEEIJCHOH", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "KLCOGEIGEBJ" + }, + { + "name": "BJFNPKFOALK", + "type": "long", + "kind": "field", + "inheritedFrom": "KLCOGEIGEBJ" + }, + { + "name": "ACBFDMLHFPB", + "type": "DateTime", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "AHGCOGFEEEE", + "type": "string", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "AKFBNELAMNA", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "BADIGBCKECA", + "type": "int", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "BNBLOBAEDEE", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "CDINMMPNAID", + "type": "NMJEKMMBDDE", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "CDNFGMHLDMJ", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "FIKEBGGCDFN", + "type": "string", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "GIBHIMGJNNO", + "type": "GPDIAKNEBKH", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "HPLBOMGACED", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "IGOPGMHHLKI", + "type": "string", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "JFEAPMIPNEP", + "type": "DPLPMKMFMPB", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "KHIJAFCHLIA", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "KLNJBBPNMBJ", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "KODBEJPEFOJ", + "type": "string", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "LPJLEMJFBPE", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "MGBDHBHCDMH", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "MIKDLDEALPN", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "OFONEIOEIED", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "OILEJFNPDDB", + "type": "HJPGEGENLPH", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "PEEFHKMOMKK", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + } + ], + "inheritedWireKeys": [ + "RoomId", + "IsDorm", + "CloningAllowed", + "DisableMicAutoMute", + "DisableRoomComments", + "EncryptVoiceChat" + ] + } + } + ] + }, + { + "family": "rooms", + "route": "rooms/{0}/name", + "evidence": [ + { + "type": "EJDCNGBEICB", + "method": "RecRoom.Async.IPromise`1 DNOGGKKHMHI(System.Int64 HNHLJONGKHB, System.String MMBOKOLAJFH)", + "file": "EJDCNGBEICB.txt", + "line": 3027, + "contract": { + "kind": "object", + "raw": "PPENFJMFPNE", + "rootType": "PPENFJMFPNE", + "typeDescription": { + "name": "PPENFJMFPNE", + "found": true, + "file": "PPENFJMFPNE.cs", + "declaration": "public class PPENFJMFPNE : KLCOGEIGEBJ", + "kind": "class", + "baseType": "KLCOGEIGEBJ", + "properties": [ + { + "name": "ADCDBMGNIPB", + "type": "List", + "accessibility": "public", + "kind": "property" + }, + { + "name": "BIODFICLKJH", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "DFKGKGIMEMP", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "EJGIMHADHBC", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "FEBCCOFBKKJ", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "IAJDFKODGFL", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "KFFBPMHGFKB", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "LHGHLGDINFL", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "LOJPLANGFMG", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "MMBJPGGOFHL", + "type": "LMLJHMJEIGM", + "accessibility": "public", + "kind": "property" + }, + { + "name": "MMFBPKOOLNG", + "type": "bool", + "accessibility": "public", + "kind": "property" + }, + { + "name": "OHNIGJECGKL", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "PKKADKGDHNI", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + } + ], + "fields": [ + { + "name": "accountId", + "type": "int", + "kind": "field" + }, + { + "name": "tag", + "type": "string", + "kind": "field" + }, + { + "name": "IGAPDHGFLDC", + "type": "PPENFJMFPNE", + "kind": "field" + } + ], + "enumValues": [], + "wireKeys": [], + "isilFile": "PPENFJMFPNE.txt", + "isilType": "PPENFJMFPNE", + "inherits": "KLCOGEIGEBJ", + "inheritedMembers": [ + { + "name": "DADOKMAOFJL", + "type": "long", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "KLCOGEIGEBJ" + }, + { + "name": "GJPGKJJCPBK", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "KLCOGEIGEBJ" + }, + { + "name": "IBLOJJBEKFF", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "KLCOGEIGEBJ" + }, + { + "name": "JCLCOCEOAEP", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "KLCOGEIGEBJ" + }, + { + "name": "JGJCBGEBLLO", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "KLCOGEIGEBJ" + }, + { + "name": "PLFEEIJCHOH", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "KLCOGEIGEBJ" + }, + { + "name": "BJFNPKFOALK", + "type": "long", + "kind": "field", + "inheritedFrom": "KLCOGEIGEBJ" + }, + { + "name": "ACBFDMLHFPB", + "type": "DateTime", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "AHGCOGFEEEE", + "type": "string", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "AKFBNELAMNA", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "BADIGBCKECA", + "type": "int", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "BNBLOBAEDEE", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "CDINMMPNAID", + "type": "NMJEKMMBDDE", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "CDNFGMHLDMJ", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "FIKEBGGCDFN", + "type": "string", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "GIBHIMGJNNO", + "type": "GPDIAKNEBKH", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "HPLBOMGACED", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "IGOPGMHHLKI", + "type": "string", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "JFEAPMIPNEP", + "type": "DPLPMKMFMPB", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "KHIJAFCHLIA", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "KLNJBBPNMBJ", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "KODBEJPEFOJ", + "type": "string", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "LPJLEMJFBPE", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "MGBDHBHCDMH", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "MIKDLDEALPN", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "OFONEIOEIED", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "OILEJFNPDDB", + "type": "HJPGEGENLPH", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "PEEFHKMOMKK", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + } + ], + "inheritedWireKeys": [ + "RoomId", + "IsDorm", + "CloningAllowed", + "DisableMicAutoMute", + "DisableRoomComments", + "EncryptVoiceChat" + ] + } + } + } + ], + "contracts": [ + { + "kind": "object", + "raw": "PPENFJMFPNE", + "rootType": "PPENFJMFPNE", + "typeDescription": { + "name": "PPENFJMFPNE", + "found": true, + "file": "PPENFJMFPNE.cs", + "declaration": "public class PPENFJMFPNE : KLCOGEIGEBJ", + "kind": "class", + "baseType": "KLCOGEIGEBJ", + "properties": [ + { + "name": "ADCDBMGNIPB", + "type": "List", + "accessibility": "public", + "kind": "property" + }, + { + "name": "BIODFICLKJH", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "DFKGKGIMEMP", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "EJGIMHADHBC", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "FEBCCOFBKKJ", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "IAJDFKODGFL", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "KFFBPMHGFKB", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "LHGHLGDINFL", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "LOJPLANGFMG", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "MMBJPGGOFHL", + "type": "LMLJHMJEIGM", + "accessibility": "public", + "kind": "property" + }, + { + "name": "MMFBPKOOLNG", + "type": "bool", + "accessibility": "public", + "kind": "property" + }, + { + "name": "OHNIGJECGKL", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "PKKADKGDHNI", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + } + ], + "fields": [ + { + "name": "accountId", + "type": "int", + "kind": "field" + }, + { + "name": "tag", + "type": "string", + "kind": "field" + }, + { + "name": "IGAPDHGFLDC", + "type": "PPENFJMFPNE", + "kind": "field" + } + ], + "enumValues": [], + "wireKeys": [], + "isilFile": "PPENFJMFPNE.txt", + "isilType": "PPENFJMFPNE", + "inherits": "KLCOGEIGEBJ", + "inheritedMembers": [ + { + "name": "DADOKMAOFJL", + "type": "long", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "KLCOGEIGEBJ" + }, + { + "name": "GJPGKJJCPBK", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "KLCOGEIGEBJ" + }, + { + "name": "IBLOJJBEKFF", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "KLCOGEIGEBJ" + }, + { + "name": "JCLCOCEOAEP", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "KLCOGEIGEBJ" + }, + { + "name": "JGJCBGEBLLO", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "KLCOGEIGEBJ" + }, + { + "name": "PLFEEIJCHOH", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "KLCOGEIGEBJ" + }, + { + "name": "BJFNPKFOALK", + "type": "long", + "kind": "field", + "inheritedFrom": "KLCOGEIGEBJ" + }, + { + "name": "ACBFDMLHFPB", + "type": "DateTime", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "AHGCOGFEEEE", + "type": "string", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "AKFBNELAMNA", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "BADIGBCKECA", + "type": "int", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "BNBLOBAEDEE", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "CDINMMPNAID", + "type": "NMJEKMMBDDE", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "CDNFGMHLDMJ", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "FIKEBGGCDFN", + "type": "string", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "GIBHIMGJNNO", + "type": "GPDIAKNEBKH", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "HPLBOMGACED", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "IGOPGMHHLKI", + "type": "string", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "JFEAPMIPNEP", + "type": "DPLPMKMFMPB", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "KHIJAFCHLIA", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "KLNJBBPNMBJ", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "KODBEJPEFOJ", + "type": "string", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "LPJLEMJFBPE", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "MGBDHBHCDMH", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "MIKDLDEALPN", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "OFONEIOEIED", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "OILEJFNPDDB", + "type": "HJPGEGENLPH", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "PEEFHKMOMKK", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + } + ], + "inheritedWireKeys": [ + "RoomId", + "IsDorm", + "CloningAllowed", + "DisableMicAutoMute", + "DisableRoomComments", + "EncryptVoiceChat" + ] + } + } + ] + }, + { + "family": "rooms", + "route": "rooms/{0}/promo_external", + "evidence": [ + { + "type": "EJDCNGBEICB", + "method": "RecRoom.Async.IPromise`1 FBNHEKCLMCJ(System.Int64 HNHLJONGKHB, NCELIDGFOEM GEDCEIDOKJL, System.String EKGHGPLFMPJ)", + "file": "EJDCNGBEICB.txt", + "line": 4862, + "contract": { + "kind": "object", + "raw": "PPENFJMFPNE", + "rootType": "PPENFJMFPNE", + "typeDescription": { + "name": "PPENFJMFPNE", + "found": true, + "file": "PPENFJMFPNE.cs", + "declaration": "public class PPENFJMFPNE : KLCOGEIGEBJ", + "kind": "class", + "baseType": "KLCOGEIGEBJ", + "properties": [ + { + "name": "ADCDBMGNIPB", + "type": "List", + "accessibility": "public", + "kind": "property" + }, + { + "name": "BIODFICLKJH", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "DFKGKGIMEMP", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "EJGIMHADHBC", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "FEBCCOFBKKJ", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "IAJDFKODGFL", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "KFFBPMHGFKB", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "LHGHLGDINFL", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "LOJPLANGFMG", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "MMBJPGGOFHL", + "type": "LMLJHMJEIGM", + "accessibility": "public", + "kind": "property" + }, + { + "name": "MMFBPKOOLNG", + "type": "bool", + "accessibility": "public", + "kind": "property" + }, + { + "name": "OHNIGJECGKL", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "PKKADKGDHNI", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + } + ], + "fields": [ + { + "name": "accountId", + "type": "int", + "kind": "field" + }, + { + "name": "tag", + "type": "string", + "kind": "field" + }, + { + "name": "IGAPDHGFLDC", + "type": "PPENFJMFPNE", + "kind": "field" + } + ], + "enumValues": [], + "wireKeys": [], + "isilFile": "PPENFJMFPNE.txt", + "isilType": "PPENFJMFPNE", + "inherits": "KLCOGEIGEBJ", + "inheritedMembers": [ + { + "name": "DADOKMAOFJL", + "type": "long", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "KLCOGEIGEBJ" + }, + { + "name": "GJPGKJJCPBK", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "KLCOGEIGEBJ" + }, + { + "name": "IBLOJJBEKFF", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "KLCOGEIGEBJ" + }, + { + "name": "JCLCOCEOAEP", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "KLCOGEIGEBJ" + }, + { + "name": "JGJCBGEBLLO", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "KLCOGEIGEBJ" + }, + { + "name": "PLFEEIJCHOH", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "KLCOGEIGEBJ" + }, + { + "name": "BJFNPKFOALK", + "type": "long", + "kind": "field", + "inheritedFrom": "KLCOGEIGEBJ" + }, + { + "name": "ACBFDMLHFPB", + "type": "DateTime", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "AHGCOGFEEEE", + "type": "string", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "AKFBNELAMNA", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "BADIGBCKECA", + "type": "int", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "BNBLOBAEDEE", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "CDINMMPNAID", + "type": "NMJEKMMBDDE", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "CDNFGMHLDMJ", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "FIKEBGGCDFN", + "type": "string", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "GIBHIMGJNNO", + "type": "GPDIAKNEBKH", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "HPLBOMGACED", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "IGOPGMHHLKI", + "type": "string", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "JFEAPMIPNEP", + "type": "DPLPMKMFMPB", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "KHIJAFCHLIA", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "KLNJBBPNMBJ", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "KODBEJPEFOJ", + "type": "string", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "LPJLEMJFBPE", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "MGBDHBHCDMH", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "MIKDLDEALPN", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "OFONEIOEIED", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "OILEJFNPDDB", + "type": "HJPGEGENLPH", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "PEEFHKMOMKK", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + } + ], + "inheritedWireKeys": [ + "RoomId", + "IsDorm", + "CloningAllowed", + "DisableMicAutoMute", + "DisableRoomComments", + "EncryptVoiceChat" + ] + } + } + } + ], + "contracts": [ + { + "kind": "object", + "raw": "PPENFJMFPNE", + "rootType": "PPENFJMFPNE", + "typeDescription": { + "name": "PPENFJMFPNE", + "found": true, + "file": "PPENFJMFPNE.cs", + "declaration": "public class PPENFJMFPNE : KLCOGEIGEBJ", + "kind": "class", + "baseType": "KLCOGEIGEBJ", + "properties": [ + { + "name": "ADCDBMGNIPB", + "type": "List", + "accessibility": "public", + "kind": "property" + }, + { + "name": "BIODFICLKJH", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "DFKGKGIMEMP", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "EJGIMHADHBC", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "FEBCCOFBKKJ", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "IAJDFKODGFL", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "KFFBPMHGFKB", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "LHGHLGDINFL", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "LOJPLANGFMG", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "MMBJPGGOFHL", + "type": "LMLJHMJEIGM", + "accessibility": "public", + "kind": "property" + }, + { + "name": "MMFBPKOOLNG", + "type": "bool", + "accessibility": "public", + "kind": "property" + }, + { + "name": "OHNIGJECGKL", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "PKKADKGDHNI", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + } + ], + "fields": [ + { + "name": "accountId", + "type": "int", + "kind": "field" + }, + { + "name": "tag", + "type": "string", + "kind": "field" + }, + { + "name": "IGAPDHGFLDC", + "type": "PPENFJMFPNE", + "kind": "field" + } + ], + "enumValues": [], + "wireKeys": [], + "isilFile": "PPENFJMFPNE.txt", + "isilType": "PPENFJMFPNE", + "inherits": "KLCOGEIGEBJ", + "inheritedMembers": [ + { + "name": "DADOKMAOFJL", + "type": "long", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "KLCOGEIGEBJ" + }, + { + "name": "GJPGKJJCPBK", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "KLCOGEIGEBJ" + }, + { + "name": "IBLOJJBEKFF", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "KLCOGEIGEBJ" + }, + { + "name": "JCLCOCEOAEP", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "KLCOGEIGEBJ" + }, + { + "name": "JGJCBGEBLLO", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "KLCOGEIGEBJ" + }, + { + "name": "PLFEEIJCHOH", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "KLCOGEIGEBJ" + }, + { + "name": "BJFNPKFOALK", + "type": "long", + "kind": "field", + "inheritedFrom": "KLCOGEIGEBJ" + }, + { + "name": "ACBFDMLHFPB", + "type": "DateTime", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "AHGCOGFEEEE", + "type": "string", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "AKFBNELAMNA", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "BADIGBCKECA", + "type": "int", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "BNBLOBAEDEE", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "CDINMMPNAID", + "type": "NMJEKMMBDDE", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "CDNFGMHLDMJ", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "FIKEBGGCDFN", + "type": "string", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "GIBHIMGJNNO", + "type": "GPDIAKNEBKH", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "HPLBOMGACED", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "IGOPGMHHLKI", + "type": "string", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "JFEAPMIPNEP", + "type": "DPLPMKMFMPB", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "KHIJAFCHLIA", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "KLNJBBPNMBJ", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "KODBEJPEFOJ", + "type": "string", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "LPJLEMJFBPE", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "MGBDHBHCDMH", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "MIKDLDEALPN", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "OFONEIOEIED", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "OILEJFNPDDB", + "type": "HJPGEGENLPH", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "PEEFHKMOMKK", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + } + ], + "inheritedWireKeys": [ + "RoomId", + "IsDorm", + "CloningAllowed", + "DisableMicAutoMute", + "DisableRoomComments", + "EncryptVoiceChat" + ] + } + } + ] + }, + { + "family": "rooms", + "route": "rooms/{0}/promo_external/{1}/{2}", + "evidence": [ + { + "type": "EJDCNGBEICB", + "method": "RecRoom.Async.IPromise`1 ACGJJCMFOCH(System.Int64 HNHLJONGKHB, NCELIDGFOEM GEDCEIDOKJL, System.String EKGHGPLFMPJ)", + "file": "EJDCNGBEICB.txt", + "line": 4970, + "contract": { + "kind": "object", + "raw": "PPENFJMFPNE", + "rootType": "PPENFJMFPNE", + "typeDescription": { + "name": "PPENFJMFPNE", + "found": true, + "file": "PPENFJMFPNE.cs", + "declaration": "public class PPENFJMFPNE : KLCOGEIGEBJ", + "kind": "class", + "baseType": "KLCOGEIGEBJ", + "properties": [ + { + "name": "ADCDBMGNIPB", + "type": "List", + "accessibility": "public", + "kind": "property" + }, + { + "name": "BIODFICLKJH", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "DFKGKGIMEMP", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "EJGIMHADHBC", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "FEBCCOFBKKJ", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "IAJDFKODGFL", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "KFFBPMHGFKB", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "LHGHLGDINFL", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "LOJPLANGFMG", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "MMBJPGGOFHL", + "type": "LMLJHMJEIGM", + "accessibility": "public", + "kind": "property" + }, + { + "name": "MMFBPKOOLNG", + "type": "bool", + "accessibility": "public", + "kind": "property" + }, + { + "name": "OHNIGJECGKL", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "PKKADKGDHNI", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + } + ], + "fields": [ + { + "name": "accountId", + "type": "int", + "kind": "field" + }, + { + "name": "tag", + "type": "string", + "kind": "field" + }, + { + "name": "IGAPDHGFLDC", + "type": "PPENFJMFPNE", + "kind": "field" + } + ], + "enumValues": [], + "wireKeys": [], + "isilFile": "PPENFJMFPNE.txt", + "isilType": "PPENFJMFPNE", + "inherits": "KLCOGEIGEBJ", + "inheritedMembers": [ + { + "name": "DADOKMAOFJL", + "type": "long", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "KLCOGEIGEBJ" + }, + { + "name": "GJPGKJJCPBK", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "KLCOGEIGEBJ" + }, + { + "name": "IBLOJJBEKFF", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "KLCOGEIGEBJ" + }, + { + "name": "JCLCOCEOAEP", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "KLCOGEIGEBJ" + }, + { + "name": "JGJCBGEBLLO", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "KLCOGEIGEBJ" + }, + { + "name": "PLFEEIJCHOH", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "KLCOGEIGEBJ" + }, + { + "name": "BJFNPKFOALK", + "type": "long", + "kind": "field", + "inheritedFrom": "KLCOGEIGEBJ" + }, + { + "name": "ACBFDMLHFPB", + "type": "DateTime", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "AHGCOGFEEEE", + "type": "string", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "AKFBNELAMNA", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "BADIGBCKECA", + "type": "int", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "BNBLOBAEDEE", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "CDINMMPNAID", + "type": "NMJEKMMBDDE", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "CDNFGMHLDMJ", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "FIKEBGGCDFN", + "type": "string", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "GIBHIMGJNNO", + "type": "GPDIAKNEBKH", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "HPLBOMGACED", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "IGOPGMHHLKI", + "type": "string", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "JFEAPMIPNEP", + "type": "DPLPMKMFMPB", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "KHIJAFCHLIA", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "KLNJBBPNMBJ", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "KODBEJPEFOJ", + "type": "string", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "LPJLEMJFBPE", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "MGBDHBHCDMH", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "MIKDLDEALPN", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "OFONEIOEIED", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "OILEJFNPDDB", + "type": "HJPGEGENLPH", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "PEEFHKMOMKK", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + } + ], + "inheritedWireKeys": [ + "RoomId", + "IsDorm", + "CloningAllowed", + "DisableMicAutoMute", + "DisableRoomComments", + "EncryptVoiceChat" + ] + } + } + } + ], + "contracts": [ + { + "kind": "object", + "raw": "PPENFJMFPNE", + "rootType": "PPENFJMFPNE", + "typeDescription": { + "name": "PPENFJMFPNE", + "found": true, + "file": "PPENFJMFPNE.cs", + "declaration": "public class PPENFJMFPNE : KLCOGEIGEBJ", + "kind": "class", + "baseType": "KLCOGEIGEBJ", + "properties": [ + { + "name": "ADCDBMGNIPB", + "type": "List", + "accessibility": "public", + "kind": "property" + }, + { + "name": "BIODFICLKJH", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "DFKGKGIMEMP", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "EJGIMHADHBC", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "FEBCCOFBKKJ", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "IAJDFKODGFL", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "KFFBPMHGFKB", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "LHGHLGDINFL", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "LOJPLANGFMG", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "MMBJPGGOFHL", + "type": "LMLJHMJEIGM", + "accessibility": "public", + "kind": "property" + }, + { + "name": "MMFBPKOOLNG", + "type": "bool", + "accessibility": "public", + "kind": "property" + }, + { + "name": "OHNIGJECGKL", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "PKKADKGDHNI", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + } + ], + "fields": [ + { + "name": "accountId", + "type": "int", + "kind": "field" + }, + { + "name": "tag", + "type": "string", + "kind": "field" + }, + { + "name": "IGAPDHGFLDC", + "type": "PPENFJMFPNE", + "kind": "field" + } + ], + "enumValues": [], + "wireKeys": [], + "isilFile": "PPENFJMFPNE.txt", + "isilType": "PPENFJMFPNE", + "inherits": "KLCOGEIGEBJ", + "inheritedMembers": [ + { + "name": "DADOKMAOFJL", + "type": "long", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "KLCOGEIGEBJ" + }, + { + "name": "GJPGKJJCPBK", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "KLCOGEIGEBJ" + }, + { + "name": "IBLOJJBEKFF", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "KLCOGEIGEBJ" + }, + { + "name": "JCLCOCEOAEP", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "KLCOGEIGEBJ" + }, + { + "name": "JGJCBGEBLLO", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "KLCOGEIGEBJ" + }, + { + "name": "PLFEEIJCHOH", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "KLCOGEIGEBJ" + }, + { + "name": "BJFNPKFOALK", + "type": "long", + "kind": "field", + "inheritedFrom": "KLCOGEIGEBJ" + }, + { + "name": "ACBFDMLHFPB", + "type": "DateTime", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "AHGCOGFEEEE", + "type": "string", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "AKFBNELAMNA", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "BADIGBCKECA", + "type": "int", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "BNBLOBAEDEE", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "CDINMMPNAID", + "type": "NMJEKMMBDDE", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "CDNFGMHLDMJ", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "FIKEBGGCDFN", + "type": "string", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "GIBHIMGJNNO", + "type": "GPDIAKNEBKH", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "HPLBOMGACED", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "IGOPGMHHLKI", + "type": "string", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "JFEAPMIPNEP", + "type": "DPLPMKMFMPB", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "KHIJAFCHLIA", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "KLNJBBPNMBJ", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "KODBEJPEFOJ", + "type": "string", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "LPJLEMJFBPE", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "MGBDHBHCDMH", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "MIKDLDEALPN", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "OFONEIOEIED", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "OILEJFNPDDB", + "type": "HJPGEGENLPH", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "PEEFHKMOMKK", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + } + ], + "inheritedWireKeys": [ + "RoomId", + "IsDorm", + "CloningAllowed", + "DisableMicAutoMute", + "DisableRoomComments", + "EncryptVoiceChat" + ] + } + } + ] + }, + { + "family": "rooms", + "route": "rooms/{0}/promo_images", + "evidence": [ + { + "type": "EJDCNGBEICB", + "method": "RecRoom.Async.IPromise`1 CFIKOAAOHFH(System.Int64 HNHLJONGKHB, System.String HFLPBHHAFIO)", + "file": "EJDCNGBEICB.txt", + "line": 4663, + "contract": { + "kind": "object", + "raw": "PPENFJMFPNE", + "rootType": "PPENFJMFPNE", + "typeDescription": { + "name": "PPENFJMFPNE", + "found": true, + "file": "PPENFJMFPNE.cs", + "declaration": "public class PPENFJMFPNE : KLCOGEIGEBJ", + "kind": "class", + "baseType": "KLCOGEIGEBJ", + "properties": [ + { + "name": "ADCDBMGNIPB", + "type": "List", + "accessibility": "public", + "kind": "property" + }, + { + "name": "BIODFICLKJH", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "DFKGKGIMEMP", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "EJGIMHADHBC", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "FEBCCOFBKKJ", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "IAJDFKODGFL", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "KFFBPMHGFKB", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "LHGHLGDINFL", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "LOJPLANGFMG", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "MMBJPGGOFHL", + "type": "LMLJHMJEIGM", + "accessibility": "public", + "kind": "property" + }, + { + "name": "MMFBPKOOLNG", + "type": "bool", + "accessibility": "public", + "kind": "property" + }, + { + "name": "OHNIGJECGKL", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "PKKADKGDHNI", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + } + ], + "fields": [ + { + "name": "accountId", + "type": "int", + "kind": "field" + }, + { + "name": "tag", + "type": "string", + "kind": "field" + }, + { + "name": "IGAPDHGFLDC", + "type": "PPENFJMFPNE", + "kind": "field" + } + ], + "enumValues": [], + "wireKeys": [], + "isilFile": "PPENFJMFPNE.txt", + "isilType": "PPENFJMFPNE", + "inherits": "KLCOGEIGEBJ", + "inheritedMembers": [ + { + "name": "DADOKMAOFJL", + "type": "long", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "KLCOGEIGEBJ" + }, + { + "name": "GJPGKJJCPBK", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "KLCOGEIGEBJ" + }, + { + "name": "IBLOJJBEKFF", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "KLCOGEIGEBJ" + }, + { + "name": "JCLCOCEOAEP", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "KLCOGEIGEBJ" + }, + { + "name": "JGJCBGEBLLO", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "KLCOGEIGEBJ" + }, + { + "name": "PLFEEIJCHOH", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "KLCOGEIGEBJ" + }, + { + "name": "BJFNPKFOALK", + "type": "long", + "kind": "field", + "inheritedFrom": "KLCOGEIGEBJ" + }, + { + "name": "ACBFDMLHFPB", + "type": "DateTime", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "AHGCOGFEEEE", + "type": "string", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "AKFBNELAMNA", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "BADIGBCKECA", + "type": "int", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "BNBLOBAEDEE", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "CDINMMPNAID", + "type": "NMJEKMMBDDE", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "CDNFGMHLDMJ", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "FIKEBGGCDFN", + "type": "string", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "GIBHIMGJNNO", + "type": "GPDIAKNEBKH", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "HPLBOMGACED", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "IGOPGMHHLKI", + "type": "string", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "JFEAPMIPNEP", + "type": "DPLPMKMFMPB", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "KHIJAFCHLIA", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "KLNJBBPNMBJ", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "KODBEJPEFOJ", + "type": "string", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "LPJLEMJFBPE", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "MGBDHBHCDMH", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "MIKDLDEALPN", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "OFONEIOEIED", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "OILEJFNPDDB", + "type": "HJPGEGENLPH", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "PEEFHKMOMKK", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + } + ], + "inheritedWireKeys": [ + "RoomId", + "IsDorm", + "CloningAllowed", + "DisableMicAutoMute", + "DisableRoomComments", + "EncryptVoiceChat" + ] + } + } + } + ], + "contracts": [ + { + "kind": "object", + "raw": "PPENFJMFPNE", + "rootType": "PPENFJMFPNE", + "typeDescription": { + "name": "PPENFJMFPNE", + "found": true, + "file": "PPENFJMFPNE.cs", + "declaration": "public class PPENFJMFPNE : KLCOGEIGEBJ", + "kind": "class", + "baseType": "KLCOGEIGEBJ", + "properties": [ + { + "name": "ADCDBMGNIPB", + "type": "List", + "accessibility": "public", + "kind": "property" + }, + { + "name": "BIODFICLKJH", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "DFKGKGIMEMP", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "EJGIMHADHBC", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "FEBCCOFBKKJ", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "IAJDFKODGFL", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "KFFBPMHGFKB", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "LHGHLGDINFL", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "LOJPLANGFMG", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "MMBJPGGOFHL", + "type": "LMLJHMJEIGM", + "accessibility": "public", + "kind": "property" + }, + { + "name": "MMFBPKOOLNG", + "type": "bool", + "accessibility": "public", + "kind": "property" + }, + { + "name": "OHNIGJECGKL", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "PKKADKGDHNI", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + } + ], + "fields": [ + { + "name": "accountId", + "type": "int", + "kind": "field" + }, + { + "name": "tag", + "type": "string", + "kind": "field" + }, + { + "name": "IGAPDHGFLDC", + "type": "PPENFJMFPNE", + "kind": "field" + } + ], + "enumValues": [], + "wireKeys": [], + "isilFile": "PPENFJMFPNE.txt", + "isilType": "PPENFJMFPNE", + "inherits": "KLCOGEIGEBJ", + "inheritedMembers": [ + { + "name": "DADOKMAOFJL", + "type": "long", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "KLCOGEIGEBJ" + }, + { + "name": "GJPGKJJCPBK", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "KLCOGEIGEBJ" + }, + { + "name": "IBLOJJBEKFF", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "KLCOGEIGEBJ" + }, + { + "name": "JCLCOCEOAEP", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "KLCOGEIGEBJ" + }, + { + "name": "JGJCBGEBLLO", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "KLCOGEIGEBJ" + }, + { + "name": "PLFEEIJCHOH", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "KLCOGEIGEBJ" + }, + { + "name": "BJFNPKFOALK", + "type": "long", + "kind": "field", + "inheritedFrom": "KLCOGEIGEBJ" + }, + { + "name": "ACBFDMLHFPB", + "type": "DateTime", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "AHGCOGFEEEE", + "type": "string", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "AKFBNELAMNA", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "BADIGBCKECA", + "type": "int", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "BNBLOBAEDEE", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "CDINMMPNAID", + "type": "NMJEKMMBDDE", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "CDNFGMHLDMJ", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "FIKEBGGCDFN", + "type": "string", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "GIBHIMGJNNO", + "type": "GPDIAKNEBKH", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "HPLBOMGACED", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "IGOPGMHHLKI", + "type": "string", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "JFEAPMIPNEP", + "type": "DPLPMKMFMPB", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "KHIJAFCHLIA", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "KLNJBBPNMBJ", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "KODBEJPEFOJ", + "type": "string", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "LPJLEMJFBPE", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "MGBDHBHCDMH", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "MIKDLDEALPN", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "OFONEIOEIED", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "OILEJFNPDDB", + "type": "HJPGEGENLPH", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "PEEFHKMOMKK", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + } + ], + "inheritedWireKeys": [ + "RoomId", + "IsDorm", + "CloningAllowed", + "DisableMicAutoMute", + "DisableRoomComments", + "EncryptVoiceChat" + ] + } + } + ] + }, + { + "family": "rooms", + "route": "rooms/{0}/promo_images/{1}", + "evidence": [ + { + "type": "EJDCNGBEICB", + "method": "RecRoom.Async.IPromise`1 OCCGHPAHGKC(System.Int64 HNHLJONGKHB, System.String HFLPBHHAFIO)", + "file": "EJDCNGBEICB.txt", + "line": 4749, + "contract": { + "kind": "object", + "raw": "PPENFJMFPNE", + "rootType": "PPENFJMFPNE", + "typeDescription": { + "name": "PPENFJMFPNE", + "found": true, + "file": "PPENFJMFPNE.cs", + "declaration": "public class PPENFJMFPNE : KLCOGEIGEBJ", + "kind": "class", + "baseType": "KLCOGEIGEBJ", + "properties": [ + { + "name": "ADCDBMGNIPB", + "type": "List", + "accessibility": "public", + "kind": "property" + }, + { + "name": "BIODFICLKJH", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "DFKGKGIMEMP", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "EJGIMHADHBC", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "FEBCCOFBKKJ", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "IAJDFKODGFL", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "KFFBPMHGFKB", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "LHGHLGDINFL", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "LOJPLANGFMG", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "MMBJPGGOFHL", + "type": "LMLJHMJEIGM", + "accessibility": "public", + "kind": "property" + }, + { + "name": "MMFBPKOOLNG", + "type": "bool", + "accessibility": "public", + "kind": "property" + }, + { + "name": "OHNIGJECGKL", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "PKKADKGDHNI", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + } + ], + "fields": [ + { + "name": "accountId", + "type": "int", + "kind": "field" + }, + { + "name": "tag", + "type": "string", + "kind": "field" + }, + { + "name": "IGAPDHGFLDC", + "type": "PPENFJMFPNE", + "kind": "field" + } + ], + "enumValues": [], + "wireKeys": [], + "isilFile": "PPENFJMFPNE.txt", + "isilType": "PPENFJMFPNE", + "inherits": "KLCOGEIGEBJ", + "inheritedMembers": [ + { + "name": "DADOKMAOFJL", + "type": "long", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "KLCOGEIGEBJ" + }, + { + "name": "GJPGKJJCPBK", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "KLCOGEIGEBJ" + }, + { + "name": "IBLOJJBEKFF", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "KLCOGEIGEBJ" + }, + { + "name": "JCLCOCEOAEP", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "KLCOGEIGEBJ" + }, + { + "name": "JGJCBGEBLLO", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "KLCOGEIGEBJ" + }, + { + "name": "PLFEEIJCHOH", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "KLCOGEIGEBJ" + }, + { + "name": "BJFNPKFOALK", + "type": "long", + "kind": "field", + "inheritedFrom": "KLCOGEIGEBJ" + }, + { + "name": "ACBFDMLHFPB", + "type": "DateTime", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "AHGCOGFEEEE", + "type": "string", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "AKFBNELAMNA", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "BADIGBCKECA", + "type": "int", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "BNBLOBAEDEE", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "CDINMMPNAID", + "type": "NMJEKMMBDDE", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "CDNFGMHLDMJ", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "FIKEBGGCDFN", + "type": "string", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "GIBHIMGJNNO", + "type": "GPDIAKNEBKH", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "HPLBOMGACED", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "IGOPGMHHLKI", + "type": "string", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "JFEAPMIPNEP", + "type": "DPLPMKMFMPB", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "KHIJAFCHLIA", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "KLNJBBPNMBJ", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "KODBEJPEFOJ", + "type": "string", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "LPJLEMJFBPE", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "MGBDHBHCDMH", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "MIKDLDEALPN", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "OFONEIOEIED", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "OILEJFNPDDB", + "type": "HJPGEGENLPH", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "PEEFHKMOMKK", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + } + ], + "inheritedWireKeys": [ + "RoomId", + "IsDorm", + "CloningAllowed", + "DisableMicAutoMute", + "DisableRoomComments", + "EncryptVoiceChat" + ] + } + } + } + ], + "contracts": [ + { + "kind": "object", + "raw": "PPENFJMFPNE", + "rootType": "PPENFJMFPNE", + "typeDescription": { + "name": "PPENFJMFPNE", + "found": true, + "file": "PPENFJMFPNE.cs", + "declaration": "public class PPENFJMFPNE : KLCOGEIGEBJ", + "kind": "class", + "baseType": "KLCOGEIGEBJ", + "properties": [ + { + "name": "ADCDBMGNIPB", + "type": "List", + "accessibility": "public", + "kind": "property" + }, + { + "name": "BIODFICLKJH", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "DFKGKGIMEMP", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "EJGIMHADHBC", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "FEBCCOFBKKJ", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "IAJDFKODGFL", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "KFFBPMHGFKB", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "LHGHLGDINFL", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "LOJPLANGFMG", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "MMBJPGGOFHL", + "type": "LMLJHMJEIGM", + "accessibility": "public", + "kind": "property" + }, + { + "name": "MMFBPKOOLNG", + "type": "bool", + "accessibility": "public", + "kind": "property" + }, + { + "name": "OHNIGJECGKL", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "PKKADKGDHNI", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + } + ], + "fields": [ + { + "name": "accountId", + "type": "int", + "kind": "field" + }, + { + "name": "tag", + "type": "string", + "kind": "field" + }, + { + "name": "IGAPDHGFLDC", + "type": "PPENFJMFPNE", + "kind": "field" + } + ], + "enumValues": [], + "wireKeys": [], + "isilFile": "PPENFJMFPNE.txt", + "isilType": "PPENFJMFPNE", + "inherits": "KLCOGEIGEBJ", + "inheritedMembers": [ + { + "name": "DADOKMAOFJL", + "type": "long", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "KLCOGEIGEBJ" + }, + { + "name": "GJPGKJJCPBK", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "KLCOGEIGEBJ" + }, + { + "name": "IBLOJJBEKFF", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "KLCOGEIGEBJ" + }, + { + "name": "JCLCOCEOAEP", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "KLCOGEIGEBJ" + }, + { + "name": "JGJCBGEBLLO", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "KLCOGEIGEBJ" + }, + { + "name": "PLFEEIJCHOH", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "KLCOGEIGEBJ" + }, + { + "name": "BJFNPKFOALK", + "type": "long", + "kind": "field", + "inheritedFrom": "KLCOGEIGEBJ" + }, + { + "name": "ACBFDMLHFPB", + "type": "DateTime", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "AHGCOGFEEEE", + "type": "string", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "AKFBNELAMNA", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "BADIGBCKECA", + "type": "int", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "BNBLOBAEDEE", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "CDINMMPNAID", + "type": "NMJEKMMBDDE", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "CDNFGMHLDMJ", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "FIKEBGGCDFN", + "type": "string", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "GIBHIMGJNNO", + "type": "GPDIAKNEBKH", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "HPLBOMGACED", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "IGOPGMHHLKI", + "type": "string", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "JFEAPMIPNEP", + "type": "DPLPMKMFMPB", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "KHIJAFCHLIA", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "KLNJBBPNMBJ", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "KODBEJPEFOJ", + "type": "string", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "LPJLEMJFBPE", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "MGBDHBHCDMH", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "MIKDLDEALPN", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "OFONEIOEIED", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "OILEJFNPDDB", + "type": "HJPGEGENLPH", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "PEEFHKMOMKK", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + } + ], + "inheritedWireKeys": [ + "RoomId", + "IsDorm", + "CloningAllowed", + "DisableMicAutoMute", + "DisableRoomComments", + "EncryptVoiceChat" + ] + } + } + ] + }, + { + "family": "rooms", + "route": "rooms/{0}/restrictions", + "evidence": [ + { + "type": "EJDCNGBEICB", + "method": "RecRoom.Async.IPromise`1 ONEJODPFOMP(System.Int64 HNHLJONGKHB, System.Boolean DDMOEIAHJBK, System.Boolean FOBLJIBLCNI, System.Boolean GELHFEFGJLA, System.Boolean NFIGHDPKLJF)", + "file": "EJDCNGBEICB.txt", + "line": 3758, + "contract": { + "kind": "object", + "raw": "PPENFJMFPNE", + "rootType": "PPENFJMFPNE", + "typeDescription": { + "name": "PPENFJMFPNE", + "found": true, + "file": "PPENFJMFPNE.cs", + "declaration": "public class PPENFJMFPNE : KLCOGEIGEBJ", + "kind": "class", + "baseType": "KLCOGEIGEBJ", + "properties": [ + { + "name": "ADCDBMGNIPB", + "type": "List", + "accessibility": "public", + "kind": "property" + }, + { + "name": "BIODFICLKJH", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "DFKGKGIMEMP", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "EJGIMHADHBC", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "FEBCCOFBKKJ", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "IAJDFKODGFL", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "KFFBPMHGFKB", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "LHGHLGDINFL", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "LOJPLANGFMG", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "MMBJPGGOFHL", + "type": "LMLJHMJEIGM", + "accessibility": "public", + "kind": "property" + }, + { + "name": "MMFBPKOOLNG", + "type": "bool", + "accessibility": "public", + "kind": "property" + }, + { + "name": "OHNIGJECGKL", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "PKKADKGDHNI", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + } + ], + "fields": [ + { + "name": "accountId", + "type": "int", + "kind": "field" + }, + { + "name": "tag", + "type": "string", + "kind": "field" + }, + { + "name": "IGAPDHGFLDC", + "type": "PPENFJMFPNE", + "kind": "field" + } + ], + "enumValues": [], + "wireKeys": [], + "isilFile": "PPENFJMFPNE.txt", + "isilType": "PPENFJMFPNE", + "inherits": "KLCOGEIGEBJ", + "inheritedMembers": [ + { + "name": "DADOKMAOFJL", + "type": "long", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "KLCOGEIGEBJ" + }, + { + "name": "GJPGKJJCPBK", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "KLCOGEIGEBJ" + }, + { + "name": "IBLOJJBEKFF", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "KLCOGEIGEBJ" + }, + { + "name": "JCLCOCEOAEP", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "KLCOGEIGEBJ" + }, + { + "name": "JGJCBGEBLLO", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "KLCOGEIGEBJ" + }, + { + "name": "PLFEEIJCHOH", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "KLCOGEIGEBJ" + }, + { + "name": "BJFNPKFOALK", + "type": "long", + "kind": "field", + "inheritedFrom": "KLCOGEIGEBJ" + }, + { + "name": "ACBFDMLHFPB", + "type": "DateTime", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "AHGCOGFEEEE", + "type": "string", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "AKFBNELAMNA", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "BADIGBCKECA", + "type": "int", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "BNBLOBAEDEE", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "CDINMMPNAID", + "type": "NMJEKMMBDDE", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "CDNFGMHLDMJ", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "FIKEBGGCDFN", + "type": "string", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "GIBHIMGJNNO", + "type": "GPDIAKNEBKH", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "HPLBOMGACED", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "IGOPGMHHLKI", + "type": "string", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "JFEAPMIPNEP", + "type": "DPLPMKMFMPB", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "KHIJAFCHLIA", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "KLNJBBPNMBJ", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "KODBEJPEFOJ", + "type": "string", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "LPJLEMJFBPE", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "MGBDHBHCDMH", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "MIKDLDEALPN", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "OFONEIOEIED", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "OILEJFNPDDB", + "type": "HJPGEGENLPH", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "PEEFHKMOMKK", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + } + ], + "inheritedWireKeys": [ + "RoomId", + "IsDorm", + "CloningAllowed", + "DisableMicAutoMute", + "DisableRoomComments", + "EncryptVoiceChat" + ] + } + } + } + ], + "contracts": [ + { + "kind": "object", + "raw": "PPENFJMFPNE", + "rootType": "PPENFJMFPNE", + "typeDescription": { + "name": "PPENFJMFPNE", + "found": true, + "file": "PPENFJMFPNE.cs", + "declaration": "public class PPENFJMFPNE : KLCOGEIGEBJ", + "kind": "class", + "baseType": "KLCOGEIGEBJ", + "properties": [ + { + "name": "ADCDBMGNIPB", + "type": "List", + "accessibility": "public", + "kind": "property" + }, + { + "name": "BIODFICLKJH", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "DFKGKGIMEMP", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "EJGIMHADHBC", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "FEBCCOFBKKJ", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "IAJDFKODGFL", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "KFFBPMHGFKB", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "LHGHLGDINFL", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "LOJPLANGFMG", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "MMBJPGGOFHL", + "type": "LMLJHMJEIGM", + "accessibility": "public", + "kind": "property" + }, + { + "name": "MMFBPKOOLNG", + "type": "bool", + "accessibility": "public", + "kind": "property" + }, + { + "name": "OHNIGJECGKL", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "PKKADKGDHNI", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + } + ], + "fields": [ + { + "name": "accountId", + "type": "int", + "kind": "field" + }, + { + "name": "tag", + "type": "string", + "kind": "field" + }, + { + "name": "IGAPDHGFLDC", + "type": "PPENFJMFPNE", + "kind": "field" + } + ], + "enumValues": [], + "wireKeys": [], + "isilFile": "PPENFJMFPNE.txt", + "isilType": "PPENFJMFPNE", + "inherits": "KLCOGEIGEBJ", + "inheritedMembers": [ + { + "name": "DADOKMAOFJL", + "type": "long", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "KLCOGEIGEBJ" + }, + { + "name": "GJPGKJJCPBK", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "KLCOGEIGEBJ" + }, + { + "name": "IBLOJJBEKFF", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "KLCOGEIGEBJ" + }, + { + "name": "JCLCOCEOAEP", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "KLCOGEIGEBJ" + }, + { + "name": "JGJCBGEBLLO", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "KLCOGEIGEBJ" + }, + { + "name": "PLFEEIJCHOH", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "KLCOGEIGEBJ" + }, + { + "name": "BJFNPKFOALK", + "type": "long", + "kind": "field", + "inheritedFrom": "KLCOGEIGEBJ" + }, + { + "name": "ACBFDMLHFPB", + "type": "DateTime", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "AHGCOGFEEEE", + "type": "string", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "AKFBNELAMNA", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "BADIGBCKECA", + "type": "int", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "BNBLOBAEDEE", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "CDINMMPNAID", + "type": "NMJEKMMBDDE", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "CDNFGMHLDMJ", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "FIKEBGGCDFN", + "type": "string", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "GIBHIMGJNNO", + "type": "GPDIAKNEBKH", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "HPLBOMGACED", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "IGOPGMHHLKI", + "type": "string", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "JFEAPMIPNEP", + "type": "DPLPMKMFMPB", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "KHIJAFCHLIA", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "KLNJBBPNMBJ", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "KODBEJPEFOJ", + "type": "string", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "LPJLEMJFBPE", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "MGBDHBHCDMH", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "MIKDLDEALPN", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "OFONEIOEIED", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "OILEJFNPDDB", + "type": "HJPGEGENLPH", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "PEEFHKMOMKK", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + } + ], + "inheritedWireKeys": [ + "RoomId", + "IsDorm", + "CloningAllowed", + "DisableMicAutoMute", + "DisableRoomComments", + "EncryptVoiceChat" + ] + } + } + ] + }, + { + "family": "rooms", + "route": "rooms/{0}/roles", + "evidence": [ + { + "type": "EJDCNGBEICB", + "method": "RecRoom.Async.IPromise`1> MIGOLOHAIAL(System.Int64 HNHLJONGKHB)", + "file": "EJDCNGBEICB.txt", + "line": 1254, + "contract": { + "kind": "array", + "raw": "System.Collections.Generic.List`1", + "rootType": "CGCEKBCIHJC", + "item": { + "kind": "object", + "raw": "CGCEKBCIHJC", + "rootType": "CGCEKBCIHJC" + }, + "typeDescription": { + "name": "CGCEKBCIHJC", + "found": true, + "file": "CGCEKBCIHJC.cs", + "declaration": "public class CGCEKBCIHJC : IFAIJAGLDFK", + "kind": "class", + "baseType": "IFAIJAGLDFK", + "properties": [ + { + "name": "CAILAOLBBPM", + "type": "LMLJHMJEIGM", + "accessibility": "public", + "kind": "property" + }, + { + "name": "CBLIGPOFEDE", + "type": "LMLJHMJEIGM", + "accessibility": "public", + "kind": "property" + }, + { + "name": "GAINIOENNCG", + "type": "ObscuredInt", + "accessibility": "public", + "kind": "property" + } + ], + "fields": [], + "enumValues": [], + "wireKeys": [ + "AccountId", + "Role", + "InvitedRole" + ], + "isilFile": "CGCEKBCIHJC.txt", + "isilType": "CGCEKBCIHJC" + } + } + } + ], + "contracts": [ + { + "kind": "array", + "raw": "System.Collections.Generic.List`1", + "rootType": "CGCEKBCIHJC", + "item": { + "kind": "object", + "raw": "CGCEKBCIHJC", + "rootType": "CGCEKBCIHJC" + }, + "typeDescription": { + "name": "CGCEKBCIHJC", + "found": true, + "file": "CGCEKBCIHJC.cs", + "declaration": "public class CGCEKBCIHJC : IFAIJAGLDFK", + "kind": "class", + "baseType": "IFAIJAGLDFK", + "properties": [ + { + "name": "CAILAOLBBPM", + "type": "LMLJHMJEIGM", + "accessibility": "public", + "kind": "property" + }, + { + "name": "CBLIGPOFEDE", + "type": "LMLJHMJEIGM", + "accessibility": "public", + "kind": "property" + }, + { + "name": "GAINIOENNCG", + "type": "ObscuredInt", + "accessibility": "public", + "kind": "property" + } + ], + "fields": [], + "enumValues": [], + "wireKeys": [ + "AccountId", + "Role", + "InvitedRole" + ], + "isilFile": "CGCEKBCIHJC.txt", + "isilType": "CGCEKBCIHJC" + } + } + ] + }, + { + "family": "rooms", + "route": "rooms/{0}/roles/{1}", + "evidence": [ + { + "type": "EJDCNGBEICB", + "method": "RecRoom.Async.IPromise`1 EKOKFNHHMPI(System.Int64 HNHLJONGKHB, System.Int32 GKLPIFBPGOD)", + "file": "EJDCNGBEICB.txt", + "line": 1332, + "contract": { + "kind": "object", + "raw": "CGCEKBCIHJC", + "rootType": "CGCEKBCIHJC", + "typeDescription": { + "name": "CGCEKBCIHJC", + "found": true, + "file": "CGCEKBCIHJC.cs", + "declaration": "public class CGCEKBCIHJC : IFAIJAGLDFK", + "kind": "class", + "baseType": "IFAIJAGLDFK", + "properties": [ + { + "name": "CAILAOLBBPM", + "type": "LMLJHMJEIGM", + "accessibility": "public", + "kind": "property" + }, + { + "name": "CBLIGPOFEDE", + "type": "LMLJHMJEIGM", + "accessibility": "public", + "kind": "property" + }, + { + "name": "GAINIOENNCG", + "type": "ObscuredInt", + "accessibility": "public", + "kind": "property" + } + ], + "fields": [], + "enumValues": [], + "wireKeys": [ + "AccountId", + "Role", + "InvitedRole" + ], + "isilFile": "CGCEKBCIHJC.txt", + "isilType": "CGCEKBCIHJC" + } + } + }, + { + "type": "EJDCNGBEICB", + "method": "RecRoom.Async.IPromise`1 GJMGBBCIHGH(System.Int64 HNHLJONGKHB, System.Int32 GKLPIFBPGOD, LMLJHMJEIGM IENKDAKBEDP)", + "file": "EJDCNGBEICB.txt", + "line": 4405, + "contract": { + "kind": "object", + "raw": "PPENFJMFPNE", + "rootType": "PPENFJMFPNE", + "typeDescription": { + "name": "PPENFJMFPNE", + "found": true, + "file": "PPENFJMFPNE.cs", + "declaration": "public class PPENFJMFPNE : KLCOGEIGEBJ", + "kind": "class", + "baseType": "KLCOGEIGEBJ", + "properties": [ + { + "name": "ADCDBMGNIPB", + "type": "List", + "accessibility": "public", + "kind": "property" + }, + { + "name": "BIODFICLKJH", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "DFKGKGIMEMP", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "EJGIMHADHBC", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "FEBCCOFBKKJ", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "IAJDFKODGFL", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "KFFBPMHGFKB", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "LHGHLGDINFL", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "LOJPLANGFMG", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "MMBJPGGOFHL", + "type": "LMLJHMJEIGM", + "accessibility": "public", + "kind": "property" + }, + { + "name": "MMFBPKOOLNG", + "type": "bool", + "accessibility": "public", + "kind": "property" + }, + { + "name": "OHNIGJECGKL", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "PKKADKGDHNI", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + } + ], + "fields": [ + { + "name": "accountId", + "type": "int", + "kind": "field" + }, + { + "name": "tag", + "type": "string", + "kind": "field" + }, + { + "name": "IGAPDHGFLDC", + "type": "PPENFJMFPNE", + "kind": "field" + } + ], + "enumValues": [], + "wireKeys": [], + "isilFile": "PPENFJMFPNE.txt", + "isilType": "PPENFJMFPNE", + "inherits": "KLCOGEIGEBJ", + "inheritedMembers": [ + { + "name": "DADOKMAOFJL", + "type": "long", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "KLCOGEIGEBJ" + }, + { + "name": "GJPGKJJCPBK", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "KLCOGEIGEBJ" + }, + { + "name": "IBLOJJBEKFF", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "KLCOGEIGEBJ" + }, + { + "name": "JCLCOCEOAEP", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "KLCOGEIGEBJ" + }, + { + "name": "JGJCBGEBLLO", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "KLCOGEIGEBJ" + }, + { + "name": "PLFEEIJCHOH", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "KLCOGEIGEBJ" + }, + { + "name": "BJFNPKFOALK", + "type": "long", + "kind": "field", + "inheritedFrom": "KLCOGEIGEBJ" + }, + { + "name": "ACBFDMLHFPB", + "type": "DateTime", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "AHGCOGFEEEE", + "type": "string", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "AKFBNELAMNA", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "BADIGBCKECA", + "type": "int", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "BNBLOBAEDEE", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "CDINMMPNAID", + "type": "NMJEKMMBDDE", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "CDNFGMHLDMJ", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "FIKEBGGCDFN", + "type": "string", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "GIBHIMGJNNO", + "type": "GPDIAKNEBKH", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "HPLBOMGACED", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "IGOPGMHHLKI", + "type": "string", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "JFEAPMIPNEP", + "type": "DPLPMKMFMPB", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "KHIJAFCHLIA", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "KLNJBBPNMBJ", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "KODBEJPEFOJ", + "type": "string", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "LPJLEMJFBPE", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "MGBDHBHCDMH", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "MIKDLDEALPN", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "OFONEIOEIED", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "OILEJFNPDDB", + "type": "HJPGEGENLPH", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "PEEFHKMOMKK", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + } + ], + "inheritedWireKeys": [ + "RoomId", + "IsDorm", + "CloningAllowed", + "DisableMicAutoMute", + "DisableRoomComments", + "EncryptVoiceChat" + ] + } + } + } + ], + "contracts": [ + { + "kind": "object", + "raw": "CGCEKBCIHJC", + "rootType": "CGCEKBCIHJC", + "typeDescription": { + "name": "CGCEKBCIHJC", + "found": true, + "file": "CGCEKBCIHJC.cs", + "declaration": "public class CGCEKBCIHJC : IFAIJAGLDFK", + "kind": "class", + "baseType": "IFAIJAGLDFK", + "properties": [ + { + "name": "CAILAOLBBPM", + "type": "LMLJHMJEIGM", + "accessibility": "public", + "kind": "property" + }, + { + "name": "CBLIGPOFEDE", + "type": "LMLJHMJEIGM", + "accessibility": "public", + "kind": "property" + }, + { + "name": "GAINIOENNCG", + "type": "ObscuredInt", + "accessibility": "public", + "kind": "property" + } + ], + "fields": [], + "enumValues": [], + "wireKeys": [ + "AccountId", + "Role", + "InvitedRole" + ], + "isilFile": "CGCEKBCIHJC.txt", + "isilType": "CGCEKBCIHJC" + } + }, + { + "kind": "object", + "raw": "PPENFJMFPNE", + "rootType": "PPENFJMFPNE", + "typeDescription": { + "name": "PPENFJMFPNE", + "found": true, + "file": "PPENFJMFPNE.cs", + "declaration": "public class PPENFJMFPNE : KLCOGEIGEBJ", + "kind": "class", + "baseType": "KLCOGEIGEBJ", + "properties": [ + { + "name": "ADCDBMGNIPB", + "type": "List", + "accessibility": "public", + "kind": "property" + }, + { + "name": "BIODFICLKJH", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "DFKGKGIMEMP", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "EJGIMHADHBC", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "FEBCCOFBKKJ", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "IAJDFKODGFL", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "KFFBPMHGFKB", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "LHGHLGDINFL", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "LOJPLANGFMG", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "MMBJPGGOFHL", + "type": "LMLJHMJEIGM", + "accessibility": "public", + "kind": "property" + }, + { + "name": "MMFBPKOOLNG", + "type": "bool", + "accessibility": "public", + "kind": "property" + }, + { + "name": "OHNIGJECGKL", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "PKKADKGDHNI", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + } + ], + "fields": [ + { + "name": "accountId", + "type": "int", + "kind": "field" + }, + { + "name": "tag", + "type": "string", + "kind": "field" + }, + { + "name": "IGAPDHGFLDC", + "type": "PPENFJMFPNE", + "kind": "field" + } + ], + "enumValues": [], + "wireKeys": [], + "isilFile": "PPENFJMFPNE.txt", + "isilType": "PPENFJMFPNE", + "inherits": "KLCOGEIGEBJ", + "inheritedMembers": [ + { + "name": "DADOKMAOFJL", + "type": "long", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "KLCOGEIGEBJ" + }, + { + "name": "GJPGKJJCPBK", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "KLCOGEIGEBJ" + }, + { + "name": "IBLOJJBEKFF", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "KLCOGEIGEBJ" + }, + { + "name": "JCLCOCEOAEP", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "KLCOGEIGEBJ" + }, + { + "name": "JGJCBGEBLLO", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "KLCOGEIGEBJ" + }, + { + "name": "PLFEEIJCHOH", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "KLCOGEIGEBJ" + }, + { + "name": "BJFNPKFOALK", + "type": "long", + "kind": "field", + "inheritedFrom": "KLCOGEIGEBJ" + }, + { + "name": "ACBFDMLHFPB", + "type": "DateTime", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "AHGCOGFEEEE", + "type": "string", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "AKFBNELAMNA", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "BADIGBCKECA", + "type": "int", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "BNBLOBAEDEE", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "CDINMMPNAID", + "type": "NMJEKMMBDDE", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "CDNFGMHLDMJ", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "FIKEBGGCDFN", + "type": "string", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "GIBHIMGJNNO", + "type": "GPDIAKNEBKH", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "HPLBOMGACED", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "IGOPGMHHLKI", + "type": "string", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "JFEAPMIPNEP", + "type": "DPLPMKMFMPB", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "KHIJAFCHLIA", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "KLNJBBPNMBJ", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "KODBEJPEFOJ", + "type": "string", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "LPJLEMJFBPE", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "MGBDHBHCDMH", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "MIKDLDEALPN", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "OFONEIOEIED", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "OILEJFNPDDB", + "type": "HJPGEGENLPH", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "PEEFHKMOMKK", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + } + ], + "inheritedWireKeys": [ + "RoomId", + "IsDorm", + "CloningAllowed", + "DisableMicAutoMute", + "DisableRoomComments", + "EncryptVoiceChat" + ] + } + } + ] + }, + { + "family": "rooms", + "route": "rooms/{0}/roles/{1}/invite", + "evidence": [ + { + "type": "EJDCNGBEICB", + "method": "RecRoom.Async.IPromise`1 MJOHJLHDMHB(System.Int64 HNHLJONGKHB, System.Int32 GKLPIFBPGOD, LMLJHMJEIGM IENKDAKBEDP)", + "file": "EJDCNGBEICB.txt", + "line": 4543, + "contract": { + "kind": "object", + "raw": "PPENFJMFPNE", + "rootType": "PPENFJMFPNE", + "typeDescription": { + "name": "PPENFJMFPNE", + "found": true, + "file": "PPENFJMFPNE.cs", + "declaration": "public class PPENFJMFPNE : KLCOGEIGEBJ", + "kind": "class", + "baseType": "KLCOGEIGEBJ", + "properties": [ + { + "name": "ADCDBMGNIPB", + "type": "List", + "accessibility": "public", + "kind": "property" + }, + { + "name": "BIODFICLKJH", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "DFKGKGIMEMP", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "EJGIMHADHBC", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "FEBCCOFBKKJ", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "IAJDFKODGFL", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "KFFBPMHGFKB", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "LHGHLGDINFL", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "LOJPLANGFMG", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "MMBJPGGOFHL", + "type": "LMLJHMJEIGM", + "accessibility": "public", + "kind": "property" + }, + { + "name": "MMFBPKOOLNG", + "type": "bool", + "accessibility": "public", + "kind": "property" + }, + { + "name": "OHNIGJECGKL", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "PKKADKGDHNI", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + } + ], + "fields": [ + { + "name": "accountId", + "type": "int", + "kind": "field" + }, + { + "name": "tag", + "type": "string", + "kind": "field" + }, + { + "name": "IGAPDHGFLDC", + "type": "PPENFJMFPNE", + "kind": "field" + } + ], + "enumValues": [], + "wireKeys": [], + "isilFile": "PPENFJMFPNE.txt", + "isilType": "PPENFJMFPNE", + "inherits": "KLCOGEIGEBJ", + "inheritedMembers": [ + { + "name": "DADOKMAOFJL", + "type": "long", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "KLCOGEIGEBJ" + }, + { + "name": "GJPGKJJCPBK", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "KLCOGEIGEBJ" + }, + { + "name": "IBLOJJBEKFF", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "KLCOGEIGEBJ" + }, + { + "name": "JCLCOCEOAEP", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "KLCOGEIGEBJ" + }, + { + "name": "JGJCBGEBLLO", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "KLCOGEIGEBJ" + }, + { + "name": "PLFEEIJCHOH", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "KLCOGEIGEBJ" + }, + { + "name": "BJFNPKFOALK", + "type": "long", + "kind": "field", + "inheritedFrom": "KLCOGEIGEBJ" + }, + { + "name": "ACBFDMLHFPB", + "type": "DateTime", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "AHGCOGFEEEE", + "type": "string", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "AKFBNELAMNA", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "BADIGBCKECA", + "type": "int", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "BNBLOBAEDEE", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "CDINMMPNAID", + "type": "NMJEKMMBDDE", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "CDNFGMHLDMJ", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "FIKEBGGCDFN", + "type": "string", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "GIBHIMGJNNO", + "type": "GPDIAKNEBKH", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "HPLBOMGACED", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "IGOPGMHHLKI", + "type": "string", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "JFEAPMIPNEP", + "type": "DPLPMKMFMPB", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "KHIJAFCHLIA", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "KLNJBBPNMBJ", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "KODBEJPEFOJ", + "type": "string", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "LPJLEMJFBPE", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "MGBDHBHCDMH", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "MIKDLDEALPN", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "OFONEIOEIED", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "OILEJFNPDDB", + "type": "HJPGEGENLPH", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "PEEFHKMOMKK", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + } + ], + "inheritedWireKeys": [ + "RoomId", + "IsDorm", + "CloningAllowed", + "DisableMicAutoMute", + "DisableRoomComments", + "EncryptVoiceChat" + ] + } + } + } + ], + "contracts": [ + { + "kind": "object", + "raw": "PPENFJMFPNE", + "rootType": "PPENFJMFPNE", + "typeDescription": { + "name": "PPENFJMFPNE", + "found": true, + "file": "PPENFJMFPNE.cs", + "declaration": "public class PPENFJMFPNE : KLCOGEIGEBJ", + "kind": "class", + "baseType": "KLCOGEIGEBJ", + "properties": [ + { + "name": "ADCDBMGNIPB", + "type": "List", + "accessibility": "public", + "kind": "property" + }, + { + "name": "BIODFICLKJH", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "DFKGKGIMEMP", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "EJGIMHADHBC", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "FEBCCOFBKKJ", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "IAJDFKODGFL", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "KFFBPMHGFKB", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "LHGHLGDINFL", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "LOJPLANGFMG", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "MMBJPGGOFHL", + "type": "LMLJHMJEIGM", + "accessibility": "public", + "kind": "property" + }, + { + "name": "MMFBPKOOLNG", + "type": "bool", + "accessibility": "public", + "kind": "property" + }, + { + "name": "OHNIGJECGKL", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "PKKADKGDHNI", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + } + ], + "fields": [ + { + "name": "accountId", + "type": "int", + "kind": "field" + }, + { + "name": "tag", + "type": "string", + "kind": "field" + }, + { + "name": "IGAPDHGFLDC", + "type": "PPENFJMFPNE", + "kind": "field" + } + ], + "enumValues": [], + "wireKeys": [], + "isilFile": "PPENFJMFPNE.txt", + "isilType": "PPENFJMFPNE", + "inherits": "KLCOGEIGEBJ", + "inheritedMembers": [ + { + "name": "DADOKMAOFJL", + "type": "long", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "KLCOGEIGEBJ" + }, + { + "name": "GJPGKJJCPBK", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "KLCOGEIGEBJ" + }, + { + "name": "IBLOJJBEKFF", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "KLCOGEIGEBJ" + }, + { + "name": "JCLCOCEOAEP", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "KLCOGEIGEBJ" + }, + { + "name": "JGJCBGEBLLO", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "KLCOGEIGEBJ" + }, + { + "name": "PLFEEIJCHOH", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "KLCOGEIGEBJ" + }, + { + "name": "BJFNPKFOALK", + "type": "long", + "kind": "field", + "inheritedFrom": "KLCOGEIGEBJ" + }, + { + "name": "ACBFDMLHFPB", + "type": "DateTime", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "AHGCOGFEEEE", + "type": "string", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "AKFBNELAMNA", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "BADIGBCKECA", + "type": "int", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "BNBLOBAEDEE", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "CDINMMPNAID", + "type": "NMJEKMMBDDE", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "CDNFGMHLDMJ", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "FIKEBGGCDFN", + "type": "string", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "GIBHIMGJNNO", + "type": "GPDIAKNEBKH", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "HPLBOMGACED", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "IGOPGMHHLKI", + "type": "string", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "JFEAPMIPNEP", + "type": "DPLPMKMFMPB", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "KHIJAFCHLIA", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "KLNJBBPNMBJ", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "KODBEJPEFOJ", + "type": "string", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "LPJLEMJFBPE", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "MGBDHBHCDMH", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "MIKDLDEALPN", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "OFONEIOEIED", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "OILEJFNPDDB", + "type": "HJPGEGENLPH", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "PEEFHKMOMKK", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + } + ], + "inheritedWireKeys": [ + "RoomId", + "IsDorm", + "CloningAllowed", + "DisableMicAutoMute", + "DisableRoomComments", + "EncryptVoiceChat" + ] + } + } + ] + }, + { + "family": "rooms", + "route": "rooms/{0}/subrooms", + "evidence": [ + { + "type": "EJDCNGBEICB", + "method": "RecRoom.Async.IPromise`1 OGNIELCNHIM(System.Int64 HNHLJONGKHB, System.String MMBOKOLAJFH)", + "file": "EJDCNGBEICB.txt", + "line": 5487, + "contract": { + "kind": "object", + "raw": "PPENFJMFPNE", + "rootType": "PPENFJMFPNE", + "typeDescription": { + "name": "PPENFJMFPNE", + "found": true, + "file": "PPENFJMFPNE.cs", + "declaration": "public class PPENFJMFPNE : KLCOGEIGEBJ", + "kind": "class", + "baseType": "KLCOGEIGEBJ", + "properties": [ + { + "name": "ADCDBMGNIPB", + "type": "List", + "accessibility": "public", + "kind": "property" + }, + { + "name": "BIODFICLKJH", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "DFKGKGIMEMP", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "EJGIMHADHBC", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "FEBCCOFBKKJ", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "IAJDFKODGFL", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "KFFBPMHGFKB", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "LHGHLGDINFL", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "LOJPLANGFMG", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "MMBJPGGOFHL", + "type": "LMLJHMJEIGM", + "accessibility": "public", + "kind": "property" + }, + { + "name": "MMFBPKOOLNG", + "type": "bool", + "accessibility": "public", + "kind": "property" + }, + { + "name": "OHNIGJECGKL", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "PKKADKGDHNI", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + } + ], + "fields": [ + { + "name": "accountId", + "type": "int", + "kind": "field" + }, + { + "name": "tag", + "type": "string", + "kind": "field" + }, + { + "name": "IGAPDHGFLDC", + "type": "PPENFJMFPNE", + "kind": "field" + } + ], + "enumValues": [], + "wireKeys": [], + "isilFile": "PPENFJMFPNE.txt", + "isilType": "PPENFJMFPNE", + "inherits": "KLCOGEIGEBJ", + "inheritedMembers": [ + { + "name": "DADOKMAOFJL", + "type": "long", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "KLCOGEIGEBJ" + }, + { + "name": "GJPGKJJCPBK", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "KLCOGEIGEBJ" + }, + { + "name": "IBLOJJBEKFF", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "KLCOGEIGEBJ" + }, + { + "name": "JCLCOCEOAEP", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "KLCOGEIGEBJ" + }, + { + "name": "JGJCBGEBLLO", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "KLCOGEIGEBJ" + }, + { + "name": "PLFEEIJCHOH", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "KLCOGEIGEBJ" + }, + { + "name": "BJFNPKFOALK", + "type": "long", + "kind": "field", + "inheritedFrom": "KLCOGEIGEBJ" + }, + { + "name": "ACBFDMLHFPB", + "type": "DateTime", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "AHGCOGFEEEE", + "type": "string", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "AKFBNELAMNA", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "BADIGBCKECA", + "type": "int", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "BNBLOBAEDEE", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "CDINMMPNAID", + "type": "NMJEKMMBDDE", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "CDNFGMHLDMJ", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "FIKEBGGCDFN", + "type": "string", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "GIBHIMGJNNO", + "type": "GPDIAKNEBKH", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "HPLBOMGACED", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "IGOPGMHHLKI", + "type": "string", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "JFEAPMIPNEP", + "type": "DPLPMKMFMPB", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "KHIJAFCHLIA", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "KLNJBBPNMBJ", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "KODBEJPEFOJ", + "type": "string", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "LPJLEMJFBPE", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "MGBDHBHCDMH", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "MIKDLDEALPN", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "OFONEIOEIED", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "OILEJFNPDDB", + "type": "HJPGEGENLPH", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "PEEFHKMOMKK", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + } + ], + "inheritedWireKeys": [ + "RoomId", + "IsDorm", + "CloningAllowed", + "DisableMicAutoMute", + "DisableRoomComments", + "EncryptVoiceChat" + ] + } + } + } + ], + "contracts": [ + { + "kind": "object", + "raw": "PPENFJMFPNE", + "rootType": "PPENFJMFPNE", + "typeDescription": { + "name": "PPENFJMFPNE", + "found": true, + "file": "PPENFJMFPNE.cs", + "declaration": "public class PPENFJMFPNE : KLCOGEIGEBJ", + "kind": "class", + "baseType": "KLCOGEIGEBJ", + "properties": [ + { + "name": "ADCDBMGNIPB", + "type": "List", + "accessibility": "public", + "kind": "property" + }, + { + "name": "BIODFICLKJH", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "DFKGKGIMEMP", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "EJGIMHADHBC", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "FEBCCOFBKKJ", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "IAJDFKODGFL", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "KFFBPMHGFKB", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "LHGHLGDINFL", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "LOJPLANGFMG", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "MMBJPGGOFHL", + "type": "LMLJHMJEIGM", + "accessibility": "public", + "kind": "property" + }, + { + "name": "MMFBPKOOLNG", + "type": "bool", + "accessibility": "public", + "kind": "property" + }, + { + "name": "OHNIGJECGKL", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "PKKADKGDHNI", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + } + ], + "fields": [ + { + "name": "accountId", + "type": "int", + "kind": "field" + }, + { + "name": "tag", + "type": "string", + "kind": "field" + }, + { + "name": "IGAPDHGFLDC", + "type": "PPENFJMFPNE", + "kind": "field" + } + ], + "enumValues": [], + "wireKeys": [], + "isilFile": "PPENFJMFPNE.txt", + "isilType": "PPENFJMFPNE", + "inherits": "KLCOGEIGEBJ", + "inheritedMembers": [ + { + "name": "DADOKMAOFJL", + "type": "long", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "KLCOGEIGEBJ" + }, + { + "name": "GJPGKJJCPBK", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "KLCOGEIGEBJ" + }, + { + "name": "IBLOJJBEKFF", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "KLCOGEIGEBJ" + }, + { + "name": "JCLCOCEOAEP", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "KLCOGEIGEBJ" + }, + { + "name": "JGJCBGEBLLO", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "KLCOGEIGEBJ" + }, + { + "name": "PLFEEIJCHOH", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "KLCOGEIGEBJ" + }, + { + "name": "BJFNPKFOALK", + "type": "long", + "kind": "field", + "inheritedFrom": "KLCOGEIGEBJ" + }, + { + "name": "ACBFDMLHFPB", + "type": "DateTime", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "AHGCOGFEEEE", + "type": "string", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "AKFBNELAMNA", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "BADIGBCKECA", + "type": "int", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "BNBLOBAEDEE", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "CDINMMPNAID", + "type": "NMJEKMMBDDE", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "CDNFGMHLDMJ", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "FIKEBGGCDFN", + "type": "string", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "GIBHIMGJNNO", + "type": "GPDIAKNEBKH", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "HPLBOMGACED", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "IGOPGMHHLKI", + "type": "string", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "JFEAPMIPNEP", + "type": "DPLPMKMFMPB", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "KHIJAFCHLIA", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "KLNJBBPNMBJ", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "KODBEJPEFOJ", + "type": "string", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "LPJLEMJFBPE", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "MGBDHBHCDMH", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "MIKDLDEALPN", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "OFONEIOEIED", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "OILEJFNPDDB", + "type": "HJPGEGENLPH", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "PEEFHKMOMKK", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + } + ], + "inheritedWireKeys": [ + "RoomId", + "IsDorm", + "CloningAllowed", + "DisableMicAutoMute", + "DisableRoomComments", + "EncryptVoiceChat" + ] + } + } + ] + }, + { + "family": "rooms", + "route": "rooms/{0}/subrooms/{1}", + "evidence": [ + { + "type": "EJDCNGBEICB", + "method": "RecRoom.Async.IPromise`1 GGCOGNAGPBP(System.Int64 HNHLJONGKHB, System.Int64 FODGKNJIGOP)", + "file": "EJDCNGBEICB.txt", + "line": 5809, + "contract": { + "kind": "object", + "raw": "PPENFJMFPNE", + "rootType": "PPENFJMFPNE", + "typeDescription": { + "name": "PPENFJMFPNE", + "found": true, + "file": "PPENFJMFPNE.cs", + "declaration": "public class PPENFJMFPNE : KLCOGEIGEBJ", + "kind": "class", + "baseType": "KLCOGEIGEBJ", + "properties": [ + { + "name": "ADCDBMGNIPB", + "type": "List", + "accessibility": "public", + "kind": "property" + }, + { + "name": "BIODFICLKJH", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "DFKGKGIMEMP", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "EJGIMHADHBC", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "FEBCCOFBKKJ", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "IAJDFKODGFL", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "KFFBPMHGFKB", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "LHGHLGDINFL", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "LOJPLANGFMG", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "MMBJPGGOFHL", + "type": "LMLJHMJEIGM", + "accessibility": "public", + "kind": "property" + }, + { + "name": "MMFBPKOOLNG", + "type": "bool", + "accessibility": "public", + "kind": "property" + }, + { + "name": "OHNIGJECGKL", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "PKKADKGDHNI", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + } + ], + "fields": [ + { + "name": "accountId", + "type": "int", + "kind": "field" + }, + { + "name": "tag", + "type": "string", + "kind": "field" + }, + { + "name": "IGAPDHGFLDC", + "type": "PPENFJMFPNE", + "kind": "field" + } + ], + "enumValues": [], + "wireKeys": [], + "isilFile": "PPENFJMFPNE.txt", + "isilType": "PPENFJMFPNE", + "inherits": "KLCOGEIGEBJ", + "inheritedMembers": [ + { + "name": "DADOKMAOFJL", + "type": "long", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "KLCOGEIGEBJ" + }, + { + "name": "GJPGKJJCPBK", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "KLCOGEIGEBJ" + }, + { + "name": "IBLOJJBEKFF", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "KLCOGEIGEBJ" + }, + { + "name": "JCLCOCEOAEP", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "KLCOGEIGEBJ" + }, + { + "name": "JGJCBGEBLLO", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "KLCOGEIGEBJ" + }, + { + "name": "PLFEEIJCHOH", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "KLCOGEIGEBJ" + }, + { + "name": "BJFNPKFOALK", + "type": "long", + "kind": "field", + "inheritedFrom": "KLCOGEIGEBJ" + }, + { + "name": "ACBFDMLHFPB", + "type": "DateTime", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "AHGCOGFEEEE", + "type": "string", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "AKFBNELAMNA", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "BADIGBCKECA", + "type": "int", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "BNBLOBAEDEE", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "CDINMMPNAID", + "type": "NMJEKMMBDDE", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "CDNFGMHLDMJ", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "FIKEBGGCDFN", + "type": "string", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "GIBHIMGJNNO", + "type": "GPDIAKNEBKH", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "HPLBOMGACED", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "IGOPGMHHLKI", + "type": "string", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "JFEAPMIPNEP", + "type": "DPLPMKMFMPB", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "KHIJAFCHLIA", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "KLNJBBPNMBJ", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "KODBEJPEFOJ", + "type": "string", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "LPJLEMJFBPE", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "MGBDHBHCDMH", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "MIKDLDEALPN", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "OFONEIOEIED", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "OILEJFNPDDB", + "type": "HJPGEGENLPH", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "PEEFHKMOMKK", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + } + ], + "inheritedWireKeys": [ + "RoomId", + "IsDorm", + "CloningAllowed", + "DisableMicAutoMute", + "DisableRoomComments", + "EncryptVoiceChat" + ] + } + } + } + ], + "contracts": [ + { + "kind": "object", + "raw": "PPENFJMFPNE", + "rootType": "PPENFJMFPNE", + "typeDescription": { + "name": "PPENFJMFPNE", + "found": true, + "file": "PPENFJMFPNE.cs", + "declaration": "public class PPENFJMFPNE : KLCOGEIGEBJ", + "kind": "class", + "baseType": "KLCOGEIGEBJ", + "properties": [ + { + "name": "ADCDBMGNIPB", + "type": "List", + "accessibility": "public", + "kind": "property" + }, + { + "name": "BIODFICLKJH", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "DFKGKGIMEMP", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "EJGIMHADHBC", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "FEBCCOFBKKJ", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "IAJDFKODGFL", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "KFFBPMHGFKB", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "LHGHLGDINFL", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "LOJPLANGFMG", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "MMBJPGGOFHL", + "type": "LMLJHMJEIGM", + "accessibility": "public", + "kind": "property" + }, + { + "name": "MMFBPKOOLNG", + "type": "bool", + "accessibility": "public", + "kind": "property" + }, + { + "name": "OHNIGJECGKL", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "PKKADKGDHNI", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + } + ], + "fields": [ + { + "name": "accountId", + "type": "int", + "kind": "field" + }, + { + "name": "tag", + "type": "string", + "kind": "field" + }, + { + "name": "IGAPDHGFLDC", + "type": "PPENFJMFPNE", + "kind": "field" + } + ], + "enumValues": [], + "wireKeys": [], + "isilFile": "PPENFJMFPNE.txt", + "isilType": "PPENFJMFPNE", + "inherits": "KLCOGEIGEBJ", + "inheritedMembers": [ + { + "name": "DADOKMAOFJL", + "type": "long", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "KLCOGEIGEBJ" + }, + { + "name": "GJPGKJJCPBK", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "KLCOGEIGEBJ" + }, + { + "name": "IBLOJJBEKFF", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "KLCOGEIGEBJ" + }, + { + "name": "JCLCOCEOAEP", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "KLCOGEIGEBJ" + }, + { + "name": "JGJCBGEBLLO", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "KLCOGEIGEBJ" + }, + { + "name": "PLFEEIJCHOH", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "KLCOGEIGEBJ" + }, + { + "name": "BJFNPKFOALK", + "type": "long", + "kind": "field", + "inheritedFrom": "KLCOGEIGEBJ" + }, + { + "name": "ACBFDMLHFPB", + "type": "DateTime", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "AHGCOGFEEEE", + "type": "string", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "AKFBNELAMNA", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "BADIGBCKECA", + "type": "int", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "BNBLOBAEDEE", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "CDINMMPNAID", + "type": "NMJEKMMBDDE", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "CDNFGMHLDMJ", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "FIKEBGGCDFN", + "type": "string", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "GIBHIMGJNNO", + "type": "GPDIAKNEBKH", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "HPLBOMGACED", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "IGOPGMHHLKI", + "type": "string", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "JFEAPMIPNEP", + "type": "DPLPMKMFMPB", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "KHIJAFCHLIA", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "KLNJBBPNMBJ", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "KODBEJPEFOJ", + "type": "string", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "LPJLEMJFBPE", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "MGBDHBHCDMH", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "MIKDLDEALPN", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "OFONEIOEIED", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "OILEJFNPDDB", + "type": "HJPGEGENLPH", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "PEEFHKMOMKK", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + } + ], + "inheritedWireKeys": [ + "RoomId", + "IsDorm", + "CloningAllowed", + "DisableMicAutoMute", + "DisableRoomComments", + "EncryptVoiceChat" + ] + } + } + ] + }, + { + "family": "rooms", + "route": "rooms/{0}/subrooms/{1}/accessibility", + "evidence": [ + { + "type": "EJDCNGBEICB", + "method": "RecRoom.Async.IPromise`1 POGEHAMJBPE(System.Int64 HNHLJONGKHB, System.Int64 FODGKNJIGOP, DPLPMKMFMPB PONCIIJOHIE)", + "file": "EJDCNGBEICB.txt", + "line": 5369, + "contract": { + "kind": "object", + "raw": "PPENFJMFPNE", + "rootType": "PPENFJMFPNE", + "typeDescription": { + "name": "PPENFJMFPNE", + "found": true, + "file": "PPENFJMFPNE.cs", + "declaration": "public class PPENFJMFPNE : KLCOGEIGEBJ", + "kind": "class", + "baseType": "KLCOGEIGEBJ", + "properties": [ + { + "name": "ADCDBMGNIPB", + "type": "List", + "accessibility": "public", + "kind": "property" + }, + { + "name": "BIODFICLKJH", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "DFKGKGIMEMP", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "EJGIMHADHBC", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "FEBCCOFBKKJ", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "IAJDFKODGFL", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "KFFBPMHGFKB", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "LHGHLGDINFL", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "LOJPLANGFMG", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "MMBJPGGOFHL", + "type": "LMLJHMJEIGM", + "accessibility": "public", + "kind": "property" + }, + { + "name": "MMFBPKOOLNG", + "type": "bool", + "accessibility": "public", + "kind": "property" + }, + { + "name": "OHNIGJECGKL", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "PKKADKGDHNI", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + } + ], + "fields": [ + { + "name": "accountId", + "type": "int", + "kind": "field" + }, + { + "name": "tag", + "type": "string", + "kind": "field" + }, + { + "name": "IGAPDHGFLDC", + "type": "PPENFJMFPNE", + "kind": "field" + } + ], + "enumValues": [], + "wireKeys": [], + "isilFile": "PPENFJMFPNE.txt", + "isilType": "PPENFJMFPNE", + "inherits": "KLCOGEIGEBJ", + "inheritedMembers": [ + { + "name": "DADOKMAOFJL", + "type": "long", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "KLCOGEIGEBJ" + }, + { + "name": "GJPGKJJCPBK", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "KLCOGEIGEBJ" + }, + { + "name": "IBLOJJBEKFF", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "KLCOGEIGEBJ" + }, + { + "name": "JCLCOCEOAEP", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "KLCOGEIGEBJ" + }, + { + "name": "JGJCBGEBLLO", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "KLCOGEIGEBJ" + }, + { + "name": "PLFEEIJCHOH", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "KLCOGEIGEBJ" + }, + { + "name": "BJFNPKFOALK", + "type": "long", + "kind": "field", + "inheritedFrom": "KLCOGEIGEBJ" + }, + { + "name": "ACBFDMLHFPB", + "type": "DateTime", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "AHGCOGFEEEE", + "type": "string", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "AKFBNELAMNA", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "BADIGBCKECA", + "type": "int", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "BNBLOBAEDEE", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "CDINMMPNAID", + "type": "NMJEKMMBDDE", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "CDNFGMHLDMJ", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "FIKEBGGCDFN", + "type": "string", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "GIBHIMGJNNO", + "type": "GPDIAKNEBKH", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "HPLBOMGACED", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "IGOPGMHHLKI", + "type": "string", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "JFEAPMIPNEP", + "type": "DPLPMKMFMPB", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "KHIJAFCHLIA", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "KLNJBBPNMBJ", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "KODBEJPEFOJ", + "type": "string", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "LPJLEMJFBPE", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "MGBDHBHCDMH", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "MIKDLDEALPN", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "OFONEIOEIED", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "OILEJFNPDDB", + "type": "HJPGEGENLPH", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "PEEFHKMOMKK", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + } + ], + "inheritedWireKeys": [ + "RoomId", + "IsDorm", + "CloningAllowed", + "DisableMicAutoMute", + "DisableRoomComments", + "EncryptVoiceChat" + ] + } + } + } + ], + "contracts": [ + { + "kind": "object", + "raw": "PPENFJMFPNE", + "rootType": "PPENFJMFPNE", + "typeDescription": { + "name": "PPENFJMFPNE", + "found": true, + "file": "PPENFJMFPNE.cs", + "declaration": "public class PPENFJMFPNE : KLCOGEIGEBJ", + "kind": "class", + "baseType": "KLCOGEIGEBJ", + "properties": [ + { + "name": "ADCDBMGNIPB", + "type": "List", + "accessibility": "public", + "kind": "property" + }, + { + "name": "BIODFICLKJH", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "DFKGKGIMEMP", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "EJGIMHADHBC", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "FEBCCOFBKKJ", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "IAJDFKODGFL", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "KFFBPMHGFKB", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "LHGHLGDINFL", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "LOJPLANGFMG", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "MMBJPGGOFHL", + "type": "LMLJHMJEIGM", + "accessibility": "public", + "kind": "property" + }, + { + "name": "MMFBPKOOLNG", + "type": "bool", + "accessibility": "public", + "kind": "property" + }, + { + "name": "OHNIGJECGKL", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "PKKADKGDHNI", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + } + ], + "fields": [ + { + "name": "accountId", + "type": "int", + "kind": "field" + }, + { + "name": "tag", + "type": "string", + "kind": "field" + }, + { + "name": "IGAPDHGFLDC", + "type": "PPENFJMFPNE", + "kind": "field" + } + ], + "enumValues": [], + "wireKeys": [], + "isilFile": "PPENFJMFPNE.txt", + "isilType": "PPENFJMFPNE", + "inherits": "KLCOGEIGEBJ", + "inheritedMembers": [ + { + "name": "DADOKMAOFJL", + "type": "long", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "KLCOGEIGEBJ" + }, + { + "name": "GJPGKJJCPBK", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "KLCOGEIGEBJ" + }, + { + "name": "IBLOJJBEKFF", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "KLCOGEIGEBJ" + }, + { + "name": "JCLCOCEOAEP", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "KLCOGEIGEBJ" + }, + { + "name": "JGJCBGEBLLO", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "KLCOGEIGEBJ" + }, + { + "name": "PLFEEIJCHOH", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "KLCOGEIGEBJ" + }, + { + "name": "BJFNPKFOALK", + "type": "long", + "kind": "field", + "inheritedFrom": "KLCOGEIGEBJ" + }, + { + "name": "ACBFDMLHFPB", + "type": "DateTime", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "AHGCOGFEEEE", + "type": "string", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "AKFBNELAMNA", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "BADIGBCKECA", + "type": "int", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "BNBLOBAEDEE", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "CDINMMPNAID", + "type": "NMJEKMMBDDE", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "CDNFGMHLDMJ", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "FIKEBGGCDFN", + "type": "string", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "GIBHIMGJNNO", + "type": "GPDIAKNEBKH", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "HPLBOMGACED", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "IGOPGMHHLKI", + "type": "string", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "JFEAPMIPNEP", + "type": "DPLPMKMFMPB", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "KHIJAFCHLIA", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "KLNJBBPNMBJ", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "KODBEJPEFOJ", + "type": "string", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "LPJLEMJFBPE", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "MGBDHBHCDMH", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "MIKDLDEALPN", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "OFONEIOEIED", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "OILEJFNPDDB", + "type": "HJPGEGENLPH", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "PEEFHKMOMKK", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + } + ], + "inheritedWireKeys": [ + "RoomId", + "IsDorm", + "CloningAllowed", + "DisableMicAutoMute", + "DisableRoomComments", + "EncryptVoiceChat" + ] + } + } + ] + }, + { + "family": "rooms", + "route": "rooms/{0}/subrooms/{1}/clone", + "evidence": [ + { + "type": "EJDCNGBEICB", + "method": "RecRoom.Async.IPromise`1 OGEKCICPACB(System.Int64 HNHLJONGKHB, System.Int64 FODGKNJIGOP)", + "file": "EJDCNGBEICB.txt", + "line": 5583, + "contract": { + "kind": "object", + "raw": "PPENFJMFPNE", + "rootType": "PPENFJMFPNE", + "typeDescription": { + "name": "PPENFJMFPNE", + "found": true, + "file": "PPENFJMFPNE.cs", + "declaration": "public class PPENFJMFPNE : KLCOGEIGEBJ", + "kind": "class", + "baseType": "KLCOGEIGEBJ", + "properties": [ + { + "name": "ADCDBMGNIPB", + "type": "List", + "accessibility": "public", + "kind": "property" + }, + { + "name": "BIODFICLKJH", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "DFKGKGIMEMP", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "EJGIMHADHBC", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "FEBCCOFBKKJ", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "IAJDFKODGFL", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "KFFBPMHGFKB", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "LHGHLGDINFL", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "LOJPLANGFMG", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "MMBJPGGOFHL", + "type": "LMLJHMJEIGM", + "accessibility": "public", + "kind": "property" + }, + { + "name": "MMFBPKOOLNG", + "type": "bool", + "accessibility": "public", + "kind": "property" + }, + { + "name": "OHNIGJECGKL", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "PKKADKGDHNI", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + } + ], + "fields": [ + { + "name": "accountId", + "type": "int", + "kind": "field" + }, + { + "name": "tag", + "type": "string", + "kind": "field" + }, + { + "name": "IGAPDHGFLDC", + "type": "PPENFJMFPNE", + "kind": "field" + } + ], + "enumValues": [], + "wireKeys": [], + "isilFile": "PPENFJMFPNE.txt", + "isilType": "PPENFJMFPNE", + "inherits": "KLCOGEIGEBJ", + "inheritedMembers": [ + { + "name": "DADOKMAOFJL", + "type": "long", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "KLCOGEIGEBJ" + }, + { + "name": "GJPGKJJCPBK", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "KLCOGEIGEBJ" + }, + { + "name": "IBLOJJBEKFF", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "KLCOGEIGEBJ" + }, + { + "name": "JCLCOCEOAEP", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "KLCOGEIGEBJ" + }, + { + "name": "JGJCBGEBLLO", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "KLCOGEIGEBJ" + }, + { + "name": "PLFEEIJCHOH", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "KLCOGEIGEBJ" + }, + { + "name": "BJFNPKFOALK", + "type": "long", + "kind": "field", + "inheritedFrom": "KLCOGEIGEBJ" + }, + { + "name": "ACBFDMLHFPB", + "type": "DateTime", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "AHGCOGFEEEE", + "type": "string", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "AKFBNELAMNA", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "BADIGBCKECA", + "type": "int", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "BNBLOBAEDEE", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "CDINMMPNAID", + "type": "NMJEKMMBDDE", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "CDNFGMHLDMJ", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "FIKEBGGCDFN", + "type": "string", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "GIBHIMGJNNO", + "type": "GPDIAKNEBKH", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "HPLBOMGACED", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "IGOPGMHHLKI", + "type": "string", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "JFEAPMIPNEP", + "type": "DPLPMKMFMPB", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "KHIJAFCHLIA", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "KLNJBBPNMBJ", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "KODBEJPEFOJ", + "type": "string", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "LPJLEMJFBPE", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "MGBDHBHCDMH", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "MIKDLDEALPN", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "OFONEIOEIED", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "OILEJFNPDDB", + "type": "HJPGEGENLPH", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "PEEFHKMOMKK", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + } + ], + "inheritedWireKeys": [ + "RoomId", + "IsDorm", + "CloningAllowed", + "DisableMicAutoMute", + "DisableRoomComments", + "EncryptVoiceChat" + ] + } + } + } + ], + "contracts": [ + { + "kind": "object", + "raw": "PPENFJMFPNE", + "rootType": "PPENFJMFPNE", + "typeDescription": { + "name": "PPENFJMFPNE", + "found": true, + "file": "PPENFJMFPNE.cs", + "declaration": "public class PPENFJMFPNE : KLCOGEIGEBJ", + "kind": "class", + "baseType": "KLCOGEIGEBJ", + "properties": [ + { + "name": "ADCDBMGNIPB", + "type": "List", + "accessibility": "public", + "kind": "property" + }, + { + "name": "BIODFICLKJH", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "DFKGKGIMEMP", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "EJGIMHADHBC", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "FEBCCOFBKKJ", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "IAJDFKODGFL", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "KFFBPMHGFKB", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "LHGHLGDINFL", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "LOJPLANGFMG", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "MMBJPGGOFHL", + "type": "LMLJHMJEIGM", + "accessibility": "public", + "kind": "property" + }, + { + "name": "MMFBPKOOLNG", + "type": "bool", + "accessibility": "public", + "kind": "property" + }, + { + "name": "OHNIGJECGKL", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "PKKADKGDHNI", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + } + ], + "fields": [ + { + "name": "accountId", + "type": "int", + "kind": "field" + }, + { + "name": "tag", + "type": "string", + "kind": "field" + }, + { + "name": "IGAPDHGFLDC", + "type": "PPENFJMFPNE", + "kind": "field" + } + ], + "enumValues": [], + "wireKeys": [], + "isilFile": "PPENFJMFPNE.txt", + "isilType": "PPENFJMFPNE", + "inherits": "KLCOGEIGEBJ", + "inheritedMembers": [ + { + "name": "DADOKMAOFJL", + "type": "long", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "KLCOGEIGEBJ" + }, + { + "name": "GJPGKJJCPBK", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "KLCOGEIGEBJ" + }, + { + "name": "IBLOJJBEKFF", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "KLCOGEIGEBJ" + }, + { + "name": "JCLCOCEOAEP", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "KLCOGEIGEBJ" + }, + { + "name": "JGJCBGEBLLO", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "KLCOGEIGEBJ" + }, + { + "name": "PLFEEIJCHOH", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "KLCOGEIGEBJ" + }, + { + "name": "BJFNPKFOALK", + "type": "long", + "kind": "field", + "inheritedFrom": "KLCOGEIGEBJ" + }, + { + "name": "ACBFDMLHFPB", + "type": "DateTime", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "AHGCOGFEEEE", + "type": "string", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "AKFBNELAMNA", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "BADIGBCKECA", + "type": "int", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "BNBLOBAEDEE", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "CDINMMPNAID", + "type": "NMJEKMMBDDE", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "CDNFGMHLDMJ", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "FIKEBGGCDFN", + "type": "string", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "GIBHIMGJNNO", + "type": "GPDIAKNEBKH", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "HPLBOMGACED", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "IGOPGMHHLKI", + "type": "string", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "JFEAPMIPNEP", + "type": "DPLPMKMFMPB", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "KHIJAFCHLIA", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "KLNJBBPNMBJ", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "KODBEJPEFOJ", + "type": "string", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "LPJLEMJFBPE", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "MGBDHBHCDMH", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "MIKDLDEALPN", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "OFONEIOEIED", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "OILEJFNPDDB", + "type": "HJPGEGENLPH", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "PEEFHKMOMKK", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + } + ], + "inheritedWireKeys": [ + "RoomId", + "IsDorm", + "CloningAllowed", + "DisableMicAutoMute", + "DisableRoomComments", + "EncryptVoiceChat" + ] + } + } + ] + }, + { + "family": "rooms", + "route": "rooms/{0}/subrooms/{1}/data", + "evidence": [ + { + "type": "EJDCNGBEICB", + "method": "RecRoom.Async.IPromise`1 HOPMJFDKPDL(System.Int64 HNHLJONGKHB, System.Int64 FODGKNJIGOP, System.String MEIIMAIGBJD, System.Collections.Generic.Dictionary`2 PNOBBNPJDLI, System.Int32 OMCLBIFCHLF)", + "file": "EJDCNGBEICB.txt", + "line": 5995, + "contract": { + "kind": "object", + "raw": "PPENFJMFPNE", + "rootType": "PPENFJMFPNE", + "typeDescription": { + "name": "PPENFJMFPNE", + "found": true, + "file": "PPENFJMFPNE.cs", + "declaration": "public class PPENFJMFPNE : KLCOGEIGEBJ", + "kind": "class", + "baseType": "KLCOGEIGEBJ", + "properties": [ + { + "name": "ADCDBMGNIPB", + "type": "List", + "accessibility": "public", + "kind": "property" + }, + { + "name": "BIODFICLKJH", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "DFKGKGIMEMP", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "EJGIMHADHBC", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "FEBCCOFBKKJ", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "IAJDFKODGFL", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "KFFBPMHGFKB", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "LHGHLGDINFL", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "LOJPLANGFMG", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "MMBJPGGOFHL", + "type": "LMLJHMJEIGM", + "accessibility": "public", + "kind": "property" + }, + { + "name": "MMFBPKOOLNG", + "type": "bool", + "accessibility": "public", + "kind": "property" + }, + { + "name": "OHNIGJECGKL", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "PKKADKGDHNI", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + } + ], + "fields": [ + { + "name": "accountId", + "type": "int", + "kind": "field" + }, + { + "name": "tag", + "type": "string", + "kind": "field" + }, + { + "name": "IGAPDHGFLDC", + "type": "PPENFJMFPNE", + "kind": "field" + } + ], + "enumValues": [], + "wireKeys": [], + "isilFile": "PPENFJMFPNE.txt", + "isilType": "PPENFJMFPNE", + "inherits": "KLCOGEIGEBJ", + "inheritedMembers": [ + { + "name": "DADOKMAOFJL", + "type": "long", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "KLCOGEIGEBJ" + }, + { + "name": "GJPGKJJCPBK", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "KLCOGEIGEBJ" + }, + { + "name": "IBLOJJBEKFF", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "KLCOGEIGEBJ" + }, + { + "name": "JCLCOCEOAEP", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "KLCOGEIGEBJ" + }, + { + "name": "JGJCBGEBLLO", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "KLCOGEIGEBJ" + }, + { + "name": "PLFEEIJCHOH", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "KLCOGEIGEBJ" + }, + { + "name": "BJFNPKFOALK", + "type": "long", + "kind": "field", + "inheritedFrom": "KLCOGEIGEBJ" + }, + { + "name": "ACBFDMLHFPB", + "type": "DateTime", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "AHGCOGFEEEE", + "type": "string", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "AKFBNELAMNA", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "BADIGBCKECA", + "type": "int", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "BNBLOBAEDEE", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "CDINMMPNAID", + "type": "NMJEKMMBDDE", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "CDNFGMHLDMJ", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "FIKEBGGCDFN", + "type": "string", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "GIBHIMGJNNO", + "type": "GPDIAKNEBKH", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "HPLBOMGACED", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "IGOPGMHHLKI", + "type": "string", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "JFEAPMIPNEP", + "type": "DPLPMKMFMPB", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "KHIJAFCHLIA", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "KLNJBBPNMBJ", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "KODBEJPEFOJ", + "type": "string", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "LPJLEMJFBPE", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "MGBDHBHCDMH", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "MIKDLDEALPN", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "OFONEIOEIED", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "OILEJFNPDDB", + "type": "HJPGEGENLPH", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "PEEFHKMOMKK", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + } + ], + "inheritedWireKeys": [ + "RoomId", + "IsDorm", + "CloningAllowed", + "DisableMicAutoMute", + "DisableRoomComments", + "EncryptVoiceChat" + ] + } + } + } + ], + "contracts": [ + { + "kind": "object", + "raw": "PPENFJMFPNE", + "rootType": "PPENFJMFPNE", + "typeDescription": { + "name": "PPENFJMFPNE", + "found": true, + "file": "PPENFJMFPNE.cs", + "declaration": "public class PPENFJMFPNE : KLCOGEIGEBJ", + "kind": "class", + "baseType": "KLCOGEIGEBJ", + "properties": [ + { + "name": "ADCDBMGNIPB", + "type": "List", + "accessibility": "public", + "kind": "property" + }, + { + "name": "BIODFICLKJH", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "DFKGKGIMEMP", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "EJGIMHADHBC", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "FEBCCOFBKKJ", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "IAJDFKODGFL", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "KFFBPMHGFKB", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "LHGHLGDINFL", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "LOJPLANGFMG", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "MMBJPGGOFHL", + "type": "LMLJHMJEIGM", + "accessibility": "public", + "kind": "property" + }, + { + "name": "MMFBPKOOLNG", + "type": "bool", + "accessibility": "public", + "kind": "property" + }, + { + "name": "OHNIGJECGKL", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "PKKADKGDHNI", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + } + ], + "fields": [ + { + "name": "accountId", + "type": "int", + "kind": "field" + }, + { + "name": "tag", + "type": "string", + "kind": "field" + }, + { + "name": "IGAPDHGFLDC", + "type": "PPENFJMFPNE", + "kind": "field" + } + ], + "enumValues": [], + "wireKeys": [], + "isilFile": "PPENFJMFPNE.txt", + "isilType": "PPENFJMFPNE", + "inherits": "KLCOGEIGEBJ", + "inheritedMembers": [ + { + "name": "DADOKMAOFJL", + "type": "long", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "KLCOGEIGEBJ" + }, + { + "name": "GJPGKJJCPBK", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "KLCOGEIGEBJ" + }, + { + "name": "IBLOJJBEKFF", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "KLCOGEIGEBJ" + }, + { + "name": "JCLCOCEOAEP", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "KLCOGEIGEBJ" + }, + { + "name": "JGJCBGEBLLO", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "KLCOGEIGEBJ" + }, + { + "name": "PLFEEIJCHOH", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "KLCOGEIGEBJ" + }, + { + "name": "BJFNPKFOALK", + "type": "long", + "kind": "field", + "inheritedFrom": "KLCOGEIGEBJ" + }, + { + "name": "ACBFDMLHFPB", + "type": "DateTime", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "AHGCOGFEEEE", + "type": "string", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "AKFBNELAMNA", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "BADIGBCKECA", + "type": "int", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "BNBLOBAEDEE", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "CDINMMPNAID", + "type": "NMJEKMMBDDE", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "CDNFGMHLDMJ", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "FIKEBGGCDFN", + "type": "string", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "GIBHIMGJNNO", + "type": "GPDIAKNEBKH", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "HPLBOMGACED", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "IGOPGMHHLKI", + "type": "string", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "JFEAPMIPNEP", + "type": "DPLPMKMFMPB", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "KHIJAFCHLIA", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "KLNJBBPNMBJ", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "KODBEJPEFOJ", + "type": "string", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "LPJLEMJFBPE", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "MGBDHBHCDMH", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "MIKDLDEALPN", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "OFONEIOEIED", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "OILEJFNPDDB", + "type": "HJPGEGENLPH", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "PEEFHKMOMKK", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + } + ], + "inheritedWireKeys": [ + "RoomId", + "IsDorm", + "CloningAllowed", + "DisableMicAutoMute", + "DisableRoomComments", + "EncryptVoiceChat" + ] + } + } + ] + }, + { + "family": "rooms", + "route": "rooms/{0}/subrooms/{1}/datahistory", + "evidence": [ + { + "type": "EJDCNGBEICB", + "method": "RecRoom.Async.IPromise`1> HAMBEABEIOC(System.Int64 HNHLJONGKHB, System.Int64 FODGKNJIGOP)", + "file": "EJDCNGBEICB.txt", + "line": 1190, + "contract": { + "kind": "array", + "raw": "System.Collections.Generic.List`1", + "rootType": "DHOCPFIOKHD", + "item": { + "kind": "object", + "raw": "DHOCPFIOKHD", + "rootType": "DHOCPFIOKHD" + }, + "typeDescription": { + "name": "DHOCPFIOKHD", + "found": true, + "file": "DHOCPFIOKHD.cs", + "declaration": "public class DHOCPFIOKHD : IFAIJAGLDFK", + "kind": "class", + "baseType": "IFAIJAGLDFK", + "properties": [ + { + "name": "ACBFDMLHFPB", + "type": "DateTime", + "accessibility": "public", + "kind": "property" + }, + { + "name": "CHKBMPIFNHH", + "type": "string", + "accessibility": "public", + "kind": "property" + }, + { + "name": "GJPHHEHBCIJ", + "type": "long", + "accessibility": "public", + "kind": "property" + }, + { + "name": "NDCONBJJDPM", + "type": "int", + "accessibility": "public", + "kind": "property" + } + ], + "fields": [], + "enumValues": [], + "wireKeys": [ + "SubRoomId", + "DataBlob", + "SavedByAccountId", + "CreatedAt" + ], + "isilFile": "DHOCPFIOKHD.txt", + "isilType": "DHOCPFIOKHD" + } + } + } + ], + "contracts": [ + { + "kind": "array", + "raw": "System.Collections.Generic.List`1", + "rootType": "DHOCPFIOKHD", + "item": { + "kind": "object", + "raw": "DHOCPFIOKHD", + "rootType": "DHOCPFIOKHD" + }, + "typeDescription": { + "name": "DHOCPFIOKHD", + "found": true, + "file": "DHOCPFIOKHD.cs", + "declaration": "public class DHOCPFIOKHD : IFAIJAGLDFK", + "kind": "class", + "baseType": "IFAIJAGLDFK", + "properties": [ + { + "name": "ACBFDMLHFPB", + "type": "DateTime", + "accessibility": "public", + "kind": "property" + }, + { + "name": "CHKBMPIFNHH", + "type": "string", + "accessibility": "public", + "kind": "property" + }, + { + "name": "GJPHHEHBCIJ", + "type": "long", + "accessibility": "public", + "kind": "property" + }, + { + "name": "NDCONBJJDPM", + "type": "int", + "accessibility": "public", + "kind": "property" + } + ], + "fields": [], + "enumValues": [], + "wireKeys": [ + "SubRoomId", + "DataBlob", + "SavedByAccountId", + "CreatedAt" + ], + "isilFile": "DHOCPFIOKHD.txt", + "isilType": "DHOCPFIOKHD" + } + } + ] + }, + { + "family": "rooms", + "route": "rooms/{0}/subrooms/{1}/maxplayers", + "evidence": [ + { + "type": "EJDCNGBEICB", + "method": "RecRoom.Async.IPromise`1 AEBGMLMMKOJ(System.Int64 HNHLJONGKHB, System.Int64 FODGKNJIGOP, System.Int32 FFAGIDMFPIF)", + "file": "EJDCNGBEICB.txt", + "line": 5235, + "contract": { + "kind": "object", + "raw": "PPENFJMFPNE", + "rootType": "PPENFJMFPNE", + "typeDescription": { + "name": "PPENFJMFPNE", + "found": true, + "file": "PPENFJMFPNE.cs", + "declaration": "public class PPENFJMFPNE : KLCOGEIGEBJ", + "kind": "class", + "baseType": "KLCOGEIGEBJ", + "properties": [ + { + "name": "ADCDBMGNIPB", + "type": "List", + "accessibility": "public", + "kind": "property" + }, + { + "name": "BIODFICLKJH", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "DFKGKGIMEMP", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "EJGIMHADHBC", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "FEBCCOFBKKJ", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "IAJDFKODGFL", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "KFFBPMHGFKB", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "LHGHLGDINFL", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "LOJPLANGFMG", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "MMBJPGGOFHL", + "type": "LMLJHMJEIGM", + "accessibility": "public", + "kind": "property" + }, + { + "name": "MMFBPKOOLNG", + "type": "bool", + "accessibility": "public", + "kind": "property" + }, + { + "name": "OHNIGJECGKL", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "PKKADKGDHNI", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + } + ], + "fields": [ + { + "name": "accountId", + "type": "int", + "kind": "field" + }, + { + "name": "tag", + "type": "string", + "kind": "field" + }, + { + "name": "IGAPDHGFLDC", + "type": "PPENFJMFPNE", + "kind": "field" + } + ], + "enumValues": [], + "wireKeys": [], + "isilFile": "PPENFJMFPNE.txt", + "isilType": "PPENFJMFPNE", + "inherits": "KLCOGEIGEBJ", + "inheritedMembers": [ + { + "name": "DADOKMAOFJL", + "type": "long", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "KLCOGEIGEBJ" + }, + { + "name": "GJPGKJJCPBK", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "KLCOGEIGEBJ" + }, + { + "name": "IBLOJJBEKFF", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "KLCOGEIGEBJ" + }, + { + "name": "JCLCOCEOAEP", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "KLCOGEIGEBJ" + }, + { + "name": "JGJCBGEBLLO", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "KLCOGEIGEBJ" + }, + { + "name": "PLFEEIJCHOH", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "KLCOGEIGEBJ" + }, + { + "name": "BJFNPKFOALK", + "type": "long", + "kind": "field", + "inheritedFrom": "KLCOGEIGEBJ" + }, + { + "name": "ACBFDMLHFPB", + "type": "DateTime", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "AHGCOGFEEEE", + "type": "string", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "AKFBNELAMNA", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "BADIGBCKECA", + "type": "int", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "BNBLOBAEDEE", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "CDINMMPNAID", + "type": "NMJEKMMBDDE", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "CDNFGMHLDMJ", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "FIKEBGGCDFN", + "type": "string", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "GIBHIMGJNNO", + "type": "GPDIAKNEBKH", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "HPLBOMGACED", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "IGOPGMHHLKI", + "type": "string", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "JFEAPMIPNEP", + "type": "DPLPMKMFMPB", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "KHIJAFCHLIA", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "KLNJBBPNMBJ", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "KODBEJPEFOJ", + "type": "string", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "LPJLEMJFBPE", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "MGBDHBHCDMH", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "MIKDLDEALPN", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "OFONEIOEIED", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "OILEJFNPDDB", + "type": "HJPGEGENLPH", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "PEEFHKMOMKK", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + } + ], + "inheritedWireKeys": [ + "RoomId", + "IsDorm", + "CloningAllowed", + "DisableMicAutoMute", + "DisableRoomComments", + "EncryptVoiceChat" + ] + } + } + } + ], + "contracts": [ + { + "kind": "object", + "raw": "PPENFJMFPNE", + "rootType": "PPENFJMFPNE", + "typeDescription": { + "name": "PPENFJMFPNE", + "found": true, + "file": "PPENFJMFPNE.cs", + "declaration": "public class PPENFJMFPNE : KLCOGEIGEBJ", + "kind": "class", + "baseType": "KLCOGEIGEBJ", + "properties": [ + { + "name": "ADCDBMGNIPB", + "type": "List", + "accessibility": "public", + "kind": "property" + }, + { + "name": "BIODFICLKJH", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "DFKGKGIMEMP", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "EJGIMHADHBC", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "FEBCCOFBKKJ", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "IAJDFKODGFL", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "KFFBPMHGFKB", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "LHGHLGDINFL", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "LOJPLANGFMG", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "MMBJPGGOFHL", + "type": "LMLJHMJEIGM", + "accessibility": "public", + "kind": "property" + }, + { + "name": "MMFBPKOOLNG", + "type": "bool", + "accessibility": "public", + "kind": "property" + }, + { + "name": "OHNIGJECGKL", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "PKKADKGDHNI", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + } + ], + "fields": [ + { + "name": "accountId", + "type": "int", + "kind": "field" + }, + { + "name": "tag", + "type": "string", + "kind": "field" + }, + { + "name": "IGAPDHGFLDC", + "type": "PPENFJMFPNE", + "kind": "field" + } + ], + "enumValues": [], + "wireKeys": [], + "isilFile": "PPENFJMFPNE.txt", + "isilType": "PPENFJMFPNE", + "inherits": "KLCOGEIGEBJ", + "inheritedMembers": [ + { + "name": "DADOKMAOFJL", + "type": "long", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "KLCOGEIGEBJ" + }, + { + "name": "GJPGKJJCPBK", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "KLCOGEIGEBJ" + }, + { + "name": "IBLOJJBEKFF", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "KLCOGEIGEBJ" + }, + { + "name": "JCLCOCEOAEP", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "KLCOGEIGEBJ" + }, + { + "name": "JGJCBGEBLLO", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "KLCOGEIGEBJ" + }, + { + "name": "PLFEEIJCHOH", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "KLCOGEIGEBJ" + }, + { + "name": "BJFNPKFOALK", + "type": "long", + "kind": "field", + "inheritedFrom": "KLCOGEIGEBJ" + }, + { + "name": "ACBFDMLHFPB", + "type": "DateTime", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "AHGCOGFEEEE", + "type": "string", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "AKFBNELAMNA", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "BADIGBCKECA", + "type": "int", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "BNBLOBAEDEE", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "CDINMMPNAID", + "type": "NMJEKMMBDDE", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "CDNFGMHLDMJ", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "FIKEBGGCDFN", + "type": "string", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "GIBHIMGJNNO", + "type": "GPDIAKNEBKH", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "HPLBOMGACED", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "IGOPGMHHLKI", + "type": "string", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "JFEAPMIPNEP", + "type": "DPLPMKMFMPB", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "KHIJAFCHLIA", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "KLNJBBPNMBJ", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "KODBEJPEFOJ", + "type": "string", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "LPJLEMJFBPE", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "MGBDHBHCDMH", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "MIKDLDEALPN", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "OFONEIOEIED", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "OILEJFNPDDB", + "type": "HJPGEGENLPH", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "PEEFHKMOMKK", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + } + ], + "inheritedWireKeys": [ + "RoomId", + "IsDorm", + "CloningAllowed", + "DisableMicAutoMute", + "DisableRoomComments", + "EncryptVoiceChat" + ] + } + } + ] + }, + { + "family": "rooms", + "route": "rooms/{0}/subrooms/{1}/modify", + "evidence": [ + { + "type": "EJDCNGBEICB", + "method": "RecRoom.Async.IPromise`1 JGMMJMHLHBO(System.Int64 HNHLJONGKHB, System.Int64 FODGKNJIGOP, System.String MMBOKOLAJFH, DPLPMKMFMPB PONCIIJOHIE, System.Int32 FFAGIDMFPIF)", + "file": "EJDCNGBEICB.txt", + "line": 6443, + "contract": { + "kind": "object", + "raw": "PPENFJMFPNE", + "rootType": "PPENFJMFPNE", + "typeDescription": { + "name": "PPENFJMFPNE", + "found": true, + "file": "PPENFJMFPNE.cs", + "declaration": "public class PPENFJMFPNE : KLCOGEIGEBJ", + "kind": "class", + "baseType": "KLCOGEIGEBJ", + "properties": [ + { + "name": "ADCDBMGNIPB", + "type": "List", + "accessibility": "public", + "kind": "property" + }, + { + "name": "BIODFICLKJH", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "DFKGKGIMEMP", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "EJGIMHADHBC", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "FEBCCOFBKKJ", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "IAJDFKODGFL", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "KFFBPMHGFKB", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "LHGHLGDINFL", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "LOJPLANGFMG", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "MMBJPGGOFHL", + "type": "LMLJHMJEIGM", + "accessibility": "public", + "kind": "property" + }, + { + "name": "MMFBPKOOLNG", + "type": "bool", + "accessibility": "public", + "kind": "property" + }, + { + "name": "OHNIGJECGKL", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "PKKADKGDHNI", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + } + ], + "fields": [ + { + "name": "accountId", + "type": "int", + "kind": "field" + }, + { + "name": "tag", + "type": "string", + "kind": "field" + }, + { + "name": "IGAPDHGFLDC", + "type": "PPENFJMFPNE", + "kind": "field" + } + ], + "enumValues": [], + "wireKeys": [], + "isilFile": "PPENFJMFPNE.txt", + "isilType": "PPENFJMFPNE", + "inherits": "KLCOGEIGEBJ", + "inheritedMembers": [ + { + "name": "DADOKMAOFJL", + "type": "long", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "KLCOGEIGEBJ" + }, + { + "name": "GJPGKJJCPBK", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "KLCOGEIGEBJ" + }, + { + "name": "IBLOJJBEKFF", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "KLCOGEIGEBJ" + }, + { + "name": "JCLCOCEOAEP", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "KLCOGEIGEBJ" + }, + { + "name": "JGJCBGEBLLO", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "KLCOGEIGEBJ" + }, + { + "name": "PLFEEIJCHOH", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "KLCOGEIGEBJ" + }, + { + "name": "BJFNPKFOALK", + "type": "long", + "kind": "field", + "inheritedFrom": "KLCOGEIGEBJ" + }, + { + "name": "ACBFDMLHFPB", + "type": "DateTime", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "AHGCOGFEEEE", + "type": "string", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "AKFBNELAMNA", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "BADIGBCKECA", + "type": "int", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "BNBLOBAEDEE", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "CDINMMPNAID", + "type": "NMJEKMMBDDE", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "CDNFGMHLDMJ", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "FIKEBGGCDFN", + "type": "string", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "GIBHIMGJNNO", + "type": "GPDIAKNEBKH", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "HPLBOMGACED", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "IGOPGMHHLKI", + "type": "string", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "JFEAPMIPNEP", + "type": "DPLPMKMFMPB", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "KHIJAFCHLIA", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "KLNJBBPNMBJ", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "KODBEJPEFOJ", + "type": "string", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "LPJLEMJFBPE", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "MGBDHBHCDMH", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "MIKDLDEALPN", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "OFONEIOEIED", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "OILEJFNPDDB", + "type": "HJPGEGENLPH", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "PEEFHKMOMKK", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + } + ], + "inheritedWireKeys": [ + "RoomId", + "IsDorm", + "CloningAllowed", + "DisableMicAutoMute", + "DisableRoomComments", + "EncryptVoiceChat" + ] + } + } + } + ], + "contracts": [ + { + "kind": "object", + "raw": "PPENFJMFPNE", + "rootType": "PPENFJMFPNE", + "typeDescription": { + "name": "PPENFJMFPNE", + "found": true, + "file": "PPENFJMFPNE.cs", + "declaration": "public class PPENFJMFPNE : KLCOGEIGEBJ", + "kind": "class", + "baseType": "KLCOGEIGEBJ", + "properties": [ + { + "name": "ADCDBMGNIPB", + "type": "List", + "accessibility": "public", + "kind": "property" + }, + { + "name": "BIODFICLKJH", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "DFKGKGIMEMP", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "EJGIMHADHBC", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "FEBCCOFBKKJ", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "IAJDFKODGFL", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "KFFBPMHGFKB", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "LHGHLGDINFL", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "LOJPLANGFMG", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "MMBJPGGOFHL", + "type": "LMLJHMJEIGM", + "accessibility": "public", + "kind": "property" + }, + { + "name": "MMFBPKOOLNG", + "type": "bool", + "accessibility": "public", + "kind": "property" + }, + { + "name": "OHNIGJECGKL", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "PKKADKGDHNI", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + } + ], + "fields": [ + { + "name": "accountId", + "type": "int", + "kind": "field" + }, + { + "name": "tag", + "type": "string", + "kind": "field" + }, + { + "name": "IGAPDHGFLDC", + "type": "PPENFJMFPNE", + "kind": "field" + } + ], + "enumValues": [], + "wireKeys": [], + "isilFile": "PPENFJMFPNE.txt", + "isilType": "PPENFJMFPNE", + "inherits": "KLCOGEIGEBJ", + "inheritedMembers": [ + { + "name": "DADOKMAOFJL", + "type": "long", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "KLCOGEIGEBJ" + }, + { + "name": "GJPGKJJCPBK", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "KLCOGEIGEBJ" + }, + { + "name": "IBLOJJBEKFF", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "KLCOGEIGEBJ" + }, + { + "name": "JCLCOCEOAEP", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "KLCOGEIGEBJ" + }, + { + "name": "JGJCBGEBLLO", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "KLCOGEIGEBJ" + }, + { + "name": "PLFEEIJCHOH", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "KLCOGEIGEBJ" + }, + { + "name": "BJFNPKFOALK", + "type": "long", + "kind": "field", + "inheritedFrom": "KLCOGEIGEBJ" + }, + { + "name": "ACBFDMLHFPB", + "type": "DateTime", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "AHGCOGFEEEE", + "type": "string", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "AKFBNELAMNA", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "BADIGBCKECA", + "type": "int", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "BNBLOBAEDEE", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "CDINMMPNAID", + "type": "NMJEKMMBDDE", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "CDNFGMHLDMJ", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "FIKEBGGCDFN", + "type": "string", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "GIBHIMGJNNO", + "type": "GPDIAKNEBKH", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "HPLBOMGACED", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "IGOPGMHHLKI", + "type": "string", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "JFEAPMIPNEP", + "type": "DPLPMKMFMPB", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "KHIJAFCHLIA", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "KLNJBBPNMBJ", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "KODBEJPEFOJ", + "type": "string", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "LPJLEMJFBPE", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "MGBDHBHCDMH", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "MIKDLDEALPN", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "OFONEIOEIED", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "OILEJFNPDDB", + "type": "HJPGEGENLPH", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "PEEFHKMOMKK", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + } + ], + "inheritedWireKeys": [ + "RoomId", + "IsDorm", + "CloningAllowed", + "DisableMicAutoMute", + "DisableRoomComments", + "EncryptVoiceChat" + ] + } + } + ] + }, + { + "family": "rooms", + "route": "rooms/{0}/subrooms/{1}/move", + "evidence": [ + { + "type": "EJDCNGBEICB", + "method": "RecRoom.Async.IPromise`1 OHHAFAIEFHP(System.Int64 HNHLJONGKHB, System.Int64 FODGKNJIGOP, System.Nullable`1 PAJMLPFMBEJ)", + "file": "EJDCNGBEICB.txt", + "line": 5709, + "contract": { + "kind": "object", + "raw": "PPENFJMFPNE", + "rootType": "PPENFJMFPNE", + "typeDescription": { + "name": "PPENFJMFPNE", + "found": true, + "file": "PPENFJMFPNE.cs", + "declaration": "public class PPENFJMFPNE : KLCOGEIGEBJ", + "kind": "class", + "baseType": "KLCOGEIGEBJ", + "properties": [ + { + "name": "ADCDBMGNIPB", + "type": "List", + "accessibility": "public", + "kind": "property" + }, + { + "name": "BIODFICLKJH", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "DFKGKGIMEMP", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "EJGIMHADHBC", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "FEBCCOFBKKJ", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "IAJDFKODGFL", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "KFFBPMHGFKB", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "LHGHLGDINFL", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "LOJPLANGFMG", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "MMBJPGGOFHL", + "type": "LMLJHMJEIGM", + "accessibility": "public", + "kind": "property" + }, + { + "name": "MMFBPKOOLNG", + "type": "bool", + "accessibility": "public", + "kind": "property" + }, + { + "name": "OHNIGJECGKL", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "PKKADKGDHNI", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + } + ], + "fields": [ + { + "name": "accountId", + "type": "int", + "kind": "field" + }, + { + "name": "tag", + "type": "string", + "kind": "field" + }, + { + "name": "IGAPDHGFLDC", + "type": "PPENFJMFPNE", + "kind": "field" + } + ], + "enumValues": [], + "wireKeys": [], + "isilFile": "PPENFJMFPNE.txt", + "isilType": "PPENFJMFPNE", + "inherits": "KLCOGEIGEBJ", + "inheritedMembers": [ + { + "name": "DADOKMAOFJL", + "type": "long", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "KLCOGEIGEBJ" + }, + { + "name": "GJPGKJJCPBK", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "KLCOGEIGEBJ" + }, + { + "name": "IBLOJJBEKFF", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "KLCOGEIGEBJ" + }, + { + "name": "JCLCOCEOAEP", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "KLCOGEIGEBJ" + }, + { + "name": "JGJCBGEBLLO", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "KLCOGEIGEBJ" + }, + { + "name": "PLFEEIJCHOH", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "KLCOGEIGEBJ" + }, + { + "name": "BJFNPKFOALK", + "type": "long", + "kind": "field", + "inheritedFrom": "KLCOGEIGEBJ" + }, + { + "name": "ACBFDMLHFPB", + "type": "DateTime", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "AHGCOGFEEEE", + "type": "string", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "AKFBNELAMNA", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "BADIGBCKECA", + "type": "int", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "BNBLOBAEDEE", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "CDINMMPNAID", + "type": "NMJEKMMBDDE", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "CDNFGMHLDMJ", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "FIKEBGGCDFN", + "type": "string", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "GIBHIMGJNNO", + "type": "GPDIAKNEBKH", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "HPLBOMGACED", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "IGOPGMHHLKI", + "type": "string", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "JFEAPMIPNEP", + "type": "DPLPMKMFMPB", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "KHIJAFCHLIA", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "KLNJBBPNMBJ", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "KODBEJPEFOJ", + "type": "string", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "LPJLEMJFBPE", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "MGBDHBHCDMH", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "MIKDLDEALPN", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "OFONEIOEIED", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "OILEJFNPDDB", + "type": "HJPGEGENLPH", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "PEEFHKMOMKK", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + } + ], + "inheritedWireKeys": [ + "RoomId", + "IsDorm", + "CloningAllowed", + "DisableMicAutoMute", + "DisableRoomComments", + "EncryptVoiceChat" + ] + } + } + } + ], + "contracts": [ + { + "kind": "object", + "raw": "PPENFJMFPNE", + "rootType": "PPENFJMFPNE", + "typeDescription": { + "name": "PPENFJMFPNE", + "found": true, + "file": "PPENFJMFPNE.cs", + "declaration": "public class PPENFJMFPNE : KLCOGEIGEBJ", + "kind": "class", + "baseType": "KLCOGEIGEBJ", + "properties": [ + { + "name": "ADCDBMGNIPB", + "type": "List", + "accessibility": "public", + "kind": "property" + }, + { + "name": "BIODFICLKJH", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "DFKGKGIMEMP", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "EJGIMHADHBC", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "FEBCCOFBKKJ", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "IAJDFKODGFL", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "KFFBPMHGFKB", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "LHGHLGDINFL", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "LOJPLANGFMG", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "MMBJPGGOFHL", + "type": "LMLJHMJEIGM", + "accessibility": "public", + "kind": "property" + }, + { + "name": "MMFBPKOOLNG", + "type": "bool", + "accessibility": "public", + "kind": "property" + }, + { + "name": "OHNIGJECGKL", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "PKKADKGDHNI", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + } + ], + "fields": [ + { + "name": "accountId", + "type": "int", + "kind": "field" + }, + { + "name": "tag", + "type": "string", + "kind": "field" + }, + { + "name": "IGAPDHGFLDC", + "type": "PPENFJMFPNE", + "kind": "field" + } + ], + "enumValues": [], + "wireKeys": [], + "isilFile": "PPENFJMFPNE.txt", + "isilType": "PPENFJMFPNE", + "inherits": "KLCOGEIGEBJ", + "inheritedMembers": [ + { + "name": "DADOKMAOFJL", + "type": "long", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "KLCOGEIGEBJ" + }, + { + "name": "GJPGKJJCPBK", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "KLCOGEIGEBJ" + }, + { + "name": "IBLOJJBEKFF", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "KLCOGEIGEBJ" + }, + { + "name": "JCLCOCEOAEP", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "KLCOGEIGEBJ" + }, + { + "name": "JGJCBGEBLLO", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "KLCOGEIGEBJ" + }, + { + "name": "PLFEEIJCHOH", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "KLCOGEIGEBJ" + }, + { + "name": "BJFNPKFOALK", + "type": "long", + "kind": "field", + "inheritedFrom": "KLCOGEIGEBJ" + }, + { + "name": "ACBFDMLHFPB", + "type": "DateTime", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "AHGCOGFEEEE", + "type": "string", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "AKFBNELAMNA", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "BADIGBCKECA", + "type": "int", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "BNBLOBAEDEE", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "CDINMMPNAID", + "type": "NMJEKMMBDDE", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "CDNFGMHLDMJ", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "FIKEBGGCDFN", + "type": "string", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "GIBHIMGJNNO", + "type": "GPDIAKNEBKH", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "HPLBOMGACED", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "IGOPGMHHLKI", + "type": "string", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "JFEAPMIPNEP", + "type": "DPLPMKMFMPB", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "KHIJAFCHLIA", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "KLNJBBPNMBJ", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "KODBEJPEFOJ", + "type": "string", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "LPJLEMJFBPE", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "MGBDHBHCDMH", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "MIKDLDEALPN", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "OFONEIOEIED", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "OILEJFNPDDB", + "type": "HJPGEGENLPH", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "PEEFHKMOMKK", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + } + ], + "inheritedWireKeys": [ + "RoomId", + "IsDorm", + "CloningAllowed", + "DisableMicAutoMute", + "DisableRoomComments", + "EncryptVoiceChat" + ] + } + } + ] + }, + { + "family": "rooms", + "route": "rooms/{0}/subrooms/{1}/name", + "evidence": [ + { + "type": "EJDCNGBEICB", + "method": "RecRoom.Async.IPromise`1 PAENPJCDLNJ(System.Int64 HNHLJONGKHB, System.Int64 FODGKNJIGOP, System.String MMBOKOLAJFH)", + "file": "EJDCNGBEICB.txt", + "line": 5099, + "contract": { + "kind": "object", + "raw": "PPENFJMFPNE", + "rootType": "PPENFJMFPNE", + "typeDescription": { + "name": "PPENFJMFPNE", + "found": true, + "file": "PPENFJMFPNE.cs", + "declaration": "public class PPENFJMFPNE : KLCOGEIGEBJ", + "kind": "class", + "baseType": "KLCOGEIGEBJ", + "properties": [ + { + "name": "ADCDBMGNIPB", + "type": "List", + "accessibility": "public", + "kind": "property" + }, + { + "name": "BIODFICLKJH", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "DFKGKGIMEMP", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "EJGIMHADHBC", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "FEBCCOFBKKJ", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "IAJDFKODGFL", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "KFFBPMHGFKB", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "LHGHLGDINFL", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "LOJPLANGFMG", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "MMBJPGGOFHL", + "type": "LMLJHMJEIGM", + "accessibility": "public", + "kind": "property" + }, + { + "name": "MMFBPKOOLNG", + "type": "bool", + "accessibility": "public", + "kind": "property" + }, + { + "name": "OHNIGJECGKL", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "PKKADKGDHNI", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + } + ], + "fields": [ + { + "name": "accountId", + "type": "int", + "kind": "field" + }, + { + "name": "tag", + "type": "string", + "kind": "field" + }, + { + "name": "IGAPDHGFLDC", + "type": "PPENFJMFPNE", + "kind": "field" + } + ], + "enumValues": [], + "wireKeys": [], + "isilFile": "PPENFJMFPNE.txt", + "isilType": "PPENFJMFPNE", + "inherits": "KLCOGEIGEBJ", + "inheritedMembers": [ + { + "name": "DADOKMAOFJL", + "type": "long", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "KLCOGEIGEBJ" + }, + { + "name": "GJPGKJJCPBK", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "KLCOGEIGEBJ" + }, + { + "name": "IBLOJJBEKFF", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "KLCOGEIGEBJ" + }, + { + "name": "JCLCOCEOAEP", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "KLCOGEIGEBJ" + }, + { + "name": "JGJCBGEBLLO", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "KLCOGEIGEBJ" + }, + { + "name": "PLFEEIJCHOH", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "KLCOGEIGEBJ" + }, + { + "name": "BJFNPKFOALK", + "type": "long", + "kind": "field", + "inheritedFrom": "KLCOGEIGEBJ" + }, + { + "name": "ACBFDMLHFPB", + "type": "DateTime", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "AHGCOGFEEEE", + "type": "string", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "AKFBNELAMNA", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "BADIGBCKECA", + "type": "int", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "BNBLOBAEDEE", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "CDINMMPNAID", + "type": "NMJEKMMBDDE", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "CDNFGMHLDMJ", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "FIKEBGGCDFN", + "type": "string", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "GIBHIMGJNNO", + "type": "GPDIAKNEBKH", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "HPLBOMGACED", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "IGOPGMHHLKI", + "type": "string", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "JFEAPMIPNEP", + "type": "DPLPMKMFMPB", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "KHIJAFCHLIA", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "KLNJBBPNMBJ", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "KODBEJPEFOJ", + "type": "string", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "LPJLEMJFBPE", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "MGBDHBHCDMH", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "MIKDLDEALPN", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "OFONEIOEIED", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "OILEJFNPDDB", + "type": "HJPGEGENLPH", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "PEEFHKMOMKK", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + } + ], + "inheritedWireKeys": [ + "RoomId", + "IsDorm", + "CloningAllowed", + "DisableMicAutoMute", + "DisableRoomComments", + "EncryptVoiceChat" + ] + } + } + } + ], + "contracts": [ + { + "kind": "object", + "raw": "PPENFJMFPNE", + "rootType": "PPENFJMFPNE", + "typeDescription": { + "name": "PPENFJMFPNE", + "found": true, + "file": "PPENFJMFPNE.cs", + "declaration": "public class PPENFJMFPNE : KLCOGEIGEBJ", + "kind": "class", + "baseType": "KLCOGEIGEBJ", + "properties": [ + { + "name": "ADCDBMGNIPB", + "type": "List", + "accessibility": "public", + "kind": "property" + }, + { + "name": "BIODFICLKJH", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "DFKGKGIMEMP", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "EJGIMHADHBC", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "FEBCCOFBKKJ", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "IAJDFKODGFL", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "KFFBPMHGFKB", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "LHGHLGDINFL", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "LOJPLANGFMG", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "MMBJPGGOFHL", + "type": "LMLJHMJEIGM", + "accessibility": "public", + "kind": "property" + }, + { + "name": "MMFBPKOOLNG", + "type": "bool", + "accessibility": "public", + "kind": "property" + }, + { + "name": "OHNIGJECGKL", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "PKKADKGDHNI", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + } + ], + "fields": [ + { + "name": "accountId", + "type": "int", + "kind": "field" + }, + { + "name": "tag", + "type": "string", + "kind": "field" + }, + { + "name": "IGAPDHGFLDC", + "type": "PPENFJMFPNE", + "kind": "field" + } + ], + "enumValues": [], + "wireKeys": [], + "isilFile": "PPENFJMFPNE.txt", + "isilType": "PPENFJMFPNE", + "inherits": "KLCOGEIGEBJ", + "inheritedMembers": [ + { + "name": "DADOKMAOFJL", + "type": "long", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "KLCOGEIGEBJ" + }, + { + "name": "GJPGKJJCPBK", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "KLCOGEIGEBJ" + }, + { + "name": "IBLOJJBEKFF", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "KLCOGEIGEBJ" + }, + { + "name": "JCLCOCEOAEP", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "KLCOGEIGEBJ" + }, + { + "name": "JGJCBGEBLLO", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "KLCOGEIGEBJ" + }, + { + "name": "PLFEEIJCHOH", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "KLCOGEIGEBJ" + }, + { + "name": "BJFNPKFOALK", + "type": "long", + "kind": "field", + "inheritedFrom": "KLCOGEIGEBJ" + }, + { + "name": "ACBFDMLHFPB", + "type": "DateTime", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "AHGCOGFEEEE", + "type": "string", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "AKFBNELAMNA", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "BADIGBCKECA", + "type": "int", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "BNBLOBAEDEE", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "CDINMMPNAID", + "type": "NMJEKMMBDDE", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "CDNFGMHLDMJ", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "FIKEBGGCDFN", + "type": "string", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "GIBHIMGJNNO", + "type": "GPDIAKNEBKH", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "HPLBOMGACED", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "IGOPGMHHLKI", + "type": "string", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "JFEAPMIPNEP", + "type": "DPLPMKMFMPB", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "KHIJAFCHLIA", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "KLNJBBPNMBJ", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "KODBEJPEFOJ", + "type": "string", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "LPJLEMJFBPE", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "MGBDHBHCDMH", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "MIKDLDEALPN", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "OFONEIOEIED", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "OILEJFNPDDB", + "type": "HJPGEGENLPH", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "PEEFHKMOMKK", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + } + ], + "inheritedWireKeys": [ + "RoomId", + "IsDorm", + "CloningAllowed", + "DisableMicAutoMute", + "DisableRoomComments", + "EncryptVoiceChat" + ] + } + } + ] + }, + { + "family": "rooms", + "route": "rooms/{0}/subrooms/{1}/restoredata", + "evidence": [ + { + "type": "EJDCNGBEICB", + "method": "RecRoom.Async.IPromise`1 BBGBOCGJFOP(System.Int64 HNHLJONGKHB, System.Int64 FODGKNJIGOP, System.String MEIIMAIGBJD)", + "file": "EJDCNGBEICB.txt", + "line": 6133, + "contract": { + "kind": "object", + "raw": "PPENFJMFPNE", + "rootType": "PPENFJMFPNE", + "typeDescription": { + "name": "PPENFJMFPNE", + "found": true, + "file": "PPENFJMFPNE.cs", + "declaration": "public class PPENFJMFPNE : KLCOGEIGEBJ", + "kind": "class", + "baseType": "KLCOGEIGEBJ", + "properties": [ + { + "name": "ADCDBMGNIPB", + "type": "List", + "accessibility": "public", + "kind": "property" + }, + { + "name": "BIODFICLKJH", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "DFKGKGIMEMP", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "EJGIMHADHBC", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "FEBCCOFBKKJ", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "IAJDFKODGFL", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "KFFBPMHGFKB", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "LHGHLGDINFL", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "LOJPLANGFMG", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "MMBJPGGOFHL", + "type": "LMLJHMJEIGM", + "accessibility": "public", + "kind": "property" + }, + { + "name": "MMFBPKOOLNG", + "type": "bool", + "accessibility": "public", + "kind": "property" + }, + { + "name": "OHNIGJECGKL", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "PKKADKGDHNI", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + } + ], + "fields": [ + { + "name": "accountId", + "type": "int", + "kind": "field" + }, + { + "name": "tag", + "type": "string", + "kind": "field" + }, + { + "name": "IGAPDHGFLDC", + "type": "PPENFJMFPNE", + "kind": "field" + } + ], + "enumValues": [], + "wireKeys": [], + "isilFile": "PPENFJMFPNE.txt", + "isilType": "PPENFJMFPNE", + "inherits": "KLCOGEIGEBJ", + "inheritedMembers": [ + { + "name": "DADOKMAOFJL", + "type": "long", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "KLCOGEIGEBJ" + }, + { + "name": "GJPGKJJCPBK", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "KLCOGEIGEBJ" + }, + { + "name": "IBLOJJBEKFF", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "KLCOGEIGEBJ" + }, + { + "name": "JCLCOCEOAEP", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "KLCOGEIGEBJ" + }, + { + "name": "JGJCBGEBLLO", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "KLCOGEIGEBJ" + }, + { + "name": "PLFEEIJCHOH", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "KLCOGEIGEBJ" + }, + { + "name": "BJFNPKFOALK", + "type": "long", + "kind": "field", + "inheritedFrom": "KLCOGEIGEBJ" + }, + { + "name": "ACBFDMLHFPB", + "type": "DateTime", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "AHGCOGFEEEE", + "type": "string", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "AKFBNELAMNA", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "BADIGBCKECA", + "type": "int", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "BNBLOBAEDEE", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "CDINMMPNAID", + "type": "NMJEKMMBDDE", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "CDNFGMHLDMJ", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "FIKEBGGCDFN", + "type": "string", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "GIBHIMGJNNO", + "type": "GPDIAKNEBKH", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "HPLBOMGACED", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "IGOPGMHHLKI", + "type": "string", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "JFEAPMIPNEP", + "type": "DPLPMKMFMPB", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "KHIJAFCHLIA", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "KLNJBBPNMBJ", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "KODBEJPEFOJ", + "type": "string", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "LPJLEMJFBPE", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "MGBDHBHCDMH", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "MIKDLDEALPN", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "OFONEIOEIED", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "OILEJFNPDDB", + "type": "HJPGEGENLPH", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "PEEFHKMOMKK", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + } + ], + "inheritedWireKeys": [ + "RoomId", + "IsDorm", + "CloningAllowed", + "DisableMicAutoMute", + "DisableRoomComments", + "EncryptVoiceChat" + ] + } + } + } + ], + "contracts": [ + { + "kind": "object", + "raw": "PPENFJMFPNE", + "rootType": "PPENFJMFPNE", + "typeDescription": { + "name": "PPENFJMFPNE", + "found": true, + "file": "PPENFJMFPNE.cs", + "declaration": "public class PPENFJMFPNE : KLCOGEIGEBJ", + "kind": "class", + "baseType": "KLCOGEIGEBJ", + "properties": [ + { + "name": "ADCDBMGNIPB", + "type": "List", + "accessibility": "public", + "kind": "property" + }, + { + "name": "BIODFICLKJH", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "DFKGKGIMEMP", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "EJGIMHADHBC", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "FEBCCOFBKKJ", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "IAJDFKODGFL", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "KFFBPMHGFKB", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "LHGHLGDINFL", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "LOJPLANGFMG", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "MMBJPGGOFHL", + "type": "LMLJHMJEIGM", + "accessibility": "public", + "kind": "property" + }, + { + "name": "MMFBPKOOLNG", + "type": "bool", + "accessibility": "public", + "kind": "property" + }, + { + "name": "OHNIGJECGKL", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "PKKADKGDHNI", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + } + ], + "fields": [ + { + "name": "accountId", + "type": "int", + "kind": "field" + }, + { + "name": "tag", + "type": "string", + "kind": "field" + }, + { + "name": "IGAPDHGFLDC", + "type": "PPENFJMFPNE", + "kind": "field" + } + ], + "enumValues": [], + "wireKeys": [], + "isilFile": "PPENFJMFPNE.txt", + "isilType": "PPENFJMFPNE", + "inherits": "KLCOGEIGEBJ", + "inheritedMembers": [ + { + "name": "DADOKMAOFJL", + "type": "long", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "KLCOGEIGEBJ" + }, + { + "name": "GJPGKJJCPBK", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "KLCOGEIGEBJ" + }, + { + "name": "IBLOJJBEKFF", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "KLCOGEIGEBJ" + }, + { + "name": "JCLCOCEOAEP", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "KLCOGEIGEBJ" + }, + { + "name": "JGJCBGEBLLO", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "KLCOGEIGEBJ" + }, + { + "name": "PLFEEIJCHOH", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "KLCOGEIGEBJ" + }, + { + "name": "BJFNPKFOALK", + "type": "long", + "kind": "field", + "inheritedFrom": "KLCOGEIGEBJ" + }, + { + "name": "ACBFDMLHFPB", + "type": "DateTime", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "AHGCOGFEEEE", + "type": "string", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "AKFBNELAMNA", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "BADIGBCKECA", + "type": "int", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "BNBLOBAEDEE", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "CDINMMPNAID", + "type": "NMJEKMMBDDE", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "CDNFGMHLDMJ", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "FIKEBGGCDFN", + "type": "string", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "GIBHIMGJNNO", + "type": "GPDIAKNEBKH", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "HPLBOMGACED", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "IGOPGMHHLKI", + "type": "string", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "JFEAPMIPNEP", + "type": "DPLPMKMFMPB", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "KHIJAFCHLIA", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "KLNJBBPNMBJ", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "KODBEJPEFOJ", + "type": "string", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "LPJLEMJFBPE", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "MGBDHBHCDMH", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "MIKDLDEALPN", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "OFONEIOEIED", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "OILEJFNPDDB", + "type": "HJPGEGENLPH", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "PEEFHKMOMKK", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + } + ], + "inheritedWireKeys": [ + "RoomId", + "IsDorm", + "CloningAllowed", + "DisableMicAutoMute", + "DisableRoomComments", + "EncryptVoiceChat" + ] + } + } + ] + }, + { + "family": "rooms", + "route": "rooms/{0}/tags", + "evidence": [ + { + "type": "EJDCNGBEICB", + "method": "RecRoom.Async.IPromise`1 ENGFFJGIEGH(System.Int64 HNHLJONGKHB, System.Collections.Generic.IReadOnlyList`1 LBAOCHFCLPO, System.Collections.Generic.IReadOnlyList`1 CAFPJPHILMN)", + "file": "EJDCNGBEICB.txt", + "line": 3384, + "contract": { + "kind": "object", + "raw": "PPENFJMFPNE", + "rootType": "PPENFJMFPNE", + "typeDescription": { + "name": "PPENFJMFPNE", + "found": true, + "file": "PPENFJMFPNE.cs", + "declaration": "public class PPENFJMFPNE : KLCOGEIGEBJ", + "kind": "class", + "baseType": "KLCOGEIGEBJ", + "properties": [ + { + "name": "ADCDBMGNIPB", + "type": "List", + "accessibility": "public", + "kind": "property" + }, + { + "name": "BIODFICLKJH", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "DFKGKGIMEMP", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "EJGIMHADHBC", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "FEBCCOFBKKJ", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "IAJDFKODGFL", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "KFFBPMHGFKB", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "LHGHLGDINFL", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "LOJPLANGFMG", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "MMBJPGGOFHL", + "type": "LMLJHMJEIGM", + "accessibility": "public", + "kind": "property" + }, + { + "name": "MMFBPKOOLNG", + "type": "bool", + "accessibility": "public", + "kind": "property" + }, + { + "name": "OHNIGJECGKL", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "PKKADKGDHNI", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + } + ], + "fields": [ + { + "name": "accountId", + "type": "int", + "kind": "field" + }, + { + "name": "tag", + "type": "string", + "kind": "field" + }, + { + "name": "IGAPDHGFLDC", + "type": "PPENFJMFPNE", + "kind": "field" + } + ], + "enumValues": [], + "wireKeys": [], + "isilFile": "PPENFJMFPNE.txt", + "isilType": "PPENFJMFPNE", + "inherits": "KLCOGEIGEBJ", + "inheritedMembers": [ + { + "name": "DADOKMAOFJL", + "type": "long", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "KLCOGEIGEBJ" + }, + { + "name": "GJPGKJJCPBK", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "KLCOGEIGEBJ" + }, + { + "name": "IBLOJJBEKFF", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "KLCOGEIGEBJ" + }, + { + "name": "JCLCOCEOAEP", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "KLCOGEIGEBJ" + }, + { + "name": "JGJCBGEBLLO", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "KLCOGEIGEBJ" + }, + { + "name": "PLFEEIJCHOH", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "KLCOGEIGEBJ" + }, + { + "name": "BJFNPKFOALK", + "type": "long", + "kind": "field", + "inheritedFrom": "KLCOGEIGEBJ" + }, + { + "name": "ACBFDMLHFPB", + "type": "DateTime", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "AHGCOGFEEEE", + "type": "string", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "AKFBNELAMNA", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "BADIGBCKECA", + "type": "int", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "BNBLOBAEDEE", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "CDINMMPNAID", + "type": "NMJEKMMBDDE", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "CDNFGMHLDMJ", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "FIKEBGGCDFN", + "type": "string", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "GIBHIMGJNNO", + "type": "GPDIAKNEBKH", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "HPLBOMGACED", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "IGOPGMHHLKI", + "type": "string", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "JFEAPMIPNEP", + "type": "DPLPMKMFMPB", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "KHIJAFCHLIA", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "KLNJBBPNMBJ", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "KODBEJPEFOJ", + "type": "string", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "LPJLEMJFBPE", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "MGBDHBHCDMH", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "MIKDLDEALPN", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "OFONEIOEIED", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "OILEJFNPDDB", + "type": "HJPGEGENLPH", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "PEEFHKMOMKK", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + } + ], + "inheritedWireKeys": [ + "RoomId", + "IsDorm", + "CloningAllowed", + "DisableMicAutoMute", + "DisableRoomComments", + "EncryptVoiceChat" + ] + } + } + } + ], + "contracts": [ + { + "kind": "object", + "raw": "PPENFJMFPNE", + "rootType": "PPENFJMFPNE", + "typeDescription": { + "name": "PPENFJMFPNE", + "found": true, + "file": "PPENFJMFPNE.cs", + "declaration": "public class PPENFJMFPNE : KLCOGEIGEBJ", + "kind": "class", + "baseType": "KLCOGEIGEBJ", + "properties": [ + { + "name": "ADCDBMGNIPB", + "type": "List", + "accessibility": "public", + "kind": "property" + }, + { + "name": "BIODFICLKJH", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "DFKGKGIMEMP", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "EJGIMHADHBC", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "FEBCCOFBKKJ", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "IAJDFKODGFL", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "KFFBPMHGFKB", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "LHGHLGDINFL", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "LOJPLANGFMG", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "MMBJPGGOFHL", + "type": "LMLJHMJEIGM", + "accessibility": "public", + "kind": "property" + }, + { + "name": "MMFBPKOOLNG", + "type": "bool", + "accessibility": "public", + "kind": "property" + }, + { + "name": "OHNIGJECGKL", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "PKKADKGDHNI", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + } + ], + "fields": [ + { + "name": "accountId", + "type": "int", + "kind": "field" + }, + { + "name": "tag", + "type": "string", + "kind": "field" + }, + { + "name": "IGAPDHGFLDC", + "type": "PPENFJMFPNE", + "kind": "field" + } + ], + "enumValues": [], + "wireKeys": [], + "isilFile": "PPENFJMFPNE.txt", + "isilType": "PPENFJMFPNE", + "inherits": "KLCOGEIGEBJ", + "inheritedMembers": [ + { + "name": "DADOKMAOFJL", + "type": "long", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "KLCOGEIGEBJ" + }, + { + "name": "GJPGKJJCPBK", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "KLCOGEIGEBJ" + }, + { + "name": "IBLOJJBEKFF", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "KLCOGEIGEBJ" + }, + { + "name": "JCLCOCEOAEP", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "KLCOGEIGEBJ" + }, + { + "name": "JGJCBGEBLLO", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "KLCOGEIGEBJ" + }, + { + "name": "PLFEEIJCHOH", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "KLCOGEIGEBJ" + }, + { + "name": "BJFNPKFOALK", + "type": "long", + "kind": "field", + "inheritedFrom": "KLCOGEIGEBJ" + }, + { + "name": "ACBFDMLHFPB", + "type": "DateTime", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "AHGCOGFEEEE", + "type": "string", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "AKFBNELAMNA", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "BADIGBCKECA", + "type": "int", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "BNBLOBAEDEE", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "CDINMMPNAID", + "type": "NMJEKMMBDDE", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "CDNFGMHLDMJ", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "FIKEBGGCDFN", + "type": "string", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "GIBHIMGJNNO", + "type": "GPDIAKNEBKH", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "HPLBOMGACED", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "IGOPGMHHLKI", + "type": "string", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "JFEAPMIPNEP", + "type": "DPLPMKMFMPB", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "KHIJAFCHLIA", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "KLNJBBPNMBJ", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "KODBEJPEFOJ", + "type": "string", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "LPJLEMJFBPE", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "MGBDHBHCDMH", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "MIKDLDEALPN", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "OFONEIOEIED", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "OILEJFNPDDB", + "type": "HJPGEGENLPH", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "PEEFHKMOMKK", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + } + ], + "inheritedWireKeys": [ + "RoomId", + "IsDorm", + "CloningAllowed", + "DisableMicAutoMute", + "DisableRoomComments", + "EncryptVoiceChat" + ] + } + } + ] + }, + { + "family": "rooms", + "route": "rooms/{0}/voice_chat_encryption", + "evidence": [ + { + "type": "EJDCNGBEICB", + "method": "RecRoom.Async.IPromise`1 PKDCMMFILPB(System.Int64 HNHLJONGKHB, System.Boolean NFJINCPDPDG)", + "file": "EJDCNGBEICB.txt", + "line": 4270, + "contract": { + "kind": "object", + "raw": "PPENFJMFPNE", + "rootType": "PPENFJMFPNE", + "typeDescription": { + "name": "PPENFJMFPNE", + "found": true, + "file": "PPENFJMFPNE.cs", + "declaration": "public class PPENFJMFPNE : KLCOGEIGEBJ", + "kind": "class", + "baseType": "KLCOGEIGEBJ", + "properties": [ + { + "name": "ADCDBMGNIPB", + "type": "List", + "accessibility": "public", + "kind": "property" + }, + { + "name": "BIODFICLKJH", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "DFKGKGIMEMP", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "EJGIMHADHBC", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "FEBCCOFBKKJ", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "IAJDFKODGFL", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "KFFBPMHGFKB", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "LHGHLGDINFL", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "LOJPLANGFMG", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "MMBJPGGOFHL", + "type": "LMLJHMJEIGM", + "accessibility": "public", + "kind": "property" + }, + { + "name": "MMFBPKOOLNG", + "type": "bool", + "accessibility": "public", + "kind": "property" + }, + { + "name": "OHNIGJECGKL", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "PKKADKGDHNI", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + } + ], + "fields": [ + { + "name": "accountId", + "type": "int", + "kind": "field" + }, + { + "name": "tag", + "type": "string", + "kind": "field" + }, + { + "name": "IGAPDHGFLDC", + "type": "PPENFJMFPNE", + "kind": "field" + } + ], + "enumValues": [], + "wireKeys": [], + "isilFile": "PPENFJMFPNE.txt", + "isilType": "PPENFJMFPNE", + "inherits": "KLCOGEIGEBJ", + "inheritedMembers": [ + { + "name": "DADOKMAOFJL", + "type": "long", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "KLCOGEIGEBJ" + }, + { + "name": "GJPGKJJCPBK", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "KLCOGEIGEBJ" + }, + { + "name": "IBLOJJBEKFF", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "KLCOGEIGEBJ" + }, + { + "name": "JCLCOCEOAEP", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "KLCOGEIGEBJ" + }, + { + "name": "JGJCBGEBLLO", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "KLCOGEIGEBJ" + }, + { + "name": "PLFEEIJCHOH", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "KLCOGEIGEBJ" + }, + { + "name": "BJFNPKFOALK", + "type": "long", + "kind": "field", + "inheritedFrom": "KLCOGEIGEBJ" + }, + { + "name": "ACBFDMLHFPB", + "type": "DateTime", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "AHGCOGFEEEE", + "type": "string", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "AKFBNELAMNA", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "BADIGBCKECA", + "type": "int", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "BNBLOBAEDEE", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "CDINMMPNAID", + "type": "NMJEKMMBDDE", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "CDNFGMHLDMJ", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "FIKEBGGCDFN", + "type": "string", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "GIBHIMGJNNO", + "type": "GPDIAKNEBKH", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "HPLBOMGACED", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "IGOPGMHHLKI", + "type": "string", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "JFEAPMIPNEP", + "type": "DPLPMKMFMPB", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "KHIJAFCHLIA", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "KLNJBBPNMBJ", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "KODBEJPEFOJ", + "type": "string", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "LPJLEMJFBPE", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "MGBDHBHCDMH", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "MIKDLDEALPN", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "OFONEIOEIED", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "OILEJFNPDDB", + "type": "HJPGEGENLPH", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "PEEFHKMOMKK", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + } + ], + "inheritedWireKeys": [ + "RoomId", + "IsDorm", + "CloningAllowed", + "DisableMicAutoMute", + "DisableRoomComments", + "EncryptVoiceChat" + ] + } + } + } + ], + "contracts": [ + { + "kind": "object", + "raw": "PPENFJMFPNE", + "rootType": "PPENFJMFPNE", + "typeDescription": { + "name": "PPENFJMFPNE", + "found": true, + "file": "PPENFJMFPNE.cs", + "declaration": "public class PPENFJMFPNE : KLCOGEIGEBJ", + "kind": "class", + "baseType": "KLCOGEIGEBJ", + "properties": [ + { + "name": "ADCDBMGNIPB", + "type": "List", + "accessibility": "public", + "kind": "property" + }, + { + "name": "BIODFICLKJH", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "DFKGKGIMEMP", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "EJGIMHADHBC", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "FEBCCOFBKKJ", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "IAJDFKODGFL", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "KFFBPMHGFKB", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "LHGHLGDINFL", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "LOJPLANGFMG", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "MMBJPGGOFHL", + "type": "LMLJHMJEIGM", + "accessibility": "public", + "kind": "property" + }, + { + "name": "MMFBPKOOLNG", + "type": "bool", + "accessibility": "public", + "kind": "property" + }, + { + "name": "OHNIGJECGKL", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "PKKADKGDHNI", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + } + ], + "fields": [ + { + "name": "accountId", + "type": "int", + "kind": "field" + }, + { + "name": "tag", + "type": "string", + "kind": "field" + }, + { + "name": "IGAPDHGFLDC", + "type": "PPENFJMFPNE", + "kind": "field" + } + ], + "enumValues": [], + "wireKeys": [], + "isilFile": "PPENFJMFPNE.txt", + "isilType": "PPENFJMFPNE", + "inherits": "KLCOGEIGEBJ", + "inheritedMembers": [ + { + "name": "DADOKMAOFJL", + "type": "long", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "KLCOGEIGEBJ" + }, + { + "name": "GJPGKJJCPBK", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "KLCOGEIGEBJ" + }, + { + "name": "IBLOJJBEKFF", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "KLCOGEIGEBJ" + }, + { + "name": "JCLCOCEOAEP", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "KLCOGEIGEBJ" + }, + { + "name": "JGJCBGEBLLO", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "KLCOGEIGEBJ" + }, + { + "name": "PLFEEIJCHOH", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "KLCOGEIGEBJ" + }, + { + "name": "BJFNPKFOALK", + "type": "long", + "kind": "field", + "inheritedFrom": "KLCOGEIGEBJ" + }, + { + "name": "ACBFDMLHFPB", + "type": "DateTime", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "AHGCOGFEEEE", + "type": "string", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "AKFBNELAMNA", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "BADIGBCKECA", + "type": "int", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "BNBLOBAEDEE", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "CDINMMPNAID", + "type": "NMJEKMMBDDE", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "CDNFGMHLDMJ", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "FIKEBGGCDFN", + "type": "string", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "GIBHIMGJNNO", + "type": "GPDIAKNEBKH", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "HPLBOMGACED", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "IGOPGMHHLKI", + "type": "string", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "JFEAPMIPNEP", + "type": "DPLPMKMFMPB", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "KHIJAFCHLIA", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "KLNJBBPNMBJ", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "KODBEJPEFOJ", + "type": "string", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "LPJLEMJFBPE", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "MGBDHBHCDMH", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "MIKDLDEALPN", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "OFONEIOEIED", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "OILEJFNPDDB", + "type": "HJPGEGENLPH", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "PEEFHKMOMKK", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + } + ], + "inheritedWireKeys": [ + "RoomId", + "IsDorm", + "CloningAllowed", + "DisableMicAutoMute", + "DisableRoomComments", + "EncryptVoiceChat" + ] + } + } + ] + }, + { + "family": "rooms", + "route": "rooms/{0}/warning", + "evidence": [ + { + "type": "EJDCNGBEICB", + "method": "RecRoom.Async.IPromise`1 NPACFDBCHDP(System.Int64 HNHLJONGKHB, GPDIAKNEBKH LEFPAAGGFFA, System.String NLEBEKDHJIJ)", + "file": "EJDCNGBEICB.txt", + "line": 3510, + "contract": { + "kind": "object", + "raw": "PPENFJMFPNE", + "rootType": "PPENFJMFPNE", + "typeDescription": { + "name": "PPENFJMFPNE", + "found": true, + "file": "PPENFJMFPNE.cs", + "declaration": "public class PPENFJMFPNE : KLCOGEIGEBJ", + "kind": "class", + "baseType": "KLCOGEIGEBJ", + "properties": [ + { + "name": "ADCDBMGNIPB", + "type": "List", + "accessibility": "public", + "kind": "property" + }, + { + "name": "BIODFICLKJH", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "DFKGKGIMEMP", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "EJGIMHADHBC", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "FEBCCOFBKKJ", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "IAJDFKODGFL", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "KFFBPMHGFKB", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "LHGHLGDINFL", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "LOJPLANGFMG", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "MMBJPGGOFHL", + "type": "LMLJHMJEIGM", + "accessibility": "public", + "kind": "property" + }, + { + "name": "MMFBPKOOLNG", + "type": "bool", + "accessibility": "public", + "kind": "property" + }, + { + "name": "OHNIGJECGKL", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "PKKADKGDHNI", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + } + ], + "fields": [ + { + "name": "accountId", + "type": "int", + "kind": "field" + }, + { + "name": "tag", + "type": "string", + "kind": "field" + }, + { + "name": "IGAPDHGFLDC", + "type": "PPENFJMFPNE", + "kind": "field" + } + ], + "enumValues": [], + "wireKeys": [], + "isilFile": "PPENFJMFPNE.txt", + "isilType": "PPENFJMFPNE", + "inherits": "KLCOGEIGEBJ", + "inheritedMembers": [ + { + "name": "DADOKMAOFJL", + "type": "long", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "KLCOGEIGEBJ" + }, + { + "name": "GJPGKJJCPBK", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "KLCOGEIGEBJ" + }, + { + "name": "IBLOJJBEKFF", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "KLCOGEIGEBJ" + }, + { + "name": "JCLCOCEOAEP", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "KLCOGEIGEBJ" + }, + { + "name": "JGJCBGEBLLO", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "KLCOGEIGEBJ" + }, + { + "name": "PLFEEIJCHOH", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "KLCOGEIGEBJ" + }, + { + "name": "BJFNPKFOALK", + "type": "long", + "kind": "field", + "inheritedFrom": "KLCOGEIGEBJ" + }, + { + "name": "ACBFDMLHFPB", + "type": "DateTime", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "AHGCOGFEEEE", + "type": "string", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "AKFBNELAMNA", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "BADIGBCKECA", + "type": "int", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "BNBLOBAEDEE", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "CDINMMPNAID", + "type": "NMJEKMMBDDE", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "CDNFGMHLDMJ", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "FIKEBGGCDFN", + "type": "string", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "GIBHIMGJNNO", + "type": "GPDIAKNEBKH", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "HPLBOMGACED", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "IGOPGMHHLKI", + "type": "string", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "JFEAPMIPNEP", + "type": "DPLPMKMFMPB", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "KHIJAFCHLIA", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "KLNJBBPNMBJ", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "KODBEJPEFOJ", + "type": "string", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "LPJLEMJFBPE", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "MGBDHBHCDMH", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "MIKDLDEALPN", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "OFONEIOEIED", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "OILEJFNPDDB", + "type": "HJPGEGENLPH", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "PEEFHKMOMKK", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + } + ], + "inheritedWireKeys": [ + "RoomId", + "IsDorm", + "CloningAllowed", + "DisableMicAutoMute", + "DisableRoomComments", + "EncryptVoiceChat" + ] + } + } + } + ], + "contracts": [ + { + "kind": "object", + "raw": "PPENFJMFPNE", + "rootType": "PPENFJMFPNE", + "typeDescription": { + "name": "PPENFJMFPNE", + "found": true, + "file": "PPENFJMFPNE.cs", + "declaration": "public class PPENFJMFPNE : KLCOGEIGEBJ", + "kind": "class", + "baseType": "KLCOGEIGEBJ", + "properties": [ + { + "name": "ADCDBMGNIPB", + "type": "List", + "accessibility": "public", + "kind": "property" + }, + { + "name": "BIODFICLKJH", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "DFKGKGIMEMP", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "EJGIMHADHBC", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "FEBCCOFBKKJ", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "IAJDFKODGFL", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "KFFBPMHGFKB", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "LHGHLGDINFL", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "LOJPLANGFMG", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "MMBJPGGOFHL", + "type": "LMLJHMJEIGM", + "accessibility": "public", + "kind": "property" + }, + { + "name": "MMFBPKOOLNG", + "type": "bool", + "accessibility": "public", + "kind": "property" + }, + { + "name": "OHNIGJECGKL", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + }, + { + "name": "PKKADKGDHNI", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + } + ], + "fields": [ + { + "name": "accountId", + "type": "int", + "kind": "field" + }, + { + "name": "tag", + "type": "string", + "kind": "field" + }, + { + "name": "IGAPDHGFLDC", + "type": "PPENFJMFPNE", + "kind": "field" + } + ], + "enumValues": [], + "wireKeys": [], + "isilFile": "PPENFJMFPNE.txt", + "isilType": "PPENFJMFPNE", + "inherits": "KLCOGEIGEBJ", + "inheritedMembers": [ + { + "name": "DADOKMAOFJL", + "type": "long", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "KLCOGEIGEBJ" + }, + { + "name": "GJPGKJJCPBK", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "KLCOGEIGEBJ" + }, + { + "name": "IBLOJJBEKFF", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "KLCOGEIGEBJ" + }, + { + "name": "JCLCOCEOAEP", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "KLCOGEIGEBJ" + }, + { + "name": "JGJCBGEBLLO", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "KLCOGEIGEBJ" + }, + { + "name": "PLFEEIJCHOH", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "KLCOGEIGEBJ" + }, + { + "name": "BJFNPKFOALK", + "type": "long", + "kind": "field", + "inheritedFrom": "KLCOGEIGEBJ" + }, + { + "name": "ACBFDMLHFPB", + "type": "DateTime", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "AHGCOGFEEEE", + "type": "string", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "AKFBNELAMNA", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "BADIGBCKECA", + "type": "int", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "BNBLOBAEDEE", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "CDINMMPNAID", + "type": "NMJEKMMBDDE", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "CDNFGMHLDMJ", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "FIKEBGGCDFN", + "type": "string", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "GIBHIMGJNNO", + "type": "GPDIAKNEBKH", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "HPLBOMGACED", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "IGOPGMHHLKI", + "type": "string", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "JFEAPMIPNEP", + "type": "DPLPMKMFMPB", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "KHIJAFCHLIA", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "KLNJBBPNMBJ", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "KODBEJPEFOJ", + "type": "string", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "LPJLEMJFBPE", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "MGBDHBHCDMH", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "MIKDLDEALPN", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "OFONEIOEIED", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "OILEJFNPDDB", + "type": "HJPGEGENLPH", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "PEEFHKMOMKK", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + } + ], + "inheritedWireKeys": [ + "RoomId", + "IsDorm", + "CloningAllowed", + "DisableMicAutoMute", + "DisableRoomComments", + "EncryptVoiceChat" + ] + } + } + ] + }, + { + "family": "rooms", + "route": "rooms/base", + "evidence": [ + { + "type": "EJDCNGBEICB", + "method": "RecRoom.Async.IPromise`1> LMEPPFGPHLA()", + "file": "EJDCNGBEICB.txt", + "line": 784, + "contract": { + "kind": "array", + "raw": "System.Collections.Generic.List`1", + "rootType": "KLCOGEIGEBJ", + "item": { + "kind": "object", + "raw": "KLCOGEIGEBJ", + "rootType": "KLCOGEIGEBJ" + }, + "typeDescription": { + "name": "KLCOGEIGEBJ", + "found": true, + "file": "KLCOGEIGEBJ.cs", + "declaration": "public class KLCOGEIGEBJ : MKAMHOIHOJK", + "kind": "class", + "baseType": "MKAMHOIHOJK", + "properties": [ + { + "name": "DADOKMAOFJL", + "type": "long", + "accessibility": "public", + "kind": "property" + }, + { + "name": "GJPGKJJCPBK", + "type": "bool", + "accessibility": "public", + "kind": "property" + }, + { + "name": "IBLOJJBEKFF", + "type": "bool", + "accessibility": "public", + "kind": "property" + }, + { + "name": "JCLCOCEOAEP", + "type": "bool", + "accessibility": "public", + "kind": "property" + }, + { + "name": "JGJCBGEBLLO", + "type": "bool", + "accessibility": "public", + "kind": "property" + }, + { + "name": "PLFEEIJCHOH", + "type": "bool", + "accessibility": "public", + "kind": "property" + } + ], + "fields": [ + { + "name": "BJFNPKFOALK", + "type": "long", + "kind": "field" + } + ], + "enumValues": [], + "wireKeys": [ + "RoomId", + "IsDorm", + "CloningAllowed", + "DisableMicAutoMute", + "DisableRoomComments", + "EncryptVoiceChat" + ], + "isilFile": "KLCOGEIGEBJ.txt", + "isilType": "KLCOGEIGEBJ", + "inherits": "MKAMHOIHOJK", + "inheritedMembers": [ + { + "name": "ACBFDMLHFPB", + "type": "DateTime", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "AHGCOGFEEEE", + "type": "string", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "AKFBNELAMNA", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "BADIGBCKECA", + "type": "int", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "BNBLOBAEDEE", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "CDINMMPNAID", + "type": "NMJEKMMBDDE", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "CDNFGMHLDMJ", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "FIKEBGGCDFN", + "type": "string", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "GIBHIMGJNNO", + "type": "GPDIAKNEBKH", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "HPLBOMGACED", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "IGOPGMHHLKI", + "type": "string", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "JFEAPMIPNEP", + "type": "DPLPMKMFMPB", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "KHIJAFCHLIA", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "KLNJBBPNMBJ", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "KODBEJPEFOJ", + "type": "string", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "LPJLEMJFBPE", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "MGBDHBHCDMH", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "MIKDLDEALPN", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "OFONEIOEIED", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "OILEJFNPDDB", + "type": "HJPGEGENLPH", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "PEEFHKMOMKK", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + } + ], + "inheritedWireKeys": [ + "Name", + "Description", + "ImageName", + "WarningMask", + "CustomWarning", + "CreatorAccountId", + "State", + "Accessibility", + "SupportsLevelVoting", + "IsRRO", + "SupportsScreens", + "SupportsWalkVR", + "SupportsTeleportVR", + "SupportsVRLow", + "SupportsQuest2", + "SupportsMobile", + "SupportsJuniors", + "CreatedAt", + "Stats" + ] + } + } + } + ], + "contracts": [ + { + "kind": "array", + "raw": "System.Collections.Generic.List`1", + "rootType": "KLCOGEIGEBJ", + "item": { + "kind": "object", + "raw": "KLCOGEIGEBJ", + "rootType": "KLCOGEIGEBJ" + }, + "typeDescription": { + "name": "KLCOGEIGEBJ", + "found": true, + "file": "KLCOGEIGEBJ.cs", + "declaration": "public class KLCOGEIGEBJ : MKAMHOIHOJK", + "kind": "class", + "baseType": "MKAMHOIHOJK", + "properties": [ + { + "name": "DADOKMAOFJL", + "type": "long", + "accessibility": "public", + "kind": "property" + }, + { + "name": "GJPGKJJCPBK", + "type": "bool", + "accessibility": "public", + "kind": "property" + }, + { + "name": "IBLOJJBEKFF", + "type": "bool", + "accessibility": "public", + "kind": "property" + }, + { + "name": "JCLCOCEOAEP", + "type": "bool", + "accessibility": "public", + "kind": "property" + }, + { + "name": "JGJCBGEBLLO", + "type": "bool", + "accessibility": "public", + "kind": "property" + }, + { + "name": "PLFEEIJCHOH", + "type": "bool", + "accessibility": "public", + "kind": "property" + } + ], + "fields": [ + { + "name": "BJFNPKFOALK", + "type": "long", + "kind": "field" + } + ], + "enumValues": [], + "wireKeys": [ + "RoomId", + "IsDorm", + "CloningAllowed", + "DisableMicAutoMute", + "DisableRoomComments", + "EncryptVoiceChat" + ], + "isilFile": "KLCOGEIGEBJ.txt", + "isilType": "KLCOGEIGEBJ", + "inherits": "MKAMHOIHOJK", + "inheritedMembers": [ + { + "name": "ACBFDMLHFPB", + "type": "DateTime", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "AHGCOGFEEEE", + "type": "string", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "AKFBNELAMNA", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "BADIGBCKECA", + "type": "int", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "BNBLOBAEDEE", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "CDINMMPNAID", + "type": "NMJEKMMBDDE", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "CDNFGMHLDMJ", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "FIKEBGGCDFN", + "type": "string", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "GIBHIMGJNNO", + "type": "GPDIAKNEBKH", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "HPLBOMGACED", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "IGOPGMHHLKI", + "type": "string", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "JFEAPMIPNEP", + "type": "DPLPMKMFMPB", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "KHIJAFCHLIA", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "KLNJBBPNMBJ", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "KODBEJPEFOJ", + "type": "string", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "LPJLEMJFBPE", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "MGBDHBHCDMH", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "MIKDLDEALPN", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "OFONEIOEIED", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "OILEJFNPDDB", + "type": "HJPGEGENLPH", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "PEEFHKMOMKK", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + } + ], + "inheritedWireKeys": [ + "Name", + "Description", + "ImageName", + "WarningMask", + "CustomWarning", + "CreatorAccountId", + "State", + "Accessibility", + "SupportsLevelVoting", + "IsRRO", + "SupportsScreens", + "SupportsWalkVR", + "SupportsTeleportVR", + "SupportsVRLow", + "SupportsQuest2", + "SupportsMobile", + "SupportsJuniors", + "CreatedAt", + "Stats" + ] + } + } + ] + }, + { + "family": "rooms", + "route": "rooms/bulk", + "evidence": [ + { + "type": "EJDCNGBEICB", + "method": "RecRoom.Async.IPromise`1> JPNBCKICJIH(System.Collections.Generic.IReadOnlyList`1 HJLOFEGEMHE)", + "file": "EJDCNGBEICB.txt", + "line": 319, + "contract": { + "kind": "array", + "raw": "System.Collections.Generic.List`1", + "rootType": "KLCOGEIGEBJ", + "item": { + "kind": "object", + "raw": "KLCOGEIGEBJ", + "rootType": "KLCOGEIGEBJ" + }, + "typeDescription": { + "name": "KLCOGEIGEBJ", + "found": true, + "file": "KLCOGEIGEBJ.cs", + "declaration": "public class KLCOGEIGEBJ : MKAMHOIHOJK", + "kind": "class", + "baseType": "MKAMHOIHOJK", + "properties": [ + { + "name": "DADOKMAOFJL", + "type": "long", + "accessibility": "public", + "kind": "property" + }, + { + "name": "GJPGKJJCPBK", + "type": "bool", + "accessibility": "public", + "kind": "property" + }, + { + "name": "IBLOJJBEKFF", + "type": "bool", + "accessibility": "public", + "kind": "property" + }, + { + "name": "JCLCOCEOAEP", + "type": "bool", + "accessibility": "public", + "kind": "property" + }, + { + "name": "JGJCBGEBLLO", + "type": "bool", + "accessibility": "public", + "kind": "property" + }, + { + "name": "PLFEEIJCHOH", + "type": "bool", + "accessibility": "public", + "kind": "property" + } + ], + "fields": [ + { + "name": "BJFNPKFOALK", + "type": "long", + "kind": "field" + } + ], + "enumValues": [], + "wireKeys": [ + "RoomId", + "IsDorm", + "CloningAllowed", + "DisableMicAutoMute", + "DisableRoomComments", + "EncryptVoiceChat" + ], + "isilFile": "KLCOGEIGEBJ.txt", + "isilType": "KLCOGEIGEBJ", + "inherits": "MKAMHOIHOJK", + "inheritedMembers": [ + { + "name": "ACBFDMLHFPB", + "type": "DateTime", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "AHGCOGFEEEE", + "type": "string", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "AKFBNELAMNA", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "BADIGBCKECA", + "type": "int", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "BNBLOBAEDEE", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "CDINMMPNAID", + "type": "NMJEKMMBDDE", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "CDNFGMHLDMJ", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "FIKEBGGCDFN", + "type": "string", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "GIBHIMGJNNO", + "type": "GPDIAKNEBKH", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "HPLBOMGACED", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "IGOPGMHHLKI", + "type": "string", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "JFEAPMIPNEP", + "type": "DPLPMKMFMPB", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "KHIJAFCHLIA", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "KLNJBBPNMBJ", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "KODBEJPEFOJ", + "type": "string", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "LPJLEMJFBPE", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "MGBDHBHCDMH", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "MIKDLDEALPN", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "OFONEIOEIED", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "OILEJFNPDDB", + "type": "HJPGEGENLPH", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "PEEFHKMOMKK", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + } + ], + "inheritedWireKeys": [ + "Name", + "Description", + "ImageName", + "WarningMask", + "CustomWarning", + "CreatorAccountId", + "State", + "Accessibility", + "SupportsLevelVoting", + "IsRRO", + "SupportsScreens", + "SupportsWalkVR", + "SupportsTeleportVR", + "SupportsVRLow", + "SupportsQuest2", + "SupportsMobile", + "SupportsJuniors", + "CreatedAt", + "Stats" + ] + } + } + }, + { + "type": "EJDCNGBEICB", + "method": "RecRoom.Async.IPromise`1> JPNBCKICJIH(System.Collections.Generic.IReadOnlyList`1 GDDBDIJIBCO)", + "file": "EJDCNGBEICB.txt", + "line": 491, + "contract": { + "kind": "array", + "raw": "System.Collections.Generic.List`1", + "rootType": "KLCOGEIGEBJ", + "item": { + "kind": "object", + "raw": "KLCOGEIGEBJ", + "rootType": "KLCOGEIGEBJ" + }, + "typeDescription": { + "name": "KLCOGEIGEBJ", + "found": true, + "file": "KLCOGEIGEBJ.cs", + "declaration": "public class KLCOGEIGEBJ : MKAMHOIHOJK", + "kind": "class", + "baseType": "MKAMHOIHOJK", + "properties": [ + { + "name": "DADOKMAOFJL", + "type": "long", + "accessibility": "public", + "kind": "property" + }, + { + "name": "GJPGKJJCPBK", + "type": "bool", + "accessibility": "public", + "kind": "property" + }, + { + "name": "IBLOJJBEKFF", + "type": "bool", + "accessibility": "public", + "kind": "property" + }, + { + "name": "JCLCOCEOAEP", + "type": "bool", + "accessibility": "public", + "kind": "property" + }, + { + "name": "JGJCBGEBLLO", + "type": "bool", + "accessibility": "public", + "kind": "property" + }, + { + "name": "PLFEEIJCHOH", + "type": "bool", + "accessibility": "public", + "kind": "property" + } + ], + "fields": [ + { + "name": "BJFNPKFOALK", + "type": "long", + "kind": "field" + } + ], + "enumValues": [], + "wireKeys": [ + "RoomId", + "IsDorm", + "CloningAllowed", + "DisableMicAutoMute", + "DisableRoomComments", + "EncryptVoiceChat" + ], + "isilFile": "KLCOGEIGEBJ.txt", + "isilType": "KLCOGEIGEBJ", + "inherits": "MKAMHOIHOJK", + "inheritedMembers": [ + { + "name": "ACBFDMLHFPB", + "type": "DateTime", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "AHGCOGFEEEE", + "type": "string", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "AKFBNELAMNA", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "BADIGBCKECA", + "type": "int", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "BNBLOBAEDEE", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "CDINMMPNAID", + "type": "NMJEKMMBDDE", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "CDNFGMHLDMJ", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "FIKEBGGCDFN", + "type": "string", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "GIBHIMGJNNO", + "type": "GPDIAKNEBKH", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "HPLBOMGACED", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "IGOPGMHHLKI", + "type": "string", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "JFEAPMIPNEP", + "type": "DPLPMKMFMPB", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "KHIJAFCHLIA", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "KLNJBBPNMBJ", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "KODBEJPEFOJ", + "type": "string", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "LPJLEMJFBPE", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "MGBDHBHCDMH", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "MIKDLDEALPN", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "OFONEIOEIED", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "OILEJFNPDDB", + "type": "HJPGEGENLPH", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "PEEFHKMOMKK", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + } + ], + "inheritedWireKeys": [ + "Name", + "Description", + "ImageName", + "WarningMask", + "CustomWarning", + "CreatorAccountId", + "State", + "Accessibility", + "SupportsLevelVoting", + "IsRRO", + "SupportsScreens", + "SupportsWalkVR", + "SupportsTeleportVR", + "SupportsVRLow", + "SupportsQuest2", + "SupportsMobile", + "SupportsJuniors", + "CreatedAt", + "Stats" + ] + } + } + } + ], + "contracts": [ + { + "kind": "array", + "raw": "System.Collections.Generic.List`1", + "rootType": "KLCOGEIGEBJ", + "item": { + "kind": "object", + "raw": "KLCOGEIGEBJ", + "rootType": "KLCOGEIGEBJ" + }, + "typeDescription": { + "name": "KLCOGEIGEBJ", + "found": true, + "file": "KLCOGEIGEBJ.cs", + "declaration": "public class KLCOGEIGEBJ : MKAMHOIHOJK", + "kind": "class", + "baseType": "MKAMHOIHOJK", + "properties": [ + { + "name": "DADOKMAOFJL", + "type": "long", + "accessibility": "public", + "kind": "property" + }, + { + "name": "GJPGKJJCPBK", + "type": "bool", + "accessibility": "public", + "kind": "property" + }, + { + "name": "IBLOJJBEKFF", + "type": "bool", + "accessibility": "public", + "kind": "property" + }, + { + "name": "JCLCOCEOAEP", + "type": "bool", + "accessibility": "public", + "kind": "property" + }, + { + "name": "JGJCBGEBLLO", + "type": "bool", + "accessibility": "public", + "kind": "property" + }, + { + "name": "PLFEEIJCHOH", + "type": "bool", + "accessibility": "public", + "kind": "property" + } + ], + "fields": [ + { + "name": "BJFNPKFOALK", + "type": "long", + "kind": "field" + } + ], + "enumValues": [], + "wireKeys": [ + "RoomId", + "IsDorm", + "CloningAllowed", + "DisableMicAutoMute", + "DisableRoomComments", + "EncryptVoiceChat" + ], + "isilFile": "KLCOGEIGEBJ.txt", + "isilType": "KLCOGEIGEBJ", + "inherits": "MKAMHOIHOJK", + "inheritedMembers": [ + { + "name": "ACBFDMLHFPB", + "type": "DateTime", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "AHGCOGFEEEE", + "type": "string", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "AKFBNELAMNA", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "BADIGBCKECA", + "type": "int", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "BNBLOBAEDEE", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "CDINMMPNAID", + "type": "NMJEKMMBDDE", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "CDNFGMHLDMJ", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "FIKEBGGCDFN", + "type": "string", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "GIBHIMGJNNO", + "type": "GPDIAKNEBKH", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "HPLBOMGACED", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "IGOPGMHHLKI", + "type": "string", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "JFEAPMIPNEP", + "type": "DPLPMKMFMPB", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "KHIJAFCHLIA", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "KLNJBBPNMBJ", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "KODBEJPEFOJ", + "type": "string", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "LPJLEMJFBPE", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "MGBDHBHCDMH", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "MIKDLDEALPN", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "OFONEIOEIED", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "OILEJFNPDDB", + "type": "HJPGEGENLPH", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "PEEFHKMOMKK", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + } + ], + "inheritedWireKeys": [ + "Name", + "Description", + "ImageName", + "WarningMask", + "CustomWarning", + "CreatorAccountId", + "State", + "Accessibility", + "SupportsLevelVoting", + "IsRRO", + "SupportsScreens", + "SupportsWalkVR", + "SupportsTeleportVR", + "SupportsVRLow", + "SupportsQuest2", + "SupportsMobile", + "SupportsJuniors", + "CreatedAt", + "Stats" + ] + } + } + ] + }, + { + "family": "rooms", + "route": "rooms/cheeredby/me", + "evidence": [ + { + "type": "EJDCNGBEICB", + "method": "RecRoom.Async.IPromise`1> GBMMJLHIDOK()", + "file": "EJDCNGBEICB.txt", + "line": 940, + "contract": { + "kind": "array", + "raw": "System.Collections.Generic.List`1", + "rootType": "KLCOGEIGEBJ", + "item": { + "kind": "object", + "raw": "KLCOGEIGEBJ", + "rootType": "KLCOGEIGEBJ" + }, + "typeDescription": { + "name": "KLCOGEIGEBJ", + "found": true, + "file": "KLCOGEIGEBJ.cs", + "declaration": "public class KLCOGEIGEBJ : MKAMHOIHOJK", + "kind": "class", + "baseType": "MKAMHOIHOJK", + "properties": [ + { + "name": "DADOKMAOFJL", + "type": "long", + "accessibility": "public", + "kind": "property" + }, + { + "name": "GJPGKJJCPBK", + "type": "bool", + "accessibility": "public", + "kind": "property" + }, + { + "name": "IBLOJJBEKFF", + "type": "bool", + "accessibility": "public", + "kind": "property" + }, + { + "name": "JCLCOCEOAEP", + "type": "bool", + "accessibility": "public", + "kind": "property" + }, + { + "name": "JGJCBGEBLLO", + "type": "bool", + "accessibility": "public", + "kind": "property" + }, + { + "name": "PLFEEIJCHOH", + "type": "bool", + "accessibility": "public", + "kind": "property" + } + ], + "fields": [ + { + "name": "BJFNPKFOALK", + "type": "long", + "kind": "field" + } + ], + "enumValues": [], + "wireKeys": [ + "RoomId", + "IsDorm", + "CloningAllowed", + "DisableMicAutoMute", + "DisableRoomComments", + "EncryptVoiceChat" + ], + "isilFile": "KLCOGEIGEBJ.txt", + "isilType": "KLCOGEIGEBJ", + "inherits": "MKAMHOIHOJK", + "inheritedMembers": [ + { + "name": "ACBFDMLHFPB", + "type": "DateTime", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "AHGCOGFEEEE", + "type": "string", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "AKFBNELAMNA", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "BADIGBCKECA", + "type": "int", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "BNBLOBAEDEE", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "CDINMMPNAID", + "type": "NMJEKMMBDDE", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "CDNFGMHLDMJ", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "FIKEBGGCDFN", + "type": "string", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "GIBHIMGJNNO", + "type": "GPDIAKNEBKH", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "HPLBOMGACED", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "IGOPGMHHLKI", + "type": "string", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "JFEAPMIPNEP", + "type": "DPLPMKMFMPB", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "KHIJAFCHLIA", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "KLNJBBPNMBJ", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "KODBEJPEFOJ", + "type": "string", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "LPJLEMJFBPE", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "MGBDHBHCDMH", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "MIKDLDEALPN", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "OFONEIOEIED", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "OILEJFNPDDB", + "type": "HJPGEGENLPH", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "PEEFHKMOMKK", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + } + ], + "inheritedWireKeys": [ + "Name", + "Description", + "ImageName", + "WarningMask", + "CustomWarning", + "CreatorAccountId", + "State", + "Accessibility", + "SupportsLevelVoting", + "IsRRO", + "SupportsScreens", + "SupportsWalkVR", + "SupportsTeleportVR", + "SupportsVRLow", + "SupportsQuest2", + "SupportsMobile", + "SupportsJuniors", + "CreatedAt", + "Stats" + ] + } + } + } + ], + "contracts": [ + { + "kind": "array", + "raw": "System.Collections.Generic.List`1", + "rootType": "KLCOGEIGEBJ", + "item": { + "kind": "object", + "raw": "KLCOGEIGEBJ", + "rootType": "KLCOGEIGEBJ" + }, + "typeDescription": { + "name": "KLCOGEIGEBJ", + "found": true, + "file": "KLCOGEIGEBJ.cs", + "declaration": "public class KLCOGEIGEBJ : MKAMHOIHOJK", + "kind": "class", + "baseType": "MKAMHOIHOJK", + "properties": [ + { + "name": "DADOKMAOFJL", + "type": "long", + "accessibility": "public", + "kind": "property" + }, + { + "name": "GJPGKJJCPBK", + "type": "bool", + "accessibility": "public", + "kind": "property" + }, + { + "name": "IBLOJJBEKFF", + "type": "bool", + "accessibility": "public", + "kind": "property" + }, + { + "name": "JCLCOCEOAEP", + "type": "bool", + "accessibility": "public", + "kind": "property" + }, + { + "name": "JGJCBGEBLLO", + "type": "bool", + "accessibility": "public", + "kind": "property" + }, + { + "name": "PLFEEIJCHOH", + "type": "bool", + "accessibility": "public", + "kind": "property" + } + ], + "fields": [ + { + "name": "BJFNPKFOALK", + "type": "long", + "kind": "field" + } + ], + "enumValues": [], + "wireKeys": [ + "RoomId", + "IsDorm", + "CloningAllowed", + "DisableMicAutoMute", + "DisableRoomComments", + "EncryptVoiceChat" + ], + "isilFile": "KLCOGEIGEBJ.txt", + "isilType": "KLCOGEIGEBJ", + "inherits": "MKAMHOIHOJK", + "inheritedMembers": [ + { + "name": "ACBFDMLHFPB", + "type": "DateTime", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "AHGCOGFEEEE", + "type": "string", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "AKFBNELAMNA", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "BADIGBCKECA", + "type": "int", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "BNBLOBAEDEE", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "CDINMMPNAID", + "type": "NMJEKMMBDDE", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "CDNFGMHLDMJ", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "FIKEBGGCDFN", + "type": "string", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "GIBHIMGJNNO", + "type": "GPDIAKNEBKH", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "HPLBOMGACED", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "IGOPGMHHLKI", + "type": "string", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "JFEAPMIPNEP", + "type": "DPLPMKMFMPB", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "KHIJAFCHLIA", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "KLNJBBPNMBJ", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "KODBEJPEFOJ", + "type": "string", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "LPJLEMJFBPE", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "MGBDHBHCDMH", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "MIKDLDEALPN", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "OFONEIOEIED", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "OILEJFNPDDB", + "type": "HJPGEGENLPH", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "PEEFHKMOMKK", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + } + ], + "inheritedWireKeys": [ + "Name", + "Description", + "ImageName", + "WarningMask", + "CustomWarning", + "CreatorAccountId", + "State", + "Accessibility", + "SupportsLevelVoting", + "IsRRO", + "SupportsScreens", + "SupportsWalkVR", + "SupportsTeleportVR", + "SupportsVRLow", + "SupportsQuest2", + "SupportsMobile", + "SupportsJuniors", + "CreatedAt", + "Stats" + ] + } + } + ] + }, + { + "family": "rooms", + "route": "rooms/createdby/{0}", + "evidence": [ + { + "type": "EJDCNGBEICB", + "method": "RecRoom.Async.IPromise`1> OGIHJJDNPKJ(System.Int32 GKLPIFBPGOD)", + "file": "EJDCNGBEICB.txt", + "line": 1073, + "contract": { + "kind": "array", + "raw": "System.Collections.Generic.List`1", + "rootType": "KLCOGEIGEBJ", + "item": { + "kind": "object", + "raw": "KLCOGEIGEBJ", + "rootType": "KLCOGEIGEBJ" + }, + "typeDescription": { + "name": "KLCOGEIGEBJ", + "found": true, + "file": "KLCOGEIGEBJ.cs", + "declaration": "public class KLCOGEIGEBJ : MKAMHOIHOJK", + "kind": "class", + "baseType": "MKAMHOIHOJK", + "properties": [ + { + "name": "DADOKMAOFJL", + "type": "long", + "accessibility": "public", + "kind": "property" + }, + { + "name": "GJPGKJJCPBK", + "type": "bool", + "accessibility": "public", + "kind": "property" + }, + { + "name": "IBLOJJBEKFF", + "type": "bool", + "accessibility": "public", + "kind": "property" + }, + { + "name": "JCLCOCEOAEP", + "type": "bool", + "accessibility": "public", + "kind": "property" + }, + { + "name": "JGJCBGEBLLO", + "type": "bool", + "accessibility": "public", + "kind": "property" + }, + { + "name": "PLFEEIJCHOH", + "type": "bool", + "accessibility": "public", + "kind": "property" + } + ], + "fields": [ + { + "name": "BJFNPKFOALK", + "type": "long", + "kind": "field" + } + ], + "enumValues": [], + "wireKeys": [ + "RoomId", + "IsDorm", + "CloningAllowed", + "DisableMicAutoMute", + "DisableRoomComments", + "EncryptVoiceChat" + ], + "isilFile": "KLCOGEIGEBJ.txt", + "isilType": "KLCOGEIGEBJ", + "inherits": "MKAMHOIHOJK", + "inheritedMembers": [ + { + "name": "ACBFDMLHFPB", + "type": "DateTime", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "AHGCOGFEEEE", + "type": "string", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "AKFBNELAMNA", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "BADIGBCKECA", + "type": "int", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "BNBLOBAEDEE", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "CDINMMPNAID", + "type": "NMJEKMMBDDE", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "CDNFGMHLDMJ", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "FIKEBGGCDFN", + "type": "string", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "GIBHIMGJNNO", + "type": "GPDIAKNEBKH", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "HPLBOMGACED", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "IGOPGMHHLKI", + "type": "string", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "JFEAPMIPNEP", + "type": "DPLPMKMFMPB", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "KHIJAFCHLIA", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "KLNJBBPNMBJ", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "KODBEJPEFOJ", + "type": "string", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "LPJLEMJFBPE", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "MGBDHBHCDMH", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "MIKDLDEALPN", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "OFONEIOEIED", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "OILEJFNPDDB", + "type": "HJPGEGENLPH", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "PEEFHKMOMKK", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + } + ], + "inheritedWireKeys": [ + "Name", + "Description", + "ImageName", + "WarningMask", + "CustomWarning", + "CreatorAccountId", + "State", + "Accessibility", + "SupportsLevelVoting", + "IsRRO", + "SupportsScreens", + "SupportsWalkVR", + "SupportsTeleportVR", + "SupportsVRLow", + "SupportsQuest2", + "SupportsMobile", + "SupportsJuniors", + "CreatedAt", + "Stats" + ] + } + } + } + ], + "contracts": [ + { + "kind": "array", + "raw": "System.Collections.Generic.List`1", + "rootType": "KLCOGEIGEBJ", + "item": { + "kind": "object", + "raw": "KLCOGEIGEBJ", + "rootType": "KLCOGEIGEBJ" + }, + "typeDescription": { + "name": "KLCOGEIGEBJ", + "found": true, + "file": "KLCOGEIGEBJ.cs", + "declaration": "public class KLCOGEIGEBJ : MKAMHOIHOJK", + "kind": "class", + "baseType": "MKAMHOIHOJK", + "properties": [ + { + "name": "DADOKMAOFJL", + "type": "long", + "accessibility": "public", + "kind": "property" + }, + { + "name": "GJPGKJJCPBK", + "type": "bool", + "accessibility": "public", + "kind": "property" + }, + { + "name": "IBLOJJBEKFF", + "type": "bool", + "accessibility": "public", + "kind": "property" + }, + { + "name": "JCLCOCEOAEP", + "type": "bool", + "accessibility": "public", + "kind": "property" + }, + { + "name": "JGJCBGEBLLO", + "type": "bool", + "accessibility": "public", + "kind": "property" + }, + { + "name": "PLFEEIJCHOH", + "type": "bool", + "accessibility": "public", + "kind": "property" + } + ], + "fields": [ + { + "name": "BJFNPKFOALK", + "type": "long", + "kind": "field" + } + ], + "enumValues": [], + "wireKeys": [ + "RoomId", + "IsDorm", + "CloningAllowed", + "DisableMicAutoMute", + "DisableRoomComments", + "EncryptVoiceChat" + ], + "isilFile": "KLCOGEIGEBJ.txt", + "isilType": "KLCOGEIGEBJ", + "inherits": "MKAMHOIHOJK", + "inheritedMembers": [ + { + "name": "ACBFDMLHFPB", + "type": "DateTime", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "AHGCOGFEEEE", + "type": "string", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "AKFBNELAMNA", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "BADIGBCKECA", + "type": "int", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "BNBLOBAEDEE", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "CDINMMPNAID", + "type": "NMJEKMMBDDE", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "CDNFGMHLDMJ", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "FIKEBGGCDFN", + "type": "string", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "GIBHIMGJNNO", + "type": "GPDIAKNEBKH", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "HPLBOMGACED", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "IGOPGMHHLKI", + "type": "string", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "JFEAPMIPNEP", + "type": "DPLPMKMFMPB", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "KHIJAFCHLIA", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "KLNJBBPNMBJ", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "KODBEJPEFOJ", + "type": "string", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "LPJLEMJFBPE", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "MGBDHBHCDMH", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "MIKDLDEALPN", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "OFONEIOEIED", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "OILEJFNPDDB", + "type": "HJPGEGENLPH", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "PEEFHKMOMKK", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + } + ], + "inheritedWireKeys": [ + "Name", + "Description", + "ImageName", + "WarningMask", + "CustomWarning", + "CreatorAccountId", + "State", + "Accessibility", + "SupportsLevelVoting", + "IsRRO", + "SupportsScreens", + "SupportsWalkVR", + "SupportsTeleportVR", + "SupportsVRLow", + "SupportsQuest2", + "SupportsMobile", + "SupportsJuniors", + "CreatedAt", + "Stats" + ] + } + } + ] + }, + { + "family": "rooms", + "route": "rooms/createdby/me", + "evidence": [ + { + "type": "EJDCNGBEICB", + "method": "RecRoom.Async.IPromise`1> MIMNJGKEGMF()", + "file": "EJDCNGBEICB.txt", + "line": 823, + "contract": { + "kind": "array", + "raw": "System.Collections.Generic.List`1", + "rootType": "KLCOGEIGEBJ", + "item": { + "kind": "object", + "raw": "KLCOGEIGEBJ", + "rootType": "KLCOGEIGEBJ" + }, + "typeDescription": { + "name": "KLCOGEIGEBJ", + "found": true, + "file": "KLCOGEIGEBJ.cs", + "declaration": "public class KLCOGEIGEBJ : MKAMHOIHOJK", + "kind": "class", + "baseType": "MKAMHOIHOJK", + "properties": [ + { + "name": "DADOKMAOFJL", + "type": "long", + "accessibility": "public", + "kind": "property" + }, + { + "name": "GJPGKJJCPBK", + "type": "bool", + "accessibility": "public", + "kind": "property" + }, + { + "name": "IBLOJJBEKFF", + "type": "bool", + "accessibility": "public", + "kind": "property" + }, + { + "name": "JCLCOCEOAEP", + "type": "bool", + "accessibility": "public", + "kind": "property" + }, + { + "name": "JGJCBGEBLLO", + "type": "bool", + "accessibility": "public", + "kind": "property" + }, + { + "name": "PLFEEIJCHOH", + "type": "bool", + "accessibility": "public", + "kind": "property" + } + ], + "fields": [ + { + "name": "BJFNPKFOALK", + "type": "long", + "kind": "field" + } + ], + "enumValues": [], + "wireKeys": [ + "RoomId", + "IsDorm", + "CloningAllowed", + "DisableMicAutoMute", + "DisableRoomComments", + "EncryptVoiceChat" + ], + "isilFile": "KLCOGEIGEBJ.txt", + "isilType": "KLCOGEIGEBJ", + "inherits": "MKAMHOIHOJK", + "inheritedMembers": [ + { + "name": "ACBFDMLHFPB", + "type": "DateTime", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "AHGCOGFEEEE", + "type": "string", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "AKFBNELAMNA", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "BADIGBCKECA", + "type": "int", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "BNBLOBAEDEE", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "CDINMMPNAID", + "type": "NMJEKMMBDDE", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "CDNFGMHLDMJ", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "FIKEBGGCDFN", + "type": "string", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "GIBHIMGJNNO", + "type": "GPDIAKNEBKH", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "HPLBOMGACED", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "IGOPGMHHLKI", + "type": "string", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "JFEAPMIPNEP", + "type": "DPLPMKMFMPB", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "KHIJAFCHLIA", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "KLNJBBPNMBJ", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "KODBEJPEFOJ", + "type": "string", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "LPJLEMJFBPE", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "MGBDHBHCDMH", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "MIKDLDEALPN", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "OFONEIOEIED", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "OILEJFNPDDB", + "type": "HJPGEGENLPH", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "PEEFHKMOMKK", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + } + ], + "inheritedWireKeys": [ + "Name", + "Description", + "ImageName", + "WarningMask", + "CustomWarning", + "CreatorAccountId", + "State", + "Accessibility", + "SupportsLevelVoting", + "IsRRO", + "SupportsScreens", + "SupportsWalkVR", + "SupportsTeleportVR", + "SupportsVRLow", + "SupportsQuest2", + "SupportsMobile", + "SupportsJuniors", + "CreatedAt", + "Stats" + ] + } + } + } + ], + "contracts": [ + { + "kind": "array", + "raw": "System.Collections.Generic.List`1", + "rootType": "KLCOGEIGEBJ", + "item": { + "kind": "object", + "raw": "KLCOGEIGEBJ", + "rootType": "KLCOGEIGEBJ" + }, + "typeDescription": { + "name": "KLCOGEIGEBJ", + "found": true, + "file": "KLCOGEIGEBJ.cs", + "declaration": "public class KLCOGEIGEBJ : MKAMHOIHOJK", + "kind": "class", + "baseType": "MKAMHOIHOJK", + "properties": [ + { + "name": "DADOKMAOFJL", + "type": "long", + "accessibility": "public", + "kind": "property" + }, + { + "name": "GJPGKJJCPBK", + "type": "bool", + "accessibility": "public", + "kind": "property" + }, + { + "name": "IBLOJJBEKFF", + "type": "bool", + "accessibility": "public", + "kind": "property" + }, + { + "name": "JCLCOCEOAEP", + "type": "bool", + "accessibility": "public", + "kind": "property" + }, + { + "name": "JGJCBGEBLLO", + "type": "bool", + "accessibility": "public", + "kind": "property" + }, + { + "name": "PLFEEIJCHOH", + "type": "bool", + "accessibility": "public", + "kind": "property" + } + ], + "fields": [ + { + "name": "BJFNPKFOALK", + "type": "long", + "kind": "field" + } + ], + "enumValues": [], + "wireKeys": [ + "RoomId", + "IsDorm", + "CloningAllowed", + "DisableMicAutoMute", + "DisableRoomComments", + "EncryptVoiceChat" + ], + "isilFile": "KLCOGEIGEBJ.txt", + "isilType": "KLCOGEIGEBJ", + "inherits": "MKAMHOIHOJK", + "inheritedMembers": [ + { + "name": "ACBFDMLHFPB", + "type": "DateTime", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "AHGCOGFEEEE", + "type": "string", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "AKFBNELAMNA", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "BADIGBCKECA", + "type": "int", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "BNBLOBAEDEE", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "CDINMMPNAID", + "type": "NMJEKMMBDDE", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "CDNFGMHLDMJ", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "FIKEBGGCDFN", + "type": "string", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "GIBHIMGJNNO", + "type": "GPDIAKNEBKH", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "HPLBOMGACED", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "IGOPGMHHLKI", + "type": "string", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "JFEAPMIPNEP", + "type": "DPLPMKMFMPB", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "KHIJAFCHLIA", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "KLNJBBPNMBJ", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "KODBEJPEFOJ", + "type": "string", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "LPJLEMJFBPE", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "MGBDHBHCDMH", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "MIKDLDEALPN", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "OFONEIOEIED", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "OILEJFNPDDB", + "type": "HJPGEGENLPH", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "PEEFHKMOMKK", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + } + ], + "inheritedWireKeys": [ + "Name", + "Description", + "ImageName", + "WarningMask", + "CustomWarning", + "CreatorAccountId", + "State", + "Accessibility", + "SupportsLevelVoting", + "IsRRO", + "SupportsScreens", + "SupportsWalkVR", + "SupportsTeleportVR", + "SupportsVRLow", + "SupportsQuest2", + "SupportsMobile", + "SupportsJuniors", + "CreatedAt", + "Stats" + ] + } + } + ] + }, + { + "family": "rooms", + "route": "rooms/favoritedby/me", + "evidence": [ + { + "type": "EJDCNGBEICB", + "method": "RecRoom.Async.IPromise`1> AFOLHKGPMOC()", + "file": "EJDCNGBEICB.txt", + "line": 979, + "contract": { + "kind": "array", + "raw": "System.Collections.Generic.List`1", + "rootType": "KLCOGEIGEBJ", + "item": { + "kind": "object", + "raw": "KLCOGEIGEBJ", + "rootType": "KLCOGEIGEBJ" + }, + "typeDescription": { + "name": "KLCOGEIGEBJ", + "found": true, + "file": "KLCOGEIGEBJ.cs", + "declaration": "public class KLCOGEIGEBJ : MKAMHOIHOJK", + "kind": "class", + "baseType": "MKAMHOIHOJK", + "properties": [ + { + "name": "DADOKMAOFJL", + "type": "long", + "accessibility": "public", + "kind": "property" + }, + { + "name": "GJPGKJJCPBK", + "type": "bool", + "accessibility": "public", + "kind": "property" + }, + { + "name": "IBLOJJBEKFF", + "type": "bool", + "accessibility": "public", + "kind": "property" + }, + { + "name": "JCLCOCEOAEP", + "type": "bool", + "accessibility": "public", + "kind": "property" + }, + { + "name": "JGJCBGEBLLO", + "type": "bool", + "accessibility": "public", + "kind": "property" + }, + { + "name": "PLFEEIJCHOH", + "type": "bool", + "accessibility": "public", + "kind": "property" + } + ], + "fields": [ + { + "name": "BJFNPKFOALK", + "type": "long", + "kind": "field" + } + ], + "enumValues": [], + "wireKeys": [ + "RoomId", + "IsDorm", + "CloningAllowed", + "DisableMicAutoMute", + "DisableRoomComments", + "EncryptVoiceChat" + ], + "isilFile": "KLCOGEIGEBJ.txt", + "isilType": "KLCOGEIGEBJ", + "inherits": "MKAMHOIHOJK", + "inheritedMembers": [ + { + "name": "ACBFDMLHFPB", + "type": "DateTime", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "AHGCOGFEEEE", + "type": "string", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "AKFBNELAMNA", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "BADIGBCKECA", + "type": "int", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "BNBLOBAEDEE", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "CDINMMPNAID", + "type": "NMJEKMMBDDE", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "CDNFGMHLDMJ", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "FIKEBGGCDFN", + "type": "string", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "GIBHIMGJNNO", + "type": "GPDIAKNEBKH", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "HPLBOMGACED", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "IGOPGMHHLKI", + "type": "string", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "JFEAPMIPNEP", + "type": "DPLPMKMFMPB", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "KHIJAFCHLIA", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "KLNJBBPNMBJ", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "KODBEJPEFOJ", + "type": "string", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "LPJLEMJFBPE", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "MGBDHBHCDMH", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "MIKDLDEALPN", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "OFONEIOEIED", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "OILEJFNPDDB", + "type": "HJPGEGENLPH", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "PEEFHKMOMKK", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + } + ], + "inheritedWireKeys": [ + "Name", + "Description", + "ImageName", + "WarningMask", + "CustomWarning", + "CreatorAccountId", + "State", + "Accessibility", + "SupportsLevelVoting", + "IsRRO", + "SupportsScreens", + "SupportsWalkVR", + "SupportsTeleportVR", + "SupportsVRLow", + "SupportsQuest2", + "SupportsMobile", + "SupportsJuniors", + "CreatedAt", + "Stats" + ] + } + } + } + ], + "contracts": [ + { + "kind": "array", + "raw": "System.Collections.Generic.List`1", + "rootType": "KLCOGEIGEBJ", + "item": { + "kind": "object", + "raw": "KLCOGEIGEBJ", + "rootType": "KLCOGEIGEBJ" + }, + "typeDescription": { + "name": "KLCOGEIGEBJ", + "found": true, + "file": "KLCOGEIGEBJ.cs", + "declaration": "public class KLCOGEIGEBJ : MKAMHOIHOJK", + "kind": "class", + "baseType": "MKAMHOIHOJK", + "properties": [ + { + "name": "DADOKMAOFJL", + "type": "long", + "accessibility": "public", + "kind": "property" + }, + { + "name": "GJPGKJJCPBK", + "type": "bool", + "accessibility": "public", + "kind": "property" + }, + { + "name": "IBLOJJBEKFF", + "type": "bool", + "accessibility": "public", + "kind": "property" + }, + { + "name": "JCLCOCEOAEP", + "type": "bool", + "accessibility": "public", + "kind": "property" + }, + { + "name": "JGJCBGEBLLO", + "type": "bool", + "accessibility": "public", + "kind": "property" + }, + { + "name": "PLFEEIJCHOH", + "type": "bool", + "accessibility": "public", + "kind": "property" + } + ], + "fields": [ + { + "name": "BJFNPKFOALK", + "type": "long", + "kind": "field" + } + ], + "enumValues": [], + "wireKeys": [ + "RoomId", + "IsDorm", + "CloningAllowed", + "DisableMicAutoMute", + "DisableRoomComments", + "EncryptVoiceChat" + ], + "isilFile": "KLCOGEIGEBJ.txt", + "isilType": "KLCOGEIGEBJ", + "inherits": "MKAMHOIHOJK", + "inheritedMembers": [ + { + "name": "ACBFDMLHFPB", + "type": "DateTime", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "AHGCOGFEEEE", + "type": "string", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "AKFBNELAMNA", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "BADIGBCKECA", + "type": "int", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "BNBLOBAEDEE", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "CDINMMPNAID", + "type": "NMJEKMMBDDE", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "CDNFGMHLDMJ", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "FIKEBGGCDFN", + "type": "string", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "GIBHIMGJNNO", + "type": "GPDIAKNEBKH", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "HPLBOMGACED", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "IGOPGMHHLKI", + "type": "string", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "JFEAPMIPNEP", + "type": "DPLPMKMFMPB", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "KHIJAFCHLIA", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "KLNJBBPNMBJ", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "KODBEJPEFOJ", + "type": "string", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "LPJLEMJFBPE", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "MGBDHBHCDMH", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "MIKDLDEALPN", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "OFONEIOEIED", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "OILEJFNPDDB", + "type": "HJPGEGENLPH", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "PEEFHKMOMKK", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + } + ], + "inheritedWireKeys": [ + "Name", + "Description", + "ImageName", + "WarningMask", + "CustomWarning", + "CreatorAccountId", + "State", + "Accessibility", + "SupportsLevelVoting", + "IsRRO", + "SupportsScreens", + "SupportsWalkVR", + "SupportsTeleportVR", + "SupportsVRLow", + "SupportsQuest2", + "SupportsMobile", + "SupportsJuniors", + "CreatedAt", + "Stats" + ] + } + } + ] + }, + { + "family": "rooms", + "route": "rooms/hot", + "evidence": [ + { + "type": "EJDCNGBEICB", + "method": "RecRoom.Async.IPromise`1 DLPLPKCNLNA(System.Collections.Generic.IReadOnlyList`1 CAFPJPHILMN)", + "file": "EJDCNGBEICB.txt", + "line": 1601, + "contract": { + "kind": "object", + "raw": "IDLBPALJJDJ", + "rootType": "IDLBPALJJDJ", + "typeDescription": { + "name": "IDLBPALJJDJ", + "found": true, + "file": "IDLBPALJJDJ.cs", + "declaration": "public class IDLBPALJJDJ : NCONANPODKN, IFAIJAGLDFK", + "kind": "class", + "baseType": "NCONANPODKN", + "properties": [], + "fields": [], + "enumValues": [], + "wireKeys": [ + "TotalResults" + ], + "isilFile": "IDLBPALJJDJ.txt", + "isilType": "IDLBPALJJDJ", + "inherits": "NCONANPODKN", + "inheritedMembers": [ + { + "name": "KPCBEDOLLFK", + "type": "long", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "NCONANPODKN" + }, + { + "name": "PPLMJPLEHLP", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "NCONANPODKN" + } + ], + "inheritedWireKeys": [] + } + } + } + ], + "contracts": [ + { + "kind": "object", + "raw": "IDLBPALJJDJ", + "rootType": "IDLBPALJJDJ", + "typeDescription": { + "name": "IDLBPALJJDJ", + "found": true, + "file": "IDLBPALJJDJ.cs", + "declaration": "public class IDLBPALJJDJ : NCONANPODKN, IFAIJAGLDFK", + "kind": "class", + "baseType": "NCONANPODKN", + "properties": [], + "fields": [], + "enumValues": [], + "wireKeys": [ + "TotalResults" + ], + "isilFile": "IDLBPALJJDJ.txt", + "isilType": "IDLBPALJJDJ", + "inherits": "NCONANPODKN", + "inheritedMembers": [ + { + "name": "KPCBEDOLLFK", + "type": "long", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "NCONANPODKN" + }, + { + "name": "PPLMJPLEHLP", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "NCONANPODKN" + } + ], + "inheritedWireKeys": [] + } + } + ] + }, + { + "family": "rooms", + "route": "rooms/moderatedby/me", + "evidence": [ + { + "type": "EJDCNGBEICB", + "method": "RecRoom.Async.IPromise`1> IMKAAIEFGIG()", + "file": "EJDCNGBEICB.txt", + "line": 901, + "contract": { + "kind": "array", + "raw": "System.Collections.Generic.List`1", + "rootType": "KLCOGEIGEBJ", + "item": { + "kind": "object", + "raw": "KLCOGEIGEBJ", + "rootType": "KLCOGEIGEBJ" + }, + "typeDescription": { + "name": "KLCOGEIGEBJ", + "found": true, + "file": "KLCOGEIGEBJ.cs", + "declaration": "public class KLCOGEIGEBJ : MKAMHOIHOJK", + "kind": "class", + "baseType": "MKAMHOIHOJK", + "properties": [ + { + "name": "DADOKMAOFJL", + "type": "long", + "accessibility": "public", + "kind": "property" + }, + { + "name": "GJPGKJJCPBK", + "type": "bool", + "accessibility": "public", + "kind": "property" + }, + { + "name": "IBLOJJBEKFF", + "type": "bool", + "accessibility": "public", + "kind": "property" + }, + { + "name": "JCLCOCEOAEP", + "type": "bool", + "accessibility": "public", + "kind": "property" + }, + { + "name": "JGJCBGEBLLO", + "type": "bool", + "accessibility": "public", + "kind": "property" + }, + { + "name": "PLFEEIJCHOH", + "type": "bool", + "accessibility": "public", + "kind": "property" + } + ], + "fields": [ + { + "name": "BJFNPKFOALK", + "type": "long", + "kind": "field" + } + ], + "enumValues": [], + "wireKeys": [ + "RoomId", + "IsDorm", + "CloningAllowed", + "DisableMicAutoMute", + "DisableRoomComments", + "EncryptVoiceChat" + ], + "isilFile": "KLCOGEIGEBJ.txt", + "isilType": "KLCOGEIGEBJ", + "inherits": "MKAMHOIHOJK", + "inheritedMembers": [ + { + "name": "ACBFDMLHFPB", + "type": "DateTime", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "AHGCOGFEEEE", + "type": "string", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "AKFBNELAMNA", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "BADIGBCKECA", + "type": "int", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "BNBLOBAEDEE", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "CDINMMPNAID", + "type": "NMJEKMMBDDE", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "CDNFGMHLDMJ", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "FIKEBGGCDFN", + "type": "string", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "GIBHIMGJNNO", + "type": "GPDIAKNEBKH", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "HPLBOMGACED", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "IGOPGMHHLKI", + "type": "string", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "JFEAPMIPNEP", + "type": "DPLPMKMFMPB", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "KHIJAFCHLIA", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "KLNJBBPNMBJ", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "KODBEJPEFOJ", + "type": "string", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "LPJLEMJFBPE", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "MGBDHBHCDMH", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "MIKDLDEALPN", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "OFONEIOEIED", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "OILEJFNPDDB", + "type": "HJPGEGENLPH", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "PEEFHKMOMKK", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + } + ], + "inheritedWireKeys": [ + "Name", + "Description", + "ImageName", + "WarningMask", + "CustomWarning", + "CreatorAccountId", + "State", + "Accessibility", + "SupportsLevelVoting", + "IsRRO", + "SupportsScreens", + "SupportsWalkVR", + "SupportsTeleportVR", + "SupportsVRLow", + "SupportsQuest2", + "SupportsMobile", + "SupportsJuniors", + "CreatedAt", + "Stats" + ] + } + } + } + ], + "contracts": [ + { + "kind": "array", + "raw": "System.Collections.Generic.List`1", + "rootType": "KLCOGEIGEBJ", + "item": { + "kind": "object", + "raw": "KLCOGEIGEBJ", + "rootType": "KLCOGEIGEBJ" + }, + "typeDescription": { + "name": "KLCOGEIGEBJ", + "found": true, + "file": "KLCOGEIGEBJ.cs", + "declaration": "public class KLCOGEIGEBJ : MKAMHOIHOJK", + "kind": "class", + "baseType": "MKAMHOIHOJK", + "properties": [ + { + "name": "DADOKMAOFJL", + "type": "long", + "accessibility": "public", + "kind": "property" + }, + { + "name": "GJPGKJJCPBK", + "type": "bool", + "accessibility": "public", + "kind": "property" + }, + { + "name": "IBLOJJBEKFF", + "type": "bool", + "accessibility": "public", + "kind": "property" + }, + { + "name": "JCLCOCEOAEP", + "type": "bool", + "accessibility": "public", + "kind": "property" + }, + { + "name": "JGJCBGEBLLO", + "type": "bool", + "accessibility": "public", + "kind": "property" + }, + { + "name": "PLFEEIJCHOH", + "type": "bool", + "accessibility": "public", + "kind": "property" + } + ], + "fields": [ + { + "name": "BJFNPKFOALK", + "type": "long", + "kind": "field" + } + ], + "enumValues": [], + "wireKeys": [ + "RoomId", + "IsDorm", + "CloningAllowed", + "DisableMicAutoMute", + "DisableRoomComments", + "EncryptVoiceChat" + ], + "isilFile": "KLCOGEIGEBJ.txt", + "isilType": "KLCOGEIGEBJ", + "inherits": "MKAMHOIHOJK", + "inheritedMembers": [ + { + "name": "ACBFDMLHFPB", + "type": "DateTime", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "AHGCOGFEEEE", + "type": "string", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "AKFBNELAMNA", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "BADIGBCKECA", + "type": "int", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "BNBLOBAEDEE", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "CDINMMPNAID", + "type": "NMJEKMMBDDE", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "CDNFGMHLDMJ", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "FIKEBGGCDFN", + "type": "string", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "GIBHIMGJNNO", + "type": "GPDIAKNEBKH", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "HPLBOMGACED", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "IGOPGMHHLKI", + "type": "string", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "JFEAPMIPNEP", + "type": "DPLPMKMFMPB", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "KHIJAFCHLIA", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "KLNJBBPNMBJ", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "KODBEJPEFOJ", + "type": "string", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "LPJLEMJFBPE", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "MGBDHBHCDMH", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "MIKDLDEALPN", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "OFONEIOEIED", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "OILEJFNPDDB", + "type": "HJPGEGENLPH", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "PEEFHKMOMKK", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + } + ], + "inheritedWireKeys": [ + "Name", + "Description", + "ImageName", + "WarningMask", + "CustomWarning", + "CreatorAccountId", + "State", + "Accessibility", + "SupportsLevelVoting", + "IsRRO", + "SupportsScreens", + "SupportsWalkVR", + "SupportsTeleportVR", + "SupportsVRLow", + "SupportsQuest2", + "SupportsMobile", + "SupportsJuniors", + "CreatedAt", + "Stats" + ] + } + } + ] + }, + { + "family": "rooms", + "route": "rooms/ownedby/me", + "evidence": [ + { + "type": "EJDCNGBEICB", + "method": "RecRoom.Async.IPromise`1> FNMNFLFIBJG()", + "file": "EJDCNGBEICB.txt", + "line": 862, + "contract": { + "kind": "array", + "raw": "System.Collections.Generic.List`1", + "rootType": "KLCOGEIGEBJ", + "item": { + "kind": "object", + "raw": "KLCOGEIGEBJ", + "rootType": "KLCOGEIGEBJ" + }, + "typeDescription": { + "name": "KLCOGEIGEBJ", + "found": true, + "file": "KLCOGEIGEBJ.cs", + "declaration": "public class KLCOGEIGEBJ : MKAMHOIHOJK", + "kind": "class", + "baseType": "MKAMHOIHOJK", + "properties": [ + { + "name": "DADOKMAOFJL", + "type": "long", + "accessibility": "public", + "kind": "property" + }, + { + "name": "GJPGKJJCPBK", + "type": "bool", + "accessibility": "public", + "kind": "property" + }, + { + "name": "IBLOJJBEKFF", + "type": "bool", + "accessibility": "public", + "kind": "property" + }, + { + "name": "JCLCOCEOAEP", + "type": "bool", + "accessibility": "public", + "kind": "property" + }, + { + "name": "JGJCBGEBLLO", + "type": "bool", + "accessibility": "public", + "kind": "property" + }, + { + "name": "PLFEEIJCHOH", + "type": "bool", + "accessibility": "public", + "kind": "property" + } + ], + "fields": [ + { + "name": "BJFNPKFOALK", + "type": "long", + "kind": "field" + } + ], + "enumValues": [], + "wireKeys": [ + "RoomId", + "IsDorm", + "CloningAllowed", + "DisableMicAutoMute", + "DisableRoomComments", + "EncryptVoiceChat" + ], + "isilFile": "KLCOGEIGEBJ.txt", + "isilType": "KLCOGEIGEBJ", + "inherits": "MKAMHOIHOJK", + "inheritedMembers": [ + { + "name": "ACBFDMLHFPB", + "type": "DateTime", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "AHGCOGFEEEE", + "type": "string", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "AKFBNELAMNA", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "BADIGBCKECA", + "type": "int", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "BNBLOBAEDEE", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "CDINMMPNAID", + "type": "NMJEKMMBDDE", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "CDNFGMHLDMJ", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "FIKEBGGCDFN", + "type": "string", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "GIBHIMGJNNO", + "type": "GPDIAKNEBKH", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "HPLBOMGACED", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "IGOPGMHHLKI", + "type": "string", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "JFEAPMIPNEP", + "type": "DPLPMKMFMPB", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "KHIJAFCHLIA", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "KLNJBBPNMBJ", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "KODBEJPEFOJ", + "type": "string", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "LPJLEMJFBPE", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "MGBDHBHCDMH", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "MIKDLDEALPN", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "OFONEIOEIED", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "OILEJFNPDDB", + "type": "HJPGEGENLPH", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "PEEFHKMOMKK", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + } + ], + "inheritedWireKeys": [ + "Name", + "Description", + "ImageName", + "WarningMask", + "CustomWarning", + "CreatorAccountId", + "State", + "Accessibility", + "SupportsLevelVoting", + "IsRRO", + "SupportsScreens", + "SupportsWalkVR", + "SupportsTeleportVR", + "SupportsVRLow", + "SupportsQuest2", + "SupportsMobile", + "SupportsJuniors", + "CreatedAt", + "Stats" + ] + } + } + } + ], + "contracts": [ + { + "kind": "array", + "raw": "System.Collections.Generic.List`1", + "rootType": "KLCOGEIGEBJ", + "item": { + "kind": "object", + "raw": "KLCOGEIGEBJ", + "rootType": "KLCOGEIGEBJ" + }, + "typeDescription": { + "name": "KLCOGEIGEBJ", + "found": true, + "file": "KLCOGEIGEBJ.cs", + "declaration": "public class KLCOGEIGEBJ : MKAMHOIHOJK", + "kind": "class", + "baseType": "MKAMHOIHOJK", + "properties": [ + { + "name": "DADOKMAOFJL", + "type": "long", + "accessibility": "public", + "kind": "property" + }, + { + "name": "GJPGKJJCPBK", + "type": "bool", + "accessibility": "public", + "kind": "property" + }, + { + "name": "IBLOJJBEKFF", + "type": "bool", + "accessibility": "public", + "kind": "property" + }, + { + "name": "JCLCOCEOAEP", + "type": "bool", + "accessibility": "public", + "kind": "property" + }, + { + "name": "JGJCBGEBLLO", + "type": "bool", + "accessibility": "public", + "kind": "property" + }, + { + "name": "PLFEEIJCHOH", + "type": "bool", + "accessibility": "public", + "kind": "property" + } + ], + "fields": [ + { + "name": "BJFNPKFOALK", + "type": "long", + "kind": "field" + } + ], + "enumValues": [], + "wireKeys": [ + "RoomId", + "IsDorm", + "CloningAllowed", + "DisableMicAutoMute", + "DisableRoomComments", + "EncryptVoiceChat" + ], + "isilFile": "KLCOGEIGEBJ.txt", + "isilType": "KLCOGEIGEBJ", + "inherits": "MKAMHOIHOJK", + "inheritedMembers": [ + { + "name": "ACBFDMLHFPB", + "type": "DateTime", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "AHGCOGFEEEE", + "type": "string", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "AKFBNELAMNA", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "BADIGBCKECA", + "type": "int", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "BNBLOBAEDEE", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "CDINMMPNAID", + "type": "NMJEKMMBDDE", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "CDNFGMHLDMJ", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "FIKEBGGCDFN", + "type": "string", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "GIBHIMGJNNO", + "type": "GPDIAKNEBKH", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "HPLBOMGACED", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "IGOPGMHHLKI", + "type": "string", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "JFEAPMIPNEP", + "type": "DPLPMKMFMPB", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "KHIJAFCHLIA", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "KLNJBBPNMBJ", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "KODBEJPEFOJ", + "type": "string", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "LPJLEMJFBPE", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "MGBDHBHCDMH", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "MIKDLDEALPN", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "OFONEIOEIED", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "OILEJFNPDDB", + "type": "HJPGEGENLPH", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "PEEFHKMOMKK", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + } + ], + "inheritedWireKeys": [ + "Name", + "Description", + "ImageName", + "WarningMask", + "CustomWarning", + "CreatorAccountId", + "State", + "Accessibility", + "SupportsLevelVoting", + "IsRRO", + "SupportsScreens", + "SupportsWalkVR", + "SupportsTeleportVR", + "SupportsVRLow", + "SupportsQuest2", + "SupportsMobile", + "SupportsJuniors", + "CreatedAt", + "Stats" + ] + } + } + ] + }, + { + "family": "rooms", + "route": "rooms/recommendations", + "evidence": [ + { + "type": "EJDCNGBEICB", + "method": "RecRoom.Async.IPromise`1> JDKLBNBIEAH(AMBEPDPIBPA NLNJMJGOKFI, System.Int16 NJJLKKLALND)", + "file": "EJDCNGBEICB.txt", + "line": 1430, + "contract": { + "kind": "array", + "raw": "System.Collections.Generic.List`1", + "rootType": "ANNKHNFLMNP", + "item": { + "kind": "object", + "raw": "ANNKHNFLMNP", + "rootType": "ANNKHNFLMNP" + }, + "typeDescription": { + "name": "ANNKHNFLMNP", + "found": true, + "file": "ANNKHNFLMNP.cs", + "declaration": "public class ANNKHNFLMNP : IFAIJAGLDFK", + "kind": "class", + "baseType": "IFAIJAGLDFK", + "properties": [ + { + "name": "CGKFPHCGBKL", + "type": "KLCOGEIGEBJ", + "accessibility": "public", + "kind": "property" + }, + { + "name": "HEFJFKFNNCJ", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + } + ], + "fields": [], + "enumValues": [], + "wireKeys": [ + "SeedRoom" + ], + "isilFile": "ANNKHNFLMNP.txt", + "isilType": "ANNKHNFLMNP" + } + } + } + ], + "contracts": [ + { + "kind": "array", + "raw": "System.Collections.Generic.List`1", + "rootType": "ANNKHNFLMNP", + "item": { + "kind": "object", + "raw": "ANNKHNFLMNP", + "rootType": "ANNKHNFLMNP" + }, + "typeDescription": { + "name": "ANNKHNFLMNP", + "found": true, + "file": "ANNKHNFLMNP.cs", + "declaration": "public class ANNKHNFLMNP : IFAIJAGLDFK", + "kind": "class", + "baseType": "IFAIJAGLDFK", + "properties": [ + { + "name": "CGKFPHCGBKL", + "type": "KLCOGEIGEBJ", + "accessibility": "public", + "kind": "property" + }, + { + "name": "HEFJFKFNNCJ", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property" + } + ], + "fields": [], + "enumValues": [], + "wireKeys": [ + "SeedRoom" + ], + "isilFile": "ANNKHNFLMNP.txt", + "isilType": "ANNKHNFLMNP" + } + } + ] + }, + { + "family": "rooms", + "route": "rooms/rro_ids", + "evidence": [ + { + "type": "EJDCNGBEICB", + "method": "RecRoom.Async.IPromise`1> JDMBPCJDEMF()", + "file": "EJDCNGBEICB.txt", + "line": 1119, + "contract": { + "kind": "array", + "raw": "System.Collections.Generic.List`1", + "rootType": "Int64", + "item": { + "kind": "primitive", + "raw": "System.Int64", + "rootType": "number" + }, + "typeDescription": { + "name": "Int64", + "found": false, + "kind": "primitive", + "wireKeys": [] + } + } + } + ], + "contracts": [ + { + "kind": "array", + "raw": "System.Collections.Generic.List`1", + "rootType": "Int64", + "item": { + "kind": "primitive", + "raw": "System.Int64", + "rootType": "number" + }, + "typeDescription": { + "name": "Int64", + "found": false, + "kind": "primitive", + "wireKeys": [] + } + } + ] + }, + { + "family": "rooms", + "route": "rooms/search", + "evidence": [ + { + "type": "EJDCNGBEICB", + "method": "RecRoom.Async.IPromise`1 IAAKGMOGLFN(System.String CNBKKCJAHPP)", + "file": "EJDCNGBEICB.txt", + "line": 1516, + "contract": { + "kind": "object", + "raw": "IDLBPALJJDJ", + "rootType": "IDLBPALJJDJ", + "typeDescription": { + "name": "IDLBPALJJDJ", + "found": true, + "file": "IDLBPALJJDJ.cs", + "declaration": "public class IDLBPALJJDJ : NCONANPODKN, IFAIJAGLDFK", + "kind": "class", + "baseType": "NCONANPODKN", + "properties": [], + "fields": [], + "enumValues": [], + "wireKeys": [ + "TotalResults" + ], + "isilFile": "IDLBPALJJDJ.txt", + "isilType": "IDLBPALJJDJ", + "inherits": "NCONANPODKN", + "inheritedMembers": [ + { + "name": "KPCBEDOLLFK", + "type": "long", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "NCONANPODKN" + }, + { + "name": "PPLMJPLEHLP", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "NCONANPODKN" + } + ], + "inheritedWireKeys": [] + } + } + } + ], + "contracts": [ + { + "kind": "object", + "raw": "IDLBPALJJDJ", + "rootType": "IDLBPALJJDJ", + "typeDescription": { + "name": "IDLBPALJJDJ", + "found": true, + "file": "IDLBPALJJDJ.cs", + "declaration": "public class IDLBPALJJDJ : NCONANPODKN, IFAIJAGLDFK", + "kind": "class", + "baseType": "NCONANPODKN", + "properties": [], + "fields": [], + "enumValues": [], + "wireKeys": [ + "TotalResults" + ], + "isilFile": "IDLBPALJJDJ.txt", + "isilType": "IDLBPALJJDJ", + "inherits": "NCONANPODKN", + "inheritedMembers": [ + { + "name": "KPCBEDOLLFK", + "type": "long", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "NCONANPODKN" + }, + { + "name": "PPLMJPLEHLP", + "type": "IReadOnlyList", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "NCONANPODKN" + } + ], + "inheritedWireKeys": [] + } + } + ] + }, + { + "family": "rooms", + "route": "rooms/visitedby/me", + "evidence": [ + { + "type": "EJDCNGBEICB", + "method": "RecRoom.Async.IPromise`1> INKFBOEEKDA()", + "file": "EJDCNGBEICB.txt", + "line": 1018, + "contract": { + "kind": "array", + "raw": "System.Collections.Generic.List`1", + "rootType": "KLCOGEIGEBJ", + "item": { + "kind": "object", + "raw": "KLCOGEIGEBJ", + "rootType": "KLCOGEIGEBJ" + }, + "typeDescription": { + "name": "KLCOGEIGEBJ", + "found": true, + "file": "KLCOGEIGEBJ.cs", + "declaration": "public class KLCOGEIGEBJ : MKAMHOIHOJK", + "kind": "class", + "baseType": "MKAMHOIHOJK", + "properties": [ + { + "name": "DADOKMAOFJL", + "type": "long", + "accessibility": "public", + "kind": "property" + }, + { + "name": "GJPGKJJCPBK", + "type": "bool", + "accessibility": "public", + "kind": "property" + }, + { + "name": "IBLOJJBEKFF", + "type": "bool", + "accessibility": "public", + "kind": "property" + }, + { + "name": "JCLCOCEOAEP", + "type": "bool", + "accessibility": "public", + "kind": "property" + }, + { + "name": "JGJCBGEBLLO", + "type": "bool", + "accessibility": "public", + "kind": "property" + }, + { + "name": "PLFEEIJCHOH", + "type": "bool", + "accessibility": "public", + "kind": "property" + } + ], + "fields": [ + { + "name": "BJFNPKFOALK", + "type": "long", + "kind": "field" + } + ], + "enumValues": [], + "wireKeys": [ + "RoomId", + "IsDorm", + "CloningAllowed", + "DisableMicAutoMute", + "DisableRoomComments", + "EncryptVoiceChat" + ], + "isilFile": "KLCOGEIGEBJ.txt", + "isilType": "KLCOGEIGEBJ", + "inherits": "MKAMHOIHOJK", + "inheritedMembers": [ + { + "name": "ACBFDMLHFPB", + "type": "DateTime", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "AHGCOGFEEEE", + "type": "string", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "AKFBNELAMNA", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "BADIGBCKECA", + "type": "int", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "BNBLOBAEDEE", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "CDINMMPNAID", + "type": "NMJEKMMBDDE", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "CDNFGMHLDMJ", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "FIKEBGGCDFN", + "type": "string", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "GIBHIMGJNNO", + "type": "GPDIAKNEBKH", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "HPLBOMGACED", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "IGOPGMHHLKI", + "type": "string", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "JFEAPMIPNEP", + "type": "DPLPMKMFMPB", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "KHIJAFCHLIA", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "KLNJBBPNMBJ", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "KODBEJPEFOJ", + "type": "string", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "LPJLEMJFBPE", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "MGBDHBHCDMH", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "MIKDLDEALPN", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "OFONEIOEIED", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "OILEJFNPDDB", + "type": "HJPGEGENLPH", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "PEEFHKMOMKK", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + } + ], + "inheritedWireKeys": [ + "Name", + "Description", + "ImageName", + "WarningMask", + "CustomWarning", + "CreatorAccountId", + "State", + "Accessibility", + "SupportsLevelVoting", + "IsRRO", + "SupportsScreens", + "SupportsWalkVR", + "SupportsTeleportVR", + "SupportsVRLow", + "SupportsQuest2", + "SupportsMobile", + "SupportsJuniors", + "CreatedAt", + "Stats" + ] + } + } + } + ], + "contracts": [ + { + "kind": "array", + "raw": "System.Collections.Generic.List`1", + "rootType": "KLCOGEIGEBJ", + "item": { + "kind": "object", + "raw": "KLCOGEIGEBJ", + "rootType": "KLCOGEIGEBJ" + }, + "typeDescription": { + "name": "KLCOGEIGEBJ", + "found": true, + "file": "KLCOGEIGEBJ.cs", + "declaration": "public class KLCOGEIGEBJ : MKAMHOIHOJK", + "kind": "class", + "baseType": "MKAMHOIHOJK", + "properties": [ + { + "name": "DADOKMAOFJL", + "type": "long", + "accessibility": "public", + "kind": "property" + }, + { + "name": "GJPGKJJCPBK", + "type": "bool", + "accessibility": "public", + "kind": "property" + }, + { + "name": "IBLOJJBEKFF", + "type": "bool", + "accessibility": "public", + "kind": "property" + }, + { + "name": "JCLCOCEOAEP", + "type": "bool", + "accessibility": "public", + "kind": "property" + }, + { + "name": "JGJCBGEBLLO", + "type": "bool", + "accessibility": "public", + "kind": "property" + }, + { + "name": "PLFEEIJCHOH", + "type": "bool", + "accessibility": "public", + "kind": "property" + } + ], + "fields": [ + { + "name": "BJFNPKFOALK", + "type": "long", + "kind": "field" + } + ], + "enumValues": [], + "wireKeys": [ + "RoomId", + "IsDorm", + "CloningAllowed", + "DisableMicAutoMute", + "DisableRoomComments", + "EncryptVoiceChat" + ], + "isilFile": "KLCOGEIGEBJ.txt", + "isilType": "KLCOGEIGEBJ", + "inherits": "MKAMHOIHOJK", + "inheritedMembers": [ + { + "name": "ACBFDMLHFPB", + "type": "DateTime", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "AHGCOGFEEEE", + "type": "string", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "AKFBNELAMNA", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "BADIGBCKECA", + "type": "int", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "BNBLOBAEDEE", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "CDINMMPNAID", + "type": "NMJEKMMBDDE", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "CDNFGMHLDMJ", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "FIKEBGGCDFN", + "type": "string", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "GIBHIMGJNNO", + "type": "GPDIAKNEBKH", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "HPLBOMGACED", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "IGOPGMHHLKI", + "type": "string", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "JFEAPMIPNEP", + "type": "DPLPMKMFMPB", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "KHIJAFCHLIA", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "KLNJBBPNMBJ", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "KODBEJPEFOJ", + "type": "string", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "LPJLEMJFBPE", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "MGBDHBHCDMH", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "MIKDLDEALPN", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "OFONEIOEIED", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "OILEJFNPDDB", + "type": "HJPGEGENLPH", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "PEEFHKMOMKK", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + } + ], + "inheritedWireKeys": [ + "Name", + "Description", + "ImageName", + "WarningMask", + "CustomWarning", + "CreatorAccountId", + "State", + "Accessibility", + "SupportsLevelVoting", + "IsRRO", + "SupportsScreens", + "SupportsWalkVR", + "SupportsTeleportVR", + "SupportsVRLow", + "SupportsQuest2", + "SupportsMobile", + "SupportsJuniors", + "CreatedAt", + "Stats" + ] + } + } + ] + }, + { + "family": "rooms", + "route": "search_rooms/", + "evidence": [ + { + "type": "OJMCBOKJFOF", + "method": "RecRoom.Async.IPromise`1> IAAKGMOGLFN(System.String CNBKKCJAHPP)", + "file": "OJMCBOKJFOF.txt", + "line": 3064, + "contract": { + "kind": "array", + "raw": "System.Collections.Generic.IReadOnlyList`1", + "rootType": "KLCOGEIGEBJ", + "item": { + "kind": "object", + "raw": "KLCOGEIGEBJ", + "rootType": "KLCOGEIGEBJ" + }, + "typeDescription": { + "name": "KLCOGEIGEBJ", + "found": true, + "file": "KLCOGEIGEBJ.cs", + "declaration": "public class KLCOGEIGEBJ : MKAMHOIHOJK", + "kind": "class", + "baseType": "MKAMHOIHOJK", + "properties": [ + { + "name": "DADOKMAOFJL", + "type": "long", + "accessibility": "public", + "kind": "property" + }, + { + "name": "GJPGKJJCPBK", + "type": "bool", + "accessibility": "public", + "kind": "property" + }, + { + "name": "IBLOJJBEKFF", + "type": "bool", + "accessibility": "public", + "kind": "property" + }, + { + "name": "JCLCOCEOAEP", + "type": "bool", + "accessibility": "public", + "kind": "property" + }, + { + "name": "JGJCBGEBLLO", + "type": "bool", + "accessibility": "public", + "kind": "property" + }, + { + "name": "PLFEEIJCHOH", + "type": "bool", + "accessibility": "public", + "kind": "property" + } + ], + "fields": [ + { + "name": "BJFNPKFOALK", + "type": "long", + "kind": "field" + } + ], + "enumValues": [], + "wireKeys": [ + "RoomId", + "IsDorm", + "CloningAllowed", + "DisableMicAutoMute", + "DisableRoomComments", + "EncryptVoiceChat" + ], + "isilFile": "KLCOGEIGEBJ.txt", + "isilType": "KLCOGEIGEBJ", + "inherits": "MKAMHOIHOJK", + "inheritedMembers": [ + { + "name": "ACBFDMLHFPB", + "type": "DateTime", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "AHGCOGFEEEE", + "type": "string", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "AKFBNELAMNA", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "BADIGBCKECA", + "type": "int", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "BNBLOBAEDEE", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "CDINMMPNAID", + "type": "NMJEKMMBDDE", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "CDNFGMHLDMJ", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "FIKEBGGCDFN", + "type": "string", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "GIBHIMGJNNO", + "type": "GPDIAKNEBKH", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "HPLBOMGACED", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "IGOPGMHHLKI", + "type": "string", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "JFEAPMIPNEP", + "type": "DPLPMKMFMPB", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "KHIJAFCHLIA", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "KLNJBBPNMBJ", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "KODBEJPEFOJ", + "type": "string", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "LPJLEMJFBPE", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "MGBDHBHCDMH", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "MIKDLDEALPN", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "OFONEIOEIED", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "OILEJFNPDDB", + "type": "HJPGEGENLPH", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "PEEFHKMOMKK", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + } + ], + "inheritedWireKeys": [ + "Name", + "Description", + "ImageName", + "WarningMask", + "CustomWarning", + "CreatorAccountId", + "State", + "Accessibility", + "SupportsLevelVoting", + "IsRRO", + "SupportsScreens", + "SupportsWalkVR", + "SupportsTeleportVR", + "SupportsVRLow", + "SupportsQuest2", + "SupportsMobile", + "SupportsJuniors", + "CreatedAt", + "Stats" + ] + } + } + }, + { + "type": "OJMCBOKJFOF+PEBCIEAPGON", + "method": "System.String IAAKGMOGLFN(System.String CNBKKCJAHPP)", + "file": "OJMCBOKJFOF_NestedType_PEBCIEAPGON.txt", + "line": 76, + "contract": { + "kind": "unknown", + "raw": "", + "rootType": "unknown" + } + } + ], + "contracts": [ + { + "kind": "array", + "raw": "System.Collections.Generic.IReadOnlyList`1", + "rootType": "KLCOGEIGEBJ", + "item": { + "kind": "object", + "raw": "KLCOGEIGEBJ", + "rootType": "KLCOGEIGEBJ" + }, + "typeDescription": { + "name": "KLCOGEIGEBJ", + "found": true, + "file": "KLCOGEIGEBJ.cs", + "declaration": "public class KLCOGEIGEBJ : MKAMHOIHOJK", + "kind": "class", + "baseType": "MKAMHOIHOJK", + "properties": [ + { + "name": "DADOKMAOFJL", + "type": "long", + "accessibility": "public", + "kind": "property" + }, + { + "name": "GJPGKJJCPBK", + "type": "bool", + "accessibility": "public", + "kind": "property" + }, + { + "name": "IBLOJJBEKFF", + "type": "bool", + "accessibility": "public", + "kind": "property" + }, + { + "name": "JCLCOCEOAEP", + "type": "bool", + "accessibility": "public", + "kind": "property" + }, + { + "name": "JGJCBGEBLLO", + "type": "bool", + "accessibility": "public", + "kind": "property" + }, + { + "name": "PLFEEIJCHOH", + "type": "bool", + "accessibility": "public", + "kind": "property" + } + ], + "fields": [ + { + "name": "BJFNPKFOALK", + "type": "long", + "kind": "field" + } + ], + "enumValues": [], + "wireKeys": [ + "RoomId", + "IsDorm", + "CloningAllowed", + "DisableMicAutoMute", + "DisableRoomComments", + "EncryptVoiceChat" + ], + "isilFile": "KLCOGEIGEBJ.txt", + "isilType": "KLCOGEIGEBJ", + "inherits": "MKAMHOIHOJK", + "inheritedMembers": [ + { + "name": "ACBFDMLHFPB", + "type": "DateTime", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "AHGCOGFEEEE", + "type": "string", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "AKFBNELAMNA", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "BADIGBCKECA", + "type": "int", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "BNBLOBAEDEE", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "CDINMMPNAID", + "type": "NMJEKMMBDDE", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "CDNFGMHLDMJ", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "FIKEBGGCDFN", + "type": "string", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "GIBHIMGJNNO", + "type": "GPDIAKNEBKH", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "HPLBOMGACED", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "IGOPGMHHLKI", + "type": "string", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "JFEAPMIPNEP", + "type": "DPLPMKMFMPB", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "KHIJAFCHLIA", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "KLNJBBPNMBJ", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "KODBEJPEFOJ", + "type": "string", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "LPJLEMJFBPE", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "MGBDHBHCDMH", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "MIKDLDEALPN", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "OFONEIOEIED", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "OILEJFNPDDB", + "type": "HJPGEGENLPH", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + }, + { + "name": "PEEFHKMOMKK", + "type": "bool", + "accessibility": "public", + "kind": "property", + "inheritedFrom": "MKAMHOIHOJK" + } + ], + "inheritedWireKeys": [ + "Name", + "Description", + "ImageName", + "WarningMask", + "CustomWarning", + "CreatorAccountId", + "State", + "Accessibility", + "SupportsLevelVoting", + "IsRRO", + "SupportsScreens", + "SupportsWalkVR", + "SupportsTeleportVR", + "SupportsVRLow", + "SupportsQuest2", + "SupportsMobile", + "SupportsJuniors", + "CreatedAt", + "Stats" + ] + } + }, + { + "kind": "unknown", + "raw": "", + "rootType": "unknown", + "typeDescription": { + "name": "unknown", + "found": false, + "kind": "object", + "wireKeys": [] + } + } + ] + }, + { + "family": "rooms", + "route": "search_roomsandplaylists/", + "evidence": [ + { + "type": "OJMCBOKJFOF", + "method": "RecRoom.Async.IPromise`1> HFHDFLDCIEM(System.String CNBKKCJAHPP)", + "file": "OJMCBOKJFOF.txt", + "line": 4567, + "contract": { + "kind": "array", + "raw": "System.Collections.Generic.IReadOnlyList`1", + "rootType": "MKAMHOIHOJK", + "item": { + "kind": "object", + "raw": "MKAMHOIHOJK", + "rootType": "MKAMHOIHOJK" + }, + "typeDescription": { + "name": "MKAMHOIHOJK", + "found": true, + "file": "MKAMHOIHOJK.cs", + "declaration": "public abstract class MKAMHOIHOJK : IFAIJAGLDFK, AKJKEMONOIL", + "kind": "class", + "baseType": "IFAIJAGLDFK", + "properties": [ + { + "name": "ACBFDMLHFPB", + "type": "DateTime", + "accessibility": "public", + "kind": "property" + }, + { + "name": "AHGCOGFEEEE", + "type": "string", + "accessibility": "public", + "kind": "property" + }, + { + "name": "AKFBNELAMNA", + "type": "bool", + "accessibility": "public", + "kind": "property" + }, + { + "name": "BADIGBCKECA", + "type": "int", + "accessibility": "public", + "kind": "property" + }, + { + "name": "BNBLOBAEDEE", + "type": "bool", + "accessibility": "public", + "kind": "property" + }, + { + "name": "CDINMMPNAID", + "type": "NMJEKMMBDDE", + "accessibility": "public", + "kind": "property" + }, + { + "name": "CDNFGMHLDMJ", + "type": "bool", + "accessibility": "public", + "kind": "property" + }, + { + "name": "FIKEBGGCDFN", + "type": "string", + "accessibility": "public", + "kind": "property" + }, + { + "name": "GIBHIMGJNNO", + "type": "GPDIAKNEBKH", + "accessibility": "public", + "kind": "property" + }, + { + "name": "HPLBOMGACED", + "type": "bool", + "accessibility": "public", + "kind": "property" + }, + { + "name": "IGOPGMHHLKI", + "type": "string", + "accessibility": "public", + "kind": "property" + }, + { + "name": "JFEAPMIPNEP", + "type": "DPLPMKMFMPB", + "accessibility": "public", + "kind": "property" + }, + { + "name": "KHIJAFCHLIA", + "type": "bool", + "accessibility": "public", + "kind": "property" + }, + { + "name": "KLNJBBPNMBJ", + "type": "bool", + "accessibility": "public", + "kind": "property" + }, + { + "name": "KODBEJPEFOJ", + "type": "string", + "accessibility": "public", + "kind": "property" + }, + { + "name": "LPJLEMJFBPE", + "type": "bool", + "accessibility": "public", + "kind": "property" + }, + { + "name": "MGBDHBHCDMH", + "type": "bool", + "accessibility": "public", + "kind": "property" + }, + { + "name": "MIKDLDEALPN", + "type": "bool", + "accessibility": "public", + "kind": "property" + }, + { + "name": "OFONEIOEIED", + "type": "bool", + "accessibility": "public", + "kind": "property" + }, + { + "name": "OILEJFNPDDB", + "type": "HJPGEGENLPH", + "accessibility": "public", + "kind": "property" + }, + { + "name": "PEEFHKMOMKK", + "type": "bool", + "accessibility": "public", + "kind": "property" + } + ], + "fields": [], + "enumValues": [], + "wireKeys": [ + "Name", + "Description", + "ImageName", + "WarningMask", + "CustomWarning", + "CreatorAccountId", + "State", + "Accessibility", + "SupportsLevelVoting", + "IsRRO", + "SupportsScreens", + "SupportsWalkVR", + "SupportsTeleportVR", + "SupportsVRLow", + "SupportsQuest2", + "SupportsMobile", + "SupportsJuniors", + "CreatedAt", + "Stats" + ], + "isilFile": "MKAMHOIHOJK.txt", + "isilType": "MKAMHOIHOJK" + } + } + }, + { + "type": "OJMCBOKJFOF+PEBCIEAPGON", + "method": "System.String HFHDFLDCIEM(System.String CNBKKCJAHPP)", + "file": "OJMCBOKJFOF_NestedType_PEBCIEAPGON.txt", + "line": 158, + "contract": { + "kind": "unknown", + "raw": "", + "rootType": "unknown" + } + } + ], + "contracts": [ + { + "kind": "array", + "raw": "System.Collections.Generic.IReadOnlyList`1", + "rootType": "MKAMHOIHOJK", + "item": { + "kind": "object", + "raw": "MKAMHOIHOJK", + "rootType": "MKAMHOIHOJK" + }, + "typeDescription": { + "name": "MKAMHOIHOJK", + "found": true, + "file": "MKAMHOIHOJK.cs", + "declaration": "public abstract class MKAMHOIHOJK : IFAIJAGLDFK, AKJKEMONOIL", + "kind": "class", + "baseType": "IFAIJAGLDFK", + "properties": [ + { + "name": "ACBFDMLHFPB", + "type": "DateTime", + "accessibility": "public", + "kind": "property" + }, + { + "name": "AHGCOGFEEEE", + "type": "string", + "accessibility": "public", + "kind": "property" + }, + { + "name": "AKFBNELAMNA", + "type": "bool", + "accessibility": "public", + "kind": "property" + }, + { + "name": "BADIGBCKECA", + "type": "int", + "accessibility": "public", + "kind": "property" + }, + { + "name": "BNBLOBAEDEE", + "type": "bool", + "accessibility": "public", + "kind": "property" + }, + { + "name": "CDINMMPNAID", + "type": "NMJEKMMBDDE", + "accessibility": "public", + "kind": "property" + }, + { + "name": "CDNFGMHLDMJ", + "type": "bool", + "accessibility": "public", + "kind": "property" + }, + { + "name": "FIKEBGGCDFN", + "type": "string", + "accessibility": "public", + "kind": "property" + }, + { + "name": "GIBHIMGJNNO", + "type": "GPDIAKNEBKH", + "accessibility": "public", + "kind": "property" + }, + { + "name": "HPLBOMGACED", + "type": "bool", + "accessibility": "public", + "kind": "property" + }, + { + "name": "IGOPGMHHLKI", + "type": "string", + "accessibility": "public", + "kind": "property" + }, + { + "name": "JFEAPMIPNEP", + "type": "DPLPMKMFMPB", + "accessibility": "public", + "kind": "property" + }, + { + "name": "KHIJAFCHLIA", + "type": "bool", + "accessibility": "public", + "kind": "property" + }, + { + "name": "KLNJBBPNMBJ", + "type": "bool", + "accessibility": "public", + "kind": "property" + }, + { + "name": "KODBEJPEFOJ", + "type": "string", + "accessibility": "public", + "kind": "property" + }, + { + "name": "LPJLEMJFBPE", + "type": "bool", + "accessibility": "public", + "kind": "property" + }, + { + "name": "MGBDHBHCDMH", + "type": "bool", + "accessibility": "public", + "kind": "property" + }, + { + "name": "MIKDLDEALPN", + "type": "bool", + "accessibility": "public", + "kind": "property" + }, + { + "name": "OFONEIOEIED", + "type": "bool", + "accessibility": "public", + "kind": "property" + }, + { + "name": "OILEJFNPDDB", + "type": "HJPGEGENLPH", + "accessibility": "public", + "kind": "property" + }, + { + "name": "PEEFHKMOMKK", + "type": "bool", + "accessibility": "public", + "kind": "property" + } + ], + "fields": [], + "enumValues": [], + "wireKeys": [ + "Name", + "Description", + "ImageName", + "WarningMask", + "CustomWarning", + "CreatorAccountId", + "State", + "Accessibility", + "SupportsLevelVoting", + "IsRRO", + "SupportsScreens", + "SupportsWalkVR", + "SupportsTeleportVR", + "SupportsVRLow", + "SupportsQuest2", + "SupportsMobile", + "SupportsJuniors", + "CreatedAt", + "Stats" + ], + "isilFile": "MKAMHOIHOJK.txt", + "isilType": "MKAMHOIHOJK" + } + }, + { + "kind": "unknown", + "raw": "", + "rootType": "unknown", + "typeDescription": { + "name": "unknown", + "found": false, + "kind": "object", + "wireKeys": [] + } + } + ] + } + ], + "types": [ + { + "name": "CCEOLAOLEKJ", + "found": true, + "file": "CCEOLAOLEKJ.cs", + "declaration": "public class CCEOLAOLEKJ : IFAIJAGLDFK", + "baseType": "IFAIJAGLDFK", + "properties": [], + "fields": [], + "enumValues": [] + }, + { + "name": "FKNGFLFDIIB", + "found": true, + "file": "FKNGFLFDIIB.cs", + "declaration": "public class FKNGFLFDIIB : IFAIJAGLDFK", + "baseType": "IFAIJAGLDFK", + "properties": [], + "fields": [], + "enumValues": [] + }, + { + "name": "PLILLKHMNDA", + "found": true, + "file": "PLILLKHMNDA.cs", + "declaration": "public class PLILLKHMNDA : IFAIJAGLDFK, IEquatable", + "baseType": "IFAIJAGLDFK", + "properties": [], + "fields": [], + "enumValues": [] + }, + { + "name": "DCGAMCKNJDB", + "found": true, + "file": "DCGAMCKNJDB.cs", + "declaration": "public class DCGAMCKNJDB", + "baseType": null, + "properties": [], + "fields": [], + "enumValues": [] + }, + { + "name": "JJGHAFKJBEI", + "found": true, + "file": "JJGHAFKJBEI.cs", + "declaration": "public class JJGHAFKJBEI : CCEOLAOLEKJ", + "baseType": "CCEOLAOLEKJ", + "properties": [], + "fields": [], + "enumValues": [], + "inherits": "CCEOLAOLEKJ", + "inheritedMembers": [] + }, + { + "name": "JOHPEJAMBHG", + "found": true, + "file": "JOHPEJAMBHG.cs", + "declaration": "public class JOHPEJAMBHG : IFAIJAGLDFK", + "baseType": "IFAIJAGLDFK", + "properties": [], + "fields": [], + "enumValues": [] + }, + { + "name": "GHNGAENLIHA", + "found": true, + "file": "JDFDLJJGHIP.cs", + "declaration": "internal class GHNGAENLIHA : IFAIJAGLDFK", + "baseType": "IFAIJAGLDFK", + "properties": [], + "fields": [], + "enumValues": [] + }, + { + "name": "JLNAMKBKPNG", + "found": true, + "file": "JDFDLJJGHIP.cs", + "declaration": "internal class JLNAMKBKPNG : IFAIJAGLDFK", + "baseType": "IFAIJAGLDFK", + "properties": [], + "fields": [], + "enumValues": [] + }, + { + "name": "LOCNECLOHCA", + "found": true, + "file": "NLEKGNENMCO.cs", + "declaration": "internal class LOCNECLOHCA : IFAIJAGLDFK, AKJKEMONOIL", + "baseType": "IFAIJAGLDFK", + "properties": [], + "fields": [], + "enumValues": [] + }, + { + "name": "EPFHLDCPAOK", + "found": true, + "file": "NLEKGNENMCO.cs", + "declaration": "internal class EPFHLDCPAOK : IFAIJAGLDFK", + "baseType": "IFAIJAGLDFK", + "properties": [], + "fields": [], + "enumValues": [] + }, + { + "name": "HPACLJHLHBG", + "found": true, + "file": "HPACLJHLHBG.cs", + "declaration": "public class HPACLJHLHBG : IFAIJAGLDFK", + "baseType": "IFAIJAGLDFK", + "properties": [], + "fields": [], + "enumValues": [] + }, + { + "name": "PIHMJGCGNLP", + "found": true, + "file": "PIHMJGCGNLP.cs", + "declaration": "public class PIHMJGCGNLP : IFAIJAGLDFK", + "baseType": "IFAIJAGLDFK", + "properties": [], + "fields": [], + "enumValues": [] + }, + { + "name": "MFOAODGNGKB", + "found": true, + "file": "MFOAODGNGKB.cs", + "declaration": "public class MFOAODGNGKB : IFAIJAGLDFK", + "baseType": "IFAIJAGLDFK", + "properties": [], + "fields": [], + "enumValues": [] + }, + { + "name": "JHMMGLNJHIB", + "found": true, + "file": "JHMMGLNJHIB.cs", + "declaration": "public class JHMMGLNJHIB : IFAIJAGLDFK", + "baseType": "IFAIJAGLDFK", + "properties": [], + "fields": [], + "enumValues": [] + }, + { + "name": "FPDADCNKELI", + "found": true, + "file": "FPDADCNKELI.cs", + "declaration": "public class FPDADCNKELI : IFAIJAGLDFK", + "baseType": "IFAIJAGLDFK", + "properties": [], + "fields": [], + "enumValues": [] + }, + { + "name": "OHLKCDKEGMN", + "found": true, + "file": "OHLKCDKEGMN.cs", + "declaration": "public class OHLKCDKEGMN : IFAIJAGLDFK, AKJKEMONOIL", + "baseType": "IFAIJAGLDFK", + "properties": [], + "fields": [], + "enumValues": [] + }, + { + "name": "PLDFOPCHHJG", + "found": true, + "file": "PLDFOPCHHJG.cs", + "declaration": "public class PLDFOPCHHJG : IFAIJAGLDFK", + "baseType": "IFAIJAGLDFK", + "properties": [], + "fields": [], + "enumValues": [] + }, + { + "name": "MPHABHIMOOO", + "found": true, + "file": "MPHABHIMOOO.cs", + "declaration": "public class MPHABHIMOOO : IFAIJAGLDFK", + "baseType": "IFAIJAGLDFK", + "properties": [], + "fields": [], + "enumValues": [] + }, + { + "name": "OGKIDDEAFND", + "found": true, + "file": "OGKIDDEAFND.cs", + "declaration": "public class OGKIDDEAFND : IFAIJAGLDFK", + "baseType": "IFAIJAGLDFK", + "properties": [], + "fields": [], + "enumValues": [] + }, + { + "name": "OHGBAPBECNM", + "found": true, + "file": "OHDHPENHDAP.cs", + "declaration": "internal class OHGBAPBECNM : IFAIJAGLDFK", + "baseType": "IFAIJAGLDFK", + "properties": [], + "fields": [], + "enumValues": [] + }, + { + "name": "IBANLCLBGLM", + "found": true, + "file": "OHDHPENHDAP.cs", + "declaration": "internal class IBANLCLBGLM : IFAIJAGLDFK", + "baseType": "IFAIJAGLDFK", + "properties": [], + "fields": [], + "enumValues": [] + }, + { + "name": "AHEPPAEOLOD", + "found": true, + "file": "AHEPPAEOLOD.cs", + "declaration": "public class AHEPPAEOLOD : IFAIJAGLDFK", + "baseType": "IFAIJAGLDFK", + "properties": [], + "fields": [], + "enumValues": [] + }, + { + "name": "HJPDBNLCGIB", + "found": true, + "file": "HJPDBNLCGIB.cs", + "declaration": "public class HJPDBNLCGIB : IFAIJAGLDFK", + "baseType": "IFAIJAGLDFK", + "properties": [], + "fields": [], + "enumValues": [] + }, + { + "name": "KLAMKCBENEA", + "found": true, + "file": "KLAMKCBENEA.cs", + "declaration": "public class KLAMKCBENEA : IFAIJAGLDFK", + "baseType": "IFAIJAGLDFK", + "properties": [], + "fields": [], + "enumValues": [] + }, + { + "name": "NJMAEIPIOAP", + "found": true, + "file": "NJMAEIPIOAP.cs", + "declaration": "public class NJMAEIPIOAP : IFAIJAGLDFK", + "baseType": "IFAIJAGLDFK", + "properties": [], + "fields": [], + "enumValues": [] + }, + { + "name": "OBBBPCBIMME", + "found": true, + "file": "OBBBPCBIMME.cs", + "declaration": "public class OBBBPCBIMME : IFAIJAGLDFK", + "baseType": "IFAIJAGLDFK", + "properties": [], + "fields": [], + "enumValues": [] + }, + { + "name": "OEGPIPBKHCN", + "found": true, + "file": "OEGPIPBKHCN.cs", + "declaration": "public class OEGPIPBKHCN : IFAIJAGLDFK", + "baseType": "IFAIJAGLDFK", + "properties": [], + "fields": [], + "enumValues": [] + }, + { + "name": "NEFHINKECJJ", + "found": true, + "file": "NEFHINKECJJ.cs", + "declaration": "public class NEFHINKECJJ : IFAIJAGLDFK", + "baseType": "IFAIJAGLDFK", + "properties": [], + "fields": [], + "enumValues": [] + }, + { + "name": "MHCKNNJOIIP", + "found": true, + "file": "RecNet/Matchmaking.cs", + "declaration": "internal enum MHCKNNJOIIP", + "baseType": null, + "properties": [], + "fields": [], + "enumValues": [] + }, + { + "name": "JHNOAKDOMHG", + "found": true, + "file": "JHNOAKDOMHG.cs", + "declaration": "public class JHNOAKDOMHG : IFAIJAGLDFK", + "baseType": "IFAIJAGLDFK", + "properties": [], + "fields": [], + "enumValues": [] + }, + { + "name": "DIBODMEJOPN", + "found": true, + "file": "DIBODMEJOPN.cs", + "declaration": "public class DIBODMEJOPN : IFAIJAGLDFK", + "baseType": "IFAIJAGLDFK", + "properties": [], + "fields": [], + "enumValues": [] + }, + { + "name": "CCMBKDINCAH", + "found": true, + "file": "CCMBKDINCAH.cs", + "declaration": "public class CCMBKDINCAH : IFAIJAGLDFK", + "baseType": "IFAIJAGLDFK", + "properties": [], + "fields": [], + "enumValues": [] + }, + { + "name": "LEKKFHBPBAB", + "found": true, + "file": "KELEPAPMOGK.cs", + "declaration": "internal class LEKKFHBPBAB", + "baseType": null, + "properties": [], + "fields": [], + "enumValues": [] + }, + { + "name": "Byte", + "found": false, + "file": null, + "declaration": null, + "baseType": null, + "properties": [], + "fields": [], + "enumValues": [] + }, + { + "name": "MKEKHKEHPLO", + "found": true, + "file": "MKEKHKEHPLO.cs", + "declaration": "public class MKEKHKEHPLO : IFAIJAGLDFK", + "baseType": "IFAIJAGLDFK", + "properties": [], + "fields": [], + "enumValues": [] + }, + { + "name": "DJGBECJHOKF", + "found": true, + "file": "GCBPKHFJKCE.cs", + "declaration": "internal class DJGBECJHOKF : IFAIJAGLDFK", + "baseType": "IFAIJAGLDFK", + "properties": [], + "fields": [], + "enumValues": [] + }, + { + "name": "JFIKDPALHOL", + "found": true, + "file": "AAHIIPOCKMB.cs", + "declaration": "internal class JFIKDPALHOL : IFAIJAGLDFK", + "baseType": "IFAIJAGLDFK", + "properties": [], + "fields": [], + "enumValues": [] + }, + { + "name": "OOCCCGPICOG", + "found": true, + "file": "OOCCCGPICOG.cs", + "declaration": "public class OOCCCGPICOG : IFAIJAGLDFK, IEquatable", + "baseType": "IFAIJAGLDFK", + "properties": [], + "fields": [], + "enumValues": [] + }, + { + "name": "LLKHFJDNFMM", + "found": true, + "file": "LLKHFJDNFMM.cs", + "declaration": "public class LLKHFJDNFMM : IFAIJAGLDFK", + "baseType": "IFAIJAGLDFK", + "properties": [], + "fields": [], + "enumValues": [] + }, + { + "name": "RewardBalanceModificationDTO>", + "found": false, + "file": null, + "declaration": null, + "baseType": null, + "properties": [], + "fields": [], + "enumValues": [] + }, + { + "name": "InventionPurchaseResponseDTO", + "found": true, + "file": "GEAPBDGCKMB.cs", + "declaration": "internal class InventionPurchaseResponseDTO : IFAIJAGLDFK", + "baseType": "IFAIJAGLDFK", + "properties": [], + "fields": [], + "enumValues": [] + }, + { + "name": "RoomKeyPurchaseResponseDTO", + "found": true, + "file": "GEAPBDGCKMB.cs", + "declaration": "internal class RoomKeyPurchaseResponseDTO : IFAIJAGLDFK", + "baseType": "IFAIJAGLDFK", + "properties": [], + "fields": [], + "enumValues": [] + }, + { + "name": "BKFGFHDDNFG", + "found": true, + "file": "BKFGFHDDNFG.cs", + "declaration": "public class BKFGFHDDNFG : DOPJPOEFDCN", + "baseType": "DOPJPOEFDCN", + "properties": [], + "fields": [], + "enumValues": [], + "inherits": "DOPJPOEFDCN", + "inheritedMembers": [] + }, + { + "name": "DOPJPOEFDCN", + "found": true, + "file": "DOPJPOEFDCN.cs", + "declaration": "public class DOPJPOEFDCN : IFAIJAGLDFK", + "baseType": "IFAIJAGLDFK", + "properties": [], + "fields": [], + "enumValues": [] + }, + { + "name": "CPFPHLINDHN", + "found": true, + "file": "CPFPHLINDHN.cs", + "declaration": "public class CPFPHLINDHN : IFAIJAGLDFK", + "baseType": "IFAIJAGLDFK", + "properties": [], + "fields": [], + "enumValues": [] + }, + { + "name": "FDAJOEOJFDN", + "found": true, + "file": "FDAJOEOJFDN.cs", + "declaration": "public class FDAJOEOJFDN : IFAIJAGLDFK", + "baseType": "IFAIJAGLDFK", + "properties": [], + "fields": [], + "enumValues": [] + }, + { + "name": "PCBCGDHBAEL", + "found": true, + "file": "PCBCGDHBAEL.cs", + "declaration": "public enum PCBCGDHBAEL", + "baseType": null, + "properties": [], + "fields": [], + "enumValues": [] + }, + { + "name": "LLNPLLBEJBE", + "found": true, + "file": "LLNPLLBEJBE.cs", + "declaration": "public class LLNPLLBEJBE : IFAIJAGLDFK", + "baseType": "IFAIJAGLDFK", + "properties": [], + "fields": [], + "enumValues": [] + }, + { + "name": "PBIJAPEOEDO", + "found": true, + "file": "PBIJAPEOEDO.cs", + "declaration": "public class PBIJAPEOEDO : IFAIJAGLDFK", + "baseType": "IFAIJAGLDFK", + "properties": [], + "fields": [], + "enumValues": [] + }, + { + "name": "TPlayerEventDTO", + "found": false, + "file": null, + "declaration": null, + "baseType": null, + "properties": [], + "fields": [], + "enumValues": [] + }, + { + "name": "FNBMIJGOOJM", + "found": true, + "file": "FNBMIJGOOJM.cs", + "declaration": "public class FNBMIJGOOJM : IFAIJAGLDFK", + "baseType": "IFAIJAGLDFK", + "properties": [], + "fields": [], + "enumValues": [] + }, + { + "name": "PDOBNLOLBAF", + "found": true, + "file": "PDOBNLOLBAF.cs", + "declaration": "public class PDOBNLOLBAF : IFAIJAGLDFK", + "baseType": "IFAIJAGLDFK", + "properties": [], + "fields": [], + "enumValues": [] + }, + { + "name": "DGOPHENCPOC", + "found": true, + "file": "DGOPHENCPOC.cs", + "declaration": "public enum DGOPHENCPOC", + "baseType": null, + "properties": [], + "fields": [], + "enumValues": [] + }, + { + "name": "NMKMJKIHDNE", + "found": true, + "file": "NMKMJKIHDNE.cs", + "declaration": "public class NMKMJKIHDNE : CCMBKDINCAH", + "baseType": "CCMBKDINCAH", + "properties": [], + "fields": [], + "enumValues": [], + "inherits": "CCMBKDINCAH", + "inheritedMembers": [] + }, + { + "name": "JGEBJEJLNBK", + "found": true, + "file": "JGEBJEJLNBK.cs", + "declaration": "public class JGEBJEJLNBK : IFAIJAGLDFK", + "baseType": "IFAIJAGLDFK", + "properties": [], + "fields": [], + "enumValues": [] + }, + { + "name": "NFMBDLEJEDD", + "found": true, + "file": "NFMBDLEJEDD.cs", + "declaration": "public class NFMBDLEJEDD : IFAIJAGLDFK", + "baseType": "IFAIJAGLDFK", + "properties": [], + "fields": [], + "enumValues": [] + }, + { + "name": "NMPFCIJPODA", + "found": true, + "file": "NMPFCIJPODA.cs", + "declaration": "public class NMPFCIJPODA : IFAIJAGLDFK", + "baseType": "IFAIJAGLDFK", + "properties": [], + "fields": [], + "enumValues": [] + }, + { + "name": "BMFAGMFKODA", + "found": true, + "file": "BMFAGMFKODA.cs", + "declaration": "public class BMFAGMFKODA : KMKPEOGJDFK", + "baseType": "KMKPEOGJDFK", + "properties": [], + "fields": [], + "enumValues": [], + "inherits": "KMKPEOGJDFK", + "inheritedMembers": [] + }, + { + "name": "KMKPEOGJDFK", + "found": true, + "file": "KMKPEOGJDFK.cs", + "declaration": "public class KMKPEOGJDFK : MKAMHOIHOJK", + "baseType": "MKAMHOIHOJK", + "properties": [], + "fields": [], + "enumValues": [], + "inherits": "MKAMHOIHOJK", + "inheritedMembers": [] + }, + { + "name": "MKAMHOIHOJK", + "found": true, + "file": "MKAMHOIHOJK.cs", + "declaration": "public abstract class MKAMHOIHOJK : IFAIJAGLDFK, AKJKEMONOIL", + "baseType": "IFAIJAGLDFK", + "properties": [], + "fields": [], + "enumValues": [] + }, + { + "name": "CJODCLDGFCF", + "found": true, + "file": "CJODCLDGFCF.cs", + "declaration": "public class CJODCLDGFCF : IFAIJAGLDFK", + "baseType": "IFAIJAGLDFK", + "properties": [], + "fields": [], + "enumValues": [] + }, + { + "name": "HJKAOMOICJG", + "found": true, + "file": "HJKAOMOICJG.cs", + "declaration": "public class HJKAOMOICJG : NCONANPODKN, IFAIJAGLDFK", + "baseType": "NCONANPODKN", + "properties": [], + "fields": [], + "enumValues": [], + "inherits": "NCONANPODKN", + "inheritedMembers": [] + }, + { + "name": "NCONANPODKN", + "found": false, + "file": null, + "declaration": null, + "baseType": null, + "properties": [], + "fields": [], + "enumValues": [] + }, + { + "name": "HOGFDJNNMHM", + "found": true, + "file": "HOGFDJNNMHM.cs", + "declaration": "public class HOGFDJNNMHM : IFAIJAGLDFK", + "baseType": "IFAIJAGLDFK", + "properties": [], + "fields": [], + "enumValues": [] + }, + { + "name": "BMHHFIGBOFD", + "found": true, + "file": "BMHHFIGBOFD.cs", + "declaration": "public enum BMHHFIGBOFD", + "baseType": null, + "properties": [], + "fields": [], + "enumValues": [] + }, + { + "name": "BCKIBFNPIPD", + "found": true, + "file": "BCKIBFNPIPD.cs", + "declaration": "public class BCKIBFNPIPD : IFAIJAGLDFK", + "baseType": "IFAIJAGLDFK", + "properties": [], + "fields": [], + "enumValues": [] + }, + { + "name": "AMAGKLLBGEC", + "found": true, + "file": "AMAGKLLBGEC.cs", + "declaration": "public class AMAGKLLBGEC : IFAIJAGLDFK", + "baseType": "IFAIJAGLDFK", + "properties": [], + "fields": [], + "enumValues": [] + }, + { + "name": "AEBEPCMAABC", + "found": true, + "file": "AEBEPCMAABC.cs", + "declaration": "public class AEBEPCMAABC : IFAIJAGLDFK", + "baseType": "IFAIJAGLDFK", + "properties": [], + "fields": [], + "enumValues": [] + }, + { + "name": "KLCOGEIGEBJ", + "found": true, + "file": "KLCOGEIGEBJ.cs", + "declaration": "public class KLCOGEIGEBJ : MKAMHOIHOJK", + "baseType": "MKAMHOIHOJK", + "properties": [], + "fields": [], + "enumValues": [], + "inherits": "MKAMHOIHOJK", + "inheritedMembers": [] + }, + { + "name": "PPENFJMFPNE", + "found": true, + "file": "PPENFJMFPNE.cs", + "declaration": "public class PPENFJMFPNE : KLCOGEIGEBJ", + "baseType": "KLCOGEIGEBJ", + "properties": [], + "fields": [], + "enumValues": [], + "inherits": "KLCOGEIGEBJ", + "inheritedMembers": [] + }, + { + "name": "EAENIFLCDGI", + "found": true, + "file": "EAENIFLCDGI.cs", + "declaration": "public class EAENIFLCDGI : IFAIJAGLDFK", + "baseType": "IFAIJAGLDFK", + "properties": [], + "fields": [], + "enumValues": [] + }, + { + "name": "CGCEKBCIHJC", + "found": true, + "file": "CGCEKBCIHJC.cs", + "declaration": "public class CGCEKBCIHJC : IFAIJAGLDFK", + "baseType": "IFAIJAGLDFK", + "properties": [], + "fields": [], + "enumValues": [] + }, + { + "name": "DHOCPFIOKHD", + "found": true, + "file": "DHOCPFIOKHD.cs", + "declaration": "public class DHOCPFIOKHD : IFAIJAGLDFK", + "baseType": "IFAIJAGLDFK", + "properties": [], + "fields": [], + "enumValues": [] + }, + { + "name": "IDLBPALJJDJ", + "found": true, + "file": "IDLBPALJJDJ.cs", + "declaration": "public class IDLBPALJJDJ : NCONANPODKN, IFAIJAGLDFK", + "baseType": "NCONANPODKN", + "properties": [], + "fields": [], + "enumValues": [], + "inherits": "NCONANPODKN", + "inheritedMembers": [] + }, + { + "name": "NCONANPODKN", + "found": false, + "file": null, + "declaration": null, + "baseType": null, + "properties": [], + "fields": [], + "enumValues": [] + }, + { + "name": "ANNKHNFLMNP", + "found": true, + "file": "ANNKHNFLMNP.cs", + "declaration": "public class ANNKHNFLMNP : IFAIJAGLDFK", + "baseType": "IFAIJAGLDFK", + "properties": [], + "fields": [], + "enumValues": [] + } + ], + "generatedAt": "2026-05-25T08:53:02.791Z", + "notes": [ + "Contracts are inferred from December 2020 client ISIL call sites and December readable C# DTO declarations.", + "wireKeys come from PPGFHEDFBEA parser methods in the ISIL dump when present; they are the strongest recovered JSON key evidence.", + "C# property names may be obfuscated and are not always wire names. Prefer wireKeys and DorkNet existing JsonPropertyName names when both exist.", + "goto/room routes have a public promise result enum, but the HTTP JSON response is the wrapper documented in httpResponseOverride." + ] +} diff --git a/docs/recroom-2020-client-response-contracts.md b/docs/recroom-2020-client-response-contracts.md new file mode 100644 index 0000000..5caa462 --- /dev/null +++ b/docs/recroom-2020-client-response-contracts.md @@ -0,0 +1,5267 @@ +# Rec Room 2020 Client Response Contracts + +This is the response-shape companion to the OpenAPI sweep. For each recovered client route it lists the actual decompiled client return type, the DTO/class members preserved by the December C# dump, and JSON wire keys recovered from `PPGFHEDFBEA` parser methods in the December ISIL dump when available. + +Important: some DTO member names are obfuscated C# names, not JSON names. `Client parser JSON keys` are the strongest evidence for exact response keys. For routes already implemented in DorkNet, keep the server DTO wire names unless the client parser evidence contradicts them. + +JSON version: `docs/recroom-2020-client-response-contracts.json` + +## account / account/{0} + +- `PEGGCEDHBOF` `` RecRoom.Async.IPromise`1 NFKMGNHDCMN(System.Int32 GKLPIFBPGOD) `` (PEGGCEDHBOF.txt:3934) + +Expected client return: `CCEOLAOLEKJ` (object) +Resolved DTO: `CCEOLAOLEKJ` from `CCEOLAOLEKJ.cs` +Declaration: `public class CCEOLAOLEKJ : IFAIJAGLDFK` +Client parser JSON keys: `accountId`, `profileImage`, `isJunior`, `platforms`, `username`, `displayName`, `createdAt` +Public/decompiled members: +- `DateTime ACBFDMLHFPB` +- `ObscuredBool AEMHDANDFOP` +- `ObscuredString BIOGKFGIMDG` +- `ObscuredString CFJLJOKIJCN` +- `ObscuredInt GAINIOENNCG` +- `ObscuredString IHADBCJNDIP` +- `ENNEGEELGMC ILOADNHKMHK` +- `ObscuredBool JIIMAIDFJPN` +- `ObscuredString OOJFBECGEAD` + +## account / account/{0}/bio + +- `PEGGCEDHBOF` `` RecRoom.Async.IPromise`1 GBLAOEFPDAH(System.Int32 GKLPIFBPGOD) `` (PEGGCEDHBOF.txt:7866) +- `PEGGCEDHBOF+<>c` `System.Void b__67_0()` (PEGGCEDHBOF_NestedType___c.txt:587) + +Expected client return: `FKNGFLFDIIB` (object) +Resolved DTO: `FKNGFLFDIIB` from `FKNGFLFDIIB.cs` +Declaration: `public class FKNGFLFDIIB : IFAIJAGLDFK` +Client parser JSON keys: `accountId`, `bio` +Public/decompiled members: +- `string AHOEKLABOBN` +- `int GAINIOENNCG` + +Expected client return: `unknown` (unknown) +Resolved DTO: `unknown` not found in readable C# dump. + +## account / account/{0}/clubs + +- `JDJGIBLMFKK` `` RecRoom.Async.IPromise`1> KGGMKBADLDM(System.Int32 GKLPIFBPGOD) `` (JDJGIBLMFKK.txt:9580) + +Expected client return: `` System.Collections.Generic.List`1 `` (array) +Resolved DTO: `PLILLKHMNDA` from `PLILLKHMNDA.cs` +Declaration: `public class PLILLKHMNDA : IFAIJAGLDFK, IEquatable` +Client parser JSON keys: `ClubId`, `Name`, `Description`, `MainImageName`, `State`, `CreatorAccountId`, `Category`, `Visibility`, `Joinability`, `AllowJuniors`, `MemberCount`, `IsRRO`, `ClubType` +Public/decompiled members: +- `int BADIGBCKECA` +- `long CCGOEDABKNN` +- `JCMLDDKFKEO CDINMMPNAID` +- `bool CDNFGMHLDMJ` +- `int EEAOJCGAOCN` +- `string EHOLKJPEGFF` +- `JCDEFCJLCHN EPGALOMHHMI` +- `string FIKEBGGCDFN` +- `Nullable HHCGNCLFKDM` +- `bool IPKLLFAJJPJ` +- `string KODBEJPEFOJ` +- `string LNGPBGCIAPP` +- `DIGMAIMMHAP MNEEEGHOGAB` +- `PJCALHOPMKJ PHBCFAJILGD` + +## account / account/bulk + +- `PEGGCEDHBOF` `` RecRoom.Async.IPromise`1> NHCHADDMPFC(System.Collections.Generic.List`1 ILNGMAANNDG) `` (PEGGCEDHBOF.txt:4917) + +Expected client return: `` System.Collections.Generic.List`1 `` (array) +Resolved DTO: `CCEOLAOLEKJ` from `CCEOLAOLEKJ.cs` +Declaration: `public class CCEOLAOLEKJ : IFAIJAGLDFK` +Client parser JSON keys: `accountId`, `profileImage`, `isJunior`, `platforms`, `username`, `displayName`, `createdAt` +Public/decompiled members: +- `DateTime ACBFDMLHFPB` +- `ObscuredBool AEMHDANDFOP` +- `ObscuredString BIOGKFGIMDG` +- `ObscuredString CFJLJOKIJCN` +- `ObscuredInt GAINIOENNCG` +- `ObscuredString IHADBCJNDIP` +- `ENNEGEELGMC ILOADNHKMHK` +- `ObscuredBool JIIMAIDFJPN` +- `ObscuredString OOJFBECGEAD` + +## account / account/bulk? + +- `PEGGCEDHBOF` `` RecRoom.Async.IPromise`1> NHCHADDMPFC(System.Collections.Generic.List`1 ILNGMAANNDG) `` (PEGGCEDHBOF.txt:4963) + +Expected client return: `` System.Collections.Generic.List`1 `` (array) +Resolved DTO: `CCEOLAOLEKJ` from `CCEOLAOLEKJ.cs` +Declaration: `public class CCEOLAOLEKJ : IFAIJAGLDFK` +Client parser JSON keys: `accountId`, `profileImage`, `isJunior`, `platforms`, `username`, `displayName`, `createdAt` +Public/decompiled members: +- `DateTime ACBFDMLHFPB` +- `ObscuredBool AEMHDANDFOP` +- `ObscuredString BIOGKFGIMDG` +- `ObscuredString CFJLJOKIJCN` +- `ObscuredInt GAINIOENNCG` +- `ObscuredString IHADBCJNDIP` +- `ENNEGEELGMC ILOADNHKMHK` +- `ObscuredBool JIIMAIDFJPN` +- `ObscuredString OOJFBECGEAD` + +## account / account/bulk/ + +- `PEGGCEDHBOF` `` RecRoom.Async.IPromise`1> NGENLHMLHNG(System.Collections.Generic.List`1 BGELOMIEKFK, System.String MAFJOJLDFJO, System.String LOOAPLFGOEN, System.String PJIBCCFFMNJ, GPOOLJODEGM OLBDHLLJJHF) `` (PEGGCEDHBOF.txt:9080) + +Expected client return: `` System.Collections.Generic.List`1 `` (array) +Resolved DTO: `DCGAMCKNJDB` from `DCGAMCKNJDB.cs` +Declaration: `public class DCGAMCKNJDB` +Public/decompiled members: +- `GPOOLJODEGM DINFEKALAJO` +- `CCEOLAOLEKJ DJMJFEJGIHM` +- `string MCHJBOBNFLN` + +## account / account/create + +- `PEGGCEDHBOF` `` RecRoom.Async.IPromise`1 HHFPNFANNEG() `` (PEGGCEDHBOF.txt:5418) + +Expected client return: `CCEOLAOLEKJ` (object) +Resolved DTO: `CCEOLAOLEKJ` from `CCEOLAOLEKJ.cs` +Declaration: `public class CCEOLAOLEKJ : IFAIJAGLDFK` +Client parser JSON keys: `accountId`, `profileImage`, `isJunior`, `platforms`, `username`, `displayName`, `createdAt` +Public/decompiled members: +- `DateTime ACBFDMLHFPB` +- `ObscuredBool AEMHDANDFOP` +- `ObscuredString BIOGKFGIMDG` +- `ObscuredString CFJLJOKIJCN` +- `ObscuredInt GAINIOENNCG` +- `ObscuredString IHADBCJNDIP` +- `ENNEGEELGMC ILOADNHKMHK` +- `ObscuredBool JIIMAIDFJPN` +- `ObscuredString OOJFBECGEAD` + +## account / account/me + +- `PEGGCEDHBOF` `` RecRoom.Async.IPromise`1 HJFCFNCKDFO() `` (PEGGCEDHBOF.txt:3576) + +Expected client return: `JJGHAFKJBEI` (object) +Resolved DTO: `JJGHAFKJBEI` from `JJGHAFKJBEI.cs` +Declaration: `public class JJGHAFKJBEI : CCEOLAOLEKJ` +Inherits: `CCEOLAOLEKJ` +Client parser JSON keys: `email`, `phone`, `juniorState`, `parentAccountId`, `availableUsernameChanges` +Inherited parser JSON keys: `accountId`, `profileImage`, `isJunior`, `platforms`, `username`, `displayName`, `createdAt` +Public/decompiled members: +- `bool AFBNICLHPBN` +- `bool CNJIJKAHHCG` +- `Nullable DKFIDIDDHNO` +- `Nullable FHKEMGJCOND` +- `int LNMPGOOPELO` +- `ObscuredString MCDGMOKGEPJ` +- `ObscuredString MDHCHOLIJEF` +- `CINICOCNPFB ONBONOFFGFC` +- `DateTime ACBFDMLHFPB` (inherited from `CCEOLAOLEKJ`) +- `ObscuredBool AEMHDANDFOP` (inherited from `CCEOLAOLEKJ`) +- `ObscuredString BIOGKFGIMDG` (inherited from `CCEOLAOLEKJ`) +- `ObscuredString CFJLJOKIJCN` (inherited from `CCEOLAOLEKJ`) +- `ObscuredInt GAINIOENNCG` (inherited from `CCEOLAOLEKJ`) +- `ObscuredString IHADBCJNDIP` (inherited from `CCEOLAOLEKJ`) +- `ENNEGEELGMC ILOADNHKMHK` (inherited from `CCEOLAOLEKJ`) +- `ObscuredBool JIIMAIDFJPN` (inherited from `CCEOLAOLEKJ`) +- `ObscuredString OOJFBECGEAD` (inherited from `CCEOLAOLEKJ`) + +## account / account/me/ + +- `PEGGCEDHBOF` `RecRoom.Async.IPromise OEBEELJDOHE(BestHTTP.HTTPMethods APAICGIHAGJ, BestHTTP.Forms.HTTPUrlEncodedForm MDOPLMHIKLP, System.String LOOAPLFGOEN)` (PEGGCEDHBOF.txt:6704) + +Expected client return: `RecRoom.Async.IPromise` (success-or-empty) +Resolved DTO: `void/success` not found in readable C# dump. + +## account / account/me/changepassword + +- `MHOKOMMOGKM` `RecRoom.Async.IPromise PGJBIIPMMJP(BestHTTP.Forms.HTTPUrlEncodedForm MDOPLMHIKLP, System.String MKIPICKCFDM)` (MHOKOMMOGKM.txt:2582) + +Expected client return: `RecRoom.Async.IPromise` (success-or-empty) +Resolved DTO: `void/success` not found in readable C# dump. + +## account / account/me/haspassword + +- `MHOKOMMOGKM` `` RecRoom.Async.IPromise`1 PPKFJEINCCN() `` (MHOKOMMOGKM.txt:2203) +- `MHOKOMMOGKM+<>c` `System.Void b__32_0()` (MHOKOMMOGKM_NestedType___c.txt:584) + +Expected client return: `System.Boolean` (primitive) +Resolved DTO: `boolean` not found in readable C# dump. + +Expected client return: `unknown` (unknown) +Resolved DTO: `unknown` not found in readable C# dump. + +## account / account/recoverpassword + +- `MHOKOMMOGKM` `RecRoom.Async.IPromise PMKEHDEDNCK(System.String LJKKKECANAB)` (MHOKOMMOGKM.txt:2779) + +Expected client return: `RecRoom.Async.IPromise` (success-or-empty) +Resolved DTO: `void/success` not found in readable C# dump. + +## account / account/search?name= + +- `PEGGCEDHBOF` `` RecRoom.Async.IPromise`1> IBDGAKHBIAJ(System.String CNBKKCJAHPP) `` (PEGGCEDHBOF.txt:9372) + +Expected client return: `` System.Collections.Generic.List`1 `` (array) +Resolved DTO: `CCEOLAOLEKJ` from `CCEOLAOLEKJ.cs` +Declaration: `public class CCEOLAOLEKJ : IFAIJAGLDFK` +Client parser JSON keys: `accountId`, `profileImage`, `isJunior`, `platforms`, `username`, `displayName`, `createdAt` +Public/decompiled members: +- `DateTime ACBFDMLHFPB` +- `ObscuredBool AEMHDANDFOP` +- `ObscuredString BIOGKFGIMDG` +- `ObscuredString CFJLJOKIJCN` +- `ObscuredInt GAINIOENNCG` +- `ObscuredString IHADBCJNDIP` +- `ENNEGEELGMC ILOADNHKMHK` +- `ObscuredBool JIIMAIDFJPN` +- `ObscuredString OOJFBECGEAD` + +## activities / api/activities/charades/v1/words + +- `CardBox` `` RecRoom.Async.IPromise`1> HMNGGFKCDGD() `` (CardBox.txt:2422) +- `CardBox` `System.Void Start()` (CardBox.txt:2221) + +Expected client return: `` System.Collections.Generic.List`1 `` (array) +Resolved DTO: `JOHPEJAMBHG` from `JOHPEJAMBHG.cs` +Declaration: `public class JOHPEJAMBHG : IFAIJAGLDFK` +Client parser JSON keys: `EN_US`, `Difficulty` +Public/decompiled members: +- `enum CJPFKEJAOBI` +- `CJPFKEJAOBI GBLNDMFIGNM` +- `string OIIMKMHNCMN` + +Expected client return: `unknown` (unknown) +Resolved DTO: `unknown` not found in readable C# dump. + +## activities / api/challenge/ + +- `PDMJMMMNGJE` `System.Collections.IEnumerator EOBJGALHFNP(BPHGKAEDBPE+OJDJIJDNFHE AFLPGGJMPOE)` (PDMJMMMNGJE.txt:156) +- `PDMJMMMNGJE` `System.Void EKJPOJFLGFO(System.Int32 DKDDEIHEIBL, COLBJJAIEIO FMIIMOCIHCD)` (PDMJMMMNGJE.txt:395) + +Expected client return: `IEnumerator` (callback-or-coroutine) +Resolved DTO: `callback` not found in readable C# dump. + +Expected client return: `unknown` (unknown) +Resolved DTO: `unknown` not found in readable C# dump. + +## activities / api/royale/ + +- `JDFDLJJGHIP` `` RecRoom.Async.IPromise`1 APOAEDMOIHO() `` (JDFDLJJGHIP.txt:266) +- `JDFDLJJGHIP` `` RecRoom.Async.IPromise`1 NGEFKCDJAMF(JDFDLJJGHIP+MatchCompleteStats CNALBEPOKJJ) `` (JDFDLJJGHIP.txt:477) + +Expected client return: `JDFDLJJGHIP+GHNGAENLIHA` (object) +Resolved DTO: `GHNGAENLIHA` from `JDFDLJJGHIP.cs` +Declaration: `internal class GHNGAENLIHA : IFAIJAGLDFK` +Client parser JSON keys: `TotalXP`, `Level`, `RankIdx`, `RankName`, `CurrentLevelXPThreshold`, `NextLevelXPThreshold`, `NextLevelAcornReward` +Public/decompiled members: +- `string AGCAAIBPPIJ` +- `long CBKDFHNBDEM` +- `int COAHPEGOOEB` +- `long COLADBAICGE` +- `int GMAJAGFHAPO` +- `int LINLLHBFIKF` +- `long MPIAOHJIBOL` + +Expected client return: `JDFDLJJGHIP+JLNAMKBKPNG` (object) +Resolved DTO: `JLNAMKBKPNG` from `JDFDLJJGHIP.cs` +Declaration: `internal class JLNAMKBKPNG : IFAIJAGLDFK` +Client parser JSON keys: `TotalXPAwarded` +Public/decompiled members: +- `List AGLPLKLAGKC` +- `long HBANCPAIDGN` +- `List HNBFKOEFCIE` + +## avatar / {0}v2/gifts/consume/ + +- `NLEKGNENMCO` `RecRoom.Async.IPromise PCKICLJFOBO(NLEKGNENMCO+LOCNECLOHCA HBBAANIPIMP, BPHGKAEDBPE+CBEOHBCIPEA AFLPGGJMPOE)` (NLEKGNENMCO.txt:2488) + +Expected client return: `RecRoom.Async.IPromise` (success-or-empty) +Resolved DTO: `void/success` not found in readable C# dump. + +## avatar / api/avatar/ + +- `NLEKGNENMCO` `RecRoom.Async.IPromise DLDENNPGADN()` (NLEKGNENMCO.txt:3480) +- `NLEKGNENMCO` `RecRoom.Async.IPromise PCKICLJFOBO(NLEKGNENMCO+LOCNECLOHCA HBBAANIPIMP, BPHGKAEDBPE+CBEOHBCIPEA AFLPGGJMPOE)` (NLEKGNENMCO.txt:2486) +- `NLEKGNENMCO` `` RecRoom.Async.IPromise`1 JEKOPJCCHKB(GiftManager+LCLKAFOPBLD LHOMKMINCHH, System.Nullable`1 PCCLFNLJAMG, System.Boolean DKHOCHIJLBG) `` (NLEKGNENMCO.txt:1504) +- `NLEKGNENMCO` `` RecRoom.Async.IPromise`1 JEKOPJCCHKB(GiftManager+LCLKAFOPBLD LHOMKMINCHH) `` (NLEKGNENMCO.txt:1794) +- `NLEKGNENMCO` `` RecRoom.Async.IPromise`1> EPOGKGLKJHD() `` (NLEKGNENMCO.txt:1226) +- `NLEKGNENMCO` `System.Collections.IEnumerator FHJKJMFABGK(BPHGKAEDBPE+CBEOHBCIPEA AFLPGGJMPOE)` (NLEKGNENMCO.txt:3101) +- `NLEKGNENMCO` `System.Collections.IEnumerator KLHANCHHJBD(CDNNKFCCONN GKDEAOJLJFI, BPHGKAEDBPE+CBEOHBCIPEA AFLPGGJMPOE)` (NLEKGNENMCO.txt:3229) +- `NLEKGNENMCO` `System.Void OFJLGPLDEKJ()` (NLEKGNENMCO.txt:3748) +- `NLEKGNENMCO+LFBBKJKCJFF` `System.Boolean MoveNext()` (NLEKGNENMCO_NestedType_LFBBKJKCJFF.txt:147) + +Expected client return: `RecRoom.Async.IPromise` (success-or-empty) +Resolved DTO: `void/success` not found in readable C# dump. + +Expected client return: `NLEKGNENMCO+LOCNECLOHCA` (object) +Resolved DTO: `LOCNECLOHCA` from `NLEKGNENMCO.cs` +Declaration: `internal class LOCNECLOHCA : IFAIJAGLDFK, AKJKEMONOIL` +Client parser JSON keys: `Message`, `Id`, `FromPlayerId`, `AvatarItemType`, `AvatarItemDesc`, `ConsumableItemDesc`, `EquipmentPrefabName`, `EquipmentModificationGuid`, `Platform`, `PlatformsToSpawnOn`, `BalanceType`, `CurrencyType`, `Currency`, `Xp`, `GiftRarity`, `GiftContext` +Public/decompiled members: +- `Nullable AJIOGFIIAPG` +- `Material BFLGAPOCLAJ` +- `string BGKNIPNNCJG` +- `LCLKAFOPBLD CPNPKDLCDOO` +- `string DFPPNHINEFO` +- `bool DLHOLKNMICH` +- `bool DMAJINBPHKJ` +- `bool FBLEJONFPAK` +- `string GBNGBHCOPFO` +- `string HAKADJAKPDO` +- `bool HEKHNHAJGIN` +- `int HOAJNDDEDAM` +- `ACDKILABNNC IILDJGJNFIM` +- `GiftPackageVariant JOPFCBEDAAD` +- `long JPOHGBCEJEJ` +- `ENNEGEELGMC KLLFPPFHNKF` +- `Nullable LKFAKLCIINB` +- `string LLEJOHGPNAE` +- `bool LMNGODEMEHA` +- `Nullable MPIODIBAMDO` +- `bool NOEDKAJMLKN` +- `MCEEFCNMNAH NPOIFNDCBAN` +- `int PDACCOLOMCB` +- `string PDEHPKNHLBL` +- `FCIKKFJOMNO PFJIBIPNDCA` +- `bool CFINKBCLFJF` + +Expected client return: `` System.Collections.Generic.List`1 `` (array) +Resolved DTO: `LOCNECLOHCA` from `NLEKGNENMCO.cs` +Declaration: `internal class LOCNECLOHCA : IFAIJAGLDFK, AKJKEMONOIL` +Client parser JSON keys: `Message`, `Id`, `FromPlayerId`, `AvatarItemType`, `AvatarItemDesc`, `ConsumableItemDesc`, `EquipmentPrefabName`, `EquipmentModificationGuid`, `Platform`, `PlatformsToSpawnOn`, `BalanceType`, `CurrencyType`, `Currency`, `Xp`, `GiftRarity`, `GiftContext` +Public/decompiled members: +- `Nullable AJIOGFIIAPG` +- `Material BFLGAPOCLAJ` +- `string BGKNIPNNCJG` +- `LCLKAFOPBLD CPNPKDLCDOO` +- `string DFPPNHINEFO` +- `bool DLHOLKNMICH` +- `bool DMAJINBPHKJ` +- `bool FBLEJONFPAK` +- `string GBNGBHCOPFO` +- `string HAKADJAKPDO` +- `bool HEKHNHAJGIN` +- `int HOAJNDDEDAM` +- `ACDKILABNNC IILDJGJNFIM` +- `GiftPackageVariant JOPFCBEDAAD` +- `long JPOHGBCEJEJ` +- `ENNEGEELGMC KLLFPPFHNKF` +- `Nullable LKFAKLCIINB` +- `string LLEJOHGPNAE` +- `bool LMNGODEMEHA` +- `Nullable MPIODIBAMDO` +- `bool NOEDKAJMLKN` +- `MCEEFCNMNAH NPOIFNDCBAN` +- `int PDACCOLOMCB` +- `string PDEHPKNHLBL` +- `FCIKKFJOMNO PFJIBIPNDCA` +- `bool CFINKBCLFJF` + +Expected client return: `IEnumerator` (callback-or-coroutine) +Resolved DTO: `callback` not found in readable C# dump. + +Expected client return: `unknown` (unknown) +Resolved DTO: `unknown` not found in readable C# dump. + +## avatar / api/avatar/v1/lockeditems? + +- `NLEKGNENMCO` `` RecRoom.Async.IPromise`1> KLPMNFCFOLE(System.Collections.Generic.List`1 EGLBFNGKDLD) `` (NLEKGNENMCO.txt:2931) + +Expected client return: `` System.Collections.Generic.List`1 `` (array) +Resolved DTO: `EPFHLDCPAOK` from `NLEKGNENMCO.cs` +Declaration: `internal class EPFHLDCPAOK : IFAIJAGLDFK` +Client parser JSON keys: `AvatarItemType`, `AvatarItemDesc`, `FriendlyName`, `Tooltip`, `Rarity` +Public/decompiled members: +- `JHDGJFEOGGA AJIOGFIIAPG` +- `MCEEFCNMNAH EPBGPFCIOMJ` +- `string FAHDIMDEHEK` +- `string KENGPMDDCML` +- `string MAMPKNECKCM` + +## bug-reporting / api/bugreporting/ + +- `RecNet.BugReporting` `RecRoom.Async.IPromise ReportBug(System.String summary, System.String description, System.Byte[] screenshotData, System.Byte[] outputLogData, System.String testCaseKey)` (RecNet\BugReporting.txt:146) + +Expected client return: `RecRoom.Async.IPromise` (success-or-empty) +Resolved DTO: `void/success` not found in readable C# dump. + +## clubs / {0}/club/{1} + +- `GNPDMBPGHBH` `System.String JDEIGGFCBPF(System.Int64 AOMBLGBCENO)` (GNPDMBPGHBH.txt:630) + +Expected client return: `unknown` (unknown) +Resolved DTO: `unknown` not found in readable C# dump. + +## clubs / announcements/club/{0} + +- `JDJGIBLMFKK` `` RecRoom.Async.IPromise`1 ONCKANJNGLA(System.Int64 AOMBLGBCENO) `` (JDJGIBLMFKK.txt:1167) +- `JDJGIBLMFKK` `` RecRoom.Async.IPromise`1 AAOLBAJFCOJ(JDJGIBLMFKK+DHPONMGBFJE JMFLHIIJFKL) `` (JDJGIBLMFKK.txt:398) + +Expected client return: `HPACLJHLHBG` (object) +Resolved DTO: `HPACLJHLHBG` from `HPACLJHLHBG.cs` +Declaration: `public class HPACLJHLHBG : IFAIJAGLDFK` +Client parser JSON keys: `clubId`, `LastReadAnnouncementId` +Public/decompiled members: +- `long CCGOEDABKNN` +- `bool IILPDPCODAE` +- `long JCCDJAADFPE` + +Expected client return: `System.Int64` (primitive) +Resolved DTO: `number` not found in readable C# dump. + +## clubs / announcements/club/{0}/{1} + +- `JDJGIBLMFKK` `RecRoom.Async.IPromise HCGFMHLEDPE(System.Int64 AOMBLGBCENO, System.Int64 LOHFFDEGIMK)` (JDJGIBLMFKK.txt:2462) +- `JDJGIBLMFKK` `RecRoom.Async.IPromise POHLPICNHJD(JDJGIBLMFKK+FOEPIDJCLMC JMFLHIIJFKL)` (JDJGIBLMFKK.txt:789) + +Expected client return: `RecRoom.Async.IPromise` (success-or-empty) +Resolved DTO: `void/success` not found in readable C# dump. + +## clubs / announcements/club/{0}/{1}/read + +- `JDJGIBLMFKK` `RecRoom.Async.IPromise OFBNAHIOHON(System.Int64 AOMBLGBCENO, System.Int64 LOHFFDEGIMK)` (JDJGIBLMFKK.txt:2865) + +Expected client return: `RecRoom.Async.IPromise` (success-or-empty) +Resolved DTO: `void/success` not found in readable C# dump. + +## clubs / api/clubreporting/v1/report + +- `JDJGIBLMFKK` `RecRoom.Async.IPromise LBOOMIDNLPP(System.Int64 AOMBLGBCENO, LCCEEFHOBEN+CJFENPHAAHI MEABFEIBEMP, System.String EFDBFLPKHKA)` (JDJGIBLMFKK.txt:21791) + +Expected client return: `RecRoom.Async.IPromise` (success-or-empty) +Resolved DTO: `void/success` not found in readable C# dump. + +## clubs / club/{0} + +- `JDJGIBLMFKK` `RecRoom.Async.IPromise IGHDIAEPHJD(System.Int64 AOMBLGBCENO, System.String DHANCEHHIDH)` (JDJGIBLMFKK.txt:13088) +- `JDJGIBLMFKK` `` RecRoom.Async.IPromise`1 NKGOFNILGPL(System.Int64 AOMBLGBCENO, System.Boolean OHLIGBELLLH = True) `` (JDJGIBLMFKK.txt:10425) + +Expected client return: `RecRoom.Async.IPromise` (success-or-empty) +Resolved DTO: `void/success` not found in readable C# dump. + +Expected client return: `PLILLKHMNDA` (object) +Resolved DTO: `PLILLKHMNDA` from `PLILLKHMNDA.cs` +Declaration: `public class PLILLKHMNDA : IFAIJAGLDFK, IEquatable` +Client parser JSON keys: `ClubId`, `Name`, `Description`, `MainImageName`, `State`, `CreatorAccountId`, `Category`, `Visibility`, `Joinability`, `AllowJuniors`, `MemberCount`, `IsRRO`, `ClubType` +Public/decompiled members: +- `int BADIGBCKECA` +- `long CCGOEDABKNN` +- `JCMLDDKFKEO CDINMMPNAID` +- `bool CDNFGMHLDMJ` +- `int EEAOJCGAOCN` +- `string EHOLKJPEGFF` +- `JCDEFCJLCHN EPGALOMHHMI` +- `string FIKEBGGCDFN` +- `Nullable HHCGNCLFKDM` +- `bool IPKLLFAJJPJ` +- `string KODBEJPEFOJ` +- `string LNGPBGCIAPP` +- `DIGMAIMMHAP MNEEEGHOGAB` +- `PJCALHOPMKJ PHBCFAJILGD` + +## clubs / club/{0}/additionalimage/{1} + +- `JDJGIBLMFKK` `` RecRoom.Async.IPromise`1 ELLIKHHEJFM(System.Int64 AOMBLGBCENO, System.Int32 EFBDCIJMFGD, System.String HFLPBHHAFIO) `` (JDJGIBLMFKK.txt:12298) + +Expected client return: `PIHMJGCGNLP` (object) +Resolved DTO: `PIHMJGCGNLP` from `PIHMJGCGNLP.cs` +Declaration: `public class PIHMJGCGNLP : IFAIJAGLDFK` +Client parser JSON keys: `Club`, `CoownerPermissions`, `ModeratorPermissions`, `MemberPermissions`, `MyMembershipType` +Public/decompiled members: +- `PPGPAHNMGEC AHDBBFIDKBN` +- `JHEEFBMODPG CMGPCPKLHLF` +- `List DOOHAKMALHL` +- `JHEEFBMODPG IIIFDCAPMEA` +- `JHEEFBMODPG KIFHEKPKILL` +- `PLILLKHMNDA NDAGAGNHNPA` +- `JHEEFBMODPG NJHNHHMCILD` +- `List OPIIBPFEODL` + +## clubs / club/{0}/clubhouse + +- `JDJGIBLMFKK` `` RecRoom.Async.IPromise`1 HELJLMINDFD(System.Int64 AOMBLGBCENO, System.Nullable`1 HNHLJONGKHB) `` (JDJGIBLMFKK.txt:12449) + +Expected client return: `PIHMJGCGNLP` (object) +Resolved DTO: `PIHMJGCGNLP` from `PIHMJGCGNLP.cs` +Declaration: `public class PIHMJGCGNLP : IFAIJAGLDFK` +Client parser JSON keys: `Club`, `CoownerPermissions`, `ModeratorPermissions`, `MemberPermissions`, `MyMembershipType` +Public/decompiled members: +- `PPGPAHNMGEC AHDBBFIDKBN` +- `JHEEFBMODPG CMGPCPKLHLF` +- `List DOOHAKMALHL` +- `JHEEFBMODPG IIIFDCAPMEA` +- `JHEEFBMODPG KIFHEKPKILL` +- `PLILLKHMNDA NDAGAGNHNPA` +- `JHEEFBMODPG NJHNHHMCILD` +- `List OPIIBPFEODL` + +## clubs / club/{0}/details + +- `JDJGIBLMFKK+BKCHBCIJHBN` `` RecRoom.Async.IPromise`1 b__0() `` (JDJGIBLMFKK_NestedType_BKCHBCIJHBN.txt:202) + +Expected client return: `PIHMJGCGNLP` (object) +Resolved DTO: `PIHMJGCGNLP` from `PIHMJGCGNLP.cs` +Declaration: `public class PIHMJGCGNLP : IFAIJAGLDFK` +Client parser JSON keys: `Club`, `CoownerPermissions`, `ModeratorPermissions`, `MemberPermissions`, `MyMembershipType` +Public/decompiled members: +- `PPGPAHNMGEC AHDBBFIDKBN` +- `JHEEFBMODPG CMGPCPKLHLF` +- `List DOOHAKMALHL` +- `JHEEFBMODPG IIIFDCAPMEA` +- `JHEEFBMODPG KIFHEKPKILL` +- `PLILLKHMNDA NDAGAGNHNPA` +- `JHEEFBMODPG NJHNHHMCILD` +- `List OPIIBPFEODL` + +## clubs / club/{0}/mainimage + +- `JDJGIBLMFKK` `` RecRoom.Async.IPromise`1 OBFKAFGALDM(System.Int64 AOMBLGBCENO, System.String HFLPBHHAFIO) `` (JDJGIBLMFKK.txt:12138) + +Expected client return: `PIHMJGCGNLP` (object) +Resolved DTO: `PIHMJGCGNLP` from `PIHMJGCGNLP.cs` +Declaration: `public class PIHMJGCGNLP : IFAIJAGLDFK` +Client parser JSON keys: `Club`, `CoownerPermissions`, `ModeratorPermissions`, `MemberPermissions`, `MyMembershipType` +Public/decompiled members: +- `PPGPAHNMGEC AHDBBFIDKBN` +- `JHEEFBMODPG CMGPCPKLHLF` +- `List DOOHAKMALHL` +- `JHEEFBMODPG IIIFDCAPMEA` +- `JHEEFBMODPG KIFHEKPKILL` +- `PLILLKHMNDA NDAGAGNHNPA` +- `JHEEFBMODPG NJHNHHMCILD` +- `List OPIIBPFEODL` + +## clubs / club/{0}/members/{1} + +- `JDJGIBLMFKK` `` RecRoom.Async.IPromise`1 BIOJPNFPNCE(System.Int64 AOMBLGBCENO, System.Int32 GKLPIFBPGOD) `` (JDJGIBLMFKK.txt:15776) + +Expected client return: `MFOAODGNGKB` (object) +Resolved DTO: `MFOAODGNGKB` from `MFOAODGNGKB.cs` +Declaration: `public class MFOAODGNGKB : IFAIJAGLDFK` +Client parser JSON keys: `AccountId`, `ClubId`, `MembershipType`, `CreatedAt` +Public/decompiled members: +- `DateTime ACBFDMLHFPB` +- `long CCGOEDABKNN` +- `int GAINIOENNCG` +- `PPGPAHNMGEC JNMPJNKEJAC` + +## clubs / club/{0}/members/acceptinvite + +- `JDGDFALBCDJ` `RecRoom.Async.IPromise HHLPPOBHMOC()` (JDGDFALBCDJ.txt:426) +- `JDJGIBLMFKK` `RecRoom.Async.IPromise FAOINNCBNFC(System.Int64 AOMBLGBCENO)` (JDJGIBLMFKK.txt:20586) + +Expected client return: `RecRoom.Async.IPromise` (success-or-empty) +Resolved DTO: `void/success` not found in readable C# dump. + +## clubs / club/{0}/members/acceptrequest + +- `JDJGIBLMFKK` `RecRoom.Async.IPromise AGEGBKKPJNN(System.Int64 AOMBLGBCENO, System.Int32 GKLPIFBPGOD)` (JDJGIBLMFKK.txt:18418) + +Expected client return: `RecRoom.Async.IPromise` (success-or-empty) +Resolved DTO: `void/success` not found in readable C# dump. + +## clubs / club/{0}/members/acceptrequests + +- `JDJGIBLMFKK` `` RecRoom.Async.IPromise FKPIMLCILLI(System.Int64 AOMBLGBCENO, System.Collections.Generic.IEnumerable`1 ILNGMAANNDG) `` (JDJGIBLMFKK.txt:18831) + +Expected client return: `RecRoom.Async.IPromise` (success-or-empty) +Resolved DTO: `void/success` not found in readable C# dump. + +## clubs / club/{0}/members/ban + +- `JDJGIBLMFKK` `RecRoom.Async.IPromise OKICAHCMPFL(System.Int64 AOMBLGBCENO, System.Int32 GKLPIFBPGOD)` (JDJGIBLMFKK.txt:21265) + +Expected client return: `RecRoom.Async.IPromise` (success-or-empty) +Resolved DTO: `void/success` not found in readable C# dump. + +## clubs / club/{0}/members/changetype + +- `JDJGIBLMFKK` `RecRoom.Async.IPromise JLBCCEIBFJJ(System.Int64 AOMBLGBCENO, System.Int32 GKLPIFBPGOD, PPGPAHNMGEC OCHEGLOFMEA)` (JDJGIBLMFKK.txt:21094) + +Expected client return: `RecRoom.Async.IPromise` (success-or-empty) +Resolved DTO: `void/success` not found in readable C# dump. + +## clubs / club/{0}/members/declineinvite + +- `JDGDFALBCDJ` `RecRoom.Async.IPromise BDFBMBCKJEP()` (JDGDFALBCDJ.txt:697) +- `JDJGIBLMFKK` `RecRoom.Async.IPromise GJJBKCEMLDC(System.Int64 AOMBLGBCENO)` (JDJGIBLMFKK.txt:20675) + +Expected client return: `RecRoom.Async.IPromise` (success-or-empty) +Resolved DTO: `void/success` not found in readable C# dump. + +## clubs / club/{0}/members/denyrequest + +- `JDJGIBLMFKK` `RecRoom.Async.IPromise LPCHODIHIBB(System.Int64 AOMBLGBCENO, System.Int32 GKLPIFBPGOD)` (JDJGIBLMFKK.txt:19048) + +Expected client return: `RecRoom.Async.IPromise` (success-or-empty) +Resolved DTO: `void/success` not found in readable C# dump. + +## clubs / club/{0}/members/denyrequests + +- `JDJGIBLMFKK` `` RecRoom.Async.IPromise CJBOECCIIJB(System.Int64 AOMBLGBCENO, System.Collections.Generic.IEnumerable`1 ILNGMAANNDG) `` (JDJGIBLMFKK.txt:19461) + +Expected client return: `RecRoom.Async.IPromise` (success-or-empty) +Resolved DTO: `void/success` not found in readable C# dump. + +## clubs / club/{0}/members/directJoin + +- `JDJGIBLMFKK` `RecRoom.Async.IPromise AJCDOLIBBKC(System.Int64 AOMBLGBCENO, PJCALHOPMKJ IEGCAIGJBBP, System.Int32 ENGEEKIMIGO)` (JDJGIBLMFKK.txt:20450) + +Expected client return: `RecRoom.Async.IPromise` (success-or-empty) +Resolved DTO: `void/success` not found in readable C# dump. + +## clubs / club/{0}/members/invite + +- `JDJGIBLMFKK` `RecRoom.Async.IPromise FEIADJMHICD(System.Int64 AOMBLGBCENO, System.Int32 GKLPIFBPGOD, PPGPAHNMGEC PGMGFKOCEDG)` (JDJGIBLMFKK.txt:19703) + +Expected client return: `RecRoom.Async.IPromise` (success-or-empty) +Resolved DTO: `void/success` not found in readable C# dump. + +## clubs / club/{0}/members/invitemembers + +- `JDJGIBLMFKK` `` RecRoom.Async.IPromise HOHPKNPHCFK(System.Int64 AOMBLGBCENO, System.Collections.Generic.List`1 ILNGMAANNDG, OFIEEDOMGPA NAMECJCFEDN) `` (JDJGIBLMFKK.txt:20070) + +Expected client return: `RecRoom.Async.IPromise` (success-or-empty) +Resolved DTO: `void/success` not found in readable C# dump. + +## clubs / club/{0}/members/leave + +- `JDJGIBLMFKK` `RecRoom.Async.IPromise EBADBEIGOEN(System.Int64 AOMBLGBCENO)` (JDJGIBLMFKK.txt:20745) + +Expected client return: `RecRoom.Async.IPromise` (success-or-empty) +Resolved DTO: `void/success` not found in readable C# dump. + +## clubs / club/{0}/members/remove + +- `JDJGIBLMFKK` `RecRoom.Async.IPromise LDOGPIHJOLP(System.Int64 AOMBLGBCENO, System.Int32 GKLPIFBPGOD)` (JDJGIBLMFKK.txt:20895) + +Expected client return: `RecRoom.Async.IPromise` (success-or-empty) +Resolved DTO: `void/success` not found in readable C# dump. + +## clubs / club/{0}/members/requesttojoin + +- `JDJGIBLMFKK` `RecRoom.Async.IPromise NJKBIMEEAPE(System.Int64 AOMBLGBCENO)` (JDJGIBLMFKK.txt:18249) + +Expected client return: `RecRoom.Async.IPromise` (success-or-empty) +Resolved DTO: `void/success` not found in readable C# dump. + +## clubs / club/{0}/members/unban + +- `JDJGIBLMFKK` `RecRoom.Async.IPromise OGMGOBHPMFC(System.Int64 AOMBLGBCENO, System.Int32 GKLPIFBPGOD)` (JDJGIBLMFKK.txt:21435) + +Expected client return: `RecRoom.Async.IPromise` (success-or-empty) +Resolved DTO: `void/success` not found in readable C# dump. + +## clubs / club/{0}/modify + +- `JDJGIBLMFKK` `` RecRoom.Async.IPromise`1 CKJIKPOIMFE(JDJGIBLMFKK+ACNILMIFDJJ JMFLHIIJFKL) `` (JDJGIBLMFKK.txt:11620) + +Expected client return: `PIHMJGCGNLP` (object) +Resolved DTO: `PIHMJGCGNLP` from `PIHMJGCGNLP.cs` +Declaration: `public class PIHMJGCGNLP : IFAIJAGLDFK` +Client parser JSON keys: `Club`, `CoownerPermissions`, `ModeratorPermissions`, `MemberPermissions`, `MyMembershipType` +Public/decompiled members: +- `PPGPAHNMGEC AHDBBFIDKBN` +- `JHEEFBMODPG CMGPCPKLHLF` +- `List DOOHAKMALHL` +- `JHEEFBMODPG IIIFDCAPMEA` +- `JHEEFBMODPG KIFHEKPKILL` +- `PLILLKHMNDA NDAGAGNHNPA` +- `JHEEFBMODPG NJHNHHMCILD` +- `List OPIIBPFEODL` + +## clubs / club/{0}/modifydetails + +- `JDJGIBLMFKK` `` RecRoom.Async.IPromise`1 KOPAANDJNKB(JDJGIBLMFKK+FHFOCPGBMPB JMFLHIIJFKL) `` (JDJGIBLMFKK.txt:12001) + +Expected client return: `PIHMJGCGNLP` (object) +Resolved DTO: `PIHMJGCGNLP` from `PIHMJGCGNLP.cs` +Declaration: `public class PIHMJGCGNLP : IFAIJAGLDFK` +Client parser JSON keys: `Club`, `CoownerPermissions`, `ModeratorPermissions`, `MemberPermissions`, `MyMembershipType` +Public/decompiled members: +- `PPGPAHNMGEC AHDBBFIDKBN` +- `JHEEFBMODPG CMGPCPKLHLF` +- `List DOOHAKMALHL` +- `JHEEFBMODPG IIIFDCAPMEA` +- `JHEEFBMODPG KIFHEKPKILL` +- `PLILLKHMNDA NDAGAGNHNPA` +- `JHEEFBMODPG NJHNHHMCILD` +- `List OPIIBPFEODL` + +## clubs / club/{0}/permissions/{1} + +- `JDJGIBLMFKK` `` RecRoom.Async.IPromise`1 GHBJEDCEKLK(JHEEFBMODPG OBJEOMAGODL) `` (JDJGIBLMFKK.txt:13631) + +Expected client return: `PIHMJGCGNLP` (object) +Resolved DTO: `PIHMJGCGNLP` from `PIHMJGCGNLP.cs` +Declaration: `public class PIHMJGCGNLP : IFAIJAGLDFK` +Client parser JSON keys: `Club`, `CoownerPermissions`, `ModeratorPermissions`, `MemberPermissions`, `MyMembershipType` +Public/decompiled members: +- `PPGPAHNMGEC AHDBBFIDKBN` +- `JHEEFBMODPG CMGPCPKLHLF` +- `List DOOHAKMALHL` +- `JHEEFBMODPG IIIFDCAPMEA` +- `JHEEFBMODPG KIFHEKPKILL` +- `PLILLKHMNDA NDAGAGNHNPA` +- `JHEEFBMODPG NJHNHHMCILD` +- `List OPIIBPFEODL` + +## clubs / club/account/{0}/created + +- `JDJGIBLMFKK` `` RecRoom.Async.IPromise`1> DIAPLKMFFKO(System.Int32 GKLPIFBPGOD) `` (JDJGIBLMFKK.txt:9416) +- `JDJGIBLMFKK` `System.String JANFGLBCBMJ(System.Int32 GKLPIFBPGOD)` (JDJGIBLMFKK.txt:7880) +- `JDJGIBLMFKK` `System.Void PJBLNGJKLEB(System.Int64 AOMBLGBCENO)` (JDJGIBLMFKK.txt:26175) + +Expected client return: `` System.Collections.Generic.List`1 `` (array) +Resolved DTO: `PLILLKHMNDA` from `PLILLKHMNDA.cs` +Declaration: `public class PLILLKHMNDA : IFAIJAGLDFK, IEquatable` +Client parser JSON keys: `ClubId`, `Name`, `Description`, `MainImageName`, `State`, `CreatorAccountId`, `Category`, `Visibility`, `Joinability`, `AllowJuniors`, `MemberCount`, `IsRRO`, `ClubType` +Public/decompiled members: +- `int BADIGBCKECA` +- `long CCGOEDABKNN` +- `JCMLDDKFKEO CDINMMPNAID` +- `bool CDNFGMHLDMJ` +- `int EEAOJCGAOCN` +- `string EHOLKJPEGFF` +- `JCDEFCJLCHN EPGALOMHHMI` +- `string FIKEBGGCDFN` +- `Nullable HHCGNCLFKDM` +- `bool IPKLLFAJJPJ` +- `string KODBEJPEFOJ` +- `string LNGPBGCIAPP` +- `DIGMAIMMHAP MNEEEGHOGAB` +- `PJCALHOPMKJ PHBCFAJILGD` + +Expected client return: `unknown` (unknown) +Resolved DTO: `unknown` not found in readable C# dump. + +## clubs / club/categoryTags + +- `JDJGIBLMFKK` `` RecRoom.Async.IPromise`1> PAKNBFOJENP() `` (JDJGIBLMFKK.txt:13314) + +Expected client return: `` System.Collections.Generic.List`1 `` (array) +Resolved DTO: `String` not found in readable C# dump. + +## clubs / club/create + +- `JDJGIBLMFKK` `` RecRoom.Async.IPromise`1 KMPJFFBGJMD(JDJGIBLMFKK+ACNILMIFDJJ JMFLHIIJFKL) `` (JDJGIBLMFKK.txt:11381) + +Expected client return: `PIHMJGCGNLP` (object) +Resolved DTO: `PIHMJGCGNLP` from `PIHMJGCGNLP.cs` +Declaration: `public class PIHMJGCGNLP : IFAIJAGLDFK` +Client parser JSON keys: `Club`, `CoownerPermissions`, `ModeratorPermissions`, `MemberPermissions`, `MyMembershipType` +Public/decompiled members: +- `PPGPAHNMGEC AHDBBFIDKBN` +- `JHEEFBMODPG CMGPCPKLHLF` +- `List DOOHAKMALHL` +- `JHEEFBMODPG IIIFDCAPMEA` +- `JHEEFBMODPG KIFHEKPKILL` +- `PLILLKHMNDA NDAGAGNHNPA` +- `JHEEFBMODPG NJHNHHMCILD` +- `List OPIIBPFEODL` + +## clubs / club/home/me + +- `JDJGIBLMFKK` `` RecRoom.Async.IPromise`1 DBDOOMCNNFE() `` (JDJGIBLMFKK.txt:10003) +- `JDJGIBLMFKK+<>c` `System.Void b__106_0()` (JDJGIBLMFKK_NestedType___c.txt:995) +- `JDJGIBLMFKK+<>c` `System.Void b__106_2()` (JDJGIBLMFKK_NestedType___c.txt:954) + +Expected client return: `PLILLKHMNDA` (object) +Resolved DTO: `PLILLKHMNDA` from `PLILLKHMNDA.cs` +Declaration: `public class PLILLKHMNDA : IFAIJAGLDFK, IEquatable` +Client parser JSON keys: `ClubId`, `Name`, `Description`, `MainImageName`, `State`, `CreatorAccountId`, `Category`, `Visibility`, `Joinability`, `AllowJuniors`, `MemberCount`, `IsRRO`, `ClubType` +Public/decompiled members: +- `int BADIGBCKECA` +- `long CCGOEDABKNN` +- `JCMLDDKFKEO CDINMMPNAID` +- `bool CDNFGMHLDMJ` +- `int EEAOJCGAOCN` +- `string EHOLKJPEGFF` +- `JCDEFCJLCHN EPGALOMHHMI` +- `string FIKEBGGCDFN` +- `Nullable HHCGNCLFKDM` +- `bool IPKLLFAJJPJ` +- `string KODBEJPEFOJ` +- `string LNGPBGCIAPP` +- `DIGMAIMMHAP MNEEEGHOGAB` +- `PJCALHOPMKJ PHBCFAJILGD` + +Expected client return: `unknown` (unknown) +Resolved DTO: `unknown` not found in readable C# dump. + +## clubs / club/mine/created + +- `JDJGIBLMFKK` `` RecRoom.Async.IPromise`1> LJJLGJIMJED() `` (JDJGIBLMFKK.txt:9275) +- `JDJGIBLMFKK` `System.Void PJBLNGJKLEB(System.Int64 AOMBLGBCENO)` (JDJGIBLMFKK.txt:26134) +- `JDJGIBLMFKK` `System.Void PJBLNGJKLEB(System.Int64 AOMBLGBCENO)` (JDJGIBLMFKK.txt:26326) + +Expected client return: `` System.Collections.Generic.List`1 `` (array) +Resolved DTO: `PLILLKHMNDA` from `PLILLKHMNDA.cs` +Declaration: `public class PLILLKHMNDA : IFAIJAGLDFK, IEquatable` +Client parser JSON keys: `ClubId`, `Name`, `Description`, `MainImageName`, `State`, `CreatorAccountId`, `Category`, `Visibility`, `Joinability`, `AllowJuniors`, `MemberCount`, `IsRRO`, `ClubType` +Public/decompiled members: +- `int BADIGBCKECA` +- `long CCGOEDABKNN` +- `JCMLDDKFKEO CDINMMPNAID` +- `bool CDNFGMHLDMJ` +- `int EEAOJCGAOCN` +- `string EHOLKJPEGFF` +- `JCDEFCJLCHN EPGALOMHHMI` +- `string FIKEBGGCDFN` +- `Nullable HHCGNCLFKDM` +- `bool IPKLLFAJJPJ` +- `string KODBEJPEFOJ` +- `string LNGPBGCIAPP` +- `DIGMAIMMHAP MNEEEGHOGAB` +- `PJCALHOPMKJ PHBCFAJILGD` + +Expected client return: `unknown` (unknown) +Resolved DTO: `unknown` not found in readable C# dump. + +## clubs / club/mine/member + +- `JDJGIBLMFKK` `` RecRoom.Async.IPromise`1> DFDMGNBKEKO() `` (JDJGIBLMFKK.txt:9817) +- `JDJGIBLMFKK` `System.Void PJBLNGJKLEB(System.Int64 AOMBLGBCENO)` (JDJGIBLMFKK.txt:26194) +- `JDJGIBLMFKK` `System.Void PJBLNGJKLEB(System.Int64 AOMBLGBCENO)` (JDJGIBLMFKK.txt:26330) + +Expected client return: `` System.Collections.Generic.List`1 `` (array) +Resolved DTO: `PLILLKHMNDA` from `PLILLKHMNDA.cs` +Declaration: `public class PLILLKHMNDA : IFAIJAGLDFK, IEquatable` +Client parser JSON keys: `ClubId`, `Name`, `Description`, `MainImageName`, `State`, `CreatorAccountId`, `Category`, `Visibility`, `Joinability`, `AllowJuniors`, `MemberCount`, `IsRRO`, `ClubType` +Public/decompiled members: +- `int BADIGBCKECA` +- `long CCGOEDABKNN` +- `JCMLDDKFKEO CDINMMPNAID` +- `bool CDNFGMHLDMJ` +- `int EEAOJCGAOCN` +- `string EHOLKJPEGFF` +- `JCDEFCJLCHN EPGALOMHHMI` +- `string FIKEBGGCDFN` +- `Nullable HHCGNCLFKDM` +- `bool IPKLLFAJJPJ` +- `string KODBEJPEFOJ` +- `string LNGPBGCIAPP` +- `DIGMAIMMHAP MNEEEGHOGAB` +- `PJCALHOPMKJ PHBCFAJILGD` + +Expected client return: `unknown` (unknown) +Resolved DTO: `unknown` not found in readable C# dump. + +## clubs / members/bulk + +- `JDJGIBLMFKK` `` RecRoom.Async.IPromise`1> DDIJPHDEDDM(System.Int64 AOMBLGBCENO, System.Collections.Generic.List`1 ILNGMAANNDG) `` (JDJGIBLMFKK.txt:16847) + +Expected client return: `` System.Collections.Generic.List`1 `` (array) +Resolved DTO: `JHMMGLNJHIB` from `JHMMGLNJHIB.cs` +Declaration: `public class JHMMGLNJHIB : IFAIJAGLDFK` +Client parser JSON keys: `AccountId`, `MembershipType` +Public/decompiled members: +- `int GAINIOENNCG` +- `PPGPAHNMGEC JNMPJNKEJAC` + +## clubs / members/bulk? + +- `JDJGIBLMFKK` `` RecRoom.Async.IPromise`1> DDIJPHDEDDM(System.Int64 AOMBLGBCENO, System.Collections.Generic.List`1 ILNGMAANNDG) `` (JDJGIBLMFKK.txt:16893) + +Expected client return: `` System.Collections.Generic.List`1 `` (array) +Resolved DTO: `JHMMGLNJHIB` from `JHMMGLNJHIB.cs` +Declaration: `public class JHMMGLNJHIB : IFAIJAGLDFK` +Client parser JSON keys: `AccountId`, `MembershipType` +Public/decompiled members: +- `int GAINIOENNCG` +- `PPGPAHNMGEC JNMPJNKEJAC` + +## config-settings / /config/{0} + +- `FHLGJDFHOKL` `RecRoom.Async.IPromise MCDNGDMJHBE()` (FHLGJDFHOKL.txt:502) + +Expected client return: `RecRoom.Async.IPromise` (success-or-empty) +Resolved DTO: `void/success` not found in readable C# dump. + +## config-settings / api/config/ + +- `BBEFMHAEEEA` `RecRoom.Async.IPromise BGNDHOHFJBN()` (BBEFMHAEEEA.txt:950) +- `BBEFMHAEEEA` `RecRoom.Async.IPromise OHHAEMHMIFE()` (BBEFMHAEEEA.txt:655) +- `BBEFMHAEEEA` `` RecRoom.Async.IPromise`1> EAJPLMPNPIJ(System.Int32 CKEBCMEKCJH) `` (BBEFMHAEEEA.txt:1147) + +Expected client return: `RecRoom.Async.IPromise` (success-or-empty) +Resolved DTO: `void/success` not found in readable C# dump. + +Expected client return: `` System.Collections.Generic.List`1 `` (array) +Resolved DTO: `FPDADCNKELI` from `FPDADCNKELI.cs` +Declaration: `public class FPDADCNKELI : IFAIJAGLDFK` +Client parser JSON keys: `Version`, `ButtonNumber`, `Override`, `CustomRoomName`, `CustomTitle`, `CustomDescription`, `DefaultRoomName`, `DefaultTitle`, `DefaultDescription` +Public/decompiled members: +- `string BOFMFGLDDAA` +- `string CGIKDDCIDNI` +- `string DBCDHIABGIP` +- `string DDLLPNLMKPP` +- `string EHFBCMOHHAI` +- `int IDPOBNAABIK` +- `string JMDDEHCPHII` +- `FCCPDGMCPBF LLAAHHEBHFP` +- `NJDEOAENIMH LMNDJGOMBNL` + +## config-settings / api/config/v1/freegiftbutton + +- `BBEFMHAEEEA` `` RecRoom.Async.IPromise`1 GMOMCLADPII() `` (BBEFMHAEEEA.txt:1230) + +Expected client return: `System.Boolean` (primitive) +Resolved DTO: `boolean` not found in readable C# dump. + +## config-settings / api/gameconfigs/ + +- `IGCCFMFHBBN` `System.Void .cctor()` (IGCCFMFHBBN.txt:1921) + +Expected client return: `unknown` (unknown) +Resolved DTO: `unknown` not found in readable C# dump. + +## config-settings / api/settings/ + +- `JFDILELKPAL` `` RecRoom.Async.IPromise`1> HOHPJKNPEGK() `` (JFDILELKPAL.txt:48) +- `JFDILELKPAL` `System.Collections.IEnumerator LHGBAFBJAHM(OHLKCDKEGMN AOKDECDAEEG, BPHGKAEDBPE+OJDJIJDNFHE AFLPGGJMPOE)` (JFDILELKPAL.txt:224) + +Expected client return: `` System.Collections.Generic.List`1 `` (array) +Resolved DTO: `OHLKCDKEGMN` from `OHLKCDKEGMN.cs` +Declaration: `public class OHLKCDKEGMN : IFAIJAGLDFK, AKJKEMONOIL` +Client parser JSON keys: `Key`, `Value` +Public/decompiled members: +- `string AKFDMGLACLA` +- `string DMLIOOCLKKP` + +Expected client return: `IEnumerator` (callback-or-coroutine) +Resolved DTO: `callback` not found in readable C# dump. + +## economy / {0}v1/consume + +- `FOEHJBDMMBH` `System.Collections.IEnumerator LCELAOEOIJO(OOCCCGPICOG BAJGNABECMN, System.Int32 FKKAPHAMPMG, BPHGKAEDBPE+CBEOHBCIPEA AFLPGGJMPOE)` (FOEHJBDMMBH.txt:840) + +Expected client return: `IEnumerator` (callback-or-coroutine) +Resolved DTO: `callback` not found in readable C# dump. + +## economy / /consume + +- `COFHGNFJMOG` `` RecRoom.Async.IPromise`1 HDLJGFEDGLH(System.String ENJEOLBEALP, System.Nullable`1 LNBJKLOINED) `` (COFHGNFJMOG.txt:1242) + +Expected client return: `PLDFOPCHHJG` (object) +Resolved DTO: `PLDFOPCHHJG` from `PLDFOPCHHJG.cs` +Declaration: `public class PLDFOPCHHJG : IFAIJAGLDFK` +Client parser JSON keys: `creatorPlayerId`, `data`, `isValid` +Public/decompiled members: +- `int ACHMMBMLGEK` +- `string PBPPCMNHODC` +- `bool FBLEJONFPAK` + +## economy / api/gamerewards/v1/pending + +- `COICJCJBABL` `RecRoom.Async.IPromise AOJJODPOJFO()` (COICJCJBABL.txt:520) + +Expected client return: `RecRoom.Async.IPromise` (success-or-empty) +Resolved DTO: `void/success` not found in readable C# dump. + +## economy / api/gamerewards/v1/select + +- `COICJCJBABL` `RecRoom.Async.IPromise ONNNOAKDIIP(COICJCJBABL+HNJGHCGJJFC FDAEIMEHDJJ, System.Int32 IAAKEHDHCAC)` (COICJCJBABL.txt:1055) + +Expected client return: `RecRoom.Async.IPromise` (success-or-empty) +Resolved DTO: `void/success` not found in readable C# dump. + +## economy / pageview/consume + +- `COFHGNFJMOG` `` RecRoom.Async.IPromise`1 LFHFLODHPJJ() `` (COFHGNFJMOG.txt:95) + +Expected client return: `MPHABHIMOOO` (object) +Resolved DTO: `MPHABHIMOOO` from `MPHABHIMOOO.cs` +Declaration: `public class MPHABHIMOOO : IFAIJAGLDFK` +Client parser JSON keys: `url`, `freshnessSeconds` +Public/decompiled members: +- `double JKKFFNGIEPL` +- `string APBAPGCDANF` + +## elo / api/PlayerElo/ + +- `RecNet.Elo` `System.Void UpdatePlayersElo(RecNet.Elo+PlayersEloUpdateDTO JFKDDPJDCDC)` (RecNet\Elo.txt:141) + +Expected client return: `unknown` (unknown) +Resolved DTO: `unknown` not found in readable C# dump. + +## equipment / api/equipment/ + +- `ECINAMCDBJO` `System.Void EEDJOJINECJ()` (ECINAMCDBJO.txt:468) +- `ECINAMCDBJO+GPBDCOCADGE` `System.Boolean MoveNext()` (ECINAMCDBJO_NestedType_GPBDCOCADGE.txt:147) + +Expected client return: `unknown` (unknown) +Resolved DTO: `unknown` not found in readable C# dump. + +## groups / api/groups/ + +- `EJECIMCPGMG` `` RecRoom.Async.IPromise`1 CCDHCLAOFOJ(System.Int64 BCNAOOIPEJO) `` (EJECIMCPGMG.txt:337) +- `EJECIMCPGMG` `` RecRoom.Async.IPromise`1 PDJKECHIHNP(System.Int64 CJFGEMGOJHB) `` (EJECIMCPGMG.txt:555) +- `EJECIMCPGMG` `System.Collections.IEnumerator JAHJIFFICHN(System.String NDNLEGKJGCD, System.String LJIGOCDPEJF, System.String HFLPBHHAFIO, BPHGKAEDBPE+OJDJIJDNFHE AFLPGGJMPOE)` (EJECIMCPGMG.txt:803) +- `EJECIMCPGMG` `System.Collections.IEnumerator JELCDBGNAHK(System.Int64 BCNAOOIPEJO, BPHGKAEDBPE+OJDJIJDNFHE AFLPGGJMPOE)` (EJECIMCPGMG.txt:1213) +- `EJECIMCPGMG` `System.Collections.IEnumerator KBHKIILLNAC(System.Int64 BCNAOOIPEJO, BPHGKAEDBPE+OJDJIJDNFHE AFLPGGJMPOE)` (EJECIMCPGMG.txt:1040) +- `EJECIMCPGMG` `System.Collections.IEnumerator NJFPGHDIANJ(System.String NDNLEGKJGCD, BPHGKAEDBPE+OJDJIJDNFHE AFLPGGJMPOE)` (EJECIMCPGMG.txt:1336) + +Expected client return: `OGKIDDEAFND` (object) +Resolved DTO: `OGKIDDEAFND` from `OGKIDDEAFND.cs` +Declaration: `public class OGKIDDEAFND : IFAIJAGLDFK` +Client parser JSON keys: `GroupId`, `Name`, `Description`, `CreatedAt`, `ImageName`, `BanStatus`, `CreatorId`, `NumMembers` +Public/decompiled members: +- `DateTime ACBFDMLHFPB` +- `LEIHOJHGJGH ADDNGFPJPFL` +- `string AHGCOGFEEEE` +- `int CIPOELOKICH` +- `long FDPKFOLAJEJ` +- `List FHAOODLGNJA` +- `string FIKEBGGCDFN` +- `int JDMKBKGIKAO` +- `string KODBEJPEFOJ` + +Expected client return: `IEnumerator` (callback-or-coroutine) +Resolved DTO: `callback` not found in readable C# dump. + +## images / api/images/ + +- `OHDHPENHDAP` `RecRoom.Async.IPromise BFHNENPOEFB(System.Int64 LKNNMPCBCKM, System.Boolean JBAGLLBLEEN)` (OHDHPENHDAP.txt:1571) +- `OHDHPENHDAP` `RecRoom.Async.IPromise FEOMMGBJFIN(System.Int64 LKNNMPCBCKM)` (OHDHPENHDAP.txt:2387) +- `OHDHPENHDAP` `RecRoom.Async.IPromise OFEBNGPIJMB()` (OHDHPENHDAP.txt:4467) +- `OHDHPENHDAP` `` RecRoom.Async.IPromise`1 KIIGBNCPAGC() `` (OHDHPENHDAP.txt:4847) +- `OHDHPENHDAP` `` RecRoom.Async.IPromise`1> NAMIJGLEPMH(System.Int64 HNHLJONGKHB, OHDHPENHDAP+ALLECIACGAH ECENAMEKNEH, OHDHPENHDAP+OGDBPFGJMOO MOIMCDPIPOL, System.Nullable`1 KKDPFGADCEK, System.Nullable`1 MADNOGDODFA) `` (OHDHPENHDAP.txt:5403) +- `OHDHPENHDAP` `` RecRoom.Async.IPromise`1 MPNCIPABNKC(System.Byte[] IJKBAADKCBM, OHDHPENHDAP+SavedImageMetaDTO FIBFIEEBFIL) `` (OHDHPENHDAP.txt:787) +- `OHDHPENHDAP` `` System.Collections.IEnumerator BEPFIALDMHK(BPHGKAEDBPE+OJDJIJDNFHE> AFLPGGJMPOE) `` (OHDHPENHDAP.txt:394) +- `OHDHPENHDAP` `System.Collections.IEnumerator EFGBDOJHLCE(System.String HFLPBHHAFIO, OHDHPENHDAP+IMKNMFOOLGF PONCIIJOHIE, BPHGKAEDBPE+CBEOHBCIPEA AFLPGGJMPOE = null)` (OHDHPENHDAP.txt:1203) +- `OHDHPENHDAP` `System.Collections.IEnumerator GLPDDNENLHN(System.String HFLPBHHAFIO, BPHGKAEDBPE+CBEOHBCIPEA AFLPGGJMPOE = null)` (OHDHPENHDAP.txt:1427) +- `OHDHPENHDAP` `System.Collections.IEnumerator JPDBMCKFKLA(System.String HFLPBHHAFIO, BPHGKAEDBPE+CBEOHBCIPEA AFLPGGJMPOE = null)` (OHDHPENHDAP.txt:1316) + +Expected client return: `RecRoom.Async.IPromise` (success-or-empty) +Resolved DTO: `void/success` not found in readable C# dump. + +Expected client return: `OHDHPENHDAP+OHGBAPBECNM` (object) +Resolved DTO: `OHGBAPBECNM` from `OHDHPENHDAP.cs` +Declaration: `internal class OHGBAPBECNM : IFAIJAGLDFK` +Client parser JSON keys: `ValidTill` +Public/decompiled members: +- `List DLJBBCDLALH` +- `DateTime HJJGFONDEFO` + +Expected client return: `` System.Collections.Generic.List`1 `` (array) +Resolved DTO: `IBANLCLBGLM` from `OHDHPENHDAP.cs` +Declaration: `internal class IBANLCLBGLM : IFAIJAGLDFK` +Client parser JSON keys: `Id`, `ImageName`, `PlayerId`, `RoomId`, `PlayerEventId`, `Accessibility`, `AccessibilityLocked`, `Type`, `CreatedAt`, `TaggedPlayerIds`, `CheerCount`, `CommentCount` +Public/decompiled members: +- `DateTime ACBFDMLHFPB` +- `string AHGCOGFEEEE` +- `int AHNBDMKBAPK` +- `Nullable DADOKMAOFJL` +- `int EEOGKBHOJGL` +- `IReadOnlyList EKGNDCHHBLE` +- `Nullable GBOIPGBGDDG` +- `IMKNMFOOLGF JFEAPMIPNEP` +- `int JHJNKJHLJBJ` +- `long JPOHGBCEJEJ` +- `bool OJNBODIHIHF` +- `DLHGJJJPHPH OPLHMKFCNOL` +- `int PKLADBPMHMC` + +Expected client return: `System.String` (primitive) +Resolved DTO: `string` not found in readable C# dump. + +Expected client return: `IEnumerator` (callback-or-coroutine) +Resolved DTO: `callback` not found in readable C# dump. + +## inventions / api/inventions/ + +- `BBHENFCNLAB` `` RecRoom.Async.IPromise`1 ANJHOOPIAKM(System.Int64 OEMDIAHHILF, System.Boolean JBAGLLBLEEN) `` (BBHENFCNLAB.txt:7610) +- `BBHENFCNLAB` `` RecRoom.Async.IPromise`1 BEPNJCGNIKA(System.Int64 OEMDIAHHILF, HECIICKPCDN AEHDODIANMG, System.Nullable`1 MACNIENMFHJ = null) `` (BBHENFCNLAB.txt:6169) +- `BBHENFCNLAB` `` RecRoom.Async.IPromise`1 HDFICFBNFOK(System.Int64 OEMDIAHHILF, System.Int32 MACNIENMFHJ) `` (BBHENFCNLAB.txt:6479) +- `BBHENFCNLAB` `` RecRoom.Async.IPromise`1 HELBOGAPCKE(OBBBPCBIMME LDNFNNNHPPB) `` (BBHENFCNLAB.txt:5990) +- `BBHENFCNLAB` `` RecRoom.Async.IPromise`1 MEIJBGBINLO(System.Int64 OEMDIAHHILF) `` (BBHENFCNLAB.txt:6327) +- `BBHENFCNLAB` `` RecRoom.Async.IPromise`1 NNDFHPLBNEN(System.Int64 OEMDIAHHILF, System.String LJIGOCDPEJF) `` (BBHENFCNLAB.txt:1731) +- `BBHENFCNLAB` `` RecRoom.Async.IPromise`1 OCBDPGLHIJL(System.Int64 OEMDIAHHILF, HECIICKPCDN ODABIMLCOIP) `` (BBHENFCNLAB.txt:2123) +- `BBHENFCNLAB` `` RecRoom.Async.IPromise`1 ONLOPCCOFMM(System.Int64 OEMDIAHHILF, System.String DLFBBIAHDMO) `` (BBHENFCNLAB.txt:1927) +- `BBHENFCNLAB` `` RecRoom.Async.IPromise`1 PONHAAALIGM(System.Int64 OEMDIAHHILF, System.String MMBOKOLAJFH) `` (BBHENFCNLAB.txt:1533) +- `BBHENFCNLAB` `` RecRoom.Async.IPromise`1 IIPHPOBADGF(System.Int64 OEMDIAHHILF) `` (BBHENFCNLAB.txt:3629) +- `BBHENFCNLAB` `` RecRoom.Async.IPromise`1 BGDGKHPGEHK(System.Int64 OEMDIAHHILF, FDEGHHFBJJO MEABFEIBEMP, System.String EFDBFLPKHKA) `` (BBHENFCNLAB.txt:7417) +- `BBHENFCNLAB` `` RecRoom.Async.IPromise`1 LGFHMMCICNF(System.Int64 OEMDIAHHILF, System.Collections.Generic.List`1 LBAOCHFCLPO, System.Collections.Generic.List`1 NOLONFFJNPA) `` (BBHENFCNLAB.txt:2353) +- `BBHENFCNLAB` `` RecRoom.Async.IPromise`1 FDEMGICNKPI(System.Int64 OEMDIAHHILF) `` (BBHENFCNLAB.txt:3127) +- `BBHENFCNLAB` `` RecRoom.Async.IPromise`1 IGFBODDECPK(System.Int64 OEMDIAHHILF) `` (BBHENFCNLAB.txt:5821) +- `BBHENFCNLAB` `` RecRoom.Async.IPromise`1> OFPICHENKNL(System.Int64 OEMDIAHHILF) `` (BBHENFCNLAB.txt:4782) +- `BBHENFCNLAB` `` RecRoom.Async.IPromise`1> CKGFHIMCOHI(System.String DFAMLDBFENB) `` (BBHENFCNLAB.txt:7207) +- `BBHENFCNLAB` `` RecRoom.Async.IPromise`1> CKGFHIMCOHI(System.String DFAMLDBFENB) `` (BBHENFCNLAB.txt:7213) +- `BBHENFCNLAB` `` RecRoom.Async.IPromise`1> FDABNLEFMKM(System.Int64 HNHLJONGKHB) `` (BBHENFCNLAB.txt:5140) +- `BBHENFCNLAB` `` RecRoom.Async.IPromise`1> JLGCBDHFIBH(System.Collections.Generic.List`1 POGCOENDJDJ) `` (BBHENFCNLAB.txt:3371) +- `BBHENFCNLAB` `` RecRoom.Async.IPromise`1> LIPBAJOAIJC(BBHENFCNLAB+IBIIKANECFA KINAEJLCDEG) `` (BBHENFCNLAB.txt:5586) +- `BBHENFCNLAB` `System.Boolean JDBIJGFEJAM(System.Int64 OEMDIAHHILF)` (BBHENFCNLAB.txt:10717) +- `BBHENFCNLAB` `System.String HFIOIJPLLDP(System.Int64 OEMDIAHHILF)` (BBHENFCNLAB.txt:41) +- `BBHENFCNLAB` `System.String HIGLOHHJDBN(System.Int64 HNHLJONGKHB)` (BBHENFCNLAB.txt:4912) +- `BBHENFCNLAB` `System.String KKGEAELNFCK(System.Int64 OEMDIAHHILF)` (BBHENFCNLAB.txt:89) +- `BBHENFCNLAB` `System.String PAMAJCIBNDP(System.Int64 OEMDIAHHILF, System.Int32 INKNNJHDIFD)` (BBHENFCNLAB.txt:11125) +- `BBHENFCNLAB` `System.Void .cctor()` (BBHENFCNLAB.txt:11687) +- `BBHENFCNLAB` `System.Void KJMGMEKAEFF(System.Int64 HNHLJONGKHB)` (BBHENFCNLAB.txt:4992) +- `BBHENFCNLAB` `System.Void LBGHAEMBHHA(System.Int64 OEMDIAHHILF)` (BBHENFCNLAB.txt:3891) +- `BBHENFCNLAB` `` System.Void LPIPNOFPFEP(System.Int64 OEMDIAHHILF, System.Collections.Generic.List`1 LBAOCHFCLPO) `` (BBHENFCNLAB.txt:11351) + +Expected client return: `AHEPPAEOLOD` (object) +Resolved DTO: `AHEPPAEOLOD` from `AHEPPAEOLOD.cs` +Declaration: `public class AHEPPAEOLOD : IFAIJAGLDFK` +Client parser JSON keys: `Status`, `Invention`, `InventionVersion` +Public/decompiled members: +- `NEFHINKECJJ BFLAPPKDDIN` +- `JJONIELLJGH HIMCGOCKLLK` +- `OBBBPCBIMME MEPFFJLDHNP` + +Expected client return: `HJPDBNLCGIB` (object) +Resolved DTO: `HJPDBNLCGIB` from `HJPDBNLCGIB.cs` +Declaration: `public class HJPDBNLCGIB : IFAIJAGLDFK` +Public/decompiled members: +- `List PKKADKGDHNI` + +Expected client return: `KLAMKCBENEA` (object) +Resolved DTO: `KLAMKCBENEA` from `KLAMKCBENEA.cs` +Declaration: `public class KLAMKCBENEA : IFAIJAGLDFK` +Client parser JSON keys: `Success`, `Message` +Public/decompiled members: +- `string DFPPNHINEFO` +- `bool ONGEANEKLNE` + +Expected client return: `NJMAEIPIOAP` (object) +Resolved DTO: `NJMAEIPIOAP` from `NJMAEIPIOAP.cs` +Declaration: `public class NJMAEIPIOAP : IFAIJAGLDFK` +Client parser JSON keys: `Result` +Public/decompiled members: +- `OCLOGFHCPHN GMFIDNACDJK` +- `List PKKADKGDHNI` + +Expected client return: `OBBBPCBIMME` (object) +Resolved DTO: `OBBBPCBIMME` from `OBBBPCBIMME.cs` +Declaration: `public class OBBBPCBIMME : IFAIJAGLDFK` +Client parser JSON keys: `InventionId`, `ReplicationId`, `CreatorPlayerId`, `Name`, `Description`, `ImageName`, `CurrentVersionNumber`, `IsPublished`, `AllowTrial`, `ModifiedAt`, `CreatedAt`, `NumPlayersHaveUsedInRoom`, `NumDownloads`, `CheerCount`, `CreatorPermission`, `GeneralPermission`, `IsAGInvention`, `Price`, `HideFromPlayer` +Public/decompiled members: +- `DateTime ACBFDMLHFPB` +- `int ACHMMBMLGEK` +- `string AHGCOGFEEEE` +- `long AJHBGBHGGAL` +- `HECIICKPCDN BAELCDIJIPJ` +- `int DKHJPGMEOHF` +- `string FIKEBGGCDFN` +- `bool GCAABGAEDPP` +- `int HKFOKCNGNFA` +- `bool ILKLEFCCJMN` +- `int JHJNKJHLJBJ` +- `int JOHEKKGMAJB` +- `DateTime KBDMNJGJACC` +- `Nullable KLLMGMJJICK` +- `string KODBEJPEFOJ` +- `string MLDAFKCENMA` +- `HECIICKPCDN MLMDFDKLELH` +- `bool MMPKOCENIIP` +- `bool NFNIPMIEMPM` +- `Nullable NPDJLOOHMDJ` +- `Nullable PJNJIKKCBNA` + +Expected client return: `OEGPIPBKHCN` (object) +Resolved DTO: `OEGPIPBKHCN` from `OEGPIPBKHCN.cs` +Declaration: `public class OEGPIPBKHCN : IFAIJAGLDFK` +Client parser JSON keys: `IsCheering` +Public/decompiled members: +- `bool GOAELNACNNJ` + +Expected client return: `` System.Collections.Generic.List`1 `` (array) +Resolved DTO: `NEFHINKECJJ` from `NEFHINKECJJ.cs` +Declaration: `public class NEFHINKECJJ : IFAIJAGLDFK` +Client parser JSON keys: `InventionId`, `ReplicationId`, `VersionNumber`, `InstantiationCost`, `LightsCost`, `BlobName` +Public/decompiled members: +- `long AJHBGBHGGAL` +- `int CLODONFLECP` +- `int JKDGKHANBMB` +- `int KHMMKHPAMLL` +- `string MLDAFKCENMA` +- `string NIFBPEMIADP` + +Expected client return: `` System.Collections.Generic.List`1 `` (array) +Resolved DTO: `OBBBPCBIMME` from `OBBBPCBIMME.cs` +Declaration: `public class OBBBPCBIMME : IFAIJAGLDFK` +Client parser JSON keys: `InventionId`, `ReplicationId`, `CreatorPlayerId`, `Name`, `Description`, `ImageName`, `CurrentVersionNumber`, `IsPublished`, `AllowTrial`, `ModifiedAt`, `CreatedAt`, `NumPlayersHaveUsedInRoom`, `NumDownloads`, `CheerCount`, `CreatorPermission`, `GeneralPermission`, `IsAGInvention`, `Price`, `HideFromPlayer` +Public/decompiled members: +- `DateTime ACBFDMLHFPB` +- `int ACHMMBMLGEK` +- `string AHGCOGFEEEE` +- `long AJHBGBHGGAL` +- `HECIICKPCDN BAELCDIJIPJ` +- `int DKHJPGMEOHF` +- `string FIKEBGGCDFN` +- `bool GCAABGAEDPP` +- `int HKFOKCNGNFA` +- `bool ILKLEFCCJMN` +- `int JHJNKJHLJBJ` +- `int JOHEKKGMAJB` +- `DateTime KBDMNJGJACC` +- `Nullable KLLMGMJJICK` +- `string KODBEJPEFOJ` +- `string MLDAFKCENMA` +- `HECIICKPCDN MLMDFDKLELH` +- `bool MMPKOCENIIP` +- `bool NFNIPMIEMPM` +- `Nullable NPDJLOOHMDJ` +- `Nullable PJNJIKKCBNA` + +Expected client return: `` System.Collections.Generic.List`1 `` (array) +Resolved DTO: `String` not found in readable C# dump. + +Expected client return: `unknown` (unknown) +Resolved DTO: `unknown` not found in readable C# dump. + +## inventions / api/inventions/v1/fulllineageowner? + +- `BBHENFCNLAB` `` RecRoom.Async.IPromise`1 DEOOEGHNIAJ(System.Collections.Generic.List`1 POGCOENDJDJ) `` (BBHENFCNLAB.txt:7823) + +Expected client return: `System.Boolean` (primitive) +Resolved DTO: `boolean` not found in readable C# dump. + +## inventions / api/inventions/v3/addversion + +- `BBHENFCNLAB+OIJIEFMJMKO` `` RecRoom.Async.IPromise`1 b__0(System.String filename) `` (BBHENFCNLAB_NestedType_OIJIEFMJMKO.txt:216) + +Expected client return: `AHEPPAEOLOD` (object) +Resolved DTO: `AHEPPAEOLOD` from `AHEPPAEOLOD.cs` +Declaration: `public class AHEPPAEOLOD : IFAIJAGLDFK` +Client parser JSON keys: `Status`, `Invention`, `InventionVersion` +Public/decompiled members: +- `NEFHINKECJJ BFLAPPKDDIN` +- `JJONIELLJGH HIMCGOCKLLK` +- `OBBBPCBIMME MEPFFJLDHNP` + +## inventions / api/inventions/v4/save + +- `BBHENFCNLAB+FODDLIKPKPE` `` RecRoom.Async.IPromise`1 b__0(System.String filename) `` (BBHENFCNLAB_NestedType_FODDLIKPKPE.txt:250) + +Expected client return: `AHEPPAEOLOD` (object) +Resolved DTO: `AHEPPAEOLOD` from `AHEPPAEOLOD.cs` +Declaration: `public class AHEPPAEOLOD : IFAIJAGLDFK` +Client parser JSON keys: `Status`, `Invention`, `InventionVersion` +Public/decompiled members: +- `NEFHINKECJJ BFLAPPKDDIN` +- `JJONIELLJGH HIMCGOCKLLK` +- `OBBBPCBIMME MEPFFJLDHNP` + +## matchmaking / goto/club/{0} + +- `RecNet.Matchmaking` `` RecRoom.Async.IPromise`1 IIDEDGKAGOE(PLILLKHMNDA EGLGJIONCCP, System.Int32[] DKFBAOLAEFE = null, System.Boolean KJHCANCBKAL = False) `` (RecNet\Matchmaking.txt:8847) + +Expected client return: `RecNet.Matchmaking+MHCKNNJOIIP` (object) +Resolved DTO: `MHCKNNJOIIP` from `RecNet/Matchmaking.cs` +Declaration: `internal enum MHCKNNJOIIP` +Enum values: `UnknownError = -1`, `Success = 0`, `NoSuchGame = 1`, `PlayerNotOnline = 2`, `InsufficientSpace = 3`, `EventNotStarted = 4`, `EventAlreadyFinished = 5`, `BlockedFromRoom = 7`, `JuniorNotAllowed = 11`, `Banned = 12`, `AlreadyInBestInstance = 13`, `InsufficientRelationship = 14`, `UpdateRequired = 16`, `AlreadyInTargetInstance = 17`, `UGCNotAllowed = 19`, `NoSuchRoom = 20`, `RoomIsNotActive = 22`, `RoomBlockedByCreator = 23`, `RoomIsPrivate = 25`, `RoomInstanceIsPrivate = 26`, `DeviceClassNotSupported = 30`, `DeviceClassNotSupportedByRoomOwner = 31`, `MovementModeNotSupportedByRoomOwner = 32`, `EventIsPrivate = 35`, `RoomInviteExpired = 40`, `NoAvailableRegion = 45`, `NotorietyTooPoor = 50`, `BannedFromRoom = 55`, `NoSuchRoomPlaylist = 60`, `RoomPlaylistIsNotActive = 61`, `RoomPlaylistIsPrivate = 62`, `NoSuchClub = 70`, `ClubHasNoClubhouse = 71`, `ClubIsNotActive = 73`, `NotAMemberOfClub = 74`, `BannedFromClub = 75`, `InstanceJoinNotPermitted = 76` + +## matchmaking / goto/code/ + +- `RecNet.Matchmaking` `` RecRoom.Async.IPromise`1 FBLFCNDIPMP(System.String BFGCDJFNJLE, System.String KIIFGNILJEA, System.Int32[] DKFBAOLAEFE = null, System.Boolean KJHCANCBKAL = True, System.Boolean PBCPIEHFDBH = False) `` (RecNet\Matchmaking.txt:8226) + +Expected client return: `RecNet.Matchmaking+MHCKNNJOIIP` (object) +Resolved DTO: `MHCKNNJOIIP` from `RecNet/Matchmaking.cs` +Declaration: `internal enum MHCKNNJOIIP` +Enum values: `UnknownError = -1`, `Success = 0`, `NoSuchGame = 1`, `PlayerNotOnline = 2`, `InsufficientSpace = 3`, `EventNotStarted = 4`, `EventAlreadyFinished = 5`, `BlockedFromRoom = 7`, `JuniorNotAllowed = 11`, `Banned = 12`, `AlreadyInBestInstance = 13`, `InsufficientRelationship = 14`, `UpdateRequired = 16`, `AlreadyInTargetInstance = 17`, `UGCNotAllowed = 19`, `NoSuchRoom = 20`, `RoomIsNotActive = 22`, `RoomBlockedByCreator = 23`, `RoomIsPrivate = 25`, `RoomInstanceIsPrivate = 26`, `DeviceClassNotSupported = 30`, `DeviceClassNotSupportedByRoomOwner = 31`, `MovementModeNotSupportedByRoomOwner = 32`, `EventIsPrivate = 35`, `RoomInviteExpired = 40`, `NoAvailableRegion = 45`, `NotorietyTooPoor = 50`, `BannedFromRoom = 55`, `NoSuchRoomPlaylist = 60`, `RoomPlaylistIsNotActive = 61`, `RoomPlaylistIsPrivate = 62`, `NoSuchClub = 70`, `ClubHasNoClubhouse = 71`, `ClubIsNotActive = 73`, `NotAMemberOfClub = 74`, `BannedFromClub = 75`, `InstanceJoinNotPermitted = 76` + +## matchmaking / goto/event/{0} + +- `RecNet.Matchmaking` `` RecRoom.Async.IPromise`1 MLIILFNOOOK(CCMBKDINCAH IAALIIFNHNP, System.Int32[] DKFBAOLAEFE = null, System.Boolean KJHCANCBKAL = False) `` (RecNet\Matchmaking.txt:8737) + +Expected client return: `RecNet.Matchmaking+MHCKNNJOIIP` (object) +Resolved DTO: `MHCKNNJOIIP` from `RecNet/Matchmaking.cs` +Declaration: `internal enum MHCKNNJOIIP` +Enum values: `UnknownError = -1`, `Success = 0`, `NoSuchGame = 1`, `PlayerNotOnline = 2`, `InsufficientSpace = 3`, `EventNotStarted = 4`, `EventAlreadyFinished = 5`, `BlockedFromRoom = 7`, `JuniorNotAllowed = 11`, `Banned = 12`, `AlreadyInBestInstance = 13`, `InsufficientRelationship = 14`, `UpdateRequired = 16`, `AlreadyInTargetInstance = 17`, `UGCNotAllowed = 19`, `NoSuchRoom = 20`, `RoomIsNotActive = 22`, `RoomBlockedByCreator = 23`, `RoomIsPrivate = 25`, `RoomInstanceIsPrivate = 26`, `DeviceClassNotSupported = 30`, `DeviceClassNotSupportedByRoomOwner = 31`, `MovementModeNotSupportedByRoomOwner = 32`, `EventIsPrivate = 35`, `RoomInviteExpired = 40`, `NoAvailableRegion = 45`, `NotorietyTooPoor = 50`, `BannedFromRoom = 55`, `NoSuchRoomPlaylist = 60`, `RoomPlaylistIsNotActive = 61`, `RoomPlaylistIsPrivate = 62`, `NoSuchClub = 70`, `ClubHasNoClubhouse = 71`, `ClubIsNotActive = 73`, `NotAMemberOfClub = 74`, `BannedFromClub = 75`, `InstanceJoinNotPermitted = 76` + +## matchmaking / goto/instance/{0} + +- `RecNet.Matchmaking` `` RecRoom.Async.IPromise`1 MGKFDNPCDBP(System.Int64 ANAPBECHGLI, System.Int32[] DKFBAOLAEFE = null, System.Boolean KJHCANCBKAL = False, System.String APJJOJMDLNP = null) `` (RecNet\Matchmaking.txt:9156) + +Expected client return: `RecNet.Matchmaking+MHCKNNJOIIP` (object) +Resolved DTO: `MHCKNNJOIIP` from `RecNet/Matchmaking.cs` +Declaration: `internal enum MHCKNNJOIIP` +Enum values: `UnknownError = -1`, `Success = 0`, `NoSuchGame = 1`, `PlayerNotOnline = 2`, `InsufficientSpace = 3`, `EventNotStarted = 4`, `EventAlreadyFinished = 5`, `BlockedFromRoom = 7`, `JuniorNotAllowed = 11`, `Banned = 12`, `AlreadyInBestInstance = 13`, `InsufficientRelationship = 14`, `UpdateRequired = 16`, `AlreadyInTargetInstance = 17`, `UGCNotAllowed = 19`, `NoSuchRoom = 20`, `RoomIsNotActive = 22`, `RoomBlockedByCreator = 23`, `RoomIsPrivate = 25`, `RoomInstanceIsPrivate = 26`, `DeviceClassNotSupported = 30`, `DeviceClassNotSupportedByRoomOwner = 31`, `MovementModeNotSupportedByRoomOwner = 32`, `EventIsPrivate = 35`, `RoomInviteExpired = 40`, `NoAvailableRegion = 45`, `NotorietyTooPoor = 50`, `BannedFromRoom = 55`, `NoSuchRoomPlaylist = 60`, `RoomPlaylistIsNotActive = 61`, `RoomPlaylistIsPrivate = 62`, `NoSuchClub = 70`, `ClubHasNoClubhouse = 71`, `ClubIsNotActive = 73`, `NotAMemberOfClub = 74`, `BannedFromClub = 75`, `InstanceJoinNotPermitted = 76` + +## matchmaking / goto/invite/{0} + +- `RecNet.Matchmaking` `` RecRoom.Async.IPromise`1 ALCJNMAOIGH(System.Int64 IAHDKAIJJLB, System.Int64 HNHLJONGKHB, System.Int32[] DKFBAOLAEFE = null, System.Boolean KJHCANCBKAL = False) `` (RecNet\Matchmaking.txt:9294) + +Expected client return: `RecNet.Matchmaking+MHCKNNJOIIP` (object) +Resolved DTO: `MHCKNNJOIIP` from `RecNet/Matchmaking.cs` +Declaration: `internal enum MHCKNNJOIIP` +Enum values: `UnknownError = -1`, `Success = 0`, `NoSuchGame = 1`, `PlayerNotOnline = 2`, `InsufficientSpace = 3`, `EventNotStarted = 4`, `EventAlreadyFinished = 5`, `BlockedFromRoom = 7`, `JuniorNotAllowed = 11`, `Banned = 12`, `AlreadyInBestInstance = 13`, `InsufficientRelationship = 14`, `UpdateRequired = 16`, `AlreadyInTargetInstance = 17`, `UGCNotAllowed = 19`, `NoSuchRoom = 20`, `RoomIsNotActive = 22`, `RoomBlockedByCreator = 23`, `RoomIsPrivate = 25`, `RoomInstanceIsPrivate = 26`, `DeviceClassNotSupported = 30`, `DeviceClassNotSupportedByRoomOwner = 31`, `MovementModeNotSupportedByRoomOwner = 32`, `EventIsPrivate = 35`, `RoomInviteExpired = 40`, `NoAvailableRegion = 45`, `NotorietyTooPoor = 50`, `BannedFromRoom = 55`, `NoSuchRoomPlaylist = 60`, `RoomPlaylistIsNotActive = 61`, `RoomPlaylistIsPrivate = 62`, `NoSuchClub = 70`, `ClubHasNoClubhouse = 71`, `ClubIsNotActive = 73`, `NotAMemberOfClub = 74`, `BannedFromClub = 75`, `InstanceJoinNotPermitted = 76` + +## matchmaking / goto/none + +- `RecNet.Matchmaking` `RecRoom.Async.IPromise OAHNGAMAPNB()` (RecNet\Matchmaking.txt:10314) + +Expected client return: `RecRoom.Async.IPromise` (success-or-empty) +Resolved DTO: `void/success` not found in readable C# dump. + +## matchmaking / goto/player/{0} + +- `RecNet.Matchmaking` `` RecRoom.Async.IPromise`1 IMMCGNNPIAD(System.Int32 CJFGEMGOJHB, System.Int32[] DKFBAOLAEFE = null, System.Boolean KJHCANCBKAL = False) `` (RecNet\Matchmaking.txt:8980) + +Expected client return: `RecNet.Matchmaking+MHCKNNJOIIP` (object) +Resolved DTO: `MHCKNNJOIIP` from `RecNet/Matchmaking.cs` +Declaration: `internal enum MHCKNNJOIIP` +Enum values: `UnknownError = -1`, `Success = 0`, `NoSuchGame = 1`, `PlayerNotOnline = 2`, `InsufficientSpace = 3`, `EventNotStarted = 4`, `EventAlreadyFinished = 5`, `BlockedFromRoom = 7`, `JuniorNotAllowed = 11`, `Banned = 12`, `AlreadyInBestInstance = 13`, `InsufficientRelationship = 14`, `UpdateRequired = 16`, `AlreadyInTargetInstance = 17`, `UGCNotAllowed = 19`, `NoSuchRoom = 20`, `RoomIsNotActive = 22`, `RoomBlockedByCreator = 23`, `RoomIsPrivate = 25`, `RoomInstanceIsPrivate = 26`, `DeviceClassNotSupported = 30`, `DeviceClassNotSupportedByRoomOwner = 31`, `MovementModeNotSupportedByRoomOwner = 32`, `EventIsPrivate = 35`, `RoomInviteExpired = 40`, `NoAvailableRegion = 45`, `NotorietyTooPoor = 50`, `BannedFromRoom = 55`, `NoSuchRoomPlaylist = 60`, `RoomPlaylistIsNotActive = 61`, `RoomPlaylistIsPrivate = 62`, `NoSuchClub = 70`, `ClubHasNoClubhouse = 71`, `ClubIsNotActive = 73`, `NotAMemberOfClub = 74`, `BannedFromClub = 75`, `InstanceJoinNotPermitted = 76` + +## matchmaking / goto/playlist/ + +- `RecNet.Matchmaking` `` RecRoom.Async.IPromise`1 NDKKNPMAGNK(System.String APJJOJMDLNP, System.Boolean OFFEBBLOEJA = False, System.Int32[] DKFBAOLAEFE = null, System.Boolean KJHCANCBKAL = False) `` (RecNet\Matchmaking.txt:8050) + +Expected client return: `RecNet.Matchmaking+MHCKNNJOIIP` (object) +Resolved DTO: `MHCKNNJOIIP` from `RecNet/Matchmaking.cs` +Declaration: `internal enum MHCKNNJOIIP` +Enum values: `UnknownError = -1`, `Success = 0`, `NoSuchGame = 1`, `PlayerNotOnline = 2`, `InsufficientSpace = 3`, `EventNotStarted = 4`, `EventAlreadyFinished = 5`, `BlockedFromRoom = 7`, `JuniorNotAllowed = 11`, `Banned = 12`, `AlreadyInBestInstance = 13`, `InsufficientRelationship = 14`, `UpdateRequired = 16`, `AlreadyInTargetInstance = 17`, `UGCNotAllowed = 19`, `NoSuchRoom = 20`, `RoomIsNotActive = 22`, `RoomBlockedByCreator = 23`, `RoomIsPrivate = 25`, `RoomInstanceIsPrivate = 26`, `DeviceClassNotSupported = 30`, `DeviceClassNotSupportedByRoomOwner = 31`, `MovementModeNotSupportedByRoomOwner = 32`, `EventIsPrivate = 35`, `RoomInviteExpired = 40`, `NoAvailableRegion = 45`, `NotorietyTooPoor = 50`, `BannedFromRoom = 55`, `NoSuchRoomPlaylist = 60`, `RoomPlaylistIsNotActive = 61`, `RoomPlaylistIsPrivate = 62`, `NoSuchClub = 70`, `ClubHasNoClubhouse = 71`, `ClubIsNotActive = 73`, `NotAMemberOfClub = 74`, `BannedFromClub = 75`, `InstanceJoinNotPermitted = 76` + +## matchmaking / goto/room/ + +- `RecNet.Matchmaking` `` RecRoom.Async.IPromise`1 GDCFFOJEIMD() `` (RecNet\Matchmaking.txt:7509) +- `RecNet.Matchmaking` `` RecRoom.Async.IPromise`1 ILPIDHICKMB(System.String BFGCDJFNJLE, System.Boolean OFFEBBLOEJA = False, System.Int32[] DKFBAOLAEFE = null, System.Boolean KJHCANCBKAL = False, System.String APJJOJMDLNP = null, System.Boolean PBCPIEHFDBH = False) `` (RecNet\Matchmaking.txt:8426) +- `RecNet.Matchmaking` `` RecRoom.Async.IPromise`1 JLMFIHJLNBL(System.String HFDPAGDDNDE, System.Int32[] DKFBAOLAEFE, System.Boolean PBCPIEHFDBH, System.Boolean KJHCANCBKAL, System.Boolean ALOAFENOBGN) `` (RecNet\Matchmaking.txt:7882) +- `RecNet.Matchmaking` `` RecRoom.Async.IPromise`1 KNCHJJDKJGK(System.String BFGCDJFNJLE, System.String HFDPAGDDNDE, RecNet.Matchmaking+GIJOGKJCEKG MIAGCJJACCL = 0, System.Int32[] DKFBAOLAEFE = null, System.Boolean PBCPIEHFDBH = False, System.Boolean KJHCANCBKAL = False, System.String APJJOJMDLNP = null) `` (RecNet\Matchmaking.txt:8632) + +HTTP response override: +The public client method resolves to MHCKNNJOIIP, but NDDKNMLHKBK actually parses the HTTP JSON into RecNet.Matchmaking+JKOFHKAHOHK and then maps that wrapper to the enum. This is the body the server should emit. +Resolved DTO: `JKOFHKAHOHK` from `RecNet/Matchmaking.cs` +Declaration: `private class JKOFHKAHOHK : IFAIJAGLDFK` +Client parser JSON keys: `errorCode`, `roomInstance` +Public/decompiled members: +- `CIIBGMBOFEI FLOHLCKCOFA` +- `MHCKNNJOIIP HIKHMNNGOHD` + +Expected client return: `RecNet.Matchmaking+MHCKNNJOIIP` (object) +Resolved DTO: `MHCKNNJOIIP` from `RecNet/Matchmaking.cs` +Declaration: `internal enum MHCKNNJOIIP` +Enum values: `UnknownError = -1`, `Success = 0`, `NoSuchGame = 1`, `PlayerNotOnline = 2`, `InsufficientSpace = 3`, `EventNotStarted = 4`, `EventAlreadyFinished = 5`, `BlockedFromRoom = 7`, `JuniorNotAllowed = 11`, `Banned = 12`, `AlreadyInBestInstance = 13`, `InsufficientRelationship = 14`, `UpdateRequired = 16`, `AlreadyInTargetInstance = 17`, `UGCNotAllowed = 19`, `NoSuchRoom = 20`, `RoomIsNotActive = 22`, `RoomBlockedByCreator = 23`, `RoomIsPrivate = 25`, `RoomInstanceIsPrivate = 26`, `DeviceClassNotSupported = 30`, `DeviceClassNotSupportedByRoomOwner = 31`, `MovementModeNotSupportedByRoomOwner = 32`, `EventIsPrivate = 35`, `RoomInviteExpired = 40`, `NoAvailableRegion = 45`, `NotorietyTooPoor = 50`, `BannedFromRoom = 55`, `NoSuchRoomPlaylist = 60`, `RoomPlaylistIsNotActive = 61`, `RoomPlaylistIsPrivate = 62`, `NoSuchClub = 70`, `ClubHasNoClubhouse = 71`, `ClubIsNotActive = 73`, `NotAMemberOfClub = 74`, `BannedFromClub = 75`, `InstanceJoinNotPermitted = 76` + +## matchmaking / room/{0}/instances + +- `RecNet.Matchmaking` `` RecRoom.Async.IPromise`1> KFMFHGLEGHF(System.Int64 HNHLJONGKHB) `` (RecNet\Matchmaking.txt:10726) + +Expected client return: `` System.Collections.Generic.List`1 `` (array) +Resolved DTO: `JHNOAKDOMHG` from `JHNOAKDOMHG.cs` +Declaration: `public class JHNOAKDOMHG : IFAIJAGLDFK` +Client parser JSON keys: `roomInstanceId`, `roomId`, `subRoomId`, `isFull`, `createdAt`, `playerIds` +Public/decompiled members: +- `DateTime ACBFDMLHFPB` +- `string AMFIPBKHEPN` +- `long BGCNPBBBEIK` +- `long DADOKMAOFJL` +- `string EDCIALKDIGM` +- `bool FDMFNIJKHKC` +- `long GJPHHEHBCIJ` +- `List HHGAKDNMHGO` +- `bool HJGLKNFDHAP` +- `int HLFNLKMCNAC` + +## messages / api/messages/ + +- `KEBJPIGKGOI` `RecRoom.Async.IPromise JHJDMMLABCG()` (KEBJPIGKGOI.txt:1297) +- `KEBJPIGKGOI` `` RecRoom.Async.IPromise MPLDGJCCIMN(System.Int64 CJFGEMGOJHB, IGCIBGKPPMO+BBELBJELLHN JMDIPDGMIOG, System.String ABADFLCBFIJ, System.Nullable`1 HNHLJONGKHB) `` (KEBJPIGKGOI.txt:2653) +- `KEBJPIGKGOI` `` RecRoom.Async.IPromise`1 IIHKDEGDMEA() `` (KEBJPIGKGOI.txt:6118) +- `KEBJPIGKGOI` `` System.Collections.IEnumerator JFAMBLHIACA(System.Collections.Generic.List`1 FNNIKBKHAFN, BPHGKAEDBPE+CBEOHBCIPEA AFLPGGJMPOE = null) `` (KEBJPIGKGOI.txt:1476) +- `KEBJPIGKGOI` `` System.Collections.IEnumerator MGIABJDLBED(System.Collections.Generic.List`1 FNNIKBKHAFN, IGCIBGKPPMO+BBELBJELLHN JMDIPDGMIOG, System.String ABADFLCBFIJ, System.Nullable`1 HNHLJONGKHB, BPHGKAEDBPE+CBEOHBCIPEA AFLPGGJMPOE = null) `` (KEBJPIGKGOI.txt:2843) +- `KEBJPIGKGOI` `System.Void CPGAJIACKIP()` (KEBJPIGKGOI.txt:3306) + +Expected client return: `RecRoom.Async.IPromise` (success-or-empty) +Resolved DTO: `void/success` not found in readable C# dump. + +Expected client return: `DIBODMEJOPN` (object) +Resolved DTO: `DIBODMEJOPN` from `DIBODMEJOPN.cs` +Declaration: `public class DIBODMEJOPN : IFAIJAGLDFK` +Client parser JSON keys: `ChatMessage`, `FriendInvite`, `FavoriteFriendOnline` +Public/decompiled members: +- `bool EOBAEAPOFKO` +- `bool FHGBCJLIGJE` +- `bool OPAAFAKPHOE` + +Expected client return: `IEnumerator` (callback-or-coroutine) +Resolved DTO: `callback` not found in readable C# dump. + +Expected client return: `unknown` (unknown) +Resolved DTO: `unknown` not found in readable C# dump. + +## messages / api/messages/v1/IOSClearDeviceToken + +- `KEBJPIGKGOI` `RecRoom.Async.IPromise LLIHILNLNEP()` (KEBJPIGKGOI.txt:5886) + +Expected client return: `RecRoom.Async.IPromise` (success-or-empty) +Resolved DTO: `void/success` not found in readable C# dump. + +## messages / api/messages/v1/IOSModifyNotificationPreferences + +- `KEBJPIGKGOI` `RecRoom.Async.IPromise NDONIOINHAE(DIBODMEJOPN JFMOEGCPNAC)` (KEBJPIGKGOI.txt:6455) + +Expected client return: `RecRoom.Async.IPromise` (success-or-empty) +Resolved DTO: `void/success` not found in readable C# dump. + +## messages / api/messages/v1/IOSResetNotificationPreferencesBadgeCount + +- `KEBJPIGKGOI` `RecRoom.Async.IPromise GENJNKHDHCN()` (KEBJPIGKGOI.txt:6347) + +Expected client return: `RecRoom.Async.IPromise` (success-or-empty) +Resolved DTO: `void/success` not found in readable C# dump. + +## messages / api/messages/v1/IOSSaveDeviceToken + +- `KEBJPIGKGOI` `RecRoom.Async.IPromise INNJDBLBCEO(System.String EAEICOOGLAK)` (KEBJPIGKGOI.txt:5768) + +Expected client return: `RecRoom.Async.IPromise` (success-or-empty) +Resolved DTO: `void/success` not found in readable C# dump. + +## messages / api/messages/v3/delete + +- `KEBJPIGKGOI` `` System.Collections.IEnumerator PEIBHPIEKGF(System.Collections.Generic.IEnumerable`1 OCCKKEIMPGP, BPHGKAEDBPE+CBEOHBCIPEA AFLPGGJMPOE = null) `` (KEBJPIGKGOI.txt:3138) + +Expected client return: `IEnumerator` (callback-or-coroutine) +Resolved DTO: `callback` not found in readable C# dump. + +## messages / api/offlineinvite/ + +- `KEBJPIGKGOI` `System.Collections.IEnumerator LPPPAHLCPCO(System.Int64 CJFGEMGOJHB, BPHGKAEDBPE+OJDJIJDNFHE AFLPGGJMPOE)` (KEBJPIGKGOI.txt:6606) + +Expected client return: `IEnumerator` (callback-or-coroutine) +Resolved DTO: `callback` not found in readable C# dump. + +## misc / {0}v1/bulkignoreplatformusers + +- `FGPIDGLCKEF` `` System.Void IMLJEBCMMKK(PlatformManager+FCIKKFJOMNO FPMCLJDEGKL, System.Collections.Generic.List`1 CIBFMCPBLEO) `` (FGPIDGLCKEF.txt:2834) + +Expected client return: `unknown` (unknown) +Resolved DTO: `unknown` not found in readable C# dump. + +## misc / /bulk + +- `GNPDMBPGHBH` `` RecRoom.Async.IPromise`1> DMHOIAJGABC(System.Collections.Generic.IReadOnlyList`1 JOGAGJIPEDN) `` (GNPDMBPGHBH.txt:2388) +- `GNPDMBPGHBH+MKCNPHKGFDM` `` RecRoom.Async.IPromise`1> b__1(System.String uri) `` (GNPDMBPGHBH_NestedType_MKCNPHKGFDM.txt:138) + +Expected client return: `` System.Collections.Generic.List`1 `` (array) +Resolved DTO: `CCMBKDINCAH` from `CCMBKDINCAH.cs` +Declaration: `public class CCMBKDINCAH : IFAIJAGLDFK` +Client parser JSON keys: `PlayerEventId`, `Name`, `Description`, `ImageName`, `StartTime`, `EndTime`, `CreatorPlayerId`, `AttendeeCount`, `RoomId`, `Accessibility` +Public/decompiled members: +- `int ACHMMBMLGEK` +- `string AHGCOGFEEEE` +- `Nullable CCGOEDABKNN` +- `long DADOKMAOFJL` +- `string FIKEBGGCDFN` +- `long GBOIPGBGDDG` +- `int GIHDFJNGFHH` +- `Nullable GJPHHEHBCIJ` +- `CMCAFKLAHCD JFEAPMIPNEP` +- `bool KJDKJFLBFOL` +- `bool KMOAHFBMONE` +- `string KODBEJPEFOJ` +- `bool LCEFHPHIHBP` +- `DateTime LIPFBIGFEBG` +- `DateTime MAFEFHEPIKI` + +## misc / /data/{0} + +- `KELEPAPMOGK+CJACMOFOFKJ` `` RecRoom.Async.IPromise`1 b__0() `` (KELEPAPMOGK_NestedType_CJACMOFOFKJ.txt:187) + +Expected client return: `KELEPAPMOGK+LEKKFHBPBAB` (object) +Resolved DTO: `LEKKFHBPBAB` from `KELEPAPMOGK.cs` +Declaration: `internal class LEKKFHBPBAB` +Public/decompiled members: +- `string MMBOKOLAJFH` +- `Byte[] MGPDDEMABPB` + +## misc / /room/ + +- `OJMCBOKJFOF+BPPECKDOINI` `` RecRoom.Async.IPromise`1 b__0() `` (OJMCBOKJFOF_NestedType_BPPECKDOINI.txt:206) + +Expected client return: `System.Byte[]` (object) +Resolved DTO: `Byte` not found in readable C# dump. + +## misc / api/announcement/v1/get + +- `NLGOJMONPKG` `` RecRoom.Async.IPromise`1> ADEEFOCPDFH() `` (NLGOJMONPKG.txt:379) + +Expected client return: `` System.Collections.Generic.List`1 `` (array) +Resolved DTO: `MKEKHKEHPLO` from `MKEKHKEHPLO.cs` +Declaration: `public class MKEKHKEHPLO : IFAIJAGLDFK` +Client parser JSON keys: `AnnouncementId`, `AnnouncementType`, `Title`, `Body`, `ImageName`, `LinkType`, `LinkName`, `LinkUri`, `Platform`, `CreatedAt` +Public/decompiled members: +- `DateTime ACBFDMLHFPB` +- `string AHGCOGFEEEE` +- `FIEFAAIHMAF CDALJPJJHDK` +- `AIOCBINPOML COJOHODFJOL` +- `long DNFIODIACBF` +- `string EAFCPPAEHJA` +- `string FDPLOMBGJGJ` +- `MBALPDDOMME HPCKFCEFFOJ` +- `bool KFJBDJMKHKG` +- `string LKIBJOMNFFD` +- `string PFACIBEMBNO` +- `FCIKKFJOMNO PFJIBIPNDCA` + +## misc / api/catalog/v1/all?onlyAvailableSkus=true + +- `GCBPKHFJKCE` `` RecRoom.Async.IPromise`1> KABHMCDHHIP(System.Boolean INGAKMAAHKL = False) `` (GCBPKHFJKCE.txt:555) +- `GCBPKHFJKCE` `` RecRoom.Async.IPromise`1> KABHMCDHHIP(System.Boolean INGAKMAAHKL = False) `` (GCBPKHFJKCE.txt:577) +- `GCBPKHFJKCE+<>c` `System.Void b__41_1()` (GCBPKHFJKCE_NestedType___c.txt:791) +- `GCBPKHFJKCE+<>c` `System.Void b__33_1()` (GCBPKHFJKCE_NestedType___c.txt:246) +- `GCBPKHFJKCE+<>c` `System.Void b__34_1()` (GCBPKHFJKCE_NestedType___c.txt:332) + +Expected client return: `` System.Collections.Generic.List`1 `` (array) +Resolved DTO: `DJGBECJHOKF` from `GCBPKHFJKCE.cs` +Declaration: `internal class DJGBECJHOKF : IFAIJAGLDFK` +Client parser JSON keys: `skuId`, `name`, `description`, `imageName`, `price`, `oculusSkuId`, `psnProductLabel`, `xboxProductId`, `isSingleUse`, `data`, `appleProductId` +Public/decompiled members: +- `string AHDFJDKLFAD` +- `string AHGCOGFEEEE` +- `string BEIIFHKMAEP` +- `string BGBHACHJMJJ` +- `string DIDDIAMJHON` +- `string ELKEGFGFBCG` +- `string FIKEBGGCDFN` +- `string GDNNKIILCLB` +- `int HFMHNNJMABK` +- `string KODBEJPEFOJ` +- `bool LFJIIFCODHO` +- `int NPDJLOOHMDJ` +- `CACOIEJCAKJ PBPPCMNHODC` + +Expected client return: `unknown` (unknown) +Resolved DTO: `unknown` not found in readable C# dump. + +## misc / api/communityboard/ + +- `AAHIIPOCKMB` `` RecRoom.Async.IPromise`1 KIAGEGDDBCG() `` (AAHIIPOCKMB.txt:941) + +Expected client return: `AAHIIPOCKMB+JFIKDPALHOL` (object) +Resolved DTO: `JFIKDPALHOL` from `AAHIIPOCKMB.cs` +Declaration: `internal class JFIKDPALHOL : IFAIJAGLDFK` +Client parser JSON keys: `FeaturedPlayer`, `FeaturedRoomGroup`, `CurrentAnnouncement` +Public/decompiled members: +- `DOBMPPKDFPI AIPAMBHPAHP` +- `NMPFCIJPODA GPHCPJEKDLB` +- `List LBFMANLIKEI` +- `EHNFCEJANHA LCJDMHPMFBP` +- `List MCCBJHDGGAG` + +## misc / api/consumables/ + +- `FOEHJBDMMBH` `RecRoom.Async.IPromise AKFDEPEHBIN(OOCCCGPICOG BAJGNABECMN, System.Int32 HLHHPEKALPI)` (FOEHJBDMMBH.txt:1463) +- `FOEHJBDMMBH` `` RecRoom.Async.IPromise`1> AFMKIHFMMBI(System.Int32 HLHHPEKALPI) `` (FOEHJBDMMBH.txt:1112) +- `FOEHJBDMMBH` `System.Collections.IEnumerator LCELAOEOIJO(OOCCCGPICOG BAJGNABECMN, System.Int32 FKKAPHAMPMG, BPHGKAEDBPE+CBEOHBCIPEA AFLPGGJMPOE)` (FOEHJBDMMBH.txt:838) +- `FOEHJBDMMBH` `System.Collections.IEnumerator PKHAMBFCEJI(OOCCCGPICOG BAJGNABECMN, System.Boolean CNEIBILGFCO, BPHGKAEDBPE+CBEOHBCIPEA AFLPGGJMPOE)` (FOEHJBDMMBH.txt:619) +- `FOEHJBDMMBH+HHFGPIBAMOB` `System.Boolean MoveNext()` (FOEHJBDMMBH_NestedType_HHFGPIBAMOB.txt:147) + +Expected client return: `RecRoom.Async.IPromise` (success-or-empty) +Resolved DTO: `void/success` not found in readable C# dump. + +Expected client return: `` System.Collections.Generic.List`1 `` (array) +Resolved DTO: `OOCCCGPICOG` from `OOCCCGPICOG.cs` +Declaration: `public class OOCCCGPICOG : IFAIJAGLDFK, IEquatable` +Client parser JSON keys: `Id`, `ConsumableItemDesc`, `CreatedAt`, `Count`, `InitialCount`, `IsActive`, `ActiveDurationMinutes`, `IsTransferable` +Public/decompiled members: +- `DateTime ACBFDMLHFPB` +- `bool AFPIKAOJDGM` +- `Nullable BPFGOHALEJO` +- `KIDKMAOBOIG GMMAAAGFAKO` +- `string HAKADJAKPDO` +- `long JPOHGBCEJEJ` +- `ConsumableInfo LENBPGEABJK` +- `int MAPJICLFKFB` +- `int MEPGPGKFENE` +- `bool MIKDLDEALPN` + +Expected client return: `IEnumerator` (callback-or-coroutine) +Resolved DTO: `callback` not found in readable C# dump. + +Expected client return: `unknown` (unknown) +Resolved DTO: `unknown` not found in readable C# dump. + +## misc / api/curatedroomplaylists + +- `FPNPBJBCMKB` `` RecRoom.Async.IPromise`1> EPPPMHMMGME() `` (FPNPBJBCMKB.txt:53) + +Expected client return: `` System.Collections.Generic.List`1 `` (array) +Resolved DTO: `Int64` not found in readable C# dump. + +## misc / api/objectives/ + +- `GMPIMLJNACB` `System.Collections.IEnumerator IPCNHLDICBM(BPHGKAEDBPE+CBEOHBCIPEA AFLPGGJMPOE)` (GMPIMLJNACB.txt:904) +- `GMPIMLJNACB` `System.Void MEGMCCFCEIP(System.Int32 CKEBCMEKCJH, BPHGKAEDBPE+OJDJIJDNFHE AFLPGGJMPOE)` (GMPIMLJNACB.txt:2171) +- `GMPIMLJNACB+GIDAGFNPPLC` `System.Boolean MoveNext()` (GMPIMLJNACB_NestedType_GIDAGFNPPLC.txt:568) +- `GMPIMLJNACB+GIDAGFNPPLC` `System.Boolean MoveNext()` (GMPIMLJNACB_NestedType_GIDAGFNPPLC.txt:623) + +Expected client return: `IEnumerator` (callback-or-coroutine) +Resolved DTO: `callback` not found in readable C# dump. + +Expected client return: `unknown` (unknown) +Resolved DTO: `unknown` not found in readable C# dump. + +## misc / api/PlayersBanned/ + +- `GGLGFENEKBJ` `System.Collections.IEnumerator BFJMPGHLGAN(System.Int64 CJFGEMGOJHB, LCCEEFHOBEN+CJFENPHAAHI MEABFEIBEMP, BPHGKAEDBPE+CBEOHBCIPEA AFLPGGJMPOE)` (GGLGFENEKBJ.txt:586) + +Expected client return: `IEnumerator` (callback-or-coroutine) +Resolved DTO: `callback` not found in readable C# dump. + +## misc / api/relationships/ + +- `FGPIDGLCKEF` `` RecRoom.Async.IPromise`1 CGPBLDOHCEI(System.Int32 CJFGEMGOJHB, CFLMJLFBOKH+MPFKNCFEDHF DIEPOPNJNCO) `` (FGPIDGLCKEF.txt:1454) +- `FGPIDGLCKEF` `` RecRoom.Async.IPromise`1 DLBAPGPACLP(System.Int32 CJFGEMGOJHB) `` (FGPIDGLCKEF.txt:1827) +- `FGPIDGLCKEF` `` RecRoom.Async.IPromise`1 DOIEMGNIMMM(System.Int32 CJFGEMGOJHB, BPHGKAEDBPE+CBEOHBCIPEA AFLPGGJMPOE = null) `` (FGPIDGLCKEF.txt:1197) +- `FGPIDGLCKEF` `` RecRoom.Async.IPromise`1 JEGAGBBBADJ(System.Int32 CJFGEMGOJHB) `` (FGPIDGLCKEF.txt:1591) +- `FGPIDGLCKEF` `` RecRoom.Async.IPromise`1 OILLHGFDILD(System.Int32 CJFGEMGOJHB) `` (FGPIDGLCKEF.txt:1709) +- `FGPIDGLCKEF` `System.Boolean CKANLFMOMCC(System.Int32 CJFGEMGOJHB, BPHGKAEDBPE+CBEOHBCIPEA AFLPGGJMPOE = null)` (FGPIDGLCKEF.txt:2141) +- `FGPIDGLCKEF` `System.Boolean GDOLAFPLNPG(System.Int32 CJFGEMGOJHB, BPHGKAEDBPE+CBEOHBCIPEA AFLPGGJMPOE = null)` (FGPIDGLCKEF.txt:1982) +- `FGPIDGLCKEF` `System.Void CPGAJIACKIP()` (FGPIDGLCKEF.txt:3002) +- `FGPIDGLCKEF` `` System.Void IMLJEBCMMKK(PlatformManager+FCIKKFJOMNO FPMCLJDEGKL, System.Collections.Generic.List`1 CIBFMCPBLEO) `` (FGPIDGLCKEF.txt:2832) +- `FGPIDGLCKEF` `System.Void KDKALCLMLIL(System.String GJIOCANDGPE, System.Int32 CJFGEMGOJHB, BPHGKAEDBPE+CBEOHBCIPEA AFLPGGJMPOE = null)` (FGPIDGLCKEF.txt:2670) + +Expected client return: `LLKHFJDNFMM` (object) +Resolved DTO: `LLKHFJDNFMM` from `LLKHFJDNFMM.cs` +Declaration: `public class LLKHFJDNFMM : IFAIJAGLDFK` +Client parser JSON keys: `PlayerID`, `RelationshipType`, `Muted`, `Ignored`, `Favorited` +Public/decompiled members: +- `enum CJEPCMDFKNF` +- `enum DDAADAMFMCL` +- `int COJNNAGMBJF` +- `bool CPAAFLNLLMP` +- `CJEPCMDFKNF GCGHEMGJCJG` +- `CJEPCMDFKNF HHFLKFOEHNL` +- `CJEPCMDFKNF LHMMBEPNODC` +- `bool MHEGHKNGOEG` +- `bool NPBCMKLOCAE` +- `DDAADAMFMCL OPLHMKFCNOL` +- `bool PLJNOOENNDP` + +Expected client return: `unknown` (unknown) +Resolved DTO: `unknown` not found in readable C# dump. + +## misc / api/sanitize/ + +- `NKHBKKGOIHL+BBGMJPMOHEO` `System.Boolean MoveNext()` (NKHBKKGOIHL_NestedType_BBGMJPMOHEO.txt:353) + +Expected client return: `unknown` (unknown) +Resolved DTO: `unknown` not found in readable C# dump. + +## misc / api/storefronts/ + +- `GEAPBDGCKMB` `` RecRoom.Async.IPromise ACEPPELPFOL(System.Int32 PGGHBNCGLDN, System.Nullable`1 POAOENHPNHE, System.Int32 MNIJHBJDPPA, System.Int32 HLHHPEKALPI) `` (GEAPBDGCKMB.txt:2977) +- `GEAPBDGCKMB` `` RecRoom.Async.IPromise`1> LLNACOGDCMF(ACDKILABNNC DLNFAILEHOA, System.Collections.Generic.IEnumerable`1 JEDEBGBEGCE) `` (GEAPBDGCKMB.txt:5929) +- `GEAPBDGCKMB` `` RecRoom.Async.IPromise`1 MOANDFACFAP(System.Int64 OEMDIAHHILF, System.Int32 HBIEIIHBDCI, System.String LGHBPPIOOBM) `` (GEAPBDGCKMB.txt:3603) +- `GEAPBDGCKMB` `` RecRoom.Async.IPromise`1 KGGMAGALNGF(System.Int64 BGICHOOBKLD, System.Int32 HBIEIIHBDCI) `` (GEAPBDGCKMB.txt:3819) +- `GEAPBDGCKMB` `` RecRoom.Async.IPromise`1 MMINJDALHNB(System.Int64 OEMDIAHHILF, System.String LGHBPPIOOBM) `` (GEAPBDGCKMB.txt:4052) +- `GEAPBDGCKMB` `` System.Collections.IEnumerator EKLFEDLCNAC(DGOHCPBKOHD NIGCECGDCFD, ACDKILABNNC DLNFAILEHOA, System.Int32 PGGHBNCGLDN, BPHGKAEDBPE+OJDJIJDNFHE> AFLPGGJMPOE) `` (GEAPBDGCKMB.txt:3402) +- `GEAPBDGCKMB` `` System.Collections.IEnumerator EPCDDOGBHMF(DGOHCPBKOHD NIGCECGDCFD, ACDKILABNNC DLNFAILEHOA, System.Int32 PGGHBNCGLDN, System.Nullable`1 POAOENHPNHE, GEAPBDGCKMB+GiftItemDTO HBBAANIPIMP, BPHGKAEDBPE+OJDJIJDNFHE> AFLPGGJMPOE) `` (GEAPBDGCKMB.txt:2709) +- `GEAPBDGCKMB` `` System.Collections.IEnumerator GDOGAIAHEHB(DGOHCPBKOHD NIGCECGDCFD, ACDKILABNNC DLNFAILEHOA, System.Int32 PGGHBNCGLDN, BPHGKAEDBPE+OJDJIJDNFHE> AFLPGGJMPOE) `` (GEAPBDGCKMB.txt:3241) +- `GEAPBDGCKMB` `System.Collections.IEnumerator IMFAFAKABHC(DGOHCPBKOHD NIGCECGDCFD, BPHGKAEDBPE+OJDJIJDNFHE AFLPGGJMPOE)` (GEAPBDGCKMB.txt:1914) +- `GEAPBDGCKMB` `System.Collections.IEnumerator LBEMMLOFBEA(ACDKILABNNC DLNFAILEHOA, BPHGKAEDBPE+OJDJIJDNFHE AFLPGGJMPOE = null)` (GEAPBDGCKMB.txt:5640) +- `GEAPBDGCKMB` `` System.Nullable`1 KJKDMOHACHJ(ACDKILABNNC DLNFAILEHOA, System.Predicate`1 NLDOHBCJOIO = null) `` (GEAPBDGCKMB.txt:4616) +- `GEAPBDGCKMB` `` System.Void EIFPPIPOIHB(System.Collections.Generic.List`1 DBDFCMJFNEC) `` (GEAPBDGCKMB.txt:2257) +- `GEAPBDGCKMB` `System.Void IMLMGPJIKCK(ACDKILABNNC DLNFAILEHOA, DKMCNBHMKIK IGNJPMALPIK, BPHGKAEDBPE+OJDJIJDNFHE AFLPGGJMPOE = null)` (GEAPBDGCKMB.txt:5781) +- `GEAPBDGCKMB+PPPNGFBHEDH` `` RecRoom.Async.IPromise`1 b__0() `` (GEAPBDGCKMB_NestedType_PPPNGFBHEDH.txt:236) + +Expected client return: `RecRoom.Async.IPromise` (success-or-empty) +Resolved DTO: `void/success` not found in readable C# dump. + +Expected client return: `` GEAPBDGCKMB+BalanceUpdateResponseDTO`1 `` (object) +Resolved DTO: `RewardBalanceModificationDTO>` not found in readable C# dump. + +Expected client return: `GEAPBDGCKMB+InventionPurchaseResponseDTO` (object) +Resolved DTO: `InventionPurchaseResponseDTO` from `GEAPBDGCKMB.cs` +Declaration: `internal class InventionPurchaseResponseDTO : IFAIJAGLDFK` +Client parser JSON keys: `InventionResponse`, `BalanceUpdateResponse` +Public/decompiled members: +- `class FHLJACIDOHO : BalanceResponseDTO` +- `class GFAPKCFGKLB : IFAIJAGLDFK` +- `KGAACCELMHP NKPDMHMBNEJ` +- `OBBBPCBIMME PBPPCMNHODC` +- `List LPDGDNGOLFM` +- `FHLJACIDOHO BalanceUpdateResponse` +- `AHEPPAEOLOD InventionResponse` + +Expected client return: `GEAPBDGCKMB+RoomKeyPurchaseResponseDTO` (object) +Resolved DTO: `RoomKeyPurchaseResponseDTO` from `GEAPBDGCKMB.cs` +Declaration: `internal class RoomKeyPurchaseResponseDTO : IFAIJAGLDFK` +Client parser JSON keys: `RoomKeyResponse`, `BalanceUpdateResponse` +Public/decompiled members: +- `class NAHADLNOINN : BalanceResponseDTO` +- `class ELKBABHKJHC : IFAIJAGLDFK` +- `KGAACCELMHP NKPDMHMBNEJ` +- `AMAGKLLBGEC PBPPCMNHODC` +- `List LPDGDNGOLFM` +- `NAHADLNOINN BalanceUpdateResponse` +- `BCKIBFNPIPD RoomKeyResponse` + +Expected client return: `OBBBPCBIMME` (object) +Resolved DTO: `OBBBPCBIMME` from `OBBBPCBIMME.cs` +Declaration: `public class OBBBPCBIMME : IFAIJAGLDFK` +Client parser JSON keys: `InventionId`, `ReplicationId`, `CreatorPlayerId`, `Name`, `Description`, `ImageName`, `CurrentVersionNumber`, `IsPublished`, `AllowTrial`, `ModifiedAt`, `CreatedAt`, `NumPlayersHaveUsedInRoom`, `NumDownloads`, `CheerCount`, `CreatorPermission`, `GeneralPermission`, `IsAGInvention`, `Price`, `HideFromPlayer` +Public/decompiled members: +- `DateTime ACBFDMLHFPB` +- `int ACHMMBMLGEK` +- `string AHGCOGFEEEE` +- `long AJHBGBHGGAL` +- `HECIICKPCDN BAELCDIJIPJ` +- `int DKHJPGMEOHF` +- `string FIKEBGGCDFN` +- `bool GCAABGAEDPP` +- `int HKFOKCNGNFA` +- `bool ILKLEFCCJMN` +- `int JHJNKJHLJBJ` +- `int JOHEKKGMAJB` +- `DateTime KBDMNJGJACC` +- `Nullable KLLMGMJJICK` +- `string KODBEJPEFOJ` +- `string MLDAFKCENMA` +- `HECIICKPCDN MLMDFDKLELH` +- `bool MMPKOCENIIP` +- `bool NFNIPMIEMPM` +- `Nullable NPDJLOOHMDJ` +- `Nullable PJNJIKKCBNA` + +Expected client return: `IEnumerator` (callback-or-coroutine) +Resolved DTO: `callback` not found in readable C# dump. + +Expected client return: `unknown` (unknown) +Resolved DTO: `unknown` not found in readable C# dump. + +Expected client return: `BKFGFHDDNFG` (object) +Resolved DTO: `BKFGFHDDNFG` from `BKFGFHDDNFG.cs` +Declaration: `public class BKFGFHDDNFG : DOPJPOEFDCN` +Inherits: `DOPJPOEFDCN` +Inherited parser JSON keys: `StorefrontType`, `NextUpdate` +Public/decompiled members: +- `List JDGEBBMDJBC` +- `Nullable BFCBMGOPKFH` (inherited from `DOPJPOEFDCN`) +- `DateTime GHHJBILFKFB` (inherited from `DOPJPOEFDCN`) +- `DGOHCPBKOHD GHOKCIMBEMM` (inherited from `DOPJPOEFDCN`) + +## misc / api/testcasemanagement/ + +- `AICHDPNIEKI` `RecRoom.Async.IPromise CNGKABDOEKH(System.String EPGHLDONDIP, NIDIHGENDJD AKPHCJFIPBB)` (AICHDPNIEKI.txt:450) +- `AICHDPNIEKI` `RecRoom.Async.IPromise DHDOAJAGEJN(System.String EPGHLDONDIP)` (AICHDPNIEKI.txt:360) +- `AICHDPNIEKI` `RecRoom.Async.IPromise KNOGAGHKIMC(System.String EPGHLDONDIP)` (AICHDPNIEKI.txt:273) +- `AICHDPNIEKI` `` RecRoom.Async.IPromise`1 APEBFFOEBFA(System.String ENJEOLBEALP) `` (AICHDPNIEKI.txt:193) +- `AICHDPNIEKI` `` RecRoom.Async.IPromise`1 BMDDGFAEDLE(System.UInt32 ENJEOLBEALP) `` (AICHDPNIEKI.txt:122) +- `AICHDPNIEKI` `` RecRoom.Async.IPromise`1> AKCPPGBNGOO() `` (AICHDPNIEKI.txt:44) + +Expected client return: `RecRoom.Async.IPromise` (success-or-empty) +Resolved DTO: `void/success` not found in readable C# dump. + +Expected client return: `CPFPHLINDHN` (object) +Resolved DTO: `CPFPHLINDHN` from `CPFPHLINDHN.cs` +Declaration: `public class CPFPHLINDHN : IFAIJAGLDFK` +Client parser JSON keys: `Id`, `Key`, `Title`, `Description`, `RoomName`, `Status`, `MinNumAssignedPlayers`, `AssignedPlayerIds`, `JiraUrl`, `JiraBugUrl` +Public/decompiled members: +- `int CPNPHDAKFCL` +- `string JPOHGBCEJEJ` +- `string AKFDMGLACLA` +- `string LKIBJOMNFFD` +- `string KODBEJPEFOJ` +- `string HEHAGDOEDHG` +- `NIDIHGENDJD HIMCGOCKLLK` +- `int JOPPLLFIIOM` +- `List JFOIIDOFNCF` +- `List GEPOBHPJHAK` +- `List PKKADKGDHNI` +- `string EGBGAKBPEBD` +- `string IOBANKPIFFG` + +Expected client return: `FDAJOEOJFDN` (object) +Resolved DTO: `FDAJOEOJFDN` from `FDAJOEOJFDN.cs` +Declaration: `public class FDAJOEOJFDN : IFAIJAGLDFK` +Client parser JSON keys: `Id`, `Name`, `Description`, `StartDate`, `WasManuallyClosed`, `NumTestCases`, `NumPassedTestCases`, `NumFailedTestCases` +Public/decompiled members: +- `bool PDFFJJFCNPO` +- `int PJOBHHCOBLE` +- `uint JPOHGBCEJEJ` +- `string FIKEBGGCDFN` +- `string KODBEJPEFOJ` +- `DateTime CKDMMCJNCHB` +- `Nullable CKNFHKKHNHP` +- `bool JICMOEJKCHG` +- `List KODOFNJJJCK` +- `List PKKADKGDHNI` +- `int BPPENAKNGNG` +- `int PFIABEONLFI` +- `int CAKKOAKBIIG` + +Expected client return: `` System.Collections.Generic.List`1 `` (array) +Resolved DTO: `FDAJOEOJFDN` from `FDAJOEOJFDN.cs` +Declaration: `public class FDAJOEOJFDN : IFAIJAGLDFK` +Client parser JSON keys: `Id`, `Name`, `Description`, `StartDate`, `WasManuallyClosed`, `NumTestCases`, `NumPassedTestCases`, `NumFailedTestCases` +Public/decompiled members: +- `bool PDFFJJFCNPO` +- `int PJOBHHCOBLE` +- `uint JPOHGBCEJEJ` +- `string FIKEBGGCDFN` +- `string KODBEJPEFOJ` +- `DateTime CKDMMCJNCHB` +- `Nullable CKNFHKKHNHP` +- `bool JICMOEJKCHG` +- `List KODOFNJJJCK` +- `List PKKADKGDHNI` +- `int BPPENAKNGNG` +- `int PFIABEONLFI` +- `int CAKKOAKBIIG` + +## misc / api/versioncheck/v4?v={0}&p={1} + +- `KMDHPCHFADM` `` RecRoom.Async.IPromise`1 BPAGDCDBHAM() `` (KMDHPCHFADM.txt:282) + +Expected client return: `PCBCGDHBAEL` (object) +Resolved DTO: `PCBCGDHBAEL` from `PCBCGDHBAEL.cs` +Declaration: `public enum PCBCGDHBAEL` +Enum values: `ValidForPlay = 0`, `ValidForMenu = 1`, `UpdateRequired = 2` + +## misc / https://apps.apple.com/account/subscriptions + +- `IOSPlatformManager` `RecRoom.Async.IPromise ShowManageSubscriptionPlatformUI()` (IOSPlatformManager.txt:4865) + +Expected client return: `RecRoom.Async.IPromise` (success-or-empty) +Resolved DTO: `void/success` not found in readable C# dump. + +## misc / https://www.instagram.com/recroom/ + +- `InstagramBulletinBoardUI` `System.Void Button_RecRoomOnInstagram()` (InstagramBulletinBoardUI.txt:1009) + +Expected client return: `unknown` (unknown) +Resolved DTO: `unknown` not found in readable C# dump. + +## player-events / api/playerevents/ + +- `GNPDMBPGHBH` `` RecRoom.Async.IPromise`1 BFBMDMLHHCF(CCMBKDINCAH MFJCKOBPMGA, System.Int64 HNHLJONGKHB, System.Nullable`1 FODGKNJIGOP, System.String MMBOKOLAJFH, System.String LJIGOCDPEJF, System.Collections.Generic.List`1 CAFPJPHILMN, System.String HFLPBHHAFIO, System.DateTime JODPOANPJNK, System.DateTime BCANLCHBKJE, CMCAFKLAHCD PONCIIJOHIE, System.Nullable`1 AOMBLGBCENO) `` (GNPDMBPGHBH.txt:4280) +- `GNPDMBPGHBH` `` RecRoom.Async.IPromise`1 EKEOHKINPDK(CCMBKDINCAH MFJCKOBPMGA) `` (GNPDMBPGHBH.txt:6151) +- `GNPDMBPGHBH` `` RecRoom.Async.IPromise`1> MBHCOIGLAIF(System.Int64 MOKMJAMNEFP, System.Boolean INGAKMAAHKL = False) `` (GNPDMBPGHBH.txt:3736) +- `GNPDMBPGHBH` `` RecRoom.Async.IPromise`1 DAICPGEMGMH(System.Int64 MOKMJAMNEFP, System.Boolean GDPJLIOIIAM = False, System.Nullable`1 AOMBLGBCENO = null) `` (GNPDMBPGHBH.txt:6434) +- `GNPDMBPGHBH` `System.String HKPFFOHBENO(System.Int64 MEJJDOHGNHG)` (GNPDMBPGHBH.txt:559) +- `GNPDMBPGHBH` `` System.Void DAOBEMMLHAD(System.Collections.Generic.Dictionary`2 NGPMADFHHKP) `` (GNPDMBPGHBH.txt:10757) +- `GNPDMBPGHBH` `System.Void DEOJOKOFMLJ(System.Int64 MOKMJAMNEFP)` (GNPDMBPGHBH.txt:11770) +- `GNPDMBPGHBH` `System.Void JILNOOIDFKC(HJCAMMLHJAE MOMJFCHIMAM)` (GNPDMBPGHBH.txt:11596) +- `GNPDMBPGHBH+JNEMPCHKFAM` `System.Void b__0(PDOBNLOLBAF response)` (GNPDMBPGHBH_NestedType_JNEMPCHKFAM.txt:168) + +Expected client return: `LLNPLLBEJBE` (object) +Resolved DTO: `LLNPLLBEJBE` from `LLNPLLBEJBE.cs` +Declaration: `public class LLNPLLBEJBE : IFAIJAGLDFK` +Client parser JSON keys: `PlayerEvent`, `Result`, `TagModifyResult` +Public/decompiled members: +- `CCMBKDINCAH AANNMHFBONM` +- `DGOPHENCPOC GMFIDNACDJK` +- `NJMAEIPIOAP POJDHKAOINA` + +Expected client return: `` System.Collections.Generic.List`1 `` (array) +Resolved DTO: `PBIJAPEOEDO` from `PBIJAPEOEDO.cs` +Declaration: `public class PBIJAPEOEDO : IFAIJAGLDFK` +Client parser JSON keys: `PlayerEventResponseId`, `PlayerEventId`, `PlayerId`, `CreatedAt`, `Type` +Public/decompiled members: +- `DateTime ACBFDMLHFPB` +- `int EEOGKBHOJGL` +- `long FAEKONHJKNK` +- `long GBOIPGBGDDG` +- `FFEICMIIBMC OPLHMKFCNOL` + +Expected client return: `TPlayerEventDTO` (object) +Resolved DTO: `TPlayerEventDTO` not found in readable C# dump. + +Expected client return: `unknown` (unknown) +Resolved DTO: `unknown` not found in readable C# dump. + +## player-events / api/playerevents/v1/all + +- `GNPDMBPGHBH` `` RecRoom.Async.IPromise`1 JKFDNKOBOJE() `` (GNPDMBPGHBH.txt:5504) + +Expected client return: `FNBMIJGOOJM` (object) +Resolved DTO: `FNBMIJGOOJM` from `FNBMIJGOOJM.cs` +Declaration: `public class FNBMIJGOOJM : IFAIJAGLDFK` +Public/decompiled members: +- `List APGINOKLOIF` +- `List NLEGMBPCMIA` +- `long playerEventId` + +## player-events / api/playerevents/v1/bulkInvite + +- `GNPDMBPGHBH` `` RecRoom.Async.IPromise`1 MGDLGLLOEMG(System.Int64 MOKMJAMNEFP, System.Collections.Generic.List`1 MHCONOPOOKJ) `` (GNPDMBPGHBH.txt:5013) + +Expected client return: `PDOBNLOLBAF` (object) +Resolved DTO: `PDOBNLOLBAF` from `PDOBNLOLBAF.cs` +Declaration: `public class PDOBNLOLBAF : IFAIJAGLDFK` +Client parser JSON keys: `Result` +Public/decompiled members: +- `List DCNIKAABNGK` +- `DGOPHENCPOC GMFIDNACDJK` + +## player-events / api/playerevents/v1/deleteResponse + +- `GNPDMBPGHBH` `` RecRoom.Async.IPromise`1 FGFDJIKJKNN(System.Int64 MOKMJAMNEFP, FFEICMIIBMC DHLDCHCKBPC) `` (GNPDMBPGHBH.txt:4769) + +Expected client return: `DGOPHENCPOC` (object) +Resolved DTO: `DGOPHENCPOC` from `DGOPHENCPOC.cs` +Declaration: `public enum DGOPHENCPOC` +Enum values: `Success = 0`, `HasModeratorClosedEvent = 1`, `DoesNotExist = 2`, `PlayerDoesNotExist = 3`, `RoomDoesNotExist = 4`, `StatusUnchanged = 5`, `PrivateEvent = 6`, `SomethingWentWrong = 7`, `DoesNotOwnRoom = 8`, `ResponseDoesNotExist = 9`, `PlayerAlreadyInvited = 10`, `EventDatesInvalid = 11`, `EventTooLong = 12`, `EventTooShort = 13`, `InappropriateName = 14`, `InappropriateDescription = 15`, `SomeInvitesFailed = 16`, `CannotInviteJunior = 17`, `EventCountLimitReached = 18`, `DoesNotOwnEvent = 19`, `UnregisteredOrJuniorNotAllowed = 20`, `InvalidClubPermissions = 21`, `ImageDoesNotExist = 22`, `SubRoomDoesNotExist = 23`, `DoesNotOwnSubRoom = 24`, `ModifyTagsFailed = 25` + +## player-events / api/playerevents/v1/report + +- `GNPDMBPGHBH` `` RecRoom.Async.IPromise`1 GLHAJLLNPBJ(System.Int64 MOKMJAMNEFP, LCCEEFHOBEN+CJFENPHAAHI MEABFEIBEMP, System.String EFDBFLPKHKA) `` (GNPDMBPGHBH.txt:5939) + +Expected client return: `KLAMKCBENEA` (object) +Resolved DTO: `KLAMKCBENEA` from `KLAMKCBENEA.cs` +Declaration: `public class KLAMKCBENEA : IFAIJAGLDFK` +Client parser JSON keys: `Success`, `Message` +Public/decompiled members: +- `string DFPPNHINEFO` +- `bool ONGEANEKLNE` + +## player-events / api/playerevents/v1/respond + +- `GNPDMBPGHBH` `` RecRoom.Async.IPromise`1 EEJGIOKDBFI(System.Int64 MOKMJAMNEFP, FFEICMIIBMC DHLDCHCKBPC) `` (GNPDMBPGHBH.txt:4567) + +Expected client return: `DGOPHENCPOC` (object) +Resolved DTO: `DGOPHENCPOC` from `DGOPHENCPOC.cs` +Declaration: `public enum DGOPHENCPOC` +Enum values: `Success = 0`, `HasModeratorClosedEvent = 1`, `DoesNotExist = 2`, `PlayerDoesNotExist = 3`, `RoomDoesNotExist = 4`, `StatusUnchanged = 5`, `PrivateEvent = 6`, `SomethingWentWrong = 7`, `DoesNotOwnRoom = 8`, `ResponseDoesNotExist = 9`, `PlayerAlreadyInvited = 10`, `EventDatesInvalid = 11`, `EventTooLong = 12`, `EventTooShort = 13`, `InappropriateName = 14`, `InappropriateDescription = 15`, `SomeInvitesFailed = 16`, `CannotInviteJunior = 17`, `EventCountLimitReached = 18`, `DoesNotOwnEvent = 19`, `UnregisteredOrJuniorNotAllowed = 20`, `InvalidClubPermissions = 21`, `ImageDoesNotExist = 22`, `SubRoomDoesNotExist = 23`, `DoesNotOwnSubRoom = 24`, `ModifyTagsFailed = 25` + +## player-events / api/playerevents/v1/search? + +- `GNPDMBPGHBH` `` RecRoom.Async.IPromise`1> ADMFNGBFPIE(System.String CNBKKCJAHPP, GNPDMBPGHBH+EHKGCEONBIB DKLJMLFNEEN = 0, System.Nullable`1 LBFLCMJFDPC = null) `` (GNPDMBPGHBH.txt:9248) + +Expected client return: `` System.Collections.Generic.List`1 `` (array) +Resolved DTO: `CCMBKDINCAH` from `CCMBKDINCAH.cs` +Declaration: `public class CCMBKDINCAH : IFAIJAGLDFK` +Client parser JSON keys: `PlayerEventId`, `Name`, `Description`, `ImageName`, `StartTime`, `EndTime`, `CreatorPlayerId`, `AttendeeCount`, `RoomId`, `Accessibility` +Public/decompiled members: +- `int ACHMMBMLGEK` +- `string AHGCOGFEEEE` +- `Nullable CCGOEDABKNN` +- `long DADOKMAOFJL` +- `string FIKEBGGCDFN` +- `long GBOIPGBGDDG` +- `int GIHDFJNGFHH` +- `Nullable GJPHHEHBCIJ` +- `CMCAFKLAHCD JFEAPMIPNEP` +- `bool KJDKJFLBFOL` +- `bool KMOAHFBMONE` +- `string KODBEJPEFOJ` +- `bool LCEFHPHIHBP` +- `DateTime LIPFBIGFEBG` +- `DateTime MAFEFHEPIKI` + +## player-events / api/playerevents/v1/searchlive? + +- `GNPDMBPGHBH` `` RecRoom.Async.IPromise`1> JBOMFDKLBOI(System.String CNBKKCJAHPP) `` (GNPDMBPGHBH.txt:8935) + +Expected client return: `` System.Collections.Generic.List`1 `` (array) +Resolved DTO: `NMKMJKIHDNE` from `NMKMJKIHDNE.cs` +Declaration: `public class NMKMJKIHDNE : CCMBKDINCAH` +Inherits: `CCMBKDINCAH` +Client parser JSON keys: `PlayerCount`, `IsFull` +Inherited parser JSON keys: `PlayerEventId`, `Name`, `Description`, `ImageName`, `StartTime`, `EndTime`, `CreatorPlayerId`, `AttendeeCount`, `RoomId`, `Accessibility` +Public/decompiled members: +- `bool FDMFNIJKHKC` +- `int HLFNLKMCNAC` +- `int ACHMMBMLGEK` (inherited from `CCMBKDINCAH`) +- `string AHGCOGFEEEE` (inherited from `CCMBKDINCAH`) +- `Nullable CCGOEDABKNN` (inherited from `CCMBKDINCAH`) +- `long DADOKMAOFJL` (inherited from `CCMBKDINCAH`) +- `string FIKEBGGCDFN` (inherited from `CCMBKDINCAH`) +- `long GBOIPGBGDDG` (inherited from `CCMBKDINCAH`) +- `int GIHDFJNGFHH` (inherited from `CCMBKDINCAH`) +- `Nullable GJPHHEHBCIJ` (inherited from `CCMBKDINCAH`) +- `CMCAFKLAHCD JFEAPMIPNEP` (inherited from `CCMBKDINCAH`) +- `bool KJDKJFLBFOL` (inherited from `CCMBKDINCAH`) +- `bool KMOAHFBMONE` (inherited from `CCMBKDINCAH`) +- `string KODBEJPEFOJ` (inherited from `CCMBKDINCAH`) +- `bool LCEFHPHIHBP` (inherited from `CCMBKDINCAH`) +- `DateTime LIPFBIGFEBG` (inherited from `CCMBKDINCAH`) +- `DateTime MAFEFHEPIKI` (inherited from `CCMBKDINCAH`) + +## player-events / api/playerevents/v1/tagfilters + +- `GNPDMBPGHBH` `` RecRoom.Async.IPromise`1> LIPBAJOAIJC(GNPDMBPGHBH+FFCCJHEPNDK KINAEJLCDEG) `` (GNPDMBPGHBH.txt:9697) + +Expected client return: `` System.Collections.Generic.List`1 `` (array) +Resolved DTO: `String` not found in readable C# dump. + +## player-events / api/playerevents/v2 + +- `GNPDMBPGHBH` `` RecRoom.Async.IPromise`1 BBPEHLACMLH(System.Int64 HNHLJONGKHB, System.Nullable`1 FODGKNJIGOP, System.Nullable`1 AOMBLGBCENO, System.String MMBOKOLAJFH, System.String LJIGOCDPEJF, System.Collections.Generic.List`1 CAFPJPHILMN, System.String HFLPBHHAFIO, System.DateTime JODPOANPJNK, System.DateTime BCANLCHBKJE, CMCAFKLAHCD PONCIIJOHIE) `` (GNPDMBPGHBH.txt:4067) + +Expected client return: `LLNPLLBEJBE` (object) +Resolved DTO: `LLNPLLBEJBE` from `LLNPLLBEJBE.cs` +Declaration: `public class LLNPLLBEJBE : IFAIJAGLDFK` +Client parser JSON keys: `PlayerEvent`, `Result`, `TagModifyResult` +Public/decompiled members: +- `CCMBKDINCAH AANNMHFBONM` +- `DGOPHENCPOC GMFIDNACDJK` +- `NJMAEIPIOAP POJDHKAOINA` + +## players / /api/playerReputation/v1/{0} + +- `EBAIPNMBKLK` `` RecRoom.Async.IPromise`1 JNLDMDCGHHJ(System.Int32 GKLPIFBPGOD) `` (EBAIPNMBKLK.txt:1231) + +Expected client return: `JGEBJEJLNBK` (object) +Resolved DTO: `JGEBJEJLNBK` from `JGEBJEJLNBK.cs` +Declaration: `public class JGEBJEJLNBK : IFAIJAGLDFK` +Client parser JSON keys: `AccountId`, `Noteriety`, `CheerGeneral`, `CheerHelpful`, `CheerGreatHost`, `CheerSportsman`, `CheerCreative`, `CheerCredit`, `SelectedCheer` +Public/decompiled members: +- `int AONADBLHNEE` +- `float APDEKEIBJPJ` +- `int GAINIOENNCG` +- `Nullable GKBDOIPKEID` +- `Nullable JABKMGPLDCO` +- `int JKMJNPJFGNC` +- `bool LCHBNILBADA` +- `int MOGEADHIOBD` +- `int NGPDMPELINB` +- `int OCJDOOKPBAE` +- `int PEDJLAMDNNA` +- `PIDFCOGLFHN cheer` + +## players / /api/playerReputation/v1/bulk + +- `EBAIPNMBKLK` `` RecRoom.Async.IPromise`1> KGPDFJMKJKL(System.Collections.Generic.List`1 ILNGMAANNDG) `` (EBAIPNMBKLK.txt:1631) + +Expected client return: `` System.Collections.Generic.List`1 `` (array) +Resolved DTO: `JGEBJEJLNBK` from `JGEBJEJLNBK.cs` +Declaration: `public class JGEBJEJLNBK : IFAIJAGLDFK` +Client parser JSON keys: `AccountId`, `Noteriety`, `CheerGeneral`, `CheerHelpful`, `CheerGreatHost`, `CheerSportsman`, `CheerCreative`, `CheerCredit`, `SelectedCheer` +Public/decompiled members: +- `int AONADBLHNEE` +- `float APDEKEIBJPJ` +- `int GAINIOENNCG` +- `Nullable GKBDOIPKEID` +- `Nullable JABKMGPLDCO` +- `int JKMJNPJFGNC` +- `bool LCHBNILBADA` +- `int MOGEADHIOBD` +- `int NGPDMPELINB` +- `int OCJDOOKPBAE` +- `int PEDJLAMDNNA` +- `PIDFCOGLFHN cheer` + +## players / /api/players/v1/progression/{0} + +- `ACJLCBNBJDK` `` RecRoom.Async.IPromise`1 AAGPDEDFCNI(System.Int32 GKLPIFBPGOD) `` (ACJLCBNBJDK.txt:1673) + +Expected client return: `NFMBDLEJEDD` (object) +Resolved DTO: `NFMBDLEJEDD` from `NFMBDLEJEDD.cs` +Declaration: `public class NFMBDLEJEDD : IFAIJAGLDFK` +Client parser JSON keys: `PlayerId`, `Level`, `XP` +Public/decompiled members: +- `float ANKCGDNCABN` +- `int FHPGELHJCFG` +- `int GAINIOENNCG` +- `int JKJIMIDBCIH` +- `int LINLLHBFIKF` + +## players / /api/players/v1/progression/bulk + +- `ACJLCBNBJDK` `` RecRoom.Async.IPromise`1> MCFMHADBDBI(System.Collections.Generic.List`1 ILNGMAANNDG) `` (ACJLCBNBJDK.txt:2051) + +Expected client return: `` System.Collections.Generic.List`1 `` (array) +Resolved DTO: `NFMBDLEJEDD` from `NFMBDLEJEDD.cs` +Declaration: `public class NFMBDLEJEDD : IFAIJAGLDFK` +Client parser JSON keys: `PlayerId`, `Level`, `XP` +Public/decompiled members: +- `float ANKCGDNCABN` +- `int FHPGELHJCFG` +- `int GAINIOENNCG` +- `int JKJIMIDBCIH` +- `int LINLLHBFIKF` + +## players / api/players/v2/objectives + +- `ACJLCBNBJDK` `` System.Void EIFPPIPOIHB(System.Collections.Generic.List`1 DBDFCMJFNEC) `` (ACJLCBNBJDK.txt:816) + +Expected client return: `unknown` (unknown) +Resolved DTO: `unknown` not found in readable C# dump. + +## playlists / featuredrooms/current + +- `EJDCNGBEICB` `` RecRoom.Async.IPromise`1 CFKDADKHAGB() `` (EJDCNGBEICB.txt:2687) + +Expected client return: `NMPFCIJPODA` (object) +Resolved DTO: `NMPFCIJPODA` from `NMPFCIJPODA.cs` +Declaration: `public class NMPFCIJPODA : IFAIJAGLDFK` +Client parser JSON keys: `FeaturedRoomGroupId`, `Name` +Public/decompiled members: +- `long EHDPEFMCIIC` +- `string FIKEBGGCDFN` +- `IReadOnlyList HEFJFKFNNCJ` + +## playlists / playlists/{0} + +- `EJDCNGBEICB` `RecRoom.Async.IPromise OFACCDEDIHE(System.Int64 DDMPFMPILCE)` (EJDCNGBEICB.txt:6619) +- `EJDCNGBEICB` `` RecRoom.Async.IPromise`1 POOBMHAAMLJ(System.Int64 DDMPFMPILCE) `` (EJDCNGBEICB.txt:2192) +- `EJDCNGBEICB` `` RecRoom.Async.IPromise`1 PDLNHHAPPIP(System.Int64 DDMPFMPILCE) `` (EJDCNGBEICB.txt:1659) + +Expected client return: `RecRoom.Async.IPromise` (success-or-empty) +Resolved DTO: `void/success` not found in readable C# dump. + +Expected client return: `BMFAGMFKODA` (object) +Resolved DTO: `BMFAGMFKODA` from `BMFAGMFKODA.cs` +Declaration: `public class BMFAGMFKODA : KMKPEOGJDFK` +Inherits: `KMKPEOGJDFK` +Inherited parser JSON keys: `PlaylistId` +Public/decompiled members: +- `IReadOnlyList BIODFICLKJH` +- `IReadOnlyList DFKGKGIMEMP` +- `IReadOnlyList FEBCCOFBKKJ` +- `IReadOnlyList HEFJFKFNNCJ` +- `IReadOnlyList IFMMBHKGHMB` +- `IReadOnlyList KFFBPMHGFKB` +- `IReadOnlyList PKKADKGDHNI` +- `string tag` +- `long BBGEDJMGFFO` (inherited from `KMKPEOGJDFK`) +- `DateTime ACBFDMLHFPB` (inherited from `MKAMHOIHOJK`) +- `string AHGCOGFEEEE` (inherited from `MKAMHOIHOJK`) +- `bool AKFBNELAMNA` (inherited from `MKAMHOIHOJK`) +- `int BADIGBCKECA` (inherited from `MKAMHOIHOJK`) +- `bool BNBLOBAEDEE` (inherited from `MKAMHOIHOJK`) +- `NMJEKMMBDDE CDINMMPNAID` (inherited from `MKAMHOIHOJK`) +- `bool CDNFGMHLDMJ` (inherited from `MKAMHOIHOJK`) +- `string FIKEBGGCDFN` (inherited from `MKAMHOIHOJK`) +- `GPDIAKNEBKH GIBHIMGJNNO` (inherited from `MKAMHOIHOJK`) +- `bool HPLBOMGACED` (inherited from `MKAMHOIHOJK`) +- `string IGOPGMHHLKI` (inherited from `MKAMHOIHOJK`) +- `DPLPMKMFMPB JFEAPMIPNEP` (inherited from `MKAMHOIHOJK`) +- `bool KHIJAFCHLIA` (inherited from `MKAMHOIHOJK`) +- `bool KLNJBBPNMBJ` (inherited from `MKAMHOIHOJK`) +- `string KODBEJPEFOJ` (inherited from `MKAMHOIHOJK`) +- `bool LPJLEMJFBPE` (inherited from `MKAMHOIHOJK`) +- `bool MGBDHBHCDMH` (inherited from `MKAMHOIHOJK`) +- `bool MIKDLDEALPN` (inherited from `MKAMHOIHOJK`) +- `bool OFONEIOEIED` (inherited from `MKAMHOIHOJK`) +- `HJPGEGENLPH OILEJFNPDDB` (inherited from `MKAMHOIHOJK`) +- `bool PEEFHKMOMKK` (inherited from `MKAMHOIHOJK`) + +Expected client return: `KMKPEOGJDFK` (object) +Resolved DTO: `KMKPEOGJDFK` from `KMKPEOGJDFK.cs` +Declaration: `public class KMKPEOGJDFK : MKAMHOIHOJK` +Inherits: `MKAMHOIHOJK` +Client parser JSON keys: `PlaylistId` +Inherited parser JSON keys: `Name`, `Description`, `ImageName`, `WarningMask`, `CustomWarning`, `CreatorAccountId`, `State`, `Accessibility`, `SupportsLevelVoting`, `IsRRO`, `SupportsScreens`, `SupportsWalkVR`, `SupportsTeleportVR`, `SupportsVRLow`, `SupportsQuest2`, `SupportsMobile`, `SupportsJuniors`, `CreatedAt`, `Stats` +Public/decompiled members: +- `long BBGEDJMGFFO` +- `DateTime ACBFDMLHFPB` (inherited from `MKAMHOIHOJK`) +- `string AHGCOGFEEEE` (inherited from `MKAMHOIHOJK`) +- `bool AKFBNELAMNA` (inherited from `MKAMHOIHOJK`) +- `int BADIGBCKECA` (inherited from `MKAMHOIHOJK`) +- `bool BNBLOBAEDEE` (inherited from `MKAMHOIHOJK`) +- `NMJEKMMBDDE CDINMMPNAID` (inherited from `MKAMHOIHOJK`) +- `bool CDNFGMHLDMJ` (inherited from `MKAMHOIHOJK`) +- `string FIKEBGGCDFN` (inherited from `MKAMHOIHOJK`) +- `GPDIAKNEBKH GIBHIMGJNNO` (inherited from `MKAMHOIHOJK`) +- `bool HPLBOMGACED` (inherited from `MKAMHOIHOJK`) +- `string IGOPGMHHLKI` (inherited from `MKAMHOIHOJK`) +- `DPLPMKMFMPB JFEAPMIPNEP` (inherited from `MKAMHOIHOJK`) +- `bool KHIJAFCHLIA` (inherited from `MKAMHOIHOJK`) +- `bool KLNJBBPNMBJ` (inherited from `MKAMHOIHOJK`) +- `string KODBEJPEFOJ` (inherited from `MKAMHOIHOJK`) +- `bool LPJLEMJFBPE` (inherited from `MKAMHOIHOJK`) +- `bool MGBDHBHCDMH` (inherited from `MKAMHOIHOJK`) +- `bool MIKDLDEALPN` (inherited from `MKAMHOIHOJK`) +- `bool OFONEIOEIED` (inherited from `MKAMHOIHOJK`) +- `HJPGEGENLPH OILEJFNPDDB` (inherited from `MKAMHOIHOJK`) +- `bool PEEFHKMOMKK` (inherited from `MKAMHOIHOJK`) + +## playlists / playlists/{0}/accessibility + +- `EJDCNGBEICB` `` RecRoom.Async.IPromise`1 KLHJFKPFPIC(System.Int64 DDMPFMPILCE, DPLPMKMFMPB PONCIIJOHIE) `` (EJDCNGBEICB.txt:7323) + +Expected client return: `BMFAGMFKODA` (object) +Resolved DTO: `BMFAGMFKODA` from `BMFAGMFKODA.cs` +Declaration: `public class BMFAGMFKODA : KMKPEOGJDFK` +Inherits: `KMKPEOGJDFK` +Inherited parser JSON keys: `PlaylistId` +Public/decompiled members: +- `IReadOnlyList BIODFICLKJH` +- `IReadOnlyList DFKGKGIMEMP` +- `IReadOnlyList FEBCCOFBKKJ` +- `IReadOnlyList HEFJFKFNNCJ` +- `IReadOnlyList IFMMBHKGHMB` +- `IReadOnlyList KFFBPMHGFKB` +- `IReadOnlyList PKKADKGDHNI` +- `string tag` +- `long BBGEDJMGFFO` (inherited from `KMKPEOGJDFK`) +- `DateTime ACBFDMLHFPB` (inherited from `MKAMHOIHOJK`) +- `string AHGCOGFEEEE` (inherited from `MKAMHOIHOJK`) +- `bool AKFBNELAMNA` (inherited from `MKAMHOIHOJK`) +- `int BADIGBCKECA` (inherited from `MKAMHOIHOJK`) +- `bool BNBLOBAEDEE` (inherited from `MKAMHOIHOJK`) +- `NMJEKMMBDDE CDINMMPNAID` (inherited from `MKAMHOIHOJK`) +- `bool CDNFGMHLDMJ` (inherited from `MKAMHOIHOJK`) +- `string FIKEBGGCDFN` (inherited from `MKAMHOIHOJK`) +- `GPDIAKNEBKH GIBHIMGJNNO` (inherited from `MKAMHOIHOJK`) +- `bool HPLBOMGACED` (inherited from `MKAMHOIHOJK`) +- `string IGOPGMHHLKI` (inherited from `MKAMHOIHOJK`) +- `DPLPMKMFMPB JFEAPMIPNEP` (inherited from `MKAMHOIHOJK`) +- `bool KHIJAFCHLIA` (inherited from `MKAMHOIHOJK`) +- `bool KLNJBBPNMBJ` (inherited from `MKAMHOIHOJK`) +- `string KODBEJPEFOJ` (inherited from `MKAMHOIHOJK`) +- `bool LPJLEMJFBPE` (inherited from `MKAMHOIHOJK`) +- `bool MGBDHBHCDMH` (inherited from `MKAMHOIHOJK`) +- `bool MIKDLDEALPN` (inherited from `MKAMHOIHOJK`) +- `bool OFONEIOEIED` (inherited from `MKAMHOIHOJK`) +- `HJPGEGENLPH OILEJFNPDDB` (inherited from `MKAMHOIHOJK`) +- `bool PEEFHKMOMKK` (inherited from `MKAMHOIHOJK`) + +## playlists / playlists/{0}/description + +- `EJDCNGBEICB` `` RecRoom.Async.IPromise`1 EPJMNNPLNDM(System.Int64 DDMPFMPILCE, System.String LJIGOCDPEJF) `` (EJDCNGBEICB.txt:6839) + +Expected client return: `BMFAGMFKODA` (object) +Resolved DTO: `BMFAGMFKODA` from `BMFAGMFKODA.cs` +Declaration: `public class BMFAGMFKODA : KMKPEOGJDFK` +Inherits: `KMKPEOGJDFK` +Inherited parser JSON keys: `PlaylistId` +Public/decompiled members: +- `IReadOnlyList BIODFICLKJH` +- `IReadOnlyList DFKGKGIMEMP` +- `IReadOnlyList FEBCCOFBKKJ` +- `IReadOnlyList HEFJFKFNNCJ` +- `IReadOnlyList IFMMBHKGHMB` +- `IReadOnlyList KFFBPMHGFKB` +- `IReadOnlyList PKKADKGDHNI` +- `string tag` +- `long BBGEDJMGFFO` (inherited from `KMKPEOGJDFK`) +- `DateTime ACBFDMLHFPB` (inherited from `MKAMHOIHOJK`) +- `string AHGCOGFEEEE` (inherited from `MKAMHOIHOJK`) +- `bool AKFBNELAMNA` (inherited from `MKAMHOIHOJK`) +- `int BADIGBCKECA` (inherited from `MKAMHOIHOJK`) +- `bool BNBLOBAEDEE` (inherited from `MKAMHOIHOJK`) +- `NMJEKMMBDDE CDINMMPNAID` (inherited from `MKAMHOIHOJK`) +- `bool CDNFGMHLDMJ` (inherited from `MKAMHOIHOJK`) +- `string FIKEBGGCDFN` (inherited from `MKAMHOIHOJK`) +- `GPDIAKNEBKH GIBHIMGJNNO` (inherited from `MKAMHOIHOJK`) +- `bool HPLBOMGACED` (inherited from `MKAMHOIHOJK`) +- `string IGOPGMHHLKI` (inherited from `MKAMHOIHOJK`) +- `DPLPMKMFMPB JFEAPMIPNEP` (inherited from `MKAMHOIHOJK`) +- `bool KHIJAFCHLIA` (inherited from `MKAMHOIHOJK`) +- `bool KLNJBBPNMBJ` (inherited from `MKAMHOIHOJK`) +- `string KODBEJPEFOJ` (inherited from `MKAMHOIHOJK`) +- `bool LPJLEMJFBPE` (inherited from `MKAMHOIHOJK`) +- `bool MGBDHBHCDMH` (inherited from `MKAMHOIHOJK`) +- `bool MIKDLDEALPN` (inherited from `MKAMHOIHOJK`) +- `bool OFONEIOEIED` (inherited from `MKAMHOIHOJK`) +- `HJPGEGENLPH OILEJFNPDDB` (inherited from `MKAMHOIHOJK`) +- `bool PEEFHKMOMKK` (inherited from `MKAMHOIHOJK`) + +## playlists / playlists/{0}/image + +- `EJDCNGBEICB` `` RecRoom.Async.IPromise`1 CKMILKDKDCN(System.Int64 DDMPFMPILCE, System.String HFLPBHHAFIO) `` (EJDCNGBEICB.txt:6955) + +Expected client return: `BMFAGMFKODA` (object) +Resolved DTO: `BMFAGMFKODA` from `BMFAGMFKODA.cs` +Declaration: `public class BMFAGMFKODA : KMKPEOGJDFK` +Inherits: `KMKPEOGJDFK` +Inherited parser JSON keys: `PlaylistId` +Public/decompiled members: +- `IReadOnlyList BIODFICLKJH` +- `IReadOnlyList DFKGKGIMEMP` +- `IReadOnlyList FEBCCOFBKKJ` +- `IReadOnlyList HEFJFKFNNCJ` +- `IReadOnlyList IFMMBHKGHMB` +- `IReadOnlyList KFFBPMHGFKB` +- `IReadOnlyList PKKADKGDHNI` +- `string tag` +- `long BBGEDJMGFFO` (inherited from `KMKPEOGJDFK`) +- `DateTime ACBFDMLHFPB` (inherited from `MKAMHOIHOJK`) +- `string AHGCOGFEEEE` (inherited from `MKAMHOIHOJK`) +- `bool AKFBNELAMNA` (inherited from `MKAMHOIHOJK`) +- `int BADIGBCKECA` (inherited from `MKAMHOIHOJK`) +- `bool BNBLOBAEDEE` (inherited from `MKAMHOIHOJK`) +- `NMJEKMMBDDE CDINMMPNAID` (inherited from `MKAMHOIHOJK`) +- `bool CDNFGMHLDMJ` (inherited from `MKAMHOIHOJK`) +- `string FIKEBGGCDFN` (inherited from `MKAMHOIHOJK`) +- `GPDIAKNEBKH GIBHIMGJNNO` (inherited from `MKAMHOIHOJK`) +- `bool HPLBOMGACED` (inherited from `MKAMHOIHOJK`) +- `string IGOPGMHHLKI` (inherited from `MKAMHOIHOJK`) +- `DPLPMKMFMPB JFEAPMIPNEP` (inherited from `MKAMHOIHOJK`) +- `bool KHIJAFCHLIA` (inherited from `MKAMHOIHOJK`) +- `bool KLNJBBPNMBJ` (inherited from `MKAMHOIHOJK`) +- `string KODBEJPEFOJ` (inherited from `MKAMHOIHOJK`) +- `bool LPJLEMJFBPE` (inherited from `MKAMHOIHOJK`) +- `bool MGBDHBHCDMH` (inherited from `MKAMHOIHOJK`) +- `bool MIKDLDEALPN` (inherited from `MKAMHOIHOJK`) +- `bool OFONEIOEIED` (inherited from `MKAMHOIHOJK`) +- `HJPGEGENLPH OILEJFNPDDB` (inherited from `MKAMHOIHOJK`) +- `bool PEEFHKMOMKK` (inherited from `MKAMHOIHOJK`) + +## playlists / playlists/{0}/interactionby/me + +- `EJDCNGBEICB` `` RecRoom.Async.IPromise`1 AINDJCIMJOB(System.Int64 DDMPFMPILCE) `` (EJDCNGBEICB.txt:8600) + +Expected client return: `CJODCLDGFCF` (object) +Resolved DTO: `CJODCLDGFCF` from `CJODCLDGFCF.cs` +Declaration: `public class CJODCLDGFCF : IFAIJAGLDFK` +Client parser JSON keys: `Cheered`, `Favorited` +Public/decompiled members: +- `Nullable EFCGNDGOFNK` +- `bool EMBNPPMOJFJ` +- `bool HHFLKFOEHNL` + +## playlists / playlists/{0}/interactionby/me/cheer + +- `EJDCNGBEICB` `RecRoom.Async.IPromise CIPEENNIAFL(System.Int64 DDMPFMPILCE)` (EJDCNGBEICB.txt:8733) +- `EJDCNGBEICB` `RecRoom.Async.IPromise LONLMONNPGL(System.Int64 DDMPFMPILCE)` (EJDCNGBEICB.txt:8665) + +Expected client return: `RecRoom.Async.IPromise` (success-or-empty) +Resolved DTO: `void/success` not found in readable C# dump. + +## playlists / playlists/{0}/interactionby/me/favorite + +- `EJDCNGBEICB` `RecRoom.Async.IPromise GDELFOHOPGF(System.Int64 DDMPFMPILCE)` (EJDCNGBEICB.txt:8801) +- `EJDCNGBEICB` `RecRoom.Async.IPromise NLNKEGDKFCG(System.Int64 DDMPFMPILCE)` (EJDCNGBEICB.txt:8869) + +Expected client return: `RecRoom.Async.IPromise` (success-or-empty) +Resolved DTO: `void/success` not found in readable C# dump. + +## playlists / playlists/{0}/levelvoting + +- `EJDCNGBEICB` `` RecRoom.Async.IPromise`1 PPBLDOPNJKC(System.Int64 DDMPFMPILCE, System.Boolean NNBHCDIKILH) `` (EJDCNGBEICB.txt:7582) + +Expected client return: `BMFAGMFKODA` (object) +Resolved DTO: `BMFAGMFKODA` from `BMFAGMFKODA.cs` +Declaration: `public class BMFAGMFKODA : KMKPEOGJDFK` +Inherits: `KMKPEOGJDFK` +Inherited parser JSON keys: `PlaylistId` +Public/decompiled members: +- `IReadOnlyList BIODFICLKJH` +- `IReadOnlyList DFKGKGIMEMP` +- `IReadOnlyList FEBCCOFBKKJ` +- `IReadOnlyList HEFJFKFNNCJ` +- `IReadOnlyList IFMMBHKGHMB` +- `IReadOnlyList KFFBPMHGFKB` +- `IReadOnlyList PKKADKGDHNI` +- `string tag` +- `long BBGEDJMGFFO` (inherited from `KMKPEOGJDFK`) +- `DateTime ACBFDMLHFPB` (inherited from `MKAMHOIHOJK`) +- `string AHGCOGFEEEE` (inherited from `MKAMHOIHOJK`) +- `bool AKFBNELAMNA` (inherited from `MKAMHOIHOJK`) +- `int BADIGBCKECA` (inherited from `MKAMHOIHOJK`) +- `bool BNBLOBAEDEE` (inherited from `MKAMHOIHOJK`) +- `NMJEKMMBDDE CDINMMPNAID` (inherited from `MKAMHOIHOJK`) +- `bool CDNFGMHLDMJ` (inherited from `MKAMHOIHOJK`) +- `string FIKEBGGCDFN` (inherited from `MKAMHOIHOJK`) +- `GPDIAKNEBKH GIBHIMGJNNO` (inherited from `MKAMHOIHOJK`) +- `bool HPLBOMGACED` (inherited from `MKAMHOIHOJK`) +- `string IGOPGMHHLKI` (inherited from `MKAMHOIHOJK`) +- `DPLPMKMFMPB JFEAPMIPNEP` (inherited from `MKAMHOIHOJK`) +- `bool KHIJAFCHLIA` (inherited from `MKAMHOIHOJK`) +- `bool KLNJBBPNMBJ` (inherited from `MKAMHOIHOJK`) +- `string KODBEJPEFOJ` (inherited from `MKAMHOIHOJK`) +- `bool LPJLEMJFBPE` (inherited from `MKAMHOIHOJK`) +- `bool MGBDHBHCDMH` (inherited from `MKAMHOIHOJK`) +- `bool MIKDLDEALPN` (inherited from `MKAMHOIHOJK`) +- `bool OFONEIOEIED` (inherited from `MKAMHOIHOJK`) +- `HJPGEGENLPH OILEJFNPDDB` (inherited from `MKAMHOIHOJK`) +- `bool PEEFHKMOMKK` (inherited from `MKAMHOIHOJK`) + +## playlists / playlists/{0}/name + +- `EJDCNGBEICB` `` RecRoom.Async.IPromise`1 NINPMBNNHMG(System.Int64 DDMPFMPILCE, System.String MMBOKOLAJFH) `` (EJDCNGBEICB.txt:6723) + +Expected client return: `BMFAGMFKODA` (object) +Resolved DTO: `BMFAGMFKODA` from `BMFAGMFKODA.cs` +Declaration: `public class BMFAGMFKODA : KMKPEOGJDFK` +Inherits: `KMKPEOGJDFK` +Inherited parser JSON keys: `PlaylistId` +Public/decompiled members: +- `IReadOnlyList BIODFICLKJH` +- `IReadOnlyList DFKGKGIMEMP` +- `IReadOnlyList FEBCCOFBKKJ` +- `IReadOnlyList HEFJFKFNNCJ` +- `IReadOnlyList IFMMBHKGHMB` +- `IReadOnlyList KFFBPMHGFKB` +- `IReadOnlyList PKKADKGDHNI` +- `string tag` +- `long BBGEDJMGFFO` (inherited from `KMKPEOGJDFK`) +- `DateTime ACBFDMLHFPB` (inherited from `MKAMHOIHOJK`) +- `string AHGCOGFEEEE` (inherited from `MKAMHOIHOJK`) +- `bool AKFBNELAMNA` (inherited from `MKAMHOIHOJK`) +- `int BADIGBCKECA` (inherited from `MKAMHOIHOJK`) +- `bool BNBLOBAEDEE` (inherited from `MKAMHOIHOJK`) +- `NMJEKMMBDDE CDINMMPNAID` (inherited from `MKAMHOIHOJK`) +- `bool CDNFGMHLDMJ` (inherited from `MKAMHOIHOJK`) +- `string FIKEBGGCDFN` (inherited from `MKAMHOIHOJK`) +- `GPDIAKNEBKH GIBHIMGJNNO` (inherited from `MKAMHOIHOJK`) +- `bool HPLBOMGACED` (inherited from `MKAMHOIHOJK`) +- `string IGOPGMHHLKI` (inherited from `MKAMHOIHOJK`) +- `DPLPMKMFMPB JFEAPMIPNEP` (inherited from `MKAMHOIHOJK`) +- `bool KHIJAFCHLIA` (inherited from `MKAMHOIHOJK`) +- `bool KLNJBBPNMBJ` (inherited from `MKAMHOIHOJK`) +- `string KODBEJPEFOJ` (inherited from `MKAMHOIHOJK`) +- `bool LPJLEMJFBPE` (inherited from `MKAMHOIHOJK`) +- `bool MGBDHBHCDMH` (inherited from `MKAMHOIHOJK`) +- `bool MIKDLDEALPN` (inherited from `MKAMHOIHOJK`) +- `bool OFONEIOEIED` (inherited from `MKAMHOIHOJK`) +- `HJPGEGENLPH OILEJFNPDDB` (inherited from `MKAMHOIHOJK`) +- `bool PEEFHKMOMKK` (inherited from `MKAMHOIHOJK`) + +## playlists / playlists/{0}/restrictions + +- `EJDCNGBEICB` `` RecRoom.Async.IPromise`1 LDLJKJLNLEE(System.Int64 DDMPFMPILCE, System.Boolean DDMOEIAHJBK, System.Boolean FOBLJIBLCNI, System.Boolean GELHFEFGJLA, System.Boolean NFIGHDPKLJF) `` (EJDCNGBEICB.txt:7454) + +Expected client return: `BMFAGMFKODA` (object) +Resolved DTO: `BMFAGMFKODA` from `BMFAGMFKODA.cs` +Declaration: `public class BMFAGMFKODA : KMKPEOGJDFK` +Inherits: `KMKPEOGJDFK` +Inherited parser JSON keys: `PlaylistId` +Public/decompiled members: +- `IReadOnlyList BIODFICLKJH` +- `IReadOnlyList DFKGKGIMEMP` +- `IReadOnlyList FEBCCOFBKKJ` +- `IReadOnlyList HEFJFKFNNCJ` +- `IReadOnlyList IFMMBHKGHMB` +- `IReadOnlyList KFFBPMHGFKB` +- `IReadOnlyList PKKADKGDHNI` +- `string tag` +- `long BBGEDJMGFFO` (inherited from `KMKPEOGJDFK`) +- `DateTime ACBFDMLHFPB` (inherited from `MKAMHOIHOJK`) +- `string AHGCOGFEEEE` (inherited from `MKAMHOIHOJK`) +- `bool AKFBNELAMNA` (inherited from `MKAMHOIHOJK`) +- `int BADIGBCKECA` (inherited from `MKAMHOIHOJK`) +- `bool BNBLOBAEDEE` (inherited from `MKAMHOIHOJK`) +- `NMJEKMMBDDE CDINMMPNAID` (inherited from `MKAMHOIHOJK`) +- `bool CDNFGMHLDMJ` (inherited from `MKAMHOIHOJK`) +- `string FIKEBGGCDFN` (inherited from `MKAMHOIHOJK`) +- `GPDIAKNEBKH GIBHIMGJNNO` (inherited from `MKAMHOIHOJK`) +- `bool HPLBOMGACED` (inherited from `MKAMHOIHOJK`) +- `string IGOPGMHHLKI` (inherited from `MKAMHOIHOJK`) +- `DPLPMKMFMPB JFEAPMIPNEP` (inherited from `MKAMHOIHOJK`) +- `bool KHIJAFCHLIA` (inherited from `MKAMHOIHOJK`) +- `bool KLNJBBPNMBJ` (inherited from `MKAMHOIHOJK`) +- `string KODBEJPEFOJ` (inherited from `MKAMHOIHOJK`) +- `bool LPJLEMJFBPE` (inherited from `MKAMHOIHOJK`) +- `bool MGBDHBHCDMH` (inherited from `MKAMHOIHOJK`) +- `bool MIKDLDEALPN` (inherited from `MKAMHOIHOJK`) +- `bool OFONEIOEIED` (inherited from `MKAMHOIHOJK`) +- `HJPGEGENLPH OILEJFNPDDB` (inherited from `MKAMHOIHOJK`) +- `bool PEEFHKMOMKK` (inherited from `MKAMHOIHOJK`) + +## playlists / playlists/{0}/rooms/{1} + +- `EJDCNGBEICB` `` RecRoom.Async.IPromise`1 JBMCPFHGIMD(System.Int64 DDMPFMPILCE, System.Int64 HNHLJONGKHB) `` (EJDCNGBEICB.txt:7765) +- `EJDCNGBEICB` `` RecRoom.Async.IPromise`1 NLOEBFMNCFM(System.Int64 DDMPFMPILCE, System.Int64 HNHLJONGKHB) `` (EJDCNGBEICB.txt:7679) + +Expected client return: `BMFAGMFKODA` (object) +Resolved DTO: `BMFAGMFKODA` from `BMFAGMFKODA.cs` +Declaration: `public class BMFAGMFKODA : KMKPEOGJDFK` +Inherits: `KMKPEOGJDFK` +Inherited parser JSON keys: `PlaylistId` +Public/decompiled members: +- `IReadOnlyList BIODFICLKJH` +- `IReadOnlyList DFKGKGIMEMP` +- `IReadOnlyList FEBCCOFBKKJ` +- `IReadOnlyList HEFJFKFNNCJ` +- `IReadOnlyList IFMMBHKGHMB` +- `IReadOnlyList KFFBPMHGFKB` +- `IReadOnlyList PKKADKGDHNI` +- `string tag` +- `long BBGEDJMGFFO` (inherited from `KMKPEOGJDFK`) +- `DateTime ACBFDMLHFPB` (inherited from `MKAMHOIHOJK`) +- `string AHGCOGFEEEE` (inherited from `MKAMHOIHOJK`) +- `bool AKFBNELAMNA` (inherited from `MKAMHOIHOJK`) +- `int BADIGBCKECA` (inherited from `MKAMHOIHOJK`) +- `bool BNBLOBAEDEE` (inherited from `MKAMHOIHOJK`) +- `NMJEKMMBDDE CDINMMPNAID` (inherited from `MKAMHOIHOJK`) +- `bool CDNFGMHLDMJ` (inherited from `MKAMHOIHOJK`) +- `string FIKEBGGCDFN` (inherited from `MKAMHOIHOJK`) +- `GPDIAKNEBKH GIBHIMGJNNO` (inherited from `MKAMHOIHOJK`) +- `bool HPLBOMGACED` (inherited from `MKAMHOIHOJK`) +- `string IGOPGMHHLKI` (inherited from `MKAMHOIHOJK`) +- `DPLPMKMFMPB JFEAPMIPNEP` (inherited from `MKAMHOIHOJK`) +- `bool KHIJAFCHLIA` (inherited from `MKAMHOIHOJK`) +- `bool KLNJBBPNMBJ` (inherited from `MKAMHOIHOJK`) +- `string KODBEJPEFOJ` (inherited from `MKAMHOIHOJK`) +- `bool LPJLEMJFBPE` (inherited from `MKAMHOIHOJK`) +- `bool MGBDHBHCDMH` (inherited from `MKAMHOIHOJK`) +- `bool MIKDLDEALPN` (inherited from `MKAMHOIHOJK`) +- `bool OFONEIOEIED` (inherited from `MKAMHOIHOJK`) +- `HJPGEGENLPH OILEJFNPDDB` (inherited from `MKAMHOIHOJK`) +- `bool PEEFHKMOMKK` (inherited from `MKAMHOIHOJK`) + +## playlists / playlists/{0}/tags + +- `EJDCNGBEICB` `` RecRoom.Async.IPromise`1 ALCFNJJHDMG(System.Int64 DDMPFMPILCE, System.Collections.Generic.IReadOnlyList`1 LBAOCHFCLPO, System.Collections.Generic.IReadOnlyList`1 CAFPJPHILMN) `` (EJDCNGBEICB.txt:7080) + +Expected client return: `BMFAGMFKODA` (object) +Resolved DTO: `BMFAGMFKODA` from `BMFAGMFKODA.cs` +Declaration: `public class BMFAGMFKODA : KMKPEOGJDFK` +Inherits: `KMKPEOGJDFK` +Inherited parser JSON keys: `PlaylistId` +Public/decompiled members: +- `IReadOnlyList BIODFICLKJH` +- `IReadOnlyList DFKGKGIMEMP` +- `IReadOnlyList FEBCCOFBKKJ` +- `IReadOnlyList HEFJFKFNNCJ` +- `IReadOnlyList IFMMBHKGHMB` +- `IReadOnlyList KFFBPMHGFKB` +- `IReadOnlyList PKKADKGDHNI` +- `string tag` +- `long BBGEDJMGFFO` (inherited from `KMKPEOGJDFK`) +- `DateTime ACBFDMLHFPB` (inherited from `MKAMHOIHOJK`) +- `string AHGCOGFEEEE` (inherited from `MKAMHOIHOJK`) +- `bool AKFBNELAMNA` (inherited from `MKAMHOIHOJK`) +- `int BADIGBCKECA` (inherited from `MKAMHOIHOJK`) +- `bool BNBLOBAEDEE` (inherited from `MKAMHOIHOJK`) +- `NMJEKMMBDDE CDINMMPNAID` (inherited from `MKAMHOIHOJK`) +- `bool CDNFGMHLDMJ` (inherited from `MKAMHOIHOJK`) +- `string FIKEBGGCDFN` (inherited from `MKAMHOIHOJK`) +- `GPDIAKNEBKH GIBHIMGJNNO` (inherited from `MKAMHOIHOJK`) +- `bool HPLBOMGACED` (inherited from `MKAMHOIHOJK`) +- `string IGOPGMHHLKI` (inherited from `MKAMHOIHOJK`) +- `DPLPMKMFMPB JFEAPMIPNEP` (inherited from `MKAMHOIHOJK`) +- `bool KHIJAFCHLIA` (inherited from `MKAMHOIHOJK`) +- `bool KLNJBBPNMBJ` (inherited from `MKAMHOIHOJK`) +- `string KODBEJPEFOJ` (inherited from `MKAMHOIHOJK`) +- `bool LPJLEMJFBPE` (inherited from `MKAMHOIHOJK`) +- `bool MGBDHBHCDMH` (inherited from `MKAMHOIHOJK`) +- `bool MIKDLDEALPN` (inherited from `MKAMHOIHOJK`) +- `bool OFONEIOEIED` (inherited from `MKAMHOIHOJK`) +- `HJPGEGENLPH OILEJFNPDDB` (inherited from `MKAMHOIHOJK`) +- `bool PEEFHKMOMKK` (inherited from `MKAMHOIHOJK`) + +## playlists / playlists/{0}/warning + +- `EJDCNGBEICB` `` RecRoom.Async.IPromise`1 KEADPPJCJGG(System.Int64 DDMPFMPILCE, GPDIAKNEBKH LEFPAAGGFFA, System.String NLEBEKDHJIJ) `` (EJDCNGBEICB.txt:7206) + +Expected client return: `BMFAGMFKODA` (object) +Resolved DTO: `BMFAGMFKODA` from `BMFAGMFKODA.cs` +Declaration: `public class BMFAGMFKODA : KMKPEOGJDFK` +Inherits: `KMKPEOGJDFK` +Inherited parser JSON keys: `PlaylistId` +Public/decompiled members: +- `IReadOnlyList BIODFICLKJH` +- `IReadOnlyList DFKGKGIMEMP` +- `IReadOnlyList FEBCCOFBKKJ` +- `IReadOnlyList HEFJFKFNNCJ` +- `IReadOnlyList IFMMBHKGHMB` +- `IReadOnlyList KFFBPMHGFKB` +- `IReadOnlyList PKKADKGDHNI` +- `string tag` +- `long BBGEDJMGFFO` (inherited from `KMKPEOGJDFK`) +- `DateTime ACBFDMLHFPB` (inherited from `MKAMHOIHOJK`) +- `string AHGCOGFEEEE` (inherited from `MKAMHOIHOJK`) +- `bool AKFBNELAMNA` (inherited from `MKAMHOIHOJK`) +- `int BADIGBCKECA` (inherited from `MKAMHOIHOJK`) +- `bool BNBLOBAEDEE` (inherited from `MKAMHOIHOJK`) +- `NMJEKMMBDDE CDINMMPNAID` (inherited from `MKAMHOIHOJK`) +- `bool CDNFGMHLDMJ` (inherited from `MKAMHOIHOJK`) +- `string FIKEBGGCDFN` (inherited from `MKAMHOIHOJK`) +- `GPDIAKNEBKH GIBHIMGJNNO` (inherited from `MKAMHOIHOJK`) +- `bool HPLBOMGACED` (inherited from `MKAMHOIHOJK`) +- `string IGOPGMHHLKI` (inherited from `MKAMHOIHOJK`) +- `DPLPMKMFMPB JFEAPMIPNEP` (inherited from `MKAMHOIHOJK`) +- `bool KHIJAFCHLIA` (inherited from `MKAMHOIHOJK`) +- `bool KLNJBBPNMBJ` (inherited from `MKAMHOIHOJK`) +- `string KODBEJPEFOJ` (inherited from `MKAMHOIHOJK`) +- `bool LPJLEMJFBPE` (inherited from `MKAMHOIHOJK`) +- `bool MGBDHBHCDMH` (inherited from `MKAMHOIHOJK`) +- `bool MIKDLDEALPN` (inherited from `MKAMHOIHOJK`) +- `bool OFONEIOEIED` (inherited from `MKAMHOIHOJK`) +- `HJPGEGENLPH OILEJFNPDDB` (inherited from `MKAMHOIHOJK`) +- `bool PEEFHKMOMKK` (inherited from `MKAMHOIHOJK`) + +## playlists / playlists/bulk + +- `EJDCNGBEICB` `` RecRoom.Async.IPromise`1> KHMJIKNHJHP(System.Collections.Generic.IReadOnlyList`1 ANIKDNFLDIG) `` (EJDCNGBEICB.txt:1896) +- `EJDCNGBEICB` `` RecRoom.Async.IPromise`1> KHMJIKNHJHP(System.Collections.Generic.IReadOnlyList`1 CJMAAFFAKDO) `` (EJDCNGBEICB.txt:2068) + +Expected client return: `` System.Collections.Generic.List`1 `` (array) +Resolved DTO: `KMKPEOGJDFK` from `KMKPEOGJDFK.cs` +Declaration: `public class KMKPEOGJDFK : MKAMHOIHOJK` +Inherits: `MKAMHOIHOJK` +Client parser JSON keys: `PlaylistId` +Inherited parser JSON keys: `Name`, `Description`, `ImageName`, `WarningMask`, `CustomWarning`, `CreatorAccountId`, `State`, `Accessibility`, `SupportsLevelVoting`, `IsRRO`, `SupportsScreens`, `SupportsWalkVR`, `SupportsTeleportVR`, `SupportsVRLow`, `SupportsQuest2`, `SupportsMobile`, `SupportsJuniors`, `CreatedAt`, `Stats` +Public/decompiled members: +- `long BBGEDJMGFFO` +- `DateTime ACBFDMLHFPB` (inherited from `MKAMHOIHOJK`) +- `string AHGCOGFEEEE` (inherited from `MKAMHOIHOJK`) +- `bool AKFBNELAMNA` (inherited from `MKAMHOIHOJK`) +- `int BADIGBCKECA` (inherited from `MKAMHOIHOJK`) +- `bool BNBLOBAEDEE` (inherited from `MKAMHOIHOJK`) +- `NMJEKMMBDDE CDINMMPNAID` (inherited from `MKAMHOIHOJK`) +- `bool CDNFGMHLDMJ` (inherited from `MKAMHOIHOJK`) +- `string FIKEBGGCDFN` (inherited from `MKAMHOIHOJK`) +- `GPDIAKNEBKH GIBHIMGJNNO` (inherited from `MKAMHOIHOJK`) +- `bool HPLBOMGACED` (inherited from `MKAMHOIHOJK`) +- `string IGOPGMHHLKI` (inherited from `MKAMHOIHOJK`) +- `DPLPMKMFMPB JFEAPMIPNEP` (inherited from `MKAMHOIHOJK`) +- `bool KHIJAFCHLIA` (inherited from `MKAMHOIHOJK`) +- `bool KLNJBBPNMBJ` (inherited from `MKAMHOIHOJK`) +- `string KODBEJPEFOJ` (inherited from `MKAMHOIHOJK`) +- `bool LPJLEMJFBPE` (inherited from `MKAMHOIHOJK`) +- `bool MGBDHBHCDMH` (inherited from `MKAMHOIHOJK`) +- `bool MIKDLDEALPN` (inherited from `MKAMHOIHOJK`) +- `bool OFONEIOEIED` (inherited from `MKAMHOIHOJK`) +- `HJPGEGENLPH OILEJFNPDDB` (inherited from `MKAMHOIHOJK`) +- `bool PEEFHKMOMKK` (inherited from `MKAMHOIHOJK`) + +## playlists / playlists/cheeredby/me + +- `EJDCNGBEICB` `` RecRoom.Async.IPromise`1> EFFMOEGALDB() `` (EJDCNGBEICB.txt:2400) + +Expected client return: `` System.Collections.Generic.List`1 `` (array) +Resolved DTO: `KMKPEOGJDFK` from `KMKPEOGJDFK.cs` +Declaration: `public class KMKPEOGJDFK : MKAMHOIHOJK` +Inherits: `MKAMHOIHOJK` +Client parser JSON keys: `PlaylistId` +Inherited parser JSON keys: `Name`, `Description`, `ImageName`, `WarningMask`, `CustomWarning`, `CreatorAccountId`, `State`, `Accessibility`, `SupportsLevelVoting`, `IsRRO`, `SupportsScreens`, `SupportsWalkVR`, `SupportsTeleportVR`, `SupportsVRLow`, `SupportsQuest2`, `SupportsMobile`, `SupportsJuniors`, `CreatedAt`, `Stats` +Public/decompiled members: +- `long BBGEDJMGFFO` +- `DateTime ACBFDMLHFPB` (inherited from `MKAMHOIHOJK`) +- `string AHGCOGFEEEE` (inherited from `MKAMHOIHOJK`) +- `bool AKFBNELAMNA` (inherited from `MKAMHOIHOJK`) +- `int BADIGBCKECA` (inherited from `MKAMHOIHOJK`) +- `bool BNBLOBAEDEE` (inherited from `MKAMHOIHOJK`) +- `NMJEKMMBDDE CDINMMPNAID` (inherited from `MKAMHOIHOJK`) +- `bool CDNFGMHLDMJ` (inherited from `MKAMHOIHOJK`) +- `string FIKEBGGCDFN` (inherited from `MKAMHOIHOJK`) +- `GPDIAKNEBKH GIBHIMGJNNO` (inherited from `MKAMHOIHOJK`) +- `bool HPLBOMGACED` (inherited from `MKAMHOIHOJK`) +- `string IGOPGMHHLKI` (inherited from `MKAMHOIHOJK`) +- `DPLPMKMFMPB JFEAPMIPNEP` (inherited from `MKAMHOIHOJK`) +- `bool KHIJAFCHLIA` (inherited from `MKAMHOIHOJK`) +- `bool KLNJBBPNMBJ` (inherited from `MKAMHOIHOJK`) +- `string KODBEJPEFOJ` (inherited from `MKAMHOIHOJK`) +- `bool LPJLEMJFBPE` (inherited from `MKAMHOIHOJK`) +- `bool MGBDHBHCDMH` (inherited from `MKAMHOIHOJK`) +- `bool MIKDLDEALPN` (inherited from `MKAMHOIHOJK`) +- `bool OFONEIOEIED` (inherited from `MKAMHOIHOJK`) +- `HJPGEGENLPH OILEJFNPDDB` (inherited from `MKAMHOIHOJK`) +- `bool PEEFHKMOMKK` (inherited from `MKAMHOIHOJK`) + +## playlists / playlists/createdby/me + +- `EJDCNGBEICB` `` RecRoom.Async.IPromise`1> PLPMPFDOICH() `` (EJDCNGBEICB.txt:2361) + +Expected client return: `` System.Collections.Generic.List`1 `` (array) +Resolved DTO: `KMKPEOGJDFK` from `KMKPEOGJDFK.cs` +Declaration: `public class KMKPEOGJDFK : MKAMHOIHOJK` +Inherits: `MKAMHOIHOJK` +Client parser JSON keys: `PlaylistId` +Inherited parser JSON keys: `Name`, `Description`, `ImageName`, `WarningMask`, `CustomWarning`, `CreatorAccountId`, `State`, `Accessibility`, `SupportsLevelVoting`, `IsRRO`, `SupportsScreens`, `SupportsWalkVR`, `SupportsTeleportVR`, `SupportsVRLow`, `SupportsQuest2`, `SupportsMobile`, `SupportsJuniors`, `CreatedAt`, `Stats` +Public/decompiled members: +- `long BBGEDJMGFFO` +- `DateTime ACBFDMLHFPB` (inherited from `MKAMHOIHOJK`) +- `string AHGCOGFEEEE` (inherited from `MKAMHOIHOJK`) +- `bool AKFBNELAMNA` (inherited from `MKAMHOIHOJK`) +- `int BADIGBCKECA` (inherited from `MKAMHOIHOJK`) +- `bool BNBLOBAEDEE` (inherited from `MKAMHOIHOJK`) +- `NMJEKMMBDDE CDINMMPNAID` (inherited from `MKAMHOIHOJK`) +- `bool CDNFGMHLDMJ` (inherited from `MKAMHOIHOJK`) +- `string FIKEBGGCDFN` (inherited from `MKAMHOIHOJK`) +- `GPDIAKNEBKH GIBHIMGJNNO` (inherited from `MKAMHOIHOJK`) +- `bool HPLBOMGACED` (inherited from `MKAMHOIHOJK`) +- `string IGOPGMHHLKI` (inherited from `MKAMHOIHOJK`) +- `DPLPMKMFMPB JFEAPMIPNEP` (inherited from `MKAMHOIHOJK`) +- `bool KHIJAFCHLIA` (inherited from `MKAMHOIHOJK`) +- `bool KLNJBBPNMBJ` (inherited from `MKAMHOIHOJK`) +- `string KODBEJPEFOJ` (inherited from `MKAMHOIHOJK`) +- `bool LPJLEMJFBPE` (inherited from `MKAMHOIHOJK`) +- `bool MGBDHBHCDMH` (inherited from `MKAMHOIHOJK`) +- `bool MIKDLDEALPN` (inherited from `MKAMHOIHOJK`) +- `bool OFONEIOEIED` (inherited from `MKAMHOIHOJK`) +- `HJPGEGENLPH OILEJFNPDDB` (inherited from `MKAMHOIHOJK`) +- `bool PEEFHKMOMKK` (inherited from `MKAMHOIHOJK`) + +## playlists / playlists/favoritedby/me + +- `EJDCNGBEICB` `` RecRoom.Async.IPromise`1> FAMGMKHMCIN() `` (EJDCNGBEICB.txt:2439) + +Expected client return: `` System.Collections.Generic.List`1 `` (array) +Resolved DTO: `KMKPEOGJDFK` from `KMKPEOGJDFK.cs` +Declaration: `public class KMKPEOGJDFK : MKAMHOIHOJK` +Inherits: `MKAMHOIHOJK` +Client parser JSON keys: `PlaylistId` +Inherited parser JSON keys: `Name`, `Description`, `ImageName`, `WarningMask`, `CustomWarning`, `CreatorAccountId`, `State`, `Accessibility`, `SupportsLevelVoting`, `IsRRO`, `SupportsScreens`, `SupportsWalkVR`, `SupportsTeleportVR`, `SupportsVRLow`, `SupportsQuest2`, `SupportsMobile`, `SupportsJuniors`, `CreatedAt`, `Stats` +Public/decompiled members: +- `long BBGEDJMGFFO` +- `DateTime ACBFDMLHFPB` (inherited from `MKAMHOIHOJK`) +- `string AHGCOGFEEEE` (inherited from `MKAMHOIHOJK`) +- `bool AKFBNELAMNA` (inherited from `MKAMHOIHOJK`) +- `int BADIGBCKECA` (inherited from `MKAMHOIHOJK`) +- `bool BNBLOBAEDEE` (inherited from `MKAMHOIHOJK`) +- `NMJEKMMBDDE CDINMMPNAID` (inherited from `MKAMHOIHOJK`) +- `bool CDNFGMHLDMJ` (inherited from `MKAMHOIHOJK`) +- `string FIKEBGGCDFN` (inherited from `MKAMHOIHOJK`) +- `GPDIAKNEBKH GIBHIMGJNNO` (inherited from `MKAMHOIHOJK`) +- `bool HPLBOMGACED` (inherited from `MKAMHOIHOJK`) +- `string IGOPGMHHLKI` (inherited from `MKAMHOIHOJK`) +- `DPLPMKMFMPB JFEAPMIPNEP` (inherited from `MKAMHOIHOJK`) +- `bool KHIJAFCHLIA` (inherited from `MKAMHOIHOJK`) +- `bool KLNJBBPNMBJ` (inherited from `MKAMHOIHOJK`) +- `string KODBEJPEFOJ` (inherited from `MKAMHOIHOJK`) +- `bool LPJLEMJFBPE` (inherited from `MKAMHOIHOJK`) +- `bool MGBDHBHCDMH` (inherited from `MKAMHOIHOJK`) +- `bool MIKDLDEALPN` (inherited from `MKAMHOIHOJK`) +- `bool OFONEIOEIED` (inherited from `MKAMHOIHOJK`) +- `HJPGEGENLPH OILEJFNPDDB` (inherited from `MKAMHOIHOJK`) +- `bool PEEFHKMOMKK` (inherited from `MKAMHOIHOJK`) + +## playlists / playlists/visitedby/me + +- `EJDCNGBEICB` `` RecRoom.Async.IPromise`1> OMMENACPKGH() `` (EJDCNGBEICB.txt:2478) + +Expected client return: `` System.Collections.Generic.List`1 `` (array) +Resolved DTO: `KMKPEOGJDFK` from `KMKPEOGJDFK.cs` +Declaration: `public class KMKPEOGJDFK : MKAMHOIHOJK` +Inherits: `MKAMHOIHOJK` +Client parser JSON keys: `PlaylistId` +Inherited parser JSON keys: `Name`, `Description`, `ImageName`, `WarningMask`, `CustomWarning`, `CreatorAccountId`, `State`, `Accessibility`, `SupportsLevelVoting`, `IsRRO`, `SupportsScreens`, `SupportsWalkVR`, `SupportsTeleportVR`, `SupportsVRLow`, `SupportsQuest2`, `SupportsMobile`, `SupportsJuniors`, `CreatedAt`, `Stats` +Public/decompiled members: +- `long BBGEDJMGFFO` +- `DateTime ACBFDMLHFPB` (inherited from `MKAMHOIHOJK`) +- `string AHGCOGFEEEE` (inherited from `MKAMHOIHOJK`) +- `bool AKFBNELAMNA` (inherited from `MKAMHOIHOJK`) +- `int BADIGBCKECA` (inherited from `MKAMHOIHOJK`) +- `bool BNBLOBAEDEE` (inherited from `MKAMHOIHOJK`) +- `NMJEKMMBDDE CDINMMPNAID` (inherited from `MKAMHOIHOJK`) +- `bool CDNFGMHLDMJ` (inherited from `MKAMHOIHOJK`) +- `string FIKEBGGCDFN` (inherited from `MKAMHOIHOJK`) +- `GPDIAKNEBKH GIBHIMGJNNO` (inherited from `MKAMHOIHOJK`) +- `bool HPLBOMGACED` (inherited from `MKAMHOIHOJK`) +- `string IGOPGMHHLKI` (inherited from `MKAMHOIHOJK`) +- `DPLPMKMFMPB JFEAPMIPNEP` (inherited from `MKAMHOIHOJK`) +- `bool KHIJAFCHLIA` (inherited from `MKAMHOIHOJK`) +- `bool KLNJBBPNMBJ` (inherited from `MKAMHOIHOJK`) +- `string KODBEJPEFOJ` (inherited from `MKAMHOIHOJK`) +- `bool LPJLEMJFBPE` (inherited from `MKAMHOIHOJK`) +- `bool MGBDHBHCDMH` (inherited from `MKAMHOIHOJK`) +- `bool MIKDLDEALPN` (inherited from `MKAMHOIHOJK`) +- `bool OFONEIOEIED` (inherited from `MKAMHOIHOJK`) +- `HJPGEGENLPH OILEJFNPDDB` (inherited from `MKAMHOIHOJK`) +- `bool PEEFHKMOMKK` (inherited from `MKAMHOIHOJK`) + +## playlists / roomsandplaylists/hot + +- `EJDCNGBEICB` `` RecRoom.Async.IPromise`1 EBBHGHEGBKG(System.Collections.Generic.IReadOnlyList`1 CAFPJPHILMN) `` (EJDCNGBEICB.txt:2645) + +Expected client return: `HJKAOMOICJG` (object) +Resolved DTO: `HJKAOMOICJG` from `HJKAOMOICJG.cs` +Declaration: `public class HJKAOMOICJG : NCONANPODKN, IFAIJAGLDFK` +Inherits: `NCONANPODKN` +Client parser JSON keys: `TotalResults` +Public/decompiled members: +- `long KPCBEDOLLFK` (inherited from `NCONANPODKN`) +- `IReadOnlyList PPLMJPLEHLP` (inherited from `NCONANPODKN`) + +## playlists / roomsandplaylists/search + +- `EJDCNGBEICB` `` RecRoom.Async.IPromise`1 HFHDFLDCIEM(System.String CNBKKCJAHPP) `` (EJDCNGBEICB.txt:2560) + +Expected client return: `HJKAOMOICJG` (object) +Resolved DTO: `HJKAOMOICJG` from `HJKAOMOICJG.cs` +Declaration: `public class HJKAOMOICJG : NCONANPODKN, IFAIJAGLDFK` +Inherits: `NCONANPODKN` +Client parser JSON keys: `TotalResults` +Public/decompiled members: +- `long KPCBEDOLLFK` (inherited from `NCONANPODKN`) +- `IReadOnlyList PPLMJPLEHLP` (inherited from `NCONANPODKN`) + +## quickplay / api/quickPlay/ + +- `ADOIEPDDEBO` `RecRoom.Async.IPromise CPGECIJBBKF()` (ADOIEPDDEBO.txt:358) + +Expected client return: `RecRoom.Async.IPromise` (success-or-empty) +Resolved DTO: `void/success` not found in readable C# dump. + +## reporting / {0}/api/userreporting + +- `KHGPLGBHIAH` `` System.Void PPOCPFGCALB(LGFHFDMDJKK GIGKODMKKHJ, System.Action`2 DFPGDEOCNIH, System.Action`2 AFLPGGJMPOE) `` (KHGPLGBHIAH.txt:2838) + +Expected client return: `unknown` (unknown) +Resolved DTO: `unknown` not found in readable C# dump. + +## reporting / api/banappeal/generateCode + +- `LCCEEFHOBEN` `` RecRoom.Async.IPromise`1 LONDNAJPCEE() `` (LCCEEFHOBEN.txt:2149) + +Expected client return: `System.String` (primitive) +Resolved DTO: `string` not found in readable C# dump. + +## reporting / api/PlayerReporting/ + +- `LCCEEFHOBEN` `` RecRoom.Async.IPromise FCPPPBKJFGK(LCCEEFHOBEN+OAODPJFGPAF GEDCEIDOKJL, System.String NGPMADFHHKP, System.Nullable`1 IMHODAEGGON = null) `` (LCCEEFHOBEN.txt:5148) +- `LCCEEFHOBEN` `RecRoom.Async.IPromise IEAOIIFNOGN(System.String PLDIBPNDJIO)` (LCCEEFHOBEN.txt:4715) +- `LCCEEFHOBEN` `` RecRoom.Async.IPromise`1 NEFPDKLMDHK() `` (LCCEEFHOBEN.txt:895) +- `LCCEEFHOBEN` `` RecRoom.Async.IPromise`1 EKIAMDFOLJM(System.Int64 CJFGEMGOJHB, System.Boolean MAOOLFEBOOD, System.String EMNPHAPBBAH) `` (LCCEEFHOBEN.txt:4216) +- `LCCEEFHOBEN` `` RecRoom.Async.IPromise`1 FFPIOECGNBI(System.Int32 CJFGEMGOJHB) `` (LCCEEFHOBEN.txt:4447) +- `LCCEEFHOBEN` `` RecRoom.Async.IPromise`1 HNFAAFGGDML(System.Collections.Generic.List`1 FNNIKBKHAFN) `` (LCCEEFHOBEN.txt:4586) +- `LCCEEFHOBEN` `` System.Collections.IEnumerator KNAHPBPBPAM(System.Int32 CJFGEMGOJHB, LCCEEFHOBEN+CJFENPHAAHI MEABFEIBEMP, System.String EFDBFLPKHKA, System.Nullable`1 HJMMCCKCDEH, BPHGKAEDBPE+OJDJIJDNFHE AFLPGGJMPOE) `` (LCCEEFHOBEN.txt:1783) + +Expected client return: `RecRoom.Async.IPromise` (success-or-empty) +Resolved DTO: `void/success` not found in readable C# dump. + +Expected client return: `HOGFDJNNMHM` (object) +Resolved DTO: `HOGFDJNNMHM` from `HOGFDJNNMHM.cs` +Declaration: `public class HOGFDJNNMHM : IFAIJAGLDFK` +Client parser JSON keys: `ReportCategory`, `Duration`, `GameSessionId`, `IsHostKick`, `PlayerIdReporter`, `VoteKickReason`, `Message`, `IsBan` +Public/decompiled members: +- `bool BKEOCBHCDLM` +- `int BPHIOMBNAAI` +- `string DFPPNHINEFO` +- `CJFENPHAAHI FNLBJMNDIOB` +- `long GMJJJKFMFMN` +- `bool JIJDNJNOLLF` +- `string KCBFFPAOCMF` +- `Nullable MCEFCFKMIIH` +- `float NJCIKENCJFC` + +Expected client return: `KLAMKCBENEA` (object) +Resolved DTO: `KLAMKCBENEA` from `KLAMKCBENEA.cs` +Declaration: `public class KLAMKCBENEA : IFAIJAGLDFK` +Client parser JSON keys: `Success`, `Message` +Public/decompiled members: +- `string DFPPNHINEFO` +- `bool ONGEANEKLNE` + +Expected client return: `IEnumerator` (callback-or-coroutine) +Resolved DTO: `callback` not found in readable C# dump. + +## reporting / api/PlayerReporting/v1/voteToKickReasons + +- `LCCEEFHOBEN` `RecRoom.Async.IPromise HHBLGNHOGLN()` (LCCEEFHOBEN.txt:2417) + +Expected client return: `RecRoom.Async.IPromise` (success-or-empty) +Resolved DTO: `void/success` not found in readable C# dump. + +## reporting / https://userreporting.cloud.unity3d.com/api/userreporting/projects/{0}/ping + +- `UserReportingScript` `System.Void Start()` (UserReportingScript.txt:1174) + +Expected client return: `unknown` (unknown) +Resolved DTO: `unknown` not found in readable C# dump. + +## room-keys / api/roomkeys/ + +- `IOIBJBLIBKM` `` RecRoom.Async.IPromise`1 ACCJPCMPJMH(System.Int64 BGICHOOBKLD) `` (IOIBJBLIBKM.txt:1299) +- `IOIBJBLIBKM` `` RecRoom.Async.IPromise`1 HECNNKLKKAG(System.Int64 BGICHOOBKLD) `` (IOIBJBLIBKM.txt:1517) +- `IOIBJBLIBKM` `` RecRoom.Async.IPromise`1 LAPDFFOJCNP(System.Int32 CJFGEMGOJHB, AMAGKLLBGEC AAHIIDDIBFD) `` (IOIBJBLIBKM.txt:1866) +- `IOIBJBLIBKM` `System.String JGLHIEEFHKE()` (IOIBJBLIBKM.txt:58) + +Expected client return: `BMHHFIGBOFD` (object) +Resolved DTO: `BMHHFIGBOFD` from `BMHHFIGBOFD.cs` +Declaration: `public enum BMHHFIGBOFD` +Enum values: `Success = 0`, `InvalidParameters = 1`, `DoesNotExist = 2`, `NameTooShort = 3`, `NameTooLong = 4`, `DuplicateName = 5`, `InappropriateName = 6`, `DescriptionTooShort = 7`, `DescriptionTooLong = 8`, `InappropriateDescription = 9`, `PriceTooLow = 10`, `PriceTooHigh = 11`, `PermissionDenied = 12`, `PlayerHasRoomUnderModerationReview = 13`, `JuniorStatusFail = 14`, `PlayerIsNotCoOwner = 15`, `RoomKeyLimitReached = 16`, `PlayerAlreadyOwns = 17`, `RoomUnderModerationReview = 18`, `PurchaseFailed = 19`, `RoomDoesNotExist = 20`, `PaidKeyPurchasingDisabled = 21`, `CreateOrModifyKeysDisabled = 22`, `RoomKeyUnderModerationReview = 23`, `PlayerRestrictedFromP2PSelling = 24`, `PlayerNotRecRoomPlusMember = 25` + +Expected client return: `System.Boolean` (primitive) +Resolved DTO: `boolean` not found in readable C# dump. + +Expected client return: `unknown` (unknown) +Resolved DTO: `unknown` not found in readable C# dump. + +## room-keys / api/roomkeys/v1/create + +- `IOIBJBLIBKM` `` RecRoom.Async.IPromise`1 BIAKPHAECJC(System.String MMBOKOLAJFH, System.String LJIGOCDPEJF, System.Int32 MACNIENMFHJ) `` (IOIBJBLIBKM.txt:573) + +Expected client return: `BCKIBFNPIPD` (object) +Resolved DTO: `BCKIBFNPIPD` from `BCKIBFNPIPD.cs` +Declaration: `public class BCKIBFNPIPD : IFAIJAGLDFK` +Client parser JSON keys: `Status`, `RoomKey` +Public/decompiled members: +- `BMHHFIGBOFD HIMCGOCKLLK` +- `AMAGKLLBGEC PIJLKMENAPG` + +## room-keys / api/roomkeys/v1/mine + +- `IOIBJBLIBKM` `` RecRoom.Async.IPromise`1> IGFLECMACBE() `` (IOIBJBLIBKM.txt:1704) + +Expected client return: `` System.Collections.Generic.List`1 `` (array) +Resolved DTO: `AMAGKLLBGEC` from `AMAGKLLBGEC.cs` +Declaration: `public class AMAGKLLBGEC : IFAIJAGLDFK` +Client parser JSON keys: `RoomKeyId`, `ReplicationId`, `RoomId`, `Name`, `Description`, `Price` +Public/decompiled members: +- `long DADOKMAOFJL` +- `string FIKEBGGCDFN` +- `long HKCFEOMMOOL` +- `string KODBEJPEFOJ` +- `Guid MLDAFKCENMA` +- `int NPDJLOOHMDJ` + +## room-keys / api/roomkeys/v1/update + +- `IOIBJBLIBKM` `` RecRoom.Async.IPromise`1 MLFKLGGPLNM(System.Int64 BGICHOOBKLD, System.String MMBOKOLAJFH = null, System.String LJIGOCDPEJF = null, System.Nullable`1 MACNIENMFHJ = null) `` (IOIBJBLIBKM.txt:1008) + +Expected client return: `BCKIBFNPIPD` (object) +Resolved DTO: `BCKIBFNPIPD` from `BCKIBFNPIPD.cs` +Declaration: `public class BCKIBFNPIPD : IFAIJAGLDFK` +Client parser JSON keys: `Status`, `RoomKey` +Public/decompiled members: +- `BMHHFIGBOFD HIMCGOCKLLK` +- `AMAGKLLBGEC PIJLKMENAPG` + +## rooms / api/rooms/v1/filters + +- `OJMCBOKJFOF+<>c` `` RecRoom.Async.IPromise`1 b__114_0() `` (OJMCBOKJFOF_NestedType___c.txt:1617) + +Expected client return: `AEBEPCMAABC` (object) +Resolved DTO: `AEBEPCMAABC` from `AEBEPCMAABC.cs` +Declaration: `public class AEBEPCMAABC : IFAIJAGLDFK` +Public/decompiled members: +- `List EBIKIFGHJBN` +- `List JGPAPFCFDJG` + +## rooms / api/rooms/v1/verifyRole + +- `OJMCBOKJFOF` `RecRoom.Async.IPromise INDDAAGNMHF(System.String LHOMKMINCHH)` (OJMCBOKJFOF.txt:12963) + +Expected client return: `RecRoom.Async.IPromise` (success-or-empty) +Resolved DTO: `void/success` not found in readable C# dump. + +## rooms / api/rooms/v2/report + +- `OJMCBOKJFOF` `RecRoom.Async.IPromise GECDGBACBFP(System.Int64 HNHLJONGKHB, LCCEEFHOBEN+CJFENPHAAHI MEABFEIBEMP, System.String EFDBFLPKHKA)` (OJMCBOKJFOF.txt:12627) + +Expected client return: `RecRoom.Async.IPromise` (success-or-empty) +Resolved DTO: `void/success` not found in readable C# dump. + +## rooms / hot_rooms/ + +- `OJMCBOKJFOF` `` RecRoom.Async.IPromise`1> DLPLPKCNLNA(System.Collections.Generic.IReadOnlyList`1 CAFPJPHILMN) `` (OJMCBOKJFOF.txt:3238) +- `OJMCBOKJFOF+PEBCIEAPGON` `` System.String CMOIGEDECGM(System.Collections.Generic.IEnumerable`1 CAFPJPHILMN) `` (OJMCBOKJFOF_NestedType_PEBCIEAPGON.txt:39) + +Expected client return: `` System.Collections.Generic.IReadOnlyList`1 `` (array) +Resolved DTO: `KLCOGEIGEBJ` from `KLCOGEIGEBJ.cs` +Declaration: `public class KLCOGEIGEBJ : MKAMHOIHOJK` +Inherits: `MKAMHOIHOJK` +Client parser JSON keys: `RoomId`, `IsDorm`, `CloningAllowed`, `DisableMicAutoMute`, `DisableRoomComments`, `EncryptVoiceChat` +Inherited parser JSON keys: `Name`, `Description`, `ImageName`, `WarningMask`, `CustomWarning`, `CreatorAccountId`, `State`, `Accessibility`, `SupportsLevelVoting`, `IsRRO`, `SupportsScreens`, `SupportsWalkVR`, `SupportsTeleportVR`, `SupportsVRLow`, `SupportsQuest2`, `SupportsMobile`, `SupportsJuniors`, `CreatedAt`, `Stats` +Public/decompiled members: +- `long DADOKMAOFJL` +- `bool GJPGKJJCPBK` +- `bool IBLOJJBEKFF` +- `bool JCLCOCEOAEP` +- `bool JGJCBGEBLLO` +- `bool PLFEEIJCHOH` +- `long BJFNPKFOALK` +- `DateTime ACBFDMLHFPB` (inherited from `MKAMHOIHOJK`) +- `string AHGCOGFEEEE` (inherited from `MKAMHOIHOJK`) +- `bool AKFBNELAMNA` (inherited from `MKAMHOIHOJK`) +- `int BADIGBCKECA` (inherited from `MKAMHOIHOJK`) +- `bool BNBLOBAEDEE` (inherited from `MKAMHOIHOJK`) +- `NMJEKMMBDDE CDINMMPNAID` (inherited from `MKAMHOIHOJK`) +- `bool CDNFGMHLDMJ` (inherited from `MKAMHOIHOJK`) +- `string FIKEBGGCDFN` (inherited from `MKAMHOIHOJK`) +- `GPDIAKNEBKH GIBHIMGJNNO` (inherited from `MKAMHOIHOJK`) +- `bool HPLBOMGACED` (inherited from `MKAMHOIHOJK`) +- `string IGOPGMHHLKI` (inherited from `MKAMHOIHOJK`) +- `DPLPMKMFMPB JFEAPMIPNEP` (inherited from `MKAMHOIHOJK`) +- `bool KHIJAFCHLIA` (inherited from `MKAMHOIHOJK`) +- `bool KLNJBBPNMBJ` (inherited from `MKAMHOIHOJK`) +- `string KODBEJPEFOJ` (inherited from `MKAMHOIHOJK`) +- `bool LPJLEMJFBPE` (inherited from `MKAMHOIHOJK`) +- `bool MGBDHBHCDMH` (inherited from `MKAMHOIHOJK`) +- `bool MIKDLDEALPN` (inherited from `MKAMHOIHOJK`) +- `bool OFONEIOEIED` (inherited from `MKAMHOIHOJK`) +- `HJPGEGENLPH OILEJFNPDDB` (inherited from `MKAMHOIHOJK`) +- `bool PEEFHKMOMKK` (inherited from `MKAMHOIHOJK`) + +Expected client return: `unknown` (unknown) +Resolved DTO: `unknown` not found in readable C# dump. + +## rooms / hot_roomsandplaylists/ + +- `OJMCBOKJFOF` `` RecRoom.Async.IPromise`1> EBBHGHEGBKG(System.Collections.Generic.IReadOnlyList`1 CAFPJPHILMN) `` (OJMCBOKJFOF.txt:4743) +- `OJMCBOKJFOF+PEBCIEAPGON` `` System.String MMCLGMMDDJF(System.Collections.Generic.IEnumerable`1 CAFPJPHILMN) `` (OJMCBOKJFOF_NestedType_PEBCIEAPGON.txt:121) + +Expected client return: `` System.Collections.Generic.IReadOnlyList`1 `` (array) +Resolved DTO: `MKAMHOIHOJK` from `MKAMHOIHOJK.cs` +Declaration: `public abstract class MKAMHOIHOJK : IFAIJAGLDFK, AKJKEMONOIL` +Client parser JSON keys: `Name`, `Description`, `ImageName`, `WarningMask`, `CustomWarning`, `CreatorAccountId`, `State`, `Accessibility`, `SupportsLevelVoting`, `IsRRO`, `SupportsScreens`, `SupportsWalkVR`, `SupportsTeleportVR`, `SupportsVRLow`, `SupportsQuest2`, `SupportsMobile`, `SupportsJuniors`, `CreatedAt`, `Stats` +Public/decompiled members: +- `DateTime ACBFDMLHFPB` +- `string AHGCOGFEEEE` +- `bool AKFBNELAMNA` +- `int BADIGBCKECA` +- `bool BNBLOBAEDEE` +- `NMJEKMMBDDE CDINMMPNAID` +- `bool CDNFGMHLDMJ` +- `string FIKEBGGCDFN` +- `GPDIAKNEBKH GIBHIMGJNNO` +- `bool HPLBOMGACED` +- `string IGOPGMHHLKI` +- `DPLPMKMFMPB JFEAPMIPNEP` +- `bool KHIJAFCHLIA` +- `bool KLNJBBPNMBJ` +- `string KODBEJPEFOJ` +- `bool LPJLEMJFBPE` +- `bool MGBDHBHCDMH` +- `bool MIKDLDEALPN` +- `bool OFONEIOEIED` +- `HJPGEGENLPH OILEJFNPDDB` +- `bool PEEFHKMOMKK` + +Expected client return: `unknown` (unknown) +Resolved DTO: `unknown` not found in readable C# dump. + +## rooms / rooms/{0} + +- `EJDCNGBEICB` `RecRoom.Async.IPromise KHDPHIGPEEH(System.Int64 HNHLJONGKHB)` (EJDCNGBEICB.txt:2923) +- `EJDCNGBEICB` `` RecRoom.Async.IPromise`1 NHBPIIGDAJP(System.Int64 HNHLJONGKHB) `` (EJDCNGBEICB.txt:82) +- `EJDCNGBEICB` `` RecRoom.Async.IPromise`1 CJKHNIIJFIN(System.Int64 HNHLJONGKHB) `` (EJDCNGBEICB.txt:615) + +Expected client return: `RecRoom.Async.IPromise` (success-or-empty) +Resolved DTO: `void/success` not found in readable C# dump. + +Expected client return: `KLCOGEIGEBJ` (object) +Resolved DTO: `KLCOGEIGEBJ` from `KLCOGEIGEBJ.cs` +Declaration: `public class KLCOGEIGEBJ : MKAMHOIHOJK` +Inherits: `MKAMHOIHOJK` +Client parser JSON keys: `RoomId`, `IsDorm`, `CloningAllowed`, `DisableMicAutoMute`, `DisableRoomComments`, `EncryptVoiceChat` +Inherited parser JSON keys: `Name`, `Description`, `ImageName`, `WarningMask`, `CustomWarning`, `CreatorAccountId`, `State`, `Accessibility`, `SupportsLevelVoting`, `IsRRO`, `SupportsScreens`, `SupportsWalkVR`, `SupportsTeleportVR`, `SupportsVRLow`, `SupportsQuest2`, `SupportsMobile`, `SupportsJuniors`, `CreatedAt`, `Stats` +Public/decompiled members: +- `long DADOKMAOFJL` +- `bool GJPGKJJCPBK` +- `bool IBLOJJBEKFF` +- `bool JCLCOCEOAEP` +- `bool JGJCBGEBLLO` +- `bool PLFEEIJCHOH` +- `long BJFNPKFOALK` +- `DateTime ACBFDMLHFPB` (inherited from `MKAMHOIHOJK`) +- `string AHGCOGFEEEE` (inherited from `MKAMHOIHOJK`) +- `bool AKFBNELAMNA` (inherited from `MKAMHOIHOJK`) +- `int BADIGBCKECA` (inherited from `MKAMHOIHOJK`) +- `bool BNBLOBAEDEE` (inherited from `MKAMHOIHOJK`) +- `NMJEKMMBDDE CDINMMPNAID` (inherited from `MKAMHOIHOJK`) +- `bool CDNFGMHLDMJ` (inherited from `MKAMHOIHOJK`) +- `string FIKEBGGCDFN` (inherited from `MKAMHOIHOJK`) +- `GPDIAKNEBKH GIBHIMGJNNO` (inherited from `MKAMHOIHOJK`) +- `bool HPLBOMGACED` (inherited from `MKAMHOIHOJK`) +- `string IGOPGMHHLKI` (inherited from `MKAMHOIHOJK`) +- `DPLPMKMFMPB JFEAPMIPNEP` (inherited from `MKAMHOIHOJK`) +- `bool KHIJAFCHLIA` (inherited from `MKAMHOIHOJK`) +- `bool KLNJBBPNMBJ` (inherited from `MKAMHOIHOJK`) +- `string KODBEJPEFOJ` (inherited from `MKAMHOIHOJK`) +- `bool LPJLEMJFBPE` (inherited from `MKAMHOIHOJK`) +- `bool MGBDHBHCDMH` (inherited from `MKAMHOIHOJK`) +- `bool MIKDLDEALPN` (inherited from `MKAMHOIHOJK`) +- `bool OFONEIOEIED` (inherited from `MKAMHOIHOJK`) +- `HJPGEGENLPH OILEJFNPDDB` (inherited from `MKAMHOIHOJK`) +- `bool PEEFHKMOMKK` (inherited from `MKAMHOIHOJK`) + +Expected client return: `PPENFJMFPNE` (object) +Resolved DTO: `PPENFJMFPNE` from `PPENFJMFPNE.cs` +Declaration: `public class PPENFJMFPNE : KLCOGEIGEBJ` +Inherits: `KLCOGEIGEBJ` +Inherited parser JSON keys: `RoomId`, `IsDorm`, `CloningAllowed`, `DisableMicAutoMute`, `DisableRoomComments`, `EncryptVoiceChat` +Public/decompiled members: +- `List ADCDBMGNIPB` +- `IReadOnlyList BIODFICLKJH` +- `IReadOnlyList DFKGKGIMEMP` +- `IReadOnlyList EJGIMHADHBC` +- `IReadOnlyList FEBCCOFBKKJ` +- `IReadOnlyList IAJDFKODGFL` +- `IReadOnlyList KFFBPMHGFKB` +- `IReadOnlyList LHGHLGDINFL` +- `IReadOnlyList LOJPLANGFMG` +- `LMLJHMJEIGM MMBJPGGOFHL` +- `bool MMFBPKOOLNG` +- `IReadOnlyList OHNIGJECGKL` +- `IReadOnlyList PKKADKGDHNI` +- `int accountId` +- `string tag` +- `PPENFJMFPNE IGAPDHGFLDC` +- `long DADOKMAOFJL` (inherited from `KLCOGEIGEBJ`) +- `bool GJPGKJJCPBK` (inherited from `KLCOGEIGEBJ`) +- `bool IBLOJJBEKFF` (inherited from `KLCOGEIGEBJ`) +- `bool JCLCOCEOAEP` (inherited from `KLCOGEIGEBJ`) +- `bool JGJCBGEBLLO` (inherited from `KLCOGEIGEBJ`) +- `bool PLFEEIJCHOH` (inherited from `KLCOGEIGEBJ`) +- `long BJFNPKFOALK` (inherited from `KLCOGEIGEBJ`) +- `DateTime ACBFDMLHFPB` (inherited from `MKAMHOIHOJK`) +- `string AHGCOGFEEEE` (inherited from `MKAMHOIHOJK`) +- `bool AKFBNELAMNA` (inherited from `MKAMHOIHOJK`) +- `int BADIGBCKECA` (inherited from `MKAMHOIHOJK`) +- `bool BNBLOBAEDEE` (inherited from `MKAMHOIHOJK`) +- `NMJEKMMBDDE CDINMMPNAID` (inherited from `MKAMHOIHOJK`) +- `bool CDNFGMHLDMJ` (inherited from `MKAMHOIHOJK`) +- `string FIKEBGGCDFN` (inherited from `MKAMHOIHOJK`) +- `GPDIAKNEBKH GIBHIMGJNNO` (inherited from `MKAMHOIHOJK`) +- `bool HPLBOMGACED` (inherited from `MKAMHOIHOJK`) +- `string IGOPGMHHLKI` (inherited from `MKAMHOIHOJK`) +- `DPLPMKMFMPB JFEAPMIPNEP` (inherited from `MKAMHOIHOJK`) +- `bool KHIJAFCHLIA` (inherited from `MKAMHOIHOJK`) +- `bool KLNJBBPNMBJ` (inherited from `MKAMHOIHOJK`) +- `string KODBEJPEFOJ` (inherited from `MKAMHOIHOJK`) +- `bool LPJLEMJFBPE` (inherited from `MKAMHOIHOJK`) +- `bool MGBDHBHCDMH` (inherited from `MKAMHOIHOJK`) +- `bool MIKDLDEALPN` (inherited from `MKAMHOIHOJK`) +- `bool OFONEIOEIED` (inherited from `MKAMHOIHOJK`) +- `HJPGEGENLPH OILEJFNPDDB` (inherited from `MKAMHOIHOJK`) +- `bool PEEFHKMOMKK` (inherited from `MKAMHOIHOJK`) + +## rooms / rooms/{0}/accessibility + +- `EJDCNGBEICB` `` RecRoom.Async.IPromise`1 BPDNAMEDJEG(System.Int64 HNHLJONGKHB, DPLPMKMFMPB PONCIIJOHIE) `` (EJDCNGBEICB.txt:3627) + +Expected client return: `PPENFJMFPNE` (object) +Resolved DTO: `PPENFJMFPNE` from `PPENFJMFPNE.cs` +Declaration: `public class PPENFJMFPNE : KLCOGEIGEBJ` +Inherits: `KLCOGEIGEBJ` +Inherited parser JSON keys: `RoomId`, `IsDorm`, `CloningAllowed`, `DisableMicAutoMute`, `DisableRoomComments`, `EncryptVoiceChat` +Public/decompiled members: +- `List ADCDBMGNIPB` +- `IReadOnlyList BIODFICLKJH` +- `IReadOnlyList DFKGKGIMEMP` +- `IReadOnlyList EJGIMHADHBC` +- `IReadOnlyList FEBCCOFBKKJ` +- `IReadOnlyList IAJDFKODGFL` +- `IReadOnlyList KFFBPMHGFKB` +- `IReadOnlyList LHGHLGDINFL` +- `IReadOnlyList LOJPLANGFMG` +- `LMLJHMJEIGM MMBJPGGOFHL` +- `bool MMFBPKOOLNG` +- `IReadOnlyList OHNIGJECGKL` +- `IReadOnlyList PKKADKGDHNI` +- `int accountId` +- `string tag` +- `PPENFJMFPNE IGAPDHGFLDC` +- `long DADOKMAOFJL` (inherited from `KLCOGEIGEBJ`) +- `bool GJPGKJJCPBK` (inherited from `KLCOGEIGEBJ`) +- `bool IBLOJJBEKFF` (inherited from `KLCOGEIGEBJ`) +- `bool JCLCOCEOAEP` (inherited from `KLCOGEIGEBJ`) +- `bool JGJCBGEBLLO` (inherited from `KLCOGEIGEBJ`) +- `bool PLFEEIJCHOH` (inherited from `KLCOGEIGEBJ`) +- `long BJFNPKFOALK` (inherited from `KLCOGEIGEBJ`) +- `DateTime ACBFDMLHFPB` (inherited from `MKAMHOIHOJK`) +- `string AHGCOGFEEEE` (inherited from `MKAMHOIHOJK`) +- `bool AKFBNELAMNA` (inherited from `MKAMHOIHOJK`) +- `int BADIGBCKECA` (inherited from `MKAMHOIHOJK`) +- `bool BNBLOBAEDEE` (inherited from `MKAMHOIHOJK`) +- `NMJEKMMBDDE CDINMMPNAID` (inherited from `MKAMHOIHOJK`) +- `bool CDNFGMHLDMJ` (inherited from `MKAMHOIHOJK`) +- `string FIKEBGGCDFN` (inherited from `MKAMHOIHOJK`) +- `GPDIAKNEBKH GIBHIMGJNNO` (inherited from `MKAMHOIHOJK`) +- `bool HPLBOMGACED` (inherited from `MKAMHOIHOJK`) +- `string IGOPGMHHLKI` (inherited from `MKAMHOIHOJK`) +- `DPLPMKMFMPB JFEAPMIPNEP` (inherited from `MKAMHOIHOJK`) +- `bool KHIJAFCHLIA` (inherited from `MKAMHOIHOJK`) +- `bool KLNJBBPNMBJ` (inherited from `MKAMHOIHOJK`) +- `string KODBEJPEFOJ` (inherited from `MKAMHOIHOJK`) +- `bool LPJLEMJFBPE` (inherited from `MKAMHOIHOJK`) +- `bool MGBDHBHCDMH` (inherited from `MKAMHOIHOJK`) +- `bool MIKDLDEALPN` (inherited from `MKAMHOIHOJK`) +- `bool OFONEIOEIED` (inherited from `MKAMHOIHOJK`) +- `HJPGEGENLPH OILEJFNPDDB` (inherited from `MKAMHOIHOJK`) +- `bool PEEFHKMOMKK` (inherited from `MKAMHOIHOJK`) + +## rooms / rooms/{0}/automute + +- `EJDCNGBEICB` `` RecRoom.Async.IPromise`1 CPFCPEONBGC(System.Int64 HNHLJONGKHB, System.Boolean AMJMLANOGKE) `` (EJDCNGBEICB.txt:4014) + +Expected client return: `PPENFJMFPNE` (object) +Resolved DTO: `PPENFJMFPNE` from `PPENFJMFPNE.cs` +Declaration: `public class PPENFJMFPNE : KLCOGEIGEBJ` +Inherits: `KLCOGEIGEBJ` +Inherited parser JSON keys: `RoomId`, `IsDorm`, `CloningAllowed`, `DisableMicAutoMute`, `DisableRoomComments`, `EncryptVoiceChat` +Public/decompiled members: +- `List ADCDBMGNIPB` +- `IReadOnlyList BIODFICLKJH` +- `IReadOnlyList DFKGKGIMEMP` +- `IReadOnlyList EJGIMHADHBC` +- `IReadOnlyList FEBCCOFBKKJ` +- `IReadOnlyList IAJDFKODGFL` +- `IReadOnlyList KFFBPMHGFKB` +- `IReadOnlyList LHGHLGDINFL` +- `IReadOnlyList LOJPLANGFMG` +- `LMLJHMJEIGM MMBJPGGOFHL` +- `bool MMFBPKOOLNG` +- `IReadOnlyList OHNIGJECGKL` +- `IReadOnlyList PKKADKGDHNI` +- `int accountId` +- `string tag` +- `PPENFJMFPNE IGAPDHGFLDC` +- `long DADOKMAOFJL` (inherited from `KLCOGEIGEBJ`) +- `bool GJPGKJJCPBK` (inherited from `KLCOGEIGEBJ`) +- `bool IBLOJJBEKFF` (inherited from `KLCOGEIGEBJ`) +- `bool JCLCOCEOAEP` (inherited from `KLCOGEIGEBJ`) +- `bool JGJCBGEBLLO` (inherited from `KLCOGEIGEBJ`) +- `bool PLFEEIJCHOH` (inherited from `KLCOGEIGEBJ`) +- `long BJFNPKFOALK` (inherited from `KLCOGEIGEBJ`) +- `DateTime ACBFDMLHFPB` (inherited from `MKAMHOIHOJK`) +- `string AHGCOGFEEEE` (inherited from `MKAMHOIHOJK`) +- `bool AKFBNELAMNA` (inherited from `MKAMHOIHOJK`) +- `int BADIGBCKECA` (inherited from `MKAMHOIHOJK`) +- `bool BNBLOBAEDEE` (inherited from `MKAMHOIHOJK`) +- `NMJEKMMBDDE CDINMMPNAID` (inherited from `MKAMHOIHOJK`) +- `bool CDNFGMHLDMJ` (inherited from `MKAMHOIHOJK`) +- `string FIKEBGGCDFN` (inherited from `MKAMHOIHOJK`) +- `GPDIAKNEBKH GIBHIMGJNNO` (inherited from `MKAMHOIHOJK`) +- `bool HPLBOMGACED` (inherited from `MKAMHOIHOJK`) +- `string IGOPGMHHLKI` (inherited from `MKAMHOIHOJK`) +- `DPLPMKMFMPB JFEAPMIPNEP` (inherited from `MKAMHOIHOJK`) +- `bool KHIJAFCHLIA` (inherited from `MKAMHOIHOJK`) +- `bool KLNJBBPNMBJ` (inherited from `MKAMHOIHOJK`) +- `string KODBEJPEFOJ` (inherited from `MKAMHOIHOJK`) +- `bool LPJLEMJFBPE` (inherited from `MKAMHOIHOJK`) +- `bool MGBDHBHCDMH` (inherited from `MKAMHOIHOJK`) +- `bool MIKDLDEALPN` (inherited from `MKAMHOIHOJK`) +- `bool OFONEIOEIED` (inherited from `MKAMHOIHOJK`) +- `HJPGEGENLPH OILEJFNPDDB` (inherited from `MKAMHOIHOJK`) +- `bool PEEFHKMOMKK` (inherited from `MKAMHOIHOJK`) + +## rooms / rooms/{0}/bans + +- `EJDCNGBEICB` `` RecRoom.Async.IPromise CCGIGHDHNLM(System.Int64 HNHLJONGKHB, System.Collections.Generic.IReadOnlyList`1 ILNGMAANNDG, KKIHLHLNGCK BFNPCCFPJHP) `` (EJDCNGBEICB.txt:7939) +- `EJDCNGBEICB` `` RecRoom.Async.IPromise`1> CGIPIOMBAFM(System.Int64 HNHLJONGKHB) `` (EJDCNGBEICB.txt:7832) + +Expected client return: `RecRoom.Async.IPromise` (success-or-empty) +Resolved DTO: `void/success` not found in readable C# dump. + +Expected client return: `` System.Collections.Generic.List`1 `` (array) +Resolved DTO: `EAENIFLCDGI` from `EAENIFLCDGI.cs` +Declaration: `public class EAENIFLCDGI : IFAIJAGLDFK` +Client parser JSON keys: `AccountId`, `BanStartTime` +Public/decompiled members: +- `DateTime CCNHPFAKKBD` +- `int GAINIOENNCG` + +## rooms / rooms/{0}/bans/{1} + +- `EJDCNGBEICB` `RecRoom.Async.IPromise JMCJDGIMKPK(System.Int64 HNHLJONGKHB, System.Int32 GKLPIFBPGOD, KKIHLHLNGCK BFNPCCFPJHP)` (EJDCNGBEICB.txt:8186) + +Expected client return: `RecRoom.Async.IPromise` (success-or-empty) +Resolved DTO: `void/success` not found in readable C# dump. + +## rooms / rooms/{0}/bans/import + +- `EJDCNGBEICB` `RecRoom.Async.IPromise PFEEJDKPPJI(System.Int64 HNHLJONGKHB, System.Int64 MOKIOEJBMFC)` (EJDCNGBEICB.txt:8054) + +Expected client return: `RecRoom.Async.IPromise` (success-or-empty) +Resolved DTO: `void/success` not found in readable C# dump. + +## rooms / rooms/{0}/clone + +- `EJDCNGBEICB` `` RecRoom.Async.IPromise`1 OFFECAMHJGE(System.Int64 HNHLJONGKHB, System.String MMBOKOLAJFH) `` (EJDCNGBEICB.txt:2843) + +Expected client return: `PPENFJMFPNE` (object) +Resolved DTO: `PPENFJMFPNE` from `PPENFJMFPNE.cs` +Declaration: `public class PPENFJMFPNE : KLCOGEIGEBJ` +Inherits: `KLCOGEIGEBJ` +Inherited parser JSON keys: `RoomId`, `IsDorm`, `CloningAllowed`, `DisableMicAutoMute`, `DisableRoomComments`, `EncryptVoiceChat` +Public/decompiled members: +- `List ADCDBMGNIPB` +- `IReadOnlyList BIODFICLKJH` +- `IReadOnlyList DFKGKGIMEMP` +- `IReadOnlyList EJGIMHADHBC` +- `IReadOnlyList FEBCCOFBKKJ` +- `IReadOnlyList IAJDFKODGFL` +- `IReadOnlyList KFFBPMHGFKB` +- `IReadOnlyList LHGHLGDINFL` +- `IReadOnlyList LOJPLANGFMG` +- `LMLJHMJEIGM MMBJPGGOFHL` +- `bool MMFBPKOOLNG` +- `IReadOnlyList OHNIGJECGKL` +- `IReadOnlyList PKKADKGDHNI` +- `int accountId` +- `string tag` +- `PPENFJMFPNE IGAPDHGFLDC` +- `long DADOKMAOFJL` (inherited from `KLCOGEIGEBJ`) +- `bool GJPGKJJCPBK` (inherited from `KLCOGEIGEBJ`) +- `bool IBLOJJBEKFF` (inherited from `KLCOGEIGEBJ`) +- `bool JCLCOCEOAEP` (inherited from `KLCOGEIGEBJ`) +- `bool JGJCBGEBLLO` (inherited from `KLCOGEIGEBJ`) +- `bool PLFEEIJCHOH` (inherited from `KLCOGEIGEBJ`) +- `long BJFNPKFOALK` (inherited from `KLCOGEIGEBJ`) +- `DateTime ACBFDMLHFPB` (inherited from `MKAMHOIHOJK`) +- `string AHGCOGFEEEE` (inherited from `MKAMHOIHOJK`) +- `bool AKFBNELAMNA` (inherited from `MKAMHOIHOJK`) +- `int BADIGBCKECA` (inherited from `MKAMHOIHOJK`) +- `bool BNBLOBAEDEE` (inherited from `MKAMHOIHOJK`) +- `NMJEKMMBDDE CDINMMPNAID` (inherited from `MKAMHOIHOJK`) +- `bool CDNFGMHLDMJ` (inherited from `MKAMHOIHOJK`) +- `string FIKEBGGCDFN` (inherited from `MKAMHOIHOJK`) +- `GPDIAKNEBKH GIBHIMGJNNO` (inherited from `MKAMHOIHOJK`) +- `bool HPLBOMGACED` (inherited from `MKAMHOIHOJK`) +- `string IGOPGMHHLKI` (inherited from `MKAMHOIHOJK`) +- `DPLPMKMFMPB JFEAPMIPNEP` (inherited from `MKAMHOIHOJK`) +- `bool KHIJAFCHLIA` (inherited from `MKAMHOIHOJK`) +- `bool KLNJBBPNMBJ` (inherited from `MKAMHOIHOJK`) +- `string KODBEJPEFOJ` (inherited from `MKAMHOIHOJK`) +- `bool LPJLEMJFBPE` (inherited from `MKAMHOIHOJK`) +- `bool MGBDHBHCDMH` (inherited from `MKAMHOIHOJK`) +- `bool MIKDLDEALPN` (inherited from `MKAMHOIHOJK`) +- `bool OFONEIOEIED` (inherited from `MKAMHOIHOJK`) +- `HJPGEGENLPH OILEJFNPDDB` (inherited from `MKAMHOIHOJK`) +- `bool PEEFHKMOMKK` (inherited from `MKAMHOIHOJK`) + +## rooms / rooms/{0}/cloning + +- `EJDCNGBEICB` `` RecRoom.Async.IPromise`1 LJMHBJKDJCE(System.Int64 HNHLJONGKHB, System.Boolean GPAGMKFIKNG) `` (EJDCNGBEICB.txt:3886) + +Expected client return: `PPENFJMFPNE` (object) +Resolved DTO: `PPENFJMFPNE` from `PPENFJMFPNE.cs` +Declaration: `public class PPENFJMFPNE : KLCOGEIGEBJ` +Inherits: `KLCOGEIGEBJ` +Inherited parser JSON keys: `RoomId`, `IsDorm`, `CloningAllowed`, `DisableMicAutoMute`, `DisableRoomComments`, `EncryptVoiceChat` +Public/decompiled members: +- `List ADCDBMGNIPB` +- `IReadOnlyList BIODFICLKJH` +- `IReadOnlyList DFKGKGIMEMP` +- `IReadOnlyList EJGIMHADHBC` +- `IReadOnlyList FEBCCOFBKKJ` +- `IReadOnlyList IAJDFKODGFL` +- `IReadOnlyList KFFBPMHGFKB` +- `IReadOnlyList LHGHLGDINFL` +- `IReadOnlyList LOJPLANGFMG` +- `LMLJHMJEIGM MMBJPGGOFHL` +- `bool MMFBPKOOLNG` +- `IReadOnlyList OHNIGJECGKL` +- `IReadOnlyList PKKADKGDHNI` +- `int accountId` +- `string tag` +- `PPENFJMFPNE IGAPDHGFLDC` +- `long DADOKMAOFJL` (inherited from `KLCOGEIGEBJ`) +- `bool GJPGKJJCPBK` (inherited from `KLCOGEIGEBJ`) +- `bool IBLOJJBEKFF` (inherited from `KLCOGEIGEBJ`) +- `bool JCLCOCEOAEP` (inherited from `KLCOGEIGEBJ`) +- `bool JGJCBGEBLLO` (inherited from `KLCOGEIGEBJ`) +- `bool PLFEEIJCHOH` (inherited from `KLCOGEIGEBJ`) +- `long BJFNPKFOALK` (inherited from `KLCOGEIGEBJ`) +- `DateTime ACBFDMLHFPB` (inherited from `MKAMHOIHOJK`) +- `string AHGCOGFEEEE` (inherited from `MKAMHOIHOJK`) +- `bool AKFBNELAMNA` (inherited from `MKAMHOIHOJK`) +- `int BADIGBCKECA` (inherited from `MKAMHOIHOJK`) +- `bool BNBLOBAEDEE` (inherited from `MKAMHOIHOJK`) +- `NMJEKMMBDDE CDINMMPNAID` (inherited from `MKAMHOIHOJK`) +- `bool CDNFGMHLDMJ` (inherited from `MKAMHOIHOJK`) +- `string FIKEBGGCDFN` (inherited from `MKAMHOIHOJK`) +- `GPDIAKNEBKH GIBHIMGJNNO` (inherited from `MKAMHOIHOJK`) +- `bool HPLBOMGACED` (inherited from `MKAMHOIHOJK`) +- `string IGOPGMHHLKI` (inherited from `MKAMHOIHOJK`) +- `DPLPMKMFMPB JFEAPMIPNEP` (inherited from `MKAMHOIHOJK`) +- `bool KHIJAFCHLIA` (inherited from `MKAMHOIHOJK`) +- `bool KLNJBBPNMBJ` (inherited from `MKAMHOIHOJK`) +- `string KODBEJPEFOJ` (inherited from `MKAMHOIHOJK`) +- `bool LPJLEMJFBPE` (inherited from `MKAMHOIHOJK`) +- `bool MGBDHBHCDMH` (inherited from `MKAMHOIHOJK`) +- `bool MIKDLDEALPN` (inherited from `MKAMHOIHOJK`) +- `bool OFONEIOEIED` (inherited from `MKAMHOIHOJK`) +- `HJPGEGENLPH OILEJFNPDDB` (inherited from `MKAMHOIHOJK`) +- `bool PEEFHKMOMKK` (inherited from `MKAMHOIHOJK`) + +## rooms / rooms/{0}/comments + +- `EJDCNGBEICB` `` RecRoom.Async.IPromise`1 FKADIOPHOGC(System.Int64 HNHLJONGKHB, System.Boolean IMHMMPKLGEP) `` (EJDCNGBEICB.txt:4142) + +Expected client return: `PPENFJMFPNE` (object) +Resolved DTO: `PPENFJMFPNE` from `PPENFJMFPNE.cs` +Declaration: `public class PPENFJMFPNE : KLCOGEIGEBJ` +Inherits: `KLCOGEIGEBJ` +Inherited parser JSON keys: `RoomId`, `IsDorm`, `CloningAllowed`, `DisableMicAutoMute`, `DisableRoomComments`, `EncryptVoiceChat` +Public/decompiled members: +- `List ADCDBMGNIPB` +- `IReadOnlyList BIODFICLKJH` +- `IReadOnlyList DFKGKGIMEMP` +- `IReadOnlyList EJGIMHADHBC` +- `IReadOnlyList FEBCCOFBKKJ` +- `IReadOnlyList IAJDFKODGFL` +- `IReadOnlyList KFFBPMHGFKB` +- `IReadOnlyList LHGHLGDINFL` +- `IReadOnlyList LOJPLANGFMG` +- `LMLJHMJEIGM MMBJPGGOFHL` +- `bool MMFBPKOOLNG` +- `IReadOnlyList OHNIGJECGKL` +- `IReadOnlyList PKKADKGDHNI` +- `int accountId` +- `string tag` +- `PPENFJMFPNE IGAPDHGFLDC` +- `long DADOKMAOFJL` (inherited from `KLCOGEIGEBJ`) +- `bool GJPGKJJCPBK` (inherited from `KLCOGEIGEBJ`) +- `bool IBLOJJBEKFF` (inherited from `KLCOGEIGEBJ`) +- `bool JCLCOCEOAEP` (inherited from `KLCOGEIGEBJ`) +- `bool JGJCBGEBLLO` (inherited from `KLCOGEIGEBJ`) +- `bool PLFEEIJCHOH` (inherited from `KLCOGEIGEBJ`) +- `long BJFNPKFOALK` (inherited from `KLCOGEIGEBJ`) +- `DateTime ACBFDMLHFPB` (inherited from `MKAMHOIHOJK`) +- `string AHGCOGFEEEE` (inherited from `MKAMHOIHOJK`) +- `bool AKFBNELAMNA` (inherited from `MKAMHOIHOJK`) +- `int BADIGBCKECA` (inherited from `MKAMHOIHOJK`) +- `bool BNBLOBAEDEE` (inherited from `MKAMHOIHOJK`) +- `NMJEKMMBDDE CDINMMPNAID` (inherited from `MKAMHOIHOJK`) +- `bool CDNFGMHLDMJ` (inherited from `MKAMHOIHOJK`) +- `string FIKEBGGCDFN` (inherited from `MKAMHOIHOJK`) +- `GPDIAKNEBKH GIBHIMGJNNO` (inherited from `MKAMHOIHOJK`) +- `bool HPLBOMGACED` (inherited from `MKAMHOIHOJK`) +- `string IGOPGMHHLKI` (inherited from `MKAMHOIHOJK`) +- `DPLPMKMFMPB JFEAPMIPNEP` (inherited from `MKAMHOIHOJK`) +- `bool KHIJAFCHLIA` (inherited from `MKAMHOIHOJK`) +- `bool KLNJBBPNMBJ` (inherited from `MKAMHOIHOJK`) +- `string KODBEJPEFOJ` (inherited from `MKAMHOIHOJK`) +- `bool LPJLEMJFBPE` (inherited from `MKAMHOIHOJK`) +- `bool MGBDHBHCDMH` (inherited from `MKAMHOIHOJK`) +- `bool MIKDLDEALPN` (inherited from `MKAMHOIHOJK`) +- `bool OFONEIOEIED` (inherited from `MKAMHOIHOJK`) +- `HJPGEGENLPH OILEJFNPDDB` (inherited from `MKAMHOIHOJK`) +- `bool PEEFHKMOMKK` (inherited from `MKAMHOIHOJK`) + +## rooms / rooms/{0}/description + +- `EJDCNGBEICB` `` RecRoom.Async.IPromise`1 CFNFNEJGCEF(System.Int64 HNHLJONGKHB, System.String LJIGOCDPEJF) `` (EJDCNGBEICB.txt:3143) + +Expected client return: `PPENFJMFPNE` (object) +Resolved DTO: `PPENFJMFPNE` from `PPENFJMFPNE.cs` +Declaration: `public class PPENFJMFPNE : KLCOGEIGEBJ` +Inherits: `KLCOGEIGEBJ` +Inherited parser JSON keys: `RoomId`, `IsDorm`, `CloningAllowed`, `DisableMicAutoMute`, `DisableRoomComments`, `EncryptVoiceChat` +Public/decompiled members: +- `List ADCDBMGNIPB` +- `IReadOnlyList BIODFICLKJH` +- `IReadOnlyList DFKGKGIMEMP` +- `IReadOnlyList EJGIMHADHBC` +- `IReadOnlyList FEBCCOFBKKJ` +- `IReadOnlyList IAJDFKODGFL` +- `IReadOnlyList KFFBPMHGFKB` +- `IReadOnlyList LHGHLGDINFL` +- `IReadOnlyList LOJPLANGFMG` +- `LMLJHMJEIGM MMBJPGGOFHL` +- `bool MMFBPKOOLNG` +- `IReadOnlyList OHNIGJECGKL` +- `IReadOnlyList PKKADKGDHNI` +- `int accountId` +- `string tag` +- `PPENFJMFPNE IGAPDHGFLDC` +- `long DADOKMAOFJL` (inherited from `KLCOGEIGEBJ`) +- `bool GJPGKJJCPBK` (inherited from `KLCOGEIGEBJ`) +- `bool IBLOJJBEKFF` (inherited from `KLCOGEIGEBJ`) +- `bool JCLCOCEOAEP` (inherited from `KLCOGEIGEBJ`) +- `bool JGJCBGEBLLO` (inherited from `KLCOGEIGEBJ`) +- `bool PLFEEIJCHOH` (inherited from `KLCOGEIGEBJ`) +- `long BJFNPKFOALK` (inherited from `KLCOGEIGEBJ`) +- `DateTime ACBFDMLHFPB` (inherited from `MKAMHOIHOJK`) +- `string AHGCOGFEEEE` (inherited from `MKAMHOIHOJK`) +- `bool AKFBNELAMNA` (inherited from `MKAMHOIHOJK`) +- `int BADIGBCKECA` (inherited from `MKAMHOIHOJK`) +- `bool BNBLOBAEDEE` (inherited from `MKAMHOIHOJK`) +- `NMJEKMMBDDE CDINMMPNAID` (inherited from `MKAMHOIHOJK`) +- `bool CDNFGMHLDMJ` (inherited from `MKAMHOIHOJK`) +- `string FIKEBGGCDFN` (inherited from `MKAMHOIHOJK`) +- `GPDIAKNEBKH GIBHIMGJNNO` (inherited from `MKAMHOIHOJK`) +- `bool HPLBOMGACED` (inherited from `MKAMHOIHOJK`) +- `string IGOPGMHHLKI` (inherited from `MKAMHOIHOJK`) +- `DPLPMKMFMPB JFEAPMIPNEP` (inherited from `MKAMHOIHOJK`) +- `bool KHIJAFCHLIA` (inherited from `MKAMHOIHOJK`) +- `bool KLNJBBPNMBJ` (inherited from `MKAMHOIHOJK`) +- `string KODBEJPEFOJ` (inherited from `MKAMHOIHOJK`) +- `bool LPJLEMJFBPE` (inherited from `MKAMHOIHOJK`) +- `bool MGBDHBHCDMH` (inherited from `MKAMHOIHOJK`) +- `bool MIKDLDEALPN` (inherited from `MKAMHOIHOJK`) +- `bool OFONEIOEIED` (inherited from `MKAMHOIHOJK`) +- `HJPGEGENLPH OILEJFNPDDB` (inherited from `MKAMHOIHOJK`) +- `bool PEEFHKMOMKK` (inherited from `MKAMHOIHOJK`) + +## rooms / rooms/{0}/image + +- `EJDCNGBEICB` `` RecRoom.Async.IPromise`1 OGFLGMMNMDD(System.Int64 HNHLJONGKHB, System.String HFLPBHHAFIO) `` (EJDCNGBEICB.txt:3259) + +Expected client return: `PPENFJMFPNE` (object) +Resolved DTO: `PPENFJMFPNE` from `PPENFJMFPNE.cs` +Declaration: `public class PPENFJMFPNE : KLCOGEIGEBJ` +Inherits: `KLCOGEIGEBJ` +Inherited parser JSON keys: `RoomId`, `IsDorm`, `CloningAllowed`, `DisableMicAutoMute`, `DisableRoomComments`, `EncryptVoiceChat` +Public/decompiled members: +- `List ADCDBMGNIPB` +- `IReadOnlyList BIODFICLKJH` +- `IReadOnlyList DFKGKGIMEMP` +- `IReadOnlyList EJGIMHADHBC` +- `IReadOnlyList FEBCCOFBKKJ` +- `IReadOnlyList IAJDFKODGFL` +- `IReadOnlyList KFFBPMHGFKB` +- `IReadOnlyList LHGHLGDINFL` +- `IReadOnlyList LOJPLANGFMG` +- `LMLJHMJEIGM MMBJPGGOFHL` +- `bool MMFBPKOOLNG` +- `IReadOnlyList OHNIGJECGKL` +- `IReadOnlyList PKKADKGDHNI` +- `int accountId` +- `string tag` +- `PPENFJMFPNE IGAPDHGFLDC` +- `long DADOKMAOFJL` (inherited from `KLCOGEIGEBJ`) +- `bool GJPGKJJCPBK` (inherited from `KLCOGEIGEBJ`) +- `bool IBLOJJBEKFF` (inherited from `KLCOGEIGEBJ`) +- `bool JCLCOCEOAEP` (inherited from `KLCOGEIGEBJ`) +- `bool JGJCBGEBLLO` (inherited from `KLCOGEIGEBJ`) +- `bool PLFEEIJCHOH` (inherited from `KLCOGEIGEBJ`) +- `long BJFNPKFOALK` (inherited from `KLCOGEIGEBJ`) +- `DateTime ACBFDMLHFPB` (inherited from `MKAMHOIHOJK`) +- `string AHGCOGFEEEE` (inherited from `MKAMHOIHOJK`) +- `bool AKFBNELAMNA` (inherited from `MKAMHOIHOJK`) +- `int BADIGBCKECA` (inherited from `MKAMHOIHOJK`) +- `bool BNBLOBAEDEE` (inherited from `MKAMHOIHOJK`) +- `NMJEKMMBDDE CDINMMPNAID` (inherited from `MKAMHOIHOJK`) +- `bool CDNFGMHLDMJ` (inherited from `MKAMHOIHOJK`) +- `string FIKEBGGCDFN` (inherited from `MKAMHOIHOJK`) +- `GPDIAKNEBKH GIBHIMGJNNO` (inherited from `MKAMHOIHOJK`) +- `bool HPLBOMGACED` (inherited from `MKAMHOIHOJK`) +- `string IGOPGMHHLKI` (inherited from `MKAMHOIHOJK`) +- `DPLPMKMFMPB JFEAPMIPNEP` (inherited from `MKAMHOIHOJK`) +- `bool KHIJAFCHLIA` (inherited from `MKAMHOIHOJK`) +- `bool KLNJBBPNMBJ` (inherited from `MKAMHOIHOJK`) +- `string KODBEJPEFOJ` (inherited from `MKAMHOIHOJK`) +- `bool LPJLEMJFBPE` (inherited from `MKAMHOIHOJK`) +- `bool MGBDHBHCDMH` (inherited from `MKAMHOIHOJK`) +- `bool MIKDLDEALPN` (inherited from `MKAMHOIHOJK`) +- `bool OFONEIOEIED` (inherited from `MKAMHOIHOJK`) +- `HJPGEGENLPH OILEJFNPDDB` (inherited from `MKAMHOIHOJK`) +- `bool PEEFHKMOMKK` (inherited from `MKAMHOIHOJK`) + +## rooms / rooms/{0}/interactionby/me + +- `EJDCNGBEICB` `` RecRoom.Async.IPromise`1 CKJBGHEIGBI(System.Int64 HNHLJONGKHB) `` (EJDCNGBEICB.txt:8266) + +Expected client return: `CJODCLDGFCF` (object) +Resolved DTO: `CJODCLDGFCF` from `CJODCLDGFCF.cs` +Declaration: `public class CJODCLDGFCF : IFAIJAGLDFK` +Client parser JSON keys: `Cheered`, `Favorited` +Public/decompiled members: +- `Nullable EFCGNDGOFNK` +- `bool EMBNPPMOJFJ` +- `bool HHFLKFOEHNL` + +## rooms / rooms/{0}/interactionby/me/cheer + +- `EJDCNGBEICB` `RecRoom.Async.IPromise JFBCCIHBKPP(System.Int64 HNHLJONGKHB)` (EJDCNGBEICB.txt:8399) +- `EJDCNGBEICB` `RecRoom.Async.IPromise PEKMAJPMCDE(System.Int64 HNHLJONGKHB)` (EJDCNGBEICB.txt:8331) + +Expected client return: `RecRoom.Async.IPromise` (success-or-empty) +Resolved DTO: `void/success` not found in readable C# dump. + +## rooms / rooms/{0}/interactionby/me/favorite + +- `EJDCNGBEICB` `RecRoom.Async.IPromise DIFNOPBMBFO(System.Int64 HNHLJONGKHB)` (EJDCNGBEICB.txt:8467) +- `EJDCNGBEICB` `RecRoom.Async.IPromise OGBLBGALMDC(System.Int64 HNHLJONGKHB)` (EJDCNGBEICB.txt:8535) + +Expected client return: `RecRoom.Async.IPromise` (success-or-empty) +Resolved DTO: `void/success` not found in readable C# dump. + +## rooms / rooms/{0}/modify + +- `EJDCNGBEICB` `` RecRoom.Async.IPromise`1 ONBIHBFOJKD(System.Int64 HNHLJONGKHB, System.String MMBOKOLAJFH, System.String LJIGOCDPEJF, DPLPMKMFMPB PONCIIJOHIE, System.Boolean DDMOEIAHJBK, System.Boolean FOBLJIBLCNI, System.Boolean GELHFEFGJLA, System.Boolean NFIGHDPKLJF, System.Boolean GPAGMKFIKNG, System.Boolean AMJMLANOGKE, System.Boolean IMHMMPKLGEP, System.Boolean NFJINCPDPDG) `` (EJDCNGBEICB.txt:6298) + +Expected client return: `PPENFJMFPNE` (object) +Resolved DTO: `PPENFJMFPNE` from `PPENFJMFPNE.cs` +Declaration: `public class PPENFJMFPNE : KLCOGEIGEBJ` +Inherits: `KLCOGEIGEBJ` +Inherited parser JSON keys: `RoomId`, `IsDorm`, `CloningAllowed`, `DisableMicAutoMute`, `DisableRoomComments`, `EncryptVoiceChat` +Public/decompiled members: +- `List ADCDBMGNIPB` +- `IReadOnlyList BIODFICLKJH` +- `IReadOnlyList DFKGKGIMEMP` +- `IReadOnlyList EJGIMHADHBC` +- `IReadOnlyList FEBCCOFBKKJ` +- `IReadOnlyList IAJDFKODGFL` +- `IReadOnlyList KFFBPMHGFKB` +- `IReadOnlyList LHGHLGDINFL` +- `IReadOnlyList LOJPLANGFMG` +- `LMLJHMJEIGM MMBJPGGOFHL` +- `bool MMFBPKOOLNG` +- `IReadOnlyList OHNIGJECGKL` +- `IReadOnlyList PKKADKGDHNI` +- `int accountId` +- `string tag` +- `PPENFJMFPNE IGAPDHGFLDC` +- `long DADOKMAOFJL` (inherited from `KLCOGEIGEBJ`) +- `bool GJPGKJJCPBK` (inherited from `KLCOGEIGEBJ`) +- `bool IBLOJJBEKFF` (inherited from `KLCOGEIGEBJ`) +- `bool JCLCOCEOAEP` (inherited from `KLCOGEIGEBJ`) +- `bool JGJCBGEBLLO` (inherited from `KLCOGEIGEBJ`) +- `bool PLFEEIJCHOH` (inherited from `KLCOGEIGEBJ`) +- `long BJFNPKFOALK` (inherited from `KLCOGEIGEBJ`) +- `DateTime ACBFDMLHFPB` (inherited from `MKAMHOIHOJK`) +- `string AHGCOGFEEEE` (inherited from `MKAMHOIHOJK`) +- `bool AKFBNELAMNA` (inherited from `MKAMHOIHOJK`) +- `int BADIGBCKECA` (inherited from `MKAMHOIHOJK`) +- `bool BNBLOBAEDEE` (inherited from `MKAMHOIHOJK`) +- `NMJEKMMBDDE CDINMMPNAID` (inherited from `MKAMHOIHOJK`) +- `bool CDNFGMHLDMJ` (inherited from `MKAMHOIHOJK`) +- `string FIKEBGGCDFN` (inherited from `MKAMHOIHOJK`) +- `GPDIAKNEBKH GIBHIMGJNNO` (inherited from `MKAMHOIHOJK`) +- `bool HPLBOMGACED` (inherited from `MKAMHOIHOJK`) +- `string IGOPGMHHLKI` (inherited from `MKAMHOIHOJK`) +- `DPLPMKMFMPB JFEAPMIPNEP` (inherited from `MKAMHOIHOJK`) +- `bool KHIJAFCHLIA` (inherited from `MKAMHOIHOJK`) +- `bool KLNJBBPNMBJ` (inherited from `MKAMHOIHOJK`) +- `string KODBEJPEFOJ` (inherited from `MKAMHOIHOJK`) +- `bool LPJLEMJFBPE` (inherited from `MKAMHOIHOJK`) +- `bool MGBDHBHCDMH` (inherited from `MKAMHOIHOJK`) +- `bool MIKDLDEALPN` (inherited from `MKAMHOIHOJK`) +- `bool OFONEIOEIED` (inherited from `MKAMHOIHOJK`) +- `HJPGEGENLPH OILEJFNPDDB` (inherited from `MKAMHOIHOJK`) +- `bool PEEFHKMOMKK` (inherited from `MKAMHOIHOJK`) + +## rooms / rooms/{0}/name + +- `EJDCNGBEICB` `` RecRoom.Async.IPromise`1 DNOGGKKHMHI(System.Int64 HNHLJONGKHB, System.String MMBOKOLAJFH) `` (EJDCNGBEICB.txt:3027) + +Expected client return: `PPENFJMFPNE` (object) +Resolved DTO: `PPENFJMFPNE` from `PPENFJMFPNE.cs` +Declaration: `public class PPENFJMFPNE : KLCOGEIGEBJ` +Inherits: `KLCOGEIGEBJ` +Inherited parser JSON keys: `RoomId`, `IsDorm`, `CloningAllowed`, `DisableMicAutoMute`, `DisableRoomComments`, `EncryptVoiceChat` +Public/decompiled members: +- `List ADCDBMGNIPB` +- `IReadOnlyList BIODFICLKJH` +- `IReadOnlyList DFKGKGIMEMP` +- `IReadOnlyList EJGIMHADHBC` +- `IReadOnlyList FEBCCOFBKKJ` +- `IReadOnlyList IAJDFKODGFL` +- `IReadOnlyList KFFBPMHGFKB` +- `IReadOnlyList LHGHLGDINFL` +- `IReadOnlyList LOJPLANGFMG` +- `LMLJHMJEIGM MMBJPGGOFHL` +- `bool MMFBPKOOLNG` +- `IReadOnlyList OHNIGJECGKL` +- `IReadOnlyList PKKADKGDHNI` +- `int accountId` +- `string tag` +- `PPENFJMFPNE IGAPDHGFLDC` +- `long DADOKMAOFJL` (inherited from `KLCOGEIGEBJ`) +- `bool GJPGKJJCPBK` (inherited from `KLCOGEIGEBJ`) +- `bool IBLOJJBEKFF` (inherited from `KLCOGEIGEBJ`) +- `bool JCLCOCEOAEP` (inherited from `KLCOGEIGEBJ`) +- `bool JGJCBGEBLLO` (inherited from `KLCOGEIGEBJ`) +- `bool PLFEEIJCHOH` (inherited from `KLCOGEIGEBJ`) +- `long BJFNPKFOALK` (inherited from `KLCOGEIGEBJ`) +- `DateTime ACBFDMLHFPB` (inherited from `MKAMHOIHOJK`) +- `string AHGCOGFEEEE` (inherited from `MKAMHOIHOJK`) +- `bool AKFBNELAMNA` (inherited from `MKAMHOIHOJK`) +- `int BADIGBCKECA` (inherited from `MKAMHOIHOJK`) +- `bool BNBLOBAEDEE` (inherited from `MKAMHOIHOJK`) +- `NMJEKMMBDDE CDINMMPNAID` (inherited from `MKAMHOIHOJK`) +- `bool CDNFGMHLDMJ` (inherited from `MKAMHOIHOJK`) +- `string FIKEBGGCDFN` (inherited from `MKAMHOIHOJK`) +- `GPDIAKNEBKH GIBHIMGJNNO` (inherited from `MKAMHOIHOJK`) +- `bool HPLBOMGACED` (inherited from `MKAMHOIHOJK`) +- `string IGOPGMHHLKI` (inherited from `MKAMHOIHOJK`) +- `DPLPMKMFMPB JFEAPMIPNEP` (inherited from `MKAMHOIHOJK`) +- `bool KHIJAFCHLIA` (inherited from `MKAMHOIHOJK`) +- `bool KLNJBBPNMBJ` (inherited from `MKAMHOIHOJK`) +- `string KODBEJPEFOJ` (inherited from `MKAMHOIHOJK`) +- `bool LPJLEMJFBPE` (inherited from `MKAMHOIHOJK`) +- `bool MGBDHBHCDMH` (inherited from `MKAMHOIHOJK`) +- `bool MIKDLDEALPN` (inherited from `MKAMHOIHOJK`) +- `bool OFONEIOEIED` (inherited from `MKAMHOIHOJK`) +- `HJPGEGENLPH OILEJFNPDDB` (inherited from `MKAMHOIHOJK`) +- `bool PEEFHKMOMKK` (inherited from `MKAMHOIHOJK`) + +## rooms / rooms/{0}/promo_external + +- `EJDCNGBEICB` `` RecRoom.Async.IPromise`1 FBNHEKCLMCJ(System.Int64 HNHLJONGKHB, NCELIDGFOEM GEDCEIDOKJL, System.String EKGHGPLFMPJ) `` (EJDCNGBEICB.txt:4862) + +Expected client return: `PPENFJMFPNE` (object) +Resolved DTO: `PPENFJMFPNE` from `PPENFJMFPNE.cs` +Declaration: `public class PPENFJMFPNE : KLCOGEIGEBJ` +Inherits: `KLCOGEIGEBJ` +Inherited parser JSON keys: `RoomId`, `IsDorm`, `CloningAllowed`, `DisableMicAutoMute`, `DisableRoomComments`, `EncryptVoiceChat` +Public/decompiled members: +- `List ADCDBMGNIPB` +- `IReadOnlyList BIODFICLKJH` +- `IReadOnlyList DFKGKGIMEMP` +- `IReadOnlyList EJGIMHADHBC` +- `IReadOnlyList FEBCCOFBKKJ` +- `IReadOnlyList IAJDFKODGFL` +- `IReadOnlyList KFFBPMHGFKB` +- `IReadOnlyList LHGHLGDINFL` +- `IReadOnlyList LOJPLANGFMG` +- `LMLJHMJEIGM MMBJPGGOFHL` +- `bool MMFBPKOOLNG` +- `IReadOnlyList OHNIGJECGKL` +- `IReadOnlyList PKKADKGDHNI` +- `int accountId` +- `string tag` +- `PPENFJMFPNE IGAPDHGFLDC` +- `long DADOKMAOFJL` (inherited from `KLCOGEIGEBJ`) +- `bool GJPGKJJCPBK` (inherited from `KLCOGEIGEBJ`) +- `bool IBLOJJBEKFF` (inherited from `KLCOGEIGEBJ`) +- `bool JCLCOCEOAEP` (inherited from `KLCOGEIGEBJ`) +- `bool JGJCBGEBLLO` (inherited from `KLCOGEIGEBJ`) +- `bool PLFEEIJCHOH` (inherited from `KLCOGEIGEBJ`) +- `long BJFNPKFOALK` (inherited from `KLCOGEIGEBJ`) +- `DateTime ACBFDMLHFPB` (inherited from `MKAMHOIHOJK`) +- `string AHGCOGFEEEE` (inherited from `MKAMHOIHOJK`) +- `bool AKFBNELAMNA` (inherited from `MKAMHOIHOJK`) +- `int BADIGBCKECA` (inherited from `MKAMHOIHOJK`) +- `bool BNBLOBAEDEE` (inherited from `MKAMHOIHOJK`) +- `NMJEKMMBDDE CDINMMPNAID` (inherited from `MKAMHOIHOJK`) +- `bool CDNFGMHLDMJ` (inherited from `MKAMHOIHOJK`) +- `string FIKEBGGCDFN` (inherited from `MKAMHOIHOJK`) +- `GPDIAKNEBKH GIBHIMGJNNO` (inherited from `MKAMHOIHOJK`) +- `bool HPLBOMGACED` (inherited from `MKAMHOIHOJK`) +- `string IGOPGMHHLKI` (inherited from `MKAMHOIHOJK`) +- `DPLPMKMFMPB JFEAPMIPNEP` (inherited from `MKAMHOIHOJK`) +- `bool KHIJAFCHLIA` (inherited from `MKAMHOIHOJK`) +- `bool KLNJBBPNMBJ` (inherited from `MKAMHOIHOJK`) +- `string KODBEJPEFOJ` (inherited from `MKAMHOIHOJK`) +- `bool LPJLEMJFBPE` (inherited from `MKAMHOIHOJK`) +- `bool MGBDHBHCDMH` (inherited from `MKAMHOIHOJK`) +- `bool MIKDLDEALPN` (inherited from `MKAMHOIHOJK`) +- `bool OFONEIOEIED` (inherited from `MKAMHOIHOJK`) +- `HJPGEGENLPH OILEJFNPDDB` (inherited from `MKAMHOIHOJK`) +- `bool PEEFHKMOMKK` (inherited from `MKAMHOIHOJK`) + +## rooms / rooms/{0}/promo_external/{1}/{2} + +- `EJDCNGBEICB` `` RecRoom.Async.IPromise`1 ACGJJCMFOCH(System.Int64 HNHLJONGKHB, NCELIDGFOEM GEDCEIDOKJL, System.String EKGHGPLFMPJ) `` (EJDCNGBEICB.txt:4970) + +Expected client return: `PPENFJMFPNE` (object) +Resolved DTO: `PPENFJMFPNE` from `PPENFJMFPNE.cs` +Declaration: `public class PPENFJMFPNE : KLCOGEIGEBJ` +Inherits: `KLCOGEIGEBJ` +Inherited parser JSON keys: `RoomId`, `IsDorm`, `CloningAllowed`, `DisableMicAutoMute`, `DisableRoomComments`, `EncryptVoiceChat` +Public/decompiled members: +- `List ADCDBMGNIPB` +- `IReadOnlyList BIODFICLKJH` +- `IReadOnlyList DFKGKGIMEMP` +- `IReadOnlyList EJGIMHADHBC` +- `IReadOnlyList FEBCCOFBKKJ` +- `IReadOnlyList IAJDFKODGFL` +- `IReadOnlyList KFFBPMHGFKB` +- `IReadOnlyList LHGHLGDINFL` +- `IReadOnlyList LOJPLANGFMG` +- `LMLJHMJEIGM MMBJPGGOFHL` +- `bool MMFBPKOOLNG` +- `IReadOnlyList OHNIGJECGKL` +- `IReadOnlyList PKKADKGDHNI` +- `int accountId` +- `string tag` +- `PPENFJMFPNE IGAPDHGFLDC` +- `long DADOKMAOFJL` (inherited from `KLCOGEIGEBJ`) +- `bool GJPGKJJCPBK` (inherited from `KLCOGEIGEBJ`) +- `bool IBLOJJBEKFF` (inherited from `KLCOGEIGEBJ`) +- `bool JCLCOCEOAEP` (inherited from `KLCOGEIGEBJ`) +- `bool JGJCBGEBLLO` (inherited from `KLCOGEIGEBJ`) +- `bool PLFEEIJCHOH` (inherited from `KLCOGEIGEBJ`) +- `long BJFNPKFOALK` (inherited from `KLCOGEIGEBJ`) +- `DateTime ACBFDMLHFPB` (inherited from `MKAMHOIHOJK`) +- `string AHGCOGFEEEE` (inherited from `MKAMHOIHOJK`) +- `bool AKFBNELAMNA` (inherited from `MKAMHOIHOJK`) +- `int BADIGBCKECA` (inherited from `MKAMHOIHOJK`) +- `bool BNBLOBAEDEE` (inherited from `MKAMHOIHOJK`) +- `NMJEKMMBDDE CDINMMPNAID` (inherited from `MKAMHOIHOJK`) +- `bool CDNFGMHLDMJ` (inherited from `MKAMHOIHOJK`) +- `string FIKEBGGCDFN` (inherited from `MKAMHOIHOJK`) +- `GPDIAKNEBKH GIBHIMGJNNO` (inherited from `MKAMHOIHOJK`) +- `bool HPLBOMGACED` (inherited from `MKAMHOIHOJK`) +- `string IGOPGMHHLKI` (inherited from `MKAMHOIHOJK`) +- `DPLPMKMFMPB JFEAPMIPNEP` (inherited from `MKAMHOIHOJK`) +- `bool KHIJAFCHLIA` (inherited from `MKAMHOIHOJK`) +- `bool KLNJBBPNMBJ` (inherited from `MKAMHOIHOJK`) +- `string KODBEJPEFOJ` (inherited from `MKAMHOIHOJK`) +- `bool LPJLEMJFBPE` (inherited from `MKAMHOIHOJK`) +- `bool MGBDHBHCDMH` (inherited from `MKAMHOIHOJK`) +- `bool MIKDLDEALPN` (inherited from `MKAMHOIHOJK`) +- `bool OFONEIOEIED` (inherited from `MKAMHOIHOJK`) +- `HJPGEGENLPH OILEJFNPDDB` (inherited from `MKAMHOIHOJK`) +- `bool PEEFHKMOMKK` (inherited from `MKAMHOIHOJK`) + +## rooms / rooms/{0}/promo_images + +- `EJDCNGBEICB` `` RecRoom.Async.IPromise`1 CFIKOAAOHFH(System.Int64 HNHLJONGKHB, System.String HFLPBHHAFIO) `` (EJDCNGBEICB.txt:4663) + +Expected client return: `PPENFJMFPNE` (object) +Resolved DTO: `PPENFJMFPNE` from `PPENFJMFPNE.cs` +Declaration: `public class PPENFJMFPNE : KLCOGEIGEBJ` +Inherits: `KLCOGEIGEBJ` +Inherited parser JSON keys: `RoomId`, `IsDorm`, `CloningAllowed`, `DisableMicAutoMute`, `DisableRoomComments`, `EncryptVoiceChat` +Public/decompiled members: +- `List ADCDBMGNIPB` +- `IReadOnlyList BIODFICLKJH` +- `IReadOnlyList DFKGKGIMEMP` +- `IReadOnlyList EJGIMHADHBC` +- `IReadOnlyList FEBCCOFBKKJ` +- `IReadOnlyList IAJDFKODGFL` +- `IReadOnlyList KFFBPMHGFKB` +- `IReadOnlyList LHGHLGDINFL` +- `IReadOnlyList LOJPLANGFMG` +- `LMLJHMJEIGM MMBJPGGOFHL` +- `bool MMFBPKOOLNG` +- `IReadOnlyList OHNIGJECGKL` +- `IReadOnlyList PKKADKGDHNI` +- `int accountId` +- `string tag` +- `PPENFJMFPNE IGAPDHGFLDC` +- `long DADOKMAOFJL` (inherited from `KLCOGEIGEBJ`) +- `bool GJPGKJJCPBK` (inherited from `KLCOGEIGEBJ`) +- `bool IBLOJJBEKFF` (inherited from `KLCOGEIGEBJ`) +- `bool JCLCOCEOAEP` (inherited from `KLCOGEIGEBJ`) +- `bool JGJCBGEBLLO` (inherited from `KLCOGEIGEBJ`) +- `bool PLFEEIJCHOH` (inherited from `KLCOGEIGEBJ`) +- `long BJFNPKFOALK` (inherited from `KLCOGEIGEBJ`) +- `DateTime ACBFDMLHFPB` (inherited from `MKAMHOIHOJK`) +- `string AHGCOGFEEEE` (inherited from `MKAMHOIHOJK`) +- `bool AKFBNELAMNA` (inherited from `MKAMHOIHOJK`) +- `int BADIGBCKECA` (inherited from `MKAMHOIHOJK`) +- `bool BNBLOBAEDEE` (inherited from `MKAMHOIHOJK`) +- `NMJEKMMBDDE CDINMMPNAID` (inherited from `MKAMHOIHOJK`) +- `bool CDNFGMHLDMJ` (inherited from `MKAMHOIHOJK`) +- `string FIKEBGGCDFN` (inherited from `MKAMHOIHOJK`) +- `GPDIAKNEBKH GIBHIMGJNNO` (inherited from `MKAMHOIHOJK`) +- `bool HPLBOMGACED` (inherited from `MKAMHOIHOJK`) +- `string IGOPGMHHLKI` (inherited from `MKAMHOIHOJK`) +- `DPLPMKMFMPB JFEAPMIPNEP` (inherited from `MKAMHOIHOJK`) +- `bool KHIJAFCHLIA` (inherited from `MKAMHOIHOJK`) +- `bool KLNJBBPNMBJ` (inherited from `MKAMHOIHOJK`) +- `string KODBEJPEFOJ` (inherited from `MKAMHOIHOJK`) +- `bool LPJLEMJFBPE` (inherited from `MKAMHOIHOJK`) +- `bool MGBDHBHCDMH` (inherited from `MKAMHOIHOJK`) +- `bool MIKDLDEALPN` (inherited from `MKAMHOIHOJK`) +- `bool OFONEIOEIED` (inherited from `MKAMHOIHOJK`) +- `HJPGEGENLPH OILEJFNPDDB` (inherited from `MKAMHOIHOJK`) +- `bool PEEFHKMOMKK` (inherited from `MKAMHOIHOJK`) + +## rooms / rooms/{0}/promo_images/{1} + +- `EJDCNGBEICB` `` RecRoom.Async.IPromise`1 OCCGHPAHGKC(System.Int64 HNHLJONGKHB, System.String HFLPBHHAFIO) `` (EJDCNGBEICB.txt:4749) + +Expected client return: `PPENFJMFPNE` (object) +Resolved DTO: `PPENFJMFPNE` from `PPENFJMFPNE.cs` +Declaration: `public class PPENFJMFPNE : KLCOGEIGEBJ` +Inherits: `KLCOGEIGEBJ` +Inherited parser JSON keys: `RoomId`, `IsDorm`, `CloningAllowed`, `DisableMicAutoMute`, `DisableRoomComments`, `EncryptVoiceChat` +Public/decompiled members: +- `List ADCDBMGNIPB` +- `IReadOnlyList BIODFICLKJH` +- `IReadOnlyList DFKGKGIMEMP` +- `IReadOnlyList EJGIMHADHBC` +- `IReadOnlyList FEBCCOFBKKJ` +- `IReadOnlyList IAJDFKODGFL` +- `IReadOnlyList KFFBPMHGFKB` +- `IReadOnlyList LHGHLGDINFL` +- `IReadOnlyList LOJPLANGFMG` +- `LMLJHMJEIGM MMBJPGGOFHL` +- `bool MMFBPKOOLNG` +- `IReadOnlyList OHNIGJECGKL` +- `IReadOnlyList PKKADKGDHNI` +- `int accountId` +- `string tag` +- `PPENFJMFPNE IGAPDHGFLDC` +- `long DADOKMAOFJL` (inherited from `KLCOGEIGEBJ`) +- `bool GJPGKJJCPBK` (inherited from `KLCOGEIGEBJ`) +- `bool IBLOJJBEKFF` (inherited from `KLCOGEIGEBJ`) +- `bool JCLCOCEOAEP` (inherited from `KLCOGEIGEBJ`) +- `bool JGJCBGEBLLO` (inherited from `KLCOGEIGEBJ`) +- `bool PLFEEIJCHOH` (inherited from `KLCOGEIGEBJ`) +- `long BJFNPKFOALK` (inherited from `KLCOGEIGEBJ`) +- `DateTime ACBFDMLHFPB` (inherited from `MKAMHOIHOJK`) +- `string AHGCOGFEEEE` (inherited from `MKAMHOIHOJK`) +- `bool AKFBNELAMNA` (inherited from `MKAMHOIHOJK`) +- `int BADIGBCKECA` (inherited from `MKAMHOIHOJK`) +- `bool BNBLOBAEDEE` (inherited from `MKAMHOIHOJK`) +- `NMJEKMMBDDE CDINMMPNAID` (inherited from `MKAMHOIHOJK`) +- `bool CDNFGMHLDMJ` (inherited from `MKAMHOIHOJK`) +- `string FIKEBGGCDFN` (inherited from `MKAMHOIHOJK`) +- `GPDIAKNEBKH GIBHIMGJNNO` (inherited from `MKAMHOIHOJK`) +- `bool HPLBOMGACED` (inherited from `MKAMHOIHOJK`) +- `string IGOPGMHHLKI` (inherited from `MKAMHOIHOJK`) +- `DPLPMKMFMPB JFEAPMIPNEP` (inherited from `MKAMHOIHOJK`) +- `bool KHIJAFCHLIA` (inherited from `MKAMHOIHOJK`) +- `bool KLNJBBPNMBJ` (inherited from `MKAMHOIHOJK`) +- `string KODBEJPEFOJ` (inherited from `MKAMHOIHOJK`) +- `bool LPJLEMJFBPE` (inherited from `MKAMHOIHOJK`) +- `bool MGBDHBHCDMH` (inherited from `MKAMHOIHOJK`) +- `bool MIKDLDEALPN` (inherited from `MKAMHOIHOJK`) +- `bool OFONEIOEIED` (inherited from `MKAMHOIHOJK`) +- `HJPGEGENLPH OILEJFNPDDB` (inherited from `MKAMHOIHOJK`) +- `bool PEEFHKMOMKK` (inherited from `MKAMHOIHOJK`) + +## rooms / rooms/{0}/restrictions + +- `EJDCNGBEICB` `` RecRoom.Async.IPromise`1 ONEJODPFOMP(System.Int64 HNHLJONGKHB, System.Boolean DDMOEIAHJBK, System.Boolean FOBLJIBLCNI, System.Boolean GELHFEFGJLA, System.Boolean NFIGHDPKLJF) `` (EJDCNGBEICB.txt:3758) + +Expected client return: `PPENFJMFPNE` (object) +Resolved DTO: `PPENFJMFPNE` from `PPENFJMFPNE.cs` +Declaration: `public class PPENFJMFPNE : KLCOGEIGEBJ` +Inherits: `KLCOGEIGEBJ` +Inherited parser JSON keys: `RoomId`, `IsDorm`, `CloningAllowed`, `DisableMicAutoMute`, `DisableRoomComments`, `EncryptVoiceChat` +Public/decompiled members: +- `List ADCDBMGNIPB` +- `IReadOnlyList BIODFICLKJH` +- `IReadOnlyList DFKGKGIMEMP` +- `IReadOnlyList EJGIMHADHBC` +- `IReadOnlyList FEBCCOFBKKJ` +- `IReadOnlyList IAJDFKODGFL` +- `IReadOnlyList KFFBPMHGFKB` +- `IReadOnlyList LHGHLGDINFL` +- `IReadOnlyList LOJPLANGFMG` +- `LMLJHMJEIGM MMBJPGGOFHL` +- `bool MMFBPKOOLNG` +- `IReadOnlyList OHNIGJECGKL` +- `IReadOnlyList PKKADKGDHNI` +- `int accountId` +- `string tag` +- `PPENFJMFPNE IGAPDHGFLDC` +- `long DADOKMAOFJL` (inherited from `KLCOGEIGEBJ`) +- `bool GJPGKJJCPBK` (inherited from `KLCOGEIGEBJ`) +- `bool IBLOJJBEKFF` (inherited from `KLCOGEIGEBJ`) +- `bool JCLCOCEOAEP` (inherited from `KLCOGEIGEBJ`) +- `bool JGJCBGEBLLO` (inherited from `KLCOGEIGEBJ`) +- `bool PLFEEIJCHOH` (inherited from `KLCOGEIGEBJ`) +- `long BJFNPKFOALK` (inherited from `KLCOGEIGEBJ`) +- `DateTime ACBFDMLHFPB` (inherited from `MKAMHOIHOJK`) +- `string AHGCOGFEEEE` (inherited from `MKAMHOIHOJK`) +- `bool AKFBNELAMNA` (inherited from `MKAMHOIHOJK`) +- `int BADIGBCKECA` (inherited from `MKAMHOIHOJK`) +- `bool BNBLOBAEDEE` (inherited from `MKAMHOIHOJK`) +- `NMJEKMMBDDE CDINMMPNAID` (inherited from `MKAMHOIHOJK`) +- `bool CDNFGMHLDMJ` (inherited from `MKAMHOIHOJK`) +- `string FIKEBGGCDFN` (inherited from `MKAMHOIHOJK`) +- `GPDIAKNEBKH GIBHIMGJNNO` (inherited from `MKAMHOIHOJK`) +- `bool HPLBOMGACED` (inherited from `MKAMHOIHOJK`) +- `string IGOPGMHHLKI` (inherited from `MKAMHOIHOJK`) +- `DPLPMKMFMPB JFEAPMIPNEP` (inherited from `MKAMHOIHOJK`) +- `bool KHIJAFCHLIA` (inherited from `MKAMHOIHOJK`) +- `bool KLNJBBPNMBJ` (inherited from `MKAMHOIHOJK`) +- `string KODBEJPEFOJ` (inherited from `MKAMHOIHOJK`) +- `bool LPJLEMJFBPE` (inherited from `MKAMHOIHOJK`) +- `bool MGBDHBHCDMH` (inherited from `MKAMHOIHOJK`) +- `bool MIKDLDEALPN` (inherited from `MKAMHOIHOJK`) +- `bool OFONEIOEIED` (inherited from `MKAMHOIHOJK`) +- `HJPGEGENLPH OILEJFNPDDB` (inherited from `MKAMHOIHOJK`) +- `bool PEEFHKMOMKK` (inherited from `MKAMHOIHOJK`) + +## rooms / rooms/{0}/roles + +- `EJDCNGBEICB` `` RecRoom.Async.IPromise`1> MIGOLOHAIAL(System.Int64 HNHLJONGKHB) `` (EJDCNGBEICB.txt:1254) + +Expected client return: `` System.Collections.Generic.List`1 `` (array) +Resolved DTO: `CGCEKBCIHJC` from `CGCEKBCIHJC.cs` +Declaration: `public class CGCEKBCIHJC : IFAIJAGLDFK` +Client parser JSON keys: `AccountId`, `Role`, `InvitedRole` +Public/decompiled members: +- `LMLJHMJEIGM CAILAOLBBPM` +- `LMLJHMJEIGM CBLIGPOFEDE` +- `ObscuredInt GAINIOENNCG` + +## rooms / rooms/{0}/roles/{1} + +- `EJDCNGBEICB` `` RecRoom.Async.IPromise`1 EKOKFNHHMPI(System.Int64 HNHLJONGKHB, System.Int32 GKLPIFBPGOD) `` (EJDCNGBEICB.txt:1332) +- `EJDCNGBEICB` `` RecRoom.Async.IPromise`1 GJMGBBCIHGH(System.Int64 HNHLJONGKHB, System.Int32 GKLPIFBPGOD, LMLJHMJEIGM IENKDAKBEDP) `` (EJDCNGBEICB.txt:4405) + +Expected client return: `CGCEKBCIHJC` (object) +Resolved DTO: `CGCEKBCIHJC` from `CGCEKBCIHJC.cs` +Declaration: `public class CGCEKBCIHJC : IFAIJAGLDFK` +Client parser JSON keys: `AccountId`, `Role`, `InvitedRole` +Public/decompiled members: +- `LMLJHMJEIGM CAILAOLBBPM` +- `LMLJHMJEIGM CBLIGPOFEDE` +- `ObscuredInt GAINIOENNCG` + +Expected client return: `PPENFJMFPNE` (object) +Resolved DTO: `PPENFJMFPNE` from `PPENFJMFPNE.cs` +Declaration: `public class PPENFJMFPNE : KLCOGEIGEBJ` +Inherits: `KLCOGEIGEBJ` +Inherited parser JSON keys: `RoomId`, `IsDorm`, `CloningAllowed`, `DisableMicAutoMute`, `DisableRoomComments`, `EncryptVoiceChat` +Public/decompiled members: +- `List ADCDBMGNIPB` +- `IReadOnlyList BIODFICLKJH` +- `IReadOnlyList DFKGKGIMEMP` +- `IReadOnlyList EJGIMHADHBC` +- `IReadOnlyList FEBCCOFBKKJ` +- `IReadOnlyList IAJDFKODGFL` +- `IReadOnlyList KFFBPMHGFKB` +- `IReadOnlyList LHGHLGDINFL` +- `IReadOnlyList LOJPLANGFMG` +- `LMLJHMJEIGM MMBJPGGOFHL` +- `bool MMFBPKOOLNG` +- `IReadOnlyList OHNIGJECGKL` +- `IReadOnlyList PKKADKGDHNI` +- `int accountId` +- `string tag` +- `PPENFJMFPNE IGAPDHGFLDC` +- `long DADOKMAOFJL` (inherited from `KLCOGEIGEBJ`) +- `bool GJPGKJJCPBK` (inherited from `KLCOGEIGEBJ`) +- `bool IBLOJJBEKFF` (inherited from `KLCOGEIGEBJ`) +- `bool JCLCOCEOAEP` (inherited from `KLCOGEIGEBJ`) +- `bool JGJCBGEBLLO` (inherited from `KLCOGEIGEBJ`) +- `bool PLFEEIJCHOH` (inherited from `KLCOGEIGEBJ`) +- `long BJFNPKFOALK` (inherited from `KLCOGEIGEBJ`) +- `DateTime ACBFDMLHFPB` (inherited from `MKAMHOIHOJK`) +- `string AHGCOGFEEEE` (inherited from `MKAMHOIHOJK`) +- `bool AKFBNELAMNA` (inherited from `MKAMHOIHOJK`) +- `int BADIGBCKECA` (inherited from `MKAMHOIHOJK`) +- `bool BNBLOBAEDEE` (inherited from `MKAMHOIHOJK`) +- `NMJEKMMBDDE CDINMMPNAID` (inherited from `MKAMHOIHOJK`) +- `bool CDNFGMHLDMJ` (inherited from `MKAMHOIHOJK`) +- `string FIKEBGGCDFN` (inherited from `MKAMHOIHOJK`) +- `GPDIAKNEBKH GIBHIMGJNNO` (inherited from `MKAMHOIHOJK`) +- `bool HPLBOMGACED` (inherited from `MKAMHOIHOJK`) +- `string IGOPGMHHLKI` (inherited from `MKAMHOIHOJK`) +- `DPLPMKMFMPB JFEAPMIPNEP` (inherited from `MKAMHOIHOJK`) +- `bool KHIJAFCHLIA` (inherited from `MKAMHOIHOJK`) +- `bool KLNJBBPNMBJ` (inherited from `MKAMHOIHOJK`) +- `string KODBEJPEFOJ` (inherited from `MKAMHOIHOJK`) +- `bool LPJLEMJFBPE` (inherited from `MKAMHOIHOJK`) +- `bool MGBDHBHCDMH` (inherited from `MKAMHOIHOJK`) +- `bool MIKDLDEALPN` (inherited from `MKAMHOIHOJK`) +- `bool OFONEIOEIED` (inherited from `MKAMHOIHOJK`) +- `HJPGEGENLPH OILEJFNPDDB` (inherited from `MKAMHOIHOJK`) +- `bool PEEFHKMOMKK` (inherited from `MKAMHOIHOJK`) + +## rooms / rooms/{0}/roles/{1}/invite + +- `EJDCNGBEICB` `` RecRoom.Async.IPromise`1 MJOHJLHDMHB(System.Int64 HNHLJONGKHB, System.Int32 GKLPIFBPGOD, LMLJHMJEIGM IENKDAKBEDP) `` (EJDCNGBEICB.txt:4543) + +Expected client return: `PPENFJMFPNE` (object) +Resolved DTO: `PPENFJMFPNE` from `PPENFJMFPNE.cs` +Declaration: `public class PPENFJMFPNE : KLCOGEIGEBJ` +Inherits: `KLCOGEIGEBJ` +Inherited parser JSON keys: `RoomId`, `IsDorm`, `CloningAllowed`, `DisableMicAutoMute`, `DisableRoomComments`, `EncryptVoiceChat` +Public/decompiled members: +- `List ADCDBMGNIPB` +- `IReadOnlyList BIODFICLKJH` +- `IReadOnlyList DFKGKGIMEMP` +- `IReadOnlyList EJGIMHADHBC` +- `IReadOnlyList FEBCCOFBKKJ` +- `IReadOnlyList IAJDFKODGFL` +- `IReadOnlyList KFFBPMHGFKB` +- `IReadOnlyList LHGHLGDINFL` +- `IReadOnlyList LOJPLANGFMG` +- `LMLJHMJEIGM MMBJPGGOFHL` +- `bool MMFBPKOOLNG` +- `IReadOnlyList OHNIGJECGKL` +- `IReadOnlyList PKKADKGDHNI` +- `int accountId` +- `string tag` +- `PPENFJMFPNE IGAPDHGFLDC` +- `long DADOKMAOFJL` (inherited from `KLCOGEIGEBJ`) +- `bool GJPGKJJCPBK` (inherited from `KLCOGEIGEBJ`) +- `bool IBLOJJBEKFF` (inherited from `KLCOGEIGEBJ`) +- `bool JCLCOCEOAEP` (inherited from `KLCOGEIGEBJ`) +- `bool JGJCBGEBLLO` (inherited from `KLCOGEIGEBJ`) +- `bool PLFEEIJCHOH` (inherited from `KLCOGEIGEBJ`) +- `long BJFNPKFOALK` (inherited from `KLCOGEIGEBJ`) +- `DateTime ACBFDMLHFPB` (inherited from `MKAMHOIHOJK`) +- `string AHGCOGFEEEE` (inherited from `MKAMHOIHOJK`) +- `bool AKFBNELAMNA` (inherited from `MKAMHOIHOJK`) +- `int BADIGBCKECA` (inherited from `MKAMHOIHOJK`) +- `bool BNBLOBAEDEE` (inherited from `MKAMHOIHOJK`) +- `NMJEKMMBDDE CDINMMPNAID` (inherited from `MKAMHOIHOJK`) +- `bool CDNFGMHLDMJ` (inherited from `MKAMHOIHOJK`) +- `string FIKEBGGCDFN` (inherited from `MKAMHOIHOJK`) +- `GPDIAKNEBKH GIBHIMGJNNO` (inherited from `MKAMHOIHOJK`) +- `bool HPLBOMGACED` (inherited from `MKAMHOIHOJK`) +- `string IGOPGMHHLKI` (inherited from `MKAMHOIHOJK`) +- `DPLPMKMFMPB JFEAPMIPNEP` (inherited from `MKAMHOIHOJK`) +- `bool KHIJAFCHLIA` (inherited from `MKAMHOIHOJK`) +- `bool KLNJBBPNMBJ` (inherited from `MKAMHOIHOJK`) +- `string KODBEJPEFOJ` (inherited from `MKAMHOIHOJK`) +- `bool LPJLEMJFBPE` (inherited from `MKAMHOIHOJK`) +- `bool MGBDHBHCDMH` (inherited from `MKAMHOIHOJK`) +- `bool MIKDLDEALPN` (inherited from `MKAMHOIHOJK`) +- `bool OFONEIOEIED` (inherited from `MKAMHOIHOJK`) +- `HJPGEGENLPH OILEJFNPDDB` (inherited from `MKAMHOIHOJK`) +- `bool PEEFHKMOMKK` (inherited from `MKAMHOIHOJK`) + +## rooms / rooms/{0}/subrooms + +- `EJDCNGBEICB` `` RecRoom.Async.IPromise`1 OGNIELCNHIM(System.Int64 HNHLJONGKHB, System.String MMBOKOLAJFH) `` (EJDCNGBEICB.txt:5487) + +Expected client return: `PPENFJMFPNE` (object) +Resolved DTO: `PPENFJMFPNE` from `PPENFJMFPNE.cs` +Declaration: `public class PPENFJMFPNE : KLCOGEIGEBJ` +Inherits: `KLCOGEIGEBJ` +Inherited parser JSON keys: `RoomId`, `IsDorm`, `CloningAllowed`, `DisableMicAutoMute`, `DisableRoomComments`, `EncryptVoiceChat` +Public/decompiled members: +- `List ADCDBMGNIPB` +- `IReadOnlyList BIODFICLKJH` +- `IReadOnlyList DFKGKGIMEMP` +- `IReadOnlyList EJGIMHADHBC` +- `IReadOnlyList FEBCCOFBKKJ` +- `IReadOnlyList IAJDFKODGFL` +- `IReadOnlyList KFFBPMHGFKB` +- `IReadOnlyList LHGHLGDINFL` +- `IReadOnlyList LOJPLANGFMG` +- `LMLJHMJEIGM MMBJPGGOFHL` +- `bool MMFBPKOOLNG` +- `IReadOnlyList OHNIGJECGKL` +- `IReadOnlyList PKKADKGDHNI` +- `int accountId` +- `string tag` +- `PPENFJMFPNE IGAPDHGFLDC` +- `long DADOKMAOFJL` (inherited from `KLCOGEIGEBJ`) +- `bool GJPGKJJCPBK` (inherited from `KLCOGEIGEBJ`) +- `bool IBLOJJBEKFF` (inherited from `KLCOGEIGEBJ`) +- `bool JCLCOCEOAEP` (inherited from `KLCOGEIGEBJ`) +- `bool JGJCBGEBLLO` (inherited from `KLCOGEIGEBJ`) +- `bool PLFEEIJCHOH` (inherited from `KLCOGEIGEBJ`) +- `long BJFNPKFOALK` (inherited from `KLCOGEIGEBJ`) +- `DateTime ACBFDMLHFPB` (inherited from `MKAMHOIHOJK`) +- `string AHGCOGFEEEE` (inherited from `MKAMHOIHOJK`) +- `bool AKFBNELAMNA` (inherited from `MKAMHOIHOJK`) +- `int BADIGBCKECA` (inherited from `MKAMHOIHOJK`) +- `bool BNBLOBAEDEE` (inherited from `MKAMHOIHOJK`) +- `NMJEKMMBDDE CDINMMPNAID` (inherited from `MKAMHOIHOJK`) +- `bool CDNFGMHLDMJ` (inherited from `MKAMHOIHOJK`) +- `string FIKEBGGCDFN` (inherited from `MKAMHOIHOJK`) +- `GPDIAKNEBKH GIBHIMGJNNO` (inherited from `MKAMHOIHOJK`) +- `bool HPLBOMGACED` (inherited from `MKAMHOIHOJK`) +- `string IGOPGMHHLKI` (inherited from `MKAMHOIHOJK`) +- `DPLPMKMFMPB JFEAPMIPNEP` (inherited from `MKAMHOIHOJK`) +- `bool KHIJAFCHLIA` (inherited from `MKAMHOIHOJK`) +- `bool KLNJBBPNMBJ` (inherited from `MKAMHOIHOJK`) +- `string KODBEJPEFOJ` (inherited from `MKAMHOIHOJK`) +- `bool LPJLEMJFBPE` (inherited from `MKAMHOIHOJK`) +- `bool MGBDHBHCDMH` (inherited from `MKAMHOIHOJK`) +- `bool MIKDLDEALPN` (inherited from `MKAMHOIHOJK`) +- `bool OFONEIOEIED` (inherited from `MKAMHOIHOJK`) +- `HJPGEGENLPH OILEJFNPDDB` (inherited from `MKAMHOIHOJK`) +- `bool PEEFHKMOMKK` (inherited from `MKAMHOIHOJK`) + +## rooms / rooms/{0}/subrooms/{1} + +- `EJDCNGBEICB` `` RecRoom.Async.IPromise`1 GGCOGNAGPBP(System.Int64 HNHLJONGKHB, System.Int64 FODGKNJIGOP) `` (EJDCNGBEICB.txt:5809) + +Expected client return: `PPENFJMFPNE` (object) +Resolved DTO: `PPENFJMFPNE` from `PPENFJMFPNE.cs` +Declaration: `public class PPENFJMFPNE : KLCOGEIGEBJ` +Inherits: `KLCOGEIGEBJ` +Inherited parser JSON keys: `RoomId`, `IsDorm`, `CloningAllowed`, `DisableMicAutoMute`, `DisableRoomComments`, `EncryptVoiceChat` +Public/decompiled members: +- `List ADCDBMGNIPB` +- `IReadOnlyList BIODFICLKJH` +- `IReadOnlyList DFKGKGIMEMP` +- `IReadOnlyList EJGIMHADHBC` +- `IReadOnlyList FEBCCOFBKKJ` +- `IReadOnlyList IAJDFKODGFL` +- `IReadOnlyList KFFBPMHGFKB` +- `IReadOnlyList LHGHLGDINFL` +- `IReadOnlyList LOJPLANGFMG` +- `LMLJHMJEIGM MMBJPGGOFHL` +- `bool MMFBPKOOLNG` +- `IReadOnlyList OHNIGJECGKL` +- `IReadOnlyList PKKADKGDHNI` +- `int accountId` +- `string tag` +- `PPENFJMFPNE IGAPDHGFLDC` +- `long DADOKMAOFJL` (inherited from `KLCOGEIGEBJ`) +- `bool GJPGKJJCPBK` (inherited from `KLCOGEIGEBJ`) +- `bool IBLOJJBEKFF` (inherited from `KLCOGEIGEBJ`) +- `bool JCLCOCEOAEP` (inherited from `KLCOGEIGEBJ`) +- `bool JGJCBGEBLLO` (inherited from `KLCOGEIGEBJ`) +- `bool PLFEEIJCHOH` (inherited from `KLCOGEIGEBJ`) +- `long BJFNPKFOALK` (inherited from `KLCOGEIGEBJ`) +- `DateTime ACBFDMLHFPB` (inherited from `MKAMHOIHOJK`) +- `string AHGCOGFEEEE` (inherited from `MKAMHOIHOJK`) +- `bool AKFBNELAMNA` (inherited from `MKAMHOIHOJK`) +- `int BADIGBCKECA` (inherited from `MKAMHOIHOJK`) +- `bool BNBLOBAEDEE` (inherited from `MKAMHOIHOJK`) +- `NMJEKMMBDDE CDINMMPNAID` (inherited from `MKAMHOIHOJK`) +- `bool CDNFGMHLDMJ` (inherited from `MKAMHOIHOJK`) +- `string FIKEBGGCDFN` (inherited from `MKAMHOIHOJK`) +- `GPDIAKNEBKH GIBHIMGJNNO` (inherited from `MKAMHOIHOJK`) +- `bool HPLBOMGACED` (inherited from `MKAMHOIHOJK`) +- `string IGOPGMHHLKI` (inherited from `MKAMHOIHOJK`) +- `DPLPMKMFMPB JFEAPMIPNEP` (inherited from `MKAMHOIHOJK`) +- `bool KHIJAFCHLIA` (inherited from `MKAMHOIHOJK`) +- `bool KLNJBBPNMBJ` (inherited from `MKAMHOIHOJK`) +- `string KODBEJPEFOJ` (inherited from `MKAMHOIHOJK`) +- `bool LPJLEMJFBPE` (inherited from `MKAMHOIHOJK`) +- `bool MGBDHBHCDMH` (inherited from `MKAMHOIHOJK`) +- `bool MIKDLDEALPN` (inherited from `MKAMHOIHOJK`) +- `bool OFONEIOEIED` (inherited from `MKAMHOIHOJK`) +- `HJPGEGENLPH OILEJFNPDDB` (inherited from `MKAMHOIHOJK`) +- `bool PEEFHKMOMKK` (inherited from `MKAMHOIHOJK`) + +## rooms / rooms/{0}/subrooms/{1}/accessibility + +- `EJDCNGBEICB` `` RecRoom.Async.IPromise`1 POGEHAMJBPE(System.Int64 HNHLJONGKHB, System.Int64 FODGKNJIGOP, DPLPMKMFMPB PONCIIJOHIE) `` (EJDCNGBEICB.txt:5369) + +Expected client return: `PPENFJMFPNE` (object) +Resolved DTO: `PPENFJMFPNE` from `PPENFJMFPNE.cs` +Declaration: `public class PPENFJMFPNE : KLCOGEIGEBJ` +Inherits: `KLCOGEIGEBJ` +Inherited parser JSON keys: `RoomId`, `IsDorm`, `CloningAllowed`, `DisableMicAutoMute`, `DisableRoomComments`, `EncryptVoiceChat` +Public/decompiled members: +- `List ADCDBMGNIPB` +- `IReadOnlyList BIODFICLKJH` +- `IReadOnlyList DFKGKGIMEMP` +- `IReadOnlyList EJGIMHADHBC` +- `IReadOnlyList FEBCCOFBKKJ` +- `IReadOnlyList IAJDFKODGFL` +- `IReadOnlyList KFFBPMHGFKB` +- `IReadOnlyList LHGHLGDINFL` +- `IReadOnlyList LOJPLANGFMG` +- `LMLJHMJEIGM MMBJPGGOFHL` +- `bool MMFBPKOOLNG` +- `IReadOnlyList OHNIGJECGKL` +- `IReadOnlyList PKKADKGDHNI` +- `int accountId` +- `string tag` +- `PPENFJMFPNE IGAPDHGFLDC` +- `long DADOKMAOFJL` (inherited from `KLCOGEIGEBJ`) +- `bool GJPGKJJCPBK` (inherited from `KLCOGEIGEBJ`) +- `bool IBLOJJBEKFF` (inherited from `KLCOGEIGEBJ`) +- `bool JCLCOCEOAEP` (inherited from `KLCOGEIGEBJ`) +- `bool JGJCBGEBLLO` (inherited from `KLCOGEIGEBJ`) +- `bool PLFEEIJCHOH` (inherited from `KLCOGEIGEBJ`) +- `long BJFNPKFOALK` (inherited from `KLCOGEIGEBJ`) +- `DateTime ACBFDMLHFPB` (inherited from `MKAMHOIHOJK`) +- `string AHGCOGFEEEE` (inherited from `MKAMHOIHOJK`) +- `bool AKFBNELAMNA` (inherited from `MKAMHOIHOJK`) +- `int BADIGBCKECA` (inherited from `MKAMHOIHOJK`) +- `bool BNBLOBAEDEE` (inherited from `MKAMHOIHOJK`) +- `NMJEKMMBDDE CDINMMPNAID` (inherited from `MKAMHOIHOJK`) +- `bool CDNFGMHLDMJ` (inherited from `MKAMHOIHOJK`) +- `string FIKEBGGCDFN` (inherited from `MKAMHOIHOJK`) +- `GPDIAKNEBKH GIBHIMGJNNO` (inherited from `MKAMHOIHOJK`) +- `bool HPLBOMGACED` (inherited from `MKAMHOIHOJK`) +- `string IGOPGMHHLKI` (inherited from `MKAMHOIHOJK`) +- `DPLPMKMFMPB JFEAPMIPNEP` (inherited from `MKAMHOIHOJK`) +- `bool KHIJAFCHLIA` (inherited from `MKAMHOIHOJK`) +- `bool KLNJBBPNMBJ` (inherited from `MKAMHOIHOJK`) +- `string KODBEJPEFOJ` (inherited from `MKAMHOIHOJK`) +- `bool LPJLEMJFBPE` (inherited from `MKAMHOIHOJK`) +- `bool MGBDHBHCDMH` (inherited from `MKAMHOIHOJK`) +- `bool MIKDLDEALPN` (inherited from `MKAMHOIHOJK`) +- `bool OFONEIOEIED` (inherited from `MKAMHOIHOJK`) +- `HJPGEGENLPH OILEJFNPDDB` (inherited from `MKAMHOIHOJK`) +- `bool PEEFHKMOMKK` (inherited from `MKAMHOIHOJK`) + +## rooms / rooms/{0}/subrooms/{1}/clone + +- `EJDCNGBEICB` `` RecRoom.Async.IPromise`1 OGEKCICPACB(System.Int64 HNHLJONGKHB, System.Int64 FODGKNJIGOP) `` (EJDCNGBEICB.txt:5583) + +Expected client return: `PPENFJMFPNE` (object) +Resolved DTO: `PPENFJMFPNE` from `PPENFJMFPNE.cs` +Declaration: `public class PPENFJMFPNE : KLCOGEIGEBJ` +Inherits: `KLCOGEIGEBJ` +Inherited parser JSON keys: `RoomId`, `IsDorm`, `CloningAllowed`, `DisableMicAutoMute`, `DisableRoomComments`, `EncryptVoiceChat` +Public/decompiled members: +- `List ADCDBMGNIPB` +- `IReadOnlyList BIODFICLKJH` +- `IReadOnlyList DFKGKGIMEMP` +- `IReadOnlyList EJGIMHADHBC` +- `IReadOnlyList FEBCCOFBKKJ` +- `IReadOnlyList IAJDFKODGFL` +- `IReadOnlyList KFFBPMHGFKB` +- `IReadOnlyList LHGHLGDINFL` +- `IReadOnlyList LOJPLANGFMG` +- `LMLJHMJEIGM MMBJPGGOFHL` +- `bool MMFBPKOOLNG` +- `IReadOnlyList OHNIGJECGKL` +- `IReadOnlyList PKKADKGDHNI` +- `int accountId` +- `string tag` +- `PPENFJMFPNE IGAPDHGFLDC` +- `long DADOKMAOFJL` (inherited from `KLCOGEIGEBJ`) +- `bool GJPGKJJCPBK` (inherited from `KLCOGEIGEBJ`) +- `bool IBLOJJBEKFF` (inherited from `KLCOGEIGEBJ`) +- `bool JCLCOCEOAEP` (inherited from `KLCOGEIGEBJ`) +- `bool JGJCBGEBLLO` (inherited from `KLCOGEIGEBJ`) +- `bool PLFEEIJCHOH` (inherited from `KLCOGEIGEBJ`) +- `long BJFNPKFOALK` (inherited from `KLCOGEIGEBJ`) +- `DateTime ACBFDMLHFPB` (inherited from `MKAMHOIHOJK`) +- `string AHGCOGFEEEE` (inherited from `MKAMHOIHOJK`) +- `bool AKFBNELAMNA` (inherited from `MKAMHOIHOJK`) +- `int BADIGBCKECA` (inherited from `MKAMHOIHOJK`) +- `bool BNBLOBAEDEE` (inherited from `MKAMHOIHOJK`) +- `NMJEKMMBDDE CDINMMPNAID` (inherited from `MKAMHOIHOJK`) +- `bool CDNFGMHLDMJ` (inherited from `MKAMHOIHOJK`) +- `string FIKEBGGCDFN` (inherited from `MKAMHOIHOJK`) +- `GPDIAKNEBKH GIBHIMGJNNO` (inherited from `MKAMHOIHOJK`) +- `bool HPLBOMGACED` (inherited from `MKAMHOIHOJK`) +- `string IGOPGMHHLKI` (inherited from `MKAMHOIHOJK`) +- `DPLPMKMFMPB JFEAPMIPNEP` (inherited from `MKAMHOIHOJK`) +- `bool KHIJAFCHLIA` (inherited from `MKAMHOIHOJK`) +- `bool KLNJBBPNMBJ` (inherited from `MKAMHOIHOJK`) +- `string KODBEJPEFOJ` (inherited from `MKAMHOIHOJK`) +- `bool LPJLEMJFBPE` (inherited from `MKAMHOIHOJK`) +- `bool MGBDHBHCDMH` (inherited from `MKAMHOIHOJK`) +- `bool MIKDLDEALPN` (inherited from `MKAMHOIHOJK`) +- `bool OFONEIOEIED` (inherited from `MKAMHOIHOJK`) +- `HJPGEGENLPH OILEJFNPDDB` (inherited from `MKAMHOIHOJK`) +- `bool PEEFHKMOMKK` (inherited from `MKAMHOIHOJK`) + +## rooms / rooms/{0}/subrooms/{1}/data + +- `EJDCNGBEICB` `` RecRoom.Async.IPromise`1 HOPMJFDKPDL(System.Int64 HNHLJONGKHB, System.Int64 FODGKNJIGOP, System.String MEIIMAIGBJD, System.Collections.Generic.Dictionary`2 PNOBBNPJDLI, System.Int32 OMCLBIFCHLF) `` (EJDCNGBEICB.txt:5995) + +Expected client return: `PPENFJMFPNE` (object) +Resolved DTO: `PPENFJMFPNE` from `PPENFJMFPNE.cs` +Declaration: `public class PPENFJMFPNE : KLCOGEIGEBJ` +Inherits: `KLCOGEIGEBJ` +Inherited parser JSON keys: `RoomId`, `IsDorm`, `CloningAllowed`, `DisableMicAutoMute`, `DisableRoomComments`, `EncryptVoiceChat` +Public/decompiled members: +- `List ADCDBMGNIPB` +- `IReadOnlyList BIODFICLKJH` +- `IReadOnlyList DFKGKGIMEMP` +- `IReadOnlyList EJGIMHADHBC` +- `IReadOnlyList FEBCCOFBKKJ` +- `IReadOnlyList IAJDFKODGFL` +- `IReadOnlyList KFFBPMHGFKB` +- `IReadOnlyList LHGHLGDINFL` +- `IReadOnlyList LOJPLANGFMG` +- `LMLJHMJEIGM MMBJPGGOFHL` +- `bool MMFBPKOOLNG` +- `IReadOnlyList OHNIGJECGKL` +- `IReadOnlyList PKKADKGDHNI` +- `int accountId` +- `string tag` +- `PPENFJMFPNE IGAPDHGFLDC` +- `long DADOKMAOFJL` (inherited from `KLCOGEIGEBJ`) +- `bool GJPGKJJCPBK` (inherited from `KLCOGEIGEBJ`) +- `bool IBLOJJBEKFF` (inherited from `KLCOGEIGEBJ`) +- `bool JCLCOCEOAEP` (inherited from `KLCOGEIGEBJ`) +- `bool JGJCBGEBLLO` (inherited from `KLCOGEIGEBJ`) +- `bool PLFEEIJCHOH` (inherited from `KLCOGEIGEBJ`) +- `long BJFNPKFOALK` (inherited from `KLCOGEIGEBJ`) +- `DateTime ACBFDMLHFPB` (inherited from `MKAMHOIHOJK`) +- `string AHGCOGFEEEE` (inherited from `MKAMHOIHOJK`) +- `bool AKFBNELAMNA` (inherited from `MKAMHOIHOJK`) +- `int BADIGBCKECA` (inherited from `MKAMHOIHOJK`) +- `bool BNBLOBAEDEE` (inherited from `MKAMHOIHOJK`) +- `NMJEKMMBDDE CDINMMPNAID` (inherited from `MKAMHOIHOJK`) +- `bool CDNFGMHLDMJ` (inherited from `MKAMHOIHOJK`) +- `string FIKEBGGCDFN` (inherited from `MKAMHOIHOJK`) +- `GPDIAKNEBKH GIBHIMGJNNO` (inherited from `MKAMHOIHOJK`) +- `bool HPLBOMGACED` (inherited from `MKAMHOIHOJK`) +- `string IGOPGMHHLKI` (inherited from `MKAMHOIHOJK`) +- `DPLPMKMFMPB JFEAPMIPNEP` (inherited from `MKAMHOIHOJK`) +- `bool KHIJAFCHLIA` (inherited from `MKAMHOIHOJK`) +- `bool KLNJBBPNMBJ` (inherited from `MKAMHOIHOJK`) +- `string KODBEJPEFOJ` (inherited from `MKAMHOIHOJK`) +- `bool LPJLEMJFBPE` (inherited from `MKAMHOIHOJK`) +- `bool MGBDHBHCDMH` (inherited from `MKAMHOIHOJK`) +- `bool MIKDLDEALPN` (inherited from `MKAMHOIHOJK`) +- `bool OFONEIOEIED` (inherited from `MKAMHOIHOJK`) +- `HJPGEGENLPH OILEJFNPDDB` (inherited from `MKAMHOIHOJK`) +- `bool PEEFHKMOMKK` (inherited from `MKAMHOIHOJK`) + +## rooms / rooms/{0}/subrooms/{1}/datahistory + +- `EJDCNGBEICB` `` RecRoom.Async.IPromise`1> HAMBEABEIOC(System.Int64 HNHLJONGKHB, System.Int64 FODGKNJIGOP) `` (EJDCNGBEICB.txt:1190) + +Expected client return: `` System.Collections.Generic.List`1 `` (array) +Resolved DTO: `DHOCPFIOKHD` from `DHOCPFIOKHD.cs` +Declaration: `public class DHOCPFIOKHD : IFAIJAGLDFK` +Client parser JSON keys: `SubRoomId`, `DataBlob`, `SavedByAccountId`, `CreatedAt` +Public/decompiled members: +- `DateTime ACBFDMLHFPB` +- `string CHKBMPIFNHH` +- `long GJPHHEHBCIJ` +- `int NDCONBJJDPM` + +## rooms / rooms/{0}/subrooms/{1}/maxplayers + +- `EJDCNGBEICB` `` RecRoom.Async.IPromise`1 AEBGMLMMKOJ(System.Int64 HNHLJONGKHB, System.Int64 FODGKNJIGOP, System.Int32 FFAGIDMFPIF) `` (EJDCNGBEICB.txt:5235) + +Expected client return: `PPENFJMFPNE` (object) +Resolved DTO: `PPENFJMFPNE` from `PPENFJMFPNE.cs` +Declaration: `public class PPENFJMFPNE : KLCOGEIGEBJ` +Inherits: `KLCOGEIGEBJ` +Inherited parser JSON keys: `RoomId`, `IsDorm`, `CloningAllowed`, `DisableMicAutoMute`, `DisableRoomComments`, `EncryptVoiceChat` +Public/decompiled members: +- `List ADCDBMGNIPB` +- `IReadOnlyList BIODFICLKJH` +- `IReadOnlyList DFKGKGIMEMP` +- `IReadOnlyList EJGIMHADHBC` +- `IReadOnlyList FEBCCOFBKKJ` +- `IReadOnlyList IAJDFKODGFL` +- `IReadOnlyList KFFBPMHGFKB` +- `IReadOnlyList LHGHLGDINFL` +- `IReadOnlyList LOJPLANGFMG` +- `LMLJHMJEIGM MMBJPGGOFHL` +- `bool MMFBPKOOLNG` +- `IReadOnlyList OHNIGJECGKL` +- `IReadOnlyList PKKADKGDHNI` +- `int accountId` +- `string tag` +- `PPENFJMFPNE IGAPDHGFLDC` +- `long DADOKMAOFJL` (inherited from `KLCOGEIGEBJ`) +- `bool GJPGKJJCPBK` (inherited from `KLCOGEIGEBJ`) +- `bool IBLOJJBEKFF` (inherited from `KLCOGEIGEBJ`) +- `bool JCLCOCEOAEP` (inherited from `KLCOGEIGEBJ`) +- `bool JGJCBGEBLLO` (inherited from `KLCOGEIGEBJ`) +- `bool PLFEEIJCHOH` (inherited from `KLCOGEIGEBJ`) +- `long BJFNPKFOALK` (inherited from `KLCOGEIGEBJ`) +- `DateTime ACBFDMLHFPB` (inherited from `MKAMHOIHOJK`) +- `string AHGCOGFEEEE` (inherited from `MKAMHOIHOJK`) +- `bool AKFBNELAMNA` (inherited from `MKAMHOIHOJK`) +- `int BADIGBCKECA` (inherited from `MKAMHOIHOJK`) +- `bool BNBLOBAEDEE` (inherited from `MKAMHOIHOJK`) +- `NMJEKMMBDDE CDINMMPNAID` (inherited from `MKAMHOIHOJK`) +- `bool CDNFGMHLDMJ` (inherited from `MKAMHOIHOJK`) +- `string FIKEBGGCDFN` (inherited from `MKAMHOIHOJK`) +- `GPDIAKNEBKH GIBHIMGJNNO` (inherited from `MKAMHOIHOJK`) +- `bool HPLBOMGACED` (inherited from `MKAMHOIHOJK`) +- `string IGOPGMHHLKI` (inherited from `MKAMHOIHOJK`) +- `DPLPMKMFMPB JFEAPMIPNEP` (inherited from `MKAMHOIHOJK`) +- `bool KHIJAFCHLIA` (inherited from `MKAMHOIHOJK`) +- `bool KLNJBBPNMBJ` (inherited from `MKAMHOIHOJK`) +- `string KODBEJPEFOJ` (inherited from `MKAMHOIHOJK`) +- `bool LPJLEMJFBPE` (inherited from `MKAMHOIHOJK`) +- `bool MGBDHBHCDMH` (inherited from `MKAMHOIHOJK`) +- `bool MIKDLDEALPN` (inherited from `MKAMHOIHOJK`) +- `bool OFONEIOEIED` (inherited from `MKAMHOIHOJK`) +- `HJPGEGENLPH OILEJFNPDDB` (inherited from `MKAMHOIHOJK`) +- `bool PEEFHKMOMKK` (inherited from `MKAMHOIHOJK`) + +## rooms / rooms/{0}/subrooms/{1}/modify + +- `EJDCNGBEICB` `` RecRoom.Async.IPromise`1 JGMMJMHLHBO(System.Int64 HNHLJONGKHB, System.Int64 FODGKNJIGOP, System.String MMBOKOLAJFH, DPLPMKMFMPB PONCIIJOHIE, System.Int32 FFAGIDMFPIF) `` (EJDCNGBEICB.txt:6443) + +Expected client return: `PPENFJMFPNE` (object) +Resolved DTO: `PPENFJMFPNE` from `PPENFJMFPNE.cs` +Declaration: `public class PPENFJMFPNE : KLCOGEIGEBJ` +Inherits: `KLCOGEIGEBJ` +Inherited parser JSON keys: `RoomId`, `IsDorm`, `CloningAllowed`, `DisableMicAutoMute`, `DisableRoomComments`, `EncryptVoiceChat` +Public/decompiled members: +- `List ADCDBMGNIPB` +- `IReadOnlyList BIODFICLKJH` +- `IReadOnlyList DFKGKGIMEMP` +- `IReadOnlyList EJGIMHADHBC` +- `IReadOnlyList FEBCCOFBKKJ` +- `IReadOnlyList IAJDFKODGFL` +- `IReadOnlyList KFFBPMHGFKB` +- `IReadOnlyList LHGHLGDINFL` +- `IReadOnlyList LOJPLANGFMG` +- `LMLJHMJEIGM MMBJPGGOFHL` +- `bool MMFBPKOOLNG` +- `IReadOnlyList OHNIGJECGKL` +- `IReadOnlyList PKKADKGDHNI` +- `int accountId` +- `string tag` +- `PPENFJMFPNE IGAPDHGFLDC` +- `long DADOKMAOFJL` (inherited from `KLCOGEIGEBJ`) +- `bool GJPGKJJCPBK` (inherited from `KLCOGEIGEBJ`) +- `bool IBLOJJBEKFF` (inherited from `KLCOGEIGEBJ`) +- `bool JCLCOCEOAEP` (inherited from `KLCOGEIGEBJ`) +- `bool JGJCBGEBLLO` (inherited from `KLCOGEIGEBJ`) +- `bool PLFEEIJCHOH` (inherited from `KLCOGEIGEBJ`) +- `long BJFNPKFOALK` (inherited from `KLCOGEIGEBJ`) +- `DateTime ACBFDMLHFPB` (inherited from `MKAMHOIHOJK`) +- `string AHGCOGFEEEE` (inherited from `MKAMHOIHOJK`) +- `bool AKFBNELAMNA` (inherited from `MKAMHOIHOJK`) +- `int BADIGBCKECA` (inherited from `MKAMHOIHOJK`) +- `bool BNBLOBAEDEE` (inherited from `MKAMHOIHOJK`) +- `NMJEKMMBDDE CDINMMPNAID` (inherited from `MKAMHOIHOJK`) +- `bool CDNFGMHLDMJ` (inherited from `MKAMHOIHOJK`) +- `string FIKEBGGCDFN` (inherited from `MKAMHOIHOJK`) +- `GPDIAKNEBKH GIBHIMGJNNO` (inherited from `MKAMHOIHOJK`) +- `bool HPLBOMGACED` (inherited from `MKAMHOIHOJK`) +- `string IGOPGMHHLKI` (inherited from `MKAMHOIHOJK`) +- `DPLPMKMFMPB JFEAPMIPNEP` (inherited from `MKAMHOIHOJK`) +- `bool KHIJAFCHLIA` (inherited from `MKAMHOIHOJK`) +- `bool KLNJBBPNMBJ` (inherited from `MKAMHOIHOJK`) +- `string KODBEJPEFOJ` (inherited from `MKAMHOIHOJK`) +- `bool LPJLEMJFBPE` (inherited from `MKAMHOIHOJK`) +- `bool MGBDHBHCDMH` (inherited from `MKAMHOIHOJK`) +- `bool MIKDLDEALPN` (inherited from `MKAMHOIHOJK`) +- `bool OFONEIOEIED` (inherited from `MKAMHOIHOJK`) +- `HJPGEGENLPH OILEJFNPDDB` (inherited from `MKAMHOIHOJK`) +- `bool PEEFHKMOMKK` (inherited from `MKAMHOIHOJK`) + +## rooms / rooms/{0}/subrooms/{1}/move + +- `EJDCNGBEICB` `` RecRoom.Async.IPromise`1 OHHAFAIEFHP(System.Int64 HNHLJONGKHB, System.Int64 FODGKNJIGOP, System.Nullable`1 PAJMLPFMBEJ) `` (EJDCNGBEICB.txt:5709) + +Expected client return: `PPENFJMFPNE` (object) +Resolved DTO: `PPENFJMFPNE` from `PPENFJMFPNE.cs` +Declaration: `public class PPENFJMFPNE : KLCOGEIGEBJ` +Inherits: `KLCOGEIGEBJ` +Inherited parser JSON keys: `RoomId`, `IsDorm`, `CloningAllowed`, `DisableMicAutoMute`, `DisableRoomComments`, `EncryptVoiceChat` +Public/decompiled members: +- `List ADCDBMGNIPB` +- `IReadOnlyList BIODFICLKJH` +- `IReadOnlyList DFKGKGIMEMP` +- `IReadOnlyList EJGIMHADHBC` +- `IReadOnlyList FEBCCOFBKKJ` +- `IReadOnlyList IAJDFKODGFL` +- `IReadOnlyList KFFBPMHGFKB` +- `IReadOnlyList LHGHLGDINFL` +- `IReadOnlyList LOJPLANGFMG` +- `LMLJHMJEIGM MMBJPGGOFHL` +- `bool MMFBPKOOLNG` +- `IReadOnlyList OHNIGJECGKL` +- `IReadOnlyList PKKADKGDHNI` +- `int accountId` +- `string tag` +- `PPENFJMFPNE IGAPDHGFLDC` +- `long DADOKMAOFJL` (inherited from `KLCOGEIGEBJ`) +- `bool GJPGKJJCPBK` (inherited from `KLCOGEIGEBJ`) +- `bool IBLOJJBEKFF` (inherited from `KLCOGEIGEBJ`) +- `bool JCLCOCEOAEP` (inherited from `KLCOGEIGEBJ`) +- `bool JGJCBGEBLLO` (inherited from `KLCOGEIGEBJ`) +- `bool PLFEEIJCHOH` (inherited from `KLCOGEIGEBJ`) +- `long BJFNPKFOALK` (inherited from `KLCOGEIGEBJ`) +- `DateTime ACBFDMLHFPB` (inherited from `MKAMHOIHOJK`) +- `string AHGCOGFEEEE` (inherited from `MKAMHOIHOJK`) +- `bool AKFBNELAMNA` (inherited from `MKAMHOIHOJK`) +- `int BADIGBCKECA` (inherited from `MKAMHOIHOJK`) +- `bool BNBLOBAEDEE` (inherited from `MKAMHOIHOJK`) +- `NMJEKMMBDDE CDINMMPNAID` (inherited from `MKAMHOIHOJK`) +- `bool CDNFGMHLDMJ` (inherited from `MKAMHOIHOJK`) +- `string FIKEBGGCDFN` (inherited from `MKAMHOIHOJK`) +- `GPDIAKNEBKH GIBHIMGJNNO` (inherited from `MKAMHOIHOJK`) +- `bool HPLBOMGACED` (inherited from `MKAMHOIHOJK`) +- `string IGOPGMHHLKI` (inherited from `MKAMHOIHOJK`) +- `DPLPMKMFMPB JFEAPMIPNEP` (inherited from `MKAMHOIHOJK`) +- `bool KHIJAFCHLIA` (inherited from `MKAMHOIHOJK`) +- `bool KLNJBBPNMBJ` (inherited from `MKAMHOIHOJK`) +- `string KODBEJPEFOJ` (inherited from `MKAMHOIHOJK`) +- `bool LPJLEMJFBPE` (inherited from `MKAMHOIHOJK`) +- `bool MGBDHBHCDMH` (inherited from `MKAMHOIHOJK`) +- `bool MIKDLDEALPN` (inherited from `MKAMHOIHOJK`) +- `bool OFONEIOEIED` (inherited from `MKAMHOIHOJK`) +- `HJPGEGENLPH OILEJFNPDDB` (inherited from `MKAMHOIHOJK`) +- `bool PEEFHKMOMKK` (inherited from `MKAMHOIHOJK`) + +## rooms / rooms/{0}/subrooms/{1}/name + +- `EJDCNGBEICB` `` RecRoom.Async.IPromise`1 PAENPJCDLNJ(System.Int64 HNHLJONGKHB, System.Int64 FODGKNJIGOP, System.String MMBOKOLAJFH) `` (EJDCNGBEICB.txt:5099) + +Expected client return: `PPENFJMFPNE` (object) +Resolved DTO: `PPENFJMFPNE` from `PPENFJMFPNE.cs` +Declaration: `public class PPENFJMFPNE : KLCOGEIGEBJ` +Inherits: `KLCOGEIGEBJ` +Inherited parser JSON keys: `RoomId`, `IsDorm`, `CloningAllowed`, `DisableMicAutoMute`, `DisableRoomComments`, `EncryptVoiceChat` +Public/decompiled members: +- `List ADCDBMGNIPB` +- `IReadOnlyList BIODFICLKJH` +- `IReadOnlyList DFKGKGIMEMP` +- `IReadOnlyList EJGIMHADHBC` +- `IReadOnlyList FEBCCOFBKKJ` +- `IReadOnlyList IAJDFKODGFL` +- `IReadOnlyList KFFBPMHGFKB` +- `IReadOnlyList LHGHLGDINFL` +- `IReadOnlyList LOJPLANGFMG` +- `LMLJHMJEIGM MMBJPGGOFHL` +- `bool MMFBPKOOLNG` +- `IReadOnlyList OHNIGJECGKL` +- `IReadOnlyList PKKADKGDHNI` +- `int accountId` +- `string tag` +- `PPENFJMFPNE IGAPDHGFLDC` +- `long DADOKMAOFJL` (inherited from `KLCOGEIGEBJ`) +- `bool GJPGKJJCPBK` (inherited from `KLCOGEIGEBJ`) +- `bool IBLOJJBEKFF` (inherited from `KLCOGEIGEBJ`) +- `bool JCLCOCEOAEP` (inherited from `KLCOGEIGEBJ`) +- `bool JGJCBGEBLLO` (inherited from `KLCOGEIGEBJ`) +- `bool PLFEEIJCHOH` (inherited from `KLCOGEIGEBJ`) +- `long BJFNPKFOALK` (inherited from `KLCOGEIGEBJ`) +- `DateTime ACBFDMLHFPB` (inherited from `MKAMHOIHOJK`) +- `string AHGCOGFEEEE` (inherited from `MKAMHOIHOJK`) +- `bool AKFBNELAMNA` (inherited from `MKAMHOIHOJK`) +- `int BADIGBCKECA` (inherited from `MKAMHOIHOJK`) +- `bool BNBLOBAEDEE` (inherited from `MKAMHOIHOJK`) +- `NMJEKMMBDDE CDINMMPNAID` (inherited from `MKAMHOIHOJK`) +- `bool CDNFGMHLDMJ` (inherited from `MKAMHOIHOJK`) +- `string FIKEBGGCDFN` (inherited from `MKAMHOIHOJK`) +- `GPDIAKNEBKH GIBHIMGJNNO` (inherited from `MKAMHOIHOJK`) +- `bool HPLBOMGACED` (inherited from `MKAMHOIHOJK`) +- `string IGOPGMHHLKI` (inherited from `MKAMHOIHOJK`) +- `DPLPMKMFMPB JFEAPMIPNEP` (inherited from `MKAMHOIHOJK`) +- `bool KHIJAFCHLIA` (inherited from `MKAMHOIHOJK`) +- `bool KLNJBBPNMBJ` (inherited from `MKAMHOIHOJK`) +- `string KODBEJPEFOJ` (inherited from `MKAMHOIHOJK`) +- `bool LPJLEMJFBPE` (inherited from `MKAMHOIHOJK`) +- `bool MGBDHBHCDMH` (inherited from `MKAMHOIHOJK`) +- `bool MIKDLDEALPN` (inherited from `MKAMHOIHOJK`) +- `bool OFONEIOEIED` (inherited from `MKAMHOIHOJK`) +- `HJPGEGENLPH OILEJFNPDDB` (inherited from `MKAMHOIHOJK`) +- `bool PEEFHKMOMKK` (inherited from `MKAMHOIHOJK`) + +## rooms / rooms/{0}/subrooms/{1}/restoredata + +- `EJDCNGBEICB` `` RecRoom.Async.IPromise`1 BBGBOCGJFOP(System.Int64 HNHLJONGKHB, System.Int64 FODGKNJIGOP, System.String MEIIMAIGBJD) `` (EJDCNGBEICB.txt:6133) + +Expected client return: `PPENFJMFPNE` (object) +Resolved DTO: `PPENFJMFPNE` from `PPENFJMFPNE.cs` +Declaration: `public class PPENFJMFPNE : KLCOGEIGEBJ` +Inherits: `KLCOGEIGEBJ` +Inherited parser JSON keys: `RoomId`, `IsDorm`, `CloningAllowed`, `DisableMicAutoMute`, `DisableRoomComments`, `EncryptVoiceChat` +Public/decompiled members: +- `List ADCDBMGNIPB` +- `IReadOnlyList BIODFICLKJH` +- `IReadOnlyList DFKGKGIMEMP` +- `IReadOnlyList EJGIMHADHBC` +- `IReadOnlyList FEBCCOFBKKJ` +- `IReadOnlyList IAJDFKODGFL` +- `IReadOnlyList KFFBPMHGFKB` +- `IReadOnlyList LHGHLGDINFL` +- `IReadOnlyList LOJPLANGFMG` +- `LMLJHMJEIGM MMBJPGGOFHL` +- `bool MMFBPKOOLNG` +- `IReadOnlyList OHNIGJECGKL` +- `IReadOnlyList PKKADKGDHNI` +- `int accountId` +- `string tag` +- `PPENFJMFPNE IGAPDHGFLDC` +- `long DADOKMAOFJL` (inherited from `KLCOGEIGEBJ`) +- `bool GJPGKJJCPBK` (inherited from `KLCOGEIGEBJ`) +- `bool IBLOJJBEKFF` (inherited from `KLCOGEIGEBJ`) +- `bool JCLCOCEOAEP` (inherited from `KLCOGEIGEBJ`) +- `bool JGJCBGEBLLO` (inherited from `KLCOGEIGEBJ`) +- `bool PLFEEIJCHOH` (inherited from `KLCOGEIGEBJ`) +- `long BJFNPKFOALK` (inherited from `KLCOGEIGEBJ`) +- `DateTime ACBFDMLHFPB` (inherited from `MKAMHOIHOJK`) +- `string AHGCOGFEEEE` (inherited from `MKAMHOIHOJK`) +- `bool AKFBNELAMNA` (inherited from `MKAMHOIHOJK`) +- `int BADIGBCKECA` (inherited from `MKAMHOIHOJK`) +- `bool BNBLOBAEDEE` (inherited from `MKAMHOIHOJK`) +- `NMJEKMMBDDE CDINMMPNAID` (inherited from `MKAMHOIHOJK`) +- `bool CDNFGMHLDMJ` (inherited from `MKAMHOIHOJK`) +- `string FIKEBGGCDFN` (inherited from `MKAMHOIHOJK`) +- `GPDIAKNEBKH GIBHIMGJNNO` (inherited from `MKAMHOIHOJK`) +- `bool HPLBOMGACED` (inherited from `MKAMHOIHOJK`) +- `string IGOPGMHHLKI` (inherited from `MKAMHOIHOJK`) +- `DPLPMKMFMPB JFEAPMIPNEP` (inherited from `MKAMHOIHOJK`) +- `bool KHIJAFCHLIA` (inherited from `MKAMHOIHOJK`) +- `bool KLNJBBPNMBJ` (inherited from `MKAMHOIHOJK`) +- `string KODBEJPEFOJ` (inherited from `MKAMHOIHOJK`) +- `bool LPJLEMJFBPE` (inherited from `MKAMHOIHOJK`) +- `bool MGBDHBHCDMH` (inherited from `MKAMHOIHOJK`) +- `bool MIKDLDEALPN` (inherited from `MKAMHOIHOJK`) +- `bool OFONEIOEIED` (inherited from `MKAMHOIHOJK`) +- `HJPGEGENLPH OILEJFNPDDB` (inherited from `MKAMHOIHOJK`) +- `bool PEEFHKMOMKK` (inherited from `MKAMHOIHOJK`) + +## rooms / rooms/{0}/tags + +- `EJDCNGBEICB` `` RecRoom.Async.IPromise`1 ENGFFJGIEGH(System.Int64 HNHLJONGKHB, System.Collections.Generic.IReadOnlyList`1 LBAOCHFCLPO, System.Collections.Generic.IReadOnlyList`1 CAFPJPHILMN) `` (EJDCNGBEICB.txt:3384) + +Expected client return: `PPENFJMFPNE` (object) +Resolved DTO: `PPENFJMFPNE` from `PPENFJMFPNE.cs` +Declaration: `public class PPENFJMFPNE : KLCOGEIGEBJ` +Inherits: `KLCOGEIGEBJ` +Inherited parser JSON keys: `RoomId`, `IsDorm`, `CloningAllowed`, `DisableMicAutoMute`, `DisableRoomComments`, `EncryptVoiceChat` +Public/decompiled members: +- `List ADCDBMGNIPB` +- `IReadOnlyList BIODFICLKJH` +- `IReadOnlyList DFKGKGIMEMP` +- `IReadOnlyList EJGIMHADHBC` +- `IReadOnlyList FEBCCOFBKKJ` +- `IReadOnlyList IAJDFKODGFL` +- `IReadOnlyList KFFBPMHGFKB` +- `IReadOnlyList LHGHLGDINFL` +- `IReadOnlyList LOJPLANGFMG` +- `LMLJHMJEIGM MMBJPGGOFHL` +- `bool MMFBPKOOLNG` +- `IReadOnlyList OHNIGJECGKL` +- `IReadOnlyList PKKADKGDHNI` +- `int accountId` +- `string tag` +- `PPENFJMFPNE IGAPDHGFLDC` +- `long DADOKMAOFJL` (inherited from `KLCOGEIGEBJ`) +- `bool GJPGKJJCPBK` (inherited from `KLCOGEIGEBJ`) +- `bool IBLOJJBEKFF` (inherited from `KLCOGEIGEBJ`) +- `bool JCLCOCEOAEP` (inherited from `KLCOGEIGEBJ`) +- `bool JGJCBGEBLLO` (inherited from `KLCOGEIGEBJ`) +- `bool PLFEEIJCHOH` (inherited from `KLCOGEIGEBJ`) +- `long BJFNPKFOALK` (inherited from `KLCOGEIGEBJ`) +- `DateTime ACBFDMLHFPB` (inherited from `MKAMHOIHOJK`) +- `string AHGCOGFEEEE` (inherited from `MKAMHOIHOJK`) +- `bool AKFBNELAMNA` (inherited from `MKAMHOIHOJK`) +- `int BADIGBCKECA` (inherited from `MKAMHOIHOJK`) +- `bool BNBLOBAEDEE` (inherited from `MKAMHOIHOJK`) +- `NMJEKMMBDDE CDINMMPNAID` (inherited from `MKAMHOIHOJK`) +- `bool CDNFGMHLDMJ` (inherited from `MKAMHOIHOJK`) +- `string FIKEBGGCDFN` (inherited from `MKAMHOIHOJK`) +- `GPDIAKNEBKH GIBHIMGJNNO` (inherited from `MKAMHOIHOJK`) +- `bool HPLBOMGACED` (inherited from `MKAMHOIHOJK`) +- `string IGOPGMHHLKI` (inherited from `MKAMHOIHOJK`) +- `DPLPMKMFMPB JFEAPMIPNEP` (inherited from `MKAMHOIHOJK`) +- `bool KHIJAFCHLIA` (inherited from `MKAMHOIHOJK`) +- `bool KLNJBBPNMBJ` (inherited from `MKAMHOIHOJK`) +- `string KODBEJPEFOJ` (inherited from `MKAMHOIHOJK`) +- `bool LPJLEMJFBPE` (inherited from `MKAMHOIHOJK`) +- `bool MGBDHBHCDMH` (inherited from `MKAMHOIHOJK`) +- `bool MIKDLDEALPN` (inherited from `MKAMHOIHOJK`) +- `bool OFONEIOEIED` (inherited from `MKAMHOIHOJK`) +- `HJPGEGENLPH OILEJFNPDDB` (inherited from `MKAMHOIHOJK`) +- `bool PEEFHKMOMKK` (inherited from `MKAMHOIHOJK`) + +## rooms / rooms/{0}/voice_chat_encryption + +- `EJDCNGBEICB` `` RecRoom.Async.IPromise`1 PKDCMMFILPB(System.Int64 HNHLJONGKHB, System.Boolean NFJINCPDPDG) `` (EJDCNGBEICB.txt:4270) + +Expected client return: `PPENFJMFPNE` (object) +Resolved DTO: `PPENFJMFPNE` from `PPENFJMFPNE.cs` +Declaration: `public class PPENFJMFPNE : KLCOGEIGEBJ` +Inherits: `KLCOGEIGEBJ` +Inherited parser JSON keys: `RoomId`, `IsDorm`, `CloningAllowed`, `DisableMicAutoMute`, `DisableRoomComments`, `EncryptVoiceChat` +Public/decompiled members: +- `List ADCDBMGNIPB` +- `IReadOnlyList BIODFICLKJH` +- `IReadOnlyList DFKGKGIMEMP` +- `IReadOnlyList EJGIMHADHBC` +- `IReadOnlyList FEBCCOFBKKJ` +- `IReadOnlyList IAJDFKODGFL` +- `IReadOnlyList KFFBPMHGFKB` +- `IReadOnlyList LHGHLGDINFL` +- `IReadOnlyList LOJPLANGFMG` +- `LMLJHMJEIGM MMBJPGGOFHL` +- `bool MMFBPKOOLNG` +- `IReadOnlyList OHNIGJECGKL` +- `IReadOnlyList PKKADKGDHNI` +- `int accountId` +- `string tag` +- `PPENFJMFPNE IGAPDHGFLDC` +- `long DADOKMAOFJL` (inherited from `KLCOGEIGEBJ`) +- `bool GJPGKJJCPBK` (inherited from `KLCOGEIGEBJ`) +- `bool IBLOJJBEKFF` (inherited from `KLCOGEIGEBJ`) +- `bool JCLCOCEOAEP` (inherited from `KLCOGEIGEBJ`) +- `bool JGJCBGEBLLO` (inherited from `KLCOGEIGEBJ`) +- `bool PLFEEIJCHOH` (inherited from `KLCOGEIGEBJ`) +- `long BJFNPKFOALK` (inherited from `KLCOGEIGEBJ`) +- `DateTime ACBFDMLHFPB` (inherited from `MKAMHOIHOJK`) +- `string AHGCOGFEEEE` (inherited from `MKAMHOIHOJK`) +- `bool AKFBNELAMNA` (inherited from `MKAMHOIHOJK`) +- `int BADIGBCKECA` (inherited from `MKAMHOIHOJK`) +- `bool BNBLOBAEDEE` (inherited from `MKAMHOIHOJK`) +- `NMJEKMMBDDE CDINMMPNAID` (inherited from `MKAMHOIHOJK`) +- `bool CDNFGMHLDMJ` (inherited from `MKAMHOIHOJK`) +- `string FIKEBGGCDFN` (inherited from `MKAMHOIHOJK`) +- `GPDIAKNEBKH GIBHIMGJNNO` (inherited from `MKAMHOIHOJK`) +- `bool HPLBOMGACED` (inherited from `MKAMHOIHOJK`) +- `string IGOPGMHHLKI` (inherited from `MKAMHOIHOJK`) +- `DPLPMKMFMPB JFEAPMIPNEP` (inherited from `MKAMHOIHOJK`) +- `bool KHIJAFCHLIA` (inherited from `MKAMHOIHOJK`) +- `bool KLNJBBPNMBJ` (inherited from `MKAMHOIHOJK`) +- `string KODBEJPEFOJ` (inherited from `MKAMHOIHOJK`) +- `bool LPJLEMJFBPE` (inherited from `MKAMHOIHOJK`) +- `bool MGBDHBHCDMH` (inherited from `MKAMHOIHOJK`) +- `bool MIKDLDEALPN` (inherited from `MKAMHOIHOJK`) +- `bool OFONEIOEIED` (inherited from `MKAMHOIHOJK`) +- `HJPGEGENLPH OILEJFNPDDB` (inherited from `MKAMHOIHOJK`) +- `bool PEEFHKMOMKK` (inherited from `MKAMHOIHOJK`) + +## rooms / rooms/{0}/warning + +- `EJDCNGBEICB` `` RecRoom.Async.IPromise`1 NPACFDBCHDP(System.Int64 HNHLJONGKHB, GPDIAKNEBKH LEFPAAGGFFA, System.String NLEBEKDHJIJ) `` (EJDCNGBEICB.txt:3510) + +Expected client return: `PPENFJMFPNE` (object) +Resolved DTO: `PPENFJMFPNE` from `PPENFJMFPNE.cs` +Declaration: `public class PPENFJMFPNE : KLCOGEIGEBJ` +Inherits: `KLCOGEIGEBJ` +Inherited parser JSON keys: `RoomId`, `IsDorm`, `CloningAllowed`, `DisableMicAutoMute`, `DisableRoomComments`, `EncryptVoiceChat` +Public/decompiled members: +- `List ADCDBMGNIPB` +- `IReadOnlyList BIODFICLKJH` +- `IReadOnlyList DFKGKGIMEMP` +- `IReadOnlyList EJGIMHADHBC` +- `IReadOnlyList FEBCCOFBKKJ` +- `IReadOnlyList IAJDFKODGFL` +- `IReadOnlyList KFFBPMHGFKB` +- `IReadOnlyList LHGHLGDINFL` +- `IReadOnlyList LOJPLANGFMG` +- `LMLJHMJEIGM MMBJPGGOFHL` +- `bool MMFBPKOOLNG` +- `IReadOnlyList OHNIGJECGKL` +- `IReadOnlyList PKKADKGDHNI` +- `int accountId` +- `string tag` +- `PPENFJMFPNE IGAPDHGFLDC` +- `long DADOKMAOFJL` (inherited from `KLCOGEIGEBJ`) +- `bool GJPGKJJCPBK` (inherited from `KLCOGEIGEBJ`) +- `bool IBLOJJBEKFF` (inherited from `KLCOGEIGEBJ`) +- `bool JCLCOCEOAEP` (inherited from `KLCOGEIGEBJ`) +- `bool JGJCBGEBLLO` (inherited from `KLCOGEIGEBJ`) +- `bool PLFEEIJCHOH` (inherited from `KLCOGEIGEBJ`) +- `long BJFNPKFOALK` (inherited from `KLCOGEIGEBJ`) +- `DateTime ACBFDMLHFPB` (inherited from `MKAMHOIHOJK`) +- `string AHGCOGFEEEE` (inherited from `MKAMHOIHOJK`) +- `bool AKFBNELAMNA` (inherited from `MKAMHOIHOJK`) +- `int BADIGBCKECA` (inherited from `MKAMHOIHOJK`) +- `bool BNBLOBAEDEE` (inherited from `MKAMHOIHOJK`) +- `NMJEKMMBDDE CDINMMPNAID` (inherited from `MKAMHOIHOJK`) +- `bool CDNFGMHLDMJ` (inherited from `MKAMHOIHOJK`) +- `string FIKEBGGCDFN` (inherited from `MKAMHOIHOJK`) +- `GPDIAKNEBKH GIBHIMGJNNO` (inherited from `MKAMHOIHOJK`) +- `bool HPLBOMGACED` (inherited from `MKAMHOIHOJK`) +- `string IGOPGMHHLKI` (inherited from `MKAMHOIHOJK`) +- `DPLPMKMFMPB JFEAPMIPNEP` (inherited from `MKAMHOIHOJK`) +- `bool KHIJAFCHLIA` (inherited from `MKAMHOIHOJK`) +- `bool KLNJBBPNMBJ` (inherited from `MKAMHOIHOJK`) +- `string KODBEJPEFOJ` (inherited from `MKAMHOIHOJK`) +- `bool LPJLEMJFBPE` (inherited from `MKAMHOIHOJK`) +- `bool MGBDHBHCDMH` (inherited from `MKAMHOIHOJK`) +- `bool MIKDLDEALPN` (inherited from `MKAMHOIHOJK`) +- `bool OFONEIOEIED` (inherited from `MKAMHOIHOJK`) +- `HJPGEGENLPH OILEJFNPDDB` (inherited from `MKAMHOIHOJK`) +- `bool PEEFHKMOMKK` (inherited from `MKAMHOIHOJK`) + +## rooms / rooms/base + +- `EJDCNGBEICB` `` RecRoom.Async.IPromise`1> LMEPPFGPHLA() `` (EJDCNGBEICB.txt:784) + +Expected client return: `` System.Collections.Generic.List`1 `` (array) +Resolved DTO: `KLCOGEIGEBJ` from `KLCOGEIGEBJ.cs` +Declaration: `public class KLCOGEIGEBJ : MKAMHOIHOJK` +Inherits: `MKAMHOIHOJK` +Client parser JSON keys: `RoomId`, `IsDorm`, `CloningAllowed`, `DisableMicAutoMute`, `DisableRoomComments`, `EncryptVoiceChat` +Inherited parser JSON keys: `Name`, `Description`, `ImageName`, `WarningMask`, `CustomWarning`, `CreatorAccountId`, `State`, `Accessibility`, `SupportsLevelVoting`, `IsRRO`, `SupportsScreens`, `SupportsWalkVR`, `SupportsTeleportVR`, `SupportsVRLow`, `SupportsQuest2`, `SupportsMobile`, `SupportsJuniors`, `CreatedAt`, `Stats` +Public/decompiled members: +- `long DADOKMAOFJL` +- `bool GJPGKJJCPBK` +- `bool IBLOJJBEKFF` +- `bool JCLCOCEOAEP` +- `bool JGJCBGEBLLO` +- `bool PLFEEIJCHOH` +- `long BJFNPKFOALK` +- `DateTime ACBFDMLHFPB` (inherited from `MKAMHOIHOJK`) +- `string AHGCOGFEEEE` (inherited from `MKAMHOIHOJK`) +- `bool AKFBNELAMNA` (inherited from `MKAMHOIHOJK`) +- `int BADIGBCKECA` (inherited from `MKAMHOIHOJK`) +- `bool BNBLOBAEDEE` (inherited from `MKAMHOIHOJK`) +- `NMJEKMMBDDE CDINMMPNAID` (inherited from `MKAMHOIHOJK`) +- `bool CDNFGMHLDMJ` (inherited from `MKAMHOIHOJK`) +- `string FIKEBGGCDFN` (inherited from `MKAMHOIHOJK`) +- `GPDIAKNEBKH GIBHIMGJNNO` (inherited from `MKAMHOIHOJK`) +- `bool HPLBOMGACED` (inherited from `MKAMHOIHOJK`) +- `string IGOPGMHHLKI` (inherited from `MKAMHOIHOJK`) +- `DPLPMKMFMPB JFEAPMIPNEP` (inherited from `MKAMHOIHOJK`) +- `bool KHIJAFCHLIA` (inherited from `MKAMHOIHOJK`) +- `bool KLNJBBPNMBJ` (inherited from `MKAMHOIHOJK`) +- `string KODBEJPEFOJ` (inherited from `MKAMHOIHOJK`) +- `bool LPJLEMJFBPE` (inherited from `MKAMHOIHOJK`) +- `bool MGBDHBHCDMH` (inherited from `MKAMHOIHOJK`) +- `bool MIKDLDEALPN` (inherited from `MKAMHOIHOJK`) +- `bool OFONEIOEIED` (inherited from `MKAMHOIHOJK`) +- `HJPGEGENLPH OILEJFNPDDB` (inherited from `MKAMHOIHOJK`) +- `bool PEEFHKMOMKK` (inherited from `MKAMHOIHOJK`) + +## rooms / rooms/bulk + +- `EJDCNGBEICB` `` RecRoom.Async.IPromise`1> JPNBCKICJIH(System.Collections.Generic.IReadOnlyList`1 HJLOFEGEMHE) `` (EJDCNGBEICB.txt:319) +- `EJDCNGBEICB` `` RecRoom.Async.IPromise`1> JPNBCKICJIH(System.Collections.Generic.IReadOnlyList`1 GDDBDIJIBCO) `` (EJDCNGBEICB.txt:491) + +Expected client return: `` System.Collections.Generic.List`1 `` (array) +Resolved DTO: `KLCOGEIGEBJ` from `KLCOGEIGEBJ.cs` +Declaration: `public class KLCOGEIGEBJ : MKAMHOIHOJK` +Inherits: `MKAMHOIHOJK` +Client parser JSON keys: `RoomId`, `IsDorm`, `CloningAllowed`, `DisableMicAutoMute`, `DisableRoomComments`, `EncryptVoiceChat` +Inherited parser JSON keys: `Name`, `Description`, `ImageName`, `WarningMask`, `CustomWarning`, `CreatorAccountId`, `State`, `Accessibility`, `SupportsLevelVoting`, `IsRRO`, `SupportsScreens`, `SupportsWalkVR`, `SupportsTeleportVR`, `SupportsVRLow`, `SupportsQuest2`, `SupportsMobile`, `SupportsJuniors`, `CreatedAt`, `Stats` +Public/decompiled members: +- `long DADOKMAOFJL` +- `bool GJPGKJJCPBK` +- `bool IBLOJJBEKFF` +- `bool JCLCOCEOAEP` +- `bool JGJCBGEBLLO` +- `bool PLFEEIJCHOH` +- `long BJFNPKFOALK` +- `DateTime ACBFDMLHFPB` (inherited from `MKAMHOIHOJK`) +- `string AHGCOGFEEEE` (inherited from `MKAMHOIHOJK`) +- `bool AKFBNELAMNA` (inherited from `MKAMHOIHOJK`) +- `int BADIGBCKECA` (inherited from `MKAMHOIHOJK`) +- `bool BNBLOBAEDEE` (inherited from `MKAMHOIHOJK`) +- `NMJEKMMBDDE CDINMMPNAID` (inherited from `MKAMHOIHOJK`) +- `bool CDNFGMHLDMJ` (inherited from `MKAMHOIHOJK`) +- `string FIKEBGGCDFN` (inherited from `MKAMHOIHOJK`) +- `GPDIAKNEBKH GIBHIMGJNNO` (inherited from `MKAMHOIHOJK`) +- `bool HPLBOMGACED` (inherited from `MKAMHOIHOJK`) +- `string IGOPGMHHLKI` (inherited from `MKAMHOIHOJK`) +- `DPLPMKMFMPB JFEAPMIPNEP` (inherited from `MKAMHOIHOJK`) +- `bool KHIJAFCHLIA` (inherited from `MKAMHOIHOJK`) +- `bool KLNJBBPNMBJ` (inherited from `MKAMHOIHOJK`) +- `string KODBEJPEFOJ` (inherited from `MKAMHOIHOJK`) +- `bool LPJLEMJFBPE` (inherited from `MKAMHOIHOJK`) +- `bool MGBDHBHCDMH` (inherited from `MKAMHOIHOJK`) +- `bool MIKDLDEALPN` (inherited from `MKAMHOIHOJK`) +- `bool OFONEIOEIED` (inherited from `MKAMHOIHOJK`) +- `HJPGEGENLPH OILEJFNPDDB` (inherited from `MKAMHOIHOJK`) +- `bool PEEFHKMOMKK` (inherited from `MKAMHOIHOJK`) + +## rooms / rooms/cheeredby/me + +- `EJDCNGBEICB` `` RecRoom.Async.IPromise`1> GBMMJLHIDOK() `` (EJDCNGBEICB.txt:940) + +Expected client return: `` System.Collections.Generic.List`1 `` (array) +Resolved DTO: `KLCOGEIGEBJ` from `KLCOGEIGEBJ.cs` +Declaration: `public class KLCOGEIGEBJ : MKAMHOIHOJK` +Inherits: `MKAMHOIHOJK` +Client parser JSON keys: `RoomId`, `IsDorm`, `CloningAllowed`, `DisableMicAutoMute`, `DisableRoomComments`, `EncryptVoiceChat` +Inherited parser JSON keys: `Name`, `Description`, `ImageName`, `WarningMask`, `CustomWarning`, `CreatorAccountId`, `State`, `Accessibility`, `SupportsLevelVoting`, `IsRRO`, `SupportsScreens`, `SupportsWalkVR`, `SupportsTeleportVR`, `SupportsVRLow`, `SupportsQuest2`, `SupportsMobile`, `SupportsJuniors`, `CreatedAt`, `Stats` +Public/decompiled members: +- `long DADOKMAOFJL` +- `bool GJPGKJJCPBK` +- `bool IBLOJJBEKFF` +- `bool JCLCOCEOAEP` +- `bool JGJCBGEBLLO` +- `bool PLFEEIJCHOH` +- `long BJFNPKFOALK` +- `DateTime ACBFDMLHFPB` (inherited from `MKAMHOIHOJK`) +- `string AHGCOGFEEEE` (inherited from `MKAMHOIHOJK`) +- `bool AKFBNELAMNA` (inherited from `MKAMHOIHOJK`) +- `int BADIGBCKECA` (inherited from `MKAMHOIHOJK`) +- `bool BNBLOBAEDEE` (inherited from `MKAMHOIHOJK`) +- `NMJEKMMBDDE CDINMMPNAID` (inherited from `MKAMHOIHOJK`) +- `bool CDNFGMHLDMJ` (inherited from `MKAMHOIHOJK`) +- `string FIKEBGGCDFN` (inherited from `MKAMHOIHOJK`) +- `GPDIAKNEBKH GIBHIMGJNNO` (inherited from `MKAMHOIHOJK`) +- `bool HPLBOMGACED` (inherited from `MKAMHOIHOJK`) +- `string IGOPGMHHLKI` (inherited from `MKAMHOIHOJK`) +- `DPLPMKMFMPB JFEAPMIPNEP` (inherited from `MKAMHOIHOJK`) +- `bool KHIJAFCHLIA` (inherited from `MKAMHOIHOJK`) +- `bool KLNJBBPNMBJ` (inherited from `MKAMHOIHOJK`) +- `string KODBEJPEFOJ` (inherited from `MKAMHOIHOJK`) +- `bool LPJLEMJFBPE` (inherited from `MKAMHOIHOJK`) +- `bool MGBDHBHCDMH` (inherited from `MKAMHOIHOJK`) +- `bool MIKDLDEALPN` (inherited from `MKAMHOIHOJK`) +- `bool OFONEIOEIED` (inherited from `MKAMHOIHOJK`) +- `HJPGEGENLPH OILEJFNPDDB` (inherited from `MKAMHOIHOJK`) +- `bool PEEFHKMOMKK` (inherited from `MKAMHOIHOJK`) + +## rooms / rooms/createdby/{0} + +- `EJDCNGBEICB` `` RecRoom.Async.IPromise`1> OGIHJJDNPKJ(System.Int32 GKLPIFBPGOD) `` (EJDCNGBEICB.txt:1073) + +Expected client return: `` System.Collections.Generic.List`1 `` (array) +Resolved DTO: `KLCOGEIGEBJ` from `KLCOGEIGEBJ.cs` +Declaration: `public class KLCOGEIGEBJ : MKAMHOIHOJK` +Inherits: `MKAMHOIHOJK` +Client parser JSON keys: `RoomId`, `IsDorm`, `CloningAllowed`, `DisableMicAutoMute`, `DisableRoomComments`, `EncryptVoiceChat` +Inherited parser JSON keys: `Name`, `Description`, `ImageName`, `WarningMask`, `CustomWarning`, `CreatorAccountId`, `State`, `Accessibility`, `SupportsLevelVoting`, `IsRRO`, `SupportsScreens`, `SupportsWalkVR`, `SupportsTeleportVR`, `SupportsVRLow`, `SupportsQuest2`, `SupportsMobile`, `SupportsJuniors`, `CreatedAt`, `Stats` +Public/decompiled members: +- `long DADOKMAOFJL` +- `bool GJPGKJJCPBK` +- `bool IBLOJJBEKFF` +- `bool JCLCOCEOAEP` +- `bool JGJCBGEBLLO` +- `bool PLFEEIJCHOH` +- `long BJFNPKFOALK` +- `DateTime ACBFDMLHFPB` (inherited from `MKAMHOIHOJK`) +- `string AHGCOGFEEEE` (inherited from `MKAMHOIHOJK`) +- `bool AKFBNELAMNA` (inherited from `MKAMHOIHOJK`) +- `int BADIGBCKECA` (inherited from `MKAMHOIHOJK`) +- `bool BNBLOBAEDEE` (inherited from `MKAMHOIHOJK`) +- `NMJEKMMBDDE CDINMMPNAID` (inherited from `MKAMHOIHOJK`) +- `bool CDNFGMHLDMJ` (inherited from `MKAMHOIHOJK`) +- `string FIKEBGGCDFN` (inherited from `MKAMHOIHOJK`) +- `GPDIAKNEBKH GIBHIMGJNNO` (inherited from `MKAMHOIHOJK`) +- `bool HPLBOMGACED` (inherited from `MKAMHOIHOJK`) +- `string IGOPGMHHLKI` (inherited from `MKAMHOIHOJK`) +- `DPLPMKMFMPB JFEAPMIPNEP` (inherited from `MKAMHOIHOJK`) +- `bool KHIJAFCHLIA` (inherited from `MKAMHOIHOJK`) +- `bool KLNJBBPNMBJ` (inherited from `MKAMHOIHOJK`) +- `string KODBEJPEFOJ` (inherited from `MKAMHOIHOJK`) +- `bool LPJLEMJFBPE` (inherited from `MKAMHOIHOJK`) +- `bool MGBDHBHCDMH` (inherited from `MKAMHOIHOJK`) +- `bool MIKDLDEALPN` (inherited from `MKAMHOIHOJK`) +- `bool OFONEIOEIED` (inherited from `MKAMHOIHOJK`) +- `HJPGEGENLPH OILEJFNPDDB` (inherited from `MKAMHOIHOJK`) +- `bool PEEFHKMOMKK` (inherited from `MKAMHOIHOJK`) + +## rooms / rooms/createdby/me + +- `EJDCNGBEICB` `` RecRoom.Async.IPromise`1> MIMNJGKEGMF() `` (EJDCNGBEICB.txt:823) + +Expected client return: `` System.Collections.Generic.List`1 `` (array) +Resolved DTO: `KLCOGEIGEBJ` from `KLCOGEIGEBJ.cs` +Declaration: `public class KLCOGEIGEBJ : MKAMHOIHOJK` +Inherits: `MKAMHOIHOJK` +Client parser JSON keys: `RoomId`, `IsDorm`, `CloningAllowed`, `DisableMicAutoMute`, `DisableRoomComments`, `EncryptVoiceChat` +Inherited parser JSON keys: `Name`, `Description`, `ImageName`, `WarningMask`, `CustomWarning`, `CreatorAccountId`, `State`, `Accessibility`, `SupportsLevelVoting`, `IsRRO`, `SupportsScreens`, `SupportsWalkVR`, `SupportsTeleportVR`, `SupportsVRLow`, `SupportsQuest2`, `SupportsMobile`, `SupportsJuniors`, `CreatedAt`, `Stats` +Public/decompiled members: +- `long DADOKMAOFJL` +- `bool GJPGKJJCPBK` +- `bool IBLOJJBEKFF` +- `bool JCLCOCEOAEP` +- `bool JGJCBGEBLLO` +- `bool PLFEEIJCHOH` +- `long BJFNPKFOALK` +- `DateTime ACBFDMLHFPB` (inherited from `MKAMHOIHOJK`) +- `string AHGCOGFEEEE` (inherited from `MKAMHOIHOJK`) +- `bool AKFBNELAMNA` (inherited from `MKAMHOIHOJK`) +- `int BADIGBCKECA` (inherited from `MKAMHOIHOJK`) +- `bool BNBLOBAEDEE` (inherited from `MKAMHOIHOJK`) +- `NMJEKMMBDDE CDINMMPNAID` (inherited from `MKAMHOIHOJK`) +- `bool CDNFGMHLDMJ` (inherited from `MKAMHOIHOJK`) +- `string FIKEBGGCDFN` (inherited from `MKAMHOIHOJK`) +- `GPDIAKNEBKH GIBHIMGJNNO` (inherited from `MKAMHOIHOJK`) +- `bool HPLBOMGACED` (inherited from `MKAMHOIHOJK`) +- `string IGOPGMHHLKI` (inherited from `MKAMHOIHOJK`) +- `DPLPMKMFMPB JFEAPMIPNEP` (inherited from `MKAMHOIHOJK`) +- `bool KHIJAFCHLIA` (inherited from `MKAMHOIHOJK`) +- `bool KLNJBBPNMBJ` (inherited from `MKAMHOIHOJK`) +- `string KODBEJPEFOJ` (inherited from `MKAMHOIHOJK`) +- `bool LPJLEMJFBPE` (inherited from `MKAMHOIHOJK`) +- `bool MGBDHBHCDMH` (inherited from `MKAMHOIHOJK`) +- `bool MIKDLDEALPN` (inherited from `MKAMHOIHOJK`) +- `bool OFONEIOEIED` (inherited from `MKAMHOIHOJK`) +- `HJPGEGENLPH OILEJFNPDDB` (inherited from `MKAMHOIHOJK`) +- `bool PEEFHKMOMKK` (inherited from `MKAMHOIHOJK`) + +## rooms / rooms/favoritedby/me + +- `EJDCNGBEICB` `` RecRoom.Async.IPromise`1> AFOLHKGPMOC() `` (EJDCNGBEICB.txt:979) + +Expected client return: `` System.Collections.Generic.List`1 `` (array) +Resolved DTO: `KLCOGEIGEBJ` from `KLCOGEIGEBJ.cs` +Declaration: `public class KLCOGEIGEBJ : MKAMHOIHOJK` +Inherits: `MKAMHOIHOJK` +Client parser JSON keys: `RoomId`, `IsDorm`, `CloningAllowed`, `DisableMicAutoMute`, `DisableRoomComments`, `EncryptVoiceChat` +Inherited parser JSON keys: `Name`, `Description`, `ImageName`, `WarningMask`, `CustomWarning`, `CreatorAccountId`, `State`, `Accessibility`, `SupportsLevelVoting`, `IsRRO`, `SupportsScreens`, `SupportsWalkVR`, `SupportsTeleportVR`, `SupportsVRLow`, `SupportsQuest2`, `SupportsMobile`, `SupportsJuniors`, `CreatedAt`, `Stats` +Public/decompiled members: +- `long DADOKMAOFJL` +- `bool GJPGKJJCPBK` +- `bool IBLOJJBEKFF` +- `bool JCLCOCEOAEP` +- `bool JGJCBGEBLLO` +- `bool PLFEEIJCHOH` +- `long BJFNPKFOALK` +- `DateTime ACBFDMLHFPB` (inherited from `MKAMHOIHOJK`) +- `string AHGCOGFEEEE` (inherited from `MKAMHOIHOJK`) +- `bool AKFBNELAMNA` (inherited from `MKAMHOIHOJK`) +- `int BADIGBCKECA` (inherited from `MKAMHOIHOJK`) +- `bool BNBLOBAEDEE` (inherited from `MKAMHOIHOJK`) +- `NMJEKMMBDDE CDINMMPNAID` (inherited from `MKAMHOIHOJK`) +- `bool CDNFGMHLDMJ` (inherited from `MKAMHOIHOJK`) +- `string FIKEBGGCDFN` (inherited from `MKAMHOIHOJK`) +- `GPDIAKNEBKH GIBHIMGJNNO` (inherited from `MKAMHOIHOJK`) +- `bool HPLBOMGACED` (inherited from `MKAMHOIHOJK`) +- `string IGOPGMHHLKI` (inherited from `MKAMHOIHOJK`) +- `DPLPMKMFMPB JFEAPMIPNEP` (inherited from `MKAMHOIHOJK`) +- `bool KHIJAFCHLIA` (inherited from `MKAMHOIHOJK`) +- `bool KLNJBBPNMBJ` (inherited from `MKAMHOIHOJK`) +- `string KODBEJPEFOJ` (inherited from `MKAMHOIHOJK`) +- `bool LPJLEMJFBPE` (inherited from `MKAMHOIHOJK`) +- `bool MGBDHBHCDMH` (inherited from `MKAMHOIHOJK`) +- `bool MIKDLDEALPN` (inherited from `MKAMHOIHOJK`) +- `bool OFONEIOEIED` (inherited from `MKAMHOIHOJK`) +- `HJPGEGENLPH OILEJFNPDDB` (inherited from `MKAMHOIHOJK`) +- `bool PEEFHKMOMKK` (inherited from `MKAMHOIHOJK`) + +## rooms / rooms/hot + +- `EJDCNGBEICB` `` RecRoom.Async.IPromise`1 DLPLPKCNLNA(System.Collections.Generic.IReadOnlyList`1 CAFPJPHILMN) `` (EJDCNGBEICB.txt:1601) + +Expected client return: `IDLBPALJJDJ` (object) +Resolved DTO: `IDLBPALJJDJ` from `IDLBPALJJDJ.cs` +Declaration: `public class IDLBPALJJDJ : NCONANPODKN, IFAIJAGLDFK` +Inherits: `NCONANPODKN` +Client parser JSON keys: `TotalResults` +Public/decompiled members: +- `long KPCBEDOLLFK` (inherited from `NCONANPODKN`) +- `IReadOnlyList PPLMJPLEHLP` (inherited from `NCONANPODKN`) + +## rooms / rooms/moderatedby/me + +- `EJDCNGBEICB` `` RecRoom.Async.IPromise`1> IMKAAIEFGIG() `` (EJDCNGBEICB.txt:901) + +Expected client return: `` System.Collections.Generic.List`1 `` (array) +Resolved DTO: `KLCOGEIGEBJ` from `KLCOGEIGEBJ.cs` +Declaration: `public class KLCOGEIGEBJ : MKAMHOIHOJK` +Inherits: `MKAMHOIHOJK` +Client parser JSON keys: `RoomId`, `IsDorm`, `CloningAllowed`, `DisableMicAutoMute`, `DisableRoomComments`, `EncryptVoiceChat` +Inherited parser JSON keys: `Name`, `Description`, `ImageName`, `WarningMask`, `CustomWarning`, `CreatorAccountId`, `State`, `Accessibility`, `SupportsLevelVoting`, `IsRRO`, `SupportsScreens`, `SupportsWalkVR`, `SupportsTeleportVR`, `SupportsVRLow`, `SupportsQuest2`, `SupportsMobile`, `SupportsJuniors`, `CreatedAt`, `Stats` +Public/decompiled members: +- `long DADOKMAOFJL` +- `bool GJPGKJJCPBK` +- `bool IBLOJJBEKFF` +- `bool JCLCOCEOAEP` +- `bool JGJCBGEBLLO` +- `bool PLFEEIJCHOH` +- `long BJFNPKFOALK` +- `DateTime ACBFDMLHFPB` (inherited from `MKAMHOIHOJK`) +- `string AHGCOGFEEEE` (inherited from `MKAMHOIHOJK`) +- `bool AKFBNELAMNA` (inherited from `MKAMHOIHOJK`) +- `int BADIGBCKECA` (inherited from `MKAMHOIHOJK`) +- `bool BNBLOBAEDEE` (inherited from `MKAMHOIHOJK`) +- `NMJEKMMBDDE CDINMMPNAID` (inherited from `MKAMHOIHOJK`) +- `bool CDNFGMHLDMJ` (inherited from `MKAMHOIHOJK`) +- `string FIKEBGGCDFN` (inherited from `MKAMHOIHOJK`) +- `GPDIAKNEBKH GIBHIMGJNNO` (inherited from `MKAMHOIHOJK`) +- `bool HPLBOMGACED` (inherited from `MKAMHOIHOJK`) +- `string IGOPGMHHLKI` (inherited from `MKAMHOIHOJK`) +- `DPLPMKMFMPB JFEAPMIPNEP` (inherited from `MKAMHOIHOJK`) +- `bool KHIJAFCHLIA` (inherited from `MKAMHOIHOJK`) +- `bool KLNJBBPNMBJ` (inherited from `MKAMHOIHOJK`) +- `string KODBEJPEFOJ` (inherited from `MKAMHOIHOJK`) +- `bool LPJLEMJFBPE` (inherited from `MKAMHOIHOJK`) +- `bool MGBDHBHCDMH` (inherited from `MKAMHOIHOJK`) +- `bool MIKDLDEALPN` (inherited from `MKAMHOIHOJK`) +- `bool OFONEIOEIED` (inherited from `MKAMHOIHOJK`) +- `HJPGEGENLPH OILEJFNPDDB` (inherited from `MKAMHOIHOJK`) +- `bool PEEFHKMOMKK` (inherited from `MKAMHOIHOJK`) + +## rooms / rooms/ownedby/me + +- `EJDCNGBEICB` `` RecRoom.Async.IPromise`1> FNMNFLFIBJG() `` (EJDCNGBEICB.txt:862) + +Expected client return: `` System.Collections.Generic.List`1 `` (array) +Resolved DTO: `KLCOGEIGEBJ` from `KLCOGEIGEBJ.cs` +Declaration: `public class KLCOGEIGEBJ : MKAMHOIHOJK` +Inherits: `MKAMHOIHOJK` +Client parser JSON keys: `RoomId`, `IsDorm`, `CloningAllowed`, `DisableMicAutoMute`, `DisableRoomComments`, `EncryptVoiceChat` +Inherited parser JSON keys: `Name`, `Description`, `ImageName`, `WarningMask`, `CustomWarning`, `CreatorAccountId`, `State`, `Accessibility`, `SupportsLevelVoting`, `IsRRO`, `SupportsScreens`, `SupportsWalkVR`, `SupportsTeleportVR`, `SupportsVRLow`, `SupportsQuest2`, `SupportsMobile`, `SupportsJuniors`, `CreatedAt`, `Stats` +Public/decompiled members: +- `long DADOKMAOFJL` +- `bool GJPGKJJCPBK` +- `bool IBLOJJBEKFF` +- `bool JCLCOCEOAEP` +- `bool JGJCBGEBLLO` +- `bool PLFEEIJCHOH` +- `long BJFNPKFOALK` +- `DateTime ACBFDMLHFPB` (inherited from `MKAMHOIHOJK`) +- `string AHGCOGFEEEE` (inherited from `MKAMHOIHOJK`) +- `bool AKFBNELAMNA` (inherited from `MKAMHOIHOJK`) +- `int BADIGBCKECA` (inherited from `MKAMHOIHOJK`) +- `bool BNBLOBAEDEE` (inherited from `MKAMHOIHOJK`) +- `NMJEKMMBDDE CDINMMPNAID` (inherited from `MKAMHOIHOJK`) +- `bool CDNFGMHLDMJ` (inherited from `MKAMHOIHOJK`) +- `string FIKEBGGCDFN` (inherited from `MKAMHOIHOJK`) +- `GPDIAKNEBKH GIBHIMGJNNO` (inherited from `MKAMHOIHOJK`) +- `bool HPLBOMGACED` (inherited from `MKAMHOIHOJK`) +- `string IGOPGMHHLKI` (inherited from `MKAMHOIHOJK`) +- `DPLPMKMFMPB JFEAPMIPNEP` (inherited from `MKAMHOIHOJK`) +- `bool KHIJAFCHLIA` (inherited from `MKAMHOIHOJK`) +- `bool KLNJBBPNMBJ` (inherited from `MKAMHOIHOJK`) +- `string KODBEJPEFOJ` (inherited from `MKAMHOIHOJK`) +- `bool LPJLEMJFBPE` (inherited from `MKAMHOIHOJK`) +- `bool MGBDHBHCDMH` (inherited from `MKAMHOIHOJK`) +- `bool MIKDLDEALPN` (inherited from `MKAMHOIHOJK`) +- `bool OFONEIOEIED` (inherited from `MKAMHOIHOJK`) +- `HJPGEGENLPH OILEJFNPDDB` (inherited from `MKAMHOIHOJK`) +- `bool PEEFHKMOMKK` (inherited from `MKAMHOIHOJK`) + +## rooms / rooms/recommendations + +- `EJDCNGBEICB` `` RecRoom.Async.IPromise`1> JDKLBNBIEAH(AMBEPDPIBPA NLNJMJGOKFI, System.Int16 NJJLKKLALND) `` (EJDCNGBEICB.txt:1430) + +Expected client return: `` System.Collections.Generic.List`1 `` (array) +Resolved DTO: `ANNKHNFLMNP` from `ANNKHNFLMNP.cs` +Declaration: `public class ANNKHNFLMNP : IFAIJAGLDFK` +Client parser JSON keys: `SeedRoom` +Public/decompiled members: +- `KLCOGEIGEBJ CGKFPHCGBKL` +- `IReadOnlyList HEFJFKFNNCJ` + +## rooms / rooms/rro_ids + +- `EJDCNGBEICB` `` RecRoom.Async.IPromise`1> JDMBPCJDEMF() `` (EJDCNGBEICB.txt:1119) + +Expected client return: `` System.Collections.Generic.List`1 `` (array) +Resolved DTO: `Int64` not found in readable C# dump. + +## rooms / rooms/search + +- `EJDCNGBEICB` `` RecRoom.Async.IPromise`1 IAAKGMOGLFN(System.String CNBKKCJAHPP) `` (EJDCNGBEICB.txt:1516) + +Expected client return: `IDLBPALJJDJ` (object) +Resolved DTO: `IDLBPALJJDJ` from `IDLBPALJJDJ.cs` +Declaration: `public class IDLBPALJJDJ : NCONANPODKN, IFAIJAGLDFK` +Inherits: `NCONANPODKN` +Client parser JSON keys: `TotalResults` +Public/decompiled members: +- `long KPCBEDOLLFK` (inherited from `NCONANPODKN`) +- `IReadOnlyList PPLMJPLEHLP` (inherited from `NCONANPODKN`) + +## rooms / rooms/visitedby/me + +- `EJDCNGBEICB` `` RecRoom.Async.IPromise`1> INKFBOEEKDA() `` (EJDCNGBEICB.txt:1018) + +Expected client return: `` System.Collections.Generic.List`1 `` (array) +Resolved DTO: `KLCOGEIGEBJ` from `KLCOGEIGEBJ.cs` +Declaration: `public class KLCOGEIGEBJ : MKAMHOIHOJK` +Inherits: `MKAMHOIHOJK` +Client parser JSON keys: `RoomId`, `IsDorm`, `CloningAllowed`, `DisableMicAutoMute`, `DisableRoomComments`, `EncryptVoiceChat` +Inherited parser JSON keys: `Name`, `Description`, `ImageName`, `WarningMask`, `CustomWarning`, `CreatorAccountId`, `State`, `Accessibility`, `SupportsLevelVoting`, `IsRRO`, `SupportsScreens`, `SupportsWalkVR`, `SupportsTeleportVR`, `SupportsVRLow`, `SupportsQuest2`, `SupportsMobile`, `SupportsJuniors`, `CreatedAt`, `Stats` +Public/decompiled members: +- `long DADOKMAOFJL` +- `bool GJPGKJJCPBK` +- `bool IBLOJJBEKFF` +- `bool JCLCOCEOAEP` +- `bool JGJCBGEBLLO` +- `bool PLFEEIJCHOH` +- `long BJFNPKFOALK` +- `DateTime ACBFDMLHFPB` (inherited from `MKAMHOIHOJK`) +- `string AHGCOGFEEEE` (inherited from `MKAMHOIHOJK`) +- `bool AKFBNELAMNA` (inherited from `MKAMHOIHOJK`) +- `int BADIGBCKECA` (inherited from `MKAMHOIHOJK`) +- `bool BNBLOBAEDEE` (inherited from `MKAMHOIHOJK`) +- `NMJEKMMBDDE CDINMMPNAID` (inherited from `MKAMHOIHOJK`) +- `bool CDNFGMHLDMJ` (inherited from `MKAMHOIHOJK`) +- `string FIKEBGGCDFN` (inherited from `MKAMHOIHOJK`) +- `GPDIAKNEBKH GIBHIMGJNNO` (inherited from `MKAMHOIHOJK`) +- `bool HPLBOMGACED` (inherited from `MKAMHOIHOJK`) +- `string IGOPGMHHLKI` (inherited from `MKAMHOIHOJK`) +- `DPLPMKMFMPB JFEAPMIPNEP` (inherited from `MKAMHOIHOJK`) +- `bool KHIJAFCHLIA` (inherited from `MKAMHOIHOJK`) +- `bool KLNJBBPNMBJ` (inherited from `MKAMHOIHOJK`) +- `string KODBEJPEFOJ` (inherited from `MKAMHOIHOJK`) +- `bool LPJLEMJFBPE` (inherited from `MKAMHOIHOJK`) +- `bool MGBDHBHCDMH` (inherited from `MKAMHOIHOJK`) +- `bool MIKDLDEALPN` (inherited from `MKAMHOIHOJK`) +- `bool OFONEIOEIED` (inherited from `MKAMHOIHOJK`) +- `HJPGEGENLPH OILEJFNPDDB` (inherited from `MKAMHOIHOJK`) +- `bool PEEFHKMOMKK` (inherited from `MKAMHOIHOJK`) + +## rooms / search_rooms/ + +- `OJMCBOKJFOF` `` RecRoom.Async.IPromise`1> IAAKGMOGLFN(System.String CNBKKCJAHPP) `` (OJMCBOKJFOF.txt:3064) +- `OJMCBOKJFOF+PEBCIEAPGON` `System.String IAAKGMOGLFN(System.String CNBKKCJAHPP)` (OJMCBOKJFOF_NestedType_PEBCIEAPGON.txt:76) + +Expected client return: `` System.Collections.Generic.IReadOnlyList`1 `` (array) +Resolved DTO: `KLCOGEIGEBJ` from `KLCOGEIGEBJ.cs` +Declaration: `public class KLCOGEIGEBJ : MKAMHOIHOJK` +Inherits: `MKAMHOIHOJK` +Client parser JSON keys: `RoomId`, `IsDorm`, `CloningAllowed`, `DisableMicAutoMute`, `DisableRoomComments`, `EncryptVoiceChat` +Inherited parser JSON keys: `Name`, `Description`, `ImageName`, `WarningMask`, `CustomWarning`, `CreatorAccountId`, `State`, `Accessibility`, `SupportsLevelVoting`, `IsRRO`, `SupportsScreens`, `SupportsWalkVR`, `SupportsTeleportVR`, `SupportsVRLow`, `SupportsQuest2`, `SupportsMobile`, `SupportsJuniors`, `CreatedAt`, `Stats` +Public/decompiled members: +- `long DADOKMAOFJL` +- `bool GJPGKJJCPBK` +- `bool IBLOJJBEKFF` +- `bool JCLCOCEOAEP` +- `bool JGJCBGEBLLO` +- `bool PLFEEIJCHOH` +- `long BJFNPKFOALK` +- `DateTime ACBFDMLHFPB` (inherited from `MKAMHOIHOJK`) +- `string AHGCOGFEEEE` (inherited from `MKAMHOIHOJK`) +- `bool AKFBNELAMNA` (inherited from `MKAMHOIHOJK`) +- `int BADIGBCKECA` (inherited from `MKAMHOIHOJK`) +- `bool BNBLOBAEDEE` (inherited from `MKAMHOIHOJK`) +- `NMJEKMMBDDE CDINMMPNAID` (inherited from `MKAMHOIHOJK`) +- `bool CDNFGMHLDMJ` (inherited from `MKAMHOIHOJK`) +- `string FIKEBGGCDFN` (inherited from `MKAMHOIHOJK`) +- `GPDIAKNEBKH GIBHIMGJNNO` (inherited from `MKAMHOIHOJK`) +- `bool HPLBOMGACED` (inherited from `MKAMHOIHOJK`) +- `string IGOPGMHHLKI` (inherited from `MKAMHOIHOJK`) +- `DPLPMKMFMPB JFEAPMIPNEP` (inherited from `MKAMHOIHOJK`) +- `bool KHIJAFCHLIA` (inherited from `MKAMHOIHOJK`) +- `bool KLNJBBPNMBJ` (inherited from `MKAMHOIHOJK`) +- `string KODBEJPEFOJ` (inherited from `MKAMHOIHOJK`) +- `bool LPJLEMJFBPE` (inherited from `MKAMHOIHOJK`) +- `bool MGBDHBHCDMH` (inherited from `MKAMHOIHOJK`) +- `bool MIKDLDEALPN` (inherited from `MKAMHOIHOJK`) +- `bool OFONEIOEIED` (inherited from `MKAMHOIHOJK`) +- `HJPGEGENLPH OILEJFNPDDB` (inherited from `MKAMHOIHOJK`) +- `bool PEEFHKMOMKK` (inherited from `MKAMHOIHOJK`) + +Expected client return: `unknown` (unknown) +Resolved DTO: `unknown` not found in readable C# dump. + +## rooms / search_roomsandplaylists/ + +- `OJMCBOKJFOF` `` RecRoom.Async.IPromise`1> HFHDFLDCIEM(System.String CNBKKCJAHPP) `` (OJMCBOKJFOF.txt:4567) +- `OJMCBOKJFOF+PEBCIEAPGON` `System.String HFHDFLDCIEM(System.String CNBKKCJAHPP)` (OJMCBOKJFOF_NestedType_PEBCIEAPGON.txt:158) + +Expected client return: `` System.Collections.Generic.IReadOnlyList`1 `` (array) +Resolved DTO: `MKAMHOIHOJK` from `MKAMHOIHOJK.cs` +Declaration: `public abstract class MKAMHOIHOJK : IFAIJAGLDFK, AKJKEMONOIL` +Client parser JSON keys: `Name`, `Description`, `ImageName`, `WarningMask`, `CustomWarning`, `CreatorAccountId`, `State`, `Accessibility`, `SupportsLevelVoting`, `IsRRO`, `SupportsScreens`, `SupportsWalkVR`, `SupportsTeleportVR`, `SupportsVRLow`, `SupportsQuest2`, `SupportsMobile`, `SupportsJuniors`, `CreatedAt`, `Stats` +Public/decompiled members: +- `DateTime ACBFDMLHFPB` +- `string AHGCOGFEEEE` +- `bool AKFBNELAMNA` +- `int BADIGBCKECA` +- `bool BNBLOBAEDEE` +- `NMJEKMMBDDE CDINMMPNAID` +- `bool CDNFGMHLDMJ` +- `string FIKEBGGCDFN` +- `GPDIAKNEBKH GIBHIMGJNNO` +- `bool HPLBOMGACED` +- `string IGOPGMHHLKI` +- `DPLPMKMFMPB JFEAPMIPNEP` +- `bool KHIJAFCHLIA` +- `bool KLNJBBPNMBJ` +- `string KODBEJPEFOJ` +- `bool LPJLEMJFBPE` +- `bool MGBDHBHCDMH` +- `bool MIKDLDEALPN` +- `bool OFONEIOEIED` +- `HJPGEGENLPH OILEJFNPDDB` +- `bool PEEFHKMOMKK` + +Expected client return: `unknown` (unknown) +Resolved DTO: `unknown` not found in readable C# dump. + diff --git a/hosts.dorknet.txt b/hosts.dorknet.txt new file mode 100644 index 0000000..2c7f5d6 --- /dev/null +++ b/hosts.dorknet.txt @@ -0,0 +1,112 @@ +# ───────────────────────────────────────────────────────────────────────────── +# DorkNet — Rec Room private server hosts file entries +# ───────────────────────────────────────────────────────────────────────────── +# +# Append this entire block to the bottom of: +# C:\Windows\System32\drivers\etc\hosts +# +# Open Notepad as Administrator (right-click → Run as administrator), File → +# Open → set the file-type filter to "All Files (*.*)", navigate to +# C:\Windows\System32\drivers\etc\, pick `hosts`, paste, save. +# +# After saving, flush the resolver cache so Windows picks up the changes: +# ipconfig /flushdns +# +# Replace 127.0.0.1 with your server's LAN IP (e.g. 192.168.1.50) on every +# line if you're hosting for other machines on your network. Each client +# machine needs its own copy of these entries pointing at the server's IP. +# +# Why every subdomain is listed individually: the Windows hosts file (and +# /etc/hosts on Linux/Mac) does NOT support wildcards. *.rec.net won't work. +# If you want true wildcard resolution, install Acrylic DNS Proxy on Windows +# or dnsmasq on Linux instead — but the explicit list below is sufficient +# for a single-host setup. +# +# Every entry below corresponds to a [Host("xxx.rec.net")] controller in +# DorkNet.Server, plus a few subdomains the client sometimes hits that +# fall through to GlobalCatchAllController. + +127.0.0.1 rec.net +127.0.0.1 admin.rec.net +127.0.0.1 api.rec.net +127.0.0.1 feed.rec.net +127.0.0.1 accounts.rec.net +127.0.0.1 auth.rec.net +127.0.0.1 bugreporting.rec.net +127.0.0.1 cards.rec.net +127.0.0.1 cdn.rec.net +127.0.0.1 chat.rec.net +127.0.0.1 clubs.rec.net +127.0.0.1 cms.rec.net +127.0.0.1 commerce.rec.net +127.0.0.1 data.rec.net +127.0.0.1 datacollection.rec.net +127.0.0.1 discovery.rec.net +127.0.0.1 econ.rec.net +127.0.0.1 gamelogs.rec.net +127.0.0.1 geo.rec.net +127.0.0.1 img.rec.net +127.0.0.1 leaderboard.rec.net +127.0.0.1 link.rec.net +127.0.0.1 lists.rec.net +127.0.0.1 match.rec.net +127.0.0.1 moderation.rec.net +127.0.0.1 ns.rec.net +127.0.0.1 notify.rec.net +127.0.0.1 platformnotifications.rec.net +127.0.0.1 playersettings.rec.net +127.0.0.1 roomcomments.rec.net +127.0.0.1 roomieintegrations.rec.net +127.0.0.1 rooms.rec.net +127.0.0.1 storage.rec.net +127.0.0.1 strings.rec.net +127.0.0.1 strings-cdn.rec.net +127.0.0.1 studio.rec.net +127.0.0.1 thorn.rec.net +127.0.0.1 videos.rec.net + +# ── DorkNet's own domain (localhost) — same subdomains as rec.net but +# points at the public domain we own. The patch-domain.ps1 script +# rewrites every "rec.net" string in the client binaries to +# "localhost" (same length, byte-for-byte), so a freshly-patched +# client connects here. The rec.net entries above stay so an +# unpatched client still works against the local server. + +127.0.0.1 localhost +127.0.0.1 admin.localhost +127.0.0.1 api.localhost +127.0.0.1 accounts.localhost +127.0.0.1 auth.localhost +127.0.0.1 bugreporting.localhost +127.0.0.1 cards.localhost +127.0.0.1 cdn.localhost +127.0.0.1 chat.localhost +127.0.0.1 clubs.localhost +127.0.0.1 cms.localhost +127.0.0.1 commerce.localhost +127.0.0.1 data.localhost +127.0.0.1 datacollection.localhost +127.0.0.1 discovery.localhost +127.0.0.1 econ.localhost +127.0.0.1 feed.localhost +127.0.0.1 gamelogs.localhost +127.0.0.1 geo.localhost +127.0.0.1 img.localhost +127.0.0.1 leaderboard.localhost +127.0.0.1 link.localhost +127.0.0.1 lists.localhost +127.0.0.1 match.localhost +127.0.0.1 moderation.localhost +127.0.0.1 ns.localhost +127.0.0.1 notify.localhost +127.0.0.1 platformnotifications.localhost +127.0.0.1 playersettings.localhost +127.0.0.1 roomcomments.localhost +127.0.0.1 roomieintegrations.localhost +127.0.0.1 rooms.localhost +127.0.0.1 storage.localhost +127.0.0.1 strings.localhost +127.0.0.1 strings-cdn.localhost +127.0.0.1 studio.localhost +127.0.0.1 thorn.localhost +127.0.0.1 videos.localhost diff --git a/nginx.conf b/nginx.conf new file mode 100644 index 0000000..a7cefc1 --- /dev/null +++ b/nginx.conf @@ -0,0 +1,206 @@ +# ───────────────────────────────────────────────────────────────────────────── +# DorkNet — nginx config for the Rec Room private server (March 2020 build) +# ───────────────────────────────────────────────────────────────────────────── +# +# What this does: +# • Listens on :80 (HTTP) and :443 (HTTPS) for every *.rec.net subdomain +# • Terminates TLS using a single mkcert wildcard cert +# • Proxies all HTTP traffic to the DorkNet.Server backend on 127.0.0.1:5137 +# • Forwards the Host header so ASP.NET Core's [Host("subdomain.rec.net")] +# attribute routing keeps working +# • Supports WebSocket upgrades — required for: +# - notify.rec.net/hub/v1 (SignalR Core; client uses BestHTTP.SignalRCore) +# - notify.rec.net/notify/v1 (legacy custom WebSocket) +# • Long read timeouts (1h) so SignalR/WS connections don't get axed mid-session +# • No request-body size limit so multipart form uploads work +# +# Two preconditions before this is useful: +# 1) mkcert ran: mkcert -install && mkcert "*.rec.net" rec.net +# → produces _wildcard.rec.net.pem and _wildcard.rec.net-key.pem +# → fix the absolute paths in the ssl_certificate lines below +# 2) hosts file maps every *.rec.net subdomain to 127.0.0.1 +# (a sample block is at the bottom of this file) +# +# Where to put this file: +# • Windows: C:\nginx\conf\nginx.conf (replace the default) +# • Linux: /etc/nginx/sites-available/dorknet +# then symlink to /etc/nginx/sites-enabled/ +# • Validate: nginx -t +# • Reload: nginx -s reload (or systemctl reload nginx) +# +# If you change the .NET dev port (DorkNet.Server/Properties/launchSettings.json +# applicationUrl), update the `set $backend` line below to match. +# ───────────────────────────────────────────────────────────────────────────── + +worker_processes auto; +events { + worker_connections 4096; +} + +http { + # ── MIME / logging ─────────────────────────────────────────────────────── + include mime.types; + default_type application/octet-stream; + + log_format dorknet '$remote_addr - $host "$request" ' + '$status $body_bytes_sent ${request_time}s ' + '"$http_user_agent"'; + access_log logs/access.log dorknet; + error_log logs/error.log warn; + + sendfile on; + tcp_nopush on; + tcp_nodelay on; + keepalive_timeout 65s; + server_tokens off; + + # ── Body size — multipart uploads on bulk endpoints, image uploads ────── + # Set to 0 for unlimited, or pick a sane cap. 32MB covers profile images. + client_max_body_size 32m; + + # ── Buffers — large enough for the kitchen-sink config response ────────── + # /api/config/v2 can be 5–10KB; default 4k buffers cause "upstream sent too + # big header" 502s. + proxy_buffer_size 16k; + proxy_buffers 8 32k; + proxy_busy_buffers_size 64k; + + # ── Connection-Upgrade map — required for any WebSocket route ──────────── + # When the client sends `Upgrade: websocket`, this map produces + # `Connection: upgrade`. For non-WS traffic it produces "" (empty), which + # is what plain HTTP requires. + map $http_upgrade $connection_upgrade { + default upgrade; + '' ''; + } + + # ── HTTP → HTTPS redirect (port 80 → 443) ──────────────────────────────── + server { + listen 80; + listen [::]:80; + server_name *.rec.net rec.net; + + # ACME / health-check passthroughs would go here. Otherwise everything + # gets bumped to HTTPS. + location / { + return 301 https://$host$request_uri; + } + } + + # ── Main TLS server — every *.rec.net subdomain ────────────────────────── + server { + listen 443 ssl; + listen [::]:443 ssl; + http2 on; + + # Match every Rec Room subdomain (api, accounts, auth, match, rooms, …) + # plus the bare apex domain. The Host header gets forwarded intact, so + # ASP.NET Core's [Host(...)] attribute routing dispatches per-subdomain. + server_name *.rec.net rec.net; + + # ─── TLS — mkcert wildcard cert ───────────────────────────────────── + # CHANGE THESE TWO PATHS to wherever mkcert dropped the files. + # Quickly find them: mkcert -CAROOT + # And the cert PEM is wherever you ran `mkcert "*.rec.net" rec.net`. + ssl_certificate C:/Users/you/Documents/DorkNet/_wildcard.rec.net.pem; + ssl_certificate_key C:/Users/you/Documents/DorkNet/_wildcard.rec.net-key.pem; + + ssl_protocols TLSv1.2 TLSv1.3; + ssl_ciphers HIGH:!aNULL:!MD5; + ssl_session_cache shared:SSL:10m; + ssl_session_timeout 10m; + + # ─── Backend ──────────────────────────────────────────────────────── + # The DorkNet.Server Kestrel listener. Change the port here if you + # change Properties/launchSettings.json applicationUrl. + set $backend http://127.0.0.1:5137; + + # ─── Catch-all proxy: every path goes to DorkNet.Server ────────────── + location / { + proxy_pass $backend; + + # Preserve the original Host so ASP.NET Core's [Host("api.rec.net")] + # routing dispatches by subdomain. + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto $scheme; + proxy_set_header X-Forwarded-Host $host; + proxy_set_header X-Forwarded-Port $server_port; + + # WebSocket upgrade — required for SignalR (notify.rec.net/hub/v1) + # and the custom WS at notify.rec.net/notify/v1. The + # $connection_upgrade var is the map() above. + proxy_http_version 1.1; + proxy_set_header Upgrade $http_upgrade; + proxy_set_header Connection $connection_upgrade; + + # Long timeouts so WebSocket / SSE connections survive idle. + # 1 hour is plenty for a play session; bump if you see disconnects. + proxy_connect_timeout 60s; + proxy_send_timeout 3600s; + proxy_read_timeout 3600s; + + # Don't buffer streaming responses — the SignalR transport breaks + # if frames are buffered before being flushed to the client. + proxy_buffering off; + proxy_request_buffering off; + + # Faster failure when the backend is actually down — avoids the + # 30-second hangs that otherwise pile up under retry storms. + proxy_next_upstream error timeout invalid_header http_502 http_503; + proxy_next_upstream_tries 1; + } + + # ─── Optional: explicit WS routes for clarity (not strictly required; + # the location / above already handles them via the upgrade map). ──── + # location /hub/v1 { proxy_pass $backend; ... } + # location /notify/v1 { proxy_pass $backend; ... } + } +} + +# ───────────────────────────────────────────────────────────────────────────── +# hosts file companion entries +# Add to: +# • Windows: C:\Windows\System32\drivers\etc\hosts (Notepad as Admin) +# • Linux/Mac: /etc/hosts +# +# Replace 127.0.0.1 with your server's LAN IP if other machines connect to it. +# ───────────────────────────────────────────────────────────────────────────── +# +# 127.0.0.1 rec.net +# 127.0.0.1 api.rec.net +# 127.0.0.1 accounts.rec.net +# 127.0.0.1 auth.rec.net +# 127.0.0.1 bugreporting.rec.net +# 127.0.0.1 cards.rec.net +# 127.0.0.1 cdn.rec.net +# 127.0.0.1 chat.rec.net +# 127.0.0.1 clubs.rec.net +# 127.0.0.1 cms.rec.net +# 127.0.0.1 commerce.rec.net +# 127.0.0.1 data.rec.net +# 127.0.0.1 datacollection.rec.net +# 127.0.0.1 discovery.rec.net +# 127.0.0.1 econ.rec.net +# 127.0.0.1 gamelogs.rec.net +# 127.0.0.1 geo.rec.net +# 127.0.0.1 img.rec.net +# 127.0.0.1 leaderboard.rec.net +# 127.0.0.1 link.rec.net +# 127.0.0.1 lists.rec.net +# 127.0.0.1 match.rec.net +# 127.0.0.1 moderation.rec.net +# 127.0.0.1 ns.rec.net +# 127.0.0.1 notify.rec.net +# 127.0.0.1 platformnotifications.rec.net +# 127.0.0.1 playersettings.rec.net +# 127.0.0.1 roomcomments.rec.net +# 127.0.0.1 roomieintegrations.rec.net +# 127.0.0.1 rooms.rec.net +# 127.0.0.1 storage.rec.net +# 127.0.0.1 strings.rec.net +# 127.0.0.1 strings-cdn.rec.net +# 127.0.0.1 studio.rec.net +# 127.0.0.1 thorn.rec.net +# 127.0.0.1 videos.rec.net diff --git a/tools/build-patcher-zip.ps1 b/tools/build-patcher-zip.ps1 new file mode 100644 index 0000000..9298232 --- /dev/null +++ b/tools/build-patcher-zip.ps1 @@ -0,0 +1,105 @@ +<# +.SYNOPSIS + Builds the per-version dorknet-clientpatch-{VersionKey}.zip the + DorkNet launcher expects to find as a GitHub Release asset. + +.DESCRIPTION + Output zip layout (see launcher/RELEASES.md on the public main + branch for the full contract): + + manifest.json (schema v1, describes the install) + MelonLoader.zip (downloaded from melonloader github) + DorkNet.ClientMod.dll (built from this repo) + dorknet-clientmod.json.template (copied from DorkNet.ClientMod/) + + The launcher reads manifest.json, unzips MelonLoader on top of the + user's Rec Room install root, copies the DLL into Mods/, then + renders the config template into UserData/. + +.PARAMETER VersionKey + The version key matching versions.json on the public main branch + (e.g. december_2020_12_18 or march_2020_03_10). + +.PARAMETER MelonLoaderVersion + MelonLoader release tag to bundle. Defaults to the 0.6.x stable + target — that's the loader DorkNet.ClientMod compiled against. + +.PARAMETER OutputDir + Where the final zip lands. Defaults to repo-root/dist. + +.EXAMPLE + .\tools\build-patcher-zip.ps1 -VersionKey december_2020_12_18 +#> +[CmdletBinding()] +param( + [Parameter(Mandatory)][string]$VersionKey, + [string]$MelonLoaderVersion = "v0.6.6", + [string]$OutputDir = $null +) + +$ErrorActionPreference = 'Stop' +$repoRoot = Resolve-Path "$PSScriptRoot\.." +if (-not $OutputDir) { $OutputDir = Join-Path $repoRoot 'dist' } +New-Item -ItemType Directory -Force $OutputDir | Out-Null + +$stage = Join-Path $env:TEMP "dorknet-patcher-$VersionKey-$([guid]::NewGuid().Guid.Substring(0,8))" +New-Item -ItemType Directory -Force $stage | Out-Null +try { + # ── 1. Build DorkNet.ClientMod ─────────────────────────────────────── + Write-Host "[build] dotnet build DorkNet.ClientMod (Release)" + dotnet build "$repoRoot\DorkNet.ClientMod\DorkNet.ClientMod.csproj" ` + -c Release --nologo | Out-Null + if ($LASTEXITCODE -ne 0) { throw "ClientMod build failed" } + $dll = Get-ChildItem "$repoRoot\DorkNet.ClientMod\bin\Release" -Recurse ` + -Filter "DorkNet.ClientMod.dll" | + Sort-Object LastWriteTime -Descending | + Select-Object -First 1 + if (-not $dll) { throw "DorkNet.ClientMod.dll not found post-build" } + Copy-Item $dll.FullName "$stage\DorkNet.ClientMod.dll" + Write-Host "[build] copied $($dll.FullName)" + + # ── 2. Fetch MelonLoader from upstream releases ────────────────────── + # The x64 .NET 6 build is what targets 2020-era Rec Room. The asset + # name pattern is stable across 0.6.x: MelonLoader.x64.zip. + $mlAsset = "MelonLoader.x64.zip" + $mlUrl = "https://github.com/LavaGang/MelonLoader/releases/download/$MelonLoaderVersion/$mlAsset" + $mlOut = "$stage\MelonLoader.zip" + Write-Host "[build] downloading $mlUrl" + Invoke-WebRequest -Uri $mlUrl -OutFile $mlOut + if (-not (Test-Path $mlOut) -or (Get-Item $mlOut).Length -lt 1MB) { + throw "MelonLoader download failed or produced an unexpectedly small file" + } + + # ── 3. Copy the config template ────────────────────────────────────── + $template = "$repoRoot\DorkNet.ClientMod\dorknet-clientmod.json.template" + if (-not (Test-Path $template)) { + throw "Config template missing: $template" + } + Copy-Item $template "$stage\dorknet-clientmod.json.template" + + # ── 4. Write manifest.json ────────────────────────────────────────── + $manifest = @{ + '$schema_version' = 1 + 'loader_archive' = 'MelonLoader.zip' + 'plugin_dll' = 'DorkNet.ClientMod.dll' + 'plugin_dest' = 'MelonLoader/Mods' + 'config_template' = 'dorknet-clientmod.json.template' + 'config_dest' = 'MelonLoader/UserData/dorknet-clientmod.json' + 'old_plugin_paths' = @( + 'BepInEx/plugins/DorkNet.ClientPatch.dll' + ) + } | ConvertTo-Json -Depth 4 + Set-Content -LiteralPath "$stage\manifest.json" -Value $manifest + + # ── 5. Bundle into the final zip ──────────────────────────────────── + $outZip = Join-Path $OutputDir "dorknet-clientpatch-$VersionKey.zip" + if (Test-Path $outZip) { Remove-Item $outZip -Force } + Compress-Archive -Path "$stage\*" -DestinationPath $outZip + $sizeMb = [math]::Round((Get-Item $outZip).Length / 1MB, 1) + Write-Host "" + Write-Host "✓ Built $outZip ($sizeMb MB)" -ForegroundColor Green + Write-Host " Upload this to the v1-* GitHub Release on this branch." +} +finally { + if (Test-Path $stage) { Remove-Item -Recurse -Force $stage } +} diff --git a/tools/cloudflared-config.coolify.yml.template b/tools/cloudflared-config.coolify.yml.template new file mode 100644 index 0000000..f263b44 --- /dev/null +++ b/tools/cloudflared-config.coolify.yml.template @@ -0,0 +1,72 @@ +# DorkNet Cloudflare Tunnel config — Coolify production target. +# ──────────────────────────────────────────────────────────────────── +# Routes every localhost subdomain through the tunnel into the server +# container running on Coolify. The tunnel runs as its own Coolify +# service (or as a sidecar in the same project) and reaches the +# server over the internal Docker network. +# +# Two upstream options, pick one for {{UPSTREAM}}: +# +# a) Internal Docker DNS (preferred when cloudflared runs in the +# same Coolify project as the server): +# http://dorknet-server:8080 +# The server image binds Kestrel to 0.0.0.0:8080 (HTTP only — +# this leg of traffic is already inside the project network). +# +# b) Coolify-published HTTPS endpoint (when cloudflared runs in a +# separate project or on a different host): +# https://dorknet. +# Requires Coolify to be issuing a real TLS cert for that +# hostname. originRequest.noTLSVerify can stay off. +# +# Origin TLS is now off the critical path because the server speaks +# plain HTTP inside the project network. Traffic between cloudflared +# and Cloudflare's edge is still TLS-encrypted; the only unencrypted +# leg is server → cloudflared inside the same Docker network. + +tunnel: {{TUNNEL_ID}} +credentials-file: {{CREDENTIALS_FILE}} + +# Defaults that apply to every ingress rule unless overridden. The +# notify.* SignalR hub needs long-lived WebSockets — cloudflared's +# defaults are conservative and can tear idle connections down, which +# the 2020 watch surfaces as "check your internet" mid-session. Two +# levers matter: +# * keepAliveTimeout — how long cloudflared keeps the origin +# connection pooled and alive between requests; long-lived +# WebSockets stay open well past the default 1m30s, raise it. +# * connectTimeout — initial origin handshake; bumping it past the +# default avoids spurious 502s when the server is warm-booting +# after a Coolify deploy. +# These pair with the matching Program.cs SignalR + WebSocket +# KeepAliveInterval values (15s / 30s) so traffic flows continuously +# in both directions and the tunnel never marks the stream idle. +originRequest: + connectTimeout: 30s + tlsTimeout: 10s + tcpKeepAlive: 30s + keepAliveConnections: 100 + keepAliveTimeout: 10m + noHappyEyeballs: false + +# Ingress rules are evaluated top-down. The wildcard catches every +# subdomain we don't list explicitly, so when the 2020 Rec Room client +# (post-domain-patch) hits a niche subdomain like roomieintegrations. +# localhost, the request still reaches the server. +ingress: + - hostname: localhost + service: {{UPSTREAM}} + originRequest: + httpHostHeader: localhost + + - hostname: "*.localhost" + service: {{UPSTREAM}} + # cloudflared forwards the original Host header by default, which + # is what the server's [Host("api.localhost")] etc. attributes match + # against. No explicit httpHostHeader here so wildcards stay + # transparent. + + # Catch-all rejects anything that didn't match (defensive — the + # tunnel only routes traffic for hostnames we've added DNS records + # for, so this is rarely reached). + - service: http_status:404 diff --git a/tools/cloudflared-config.yml.template b/tools/cloudflared-config.yml.template new file mode 100644 index 0000000..43b853a --- /dev/null +++ b/tools/cloudflared-config.yml.template @@ -0,0 +1,40 @@ +# DorkNet Cloudflare Tunnel config +# ──────────────────────────────────────────────────────────────────── +# Routes every localhost subdomain through the tunnel into the local +# server (HTTPS on port 443). Generated by tools/setup-cloudflared.ps1 +# — copy to %USERPROFILE%\.cloudflared\config.yml and replace the +# {{TUNNEL_ID}} / {{CREDENTIALS_FILE}} placeholders. +# +# Origin TLS verify is OFF because the local server's wildcard cert is +# from mkcert (a private CA trusted only on this machine). cloudflared +# would reject it as untrusted otherwise. Traffic between cloudflared +# and Cloudflare's edge is still TLS-encrypted; this only relaxes the +# leg from cloudflared → localhost. + +tunnel: {{TUNNEL_ID}} +credentials-file: {{CREDENTIALS_FILE}} + +# Ingress rules are evaluated top-down. The wildcard catches every +# subdomain we don't list explicitly, so when the 2020 Rec Room client +# (post-domain-patch) hits a niche subdomain like roomieintegrations. +# localhost, the request still reaches the server. +ingress: + - hostname: localhost + service: https://localhost:443 + originRequest: + noTLSVerify: true + httpHostHeader: localhost + + - hostname: "*.localhost" + service: https://localhost:443 + originRequest: + noTLSVerify: true + # Forward the original Host header verbatim so the server's + # [Host("api.localhost")] etc. attributes match correctly. + # (cloudflared does this by default; setting it explicitly so + # config.yml is self-documenting.) + + # Catch-all rejects anything that didn't match (defensive — the + # tunnel only routes traffic for hostnames we've added DNS records + # for, so this is rarely reached). + - service: http_status:404 diff --git a/tools/cloudflared.README.md b/tools/cloudflared.README.md new file mode 100644 index 0000000..aa97e5e --- /dev/null +++ b/tools/cloudflared.README.md @@ -0,0 +1,99 @@ +# Cloudflare Tunnel for `localhost` + +Stand up a Cloudflare Tunnel that exposes your local DorkNet server +on `*.localhost` for public testing — without opening port 443 on your +firewall, and using Cloudflare's real Let's-Encrypt-equivalent cert +edge-side so visitors don't need your mkcert CA installed. + +## When to use this + +- You want a friend to play the patched 2020 Rec Room client against + your server, but they're not on your LAN and don't want to install + your local CA. +- You want `https://admin.localhost` / `https://feed.localhost` reachable + from any browser, not just yours. + +## Prerequisites + +1. **`localhost` is on a Cloudflare account you control.** The DNS + nameservers for the domain need to be Cloudflare's. Check at + — the zone should be Active. +2. **Local server running** on `0.0.0.0:443` with the wildcard cert + that covers `*.localhost` (re-run `tools/patch-client.ps1` if your + cert is still the `+1` rec.net-only one — it'll regenerate as + `+3` covering both domains). +3. **Patched client** with `tools/patch-domain.ps1` so the in-game + URLs resolve to `*.localhost`. (Players using the public deployment + need to run this on their own client too.) + +## One-shot setup + +```powershell +PS C:\…\Recnet> .\tools\setup-cloudflared.ps1 +``` + +The script: +1. Installs `cloudflared` via winget if missing. +2. Runs `cloudflared tunnel login` — opens a browser, you pick the + `localhost` zone, Cloudflare drops `cert.pem` into + `%USERPROFILE%\.cloudflared\`. +3. Creates a named tunnel called `dorknet` (or reuses one if it + already exists). +4. Generates `%USERPROFILE%\.cloudflared\config.yml` from the + `tools/cloudflared-config.yml.template` template, filling in the + tunnel id and credentials path. +5. Adds DNS CNAMEs for every localhost subdomain DorkNet uses + (api, auth, accounts, match, notify, ns, admin, feed, …). + +## Run the tunnel + +Foreground (for testing — Ctrl+C to stop): + +```powershell +cloudflared tunnel run dorknet +``` + +Or install as a Windows service so it survives reboots: + +```powershell +cloudflared service install +``` + +## What happens at request time + +``` +visitor → https://api.localhost/api/versioncheck/v4 + → Cloudflare edge (real LE cert, validated) + → cloudflared tunnel + → cloudflared (running on your machine) + → https://localhost:443 (Host: api.localhost, mkcert cert) + → DorkNet.Server matches [Host("api.localhost")] → handler runs +``` + +The leg from cloudflared → localhost has `noTLSVerify: true` because +the local server's cert is from mkcert (private CA only trusted on +your machine). The Cloudflare-edge → cloudflared leg is still +TLS-encrypted with their cert, so this isn't a security weakening +— it's just acknowledging that "127.0.0.1 with self-trusted cert" is +where the trust chain ends. + +## Tearing down + +```powershell +cloudflared service uninstall # if you ran service install +cloudflared tunnel delete dorknet +``` + +DNS records left behind in the Cloudflare dashboard need to be +deleted manually if you want them gone. + +## Limits / caveats + +- **Photon traffic is NOT proxied.** Cloudflare Tunnel is HTTP-only; + the realtime Photon connection still hits the public Photon Cloud + directly, with the AppId from your `appsettings.json`. That's fine + — Photon doesn't go through your server. +- **WebSocket support** is on by default in modern cloudflared, so + the SignalR notify hub works through the tunnel. +- **Free tier rate limits** — Cloudflare Free is generous for a + small private server but watch the dashboard if you scale up. diff --git a/tools/install-legacy-client.ps1 b/tools/install-legacy-client.ps1 new file mode 100644 index 0000000..4908404 --- /dev/null +++ b/tools/install-legacy-client.ps1 @@ -0,0 +1,190 @@ +<# +.SYNOPSIS + Switch a Rec Room 2020 client to DorkNet without BepInEx. + +.DESCRIPTION + This is the loader-free fallback path. It removes the BepInEx plugin + install made by tools/install-plugin.ps1, then runs tools/patch-client.ps1 + in its legacy mode: + + - byte-patches Photon Realtime/Voice AppIds in resources.assets; + - byte-patches the native BestHTTP/BouncyCastle TLS certificate + verifier return in GameAssembly.dll; + - points *.rec.net at the DorkNet server through the hosts file; + - generates/uses a mkcert wildcard certificate for HTTPS. + + No BepInEx, Harmony, Doorstop, or IL2CPP managed plugin remains involved + unless -KeepBepInEx is supplied. + + Photon identity note: the stock 2020 client does not send userid/LoginLock + AuthValues. DorkNet's Photon custom-auth endpoint allows anonymous connects, + but duplicate-login enforcement through Photon is unavailable in this mode. + +.PARAMETER RecRoomPath + Path to Recroom_Release_Data. + +.PARAMETER PhotonAppId + Photon Cloud Realtime AppId. + +.PARAMETER PhotonVoiceAppId + Photon Cloud Voice AppId. Defaults to PhotonAppId. + +.PARAMETER ServerIp + IP address to put in the hosts file for *.rec.net. + +.PARAMETER CertPath + Optional path for the mkcert wildcard certificate. + +.PARAMETER KeepBepInEx + Skip removing BepInEx before applying the legacy patch. + +.PARAMETER DryRun + Show what would happen without changing files. + +.EXAMPLE + .\tools\install-legacy-client.ps1 ` + -RecRoomPath "C:\Users\you\Documents\Recnet\dist\RecRoom-Clean-2020.03.10\Recroom_Release_Data" ` + -PhotonAppId "" +#> + +[CmdletBinding()] +param( + [Parameter(Mandatory = $true)] + [string]$RecRoomPath, + + [Parameter(Mandatory = $true)] + [ValidatePattern('(?i)^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$')] + [string]$PhotonAppId, + + [ValidatePattern('(?i)^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$')] + [string]$PhotonVoiceAppId, + + [string]$ServerIp = '127.0.0.1', + + [string]$CertPath, + + [switch]$KeepBepInEx, + + [switch]$DryRun +) + +$ErrorActionPreference = 'Stop' +Set-StrictMode -Version Latest + +function Write-Step($msg) { Write-Host ('> ' + $msg) -ForegroundColor Cyan } +function Write-OK($msg) { Write-Host ('OK: ' + $msg) -ForegroundColor Green } +function Write-Warn($msg) { Write-Host ('WARN: ' + $msg) -ForegroundColor Yellow } + +$TlsBypassOffset = 0xB5EE50 +$TlsBypassOriginalByte = 0x48 +$TlsBypassPatchedByte = 0xC3 + +function Apply-TlsBypassPatch { + param( + [Parameter(Mandatory = $true)] + [string]$DataPath, + + [switch]$DryRun + ) + + $gameDir = Split-Path -Parent $DataPath + $gameAssembly = Join-Path $gameDir 'GameAssembly.dll' + if (-not (Test-Path -LiteralPath $gameAssembly -PathType Leaf)) { + throw "GameAssembly.dll not found next to '$DataPath'." + } + + Write-Step 'Applying native TLS verifier bypass' + $bytes = [System.IO.File]::ReadAllBytes($gameAssembly) + if ($bytes.Length -le $TlsBypassOffset) { + throw "GameAssembly.dll is too small for TLS bypass offset 0x$($TlsBypassOffset.ToString('X')). Wrong client build?" + } + + $current = $bytes[$TlsBypassOffset] + if ($current -eq $TlsBypassPatchedByte) { + Write-OK "TLS bypass already patched at GameAssembly.dll+0x$($TlsBypassOffset.ToString('X'))." + return + } + + if ($current -ne $TlsBypassOriginalByte) { + $actual = '0x{0:X2}' -f $current + $expected = '0x{0:X2}' -f $TlsBypassOriginalByte + throw "Unexpected byte at GameAssembly.dll+0x$($TlsBypassOffset.ToString('X')): $actual, expected $expected. Refusing to patch an unknown build." + } + + $backup = "$gameAssembly.tls-bypass-backup-$(Get-Date -Format 'yyyyMMdd-HHmmss')" + if ($DryRun) { + Write-Host "DRY RUN: would backup GameAssembly.dll to $backup" + Write-Host "DRY RUN: would patch GameAssembly.dll+0x$($TlsBypassOffset.ToString('X')) from 0x48 to 0xC3" + return + } + + Copy-Item -LiteralPath $gameAssembly -Destination $backup + $bytes[$TlsBypassOffset] = [byte]$TlsBypassPatchedByte + [System.IO.File]::WriteAllBytes($gameAssembly, $bytes) + Write-OK "TLS bypass patched at GameAssembly.dll+0x$($TlsBypassOffset.ToString('X'))." + Write-OK "Backup written to $backup" +} + +$repoRoot = Split-Path -Parent $PSScriptRoot +$installPlugin = Join-Path $PSScriptRoot 'install-plugin.ps1' +$patchClient = Join-Path $PSScriptRoot 'patch-client.ps1' + +if (-not (Test-Path -LiteralPath $installPlugin -PathType Leaf)) { + throw "Missing $installPlugin" +} +if (-not (Test-Path -LiteralPath $patchClient -PathType Leaf)) { + throw "Missing $patchClient" +} + +$resolved = Resolve-Path -LiteralPath $RecRoomPath -ErrorAction SilentlyContinue +if (-not $resolved) { + throw "RecRoomPath '$RecRoomPath' does not exist." +} +$RecRoomPath = $resolved.Path + +if (-not $PhotonVoiceAppId) { + $PhotonVoiceAppId = $PhotonAppId +} + +Write-Host '' +Write-Step 'DorkNet legacy client install' +Write-Host (" RecRoomPath : {0}" -f $RecRoomPath) +Write-Host (" PhotonAppId : {0}" -f $PhotonAppId) +Write-Host (" VoiceAppId : {0}" -f $PhotonVoiceAppId) +Write-Host (" ServerIp : {0}" -f $ServerIp) +Write-Host (" RepoRoot : {0}" -f $repoRoot) +Write-Host '' + +if (-not $KeepBepInEx) { + Write-Step 'Removing BepInEx/plugin install' + if ($DryRun) { + Write-Host "DRY RUN: would run install-plugin.ps1 -Revert" + } else { + & $installPlugin -RecRoomPath $RecRoomPath -Revert + if (-not $?) { + throw "install-plugin.ps1 -Revert failed" + } + } +} else { + Write-Warn 'Keeping BepInEx because -KeepBepInEx was supplied.' +} + +Write-Step 'Applying legacy resources.assets/hosts/cert patch' +if ($CertPath -and $DryRun) { + & $patchClient -RecRoomPath $RecRoomPath -PhotonAppId $PhotonAppId -PhotonVoiceAppId $PhotonVoiceAppId -ServerIp $ServerIp -CertPath $CertPath -DryRun +} elseif ($CertPath) { + & $patchClient -RecRoomPath $RecRoomPath -PhotonAppId $PhotonAppId -PhotonVoiceAppId $PhotonVoiceAppId -ServerIp $ServerIp -CertPath $CertPath +} elseif ($DryRun) { + & $patchClient -RecRoomPath $RecRoomPath -PhotonAppId $PhotonAppId -PhotonVoiceAppId $PhotonVoiceAppId -ServerIp $ServerIp -DryRun +} else { + & $patchClient -RecRoomPath $RecRoomPath -PhotonAppId $PhotonAppId -PhotonVoiceAppId $PhotonVoiceAppId -ServerIp $ServerIp +} +if (-not $?) { + throw "patch-client.ps1 failed" +} + +Apply-TlsBypassPatch -DataPath $RecRoomPath -DryRun:$DryRun + +Write-Host '' +Write-OK 'Legacy client path installed.' +Write-Warn 'For this mode, Photon Custom Authentication should allow anonymous connects; DorkNet already does this, but Photon dashboard strict rejection should not depend on userid/LoginLock.' diff --git a/tools/install-melon.ps1 b/tools/install-melon.ps1 new file mode 100644 index 0000000..ab2a63e --- /dev/null +++ b/tools/install-melon.ps1 @@ -0,0 +1,648 @@ +<# +.SYNOPSIS + MelonLoader-based setup for the DorkNet 2020 client. + +.DESCRIPTION + One command that: + 1. Downloads MelonLoader (IL2CPP build, x64) from GitHub releases + and extracts it into the Rec Room install dir. + 2. Builds DorkNet.ClientMod (the MelonLoader port of the BepInEx + ClientPatch plugin) against MelonLoader's generated + Il2CppAssemblies. + 3. Drops the built DLL into \Mods\ and writes + \MelonLoader\UserData\dorknet-clientmod.json + with the user-supplied Photon AppId / server host / etc. + 4. Calls tools\patch-client.ps1 to apply the static byte patches + (Photon AppId in resources.assets, image signing public key, + image signature verifier, and the CodeStage Anti-Cheat Toolkit + neutraliser). + + Idempotent — re-running re-applies any missing pieces but won't + re-download MelonLoader if it's already cached, and the byte patches + detect already-patched bytes and skip. + + First-run gotcha: MelonLoader needs ONE successful game launch to + generate Il2CppAssemblies\ before the mod can build against it. This + script auto-detects that state. If Il2CppAssemblies\ is missing it + installs MelonLoader, writes the JSON config, runs the byte patches, + then prints "launch the game once, then re-run this script with + -ResumeBuild" instead of failing the build. + +.PARAMETER RecRoomPath + Path to the Recroom_Release_Data folder (the one with + resources.assets). The script uses this folder's parent as the install + root for MelonLoader extraction. + +.PARAMETER PhotonAppId + Photon Cloud Realtime AppId (GUID, no curly braces). Required on + the first install run. + +.PARAMETER PhotonVoiceAppId + Photon Cloud Voice AppId. Defaults to PhotonAppId. + +.PARAMETER ServerHost + Domain that replaces .rec.net in every URL the watch builds. + Default: localhost. + +.PARAMETER MelonLoaderZip + Optional local path to a MelonLoader.x64.zip. Skips the download. + +.PARAMETER ForceMelonLoader + Re-extract MelonLoader even if it's already present. + +.PARAMETER ResumeBuild + Skip the MelonLoader install + patch-client.ps1 step and only do + the mod build/copy step. Use after the first run has prompted you to + launch the game once. + +.PARAMETER SkipBytePatches + Skip the tools\patch-client.ps1 call. Use if you've already run it + separately or want to manage byte patches by hand. + +.PARAMETER Revert + Uninstall the mod + MelonLoader. Does not touch resources.assets + byte patches (use patch-client.ps1 -Revert for those). + +.PARAMETER ImageSigningPublicKeyPath + Forwarded to patch-client.ps1 unchanged — see that script's help. + +.EXAMPLE + # First-time setup. After the script prints "launch the game once", + # double-click Recroom_Release.exe, wait for the watch UI, quit, then + # re-run with -ResumeBuild to finish the mod build. + .\tools\install-melon.ps1 ` + -RecRoomPath "C:\Users\you\Documents\Recnet\dist\RecRoom-Clean-2020.03.10\Recroom_Release_Data" ` + -PhotonAppId + +.EXAMPLE + # Second pass after the first game launch generated Il2CppAssemblies. + .\tools\install-melon.ps1 ` + -RecRoomPath "C:\Users\you\Documents\Recnet\dist\RecRoom-Clean-2020.03.10\Recroom_Release_Data" ` + -PhotonAppId ` + -ResumeBuild + +.EXAMPLE + # Uninstall the mod (keeps the byte patches; use patch-client.ps1 -Revert + # for those). + .\tools\install-melon.ps1 -RecRoomPath D:\RecRoom\Recroom_Release_Data -Revert +#> + +[CmdletBinding(DefaultParameterSetName = 'Install')] +param( + [Parameter(Mandatory = $true)] + [string]$RecRoomPath, + + # Mandatory only when -FromServerConfig is not used. Validation + # happens after FromServerConfig fills it in below. + [Parameter(ParameterSetName = 'Install')] + [string]$PhotonAppId, + + [Parameter(ParameterSetName = 'Install')] + [string]$PhotonVoiceAppId, + + [Parameter(ParameterSetName = 'Install')] + [ValidateSet('us','eu','asia','jp','au','usw','sa','cae','kr','in','ru','rue')] + [string]$PhotonCloudRegion = 'us', + + # Read PhotonAppId / VoiceAppId / CloudRegion from the DorkNet.Server + # appsettings file at this path. Use the value from + # `DorkNet.Server\appsettings.Local.json` (falls back to + # `appsettings.json`) so you don't have to retype the GUIDs you + # already configured on the server side. Any explicit -PhotonAppId / + # -PhotonVoiceAppId / -PhotonCloudRegion override the file values. + [Parameter(ParameterSetName = 'Install')] + [string]$FromServerConfig, + + [Parameter(ParameterSetName = 'Install')] + [string]$ServerHost = 'localhost', + + [Parameter(ParameterSetName = 'Install')] + [string]$MelonLoaderZip, + + [Parameter(ParameterSetName = 'Install')] + [switch]$ForceMelonLoader, + + [Parameter(ParameterSetName = 'Install')] + [switch]$ResumeBuild, + + # Auto-bootstrap MelonLoader by launching Recroom_Release.exe in the + # background, polling for Il2CppAssemblies\Assembly-CSharp.dll to + # appear, then killing the process. Makes the install a single + # script run instead of the install -> launch -> ResumeBuild dance. + # On by default; pass -AutoBootstrap:$false to keep the two-step flow. + [Parameter(ParameterSetName = 'Install')] + [bool]$AutoBootstrap = $true, + + [Parameter(ParameterSetName = 'Install')] + [switch]$SkipBytePatches, + + [Parameter(ParameterSetName = 'Install')] + [string]$ImageSigningPublicKeyPath, + + [Parameter(Mandatory = $true, ParameterSetName = 'Revert')] + [switch]$Revert +) + +$ErrorActionPreference = 'Stop' +Set-StrictMode -Version Latest + +# Seed $LASTEXITCODE so every later "did the native call succeed" check +# has something to read under StrictMode. PowerShell only populates this +# automatic variable AFTER a native executable runs, so a cold-shell +# invocation of this script can otherwise hit a VariableIsUndefined +# error the first time we test it. +$global:LASTEXITCODE = 0 + +# Pinned MelonLoader release. "latest" would be more convenient but +# pinning means a regression upstream doesn't silently land on tester +# machines — same rationale as the BepInEx pin in install-plugin.ps1. +# Bump when there's a known-good newer build. +$Script:MelonLoaderUrl = 'https://github.com/LavaGang/MelonLoader/releases/download/v0.6.6/MelonLoader.x64.zip' +$Script:MelonLoaderCacheName = 'MelonLoader.x64-0.6.6.zip' + +# Steamless pin — same release as install-plugin.ps1 so the cache hits +# between the two scripts. The 2020 Rec Room build is wrapped with +# Valve's SteamStub DRM; if the .exe is launched outside Steam the +# stub aborts with exit code 1, which is what kills MelonLoader's +# auto-bootstrap before it can generate Il2CppAssemblies. Stripping +# the wrapper is the cleanest fix. +$Script:SteamlessUrl = 'https://github.com/atom0s/Steamless/releases/download/v3.1.0.5/Steamless.v3.1.0.5.-.by.atom0s.zip' +$Script:SteamlessCacheName = 'Steamless.v3.1.0.5.zip' + +function Write-Step($msg) { Write-Host ('> ' + $msg) -ForegroundColor Cyan } +function Write-OK($msg) { Write-Host ('+ ' + $msg) -ForegroundColor Green } +function Write-Warn2($msg) { Write-Host ('! ' + $msg) -ForegroundColor Yellow } +function Write-Err($msg) { Write-Host ('x ' + $msg) -ForegroundColor Red } + +# ── Steam DRM detection + stripping ────────────────────────────────── +# These three helpers are mechanically identical to the ones in +# install-plugin.ps1 (they predate this script). Kept locally here +# rather than dot-sourced so install-melon.ps1 stays a single file you +# can drop on a fresh machine. + +function Test-SteamDrmWrapped { + # Walks the PE header to see whether the AddressOfEntryPoint lands + # inside a `.bind` section — Valve's drm_wrap injects `.bind` + # carrying the SteamStub trampoline and rewrites the entry point + # to land there. After Steamless strips, the entry point points + # back into `.text` and the leftover `.bind` section is harmless. + param([string]$ExePath) + try { + $fs = [System.IO.File]::OpenRead($ExePath) + try { + $br = New-Object System.IO.BinaryReader($fs) + $fs.Seek(0x3C, 'Begin') | Out-Null + $peOff = $br.ReadInt32() + if ($peOff -lt 0 -or $peOff + 24 -gt $fs.Length) { return $false } + $fs.Seek($peOff + 6, 'Begin') | Out-Null + $sections = $br.ReadUInt16() + $fs.Seek($peOff + 20, 'Begin') | Out-Null + $optSize = $br.ReadUInt16() + $fs.Seek($peOff + 24 + 16, 'Begin') | Out-Null + $entryRva = $br.ReadUInt32() + $secStart = $peOff + 24 + $optSize + for ($i = 0; $i -lt $sections; $i++) { + $fs.Seek($secStart + $i * 40, 'Begin') | Out-Null + $name = [System.Text.Encoding]::ASCII.GetString($br.ReadBytes(8)).TrimEnd([char]0) + $vSize = $br.ReadUInt32() + $vAddr = $br.ReadUInt32() + if ($name -eq '.bind' -and $entryRva -ge $vAddr -and $entryRva -lt ($vAddr + $vSize)) { + return $true + } + } + return $false + } finally { $fs.Close() } + } catch { + return $false + } +} + +function Get-Steamless { + # Returns the path to a usable Steamless.CLI.exe. Downloads + extracts + # the pinned release on first run, caches under %LOCALAPPDATA%\DorkNet. + $cacheDir = Join-Path $env:LOCALAPPDATA 'DorkNet' + $extractDir = Join-Path $cacheDir 'Steamless' + $cli = Join-Path $extractDir 'Steamless.CLI.exe' + if (Test-Path -LiteralPath $cli -PathType Leaf) { + return $cli + } + New-Item -ItemType Directory -Path $cacheDir -Force | Out-Null + $zipPath = Join-Path $cacheDir $Script:SteamlessCacheName + if (-not (Test-Path -LiteralPath $zipPath -PathType Leaf)) { + Write-Step "Downloading Steamless -> $zipPath" + Write-Host " $Script:SteamlessUrl" -ForegroundColor DarkGray + Invoke-WebRequest -Uri $Script:SteamlessUrl -OutFile $zipPath ` + -UseBasicParsing -TimeoutSec 60 -ErrorAction Stop + } + Write-Step "Extracting Steamless -> $extractDir" + Expand-Archive -LiteralPath $zipPath -DestinationPath $extractDir -Force + if (-not (Test-Path -LiteralPath $cli -PathType Leaf)) { + # Some Steamless releases nest the CLI in a subfolder. + $found = Get-ChildItem -LiteralPath $extractDir -Recurse -Filter 'Steamless.CLI.exe' -ErrorAction SilentlyContinue | Select-Object -First 1 + if ($found) { $cli = $found.FullName } + } + if (-not (Test-Path -LiteralPath $cli -PathType Leaf)) { + throw "Steamless.CLI.exe not found after extracting $zipPath" + } + return $cli +} + +function Invoke-DrmStrip { + # Runs Steamless against the wrapped exe in-place. Steamless writes + # an unpacked copy alongside the original; we move the original to + # .drm-original.exe and rename the unpacked file in place so the + # auto-bootstrap step doesn't have to know which is which. + param([string]$ExePath) + $cli = Get-Steamless + Write-Step "Stripping Steam DRM from $ExePath" + $exeDir = Split-Path -Parent $ExePath + $proc = Start-Process -FilePath $cli ` + -ArgumentList @('--quiet', '--keepbind', '0', $ExePath) ` + -NoNewWindow -PassThru -Wait + if ($proc.ExitCode -ne 0) { + throw "Steamless exited with code $($proc.ExitCode). The exe may not be a recognised Steam DRM build." + } + $unpacked = "$ExePath.unpacked.exe" + if (-not (Test-Path -LiteralPath $unpacked -PathType Leaf)) { + throw "Steamless ran but didn't produce $unpacked. Check the tool's output." + } + $bak = Join-Path $exeDir 'Recroom_Release.drm-original.exe' + if (-not (Test-Path -LiteralPath $bak -PathType Leaf)) { + Move-Item -LiteralPath $ExePath -Destination $bak -Force + } else { + Remove-Item -LiteralPath $ExePath -Force + } + Move-Item -LiteralPath $unpacked -Destination $ExePath -Force + Write-OK "DRM stripped. Original kept as $bak" +} + +# ── Path resolution ────────────────────────────────────────────────── +$resolved = Resolve-Path -LiteralPath $RecRoomPath -ErrorAction SilentlyContinue +if ($resolved) { $RecRoomPath = $resolved.Path } else { $RecRoomPath = $null } +if (-not $RecRoomPath -or -not (Test-Path -LiteralPath $RecRoomPath -PathType Container)) { + Write-Err "RecRoomPath '$RecRoomPath' does not exist." + exit 1 +} +if (-not (Test-Path -LiteralPath (Join-Path $RecRoomPath 'resources.assets') -PathType Leaf)) { + Write-Err "resources.assets not found in '$RecRoomPath'. Point at the Recroom_Release_Data folder." + exit 1 +} + +$RecRoomRoot = Split-Path -Parent $RecRoomPath +$RecRoomExe = Join-Path $RecRoomRoot 'Recroom_Release.exe' +$MelonLoaderDir = Join-Path $RecRoomRoot 'MelonLoader' +$ModsDir = Join-Path $RecRoomRoot 'Mods' +$UserDataDir = Join-Path $MelonLoaderDir 'UserData' +$Il2CppAsmsDir = Join-Path $MelonLoaderDir 'Il2CppAssemblies' +$RepoRoot = Split-Path -Parent $PSScriptRoot +$ModProj = Join-Path $RepoRoot 'DorkNet.ClientMod' +$ModCsproj = Join-Path $ModProj 'DorkNet.ClientMod.csproj' +$PatchClient = Join-Path $PSScriptRoot 'patch-client.ps1' + +# ── Pull Photon values from the server's appsettings when -FromServerConfig +# is set OR when a sibling DorkNet.Server\appsettings.Local.json exists. +# Explicit -Photon* params still win over file values so you can patch +# a one-off client against a different Photon app without touching the +# server config. Both Local and the base file get a look — Local first. +function Read-ServerPhotonConfig { + param([string]$Path) + if (-not (Test-Path -LiteralPath $Path -PathType Leaf)) { return $null } + try { + $json = Get-Content -LiteralPath $Path -Raw | ConvertFrom-Json + if (-not $json.PSObject.Properties['Photon']) { return $null } + $photon = $json.Photon + $appId = if ($photon.PSObject.Properties['AppId']) { [string]$photon.AppId } else { $null } + $voice = if ($photon.PSObject.Properties['VoiceAppId']) { [string]$photon.VoiceAppId } else { $null } + $region = if ($photon.PSObject.Properties['CloudRegion']) { [string]$photon.CloudRegion } else { $null } + return [pscustomobject]@{ AppId = $appId; VoiceAppId = $voice; CloudRegion = $region; Source = $Path } + } catch { + Write-Warn2 "Could not parse Photon section from $Path : $($_.Exception.Message)" + return $null + } +} + +if ($PSCmdlet.ParameterSetName -eq 'Install') { + if ($FromServerConfig) { + $candidates = @($FromServerConfig) + } else { + $candidates = @( + (Join-Path $RepoRoot 'DorkNet.Server\appsettings.Local.json'), + (Join-Path $RepoRoot 'DorkNet.Server\appsettings.json') + ) + } + foreach ($cand in $candidates) { + $cfg = Read-ServerPhotonConfig -Path $cand + if (-not $cfg) { continue } + if (-not $PhotonAppId -and $cfg.AppId) { $PhotonAppId = $cfg.AppId } + if (-not $PhotonVoiceAppId -and $cfg.VoiceAppId) { $PhotonVoiceAppId = $cfg.VoiceAppId } + if (-not $PSBoundParameters.ContainsKey('PhotonCloudRegion') -and $cfg.CloudRegion) { + $PhotonCloudRegion = $cfg.CloudRegion + } + Write-OK ("Loaded Photon config from $($cfg.Source) " + + "(AppId=$(if($cfg.AppId){''}else{''}), " + + "VoiceAppId=$(if($cfg.VoiceAppId){''}else{''}), " + + "CloudRegion=$(if($cfg.CloudRegion){$cfg.CloudRegion}else{''}))") + break + } + + if (-not $PhotonAppId) { + Write-Err 'PhotonAppId is required (pass -PhotonAppId or -FromServerConfig ).' + exit 1 + } + $guidRe = '^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$' + if ($PhotonAppId -notmatch $guidRe) { + Write-Err "PhotonAppId '$PhotonAppId' is not a GUID." + exit 1 + } + if ($PhotonVoiceAppId -and $PhotonVoiceAppId -notmatch $guidRe) { + Write-Err "PhotonVoiceAppId '$PhotonVoiceAppId' is not a GUID." + exit 1 + } +} + +# ── Revert ─────────────────────────────────────────────────────────── +if ($Revert) { + Write-Step 'REVERT MODE — removing MelonLoader + DorkNet.ClientMod' + + foreach ($p in @( + (Join-Path $RecRoomRoot 'MelonLoader'), + (Join-Path $RecRoomRoot 'Mods'), + (Join-Path $RecRoomRoot 'version.dll'), + (Join-Path $RecRoomRoot 'dobby.dll'), + (Join-Path $RecRoomRoot 'NOTICE.txt') + )) { + if (Test-Path -LiteralPath $p) { + Write-Step "Removing $p" + Remove-Item -LiteralPath $p -Recurse -Force + Write-OK "Removed $p" + } + } + + Write-OK 'MelonLoader and ClientMod removed.' + Write-Host ' resources.assets / image signing / anti-cheat byte patches are still applied.' -ForegroundColor DarkGray + Write-Host ' Use tools\patch-client.ps1 -Revert -RecRoomPath ... to roll those back too.' -ForegroundColor DarkGray + exit 0 +} + +# ── 1. MelonLoader install ─────────────────────────────────────────── +function Get-MelonLoaderZip { + if ($MelonLoaderZip) { + if (-not (Test-Path -LiteralPath $MelonLoaderZip -PathType Leaf)) { + Write-Err "MelonLoaderZip '$MelonLoaderZip' not found." + exit 1 + } + return $MelonLoaderZip + } + $cacheDir = Join-Path $env:LOCALAPPDATA 'DorkNet' + New-Item -ItemType Directory -Path $cacheDir -Force | Out-Null + $cached = Join-Path $cacheDir $Script:MelonLoaderCacheName + if (Test-Path -LiteralPath $cached -PathType Leaf) { + Write-OK "Using cached MelonLoader zip -> $cached" + return $cached + } + Write-Step "Downloading MelonLoader -> $cached" + Write-Host " $Script:MelonLoaderUrl" -ForegroundColor DarkGray + Invoke-WebRequest -Uri $Script:MelonLoaderUrl -OutFile $cached ` + -UseBasicParsing -TimeoutSec 120 -ErrorAction Stop + return $cached +} + +$melonAlreadyInstalled = (Test-Path -LiteralPath (Join-Path $RecRoomRoot 'version.dll') -PathType Leaf) ` + -and (Test-Path -LiteralPath $MelonLoaderDir -PathType Container) + +if ($ResumeBuild) { + if (-not $melonAlreadyInstalled) { + Write-Err 'ResumeBuild requested but MelonLoader is not installed. Run without -ResumeBuild first.' + exit 1 + } + Write-OK 'Resuming after game launch — skipping MelonLoader extract.' +} elseif ($melonAlreadyInstalled -and -not $ForceMelonLoader) { + Write-OK "MelonLoader already installed at $MelonLoaderDir" +} else { + $zipPath = Get-MelonLoaderZip + Write-Step "Extracting MelonLoader -> $RecRoomRoot" + Expand-Archive -LiteralPath $zipPath -DestinationPath $RecRoomRoot -Force + if (-not (Test-Path -LiteralPath (Join-Path $RecRoomRoot 'version.dll'))) { + Write-Err 'MelonLoader extracted but version.dll proxy missing. The downloaded zip may be the wrong shape.' + exit 1 + } + Write-OK 'MelonLoader extracted.' +} + +# ── 2. Make sure UserData + Mods exist, write mod config ──────────── +New-Item -ItemType Directory -Path $UserDataDir -Force | Out-Null +New-Item -ItemType Directory -Path $ModsDir -Force | Out-Null + +$cfgPath = Join-Path $UserDataDir 'dorknet-clientmod.json' +$cfg = [ordered]@{ + ServerHost = $ServerHost + PhotonAppId = $PhotonAppId + PhotonVoiceAppId = if ($PhotonVoiceAppId) { $PhotonVoiceAppId } else { '' } + PhotonCloudRegion = $PhotonCloudRegion + InjectAuthValues = $true + EnableTlsTrustBypass = $true +} +Write-Step "Writing mod config -> $cfgPath" +[System.IO.File]::WriteAllText($cfgPath, ($cfg | ConvertTo-Json -Depth 4)) +Write-OK 'dorknet-clientmod.json written.' + +# ── 3. Byte patches via patch-client.ps1 ───────────────────────────── +if (-not $SkipBytePatches) { + if (-not (Test-Path -LiteralPath $PatchClient -PathType Leaf)) { + Write-Warn2 "patch-client.ps1 not found at $PatchClient — skipping byte patches." + } else { + Write-Step 'Running patch-client.ps1 (Photon AppIds, image signing, anti-cheat)' + $patchArgs = @{ + RecRoomPath = $RecRoomPath + PhotonAppId = $PhotonAppId + } + if ($PhotonVoiceAppId) { $patchArgs.PhotonVoiceAppId = $PhotonVoiceAppId } + if ($ImageSigningPublicKeyPath) { $patchArgs.ImageSigningPublicKeyPath = $ImageSigningPublicKeyPath } + # Seed $LASTEXITCODE so the post-call check works under StrictMode. + # PowerShell only populates $LASTEXITCODE after a native exe runs; + # a pure-cmdlet path through patch-client.ps1 leaves it undefined, + # and reading an undefined variable throws under + # Set-StrictMode -Version Latest. + $global:LASTEXITCODE = 0 + & $PatchClient @patchArgs + $patchExit = $LASTEXITCODE + if ($patchExit -ne 0) { + Write-Err "patch-client.ps1 returned exit code $patchExit" + exit $patchExit + } + } +} + +# ── 4a. Auto-bootstrap MelonLoader to generate Il2CppAssemblies ────── +# +# When MelonLoader extracts for the first time it ships no interop +# assemblies — they only appear once the game boots with the loader +# attached and Cpp2IL + Il2CppInterop.Generator run against +# GameAssembly.dll + il2cpp_data\Metadata\global-metadata.dat. +# Generation finishes a few seconds into startup (long before the +# game's own bootloader gets anywhere interesting), so we can launch +# the .exe, poll for the marker DLL, then kill the process. +# +# Anchor marker: Assembly-CSharp.dll inside Il2CppAssemblies\. That's +# the last assembly the generator writes (after Il2Cppmscorlib + +# Il2CppSystem + all the Unity proxies), so seeing it on disk means +# the rest are already in place. +# +# Caveats called out in the user-facing log line on failure: +# * if Recroom_Release.exe is wrapped with Steam DRM (.bind section) +# it'll silently refuse to launch outside Steam; the user has +# either to keep Steam running OR strip DRM via Steamless first +# (see install-plugin.ps1's Invoke-DrmStrip for that path). +# * anti-cheat WOULD interfere here, but patch-client.ps1 above +# just neutralised it before this step runs. +function Invoke-MelonBootstrap { + param([string]$ExePath, [string]$Il2CppAsmsDir) + + if (-not (Test-Path -LiteralPath $ExePath -PathType Leaf)) { + Write-Err "Recroom_Release.exe not found at $ExePath" + return $false + } + $exeDir = Split-Path -Parent $ExePath + foreach ($required in @( + (Join-Path $exeDir 'version.dll'), + (Join-Path $exeDir 'MelonLoader\Dependencies\Bootstrap.dll'), + (Join-Path $exeDir 'MelonLoader\Dependencies\dobby.dll') + )) { + if (-not (Test-Path -LiteralPath $required -PathType Leaf)) { + Write-Err "MelonLoader native dependency missing: $required" + Write-Host ' Re-run this script without -ResumeBuild, or use -ForceMelonLoader to re-extract MelonLoader.' -ForegroundColor DarkGray + return $false + } + } + + Write-Step "Auto-bootstrap: launching Recroom_Release.exe to generate Il2CppAssemblies" + Write-Host ' (the game window will open briefly, then get killed once the proxy' -ForegroundColor DarkGray + Write-Host ' assemblies are on disk. Boot to the watch is NOT required.)' -ForegroundColor DarkGray + + $proc = $null + try { + $proc = Start-Process -FilePath $ExePath -WorkingDirectory $exeDir -PassThru -ErrorAction Stop + } catch { + Write-Err "Failed to launch the game: $($_.Exception.Message)" + return $false + } + + $marker = Join-Path $Il2CppAsmsDir 'Assembly-CSharp.dll' + $timeoutSec = 180 + $pollSec = 2 + $start = Get-Date + + while ($true) { + if ($proc.HasExited) { + Write-Err "Game process exited (code $($proc.ExitCode)) before generating assemblies." + Write-Host ' Common causes: Steam DRM wrapper rejects standalone launch, MelonLoader proxy' -ForegroundColor DarkGray + Write-Host ' DLL got quarantined by Defender, or the game crashed on boot.' -ForegroundColor DarkGray + return $false + } + if (Test-Path -LiteralPath $marker -PathType Leaf) { + # Give Cpp2IL/Il2CppInterop a few extra seconds to finish + # writing the last of the proxy DLLs before we yank the process. + # The marker shows up partway through the generator run; the + # rest of the writes are quick but not instant. + Start-Sleep -Seconds 6 + break + } + $elapsed = (Get-Date) - $start + if ($elapsed.TotalSeconds -gt $timeoutSec) { + Write-Warn2 ("Timed out after {0:n0}s waiting for {1}." -f $timeoutSec, $marker) + try { Stop-Process -Id $proc.Id -Force -ErrorAction SilentlyContinue } catch {} + return $false + } + Start-Sleep -Seconds $pollSec + } + + try { + Stop-Process -Id $proc.Id -Force -ErrorAction SilentlyContinue + # Wait a beat for filesystem handles to release. + Start-Sleep -Seconds 1 + } catch {} + Write-OK "Il2CppAssemblies generated; game process closed." + return $true +} + +if (-not (Test-Path -LiteralPath $Il2CppAsmsDir -PathType Container) -and $AutoBootstrap -and -not $ResumeBuild) { + # Strip Steam DRM first if the .exe is wrapped. The launch in + # Invoke-MelonBootstrap is detached from Steam, so the SteamStub + # trampoline would reject with exit code 1 (same symptom the user + # gets if they double-click outside Steam too — MelonLoader's + # proxy DLL never gets loaded, no Il2CppAssemblies generated, and + # MelonLoader's first-launch state stays incomplete). + if (Test-SteamDrmWrapped -ExePath $RecRoomExe) { + Write-Warn2 "Recroom_Release.exe is wrapped with Steam DRM." + Write-Step "Stripping DRM so MelonLoader can launch the .exe directly." + try { + Invoke-DrmStrip -ExePath $RecRoomExe + } catch { + Write-Err "DRM strip failed: $($_.Exception.Message)" + Write-Host ' Falling back to the manual two-step flow. Either run' -ForegroundColor DarkGray + Write-Host ' tools\install-plugin.ps1 -InstallBepInEx on this same .exe' -ForegroundColor DarkGray + Write-Host ' (it has the same Steamless logic) or launch the game from Steam' -ForegroundColor DarkGray + Write-Host ' once and re-run with -ResumeBuild.' -ForegroundColor DarkGray + } + } else { + Write-OK "Recroom_Release.exe is already DRM-stripped." + } + + if (-not (Invoke-MelonBootstrap -ExePath $RecRoomExe -Il2CppAsmsDir $Il2CppAsmsDir)) { + Write-Warn2 'Auto-bootstrap failed. Falling through to the manual two-step flow.' + } +} + +# ── 4b. Build DorkNet.ClientMod against Il2CppAssemblies ───────────── +if (-not (Test-Path -LiteralPath $Il2CppAsmsDir -PathType Container)) { + Write-Warn2 "Il2CppAssemblies not generated yet at $Il2CppAsmsDir" + Write-Host '' + Write-Host ' MelonLoader needs ONE successful launch to generate the IL2CPP proxy' -ForegroundColor Yellow + Write-Host ' assemblies the mod builds against. Steps:' -ForegroundColor Yellow + Write-Host '' + Write-Host ' 1. Launch Recroom_Release.exe (just open the game and let it boot' -ForegroundColor Yellow + Write-Host ' to the watch UI; MelonLoader will dump assemblies in the' -ForegroundColor Yellow + Write-Host ' background).' -ForegroundColor Yellow + Write-Host ' 2. Close the game.' -ForegroundColor Yellow + Write-Host ' 3. Re-run this script with -ResumeBuild to finish the mod build.' -ForegroundColor Yellow + Write-Host '' + Write-Host ' (The byte patches and mod config are already in place; only the' -ForegroundColor DarkGray + Write-Host ' compiled mod DLL is missing.)' -ForegroundColor DarkGray + exit 0 +} + +if (-not (Test-Path -LiteralPath $ModCsproj -PathType Leaf)) { + Write-Err "DorkNet.ClientMod csproj not found at $ModCsproj" + exit 1 +} + +Write-Step "Building DorkNet.ClientMod (interop=$Il2CppAsmsDir)" +$buildOutput = & dotnet build $ModCsproj -c Release --nologo ` + -p:MelonLoaderDir=$MelonLoaderDir ` + -p:Il2CppAssembliesDir=$Il2CppAsmsDir 2>&1 +if ($LASTEXITCODE -ne 0) { + Write-Err 'DorkNet.ClientMod build failed. Build output:' + Write-Host ($buildOutput -join "`n") -ForegroundColor DarkGray + exit 1 +} +$builtDll = Join-Path $ModProj 'bin\Release\DorkNet.ClientMod.dll' +if (-not (Test-Path -LiteralPath $builtDll -PathType Leaf)) { + Write-Err "Build succeeded but $builtDll not found." + exit 1 +} + +# ── 5. Copy mod DLL into Mods/ ─────────────────────────────────────── +Copy-Item -LiteralPath $builtDll -Destination $ModsDir -Force +Write-OK ("Mod DLL copied -> " + (Join-Path $ModsDir 'DorkNet.ClientMod.dll')) + +# ── 6. Summary ─────────────────────────────────────────────────────── +Write-Host '' +Write-OK 'Install complete.' +Write-Host (' MelonLoader : ' + $MelonLoaderDir) -ForegroundColor DarkGray +Write-Host (' Mods folder : ' + $ModsDir) -ForegroundColor DarkGray +Write-Host (' Mod config : ' + $cfgPath) -ForegroundColor DarkGray +Write-Host (' Photon AppId : ' + $PhotonAppId) -ForegroundColor DarkGray +Write-Host (' Server host : ' + $ServerHost) -ForegroundColor DarkGray +Write-Host '' +Write-Host ' Launch Recroom_Release.exe. MelonLoader log appears in:' -ForegroundColor DarkGray +Write-Host (' ' + (Join-Path $MelonLoaderDir 'Latest.log')) -ForegroundColor DarkGray diff --git a/tools/install-plugin.ps1 b/tools/install-plugin.ps1 new file mode 100644 index 0000000..cc0050f --- /dev/null +++ b/tools/install-plugin.ps1 @@ -0,0 +1,754 @@ +<# +.SYNOPSIS + BepInEx-only setup for DorkNet ClientPatch. + +.DESCRIPTION + Replaces the legacy patch-client.ps1 (asset-byte-edit + hosts file + + mkcert wildcard). Now everything lives in the + DorkNet.ClientPatch BepInEx plugin: Photon AppId override, *.rec.net + → user-configured-host URL rewrite, and Photon CustomAuth identity + injection. + + After this script runs once successfully, all you need to do on a + client version update is download the new build and re-run + this script — it'll re-extract BepInEx and rebuild the plugin + against the new game's interop assemblies. No more hardcoded + resources.assets offsets, no more hosts file, no more wildcard + cert. + +.PARAMETER RecRoomPath + Path to the Recroom_Release_Data folder (the one containing + resources.assets). The patcher uses this to locate the actual + install root (its parent directory). + +.PARAMETER BepInExZip + Optional path to a BepInEx 6.x IL2CPP zip. Only needed if you want + to override the pinned build the script auto-downloads. On first + install (or when -ForceBepInEx is set), the script fetches a + pinned BepInEx build from builds.bepinex.dev and caches it under + %LOCALAPPDATA%\DorkNet so repeated runs don't re-download. Pass + this only if you need a specific (newer/older) build. + +.PARAMETER ServerHost + Domain that replaces *.rec.net in every URL the watch builds. + Default "localhost" — set to your own apex if hosting elsewhere. + +.PARAMETER PhotonAppId + Photon Cloud Realtime AppId (GUID without curly braces). + +.PARAMETER PhotonVoiceAppId + Photon Cloud Voice AppId. Defaults to PhotonAppId. + +.PARAMETER ForceBepInEx + Re-extract BepInEx even if it's already installed. Use after a client + update. + +.PARAMETER Revert + Remove BepInEx + the plugin from the client install. Doesn't + touch the game's own files. + +.EXAMPLE + # First-time install — script auto-downloads BepInEx + .\tools\install-plugin.ps1 ` + -RecRoomPath "C:\Program Files (x86)\Steam\steamapps\common\RecRoom\Recroom_Release_Data" ` + -PhotonAppId + +.EXAMPLE + # Override the pinned BepInEx build with your own zip + .\tools\install-plugin.ps1 ` + -RecRoomPath "C:\…\Recroom_Release_Data" ` + -BepInExZip "C:\Downloads\BepInEx-Unity.IL2CPP-win-x64-6.0.0-be.NNN.zip" ` + -PhotonAppId cb0880d9-… + +.EXAMPLE + # After a client update (interop changed) — re-run with + # the same args, optionally adding -ForceBepInEx if BepInEx itself + # got clobbered. + .\tools\install-plugin.ps1 ` + -RecRoomPath "C:\…\Recroom_Release_Data" ` + -PhotonAppId cb0880d9-… + +.EXAMPLE + # Remove the plugin + .\tools\install-plugin.ps1 -RecRoomPath D:\RecRoom\Recroom_Release_Data -Revert +#> + +[CmdletBinding(DefaultParameterSetName = 'Install')] +param( + [Parameter(Mandatory = $true)] + [string]$RecRoomPath, + + [Parameter(ParameterSetName = 'Install')] + [string]$BepInExZip, + + [Parameter(ParameterSetName = 'Install')] + [string]$ServerHost = 'localhost', + + [Parameter(Mandatory = $true, ParameterSetName = 'Install')] + [ValidatePattern('^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$')] + [string]$PhotonAppId, + + [Parameter(ParameterSetName = 'Install')] + [ValidatePattern('^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$')] + [string]$PhotonVoiceAppId, + + [Parameter(ParameterSetName = 'Install')] + [switch]$ForceBepInEx, + + # Tell the plugin to fake the Steamworks SDK so the watch boots + # without Steam.exe running. Pure C#-level Harmony patches inside + # the plugin DLL — no replacement steam_api64.dll, no Goldberg / + # SmartSteamEmu (which Defender flags as PUA). Default off so + # Steam-launched runs behave normally. + [Parameter(ParameterSetName = 'Install')] + [switch]$FakeSteamApi, + + # SteamID64 the plugin should report from SteamUser.GetSteamID + # when -FakeSteamApi is on. Empty = deterministic per-machine ID + # derived from OS user + machine name. Pin an explicit value to + # keep the same DorkNet account across machines. + [Parameter(ParameterSetName = 'Install')] + [string]$FakeSteamId, + + # Display name returned from SteamFriends.GetPersonaName when + # -FakeSteamApi is on. Empty = OS user name. + [Parameter(ParameterSetName = 'Install')] + [string]$FakeAccountName, + + # RSA public modulus, base64 encoded, that replaces the client's + # embedded image-signing key. Defaults to the public key derived + # from DorkNet.Server/appsettings.Local.json when that file has + # ImageSigning:PrivateKeyPem. + [Parameter(ParameterSetName = 'Install')] + [string]$ImageSigningPublicKeyBase64, + + # PEM private key to derive the image-signing public modulus from. + # Use this when Dokploy/prod signs images with a key that differs + # from appsettings.Local.json. + [Parameter(ParameterSetName = 'Install')] + [string]$ImageSigningPrivateKeyPath, + + [Parameter(Mandatory = $true, ParameterSetName = 'Revert')] + [switch]$Revert +) + +$ErrorActionPreference = 'Stop' +Set-StrictMode -Version Latest + +# Pinned BepInEx 6 IL2CPP win-x64 build. Auto-downloaded when neither +# BepInEx is already installed nor -BepInExZip is provided. Pin a +# specific build rather than 'latest' so a regression upstream +# doesn't silently land on tester machines. +$Script:BepInExUrl = 'https://builds.bepinex.dev/projects/bepinex_be/755/BepInEx-Unity.IL2CPP-win-x64-6.0.0-be.755%2B3fab71a.zip' +$Script:BepInExCacheName = 'BepInEx-Unity.IL2CPP-win-x64-6.0.0-be.755.zip' + +# Pinned Steamless release. atom0s ships it as a single zip with a +# CLI exe inside. Cached alongside BepInEx under %LOCALAPPDATA%\DorkNet +# so a one-time download per machine handles both DRM-stripping and +# mod loading. +$Script:SteamlessUrl = 'https://github.com/atom0s/Steamless/releases/download/v3.1.0.5/Steamless.v3.1.0.5.-.by.atom0s.zip' +$Script:SteamlessCacheName = 'Steamless.v3.1.0.5.zip' + +# 2020.12 image-signing public keys embedded in OHDHPENHDAP. The watch +# chooses p1 for non-localhost hosts and d1 for localhost, then expects +# Content-Signature: key-id=KEY:RSA:.; data=. +# DorkNet signs with its own private key, so stock clients must have both +# embedded moduli replaced with the server key's public modulus. +$Script:OriginalImageSigningP1Modulus = 'X07yXkxaaLcZ1wVXfkWjgFkkqdoLhDFm0GPODsF+Q47pSUlbLvtXGqStnyEJEIrQmgDiicAvCdGRq4lovr2l5sIPMaoyizsbVHBdwLUrCsji0RvSBnmvN+8KqQ8STnB4DP4pAsPilfD35def4WuX/xMCXB5+hQUVhv27HPV8Dj9XzHuJAijIM9UwDZmvUcECyiO4wv+TaZi2+ELBtaLCQR8Gm1ZPeDEwP62Ch6MJy0jx5pkvvD0KdF9Wye+3/Wx31Zn/Trdo9HL4sGFWPDM9H9kQhZd5wkTHuxpwGIIhlzIwvY2/pBGdZKP6fi1D2jROEmVkBDyhmYY9nO+s3/bndQ==' +$Script:OriginalImageSigningD1Modulus = 'htW8AsNuy5E+nBkukGpKGInje8YotC7yHekeuotwMzDiCdR5jU69H9MN9r+Dplp/g0Pz1cXNPD+PX/aGgtiXaKTBjPzTcedQCsnkDIA4V6Y5OKMbrM84x7VNUaFWC1GoCLOraTy5RA3jnMUa2bPX1CAOaRYAKbBd65T76b3GR+oI4LRuua6zxe8tbZ+RKGS9ktNuZonPcKrcAQNrEN2E+z0ig9ls+z0EE3H6ufl48N1ix53ROwTOG3DPNwqp+w+n5oE2KbdL2V3MgS6TD+C1wUaPJI1//8UEzJs6ItNoFuez5PaBr3y9P/CyKVyGQRzTS82A/8gDEvOdEC4ZKCbKQQ==' + +# NOTE: Goldberg Steam Emu integration was tried and removed. +# Windows Defender flags every Steam emulator as PUA and +# quarantines mid-download. The "boot without Steam running" +# capability now lives entirely inside DorkNet.ClientPatch — +# Harmony patches on the Steamworks.NET C# wrappers +# (SteamAPI.Init, SteamUser.GetSteamID, GetAuthSessionTicket, …) +# so the watch's SteamPlatformManager.Initialize succeeds without +# Steam.exe running. Toggled via the -FakeSteamApi switch on this +# script (writes the [Steam] FakeSteamApi=true config the plugin +# reads at load). + +function Write-Step($msg) { Write-Host ('▸ ' + $msg) -ForegroundColor Cyan } +function Write-OK($msg) { Write-Host ('✓ ' + $msg) -ForegroundColor Green } +function Write-Warn2($msg) { Write-Host ('! ' + $msg) -ForegroundColor Yellow } +function Write-Err($msg) { Write-Host ('✗ ' + $msg) -ForegroundColor Red } + +function Find-AsciiOffsets { + param([byte[]]$Bytes, [string]$Needle) + + $hits = New-Object System.Collections.Generic.List[int] + if ([string]::IsNullOrEmpty($Needle)) { return @() } + $pattern = [System.Text.Encoding]::ASCII.GetBytes($Needle) + for ($i = 0; $i -le $Bytes.Length - $pattern.Length; $i++) { + if ($Bytes[$i] -ne $pattern[0]) { continue } + $ok = $true + for ($j = 1; $j -lt $pattern.Length; $j++) { + if ($Bytes[$i + $j] -ne $pattern[$j]) { $ok = $false; break } + } + if ($ok) { $hits.Add($i) } + } + return @($hits) +} + +function Get-ImageSigningPublicModulus { + param( + [string]$RepoRoot, + [string]$PublicKeyBase64, + [string]$PrivateKeyPath + ) + + if ($PublicKeyBase64 -and $PrivateKeyPath) { + Write-Err 'Use only one of -ImageSigningPublicKeyBase64 or -ImageSigningPrivateKeyPath.' + exit 1 + } + + if ($PublicKeyBase64) { return $PublicKeyBase64.Trim() } + + $privatePem = $null + if ($PrivateKeyPath) { + $resolvedKey = Resolve-Path -LiteralPath $PrivateKeyPath -ErrorAction SilentlyContinue + if (-not $resolvedKey) { + Write-Err "Image signing private key '$PrivateKeyPath' not found." + exit 1 + } + $privatePem = Get-Content -LiteralPath $resolvedKey.Path -Raw + } else { + $localSettings = Join-Path $RepoRoot 'DorkNet.Server\appsettings.Local.json' + if (Test-Path -LiteralPath $localSettings -PathType Leaf) { + try { + $json = Get-Content -LiteralPath $localSettings -Raw | ConvertFrom-Json + if ($json.PSObject.Properties['ImageSigning'] -and + $json.ImageSigning.PSObject.Properties['PrivateKeyPem']) { + $privatePem = [string]$json.ImageSigning.PrivateKeyPem + } + } catch { + Write-Warn2 "Could not read ImageSigning:PrivateKeyPem from $localSettings: $($_.Exception.Message)" + } + } + } + + if ([string]::IsNullOrWhiteSpace($privatePem)) { return $null } + + $rsa = [System.Security.Cryptography.RSA]::Create() + try { + $rsa.ImportFromPem($privatePem) + return [Convert]::ToBase64String($rsa.ExportParameters($false).Modulus) + } catch { + Write-Warn2 "Could not derive image-signing public key from configured private key: $($_.Exception.Message)" + return $null + } finally { + $rsa.Dispose() + } +} + +function Invoke-ImageSigningPublicKeyPatch { + param( + [string]$DataDir, + [string]$RecRoomRoot, + [string]$PublicModulusBase64 + ) + + if ([string]::IsNullOrWhiteSpace($PublicModulusBase64)) { + Write-Warn2 'No image-signing public key configured; image Content-Signature verification may fail in-game.' + return + } + $PublicModulusBase64 = $PublicModulusBase64.Trim() + if ($PublicModulusBase64.Length -ne $Script:OriginalImageSigningP1Modulus.Length) { + Write-Err "Image signing public modulus must be $($Script:OriginalImageSigningP1Modulus.Length) base64 chars for the in-place client patch." + exit 1 + } + + $meta = Join-Path $DataDir 'il2cpp_data\Metadata\global-metadata.dat' + $gameAssembly = Join-Path $RecRoomRoot 'GameAssembly.dll' + $candidates = @($meta, $gameAssembly) | Where-Object { Test-Path -LiteralPath $_ -PathType Leaf } + if (-not $candidates) { + Write-Warn2 'No IL2CPP metadata or GameAssembly file found for image-signing key patch.' + return + } + + $replacement = [System.Text.Encoding]::ASCII.GetBytes($PublicModulusBase64) + $patchedFiles = 0 + $alreadyPatched = $false + foreach ($path in $candidates) { + $bytes = [System.IO.File]::ReadAllBytes($path) + $desiredHits = @(Find-AsciiOffsets -Bytes $bytes -Needle $PublicModulusBase64) + if ($desiredHits.Count -gt 0) { + $alreadyPatched = $true + } + + $offsets = New-Object System.Collections.Generic.List[int] + foreach ($needle in @($Script:OriginalImageSigningP1Modulus, $Script:OriginalImageSigningD1Modulus)) { + foreach ($hit in @(Find-AsciiOffsets -Bytes $bytes -Needle $needle)) { + if (-not $offsets.Contains($hit)) { $offsets.Add($hit) } + } + } + if ($offsets.Count -eq 0) { continue } + + Write-Step "Patching image signing public key in $path" + if ($path -ne $meta) { + $backup = "$path.dorknet-image-signing-original" + if (-not (Test-Path -LiteralPath $backup -PathType Leaf)) { + Copy-Item -LiteralPath $path -Destination $backup + } + } + + foreach ($offset in $offsets) { + [Array]::Copy($replacement, 0, $bytes, $offset, $replacement.Length) + } + [System.IO.File]::WriteAllBytes($path, $bytes) + $formatted = @($offsets | ForEach-Object { ('0x{0:x}' -f $_) }) -join ', ' + Write-OK "Image signing public key patched at $formatted" + $patchedFiles++ + } + + if ($patchedFiles -eq 0) { + if ($alreadyPatched) { + Write-OK 'Image signing public key already matches requested key.' + } else { + Write-Warn2 'Original image signing key was not found. This client may already be patched or may not be the 2020.12 IL2CPP build.' + } + } +} + +function Test-SteamDrmWrapped { + # Detects the Steamworks DRM wrapper by checking whether the PE's + # AddressOfEntryPoint falls inside a '.bind' section. Valve's + # drm_wrap tool injects .bind carrying the DRM stub and rewrites + # the entry point to land there; SteamStub jumps to the real + # .text once it's done. After Steamless strips, the entry point + # is rewritten back into .text and .bind is left as dead weight + # (the section header survives but execution never visits it), + # so we'd false-positive if we just looked at .bind's size. + # + # PE layout walked here (offsets relative to start of file): + # e_lfanew @ 0x3C → 4-byte offset to PE header + # PE+0 "PE\0\0" + COFF header (Sections @ +6, OptHeader size @ +20) + # OptionalHeader+0x10 = AddressOfEntryPoint (RVA) + # Section table starts at PE+24+OptHeaderSize, 40 bytes/entry: + # Name(8) VirtSize(4) VirtAddr(4) RawSize(4) RawPtr(4) ... + param([string]$ExePath) + try { + $fs = [System.IO.File]::OpenRead($ExePath) + try { + $br = New-Object System.IO.BinaryReader($fs) + $fs.Seek(0x3C, 'Begin') | Out-Null + $peOff = $br.ReadInt32() + if ($peOff -lt 0 -or $peOff + 24 -gt $fs.Length) { return $false } + $fs.Seek($peOff + 6, 'Begin') | Out-Null + $sections = $br.ReadUInt16() + $fs.Seek($peOff + 20, 'Begin') | Out-Null + $optSize = $br.ReadUInt16() + $fs.Seek($peOff + 24 + 16, 'Begin') | Out-Null + $entryRva = $br.ReadUInt32() + $secStart = $peOff + 24 + $optSize + for ($i = 0; $i -lt $sections; $i++) { + $fs.Seek($secStart + $i * 40, 'Begin') | Out-Null + $name = [System.Text.Encoding]::ASCII.GetString($br.ReadBytes(8)).TrimEnd([char]0) + $vSize = $br.ReadUInt32() + $vAddr = $br.ReadUInt32() + if ($name -eq '.bind' -and $entryRva -ge $vAddr -and $entryRva -lt ($vAddr + $vSize)) { + return $true + } + } + return $false + } finally { $fs.Close() } + } catch { + return $false + } +} + + +function Get-Steamless { + # Returns the path to a usable Steamless.CLI.exe. Downloads + extracts + # the pinned release on first run, caches under %LOCALAPPDATA%\DorkNet. + param() + $cacheDir = Join-Path $env:LOCALAPPDATA 'DorkNet' + $extractDir = Join-Path $cacheDir 'Steamless' + $cli = Join-Path $extractDir 'Steamless.CLI.exe' + if (Test-Path -LiteralPath $cli -PathType Leaf) { + return $cli + } + New-Item -ItemType Directory -Path $cacheDir -Force | Out-Null + $zipPath = Join-Path $cacheDir $Script:SteamlessCacheName + if (-not (Test-Path -LiteralPath $zipPath -PathType Leaf)) { + Write-Step "Downloading Steamless → $zipPath" + Write-Host " $Script:SteamlessUrl" -ForegroundColor DarkGray + try { + Invoke-WebRequest -Uri $Script:SteamlessUrl -OutFile $zipPath ` + -UseBasicParsing -TimeoutSec 60 -ErrorAction Stop + } catch { + Write-Err "Steamless download failed: $($_.Exception.Message)" + Write-Host ' Manually download from https://github.com/atom0s/Steamless/releases' -ForegroundColor DarkGray + Write-Host " and extract to $extractDir, then re-run." -ForegroundColor DarkGray + throw + } + } + Write-Step "Extracting Steamless → $extractDir" + Expand-Archive -LiteralPath $zipPath -DestinationPath $extractDir -Force + if (-not (Test-Path -LiteralPath $cli -PathType Leaf)) { + # Some Steamless zips put the CLI inside a subfolder; locate it. + $found = Get-ChildItem -LiteralPath $extractDir -Recurse -Filter 'Steamless.CLI.exe' -ErrorAction SilentlyContinue | Select-Object -First 1 + if ($found) { $cli = $found.FullName } + } + if (-not (Test-Path -LiteralPath $cli -PathType Leaf)) { + throw "Steamless.CLI.exe not found after extracting $zipPath" + } + return $cli +} + +function Invoke-DrmStrip { + # Runs Steamless against the wrapped exe in-place. Steamless writes + # an unpacked copy alongside the original; we move the original to + # .drm-original.exe and rename the unpacked file in place so the + # rest of the script doesn't have to know which is which. + param([string]$ExePath) + $cli = Get-Steamless + Write-Step "Stripping Steam DRM from $ExePath" + $exeDir = Split-Path -Parent $ExePath + # Steamless emits .exe.unpacked.exe by default. Run with + # --quiet to keep our log clean; --keepbind 0 lets it strip the + # .bind section so subsequent runs don't re-trigger the detector. + $proc = Start-Process -FilePath $cli ` + -ArgumentList @('--quiet', '--keepbind', '0', $ExePath) ` + -NoNewWindow -PassThru -Wait + if ($proc.ExitCode -ne 0) { + throw "Steamless exited with code $($proc.ExitCode). The exe may not be a recognised Steam DRM build." + } + $unpacked = "$ExePath.unpacked.exe" + if (-not (Test-Path -LiteralPath $unpacked -PathType Leaf)) { + throw "Steamless ran but didn't produce $unpacked. Check the tool's output." + } + $bak = Join-Path $exeDir 'Recroom_Release.drm-original.exe' + Move-Item -LiteralPath $ExePath -Destination $bak -Force + Move-Item -LiteralPath $unpacked -Destination $ExePath -Force + Write-OK "DRM stripped. Original kept as $bak" +} + +function Get-BepInExZip { + # Returns a path to a usable BepInEx zip. Reuses a cached download + # under %LOCALAPPDATA%\DorkNet so a repeated install on the same + # box doesn't re-download the ~9 MB blob each run. + param() + $cacheDir = Join-Path $env:LOCALAPPDATA 'DorkNet' + New-Item -ItemType Directory -Path $cacheDir -Force | Out-Null + $cached = Join-Path $cacheDir $Script:BepInExCacheName + if (Test-Path -LiteralPath $cached -PathType Leaf) { + Write-OK "Using cached BepInEx zip → $cached" + return $cached + } + Write-Step "Downloading BepInEx → $cached" + Write-Host " $Script:BepInExUrl" -ForegroundColor DarkGray + try { + # -UseBasicParsing for compatibility with Windows PowerShell 5.1 + # (PS7 ignores it). 90-second timeout — the file is small but a + # cold builds.bepinex.dev cdn can be sluggish from some regions. + Invoke-WebRequest -Uri $Script:BepInExUrl -OutFile $cached ` + -UseBasicParsing -TimeoutSec 90 -ErrorAction Stop + } catch { + Write-Err "Download failed: $($_.Exception.Message)" + Write-Host ' Manually download the zip and pass it via -BepInExZip .' -ForegroundColor DarkGray + Write-Host " Releases page: https://github.com/BepInEx/BepInEx/releases" -ForegroundColor DarkGray + throw + } + $size = (Get-Item -LiteralPath $cached).Length + Write-OK ("Downloaded {0:N1} MB" -f ($size / 1MB)) + return $cached +} + +# Resolve via a two-step assignment instead of the ?. null-conditional +# operator — that's PS 7+ only and we want to support Windows +# PowerShell 5.1 (still the default on stock Win10/11). +$resolved = Resolve-Path -LiteralPath $RecRoomPath -ErrorAction SilentlyContinue +if ($resolved) { $RecRoomPath = $resolved.Path } else { $RecRoomPath = $null } +if (-not $RecRoomPath -or -not (Test-Path -LiteralPath $RecRoomPath -PathType Container)) { + Write-Err "RecRoomPath '$RecRoomPath' does not exist." + exit 1 +} + +# RecRoomPath = …\Recroom_Release_Data, BepInEx lives one level up +$RecRoomRoot = Split-Path -Parent $RecRoomPath +$BepInExDir = Join-Path $RecRoomRoot 'BepInEx' +$ConfigDir = Join-Path $BepInExDir 'config' +$PluginsDir = Join-Path $BepInExDir 'plugins' +$InteropDir = Join-Path $BepInExDir 'interop' +$RepoRoot = Split-Path -Parent $PSScriptRoot +$PluginProj = Join-Path $RepoRoot 'DorkNet.ClientPatch' + +if ($Revert) { + Write-Step "Removing BepInEx + plugin from $RecRoomRoot" + if (Test-Path -LiteralPath $BepInExDir -PathType Container) { + Remove-Item -LiteralPath $BepInExDir -Recurse -Force + Write-OK "Removed $BepInExDir" + } + foreach ($leaf in @('winhttp.dll', 'doorstop_config.ini', '.doorstop_version', 'changelog.txt')) { + $p = Join-Path $RecRoomRoot $leaf + if (Test-Path -LiteralPath $p -PathType Leaf) { + Remove-Item -LiteralPath $p -Force + Write-OK "Removed $leaf" + } + } + # Restore the pristine global-metadata.dat from the backup we made + # before host-patching, so the watch points at *.rec.net again. + $meta = Join-Path $RecRoomPath 'il2cpp_data\Metadata\global-metadata.dat' + $backup = $meta + '.dorknet-original' + if (Test-Path -LiteralPath $backup -PathType Leaf) { + Copy-Item -LiteralPath $backup -Destination $meta -Force + Remove-Item -LiteralPath $backup -Force + Write-OK 'Restored stock global-metadata.dat from backup.' + } + Write-OK 'Plugin reverted. Rec Room is now stock.' + exit 0 +} + +if (-not (Test-Path -LiteralPath $PluginProj -PathType Container)) { + Write-Err "Plugin project missing at '$PluginProj'. Run from a DorkNet repo checkout." + exit 1 +} + +# steam_appid.txt — Steamworks SDK uses this file as "app id is +# pre-configured, don't relaunch via Steam." Without it, even a +# DRM-stripped binary still calls SteamAPI.RestartAppIfNecessary +# at startup and gets handed back to Steam.exe before the .NET +# payload (and therefore our plugin) has a chance to run. Drop it +# next to the exe before we test the DRM-strip path. +$AppIdFile = Join-Path $RecRoomRoot 'steam_appid.txt' +if (-not (Test-Path -LiteralPath $AppIdFile -PathType Leaf)) { + '471710' | Out-File -LiteralPath $AppIdFile -Encoding ASCII -NoNewline + Write-OK "Wrote $AppIdFile (Rec Room AppID 471710 — skips Steam relaunch on boot)" +} + +# DRM check + auto-strip — Steamworks DRM stub runs before BepInEx's +# winhttp.dll doorstop fires, so a stock Steam-wrapped exe would just +# exit and ask Steam to relaunch (BepInEx + plugin never get a chance). +# Detect via .bind entry-point check, then run Steamless against it. +# Original kept as Recroom_Release.drm-original.exe. +$RecRoomExe = Join-Path $RecRoomRoot 'Recroom_Release.exe' +if (Test-Path -LiteralPath $RecRoomExe -PathType Leaf) { + if (Test-SteamDrmWrapped -ExePath $RecRoomExe) { + Write-Step 'Steam DRM wrapper detected on Recroom_Release.exe — stripping with Steamless' + Invoke-DrmStrip -ExePath $RecRoomExe + # Sanity-check: if Steamless somehow left .bind in place, bail. + if (Test-SteamDrmWrapped -ExePath $RecRoomExe) { + Write-Err 'Stripped exe still reports a .bind section — Steamless may not support this build.' + Write-Host ' Manually strip with the Steamless GUI and re-run this script.' -ForegroundColor DarkGray + exit 1 + } + Write-OK 'Recroom_Release.exe is now DRM-stripped.' + } else { + Write-OK 'Recroom_Release.exe is DRM-stripped (no .bind section).' + } +} + +# Steam-without-Steam.exe handling now lives inside the plugin via +# the [Steam] FakeSteamApi=true config — no DLL replacement, no +# Goldberg AV trip. We just write the config in step 4 below. + +# ── 0b. Patch ns.rec.net / www.rec.net in IL2CPP global metadata ────────── +# The watch hard-codes the bootstrap URL "https://ns.rec.net/?v=2" as a +# string literal in IL2CPP global-metadata.dat. It builds the URL via +# BestHTTP.HTTPRequest directly with the raw string — never goes through +# `new System.Uri(string)` — so the plugin's URL-rewrite Harmony patch +# can't see it. Same for the WWW link "www.rec.net". Both literals are +# the same length as their localhost equivalents, so we can byte-replace +# in place without shifting offsets. +# +# This is the ONLY hardcoded-byte edit the install does, and it's +# auto-detected & re-applied on every install run. The original file +# is preserved as `global-metadata.dat.dorknet-original` so -Revert +# can put it back. +function Invoke-MetadataHostPatch { + param([string]$DataDir, [string]$ServerHost) + $meta = Join-Path $DataDir 'il2cpp_data\Metadata\global-metadata.dat' + if (-not (Test-Path -LiteralPath $meta -PathType Leaf)) { + Write-Warn2 "global-metadata.dat not found at $meta — skipping host byte-patch." + return + } + $backup = $meta + '.dorknet-original' + if (-not (Test-Path -LiteralPath $backup -PathType Leaf)) { + Copy-Item -LiteralPath $meta -Destination $backup + } + # Always patch from the pristine backup so re-runs are idempotent and + # don't accidentally compound substitutions if the host changes. + $bytes = [System.IO.File]::ReadAllBytes($backup) + $subs = @( + @{ Old = 'ns.rec.net'; New = "ns.$ServerHost" }, + @{ Old = 'www.rec.net'; New = "www.$ServerHost" } + ) + $totalReplacements = 0 + foreach ($s in $subs) { + $oldB = [System.Text.Encoding]::ASCII.GetBytes($s.Old) + $newB = [System.Text.Encoding]::ASCII.GetBytes($s.New) + if ($oldB.Length -ne $newB.Length) { + Write-Err "Host '$($s.Old)' (len $($oldB.Length)) and '$($s.New)' (len $($newB.Length)) differ in length — refusing to patch." + return + } + $i = 0 + while ($i -le $bytes.Length - $oldB.Length) { + $match = $true + for ($k = 0; $k -lt $oldB.Length; $k++) { + if ($bytes[$i + $k] -ne $oldB[$k]) { $match = $false; break } + } + if ($match) { + [Array]::Copy($newB, 0, $bytes, $i, $newB.Length) + $totalReplacements++ + $i += $newB.Length + } else { + $i++ + } + } + } + [System.IO.File]::WriteAllBytes($meta, $bytes) + Write-OK "global-metadata.dat host-patched ($totalReplacements substitution$( if ($totalReplacements -ne 1) { 's' }))." +} +Invoke-MetadataHostPatch -DataDir $RecRoomPath -ServerHost $ServerHost + +# ── 1. BepInEx install ──────────────────────────────────────────────────── +$bepInExPresent = Test-Path -LiteralPath $BepInExDir -PathType Container +if (-not $bepInExPresent -or $ForceBepInEx) { + # Auto-fetch the pinned build when the operator didn't supply one. + # First-run UX: just `install-plugin.ps1 -RecRoomPath ... -PhotonAppId ...` + # works without the operator having to manually download a zip. + if (-not $BepInExZip) { + $BepInExZip = Get-BepInExZip + } + if (-not (Test-Path -LiteralPath $BepInExZip -PathType Leaf)) { + Write-Err "BepInExZip '$BepInExZip' not found." + exit 1 + } + if ($ForceBepInEx -and $bepInExPresent) { + Write-Step "Removing existing BepInEx install (-ForceBepInEx)" + Remove-Item -LiteralPath $BepInExDir -Recurse -Force + } + Write-Step "Extracting BepInEx → $RecRoomRoot" + Expand-Archive -LiteralPath $BepInExZip -DestinationPath $RecRoomRoot -Force + Write-OK 'BepInEx extracted.' +} else { + Write-OK "BepInEx already installed at $BepInExDir" +} + +# ── 2. Interop assemblies ───────────────────────────────────────────────── +if (-not (Test-Path -LiteralPath $InteropDir -PathType Container)) { + Write-Warn2 'Interop assemblies not found.' + Write-Host ' Launch the client ONCE so BepInEx generates them, then re-run this script.' -ForegroundColor DarkGray + Write-Host ' (One-time step per game version — takes ~30s on first launch.)' -ForegroundColor DarkGray + exit 0 +} +Write-OK "Interop assemblies present at $InteropDir" + +# ── 3. Build plugin ─────────────────────────────────────────────────────── +$env:BepInExInterop = $InteropDir +$env:BepInExCore = Join-Path $BepInExDir 'core' +Write-Step 'Building DorkNet.ClientPatch' +$build = & dotnet build $PluginProj -c Release --nologo 2>&1 +if ($LASTEXITCODE -ne 0) { + Write-Err 'Plugin build failed:' + Write-Host ($build -join "`n") -ForegroundColor DarkGray + exit 1 +} +$pluginDll = Join-Path $PluginProj 'bin\Release\DorkNet.ClientPatch.dll' +if (-not (Test-Path -LiteralPath $pluginDll -PathType Leaf)) { + Write-Err "Plugin DLL not produced at $pluginDll" + exit 1 +} +New-Item -ItemType Directory -Path $PluginsDir -Force | Out-Null +$oldPluginDll = Join-Path $PluginsDir ('DorkNet.' + 'Rec' + 'RoomPatch.dll') +if (Test-Path -LiteralPath $oldPluginDll -PathType Leaf) { + Remove-Item -LiteralPath $oldPluginDll -Force + Write-OK 'Removed old client patch plugin.' +} +Copy-Item -LiteralPath $pluginDll -Destination $PluginsDir -Force +Write-OK ('Plugin DLL → ' + (Join-Path $PluginsDir 'DorkNet.ClientPatch.dll')) + +# ── 4. Plugin config ────────────────────────────────────────────────────── +# Write the BepInEx config file with the operator's chosen values. +# Format mirrors what the plugin's Config.Bind defaults produce on first +# run, so editing afterwards via BepInEx config manager works as expected. +$cfgFile = Join-Path $ConfigDir 'sh.dork.clientpatch.cfg' +New-Item -ItemType Directory -Path $ConfigDir -Force | Out-Null + +$voiceId = if ($PhotonVoiceAppId) { $PhotonVoiceAppId } else { '' } +$cfg = @" +## Settings file was created by DorkNet install-plugin.ps1 +## Plugin GUID: sh.dork.clientpatch + +[Server] + +## Domain that replaces *.rec.net in every URL the watch builds. +## Empty disables the rewrite (use only if pointing at the official servers). +# Setting type: String +# Default value: localhost +Host = $ServerHost + +[Photon] + +## Photon Cloud Realtime AppId (UUID, no curly braces). +## Empty leaves the AppId baked into the build untouched. +# Setting type: String +# Default value: +AppId = $PhotonAppId + +## Photon Cloud Voice AppId. Empty falls back to AppId. +# Setting type: String +# Default value: +VoiceAppId = $voiceId + +## Attach userid + LoginLock to PhotonNetwork.AuthValues right before +## OpAuthenticate so DorkNet's /photon/customauth endpoint can identify +## the player. Disable to debug pure Photon connectivity issues. +# Setting type: Boolean +# Default value: true +InjectAuthValues = true + +[UI] + +## Replacement branding string. Any UI label containing 'RecNet' +## gets that substring replaced with this value at runtime, so error +## dialogs and the loading screen show the private-server brand. +## Empty disables the rewrite. +# Setting type: String +# Default value: DorkNet +BrandName = DorkNet + +[Steam] + +## Bypass Steamworks.SteamAPI.RestartAppIfNecessary so the watch +## doesn't relaunch itself through Steam on boot. No effect when +## launched normally from the Steam library; useful when running the +## game directly via Recroom_Release.exe (BepInEx mod-loader shims +## often need this to avoid an infinite restart loop). +# Setting type: Boolean +# Default value: true +SkipRestartAppIfNecessary = true + +## Pretend the Steam SDK initialised even when Steam.exe isn't +## running. Patches Steamworks.NET wrappers to return canned success +## values so the watch's SteamPlatformManager.Initialize succeeds +## without a live Steam process. DorkNet's auth backend uses SteamID +## as a lookup key only — no real ticket validation — so the fake +## ticket is accepted. Default off so launches via Steam itself +## behave normally. +# Setting type: Boolean +# Default value: false +FakeSteamApi = $($FakeSteamApi.IsPresent.ToString().ToLower()) + +## SteamID64 returned by SteamUser.GetSteamID when FakeSteamApi=true. +## Empty = deterministic value derived from OS user + machine name. +## Pin an explicit ID to keep the same DorkNet account across +## different machines. +# Setting type: String +# Default value: +FakeSteamId = $FakeSteamId + +## Display name returned by SteamFriends.GetPersonaName when +## FakeSteamApi=true. Empty = OS user name. +# Setting type: String +# Default value: +FakeAccountName = $FakeAccountName +"@ +$cfg | Out-File -LiteralPath $cfgFile -Encoding utf8 -Force +Write-OK "Plugin config → $cfgFile" + +Write-Host '' +Write-OK 'All done. Launch the client from Steam.' +Write-Host ' Tail BepInEx/LogOutput.log for `[auth-injector] set Photon AuthValues …`' -ForegroundColor DarkGray +Write-Host ' to confirm the plugin loaded and ran.' -ForegroundColor DarkGray diff --git a/tools/remove-eac.ps1 b/tools/remove-eac.ps1 new file mode 100644 index 0000000..5d4adaa --- /dev/null +++ b/tools/remove-eac.ps1 @@ -0,0 +1,131 @@ +<# +.SYNOPSIS + Disable Easy Anti-Cheat in a Rec Room 2020.12+ install so MelonLoader / + Harmony patches can attach. + +.DESCRIPTION + The 2020.12+ Steam builds ship EAC, but the integration is the standard + EAC-launcher-wrapper model — NOT in-process anti-cheat in the game + binary itself. + + Files in a 2020.12 install: + + RecRoom.exe (~650 KB) The actual Unity game binary. + No SteamStub, no EAC strings. + Recroom_Release.exe (~1.2 MB) The EAC launcher front-end. Steam + launches this; it reads + EasyAntiCheat/Launcher/Settings.json, + talks to the EAC service, then + spawns RecRoom.exe under EAC. + EasyAntiCheat/ EAC service binaries + the launcher's + Settings.json. + + EAC blocks foreign DLL injection (MelonLoader's version.dll proxy, + Harmony hooks, etc.), which makes a normal Steam launch unrunnable for + the modded client. The fix is simple: skip the EAC launcher entirely + and run RecRoom.exe directly. EAC is never loaded into the process, + no patching of any binary required. + + This script just writes Launch-DorkNet.bat at the install root that + starts RecRoom.exe directly with any flags you pass through. The + EasyAntiCheat/ folder and Recroom_Release.exe are left in place — + they're harmless when nothing invokes them, and leaving them avoids + tripping unrelated existence checks. + + Designed for use with a DorkNet private server only — EAC's whole + point is the official live game. On a self-hosted server where every + client talks to YOUR backend, EAC isn't doing anything useful. + +.PARAMETER RecRoomDir + The Rec Room install root (the folder containing RecRoom.exe, + EasyAntiCheat/, RecRoom_Data/). + +.PARAMETER Revert + Remove the Launch-DorkNet.bat shortcut. (There's nothing else to + undo — the script never touches the game files.) + +.PARAMETER WhatIf + Show what would be done without touching the filesystem. + +.EXAMPLE + # Set up direct launch on a 2020.12 install + .\tools\remove-eac.ps1 -RecRoomDir 'C:\Games\RecRoom' + +.EXAMPLE + # Tear it down + .\tools\remove-eac.ps1 -RecRoomDir 'C:\Games\RecRoom' -Revert +#> + +[CmdletBinding(SupportsShouldProcess = $true)] +param( + [Parameter(Mandatory)] [string] $RecRoomDir, + [switch] $Revert +) + +$ErrorActionPreference = 'Stop' +Set-StrictMode -Version Latest + +function Write-Step($msg) { Write-Host ('> ' + $msg) -ForegroundColor Cyan } +function Write-OK($msg) { Write-Host ('+ ' + $msg) -ForegroundColor Green } +function Write-Warn2($msg){ Write-Host ('! ' + $msg) -ForegroundColor Yellow } +function Write-Err($msg) { Write-Host ('x ' + $msg) -ForegroundColor Red } + +$resolved = Resolve-Path -LiteralPath $RecRoomDir -ErrorAction SilentlyContinue +if (-not $resolved) { Write-Err "RecRoomDir '$RecRoomDir' does not exist."; exit 1 } +$RecRoomDir = $resolved.Path + +$GameExe = Join-Path $RecRoomDir 'RecRoom.exe' +$BootBat = Join-Path $RecRoomDir 'Launch-DorkNet.bat' + +if ($Revert) { + Write-Step 'Reverting — removing Launch-DorkNet.bat.' + if (Test-Path -LiteralPath $BootBat) { + if ($PSCmdlet.ShouldProcess($BootBat, 'remove launch shortcut')) { + Remove-Item -LiteralPath $BootBat + Write-OK 'Removed Launch-DorkNet.bat' + } + } else { + Write-Warn2 'No Launch-DorkNet.bat present — already reverted.' + } + Write-OK 'Done. Steam launch path is unchanged (the script never touched it).' + exit 0 +} + +if (-not (Test-Path -LiteralPath $GameExe -PathType Leaf)) { + Write-Err "RecRoom.exe not found in '$RecRoomDir'." + Write-Host ' This script targets 2020.12+ installs. For 2020.03.10 there' -ForegroundColor DarkGray + Write-Host ' is no EAC; just run Recroom_Release.exe directly.' -ForegroundColor DarkGray + exit 1 +} + +Write-Step "Setting up direct-launch in $RecRoomDir" + +$batBody = @" +@echo off +REM Launch Rec Room 2020.12+ directly, bypassing the EAC launcher +REM (Recroom_Release.exe). Used with a DorkNet private server. +REM +REM RecRoom.exe is the real Unity game binary; the EAC launcher is only +REM responsible for starting it under EAC's process protection. Running +REM the game binary directly means EAC is never loaded into the process, +REM so MelonLoader / Harmony patches attach cleanly. +REM +REM Generated by tools/remove-eac.ps1 -- re-run that script with -Revert +REM to delete this shortcut. + +pushd "%~dp0" +start "" "RecRoom.exe" %* +popd +"@ +if ($PSCmdlet.ShouldProcess($BootBat, 'write Launch-DorkNet.bat')) { + Set-Content -LiteralPath $BootBat -Value $batBody -Encoding ASCII + Write-OK "Wrote $BootBat" +} + +Write-Host '' +Write-OK 'EAC bypass ready.' +Write-Host ' - Launch with: ' -NoNewline; Write-Host $BootBat -ForegroundColor White +Write-Host ' - Or run RecRoom.exe directly.' -ForegroundColor DarkGray +Write-Host ' - Do NOT launch Recroom_Release.exe -- that is the EAC launcher.' -ForegroundColor DarkGray +Write-Host ' - Steam "Play" will still try to start Recroom_Release.exe; avoid it.' -ForegroundColor DarkGray +Write-Host ' - To undo: ' -NoNewline; Write-Host '.\tools\remove-eac.ps1 -RecRoomDir "" -Revert' -ForegroundColor White diff --git a/tools/setup-cloudflared.ps1 b/tools/setup-cloudflared.ps1 new file mode 100644 index 0000000..7984c9c --- /dev/null +++ b/tools/setup-cloudflared.ps1 @@ -0,0 +1,229 @@ +<# +.SYNOPSIS + Stand up a Cloudflare Tunnel that routes every *.localhost request to + the local DorkNet server. One-shot setup script for temp testing. + +.DESCRIPTION + Performs the steps a tester would otherwise do by hand: + + 1. Installs cloudflared if missing (via winget). + 2. Runs `cloudflared tunnel login` (browser auth — one-time). + 3. Creates a named tunnel "dorknet" (idempotent — re-uses an + existing tunnel of the same name). + 4. Generates %USERPROFILE%\.cloudflared\config.yml from the + template at tools/cloudflared-config.yml.template, substituting + the tunnel id + credentials file path. + 5. Adds DNS routes for every localhost subdomain DorkNet uses + (api, auth, match, accounts, admin, feed, …). Cloudflare + creates each as a CNAME pointing at .cfargotunnel.com. + 6. Prints the next steps to start the tunnel. + + Prerequisite: localhost must be on a Cloudflare account you control, + with the nameservers pointed at Cloudflare. The CLI works against + any zone the logged-in account can manage. + +.PARAMETER TunnelName + The name to give the tunnel. Default 'dorknet'. + +.PARAMETER Domain + Apex domain to route. Default 'localhost'. + +.PARAMETER DryRun + Print every command that would run without executing. + +.EXAMPLE + PS> .\tools\setup-cloudflared.ps1 +#> + +[CmdletBinding()] +param( + [string]$TunnelName = 'dorknet', + [string]$Domain = 'localhost', + [switch]$DryRun +) + +$ErrorActionPreference = 'Stop' +Set-StrictMode -Version Latest + +function Write-Step($msg) { Write-Host ('▸ ' + $msg) -ForegroundColor Cyan } +function Write-OK($msg) { Write-Host ('✓ ' + $msg) -ForegroundColor Green } +function Write-Warn2($msg) { Write-Host ('! ' + $msg) -ForegroundColor Yellow } +function Write-Err($msg) { Write-Host ('✗ ' + $msg) -ForegroundColor Red } +function Write-DryRun($msg) { Write-Host ('[dry-run] ' + $msg) -ForegroundColor DarkGray } + +# cloudflared writes harmless warnings (e.g. "Your version is outdated") +# to stderr. With ErrorActionPreference='Stop' globally, the 2>&1 +# merge would surface those as "NativeCommandError" terminating +# errors. Wrap every cloudflared invocation in this helper so stderr +# becomes informational output and only a non-zero exit is treated +# as a failure. +function Invoke-Cloudflared { + param([Parameter(ValueFromRemainingArguments = $true)] $CfArgs) + $prev = $ErrorActionPreference + $ErrorActionPreference = 'Continue' + try { + $output = & cloudflared @CfArgs 2>&1 | ForEach-Object { $_.ToString() } + $exit = $LASTEXITCODE + return [PSCustomObject]@{ + Output = $output + ExitCode = $exit + } + } finally { + $ErrorActionPreference = $prev + } +} + +$RepoRoot = Split-Path -Parent $PSScriptRoot +$Template = Join-Path $PSScriptRoot 'cloudflared-config.yml.template' +$CfDir = Join-Path $env:USERPROFILE '.cloudflared' +$ConfigPath = Join-Path $CfDir 'config.yml' + +# Subdomains we want routed through the tunnel. The wildcard route in +# config.yml catches everything; this list drives the per-subdomain +# DNS CNAMEs Cloudflare needs (no automatic wildcard CNAME via the +# CLI — each one is created explicitly via `cloudflared tunnel route +# dns`). +$Subdomains = @( + '@', # apex (localhost itself) + 'admin', 'api', 'accounts', 'auth', 'cdn', 'chat', 'commerce', + 'discovery', 'econ', 'feed', 'geo', 'img', 'match', 'notify', + 'ns', 'playersettings', 'rooms', 'storage', 'strings', 'strings-cdn', + # Long-tail subdomains DorkNet's stub controllers ack — adding + # CNAMEs for these means the patched client never hits a NXDOMAIN + # for any of the URLs it might probe. + 'bugreporting', 'cards', 'clubs', 'cms', 'data', 'datacollection', + 'gamelogs', 'leaderboard', 'link', 'lists', 'moderation', + 'platformnotifications', 'roomcomments', 'roomieintegrations', + 'studio', 'thorn', 'videos' +) + +# ── 1. cloudflared install ──────────────────────────────────────────────────── +Write-Step 'Checking cloudflared' +$cf = Get-Command cloudflared -ErrorAction SilentlyContinue +if (-not $cf) { + Write-Warn2 'cloudflared not in PATH.' + $winget = Get-Command winget -ErrorAction SilentlyContinue + if ($winget) { + if ($DryRun) { + Write-DryRun 'Would run: winget install --id Cloudflare.cloudflared --silent' + } else { + Write-Step 'Installing via winget' + & winget install --id Cloudflare.cloudflared --silent --accept-package-agreements --accept-source-agreements + # winget puts it in PATH but not in the current shell session + $env:PATH = "$env:PATH;$env:LOCALAPPDATA\Microsoft\WinGet\Links" + $cf = Get-Command cloudflared -ErrorAction SilentlyContinue + if (-not $cf) { + Write-Err 'cloudflared still not found after install. Open a new shell and re-run.' + exit 1 + } + } + } else { + Write-Err 'winget not found either. Install cloudflared manually:' + Write-Host ' https://github.com/cloudflare/cloudflared/releases' -ForegroundColor DarkGray + exit 1 + } +} +$cfSource = if ($cf) { $cf.Source } else { 'just-installed' } +Write-OK ('cloudflared at ' + $cfSource) + +# ── 2. Login ────────────────────────────────────────────────────────────────── +$certPath = Join-Path $CfDir 'cert.pem' +if (-not (Test-Path -LiteralPath $certPath)) { + Write-Step 'Cloudflare login (browser will open — pick the localhost zone)' + if ($DryRun) { + Write-DryRun 'Would run: cloudflared tunnel login' + } else { + $login = Invoke-Cloudflared tunnel login + $login.Output | ForEach-Object { Write-Host $_ -ForegroundColor DarkGray } + if (-not (Test-Path -LiteralPath $certPath)) { + Write-Err 'cert.pem still missing — login was cancelled or failed.' + exit 1 + } + } +} +Write-OK ('Auth cert present at ' + $certPath) + +# ── 3. Tunnel create / look up ───────────────────────────────────────────────── +Write-Step ("Looking up tunnel '" + $TunnelName + "'") +if ($DryRun) { + Write-DryRun ('Would run: cloudflared tunnel list and create ' + $TunnelName + ' if missing') + $tunnelId = '{{TUNNEL_ID}}' + $credsFile = '{{CREDENTIALS_FILE}}' +} else { + # `cloudflared tunnel list` outputs human-readable text by default; + # parsing the table is fragile but works. We grep for our name and + # extract the UUID. If not found, create. + $list = Invoke-Cloudflared tunnel list + $existingLine = $list.Output | Select-String -Pattern ('\b' + [regex]::Escape($TunnelName) + '\b') + if ($existingLine) { + # Format: " " + $tunnelId = ($existingLine.Line -split '\s+')[0] + Write-OK ('Tunnel exists: ' + $tunnelId) + } else { + Write-Step ("Creating tunnel '" + $TunnelName + "'") + $create = Invoke-Cloudflared tunnel create $TunnelName + $create.Output | ForEach-Object { Write-Host $_ -ForegroundColor DarkGray } + $list = Invoke-Cloudflared tunnel list + $existingLine = $list.Output | Select-String -Pattern ('\b' + [regex]::Escape($TunnelName) + '\b') + if (-not $existingLine) { + Write-Err 'Tunnel create failed; cloudflared list still empty.' + exit 1 + } + $tunnelId = ($existingLine.Line -split '\s+')[0] + Write-OK ('Created tunnel ' + $tunnelId) + } + $credsFile = Join-Path $CfDir ($tunnelId + '.json') + if (-not (Test-Path -LiteralPath $credsFile)) { + Write-Warn2 ('Credentials file missing: ' + $credsFile) + } +} + +# ── 4. config.yml from template ─────────────────────────────────────────────── +Write-Step ('Writing ' + $ConfigPath) +if (-not (Test-Path -LiteralPath $Template)) { + Write-Err ('Template missing: ' + $Template) + exit 1 +} +if (-not (Test-Path -LiteralPath $CfDir)) { + if (-not $DryRun) { New-Item -ItemType Directory -Path $CfDir -Force | Out-Null } +} +$tmpl = Get-Content -LiteralPath $Template -Raw +$config = $tmpl ` + -replace '\{\{TUNNEL_ID\}\}', $tunnelId ` + -replace '\{\{CREDENTIALS_FILE\}\}', ($credsFile -replace '\\', '\\') +if ($DryRun) { + Write-DryRun ('Would write ' + $ConfigPath) + Write-Host $config -ForegroundColor DarkGray +} else { + Set-Content -LiteralPath $ConfigPath -Value $config -NoNewline + Write-OK 'Config written.' +} + +# ── 5. DNS routes ────────────────────────────────────────────────────────────── +Write-Step ('Adding DNS CNAMEs on ' + $Domain) +foreach ($sub in $Subdomains) { + $hostname = if ($sub -eq '@') { $Domain } else { "$sub.$Domain" } + if ($DryRun) { + Write-DryRun ("Would run: cloudflared tunnel route dns $TunnelName $hostname") + continue + } + Write-Host (' ' + $hostname) -ForegroundColor DarkGray + $null = Invoke-Cloudflared tunnel route dns $TunnelName $hostname + # cloudflared returns nonzero if the CNAME already exists, but the + # net result is fine — proceed. +} +Write-OK 'DNS routes registered.' + +# ── 6. Done ─────────────────────────────────────────────────────────────────── +Write-Host '' +Write-OK 'Cloudflare Tunnel ready.' +Write-Host '' +Write-Host ' Start the tunnel (keep running while testing):' -ForegroundColor White +Write-Host (' cloudflared tunnel run ' + $TunnelName) -ForegroundColor Cyan +Write-Host '' +Write-Host ' Or install as a Windows service so it auto-starts:' -ForegroundColor White +Write-Host ' cloudflared service install' -ForegroundColor Cyan +Write-Host '' +Write-Host ' Then: visit https://admin.localhost / https://feed.localhost / play the' -ForegroundColor DarkGray +Write-Host ' patched client. Make sure the local DorkNet.Server is running' -ForegroundColor DarkGray +Write-Host ' and listening on 0.0.0.0:443 with the *.localhost-covering cert.' -ForegroundColor DarkGray