UNPKG

homebridge-xbox-tv

Version:

Homebridge plugin to control Xbox game consoles.

217 lines (203 loc) 11.1 kB
import { LocalApi } from '../constants.js'; class Packets { constructor(type) { this.type = type; const types = { flags(length, value) { const packet = { value, length, pack(packetStructure) { return packetStructure.writeBytes(setFlags(this.value)); }, unpack(packetStructure) { return readFlags(packetStructure.readBytes(this.length)); } }; return packet; }, bytes(length = 0, value = Buffer.alloc(length)) { const packet = { value, length, pack(packetStructure) { return packetStructure.writeBytes(this.value); }, unpack(packetStructure) { const readLength = this.length > 0 ? this.length : packetStructure.packet.length - packetStructure.offset; return packetStructure.readBytes(readLength); } }; return packet; }, uInt16(value) { const packet = { value, pack(packetStructure) { return packetStructure.writeUInt16(this.value); }, unpack(packetStructure) { return packetStructure.readUInt16(); } }; return packet; }, uInt32(value) { const packet = { value, pack(packetStructure) { return packetStructure.writeUInt32(this.value); }, unpack(packetStructure) { return packetStructure.readUInt32(); } }; return packet; }, sInt32(value) { const packet = { value, pack(packetStructure) { return packetStructure.writeInt32(this.value); }, unpack(packetStructure) { return packetStructure.readInt32(); } }; return packet; }, // FIX: uInt64 value must be a Buffer — '' produces a 0-byte buffer, // corrupting the 8-byte field on the wire. Default is Buffer.alloc(length). uInt64(length, value = Buffer.alloc(length)) { const packet = { value: Buffer.isBuffer(value) ? value : Buffer.alloc(length), length, pack(packetStructure) { return packetStructure.writeBytes(this.value); }, unpack(packetStructure) { return packetStructure.readBytes(length); } }; return packet; }, sgString(value) { const packet = { value, pack(packetStructure) { return packetStructure.writeSGString(this.value); }, unpack(packetStructure) { return packetStructure.readSGString().toString(); } }; return packet; }, sgArray(type, value = []) { const packet = { value, type, pack(packetStructure) { packetStructure.writeUInt16(this.value.length); const arrayStructure = packets[this.type]; for (const item of this.value) { Object.keys(arrayStructure).forEach(name => { arrayStructure[name].value = item[name]; packetStructure = arrayStructure[name].pack(packetStructure); }); } return packetStructure; }, unpack(packetStructure) { const arrayCount = packetStructure.readUInt16(); const array = []; for (let i = 0; i < arrayCount; i++) { const arrayStructure = packets[this.type]; const item = {}; Object.keys(arrayStructure).forEach(name => { item[name] = arrayStructure[name].unpack(packetStructure); }); array.push(item); } return array; } }; return packet; }, sgList(type, value = []) { const packet = { value, type, pack(packetStructure) { packetStructure.writeUInt32(this.value.length); const arrayStructure = packets[this.type]; for (const item of this.value) { Object.keys(arrayStructure).forEach(name => { arrayStructure[name].value = item[name]; packetStructure = arrayStructure[name].pack(packetStructure); }); } return packetStructure; }, unpack(packetStructure) { const arrayCount = packetStructure.readUInt32(); const array = []; for (let i = 0; i < arrayCount; i++) { const arrayStructure = packets[this.type]; const item = {}; Object.keys(arrayStructure).forEach(name => { item[name] = arrayStructure[name].unpack(packetStructure); }); array.push(item); } return array; } }; return packet; }, mapper(map, item) { const packet = { item, value: false, pack(packetStructure) { return item.pack(packetStructure); }, unpack(packetStructure) { const key = item.unpack(packetStructure); return map[key] ?? key; } }; return packet; } }; // FIX: uInt64 fields that were '' now default to Buffer.alloc(8) via fixed uInt64() factory. // acknowledge keeps sgList (UInt32 count) — console accepts it and original worked with it. const packets = { powerOn: { liveId: types.sgString() }, json: { json: types.sgString('{}') }, discoveryRequest: { flags: types.uInt32(0), clientType: types.uInt16(3), minVersion: types.uInt16(0), maxVersion: types.uInt16(2) }, discoveryResponse: { flags: types.uInt32(0), clientType: types.uInt16(0), consoleName: types.sgString(), uuid: types.sgString(), lastError: types.uInt32(0), certificateLength: types.uInt16(0), certificate: types.bytes() }, connectRequest: { uuid: types.bytes(16, ''), publicKeyType: types.uInt16(0), publicKey: types.bytes(64, ''), iv: types.bytes(16, ''), payloadProtected: types.bytes() }, connectResponse: { iv: types.bytes(16, ''), payloadProtected: types.bytes() }, connectRequestProtected: { userHash: types.sgString(''), token: types.sgString(''), connectRequestNum: types.uInt32(0), connectRequestGroupStart: types.uInt32(0), connectRequestGroupEnd: types.uInt32(1) }, connectResponseProtected: { connectResult: types.uInt16(1), pairingState: types.uInt16(2), participantId: types.uInt32(0) }, localJoin: { clientType: types.uInt16(3), nativeWidth: types.uInt16(1080), nativeHeight: types.uInt16(1920), dpiX: types.uInt16(96), dpiY: types.uInt16(96), deviceCapabilities: types.uInt64(8, Buffer.from('ffffffffffffffff', 'hex')), clientVersion: types.uInt32(15), osMajorVersion: types.uInt32(6), osMinorVersion: types.uInt32(2), displayName: types.sgString('Xbox-TV') }, channelStartRequest: { channelRequestId: types.uInt32(0), titleId: types.uInt32(0), service: types.bytes(16, ''), activityId: types.uInt32(0) }, channelStartResponse: { channelRequestId: types.uInt32(0), channelTargetId: types.uInt64(8), result: types.uInt32(0) }, acknowledge: { lowWatermark: types.uInt32(0), processedList: types.sgList('processedList', []), rejectedList: types.sgList('rejectedList', []) }, processedList: { id: types.uInt32(0) }, rejectedList: { id: types.uInt32(0) }, consoleStatus: { liveTvProvider: types.uInt32(0), majorVersion: types.uInt32(0), minorVersion: types.uInt32(0), buildNumber: types.uInt32(0), locale: types.sgString('en-US'), activeTitles: types.sgArray('activeTitle') }, activeTitle: { flags: types.bytes(2), titleId: types.uInt32(0), productId: types.bytes(16, ''), sandboxId: types.bytes(16, ''), aumId: types.sgString('') }, recordGameDvr: { startTimeDelta: types.sInt32(0), endTimeDelta: types.sInt32(0) }, gamepad: { timestamp: types.uInt64(8), buttons: types.uInt16(0), leftTrigger: types.uInt32(0), rightTrigger: types.uInt32(0), leftThumbstickX: types.uInt32(0), leftThumbstickY: types.uInt32(0), rightThumbstickX: types.uInt32(0), rightThumbstickY: types.uInt32(0) }, mediaState: { titleId: types.uInt32(0), aumId: types.sgString(), assetId: types.sgString(), mediaType: types.mapper(LocalApi.Media.Types, types.uInt16(0)), soundLevel: types.mapper(LocalApi.Media.SoundLevel, types.uInt16(0)), enabledCommands: types.uInt32(0), playbackStatus: types.mapper(LocalApi.Media.PlaybackState, types.uInt16(0)), rate: types.uInt32(0), position: types.uInt64(8), mediaStart: types.uInt64(8), mediaEnd: types.uInt64(8), minSeek: types.uInt64(8), maxSeek: types.uInt64(8), metadata: types.sgArray('mediaStateList', []) }, mediaStateList: { name: types.sgString(), value: types.sgString() }, mediaCommand: { requestId: types.uInt64(8), titleId: types.uInt32(0), command: types.uInt32(0) }, powerOff: { liveId: types.sgString('') }, disconnect: { reason: types.uInt32(1), errorCode: types.uInt32(0) } }; return packets; } } export default Packets;