rakata_formats/bif/
writer.rs1use std::io::{Cursor, Write};
4
5#[cfg(feature = "bzf")]
6use lzma_rust2::{LzmaOptions, LzmaWriter};
7
8#[cfg(feature = "bzf")]
9use super::LZMA_ALONE_HEADER_SIZE;
10use super::{
11 binary::write_u32, Bif, BifBinaryError, BifContainer, BifResourceStorage, BIF_MAGIC,
12 BIF_VERSION_V10, FILE_HEADER_SIZE, FIXED_ENTRY_SIZE, VARIABLE_ENTRY_SIZE,
13};
14
15#[cfg_attr(
28 feature = "tracing",
29 tracing::instrument(level = "debug", skip(writer, bif))
30)]
31pub fn write_bif<W: Write>(writer: &mut W, bif: &Bif) -> Result<(), BifBinaryError> {
32 let result = match bif.container {
33 BifContainer::Biff => write_bif_impl(writer, bif, false),
34 BifContainer::Bzf => {
35 #[cfg(not(feature = "bzf"))]
36 {
37 Err(BifBinaryError::BzfFeatureDisabled)
38 }
39 #[cfg(feature = "bzf")]
40 {
41 write_bif_impl(writer, bif, true)
42 }
43 }
44 };
45 if result.is_ok() {
46 crate::trace_debug!(
47 container = ?bif.container,
48 resource_count = bif.resources.len(),
49 "wrote bif/bzf to writer"
50 );
51 }
52 result
53}
54
55#[cfg_attr(feature = "tracing", tracing::instrument(level = "debug", skip(bif), fields(container = ?bif.container)))]
62pub fn write_bif_to_vec(bif: &Bif) -> Result<Vec<u8>, BifBinaryError> {
63 let mut cursor = Cursor::new(Vec::new());
64 write_bif(&mut cursor, bif)?;
65 let bytes = cursor.into_inner();
66 crate::trace_debug!(
67 container = ?bif.container,
68 bytes_len = bytes.len(),
69 "serialized bif/bzf to vec"
70 );
71 Ok(bytes)
72}
73
74#[cfg_attr(
75 feature = "tracing",
76 tracing::instrument(level = "debug", skip(writer, bif), fields(as_bzf, resource_count = bif.resources.len()))
77)]
78fn write_bif_impl<W: Write>(writer: &mut W, bif: &Bif, as_bzf: bool) -> Result<(), BifBinaryError> {
79 #[cfg(not(feature = "bzf"))]
82 let _ = as_bzf;
83 let variable_resources: Vec<_> = bif
84 .resources
85 .iter()
86 .filter(|resource| matches!(resource.storage, BifResourceStorage::Variable))
87 .collect();
88 let fixed_resources: Vec<_> = bif
89 .resources
90 .iter()
91 .filter(|resource| matches!(resource.storage, BifResourceStorage::Fixed { .. }))
92 .collect();
93
94 let variable_count = u32::try_from(variable_resources.len())
95 .map_err(|_| BifBinaryError::ValueOverflow("variable_count"))?;
96 let fixed_count = u32::try_from(fixed_resources.len())
97 .map_err(|_| BifBinaryError::ValueOverflow("fixed_count"))?;
98 let variable_table_offset = u32::try_from(FILE_HEADER_SIZE)
99 .map_err(|_| BifBinaryError::ValueOverflow("variable_table_offset"))?;
100
101 let variable_table_size = variable_resources
102 .len()
103 .checked_mul(VARIABLE_ENTRY_SIZE)
104 .ok_or(BifBinaryError::ValueOverflow("variable_table_size"))?;
105 let fixed_table_size = fixed_resources
106 .len()
107 .checked_mul(FIXED_ENTRY_SIZE)
108 .ok_or(BifBinaryError::ValueOverflow("fixed_table_size"))?;
109
110 #[cfg(feature = "bzf")]
111 let variable_payloads = if as_bzf {
112 variable_resources
113 .iter()
114 .map(|resource| encode_bzf_payload(&resource.data))
115 .collect::<Result<Vec<_>, _>>()?
116 } else {
117 Vec::new()
118 };
119 #[cfg(feature = "bzf")]
120 let fixed_payloads = if as_bzf {
121 fixed_resources
122 .iter()
123 .map(|resource| encode_bzf_payload(&resource.data))
124 .collect::<Result<Vec<_>, _>>()?
125 } else {
126 Vec::new()
127 };
128
129 let mut variable_offsets = Vec::with_capacity(variable_resources.len());
130 let mut fixed_offsets = Vec::with_capacity(fixed_resources.len());
131 let mut next_data_offset = FILE_HEADER_SIZE
132 .checked_add(variable_table_size)
133 .and_then(|offset| offset.checked_add(fixed_table_size))
134 .ok_or(BifBinaryError::ValueOverflow("data_offset"))?;
135
136 #[cfg(feature = "bzf")]
137 for (index, resource) in variable_resources.iter().enumerate() {
138 let target = if let Some(source) = resource.source_data_offset {
139 usize::try_from(source).expect("source offset fits in usize")
140 } else {
141 align_to_four(next_data_offset)
142 .ok_or(BifBinaryError::ValueOverflow("data_offset_alignment"))?
143 };
144 variable_offsets.push(target);
145 let payload_len = if as_bzf {
146 variable_payloads[index].len()
147 } else {
148 resource.data.len()
149 };
150 next_data_offset = target
151 .checked_add(payload_len)
152 .ok_or(BifBinaryError::ValueOverflow("data_offset"))?;
153 }
154 #[cfg(not(feature = "bzf"))]
155 for resource in &variable_resources {
156 let target = if let Some(source) = resource.source_data_offset {
157 usize::try_from(source).expect("source offset fits in usize")
158 } else {
159 align_to_four(next_data_offset)
160 .ok_or(BifBinaryError::ValueOverflow("data_offset_alignment"))?
161 };
162 variable_offsets.push(target);
163 next_data_offset = target
164 .checked_add(resource.data.len())
165 .ok_or(BifBinaryError::ValueOverflow("data_offset"))?;
166 }
167
168 #[cfg(feature = "bzf")]
169 for (index, resource) in fixed_resources.iter().enumerate() {
170 let target = if let Some(source) = resource.source_data_offset {
171 usize::try_from(source).expect("source offset fits in usize")
172 } else {
173 align_to_four(next_data_offset)
174 .ok_or(BifBinaryError::ValueOverflow("data_offset_alignment"))?
175 };
176 fixed_offsets.push(target);
177 let payload_len = if as_bzf {
178 fixed_payloads[index].len()
179 } else {
180 resource.data.len()
181 };
182 next_data_offset = target
183 .checked_add(payload_len)
184 .ok_or(BifBinaryError::ValueOverflow("data_offset"))?;
185 }
186 #[cfg(not(feature = "bzf"))]
187 for resource in &fixed_resources {
188 let target = if let Some(source) = resource.source_data_offset {
189 usize::try_from(source).expect("source offset fits in usize")
190 } else {
191 align_to_four(next_data_offset)
192 .ok_or(BifBinaryError::ValueOverflow("data_offset_alignment"))?
193 };
194 fixed_offsets.push(target);
195 next_data_offset = target
196 .checked_add(resource.data.len())
197 .ok_or(BifBinaryError::ValueOverflow("data_offset"))?;
198 }
199
200 writer.write_all(&BIF_MAGIC)?;
203 writer.write_all(&BIF_VERSION_V10)?;
204 write_u32(writer, variable_count)?;
205 write_u32(writer, fixed_count)?;
206 write_u32(writer, variable_table_offset)?;
207
208 for (resource, data_offset) in variable_resources.iter().zip(variable_offsets.iter()) {
209 write_u32(writer, resource.resource_id.raw())?;
210 write_u32(
211 writer,
212 u32::try_from(*data_offset)
213 .map_err(|_| BifBinaryError::ValueOverflow("data_offset"))?,
214 )?;
215 write_u32(
216 writer,
217 u32::try_from(resource.data.len())
218 .map_err(|_| BifBinaryError::ValueOverflow("data_size"))?,
219 )?;
220 write_u32(writer, u32::from(resource.resource_type.raw_id()))?;
221 }
222
223 for (resource, data_offset) in fixed_resources.iter().zip(fixed_offsets.iter()) {
224 let part_count = match resource.storage {
225 BifResourceStorage::Fixed { part_count } => part_count,
226 BifResourceStorage::Variable => {
227 unreachable!("fixed resource vector must only contain fixed entries")
228 }
229 };
230 write_u32(writer, resource.resource_id.raw())?;
231 write_u32(
232 writer,
233 u32::try_from(*data_offset)
234 .map_err(|_| BifBinaryError::ValueOverflow("fixed_data_offset"))?,
235 )?;
236 write_u32(writer, part_count)?;
237 write_u32(
238 writer,
239 u32::try_from(resource.data.len())
240 .map_err(|_| BifBinaryError::ValueOverflow("fixed_data_size"))?,
241 )?;
242 write_u32(writer, u32::from(resource.resource_type.raw_id()))?;
243 }
244
245 let mut written_offset = FILE_HEADER_SIZE
246 .checked_add(variable_table_size)
247 .and_then(|offset| offset.checked_add(fixed_table_size))
248 .ok_or(BifBinaryError::ValueOverflow("written_offset"))?;
249 #[cfg(feature = "bzf")]
250 for (index, (resource, target_offset)) in variable_resources
251 .iter()
252 .zip(variable_offsets.iter())
253 .enumerate()
254 {
255 if written_offset < *target_offset {
256 let pad_len = target_offset
257 .checked_sub(written_offset)
258 .ok_or(BifBinaryError::ValueOverflow("padding"))?;
259 writer.write_all(&vec![0_u8; pad_len])?;
260 written_offset = *target_offset;
261 }
262
263 let payload = if as_bzf {
264 variable_payloads[index].as_slice()
265 } else {
266 resource.data.as_slice()
267 };
268
269 writer.write_all(payload)?;
270 written_offset = written_offset
271 .checked_add(payload.len())
272 .ok_or(BifBinaryError::ValueOverflow("written_offset"))?;
273 }
274 #[cfg(not(feature = "bzf"))]
275 for (resource, target_offset) in variable_resources.iter().zip(variable_offsets.iter()) {
276 if written_offset < *target_offset {
277 let pad_len = target_offset
278 .checked_sub(written_offset)
279 .ok_or(BifBinaryError::ValueOverflow("padding"))?;
280 writer.write_all(&vec![0_u8; pad_len])?;
281 written_offset = *target_offset;
282 }
283
284 let payload = resource.data.as_slice();
285
286 writer.write_all(payload)?;
287 written_offset = written_offset
288 .checked_add(payload.len())
289 .ok_or(BifBinaryError::ValueOverflow("written_offset"))?;
290 }
291
292 #[cfg(feature = "bzf")]
293 for (index, (resource, target_offset)) in
294 fixed_resources.iter().zip(fixed_offsets.iter()).enumerate()
295 {
296 if written_offset < *target_offset {
297 let pad_len = target_offset
298 .checked_sub(written_offset)
299 .ok_or(BifBinaryError::ValueOverflow("padding"))?;
300 writer.write_all(&vec![0_u8; pad_len])?;
301 written_offset = *target_offset;
302 }
303
304 let payload = if as_bzf {
305 fixed_payloads[index].as_slice()
306 } else {
307 resource.data.as_slice()
308 };
309
310 writer.write_all(payload)?;
311 written_offset = written_offset
312 .checked_add(payload.len())
313 .ok_or(BifBinaryError::ValueOverflow("written_offset"))?;
314 }
315 #[cfg(not(feature = "bzf"))]
316 for (resource, target_offset) in fixed_resources.iter().zip(fixed_offsets.iter()) {
317 if written_offset < *target_offset {
318 let pad_len = target_offset
319 .checked_sub(written_offset)
320 .ok_or(BifBinaryError::ValueOverflow("padding"))?;
321 writer.write_all(&vec![0_u8; pad_len])?;
322 written_offset = *target_offset;
323 }
324
325 let payload = resource.data.as_slice();
326
327 writer.write_all(payload)?;
328 written_offset = written_offset
329 .checked_add(payload.len())
330 .ok_or(BifBinaryError::ValueOverflow("written_offset"))?;
331 }
332
333 Ok(())
334}
335
336fn align_to_four(value: usize) -> Option<usize> {
337 let padding = (4 - (value % 4)) % 4;
338 value.checked_add(padding)
339}
340
341#[cfg(feature = "bzf")]
342fn encode_bzf_payload(payload: &[u8]) -> Result<Vec<u8>, BifBinaryError> {
353 let options = LzmaOptions::with_preset(6);
354 let literal_context = LzmaOptions::LC_DEFAULT;
355 let literal_position = LzmaOptions::LP_DEFAULT;
356 let position = LzmaOptions::PB_DEFAULT;
357 let dictionary_size = options.dict_size;
358
359 let properties = (position * 5 + literal_position) * 9 + literal_context;
361 let properties =
362 u8::try_from(properties).map_err(|_| BifBinaryError::ValueOverflow("lzma_properties"))?;
363
364 let mut out = Vec::with_capacity(payload.len() / 2 + LZMA_ALONE_HEADER_SIZE);
365 out.push(properties);
366 out.extend_from_slice(&dictionary_size.to_le_bytes());
367
368 let mut writer =
369 LzmaWriter::new_no_header(Cursor::new(Vec::new()), &options, true).map_err(|error| {
370 BifBinaryError::InvalidData(format!(
371 "failed to initialize the payload encoder: {error}"
372 ))
373 })?;
374 writer.write_all(payload).map_err(|error| {
375 BifBinaryError::InvalidData(format!("failed to encode a payload: {error}"))
376 })?;
377 let cursor = writer.finish().map_err(|error| {
378 BifBinaryError::InvalidData(format!("failed to finalize a payload: {error}"))
379 })?;
380 out.extend_from_slice(&cursor.into_inner());
381 Ok(out)
382}