36 KiB
export.pdb value-construction rules (RE findings)
This file records the exact rules rekordbox uses to populate export.pdb
fields that maple currently writes wrong or as zero. Each section is a
self-contained spec for the maple writer to implement. Evidence is from
reverse-engineering rekordbox.exe (DeviceSQL "Page Manager", dsql* /
edb_dyn_*) in Ghidra plus byte-level analysis of the reference export
maple-py/data/acemo/PIONEER/rekordbox/export.pdb (6 tracks, 1 album,
1 artist, 1 key, 6 artwork).
Reference-pdb facts used throughout:
len_page= 4096.- Row index grows backward from end of page; each group spans 36 (0x24) bytes:
group_base = len_page - group_index*36. Within a group,present_flags(u2) live atgroup_base-4; the row offset for sloti(0..15) is the u2 atgroup_base-(6+2*i); offsets are relative to the heap start (0x28, i.e. end of the 0x28-byte page header).
1. Track row index_shift (offset 0x02, u2) — SOLVED
Field: TrackRow.index_shift (also present on artist/album/genre/label/
key rows — every row type whose layout begins subtype:u2, index_shift:u2).
Rule:
index_shift = slot_index * 0x20 # i.e. slot_index << 5
where slot_index is the row's 0-based position in the page row index
(group_index*16 + slot_within_group), NOT its ordinal among present rows and
NOT its position in the heap. It resets to 0 at the start of every data page.
Key consequences for the writer:
- The first row index slot (slot 0) gets
index_shift = 0. - Deleted/absent slots still consume a slot number, so a gap in
present_flagsproduces a gap inindex_shift. (See evidence: the AceMo page has slot 5 deleted, so the row in slot 6 hasindex_shift = 0xc0, skipping0xa0.) index_shiftis purely a function of the row's slot, so the value is fully reproducible from the page layout maple already controls — there is no external/runtime input. If maple writes each track to sloti, setindex_shift = i*0x20.
Evidence:
- Reference AceMo
trackspage (page 2): present slots are 1,2,3,4,6 (slots 0 and 5 are deleted), withindex_shift= 0x20,0x40,0x60,0x80,0xc0 respectively. Overflow page (page 48) has one row in slot 1 →index_shift = 0x20. The 0x80→0xc0 jump across the deleted slot 5 proves the value tracksslot_index, not present-count. - Cross-checked against
artists(page 5) andalbums(page 7) rows: every row satisfiesindex_shift == slot_index*0x20with 0 mismatches over all present rows in the reference file (maple-py/re/scripts-style raw parse, 8 rows checked, see RE notes). - Ghidra: the value is assigned inside the DeviceSQL Page Manager record-insert
path, which is reached only through vtable dispatch
(
(**(code**)(*obj + 0x4d0))(obj,...)etc. inFUN_141b8b4c0/FUN_141cac390), so there is no single statically-anchoredindex_shift = slot<<5instruction to cite; a constant-scan forshl ,5paired with the row-index masks (0x1fff/0x7ff) and stride 0x24 produced only CRT/printf("%a")false positives (FUN_14232a86c). The empirical rule above is exact and zero-mismatch, so confidence is high on the formula; confidence on "no hidden runtime input" is high because the value is fully explained by slot position alone.
Confidence: high.
2. Track row 0x14 / 0x18 / 0x1a (opaque id blob) — SOLVED (stored value, not a formula)
Fields: TrackRow.opaque_track_id_lo (u4 @0x14), TrackRow.opaque_seed_lo
(u2 @0x18), TrackRow.opaque_seed_hi (u2 @0x1a). Together these are one
8-byte little-endian value:
blob_u64 = opaque_track_id_lo | (opaque_seed << 32)
opaque_seed = opaque_seed_lo | (opaque_seed_hi << 16) # u4 at offset 0x18
(@flesniak's schema labels these "Some ID? unknown", "always 19048?", "always 30967?". The "19048"/"30967" = 0x4A68/0x78F7 are only correct for some older exports; they are NOT universal — see below.)
What they are (proven empirically across 5 GENUINE rekordbox exports):
NOTE (2026-06-09 correction): a prior draft included a 5th row "usb (176), seq=1, seed 0x78f74a68, lo all 0" and concluded rekordbox writes lo=0 / seed 0x78f74a68 for unanalyzed tracks. That file (
data/usb/.../export.pdb) is maple's OWN output, not a rekordbox export (it is the compare_pdb.py "ours" path). Reading it back was circular; that claim is WRONG and has been dropped. Provenance rule: maple output = seed0x78f74a68+ ALL per-track lo == 0; genuine rekordbox = per-install nonzero seed + nonzero per-track lo.
| export (tracks) | header.seq | opaque_seed | opaque_track_id_lo |
|---|---|---|---|
| acemo (6) | 353 | 0x21b4a7ab |
6/6 nonzero |
| acemo_fresh (7, same lib) | 35 | 0x21b4a7ab |
7/7 nonzero |
| lib_multi (10, same lib) | 56 | 0x21b4a7ab |
10/10 nonzero |
| MAPLE (176, install B) | 546 | 0xd6d3a567 |
176/176 nonzero |
| data/PIONEER (847, inst. B) | 4211 | 0xd6d3a567 |
847/847 nonzero |
Across all 1046 real tracks in 5 genuine exports: 100% nonzero per-track lo,
exactly one nonzero per-install seed per file. There is NO evidence rekordbox
ever writes lo=0 or seed 0x78f74a68. The lo values span nearly the full
28-bit space (0x0003edeb..0x0ffe7f4e).
Findings:
opaque_seedis constant within a single export (exactly one value per file) and is per-rekordbox-library/install: acemo/acemo_fresh/lib_multi are three exports of the same library and all share0x21b4a7ab; the separate install behind MAPLE + data/PIONEER uses0xd6d3a567. (The old maple hardcode0xa567/0xd6d3=0xd6d3a567was correct only for install B.) The seed is NOT a binary constant (proven: no0xa7ab/0x21b4/0xd6d3/0xa567immediate or data-byte sequence in rekordbox.exe), so it is derived at runtime from the per-library DB (most likely a hash/truncation of the master.db's per-library UUID or creation marker). maple cannot reproduce a specific install's seed (see recommendation).opaque_track_id_lois a stable per-track value within a library. Every track that appears in both the acemo and acemo_fresh exports has an identical lo (matched by title): "All My Life"→0x07ff5a56, "Smooth Move"→0x0a20de34, "Jelly Fish Jam"→0x0f8fd598, etc. It is NOT per-export-random.- It is NOT derivable from the track's file metadata. Tested and ruled out
(no match, 28-bit and full, utf-8/utf-16, several hash funcs): crc32 / adler32
/ md5 / sha1 / fnv1 / fnv1a / djb2 of the title, filename, full
/Contents/...path, and crc32 of the ANLZ.DAT/.EXTand the.mp3bytes. Also not the USBANLZ folder id, not sequential, not evenly spaced. - The values are runtime-derived, not binary constants. A full
instruction-immediate scan of
rekordbox.exefound NO occurrence of either real seed (0x21b4a7ab,0xd6d3a567), the per-track lo values, or the track-rowbitmaskconstant0xc0700. So these come from rekordbox's internal database (master.db /djmdContent+ a per-library seed), copied into the row by the DeviceSQL record builder (vtable-dispatched, not statically anchorable). (0x78f74a68does not appear either — and per the correction above it was never a real rekordbox value here, just maple's placeholder.)
Round-2 evidence (2026-06-09, after team-lead supplied acemo_fresh and asked
to chase an audio-content hash):
5. Audio-content-hash hypothesis: DISPROVEN. Hashed the actual .mp3
bytes in 8 framings — full file, ID3v2-stripped (audio frames only),
first 1k/2k/4k/64k, last 64k, ID3-stripped-first-64k — each with crc32,
adler32, md5 (lo/hi, LE+BE), sha1 (LE+BE), sha256, fnv1a, both full-32 and
masked-to-28-bit. NONE matched any track's opaque_track_id_lo. So it is
not a hash of the audio file content (at least not of the file bytes; a hash
of decoded PCM cannot be ruled out but there is no evidence for it and it is
not reproducible anyway).
6. Source DB confirmed, and it is ENCRYPTED. Both exportLibrary.db (next
to the pdb) and the captured master.db
(maple-py/captures/p17328_master.db) fail to open as plain SQLite
("file is not a database") — rekordbox 6/7 encrypts them with SQLCipher.
The opaque_track_id_lo originates there; it cannot be read without the key.
7. The value is a pure stored field, used nowhere else. It appears in the
export.pdb ONLY at each track row's own offset 0x14 (present and stale/deleted
rows alike); it does NOT appear in exportExt.pdb (no tag_tracks/ContentID
link), nor in the ANLZ .DAT/.EXT (searched LE+BE), nor does it equal the
USBANLZ folder id. So it is not a foreign key the player resolves.
8. Range note (suggestive, not conclusive): across the 5 real exports the
lo values span nearly the full 28-bit space (0x0003edeb..0x0ffe7f4e),
the band of rekordbox-6/7 9-digit djmdContent.ID integers, so the most
likely identity is "the rekordbox internal Content ID" — a stored
random/sequential DB id, NOT a content-derived hash. Consistent with every
other finding and with it being unreadable (encrypted DB).
Conclusion: this 8-byte blob is a stored rekordbox-internal id (most likely the encrypted-DB Content ID), not a function of the track metadata or audio bytes maple has. It cannot be reproduced byte-for-byte for an arbitrary track without rekordbox's own (encrypted) DB. The audio-content-hash idea is ruled out. There is no "unanalyzed/zero" form in any real export — every real track carries a nonzero seed and a nonzero lo.
Recommendation for maple's writer:
- Players/crate-digger treat this blob as opaque (never decoded) and play fine regardless — so any value works for playback.
- For maximum faithfulness to genuine rekordbox output (which never has
lo=0/seed
0x78f74a68), maple should write a nonzero per-install seed and a nonzero, stable per-track lo:opaque_seed: use a real per-install seed. If maple has no install of its own,0x21b4a7ab(this RE machine's install) is a known-real value; keep it constant across all tracks in one export. Do NOT use0x78f74a68(that was maple's own placeholder, not a rekordbox value).opaque_track_id_lo: a nonzero 28-bit value, stable per track. Since the real value is an opaque DB id maple can't recover, a deterministic stand-in (e.g. a 28-bit hash of a stable per-track key, or the track's own pdb id offset into the ContentID band) is fine — the goal is "looks like a real nonzero ContentID", not byte-parity.
- Either way these 8 bytes will NOT match a specific reference export byte-for-byte (the real ids live in that install's encrypted DB) — record the divergence in re/KNOWN_DIVERGENCE.md.
Confidence: high (5-export empirical proof: 1046/1046 real tracks nonzero; exhaustive negative hash search incl. audio bytes; binary const-absence). The earlier "write 0 / 0x78f74a68" advice was based on a maple-output file and is retracted.
Round-3 (task #10): can pyrekordbox read the real ids out of master.db? User insight: pyrekordbox holds the SQLCipher key, so the DBs are not truly unreadable. Validated — partly true, but blocked on the data we have:
- pyrekordbox 0.4.4 +
sqlcipher3-wheelsinstalls and wires up correctly. Its master.db key is the single global rekordbox-6 key402fd482c38817c35ffa8ffb8c7d93143b749e7d315df7a81732a1ff43608497(deobfuscate(BLOB), must start with402fd) — same for every install, NOT per-install. So a VALID master.db would open and exposeDjmdContent. - BUT the captured
maple-py/captures/p17328_master.dbis corrupt/truncated: 1,024,000 bytes but only 4.4% non-zero, and the first 32 KB (the mandatory SQLCipher salt + page 1) is all zeros. No key can decrypt a DB with a zeroed salt.p17328_master.backup.dbis 0 bytes. The Frida NtWriteFile capture only caught scattered later pages, never the header. → unusable. - The genuine
exportLibrary.dbshipped next to export.pdb (valid 16-byte salted0d3a3c…) does NOT open with the global master.db key (different key scheme) and pyrekordbox has no exportLibrary.db support. → can't read it here. - Strong inference (untested for lack of a readable DB): the per-track
opaque_track_id_lois almost certainlyDjmdContent.ID(the 9-digit rekordbox Content ID; our lo range 0x0003edeb..0x0ffe7f4e fits). If maple can open the user's LIVE%APPDATA%/Pioneer/rekordbox6/master.dbwith pyrekordbox (global key),db.get_content()would give(ID, …)per track, and the blob would become EXACTLY reproducible:opaque_track_id_lo = DjmdContent.ID & 0xFFFFFFFF. - The per-install
opaque_seed(0x21b4a7ab here) is NOT a DjmdContent column; it is one value for the whole export and most likely derived from a master.db-level per-library marker (a USB/library UUID or anagentRegistry/local_usn-style value). Identifying it needs a readable master.db to inspect candidate columns; unconfirmed.
pyrekordbox write / export capability (asked in #10):
- It CAN write the desktop master.db:
add_content/add_album/add_artist/create_playlist/commit, plusset_local_usn,update_content_path. Butadd_contentlets rekordbox ASSIGN the Content ID; there is no API to force a specific ID. So it is a source of real ids, not a way to dictate them. - It does NOT drive a USB export — only
get_device(read). The actualexport.pdb/exportLibrary.dbwriting is rekordbox.exe's DeviceSQL engine (the thing maple reimplements). pyrekordbox manipulates only the desktop library DB, not the USB export. So pyrekordbox cannot replace maple's writer; its only use to maple is as a READ source of the realDjmdContent.ID(and potentially the seed) from a valid master.db.
Net for #10: the approach is sound (pyrekordbox + global key reads a real
master.db → Content IDs), so opaque_track_id_lo is likely reproducible if
maple reads the live master.db. It is NOT reproducible from the corrupt
captured DB we have, and the seed source is still unidentified. pyrekordbox
cannot produce the USB pdb itself. Recommended next step for maple/RE: point
pyrekordbox at the user's LIVE rekordbox6 master.db
(%APPDATA%/Pioneer/rekordbox6/master.db), confirm
DjmdContent.ID == opaque_track_id_lo for the AceMo tracks, then hunt the seed
in that DB. Until then, the nonzero stand-in (above) stays.
3. File-header sequence (and per-page sequence)
Field: RekordboxPdb.sequence (u4 @0x14 of the file header) and
Page.sequence (u4 in each page header).
Rule / meaning (confirmed from schema docs + the 5 genuine exports above):
sequenceis a monotonically increasing edit counter for the database. The file-header value is the next page-sequence number; each page records the counter value as of its last edit (soheader.sequence >= max(page.sequence)). Observed header values in genuine rekordbox exports: 35, 56, 353, 546, 4211 — scales with edit history, not track count. (No real export with sequence==1 is available;data/usb's seq==1 is maple's own output, not a rekordbox sample — see §2 correction.)- The firmware/players (and crate-digger) use page
sequenceonly as informational ordering metadata; they do not validate it for playback. No special value is required beyond keepingheader.sequencegreater than any page'ssequence. - For maple it is enough to (a) pick a header
sequencelarger than every pagesequenceit writes, and (b) give each page asequence<= header. A simple scheme (all pages = N, header = N+1) is accepted. This is what task #3 (incrementing sequence) implements.
Confidence: high for semantics and "not validated by players"; the exact increment rekordbox uses per edit is not needed for a valid export.
4. File-header offset 0x10 (u4, schema _unnamed4) — HISTORY-DEPENDENT; not byte-reproducible
CORRECTION (2026-06-09): a prior draft cited a "usb fresh, sequence==1, _unnamed4==0" row as proof that 0 is the fresh value. That file is maple's own output, not a rekordbox export, so it is removed below. The remaining proof (same-library divergence) does not depend on it.
Field: the unnamed u4 between next_unused_page (0x0c) and sequence
(0x14). maple writes 0; the AceMo ref has 4.
Finding (5 genuine rekordbox exports): an edit/allocator accumulator,
not a structural value derivable from the tables. It does NOT equal track count,
page count, or next_unused_page - npages:
| export | sequence | _unnamed4@0x10 | next_unused | npages |
|---|---|---|---|---|
| acemo_fresh | 35 | 5 | 52 | 49 |
| lib_multi | 56 | 4 | 57 | 52 |
| acemo | 353 | 4 | 53 | 49 |
| MAPLE | 546 | 5 | — | — |
| data/PIONEER | 4211 | 1 | 174 | 174 |
Decisive (same-library proof): acemo and acemo_fresh are the SAME library
(identical track set + identical per-track opaque ids) yet have _unnamed4 =
4 vs 5. A structural field would be identical; differing proves it is edit
state. (We have NO genuine rekordbox export with sequence==1, so the exact
never-edited value is unconfirmed — but it is clearly not derivable from the
file's structure.)
Rule for maple: there is no reproducible rule. Write 0 (maple already
does) as a reasonable minimal default — NOT a confirmed match (every genuine
export we have is nonzero here, and we have no never-edited rekordbox export to
check what fresh rekordbox writes). Record the divergence in
re/KNOWN_DIVERGENCE.md. It cannot be matched to the AceMo ref's 4 without that
DB's edit history.
Confidence: high that it is edit-history/allocator state and not structurally reproducible; the specific fresh-write value is unconfirmed (no seq==1 rekordbox sample available).
5. Page-header offset 0x26 (u2, "unknown7"/strange-page) — HISTORY-DEPENDENT; maple's 0 is a reasonable default
CORRECTION (2026-06-09): do NOT cite
data/usb(sequence==1) here — it is maple's OWN output, so "maple's 0 matches usb" would be circular. The observations below are from the five GENUINE rekordbox exports only.
Field: the second of the two trailing u2s in the page header (after
transaction_row_index @0x22 and the "1004 for strange blocks" u2 @0x24).
@flesniak: "always 0 except 1 for history pages, num entries for strange
pages?". maple writes 0; the AceMo ref has 0/1/2 on various tables' first
("strange") pages.
Finding (5 genuine rekordbox exports — all incrementally edited):
- On DATA pages it is always 0 (all five exports). This is a solid observation about genuine rekordbox output.
- It is nonzero only on a table's first/"strange" page (flags 0x64), and the value does NOT correlate with that table's data-page count or row count (acemo tracks: 2 data pages → 2; data/PIONEER tracks: 105 data pages → 23). It is transaction/edit-tracking state.
- Decisive (same-library proof): acemo and acemo_fresh hold the identical tracks table, yet its strange-page unknown7 = 2 (acemo) vs 1 (acemo_fresh). Same data → different value ⇒ not structural, it is edit state. (lib_multi: tracks strange unk7 = 2 with 3 data pages / 10 rows — again no count relation.)
Rule for maple: write 0 on every page (maple already does). The data pages being 0 across all five genuine exports makes 0 a well-supported value for data pages. For strange pages the real value is per-export edit state (0/1/2…) that cannot be reproduced — maple's 0 there is a reasonable minimal default, NOT a confirmed match (we have no never-edited rekordbox export to check what fresh rekordbox stamps on a strange page). Record the strange-page divergence in re/KNOWN_DIVERGENCE.md.
Confidence: high that it is history-dependent and that data-page 0 matches real rekordbox; the fresh-write strange-page value is unconfirmed (no seq==1 rekordbox sample).
6. Row-group trailing u2 (transaction_row_flags) — write present_flags (NOT 16); residual mismatch is edit history
CORRECTION (2026-06-09): an earlier draft of this section claimed "a fresh export writes 16 everywhere, so maple's 16 is correct." That was based on
data/usb/.../export.pdb, which on re-check is maple's OWN output, not a rekordbox export (opaque seed0x78f74a68+ all-zero per-track lo +sequence==low). Reading it back was circular. The analysis below uses ONLY the five genuine rekordbox exports (provenance verified by nonzero per-track opaque ids + per-install seed): acemo, acemo_fresh, lib_multi, MAPLE, data/PIONEER.
Field: the last u2 of each 36-byte row-group block in the page row index
(crate-digger transaction_row_flags, at base; in maple's pager.py it is
pack("<H", 16) # unknown (rekordcrate default)).
Finding (5 genuine rekordbox exports): this u2 is the
"rows touched by the last transaction" bitmask (same bit layout as
row_present_flags), and is therefore edit-history state, not reproducible:
- No genuine rekordbox export writes 16 as the field's value on settled pages (count of groups == 16 across the five exports: 0, 0, 0, 1, 8 — and those few are coincidental transaction bitmasks, not a default). So maple's constant 16 does NOT match rekordbox — it is a rekordcrate invention.
- The dominant value in heavily-edited tables is 0x0 (last transaction touched no rows on that page), e.g. data/PIONEER 170/294 groups = 0x0.
- Three DIFFERENT patterns coexist in ONE file, proving it is not structural:
(a) some pages
trailing == present_flags(columns/unk17/unk18); (b) artwork pages consistently uselast group = 1<<(rows_in_last_group-1), earlier groups = 0 (data/PIONEER p28 g6 10rows→0x200, p147 g2 13rows→0x1000; acemo_fresh artwork 7rows→0x40); (c) churned tracks pages use an arbitrary single bit (0x1/0x2/0x4/0x8/0x10/0x40…) = whatever row that page's last edit touched. A candidate clean rule[last grp=1<<(rows-1), others=0]matches only 181/257 fully-present pages in data/PIONEER — confirming NO single formula. - Same-library proof: acemo and acemo_fresh hold identical data, yet the
tracks page-2 group differs (acemo present=0x5e trailing=0x20; acemo_fresh
present=0xbb trailing=0x4) — same rows, different value ⇒ not structural.
The previously-flagged artwork
0x40is likewise transaction state (acemo_fresh artwork=0x40 vs lib_multi artwork p49=0x4), not an open reproducible byte.
Rule for maple (DECISION): write present_flags, NOT the constant 16.
maple's old constant 16 (rekordcrate's placeholder) matches 0/14 real groups;
trailing = row_present_flags matches 12/14 clean groups in the real
data/acemo_fresh export (8 rows→0xff, 16→0xffff, 11→0x7ff, 6→0x3f, 1→0x1). The
2 misses (page2→0x4, page28→0x40, page40→0xc0) are the last-transaction bitmask
on pages that were edited after creation — i.e. residual edit history, not a
different rule. So for a from-scratch single-transaction write the correct value
IS present_flags (all rows touched in the one creating transaction). maple now
ships present_flags (pager.py) — this is the best achievable; reverting to 16
would regress 12 groups. Remaining mismatch vs an EDITED reference (the per-page
last-tx bitmask) is unavoidable edit history → KNOWN_DIVERGENCE.
NOTE: the earlier "keep 16 / fresh usb writes 0x10 everywhere" framing was WRONG — it read
data/usb, which is MAPLE'S OWN OUTPUT (an old build that emitted the rekordcrate-default 16), not a real export.data/usbmust NOT be used as a 'fresh rekordbox' baseline for ANY field (same contamination that produced the wrong opaque-blob default in §2). The fresh-write value is established from the REALdata/acemo_freshaspresent_flags.
Confidence: high that it is history-dependent and that real rekordbox never uses 16; the correct fresh-write value is an open question (no seq==1 sample).
Note on the three "reproducible header fields" (task #9)
After multi-export analysis across the FIVE genuine rekordbox exports (acemo, acemo_fresh, lib_multi, MAPLE, data/PIONEER — provenance verified by nonzero per-track opaque ids + per-install seed), all three fields are edit-history / transaction state, NOT structurally reproducible:
- §4 file-header 0x10: allocator/edit accumulator. → write 0, DIVERGENCE.
- §5 page 0x26 unknown7: 0 on all data pages (exact); per-table 0/1/2 on strange pages is edit state. → write 0, strange-page DIVERGENCE.
- §6 row-group trailing u2: "rows touched by last transaction" bitmask. Fresh
single-transaction value =
present_flags(12/14 in real data/acemo_fresh; constant 16 = 0/14). → maple writespresent_flags; residual mismatch vs an EDITED ref is the per-page last-tx bitmask = edit history, DIVERGENCE.
The decisive evidence is the same-library pair (acemo vs acemo_fresh: identical track data, yet header 0x10 = 4 vs 5, tracks strange unk7 = 2 vs 1, tracks-page trailing differs) — a structural field would be identical.
maple's values: §4 header0x10 = 0 and §5 unknown7 = 0 are reasonable minimal
defaults (confirmed = real exports on data pages; fresh strange-page value
unconfirmed). §6 trailing u2 = present_flags is the established fresh-write
value (NOT the old constant 16). No further writer change needed; remaining
mismatches vs edited references are unavoidable edit history.
IMPORTANT corrections to the earlier draft: (1) data/usb/.../export.pdb is
maple's OWN output (an old build that emitted the rekordcrate-default 16), not a
rekordbox export — so any "fresh usb" claim was circular; data/usb is NOT an
authority for ANY field (same contamination that produced the wrong opaque-blob
default in §2). (2) We have NO genuine rekordbox export with sequence==1, so the
never-edited header0x10 / strange-page-unknown7 values are unconfirmed. (3) The
row-group trailing u2 fresh value is present_flags, established from the REAL
data/acemo_fresh (12/14); the rekordcrate "Default=16" is a placeholder that
matches 0/14 real groups.
All three go to re/KNOWN_DIVERGENCE.md, not the writer.
7. Artwork + USBANLZ path schemes (task #11)
Decoded from genuine data/MAPLE (176 tracks/176 artwork) + acemo. The guiding
principle (per the task): for player acceptance only PATH SELF-CONSISTENCY
matters — every path stored in the pdb must resolve to a real on-disk file.
Matching rekordbox's exact id-derivation is NOT required; maple's own consistent
scheme is acceptable.
7a. Artwork — SOLVED, exactly reproducible (maple already matches)
- Path:
/PIONEER/Artwork/{folder:05d}/a{artwork_id}.jpg(80×80) plus aa{artwork_id}_m.jpg(240×240) variant in the same folder (rekordbox writes both; verified on disk in data/MAPLE). - Folder bucket rule:
folder = artwork_id // 20 + 1(20 ids per bucket; bucket 00001 = ids 1–19 because ids are 1-based, 00002 = 20–39, 00003 = 40–59, …). Verified 176/176 in data/MAPLE. - maple's
artwork._bucket()is literallyf"{artwork_id // 20 + 1:05d}"andextract_artworkwrites both jpg variants at that path → byte-identical to rekordbox AND self-consistent. No change needed.
7b. USBANLZ — opaque id; maple's own scheme is self-consistent (DIVERGENCE from rekordbox)
- Path:
/PIONEER/USBANLZ/P{bucket}/{8-hex}/ANLZ0000.DAT(+.EXT). - rekordbox's
{8-hex}is a per-track opaque analysis id (range seen 0x117..0x309fd over 176 tracks; NOT == track id, NOT == the row-0x14opaque_track_id_lo, NOT a crc of path/title — tested) and theP{bucket}(0–127, 94 distinct over 176) is a spreading bucket that is NOT a derivable function of the 8-hex or any track field (tested:&0x7f,>>k,%127/128, low-byte — all fail). Like the row-0x14 blob, these come from rekordbox's internal DB and are not reproducible without it. - maple (cli.py
build()): generates its OWN deterministic scheme —bucket = P{(crc32(file_path) >> 20) & 0xFFF:03X},analyze_id = {crc32(file_path):08X}, thenwrite_anlz()writes the .DAT at exactly that path. This is self-consistent by construction (the path it records is the path it writes) and is the correct approach per the self-consistency principle. It does NOT match rekordbox's id/bucket (expected DIVERGENCE).- Minor cosmetic note (not a correctness issue): maple's bucket is 3 hex
digits
P000..PFFFwhereas rekordbox usesP00..P7F(0–127). Harmless; optionally narrow to& 0x7F(P{..:02X}) to look more rekordbox-like.
- Minor cosmetic note (not a correctness issue): maple's bucket is 3 hex
digits
7c. Self-consistency verification
- Genuine rekordbox (data/MAPLE): 176/176 ANLZ paths and 176/176 artwork paths resolve to on-disk files. (rekordbox is fully self-consistent — sanity check that confirms the parser and the principle.)
- maple production
build()(cli.py): self-consistent BY CONSTRUCTION —extract_artwork(artwork.py:62-65) writesa{id}.jpg/a{id}_m.jpgat the recorded path;write_anlz(cli.py:93) writesANLZ0000.DATat the recordedanalyze_path. Each pdb path is computed and the file is written there in the same step, so every path resolves. - CAVEAT — the byte-diff harness
tools/build_acemo.pyis NOT the production path: it MIRRORS a reference rekordbox export (copies itsanalyze_path/ artworkpathstrings verbatim, writes no ANLZ/artwork files), so thedata/usbexport it produces references the reference's USBANLZ files which are not co-located → those paths don't resolve. This is expected for a structural-diff fixture and says nothing about production self-consistency.
Confidence: high. Artwork rule is exact (176/176). USBANLZ rekordbox ids are opaque/non-reproducible (consistent with the row-0x14 finding); maple's own USBANLZ scheme is self-consistent, which is all the player requires.
8. Page-manager free-space: empty_candidate + free pool (task #13 — THE FREEZE)
DECK TEST: maple's pdb FROZE the player (parser hang, not reject). Decoded the
page-manager/free-space divergence against genuine rekordbox exports
(data/working 9trk seq44, data/MAPLE 176trk, data/PIONEER 847trk — all
load) vs maple output (captures/maple_acemo.pdb).
Data-page next_page SELF-LOOP — ROOT CAUSE (confirmed divergence; high confidence)
- The player reads each table by following
first_page → next_page → …. maple's last data page of each multi-page chain setnext_page == its own index(page 2→2, 6→6, 8→8, 12, 14, 28, 34, 36, 38, 40 — 10 self-loops in the 9-track build). A page whosenext_pageis itself is an infinite loop → FREEZE. - Genuine rekordbox has ZERO self-loops. A terminal data page uses
next_page = 0(the "no next" sentinel; seen on pages 10/16/18/20/…/41-47 indata/working) OR points FORWARD to a distinct free page (never to itself). - FIX (maple, done in writer.py): point every chain's last page at a single
dedicated zero-filled free-pool page (
free_sentinel, just past the overflow region) instead of itself; materialise that page and setnext_unusedbeyond it. Verified on the rebuilt pdb: 10 self-loops → 0; all 20 chains terminate at the blank sentinel; player tolerates last-page→free-page (genuine exports do the same). This is the primary freeze fix.
Table-dir empty_candidate (u4 after page_type) — SECONDARY divergence (also wrong; lower-confidence freeze contributor)
- Layout: each 16-byte table-dir entry is
type:u4, empty_candidate:u4, first_page:u4, last_page:u4. empty_candidateis the head of that table's empty/free-page list (the page the Page Manager grabs when the table needs to grow).- In EVERY genuine rekordbox export all 20
empty_candidates are NONZERO and distinct, and each points to an all-zero "free" page — either an in-file allocated-but-empty page (e.g.last_page+1, or a pooled page like 41–47/49) or a beyond-EOF sentinel (index ≥ npages, e.g. 50/51/52 when npages=49 — the firmware tolerates beyond-EOF here). It NEVER points to a used data page and NEVER to 0. - maple writes
empty_candidate = 0for all 20 tables. Page 0 is the FILE HEADER. The firmware Page Manager, walking a table's empty-page list, follows0→ reads the 4 KB file header AS a page → its "next_page" field (header offset 0x0c =next_unused_page) and bogus type/flags send the walk into a loop or out-of-bounds read → hang/FREEZE. This is the highest-confidence cause: it is the only systematic, data-independent divergence, and0is never valid (collides with the header).
Free pool / page layout
- rekordbox keeps the data pages in chains
1..40,48,…and pre-allocates a pool of all-zero "free" pages past them;next_unused_page= one past the highest allocated page (sonext_unused > npagesis normal, e.g. 53 > 49). - maple compacts: 41 pages, every page used,
next_unused == npages == 41, no free pool. The compaction itself is structurally legal (chains are clean), but it leftempty_candidate = 0with no free page to point at. - Beyond-EOF links (a page's
next_page≥ npages, orempty_candidate≥ npages) are TOLERATED — the genuineworkingfile has many (page 4→53, 46→52, empty_candidate 51/52/50/49 all ≥ 49). So beyond-EOF is a valid "no-more-pages / no-free-page" sentinel; it is NOT the freeze.
Strange-page free-map — NOT the cause (decoded, maple matches)
- Each table's first/"strange" page (flags 0x64) carries, after a small prefix
[this_page, next_or_0x03ffffff, 0x03ffffff, 0, 0x1fff0000], a heap filled with 1004 ×0x1ffffff8(f8 ff ff 1fLE) — the empty-heap free-map (count == them1004=1004 marker at header 0x24).0x03ffffffin the 2nd u4 is the legitimate "this table has no data page" sentinel (genuine rekordbox writes it for every empty table; e.g. labels/pltree/plentries). - maple reproduces this free-map correctly; its strange-page prefixes match rekordbox's pattern byte-for-byte (data table → u1=first_data_page; empty table → u1=0x03ffffff). So the free-map is NOT the freeze.
FIX (for maple → deck test)
PRIMARY (high confidence, DONE): eliminate the data-page next_page self-loop
— last page of each chain must point to a blank free page (or use next_page=0),
never to itself. maple now points all last pages at one zero-filled free_sentinel
page. Verified: 0 self-loops in the rebuilt pdb.
SECONDARY (cheap, also a real divergence — fold into the same build): stop
writing empty_candidate = 0. Point it at the same free_sentinel
(pack("<I", free_sentinel) instead of 0). Genuine rekordbox NEVER writes 0
(0 = the header page); it always points each table's empty_candidate at a blank
free page. Lower-confidence as a freeze cause for a read-only player than the
self-loop, but wrong and trivially fixable with the page maple already
materialised.
If the deck STILL hangs after both, next suspects (lower confidence):
next_unused_page == npages (give a little slack), and whether the single shared
sentinel needs to be per-table-distinct (rekordbox uses distinct free pages).
Confidence: HIGH that the self-loop was the freeze (self-referential next_page is an unconditional infinite loop; 0/all genuine exports self-loop). empty_candidate=0 is independently wrong (0/all genuine exports use it; collides with header). Exact firmware behaviour is inferred (no firmware to trace); the deck test confirms. is the confirmation.