# Architecture ``` ┌──────────────────────────┐ HTTPS (JSON) ┌──────────────────────────┐ │ Launcher (Tauri) │ ─────────────────────────▶ │ Panel (Axum + SQLite) │ │ Svelte UI ⇄ Rust shell │ manifest, instances, │ Svelte admin UI │ │ │ │ auth, stats │ /files, /uploads │ │ scopenet-core engine │ └──────────────────────────┘ │ ├─ Mojang / Java │ ──▶ piston-meta, libraries.minecraft.net, resources │ ├─ Fabric/Quilt/Forge │ ──▶ meta.fabricmc.net, maven.minecraftforge.net, … │ └─ modpack files │ ──▶ Modrinth/CurseForge CDNs, panel /files └──────────────────────────┘ ``` - **`crates/shared`** defines every JSON type exchanged, so the panel and launcher can't drift apart. - **`crates/core`** has no UI dependencies. It's unit-tested, and CI runs real installs of vanilla, legacy, Fabric, Quilt, Forge and NeoForge against the live servers. - The **panel** also uses `core` for version and loader lookups. ## Launch pipeline 1. Resolve the account and validate its private Yggdrasil session. 2. Fetch the instance from the panel (cached for offline play). 3. `install`: vanilla version JSON → Java runtime (Mojang's own builds) → client jar, libraries, assets → loader profile (Fabric/Quilt), or replay the Forge/NeoForge installer's processors → merge → natives. 4. `sync`: download the admin's files; remove files the admin removed; never touch the player's own files. With an unchanged revision, only existence is checked. 5. Write `servers.dat` and keybinds, build the Java command (quick play / `--server`, memory, GC presets, `@argfile` for long classpaths), and start the game. 6. Stream logs to the console; on a crash, show the tail of the log with hints. ## Disk layout (launcher) ``` %APPDATA%\net.scopenet.launcher\ settings.json, accounts.json, secrets.bin (encrypted), manifest.json (cache) minecraft\ versions\ libraries\ assets\ runtimes\ natives\ cache\ instances\\ ← game directory (saves, mods, options.txt, …) ``` ## HTTP API Public (used by launchers). Send `Authorization: Bearer ` to see restricted instances: | Method | Path | | |---|---|---| | GET | `/api/v1/launcher/manifest` | Branding, auth options, visible instances | | GET | `/api/v1/launcher/instances/{id}` | Instance + file list | | POST | `/api/v1/launcher/events` | Launch stats | | POST | `/api/v1/auth/login` · `/register` | Panel accounts | | GET | `/api/v1/auth/me` | Current user | | GET | `/files/{instance}/{path}` | Hosted instance files | | GET | `/healthz` | Health check | Admin (`role = admin`): `/api/admin/{stats,users,groups,branding,settings,instances,uploads}` plus `instances/{id}/{import/modrinth,import/curseforge,import/upload,files,reset}` and meta/search proxies under `/api/admin/{meta,modrinth,curseforge}`. ## Security notes - Passwords are hashed with Argon2id; tokens are HS256 JWTs (30 days); disabled accounts are rejected on every request. - Server-provided paths are sanitised (`safe_join`) on both sides — no `..` or absolute paths. - The panel never returns the CurseForge key; SVG uploads are refused (script risk). - The launcher's CSP only allows scripts from the app itself; media may load from any HTTPS host (admin-chosen images). - Hosted `/files` are public URLs. Don't put secrets in instance files.