Skip to content
Copied to clipboard!
Launcher API

Launcher WebView API

Customize the launcher UI with JS commands, message format, and required HTML structure.

Overview

The launcher displays two WebView pages: the main index (layout/index.php) and the settings window (layout/settings.php). Communication is done via window.chrome.webview.postMessage (JS → C#) and C# calls to ExecuteScriptAsync / PostWebMessageAsJson (C# → JS).

Message format (JS → C#): Send a JSON string: {"action": "actionName", "value": "optionalValue"}. The C# side expects this exact shape.

Creating Your Template

To create your own launcher theme without our help, follow these steps. The launcher loads your UI from a web server — your template is just HTML, CSS, and JavaScript.

Step 1: Host your files

Host two pages on a web server (HTTP/HTTPS):

  • layout/index.php — main launcher UI (can be .html if your server serves it at that path)
  • layout/settings.php — settings window UI

The launcher loads from baseUrl + "layout/index.php" and baseUrl + "layout/settings.php". Your baseUrl is set in config.EMU (see Server Setup).

Step 2: Index page — implement the 4 functions

C# calls these on window. Define them in your JS before DOMContentLoaded:

  • window.updateDownloadProgress(progress, speed)
  • window.updateProgressText(text)
  • window.setWindowMode(mode)
  • window.setOnlineCount(count)

Step 3: Index page — attach event handlers

The C# launcher does not inject any script. Your template must attach all click handlers and call postMessage. On DOMContentLoaded:

  • #closesendMessage('close')
  • #minimizesendMessage('minimize')
  • #openSettingssendMessage('openSettings')
  • #openGamesendMessage('openGame')
  • #window (checkbox) → sendMessage(checked ? 'checked' : 'unchecked')
  • Links: use class="openLink" on <a href="url">. On click, sendMessage('openLink', element.href)
  • Window drag: bind mousedown on your title bar area. Exclude buttons/inputs. Call sendMessage('dragWindow')

Step 4: Settings page

Implement window.setSettings(obj) and window.setWindowMode(mode). On load, send getSettings. Listen for event.data.type === 'getSettings' and call setSettings(event.data.value). Bind change handlers for each field to send resolution, color, sound, music, username, language. Bind #saveSettings to sendMessage('saveSettings').

Step 5: Helper and message listener

Add a helper and wire the message listener:

function sendMessage(action, value = '') {
  window.chrome.webview.postMessage(JSON.stringify({ action, value }));
}
window.chrome.webview.addEventListener('message', event => {
  const d = event.data;
  if (d?.type === 'getSettings') window.setSettings(d.value);
  if (d?.type === 'windowMode') window.setWindowMode(d.value);
});

Server Setup

The launcher reads config.EMU in the exe directory. It must contain a Base64-encoded URL. Example:

baseUrl=<Base64 of your URL, e.g. "https://yourserver.com/launcher/">

The server must serve config.json at baseUrl + "config.json". Required keys:

  • updateHost — must equal baseUrl (no trailing slash). Used for validation.
  • lastUpdate — number. Used for update check.
  • indexWidth, indexHeight — main window size in pixels.
  • settingsWidth, settingsHeight — settings window size.

Your server must also serve checksum.json for the game launch flow. File paths loaded by the launcher:

  • {baseUrl}config.json
  • {baseUrl}layout/index.php
  • {baseUrl}layout/settings.php
  • {baseUrl}updates/{N}.exe (for updates)
  • {baseUrl}checksum.json

Index Page (Main Launcher)

Functions your page must implement

C# calls these functions. Implement them on window.

window.updateDownloadProgress(progress, speed)

progress = 0–100 (number). speed = string (e.g. "1.5 MB/s") or "" to hide. When progress >= 100 or speed === '', the C# launcher expects you to enable the "Open Game" button.

window.updateProgressText(text)

Updates the status text (e.g. "No updates available. You can play now!").

window.setWindowMode(mode)

mode = 0 (fullscreen) or 1 (window). Set checkbox #window checked when mode === 1.

window.setOnlineCount(count)

Called when the Launcher Server sends the online player count. count = number of connected users. Use {{ONLINE_COUNT}} in your HTML as a placeholder (replaced on first call), or elements with class .launcher-online-count. Sent every 30 seconds and on connect/disconnect.

Optional: window.initializeConfig(config) — C# may send config JSON; store it for your UI.

Settings Page

Functions your page must implement

window.setSettings(settingsObject)

Receives: resolution (number), colorDepth (number), soundOnOff (1/0), musicOnOff (1/0), username (string), language (string), windowMode (0 or 1). Update form inputs from this object.

window.setWindowMode(mode)

Same as index: mode 0 or 1; set checkbox #window checked when mode === 1.

On load, send "getSettings". C# responds with { type: "getSettings", value: { ... } }. Use window.chrome.webview.addEventListener('message', ...) and call setSettings(event.data.value) when event.data.type === 'getSettings'.

Messages: JS → C#

Send with: window.chrome.webview.postMessage(JSON.stringify({ action: "actionName", value: "..." })). For openLink, pass the full URL as value.

Action Value Page Description
closeIndexCloses the launcher.
minimizeIndexMinimizes to system tray.
openSettingsIndexOpens the settings window.
openLinkURL stringIndexOpens the URL in the default browser.
checked / uncheckedBothWindow mode (1) / fullscreen (0).
openGameIndexRuns checksum and starts the game.
dragWindowIndexStarts window drag.
getConfigIndexC# responds with the full config.json object (updateHost, lastUpdate, etc.).
index-dataIndexC# responds with { type: "windowMode", value: 0|1 }. Call on load to sync window mode checkbox.
getSettingsSettingsC# responds with getSettings payload.
resolution, color, sound, music, username, languagestringSettingsUpdate registry/settings.
saveSettingsSettingsCloses the settings window.

C# → JS

Functions the launcher calls on your page.

  • Index: setWindowMode, updateDownloadProgress, updateProgressText, setOnlineCount. Plus JSON for getConfig and index-data.
  • Settings: setSettings, setWindowMode. Response to getSettings has type: "getSettings" and value: { ... }.

Launcher Behavior

Main.exe exit when launcher closes

When the user closes the launcher, if main.exe is still running, the launcher sends WM_CLOSE to the game's main window — a graceful close request (like clicking the window's X). This allows the game to save and shut down properly instead of a forced Kill(), which could cause rollbacks.

Online count (Launcher Server)

The Launcher Server sends ONLINE_COUNT|N to the Launcher:

  • Every 30 seconds — timer-based broadcast.
  • On connect/disconnect — real-time update.
  • Count uses max(connectedClients.Count, DB count) when DB has a higher value.

The Launcher calls window.setOnlineCount(count) so the WebView updates.

Required HTML Structure

Index page

  • IDs: #close, #minimize, #openSettings, #openGame.
  • Links: use class="openLink" on <a href="url"> elements. On click, send openLink with the href value.
  • Window mode: #window checkbox.
  • Progress: .progress-bar (width in %), .progressBar strong or similar for status text, span.status for speed.
  • Online count: {{ONLINE_COUNT}} placeholder (replaced on first call) or elements with class .launcher-online-count.
  • Window drag: attach mousedown to your title bar / drag region. Call sendMessage('dragWindow'). Exclude inputs/buttons.

Settings page

  • Fields: #resolution, #color, #sound, #music, #username, #language, #window.
  • Button: #saveSettings.

Minimal Template Example

A minimal index page structure. Your CSS can style it however you like.

<!-- Minimal index.html structure -->
<div class="titlebar">
  <span id="close">×</span>
  <span id="minimize">−</span>
  <span class="drag-region">Launcher</span>
</div>
<div class="content">
  <a href="https://example.com" class="openLink">Link</a>
  <button id="openSettings">Settings</button>
  <div class="progressBar">
    <div class="progress-bar" style="width:0%"></div>
    <strong>Checking...</strong>
    <span class="status"></span>
  </div>
  <label><input type="checkbox" id="window"> Window mode</label>
  <button id="openGame" disabled>Open Game</button>
  <span class="launcher-online-count">0</span> online
</div>
<script>
function sendMessage(action, value = '') {
  window.chrome.webview.postMessage(JSON.stringify({ action, value }));
}
window.updateDownloadProgress = (p, s) => {
  document.querySelector('.progress-bar').style.width = p + '%';
  document.querySelector('.status').textContent = s || '';
  if (p >= 100) document.querySelector('#openGame').disabled = false;
};
window.updateProgressText = (t) => { document.querySelector('.progressBar strong').textContent = t; };
window.setWindowMode = (m) => { document.querySelector('#window').checked = m === 1; };
window.setOnlineCount = (n) => { document.querySelectorAll('.launcher-online-count').forEach(el => el.textContent = n); };
document.addEventListener('DOMContentLoaded', () => {
  document.querySelector('#close').onclick = () => sendMessage('close');
  document.querySelector('#minimize').onclick = () => sendMessage('minimize');
  document.querySelector('#openSettings').onclick = () => sendMessage('openSettings');
  document.querySelector('#openGame').onclick = () => sendMessage('openGame');
  document.querySelector('#window').onchange = (e) => sendMessage(e.target.checked ? 'checked' : 'unchecked');
  document.querySelectorAll('.openLink').forEach(link => link.onclick = (e) => { e.preventDefault(); sendMessage('openLink', link.href); });
  document.querySelector('.drag-region').onmousedown = () => sendMessage('dragWindow');
  window.chrome.webview.addEventListener('message', e => {
    if (e.data?.type === 'windowMode') window.setWindowMode(e.data.value);
  });
  sendMessage('index-data');
});
</script>

Quick Reference

function sendMessage(action, value = '') {
  window.chrome.webview.postMessage(JSON.stringify({ action, value }));
}
sendMessage('close');
sendMessage('openLink', 'https://example.com');
sendMessage('checked');  // window mode on
sendMessage('unchecked'); // fullscreen