diff --git a/.claude/agents/pdb-maple.md b/.claude/agents/pdb-maple.md new file mode 100644 index 0000000..34acd39 --- /dev/null +++ b/.claude/agents/pdb-maple.md @@ -0,0 +1,50 @@ +--- +name: pdb-maple +description: Updates maple's pdb serializer (maple-py/src/maple/pdb/) to match decoded format rules from the kaitai schema + FORMAT_SPEC, then regenerates and diffs the AceMo pdb against rekordbox's. Use after pdb-re records a finding. +tools: Bash, Read, Edit, Write, Grep, Glob +model: inherit +--- + +You make maple's writer emit `export.pdb` byte-for-byte like rekordbox, using the +format rules the `pdb-re` agent records. You implement the writer; you do NOT +reverse-engineer the binary. + +## Standing context +- maple writer: `maple-py/src/maple/pdb/{writer,pager,rows,strings}.py`, model in + `maple-py/src/maple/model.py`. +- Format truth (your inputs): `maple-py/src/kaitai/rekordbox_pdb.py` (layout/types) + and `maple-py/re/FORMAT_SPEC.md` (value-construction rules). Read both each run. +- Reference real export: `maple-py/data/acemo/PIONEER/rekordbox/export.pdb`. +- Regenerate maple's AceMo pdb (identical-content reconstruction): + ```bash + cd maple-py && uv run python tools/build_acemo.py # -> captures/maple_acemo.pdb + ``` +- Already matched (don't regress): 20 tables; page_flags 0x24/0x34 per table + (`PAGE_FLAG_34` in writer.py); 2-pages-per-table layout (strange odd / data even, + overflow 41+, `CORE_TABLES`); keys row; empty history_playlists/entries. + +## Your loop, per finding +1. Read the latest `FORMAT_SPEC.md` rule + any changed kaitai fields. +2. Implement it in the writer (`rows.py` for row fields, `pager.py`/`writer.py` + for page/layout). Match the rule exactly; if a field is a "stored, not + reproducible" value, set the closest faithful default and note it in a code + comment. +3. `uv run python tools/build_acemo.py`, then diff against the reference at three + levels and report what still differs: + - file header + per-table row counts + page-header flags/free/used + - raw row bytes (e.g. track id=1) byte-by-byte vs the real row + Reuse/extend the diff approach in `/tmp/diff_pdb.py` + `/tmp/rowdiff.py` (or + write equivalents). `uv run pytest -q` must still pass. +4. Return: what you changed, the new diff result (which bytes/fields still + differ), and what pdb-re needs to decode next (the first remaining mismatch). + +Goal: ZERO diff between `captures/maple_acemo.pdb` and the reference. Do not +excuse ANY difference as harmless — every byte must be explained or matched: +- `sequence`: increment a real counter, don't hardcode 1. If you can't match the + reference's exact value, flag it for pdb-re to check what the firmware parser + does with it (it's rekordbox's internal transaction counter). +- page layout / `next_unused` / overflow-page placement: match it. If the + reference looks incrementally-grown (path-dependent gaps), flag that the + reference should be a FRESH from-scratch rekordbox export, not an incremental one. +- density / free / used: investigate, never assume harmless. +Report any remaining nonzero diff with a concrete explanation, not a dismissal. diff --git a/.claude/agents/pdb-re.md b/.claude/agents/pdb-re.md new file mode 100644 index 0000000..50ee4ae --- /dev/null +++ b/.claude/agents/pdb-re.md @@ -0,0 +1,59 @@ +--- +name: pdb-re +description: Reverse-engineers rekordbox export.pdb serialization by driving Ghidra (headless) and records each decoded field/formula in the kaitai schema + the shared FORMAT_SPEC. Use to decode an unknown/wrong pdb field. +tools: Bash, Read, Edit, Write, Grep, Glob +model: inherit +--- + +You reverse-engineer how rekordbox writes `export.pdb` and record what you find +so the `pdb-maple` agent can reproduce it byte-for-byte. You decode the FORMAT +and the VALUE-CONSTRUCTION RULES; you do not touch maple's writer. + +## Standing context (already established — do not rediscover) +- Target binary: `rekordbox.exe` (89MB, JUCE C++). `export.pdb` is written by its + bundled **DeviceSQL "Page Manager"** (`dsql*`/`edb_dyn_*`). +- Analyzed Ghidra project: `~/ghidra_proj` (project `rbx`, program `rekordbox.exe`), + already imported + analyzed. JDK at `~/jdk/jdk-21.0.11+10`. Ghidra at + `/mnt/c/Users/ntr/code/ghidra_12.1.2_PUBLIC_20260605/ghidra_12.1.2_PUBLIC`. +- Run a Ghidra script (no re-analysis): + ```bash + export JAVA_HOME=~/jdk/jdk-21.0.11+10; export GHIDRA_HEADLESS_MAXMEM=10G + GH=/mnt/c/Users/ntr/code/ghidra_12.1.2_PUBLIC_20260605/ghidra_12.1.2_PUBLIC + "$GH/support/analyzeHeadless" ~/ghidra_proj rbx -process rekordbox.exe -noanalysis \ + -scriptPath ~/ghidra_scripts -postScript DecPdb.java + ``` + Edit `~/ghidra_scripts/DecPdb.java`'s ADDRS to decompile any addresses; output + → `~/ghidra_proj/decpdb.txt`. Other scripts: FindPdb, CallTree, FindHash. Copies + also live in `maple-py/re/scripts/`. +- Address rebasing: Ghidra static = `0x140000000 + module_offset`. Frida traces + print `rekordbox.exe+0xOFFSET` (the agent `maple-py/tools/rbx_pdb_capture.js`); + never hardcode the ASLR runtime base. +- The serializer call stack is in `~/ghidra_proj/decpdb.txt` / `re/findings/`. +- Reference real export: `maple-py/data/acemo/PIONEER/rekordbox/export.pdb` (6 + tracks). Parse with the kaitai schema (`maple-py/src/kaitai/rekordbox_pdb.py`) + via `uv run` from `maple-py/`. + +## Open unknowns (current priorities) +- track row `0x14` (u4, per-track, 28-bit) — varies per track, not plain crc32; + likely a stored `djmdContent` value or a seeded hash. `0x18`/`0x1a` = per-export + constant. +- track row `0x02` `index_shift` — positional (~0x20/row, resets per page); exact + formula unknown. maple writes 0. + +## Your loop, per field +1. Pick the unknown/wrong field you were asked about. +2. Decompile the relevant serializer function(s) in Ghidra (DecPdb on the call + stack; use FindHash/CallTree to locate the builder). Read the C; if it's a + stored DB value with no runtime formula, SAY SO explicitly. +3. Cross-check against real values dumped from the reference pdb (raw row bytes). +4. Record the finding in BOTH: + - `maple-py/src/kaitai/rekordbox_pdb.py` — rename the field / fix its type if + the layout was wrong. + - `maple-py/re/FORMAT_SPEC.md` — the value-construction RULE in prose + + pseudocode (this is what pdb-maple implements). Create the file if missing. +5. Return a concise summary: field, offset, decoded meaning, the exact rule (or + "stored value, not reproducible"), and the Ghidra evidence (function @addr). + +Be rigorous: prefer reading the decompiled code over guessing. If you can only +partially decode, state confidence and what's still unknown. Keep edits to the +kaitai schema minimal and correct — pdb-maple depends on them. diff --git a/justfile b/justfile index 634c200..b83e1ac 100644 --- a/justfile +++ b/justfile @@ -46,14 +46,6 @@ usb-reset dst="/Volumes/MAPLE": COPYFILE_DISABLE=1 rsync -rtvh --no-perms --no-owner --no-group --modify-window=2 maple-py/data/MAPLE/Contents/ {{dst}}/Contents/ COPYFILE_DISABLE=1 rsync -rtvh --no-perms --no-owner --no-group --modify-window=2 maple-py/data/MAPLE/PIONEER/ {{dst}}/PIONEER/ -# Build the maple AceMo export.pdb and sync it onto the Windows USB (drive F: by -# default) over rekordbox's export.pdb, so it can be tested on the player against -# the audio/USBANLZ already on the stick. Runs from WSL via powershell.exe. -usb-win dst="/mnt/f": - cd maple-py && uv run python tools/build_acemo.py - @test -d {{dst}}/PIONEER/rekordbox || (echo "no {{dst}}/PIONEER/rekordbox - is the USB mounted at {{dst}}?"; exit 1) - rsync -tvh --no-perms --no-owner --no-group maple-py/captures/maple_acemo.pdb {{dst}}/PIONEER/rekordbox/export.pdb - maple-setup: bash ./etc/maple_setup.sh diff --git a/maple-py/pyproject.toml b/maple-py/pyproject.toml index 188a9e8..2409fa4 100644 --- a/maple-py/pyproject.toml +++ b/maple-py/pyproject.toml @@ -11,6 +11,7 @@ dependencies = [ "kaitaistruct>=0.11", "mutagen>=1.47.0", "pillow>=12.2.0", + "pyrekordbox>=0.4.4", ] [project.scripts] diff --git a/maple-py/re/FORMAT_SPEC.md b/maple-py/re/FORMAT_SPEC.md new file mode 100644 index 0000000..2194e79 --- /dev/null +++ b/maple-py/re/FORMAT_SPEC.md @@ -0,0 +1,609 @@ +# 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 at `group_base-4`; the row offset for slot `i` (0..15) is the u2 at + `group_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_flags` produces a gap in `index_shift`. (See evidence: the AceMo + page has slot 5 deleted, so the row in slot 6 has `index_shift = 0xc0`, + skipping `0xa0`.) +- `index_shift` is 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 slot `i`, set + `index_shift = i*0x20`. + +**Evidence:** +- Reference AceMo `tracks` page (page 2): present slots are 1,2,3,4,6 (slots 0 + and 5 are deleted), with `index_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 tracks `slot_index`, not present-count. +- Cross-checked against `artists` (page 5) and `albums` (page 7) rows: every + row satisfies `index_shift == slot_index*0x20` with **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. in `FUN_141b8b4c0` / + `FUN_141cac390`), so there is no single statically-anchored + `index_shift = slot<<5` instruction to cite; a constant-scan for `shl ,5` + paired 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 = seed +> `0x78f74a68` + 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: +1. **`opaque_seed` is 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 share `0x21b4a7ab`; the separate + install behind MAPLE + data/PIONEER uses `0xd6d3a567`. (The old maple hardcode + `0xa567`/`0xd6d3` = `0xd6d3a567` was correct only for install B.) The seed is + NOT a binary constant (proven: no `0xa7ab`/`0x21b4`/`0xd6d3`/`0xa567` + immediate 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). +2. **`opaque_track_id_lo` is 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. +3. **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`/`.EXT` and the `.mp3` + bytes. Also not the USBANLZ folder id, not sequential, not evenly spaced. +4. **The values are runtime-derived, not binary constants.** A full + instruction-immediate scan of `rekordbox.exe` found NO occurrence of either + real seed (`0x21b4a7ab`, `0xd6d3a567`), the per-track lo values, or the + track-row `bitmask` constant `0xc0700`. 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). (`0x78f74a68` does 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 use `0x78f74a68` + (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-wheels` installs and wires up correctly. Its + master.db key is the single **global** rekordbox-6 key + `402fd482c38817c35ffa8ffb8c7d93143b749e7d315df7a81732a1ff43608497` + (`deobfuscate(BLOB)`, must start with `402fd`) — same for every install, NOT + per-install. So a VALID master.db *would* open and expose `DjmdContent`. +- BUT the captured `maple-py/captures/p17328_master.db` is **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.db` is 0 bytes. The Frida NtWriteFile + capture only caught scattered later pages, never the header. → unusable. +- The genuine `exportLibrary.db` shipped next to export.pdb (valid 16-byte salt + `ed0d3a3c…`) 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_lo` is almost certainly `DjmdContent.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.db` with 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 an `agentRegistry`/ + `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`, plus `set_local_usn`, `update_content_path`. But + `add_content` lets 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 actual + `export.pdb`/`exportLibrary.db` writing 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 real `DjmdContent.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):** +- `sequence` is 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 (so `header.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 `sequence` only as + informational ordering metadata; **they do not validate it** for playback. + No special value is required beyond keeping `header.sequence` greater than any + page's `sequence`. +- For maple it is enough to (a) pick a header `sequence` larger than every page + `sequence` it writes, and (b) give each page a `sequence` <= 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 seed `0x78f74a68` + 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(" 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/usb` must 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 REAL `data/acemo_fresh` as `present_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 writes `present_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 a + `a{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 literally `f"{artwork_id // 20 + 1:05d}"` and + `extract_artwork` writes 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-0x14 + `opaque_track_id_lo`, NOT a crc of path/title — tested) and the `P{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}`, then `write_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..PFFF` whereas rekordbox uses `P00..P7F` (0–127). Harmless; + optionally narrow to `& 0x7F` (`P{..:02X}`) to look more rekordbox-like. + +### 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) writes `a{id}.jpg`/`a{id}_m.jpg` at the + recorded path; `write_anlz` (cli.py:93) writes `ANLZ0000.DAT` at the recorded + `analyze_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.py` is NOT the production + path: it MIRRORS a reference rekordbox export (copies its `analyze_path`/ + artwork `path` strings verbatim, writes no ANLZ/artwork files), so the + `data/usb` export 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 set `next_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 whose `next_page` is 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 in + `data/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 set `next_unused` beyond + 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_candidate` is 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_candidate`s 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 = 0` for all 20 tables.** Page 0 is the FILE + HEADER. The firmware Page Manager, walking a table's empty-page list, follows + `0` → 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, and `0` is + 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 (so `next_unused > npages` is 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 left `empty_candidate = 0` with no free page to point at. +- Beyond-EOF links (a page's `next_page` ≥ npages, or `empty_candidate` ≥ + npages) are TOLERATED — the genuine `working` file 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 1f` LE) — the empty-heap free-map + (count == the `m1004`=1004 marker at header 0x24). `0x03ffffff` in 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(" `num_rows`.** + Reference row groups list offsets for dead slots too: e.g. tracks page 2 has + `nro=9, nrows=5` with offsets `[0,428,856,1324,1792,2224,2656,3112,3568]` + (slots 0,5,7,8 dead); artists page 6 has `nro=9, nrows=1`. maple emits exactly + `nro == nrows` with no dead slots. Reproducing dead offsets would mean + reproducing the edit history. + +7. **Live row in a non-zero slot.** + Reference artists page 6 keeps its single live row in **slot 8** (slots 0–7 + are dead). maple places the single live row in **slot 0**. A from-scratch + export also writes slot 0. + +8. **`free_size` / `used_size` totals on data pages.** + Reference `used+free` totals vary (4034, 4050, …) because `used_size` covers + the dead-row region and the strange-block markers differ. maple's pages total + a clean `4020` (= heap capacity for a single 36-byte row group). Direct + consequence of (6) — dead bytes are counted as "used". + +9. **Track row opaque id blob — `0x14`/`0x18`/`0x1a` (8 bytes/track).** + One 8-byte LE value `opaque_track_id_lo | (opaque_seed << 32)`, both halves + stored in rekordbox's internal master.db (a per-install `opaque_seed` + a + per-track `opaque_track_id_lo`). Proven non-derivable from track metadata + across every real export + an exhaustive negative hash search + (FORMAT_SPEC.md s2). **Every real rekordbox export has BOTH halves non-zero** + (this install's seed = `0x21b4a7ab`; `data/MAPLE` = `0xd6d3a567`); the + "unanalyzed default" lo=0/seed=0x78f74a68 was a false lead — it came from + maple's own earlier output (`data/usb`), not a real export. maple emits a + plausible non-zero stand-in of the right shape: `opaque_seed = 0x21b4a7ab` + (module const `TRACK_OPAQUE_SEED`, overridable per install) and a stable + per-track `opaque_track_id_lo` mapped into the real Content-ID band + (`OPAQUE_LO_MIN..MAX` = 0x08000000..0x0F900000, where every observed AceMo lo + lands) via `crc32(id|file_path)`. This will NOT match rekordbox's master.db + id byte-for-byte — unreproducible without its DB — but it is non-zero, + in-range and deterministic, the safest shape for deck acceptance. Players + treat the blob as opaque. + +10. **Strange-page free-map extension + per-page seq on tracks/history.** + On the tracks (type 0) and history (type 19) strange pages only, the + free-space map carries an extra accounting word (body offset 16: `01 00`, + offset 20: a u4 = the data page's used_size for history=320; tracks differs + because of its dead-row/overflow state). maple writes the plain free-map. + Tied to the same content/overflow state as items (1)/(2); not reproducible + for a compacted writer. (The rest of every strange page — header, free-map + head, 1004× `f8 ff ff 1f`, the data-page pointer word, and unknown7 — now + matches the fresh reference byte-for-byte.) + +11. **Table-directory `empty_candidate` (u32, table_dir entry +4) + header + `next_unused_page`.** Reference: a nonzero per-table `empty_candidate` + (e.g. 0x33, 0x04, 0x2e…); maple: 0. rekordcrate documents this field as + "maybe links to a chain of empty pages if the database is ever garbage + collected", and its fixtures show non-sequential free-pool page indices + (47, 4, 49, 8, 50, 46, 42, 16…). It is free-list/GC allocator state, not + derivable from content. `next_unused_page` (rekordcrate: "points past end of + file", unused) is the same class — see also item (1). + +> Canonical reference note: the diff target is now the FRESH export +> `data/acemo_fresh/PIONEER/rekordbox/export.pdb` (7 tracks). It is cleaner than +> the edited `data/acemo` (6 tracks) but still incrementally built per-file, so +> the divergences above still apply — just smaller (e.g. track 7's dead-row span +> is 464B not 3568B, but it still overflows to page 48). + +## What IS reproducible (NOW matched against the fresh reference) + +- The 20-table directory layout and page placement: an empty table is + strange-page-only; a table gets a data page IFF it has rows (`CORE_TABLES` is + now empty — the old genres data page was an edit-history artifact of the + edited ref). +- `page_flags`: data pages are `0x34` for **tracks and history only**, `0x24` + for every other table (the wider `{0,2,3,13,17,19}` was edited-ref noise); + empty data pages `0x44`; strange pages `0x64`. +- Strange-page body: the full DeviceSQL free-space map — `u4(page_index)`, + `u4(first_data_page or 0x03ffffff)`, the 12-byte const tail, 1004× + `f8 ff ff 1f`, 20 zero bytes — plus header `free=used=0`, `txc=txi=0x1fff`, + `next_page = page_index+1`, and `unknown7 = 1` for tracks/history else 0. +- Track row `index_shift` (`slot_index<<5`) and every other live-row content + field except the opaque blob in divergence item (9). +- Three header fields `re` decoded (FORMAT_SPEC §4/§5/§6) as edit-history state + whose FRESH-export value maple already writes — so they're matched on a fresh + export and only diverge against edited refs: + - file-header offset 16 (`_unnamed4`): fresh=`0` (maple writes 0); edited DBs + accumulate a small nonzero with no structural meaning. + - strange/data-page `unknown7` (0x26): fresh=`0` everywhere (maple writes 0); + nonzero only on edited DBs' strange pages. + - row-group trailing u2: it is the "rows-touched-in-last-transaction" bitmask. + On a fresh export it equals the row-present mask, so maple writes + `presence` (matches 12/14 groups in `data/acemo_fresh`; the 2 misses are + edited pages — divergence). NOTE: `re`'s "keep constant 16" advice came from + reading `data/usb`, which is maple's OWN output, not a real export — a real + fresh export uses the presence value, not 16. + +See `re/FORMAT_SPEC.md` for the value-construction rules and `re/README.md` for +the open RE questions. diff --git a/maple-py/re/REFERENCE_COMPARISON.md b/maple-py/re/REFERENCE_COMPARISON.md new file mode 100644 index 0000000..2749b93 --- /dev/null +++ b/maple-py/re/REFERENCE_COMPARISON.md @@ -0,0 +1,470 @@ +# 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/*`, this `re/` 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 == 0x5e` and + seeks `0x2a` for 21 string offsets (mod.rs ~986-996). maple's + `TRACK_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_options` and maple's `serialize_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.rs` roundtrips the + same three forms maple's `strings.py` writes: 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 is `0x03` in 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 in `pager.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 sets `next_page == that table's +> `empty_candidate`**, where `empty_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's `next_page` equalled its own +> dir `empty_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-map `data_ptr=0x03ffffff`) — maple's +> `build_strange_page` does this correctly. +> +> So maple's current #13 fix (a **single shared sentinel** for every data +> table + `empty_candidate=0` in the directory) removes the freeze but still +> **diverges** from real rekordbox, which uses a **distinct per-table +> `empty_candidate`** that the directory entry also records (nonzero). If +> firmware ever cross-checks `next_page` against the directory's +> `empty_candidate`, the shared sentinel + zero ec could still misbehave. +> Recommended (reported to `maple`): give each data table its own free page, +> set `next_page = that page` AND write that index into the table-dir +> `empty_candidate`, keep them all `< next_unused`. This is straightforwardly +> reproducible (unlike the *value* of `empty_candidate` on 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 value `0x23811c` + decodes (kaitai/maple way) to `num_row_offsets=284, num_rows=284` — **correct**. +- maple's packer reproduces `1c 81 23` **exactly** for n=284 (and `a6 c0 14` for + 166, `7e c0 0f` for 126 — all matching real pages byte-for-byte). +- rekordcrate's reading of the *same* bytes: `num_rows_small = 0x1c = 28` (just + the low byte) and `num_rows_large @0x22 = 283` (which is really the + `transaction_row_index` edit field), giving `calculate_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 + when `n <= 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_large` is an older/divergent decode that breaks for `n > 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 it `index_shift` but writes + whatever the caller stored — it does **not** know the value (mod.rs:104-105 + "Unknown field, called `index_shift` by flesniak"). maple **derives** it as + `slot_index << 5` and 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 fixed `788224` (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 vs `data/acemo_fresh` and the real strange pages in data/PIONEER), + including the `f8 ff ff 1f`×1004 map, the `0x03ffffff` no-data sentinel, and + `unknown7=1` for tracks/history. `re` confirms 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 `16` placeholder + (`Default::default`, mod.rs:454) and does **not** decode it; `re` confirms + genuine rekordbox **never** uses 16 there — it is the "rows touched by the + last transaction" bitmask (FORMAT_SPEC §6). maple writes `present_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 `pdb` module 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 derives `BinWrite` on the waveform-column structs + (`WaveformPreviewColumn` etc., anlz.rs:368-428) and has a `write_encrypted` + for 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-16**BE**, big-endian throughout) matches the format both + references parse. The big-endian-for-ANLZ-vs-little-endian-for-pdb split that + `anlz.py` calls out is corroborated by both libraries. + +### GAPS maple must close for full faithfulness +1. **No `.EXT` file.** Modern CDJs/XDJs (NXS2+) read `ANLZ0000.EXT` for 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. The `analyze_path` in the pdb points at the `.DAT`; the player + derives the `.EXT` sibling. maple writes neither beyond the `.DAT` PPTH stub. +2. **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. +3. **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 `BinWrite` waveform-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. +4. **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). `re` validated the concrete key (FORMAT_SPEC §2 + Round-3): `402fd482c38817c35ffa8ffb8c7d93143b749e7d315df7a81732a1ff43608497`. + pyrekordbox 0.4.4 + `sqlcipher3-wheels` installs and wires up correctly, so a + **valid** master.db opens and exposes `DjmdContent` `[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; plus `djmdCue`, `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.db` is **corrupt/truncated** +> (zeroed SQLCipher salt + page 1) — no key can open it. The genuine +> `exportLibrary.db` shipped 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 & 0xFFFFFFFF` is 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_content` lets *rekordbox* assign the Content ID — there is +> **no API to force a specific ID** — and it does **not** drive a USB export +> (`get_device` is 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.db` and read + `DjmdContent.ID`** per track, then write those into the pdb blob instead of + the stand-in. `re` validated the *mechanism* (global key, DjmdContent schema) + but could NOT confirm `ID == opaque_track_id_lo` here 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 **seed** + `0x21b4a7ab` source is still unidentified, so maple keeps the configurable + `TRACK_OPAQUE_SEED` either 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_content` cannot **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), and + `DJMMYSETTING.DAT` (52B, DJM mixer) — dict-style get/set + save. (`DEVSETTING` + payload still partly ununderstood.) +- **rekordcrate:** `setting` module **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.jpg`, plus optional `/PIONEER/*SETTING.DAT`. +maple writes `PIONEER/rekordbox/export.pdb` (`cli.py:156`) and +`PIONEER/USBANLZ///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` (+ a `a{id}_m.jpg` 240px + thumb), 20 ids per bucket folder (bucket 1 = ids 1-19). `re` verified + 176/176 in data/MAPLE, and maple's `artwork._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.DAT` where 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 — `re` ruled 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 own + `P{(crc32(path)>>20)&0xFFF:03X}/{crc32(path):08X}` and writes the `.DAT` + there, so the pdb's `analyze_path` and 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.py` MIRRORS a +> reference export's paths verbatim and writes **no** ANLZ/artwork files (it's a +> byte-diff fixture builder). The **production** `cli.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 + +1. **(high, lead — untested) Real opaque Content IDs via pyrekordbox/master.db** + — mechanism validated by `re` (global SQLCipher key `402fd482…` + + `Rekordbox6Database` reads `DjmdContent`), but `DjmdContent.ID == + opaque_track_id_lo` is a **strong INFERENCE, NOT validated** (the only + captured master.db is corrupt — zeroed salt — so `re` never 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]` +2. **(OPEN — Ghidra trace pending) Per-install `opaque_seed` source.** The seed + (`0x21b4a7ab` / `0xd6d3a567`) is **NOT** a `DjmdContent` column and `re` has + **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 (which `re` can now attempt — the Ghidra MCP just came + online), so it is **not permanently blocked.** Until resolved, maple keeps the + configurable `TRACK_OPAQUE_SEED` stand-in. This is the one genuinely open + question touching the opaque blob. `[see re #10 — OPEN; FORMAT_SPEC §2 Round-3]` +3. **(high) ANLZ beat grid (PQTZ) + an `.EXT` colour waveform (PWV5)** — needs + DSP maple must build; references validate the byte format only. Biggest + functional gap on modern players. +4. **(structural — in progress) Per-table `empty_candidate` / last-page + `next_page`** — the FREEZE fix (#13). maple must point each table's last page + at a distinct free page recorded in the dir's `empty_candidate`, not a shared + sentinel with `empty_candidate=0`. See the FREEZE section in §1. +5. **(low) mysettings** — optional; pyrekordbox can build them. +6. **(low/skip) exportExt.pdb** — optional My-Tag data; only if maple adds tags. +7. **(reference, not a gap) Use rekordcrate as a read-back oracle** — parse + maple's `export.pdb` with 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 (the `next_page` self-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. diff --git a/maple-py/re/findings/decrow.txt b/maple-py/re/findings/decrow.txt new file mode 100644 index 0000000..1452ee9 --- /dev/null +++ b/maple-py/re/findings/decrow.txt @@ -0,0 +1,2155 @@ + +//===== FUN_141c04be0 @ 141c04be0 [AnalysisDataPath/SearchStr/MasterContentID mapper] ===== + +/* WARNING: Function: __security_check_cookie replaced with injection: security_check_cookie */ +/* WARNING: Type propagation algorithm not settling */ + +int FUN_141c04be0(undefined8 param_1) + +{ + code *pcVar1; + bool bVar2; + bool bVar3; + bool bVar4; + byte bVar5; + byte bVar6; + byte bVar7; + byte bVar8; + byte bVar9; + byte bVar10; + byte bVar11; + bool bVar12; + bool bVar13; + bool bVar14; + bool bVar15; + byte bVar16; + byte bVar17; + byte bVar18; + byte bVar19; + byte bVar20; + undefined8 uVar21; + undefined8 uVar22; + longlong lVar23; + longlong lVar24; + longlong *plVar25; + undefined4 *puVar26; + undefined2 *puVar27; + char *pcVar28; + undefined1 *puVar29; + bool bVar30; + undefined1 auStack_798 [32]; + undefined8 local_778; + undefined8 local_770; + undefined4 local_768; + undefined8 local_760; + char local_758 [4]; + int local_754; + undefined1 local_750 [4]; + int local_74c [3]; + undefined1 local_740 [8]; + int local_738; + undefined1 local_730 [8]; + undefined8 local_728; + undefined1 local_720 [8]; + undefined1 local_718 [8]; + undefined1 local_710 [8]; + longlong local_708; + undefined1 local_700 [8]; + undefined1 local_6f8 [8]; + undefined1 local_6f0 [8]; + undefined1 local_6e8 [8]; + undefined1 local_6e0 [8]; + undefined1 local_6d8 [8]; + undefined1 local_6d0 [8]; + undefined1 local_6c8 [8]; + undefined1 local_6c0 [8]; + undefined1 local_6b8 [8]; + undefined1 local_6b0 [8]; + undefined1 local_6a8 [8]; + undefined1 local_6a0 [8]; + undefined1 local_698 [8]; + undefined1 local_690 [8]; + undefined1 local_688 [8]; + undefined1 local_680 [8]; + undefined1 local_678 [8]; + undefined1 local_670 [8]; + undefined1 local_668 [8]; + undefined1 local_660 [8]; + undefined1 local_658 [8]; + undefined1 local_650 [8]; + undefined1 local_648 [8]; + undefined1 local_640 [8]; + undefined1 local_638 [8]; + undefined1 local_630 [8]; + undefined1 local_628 [8]; + undefined1 local_620 [8]; + undefined1 local_618 [8]; + undefined1 local_610 [8]; + undefined1 local_608 [8]; + undefined1 local_600 [8]; + undefined1 local_5f8 [8]; + undefined1 local_5f0 [8]; + undefined1 local_5e8 [8]; + undefined1 local_5e0 [8]; + undefined1 local_5d8 [8]; + undefined1 local_5d0 [8]; + undefined1 local_5c8 [8]; + undefined1 local_5c0 [8]; + undefined1 local_5b8 [8]; + undefined1 local_5b0 [8]; + undefined1 local_5a8 [8]; + undefined1 local_5a0 [8]; + undefined1 local_598 [8]; + undefined1 local_590 [8]; + undefined1 local_588 [8]; + undefined1 local_580 [8]; + undefined1 local_578 [8]; + undefined1 local_570 [8]; + undefined1 local_568 [8]; + undefined1 local_560 [8]; + undefined1 local_558 [8]; + undefined1 local_550 [8]; + undefined1 local_548 [8]; + undefined1 local_540 [8]; + undefined1 local_538 [8]; + undefined1 local_530 [8]; + undefined1 local_528 [8]; + undefined1 local_520 [8]; + undefined1 local_518 [8]; + undefined1 local_510 [8]; + undefined1 local_508 [8]; + undefined1 local_500 [8]; + undefined1 local_4f8 [8]; + undefined1 local_4f0 [8]; + undefined1 local_4e8 [8]; + undefined1 local_4e0 [8]; + undefined1 local_4d8 [8]; + undefined1 local_4d0 [8]; + undefined1 local_4c8 [8]; + undefined1 local_4c0 [8]; + undefined1 local_4b8 [8]; + undefined1 local_4b0 [8]; + undefined1 local_4a8 [8]; + undefined1 local_4a0 [8]; + undefined1 local_498 [8]; + undefined1 local_490 [8]; + undefined1 local_488 [8]; + undefined1 local_480 [8]; + undefined1 local_478 [8]; + undefined1 local_470 [8]; + undefined1 local_468 [8]; + undefined1 local_460 [8]; + undefined1 local_458 [8]; + undefined1 local_450 [8]; + undefined1 local_448 [8]; + undefined1 local_440 [8]; + undefined1 local_438 [8]; + undefined1 local_430 [8]; + undefined1 local_428 [8]; + undefined1 local_420 [8]; + undefined1 local_418 [8]; + undefined1 local_410 [8]; + undefined1 local_408 [8]; + undefined1 local_400 [8]; + undefined1 local_3f8 [8]; + undefined1 local_3f0 [8]; + undefined1 local_3e8 [8]; + undefined1 local_3e0 [8]; + undefined1 local_3d8 [8]; + undefined1 local_3d0 [8]; + undefined1 local_3c8 [8]; + undefined1 local_3c0 [8]; + undefined1 local_3b8 [8]; + undefined1 local_3b0 [8]; + undefined1 local_3a8 [8]; + undefined1 local_3a0 [8]; + undefined1 local_398 [8]; + undefined1 local_390 [8]; + undefined1 local_388 [8]; + longlong local_380; + undefined1 local_378 [16]; + undefined1 local_368 [16]; + undefined1 local_358 [16]; + undefined1 local_348 [16]; + undefined1 local_338 [16]; + undefined1 local_328 [16]; + undefined1 local_318 [16]; + undefined1 local_308 [16]; + undefined1 local_2f8 [16]; + undefined1 local_2e8 [16]; + undefined1 local_2d8 [16]; + undefined1 local_2c8 [16]; + undefined1 local_2b8 [16]; + undefined1 local_2a8 [16]; + undefined1 local_298 [16]; + undefined1 local_288 [16]; + undefined1 local_278 [16]; + undefined1 local_268 [16]; + undefined1 local_258 [16]; + undefined1 local_248 [16]; + undefined1 local_238 [16]; + undefined1 local_228 [16]; + undefined1 local_218 [16]; + undefined1 local_208 [16]; + undefined1 local_1f8 [16]; + undefined1 local_1e8 [16]; + undefined1 local_1d8 [16]; + undefined1 local_1c8 [16]; + undefined1 local_1b8 [16]; + undefined1 local_1a8 [16]; + undefined1 local_198 [16]; + undefined1 local_188 [16]; + undefined1 local_178 [16]; + undefined1 local_168 [16]; + undefined1 local_158 [16]; + undefined1 local_148 [16]; + undefined1 local_138 [16]; + undefined1 local_128 [16]; + undefined1 local_118 [16]; + undefined1 local_108 [16]; + undefined1 local_f8 [16]; + undefined1 local_e8 [16]; + undefined1 local_d8 [16]; + undefined1 local_c8 [16]; + undefined1 local_b8 [16]; + undefined1 local_a8 [16]; + undefined1 local_98 [16]; + undefined1 local_88 [16]; + undefined1 local_78 [16]; + undefined1 local_68 [16]; + undefined1 local_58 [16]; + undefined1 local_48 [16]; + ulonglong local_38; + + local_38 = DAT_14519c398 ^ (ulonglong)auStack_798; + bVar19 = 0; + bVar17 = 0; + bVar14 = false; + bVar12 = false; + bVar11 = 0; + bVar10 = 0; + bVar9 = 0; + bVar8 = 0; + bVar7 = 0; + bVar6 = 0; + local_74c[1] = 0; + local_728 = param_1; + local_754 = thunk_FUN_1420de9e0(0); + if (local_754 == 0) { + uVar21 = thunk_FUN_141d75760(1); + local_760 = 0; + local_768 = 0; + local_770 = 0; + local_778 = 0; + lVar23 = thunk_FUN_1420dcbb8(&local_754,uVar21,0,0); + local_708 = lVar23; + if ((local_754 == 0) && (lVar23 != 0)) { + local_74c[0] = 0; + local_754 = thunk_FUN_1420dd31c(lVar23,local_74c); + if ((local_754 == 0) && (local_74c[0] != 0)) { + local_74c[1] = 0; + local_750[0] = 1; + thunk_FUN_1420dd10c(lVar23,1,local_74c + 1,local_750); + local_758[0] = '\0'; + FUN_1423c3af0(local_378); + local_738 = 0; + bVar3 = bVar12; + bVar13 = bVar12; + bVar4 = bVar14; + bVar15 = bVar14; + bVar16 = bVar17; + bVar18 = bVar17; + if (0 < local_74c[0]) { + do { + plVar25 = (longlong *)0x0; + lVar24 = thunk_FUN_1420dd538(lVar23,&local_754); + param_1 = local_728; + if ((local_754 != 0) || (lVar24 == 0)) break; + local_380 = FUN_14217c644(0x20); + if (local_380 != 0) { + plVar25 = (longlong *)FUN_1423c1e00(local_380); + } + local_758[0] = '\0'; + pcVar1 = *(code **)(*plVar25 + 0x18); + puVar26 = (undefined4 *)thunk_FUN_1420dd5e4(lVar24,0,local_758); + FUN_1423c3a40(local_368,*puVar26); + FUN_1423d68a0(local_588,&DAT_143293ecc); + (*pcVar1)(plVar25,local_588,local_368); + FUN_1423d69f0(local_588); + FUN_1423c3ab0(local_368); + local_758[0] = '\0'; + pcVar1 = *(code **)(*plVar25 + 0x18); + lVar23 = thunk_FUN_1420dd5e4(lVar24,1,local_758); + if (lVar23 == 0) { + uVar21 = FUN_1423d59e0(local_398); + bVar3 = true; + } + else { + uVar21 = FUN_1423d56a0(local_390,lVar23); + bVar4 = true; + } + FUN_1423d58b0(local_508,uVar21); + if (bVar3) { + bVar3 = false; + FUN_1423d59b0(local_398); + } + if (bVar4) { + bVar4 = false; + FUN_1423d59b0(local_390); + } + uVar21 = FUN_1419f6820(local_388,local_508); + FUN_1423c2f60(local_358,uVar21); + FUN_1423d68a0(local_510,"FolderPath"); + (*pcVar1)(plVar25,local_510,local_358); + FUN_1423d69f0(local_510); + FUN_1423c3ab0(local_358); + FUN_1423d59b0(local_388); + FUN_1423d59b0(local_508); + local_758[0] = '\0'; + pcVar1 = *(code **)(*plVar25 + 0x18); + lVar23 = thunk_FUN_1420dd5e4(lVar24,2,local_758); + bVar30 = lVar23 == 0; + if (bVar30) { + uVar21 = FUN_1423d59e0(local_4e0); + } + else { + uVar21 = FUN_1423d56a0(local_4d8,lVar23); + } + FUN_1423d58b0(local_4f8,uVar21); + bVar5 = bVar30 | bVar16; + if ((bool)(bVar30 | bVar16)) { + bVar5 = 0; + FUN_1423d59b0(local_4e0); + } + if (!bVar30) { + FUN_1423d59b0(local_4d8); + } + FUN_1423c2f60(local_348,local_4f8); + FUN_1423d68a0(local_500,"FileNameL"); + (*pcVar1)(plVar25,local_500,local_348); + FUN_1423d69f0(local_500); + FUN_1423c3ab0(local_348); + FUN_1423d59b0(local_4f8); + local_758[0] = '\0'; + pcVar1 = *(code **)(*plVar25 + 0x18); + lVar23 = thunk_FUN_1420dd5e4(lVar24,3,local_758); + bVar30 = lVar23 == 0; + if (bVar30) { + uVar21 = FUN_1423d59e0(local_4d0); + } + else { + uVar21 = FUN_1423d56a0(local_4c8,lVar23); + } + FUN_1423d58b0(local_700,uVar21); + bVar2 = (bool)(bVar7 | bVar30); + bVar7 = bVar7 | bVar30; + if (bVar2) { + bVar7 = 0; + FUN_1423d59b0(local_4d0); + } + if (!bVar30) { + FUN_1423d59b0(local_4c8); + } + FUN_1423c2f60(local_338,local_700); + FUN_1423d68a0(local_4f0,"FileNameS"); + (*pcVar1)(plVar25,local_4f0,local_338); + FUN_1423d69f0(local_4f0); + FUN_1423c3ab0(local_338); + FUN_1423d59b0(local_700); + local_758[0] = '\0'; + pcVar1 = *(code **)(*plVar25 + 0x18); + lVar23 = thunk_FUN_1420dd5e4(lVar24,4,local_758); + bVar30 = lVar23 == 0; + if (bVar30) { + uVar21 = FUN_1423d59e0(local_4c0); + } + else { + uVar21 = FUN_1423d56a0(local_4b8,lVar23); + } + FUN_1423d58b0(local_6f0,uVar21); + bVar2 = (bool)(bVar8 | bVar30); + bVar8 = bVar8 | bVar30; + if (bVar2) { + bVar8 = 0; + FUN_1423d59b0(local_4c0); + } + if (!bVar30) { + FUN_1423d59b0(local_4b8); + } + FUN_1423c2f60(local_328,local_6f0); + FUN_1423d68a0(local_6f8,"Title"); + (*pcVar1)(plVar25,local_6f8,local_328); + FUN_1423d69f0(local_6f8); + FUN_1423c3ab0(local_328); + FUN_1423d59b0(local_6f0); + local_758[0] = '\0'; + pcVar1 = *(code **)(*plVar25 + 0x18); + puVar26 = (undefined4 *)thunk_FUN_1420dd5e4(lVar24,5,local_758); + FUN_1423c3a40(local_318,*puVar26); + FUN_1423d68a0(local_6e8,"ArtistID"); + (*pcVar1)(plVar25,local_6e8,local_318); + FUN_1423d69f0(local_6e8); + FUN_1423c3ab0(local_318); + local_758[0] = '\0'; + pcVar1 = *(code **)(*plVar25 + 0x18); + puVar26 = (undefined4 *)thunk_FUN_1420dd5e4(lVar24,6,local_758); + FUN_1423c3a40(local_308,*puVar26); + FUN_1423d68a0(local_6e0,"AlbumID"); + (*pcVar1)(plVar25,local_6e0,local_308); + FUN_1423d69f0(local_6e0); + FUN_1423c3ab0(local_308); + local_758[0] = '\0'; + pcVar1 = *(code **)(*plVar25 + 0x18); + puVar26 = (undefined4 *)thunk_FUN_1420dd5e4(lVar24,7,local_758); + FUN_1423c3a40(local_2f8,*puVar26); + FUN_1423d68a0(local_6d8,"GenreID"); + (*pcVar1)(plVar25,local_6d8,local_2f8); + FUN_1423d69f0(local_6d8); + FUN_1423c3ab0(local_2f8); + local_758[0] = '\0'; + pcVar1 = *(code **)(*plVar25 + 0x18); + puVar26 = (undefined4 *)thunk_FUN_1420dd5e4(lVar24,8,local_758); + FUN_1423c3a40(local_2e8,*puVar26); + FUN_1423d68a0(local_6d0,&DAT_143293f28); + (*pcVar1)(plVar25,local_6d0,local_2e8); + FUN_1423d69f0(local_6d0); + FUN_1423c3ab0(local_2e8); + local_758[0] = '\0'; + pcVar1 = *(code **)(*plVar25 + 0x18); + puVar27 = (undefined2 *)thunk_FUN_1420dd5e4(lVar24,9,local_758); + FUN_1423c3a60(local_2d8,*puVar27); + FUN_1423d68a0(local_6c8,"Length"); + (*pcVar1)(plVar25,local_6c8,local_2d8); + FUN_1423d69f0(local_6c8); + FUN_1423c3ab0(local_2d8); + local_758[0] = '\0'; + pcVar1 = *(code **)(*plVar25 + 0x18); + puVar26 = (undefined4 *)thunk_FUN_1420dd5e4(lVar24,10,local_758); + FUN_1423c3a40(local_2c8,*puVar26); + FUN_1423d68a0(local_6c0,"TrackNo"); + (*pcVar1)(plVar25,local_6c0,local_2c8); + FUN_1423d69f0(local_6c0); + FUN_1423c3ab0(local_2c8); + local_758[0] = '\0'; + pcVar1 = *(code **)(*plVar25 + 0x18); + puVar26 = (undefined4 *)thunk_FUN_1420dd5e4(lVar24,0xb,local_758); + FUN_1423c3a40(local_2b8,*puVar26); + FUN_1423d68a0(local_6b8,"BitRate"); + (*pcVar1)(plVar25,local_6b8,local_2b8); + FUN_1423d69f0(local_6b8); + FUN_1423c3ab0(local_2b8); + local_758[0] = '\0'; + pcVar1 = *(code **)(*plVar25 + 0x18); + puVar27 = (undefined2 *)thunk_FUN_1420dd5e4(lVar24,0xc,local_758); + FUN_1423c3a60(local_2a8,*puVar27); + FUN_1423d68a0(local_6b0,"BitDepth"); + (*pcVar1)(plVar25,local_6b0,local_2a8); + FUN_1423d69f0(local_6b0); + FUN_1423c3ab0(local_2a8); + local_758[0] = '\0'; + pcVar1 = *(code **)(*plVar25 + 0x18); + lVar23 = thunk_FUN_1420dd5e4(lVar24,0xd,local_758); + if (lVar23 == 0) { + uVar21 = FUN_1423d59e0(local_4b0); + } + else { + uVar21 = FUN_1423d56a0(local_4a8,lVar23); + } + FUN_1423d58b0(local_6a0,uVar21); + if (lVar23 == 0) { + FUN_1423d59b0(local_4b0); + } + else { + FUN_1423d59b0(local_4a8); + } + FUN_1423c2f60(local_298,local_6a0); + FUN_1423d68a0(local_6a8,"Commnt"); + (*pcVar1)(plVar25,local_6a8,local_298); + FUN_1423d69f0(local_6a8); + FUN_1423c3ab0(local_298); + FUN_1423d59b0(local_6a0); + local_758[0] = '\0'; + pcVar1 = *(code **)(*plVar25 + 0x18); + pcVar28 = (char *)thunk_FUN_1420dd5e4(lVar24,0xe,local_758); + FUN_1423c3a60(local_288,(int)*pcVar28); + FUN_1423d68a0(local_698,"FileType"); + (*pcVar1)(plVar25,local_698,local_288); + FUN_1423d69f0(local_698); + FUN_1423c3ab0(local_288); + local_758[0] = '\0'; + pcVar1 = *(code **)(*plVar25 + 0x18); + pcVar28 = (char *)thunk_FUN_1420dd5e4(lVar24,0xf,local_758); + FUN_1423c3a60(local_278,(int)*pcVar28); + FUN_1423d68a0(local_690,"Rating"); + (*pcVar1)(plVar25,local_690,local_278); + FUN_1423d69f0(local_690); + FUN_1423c3ab0(local_278); + local_758[0] = '\0'; + pcVar1 = *(code **)(*plVar25 + 0x18); + puVar27 = (undefined2 *)thunk_FUN_1420dd5e4(lVar24,0x10,local_758); + FUN_1423c3a60(local_268,*puVar27); + FUN_1423d68a0(local_688,"ReleaseYear"); + (*pcVar1)(plVar25,local_688,local_268); + FUN_1423d69f0(local_688); + FUN_1423c3ab0(local_268); + local_758[0] = '\0'; + pcVar1 = *(code **)(*plVar25 + 0x18); + puVar26 = (undefined4 *)thunk_FUN_1420dd5e4(lVar24,0x11,local_758); + FUN_1423c3a40(local_258,*puVar26); + FUN_1423d68a0(local_680,"RemixerID"); + (*pcVar1)(plVar25,local_680,local_258); + FUN_1423d69f0(local_680); + FUN_1423c3ab0(local_258); + local_758[0] = '\0'; + pcVar1 = *(code **)(*plVar25 + 0x18); + puVar26 = (undefined4 *)thunk_FUN_1420dd5e4(lVar24,0x12,local_758); + FUN_1423c3a40(local_248,*puVar26); + FUN_1423d68a0(local_678,"LabelID"); + (*pcVar1)(plVar25,local_678,local_248); + FUN_1423d69f0(local_678); + FUN_1423c3ab0(local_248); + local_758[0] = '\0'; + pcVar1 = *(code **)(*plVar25 + 0x18); + puVar26 = (undefined4 *)thunk_FUN_1420dd5e4(lVar24,0x13,local_758); + FUN_1423c3a40(local_238,*puVar26); + FUN_1423d68a0(local_670,"OrgArtistID"); + (*pcVar1)(plVar25,local_670,local_238); + FUN_1423d69f0(local_670); + FUN_1423c3ab0(local_238); + local_758[0] = '\0'; + pcVar1 = *(code **)(*plVar25 + 0x18); + puVar26 = (undefined4 *)thunk_FUN_1420dd5e4(lVar24,0x14,local_758); + FUN_1423c3a40(local_228,*puVar26); + FUN_1423d68a0(local_668,"KeyID"); + (*pcVar1)(plVar25,local_668,local_228); + FUN_1423d69f0(local_668); + FUN_1423c3ab0(local_228); + local_758[0] = '\0'; + pcVar1 = *(code **)(*plVar25 + 0x18); + lVar23 = thunk_FUN_1420dd5e4(lVar24,0x15,local_758); + bVar30 = lVar23 == 0; + if (bVar30) { + uVar21 = FUN_1423d59e0(local_4a0); + } + else { + uVar21 = FUN_1423d56a0(local_498,lVar23); + } + FUN_1423d58b0(local_658,uVar21); + bVar2 = (bool)(bVar9 | bVar30); + bVar9 = bVar9 | bVar30; + if (bVar2) { + bVar9 = 0; + FUN_1423d59b0(local_4a0); + } + if (!bVar30) { + FUN_1423d59b0(local_498); + } + FUN_1423c2f60(local_218,local_658); + FUN_1423d68a0(local_660,"StockDate"); + (*pcVar1)(plVar25,local_660,local_218); + FUN_1423d69f0(local_660); + FUN_1423c3ab0(local_218); + FUN_1423d59b0(local_658); + local_758[0] = '\0'; + pcVar1 = *(code **)(*plVar25 + 0x18); + pcVar28 = (char *)thunk_FUN_1420dd5e4(lVar24,0x16,local_758); + FUN_1423c3a60(local_208,(int)*pcVar28); + FUN_1423d68a0(local_650,"ColorID"); + (*pcVar1)(plVar25,local_650,local_208); + FUN_1423d69f0(local_650); + FUN_1423c3ab0(local_208); + local_758[0] = '\0'; + pcVar1 = *(code **)(*plVar25 + 0x18); + puVar27 = (undefined2 *)thunk_FUN_1420dd5e4(lVar24,0x17,local_758); + FUN_1423c3a60(local_1f8,*puVar27); + FUN_1423d68a0(local_648,"DjPlayCount"); + (*pcVar1)(plVar25,local_648,local_1f8); + FUN_1423d69f0(local_648); + FUN_1423c3ab0(local_1f8); + local_758[0] = '\0'; + pcVar1 = *(code **)(*plVar25 + 0x18); + puVar26 = (undefined4 *)thunk_FUN_1420dd5e4(lVar24,0x18,local_758); + FUN_1423c3a40(local_1e8,*puVar26); + FUN_1423d68a0(local_640,"ImageID"); + (*pcVar1)(plVar25,local_640,local_1e8); + FUN_1423d69f0(local_640); + FUN_1423c3ab0(local_1e8); + local_758[0] = '\0'; + pcVar1 = *(code **)(*plVar25 + 0x18); + puVar26 = (undefined4 *)thunk_FUN_1420dd5e4(lVar24,0x19,local_758); + FUN_1423c3a40(local_1d8,*puVar26); + FUN_1423d68a0(local_638,"MasterDBID"); + (*pcVar1)(plVar25,local_638,local_1d8); + FUN_1423d69f0(local_638); + FUN_1423c3ab0(local_1d8); + local_758[0] = '\0'; + pcVar1 = *(code **)(*plVar25 + 0x18); + puVar26 = (undefined4 *)thunk_FUN_1420dd5e4(lVar24,0x1a,local_758); + FUN_1423c3a40(local_1c8,*puVar26); + FUN_1423d68a0(local_630,"MasterContentID"); + (*pcVar1)(plVar25,local_630,local_1c8); + FUN_1423d69f0(local_630); + FUN_1423c3ab0(local_1c8); + local_758[0] = '\0'; + pcVar1 = *(code **)(*plVar25 + 0x18); + lVar23 = thunk_FUN_1420dd5e4(lVar24,0x1b,local_758); + bVar30 = lVar23 == 0; + if (bVar30) { + uVar21 = FUN_1423d59e0(local_490); + } + else { + uVar21 = FUN_1423d56a0(local_488,lVar23); + } + FUN_1423d58b0(local_620,uVar21); + bVar2 = (bool)(bVar30 | bVar10); + bVar10 = bVar30 | bVar10; + if (bVar2) { + bVar10 = 0; + FUN_1423d59b0(local_490); + } + if (!bVar30) { + FUN_1423d59b0(local_488); + } + FUN_1423c2f60(local_1b8,local_620); + FUN_1423d68a0(local_628,"AnalysisDataPath"); + (*pcVar1)(plVar25,local_628,local_1b8); + FUN_1423d69f0(local_628); + FUN_1423c3ab0(local_1b8); + FUN_1423d59b0(local_620); + local_758[0] = '\0'; + pcVar1 = *(code **)(*plVar25 + 0x18); + lVar23 = thunk_FUN_1420dd5e4(lVar24,0x1c,local_758); + bVar30 = lVar23 == 0; + if (bVar30) { + uVar21 = FUN_1423d59e0(local_480); + } + else { + uVar21 = FUN_1423d56a0(local_478,lVar23); + } + FUN_1423d58b0(local_610,uVar21); + bVar2 = (bool)(bVar30 | bVar11); + bVar11 = bVar30 | bVar11; + if (bVar2) { + bVar11 = 0; + FUN_1423d59b0(local_480); + } + if (!bVar30) { + FUN_1423d59b0(local_478); + } + FUN_1423c2f60(local_1a8,local_610); + FUN_1423d68a0(local_618,"SearchStr"); + (*pcVar1)(plVar25,local_618,local_1a8); + FUN_1423d69f0(local_618); + FUN_1423c3ab0(local_1a8); + FUN_1423d59b0(local_610); + local_758[0] = '\0'; + pcVar1 = *(code **)(*plVar25 + 0x18); + puVar26 = (undefined4 *)thunk_FUN_1420dd5e4(lVar24,0x1d,local_758); + FUN_1423c3a40(local_198,*puVar26); + FUN_1423d68a0(local_608,"FileSize"); + (*pcVar1)(plVar25,local_608,local_198); + FUN_1423d69f0(local_608); + FUN_1423c3ab0(local_198); + local_758[0] = '\0'; + pcVar1 = *(code **)(*plVar25 + 0x18); + puVar27 = (undefined2 *)thunk_FUN_1420dd5e4(lVar24,0x1e,local_758); + FUN_1423c3a60(local_188,*puVar27); + FUN_1423d68a0(local_600,"DiscNo"); + (*pcVar1)(plVar25,local_600,local_188); + FUN_1423d69f0(local_600); + FUN_1423c3ab0(local_188); + local_758[0] = '\0'; + pcVar1 = *(code **)(*plVar25 + 0x18); + puVar26 = (undefined4 *)thunk_FUN_1420dd5e4(lVar24,0x1f,local_758); + FUN_1423c3a40(local_178,*puVar26); + FUN_1423d68a0(local_5f8,"ComposerID"); + (*pcVar1)(plVar25,local_5f8,local_178); + FUN_1423d69f0(local_5f8); + FUN_1423c3ab0(local_178); + local_758[0] = '\0'; + pcVar1 = *(code **)(*plVar25 + 0x18); + lVar23 = thunk_FUN_1420dd5e4(lVar24,0x20,local_758); + if (lVar23 == 0) { + uVar21 = FUN_1423d59e0(local_470); + bVar12 = true; + } + else { + uVar21 = FUN_1423d56a0(local_468,lVar23); + bVar14 = true; + } + FUN_1423d58b0(local_5e8,uVar21); + if (bVar12) { + bVar12 = false; + FUN_1423d59b0(local_470); + } + if (bVar14) { + bVar14 = false; + FUN_1423d59b0(local_468); + } + FUN_1423c2f60(local_168,local_5e8); + FUN_1423d68a0(local_5f0,"Subtitle"); + (*pcVar1)(plVar25,local_5f0,local_168); + FUN_1423d69f0(local_5f0); + FUN_1423c3ab0(local_168); + FUN_1423d59b0(local_5e8); + local_758[0] = '\0'; + pcVar1 = *(code **)(*plVar25 + 0x18); + puVar26 = (undefined4 *)thunk_FUN_1420dd5e4(lVar24,0x21,local_758); + FUN_1423c3a40(local_158,*puVar26); + FUN_1423d68a0(local_5e0,"SampleRate"); + (*pcVar1)(plVar25,local_5e0,local_158); + FUN_1423d69f0(local_5e0); + FUN_1423c3ab0(local_158); + local_758[0] = '\0'; + pcVar1 = *(code **)(*plVar25 + 0x18); + pcVar28 = (char *)thunk_FUN_1420dd5e4(lVar24,0x22,local_758); + FUN_1423c3a60(local_148,(int)*pcVar28); + FUN_1423d68a0(local_5d8,"DisableQuantize"); + (*pcVar1)(plVar25,local_5d8,local_148); + FUN_1423d69f0(local_5d8); + FUN_1423c3ab0(local_148); + local_758[0] = '\0'; + pcVar1 = *(code **)(*plVar25 + 0x18); + pcVar28 = (char *)thunk_FUN_1420dd5e4(lVar24,0x23,local_758); + FUN_1423c3a60(local_138,(int)*pcVar28); + FUN_1423d68a0(local_5d0,"Analysed"); + (*pcVar1)(plVar25,local_5d0,local_138); + FUN_1423d69f0(local_5d0); + FUN_1423c3ab0(local_138); + local_758[0] = '\0'; + pcVar1 = *(code **)(*plVar25 + 0x18); + lVar23 = thunk_FUN_1420dd5e4(lVar24,0x24,local_758); + bVar30 = lVar23 == 0; + if (bVar30) { + uVar21 = FUN_1423d59e0(local_460); + } + else { + uVar21 = FUN_1423d56a0(local_458,lVar23); + } + FUN_1423d58b0(local_5c0,uVar21); + bVar20 = !bVar30 | bVar19; + bVar16 = bVar30 | bVar17; + if ((bool)(bVar30 | bVar17)) { + bVar16 = 0; + FUN_1423d59b0(local_460); + } + if ((bool)(!bVar30 | bVar19)) { + bVar20 = 0; + FUN_1423d59b0(local_458); + } + FUN_1423c2f60(local_128,local_5c0); + FUN_1423d68a0(local_5c8,"ReleaseDate"); + (*pcVar1)(plVar25,local_5c8,local_128); + FUN_1423d69f0(local_5c8); + FUN_1423c3ab0(local_128); + FUN_1423d59b0(local_5c0); + local_758[0] = '\0'; + pcVar1 = *(code **)(*plVar25 + 0x18); + lVar23 = thunk_FUN_1420dd5e4(lVar24,0x25,local_758); + if (lVar23 == 0) { + uVar21 = FUN_1423d59e0(local_450); + } + else { + uVar21 = FUN_1423d56a0(local_448,lVar23); + } + FUN_1423d58b0(local_5b0,uVar21); + if (lVar23 == 0) { + FUN_1423d59b0(local_450); + } + else { + FUN_1423d59b0(local_448); + } + FUN_1423c2f60(local_118,local_5b0); + FUN_1423d68a0(local_5b8,"DateCreated"); + (*pcVar1)(plVar25,local_5b8,local_118); + FUN_1423d69f0(local_5b8); + FUN_1423c3ab0(local_118); + FUN_1423d59b0(local_5b0); + local_758[0] = '\0'; + pcVar1 = *(code **)(*plVar25 + 0x18); + puVar26 = (undefined4 *)thunk_FUN_1420dd5e4(lVar24,0x26,local_758); + FUN_1423c3a40(local_108,*puVar26); + FUN_1423d68a0(local_5a8,"ContentLink"); + (*pcVar1)(plVar25,local_5a8,local_108); + FUN_1423d69f0(local_5a8); + FUN_1423c3ab0(local_108); + local_758[0] = '\0'; + pcVar1 = *(code **)(*plVar25 + 0x18); + lVar23 = thunk_FUN_1420dd5e4(lVar24,0x27,local_758); + if (lVar23 == 0) { + uVar21 = FUN_1423d59e0(local_440); + } + else { + uVar21 = FUN_1423d56a0(local_438,lVar23); + } + FUN_1423d58b0(local_598,uVar21); + if (lVar23 == 0) { + FUN_1423d59b0(local_440); + } + else { + FUN_1423d59b0(local_438); + } + FUN_1423c2f60(local_f8,local_598); + FUN_1423d68a0(local_5a0,&DAT_143294164); + (*pcVar1)(plVar25,local_5a0,local_f8); + FUN_1423d69f0(local_5a0); + FUN_1423c3ab0(local_f8); + FUN_1423d59b0(local_598); + local_758[0] = '\0'; + pcVar1 = *(code **)(*plVar25 + 0x18); + lVar23 = thunk_FUN_1420dd5e4(lVar24,0x28,local_758); + if (lVar23 == 0) { + uVar21 = FUN_1423d59e0(local_430); + } + else { + uVar21 = FUN_1423d56a0(local_428,lVar23); + } + FUN_1423d58b0(local_538,uVar21); + if (lVar23 == 0) { + FUN_1423d59b0(local_430); + } + else { + FUN_1423d59b0(local_428); + } + FUN_1423c2f60(local_e8,local_538); + FUN_1423d68a0(local_590,"reserved1"); + (*pcVar1)(plVar25,local_590,local_e8); + FUN_1423d69f0(local_590); + FUN_1423c3ab0(local_e8); + FUN_1423d59b0(local_538); + local_758[0] = '\0'; + pcVar1 = *(code **)(*plVar25 + 0x18); + lVar23 = thunk_FUN_1420dd5e4(lVar24,0x29,local_758); + if (lVar23 == 0) { + uVar21 = FUN_1423d59e0(local_420); + } + else { + uVar21 = FUN_1423d56a0(local_418,lVar23); + } + FUN_1423d58b0(local_578,uVar21); + if (lVar23 == 0) { + FUN_1423d59b0(local_420); + } + else { + FUN_1423d59b0(local_418); + } + FUN_1423c2f60(local_d8,local_578); + FUN_1423d68a0(local_580,"reserved2"); + (*pcVar1)(plVar25,local_580,local_d8); + FUN_1423d69f0(local_580); + FUN_1423c3ab0(local_d8); + FUN_1423d59b0(local_578); + local_758[0] = '\0'; + pcVar1 = *(code **)(*plVar25 + 0x18); + lVar23 = thunk_FUN_1420dd5e4(lVar24,0x2a,local_758); + if (lVar23 == 0) { + uVar21 = FUN_1423d59e0(local_410); + } + else { + uVar21 = FUN_1423d56a0(local_408,lVar23); + } + FUN_1423d58b0(local_568,uVar21); + if (lVar23 == 0) { + FUN_1423d59b0(local_410); + } + else { + FUN_1423d59b0(local_408); + } + FUN_1423c2f60(local_c8,local_568); + FUN_1423d68a0(local_570,"reserved3"); + (*pcVar1)(plVar25,local_570,local_c8); + FUN_1423d69f0(local_570); + FUN_1423c3ab0(local_c8); + FUN_1423d59b0(local_568); + local_758[0] = '\0'; + pcVar1 = *(code **)(*plVar25 + 0x18); + lVar23 = thunk_FUN_1420dd5e4(lVar24,0x2b,local_758); + if (lVar23 == 0) { + uVar21 = FUN_1423d59e0(local_400); + } + else { + uVar21 = FUN_1423d56a0(local_3f8,lVar23); + } + FUN_1423d58b0(local_558,uVar21); + if (lVar23 == 0) { + FUN_1423d59b0(local_400); + } + else { + FUN_1423d59b0(local_3f8); + } + FUN_1423c2f60(local_b8,local_558); + FUN_1423d68a0(local_560,"reserved4"); + (*pcVar1)(plVar25,local_560,local_b8); + FUN_1423d69f0(local_560); + FUN_1423c3ab0(local_b8); + FUN_1423d59b0(local_558); + local_758[0] = '\0'; + pcVar1 = *(code **)(*plVar25 + 0x18); + lVar23 = thunk_FUN_1420dd5e4(lVar24,0x2c,local_758); + if (lVar23 == 0) { + uVar21 = FUN_1423d59e0(local_3f0); + bVar13 = true; + } + else { + uVar21 = FUN_1423d56a0(local_3e8,lVar23); + bVar15 = true; + } + FUN_1423d58b0(local_548,uVar21); + if (bVar13) { + bVar13 = false; + FUN_1423d59b0(local_3f0); + } + if (bVar15) { + bVar15 = false; + FUN_1423d59b0(local_3e8); + } + FUN_1423c2f60(local_a8,local_548); + FUN_1423d68a0(local_550,"reserved5"); + (*pcVar1)(plVar25,local_550,local_a8); + FUN_1423d69f0(local_550); + FUN_1423c3ab0(local_a8); + FUN_1423d59b0(local_548); + local_758[0] = '\0'; + pcVar1 = *(code **)(*plVar25 + 0x18); + lVar23 = thunk_FUN_1420dd5e4(lVar24,0x2d,local_758); + bVar30 = lVar23 == 0; + if (bVar30) { + uVar21 = FUN_1423d59e0(local_3e0); + } + else { + uVar21 = FUN_1423d56a0(local_3d8,lVar23); + } + FUN_1423d58b0(local_4e8,uVar21); + bVar19 = bVar18 | bVar30; + if ((bool)(bVar18 | bVar30)) { + bVar19 = 0; + FUN_1423d59b0(local_3e0); + } + if (!bVar30) { + FUN_1423d59b0(local_3d8); + } + FUN_1423c2f60(local_98,local_4e8); + FUN_1423d68a0(local_540,"reserved6"); + (*pcVar1)(plVar25,local_540,local_98); + FUN_1423d69f0(local_540); + FUN_1423c3ab0(local_98); + FUN_1423d59b0(local_4e8); + local_758[0] = '\0'; + pcVar1 = *(code **)(*plVar25 + 0x18); + lVar23 = thunk_FUN_1420dd5e4(lVar24,0x2e,local_758); + if (lVar23 == 0) { + uVar21 = FUN_1423d59e0(local_3d0); + } + else { + uVar21 = FUN_1423d56a0(local_3c8,lVar23); + } + FUN_1423d58b0(local_528,uVar21); + if (lVar23 == 0) { + FUN_1423d59b0(local_3d0); + } + else { + FUN_1423d59b0(local_3c8); + } + FUN_1423c2f60(local_88,local_528); + FUN_1423d68a0(local_530,"reserved7"); + (*pcVar1)(plVar25,local_530,local_88); + FUN_1423d69f0(local_530); + FUN_1423c3ab0(local_88); + FUN_1423d59b0(local_528); + local_758[0] = '\0'; + pcVar1 = *(code **)(*plVar25 + 0x18); + lVar23 = thunk_FUN_1420dd5e4(lVar24,0x2f,local_758); + if (lVar23 == 0) { + uVar21 = FUN_1423d59e0(local_3c0); + } + else { + uVar21 = FUN_1423d56a0(local_3b8,lVar23); + } + FUN_1423d58b0(local_518,uVar21); + if (lVar23 == 0) { + FUN_1423d59b0(local_3c0); + } + else { + FUN_1423d59b0(local_3b8); + } + FUN_1423c2f60(local_78,local_518); + FUN_1423d68a0(local_520,"reserved8"); + (*pcVar1)(plVar25,local_520,local_78); + FUN_1423d69f0(local_520); + FUN_1423c3ab0(local_78); + FUN_1423d59b0(local_518); + local_758[0] = '\0'; + pcVar28 = (char *)thunk_FUN_1420dd5e4(lVar24,0x30,local_758); + FUN_1423d59e0(local_730); + if ((pcVar28 == (char *)0x0) || (*pcVar28 != '\x03')) { +LAB_141c06ba8: + uVar21 = FUN_1423d59e0(local_3a8); + FUN_1423d5890(local_730,uVar21); + puVar29 = local_3a8; + } + else { + lVar23 = -1; + do { + lVar23 = lVar23 + 1; + } while ((pcVar28 + 1)[lVar23] != '\0'); + if (lVar23 != 0xc) goto LAB_141c06ba8; + uVar21 = FUN_1423ce870(local_3b0,pcVar28 + 1,0xc); + FUN_1423d5890(local_730,uVar21); + puVar29 = local_3b0; + } + FUN_1423d59b0(puVar29); + if (local_758[0] == '\x01') { + FUN_141d79520(pcVar28); + } + pcVar1 = *(code **)(*plVar25 + 0x18); + FUN_1423c3960(local_68,local_730); + FUN_1423d68a0(local_720,"reserved9"); + (*pcVar1)(plVar25,local_720,local_68); + FUN_1423d69f0(local_720); + FUN_1423c3ab0(local_68); + local_758[0] = '\0'; + pcVar1 = *(code **)(*plVar25 + 0x18); + lVar23 = thunk_FUN_1420dd5e4(lVar24,0x31,local_758); + bVar30 = lVar23 == 0; + if (bVar30) { + uVar21 = FUN_1423d59e0(local_3a0); + } + else { + uVar21 = FUN_1423d56a0(local_710,lVar23); + } + FUN_1423d58b0(local_740,uVar21); + bVar2 = (bool)(bVar6 | bVar30); + bVar6 = bVar6 | bVar30; + if (bVar2) { + bVar6 = 0; + FUN_1423d59b0(local_3a0); + } + if (!bVar30) { + FUN_1423d59b0(local_710); + } + FUN_1423c2f60(local_58,local_740); + FUN_1423d68a0(local_718,"reserved10"); + (*pcVar1)(plVar25,local_718,local_58); + FUN_1423d69f0(local_718); + FUN_1423c3ab0(local_58); + FUN_1423d59b0(local_740); + FUN_1423c35f0(local_48,plVar25); + FUN_1423c25c0(local_378,local_48); + FUN_1423c3ab0(local_48); + FUN_1423d59b0(local_730); + local_738 = local_738 + 1; + param_1 = local_728; + lVar23 = local_708; + bVar17 = bVar16; + bVar16 = bVar5; + bVar18 = bVar19; + bVar19 = bVar20; + } while (local_738 < local_74c[0]); + } + plVar25 = (longlong *)FUN_1423c3380(param_1); + pcVar1 = *(code **)(*plVar25 + 0x18); + uVar21 = thunk_FUN_141d75760(1); + FUN_1423d68a0(local_740,uVar21); + (*pcVar1)(plVar25,local_740,local_378); + FUN_1423d69f0(local_740); + FUN_1423c3ab0(local_378); + } + thunk_FUN_1420dd1dc(lVar23); + } + thunk_FUN_1420dea50(0); + if (local_754 == 0) { + return 0; + } + uVar21 = FUN_1423d5720(local_710," Select ERROR"); + uVar22 = FUN_1423d5720(&local_728,"db::dsqlGetAllDevContent"); + uVar21 = FUN_1423d3360(&local_708,uVar22,uVar21); + FUN_1423ac220(uVar21); + FUN_1423d59b0(&local_708); + puVar29 = local_710; + } + else { + uVar21 = FUN_1423d5720(local_720,"edb_dyn_CriticalSectionOn ERROR"); + uVar22 = FUN_1423d5720(local_740,"db::dsqlGetAllDevContent"); + uVar21 = FUN_1423d3360(local_718,uVar22,uVar21); + FUN_1423ac220(uVar21); + FUN_1423d59b0(local_718); + puVar29 = local_720; + } + FUN_1423d59b0(puVar29); + return local_754; +} + + + +//===== FUN_141c07230 @ 141c07230 [SearchStr 2] ===== + +/* WARNING: Function: __security_check_cookie replaced with injection: security_check_cookie */ + +int FUN_141c07230(undefined8 param_1) + +{ + code *pcVar1; + undefined8 uVar2; + undefined8 uVar3; + longlong lVar4; + longlong lVar5; + longlong *plVar6; + undefined4 *puVar7; + longlong lVar8; + undefined1 *puVar9; + uint uVar10; + uint uVar11; + uint uVar12; + undefined1 auStack_138 [32]; + undefined8 local_118; + undefined8 local_110; + undefined4 local_108; + undefined8 local_100; + undefined1 local_f8 [4]; + int local_f4; + undefined1 local_f0 [4]; + int local_ec; + undefined1 local_e8 [8]; + undefined4 local_e0 [2]; + undefined1 local_d8 [8]; + undefined1 local_d0 [8]; + undefined8 local_c8; + undefined1 local_c0 [8]; + undefined1 local_b8 [8]; + undefined1 local_b0 [8]; + undefined1 local_a8 [8]; + undefined1 local_a0 [8]; + undefined1 local_98 [8]; + longlong local_90; + undefined1 local_88 [16]; + undefined1 local_78 [16]; + undefined1 local_68 [16]; + undefined1 local_58 [16]; + undefined1 local_48 [16]; + ulonglong local_38; + + local_38 = DAT_14519c398 ^ (ulonglong)auStack_138; + uVar10 = 0; + local_e0[0] = 0; + local_c8 = param_1; + local_f4 = thunk_FUN_1420de9e0(0); + if (local_f4 == 0) { + uVar2 = thunk_FUN_141d75760(1); + local_100 = 0; + local_108 = 0; + local_110 = 0; + local_118 = 0; + lVar4 = thunk_FUN_1420dcbb8(&local_f4,uVar2,0,0); + if ((local_f4 == 0) && (lVar4 != 0)) { + local_ec = 0; + local_f4 = thunk_FUN_1420dd31c(lVar4,&local_ec); + if ((local_f4 == 0) && (local_ec != 0)) { + local_e0[0] = 0; + local_f0[0] = 1; + thunk_FUN_1420dd10c(lVar4,1,local_e0,local_f0); + local_f8[0] = 0; + FUN_1423c3af0(local_88); + uVar12 = uVar10; + if (0 < local_ec) { + do { + plVar6 = (longlong *)0x0; + lVar5 = thunk_FUN_1420dd538(lVar4,&local_f4); + param_1 = local_c8; + if ((local_f4 != 0) || (lVar5 == 0)) break; + local_90 = FUN_14217c644(0x20); + if (local_90 != 0) { + plVar6 = (longlong *)FUN_1423c1e00(local_90); + } + local_f8[0] = 0; + pcVar1 = *(code **)(*plVar6 + 0x18); + puVar7 = (undefined4 *)thunk_FUN_1420dd5e4(lVar5,0,local_f8); + FUN_1423c3a40(local_78,*puVar7); + FUN_1423d68a0(local_b0,&DAT_143294460); + (*pcVar1)(plVar6,local_b0,local_78); + FUN_1423d69f0(local_b0); + FUN_1423c3ab0(local_78); + local_f8[0] = 0; + pcVar1 = *(code **)(*plVar6 + 0x18); + lVar8 = thunk_FUN_1420dd5e4(lVar5,1,local_f8); + if (lVar8 == 0) { + uVar2 = FUN_1423d59e0(local_a0); + uVar10 = uVar10 | 4; + } + else { + uVar2 = FUN_1423d56a0(local_98,lVar8); + uVar10 = uVar10 | 2; + } + FUN_1423d58b0(local_d8,uVar2); + if ((uVar10 & 4) != 0) { + uVar10 = uVar10 & 0xfffffffb; + FUN_1423d59b0(local_a0); + } + if ((uVar10 & 2) != 0) { + uVar10 = uVar10 & 0xfffffffd; + FUN_1423d59b0(local_98); + } + FUN_1423c2f60(local_68,local_d8); + FUN_1423d68a0(local_a8,&DAT_143294464); + (*pcVar1)(plVar6,local_a8,local_68); + FUN_1423d69f0(local_a8); + FUN_1423c3ab0(local_68); + FUN_1423d59b0(local_d8); + local_f8[0] = 0; + pcVar1 = *(code **)(*plVar6 + 0x18); + lVar5 = thunk_FUN_1420dd5e4(lVar5,2,local_f8); + if (lVar5 == 0) { + uVar2 = FUN_1423d59e0(local_c0); + uVar11 = 0xc1; + } + else { + uVar2 = FUN_1423d56a0(local_b8,lVar5); + uVar11 = 0xa1; + } + FUN_1423d58b0(local_e8,uVar2); + uVar10 = uVar10 | uVar11; + if ((uVar10 & 0x40) != 0) { + uVar10 = uVar10 & 0xffffffbf; + FUN_1423d59b0(local_c0); + } + if ((uVar10 & 0x20) != 0) { + uVar10 = uVar10 & 0xffffffdf; + FUN_1423d59b0(local_b8); + } + FUN_1423c2f60(local_58,local_e8); + FUN_1423d68a0(local_d0,"SearchStr"); + (*pcVar1)(plVar6,local_d0,local_58); + FUN_1423d69f0(local_d0); + FUN_1423c3ab0(local_58); + FUN_1423d59b0(local_e8); + FUN_1423c35f0(local_48,plVar6); + FUN_1423c25c0(local_88,local_48); + FUN_1423c3ab0(local_48); + uVar12 = uVar12 + 1; + param_1 = local_c8; + } while ((int)uVar12 < local_ec); + } + plVar6 = (longlong *)FUN_1423c3380(param_1); + pcVar1 = *(code **)(*plVar6 + 0x18); + uVar2 = thunk_FUN_141d75760(1); + FUN_1423d68a0(local_e8,uVar2); + (*pcVar1)(plVar6,local_e8,local_88); + FUN_1423d69f0(local_e8); + FUN_1423c3ab0(local_88); + } + thunk_FUN_1420dd1dc(lVar4); + } + thunk_FUN_1420dea50(0); + if (local_f4 == 0) { + return 0; + } + uVar2 = FUN_1423d5720(local_c0," Select ERROR"); + uVar3 = FUN_1423d5720(&local_c8,"db::dsqlGetAllDevArtist"); + uVar2 = FUN_1423d3360(local_b8,uVar3,uVar2); + FUN_1423ac220(uVar2); + FUN_1423d59b0(local_b8); + puVar9 = local_c0; + } + else { + uVar2 = FUN_1423d5720(local_d8,"edb_dyn_CriticalSectionOn ERROR"); + uVar3 = FUN_1423d5720(local_e8,"db::dsqlGetAllDevArtist"); + uVar2 = FUN_1423d3360(local_d0,uVar3,uVar2); + FUN_1423ac220(uVar2); + FUN_1423d59b0(local_d0); + puVar9 = local_d8; + } + FUN_1423d59b0(puVar9); + return local_f4; +} + + + +//===== FUN_141c076d0 @ 141c076d0 [SearchStr 3] ===== + +/* WARNING: Function: __security_check_cookie replaced with injection: security_check_cookie */ + +int FUN_141c076d0(undefined8 param_1) + +{ + code *pcVar1; + undefined8 uVar2; + undefined8 uVar3; + longlong lVar4; + longlong lVar5; + longlong *plVar6; + undefined4 *puVar7; + longlong lVar8; + char *pcVar9; + undefined1 *puVar10; + uint uVar11; + uint uVar12; + uint uVar13; + undefined1 auStack_178 [32]; + undefined8 local_158; + undefined8 local_150; + undefined4 local_148; + undefined8 local_140; + undefined1 local_138 [4]; + int local_134; + undefined1 local_130 [4]; + int local_12c; + undefined1 local_128 [8]; + undefined4 local_120 [2]; + undefined1 local_118 [8]; + undefined1 local_110 [8]; + undefined8 local_108; + undefined1 local_100 [8]; + undefined1 local_f8 [8]; + undefined1 local_f0 [8]; + undefined1 local_e8 [8]; + undefined1 local_e0 [8]; + undefined1 local_d8 [8]; + undefined1 local_d0 [8]; + undefined1 local_c8 [8]; + undefined1 local_c0 [8]; + longlong local_b8; + undefined1 local_b0 [16]; + undefined1 local_a0 [16]; + undefined1 local_90 [16]; + undefined1 local_80 [16]; + undefined1 local_70 [16]; + undefined1 local_60 [16]; + undefined1 local_50 [16]; + undefined1 local_40 [16]; + ulonglong local_30; + + local_30 = DAT_14519c398 ^ (ulonglong)auStack_178; + uVar11 = 0; + local_120[0] = 0; + local_108 = param_1; + local_134 = thunk_FUN_1420de9e0(0); + if (local_134 == 0) { + uVar2 = thunk_FUN_141d75760(1); + local_140 = 0; + local_148 = 0; + local_150 = 0; + local_158 = 0; + lVar4 = thunk_FUN_1420dcbb8(&local_134,uVar2,0,0); + if ((local_134 == 0) && (lVar4 != 0)) { + local_12c = 0; + local_134 = thunk_FUN_1420dd31c(lVar4,&local_12c); + if ((local_134 == 0) && (local_12c != 0)) { + local_120[0] = 0; + local_130[0] = 1; + thunk_FUN_1420dd10c(lVar4,1,local_120,local_130); + local_138[0] = 0; + FUN_1423c3af0(local_b0); + uVar13 = uVar11; + if (0 < local_12c) { + do { + plVar6 = (longlong *)0x0; + lVar5 = thunk_FUN_1420dd538(lVar4,&local_134); + param_1 = local_108; + if ((local_134 != 0) || (lVar5 == 0)) break; + local_b8 = FUN_14217c644(0x20); + if (local_b8 != 0) { + plVar6 = (longlong *)FUN_1423c1e00(local_b8); + } + local_138[0] = 0; + pcVar1 = *(code **)(*plVar6 + 0x18); + puVar7 = (undefined4 *)thunk_FUN_1420dd5e4(lVar5,0,local_138); + FUN_1423c3a40(local_a0,*puVar7); + FUN_1423d68a0(local_f0,&DAT_143294558); + (*pcVar1)(plVar6,local_f0,local_a0); + FUN_1423d69f0(local_f0); + FUN_1423c3ab0(local_a0); + local_138[0] = 0; + pcVar1 = *(code **)(*plVar6 + 0x18); + lVar8 = thunk_FUN_1420dd5e4(lVar5,1,local_138); + if (lVar8 == 0) { + uVar2 = FUN_1423d59e0(local_c8); + uVar11 = uVar11 | 4; + } + else { + uVar2 = FUN_1423d56a0(local_c0,lVar8); + uVar11 = uVar11 | 2; + } + FUN_1423d58b0(local_e0,uVar2); + if ((uVar11 & 4) != 0) { + uVar11 = uVar11 & 0xfffffffb; + FUN_1423d59b0(local_c8); + } + if ((uVar11 & 2) != 0) { + uVar11 = uVar11 & 0xfffffffd; + FUN_1423d59b0(local_c0); + } + FUN_1423c2f60(local_90,local_e0); + FUN_1423d68a0(local_e8,&DAT_14329455c); + (*pcVar1)(plVar6,local_e8,local_90); + FUN_1423d69f0(local_e8); + FUN_1423c3ab0(local_90); + FUN_1423d59b0(local_e0); + local_138[0] = 0; + pcVar1 = *(code **)(*plVar6 + 0x18); + puVar7 = (undefined4 *)thunk_FUN_1420dd5e4(lVar5,2,local_138); + FUN_1423c3a40(local_80,*puVar7); + FUN_1423d68a0(local_d8,"AlbumArtistID"); + (*pcVar1)(plVar6,local_d8,local_80); + FUN_1423d69f0(local_d8); + FUN_1423c3ab0(local_80); + local_138[0] = 0; + pcVar1 = *(code **)(*plVar6 + 0x18); + puVar7 = (undefined4 *)thunk_FUN_1420dd5e4(lVar5,3,local_138); + FUN_1423c3a40(local_70,*puVar7); + FUN_1423d68a0(local_d0,"ImageID"); + (*pcVar1)(plVar6,local_d0,local_70); + FUN_1423d69f0(local_d0); + FUN_1423c3ab0(local_70); + local_138[0] = 0; + pcVar1 = *(code **)(*plVar6 + 0x18); + pcVar9 = (char *)thunk_FUN_1420dd5e4(lVar5,4,local_138); + FUN_1423c3a60(local_60,(int)*pcVar9); + FUN_1423d68a0(local_118,"Compilation"); + (*pcVar1)(plVar6,local_118,local_60); + FUN_1423d69f0(local_118); + FUN_1423c3ab0(local_60); + local_138[0] = 0; + pcVar1 = *(code **)(*plVar6 + 0x18); + lVar5 = thunk_FUN_1420dd5e4(lVar5,5,local_138); + if (lVar5 == 0) { + uVar2 = FUN_1423d59e0(local_100); + uVar12 = 0xc1; + } + else { + uVar2 = FUN_1423d56a0(local_f8,lVar5); + uVar12 = 0xa1; + } + FUN_1423d58b0(local_128,uVar2); + uVar11 = uVar11 | uVar12; + if ((uVar11 & 0x40) != 0) { + uVar11 = uVar11 & 0xffffffbf; + FUN_1423d59b0(local_100); + } + if ((uVar11 & 0x20) != 0) { + uVar11 = uVar11 & 0xffffffdf; + FUN_1423d59b0(local_f8); + } + FUN_1423c2f60(local_50,local_128); + FUN_1423d68a0(local_110,"SearchStr"); + (*pcVar1)(plVar6,local_110,local_50); + FUN_1423d69f0(local_110); + FUN_1423c3ab0(local_50); + FUN_1423d59b0(local_128); + FUN_1423c35f0(local_40,plVar6); + FUN_1423c25c0(local_b0,local_40); + FUN_1423c3ab0(local_40); + uVar13 = uVar13 + 1; + param_1 = local_108; + } while ((int)uVar13 < local_12c); + } + plVar6 = (longlong *)FUN_1423c3380(param_1); + pcVar1 = *(code **)(*plVar6 + 0x18); + uVar2 = thunk_FUN_141d75760(1); + FUN_1423d68a0(local_128,uVar2); + (*pcVar1)(plVar6,local_128,local_b0); + FUN_1423d69f0(local_128); + FUN_1423c3ab0(local_b0); + } + thunk_FUN_1420dd1dc(lVar4); + } + thunk_FUN_1420dea50(0); + if (local_134 == 0) { + return 0; + } + uVar2 = FUN_1423d5720(local_100," Select ERROR"); + uVar3 = FUN_1423d5720(&local_108,"db::dsqlGetAllDevAlbum"); + uVar2 = FUN_1423d3360(local_f8,uVar3,uVar2); + FUN_1423ac220(uVar2); + FUN_1423d59b0(local_f8); + puVar10 = local_100; + } + else { + uVar2 = FUN_1423d5720(local_118,"edb_dyn_CriticalSectionOn ERROR"); + uVar3 = FUN_1423d5720(local_128,"db::dsqlGetAllDevAlbum"); + uVar2 = FUN_1423d3360(local_110,uVar3,uVar2); + FUN_1423ac220(uVar2); + FUN_1423d59b0(local_110); + puVar10 = local_118; + } + FUN_1423d59b0(puVar10); + return local_134; +} + + + +//===== FUN_141ae4860 @ 141ae4860 [djmdContent xrefs] ===== + +/* WARNING: Function: __security_check_cookie replaced with injection: security_check_cookie */ +/* WARNING: Globals starting with '_' overlap smaller symbols at the same address */ + +void FUN_141ae4860(longlong param_1,int param_2,longlong param_3) + +{ + int *piVar1; + bool bVar2; + bool bVar3; + char cVar4; + byte bVar5; + int iVar6; + uint uVar7; + int iVar8; + int iVar9; + undefined4 uVar10; + undefined4 uVar11; + longlong lVar12; + longlong *plVar13; + undefined8 uVar14; + undefined8 *puVar15; + longlong *plVar16; + undefined ***pppuVar17; + undefined ***pppuVar18; + undefined1 auStack_848 [32]; + undefined ***local_828; + undefined ***local_820; + undefined *local_818; + undefined8 local_810; + char local_808; + char local_807 [7]; + undefined1 local_800 [8]; + int local_7f8; + undefined4 uStack_7f4; + undefined1 local_7f0 [8]; + char local_7e8; + longlong local_7e0; + undefined ***local_7d8; + undefined8 local_7d0; + undefined1 local_7c8 [8]; + longlong local_7c0; + undefined8 uStack_7b8; + undefined8 local_7b0; + ulonglong local_7a8; + undefined1 local_7a0 [8]; + undefined1 local_798 [8]; + undefined1 local_790 [16]; + undefined1 local_780 [8]; + undefined8 *local_778; + undefined1 local_770 [8]; + undefined **local_768; + undefined1 local_760 [8]; + int local_758; + longlong local_750; + int local_748; + undefined ***local_730; + undefined **local_728; + longlong local_720; + undefined8 uStack_718; + longlong local_710; + undefined ***local_6f0; + undefined **local_6e8; + longlong local_6e0; + longlong *plStack_6d8; + undefined ***local_6b0; + undefined **local_6a8; + longlong local_6a0; + undefined8 uStack_698; + undefined ***local_670; + undefined **local_668; + longlong *local_660; + undefined ***local_630; + undefined **local_628 [7]; + undefined ***local_5f0; + undefined **local_5e8 [7]; + undefined ***local_5b0; + longlong local_5a8; + undefined8 local_5a0; + longlong local_598; + int iStack_590; + int iStack_58c; + longlong local_588; + int local_580; + undefined1 local_570 [16]; + undefined **local_560 [7]; + undefined ***local_528; + undefined1 local_518 [8]; + undefined4 local_510; + int local_130; + ulonglong local_48; + + local_48 = DAT_14519c398 ^ (ulonglong)auStack_848; + pppuVar17 = (undefined ***)(longlong)param_2; + bVar3 = false; + bVar2 = false; + local_7f8 = 0; + local_7e0 = param_3; + EnterCriticalSection((LPCRITICAL_SECTION)(param_1 + 0x378)); + if (0 < *(int *)(param_1 + 0x374)) { + LeaveCriticalSection((LPCRITICAL_SECTION)(param_1 + 0x378)); + return; + } + LeaveCriticalSection((LPCRITICAL_SECTION)(param_1 + 0x378)); + lVar12 = FUN_1425572d0(); + if (*(char *)(lVar12 + 0xc) != '\0') { + return; + } + lVar12 = FUN_142470a50(); + if (*(int *)(lVar12 + 8) != 0) { + return; + } + param_1 = param_1 + -0x390; + if (param_2 < 1) { + plVar13 = (longlong *)FUN_141b988e0(param_1); + } + else { + plVar13 = (longlong *)FUN_141ad6500(param_1,param_2,0); + } + lVar12 = local_7e0; + pppuVar18 = (undefined ***)0x1; + if (*(int *)((longlong)plVar13 + 0x34) != 3) { + if (*(int *)((longlong)plVar13 + 0x34) != 1) { + return; + } + pppuVar18 = (undefined ***)0x2; + } + cVar4 = FUN_1423c3550(local_7e0); + if (cVar4 == '\0') { + return; + } + uVar14 = FUN_1423c2ab0(lVar12,&DAT_14327197c); + FUN_1423c3420(uVar14,local_7f0); + uVar14 = FUN_1423c2ab0(lVar12,"parameters"); + uVar14 = FUN_1423c2ab0(uVar14,"model"); + FUN_1423c3420(uVar14,local_800); + uVar14 = FUN_1423c2ab0(lVar12,"parameters"); + uVar14 = FUN_1423c2ab0(uVar14,&DAT_1432719cc); + FUN_1423c3a80(local_570,uVar14); + uVar14 = FUN_1423c2ab0(lVar12,"parameters"); + uVar14 = FUN_1423c2ab0(uVar14,&DAT_1432719e4); + uVar14 = FUN_1423c3450(uVar14,local_780); + iVar6 = FUN_1423cf4b0(uVar14); + FUN_1423d59b0(local_780); + puVar15 = (undefined8 *)FUN_1423ce840(local_770,"data.db.item"); + local_7e8 = FUN_1423d2840(local_7f0,*puVar15); + local_808 = FUN_1423d45a0(local_7f0,"data.db.item.id_changed"); + cVar4 = FUN_1423d45a0(local_7f0,"agent.quit.requested"); + if (cVar4 != '\0') { + local_560[0] = std::_Func_impl_no_alloc<,void>::vftable + ; + local_528 = local_560; + FUN_1424704f0(local_560); + goto LAB_141ae5a25; + } + cVar4 = FUN_1423d45a0(local_7f0,"account.auth.error"); + if (cVar4 != '\0') { + uVar14 = FUN_14106d7d0(); + plVar13 = (longlong *)FUN_1416ede60(uVar14,1); + if (plVar13 != (longlong *)0x0) { + local_628[0] = std:: + _Func_impl_no_alloc<,void,SignInErrorType> + ::vftable; + local_5f0 = local_628; + local_5e8[0] = std::_Func_impl_no_alloc<,void>:: + vftable; + local_5b0 = local_5e8; + local_668 = std::_Func_impl_no_alloc<,void>::vftable; + local_630 = &local_668; + local_818 = &DAT_1453698a8; + local_820 = (undefined ***)CONCAT44(local_820._4_4_,DAT_1430e8980); + local_828 = local_628; + local_660 = plVar13; + (**(code **)(*plVar13 + 0x98))(plVar13,&local_778,&local_668,local_5e8); + if (local_778 != (undefined8 *)0x0) { + LOCK(); + piVar1 = (int *)(local_778 + 1); + *piVar1 = *piVar1 + -1; + UNLOCK(); + if (*piVar1 == 0) { + (**(code **)*local_778)(local_778,1); + } + } + } + goto LAB_141ae5a25; + } + cVar4 = FUN_1423d45a0(local_800,"djmdContent"); + iVar8 = (int)pppuVar18; + if (((cVar4 == '\0') && (cVar4 = FUN_1423d45a0(local_800,"djmdSongMyTag"), cVar4 == '\0')) && + (local_808 == '\0')) { + cVar4 = FUN_1423d45a0(local_800,"djmdCue"); + if (cVar4 != '\0') { + uVar14 = FUN_1423c2ab0(local_570,"ContentID"); + uVar14 = FUN_1423c3450(uVar14,&local_7e0); + uVar7 = FUN_1423cf4b0(uVar14); + FUN_1423d59b0(&local_7e0); + if (uVar7 != 0) { + local_807[0] = '\0'; + local_7f8 = 0; + iVar6 = FUN_141c92e00(plVar13,uVar7,local_807,&local_7f8); + if (iVar6 == 0) { + local_7d8 = (undefined ***)0x0; + local_7d0 = 0; + FUN_140c8ae10(&local_7d8,8); + lVar12 = (longlong)local_7d0._4_4_; + local_7d0 = CONCAT44(local_7d0._4_4_ + 1,(undefined4)local_7d0); + *(uint *)((longlong)local_7d8 + lVar12 * 4) = uVar7; + FUN_141b67f30(param_1,&local_7d8); + local_828 = (undefined ***)(ulonglong)uVar7; + local_820 = pppuVar17; + FUN_141ad5900(param_1,1,0x17,pppuVar18); + FUN_141ad7bc0(param_1,uVar7,pppuVar18,param_2); + if (local_807[0] != '\0') { + local_828 = (undefined ***)CONCAT44(local_828._4_4_,param_2); + FUN_141ad7a30(param_1,uVar7,local_7f8,pppuVar18); + } + local_7d0 = local_7d0 & 0xffffffff; + FUN_14230889c(local_7d8); + } + } + goto LAB_141ae5a25; + } + cVar4 = FUN_1423d45a0(local_800,"djmdPlaylist"); + if ((cVar4 == '\0') && (cVar4 = FUN_1423d45a0(local_800,"djmdSongPlaylist"), cVar4 == '\0')) { + cVar4 = FUN_1423d45a0(local_800,"djmdSharedPlaylist"); + if ((cVar4 == '\0') && + (cVar4 = FUN_1423d45a0(local_800,"djmdSharedPlaylistUser"), cVar4 == '\0')) { + cVar4 = FUN_1423d45a0(local_7f0,"data.db.updated"); + if (cVar4 == '\0') { + puVar15 = (undefined8 *)FUN_1423ce840(&local_7b0,"data.db.item"); + cVar4 = FUN_1423d2890(local_7f0,*puVar15); + if (cVar4 == '\0') { + cVar4 = FUN_1423d45a0(local_7f0,"data.file.content.downloaded"); + if (cVar4 != '\0') { + uVar14 = FUN_1423c2ab0(lVar12,"parameters"); + uVar14 = FUN_1423c2ab0(uVar14,&DAT_143271d44); + FUN_1423c3420(uVar14,&local_7f8); + puVar15 = (undefined8 *)FUN_1423ce840(local_790,&DAT_143271d4c); + cVar4 = FUN_1423d14c0(&local_7f8,*puVar15); + if (cVar4 != '\0') { + puVar15 = (undefined8 *)FUN_1423ce840(&local_5a8,"_s.jpg"); + uVar14 = *puVar15; + puVar15 = (undefined8 *)FUN_1423ce840(&local_7c0,&DAT_143271d5c); + local_828 = (undefined ***)((ulonglong)local_828 & 0xffffffffffffff00); + uVar14 = FUN_1423d1c70(&local_7f8,local_798,*puVar15,uVar14); + uVar14 = FUN_1419f6820(local_7a0,uVar14); + FUN_141ad7dd0(param_1,uVar14); + FUN_1423d59b0(local_7a0); + FUN_1423d59b0(local_798); + uVar14 = FUN_1419f6820(local_7c8,&local_7f8); + FUN_141ad7dd0(param_1,uVar14); + FUN_1423d59b0(local_7c8); + lVar12 = local_7e0; + } + FUN_140ff8e40(local_518); + uVar14 = FUN_1423c2ab0(lVar12,"parameters"); + uVar14 = FUN_1423c2ab0(uVar14,"ContentID"); + uVar14 = FUN_1423c3450(uVar14,&local_7e0); + iVar6 = FUN_1423cf4b0(uVar14); + FUN_1423d59b0(&local_7e0); + plVar13 = (longlong *)FUN_141ad5670(param_1,pppuVar18,param_2); + cVar4 = (**(code **)(*plVar13 + 0x40))(plVar13,iVar6,local_518); + if (cVar4 != '\0') { + puVar15 = (undefined8 *)FUN_1423ce840(&local_7d8,&DAT_143271d84); + bVar5 = FUN_1423d14c0(&local_7f8,*puVar15); + local_7d8 = (undefined ***)FUN_14217c644(0x4c8); + if (local_7d8 == (undefined ***)0x0) { + uVar14 = 0; + } + else { + uVar14 = FUN_141020db0(local_7d8,local_518); + } + local_810 = 0; + local_818 = (undefined *)0x0; + local_828 = (undefined ***)(ulonglong)bVar5; + local_820 = pppuVar18; + FUN_141ad57a0(param_1,1,0xd,uVar14); + FUN_1423e3f10(&local_598,0); + iVar8 = FUN_1423e39b0(&local_598); + local_820 = (undefined ***)(longlong)iVar8; + local_828 = (undefined ***)(longlong)local_130; + FUN_141ad5900(param_1,1,0xc,(longlong)iVar6); + FUN_1423e3b10(&local_598); + } + FUN_140ff9950(local_518); + FUN_1423d59b0(&local_7f8); + } + goto LAB_141ae5a25; + } + } + if ((*(int *)(*(longlong *)((longlong)ThreadLocalStoragePointer + (ulonglong)_tls_index * 8) + + 8) < DAT_1454373a0) && (FUN_14217cd0c(&DAT_1454373a0), DAT_1454373a0 == -1)) + { + FUN_142471b60(&DAT_145437340); + _DAT_145437340 = + `private:_virtual_void___cdecl_db::DatabaseMediator::onRekordboxDBChanged(long,juce::var_const&___ptr64)___ptr64' + ::__l24::DBInitializer::vftable; + FUN_1423d59e0(&DAT_145437358); + DAT_145437398 = (longlong *)0x0; + atexit(FUN_142c3a780); + _Init_thread_footer(&DAT_1454373a0); + } + FUN_1423d5980(&local_598,local_7f0); + local_730 = (undefined ***)0x0; + local_768 = std::_Func_impl_no_alloc<,void>:: + vftable; + iStack_590 = param_2; + local_588 = param_1; + local_580 = iVar8; + FUN_1423d5980(local_760,&local_598); + local_758 = iStack_590; + local_750 = local_588; + local_748 = local_580; + local_730 = &local_768; + local_7d8 = &local_768; + if (((*DAT_145437358 != '\0') && + (cVar4 = FUN_1423d45c0(&DAT_145437358,local_800), cVar4 != '\0')) && + (DAT_145437398 != (longlong *)0x0)) { + (**(code **)(*DAT_145437398 + 0x10))(); + } + FUN_1423d58d0(&DAT_145437358,local_800); + FUN_140d24ba0(&DAT_145437360,&local_768); + FUN_142471a20(&DAT_145437340,3000); + if (local_730 != (undefined ***)0x0) { + (*(code *)(*local_730)[4]) + (local_730,CONCAT71((int7)((ulonglong)&local_768 >> 8),local_730 != &local_768)) + ; + local_730 = (undefined ***)0x0; + } + FUN_1423d59b0(&local_598); + } + else { + cVar4 = FUN_1423d45a0(local_7f0,"data.db.item.created"); + if (((cVar4 != '\0') || + (cVar4 = FUN_1423d45a0(local_7f0,"data.db.item.deleted"), cVar4 != '\0')) || + ((cVar4 = FUN_1423d45a0(local_800,"djmdSharedPlaylistUser"), cVar4 != '\0' && + (cVar4 = FUN_1423d45a0(local_7f0,"data.db.item.updated"), cVar4 != '\0')))) { + uVar14 = FUN_1423c2ab0(lVar12,"parameters"); + uVar14 = FUN_1423c2ab0(uVar14,&DAT_143271ce4); + uVar14 = FUN_1423c3450(uVar14,local_7c8); + puVar15 = (undefined8 *)FUN_1423ce840(&local_7d8,&DAT_143271ce8); + FUN_1423cfaf0(uVar14,&local_7f8,*puVar15); + FUN_1423d59b0(local_7c8); + lVar12 = FUN_14217c644(0x10); + local_7c0 = lVar12; + if (lVar12 == 0) { + uVar14 = 0; + } + else { + if ((*(char *)CONCAT44(uStack_7f4,local_7f8) == '\0') || + (cVar4 = FUN_1423d4340(&local_7f8,&DAT_143271cec), cVar4 != '\0')) { + uVar14 = FUN_1423dea60(&local_598); + bVar3 = false; + bVar2 = true; + } + else { + uVar14 = FUN_1423de600(&local_5a8,&local_7f8); + bVar3 = true; + bVar2 = false; + } + uVar14 = FUN_1423deab0(lVar12,uVar14); + } + if (bVar2) { + FUN_1423deac0(&local_598); + } + if (bVar3) { + FUN_1423deac0(&local_5a8); + } + local_810 = 0; + local_818 = (undefined *)0x0; + local_820 = (undefined ***)0x0; + local_828 = (undefined ***)0x0; + FUN_141ad57a0(param_1,1,0x33,uVar14); + local_810 = 0; + local_818 = (undefined *)0x0; + local_820 = (undefined ***)0x2; + local_828 = pppuVar17; + FUN_141ad57a0(param_1,1,0xf,pppuVar18); + FUN_1423d59b0(&local_7f8); + } + } + goto LAB_141ae5a25; + } + cVar4 = FUN_1423d45a0(local_800,"djmdSongPlaylist"); + if (cVar4 != '\0') { + uVar14 = FUN_1423c2ab0(local_570,"PlaylistID"); + uVar14 = FUN_1423c3450(uVar14,local_7c8); + iVar6 = FUN_1423cf4b0(uVar14); + FUN_1423d59b0(local_7c8); + cVar4 = FUN_1423d45a0(local_7f0,"data.db.item.created"); + if ((cVar4 == '\0') && + (cVar4 = FUN_1423d45a0(local_7f0,"data.db.item.updated"), cVar4 == '\0')) { + FUN_1423d45a0(local_7f0,"data.db.item.deleted"); + } + } + local_588 = CONCAT44(local_588._4_4_,iVar6); + if (DAT_1454372c4 != iVar6) { + DAT_1454372c4 = iVar6; + local_598 = param_1; + iStack_590 = iVar8; + iStack_58c = param_2; + FUN_141aed600(&local_598); + goto LAB_141ae5a25; + } + local_598 = param_1; + iStack_590 = iVar8; + iStack_58c = param_2; + if ((*(int *)(*(longlong *)((longlong)ThreadLocalStoragePointer + (ulonglong)_tls_index * 8) + 8 + ) < DAT_145437330) && (FUN_14217cd0c(&DAT_145437330), DAT_145437330 == -1)) { + FUN_142471b60(&DAT_1454372d0); + _DAT_1454372d0 = + `private:_virtual_void___cdecl_db::DatabaseMediator::onRekordboxDBChanged(long,juce::var_const&___ptr64)___ptr64' + ::__l24::DBInitializer::vftable; + FUN_1423d59e0(&DAT_1454372e8); + DAT_145437328 = (longlong *)0x0; + atexit(FUN_142c3a730); + _Init_thread_footer(&DAT_145437330); + } + local_728 = std::_Func_impl_no_alloc<,void>::vftable; + uStack_718 = CONCAT44(iStack_58c,iStack_590); + local_720 = local_598; + local_710 = local_588; + local_6f0 = &local_728; + local_7d8 = &local_728; + if (((*DAT_1454372e8 != '\0') && + (cVar4 = FUN_1423d45c0(&DAT_1454372e8,local_800), cVar4 != '\0')) && + (DAT_145437328 != (longlong *)0x0)) { + (**(code **)(*DAT_145437328 + 0x10))(); + } + FUN_1423d58d0(&DAT_1454372e8,local_800); + FUN_140d24ba0(&DAT_1454372f0,&local_728); + FUN_142471a20(&DAT_1454372d0,3000); + if (local_6f0 == (undefined ***)0x0) goto LAB_141ae5a25; + pppuVar17 = &local_728; + pppuVar18 = local_6f0; + } + else { + cVar4 = FUN_1423d45a0(local_7f0,"data.db.updated"); + if (cVar4 == '\0') { + if (local_808 != '\0') { +LAB_141ae5a7c: + uVar14 = FUN_1423c2ab0(lVar12,"parameters"); + uVar14 = FUN_1423c2ab0(uVar14,"newID"); + uVar14 = FUN_1423c3450(uVar14,local_7c8); + uVar10 = FUN_1423cf4b0(uVar14); + FUN_1423d59b0(local_7c8); + uVar14 = FUN_1423c2ab0(lVar12,"parameters"); + uVar14 = FUN_1423c2ab0(uVar14,"oldID"); + uVar14 = FUN_1423c3450(uVar14,local_798); + uVar11 = FUN_1423cf4b0(uVar14); + FUN_1423d59b0(local_798); + FUN_1423d5720(&local_7f8,"id_changed!!! %lu -> %lu"); + uVar14 = FUN_1423ceae0(&local_7f8); + FUN_1423cf4d0(&local_7e0,uVar14,uVar11,uVar10); + uVar14 = FUN_1423d5720(local_7a0,"db::DatabaseMediator::onRekordboxDBChanged"); + uVar14 = FUN_1423d32c0(local_790,uVar14,&DAT_143271abc); + uVar14 = FUN_1423d3360(&local_7b0,uVar14,&local_7e0); + FUN_1423e4000(uVar14); + FUN_1423d59b0(&local_7b0); + FUN_1423d59b0(&local_7e0); + FUN_1423d59b0(&local_7f8); + goto LAB_141ae5b8c; + } + if (local_7e8 != '\0') { + FUN_140ff8e40(local_518); + cVar4 = FUN_1423d45a0(local_800,"djmdContent"); + if (cVar4 == '\0') { + cVar4 = FUN_1423d45a0(local_800,"djmdSongMyTag"); + if (cVar4 != '\0') { + uVar14 = FUN_1423c2ab0(local_570,"ContentID"); + uVar14 = FUN_1423c3450(uVar14,local_7c8); + iVar6 = FUN_1423cf4b0(uVar14); + FUN_1423d59b0(local_7c8); + plVar16 = (longlong *)FUN_141ad5670(param_1,pppuVar18,param_2); + cVar4 = (**(code **)(*plVar16 + 0x40))(plVar16,iVar6,local_518); + if ((cVar4 != '\0') && + (iVar9 = (**(code **)(*plVar13 + 0x538))(plVar13,local_518), iVar9 != -1)) { + local_7f8 = -1; + plVar16 = (longlong *)plVar13[0xf]; + if ((plVar16 != (longlong *)0x0) && + (cVar4 = FUN_141cb2540(plVar13,local_510,&local_7f8,1), cVar4 != '\0')) { + local_7d8 = (undefined ***)FUN_14217c644(0x4c8); + if (local_7d8 == (undefined ***)0x0) { + local_7e0 = 0; + } + else { + local_7e0 = FUN_141020db0(local_7d8,local_518); + } + if (-1 < local_7f8) { + if (local_7f8 < *(int *)((longlong)plVar16 + 0xc)) { + plVar16 = (longlong *)(*plVar16 + (longlong)local_7f8 * 8); + puVar15 = (undefined8 *)0x0; + if (*plVar16 != local_7e0) { + puVar15 = (undefined8 *)*plVar16; + } + *plVar16 = local_7e0; + if (puVar15 != (undefined8 *)0x0) { + (**(code **)*puVar15)(puVar15,1); + } + } + else { + iVar9 = *(int *)((longlong)plVar16 + 0xc) + 1; + if ((int)plVar16[1] < iVar9) { + FUN_140c708a0(plVar16,iVar9 / 2 + 8 + iVar9 & 0xfffffff8); + } + iVar9 = *(int *)((longlong)plVar16 + 0xc); + *(int *)((longlong)plVar16 + 0xc) = iVar9 + 1; + *(longlong *)(*plVar16 + (longlong)iVar9 * 8) = local_7e0; + } + } + } + } + } + } + else { + cVar4 = FUN_1423c3550(local_570); + if ((cVar4 != '\0') && + (iVar9 = (**(code **)(*plVar13 + 0x148))(plVar13,local_570,local_518), iVar9 != 0)) { + FUN_140ff9950(local_518); + goto LAB_141ae5a25; + } + } + uVar14 = 0; + local_7c0 = 0; + uStack_7b8 = (longlong *)0x0; + FUN_140c8ae10(&local_7c0,8); + lVar12 = (longlong)uStack_7b8._4_4_; + uStack_7b8 = (longlong *)CONCAT44(uStack_7b8._4_4_ + 1,(undefined4)uStack_7b8); + *(int *)(local_7c0 + lVar12 * 4) = iVar6; + FUN_141b67f30(param_1,&local_7c0); + uStack_7b8 = (longlong *)((ulonglong)uStack_7b8 & 0xffffffff); + FUN_14230889c(local_7c0); + cVar4 = FUN_1423d45a0(local_7f0,"data.db.item.created"); + if ((cVar4 == '\0') || (cVar4 = FUN_1423d45a0(local_800,"djmdContent"), cVar4 == '\0')) { + cVar4 = FUN_1423d45a0(local_7f0,"data.db.item.updated"); + if ((cVar4 == '\0') && (cVar4 = FUN_1423d45a0(local_800,"djmdSongMyTag"), cVar4 == '\0')) + { + cVar4 = FUN_1423d45a0(local_7f0,"data.db.item.deleted"); + if (cVar4 != '\0') { + local_5a8 = 0; + local_5a0 = 0; + local_7b0 = 0; + local_7a8 = 0; + FUN_140c708a0(&local_5a8,8); + lVar12 = (longlong)local_5a0._4_4_; + local_5a0 = CONCAT44(local_5a0._4_4_ + 1,(undefined4)local_5a0); + *(undefined1 **)(local_5a8 + lVar12 * 8) = local_518; + local_828 = (undefined ***)((ulonglong)local_828 & 0xffffffffffffff00); + FUN_141b698b0(param_1,&local_5a8,&local_7b0,0); + FUN_141af0a30(plVar13); + local_820 = (undefined ***)0x0; + local_828 = pppuVar17; + FUN_141ad5900(param_1,1,0x19,pppuVar18); + local_810 = 0; + local_818 = (undefined *)0x0; + local_820 = (undefined ***)0x0; + local_828 = pppuVar17; + FUN_141ad5850(param_1,1,0x19,pppuVar18); + FUN_141ad87e0(param_1,0x19,local_518); + local_7a8 = local_7a8 & 0xffffffff; + FUN_14230889c(local_7b0); + local_5a0 = local_5a0 & 0xffffffff; + FUN_14230889c(local_5a8); + } + } + else { + FUN_141ad7ec0(param_1,pppuVar18,param_2); + local_7d8 = (undefined ***)FUN_14217c644(); + if (local_7d8 != (undefined ***)0x0) { + uVar14 = FUN_141020db0(local_7d8,local_518); + } + local_810 = 0; + local_818 = (undefined *)0x0; + local_828 = (undefined ***)0x1; + local_820 = pppuVar18; + FUN_141ad57a0(param_1,1,0xd,uVar14); + FUN_1423e3f10(local_560,0); + iVar9 = FUN_1423e39b0(local_560); + local_820 = (undefined ***)(longlong)iVar9; + local_828 = (undefined ***)0x2; + FUN_141ad5900(param_1,1,0xc,(longlong)iVar6); + FUN_1423e3b10(local_560); + local_7d8 = (undefined ***)FUN_14217c644(0x4c8); + if (local_7d8 == (undefined ***)0x0) { + uVar14 = 0; + } + else { + uVar14 = FUN_141020db0(local_7d8,local_518); + } + local_810 = 0; + local_818 = (undefined *)0x0; + local_828 = (undefined ***)0x0; + local_820 = pppuVar18; + FUN_141ad5850(param_1,1,0xd,uVar14); + FUN_141ad87e0(param_1,0xd,local_518); + } + } + FUN_140ff9950(local_518); + } + } + else { + if (local_808 != '\0') goto LAB_141ae5a7c; +LAB_141ae5b8c: + if ((*(int *)(*(longlong *)((longlong)ThreadLocalStoragePointer + (ulonglong)_tls_index * 8) + + 8) < DAT_145437250) && (FUN_14217cd0c(&DAT_145437250), DAT_145437250 == -1)) { + FUN_142471b60(&DAT_1454371f0); + _DAT_1454371f0 = + `private:_virtual_void___cdecl_db::DatabaseMediator::onRekordboxDBChanged(long,juce::var_const&___ptr64)___ptr64' + ::__l24::DBInitializer::vftable; + FUN_1423d59e0(&DAT_145437208); + DAT_145437248 = (longlong *)0x0; + atexit(FUN_142c3a690); + _Init_thread_footer(&DAT_145437250); + } + local_6e8 = std::_Func_impl_no_alloc<,void>::vftable; + local_6b0 = &local_6e8; + local_7d8 = &local_6e8; + local_7c0 = param_1; + uStack_7b8 = plVar13; + local_6e0 = param_1; + plStack_6d8 = plVar13; + if (((*DAT_145437208 != '\0') && + (cVar4 = FUN_1423d45c0(&DAT_145437208,local_800), cVar4 != '\0')) && + (DAT_145437248 != (longlong *)0x0)) { + (**(code **)(*DAT_145437248 + 0x10))(); + } + FUN_1423d58d0(&DAT_145437208,local_800); + FUN_140d24ba0(&DAT_145437210,&local_6e8); + FUN_142471a20(&DAT_1454371f0,3000); + if (local_6b0 != (undefined ***)0x0) { + (*(code *)(*local_6b0)[4]) + (local_6b0,CONCAT71((int7)((ulonglong)&local_6e8 >> 8),local_6b0 != &local_6e8)); + } + } + if ((*(int *)(*(longlong *)((longlong)ThreadLocalStoragePointer + (ulonglong)_tls_index * 8) + 8 + ) < DAT_1454372c0) && (FUN_14217cd0c(&DAT_1454372c0), DAT_1454372c0 == -1)) { + FUN_142471b60(&DAT_145437260); + _DAT_145437260 = + `private:_virtual_void___cdecl_db::DatabaseMediator::onRekordboxDBChanged(long,juce::var_const&___ptr64)___ptr64' + ::__l24::DBInitializer::vftable; + FUN_1423d59e0(&DAT_145437278); + DAT_1454372b8 = (longlong *)0x0; + atexit(FUN_142c3a6e0); + _Init_thread_footer(&DAT_1454372c0); + } + local_6a8 = std::_Func_impl_no_alloc<,void>::vftable; + uStack_698 = CONCAT44(param_2,iVar8); + local_670 = &local_6a8; + local_7d8 = &local_6a8; + local_6a0 = param_1; + local_598 = param_1; + iStack_590 = iVar8; + iStack_58c = param_2; + if (((*DAT_145437278 != '\0') && + (cVar4 = FUN_1423d45c0(&DAT_145437278,local_800), cVar4 != '\0')) && + (DAT_1454372b8 != (longlong *)0x0)) { + (**(code **)(*DAT_1454372b8 + 0x10))(); + } + FUN_1423d58d0(&DAT_145437278,local_800); + FUN_140d24ba0(&DAT_145437280,&local_6a8); + FUN_142471a20(&DAT_145437260,3000); + if (local_670 == (undefined ***)0x0) goto LAB_141ae5a25; + pppuVar17 = &local_6a8; + pppuVar18 = local_670; + } + (*(code *)(*pppuVar18)[4]) + (pppuVar18,CONCAT71((int7)((ulonglong)pppuVar17 >> 8),pppuVar18 != pppuVar17)); +LAB_141ae5a25: + FUN_1423c3ab0(local_570); + FUN_1423d59b0(local_800); + FUN_1423d59b0(local_7f0); + return; +} + + +// scanned 9123005 instructions diff --git a/maple-py/re/findings/find4a68.txt b/maple-py/re/findings/find4a68.txt new file mode 100644 index 0000000..a67cbd2 --- /dev/null +++ b/maple-py/re/findings/find4a68.txt @@ -0,0 +1,595 @@ +// HIT 0x4a68 @141f0a08b in FUN_141f09e70@141f09e70 : LEA RCX,[RDI + 0x4a68] +// HIT 0x4a68 @141f0a08b in FUN_141f09e70@141f09e70 : LEA RCX,[RDI + 0x4a68] +// HIT 0x4a68 @141f15d17 in FUN_141f15bf0@141f15bf0 : LEA RCX,[RBX + 0x4a68] +// HIT 0x4a68 @141f15d17 in FUN_141f15bf0@141f15bf0 : LEA RCX,[RBX + 0x4a68] +// HIT 0x4a68 @141f637b7 in FUN_141f63210@141f63210 : MOV RAX,qword ptr [RBX + 0x4a68] +// HIT 0x4a68 @141f637b7 in FUN_141f63210@141f63210 : MOV RAX,qword ptr [RBX + 0x4a68] +// HIT 0x4a68 @141f637be in FUN_141f63210@141f63210 : LEA RCX,[RBX + 0x4a68] +// HIT 0x4a68 @141f637be in FUN_141f63210@141f63210 : LEA RCX,[RBX + 0x4a68] +// HIT 0x4a68 @141f637da in FUN_141f63210@141f63210 : LEA RCX,[RBX + 0x4a68] +// HIT 0x4a68 @141f637da in FUN_141f63210@141f63210 : LEA RCX,[RBX + 0x4a68] +// HIT 0x4a68 @141f637e1 in FUN_141f63210@141f63210 : MOV RAX,qword ptr [RBX + 0x4a68] +// HIT 0x4a68 @141f637e1 in FUN_141f63210@141f63210 : MOV RAX,qword ptr [RBX + 0x4a68] +// --- decompiling containing functions --- + +//===== FUN_141f09e70 @ 141f09e70 ===== + +undefined8 * +FUN_141f09e70(undefined8 *param_1,undefined4 param_2,undefined8 param_3,longlong param_4) + +{ + undefined8 *puVar1; + char cVar2; + undefined8 uVar3; + undefined8 *puVar4; + undefined8 *puVar5; + longlong lVar6; + undefined8 *puVar7; + undefined8 *puVar8; + longlong local_res20; + undefined1 local_f8 [4]; + undefined8 local_f4; + undefined1 local_ec; + undefined4 local_e8; + undefined1 local_e0 [8]; + undefined1 local_d8 [8]; + undefined1 local_d0 [8]; + undefined1 local_c8 [8]; + undefined2 local_c0; + undefined1 local_b8 [8]; + undefined1 local_b0; + undefined1 local_a8 [8]; + undefined4 local_a0; + undefined1 local_98 [8]; + undefined1 local_90 [8]; + undefined1 local_88 [8]; + undefined1 local_80 [8]; + char *local_78; + undefined1 local_70 [16]; + undefined1 local_60 [16]; + undefined1 local_50 [24]; + + puVar8 = (undefined8 *)0x0; + uVar3 = FUN_1423d5980(&local_res20,param_3); + FUN_141f09b00(param_1,0xffffffff,uVar3); + FUN_14246e8b0(param_1 + 0x134); + *param_1 = djplay::HidMapCDJ2000NXS2::vftable; + param_1[0x14] = djplay::HidMapCDJ2000NXS2::vftable; + puVar1 = param_1 + 0x15; + *puVar1 = djplay::HidMapCDJ2000NXS2::vftable; + param_1[0x134] = djplay::HidMapCDJ2000NXS2::vftable; + param_1[0x138] = djplay::HidMapCDJ2000NXS2::vftable; + param_1[0x139] = djplay::HidMapCDJ2000NXS2::vftable; + param_1[0x13a] = djplay::HidMapCDJ2000NXS2::vftable; + *(undefined4 *)(param_1 + 0x13b) = 0x25f8; + param_1[0x1a3] = 0; + param_1[0x1a4] = 0; + param_1[0x1bb] = 0; + param_1[0x1bd] = 0; + param_1[0x1be] = 0; + param_1[0x1bf] = 0; + param_1[0x1c0] = 0; + *(undefined4 *)(param_1 + 0x1c1) = 0; + *(undefined2 *)((longlong)param_1 + 0xe0c) = 0; + *(undefined1 *)((longlong)param_1 + 0xe0e) = 0; + *(undefined4 *)(param_1 + 0x1c2) = 3; + puVar7 = param_1 + 0x1c4; + *puVar7 = 0; + param_1[0x1c5] = 0; + param_1[0x1c6] = 0; + param_1[0x1c8] = 0; + FUN_141ee27c0(param_1 + 0x1c9); + _eh_vector_constructor_iterator_(param_1 + 0x2d5,0x470,4,FUN_141ee3f50,FUN_141ee3fe0); + FUN_141ee3f50(param_1 + 0x50d); + FUN_141ee4d90(param_1 + 0x59c); + FUN_141ee5080(param_1 + 0x62b); + FUN_141ee5400(param_1 + 0x6ba); + FUN_141ee5970(param_1 + 0x74a); + FUN_141ee5db0(param_1 + 0x7da); + FUN_141ee6210(param_1 + 0x86a); + FUN_141ee97f0(param_1 + 0x8fb); + FUN_141ee9a10(param_1 + 0x907); + FUN_141ee9c10(param_1 + 0x915); + FUN_141ee9c10(param_1 + 0x920); + FUN_141ee9c10(param_1 + 0x92b); + FUN_141ee9c10(param_1 + 0x936); + FUN_141ee9e30(param_1 + 0x941); + FUN_141eea060(param_1 + 0x94d); + FUN_141eea150(param_1 + 0x959); + FUN_141eea330(param_1 + 0x964); + *(undefined2 *)(param_1 + 0x96f) = 0; + *(undefined1 *)((longlong)param_1 + 0x4b7a) = 0; + *(undefined4 *)((longlong)param_1 + 0x4b7c) = 0; + *(undefined1 *)(param_1 + 0x970) = 1; + param_1[0x197] = 0; + puVar4 = (undefined8 *)FUN_1423ce840(local_98,"CDJ-TOUR1"); + cVar2 = FUN_1423d2890(param_3,*puVar4); + if (cVar2 != '\0') { + *(undefined1 *)((longlong)param_1 + 0xe0b) = 1; + } + *(undefined4 *)((longlong)param_1 + 0xe1a) = 0; + *(undefined1 *)((longlong)param_1 + 0xe1e) = 0; + LOCK(); + *(undefined4 *)(param_1 + 0x1c8) = 0; + UNLOCK(); + LOCK(); + *(undefined4 *)((longlong)param_1 + 0xe44) = 0; + UNLOCK(); + puVar5 = (undefined8 *)FUN_14217c644(0x40); + puVar4 = puVar8; + if (puVar5 != (undefined8 *)0x0) { + FUN_1423aa4f0(puVar5); + puVar5[6] = 0; + *(undefined4 *)(puVar5 + 7) = 8; + puVar4 = puVar5; + } + param_1[0x1c7] = puVar4; + lVar6 = FUN_14217c644(0x478); + puVar4 = puVar8; + if (lVar6 != 0) { + puVar4 = (undefined8 *)FUN_141ee41c0(lVar6,8); + } + param_1[0x59b] = puVar4; + local_f8[0] = 0; + local_f4 = 0; + local_ec = 0; + local_e8 = 0; + FUN_1423d59e0(local_e0); + FUN_1423d59e0(local_d8); + FUN_1423d59e0(local_d0); + FUN_1423d59e0(local_c8); + local_c0 = 0; + FUN_1423d59e0(local_b8); + local_b0 = 0; + FUN_1423d59e0(local_a8); + local_a0 = 0; + lVar6 = param_1[0x1c5]; + if (lVar6 == param_1[0x1c6]) { + FUN_141edc340(puVar7,lVar6,local_f8); + } + else { + FUN_141068df0(lVar6,local_f8); + param_1[0x1c5] = param_1[0x1c5] + 0x60; + } + lVar6 = param_1[0x1c5]; + if (lVar6 == param_1[0x1c6]) { + FUN_141edc340(puVar7,lVar6,local_f8); + } + else { + FUN_141068df0(lVar6,local_f8); + param_1[0x1c5] = param_1[0x1c5] + 0x60; + } + lVar6 = param_1[0x1c5]; + if (lVar6 == param_1[0x1c6]) { + FUN_141edc340(puVar7,lVar6,local_f8); + } + else { + FUN_141068df0(lVar6,local_f8); + param_1[0x1c5] = param_1[0x1c5] + 0x60; + } + lVar6 = param_1[0x1c5]; + if (lVar6 == param_1[0x1c6]) { + FUN_141edc340(puVar7,lVar6,local_f8); + } + else { + FUN_141068df0(lVar6,local_f8); + param_1[0x1c5] = param_1[0x1c5] + 0x60; + } + lVar6 = param_1[0x1c5]; + if (lVar6 == param_1[0x1c6]) { + FUN_141edc340(puVar7,lVar6,local_f8); + } + else { + FUN_141068df0(lVar6,local_f8); + param_1[0x1c5] = param_1[0x1c5] + 0x60; + } + lVar6 = param_1[0x1c5]; + if (lVar6 == param_1[0x1c6]) { + FUN_141edc340(puVar7,lVar6,local_f8); + } + else { + FUN_141068df0(lVar6,local_f8); + param_1[0x1c5] = param_1[0x1c5] + 0x60; + } + lVar6 = param_1[0x1c5]; + if (lVar6 == param_1[0x1c6]) { + FUN_141edc340(puVar7,lVar6,local_f8); + } + else { + FUN_141068df0(lVar6,local_f8); + param_1[0x1c5] = param_1[0x1c5] + 0x60; + } + lVar6 = param_1[0x1c5]; + if (lVar6 == param_1[0x1c6]) { + FUN_141edc340(puVar7,lVar6,local_f8); + } + else { + FUN_141068df0(lVar6,local_f8); + param_1[0x1c5] = param_1[0x1c5] + 0x60; + } + if (param_4 == 0) { + uVar3 = FUN_1423e00f0(local_60,0x41,1); + local_78 = "@Activate"; + FUN_1423dfec0(local_70,uVar3); + FUN_141f07e80(param_1 + 0x132,local_50,&local_78); + FUN_1423dffc0(local_70); + FUN_1423dffc0(local_60); + puVar4 = (undefined8 *)FUN_14217c644(0x30); + puVar7 = puVar8; + if (puVar4 != (undefined8 *)0x0) { + *puVar4 = djplay::HidExtentFormat::vftable; + *(undefined2 *)(puVar4 + 1) = 0xf0; + puVar4[2] = 0; + puVar4[3] = 0; + puVar4[4] = 0; + puVar4[5] = 0; + puVar7 = puVar4; + } + puVar4 = (undefined8 *)param_1[0x1be]; + param_1[0x1be] = puVar7; + if (puVar4 != (undefined8 *)0x0) { + (**(code **)*puVar4)(puVar4,1); + } + uVar3 = FUN_1423d5720(&local_res20,"@AuthReq"); + FUN_141f1be20(param_1,0,0x11,uVar3,puVar1); + uVar3 = FUN_1423d5720(local_90,"@AuthChallengeA"); + FUN_141f1be20(param_1,0,0x12,uVar3,puVar1); + uVar3 = FUN_1423d5720(local_88,"@AuthResponseA"); + FUN_141f1be20(param_1,0,0x13,uVar3,puVar1); + uVar3 = FUN_1423d5720(local_80,"@AuthResponseE"); + FUN_141f1be20(param_1,0,0x14,uVar3,puVar1); + uVar3 = FUN_1423d5720(local_98,"@AuthEnd"); + FUN_141f1be20(param_1,0,0x15,uVar3,puVar1); + local_res20 = FUN_14217c644(0x1d0); + if (local_res20 != 0) { + puVar8 = (undefined8 *)FUN_141f093a0(local_res20,puVar1,0); + } + param_1[0x1bd] = puVar8; + *(undefined4 *)(puVar8 + 0x2f) = 4; + if (param_1[0x1bd] != 0) { + FUN_141f69980(param_1[0x1bd],param_1 + 0x13a); + } + } + else { + param_1[0x1bd] = param_4; + *(undefined1 *)((longlong)param_1 + 0xe09) = 1; + *(undefined4 *)((longlong)param_1 + 0x4b7c) = param_2; + FUN_141f40750(param_1); + } + FUN_1423d59b0(local_a8); + FUN_1423d59b0(local_b8); + FUN_1423d59b0(local_c8); + FUN_1423d59b0(local_d0); + FUN_1423d59b0(local_d8); + FUN_1423d59b0(local_e0); + FUN_1423d59b0(param_3); + return param_1; +} + + + +//===== FUN_141f15bf0 @ 141f15bf0 ===== + +void FUN_141f15bf0(undefined8 *param_1) + +{ + undefined8 *puVar1; + LPCRITICAL_SECTION lpCriticalSection; + longlong *plVar2; + undefined8 uVar3; + int iVar4; + + *param_1 = djplay::HidMapCDJ2000NXS2::vftable; + param_1[0x14] = djplay::HidMapCDJ2000NXS2::vftable; + param_1[0x15] = djplay::HidMapCDJ2000NXS2::vftable; + param_1[0x134] = djplay::HidMapCDJ2000NXS2::vftable; + param_1[0x138] = djplay::HidMapCDJ2000NXS2::vftable; + param_1[0x139] = djplay::HidMapCDJ2000NXS2::vftable; + param_1[0x13a] = djplay::HidMapCDJ2000NXS2::vftable; + if (*(char *)((longlong)param_1 + 0xe09) == '\0') { + if (param_1[0x1bd] != 0) { + FUN_141f72e10(param_1[0x1bd],param_1 + 0x13a); + } + puVar1 = (undefined8 *)param_1[0x1bd]; + if (puVar1 != (undefined8 *)0x0) { + (**(code **)*puVar1)(puVar1,1); + } + } + uVar3 = FUN_14106db40(); + FUN_141ed5ce0(uVar3,param_1 + 0x138); + uVar3 = FUN_1414518c0(); + FUN_141ede760(uVar3,param_1 + 0x139); + lpCriticalSection = (LPCRITICAL_SECTION)param_1[0x1c7]; + if (lpCriticalSection != (LPCRITICAL_SECTION)0x0) { + DeleteCriticalSection(lpCriticalSection); + thunk_FUN_14217cc5c(lpCriticalSection,0x40); + } + puVar1 = (undefined8 *)param_1[0x59b]; + if (puVar1 != (undefined8 *)0x0) { + (**(code **)*puVar1)(puVar1,1); + } + FUN_141eea370(param_1 + 0x964); + FUN_141eea1a0(param_1 + 0x959); + FUN_141eea0a0(param_1 + 0x94d); + FUN_141ee9e80(param_1 + 0x941); + FUN_141ee9c60(param_1 + 0x936); + FUN_141ee9c60(param_1 + 0x92b); + FUN_141ee9c60(param_1 + 0x920); + FUN_141ee9c60(param_1 + 0x915); + FUN_141ee9a70(param_1 + 0x907); + FUN_141ee9860(param_1 + 0x8fb); + FUN_141ee6340(param_1 + 0x86a); + FUN_141ee5ee0(param_1 + 0x7da); + FUN_141ee5aa0(param_1 + 0x74a); + FUN_141ee54a0(param_1 + 0x6ba); + FUN_141ee5120(param_1 + 0x62b); + FUN_141ee4ea0(param_1 + 0x59c); + FUN_141ee3fe0(param_1 + 0x50d); + _eh_vector_destructor_iterator_(param_1 + 0x2d5,0x470,4,FUN_141ee3fe0); + FUN_141ee2880(param_1 + 0x1c9); + FUN_141edd2a0(param_1 + 0x1c4); + iVar4 = 0; + if (0 < *(int *)((longlong)param_1 + 0xe04)) { + do { + FUN_1423dffc0((longlong)iVar4 * 0x10 + param_1[0x1bf]); + iVar4 = iVar4 + 1; + } while (iVar4 < *(int *)((longlong)param_1 + 0xe04)); + } + *(undefined4 *)((longlong)param_1 + 0xe04) = 0; + FUN_14230889c(param_1[0x1bf]); + puVar1 = (undefined8 *)param_1[0x1be]; + if (puVar1 != (undefined8 *)0x0) { + (**(code **)*puVar1)(puVar1,1); + } + plVar2 = (longlong *)param_1[0x1bb]; + if (plVar2 != (longlong *)0x0) { + (**(code **)(*plVar2 + 0x38))(plVar2,1); + } + puVar1 = (undefined8 *)param_1[0x1a4]; + if (puVar1 != (undefined8 *)0x0) { + (**(code **)*puVar1)(puVar1,1); + } + puVar1 = (undefined8 *)param_1[0x1a3]; + if (puVar1 != (undefined8 *)0x0) { + (**(code **)*puVar1)(puVar1,1); + } + param_1[0x13a] = djplay::DeviceAuth::Listener::vftable; + param_1[0x139] = djplay::TrackInformationManager::Listener::vftable; + param_1[0x138] = djplay::DisplayDataManager::Listener::vftable; + FUN_14246e800(param_1 + 0x134); + FUN_141f15b10(param_1); + return; +} + + + +//===== FUN_141f63210 @ 141f63210 ===== + +void FUN_141f63210(longlong param_1,undefined8 *param_2,undefined4 *param_3,undefined8 param_4, + undefined1 param_5) + +{ + char cVar1; + undefined8 uVar2; + undefined8 local_18 [2]; + + switch(param_5) { + case 0x21: + cVar1 = (**(code **)(*(longlong *)(param_1 + 0xe48) + 0x10))(param_1 + 0xe48); + if (cVar1 != '\0') { + local_18[0] = 0; + uVar2 = (**(code **)(*(longlong *)(param_1 + 0xe48) + 8))(param_1 + 0xe48,local_18,param_4); + *param_2 = uVar2; + goto LAB_141f63871; + } + break; + case 0x22: + cVar1 = (**(code **)(*(longlong *)(param_1 + 0x16a8) + 0x10))(param_1 + 0x16a8); + if (cVar1 != '\0') { + local_18[0] = 0; + uVar2 = (**(code **)(*(longlong *)(param_1 + 0x16a8) + 8))(param_1 + 0x16a8,local_18,param_4); + *param_2 = uVar2; + goto LAB_141f63871; + } + break; + case 0x23: + cVar1 = (**(code **)(*(longlong *)(param_1 + 0x1b18) + 0x10))(param_1 + 0x1b18); + if (cVar1 != '\0') { + local_18[0] = 0; + uVar2 = (**(code **)(*(longlong *)(param_1 + 0x1b18) + 8))(param_1 + 0x1b18,local_18,param_4); + *param_2 = uVar2; + goto LAB_141f63871; + } + break; + case 0x24: + cVar1 = (**(code **)(*(longlong *)(param_1 + 0x1f88) + 0x10))(param_1 + 0x1f88); + if (cVar1 != '\0') { + local_18[0] = 0; + uVar2 = (**(code **)(*(longlong *)(param_1 + 0x1f88) + 8))(param_1 + 0x1f88,local_18,param_4); + *param_2 = uVar2; + goto LAB_141f63871; + } + break; + case 0x25: + cVar1 = (**(code **)(*(longlong *)(param_1 + 0x23f8) + 0x10))(param_1 + 0x23f8); + if (cVar1 != '\0') { + local_18[0] = 0; + uVar2 = (**(code **)(*(longlong *)(param_1 + 0x23f8) + 8))(param_1 + 0x23f8,local_18,param_4); + *param_2 = uVar2; + goto LAB_141f63871; + } + break; + case 0x26: + cVar1 = (**(code **)(*(longlong *)(param_1 + 0x2868) + 0x10))(param_1 + 0x2868); + if (cVar1 != '\0') { + local_18[0] = 0; + uVar2 = (**(code **)(*(longlong *)(param_1 + 0x2868) + 8))(param_1 + 0x2868,local_18,param_4); + *param_2 = uVar2; + goto LAB_141f63871; + } + break; + case 0x2a: + if ((*(longlong **)(param_1 + 0x2cd8) != (longlong *)0x0) && + (cVar1 = (**(code **)(**(longlong **)(param_1 + 0x2cd8) + 0x10))(), cVar1 != '\0')) { + local_18[0] = 0; + uVar2 = (**(code **)(**(longlong **)(param_1 + 0x2cd8) + 8)) + (*(longlong **)(param_1 + 0x2cd8),local_18,param_4); + *param_2 = uVar2; + goto LAB_141f63871; + } + break; + case 0x2b: + cVar1 = (**(code **)(*(longlong *)(param_1 + 0x2ce0) + 0x10))(param_1 + 0x2ce0); + if (cVar1 != '\0') { + local_18[0] = 0; + uVar2 = (**(code **)(*(longlong *)(param_1 + 0x2ce0) + 8))(param_1 + 0x2ce0,local_18,param_4); + *param_2 = uVar2; + goto LAB_141f63871; + } + break; + case 0x2c: + cVar1 = (**(code **)(*(longlong *)(param_1 + 0x3158) + 0x10))(param_1 + 0x3158); + if (cVar1 != '\0') { + local_18[0] = 0; + uVar2 = (**(code **)(*(longlong *)(param_1 + 0x3158) + 8))(param_1 + 0x3158,local_18,param_4); + *param_2 = uVar2; + goto LAB_141f63871; + } + break; + case 0x2d: + cVar1 = (**(code **)(*(longlong *)(param_1 + 0x35d0) + 0x10))(param_1 + 0x35d0); + if (cVar1 != '\0') { + local_18[0] = 0; + uVar2 = (**(code **)(*(longlong *)(param_1 + 0x35d0) + 8))(param_1 + 0x35d0,local_18,param_4); + *param_2 = uVar2; + goto LAB_141f63871; + } + break; + case 0x2e: + if ((*(char *)(param_1 + 0xe0c) == '\0') && + (cVar1 = (**(code **)(*(longlong *)(param_1 + 0x3a50) + 0x10))(param_1 + 0x3a50), + cVar1 != '\0')) { + local_18[0] = 0; + uVar2 = (**(code **)(*(longlong *)(param_1 + 0x3a50) + 8))(param_1 + 0x3a50,local_18,param_4); + *param_2 = uVar2; + goto LAB_141f63871; + } + break; + case 0x2f: + if ((*(char *)(param_1 + 0xe0c) == '\0') && + (cVar1 = (**(code **)(*(longlong *)(param_1 + 0x3ed0) + 0x10))(param_1 + 0x3ed0), + cVar1 != '\0')) { + local_18[0] = 0; + uVar2 = (**(code **)(*(longlong *)(param_1 + 0x3ed0) + 8))(param_1 + 0x3ed0,local_18,param_4); + *param_2 = uVar2; + goto LAB_141f63871; + } + break; + case 0x30: + if ((*(char *)(param_1 + 0xe0c) == '\0') && + (cVar1 = (**(code **)(*(longlong *)(param_1 + 0x4350) + 0x10))(param_1 + 0x4350), + cVar1 != '\0')) { + local_18[0] = 0; + uVar2 = (**(code **)(*(longlong *)(param_1 + 0x4350) + 8))(param_1 + 0x4350,local_18,param_4); + *param_2 = uVar2; + goto LAB_141f63871; + } + break; + case 0x40: + cVar1 = (**(code **)(*(longlong *)(param_1 + 0x47d8) + 0x10))(param_1 + 0x47d8); + if (cVar1 != '\0') { + local_18[0] = 0; + uVar2 = (**(code **)(*(longlong *)(param_1 + 0x47d8) + 8))(param_1 + 0x47d8,local_18,param_4); + *param_2 = uVar2; + goto LAB_141f63871; + } + break; + case 0x41: + cVar1 = (**(code **)(*(longlong *)(param_1 + 0x4838) + 0x10))(param_1 + 0x4838); + if (cVar1 != '\0') { + local_18[0] = 0; + uVar2 = (**(code **)(*(longlong *)(param_1 + 0x4838) + 8))(param_1 + 0x4838,local_18,param_4); + *param_2 = uVar2; + goto LAB_141f63871; + } + break; + case 0x42: + cVar1 = (**(code **)(*(longlong *)(param_1 + 0x48a8) + 0x10))(param_1 + 0x48a8); + if (cVar1 != '\0') { + local_18[0] = 0; + uVar2 = (**(code **)(*(longlong *)(param_1 + 0x48a8) + 8))(param_1 + 0x48a8,local_18,param_4); + *param_2 = uVar2; + goto LAB_141f63871; + } + break; + case 0x43: + cVar1 = (**(code **)(*(longlong *)(param_1 + 0x4900) + 0x10))(param_1 + 0x4900); + if (cVar1 != '\0') { + local_18[0] = 0; + uVar2 = (**(code **)(*(longlong *)(param_1 + 0x4900) + 8))(param_1 + 0x4900,local_18,param_4); + *param_2 = uVar2; + goto LAB_141f63871; + } + break; + case 0x44: + cVar1 = (**(code **)(*(longlong *)(param_1 + 0x4958) + 0x10))(param_1 + 0x4958); + if (cVar1 != '\0') { + local_18[0] = 0; + uVar2 = (**(code **)(*(longlong *)(param_1 + 0x4958) + 8))(param_1 + 0x4958,local_18,param_4); + *param_2 = uVar2; + goto LAB_141f63871; + } + break; + case 0x45: + cVar1 = (**(code **)(*(longlong *)(param_1 + 0x49b0) + 0x10))(param_1 + 0x49b0); + if (cVar1 != '\0') { + local_18[0] = 0; + uVar2 = (**(code **)(*(longlong *)(param_1 + 0x49b0) + 8))(param_1 + 0x49b0,local_18,param_4); + *param_2 = uVar2; + goto LAB_141f63871; + } + break; + case 0x46: + cVar1 = (**(code **)(*(longlong *)(param_1 + 0x4a08) + 0x10))(param_1 + 0x4a08); + if (cVar1 != '\0') { + local_18[0] = 0; + uVar2 = (**(code **)(*(longlong *)(param_1 + 0x4a08) + 8))(param_1 + 0x4a08,local_18,param_4); + *param_2 = uVar2; + goto LAB_141f63871; + } + break; + case 0x47: + cVar1 = (**(code **)(*(longlong *)(param_1 + 0x4a68) + 0x10))(param_1 + 0x4a68); + if (cVar1 != '\0') { + local_18[0] = 0; + uVar2 = (**(code **)(*(longlong *)(param_1 + 0x4a68) + 8))(param_1 + 0x4a68,local_18,param_4); + *param_2 = uVar2; + goto LAB_141f63871; + } + break; + case 0x48: + cVar1 = (**(code **)(*(longlong *)(param_1 + 0x4ac8) + 0x10))(param_1 + 0x4ac8); + if (cVar1 != '\0') { + local_18[0] = 0; + uVar2 = (**(code **)(*(longlong *)(param_1 + 0x4ac8) + 8))(param_1 + 0x4ac8,local_18,param_4); + *param_2 = uVar2; + goto LAB_141f63871; + } + break; + case 0x4f: + cVar1 = (**(code **)(*(longlong *)(param_1 + 0x4b20) + 0x10))(param_1 + 0x4b20); + if (cVar1 != '\0') { + local_18[0] = 0; + uVar2 = (**(code **)(*(longlong *)(param_1 + 0x4b20) + 8))(param_1 + 0x4b20,local_18,param_4); + *param_2 = uVar2; + goto LAB_141f63871; + } + } + local_18[0]._0_4_ = 0; + *param_2 = 0; +LAB_141f63871: + *param_3 = (undefined4)local_18[0]; + return; +} + + diff --git a/maple-py/re/scripts/DecRoot.java b/maple-py/re/scripts/DecRoot.java new file mode 100644 index 0000000..c97944c --- /dev/null +++ b/maple-py/re/scripts/DecRoot.java @@ -0,0 +1,31 @@ +// Decompile the export root FUN_14119d2d0 and selected callees, to find the +// per-track row-insert that sets index_shift (0x02) and the 0x14/0x18/0x1a blob. +// Also dump callees of FUN_14117be20 (rekordbox/export.pdb string xref). +// Output -> ~/ghidra_proj/decroot.txt +import java.io.PrintWriter; +import java.util.*; +import ghidra.app.script.GhidraScript; +import ghidra.app.decompiler.*; +import ghidra.program.model.listing.*; + +public class DecRoot extends GhidraScript { + PrintWriter out; DecompInterface di; FunctionManager fm; + Set done = new HashSet<>(); + void dec(String a, String why) { + Function f = fm.getFunctionContaining(currentProgram.getAddressFactory().getAddress(a)); + if (f == null) { out.println("// no fn at " + a + " (" + why + ")"); return; } + if (!done.add(f.getEntryPoint().toString())) return; + out.println("\n//===== " + f.getName() + " @ " + f.getEntryPoint() + " [" + why + "] ====="); + DecompileResults r = di.decompileFunction(f, 120, monitor); + out.println(r != null && r.decompileCompleted() ? r.getDecompiledFunction().getC() : "// decompile failed: "+(r!=null?r.getErrorMessage():"null")); + } + @Override public void run() throws Exception { + out = new PrintWriter("/home/ntr/ghidra_proj/decroot.txt"); + di = new DecompInterface(); di.openProgram(currentProgram); + fm = currentProgram.getFunctionManager(); + dec("0x14119d2d0", "export root (/.PIONEER/rekordbox/export.pdb)"); + dec("0x14117be20", "rekordbox/export.pdb xref"); + out.flush(); out.close(); + println("wrote decroot.txt"); + } +} diff --git a/maple-py/re/scripts/DecRow.java b/maple-py/re/scripts/DecRow.java new file mode 100644 index 0000000..0626715 --- /dev/null +++ b/maple-py/re/scripts/DecRow.java @@ -0,0 +1,79 @@ +// Decode the track-row builder: the function that maps a djmdContent record into +// the on-disk DeviceSQL row (offset 0x02 index_shift, 0x14 u4 hash, 0x18/0x1a u2). +// Decompiles FUN_141c04be0 (AnalysisDataPath/SearchStr/MasterContentID mapper) and +// FUN_141ae4860 (djmdContent), plus scans for code referencing the constant +// 0x21b4a7ab / 0xa7ab and for `<<5` row-index assignment. +// Output -> ~/ghidra_proj/decrow.txt +import java.io.PrintWriter; +import java.util.*; +import ghidra.app.script.GhidraScript; +import ghidra.app.decompiler.*; +import ghidra.program.model.address.*; +import ghidra.program.model.listing.*; +import ghidra.program.model.scalar.Scalar; +import ghidra.program.model.lang.OperandType; + +public class DecRow extends GhidraScript { + PrintWriter out; + DecompInterface di; + Set done = new HashSet<>(); + FunctionManager fm; + + void dec(String a, String why) { + Function f = fm.getFunctionContaining(currentProgram.getAddressFactory().getAddress(a)); + if (f == null) { out.println("// no function at " + a + " (" + why + ")"); return; } + if (!done.add(f.getEntryPoint().toString())) return; + out.println("\n//===== " + f.getName() + " @ " + f.getEntryPoint() + " [" + why + "] ====="); + DecompileResults r = di.decompileFunction(f, 90, monitor); + out.println(r != null && r.decompileCompleted() ? r.getDecompiledFunction().getC() : "// decompile failed"); + } + + @Override public void run() throws Exception { + out = new PrintWriter("/home/ntr/ghidra_proj/decrow.txt"); + di = new DecompInterface(); di.openProgram(currentProgram); + fm = currentProgram.getFunctionManager(); + + dec("0x141c04be0", "AnalysisDataPath/SearchStr/MasterContentID mapper"); + dec("0x141c07230", "SearchStr 2"); + dec("0x141c076d0", "SearchStr 3"); + dec("0x141ae4860", "djmdContent xrefs"); + + // Scan all instructions for the per-export constants used in 0x18/0x1a + // (0xa7ab=42923, 0x21b4=8628, combined u4 0x21b4a7ab=565353387) and for + // an immediate 0x20 / shift-by-5 pattern that would build index_shift. + long[] consts = {0xa7abL, 0x21b4L, 0x21b4a7abL, 0xa7ab21b4L}; + Set hitFns = new LinkedHashSet<>(); + Listing lst = currentProgram.getListing(); + InstructionIterator it = lst.getInstructions(true); + int scanned = 0; + while (it.hasNext() && !monitor.isCancelled()) { + Instruction ins = it.next(); + scanned++; + for (int op = 0; op < ins.getNumOperands(); op++) { + Object[] objs = ins.getOpObjects(op); + for (Object o : objs) { + if (o instanceof Scalar) { + long v = ((Scalar) o).getUnsignedValue(); + for (long c : consts) { + if (v == c) { + Function f = fm.getFunctionContaining(ins.getAddress()); + String tag = "const 0x" + Long.toHexString(c) + " @" + ins.getAddress() + + " in " + (f != null ? f.getName() + "@" + f.getEntryPoint() : "?") + + " : " + ins.toString(); + out.println("// SCAN " + tag); + if (f != null) hitFns.add(f.getEntryPoint().toString() + "|const0x" + Long.toHexString(c)); + } + } + } + } + } + } + out.println("// scanned " + scanned + " instructions"); + for (String h : hitFns) { + String[] parts = h.split("\\|"); + dec("0x" + parts[0], parts[1]); + } + out.flush(); out.close(); + println("wrote decrow.txt"); + } +} diff --git a/maple-py/re/scripts/DecShift.java b/maple-py/re/scripts/DecShift.java new file mode 100644 index 0000000..92c5287 --- /dev/null +++ b/maple-py/re/scripts/DecShift.java @@ -0,0 +1,30 @@ +// Decompile the prime index_shift candidate FUN_14232a86c (shl5 + mask 0x7ff) and +// a few neighboring DeviceSQL page-manager functions. Output -> ~/ghidra_proj/decshift.txt +import java.io.PrintWriter; +import java.util.*; +import ghidra.app.script.GhidraScript; +import ghidra.app.decompiler.*; +import ghidra.program.model.listing.*; + +public class DecShift extends GhidraScript { + PrintWriter out; DecompInterface di; FunctionManager fm; Set done=new HashSet<>(); + void dec(String a, String why) { + Function f = fm.getFunctionContaining(currentProgram.getAddressFactory().getAddress(a)); + if (f == null) { out.println("// no fn at " + a + " (" + why + ")"); return; } + if (!done.add(f.getEntryPoint().toString())) return; + out.println("\n//===== " + f.getName() + " @ " + f.getEntryPoint() + " [" + why + "] ====="); + DecompileResults r = di.decompileFunction(f, 120, monitor); + out.println(r != null && r.decompileCompleted() ? r.getDecompiledFunction().getC() : "// decompile failed"); + } + @Override public void run() throws Exception { + out = new PrintWriter("/home/ntr/ghidra_proj/decshift.txt"); + di = new DecompInterface(); di.openProgram(currentProgram); + fm = currentProgram.getFunctionManager(); + dec("0x14232a86c", "shl5+mask7ff PRIME index_shift candidate"); + // neighbors in DeviceSQL page-manager range + dec("0x1423e1550", "page-mgr neighbor"); + dec("0x1423e12a0", "page-mgr neighbor"); + out.flush(); out.close(); + println("wrote decshift.txt"); + } +} diff --git a/maple-py/re/scripts/Find4A68.java b/maple-py/re/scripts/Find4A68.java new file mode 100644 index 0000000..267344f --- /dev/null +++ b/maple-py/re/scripts/Find4A68.java @@ -0,0 +1,47 @@ +// @flesniak documented track-row 0x18=u2 "always 19048" (0x4A68) and 0x1a=u2 +// "always 30967" (0x78F7). Our AceMo export has 0xa7ab / 0x21b4 instead, and a +// const-scan found no 0xa7ab/0x21b4 immediates. So locate every reference to the +// documented defaults 0x4A68 and 0x78F7 (and combined 0x78F74A68 / 0x4A6878F7), +// decompile the containing functions, and report. This should reveal how 0x18/0x1a +// are produced (literal default vs computed). Output -> ~/ghidra_proj/find4a68.txt +import java.io.PrintWriter; +import java.util.*; +import ghidra.app.script.GhidraScript; +import ghidra.app.decompiler.*; +import ghidra.program.model.listing.*; +import ghidra.program.model.scalar.Scalar; + +public class Find4A68 extends GhidraScript { + @Override public void run() throws Exception { + PrintWriter out = new PrintWriter("/home/ntr/ghidra_proj/find4a68.txt"); + FunctionManager fm = currentProgram.getFunctionManager(); + DecompInterface di = new DecompInterface(); di.openProgram(currentProgram); + long[] consts = {0x4A68L, 0x78F7L, 0x78F74A68L, 0x4A6878F7L, 19048L, 30967L}; + Set fns = new LinkedHashSet<>(); + InstructionIterator it = currentProgram.getListing().getInstructions(true); + while (it.hasNext() && !monitor.isCancelled()) { + Instruction ins = it.next(); + for (int op=0; op +// ~/ghidra_proj/findbitmask.txt +import java.io.PrintWriter; +import java.util.*; +import ghidra.app.script.GhidraScript; +import ghidra.app.decompiler.*; +import ghidra.program.model.listing.*; +import ghidra.program.model.scalar.Scalar; + +public class FindBitmask extends GhidraScript { + @Override public void run() throws Exception { + PrintWriter out = new PrintWriter("/home/ntr/ghidra_proj/findbitmask.txt"); + FunctionManager fm = currentProgram.getFunctionManager(); + DecompInterface di = new DecompInterface(); di.openProgram(currentProgram); + long[] consts = {0xc0700L, 0x700L, 0xc07L}; + Set fns = new LinkedHashSet<>(); + InstructionIterator it = currentProgram.getListing().getInstructions(true); + while (it.hasNext() && !monitor.isCancelled()) { + Instruction ins = it.next(); + for (int op=0; op ~/ghidra_proj/finddefault.txt +import java.io.PrintWriter; +import ghidra.app.script.GhidraScript; +import ghidra.program.model.address.*; +import ghidra.program.model.mem.*; + +public class FindDefault extends GhidraScript { + @Override public void run() throws Exception { + PrintWriter out = new PrintWriter("/home/ntr/ghidra_proj/finddefault.txt"); + Memory mem = currentProgram.getMemory(); + byte[] pat = { (byte)0x68,(byte)0x4a,(byte)0xf7,(byte)0x78 }; // 0x78f74a68 LE + Address a = mem.getMinAddress(); + int found=0; + while (a != null && found < 50) { + Address hit = mem.findBytes(a, pat, null, true, monitor); + if (hit == null) break; + out.println("// 0x78f74a68 bytes @ " + hit); + found++; + a = hit.add(1); + } + out.println("// total occurrences of 0x78f74a68 LE in memory image: " + found); + out.flush(); out.close(); + println("wrote finddefault.txt found="+found); + } +} diff --git a/maple-py/re/scripts/FindShift.java b/maple-py/re/scripts/FindShift.java new file mode 100644 index 0000000..e72c1d5 --- /dev/null +++ b/maple-py/re/scripts/FindShift.java @@ -0,0 +1,51 @@ +// Find the DeviceSQL row-index code that computes index_shift = slot << 5. +// Heuristic: functions that contain BOTH a left-shift-by-5 (SHL r,5 / LEA r,[r*0x20]) +// AND reference the row-index masks (0x1fff / 0x7ff) or stride 0x24, AND a u2 store. +// Output -> ~/ghidra_proj/findshift.txt +import java.io.PrintWriter; +import java.util.*; +import ghidra.app.script.GhidraScript; +import ghidra.program.model.listing.*; +import ghidra.program.model.scalar.Scalar; + +public class FindShift extends GhidraScript { + @Override public void run() throws Exception { + PrintWriter out = new PrintWriter("/home/ntr/ghidra_proj/findshift.txt"); + FunctionManager fm = currentProgram.getFunctionManager(); + Listing lst = currentProgram.getListing(); + // accumulate per-function evidence + Map> ev = new HashMap<>(); + InstructionIterator it = lst.getInstructions(true); + while (it.hasNext() && !monitor.isCancelled()) { + Instruction ins = it.next(); + Function f = fm.getFunctionContaining(ins.getAddress()); + if (f == null) continue; + String key = f.getEntryPoint().toString(); + String mn = ins.getMnemonicString(); + String txt = ins.toString(); + boolean shl5 = (mn.equalsIgnoreCase("SHL") && txt.endsWith(",0x5")) + || (mn.equalsIgnoreCase("LEA") && txt.contains("*0x20")); + if (shl5) ev.computeIfAbsent(key,k->new HashSet<>()).add("shl5@"+ins.getAddress()); + for (int op=0; opnew HashSet<>()).add("mask1fff@"+ins.getAddress()); + if (v==0x7ff) ev.computeIfAbsent(key,k->new HashSet<>()).add("mask7ff@"+ins.getAddress()); + if (v==0x24) ev.computeIfAbsent(key,k->new HashSet<>()).add("stride24@"+ins.getAddress()); + } + } + // report functions that have shl5 AND (mask or stride) + for (Map.Entry> e : ev.entrySet()) { + Set tags = e.getValue(); + boolean hasShl = tags.stream().anyMatch(t->t.startsWith("shl5")); + boolean hasCtx = tags.stream().anyMatch(t->t.startsWith("mask1fff")||t.startsWith("mask7ff")||t.startsWith("stride24")); + if (hasShl && hasCtx) { + Function f = fm.getFunctionContaining(currentProgram.getAddressFactory().getAddress(e.getKey())); + out.println("CANDIDATE " + (f!=null?f.getName():"?") + " @" + e.getKey() + " : " + tags); + } + } + out.flush(); out.close(); + println("wrote findshift.txt"); + } +} diff --git a/maple-py/src/kaitai/rekordbox_pdb.ksy b/maple-py/src/kaitai/rekordbox_pdb.ksy index 152bdce..b283d69 100644 --- a/maple-py/src/kaitai/rekordbox_pdb.ksy +++ b/maple-py/src/kaitai/rekordbox_pdb.ksy @@ -652,15 +652,31 @@ types: type: u4 doc: | The length of the audio file, in bytes. - - type: u4 + - id: opaque_track_id_lo + type: u4 doc: | - Some ID? Purpose as yet unknown. - - type: u2 + RE (maple): per-track opaque value. Together with the two u2 fields + below this forms an 8-byte little-endian blob + (opaque_track_id_lo | (opaque_seed << 32)). The high 32 bits + (opaque_seed) are constant across every track in one export; the low + 32 bits vary per track and are NOT a hash of the file path, title, + filename, ANLZ .DAT/.EXT, or audio bytes (all tested). Believed to be + a stored rekordbox-internal id (rb_data UUID/hash). Not reproducible + from track metadata. See re/FORMAT_SPEC.md section 2. + - id: opaque_seed_lo + type: u2 doc: | - From @flesniak: "always 19048?" - - type: u2 + RE (maple): low u2 of the 32-bit per-export "opaque_seed" (see + opaque_track_id_lo). @flesniak called it "always 19048?"; that value + (0x4A68) does NOT hold here (AceMo export = 0xa7ab) and there is no + 0x4A68/0x78F7 immediate in the binary, so it is a per-export runtime + value, not a constant. + - id: opaque_seed_hi + type: u2 doc: | - From @flesniak: "always 30967?" + RE (maple): high u2 of the per-export "opaque_seed" + (AceMo export = 0x21b4; @flesniak's "always 30967?" = 0x78F7 does not + hold). Constant across all tracks in one export. - id: artwork_id type: u4 doc: | diff --git a/maple-py/src/kaitai/rekordbox_pdb.py b/maple-py/src/kaitai/rekordbox_pdb.py index eb7c5e7..6afd05a 100644 --- a/maple-py/src/kaitai/rekordbox_pdb.py +++ b/maple-py/src/kaitai/rekordbox_pdb.py @@ -1182,14 +1182,22 @@ class RekordboxPdb(KaitaiStruct): def _read(self): self.subtype = self._io.read_u2le() + # index_shift (0x02): RE rule = slot_index * 0x20 (slot_index << 5), + # where slot_index is the 0-based row-index slot on the page (counts + # deleted slots), resets per page. See re/FORMAT_SPEC.md section 1. self.index_shift = self._io.read_u2le() self.bitmask = self._io.read_u4le() self.sample_rate = self._io.read_u4le() self.composer_id = self._io.read_u4le() self.file_size = self._io.read_u4le() - self._unnamed6 = self._io.read_u4le() - self._unnamed7 = self._io.read_u2le() - self._unnamed8 = self._io.read_u2le() + # 0x14..0x1b: 8-byte LE blob = opaque_track_id_lo | (opaque_seed<<32). + # opaque_seed (the two u2s below) is per-export constant; the lo u4 is + # per-track and is NOT a hash of path/title/filename/ANLZ/audio. + # Believed a stored rekordbox-internal id; not reproducible from + # track metadata. See re/FORMAT_SPEC.md section 2. + self.opaque_track_id_lo = self._io.read_u4le() + self.opaque_seed_lo = self._io.read_u2le() + self.opaque_seed_hi = self._io.read_u2le() self.artwork_id = self._io.read_u4le() self.key_id = self._io.read_u4le() self.original_artist_id = self._io.read_u4le() diff --git a/maple-py/src/maple/cli.py b/maple-py/src/maple/cli.py index 58536fa..859c01b 100644 --- a/maple-py/src/maple/cli.py +++ b/maple-py/src/maple/cli.py @@ -87,7 +87,12 @@ def build(input_dir: Path, usb_root: Path, with_playlists: bool = True) -> Datab # path keyed off the track id even if we don't write the .DAT. import zlib h = zlib.crc32(file_path.encode()) & 0xFFFFFFFF - bucket = f"P{(h >> 20) & 0xFFF:03X}" + # rekordbox's USBANLZ P-bucket is 0..127 (P00..P7F) and the 8-hex id is an + # opaque master.db value -- not reproducible without its DB (FORMAT_SPEC + # §7), so maple derives both from a stable crc32(path). Mask the bucket to + # 0x7F so it has rekordbox's P00..P7F shape; the path used here is the same + # one written to the pdb row, so it's self-consistent by construction. + bucket = f"P{(h >> 20) & 0x7F:02X}" analyze_id = f"{h & 0xFFFFFFFF:08X}" analyze_path = f"/PIONEER/USBANLZ/{bucket}/{analyze_id}/ANLZ0000.DAT" write_anlz(file_path, usb_root / analyze_path.lstrip("/")) diff --git a/maple-py/src/maple/model.py b/maple-py/src/maple/model.py index 32cbaf7..7fd28b1 100644 --- a/maple-py/src/maple/model.py +++ b/maple-py/src/maple/model.py @@ -127,7 +127,7 @@ UNKNOWN_18_ROWS: list[tuple[int, int, int, int]] = [ (1, 6, 1, 0), (21, 7, 1, 0), (14, 8, 1, 0), (8, 9, 1, 0), (9, 10, 1, 0), (10, 11, 1, 0), (15, 13, 1, 0), (13, 15, 1, 0), (23, 16, 1, 0), (22, 17, 1, 0), (25, 0, 256, 0), (26, 1, 512, 0), - (2, 2, 768, 0), (3, 3, 1024, 0), (5, 4, 1282, 0), (6, 5, 1536, 0), + (2, 2, 768, 0), (3, 3, 1024, 0), (5, 4, 1280, 0), (6, 5, 1536, 0), (11, 12, 1792, 0), ] @@ -172,3 +172,40 @@ class Database: genres: list[Genre] = field(default_factory=list) colors: list[Color] = field(default_factory=list) keys: list[Key] = field(default_factory=list) + + +def validate_referential_integrity(db: "Database") -> list[str]: + """Return a list of dangling-foreign-key errors. Every FK must be 0 ("none") + or resolve to a real row in its target table. A track that references a + genre/artist/album/artwork id with no matching row is freeze-class: the + player follows the FK into an empty/short table and hangs (this is exactly + how build_acemo.py's omitted-genres bug froze the deck). Empty string = OK. + """ + errors: list[str] = [] + artist_ids = {a.id for a in db.artists} + album_ids = {a.id for a in db.albums} + artwork_ids = {a.id for a in db.artwork} + genre_ids = {g.id for g in db.genres} + key_ids = {k.id for k in db.keys} + track_ids = {t.id for t in db.tracks} + playlist_ids = {n.id for n in db.playlist_nodes} + + def check(kind: str, owner_id: int, field: str, value: int, valid: set[int]) -> None: + # 0 means "unset / no reference" everywhere in the pdb schema. + if value and value not in valid: + errors.append(f"{kind} id={owner_id}: {field}={value} has no matching row") + + for t in db.tracks: + check("track", t.id, "artist_id", t.artist_id, artist_ids) + check("track", t.id, "album_id", t.album_id, album_ids) + check("track", t.id, "artwork_id", t.artwork_id, artwork_ids) + check("track", t.id, "genre_id", t.genre_id, genre_ids) + for a in db.albums: + check("album", a.id, "artist_id", a.artist_id, artist_ids) + for e in db.playlist_entries: + check("playlist_entry", e.playlist_id, "track_id", e.track_id, track_ids) + check("playlist_entry", e.playlist_id, "playlist_id", e.playlist_id, playlist_ids) + # key_ids is currently unreferenced by Track in maple's model; included for + # completeness so adding a track.key_id later is covered. + _ = key_ids + return errors diff --git a/maple-py/src/maple/pdb/pager.py b/maple-py/src/maple/pdb/pager.py index dfb0b1d..6fd838d 100644 --- a/maple-py/src/maple/pdb/pager.py +++ b/maple-py/src/maple/pdb/pager.py @@ -15,11 +15,34 @@ def _num_row_groups(n: int) -> int: return (n + ROWS_PER_GROUP - 1) // ROWS_PER_GROUP -def build_strange_page(page_index: int, page_type: int, next_page: int) -> bytes: - """Empty placeholder page that the reference exports use as the first - page of every table chain. Reproduces the observed header flags and - marker bytes; the heap is left zero-filled (rekordbox fills it with a - `f8 ff ff 1f` pattern, but players don't care because num_rows=0). +# Strange-page heap is a DeviceSQL free-space map, byte-exact in a fresh export: +# u4(page_index) + u4(data_page_ptr) + 12-byte const tail +# + (f8 ff ff 1f)*1004 + 20 trailing zeros. +# data_page_ptr is the table's first data-page index, or the sentinel +# 0x03ffffff when the table is strange-only (no data page). The 1004 repeat +# count is the same value stored in the header's unknown6 field (0x03ec). +# Verified byte-for-byte against data/acemo_fresh strange pages. +_STRANGE_HEAP_NO_DATA = 0x03FFFFFF +_STRANGE_HEAP_TAIL_CONST = bytes.fromhex("ffffff03000000000000ff1f") +_STRANGE_HEAP_PATTERN = bytes.fromhex("f8ffff1f") +_STRANGE_HEAP_REPEAT = 1004 +_STRANGE_HEAP_TAIL = 20 + +# Strange pages on these table types carry unknown7=1; all others 0. In a fresh +# export this is set for tracks (first table) and history (last table). Other +# fields (free/used=0, txc/txi=0x1fff) match across all strange pages. +STRANGE_UNKNOWN7 = frozenset({0, 19}) + + +def build_strange_page(page_index: int, page_type: int, next_page: int, + data_page: int | None = None) -> bytes: + """Empty placeholder page that the reference exports use as the first page of + every table chain. Reproduces a FRESH rekordbox export byte-for-byte: header + flags/markers plus the DeviceSQL free-space heap map. `next_page` should be + the reserved data-page index (page_index+1) even for strange-only tables -- + rekordbox always points the strange page at its (possibly-blank) data slot. + `data_page` is the table's first data-page index, or None when the table is + strange-only (the free-map then stores the 0x03ffffff sentinel there). """ header = b"" header += pack(" bytes header += pack(" bytes: return b + b"\x00" if len(b) & 1 else b @@ -56,20 +74,24 @@ def serialize_track(t: Track) -> bytes: prefix += pack(" band, so it's deterministic and in-range. + # (Was the 0xa567/0xd6d3 marker, which matched no real export; lo=0/ + # seed=0x78f74a68 was also wrong -- that came from maple's own output + # mistaken for an "unanalyzed library", not a real rekordbox export.) + h = zlib.crc32(f"{t.id}|{t.file_path}".encode()) & 0xFFFFFFFF + opaque_lo = OPAQUE_LO_MIN + (h % (OPAQUE_LO_MAX - OPAQUE_LO_MIN)) + prefix += pack(" bytes: if near_ofs < far_threshold: subtype = 0x60 row = b"" - row += pack(" bytes: # Layout: subtype u2, index_shift u2, id u4, 0x03 u1, ofs_name_near u1, ofs_name_far u2 -> name follows at offset 0x0e far = 0x0E row = b"" - row += pack(" bytes: near_ofs = 0x16 subtype = 0x80 row = b"" - row += pack("0", i.e. no table is forced. Kept as an (empty) hook in +# case a future fresh export proves some table is always materialised. +CORE_TABLES: frozenset[int] = frozenset() + +# Row types whose layout begins `subtype:u2, index_shift:u2`, so they carry a +# slot-based index_shift the pager must patch (FORMAT_SPEC.md s1): tracks, +# artists, albums. +INDEX_SHIFT_TABLES = frozenset({0, 2, 3}) + + +SEQUENCE_OFFSET = 20 # u4 offset of `sequence` in the file header (page 0) + + +def _next_sequence(path: Path) -> int: + """rekordbox's file-header `sequence` is an internal transaction counter from + its master.db -- we can't reproduce its exact value. The faithful behaviour is + to monotonically increment: read the sequence already in the destination + export (if any) and add 1, defaulting to 1 for a brand-new file. This keeps + each export's sequence strictly greater than the last, as rekordbox does. + """ + try: + head = path.read_bytes()[:FILE_HEADER_SIZE] + except (FileNotFoundError, OSError): + return 1 + if len(head) < FILE_HEADER_SIZE: + return 1 + existing = unpack_from(" None: + # Refuse to emit a pdb with dangling foreign keys -- a track pointing at a + # genre/artist/album/artwork row that doesn't exist makes the player follow + # the FK into an empty table and freeze (freeze-class, like the deck hang). + fk_errors = validate_referential_integrity(db) + if fk_errors: + raise ValueError("dangling foreign keys (would freeze the player):\n " + "\n ".join(fk_errors)) + # Layout (matching rekordbox): page 0 is the file header. Pages 1..2*N are # reserved 2-per-table -- strange page at the odd index 1+2k, first data page # at the even index 2+2k. Overflow data pages come from the pool at 2*N+1. @@ -112,36 +156,82 @@ def write_pdb(db: Database, path: str | Path) -> None: overflow_next = 1 + 2 * num_tables # first overflow page (41) pages_by_index: dict[int, bytes] = {} # page_index -> 4096-byte page table_dir: list[tuple[int, int, int]] = [] # (page_type, first_page, last_page) + last_builders: list[PageBuilder] = [] # last data page of each chain, patched below for k, ptype in enumerate(ALL_PAGE_TYPES): strange_idx = 1 + 2 * k data_idx = 2 + 2 * k rows = rows_by_type.get(ptype, []) data_flag = 0x34 if ptype in PAGE_FLAG_34 else 0x24 + has_index_shift = ptype in INDEX_SHIFT_TABLES - # Optional table with no rows: strange page only (its even page stays blank). + # Optional table with no rows: strange page only (its even page stays + # blank). rekordbox still points the strange page at the reserved data + # index (strange_idx+1 == data_idx), and the table dir's last_page is the + # strange page itself. if not rows and ptype not in CORE_TABLES: - pages_by_index[strange_idx] = build_strange_page(strange_idx, ptype, next_page=strange_idx) + pages_by_index[strange_idx] = build_strange_page( + strange_idx, ptype, next_page=data_idx, data_page=None) table_dir.append((ptype, strange_idx, strange_idx)) continue # First data page at the reserved even index; overflow pages from the pool. - builders: list[PageBuilder] = [PageBuilder(data_idx, ptype, data_flag=data_flag)] + builders: list[PageBuilder] = [ + PageBuilder(data_idx, ptype, data_flag=data_flag, has_index_shift=has_index_shift) + ] for row in rows: if not builders[-1].try_add(row): - nb = PageBuilder(overflow_next, ptype, data_flag=data_flag) + nb = PageBuilder(overflow_next, ptype, data_flag=data_flag, + has_index_shift=has_index_shift) overflow_next += 1 if not nb.try_add(row): raise ValueError(f"row of size {len(row)} too large for an empty page") builders.append(nb) - pages_by_index[strange_idx] = build_strange_page(strange_idx, ptype, next_page=data_idx) + pages_by_index[strange_idx] = build_strange_page( + strange_idx, ptype, next_page=data_idx, data_page=data_idx) for i, b in enumerate(builders): - b.next_page = b.page_index if i == len(builders) - 1 else builders[i + 1].page_index - pages_by_index[b.page_index] = b.finalize() + # Intermediate pages chain forward to the next data page and are + # finalised now. The LAST page's next_page is patched after the loop + # to a free-pool sentinel (FREEZE FIX) -- never at itself -- so it is + # finalised there, not here. + if i != len(builders) - 1: + b.next_page = builders[i + 1].page_index + pages_by_index[b.page_index] = b.finalize() + last_builders.append(builders[-1]) table_dir.append((ptype, strange_idx, builders[-1].page_index)) - next_unused_page = max(overflow_next, 1 + 2 * num_tables) + # FREEZE FIX (#13): no data page may self-loop its next_page. In every + # genuine rekordbox export (data/MAPLE, lib_multi) the last page of each + # chain points FORWARD to a free/unallocated page; the firmware Page Manager + # walks next_page and treats an index at/after next_unused (incl. past EOF) + # as "end of chain", but an index == the current page is an infinite loop -> + # the deck froze. maple used to set last.next_page = its own index. Point + # every last page at a single dedicated free-pool sentinel just past the + # overflow region, materialise that page (zero-filled, as real exports do), + # and set next_unused beyond it. + free_sentinel = overflow_next + for b in last_builders: + b.next_page = free_sentinel + for b in last_builders: + pages_by_index[b.page_index] = b.finalize() + + # Materialise a small free pool of zero-filled pages and set next_unused PAST + # it, mirroring a real export (e.g. data/MAPLE: 71 pages, next_unused=74 -> + # ~3 free pages past the last live page). A genuine export always keeps + # allocated-but-empty pages after the data and points next_unused beyond the + # file; maple previously had next_unused == EOF with no free pool, which can + # starve the firmware's free-list/allocator walk of a valid sentinel page. + FREE_POOL_PAGES = 4 + for j in range(FREE_POOL_PAGES): + pages_by_index[free_sentinel + j] = b"\x00" * PAGE_SIZE + # File ends after the materialised free pool; next_unused points a couple + # pages PAST EOF, exactly as real exports do (data/MAPLE: file=71 pages, + # next_unused=74). The firmware tolerates a past-EOF next_unused. + file_page_count = free_sentinel + FREE_POOL_PAGES # number of pages incl. page 0 + next_unused_page = file_page_count + 2 + + sequence = _next_sequence(Path(path)) file_header = b"" file_header += pack(" None: file_header += pack(" None: out_path.parent.mkdir(parents=True, exist_ok=True) with out_path.open("wb") as f: f.write(page0) - for idx in range(1, next_unused_page): # reserved-but-unused pages written blank + # Write pages 1..file_page_count-1 (reserved-but-unused written blank). + # next_unused_page intentionally points a couple pages PAST this EOF. + for idx in range(1, file_page_count): page = pages_by_index.get(idx, blank) assert len(page) == PAGE_SIZE f.write(page) diff --git a/maple-py/uv.lock b/maple-py/uv.lock index 4fa3b8b..d7aa12a 100644 --- a/maple-py/uv.lock +++ b/maple-py/uv.lock @@ -1,31 +1,89 @@ version = 1 +revision = 3 requires-python = ">=3.14" +[[package]] +name = "bidict" +version = "0.23.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/9a/6e/026678aa5a830e07cd9498a05d3e7e650a4f56a42f267a53d22bcda1bdc9/bidict-0.23.1.tar.gz", hash = "sha256:03069d763bc387bbd20e7d49914e75fc4132a41937fa3405417e1a5a2d006d71", size = 29093, upload-time = "2024-02-18T19:09:05.748Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/99/37/e8730c3587a65eb5645d4aba2d27aae48e8003614d6aaf15dda67f702f1f/bidict-0.23.1-py3-none-any.whl", hash = "sha256:5dae8d4d79b552a71cbabc7deb25dfe8ce710b17ff41711e13010ead2abfc3e5", size = 32764, upload-time = "2024-02-18T19:09:04.156Z" }, +] + [[package]] name = "colorama" version = "0.4.6" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/d8/53/6f443c9a4a8358a93a6792e2acffb9d9d5cb0a5cfd8802644b7b1c9a02e4/colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44", size = 27697 } +sdist = { url = "https://files.pythonhosted.org/packages/d8/53/6f443c9a4a8358a93a6792e2acffb9d9d5cb0a5cfd8802644b7b1c9a02e4/colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44", size = 27697, upload-time = "2022-10-25T02:36:22.414Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/d1/d6/3965ed04c63042e047cb6a3e6ed1a63a35087b6a609aa3a15ed8ac56c221/colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6", size = 25335 }, + { url = "https://files.pythonhosted.org/packages/d1/d6/3965ed04c63042e047cb6a3e6ed1a63a35087b6a609aa3a15ed8ac56c221/colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6", size = 25335, upload-time = "2022-10-25T02:36:20.889Z" }, +] + +[[package]] +name = "construct" +version = "2.10.70" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/02/77/8c84b98eca70d245a2a956452f21d57930d22ab88cbeed9290ca630cf03f/construct-2.10.70.tar.gz", hash = "sha256:4d2472f9684731e58cc9c56c463be63baa1447d674e0d66aeb5627b22f512c29", size = 86337, upload-time = "2023-11-29T08:44:49.545Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b2/fb/08b3f4bf05da99aba8ffea52a558758def16e8516bc75ca94ff73587e7d3/construct-2.10.70-py3-none-any.whl", hash = "sha256:c80be81ef595a1a821ec69dc16099550ed22197615f4320b57cc9ce2a672cb30", size = 63020, upload-time = "2023-11-29T08:44:46.876Z" }, +] + +[[package]] +name = "greenlet" +version = "3.5.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/6d/6e/802acd792aebb2256fbbee8cacf2727faaeb6f240ac11008f09eae4414bc/greenlet-3.5.1.tar.gz", hash = "sha256:5a56aeb7d5d9cc4b3a735efb5095bd4b4f6f0e4f93e5ca876d0e2315137b7829", size = 197356, upload-time = "2026-05-20T15:05:03.917Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/8a/cb/c62454606daf5640369c94d8a9dd540599b1bfc090e2d2180cb77f4038d2/greenlet-3.5.1-cp314-cp314-macosx_11_0_universal2.whl", hash = "sha256:d8ab31c9de8651a2facdd5c5bb0011f2380dd1a7af78ce2adf4b56095294fc07", size = 285579, upload-time = "2026-05-20T13:08:56.396Z" }, + { url = "https://files.pythonhosted.org/packages/ec/71/c4270398c2eba968a6071af1dfbdcaeee6ec1c24bc8b435b8cc452700da6/greenlet-3.5.1-cp314-cp314-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5e300185139abc337ade480c327183adf42a875ac7181bfe66d7d4efea31fbea", size = 651106, upload-time = "2026-05-20T14:00:09.448Z" }, + { url = "https://files.pythonhosted.org/packages/1a/ab/71e34b78a44ec271fb5f550c17bc46d301ddc5953890d935f270b0dcdb5a/greenlet-3.5.1-cp314-cp314-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:7ffdb990dcaa0234cf9845aead5df2e3c3a8b6507d409274dd87e0d5ab05ffc2", size = 663478, upload-time = "2026-05-20T14:05:45.88Z" }, + { url = "https://files.pythonhosted.org/packages/77/96/4efd6fa5c62c85426a0c19077a586258ebc3a2a146ff2493e4312a697a22/greenlet-3.5.1-cp314-cp314-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:2f82b3597e9d83b63408affed0b48fd0f54935edac4302237b9a837be0dae33c", size = 660800, upload-time = "2026-05-20T13:14:29.129Z" }, + { url = "https://files.pythonhosted.org/packages/7a/e0/6c71401a25cac7000261304e866a2f2cc04dc74810d40e2f118aa4799495/greenlet-3.5.1-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:c0141e37414c10164e702b8fb1473304221ad98f71600850c6ef7ff4880feba0", size = 1617518, upload-time = "2026-05-20T14:02:28.662Z" }, + { url = "https://files.pythonhosted.org/packages/41/26/c5c06643e8c0af9e7bf18e16cb51d0ab7625155f0392e1c9015d66d556cd/greenlet-3.5.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:50ae25a67bea74ea41fb14b960bc532df73eb713417b2d61892dced82fe8d3bc", size = 1681593, upload-time = "2026-05-20T13:14:39.417Z" }, + { url = "https://files.pythonhosted.org/packages/8a/bd/e11a108317485075e68af9d23039619b86b28130c3b50d227d42edece64b/greenlet-3.5.1-cp314-cp314-win_amd64.whl", hash = "sha256:8a17c42330e261299766b75ac1ea32caa437a9453c8f65d16a13140db378ecd3", size = 239800, upload-time = "2026-05-20T13:09:30.128Z" }, + { url = "https://files.pythonhosted.org/packages/47/f8/8e8e8417b7bf28639a5a56356ef934d0375e1d0c70a57e04d7701e870ffe/greenlet-3.5.1-cp314-cp314-win_arm64.whl", hash = "sha256:7b5f5fae05b8ac6d176a61b60c394a8cbdc2b5b91b81793066e68745cf165e54", size = 236862, upload-time = "2026-05-20T13:09:10.498Z" }, + { url = "https://files.pythonhosted.org/packages/90/12/41bf27fde4d3605d3773ae57751eda182b8be2f5398011c041173b1d9534/greenlet-3.5.1-cp314-cp314t-macosx_11_0_universal2.whl", hash = "sha256:ea8da1e900d758d078810d4255d8c6aa572181896a31ec79d779eb79c3adc9ad", size = 293637, upload-time = "2026-05-20T13:12:35.529Z" }, + { url = "https://files.pythonhosted.org/packages/44/44/ba14b23e9757707050c2f397d305bbcae62e5d7cad122f8b6baec5ae4a1f/greenlet-3.5.1-cp314-cp314t-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:a19570c52a21420dcbc94e661994bc325c0b5b11304540fed514586da5dc8f2e", size = 650840, upload-time = "2026-05-20T14:00:11.079Z" }, + { url = "https://files.pythonhosted.org/packages/a8/37/5ddc2b686a6844f91abecef43411842426da2e1573f60b49ecf2547f4ae1/greenlet-3.5.1-cp314-cp314t-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:3d955c89b75eeca4723d7cc14135f393cd47c32e2a6cb4a8e4c6e760a26b0986", size = 656416, upload-time = "2026-05-20T14:05:47.118Z" }, + { url = "https://files.pythonhosted.org/packages/e1/f0/d17510297c35a2992712f0bf84de3779749999f7d3d63aa1f09db7c62dbe/greenlet-3.5.1-cp314-cp314t-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:de2daaaebd1a5aa88c49045b6baf9310b3263796bd88db713edf37cf53e7bb4e", size = 654397, upload-time = "2026-05-20T13:14:30.696Z" }, + { url = "https://files.pythonhosted.org/packages/37/eb/147387705bb89092645b012586e7273cb5ed3c90ef7eaf3a69173eaf0209/greenlet-3.5.1-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:3bfbd69cc349e43bf3a8ae1c85548ff0718efc887615c2db16c3833d7b0b072d", size = 1614469, upload-time = "2026-05-20T14:02:30.192Z" }, + { url = "https://files.pythonhosted.org/packages/a6/4e/37ee0da7732b7aa9896f17e15579a9df34b9fcb9dd494f0adfa749af6623/greenlet-3.5.1-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:4378720dd888136c27215a0214d32a4d37c3852765d45bc37aad0623423cfd78", size = 1675115, upload-time = "2026-05-20T13:14:40.972Z" }, + { url = "https://files.pythonhosted.org/packages/57/f3/97dfcf4a6eb5077f8a672234216fb5923eb89f2cab7081cb10b2cf75b605/greenlet-3.5.1-cp314-cp314t-win_amd64.whl", hash = "sha256:45718441607f9325d948db98cbc691276059316d0358c188c246da4e1d4d23d2", size = 245246, upload-time = "2026-05-20T13:12:22.646Z" }, + { url = "https://files.pythonhosted.org/packages/5d/73/d7f72e34b582f694f4a9b248162db7b09cc458a259ba8f0c0bfa1a34ea7d/greenlet-3.5.1-cp315-cp315-macosx_11_0_universal2.whl", hash = "sha256:2baee5ca02031757ffe8cc3d69f0cc0aec7065ce362622da74f32d3bcab1c541", size = 285575, upload-time = "2026-05-20T13:12:07.043Z" }, + { url = "https://files.pythonhosted.org/packages/df/59/fa9c6e87dc8ad27a95dabe2f29f372b733d05a8a67470f6c901ed9975655/greenlet-3.5.1-cp315-cp315-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:9b1ec3274918a81d3ea778b9e75b56b72b33f300edb6cf7f3a7fe1dae56683de", size = 656428, upload-time = "2026-05-20T14:00:12.556Z" }, + { url = "https://files.pythonhosted.org/packages/f6/f9/e753408871eaa61dfe35e619cfc67512b036fde99893685d50eea9e07146/greenlet-3.5.1-cp315-cp315-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:111e2390ffffc47d5840b01711dd7fac07d4c09283d0283e7f3264b14e284c64", size = 667064, upload-time = "2026-05-20T14:05:48.662Z" }, + { url = "https://files.pythonhosted.org/packages/96/27/5565b5b40389f1c7753003a07e21892fda8660926787036d5bc0308b8113/greenlet-3.5.1-cp315-cp315-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:e630136e905fe5ff43e86945ae41220b6d1470956a39220e708110ac48d01ea5", size = 665697, upload-time = "2026-05-20T13:14:32.943Z" }, + { url = "https://files.pythonhosted.org/packages/cf/82/e7de4178c0c2d1c9a5a3be3cc0b33e46a85b3ee4a77c071bf7ad8600e079/greenlet-3.5.1-cp315-cp315-musllinux_1_2_aarch64.whl", hash = "sha256:975eac34b44a7077ca4d421348455b94f0f518246a7f14bc6d2fdcfe5b584368", size = 1621256, upload-time = "2026-05-20T14:02:31.91Z" }, + { url = "https://files.pythonhosted.org/packages/00/10/f2dddcf7dacac17dfc68691809589adad06135eb28930429cf58a6467a2f/greenlet-3.5.1-cp315-cp315-musllinux_1_2_x86_64.whl", hash = "sha256:9ab3c3a0b2ae6198e67c898dad5215a49f9ae0d0081b3c3ec59f333e39eeca26", size = 1685956, upload-time = "2026-05-20T13:14:42.55Z" }, + { url = "https://files.pythonhosted.org/packages/22/17/4a232b32133230ada52f70e9d7f5b65b0caef8772f01849bd8d149e7e4ca/greenlet-3.5.1-cp315-cp315-win_amd64.whl", hash = "sha256:cbfc69be86e10dcfef5b1e6269d1d6926552aa89ee39e1de3353360c1b6989ab", size = 239802, upload-time = "2026-05-20T13:13:15.481Z" }, + { url = "https://files.pythonhosted.org/packages/c2/ae/4e623a7e6d4d2a5f4cb8e4c82de4169fc637942caae68d6e676b8a128ac5/greenlet-3.5.1-cp315-cp315-win_arm64.whl", hash = "sha256:92fd6d44ac5e5a887c8a5dc4a8ba0ba908527c31c12f78c6bc7dcfe8aab279f6", size = 236853, upload-time = "2026-05-20T13:15:37.301Z" }, + { url = "https://files.pythonhosted.org/packages/7a/57/816d9cff29119da3505b3d6a5e14a8af89006ac36f47f891ff293ee05af1/greenlet-3.5.1-cp315-cp315t-macosx_11_0_universal2.whl", hash = "sha256:a6fdf2433a5441ef9a95464f7c3e674775da1c8c1177fff311cee1acad4626ed", size = 293877, upload-time = "2026-05-20T13:10:19.078Z" }, + { url = "https://files.pythonhosted.org/packages/23/a1/59b0a7c7d140ff1a75626680b9a9899b79a9176cab298b394968fb023295/greenlet-3.5.1-cp315-cp315t-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:7546556f0d649f99f6a361098a55f761181bb2ea12ff150bb16d26092ad88244", size = 655333, upload-time = "2026-05-20T14:00:14.758Z" }, + { url = "https://files.pythonhosted.org/packages/72/1b/5efe127597625042218939d01855109f352779050768b670b52edcc16a6c/greenlet-3.5.1-cp315-cp315t-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:d5ee3ea898009fa898f85f9982255d35278c477bebe185beca249cab42d4526c", size = 659443, upload-time = "2026-05-20T14:05:50.159Z" }, + { url = "https://files.pythonhosted.org/packages/6c/6d/c404246ea4d22d097a7426d0efb5b781bd7eb67715f09e79001bd552ab18/greenlet-3.5.1-cp315-cp315t-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a5c81f74d204d3edd136ebfd50dce53acbb776995d721a0fe801626cfc93b8cd", size = 658356, upload-time = "2026-05-20T13:14:35.091Z" }, + { url = "https://files.pythonhosted.org/packages/51/02/f8ee37fb6d2219329f350af241c27fcf12df57e723d11f6fc6d3bacdadaa/greenlet-3.5.1-cp315-cp315t-musllinux_1_2_aarch64.whl", hash = "sha256:2c18ef16bf6d4dd410e4dd52996888ea1497be26892fe5bbc73580aba4287b8e", size = 1619216, upload-time = "2026-05-20T14:02:33.403Z" }, + { url = "https://files.pythonhosted.org/packages/93/c5/3dc9475ace2c7a3680da12372cddd7f1ac874eb410a1ac48d3e9dab83782/greenlet-3.5.1-cp315-cp315t-musllinux_1_2_x86_64.whl", hash = "sha256:17d86354f0ae6b61bf9be5148d0dd34e06c3cb7c602c671f79f29ac3b150e659", size = 1678427, upload-time = "2026-05-20T13:14:43.71Z" }, + { url = "https://files.pythonhosted.org/packages/df/4e/750c15c317a41ffb36f0bf40b933e3d744a7dede61889f74443ea69690cf/greenlet-3.5.1-cp315-cp315t-win_amd64.whl", hash = "sha256:e7516cf6ae6b8a582c2770a0caed47b8a48373ed732c33d69a72913ae6ac923e", size = 245225, upload-time = "2026-05-20T13:13:59.366Z" }, + { url = "https://files.pythonhosted.org/packages/4f/fd/d3baea2eeb7b617efd47e87ca06e2ec2c6118d303aa9e918e0ce16eadc10/greenlet-3.5.1-cp315-cp315t-win_arm64.whl", hash = "sha256:5028648bf2253ec4745add746129d3904121fa7fe871a76bed23c5720573ce0a", size = 239590, upload-time = "2026-05-20T13:13:37.382Z" }, ] [[package]] name = "iniconfig" version = "2.3.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/72/34/14ca021ce8e5dfedc35312d08ba8bf51fdd999c576889fc2c24cb97f4f10/iniconfig-2.3.0.tar.gz", hash = "sha256:c76315c77db068650d49c5b56314774a7804df16fee4402c1f19d6d15d8c4730", size = 20503 } +sdist = { url = "https://files.pythonhosted.org/packages/72/34/14ca021ce8e5dfedc35312d08ba8bf51fdd999c576889fc2c24cb97f4f10/iniconfig-2.3.0.tar.gz", hash = "sha256:c76315c77db068650d49c5b56314774a7804df16fee4402c1f19d6d15d8c4730", size = 20503, upload-time = "2025-10-18T21:55:43.219Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/cb/b1/3846dd7f199d53cb17f49cba7e651e9ce294d8497c8c150530ed11865bb8/iniconfig-2.3.0-py3-none-any.whl", hash = "sha256:f631c04d2c48c52b84d0d0549c99ff3859c98df65b3101406327ecc7d53fbf12", size = 7484 }, + { url = "https://files.pythonhosted.org/packages/cb/b1/3846dd7f199d53cb17f49cba7e651e9ce294d8497c8c150530ed11865bb8/iniconfig-2.3.0-py3-none-any.whl", hash = "sha256:f631c04d2c48c52b84d0d0549c99ff3859c98df65b3101406327ecc7d53fbf12", size = 7484, upload-time = "2025-10-18T21:55:41.639Z" }, ] [[package]] name = "kaitaistruct" version = "0.11" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/27/b8/ca7319556912f68832daa4b81425314857ec08dfccd8dbc8c0f65c992108/kaitaistruct-0.11.tar.gz", hash = "sha256:053ee764288e78b8e53acf748e9733268acbd579b8d82a427b1805453625d74b", size = 11519 } +sdist = { url = "https://files.pythonhosted.org/packages/27/b8/ca7319556912f68832daa4b81425314857ec08dfccd8dbc8c0f65c992108/kaitaistruct-0.11.tar.gz", hash = "sha256:053ee764288e78b8e53acf748e9733268acbd579b8d82a427b1805453625d74b", size = 11519, upload-time = "2025-09-08T15:46:25.037Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/4a/4a/cf14bf3b1f5ffb13c69cf5f0ea78031247790558ee88984a8bdd22fae60d/kaitaistruct-0.11-py2.py3-none-any.whl", hash = "sha256:5c6ce79177b4e193a577ecd359e26516d1d6d000a0bffd6e1010f2a46a62a561", size = 11372 }, + { url = "https://files.pythonhosted.org/packages/4a/4a/cf14bf3b1f5ffb13c69cf5f0ea78031247790558ee88984a8bdd22fae60d/kaitaistruct-0.11-py2.py3-none-any.whl", hash = "sha256:5c6ce79177b4e193a577ecd359e26516d1d6d000a0bffd6e1010f2a46a62a561", size = 11372, upload-time = "2025-09-08T15:46:23.635Z" }, ] [[package]] @@ -36,6 +94,7 @@ dependencies = [ { name = "kaitaistruct" }, { name = "mutagen" }, { name = "pillow" }, + { name = "pyrekordbox" }, ] [package.dev-dependencies] @@ -48,6 +107,7 @@ requires-dist = [ { name = "kaitaistruct", specifier = ">=0.11" }, { name = "mutagen", specifier = ">=1.47.0" }, { name = "pillow", specifier = ">=12.2.0" }, + { name = "pyrekordbox", specifier = ">=0.4.4" }, ] [package.metadata.requires-dev] @@ -57,69 +117,139 @@ dev = [{ name = "pytest", specifier = ">=9.0.3" }] name = "mutagen" version = "1.47.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/81/e6/64bc71b74eef4b68e61eb921dcf72dabd9e4ec4af1e11891bbd312ccbb77/mutagen-1.47.0.tar.gz", hash = "sha256:719fadef0a978c31b4cf3c956261b3c58b6948b32023078a2117b1de09f0fc99", size = 1274186 } +sdist = { url = "https://files.pythonhosted.org/packages/81/e6/64bc71b74eef4b68e61eb921dcf72dabd9e4ec4af1e11891bbd312ccbb77/mutagen-1.47.0.tar.gz", hash = "sha256:719fadef0a978c31b4cf3c956261b3c58b6948b32023078a2117b1de09f0fc99", size = 1274186, upload-time = "2023-09-03T16:33:33.411Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/b0/7a/620f945b96be1f6ee357d211d5bf74ab1b7fe72a9f1525aafbfe3aee6875/mutagen-1.47.0-py3-none-any.whl", hash = "sha256:edd96f50c5907a9539d8e5bba7245f62c9f520aef333d13392a79a4f70aca719", size = 194391 }, + { url = "https://files.pythonhosted.org/packages/b0/7a/620f945b96be1f6ee357d211d5bf74ab1b7fe72a9f1525aafbfe3aee6875/mutagen-1.47.0-py3-none-any.whl", hash = "sha256:edd96f50c5907a9539d8e5bba7245f62c9f520aef333d13392a79a4f70aca719", size = 194391, upload-time = "2023-09-03T16:33:29.955Z" }, +] + +[[package]] +name = "numpy" +version = "2.4.6" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/d0/ad/fed0499ce6a338d2a03ebae59cd15093910c8875328855781952abf6c2fe/numpy-2.4.6.tar.gz", hash = "sha256:f3a3570c4a2a16746ac2c31a7c7c7b0c186b95ce902e33db6f28094ed7387dda", size = 20735807, upload-time = "2026-05-18T23:37:14.07Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/f8/91/3ab2044d05fd16d343c5ac2e69b127f1b2854040dd20b193257c78028bd3/numpy-2.4.6-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:06ca2f61ec4385a07a6977c55ba998a4466c123642b4a32694d3128fce18c079", size = 16683458, upload-time = "2026-05-18T23:35:38.353Z" }, + { url = "https://files.pythonhosted.org/packages/8e/62/764ce66fa4147ae6d73071a3abf804ffe606f174618697c571acdf26a7c9/numpy-2.4.6-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:38efbc8de75c7a0fc1ac190162d892787f3f47b57cc291231aafee36b80982b7", size = 14704559, upload-time = "2026-05-18T23:35:42.14Z" }, + { url = "https://files.pythonhosted.org/packages/60/61/23f27c172f022e04025b7dc2367f4d63c1a398120607ec896228649a6f48/numpy-2.4.6-cp314-cp314-macosx_14_0_arm64.whl", hash = "sha256:d581b735e177fdcdce6fed8e7e8880a3fb6ee4e3653a3ac6af01c6f4c03effc5", size = 5209716, upload-time = "2026-05-18T23:35:45.377Z" }, + { url = "https://files.pythonhosted.org/packages/03/71/21cf70dc6ea3e3acb95fc53a265b2fc248b981f0194ceb5b475271b8809d/numpy-2.4.6-cp314-cp314-macosx_14_0_x86_64.whl", hash = "sha256:0a041d3d761dc3c35cc56ce0351506a02bcbc25f7b169f652435141a17db9096", size = 6543947, upload-time = "2026-05-18T23:35:47.926Z" }, + { url = "https://files.pythonhosted.org/packages/d5/91/64288395ee1799bd2e0b04a305dce9666da90c961e1f3fe982a05ee1c036/numpy-2.4.6-cp314-cp314-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:40fdc1ae7125e518ea98e53e69a4ebc27e1fd50510c47b7ea130cf21e5e1d42b", size = 15685197, upload-time = "2026-05-18T23:35:50.863Z" }, + { url = "https://files.pythonhosted.org/packages/f3/eb/ebffaa97dc55502df69584a8f0dcf07f69a3e0b3e2323670a2722db9aa39/numpy-2.4.6-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a2c306dea656c12c68f51f4cea133cbe78ca7435eb28c735eac1d3ebe73be6e8", size = 16638245, upload-time = "2026-05-18T23:35:54.752Z" }, + { url = "https://files.pythonhosted.org/packages/b8/0b/54f9da33128d7e350fab89c7455902eeae70349ee52bddb448dc4a576f45/numpy-2.4.6-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:33111801a01c12a8a1e3721f0a9232f8cfc8ae2c6b7098167e6f623c6073f402", size = 17036587, upload-time = "2026-05-18T23:35:58.355Z" }, + { url = "https://files.pythonhosted.org/packages/b6/f0/fdebc1052db1cc37c64beb22072d67cd6d1c71adca1299f53dec2b5e20d3/numpy-2.4.6-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:ae506e6902902557576a26ff33eda8695e7ecb3cb36c3b573a0765dee114ebdb", size = 18363226, upload-time = "2026-05-18T23:36:02.845Z" }, + { url = "https://files.pythonhosted.org/packages/aa/b4/298628d98c72b57e57f7165ae6a481a1deaf6f3c28262a6e4c739c275930/numpy-2.4.6-cp314-cp314-win32.whl", hash = "sha256:aaf159caa35993cb1f56fb9b8e4610d35758e7ca005412eb1daa856a78c9c4b1", size = 6010196, upload-time = "2026-05-18T23:36:05.92Z" }, + { url = "https://files.pythonhosted.org/packages/df/ac/46de6dda46478f7942f839e094970be2d4a861e005c4b3bf07c92e291a09/numpy-2.4.6-cp314-cp314-win_amd64.whl", hash = "sha256:b507f5c4c1d508876d1819b6bf9a49d365b96320b5d4993426b33a23ca4b8261", size = 12450334, upload-time = "2026-05-18T23:36:09.107Z" }, + { url = "https://files.pythonhosted.org/packages/78/92/b8b798ac784102c0da830d2257d59358e3d3d90d1e2b3f2575dad976c5cf/numpy-2.4.6-cp314-cp314-win_arm64.whl", hash = "sha256:6f41ae150c4e32db4f3310cdaf64b1593a03dbabe29eec77fc9b50fe64061df6", size = 10495678, upload-time = "2026-05-18T23:36:12.766Z" }, + { url = "https://files.pythonhosted.org/packages/30/34/ec28d1aa8115971537c01469ab2011ee96827930f0a124de1000cc2a7ed7/numpy-2.4.6-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:ece3d2cfe132e7d51f44a832b303895e6f2d499c5e74dfbdb06ee246147a304a", size = 14823672, upload-time = "2026-05-18T23:36:16.473Z" }, + { url = "https://files.pythonhosted.org/packages/16/bd/f6d1fede4e54e8042a7ff97bb495510f3c220f94bcd9e8b228e87c92cc0d/numpy-2.4.6-cp314-cp314t-macosx_14_0_arm64.whl", hash = "sha256:e3e5193ef5a3dc73bceee50f7fdc2c90dbb76c42df8d8fae3d1067a583df579e", size = 5328731, upload-time = "2026-05-18T23:36:19.767Z" }, + { url = "https://files.pythonhosted.org/packages/f4/f0/e105b9e2fd728a9910103884decd6951d9dd73896b914a98d9a231de02ee/numpy-2.4.6-cp314-cp314t-macosx_14_0_x86_64.whl", hash = "sha256:17f9ade344e7d9b464a084d69bcf18fc691cb1db67c62ed80820bf4926d78f0e", size = 6649805, upload-time = "2026-05-18T23:36:22.266Z" }, + { url = "https://files.pythonhosted.org/packages/82/dd/1206a7ca6ab15e3f02069707ca96222e202af681bb73756da7527f3cb837/numpy-2.4.6-cp314-cp314t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:9cd5ffd25db4e7ba6a375693b3fc0fc1791ec636c17db3720da19bde7180ec43", size = 15730496, upload-time = "2026-05-18T23:36:25.713Z" }, + { url = "https://files.pythonhosted.org/packages/51/e7/38d3ea825dcab85a591734decb2f6c67caa7c8367d374df1a1c3842f9b07/numpy-2.4.6-cp314-cp314t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:7d92c3819208a60205a12a245c91ad70cb0a85336659b19b834205573ac8456e", size = 16679616, upload-time = "2026-05-18T23:36:29.652Z" }, + { url = "https://files.pythonhosted.org/packages/93/b7/caabfdf53edf663e0b4eb74d7d405d83baef09eb5e83bcd32d601d72b93e/numpy-2.4.6-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:e85b752a1e912b70eaad4fafbd4d1238007ab221de2009b9a2f5ae7461239895", size = 17085145, upload-time = "2026-05-18T23:36:33.449Z" }, + { url = "https://files.pythonhosted.org/packages/f9/45/68d7c33a6bcf3e5aa3bdbd57a367e6f615286dfd6482f97e8ffeb734306e/numpy-2.4.6-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:29cb7f67d10b479ff07c17d33e39f78c07f71c40ef30d63c153d340e96cd3fb4", size = 18403813, upload-time = "2026-05-18T23:36:37.369Z" }, + { url = "https://files.pythonhosted.org/packages/9c/50/0753655aa844c99cd9e018aacf76f130f1bd81d881bb74bc0aef5d73a8ba/numpy-2.4.6-cp314-cp314t-win32.whl", hash = "sha256:260a5d70215b61ab4fadf5c7baacd64821842975eea312125ed3c39a6391b063", size = 6156982, upload-time = "2026-05-18T23:36:40.817Z" }, + { url = "https://files.pythonhosted.org/packages/b2/d4/7c67becf668f973cb490cec3e98dfd799d866f9c989a54d355672cfa0db6/numpy-2.4.6-cp314-cp314t-win_amd64.whl", hash = "sha256:81a1cca95ed5bb92aa8b10dd2cdc9a0d3853a50fad926c28b5d7e8ea54389627", size = 12638908, upload-time = "2026-05-18T23:36:43.996Z" }, + { url = "https://files.pythonhosted.org/packages/43/bb/e1c71a4295b1b1d1393d50dbb4f2a36283c6859d9d3892e84f00ec5a91d5/numpy-2.4.6-cp314-cp314t-win_arm64.whl", hash = "sha256:0c9136e14ed34a9e343a31c533d78a9813a69a3148332bce5e9821cb2f996e66", size = 10565867, upload-time = "2026-05-18T23:36:47.114Z" }, ] [[package]] name = "packaging" version = "26.2" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/d7/f1/e7a6dd94a8d4a5626c03e4e99c87f241ba9e350cd9e6d75123f992427270/packaging-26.2.tar.gz", hash = "sha256:ff452ff5a3e828ce110190feff1178bb1f2ea2281fa2075aadb987c2fb221661", size = 228134 } +sdist = { url = "https://files.pythonhosted.org/packages/d7/f1/e7a6dd94a8d4a5626c03e4e99c87f241ba9e350cd9e6d75123f992427270/packaging-26.2.tar.gz", hash = "sha256:ff452ff5a3e828ce110190feff1178bb1f2ea2281fa2075aadb987c2fb221661", size = 228134, upload-time = "2026-04-24T20:15:23.917Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/df/b2/87e62e8c3e2f4b32e5fe99e0b86d576da1312593b39f47d8ceef365e95ed/packaging-26.2-py3-none-any.whl", hash = "sha256:5fc45236b9446107ff2415ce77c807cee2862cb6fac22b8a73826d0693b0980e", size = 100195 }, + { url = "https://files.pythonhosted.org/packages/df/b2/87e62e8c3e2f4b32e5fe99e0b86d576da1312593b39f47d8ceef365e95ed/packaging-26.2-py3-none-any.whl", hash = "sha256:5fc45236b9446107ff2415ce77c807cee2862cb6fac22b8a73826d0693b0980e", size = 100195, upload-time = "2026-04-24T20:15:22.081Z" }, ] [[package]] name = "pillow" version = "12.2.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/8c/21/c2bcdd5906101a30244eaffc1b6e6ce71a31bd0742a01eb89e660ebfac2d/pillow-12.2.0.tar.gz", hash = "sha256:a830b1a40919539d07806aa58e1b114df53ddd43213d9c8b75847eee6c0182b5", size = 46987819 } +sdist = { url = "https://files.pythonhosted.org/packages/8c/21/c2bcdd5906101a30244eaffc1b6e6ce71a31bd0742a01eb89e660ebfac2d/pillow-12.2.0.tar.gz", hash = "sha256:a830b1a40919539d07806aa58e1b114df53ddd43213d9c8b75847eee6c0182b5", size = 46987819, upload-time = "2026-04-01T14:46:17.687Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/bf/98/4595daa2365416a86cb0d495248a393dfc84e96d62ad080c8546256cb9c0/pillow-12.2.0-cp314-cp314-ios_13_0_arm64_iphoneos.whl", hash = "sha256:3adc9215e8be0448ed6e814966ecf3d9952f0ea40eb14e89a102b87f450660d8", size = 4100848 }, - { url = "https://files.pythonhosted.org/packages/0b/79/40184d464cf89f6663e18dfcf7ca21aae2491fff1a16127681bf1fa9b8cf/pillow-12.2.0-cp314-cp314-ios_13_0_arm64_iphonesimulator.whl", hash = "sha256:6a9adfc6d24b10f89588096364cc726174118c62130c817c2837c60cf08a392b", size = 4176515 }, - { url = "https://files.pythonhosted.org/packages/b0/63/703f86fd4c422a9cf722833670f4f71418fb116b2853ff7da722ea43f184/pillow-12.2.0-cp314-cp314-ios_13_0_x86_64_iphonesimulator.whl", hash = "sha256:6a6e67ea2e6feda684ed370f9a1c52e7a243631c025ba42149a2cc5934dec295", size = 3640159 }, - { url = "https://files.pythonhosted.org/packages/71/e0/fb22f797187d0be2270f83500aab851536101b254bfa1eae10795709d283/pillow-12.2.0-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:2bb4a8d594eacdfc59d9e5ad972aa8afdd48d584ffd5f13a937a664c3e7db0ed", size = 5312185 }, - { url = "https://files.pythonhosted.org/packages/ba/8c/1a9e46228571de18f8e28f16fabdfc20212a5d019f3e3303452b3f0a580d/pillow-12.2.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:80b2da48193b2f33ed0c32c38140f9d3186583ce7d516526d462645fd98660ae", size = 4695386 }, - { url = "https://files.pythonhosted.org/packages/70/62/98f6b7f0c88b9addd0e87c217ded307b36be024d4ff8869a812b241d1345/pillow-12.2.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:22db17c68434de69d8ecfc2fe821569195c0c373b25cccb9cbdacf2c6e53c601", size = 6280384 }, - { url = "https://files.pythonhosted.org/packages/5e/03/688747d2e91cfbe0e64f316cd2e8005698f76ada3130d0194664174fa5de/pillow-12.2.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:7b14cc0106cd9aecda615dd6903840a058b4700fcb817687d0ee4fc8b6e389be", size = 8091599 }, - { url = "https://files.pythonhosted.org/packages/f6/35/577e22b936fcdd66537329b33af0b4ccfefaeabd8aec04b266528cddb33c/pillow-12.2.0-cp314-cp314-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:8cbeb542b2ebc6fcdacabf8aca8c1a97c9b3ad3927d46b8723f9d4f033288a0f", size = 6396021 }, - { url = "https://files.pythonhosted.org/packages/11/8d/d2532ad2a603ca2b93ad9f5135732124e57811d0168155852f37fbce2458/pillow-12.2.0-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:4bfd07bc812fbd20395212969e41931001fd59eb55a60658b0e5710872e95286", size = 7083360 }, - { url = "https://files.pythonhosted.org/packages/5e/26/d325f9f56c7e039034897e7380e9cc202b1e368bfd04d4cbe6a441f02885/pillow-12.2.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:9aba9a17b623ef750a4d11b742cbafffeb48a869821252b30ee21b5e91392c50", size = 6507628 }, - { url = "https://files.pythonhosted.org/packages/5f/f7/769d5632ffb0988f1c5e7660b3e731e30f7f8ec4318e94d0a5d674eb65a4/pillow-12.2.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:deede7c263feb25dba4e82ea23058a235dcc2fe1f6021025dc71f2b618e26104", size = 7209321 }, - { url = "https://files.pythonhosted.org/packages/6a/7a/c253e3c645cd47f1aceea6a8bacdba9991bf45bb7dfe927f7c893e89c93c/pillow-12.2.0-cp314-cp314-win32.whl", hash = "sha256:632ff19b2778e43162304d50da0181ce24ac5bb8180122cbe1bf4673428328c7", size = 6479723 }, - { url = "https://files.pythonhosted.org/packages/cd/8b/601e6566b957ca50e28725cb6c355c59c2c8609751efbecd980db44e0349/pillow-12.2.0-cp314-cp314-win_amd64.whl", hash = "sha256:4e6c62e9d237e9b65fac06857d511e90d8461a32adcc1b9065ea0c0fa3a28150", size = 7217400 }, - { url = "https://files.pythonhosted.org/packages/d6/94/220e46c73065c3e2951bb91c11a1fb636c8c9ad427ac3ce7d7f3359b9b2f/pillow-12.2.0-cp314-cp314-win_arm64.whl", hash = "sha256:b1c1fbd8a5a1af3412a0810d060a78b5136ec0836c8a4ef9aa11807f2a22f4e1", size = 2554835 }, - { url = "https://files.pythonhosted.org/packages/b6/ab/1b426a3974cb0e7da5c29ccff4807871d48110933a57207b5a676cccc155/pillow-12.2.0-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:57850958fe9c751670e49b2cecf6294acc99e562531f4bd317fa5ddee2068463", size = 5314225 }, - { url = "https://files.pythonhosted.org/packages/19/1e/dce46f371be2438eecfee2a1960ee2a243bbe5e961890146d2dee1ff0f12/pillow-12.2.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:d5d38f1411c0ed9f97bcb49b7bd59b6b7c314e0e27420e34d99d844b9ce3b6f3", size = 4698541 }, - { url = "https://files.pythonhosted.org/packages/55/c3/7fbecf70adb3a0c33b77a300dc52e424dc22ad8cdc06557a2e49523b703d/pillow-12.2.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:5c0a9f29ca8e79f09de89293f82fc9b0270bb4af1d58bc98f540cc4aedf03166", size = 6322251 }, - { url = "https://files.pythonhosted.org/packages/1c/3c/7fbc17cfb7e4fe0ef1642e0abc17fc6c94c9f7a16be41498e12e2ba60408/pillow-12.2.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:1610dd6c61621ae1cf811bef44d77e149ce3f7b95afe66a4512f8c59f25d9ebe", size = 8127807 }, - { url = "https://files.pythonhosted.org/packages/ff/c3/a8ae14d6defd2e448493ff512fae903b1e9bd40b72efb6ec55ce0048c8ce/pillow-12.2.0-cp314-cp314t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0a34329707af4f73cf1782a36cd2289c0368880654a2c11f027bcee9052d35dd", size = 6433935 }, - { url = "https://files.pythonhosted.org/packages/6e/32/2880fb3a074847ac159d8f902cb43278a61e85f681661e7419e6596803ed/pillow-12.2.0-cp314-cp314t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:8e9c4f5b3c546fa3458a29ab22646c1c6c787ea8f5ef51300e5a60300736905e", size = 7116720 }, - { url = "https://files.pythonhosted.org/packages/46/87/495cc9c30e0129501643f24d320076f4cc54f718341df18cc70ec94c44e1/pillow-12.2.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:fb043ee2f06b41473269765c2feae53fc2e2fbf96e5e22ca94fb5ad677856f06", size = 6540498 }, - { url = "https://files.pythonhosted.org/packages/18/53/773f5edca692009d883a72211b60fdaf8871cbef075eaa9d577f0a2f989e/pillow-12.2.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:f278f034eb75b4e8a13a54a876cc4a5ab39173d2cdd93a638e1b467fc545ac43", size = 7239413 }, - { url = "https://files.pythonhosted.org/packages/c9/e4/4b64a97d71b2a83158134abbb2f5bd3f8a2ea691361282f010998f339ec7/pillow-12.2.0-cp314-cp314t-win32.whl", hash = "sha256:6bb77b2dcb06b20f9f4b4a8454caa581cd4dd0643a08bacf821216a16d9c8354", size = 6482084 }, - { url = "https://files.pythonhosted.org/packages/ba/13/306d275efd3a3453f72114b7431c877d10b1154014c1ebbedd067770d629/pillow-12.2.0-cp314-cp314t-win_amd64.whl", hash = "sha256:6562ace0d3fb5f20ed7290f1f929cae41b25ae29528f2af1722966a0a02e2aa1", size = 7225152 }, - { url = "https://files.pythonhosted.org/packages/ff/6e/cf826fae916b8658848d7b9f38d88da6396895c676e8086fc0988073aaf8/pillow-12.2.0-cp314-cp314t-win_arm64.whl", hash = "sha256:aa88ccfe4e32d362816319ed727a004423aab09c5cea43c01a4b435643fa34eb", size = 2556579 }, + { url = "https://files.pythonhosted.org/packages/bf/98/4595daa2365416a86cb0d495248a393dfc84e96d62ad080c8546256cb9c0/pillow-12.2.0-cp314-cp314-ios_13_0_arm64_iphoneos.whl", hash = "sha256:3adc9215e8be0448ed6e814966ecf3d9952f0ea40eb14e89a102b87f450660d8", size = 4100848, upload-time = "2026-04-01T14:44:48.48Z" }, + { url = "https://files.pythonhosted.org/packages/0b/79/40184d464cf89f6663e18dfcf7ca21aae2491fff1a16127681bf1fa9b8cf/pillow-12.2.0-cp314-cp314-ios_13_0_arm64_iphonesimulator.whl", hash = "sha256:6a9adfc6d24b10f89588096364cc726174118c62130c817c2837c60cf08a392b", size = 4176515, upload-time = "2026-04-01T14:44:51.353Z" }, + { url = "https://files.pythonhosted.org/packages/b0/63/703f86fd4c422a9cf722833670f4f71418fb116b2853ff7da722ea43f184/pillow-12.2.0-cp314-cp314-ios_13_0_x86_64_iphonesimulator.whl", hash = "sha256:6a6e67ea2e6feda684ed370f9a1c52e7a243631c025ba42149a2cc5934dec295", size = 3640159, upload-time = "2026-04-01T14:44:53.588Z" }, + { url = "https://files.pythonhosted.org/packages/71/e0/fb22f797187d0be2270f83500aab851536101b254bfa1eae10795709d283/pillow-12.2.0-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:2bb4a8d594eacdfc59d9e5ad972aa8afdd48d584ffd5f13a937a664c3e7db0ed", size = 5312185, upload-time = "2026-04-01T14:44:56.039Z" }, + { url = "https://files.pythonhosted.org/packages/ba/8c/1a9e46228571de18f8e28f16fabdfc20212a5d019f3e3303452b3f0a580d/pillow-12.2.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:80b2da48193b2f33ed0c32c38140f9d3186583ce7d516526d462645fd98660ae", size = 4695386, upload-time = "2026-04-01T14:44:58.663Z" }, + { url = "https://files.pythonhosted.org/packages/70/62/98f6b7f0c88b9addd0e87c217ded307b36be024d4ff8869a812b241d1345/pillow-12.2.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:22db17c68434de69d8ecfc2fe821569195c0c373b25cccb9cbdacf2c6e53c601", size = 6280384, upload-time = "2026-04-01T14:45:01.5Z" }, + { url = "https://files.pythonhosted.org/packages/5e/03/688747d2e91cfbe0e64f316cd2e8005698f76ada3130d0194664174fa5de/pillow-12.2.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:7b14cc0106cd9aecda615dd6903840a058b4700fcb817687d0ee4fc8b6e389be", size = 8091599, upload-time = "2026-04-01T14:45:04.5Z" }, + { url = "https://files.pythonhosted.org/packages/f6/35/577e22b936fcdd66537329b33af0b4ccfefaeabd8aec04b266528cddb33c/pillow-12.2.0-cp314-cp314-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:8cbeb542b2ebc6fcdacabf8aca8c1a97c9b3ad3927d46b8723f9d4f033288a0f", size = 6396021, upload-time = "2026-04-01T14:45:07.117Z" }, + { url = "https://files.pythonhosted.org/packages/11/8d/d2532ad2a603ca2b93ad9f5135732124e57811d0168155852f37fbce2458/pillow-12.2.0-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:4bfd07bc812fbd20395212969e41931001fd59eb55a60658b0e5710872e95286", size = 7083360, upload-time = "2026-04-01T14:45:09.763Z" }, + { url = "https://files.pythonhosted.org/packages/5e/26/d325f9f56c7e039034897e7380e9cc202b1e368bfd04d4cbe6a441f02885/pillow-12.2.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:9aba9a17b623ef750a4d11b742cbafffeb48a869821252b30ee21b5e91392c50", size = 6507628, upload-time = "2026-04-01T14:45:12.378Z" }, + { url = "https://files.pythonhosted.org/packages/5f/f7/769d5632ffb0988f1c5e7660b3e731e30f7f8ec4318e94d0a5d674eb65a4/pillow-12.2.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:deede7c263feb25dba4e82ea23058a235dcc2fe1f6021025dc71f2b618e26104", size = 7209321, upload-time = "2026-04-01T14:45:15.122Z" }, + { url = "https://files.pythonhosted.org/packages/6a/7a/c253e3c645cd47f1aceea6a8bacdba9991bf45bb7dfe927f7c893e89c93c/pillow-12.2.0-cp314-cp314-win32.whl", hash = "sha256:632ff19b2778e43162304d50da0181ce24ac5bb8180122cbe1bf4673428328c7", size = 6479723, upload-time = "2026-04-01T14:45:17.797Z" }, + { url = "https://files.pythonhosted.org/packages/cd/8b/601e6566b957ca50e28725cb6c355c59c2c8609751efbecd980db44e0349/pillow-12.2.0-cp314-cp314-win_amd64.whl", hash = "sha256:4e6c62e9d237e9b65fac06857d511e90d8461a32adcc1b9065ea0c0fa3a28150", size = 7217400, upload-time = "2026-04-01T14:45:20.529Z" }, + { url = "https://files.pythonhosted.org/packages/d6/94/220e46c73065c3e2951bb91c11a1fb636c8c9ad427ac3ce7d7f3359b9b2f/pillow-12.2.0-cp314-cp314-win_arm64.whl", hash = "sha256:b1c1fbd8a5a1af3412a0810d060a78b5136ec0836c8a4ef9aa11807f2a22f4e1", size = 2554835, upload-time = "2026-04-01T14:45:23.162Z" }, + { url = "https://files.pythonhosted.org/packages/b6/ab/1b426a3974cb0e7da5c29ccff4807871d48110933a57207b5a676cccc155/pillow-12.2.0-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:57850958fe9c751670e49b2cecf6294acc99e562531f4bd317fa5ddee2068463", size = 5314225, upload-time = "2026-04-01T14:45:25.637Z" }, + { url = "https://files.pythonhosted.org/packages/19/1e/dce46f371be2438eecfee2a1960ee2a243bbe5e961890146d2dee1ff0f12/pillow-12.2.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:d5d38f1411c0ed9f97bcb49b7bd59b6b7c314e0e27420e34d99d844b9ce3b6f3", size = 4698541, upload-time = "2026-04-01T14:45:28.355Z" }, + { url = "https://files.pythonhosted.org/packages/55/c3/7fbecf70adb3a0c33b77a300dc52e424dc22ad8cdc06557a2e49523b703d/pillow-12.2.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:5c0a9f29ca8e79f09de89293f82fc9b0270bb4af1d58bc98f540cc4aedf03166", size = 6322251, upload-time = "2026-04-01T14:45:30.924Z" }, + { url = "https://files.pythonhosted.org/packages/1c/3c/7fbc17cfb7e4fe0ef1642e0abc17fc6c94c9f7a16be41498e12e2ba60408/pillow-12.2.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:1610dd6c61621ae1cf811bef44d77e149ce3f7b95afe66a4512f8c59f25d9ebe", size = 8127807, upload-time = "2026-04-01T14:45:33.908Z" }, + { url = "https://files.pythonhosted.org/packages/ff/c3/a8ae14d6defd2e448493ff512fae903b1e9bd40b72efb6ec55ce0048c8ce/pillow-12.2.0-cp314-cp314t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0a34329707af4f73cf1782a36cd2289c0368880654a2c11f027bcee9052d35dd", size = 6433935, upload-time = "2026-04-01T14:45:36.623Z" }, + { url = "https://files.pythonhosted.org/packages/6e/32/2880fb3a074847ac159d8f902cb43278a61e85f681661e7419e6596803ed/pillow-12.2.0-cp314-cp314t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:8e9c4f5b3c546fa3458a29ab22646c1c6c787ea8f5ef51300e5a60300736905e", size = 7116720, upload-time = "2026-04-01T14:45:39.258Z" }, + { url = "https://files.pythonhosted.org/packages/46/87/495cc9c30e0129501643f24d320076f4cc54f718341df18cc70ec94c44e1/pillow-12.2.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:fb043ee2f06b41473269765c2feae53fc2e2fbf96e5e22ca94fb5ad677856f06", size = 6540498, upload-time = "2026-04-01T14:45:41.879Z" }, + { url = "https://files.pythonhosted.org/packages/18/53/773f5edca692009d883a72211b60fdaf8871cbef075eaa9d577f0a2f989e/pillow-12.2.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:f278f034eb75b4e8a13a54a876cc4a5ab39173d2cdd93a638e1b467fc545ac43", size = 7239413, upload-time = "2026-04-01T14:45:44.705Z" }, + { url = "https://files.pythonhosted.org/packages/c9/e4/4b64a97d71b2a83158134abbb2f5bd3f8a2ea691361282f010998f339ec7/pillow-12.2.0-cp314-cp314t-win32.whl", hash = "sha256:6bb77b2dcb06b20f9f4b4a8454caa581cd4dd0643a08bacf821216a16d9c8354", size = 6482084, upload-time = "2026-04-01T14:45:47.568Z" }, + { url = "https://files.pythonhosted.org/packages/ba/13/306d275efd3a3453f72114b7431c877d10b1154014c1ebbedd067770d629/pillow-12.2.0-cp314-cp314t-win_amd64.whl", hash = "sha256:6562ace0d3fb5f20ed7290f1f929cae41b25ae29528f2af1722966a0a02e2aa1", size = 7225152, upload-time = "2026-04-01T14:45:50.032Z" }, + { url = "https://files.pythonhosted.org/packages/ff/6e/cf826fae916b8658848d7b9f38d88da6396895c676e8086fc0988073aaf8/pillow-12.2.0-cp314-cp314t-win_arm64.whl", hash = "sha256:aa88ccfe4e32d362816319ed727a004423aab09c5cea43c01a4b435643fa34eb", size = 2556579, upload-time = "2026-04-01T14:45:52.529Z" }, ] [[package]] name = "pluggy" version = "1.6.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/f9/e2/3e91f31a7d2b083fe6ef3fa267035b518369d9511ffab804f839851d2779/pluggy-1.6.0.tar.gz", hash = "sha256:7dcc130b76258d33b90f61b658791dede3486c3e6bfb003ee5c9bfb396dd22f3", size = 69412 } +sdist = { url = "https://files.pythonhosted.org/packages/f9/e2/3e91f31a7d2b083fe6ef3fa267035b518369d9511ffab804f839851d2779/pluggy-1.6.0.tar.gz", hash = "sha256:7dcc130b76258d33b90f61b658791dede3486c3e6bfb003ee5c9bfb396dd22f3", size = 69412, upload-time = "2025-05-15T12:30:07.975Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/54/20/4d324d65cc6d9205fabedc306948156824eb9f0ee1633355a8f7ec5c66bf/pluggy-1.6.0-py3-none-any.whl", hash = "sha256:e920276dd6813095e9377c0bc5566d94c932c33b27a3e3945d8389c374dd4746", size = 20538 }, + { url = "https://files.pythonhosted.org/packages/54/20/4d324d65cc6d9205fabedc306948156824eb9f0ee1633355a8f7ec5c66bf/pluggy-1.6.0-py3-none-any.whl", hash = "sha256:e920276dd6813095e9377c0bc5566d94c932c33b27a3e3945d8389c374dd4746", size = 20538, upload-time = "2025-05-15T12:30:06.134Z" }, +] + +[[package]] +name = "psutil" +version = "7.2.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/aa/c6/d1ddf4abb55e93cebc4f2ed8b5d6dbad109ecb8d63748dd2b20ab5e57ebe/psutil-7.2.2.tar.gz", hash = "sha256:0746f5f8d406af344fd547f1c8daa5f5c33dbc293bb8d6a16d80b4bb88f59372", size = 493740, upload-time = "2026-01-28T18:14:54.428Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/81/69/ef179ab5ca24f32acc1dac0c247fd6a13b501fd5534dbae0e05a1c48b66d/psutil-7.2.2-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:eed63d3b4d62449571547b60578c5b2c4bcccc5387148db46e0c2313dad0ee00", size = 130664, upload-time = "2026-01-28T18:15:09.469Z" }, + { url = "https://files.pythonhosted.org/packages/7b/64/665248b557a236d3fa9efc378d60d95ef56dd0a490c2cd37dafc7660d4a9/psutil-7.2.2-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:7b6d09433a10592ce39b13d7be5a54fbac1d1228ed29abc880fb23df7cb694c9", size = 131087, upload-time = "2026-01-28T18:15:11.724Z" }, + { url = "https://files.pythonhosted.org/packages/d5/2e/e6782744700d6759ebce3043dcfa661fb61e2fb752b91cdeae9af12c2178/psutil-7.2.2-cp314-cp314t-manylinux2010_x86_64.manylinux_2_12_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:1fa4ecf83bcdf6e6c8f4449aff98eefb5d0604bf88cb883d7da3d8d2d909546a", size = 182383, upload-time = "2026-01-28T18:15:13.445Z" }, + { url = "https://files.pythonhosted.org/packages/57/49/0a41cefd10cb7505cdc04dab3eacf24c0c2cb158a998b8c7b1d27ee2c1f5/psutil-7.2.2-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e452c464a02e7dc7822a05d25db4cde564444a67e58539a00f929c51eddda0cf", size = 185210, upload-time = "2026-01-28T18:15:16.002Z" }, + { url = "https://files.pythonhosted.org/packages/dd/2c/ff9bfb544f283ba5f83ba725a3c5fec6d6b10b8f27ac1dc641c473dc390d/psutil-7.2.2-cp314-cp314t-win_amd64.whl", hash = "sha256:c7663d4e37f13e884d13994247449e9f8f574bc4655d509c3b95e9ec9e2b9dc1", size = 141228, upload-time = "2026-01-28T18:15:18.385Z" }, + { url = "https://files.pythonhosted.org/packages/f2/fc/f8d9c31db14fcec13748d373e668bc3bed94d9077dbc17fb0eebc073233c/psutil-7.2.2-cp314-cp314t-win_arm64.whl", hash = "sha256:11fe5a4f613759764e79c65cf11ebdf26e33d6dd34336f8a337aa2996d71c841", size = 136284, upload-time = "2026-01-28T18:15:19.912Z" }, + { url = "https://files.pythonhosted.org/packages/e7/36/5ee6e05c9bd427237b11b3937ad82bb8ad2752d72c6969314590dd0c2f6e/psutil-7.2.2-cp36-abi3-macosx_10_9_x86_64.whl", hash = "sha256:ed0cace939114f62738d808fdcecd4c869222507e266e574799e9c0faa17d486", size = 129090, upload-time = "2026-01-28T18:15:22.168Z" }, + { url = "https://files.pythonhosted.org/packages/80/c4/f5af4c1ca8c1eeb2e92ccca14ce8effdeec651d5ab6053c589b074eda6e1/psutil-7.2.2-cp36-abi3-macosx_11_0_arm64.whl", hash = "sha256:1a7b04c10f32cc88ab39cbf606e117fd74721c831c98a27dc04578deb0c16979", size = 129859, upload-time = "2026-01-28T18:15:23.795Z" }, + { url = "https://files.pythonhosted.org/packages/b5/70/5d8df3b09e25bce090399cf48e452d25c935ab72dad19406c77f4e828045/psutil-7.2.2-cp36-abi3-manylinux2010_x86_64.manylinux_2_12_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:076a2d2f923fd4821644f5ba89f059523da90dc9014e85f8e45a5774ca5bc6f9", size = 155560, upload-time = "2026-01-28T18:15:25.976Z" }, + { url = "https://files.pythonhosted.org/packages/63/65/37648c0c158dc222aba51c089eb3bdfa238e621674dc42d48706e639204f/psutil-7.2.2-cp36-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b0726cecd84f9474419d67252add4ac0cd9811b04d61123054b9fb6f57df6e9e", size = 156997, upload-time = "2026-01-28T18:15:27.794Z" }, + { url = "https://files.pythonhosted.org/packages/8e/13/125093eadae863ce03c6ffdbae9929430d116a246ef69866dad94da3bfbc/psutil-7.2.2-cp36-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:fd04ef36b4a6d599bbdb225dd1d3f51e00105f6d48a28f006da7f9822f2606d8", size = 148972, upload-time = "2026-01-28T18:15:29.342Z" }, + { url = "https://files.pythonhosted.org/packages/04/78/0acd37ca84ce3ddffaa92ef0f571e073faa6d8ff1f0559ab1272188ea2be/psutil-7.2.2-cp36-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:b58fabe35e80b264a4e3bb23e6b96f9e45a3df7fb7eed419ac0e5947c61e47cc", size = 148266, upload-time = "2026-01-28T18:15:31.597Z" }, + { url = "https://files.pythonhosted.org/packages/b4/90/e2159492b5426be0c1fef7acba807a03511f97c5f86b3caeda6ad92351a7/psutil-7.2.2-cp37-abi3-win_amd64.whl", hash = "sha256:eb7e81434c8d223ec4a219b5fc1c47d0417b12be7ea866e24fb5ad6e84b3d988", size = 137737, upload-time = "2026-01-28T18:15:33.849Z" }, + { url = "https://files.pythonhosted.org/packages/8c/c7/7bb2e321574b10df20cbde462a94e2b71d05f9bbda251ef27d104668306a/psutil-7.2.2-cp37-abi3-win_arm64.whl", hash = "sha256:8c233660f575a5a89e6d4cb65d9f938126312bca76d8fe087b947b3a1aaac9ee", size = 134617, upload-time = "2026-01-28T18:15:36.514Z" }, ] [[package]] name = "pygments" version = "2.20.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/c3/b2/bc9c9196916376152d655522fdcebac55e66de6603a76a02bca1b6414f6c/pygments-2.20.0.tar.gz", hash = "sha256:6757cd03768053ff99f3039c1a36d6c0aa0b263438fcab17520b30a303a82b5f", size = 4955991 } +sdist = { url = "https://files.pythonhosted.org/packages/c3/b2/bc9c9196916376152d655522fdcebac55e66de6603a76a02bca1b6414f6c/pygments-2.20.0.tar.gz", hash = "sha256:6757cd03768053ff99f3039c1a36d6c0aa0b263438fcab17520b30a303a82b5f", size = 4955991, upload-time = "2026-03-29T13:29:33.898Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/f4/7e/a72dd26f3b0f4f2bf1dd8923c85f7ceb43172af56d63c7383eb62b332364/pygments-2.20.0-py3-none-any.whl", hash = "sha256:81a9e26dd42fd28a23a2d169d86d7ac03b46e2f8b59ed4698fb4785f946d0176", size = 1231151 }, + { url = "https://files.pythonhosted.org/packages/f4/7e/a72dd26f3b0f4f2bf1dd8923c85f7ceb43172af56d63c7383eb62b332364/pygments-2.20.0-py3-none-any.whl", hash = "sha256:81a9e26dd42fd28a23a2d169d86d7ac03b46e2f8b59ed4698fb4785f946d0176", size = 1231151, upload-time = "2026-03-29T13:29:30.038Z" }, +] + +[[package]] +name = "pyrekordbox" +version = "0.4.4" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "bidict" }, + { name = "construct" }, + { name = "numpy" }, + { name = "packaging" }, + { name = "psutil" }, + { name = "python-dateutil" }, + { name = "sqlalchemy" }, + { name = "sqlcipher3-wheels" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/39/8b/8fb7f21f4eb676df35c53214129ad33624634e2b678069abbf6a88509c6b/pyrekordbox-0.4.4.tar.gz", hash = "sha256:8bdb59f7c45c743bd2021613a72604b7607092d386e25f20cce78adfe30bf9a4", size = 2310256, upload-time = "2025-08-17T20:09:18.773Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b1/a8/28d28f02683bee40e3cfa32caed7c46e47daffb51482798fea9af0ab23cb/pyrekordbox-0.4.4-py3-none-any.whl", hash = "sha256:2de933870126b9a0ef04bea372f1c5934efcfbb2501efb6b47d8ace0e17fb3d8", size = 76000, upload-time = "2025-08-17T20:09:16.985Z" }, ] [[package]] @@ -133,7 +263,104 @@ dependencies = [ { name = "pluggy" }, { name = "pygments" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/7d/0d/549bd94f1a0a402dc8cf64563a117c0f3765662e2e668477624baeec44d5/pytest-9.0.3.tar.gz", hash = "sha256:b86ada508af81d19edeb213c681b1d48246c1a91d304c6c81a427674c17eb91c", size = 1572165 } +sdist = { url = "https://files.pythonhosted.org/packages/7d/0d/549bd94f1a0a402dc8cf64563a117c0f3765662e2e668477624baeec44d5/pytest-9.0.3.tar.gz", hash = "sha256:b86ada508af81d19edeb213c681b1d48246c1a91d304c6c81a427674c17eb91c", size = 1572165, upload-time = "2026-04-07T17:16:18.027Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/d4/24/a372aaf5c9b7208e7112038812994107bc65a84cd00e0354a88c2c77a617/pytest-9.0.3-py3-none-any.whl", hash = "sha256:2c5efc453d45394fdd706ade797c0a81091eccd1d6e4bccfcd476e2b8e0ab5d9", size = 375249 }, + { url = "https://files.pythonhosted.org/packages/d4/24/a372aaf5c9b7208e7112038812994107bc65a84cd00e0354a88c2c77a617/pytest-9.0.3-py3-none-any.whl", hash = "sha256:2c5efc453d45394fdd706ade797c0a81091eccd1d6e4bccfcd476e2b8e0ab5d9", size = 375249, upload-time = "2026-04-07T17:16:16.13Z" }, +] + +[[package]] +name = "python-dateutil" +version = "2.9.0.post0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "six" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/66/c0/0c8b6ad9f17a802ee498c46e004a0eb49bc148f2fd230864601a86dcf6db/python-dateutil-2.9.0.post0.tar.gz", hash = "sha256:37dd54208da7e1cd875388217d5e00ebd4179249f90fb72437e91a35459a0ad3", size = 342432, upload-time = "2024-03-01T18:36:20.211Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ec/57/56b9bcc3c9c6a792fcbaf139543cee77261f3651ca9da0c93f5c1221264b/python_dateutil-2.9.0.post0-py2.py3-none-any.whl", hash = "sha256:a8b2bc7bffae282281c8140a97d3aa9c14da0b136dfe83f850eea9a5f7470427", size = 229892, upload-time = "2024-03-01T18:36:18.57Z" }, +] + +[[package]] +name = "six" +version = "1.17.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/94/e7/b2c673351809dca68a0e064b6af791aa332cf192da575fd474ed7d6f16a2/six-1.17.0.tar.gz", hash = "sha256:ff70335d468e7eb6ec65b95b99d3a2836546063f63acc5171de367e834932a81", size = 34031, upload-time = "2024-12-04T17:35:28.174Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b7/ce/149a00dd41f10bc29e5921b496af8b574d8413afcd5e30dfa0ed46c2cc5e/six-1.17.0-py2.py3-none-any.whl", hash = "sha256:4721f391ed90541fddacab5acf947aa0d3dc7d27b2e1e8eda2be8970586c3274", size = 11050, upload-time = "2024-12-04T17:35:26.475Z" }, +] + +[[package]] +name = "sqlalchemy" +version = "2.0.50" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "greenlet", marker = "platform_machine == 'AMD64' or platform_machine == 'WIN32' or platform_machine == 'aarch64' or platform_machine == 'amd64' or platform_machine == 'ppc64le' or platform_machine == 'win32' or platform_machine == 'x86_64'" }, + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/57/da/6fbf010c8ebb347679d0d100b22fe9ba5e13fd04046c5df7280d2f0bf706/sqlalchemy-2.0.50.tar.gz", hash = "sha256:af5607d11ef90fd6a5c0549fe0045dce1663d427426bcfb506dcb5346a85a3b9", size = 9907424, upload-time = "2026-05-24T19:20:04.018Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/df/32/10ac51b4be7cdecd7e93d069251c86dfbf70b7adbd7c67b48ccea6c49e1c/sqlalchemy-2.0.50-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:c966932507a4d7d0a37314927dbfcd89720e3f37d2a1e3352e7ae7939fa8e8a0", size = 2158519, upload-time = "2026-05-24T19:27:56.472Z" }, + { url = "https://files.pythonhosted.org/packages/5a/76/e703d2f7681d7d66c4c891af3f07c7ccf4c76ad7f18351de035b5eda007a/sqlalchemy-2.0.50-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:faffef4bcc20a1892e65e155293d99d60855bbbc79250ab712819cfd56a8e6bb", size = 3282063, upload-time = "2026-05-24T20:09:38.57Z" }, + { url = "https://files.pythonhosted.org/packages/31/26/ef168b184a25701f9995e8fb7e503fafd7a99c1c77cda1bc1a26ea2ed486/sqlalchemy-2.0.50-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:6c206aec519a2e7bd08abbfb33436e325fd22c632d9c21a9047e376ce241646e", size = 3287069, upload-time = "2026-05-24T20:17:21.942Z" }, + { url = "https://files.pythonhosted.org/packages/c2/15/765acc2bc693bccc43ca4a95d5b69750da8aaf6db1b5c616536e087f8920/sqlalchemy-2.0.50-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:bef4ac756363227ef6402a75fee025a4bc690f92328e825868939b3b3a446a6d", size = 3230453, upload-time = "2026-05-24T20:09:40.398Z" }, + { url = "https://files.pythonhosted.org/packages/63/61/08e03c3adbf5db0087a0b6816746fec8f3032fb2f7fc899a9bb9b2a48ce4/sqlalchemy-2.0.50-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:96fbee6b19c19cd1556c8bf9419447cf2ec149ffcab7ab64348c23e54ef8547f", size = 3252413, upload-time = "2026-05-24T20:17:24.067Z" }, + { url = "https://files.pythonhosted.org/packages/03/0c/370a1f2db38436c615e10134c8a37de3688e74084792380695f3f5083860/sqlalchemy-2.0.50-cp314-cp314-win32.whl", hash = "sha256:8f00e3eb43ba30eb1b238ee03a8a62309486d1321eda3328bb611e0340033ad8", size = 2120063, upload-time = "2026-05-24T19:50:11.08Z" }, + { url = "https://files.pythonhosted.org/packages/7f/a0/fe92bb9817863bc13ba093bda931979a26cc2ca69f8e8f26d07add3d7c6f/sqlalchemy-2.0.50-cp314-cp314-win_amd64.whl", hash = "sha256:15708c613cd5005b7dffe1f66ee6a63ee8f5e46799f71c70ebad74178c676a39", size = 2145830, upload-time = "2026-05-24T19:50:12.452Z" }, + { url = "https://files.pythonhosted.org/packages/cc/ff/e5640a98a0b2f491eb8fde10fb6c773621a2e44340de231fafcc9370f4a9/sqlalchemy-2.0.50-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:3699dac4be410e97049a1658e9480da9cde956594aa0f3aebc60b88f21c5ba70", size = 2178435, upload-time = "2026-05-24T19:42:58.889Z" }, + { url = "https://files.pythonhosted.org/packages/b7/85/337116e186f1236375b5fb70c21cfac98e8e8ab0d3a47be838dc47a59e08/sqlalchemy-2.0.50-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f96233858e3df43932ac11589e22520da6e8aeb624b03fedfeebb0e8ea213086", size = 3566059, upload-time = "2026-05-24T20:01:20.848Z" }, + { url = "https://files.pythonhosted.org/packages/96/34/bb0e190e161c3c2c24314a65add57218be14a4a9486886b7f5047c1ff7c8/sqlalchemy-2.0.50-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c4e70c46fad30c3bcc6a4708bc0130a3173e11a5b25f0ea4a9d8911b450f1f52", size = 3535366, upload-time = "2026-05-24T20:03:56.768Z" }, + { url = "https://files.pythonhosted.org/packages/df/5a/a7f759f97e4fd499c5d4e4488c760d5a7fbecf3028b465a04274fcd52384/sqlalchemy-2.0.50-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:1918a3cf564d16d95bca7301005f41ab2ad50b07cd3b9da50d3ed986db148d6a", size = 3474879, upload-time = "2026-05-24T20:01:23.058Z" }, + { url = "https://files.pythonhosted.org/packages/9d/d9/2907ea38eb60687d297bf9c39e5ee58053c87b57fe8a9cae97090cecbf10/sqlalchemy-2.0.50-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:b00098cdbdbd38c7be3d568b0c9c3122b8c0ec62b911b57cd5e6e0254d60a76d", size = 3486117, upload-time = "2026-05-24T20:03:59.052Z" }, + { url = "https://files.pythonhosted.org/packages/f2/e3/5aa06f167559f8c0bdae487e297d23ba548150ab016a3418265d617a4985/sqlalchemy-2.0.50-cp314-cp314t-win32.whl", hash = "sha256:1fbd55a969d7ac44a98e3dec75016074f809fa08f871585ace58dde110d1bf3e", size = 2150823, upload-time = "2026-05-24T20:08:58.644Z" }, + { url = "https://files.pythonhosted.org/packages/65/9b/112fb8f977582d7489d036e409e3723948bcf5320b3ac465f3c481bbe8f9/sqlalchemy-2.0.50-cp314-cp314t-win_amd64.whl", hash = "sha256:c5c3cdb753a9004183e1ccb634b41611654c989e61bc68617ce878e46d6f1e51", size = 2185794, upload-time = "2026-05-24T20:09:00.319Z" }, + { url = "https://files.pythonhosted.org/packages/d0/10/f7220e9b784d295d241c86ed99aeb537f92afcd469a64861f2717e9bb077/sqlalchemy-2.0.50-py3-none-any.whl", hash = "sha256:92064363517a3ff8212b5a93b8c62876579d8dfd1ca5b561335f30152d884fa9", size = 1943861, upload-time = "2026-05-24T19:59:01.119Z" }, +] + +[[package]] +name = "sqlcipher3-wheels" +version = "0.5.7" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/61/ec/d79c2e5f5b9246471f217cdd5f703dc8d804bce557a047e1bfad4f33c376/sqlcipher3_wheels-0.5.7.tar.gz", hash = "sha256:5fd344abba62c487c4d857a6fb55cb8dff8a97d4c4fbb4f9394b473e8e315dee", size = 2669821, upload-time = "2026-01-07T09:53:25.48Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/1e/b5/8ca4fe7928d1e7151ecdf221ad757540e9a815ea10d854f047491859cd0c/sqlcipher3_wheels-0.5.7-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:6831fd121adf8374ecfb67b63d9bdfc3bc405e7103e35dd3152f7f2d2bbe87ae", size = 4946253, upload-time = "2026-01-07T09:51:46.013Z" }, + { url = "https://files.pythonhosted.org/packages/cd/39/5eca1cced6b3a78cb20201232103b58ded2d57e87c413212bfd2a30c7209/sqlcipher3_wheels-0.5.7-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:9d03491d9d4023cca969a4c87f01ea845368defcc52ea7cd71ce93deac3df286", size = 2898557, upload-time = "2026-01-07T09:51:47.261Z" }, + { url = "https://files.pythonhosted.org/packages/34/93/8c34dd286ff8bea8785896eee401a5e15ab37901687ba09d2bf0df584712/sqlcipher3_wheels-0.5.7-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:1f3325fee4698fadbfa9b8245f213f7f3ecc3f0c86a761a2b26c787d54d3ecd1", size = 3163380, upload-time = "2026-01-07T09:51:48.384Z" }, + { url = "https://files.pythonhosted.org/packages/6d/2e/5b62f3c4181a64ab041589c6fdb509a70d69c2653f91d35b19c7550ad0d3/sqlcipher3_wheels-0.5.7-cp314-cp314-manylinux_2_28_aarch64.whl", hash = "sha256:f80b8c72df20f8e2696e3b23577145e94b3f5a10217fef02d5c78d0ef2e578ba", size = 7259742, upload-time = "2026-01-07T09:51:49.698Z" }, + { url = "https://files.pythonhosted.org/packages/09/18/d78100264c66c96aec0723b2ddaf2b4edc72374e60c25b5ed8f3afb5e4f0/sqlcipher3_wheels-0.5.7-cp314-cp314-manylinux_2_28_i686.whl", hash = "sha256:9feb22952dd44a6e86bbb1e95a0a603bd7cc61332d0b1adad2484d2ccdc61704", size = 6875763, upload-time = "2026-01-07T09:51:51.009Z" }, + { url = "https://files.pythonhosted.org/packages/8b/e0/0529fc2d3cb0d58950557e2ac229d338506e9522a7d3eb03540acd4a6ee7/sqlcipher3_wheels-0.5.7-cp314-cp314-manylinux_2_28_ppc64le.whl", hash = "sha256:79fd014b64fefd85086cff6af85286cfb3b5761473f0a541f973a02a3c0e184b", size = 7413959, upload-time = "2026-01-07T09:51:52.344Z" }, + { url = "https://files.pythonhosted.org/packages/06/81/290107b3bd5a84edcc7a542ce1e7fe42e401cd94f4a8b6ab22e546ba034d/sqlcipher3_wheels-0.5.7-cp314-cp314-manylinux_2_28_s390x.whl", hash = "sha256:92462368d7afc2c396466eeb4cfed85dccc12bef06a5f3a9a43a6598890ddd15", size = 7389084, upload-time = "2026-01-07T09:51:53.786Z" }, + { url = "https://files.pythonhosted.org/packages/54/f1/9237d5d14807d1a6c7900330b55cad40e192f54dd4d5f732239a2271922d/sqlcipher3_wheels-0.5.7-cp314-cp314-manylinux_2_28_x86_64.whl", hash = "sha256:663f814ca785d064408ec96c7abdc6e940809efac34cdc1656fb3536cffb8ff5", size = 7039875, upload-time = "2026-01-07T09:51:55.236Z" }, + { url = "https://files.pythonhosted.org/packages/b9/46/ba150f6d1c38f0742055d5a693f5c3532a85c4aac3c8789d130ab8492dd9/sqlcipher3_wheels-0.5.7-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:47b5ae81361c0ca45fab27ce09f94ff795a7763767c9bcbbbce19923c82d2f65", size = 7252142, upload-time = "2026-01-07T09:51:56.581Z" }, + { url = "https://files.pythonhosted.org/packages/b0/d5/e493a4a19dedc2a9c25751bcc1a7eebc29a5cbc8859108b814ef20a8742e/sqlcipher3_wheels-0.5.7-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:869f77c3f8bfc94f05352b6ca42236adfb1b46f6c92082829a608cd561793a54", size = 6948368, upload-time = "2026-01-07T09:51:57.855Z" }, + { url = "https://files.pythonhosted.org/packages/6e/ac/e82f8e26d91859570c79e3c07d2de07c65001dde49b25cb60f058f694929/sqlcipher3_wheels-0.5.7-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:98f1b5f0f54316f105f553ec254961986c8dc617947f67daa11d49a19cbc9a58", size = 7385735, upload-time = "2026-01-07T09:51:59.874Z" }, + { url = "https://files.pythonhosted.org/packages/c2/2e/3152b89c2b5ce3f7c830b675451957349e4e0d2006d0d90354a50f5c8a01/sqlcipher3_wheels-0.5.7-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:e4ca2cd825be2c30e6530c20ba6112545aaa409ad77478fb66285f98f44aac78", size = 7027637, upload-time = "2026-01-07T09:52:03.298Z" }, + { url = "https://files.pythonhosted.org/packages/76/8b/a1b3e8044128ab6a6343ef181a658298b598d0a5a2973d33e7f8fa5e45c0/sqlcipher3_wheels-0.5.7-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:a823f48efe2927a781d28ce31a11263be0e836016ea7ef26e53da18efba2569b", size = 7061293, upload-time = "2026-01-07T09:52:04.997Z" }, + { url = "https://files.pythonhosted.org/packages/cb/2f/350f8ab4e061c588ad90e12dec59f609a7e4b51c7dc921b9c62eee8c020c/sqlcipher3_wheels-0.5.7-cp314-cp314-win32.whl", hash = "sha256:0f3a11b144c46a33cb34235b253a46b1b255968be34430f9bd7f9ed710283d41", size = 2033959, upload-time = "2026-01-07T09:52:06.307Z" }, + { url = "https://files.pythonhosted.org/packages/a0/f4/ecb143774ccf0c6c8b7c718b97ac5a69ef59aa57d89c2422aa564d4a9b2f/sqlcipher3_wheels-0.5.7-cp314-cp314-win_amd64.whl", hash = "sha256:f210fdbbb794cc1b3739c7bff879b8bcc5949567f5c222e251882ec076e69b72", size = 2560254, upload-time = "2026-01-07T09:52:07.614Z" }, + { url = "https://files.pythonhosted.org/packages/2e/bc/82cd7b7ecc0ad647d9641c9115a875ea6fe8bbc04b83bfd583ebf954260f/sqlcipher3_wheels-0.5.7-cp314-cp314-win_arm64.whl", hash = "sha256:30f825607048436ab6134450ddcc49ad05f17479640a830c0ea8d78f1700e6e0", size = 2727306, upload-time = "2026-01-07T09:52:08.83Z" }, + { url = "https://files.pythonhosted.org/packages/8e/06/dcad55d438eeb9b9b46349434a75ee98f2c42196077a63d7e263969195eb/sqlcipher3_wheels-0.5.7-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:b822211ea0d50500bc8dc82863fa570543e0f224359ccb46b7675a24c89983f5", size = 4948699, upload-time = "2026-01-07T09:52:10.513Z" }, + { url = "https://files.pythonhosted.org/packages/31/c2/66fcd2a7c3200323517e69c2ff49d48bb6aad15bc47d6f7d75710531a5c2/sqlcipher3_wheels-0.5.7-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:670f493bf5a9e70e86b0ae680a8bbf96aeaaf526c5018262186e6d7c52f021b1", size = 2900350, upload-time = "2026-01-07T09:52:11.766Z" }, + { url = "https://files.pythonhosted.org/packages/7b/3a/94bebb8260624be45531af2692e9f5329ee1163237f027a3716ce3f2ae39/sqlcipher3_wheels-0.5.7-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:8c4a6dd38e9c4eff2304ea0751e8c5612394f277e63f007ae85118d9ce49b427", size = 3167268, upload-time = "2026-01-07T09:52:13.036Z" }, + { url = "https://files.pythonhosted.org/packages/a1/04/00d4d7e8905d9b53cc3cf490223b151df9a794e10138692fd4822962408b/sqlcipher3_wheels-0.5.7-cp314-cp314t-manylinux_2_28_aarch64.whl", hash = "sha256:7438567a502bfde64ca8732d1466ee6801deb97856315ac06d168a68456b2783", size = 7301506, upload-time = "2026-01-07T09:52:14.366Z" }, + { url = "https://files.pythonhosted.org/packages/95/81/75e439c4f13cbf79b243de08394916450c812eb8e949b726a758089aaf3b/sqlcipher3_wheels-0.5.7-cp314-cp314t-manylinux_2_28_i686.whl", hash = "sha256:098112cdba65feefdbaba3eebbefe45b5481158882d21a7d133f475614a2fff0", size = 6915748, upload-time = "2026-01-07T09:52:16.166Z" }, + { url = "https://files.pythonhosted.org/packages/48/3c/36e5aa4fbfe25884d7c930215cc2003b4ac7709866992086179982d04bc5/sqlcipher3_wheels-0.5.7-cp314-cp314t-manylinux_2_28_ppc64le.whl", hash = "sha256:3f54e5284ebfeb2b3578ec35d3c4ad209ec1d4a3fd31b910608eadd95169cf83", size = 7453665, upload-time = "2026-01-07T09:52:18.415Z" }, + { url = "https://files.pythonhosted.org/packages/ec/62/9eddf7efdd523b3a109416f31185361608d1db60b550eb6e787a209765bc/sqlcipher3_wheels-0.5.7-cp314-cp314t-manylinux_2_28_s390x.whl", hash = "sha256:8d3bcfbb799abfa4e31e70e61c779e963b993748bf56bd0603007a47cb2a3c3f", size = 7431832, upload-time = "2026-01-07T09:52:22.041Z" }, + { url = "https://files.pythonhosted.org/packages/61/86/3e1f57a9e65d42f67f6e2e07b6c1eee14ef12caa08eedcbeebdba4389ba4/sqlcipher3_wheels-0.5.7-cp314-cp314t-manylinux_2_28_x86_64.whl", hash = "sha256:97617b4c838ad82877a342df111aecbd8653dd6c4c7dc3dccd1ef6389247d614", size = 7077205, upload-time = "2026-01-07T09:52:23.85Z" }, + { url = "https://files.pythonhosted.org/packages/98/0a/4c062770cb79860cf2cdced2b34887aa27e400fd261c553025b74ba0de74/sqlcipher3_wheels-0.5.7-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:b750c3d51134b82a4c82c9e5a6463de495ad75b7e073d03b1377083332b13ac8", size = 7296097, upload-time = "2026-01-07T09:52:25.622Z" }, + { url = "https://files.pythonhosted.org/packages/58/c7/6a6cec55e666ddd998c2c15e37a40e4fcab3c0b67c9d03c3d12b5f30d56a/sqlcipher3_wheels-0.5.7-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:dbebc62280b9fd005511189d887c080c7595226bb7242c11f1298a0b25a4c123", size = 6988446, upload-time = "2026-01-07T09:52:26.983Z" }, + { url = "https://files.pythonhosted.org/packages/fd/f4/ce2198d1296f63d0917fde374a20c09c5c5e454d65681bf8c2ee4b59591b/sqlcipher3_wheels-0.5.7-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:a806f240ede8da4547f3e50dc3ad88c0d244712270fe361a9b7e3f5d52b4bc39", size = 7425807, upload-time = "2026-01-07T09:52:28.416Z" }, + { url = "https://files.pythonhosted.org/packages/af/4b/d8735ffcf0b463bd54e23be7df46d77d40d43a672bc01a7a8da3e8f6daf3/sqlcipher3_wheels-0.5.7-cp314-cp314t-musllinux_1_2_s390x.whl", hash = "sha256:7198ee5b4c51aae6a7dfab93da35fe82a3c8ed37b4bf6cf55d6f1a0eea387d72", size = 7067479, upload-time = "2026-01-07T09:52:29.867Z" }, + { url = "https://files.pythonhosted.org/packages/97/54/663f4c349ae59a1a9b2180f2b06c9579ddc611649b50e2ad88c2e910582c/sqlcipher3_wheels-0.5.7-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:af83b12791a1221f1799d7ad6f79edb74a3be642326a9d780dd9f86a290f9c37", size = 7097843, upload-time = "2026-01-07T09:52:31.172Z" }, + { url = "https://files.pythonhosted.org/packages/f3/8e/f8737c593394f050a73742a81e47be2b46b6413f24e96fa0b10d01dc2555/sqlcipher3_wheels-0.5.7-cp314-cp314t-win32.whl", hash = "sha256:90994590f474975d6b480af96ae5ca3887e17303b1144bfea80a38c2e9fdc157", size = 2036431, upload-time = "2026-01-07T09:52:32.444Z" }, + { url = "https://files.pythonhosted.org/packages/2a/05/573df32b1b1738779dc5326b71d0d611d28d386e7141526ed27043d476a1/sqlcipher3_wheels-0.5.7-cp314-cp314t-win_amd64.whl", hash = "sha256:85422846b4c86e63a2b495a2a60945f7a1a04641d1600859cb65fc68a1ce53ed", size = 2564275, upload-time = "2026-01-07T09:52:33.574Z" }, + { url = "https://files.pythonhosted.org/packages/54/ee/40e79926f469d630bc1df03b131e8e84740b82dc7e202fe272d2da9110e3/sqlcipher3_wheels-0.5.7-cp314-cp314t-win_arm64.whl", hash = "sha256:aeeacd6da1fc293b97641a794d15d862d7928408ddad9ea5072fad99229970aa", size = 2728867, upload-time = "2026-01-07T09:52:34.718Z" }, +] + +[[package]] +name = "typing-extensions" +version = "4.15.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/72/94/1a15dd82efb362ac84269196e94cf00f187f7ed21c242792a923cdb1c61f/typing_extensions-4.15.0.tar.gz", hash = "sha256:0cea48d173cc12fa28ecabc3b837ea3cf6f38c6d1136f85cbaaf598984861466", size = 109391, upload-time = "2025-08-25T13:49:26.313Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/18/67/36e9267722cc04a6b9f15c7f3441c2363321a3ea07da7ae0c0707beb2a9c/typing_extensions-4.15.0-py3-none-any.whl", hash = "sha256:f0fa19c6845758ab08074a0cfa8b7aecb71c999ca73d62883bc25cc018c4e548", size = 44614, upload-time = "2025-08-25T13:49:24.86Z" }, ] diff --git a/maple.sublime-project b/maple.sublime-project index 04f2e6b..cb97cdb 100755 --- a/maple.sublime-project +++ b/maple.sublime-project @@ -5,10 +5,13 @@ "path": ".", }, { - "path": "/home/ntr/code/rekordcrate", + "path": "h:ome\\ntr\\code\\rekordcrate", }, { - "path": "/Users/nathanrashleigh/code/code/rekordcrate" + "path": "U:sers\\nathanrashleigh\\code\\code\\rekordcrate", + }, + { + "path": "C:\\Users\\ntr\\code\\rekordcrate" }, ], "settings":