Skip to content

Launcher: architecture, library & game sessions

Follow the Qt interface, background scanning, artwork pipeline and process handoff into gameplay.

7 min read Updated 28 Oct 2026

On this page

The launcher is the part of GBAx that turns a folder of game files into a usable collection. It shows covers, remembers favourites and recent games, helps you find a title, and opens the selected game. Those responsibilities are separate from emulating the GBA’s processor and graphics hardware. A library scan can finish successfully even if a particular game later exposes an emulation problem.

This article follows the C++ implementation in GBAx 2.0.0 Preview 1. Its central class is LauncherWindow in the Qt frontend. Understanding that class, the application entry point and the game-launch boundary gives a practical map of how the desktop experience is assembled.

A native Qt interface

The launcher uses Qt widgets: buttons for navigation, a list widget for the cover grid, labels for artwork and details, and layouts that arrange those controls as the window changes size. Qt’s signals and slots connect events to actions. Clicking the Play button invokes launchSelected; selecting a different item updates the details panel; changing the search text schedules filtering.

The interface is built by the application, rather than loading the WordPress website into a desktop window. The website, its game catalogue and the EmulatorJS player are separate products. Updating a website page does not replace the native launcher code.

Several short timers combine repeated events before doing expensive work. Search changes, selection changes and resize events have debounce timers. For example, typing a title quickly need not rebuild the grid independently for every intermediate keystroke. The final search text is applied after the short delay, reducing redundant interface work.

What each library view represents

View Source of information Practical meaning
Installed Files discovered in the chosen ROM folder, plus maintained local links. A local file is available to launch; its presence does not certify compatibility.
Favourites A saved set of selected file paths. A personal subset of the library, without copying the games.
Recently Played The most-recently-used list and recorded session information. A route back to games you opened previously.
Browse Games The built-in catalogue and its links to available game files. A catalogue entry and an installed ROM are different things.
Settings Launcher preferences and entry points to emulator configuration. Library, artwork and appearance controls alongside access to gameplay settings.

Favourites are stored using file paths. Moving or renaming a game can therefore affect its association with a favourite or recent entry. Refreshing the library discovers the new location; it is not the same operation as migrating every item of personal metadata to a new identity.

How a folder becomes a library

refreshLibrary reads the configured ROM directory and whether subfolders should be included. It can display previously cached paths first, then starts a scan to validate those paths and discover changes. This provides useful startup behaviour without treating yesterday’s cache as proof that a file still exists.

The active scan runs on a worker created with QThread::create. A directory iterator looks for supported game and archive extensions: .gba, .gb, .gbc, .zip and .7z. Symbolic links are excluded by this scan. Archive entries are checked for a supported ROM rather than accepting every ZIP as a game.

Archive checks are cached against a signature containing file size and modification time. If that signature changes, the archive is inspected again. This is a performance shortcut for discovery, not a cryptographic integrity check. Paths are normalised and duplicates are removed before the results become the current library.

  1. ChooseRead the folder and subfolder preference.
  2. DiscoverA worker enumerates files and checks archives.
  3. ReconcileNormalise paths, remove duplicates and update the index.
  4. DisplayFilter the collection and populate its visible items.
The GUI receives progress and completion through queued Qt events. The worker does not directly repaint the cover grid.

The scan has a cancellation flag, and completion is handled after the worker stops. Progress notifications are batched during large scans to avoid flooding the GUI’s event queue. These details matter because finding thousands of files and responding to a controller are competing demands on the application’s responsiveness.

How artwork appears without blocking browsing

Game discovery and artwork retrieval use different paths. A game can be playable while its cover is still missing. The launcher checks local artwork, consults artwork indexes and queues missing downloads when automatic downloading is enabled or you request it manually.

Network requests use Qt’s network manager. Parsing indexes and decoding images can run through QtConcurrent on an artwork worker pool, which is limited to two threads in this revision. The interface keeps caches for cover paths, pixmaps and backdrops so it does not repeatedly decode the same image whenever a card becomes visible.

Generation identifiers help reject work that belongs to an older refresh. Suppose you change ROM folders while an image is being processed: that completion should not blindly update the new selection. UI refreshes are also collected into batches. Together, background work, caching and careful handoff make downloading covers compatible with continued browsing.

The release stores new artwork in the user’s GBAx configuration area, outside the installed application bundle. Use Open Artwork Folder to inspect the location on your machine. This separation is especially useful for read-only AppImages and signed Mac bundles: installing the application and maintaining personal artwork are different operations.

What happens when you press Play

In Preview 1, the launcher starts a separate process of the same GBAx executable using QProcess. It first rejects an empty selection, an already pending launch, another running game, or a path that no longer points to a file. It saves the launcher window’s geometry and fullscreen state so they can be restored after gameplay.

The launch arguments include --launcher and the absolute game path. Fullscreen adds -f; choosing a quick-save slot can add --savestate with the state-file path. Configuration is written before the process starts. The game process then follows the application’s game-window path, where the frontend and core controller handle emulation.

  1. Select a fileThe launcher resolves the card or save slot.
  2. Start GBAxQProcess launches the game session with explicit arguments.
  3. Run the gameThe game frontend drives the mGBA-derived core.
  4. ReturnExit handling reloads settings and restores the launcher.
The launch is asynchronous. Process signals report startup, completion or failure without making the GUI wait in a blocking loop.

The most-recently-used list is updated when the process reports that it started. If startup fails or the process crashes, the launcher restores its controls and reports the process error. A successful process start is not itself a compatibility test for the game.

Controller navigation and communication between windows

The launcher maps controller actions to interface operations such as moving the selection, confirming, backing out and opening another sidebar section. The game frontend separately maps input to the emulated console. This is why controller navigation and in-game button mapping should be tested independently.

The application entry point also sets up local Qt server/socket communication for launcher and game-window commands. These are local control messages used to coordinate the desktop experience, including bringing the launcher forward. They are not a GBA link-cable implementation and do not provide internet multiplayer.

Use the controller guide for the actual shortcuts. The architecture here explains why a focus or navigation problem can belong to the launcher while a wrong in-game button belongs to the input mapping in the game frontend.

Preferences, caches and reset behaviour

The configuration controller holds preferences such as the ROM directory, appearance choices and favourites. JSON-backed launcher data includes cached library discovery and catalogue-to-file associations. These are different kinds of data from the game’s battery save or an emulator save state.

Clear Launcher Cache rebuilds generated thumbnails and temporary launcher data while retaining downloaded/custom artwork, ROMs, favourites, saves and settings. Reset GBAx (Clean Start) closes the application, resets settings and the library index, and reopens first-run setup; its confirmation states that ROMs, saves, artwork and other files are kept. Close the running game first and keep normal backups of valuable progress.

Performance Mode reduces launcher blur and heavy visual effects. It can make the interface less demanding, but it does not change the meaning of a CPU instruction or repair a game-specific emulation fault. For those mechanisms, continue with the emulation overview.

Where to begin in the code

Read LauncherWindow in small paths rather than from top to bottom: constructor connections → selected action → state update → UI refresh. For scanning, follow refreshLibrary, startLibraryScan and finalisation. For gameplay, follow launchSelected into launchPath, then the command-line handling in main.cpp. For artwork, follow the queue, network result, worker task and queued UI refresh.

This approach exposes the boundaries that a change needs to respect. A cover-layout adjustment should not alter ROM identity. A scan improvement must handle cancellation and window lifetime. A launch change must preserve arguments, error handling and the return path. Those are concrete behaviours to test when extending the launcher.

Sources & implementation

Implementation references follow GBAx 2.0.0 Preview 1, commit c1a826ae. The hardware implementation derives from mGBA; these links point to the matching GBAx source, rather than assuming a newer upstream revision behaves identically.

Need help with a specific problem? Start with troubleshooting →