diff --git a/.github/workflows/package.yml b/.github/workflows/package.yml
index 1acefe9d4..f6a8daa63 100644
--- a/.github/workflows/package.yml
+++ b/.github/workflows/package.yml
@@ -97,6 +97,24 @@ jobs:
if-no-files-found: error
archive: false
+ # The same setup, zipped with the recomplauncher.json RecompLauncher reads, so it unpacks into
+ # its own folder instead of being run as an installer. WiiCompiled-Setup.exe stays as it is.
+ - name: Zip for RecompLauncher
+ shell: pwsh
+ run: |
+ $stage = Join-Path $env:RUNNER_TEMP 'recomplauncher-zip'
+ New-Item -ItemType Directory -Force -Path $stage | Out-Null
+ Copy-Item Launcher/dist/WiiCompiled-Setup.exe $stage
+ Copy-Item Launcher/recomplauncher/windows.json (Join-Path $stage 'recomplauncher.json')
+ Compress-Archive -Path (Join-Path $stage '*') -DestinationPath Launcher/dist/WiiCompiled-Setup-windows-x64.zip
+
+ - uses: actions/upload-artifact@v7
+ with:
+ name: WiiCompiled-Setup-windows-x64-zip
+ path: Launcher/dist/WiiCompiled-Setup-windows-x64.zip
+ if-no-files-found: error
+ archive: false
+
macos-setup-package:
name: macOS (universal Setup.pkg)
runs-on: macos-15
@@ -307,7 +325,7 @@ jobs:
set -euo pipefail
ls -lR artifacts
assets=()
- for name in WiiCompiled-Setup.exe WiiCompiled-Setup-x86_64.AppImage WiiCompiled-Setup-aarch64.AppImage WiiCompiled-Setup.pkg WiiCompiled-Setup-macos.run; do
+ for name in WiiCompiled-Setup.exe WiiCompiled-Setup-windows-x64.zip WiiCompiled-Setup-x86_64.AppImage WiiCompiled-Setup-aarch64.AppImage WiiCompiled-Setup.pkg WiiCompiled-Setup-macos.run; do
found="$(find artifacts -type f -name "$name" | head -n 1)"
[ -n "$found" ] && [ -s "$found" ] || { echo "::error::missing release asset $name"; exit 1; }
assets+=("$found")
diff --git a/Launcher/WiiCompiled.Setup.Common/InstallProgress.cs b/Launcher/WiiCompiled.Setup.Common/InstallProgress.cs
index 1650d98df..84895ff23 100644
--- a/Launcher/WiiCompiled.Setup.Common/InstallProgress.cs
+++ b/Launcher/WiiCompiled.Setup.Common/InstallProgress.cs
@@ -95,6 +95,40 @@ private static void WriteLine(object value)
}
}
+///
+/// The --progress-lines format RecompLauncher reads from a setup step: one
+/// progress <0 to 1> <text> line per update on stdout, diagnostics on stderr. The
+/// outcome is the exit code.
+///
+public sealed class LauncherProgressInstallReporter : IInstallReporter
+{
+ private readonly Action? _log;
+ private readonly object _gate = new();
+ private int _lastPercent;
+
+ public LauncherProgressInstallReporter(Action? log = null) => _log = log;
+
+ public void Progress(string stage, string message, int percent)
+ {
+ lock (_gate)
+ {
+ // Monotonic, like the NDJSON reporter: the bar never walks backwards.
+ _lastPercent = Math.Clamp(Math.Max(percent, _lastPercent), 0, 99);
+ var text = message.ReplaceLineEndings(" ").Trim();
+ Console.Out.WriteLine(string.Create(System.Globalization.CultureInfo.InvariantCulture,
+ $"progress {_lastPercent / 100.0:0.00} {text}"));
+ Console.Out.Flush();
+ }
+ _log?.Invoke(message);
+ }
+
+ public void Diagnostic(string line)
+ {
+ Console.Error.WriteLine(line);
+ _log?.Invoke(line);
+ }
+}
+
/// Plain-text console reporting for a run without --progress-json.
public sealed class ConsoleInstallReporter : IInstallReporter
{
diff --git a/Launcher/WiiCompiled.Setup.Linux/Program.cs b/Launcher/WiiCompiled.Setup.Linux/Program.cs
index 3eea85ba5..efe7ff825 100644
--- a/Launcher/WiiCompiled.Setup.Linux/Program.cs
+++ b/Launcher/WiiCompiled.Setup.Linux/Program.cs
@@ -31,7 +31,15 @@ private static async Task RunAsync(string[] args, CancellationTokenSource c
var (command, flags) = ParseArgs(args);
if (command is null) { PrintUsage(); return 1; }
var progressJson = flags.ContainsKey("progress-json");
- IInstallReporter reporter = progressJson ? new NdjsonInstallReporter(ProductInfo.Version) : new ConsoleInstallReporter();
+ var progressLines = flags.ContainsKey("progress-lines");
+ if (progressJson && progressLines)
+ {
+ Console.Error.WriteLine("error: Choose only one of --progress-json and --progress-lines.");
+ return 1;
+ }
+ IInstallReporter reporter = progressJson ? new NdjsonInstallReporter(ProductInfo.Version)
+ : progressLines ? (IInstallReporter)new LauncherProgressInstallReporter()
+ : new ConsoleInstallReporter();
try
{
@@ -361,7 +369,8 @@ install [--game ISO_PATH] [--install-dir DIR] [--retro-dir DIR
{--download-retro-wfc-payload | --skip-retro-wfc-payload}]
[--force-clean-build] [--translator-bin PATH] [--disc-tool-bin PATH]
[--cc PATH] [--cxx PATH] [--fuse-ld NAME_OR_PATH] [--cmake PATH] [--ninja PATH]
- [--native-prebuilt-dir DIR] [--sysroot PATH] [--progress-json] [--workspace DIR]
+ [--native-prebuilt-dir DIR] [--sysroot PATH] [--workspace DIR]
+ [--progress-json | --progress-lines]
uninstall
launch-base
launch-retro
diff --git a/Launcher/WiiCompiled.Setup.Windows/CommandLine.cs b/Launcher/WiiCompiled.Setup.Windows/CommandLine.cs
index 14cd66409..4352a3e78 100644
--- a/Launcher/WiiCompiled.Setup.Windows/CommandLine.cs
+++ b/Launcher/WiiCompiled.Setup.Windows/CommandLine.cs
@@ -25,6 +25,7 @@ internal sealed record ModeRules
{
public string Flag { get; init; } = "this command";
public bool AcceptsProgressJson { get; init; }
+ public bool AcceptsProgressLines { get; init; }
public bool AcceptsGame { get; init; }
public bool RequiresGame { get; init; }
public bool AcceptsRetroDirectory { get; init; }
@@ -44,6 +45,7 @@ internal sealed class CommandLine
public string? InstallDirectory { get; private set; }
public bool Quiet { get; private set; }
public bool ProgressJson { get; private set; }
+ public bool ProgressLines { get; private set; }
public string? PayloadRootPath { get; private set; }
public bool Portable { get; private set; }
@@ -73,6 +75,7 @@ public static CommandLine Parse(string[] args)
case "--payload-root": result.PayloadRootPath = RequireValue(args, ref i); break;
case "--quiet": result.Quiet = true; break;
case "--progress-json": result.ProgressJson = true; break;
+ case "--progress-lines": result.ProgressLines = true; break;
case "--game": result.GamePath = RequireValue(args, ref i); break;
case "--retro-dir": result.RetroDirectoryPath = RequireValue(args, ref i); break;
case "--download-retro-wfc-payload":
@@ -100,7 +103,7 @@ public static CommandLine Parse(string[] args)
[AppMode.SilentInstall] = new ModeRules
{
Flag = "--silent",
- AcceptsProgressJson = true, AcceptsGame = true, RequiresGame = true,
+ AcceptsProgressJson = true, AcceptsProgressLines = true, AcceptsGame = true, RequiresGame = true,
AcceptsRetroDirectory = true, AcceptsPayloadMode = true,
AcceptsPortable = true
},
@@ -149,6 +152,9 @@ void Reject(bool present, string option)
Reject(GamePath is not null && !rules.AcceptsGame, "--game");
Reject(RetroDirectoryPath is not null && !rules.AcceptsRetroDirectory, "--retro-dir");
Reject(ProgressJson && !rules.AcceptsProgressJson, "--progress-json");
+ Reject(ProgressLines && !rules.AcceptsProgressLines, "--progress-lines");
+ if (ProgressJson && ProgressLines)
+ throw new ArgumentException("Choose only one of --progress-json and --progress-lines.");
Reject(PayloadRootPath is not null && !rules.RequiresPayloadRoot, "--payload-root");
Reject(Portable && !rules.AcceptsPortable, "--portable");
Reject(RetroWfcPayloadMode != RetroWfcPayloadMode.NotApplicable && !rules.AcceptsPayloadMode,
diff --git a/Launcher/WiiCompiled.Setup.Windows/ConsoleCommands.cs b/Launcher/WiiCompiled.Setup.Windows/ConsoleCommands.cs
index 02ab9a2a2..6fd00d0c0 100644
--- a/Launcher/WiiCompiled.Setup.Windows/ConsoleCommands.cs
+++ b/Launcher/WiiCompiled.Setup.Windows/ConsoleCommands.cs
@@ -12,6 +12,7 @@ public static int Help()
Console.Out.WriteLine();
Console.Out.WriteLine("Commands:");
Console.Out.WriteLine(" --silent --game --install-dir [--retro-dir ] [--portable]");
+ Console.Out.WriteLine(" [--progress-json | --progress-lines]");
Console.Out.WriteLine(" --verify-inputs --game [--retro-dir ]");
Console.Out.WriteLine(" --check-products [--install-dir ] [--retro-dir ] [--progress-json]");
Console.Out.WriteLine(" --repair-products --install-dir --retro-dir " +
@@ -106,7 +107,9 @@ void Log(string message)
File.WriteAllText(logPath, $"{ProductInfo.Name} setup {ProductInfo.Version}{Environment.NewLine}");
var ndjson = command.ProgressJson ? new NdjsonInstallReporter(ProductInfo.Version, Log) : null;
- IInstallReporter reporter = ndjson ?? (IInstallReporter)new ConsoleInstallReporter(Log);
+ IInstallReporter reporter = ndjson ?? (command.ProgressLines
+ ? (IInstallReporter)new LauncherProgressInstallReporter(Log)
+ : new ConsoleInstallReporter(Log));
var installDirectory = Path.GetFullPath(command.InstallDirectory!);
try
{
diff --git a/Launcher/WiiCompiled.Setup.Windows/SelfTests.cs b/Launcher/WiiCompiled.Setup.Windows/SelfTests.cs
index f688a8acd..dcffe7ed4 100644
--- a/Launcher/WiiCompiled.Setup.Windows/SelfTests.cs
+++ b/Launcher/WiiCompiled.Setup.Windows/SelfTests.cs
@@ -543,6 +543,16 @@ void Rejects(string description, params string[] args)
if (portable.Mode != AppMode.SilentInstall || !portable.Portable)
throw new Exception("The portable install contract did not parse.");
+ var lines = CommandLine.Parse([
+ "--silent", "--game", "game.iso", "--install-dir", "D:\\Games\\Recomp\\Install",
+ "--portable", "--progress-lines"
+ ]);
+ if (lines.Mode != AppMode.SilentInstall || !lines.ProgressLines || lines.ProgressJson)
+ throw new Exception("The progress-lines install contract did not parse.");
+ Rejects("--progress-lines with --progress-json", "--silent", "--game", "g.iso",
+ "--progress-lines", "--progress-json");
+ Rejects("--progress-lines outside an install", "--check-products", "--progress-lines");
+
Rejects("--portable without an install directory", "--silent", "--game", "g.iso", "--portable");
Rejects("--portable outside an install", "--check-products", "--install-dir", "fixed", "--portable");
// The config verbs are gone: WheelWizard reads and writes Config.toml directly now.
diff --git a/Launcher/build-appimage.sh b/Launcher/build-appimage.sh
index 2f6b2bbaa..cf5df2d17 100755
--- a/Launcher/build-appimage.sh
+++ b/Launcher/build-appimage.sh
@@ -263,6 +263,9 @@ exec "$HERE/usr/bin/wiicompiled-setup" --workspace "$CACHE" \
APPRUN
chmod +x "$appdir/AppRun"
+# RecompLauncher reads this straight out of the AppImage to set the game up and start it.
+cp "$script_dir/recomplauncher/linux.json" "$appdir/recomplauncher.json"
+
echo "Writing desktop entry and icon..."
cat > "$appdir/wiicompiled-setup.desktop" <<'DESKTOP'
[Desktop Entry]
diff --git a/Launcher/recomplauncher/linux.json b/Launcher/recomplauncher/linux.json
new file mode 100644
index 000000000..8ebab1476
--- /dev/null
+++ b/Launcher/recomplauncher/linux.json
@@ -0,0 +1,119 @@
+{
+ "$schema": "https://recomplauncher.com/schema/recomplauncher.json",
+ "format": 1,
+ "name": "Mario Kart WiiCompiled",
+ "repo": "patchzyy/Wiicompiled",
+ "game": {
+ "kind": "iso",
+ "label": "Your own disc image of Mario Kart Wii: the PAL (RMCP01), USA (RMCE01) or Japan (RMCJ01) release, a clean dump as .iso, .gcm, .gcz, .ciso, .wbfs, .wia or .rvz.",
+ "check": { "ids": ["RMCP01", "RMCE01", "RMCJ01"] }
+ },
+ "prepare": {
+ "program": ["WiiCompiled-Setup-x86_64.AppImage", "WiiCompiled-Setup-aarch64.AppImage"],
+ "args": ["install", "--game", "{game}", "--progress-lines"],
+ "label": "Translating and compiling the game from your disc. This takes a while, and only happens once",
+ "timeout": 10800
+ },
+ "start": {
+ "program": ["WiiCompiled-Setup-x86_64.AppImage", "WiiCompiled-Setup-aarch64.AppImage"],
+ "args": ["launch-base"]
+ },
+ "config": [
+ {
+ "id": "main",
+ "format": "toml",
+ "file": [{ "path": "{local}/WiiCompiled/Config.toml" }]
+ }
+ ],
+ "saves": ["{local}/WiiCompiled/NAND"],
+ "folders": {
+ "saves": [{ "path": "{local}/WiiCompiled/NAND" }],
+ "logs": [{ "path": "{local}/WiiCompiled/Logs" }]
+ },
+ "settings": [
+ {
+ "id": "display_mode", "label": "Window", "group": "Video",
+ "help": "Windowed, borderless fullscreen, or exclusive fullscreen, which asks your screen for the closest mode to the game's frame rate.",
+ "choices": [
+ { "label": "Windowed", "set": { "video/display_mode": "\"windowed\"" } },
+ { "label": "Borderless", "set": { "video/display_mode": "\"borderless\"" } },
+ { "label": "Exclusive", "set": { "video/display_mode": "\"exclusive\"" } }
+ ],
+ "defaults": { "video/display_mode": "\"windowed\"" }
+ },
+ {
+ "id": "resolution", "label": "Resolution", "group": "Video",
+ "help": "What the game draws at. Native is the Wii's own. Auto follows the size of the window. Higher is sharper and needs a faster graphics card.",
+ "choices": [
+ { "label": "Auto", "set": { "video/resolution_multiplier": "0.0" } },
+ { "label": "Native", "set": { "video/resolution_multiplier": "1.0" } },
+ { "label": "1.5x", "set": { "video/resolution_multiplier": "1.5" } },
+ { "label": "2x", "set": { "video/resolution_multiplier": "2.0" } },
+ { "label": "3x", "set": { "video/resolution_multiplier": "3.0" } },
+ { "label": "4x", "set": { "video/resolution_multiplier": "4.0" } },
+ { "label": "6x", "set": { "video/resolution_multiplier": "6.0" } },
+ { "label": "8x", "set": { "video/resolution_multiplier": "8.0" } }
+ ],
+ "defaults": { "video/resolution_multiplier": "1.0" }
+ },
+ {
+ "id": "aspect", "label": "Aspect ratio", "group": "Video",
+ "help": "16:9 for widescreen screens, 4:3 for the original shape. Takes effect the next time the game starts.",
+ "choices": [
+ { "label": "16:9", "set": { "video/widescreen": "true" } },
+ { "label": "4:3", "set": { "video/widescreen": "false" } }
+ ],
+ "defaults": { "video/widescreen": "true" }
+ },
+ {
+ "id": "interpolation", "label": "Race frame rate", "group": "Video",
+ "help": "The game runs at 60 FPS. Interpolation adds frames in between during races for 120 or 144 Hz screens. It is experimental and can show artifacts.",
+ "choices": [
+ { "label": "60 FPS", "set": { "video/frame_interpolation_fps": "0" } },
+ { "label": "120 FPS", "set": { "video/frame_interpolation_fps": "120" } },
+ { "label": "180 FPS", "set": { "video/frame_interpolation_fps": "180" } }
+ ],
+ "defaults": { "video/frame_interpolation_fps": "0" }
+ },
+ {
+ "id": "vsync", "label": "V-Sync", "group": "Video",
+ "help": "Waits for the screen's refresh. Turn it on if frame interpolation looks uneven on your screen.",
+ "choices": [
+ { "label": "Off", "set": { "video/vsync": "false" } },
+ { "label": "On", "set": { "video/vsync": "true" } }
+ ],
+ "defaults": { "video/vsync": "false" }
+ },
+ {
+ "id": "fps", "label": "Show FPS", "group": "Video",
+ "help": "The frame rate in a corner of the game.",
+ "choices": [
+ { "label": "On", "set": { "video/show_fps": "true" } },
+ { "label": "Off", "set": { "video/show_fps": "false" } }
+ ],
+ "defaults": { "video/show_fps": "true" }
+ },
+ {
+ "id": "mute", "label": "Sound", "group": "Audio",
+ "help": "Turn all of the game's sound off or on.",
+ "choices": [
+ { "label": "On", "set": { "audio/muted": "false" } },
+ { "label": "Muted", "set": { "audio/muted": "true" } }
+ ],
+ "defaults": { "audio/muted": "false" }
+ },
+ {
+ "id": "ducking", "label": "Music while other audio plays", "group": "Audio",
+ "help": "Mutes the game's music while another app, like a video or a music player, is playing sound.",
+ "choices": [
+ { "label": "Keep playing", "set": { "audio/attenuate_music_when_media_plays": "false" } },
+ { "label": "Mute music", "set": { "audio/attenuate_music_when_media_plays": "true" } }
+ ],
+ "defaults": { "audio/attenuate_music_when_media_plays": "false" }
+ }
+ ],
+ "actions": [
+ { "id": "saves", "label": "Saves", "help": "Your Mario Kart Wii save data.", "open": "saves" },
+ { "id": "logs", "label": "Logs", "help": "The log of each run, for bug reports.", "open": "logs" }
+ ]
+}
diff --git a/Launcher/recomplauncher/windows.json b/Launcher/recomplauncher/windows.json
new file mode 100644
index 000000000..43fb64dd0
--- /dev/null
+++ b/Launcher/recomplauncher/windows.json
@@ -0,0 +1,119 @@
+{
+ "$schema": "https://recomplauncher.com/schema/recomplauncher.json",
+ "format": 1,
+ "name": "Mario Kart WiiCompiled",
+ "repo": "patchzyy/Wiicompiled",
+ "game": {
+ "kind": "iso",
+ "label": "Your own disc image of Mario Kart Wii: the PAL (RMCP01), USA (RMCE01) or Japan (RMCJ01) release, a clean dump as .iso, .gcm, .gcz, .ciso, .wbfs, .wia or .rvz.",
+ "check": { "ids": ["RMCP01", "RMCE01", "RMCJ01"] }
+ },
+ "prepare": {
+ "program": ["WiiCompiled-Setup.exe"],
+ "args": ["--silent", "--game", "{game}", "--install-dir", "{install}/WiiCompiled/Install", "--portable", "--progress-lines"],
+ "label": "Translating and compiling the game from your disc. This takes a while, and only happens once",
+ "timeout": 10800
+ },
+ "start": {
+ "program": ["WiiCompiled/Install/WiiCompiled-Setup.exe"],
+ "args": ["--launch-base"]
+ },
+ "config": [
+ {
+ "id": "main",
+ "format": "toml",
+ "file": [{ "path": "{install}/WiiCompiled/UserData/Config.toml" }]
+ }
+ ],
+ "saves": ["{install}/WiiCompiled/UserData/NAND"],
+ "folders": {
+ "saves": [{ "path": "{install}/WiiCompiled/UserData/NAND" }],
+ "logs": [{ "path": "{install}/WiiCompiled/UserData/Logs" }]
+ },
+ "settings": [
+ {
+ "id": "display_mode", "label": "Window", "group": "Video",
+ "help": "Windowed, borderless fullscreen, or exclusive fullscreen, which asks your screen for the closest mode to the game's frame rate.",
+ "choices": [
+ { "label": "Windowed", "set": { "video/display_mode": "\"windowed\"" } },
+ { "label": "Borderless", "set": { "video/display_mode": "\"borderless\"" } },
+ { "label": "Exclusive", "set": { "video/display_mode": "\"exclusive\"" } }
+ ],
+ "defaults": { "video/display_mode": "\"windowed\"" }
+ },
+ {
+ "id": "resolution", "label": "Resolution", "group": "Video",
+ "help": "What the game draws at. Native is the Wii's own. Auto follows the size of the window. Higher is sharper and needs a faster graphics card.",
+ "choices": [
+ { "label": "Auto", "set": { "video/resolution_multiplier": "0.0" } },
+ { "label": "Native", "set": { "video/resolution_multiplier": "1.0" } },
+ { "label": "1.5x", "set": { "video/resolution_multiplier": "1.5" } },
+ { "label": "2x", "set": { "video/resolution_multiplier": "2.0" } },
+ { "label": "3x", "set": { "video/resolution_multiplier": "3.0" } },
+ { "label": "4x", "set": { "video/resolution_multiplier": "4.0" } },
+ { "label": "6x", "set": { "video/resolution_multiplier": "6.0" } },
+ { "label": "8x", "set": { "video/resolution_multiplier": "8.0" } }
+ ],
+ "defaults": { "video/resolution_multiplier": "1.0" }
+ },
+ {
+ "id": "aspect", "label": "Aspect ratio", "group": "Video",
+ "help": "16:9 for widescreen screens, 4:3 for the original shape. Takes effect the next time the game starts.",
+ "choices": [
+ { "label": "16:9", "set": { "video/widescreen": "true" } },
+ { "label": "4:3", "set": { "video/widescreen": "false" } }
+ ],
+ "defaults": { "video/widescreen": "true" }
+ },
+ {
+ "id": "interpolation", "label": "Race frame rate", "group": "Video",
+ "help": "The game runs at 60 FPS. Interpolation adds frames in between during races for 120 or 144 Hz screens. It is experimental and can show artifacts.",
+ "choices": [
+ { "label": "60 FPS", "set": { "video/frame_interpolation_fps": "0" } },
+ { "label": "120 FPS", "set": { "video/frame_interpolation_fps": "120" } },
+ { "label": "180 FPS", "set": { "video/frame_interpolation_fps": "180" } }
+ ],
+ "defaults": { "video/frame_interpolation_fps": "0" }
+ },
+ {
+ "id": "vsync", "label": "V-Sync", "group": "Video",
+ "help": "Waits for the screen's refresh. Turn it on if frame interpolation looks uneven on your screen.",
+ "choices": [
+ { "label": "Off", "set": { "video/vsync": "false" } },
+ { "label": "On", "set": { "video/vsync": "true" } }
+ ],
+ "defaults": { "video/vsync": "false" }
+ },
+ {
+ "id": "fps", "label": "Show FPS", "group": "Video",
+ "help": "The frame rate in a corner of the game.",
+ "choices": [
+ { "label": "On", "set": { "video/show_fps": "true" } },
+ { "label": "Off", "set": { "video/show_fps": "false" } }
+ ],
+ "defaults": { "video/show_fps": "true" }
+ },
+ {
+ "id": "mute", "label": "Sound", "group": "Audio",
+ "help": "Turn all of the game's sound off or on.",
+ "choices": [
+ { "label": "On", "set": { "audio/muted": "false" } },
+ { "label": "Muted", "set": { "audio/muted": "true" } }
+ ],
+ "defaults": { "audio/muted": "false" }
+ },
+ {
+ "id": "ducking", "label": "Music while other audio plays", "group": "Audio",
+ "help": "Mutes the game's music while another app, like a video or a music player, is playing sound.",
+ "choices": [
+ { "label": "Keep playing", "set": { "audio/attenuate_music_when_media_plays": "false" } },
+ { "label": "Mute music", "set": { "audio/attenuate_music_when_media_plays": "true" } }
+ ],
+ "defaults": { "audio/attenuate_music_when_media_plays": "false" }
+ }
+ ],
+ "actions": [
+ { "id": "saves", "label": "Saves", "help": "Your Mario Kart Wii save data.", "open": "saves" },
+ { "id": "logs", "label": "Logs", "help": "The log of each run, for bug reports.", "open": "logs" }
+ ]
+}
diff --git a/README.md b/README.md
index 7c31b9029..d54b7df0b 100644
--- a/README.md
+++ b/README.md
@@ -14,6 +14,9 @@
+
+
+
A native PC port of Mario Kart Wii, made with static recompilation.