#pragma once // ============================================================================== // GIP protocol (Game Input Protocol) // ============================================================================== // Port target: medusalix/xone bus/protocol.c + bus/bus.c. // // The kernel driver's device model (struct device, sysfs, driver // registration) is gone. The adapter owns up to 16 clients; the transport // interface abstracts the MT76 data path (Phase 3), and client_listener // delivers input/battery/audio events to the HID layer (Phase 4). The // adapter is driven single-threaded from the USB read callback. // ============================================================================== #include #include #include #include #include #include #include "auth/auth.hpp" #include "common/types.hpp" namespace xone::gip { using u8 = std::uint8_t; using u16 = std::uint16_t; using u32 = std::uint32_t; inline constexpr u16 k_vid_microsoft = 0x045e; inline constexpr int k_audio_interval = 8; // ms between audio packets inline constexpr u32 k_pkt_max_length = 58; inline constexpr u32 k_chunk_buf_max_length = 0xffff; inline constexpr int k_max_clients = 16; enum : u8 { GIP_CMD_ACKNOWLEDGE = 0x01, GIP_CMD_ANNOUNCE = 0x02, GIP_CMD_STATUS = 0x03, GIP_CMD_IDENTIFY = 0x04, GIP_CMD_POWER = 0x05, GIP_CMD_AUTHENTICATE = 0x06, GIP_CMD_VIRTUAL_KEY = 0x07, GIP_CMD_AUDIO_CONTROL = 0x08, GIP_CMD_RUMBLE = 0x09, GIP_CMD_LED = 0x0a, GIP_CMD_HID_REPORT = 0x0b, GIP_CMD_FIRMWARE = 0x0c, GIP_CMD_SERIAL_NUMBER = 0x1e, GIP_CMD_INPUT = 0x20, GIP_CMD_AUDIO_SAMPLES = 0x60, }; enum : u8 { GIP_OPT_ACKNOWLEDGE = 1 << 4, GIP_OPT_INTERNAL = 1 << 5, GIP_OPT_CHUNK_START = 1 << 6, GIP_OPT_CHUNK = 1 << 7, }; enum : u8 { GIP_BATT_TYPE_NONE = 0x00, GIP_BATT_TYPE_STANDARD = 0x01, GIP_BATT_TYPE_KIT = 0x02, }; enum : u8 { GIP_BATT_LEVEL_LOW = 0x00, GIP_BATT_LEVEL_NORMAL = 0x01, GIP_BATT_LEVEL_HIGH = 0x02, GIP_BATT_LEVEL_FULL = 0x03, }; enum : u8 { GIP_PWR_ON = 0x00, GIP_PWR_SLEEP = 0x01, GIP_PWR_OFF = 0x04, GIP_PWR_RESET = 0x07, }; enum : u8 { GIP_LED_OFF = 0x00, GIP_LED_ON = 0x01, GIP_LED_BLINK_FAST = 0x02, GIP_LED_BLINK_NORMAL = 0x03, GIP_LED_BLINK_SLOW = 0x04, GIP_LED_FADE_SLOW = 0x08, GIP_LED_FADE_FAST = 0x09, }; enum : u8 { GIP_AUD_FORMAT_16KHZ_MONO = 0x05, GIP_AUD_FORMAT_24KHZ_MONO = 0x09, GIP_AUD_FORMAT_48KHZ_STEREO = 0x10, }; enum : u8 { GIP_AUD_FORMAT_CHAT_24KHZ = 0x04, GIP_AUD_FORMAT_CHAT_16KHZ = 0x05, }; // Decoded wire header (variable length, not packed). struct gip_header { u8 command = 0; u8 options = 0; u8 sequence = 0; u32 packet_length = 0; u32 chunk_offset = 0; }; struct gip_hardware { u16 vendor = 0; u16 product = 0; u16 version = 0; }; struct gip_audio_config { u8 format = 0; int channels = 0; int sample_rate = 0; int buffer_size = 0; int fragment_size = 0; int packet_size = 0; }; // Low-level data path to the MT76 chip (implemented by Phase 3). class transport { public: virtual ~transport() = default; // Send one framed GIP data buffer to the chip. virtual auto send_frame(std::span frame) -> int = 0; // Install the AES-CCMP session key for a client (no-op by default). virtual auto set_encryption_key(u8 client_id, std::span key) -> int { (void)client_id; (void)key; return 0; } }; // Events delivered to the HID layer (Phase 4). class client_listener { public: virtual ~client_listener() = default; virtual auto on_client_added(class client&) -> void {} virtual auto on_client_removed(u8) -> void {} virtual auto on_battery(class client&, u8, u8) -> void {} virtual auto on_guide_button(class client&, bool) -> void {} virtual auto on_input(class client&, std::span) -> void {} virtual auto on_hid_report(class client&, std::span) -> void {} virtual auto on_audio_ready(class client&) -> void {} virtual auto on_audio_volume(class client&, u8, u8) -> void {} virtual auto on_audio_samples(class client&, std::span) -> void {} }; class adapter; // One connected controller. Also implements the auth_sink so the auth // handshake can send AUTHENTICATE packets back through the GIP layer. class client : public xone::auth::auth_sink { public: client(adapter& adapter, u8 id); auto id() const -> u8 { return m_id; } auto hardware() const -> gip_hardware const& { return m_hardware; } auto audio_config_in() const -> gip_audio_config const& { return m_audio_config_in; } auto audio_config_out() const -> gip_audio_config const& { return m_audio_config_out; } auto classes() const -> std::vector const& { return m_classes; } auto has_interface(xone::guid_t const& guid) const -> bool; auto set_power_mode(u8 mode) -> int; auto send_rumble(std::span pkt) -> int; auto set_led_mode(u8 mode, u8 brightness) -> int; auto suggest_audio_format(u8 in, u8 out, bool chat) -> int; auto set_audio_volume(u8 in, u8 chat, u8 out) -> int; auto send_audio_samples(std::span samples) -> int; auto enable_audio() -> int; auto init_audio_in() -> int; auto init_audio_out() -> int; auto disable_audio() -> void; // auth_sink implementation. auto send(std::span pkt, bool acknowledge) -> int override; auto set_encryption_key(std::span key) -> int override; auto start_auth() -> int { return m_auth.start(); } auto process_auth(std::span data) -> int { return m_auth.process_pkt(data); } private: friend class adapter; adapter& m_adapter; u8 m_id; gip_hardware m_hardware{}; struct info_element { u8 count = 0; std::vector data; }; std::unique_ptr m_client_commands; std::unique_ptr m_firmware_versions; std::unique_ptr m_audio_formats; std::unique_ptr m_capabilities_out; std::unique_ptr m_capabilities_in; std::vector m_classes; std::unique_ptr m_interfaces; std::unique_ptr m_hid_descriptor; gip_audio_config m_audio_config_in; gip_audio_config m_audio_config_out; // Chunk reassembly buffers (large packets are split into chunks). struct chunk_buffer { gip_header header; u32 length = 0; std::vector data; }; std::unique_ptr m_chunk_buf_out; std::unique_ptr m_chunk_buf_in; xone::auth::auth m_auth{*this}; }; class adapter { public: adapter(transport& transport, client_listener& listener, int audio_packet_count = 8); // Dispatch one USB data buffer (may contain multiple GIP packets). auto process_buffer(std::span data) -> int; auto get_client(u8 id) -> client*; auto client_count() const -> int; private: friend class client; auto send_pkt(client& c, gip_header& hdr, void const* data) -> int; auto send_pkt_simple(gip_header& hdr, void const* data) -> int; auto init_chunk_buffer(gip_header const& hdr, std::unique_ptr& buf) -> int; auto send_remaining_chunks(client& c) -> int; auto request_identification(client& c) -> int; auto acknowledge_pkt(client& c, gip_header const& ack) -> int; auto remove_client(client& c) -> void; auto free_client_info(client& c) -> void; auto add_client(client& c) -> void; auto make_audio_config(gip_audio_config& cfg) -> int; auto set_audio_format(client& c, u8 in, u8 out) -> int; auto set_audio_format_chat(client& c, u8 in_out) -> int; auto dispatch_pkt(client& c, gip_header const& hdr, void const* data, u32 len) -> int; auto process_pkt(client& c, gip_header& hdr, void const* data) -> int; auto process_pkt_chunked(client& c, gip_header const& hdr, void const* data) -> int; auto handle_pkt_acknowledge(client& c, void const* data, u32 len) -> int; auto handle_pkt_announce(client& c, void const* data, u32 len) -> int; auto handle_pkt_status(client& c, void const* data, u32 len) -> int; auto handle_pkt_identify(client& c, void const* data, u32 len) -> int; auto handle_pkt_authenticate(client& c, void const* data, u32 len) -> int; auto handle_pkt_virtual_key(client& c, void const* data, u32 len) -> int; auto handle_pkt_audio_control(client& c, void const* data, u32 len) -> int; auto handle_pkt_hid_report(client& c, void const* data, u32 len) -> int; auto handle_pkt_input(client& c, void const* data, u32 len) -> int; auto handle_pkt_audio_samples(client& c, void const* data, u32 len) -> int; auto parse_info_element(std::span data, u16 offset, int item_length, std::unique_ptr& out) -> int; auto parse_client_commands(client& c, u16 const offsets[8], std::span data) -> int; auto parse_firmware_versions(client& c, u16 const offsets[8], std::span data) -> int; auto parse_audio_formats(client& c, u16 const offsets[8], std::span data) -> int; auto parse_capabilities(client& c, u16 const offsets[8], std::span data) -> int; auto parse_classes(client& c, std::span data, u16 offset) -> int; auto parse_interfaces(client& c, u16 const offsets[8], std::span data) -> int; auto parse_hid_descriptor(client& c, u16 const offsets[8], std::span data) -> int; transport& m_transport; client_listener& m_listener; int m_audio_packet_count; std::array, k_max_clients> m_clients; u8 m_data_sequence = 0; u8 m_audio_sequence = 0; }; } // namespace xone::gip