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