#pragma once // Emulated Bluetooth controller behind the Wii's internal USB module // (/dev/usb/oh1/57e/305). The game's own Broadcom stack (BTE), WUD, WPAD and KPAD // talk HCI to it; each of the four virtual Wii Remotes it hosts is backed by a // physical remote on the PC (see RemoteHost), whose HID reports are passed // through unchanged. Pure host code: no guest memory, so it is unit-testable. #include #include #include #include #include #include namespace Bluetooth { // Bluetooth device address in HCI (least significant byte first) order. using BdAddr = std::array; inline constexpr size_t kRemoteSlots = 4; enum class LinkState : uint8_t { Down, // no baseband connection to the game Connecting, // baseband up, HID channels being opened Ready, // HID control and interrupt channels open }; // The physical side of the virtual remotes. Called from the thread that drives // the Controller; implementations synchronize with their own I/O threads. class RemoteHost { public: virtual ~RemoteHost() = default; // A physical remote is attached to `slot` and can carry a connection. virtual bool IsAttached(size_t slot) const = 0; // True once each time the remote on `slot` asks to connect (it was just // attached, or a button was pressed while it had no connection). virtual bool ConsumeWake(size_t slot) = 0; // Next input report (report id first, no HID header), false when none. virtual bool PopInputReport(size_t slot, std::vector& report) = 0; // Output report from the game (report id first, no HID header). virtual void SendOutputReport(size_t slot, const uint8_t* report, size_t size) = 0; virtual void OnLinkStateChanged(size_t slot, LinkState state) = 0; // The game ended a working connection; a real remote powers off here. virtual void OnGuestDisconnect(size_t slot) = 0; // The game is searching for remotes (its 1+2 / reconnect screens). virtual void OnGuestInquiry(bool active) = 0; // "Nintendo RVL-CNT-01" or "Nintendo RVL-CNT-01-TR". virtual const char* DeviceName(size_t slot) const = 0; }; class RemoteLink; class Controller { public: explicit Controller(RemoteHost& host); ~Controller(); Controller(const Controller&) = delete; Controller& operator=(const Controller&) = delete; // The guest opened / closed the USB device. void Open(uint64_t nowUs); void Close(); bool IsOpen() const { return m_open; } // Raw HCI command packet (opcode first, no H4 byte). void ExecuteCommand(const uint8_t* packet, size_t size, uint64_t nowUs); // Raw ACL packet from the guest (handle first). void ReceiveAcl(const uint8_t* packet, size_t size, uint64_t nowUs); // Advances timers, connection attempts, input forwarding and flow control. void Tick(uint64_t nowUs); bool HasEvent() const { return !m_events.empty(); } bool PopEvent(std::vector& event); // ACL data is only handed out once every queued event has been delivered, // so the guest always sees a connection before data on it. bool PopAcl(std::vector& packet); size_t AclQueued() const { return m_acl.size(); } LinkState SlotLinkState(size_t slot) const; bool PageScanEnabled() const { return (m_scanEnable & 0x02) != 0; } bool InquiryActive() const { return m_inquiry.active; } static BdAddr LocalAddress(); static BdAddr RemoteAddress(size_t slot); static uint16_t ConnectionHandle(size_t slot); // Used by RemoteLink. void QueueEvent(std::vector event); void QueueAcl(uint16_t handle, const std::vector& l2capFrame); RemoteHost& Host() { return m_host; } uint32_t PageTimeoutUs() const { return static_cast(m_pageTimeoutSlots) * 625u; } private: struct StoredKey { BdAddr addr{}; std::array key{}; }; struct Inquiry { bool active = false; uint64_t endUs = 0; uint8_t maxResponses = 0; uint8_t reported = 0; std::array slotReported{}; }; struct Reassembly { std::vector frame; size_t expected = 0; }; // A reply the controller sends after a delay (page / name request timeouts). struct PendingEvent { uint64_t dueUs = 0; std::vector event; }; void ResetState(bool keepStoredKeys); void CommandComplete(uint16_t opcode, const std::vector& params); void CommandStatus(uint16_t opcode, uint8_t status); void TickInquiry(uint64_t nowUs); void FinishInquiry(); void SendNumberOfCompletedPackets(); int SlotForAddress(const BdAddr& addr) const; int SlotForHandle(uint16_t handle) const; const StoredKey* FindStoredKey(const BdAddr& addr) const; void HandleLinkControl(uint16_t opcode, const uint8_t* p, size_t n, uint64_t nowUs); void HandleLinkPolicy(uint16_t opcode, const uint8_t* p, size_t n); void HandleControllerBaseband(uint16_t opcode, const uint8_t* p, size_t n, uint64_t nowUs); void HandleInformational(uint16_t opcode, const uint8_t* p, size_t n); void HandleStatusParameters(uint16_t opcode, const uint8_t* p, size_t n); void HandleVendor(uint16_t opcode, const uint8_t* p, size_t n); void UnknownCommand(uint16_t opcode); RemoteHost& m_host; bool m_open = false; uint8_t m_scanEnable = 0; uint16_t m_pageTimeoutSlots = 0x2000; uint8_t m_inquiryMode = 0; std::array m_classOfDevice{}; std::array m_localName{}; Inquiry m_inquiry; bool m_hostInquiryNotified = false; std::vector m_storedKeys; std::array, kRemoteSlots> m_links; std::array m_completedPackets{}; std::array m_reassembly; std::array m_authPending{}; std::vector m_pending; std::deque> m_events; std::deque> m_acl; }; } // namespace Bluetooth