1use std::fs::read;
5use std::io::Cursor;
6use std::path::Path;
7
8use crate::common_file_operations::{read_string, write_string};
9use crate::{ByteBuffer, ByteSpan};
10use binrw::binrw;
11use binrw::{BinRead, BinWrite};
12
13use crate::sha1::Sha1;
14
15#[binrw]
16#[brw(magic = b"FileInfo")]
17#[derive(Debug)]
18#[brw(little)]
19pub struct FileInfo {
21 #[brw(pad_before = 16)]
22 #[bw(calc = 1024)]
23 _unknown: i32,
24
25 #[br(temp)]
26 #[bw(calc = (entries.len() * 96) as i32)]
27 entries_size: i32,
28
29 #[brw(pad_before = 992)]
30 #[br(count = entries_size / 96)]
31 pub entries: Vec<FIINEntry>,
33}
34
35#[binrw]
36#[derive(Debug)]
37pub struct FIINEntry {
39 pub file_size: i32,
41
42 #[brw(pad_before = 4)]
44 #[br(count = 64)]
45 #[bw(pad_size_to = 64)]
46 #[bw(map = write_string)]
47 #[br(map = read_string)]
48 pub file_name: String,
49
50 #[br(count = 24)]
52 #[bw(pad_size_to = 24)]
53 pub sha1: Vec<u8>,
54}
55
56impl FileInfo {
57 pub fn from_existing(buffer: ByteSpan) -> Option<FileInfo> {
59 let mut cursor = Cursor::new(buffer);
60 FileInfo::read(&mut cursor).ok()
61 }
62
63 pub fn write_to_buffer(&self) -> Option<ByteBuffer> {
65 let mut buffer = ByteBuffer::new();
66
67 {
68 let mut cursor = Cursor::new(&mut buffer);
69 self.write(&mut cursor).ok()?;
70 }
71
72 Some(buffer)
73 }
74
75 pub fn new(files: &[&str]) -> Option<FileInfo> {
83 let mut entries = vec![];
84
85 for path in files {
86 let file = &read(path).expect("Cannot read file.");
87
88 entries.push(FIINEntry {
89 file_size: file.len() as i32,
90 file_name: Path::new(path).file_name()?.to_str()?.to_string(),
91 sha1: Sha1::from(file).digest().bytes().to_vec(),
92 });
93 }
94
95 Some(FileInfo { entries })
96 }
97}
98
99#[cfg(test)]
100mod tests {
101 use std::fs::read;
102 use std::path::PathBuf;
103
104 use crate::fiin::FileInfo;
105
106 fn common_setup() -> FileInfo {
107 let mut d = PathBuf::from(env!("CARGO_MANIFEST_DIR"));
108 d.push("resources/tests");
109 d.push("test.fiin");
110
111 FileInfo::from_existing(&read(d).unwrap()).unwrap()
112 }
113
114 #[test]
115 fn basic_parsing() {
116 let fiin = common_setup();
117
118 assert_eq!(fiin.entries[0].file_name, "test.txt");
119
120 assert_eq!(fiin.entries[1].file_name, "test.exl");
121 }
122
123 #[test]
124 fn basic_writing() {
125 let mut d = PathBuf::from(env!("CARGO_MANIFEST_DIR"));
126 d.push("resources/tests");
127 d.push("test.fiin");
128
129 let valid_fiin = &read(d).unwrap();
130
131 let mut d2 = PathBuf::from(env!("CARGO_MANIFEST_DIR"));
132 d2.push("resources/tests");
133 d2.push("test.txt");
134
135 let mut d3 = PathBuf::from(env!("CARGO_MANIFEST_DIR"));
136 d3.push("resources/tests");
137 d3.push("test.exl");
138
139 let testing_fiin = FileInfo::new(&[d2.to_str().unwrap(), d3.to_str().unwrap()]).unwrap();
140
141 assert_eq!(*valid_fiin, testing_fiin.write_to_buffer().unwrap());
142 }
143
144 #[test]
145 fn test_invalid() {
146 let mut d = PathBuf::from(env!("CARGO_MANIFEST_DIR"));
147 d.push("resources/tests");
148 d.push("random");
149
150 FileInfo::from_existing(&read(d).unwrap());
152 }
153}