Hi James! I've been building DJUSB, a cross-platform app (macOS/Windows/Linux) that exports USB drives for Pioneer CDJ hardware. Your Kaitai Struct specs in crate-digger were the foundation for the PDB writer — thank you for all that reverse-engineering work.
During development and testing against a real CDJ, I discovered several format details that might be useful to document or add to the specs. Sharing them here in case they help others:
Page Header Packed Bitfield (offset 0x18–0x1A)
The 3 bytes at offset 0x18 in the page header encode a packed 24-bit value:
- Low 13 bits =
num_row_offsets (number of row offset entries at end of page)
- High 11 bits =
num_rows (actual row count)
This is different from treating them as separate fields. Getting this wrong causes the CDJ to silently skip the page.
INDEX (Strange) Page Requirements
For INDEX pages (page_flags = 0x64), these exact values are required or the CDJ ignores the table entirely:
free_size = 0
u5 = 0x1FFF
nrl (num_rows_large) = 0x1FFF
u6 = 0x03EC (1004)
Row Subtype Values
Track, artist, and album rows have mandatory subtype values. Wrong subtypes make rows invisible to the CDJ:
- Track: 0x0024
- Artist: 0x0060
- Album: 0x0080
DATA Page Bytes 0x19–0x1A Must Be Zero
CDJ firmware explicitly checks bytes at offset 0x19–0x1A in DATA pages. Non-zero values cause the page to be rejected. (This is within the packed bitfield region mentioned above.)
Header Offset 0x10
The unknown field at file header offset 0x10 must be 5 (verified against reference rekordbox export). Other values may cause rejection on some firmware versions.
20 Table Types Required
The directory at offset 0x1C must contain entries for all 20 table types (0–19), even if most are empty. Missing tables = CDJ ignores the entire file.
Track Row String Indices
Track rows have 21 string index slots starting at offset 0x5E. The mapping:
- 10 = date_added, 11 = release_date, 12 = mix_name
- 14 = analyze_path, 15 = analyze_date, 16 = comment
- 17 = title, 19 = filename, 20 = file_path
ANLZ Tag Ordering
ANLZ files (.DAT/.EXT) require the PPTH tag to be first after the PMAI header. Audio file path is encoded in UTF-16BE with null terminator. Moving PPTH to any other position causes the CDJ to not associate the analysis with the track.
All of this was verified against a CDJ with reference rekordbox PDB exports. The project has both a Swift PDB writer and a Rust PDB writer with 172+ tests if any of this is useful as reference implementations.
Happy to contribute PRs to the Kaitai specs or documentation if any of these findings would be welcome. Thanks again for making crate-digger — it made this project possible!
Hi James! I've been building DJUSB, a cross-platform app (macOS/Windows/Linux) that exports USB drives for Pioneer CDJ hardware. Your Kaitai Struct specs in crate-digger were the foundation for the PDB writer — thank you for all that reverse-engineering work.
During development and testing against a real CDJ, I discovered several format details that might be useful to document or add to the specs. Sharing them here in case they help others:
Page Header Packed Bitfield (offset 0x18–0x1A)
The 3 bytes at offset 0x18 in the page header encode a packed 24-bit value:
num_row_offsets(number of row offset entries at end of page)num_rows(actual row count)This is different from treating them as separate fields. Getting this wrong causes the CDJ to silently skip the page.
INDEX (Strange) Page Requirements
For INDEX pages (page_flags = 0x64), these exact values are required or the CDJ ignores the table entirely:
free_size= 0u5= 0x1FFFnrl(num_rows_large) = 0x1FFFu6= 0x03EC (1004)Row Subtype Values
Track, artist, and album rows have mandatory subtype values. Wrong subtypes make rows invisible to the CDJ:
DATA Page Bytes 0x19–0x1A Must Be Zero
CDJ firmware explicitly checks bytes at offset 0x19–0x1A in DATA pages. Non-zero values cause the page to be rejected. (This is within the packed bitfield region mentioned above.)
Header Offset 0x10
The unknown field at file header offset 0x10 must be 5 (verified against reference rekordbox export). Other values may cause rejection on some firmware versions.
20 Table Types Required
The directory at offset 0x1C must contain entries for all 20 table types (0–19), even if most are empty. Missing tables = CDJ ignores the entire file.
Track Row String Indices
Track rows have 21 string index slots starting at offset 0x5E. The mapping:
ANLZ Tag Ordering
ANLZ files (.DAT/.EXT) require the PPTH tag to be first after the PMAI header. Audio file path is encoded in UTF-16BE with null terminator. Moving PPTH to any other position causes the CDJ to not associate the analysis with the track.
All of this was verified against a CDJ with reference rekordbox PDB exports. The project has both a Swift PDB writer and a Rust PDB writer with 172+ tests if any of this is useful as reference implementations.
Happy to contribute PRs to the Kaitai specs or documentation if any of these findings would be welcome. Thanks again for making crate-digger — it made this project possible!