An installable Flask/PWA catalog and classroom service hub for PRG32 .prg32
game cartridges.
docker compose up --buildOpen http://127.0.0.1:5080/.
The compose file mounts ./data to /data in the container so uploaded
cartridges, scores, metrics, and the catalog index persist across restarts.
Stop the service:
docker compose downSee docs/docker.md for manual docker run, LAN deployment,
logs, and maintenance notes.
python3 -m venv .venv
. .venv/bin/activate
pip install -r requirements.txt
python app.pyOpen http://127.0.0.1:5080/.
Set PRG32_STORE_DATA=/path/to/data to move filesystem storage. By default the
store writes cartridges to data/cartridges, the extracted index to
data/index.json, and service data to data/cartridge_store.sqlite.
Set PRG32_STORE_DB=/path/to/cartridge_store.sqlite to choose a different
SQLite database. Legacy PRG32_SCORE_DB and PRG32_METRICS_DB environment
variables are also honored when PRG32_STORE_DB is not set.
For a detailed environment setup, see docs/getting_started.md.
The service seeds a default administrator account:
username: admin
password: password
Change that password before real use. New users register by entering an email address, receiving a verification link, and setting a password from that link. Their username is the verified email address.
Authenticated users can upload cartridge packages. Users in the editors group
verify pending packages before they appear in the public catalog. Legacy
PRG32_USERS tokens are still accepted for API and multiplayer clients.
Administrators use /setup for store, publish, mDNS, SMTP, OpenID Connect,
and SAML2 settings. /admin/users, /admin/groups, /admin/roles,
/admin/cartridges, and /admin/backup cover user/group/role assignment,
cartridge administration, and full backup/restore.
A .prg32 file contains one linked executable image. The store groups uploads
by metadata id and version, and then stores one artifact per architecture:
esp32c6: physical ESP32-C6 PRG32 firmware.qemu: ESP32-C3 QEMU RGB screen workflow.
Download endpoints accept version and architecture query parameters:
GET /api/games/org.example.game/download?version=1.0.0&architecture=esp32c6| Method | Path |
|---|---|
GET |
/api |
GET |
/api/me |
GET |
/api/games |
GET |
/api/games/<id> |
GET |
/api/games/<id>/icon |
GET |
/api/games/<id>/screenshot |
GET |
/api/games/<id>/colophon |
GET |
/api/games/<id>/download |
POST |
/api/publish |
GET |
/api/scores |
POST |
/api/scores |
GET |
/api/metrics |
GET |
/api/runs |
POST |
/api/runs |
POST |
/api/metrics/batch |
POST |
/api/runs/<run_id>/finish |
GET |
/api/runs/<run_id> |
GET |
/api/runs/<run_id>/samples.csv |
GET |
/api/runs/<run_id>/report.md |
GET |
/api/multiplayer |
GET |
/api/multiplayer/status |
WS |
/api/multiplayer |
GET |
/.well-known/prg32-store.json |
POST /api/publish and POST /api/publish/bundle accept multipart/form-data
with a single bundle=@game.zip package. Uploads enter the editor review queue.
The score API is compatible with the standalone PRG32 ScoreServer:
curl http://localhost:5080/api/scores
curl -X POST http://localhost:5080/api/scores \
-H 'Content-Type: application/json' \
-d '{"game":"pong","player":"Ada","score":42}'Use ?game=<name> to filter scores for one game and ?limit=<n> to choose a
result limit between 1 and 100. Add ?player=<name> to show scores for one
player. The browser scoreboard is available from the navigation bar at
/scores and accepts the same game, player, and limit query filters.
The metrics API is compatible with the standalone PRG32 MetricsServer:
curl -X POST http://127.0.0.1:5080/api/runs \
-H 'Content-Type: application/json' \
-d '{"run_id":"demo","board_id":"board-1","target":"esp32c6"}'
curl -X POST http://127.0.0.1:5080/api/metrics/batch \
-H 'Content-Type: application/json' \
-d '{"run_id":"demo","samples":[{"frame":1,"frame_us":23500}]}'Useful URLs:
http://127.0.0.1:5080/api/runshttp://127.0.0.1:5080/api/runs/demohttp://127.0.0.1:5080/api/runs/demo/samples.csvhttp://127.0.0.1:5080/api/runs/demo/report.md
Export one metrics run:
python -m cartridge_store.export_run demo \
--db data/cartridge_store.sqlite \
--out metrics_export/demoThe export directory contains metadata JSON, sample CSV, summary CSV, a LaTeX
summary table, a Markdown report, and plots when matplotlib is available.
The multiplayer relay keeps the standalone MultiplayerServer JSON protocol, but runs on the same Cartridge Store service:
ws://127.0.0.1:5080/api/multiplayer
Clients send:
{"type":"join","signature":"pong-v1","flags":1,"player_id":123}
{"type":"state","x":120,"y":80,"sprite":0,"flags":0,"input":2,"frame":42}
{"type":"leave"}The relay replies with welcome, peer, leave, or error messages. Clients
with different cartridge signatures stay isolated. Set PRG32_MP_MAX_PEERS to
change the per-signature room limit.
- The store never executes uploaded cartridge code.
- Upload size is limited by Flask
MAX_CONTENT_LENGTH. - Game IDs and versions are restricted to path-safe characters.
- Metadata and colophon JSON are validated before publishing.
- Icon and screenshot uploads must be PNG or JPEG bytes.
- Unknown metadata trailer TLV blocks are preserved when rewriting a cartridge.
- Score and metrics inputs are length-limited before storage.
- Multiplayer rooms are isolated by path-safe cartridge signatures.
- The game colophon is shown after the cartridge is activated, before the player starts a new play.
- Score, metrics, and multiplayer APIs keep the legacy request and response contracts from the archived standalone servers.
pytest -qStart here:
- Getting started: local Python setup, first-run Store configuration, default administrator login, package upload and review, score submission, metrics smoke tests, mDNS, SMTP, external auth, and theme customization.
- Getting started with PRG32 game development: host tool installation on Windows, Linux, and macOS; PlatformIO and ESP-IDF setup; creating a hello world cartridge from scratch; running it in QEMU; uploading it to ESP32-C6 hardware; packaging and publishing it to the Store.
Operate and deploy the Store:
- Docker deployment: Docker Compose quick start, persistent
/datastorage, manualdocker run, classroom LAN URLs, mDNS behavior, full backup/restore, logs, restarts, and data reset. - Deployment: production environment variables, reverse-proxy notes, WebSocket proxying, SMTP, OIDC, SAML2, mDNS, backup expectations, and production hardening checklist.
- Operations: daily classroom operations, backup and restore, health checks, publishing workflow, user registration, admin pages, access-token rotation, logs, network checks, and full data reset.
Integrate with clients and tools:
- Accounts and single sign-on: sharing users with a PRG32-Construction-Kit.
- Unified service API: discovery document, auth endpoints, admin endpoints, cartridge package format, editor verification API, scores, metrics, statistics, multiplayer, and compatibility notes.
What to read for common tasks:
- Run the Store locally: Getting started.
- Deploy the Store with Docker: Docker deployment.
- Deploy the Store behind HTTPS: Deployment.
- Administer users, groups, cartridges, setup, or backups: Operations.
- Create a new PRG32 game cartridge: Getting started with PRG32 game development.
- Publish a cartridge package: Getting started for the browser flow, or Unified service API for the API flow.
- Build firmware or prepare PlatformIO/ESP-IDF: Getting started with PRG32 game development.
- Run a cartridge in QEMU: Getting started with PRG32 game development.
- Upload a cartridge to physical hardware: Getting started with PRG32 game development.
- Perform performance or metrics tests: Getting started for a smoke test, Unified service API for ingestion endpoints, and Operations for service checks.
- Contribute to game development: Getting started with PRG32 game development.
- Contribute to firmware development: Getting started with PRG32 game development for PlatformIO/ESP-IDF setup, then use the firmware SDK's own contribution guide for board-specific internals.
- Integrate firmware, launchers, classroom dashboards, or CI tools: Unified service API.