8BitDo ships beta and support-issued firmware as .dat files, but their macOS
updater only flashes firmware it downloads from their own server — there is no
"choose a local file" option, and the standalone Windows firmware tool is not
available for macOS.
This repo makes the official 8BitDo Ultimate Software V2 for macOS flash a
.dat file of your choosing. It does not reimplement the flashing protocol.
The official app still does the flashing, exactly as it normally would; it is
simply handed your file instead of the one it meant to download.
Because the real app performs the actual flash, this works for any controller that app supports, not just the one it was developed against.
- No firmware is included here, and none should be. Firmware is 8BitDo's
intellectual property. Bring your own
.dat, from 8BitDo support or an official beta programme. Do not flash files from strangers. - Flashing can brick a controller. Boot mode is entered by a hardware button combination and does not depend on the installed firmware, so a failed flash is normally recoverable by flashing again. "Normally" is not "always".
- Only flash firmware built for your exact model.
inspect_firmware.pyprints a model id; compare it against the official firmware for your device before flashing. A mismatched image is the most likely way to cause real harm. - This modifies
/etc/hostsand temporarily adds a certificate authority to your System keychain.stop.shreverses both and verifies the result.
Developed and tested on an 8BitDo Ultimate 2C Wireless with Ultimate Software V2 build 20 on macOS 15. Nothing here is endorsed by 8BitDo.
Firmware is not in this repository. Known files are identified by hash in
FIRMWARE.md, so you can confirm a .dat you obtained elsewhere
is genuine before flashing it.
One file is archived as a release asset, because it is not available through any normal channel:
- Ultimate 2C Wireless 1.06 beta — support-issued, not published through
the updater. Download from Releases ·
SHA-256
11c8d195d3ca76da24a33ba92ad9d7ae8ad6f455244864bed4c66898e9074bfb
Everything else your controller might need, the official app already downloads by itself — you do not need this project for those. Note that the beta is numerically older than the current 1.09 release, so it is a downgrade onto a separate branch; try official 1.09 first.
Firmware remains 8BitDo's property and is archived here only because it is otherwise unobtainable. It will be removed on request from 8BitDo.
- macOS with
python3andopenssl(both ship with the system or Xcode tools) - 8BitDo Ultimate Software V2 installed
- Your firmware
.dat - Administrator access
Check that your firmware targets your device:
python3 inspect_firmware.py /path/to/firmware.datCompare the reported model id with the official firmware for your controller. The official file is cached by the app at:
~/Library/Containers/com.8BitDo.UltimateV2/Data/Documents/https:/support.8bitdo.com/bd-uploads/files/old/
Start the intercept:
sudo ./start.sh /path/to/firmware.datLeave that terminal open. Then quit the 8BitDo app completely and relaunch it, put the controller in boot mode, and start the firmware update as usual.
Watch the terminal. This line is your confirmation that your file was used:
>> SERVING OUR FIRMWARE (74780 bytes) for GET /bd-uploads/files/old/....dat
If you do not see it, your firmware was not flashed — see Troubleshooting.
When the flash finishes, press Ctrl-C and clean up:
sudo ./stop.shThe updater fetches firmware over plain HTTPS from support.8bitdo.com and
performs no integrity check on what it receives, so substituting the file is
enough.
start.sh:
- Resolves the real IP of
support.8bitdo.combefore redirecting anything. - Generates a throwaway CA and a certificate for that hostname, valid 30 days, and trusts the CA in the System keychain. The app does no certificate pinning, so this is sufficient.
- Adds
127.0.0.1 support.8bitdo.comto/etc/hosts. - Restarts
nsurlsessiond(see Troubleshooting — this step is essential). - Runs a local HTTPS server on port 443 that proxies every request to the
real server so the firmware list, authentication and analytics behave
normally, except for firmware
.datrequests, which return your file.
Only the firmware bytes are substituted. Everything else the app does is real.
Nothing that matters. Its firmware list looks like this:
{
"fileName": "Ultimate 2C",
"file_version": "1.09",
"fileSize": 74780,
"fileURL": "https://support.8bitdo.com/bd-uploads/files/old/7f93201d-....dat",
"beta": 0,
"md5": "57FBA9942000BE57BEB94FD0C217236D"
}That md5 field is a placeholder: the API returns the same value for
different firmware versions, and it matches none of the actual files. The
downloaded file is flashed as-is.
Firmware is a 28-byte header followed by the payload. Field meanings are inferred by diffing several official files, not from documentation.
| Offset | Size | Meaning |
|---|---|---|
| 0 | u16 | version × 100 (106 = 1.06) |
| 2 | u16 | beta flag (1 = beta build) |
| 4 | u32 | flash destination address |
| 8 | u32 | payload length (file size − 28) |
| 12 | u32 | model id |
| 16 | u32 | checksum over the payload |
| 20 | 8 | reserved, observed as zero |
Real examples from the Ultimate 2C:
| file | version | beta | model | payload |
|---|---|---|---|---|
| official factory | 1.06 | 0 | 0x301B |
74752 |
| official latest | 1.09 | 0 | 0x301B |
74752 |
| support-issued beta | 1.06 | 1 | 0x301B |
74752 |
The beta and the official firmware share a model id, flash address and payload length, differing only in the beta flag and the payload — which is exactly what you want to confirm before flashing anything.
No SERVING OUR FIRMWARE line, but the flash completed.
The official firmware was flashed, not yours. The usual cause is
nsurlsessiond, the macOS daemon that performs background downloads on behalf
of apps. It is long-lived and keeps its own connection and DNS state, so it
happily fetches from the real server while every request the app makes in its
own process goes through the intercept. start.sh restarts it for you; if you
changed /etc/hosts by hand, run killall nsurlsessiond yourself.
"Firmware download failed, please try again later".
The app could not reach the download URL. Confirm the server is running, that
dscacheutil -q host -a name support.8bitdo.com reports 127.0.0.1, and that
nothing else is bound to port 443.
The app reflashes the same version and nothing changes. Pick a version the app considers an upgrade. The substituted file is served for whichever entry is chosen, so the label in the list is irrelevant — only the bytes matter.
Verifying after the fact.
The app writes what it downloaded into its container. Comparing that file's md5
with your .dat tells you which bytes it received:
md5 -q ~/Library/Containers/com.8BitDo.UltimateV2/Data/Documents/https:/support.8bitdo.com/bd-uploads/files/old/*.dat
md5 -q /path/to/your/firmware.datRecorded so nobody repeats them.
- Replacing the cached
.datbefore flashing. The app re-downloads unconditionally at the start of every update, overwriting the swap. - Marking the cached file immutable with
chflags uchg. The download then fails and the app aborts with an error instead of using the existing file. - Racing the download with a filesystem watcher. Works occasionally, produces no evidence of which file was actually flashed, and cannot be trusted for something that writes to flash memory.
- Driving the bootloader directly over USB HID. The controller enumerates
in boot mode as
2DC8:3208, a vendor HID device with 64-byte reports. Writing reports succeeds, but no replies are ever delivered to a third-party process on macOS, so the protocol could not be confirmed. Letting the official app own the protocol sidesteps this entirely.
For interoperability and repair on hardware you own. No 8BitDo code or firmware is included or redistributed here. Trademarks belong to their owners. You are responsible for what you flash onto your own device.
MIT licensed — see LICENSE.