├── .gitignore ├── test_frame ├── toblerone_1.jpg ├── toblerone_2.jpg ├── toblerone_3.jpg ├── toblerone_4.jpg ├── toblerone_5.jpg ├── test_toblerone_1.264 ├── test_toblerone_2.264 ├── test_toblerone_3.264 ├── test_toblerone_4.264 └── test_toblerone_5.264 ├── protos ├── Audio.capnp ├── RuntimeIPC.capnp ├── Logging.capnp ├── Slice.capnp ├── CameraStream.capnp ├── Pose.capnp └── HostInfo.capnp ├── utils.py ├── float_to_bin.py ├── pkt_parse.py ├── notes.txt ├── 264_extract_wireshark.py ├── xrsp.py ├── xrsp_constants.py ├── capnp_parse.py ├── README.md ├── camera_metadata_quest2.json ├── xrsp_host.py └── xrsp_parse.py /.gitignore: -------------------------------------------------------------------------------- 1 | *.bin 2 | *.DS_Store 3 | __pycache__/* -------------------------------------------------------------------------------- /test_frame/toblerone_1.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shinyquagsire23/xrsp_tests/HEAD/test_frame/toblerone_1.jpg -------------------------------------------------------------------------------- /test_frame/toblerone_2.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shinyquagsire23/xrsp_tests/HEAD/test_frame/toblerone_2.jpg -------------------------------------------------------------------------------- /test_frame/toblerone_3.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shinyquagsire23/xrsp_tests/HEAD/test_frame/toblerone_3.jpg -------------------------------------------------------------------------------- /test_frame/toblerone_4.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shinyquagsire23/xrsp_tests/HEAD/test_frame/toblerone_4.jpg -------------------------------------------------------------------------------- /test_frame/toblerone_5.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shinyquagsire23/xrsp_tests/HEAD/test_frame/toblerone_5.jpg -------------------------------------------------------------------------------- /test_frame/test_toblerone_1.264: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shinyquagsire23/xrsp_tests/HEAD/test_frame/test_toblerone_1.264 -------------------------------------------------------------------------------- /test_frame/test_toblerone_2.264: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shinyquagsire23/xrsp_tests/HEAD/test_frame/test_toblerone_2.264 -------------------------------------------------------------------------------- /test_frame/test_toblerone_3.264: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shinyquagsire23/xrsp_tests/HEAD/test_frame/test_toblerone_3.264 -------------------------------------------------------------------------------- /test_frame/test_toblerone_4.264: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shinyquagsire23/xrsp_tests/HEAD/test_frame/test_toblerone_4.264 -------------------------------------------------------------------------------- /test_frame/test_toblerone_5.264: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shinyquagsire23/xrsp_tests/HEAD/test_frame/test_toblerone_5.264 -------------------------------------------------------------------------------- /protos/Audio.capnp: -------------------------------------------------------------------------------- 1 | @0xb898e25c1934735d; 2 | 3 | struct PayloadAudio { 4 | dataUnk0 @0 :UInt64; 5 | data @1 :List(UInt8); 6 | } -------------------------------------------------------------------------------- /protos/RuntimeIPC.capnp: -------------------------------------------------------------------------------- 1 | @0xba804339ecadd443; 2 | 3 | struct PayloadRuntimeIPC { 4 | id @0 :UInt32; 5 | nextSize @1 :UInt32; 6 | unk1 @2 :UInt32; 7 | unk2 @3 :UInt32; 8 | data @4 :List(UInt8); 9 | } -------------------------------------------------------------------------------- /protos/Logging.capnp: -------------------------------------------------------------------------------- 1 | @0xecf5a225496ec29b; 2 | struct LogEntry { 3 | unk0 @0 :UInt64; 4 | timestampUs @1 :UInt64; 5 | data @2 :Text; 6 | } 7 | 8 | struct PayloadLogging { 9 | error @0 :List(LogEntry); 10 | warn @1 :List(LogEntry); 11 | debug @2 :List(LogEntry); 12 | info @3 :List(LogEntry); 13 | } -------------------------------------------------------------------------------- /utils.py: -------------------------------------------------------------------------------- 1 | def hex_dump(b, prefix=""): 2 | p = prefix 3 | b = bytes(b) 4 | for i in range(0, len(b)): 5 | if i != 0 and i % 16 == 0: 6 | print (p) 7 | p = prefix 8 | p += ("%02x " % b[i]) 9 | print (p) 10 | 11 | def hash_djb2(s): 12 | hash = 5381 13 | for x in s: 14 | hash = (( hash << 5) + hash) + ord(x) 15 | return hash & 0xFFFFFFFF -------------------------------------------------------------------------------- /float_to_bin.py: -------------------------------------------------------------------------------- 1 | import struct 2 | 3 | import sys 4 | 5 | if __name__ == '__main__': 6 | if len(sys.argv) < 2: 7 | print ("pkt_parse.py file.bin") 8 | sys.exit(-1) 9 | 10 | f = open(sys.argv[1], "rb") 11 | contents = f.read() 12 | f.close() 13 | 14 | f = open(sys.argv[1] + ".u8", "wb") 15 | 16 | for i in range(0, len(contents)//4): 17 | b = contents[i*4:(i+1)*4] 18 | val = struct.unpack(" 255): 21 | print (val_u8) 22 | f.write(bytes([val_u8 & 0xFF])) 23 | #print (val) 24 | f.close() -------------------------------------------------------------------------------- /protos/Slice.capnp: -------------------------------------------------------------------------------- 1 | @0xf885ae51503e46e3; 2 | 3 | struct PayloadQuat { 4 | x @0 :Float32; 5 | y @1 :Float32; 6 | z @2 :Float32; 7 | w @3 :Float32; 8 | } 9 | 10 | struct PayloadSlice { 11 | frameIdx @0 :UInt32; 12 | unk0p1 @1 :UInt32; 13 | unk1p0 @2 :UInt32; 14 | 15 | poseQuatX @3 :Float32; 16 | poseQuatY @4 :Float32; 17 | poseQuatZ @5 :Float32; 18 | poseQuatW @6 :Float32; 19 | poseX @7 :Float32; 20 | poseY @8 :Float32; 21 | poseZ @9 :Float32; 22 | 23 | timestamp05 @10 :UInt64; 24 | sliceNum @11 :UInt8; 25 | unk6p1 @12 :UInt8; 26 | unk6p2 @13 :UInt8; 27 | unk6p3 @14 :UInt8; 28 | blitYPos @15 :UInt32; 29 | unk7p0 @16 :UInt32; 30 | csdSize @17 :UInt32; 31 | videoSize @18 :UInt32; 32 | unk8p1 @19 :UInt32; 33 | 34 | timestamp09 @20 :UInt64; 35 | unkA @21 :UInt64; # some delta ns? 36 | timestamp0B @22 :UInt64; 37 | timestamp0C @23 :UInt64; 38 | timestamp0D @24 :UInt64; 39 | 40 | quat1 @25 :PayloadQuat; 41 | quat2 @26 :PayloadQuat; 42 | } -------------------------------------------------------------------------------- /pkt_parse.py: -------------------------------------------------------------------------------- 1 | import sys 2 | import struct 3 | 4 | from capnp_parse import CapnpParser 5 | from xrsp_parse import * 6 | from xrsp_constants import * 7 | from xrsp_host import * 8 | 9 | from utils import hex_dump 10 | 11 | if __name__ == '__main__': 12 | if len(sys.argv) < 2: 13 | print ("pkt_parse.py file.bin") 14 | sys.exit(-1) 15 | 16 | f = open(sys.argv[1], "rb") 17 | contents = f.read() 18 | f.close() 19 | 20 | xrsp_host = XrspHost() 21 | 22 | full_size = len(contents) 23 | remainder = contents 24 | buildup = b'' 25 | idx = 0 26 | while len(remainder) > 0: 27 | buildup = remainder[idx:idx+0x200] 28 | idx += 0x200 29 | 30 | pkt = TopicPkt(xrsp_host, buildup) 31 | while pkt.missing_bytes() > 0: 32 | #print ("MISSING", hex(pkt.missing_bytes())) 33 | _b = remainder[idx:idx+0x200] 34 | idx += 0x200 35 | pkt.add_missing_bytes(_b) 36 | pkt.dump() 37 | #print("Remains:") 38 | #hex_dump(pkt.remainder_bytes()) 39 | idx -= len(pkt.remainder_bytes()) 40 | print ("At:", hex(idx)) 41 | #hex_dump(remainder[:0x200]) -------------------------------------------------------------------------------- /protos/CameraStream.capnp: -------------------------------------------------------------------------------- 1 | @0x97ef77344d08d1b2; 2 | struct CameraMetadata { 3 | width @0 :UInt32; 4 | height @1 :UInt32; 5 | metaUnk1p0 @2 :UInt16; # small ir: 1, big ir: 1, color: 3, float: 4 6 | metaUnk1p1 @3 :UInt16; # small ir: 2, big ir: 2, color: 3, float: 4 7 | metaUnk1p2 @4 :UInt16; # small ir: 1, big ir: 1, color: 3, float: 1 8 | metaUnk1p3 @5 :UInt16; # small ir: 0, big ir: 0, color: 0, float: 0 9 | metaUnk2p0 @6 :UInt32; # small ir: 1, big ir: 1, color: 3, float: 4 10 | 11 | stride @7 :UInt32; # in bytes 12 | bufferSize @8 :UInt32; # in bytes 13 | id @9 :Int32; # small ir: 1/2, big ir: 0/1, color: 4, float: -1 14 | } 15 | 16 | struct CameraData { 17 | dataUnk0 @0 :UInt64; 18 | data @1 :List(UInt8); 19 | } 20 | 21 | struct CameraStruct2 { 22 | struct2Unk0p0 @0 :UInt32; 23 | struct2Unk0p1 @1 :UInt32; 24 | metas @2 :List(CameraMetadata); 25 | } 26 | 27 | struct CameraStruct1 { 28 | struct1Unk0 @0 :UInt64; # small ir: 2, float: 1 29 | timestampSecs @1 :Float64; 30 | struct1Unk2 @2 :UInt64; 31 | struct1Unk3 @3 :CameraStruct2; 32 | struct1Unk4 @4 :List(CameraData); 33 | } 34 | 35 | struct PayloadCameraStream { 36 | unk0 @0 :UInt64; 37 | unk1 @1 :CameraStruct1; 38 | } 39 | 40 | struct CameraMetaData { 41 | dataUnk0 @0 :UInt64; 42 | data @1 :List(UInt8); 43 | } 44 | 45 | struct PayloadCameraStreamMeta { 46 | unk0 @0 :UInt64; 47 | metadata @1 :CameraMetaData; 48 | } -------------------------------------------------------------------------------- /notes.txt: -------------------------------------------------------------------------------- 1 | - XrspSessionType: 2 | 0: None 3 | 1: basic 4 | 2: benchmark 5 | 3: streaming 6 | 4: xrsp_tester 7 | 8 | -XrspTransportType 9 | 0: none 10 | 1: file 11 | 2: winusb 12 | 3: libusb 13 | 4: tcpclient 14 | 5: tcpserver 15 | 6: udpclient 16 | 7: udpserver 17 | 8: tlsclient 18 | 9: tlsserver 19 | 10: replay 20 | 21 | -XrspParticipantTransmissionType 22 | 0: none 23 | 1: QUIC 24 | 2: TCP 25 | 3: UDP 26 | 4: USB2 27 | 5: USB3 28 | 6: file 29 | 7: Replay 30 | 8: CommLib 31 | 32 | -XrspResult 33 | 0x0: "success" 34 | 0x1: "invalid argument" 35 | 0x2: "invalid data" 36 | 0x3: "invalid state" 37 | 0x4: "buffer too small" 38 | 0x5: "out of memory" 39 | 0x6: "topic does not exist" 40 | 0x7: "topic already exists" 41 | 0x8: "topic is not writable" 42 | 0x9: "topic is not readable" 43 | 0xa: "no device" 44 | 0xb: "invalid transport description" 45 | 0xc: "transport closed" 46 | 0xd: "I/O error" 47 | 0xe: "timeout occurred" 48 | 0xf: "packet lost" 49 | 0x10: "incompatible packet version" 50 | 0x11: "forced termination" 51 | 0x12: "property does not exist" 52 | 0x13: "no session active" 53 | 0x14: "not implemented" 54 | 0x15: "unknown error" 55 | 0x16: "network host disconnected" 56 | 0x17: "ssl memory allocation failed" 57 | 0x18: "ssl set cipher list failed" 58 | 0x19: "ssl failed to use the provided cert" 59 | 0x1A: "ssl failed to use the privided private "... 60 | 0x1B: "cert and key provided failed validation" 61 | 0x1C: "ssl failed to set read/write fds" 62 | 0x1D: "ssl handshake failed" 63 | 0x1E: "peer failed to provide cert" 64 | 0x1F: "invalid pairing code" 65 | 0x20: "pairing refused" 66 | 0x21: "pairing timed out" 67 | 0x22: "pairing_invalid_cert" 68 | 69 | -XrspBuiltinMessageType 70 | 0x0: PAIRING_ACK 71 | 0x1: INVITE 72 | 0x2: OK 73 | 0x3: ACK 74 | 0x4: ERROR 75 | 0x5: BYE 76 | 0x6: ECHO 77 | 0x7: PAIRING 78 | 0x8: unknown 79 | 0x9: CODE_GENERATION 80 | 0xA: CODE_GENERATION_ACK 81 | 0xB: unknown 82 | 0xC: unknown 83 | 0xD: unknown 84 | 0xE: unknown 85 | 0xF: RESERVED 86 | -------------------------------------------------------------------------------- /protos/Pose.capnp: -------------------------------------------------------------------------------- 1 | @0x999fa2218acd6729; 2 | 3 | # Same as it ever was 4 | # (look at old OVR SDKs for a C struct) 5 | struct OvrPoseF { 6 | 7 | angVelX @0 :Float32; 8 | angVelY @1 :Float32; 9 | angVelZ @2 :Float32; 10 | linVelX @3 :Float32; 11 | linVelY @4 :Float32; 12 | linVelZ @5 :Float32; 13 | angAccX @6 :Float32; 14 | angAccY @7 :Float32; 15 | angAccZ @8 :Float32; 16 | linAccX @9 :Float32; 17 | linAccY @10 :Float32; 18 | linAccZ @11 :Float32; 19 | 20 | quatX @12 :Float32; 21 | quatY @13 :Float32; 22 | quatZ @14 :Float32; 23 | quatW @15 :Float32; 24 | posX @16 :Float32; 25 | posY @17 :Float32; 26 | posZ @18 :Float32; 27 | 28 | pad0 @19 :UInt32; 29 | timestamp @20 :UInt64; # not a double I don't think... 30 | } 31 | 32 | struct PoseStruct1 { 33 | unk0 @0 :UInt64; 34 | } 35 | 36 | struct PoseTrackedController { 37 | unk0p0 @0 :UInt32; 38 | buttons @1 :UInt32; 39 | capacitance @2 :UInt32; 40 | triggerZ @3 :Float32; 41 | gripZ @4 :Float32; 42 | stickX @5 :Float32; 43 | stickY @6 :Float32; 44 | touchpadX @7 :Float32; 45 | touchpadY @8 :Float32; 46 | touchpadPressure @9 :Float32; 47 | stylusPressure @10 :Float32; 48 | triggerCovered @11 :Float32; 49 | triggerFingerCurl @12 :Float32; 50 | unk6p1 @13 :Float32; 51 | struct0 @14 :OvrPoseF; 52 | struct1 @15 :PoseStruct1; 53 | } 54 | 55 | struct PoseStruct4 { 56 | unk0 @0 :UInt64; 57 | } 58 | 59 | struct PayloadPose { 60 | unk0p0 @0 :UInt32; 61 | unk0p1 @1 :Float32; 62 | unk1p0 @2 :Float32; 63 | unk1p1 @3 :Float32; 64 | unk2p0 @4 :Float32; 65 | unk2p1 @5 :Float32; 66 | unk3p0 @6 :Float32; 67 | unk3p1 @7 :Float32; 68 | unk4p0 @8 :Float32; 69 | unk4p1 @9 :Float32; 70 | unk5p0 @10 :Float32; 71 | unk5p1 @11 :Float32; 72 | unk6p0 @12 :Float32; 73 | unk6p1 @13 :Float32; 74 | unk7p0 @14 :Float32; 75 | unk7p1 @15 :Float32; 76 | unk8p0 @16 :Float32; 77 | unk8p1 @17 :Float32; 78 | unk9p0 @18 :Float32; 79 | unk9p1 @19 :Float32; 80 | unkAp0 @20 :Float32; 81 | unkAp1 @21 :Float32; 82 | unkBp0 @22 :Float32; 83 | unkBp1 @23 :Float32; 84 | unkCp0 @24 :Float32; 85 | unkCp1 @25 :Float32; 86 | unkDp0 @26 :Float32; 87 | unkDp1 @27 :Float32; 88 | unkEp0 @28 :Float32; 89 | unkEp1 @29 :Float32; 90 | timestamp @30 :UInt64; 91 | controllers @31 :List(PoseTrackedController); 92 | headset @32 :OvrPoseF; 93 | struct3 @33 :PoseStruct4; 94 | } -------------------------------------------------------------------------------- /protos/HostInfo.capnp: -------------------------------------------------------------------------------- 1 | @0xc38e084d4e4d5624; 2 | 3 | struct HeadsetLens 4 | { 5 | info8Unk1p0 @0 :Float32; 6 | info8Unk1p1 @1 :Float32; 7 | info8Unk2p0 @2 :Float32; 8 | info8Unk2p1 @3 :Float32; 9 | 10 | info8Unk3p0 @4 :Float32; 11 | info8Unk3p1 @5 :Float32; 12 | info8Unk4p0 @6 :Float32; 13 | info8Unk4p1 @7 :Float32; 14 | 15 | info8Unk5p0 @8 :Float32; 16 | info8Unk5p1 @9 :Float32; 17 | info8Unk6p0 @10 :Float32; 18 | info8Unk6p1 @11 :Float32; 19 | } 20 | 21 | struct HeadsetInfo7 22 | { 23 | info7Unk1p0 @0 :UInt32; 24 | info7Unk1p1 @1 :UInt32; 25 | } 26 | 27 | struct HeadsetInfo6 28 | { 29 | info6Unk1p0 @0 :UInt32; 30 | info6Unk1p1 @1 :UInt32; 31 | } 32 | 33 | struct HeadsetInfo5 34 | { 35 | info5Unk1p0 @0 :Float32; 36 | info5Unk1p1 @1 :Float32; 37 | 38 | info5Unk2p0 @2 :Float32; 39 | info5Unk2p1 @3 :Float32; 40 | } 41 | 42 | struct HeadsetTimings 43 | { 44 | frameRate @0 :UInt32; 45 | unused @1 :UInt32; 46 | 47 | timingsUnk2 @2 :HeadsetInfo5; 48 | timingsUnk3 @3 :HeadsetInfo6; 49 | } 50 | 51 | struct RectilinearDistortionParameters 52 | { 53 | distortUnk1p0 @0 :Float32; 54 | distortUnk1p1 @1 :Float32; 55 | 56 | distortUnk2p0 @2 :Float32; 57 | distortUnk2p1 @3 :Float32; 58 | 59 | distortUnk3p0 @4 :Float32; 60 | distortUnk3p1 @5 :Float32; 61 | } 62 | 63 | struct AxisAlignedDistortionParameters 64 | { 65 | distortUnk1p0 @0 :UInt32; 66 | distortUnk1p1 @1 :UInt32; 67 | 68 | distortUnk2p0 @2 :UInt32; 69 | distortUnk2p1 @3 :UInt32; 70 | 71 | distortUnk3p0 @4 :UInt32; 72 | distortUnk3p1 @5 :UInt32; 73 | } 74 | 75 | struct HeadsetDescription 76 | { 77 | deviceType @0 :UInt32; # 1 == Quest 1, 2 == Quest 2, 3 == Quest 3 78 | 79 | resolutionWidth @1 :UInt32; 80 | resolutionHeight @2 :UInt32; 81 | refreshRateHz @3 :Float32; 82 | 83 | info2Unk3p0 @4 :Float32; 84 | info2Unk3p1 @5 :Float32; 85 | 86 | info2Unk4p0 @6 :Float32; 87 | info2Unk4p1 @7 :Float32; 88 | 89 | info2Unk5p0 @8 :Float32; 90 | info2Unk5p1 @9 :Float32; 91 | 92 | renderWidth @10 :UInt32; 93 | renderHeight @11 :UInt32; 94 | 95 | info2Unk7p0 @12 :UInt32; 96 | info2Unk7p1 @13 :UInt32; 97 | 98 | name @14 :Text; 99 | manufacturer @15 :Text; 100 | info2Unk10 @16 :HeadsetInfo7; 101 | leftLens @17 :HeadsetLens; 102 | rightLens @18 :HeadsetLens; 103 | timings @19 :List(HeadsetTimings); 104 | } 105 | 106 | struct HeadsetConfig 107 | { 108 | description @0 :HeadsetDescription; 109 | kNative @1 :List(Float32); 110 | kAgressive @2 :List(Float32); 111 | rectilinearDistortionParameters @3 :RectilinearDistortionParameters; 112 | balancedAxisAlignedDistortionParameters @4 :AxisAlignedDistortionParameters; 113 | qualityAxisAlignedDistortionParameters @5 :AxisAlignedDistortionParameters; 114 | performanceAxisAlignedDistortionParameters @6 :AxisAlignedDistortionParameters; 115 | } 116 | 117 | struct PayloadHostInfo { 118 | payloadUnk1p0 @0 :UInt32; 119 | payloadUnk1p1 @1 :UInt32; 120 | 121 | payloadUnk2p0 @2 :UInt32; 122 | payloadUnk2p1 @3 :UInt32; 123 | 124 | payloadUnk3p0 @4 :UInt32; 125 | payloadUnk3p1 @5 :UInt32; 126 | 127 | payloadUnk4p0 @6 :UInt32; 128 | payloadUnk4p1 @7 :UInt32; 129 | 130 | payloadUnk5p0 @8 :UInt32; 131 | payloadUnk5p1 @9 :UInt32; 132 | 133 | serial @10 :Text; 134 | someGUID @11 :Text; 135 | unk6 @12 :Text; 136 | config @13 :HeadsetConfig; 137 | 138 | unk7 @14 :List(UInt32); 139 | softwareVersion @15 :Text; 140 | unk9 @16 :List(UInt16); 141 | unk10 @17 :List(UInt8); 142 | } -------------------------------------------------------------------------------- /264_extract_wireshark.py: -------------------------------------------------------------------------------- 1 | import sys 2 | import struct 3 | 4 | from capnp_parse import CapnpParser 5 | from xrsp_parse import * 6 | 7 | from utils import hex_dump 8 | 9 | # Didn't work 10 | #(((usb.data_len > 0) && (usb.endpoint_address != 0x81) && (usb.endpoint_address != 0x01))) && (usb.endpoint_address != 0x00) && (usb.endpoint_address != 0x80) && (usb.capdata[0x1] == 0x0A) && (usb.capdata[0xB] == 0x01) 11 | 12 | # All the video slices 13 | #(((usb.data_len > 0) && (usb.endpoint_address != 0x81) && (usb.endpoint_address != 0x01))) && (usb.endpoint_address != 0x00) && (usb.endpoint_address != 0x80) && (usb.capdata[0x1] >= 0x0A && usb.capdata[0x1] < 0x1A) 14 | 15 | #(usb.capdata[0x1] != 0x0A) 16 | 17 | #ffmpeg -framerate 24 -f h264 -i 264_test2.bin -analyzeduration 100M -probesize 100M -vcodec copy slice_idk.mp4 18 | 19 | if __name__ == '__main__': 20 | if len(sys.argv) < 2: 21 | print ("264_extract_wireshark.py file.bin") 22 | sys.exit(-1) 23 | 24 | f = open(sys.argv[1], "rb") 25 | contents = f.read() 26 | f.close() 27 | 28 | xrsp_host = XrspHost() 29 | 30 | contig = b'' 31 | idx = 0x18 32 | 33 | out_idx = 0 34 | 35 | try: 36 | while True: 37 | idx += 0x8 38 | if idx >= len(contents): 39 | break 40 | pkt_size_file = struct.unpack("= TOPIC_SLICE_0 and b[1] <= TOPIC_SLICE_15 and contents[idx-0x1b+0x15] == 0x02: 59 | f = open("video_baked_" + str(out_idx) + ".bin", "wb") 60 | f.write(b) 61 | f.close() 62 | out_idx += 1 63 | 64 | contig += b 65 | 66 | idx += pkt_size_file 67 | if idx >= len(contents): 68 | break 69 | except: 70 | pass 71 | 72 | f = open("264_test.bin", "wb") 73 | f.write(contig) 74 | f.close() 75 | 76 | contents = contig 77 | contig = b'' 78 | 79 | full_size = len(contents) 80 | remainder = contents 81 | buildup = b'' 82 | idx = 0 83 | skip = 0 84 | while len(remainder) > 0: 85 | buildup = remainder[idx:idx+0x200] 86 | idx += 0x200 87 | 88 | pkt = TopicPkt(xrsp_host, buildup) 89 | while pkt.missing_bytes() > 0: 90 | if idx >= len(contents): 91 | break 92 | #print ("MISSING", hex(pkt.missing_bytes())) 93 | _b = remainder[idx:idx+0x200] 94 | idx += 0x200 95 | pkt.add_missing_bytes(_b) 96 | if pkt.topic_idx >= TOPIC_SLICE_0 and pkt.topic_idx <= TOPIC_SLICE_15: 97 | if pkt.num_words == 3: 98 | skip = 2 99 | if skip == 0 or pkt.num_words > 0x2b: 100 | contig += pkt.payload 101 | skip = 0 102 | else: 103 | skip -= 1 104 | last_numwords = pkt.num_words 105 | #if pkt.payload[3] == 0x1: 106 | 107 | #print("Remains:") 108 | #hex_dump(pkt.remainder_bytes()) 109 | idx -= len(pkt.remainder_bytes()) 110 | print ("At:", hex(idx)) 111 | #hex_dump(remainder[:0x200]) 112 | if idx >= len(contents): 113 | break 114 | 115 | f = open("264_test2.bin", "wb") 116 | f.write(contig) 117 | f.close() -------------------------------------------------------------------------------- /xrsp.py: -------------------------------------------------------------------------------- 1 | 2 | import struct 3 | import time 4 | 5 | from capnp_parse import CapnpParser 6 | from xrsp_parse import * 7 | from xrsp_host import * 8 | from xrsp_constants import * 9 | from utils import hex_dump 10 | 11 | # XRSP host context 12 | xrsp_host = XrspHost() 13 | xrsp_host.init_usb() 14 | 15 | xrsp_host.wait_pairing() 16 | 17 | ''' 18 | h264_dat = open("h264_dat.bin", "rb").read() 19 | for i in range(TOPIC_SLICE_0, TOPIC_SLICE_5): 20 | xrsp_host.read_xrsp() 21 | try: 22 | h264_dat = h264_dat[0:1] + bytes([i]) + h264_dat[2:] 23 | 24 | xrsp_host.ep_out.write(h264_dat) 25 | except usb.core.USBTimeoutError as e: 26 | print ("Failed to send to topic", hex(i), e) 27 | xrsp_host.read_xrsp() 28 | ''' 29 | 30 | # Replay IPC packets 31 | ''' 32 | for i in range(0, 18): 33 | f = open("ipc_baked/ipc_baked_" + str(i) + ".bin", "rb") 34 | dat = f.read() 35 | f.close() 36 | xrsp_host.read_xrsp() 37 | try: 38 | xrsp_host.ep_out.write(dat) 39 | except usb.core.USBTimeoutError as e: 40 | print ("Failed to send baked IPC", e) 41 | xrsp_host.read_xrsp() 42 | ''' 43 | 44 | start_ns = xrsp_host.ts_ns() 45 | while True: 46 | if xrsp_host.ts_ns() - start_ns > 5000000000: # 5s 47 | break 48 | xrsp_host.read_xrsp() 49 | 50 | #ffmpeg -f image2 -r 72 -i ~/Pictures/toblerone_crop.jpg -c:v libx264 -f rawvideo -pix_fmt yuvj420p -b:v 70000k -x264opts keyint=1 test_toblerone.264 51 | #ffmpeg -f image2 -r 72 -i ~/Pictures/toblerone_crop.jpg -c:v libx264 -f rawvideo -pix_fmt yuvj420p -b:v 70000k -x264opts keyint=1 -profile:v baseline test_toblerone.264 52 | #ffmpeg -f image2 -r 72 -i ~/Pictures/toblerone.jpg -c:v libx264 -f rawvideo -pix_fmt yuvj420p -b:v 70000k -x264opts keyint=1 -profile:v baseline test_toblerone.264 53 | 54 | #ffmpeg -f image2 -r 72 -i test_frame/toblerone_1.jpg -c:v libx264 -f rawvideo -pix_fmt yuvj420p -b:v 70000k -x264opts keyint=1 -profile:v baseline test_toblerone_1.264 55 | #ffmpeg -f image2 -r 72 -i test_frame/toblerone_2.jpg -c:v libx264 -f rawvideo -pix_fmt yuvj420p -b:v 70000k -x264opts keyint=1 -profile:v baseline test_toblerone_2.264 56 | #ffmpeg -f image2 -r 72 -i test_frame/toblerone_3.jpg -c:v libx264 -f rawvideo -pix_fmt yuvj420p -b:v 70000k -x264opts keyint=1 -profile:v baseline test_toblerone_3.264 57 | #ffmpeg -f image2 -r 72 -i test_frame/toblerone_4.jpg -c:v libx264 -f rawvideo -pix_fmt yuvj420p -b:v 70000k -x264opts keyint=1 -profile:v baseline test_toblerone_4.264 58 | #ffmpeg -f image2 -r 72 -i test_frame/toblerone_5.jpg -c:v libx264 -f rawvideo -pix_fmt yuvj420p -b:v 70000k -x264opts keyint=1 -profile:v baseline test_toblerone_5.264 59 | 60 | # Replay H.264 data 61 | print ("Begin streaming") 62 | frameIdx = 0 63 | while True: 64 | 65 | ''' 66 | for i in range(0, 5): #2070 7550 67 | xrsp_host.read_xrsp() 68 | 69 | f = open("video_extract/video_" + str(i) + "_0.bin", "rb") 70 | dat0 = f.read() 71 | f.close() 72 | 73 | f = open("video_extract/video_" + str(i) + "_1.bin", "rb") 74 | dat1 = f.read() 75 | f.close() 76 | 77 | if (i % xrsp_host.num_slices) == 0: 78 | frameIdx += 1 79 | 80 | #xrsp_host.read_xrsp() 81 | try: 82 | xrsp_host.send_video(i % xrsp_host.num_slices, frameIdx, dat0, dat1, (i % 5)*(1920 // xrsp_host.num_slices)) 83 | except usb.core.USBTimeoutError as e: 84 | print ("Failed to send baked video", e) 85 | print (str(i)) 86 | #xrsp_host.send_to_topic(1, request_echo) 87 | ''' 88 | 89 | 90 | xrsp_host.read_xrsp() 91 | 92 | for i in range(0, xrsp_host.num_slices): 93 | #f = open("test_toblerone.264.0", "rb") 94 | f = open("test_frame/test_toblerone_" + str(i+1) + ".264", "rb") 95 | dat0 = f.read()[:0x27] 96 | f.close() 97 | #dat0 = b'' 98 | 99 | #f = open("test_toblerone.264.1", "rb") 100 | f = open("test_frame/test_toblerone_" + str(i+1) + ".264", "rb") 101 | dat1 = f.read()[0x29A:] 102 | f.close() 103 | 104 | #xrsp_host.read_xrsp() 105 | try: 106 | xrsp_host.send_video(i, frameIdx, dat0, dat1, (i % 5)*(1920 // xrsp_host.num_slices)) 107 | except usb.core.USBTimeoutError as e: 108 | print ("Failed to send baked video", e) 109 | 110 | frameIdx += 1 111 | print (str(frameIdx)) 112 | #xrsp_host.send_to_topic(1, request_echo) 113 | 114 | print ("last reads") 115 | while True: 116 | ret = xrsp_host.read_xrsp() 117 | -------------------------------------------------------------------------------- /xrsp_constants.py: -------------------------------------------------------------------------------- 1 | # Topics 2 | TOPIC_AUI4A_ADV = 0x0 3 | TOPIC_HOSTINFO_ADV = 0x1 4 | TOPIC_COMMAND = 0x2 5 | TOPIC_POSE = 0x3 6 | TOPIC_MESH = 0x4 7 | TOPIC_VIDEO = 0x5 8 | TOPIC_AUDIO = 0x6 9 | TOPIC_HAPTIC = 0x7 10 | TOPIC_HANDS = 0x8 11 | TOPIC_SKELETON = 0x9 12 | TOPIC_SLICE_0 = 0xA 13 | TOPIC_SLICE_1 = 0xB 14 | TOPIC_SLICE_2 = 0xC 15 | TOPIC_SLICE_3 = 0xD 16 | TOPIC_SLICE_4 = 0xE 17 | TOPIC_SLICE_5 = 0xF 18 | TOPIC_SLICE_6 = 0x10 19 | TOPIC_SLICE_7 = 0x11 20 | TOPIC_SLICE_8 = 0x12 21 | TOPIC_SLICE_9 = 0x13 22 | TOPIC_SLICE_10 = 0x14 23 | TOPIC_SLICE_11 = 0x15 24 | TOPIC_SLICE_12 = 0x16 25 | TOPIC_SLICE_13 = 0x17 26 | TOPIC_SLICE_14 = 0x18 27 | TOPIC_SLICE_15 = 0x19 28 | TOPIC_AUDIO_CONTROL = 0x1A 29 | TOPIC_USER_SETTINGS_SYNC = 0x1B 30 | TOPIC_INPUT_CONTROL = 0x1C 31 | TOPIC_ASW = 0x1D 32 | TOPIC_BODY = 0x1E 33 | TOPIC_RUNTIME_IPC = 0x1F 34 | TOPIC_CAMERA_STREAM = 0x20 35 | TOPIC_LOGGING = 0x21 36 | TOPIC_22 = 0x22 37 | TOPIC_23 = 0x23 38 | 39 | # TOPIC_HOSTINFO_ADV 40 | BUILTIN_PAIRING_ACK = 0x0 41 | BUILTIN_INVITE = 0x1 42 | BUILTIN_OK = 0x2 43 | BUILTIN_ACK = 0x3 44 | BUILTIN_ERROR = 0x4 45 | BUILTIN_BYE = 0x5 46 | BUILTIN_ECHO = 0x6 47 | BUILTIN_PAIRING = 0x7 48 | BUILTIN_CODE_GENERATION = 0x9 49 | BUILTIN_CODE_GENERATION_ACK = 0xA 50 | BUILTIN_RESERVED = 0xF 51 | 52 | # TOPIC_COMMAND 53 | COMMAND_TERMINATE = 0x00 54 | COMMAND_1 = 0x01 55 | COMMAND_2 = 0x02 56 | COMMAND_3 = 0x03 57 | COMMAND_4 = 0x04 58 | COMMAND_5 = 0x05 59 | COMMAND_6 = 0x06 60 | COMMAND_7 = 0x07 61 | COMMAND_8 = 0x08 62 | COMMAND_9 = 0x09 63 | COMMAND_A = 0x0A 64 | COMMAND_RESET_GUARDIAN = 0x0B 65 | COMMAND_TOGGLE_CHEMX = 0x0C 66 | COMMAND_ENABLE_CAMERA_STREAM = 0x0D 67 | COMMAND_DISABLE_CAMERA_STREAM = 0x0E 68 | COMMAND_TOGGLE_ASW = 0x0F 69 | COMMAND_10 = 0x10 70 | COMMAND_DROP_FRAMES_STATE = 0x11 71 | 72 | # TOPIC_RUNTIME_IPC 73 | RIPC_MSG_CONNECT_TO_REMOTE_SERVER = 0x1 74 | RIPC_MSG_RPC = 0x2 75 | RIPC_MSG_ENSURE_SERVICE_STARTED = 0x4 76 | 77 | # BUILTIN_ECHO 78 | ECHO_PING = 0 79 | ECHO_PONG = 1 80 | 81 | # Internal 82 | STATE_SEGMENT_META = 0 83 | STATE_SEGMENT_READ = 1 84 | STATE_EXT_READ = 2 85 | 86 | POSEDATA_0 = 0x0 87 | 88 | XrspResultLut = [ 89 | "success", 90 | "invalid argument", 91 | "invalid data", 92 | "invalid state", 93 | "buffer too small", 94 | "out of memory", 95 | "topic does not exist", 96 | "topic already exists", 97 | "topic is not writable", 98 | "topic is not readable", 99 | "no device", 100 | "invalid transport description", 101 | "transport closed", 102 | "I/O error", 103 | "timeout occurred", 104 | "packet lost", 105 | "incompatible packet version", 106 | "forced termination", 107 | "property does not exist", 108 | "no session active", 109 | "not implemented", 110 | "unknown error", 111 | "network host disconnected", 112 | "ssl memory allocation failed", 113 | "ssl set cipher list failed", 114 | "ssl failed to use the provided cert", 115 | "ssl failed to use the privided private ", 116 | "cert and key provided failed validation", 117 | "ssl failed to set read/write fds", 118 | "ssl handshake failed", 119 | "peer failed to provide cert", 120 | "invalid pairing code", 121 | "pairing refused", 122 | "pairing timed out", 123 | "pairing_invalid_cert" 124 | ] 125 | 126 | XrspTopic = [ 127 | "aui4a-adv", 128 | "hostinfo-adv", 129 | "Command", 130 | "Pose", 131 | "Mesh", 132 | "Video", 133 | "Audio", 134 | "Haptic", 135 | "Hands", 136 | "Skeleton", 137 | "Slice 0", 138 | "Slice 1", 139 | "Slice 2", 140 | "Slice 3", 141 | "Slice 4", 142 | "Slice 5", 143 | "Slice 6", 144 | "Slice 7", 145 | "Slice 8", 146 | "Slice 9", 147 | "Slice 10", 148 | "Slice 11", 149 | "Slice 12", 150 | "Slice 13", 151 | "Slice 14", 152 | "Slice 15", 153 | "AudioControl", 154 | "UserSettingsSync", 155 | "InputControl", 156 | "Asw", 157 | "Body", 158 | "RuntimeIPC", 159 | "CameraStream", 160 | "Logging", 161 | ] 162 | 163 | def XrspBuiltinMessageType(idx): 164 | if idx == BUILTIN_PAIRING_ACK: 165 | return "PAIRING_ACK" 166 | elif idx == BUILTIN_INVITE: 167 | return "INVITE" 168 | elif idx == BUILTIN_OK: 169 | return "OK" 170 | elif idx == BUILTIN_ACK: 171 | return "ACK" 172 | elif idx == BUILTIN_ERROR: 173 | return "ERROR" 174 | elif idx == BUILTIN_BYE: 175 | return "BYE" 176 | elif idx == BUILTIN_ECHO: 177 | return "ECHO" 178 | elif idx == BUILTIN_PAIRING: 179 | return "PAIRING" 180 | elif idx == BUILTIN_CODE_GENERATION: 181 | return "CODE_GENERATION" 182 | elif idx == BUILTIN_CODE_GENERATION_ACK: 183 | return "CODE_GENERATION_ACK" 184 | elif idx == BUILTIN_RESERVED: 185 | return "RESERVED" 186 | else: 187 | return "unknown" -------------------------------------------------------------------------------- /capnp_parse.py: -------------------------------------------------------------------------------- 1 | import sys 2 | import struct 3 | 4 | from utils import hex_dump 5 | 6 | class CapnpParser: 7 | 8 | def __init__(self, contents): 9 | self.contents = contents 10 | 11 | def get_bytes(self, idx, len): 12 | b = self.contents[idx*8:(idx*8)+len] 13 | return b 14 | 15 | def get_word(self, idx): 16 | b = self.contents[idx*8:(idx+1)*8] 17 | return struct.unpack("> 2) & 0x3FFFFFFF 31 | dataWords = (word >> 32) & 0xFFFF 32 | dataPtrs = (word >> 48) & 0xFFFF 33 | 34 | print (prefix + "struct", "offs="+hex(dataOffs), "words="+hex(dataWords), "ptrs="+hex(dataPtrs)) 35 | 36 | print (prefix + " data:") 37 | for i in range(0, dataWords): 38 | print(prefix + " " + "data " + hex(i) + ": " + hex(self.get_word(idx + 1 + dataOffs + i))) 39 | 40 | print (prefix + " ptrs:") 41 | for i in range(0, dataPtrs): 42 | self.parse_entry(idx + 1 + dataOffs + dataWords + i, prefix + " ") 43 | 44 | ret += dataWords + dataPtrs 45 | elif a == 1: # list 46 | dataOffs = (word >> 2) & 0x3FFFFFFF 47 | dataSizeEnc = (word >> 32) & 0x7 48 | numElements = (word >> 35) 49 | 50 | sizeLut = [0, 0, 1, 2, 4, 8, 8, 0] 51 | 52 | dataSize = sizeLut[dataSizeEnc] 53 | totalSize = dataSize * numElements 54 | 55 | if dataSizeEnc == 1: 56 | totalSize = numElements // 8 57 | if totalSize <= 8: 58 | totalSize = 1 59 | 60 | print (prefix + "list dataOffs=" + hex(dataOffs) + ", dataSizeEnc=" + hex(dataSizeEnc) + ", numElements=" + hex(numElements) + ", dataSize=" + hex(dataSize) + ", totalSize=" + hex(totalSize)) 61 | 62 | if dataSizeEnc == 7: 63 | tmp = idx + 1 + dataOffs 64 | tag = self.get_word(tmp) 65 | tmp += 1 66 | ret += 1 67 | 68 | numWords = numElements 69 | numElements = (tag >> 2) & 0x3FFFFFFF 70 | dataWords = (tag >> 32) & 0xFFFF 71 | dataPtrs = (tag >> 48) & 0xFFFF 72 | print (prefix + " composite (" + hex(tag) + "), numElements=" + hex(numElements) + ", words=" + hex(dataWords) + ", ptrs=" + hex(dataPtrs) + ":") 73 | 74 | for i in range(0, numElements): 75 | print (prefix + " entry " + hex(i) + ":") 76 | print (prefix + " data:") 77 | for i in range(0, dataWords): 78 | print(prefix + " " + "data " + hex(i) + ": " + hex(self.get_word(tmp))) 79 | tmp += 1 80 | ret += 1 81 | 82 | print (prefix + " ptrs:") 83 | for i in range(0, dataPtrs): 84 | self.parse_entry(tmp, prefix + " ") 85 | tmp += 1 86 | ret += 1 87 | 88 | else: 89 | dataBytes = self.get_bytes(idx + 1 + dataOffs, totalSize) 90 | print (prefix + " contents:", dataBytes) 91 | hex_dump(dataBytes, prefix + " ") 92 | print ("") 93 | 94 | ret += (totalSize // 8) 95 | 96 | elif a == 2: # inter-segment ptr 97 | landingPadSize = (word >> 2) & 1 98 | dataOffs = (word >> 3) & 0x1FFFFFFF 99 | segmentId = (word >> 32) 100 | print (prefix + "inter-seg: landingPadSize=" + hex(landingPadSize) + ", dataOffs=" + hex(dataOffs) + ", segmentId=" + hex(segmentId)) 101 | 102 | elif a == 3: # capabilities 103 | print (prefix + "capability") 104 | return ret 105 | 106 | if __name__ == '__main__': 107 | if len(sys.argv) < 2: 108 | print ("capnp_parse.py file.bin") 109 | sys.exit(-1) 110 | 111 | f = open(sys.argv[1], "rb") 112 | contents = f.read() 113 | f.close() 114 | 115 | parser = CapnpParser(contents) 116 | 117 | entries = [] 118 | for i in range(0, len(contents) // 8): 119 | word = parser.get_word(i) 120 | if word == 0x0006000500000000: 121 | entries += [i] 122 | 123 | if len(entries) == 0: 124 | entries = [0] 125 | 126 | for e in entries: 127 | parser.parse_entry(e) 128 | 129 | ''' 130 | wordIdx = 0 131 | while True: 132 | if wordIdx >= len(contents) // 8: 133 | break 134 | 135 | wordIdx += parse_entry(wordIdx) 136 | ''' 137 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # xrsp_tests 2 | Attempting to talk to Meta Quest's USB/XRSP interface 3 | 4 | See also: https://github.com/OpenOculus/XrspDocs 5 | 6 | ## Repo Organization 7 | 8 | **Mains:** 9 | - `xrsp.py`: Main script, talks USB and displays an image. 10 | - `pkt_parse.py`: Replay packets dumped from `xrsp.py`. 11 | - `capnp_parse.py`: Display raw capnproto data. 12 | - `264_extract_wireshark.py`: Wireshark pcap parsing and misc dumping. 13 | - `float_to_bin.py`: Look at the weird float camera images. 14 | 15 | **Helpers**: 16 | - `xrsp_constants.py`: Constants related to the XRSP protocol. 17 | - `xrsp_host.py`: XrspHost, the USB device and state. Manages initialization, packet sending, and responses. 18 | - `xrsp_parse.py`: Objects/parsing for all topics. 19 | - `utils.py`: Helper functions. 20 | 21 | **Data:** 22 | - `video_extract/`: Displayed H.264 frame. 23 | - `notes.txt`: Misc docs. 24 | - `camera_metadata_questpro.json`: JSON sent by CameraStream topic for Quest Pro. 25 | - `camera_metadata_quest2.json`: JSON sent by CameraStream topic for Quest 2. 26 | 27 | # Data Structures 28 | ## XrspPacketHeader 29 | | Position | Size | Name | 30 | |----------|------|------------------------------| 31 | | 0x00 | u16 | Version, Topic (See below) | 32 | | 0x02 | u16 | Number of u32 words | 33 | | 0x04 | u16 | Sequence number | 34 | | 0x06 | u16 | Padding | 35 | 36 | ## Version/Topic u16 37 | | Bits | Size | Name | 38 | |-------|------|------------------------------| 39 | | 0-2 | 3 | Version? | 40 | | 3 | 1 | Packet has alignment padding | 41 | | 4 | 1 | xrspPacketVersionIsInternal | 42 | | 5-7 | 3 | xrspPacketVersionNumber | 43 | | 8-13 | 6 | Topic | 44 | | 14-15 | 2 | Unk | 45 | 46 | ## XrspBuiltinHeader 47 | 48 | Header for hostinfo-adv 49 | 50 | | Position | Size | Name | 51 | |----------|------|-------------------------------| 52 | | 0x00 | u32 | Header_0 | 53 | | 0x04 | u32 | Data stream version | 54 | | 0x08 | u32 | Unk | 55 | | 0x0C | u32 | Payload length (in u64 words) | 56 | | 0x10 | ... | Payload | 57 | 58 | ## Header_0 59 | | Bits | Size | Name | 60 | |-------|------|-----------------------------------------| 61 | | 0-3 | 4 | XrspBuiltinMessageType | 62 | | 4-13 | 10 | XrspResult(?) xrspTransactionStatusCode | 63 | | 14-31 | 18 | Stream size in words, including header | 64 | 65 | ## HostInfo Packet 66 | The initial hostinfo sent is a capnp message containing the device's info, including the serial, name/device type, lens intrinsics, etc. 67 | 68 | ## Topics 69 | Topics are are 6-bits in length (mask 0x3f). Topics over ID 2 are always encrypted; it is currently unknown as to how they are encrypted. Each topic is routed to and parsed in a separate handler, usually in its own thread. 70 | 71 | | ID | Name | 72 | |------|--------------------| 73 | | 0x00 | "aui4a-adv" | 74 | | 0x01 | "hostinfo-adv" | 75 | | 0x02 | "Command" | 76 | | 0x03 | "Pose" | 77 | | 0x04 | "Mesh" | 78 | | 0x05 | "Video" | 79 | | 0x06 | "Audio" | 80 | | 0x07 | "Haptic" | 81 | | 0x08 | "Hands" | 82 | | 0x09 | "Skeleton" | 83 | | 0x0A | "Slice 0" | 84 | | 0x0B | "Slice 1" | 85 | | 0x0C | "Slice 2" | 86 | | 0x0D | "Slice 3" | 87 | | 0x0E | "Slice 4" | 88 | | 0x0F | "Slice 5" | 89 | | 0x10 | "Slice 6" | 90 | | 0x11 | "Slice 7" | 91 | | 0x12 | "Slice 8" | 92 | | 0x13 | "Slice 9" | 93 | | 0x14 | "Slice 10" | 94 | | 0x15 | "Slice 11" | 95 | | 0x16 | "Slice 12" | 96 | | 0x17 | "Slice 13" | 97 | | 0x18 | "Slice 14" | 98 | | 0x19 | "Slice 15" | 99 | | 0x1A | "AudioControl" | 100 | | 0x1B | "UserSettingsSync" | 101 | | 0x1C | "InputControl" | 102 | | 0x1D | "Asw" | 103 | | 0x1E | "Body" | 104 | | 0x1F | "RuntimeIPC" | 105 | | 0x20 | "CameraStream" | 106 | | 0x21 | "Logging" | 107 | 108 | ### Command pkts ids 109 | 110 | Search for `Command result err %d` 111 | 112 | | Num | Function | 113 | |------|----------------------------------------| 114 | | 0x00 | Termination | 115 | | 0x01 | ? | 116 | | 0x02 | ? | 117 | | 0x03 | Calls some funcs in a while loop? | 118 | | 0x04 | ? | 119 | | 0x05 | Sets some bool true | 120 | | 0x06 | Sets some other bool true | 121 | | 0x07 | Sets some other bool false (same as 6) | 122 | | 0x08 | Starts a timer and calls a func | 123 | | 0x09 | ? | 124 | | 0x0A | ? | 125 | | 0x0B | Reset guardian | 126 | | 0x0C | CHEMX toggle command | 127 | | 0x0D | Start camera frame streaming | 128 | | 0x0E | Stop camera frame streaming | 129 | | 0x0F | ASW toggle command | 130 | | 0x10 | ? | 131 | | 0x11 | Drop frames state command | 132 | 133 | ### getInputControl 134 | | Num | Function | 135 | |------|---------------------------| 136 | | 0x00 | ? | 137 | | 0x01 | Toggle hands | 138 | | 0x02 | Toggle 3-pt body tracking | 139 | 140 | ## Packet Patterns 141 | All packets are wrapped with XrspPacketHeader and Topics, but not all Topics function the same. 142 | 143 | - Some, like hostinfo-adv, wrap capnproto payloads directly in one packet. 144 | - Some, like AudioControl, Video, and CameraStream have a simple two-state state machine: A raw packet is sent with a u32 type, u32 segment0 length (CameraStream/others have two more u32s for segments 1 and 2). XrspPacketHeader-wrapped packets are then sent until each segment buffer is filled. Once filled, the segment is decoded. This is followed both sending and receiving. 145 | - Some, like Slice N, have a 3+ stage state machine: A raw packet (u32 type, u32 segment len), a capnproto packet which describes the H.264 CSD NAL size and H.264 IDR NAL size, an optional CSD packet, and an optional IDR packet. 146 | - Some topics send raw struct data, no capnproto at all (Hands, ...) 147 | -------------------------------------------------------------------------------- /camera_metadata_quest2.json: -------------------------------------------------------------------------------- 1 | {"FileFormat":{"Version":"1","Timestamp":"2022-11-08T18:19:28","UnixTime":1667931568},"Device":{"SerialNumber":"1WMHH8648X1203","DeviceType":"Hollywood","BuildType":"EVT2","BuildSubType":""},"Metadata":{"AlgorithmVersion":3,"Source":"Online","Tags":["vega_online_calibration"]},"CameraCalibration":[{"Id":"0","SensorType":"","Shutter":{"Type":"Global"},"ImageSize":[640,480],"DeviceFromCamera":[0.6338148726212582,0.125474617503469,-0.7632396921062585,-0.07149786292652223,0.7052562154504736,-0.49896392393264,0.5036354566372564,0.030754555165336318,-0.31763560539173416,-0.8574911795325365,-0.4047425097655617,-0.06215526592210936,0.0,0.0,0.0,1.0],"Projection":{"Model":"PinholeSymmetric","Coefficients":[242.20742751451039,318.51319377630167,241.13947711038473]},"Distortion":{"Model":"Fisheye62","Coefficients":[-0.025009305155799866,0.09295447289179192,-0.05569840767793695,-0.00037856519443866675,0.0059183191721825629,-0.001049561967386322,-0.00005155755335512564,0.00001536371999331907]}},{"Id":"1","SensorType":"","Shutter":{"Type":"Global"},"ImageSize":[640,480],"DeviceFromCamera":[0.20662358431634188,0.9636615679650827,-0.1693017331020134,-0.047695876639966289,0.7297472915937143,-0.26704698445609989,-0.6294082923703355,-0.03517275985043566,-0.6517480992039069,0.006503116174670531,-0.7584076243446609,-0.06828677991140356,0.0,0.0,0.0,1.0],"Projection":{"Model":"PinholeSymmetric","Coefficients":[241.85893594959496,320.9246645552424,240.58616928243786]},"Distortion":{"Model":"Fisheye62","Coefficients":[-0.026440060297733556,0.10615242753489059,-0.0827545872700942,0.02557327552072848,-0.00572180866006264,0.0009189465372838487,0.0002852192745621149,0.0001376480112361074]}},{"Id":"2","SensorType":"","Shutter":{"Type":"Global"},"ImageSize":[640,480],"DeviceFromCamera":[-0.2069048073447013,0.963680461031452,0.16885013983962209,0.047665301213487599,0.7287371292499911,0.26695460329255096,-0.630616711032461,-0.035437491966015758,-0.652788324918678,-0.007430262924334852,-0.7575039234503946,-0.06758187299216573,0.0,0.0,0.0,1.0],"Projection":{"Model":"PinholeSymmetric","Coefficients":[241.4613130758027,320.0747406446129,238.93928977071409]},"Distortion":{"Model":"Fisheye62","Coefficients":[-0.026618731566195137,0.10560330986805007,-0.08109515327978695,0.02198140174072569,-0.003197050025331285,0.00035531677171002585,0.0005293837299626318,0.00027750695908279003]}},{"Id":"3","SensorType":"","Shutter":{"Type":"Global"},"ImageSize":[640,480],"DeviceFromCamera":[-0.6329968438479621,0.12188108912901363,0.7644998337418029,0.07190497312825264,0.7037551654264049,0.5020573363218845,0.5026600224615317,0.030621114398760239,-0.3225579991477071,0.8562029147101168,-0.40357515536480706,-0.06326315745476612,0.0,0.0,0.0,1.0],"Projection":{"Model":"PinholeSymmetric","Coefficients":[241.50893348911215,322.1747700360682,239.13078288727307]},"Distortion":{"Model":"Fisheye62","Coefficients":[-0.022910631634162998,0.09491618378074302,-0.06485672129164109,0.01104995386125768,0.00012862465143788273,-0.00000604676093245385,0.000059556366205616768,-0.0005216141500882547]}}],"ImuCalibration":{"Id":"","SensorType":"ICM42688","DeviceFromImu":[1.0,0.0,0.0,0.0384,0.0,1.0,0.0,0.0321,0.0,0.0,1.0,-0.0674,0.0,0.0,0.0,1.0],"Accelerometer":{"Model":"Linear","RectificationMatrix":[0.0010603417635004533,0.9996303750950689,-0.014527676883873964,0.99800751539139,-0.0015012670930739118,-0.007795965163506693,-0.008558585292454303,-0.01635994048954267,-0.9993512996665794],"Offset":{"Model":"TemperaturePiecewiseLinear","CoordinateSystem":"Raw","DataDescription":"temperature, offset.x, offset.y, offset.z","TemperatureOffsetPairs":[[22.537065505981447,-0.019453737884759904,-0.040138136595487598,0.025651877745985986],[23.493776321411134,-0.019991599023342134,-0.03766191750764847,0.024520132690668107],[24.482210159301759,-0.020105762407183648,-0.03248262405395508,0.024871675297617914],[25.494665145874025,-0.02073698490858078,-0.024545986205339433,0.0236930251121521],[26.494548797607423,-0.02117658592760563,-0.02018851786851883,0.023032115772366525],[27.473751068115236,-0.02155018225312233,-0.019123993813991548,0.02154875174164772],[28.504213333129884,-0.02173684723675251,-0.018517494201660157,0.019968314096331598],[29.49630355834961,-0.02181912027299404,-0.015422581695020199,0.0205343309789896],[30.500164031982423,-0.022000504657626153,-0.011382234282791615,0.020412951707839967],[31.499177932739259,-0.022214403375983239,-0.009889334440231324,0.02086985670030117],[32.36756134033203,-0.02192697301506996,-0.013620588928461075,0.025358447805047036],[33.50114059448242,-0.01610124111175537,-0.01874830387532711,-0.0008119593840092421],[34.529083251953128,-0.01582847163081169,-0.017441723495721818,0.0018803764833137394],[35.48320007324219,-0.01605120673775673,-0.0139659084379673,0.0018076662672683597],[36.56521224975586,-0.016916649416089059,-0.01135827787220478,-0.0010473151924088598],[37.900691986083987,-0.021097613498568536,-0.019959570840001107,0.0023000305518507959],[38.46084976196289,-0.014095504768192768,-0.014395337551832199,0.01833689771592617],[39.4703369140625,-0.011570250615477562,-0.011864163912832737,0.011879847384989262],[40.52216339111328,-0.0030052096117287876,-0.0005984611343592405,-0.0020795937161892654],[41.63888168334961,-0.01326641347259283,-0.005437314510345459,-0.00045304009108804166],[42.428890228271487,-0.014256104826927185,0.0003286575374659151,0.014829539693892002],[43.549537658691409,-0.014282594434916973,-0.0032733730040490629,-0.00154801644384861],[44.51606750488281,-0.018806947395205499,-0.0012421970022842289,-0.00017278445011470467],[45.50738525390625,-0.0089387446641922,-0.009472114965319634,-0.01899310015141964],[46.381954193115237,-0.001133530167862773,-0.009692882187664509,-0.012115489691495896],[47.119956970214847,-0.006623451132327318,-0.0074803950265049938,0.028925668448209764]]},"DtAccelRef":0.0},"Gyroscope":{"Model":"Linear","RectificationMatrix":[0.005406817239829856,1.0012380010864027,-0.016606191892274147,0.9987002184083774,-0.0001084706868217108,-0.009360784054065775,-0.0071610198990153949,-0.018654656202100884,-1.0006268187346175],"Offset":{"Model":"TemperaturePiecewiseLinear","CoordinateSystem":"Raw","DataDescription":"temperature, offset.x, offset.y, offset.z","TemperatureOffsetPairs":[[22.537065505981447,-0.00686239218339324,-0.026264041662216188,0.006152094341814518],[23.493776321411134,-0.007736447732895613,-0.025996623560786248,0.006432197522372007],[24.482210159301759,-0.008537016808986664,-0.025591908022761346,0.0067919897846877579],[25.494665145874025,-0.009229541756212712,-0.02541748806834221,0.007024688180536032],[26.494548797607423,-0.00977269932627678,-0.025202007964253427,0.007078773807734251],[27.473751068115236,-0.010039030574262143,-0.024892067536711694,0.007306842599064112],[28.504213333129884,-0.010260024107992649,-0.024589717388153077,0.0074409907683730129],[29.49630355834961,-0.010364968329668045,-0.02432117983698845,0.007490874733775854],[30.500164031982423,-0.010470016859471798,-0.02389209344983101,0.007475154008716345],[31.499177932739259,-0.010451698675751686,-0.023624861612915994,0.007494522724300623],[32.36756134033203,-0.01049759704619646,-0.02331860363483429,0.007244094740599394],[33.50114059448242,-0.008008787408471108,-0.02079503796994686,0.006825968157500029],[34.529083251953128,-0.008020204491913319,-0.02053878642618656,0.0066834744065999989],[35.48320007324219,-0.008124247193336487,-0.02038457989692688,0.006570891942828894],[36.56521224975586,-0.008268083445727826,-0.020362960174679757,0.006508998107165098],[37.900691986083987,-0.011491591110825539,-0.023832030594348909,0.006327428389340639],[38.46084976196289,-0.011545486748218537,-0.02421034872531891,0.006969706621021032],[39.4703369140625,-0.011790175922214985,-0.024209504947066308,0.006925707682967186],[40.52216339111328,-0.012160825543105603,-0.02425288036465645,0.00700907688587904],[41.63888168334961,-0.012481407262384892,-0.024344731122255327,0.006714133080095053],[42.428890228271487,-0.012674856930971146,-0.024227816611528398,0.006928985007107258],[43.549537658691409,-0.013076860457658768,-0.024068133905529977,0.006849744822829962],[44.51606750488281,-0.013376534916460514,-0.023752879351377488,0.006905702408403158],[45.50738525390625,-0.01363322138786316,-0.02351253665983677,0.006736616604030132],[46.381954193115237,-0.013910843059420586,-0.023819562047719957,0.006603096146136522],[47.119956970214847,-0.01348727848380804,-0.023223398253321649,0.006377766374498606]]},"DtGyroRef":0.0}}} -------------------------------------------------------------------------------- /xrsp_host.py: -------------------------------------------------------------------------------- 1 | import struct 2 | 3 | # Requires pyusb 4 | import usb.core 5 | import usb.util 6 | 7 | # Draw cameras 8 | import pygame 9 | import numpy as np 10 | import json 11 | 12 | import time 13 | 14 | from xrsp_parse import * 15 | from xrsp_constants import * 16 | from protos.Slice_capnp import PayloadSlice 17 | 18 | PAIRINGSTATE_WAIT_FIRST = 0 19 | PAIRINGSTATE_WAIT_SECOND = 1 20 | PAIRINGSTATE_PAIRING = 2 21 | PAIRINGSTATE_PAIRED = 3 22 | 23 | ECHOSTATE_0 = 0 24 | ECHOSTATE_1 = 1 25 | ECHOSTATE_2 = 2 26 | ECHOSTATE_3 = 3 27 | 28 | class CameraStreamState: 29 | 30 | def __init__(self): 31 | self.has_meta = False 32 | self.seq_collecting = False 33 | self.which = 0 34 | self.seq_seg0_size = 0 35 | self.seq_seg1_size = 0 36 | self.seq_seg2_size = 0 37 | 38 | self.seq_seg0 = b'' 39 | self.seq_seg1 = b'' 40 | self.seq_seg2 = b'' 41 | 42 | self.pixels = np.zeros((1024,1280)) 43 | 44 | 45 | class GenericState: 46 | 47 | def __init__(self): 48 | self.state = STATE_SEGMENT_META 49 | 50 | self.reset() 51 | 52 | def reset(self): 53 | self.type_idx = 0 54 | self.seg0_size = 0 55 | self.seg0 = b'' 56 | self.seg1_size = 0 57 | self.seg1 = b'' 58 | 59 | # runtime IPC 60 | self.timestamp = 0 61 | 62 | class XrspHost: 63 | 64 | def __init__(self): 65 | self.topics_mute = [TOPIC_AUI4A_ADV, TOPIC_POSE, TOPIC_HANDS, TOPIC_AUDIO] #TOPIC_CAMERA_STREAM 66 | #self.topics_mute = [TOPIC_AUI4A_ADV, TOPIC_POSE, TOPIC_AUDIO] 67 | 68 | self.camera_state = CameraStreamState() 69 | self.pose_state = GenericState() 70 | self.audio_state = GenericState() 71 | self.logging_state = GenericState() 72 | self.ipc_state = GenericState() 73 | self.slice_state = [None] * 15 74 | for i in range(0, 15): 75 | self.slice_state[i] = GenericState() 76 | 77 | # USB 78 | self.has_usb = False 79 | self.dev = None 80 | self.ep_in = None 81 | self.ep_out = None 82 | self.increment = 0 83 | self.remainder_bytes = b'' 84 | self.working_pkt = None 85 | 86 | # Pairing 87 | self.pairing_state = PAIRINGSTATE_WAIT_FIRST 88 | self.reset_echo() 89 | self.last_xmt = 0 90 | self.start_ns = time.time_ns() 91 | 92 | # Video 93 | self.num_slices = 5 94 | 95 | # Misc 96 | self.video_inc = 0 97 | self.touchpad_pos = (0,0) 98 | self.touchpad_z = 0.0 99 | self.headsetQuat = [0,0,0,1] 100 | self.headsetPos = [0,0,0] 101 | 102 | self.window = pygame.display.set_mode((1280, 1024)) 103 | pygame.display.set_caption('Camera') 104 | self.update() 105 | 106 | def reset_echo(self): 107 | self.echo_state = ECHOSTATE_0 108 | self.echo_idx = 1 109 | self.ns_offset = 0 110 | 111 | self.echo_req_sent_ns = 0 # client ns 112 | self.echo_req_recv_ns = 0 # server ns 113 | self.echo_resp_sent_ns = 0 # server ns 114 | self.echo_resp_recv_ns = 0 # server ns 115 | 116 | def init_usb(self): 117 | # find our device 118 | self.dev = usb.core.find(idVendor=0x2833) #, idProduct=0x0183 119 | 120 | # was it found? 121 | if self.dev is None: 122 | raise ValueError('Device not found') 123 | 124 | #dev.reset() 125 | 126 | # set the active configuration. With no arguments, the first 127 | # configuration will be the active one 128 | self.dev.set_configuration() 129 | 130 | # get an endpoint instance 131 | cfg = self.dev.get_active_configuration() 132 | intf = None 133 | for i in range(0, 10): 134 | intf = cfg[(i,0)] 135 | if "XRSP" in str(intf): 136 | print (intf) 137 | break 138 | 139 | 140 | self.ep_out = usb.util.find_descriptor( 141 | intf, 142 | # match the first OUT endpoint 143 | custom_match = \ 144 | lambda e: \ 145 | usb.util.endpoint_direction(e.bEndpointAddress) == \ 146 | usb.util.ENDPOINT_OUT) 147 | 148 | self.ep_in = usb.util.find_descriptor( 149 | intf, 150 | # match the first OUT endpoint 151 | custom_match = \ 152 | lambda e: \ 153 | usb.util.endpoint_direction(e.bEndpointAddress) == \ 154 | usb.util.ENDPOINT_IN) 155 | 156 | try: 157 | self.ep_out.clear_halt() 158 | except: 159 | a='a' 160 | 161 | try: 162 | self.ep_in.clear_halt() 163 | except: 164 | a='a' 165 | 166 | # Clear pkt dump 167 | f = open("dump_pkts.bin", "wb") 168 | f.write(b'') 169 | f.close() 170 | 171 | self.has_usb = True 172 | 173 | def ts_ns(self): 174 | return time.time_ns() - self.start_ns 175 | 176 | def init_session(self): 177 | self.read_xrsp() 178 | 179 | response_ok_payload = bytes([0x00, 0x00, 0x00, 0x00, 0x03, 0x00, 0x03, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, 0x2B, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x03, 0x00, 0x02, 0x00, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]) 180 | response_ok = HostInfoPkt.craft_capnp(self, BUILTIN_OK, result=0xC8, unk_4=1, payload=response_ok_payload).to_bytes() 181 | 182 | print ("OK send") 183 | self.send_to_topic(1, response_ok) 184 | self.old_read_xrsp() 185 | 186 | def send_codegen_1(self): 187 | request_codegen_payload = bytes([0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x01, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]) 188 | request_codegen = HostInfoPkt.craft_capnp(self, BUILTIN_CODE_GENERATION, result=0xC8, unk_4=1, payload=request_codegen_payload).to_bytes() 189 | 190 | print ("Codegen send") 191 | self.send_to_topic(1, request_codegen) 192 | self.old_read_xrsp() 193 | 194 | def send_pairing_1(self): 195 | request_pairing_payload = bytes([0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00]) 196 | request_pairing = HostInfoPkt.craft_capnp(self, BUILTIN_PAIRING, result=0xC8, unk_4=1, payload=request_pairing_payload).to_bytes() 197 | 198 | print ("Pairing send") 199 | self.send_to_topic(1, request_pairing) 200 | self.old_read_xrsp() 201 | 202 | def finish_pairing_1(self): 203 | request_video_idk = [0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00] 204 | 205 | print ("Echo send") 206 | self.send_ping() 207 | 208 | print ("Video idk cmd send") 209 | self.send_to_topic_capnp_wrapped(TOPIC_VIDEO, 0, request_video_idk) 210 | 211 | print ("Waiting for user to accept...") 212 | 213 | def init_session_2(self): 214 | self.reset_echo() 215 | self.read_xrsp() 216 | 217 | response_ok_2_payload = bytes([0x00, 0x00, 0x00, 0x00, 0x03, 0x00, 0x03, 0x00, 0x03, 0x00, 0x01, 0x00, 0x1F, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x48, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, 0x1B, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x2A, 0x00, 0x00, 0x00, 0x55, 0x53, 0x42, 0x33, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x03, 0x00, 0x02, 0x00, 0x00, 0x00]) 218 | response_ok_2 = HostInfoPkt.craft_capnp(self, message_type=BUILTIN_OK, result=0xC8, unk_4=1, payload=response_ok_2_payload).to_bytes() 219 | 220 | print ("Done?") 221 | 222 | print ("OK send #2") 223 | self.send_to_topic(TOPIC_HOSTINFO_ADV, response_ok_2) 224 | 225 | print ("OK read #2") 226 | self.old_read_xrsp() 227 | 228 | def send_codegen_2(self): 229 | request_codegen_2_payload = bytes([0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x01, 0x00, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]) 230 | request_codegen_2 = HostInfoPkt.craft_capnp(self, BUILTIN_CODE_GENERATION, result=0xC8, unk_4=1, payload=request_codegen_2_payload).to_bytes() 231 | 232 | print ("Codegen send #2") 233 | self.send_to_topic(TOPIC_HOSTINFO_ADV, request_codegen_2) 234 | 235 | print ("Codegen read #2") 236 | self.old_read_xrsp() 237 | 238 | def send_pairing_2(self): 239 | request_pairing_2_payload = bytes([0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]) 240 | request_pairing_2 = HostInfoPkt.craft_capnp(self, BUILTIN_PAIRING, result=0xC8, unk_4=1, payload=request_pairing_2_payload).to_bytes() 241 | 242 | print ("Pairing send #2") 243 | self.send_to_topic(TOPIC_HOSTINFO_ADV, request_pairing_2) 244 | 245 | print ("Pairing read #2") 246 | self.old_read_xrsp() 247 | 248 | def finish_pairing_2(self): 249 | send_audiocontrol_idk = struct.pack(" 0: 300 | bits |= 1 301 | if sliceIdx == self.num_slices-1: 302 | bits |= 2 303 | 304 | msg.frameIdx = frameIdx 305 | msg.unk0p1 = 0 306 | msg.unk1p0 = 0 307 | msg.poseQuatX = self.headsetQuat[0] 308 | msg.poseQuatY = self.headsetQuat[1] 309 | msg.poseQuatZ = self.headsetQuat[2] 310 | msg.poseQuatW = self.headsetQuat[3] 311 | msg.poseX = self.headsetPos[0] 312 | msg.poseY = self.headsetPos[1] 313 | msg.poseZ = self.headsetPos[2] 314 | msg.timestamp05 = self.ts_ns()#18278312488115 315 | msg.sliceNum = sliceIdx 316 | msg.unk6p1 = bits 317 | msg.unk6p2 = 0 318 | msg.unk6p3 = 0 319 | msg.blitYPos = blitYPos 320 | msg.unk7p0 = 24 321 | 322 | msg.unk8p1 = 0 323 | msg.timestamp09 = self.ts_ns()#18787833654115 324 | msg.unkA = 29502900 325 | msg.timestamp0B = self.ts_ns()#18278296859411 326 | msg.timestamp0C = self.ts_ns()#18278292486840 327 | msg.timestamp0D = self.ts_ns()#18787848654114 328 | msg.quat1.x = 0 329 | msg.quat1.y = 0 330 | msg.quat1.z = 0 331 | msg.quat1.w = 0 332 | msg.quat2.x = 0 333 | msg.quat2.y = 0 334 | msg.quat2.z = 0 335 | msg.quat2.w = 0 336 | 337 | msg.csdSize = len(csdDat) 338 | msg.videoSize = len(videoDat) 339 | 340 | segments = msg.to_segments() 341 | 342 | hex_dump(segments[0]) 343 | 344 | self.send_to_topic_capnp_wrapped(TOPIC_SLICE_0+sliceIdx, 0, segments[0]) 345 | self.send_to_topic(TOPIC_SLICE_0+sliceIdx, csdDat) 346 | self.send_to_topic(TOPIC_SLICE_0+sliceIdx, videoDat) 347 | 348 | def send_to_topic(self, topic, msg): 349 | if not self.has_usb: 350 | return 351 | 352 | if len(msg) <= 0: 353 | return 354 | 355 | idx = 0 356 | to_send = len(msg) 357 | while True: 358 | if idx >= to_send: 359 | break 360 | self.send_to_topic_raw(topic, msg[idx:idx+0xFFFF]) 361 | 362 | idx += 0xFFFF 363 | 364 | def send_to_topic_raw(self, topic, msg): 365 | if not self.has_usb: 366 | return 367 | 368 | aligned = True 369 | real_len = len(msg) 370 | align_up_bytes = (((4+len(msg)) // 4) * 4) - len(msg) 371 | if align_up_bytes != 4 and align_up_bytes != 0: 372 | if align_up_bytes > 1: 373 | msg += bytes([0xDE] * (align_up_bytes-1)) 374 | msg += bytes([(len(msg)+1) - real_len]) 375 | aligned = False 376 | 377 | try: 378 | pkt_out = struct.pack(" 0: 383 | pkt_out += struct.pack("= 8: 437 | pkt = TopicPkt(self, b) 438 | while pkt.missing_bytes() > 0: 439 | #print ("MISSING", hex(pkt.missing_bytes())) 440 | _b = bytes(self.ep_in.read(0x200)) 441 | f = open("dump_pkts.bin", "ab") 442 | f.write(_b) 443 | f.close() 444 | 445 | pkt.add_missing_bytes(_b) 446 | b += _b 447 | 448 | except usb.core.USBTimeoutError as e: 449 | print ("Failed read", e) 450 | except usb.core.USBError as e: 451 | print ("Failed read", e) 452 | 453 | if len(b) >= 8: 454 | try: 455 | ''' 456 | pkt = TopicPkt(self, b) 457 | if pkt.missing_bytes() > 0: 458 | self.remainder_bytes = b 459 | else: 460 | self.remainder_bytes = pkt.remainder_bytes() 461 | b = b[:len(b)-len(remainder_bytes)] 462 | self.handle_packet(pkt) 463 | ''' 464 | pkt = TopicPkt(self, b) 465 | self.remainder_bytes = pkt.remainder_bytes() 466 | b = b[:len(b)-len(self.remainder_bytes)] 467 | self.handle_packet(pkt) 468 | except Exception as e: 469 | print (e) 470 | 471 | return b 472 | 473 | def read_xrsp(self): 474 | if not self.has_usb: 475 | return 476 | 477 | if self.ts_ns() - self.echo_req_sent_ns > 1000000000 and self.pairing_state >= PAIRINGSTATE_PAIRING: 478 | self.send_ping() 479 | 480 | f = open("dump_pkts.bin", "ab") 481 | 482 | b = b'' 483 | while True: 484 | try: 485 | b = bytes(self.ep_in.read(0x200)) 486 | 487 | f.write(bytes(b)) 488 | 489 | if self.working_pkt is None: 490 | self.working_pkt = TopicPkt(self, b) 491 | elif self.working_pkt.missing_bytes() == 0: 492 | self.handle_packet(self.working_pkt) 493 | remains = self.working_pkt.remainder_bytes() 494 | if len(remains) > 0 and len(remains) < 8: 495 | self.working_pkt = None 496 | print("Weird remainder!") 497 | hex_dump(remains) 498 | elif len(remains) > 0: 499 | self.working_pkt = TopicPkt(self, remains) 500 | self.working_pkt.add_missing_bytes(b) 501 | else: 502 | self.working_pkt = TopicPkt(self, b) 503 | else: 504 | self.working_pkt.add_missing_bytes(b) 505 | 506 | while self.working_pkt is not None and self.working_pkt.missing_bytes() == 0: 507 | self.handle_packet(self.working_pkt) 508 | remains = self.working_pkt.remainder_bytes() 509 | if len(remains) > 0 and len(remains) < 8: 510 | self.working_pkt = None 511 | print("Weird remainder!") 512 | hex_dump(remains) 513 | elif len(remains) > 0: 514 | self.working_pkt = TopicPkt(self, remains) 515 | else: 516 | self.working_pkt = None 517 | break 518 | except usb.core.USBTimeoutError as e: 519 | print ("Failed read", e) 520 | f.close() 521 | return b 522 | except usb.core.USBError as e: 523 | print ("Failed read", e) 524 | f.close() 525 | return b 526 | 527 | f.close() 528 | return b 529 | 530 | def wait_pairing(self): 531 | while self.pairing_state != PAIRINGSTATE_PAIRED: 532 | self.read_xrsp() 533 | 534 | def handle_packet(self, pkt): 535 | pkt.dump() 536 | 537 | if pkt.topic_idx == TOPIC_HOSTINFO_ADV: 538 | self.handle_hostinfo_adv(pkt) 539 | return 540 | 541 | def handle_hostinfo_adv(self, pkt): 542 | hostinfo = pkt.specificObj 543 | 544 | if hostinfo is None: 545 | return 546 | 547 | if hostinfo.message_type == BUILTIN_ECHO: 548 | self.handle_echo(hostinfo) 549 | return 550 | 551 | if self.pairing_state == PAIRINGSTATE_WAIT_FIRST: 552 | if hostinfo.message_type == BUILTIN_INVITE: 553 | self.init_session() 554 | elif hostinfo.message_type == BUILTIN_ACK: 555 | self.send_codegen_1() 556 | elif hostinfo.message_type == BUILTIN_CODE_GENERATION_ACK: 557 | self.send_pairing_1() 558 | elif hostinfo.message_type == BUILTIN_PAIRING_ACK: 559 | self.finish_pairing_1() 560 | 561 | self.pairing_state = PAIRINGSTATE_WAIT_SECOND 562 | elif self.pairing_state == PAIRINGSTATE_WAIT_SECOND or self.pairing_state == PAIRINGSTATE_PAIRING: 563 | if hostinfo.message_type == BUILTIN_INVITE: 564 | self.pairing_state = PAIRINGSTATE_PAIRING 565 | self.init_session_2() 566 | elif hostinfo.message_type == BUILTIN_ACK: 567 | self.send_codegen_2() 568 | elif hostinfo.message_type == BUILTIN_CODE_GENERATION_ACK: 569 | self.send_pairing_2() 570 | elif hostinfo.message_type == BUILTIN_PAIRING_ACK: 571 | self.finish_pairing_2() 572 | 573 | self.pairing_state = PAIRINGSTATE_PAIRED 574 | 575 | def handle_echo(self, echopkt): 576 | if (echopkt.result & 1) == 1: # PONG 577 | 578 | #print ("Pong!") 579 | 580 | self.echo_req_recv_ns = echopkt.echo_recv # server recv ns 581 | self.echo_resp_sent_ns = echopkt.echo_xmt # server tx ns 582 | self.echo_resp_recv_ns = echopkt.recv_ns # client rx ns 583 | self.echo_req_sent_ns = self.ts_ns() 584 | 585 | self.ns_offset = ((self.echo_req_recv_ns-self.echo_req_sent_ns) + (self.echo_resp_sent_ns-echopkt.recv_ns)) // 2 586 | self.ns_offset = self.ns_offset & 0xFFFFFFFFFFFFFFFF 587 | 588 | #print("Ping offs:", hex(self.ns_offset)) 589 | 590 | if self.pairing_state == PAIRINGSTATE_PAIRED: 591 | self.send_ping() 592 | 593 | else: # PING 594 | #print ("Ping!", hex(self.ns_offset)) 595 | self.last_xmt = echopkt.echo_xmt 596 | 597 | outpkt = HostInfoPkt.craft_echo(self, result=ECHO_PONG, echo_id=echopkt.unk_4, org=self.last_xmt, recv=echopkt.recv_ns, xmt=self.ts_ns(), offset=self.ns_offset) 598 | response_echo_pong = outpkt.to_bytes() 599 | #hex_dump(response_echo_pong) 600 | #outpkt.dump() 601 | self.send_to_topic(TOPIC_HOSTINFO_ADV, response_echo_pong) 602 | 603 | 604 | def update(self): 605 | background_color = (0, 0, 0) 606 | self.window.fill(background_color) 607 | 608 | 609 | pixel_array = pygame.PixelArray(self.window) 610 | 611 | for x in range(0, 1280): 612 | for y in range(0, 1024): 613 | val = self.camera_state.pixels[y,x] 614 | pixel_array[x,y] = (val,val,val) 615 | 616 | 617 | pygame.draw.circle(self.window, (int(self.touchpad_z * 255),255,0), self.touchpad_pos, 20 + (self.touchpad_z * 20)) 618 | 619 | #pixel_array.close() 620 | pygame.display.flip() -------------------------------------------------------------------------------- /xrsp_parse.py: -------------------------------------------------------------------------------- 1 | # pip3 install pycapnp pygame numpy 2 | import struct 3 | 4 | # Draw cameras 5 | import pygame 6 | import numpy as np 7 | import json 8 | 9 | import capnp 10 | from protos.Logging_capnp import PayloadLogging 11 | from protos.CameraStream_capnp import PayloadCameraStream 12 | from protos.Pose_capnp import PayloadPose 13 | from protos.Audio_capnp import PayloadAudio 14 | from protos.RuntimeIPC_capnp import PayloadRuntimeIPC 15 | from protos.Slice_capnp import PayloadSlice 16 | from protos.HostInfo_capnp import PayloadHostInfo 17 | 18 | from capnp_parse import CapnpParser 19 | from utils import hex_dump 20 | from xrsp_constants import * 21 | #from xrsp_host import * 22 | 23 | class TopicPkt: 24 | 25 | def __init__(self, host, b): 26 | if len(b) < 7: 27 | print ("Bad topic pkt!") 28 | hex_dump(b) 29 | return 30 | 31 | self.recv_ns = host.ts_ns() 32 | self.host = host 33 | 34 | topic_raw, num_words, sequence = struct.unpack("> 8) & 0x3F 41 | self.unk_14_15 = (topic_raw >> 14) & 0x3 42 | self.num_words = num_words 43 | self.sequence = sequence 44 | 45 | self.real_size = ((self.num_words - 1) * 4) 46 | self.full_size = ((self.num_words - 1) * 4) 47 | 48 | self.payload = b[8:] 49 | 50 | if self.bHasAlignPadding and len(self.payload) >= self.real_size: 51 | self.real_size -= self.payload[self.real_size-1] 52 | 53 | self.payload = self.payload[:self.real_size] 54 | 55 | self.payload_remainder = b[8+self.full_size:] 56 | 57 | self.specificObj = None 58 | 59 | def rebuild_specific(self): 60 | if self.specificObj is not None: 61 | return 62 | 63 | self.specificObj = None 64 | 65 | if self.topic_idx == TOPIC_HOSTINFO_ADV: 66 | self.specificObj = HostInfoPkt(self.host, self.recv_ns, self.payload) 67 | elif self.topic_idx == TOPIC_POSE: 68 | self.specificObj = PosePkt(self.host, self.payload) 69 | elif self.topic_idx == TOPIC_AUDIO: 70 | self.specificObj = AudioPkt(self.host, self.payload) 71 | elif self.topic_idx == TOPIC_HANDS: 72 | self.specificObj = HandsPkt(self.host, self.payload) 73 | elif self.topic_idx == TOPIC_SKELETON: 74 | self.specificObj = SkeletonPkt(self.host, self.payload) 75 | elif self.topic_idx == TOPIC_CAMERA_STREAM: 76 | self.specificObj = CameraStreamPkt(self.host, self.payload, self.sequence) 77 | elif self.topic_idx >= TOPIC_SLICE_0 and self.topic_idx <= TOPIC_SLICE_15: 78 | self.specificObj = SlicePkt(self.host, self.topic_idx - TOPIC_SLICE_0, self.payload) 79 | elif self.topic_idx == TOPIC_RUNTIME_IPC: 80 | self.specificObj = RuntimeIPCPkt(self.host, self.payload) 81 | elif self.topic_idx == TOPIC_LOGGING: 82 | self.specificObj = LoggingPkt(self.host, self.payload) 83 | 84 | def missing_bytes(self): 85 | amt = self.real_size - len(self.payload) 86 | 87 | if amt >= 0: 88 | return amt 89 | return 0 90 | 91 | def add_missing_bytes(self, b): 92 | self.payload += b 93 | before = len(self.payload) 94 | self.payload = self.payload[:self.real_size] 95 | after = len(self.payload) 96 | 97 | if self.bHasAlignPadding and len(self.payload) >= self.real_size: 98 | self.real_size -= self.payload[self.real_size-1] 99 | self.payload = self.payload[:self.real_size] 100 | 101 | self.payload_remainder = b[len(b) - (before-after):] 102 | 103 | def remainder_bytes(self): 104 | #if self.missing_bytes() != 0: 105 | # return self.payload + self.payload_remainder 106 | 107 | return self.payload_remainder 108 | 109 | def dump(self): 110 | # Don't print aui4a-adv padding for now 111 | 112 | if self.missing_bytes() == 0: 113 | self.rebuild_specific() 114 | else: 115 | print ("Missing %x bytes..." % self.missing_bytes()) 116 | return 117 | 118 | if self.topic_idx in self.host.topics_mute: 119 | return 120 | 121 | print ("TopicPkt:") 122 | print (" unk_0_2: %01x" % self.unk_0_2) 123 | print (" bHasAlignPadding: " + str(self.bHasAlignPadding)) 124 | print (" bPacketVersionIsInternal: " + str(self.bPacketVersionIsInternal)) 125 | print (" bPacketVersionNumber: " + str(self.bPacketVersionNumber)) 126 | print (" topic: %s (%01x)" % (XrspTopic[self.topic_idx], self.topic_idx)) 127 | print (" unk_14_15: %01x" % self.unk_14_15) 128 | print (" num_words: %04x" % self.num_words) 129 | print (" sequence: %04x" % self.sequence) 130 | 131 | if self.specificObj is not None: 132 | self.specificObj.dump() 133 | else: 134 | print ("Missing %x bytes..." % self.missing_bytes()) 135 | hex_dump(self.payload) 136 | print ("---------") 137 | print ("") 138 | 139 | 140 | class HostInfoPkt: 141 | 142 | def __init__(self, host, recv_ns, b): 143 | if len(b) < 16: 144 | print ("Bad hostinfo pkt!") 145 | hex_dump(b) 146 | return 147 | 148 | self.recv_ns = recv_ns 149 | 150 | header_0, unk_4 = struct.unpack("> 4) & 0x3FF 159 | self.stream_size = (header_0 >> 12) & 0xFFFFC 160 | 161 | self.unk_8 = 0 162 | self.len_u64s = 0 163 | 164 | self.echo_org = -1 165 | self.echo_recv = -1 166 | self.echo_xmt = -1 167 | self.echo_offset = -1 168 | 169 | if self.message_type == BUILTIN_ECHO: 170 | self.payload = b[8:] 171 | self.echo_org, self.echo_recv, self.echo_xmt, self.echo_offset = struct.unpack("= 1: 403 | self.host.touchpad_pos = ((pose_payload.controllers[0].touchpadX * (1280/2)) + (1280/2), (pose_payload.controllers[0].touchpadY * (-1024/2)) + (1024/2)) 404 | self.host.touchpad_z = pose_payload.controllers[0].touchpadPressure 405 | #self.host.update() 406 | 407 | #print (self.host.touchpad_pos, self.host.touchpad_z) 408 | except Exception as e: 409 | print ("Exception in PosePkt:", e) 410 | hex_dump(self.host.pose_state.seg0) 411 | self.host.pose_state.state = STATE_SEGMENT_META 412 | 413 | 414 | def dump(self): 415 | print ("PosePkt:", hex(len(self.payload))) 416 | if len(self.payload) <= 0x8: 417 | hex_dump(self.payload) 418 | return 419 | 420 | ''' 421 | try: 422 | parser = CapnpParser(self.payload) 423 | parser.parse_entry(0) 424 | except Exception as e: 425 | print (e) 426 | ''' 427 | 428 | try: 429 | print (self.printable) 430 | pass 431 | except Exception as e: 432 | print ("Exception in PosePkt dump:", e) 433 | 434 | class AudioPkt: 435 | 436 | def __init__(self, host, b): 437 | if len(b) < 8: 438 | print ("Bad audio pkt!") 439 | return 440 | 441 | self.host = host 442 | self.payload = b[0x0:] 443 | 444 | self.printable = "" 445 | 446 | if self.host.audio_state.state == STATE_SEGMENT_META: 447 | self.host.audio_state.reset() 448 | self.host.audio_state.type_idx, self.host.audio_state.seg0_size = struct.unpack(" 0x200: 896 | return 897 | 898 | #test = PayloadCameraStream.new_message() 899 | 900 | 901 | 902 | try: 903 | parser = CapnpParser(self.payload) 904 | parser.parse_entry(0) 905 | except Exception as e: 906 | print ("Exception in CameraStreamPkt dump:", e) 907 | --------------------------------------------------------------------------------