# MMORPG launcher for Windows

1. Unzip this package anywhere, for example a folder named MMORPG on your Desktop. Keep the
   bundle's files together, and put it somewhere you can write to (not `C:\Program Files`), because
   the launcher updates itself in place.
2. Double-click **MMORPG Launcher.exe** and choose **Production** or **Test**. Windows SmartScreen may
   say the publisher is unknown because the launcher is not code-signed: choose "More info" then
   "Run anyway" once.
3. The launcher checks for updates as soon as it opens. If anything is out of date the button says
   **Update**: press it, and it installs the newer launcher and the newer game. When everything is
   current the button says **Play**. Press it, sign in, then
   choose a server on the character screen: **Serenity** in Production or **Rex** in Test.
   Characters belong to their server. Create or choose a character, then enter the world.

Playing needs nothing else installed: no .NET runtime, no Godot, no Python. The game is
installed under `%LOCALAPPDATA%\MMORPG\client\regions\<regionId>\releases\<releaseId>\`; `current.json` beside
`releases` records the active release and the launcher's log is `%LOCALAPPDATA%\MMORPG\launcher.log`.
The launcher keeps earlier releases so an update can never remove files beneath a running game.

## One button: Update, then Play

The launcher checks for updates when it opens, and again whenever you change the region — before you
press anything. The check is two small secure requests; it downloads nothing and changes nothing on
your computer. What it finds decides what the button says:

| The button says | What it means | The line under it |
|---|---|---|
| **Checking…** | asking right now | Connecting securely to Production/Test |
| **Update** | a newer launcher or a newer game is published | what would be installed |
| **Play** | you have the published launcher and the published game | the installed release |
| **Retry** | the check could not be made — usually the update service is unreachable | what went wrong |

While it is working the button says **Checking…**, **Updating…** or **Starting…** and cannot be
pressed, and the region cannot be changed until it finishes.

Pressing **Update** does the whole update and then checks again, so it finishes on **Play**. It never
starts the game by itself: starting is always your choice.

Pressing **Play** asks once more and *then* starts, so a release that appeared while the window was
sitting open turns the button back into **Update** instead of starting a download behind a button
that said Play. That extra check is the reason a Play press can take a second before the game
appears.

**Retry** always means "check again". After any failure the launcher re-establishes what is installed
and what is published before it offers Play again.

## The versions at the bottom of the window

At the very bottom of the window, in the smallest type and never changing with the button, is one
line:

```
Launcher abaa3ee1bf0d3043 · Game 1.0.3455.5f2bb4d1003e (build-1a2b3c4d5e6f-20260918-windows-x64)
```

If you ever contact support, this is the line you will be asked for. You can select it with the
mouse and copy it.

- **Launcher** is which build of the launcher itself you are running. A launcher that was not
  installed from a published package says `Launcher unpackaged`.
- **Game** is the world version of the game **installed on this computer** for the region you have
  selected, followed by the release it came from. A game installed before world versions existed
  shows its release name alone, and a region with nothing installed yet says
  `Game not installed`.

It is re-read after every check and after every update, so it always describes what is on the disk
right now. Each region has its own installed game, so changing the region changes this line too.

### For the people who publish

The world version is `MAJOR.MINOR.BUILD.SHA12` — `1.0.3455.5f2bb4d1003e` — and it reaches the window
like this: `worldVersion` in the release manifest → `release.json` inside the archive → `current.json`
when the release is installed → the footer.

- It is the **only optional field** in the release manifest, and only because every release published
  before sc-482 has none. Absent is "unknown" and the footer falls back to the release id; a
  `worldVersion` that is present but is not that exact shape is a **refusal**
  (`Release manifest worldVersion must be MAJOR.MINOR.BUILD.SHA12.`) by the launcher, by
  `mmorpg-launcher.exe --verify-package` and by the publisher, which runs the same parser.
- A `current.json` that is unreadable, or whose `worldVersion` is not that shape, reads as "not
  installed" and "no world version" respectively. The footer never guesses and never throws.
- Changing any launcher source — including this footer — changes the launcher's own version, so both
  launcher packages must be rebuilt and republished with the release that first carries a world
  version. Otherwise the field is published and nothing shows it.

## The launcher updates itself

When the check finds a newer launcher, pressing Update downloads it, checks its SHA-256, verifies
that it really is an MMORPG launcher, replaces its own files beside itself and restarts. You see
"Updating the launcher…", "Installing the launcher…", "Restarting the launcher…", the window comes
back, finishes the update by itself and stops on **Play**. The version it is running is in the small
line at the bottom of the window, and `mmorpg-launcher.exe --launcher-status` prints it.

The previous launcher is kept beside the new one as `*.old` and removed at the next start (see
[Files the launcher leaves behind](#files-the-launcher-leaves-behind)). If anything at all goes
wrong — the download is interrupted, a check fails, the folder is read-only — **the launcher you have
keeps working**: it is never half-replaced, and the message tells you what happened. If the update
service cannot be reached, the launcher's own version is simply left alone and it carries on to the
game; if the *game's* list of releases cannot be read either, the button becomes Retry, because
without it there is nothing safe to start.

**One manual download, once.** A launcher from before this version cannot update itself, because it
does not know how to. If you are running one, download `launcher-windows-x64.zip` from the website
(or `https://192.168.50.99:30445/launcher-windows-x64.zip`) one last time and unzip it over your
folder. Every launcher from this version on keeps itself up to date.

`--no-self-update` skips replacing the launcher for one run, which is useful only for
troubleshooting. With it, `--check` answers `launcher=unpackaged` and never asks the update service
about the launcher at all: a run that is not allowed to replace the launcher must not report one as
available, or the window would be offering an Update it has been told not to do.

## Files the launcher leaves behind

- **`mmorpg-launcher.exe.old`** can survive one update of the *console* launcher. When
  `mmorpg-launcher.exe` replaces itself it starts the new launcher and waits for it, so at the moment
  the new one sweeps up, `mmorpg-launcher.exe.old` is still the running image of the parent that is
  waiting — Windows will not delete a running image. The next update run removes it.
  `MMORPG Launcher.exe.old` is deleted at that same moment, because nothing is running it. The window
  sweeps again just before it shows itself, which is when the launcher it replaced has normally
  finished exiting, and `--launcher-status`, `--status` and `--check` never delete anything at all.
- **`.launcher-update.lock`**, zero bytes, stays in the folder. It is the anchor the exclusive-open
  lock uses to keep two launchers from updating at once — the same thing `.update.lock` is in the
  game folder — and an empty file left behind is how that lock is meant to look. Harmless.
- **`%LOCALAPPDATA%\MMORPG\launcher.log`** is written by the window only. `mmorpg-launcher.exe`
  prints to the console it was started from and writes no log, so a console run's evidence is its own
  output — redirect it if you need to keep it.

## Security

The launcher uses `release-ca.pem` for Production and for its own updates, and `test-release-ca.pem`
for Test. It verifies the archive's size and SHA-256 from the manifest (or the launcher descriptor),
extracts it safely, installs it atomically and keeps the previous working game — and the previous
working launcher — if a download, check or install fails. On failure it shows the
reason with Retry and View Log and starts nothing.

## Command line (from the unzipped folder)

```
mmorpg-launcher.exe --check           say what is out of date, change nothing
mmorpg-launcher.exe --update-only     update the launcher, then download and verify the game without starting it
mmorpg-launcher.exe --status          show the launcher version and the installed release
mmorpg-launcher.exe --launcher-status show only the launcher version
mmorpg-launcher.exe --self-check      install, then start the game headless and require its login screen
mmorpg-launcher.exe --no-self-update --update-only
mmorpg-launcher.exe --region test --update-only
mmorpg-launcher.exe -- --resolution 1280x800
```

`--check` is the window's check on the command line. It prints the same lines the window logs —
`Checking for launcher updates…`, then `Launcher <v> is current.` or `Launcher update available: <v>.`;
`Checking for the current <Region> client…`, then `Client <id> is current.`, `Client update available: <id>.`
or `No client installed; <id> is available.` — and ends with one line a script can read:

```
UPDATE_CHECK launcher=current client=available:build-1a2b3c4d5e6f-20260918 world=1.0.3455.5f2bb4d1003e
```

`launcher=` is `current`, `available:<version>`, `unknown` (the update service could not be reached —
soft, exactly as during an update), `unpackaged` (this launcher carries no version stamp, so it can
never replace itself — also what `--no-self-update` answers) or `unconfigured` (a launcher packaged
before self-update existed). `client=` is `current` or `available:<releaseId>`, and
`available:<releaseId>` is also the answer when nothing is installed, when `current.json` cannot be
read, and when it names a release folder that is not there — an install is what repairs all three.
`world=` is the world version of the release `client=` names — the **published** one, not the
installed one — or `unknown` for a release published before world versions existed. It is appended
after `client=` rather than inserted, so a script written against the older two-field line keeps
working on the prefix it already parses. Exit code 0.

Two things are **hard**: a game manifest that cannot be fetched or does not parse, and a launcher
descriptor that answers and then breaks its contract (a publisher bug). Either prints
`MMORPG launcher error: …` and exits 1 **without** an `UPDATE_CHECK` line, so half an answer can
never be read as an answer. Only an unreachable update service is soft, and that is `launcher=unknown`
with exit 0.

When `mmorpg-launcher.exe` replaces itself it starts the new launcher with the same arguments and
waits for it, so a script that runs the command still gets one answer and one exit code.

`MMORPG Launcher.exe` accepts `--region ID`, `--continue`, `--no-self-update` and
`-- GAME ARGUMENTS...`. `--continue` is what a launcher that has just replaced itself passes to its
replacement: finish the update that was already asked for, then stop on **Play**. It does not start
the game — the player pressed Update, not Play.
