Update Xbox controller firmware on Linux and macOS
xboxctl inspects and updates the firmware of Xbox (GIP) controllers from
Linux and macOS, without the Xbox Accessories app. It speaks the same
firmware update protocol (“UFU”, carried in GIP command 0x0c) as the
Windows updater and downloads firmware from the same Microsoft feed.
Contents
Supported controllers
Controllers must be connected with a USB cable; updates over the Xbox Wireless Adapter or Bluetooth are not supported.
| Controller | USB ID | Codename | Tested |
|---|---|---|---|
| Xbox Series X|S Controller | 045e:0b12 |
MERL | ✅ |
| Xbox One S Controller | 045e:02ea |
CRET | ✅ |
| Xbox Elite Controller Series 2 | 045e:0b00 |
DELP | ❌ |
| Xbox Adaptive Controller | 045e:0b0a |
ZEPH | ❌ |
| Xbox Adaptive Joystick | 045e:0b1a, 045e:0b1c |
DURA | ❌ |
| Xbox One Elite Controller | 045e:02e3 |
TROY | ❌ |
| Xbox One Controller (2015) | 045e:02dd |
LANG | ❌ |
| Xbox One Controller (original) | 045e:02d1, 045e:02d2 |
ARGO, ARGD | ❌ |
The original Xbox One controller uses a legacy update protocol that xboxctl
does not support.
Untested controllers may well work: xboxctl info and xboxctl update --latest
--dry-run are read-only and show whether a controller speaks the update
protocol and what an update would change. If you try one, please
open an issue with the result.
Microsoft’s feed also lists models that xboxctl has never seen (codenames
NORL, ORRE, PARK, NICO); their firmware layouts suggest accessories other than
controllers. Feed entries with a -cc- suffix (variants of 0b17 and 0b32)
cannot be selected yet, and the SCUF controller (2e95:0504) gets MERL
firmware, which update refuses because the USB IDs differ.
Installation
On Linux (x86_64, aarch64) and Apple Silicon Macs:
curl -fsSL https://raw.githubusercontent.com/bb441db/xboxctl/main/install.sh | sh
Or with wget:
wget -qO- https://raw.githubusercontent.com/bb441db/xboxctl/main/install.sh | sh
The script downloads the binary for your system from the
latest release, checks
its SHA-256 and installs it to ~/.local/bin (Linux) or /usr/local/bin
(macOS). Set XBOXCTL_INSTALL_DIR to install elsewhere, or XBOXCTL_VERSION
to a release tag to install that release. The binaries have no runtime
dependencies; you can also download them from the releases page directly.
On Linux, install the udev rule below so that xboxctl runs without root. On
macOS, run it with sudo; see Permissions.
Using a Steam Deck or another SteamOS device? Follow the step by step guide for SteamOS.
Usage
xboxctl list # connected controllers (read-only)
xboxctl info # identity and per-component firmware versions (read-only)
xboxctl download # current firmware for the connected model
xboxctl download --list # every model in Microsoft's feed
xboxctl update --latest --dry-run # compare the controller with the current firmware
xboxctl update --latest # install the current firmware
xboxctl update <package.bin> # install an rdhm package file
xboxctl rumble # run each rumble motor briefly (--strength 1-100)
xboxctl diag # controller info, then a live view with stick drift check
xboxctl calibrate # guided thumbstick calibration (--stick left|right|both)
xboxctl calibrate --dry-run # show the stored calibration, change nothing
xboxctl calibrate --trigger both # guided trigger calibration (--trigger left|right|both)
With several controllers connected, choose one with --device BUS:ADDRESS
from xboxctl list.
update first shows what would change and stops if the controller is already
up to date. During an update the controller restarts several times (it
re-enumerates on USB) and xboxctl reconnects to it. Keep it connected until
the update finishes.
Downloads are checked against the SHA-256 in their file name and cached in the
user cache directory (~/.cache/xboxctl/firmware on Linux,
~/Library/Caches/xboxctl/firmware on macOS). Under sudo the files still go
to, and belong to, the user who ran it.
Testing rumble
xboxctl rumble runs the handle motors and then the trigger motors for a second
each, by talking to the controller directly over USB. It changes nothing on the
controller. Use it to tell a controller fault from a driver problem: if the
motors move here but not in a game or browser, the controller is fine. Before
the test it sends a few zero-strength packets so the controller accepts it on
repeated runs; see the rumble notes.
Wired rumble does not work through macOS itself for Xbox One, Series X|S and Elite Series 2 controllers (checked on macOS 15.7 and reported on 27.0.1), so this is the way to check the hardware there. See the notes on the macOS driver.
Diagnosing a controller
xboxctl diag shows what the controller reports about itself (identity and
firmware versions) and then opens a live view of the buttons, both sticks and
both triggers. It changes nothing on the
controller.
A LB L +12% -3% R +0% +0% LT 0% RT 100%
The controller only reports changes, so press any button first: that report also
tells where the sticks rest. Press every button and push the sticks and
triggers all the way to see if something does not respond. If you leave a stick alone for a few seconds and it
rests 6% to 30% of its travel away from the centre, the view warns about possible
stick drift; xboxctl calibrate may fix that. A stick that stays further out
makes the view ask you to let go of it: a stick that does not come back is
drifting badly. A few percent off-centre (3% to 6%) is only noted in the summary.
Hold the Xbox button for two seconds to finish, or use --duration SECONDS. The summary at the
end lists the buttons you did not press, the resting state from the first report,
how far each stick and trigger reached after that (“not moved” if you left it
alone) and whether either stick drifted.
Calibrating the sticks
xboxctl calibrate walks you through recalibrating the thumbsticks, like the
Xbox Accessories app does. For each stick it asks you to turn it clockwise 6
times and then counter-clockwise 6 times at the outer edge, with a counter that
follows your progress, and it finishes with a check: push the stick up, right,
down and left and let go, and xboxctl confirms that it reached the edge and
came back to the centre. Without --stick it does the left stick and then the
right one.
The controller learns the stick’s range itself and stores it, so the result
stays after you unplug the controller. It also erases a stick’s stored
calibration the moment the calibration starts: finish all the steps, and if you
stop halfway run the command again (the controller falls back to its default
calibration meanwhile). xboxctl calibrate --dry-run only shows the stored
range of each stick.
Checked on an Xbox Series X|S controller (firmware 5.23). Firmware that does not
support calibration does not answer (a Series X|S on 5.13.3146.0 did not) or
refuses (an Xbox One S does, also on its latest firmware, 5.23, which has no
calibration code), and xboxctl says so; xboxctl update --latest brings the controller up to date.
A firmware update leaves the stored calibration alone: it was unchanged after
going from 5.23 down to 5.13.3146.0 and back.
Calibrating the triggers
xboxctl calibrate --trigger left|right|both is the same for the triggers: it
asks for 12 full presses (what the Xbox Accessories app asks for), follows them,
and then checks that the trigger reads zero at rest and reaches the end of its
range when fully pressed. Checked on an Xbox Series X|S controller (firmware
5.23): the controller learns the range itself and stores it, and the result
stays after a power cycle, as for the sticks.
xboxctl calibrate --trigger both --dry-run only reads, and prints the stored
range of each trigger with the raw replies.
Permissions
Linux
xboxctl detaches xpad from the controller while it runs and needs
read/write access to the controller’s USB device node. Install this udev rule
once to run it without root. It gives the logged-in user access to GIP
controllers (USB class ff/47/d0):
echo 'SUBSYSTEM=="usb", ENV{DEVTYPE}=="usb_device", ATTR{bDeviceClass}=="ff", ATTR{bDeviceSubClass}=="47", ATTR{bDeviceProtocol}=="d0", TAG+="uaccess"' \
| sudo tee /etc/udev/rules.d/70-xboxctl.rules
sudo udevadm control --reload && sudo udevadm trigger
macOS
A system driver (XboxUSBDevice) holds the controller exclusively, and there
is no udev equivalent for taking it over. Commands that talk to the controller
(info, update, rumble, …) must run as root:
sudo xboxctl info
sudo xboxctl update --latest
list and download do not need root.
Building from source
Build with Rust (the toolchain is pinned in rust-toolchain.toml) and a C
compiler; libusb is compiled in.
git clone https://github.com/bb441db/xboxctl
cd xboxctl
cargo build --release # target/release/xboxctl
License
0BSD.