# Horizon launcher A small desktop app that installs Horizon builds, prepares them from your own copy of Minecraft, and starts the client or a dedicated server. Built with [GPUI](https://www.gpui.rs), Zed's UI framework; it has its own Cargo workspace and is not part of the game's builds. ![Play](docs/play.png) The opening, then Play ([clip](docs/opening.webm)): ![Opening](docs/opening-1.png) ## What it does - **Versions**: lists the published releases of horizon-rust/horizon, installs the build for this platform into its own folder, and removes it again. - **Play**: prepares resources as `PLAY.txt` describes (your launcher's 26.3 install, the 26.3 server jar from that version's own json, and Java 25) and copies its sounds from your launcher's asset store (`horizon-prepare sounds`; a launcher install without them only leaves the game silent), then starts the client. Preparing is horizon-prepare: linked into the launcher, used in-process when a version ships the same preparer sources, else that version's own `horizon-prepare`; versions released before it still prepare with their Python script and need Python 3.10+. Before importing, preparation reuses checksum-verified game jars and libraries from the current or another installed Horizon version, so the original Minecraft install is only required when no valid imported copy exists. - **Download and prepare**: for a signed-in account that owns the game (asked again first), the launcher downloads the pinned version from Mojang itself (piston-meta, piston-data, libraries.minecraft.net, resources.download.minecraft.net; never a mirror) into `minecraft/` in its data folder, in the official launcher's layout: the version file, the client jar, this system's libraries, the asset index and the assets. Every file must have the size and sha1 Mojang lists. The Play page shows how far it is and has Cancel; files already whole are kept, so the next try goes on from there. **From my install** prepares from the player's own launcher instead, and an install that lacks the version falls back to the download. Without an owner, the Play page asks to sign in first. `horizon-launcher prepare --download`. - **Mods**: search Modrinth (CurseForge support is coming soon) for resource packs and modpacks, each marked Works, Partly or Doesn't run in Horizon; Java mod jars are never offered. Install puts a pack and what it needs in `resourcepacks/` only after its SHA-512 matches, and a modpack goes to the Modpacks page; installed packs show their source and checksum, with Update, **Update all** and Remove ([docs/mod-browser.md](../docs/mod-browser.md)). - **Modpacks**: import a Java modpack (a Modrinth `.mrpack`, a CurseForge zip, or a Prism, MultiMC or CurseForge instance folder) and see its coverage report: per mod, ported, partial, not yet or not needed, with the reason ([docs/modpacks.md](../docs/modpacks.md)). **Port** gets the jars of what this computer can port today (content mods with data and assets only) the player's own way: copied from a folder they have, or downloaded from the pack's own links or the same file on Modrinth's CDN, hash checked; files only CurseForge has come from your installed instance (CurseForge downloads are coming soon). It carries them over for the selected version, and **Play** starts the game as the pack's own profile (`ports//home/` in the game's home: its own saves and options) with those mods. Nothing is uploaded or shared: mod jars and ports stay on this computer. - **Servers**: host a world for friends with buttons. **Create a server** asks for a name, a world (a new one, with an optional seed, or a copy of one of your single-player worlds), the game mode, difficulty, most players, who can join (friends only, the default, or anyone with the address) and your agreement to the Minecraft EULA; with the defaults (tick the EULA, press Create) you get a friends-only survival world with you as its operator. Each server then has **Start**, **Stop** and **Restart**, its state (also when it crashed and was restarted), players online with their heads, TPS, MSPT and RAM, the address friends on your network type with **Copy**, a console that takes commands, **Open the web panel**, and four tabs: **Overview**; **Players** (online players with Make operator, Kick and Ban; who may join, with Friends only, Let in and Remove; Unban); **Settings** (the message in the server list, game mode, difficulty, PvP, most players, view and simulation distance, port, account checks, **Run in the background and at login**); **Backups** (Back up now, the list with dates and sizes, Restore with a confirmation, Back up automatically, and **Updates**: Check for updates, Update now with its automatic rollback, and how many older versions to keep). Changes the running game can take apply at once, the others say "Restart to apply". Everything goes through `horizon-server` itself ([docs/hosting.md](../docs/hosting.md)): the launcher copies a prepared version's programs and game files into `server-runtime/` laid out as `install.sh` does, runs `horizon-server init`, `config`, `start`, `backup`, `restore`, `update` and `service` there, and talks to a running server through its control API. Problems are said in plain words, with a button when there's a fix (another port, the game files, the EULA). **Friends over the internet** (on Overview) uses Tailscale: it says whether Tailscale is installed and signed in (with Install, Sign in and Check again; signing in is always yours, in Tailscale's own app or page), shows this computer's Tailscale name and address with **Copy**, and invites a friend to this one computer: in the admin console, or here with an access token you make there (kept in the system keychain), which also lists invitations with Withdraw / Stop sharing and offers **Limit them to Minecraft**: when your tailnet has Tailscale's default access controls, one click changes them so friends you share with reach only the server's port (Remove the limit undoes it); custom access controls are never rewritten, the page gives the rule to add instead. Horizon never turns on Funnel. - **Instances**: separate game folders, each with its own worlds, options and packs. The game's folder stays the first one, "Horizon", exactly where it was; imported and registry modpacks are instances too, in their own folders as before; new ones go to `instances//` in the game's folder, empty but for the player's options and name, or a copy of another instance (never its sign-in sessions, logs or data collection state). Names, icons (a block or item texture from the player's prepared files, or a colour) and groups are kept in `instances.json` in the launcher's data folder, so renaming moves nothing. Every instance follows the launcher's data collection answer and No AI. In the window, **+** beside Instances makes one, the sidebar lists them by group (click one to play it), and Home's Instance row has Edit (name, group, icon), Copy and Open folder. The Instance row shows its play time and when it was last played: every run started from the window or `horizon-launcher play` adds to it (`instance list` shows it too). Each instance plays the launcher's version, which follows updates, unless the Play page's version picker keeps it on another installed one (to keep an old world on the version it was made with); picking the version marked "follows updates" lets it follow again. - **Export**: Home's Instance row has **Export…**: the selected instance (or a port) as a `.mrpack` (Modrinth's modpack format: resource packs the browser installed from Modrinth are listed by their download, the rest is in `overrides/`) or a `.zip` of the game folder's layout, with the worlds switched on. It is written to `exports/` in the launcher's data folder (**Show in folder**). Only settings (options.txt, horizon-*.txt, servers.dat), resource and shader packs, `horizon-mods/` and `config/` go in: never logs, screenshots, backups, caches or account files; a world goes without `playerdata/`, `stats/` and `advancements/`, and its level.dat without the player's UUID. `horizon-launcher instance export [] [--format mrpack|zip] [--world ]… [--out ]`. - **Import by dropping a file**: a file dropped anywhere on the window is imported for what it is (`horizon_launcher::import`). A shared instance (a `.mrpack` that needs no mod loader, such as Export writes, or a game folder `.zip`) becomes a new instance, opened on Home. Its `.mrpack` downloads come from Modrinth with their SHA-512 checked, and only what an instance keeps is taken, inside its folder. A Java modpack goes to the Modpacks import, and a resource or shader pack into the selected instance. A data pack is pointed at the world rows, and the Packs section and the world rows keep taking their own drops. A note in the corner says what happened. `horizon-launcher instance import `. - **Launch options**: Home's Instance row has **Launch…**: per instance, a shell command run in its folder before the game (the game starts only if it succeeds), a wrapper the game runs through (`nice -n 5`), and environment variables (`NAME=value OTHER="two words"`), kept in `instances.json` (`horizon_launcher::launch`). The game stays one process the launcher follows: on Unix `/bin/sh` runs the command and then `exec`s the wrapper and the game, whose words pass through `"$@"` untouched; on Windows `cmd /C` runs ` && `. Both the window's Play and `play` use them. `horizon-launcher instance launch [--pre-launch …] [--wrapper …] [--env …]` (an MCP agent needs `confirm: true`: it sets commands Play runs). On Linux the panel also has switches for what the computer has: **GameMode** (`gamemoderun` on the PATH), **MangoHud** (`mangohud`), and, with two graphics cards under `/sys/class/drm`, the **dedicated graphics card** (`DRI_PRIME=1`, or NVIDIA's `__NV_PRIME_RENDER_OFFLOAD`, `__VK_LAYER_NV_optimus` and `__GLX_VENDOR_LIBRARY_NAME` when its driver is loaded). A switch whose tool is gone does nothing. `instance launch --gamemode on|off --mangohud on|off --dedicated-gpu on|off`; `HORIZON_LAUNCHER_LINUX_TOOLS` makes any system show them, for tests. - **Desktop shortcut**: Home's Instance row has **Shortcut**: a shortcut on the desktop that plays the selected instance without opening the launcher (`horizon-launcher play --instance `). A `.desktop` file on Linux (also put in the applications menu, and marked trusted with `gio` where it exists), a small app with Horizon's icon on macOS, a `.lnk` on Windows (`horizon_launcher::shortcut`). **Remove shortcut** moves it to the trash; it is found by the instance id inside it, so a rename doesn't lose it. `horizon-launcher instance shortcut [instance] [--remove]`; `HORIZON_LAUNCHER_DESKTOP` points it at another folder. - **Accounts and quick switch**: the top bar's account chip opens a menu of everyone who can play: each signed-in Microsoft account, the offline names the player keeps, and the name the game made up (`horizon-player.txt`). Picking one plays as it from the next Play. Offline names play only while an account that owns the game is signed in ([Microsoft accounts](#microsoft-accounts)); signing the last one out keeps them, unplayable until one is back. Settings > Account adds offline names (3 to 16 letters, digits or `_`; the game gets `--name`) and forgets them; the Console's Share log takes them out of the log too. `horizon-launcher account offline [NAME]`, `account select ` (an account or an offline name), `account forget-offline `. - **Accent and Home background**: on top of Dark, Light or System, Settings > Launcher picks an accent (Horizon's violet, blue, teal, green, amber, red or pink) and a picture of the player's own behind Home (PNG, JPEG or WebP, cropped to fill; **Landscape** brings Horizon's back). An accent turns the violet tokens to its hue, then darkens or lightens them just enough to keep white on the Play button at 4.5:1 and the accent at 3:1 on every background (a test holds every accent in both looks to it). The picture is copied into the data folder. `horizon-launcher settings accent NAME`, `settings background PATH|default`. - **Language**: English, French and Russian (the languages Horizon's own game menus have). The window follows the system's language unless the player picks one in Settings > Launcher (each named in itself); a string a table lacks shows in English. The tables are `launcher/lang/.json`, English text to its translation, Horizon's own; a test fails when the code asks `tr("…")` for a string a table lacks, or a table keeps one the code dropped. `horizon-launcher settings language system|en|fr|ru`. - **Skin and cape**: each offline name's **Skin…** in Settings > Account shows the front of its skin and cape, takes a skin PNG (64×64, or the older 64×32) and a cape PNG (64×32), and picks classic or slim arms. The launcher keeps copies in its `skins/` folder and gives the game `HORIZON_OFFLINE_SKIN`, `HORIZON_OFFLINE_SKIN_MODEL` and `HORIZON_OFFLINE_CAPE` when that name plays; the game draws the default skins until it reads them. A Microsoft account's skin and cape: coming soon, with sign-in. `horizon-launcher account skin [FILE] [--model classic|slim] [--cape FILE] [--no-skin] [--no-cape]`. - **Console**: Home's Console shows the game's output of this run as it comes: a search (every word, any case), Follow to keep the newest line in view, Copy, and Open logs folder. Warnings and errors stand out. **Share log…** uploads the log to [mclo.gs](https://mclo.gs), a paste service for Minecraft logs, only after the player confirms; the home folder, the user name and the account names are taken out first (`horizon_launcher::game_log`), and **Delete it** removes the paste. `horizon-launcher share-log [--instance]` and `unshare-log `; `HORIZON_LAUNCHER_PASTE_URL` points it at a test server. - **Worlds**: Home lists the selected instance's worlds (picture, mode, version, last played, size). **Back up** zips one into the instance's `backups/` as the game's Make Backup does (`_