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