29 KiB
REFERENCE_COMPARISON.md — pyrekordbox & rekordcrate vs maple
What the two mature open-source rekordbox libraries can decode/encode, how that lines up with maple's writer and our RE findings, and the concrete GAPS maple must close for a full from-scratch USB export.
- pyrekordbox (Python, dylanljones) — https://pyrekordbox.readthedocs.io
- rekordcrate (Rust, Holzhaus) — https://holzhaus.github.io/rekordcrate/
local source at
/mnt/c/Users/ntr/code/rekordcrate/src - maple —
maple-py/src/maple/*, thisre/dir.
Scope note (coordination): the per-format library-capability comparison and
the from-scratch-export gaps are mine (task #12). The master.db SQLCipher key
mechanics and opaque Content ID identity are owned by re (#10); the exact
USBANLZ/artwork on-disk path schemes by re (#11). Where those are cited below
they are tagged [see re #10] / [see re #11] and should be treated as
re's authority, not re-derived here.
0. One-screen capability matrix
| Format | pyrekordbox | rekordcrate | maple |
|---|---|---|---|
| export.pdb | parse only (via the pdb parser, read) |
parse + WRITE (BinWrite on rows/pages, full roundtrip) |
WRITE only (from-scratch, the whole point) |
| exportExt.pdb | not supported | not supported | not written |
| ANLZ (.DAT/.EXT/.2EX) | parse only ("creating … is planned") | parse + partial write (binrw BinWrite derived on waveform/cue structs; not a whole-file builder) |
WRITE (minimal: PMAI + PPTH only) |
| artwork (.jpg thumbs) | reads ImagePath from db only |
n/a (just the pdb Artwork row path string) |
WRITE (extracts embedded art) |
| master.db (db6/7) | READ + WRITE (SQLCipher unlock) | not supported | not used |
| mysetting (*SETTING.DAT) | READ + WRITE (build from scratch) | parse only (setting module) |
not written |
| rekordbox XML | READ + WRITE | parse only (xml module) |
not used |
The headline: no existing library writes a complete from-scratch USB export.
rekordcrate writes export.pdb rows but not ANLZ/exportExt and has no
metadata source; pyrekordbox writes master.db + mysetting + XML but not the USB
export.pdb/ANLZ. maple is filling a real gap. The reference libs are most
valuable to maple as (a) a read-back oracle (parse maple's output and diff)
and (b) for pyrekordbox, a source of the real opaque Content IDs from
master.db.
1. export.pdb
What rekordcrate does (the closest reference writer)
pdb/mod.rs decodes and writes every row type maple emits: Track, Artist,
Album, Artwork, Color, Genre, Key, Label, PlaylistTreeNode, PlaylistEntry,
ColumnEntry, HistoryPlaylist, HistoryEntry (pub enum Row, lines ~1064-1103).
It has a genuine page serializer: write_page_contents (mod.rs:212),
write_options_and_get_row_offset (mod.rs:463), per-row-type alignment
(align_by, mod.rs:1121-1133), and a Track BinWrite impl (mod.rs:934).
Where rekordcrate CONFIRMS maple's layout (high-value confirmations)
- Track fixed prefix = 0x5e bytes, then a 21×u2 (0x2a) offset block.
rekordcrate asserts
start_of_string_section - base_position == 0x5eand seeks0x2afor 21 string offsets (mod.rs ~986-996). maple'sTRACK_FIXED_PREFIX = 0x5E,TRACK_HEADER_LEN = 0x5E + 21*2(rows.py:8-10) and its 21-string list match field-for-field, including the exact string order (isrc, …, analyze_path, analyze_date, comment, title, unknown_string8, filename, file_path). Independent confirmation of maple's track row. - Field order of the 0x5e prefix is identical between rekordcrate's
Track::write_optionsand maple'sserialize_track: unknown1/index_shift/ bitmask/sample_rate/composer_id/file_size/unknown2(=opaque lo @0x14)/unknown3+unknown4(=seed @0x18/0x1a)/artwork_id/key_id/orig_artist_id/ label_id/remixer_id/bitrate/track_number/tempo/genre_id/album_id/artist_id/ id/disc/play_count/year/sample_depth/duration/unknown5/color/rating/unknown6/ unknown7. - DeviceSQL string encoding. rekordcrate
pdb/string.rsroundtrips the same three forms maple'sstrings.pywrites: short-ASCII ((len<<1)|1), long-ASCII (0x40), long-UTF16LE (0x90). Its tests encode"I ❤ Rust"as 0x90 and a long string as 0x40 — exactly maple's selection rule. The empty string is0x03in both. Confirms strings.py. - Row-group / page-index mechanics. rekordcrate's
RowGroup(mod.rs:451, 2102) uses the same 16-rows-per-group, backward-growing offset index, and presence bitmask maple builds inpager.py.Header(mod.rs:162) carries the same 13-bit num_row_offsets / 11-bit num_rows packing maple writes (pager.py:131).
Where rekordcrate caught a maple BUG — next_page chain termination (the FREEZE)
rekordcrate pdb/mod.rs documents Page::next_page (lines 262-265) precisely:
"Index of the next page with the same page type. If this page is the last one of that type, the page index stored in the field will point past the end of the file."
maple's writer originally set the last page of each table chain to
next_page = its own page_index (a self-pointer). That is exactly what
rekordcrate says must NOT happen — the last page must point forward, past the
allocated pages. A self-referencing next_page makes a firmware page-walk that
terminates on "next_page past EOF" loop forever on the last page → the deck
freezes. This is the root cause being fixed in task #13: writer.py now points
every last page at a dedicated free-pool sentinel page just past the overflow
region (free_sentinel, materialised zero-filled) and sets next_unused_page
beyond it — which removes the self-loop.
(rekordcrate's own read_pages, mod.rs:185-196, terminates the walk on
page_index == table.last_page from the directory, so its parser tolerates a
self-pointer — which is why maple's read-back oracle never flagged it. The
firmware uses the chain-walk, so only a real deck exposed the freeze.
Lesson: a clean rekordcrate parse is NOT proof the chain is firmware-valid.)
Verified against a real export — the EXACT rule (not just "past EOF"). I decoded
data/PIONEER/rekordbox/export.pdb(847 tracks, 174 pages,next_unused=177). rekordcrate's "points past the end of file" is a simplification; the real, consistent rule across all 20 tables is: **each table's last/only page setsnext_page == that table'sempty_candidate**, whereempty_candidate(table-dir entry +4) is a *distinct, nonzero* page index< next_unused— the head of that table's free-page chain. Evidence: every last page'snext_pageequalled its own dirempty_candidate(type0 173→174=ec; type1 4→53=ec; type19 40→41=ec; …), and those targets (174,53,158,163,54,49,42,46,170,20,22,175,176,165,30,32, 43,44,45,41) are all< next_unused. Strange-only tables already match (next_page = page_idx+1 = their ec; free-mapdata_ptr=0x03ffffff) — maple'sbuild_strange_page` does this correctly.So maple's current #13 fix (a single shared sentinel for every data table +
empty_candidate=0in the directory) removes the freeze but still diverges from real rekordbox, which uses a distinct per-tableempty_candidatethat the directory entry also records (nonzero). If firmware ever cross-checksnext_pageagainst the directory'sempty_candidate, the shared sentinel + zero ec could still misbehave. Recommended (reported tomaple): give each data table its own free page, setnext_page = that pageAND write that index into the table-dirempty_candidate, keep them all< next_unused. This is straightforwardly reproducible (unlike the value ofempty_candidateon an edited ref, which is GC state — here we only need a valid distinct free page, which a from-scratch writer fully controls).
Where rekordcrate is WRONG and maple is RIGHT — page row-count header (>255 rows)
Audited because rekordcrate's Page header (mod.rs:277-377) reads the row count
as num_rows_small: u8 @0x18 plus num_rows_large: u16 @0x22 with a
calculate_num_rows rule (large if large>small && large!=0x1fff else small).
maple instead writes the canonical crate-digger .ksy bitfield at 0x18:
13-bit num_row_offsets + 11-bit num_rows, then page_flags @0x1b
(pager.py:131). These are two different readings of the same bytes — so I
verified against a real export (data/PIONEER, which has 284-row
playlist-entry pages):
- Real page 18 bytes @0x18 =
1c 81 23 24. The 24-bit LE value0x23811cdecodes (kaitai/maple way) tonum_row_offsets=284, num_rows=284— correct. - maple's packer reproduces
1c 81 23exactly for n=284 (anda6 c0 14for 166,7e c0 0ffor 126 — all matching real pages byte-for-byte). - rekordcrate's reading of the same bytes:
num_rows_small = 0x1c = 28(just the low byte) andnum_rows_large @0x22 = 283(which is really thetransaction_row_indexedit field), givingcalculate_num_rows = 283— i.e. rekordcrate would report 283 rows on a 284-row page, and 28 if the transaction field were 0. Its small/large model only coincidentally agrees whenn <= 255. - Conclusion: maple's header encoding is correct and firmware-faithful for
pages up to 2047 rows (the 11-bit max); rekordcrate's
num_rows_small/num_rows_largeis an older/divergent decode that breaks forn > 255. This matters because maple can emit >255-row pages (playlist_entries fits ~284/ page, colors ~394). No maple change needed — this is a maple-is-right result.
Where rekordcrate's row alignment diverges (maple matches real rekordbox)
rekordcrate aligns each row by its Rust struct's natural align_of_val
(mod.rs:1114-1136), which would 2-align u16-only row types. maple aligns every
row to 4 (pager._align4). Checked real exports: every row in
data/PIONEER (3567/3567) and rb_ref (101/101) is 4-aligned. So maple's
universal 4-alignment matches rekordbox; rekordcrate's per-type scheme is the
divergent one (its own code comments admit "we should survey all the different
kind of rows and ensure they're aligned properly", mod.rs:1110). No maple change
needed.
Where maple's RE goes BEYOND rekordcrate (we know more than the reference)
These are fields rekordcrate leaves as opaque caller-supplied "unknown"s but maple's RE solved/characterised — important because it means rekordcrate cannot be used to validate these; maple's FORMAT_SPEC is the authority:
index_shift(row +0x02). rekordcrate calls itindex_shiftbut writes whatever the caller stored — it does not know the value (mod.rs:104-105 "Unknown field, calledindex_shiftby flesniak"). maple derives it asslot_index << 5and patches it at page-build time (pager.py:142-145, FORMAT_SPEC.md §1). maple is correct where rekordcrate is agnostic.- The 8-byte opaque blob (0x14 lo / 0x18+0x1a seed). rekordcrate:
unknown2: u32,unknown3: u16("always 19048?"),unknown4: u16("always 30967?") — three blind unknowns it round-trips verbatim (mod.rs:115-119). maple's RE (FORMAT_SPEC.md §2) proved this is one LE u64 =opaque_track_id_lo | (opaque_seed<<32), a per-track Content ID + per-install seed stored in master.db, non-derivable from metadata, and that the "19048/30967" constants are NOT universal (they only hold for some old exports). maple emits a non-zero stand-in; the real values live in master.db[see re #10]. maple's understanding is strictly deeper than rekordcrate's. bitmask(0x06). rekordcrate: opaque "unknown" round-tripped. maple writes a fixed788224(0xC0700) — itself not yet RE'd to a formula, but maple at least pins a known-good value.- Strange/"first" pages. rekordcrate leaves the strange-page heap zero
(it round-trips parsed input); maple constructs the full DeviceSQL
free-space map from scratch byte-for-byte (
pager.build_strange_page, verified vsdata/acemo_freshand the real strange pages in data/PIONEER), including thef8 ff ff 1f×1004 map, the0x03ffffffno-data sentinel, andunknown7=1for tracks/history.reconfirms maple is strictly more faithful than rekordcrate on strange pages. This is from-scratch generation rekordcrate never had to do. - RowGroup trailing u2. rekordcrate writes a constant
16placeholder (Default::default, mod.rs:454) and does not decode it;reconfirms genuine rekordbox never uses 16 there — it is the "rows touched by the last transaction" bitmask (FORMAT_SPEC §6). maple writespresent_flags(the single-transaction value), closer to real output; either way it is edit-history state (KNOWN_DIVERGENCE §6), not a rekordcrate-validatable field.
Where rekordcrate (and our RE) AGREE the field is unreproducible — with a caveat
rekordcrate documents empty_candidate ("maybe links to a chain of empty
pages if the database is ever garbage collected") and next_unused_page
("points past end of file") as allocator/GC state — matching
KNOWN_DIVERGENCE.md items 11 and 1. The historical VALUE on an edited
reference is indeed unreproducible. BUT my data-page audit (see the FREEZE
section above) found empty_candidate is load-bearing structure, not just
GC trivia: in data/PIONEER every table's last page has
next_page == that table's empty_candidate, a distinct free page < next_unused.
So a from-scratch writer must still emit a valid distinct empty_candidate
per table (which it fully controls) even though it can't match an edited ref's
specific value. Confirms these are correctly on
maple's divergence list, not bugs.
GAP — none structural for export.pdb
maple writes a complete, players-accepted export.pdb. Remaining export.pdb divergences from a reference file are all edit-history artifacts (KNOWN_DIVERGENCE.md) that neither library can reproduce from content either. The one content-level gap is the opaque blob (see §4).
2. exportExt.pdb — GAP (nobody writes it; maybe nobody needs to)
- pyrekordbox: no support.
- rekordcrate: no support (its
pdbmodule is export.pdb only). - maple: does not emit it.
Format facts (Deep Symmetry export analysis; reverse-engineered by Dominik
Stolz, crate-digger issue #11): exportExt.pdb is a second, smaller
DeviceSQL file beside export.pdb holding only two table types — Tags
(type 03) and Tag Tracks (type 04) — i.e. the "My Tag" categorisation
feature and its tag→track associations. It uses the same page/row container
format as export.pdb.
Assessment / recommendation: exportExt.pdb is optional. It only carries
the My-Tag feature; players that don't find it simply show no custom tags, and
tracks/playlists from export.pdb play normally. maple has no tag data to
populate anyway. Do not block a v1 export on this. If maple ever supports
tags, the existing pager.py/PageBuilder already produces the container
format — only two new row serialisers (tag row, tag-track row) and a second
write_pdb-style call writing the 2-table directory would be needed.
Confidence the file is skippable: high (it post-dates the core format and is
absent from older exports that still play).
3. ANLZ (.DAT / .EXT / .2EX) — the biggest real GAP
What the references decode
- pyrekordbox (
pyrekordbox.anlz): full parser for PMAI + all section tags — PPTH, PQTZ/PQT2 (beat grids), PCOB/PCO2 (cues/loops), PVBR, PSSI (phrase/song-structure, XOR-masked), PWAV/PWV2 (previews), PWV3/4/5 (detailed + colour waveforms), PWV6/7/PWVC (.2EX). Writing is "planned … but the full structure has to be understood first" — i.e. read-only today. - rekordcrate (
anlz.rs): parses the same tag set; magic constants present in source: PMAI, PPTH, PQTZ, PVBR, PWAV, PWV2, PCOB, PCO2, PCPT, PCP2, PSSI, PWV3/4/5. It derivesBinWriteon the waveform-column structs (WaveformPreviewColumnetc., anlz.rs:368-428) and has awrite_encryptedfor the XOR-masked PSSI (anlz.rs:902) — so it has piece-level write code, but no whole-file ANLZ builder / CLI to emit a valid .DAT/.EXT from track audio.
What maple does
anlz.py writes a minimal ANLZ0000.DAT: a 28-byte PMAI header + a single
PPTH section (u4 len + UTF-16BE path). No beat grid, no waveform, no cues,
no .EXT, no .2EX. Rationale in the module docstring: firmware reads PPTH to
confirm which audio file the analysis belongs to, and a track whose
analyze_path points at nothing can be hidden from the library, so PPTH
alone makes the track "appear analysed enough to surface."
CONFIRMATIONS
- maple's PMAI/PPTH layout (4-cc + u4 len_header + u4 len_tag; PPTH = u4
len_path + UTF-16BE, big-endian throughout) matches the format both
references parse. The big-endian-for-ANLZ-vs-little-endian-for-pdb split that
anlz.pycalls out is corroborated by both libraries.
GAPS maple must close for full faithfulness
- No
.EXTfile. Modern CDJs/XDJs (NXS2+) readANLZ0000.EXTfor colour waveforms (PWV4/5), extended cues (PCO2), and phrase data (PSSI). Without it maple tracks show no colour waveform and no phrase/structure on newer players. Theanalyze_pathin the pdb points at the.DAT; the player derives the.EXTsibling. maple writes neither beyond the.DATPPTH stub. - No beat grid (PQTZ). Without a beat grid, beat-sync / quantize / loop features have nothing to lock to; the track plays but degrades the CDJ experience. This is the highest-value ANLZ section to add after PPTH.
- No waveform (PWAV/PWV3-5). Cosmetic but conspicuous — tracks show a flat
or absent waveform. pyrekordbox can parse these to learn the exact byte
layout, and rekordcrate's
BinWritewaveform-column structs are a concrete reference for the encoding, but maple must generate the waveform data itself (decode audio → downsample → colour-band), which neither library provides. - Generation, not just layout, is the hard part. The byte format is fully documented (both libs + Deep Symmetry); what's missing everywhere is the DSP that produces beat positions and waveform columns from PCM. That DSP is maple's to build — the references only validate the container.
Recommendation: keep the PPTH-only .DAT as the working baseline (it is
what gets tracks visible), and treat PQTZ beat grid + a .EXT with PWV5 colour
waveform as the next ANLZ milestones. Use pyrekordbox's parser as the
round-trip oracle (write a section, parse it back, compare). Confidence the
PPTH-only stub is sufficient for visibility/playback: medium-high (matches
maple's own deck testing rationale); confidence it's sufficient for a complete
experience: low — beat grid/waveform are expected by users.
4. master.db (db6/7) + the opaque Content IDs — GAP maple can close via pyrekordbox
What pyrekordbox does (this is its core strength) — VALIDATED by re #10
- Reads and writes the encrypted
master.db. The db is SQLCipher4 encrypted with a single GLOBAL key, identical on every install (not machine/license-derived).revalidated the concrete key (FORMAT_SPEC §2 Round-3):402fd482c38817c35ffa8ffb8c7d93143b749e7d315df7a81732a1ff43608497. pyrekordbox 0.4.4 +sqlcipher3-wheelsinstalls and wires up correctly, so a valid master.db opens and exposesDjmdContent[see re #10]. - Exposes
DjmdContent(one row per track) with:ID(the rekordbox internal Content ID),FolderPath,FileNameL,AnalysisDataPath(the/PIONEER/USBANLZ/...analysis path),ImagePath(artwork), and FK ids to Artist/Album/Genre/Label/Key/Composer; plusdjmdCue,djmdPlaylist/djmdSongPlaylist,djmdHistory,djmdMixerParam,djmdDevice, etc.
Reality check from re #10 — currently BLOCKED on data, not on method:
- The captured
maple-py/captures/p17328_master.dbis corrupt/truncated (zeroed SQLCipher salt + page 1) — no key can open it. The genuineexportLibrary.dbshipped beside export.pdb uses a different key scheme that the global master.db key does not open, and pyrekordbox has no exportLibrary.db support. So the IDs have not yet been read on this RE machine — the path works only against a live%APPDATA%/Pioneer/rekordbox6/master.db.opaque_track_id_lo == DjmdContent.ID & 0xFFFFFFFFis a strong inference, still UNTESTED (the lo range fits the 9-digit Content-ID band), not a confirmed equality.- The per-install seed (
0x21b4a7ab) is NOT a DjmdContent column and its source is still unidentified (likely a library/USB UUID or agentRegistry/local_usn-style marker; needs a readable master.db to find).- pyrekordbox
add_contentlets rekordbox assign the Content ID — there is no API to force a specific ID — and it does not drive a USB export (get_deviceis read-only). So pyrekordbox is a read source of real IDs, not a writer/exporter and not a way to dictate IDs.
Why this matters for maple (ties to FORMAT_SPEC §2 / KNOWN_DIVERGENCE #9)
maple's RE proved the export.pdb track blob at 0x14/0x18 is the rekordbox
Content ID (per-track, 28-bit band) plus a per-install seed, both stored
in master.db and not derivable from the audio/metadata maple has. maple
currently emits a non-zero stand-in (crc32(id|file_path) lo +
TRACK_OPAQUE_SEED = 0x21b4a7ab). Therefore:
- GAP (closable in principle, not yet proven): real Content IDs. If maple
runs on a machine with rekordbox installed, it can use pyrekordbox + the
global key to open the LIVE
%APPDATA%/Pioneer/rekordbox6/master.dband readDjmdContent.IDper track, then write those into the pdb blob instead of the stand-in.revalidated the mechanism (global key, DjmdContent schema) but could NOT confirmID == opaque_track_id_lohere because the only captured master.db is corrupt — so this is a strong-but-untested lead, not a closed item. Next step (re #10): point pyrekordbox at a live master.db and confirm the equality for the AceMo tracks. The per-install seed0x21b4a7absource is still unidentified, so maple keeps the configurableTRACK_OPAQUE_SEEDeither way. - Caveats: (a) works only with a rekordbox install present and a readable
master.db (not the corrupt capture), and only for tracks rekordbox already
knows; (b) pyrekordbox
add_contentcannot force a chosen ID (rekordbox assigns it) and does not drive a USB export, so it can supply IDs but cannot replace maple's writer. Pure from-scratch (no rekordbox install) stays a documented divergence (KNOWN_DIVERGENCE #9), with maple's non-zero stand-in.
rekordcrate
No master.db support at all — it is a device-export (USB) library, not a library-database library. So it cannot help with Content IDs.
5. mysettings (*SETTING.DAT) — GAP (small, easy) maple doesn't write
- pyrekordbox: reads and writes/builds
DEVSETTING.DAT,MYSETTING.DAT(40B, CDJ play settings),MYSETTING2.DAT(40B), andDJMMYSETTING.DAT(52B, DJM mixer) — dict-style get/set + save. (DEVSETTINGpayload still partly ununderstood.) - rekordcrate:
settingmodule parses these (read-only). - maple: does not write any of them.
These live in PIONEER/ on the USB alongside rekordbox/. They are optional
preference files — absent, the player just uses its own defaults; tracks and
playlists are unaffected. Recommendation: low priority. If desired,
pyrekordbox can build default ones in a few lines (it's read/write), so maple
could shell out to / import pyrekordbox to drop standard MYSETTING.DAT +
MYSETTING2.DAT rather than re-implementing the 40-byte structs. Confidence
they're optional for playback: high.
6. Overall directory layout + path schemes (cross-check) [see re #11 / FORMAT_SPEC §7]
Both references and Deep Symmetry agree on the USB tree maple targets:
/PIONEER/rekordbox/export.pdb (+ optional exportExt.pdb),
/PIONEER/USBANLZ/<...>/ANLZ0000.{DAT,EXT,2EX},
/PIONEER/Artwork/<...>/a<id>.jpg, plus optional /PIONEER/*SETTING.DAT.
maple writes PIONEER/rekordbox/export.pdb (cli.py:156) and
PIONEER/USBANLZ/<bucket>/<id>/ANLZ0000.DAT (cli.py:92).
re resolved both path schemes (#11, FORMAT_SPEC §7):
-
Artwork — EXACTLY reproducible, and maple already matches. Real rekordbox uses
/PIONEER/Artwork/{id//20+1:05d}/a{id}.jpg(+ aa{id}_m.jpg240px thumb), 20 ids per bucket folder (bucket 1 = ids 1-19).reverified 176/176 in data/MAPLE, and maple'sartwork._bucket()already implements this rule. Reproducible, confirmed — no gap. -
USBANLZ — NOT reproducible; maple's own scheme is correct-by-construction. Real rekordbox uses
/PIONEER/USBANLZ/P{bucket}/{8hex}/ANLZ0000.DATwhere the 8-hex is an opaque per-track analysis id (NOT the track id, NOT the row 0x14 lo, NOT any crc of the path —reruled these out) and the P-bucket (0-127) is not a derivable function of it. Like the 0x14 blob, it is DB-sourced and non-reproducible. maple synthesises its ownP{(crc32(path)>>20)&0xFFF:03X}/{crc32(path):08X}and writes the.DATthere, so the pdb'sanalyze_pathand the on-disk file always agree. This is fine: the player only requires every pdb path to resolve to a real on-disk file, not to match rekordbox's id scheme.
Gotcha for the gaps below (from
re):tools/build_acemo.pyMIRRORS a reference export's paths verbatim and writes no ANLZ/artwork files (it's a byte-diff fixture builder). The productioncli.py build()is the one that actually writes each ANLZ/artwork file at the recorded path. Don't conflate the two when reasoning about what maple emits on disk.
7. Bottom line — prioritised GAP list for a full from-scratch export
- (high, lead — untested) Real opaque Content IDs via pyrekordbox/master.db
— mechanism validated by
re(global SQLCipher key402fd482…+Rekordbox6DatabasereadsDjmdContent), butDjmdContent.ID == opaque_track_id_lois a strong INFERENCE, NOT validated (the only captured master.db is corrupt — zeroed salt — sorenever confirmed the equality on real data). Would close KNOWN_DIVERGENCE #9 if confirmed against a readable live%APPDATA%/Pioneer/rekordbox6/master.db.[see re #10 / FORMAT_SPEC §2 Round-3] - (OPEN — Ghidra trace pending) Per-install
opaque_seedsource. The seed (0x21b4a7ab/0xd6d3a567) is NOT aDjmdContentcolumn andrehas not located it — likely a per-library UUID/agent/usn marker or a computed value. It needs a readable master.db to inspect candidate rows, OR a Ghidra trace of rekordbox.exe (whichrecan now attempt — the Ghidra MCP just came online), so it is not permanently blocked. Until resolved, maple keeps the configurableTRACK_OPAQUE_SEEDstand-in. This is the one genuinely open question touching the opaque blob.[see re #10 — OPEN; FORMAT_SPEC §2 Round-3] - (high) ANLZ beat grid (PQTZ) + an
.EXTcolour waveform (PWV5) — needs DSP maple must build; references validate the byte format only. Biggest functional gap on modern players. - (structural — in progress) Per-table
empty_candidate/ last-pagenext_page— the FREEZE fix (#13). maple must point each table's last page at a distinct free page recorded in the dir'sempty_candidate, not a shared sentinel withempty_candidate=0. See the FREEZE section in §1. - (low) mysettings — optional; pyrekordbox can build them.
- (low/skip) exportExt.pdb — optional My-Tag data; only if maple adds tags.
- (reference, not a gap) Use rekordcrate as a read-back oracle — parse
maple's
export.pdbwith rekordcrate's CLI to catch container regressions; use pyrekordbox's anlz parser as the oracle for any new ANLZ sections. Caveat (proven this session): rekordcrate's parser tolerates things the firmware rejects (thenext_pageself-loop) and is itself wrong on some fields (>255-row count, per-type alignment) — so it is a regression oracle, NOT a correctness authority. Validate load-critical fields against REAL export bytes (e.g. data/PIONEER), not against rekordcrate.
Headline confirmation for the team: rekordcrate's writer independently
validates maple's export.pdb track-row layout, DeviceSQL string encoding, and
page/row-group container down to the 0x5e/0x2a offsets — but it is agnostic
exactly where maple's RE added knowledge (index_shift formula, the opaque-id
semantics). So maple's FORMAT_SPEC is the authority for those fields, and the
remaining real work is ANLZ generation and pulling the real Content IDs
from master.db — neither of which any existing library does end-to-end.