nils 5022b59934 USB-1->CV routing patch: mirror cable-0 events into the CV ring
The stock CV_Out_Source only offers Expander / USB-3, so MIDI arriving on
USB port 1 (Control Surface, cable 0) never reaches the CV engine. This
patch retargets the single USB-MIDI dispatch call at 0xDF28 from the
stock cable-number router (0xA57B) to a 23-byte stub written into the 0xFF
padding at 0x8126.

The stub runs the original router unchanged, then inspects the 4-byte
USB-MIDI event buffer at 0x0F9B: if the event came in on cable 0 (top
nibble == 0) it retags it as cable 2 (sets bit 0x20) and calls the router
again, so the event also lands in the USB-3 / CV ring. Events on any other
cable pass through untouched (one router call, as stock).

  0x8126:  23x 0xFF padding  ->  usb1_cv_stub (lcall/mov/movx/.../ret)
  0xDF28:  lcall 0xa57b       ->  lcall L_8126   (-> usb1_cv_stub)

Assembles byte-identical to the patched image (stock + stub + retarget),
matching what patch_c2.py writes to the device over C2. Verified with
`python3 roundtrip.py --patch` (PASS, 3 iterations).

Stock source is regenerable with:  python3 roundtrip.py
2026-08-17 23:00:50 +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%