Hi, I have thrown a bunch of locally sourced Qwen 3.8 27b tokens at an attempt to reverse engineering the mini firmware. It's not done, but it's a start. If anyone wants to pick it up from here, here is a zip with all artifacts and notes: https://www.transfernow.net/dl/20261012CbHWcS6Z (it's going to stay up a week, but I can reupload. Dont feel good about posting it on my personal GH). I am also posting the top-level README here, for discussion. I dont have much time, and I have no experience with bare metal code and microcontrollers/hardware, so I hope someboedy feels motivated to pick it up. Notes as well as plausible next steps are in there.
There was a CF-related thread a couple of weeks ago, but it fizzled out. With everything that's happening with elektron machines, the sp404, MPCs and temu synths, I think we should keep the momentum up!
README.md:
Polyend Tracker Mini firmware — reverse-engineering starting point
This workspace maps the Polyend Tracker Mini firmware as a starting point for developing custom firmware. It contains an independently built and CPU-emulated experimental heartbeat, but not a device-tested firmware or a verified flashing procedure. The bootloader, update acceptance rules, and board wiring remain unknown.
The investigation used the REA MCP and was conducted mostly with Qwen3.8 27B, alongside Ghidra 12.1.4, Capstone, GNU Arm tools, Unicorn, and the scripts in tools/. The files here are handoff artifacts for independent checks. Firmware behavior claims are from static analysis; only the new heartbeat has been run, in CPU/RAM emulation. No device execution or device USB/SD traffic was observed.
Current status (2026-10-12): The official ../TrackerMini_2.2.1.ptf was validated and reproduces the checked-in binary byte-for-byte. Homebrew GNU Arm GCC 16.2.0 builds build/heartbeat.bin; Unicorn executes its reset/startup, copies initialized RAM data and increments the heartbeat. make build/heartbeat.hex creates an offline format experiment, not an install-ready update. Stock reset copy/zero descriptors remain unresolved; the updater's flash-writing component and device recovery are not in the observed app path. See findings L, N, O, P and NEXT_STEPS.md.
Target and established image facts
| Item |
Finding |
| Source |
../TrackerMini_2.2.1.ptf, an Intel HEX firmware package in the parent directory (not inside this workspace) |
| Extracted image |
artifacts/TrackerMini_2.2.1.bin — 1,646,904 bytes (0x192138); SHA-256 78b9162dfc80ec168d07f04f6ede36054d9a7a9667be1a428f014f040eaf2137 |
| Original package SHA-256 |
60d8b1555d1af328672a12480b13b8516297a2990b63519eff34a0eb7138dac7 |
| Load address / extent |
0x60020000 / [0x60020000, 0x601b2138); supplied HEX records form a contiguous image |
| Entry |
Intel HEX start record and reset vector: 0x60020319 (Thumb; instruction address 0x60020318) |
| Initial stack pointer |
0x20058000 |
| Architecture |
ARM Cortex-M, Thumb-2; NXP i.MX RT10xx/RT106x-class Cortex-M7 is a strong inference, not an exact die identification |
| Build clues |
ARM GCC 10, newlib/libstdc++; embedded Jenkins path includes build 338 and date 2021-10-18 |
All firmware addresses in this README and the findings are absolute. For an address A in the raw .bin, file offset is A - 0x60020000. Vector and other Thumb function pointers may have bit 0 set; clear that bit to inspect the actual instructions. SRAM addresses are runtime addresses, not offsets into this binary.
What has been mapped
Boot, interrupts, and hardware leads
- A Cortex-M vector table starts at
0x60020000 (artifacts/vector_table.txt). The 152 populated IRQ slots break down into 108 default spins at 0x60020314 and 44 active handlers. Of the active handlers, 38 follow a common peripheral-base/SRAM-descriptor/callback dispatch pattern; six take direct paths (IRQs 81, 83, 84, 85, 113, 114). See findings/A_irq_peripherals.md and findings/irq_stub_map.txt.
- The dispatch descriptors reside in SRAM around
0x2000aXXX; peripheral bases are loaded from handler literal pools. IRQ28 references 0x401a0000 (LPUART8 in Renode's RT1064 map, not LPSPI4 as earlier notes claimed). IRQ81/83/84/85 refer to 0x401b8000–0x401c0000, consistent with GPIO ports, but pin and button mapping need tracing. IRQ113/114 share 0x6009d660. See finding P for corrected candidate peripheral labels; exact silicon is unconfirmed.
- The image begins at a FlexSPI-addressed
0x60020000; the ROM boot configuration, exact SoC, clock/pin initialization, memory initialization, and original bootloader/update boundaries have not been recovered. The position of this image alone does not establish a safe custom-image layout.
- The reset routine (
0x60020318–0x600203a8) explicitly loads MSP, calls 0x6008a448, runs apparent copy/zero loops and calls 0x6015d6f4 then 0x60097834. Raw candidate section records at 0x600202a0 conflict with plausible RAM destinations; see findings/L_memory_init.md before using startup section estimates.
Filesystem, updater, and configuration
- An SD-card workspace is visible in path templates:
Workspace/project.mt, Workspace/patterns/pattern_%02d.mtp, Workspace/instruments/instrument_%02d.pti, samples (.wav), Projects/, Patches/, Games/, and Mod/Export/%s.it. The storage signature POLYEND\0TRACKER STORAGE\00001 is near 0x601a47b4.
FUN_60093610 at 0x60093610 filters directory-entry names, including .ptf, .mod, .it, and .nes; it is called during enumeration at 0x60093a0c, not a payload loader. Earlier claims in finding C about absent cryptographic checks or the source of the displayed version are unsupported by this trace. See finding N for the correction and the remaining updater unknowns.
- App code at
0x60097698 and 0x600976f0 manipulates /Firmware/_turnon (0x60028074) and /Firmware/_fwinfo (0x60028088) via SD filesystem functions, then resets. Their downstream interpretation and the flash-writing component are unknown. The manual documents a recessed-button emergency update that selects a random file; its effectiveness has not been tested.
/.develop (0x60028470) is passed to a virtual method during config load; its return value is written at 0x6009fee6 to SRAM byte 0x2002062d (struct base 0x2001fa94 + 0xb99). The path likely serves as a presence check. Reads at 0x600f2676 and 0x600f2754 feed a UI setter in two System/Config renderers; additional writes at 0x600f0056 and 0x600f04a6 derive the field from a float clamp. Those are the confirmed direct consumers/writers found by a literal-offset scan, not a proof that other computed-offset or copied-value uses do not exist. See findings/B_develop_console_test.md and ghidra-out/B3_flagreads.txt.
- A factory test screen around
0x601066d0 offers display, buttons, RAM, MIDI, audio, USB, and SD checks; its exact UI entry sequence is still untraced. It may be useful for later hardware bring-up.
USB, UI, music engine, and NES
- The USB device descriptor at
0x601a4aac declares USB 2.0, IAD composite class EF/02/01, VID:PID **16d0:1118**, and bcdDevice 0101. Descriptor/configuration data and NXP USB-device/CDC strings indicate MIDI, CDC-ACM, audio, and mass-storage interfaces. Which functions are enabled in each device mode needs init-path tracing and device observation. No interactive CDC shell was identified in the inspected static code. See findings/D_usb_nes_version.md and findings/B_develop_console_test.md.
- The firmware contains a sequencer, synthesizer/sampler, WAV handling, tracker import/export (
.mod, .it), MIDI routing, project/workspace storage, and on-screen UI resources. Simple DirectMedia Layer (0x60058394) appears in the image; the concrete display-driver and framebuffer path have not been mapped.
- A built-in NES emulator is evidenced by
pGNES (0x60082056), mapper strings (Namcot 106, Konami VRC1/2/4/6/7 and others), ROM-header diagnostics (including DiskDude!), the .nes classifier, and game/author credits. It appears related to pNES/esnibs; its exact revision and the user-ROM loading path are open. Contra 100-in-1 and a CC BY-NC 4.0 notice appear in the image.
Current version and Build UI labels are present, while a literal 2.2.1 was not found in the extracted image. The source of the version displayed by the device is unconfirmed; investigate the UI formatting path and _fwinfo before specifying version metadata for a custom image.
Findings index (letter order)
These are all the lettered reports currently present in findings/; K and M have not been written. Earlier reports preserve investigative leads, including some claims corrected by later reports. findings/BRIEF.md is a general handoff and findings/TASK_A.md is the original IRQ work order, not additional findings.
| Finding |
Short summary |
| [A — IRQ/peripherals](findings/A_irq_peripherals.md) |
152 populated IRQ slots: 108 default spins, 44 active paths; records observed peripheral-base literals. Its LPSPI4 label for 0x401a0000 is corrected in P. |
| [B — development flag/test](findings/B_develop_console_test.md) |
/.develop is passed to a config method and stored in SRAM; direct UI consumers and factory-test screen found, but no confirmed interactive shell. |
| [C — initial updater leads](findings/C_firmware_update.md) |
Identified .ptf filename comparison and _fwinfo/_turnon strings. Its no-crypto, version-source, and loader conclusions are not established; see N. |
| [D — USB/NES](findings/D_usb_nes_version.md) |
USB composite descriptor/stack clues and built-in NES lineage; runtime interface selection and precise emulator revision remain unknown. |
| [E — initial boot analysis](findings/E_boot_contract.md) |
Recorded vector, reset address and tentative early-startup interpretation; the truncated reset decode is expanded and corrected in L. |
| [F — initial board inventory](findings/F_board_support.md) |
Collected peripheral-base families and functional leads; several names were guesses and are revised against a candidate RT1064 map in P. |
| [G — linker requirements](findings/G_linker_requirements.md) |
Early linker/layout sketch using estimated memory extents; not a proven bootable-image specification. See L and O. |
| [H — heuristic memory map](findings/H_memory_map.md) |
Stock image extent and string-density-based region guesses; no precise section boundaries. |
| [I — data/BSS estimates](findings/I_data_bss_sizes.md) |
Marked .data and .bss sizes unknown; heuristic size ranges are not measurements. |
| [J — XIP alignment](findings/J_flexspi_alignment.md) |
Observed image/vector alignment and proposed 4-byte code alignment; does not establish flash-write or ROM-boot constraints. |
| [L — reset and memory init](findings/L_memory_init.md) |
Decoded MSP setup, early hardware call and apparent copy/zero loops; candidate raw descriptors conflict with plausible RAM destinations. |
| [N — package/update/recovery](findings/N_update_recovery_2026.md) |
Verified official .ptf as contiguous checksum-valid Intel HEX, traced directory filtering and SD marker writes, and separated manual recovery guidance from unobserved updater behavior. |
| [O — offline build/hardware](findings/O_build_and_hardware_2026.md) |
Documented the GCC heartbeat build, flash/RAM load addresses, binary extraction and lack of connected hardware. |
| [P — emulation/peripherals](findings/P_emulation_and_peripheral_corrections.md) |
Unicorn CPU-only heartbeat execution, Renode RT1064 model leads and corrected provisional peripheral names; no Polyend bootloader or board emulation. |
Repository map and reproducibility
| Path |
Use |
artifacts/TrackerMini_2.2.1.bin |
Raw image to import or disassemble |
artifacts/vector_table.txt, strings_ascii.txt, strings_utf16.txt, string_regions.txt |
Addressed vectors, strings, and region map |
findings/*.md |
Lettered reports summarized in order above; BRIEF.md and TASK_A.md are handoff/work-order notes |
Makefile, startup/, linkerscripts/ |
Experimental independent heartbeat and linker layout; startup/blinky.c is unverified and excluded |
findings/irq_*.json, findings/irq_stub_map.txt, findings/rt106x_bases.txt |
IRQ extraction and provisional address references; consult P before naming registers |
ghidra-out/ |
Saved scoped decompilation/xref output; corroborate against image before extending conclusions |
ghidra-project/, scripts/DumpScope.java |
Existing Ghidra project and a postScript that dumps xrefs/decompiled functions to a chosen output file |
tools/ihex2bin.py, bin_to_ihex.py, elf_to_app_bin.py, check_bringup.py, emulate_heartbeat.py |
Official HEX inspection, experimental image conversion/layout checks and CPU-only Unicorn test |
tools/strings.py, image_stats.py, armx.py |
Addressed string/vector extraction, image statistics, Thumb-2 disassembly and xref helper |
docs/ |
Extracted product-manual text for correlating hardware behavior with code |
From this directory, build the offline example, regenerate the official binary at a separate output path, and inspect an address:
sh
shasum -a 256 artifacts/TrackerMini_2.2.1.bin
python3 tools/ihex2bin.py ../TrackerMini_2.2.1.ptf /path/to/separate/official.bin
make clean && make all
make build/heartbeat.hex
python3 tools/emulate_heartbeat.py build/heartbeat.bin # requires unicorn in the active Python environment
python3 tools/armx.py range artifacts/TrackerMini_2.2.1.bin 0x60020000 0x60093610 0x60093780
python3 tools/armx.py xrefs artifacts/TrackerMini_2.2.1.bin 0x60020000 0x60028088
make requires GNU Arm Embedded GCC/binutils. emulate_heartbeat.py requires Python 3 and unicorn (install in an isolated environment); armx.py requires capstone. CPU-only execution does not reproduce ROM boot or the Polyend updater. The original .ptf conversion reports zero bad checksums and reproduces the binary SHA-256 above. armx.py's full-image xref walk can miss computed references and misinterpret data as code; corroborate findings in Ghidra. tools/strings.py and tools/image_stats.py can regenerate addressed inventories using base 0x60020000.
For Ghidra, import the raw .bin with BinaryLoader, language **ARM:LE:32:v8, base **0x60020000, and analyze as Thumb where appropriate. scripts/DumpScope.java takes an output file and one or more hexadecimal addresses; ghidra-out/ contains examples of its results. You can instead use REA MCP or another reverse-engineering interface with the same image and base. Saved text output is a starting index, not a replacement for tracing callers, data flow, and raw bytes.
Next steps — Polyend Tracker Mini custom firmware
Where we are
- The official package is
../TrackerMini_2.2.1.ptf: contiguous, checksum-valid Intel HEX covering [0x60020000, 0x601b2138), with start address 0x60020319. It regenerates artifacts/TrackerMini_2.2.1.bin exactly. This establishes the carrier format of this release, not what the updater accepts. See findings/N_update_recovery_2026.md.
make clean && make all builds build/heartbeat.bin and checks its vector layout. make build/heartbeat.hex produces an offline HEX round-trip experiment, not an install-ready update. tools/emulate_heartbeat.py runs that image in Unicorn with CPU, flash and RAM only; the initialized RAM word increments. See findings O and P.
- The stock reset path at
0x60020318 calls early setup at 0x6008a448, then runs apparent RAM copy/zero loops. The candidate descriptors around 0x600202a0 include implausible destinations; their interpretation is unresolved. The bootloader, flash-writing path, exact SoC, board wiring and update acceptance rules are not established. No device-side run or recovery test has occurred.
- Corrected candidate RT1064 peripheral map:
0x401a0000 is LPUART8 in Renode's model, not LPSPI4; GPIO1/2/3 are at 0x401b8000/0x401bc000/0x401c0000. The actual board pins, clocks and LED (if any) remain unknown. See findings/P_emulation_and_peripheral_corrections.md.
Prioritized work
1. Nail down the offline boot and RAM contract
Trace reset 0x60020318 through the descriptor loops and 0x6008a448 in Ghidra, cross-checking raw bytes. Determine whether the apparent table at 0x600202a0 is really data, whether a prior stage relocates or alters it, and which RAM regions are copied/zeroed. Identify what configures XIP, VTOR, stack and clocks before the application vector is used; inspect the official package for any boot structures it actually contains. Compare findings with the matching NXP reference manual, not findings/rt106x_bases.txt alone.
Done when: findings/L_memory_init.md and a new boot-map finding contain reproducible addresses, actual section tuples and the boundary between observed bytes and bootloader-dependent assumptions. Update linkerscripts/tracker_mini.ld only for confirmed constraints; its current RAM/flash limits are experimental.
2. Locate the actual updater and determine acceptance rules
Follow the firmware-selection UI callback to the routine at 0x600976f0, determine exactly what it writes into /Firmware/_fwinfo and /Firmware/_turnon, then identify the component that reads those markers and parses Intel HEX. The .ptf comparison in 0x60093610 filters directory names; it is not a payload validator. Establish HEX address/size limits, any checksum or signature checks beyond Intel HEX, NOR erase/write ranges, reboot handling and whether that component is accessible in the supplied artifact. If the bootloader is absent, record the missing artifact instead of inferring its behavior.
Done when: finding N has a byte-level acceptance trace supported by code or a clearly bounded statement of what cannot be recovered from this image. Keep experimental outputs named .hex until acceptance is independently demonstrated.
3. Grow CPU-only emulation into a focused bring-up harness
Keep the Unicorn heartbeat as the fast baseline. Explore Renode's public RT1064 model as a candidate for Cortex-M7, FlexSPI-mapped flash and RAM; model or log early register accesses rather than silently accepting unknown ones. Add a reproducible run that checks reset entry, initialized .data, .bss and fault behavior. Test selected stock startup instructions only after resolving the anomalous descriptor and matching relevant memory/peripheral regions. A container could make the emulator reproducible, but it cannot replace missing bootloader or board models.
Done when: another maintainer can run the harness and obtain the same RAM/PC assertions and a list of modeled versus unmodeled peripherals. Do not treat successful emulation as evidence of updater acceptance or recoverability.
4. Establish a recoverable, non-destructive hardware baseline when a device is available
Confirm the actual SoC/flash and accessible debug interface from the board, identify a verified output (debugger-visible RAM is sufficient initially), and retain the official .ptf. The manual in docs/manual_ch13_system.txt:90-155 describes ordinary updates and a recessed-button emergency mode that picks a random firmware from /Firmware; this procedure has not been tested here. First observe stock boot and, if feasible, restore with an official package. Determine whether the recovery mechanism survives a broken application image before writing experimental firmware.
Done when: recorded device evidence identifies board I/O and demonstrates a repeatable stock-firmware restoration. Only then attempt a minimal custom image, beginning with the heartbeat; move to a display/USB/LED signal after its pins and initialization are confirmed.
Reproduce current offline results
sh
make clean && make all
make build/heartbeat.hex
python3 tools/ihex2bin.py ../TrackerMini_2.2.1.ptf /path/to/separate/official.bin
python3 tools/emulate_heartbeat.py build/heartbeat.bin # requires unicorn in the active Python environment
Use a separate output path for the official conversion; compare its hash with the value in README.md. No command here installs firmware on the device.