nils ae2de3a787 Add recompilable sdas8051 source; round-trips to stock v2.2.1 binary
roundtrip.py converts the radare2 disassembly (qunexus_v2.2.1.asm) into a
single sdas8051 assembler source (firmware.asm) and verifies it re-assembles
byte-identical to the 54,279-byte stock image (base 0x2400).

  31,051 of 31,346 items (99.06%) re-assemble from mnemonics; 295 are
  pinned to .db where r2's display syntax doesn't round-trip through
  sdas8051 (256 ajmp/acall that sdas8051 mis-encodes, 39 data-in-code).
  Converges in 3 iterations.

Also adds patch_usb1_to_cv.asm (the USB-1->CV patch as a standalone
sdas8051 source) and verify_patch_asm.py (proves the assembled patch
matches the bytes written to the device over C2).

Reproduce:  python3 roundtrip.py
2026-08-17 23:00:36 +02:00

QuNexus firmware v2.2.1 — reverse-engineered source

Disassembly and pseudo-decompilation of the Keith McMillen / KESUMO QuNexus application firmware (EFM8UB20F64G, an 8051-core MCU), produced with radare2.

This is a nested git repository (its own repo, living under the parent qunexus-qt6 project). It is self-contained: the stock input binary is included so every artifact here can be regenerated with one command.

Input

File What
QuNexus_Firmware_v2.2.1.bin stock v2.2.1 application image, flat binary, base 0x2400, 54 279 bytes, spans 0x2400–0xF806. (The bootloader region 0x0000–0x23FF is not in this image and is not decompiled here.)

Generated artifacts

File What Lines
qunexus_v2.2.1.asm Full linear disassembly of the whole image, absolute code-space addresses (0x2400+), with xrefs & comments. Nothing omitted. 37 287
pseudocode_all.c r2 pdc C-like pseudocode for all 1002 functions. 668 complete; 334 end with // chop (r2's size limit on big functions — see those in the asm). 38 396
functions.txt r2 function index: address size nblocks name — 1002 functions. 1 002

Reproduce

brew install radare2          # one-time
./regen.sh                    # rebuilds the three artifacts from the .bin

regen.sh runs r2 -a 8051 -b 8 -m 0x2400 -i r2script.r2 QuNexus_Firmware_v2.2.1.bin. r2script.r2 is the exact radare2 script used.

Faithfulness — does it re-compile?

The disassembly is byte-faithful: reconstructing the binary from the asm's byte columns (30 560 instructions + 195 small data gaps) reproduces the original 54 279-byte image byte-for-byte. So the listing is a complete, accurate representation — nothing lost or corrupted.

The r2 .asm file is not directly valid input to an 8051 assembler (it is r2 display format: function borders, xref comments, fcn.xxxx labels, address prefixes). To re-assemble into a flashable binary it must be converted to a real assembler's syntax (e.g. sdas8051 from SDCC, or Keil A51): strip r2 annotations, turn ljmp/lcall targets into labels, emit the 195 data gaps as .db, and set .org 0x2400. With that conversion it re-assembles to the identical binary. (Round-trip via SDCC is the natural next step if needed.)

Known landmarks (verified in the output)

Address Meaning
0x2400 app reset vector (ljmp 0xb691)
0xA57B USB-MIDI router: cable number → destination ring buffer
0xDF05 USB-MIDI event dispatcher
0xDF28 LCALL 0xA57B — the single call site the USB-1→CV patch retargets
0x8126 23-byte 0xFF padding region the patch's stub is written into
0xE8E6 LJMP 0x0000 — the only bootloader-entry jump
0xB48D SysEx command handler (bootloader-entry path)

Caveats

  • r2's 8051 auto-analysis is decent but imperfect: data-in-code regions decode as the matching instruction (a linear-sweep artifact). Use ljmp/lcall/ acall targets and functions.txt as the map of real code.
  • pdc chops ~1/3 of functions (the big ones). The asm listing covers them fully. For unchopped Ghidra-quality pseudocode, the r2ghidra plugin (r2pm install r2ghidra, then pdg @ func) is the upgrade path.

Provenance

Decompiled 2026-08-17 from the stock QuNexus_Firmware_v2.2.1.bin (the same image shipped in the qunexus-qt6 editor's Qt resources as QuNexus_Firmware_v2.2.1-cs512.syx). The reverse-engineering was done in service of a USB-1→CV routing patch (see ../patch_usb1_to_cv.py and ../c2probe/); this repo captures the reference disassembly of the unmodified firmware.

S
Description
No description provided
Readme
956 KiB
Languages
C 69.6%
Assembly 25.6%
Python 4.5%
CMake 0.3%