Announcing Project Waterjet
Regardless of what's your take on Apple, they do make products that are beautiful. Beauty in design, beauty in simplicity. As I am typing this on my Macbook, I see crisp fonts, I see gorgeous icons.
Now, mass-produced gadgets from China usually lack that design fine-tuning even when the hardware is amazing.
Starting from serif fonts which make your 24-bit FLAC-playing DAP look like it is a typewriter from 90s, to the hodgepodge of icons and backgrounds.
Usually these devices do not support customer theming, but we are going to change this a bit with Waterjet.
In the coming months we will be releasing docs and tools allowing decrypting, unpacking, updating, and re-packing firmware resources for devices running on Actions Semiconductor ATJ212X, ATJ215X, and others that use μC/OS-based SDK, allowing everytone to personalize their devices without the need for SDK from Actions.
And to the vendors who ship these devices — you will have a better customer experience if you run the fonts and designs past a designer, then we would not need to do all this.
And to start us up, here's the format of FWIMAGE.FW for ATJ212X devices.
Actions Semiconductor FWIMAGE.FW Specification
1. File Structure
The firmware image is a sector-based container (512 bytes per sector) with a fixed-size header area of 16 sectors (8192 bytes).
| Section | Size | Description |
|---|---|---|
| Global Header | 512 bytes | Basic metadata (Magic, VID/PID, Ver) |
| LDIR Table | 240 * 32 bytes | Fixed-size Logical Directory entries for all files |
| Component Data | Variable | Raw binary data for drivers, APs, and STY files |
2. Global Header (Sector 0)
The first 512 bytes contain the system metadata.
| Offset | Size | Description |
|---|---|---|
| 0x00 | 4 | Magic: 0x0FF0AA55 |
| 0x04 | 4 | SDK Version (ASCII) |
| 0x08 | 4 | Firmware Version (ASCII) |
| 0x0C | 2 | Vendor ID (VID) |
| 0x0E | 2 | Product ID (PID) |
| 0x10 | 4 | LDIR Checksum (Stride 4) |
| 0x50 | 48 | USB Setup Info (ASCII) |
| 0x80 | 336 | SDK Description (ASCII) |
| 0x1FA | 4 | R3 Config Sector Offset (Pointer to DEVINFO.BIN) |
| 0x1FE | 2 | Global Header Checksum (Sum of first 510 bytes) |
3. Logical Directory (LDIR) Table
Starting at offset 0x200 (Sector 1) and ending at 0x2000 (Sector 16). This is a static table of exactly 240 entries. Unused entries are null-padded.
| Offset | Size | Description |
|---|---|---|
| 0x00 | 8 | Filename (8.3 format, space padded) |
| 0x08 | 3 | Extension (ASCII) |
| 0x0B | 5 | Padding |
| 0x10 | 4 | Sector Offset: Start position in sectors (absolute position = offset * 512) |
| 0x14 | 4 | File Size: Size in bytes |
| 0x18 | 4 | Reserved |
| 0x1C | 4 | File Checksum (Stride 4 sums) |
4. Checksums
Global Header Checksum
The last two bytes of the Sector 0 header (offset 0x1FE) contain a 16-bit checksum of the first 510 bytes using a 2-byte stride.
uint16_t calculate_header_checksum(const uint8_t *data, size_t len) {
uint16_t sum = 0;
for (size_t i = 0; i < len; i += 2) {
uint16_t val = (uint16_t)data[i] | ((uint16_t)data[i+1] << 8);
sum += val;
}
return sum;
}
LDIR & File Checksum Algorithm (Stride 4)
Accumulates 32-bit words interpretated as little-endian. The sum naturally wraps at 32 bits.
#include <stdint.h>
#include <stddef.h>
/**
* Calculates the Actions Stride-4 checksum.
* @param data Pointer to the buffer (must be 4-byte aligned for some platforms)
* @param len Length of data in bytes (should be multiple of 4)
* @return 32-bit unsigned checksum
*/
uint32_t calculate_checksum_s4(const uint8_t *data, size_t len) {
uint32_t sum = 0;
for (size_t i = 0; i < len; i += 4) {
uint32_t val = (uint32_t)data[i] |
((uint32_t)data[i+1] << 8) |
((uint32_t)data[i+2] << 16) |
((uint32_t)data[i+3] << 24);
sum += val;
}
return sum;
}
Sector Alignment
Every file within the image must start on a 512-byte boundary. When packing, files must be padded with null bytes to reach the next sector.
Boot Sequence
The firmware expects KERNEL.DRV and CONFIG.BIN to be present at specific LDIR indices or offsets defined by bootloader. Just put them at the same location as where you took them.
Interested in the format of ATJ215X firmware? It is an encrypted sqlite3 database. And encryption has already been reverse-engineered — see rockbox sources for atjboottool.