Reading and writing the firmware
Where the procedure comes from
The vendor’s Windows flashing tool is a script interpreter; its script (AdfuUpgrade) is stored inside every
firmware package. The a02-os project re-implemented it on Linux with actions_dump for an AGPTEK A02 and proved it
there. The X91 matches the A02 in what the procedure depends on: NAND chip (89 84 64 3c a5), flash driver
(fwscf651.bin, the same file) and firmware line (1.101.37). Every device-specific value (pin table, boot record
size, firmware size, the chip’s NAND record) is taken from this unit’s own boot record.
The sequence, each step run from RAM in upgrade mode:
Load the
adfusloader at 0x118000.Run the vendor hardware scan (
nandhwsc.bin); it returns the NAND ID and where the driver expects the chip’s record.Put the chip’s 128-byte record there, load the flash driver with a few header fields filled in, and call it; it must answer
NAFWSC.Read through the driver (storage type 0x80), or write the firmware area (0xc0) and finalise (0xff).
Read everything back and compare, before the player is restarted.
Only the firmware area is written; the boot records are never touched.
x91_flash.py
x91_dump/x91_flash.py runs the procedure and stops at the first unexpected answer.
Command |
Device |
Driver |
What it does |
|---|---|---|---|
|
no |
builds the driver files and runs nine cross-checks against the boot record and the dump |
|
|
yes |
read-only |
reads the whole firmware through the driver and compares it with the verified dump |
|
yes |
read-only |
reads the firmware area into F and checks its checksums |
|
yes |
stock |
saves the current firmware to B, writes F, finalises, reads back and compares; refuses without a passed rehearsal |
The read-only driver is the stock driver with a published two-instruction patch that makes its erase and program
routines return at once (sha256 9008981c…). The player must be in upgrade mode, entered with adfu_reboot from a
fresh power-on, with no other program run in that session.
x91_tool.py
The terminal interface (Python curses, no extra packages) drives x91_flash.py. It lists the images from
x91_dump/themes/themes.json, the original firmware and “Previous firmware” (the newest
x91_dump/backups/before_*.bin), checks the chosen image, asks for a typed YES, switches the player into upgrade
mode, saves a backup, writes, reads back and compares, and explains the outcome. It restarts itself through sudo and
hands the files it wrote back to the calling user. --demo runs the same screens against a simulated player.
The first dump
x91_flash.py needs a verified stock image to start from. On a new unit it is made through the boot area, without
the vendor driver:
Build
actions_dump_x91, upstreamactions_dumpwith our page-size scan patch, and thenandreadpayload:patch -o actions_flash/actions_dump_x91.c actions_flash/actions_dump.c patches/actions_flash-x91_scan.patch, thenmake -C actions_flash APPNAME=actions_dump_x91andmake -C actions_flash/payload_arm TOOLCHAIN=arm-none-eabi CC="arm-none-eabi-gcc -march=armv7-m -mthumb" NAME=nandread.read_brecreads the boot records (mbrec.bin,brec0.bin); they give the firmware size, 38,811 sectors.find_lfiwith the page-size overridelfi_geom 32 0x4000(the firmware area uses 16 KiB pages, the boot area 8 KiB) dumps both copies of the firmware. Do it twice.firmware_analysis/tools/lfi_verify.py READ1 READ2 38811 0x4000 x91_lfi_verified.bin unpacked/checks header, directory and every file in all four copies and assembles a verified image.
History of the write path
Date |
Step |
Result |
|---|---|---|
2026-10-13 |
Dump through the boot area ( |
six firmware blocks (main and mirror copy); two unstable pages of the main copy, inside |
2026-10-13 |
Rehearsal with the read-only driver |
0 of 38,811 sectors differ; the driver handles the four chip selects |
2026-10-13 |
First write: the unchanged image |
written, finalised, read back identical |
2026-10-13 |
Custom start-up picture |
read-back equals the image and differs from stock in 83 sectors: the write lands |
since then |
Every stage |
written and read back identical |
Risks that remain
A firmware that passes its checksums and starts, but then hangs, has no known way back (Hardware, boot and upgrade mode).
An interrupted write relies on the boot loader’s restore logic, read from code, not tested by breaking a unit; use a charged battery and a direct USB port.
The finalise step writes a marker for the settings store; the player comes back with default settings, and the built-in playlists are emptied. Music and files are not touched.