On this page

← All documentation

Mod and skin catalogue (Download page)

The site's mod and skin catalogue: the API contract, one-click install, verification.

The Download page has two additional tabs, Mods and Skins, which list the catalog hosted by the project's website (https://prism.am) and install an item in one click. The game does everything itself, natively (crates/downloader/src/repository.rs for networking, apps/desktop/src/app/hosted.rs for orchestration): never the mod thread, never the WebView.

API contract (site side)

GET <catalogBase>/api/catalog?kind=mod|skin responds with { "items": [ … ] }. Only approved items are listed.

Field Role
id slug: 1 to 64 characters A-Za-z0-9._-, no . or - at the start, no . at the end; this is the identifier of the installed mod or skin
kind mod or skin (an item of any other type is ignored)
name, author, version displayed as is (version is free-form); at most 80, 80 and 32 characters
seq integer that increases with each approved version; used to detect an update
description, descriptionFr? at most 600 characters; French reads descriptionFr, other languages read description
permissions list of strings (network, stage, skinImport…), displayed before a mod is installed
sha256, size 64 hexadecimal digits, bytes; verified after the download
filename informational (the game names its own temporary file)
url absolute or relative to the site, on the catalog host only (same scheme, same host, same port)
updatedAt, screenshots? updatedAt is read; screenshots and any unknown field are tolerated and ignored

An invalid item (slug, hash, size out of bounds — 64 MiB for a mod, 256 MiB for a skin —, URL elsewhere, different type, duplicate) is ignored without breaking the list. The list is read from at most 1 MiB, with at most 500 items.

Catalog address

https://prism.am by default. In order: the PRISM_CATALOG environment variable, then the catalogUrl setting (Settings → Library → Downloads → Catalog), then the default value.

  • The setting requires https; http is accepted there only for localhost or a loopback address.
  • The variable alone can name an http server (for example the current test server, with no DNS: PRISM_CATALOG=http://15.235.95.198); it then allows http and a port, only for the named host.
  • The catalogUrl setting is never exported with the settings.

What the network does

  • The list is requested when the tab opens, never before. No identifier is sent (no cookie, no token, no machine or player identifier): only Accept and the game's user agent.
  • Each request carries a HostPolicy limited to the catalog host: redirects are followed manually, a single host (a redirect to another host is refused), 3 hops at most. For a public https catalog, a private or local address is refused at resolution, as for mod sources.
  • Timeouts and sizes are bounded; an unreachable catalog results in the calm "Catalog unavailable" state with a Retry button, without affecting the other tabs.

Installation

  1. The item is downloaded to a temporary file (<data>/catalog-tmp/<n>/…), with progress and a Cancel button. The stream is bounded by the announced size.
  2. The exact size and the SHA-256 are verified; at the slightest discrepancy (file too long, too short, different hash) the file is deleted and the error "does not match what the catalog announced" is displayed.
  3. The verified file is handed exactly to the existing import: ModInstaller::install_package for a .pvmod (what the Mods page does), skin::Library::import for a skin .zip (what the Skins page does). All of their validations apply (zip-slip, caps, identifiers).
  4. The temporary file is deleted at the end; folders left behind by an interruption are deleted the next time a tab is opened (after one hour).

Collisions. A mod with the same identifier is replaced by the existing import (this is its explicit update: the confirmation states the version being replaced). A skin never overwrites a skin: the import gives it a free identifier (neon-2); a skin update is therefore added alongside the current one.

An installed mod is active immediately (this is the behavior of the .pvmod import; disabledMods is empty by default). This is why the confirmation comes before the installation. Afterwards, the interface says so ("it is active: check it, along with its permissions, in the Mods page") with a button that opens the Mods page.

Permissions

For a mod, the catalog's permissions are displayed as badges with a plain-language explanation, on the card and then in the installation confirmation. These are the permissions announced by the site; what matters is the package's manifest. Once installed, the game compares them: if the package declares a permission that the catalog did not announce, the interface says so ("the package requests permissions that the catalog did not announce: …"). The existing revocations (networkDenied, stageDenied, Mods page) apply as for any mod.

Installed / Update

The game keeps <data>/catalog-installed.json: for each item installed from the catalog, the local identifier (the one in a mod's manifest, the one the import chose for a skin), seq and version.

  • With this link (and as long as the local item exists): update if the catalog's seq is greater.
  • Without a link (installed by hand): versions are compared when both are dot-separated numbers (1.2.10; a leading v and a trailing -pre or +build are ignored); otherwise the item counts as installed.
  • No local item of that name: available.

Interface

CatalogTab.svelte is a generic component, with no per-theme styles: it reads the --pv-* tokens (the eight themes and the classic one) and reuses Button, Badge and IconButton. Text in en/fr/zh (catalog.*).

Tests

  • crates/downloader/src/repository/tests.rs: real local HTTP server and real transport — list (unknown fields, invalid items ignored, no identifier sent), list errors (500, not JSON, too large, unreachable, redirect to another host), valid file with progress, wrong hash, file too short or too long, 404, redirect to another host, cancellation, existing file never overwritten, base address rules.
  • apps/desktop/src/app/hosted.rs (safe file names on Windows, registry) and protocol/tests.rs (contract).
  • apps/web/src/lib/catalog.test.ts (states, versions, language, permissions) and tests/catalog-tabs.spec.ts (tabs, confirmation, installation, cancellation, integrity failure, unavailable, language; classic, cabinet, slant).

What is not verified natively

The real server (prism.am or the test address), the installation of a real .pvmod or skin from the catalog in the running game, and rendering in the real WebView are not exercised: the Rust tests use a local server, and the interface is viewed with the mock.

Source in the game repository: docs/catalogue.md