sources: stateful one-key-at-a-time providers, UK-first keydb, shared resolve loop

Each source implements next_key (a cursor over its candidates) instead of
returning them all at once. The keydb hands its per-disc candidates out
UK-first (UK > VK > MK > DK) so a stale/wrong VUK never pre-empts a good UK in
the same entry; online and mapfile are one-shot. MultiSource composes sources
in the caller's chosen order and resolve_and_apply drives the
next_key -> decrypt_with loop, stopping at the first key that decrypts.
read_sample_units moves here so the CLI and autorip share one content sampler.
This commit is contained in:
MattJackson
2026-06-05 09:23:17 -07:00
parent e3f452166b
commit da5bd08d3f
4 changed files with 241 additions and 64 deletions
+16 -12
View File
@@ -12,31 +12,35 @@
use std::path::PathBuf;
use libfreemkv::disc::mapfile::Mapfile;
use libfreemkv::{DiscInputs, Key, KeySource, Result};
use libfreemkv::{DiscInputs, Key, KeySource};
/// A [`KeySource`] backed by a rip mapfile's persisted unit keys.
pub struct MapfileSource {
path: PathBuf,
/// The mapfile holds exactly one (terminal) UK set, so it is read once —
/// this flips true after the first `next_key`.
asked: bool,
}
impl MapfileSource {
/// A mapfile source reading the given `*.mapfile` path.
pub fn new(path: impl Into<PathBuf>) -> Self {
Self { path: path.into() }
Self {
path: path.into(),
asked: false,
}
}
}
impl KeySource for MapfileSource {
fn resolve(&self, _inputs: &DiscInputs) -> Result<Vec<Key>> {
// A missing/unreadable/keyless mapfile simply offers nothing.
let Ok(map) = Mapfile::load(&self.path) else {
return Ok(Vec::new());
};
let uks = map.unit_keys();
if uks.is_empty() {
Ok(Vec::new())
} else {
Ok(vec![Key::Unit(uks.to_vec())])
fn next_key(&mut self, _inputs: &DiscInputs) -> Option<Key> {
if self.asked {
return None;
}
self.asked = true;
// A missing/unreadable/keyless mapfile simply offers nothing.
let map = Mapfile::load(&self.path).ok()?;
let uks = map.unit_keys();
(!uks.is_empty()).then(|| Key::Unit(uks.to_vec()))
}
}