Metadata-Version: 2.4
Name: ps5-browser-builder
Version: 0.1.0
Summary: Build deterministic online and offline PS5 browser-tile installers
Author: PS5 Browser Builder contributors
License-Expression: AGPL-3.0-or-later
Requires-Python: >=3.11
Description-Content-Type: text/markdown
License-File: LICENSE
Dynamic: license-file

# PS5 Browser Builder

PS5 Browser Builder creates deterministic ELF installers for custom PS5
browser tiles. A tile can open an online homepage or pre-cache a bundled site
in WebKit ApplicationCache for offline use.

The repository includes one complete project,
[`examples/slopkit`](examples/slopkit), whose site is pinned to SlopKit's
curated public `main` branch with a Git submodule.

## Installation and toolchain

The Python package requires Python 3.11 or newer. Building an ELF directly on
the host also requires the PS5 Payload SDK revision and fingerprint listed in
[`toolchain-lock.json`](ps5_browser_builder/data/toolchain-lock.json).

The SDK must be supplied separately and is never copied into this repository.
Set the standard SDK environment variable before building:

```sh
export PS5_PAYLOAD_SDK=/path/to/ps5-payload-sdk
```

You can also pass `--sdk /path/to/ps5-payload-sdk` to a build command.
Offline builds use the bundled, hash-locked SQLite amalgamation.
The seed format, transaction ordering, managed-update authorization, and
physical-qualification boundary are documented in
[`docs/OFFLINE_CACHE.md`](docs/OFFLINE_CACHE.md).

Tagged releases publish a wheel, source distribution, and an amd64 OCI image.
The image contains the exact validated SDK and compiler, so its `build` command
does not depend on host toolchain state. Pin the `name@sha256:digest` value from
the release's `container-reference.txt`, not the moving `latest` tag:

```sh
docker run --rm --read-only --tmpfs /tmp:rw,exec \
  -v "$PWD/project:/project:ro" -v "$PWD/out:/out" \
  git.richey.dev/miles/ps5-browser-builder@sha256:... \
  build /project --output /out/browser.elf
```

The image consumes only the required SDK directories from the SHA-256-pinned
PacBrew v0.39 release archive. It does not follow PacBrew or SDK `main`. Exact
base-image, package, SDK source, archive, and fingerprint provenance is recorded
in
[`toolchain-provenance.json`](ps5_browser_builder/data/toolchain-provenance.json).
The image and release payload also carry the SDK's GPL-3.0-or-later license
text alongside the builder's AGPL license and third-party notice.

Clone the complete project with its public SlopKit dependency:

```sh
git clone --recurse-submodules \
  git@forgejo:miles/ps5-browser-builder.git
```

For an existing checkout, initialize the site with
`git submodule update --init`. Generated source archives do not include the
submodule site. Run the builder from a checkout, or install the Python package
with:

```sh
python3 -m pip install .
```

## Configure a project

Copy `examples/slopkit` and edit its `browser.json`:

```json
{
  "schema": 1,
  "titleId": "SLOP00001",
  "url": "https://slop00001.ps5.invalid/",
  "title": "SlopKit",
  "language": "en-US",
  "version": "01.00",
  "category": "media",
  "artwork": {"icon0": "icon0.png"},
  "offlineCache": {
    "sourceDirectory": "site",
    "document": "index.html",
    "manifestPath": "slopkit.appcache",
    "excludePaths": [
      ".git",
      "slopkit.appcache",
      "slopkit.manifest"
    ],
    "networkWildcard": true,
    "revision": 4,
    "updatePolicy": "replaceOlder",
    "fallbacks": [
      {"namespace": "notify.html", "resource": "notify.html"}
    ]
  }
}
```

- Use a unique title ID: four uppercase letters followed by five digits. For an
  offline project, also give it a unique HTTPS origin so independent tiles do
  not share an ApplicationCache group.
- Set `category` to `game` or `media`.
- Use a 512×512 or 660×660 PNG for `artwork.icon0`.
- Remove `offlineCache` for an online-only tile.
- `excludePaths` can omit normalized relative files or complete directory
  subtrees. Exclusions may be absent, but cannot overlap or remove the cached
  document or a fallback resource.
- For an offline update, keep the same identity, increment `revision`, and set
  `updatePolicy` to `replaceOlder`.

`--force-cache-overwrite` is for controlled testing only. It bypasses revision
ordering, but still rejects caches that are unsafe, malformed, shared,
browser-created, unknown, or owned by another title.

## Build the example

The included example tracks SlopKit public release ordinal 4. Its
`replaceOlder` policy can replace only a strictly older canonical cache managed
by this builder for the same title and manifest URL.

```sh
python3 -m ps5_browser_builder validate examples/slopkit

python3 -m ps5_browser_builder build examples/slopkit \
  --output /tmp/slopkit.elf

python3 -m ps5_browser_builder inspect /tmp/slopkit.elf --json
```

Builds are create-only and compile twice to confirm deterministic output.
These commands do not connect to a PS5.

To build a force-enabled testing artifact:

```sh
python3 -m ps5_browser_builder build examples/slopkit \
  --force-cache-overwrite \
  --output /tmp/slopkit-force.elf
```

## Specialize an online carrier

`specialize` is the narrow integration surface for consumers that need to
embed a prebuilt installer in another binary. It needs no SDK and replaces only
the carrier's fixed 4096-byte metadata record:

```sh
python3 -m ps5_browser_builder specialize /tmp/carrier.elf project \
  --output /tmp/product.elf
```

The command fully validates the carrier and result, rejects offline projects,
requires the project's `icon0.png` to match the carrier, and verifies that all
bytes outside the metadata record remain identical. A legacy game tile whose
`param.json` omitted `applicationCategoryType` can be reproduced explicitly:

```sh
python3 -m ps5_browser_builder specialize /tmp/carrier.elf project \
  --implicit-game-category --output /tmp/legacy-game.elf
```

`inspect --json` has schema `1` and reports whether the category encoding is
`explicit` or `implicit`. The public compatibility boundary is the CLI and its
JSON output; internal Python modules are not a stable API.

## Audit firmware inputs

The read-only firmware preflight moved with the installer implementation. It
audits caller-supplied decrypted libraries for the WebKit AppCache schema,
mount markers, runtime exports, euid path, and pre-12/12.00 AppInst routes:

```sh
ps5-browser-builder firmware-audit \
  --root /path/to/ps5_sys_libs \
  --firmware 08.60 \
  --json
```

The audit never copies or modifies its inputs. A pass is static structural
evidence only: directory names are caller-supplied labels, and the result does
not qualify live database ownership, AppInst semantics, browser concurrency,
profile selection, cache consumption, or reboot persistence.

## Verification

Run the package checks from a recursive checkout:

```sh
python3 -m ruff format --check .
python3 -m ruff check .
python3 -m unittest discover -s tests -v
python3 tools/update_slopkit_example.py check
python3 tools/check_toolchain_provenance.py
```

Forgejo CI repeats these checks, builds and installs the wheel, then performs a
clean build/specialize/inspect cycle inside the locked toolchain image. Release
tags must match the Python package version before the workflow publishes any
package or image.

The release workflow expects `CONTAINER_REGISTRY_USERNAME` and
`CONTAINER_REGISTRY_TOKEN` for both the OCI and Forgejo Python registries. Use
scoped service credentials or a personal access token with only the required
package permission. Release asset upload uses Forgejo's short-lived automatic
workflow token, so it does not require a stored repository token. After the
workflow for `v0.1.0` succeeds, use its `container-reference.txt` manifest
digest to finalize the consuming WebKitty lock; never copy the digest of a
local image or of the moving `latest` tag.
