Initial commit: Null DRM Official

Capture, decrypt, and restream toolkit with compiled-in app modules
(RTE, TG4, BBC), on-device MITM proxy, streamd control plane, and www.
BBC module.yaml is published (clear streams); other module values stay local.
This commit is contained in:
404errordeveloper 2026-10-06 00:25:35 +02:00
commit 2fa8f2435f
121 changed files with 17802 additions and 0 deletions

252
docs/quickstart.md Normal file
View file

@ -0,0 +1,252 @@
# Quickstart
From an empty directory to a captured Widevine key.
## 1. Prerequisites
| Need | Version | Needed for |
|---|---|---|
| Go | 1.25+ | everything |
| Python | 3.10+ | the CDM helper (`apps/wvkey/wvkey.py`) |
| adb | any recent platform-tools, on `PATH` | phone capture only |
| `.wvd` device file | — | fetching content keys |
| Rooted Android phone (Magisk) | — | phone capture only |
Check Go:
```bash
go version
```
If Go is installed but not on `PATH`, set `GOROOT` and prepend its `bin`:
```bash
export GOROOT=/path/to/go
export PATH="$GOROOT/bin:$PATH"
```
## 2. Clone and build
```bash
git clone https://git.nulldrm.com/nulldrm/Null-DRM-Official.git
cd Null-DRM-Official
go -C apps/cli build -o ../../bin/drm . # Windows: -o ../../bin/drm.exe
```
That single command builds the whole toolchain: capture, catalog, agent, the
streamd control plane, the MITM host CLI, and every app module.
Verify:
```bash
./bin/drm help
./bin/drm modules
```
Expected on a fresh clone — modules exist, values do not:
```text
APP PACKAGE KEY MODE REWRITER CHANNELS VALUES
rte (not configured) modulardrm rte 0 missing: .../apps/modules/rte/module.yaml
tg4 (not configured) raw none 0 missing: .../apps/modules/tg4/module.yaml
```
## 3. The Python CDM helper
Only `wvkey.py` is Python; it turns a PSSH + license URL into `KID:KEY` lines.
```bash
python -m venv .venv
.venv/bin/pip install -r apps/wvkey/requirements.txt
```
On Windows:
```powershell
python -m venv .venv
.venv\Scripts\pip install -r apps\wvkey\requirements.txt
```
The binary finds `.venv` automatically; override with `--python /path/to/python`.
Place your Widevine device file under `data/` (gitignored):
```text
data/device.wvd
```
## 4. Add values for a module
Create the module's values file. It is gitignored, so it never leaves your machine.
```bash
$EDITOR apps/modules/tg4/module.yaml
```
Minimum for TG4 — see [providers/tg4.md](providers/tg4.md) for where each comes from:
```yaml
package: <android package id>
account_id: "<brightcove account id>"
policy_key: "BCpkAD..."
playback_config_url: "https://.../playback_config.json"
channels:
ioi:
label: "Main channel"
video_id: "<brightcove video id>"
token_field: <field name in the playback config>
live_card_index: 0
```
Confirm it loaded:
```bash
./bin/drm modules
./bin/drm catalog --app tg4 --list
```
Nothing stored? Pass values per run instead:
```bash
./bin/drm catalog --app tg4 --channel ioi \
--tg4.account-id 1234567890 --tg4.policy-key 'BCpkAD...'
```
Or through the environment (`<APP>_<FIELD>`, upper snake case):
```bash
export TG4_POLICY_KEY='BCpkAD...'
```
Precedence is **flag → environment → module.yaml → built-in default**. Only
mechanical defaults (timeouts, DASH timescales, card geometry) have built-ins.
## 5. Get keys without a phone
If the app has a public playback catalog:
```bash
./bin/drm catalog --app tg4 --channel ioi --keys --wvd data/device.wvd
```
```text
manifest: https://.../playlist-hls-dvr.m3u8
license_url: https://.../lic/wv?token=...
pssh: AAAAYHBzc2gAAAAA7e+LqXnWSs6j...
key: 27c38b955b983ac2827204f8d260d628:f31efbc58fc89526cce5e5dee50a7ebd
key[0]: 5b9d58edf81f3b5b9c3cc61f3e81845e:688236976dcf8cdde5cdfac4a698f256
...
```
Add `--json` for a machine-readable object, `--out` to write a session directory
without fetching keys.
## 6. Get keys from the phone
One-time device setup:
```bash
adb devices # your phone must be listed and authorised
./bin/drm proxy build # cross-compiles the on-device MITM (linux/arm64)
./bin/drm proxy install-ca --reinject
```
`install-ca` pushes the MITM CA and Magisk-mounts it into the system trust store.
Without it apps fail TLS with `unknown certificate`. See [capture.md](capture.md)
if the reinject does not report success.
Then capture:
```bash
./bin/drm capture --app rte --channel rteone --auto-play --wvd data/device.wvd
```
What it does: start the on-device MITM → point the phone's HTTP proxy at it →
reinject the CA → launch the app → drive its UI to the channel → wait for the
license/manifest traffic → run the CDM → write a session → close the app.
```text
[*] Launching rte (...)
[*] Auto-play RTE One…
[*] tap_ui "Live"/"Live tab, 2 out of 5" @ 324,2253
[*] tap Play @ 540,720
[+] ... is PLAYING
[+] Capture:
pid: 8T8Ohq7XT5KN
mpd: https://dai.google.com/linear/dash/pa/event/...
[+] key: df1633821dddfdd5bec9822c1ec0f052:b6324f76d2bb4dfe63e4272f803274f4
[+] session: outputs/rte/20261005-215448/session.json
```
Keep the screen on for the whole run:
```bash
adb shell svc power stayon true
```
Drop `--auto-play` to launch the app and navigate by hand, or drop `--app`
entirely for passive mode — the MITM records whatever you play.
## 7. Control plane
```bash
./bin/drm serve --bind 127.0.0.1:8083 --data .cache/streamd --token SECRET
```
Open <http://127.0.0.1:8083>. The app and channel pickers are populated from
`GET /api/apps`, i.e. from the modules compiled into the binary.
Create a stream from a capture:
```bash
./bin/drm catalog --app tg4 --channel ioi --keys --wvd data/device.wvd --json \
> /tmp/s.json
curl -X POST http://127.0.0.1:8083/api/streams \
-H 'Authorization: Bearer SECRET' -H 'Content-Type: application/json' \
--data @/tmp/s.json
```
## 8. Agent
The agent polls streamd, queues any enabled stream that is down or missing
credentials, claims a free phone, re-captures it and POSTs the result back.
```bash
export STREAMD_TOKEN=SECRET
./bin/drm agent run --config apps/agent/agent.yaml
./bin/drm agent devices # which phones it can use
./bin/drm agent status # recent jobs
./bin/drm agent enqueue --stream tg4-ioi --reason manual
```
Details in [streamd.md](streamd.md).
## 9. Tests
```bash
go -C apps/pkg test ./...
go -C apps/modules test ./...
go -C apps/streamd test ./...
```
Live-origin tests are tag-gated and need your local values:
```bash
go -C apps/modules test -tags live ./rte/ -v
```
## Troubleshooting
| Symptom | Cause |
|---|---|
| `unknown app "x"` | not compiled in — check `apps/modules/all/all.go`, then rebuild |
| `channels 0` in `drm modules` | no `module.yaml`, or `channels:` is empty |
| `unknown channel "y"` | id not in `channels:` and not in `aliases:` |
| `tls: unknown certificate` on the phone | CA not mounted — `drm proxy install-ca --reinject` |
| `wvkey requires --wvd` | pass `--wvd path/to/device.wvd` |
| `timed out waiting for capture` | playback never started, or the app used a path the MITM missed — see [capture.md](capture.md) |
| `INVALID_POLICY_KEY` | wrong or stale `policy_key` |