use anyhow::{bail, Context, Result};
use std::path::{Path, PathBuf};
pub const FILES: [&str; 4] = [
"MYSETTING.DAT",
"MYSETTING2.DAT",
"DJMMYSETTING.DAT",
"DEVSETTING.DAT",
];
pub fn default_dirs() -> Vec<PathBuf> {
let mut dirs = Vec::new();
if let Some(home) = std::env::var_os("HOME") {
dirs.push(PathBuf::from(home).join("Library/Application Support/Pioneer/rekordbox6"));
}
if let Some(appdata) = std::env::var_os("APPDATA") {
dirs.push(PathBuf::from(appdata).join(r"Pioneer\rekordbox6"));
}
dirs
}
pub fn locate(explicit: Option<&Path>) -> std::result::Result<PathBuf, Vec<PathBuf>> {
let candidates: Vec<PathBuf> = match explicit {
Some(p) => vec![p.to_path_buf()],
None => default_dirs(),
};
candidates
.iter()
.find(|d| FILES.iter().all(|f| d.join(f).is_file()))
.cloned()
.ok_or(candidates)
}
fn crc16_xmodem(data: &[u8]) -> u16 {
let mut crc: u16 = 0;
for &b in data {
crc ^= (b as u16) << 8;
for _ in 0..8 {
crc = if crc & 0x8000 != 0 {
(crc << 1) ^ 0x1021
} else {
crc << 1
};
}
}
crc
}
pub fn validate(path: &Path) -> Result<()> {
let b = std::fs::read(path).with_context(|| format!("reading {}", path.display()))?;
let name = path
.file_name()
.map(|n| n.to_string_lossy().into_owned())
.unwrap_or_default();
if b.len() < 110 {
bail!("{name}: too short ({} bytes)", b.len());
}
let len_data = u32::from_le_bytes(b[100..104].try_into().unwrap()) as usize;
let expected = match name.as_str() {
"MYSETTING.DAT" | "MYSETTING2.DAT" => 40,
"DJMMYSETTING.DAT" => 52,
"DEVSETTING.DAT" => 32,
_ => len_data,
};
if len_data != expected || b.len() != 104 + len_data + 4 {
bail!(
"{name}: unexpected layout (len_data {len_data}, file {} bytes)",
b.len()
);
}
if !b[36..].starts_with(b"rekordbox\0")
&& !b[4..].starts_with(b"PIONEER")
&& !b[4..].starts_with(b"PioneerDJ")
{
bail!("{name}: header does not look like a rekordbox settings file");
}
let stored = u16::from_le_bytes([b[104 + len_data], b[105 + len_data]]);
let computed = if name == "DJMMYSETTING.DAT" {
crc16_xmodem(&b[..104 + len_data])
} else {
crc16_xmodem(&b[104..104 + len_data])
};
if stored != computed {
bail!("{name}: checksum mismatch (stored {stored:#06x}, computed {computed:#06x})");
}
Ok(())
}
pub fn copy_all(from: &Path, device: &Path) -> Result<()> {
let dest = device.join("PIONEER");
std::fs::create_dir_all(&dest)?;
for f in FILES {
let src = from.join(f);
validate(&src)?;
std::fs::copy(&src, dest.join(f)).with_context(|| format!("copying {f}"))?;
}
Ok(())
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn crc_reference() {
assert_eq!(crc16_xmodem(b"123456789"), 0x31C3);
}
#[test]
fn local_files_validate_when_present() {
let Ok(dir) = locate(None) else { return };
for f in FILES {
validate(&dir.join(f)).unwrap();
}
}
#[test]
fn fixture_devsetting_written_by_player_validates() {
let p = std::path::PathBuf::from(env!("CARGO_MANIFEST_DIR"))
.join("../../.claude/fixtures/JPHFAREKORD-20260918/PIONEER/DEVSETTING.DAT");
if p.exists() {
validate(&p).unwrap();
}
}
}