Skip to main content
← PSoXide SDK guides and examples · SDK f2fec1c301f1 · All Cargo features enabled; check feature gates before use.

psx_fmv/
iso.rs

1// SPDX-License-Identifier: GPL-2.0-or-later
2//! Minimal ISO9660 lookup: find a file in the disc's root directory.
3//!
4//! Enough for a movie player to open `MOVIE.STR` by name instead of by a
5//! baked LBA: read the primary volume descriptor (LBA 16), take the root
6//! directory extent from it, then scan that directory's records.
7
8/// LBA of the primary volume descriptor.
9pub const PVD_LBA: u32 = 16;
10
11/// Root directory `(lba, size_bytes)` from the primary volume descriptor.
12pub fn root_directory(pvd: &[u8]) -> Option<(u32, u32)> {
13    if pvd.len() < 190 || pvd[0] != 1 || &pvd[1..6] != b"CD001" {
14        return None;
15    }
16    let rec = &pvd[156..190];
17    Some((le32(&rec[2..6]), le32(&rec[10..14])))
18}
19
20/// Search one directory sector for `name` (compared without the `;1`
21/// version suffix, ASCII case-insensitive). Returns `(lba, size_bytes)`.
22pub fn find_in_directory(sector: &[u8], name: &str) -> Option<(u32, u32)> {
23    let mut at = 0;
24    while at + 33 <= sector.len() {
25        let len = sector[at] as usize;
26        if len == 0 {
27            break;
28        }
29        if at + len > sector.len() {
30            break;
31        }
32        let rec = &sector[at..at + len];
33        let name_len = rec[32] as usize;
34        if 33 + name_len <= len {
35            let raw = &rec[33..33 + name_len];
36            let base = match raw.iter().position(|&b| b == b';') {
37                Some(p) => &raw[..p],
38                None => raw,
39            };
40            if base.eq_ignore_ascii_case(name.as_bytes()) {
41                return Some((le32(&rec[2..6]), le32(&rec[10..14])));
42            }
43        }
44        at += len;
45    }
46    None
47}
48
49fn le32(b: &[u8]) -> u32 {
50    u32::from_le_bytes([b[0], b[1], b[2], b[3]])
51}
52
53#[cfg(test)]
54mod tests {
55    use super::*;
56
57    fn record(name: &[u8], lba: u32, size: u32) -> [u8; 48] {
58        let mut r = [0u8; 48];
59        let len = 33 + name.len() + (name.len() + 1) % 2;
60        r[0] = len as u8;
61        r[2..6].copy_from_slice(&lba.to_le_bytes());
62        r[10..14].copy_from_slice(&size.to_le_bytes());
63        r[32] = name.len() as u8;
64        r[33..33 + name.len()].copy_from_slice(name);
65        r
66    }
67
68    #[test]
69    fn finds_file_by_name() {
70        let mut dir = [0u8; 2048];
71        let mut at = 0;
72        for (name, lba, size) in [
73            (&b"\0"[..], 20, 2048),
74            (b"PSX.EXE;1", 22, 90000),
75            (b"MOVIE.STR;1", 70, 81920),
76        ] {
77            let r = record(name, lba, size);
78            let len = r[0] as usize;
79            dir[at..at + len].copy_from_slice(&r[..len]);
80            at += len;
81        }
82        assert_eq!(find_in_directory(&dir, "movie.str"), Some((70, 81920)));
83        assert_eq!(find_in_directory(&dir, "NOPE.STR"), None);
84    }
85
86    #[test]
87    fn reads_root_from_pvd() {
88        let mut pvd = [0u8; 2048];
89        pvd[0] = 1;
90        pvd[1..6].copy_from_slice(b"CD001");
91        pvd[156 + 2..156 + 6].copy_from_slice(&20u32.to_le_bytes());
92        pvd[156 + 10..156 + 14].copy_from_slice(&2048u32.to_le_bytes());
93        assert_eq!(root_directory(&pvd), Some((20, 2048)));
94    }
95}