Tested on Raspberry Pi Zero 2W (64-bit Raspberry Pi OS). Should work on any Pi with a USB OTG port.
Run the install script — it handles everything automatically and is safe to re-run:
cd ~
git clone https://github.com/CasperVM/cursed_controls.git
bash ~/cursed_controls/install.shOptional for a headless appliance install with faster boot and lower idle power:
bash ~/cursed_controls/install.sh --headless-fast-bootIt will:
- Install system packages (adapts linux-headers to your Pi's architecture)
- Configure Bluetooth for Wiimote compatibility
- Enable USB OTG overlay
- Clone and build
raw-gadget - Install Rust and build the gadget shared library
- Set up the Python venv
- Seed
mapping.yamlfromexample_tv_remote.yamlif it does not already exist - Register and enable the
cursed-controls-web.servicesystemd service
Reboot when it finishes, then plug the Pi into USB and open the web UI. The web service starts automatically on boot.
The web service uses ~/cursed_controls/mapping.yaml. On a fresh install, the installer creates it from example_tv_remote.yaml so the UI has a working starting point.
# Wii Remote + Nunchuk (generic)
cp ~/cursed_controls/example_wiimote.yaml ~/cursed_controls/mapping.yaml
# Wii Remote + Nunchuk for Rocket League (sideways hold)
cp ~/cursed_controls/example_rocket_league.yaml ~/cursed_controls/mapping.yamlEdit the file to adjust button mappings as needed.
Plug in or connect your controller first, then:
cd ~/cursed_controls
sudo .venv/bin/cursed-controls map mapping.yamlThe TUI walks you through each Xbox surface and asks you to press the matching button on your controller. It writes the YAML automatically.
See the config format in the README, or use an example as a reference.
sudo systemctl status cursed-controls-web.service
# Watch logs live
journalctl -u cursed-controls-web.service -fThe web UI starts automatically on every boot after install. Use the UI to edit the mapping config and start or stop the gadget runtime.
sudo apt update -y && sudo apt upgrade -y
# 64-bit OS (Pi Zero 2W, Pi 3, Pi 4, Pi 5)
sudo apt install -y git build-essential curl python3-venv bluetooth bluez \
libtool autoconf automake m4 libudev-dev libncurses5-dev \
linux-headers-rpi-v8
# 32-bit OS — replace linux-headers-rpi-v8 with:
# armv7l → linux-headers-rpi-v7l
# armv6l → linux-headers-rpi-v6Allow Wiimotes to stay connected without full bonding:
sudo mkdir -p /etc/bluetooth
grep -q '\[Policy\]' /etc/bluetooth/input.conf 2>/dev/null \
|| echo '[Policy]' | sudo tee -a /etc/bluetooth/input.conf
echo 'ClassicBondedOnly=false' | sudo tee -a /etc/bluetooth/input.conf
sudo systemctl restart bluetoothAllow evdev access without root:
echo 'KERNEL=="uinput", MODE="0666"' \
| sudo tee /etc/udev/rules.d/99-cursed-controls.rules
sudo udevadm control --reload-rulesecho "dtoverlay=dwc2" | sudo tee -a /boot/firmware/config.txt
echo "dwc2" | sudo tee -a /etc/modules
sudo rebootgit clone https://github.com/xairy/raw-gadget.git ~/raw-gadget
make -C ~/raw-gadget/raw_gadget -j$(nproc)# RUSTUP_IO_THREADS=1 prevents OOM on Pi Zero
export RUSTUP_IO_THREADS=1
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y
source ~/.cargo/env
cargo build --release \
--manifest-path ~/cursed_controls/360-w-raw-gadget/Cargo.tomlcd ~/cursed_controls
python3 -m venv .venv
.venv/bin/pip install -e .bash ~/cursed_controls/init-raspbian.shThe installer can apply this for you with:
bash ~/cursed_controls/install.sh --headless-fast-bootThat writes these lines to the active boot config file:
hdmi_blanking=1
hdmi_ignore_hotplug=1
camera_auto_detect=0
display_auto_detect=0
dtparam=audio=off
gpu_mem=16
dtparam=act_led_trigger=none
dtparam=act_led_activelow=on
It does not disable Wi-Fi or Bluetooth.
- On 32-bit OS, replace
linux-headers-rpi-v8with the variant matching your kernel (rpi-v6,rpi-v7l). raw_gadget.kois built against your current kernel. After a kernel upgrade (apt upgrade), rebuild it:make -C ~/raw-gadget/raw_gadget -j$(nproc).