# ffxiv-tiny-launcher A minimal FFXIV launcher written in Go with no dependencies. Designed to run in a minimalistic environment such as GameNative (Windows-on-Android). ## AI Disclosure This entire application was built using agentic AI. I used a local model, Qwen 3.8 27B, with OpenCode. ## Usage 1. Use the official installer to install the game. Download it here: [https://na.finalfantasyxiv.com/windows/download/](https://na.finalfantasyxiv.com/windows/download/) 2. Run this launcher. 3. Pick your FFXIV install folder — the one containing `game/` — via `...` (In GameNative this is usually `A:\`). 4. (Optional) Ensure 'Enable Dalamud' is checked if you'd like to use the Dalamud mod loader, or unchecked if you'd prefer to not use it. 5. Enter credentials + 2FA code and click **Log in**. Whenever there is a game update, or if you are installing the game for the first time, click 'Open official launcher'. Your password will be copied to the clipboard so you can paste it in to the official launcher. Use the official launcher to update the game. Right after a game update Dalamud will stop working. You must uncheck 'Enable Dalamud' to launch the game. ## Details A small native Windows dialog asks for username / password / 2FA code, logs in against the official login service, and launches `game/ffxiv_dx11.exe` with the SE-encrypted argument string — no game update management of its own. Optionally it can launch the game through the official Dalamud injector (toggle in the dialog; see [Dalamud](#dalamud)). The UI is [winc](https://github.com/tadvi/winc), a pure-Go wrapper around native Win32 controls — no cgo and no other dependencies (built with `CGO_ENABLED=0`). The binary is Windows-only; run it inside any full Windows environment such as GameNative. ## Build ```sh ./build.sh # -> dist/ffxiv-tiny-launcher-windows_amd64.exe ``` Or by hand: `CGO_ENABLED=0 GOOS=windows go build -o ffxiv.exe .` ## Configuration Stored at `%APPDATA%\ffxiv-tiny-launcher\config`, obfuscated (XOR+base64 — hiding from casual eyes, not real protection): ```jsonc { "game_path": "C:\\FFXIV", // folder containing game/ "dalamud": false, // launch through the Dalamud injector "user": "...", // saved after a successful login "pass": "..." // saved after a successful login; 2FA is never saved } ``` ### Environment variables | Variable | Meaning | Default | |---|---|---| | `FFXIV_REGION` | `1` = NA, `2` = EU | `1` | | `FFXIV_LANG` | client language: 0 ja / 1 en / 2 de / 3 fr | `1` (en) | | `FFXIV_ACCEPT_LANGUAGE` | value sent in the login request's Accept-Language header | `en-US,en;q=0.5` | | `FFXIV_CONSOLE` | set to keep the console window visible (it is hidden by default; a console shared with your terminal is never hidden) | unset = hidden | ## How it works - **Login** mirrors FFXIVQuickLauncher: fetches the region's login top page, extracts `_STORED_`, POSTs to `login.send`, and parses `window.external.user("login=auth,ok,...")` for session id, region, terms-accepted flag, playable flag and max expansion. The referer is pulled from the public client config (kamori.goats.dev) with a hardcoded fallback, so it tracks launcher version bumps automatically. The session id this returns is only used for the registration step below — it must not be passed to the game. - **Session registration** posts a version report (boot file hashes + per-expansion versions) to `patch-gamever.ffxiv.com/.../{gameVer}/{sessionId}`; the server answers with an `X-Patch-Unique-Id` header, and *that* is what becomes the game's `DEV.TestSID`. The lobby only accepts this unique id — skipping the step (or sending the raw session id) boots to the title screen but fails at "error connecting to lobby server". A non-empty response body is the patch list (tab-separated rows, parsed in `sqex/patchlist.go`); if there are pending patches the launcher stops short of starting the game, saying so in a popup (and the status field): press **Open official launcher** to patch first. That button starts `boot/ffxivboot64.exe` from the install folder and puts your password on the clipboard so it can be pasted into SE's own login form — applying updates is left to the official launcher, which owns the patch format. - **Server status** is checked before login and again right before launch, like FFXIVQuickLauncher: a GET to `frontier.ffxiv.com/worldStatus/gate_status.json?lang=` (the same endpoint QL's "gate closed" dialog reads). While the gate is open SE answers `{"status":1}` — note that's a *number*, not a boolean. When it's closed, the response carries a localized `message` array (and optional `news`); login stops and a popup shows SE's notice (first line also goes to the status field, full text to `launcher.log`) — dismissing it just re-enables **Log in**, so you can leave the window open and try again later. If the endpoint itself can't be reached — which is what happens during some maintenance windows — we stop with an explicit "could not reach the server-status service" popup rather than guessing. The same popup mechanism surfaces other terminal failures: a rejected login (SE's own message, e.g. bad credentials) and pending client updates. - **Launch** builds the same `//**sqex0003...X**//` argument string the official launchers use: SE's legacy Blowfish (ported from FFXIVQuickLauncher and tested against known-good vectors in `sqex/sqex_test.go`) over the argument list, keyed by the top 16 bits of system uptime — which is why the game must start within ~a minute of login. ## Dalamud The **Enable Dalamud** toggle (persisted in the config) makes the launcher run the game through the official `Dalamud.Injector.exe` instead of starting `ffxiv_dx11.exe` directly — the same flow FFXIVQuickLauncher uses. On first use it downloads, MD5-verified: | Piece | Size (approx.) | Where it lands | |---|---|---| | Dalamud release bundle | ~60 MB | `%APPDATA%\ffxiv-tiny-launcher\dalamud\hooks\\` | | .NET runtime (DotNet + WindowsDesktop) | ~75 MB | `.../dalamud/runtime/` | | Dalamud asset package | ~50 MB | `.../dalamud/assets//` | (≈ 190 MB total, one-time; later launches re-verify integrity and only re-download what changed.) All of it lives under the launcher's own config dir, never in `%APPDATA%\XIVLauncher`, so a parallel QuickLauncher install is untouched. The injector itself starts the game with the same encrypted argument string. If anything fails — the running game version isn't supported by the current Dalamud release, a download or integrity check fails, the injector exits non-zero, or it hangs past 60 s — the launch is **cancelled** with an error in the dialog (and on stderr); the launcher won't silently start the game without plugins when you asked for them. Turn the toggle off to play without Dalamud. Plugin configuration and installed plugins are kept in `.../dalamud/config/` (standard Dalamud layout: `dalamudConfig.json` + `installedPlugins/`). ## Troubleshooting - **Dialog doesn't appear / launcher exits instantly** — check `launcher.log` next to the executable (capped at 32 KiB; when full, the oldest half is dropped and the newest lines are kept). - **Login fails with `unrecognized login response` / generic error page** — SE answered with its generic multilingual error page instead of the login form. Set `FFXIV_DEBUG_LOGIN=/tmp/login.html`, retry, and inspect the dump; this is an edge case on SE's side (malformed request or transient rejection), not a launcher bug in the normal flow. - **Login rejected with a clock/arg error right after a long session** — the uptime-derived key has a ~65-second validity window; log in again and launch promptly. - **Game path wrong** — it must be the folder *containing* `game/` (where `ffxiv_dx11.exe` lives), not `game/` itself. - **Dalamud enabled but nothing started** — check the `[dalamud]` / `[ffxiv-launcher]` lines in `launcher.log`: a version mismatch, failed integrity check, or injector error cancels the launch on purpose (no silent fallback to a plugin-less start). ## License MIT — see [LICENSE](LICENSE). This project is not affiliated with or endorsed by Square Enix; FFXIV and related marks belong to their respective owners.