Skip to main content

zipatch_rs/chunk/
adir.rs

1use binrw::BinRead;
2
3use super::util::read_null_trimmed_utf8;
4
5/// `ADIR` chunk: create a directory under the game install root.
6///
7/// When applied, the patcher calls `create_dir_all` for
8/// `<game_root>/<name>`. The directory is created recursively, so intermediate
9/// path components are created as needed. If the directory already exists the
10/// apply step succeeds silently.
11///
12/// `ADIR` chunks appear rarely in modern FFXIV patch files; they are
13/// theoretically possible but seldom emitted by SE's current patch tooling.
14///
15/// # Wire format
16///
17/// ```text
18/// [name_len: u32 BE] [name: name_len bytes, NUL-padded]
19/// ```
20///
21/// `name_len` includes any trailing NUL bytes used for alignment padding.
22/// The parsed [`AddDirectory::name`] field has those NULs stripped.
23///
24/// # Errors
25///
26/// Parsing fails with [`crate::ParseError::Decode`] if:
27/// - the body is too short to contain the `name_len` field or the declared
28///   number of name bytes (truncated input), or
29/// - the name bytes are not valid UTF-8.
30#[derive(BinRead, Debug, Clone, PartialEq, Eq)]
31#[br(big)]
32pub struct AddDirectory {
33    /// Directory path relative to the game install root.
34    ///
35    /// Encoded as UTF-8 on the wire, length-prefixed by a `u32` big-endian
36    /// byte count. Trailing NUL bytes used as alignment padding are stripped
37    /// before this field is populated. Example: `"sqpack/ffxiv"`.
38    #[br(parse_with = read_null_trimmed_utf8)]
39    pub name: String,
40}
41
42pub(crate) fn parse(body: &[u8]) -> crate::ParseResult<AddDirectory> {
43    super::util::parse_be(body)
44}
45
46#[cfg(test)]
47mod tests {
48    use super::parse;
49
50    #[test]
51    fn parses_add_directory() {
52        let mut body = Vec::new();
53        body.extend_from_slice(&5u32.to_be_bytes());
54        body.extend_from_slice(b"sqex\0");
55        assert_eq!(parse(&body).unwrap().name, "sqex");
56    }
57
58    #[test]
59    fn null_padding_trimmed() {
60        let mut body = Vec::new();
61        body.extend_from_slice(&8u32.to_be_bytes());
62        body.extend_from_slice(b"ex\0\0\0\0\0\0");
63        assert_eq!(parse(&body).unwrap().name, "ex");
64    }
65}