Files
DroidDeck/tools/xcloud-player/greenlight/lib/channel/message.ts
The412Banner c5d7cdc058 Xbox Cloud Gaming: stream from the store and Games tab, native player with DroidDeck frame generation (#1531)
Xbox Cloud Gaming: stream from the store and Games tab, native player with DroidDeck frame generation

Stream Xbox games with Xbox Cloud Gaming (Game Pass) from DroidDeck, outside
the Linux session.

- Backend: `xodus-cli cloud-token` (tools/xbox/droiddeck-cloud-token.patch)
  gets the streaming tokens; they stay in memory.
- Web player (default): the Greenlight player page (tools/xcloud-player, MIT)
  in a WebView, plus Microsoft's own xbox.com/play player. In-stream menu with
  screen effects (sharpen, colour, deband, FXAA, CRT, NTSC, Toon, fps cap),
  stream quality, performance overlay (level, layout, top right / top left)
  and rumble.
- Native player (opt-in, Android 10+, Player engine: Native): Google's WebRTC
  (io.github.webrtc-sdk 125.6422.07, BSD) with the Greenlight protocol ported
  to Kotlin, in its own process (:xcloudnative) with its own instance of
  DroidDeck's compositor. Decoded frames reach the compositor as
  AHardwareBuffers (new stream mode, VK_ANDROID_external_memory_android_
  hardware_buffer import), so DDFG 2x and LSFG 2x/3x/4x run on them. Live
  frame generation, frame-rate target (presets and a 10-165 fps slider, even
  pacing), zero-copy picture, small network buffer, fast decoder, instant
  controller input, overlay with engine and on-screen fps, background end,
  restart and web fallback. It takes the main process's tokens through
  XboxCloudTokenService; they are fetched ahead while a cloud game is shown.
- The compositor requests the dma-buf import extensions only where the driver
  has them (the system driver has none).
- Catalog: cloud availability, Game Pass plan CFQ7TTC0K6L8 counted, library
  filter All / Owned / Game Pass / Cloud, Stream and Add to Games on the store
  page, stream-only Games-tab entries (Stream + Download / Play + Stream).
- Xbox games get VC++ 2015-2022 and the ARM64 runtime registry entries
  (vcredist2022_arm64, built in until the catalog has it) before each launch.
- Controller focus reaches pickers opened from a game page (install location,
  uninstall); Add / Remove from Games keep the focus.

Credits: Greenlight by UnknownSKL (Jim Kroon), MIT, player and protocol;
Better xCloud for the resolution / OS mapping; MGamer404's Bannerlator #627
for the ARM64 runtime approach. Ported from the412banner's Bannerlator.
2026-10-24 23:17:57 -04:00

120 lines
3.9 KiB
TypeScript

import { v4 as uuidv4 } from 'uuid'
import Channel from './channel'
import MessageHandler from './message/handler'
export default class MessageChannel extends Channel {
_handshakeCompleted = false
getChannelName() {
return 'message'
}
getChannelConfig() {
return {
protocol: 'messageV1',
ordered: true,
}
}
onOpen() {
console.log('[MessageChannel] Sending handshake...')
const handshake = JSON.stringify({
'type':'Handshake',
'version':'messageV1',
'id':'be0bfc6d-1e83-4c8a-90ed-fa8601c5a179',
'cv':'0',
})
this.send(handshake)
}
onMessage(event: MessageEvent<any>) {
const data = JSON.parse(event.data)
// console.log('[MessageChannel] received:', data)
switch(data.type){
case 'HandshakeAck':
console.log('[MessageChannel] Handshake completed')
this._handshakeCompleted = true
// Start control & input channels
this.getPlayer()._channels.control.sendAuthorization()
this.getPlayer()._channels.input.start()
this.sendConfig()
break
case 'TransactionStart':
case 'Message':
new MessageHandler(this, data)
break
default:
console.log('[MessageChannel] Unhandled message type:', data.type, data)
}
}
sendConfig(){
const systemUis = []
const systemVersion = [0, 2, 0]
const uiConfig = JSON.stringify(this.generateMessage('/streaming/systemUi/configuration', {
'version': systemVersion,
'systemUis': systemUis, // Xbox Windows app has [33], xCloud has [10,19,31,27,32,-41]
// 8 = unknown
// 10 = ShowVirtualKeyboard
// 13 = unknown
// 15 = unknown
// 19 = ShowMessageDialog
// 31 = ShowApplication
// 27 = ShowPurchase
// 32 = ShowTimerExtensions
// 33 = Xbox windows app, disables the nexus menu on xCloud (Alt nexus menu?)
// -44 = unknown
// 40 = unknown
// 41 = unknown
// -43 = unknown
// Possible options: Keyboard, PurchaseModal
}))
this.send(uiConfig)
const clientConfig = JSON.stringify(this.generateMessage('/streaming/properties/clientappinstallidchanged', { 'clientAppInstallId': 'c97d7ee0-73b2-4239-bf1d-9d805a338429' }))
this.send(clientConfig)
const orientationConfig = JSON.stringify(this.generateMessage('/streaming/characteristics/orientationchanged', { 'orientation': 0 }))
this.send(orientationConfig)
const touchConfig = JSON.stringify(this.generateMessage('/streaming/characteristics/touchinputenabledchanged', { 'touchInputEnabled': false }))
this.send(touchConfig)
const deviceConfig = JSON.stringify(this.generateMessage('/streaming/characteristics/clientdevicecapabilities', {}))
this.send(deviceConfig)
const dimensionsConfig = JSON.stringify(this.generateMessage('/streaming/characteristics/dimensionschanged', {
'horizontal': 1920,
'vertical': 1080,
'preferredWidth': 1920,
'preferredHeight': 1080,
'safeAreaLeft': 0,
'safeAreaTop': 0,
'safeAreaRight': 1920,
'safeAreaBottom': 1080,
'supportsCustomResolution':true,
}))
this.send(dimensionsConfig)
}
generateMessage(path, data) {
return {
'type': 'Message',
'content': JSON.stringify(data),
'id': uuidv4(),
'target': path,
'cv': '',
}
}
destroy() {
// console.log('DebugChannel destroy() called')
}
}