import { useEffect, useState } from 'react'; import { Link } from 'react-router'; import { fileUrl, getDownloads, type DownloadFile, type DownloadRelease, type DownloadsManifest } from '../lib/downloads'; const fmtSize = (n: number) => n >= 2 ** 30 ? `${(n / 2 ** 30).toFixed(2)} GB` : n >= 2 ** 20 ? `${(n / 2 ** 20).toFixed(1)} MB` : `${Math.max(1, Math.round(n / 2 ** 10))} KB`; const fmtDate = (iso?: string) => { if (!iso) return ''; const d = new Date(iso); return Number.isNaN(+d) ? '' : d.toLocaleDateString(undefined, { year: 'numeric', month: 'short', day: 'numeric' }); }; const PLATFORMS = [ { id: 'pc', title: 'PC', hint: 'Windows, with or without a VR headset.' }, { id: 'quest', title: 'Meta Quest', hint: 'Needs developer mode on your headset and a USB cable.' }, ]; // What each file is for, in players' terms. Keyed by the ids publish.py writes. function describe(f: DownloadFile, rel: DownloadRelease): { title: string; primary?: boolean; body: React.ReactNode } { switch (f.id) { case 'pc-full': return { title: 'Full game', primary: true, body: <>Rec Room {rel.title} with DorkNet already set up. Unzip it anywhere and run {rel.pc_exe ? {rel.pc_exe} : 'the game'}., }; case 'pc-patch': return { title: 'Patch only', body: <>Just the DorkNet part (MelonLoader and the DorkNet mod), for a copy of the game you already have. The DorkNet launcher installs this for you; skip it if you got the full game., }; case 'quest': return { title: 'Quest app', primary: true, body: <>Install it straight from this site: plug in your headset and use the Quest page (Chrome or Edge). Or download it for SideQuest., }; case 'quest-installer': return { title: 'Install scripts', body: <>Prefer the command line? Unzip these next to the Quest app and run install.cmd (Windows) or install.sh (macOS / Linux). Needs adb., }; default: return { title: f.name, body: null }; } } function FileCard({ f, rel }: { f: DownloadFile; rel: DownloadRelease }) { const d = describe(f, rel); const [copied, setCopied] = useState(false); const copy = async () => { try { await navigator.clipboard.writeText(f.sha256); setCopied(true); setTimeout(() => setCopied(false), 1500); } catch { /* clipboard blocked: the full hash is in the tooltip */ } }; return (

{d.title}

{d.primary && Start here}

{d.body}

{f.name}
{fmtSize(f.size)} {f.built_at && Updated {fmtDate(f.built_at)}} {f.build && Build {f.build}}
{f.id === 'quest' ? ( <> Install on Quest Download ) : ( Download )}
); } export function Downloads() { const [m, setM] = useState(undefined); const [err, setErr] = useState(null); useEffect(() => { getDownloads(true).then(setM).catch((e) => setErr((e as Error).message)); }, []); const releases = (m?.releases ?? []).filter((r) => r.files.length); return (

Downloads

Rec Room for DorkNet, built from the DorkNet source by the build server. The SHA-256 next to each file lets you check your download.

{err &&
Couldn't load the builds: {err}
} {m === undefined && !err &&

Loading builds…

} {m === null &&

This server doesn't offer downloads.

} {m && !releases.length &&

No builds have been published yet.

} {releases.map((rel) => (

Rec Room {rel.title}

{rel.game_version && Game build of {fmtDate(`${rel.game_version}T12:00:00Z`)}}
{PLATFORMS.map((p) => { const files = rel.files.filter((f) => f.platform === p.id); if (!files.length) return null; return (

{p.title}

{p.hint}

{files.map((f) => )}
); })}
))} {releases.length > 0 && (

These builds contain Rec Room's own game files. They're for this community only: please don't re-share them.

)}
); }