Round 5: fix the gates added in round 4, and two placement holes
The zero-frame check ran before the seam gate, and its error is classified as a skippable nav stub — so a title the plan dropped ENTIRELY was reported as an empty stub and an all-titles rip would omit a real feature and finish the rest at exit 0. The seam case is decided first now, with a code that is not skippable. The demux sink read a frame's track kind out of the FILTERED slot, which is empty for a class the export drops. On an audio:// or sub:// export the video track was therefore called non-video and handed the permissive crossing rule — the same defect round 4 fixed for a Dolby Vision layer, reintroduced one file over. Video tracks are now recorded before the kind filter, beside the primary-video reference that exists for this reason. Its frame counter counted frames PLACED, not written, while its name and doc claimed otherwise. Renamed and documented for what it is, including that it cannot see a single lost track among many. Placement: files in a subdirectory of VIDEO_TS were never given data. They were declared at full size with no extents, so they appeared in the tree and read as nothing. The same folder under BDMV was always placed correctly. And the duplicate title-set guard keyed on the constraint maps, so an IFO declaring no offsets inserted nothing and a colliding second IFO went undetected — it keys on the groups seen now. Display for SeamPlanDroppedMost and ShortImageRead discarded their payloads, and four new variants were missing from the code-uniqueness test.
This commit is contained in:
+16
-1
@@ -403,6 +403,11 @@ fn place_video_ts(vts: &mut DirNode, start: u32) -> Result<u32> {
|
||||
// Required start blocks, resolved as each IFO is placed.
|
||||
let mut menu_req: std::collections::HashMap<u32, u32> = std::collections::HashMap::new();
|
||||
let mut title_req: std::collections::HashMap<u32, u32> = std::collections::HashMap::new();
|
||||
// Groups whose IFO has already been placed. Kept separately from the
|
||||
// constraint maps because an IFO that declares no offsets inserts into
|
||||
// neither, so keying the duplicate check on those maps would miss a second
|
||||
// IFO for the same set — which is the collision the check exists for.
|
||||
let mut seen_ifo: std::collections::HashSet<u32> = std::collections::HashSet::new();
|
||||
|
||||
let mut cursor = start;
|
||||
for &i in &order {
|
||||
@@ -438,7 +443,7 @@ fn place_video_ts(vts: &mut DirNode, start: u32) -> Result<u32> {
|
||||
// parse to the same group, and the second insert would overwrite
|
||||
// the first's constraint, placing a VOB at an address the IFO the
|
||||
// reader uses does not point to. Refuse instead of picking one.
|
||||
if menu_req.contains_key(&c.group) || title_req.contains_key(&c.group) {
|
||||
if !seen_ifo.insert(c.group) {
|
||||
return Err(Error::DirNameCollision {
|
||||
host: vts.files[i].disc_path.clone(),
|
||||
});
|
||||
@@ -639,6 +644,16 @@ pub(super) fn plan(root: &Path) -> Result<Layout> {
|
||||
.position(|d| d.name.eq_ignore_ascii_case("VIDEO_TS"))
|
||||
{
|
||||
cursor = place_video_ts(&mut tree.dirs[idx], cursor)?;
|
||||
// `place_video_ts` places only the FILES directly inside VIDEO_TS,
|
||||
// because only those carry the IFO-relative constraints. Anything in a
|
||||
// subdirectory of VIDEO_TS still needs data placed: without this its
|
||||
// File Entry is written with the file's real size but no extents, so it
|
||||
// appears in the tree at full length and reads back as nothing, at
|
||||
// exit 0. The same folder under BDMV/ was always placed correctly,
|
||||
// which is what made the gap easy to miss.
|
||||
for sub in tree.dirs[idx].dirs.iter_mut() {
|
||||
place_generic(sub, &mut cursor)?;
|
||||
}
|
||||
for f in &mut tree.files {
|
||||
cursor = place_file(f, cursor)?;
|
||||
}
|
||||
|
||||
@@ -447,6 +447,35 @@ fn an_oversized_vob_offset_leaves_a_gap_rather_than_failing() {
|
||||
assert!(buf.iter().all(|&b| b == 0));
|
||||
}
|
||||
|
||||
/// A file inside a SUBDIRECTORY of `VIDEO_TS` must still have its data placed.
|
||||
///
|
||||
/// Audit finding. The DVD branch places only the files directly inside
|
||||
/// `VIDEO_TS`, because only those carry the IFO-relative constraints — and the
|
||||
/// follow-up loop skipped that directory entirely, so anything one level deeper
|
||||
/// got a File Entry declaring the file's real size with no extents behind it.
|
||||
/// It appeared in the tree at full length and read back as nothing, at exit 0.
|
||||
/// The identical folder under `BDMV/` was always placed correctly, which is
|
||||
/// what made the gap easy to miss.
|
||||
#[test]
|
||||
fn a_file_below_video_ts_is_placed_not_just_declared() {
|
||||
let s = Scratch::new("dvdsubdir");
|
||||
s.file("VIDEO_TS/VIDEO_TS.IFO", &vec![0u8; SECTOR]);
|
||||
s.file("VIDEO_TS/VTS_01_0.IFO", &vts_ifo(SECTOR, 0, 2));
|
||||
s.file("VIDEO_TS/VTS_01_1.VOB", &pattern(1, SECTOR));
|
||||
let payload = pattern(7, SECTOR);
|
||||
s.file("VIDEO_TS/EXTRA/notes.bin", &payload);
|
||||
|
||||
let mut img = DirImage::open(s.path()).unwrap();
|
||||
let fs = udf::read_filesystem(&mut img).unwrap();
|
||||
let got = fs
|
||||
.read_file(&mut img, "/VIDEO_TS/EXTRA/notes.bin")
|
||||
.expect("the file must be readable");
|
||||
assert_eq!(
|
||||
got, payload,
|
||||
"a file below VIDEO_TS must read back as its real contents, not zeros"
|
||||
);
|
||||
}
|
||||
|
||||
/// A VOBS offset far past the content must be REFUSED, not honoured.
|
||||
///
|
||||
/// Audit finding. The planner honours a title set's declared VOBS offset
|
||||
|
||||
Reference in New Issue
Block a user