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; the reference notes
13/// they are theoretically possible but seldom emitted by SE's current patch
14/// tooling. See `lib/FFXIVQuickLauncher/.../Chunk/AddDirectoryChunk.cs`.
15///
16/// # Wire format
17///
18/// ```text
19/// [name_len: u32 BE] [name: name_len bytes, NUL-padded]
20/// ```
21///
22/// `name_len` includes any trailing NUL bytes used for alignment padding.
23/// The parsed [`AddDirectory::name`] field has those NULs stripped.
24///
25/// # Errors
26///
27/// Parsing fails with [`crate::ZiPatchError::BinrwError`] if:
28/// - the body is too short to contain the `name_len` field or the declared
29/// number of name bytes (truncated input), or
30/// - the name bytes are not valid UTF-8.
31#[derive(BinRead, Debug, Clone, PartialEq, Eq)]
32#[br(big)]
33pub struct AddDirectory {
34 /// Directory path relative to the game install root.
35 ///
36 /// Encoded as UTF-8 on the wire, length-prefixed by a `u32` big-endian
37 /// byte count. Trailing NUL bytes used as alignment padding are stripped
38 /// before this field is populated. Example: `"sqpack/ffxiv"`.
39 #[br(parse_with = read_null_trimmed_utf8)]
40 pub name: String,
41}
42
43pub(crate) fn parse(body: &[u8]) -> crate::Result<AddDirectory> {
44 super::util::parse_be(body)
45}
46
47#[cfg(test)]
48mod tests {
49 use super::parse;
50
51 #[test]
52 fn parses_add_directory() {
53 let mut body = Vec::new();
54 body.extend_from_slice(&5u32.to_be_bytes());
55 body.extend_from_slice(b"sqex\0");
56 assert_eq!(parse(&body).unwrap().name, "sqex");
57 }
58
59 #[test]
60 fn null_padding_trimmed() {
61 let mut body = Vec::new();
62 body.extend_from_slice(&8u32.to_be_bytes());
63 body.extend_from_slice(b"ex\0\0\0\0\0\0");
64 assert_eq!(parse(&body).unwrap().name, "ex");
65 }
66}