1use std::io::{Cursor, Write};
4
5use rakata_core::encode_text;
6
7use super::{
8 binary::{write_u16, write_u32},
9 Erf, ErfBinaryError, ErfFileType, ErfWriteMode, ErfWriteOptions, ModLayout, ERF_TEXT_ENCODING,
10 ERF_VERSION_V10, FILE_HEADER_SIZE, KEY_ENTRY_SIZE, MOD_BLANK_BLOCK_ENTRY_SIZE,
11 RESOURCE_ENTRY_SIZE,
12};
13
14#[cfg_attr(
16 feature = "tracing",
17 tracing::instrument(level = "debug", skip(writer, erf))
18)]
19pub fn write_erf<W: Write>(writer: &mut W, erf: &Erf) -> Result<(), ErfBinaryError> {
20 write_erf_with_options(writer, erf, ErfWriteOptions::default())
21}
22
23#[cfg_attr(
25 feature = "tracing",
26 tracing::instrument(level = "debug", skip(writer, erf), fields(file_type = ?erf.file_type, mod_layout = ?options.mod_layout))
27)]
28pub fn write_erf_with_options<W: Write>(
29 writer: &mut W,
30 erf: &Erf,
31 options: ErfWriteOptions,
32) -> Result<(), ErfBinaryError> {
33 let entry_count = u32::try_from(erf.resources.len())
34 .map_err(|_| ErfBinaryError::ValueOverflow("entry_count"))?;
35
36 let mut localized_bytes = Vec::new();
37 for (index, entry) in erf.localized_strings.iter().enumerate() {
38 push_u32(&mut localized_bytes, entry.language_id.raw());
39 let encoded = encode_text(&entry.text, ERF_TEXT_ENCODING).map_err(|source| {
40 ErfBinaryError::TextEncoding {
41 context: format!("localized_strings[{index}]"),
42 source,
43 }
44 })?;
45 let encoded_len = u32::try_from(encoded.len())
46 .map_err(|_| ErfBinaryError::ValueOverflow("localized_string_len"))?;
47 push_u32(&mut localized_bytes, encoded_len);
48 localized_bytes.extend_from_slice(&encoded);
49 }
50 let language_count = u32::try_from(erf.localized_strings.len())
51 .map_err(|_| ErfBinaryError::ValueOverflow("language_count"))?;
52 let localized_size = u32::try_from(localized_bytes.len())
53 .map_err(|_| ErfBinaryError::ValueOverflow("localized_string_size"))?;
54
55 let localized_offset = u32::try_from(FILE_HEADER_SIZE)
56 .map_err(|_| ErfBinaryError::ValueOverflow("localized_offset"))?;
57 let keys_offset = localized_offset
58 .checked_add(localized_size)
59 .ok_or(ErfBinaryError::ValueOverflow("keys_offset"))?;
60 let mod_blank_block_size = if erf.file_type == ErfFileType::Mod
61 && options.mod_layout == ModLayout::WithBlankBlock
62 {
63 entry_count
64 .checked_mul(u32::try_from(MOD_BLANK_BLOCK_ENTRY_SIZE).expect("constant fits in u32"))
65 .ok_or(ErfBinaryError::ValueOverflow("mod_blank_block_size"))?
66 } else {
67 0
68 };
69 let resources_offset = keys_offset
70 .checked_add(
71 entry_count
72 .checked_mul(u32::try_from(KEY_ENTRY_SIZE).expect("constant fits in u32"))
73 .ok_or(ErfBinaryError::ValueOverflow("resources_offset"))?,
74 )
75 .and_then(|offset| offset.checked_add(mod_blank_block_size))
76 .ok_or(ErfBinaryError::ValueOverflow("resources_offset"))?;
77
78 let serialized_file_type = match options.output {
79 ErfWriteMode::CanonicalK1 if erf.file_type == ErfFileType::Sav => ErfFileType::Mod,
80 _ => erf.file_type,
81 };
82
83 writer.write_all(&serialized_file_type.fourcc())?;
84 writer.write_all(&ERF_VERSION_V10)?;
85 write_u32(writer, language_count)?;
86 write_u32(writer, localized_size)?;
87 write_u32(writer, entry_count)?;
88 write_u32(writer, localized_offset)?;
89 write_u32(writer, keys_offset)?;
90 write_u32(writer, resources_offset)?;
91 write_u32(writer, erf.build_year)?;
92 write_u32(writer, erf.build_day)?;
93 write_u32(
94 writer,
95 u32::from_le_bytes(erf.description_strref.raw().to_le_bytes()),
96 )?;
97 writer.write_all(&erf.reserved)?;
98
99 writer.write_all(&localized_bytes)?;
100
101 for (index, resource) in erf.resources.iter().enumerate() {
102 let resref_bytes = resource.resref.as_str().as_bytes();
105 let mut key_resref = [0_u8; 16];
106 key_resref[..resref_bytes.len()].copy_from_slice(resref_bytes);
107 writer.write_all(&key_resref)?;
108 write_u32(
109 writer,
110 u32::try_from(index).map_err(|_| ErfBinaryError::ValueOverflow("resource_id"))?,
111 )?;
112 write_u16(writer, resource.resource_type.raw_id())?;
113 write_u16(writer, 0)?;
114 }
115
116 if mod_blank_block_size > 0 {
117 writer.write_all(&vec![
118 0_u8;
119 usize::try_from(mod_blank_block_size).map_err(
120 |_| { ErfBinaryError::ValueOverflow("mod_blank_block_size") }
121 )?
122 ])?;
123 }
124
125 let mut next_data_offset = resources_offset
126 .checked_add(
127 entry_count
128 .checked_mul(u32::try_from(RESOURCE_ENTRY_SIZE).expect("constant fits in u32"))
129 .ok_or(ErfBinaryError::ValueOverflow("data_offset"))?,
130 )
131 .ok_or(ErfBinaryError::ValueOverflow("data_offset"))?;
132 for resource in &erf.resources {
133 write_u32(writer, next_data_offset)?;
134 let data_len = u32::try_from(resource.data.len())
135 .map_err(|_| ErfBinaryError::ValueOverflow("resource_size"))?;
136 write_u32(writer, data_len)?;
137 next_data_offset = next_data_offset
138 .checked_add(data_len)
139 .ok_or(ErfBinaryError::ValueOverflow("data_offset"))?;
140 }
141
142 for resource in &erf.resources {
143 writer.write_all(&resource.data)?;
144 }
145 crate::trace_debug!(
146 file_type = ?erf.file_type,
147 localized_string_count = erf.localized_strings.len(),
148 resource_count = erf.resources.len(),
149 "wrote erf-family archive to writer"
150 );
151 Ok(())
152}
153
154#[cfg_attr(
158 feature = "tracing",
159 tracing::instrument(level = "debug", skip(writer, erf))
160)]
161pub fn write_save_archive<W: Write>(writer: &mut W, erf: &Erf) -> Result<(), ErfBinaryError> {
162 let mut archive = erf.clone();
163 archive.file_type = ErfFileType::Mod;
164 write_erf(writer, &archive)
165}
166
167#[cfg_attr(feature = "tracing", tracing::instrument(level = "debug", skip(erf)))]
169pub fn write_erf_to_vec(erf: &Erf) -> Result<Vec<u8>, ErfBinaryError> {
170 write_erf_to_vec_with_options(erf, ErfWriteOptions::default())
171}
172
173#[cfg_attr(
175 feature = "tracing",
176 tracing::instrument(level = "debug", skip(erf), fields(file_type = ?erf.file_type, mod_layout = ?options.mod_layout))
177)]
178pub fn write_erf_to_vec_with_options(
179 erf: &Erf,
180 options: ErfWriteOptions,
181) -> Result<Vec<u8>, ErfBinaryError> {
182 let mut cursor = Cursor::new(Vec::new());
183 write_erf_with_options(&mut cursor, erf, options)?;
184 let bytes = cursor.into_inner();
185 crate::trace_debug!(
186 bytes_len = bytes.len(),
187 "serialized erf-family archive to vec"
188 );
189 Ok(bytes)
190}
191
192#[cfg_attr(feature = "tracing", tracing::instrument(level = "debug", skip(erf)))]
196pub fn write_save_archive_to_vec(erf: &Erf) -> Result<Vec<u8>, ErfBinaryError> {
197 let mut cursor = Cursor::new(Vec::new());
198 write_save_archive(&mut cursor, erf)?;
199 let bytes = cursor.into_inner();
200 crate::trace_debug!(bytes_len = bytes.len(), "serialized save archive to vec");
201 Ok(bytes)
202}
203
204fn push_u32(bytes: &mut Vec<u8>, value: u32) {
205 bytes.extend_from_slice(&value.to_le_bytes());
206}