> ## Documentation Index
> Fetch the complete documentation index at: https://ej3mplo.mintlify.site/llms.txt
> Use this file to discover all available pages before exploring further.

# Explore and downloads

> ejg.explore and ejg.downloads: the repack store and the download queue.

**Explore** is the launcher's repack store and **Downloads** is the queue of its
built-in torrent client (with the Install button when a download finishes). A theme
can paint both its own way. Declare this in `theme.json`:

```json theme={null}
"features": { "explore": true }
```

and the quick menu, **Ctrl+E**, **Ctrl+J** and the host's notices will ask it to
open its view instead of opening the launcher's generic windows:

```js theme={null}
ejg.ui.onView(({ view, slug }) => {
  // view: "explore" | "downloads" | "repack" (a page: slug)
});
```

Without `features.explore`, the theme does not have to do anything. The host opens
its own windows (and the theme can open them with `ejg.downloads.open("explore" | "downloads")`).

```js theme={null}
ejg.explore.enabled              // the user has Explore turned on
ejg.explore.onEnabled((on) => …)
await ejg.explore.home()         // { sections: [{ id: "today"|"week"|"month"|"latest", title, items }] }
await ejg.explore.search("elden", 1)   // { query, items, page, pages, total }; with no text: new releases
await ejg.explore.genres()       // [{ id, name, group: "genre" | "view" | "setting" }]
await ejg.explore.browse(filters, 1)   // the whole catalog with filters (same shape as search)
await ejg.explore.details(slug)  // page: screenshots [{thumb, full}], features, installSize, description
ejg.explore.openPage(slug)       // opens the page on the source's website
```

## Wishlist

The games the user wants. It is stored on the PC (in ejGames' database), one per
local profile, and it is the same in every theme and in the host's Explore window.
Each game carries its status, kept up to date (downloading, installed, in the
library…), like the other lists.

```js theme={null}
ejg.explore.wishlist.items              // [{ ...page, addedAt }], the latest added first
ejg.explore.wishlist.has(slug)
await ejg.explore.wishlist.toggle(slug) // true if it stays on the list (add and remove too)
ejg.explore.wishlist.onChange((items) => …)  // changes from any theme or the host
```

The kit has the usual pieces for painting it (`/_sdk/kit/store.js`): `WISH_SORTS`
and `sortWishlist(items, order)` (added, name, release, size) and `toggleWish(ejg, page)`,
which adds or removes it with a notice. Each theme names it in its own words. The
source strings are "Lista de deseados" on Steam and Moneda, "Lista de deseos" on
PS5, Xbox and Switch, "Deseados" on Cinema and "DESEADOS" on Retro. In English those
become Wishlist, or WISHLIST on Retro. `toggleWish(ejg, page, "tu lista de deseos")`
uses that name in the notice.

## Hypervisor cracks (HV)

Some repacks carry a **hypervisor** crack. They do not remove the game's protection.
They fool it with an unsigned driver that runs underneath Windows. To play them you
have to turn off Windows' virtualization-based security for a while (memory integrity,
Credential Guard, the Windows hypervisor, mandatory driver signing…) and turn it
back on when you are done. Those pages carry `hypervisor: true` (and the prepared
download, `p.hypervisor`).

Mark it next to the name wherever the game appears, and explain it on the page
before the download button. The kit has the text and the pieces, so each theme only
has to give them its skin:

```js theme={null}
import { HYPERVISOR, repackName, hypervisorTag, hypervisorInfo, openHypervisorGuide } from "/_sdk/kit/store.js";

repackName(r)                 // <b>Name</b>, or with HV: <b class="hv-name"><span class="hv-name-t">Name</span><span class="hv-tag">HV</span></b>
repackName(r, "h1", { class: "titulo" })
hypervisorTag(r)              // just the tag (null if it is not HV)
hypervisorInfo(ejg, r)        // <section class="hv">: a summary, and when expanded the requirements,
                              // what gets turned off, the steps and the risks, with a button to the
                              // full guide on the web (null if it is not HV)
HYPERVISOR.download           // a short warning for the download dialog
```

`repackName` puts the name in a `<span>` so it can be cut with "…" without taking the tag with it:

```css theme={null}
.hv-name { display: flex; align-items: baseline; gap: 6px; min-width: 0; }
.hv-name-t { min-width: 0; overflow: hidden; text-overflow: ellipsis; }
```

`hypervisorInfo(ejg, r, { cls, open, labels })` starts collapsed (`open: true` opens
it). `cls` prefixes all of its classes (`hv`, `hv-head`, `hv-badge`, `hv-title`,
`hv-sum`, `hv-body`, `hv-sec`, `hv-sec-<id>`, `hv-sec-title`, `hv-note`, `hv-list`,
`hv-foot`, `hv-more`, `hv-guide`; `is-open` when expanded) and `labels` changes the
`HYPERVISOR` texts (for example, into capitals). Its buttons carry `data-focus`.

## The catalog with filters

`browse(filters, page)` walks the whole catalog:

```js theme={null}
await ejg.explore.browse({
  query: "",                 // text in the title
  genres: [47, 59],          // up to 4 ids from genres(); games that have all of them
  sort: "date",              // "date" (new) | "modified" (recently updated) | "title" (A–Z)
  maxGb: 25,                 // maximum download size (null: any)
  hideOwned: true,           // without the ones already in the library or in downloads
}, 1);
```

With `maxGb` or `hideOwned` the filtering happens in the launcher, so one response
can read several pages of the website. Ask for the next one with `page + 1` **from
the response** (not yours) and, if `filtered` is `true`, `total` is an estimate.

`genres()` returns the genres in Spanish, with their group: `genre` (genres),
`view` (perspective: first person, isometric…) and `setting` (setting: fantasy,
cyberpunk…). Each `Repack` carries its ids in `tags`, so on a page you can open
the catalog filtered by its genres.

Each item (`Repack`) carries `title`, `version`, `cover` (vertical cover, a
thumbnail), `coverFull`, `hero` (a large screenshot; it can be missing), `genres`,
`companies`, `languages`, `originalSize`, `repackSize`, `selective`, `hypervisor`
and `status` (`{ state, downloadId, gameId, progress }`, with `state` = `none`,
`library`, `queued`, `downloading`, `paused`, `seeding`, `completed`, `installing`,
`installed` or `error`). The images already arrive as `ejg-media` URLs.

If the game is on the Steam store, it also carries its art: `capsule` (the
horizontal 460×215 capsule), `capsuleBig` (the large carousel one, 616×353) and
`library` (the vertical 600×900). The first time, ejGames looks it up in the
background and, once it has it, fires the `explore-art` event. The kit's
`createExplore` already asks for the front page and the search again. If you do
not use it:

```js theme={null}
ejg.on("explore-art", () => reloadTheStore());
```

Until it arrives (or if the game is not on Steam), use `cover` and `hero`.

```js theme={null}
ejg.downloads.all                // the in-memory list, up to date (at most once a second)
ejg.downloads.onChange((list) => …)
const p = await ejg.downloads.prepare(slug)   // the torrent's file list (it can take a while)
// p = { token, files: [{ index, path, size, kind, label, required, selected }], dir, freeBytes,
//       installSize, installDir, installFreeBytes, hypervisor }
await ejg.downloads.start(p.token, indices, dir)   // dir: "" or a folder from pickFolder()
ejg.downloads.cancelPrepare(slug)
ejg.downloads.pause(id) / resume(id)      // with no id: all of them
ejg.downloads.move(id, 0)                  // download this one first
ejg.downloads.remove(id, deleteFiles)
ejg.downloads.install(id)                  // opens the installer (asks for administrator)
ejg.downloads.locate(id)                   // after installing, choose the game's folder
ejg.downloads.openFolder(id)
ejg.downloads.deleteFiles(id)              // deletes the repack of a game already installed
await ejg.downloads.pickFolder(current)    // { path, freeBytes } or null
ejg.ui.keyboard({ title, value })          // on-screen keyboard: text, or null
```

A download's states: `queued` (waiting; `pauseReason` says why: `queue`, `playing`
or `install`), `downloading` (`checking` while it checks files), `paused` (the user
paused it), `seeding` and `completed` (ready to install), `installing`, `installed`
(`gameId`) and `error` (`error`). Also: `progress` (0..1), `doneBytes`, `totalBytes`,
`downBps`, `upBps`, `peers` and `eta` (seconds).

Each file's `kind`: `setup` and `core` are required. `selective` are languages and
`optional` are extras you can leave out (`selected` is the proposed selection:
English, the user's language, and nothing optional).

The kit has the shared logic in `/_sdk/kit/store.js`: `createExplore` (front page,
search that waits while you type, pages and cached game pages), `createDownloads`
(the queue, grouped), `fileSelection` (what is required cannot be removed; `bytes`,
`error`, `fits(free)`, `indices()`), `askQuery` (with a controller it opens the
on-screen keyboard), `repackAction` (which button is due: Download, Install, Play…),
`downloadLabel`, `canInstall`, `isActive`, `genreLabel` (genres arrive in English:
"Open world" → "Mundo abierto") and the formatters `bytes`, `speed`, `eta`,
`percent` and `sizeText` ("from 18 GB" → "desde 18 GB"). The Steam theme uses all
of it (`themes/steam/store.js`).

For the catalog, `createExplore` has `loadGenres()`, `browse(changes, wait)` (it
merges the changes with the current filters and goes back to the first page),
`browseMore()`, `toggleGenre(id)` (`false` if there are already four),
`clearFilters()` and `similar(repack)` (similar games: the ones that share its first
two genres), with the state in `state.catalog` (`filters`, `items`, `page`, `pages`,
`total`, `filtered`, `loading`, `error`) and `state.genres`. It also exports `SORTS`,
`SIZES`, `GENRE_GROUPS`, `MAX_GENRES`, `emptyFilters`, `filterCount` and
`describeFilters` ("Rol · Mundo abierto · Hasta 25 GB").

Inside the iframe, `confirm()`, `alert()` and `window.open()` do not work. Paint
your own dialogs.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.