egdata_manifests_parser/types/
file.rs1use byteorder::{LittleEndian, ReadBytesExt};
2use hex;
3use log::debug;
4use serde::{Deserialize, Serialize};
5use std::io::{Read, Seek, SeekFrom};
6
7use crate::error::Error;
8use crate::parser::reader::ReadExt;
9use crate::types::chunk::{ChunkDataList, ChunkPart};
10
11#[derive(Debug, Clone, Serialize, Deserialize, Default)]
12pub struct FileManifest {
13 #[serde(serialize_with = "trim_null_chars")]
14 pub filename: String,
15 pub symlink_target: String,
16 pub sha_hash: String,
17 pub file_meta_flags: u8,
18 #[serde(serialize_with = "vector_trim_null_chars")]
19 pub install_tags: Vec<String>,
20 pub chunk_parts: Vec<ChunkPart>,
21 pub file_size: i64,
22 #[serde(skip_serializing_if = "String::is_empty")]
23 pub mime_type: String,
24}
25
26#[derive(Debug, Clone, Serialize, Deserialize, Default)]
27pub struct FileManifestList {
28 pub data_size: u32,
29 pub data_version: u8,
30 pub count: u32,
31 pub file_manifest_list: Vec<FileManifest>,
32}
33
34fn trim_null_chars<S>(value: &String, serializer: S) -> Result<S::Ok, S::Error>
35where
36 S: serde::Serializer,
37{
38 let trimmed = value.trim_end_matches('\0');
39 serializer.serialize_str(trimmed)
40}
41
42fn vector_trim_null_chars<S>(value: &Vec<String>, serializer: S) -> Result<S::Ok, S::Error>
43where
44 S: serde::Serializer,
45{
46 let trimmed: Vec<String> = value
47 .iter()
48 .map(|s| s.trim_end_matches('\0').to_string())
49 .collect();
50 trimmed.serialize(serializer)
51}
52
53#[repr(u8)]
55pub enum EFileMetaFlags {
56 None = 0,
57 ReadOnly = 1 << 0,
58 Compressed = 1 << 1,
59 UnixExecutable = 1 << 2,
60}
61
62impl FileManifest {
63 pub fn is_readonly(&self) -> bool {
64 self.file_meta_flags & EFileMetaFlags::ReadOnly as u8 != 0
65 }
66
67 pub fn is_compressed(&self) -> bool {
68 self.file_meta_flags & EFileMetaFlags::Compressed as u8 != 0
69 }
70
71 pub fn is_unix_executable(&self) -> bool {
72 self.file_meta_flags & EFileMetaFlags::UnixExecutable as u8 != 0
73 }
74}
75
76impl FileManifestList {
77 pub fn read<R: Read + Seek>(rdr: &mut R, chunk_list: &ChunkDataList) -> Result<Self, Error> {
78 let start_pos = rdr.stream_position()?;
79 debug!(
80 "\nReading file list at position: {} (0x{:x})",
81 start_pos, start_pos
82 );
83
84 let data_size = rdr.read_u32::<LittleEndian>()?;
86 debug!(" Data size: {} (0x{:x})", data_size, data_size);
87
88 if data_size == 0 || data_size > 1024 * 1024 * 1024 {
90 return Err(Error::Invalid(format!(
92 "Invalid data size: {} (0x{:x}). Must be between 1 and 1GB",
93 data_size, data_size
94 )));
95 }
96
97 let data_version = rdr.read_u8()?;
99 debug!(" Data version: {} (0x{:x})", data_version, data_version);
100
101 if data_version > 2 {
103 return Err(Error::Invalid(format!(
104 "Invalid data version: {} (0x{:x}). Must be 0, 1, or 2",
105 data_version, data_version
106 )));
107 }
108
109 let count = rdr.read_u32::<LittleEndian>()?;
111 debug!(" Count: {} (0x{:x})", count, count);
112
113 if count > 1_000_000 {
115 return Err(Error::Invalid(format!(
117 "Invalid count: {} (0x{:x}). Must be less than 1,000,000",
118 count, count
119 )));
120 }
121
122 let mut files = Vec::with_capacity(count as usize);
124
125 debug!("\nReading filenames...");
127 for _ in 0..count {
128 let mut file = FileManifest::default();
129 file.filename = rdr.fstring()?;
130 files.push(file);
131 }
132
133 debug!("\nReading symlink targets...");
135 for i in 0..count {
136 files[i as usize].symlink_target = rdr.fstring()?;
137 }
138
139 debug!("\nReading file hashes...");
141 for i in 0..count {
142 let mut hash = [0u8; 20];
143 rdr.read_exact(&mut hash)?;
144 files[i as usize].sha_hash = hex::encode(hash);
145 }
146
147 debug!("\nReading file meta flags...");
149 for i in 0..count {
150 files[i as usize].file_meta_flags = rdr.read_u8()?;
151 }
152
153 debug!("\nReading install tags...");
155 for i in 0..count {
156 files[i as usize].install_tags = rdr.fstring_array()?;
157 }
158
159 debug!("\nReading chunk parts...");
161 let mut total_chunk_parts = 0;
162 let mut total_chunk_size = 0i64;
163 for i in 0..count {
164 let chunk_count = rdr.read_u32::<LittleEndian>()?;
165 let pos = rdr.stream_position()?;
166 debug!(
167 "File {}: Reading {} chunk parts at position {}",
168 i, chunk_count, pos
169 );
170
171 let remaining_bytes = data_size as u64 - (pos - start_pos);
173 let max_possible_chunks = remaining_bytes / 32; if chunk_count > max_possible_chunks as u32 {
175 debug!(
176 " Warning: Invalid chunk count ({}) for file {} at position {}, skipping.",
177 chunk_count, i, pos
178 );
179 files[i as usize].chunk_parts = Vec::new();
180 continue;
181 }
182
183 let mut chunks = Vec::with_capacity(chunk_count as usize);
185 let mut file_chunk_size = 0i64;
186 let mut valid_chunks = 0;
187
188 for j in 0..chunk_count {
189 let chunk_pos = rdr.stream_position()?;
190 match ChunkPart::read(rdr, &chunk_list.chunk_lookup, &chunk_list.elements) {
191 Ok(chunk) => {
192 file_chunk_size += chunk.size as i64;
193 chunks.push(chunk);
194 valid_chunks += 1;
195 if j == 0 || j == chunk_count - 1 {
196 debug!(
197 " Chunk part {}: size={}, offset={}, parent={} (at pos {})",
198 j,
199 chunks[j as usize].size,
200 chunks[j as usize].offset,
201 chunks[j as usize].parent_guid,
202 chunk_pos
203 );
204 }
205 }
206 Err(e) => {
207 debug!(
208 " Warning: Failed to read chunk part {} for file {}: {}. Skipping remaining chunks.",
209 j, i, e
210 );
211 break;
212 }
213 }
214 }
215
216 if valid_chunks > 0 {
217 total_chunk_parts += valid_chunks;
218 total_chunk_size += file_chunk_size;
219 files[i as usize].chunk_parts = chunks;
220 files[i as usize].file_size = file_chunk_size; } else {
222 debug!(
223 " Warning: No valid chunks found for file {}, skipping.",
224 i
225 );
226 files[i as usize].chunk_parts = Vec::new();
227 }
228 }
229
230 if data_version >= 2 {
232 debug!("\nReading version 2+ specific data...");
233 for _ in 0..count {
234 let array_size = rdr.read_u32::<LittleEndian>()?;
236 rdr.seek(SeekFrom::Current(array_size as i64 * 16))?;
237 }
238
239 for i in 0..count {
241 files[i as usize].mime_type = rdr.fstring()?;
242 }
243
244 for _ in 0..count {
246 rdr.seek(SeekFrom::Current(32))?;
247 }
248 }
249
250 debug!(
251 "Total chunk parts: {}, Total chunk size: {} bytes",
252 total_chunk_parts, total_chunk_size
253 );
254
255 let end_pos = rdr.stream_position()?;
256 let bytes_read = end_pos - start_pos;
257
258 if bytes_read != data_size as u64 {
260 debug!(
261 "Warning: Read {} bytes but expected {} bytes",
262 bytes_read, data_size
263 );
264 }
265
266 Ok(Self {
267 data_size,
268 data_version,
269 count,
270 file_manifest_list: files,
271 })
272 }
273}