hssp2/
create.rs

1use crate::FileWithSource;
2use acr::{
3    encryption::aes256cbc,
4    hash::{murmur3, sha256},
5};
6use dh::{recommended::*, Readable, Rw, Writable};
7use std::io::Result;
8
9pub fn create<'a>(
10    version: u8,
11    sources: Vec<FileWithSource<'a>>,
12    encryption: Option<(&str, &[u8; 16])>,
13    main_file: Option<u32>,
14    target: &'a mut dyn Rw<'a>,
15    buffer_size: u64,
16) -> Result<(u64, u32)> {
17    let encrypted = encryption.is_some();
18    let key = if encrypted {
19        sha256(
20            &mut dh::data::read_ref(encryption.unwrap().0.as_bytes()),
21            0,
22            encryption.unwrap().0.len() as u64,
23        )?
24    } else {
25        [0; 32]
26    };
27    let key_hash = if encrypted {
28        sha256(&mut dh::data::read_ref(&key), 0, 32)?
29    } else {
30        [0; 32]
31    };
32    let iv = if encrypted {
33        encryption.unwrap().1
34    } else {
35        &[0; 16]
36    };
37
38    target.write_bytes(if version == 1 { b"SFA\0" } else { b"HSSP" })?;
39    let hash_pos = target.pos()?;
40    target.write_u32le(0)?;
41    target.write_u32le(sources.len() as u32)?;
42    if encrypted {
43        target.write_bytes(&key_hash)?;
44        target.write_bytes(iv)?;
45    } else {
46        target.write_bytes(&[0; 48])?;
47    }
48    if main_file.is_some() {
49        let main_file = main_file.unwrap();
50        target.write_u32le(if main_file == (main_file % 4294967295) {
51            main_file + 1
52        } else {
53            0
54        })?;
55    } else {
56        target.write_u32le(0)?;
57    }
58
59    if version > 2 {
60        target.write_bytes(&[0; 64])?;
61    }
62
63    let body_pos = target.pos()?;
64
65    if encrypted {
66        let mut body = dh::data::rw_empty();
67        for source in sources {
68            let file = source.0;
69            let reader = source.1;
70            let path = if file.directory {
71                &(("//").to_string() + &file.path)
72            } else {
73                &file.path
74            };
75
76            body.write_u64le(file.length)?;
77            body.write_u16le(path.len() as u16)?;
78            body.write_utf8(path)?;
79            reader.copy_at(file.offset, file.length, &mut body, buffer_size)?;
80            body.write_bytes(&vec![0; path.len()])?;
81        }
82
83        let body_size = body.size()?;
84
85        let cipher = aes256cbc::encrypt(&mut body, &key, iv, 0, body_size)?;
86        target.write_bytes(&cipher)?;
87    } else {
88        for source in sources {
89            let file = source.0;
90            let reader = source.1;
91            let path = if file.directory {
92                &(("//").to_string() + &file.path)
93            } else {
94                &file.path
95            };
96
97            target.write_u64le(file.length)?;
98            target.write_u16le(path.len() as u16)?;
99            target.write_utf8(path)?;
100            reader.copy_at(
101                file.offset,
102                file.length,
103                Writable::as_trait(target),
104                buffer_size,
105            )?;
106            target.write_bytes(&vec![0; path.len()])?;
107        }
108    }
109
110    let body_size = target.pos()? - body_pos;
111
112    let hash = murmur3(Readable::as_trait(target), body_pos, body_size, 0x31082007)?;
113
114    Ok((hash_pos, hash))
115}
116
117// TODO: Implement this inside the create function
118pub fn write_hash(target: &mut dyn Writable, create_result: (u64, u32)) -> Result<()> {
119    target.write_u32le_at(create_result.0, create_result.1)
120}