stadiacast 1.0.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- stadiacast-1.0.0/.gitignore +28 -0
- stadiacast-1.0.0/CHANGELOG.md +72 -0
- stadiacast-1.0.0/LICENSE +21 -0
- stadiacast-1.0.0/NOTICE +24 -0
- stadiacast-1.0.0/PKG-INFO +277 -0
- stadiacast-1.0.0/README.md +235 -0
- stadiacast-1.0.0/certs/README.md +11 -0
- stadiacast-1.0.0/docs/architecture.md +161 -0
- stadiacast-1.0.0/docs/protocol.md +339 -0
- stadiacast-1.0.0/docs/setup.md +218 -0
- stadiacast-1.0.0/pyproject.toml +116 -0
- stadiacast-1.0.0/src/stadiacast/__init__.py +11 -0
- stadiacast-1.0.0/src/stadiacast/__main__.py +6 -0
- stadiacast-1.0.0/src/stadiacast/audio.py +662 -0
- stadiacast-1.0.0/src/stadiacast/ble_provisioner.py +374 -0
- stadiacast-1.0.0/src/stadiacast/capture.py +238 -0
- stadiacast-1.0.0/src/stadiacast/certs.py +198 -0
- stadiacast-1.0.0/src/stadiacast/cli.py +474 -0
- stadiacast-1.0.0/src/stadiacast/config.py +133 -0
- stadiacast-1.0.0/src/stadiacast/controller.py +1069 -0
- stadiacast-1.0.0/src/stadiacast/data/identity.bin +0 -0
- stadiacast-1.0.0/src/stadiacast/errors.py +15 -0
- stadiacast-1.0.0/src/stadiacast/game_session.py +751 -0
- stadiacast-1.0.0/src/stadiacast/gamepad.py +172 -0
- stadiacast-1.0.0/src/stadiacast/helpers/__init__.py +38 -0
- stadiacast-1.0.0/src/stadiacast/helpers/dns_sink.py +241 -0
- stadiacast-1.0.0/src/stadiacast/helpers/dnswire.py +147 -0
- stadiacast-1.0.0/src/stadiacast/helpers/sntp_server.py +109 -0
- stadiacast-1.0.0/src/stadiacast/http_server.py +329 -0
- stadiacast-1.0.0/src/stadiacast/input_protocol.py +190 -0
- stadiacast-1.0.0/src/stadiacast/log.py +140 -0
- stadiacast-1.0.0/src/stadiacast/loopback.py +139 -0
- stadiacast-1.0.0/src/stadiacast/netutil.py +63 -0
- stadiacast-1.0.0/src/stadiacast/protos.py +349 -0
- stadiacast-1.0.0/src/stadiacast/py.typed +0 -0
- stadiacast-1.0.0/src/stadiacast/receiver.py +719 -0
- stadiacast-1.0.0/src/stadiacast/rtc/__init__.py +108 -0
- stadiacast-1.0.0/src/stadiacast/rtc/dtls.py +205 -0
- stadiacast-1.0.0/src/stadiacast/rtc/endpoint.py +644 -0
- stadiacast-1.0.0/src/stadiacast/rtc/rtp.py +134 -0
- stadiacast-1.0.0/src/stadiacast/rtc/srtp.py +192 -0
- stadiacast-1.0.0/src/stadiacast/rtc/stun.py +145 -0
- stadiacast-1.0.0/src/stadiacast/rtc/wire.py +59 -0
- stadiacast-1.0.0/src/stadiacast/schema_compiler.py +313 -0
- stadiacast-1.0.0/src/stadiacast/schemas/README.md +37 -0
- stadiacast-1.0.0/src/stadiacast/schemas/controller_message.descriptor +0 -0
- stadiacast-1.0.0/src/stadiacast/schemas/controller_message.proto +258 -0
- stadiacast-1.0.0/src/stadiacast/schemas/discovery_api.descriptor +0 -0
- stadiacast-1.0.0/src/stadiacast/schemas/discovery_api.proto +237 -0
- stadiacast-1.0.0/src/stadiacast/schemas/input.descriptor +0 -0
- stadiacast-1.0.0/src/stadiacast/schemas/input.proto +397 -0
- stadiacast-1.0.0/src/stadiacast/schemas/rpc_message.descriptor +0 -0
- stadiacast-1.0.0/src/stadiacast/schemas/rpc_message.proto +78 -0
- stadiacast-1.0.0/src/stadiacast/schemas/streamer_message.descriptor +16 -0
- stadiacast-1.0.0/src/stadiacast/schemas/streamer_message.proto +35 -0
- stadiacast-1.0.0/src/stadiacast/schemas/webrtc_sdp.descriptor +0 -0
- stadiacast-1.0.0/src/stadiacast/schemas/webrtc_sdp.proto +237 -0
- stadiacast-1.0.0/src/stadiacast/schemas/yeti_clientanalytics.descriptor +0 -0
- stadiacast-1.0.0/src/stadiacast/schemas/yeti_clientanalytics.proto +95 -0
- stadiacast-1.0.0/src/stadiacast/show_input.py +100 -0
- stadiacast-1.0.0/src/stadiacast/signaling.py +158 -0
- stadiacast-1.0.0/src/stadiacast/sysvolume.py +189 -0
- stadiacast-1.0.0/src/stadiacast/virtual_gamepad.py +341 -0
- stadiacast-1.0.0/tests/__init__.py +0 -0
- stadiacast-1.0.0/tests/conftest.py +35 -0
- stadiacast-1.0.0/tests/fake_controller.py +1075 -0
- stadiacast-1.0.0/tests/helpers.py +103 -0
- stadiacast-1.0.0/tests/test_audio.py +658 -0
- stadiacast-1.0.0/tests/test_audio_control.py +212 -0
- stadiacast-1.0.0/tests/test_ble.py +194 -0
- stadiacast-1.0.0/tests/test_boot_flow.py +350 -0
- stadiacast-1.0.0/tests/test_certs.py +139 -0
- stadiacast-1.0.0/tests/test_clearcut.py +127 -0
- stadiacast-1.0.0/tests/test_cli.py +150 -0
- stadiacast-1.0.0/tests/test_control_endpoints.py +110 -0
- stadiacast-1.0.0/tests/test_controller_state.py +69 -0
- stadiacast-1.0.0/tests/test_dns.py +180 -0
- stadiacast-1.0.0/tests/test_game_session.py +346 -0
- stadiacast-1.0.0/tests/test_gamepad.py +82 -0
- stadiacast-1.0.0/tests/test_http.py +168 -0
- stadiacast-1.0.0/tests/test_log.py +55 -0
- stadiacast-1.0.0/tests/test_multi_controller.py +139 -0
- stadiacast-1.0.0/tests/test_netutil.py +39 -0
- stadiacast-1.0.0/tests/test_protos.py +157 -0
- stadiacast-1.0.0/tests/test_rtc.py +198 -0
- stadiacast-1.0.0/tests/test_schemas.py +189 -0
- stadiacast-1.0.0/tests/test_show_input.py +68 -0
- stadiacast-1.0.0/tests/test_signaling_legacy.py +79 -0
- stadiacast-1.0.0/tests/test_sntp.py +34 -0
- stadiacast-1.0.0/tests/test_virtual_gamepad.py +314 -0
- stadiacast-1.0.0/tests/test_windows_lab.py +133 -0
- stadiacast-1.0.0/tools/pack_credential.py +45 -0
- stadiacast-1.0.0/tools/start_receiver.sh +74 -0
- stadiacast-1.0.0/tools/stop_receiver.sh +16 -0
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
# Python
|
|
2
|
+
__pycache__/
|
|
3
|
+
*.py[cod]
|
|
4
|
+
*.egg-info/
|
|
5
|
+
dist/
|
|
6
|
+
build/
|
|
7
|
+
.venv/
|
|
8
|
+
.pytest_cache/
|
|
9
|
+
.ruff_cache/
|
|
10
|
+
.mypy_cache/
|
|
11
|
+
.coverage
|
|
12
|
+
coverage.xml
|
|
13
|
+
htmlcov/
|
|
14
|
+
|
|
15
|
+
# Receiver runtime data: run directories (logs, packet captures, dumped request bodies)
|
|
16
|
+
captures/
|
|
17
|
+
|
|
18
|
+
# Certificates and private keys: never commit them
|
|
19
|
+
certs/*
|
|
20
|
+
!certs/README.md
|
|
21
|
+
|
|
22
|
+
# Local overrides for tools/start_receiver.sh
|
|
23
|
+
.env
|
|
24
|
+
|
|
25
|
+
# Editors / OS
|
|
26
|
+
.idea/
|
|
27
|
+
.vscode/
|
|
28
|
+
.DS_Store
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
All notable changes to this project are documented here. The format follows
|
|
4
|
+
[Keep a Changelog](https://keepachangelog.com/en/1.1.0/); versions follow
|
|
5
|
+
[Semantic Versioning](https://semver.org/).
|
|
6
|
+
|
|
7
|
+
## [Unreleased]
|
|
8
|
+
|
|
9
|
+
## [1.0.0] - 2026-11-06
|
|
10
|
+
|
|
11
|
+
Nothing to configure before the first run, sound on the headset, a virtual gamepad on Windows
|
|
12
|
+
and ready-made executables.
|
|
13
|
+
|
|
14
|
+
### Added
|
|
15
|
+
- A built-in certificate the controller accepts, so there is nothing to obtain: the receiver
|
|
16
|
+
serves it when none is configured. Your own still wins with `--cert`/`--key` or a pair under
|
|
17
|
+
`certs/`.
|
|
18
|
+
- Headset audio: `--audio-source` plays a test tone, a file, what the computer plays
|
|
19
|
+
(`system`) or one capture device on a headset plugged into the controller.
|
|
20
|
+
`--audio-bitrate`, `--audio-application`, `--audio-volume` and `--audio-follow-volume` tune
|
|
21
|
+
it; `/_audio` starts, stops and adjusts the stream while the receiver runs, and `/_status`
|
|
22
|
+
reports it under `playback`.
|
|
23
|
+
- A virtual gamepad on Windows: `--gamepad vigem` turns each controller into an Xbox 360
|
|
24
|
+
controller for the games on the PC, through the ViGEmBus driver, with the game's rumble
|
|
25
|
+
sent back to the controller; `/_status` lists the pads under `gamepads`.
|
|
26
|
+
- Standalone executables for Windows, macOS (Apple silicon) and Linux attached to every
|
|
27
|
+
release: `stadiacast` and `stadiacast-provision`, Python included.
|
|
28
|
+
- The battery level, whether a headset is plugged in and the controller's hardware
|
|
29
|
+
description, in `/_status` and in the log.
|
|
30
|
+
|
|
31
|
+
### Changed
|
|
32
|
+
- A port 80 or 8008 held by another program no longer stops the receiver; 443 is the only
|
|
33
|
+
port the controller needs.
|
|
34
|
+
- Whatever is missing on the computer (ffmpeg, an audio device, the `audio` or `gamepad`
|
|
35
|
+
extra, the ViGEmBus driver, a Windows that cannot host a pad) stops the receiver with one
|
|
36
|
+
line that says what to install, instead of a traceback.
|
|
37
|
+
|
|
38
|
+
## [0.1.0] - 2026-10-29
|
|
39
|
+
|
|
40
|
+
First public version of Stadiacast: a LAN-side stand-in for the Stadia cloud endpoints that
|
|
41
|
+
keeps the Stadia Controller's Wi-Fi mode working with your own PC. Everything below was
|
|
42
|
+
confirmed against real controllers running the last Wi-Fi firmware, `ota.282115`.
|
|
43
|
+
|
|
44
|
+
### Added
|
|
45
|
+
- The receiver (`stadiacast`): the HTTPS boot endpoints (`generate_204`, `stadiaconfig`,
|
|
46
|
+
Omaha, Clearcut), the cloudcast discovery RPCs, the GMsg push channel, protobuf WebRTC
|
|
47
|
+
signaling, STUN/ICE, DTLS-SRTP, WireHeader2 framing and the display RPC state machine up to
|
|
48
|
+
READY, with every button, stick and trigger decoded into a JSON Lines event log.
|
|
49
|
+
- The game session: `StartGame`, the proxied `InitializationRequest`, a second ICE/DTLS
|
|
50
|
+
endpoint, input on its `0x66` channel and rumble (`POST /_rumble`) kept alive across the
|
|
51
|
+
controller's haptics watchdog.
|
|
52
|
+
- Several controllers at once: one session per controller (own UDP ports, DTLS certificates,
|
|
53
|
+
GMsg queue and display link), labelled `c0`, `c1`, ... in the log and in `/_status`;
|
|
54
|
+
`?controller=` selects the target of `/_rumble` and `/_start_game`.
|
|
55
|
+
- `--preset live`: the flag set proven end to end, including the Clearcut TLS block that keeps
|
|
56
|
+
the controller from powering off after its first analytics upload.
|
|
57
|
+
- Helper processes spawned by the receiver: a redirecting DNS sink and an SNTP server.
|
|
58
|
+
- `stadiacast-provision`: Wi-Fi provisioning over Bluetooth LE for a controller in setup mode.
|
|
59
|
+
- `stadiacast-show-input`: a per-controller summary of the input recorded in a run.
|
|
60
|
+
- `tools/start_receiver.sh` / `tools/stop_receiver.sh`: rig settings in `.env`, a fresh run
|
|
61
|
+
directory per start, an optional packet capture.
|
|
62
|
+
- The protobuf schemas with their provenance (`src/stadiacast/schemas/`) and a small proto2
|
|
63
|
+
compiler that keeps the hand-written `.proto` files and their descriptors in sync.
|
|
64
|
+
- A fake controller (`tests/fake_controller.py`) that reproduces the firmware's network
|
|
65
|
+
behaviour, and a test suite that drives the whole path over loopback without hardware.
|
|
66
|
+
- Documentation: setup guide, protocol notes, architecture overview, contributing guide,
|
|
67
|
+
security policy, code of conduct.
|
|
68
|
+
- MIT License, with a `NOTICE` for the three protobuf schemas reproduced from the firmware.
|
|
69
|
+
|
|
70
|
+
[Unreleased]: https://github.com/jkfran/stadiacast/compare/v1.0.0...HEAD
|
|
71
|
+
[1.0.0]: https://github.com/jkfran/stadiacast/compare/v0.1.0...v1.0.0
|
|
72
|
+
[0.1.0]: https://github.com/jkfran/stadiacast/releases/tag/v0.1.0
|
stadiacast-1.0.0/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Francisco Jimenez Cabrera and the Stadiacast contributors
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
stadiacast-1.0.0/NOTICE
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
Stadiacast
|
|
2
|
+
Copyright (c) 2026 Francisco Jimenez Cabrera and the Stadiacast contributors
|
|
3
|
+
|
|
4
|
+
Stadiacast is licensed under the MIT License (see LICENSE), with one exception.
|
|
5
|
+
|
|
6
|
+
Three protobuf schemas under src/stadiacast/schemas/ are reproduced from the Stadia
|
|
7
|
+
Controller firmware (ota.282115), which embeds them as serialized descriptors:
|
|
8
|
+
|
|
9
|
+
input.descriptor and its rendering input.proto
|
|
10
|
+
webrtc_sdp.descriptor and its rendering webrtc_sdp.proto
|
|
11
|
+
yeti_clientanalytics.descriptor and its rendering yeti_clientanalytics.proto
|
|
12
|
+
|
|
13
|
+
These schemas, including the names in them, are Google's work. They are not covered by
|
|
14
|
+
the MIT License and the Stadiacast authors claim no rights in them. They are included
|
|
15
|
+
solely so that an independently created program can interoperate with the controller's
|
|
16
|
+
stock firmware, and they are used here for that purpose only.
|
|
17
|
+
|
|
18
|
+
Everything else in this repository is the work of the Stadiacast authors and is
|
|
19
|
+
licensed under the MIT License. That includes the four hand-written schemas
|
|
20
|
+
(discovery_api, rpc_message, controller_message, streamer_message), whose field numbers
|
|
21
|
+
and wire types describe the firmware's protocol but whose names and text are ours.
|
|
22
|
+
|
|
23
|
+
Stadiacast is an independent interoperability project. It is not affiliated with,
|
|
24
|
+
endorsed by or supported by Google. Stadia is a trademark of Google LLC.
|
|
@@ -0,0 +1,277 @@
|
|
|
1
|
+
Metadata-Version: 2.5
|
|
2
|
+
Name: stadiacast
|
|
3
|
+
Version: 1.0.0
|
|
4
|
+
Summary: A local server for the Stadia Controller's Wi-Fi mode: keeps the controller working with your own PC after the Stadia shutdown
|
|
5
|
+
Project-URL: Homepage, https://github.com/jkfran/stadiacast
|
|
6
|
+
Project-URL: Issues, https://github.com/jkfran/stadiacast/issues
|
|
7
|
+
Project-URL: Changelog, https://github.com/jkfran/stadiacast/blob/main/CHANGELOG.md
|
|
8
|
+
Author: Francisco Jimenez Cabrera
|
|
9
|
+
License-Expression: MIT
|
|
10
|
+
License-File: LICENSE
|
|
11
|
+
License-File: NOTICE
|
|
12
|
+
Keywords: controller,dtls,gamepad,interoperability,srtp,stadia,webrtc
|
|
13
|
+
Classifier: Development Status :: 3 - Alpha
|
|
14
|
+
Classifier: Environment :: Console
|
|
15
|
+
Classifier: Intended Audience :: End Users/Desktop
|
|
16
|
+
Classifier: Operating System :: MacOS
|
|
17
|
+
Classifier: Operating System :: Microsoft :: Windows
|
|
18
|
+
Classifier: Operating System :: POSIX :: Linux
|
|
19
|
+
Classifier: Programming Language :: Python :: 3
|
|
20
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
21
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
22
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
23
|
+
Classifier: Programming Language :: Python :: 3.14
|
|
24
|
+
Classifier: Topic :: Games/Entertainment
|
|
25
|
+
Classifier: Topic :: Software Development :: Embedded Systems
|
|
26
|
+
Classifier: Topic :: System :: Networking
|
|
27
|
+
Classifier: Typing :: Typed
|
|
28
|
+
Requires-Python: >=3.11
|
|
29
|
+
Requires-Dist: cryptography>=42
|
|
30
|
+
Requires-Dist: protobuf>=4.25
|
|
31
|
+
Requires-Dist: pyopenssl>=23.2
|
|
32
|
+
Provides-Extra: audio
|
|
33
|
+
Requires-Dist: pyaudiowpatch>=0.2.12; (sys_platform == 'win32') and extra == 'audio'
|
|
34
|
+
Requires-Dist: sounddevice>=0.4.6; (sys_platform != 'win32') and extra == 'audio'
|
|
35
|
+
Provides-Extra: ble
|
|
36
|
+
Requires-Dist: bleak>=0.21; extra == 'ble'
|
|
37
|
+
Provides-Extra: gamepad
|
|
38
|
+
Requires-Dist: vgamepad>=0.1.0; (sys_platform == 'win32') and extra == 'gamepad'
|
|
39
|
+
Provides-Extra: passthrough
|
|
40
|
+
Requires-Dist: aiohttp>=3.9; extra == 'passthrough'
|
|
41
|
+
Description-Content-Type: text/markdown
|
|
42
|
+
|
|
43
|
+
# Stadiacast
|
|
44
|
+
|
|
45
|
+
[](https://github.com/jkfran/stadiacast/actions/workflows/ci.yml)
|
|
46
|
+

|
|
47
|
+

|
|
48
|
+
[](LICENSE)
|
|
49
|
+
|
|
50
|
+
Google shut Stadia down in January 2023 and, with it, the cloud endpoints the Stadia
|
|
51
|
+
Controller's **Wi-Fi mode** depends on. Stadiacast is a small server you run on your own
|
|
52
|
+
network that stands in for those endpoints, so the controller you own keeps working with your
|
|
53
|
+
own PC: it joins your Wi-Fi, connects, stays on, streams every button, stick and trigger, and
|
|
54
|
+
vibrates on request.
|
|
55
|
+
|
|
56
|
+
It speaks the controller's stock protocol as is: **no firmware modification**, no Google
|
|
57
|
+
account, and nothing leaves your LAN.
|
|
58
|
+
|
|
59
|
+
> **Status:** works end to end (input and rumble) on the controller's last Wi-Fi firmware,
|
|
60
|
+
> `ota.282115`, with several controllers at once. Automatic first-boot provisioning and a
|
|
61
|
+
> virtual gamepad on Linux and macOS are on the [roadmap](#roadmap); Windows has one already.
|
|
62
|
+
> Expect rough edges; issues and pull requests are very welcome.
|
|
63
|
+
|
|
64
|
+
> [!TIP]
|
|
65
|
+
> Switched your controller to Bluetooth mode? It accepts older Google-signed images, so the
|
|
66
|
+
> community's USB flashing tools put the Wi-Fi firmware back ([Requirements](#requirements)).
|
|
67
|
+
|
|
68
|
+
## How it works
|
|
69
|
+
|
|
70
|
+
```
|
|
71
|
+
Stadia Controller (Wi-Fi) your PC / Mac running the receiver
|
|
72
|
+
───────────────────────── ──────────────────────────────────
|
|
73
|
+
DNS *.googleapis.com ─────────────────────▶ DNS sink: answers with the receiver's IP
|
|
74
|
+
HTTPS boot: generate_204, stadiaconfig,
|
|
75
|
+
Omaha update check, Clearcut logs ─────▶ boot-time endpoints (GTS certificate)
|
|
76
|
+
HTTPS v1:RegisterController,
|
|
77
|
+
v1:DiscoverDisplay (WebRTC offer) ─────▶ discovery RPCs, answer queued on GMsg
|
|
78
|
+
HTTPS /gmsg long poll ◀────────────────────── GMsg push channel: the WebRTC answer
|
|
79
|
+
UDP STUN + DTLS (display link) ◀──────────▶ RTC endpoint: display RPC handshake → READY
|
|
80
|
+
DTLS WireHeader2 0x74/0x75 RPCs, input ────▶ decoded input events (JSON Lines log)
|
|
81
|
+
DTLS second session for game streaming ◀───▶ game session: input + rumble (haptics)
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
1. **DNS.** The controller must use the receiver's machine as its DNS server. The receiver
|
|
85
|
+
spawns a small DNS server that points the Google names it needs at itself and forwards
|
|
86
|
+
everything else upstream.
|
|
87
|
+
2. **HTTPS.** The firmware validates the server certificate against its built-in Google Trust
|
|
88
|
+
Services roots and checks neither the hostname, nor the dates, nor revocation, so one GTS
|
|
89
|
+
chain, the one the receiver ships, lets it answer every Google name.
|
|
90
|
+
3. **WebRTC.** The controller offers a WebRTC session in its `DiscoverDisplay` request; the
|
|
91
|
+
receiver answers over the GMsg push channel, terminates STUN and DTLS, and plays the
|
|
92
|
+
display side of the post-DTLS RPC handshake until the controller reaches READY and starts
|
|
93
|
+
reporting input.
|
|
94
|
+
4. **Game session.** On `StartGame` the controller opens a second session for game streaming.
|
|
95
|
+
That session carries input too, and it is the only path to the controller's rumble motors.
|
|
96
|
+
|
|
97
|
+
[docs/protocol.md](docs/protocol.md) describes the protocol in detail and
|
|
98
|
+
[docs/architecture.md](docs/architecture.md) how the code is put together.
|
|
99
|
+
|
|
100
|
+
## Requirements
|
|
101
|
+
|
|
102
|
+
- A Stadia Controller on its Wi-Fi firmware (tested with `ota.282115`, the last Wi-Fi build).
|
|
103
|
+
A controller switched to Bluetooth mode accepts older Google-signed images, so the
|
|
104
|
+
community's flashing tools put the Wi-Fi firmware back. Stadiacast itself never flashes
|
|
105
|
+
anything.
|
|
106
|
+
- A Windows, macOS or Linux computer on the controller's network, acting as its DNS server.
|
|
107
|
+
The reference rig is a macOS Internet Sharing hotspot. On Linux, binding ports 53, 80, 123
|
|
108
|
+
and 443 needs root or `CAP_NET_BIND_SERVICE`; on Windows the firewall may have to let the
|
|
109
|
+
controller in ([docs/setup.md](docs/setup.md#network)).
|
|
110
|
+
- Nothing else for the basics: the receiver ships a certificate the controller accepts
|
|
111
|
+
([serving your own](docs/setup.md#certificate) is optional), and Python 3.11 or newer is
|
|
112
|
+
only needed to install from source.
|
|
113
|
+
- Optional: ffmpeg for [headset audio](#headset-audio), the ViGEmBus driver for the
|
|
114
|
+
[virtual gamepad](#virtual-gamepad-windows) on Windows, `tcpdump` for packet captures.
|
|
115
|
+
|
|
116
|
+
## Install
|
|
117
|
+
|
|
118
|
+
**Executables.** Every [release](https://github.com/jkfran/stadiacast/releases) carries an
|
|
119
|
+
archive for Windows x86_64, macOS (Apple silicon) and Linux x86_64, with `stadiacast` and
|
|
120
|
+
`stadiacast-provision` as single files, Python included. Unpack it and run `stadiacast` from a
|
|
121
|
+
terminal. The builds are not signed: macOS asks you to allow the program once (right-click,
|
|
122
|
+
Open, or `xattr -d com.apple.quarantine stadiacast`) and Windows SmartScreen may warn.
|
|
123
|
+
|
|
124
|
+
**From source**, with [uv](https://docs.astral.sh/uv/):
|
|
125
|
+
|
|
126
|
+
```sh
|
|
127
|
+
git clone https://github.com/jkfran/stadiacast.git
|
|
128
|
+
cd stadiacast
|
|
129
|
+
uv sync --all-extras
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
or with pip, `pip install -e ".[ble,passthrough,audio,gamepad]"`. Either way you get
|
|
133
|
+
`stadiacast`, `stadiacast-provision` and `stadiacast-show-input`, and the scripts under
|
|
134
|
+
`tools/` find the virtualenv on their own.
|
|
135
|
+
|
|
136
|
+
## Quick start
|
|
137
|
+
|
|
138
|
+
1. **Network.** Put the controller and the receiver on one network where the receiver's
|
|
139
|
+
machine is the controller's DNS server; a hotspot hosted by that machine is the simplest
|
|
140
|
+
([docs/setup.md](docs/setup.md#network)).
|
|
141
|
+
2. **First boot only.** A controller fresh out of the box, or one that was reset, waits in
|
|
142
|
+
Bluetooth setup mode for its Wi-Fi credentials:
|
|
143
|
+
|
|
144
|
+
```sh
|
|
145
|
+
stadiacast-provision scan
|
|
146
|
+
stadiacast-provision provision --address <ADDR> --ssid <your SSID>
|
|
147
|
+
```
|
|
148
|
+
|
|
149
|
+
3. **Start the receiver.** `tools/start_receiver.sh` runs it with the proven settings (see
|
|
150
|
+
`.env.example`) and writes a run directory under `captures/`; or directly:
|
|
151
|
+
|
|
152
|
+
```sh
|
|
153
|
+
stadiacast --preset live --advertise-ip 192.168.2.1 --dns-upstream 192.168.1.1 -v
|
|
154
|
+
```
|
|
155
|
+
|
|
156
|
+
4. **Wake the controller** with a ~1 s press of the Stadia button, released at the first
|
|
157
|
+
vibration. Two short rumbles and a steady light mean it found the receiver; a blinking
|
|
158
|
+
light with no rumble is a short "report wake", so press again.
|
|
159
|
+
5. **Watch the input.** The console shows one line per report, `stadiacast-show-input
|
|
160
|
+
captures/<stamp>` summarizes a run, and `GET http://<receiver>/_status` shows the session
|
|
161
|
+
state, the battery level and whether a headset is plugged in.
|
|
162
|
+
6. **Rumble.** Start with `--start-game`, since the game session is the only path to the
|
|
163
|
+
motors, then post levels from 0 to 65535:
|
|
164
|
+
|
|
165
|
+
```sh
|
|
166
|
+
curl -d '{"left":40000,"right":40000,"ms":1500}' http://192.168.2.1/_rumble # pulse
|
|
167
|
+
curl -d '{"left":65535,"right":20000}' http://192.168.2.1/_rumble # hold
|
|
168
|
+
curl -d '{"left":0,"right":0}' http://192.168.2.1/_rumble # stop
|
|
169
|
+
```
|
|
170
|
+
|
|
171
|
+
7. **More controllers** each get their own session, `c0`, `c1`, ... in the log and in
|
|
172
|
+
`/_status`; name the target of a control request with `?controller=c1` or a `"controller"`
|
|
173
|
+
field in the JSON body.
|
|
174
|
+
8. **Stop** with `tools/stop_receiver.sh`.
|
|
175
|
+
|
|
176
|
+
`--preset live` is the flag set proven end to end; explicit flags override it, and
|
|
177
|
+
`stadiacast --help` lists every knob.
|
|
178
|
+
|
|
179
|
+
## Input
|
|
180
|
+
|
|
181
|
+
In READY the controller reports every change on the display link. The receiver decodes it
|
|
182
|
+
into events with named buttons, stick axes (-32768 to 32767, Y up) and triggers (0 to 255),
|
|
183
|
+
with the button bits in the XInput layout. The full report is in
|
|
184
|
+
[docs/protocol.md](docs/protocol.md#input-report-controllertodisplay-3).
|
|
185
|
+
|
|
186
|
+
## Headset audio
|
|
187
|
+
|
|
188
|
+
The receiver can play sound on a headset plugged into the controller. ffmpeg with libopus
|
|
189
|
+
encodes every source, so it has to be on `PATH`:
|
|
190
|
+
|
|
191
|
+
```sh
|
|
192
|
+
stadiacast --audio-source tone # 440 Hz, proves the path without any audio setup
|
|
193
|
+
stadiacast --audio-source file:x.ogg # anything ffmpeg can decode
|
|
194
|
+
stadiacast --audio-source system # what this computer plays
|
|
195
|
+
stadiacast --audio-source device:NAME # one capture device; --audio-devices lists them
|
|
196
|
+
```
|
|
197
|
+
|
|
198
|
+
The stream starts once the display link reaches READY. `--audio-bitrate` sets the Opus bitrate
|
|
199
|
+
(64 kbit/s by default), `--audio-application` favours delay (`lowdelay`, the default) or
|
|
200
|
+
music (`audio`), and `--audio-volume` the gain. `/_status` reports the stream under `playback`
|
|
201
|
+
and `/_audio` starts, stops and adjusts it while the receiver runs
|
|
202
|
+
([docs/setup.md](docs/setup.md#running)).
|
|
203
|
+
|
|
204
|
+
`system` and `device:NAME` capture sound and need the `audio` extra, which the executables and
|
|
205
|
+
`uv sync --all-extras` include (Linux also needs the PortAudio library, `apt install
|
|
206
|
+
libportaudio2`). On macOS, send the sound to a virtual output such as BlackHole
|
|
207
|
+
(`brew install blackhole-2ch`), alone or in a Multi-Output Device next to the speakers; the
|
|
208
|
+
volume keys do not act on it, so set the loudness in the app or with `--audio-volume`. On
|
|
209
|
+
Windows the default output's loopback is captured and the volume slider and mute reach the
|
|
210
|
+
headset (`--audio-follow-volume off` sends the raw mix). On Linux the default output's
|
|
211
|
+
PulseAudio monitor is read, untested on a real machine. How the stream rides the display link
|
|
212
|
+
is in [docs/protocol.md](docs/protocol.md#audio-to-the-headset-jack).
|
|
213
|
+
|
|
214
|
+
## Virtual gamepad (Windows)
|
|
215
|
+
|
|
216
|
+
`--gamepad vigem` turns each controller into an ordinary Xbox 360 controller for the games on
|
|
217
|
+
the receiver's PC:
|
|
218
|
+
|
|
219
|
+
```sh
|
|
220
|
+
stadiacast --gamepad vigem # one virtual pad per controller
|
|
221
|
+
```
|
|
222
|
+
|
|
223
|
+
Only a driver can create a device that Windows and its games see as a controller, so this
|
|
224
|
+
needs one extra install, once and with an admin prompt: the
|
|
225
|
+
[ViGEmBus](https://github.com/nefarius/ViGEmBus/releases) driver, the same one behind
|
|
226
|
+
DS4Windows, Sunshine and the other tools of this kind. Its author archived the project in 2023
|
|
227
|
+
and 1.22.0 is the final version; it still installs and works on current Windows 10 and 11. It
|
|
228
|
+
has to be desktop Windows, since Windows Server lacks the Xbox 360 controller driver the pad
|
|
229
|
+
binds to. Without the driver, the receiver stops at start-up with one line that says so; from
|
|
230
|
+
source, installing the `gamepad` extra offers to run the driver's installer.
|
|
231
|
+
|
|
232
|
+
Every report reaches the pad as XInput fields with the standard stick deadzones
|
|
233
|
+
(`--gamepad-deadzone off` for raw sticks); the Stadia button is the guide button, Assistant
|
|
234
|
+
and Capture have no XInput equivalent. The game's rumble comes back to the controller through
|
|
235
|
+
the game session, which `--gamepad` starts for that reason. Two controllers are players 1 and
|
|
236
|
+
2, and `/_status` lists the pads under `gamepads`. The pad has not been run on a real Windows
|
|
237
|
+
PC yet.
|
|
238
|
+
|
|
239
|
+
## Development
|
|
240
|
+
|
|
241
|
+
```sh
|
|
242
|
+
uv sync --all-extras # the receiver, its extras and the dev tools
|
|
243
|
+
make check # ruff, shellcheck, mypy, the schema check and the tests
|
|
244
|
+
```
|
|
245
|
+
|
|
246
|
+
The tests need no hardware: `tests/fake_controller.py` reproduces the firmware's network
|
|
247
|
+
behaviour. [CONTRIBUTING.md](CONTRIBUTING.md) has the workflow, how to run the fake by hand
|
|
248
|
+
against a receiver in lab mode, and how to test against a real controller safely.
|
|
249
|
+
|
|
250
|
+
## Roadmap
|
|
251
|
+
|
|
252
|
+
1. **First-boot autoconnect:** detect a controller in BLE setup mode and provision it onto the
|
|
253
|
+
network automatically (today `stadiacast-provision` is the manual version).
|
|
254
|
+
2. **Virtual gamepad on Linux and macOS:** Windows has one (`--gamepad vigem`); Linux (uinput)
|
|
255
|
+
and macOS are open.
|
|
256
|
+
|
|
257
|
+
## Security and privacy
|
|
258
|
+
|
|
259
|
+
The receiver listens on your LAN with no authentication and is meant for a trusted network
|
|
260
|
+
such as a hotspot you host. It never contacts Google except for the optional `stadiaconfig`
|
|
261
|
+
passthrough. See [SECURITY.md](SECURITY.md).
|
|
262
|
+
|
|
263
|
+
## Legal
|
|
264
|
+
|
|
265
|
+
This is an independent interoperability project for owners of the Stadia Controller. It is
|
|
266
|
+
not affiliated with, endorsed by or supported by Google. Stadia is a trademark of Google LLC.
|
|
267
|
+
|
|
268
|
+
Stadiacast contains no Google source code and neither includes nor distributes the
|
|
269
|
+
controller's firmware: the firmware stays on your controller and the receiver speaks its
|
|
270
|
+
stock network protocol as-is. The protocol was recovered by observing the controller and by
|
|
271
|
+
analysing the firmware it runs, for the sole purpose of interoperability. Three protobuf
|
|
272
|
+
schemas are reproduced from descriptors embedded in that firmware; they are Google's, and
|
|
273
|
+
[NOTICE](NOTICE) lists them. Please do not post firmware images, decompiled code or whole
|
|
274
|
+
packet captures in issues or pull requests.
|
|
275
|
+
|
|
276
|
+
Stadiacast is released under the [MIT License](LICENSE), with the exception stated in
|
|
277
|
+
[NOTICE](NOTICE).
|
|
@@ -0,0 +1,235 @@
|
|
|
1
|
+
# Stadiacast
|
|
2
|
+
|
|
3
|
+
[](https://github.com/jkfran/stadiacast/actions/workflows/ci.yml)
|
|
4
|
+

|
|
5
|
+

|
|
6
|
+
[](LICENSE)
|
|
7
|
+
|
|
8
|
+
Google shut Stadia down in January 2023 and, with it, the cloud endpoints the Stadia
|
|
9
|
+
Controller's **Wi-Fi mode** depends on. Stadiacast is a small server you run on your own
|
|
10
|
+
network that stands in for those endpoints, so the controller you own keeps working with your
|
|
11
|
+
own PC: it joins your Wi-Fi, connects, stays on, streams every button, stick and trigger, and
|
|
12
|
+
vibrates on request.
|
|
13
|
+
|
|
14
|
+
It speaks the controller's stock protocol as is: **no firmware modification**, no Google
|
|
15
|
+
account, and nothing leaves your LAN.
|
|
16
|
+
|
|
17
|
+
> **Status:** works end to end (input and rumble) on the controller's last Wi-Fi firmware,
|
|
18
|
+
> `ota.282115`, with several controllers at once. Automatic first-boot provisioning and a
|
|
19
|
+
> virtual gamepad on Linux and macOS are on the [roadmap](#roadmap); Windows has one already.
|
|
20
|
+
> Expect rough edges; issues and pull requests are very welcome.
|
|
21
|
+
|
|
22
|
+
> [!TIP]
|
|
23
|
+
> Switched your controller to Bluetooth mode? It accepts older Google-signed images, so the
|
|
24
|
+
> community's USB flashing tools put the Wi-Fi firmware back ([Requirements](#requirements)).
|
|
25
|
+
|
|
26
|
+
## How it works
|
|
27
|
+
|
|
28
|
+
```
|
|
29
|
+
Stadia Controller (Wi-Fi) your PC / Mac running the receiver
|
|
30
|
+
───────────────────────── ──────────────────────────────────
|
|
31
|
+
DNS *.googleapis.com ─────────────────────▶ DNS sink: answers with the receiver's IP
|
|
32
|
+
HTTPS boot: generate_204, stadiaconfig,
|
|
33
|
+
Omaha update check, Clearcut logs ─────▶ boot-time endpoints (GTS certificate)
|
|
34
|
+
HTTPS v1:RegisterController,
|
|
35
|
+
v1:DiscoverDisplay (WebRTC offer) ─────▶ discovery RPCs, answer queued on GMsg
|
|
36
|
+
HTTPS /gmsg long poll ◀────────────────────── GMsg push channel: the WebRTC answer
|
|
37
|
+
UDP STUN + DTLS (display link) ◀──────────▶ RTC endpoint: display RPC handshake → READY
|
|
38
|
+
DTLS WireHeader2 0x74/0x75 RPCs, input ────▶ decoded input events (JSON Lines log)
|
|
39
|
+
DTLS second session for game streaming ◀───▶ game session: input + rumble (haptics)
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
1. **DNS.** The controller must use the receiver's machine as its DNS server. The receiver
|
|
43
|
+
spawns a small DNS server that points the Google names it needs at itself and forwards
|
|
44
|
+
everything else upstream.
|
|
45
|
+
2. **HTTPS.** The firmware validates the server certificate against its built-in Google Trust
|
|
46
|
+
Services roots and checks neither the hostname, nor the dates, nor revocation, so one GTS
|
|
47
|
+
chain, the one the receiver ships, lets it answer every Google name.
|
|
48
|
+
3. **WebRTC.** The controller offers a WebRTC session in its `DiscoverDisplay` request; the
|
|
49
|
+
receiver answers over the GMsg push channel, terminates STUN and DTLS, and plays the
|
|
50
|
+
display side of the post-DTLS RPC handshake until the controller reaches READY and starts
|
|
51
|
+
reporting input.
|
|
52
|
+
4. **Game session.** On `StartGame` the controller opens a second session for game streaming.
|
|
53
|
+
That session carries input too, and it is the only path to the controller's rumble motors.
|
|
54
|
+
|
|
55
|
+
[docs/protocol.md](docs/protocol.md) describes the protocol in detail and
|
|
56
|
+
[docs/architecture.md](docs/architecture.md) how the code is put together.
|
|
57
|
+
|
|
58
|
+
## Requirements
|
|
59
|
+
|
|
60
|
+
- A Stadia Controller on its Wi-Fi firmware (tested with `ota.282115`, the last Wi-Fi build).
|
|
61
|
+
A controller switched to Bluetooth mode accepts older Google-signed images, so the
|
|
62
|
+
community's flashing tools put the Wi-Fi firmware back. Stadiacast itself never flashes
|
|
63
|
+
anything.
|
|
64
|
+
- A Windows, macOS or Linux computer on the controller's network, acting as its DNS server.
|
|
65
|
+
The reference rig is a macOS Internet Sharing hotspot. On Linux, binding ports 53, 80, 123
|
|
66
|
+
and 443 needs root or `CAP_NET_BIND_SERVICE`; on Windows the firewall may have to let the
|
|
67
|
+
controller in ([docs/setup.md](docs/setup.md#network)).
|
|
68
|
+
- Nothing else for the basics: the receiver ships a certificate the controller accepts
|
|
69
|
+
([serving your own](docs/setup.md#certificate) is optional), and Python 3.11 or newer is
|
|
70
|
+
only needed to install from source.
|
|
71
|
+
- Optional: ffmpeg for [headset audio](#headset-audio), the ViGEmBus driver for the
|
|
72
|
+
[virtual gamepad](#virtual-gamepad-windows) on Windows, `tcpdump` for packet captures.
|
|
73
|
+
|
|
74
|
+
## Install
|
|
75
|
+
|
|
76
|
+
**Executables.** Every [release](https://github.com/jkfran/stadiacast/releases) carries an
|
|
77
|
+
archive for Windows x86_64, macOS (Apple silicon) and Linux x86_64, with `stadiacast` and
|
|
78
|
+
`stadiacast-provision` as single files, Python included. Unpack it and run `stadiacast` from a
|
|
79
|
+
terminal. The builds are not signed: macOS asks you to allow the program once (right-click,
|
|
80
|
+
Open, or `xattr -d com.apple.quarantine stadiacast`) and Windows SmartScreen may warn.
|
|
81
|
+
|
|
82
|
+
**From source**, with [uv](https://docs.astral.sh/uv/):
|
|
83
|
+
|
|
84
|
+
```sh
|
|
85
|
+
git clone https://github.com/jkfran/stadiacast.git
|
|
86
|
+
cd stadiacast
|
|
87
|
+
uv sync --all-extras
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
or with pip, `pip install -e ".[ble,passthrough,audio,gamepad]"`. Either way you get
|
|
91
|
+
`stadiacast`, `stadiacast-provision` and `stadiacast-show-input`, and the scripts under
|
|
92
|
+
`tools/` find the virtualenv on their own.
|
|
93
|
+
|
|
94
|
+
## Quick start
|
|
95
|
+
|
|
96
|
+
1. **Network.** Put the controller and the receiver on one network where the receiver's
|
|
97
|
+
machine is the controller's DNS server; a hotspot hosted by that machine is the simplest
|
|
98
|
+
([docs/setup.md](docs/setup.md#network)).
|
|
99
|
+
2. **First boot only.** A controller fresh out of the box, or one that was reset, waits in
|
|
100
|
+
Bluetooth setup mode for its Wi-Fi credentials:
|
|
101
|
+
|
|
102
|
+
```sh
|
|
103
|
+
stadiacast-provision scan
|
|
104
|
+
stadiacast-provision provision --address <ADDR> --ssid <your SSID>
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
3. **Start the receiver.** `tools/start_receiver.sh` runs it with the proven settings (see
|
|
108
|
+
`.env.example`) and writes a run directory under `captures/`; or directly:
|
|
109
|
+
|
|
110
|
+
```sh
|
|
111
|
+
stadiacast --preset live --advertise-ip 192.168.2.1 --dns-upstream 192.168.1.1 -v
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
4. **Wake the controller** with a ~1 s press of the Stadia button, released at the first
|
|
115
|
+
vibration. Two short rumbles and a steady light mean it found the receiver; a blinking
|
|
116
|
+
light with no rumble is a short "report wake", so press again.
|
|
117
|
+
5. **Watch the input.** The console shows one line per report, `stadiacast-show-input
|
|
118
|
+
captures/<stamp>` summarizes a run, and `GET http://<receiver>/_status` shows the session
|
|
119
|
+
state, the battery level and whether a headset is plugged in.
|
|
120
|
+
6. **Rumble.** Start with `--start-game`, since the game session is the only path to the
|
|
121
|
+
motors, then post levels from 0 to 65535:
|
|
122
|
+
|
|
123
|
+
```sh
|
|
124
|
+
curl -d '{"left":40000,"right":40000,"ms":1500}' http://192.168.2.1/_rumble # pulse
|
|
125
|
+
curl -d '{"left":65535,"right":20000}' http://192.168.2.1/_rumble # hold
|
|
126
|
+
curl -d '{"left":0,"right":0}' http://192.168.2.1/_rumble # stop
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
7. **More controllers** each get their own session, `c0`, `c1`, ... in the log and in
|
|
130
|
+
`/_status`; name the target of a control request with `?controller=c1` or a `"controller"`
|
|
131
|
+
field in the JSON body.
|
|
132
|
+
8. **Stop** with `tools/stop_receiver.sh`.
|
|
133
|
+
|
|
134
|
+
`--preset live` is the flag set proven end to end; explicit flags override it, and
|
|
135
|
+
`stadiacast --help` lists every knob.
|
|
136
|
+
|
|
137
|
+
## Input
|
|
138
|
+
|
|
139
|
+
In READY the controller reports every change on the display link. The receiver decodes it
|
|
140
|
+
into events with named buttons, stick axes (-32768 to 32767, Y up) and triggers (0 to 255),
|
|
141
|
+
with the button bits in the XInput layout. The full report is in
|
|
142
|
+
[docs/protocol.md](docs/protocol.md#input-report-controllertodisplay-3).
|
|
143
|
+
|
|
144
|
+
## Headset audio
|
|
145
|
+
|
|
146
|
+
The receiver can play sound on a headset plugged into the controller. ffmpeg with libopus
|
|
147
|
+
encodes every source, so it has to be on `PATH`:
|
|
148
|
+
|
|
149
|
+
```sh
|
|
150
|
+
stadiacast --audio-source tone # 440 Hz, proves the path without any audio setup
|
|
151
|
+
stadiacast --audio-source file:x.ogg # anything ffmpeg can decode
|
|
152
|
+
stadiacast --audio-source system # what this computer plays
|
|
153
|
+
stadiacast --audio-source device:NAME # one capture device; --audio-devices lists them
|
|
154
|
+
```
|
|
155
|
+
|
|
156
|
+
The stream starts once the display link reaches READY. `--audio-bitrate` sets the Opus bitrate
|
|
157
|
+
(64 kbit/s by default), `--audio-application` favours delay (`lowdelay`, the default) or
|
|
158
|
+
music (`audio`), and `--audio-volume` the gain. `/_status` reports the stream under `playback`
|
|
159
|
+
and `/_audio` starts, stops and adjusts it while the receiver runs
|
|
160
|
+
([docs/setup.md](docs/setup.md#running)).
|
|
161
|
+
|
|
162
|
+
`system` and `device:NAME` capture sound and need the `audio` extra, which the executables and
|
|
163
|
+
`uv sync --all-extras` include (Linux also needs the PortAudio library, `apt install
|
|
164
|
+
libportaudio2`). On macOS, send the sound to a virtual output such as BlackHole
|
|
165
|
+
(`brew install blackhole-2ch`), alone or in a Multi-Output Device next to the speakers; the
|
|
166
|
+
volume keys do not act on it, so set the loudness in the app or with `--audio-volume`. On
|
|
167
|
+
Windows the default output's loopback is captured and the volume slider and mute reach the
|
|
168
|
+
headset (`--audio-follow-volume off` sends the raw mix). On Linux the default output's
|
|
169
|
+
PulseAudio monitor is read, untested on a real machine. How the stream rides the display link
|
|
170
|
+
is in [docs/protocol.md](docs/protocol.md#audio-to-the-headset-jack).
|
|
171
|
+
|
|
172
|
+
## Virtual gamepad (Windows)
|
|
173
|
+
|
|
174
|
+
`--gamepad vigem` turns each controller into an ordinary Xbox 360 controller for the games on
|
|
175
|
+
the receiver's PC:
|
|
176
|
+
|
|
177
|
+
```sh
|
|
178
|
+
stadiacast --gamepad vigem # one virtual pad per controller
|
|
179
|
+
```
|
|
180
|
+
|
|
181
|
+
Only a driver can create a device that Windows and its games see as a controller, so this
|
|
182
|
+
needs one extra install, once and with an admin prompt: the
|
|
183
|
+
[ViGEmBus](https://github.com/nefarius/ViGEmBus/releases) driver, the same one behind
|
|
184
|
+
DS4Windows, Sunshine and the other tools of this kind. Its author archived the project in 2023
|
|
185
|
+
and 1.22.0 is the final version; it still installs and works on current Windows 10 and 11. It
|
|
186
|
+
has to be desktop Windows, since Windows Server lacks the Xbox 360 controller driver the pad
|
|
187
|
+
binds to. Without the driver, the receiver stops at start-up with one line that says so; from
|
|
188
|
+
source, installing the `gamepad` extra offers to run the driver's installer.
|
|
189
|
+
|
|
190
|
+
Every report reaches the pad as XInput fields with the standard stick deadzones
|
|
191
|
+
(`--gamepad-deadzone off` for raw sticks); the Stadia button is the guide button, Assistant
|
|
192
|
+
and Capture have no XInput equivalent. The game's rumble comes back to the controller through
|
|
193
|
+
the game session, which `--gamepad` starts for that reason. Two controllers are players 1 and
|
|
194
|
+
2, and `/_status` lists the pads under `gamepads`. The pad has not been run on a real Windows
|
|
195
|
+
PC yet.
|
|
196
|
+
|
|
197
|
+
## Development
|
|
198
|
+
|
|
199
|
+
```sh
|
|
200
|
+
uv sync --all-extras # the receiver, its extras and the dev tools
|
|
201
|
+
make check # ruff, shellcheck, mypy, the schema check and the tests
|
|
202
|
+
```
|
|
203
|
+
|
|
204
|
+
The tests need no hardware: `tests/fake_controller.py` reproduces the firmware's network
|
|
205
|
+
behaviour. [CONTRIBUTING.md](CONTRIBUTING.md) has the workflow, how to run the fake by hand
|
|
206
|
+
against a receiver in lab mode, and how to test against a real controller safely.
|
|
207
|
+
|
|
208
|
+
## Roadmap
|
|
209
|
+
|
|
210
|
+
1. **First-boot autoconnect:** detect a controller in BLE setup mode and provision it onto the
|
|
211
|
+
network automatically (today `stadiacast-provision` is the manual version).
|
|
212
|
+
2. **Virtual gamepad on Linux and macOS:** Windows has one (`--gamepad vigem`); Linux (uinput)
|
|
213
|
+
and macOS are open.
|
|
214
|
+
|
|
215
|
+
## Security and privacy
|
|
216
|
+
|
|
217
|
+
The receiver listens on your LAN with no authentication and is meant for a trusted network
|
|
218
|
+
such as a hotspot you host. It never contacts Google except for the optional `stadiaconfig`
|
|
219
|
+
passthrough. See [SECURITY.md](SECURITY.md).
|
|
220
|
+
|
|
221
|
+
## Legal
|
|
222
|
+
|
|
223
|
+
This is an independent interoperability project for owners of the Stadia Controller. It is
|
|
224
|
+
not affiliated with, endorsed by or supported by Google. Stadia is a trademark of Google LLC.
|
|
225
|
+
|
|
226
|
+
Stadiacast contains no Google source code and neither includes nor distributes the
|
|
227
|
+
controller's firmware: the firmware stays on your controller and the receiver speaks its
|
|
228
|
+
stock network protocol as-is. The protocol was recovered by observing the controller and by
|
|
229
|
+
analysing the firmware it runs, for the sole purpose of interoperability. Three protobuf
|
|
230
|
+
schemas are reproduced from descriptors embedded in that firmware; they are Google's, and
|
|
231
|
+
[NOTICE](NOTICE) lists them. Please do not post firmware images, decompiled code or whole
|
|
232
|
+
packet captures in issues or pull requests.
|
|
233
|
+
|
|
234
|
+
Stadiacast is released under the [MIT License](LICENSE), with the exception stated in
|
|
235
|
+
[NOTICE](NOTICE).
|